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.graphexplorer;
29  
30  import demo.view.DemoBase;
31  import y.option.AbstractItemEditor;
32  import y.option.ConstraintManager;
33  import y.option.DefaultCompoundEditor;
34  import y.option.DefaultEditorFactory;
35  import y.option.Editor;
36  import y.option.ItemEditor;
37  import y.option.OptionHandler;
38  import y.option.OptionItem;
39  import y.option.OptionSection;
40  import y.option.PropertyChangeReporter;
41  
42  import java.awt.Dimension;
43  import java.awt.Font;
44  import java.awt.GridBagConstraints;
45  import java.awt.GridBagLayout;
46  import java.awt.Insets;
47  import java.awt.event.ActionListener;
48  import java.beans.PropertyChangeEvent;
49  import java.beans.PropertyChangeListener;
50  import java.util.Map;
51  import javax.swing.BorderFactory;
52  import javax.swing.Box;
53  import javax.swing.BoxLayout;
54  import javax.swing.JButton;
55  import javax.swing.JComboBox;
56  import javax.swing.JComponent;
57  import javax.swing.JLabel;
58  import javax.swing.JPanel;
59  import javax.swing.UIManager;
60  
61  /**
62   * Provides settings for graph exploration.
63   */
64  class GraphExplorerOptionHandler extends OptionHandler {
65    static final String ATTRIBUTE_LAYOUT_CALLBACK =
66            "GraphExplorerOptionHandler.layoutCallback";
67  
68    static final byte ID_LAYOUT_ORTHOGONAL = 0;
69    static final byte ID_LAYOUT_ORGANIC = 1;
70    static final byte ID_LAYOUT_HIERARCHIC = 2;
71    static final byte ID_LAYOUT_BALLOON = 3;
72    static final byte ID_LAYOUT_CIRCULAR = 4;
73  
74    static final byte EDGE_TYPE_ALL = 0;
75    static final byte EDGE_TYPE_OUT = 1;
76    static final byte EDGE_TYPE_IN = 2;
77  
78    private static final String EXPLORE_NODES = "Explore Nodes";
79    private static final String EXPLORE_NEIGHBORS = "Neighbors";
80    private static final String EXPLORE_SUCCESSORS = "Successors";
81    private static final String EXPLORE_PREDECESSORS = "Predecessors";
82    private static final String LAYOUT = "Layout";
83    private static final String LAYOUT_ORTHOGONAL = "Orthogonal";
84    private static final String LAYOUT_ORGANIC = "Organic";
85    private static final String LAYOUT_HIERARCHIC = "Hierarchic";
86    private static final String LAYOUT_BALLOON = "Balloon";
87    private static final String LAYOUT_CIRCULAR = "Circular";
88    private static final String MAX_NEW_NODES = "Max New Nodes on Click";
89    private static final String ENABLE_FILTER = "Filter Neighbors";
90    private static final String MAX_NEIGHBOR_DIST = "Max Neighbor Distance";
91  
92    GraphExplorerOptionHandler() {
93      super("Option Table");
94      useSection("General");
95      addEnum(LAYOUT, new String[]{
96              LAYOUT_ORGANIC,
97              LAYOUT_ORTHOGONAL,
98              LAYOUT_HIERARCHIC,
99              LAYOUT_BALLOON,
100             LAYOUT_CIRCULAR
101     }, 1);
102     addEnum(EXPLORE_NODES, new String[]{
103             EXPLORE_NEIGHBORS,
104             EXPLORE_SUCCESSORS,
105             EXPLORE_PREDECESSORS
106     }, 0);
107     addInt(MAX_NEW_NODES, 10, 1, 100);
108     useSection("Filter");
109     addBool(ENABLE_FILTER, true);
110     addInt(MAX_NEIGHBOR_DIST, 4, 1, 100);
111 
112     final ConstraintManager constraints = new ConstraintManager(this);
113     constraints.setEnabledOnValueEquals(ENABLE_FILTER, Boolean.TRUE, MAX_NEIGHBOR_DIST);
114   }
115 
116   /**
117    * Returns the maximum number of new neighbor nodes that are added when
118    * double-clicking an existing node (with hidden neighbors).
119    * @return the maximum number of new nodes for double-clicks.
120    */
121   int getMaxNewNodes() {
122     return getInt(MAX_NEW_NODES);
123   }
124 
125   /**
126    * Returns the maximum graph distance for nodes to be kept when
127    * double-clicking an existing node. More formally, for a return value
128    * of <code>D</code> and a node <code>A</code>, all nodes whose graph distance
129    * to <code>A</code> is greater than <code>D</code> are removed.
130    * @return the maximum graph distance for nodes to be kept or
131    * <code>-1</code> if all neighbors are to be kept.
132    */
133   int getMaxDist() {
134     if (getBool(ENABLE_FILTER)) {
135       return getInt(MAX_NEIGHBOR_DIST);
136     } else {
137       return -1;
138     }
139   }
140 
141   /**
142    * Returns the edge type that corresponds to the type of nodes that are
143    * added when double-clicking an existing node (with hidden neighbors).
144    * @return one of {@link #EDGE_TYPE_ALL}, {@link #EDGE_TYPE_OUT}, and
145    * {@link #EDGE_TYPE_IN}.
146    */
147   byte getExplorationEdgeType() {
148     final String exploration = getString(EXPLORE_NODES);
149     if (EXPLORE_NEIGHBORS.equals(exploration)) {
150       return EDGE_TYPE_ALL;
151     } else if (EXPLORE_SUCCESSORS.equals(exploration)) {
152       return EDGE_TYPE_OUT;
153     } else if (EXPLORE_PREDECESSORS.equals(exploration)) {
154       return EDGE_TYPE_IN;
155     } else {
156       return EDGE_TYPE_ALL;
157     }
158   }
159 
160   /**
161    * Returns a symbolic constant representing the layout algorithm to lay
162    * out the displayed graph.
163    * @return one of {@link #ID_LAYOUT_ORTHOGONAL}, {@link #ID_LAYOUT_ORGANIC},
164    * {@link #ID_LAYOUT_HIERARCHIC}, {@link #ID_LAYOUT_BALLOON}, or
165    * {@link #ID_LAYOUT_CIRCULAR}.
166    */
167   byte getLayoutId() {
168     final String layout = getString(LAYOUT);
169     if (layout.equals(LAYOUT_HIERARCHIC)) {
170       return ID_LAYOUT_HIERARCHIC;
171     } else if (layout.equals(LAYOUT_ORGANIC)) {
172       return ID_LAYOUT_ORGANIC;
173     } else if (layout.equals(LAYOUT_BALLOON)) {
174       return ID_LAYOUT_BALLOON;
175     } else if (layout.equals(LAYOUT_CIRCULAR)) {
176       return ID_LAYOUT_CIRCULAR;
177     } else {
178       return ID_LAYOUT_ORTHOGONAL;
179     }
180   }
181 
182   /**
183    * Creates controls to edit the provided settings.
184    * @return controls to edit the provided settings.
185    */
186   JComponent createEditorComponent() {
187     final SimpleEditorFactory factory = new SimpleEditorFactory();
188     final Editor editor = factory.createEditor(this);
189 
190     final JComponent optionComponent = editor.getComponent();
191     final Dimension size = optionComponent.getPreferredSize();
192     size.width = Math.max(size.width, 400);
193     size.height = Math.max(size.height, 250);
194     optionComponent.setPreferredSize(size);
195     optionComponent.setMaximumSize(size);
196     return optionComponent;
197   }
198 
199   /**
200    * Editor factory for option handlers that displays sections as titled
201    * components.
202    */
203   private static final class SimpleEditorFactory extends DefaultEditorFactory {
204     public Editor createEditor( final OptionHandler handler, final Map attributes ) {
205       final SimpleEditor editor = new SimpleEditor(handler, this);
206       handler.addEditor(editor);
207       return editor;
208     }
209   }
210 
211   /**
212    * Compound editor whose editor component is made up of one title
213    * component per section.
214    */
215   private static final class SimpleEditor extends DefaultCompoundEditor {
216     private final JComponent component;
217 
218     SimpleEditor( final OptionHandler handler, final SimpleEditorFactory factory ) {
219       final Font font;
220       final Font cbf;
221       if ("javax.swing.plaf.metal.MetalLookAndFeel".equals(UIManager.getLookAndFeel().getClass().getName())) {
222         font = new JLabel().getFont().deriveFont(Font.PLAIN);
223         cbf = font.deriveFont((float) (font.getSize() - 2));
224       } else {
225         font = new JLabel().getFont();
226         cbf = font;
227       }
228 
229       final int sc = handler.sectionCount();
230       final JLabel[][] labels = new JLabel[sc][];
231       final JComponent[][] controls = new JComponent[sc][];
232 
233       int maxW = 0;
234 
235       JButton lb = null;
236       
237       final String handlerName = handler.getName();
238       for (int i = 0; i < sc; ++i) {
239         final OptionSection section = handler.section(i);
240         section.setAttribute(OptionSection.ATTRIBUTE_CONTEXT, handlerName);
241 
242         final int ic = section.itemCount();
243         labels[i] = new JLabel[ic];
244         controls[i] = new JComponent[ic];
245         for (int j = 0; j < ic; ++j) {
246           final OptionItem item = section.item(j);
247 
248           final Object attr = item.getAttribute(ATTRIBUTE_LAYOUT_CALLBACK);
249           if (attr instanceof ActionListener) {
250             lb = createLayoutButton();
251             lb.addActionListener((ActionListener) attr);
252           }
253 
254           labels[i][j] = createLabel(item.getName(), font);
255           final Dimension s1 = labels[i][j].getPreferredSize();
256           if (maxW < s1.width) {
257             maxW = s1.width;
258           }
259 
260           final ItemEditor editor = factory.createEditor(item);
261           editor.setAutoCommit(true);
262           addEditor(editor);
263           if (editor instanceof PropertyChangeReporter) {
264             final JLabel label = labels[i][j];
265             ((PropertyChangeReporter) editor).addPropertyChangeListener(
266                     AbstractItemEditor.PROPERTY_ENABLED,
267                     new PropertyChangeListener() {
268                       public void propertyChange( final PropertyChangeEvent e ) {
269                         label.setEnabled(((Boolean) e.getNewValue()).booleanValue());
270                       }
271                     });
272           }
273 
274           controls[i][j] = editor.getComponent();
275           if (controls[i][j] instanceof JComboBox) {
276             controls[i][j].setFont(cbf);
277           }
278         }
279       }
280 
281       final int margin = 5;
282 
283       final Box main = new Box(BoxLayout.Y_AXIS);
284       for (int i = 0; i < sc; ++i) {
285         final OptionSection section = handler.section(i);
286 
287         final JPanel sectionPane = new JPanel(new GridBagLayout());
288         sectionPane.setBorder(BorderFactory.createTitledBorder(section.getName()));
289 
290         final GridBagConstraints gbc = new GridBagConstraints();
291         gbc.anchor = GridBagConstraints.WEST;
292 
293         final int ic = section.itemCount();
294         for (int j = 0; j < ic; ++j) {
295           final int topInset = j == 0 ? 3 : margin;
296           final int bottomInset = j == ic - 1 ? margin : 0;
297 
298           gbc.insets = new Insets(topInset, margin, bottomInset, 0);
299           gbc.fill = GridBagConstraints.NONE;
300           gbc.gridx = 0;
301           gbc.gridy = j;
302           gbc.weightx = 0.0;
303           gbc.weighty = 0.0;
304           final JLabel jl = labels[i][j];
305           jl.setPreferredSize(new Dimension(maxW, jl.getPreferredSize().height));
306           sectionPane.add(jl, gbc);
307 
308           gbc.insets = new Insets(topInset, margin, bottomInset, 0);
309           gbc.fill = GridBagConstraints.HORIZONTAL;
310           gbc.gridx = 1;
311           gbc.weightx = 1.0;
312           sectionPane.add(controls[i][j], gbc);
313 
314           if (lb != null) {
315             gbc.insets = new Insets(topInset, 1, bottomInset, margin);
316             gbc.fill = GridBagConstraints.BOTH;
317             gbc.gridx = 2;
318             gbc.weightx = 0.0;
319             if (section.item(j).getAttribute(ATTRIBUTE_LAYOUT_CALLBACK) instanceof ActionListener) {
320               sectionPane.add(lb, gbc);
321             } else {
322               sectionPane.add(createSpacer(lb), gbc);
323             }
324           }
325         }
326 
327         main.add(sectionPane);
328         if (i != sc - 1) {
329           main.add(Box.createRigidArea(new Dimension(margin, margin)));
330         }
331       }
332 
333       component = new JPanel(new GridBagLayout());
334       final GridBagConstraints gbc = new GridBagConstraints();
335       gbc.anchor = GridBagConstraints.NORTHWEST;
336       gbc.fill = GridBagConstraints.HORIZONTAL;
337       gbc.gridx = 0;
338       gbc.gridy = 0;
339       gbc.weightx = 1.0;
340       gbc.weighty = 0.0;
341       component.add(main, gbc);
342 
343       gbc.fill = GridBagConstraints.BOTH;
344       gbc.gridy = 1;
345       gbc.weighty = 1.0;
346       component.add(new JPanel(), gbc);
347     }
348 
349     public JComponent getComponent() {
350       return component;
351     }
352 
353     private static JLabel createLabel( final String name, final Font font ) {
354       final JLabel jl = new JLabel(name);
355       jl.setFont(font);
356       return jl;
357     }
358 
359     private static JComponent createSpacer( final JComponent jc ) {
360       final Dimension s = jc.getPreferredSize();
361       final JPanel jp = new JPanel();
362       jp.setPreferredSize(s);
363       jp.setSize(s);
364       jp.setMinimumSize(s);
365       jp.setMaximumSize(s);
366       return jp;
367     }
368 
369     private static JButton createLayoutButton() {
370       if (DemoBase.SHARED_LAYOUT_ICON == null) {
371         return new JButton("Layout");
372       } else {
373         final JButton jb = new JButton();
374         jb.setIcon(DemoBase.SHARED_LAYOUT_ICON);
375         jb.setMargin(new Insets(0, 0, 0, 0));
376         jb.setToolTipText("Layout");
377         return jb;
378       }
379     }
380   }
381 }
382