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