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