001: /*
002: * $Id: JGraphpadLibraryPane.java,v 1.8 2007/07/27 14:15:59 gaudenz Exp $
003: * Copyright (c) 2001-2005, Gaudenz Alder
004: *
005: * All rights reserved.
006: *
007: * See LICENSE file for license details. If you are unable to locate
008: * this file please contact info (at) jgraph (dot) com.
009: */
010: package com.jgraph.pad.factory;
011:
012: import java.awt.Component;
013: import java.awt.Dimension;
014: import java.awt.Graphics;
015: import java.awt.Graphics2D;
016: import java.awt.Rectangle;
017: import java.awt.RenderingHints;
018: import java.awt.Shape;
019: import java.awt.datatransfer.DataFlavor;
020: import java.awt.datatransfer.Transferable;
021: import java.awt.event.MouseEvent;
022: import java.awt.geom.Rectangle2D;
023: import java.util.ArrayList;
024: import java.util.Hashtable;
025: import java.util.List;
026: import java.util.Map;
027:
028: import javax.swing.CellRendererPane;
029: import javax.swing.JComponent;
030: import javax.swing.JInternalFrame;
031: import javax.swing.JTabbedPane;
032: import javax.swing.SwingUtilities;
033: import javax.swing.TransferHandler;
034: import javax.swing.event.TreeModelEvent;
035:
036: import org.jgraph.JGraph;
037: import org.jgraph.event.GraphModelEvent;
038: import org.jgraph.event.GraphModelListener;
039: import org.jgraph.event.GraphSelectionEvent;
040: import org.jgraph.event.GraphSelectionListener;
041: import org.jgraph.graph.CellView;
042: import org.jgraph.graph.ConnectionSet;
043: import org.jgraph.graph.DefaultGraphCell;
044: import org.jgraph.graph.GraphModel;
045: import org.jgraph.graph.GraphTransferHandler;
046: import org.jgraph.graph.ParentMap;
047: import org.w3c.dom.Node;
048:
049: import com.jgraph.JGraphEditor;
050: import com.jgraph.editor.JGraphEditorAction;
051: import com.jgraph.editor.JGraphEditorFactory;
052: import com.jgraph.editor.factory.JGraphEditorFactoryMethod;
053: import com.jgraph.pad.JGraphpadLibrary;
054: import com.jgraph.pad.graph.JGraphpadVertexRenderer;
055: import com.jgraph.pad.util.JGraphpadMouseAdapter;
056: import com.jgraph.pad.util.JGraphpadTreeModelAdapter;
057:
058: /**
059: * Displays groups in a {@link JGraphpadLibrary} as a list of entries. Allows to
060: * drag and drop entries to/from {@link JGraph}.
061: */
062: public class JGraphpadLibraryPane extends JComponent {
063:
064: /**
065: * Node name for the library popup menu configuration.
066: */
067: public static final String NODENAME_LIBRARYPOPUPMENU = "librarypopupmenu";
068:
069: /**
070: * Node name for the library popup menu configuration.
071: */
072: public static final String NODENAME_ENTRYPOPUPMENU = "entrypopupmenu";
073:
074: /**
075: * Defines the preferred width which is used to find the best number of
076: * columns.
077: */
078: public static int PREFERRED_WIDTH = 100;
079:
080: /**
081: * References the library.
082: */
083: protected JGraphpadLibrary library;
084:
085: /**
086: * References the enclosing editor.
087: */
088: protected JGraphEditor editor;
089:
090: /**
091: * Defines geometry and spacing.
092: */
093: protected int entrywidth = 60, entryheight = 40, hgap = 10,
094: vgap = 10;
095:
096: protected CellRendererPane rendererPane = new CellRendererPane();
097:
098: /**
099: * Holds the backing graph for rendering. Makes sure the cell renderer pane
100: * is actually inserted into a valid component.
101: */
102: protected JGraph backingGraph = new JGraph();
103:
104: /**
105: * Automatically groups cells on drop and ungroups cells on drag. Default is
106: * true.
107: */
108: protected boolean autoBoxing = true;
109:
110: /**
111: * Specifies whether to use antialiasing to render the entries.
112: */
113: protected boolean antiAliased = true;
114:
115: /**
116: * Specifies whether the library can be changed.
117: */
118: protected boolean isReadOnly = false;
119:
120: /**
121: * Internal variable to block drag and drop if the operation was initiated
122: * from here. This variable is true during drag operations that started
123: * here.
124: */
125: protected transient boolean dragging = false;
126:
127: /**
128: * Constructs a new repository pane for the specified library.
129: *
130: * @param library
131: * The library that contains the cells.
132: */
133: public JGraphpadLibraryPane(final JGraphEditor editor,
134: JGraphpadLibrary library) {
135: setTransferHandler(new LibraryTransferHandler());
136: this .editor = editor;
137: this .library = library;
138: this .isReadOnly = library.isReadOnly();
139:
140: // Disables the folding icon in the backing graph
141: backingGraph
142: .putClientProperty(
143: JGraphpadVertexRenderer.CLIENTPROPERTY_SHOWFOLDINGICONS,
144: new Boolean(false));
145:
146: // Configures the backing graph
147: GraphTransferHandler transferHandler = new LibraryGraphTransferHandler();
148: transferHandler.setAlwaysReceiveAsCopyAction(true);
149: backingGraph.setTransferHandler(transferHandler);
150: backingGraph.setGraphLayoutCache(library.getGraphLayoutCache());
151: backingGraph.setDragEnabled(true);
152: backingGraph.setDoubleBuffered(false);
153:
154: // Starts dragging the entry under the mouse pointer
155: addMouseListener(new JGraphpadMouseAdapter(editor,
156: NODENAME_LIBRARYPOPUPMENU) {
157:
158: /**
159: * Selects the entry under the mouse pointer.
160: */
161: public void mousePressed(MouseEvent event) {
162: int index = getIndexAt(event.getX(), event.getY());
163:
164: // Selects cell at index and starts dragging
165: if (index >= 0) {
166: getBackingGraph().setSelectionCell(
167: getBackingGraph().getModel().getRootAt(
168: index));
169: if (!SwingUtilities.isRightMouseButton(event))
170: getTransferHandler().exportAsDrag(
171: getBackingGraph(), event,
172: TransferHandler.COPY);
173: }
174:
175: // Clear selection if no cell found
176: else {
177: getBackingGraph().clearSelection();
178: }
179: requestFocus();
180: }
181:
182: /**
183: * Overrides the parent implementation to return a different config
184: * if the selection is not empty.
185: */
186: public String getConfigName() {
187: return (!isSelectionEmpty()) ? NODENAME_ENTRYPOPUPMENU
188: : super .getConfigName();
189: }
190:
191: });
192:
193: // Repaints and revalidates on model changes
194: backingGraph.getModel().addGraphModelListener(
195: new GraphModelListener() {
196: public void graphChanged(GraphModelEvent e) {
197: revalidate();
198: repaint();
199: }
200: });
201:
202: // Repaints on selection changes
203: backingGraph.getSelectionModel().addGraphSelectionListener(
204: new GraphSelectionListener() {
205: public void valueChanged(GraphSelectionEvent e) {
206: repaint();
207: }
208: });
209:
210: add(rendererPane);
211: }
212:
213: /**
214: * Returns true if the library contains no entries.
215: *
216: * @return Returns true if the library is empty.
217: */
218: public boolean isEmpty() {
219: return backingGraph.getModel().getRootCount() == 0;
220: }
221:
222: /**
223: * Returns true if the library contains no selectio entries.
224: *
225: * @return Returns true if the selection is empty.
226: */
227: public boolean isSelectionEmpty() {
228: return backingGraph.isSelectionEmpty();
229: }
230:
231: /**
232: * Removes the selection entry from the library.
233: */
234: public void removeEntry() {
235: if (!backingGraph.isSelectionEmpty()) {
236: Object[] cells = backingGraph.getDescendants(backingGraph
237: .getSelectionCells());
238: backingGraph.getModel().remove(cells);
239: }
240: }
241:
242: /**
243: * Brings the selection entry to front (start of list).
244: */
245: public void bringEntryToFront() {
246: if (!backingGraph.isSelectionEmpty())
247: backingGraph.getModel().toFront(
248: backingGraph.getSelectionCells());
249: }
250:
251: /**
252: * Sends the selection entry to back (end of list).
253: */
254: public void sendEntryToBack() {
255: if (!backingGraph.isSelectionEmpty())
256: backingGraph.getModel().toBack(
257: backingGraph.getSelectionCells());
258: }
259:
260: /**
261: * Returns the bounds of the entry at <code>index</code>. This returns a
262: * value regardless of whether an entry at index actually exists.
263: *
264: * @param index
265: * The index that specifies the entry.
266: * @return Returns the bounds for the entry at <code>index</code>.
267: */
268: public Rectangle getBounds(int index) {
269: Rectangle outer = getBounds();
270: int cols = Math.max(outer.width / (entrywidth + hgap), 1);
271: int col = index % cols;
272: int row = index / cols;
273: int x = hgap + col * (entrywidth + hgap);
274: int y = vgap + row * (entryheight + vgap);
275: return new Rectangle(x, y, entrywidth, entryheight);
276: }
277:
278: /**
279: * Returns the index of the entry at the specified location. If no entry
280: * exists at the specified location then -1 is returned.
281: *
282: * @param x
283: * The x position.
284: * @param y
285: * The y position.
286: * @return Returns the index for the specified location or -1.
287: */
288: public int getIndexAt(int x, int y) {
289: Rectangle outer = getBounds();
290: int cols = Math.max(outer.width / (entrywidth + hgap), 1);
291: int col = x / (entrywidth + hgap);
292: int row = y / (entryheight + vgap);
293: int index = row * cols + col;
294: if (index >= 0
295: && index < backingGraph.getModel().getRootCount())
296: return index;
297: return -1;
298:
299: }
300:
301: /**
302: * Paints the library pane.
303: *
304: * @param g
305: * The graphics to paint the library pane to.
306: */
307: public void paint(Graphics g) {
308: super .paint(g);
309: Graphics2D g2 = (Graphics2D) g;
310: if (antiAliased)
311: g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
312: RenderingHints.VALUE_ANTIALIAS_ON);
313: Shape clip = g.getClip();
314: CellView[] entries = backingGraph.getGraphLayoutCache()
315: .getRoots();
316: for (int i = 0; i < entries.length; i++) {
317:
318: // Computes the scale to draw the entry width
319: Rectangle2D bounds = entries[i].getBounds();
320: double scale = Math.min(entrywidth / bounds.getWidth(),
321: entryheight / bounds.getHeight());
322: Rectangle rect = getBounds(i);
323: Rectangle frame = new Rectangle((int) rect.getX() - 2,
324: (int) rect.getY() - 2, entrywidth + 4,
325: entryheight + 4);
326:
327: // Translates and renders the entry using the backing graph
328: if (clip.intersects(frame)) {
329: g2.scale(scale, scale);
330: g.translate(
331: (int) (rect.getX() / scale - bounds.getX()),
332: (int) (rect.getY() / scale - bounds.getY()));
333:
334: // Since the backing graph has not been added to a component
335: // hierarchy we have to use our own cell renderer pane here.
336: paintView(g, entries[i]);
337: g.translate(
338: -(int) (rect.getX() / scale - bounds.getX()),
339: -(int) (rect.getY() / scale - bounds.getY()));
340: g2.scale(1 / scale, 1 / scale);
341: if (getBackingGraph().isCellSelected(
342: entries[i].getCell())) {
343: g.setColor(backingGraph.getHandleColor());
344: g.drawRect(frame.x, frame.y, frame.width,
345: frame.height);
346: }
347: }
348: }
349: }
350:
351: /**
352: * Paints the specified cell view on the local cell renderer pane.
353: *
354: * @param g
355: * @param view
356: */
357: protected void paintView(Graphics g, CellView view) {
358: // Paint parent
359: Component component = view.getRendererComponent(backingGraph,
360: false, false, false);
361: Rectangle2D bounds = view.getBounds();
362: component.setBounds(0, 0, (int) bounds.getWidth(), (int) bounds
363: .getHeight());
364: rendererPane.paintComponent(g, component, this , (int) bounds
365: .getX(), (int) bounds.getY(), (int) bounds.getWidth(),
366: (int) bounds.getHeight(), true);
367:
368: // Paint children recursively
369: CellView[] children = view.getChildViews();
370: for (int i = 0; i < children.length; i++) {
371: paintView(g, children[i]); // recurse
372: }
373: }
374:
375: /**
376: * Overrides the parent method to contain all entries in a matrix with as
377: * many columns as fit into {@link #PREFERRED_WIDTH} with the current
378: * {@link #entrywidth}.
379: *
380: * @return Returns the preferred size.
381: */
382: public Dimension preferredSize() {
383: int rootCount = getBackingGraph().getModel().getRootCount();
384: int cols = Math.max(1, PREFERRED_WIDTH / (entrywidth + vgap));
385: return new Dimension(cols * (entrywidth + vgap), (Math.max(
386: (rootCount + 1) / cols, 1) * (entryheight + hgap)));
387: }
388:
389: /**
390: * Overrides the parent method to contain a single entry.
391: *
392: * @return Returns the minimum size.
393: */
394: public Dimension getMinimumSize() {
395: return new Dimension(entrywidth + vgap, entryheight + hgap);
396: }
397:
398: /**
399: * Returns the backing graph used for rendering entries.
400: *
401: * @return Returns the backing graph.
402: */
403: public JGraph getBackingGraph() {
404: return backingGraph;
405: }
406:
407: /**
408: * Sets the backing graph to be used to render entries.
409: *
410: * @param backingGraph
411: * The backing graph to set.
412: */
413: public void setBackingGraph(JGraph backingGraph) {
414: this .backingGraph = backingGraph;
415: }
416:
417: /**
418: * Returns the library associated with the library pane.
419: *
420: * @return Returns the library.
421: */
422: public JGraphpadLibrary getLibrary() {
423: return library;
424: }
425:
426: /**
427: * Sets the library associated with the library pane.
428: *
429: * @param library
430: * The library to set.
431: */
432: public void setLibrary(JGraphpadLibrary library) {
433: this .library = library;
434: }
435:
436: /**
437: * Returns true if the library uses autoboxing.
438: *
439: * @return Returns the autoBoxing.
440: */
441: public boolean isAutoBoxing() {
442: return autoBoxing;
443: }
444:
445: /**
446: * Sets if the library should use autoboxing.
447: *
448: * @param autoBoxing
449: * The autoBoxing to set.
450: */
451: public void setAutoBoxing(boolean autoBoxing) {
452: this .autoBoxing = autoBoxing;
453: }
454:
455: /**
456: * Returns the height to draw entries.
457: *
458: * @return Returns the entryheight.
459: */
460: public int getEntryheight() {
461: return entryheight;
462: }
463:
464: /**
465: * Sets the height to draw entries.
466: *
467: * @param entryheight
468: * The entryheight to set.
469: */
470: public void setEntryheight(int entryheight) {
471: this .entryheight = entryheight;
472: }
473:
474: /**
475: * Returns the width to draw entries.
476: *
477: * @return Returns the entrywidth.
478: */
479: public int getEntrywidth() {
480: return entrywidth;
481: }
482:
483: /**
484: * Sets the width to draw entries.
485: *
486: * @param entrywidth
487: * The entrywidth to set.
488: */
489: public void setEntrywidth(int entrywidth) {
490: this .entrywidth = entrywidth;
491: }
492:
493: /**
494: * Returns the horizontal gap between entries.
495: *
496: * @return Returns the hgap.
497: */
498: public int getHgap() {
499: return hgap;
500: }
501:
502: /**
503: * Sets the horizontal gap between entries.
504: *
505: * @param hgap
506: * The hgap to set.
507: */
508: public void setHgap(int hgap) {
509: this .hgap = hgap;
510: }
511:
512: /**
513: * Returns the vertical gap between entries.
514: *
515: * @return Returns the vgap.
516: */
517: public int getVgap() {
518: return vgap;
519: }
520:
521: /**
522: * Sets the vertical gap between entries.
523: *
524: * @param vgap
525: * The vgap to set.
526: */
527: public void setVgap(int vgap) {
528: this .vgap = vgap;
529: }
530:
531: /**
532: * Returns true if rendering should be antialiased.
533: *
534: * @return Returns the antiAliased.
535: */
536: public boolean isAntiAliased() {
537: return antiAliased;
538: }
539:
540: /**
541: * Sets if the rendering should be antialiased.
542: *
543: * @param antiAliased
544: * The antiAliased to set.
545: */
546: public void setAntiAliased(boolean antiAliased) {
547: this .antiAliased = antiAliased;
548: }
549:
550: /**
551: * @return Returns the readOnly.
552: */
553: public boolean isReadOnly() {
554: return isReadOnly;
555: }
556:
557: /**
558: * @param readOnly
559: * The readOnly to set.
560: */
561: public void setReadOnly(boolean readOnly) {
562: this .isReadOnly = readOnly;
563: }
564:
565: /**
566: * Returns the parent library pane of the specified component or the
567: * component itself if it is a library pane.
568: *
569: * @return Returns the parent library pane.
570: */
571: public static JGraphpadLibraryPane getParentLibraryPane(
572: Component component) {
573: while (component != null) {
574: if (component instanceof JGraphpadLibraryPane)
575: return (JGraphpadLibraryPane) component;
576: component = component.getParent();
577: }
578: return null;
579: }
580:
581: /**
582: * Utility class to implement autoboxing and to set the {@link #dragging}
583: * flag.
584: */
585: public class LibraryGraphTransferHandler extends
586: GraphTransferHandler {
587:
588: /**
589: * Only allows importing data if the enclosing library is not read-only.
590: */
591: public boolean canImport(JComponent component,
592: DataFlavor[] flavors) {
593: return !isReadOnly;
594: }
595:
596: /**
597: * Overrides the parent method to set the dragging flag and replaces the
598: * selection group with its children if autoboxing is turned on.
599: *
600: * @param c
601: * The component to perform the opeation in.
602: */
603: protected Transferable createTransferable(JComponent c) {
604: dragging = true;
605:
606: // Creates a transferable that contains the children
607: // of the autobox cell (selection cell) and returns them.
608: if (c instanceof JGraph) {
609: JGraph graph = (JGraph) c;
610: if (!graph.isSelectionEmpty()) {
611: Object cell = graph.getSelectionCell();
612: GraphModel model = graph.getModel();
613: if (cell instanceof AutoBoxCell) {
614: int childCount = model.getChildCount(cell);
615: List children = new ArrayList(childCount);
616: for (int i = 0; i < childCount; i++)
617: children.add(model.getChild(cell, i));
618: Object[] cells = graph.getDescendants(graph
619: .order(children.toArray()));
620: return createTransferable(graph, cells); // exit
621: }
622: }
623: }
624: return super .createTransferable(c);
625: }
626:
627: /**
628: * Overrides the parent method to reset the dragging flag in order to
629: * accept external drops.
630: */
631: protected void exportDone(JComponent source, Transferable data,
632: int action) {
633: dragging = false;
634: super .exportDone(source, data, action);
635: }
636:
637: /**
638: * Overrides the parent method to add a group to the dropped cells if
639: * autoboxing is turned on.
640: *
641: * @param graph
642: * The graph to perform the operation in.
643: * @param cells
644: * The cells to be inserted into the library.
645: * @param nested
646: * The attributes to be assigned to the cells.
647: * @param cs
648: * The connections to be established between the cells.
649: * @param pm
650: * The parent map that describes the parent-child relations.
651: * @param dx
652: * The x-offset to translate the cells with.
653: * @param dy
654: * The y-offset to translate the cells with.
655: */
656: protected void handleExternalDrop(JGraph graph, Object[] cells,
657: Map nested, ConnectionSet cs, ParentMap pm, double dx,
658: double dy) {
659: if (autoBoxing) {
660: // Adds a new autoboxcell as the parent cell to all inserted
661: // cells which have no parent in the transfer data.
662: DefaultGraphCell parent = new AutoBoxCell();
663: List tmp = new ArrayList(cells.length + 1);
664: tmp.add(parent);
665: for (int i = 0; i < cells.length; i++) {
666: if (!(cells[i] instanceof AutoBoxCell)) {
667: tmp.add(cells[i]);
668: Object oldParent = graph.getModel().getParent(
669: cells[i]);
670: if (oldParent == null
671: || !pm.getChangedNodes().contains(
672: oldParent))
673: pm.addEntry(cells[i], parent);
674: }
675: }
676: cells = tmp.toArray();
677: }
678: super .handleExternalDrop(graph, cells, nested, cs, pm, dx,
679: dy);
680: }
681:
682: }
683:
684: /**
685: * Utility class to redirect transfer events from the library pane to the
686: * backing graph if the dragging flag is not set.
687: *
688: */
689: public class LibraryTransferHandler extends TransferHandler {
690:
691: /*
692: * (non-Javadoc)
693: */
694: public int getSourceActions(JComponent c) {
695: return COPY;
696: }
697:
698: /*
699: * (non-Javadoc)
700: */
701: public boolean canImport(JComponent comp, DataFlavor[] flavors) {
702: return !dragging
703: && getBackingGraph().getTransferHandler()
704: .canImport(comp, flavors);
705: }
706:
707: /*
708: * (non-Javadoc)
709: */
710: public boolean importData(JComponent comp, Transferable t) {
711: return getBackingGraph().getTransferHandler().importData(
712: getBackingGraph(), t);
713: }
714:
715: }
716:
717: /**
718: * Utility class to establish a listener in a editor's document model and
719: * update the library panes in a tabbed pane.
720: */
721: public static class LibraryTracker extends
722: JGraphpadTreeModelAdapter {
723:
724: /**
725: * References the enclosing editor.
726: */
727: protected JGraphEditor editor;
728:
729: /**
730: * References the tabbed pane to be updated.
731: */
732: protected JTabbedPane tabPane;
733:
734: /**
735: * Holds library, component pairs to find the respective tabs.
736: */
737: protected Map tabs = new Hashtable();
738:
739: /**
740: * Constructs a new library tracker for updating the specified tabPane
741: * using factory to create required components. The library tracker must
742: * be added as a tree model listener to an editor's document model.
743: *
744: * @param tabPane
745: * The pane to be updated on document model changes.
746: * @param editor
747: * The enclosing editor.
748: */
749: public LibraryTracker(JTabbedPane tabPane, JGraphEditor editor) {
750: this .tabPane = tabPane;
751: this .editor = editor;
752: }
753:
754: /**
755: * Creates a new {@link JGraphpadLibraryPane}, wraps it up in a scroll
756: * pane using {@link JGraphEditorFactory#createScrollPane(Component)}
757: * and adds it as a tab to {@link #tabPane} using the library's toString
758: * method to set the tab's title.
759: *
760: * @param arg0
761: * The object that describes the event.
762: */
763: public void treeNodesInserted(TreeModelEvent arg0) {
764: JGraphpadPane padPane = JGraphEditorAction
765: .getJGraphpadPane();
766:
767: Object[] children = arg0.getChildren();
768: for (int i = 0; i < children.length; i++) {
769:
770: if (children[i] instanceof JGraphpadLibrary) {
771: JGraphpadLibrary library = (JGraphpadLibrary) children[i];
772:
773: JInternalFrame frame = null;
774:
775: if (padPane != null) {
776: frame = padPane.getInternalFrame(library
777: .getParent());
778: }
779:
780: if (frame == null || frame.isAncestorOf(tabPane)) {
781: final JGraphpadLibraryPane libraryPane = new JGraphpadLibraryPane(
782: editor, library);
783: Component pane = editor.getFactory()
784: .createScrollPane(libraryPane);
785: tabPane.addTab(getTitle(library), pane);
786: tabPane.setSelectedComponent(pane);
787: tabs.put(children[i], pane);
788:
789: // Transfers the focus to the new library pane
790: // after the component hierarchy has been revalidated.
791: SwingUtilities.invokeLater(new Runnable() {
792: public void run() {
793: libraryPane.requestFocus();
794: }
795: });
796: }
797: }
798: }
799: }
800:
801: /**
802: * Removes the tabs for the removed libraries from the tab pane.
803: */
804: public void treeNodesRemoved(TreeModelEvent arg0) {
805: Object[] children = arg0.getChildren();
806: for (int i = 0; i < children.length; i++) {
807: if (children[i] instanceof JGraphpadLibrary) {
808:
809: // Finds the tab with the scrollpane that contains
810: // the library pane for the removed library and
811: // removes it.
812: Object tab = tabs.remove(children[i]);
813: if (tab instanceof Component)
814: tabPane.remove((Component) tab);
815: }
816: }
817: }
818:
819: /**
820: * Calls {@link #updateTabTitle(JGraphpadLibrary)} for all libraries
821: * that have changed in the tree.
822: *
823: * @param arg0
824: * The object that describes the event.
825: */
826: public void treeNodesChanged(TreeModelEvent arg0) {
827: Object[] children = arg0.getChildren();
828: for (int i = 0; i < children.length; i++)
829: if (children[i] instanceof JGraphpadLibrary)
830: updateTabTitle((JGraphpadLibrary) children[i]);
831: }
832:
833: /**
834: * Invoked to update the title for the tab of the specified library.
835: *
836: * @param library
837: * The library who's title needs to be updated.
838: */
839: protected void updateTabTitle(JGraphpadLibrary library) {
840: Object tab = tabs.get(library);
841: if (tab instanceof Component) {
842: String title = getTitle(library);
843: int index = tabPane.indexOfComponent((Component) tab);
844: tabPane.setTitleAt(index, title);
845: }
846: }
847:
848: /**
849: * Hook for subclassers to return the tab title to be used for the
850: * specified library. This implementation returns the filename-part of a
851: * path.
852: *
853: * @param library
854: * The library to return the title for.
855: * @return Returns a title for <code>library</code>.
856: */
857: protected String getTitle(JGraphpadLibrary library) {
858: String state = (library.isModified()) ? " *" : "";
859: return String.valueOf(library) + state;
860: }
861:
862: }
863:
864: /**
865: * Utility class to identify autoboxing cells.
866: */
867: public static class AutoBoxCell extends DefaultGraphCell {
868: // empty
869: }
870:
871: /**
872: * Provides a factory method to construct a library pane.
873: */
874: public static class FactoryMethod extends JGraphEditorFactoryMethod {
875:
876: /**
877: * Defines the default name for factory methods of this kind.
878: */
879: public static String NAME = "createLibraryPane";
880:
881: /**
882: * References the enclosing editor.
883: */
884: protected JGraphEditor editor;
885:
886: /**
887: * Constructs a new factory method for the specified enclosing editor
888: * using {@link #NAME}.
889: *
890: * @param editor
891: * The editor that contains the factory method.
892: */
893: public FactoryMethod(JGraphEditor editor) {
894: super (NAME);
895: this .editor = editor;
896: }
897:
898: /*
899: * (non-Javadoc)
900: */
901: public Component createInstance(Node configuration) {
902: JTabbedPane tabPane = editor.getFactory().createTabbedPane(
903: JTabbedPane.BOTTOM);
904: tabPane.addMouseListener(new JGraphpadMouseAdapter(editor,
905: JGraphpadPane.NODENAME_DESKTOPPOPUPMENU));
906: LibraryTracker tracker = new LibraryTracker(tabPane, editor);
907: editor.getModel().addTreeModelListener(tracker);
908: return tabPane;
909: }
910:
911: }
912:
913: }
|