1
14 package demo.view.graphexplorer;
15
16 import java.awt.BorderLayout;
17 import java.awt.Color;
18 import java.awt.Component;
19 import java.awt.Dimension;
20 import java.awt.EventQueue;
21 import java.awt.FlowLayout;
22 import java.awt.Graphics2D;
23 import java.awt.Window;
24 import java.awt.dnd.DnDConstants;
25 import java.awt.dnd.DragGestureEvent;
26 import java.awt.dnd.DragGestureListener;
27 import java.awt.dnd.DragSource;
28 import java.awt.event.ActionEvent;
29 import java.awt.event.ActionListener;
30 import java.awt.event.MouseAdapter;
31 import java.awt.event.MouseEvent;
32 import java.awt.geom.Rectangle2D;
33 import java.io.File;
34 import java.io.IOException;
35 import java.net.MalformedURLException;
36 import java.net.URI;
37 import java.net.URISyntaxException;
38 import java.net.URL;
39 import java.util.ArrayList;
40 import java.util.Collections;
41 import java.util.Comparator;
42 import java.util.HashMap;
43 import java.util.HashSet;
44 import java.util.Iterator;
45 import java.util.List;
46 import java.util.Locale;
47 import java.util.Map;
48 import java.util.Set;
49
50 import javax.swing.AbstractAction;
51 import javax.swing.Action;
52 import javax.swing.ActionMap;
53 import javax.swing.BorderFactory;
54 import javax.swing.InputMap;
55 import javax.swing.JComponent;
56 import javax.swing.JDialog;
57 import javax.swing.JEditorPane;
58 import javax.swing.JFileChooser;
59 import javax.swing.JFrame;
60 import javax.swing.JLabel;
61 import javax.swing.JMenu;
62 import javax.swing.JMenuBar;
63 import javax.swing.JPanel;
64 import javax.swing.JProgressBar;
65 import javax.swing.JRootPane;
66 import javax.swing.JScrollPane;
67 import javax.swing.JSplitPane;
68 import javax.swing.JToolBar;
69 import javax.swing.JTree;
70 import javax.swing.SwingUtilities;
71 import javax.swing.Timer;
72 import javax.swing.filechooser.FileFilter;
73 import javax.swing.tree.DefaultTreeCellRenderer;
74 import javax.swing.tree.TreePath;
75
76 import demo.view.DemoBase;
77
78 import y.algo.Bfs;
79 import y.anim.AnimationEvent;
80 import y.anim.AnimationFactory;
81 import y.anim.AnimationListener;
82 import y.anim.AnimationPlayer;
83 import y.anim.CompositeAnimationObject;
84 import y.base.DataProvider;
85 import y.base.Edge;
86 import y.base.EdgeCursor;
87 import y.base.Graph;
88 import y.base.Node;
89 import y.base.NodeCursor;
90 import y.base.NodeList;
91 import y.base.NodeMap;
92 import y.geom.YPoint;
93 import y.geom.YRectangle;
94 import y.io.IOHandler;
95 import y.io.ZipGraphMLIOHandler;
96 import y.layout.LayoutTool;
97 import y.util.Comparators;
98 import y.util.D;
99 import y.util.DataProviderAdapter;
100 import y.util.DefaultMutableValue2D;
101 import y.util.GraphCopier;
102 import y.util.Maps;
103 import y.view.DefaultGraph2DRenderer;
104 import y.view.DropSupport;
105 import y.view.EditMode;
106 import y.view.Graph2D;
107 import y.view.Graph2DCopyFactory;
108 import y.view.Graph2DSelectionEvent;
109 import y.view.Graph2DSelectionListener;
110 import y.view.Graph2DTraversal;
111 import y.view.Graph2DUndoManager;
112 import y.view.Graph2DView;
113 import y.view.Graph2DViewActions;
114 import y.view.Graph2DViewMouseWheelZoomListener;
115 import y.view.HitInfo;
116 import y.view.ModelViewManager;
117 import y.view.NodeRealizer;
118 import y.view.ViewAnimationFactory;
119 import y.view.hierarchy.AutoBoundsFeature;
120 import y.view.hierarchy.DefaultHierarchyGraphFactory;
121 import y.view.hierarchy.GroupNodeRealizer;
122 import y.view.hierarchy.HierarchyManager;
123
124
138 public class GraphExplorerDemo extends DemoBase {
139 private static final int DURATION_ADD = 500;
140
141 private final Graph2D baseModel;
142 private final ModelViewManager manager;
143 private final InclusionFilter inclusionFilter;
144 private final GraphExplorerOptionHandler oh;
145 private final Graph2DUndoManager undoManager;
146
147
148 public GraphExplorerDemo() {
149 this(null);
150 }
151
152 public GraphExplorerDemo( final String helpFilePath ) {
153 baseModel = view.getGraph2D();
154 new HierarchyManager(baseModel);
155
156 manager = ModelViewManager.getInstance(baseModel);
157 manager.setInnerGraphBindingAllowed(true);
158 inclusionFilter = new InclusionFilter();
159
160
161 final SearchableTreeViewPanel baseModelView =
162 new SearchableTreeViewPanel(baseModel);
163 final JTree jt = baseModelView.getTree();
164 jt.addMouseListener(new MyDoubleClickListener());
166 jt.setCellRenderer(new MyTreeCellRenderer());
167
168
169 view.setPreferredSize(new Dimension(480, 640));
170 view.setGraph2D((Graph2D) manager.createViewGraph(new MyGraphCopyFactory(), inclusionFilter, false, false));
171 view.setFitContentOnResize(true);
172 view.setGraph2DRenderer(new MyGraph2DRenderer());
173 view.getCanvasComponent().addMouseWheelListener(new Graph2DViewMouseWheelZoomListener());
174 view.getGraph2D().addGraph2DSelectionListener(new SelectionTrigger(baseModel));
175 view.getGraph2D().addDataProvider(
176 EditMode.ORTHOGONAL_ROUTING_DPKEY,
177 new DataProviderAdapter() {
178 public boolean getBool( Object edge ) {
179 final byte id = oh.getLayoutId();
180 return id == GraphExplorerOptionHandler.ID_LAYOUT_HIERARCHIC ||
181 id == GraphExplorerOptionHandler.ID_LAYOUT_ORTHOGONAL;
182 }
183 });
184
185 final Graph2DViewActions actions = new Graph2DViewActions(view);
187 final ActionMap actionMap = view.getCanvasComponent().getActionMap();
188 actionMap.put(Graph2DViewActions.DELETE_SELECTION, new MyDeleteSelectionAction());
189 final InputMap inputMap = actions.createDefaultInputMap(actionMap);
190 view.getCanvasComponent().setActionMap(actionMap);
191 view.getCanvasComponent().setInputMap(JComponent.WHEN_FOCUSED, inputMap);
192
193 final EditMode filteredViewMode = new EditMode() {
195 public void mouseClicked(final double x, final double y) {
196 if (SwingUtilities.isLeftMouseButton(lastClickEvent) &&
197 lastClickEvent.getClickCount() == 2) {
198 final HitInfo info = view.getHitInfoFactory().createHitInfo(x, y,
199 Graph2DTraversal.NODES | Graph2DTraversal.EDGES, true);
200 if (info.hasHitNodes()) {
201 final Node modelNode = manager.getModelNode(info.getHitNode());
202 if (baseModel.getHierarchyManager() != null && !baseModel.getHierarchyManager().isGroupNode(modelNode)) {
203 final NodeList selectedNodes = new NodeList();
204 final Graph2D graph = view.getGraph2D();
205 for (NodeCursor nc = graph.selectedNodes(); nc.ok(); nc.next()) {
206 selectedNodes.add(manager.getModelNode(nc.node()));
207 }
208
209 graph.firePreEvent();
210 try {
211 handleClick(selectedNodes, modelNode, !lastClickEvent.isControlDown());
212 } finally {
213 graph.firePostEvent();
214 }
215
216 baseModelView.repaint();
217 }
218 }
219 }
220 }
221 };
222 filteredViewMode.setOrthogonalEdgeRouting(true);
223 filteredViewMode.getMouseInputMode().setNodeSearchingEnabled(true);
224 filteredViewMode.allowEdgeCreation(false);
225 filteredViewMode.allowNodeCreation(false);
226 view.addViewMode(filteredViewMode);
227
228 final JToolBar jtb = getToolBar();
229 if (jtb == null) {
230 undoManager = null;
231 } else {
232 undoManager = new Graph2DUndoManager(view.getGraph2D());
233 undoManager.setViewContainer(view);
234 jtb.addSeparator();
235 jtb.add(prepare(undoManager.getUndoAction(), "Undo", "resource/undo.png"));
236 jtb.add(prepare(undoManager.getRedoAction(), "Redo", "resource/redo.png"));
237 }
238
239
240 JComponent helpPane = null;
242 if (helpFilePath != null) {
243 final URL url = getClass().getResource(helpFilePath);
244 if (url == null) {
245 System.err.println("Could not locate help file: " + helpFilePath);
246 } else {
247 helpPane = createHelpPane(url);
248 }
249 }
250
251 final JPanel rightPanel = new JPanel(new BorderLayout());
253
254 oh = new GraphExplorerOptionHandler();
255 oh.getItem("General", "Layout").setAttribute(
256 GraphExplorerOptionHandler.ATTRIBUTE_LAYOUT_CALLBACK,
257 new ActionListener() {
258 public void actionPerformed( final ActionEvent e ) {
259 final NodeCursor nc = view.getGraph2D().selectedNodes();
260 final Node node = nc.ok() ? nc.node() : null;
261 doLayout(new LayoutContext(view, true, false, node, baseModel.getHierarchyManager().containsGroups()),
262 oh.getLayoutId());
263 }
264 });
265 rightPanel.add(oh.createEditorComponent(), BorderLayout.NORTH);
266
267 if (helpPane != null) {
268 rightPanel.add(helpPane, BorderLayout.CENTER);
269 }
270
271 final JSplitPane splitPane = newSplitPane(view, rightPanel);
272 splitPane.setBorder(BorderFactory.createEmptyBorder());
273 splitPane.setResizeWeight(1);
274 splitPane.setContinuousLayout(false);
275 contentPane.add(newSplitPane(baseModelView, splitPane), BorderLayout.CENTER);
276
277 final DropSupport dropSupport = new DropSupport(view) {
280 private Node createNodeImpl(
281 final Graph2DView view,
282 final NodeRealizer r,
283 final double x,
284 final double y
285 ) {
286 final Node modelNode = r.getNode();
287 final HierarchyManager hierarchy = baseModel.getHierarchyManager();
288 for (Node n = modelNode; n != null; n = hierarchy.getParentNode(n)) {
289 inclusionFilter.includeNode(n);
290 }
291
292 final Graph2D filteredGraph = view.getGraph2D();
293
294 AutoBoundsFeature abf = null;
295 final Node modelParent = hierarchy.getParentNode(modelNode);
296 if (modelParent != null) {
297 final Node viewParent = manager.getViewNode(modelParent, filteredGraph);
298 if (viewParent != null) {
299 final NodeRealizer nr = filteredGraph.getRealizer(viewParent);
300 abf = nr.getAutoBoundsFeature();
301 }
302 }
303 final boolean oldABF = abf != null && abf.isAutoBoundsEnabled();
304 if (oldABF) {
305 abf.setAutoBoundsEnabled(false);
306 }
307
308 updateFilteredGraph(filteredGraph, true);
309
310 final Node viewNode = manager.getViewNode(modelNode, filteredGraph);
311 filteredGraph.setCenter(viewNode, x, y);
312
313 if (oldABF) {
314 abf.setAutoBoundsEnabled(true);
315 }
316
317 return viewNode;
318 }
319
320 protected Node createNode(
321 final Graph2DView view,
322 final NodeRealizer r,
323 final double x,
324 final double y
325 ) {
326 final Graph2D graph = view.getGraph2D();
327 graph.firePreEvent();
328 try {
329 return createNodeImpl(view, r, x ,y);
330 } finally {
331 graph.firePostEvent();
332 }
333 }
334
335 protected boolean dropNodeRealizer(
336 final Graph2DView view,
337 final NodeRealizer r,
338 final double x,
339 final double y
340 ) {
341 final boolean success = super.dropNodeRealizer(view, r, x, y);
342 if (success) {
343 view.requestFocus();
344 }
345 return success;
346 }
347 };
348 dropSupport.setPreviewEnabled(true);
349
350 final DragSource dragSource = new DragSource();
351 dragSource.createDefaultDragGestureRecognizer(jt, DnDConstants.ACTION_MOVE,
352 new DragGestureListener() {
353 public void dragGestureRecognized(final DragGestureEvent e) {
354 final TreePath[] paths = jt.getSelectionPaths();
355 if (paths != null && paths.length > 0) {
356 final Object value = paths[0].getLastPathComponent();
357 if (value instanceof Node) {
358 final Node modelNode = (Node) value;
359 final HierarchyManager hierarchy = baseModel.getHierarchyManager();
360 if (!hierarchy.isGroupNode(modelNode)) {
361 dropSupport.startDrag(
362 dragSource,
363 baseModel.getRealizer(modelNode),
364 e,
365 DragSource.DefaultMoveDrop);
366 }
367 }
368 }
369 }
370 });
371 }
372
373
383 private static void doLayout( final LayoutContext context, final byte layout ) {
384 switch (layout) {
385 case GraphExplorerOptionHandler.ID_LAYOUT_ORGANIC:
386 LayoutSupport.doOrganicLayout(context);
387 break;
388 case GraphExplorerOptionHandler.ID_LAYOUT_HIERARCHIC:
389 LayoutSupport.doHierarchicLayout(context);
390 break;
391 case GraphExplorerOptionHandler.ID_LAYOUT_BALLOON:
392 LayoutSupport.doBalloonLayout(context);
393 break;
394 case GraphExplorerOptionHandler.ID_LAYOUT_CIRCULAR:
395 LayoutSupport.doCircularLayout(context);
396 break;
397 default:
398 LayoutSupport.doOrthogonalLayout(context);
399 break;
400 }
401 }
402
403
412 private void updateFilteredGraph(
413 final Graph2D graph, final boolean allowRemovals
414 ) {
415 final InclusionFilter filter = inclusionFilter;
416 try {
417 filter.setAllowRemovals(allowRemovals);
418 manager.synchronizeModelToViewGraph(graph);
419 } finally {
420 filter.setAllowRemovals(true);
421 }
422 }
423
424
429 private JToolBar getToolBar() {
430 final JPanel container = contentPane;
431 for (int i = 0, n = container.getComponentCount(); i < n; ++i) {
432 final Component c = container.getComponent(i);
433 if (c instanceof JToolBar) {
434 return (JToolBar) c;
435 }
436 }
437
438 return null;
439 }
440
441
448 private Action prepare( final Action a, final String tip, final String icon ) {
449 a.putValue(Action.SHORT_DESCRIPTION, tip);
450 a.putValue(Action.SMALL_ICON, getIconResource(icon));
451 return a;
452 }
453
454
459 protected JComponent createHelpPane(final URL helpURL) {
460 try {
461 JEditorPane editorPane = new JEditorPane(helpURL);
462 editorPane.setEditable(false);
463 editorPane.setPreferredSize(new Dimension(250, 250));
464 return new JScrollPane(editorPane);
465 } catch (IOException e) {
466 e.printStackTrace();
467 }
468 return null;
469 }
470
471
476 public void addContentTo( final JRootPane rootPane ) {
477 super.addContentTo(rootPane);
478 loadInitialGraph();
479 }
480
481
487 protected Action createLoadAction() {
488 return new LoadAction();
489 }
490
491
497 protected Action createSaveAction() {
498 return new SaveAction("Save Model Graph", baseModel);
499 }
500
501
507 protected Action createSaveFilteredGraphAction() {
508 return new SaveAction("Save Filtered Graph", view.getGraph2D());
509 }
510
511
516 protected JMenuBar createMenuBar() {
517 JMenuBar mb = new JMenuBar();
518
519 JMenu file = new JMenu("File");
520 Action action;
521 action = createLoadAction();
522 if (action != null) {
523 file.add(action);
524 }
525 action = createSaveAction();
526 if (action != null) {
527 file.add(action);
528 }
529 file.add(createSaveFilteredGraphAction());
530 file.addSeparator();
531 file.add(new PrintAction());
532 file.addSeparator();
533 file.add(new ExitAction());
534 mb.add(file);
535
536 JMenu sampleGraphs = new JMenu("Sample Graphs");
538 for (Iterator it = getLoadSampleActions(); it.hasNext();) {
539 sampleGraphs.add((Action) it.next());
540 }
541 mb.add(sampleGraphs);
542
543 return mb;
544 }
545
546 private Iterator getLoadSampleActions() {
547 final String key = "MultiPageLayoutDemo.samples";
548 final Object samples = contentPane.getClientProperty(key);
549 if (samples instanceof List) {
550 return ((List) samples).iterator();
551 } else {
552 final ArrayList list = new ArrayList(3);
553 list.add(createLoadSampleActions(
554 "Pop Artists Relationships",
555 "resource/pop-artists.graphmlz"));
556 list.add(createLoadSampleActions(
557 "yFiles Class Relationships",
558 "resource/yfiles-classes.graphmlz"));
559 list.add(createLoadSampleActions(
560 "yFiles Class Relationships with Nested Packages",
561 "resource/yfiles-classes-and-packages-nested.graphmlz"));
562 contentPane.putClientProperty(key, list);
563 return list.iterator();
564 }
565 }
566
567 private Action createLoadSampleActions(final String name, final String resource) {
568 if (isResourceValid(resource)) {
569 return new LoadSampleGraphAction(name, resource);
570 } else {
571 throw new RuntimeException("Missing resource: " + resource);
572 }
573 }
574
575
581 private boolean isResourceValid(final String resource) {
582 return getClass().getResource(resource) != null;
583 }
584
585
588 protected void loadInitialGraph() {
589 EventQueue.invokeLater(new Runnable() {
590 public void run() {
591 final Iterator it = getLoadSampleActions();
592 if (it.hasNext()) {
593 final Action action = (Action) it.next();
594 action.putValue("onLoaded", new Runnable() {
595 public void run() {
596 final Node mn = baseModel.firstNode();
597 baseModel.unselectAll();
598 baseModel.setSelected(mn, true);
599
600 inclusionFilter.reset();
601 inclusionFilter.includeNode(mn);
602
603 final Graph2D v = view.getGraph2D();
604 v.firePreEvent();
605 try {
606 updateFilteredGraph(v, true);
607
608 final Node vn = manager.getViewNode(mn, v);
609 final NodeRealizer vnr = v.getRealizer(vn);
610
611 view.setZoom(1);
612 view.setCenter(vnr.getCenterX(), vnr.getCenterY());
613
614 handleClick(new NodeList(mn), mn, true);
615 } finally {
616 v.firePostEvent();
617 }
618
619 if (undoManager != null) {
620 undoManager.resetQueue();
621 }
622 }
623 });
624 action.actionPerformed(null);
625 }
626 }
627 });
628 }
629
630
634 protected void loadGraph(String resourceName) {
635 final Graph2D filteredGraph = view.getGraph2D();
636 filteredGraph.clear();
637 filteredGraph.updateViews();
638
639 loadGraph(baseModel, resourceName);
640 }
641
642
648 private void loadGraph(final Graph2D graph, final String resourceName) {
649 URL resource = null;
650 final File file = new File(resourceName);
651 if (file.exists()) {
652 try {
653 resource = file.toURI().toURL();
654 } catch (MalformedURLException e) {
655 D.showError(e.getMessage());
656 return;
657 }
658 } else {
659 final Class aClass = getClass();
660 resource = aClass.getResource(resourceName);
661 if (resource == null) {
662 D.showError("Resource \"" + resourceName + "\" not found in classpath of " + aClass);
663 return;
664 }
665 }
666
667 try {
668 final IOHandler ioh = resource.getFile().endsWith(".graphmlz") ? new ZipGraphMLIOHandler() : createGraphMLIOHandler();
669
670 graph.clear();
671 ioh.read(graph, resource);
672
673 normalizeModel(graph);
674 } catch (IOException ioe) {
675 D.showError("Unexpected error while loading resource \"" + resource + "\" due to " + ioe.getMessage());
676 }
677 graph.setURL(resource);
678 }
679
680 private static void normalizeModel( final Graph2D graph ) {
682 if (!graph.isEmpty()) {
683 final GroupNodeRealizer.StateChangeListener listener =
684 new GroupNodeRealizer.StateChangeListener();
685 final HierarchyManager hierarchy = graph.getHierarchyManager();
686 hierarchy.addHierarchyListener(listener);
687
688 final ArrayList stack = new ArrayList();
690 stack.add(null);
691 while (!stack.isEmpty()) {
692 final Node root = (Node) stack.remove(stack.size() - 1);
693
694 for (NodeCursor nc = hierarchy.getChildren(root); nc.ok(); nc.next()) {
695 final Node node = nc.node();
696 if (hierarchy.isFolderNode(node)) {
697 hierarchy.openFolder(node);
698 stack.add(node);
699 }
700 }
701 }
702 hierarchy.removeHierarchyListener(listener);
703
704 for (NodeCursor nc = graph.nodes(); nc.ok(); nc.next()) {
706 Node node = nc.node();
707 graph.setCenter(node, YPoint.ORIGIN);
708 }
709 LayoutTool.resetPaths(graph, true);
710
711 final Rectangle2D.Double r = new Rectangle2D.Double(0, 0, -1, -1);
712 for (NodeCursor nc = hierarchy.getChildren(null); nc.ok(); nc.next()) {
713 graph.getRealizer(nc.node()).calcUnionRect(r);
714 }
715 }
716 }
717
718
722 protected void registerViewActions() {
723 }
724
725
729 protected void registerViewListeners() {
730 }
731
732
736 protected void registerViewModes() {
737 }
738
739
746 protected void handleClick(
747 final NodeList selectedModelNodes,
748 final Node clickedModelNode,
749 final boolean explore
750 ) {
751 final InclusionFilter filter = inclusionFilter;
752 final Graph2D graph = view.getGraph2D();
753
754 final LayoutContext context = new LayoutContext(
755 view, true, true, manager.getViewNode(clickedModelNode, graph), baseModel.getHierarchyManager().containsGroups());
756
757
758 final byte edgeType = oh.getExplorationEdgeType();
759
760 for (NodeCursor nc = graph.nodes(); nc.ok(); nc.next()) {
761 context.addOldNode(nc.node());
762 }
763 for (EdgeCursor ec = graph.edges(); ec.ok(); ec.next()) {
764 context.addOldEdge(ec.edge());
765 }
766
767 filter.reset();
768 for (NodeCursor nc = selectedModelNodes.nodes(); nc.ok(); nc.next()) {
770 filter.includeNode(nc.node());
771 }
772
773 for (NodeCursor nc = selectedModelNodes.nodes(); nc.ok(); nc.next()) {
775 final Node modelNode = nc.node();
776 for (EdgeCursor ec = getEdges(modelNode, edgeType); ec.ok(); ec.next()) {
777 final Edge modelEdge = ec.edge();
778 if (filter.acceptInsertion(modelEdge.opposite(modelNode))) {
779 filter.includeEdge(modelEdge);
780 }
781 }
782 }
783
784 final byte direction;
785 final byte oppositeEdgeType;
786 switch (edgeType) {
787 case GraphExplorerOptionHandler.EDGE_TYPE_ALL:
788 direction = Bfs.DIRECTION_BOTH;
789 oppositeEdgeType = GraphExplorerOptionHandler.EDGE_TYPE_ALL;
790 break;
791 case GraphExplorerOptionHandler.EDGE_TYPE_OUT:
792 direction = Bfs.DIRECTION_SUCCESSOR;
793 oppositeEdgeType = GraphExplorerOptionHandler.EDGE_TYPE_IN;
794 break;
795 case GraphExplorerOptionHandler.EDGE_TYPE_IN:
796 direction = Bfs.DIRECTION_PREDECESSOR;
797 oppositeEdgeType = GraphExplorerOptionHandler.EDGE_TYPE_OUT;
798 break;
799 default:
800 throw new IllegalStateException("Unsupported edge type: " + edgeType);
801 }
802
803 final NodeList[] modelLayers = Bfs.getLayers(
804 baseModel,
805 selectedModelNodes,
806 explore ? direction : Bfs.DIRECTION_BOTH,
807 Maps.createHashedNodeMap(),
808 2);
809
810 if (modelLayers.length > 1) {
811 if (explore) {
812 int nodesToAdd = oh.getMaxNewNodes();
813 final HashSet tmpModelLayer = new HashSet();
814 tmpModelLayer.addAll(modelLayers[0]);
815 for (NodeCursor nc = modelLayers[1].nodes(); nc.ok(); nc.next()) {
816 final Node modelNode = nc.node();
817 final Node viewNode = manager.getViewNode(modelNode, graph);
818 if (viewNode == null) {
819 if (nodesToAdd > 0) {
820 filter.includeNode(modelNode);
821 --nodesToAdd;
822 }
823 }
824 for (EdgeCursor ec = getEdges(modelNode, oppositeEdgeType); ec.ok(); ec.next()) {
826 final Edge modelEdge = ec.edge();
827 if (tmpModelLayer.contains(modelEdge.opposite(modelNode))) {
828 filter.includeEdge(modelEdge);
829 }
830 }
831 }
832 } else {
833 for (NodeCursor nc = modelLayers[1].nodes(); nc.ok(); nc.next()) {
834 final Node modelNode = nc.node();
835 final Node viewNode = manager.getViewNode(modelNode, graph);
836 if (viewNode != null) {
837 context.addNewNode(viewNode);
838 }
839 }
840 }
841 }
842
843 for (NodeCursor nc = graph.nodes(); nc.ok(); nc.next()) {
844 filter.includeNode(manager.getModelNode(nc.node()));
845 }
846 for (EdgeCursor ec = graph.edges(); ec.ok(); ec.next()) {
847 filter.includeEdge(manager.getModelEdge(ec.edge()));
848 }
849
850 final HierarchyManager mh = baseModel.getHierarchyManager();
851 final HashSet parents = new HashSet();
852 for (Iterator it = inclusionFilter.includedNodes.iterator(); it.hasNext();) {
853 final Node next = (Node) it.next();
854 for (Node p = mh.getParentNode(next); p != null; p = mh.getParentNode(p)) {
855 if (!parents.add(p)) {
856 break;
857 }
858 }
859 }
860 for (Iterator it = parents.iterator(); it.hasNext();) {
861 inclusionFilter.includeNode((Node) it.next());
862 }
863
864 final NodeMap node2Predecessor = Maps.createHashedNodeMap();
865 for (NodeCursor nc = selectedModelNodes.nodes(); nc.ok(); nc.next()) {
866 final Node modelNode = nc.node();
867 for (EdgeCursor ec = getEdges(modelNode, edgeType); ec.ok(); ec.next()) {
868 final Edge modelEdge = ec.edge();
869 final Node modelNeighbor = modelEdge.opposite(modelNode);
870 if (node2Predecessor.get(modelNeighbor) == null) {
871 node2Predecessor.set(modelNeighbor, modelNode);
872 }
873 }
874 }
875
876 updateFilteredGraph(graph, false);
877
878 final NodeList viewNodes = new NodeList();
879 for (NodeCursor nc = selectedModelNodes.nodes(); nc.ok(); nc.next()) {
880 final Node viewNode = manager.getViewNode(nc.node(), graph);
881 if (viewNode != null) {
882 viewNodes.add(viewNode);
883 }
884 }
885 final NodeMap viewDist = Maps.createHashedNodeMap();
886 final int maxDist = oh.getMaxDist();
887 Bfs.getLayers(
888 graph,
889 viewNodes,
890 Bfs.DIRECTION_BOTH,
891 viewDist,
892 maxDist + 1);
893 final HierarchyManager vh = graph.getHierarchyManager();
894 if (maxDist > -1) {
895 for (NodeCursor nc = graph.nodes(); nc.ok(); nc.next()) {
896 final Node viewNode = nc.node();
897 final int dist = viewDist.getInt(viewNode);
898 if ((dist < 0 || maxDist < dist) && !vh.isGroupNode(viewNode)) {
899 filter.excludeNode(manager.getModelNode(viewNode));
900 context.addRemovedNode(viewNode);
901 for (EdgeCursor ec = viewNode.edges(); ec.ok(); ec.next()) {
902 context.addRemovedEdge(ec.edge());
903 }
904 }
905 }
906
907 for (NodeCursor nc = vh.getChildren(null); nc.ok(); nc.next()) {
908 checkEmptyGroups(vh, nc.node(), viewDist, maxDist, context);
909 }
910 } else {
911 for (NodeCursor nc = vh.getChildren(null); nc.ok(); nc.next()) {
912 checkEmptyGroups(vh, nc.node(), viewDist, Integer.MAX_VALUE, context);
913 }
914 }
915
916 for (EdgeCursor ec = graph.edges(); ec.ok(); ec.next()) {
917 if (!context.isOldEdge(ec.edge())) {
918 context.addNewEdge(ec.edge());
919 }
920 }
921
922 if (explore) {
923 for (NodeCursor nc = graph.nodes(); nc.ok(); nc.next()) {
924 final Node viewNode = nc.node();
925 if (!context.isOldNode(viewNode)) {
926 context.addNewNode(viewNode);
927
928 final Node modelNode = manager.getModelNode(viewNode);
929 final Node predecessorNode = manager.getViewNode(
930 (Node) node2Predecessor.get(modelNode), graph);
931 if (predecessorNode == null) {
932 graph.setCenter(viewNode, 0, 0);
933 } else if (!context.isOldNode(viewNode)) {
934 graph.setCenter(viewNode, graph.getCenter(predecessorNode));
935 }
936 }
937 }
938
939 final Rectangle2D.Double r = new Rectangle2D.Double();
940 for (NodeCursor nc = graph.nodes(); nc.ok(); nc.next()) {
941 graph.getRealizer(nc.node()).calcUnionRect(r);
942 }
943 }
944
945 doLayout(context, oh.getLayoutId());
946 }
947
948
953 private boolean checkEmptyGroups(
954 final HierarchyManager vh,
955 final Node viewNode,
956 final DataProvider viewDist,
957 final int maxDist,
958 final LayoutContext context
959 ) {
960 if (vh.isGroupNode(viewNode)) {
961 boolean keepGroup = false;
962 for (NodeCursor nc = vh.getChildren(viewNode); nc.ok(); nc.next()) {
963 if (checkEmptyGroups(vh, nc.node(), viewDist, maxDist, context)) {
964 keepGroup = true;
965 }
966 }
967 if (keepGroup) {
968 return true;
969 }
970
971 final int dist = viewDist.getInt(viewNode);
972 if (0 <= dist && dist <= maxDist) {
973 return true;
974 }
975 context.addRemovedNode(viewNode);
976 for (EdgeCursor ec = viewNode.edges(); ec.ok(); ec.next()) {
977 context.addRemovedEdge(ec.edge());
978 }
979 return false;
980 } else {
981 return !context.isRemovedNode(viewNode);
982 }
983 }
984
985
996 private static EdgeCursor getEdges( final Node node, final byte edgeType ) {
997 switch (edgeType) {
998 case GraphExplorerOptionHandler.EDGE_TYPE_ALL:
999 return node.edges();
1000 case GraphExplorerOptionHandler.EDGE_TYPE_OUT:
1001 return node.outEdges();
1002 case GraphExplorerOptionHandler.EDGE_TYPE_IN:
1003 return node.inEdges();
1004 default:
1005 throw new IllegalArgumentException("Unsupported edge type: " + edgeType);
1006 }
1007 }
1008
1009
1010
1016 private static JSplitPane newSplitPane(
1017 final JComponent left, final JComponent right
1018 ) {
1019 return new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, left, right);
1020 }
1021
1022
1023 public static void main(String[] args) {
1024 EventQueue.invokeLater(new Runnable() {
1025 public void run() {
1026 Locale.setDefault(Locale.ENGLISH);
1027 initLnF();
1028 (new GraphExplorerDemo("resource/graphexplorerhelp.html")).start();
1029 }
1030 });
1031 }
1032
1033
1036 final class MyDeleteSelectionAction extends Graph2DViewActions.DeleteSelectionAction {
1037 protected boolean acceptEdge(Graph2D graph, Edge edge) {
1038 final boolean accept = super.acceptEdge(graph, edge);
1039 if (accept) {
1040 final Edge modelEdge = manager.getModelEdge(edge);
1041 inclusionFilter.excludeEdge(modelEdge);
1042 }
1043 return accept;
1044 }
1045
1046 protected boolean acceptNode(Graph2D graph, Node node) {
1047 final boolean accept = super.acceptNode(graph, node);
1048 if (accept) {
1049 final Node modelNode = manager.getModelNode(node);
1050 inclusionFilter.excludeNode(modelNode);
1051 }
1052 return accept;
1053 }
1054 }
1055
1056 final class MyDoubleClickListener extends MouseAdapter {
1057 public void mouseClicked(MouseEvent e) {
1058 final JTree tree = (JTree) e.getSource();
1059 if (e.getClickCount() == 2) {
1060 final TreePath path = tree.getPathForLocation(e.getX(), e.getY());
1061 if (path != null) {
1062 final Object last = path.getLastPathComponent();
1063 if (last instanceof Node) {
1064 final Node modelNode = (Node) last;
1065 final HierarchyManager hierarchy = baseModel.getHierarchyManager();
1066 if (!hierarchy.isGroupNode(modelNode)) {
1067 final Graph2D graph = view.getGraph2D();
1068 graph.firePreEvent();
1069 try {
1070 createViewNode(modelNode);
1071 } finally {
1072 graph.firePostEvent();
1073 }
1074 }
1075 }
1076 }
1077 }
1078 }
1079
1080 private void createViewNode( final Node clickedModelNode ) {
1081 final NodeMap modelNode2IsNew = Maps.createHashedNodeMap();
1082 final NodeList selectedNodes = new NodeList(baseModel.selectedNodes());
1083 if (!selectedNodes.contains(clickedModelNode)) {
1084 selectedNodes.add(clickedModelNode);
1085 }
1086 collectParentNodes(selectedNodes);
1087 for (NodeCursor nc = selectedNodes.nodes(); nc.ok(); nc.next()) {
1088 Node node = nc.node();
1089 if (!inclusionFilter.isIncluded(node)) {
1090 modelNode2IsNew.setBool(node, true);
1091 inclusionFilter.includeNode(node);
1092 }
1093 }
1094
1095 final Graph2D graph = view.getGraph2D();
1096 final boolean empty = graph.nodeCount() == 0;
1097 updateFilteredGraph(graph, true);
1098
1099 final Node clickedViewNode = manager.getViewNode(clickedModelNode, graph);
1100 final LayoutContext context = new LayoutContext(view, false, true, null, baseModel.getHierarchyManager().containsGroups());
1101 for (NodeCursor nc = selectedNodes.nodes(); nc.ok(); nc.next()) {
1102 final Node modelNode = nc.node();
1103 if (modelNode2IsNew.getBool(modelNode)) {
1104 final Node viewNode = manager.getViewNode(modelNode, graph);
1105 if (viewNode.getGraph() == graph) {
1106 context.addNewNode(viewNode);
1107 }
1108 }
1109 }
1110
1111 doLayout(context, oh.getLayoutId());
1112
1113 final ViewAnimationFactory factory = new ViewAnimationFactory(view);
1114 factory.setQuality(ViewAnimationFactory.HIGH_PERFORMANCE);
1115 final CompositeAnimationObject composite = AnimationFactory.createConcurrency();
1116 for (Iterator it = orderByGroupDepth(graph, context.newNodes()); it.hasNext();) {
1117 final NodeRealizer nr = graph.getRealizer((Node) it.next());
1118 composite.addAnimation(factory.fadeIn(nr, DURATION_ADD));
1119 }
1120
1121 if (empty) {
1122 view.setZoom(1);
1123 view.setCenter(graph.getX(clickedViewNode), graph.getY(clickedViewNode));
1124 } else {
1125 composite.addAnimation(factory.focusView(
1126 Math.max(view.getZoom(), 1),
1127 DefaultMutableValue2D.createView(graph.getLocation(clickedViewNode)),
1128 DURATION_ADD));
1129 }
1130
1131 final AnimationPlayer player = factory.createConfiguredPlayer();
1132 player.addAnimationListener(new AnimationListener() {
1133 public void animationPerformed( final AnimationEvent e ) {
1134 if (AnimationEvent.END == e.getHint()) {
1135 view.requestFocus();
1136 }
1137 }
1138 });
1139 player.animate(composite);
1140 }
1141
1142 private Iterator orderByGroupDepth( final Graph2D graph, final Iterator nodes ) {
1143 final HierarchyManager manager = graph.getHierarchyManager();
1144 if (manager == null) {
1145 return nodes;
1146 } else {
1147 final NodeMap depth = Maps.createHashedNodeMap();
1148 final ArrayList ordered = new ArrayList();
1149 while (nodes.hasNext()) {
1150 final Node node = (Node) nodes.next();
1151 depth.setInt(node, manager.getLocalGroupDepth(node));
1152 ordered.add(node);
1153 }
1154 Collections.sort(ordered, new Comparator() {
1155 public int compare( final Object o1, final Object o2 ) {
1156 return Comparators.compare(depth.getInt(o1), depth.getInt(o2));
1157 }
1158 });
1159 return ordered.iterator();
1160 }
1161 }
1162
1163 private void collectParentNodes( final NodeList nodes ) {
1164 final HierarchyManager hierarchy = baseModel.getHierarchyManager();
1165 final NodeList newNodes = new NodeList();
1166 final HashSet marked = new HashSet();
1167 for (NodeCursor nc = nodes.nodes(); nc.ok(); nc.next()) {
1168 final Node node = nc.node();
1169 for (Node p = hierarchy.getParentNode(node); p != null; p = hierarchy.getParentNode(p)) {
1170 if (marked.add(p)) {
1171 newNodes.add(p);
1172 } else {
1173 break;
1174 }
1175 }
1176 }
1177 marked.clear();
1178 marked.addAll(nodes);
1179 for (NodeCursor nc = newNodes.nodes(); nc.ok(); nc.next()) {
1180 final Node p = nc.node();
1181 if (marked.add(p)) {
1182 nodes.add(p);
1183 }
1184 }
1185 }
1186 }
1187
1188
1192 final class MyGraph2DRenderer extends DefaultGraph2DRenderer {
1193 private static final int OVAL_WIDTH = 25;
1194 private static final int OVAL_HEIGHT = 15;
1195
1196 protected void paintSloppy(Graphics2D gfx, NodeRealizer nr) {
1197 super.paintSloppy(gfx, nr);
1198 }
1199
1200 protected void paint(Graphics2D gfx, NodeRealizer nr) {
1201 super.paint(gfx, nr);
1202 final Color bkpColor = gfx.getColor();
1203 final Graph2D filteredGraph = view.getGraph2D();
1204 final Node viewNode = nr.getNode();
1205 final Node modelNode = manager.getModelNode(viewNode);
1206 final byte edgeType = oh.getExplorationEdgeType();
1207 final int hiddenLinks = getDegree(modelNode, edgeType) - getDegree(viewNode, edgeType); if (hiddenLinks > 0) {
1209 final YRectangle viewNodeRect = filteredGraph.getRectangle(viewNode);
1211 final YPoint ovalLocation = new YPoint(viewNodeRect.x + viewNodeRect.width - OVAL_WIDTH * 0.5,
1212 viewNodeRect.y - OVAL_HEIGHT * 0.5);
1213 gfx.setColor(Color.YELLOW);
1214 gfx.fillOval((int) ovalLocation.x, (int) ovalLocation.y, OVAL_WIDTH, OVAL_HEIGHT);
1215 gfx.setColor(Color.YELLOW.darker());
1216 gfx.drawOval((int) ovalLocation.x, (int) ovalLocation.y, OVAL_WIDTH, OVAL_HEIGHT);
1217 gfx.setColor(Color.BLACK);
1218 final String hiddenLinksString = hiddenLinks + "";
1219 final int stringWidth = gfx.getFontMetrics().stringWidth(hiddenLinksString);
1220 final int stringHeight = gfx.getFontMetrics().getAscent() - 2;
1221 gfx.drawString("" + hiddenLinks, (float) (ovalLocation.x + OVAL_WIDTH * 0.5 - stringWidth * 0.5),
1222 (float) (ovalLocation.y + OVAL_HEIGHT * 0.5 + stringHeight * 0.5));
1223 gfx.setColor(bkpColor);
1224 }
1225 }
1226
1227 private int getDegree( final Node node, final byte edgeType ) {
1228 switch (edgeType) {
1229 case GraphExplorerOptionHandler.EDGE_TYPE_ALL:
1230 return node.degree();
1231 case GraphExplorerOptionHandler.EDGE_TYPE_OUT:
1232 return node.outDegree();
1233 case GraphExplorerOptionHandler.EDGE_TYPE_IN:
1234 return node.inDegree();
1235 default:
1236 throw new IllegalArgumentException("Unsupported edge type: " + edgeType);
1237 }
1238 }
1239 }
1240
1241 final class MyGraphCopyFactory extends Graph2DCopyFactory.HierarchicGraph2DCopyFactory {
1242 private final DefaultHierarchyGraphFactory factory;
1243
1244 MyGraphCopyFactory() {
1245 final GraphCopier.CopyFactory f = baseModel.getGraphCopyFactory();
1246 if (f instanceof DefaultHierarchyGraphFactory) {
1247 factory = (DefaultHierarchyGraphFactory) f;
1248 } else {
1249 factory = new DefaultHierarchyGraphFactory();
1250 }
1251 }
1252
1253 public Graph createGraph() {
1254 final Graph2D g = new Graph2D();
1255 final HierarchyManager hierarchy = new HierarchyManager(g);
1256 hierarchy.setGraphFactory(factory);
1257 g.setGraphCopyFactory(this);
1258 return g;
1259 }
1260
1261 public void postCopyGraphData(
1262 final Graph sourceGraph,
1263 final Graph targetGraph,
1264 final Map nodeMap,
1265 final Map edgeMap
1266 ) {
1267 final HashMap tmp = new HashMap();
1268 tmp.putAll(nodeMap);
1269
1270 for (NodeCursor nc = targetGraph.nodes(); nc.ok(); nc.next()) {
1272 final Node view = nc.node();
1273 final Node model = manager.getModelNode(view);
1274 if (!tmp.containsKey(model)) {
1275 tmp.put(model, view);
1276 }
1277 }
1278
1279 super.postCopyGraphData(sourceGraph, targetGraph, tmp, edgeMap);
1280 }
1281 }
1282
1283
1288 final class MyTreeCellRenderer extends DefaultTreeCellRenderer {
1289 public Component getTreeCellRendererComponent(
1290 JTree tree,
1291 Object value,
1292 boolean sel,
1293 boolean expanded,
1294 boolean leaf,
1295 int row,
1296 boolean hasFocus
1297 ) {
1298 Component comp = super.getTreeCellRendererComponent(
1299 tree, value, sel, expanded, leaf, row, hasFocus);
1300 if (value instanceof Node && !inclusionFilter.isIncluded((Node) value)) {
1301 comp.setForeground(Color.GRAY);
1302 } else if(value instanceof Graph) {
1303 comp = new JLabel("Nodes: " + ((Graph) value).nodeCount());
1304 }
1305 return comp;
1306 }
1307 }
1308
1309
1310 final class SelectionTrigger implements Graph2DSelectionListener {
1311 private final Timer timer;
1312 private Graph2DSelectionEvent lastEvent;
1313
1314 SelectionTrigger(final Graph2D modelGraph) {
1315 timer = new Timer(100, new ActionListener() {
1316 public void actionPerformed(final ActionEvent e) {
1317 if (lastEvent != null) {
1318 handleEvent(lastEvent);
1319 }
1320 }
1321
1322
1325 private void handleEvent(final Graph2DSelectionEvent e) {
1326 boolean modelIsSource = (e.getGraph2D() == modelGraph); if (e.isNodeSelection()) {
1328 if (modelIsSource) {
1329 for (Iterator iter = manager.viewGraphs(); iter.hasNext();) {
1330 ((Graph2D) iter.next()).unselectAll();
1331 }
1332 } else {
1333 baseModel.unselectAll();
1334 }
1335
1336 for (NodeCursor nc = e.getGraph2D().selectedNodes(); nc.ok(); nc.next()) {
1337 final Node n = nc.node();
1338 if (modelIsSource) {
1339 for (Iterator iter = manager.viewGraphs(); iter.hasNext();) {
1340 final Graph2D viewGraph = ((Graph2D) iter.next());
1341 Node viewNode = manager.getViewNode(n, viewGraph);
1342 if (viewNode != null) {
1343 viewGraph.setSelected(viewNode, true);
1344 }
1345 }
1346 } else {
1347 Node orig = manager.getModelNode(n);
1348 if (orig != null) {
1349 baseModel.setSelected(orig, true);
1350 }
1351 }
1352 }
1353 } else if (e.isEdgeSelection()) {
1354 if (modelIsSource) {
1355 for (Iterator iter = manager.viewGraphs(); iter.hasNext();) {
1356 ((Graph2D) iter.next()).unselectAll();
1357 }
1358 } else {
1359 baseModel.unselectAll();
1360 }
1361
1362 for (EdgeCursor ec = e.getGraph2D().selectedEdges(); ec.ok(); ec.next()) {
1363 final Edge edge = ec.edge();
1364 if (modelIsSource) {
1365 for (Iterator iter = manager.viewGraphs(); iter.hasNext();) {
1366 final Graph2D viewGraph = ((Graph2D) iter.next());
1367 Edge viewEdge = manager.getViewEdge(edge, viewGraph);
1368 if (viewEdge != null) {
1369 viewGraph.setSelected(viewEdge, true);
1370 }
1371 }
1372 } else {
1373 Edge orig = manager.getModelEdge(edge);
1374 if (orig != null) {
1375 baseModel.setSelected(orig, true);
1376 }
1377 }
1378 }
1379 }
1380 }
1381 });
1382 timer.setRepeats(false);
1383 }
1384
1385 public void onGraph2DSelectionEvent(final Graph2DSelectionEvent e) {
1386 if (e.isNodeSelection() || e.isEdgeSelection()) {
1387 lastEvent = e;
1388 timer.restart();
1389 }
1390 }
1391 }
1392
1393
1394
1397 abstract class AbstractLoadAction extends AbstractAction {
1398 AbstractLoadAction( final String name ) {
1399 super(name);
1400 }
1401
1402
1407 void load( final String resource, final String name ) {
1408 final Object onLoaded = getValue("onLoaded");
1409 putValue("onLoaded", null);
1410
1411 EventQueue.invokeLater(new Runnable() {
1412 public void run() {
1413 final JDialog pd = createProgressDialog(name);
1414
1415 final Graph2D filteredGraph = view.getGraph2D();
1416 filteredGraph.clear();
1417 filteredGraph.updateViews();
1418
1419 (new Thread(new Runnable() {
1420 public void run() {
1421 loadGraph(baseModel, resource);
1422 EventQueue.invokeLater(new Runnable() {
1423 public void run() {
1424 if (undoManager != null) {
1425 undoManager.resetQueue();
1426 }
1427
1428 pd.setVisible(false);
1429 pd.dispose();
1430
1431 if (onLoaded instanceof Runnable) {
1432 ((Runnable) onLoaded).run();
1433 }
1434 }
1435 });
1436 }
1437 })).start();
1438
1439 pd.setVisible(true);
1440 }
1441 });
1442 }
1443
1444
1450 private JDialog createProgressDialog( final String name ) {
1451 final String title = "Loading " + name;
1452 final JDialog jd = new JDialog(getFrame(), title, true);
1453
1454 final JProgressBar jpb = new JProgressBar(0, 1);
1455 jpb.setString(title);
1456 jpb.setIndeterminate(true);
1457
1458 final JLabel lbl = new JLabel(title);
1459 final JPanel progressPane = new JPanel(new BorderLayout());
1460 progressPane.add(lbl, BorderLayout.NORTH);
1461 progressPane.add(jpb, BorderLayout.CENTER);
1462 final JPanel contentPane = new JPanel(new FlowLayout());
1463 contentPane.add(progressPane);
1464
1465 jd.setContentPane(contentPane);
1466 jd.pack();
1467 jd.setLocationRelativeTo(null);
1468 jd.setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE);
1469
1470 return jd;
1471 }
1472
1473 private JFrame getFrame() {
1474 final Window ancestor = SwingUtilities.getWindowAncestor(contentPane);
1475 if (ancestor instanceof JFrame) {
1476 return (JFrame) ancestor;
1477 } else {
1478 return null;
1479 }
1480 }
1481 }
1482
1483
1486 final class LoadSampleGraphAction extends AbstractLoadAction {
1487 private final String name;
1488 private final String resource;
1489
1490 LoadSampleGraphAction(final String name, final String resource) {
1491 super(name);
1492 this.name = name;
1493 this.resource = resource;
1494 }
1495
1496 public void actionPerformed(ActionEvent e) {
1497 load(resource, name);
1498 }
1499 }
1500
1501
1502 protected class LoadAction extends AbstractLoadAction {
1503 JFileChooser chooser;
1504
1505 public LoadAction() {
1506 super("Load Model Graph");
1507 chooser = null;
1508 }
1509
1510 public void actionPerformed(ActionEvent e) {
1511 if (chooser == null) {
1512 chooser = new JFileChooser();
1513 chooser.setAcceptAllFileFilterUsed(false);
1514 chooser.addChoosableFileFilter(new FileFilter() {
1515 public boolean accept(File f) {
1516 return f.isDirectory() || f.getName().endsWith(".graphml");
1517 }
1518
1519 public String getDescription() {
1520 return "GraphML Format (.graphml)";
1521 }
1522 });
1523 chooser.addChoosableFileFilter(new FileFilter() {
1524 public boolean accept(File f) {
1525 return f.isDirectory() || f.getName().endsWith(".graphmlz");
1526 }
1527
1528 public String getDescription() {
1529 return "Zipped GraphML Format (.graphmlz)";
1530 }
1531 });
1532 }
1533 if (chooser.showOpenDialog(contentPane) == JFileChooser.APPROVE_OPTION) {
1534 try {
1535 final URL resource = chooser.getSelectedFile().toURI().toURL();
1536 final String ln = resource.getFile();
1537 load(ln, (new File(ln)).getName());
1538 } catch (MalformedURLException mue) {
1539 mue.printStackTrace();
1540 }
1541 }
1542 }
1543 }
1544
1545
1549 protected class SaveAction extends AbstractAction {
1550 private JFileChooser chooser;
1551 private Graph2D graph;
1552
1553
1556 public SaveAction(final String name, final Graph2D graph) {
1557 super(name);
1558 chooser = null;
1559 this.graph = graph;
1560 }
1561
1562 private void setFileChooser(final JFileChooser chooser, final File file) {
1563 FileFilter[] filters = chooser.getChoosableFileFilters();
1564 for (int i = 0; i < filters.length; i++) {
1565 if (filters[i].accept(file)) {
1566 chooser.setFileFilter(filters[i]);
1567 return;
1568 }
1569 }
1570 }
1571
1572 public void actionPerformed(ActionEvent e) {
1573 if (chooser == null) {
1574 chooser = new JFileChooser();
1575 chooser.setAcceptAllFileFilterUsed(false);
1576 chooser.addChoosableFileFilter(new FileFilter() {
1577 public boolean accept(File f) {
1578 return f.isDirectory() || f.getName().endsWith(".graphml");
1579 }
1580
1581 public String getDescription() {
1582 return "GraphML Format (.graphml)";
1583 }
1584 });
1585 chooser.addChoosableFileFilter(new FileFilter() {
1586 public boolean accept(File f) {
1587 return f.isDirectory() || f.getName().endsWith(".graphmlz");
1588 }
1589
1590 public String getDescription() {
1591 return "Zipped GraphML Format (.graphmlz)";
1592 }
1593 });
1594 }
1595
1596 final URL url = view.getGraph2D().getURL();
1597 if (url != null && "file".equals(url.getProtocol())) {
1598 try {
1599 final File file = new File(new URI(url.toString()));
1600 chooser.setSelectedFile(file);
1601 setFileChooser(chooser, file);
1602 } catch (URISyntaxException e1) {
1603 }
1605 }
1606
1607 if (chooser.showSaveDialog(contentPane) == JFileChooser.APPROVE_OPTION) {
1608 IOHandler ioh;
1609 String name = chooser.getSelectedFile().toString();
1610 final FileFilter filter = chooser.getFileFilter();
1611 if (filter.accept(new File("file.graphml"))) {
1612 if (!name.endsWith(".graphml")) {
1613 name += ".graphml";
1614 }
1615 ioh = createGraphMLIOHandler();
1616 } else {
1617 if (!name.endsWith(".graphmlz")) {
1618 name += ".graphmlz";
1619 }
1620 ioh = new ZipGraphMLIOHandler();
1621 }
1622
1623 try {
1624 ioh.write(graph, name);
1625 } catch (IOException ioe) {
1626 D.show(ioe);
1627 }
1628 }
1629 }
1630 }
1631
1632
1633
1638 static final class InclusionFilter implements ModelViewManager.Filter {
1639 private final Set includedNodes;
1640 private final Set includedEdges;
1641
1642 private boolean allowRemovals;
1643
1644 InclusionFilter() {
1645 includedNodes = new HashSet();
1646 includedEdges = new HashSet();
1647 allowRemovals = true;
1648 }
1649
1650 void reset() {
1651 includedNodes.clear();
1652 includedEdges.clear();
1653 }
1654
1655 boolean getAllowRemovals() {
1656 return allowRemovals;
1657 }
1658
1659 void setAllowRemovals(final boolean allowRemovals) {
1660 this.allowRemovals = allowRemovals;
1661 }
1662
1663 boolean isIncluded(final Node modelNode) {
1664 return includedNodes.contains(modelNode);
1665 }
1666
1667 boolean includeNode(final Node modelNode) {
1668 return includedNodes.add(modelNode);
1669 }
1670
1671 boolean includeEdge(final Edge modelEdge) {
1672 return includedEdges.add(modelEdge);
1673 }
1674
1675 boolean excludeNode(final Node modelNode) {
1676 for(EdgeCursor ec = modelNode.edges(); ec.ok(); ec.next()) {
1677 includedEdges.remove(ec.edge());
1678 }
1679 return includedNodes.remove(modelNode);
1680 }
1681
1682 boolean excludeEdge(final Edge modelEdge) {
1683 return includedEdges.remove(modelEdge);
1684 }
1685
1686 public boolean acceptInsertion(final Node node) {
1687 return includedNodes.contains(node);
1688 }
1689
1690 public boolean acceptInsertion(final Edge edge) {
1691 return includedEdges.contains(edge);
1692 }
1693
1694 public boolean acceptRemoval(final Node node) {
1695 return allowRemovals;
1696 }
1697
1698 public boolean acceptRemoval(final Edge edge) {
1699 return allowRemovals;
1700 }
1701
1702 public boolean acceptRetention(final Node node) {
1703 return !allowRemovals || includedNodes.contains(node);
1704 }
1705
1706 public boolean acceptRetention(final Edge edge) {
1707 return !allowRemovals || includedEdges.contains(edge);
1708 }
1709 }
1710}
1711