1   /****************************************************************************
2    **
3    ** This file is part of yFiles-2.9. 
4    ** 
5    ** yWorks proprietary/confidential. Use is subject to license terms.
6    **
7    ** Redistribution of this file or of an unauthorized byte-code version
8    ** of this file is strictly forbidden.
9    **
10   ** Copyright (c) 2000-2011 by yWorks GmbH, Vor dem Kreuzberg 28, 
11   ** 72070 Tuebingen, Germany. All rights reserved.
12   **
13   ***************************************************************************/
14  package demo.view.advanced;
15  
16  import demo.view.DemoBase;
17  
18  import java.awt.BorderLayout;
19  import java.awt.Color;
20  import java.awt.Graphics2D;
21  import java.awt.GridLayout;
22  import java.awt.EventQueue;
23  import java.awt.Font;
24  import java.awt.FontMetrics;
25  import java.awt.Rectangle;
26  import java.awt.event.ActionEvent;
27  import java.awt.event.ComponentAdapter;
28  import java.awt.event.ComponentEvent;
29  import java.awt.geom.Rectangle2D;
30  import java.net.URL;
31  import java.util.HashSet;
32  import java.util.Iterator;
33  import java.util.Locale;
34  import java.util.Set;
35  import javax.swing.AbstractAction;
36  import javax.swing.Action;
37  import javax.swing.ActionMap;
38  import javax.swing.ImageIcon;
39  import javax.swing.InputMap;
40  import javax.swing.JComponent;
41  import javax.swing.JPanel;
42  import javax.swing.JRootPane;
43  import javax.swing.JToolBar;
44  
45  import y.base.Edge;
46  import y.base.EdgeCursor;
47  import y.base.Graph;
48  import y.base.GraphEvent;
49  import y.base.GraphListener;
50  import y.base.Node;
51  import y.base.NodeCursor;
52  import y.layout.ComponentLayouter;
53  import y.layout.Layouter;
54  import y.layout.hierarchic.IncrementalHierarchicLayouter;
55  import y.layout.orthogonal.OrthogonalLayouter;
56  import y.layout.router.OrthogonalEdgeRouter;
57  import y.layout.tree.BalloonLayouter;
58  import y.layout.tree.TreeReductionStage;
59  import y.view.DefaultBackgroundRenderer;
60  import y.view.EditMode;
61  import y.view.Graph2D;
62  import y.view.Graph2DCopyFactory;
63  import y.view.Graph2DEvent;
64  import y.view.Graph2DListener;
65  import y.view.Graph2DView;
66  import y.view.Graph2DViewActions;
67  import y.view.Graph2DViewMouseWheelZoomListener;
68  import y.view.ModelViewManager;
69  import y.view.NodeLabel;
70  import y.view.NodeRealizer;
71  import y.view.ShapeNodeRealizer;
72  import y.view.Graph2DLayoutExecutor;
73  
74  /**
75   * Demonstrates automatic structural synchronization between several graphs using 
76   * {@link y.view.ModelViewManager}.
77   * <p>
78   * The demo shows four different Graph2DViews in a 2-by-2 matrix. The top-left one 
79   * presents the model graph, the latter three ones show derived views of this model 
80   * graph.
81   * <br/>
82   * Each of the derived views has special characteristics: for example, the bottom-left 
83   * one does not show any of the edges from the model graph, the bottom-right one 
84   * is empty at first and only shows nodes created interactively by a user. 
85   * Also, in some views the visual representation of the nodes differs from the model 
86   * graph.
87   * </p>
88   * <p>
89   * In all views there can be applied an automatic layout to the contained graph. 
90   * Additionally, the two views at the bottom, which prevent editing of their contained 
91   * graphs, provide a button to synchronize their contents back to the model graph's 
92   * view, which in turn updates the other derived views.
93   * </p>
94   *
95   * @see <a href="http://docs.yworks.com/yfiles/doc/developers-guide/multiviews.html">Section Multiple Views on a Common Model Graph</a> in the yFiles for Java Developer's Guide
96   */
97  public class ModelViewManagerDemo extends DemoBase {
98    private final ModelViewManager manager;
99    private final Graph2DView[] subViews;
100 
101   public ModelViewManagerDemo() {
102     subViews = new Graph2DView[3];
103 
104     initGraph(view.getGraph2D());
105     manager = ModelViewManager.getInstance(view.getGraph2D());
106 
107     contentPane.remove(view);
108     contentPane.add(createMultiView(), BorderLayout.CENTER);
109   }
110 
111   /**
112    * Creates a sample graph.
113    */
114   private void initGraph( final Graph2D graph ) {
115     graph.clear();
116     graph.getDefaultNodeRealizer().setFillColor(new Color(73, 147, 255));
117 
118     //create nodes
119     final Node[] nodes = new Node[10];
120     for (int i = 0; i < nodes.length; ++i) {
121       nodes[i] = graph.createNode();
122     }
123 
124     //create edges
125     graph.createEdge(nodes[1], nodes[8]);
126     graph.createEdge(nodes[1], nodes[2]);
127     graph.createEdge(nodes[1], nodes[6]);
128     graph.createEdge(nodes[1], nodes[0]);
129     graph.createEdge(nodes[2], nodes[0]);
130     graph.createEdge(nodes[3], nodes[5]);
131     graph.createEdge(nodes[3], nodes[6]);
132     graph.createEdge(nodes[3], nodes[1]);
133     graph.createEdge(nodes[4], nodes[2]);
134     graph.createEdge(nodes[4], nodes[7]);
135     graph.createEdge(nodes[5], nodes[0]);
136     graph.createEdge(nodes[6], nodes[5]);
137     graph.createEdge(nodes[6], nodes[0]);
138     graph.createEdge(nodes[7], nodes[2]);
139     graph.createEdge(nodes[7], nodes[8]);
140     graph.createEdge(nodes[8], nodes[4]);
141     graph.createEdge(nodes[9], nodes[8]);
142     graph.createEdge(nodes[9], nodes[7]);
143 
144     //node labels
145     for (NodeCursor nc = graph.nodes(); nc.ok(); nc.next()) {
146       graph.getRealizer(nc.node()).setLabelText(Integer.toString(nc.node().index()));
147     }
148 
149     // calculate an initial hierarchical layout
150     final IncrementalHierarchicLayouter ihl = new IncrementalHierarchicLayouter();
151     new Graph2DLayoutExecutor().doLayout(graph, ihl);
152   }
153 
154   /**
155    * Creates and initializes the views managed by the demo's
156    * <code>ModelViewManager</code>.
157    */
158   private JComponent createMultiView() {
159     final JPanel pane = new JPanel(new GridLayout(2, 2, 1, 1));
160 
161     // shared listeners
162     final LabelChangeHandler labelChangeHandler = new LabelChangeHandler();
163     final Graph2DViewMouseWheelZoomListener mwzl =
164             new Graph2DViewMouseWheelZoomListener();
165 
166     // the model view
167     final JToolBar vtb = createToolBar(view);
168     if (vtb != null) {
169       final JPanel viewAndTools = new JPanel(new BorderLayout());
170       viewAndTools.add(view, BorderLayout.CENTER);
171       viewAndTools.add(vtb, BorderLayout.NORTH);
172       pane.add(viewAndTools);
173     } else {
174       pane.add(view);
175     }
176     view.fitContent();
177     view.getGraph2D().addGraph2DListener(labelChangeHandler);
178     MyBackgroundRenderer.newInstance(view).setText("Editable Model");
179 
180 
181 
182     // create Graph2DViews for the graphs handled as views for the model
183     // in the demo's ModelViewManager
184     for (int i = 0; i < subViews.length; ++i) {
185       subViews[i] = new Graph2DView();
186       subViews[i].setFitContentOnResize(true);
187       final JToolBar svitb = createToolBar(subViews[i]);
188       if (svitb != null) {
189         final JPanel viewAndTools = new JPanel(new BorderLayout());
190         viewAndTools.add(subViews[i], BorderLayout.CENTER);
191         viewAndTools.add(svitb, BorderLayout.NORTH);
192         pane.add(viewAndTools);
193       } else {
194         pane.add(subViews[i]);
195       }
196     }
197 
198 
199 
200     // set up an editable, auto synchronizing view
201     final Graph2D graph = subViews[0].getGraph2D();
202     graph.setGraphCopyFactory(new MyCopyFactory(createRedCircle()));
203 
204     // register the Graph2DView's graph as a graph view of the demo's
205     // ModelViewManager model
206     manager.addViewGraph(graph, null, true);
207     manager.synchronizeModelToViewGraph(graph);
208 
209     graph.setDefaultNodeRealizer(createRedCircle());
210     graph.addGraph2DListener(labelChangeHandler);
211 
212     // configure the Graph2DView for editing
213     final Graph2DViewActions actions = new Graph2DViewActions(subViews[0]);
214     final ActionMap amap = actions.createActionMap();
215     final InputMap imap = actions.createDefaultInputMap(amap);
216     subViews[0].getCanvasComponent().setActionMap(amap);
217     subViews[0].getCanvasComponent().setInputMap(JComponent.WHEN_FOCUSED, imap);
218     subViews[0].getCanvasComponent().addMouseWheelListener(mwzl);
219     subViews[0].addViewMode(new EditMode());
220     MyBackgroundRenderer.newInstance(subViews[0]).setText("Editable View");
221 
222     // calculate an initial layout that differs from the model's layout
223     subViews[0].applyLayout(createOrthogonalLayouter());
224     subViews[0].fitContent();
225 
226 
227 
228     // set up non-editable view, that displays nodes only
229     subViews[1].setGraph2D((Graph2D) manager.createViewGraph(
230             new MyCopyFactory(createOrangeOctagon()), new NoEdgesFilter(), false));
231 
232     // configure the Graph2DView
233     subViews[1].fitContent();
234     subViews[1].getCanvasComponent().addMouseWheelListener(mwzl);
235     MyBackgroundRenderer.newInstance(subViews[1]).setText("Non-editable View");
236 
237 
238 
239     // set up non-editable view, that displays only user-created graph elements
240     subViews[2].setGraph2D((Graph2D) manager.createViewGraph(
241             null, new ExcludeFilter(view.getGraph2D()), false));
242 
243     // configure the Graph2DView
244     subViews[2].fitContent();
245     subViews[2].getCanvasComponent().addMouseWheelListener(mwzl);
246     MyBackgroundRenderer.newInstance(subViews[2]).setText("Non-editable View");
247 
248 
249 
250     // ensure that all Graph2DViews are properly refreshed on structural
251     // changes
252     manager.getModel().addGraphListener(new UpdateHandler());
253     for (Iterator it = manager.viewGraphs(); it.hasNext();) {
254       ((Graph) it.next()).addGraphListener(new UpdateHandler());
255     }
256 
257 
258     return pane;
259   }
260 
261   /**
262    * Overwritten to be able to trigger an initial <code>fitContent()</code>
263    * for all the <code>Graph2DView</code>s used in this demo.
264    */
265   public void addContentTo( final JRootPane rootPane ) {
266     super.addContentTo(rootPane);
267     final ComponentAdapter handler = new ComponentAdapter() {
268       private int callCount;
269 
270       public void componentResized( final ComponentEvent e ) {
271         if (callCount < 2) {
272           configureviews();
273         }
274         ++callCount;
275         if (callCount == 2) {
276           rootPane.removeComponentListener(this);
277         }
278       }
279 
280       private void configureviews() {
281         view.fitContent();
282         view.updateView();
283         for (int i = 0; i < subViews.length; ++i) {
284           subViews[i].fitContent();
285           subViews[i].updateView();
286         }
287       }
288     };
289     rootPane.addComponentListener(handler);
290   }
291 
292   /**
293    * Overwritten to prevent the standard toolbar from being created.
294    * Each <code>Graph2DView</code> used in this demo comes with its own
295    * custom toolbar.
296    * @return <code>null</code>.
297    */
298   protected JToolBar createToolBar() {
299     return null;
300   }
301 
302   /**
303    * Create a custom toolbar for the specified <code>Graph2DView</code>.
304    */
305   private JToolBar createToolBar( final Graph2DView view ) {
306     final JToolBar jtb = new JToolBar();
307     jtb.setFloatable(false);
308 
309     // add delete actions for the two editable views
310     if (view == this.view || view == this.subViews[0]) {
311       jtb.add(new DeleteSelection(view));
312     }
313 
314     // add a fit content action for all views
315     jtb.add(new FitContent(view));
316 
317     jtb.addSeparator();
318     // add a layout action for each view
319     jtb.add(createActionControl(createLayoutAction(view)));
320 
321     // add a synchronize view contents to model for the two non-editable views
322     if (view == this.subViews[1] || view == this.subViews[2]) {
323       jtb.add(createActionControl(new SynchViewToModel(view)));
324     }
325 
326     return jtb;
327   }
328 
329   /**
330    * Factory method for layout actions depending on the specified
331    * <code>Graph2DView</code>.
332    */
333   private Action createLayoutAction( final Graph2DView view ) {
334     final Layout layout;
335 
336     if (view == this.view) {
337       // create a hierarchical layout action for the model view
338       layout = new Layout(view, new IncrementalHierarchicLayouter());
339       layout.putValue(Action.SHORT_DESCRIPTION, "Layout Hierarchically");
340     } else if (view == subViews[0]) {
341       // create an orthogonal layout action for the editable non-model view
342       layout = new Layout(view, createOrthogonalLayouter());
343       layout.putValue(Action.SHORT_DESCRIPTION, "Layout Orthogonally");
344     } else if (view == subViews[1]) {
345       // create a grid layout action for the non-editable nodes-only view
346       layout = new Layout(view, new ComponentLayouter());
347       layout.putValue(Action.SHORT_DESCRIPTION, "Layout Component Grid");
348     } else if (view == subViews[2]) {
349       // create a balloon layout action for the non-editable diffs view
350       layout = new Layout(view, createBalloonLayouter());
351       layout.putValue(Action.SHORT_DESCRIPTION, "Layout Balloon-style Tree");
352     } else {
353       layout = new Layout(view, null);
354     }
355 
356     return layout;
357   }
358 
359 
360   public static void main( String[] args ) {
361     try {
362       EventQueue.invokeAndWait(new Runnable() {
363         public void run() {
364           Locale.setDefault(Locale.ENGLISH);
365           initLnF();
366           (new ModelViewManagerDemo()).start();
367         }
368       });
369     } catch (Throwable t) {
370       t.printStackTrace();
371     }
372   }
373 
374   /**
375    * Factory method for a template <code>NodeRealizer</code>.
376    */
377   private static NodeRealizer createRedCircle() {
378     final ShapeNodeRealizer snr = new ShapeNodeRealizer();
379     snr.setShapeType(ShapeNodeRealizer.ELLIPSE);
380     snr.setFillColor(new Color(196, 0, 64));
381     return snr;
382   }
383 
384   /**
385    * Factory method for a template <code>NodeRealizer</code>.
386    */
387   private static NodeRealizer createOrangeOctagon() {
388     final ShapeNodeRealizer snr = new ShapeNodeRealizer();
389     snr.setWidth(50);
390     snr.setShapeType(ShapeNodeRealizer.OCTAGON);
391     snr.setFillColor(new Color(223, 134, 17));
392     return snr;
393   }
394 
395   /**
396    * Factory method for a configured <code>Layouter</code>.
397    */
398   private static Layouter createBalloonLayouter() {
399     final OrthogonalEdgeRouter orthogonal = new OrthogonalEdgeRouter();
400     orthogonal.setCrossingCost(1.0);
401     orthogonal.setReroutingEnabled(true);
402     orthogonal.setSphereOfAction(OrthogonalEdgeRouter.ROUTE_SELECTED_EDGES);
403 
404     final TreeReductionStage trs = new TreeReductionStage();
405     trs.setNonTreeEdgeSelectionKey(OrthogonalEdgeRouter.SELECTED_EDGES);
406     trs.setNonTreeEdgeRouter(orthogonal);
407 
408     final BalloonLayouter bl = new BalloonLayouter();
409     bl.setRootNodePolicy(BalloonLayouter.DIRECTED_ROOT);
410     bl.setPreferredChildWedge(300);
411     bl.setPreferredRootWedge(360);
412     bl.setMinimalEdgeLength(40);
413     bl.setCompactnessFactor(0.5);
414     bl.setAllowOverlaps(false);
415     bl.setFromSketchModeEnabled(false);
416     bl.appendStage(trs);
417     return bl;
418   }
419 
420   /**
421    * Factory method for a configured <code>Layouter</code>.
422    */
423   private static Layouter createOrthogonalLayouter() {
424     final OrthogonalLayouter ol = new OrthogonalLayouter();
425     ol.setLayoutStyle(OrthogonalLayouter.NORMAL_TREE_STYLE);
426     ol.setGrid(25);
427     ol.setUseLengthReduction(true);
428     ol.setUseCrossingPostprocessing(true);
429     ol.setPerceivedBendsOptimizationEnabled(true);
430     ol.setUseRandomization(false);
431     ol.setUseFaceMaximization(true);
432     ol.setUseSketchDrawing(false);
433     return ol;
434   }
435 
436 
437   /**
438    * Custom <code>ModelViewManager.Filter</code> filter implementation
439    * that rejects all edge representatives from being automatically created by a
440    * <code>ModelViewManager</code>.
441    */
442   private static final class NoEdgesFilter implements ModelViewManager.Filter {
443     public boolean acceptInsertion( final Node node ) {
444       return true;
445     }
446 
447     public boolean acceptInsertion( final Edge edge ) {
448       return false;
449     }
450 
451     public boolean acceptRemoval( final Node node ) {
452       return true;
453     }
454 
455     public boolean acceptRemoval( final Edge edge ) {
456       return true;
457     }
458 
459     public boolean acceptRetention( final Node node ) {
460       return true;
461     }
462 
463     public boolean acceptRetention( final Edge edge ) {
464       return true;
465     }
466   }
467 
468   /**
469    * Custom <code>ModelViewManager.Filter</code> filter implementation
470    * that rejects edge and node representatives from being automatically
471    * created by a <code>ModelViewManager</code>, if the corresponding
472    * model element is stored in one of this filter's exclusion sets.
473    */
474   private static final class ExcludeFilter implements ModelViewManager.Filter {
475     final Set excludedNodes;
476     final Set excludedEdges;
477 
478     ExcludeFilter( final Graph graph ) {
479       excludedNodes = new HashSet();
480       excludedEdges = new HashSet();
481 
482       for (NodeCursor nc = graph.nodes(); nc.ok(); nc.next()) {
483         excludedNodes.add(nc.node());
484       }
485       for (EdgeCursor ec = graph.edges(); ec.ok(); ec.next()) {
486         excludedEdges.add(ec.edge());
487       }
488     }
489 
490     public boolean acceptInsertion( final Node node ) {
491       return !excludedNodes.contains(node);
492     }
493 
494     public boolean acceptInsertion( final Edge edge ) {
495       return !excludedEdges.contains(edge);
496     }
497 
498     public boolean acceptRemoval( final Node node ) {
499       return true;
500     }
501 
502     public boolean acceptRemoval( final Edge edge ) {
503       return true;
504     }
505 
506     public boolean acceptRetention( final Node node ) {
507       return true;
508     }
509 
510     public boolean acceptRetention( final Edge edge ) {
511       return true;
512     }
513   }
514 
515   /**
516    * <code>Graph2DCopyFactory</code> that uses a template
517    * <code>NodeRealizer</code> when copying nodes instead of copying the
518    * original realizer.
519    */
520   private static final class MyCopyFactory extends Graph2DCopyFactory {
521     private NodeRealizer template;
522 
523     MyCopyFactory( final NodeRealizer template ) {
524       setTemplateImpl(template);
525     }
526 
527     protected NodeRealizer copyRealizer( final NodeRealizer nr ) {
528       if (template != null) {
529         // instead of copying the original realizer, create a copy of the
530         // template realizer at an appropriate position
531         final NodeRealizer _nr = template.createCopy();
532         _nr.setCenter(nr.getCenterX(), nr.getCenterY());
533 
534         // manually copy node labels
535         final int lc = nr.labelCount();
536         if (lc > 0) {
537           _nr.setLabel((NodeLabel) nr.getLabel().clone());
538           for (int i = 1; i < lc; ++i) {
539             _nr.addLabel((NodeLabel) nr.getLabel(i).clone());
540           }
541         }
542         return _nr;
543       } else {
544         return nr.createCopy();
545       }
546     }
547 
548     NodeRealizer getTemplate() {
549       return template;
550     }
551 
552     void setTemplate( final NodeRealizer template ) {
553       setTemplateImpl(template);
554     }
555 
556     private void setTemplateImpl( final NodeRealizer template ) {
557       this.template = template != null ? template.createCopy() : null;
558     }
559   }
560 
561   /**
562    * <code>BackgroundRenderer</code> that displays a short text message.
563    */
564   private static final class MyBackgroundRenderer
565           extends DefaultBackgroundRenderer {
566     private String text;
567     private Color textColor;
568     private final Rectangle r;
569 
570     MyBackgroundRenderer( final Graph2DView view ) {
571       super(view);
572       textColor = new Color(192, 192, 192);
573       r = new Rectangle(0, 0, -1, 1);
574     }
575 
576     String getText() {
577       return text;
578     }
579 
580     void setText( final String text ) {
581       this.text = text;
582     }
583 
584     Color getTextColor() {
585       return textColor;
586     }
587 
588     void setTextColor( final Color color ) {
589       this.textColor = color;
590     }
591 
592     public void paint(
593             final Graphics2D gfx,
594             final int x,
595             final int y,
596             final int w,
597             final int h ) {
598       super.paint(gfx, x, y, w, h);
599       paintText(gfx);
600     }
601 
602     private void paintText( final Graphics2D gfx ) {
603       if (text != null && textColor != null) {
604         final Color oldColor = gfx.getColor();
605         final Font oldFont = gfx.getFont();
606 
607         undoWorldTransform(gfx);
608 
609         gfx.setColor(textColor);
610         gfx.setFont(oldFont.deriveFont(30.0f));
611 
612         view.getBounds(r);
613         r.setLocation(0, 0);
614         final FontMetrics fm = gfx.getFontMetrics();
615         final Rectangle2D bnds = fm.getStringBounds(text, gfx);
616         final float textX = (float) (r.x + (r.width - bnds.getWidth()) * 0.5);
617         final float textY = (float) (r.y + (r.height - bnds.getHeight()) * 0.5 + fm.getMaxAscent());
618         gfx.drawString(text, textX, textY);
619 
620         redoWorldTransform(gfx);
621 
622         gfx.setFont(oldFont);
623         gfx.setColor(oldColor);
624       }
625     }
626 
627 
628     static MyBackgroundRenderer newInstance( final Graph2DView view ) {
629       final MyBackgroundRenderer mbr = new MyBackgroundRenderer(view);
630       view.setBackgroundRenderer(mbr);
631       return mbr;
632     }
633   }
634 
635   /**
636    * <code>GraphListener</code> that updates all {@link y.view.View}s
637    * associated to source of an structural change.
638    */
639   private static class UpdateHandler implements GraphListener {
640     private int block;
641 
642     public void onGraphEvent( final GraphEvent e ) {
643       if (e.getGraph() instanceof Graph2D) {
644         switch (e.getType()) {
645           case GraphEvent.PRE_EVENT:
646             ++block;
647             break;
648           case GraphEvent.POST_EVENT:
649             --block;
650             break;
651           default:
652             break;
653         }
654         if (block == 0) {
655           ((Graph2D) e.getGraph()).updateViews();
656         }
657       }
658     }
659   }
660 
661   /**
662    * <code>Graph2DListener</code> that propagates label text changes to the
663    * model and all views of the demo's <code>ModelViewManager</code>.
664    */
665   private class LabelChangeHandler implements Graph2DListener {
666     private boolean armed;
667 
668     LabelChangeHandler() {
669       armed = true;
670     }
671 
672     public void onGraph2DEvent( final Graph2DEvent e ) {
673       if (!armed) {
674         return;
675       }
676 
677       if ("text".equals(e.getPropertyName()) &&
678           e.getSubject() instanceof NodeLabel) {
679         final NodeLabel nl = (NodeLabel) e.getSubject();
680         setLabelText(nl.getNode(), nl.getText());
681       }
682     }
683 
684     private void setLabelText( final Node node, final String text ) {
685       armed = false;
686 
687       final Node mn;
688       final Graph2D model = view.getGraph2D();
689       if (node.getGraph() != model) {
690         // determine the model representative of node
691         mn = manager.getModelNode(node);
692         if (mn != null) {
693           // set the label text for the model representative
694           model.getRealizer(mn).setLabelText(text);
695           model.updateViews();
696         }
697       } else {
698         mn = node;
699       }
700 
701       if (mn != null) {
702         for (Iterator it = manager.viewGraphs(); it.hasNext();) {
703           final Graph2D graph = ((Graph2D) it.next());
704           // determine the view representative of node
705           final Node vn = manager.getViewNode(mn, graph);
706           if (vn != null && vn != node) {
707             // set the label text for the view representative
708             graph.getRealizer(vn).setLabelText(text);
709             graph.updateViews();
710           }
711         }
712       }
713 
714       armed = true;
715     }
716   }
717 
718   /**
719    * <code>Action</code> that synchronizes the contents of the graph of its
720    * associated view to the model of the demo's <code>ModelViewManager</code.
721    */
722   private final class SynchViewToModel extends AbstractAction {
723     private final Graph2DView view;
724 
725     SynchViewToModel( final Graph2DView view ) {
726       super("Synchronize");
727       this.view = view;
728       final URL imageURL = ClassLoader.getSystemResource(
729               "demo/view/advanced/resource/Export16.gif");
730       if (imageURL != null) {
731         this.putValue(Action.SMALL_ICON, new ImageIcon(imageURL));
732       }
733       this.putValue(Action.SHORT_DESCRIPTION, "Synchronize View to Model");
734     }
735 
736     public void actionPerformed( final ActionEvent e ) {
737       manager.synchronizeViewGraphToModel(view.getGraph2D());
738 
739       ModelViewManagerDemo.this.view.fitContent();
740       ModelViewManagerDemo.this.view.updateView();
741       for (int i = 0; i < subViews.length; ++i) {
742         subViews[i].fitContent();
743         subViews[i].updateView();
744       }
745     }
746   }
747 
748   /**
749    * <code>Action</code> that calculates a layout for the graph of its
750    * associated view using its associated layout algorithm.
751    */
752   private static final class Layout extends AbstractAction {
753     private final Graph2DView view;
754     private final Layouter layouter;
755 
756     Layout( final Graph2DView view, final Layouter layouter ) {
757       super("Layout");
758       this.view = view;
759       this.layouter = layouter;
760       this.putValue(Action.SMALL_ICON, getIconResource("resource/layout.png"));
761       this.putValue(Action.SHORT_DESCRIPTION, "Layout Graph");
762     }
763 
764     public void actionPerformed( final ActionEvent e ) {
765       view.applyLayout(layouter);
766 
767       view.fitContent();
768       view.updateView();
769     }
770   }
771 }
772