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