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