001: /*=============================================================================
002: * Copyright Texas Instruments, Inc., 2001. All Rights Reserved.
003: *
004: * This program is free software; you can redistribute it and/or modify
005: * it under the terms of the GNU General Public License as published by
006: * the Free Software Foundation; either version 2 of the License, or
007: * (at your option) any later version.
008: *
009: * This program is distributed in the hope that it will be useful,
010: * but WITHOUT ANY WARRANTY; without even the implied warranty of
011: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
012: * GNU General Public License for more details.
013: *
014: * You should have received a copy of the GNU General Public License
015: * along with this program; if not, write to the Free Software
016: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
017: */
018:
019: package ti.chimera;
020:
021: import java.util.*;
022: import java.awt.event.*;
023: import java.awt.Component;
024: import javax.swing.*;
025: import javax.swing.event.*;
026: import javax.swing.plaf.TabbedPaneUI;
027:
028: import ti.exceptions.ProgrammingErrorException;
029: import ti.chimera.registry.*;
030:
031: /**
032: * A <code>Dock</code> is used to display one or more <code>View</code>s. When
033: * all the <code>View</code>s are removed from a <code>Dock</code>, it destroys
034: * itself.
035: * <p>
036: * The <code>Dock</code>'s methods to add/remove <code>View</code>s handles the
037: * threading issues so that they can be called from any thread, without having
038: * to worry about swing's lack of thread safeness.
039: * <p>
040: * NOTE: Do not assume that this class extends <code>JTabbedPane</code>. I
041: * may change this to be an interface, and have the actual implementation
042: * hidden, perhaps as an inner-class of <code>WindowManager</code>.
043: * <p>
044: * The visibility of the <code>Dock</code> depends on it's contents. If a dock
045: * contains no views, it is hiden, otherwise it is visible.
046: *
047: * @author Rob Clark
048: * @version 0.0
049: */
050: public class Dock extends JTabbedPane {
051: // protected void finalize() { System.out.println("finalize dock: " + getName()); }
052:
053: /**
054: * The main application.
055: */
056: private final Main main;
057:
058: /**
059: * The registry
060: */
061: private final Registry registry;
062:
063: /**
064: * The name of the dock.
065: */
066: private final String name;
067:
068: /**
069: * Table that maps components to views. This is needed to determine
070: * the selected view from the selected component.
071: */
072: private Hashtable componentToViewTable = new Hashtable();
073:
074: /**
075: * Set of docks.
076: */
077: private static Set dockSet = new HashSet();
078:
079: /**
080: * List of views.
081: */
082: private LinkedList viewList = new LinkedList();
083:
084: /**
085: * Table mapping view-descriptors (a string encoding the name of the view
086: * and the name of the plugin owning the view) to dock name. Used to ensure
087: * that the next time the view is created, it magically re-appears in the
088: * correct dock.
089: *
090: * @see #getDockName
091: */
092: private static Hashtable viewToDockTable;
093:
094: /**
095: * called from Main at startup... kinda ugly, but needs to happen before
096: * constructor, getDockName, etc. are called.
097: */
098: static synchronized void init(Main main) {
099: if (viewToDockTable == null) {
100: viewToDockTable = (Hashtable) (main
101: .restore("viewToDockTable"));
102:
103: if (viewToDockTable == null) {
104: viewToDockTable = new Hashtable();
105: main.store("viewToDockTable", viewToDockTable);
106: }
107: }
108:
109: final Registry registry = main.getRegistry();
110:
111: registry.subscribeToValue("/Window Manager/visible",
112: NodeContract.BOOLEAN_CONTRACT, new SwingNodeSubscriber(
113: new NodeSubscriber() {
114: public void publish(Node node, Object value) {
115: if (((Boolean) value).booleanValue()
116: && !wmVisible) {
117: for (Iterator itr = getDocks(); itr
118: .hasNext();) {
119: String dockName = ((Dock) (itr
120: .next())).getName();
121:
122: try {
123: String viewName = (String) (registry
124: .resolve("/Docks/docks/"
125: + dockName
126: + "/selected view name")
127: .getValue());
128:
129: if (viewName != null) {
130: String path = "/Docks/docks/"
131: + dockName
132: + "/views/"
133: + viewName;
134: if (registry
135: .exists(path))
136: ((View) (registry
137: .resolve(path)
138: .getValue()))
139: .toForeground();
140: }
141: } catch (RegistryException e) {
142: e.printStackTrace(); // hmm? shouldn't happen
143: }
144: }
145: // ... restore foreground views ...
146: wmVisible = true;
147: }
148: }
149: }));
150: }
151:
152: private static boolean wmVisible = false;
153:
154: /*=======================================================================*/
155: /**
156: * Class Constructor.
157: *
158: * @param main the main application
159: * @param name the name of the view
160: */
161: private Dock(Main main, final String name) {
162: super ();
163:
164: this .main = main;
165: this .name = name;
166:
167: this .registry = main.getRegistry();
168:
169: try {
170: registry.mkdir("/Docks/docks/" + name);
171: registry.link(new PersistentNode(null,
172: NodeContract.STRING_CONTRACT, null),
173: "/Docks/docks/" + name + "/selected view name");
174: } catch (RegistryException e) {
175: throw new ProgrammingErrorException(e);
176: }
177:
178: addMouseListener(new PopupTrigger(
179: new PopupTrigger.PopupListener() {
180:
181: public void showPopup(MouseEvent evt) {
182: TabbedPaneUI ui = getUI();
183: int tab = ui.tabForCoordinate(Dock.this , evt
184: .getX(), evt.getY());
185:
186: if (getSelectedIndex() == tab) {
187: View view = (View) (componentToViewTable
188: .get(getSelectedComponent()));
189:
190: view.getPopupMenu().show(Dock.this ,
191: evt.getX(), evt.getY());
192: }
193: }
194:
195: }));
196:
197: addChangeListener(new ChangeListener() {
198:
199: public void stateChanged(ChangeEvent evt) {
200: synchronized (Dock.this ) {
201: try {
202: java.awt.Component c = getSelectedComponent();
203: if (c == null)
204: return;
205:
206: View view = (View) (componentToViewTable.get(c));
207:
208: if (view != null)
209: markSelectedView(view);
210: } catch (RegistryException e) {
211: throw new ProgrammingErrorException(e);
212: }
213: }
214: }
215:
216: });
217:
218: addFocusListener(new FocusAdapter() {
219:
220: public void focusGained(FocusEvent evt) {
221: Component c = getSelectedComponent();
222: if (c != null)
223: c.requestFocus();
224: }
225:
226: });
227: }
228:
229: private void markSelectedView(View view) throws RegistryException {
230: if (registry.exists("/Docks/docks/" + name + "/views/"
231: + view.getName())) {
232: if (wmVisible)
233: registry.resolve(
234: "/Docks/docks/" + name + "/selected view name")
235: .setValue(view.getName());
236:
237: if (registry.exists("/Docks/docks/" + name
238: + "/selected view"))
239: registry.unlink("/Docks/docks/" + name
240: + "/selected view");
241:
242: registry.link(registry.resolve("/Docks/docks/" + name
243: + "/views/" + view.getName()), "/Docks/docks/"
244: + name + "/selected view");
245: }
246: }
247:
248: private void markAsSelectedDock() throws RegistryException {
249: if (registry.exists("/Docks/docks/" + name)) {
250: if (registry.exists("/Docks/selected dock"))
251: registry.unlink("/Docks/selected dock");
252:
253: registry.link(registry.resolve("/Docks/docks/" + name),
254: "/Docks/selected dock");
255: }
256: }
257:
258: /*=======================================================================*/
259: private Dialog dialog; /// XXX get rid of this
260: private WindowListener dialogWindowListener = new WindowAdapter() {
261:
262: public void windowActivated(WindowEvent evt) {
263: try {
264: markAsSelectedDock();
265: } catch (RegistryException e) {
266: throw new ProgrammingErrorException(e);
267: }
268: }
269:
270: };
271:
272: public/*XXX*/synchronized void setDialog(Dialog dialog) {
273: try {
274: main.debug(0, "setDialog, "
275: + this .dialog
276: + ", "
277: + registry.exists("/Docks/docks/" + name
278: + "/dialog"));
279:
280: if (this .dialog != null) {
281: this .dialog.removeWindowListener(dialogWindowListener);
282: registry.unlink("/Docks/docks/" + name + "/dialog");
283: }
284:
285: this .dialog = dialog;
286:
287: if (this .dialog != null) {
288: registry.link(new Node(dialog, null, null),
289: "/Docks/docks/" + name + "/dialog");
290: this .dialog.addWindowListener(dialogWindowListener);
291: }
292: } catch (RegistryException e) {
293: throw new ProgrammingErrorException(e);
294: }
295: }
296:
297: public/*XXX*/Dialog getDialog() {
298: return dialog;
299: }
300:
301: /*=======================================================================*/
302:
303: /*=======================================================================*/
304: /**
305: * Get the name of this dock.
306: */
307: public String getName() {
308: return name;
309: }
310:
311: private void ensureUniqueViewName(View view) {
312: if (registry.exists("/Docks/docks/" + getName() + "/views/"
313: + view.getName())) {
314: // need to ensure current dock is null before changing the view's name:
315: view.setDock(null);
316:
317: int idx = 0;
318: String origName = view.getName();
319:
320: while (registry.exists("/Docks/docks/" + getName()
321: + "/views/" + view.getName()))
322: view.setName(origName + " " + (++idx));
323:
324: view.setDock(this );
325: }
326: }
327:
328: /*=======================================================================*/
329: /**
330: * Add a <code>View</code> to this <code>Dock</code>. If a view is being
331: * moved between docks, it should be removed from it's original dock
332: * before being added to this dock.
333: *
334: * @param view the <code>View</code> to add
335: * @see #removeView
336: */
337: public void addView(final View view) {
338: // this needs to happen here, instead of in the runnable, to ensure that
339: // it happens with the appropriate synchronization, when this is called
340: // from showView()
341: registerDock(this );
342:
343: if (view.getDock() == this )
344: throw new ProgrammingErrorException(
345: "view already a member of this dock!");
346:
347: viewList.add(view);
348:
349: invokeLater(new Runnable() {
350:
351: public void run() {
352: java.awt.Component viewComponent = view.getComponent();
353: componentToViewTable.put(viewComponent, view);
354:
355: synchronized (Dock.this ) {
356: ensureUniqueViewName(view);
357:
358: // descriptor will change if name changes, so this has to
359: // be after ensureUniqueViewName():
360: viewToDockTable
361: .put(view.getDescriptor(), getName());
362:
363: view.setDock(Dock.this );
364:
365: addTab(view.getName(), view.getIcon(),
366: viewComponent);
367:
368: try {
369: if (!registry.exists("/Docks/docks/" + name
370: + "/views"))
371: registry.mkdir("/Docks/docks/" + name
372: + "/views");
373:
374: registry.link(
375: new Node(view, new TypeNodeContract(
376: View.class), null),
377: "/Docks/docks/" + name + "/views/"
378: + view.getName());
379:
380: markSelectedView(view);
381: } catch (RegistryException e) {
382: throw new ProgrammingErrorException(e);
383: }
384: }
385:
386: view.toForeground();
387:
388: // could be done with setForegroundView() at the expense of an
389: // extra runnable:
390: setSelectedComponent(viewComponent);
391:
392: //XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
393: if (getTabCount() == 1)
394: main.getWindowManager().addDock(Dock.this );
395: else
396: main.getWindowManager().dockUpdated(Dock.this );
397: //XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
398: }
399: });
400: }
401:
402: /*=======================================================================*/
403: /**
404: * Remove a <code>View</code> from this <code>Dock</code>.
405: *
406: * @param view the <code>View</code> to remove
407: * @see #addView
408: */
409: public void removeView(final View view) {
410: if (view.getDock() != this )
411: throw new ProgrammingErrorException(
412: "view not a member of this dock!");
413:
414: viewList.remove(view);
415:
416: invokeLater(new Runnable() {
417:
418: public void run() {
419: componentToViewTable.remove(view.getComponent());
420: remove(view.getComponent());
421:
422: view.setDock(null);
423:
424: synchronized (Dock.this ) {
425: try {
426: registry.unlink("/Docks/docks/" + name
427: + "/views/" + view.getName());
428:
429: if (getTabCount() == 0) {
430: if (registry.exists("/Docks/docks/" + name
431: + "/selected view"))
432: registry.unlink("/Docks/docks/" + name
433: + "/selected view");
434: registry.unlink("/Docks/docks/" + name
435: + "/views");
436: registry.unlink("/Docks/docks/" + name
437: + "/selected view name");
438:
439: //XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
440: main.getWindowManager().removeDock(
441: Dock.this );
442: //XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
443:
444: registry.unlink("/Docks/docks/" + name);
445: }
446: } catch (RegistryException e) {
447: throw new ProgrammingErrorException(e);
448: }
449: }
450:
451: if (getTabCount() == 0)
452: deregisterDock(Dock.this );
453:
454: //XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
455: if (getTabCount() > 0)
456: main.getWindowManager().dockUpdated(Dock.this );
457: //XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
458:
459: }
460: });
461: }
462:
463: /*=======================================================================*/
464: /**
465: * An implementation detail of how the view has to notify the dock that
466: * it's name has been changed... a more general notification mechanism
467: * (for example the registry?) might be a more elegant solution, but
468: * this is the quick and dirty solution for now...
469: */
470: void notifySetName(final View view, final String oldName,
471: final String newName) {
472: if (view.getDock() != this )
473: throw new ProgrammingErrorException(
474: "view not a member of this dock!");
475:
476: invokeLater(new Runnable() {
477:
478: public void run() {
479: synchronized (Dock.this ) {
480: try {
481: registry.unlink("/Docks/docks/" + name
482: + "/views/" + oldName);
483:
484: ensureUniqueViewName(view);
485:
486: // descriptor will change if name changes, so this has to
487: // be after ensureUniqueViewName():
488: viewToDockTable.put(view.getDescriptor(),
489: getName());
490:
491: remove(view.getComponent());
492: addTab(newName, view.getIcon(), view
493: .getComponent());
494: setSelectedComponent(view.getComponent());
495:
496: registry.link(
497: new Node(view, new TypeNodeContract(
498: View.class), null),
499: "/Docks/docks/" + name + "/views/"
500: + newName);
501: } catch (RegistryException e) {
502: throw new ProgrammingErrorException(e);
503: }
504: }
505: }
506: });
507: }
508:
509: /*=======================================================================*/
510: /**
511: * An implementation detail of how the view has to notify the dock that
512: * it's icon has been changed... a more general notification mechanism
513: * (for example the registry?) might be a more elegant solution, but
514: * this is the quick and dirty solution for now...
515: */
516: void notifySetIcon(final View view, final Icon oldIcon,
517: final Icon newIcon) {
518: if (view.getDock() != this )
519: throw new ProgrammingErrorException(
520: "view not a member of this dock!");
521:
522: invokeLater(new Runnable() {
523:
524: public void run() {
525: setIconAt(indexOfComponent(view.getComponent()),
526: newIcon);
527: }
528:
529: });
530: }
531:
532: /*=======================================================================*/
533: /**
534: * Programmatically set the specified view to be the foreground tab.
535: *
536: * @param view the <code>View</code> to move to foreground
537: */
538: public void setForegroundView(final View view) {
539: if (view.getDock() != this )
540: throw new ProgrammingErrorException(
541: "view not a member of this dock!");
542:
543: invokeLater(new Runnable() {
544:
545: public void run() {
546: setSelectedComponent(view.getComponent());
547: }
548: });
549: }
550:
551: /*=======================================================================*/
552: /**
553: * Get the number of views in this dock.
554: * <p>
555: * NOTE: Use this instead of <code>getTabCount</code> in case I change
556: * things so that <code>Dock</code> doesn't extend <code>JTabbedPane</code>.
557: *
558: * @return the number of views.
559: */
560: public int getViewCount() {
561: return viewList.size();
562: }
563:
564: /*=======================================================================*/
565: /**
566: * Get the views.
567: *
568: * @return iteration of all views in this <code>Dock</code>
569: */
570: public Iterator getViews() {
571: return viewList.iterator();
572: }
573:
574: /*=======================================================================*/
575: /**
576: * Close the dock. This will close and remove all views in this dock.
577: */
578: public void close() {
579: main.debug(0, "closing dock: " + getName());
580:
581: /* we have to use the less efficient toArray(), otherwise there will
582: * a race condition between us walking the list, and the view removing
583: * itself from the list.
584: */
585: Object[] arr = viewList.toArray();
586: for (int i = 0; i < arr.length; i++) {
587: View view = (View) arr[i];
588:
589: if (!view.close())
590: throw new ti.chimera.service.WindowManager.DialogNotClosableException();
591: }
592: }
593:
594: /*=======================================================================*/
595: /**
596: * Get the docks. The docks in this list don't necessarily have to be
597: * visible, ie. contain any views.
598: *
599: * @return an Iterator of Docks
600: */
601: static Iterator getDocks() {
602: return dockSet.iterator();
603: }
604:
605: /*=======================================================================*/
606: /**
607: * Search for the specified dock.
608: * <p>
609: * XXX only call from WindowManagerPlugin...
610: *
611: * @param name the name of the dock to search for
612: * @return the dock, if found, else null
613: */
614: public/*XXX*/static Dock getDock(String name) {
615: synchronized (dockSet) {
616: for (Iterator itr = getDocks(); itr.hasNext();) {
617: Dock dock = (Dock) (itr.next());
618:
619: if (dock.getName().equals(name))
620: return dock;
621: }
622: }
623:
624: return null;
625: }
626:
627: /*=======================================================================*/
628: /**
629: * Determine the name of the dock for this view to appear in. If this is
630: * the first time the view is displayed, the dock name will be the name of
631: * the plugin the view is a owned by. Otherwise, it will be the name of
632: * the last dock this plugin was added to.
633: *
634: * @param view the view
635: * @return a dock name
636: */
637: static String getDockName(View view) {
638: String dockName = (String) (viewToDockTable.get(view
639: .getDescriptor()));
640:
641: if (dockName == null)
642: dockName = view.getPlugin().getName();
643:
644: return dockName;
645: }
646:
647: /*=======================================================================*/
648: /**
649: * Show the view in the dock with the specified name. If this dock does
650: * not already exist, it is created.
651: *
652: * @param view the view to show
653: * @param name the dock name
654: */
655: static void showView(View view, String name) {
656: synchronized (Dock.class) {
657: Dock dock = Dock.getDock(name);
658:
659: /* if there is no such dock, create a new one:
660: */
661: if (dock == null)
662: dock = new Dock(view.getMain(), name);
663:
664: dock.addView(view);
665: }
666: }
667:
668: /*=======================================================================*/
669: /**
670: * Called to register a new dock.
671: *
672: * @param dock the dock to register
673: */
674: private static void registerDock(Dock dock) {
675: dock.main.debug(0, "registerDock: " + dock.getName());
676:
677: synchronized (dockSet) {
678: dockSet.add(dock);
679: }
680: }
681:
682: /*=======================================================================*/
683: /**
684: * Called to deregister a new dock.
685: *
686: * @param dock the dock to deregister
687: */
688: private static void deregisterDock(Dock dock) {
689: dock.main.debug(0, "deregisterDock: " + dock.getName());
690:
691: synchronized (dockSet) {
692: dockSet.remove(dock);
693: }
694: }
695:
696: /*=======================================================================*/
697: /**
698: */
699: private static void invokeLater(Runnable r) {
700: try {
701: if (SwingUtilities.isEventDispatchThread())
702: r.run();
703: else
704: SwingUtilities.invokeLater(r);
705: } catch (Throwable e) {
706: throw new ProgrammingErrorException(e);
707: }
708: }
709:
710: public String toString() {
711: return "[dock: " + getName() + "]";
712: }
713: }
714:
715: /*
716: * Local Variables:
717: * tab-width: 2
718: * indent-tabs-mode: nil
719: * mode: java
720: * c-indentation-style: java
721: * c-basic-offset: 2
722: * eval: (c-set-offset 'substatement-open '0)
723: * eval: (c-set-offset 'case-label '+)
724: * eval: (c-set-offset 'inclass '+)
725: * eval: (c-set-offset 'inline-open '0)
726: * End:
727: */
|