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