KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > netbeans > modules > navigator > NavigatorController


1 /*
2  * The contents of this file are subject to the terms of the Common Development
3  * and Distribution License (the License). You may not use this file except in
4  * compliance with the License.
5  *
6  * You can obtain a copy of the License at http://www.netbeans.org/cddl.html
7  * or http://www.netbeans.org/cddl.txt.
8  *
9  * When distributing Covered Code, include this CDDL Header Notice in each file
10  * and include the License file at http://www.netbeans.org/cddl.txt.
11  * If applicable, add the following below the CDDL Header, with the fields
12  * enclosed by brackets [] replaced by your own identifying information:
13  * "Portions Copyrighted [year] [name of copyright owner]"
14  *
15  * The Original Software is NetBeans. The Initial Developer of the Original
16  * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
17  * Microsystems, Inc. All Rights Reserved.
18  */

19
20 package org.netbeans.modules.navigator;
21
22 import java.awt.Component JavaDoc;
23 import java.awt.event.ActionEvent JavaDoc;
24 import java.awt.event.ActionListener JavaDoc;
25 import java.awt.event.KeyEvent JavaDoc;
26 import java.beans.PropertyChangeEvent JavaDoc;
27 import java.beans.PropertyChangeListener JavaDoc;
28 import java.lang.ref.Reference JavaDoc;
29 import java.lang.ref.WeakReference JavaDoc;
30 import java.util.ArrayList JavaDoc;
31 import java.util.Collection JavaDoc;
32 import java.util.List JavaDoc;
33 import javax.swing.AbstractAction JavaDoc;
34 import javax.swing.FocusManager JavaDoc;
35 import javax.swing.JComboBox JavaDoc;
36 import javax.swing.JComponent JavaDoc;
37 import javax.swing.KeyStroke JavaDoc;
38 import javax.swing.SwingUtilities JavaDoc;
39 import org.netbeans.spi.navigator.NavigatorLookupHint;
40 import org.netbeans.spi.navigator.NavigatorPanel;
41 import org.openide.filesystems.FileObject;
42 import org.openide.loaders.DataShadow;
43 import org.openide.nodes.Node;
44 import org.openide.util.Lookup;
45 import org.openide.util.LookupEvent;
46 import org.openide.util.LookupListener;
47 import org.openide.util.NbBundle;
48 import org.openide.util.RequestProcessor;
49 import org.openide.util.Task;
50 import org.openide.util.TaskListener;
51 import org.openide.util.Utilities;
52 import org.openide.util.lookup.Lookups;
53 import org.openide.windows.TopComponent;
54 import org.openide.loaders.DataObject;
55 import org.openide.windows.WindowManager;
56
57 /**
58  * Listen to user action and handles navigator behaviour.
59  *
60  * @author Dafe Simonek
61  */

62 public final class NavigatorController implements LookupListener, ActionListener JavaDoc, Lookup.Provider, PropertyChangeListener JavaDoc {
63     
64     /** Time in ms to wait before propagating current node changes further
65      * into navigator UI */

66     /* package private for tests */
67     static final int COALESCE_TIME = 100;
68     
69     /** Asociation with navigator UI, which we control */
70     private NavigatorTC navigatorTC;
71     
72     /** holds currently scheduled/running task for set data context of selected node */
73     private RequestProcessor.Task nodeSetterTask;
74     private final Object JavaDoc NODE_SETTER_LOCK = new Object JavaDoc();
75     
76     /** template for finding current nodes in actions global context */
77     private static final Lookup.Template<Node> CUR_NODES =
78             new Lookup.Template<Node>(Node.class);
79     /** template for finding nav hints in actions global context */
80     private static final Lookup.Template<NavigatorLookupHint> CUR_HINTS =
81             new Lookup.Template<NavigatorLookupHint>(NavigatorLookupHint.class);
82     
83     /** current nodes (lookup result) to listen on when we are active */
84     private Lookup.Result<Node> curNodes;
85     /** current navigator hints (lookup result) to listen on when we are active */
86     private Lookup.Result<NavigatorLookupHint> curHints;
87     
88     /** current node to show content for */
89     private Node curNode;
90     /** Lookup that is passed to clients */
91     private final Lookup clientsLookup;
92     /** Lookup that wraps lookup of active panel */
93     private final Lookup panelLookup;
94     
95     /** A TopComponent which was active in winsys before navigator */
96     private Reference JavaDoc<TopComponent> lastActivatedRef;
97     
98     
99     /** Creates a new instance of NavigatorController */
100     public NavigatorController(NavigatorTC navigatorTC) {
101         this.navigatorTC = navigatorTC;
102         clientsLookup = Lookups.proxy(this);
103         panelLookup = Lookups.proxy(new PanelLookupWrapper());
104     }
105     
106     /** Starts listening to selected nodes and active component */
107     public void navigatorTCOpened() {
108         curNodes = Utilities.actionsGlobalContext().lookup(CUR_NODES);
109         curNodes.addLookupListener(this);
110         curHints = Utilities.actionsGlobalContext().lookup(CUR_HINTS);
111         curHints.addLookupListener(this);
112         navigatorTC.getPanelSelector().addActionListener(this);
113         TopComponent.getRegistry().addPropertyChangeListener(this);
114         
115         updateContext();
116     }
117     
118     /** Stops listening to selected nodes and active component */
119     public void navigatorTCClosed() {
120         curNodes.removeLookupListener(this);
121         curHints.removeLookupListener(this);
122         navigatorTC.getPanelSelector().removeActionListener(this);
123         TopComponent.getRegistry().removePropertyChangeListener(this);
124         curNodes = null;
125         curHints = null;
126         curNode = null;
127         lastActivatedRef = null;
128         navigatorTC.setPanels(null);
129     }
130     
131     /** Returns lookup that delegates to lookup of currently active
132      * navigator panel
133      */

134     public Lookup getPanelLookup () {
135         return panelLookup;
136     }
137
138     /** Reacts on user selecting some new Navigator panel in panel selector
139      * combo box - shows the panel user has selected.
140      */

141     public void actionPerformed (ActionEvent JavaDoc e) {
142         int index = navigatorTC.getPanelSelector().getSelectedIndex();
143         if (index == -1) {
144             // combo box cleared, nothing to activate
145
return;
146         }
147         NavigatorPanel newPanel = (NavigatorPanel)navigatorTC.getPanels().get(index);
148         activatePanel(newPanel);
149     }
150     
151     /** Activates given panel. Throws IllegalArgumentException if panel is
152      * not available for activation.
153      */

154     public void activatePanel (NavigatorPanel panel) {
155         if (!navigatorTC.getPanels().contains(panel)) {
156             throw new IllegalArgumentException JavaDoc("Panel is not available for activation: " + panel); //NOI18N
157
}
158         NavigatorPanel oldPanel = navigatorTC.getSelectedPanel();
159         if (!panel.equals(oldPanel)) {
160             if (oldPanel != null) {
161                 oldPanel.panelDeactivated();
162             }
163             panel.panelActivated(clientsLookup);
164             navigatorTC.setSelectedPanel(panel);
165         }
166     }
167     
168     /** Invokes navigator data context change upon current nodes change or
169      * current navigator hints change,
170      * performs coalescing of fast coming changes.
171      */

172     public void resultChanged(LookupEvent ev) {
173         if (!navigatorTC.equals(WindowManager.getDefault().getRegistry().getActivated())) {
174             ActNodeSetter nodeSetter = new ActNodeSetter();
175             synchronized (NODE_SETTER_LOCK) {
176                 if (nodeSetterTask != null) {
177                     nodeSetterTask.cancel();
178                 }
179                 // wait some time before propagating the change further
180
nodeSetterTask = RequestProcessor.getDefault().post(nodeSetter, COALESCE_TIME);
181                 nodeSetterTask.addTaskListener(nodeSetter);
182             }
183         }
184     }
185     
186     /** Returns first node of collection of nodes from active lookup context */
187     private Node obtainFirstCurNode () {
188         Collection JavaDoc nodeList = curNodes.allInstances();
189         return nodeList.isEmpty() ? null : (Node)nodeList.iterator().next();
190     }
191     
192     /** @return True when update show be performed, false otherwise. Update
193      * isn't needed when current nodes are null and no navigator lookup hints
194      * in lookup.
195      */

196     private boolean shouldUpdate () {
197         return TopComponent.getRegistry().getCurrentNodes() != null ||
198                Utilities.actionsGlobalContext().lookup(NavigatorLookupHint.class) != null;
199     }
200     
201     /** Important worker method, sets navigator content (available panels)
202      * according to providers found in current lookup context.
203      */

204     private void updateContext () {
205         // #80155: don't empty navigator for Properties window and similar
206
// which don't define activated nodes
207
Node node = obtainFirstCurNode();
208         if (node == null && !shouldUpdate()) {
209             return;
210         }
211         
212         // #63165: curNode has to be modified only in updateContext
213
// body, to prevent situation when curNode is null in getLookup
214
curNode = node;
215         
216         List JavaDoc<NavigatorPanel> providers = obtainProviders(node);
217         List JavaDoc oldProviders = navigatorTC.getPanels();
218
219         // set Navigator's active node to be the same as the content
220
// it is showing
221
navigatorTC.setActivatedNodes(node == null ? new Node[0] : new Node[] { node });
222
223         updateTCTitle(providers != null && !providers.isEmpty() ? node : null);
224         
225         // navigator remains empty, do nothing
226
if (oldProviders == null && providers == null) {
227             return;
228         }
229         
230         NavigatorPanel selPanel = navigatorTC.getSelectedPanel();
231         // don't call panelActivated/panelDeactivated if the same provider is
232
// still available, it's client's responsibility to listen to
233
// context changes while active
234
if (oldProviders != null && oldProviders.contains(selPanel) &&
235             providers != null && providers.contains(selPanel)) {
236             // trigger resultChanged() call on client side
237
clientsLookup.lookup(Node.class);
238             // #93123: refresh providers list if needed
239
if (!oldProviders.equals(providers)) {
240                 // we must disable combo-box listener to not receive unwanted events
241
// during combo box content change
242
navigatorTC.getPanelSelector().removeActionListener(this);
243                 navigatorTC.setPanels(providers);
244                 navigatorTC.setSelectedPanel(selPanel);
245                 navigatorTC.getPanelSelector().addActionListener(this);
246             }
247             return;
248         }
249         
250         if (selPanel != null) {
251             selPanel.panelDeactivated();
252         }
253         if (providers != null && providers.size() > 0) {
254             NavigatorPanel newSel = (NavigatorPanel)providers.get(0);
255             newSel.panelActivated(clientsLookup);
256         }
257         // we must disable combo-box listener to not receive unwanted events
258
// during combo box content change
259
navigatorTC.getPanelSelector().removeActionListener(this);
260         navigatorTC.setPanels(providers);
261         navigatorTC.getPanelSelector().addActionListener(this);
262     }
263
264     /** Sets navigator title according to active context */
265     private void updateTCTitle (Node node) {
266         String JavaDoc newTitle;
267         if (node != null) {
268             newTitle = NbBundle.getMessage(
269                     NavigatorTC.class, "FMT_Navigator", node.getDisplayName() //NOI18N
270
);
271         } else {
272             newTitle = NbBundle.getMessage(NavigatorTC.class, "LBL_Navigator"); //NOI18N
273
}
274         navigatorTC.setName(newTitle);
275     }
276     
277     /** Searches and return a list of providers which are suitable for given
278      * node context. Both Node lookup registered clients and xml layer registered
279      * clients are returned.
280      *
281      * @node Node context, may be also null.
282      */

283     /* package private for tests */ List JavaDoc<NavigatorPanel> obtainProviders (Node node) {
284         List JavaDoc<NavigatorPanel> result = null;
285         // search in global lookup first, they had preference
286
Collection JavaDoc<? extends NavigatorLookupHint> lkpHints =
287                 Utilities.actionsGlobalContext().lookupAll(NavigatorLookupHint.class);
288         for (NavigatorLookupHint curHint : lkpHints) {
289             Collection JavaDoc<? extends NavigatorPanel> providers = ProviderRegistry.getInstance().getProviders(curHint.getContentType());
290             if (providers != null && !providers.isEmpty()) {
291                 if (result == null) {
292                     result = new ArrayList JavaDoc<NavigatorPanel>(providers.size() * lkpHints.size());
293                 }
294                 result.addAll(providers);
295             }
296         }
297         
298         // search in declarative layers
299
if (node != null) {
300             DataObject dObj = (DataObject)node.getLookup().lookup(DataObject.class);
301             // #64871: Follow DataShadows to their original
302
while (dObj instanceof DataShadow) {
303                 dObj = ((DataShadow)dObj).getOriginal();
304             }
305             if (dObj != null) {
306                 FileObject fo = dObj.getPrimaryFile();
307                 // #65589: be no friend with virtual files
308
if (!fo.isVirtual()) {
309                 String JavaDoc contentType = fo.getMIMEType();
310                     Collection JavaDoc<? extends NavigatorPanel> providers = ProviderRegistry.getInstance().getProviders(contentType);
311                     if (providers != null && !providers.isEmpty()) {
312                         if (result == null) {
313                             result = new ArrayList JavaDoc<NavigatorPanel>(providers.size());
314                         }
315                         result.addAll(providers);
316                     }
317                 }
318             }
319         }
320         
321         return result;
322     }
323     
324     /** Impl of Lookup.Provider to provide Lookup suitable for clients of
325      * Navigator API. Delegates to lookup of current node.
326      *
327      * Public only due to impl reasons, please treate as private.
328      */

329     public Lookup getLookup () {
330         // #63165: null check must be here, because curNode may be null sometimes,
331
// and as this lookup is given to clients, this method can be called
332
// anytime, so we can't avoid the situation where curNode is null
333
if (curNode == null) {
334             return Lookup.EMPTY;
335         }
336         return curNode.getLookup();
337     }
338
339     /** Installs user actions handling for NavigatorTC top component */
340     public void installActions () {
341         // ESC key handling - return focus to previous focus owner
342
KeyStroke JavaDoc returnKey = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0, true);
343         //JComponent contentArea = navigatorTC.getContentArea();
344
navigatorTC.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(returnKey, "return"); //NOI18N
345
navigatorTC.getActionMap().put("return", new ESCHandler()); //NOI18N
346
}
347
348     /***** PropertyChangeListener implementation *******/
349     
350     /** Stores last TopComponent activated before NavigatorTC. Used to handle
351      * ESC key functionality */

352     public void propertyChange(PropertyChangeEvent JavaDoc evt) {
353         if (TopComponent.Registry.PROP_ACTIVATED.equals(evt.getPropertyName())) {
354             TopComponent tc = TopComponent.getRegistry().getActivated();
355             if (tc != null && tc != navigatorTC) {
356                 lastActivatedRef = new WeakReference JavaDoc<TopComponent>(tc);
357             }
358         }
359     }
360
361     /** Handles ESC key request - returns focus to previously focused top component
362      */

363     private class ESCHandler extends AbstractAction JavaDoc {
364         public void actionPerformed (ActionEvent JavaDoc evt) {
365             Component JavaDoc focusOwner = FocusManager.getCurrentManager().getFocusOwner();
366             // move focus away only from navigator AWT children,
367
// but not combo box to preserve its ESC functionality
368
if (lastActivatedRef == null ||
369                 focusOwner == null ||
370                 !SwingUtilities.isDescendingFrom(focusOwner, navigatorTC) ||
371                 focusOwner instanceof JComboBox JavaDoc) {
372                 return;
373             }
374             TopComponent prevFocusedTc = lastActivatedRef.get();
375             if (prevFocusedTc != null) {
376                 prevFocusedTc.requestActive();
377             }
378         }
379     } // end of ESCHandler
380

381     /** Lookup delegating to lookup of currently selected panel.
382      * If no panel is selected or panels' lookup is null, then acts as
383      * dummy empty lookup.
384      */

385     private class PanelLookupWrapper implements Lookup.Provider {
386         
387         public Lookup getLookup () {
388             NavigatorPanel selPanel = navigatorTC.getSelectedPanel();
389             if (selPanel != null) {
390                 Lookup panelLkp = selPanel.getLookup();
391                 if (panelLkp != null) {
392                     return panelLkp;
393                 }
394             }
395             return Lookup.EMPTY;
396         }
397         
398     } // end of PanelLookupWrapper
399

400     /** Task to set given node (as data context). Used to be able to coalesce
401      * data context changes if selected nodes changes too fast.
402      * Listens to own finish for cleanup */

403     private class ActNodeSetter implements Runnable JavaDoc, TaskListener {
404         
405         public void run() {
406             // technique to share one runnable impl between RP and Swing,
407
// to save one inner class
408
if (RequestProcessor.getDefault().isRequestProcessorThread()) {
409                 SwingUtilities.invokeLater(this);
410             } else {
411                 // AWT thread
412
// #67599: This task runs delayed, so it's possible that
413
// navigator was already closed, that's why the check
414
if (curNodes != null) {
415                     updateContext();
416                 }
417             }
418         }
419         
420         public void taskFinished(Task task) {
421             synchronized (NODE_SETTER_LOCK) {
422                 if (task == nodeSetterTask) {
423                     nodeSetterTask = null;
424                 }
425             }
426         }
427         
428     } // end of ActNodeSetter
429

430     
431 }
432
Popular Tags