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.hierarchy;
15  
16  import y.view.ImageNodePainter;
17  import y.view.GenericNodeRealizer;
18  import y.view.HitInfo;
19  import y.view.Graph2DViewActions;
20  import y.view.ViewMode;
21  import y.view.Graph2D;
22  import y.view.NodeRealizer;
23  import y.view.NodeLabel;
24  import y.view.ShapeNodePainter;
25  import y.view.hierarchy.DefaultGenericAutoBoundsFeature;
26  import y.view.hierarchy.GenericGroupNodeRealizer;
27  import y.view.hierarchy.DefaultHierarchyGraphFactory;
28  import y.view.hierarchy.HierarchyManager;
29  import y.base.Node;
30  import y.geom.YInsets;
31  
32  import java.awt.EventQueue;
33  import java.awt.Font;
34  import java.awt.Color;
35  import java.awt.Dimension;
36  import java.awt.geom.Dimension2D;
37  import java.awt.event.ActionEvent;
38  import java.net.URL;
39  import java.util.Locale;
40  import java.util.Map;
41  import javax.swing.ActionMap;
42  import javax.swing.Action;
43  
44  import demo.view.DemoBase;
45  
46  /**
47   * Demonstrates how to customize the visual representation of group and folder
48   * nodes using {@link y.view.hierarchy.GenericGroupNodeRealizer}.
49   *
50   * @see <a href="http://docs.yworks.com/yfiles/doc/developers-guide/hier_realizers.html#cls_GroupNodeRealizer">Section Node Realizers</a> in the yFiles for Java Developer's Guide
51   */
52  public class CustomGroupVisualizationDemo extends GroupingDemo {
53    /**
54     * The name of the configuration used for group nodes.
55     */
56    private static final String CONFIGURATION_GROUP =
57            "CustomGroupVisualizationDemo_GROUP_NODE";
58    /**
59     * The name of the configuration used for folder nodes.
60     */
61    private static final String CONFIGURATION_FOLDER =
62            "CustomGroupVisualizationDemo_FOLDER_NODE";
63  
64    protected void loadInitialGraph() {
65      loadGraph(getClass().getResource("resource/CustomGroupVisualizationDemo.graphml"));
66    }
67  
68    /**
69     * Overwritten to register a view mode that opens folders/closes groups on
70     * double clicks.
71     */
72    protected void registerViewModes() {
73      super.registerViewModes();
74      view.addViewMode(new StateChangeViewMode());
75    }
76  
77    /**
78     * Creates and registers configured, customized group and folder node
79     * representations.
80     */
81    protected void configureDefaultGroupNodeRealizers() {
82      final GenericNodeRealizer.Factory factory = GenericNodeRealizer.getFactory();
83  
84      // configure folder nodes
85      // for folders, simply display an image instead of the usual rectangle
86      final String resource = "resource/yicon.png";
87      final URL folderUrl = DemoBase.class.getResource(resource);
88      if (folderUrl == null) {
89        throw new IllegalStateException("Could not find \"" + resource + "\".");
90      } else {
91        final ImageNodePainter folderPainter = new ImageNodePainter(folderUrl);
92        final Map map = factory.createDefaultConfigurationMap();
93        map.put(GenericNodeRealizer.Painter.class, folderPainter);
94        map.put(GenericNodeRealizer.ContainsTest.class, folderPainter);
95  
96        configureBoundsAndSizeHandling(map);
97  
98        factory.addConfiguration(CONFIGURATION_FOLDER, map);
99      }
100 
101     final GenericGroupNodeRealizer fnr = new GenericGroupNodeRealizer();
102     fnr.setConfiguration(CONFIGURATION_FOLDER);
103     fnr.setGroupClosed(true);
104     configureDefaultFolderLabel(fnr.getLabel());
105 
106 
107     // configure group nodes
108     // for groups, display a round rectangle with the default label to the left
109     // and rotated 90 degrees counterclockwise
110     {
111       final ShapeNodePainter groupPainter =
112               new ShapeNodePainter(ShapeNodePainter.ROUND_RECT);
113       final Map map = factory.createDefaultConfigurationMap();
114       map.put(GenericNodeRealizer.Painter.class, groupPainter);
115       map.put(GenericNodeRealizer.ContainsTest.class, groupPainter);
116 
117       configureBoundsAndSizeHandling(map);
118 
119       factory.addConfiguration(CONFIGURATION_GROUP, map);
120     }
121 
122     final GenericGroupNodeRealizer gnr = new GenericGroupNodeRealizer();
123     gnr.setFillColor(new Color(202, 227, 255));
124     gnr.setConfiguration(CONFIGURATION_GROUP);
125     gnr.setGroupClosed(false);
126     configureDefaultGroupLabel(gnr.getLabel());
127 
128 
129     // register the above configured group and folder node representations
130     final DefaultHierarchyGraphFactory hgf =
131             (DefaultHierarchyGraphFactory) getHierarchyManager().getGraphFactory();
132     hgf.setProxyNodeRealizerEnabled(true);
133     hgf.setDefaultGroupNodeRealizer(gnr);
134     hgf.setDefaultFolderNodeRealizer(fnr);
135   }
136 
137   /**
138    * Configures the default label for folder node representations.
139    * @param fnl   the default label of a folder node.
140    */
141   private void configureDefaultFolderLabel( final NodeLabel fnl ) {
142     fnl.setFontSize(14);
143     fnl.setFontStyle(Font.BOLD);
144     fnl.setTextColor(Color.WHITE);
145     fnl.setAutoSizePolicy(NodeLabel.AUTOSIZE_NODE_WIDTH);
146     fnl.setBackgroundColor(new Color(62, 66, 69));
147     fnl.setModel(NodeLabel.SANDWICH);
148     fnl.setPosition(NodeLabel.S);
149   }
150 
151   /**
152    * Configures the default label for group node representations.
153    * @param gnl   the default label of a group node.
154    */
155   private void configureDefaultGroupLabel( final NodeLabel gnl ) {
156     gnl.setFontSize(14);
157     gnl.setFontStyle(Font.BOLD);
158     gnl.setTextColor(Color.WHITE);
159     gnl.setAutoSizePolicy(NodeLabel.AUTOSIZE_CONTENT);
160     gnl.setBackgroundColor(null);
161     gnl.setRotationAngle(270);
162     gnl.setPosition(NodeLabel.BOTTOM_LEFT);
163   }
164 
165   /**
166    * Registers custom auto bounds handling in the specified implementations
167    * map for vertical labels.
168    * @param map   a configuration map.
169    */
170   private void configureBoundsAndSizeHandling( final Map map ) {
171     final DefaultGenericAutoBoundsFeature abf = new DefaultGenericAutoBoundsFeature() {
172       /**
173        * Overwritten to handle <code>GenericGroupNodeRealizer</code> instances
174        * with vertical, left positioned default labels appropriately.
175        * @param context   the group or folder node representation for which
176        * insets have to be calculated.
177        * @return the insets for the specified group or folder node
178        * representation.
179        */
180       public YInsets getAutoBoundsInsets( final NodeRealizer context ) {
181         if (accept(context)) {
182           final YInsets insets =
183                   ((GenericGroupNodeRealizer) context).getMinimalInsets();
184           return new YInsets(
185               insets.top,
186               Math.max(insets.left, context.getLabel().getWidth() + 5),
187               insets.bottom,
188               insets.right
189           );
190         }
191 
192         return super.getInsets(context);
193       }
194 
195       /**
196        * Overwritten to handle <code>GenericGroupNodeRealizer</code> instances
197        * with vertical, left positioned default labels appropriately.
198        * @param context   the group or folder node representation for which
199        * the minimal label size has to be calculated.
200        * @return the minimum size that is to be reserved as label size for the
201        * the specified group or folder node representation.
202        */
203       protected Dimension2D calculateMinimalLabelSize( final NodeRealizer context ) {
204         if (accept(context)) {
205           final NodeLabel label = context.getLabel();
206           final double d = 2*label.getDistance();
207           // since the label is rotated 90 degrees counterclockwise
208           // switch its width and height
209           return new Dimension(
210                   (int) Math.ceil(label.getContentHeight() + d),
211                   (int) Math.ceil(label.getContentWidth() + d));
212         }
213 
214         return super.calculateMinimalLabelSize(context);
215       }
216 
217       /**
218        * Determines whether the specified realizer qualifies for custom
219        * auto bounds handling due to a vertical, left positioned default label.
220        * @param context   the <code>NodeRealizer</code> to check.
221        * @return <code>true</code> if the specified realizer qualifies for
222        * custom auto bounds handling; <code>false</code> otherwise.
223        */
224       private boolean accept( final NodeRealizer context ) {
225         if (context instanceof GenericGroupNodeRealizer) {
226           GenericGroupNodeRealizer ggnr = ((GenericGroupNodeRealizer) context);
227           if (ggnr.labelCount() > 0) {
228             final NodeLabel nl = ggnr.getLabel();
229             if (nl.getModel() == NodeLabel.INTERNAL &&
230                 nl.getPosition() == NodeLabel.BOTTOM_LEFT &&
231                 nl.getRotationAngle() == 270) {
232               return true;
233             }
234           }
235         }
236 
237         return false;
238       }
239     };
240     abf.setConsiderNodeLabelSize(true);
241     map.put(GenericGroupNodeRealizer.GenericAutoBoundsFeature.class, abf);
242     map.put(GenericNodeRealizer.GenericSizeConstraintProvider.class, abf);
243     map.put(GenericNodeRealizer.LabelBoundsChangedHandler.class, abf);
244   }
245 
246 
247 
248   public static void main( String[] args ) {
249     EventQueue.invokeLater(new Runnable() {
250       public void run() {
251         Locale.setDefault(Locale.ENGLISH);
252         initLnF();
253         (new CustomGroupVisualizationDemo()).start();
254       }
255     });
256   }
257 
258 
259   /**
260    * A {@link y.view.ViewMode} that handles double clicks for folder and
261    * group nodes.
262    */
263   private static final class StateChangeViewMode extends ViewMode {
264     public void mouseClicked( final double x, final double y ) {
265       if (lastClickEvent.getClickCount() == 2 &&
266           lastClickEvent.getButton() == 1) {
267         final HitInfo hitInfo = getHitInfo(x, y);
268         if (hitInfo.hasHitNodes()) {
269           final Node node = hitInfo.getHitNode();
270           final Graph2D graph = getGraph2D();
271           final HierarchyManager manager = graph.getHierarchyManager();
272           if (manager != null) {
273             if (manager.isGroupNode(node)) {
274               closeGroup(graph, node);
275             } else if (manager.isFolderNode(node)) {
276               openFolder(graph, node);
277             }
278           }
279         }
280       }
281     }
282 
283     /**
284      * Closes the specified group node.
285      * @param graph   the specified node's associated graph.
286      * @param node    the group node that has to be converted to a folder node.
287      */
288     protected void closeGroup( final Graph2D graph, final Node node ) {
289       Action action = null;
290 
291       final ActionMap amap = view.getCanvasComponent().getActionMap();
292       if (amap != null) {
293         action = amap.get(Graph2DViewActions.CLOSE_GROUPS);
294       }
295       if (action == null) {
296         action = new Graph2DViewActions.CloseGroupsAction();
297       }
298 
299       view.getGraph2D().unselectAll();
300       view.getGraph2D().setSelected(node, true);
301       action.actionPerformed(new ActionEvent(view, ActionEvent.ACTION_PERFORMED, ""));
302     }
303 
304     /**
305      * Opens the specified folder node.
306      * @param graph   the specified node's associated graph.
307      * @param node    the folder node that has to be converted to a group node.
308      */
309     protected void openFolder( final Graph2D graph, final Node node ) {
310       Action action = null;
311 
312       final ActionMap amap = view.getCanvasComponent().getActionMap();
313       if (amap != null) {
314         action = amap.get(Graph2DViewActions.OPEN_FOLDERS);
315       }
316       if (action == null) {
317         action = new Graph2DViewActions.OpenFoldersAction();
318       }
319 
320       view.getGraph2D().unselectAll();
321       view.getGraph2D().setSelected(node, true);
322       action.actionPerformed(new ActionEvent(view, ActionEvent.ACTION_PERFORMED, ""));
323     }
324   }
325 }
326