001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041:
042: package org.netbeans.modules.navigator;
043:
044: import java.awt.Component;
045: import java.awt.EventQueue;
046: import java.awt.event.ActionEvent;
047: import java.awt.event.ActionListener;
048: import java.awt.event.KeyEvent;
049: import java.beans.PropertyChangeEvent;
050: import java.beans.PropertyChangeListener;
051: import java.lang.ref.Reference;
052: import java.lang.ref.WeakReference;
053: import java.util.ArrayList;
054: import java.util.Collection;
055: import java.util.Collections;
056: import java.util.Iterator;
057: import java.util.List;
058: import java.util.logging.Logger;
059: import javax.swing.AbstractAction;
060: import javax.swing.FocusManager;
061: import javax.swing.JComboBox;
062: import javax.swing.JComponent;
063: import javax.swing.KeyStroke;
064: import javax.swing.SwingUtilities;
065: import javax.swing.text.StyledEditorKit.ForegroundAction;
066: import org.netbeans.spi.navigator.NavigatorLookupHint;
067: import org.netbeans.spi.navigator.NavigatorLookupPanelsPolicy;
068: import org.netbeans.spi.navigator.NavigatorPanel;
069: import org.netbeans.spi.navigator.NavigatorPanelWithUndo;
070: import org.openide.awt.UndoRedo;
071: import org.openide.filesystems.FileObject;
072: import org.openide.loaders.DataShadow;
073: import org.openide.nodes.Node;
074: import org.openide.nodes.NodeEvent;
075: import org.openide.nodes.NodeMemberEvent;
076: import org.openide.nodes.NodeReorderEvent;
077: import org.openide.util.Lookup;
078: import org.openide.util.Lookup.Template;
079: import org.openide.util.LookupEvent;
080: import org.openide.util.LookupListener;
081: import org.openide.util.NbBundle;
082: import org.openide.util.RequestProcessor;
083: import org.openide.util.Task;
084: import org.openide.util.TaskListener;
085: import org.openide.util.Utilities;
086: import org.openide.util.lookup.Lookups;
087: import org.openide.windows.TopComponent;
088: import org.openide.loaders.DataObject;
089: import org.openide.nodes.NodeListener;
090: import org.openide.util.WeakListeners;
091: import org.openide.util.lookup.ProxyLookup;
092: import org.openide.windows.WindowManager;
093:
094: /**
095: * Listen to user action and handles navigator behaviour.
096: *
097: * @author Dafe Simonek
098: */
099: public final class NavigatorController implements LookupListener,
100: ActionListener, PropertyChangeListener, NodeListener, Runnable {
101:
102: /** Time in ms to wait before propagating current node changes further
103: * into navigator UI */
104: /* package private for tests */
105: static final int COALESCE_TIME = 100;
106:
107: /** Asociation with navigator UI, which we control */
108: private NavigatorTC navigatorTC;
109:
110: /** holds currently scheduled/running task for set data context of selected node */
111: private RequestProcessor.Task nodeSetterTask;
112: private final Object NODE_SETTER_LOCK = new Object();
113:
114: private final Object CUR_NODES_LOCK = new Object();
115:
116: /** template for finding current nodes in actions global context */
117: private static final Lookup.Template<Node> CUR_NODES = new Lookup.Template<Node>(
118: Node.class);
119: /** template for finding nav hints in actions global context */
120: private static final Lookup.Template<NavigatorLookupHint> CUR_HINTS = new Lookup.Template<NavigatorLookupHint>(
121: NavigatorLookupHint.class);
122:
123: /** current nodes (lookup result) to listen on when we are active */
124: private Lookup.Result<Node> curNodesRes;
125: /** current navigator hints (lookup result) to listen on when we are active */
126: private Lookup.Result<NavigatorLookupHint> curHintsRes;
127:
128: /** current nodes to show content for */
129: private Collection<? extends Node> curNodes = Collections
130: .emptyList();
131: /** Lookup that is passed to clients */
132: private final ClientsLookup clientsLookup;
133: /** Lookup that wraps lookup of active panel */
134: private final Lookup panelLookup;
135: /** Lookup result that track nodes (for activated nodes propagation) */
136: private Lookup.Result<Node> panelLookupNodesResult;
137: /** Listener for panel lookup content changes */
138: private final LookupListener panelLookupListener;
139:
140: /** A TopComponent which was active in winsys before navigator */
141: private Reference<TopComponent> lastActivatedRef;
142:
143: /** Listen to possible destroy of asociated curNodes */
144: private List<NodeListener> weakNodesL = Collections.emptyList();
145:
146: /** boolean flag to indicate whether updateContext is currently running */
147: private boolean inUpdate;
148:
149: private static final Logger LOG = Logger
150: .getLogger(NavigatorController.class.getName());
151:
152: /** Creates a new instance of NavigatorController */
153: public NavigatorController(NavigatorTC navigatorTC) {
154: this .navigatorTC = navigatorTC;
155: clientsLookup = new ClientsLookup();
156: panelLookup = Lookups.proxy(new PanelLookupWrapper());
157: panelLookupListener = new PanelLookupListener();
158: }
159:
160: /** Starts listening to selected nodes and active component */
161: public void navigatorTCOpened() {
162: LOG.fine("Entering navigatorTCOpened");
163: curNodesRes = Utilities.actionsGlobalContext()
164: .lookup(CUR_NODES);
165: curNodesRes.addLookupListener(this );
166: curHintsRes = Utilities.actionsGlobalContext()
167: .lookup(CUR_HINTS);
168: curHintsRes.addLookupListener(this );
169: navigatorTC.getPanelSelector().addActionListener(this );
170: TopComponent.getRegistry().addPropertyChangeListener(this );
171: panelLookupNodesResult = panelLookup.lookup(CUR_NODES);
172: panelLookupNodesResult.addLookupListener(panelLookupListener);
173:
174: updateContext();
175: }
176:
177: /** Stops listening to selected nodes and active component */
178: public void navigatorTCClosed() {
179: LOG.fine("Entering navigatorTCClosed");
180: curNodesRes.removeLookupListener(this );
181: curHintsRes.removeLookupListener(this );
182: navigatorTC.getPanelSelector().removeActionListener(this );
183: TopComponent.getRegistry().removePropertyChangeListener(this );
184: panelLookupNodesResult
185: .removeLookupListener(panelLookupListener);
186: curNodesRes = null;
187: curHintsRes = null;
188: synchronized (CUR_NODES_LOCK) {
189: curNodes = Collections.emptyList();
190: }
191: weakNodesL = Collections.emptyList();
192: // #113764: mem leak fix - update lookup - force ClientsLookup to free its delegates
193: clientsLookup.lookup(Object.class);
194: // #104145: panelDeactivated called if needed
195: NavigatorPanel selPanel = navigatorTC.getSelectedPanel();
196: if (selPanel != null) {
197: selPanel.panelDeactivated();
198: }
199: lastActivatedRef = null;
200: navigatorTC.setPanels(null);
201: panelLookupNodesResult = null;
202: LOG.fine("navigatorTCClosed: activated nodes: "
203: + navigatorTC.getActivatedNodes());
204: if (navigatorTC.getActivatedNodes() != null) {
205: LOG.fine("navigatorTCClosed: clearing act nodes...");
206: navigatorTC.setActivatedNodes(new Node[0]);
207: }
208: }
209:
210: /** Returns lookup that delegates to lookup of currently active
211: * navigator panel
212: */
213: public Lookup getPanelLookup() {
214: return panelLookup;
215: }
216:
217: /** Reacts on user selecting some new Navigator panel in panel selector
218: * combo box - shows the panel user has selected.
219: */
220: public void actionPerformed(ActionEvent e) {
221: int index = navigatorTC.getPanelSelector().getSelectedIndex();
222: if (index == -1) {
223: // combo box cleared, nothing to activate
224: return;
225: }
226: NavigatorPanel newPanel = navigatorTC.getPanels().get(index);
227: activatePanel(newPanel);
228: }
229:
230: /** Activates given panel. Throws IllegalArgumentException if panel is
231: * not available for activation.
232: */
233: public void activatePanel(NavigatorPanel panel) {
234: if (!navigatorTC.getPanels().contains(panel)) {
235: throw new IllegalArgumentException(
236: "Panel is not available for activation: " + panel); //NOI18N
237: }
238: NavigatorPanel oldPanel = navigatorTC.getSelectedPanel();
239: if (!panel.equals(oldPanel)) {
240: if (oldPanel != null) {
241: oldPanel.panelDeactivated();
242: }
243: panel.panelActivated(clientsLookup);
244: navigatorTC.setSelectedPanel(panel);
245: }
246: }
247:
248: /** Invokes navigator data context change upon current nodes change or
249: * current navigator hints change,
250: * performs coalescing of fast coming changes.
251: */
252: public void resultChanged(LookupEvent ev) {
253: if (!navigatorTC.equals(WindowManager.getDefault()
254: .getRegistry().getActivated())
255: // #117089: allow node change when we are empty
256: || (curNodes == null || curNodes.isEmpty())) {
257: ActNodeSetter nodeSetter = new ActNodeSetter();
258: synchronized (NODE_SETTER_LOCK) {
259: if (nodeSetterTask != null) {
260: nodeSetterTask.cancel();
261: }
262: // wait some time before propagating the change further
263: nodeSetterTask = RequestProcessor.getDefault().post(
264: nodeSetter, COALESCE_TIME);
265: nodeSetterTask.addTaskListener(nodeSetter);
266: }
267: }
268: }
269:
270: /** @return True when update show be performed, false otherwise. Update
271: * isn't needed when current nodes are null and no navigator lookup hints
272: * in lookup.
273: */
274: private boolean shouldUpdate() {
275: return TopComponent.getRegistry().getCurrentNodes() != null
276: || Utilities.actionsGlobalContext().lookup(
277: NavigatorLookupHint.class) != null;
278: }
279:
280: private void updateContext() {
281: updateContext(false);
282: }
283:
284: /** Important worker method, sets navigator content (available panels)
285: * according to providers found in current lookup context.
286: *
287: * @force if true that update is forced even if it means clearing navigator content
288: */
289: private void updateContext(boolean force) {
290: LOG.fine("updateContext entered, force: " + force);
291: // #105327: don't allow reentrancy, may happen due to listening to node changes
292: if (inUpdate) {
293: LOG.fine("Exit because inUpdate already, force: " + force);
294: return;
295: }
296: inUpdate = true;
297:
298: // #67599,108066: Some updates runs delayed, so it's possible that
299: // navigator was already closed, that's why the check
300: if (curNodesRes == null) {
301: inUpdate = false;
302: LOG.fine("Exit because curNodesRes is null, force: "
303: + force);
304: return;
305: }
306:
307: // #80155: don't empty navigator for Properties window and similar
308: // which don't define activated nodes
309: Collection<? extends Node> nodes = curNodesRes.allInstances();
310: if (nodes.isEmpty() && !shouldUpdate() && !force) {
311: inUpdate = false;
312: LOG.fine("Exit because act nodes empty, force: " + force);
313: return;
314: }
315:
316: synchronized (CUR_NODES_LOCK) {
317: // detach node listeners
318: Iterator<? extends NodeListener> curL = weakNodesL
319: .iterator();
320: for (Iterator<? extends Node> curNode = curNodes.iterator(); curNode
321: .hasNext();) {
322: curNode.next().removeNodeListener(curL.next());
323: }
324: weakNodesL = new ArrayList<NodeListener>(nodes.size());
325:
326: // #63165: curNode has to be modified only in updateContext
327: // body, to prevent situation when curNode is null in getLookup
328: curNodes = nodes;
329: LOG.fine("new CurNodes size " + curNodes.size());
330:
331: // #104229: listen to node destroy and update navigator correctly
332: NodeListener weakNodeL = null;
333: for (Node curNode : curNodes) {
334: weakNodeL = WeakListeners.create(NodeListener.class,
335: this , curNode);
336: weakNodesL.add(weakNodeL);
337: curNode.addNodeListener(weakNodeL);
338: }
339: }
340:
341: List<NavigatorPanel> providers = obtainProviders(nodes);
342: List oldProviders = navigatorTC.getPanels();
343:
344: final boolean areNewProviders = providers != null
345: && !providers.isEmpty();
346:
347: // navigator remains empty, do nothing
348: if (oldProviders == null && providers == null) {
349: inUpdate = false;
350: LOG.fine("Exit because nav remain empty, force: " + force);
351: return;
352: }
353:
354: NavigatorPanel selPanel = navigatorTC.getSelectedPanel();
355:
356: // don't call panelActivated/panelDeactivated if the same provider is
357: // still available, it's client's responsibility to listen to
358: // context changes while active
359: if (oldProviders != null && oldProviders.contains(selPanel)
360: && providers != null && providers.contains(selPanel)) {
361: // trigger resultChanged() call on client side
362: clientsLookup.lookup(Node.class);
363: // #93123: refresh providers list if needed
364: if (!oldProviders.equals(providers)) {
365: // we must disable combo-box listener to not receive unwanted events
366: // during combo box content change
367: navigatorTC.getPanelSelector().removeActionListener(
368: this );
369: navigatorTC.setPanels(providers);
370: navigatorTC.setSelectedPanel(selPanel);
371: navigatorTC.getPanelSelector().addActionListener(this );
372: }
373: // #100122: update activated nodes of Navigator TC
374: updateActNodesAndTitle();
375:
376: LOG
377: .fine("Exit because same provider and panel, notified. Force: "
378: + force);
379: inUpdate = false;
380: return;
381: }
382:
383: if (selPanel != null) {
384: // #61334: don't deactivate previous providers if there are no new ones
385: if (!areNewProviders && !force) {
386: inUpdate = false;
387: LOG.fine("Exit because no new providers, force: "
388: + force);
389: return;
390: }
391: selPanel.panelDeactivated();
392: }
393:
394: // #67849: curNode's lookup cleanup, held through ClientsLookup delegates
395: clientsLookup.lookup(Node.class);
396:
397: if (areNewProviders) {
398: NavigatorPanel newSel = providers.get(0);
399: newSel.panelActivated(clientsLookup);
400: }
401: // we must disable combo-box listener to not receive unwanted events
402: // during combo box content change
403: navigatorTC.getPanelSelector().removeActionListener(this );
404: navigatorTC.setPanels(providers);
405: navigatorTC.getPanelSelector().addActionListener(this );
406:
407: updateActNodesAndTitle();
408:
409: LOG
410: .fine("Normal exit, change to new provider, force: "
411: + force);
412: inUpdate = false;
413: }
414:
415: /** Updates activated nodes of Navigator TopComponent and updates its
416: * display name to reflect activated nodes */
417: private void updateActNodesAndTitle() {
418: LOG.fine("updateActNodesAndTitle called...");
419: Node[] actNodes = obtainActivatedNodes();
420: navigatorTC.setActivatedNodes(actNodes);
421: updateTCTitle(actNodes);
422: }
423:
424: /** Sets navigator title according to active context */
425: private void updateTCTitle(Node[] nodes) {
426: String newTitle;
427: if (nodes != null && nodes.length > 0) {
428: newTitle = NbBundle.getMessage(NavigatorTC.class,
429: "FMT_Navigator", nodes[0].getDisplayName() //NOI18N
430: );
431: } else {
432: newTitle = NbBundle.getMessage(NavigatorTC.class,
433: "LBL_Navigator"); //NOI18N
434: }
435: navigatorTC.setDisplayName(newTitle);
436: }
437:
438: /** Searches and return a list of providers which are suitable for given
439: * node context. Both Node lookup registered clients and xml layer registered
440: * clients are returned.
441: *
442: * @node Nodes collection context, may be empty.
443: */
444: /* package private for tests */List<NavigatorPanel> obtainProviders(
445: Collection<? extends Node> nodes) {
446: // obtain policy for panels if there is one
447: Lookup globalContext = Utilities.actionsGlobalContext();
448: NavigatorLookupPanelsPolicy panelsPolicy = globalContext
449: .lookup(NavigatorLookupPanelsPolicy.class);
450:
451: List<NavigatorPanel> result = null;
452:
453: // search in global lookup first, they had preference
454: Collection<? extends NavigatorLookupHint> lkpHints = globalContext
455: .lookupAll(NavigatorLookupHint.class);
456: for (NavigatorLookupHint curHint : lkpHints) {
457: Collection<? extends NavigatorPanel> providers = ProviderRegistry
458: .getInstance().getProviders(
459: curHint.getContentType());
460: if (providers != null && !providers.isEmpty()) {
461: if (result == null) {
462: result = new ArrayList<NavigatorPanel>(providers
463: .size()
464: * lkpHints.size());
465: }
466: for (NavigatorPanel np : providers) {
467: if (!result.contains(np))
468: result.add(np);
469: }
470: }
471: }
472:
473: // #100457: exclude Node/DataObject providers if requested
474: if (panelsPolicy != null
475: && panelsPolicy.getPanelsPolicy() == NavigatorLookupPanelsPolicy.LOOKUP_HINTS_ONLY) {
476: return result;
477: }
478:
479: // search based on Node/DataObject's primary file mime type
480: List<NavigatorPanel> fileResult = null;
481: for (Node node : nodes) {
482: DataObject dObj = node.getLookup().lookup(DataObject.class);
483: // #64871: Follow DataShadows to their original
484: while (dObj instanceof DataShadow) {
485: dObj = ((DataShadow) dObj).getOriginal();
486: }
487: if (dObj == null) {
488: fileResult = null;
489: break;
490: }
491:
492: FileObject fo = dObj.getPrimaryFile();
493: // #65589: be no friend with virtual files
494: if (fo.isVirtual()) {
495: fileResult = null;
496: break;
497: }
498:
499: String contentType = fo.getMIMEType();
500: Collection<? extends NavigatorPanel> providers = ProviderRegistry
501: .getInstance().getProviders(contentType);
502: if (providers == null || providers.isEmpty()) {
503: fileResult = null;
504: break;
505: }
506: if (fileResult == null) {
507: fileResult = new ArrayList<NavigatorPanel>(providers
508: .size());
509: fileResult.addAll(providers);
510: } else {
511: fileResult.retainAll(providers);
512: }
513: }
514:
515: if (result != null) {
516: if (fileResult != null) {
517: result.addAll(fileResult);
518: }
519: } else {
520: result = fileResult;
521: }
522:
523: return result;
524: }
525:
526: /** Builds and returns activated nodes array for Navigator TopComponent.
527: */
528: private Node[] obtainActivatedNodes() {
529: Collection<? extends Node> nodes = getPanelLookup().lookupAll(
530: Node.class);
531: if (nodes.isEmpty()) {
532: // set Navigator's active node to be the same as the content it is showing
533: return curNodes.toArray(new Node[0]);
534: } else {
535: return nodes.toArray(new Node[0]);
536: }
537: }
538:
539: /** Retrieves and returns UndoRedo support from selected panel if panel
540: * offers UndoRedo (implements NavigatorPanelWithUndo).
541: */
542: UndoRedo getUndoRedo() {
543: NavigatorPanel panel = navigatorTC.getSelectedPanel();
544: if (panel == null || !(panel instanceof NavigatorPanelWithUndo)) {
545: return UndoRedo.NONE;
546: }
547: return ((NavigatorPanelWithUndo) panel).getUndoRedo();
548: }
549:
550: /** Installs user actions handling for NavigatorTC top component */
551: public void installActions() {
552: // ESC key handling - return focus to previous focus owner
553: KeyStroke returnKey = KeyStroke.getKeyStroke(
554: KeyEvent.VK_ESCAPE, 0, true);
555: //JComponent contentArea = navigatorTC.getContentArea();
556: navigatorTC.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
557: returnKey, "return"); //NOI18N
558: navigatorTC.getActionMap().put("return", new ESCHandler()); //NOI18N
559: }
560:
561: /***** PropertyChangeListener implementation *******/
562:
563: /** Stores last TopComponent activated before NavigatorTC. Used to handle
564: * ESC key functionality */
565: public void propertyChange(PropertyChangeEvent evt) {
566: // careful here, note that prop changes coming here both from
567: // TopComponent.Registry and currently asociated Node
568:
569: if (TopComponent.Registry.PROP_ACTIVATED.equals(evt
570: .getPropertyName())) {
571: TopComponent tc = TopComponent.getRegistry().getActivated();
572: if (tc != null && tc != navigatorTC) {
573: lastActivatedRef = new WeakReference<TopComponent>(tc);
574: }
575: } else if (TopComponent.Registry.PROP_TC_CLOSED.equals(evt
576: .getPropertyName())) {
577: // force update context if some tc was closed
578: // invokeLater to let node change perform before calling update
579: LOG
580: .fine("Component closed, invoking update through invokeLater...");
581: // #124061 - force navigator cleanup in special situation
582: TopComponent tc = TopComponent.getRegistry().getActivated();
583: if (tc == navigatorTC) {
584: LOG
585: .fine("navigator active, clearing its activated nodes");
586: navigatorTC.setActivatedNodes(new Node[0]);
587: }
588:
589: EventQueue.invokeLater(this );
590: }
591: }
592:
593: /****** NodeListener implementation *****/
594:
595: public void nodeDestroyed(NodeEvent ev) {
596: LOG.fine("Node destroyed reaction...");
597: // #121944: don't react on node destroy when we are active
598: if (navigatorTC.equals(WindowManager.getDefault().getRegistry()
599: .getActivated())) {
600: LOG
601: .fine("NavigatorTC active, skipping node destroyed reaction.");
602: return;
603: }
604: LOG
605: .fine("invokeLater on updateContext from node destroyed reaction...");
606: // #122257: update content later to fight possible deadlocks
607: EventQueue.invokeLater(this );
608: }
609:
610: public void childrenAdded(NodeMemberEvent ev) {
611: // no operation
612: }
613:
614: public void childrenRemoved(NodeMemberEvent ev) {
615: // no operation
616: }
617:
618: public void childrenReordered(NodeReorderEvent ev) {
619: // no operation
620: }
621:
622: /** Runnable implementation - forces update */
623: public void run() {
624: updateContext(true);
625: }
626:
627: /** Handles ESC key request - returns focus to previously focused top component
628: */
629: private class ESCHandler extends AbstractAction {
630: public void actionPerformed(ActionEvent evt) {
631: Component focusOwner = FocusManager.getCurrentManager()
632: .getFocusOwner();
633: // move focus away only from navigator AWT children,
634: // but not combo box to preserve its ESC functionality
635: if (lastActivatedRef == null
636: || focusOwner == null
637: || !SwingUtilities.isDescendingFrom(focusOwner,
638: navigatorTC)
639: || focusOwner instanceof JComboBox) {
640: return;
641: }
642: TopComponent prevFocusedTc = lastActivatedRef.get();
643: if (prevFocusedTc != null) {
644: prevFocusedTc.requestActive();
645: }
646: }
647: } // end of ESCHandler
648:
649: /** Lookup delegating to lookup of currently selected panel.
650: * If no panel is selected or panels' lookup is null, then acts as
651: * dummy empty lookup.
652: */
653: private final class PanelLookupWrapper implements Lookup.Provider {
654:
655: public Lookup getLookup() {
656: NavigatorPanel selPanel = navigatorTC.getSelectedPanel();
657: if (selPanel != null) {
658: Lookup panelLkp = selPanel.getLookup();
659: if (panelLkp != null) {
660: return panelLkp;
661: }
662: }
663: return Lookup.EMPTY;
664: }
665:
666: } // end of PanelLookupWrapper
667:
668: /** Listens to changes of lookup content of panel's lookup
669: * (NavigatorPanel.getLookup()) and updates activated nodes.
670: */
671: private final class PanelLookupListener implements LookupListener {
672:
673: public void resultChanged(LookupEvent ev) {
674: // #103981: update also display name of Navigator TopComp
675: updateActNodesAndTitle();
676: }
677:
678: } // end of PanelLookupListener
679:
680: /** Task to set given node (as data context). Used to be able to coalesce
681: * data context changes if selected nodes changes too fast.
682: * Listens to own finish for cleanup */
683: private class ActNodeSetter implements Runnable, TaskListener {
684:
685: public void run() {
686: // technique to share one runnable impl between RP and Swing,
687: // to save one inner class
688: if (RequestProcessor.getDefault()
689: .isRequestProcessorThread()) {
690: LOG
691: .fine("invokeLater on updateContext from ActNodeSetter");
692: SwingUtilities.invokeLater(this );
693: } else {
694: // AWT thread
695: LOG.fine("Calling updateContext from ActNodeSetter");
696: updateContext();
697: }
698: }
699:
700: public void taskFinished(Task task) {
701: synchronized (NODE_SETTER_LOCK) {
702: if (task == nodeSetterTask) {
703: nodeSetterTask = null;
704: }
705: }
706: }
707:
708: } // end of ActNodeSetter
709:
710: /** accessor for tests */
711: ClientsLookup getClientsLookup() {
712: return clientsLookup;
713: }
714:
715: /** Lookup that holds context for clients, for NavigatorPanel implementors.
716: * It's proxy lookup that delegates to lookups of current nodes */
717: /* package private for tests */class ClientsLookup extends
718: ProxyLookup {
719:
720: @Override
721: protected void beforeLookup(Template<?> template) {
722: super .beforeLookup(template);
723:
724: Lookup[] curNodesLookups;
725:
726: synchronized (CUR_NODES_LOCK) {
727: curNodesLookups = new Lookup[curNodes.size()];
728: int i = 0;
729: for (Iterator<? extends Node> it = curNodes.iterator(); it
730: .hasNext(); i++) {
731: curNodesLookups[i] = it.next().getLookup();
732: }
733: }
734:
735: setLookups(curNodesLookups);
736: }
737:
738: /** for tests */
739: Lookup[] obtainLookups() {
740: return getLookups();
741: }
742:
743: } // end of ClientsLookup
744:
745: }
|