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