1   /****************************************************************************
2    **
3    ** This file is part of the yFiles extension package ySVG-2.2.
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) 2002-2009 by yWorks GmbH, Vor dem Kreuzberg 28,
11   ** 72070 Tuebingen, Germany. All rights reserved.
12   **
13   ***************************************************************************/
14  package demo.yext.svg;
15  
16  import yext.svg.view.SVGNodeRealizer;
17  
18  import y.base.Edge;
19  import y.base.Node;
20  import y.base.NodeCursor;
21  import y.geom.YRectangle;
22  import y.layout.EdgeLabelModel;
23  import y.util.D;
24  import y.view.EdgeLabel;
25  import y.view.EdgeRealizer;
26  import y.view.Graph2D;
27  import y.view.Graph2DView;
28  import y.view.NodeRealizer;
29  
30  import java.awt.BorderLayout;
31  import java.awt.Component;
32  import java.awt.Dimension;
33  import java.awt.EventQueue;
34  import java.awt.Graphics;
35  import java.awt.Graphics2D;
36  import java.awt.Point;
37  import java.awt.RenderingHints;
38  import java.awt.datatransfer.DataFlavor;
39  import java.awt.datatransfer.StringSelection;
40  import java.awt.datatransfer.Transferable;
41  import java.awt.datatransfer.UnsupportedFlavorException;
42  import java.awt.dnd.DnDConstants;
43  import java.awt.dnd.DragGestureEvent;
44  import java.awt.dnd.DragGestureListener;
45  import java.awt.dnd.DragSource;
46  import java.awt.dnd.DropTarget;
47  import java.awt.dnd.DropTargetDragEvent;
48  import java.awt.dnd.DropTargetDropEvent;
49  import java.awt.dnd.DropTargetEvent;
50  import java.awt.dnd.DropTargetListener;
51  import java.io.IOException;
52  import java.net.URL;
53  import java.util.Collection;
54  import java.util.HashMap;
55  import java.util.Map;
56  import javax.swing.DefaultListCellRenderer;
57  import javax.swing.Icon;
58  import javax.swing.JLabel;
59  import javax.swing.JList;
60  import javax.swing.JOptionPane;
61  import javax.swing.JScrollPane;
62  import javax.swing.JSplitPane;
63  import javax.swing.ListCellRenderer;
64  import javax.swing.ListSelectionModel;
65  import javax.swing.event.ListSelectionEvent;
66  import javax.swing.event.ListSelectionListener;
67  
68  
69  /**
70   * Demonstrates yFiles' {@link yext.svg.view.SVGNodeRealizer}.
71   *
72   */
73  public class SVGNodeRealizerDemo extends SVGExportDemo {
74    /**
75     * Create a new instance of <code>SVGNodeRealizerDemo</code>.
76     * A sample graph displaying a couple of SVG nodes is created and
77     * layouted.
78     */
79    public SVGNodeRealizerDemo() {
80      // create a graph with a couple of nodes that display SVG icons
81      createGraph();
82  
83      // notify the view of the changes made to its graph
84      view.fitContent();
85      view.updateView();
86  
87      // some gui stuff to setup a template list for SVGNodeRealizers
88      createContent();
89    }
90  
91    /**
92     * Creates the GUI components.
93     */
94    private void createContent() {
95      final String[] tmp = {
96              "camera.svg",
97              "cdrom.svg",
98              "computer.svg",
99              "floppy.svg",
100             "harddisk.svg",
101             "ipod.svg",
102             "network.svg",
103             "printer.svg",
104             "scanner.svg",
105             "zipdisk.svg"
106     };
107     double maxW = 90.0;
108     final Map stringToRealizerMap = new HashMap();
109     final Map inverse = new HashMap();
110     for (int i = 0; i < tmp.length; ++i) {
111       final SVGNodeRealizer snr = createSvgRealizer(tmp[i]);
112       if (maxW < snr.getWidth()) {
113         maxW = snr.getWidth();
114       }
115       final URL url = snr.getSVGURL();
116       if (url != null) {
117         final String s = url.toString();
118         stringToRealizerMap.put(s, snr);
119         inverse.put(snr, s);
120       }
121     }
122 
123     if (inverse.isEmpty()) {
124       JOptionPane.showMessageDialog(
125               this,
126               "Could not find SVG resources.",
127               "Warning",
128               JOptionPane.WARNING_MESSAGE);
129       System.err.println("Could not find SVG resources.");
130       return;
131     }
132 
133     final DragAndDropSupport templates =
134             new DragAndDropSupport(inverse.keySet(), view) {
135               protected String getTextValue( final NodeRealizer selected ) {
136                 return (String) inverse.get(selected);
137               }
138 
139               protected NodeRealizer createNodeRealizerFromTextValue( final String s ) {
140                 return (NodeRealizer) stringToRealizerMap.get(s);
141               }
142             };
143 
144     int prefW = (int) (1.5 * maxW);
145     prefW += 10 - (prefW % 10);
146     final JScrollPane templatesScroll = new JScrollPane(templates.getList());
147     templatesScroll.setPreferredSize(
148             new Dimension(prefW, view.getPreferredSize().height));
149     remove(view);
150     add(new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, templatesScroll, view),
151             BorderLayout.CENTER);
152   }
153 
154   /**
155    * Creates the initial graph structure.
156    */
157   private void createGraph() {
158     final Graph2D graph = view.getGraph2D();
159     final Node computer = graph.createNode(createSvgRealizer("computer.svg"));
160     final Node cdrom = graph.createNode(createSvgRealizer("cdrom.svg"));
161     final Node hd = graph.createNode(createSvgRealizer("harddisk.svg"));
162     final Node printer = graph.createNode(createSvgRealizer("printer.svg"));
163     final Node scanner = graph.createNode(createSvgRealizer("scanner.svg"));
164     final Node ipod = graph.createNode(createSvgRealizer("ipod.svg"));
165 
166     // ... graphs without edges are boring
167     final Edge hasCdrom = graph.createEdge(computer, cdrom);
168     final Edge hasHd = graph.createEdge(computer, hd);
169     final Edge hasPrinter = graph.createEdge(computer, printer);
170     final Edge hasScanner = graph.createEdge(computer, scanner);
171     graph.createEdge(computer, ipod);
172 
173     // layout the graph (in a very simple manner) for improved viewing pleasure
174     double maxW = 0;
175     double maxH = 0;
176 
177     NodeRealizer nr;
178     for (NodeCursor nc = graph.nodes(); nc.ok(); nc.next()) {
179       nr = graph.getRealizer(nc.node());
180       if (maxW < nr.getWidth()) {
181         maxW = nr.getWidth();
182       }
183       if (maxH < nr.getHeight()) {
184         maxH = nr.getHeight();
185       }
186     }
187 
188     nr = graph.getRealizer(computer);
189     nr.setCenter(0.0, 0.0);
190 
191     nr = graph.getRealizer(cdrom);
192     nr.setCenter(maxW, 3 * maxH);
193     nr = graph.getRealizer(hd);
194     nr.setCenter(-maxW, 3 * maxH);
195 
196     nr = graph.getRealizer(printer);
197     nr.setCenter(3 * maxW, maxH);
198     nr = graph.getRealizer(scanner);
199     nr.setCenter(3 * maxW, -maxH);
200 
201     nr = graph.getRealizer(ipod);
202     nr.setCenter(-3 * maxW, 0.0);
203 
204     EdgeRealizer er;
205     er = graph.getRealizer(hasCdrom);
206     er.appendBend(0.0, 2 * maxH);
207     er.appendBend(maxW, 2 * maxH);
208 
209     er = graph.getRealizer(hasHd);
210     er.appendBend(0.0, 2 * maxH);
211     er.appendBend(-maxW, 2 * maxH);
212     er.setLabelText("Storage Devices");
213     double[] size;
214     size = getLabelSize(er);
215     setLabelPosition(er, -size[0] * 0.5, 2 * maxH + 5);
216 
217     er = graph.getRealizer(hasPrinter);
218     er.appendBend(2 * maxW, 0.0);
219     er.appendBend(2 * maxW, maxH);
220 
221     er = graph.getRealizer(hasScanner);
222     er.appendBend(2 * maxW, 0.0);
223     er.appendBend(2 * maxW, -maxH);
224     er.setLabelText("Periphery");
225     size = getLabelSize(er);
226     setLabelPosition(er, 2 * maxW - size[0] - 5, -size[1] - 5);
227   }
228 
229   /**
230    * Determines the size of the default edge label associated with the
231    * specified edge realizer. Returns an array of length 2, with the first
232    * item being label width and the second item label height.
233    */
234   private static double[] getLabelSize( final EdgeRealizer er ) {
235     final EdgeLabel label = er.getLabel();
236     final YRectangle bounds = label.getBox();
237     final double w = bounds.getWidth();
238     final double h = bounds.getHeight();
239     return new double[]{w, h};
240   }
241 
242   /**
243    * Specifies the position (i.e. coordinates of the upper left corner) for
244    * the defaul edge label of the specified edge realizer.
245    */
246   private static void setLabelPosition(
247           final EdgeRealizer er, final double x, final double y
248   ) {
249     final Edge edge = er.getEdge();
250     final Graph2D graph = (Graph2D) edge.getGraph();
251     final EdgeLabel label = er.getLabel();
252 
253     final YRectangle oldBounds = label.getBox();
254     final YRectangle newBounds =
255             new YRectangle(x, y, oldBounds.getWidth(), oldBounds.getHeight());
256 
257     label.setModel(EdgeLabel.FREE);
258     final EdgeLabelModel model = label.getLabelModel();
259     final Object param =
260             model.createModelParameter(newBounds, er,
261                     graph.getRealizer(edge.source()),
262                     graph.getRealizer(edge.target()));
263     label.setModelParameter(param);
264   }
265 
266   /**
267    * Creates a new <code>SVGNodeRealizer</code> displaying the icon at
268    * <code>"resource/svg/" + icon</code>.
269    */
270   private static SVGNodeRealizer createSvgRealizer( final String icon ) {
271     URL url = null;
272     try {
273       url = SVGNodeRealizerDemo.class.getResource("resource/svg/" + icon);
274     } catch (Exception ex) {
275       System.err.println("Can't find SVG resource resource/svg/" + icon);
276       ex.printStackTrace(System.err);
277       url = null;
278     }
279 
280     if (url != null) {
281       return new SVGNodeRealizer(url);
282     } else {
283       final SVGNodeRealizer nr = new SVGNodeRealizer();
284       nr.setLabelText(icon);
285       return nr;
286     }
287   }
288 
289 
290   /**
291    * Launches this demo.
292    * @param args ignored.
293    */
294   public static void main( final String[] args ) {
295     EventQueue.invokeLater(new Runnable() {
296       public void run() {
297         initLnF();
298         (new SVGNodeRealizerDemo()).start();
299       }
300     });
301   }
302 
303 
304   /*
305    * #####################################################################
306    * nested types
307    * #####################################################################
308    */
309 
310   /**
311    * Support class that be used to create a JList that contains NodeRealizers
312    * that can be dragged and dropped onto the given Graph2DView object.
313    */
314   public abstract static class DragAndDropSupport {
315     private final JList realizerList;
316 
317     public DragAndDropSupport( Collection nodeRealizerList, final Graph2DView view ) {
318       this(nodeRealizerList.toArray(), view);
319     }
320 
321     public DragAndDropSupport( Object[] nodeRealizers, final Graph2DView view ) {
322       // create a nice GUI for displaying NodeRealizers
323       realizerList = new JList(nodeRealizers);
324       realizerList.setCellRenderer(new NodeRealizerCellRenderer());
325 
326       // set the currently selected NodeRealizer as default nodeRealizer
327       realizerList.addListSelectionListener(new ListSelectionListener() {
328         public void valueChanged( ListSelectionEvent e ) {
329           if (realizerList.getSelectedValue() instanceof NodeRealizer) {
330             nodeRealizerSelected(view);
331           }
332         }
333       });
334 
335       realizerList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
336       realizerList.setSelectedIndex(0);
337 
338       // define the realizer list to be the drag source
339       // use the string-valued name of the realizer as transferable
340       final DragSource dragSource = new DragSource();
341       dragSource.createDefaultDragGestureRecognizer(realizerList, DnDConstants.ACTION_MOVE,
342               new DragGestureListener() {
343                 public void dragGestureRecognized( DragGestureEvent event ) {
344                   Object selected = realizerList.getSelectedValue();
345                   final String textValue = getTextValue((NodeRealizer) selected);
346                   if (textValue != null) {
347                     StringSelection text = new StringSelection(textValue);
348                     // as the name suggests, starts the dragging
349                     dragSource.startDrag(event, DragSource.DefaultMoveDrop, text, null);
350                   }
351                 }
352               });
353 
354       // define the graph view to be the drop target. Create a node with the
355       // dropped shape to the graph.
356       DropTarget dropTarget = new DropTarget(view.getCanvasComponent(),
357               new DropTargetListener() {
358 
359                 // called by the dnd framework once a drag enters the view
360                 public void dragEnter( DropTargetDragEvent event ) {
361                   if (checkStringFlavor(event)) {
362                     event.acceptDrag(DnDConstants.ACTION_COPY_OR_MOVE);
363                   } else {
364                     event.rejectDrag();
365                   }
366                 }
367 
368                 // inspects the event and tries to create a NodeRealizer from it
369                 private boolean checkStringFlavor( DropTargetDragEvent event ) {
370                   // we accept only Strings
371                   DataFlavor[] flavors = event.getCurrentDataFlavors();
372                   for (int i = 0; i < flavors.length; i++) {
373                     if (flavors[i] == DataFlavor.stringFlavor) {
374                       return true;
375                     }
376                   }
377                   return false;
378                 }
379 
380                 // called by the dnd framework once a drag ends with a drop operation
381                 public void drop( DropTargetDropEvent event ) {
382                   try {
383                     // we accept only Strings
384                     boolean foundStringFlavor = false;
385                     DataFlavor[] flavors = event.getCurrentDataFlavors();
386                     for (int i = 0; i < flavors.length; i++) {
387                       if (flavors[i] == DataFlavor.stringFlavor) {
388                         foundStringFlavor = true;
389                         break;
390                       }
391                     }
392                     if (foundStringFlavor) {
393                       event.acceptDrop(event.getDropAction());
394                     } else {
395                       event.rejectDrop();
396                       return;
397                     }
398                   } catch (RuntimeException rex) {
399                     event.rejectDrop();
400                     D.show(rex);
401                   }
402                   try {
403                     Transferable transferable = event.getTransferable();
404                     String s = (String) transferable.getTransferData(DataFlavor.stringFlavor);
405                     Point p = event.getLocation();
406                     NodeRealizer r = createNodeRealizerFromTextValue(s);
407                     if (r != null) {
408                       // found a suitable realizer using the given name
409                       final double worldCoordX = view.toWorldCoordX(p.x);
410                       final double worldCoordY = view.toWorldCoordY(p.y);
411                       event.dropComplete(dropRealizer(view, r, worldCoordX, worldCoordY));
412                     } else {
413                       // no suitable realizer
414                       event.dropComplete(false);
415                     }
416                   } catch (IOException ioe) {
417                     // should not happen
418                     event.dropComplete(false);
419                     D.show(ioe);
420                   } catch (UnsupportedFlavorException ufe) {
421                     // should never happen
422                     event.dropComplete(false);
423                     D.show(ufe);
424                   } catch (RuntimeException x) {
425                     event.dropComplete(false);
426                     throw x;
427                   }
428                 }
429 
430                 // called by the dnd framework when a drag leaves the view
431                 public void dragExit( DropTargetEvent dte ) {
432                 }
433 
434                 // called by the dnd framework when the drag action changes
435                 public void dropActionChanged( DropTargetDragEvent dtde ) {
436                   dragEnter(dtde);
437                 }
438 
439                 // called by the dnd framework when the drag hovers over the view
440                 public void dragOver( DropTargetDragEvent dtde ) {
441                   dragEnter(dtde);
442                 }
443               });
444     }
445 
446     /**
447      * Callback method that returns a String representation for use during the DnD operation
448      * of a NodeRealizer instance.
449      *
450      * @param selected the selected NodeRealizer
451      * @return a String representation or <code>null</code>
452      */
453     protected abstract String getTextValue( NodeRealizer selected );
454 
455     /**
456      * Callback method that converts the given String representation to a valid NodeRealizer
457      * instance
458      *
459      * @param s the String transferable
460      * @return a NodeRealizer instance or <code>null</code>
461      */
462     protected abstract NodeRealizer createNodeRealizerFromTextValue( String s );
463 
464     /**
465      * The code that performs the actual drop operation.
466      * Note that this method should return quickly since it may block the OS's drag-and-drop system.
467      *
468      * @return <code>true</code> iff the drop was successfull.
469      */
470     protected boolean dropRealizer( Graph2DView view, NodeRealizer r, double worldCoordX, double worldCoordY ) {
471       final Graph2D graph = view.getGraph2D();
472       r = r.createCopy();
473 
474       if (view.getGridMode()) {
475         double gridSize = view.getGridResolution();
476         double x = Math.floor(worldCoordX / gridSize + 0.5) * gridSize;
477         double y = Math.floor(worldCoordY / gridSize + 0.5) * gridSize;
478 
479         r.setCenter(x, y);
480       } else {
481         r.setCenter(worldCoordX, worldCoordY);
482       }
483       graph.createNode(r);
484       view.updateView();
485       return true;
486     }
487 
488     /**
489      * Callback method that is triggered whenever the selection changes in the JList.
490      * This method sets the given NodeRealizer as the view's graph default node realizer.
491      */
492     protected void nodeRealizerSelected( Graph2DView view ) {
493       view.getGraph2D().setDefaultNodeRealizer((NodeRealizer) realizerList.getSelectedValue());
494     }
495 
496     /**
497      * Return the JList that has been configured by this support class.
498      */
499     public JList getList() {
500       return realizerList;
501     }
502   }
503 
504 
505   /**
506    * ListCellRenderer implementation that handles NodeRealizer instances.
507    * Used internally by {@link DragAndDropSupport} and publicly available for others.
508    */
509   public static final class NodeRealizerCellRenderer
510           implements ListCellRenderer {
511 
512     private DefaultListCellRenderer renderer = new DefaultListCellRenderer();
513     private NodeRealizerIcon icon = new NodeRealizerIcon();
514 
515     public Component getListCellRendererComponent( JList list, Object value, int index, boolean isSelected, boolean cellHasFocus ) {
516       JLabel label = (JLabel) renderer.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
517       icon.setRealizer((NodeRealizer) value);
518       label.setText("");
519       label.setIcon(icon);
520       return label;
521     }
522 
523     /**
524      * Icon implementation that renders a NodeRealizer
525      */
526     public static final class NodeRealizerIcon implements Icon {
527       private static final int inset = 10;
528       private NodeRealizer realizer;
529 
530       public void setRealizer( NodeRealizer realizer ) {
531         this.realizer = realizer;
532       }
533 
534       public int getIconWidth() {
535         return (int) (realizer.getWidth() + inset);
536       }
537 
538       public int getIconHeight() {
539         return (int) (realizer.getHeight() + inset);
540       }
541 
542       public void paintIcon( Component c, Graphics g, int x, int y ) {
543         realizer.setLocation(x + inset * 0.5d, y + inset * 0.5d);
544         g = g.create();
545         try {
546           final Graphics2D gfx = (Graphics2D) g;
547           gfx.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
548           realizer.paint(gfx);
549         } finally {
550           g.dispose();
551         }
552       }
553     }
554   }
555 }
556