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