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.flowchart;
15  
16  import demo.view.flowchart.layout.FlowchartLayouter;
17  import demo.view.flowchart.painters.FlowchartLayoutConfigurator;
18  import y.base.Edge;
19  import y.base.EdgeCursor;
20  import y.base.EdgeMap;
21  import y.base.Node;
22  import y.base.NodeCursor;
23  import y.layout.LayoutOrientation;
24  import y.layout.PortConstraintKeys;
25  import y.module.LayoutModule;
26  import y.option.OptionGroup;
27  import y.option.OptionHandler;
28  import y.util.Maps;
29  import y.view.Graph2D;
30  import y.view.Graph2DLayoutExecutor;
31  import y.view.tabular.TableLayoutConfigurator;
32  
33  /**
34   * Provides a graphical settings component for {@link FlowchartLayouter}.
35   */
36  public class FlowchartLayoutModule extends LayoutModule {
37    private static final String FLOWCHART_LAYOUT = "FLOWCHART_LAYOUT";
38    private static final String GROUP_NEGATIVE_BRANCH = "GROUP_NEGATIVE_BRANCH";
39    private static final String GROUP_POSITIVE_BRANCH = "GROUP_POSITIVE_BRANCH";
40  
41    private static final String ALLOW_FLATWISE_EDGES = "ALLOW_FLATWISE_EDGES";
42    private static final String LANE_INSETS = "LANE_INSETS";
43    private static final String MINIMUM_EDGE_DISTANCE = "MINIMUM_EDGE_DISTANCE";
44    private static final String MINIMUM_NODE_DISTANCE = "MINIMUM_NODE_DISTANCE";
45    private static final String MINIMUM_POOL_DISTANCE = "MINIMUM_POOL_DISTANCE";
46  
47    private static final String IN_EDGE_GROUPING = "IN_EDGE_GROUPING";
48    private static final String GROUPING_ALL = "GROUPING_ALL";
49    private static final String GROUPING_OPTIMIZED = "GROUPING_OPTIMIZED";
50    private static final String GROUPING_NONE = "GROUPING_NONE";
51  
52    private static final String ORIENTATION = "ORIENTATION";
53    private static final String LEFT_TO_RIGHT = "LEFT_TO_RIGHT";
54    private static final String TOP_TO_BOTTOM = "TOP_TO_BOTTOM";
55  
56    private static final String NEGATIVE_BRANCH_LABEL = "NEGATIVE_BRANCH_LABEL";
57    private static final String NEGATIVE_BRANCH_DEFAULT = "No";
58    private static final String NEGATIVE_BRANCH_DIRECTION = "NEGATIVE_BRANCH_DIRECTION";
59  
60    private static final String POSITIVE_BRANCH_LABEL = "POSITIVE_BRANCH_LABEL";
61    private static final String POSITIVE_BRANCH_DEFAULT = "Yes";
62    private static final String POSITIVE_BRANCH_DIRECTION = "POSITIVE_BRANCH_DIRECTION";
63  
64    private static final String DIRECTION_WITH_THE_FLOW = "DIRECTION_WITH_THE_FLOW";
65    private static final String DIRECTION_FLATWISE = "DIRECTION_FLATWISE";
66    private static final String DIRECTION_LEFT_IN_FLOW = "DIRECTION_LEFT_IN_FLOW";
67    private static final String DIRECTION_RIGHT_IN_FLOW = "DIRECTION_RIGHT_IN_FLOW";
68    private static final String DIRECTION_UNDEFINED = "DIRECTION_UNDEFINED";
69  
70    private static final String[] DIRECTION_ENUM = {
71        DIRECTION_WITH_THE_FLOW, DIRECTION_FLATWISE,
72        DIRECTION_LEFT_IN_FLOW, DIRECTION_RIGHT_IN_FLOW,
73        DIRECTION_UNDEFINED
74    };
75  
76    private static final String[] GROUPING_ENUM = {
77        GROUPING_ALL, GROUPING_OPTIMIZED, GROUPING_NONE
78    };
79  
80    private static final String[] ORIENTATION_ENUM = {
81        TOP_TO_BOTTOM, LEFT_TO_RIGHT
82    };
83  
84    /**
85     * Creates a new FlowchartLayoutModule.
86     */
87    public FlowchartLayoutModule() {
88      super(FLOWCHART_LAYOUT, "yFiles Layout Team", "Flowchart Layout");
89  
90      // PortIntersectionCalculator is enabled since there are many non-rectangular symbols in the flowchart palette
91      setPortIntersectionCalculatorEnabled(true);
92    }
93  
94  
95    /**
96     * Creates the option handler for this module.
97     */
98    protected OptionHandler createOptionHandler() {
99      final OptionHandler op = new OptionHandler(getModuleName());
100 
101     op.addEnum(ORIENTATION, ORIENTATION_ENUM, 0);
102 
103     op.addString(POSITIVE_BRANCH_LABEL, POSITIVE_BRANCH_DEFAULT);
104     op.addEnum(POSITIVE_BRANCH_DIRECTION, DIRECTION_ENUM, 0);
105     op.addString(NEGATIVE_BRANCH_LABEL, NEGATIVE_BRANCH_DEFAULT);
106     op.addEnum(NEGATIVE_BRANCH_DIRECTION, DIRECTION_ENUM, 1);
107 
108     op.addEnum(IN_EDGE_GROUPING, GROUPING_ENUM, 1);
109     op.addBool(ALLOW_FLATWISE_EDGES, false);
110 
111     op.addDouble(MINIMUM_NODE_DISTANCE, 30.0);
112     op.addDouble(MINIMUM_EDGE_DISTANCE, 15.0);
113     op.addDouble(MINIMUM_POOL_DISTANCE, 30.0);
114     op.addDouble(LANE_INSETS, 10.0);
115 
116 
117     OptionGroup og = new OptionGroup();
118     og.setAttribute(OptionGroup.ATTRIBUTE_TITLE, GROUP_POSITIVE_BRANCH);
119     og.addItem(op.getItem(POSITIVE_BRANCH_LABEL));
120     og.addItem(op.getItem(POSITIVE_BRANCH_DIRECTION));
121 
122     og = new OptionGroup();
123     og.setAttribute(OptionGroup.ATTRIBUTE_TITLE, GROUP_NEGATIVE_BRANCH);
124     og.addItem(op.getItem(NEGATIVE_BRANCH_LABEL));
125     og.addItem(op.getItem(NEGATIVE_BRANCH_DIRECTION));
126 
127     // Ensure that the initial settings of this option handler and class FlowchartLayoutConfigurator are the same
128     adoptSettings(op, new FlowchartLayoutConfigurator());
129     // Ensure that the initial settings of this option handler and class FlowchartLayouter are the same
130     adoptSettings(op, new FlowchartLayouter());
131 
132     return op;
133   }
134 
135   /**
136    * Configures and runs the flowchart layout algorithm.
137    */
138   protected void mainrun() {
139     final OptionHandler op = getOptionHandler();
140 
141     final FlowchartLayouter layouter = new FlowchartLayouter();
142     final FlowchartLayoutConfigurator layoutConfigurator = new FlowchartLayoutConfigurator();
143 
144     configureLayouter(op, layouter);
145     configureLayoutConfigurator(op, layoutConfigurator);
146     configureLayoutExecutor(layouter);
147 
148     final Graph2D graph = getGraph2D();
149 
150     try {
151       configureInEdgeGrouping();
152       layoutConfigurator.prepareAll(graph);
153 
154       launchLayouter(layouter, true);
155 
156     } finally {
157       layoutConfigurator.restoreAll(graph);
158       graph.removeDataProvider(PortConstraintKeys.TARGET_GROUPID_KEY);
159     }
160   }
161 
162   /**
163    * Configures the given layouter according to the settings of the given option handler.
164    */
165   static void configureLayouter(OptionHandler op, FlowchartLayouter layouter) {
166     layouter.setAllowFlatwiseEdges(op.getBool(ALLOW_FLATWISE_EDGES));
167     layouter.setLayoutOrientation(convertEnumToLayoutOrientation(op.getEnum(ORIENTATION)));
168 
169     layouter.setLaneInsets(op.getDouble(LANE_INSETS));
170     layouter.setMinimumEdgeLength(op.getDouble(MINIMUM_EDGE_DISTANCE));
171     layouter.setMinimumEdgeDistance(op.getDouble(MINIMUM_EDGE_DISTANCE));
172     layouter.setMinimumNodeDistance(op.getDouble(MINIMUM_NODE_DISTANCE));
173     layouter.setMinimumPoolDistance(op.getDouble(MINIMUM_POOL_DISTANCE));
174   }
175 
176   /**
177    * Adopts the settings of the given layouter to the given option handler.
178    */
179   static void adoptSettings(OptionHandler oh, FlowchartLayouter layouter) {
180     oh.set(ORIENTATION, isHorizontalOrientation(layouter) ? ORIENTATION_ENUM[1] : ORIENTATION_ENUM[0]);
181     oh.set(ALLOW_FLATWISE_EDGES, Boolean.valueOf(layouter.isAllowFlatwiseEdges()));
182 
183     oh.set(LANE_INSETS, new Double(layouter.getLaneInsets()));
184     oh.set(MINIMUM_EDGE_DISTANCE, new Double(layouter.getMinimumEdgeDistance()));
185     oh.set(MINIMUM_NODE_DISTANCE, new Double(layouter.getMinimumNodeDistance()));
186     oh.set(MINIMUM_POOL_DISTANCE, new Double(layouter.getMinimumPoolDistance()));
187   }
188 
189   /**
190    * Configures the given {@link FlowchartLayoutConfigurator} according to the settings of the given option handler.
191    */
192   static void configureLayoutConfigurator(OptionHandler op, FlowchartLayoutConfigurator configurator) {
193     configurator.setPositiveBranchLabel(op.getString(POSITIVE_BRANCH_LABEL));
194     configurator.setNegativeBranchLabel(op.getString(NEGATIVE_BRANCH_LABEL));
195     configurator.setPreferredNegativeBranchDirection(
196         convertEnumToBranchDirection(op.getEnum(NEGATIVE_BRANCH_DIRECTION)));
197     configurator.setPreferredPositiveBranchDirection(
198         convertEnumToBranchDirection(op.getEnum(POSITIVE_BRANCH_DIRECTION)));
199   }
200 
201   /**
202    * Adopts the settings of the given {@link FlowchartLayoutConfigurator} to the given option handler.
203    */
204   static void adoptSettings(OptionHandler oh, FlowchartLayoutConfigurator configurator) {
205     oh.set(POSITIVE_BRANCH_LABEL, configurator.getPositiveBranchLabel());
206     oh.set(NEGATIVE_BRANCH_LABEL, configurator.getNegativeBranchLabel());
207     oh.set(NEGATIVE_BRANCH_DIRECTION,
208         DIRECTION_ENUM[convertBranchDirectionToEnum(configurator.getPreferredNegativeBranchDirection())]);
209     oh.set(POSITIVE_BRANCH_DIRECTION,
210         DIRECTION_ENUM[convertBranchDirectionToEnum(configurator.getPreferredPositiveBranchDirection())]);
211   }
212 
213   /**
214    * Configures the layout executor of this module.
215    */
216   private void configureLayoutExecutor(FlowchartLayouter layouter) {
217     final Graph2DLayoutExecutor layoutExecutor = getLayoutExecutor();
218     layoutExecutor.getLayoutMorpher().setPreferredDuration(500L);
219 
220     layoutExecutor.setConfiguringTableNodeRealizers(true);
221     final TableLayoutConfigurator tableLayoutConfigurator = getLayoutExecutor().getTableLayoutConfigurator();
222     tableLayoutConfigurator.setHorizontalLayoutConfiguration(isHorizontalOrientation(layouter));
223     tableLayoutConfigurator.setTableToTableDistance(layouter.getMinimumPoolDistance());
224     tableLayoutConfigurator.setFromSketchModeEnabled(true);
225   }
226 
227   private void configureInEdgeGrouping() {
228     final OptionHandler oh = getOptionHandler();
229 
230     if (oh.get(IN_EDGE_GROUPING).equals(GROUPING_NONE)) {
231       return;
232     }
233 
234     final int inDegreeThreshold;
235     final int degreeThreshold;
236     if (oh.get(IN_EDGE_GROUPING).equals(GROUPING_ALL)) {
237       inDegreeThreshold = 0;
238       degreeThreshold = 0;
239     } else {
240       inDegreeThreshold = 3;
241       degreeThreshold = 4;
242     }
243 
244     final Graph2D graph = getGraph2D();
245     final EdgeMap map = Maps.createHashedEdgeMap();
246     graph.addDataProvider(PortConstraintKeys.TARGET_GROUPID_KEY, map);
247 
248     for (NodeCursor nc = graph.nodes(); nc.ok(); nc.next()) {
249       final Node node = nc.node();
250       if (node.inDegree() < 2 || node.inDegree() < inDegreeThreshold || node.degree() < degreeThreshold) {
251         continue;
252       }
253 
254       for (EdgeCursor edgeCursor = node.inEdges(); edgeCursor.ok(); edgeCursor.next()) {
255         final Edge edge = edgeCursor.edge();
256         map.set(edge, node);
257       }
258     }
259   }
260 
261   /**
262    * Returns the branch direction that corresponds to the given index of the branch direction settings array.
263    */
264   private static int convertEnumToBranchDirection(int enumIndex) {
265     switch (enumIndex) {
266       case 0:
267         return FlowchartLayouter.DIRECTION_WITH_THE_FLOW;
268       case 1:
269         return FlowchartLayouter.DIRECTION_FLATWISE;
270       case 2:
271         return FlowchartLayouter.DIRECTION_LEFT_IN_FLOW;
272       case 3:
273         return FlowchartLayouter.DIRECTION_RIGHT_IN_FLOW;
274       case 4:
275       default:
276         return FlowchartLayouter.DIRECTION_UNDEFINED;
277     }
278   }
279 
280   /**
281    * Returns the index of the branch direction settings array that corresponds to the given branch direction.
282    */
283   private static int convertBranchDirectionToEnum(int direction) {
284     switch (direction) {
285       case FlowchartLayouter.DIRECTION_WITH_THE_FLOW:
286         return 0;
287       case FlowchartLayouter.DIRECTION_FLATWISE:
288         return 1;
289       case FlowchartLayouter.DIRECTION_LEFT_IN_FLOW:
290         return 2;
291       case FlowchartLayouter.DIRECTION_RIGHT_IN_FLOW:
292         return 3;
293       case FlowchartLayouter.DIRECTION_UNDEFINED:
294       default:
295         return 4;
296     }
297   }
298 
299   /**
300    * Returns the layout orientation that corresponds to the given index of the orientation settings array.
301    */
302   private static byte convertEnumToLayoutOrientation(int enumIndex) {
303     return enumIndex == 0 ? LayoutOrientation.TOP_TO_BOTTOM : LayoutOrientation.LEFT_TO_RIGHT;
304   }
305 
306   private static boolean isHorizontalOrientation(FlowchartLayouter layouter) {
307     final int layoutOrientation = (int) layouter.getLayoutOrientation();
308     return layoutOrientation == (int) LayoutOrientation.LEFT_TO_RIGHT
309         || layoutOrientation == (int) LayoutOrientation.RIGHT_TO_LEFT;
310   }
311 
312 }
313