1
28 package demo.view.mindmap;
29
30 import demo.view.DemoBase;
31 import y.algo.GraphConnectivity;
32 import y.algo.Trees;
33 import y.base.Command;
34 import y.base.Edge;
35 import y.base.EdgeCursor;
36 import y.base.EdgeMap;
37 import y.base.Graph;
38 import y.base.Node;
39 import y.base.NodeMap;
40 import y.base.WrongGraphStructure;
41 import y.io.GraphMLIOHandler;
42 import y.io.IOHandler;
43 import y.io.graphml.KeyScope;
44 import y.io.graphml.KeyType;
45 import y.view.ArcEdgeRealizer;
46 import y.view.Arrow;
47 import y.view.BezierPathCalculator;
48 import y.view.DefaultBackgroundRenderer;
49 import y.view.EdgeLabel;
50 import y.view.EditMode;
51 import y.view.GenericEdgeRealizer;
52 import y.view.GenericNodeRealizer;
53 import y.view.Graph2D;
54 import y.view.Graph2DView;
55 import y.view.Graph2DViewMouseWheelZoomListener;
56 import y.view.LineType;
57 import y.view.ShapeNodeRealizer;
58 import y.view.SmartEdgeLabelModel;
59 import y.view.YRenderingHints;
60
61 import javax.swing.AbstractAction;
62 import javax.swing.Action;
63 import javax.swing.JFileChooser;
64 import javax.swing.JMenu;
65 import javax.swing.JMenuBar;
66 import javax.swing.JMenuItem;
67 import javax.swing.filechooser.FileFilter;
68 import java.awt.Color;
69 import java.awt.EventQueue;
70 import java.awt.Graphics2D;
71 import java.awt.Rectangle;
72 import java.awt.event.ActionEvent;
73 import java.awt.event.MouseWheelListener;
74 import java.io.File;
75 import java.io.IOException;
76 import java.net.MalformedURLException;
77 import java.net.URL;
78 import java.util.ArrayList;
79 import java.util.Iterator;
80 import java.util.Locale;
81 import java.util.Map;
82
83
150 public class MindMapDemo extends DemoBase {
151 public static void main(String[] args) {
152 EventQueue.invokeLater(new Runnable() {
153 public void run() {
154 Locale.setDefault(Locale.ENGLISH);
155 initLnF();
156 (new MindMapDemo("resource/mindmaphelp.html")).start("MindMapDemo");
157 }
158 });
159 }
160
161 public MindMapDemo() {
162 this(null);
163 }
164
165 public MindMapDemo(final String helpFilePath) {
166 new CollapseButton.Handler(view);
167
168 loadFile(getResource("resource/hobbies.graphml"));
169
170 KeyboardHandling.setKeyActions(view);
171 configureRendering();
172 final Graph2DViewMouseWheelZoomListener zl = new Graph2DViewMouseWheelZoomListener();
174 zl.setMaximumZoom(2.5);
175 zl.setMinimumZoom(0.08);
176 zl.setCenterZooming(false);
177 MouseWheelListener[] mouseWheelListeners = view.getCanvasComponent().getMouseWheelListeners();
179 view.getCanvasComponent().removeMouseWheelListener(mouseWheelListeners[0]);
180 zl.addToCanvas(view);
181
182 addHelpPane(helpFilePath);
183
184 configureBackgroundRenderer();
186 getUndoManager().resetQueue();
187 }
188
189 protected Graph2DView createGraphView() {
190 final Graph2DView view = new Graph2DView(ViewModel.instance.graph);
191 view.setFitContentOnResize(true);
192 return view;
193 }
194
195
198 private void configureBackgroundRenderer() {
199 view.setBackgroundRenderer(new DefaultBackgroundRenderer(view) {
200 public void paint( Graphics2D gfx, int x, int y, int w, int h ) {
201 super.paint(gfx, x, y, w, h);
202 final int SQUARE_SIZE = 25;
203 final int HOLE_SIZE = 30;
204 final Rectangle bounds = view.getBounds();
205
206 gfx.setColor(new Color(174, 174, 174));
207
208 undoWorldTransform(gfx);
209 final int width = 5;
210 gfx.fillRect(7 * SQUARE_SIZE - (width / 2), 0, width, bounds.y + bounds.height);
211 for (int i = 0; i < bounds.height; i += SQUARE_SIZE) {
212 gfx.drawLine(0, i, bounds.width + bounds.x, i);
213 }
214 for (int i = 0; i < bounds.width; i += SQUARE_SIZE) {
215 gfx.drawLine(i, 0, i, bounds.y + bounds.height);
216 }
217 double height = bounds.height / 8;
218
219 gfx.setColor(Color.WHITE);
220
221 gfx.fillOval((int) (1.5 * SQUARE_SIZE), (int) height, HOLE_SIZE, HOLE_SIZE);
222 gfx.fillOval((int) (1.5 * SQUARE_SIZE), (int) (height * 3), HOLE_SIZE, HOLE_SIZE);
223 gfx.fillOval((int) (1.5 * SQUARE_SIZE), (int) (height * 5), HOLE_SIZE, HOLE_SIZE);
224 gfx.fillOval((int) (1.5 * SQUARE_SIZE), (int) (height * 7), HOLE_SIZE, HOLE_SIZE);
225
226 gfx.setColor(new Color(167, 167, 167));
227 gfx.drawOval((int) (1.5 * SQUARE_SIZE), (int) height, HOLE_SIZE, HOLE_SIZE);
228 gfx.drawOval((int) (1.5 * SQUARE_SIZE), (int) (height * 3), HOLE_SIZE, HOLE_SIZE);
229 gfx.drawOval((int) (1.5 * SQUARE_SIZE), (int) (height * 5), HOLE_SIZE, HOLE_SIZE);
230 gfx.drawOval((int) (1.5 * SQUARE_SIZE), (int) (height * 7), HOLE_SIZE, HOLE_SIZE);
231
232 redoWorldTransform(gfx);
233 }
234 });
235 }
236
237
248 private void configureRendering() {
249 view.setGraph2DRenderer(new MindMapRenderer());
250
251 view.getRenderingHints().put(
254 ShapeNodeRealizer.KEY_SLOPPY_RECT_PAINTING,
255 ShapeNodeRealizer.VALUE_SLOPPY_RECT_PAINTING_OFF);
256
257 view.getRenderingHints().put(
260 YRenderingHints.KEY_NODE_LABEL_PAINTING,
261 YRenderingHints.VALUE_NODE_LABEL_PAINTING_OFF);
262 }
263
264
267 protected void configureDefaultRealizers() {
268 final GenericNodeRealizer.Factory nodeFactory = GenericNodeRealizer.getFactory();
269 final Map nodeMap = nodeFactory.createDefaultConfigurationMap();
270 nodeMap.put(GenericNodeRealizer.Painter.class, new MindMapNodePainter());
271 nodeFactory.addConfiguration("MindMapUnderline", nodeMap);
272 final GenericNodeRealizer gnr = new GenericNodeRealizer();
273 gnr.setConfiguration("MindMapUnderline");
274 gnr.setHeight(24);
275 view.getGraph2D().setDefaultNodeRealizer(gnr);
276
277 final GenericEdgeRealizer.Factory edgeFactory = GenericEdgeRealizer.getFactory();
278 final Map edgeMap = edgeFactory.createDefaultConfigurationMap();
279 edgeMap.put(GenericEdgeRealizer.PathCalculator.class, new BezierPathCalculator());
280 edgeMap.put(GenericEdgeRealizer.Painter.class,new GradientEdgePainter());
281 edgeFactory.addConfiguration("BezierGradientEdge", edgeMap);
282 final GenericEdgeRealizer edgeRealizer = new GenericEdgeRealizer();
283 edgeRealizer.setConfiguration("BezierGradientEdge");
284 final ArcEdgeRealizer realizer = new ArcEdgeRealizer(ArcEdgeRealizer.FIXED_RATIO);
285
286 final EdgeLabel label = realizer.getLabel();
287 final SmartEdgeLabelModel model = new SmartEdgeLabelModel();
288 final SmartEdgeLabelModel.ModelParameter modelParameter = new SmartEdgeLabelModel.ModelParameter(0, 1, 0, false,
289 SmartEdgeLabelModel.ModelParameter.LEFT, 0);
290 label.setLabelModel(model,modelParameter);
291 label.setBackgroundColor(Color.WHITE);
292 realizer.setTargetArrow(Arrow.DELTA);
293 realizer.setLineColor(MindMapUtil.CROSS_EDGE_COLOR);
294 realizer.setLineType(LineType.LINE_7);
295 view.getGraph2D().setDefaultEdgeRealizer(realizer);
296 }
297
298
301 protected void registerViewModes() {
302 new HoverButton(view);
306
307 final EditMode editMode = new MoveNodeMode(getUndoManager());
308 editMode.getMouseInputMode().setDrawableSearchingEnabled(true);
309 editMode.allowMouseInput(true);
310 editMode.allowBendCreation(false);
311 editMode.allowMovePorts(false);
312 editMode.allowNodeCreation(false);
313 editMode.allowEdgeCreation(false);
314 editMode.allowResizeNodes(false);
315 editMode.allowMoveLabels(false);
316 view.addViewMode(editMode);
317 view.addViewMode(new KeyboardHandling.LabelChangeViewMode());
318 }
319
320
324 protected Action createDeleteSelectionAction() {
325 return KeyboardHandling.createDeleteSelectionAction(view);
326 }
327
328
331 public class SaveFileAction extends AbstractAction {
332 JFileChooser chooser;
333
334 public SaveFileAction() {
335 super("Save...");
336 putValue(Action.SHORT_DESCRIPTION, "Save...");
337 }
338
339 public void actionPerformed(final ActionEvent e) {
340 if (chooser == null) {
341 chooser = new JFileChooser();
342 chooser.setDialogTitle("Save Mindmap...");
343 chooser.setAcceptAllFileFilterUsed(false);
345 chooser.addChoosableFileFilter(new FileFilter() {
346 public boolean accept(File f) {
347 return f.isDirectory() || f.getName().endsWith(".mm");
348 }
349
350 public String getDescription() {
351 return "FreeMind Mindmap (.mm)";
352 }
353 });
354 chooser.addChoosableFileFilter(new FileFilter() {
355 public boolean accept(File f) {
356 return f.isDirectory() || f.getName().endsWith(".graphml");
357 }
358
359 public String getDescription() {
360 return "GraphML (.graphml)";
361 }
362 });
363 }
364
365 if (chooser.showSaveDialog(contentPane) == JFileChooser.APPROVE_OPTION) {
366 final boolean useNativeFormat =
367 chooser.getFileFilter().getDescription().startsWith("GraphML");
368
369 String fileName = chooser.getSelectedFile().getAbsolutePath();
370 if (useNativeFormat) {
372 final String suffix = ".graphml";
373 if (!fileName.toLowerCase().endsWith(suffix)) {
374 fileName += suffix;
375 }
376 } else {
377 final String suffix = ".mm";
378 if (!fileName.toLowerCase().endsWith(suffix)) {
379 fileName += suffix;
380 }
381 }
382
383 final IOHandler writer =
385 useNativeFormat
386 ? (IOHandler) createGraphMLIOHandler()
387 : new FreeMindIOHandler();
388 try {
389 writer.write(view.getGraph2D(), fileName);
390 } catch (IOException ioe) {
391 ioe.printStackTrace();
392 }
393 }
394 }
395 }
396
397
400 public class OpenFileAction extends AbstractAction {
401 JFileChooser chooser;
402
403 public OpenFileAction() {
404 super("Load...");
405 putValue(Action.SHORT_DESCRIPTION, "Load...");
406 }
407
408 public void actionPerformed(final ActionEvent e) {
409 if (chooser == null) {
410 chooser = new JFileChooser();
411 chooser.setAcceptAllFileFilterUsed(false);
412 chooser.setDialogTitle("Open Mindmap...");
413 chooser.addChoosableFileFilter(new FileFilter() {
414 public boolean accept( final File f ) {
415 return f.isDirectory() ||
416 f.getName().endsWith(".mm") ||
417 f.getName().endsWith(".graphml");
418 }
419
420 public String getDescription() {
421 return "Mindmap (.graphml, .mm)";
422 }
423 });
424 }
425
426 if (chooser.showOpenDialog(contentPane) == JFileChooser.APPROVE_OPTION) {
427 try {
428 final URL url = chooser.getSelectedFile().toURI().toURL();
429 loadFile(url);
430 } catch (MalformedURLException mue) {
431 mue.printStackTrace();
432 }
433 }
434 }
435 }
436
437 private void loadFile( final URL resource ) {
438 final ArrayList exceptions = new ArrayList();
439 final ViewModel model = ViewModel.instance;
440
441 final Graph2D graph = view.getGraph2D();
442 graph.firePreEvent();
443
444 graph.clear();
445
446 final boolean useNativeFormat = resource.getFile().toLowerCase().endsWith(".graphml");
447 if (useNativeFormat) {
448 try {
449 createGraphMLIOHandler().read(graph, resource);
450 } catch (IOException ioe) {
451 exceptions.add(ioe);
452 }
453 } else {
454 try {
455 (new FreeMindIOHandler()).read(graph, resource);
456 } catch (IOException ioe) {
457 exceptions.add(ioe);
458 }
459 }
460
461 if (exceptions.isEmpty()) {
462 try {
463 final Node root = findRoot(graph);
464 model.setRoot(root);
465
466 if (!useNativeFormat) {
467 MindMapUtil.setRootRealizer(graph, root, graph.getLabelText(root));
468 LayoutUtil.layout(graph);
469 }
470 } catch (WrongGraphStructure wgse) {
471 exceptions.add(wgse);
472 }
473 }
474
475 if (!exceptions.isEmpty()) {
476 graph.clear();
477 final Node root = graph.createNode();
478 MindMapUtil.setRootRealizer(graph, root, "Mind Map");
479 model.setRoot(root);
480
481 for (Iterator it = exceptions.iterator(); it.hasNext(); ) {
482 ((Exception) it.next()).printStackTrace();
483 }
484
485 view.fitContent();
486 view.updateView();
487 } else if (useNativeFormat) {
488 view.fitContent();
489 view.updateView();
490 }
491
492 graph.firePostEvent();
493 model.clearHiddenCrossReferences();
494 getUndoManager().resetQueue();
495 }
496
497
508 private Node findRoot( final Graph2D graph ) {
509 if (GraphConnectivity.isConnected(graph)) {
510 final Graph copy = new Graph();
511 final Node[] cNodes = new Node[graph.nodeCount()];
512 for (int i = 0; i < cNodes.length; ++i) {
513 cNodes[i] = copy.createNode();
514 }
515
516 final ViewModel model = ViewModel.instance;
517 for (EdgeCursor ec = graph.edges(); ec.ok(); ec.next()) {
518 final Edge edge = ec.edge();
519 if (!model.isCrossReference(edge)) {
520 copy.createEdge(
521 cNodes[edge.source().index()],
522 cNodes[edge.target().index()]);
523 }
524 }
525
526 if (Trees.isRootedTree(copy)) {
527 final Node cRoot = Trees.getRoot(copy);
528 return graph.getNodeArray()[cRoot.index()];
529 }
530 }
531 throw new WrongGraphStructure("Graph is not a mind map");
532 }
533
534 protected Action createLoadAction() {
535 return new OpenFileAction();
536 }
537
538 protected Action createSaveAction() {
539 return new SaveFileAction();
540 }
541
542 private class OpenExampleAction extends AbstractAction {
543 private String filename;
544
545 public OpenExampleAction(String filename) {
546 super(filename);
547 this.filename = filename;
548 }
549
550 public void actionPerformed(ActionEvent e) {
551 loadFile(getResource("resource/" + filename + ".graphml"));
552 }
553 }
554
555 protected JMenuBar createMenuBar() {
556 JMenuBar menuBar = super.createMenuBar();
557 JMenu menu = menuBar.getMenu(0);
558 JMenuItem item = new JMenuItem(new NewFileAction());
559 menu.add(item,0);
560 menu = new JMenu("Example Mind Maps");
561 menuBar.add(menu);
562 menu.add(new OpenExampleAction("hobbies"));
563 menu.add(new OpenExampleAction("yFiles"));
564 menu.add(new OpenExampleAction("packages"));
565 return menuBar;
566 }
567
568 private class NewFileAction extends AbstractAction {
569 public NewFileAction() {
570 super("New");
571 }
572
573 public void actionPerformed(ActionEvent e) {
574 final Graph2D graph = view.getGraph2D();
575 graph.firePreEvent();
576 graph.backupRealizers();
577 graph.clear();
578
579 final Node root = graph.createNode();
580 MindMapUtil.setRootRealizer(graph, root, "Mind Map");
581 final ViewModel model = ViewModel.instance;
582 getUndoManager().push(new SetRoot(model.getRoot(), root));
583 model.setRoot(root);
584 graph.firePostEvent();
585
586 view.fitContent();
587 view.updateView();
588 }
589 }
590
591 private static final class SetRoot implements Command {
592 private final Node oldRoot;
593 private final Node newRoot;
594
595 SetRoot( final Node oldRoot, final Node newRoot ) {
596 this.oldRoot = oldRoot;
597 this.newRoot = newRoot;
598 }
599
600 public void execute() {
601 }
602
603 public void undo() {
604 ViewModel.instance.setRoot(oldRoot);
605 }
606
607 public void redo() {
608 ViewModel.instance.setRoot(newRoot);
609 }
610 }
611
612
613 protected boolean isClipboardEnabled() {
614 return false;
615 }
616
617
618 protected GraphMLIOHandler createGraphMLIOHandler() {
619 final GraphMLIOHandler ioHandler = super.createGraphMLIOHandler();
620
621 final NodeMap leftRightMap = LayoutUtil.getLeftRightMap(ViewModel.instance.graph);
622 ioHandler.getGraphMLHandler().addOutputDataProvider("Mindmap.LeftSide", leftRightMap, KeyScope.NODE, KeyType.BOOLEAN);
623 ioHandler.getGraphMLHandler().addInputDataAcceptor("Mindmap.LeftSide", leftRightMap, KeyScope.NODE, KeyType.BOOLEAN);
624
625 final EdgeMap crossReferences = LayoutUtil.getCrossReferencesMap(ViewModel.instance.graph);
626 ioHandler.getGraphMLHandler().addOutputDataProvider("Mindmap.CrossReference", crossReferences, KeyScope.EDGE, KeyType.BOOLEAN);
627 ioHandler.getGraphMLHandler().addInputDataAcceptor("Mindmap.CrossReference", crossReferences, KeyScope.EDGE, KeyType.BOOLEAN);
628
629 return ioHandler;
630 }
631 }
632