1
28 package demo.layout.multipage;
29
30 import demo.view.DemoBase;
31 import demo.view.DemoDefaults;
32 import y.base.DataProvider;
33 import y.base.Edge;
34 import y.base.Graph;
35 import y.base.Node;
36 import y.base.NodeCursor;
37 import y.geom.YDimension;
38 import y.io.IOHandler;
39 import y.io.ZipGraphMLIOHandler;
40 import y.layout.LayoutTool;
41 import y.layout.Layouter;
42 import y.layout.hierarchic.IncrementalHierarchicLayouter;
43 import y.layout.multipage.LayoutCallback;
44 import y.layout.multipage.MultiPageLayout;
45 import y.layout.multipage.MultiPageLayouter;
46 import y.layout.multipage.NodeInfo;
47 import y.layout.organic.SmartOrganicLayouter;
48 import y.layout.orthogonal.CompactOrthogonalLayouter;
49 import y.layout.orthogonal.OrthogonalLayouter;
50 import y.option.Editor;
51 import y.option.OptionHandler;
52 import y.option.TableEditorFactory;
53 import y.util.D;
54 import y.util.DataProviderAdapter;
55 import y.util.GraphCopier;
56 import y.view.Drawable;
57 import y.view.EdgeRealizer;
58 import y.view.Graph2D;
59 import y.view.Graph2DLayoutExecutor;
60 import y.view.Graph2DTraversal;
61 import y.view.Graph2DView;
62 import y.view.Graph2DViewMouseWheelZoomListener;
63 import y.view.HitInfo;
64 import y.view.LineType;
65 import y.view.NavigationMode;
66 import y.view.NodeRealizer;
67 import y.view.ViewMode;
68 import y.view.hierarchy.HierarchyManager;
69
70 import javax.swing.AbstractAction;
71 import javax.swing.Action;
72 import javax.swing.BorderFactory;
73 import javax.swing.JButton;
74 import javax.swing.JComponent;
75 import javax.swing.JDialog;
76 import javax.swing.JEditorPane;
77 import javax.swing.JFileChooser;
78 import javax.swing.JFrame;
79 import javax.swing.JLabel;
80 import javax.swing.JMenu;
81 import javax.swing.JMenuBar;
82 import javax.swing.JPanel;
83 import javax.swing.JProgressBar;
84 import javax.swing.JRootPane;
85 import javax.swing.JScrollPane;
86 import javax.swing.JSplitPane;
87 import javax.swing.JTextField;
88 import javax.swing.JToolBar;
89 import javax.swing.JTree;
90 import javax.swing.SwingUtilities;
91 import javax.swing.filechooser.FileFilter;
92 import javax.swing.tree.DefaultTreeCellRenderer;
93 import javax.swing.tree.TreePath;
94 import java.awt.BorderLayout;
95 import java.awt.Color;
96 import java.awt.Component;
97 import java.awt.Cursor;
98 import java.awt.Dimension;
99 import java.awt.EventQueue;
100 import java.awt.FlowLayout;
101 import java.awt.Graphics2D;
102 import java.awt.Rectangle;
103 import java.awt.Window;
104 import java.awt.event.ActionEvent;
105 import java.awt.event.MouseAdapter;
106 import java.awt.event.MouseEvent;
107 import java.awt.geom.Rectangle2D;
108 import java.beans.PropertyChangeEvent;
109 import java.beans.PropertyChangeListener;
110 import java.io.File;
111 import java.io.IOException;
112 import java.net.MalformedURLException;
113 import java.net.URI;
114 import java.net.URISyntaxException;
115 import java.net.URL;
116 import java.util.ArrayList;
117 import java.util.HashMap;
118 import java.util.Iterator;
119 import java.util.List;
120 import java.util.Locale;
121 import java.util.Map;
122
123
158 public class MultiPageLayoutDemo extends DemoBase {
159 private static final Color PAGE_BACKGROUND = new Color(230, 230, 230);
160
161 private final Graph2D baseModel;
162 private final MultiPageGraph2DBuilder pageBuilder;
163 private final MultiPageLayoutOptionHandler oh;
164 private JTextField pageNumberTextField;
165
166 private List pageList;
167 private Map id2LocationInfo;
168
169 private int previousPageIndex;
170 private int currentPageIndex;
171
172 public MultiPageLayoutDemo() {
173 this(null);
174 }
175
176 public MultiPageLayoutDemo( final String helpFilePath ) {
177 oh = new MultiPageLayoutOptionHandler();
178 oh.addDrawPageChangeListener(new PropertyChangeListener() {
179 public void propertyChange( final PropertyChangeEvent e ) {
180 view.updateView();
181 }
182 });
183 baseModel = view.getGraph2D();
184 new HierarchyManager(baseModel);
185 pageBuilder = new MultiPageGraph2DBuilder(null, null);
186 id2LocationInfo = new HashMap();
187
188 view.setGraph2D(new Graph2D());
190 view.setContentPolicy(Graph2DView.CONTENT_POLICY_BACKGROUND_DRAWABLES);
191 view.addBackgroundDrawable(new PageBorderDrawer());
192 setPageGraph(0);
193 view.setFitContentOnResize(true);
194 Graph2DViewMouseWheelZoomListener mouseWheelZoomListener = new Graph2DViewMouseWheelZoomListener();
195 mouseWheelZoomListener.setCenterZooming(false);
196 mouseWheelZoomListener.addToCanvas(view);
197
198 final MultiPageOverview overview = new MultiPageOverview(view, pageBuilder);
199 overview.addViewMode(new OverviewViewMode());
200
201 view.addViewMode(new PageViewMode() {
202 void setActive( final Graph2D graph, final Node node, final boolean active ) {
203 super.setActive(graph, node, active);
204 highlight(node, active);
205 }
206
207 private void highlight( final Node node, final boolean active ) {
208 final int refPageNo = pageBuilder.getReferencedPageNo(node);
209 if (refPageNo > -1) {
210 final Graph2D g = overview.getGraph2D();
211 final Node page = find(g, Integer.toString(refPageNo + 1));
212 if (page != null) {
213 g.getRealizer(page).setFillColor(
214 active
215 ? DemoDefaults.DEFAULT_CONTRAST_COLOR
216 : PAGE_BACKGROUND);
217 overview.updateView();
218 }
219 }
220 }
221
222 private Node find( final Graph2D g, final String label ) {
223 for (NodeCursor nc = g.nodes(); nc.ok(); nc.next()) {
224 final NodeRealizer nr = g.getRealizer(nc.node());
225 if (nr.labelCount() > 0 && label.equals(nr.getLabelText())) {
226 return nc.node();
227 }
228 }
229 return null;
230 }
231 });
232 view.addViewMode(new NavigationMode());
233
234
235 JComponent helpPane = null;
237 if (helpFilePath != null) {
238 final URL url = getResource(helpFilePath);
239 if (url == null) {
240 System.err.println("Could not locate help file: " + helpFilePath);
241 } else {
242 helpPane = createHelpPane(url);
243 }
244 }
245
246 final JPanel rightPanel = new JPanel(new BorderLayout());
248 rightPanel.setMinimumSize(new Dimension(180, 240));
249 rightPanel.add(createOptionTable(oh), BorderLayout.NORTH);
250
251 if (helpPane != null) {
252 rightPanel.add(helpPane, BorderLayout.CENTER);
253 }
254
255 final JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, view, rightPanel);
256 splitPane.setBorder(BorderFactory.createEmptyBorder());
257 splitPane.setResizeWeight(1);
258 splitPane.setContinuousLayout(false);
259 contentPane.add(splitPane, BorderLayout.CENTER);
260
261 final SearchableTreeViewPanel baseModelView = new SearchableTreeViewPanel(baseModel);
262 final JTree jt = baseModelView.getTree();
263 jt.addMouseListener(new MyDoubleClickListener());
265 jt.setCellRenderer(new MyTreeCellRenderer());
266
267 final JPanel navPane = new JPanel(new BorderLayout());
268 navPane.add(baseModelView, BorderLayout.CENTER);
269 navPane.add(overview, BorderLayout.NORTH);
270
271 final JSplitPane splitPane2 = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, navPane, splitPane);
272 splitPane2.setBorder(BorderFactory.createEmptyBorder());
273 contentPane.add(splitPane2, BorderLayout.CENTER);
274 }
275
276
280 protected void registerViewActions() {
281 }
282
283
287 protected void registerViewListeners() {
288 }
289
290
294 protected void registerViewModes() {
295 }
296
297
301 protected boolean isDeletionEnabled() {
302 return false;
303 }
304
305 private JTextField getPageNumberTextField() {
306 if (pageNumberTextField == null) {
307 final JButton dummy = new JButton(new FirstPageAction());
308 final Dimension size = dummy.getPreferredSize();
309 pageNumberTextField = new JTextField();
310 pageNumberTextField.setHorizontalAlignment(JTextField.CENTER);
311 pageNumberTextField.setEditable(false);
312 pageNumberTextField.setColumns(11);
313 pageNumberTextField.setMaximumSize(new Dimension(80, size.height));
314 pageNumberTextField.setPreferredSize(new Dimension(80, size.height));
315 }
316 return pageNumberTextField;
317 }
318
319
322 protected JToolBar createToolBar() {
323 final JToolBar toolBar = super.createToolBar();
324 toolBar.addSeparator();
325 toolBar.add(new FirstPageAction());
326 toolBar.add(new PreviousPageAction());
327 toolBar.add(getPageNumberTextField());
328 toolBar.add(new NextPageAction());
329 toolBar.add(new LastPageAction());
330 toolBar.addSeparator(new Dimension(10, 0));
331 toolBar.add(new GoBackAction());
332 toolBar.addSeparator();
333 toolBar.add(createActionControl(new AbstractAction("Layout", SHARED_LAYOUT_ICON) {
334 public void actionPerformed(final ActionEvent e) {
335 doLayoutInBackground();
336 }
337 }));
338 return toolBar;
339 }
340
341
344 protected boolean isUndoRedoEnabled() {
345 return false;
346 }
347
348
351 protected boolean isClipboardEnabled() {
352 return false;
353 }
354
355 protected JMenuBar createMenuBar() {
356 JMenuBar mb = new JMenuBar();
357
358 JMenu file = new JMenu("File");
359 file.add(createLoadAction());
360 file.add(createSaveAction());
361 file.add(new SaveAction("Save Page Graph", view));
362 file.addSeparator();
363 file.add(new PrintAction());
364 file.addSeparator();
365 file.add(new ExitAction());
366 mb.add(file);
367
368 JMenu sampleGraphs = new JMenu("Sample Graphs");
369 for (Iterator it = getLoadSampleActions(); it.hasNext();) {
370 sampleGraphs.add((Action) it.next());
371 }
372 mb.add(sampleGraphs);
373
374 JMenu sampleSettings = new JMenu("Sample Settings");
375 sampleSettings.add(new SetOptionsAction(MultiPageLayoutOptionHandler.OPTIONS_NETWORK_SMALL_DISPLAY));
376 sampleSettings.add(new SetOptionsAction(MultiPageLayoutOptionHandler.OPTIONS_NETWORK_LARGE_DISPLAY));
377 sampleSettings.add(new SetOptionsAction(MultiPageLayoutOptionHandler.OPTIONS_CLASS_DIAGRAM_SMALL_DISPLAY));
378 sampleSettings.add(new SetOptionsAction(MultiPageLayoutOptionHandler.OPTIONS_CLASS_DIAGRAM_LARGE_DISPLAY));
379 mb.add(sampleSettings);
380
381 return mb;
382 }
383
384 private Iterator getLoadSampleActions() {
385 final String key = "MultiPageLayoutDemo.samples";
386 final Object samples = contentPane.getClientProperty(key);
387 if (samples instanceof List) {
388 return ((List) samples).iterator();
389 } else {
390 final ArrayList list = new ArrayList(3);
391 list.add(createLoadSampleActions(
392 "Pop Artists",
393 "resource/pop-artists.graphmlz"));
394 list.add(createLoadSampleActions(
395 "yFiles Classes",
396 "resource/yfiles-classes.graphmlz"));
397 list.add(createLoadSampleActions(
398 "yFiles Classes and Packages",
399 "resource/yfiles-classes-and-packages-nested.graphmlz"));
400 contentPane.putClientProperty(key, list);
401 return list.iterator();
402 }
403 }
404
405 private Action createLoadSampleActions(final String name, final String resource) {
406 if (isResourceValid(resource)) {
407 return new LoadSampleGraphAction(name, resource);
408 } else {
409 throw new RuntimeException("Missing resource: " + resource);
410 }
411 }
412
413
419 private boolean isResourceValid(final String resource) {
420 return getResource(resource) != null;
421 }
422
423
428 private JComponent createOptionTable(OptionHandler oh) {
429 oh.setAttribute(
430 TableEditorFactory.ATTRIBUTE_INFO_POSITION,
431 TableEditorFactory.InfoPosition.NONE);
432 oh.setAttribute(
433 TableEditorFactory.ATTRIBUTE_USE_ITEM_NAME_AS_TOOLTIP_FALLBACK,
434 Boolean.TRUE);
435
436 TableEditorFactory tef = new TableEditorFactory();
437 Editor editor = tef.createEditor(oh);
438
439 JComponent optionComponent = editor.getComponent();
440 optionComponent.setPreferredSize(new Dimension(400, 240));
441 optionComponent.setMaximumSize(new Dimension(400, 240));
442 return optionComponent;
443 }
444
445
450 protected JComponent createHelpPane(final URL helpURL) {
451 try {
452 JEditorPane editorPane = new JEditorPane(helpURL);
453 editorPane.setEditable(false);
454 editorPane.setPreferredSize(new Dimension(250, 250));
455 return new JScrollPane(editorPane);
456 } catch (IOException e) {
457 e.printStackTrace();
458 }
459 return null;
460 }
461
462
468 private JDialog createProgressDialog( final String name ) {
469 final JDialog jd = new JDialog(getFrame(), name, true);
470
471 final JProgressBar jpb = new JProgressBar(0, 1);
472 jpb.setString(name);
473 jpb.setIndeterminate(true);
474
475 final JLabel lbl = new JLabel(name);
476 final JPanel progressPane = new JPanel(new BorderLayout());
477 progressPane.add(lbl, BorderLayout.NORTH);
478 progressPane.add(jpb, BorderLayout.CENTER);
479 final JPanel contentPane = new JPanel(new FlowLayout());
480 contentPane.add(progressPane);
481
482 jd.setContentPane(contentPane);
483 jd.pack();
484 jd.setLocationRelativeTo(null);
485 jd.setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE);
486
487 return jd;
488 }
489
490
495 private JFrame getFrame() {
496 final Window ancestor = SwingUtilities.getWindowAncestor(contentPane);
497 if (ancestor instanceof JFrame) {
498 return (JFrame) ancestor;
499 } else {
500 return null;
501 }
502 }
503
504
509 public void addContentTo( final JRootPane rootPane ) {
510 super.addContentTo(rootPane);
511 loadInitialGraph();
512 }
513
514
518 private void setPageGraph( final int page ) {
519 if (pageList != null && !pageList.isEmpty()) {
520 previousPageIndex = currentPageIndex;
521 currentPageIndex = Math.min(Math.max(page, 0), pageList.size() - 1);
522 final Graph2D currentPageGraph = (Graph2D) pageList.get(currentPageIndex);
523 getPageNumberTextField().setText((currentPageIndex + 1) + " / " + pageList.size());
524 view.setGraph2D(currentPageGraph);
525 } else {
526 view.setGraph2D(new Graph2D());
527 }
528 view.fitContent();
529 view.updateView();
530 }
531
532
539 private void jump( final Object target, final String focus ) {
540 final LocationInfo locationInfo = getLocationInfo(target);
541 if (locationInfo != null) {
542 final double zoomLevel = view.getZoom();
543 final Graph2D oldGraph = view.getGraph2D();
544 oldGraph.setSelected(oldGraph.nodes(), false);
545 setPageGraph(locationInfo.pageNo);
546 view.getGraph2D().setSelected(locationInfo.node, true);
547
548 final Graph2D newGraph = view.getGraph2D();
550 Node matchingNode = locationInfo.node; if (focus != null) {
552 for (NodeCursor nc = locationInfo.node.neighbors(); nc.ok(); nc.next()) {
553 final Node neighbor = nc.node();
554 if (focus.equals(newGraph.getLabelText(neighbor))) {
555 matchingNode = neighbor;
556 break;
557 }
558 }
559 }
560
561 view.setCenter(newGraph.getCenterX(matchingNode), newGraph.getCenterY(matchingNode));
562 view.setZoom(zoomLevel);
563 }
564 }
565
566 private LocationInfo getLocationInfo( final Object id ) {
567 return (LocationInfo) id2LocationInfo.get(id);
568 }
569
570
573 protected void loadInitialGraph() {
574 EventQueue.invokeLater(new Runnable() {
575 public void run() {
576 final Iterator it = getLoadSampleActions();
577 if (it.hasNext()) {
578 ((Action) it.next()).actionPerformed(null);
579 }
580 }
581 });
582 }
583
584 protected void loadGraph(String resourceString) {
585 loadGraph(baseModel, resourceString);
586 }
587
588
594 private void loadGraph(final Graph2D graph, final String resourceName) {
595 URL resource = null;
596 final File file = new File(resourceName);
597 if (file.exists()) {
598 try {
599 resource = file.toURI().toURL();
600 } catch (MalformedURLException e) {
601 D.showError(e.getMessage());
602 return;
603 }
604 } else {
605 resource = getResource(resourceName);
606 if (resource == null) {
607 return;
608 }
609 }
610
611 try {
612 final IOHandler ioh = resource.getFile().endsWith(".graphmlz") ? new ZipGraphMLIOHandler() : createGraphMLIOHandler();
613
614 graph.clear();
615 ioh.read(graph, resource);
616 } catch (IOException ioe) {
617 D.showError("Unexpected error while loading resource \"" + resource + "\" due to " + ioe.getMessage());
618 }
619 graph.setURL(resource);
620 }
621
622
625 private void doLayoutInBackground() {
626 EventQueue.invokeLater(new Runnable() {
627 public void run() {
628 final JDialog pd = createProgressDialog("Do Layout");
629
630 (new Thread(new Runnable() {
631 public void run() {
632 doLayout();
633
634 EventQueue.invokeLater(new Runnable() {
635 public void run() {
636 pd.setVisible(false);
637 pd.dispose();
638
639 previousPageIndex = 0;
641 setPageGraph(0);
642 }
643 });
644 }
645 })).start();
646
647 pd.setVisible(true);
648 }
649 });
650 }
651
652 private void doLayout() {
653 id2LocationInfo.clear();
654 if (oh.isUseSinglePageLayout()) {
655 (new Graph2DLayoutExecutor()).doLayout(baseModel, createCoreLayouter());
656 pageList = new ArrayList();
657 pageList.add((new GraphCopier(baseModel.getGraphCopyFactory())).copy(baseModel));
658 } else {
659 doMultiPageLayout();
660 }
661 }
662
663
666 private void doMultiPageLayout() {
667 final DataProvider idProvider = new DataProviderAdapter() {
671 public Object get(Object dataHolder) {
672 return dataHolder;
673 }
674 };
675 baseModel.addDataProvider(MultiPageLayouter.NODE_ID_DPKEY, idProvider);
676 baseModel.addDataProvider(MultiPageLayouter.EDGE_ID_DPKEY, idProvider);
677 baseModel.addDataProvider(MultiPageLayouter.NODE_LABEL_ID_DPKEY, idProvider);
678 baseModel.addDataProvider(MultiPageLayouter.EDGE_LABEL_ID_DPKEY, idProvider);
679
680 final MultiPageLayouter mpl = new MultiPageLayouter(createCoreLayouter());
681
682 mpl.setLabelLayouterEnabled(oh.isLayout(MultiPageLayoutOptionHandler.LAYOUT_ORGANIC));
684 mpl.setPreferredMaximalDuration(oh.getMaximalDuration() * 1000);
685 mpl.setGroupMode(oh.getGroupingMode());
686 mpl.setEdgeBundleModeMask(oh.getSeparationMask());
687 final boolean addEdgeTypeDp =
688 (mpl.getEdgeBundleModeMask() &
689 MultiPageLayouter.EDGE_BUNDLE_DISTINGUISH_TYPES) != 0;
690 if (addEdgeTypeDp) {
691 baseModel.addDataProvider(
692 MultiPageLayouter.EDGE_TYPE_DPKEY,
693 new DataProviderAdapter() {
694 public Object get(Object dataHolder) {
695 return new EdgeType(baseModel.getRealizer((Edge) dataHolder));
696 }
697 });
698 }
699
700 mpl.setMaxPageSize(new YDimension(oh.getMaximumWidth(), oh.getMaximumHeight()));
701
702 final SimpleLayoutCallback callback = new SimpleLayoutCallback();
703 mpl.setLayoutCallback(callback);
704
705 try {
706 (new Graph2DLayoutExecutor()).doLayout(baseModel, mpl);
708
709 pageList = createPageViews(callback.pop());
711 } finally {
712 if (addEdgeTypeDp) {
714 baseModel.removeDataProvider(MultiPageLayouter.EDGE_TYPE_DPKEY);
715 }
716
717 baseModel.removeDataProvider(MultiPageLayouter.EDGE_LABEL_ID_DPKEY);
718 baseModel.removeDataProvider(MultiPageLayouter.NODE_LABEL_ID_DPKEY);
719 baseModel.removeDataProvider(MultiPageLayouter.EDGE_ID_DPKEY);
720 baseModel.removeDataProvider(MultiPageLayouter.NODE_ID_DPKEY);
721 }
722 }
723
724
729 private Layouter createCoreLayouter() {
730 if (oh.isLayout(MultiPageLayoutOptionHandler.LAYOUT_HIERARCHIC)) {
731 final IncrementalHierarchicLayouter ihl = new IncrementalHierarchicLayouter();
732 ihl.setConsiderNodeLabelsEnabled(true);
733 ihl.setIntegratedEdgeLabelingEnabled(true);
734 ihl.setOrthogonallyRouted(true);
735 ihl.setConsiderNodeLabelsEnabled(true);
736 return ihl;
737 } else if (oh.isLayout(MultiPageLayoutOptionHandler.LAYOUT_ORGANIC)) {
738 final SmartOrganicLayouter sol = new SmartOrganicLayouter();
739 sol.setMinimalNodeDistance(10);
740 sol.setDeterministic(true);
741 sol.setMultiThreadingAllowed(true);
742 return sol;
743 } else if (oh.isLayout(MultiPageLayoutOptionHandler.LAYOUT_COMPACT_ORTHOGONAL)) {
744 return new CompactOrthogonalLayouter();
745 } else {
746 return new OrthogonalLayouter();
747 }
748 }
749
750
755 private List createPageViews( final MultiPageLayout layout ) {
756 final ArrayList newPageList = new ArrayList();
757 pageBuilder.reset(baseModel, layout);
758 for (int i = 0, pc = layout.pageCount(); i < pc; ++i) {
759 final Graph2D subgraph = pageBuilder.createPageView(new Graph2D(), i);
760 for (NodeCursor nc = subgraph.nodes(); nc.ok(); nc.next()) {
761 final Node n = nc.node();
762 id2LocationInfo.put(pageBuilder.getNodeId(n), new LocationInfo(i, n));
763 }
764 newPageList.add(subgraph);
765 }
766 return newPageList;
767 }
768
769 protected Action createLoadAction() {
770 return new LoadAction();
771 }
772
773 protected Action createSaveAction() {
774 return new SaveAction("Save Model Graph", baseModel);
775 }
776
777 public static void main(String[] args) {
778 EventQueue.invokeLater(new Runnable() {
779 public void run() {
780 Locale.setDefault(Locale.ENGLISH);
781 initLnF();
782 (new MultiPageLayoutDemo("resource/multipagelayouthelp.html")).start("Multi-Page Layout Demo");
783 }
784 });
785 }
786
787
788
789
794 static final class EdgeType {
795 byte sourceArrowType;
796 byte targetArrowType;
797 Color lineColor;
798 LineType lineType;
799
800 EdgeType(final EdgeRealizer realizer) {
801 sourceArrowType = realizer.getSourceArrow().getType();
802 targetArrowType = realizer.getTargetArrow().getType();
803 lineColor = realizer.getLineColor();
804 lineType = realizer.getLineType();
805 }
806
807 public boolean equals( Object o ) {
808 if (this == o) {
809 return true;
810 }
811 if (o == null || getClass() != o.getClass()) {
812 return false;
813 }
814
815 EdgeType edgeType = (EdgeType) o;
816
817 if (sourceArrowType != edgeType.sourceArrowType) {
818 return false;
819 }
820 if (targetArrowType != edgeType.targetArrowType) {
821 return false;
822 }
823 if (lineColor != null ? !lineColor.equals(edgeType.lineColor) : edgeType.lineColor != null) {
824 return false;
825 }
826 if (lineType != null ? !lineType.equals(edgeType.lineType) : edgeType.lineType != null) {
827 return false;
828 }
829
830 return true;
831 }
832
833 public int hashCode() {
834 int result = (int) sourceArrowType;
835 result = 31 * result + (int) targetArrowType;
836 result = 31 * result + (lineColor != null ? lineColor.hashCode() : 0);
837 result = 31 * result + (lineType != null ? lineType.hashCode() : 0);
838 return result;
839 }
840 }
841
842
843
844
848 class MyDoubleClickListener extends MouseAdapter {
849 public void mouseClicked(MouseEvent e) {
850 final JTree tree = (JTree) e.getSource();
851 if (e.getClickCount() == 2) {
852 final TreePath path = tree.getPathForLocation(e.getX(), e.getY());
853 if (path != null) {
854 final Object last = path.getLastPathComponent();
855 if (last instanceof Node) {
856 jump(last, baseModel.getLabelText((Node) last));
857 }
858 }
859 }
860 }
861 }
862
863 class MyTreeCellRenderer extends DefaultTreeCellRenderer {
864 public Component getTreeCellRendererComponent(
865 JTree tree,
866 Object value,
867 boolean sel,
868 boolean expanded,
869 boolean leaf,
870 int row,
871 boolean hasFocus
872 ) {
873 super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, hasFocus);
874 if (value instanceof Graph) {
875 setIcon(null);
876 setText("Nodes: " + ((Graph) value).nodeCount() + "\t Edges: " + ((Graph) value).edgeCount());
877 }
878 return this;
879 }
880 }
881
882
883
886 abstract class AbstractLoadAction extends AbstractAction {
887 AbstractLoadAction( final String name ) {
888 super(name);
889 }
890
891
896 void load( final String resource, final String name ) {
897 EventQueue.invokeLater(new Runnable() {
898 public void run() {
899 final JDialog pd = createProgressDialog("Loading " + name);
900
901 view.setGraph2D(new Graph2D());
902 view.fitContent();
903 view.updateView();
904
905 (new Thread(new Runnable() {
906 public void run() {
907 loadGraph(baseModel, resource);
908 EventQueue.invokeLater(new Runnable() {
909 public void run() {
910 pd.setVisible(false);
911 pd.dispose();
912
913 doLayoutInBackground();
914 }
915 });
916 }
917 })).start();
918
919 pd.setVisible(true);
920 }
921 });
922 }
923 }
924
925
928 protected class LoadSampleGraphAction extends AbstractLoadAction {
929 private final String name;
930 private final String resource;
931
932 LoadSampleGraphAction(final String name, final String resource) {
933 super(name);
934 this.name = name;
935 this.resource = resource;
936 }
937
938 public void actionPerformed(ActionEvent e) {
939 load(resource, name);
940 }
941 }
942
943
944 protected class LoadAction extends AbstractLoadAction {
945 JFileChooser chooser;
946
947 public LoadAction() {
948 super("Load Model Graph");
949 chooser = null;
950 }
951
952 public void actionPerformed(ActionEvent e) {
953 if (chooser == null) {
954 chooser = new JFileChooser();
955 chooser.setAcceptAllFileFilterUsed(false);
956 chooser.addChoosableFileFilter(new FileFilter() {
957 public boolean accept(File f) {
958 return f.isDirectory() || f.getName().endsWith(".graphml");
959 }
960
961 public String getDescription() {
962 return "GraphML Format (.graphml)";
963 }
964 });
965 chooser.addChoosableFileFilter(new FileFilter() {
966 public boolean accept(File f) {
967 return f.isDirectory() || f.getName().endsWith(".graphmlz");
968 }
969
970 public String getDescription() {
971 return "Zipped GraphML Format (.graphmlz)";
972 }
973 });
974 }
975 if (chooser.showOpenDialog(contentPane) == JFileChooser.APPROVE_OPTION) {
976 try {
977 final URL resource = chooser.getSelectedFile().toURI().toURL();
978 final String ln = resource.getFile();
979 load(ln, (new File(ln)).getName());
980 } catch (MalformedURLException mue) {
981 mue.printStackTrace();
982 }
983 }
984 }
985 }
986
987
988 protected class SaveAction extends AbstractAction {
989 private JFileChooser chooser;
990 private Graph2D graph;
991 private Graph2DView graphView;
992
993
998 public SaveAction( final String name, final Graph2D graph ) {
999 super(name);
1000 this.graph = graph;
1001 this.graphView = null;
1002 }
1003
1004
1009 public SaveAction( final String name, final Graph2DView graphView ) {
1010 super(name);
1011 this.graph = null;
1012 this.graphView = graphView;
1013 }
1014
1015 private Graph2D getGraph() {
1016 if(graph != null) {
1017 return graph;
1018 } else if(graphView != null) {
1019 return graphView.getGraph2D();
1020 } else {
1021 return null;
1022 }
1023 }
1024
1025 private void setFileFilter( final JFileChooser chooser, final File file ) {
1026 FileFilter[] filters = chooser.getChoosableFileFilters();
1027 for (int i = 0; i < filters.length; i++) {
1028 if (filters[i].accept(file)) {
1029 chooser.setFileFilter(filters[i]);
1030 return;
1031 }
1032 }
1033 }
1034
1035 public void actionPerformed( final ActionEvent e ) {
1036 if (chooser == null) {
1037 chooser = new JFileChooser();
1038 chooser.setAcceptAllFileFilterUsed(false);
1039 chooser.addChoosableFileFilter(new FileFilter() {
1040 public boolean accept(File f) {
1041 return f.isDirectory() || f.getName().endsWith(".graphml");
1042 }
1043
1044 public String getDescription() {
1045 return "GraphML Format (.graphml)";
1046 }
1047 });
1048 chooser.addChoosableFileFilter(new FileFilter() {
1049 public boolean accept(File f) {
1050 return f.isDirectory() || f.getName().endsWith(".graphmlz");
1051 }
1052
1053 public String getDescription() {
1054 return "Zipped GraphML Format (.graphmlz)";
1055 }
1056 });
1057 }
1058
1059 final URL url = view.getGraph2D().getURL();
1060 if (url != null && "file".equals(url.getProtocol())) {
1061 try {
1062 final File file = new File(new URI(url.toString()));
1063 chooser.setSelectedFile(file);
1064 setFileFilter(chooser, file);
1065 } catch (URISyntaxException use) {
1066 }
1068 }
1069
1070 if (chooser.showSaveDialog(contentPane) == JFileChooser.APPROVE_OPTION) {
1071 IOHandler ioh;
1072 String name = chooser.getSelectedFile().toString();
1073 final FileFilter filter = chooser.getFileFilter();
1074 if (filter.accept(new File("file.graphml"))) {
1075 if (!name.endsWith(".graphml")) {
1076 name += ".graphml";
1077 }
1078 ioh = createGraphMLIOHandler();
1079 } else {
1080 if (!name.endsWith(".graphmlz")) {
1081 name += ".graphmlz";
1082 }
1083 ioh = new ZipGraphMLIOHandler();
1084 }
1085
1086 try {
1087 ioh.write(getGraph(), name);
1088 } catch (IOException ioe) {
1089 D.show(ioe);
1090 }
1091 }
1092 }
1093 }
1094
1095
1098 private final class FirstPageAction extends AbstractAction {
1099 public FirstPageAction() {
1100 super("<<");
1101 putValue(SHORT_DESCRIPTION, "Go to first page");
1102 }
1103
1104 public void actionPerformed(ActionEvent e) {
1105 setPageGraph(0);
1106 }
1107 }
1108
1109
1112 private final class LastPageAction extends AbstractAction {
1113 public LastPageAction() {
1114 super(">>");
1115 putValue(SHORT_DESCRIPTION, "Go to last page");
1116 }
1117
1118 public void actionPerformed(ActionEvent e) {
1119 setPageGraph(pageList == null ? 0 : pageList.size()-1);
1120 }
1121 }
1122
1123
1126 private final class NextPageAction extends AbstractAction {
1127 public NextPageAction() {
1128 super(">");
1129 putValue(SHORT_DESCRIPTION, "Go to next page");
1130 }
1131
1132 public void actionPerformed(ActionEvent e) {
1133 setPageGraph(currentPageIndex + 1);
1134 }
1135 }
1136
1137
1140 private final class PreviousPageAction extends AbstractAction {
1141 public PreviousPageAction() {
1142 super("<");
1143 putValue(SHORT_DESCRIPTION, "Go to previous page");
1144 }
1145
1146 public void actionPerformed(ActionEvent e) {
1147 setPageGraph(currentPageIndex - 1);
1148 }
1149 }
1150
1151
1154 private final class GoBackAction extends AbstractAction {
1155 public GoBackAction() {
1156 super("Go Back");
1157 putValue(SHORT_DESCRIPTION, "Go to last visited page");
1158 }
1159
1160 public void actionPerformed(ActionEvent e) {
1161 setPageGraph(previousPageIndex);
1162 }
1163 }
1164
1165
1168 private final class SetOptionsAction extends AbstractAction {
1169 private final MultiPageLayoutOptionHandler.OptionSet set;
1170
1171 SetOptionsAction( final MultiPageLayoutOptionHandler.OptionSet set ) {
1172 super(set.getName());
1173 this.set = set;
1174 }
1175
1176 public void actionPerformed( final ActionEvent e ) {
1177 set.apply(oh);
1178 doLayoutInBackground();
1179 }
1180 }
1181
1182
1183
1184
1187 class PageBorderDrawer implements Drawable {
1188 public Rectangle getBounds() {
1189 final Rectangle2D bnds = getPageBounds();
1190 if (oh.isDrawingPage()) {
1191 final int margin = 5;
1192 bnds.setFrame(
1193 bnds.getX() - margin,
1194 bnds.getY() - margin,
1195 bnds.getWidth() + 2*margin,
1196 bnds.getHeight() + 2*margin);
1197 return bnds.getBounds();
1198 } else {
1199 return new Rectangle(
1200 (int) Math.floor(bnds.getCenterX()),
1201 (int) Math.floor(bnds.getCenterY()),
1202 1,
1203 1);
1204 }
1205 }
1206
1207 public void paint(Graphics2D g) {
1208 if (oh.isDrawingPage()) {
1209 final Rectangle2D bBoxGraph = getPageBounds();
1210 final Color colorBkp = g.getColor();
1211 g.setColor(PAGE_BACKGROUND);
1212 g.fill(bBoxGraph);
1213 g.setColor(Color.DARK_GRAY);
1214 g.draw(bBoxGraph);
1215 g.setColor(colorBkp);
1216 }
1217 }
1218
1219 private Rectangle2D getPageBounds() {
1220 final Graph2D graph = view.getGraph2D();
1221 final Rectangle2D bBoxGraph = LayoutTool.getBoundingBox(graph, graph.nodes(), graph.edges(), false);
1222 final double cx = bBoxGraph.getCenterX();
1223 final double cy = bBoxGraph.getCenterY();
1224 final int maxPageWidth = oh.getMaximumWidth();
1225 final int maxPageHeight = oh.getMaximumHeight();
1226 bBoxGraph.setFrame(
1227 cx - maxPageWidth * 0.5,
1228 cy - maxPageHeight * 0.5,
1229 maxPageWidth,
1230 maxPageHeight);
1231 return bBoxGraph;
1232 }
1233 }
1234
1235
1236
1237
1240 private static class LocationInfo {
1241 int pageNo;
1242 Node node;
1243
1244 LocationInfo(final int pageNo, final Node node) {
1245 this.pageNo = pageNo;
1246 this.node = node;
1247 }
1248 }
1249
1250
1251
1254 private static class SimpleLayoutCallback implements LayoutCallback {
1255 private MultiPageLayout result;
1256
1257 public void layoutDone( final MultiPageLayout result ) {
1258 this.result = result;
1259 }
1260
1261 MultiPageLayout pop() {
1262 final MultiPageLayout result = this.result;
1263 this.result = null;
1264 return result;
1265 }
1266 }
1267
1268
1269
1273 abstract static class LinkViewMode extends ViewMode {
1274 private Node hitNode;
1275
1276
1283 public void mouseMoved( final double x, final double y ) {
1284 final Graph2DView view = this.view;
1285 final Graph2D graph = view.getGraph2D();
1286 final HitInfo hitInfo = getHitInfo(x, y);
1287 final Node oldHitNode = hitNode;
1288 hitNode = hitInfo.getHitNode();
1289 if (hitNode != oldHitNode) {
1290 if (oldHitNode != null) {
1291 setActive(graph, oldHitNode, false);
1292 }
1293
1294 if (hitNode != null && isLink(hitNode)) {
1295 setToolTipText(hitNode);
1296 setActive(graph, hitNode, true);
1297 view.setViewCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
1298 } else {
1299 reset(view);
1300 }
1301 view.updateView();
1302 }
1303 }
1304
1305 public void mouseExited() {
1306 final Node oldHitNode = hitNode;
1307 if (oldHitNode != null) {
1308 hitNode = null;
1309 setActive(view.getGraph2D(), oldHitNode, false);
1310 reset(view);
1311 view.updateView();
1312 }
1313 }
1314
1315
1322 void setActive( final Graph2D graph, final Node node, final boolean active ) {
1323 final NodeRealizer nr = graph.getRealizer(node);
1324 if (nr.labelCount() > 0) {
1325 nr.getLabel().setUnderlinedTextEnabled(active);
1326 }
1327 }
1328
1329 void reset( final Graph2DView view ) {
1330 view.setViewCursor(Cursor.getDefaultCursor());
1331 view.setToolTipText(null);
1332 }
1333
1334
1340 public HitInfo getHitInfo( final double x, final double y ) {
1341 final HitInfo info = view.getHitInfoFactory().createHitInfo(x, y, Graph2DTraversal.NODES, true);
1342 setLastHitInfo(info);
1343 return info;
1344 }
1345
1346
1353 abstract boolean isLink( Node node );
1354
1355
1360 abstract void setToolTipText( Node node );
1361 }
1362
1363
1366 class OverviewViewMode extends LinkViewMode {
1367
1372 public void mouseClicked( final double x, final double y ) {
1373 final int page = getPage(x, y);
1374 if (page > 0 && page - 1 != currentPageIndex) {
1375 reset(view);
1376 setPageGraph(page - 1);
1377 }
1378 }
1379
1380
1387 boolean isLink( final Node node ) {
1388 final int page = getPage(node);
1389 return page > 0 && page - 1 != currentPageIndex;
1390 }
1391
1392
1397 void setToolTipText( final Node node ) {
1398 view.setToolTipText("Go to page " + getPage(node));
1399 }
1400
1401
1410 private int getPage( final double x, final double y ) {
1411 final HitInfo hitInfo = getHitInfo(x, y);
1412 if (hitInfo.hasHitNodes()) {
1413 return getPage(hitInfo.getHitNode());
1414 } else {
1415 return -1;
1416 }
1417 }
1418
1419
1426 private int getPage( final Node node ) {
1427 final NodeRealizer nr = getGraph2D().getRealizer(node);
1428 if (nr.labelCount() > 0) {
1429 try {
1430 return Integer.parseInt(nr.getLabelText());
1431 } catch (NumberFormatException e) {
1432 return -1;
1433 }
1434 }
1435 return -1;
1436 }
1437 }
1438
1439
1446 class PageViewMode extends LinkViewMode {
1447
1455 public void mouseClicked( final double x, final double y ) {
1456 if (lastClickEvent.getButton() == MouseEvent.BUTTON1) {
1457 final Graph2DView view = this.view;
1458 final HitInfo info = view.getHitInfoFactory().createHitInfo(x, y,
1459 Graph2DTraversal.NODES, true);
1460 if (info.hasHitNodes()) {
1461 reset(view);
1462 final Node hitNode = info.getHitNode();
1463 jump(pageBuilder.getReferencingNodeId(hitNode),
1464 view.getGraph2D().getLabelText(hitNode));
1465 }
1466 }
1467 }
1468
1469
1476 boolean isLink( final Node node ) {
1477 return pageBuilder.getReferencingNodeId(node) != null;
1478 }
1479
1480
1485 void setToolTipText( final Node node ) {
1486 final Graph2DView view = this.view;
1487 final int pageNo = getLocationInfo(
1488 pageBuilder.getReferencingNodeId(node)).pageNo + 1;
1489 switch (pageBuilder.getNodeType(node)) {
1490 case NodeInfo.TYPE_PROXY:
1491 view.setToolTipText(
1492 "<html>" +
1493 "<h3>Proxy</h3>" +
1494 "<p>Transfers to the original node on page " + pageNo +
1495 ".</p></html>");
1496 break;
1497 case NodeInfo.TYPE_PROXY_REFERENCE:
1498 view.setToolTipText(
1499 "<html>" +
1500 "<h3>Proxy Reference</h3>" +
1501 "<p>Transfers to the proxy on page " + pageNo +
1502 ".</p></html>");
1503 break;
1504 case NodeInfo.TYPE_CONNECTOR:
1505 view.setToolTipText(
1506 "<html>" +
1507 "<h3>Connector</h3>" +
1508 "<p>Transfers to the opposite node of the connecting edge" +
1509 " on page " + pageNo + ".</p></html>");
1510 break;
1511 default:
1512 throw new IllegalStateException();
1513 }
1514 }
1515 }
1516}
1517