1   /****************************************************************************
2    * This demo file is part of yFiles for Java 2.14.
3    * Copyright (c) 2000-2017 by yWorks GmbH, Vor dem Kreuzberg 28,
4    * 72070 Tuebingen, Germany. All rights reserved.
5    * 
6    * yFiles demo files exhibit yFiles for Java functionalities. Any redistribution
7    * of demo files in source code or binary form, with or without
8    * modification, is not permitted.
9    * 
10   * Owners of a valid software license for a yFiles for Java version that this
11   * demo is shipped with are allowed to use the demo source code as basis
12   * for their own yFiles for Java powered applications. Use of such programs is
13   * governed by the rights and conditions as set out in the yFiles for Java
14   * license agreement.
15   * 
16   * THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY EXPRESS OR IMPLIED
17   * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
18   * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
19   * NO EVENT SHALL yWorks BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
21   * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
22   * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
23   * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
24   * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25   * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26   *
27   ***************************************************************************/
28  package demo.view.hierarchy;
29  
30  import y.view.ImageNodePainter;
31  import y.view.GenericNodeRealizer;
32  import y.view.HitInfo;
33  import y.view.Graph2DViewActions;
34  import y.view.ViewMode;
35  import y.view.Graph2D;
36  import y.view.NodeRealizer;
37  import y.view.NodeLabel;
38  import y.view.ShapeNodePainter;
39  import y.view.hierarchy.DefaultGenericAutoBoundsFeature;
40  import y.view.hierarchy.GenericGroupNodeRealizer;
41  import y.view.hierarchy.DefaultHierarchyGraphFactory;
42  import y.view.hierarchy.HierarchyManager;
43  import y.base.Node;
44  import y.geom.YInsets;
45  
46  import java.awt.EventQueue;
47  import java.awt.Font;
48  import java.awt.Color;
49  import java.awt.Dimension;
50  import java.awt.geom.Dimension2D;
51  import java.awt.event.ActionEvent;
52  import java.net.URL;
53  import java.util.Locale;
54  import java.util.Map;
55  import javax.swing.ActionMap;
56  import javax.swing.Action;
57  
58  import demo.view.DemoBase;
59  
60  /**
61   * Demonstrates how to customize the visual representation of group and folder
62   * nodes using {@link y.view.hierarchy.GenericGroupNodeRealizer}.
63   *
64   * @see <a href="http://docs.yworks.com/yfiles/doc/api/index.html#/dguide/hier_realizers#cls_GroupNodeRealizer" target="_blank">Section Node Realizers</a> in the yFiles for Java Developer's Guide
65   */
66  public class CustomGroupVisualizationDemo extends GroupingDemo {
67    /**
68     * The name of the configuration used for group nodes.
69     */
70    private static final String CONFIGURATION_GROUP =
71            "CustomGroupVisualizationDemo_GROUP_NODE";
72    /**
73     * The name of the configuration used for folder nodes.
74     */
75    private static final String CONFIGURATION_FOLDER =
76            "CustomGroupVisualizationDemo_FOLDER_NODE";
77  
78    protected void loadInitialGraph() {
79      loadGraph(getResource("resource/CustomGroupVisualizationDemo.graphml"));
80    }
81  
82    /**
83     * Overwritten to register a view mode that opens folders/closes groups on
84     * double clicks.
85     */
86    protected void registerViewModes() {
87      super.registerViewModes();
88      view.addViewMode(new StateChangeViewMode());
89    }
90  
91    /**
92     * Creates and registers configured, customized group and folder node
93     * representations.
94     */
95    protected void configureDefaultGroupNodeRealizers() {
96      final GenericNodeRealizer.Factory factory = GenericNodeRealizer.getFactory();
97  
98      // configure folder nodes
99      final Map folderImpls = factory.createDefaultConfigurationMap();
100     configureFolderNodes(folderImpls);
101     factory.addConfiguration(CONFIGURATION_FOLDER, folderImpls);
102 
103     final GenericGroupNodeRealizer fnr = new GenericGroupNodeRealizer();
104     fnr.setConfiguration(CONFIGURATION_FOLDER);
105     fnr.setGroupClosed(true);
106     configureDefaultFolderLabel(fnr.getLabel());
107 
108 
109     // configure group nodes
110     final Map groupImpls = factory.createDefaultConfigurationMap();
111     configureGroupNodes(groupImpls);
112     factory.addConfiguration(CONFIGURATION_GROUP, groupImpls);
113 
114     final GenericGroupNodeRealizer gnr = new GenericGroupNodeRealizer();
115     gnr.setFillColor(new Color(202, 227, 255));
116     gnr.setConfiguration(CONFIGURATION_GROUP);
117     gnr.setGroupClosed(false);
118     configureDefaultGroupLabel(gnr.getLabel());
119 
120 
121     // register the above configured group and folder node representations
122     final DefaultHierarchyGraphFactory hgf =
123             (DefaultHierarchyGraphFactory) getHierarchyManager().getGraphFactory();
124     hgf.setProxyNodeRealizerEnabled(true);
125     hgf.setDefaultGroupNodeRealizer(gnr);
126     hgf.setDefaultFolderNodeRealizer(fnr);
127   }
128 
129   /**
130    * Registers {@link GenericNodeRealizer} behavior interface implementations
131    * that display a round rectangle with the default label to the left and
132    * rotated 90 degrees counterclockwise for group nodes.
133    * @param impls the implementations map for group nodes.
134    */
135   protected void configureGroupNodes( final Map impls ) {
136     final ShapeNodePainter painter = new ShapeNodePainter(ShapeNodePainter.ROUND_RECT);
137     impls.put(GenericNodeRealizer.Painter.class, painter);
138     impls.put(GenericNodeRealizer.ContainsTest.class, painter);
139 
140     configureBoundsAndSizeHandling(impls);
141   }
142 
143   /**
144    * Registers {@link GenericNodeRealizer} behavior interface implementations
145    * that display an image instead of the usual rectangle for folder nodes.
146    * @param impls the implementations map for folder nodes.
147    */
148   protected void configureFolderNodes( final Map impls ) {
149     final String resource = "resource/yicon.png";
150     final URL folderUrl = getResource(DemoBase.class, resource);
151     if (folderUrl == null) {
152       throw new IllegalStateException("Could not find \"" + resource + "\".");
153     } else {
154       final ImageNodePainter painter = new ImageNodePainter(folderUrl);
155       impls.put(GenericNodeRealizer.Painter.class, painter);
156       impls.put(GenericNodeRealizer.ContainsTest.class, painter);
157 
158       configureBoundsAndSizeHandling(impls);
159     }
160   }
161 
162   /**
163    * Configures the default label for folder node representations.
164    * @param fnl   the default label of a folder node.
165    */
166   private void configureDefaultFolderLabel( final NodeLabel fnl ) {
167     fnl.setFontSize(14);
168     fnl.setFontStyle(Font.BOLD);
169     fnl.setTextColor(Color.WHITE);
170     fnl.setAutoSizePolicy(NodeLabel.AUTOSIZE_NODE_WIDTH);
171     fnl.setBackgroundColor(new Color(62, 66, 69));
172     fnl.setModel(NodeLabel.SANDWICH);
173     fnl.setPosition(NodeLabel.S);
174   }
175 
176   /**
177    * Configures the default label for group node representations.
178    * @param gnl   the default label of a group node.
179    */
180   private void configureDefaultGroupLabel( final NodeLabel gnl ) {
181     gnl.setFontSize(14);
182     gnl.setFontStyle(Font.BOLD);
183     gnl.setTextColor(Color.WHITE);
184     gnl.setAutoSizePolicy(NodeLabel.AUTOSIZE_CONTENT);
185     gnl.setBackgroundColor(null);
186     gnl.setRotationAngle(270);
187     gnl.setPosition(NodeLabel.BOTTOM_LEFT);
188   }
189 
190   /**
191    * Registers custom auto bounds handling in the specified implementations
192    * map for vertical labels.
193    * @param map   a configuration map.
194    */
195   private void configureBoundsAndSizeHandling( final Map map ) {
196     final DefaultGenericAutoBoundsFeature abf = new DefaultGenericAutoBoundsFeature() {
197       /**
198        * Overwritten to handle <code>GenericGroupNodeRealizer</code> instances
199        * with vertical, left positioned default labels appropriately.
200        * @param context   the group or folder node representation for which
201        * insets have to be calculated.
202        * @return the insets for the specified group or folder node
203        * representation.
204        */
205       public YInsets getAutoBoundsInsets( final NodeRealizer context ) {
206         if (accept(context)) {
207           final YInsets insets =
208                   ((GenericGroupNodeRealizer) context).getMinimalInsets();
209           return new YInsets(
210               insets.top,
211               Math.max(insets.left, context.getLabel().getWidth() + 5),
212               insets.bottom,
213               insets.right
214           );
215         }
216 
217         return super.getInsets(context);
218       }
219 
220       /**
221        * Overwritten to handle <code>GenericGroupNodeRealizer</code> instances
222        * with vertical, left positioned default labels appropriately.
223        * @param context   the group or folder node representation for which
224        * the minimal label size has to be calculated.
225        * @return the minimum size that is to be reserved as label size for the
226        * the specified group or folder node representation.
227        */
228       protected Dimension2D calculateMinimalLabelSize( final NodeRealizer context ) {
229         if (accept(context)) {
230           final NodeLabel label = context.getLabel();
231           final double d = 2*label.getDistance();
232           // since the label is rotated 90 degrees counterclockwise
233           // switch its width and height
234           return new Dimension(
235                   (int) Math.ceil(label.getContentHeight() + d),
236                   (int) Math.ceil(label.getContentWidth() + d));
237         }
238 
239         return super.calculateMinimalLabelSize(context);
240       }
241 
242       /**
243        * Determines whether the specified realizer qualifies for custom
244        * auto bounds handling due to a vertical, left positioned default label.
245        * @param context   the <code>NodeRealizer</code> to check.
246        * @return <code>true</code> if the specified realizer qualifies for
247        * custom auto bounds handling; <code>false</code> otherwise.
248        */
249       private boolean accept( final NodeRealizer context ) {
250         if (context instanceof GenericGroupNodeRealizer) {
251           GenericGroupNodeRealizer ggnr = ((GenericGroupNodeRealizer) context);
252           if (ggnr.labelCount() > 0) {
253             final NodeLabel nl = ggnr.getLabel();
254             if (nl.getModel() == NodeLabel.INTERNAL &&
255                 nl.getPosition() == NodeLabel.BOTTOM_LEFT &&
256                 nl.getRotationAngle() == 270) {
257               return true;
258             }
259           }
260         }
261 
262         return false;
263       }
264     };
265     abf.setConsiderNodeLabelSize(true);
266     map.put(GenericGroupNodeRealizer.GenericAutoBoundsFeature.class, abf);
267     map.put(GenericNodeRealizer.GenericSizeConstraintProvider.class, abf);
268     map.put(GenericNodeRealizer.LabelBoundsChangedHandler.class, abf);
269   }
270 
271 
272 
273   public static void main( String[] args ) {
274     EventQueue.invokeLater(new Runnable() {
275       public void run() {
276         Locale.setDefault(Locale.ENGLISH);
277         initLnF();
278         (new CustomGroupVisualizationDemo()).start();
279       }
280     });
281   }
282 
283 
284   /**
285    * A {@link y.view.ViewMode} that handles double clicks for folder and
286    * group nodes.
287    */
288   private static final class StateChangeViewMode extends ViewMode {
289     public void mouseClicked( final double x, final double y ) {
290       if (lastClickEvent.getClickCount() == 2 &&
291           lastClickEvent.getButton() == 1) {
292         final HitInfo hitInfo = getHitInfo(x, y);
293         if (hitInfo.hasHitNodes()) {
294           final Node node = hitInfo.getHitNode();
295           final Graph2D graph = getGraph2D();
296           final HierarchyManager manager = graph.getHierarchyManager();
297           if (manager != null) {
298             if (manager.isGroupNode(node)) {
299               closeGroup(graph, node);
300             } else if (manager.isFolderNode(node)) {
301               openFolder(graph, node);
302             }
303           }
304         }
305       }
306     }
307 
308     /**
309      * Closes the specified group node.
310      * @param graph   the specified node's associated graph.
311      * @param node    the group node that has to be converted to a folder node.
312      */
313     protected void closeGroup( final Graph2D graph, final Node node ) {
314       Action action = null;
315 
316       final ActionMap amap = view.getCanvasComponent().getActionMap();
317       if (amap != null) {
318         action = amap.get(Graph2DViewActions.CLOSE_GROUPS);
319       }
320       if (action == null) {
321         action = new Graph2DViewActions.CloseGroupsAction();
322       }
323 
324       view.getGraph2D().unselectAll();
325       view.getGraph2D().setSelected(node, true);
326       action.actionPerformed(new ActionEvent(view, ActionEvent.ACTION_PERFORMED, ""));
327     }
328 
329     /**
330      * Opens the specified folder node.
331      * @param graph   the specified node's associated graph.
332      * @param node    the folder node that has to be converted to a group node.
333      */
334     protected void openFolder( final Graph2D graph, final Node node ) {
335       Action action = null;
336 
337       final ActionMap amap = view.getCanvasComponent().getActionMap();
338       if (amap != null) {
339         action = amap.get(Graph2DViewActions.OPEN_FOLDERS);
340       }
341       if (action == null) {
342         action = new Graph2DViewActions.OpenFoldersAction();
343       }
344 
345       view.getGraph2D().unselectAll();
346       view.getGraph2D().setSelected(node, true);
347       action.actionPerformed(new ActionEvent(view, ActionEvent.ACTION_PERFORMED, ""));
348     }
349   }
350 }
351