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.realizer;
15  
16  import demo.view.DemoBase;
17  import y.base.DataProvider;
18  import y.base.Node;
19  import y.util.DataProviderAdapter;
20  import y.view.CellEditorMode;
21  import y.view.EditMode;
22  import y.view.GenericNodeRealizer;
23  import y.view.Graph2DView;
24  import y.view.Graph2DViewActions;
25  import y.view.NodeCellEditor;
26  import y.view.NodeCellRenderer;
27  import y.view.NodeCellRendererPainter;
28  import y.view.NodeLabel;
29  import y.view.NodeRealizer;
30  import y.view.ShapeNodeRealizer;
31  import y.view.SimpleUserDataHandler;
32  import y.view.SmartNodeLabelModel;
33  
34  import javax.swing.AbstractCellEditor;
35  import javax.swing.ActionMap;
36  import javax.swing.BorderFactory;
37  import javax.swing.JComboBox;
38  import javax.swing.JComponent;
39  import javax.swing.JLabel;
40  import javax.swing.JPanel;
41  import javax.swing.JTable;
42  import javax.swing.JTextField;
43  import javax.swing.JToolBar;
44  import javax.swing.table.DefaultTableModel;
45  import java.awt.BorderLayout;
46  import java.awt.EventQueue;
47  import java.awt.event.ActionEvent;
48  import java.awt.event.ActionListener;
49  import java.awt.event.KeyAdapter;
50  import java.awt.event.KeyEvent;
51  import java.beans.PropertyChangeEvent;
52  import java.beans.PropertyChangeListener;
53  import java.util.Locale;
54  import java.util.Map;
55  
56  
57  /**
58   * This demo shows how yFiles can deal with Swing-like cell rendering and cell editing mechanisms.
59   * It shows both how to customize {@link GenericNodeRealizer} to display JComponents as nodes, and
60   * how to configure {@link y.view.EditMode} to work with {@link CellEditorMode} so that a double click
61   * on a node initiates inline cell editing.
62   * @see <a href="http://docs.yworks.com/yfiles/doc/developers-guide/jcomponent_support.html">Section Swing User Interface Components as Node Realizers</a> in the yFiles for Java Developer's Guide
63   * @see <a href="http://docs.yworks.com/yfiles/doc/developers-guide/mvc_controller.html#node_cell_editors">Section User Interaction</a> in the yFiles for Java Developer's Guide
64   * @see <a href="http://docs.yworks.com/yfiles/doc/developers-guide/realizers.html#cls_GenericNodeRealizer">Section Bringing Graph Elements to Life: The Realizer Concept</a> in the yFiles for Java Developer's Guide
65   */
66  public class SwingRendererDemo extends DemoBase
67  {
68    private GenericNodeRealizer gnr;
69    private ShapeNodeRealizer snr = new ShapeNodeRealizer();
70  
71    /**
72     * Instantiates this demo.
73     */
74    public SwingRendererDemo()
75    {
76      // create a simple NodeCellRenderer and NodeCellEditor instance that work together nicely
77      NodeCellRenderer simpleNodeCellRenderer = new SimpleNodeCellRenderer();
78  
79      // Get the factory to register custom styles/configurations.
80      GenericNodeRealizer.Factory factory = GenericNodeRealizer.getFactory();
81  
82      // prepare a GenericNodeRealizer to use the NodeCellRenderer for rendering
83      Map map = factory.createDefaultConfigurationMap();
84      map.put(GenericNodeRealizer.Painter.class, new NodeCellRendererPainter(simpleNodeCellRenderer, NodeCellRendererPainter.USER_DATA_MAP));
85      map.put(GenericNodeRealizer.UserDataHandler.class, new SimpleUserDataHandler(SimpleUserDataHandler.REFERENCE_ON_FAILURE));
86      // register the configuration using the given name
87      factory.addConfiguration("JTextField", map);
88  
89      // create another configuration based on the first one, this time use a more complex renderer
90      map.put(GenericNodeRealizer.Painter.class, new NodeCellRendererPainter(new ComplexNodeCellRenderer(), NodeCellRendererPainter.USER_DATA_MAP));
91      // register it
92      factory.addConfiguration("JTable", map);
93  
94      // instantiate a default node realizer
95      gnr = new GenericNodeRealizer();
96      gnr.setSize(200.0, 50.0);
97      gnr.setConfiguration("JTextField");
98      gnr.setUserData("Hello Renderer World!");
99      NodeLabel label = gnr.getLabel();
100     SmartNodeLabelModel model = new SmartNodeLabelModel();
101     label.setLabelModel(model);
102     label.setModelParameter(model.getDefaultParameter());
103 
104     // create a sample instance
105     view.getGraph2D().setDefaultNodeRealizer(gnr);
106     view.getGraph2D().createNode(150.0, 50.0, 200.0, 50.0, "");
107 
108     // and another one of the other kind
109     gnr.setConfiguration("JTable");
110     view.getGraph2D().createNode(150.0, 200.0, 150.0, 150.0, "");
111 
112   }
113 
114   /**
115    * Adds the view modes to the view.
116    * This implementation adds a new EditMode (with showNodeTips enabled) and
117    * a new {@link y.view.AutoDragViewMode}.
118    */
119   protected void registerViewModes() {
120     final NodeCellEditor simpleNodeCellEditor = new SimpleNodeCellEditor();
121     // instantiate an appropriate editor for the complex renderer
122     final NodeCellEditor complexNodeCellEditor = new SwingRendererDemo.ComplexNodeCellEditor();
123 
124     // create a data provider that dynamically switches between the different NodeCellEditor instances
125     DataProvider nodeCellEditorProvider = new DataProviderAdapter() {
126       public Object get(Object dataHolder) {
127         NodeRealizer realizer = view.getGraph2D().getRealizer((Node) dataHolder);
128         if (realizer instanceof GenericNodeRealizer){
129           if ("JTextField".equals(((GenericNodeRealizer) realizer).getConfiguration())){
130             return simpleNodeCellEditor;
131           } else {
132             return complexNodeCellEditor;
133           }
134         } else {
135           return null;
136         }
137       }
138     };
139 
140     EditMode editMode = new EditMode();
141     // create the CellEditorMode and give it the multiplexing NodeCellEditor provider,
142     // as well as tell it where to find the user data
143     CellEditorMode cellEditorMode = new CellEditorMode(nodeCellEditorProvider, NodeCellRendererPainter.USER_DATA_MAP);
144     // register it with the EditMode
145     editMode.setEditNodeMode(cellEditorMode);
146     // Disable generic node label assignment in the view since it would spoil the
147     // effect of the node cell editors/renderers.
148     editMode.assignNodeLabel(false);
149 
150     view.addViewMode( editMode );
151   }
152 
153   protected void registerViewActions() {
154     super.registerViewActions();
155 
156     // disable label editing shortcut
157     ActionMap amap = view.getCanvasComponent().getActionMap();
158     if (amap != null) {
159       amap.remove(Graph2DViewActions.EDIT_LABEL);
160     }
161   }
162 
163   /** Creates a toolbar that allows to switch the default node realizer type. */
164   protected JToolBar createToolBar()
165   {
166     JToolBar toolBar = super.createToolBar();
167     toolBar.addSeparator();
168     toolBar.add(new JLabel("Node Style:"));
169     toolBar.addSeparator(TOOLBAR_SMALL_SEPARATOR);
170 
171     final JComboBox cb = new JComboBox(new Object[]{"JTextField", "JTable", "Rectangle"});
172     cb.setMaximumSize(cb.getPreferredSize());
173     cb.setSelectedIndex(1);
174     toolBar.add(cb);
175     cb.addActionListener(new ActionListener()
176     {
177       public void actionPerformed(ActionEvent ae)
178       {
179         if ( !"Rectangle".equals( cb.getSelectedItem().toString() ) ) {
180           gnr.setConfiguration( cb.getSelectedItem().toString() );
181           view.getGraph2D().setDefaultNodeRealizer( gnr );
182         } else {
183           view.getGraph2D().setDefaultNodeRealizer( snr );
184         }
185       }
186     });
187 
188     return toolBar;
189   }
190 
191   /**
192    * A simple {@link NodeCellEditor} implementation that is based on an even simpler
193    * {@link NodeCellRenderer} implementation.
194    */
195   public static class SimpleNodeCellEditor extends AbstractCellEditor implements NodeCellEditor
196   {
197     // the delegate
198     private final SimpleNodeCellRenderer ncr;
199 
200     public SimpleNodeCellEditor()
201     {
202       // initialize
203       this.ncr = new SimpleNodeCellRenderer();
204       // add editor hooks
205       this.ncr.tf.addActionListener(new ActionListener()
206       {
207         public void actionPerformed(ActionEvent ae)
208         {
209           SimpleNodeCellEditor.this.fireEditingStopped();
210         }
211       });
212       this.ncr.tf.addKeyListener(new KeyAdapter()
213       {
214         public void keyPressed(KeyEvent ke)
215         {
216           if (ke.getKeyCode() ==  KeyEvent.VK_ESCAPE)
217           {
218             SimpleNodeCellEditor.this.fireEditingCanceled();
219           }
220         }
221       });
222     }
223 
224     public JComponent getNodeCellEditorComponent(Graph2DView view, NodeRealizer context, Object value, boolean isSelected)
225     {
226       // get the renderer as editor
227       return ncr.getNodeCellRendererComponent(view, context, value, isSelected);
228     }
229 
230     public Object getCellEditorValue()
231     {
232       // get the value this editor represents
233       return ncr.getValue();
234     }
235   }
236 
237   /**
238    * A simple NodeCellRenderer that uses a JTextField and a JLabel in a JPanel to display the nodes contents.
239    */
240   public static final class SimpleNodeCellRenderer extends JPanel implements NodeCellRenderer
241   {
242     /**
243      * the text field that holds/displays the actual data
244      */
245     JTextField tf;
246 
247     public SimpleNodeCellRenderer()
248     {
249       super(new BorderLayout());
250       // create a nice GUI
251       setBorder(BorderFactory.createCompoundBorder(BorderFactory.createEmptyBorder(3,3,3,3), BorderFactory.createEtchedBorder()));
252       add(new JLabel("Content"), BorderLayout.NORTH);
253       add(tf = new JTextField(), BorderLayout.CENTER);
254     }
255 
256     public JComponent getNodeCellRendererComponent(Graph2DView view, NodeRealizer nodeRealizer, Object userObject, boolean selected)
257     {
258       // initialize the text field
259       tf.setText(String.valueOf(userObject));
260       return this;
261     }
262 
263     public Object getValue()
264     {
265       // return the value of the text field
266       return tf.getText();
267     }
268   }
269 
270   /**
271    * A more sophisticated NodeCellEditor that uses a sophisticated NodeCellRenderer to
272    * display/edit a node.
273    * This implementation displays an editable JTable where the value column is editable.
274    */
275   public static class ComplexNodeCellEditor extends AbstractCellEditor implements NodeCellEditor
276   {
277     // the delegate
278     private final ComplexNodeCellRenderer ncr;
279 
280     public ComplexNodeCellEditor()
281     {
282       this.ncr = new ComplexNodeCellRenderer();
283       // add editor hooks
284       this.ncr.table.addPropertyChangeListener("tableCellEditor", new PropertyChangeListener() {
285         public void propertyChange(PropertyChangeEvent evt) {
286           if (evt.getNewValue() == null && evt.getOldValue() != null){
287             ComplexNodeCellEditor.this.fireEditingStopped();
288           }
289         }
290       });
291     }
292 
293     /**
294      * Delegates the request to the table.
295      */
296     public boolean stopCellEditing() {
297       if (ncr.table.isEditing() && ncr.table.getCellEditor() != null){
298         return ncr.table.getCellEditor().stopCellEditing();
299       } else {
300         fireEditingStopped();
301         return true;
302       }
303     }
304 
305     /**
306      * Delegates the request to the table.
307      */
308     public void cancelCellEditing() {
309       if (ncr.table.isEditing() && ncr.table.getCellEditor() != null){
310         ncr.table.getCellEditor().cancelCellEditing();
311       } else {
312         fireEditingCanceled();
313       }
314     }
315 
316     public JComponent getNodeCellEditorComponent(Graph2DView view, NodeRealizer context, Object value, boolean isSelected)
317     {
318       ncr.getNodeCellRendererComponent(view, context, value, isSelected);
319       return ncr;
320     }
321 
322     public Object getCellEditorValue()
323     {
324       return ncr.getValue();
325     }
326   }
327 
328   /**
329    * A nice renderer that can be used to display data in a JTable
330    */
331   public static final class ComplexNodeCellRenderer extends JPanel implements NodeCellRenderer
332   {
333     // the table
334     JTable table;
335     // the data model
336     DefaultTableModel tableModel;
337 
338     public ComplexNodeCellRenderer()
339     {
340       super(new BorderLayout());
341 
342       // create a sample table model with the first column being editable
343       tableModel = new DefaultTableModel(new Object[][]{{"Keys", "Values"}}, new Object[]{"Key", "Value"}) {
344         public boolean isCellEditable(int row, int column) {
345           return column == 1;
346         }
347       };
348 
349       setBorder(BorderFactory.createCompoundBorder(BorderFactory.createEmptyBorder(3,3,3,3), BorderFactory.createEtchedBorder()));
350       add(table = new JTable(tableModel), BorderLayout.CENTER);
351       add(table.getTableHeader(), BorderLayout.NORTH);
352     }
353 
354     public JComponent getNodeCellRendererComponent(Graph2DView view, NodeRealizer nodeRealizer, Object userObject, boolean selected)
355     {
356       // initialize the value in the model
357       tableModel.setValueAt(userObject, 0, 1);
358       return this;
359     }
360 
361     public Object getValue()
362     {
363       // construct the value from the model
364       return tableModel.getValueAt(0, 1);
365     }
366   }
367 
368 
369   /**
370    * Launches this demo.
371    *
372    * @param args ignored command line arguments
373    */
374   public static void main(String[] args) {
375     EventQueue.invokeLater(new Runnable() {
376       public void run() {
377         Locale.setDefault(Locale.ENGLISH);
378         initLnF();
379         (new SwingRendererDemo()).start("Swing Renderer Demo");
380       }
381     });
382   }
383 }
384