1   
28  package demo.view.isometry;
29  
30  import demo.view.DemoBase;
31  import org.w3c.dom.Element;
32  import y.base.DataProvider;
33  import y.base.Edge;
34  import y.base.Node;
35  import y.base.NodeList;
36  import y.geom.YPoint;
37  import y.io.GraphMLIOHandler;
38  import y.io.IOHandler;
39  import y.io.graphml.NamespaceConstants;
40  import y.io.graphml.graph2d.EdgeLabelDeserializer;
41  import y.io.graphml.graph2d.EdgeLabelSerializer;
42  import y.io.graphml.graph2d.Graph2DGraphMLHandler;
43  import y.io.graphml.graph2d.NodeLabelDeserializer;
44  import y.io.graphml.graph2d.NodeLabelSerializer;
45  import y.io.graphml.input.DeserializationEvent;
46  import y.io.graphml.input.DeserializationHandler;
47  import y.io.graphml.input.GraphMLParseContext;
48  import y.io.graphml.input.GraphMLParseException;
49  import y.io.graphml.input.XPathUtils;
50  import y.io.graphml.output.GraphMLWriteContext;
51  import y.io.graphml.output.GraphMLWriteException;
52  import y.io.graphml.output.SerializationEvent;
53  import y.io.graphml.output.SerializationHandler;
54  import y.io.graphml.output.XmlWriter;
55  import y.layout.FixNodeLayoutStage;
56  import y.layout.LabelLayoutTranslator;
57  import y.layout.LayoutGraph;
58  import y.layout.LayoutTool;
59  import y.layout.Layouter;
60  import y.layout.NodeLayout;
61  import y.layout.hierarchic.IncrementalHierarchicLayouter;
62  import y.layout.orthogonal.OrthogonalGroupLayouter;
63  import y.util.D;
64  import y.util.DataProviderAdapter;
65  import y.view.DefaultBackgroundRenderer;
66  import y.view.DefaultOrderRenderer;
67  import y.view.EdgeLabel;
68  import y.view.EdgeRealizer;
69  import y.view.EditMode;
70  import y.view.GenericNodeRealizer;
71  import y.view.Graph2D;
72  import y.view.Graph2DLayoutExecutor;
73  import y.view.Graph2DTraversal;
74  import y.view.Graph2DView;
75  import y.view.Graph2DViewActions;
76  import y.view.HitInfo;
77  import y.view.NavigationMode;
78  import y.view.NodeLabel;
79  import y.view.NodeRealizer;
80  import y.view.NodeStateChangeHandler;
81  import y.view.ProxyShapeNodeRealizer;
82  import y.view.ViewMode;
83  import y.view.YLabel;
84  import y.view.YRenderingHints;
85  import y.view.hierarchy.GenericGroupNodeRealizer;
86  import y.view.hierarchy.HierarchyManager;
87  
88  import javax.swing.AbstractAction;
89  import javax.swing.Action;
90  import javax.swing.JToolBar;
91  import java.awt.Color;
92  import java.awt.Cursor;
93  import java.awt.EventQueue;
94  import java.awt.GradientPaint;
95  import java.awt.Graphics2D;
96  import java.awt.Paint;
97  import java.awt.Rectangle;
98  import java.awt.RenderingHints;
99  import java.awt.event.ActionEvent;
100 import java.awt.geom.AffineTransform;
101 import java.beans.PropertyChangeEvent;
102 import java.beans.PropertyChangeListener;
103 import java.io.IOException;
104 import java.net.URL;
105 import java.util.HashMap;
106 import java.util.Iterator;
107 import java.util.Locale;
108 import java.util.Map;
109 
110 
126 public class IsometryDemo extends DemoBase {
127   private static final double PAINT_DETAIL_THRESHOLD = 0.4;
128   private static final double MAX_ZOOM = 4.0;
129   private static final double MIN_ZOOM = 0.05;
130 
131   private static final int TYPE_INCREMENTAL_HIERARCHIC_LAYOUT = 0;
132   private static final int TYPE_ORTHOGONAL_GROUP_LAYOUT = 1;
133 
134   private int layoutType;
135   private boolean fractionMetricsEnabled;
136 
137   private IncrementalHierarchicLayouter ihl;
138   private OrthogonalGroupLayouter ogl;
139 
140   public IsometryDemo() {
141     this(null);
142   }
143 
144   public IsometryDemo(String helpFile) {
145     addHelpPane(helpFile);
146 
147     final Graph2D graph = view.getGraph2D();
148     new HierarchyManager(graph);
149 
150         graph.addDataProvider(
152         IsometryTransformationLayoutStage.TRANSFORMATION_DATA_DPKEY,
153         new TransformationDataProvider(graph));
154 
155             view.getRenderingHints().put(YRenderingHints.KEY_EDGE_LABEL_PAINTING, YRenderingHints.VALUE_EDGE_LABEL_PAINTING_OFF);
158     final IsometryGraphTraversal graphTraversal = new IsometryGraphTraversal();
159     view.setGraph2DRenderer(new DefaultOrderRenderer(graphTraversal, graphTraversal) {
160       public void paint(Graphics2D gfx, Graph2D graph) {
161         Rectangle clip = gfx.getClipBounds();
162         if (clip == null) {
163           clip = graph.getBoundingBox();
164         }
165 
166         final Graph2DTraversal paintOrder = getPaintOrder();
167         final int types = Graph2DTraversal.NODES | Graph2DTraversal.EDGES | Graph2DTraversal.EDGE_LABELS;
168         for (Iterator it = paintOrder.firstToLast(graph, types); it.hasNext();) {
169           final Object element = it.next();
170           if (element instanceof Edge) {
171             final EdgeRealizer er = graph.getRealizer((Edge) element);
172             if (intersects(er, clip)) {
173               er.paint(gfx);
174             }
175           } else if (element instanceof Node) {
176             final NodeRealizer nr = graph.getRealizer((Node) element);
177             if (intersects(nr, clip)) {
178               nr.paint(gfx);
179             }
180           } else if (element instanceof EdgeLabel) {
181             final EdgeLabel label = (EdgeLabel) element;
182             if (label.intersects(clip.getX(), clip.getY(), clip.getWidth(), clip.getHeight())) {
183               label.paint(gfx);
184             }
185           }
186         }
187       }
188     });
189 
190     IsometryRealizerFactory.initializeConfigurations();
191     configureLayouter();
192     configureBackgroundRenderer();
193     configureLabelRendering();
194     configureZoomThreshold();
195 
196     loadGraph("resource/iso_sample_1.graphml");
197   }
198 
199   
203   private void configureLayouter() {
204     ihl = new IncrementalHierarchicLayouter();
205     ihl.setOrthogonallyRouted(true);
206     ihl.setNodeToEdgeDistance(50);
207     ihl.setMinimumLayerDistance(40);
208     ihl.setLabelLayouterEnabled(false);
209     ihl.setIntegratedEdgeLabelingEnabled(true);
210     ihl.setConsiderNodeLabelsEnabled(true);
211 
212             final LabelLayoutTranslator llt = new LabelLayoutTranslator() {
215       public void doLayout(LayoutGraph graph) {
216         final Layouter coreLayouter = getCoreLayouter();
217         if (coreLayouter != null) {
218           coreLayouter.doLayout(graph);
219         }
220       }
221     };
222 
223     ogl = new OrthogonalGroupLayouter();
224     ogl.setIntegratedEdgeLabelingEnabled(true);
225     ogl.setConsiderNodeLabelsEnabled(true);
226     ogl.setLabelLayouter(llt);
227   }
228 
229   
232   private void configureBackgroundRenderer() {
233     final DefaultBackgroundRenderer bgRenderer = new FoggyFrameBackgroundRenderer(view);
234     bgRenderer.setImageResource(getResource("resource/grid.png"));
235     bgRenderer.setMode(DefaultBackgroundRenderer.CENTERED);
236     bgRenderer.setColor(Color.WHITE);
237     view.setBackgroundRenderer(bgRenderer);
238   }
239 
240   
244   private void configureLabelRendering() {
245     fractionMetricsEnabled = YLabel.isFractionMetricsForSizeCalculationEnabled();
246     YLabel.setFractionMetricsForSizeCalculationEnabled(true);
247     view.getRenderingHints().put(
248         RenderingHints.KEY_FRACTIONALMETRICS,
249         RenderingHints.VALUE_FRACTIONALMETRICS_ON);
250   }
251 
252   
256   public void dispose() {
257     YLabel.setFractionMetricsForSizeCalculationEnabled(fractionMetricsEnabled);
258   }
259 
260   
263   private void configureZoomThreshold() {
264         view.setPaintDetailThreshold(PAINT_DETAIL_THRESHOLD);
266         view.getCanvasComponent().addPropertyChangeListener(
268         new PropertyChangeListener() {
269           public void propertyChange(PropertyChangeEvent evt) {
270             if ("Zoom".equals(evt.getPropertyName())) {
271               final double zoom = ((Double) evt.getNewValue()).doubleValue();
272               if (zoom > MAX_ZOOM) {
273                 view.setZoom(MAX_ZOOM);
274               } else if (zoom < MIN_ZOOM) {
275                 view.setZoom(MIN_ZOOM);
276               }
277             }
278           }
279         });
280   }
281 
282   
285   protected void loadGraph(URL resource) {
286     if (resource == null) {
287       String message = "Resource \"" + resource + "\" not found in classpath";
288       D.showError(message);
289       throw new RuntimeException(message);
290     }
291 
292     try {
293       IOHandler ioh = createGraphMLIOHandler();
294       view.getGraph2D().clear();
295       ioh.read(view.getGraph2D(), resource);
296     } catch (IOException e) {
297       String message = "Unexpected error while loading resource \"" + resource + "\" due to " + e.getMessage();
298       D.bug(message);
299       throw new RuntimeException(message, e);
300     }
301 
302     view.getGraph2D().setURL(resource);
303 
304         IsometryRealizerFactory.applyIsometryRealizerDefaults(view.getGraph2D());
306 
307         runLayout(false);
309   }
310 
311   
315   protected JToolBar createToolBar() {
316     final JToolBar toolBar = super.createToolBar();
317     toolBar.addSeparator();
318     toolBar.add(createActionControl(createLayoutAction("Hierarchic", TYPE_INCREMENTAL_HIERARCHIC_LAYOUT), true));
319     toolBar.add(createActionControl(createLayoutAction("Orthogonal", TYPE_ORTHOGONAL_GROUP_LAYOUT), true));
320     return toolBar;
321   }
322 
323   
326   protected boolean isDeletionEnabled() {
327     return false;
328   }
329 
330   
333   protected boolean isUndoRedoEnabled() {
334     return false;
335   }
336 
337   
340   protected boolean isClipboardEnabled() {
341     return false;
342   }
343 
344   
347   private Action createLayoutAction(final String text, final int type) {
348     final AbstractAction action = new AbstractAction(text) {
349       public void actionPerformed(ActionEvent e) {
350         layoutType = type;
351         runLayout(false);
352       }
353     };
354     action.putValue(Action.SMALL_ICON, SHARED_LAYOUT_ICON);
355     return action;
356   }
357 
358   
362   protected GraphMLIOHandler createGraphMLIOHandler() {
363     final GraphMLIOHandler graphMLIOHandler = super.createGraphMLIOHandler();
364     final Graph2DGraphMLHandler graphMLHandler = graphMLIOHandler.getGraphMLHandler();
365     final IsometryDataIOHandler dataHandler = new IsometryDataIOHandler();
366     graphMLHandler.addSerializationHandler(dataHandler);
367     graphMLHandler.addDeserializationHandler(dataHandler);
368     graphMLHandler.addSerializationHandler(new IsometryEdgeLabelSerializer());
369     graphMLHandler.addDeserializationHandler(new IsometryEdgeLabelDeserializer());
370     graphMLHandler.addSerializationHandler(new IsometryNodeLabelSerializer());
371     graphMLHandler.addDeserializationHandler(new IsometryNodeLabelDeserializer());
372     return graphMLIOHandler;
373   }
374 
375   
380   protected EditMode createEditMode() {
381     return null;
382   }
383 
384   
387   protected void registerViewModes() {
388     super.registerViewModes();
389     view.addViewMode(new NavigationMode());
390     view.addViewMode(new GroupingViewMode());
391   }
392 
393   
397   private void runLayout(final boolean fromSketch) {
398     final Graph2DLayoutExecutor executor = new Graph2DLayoutExecutor();
399     executor.getLayoutMorpher().setKeepZoomFactor(fromSketch);
400 
401     if (layoutType == TYPE_INCREMENTAL_HIERARCHIC_LAYOUT) {
402       if (fromSketch) {
403         ihl.setLayoutMode(IncrementalHierarchicLayouter.LAYOUT_MODE_INCREMENTAL);
404         executor.doLayout(view, new FixGroupStateIconLayoutStage(new IsometryTransformationLayoutStage(ihl, fromSketch)));
405       } else {
406         ihl.setLayoutMode(IncrementalHierarchicLayouter.LAYOUT_MODE_FROM_SCRATCH);
407         executor.doLayout(view, new IsometryTransformationLayoutStage(ihl, fromSketch));
408       }
409     } else if (layoutType == TYPE_ORTHOGONAL_GROUP_LAYOUT) {
410       if (fromSketch) {
411         executor.doLayout(view, new FixGroupStateIconLayoutStage(new IsometryTransformationLayoutStage(ogl, fromSketch)));
412       } else {
413         executor.doLayout(view, new IsometryTransformationLayoutStage(ogl, fromSketch));
414       }
415     }
416   }
417 
418   
421   public static void main(final String[] args) {
422     EventQueue.invokeLater(new Runnable() {
423       public void run() {
424         Locale.setDefault(Locale.ENGLISH);
425         initLnF();
426         (new IsometryDemo("resource/iso_help.html")).start("Isometry Demo");
427       }
428     });
429   }
430 
431   
435   private static class FoggyFrameBackgroundRenderer extends DefaultBackgroundRenderer {
436 
437     private static final Color COLOR_BLANK = new Color(255, 255, 255, 0);
438 
439     private Color bgColor;
440 
441     public FoggyFrameBackgroundRenderer(Graph2DView view) {
442       super(view);
443       bgColor = Color.WHITE;
444     }
445 
446     public void paint(Graphics2D graphics, int x, int y, int w, int h) {
447       super.paint(graphics, x, y, w, h);
448 
449       paintFoggyFrame(graphics);
450     }
451 
452     private void paintFoggyFrame(Graphics2D graphics) {
453       final Paint oldPaint = graphics.getPaint();
454       final AffineTransform oldTransform = graphics.getTransform();
455 
456       undoWorldTransform(graphics);
457 
458       final float viewX = (float) 0;
459       final float viewY = (float) 0;
460       final float viewW = (float) view.getWidth();
461       final float viewH = (float) view.getHeight();
462 
463       final float halfWidth = viewW * 0.5f;
464       final float halfHeight = viewH * 0.5f;
465       graphics.setPaint(
466           new GradientPaint(viewX + halfWidth, viewY, bgColor, viewX + halfWidth, viewY + viewH * 0.2f, COLOR_BLANK));
467       graphics.fillRect((int) viewX, (int) viewY, (int) viewW, (int) (viewH * 0.2));
468       graphics.setPaint(
469           new GradientPaint(viewX + halfWidth, viewY + viewH * 0.8f, COLOR_BLANK, viewX + halfWidth, viewY + viewH,
470               bgColor));
471       graphics.fillRect((int) viewX, (int) (viewY + viewH * 0.8), (int) viewW, (int) (viewH * 0.2));
472       graphics.setPaint(
473           new GradientPaint(viewX, viewY + halfHeight, bgColor, viewX + viewW * 0.2f, viewY + halfHeight, COLOR_BLANK));
474       graphics.fillRect((int) viewX, (int) viewY, (int) (viewW * 0.2), (int) viewH);
475       graphics.setPaint(
476           new GradientPaint(viewX + viewW * 0.8f, viewY + halfHeight, COLOR_BLANK, viewX + viewW, viewY + halfHeight,
477               bgColor));
478       graphics.fillRect((int) (viewX + viewW * 0.8), (int) viewY, (int) (viewW * 0.2), (int) viewH);
479 
480       graphics.setPaint(oldPaint);
481       graphics.setTransform(oldTransform);
482     }
483   }
484 
485   
488   private static class TransformationDataProvider extends DataProviderAdapter {
489     private final Graph2D graph;
490 
491     public TransformationDataProvider(final Graph2D graph) {
492       this.graph = graph;
493     }
494 
495     public Object get(final Object dataHolder) {
496       if (dataHolder instanceof Node) {
497         NodeRealizer realizer = graph.getRealizer((Node) dataHolder);
498         if (realizer instanceof ProxyShapeNodeRealizer) {
499           realizer = ((ProxyShapeNodeRealizer) realizer).getRealizerDelegate();
500         }
501         if (realizer instanceof GenericNodeRealizer) {
502           return ((GenericNodeRealizer) realizer).getUserData();
503         }
504       } else if (dataHolder instanceof EdgeLabel) {
505         return ((EdgeLabel) dataHolder).getUserData();
506       }
507       return null;
508     }
509   }
510 
511   
514   private class IsometryDataIOHandler implements SerializationHandler, DeserializationHandler {
515     private static final String ELEMENT_NAME = "IsometryData";
516     private static final String ELEMENT_WIDTH = "width";
517     private static final String ELEMENT_HEIGHT = "height";
518     private static final String ELEMENT_DEPTH = "depth";
519     private static final String ELEMENT_HORIZONTAL = "horizontal";
520 
521     public void onHandleSerialization(SerializationEvent event) throws GraphMLWriteException {
522       final Object item = event.getItem();
523       if (item instanceof IsometryData) {
524         IsometryData isometryData = (IsometryData) item;
525         final XmlWriter writer = event.getWriter();
526         writer.writeStartElement(ELEMENT_NAME, NamespaceConstants.YFILES_JAVA_NS);
527         writer.writeAttribute(ELEMENT_WIDTH, isometryData.getWidth());
528         writer.writeAttribute(ELEMENT_HEIGHT, isometryData.getHeight());
529         writer.writeAttribute(ELEMENT_DEPTH, isometryData.getDepth());
530         writer.writeAttribute(ELEMENT_HORIZONTAL, isometryData.isHorizontal());
531         writer.writeEndElement();
532         event.setHandled(true);
533       }
534     }
535 
536     public void onHandleDeserialization(DeserializationEvent event) throws GraphMLParseException {
537       final org.w3c.dom.Node xmlNode = event.getXmlNode();
538       if (xmlNode.getNodeType() == org.w3c.dom.Node.ELEMENT_NODE
539           && NamespaceConstants.YFILES_JAVA_NS.equals(xmlNode.getNamespaceURI())
540           && ELEMENT_NAME.equals(xmlNode.getLocalName())) {
541         final Element element = (Element) xmlNode;
542         String attribute = element.getAttribute(ELEMENT_WIDTH);
543         final double width = Double.parseDouble(attribute);
544         attribute = element.getAttribute(ELEMENT_HEIGHT);
545         final double height = Double.parseDouble(attribute);
546         attribute = element.getAttribute(ELEMENT_DEPTH);
547         final double depth = Double.parseDouble(attribute);
548         attribute = element.getAttribute(ELEMENT_HORIZONTAL);
549         final boolean horizontal = Boolean.getBoolean(attribute);
550         event.setResult(new IsometryData(width, depth, height, horizontal));
551       }
552     }
553   }
554 
555   
559   private static class IsometryEdgeLabelSerializer extends EdgeLabelSerializer {
560     protected void serializeContent(EdgeLabel label, XmlWriter writer, GraphMLWriteContext context) throws
561         GraphMLWriteException {
562       super.serializeContent(label, writer, context);
563       if (label.getUserData() != null) {
564         writer.writeStartElement("UserData", NamespaceConstants.YFILES_JAVA_NS);
565         context.serialize(label.getUserData());
566         writer.writeEndElement();
567       }
568     }
569   }
570 
571   
575   private static class IsometryEdgeLabelDeserializer extends EdgeLabelDeserializer {
576     protected void parseEdgeLabel(GraphMLParseContext context, org.w3c.dom.Node root, EdgeLabel label) throws
577         GraphMLParseException {
578       super.parseEdgeLabel(context, root, label);
579       final org.w3c.dom.Node userDataNode = XPathUtils.selectFirstChildElement(root, "UserData",
580           NamespaceConstants.YFILES_JAVA_NS);
581       Object userData = null;
582       if (userDataNode != null) {
583         final org.w3c.dom.Node isoData = XPathUtils.selectFirstChildElement(userDataNode, "IsometryData",
584                   NamespaceConstants.YFILES_JAVA_NS);
585         if (isoData != null) {
586           userData = context.deserialize(isoData);
587         }
588       }
589       if (userData != null) {
590         label.setUserData(userData);
591       }
592     }
593   }
594 
595   
599   private static class IsometryNodeLabelSerializer extends NodeLabelSerializer {
600     protected void serializeContent(NodeLabel label, XmlWriter writer, GraphMLWriteContext context) throws
601         GraphMLWriteException {
602       super.serializeContent(label, writer, context);
603       if (label.getUserData() != null) {
604         writer.writeStartElement("UserData", NamespaceConstants.YFILES_JAVA_NS);
605         context.serialize(label.getUserData());
606         writer.writeEndElement();
607       }
608     }
609   }
610 
611   
615   private static class IsometryNodeLabelDeserializer extends NodeLabelDeserializer {
616     protected void parseNodeLabel(GraphMLParseContext context, org.w3c.dom.Node root, NodeLabel label) throws
617         GraphMLParseException {
618       super.parseNodeLabel(context, root, label);
619       final org.w3c.dom.Node userDataNode = XPathUtils.selectFirstChildElement(root, "UserData",
620           NamespaceConstants.YFILES_JAVA_NS);
621       Object userData = null;
622       if (userDataNode != null) {
623         final org.w3c.dom.Node isoData = XPathUtils.selectFirstChildElement(userDataNode, "IsometryData",
624             NamespaceConstants.YFILES_JAVA_NS);
625         if (isoData != null) {
626           userData = context.deserialize(isoData);
627         }
628       }
629       if (userData != null) {
630         label.setUserData(userData);
631       }
632     }
633   }
634 
635   
638   private class GroupingViewMode extends ViewMode {
639     public void mouseMoved(double x, double y) {
640       if (hitsGroupStateIcon(x, y)) {
641         view.setViewCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
642       } else {
643         view.setViewCursor(Cursor.getDefaultCursor());
644       }
645     }
646 
647     public void mouseClicked(double x, double y) {
648       if (hitsGroupStateIcon(x, y)) {
649         final HitInfo hit = getHitInfo(x, y);
650         final Node hitNode = hit.getHitNode();
651         final HierarchyManager hm = view.getGraph2D().getHierarchyManager();
652         view.getGraph2D().addDataProvider(FixNodeLayoutStage.FIXED_NODE_DPKEY, new DataProviderAdapter() {
653           public boolean getBool(Object dataHolder) {
654             return dataHolder instanceof Node && hitNode == dataHolder;
655           }
656         });
657         if (hm.isFolderNode(hitNode)) {
658           openFolder(hitNode);
659         } else {
660           closeGroup(hitNode);
661         }
662 
663                 runLayout(true);
665 
666         view.getGraph2D().removeDataProvider(FixNodeLayoutStage.FIXED_NODE_DPKEY);
667       }
668     }
669 
670     
673     private boolean hitsGroupStateIcon(double x, double y) {
674       final HitInfo hit = getHitInfo(x, y);
675       if (hit.hasHitNodes()) {
676         final Node hitNode = hit.getHitNode();
677         NodeRealizer realizer = view.getGraph2D().getRealizer(hitNode);
678         if (realizer instanceof ProxyShapeNodeRealizer) {
679           ProxyShapeNodeRealizer proxy = (ProxyShapeNodeRealizer) realizer;
680           realizer = proxy.getRealizerDelegate();
681         }
682         if (realizer instanceof GenericGroupNodeRealizer
683             && IsometryGroupPainter.hitsGroupStateIcon(realizer, x, y)) {
684           return true;
685         }
686       }
687       return false;
688     }
689 
690     
694     protected void closeGroup(final Node node) {
695       Graph2DViewActions.CloseGroupsAction helper = new Graph2DViewActions.CloseGroupsAction(view) {
696         protected boolean acceptNode(final Graph2D graph, final Node groupNode) {
697           return groupNode == node;
698         }
699       };
700       helper.setNodeStateChangeHandler(new GroupNodeStateChangeHandler(helper.getNodeStateChangeHandler()));
701       helper.closeGroups(view);
702     }
703 
704     
708     protected void openFolder(final Node node) {
709       Graph2DViewActions.OpenFoldersAction helper = new Graph2DViewActions.OpenFoldersAction(view) {
710         protected boolean acceptNode(final Graph2D graph, final Node groupNode) {
711           return groupNode == node;
712         }
713       };
714       helper.setNodeStateChangeHandler(new GroupNodeStateChangeHandler(helper.getNodeStateChangeHandler()));
715       helper.openFolders(view);
716     }
717   }
718 
719   
725   private static final class GroupNodeStateChangeHandler implements NodeStateChangeHandler {
726     private final NodeStateChangeHandler handler;
727     private final Map state;
728 
729     GroupNodeStateChangeHandler(final NodeStateChangeHandler handler) {
730       this.handler = handler;
731       state = new HashMap();
732     }
733 
734     
737     public void preNodeStateChange(final Node node) {
738       state.put(node, calculateFixPoint(node));
739 
740       if (handler != null) {
741         handler.postNodeStateChange(node);
742       }
743     }
744 
745     private Object calculateFixPoint(final Node node) {
746       final NodeRealizer realizer = ((Graph2D) node.getGraph()).getRealizer(node);
747       final double[] corners = new double[16];
748       final IsometryData isometryData = IsometryRealizerFactory.getIsometryData(realizer);
749       isometryData.calculateCorners(corners);
750       IsometryData.moveTo(realizer.getX(), realizer.getY(), corners);
751 
752       return new YPoint(corners[IsometryData.C3_X], corners[IsometryData.C3_Y]);
753     }
754 
755     
758     public void postNodeStateChange(final Node node) {
759       final YPoint fixPoint = (YPoint) state.remove(node);
760       if (fixPoint != null) {
761         restoreFixPoint(node, fixPoint);
762       }
763 
764       if (handler != null) {
765         handler.postNodeStateChange(node);
766       }
767     }
768 
769     private void restoreFixPoint(final Node node, final YPoint fixPoint) {
770       final Graph2D graph = (Graph2D) node.getGraph();
771       NodeRealizer realizer = graph.getRealizer(node);
772       if (realizer instanceof ProxyShapeNodeRealizer) {
773         realizer = ((ProxyShapeNodeRealizer) realizer).getRealizerDelegate();
774       }
775       final double[] corners = new double[16];
776       final IsometryData isometryData = IsometryRealizerFactory.getIsometryData(realizer);
777       isometryData.calculateCorners(corners);
778       IsometryData.moveTo(realizer.getX(), realizer.getY(), corners);
779 
780       final double newCornerX = corners[IsometryData.C3_X];
781       final double newCornerY = corners[IsometryData.C3_Y];
782 
783       final double dx = fixPoint.getX() - newCornerX;
784       final double dy = fixPoint.getY() - newCornerY;
785 
786       final HierarchyManager hm = graph.getHierarchyManager();
787       if (hm.isGroupNode(node)) {
788         final NodeList subGraph = new NodeList(hm.getChildren(node));
789         subGraph.add(node);
790         LayoutTool.moveSubgraph(graph, subGraph.nodes(), dx, dy);
791       } else {
792         LayoutTool.moveNode(graph, node, dx, dy);
793       }
794     }
795   }
796 
797   
802   private class FixGroupStateIconLayoutStage extends FixNodeLayoutStage {
803     private FixGroupStateIconLayoutStage(final Layouter core) {
804       super(core);
805     }
806 
807     
811     protected YPoint calculateFixPoint(final LayoutGraph graph, final NodeList fixed) {
812       final Node node = fixed.firstNode();
813       final DataProvider provider = graph.getDataProvider(IsometryTransformationLayoutStage.TRANSFORMATION_DATA_DPKEY);
814       final IsometryData isometryData = (IsometryData) provider.get(node);
815 
816       final double[] corners = new double[16];
817       isometryData.calculateCorners(corners);
818       final NodeLayout nodeLayout = graph.getNodeLayout(node);
819       IsometryData.moveTo(nodeLayout.getX(), nodeLayout.getY(), corners);
820       return new YPoint(corners[IsometryData.C3_X], corners[IsometryData.C3_Y]);
821     }
822   }
823 }
824