1   /****************************************************************************
2    **
3    ** This file is part of yFiles-2.6. 
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-2008 by yWorks GmbH, Vor dem Kreuzberg 28, 
11   ** 72070 Tuebingen, Germany. All rights reserved.
12   **
13   ***************************************************************************/
14  
15  package demo.view.advanced;
16  
17  import y.util.D;
18  import y.view.NodeRealizer;
19  import y.view.ShapeNodeRealizer;
20  import y.view.Graph2DView;
21  import y.view.Graph2D;
22  import y.view.Drawable;
23  
24  import javax.swing.Icon;
25  import javax.swing.JLabel;
26  import javax.swing.JList;
27  import javax.swing.JScrollPane;
28  import javax.swing.ListCellRenderer;
29  import javax.swing.DefaultListCellRenderer;
30  import javax.swing.ListSelectionModel;
31  import javax.swing.event.ListSelectionListener;
32  import javax.swing.event.ListSelectionEvent;
33  import java.awt.BorderLayout;
34  import java.awt.Color;
35  import java.awt.Component;
36  import java.awt.Graphics;
37  import java.awt.Graphics2D;
38  import java.awt.Point;
39  import java.awt.RenderingHints;
40  import java.awt.datatransfer.DataFlavor;
41  import java.awt.datatransfer.StringSelection;
42  import java.awt.datatransfer.Transferable;
43  import java.awt.datatransfer.UnsupportedFlavorException;
44  import java.awt.dnd.DnDConstants;
45  import java.awt.dnd.DragGestureEvent;
46  import java.awt.dnd.DragGestureListener;
47  import java.awt.dnd.DragSource;
48  import java.awt.dnd.DropTarget;
49  import java.awt.dnd.DropTargetDragEvent;
50  import java.awt.dnd.DropTargetDropEvent;
51  import java.awt.dnd.DropTargetListener;
52  import java.awt.dnd.DropTargetEvent;
53  import java.io.IOException;
54  import java.util.HashMap;
55  import java.util.Iterator;
56  import java.util.Map;
57  import java.util.Collection;
58  
59  import demo.view.DemoBase;
60  
61  /**
62   * Demo that shows how to display drag different {@link NodeRealizer} instances in and from
63   * a list and how to drop
64   * them on a {@link Graph2DView} using a {@link Drawable} that indicates the drop operation.
65   * This demo makes use of the {@link java.awt.dnd} package.
66   */
67  public class DragAndDropDemo extends DemoBase
68  {
69    /** Creates a new instance of DragAndDropDemo */
70    public DragAndDropDemo()
71    {
72      // create two maps, mapping NodeRealizer instances to strings...
73      final Map stringToNodeRealizerMap = createStringToRealizerMap();
74      final Map inverse = new HashMap();
75      for(Iterator it = stringToNodeRealizerMap.entrySet().iterator();it.hasNext();){
76        Map.Entry entry = (Map.Entry) it.next();
77        inverse.put(entry.getValue(), entry.getKey());
78      }
79  
80  
81      // create the customized DnD support instance
82      final DragAndDropSupport dndSupport = new DragAndDropSupport(inverse.keySet(), view) {
83        protected String getTextValue(NodeRealizer selected) {
84          return (String) inverse.get(selected);
85        }
86  
87        protected NodeRealizer createNodeRealizerFromTextValue(String s) {
88          return (NodeRealizer) stringToNodeRealizerMap.get(s);
89        }
90      };
91  
92      // get the List UI
93      final JList realizerList = dndSupport.getList();
94      realizerList.setBackground(Color.lightGray);
95  
96      //add the realizer list to the panel
97      contentPane.add(new JScrollPane(realizerList),BorderLayout.WEST);
98    }
99  
100   /**
101    * Creates a map that has the names of realizers as keys and node realizer
102    * instances as associated values. The realizer instances have different shapes
103    * and colors.
104    */
105   protected Map createStringToRealizerMap()
106   {
107     Map result = new HashMap();
108 
109     Map shapeTypeToStringMap = ShapeNodeRealizer.shapeTypeToStringMap();
110     float hueIncrease = 1.0f/shapeTypeToStringMap.size();
111     float hue = 0.0f;
112     for(Iterator iter = shapeTypeToStringMap.keySet().iterator(); iter.hasNext(); hue += hueIncrease)
113     {
114       Byte shapeType = (Byte)iter.next();
115       ShapeNodeRealizer r = new ShapeNodeRealizer(shapeType.byteValue());
116       r.setWidth(100);
117       r.setLabelText( (String)shapeTypeToStringMap.get(shapeType) );
118       r.setFillColor(new Color(Color.HSBtoRGB(hue, 1.0f, 1.0f)));
119       result.put(r.getLabelText(), r);
120     }
121     return result;
122   }
123 
124   /**
125    * Support class that be used to create a JList that contains NodeRealizers that can be dragged
126    * and dropped onto the given Graph2DView object.
127    */
128   public abstract static class DragAndDropSupport {
129     private final JList realizerList;
130 
131     protected DragAndDropSupport(Collection nodeRealizerList, final Graph2DView view) {
132       this( nodeRealizerList.toArray(), view );
133     }
134 
135     protected DragAndDropSupport(Object[] nodeRealizers, final Graph2DView view) {
136       // create a nice GUI for displaying NodeRealizers
137       realizerList = new JList(nodeRealizers );
138       realizerList.setCellRenderer(new NodeRealizerCellRenderer());
139 
140       // set the currently selected NodeRealizer as default nodeRealizer
141       realizerList.addListSelectionListener(new ListSelectionListener() {
142         public void valueChanged(ListSelectionEvent e) {
143           if (realizerList.getSelectedValue() instanceof NodeRealizer) {
144             nodeRealizerSelected(view);
145           }
146         }
147       });
148 
149       realizerList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
150       realizerList.setSelectedIndex(0);
151 
152       // define the realizer list to be the drag source
153       // use the string-valued name of the realizer as transferable
154       final DragSource dragSource = new DragSource();
155       dragSource.createDefaultDragGestureRecognizer(realizerList, DnDConstants.ACTION_MOVE,
156           new DragGestureListener() {
157             public void dragGestureRecognized(DragGestureEvent event) {
158               Object selected = realizerList.getSelectedValue();
159               final String textValue = getTextValue((NodeRealizer) selected);
160               if (textValue != null) {
161                 StringSelection text = new StringSelection(textValue);
162                 // as the name suggests, starts the dragging
163                 dragSource.startDrag(event, DragSource.DefaultMoveDrop, text, null);
164               }
165             }
166           });
167 
168       // define the graph view to be the drop target. Create a node with the
169       // dropped shape to the graph.
170       new DropTarget(view.getCanvasComponent(), new DropTargetListener() {
171 
172         // called by the dnd framework once a drag enters the view
173         public void dragEnter(DropTargetDragEvent event) {
174           if (checkStringFlavor(event)) {
175             event.acceptDrag(DnDConstants.ACTION_COPY_OR_MOVE);
176           } else {
177             event.rejectDrag();
178           }
179         }
180 
181         // inspects the event and tries to create a NodeRealizer from it
182         private boolean checkStringFlavor(DropTargetDragEvent event) {
183           // we accept only Strings
184           DataFlavor[] flavors = event.getCurrentDataFlavors();
185           for (int i = 0; i < flavors.length; i++) {
186             if (flavors[i] == DataFlavor.stringFlavor) {
187               return true;
188             }
189           }
190           return false;
191         }
192 
193         // called by the dnd framework once a drag ends with a drop operation
194         public void drop(DropTargetDropEvent event) {
195           try {
196             // we accept only Strings
197             boolean foundStringFlavor = false;
198             DataFlavor[] flavors = event.getCurrentDataFlavors();
199             for (int i = 0; i < flavors.length; i++) {
200               if (flavors[i] == DataFlavor.stringFlavor) {
201                 foundStringFlavor = true;
202                 break;
203               }
204             }
205             if (foundStringFlavor) {
206               event.acceptDrop(event.getDropAction());
207             } else {
208               event.rejectDrop();
209               return;
210             }
211           } catch (RuntimeException rex) {
212             event.rejectDrop();
213             D.show(rex);
214           }
215           try {
216             Transferable transferable = event.getTransferable();
217             String s = (String) transferable.getTransferData(DataFlavor.stringFlavor);
218             Point p = event.getLocation();
219             NodeRealizer r = createNodeRealizerFromTextValue(s);
220             if (r != null) {
221               // found a suitable realizer using the given name
222               final double worldCoordX = view.toWorldCoordX(p.x);
223               final double worldCoordY = view.toWorldCoordY(p.y);
224               event.dropComplete(dropRealizer(view, r, worldCoordX, worldCoordY));
225             } else {
226               // no suitable realizer
227               event.dropComplete(false);
228             }
229           } catch (IOException ioe) {
230             // should not happen
231             event.dropComplete(false);
232             D.show(ioe);
233           } catch (UnsupportedFlavorException ufe) {
234             // should never happen
235             event.dropComplete(false);
236             D.show(ufe);
237           } catch (RuntimeException x) {
238             event.dropComplete(false);
239             throw x;
240           }
241         }
242 
243         // called by the dnd framework when a drag leaves the view
244         public void dragExit(DropTargetEvent dte) {
245         }
246 
247         // called by the dnd framework when the drag action changes
248         public void dropActionChanged(DropTargetDragEvent dtde) {
249           dragEnter(dtde);
250         }
251 
252         // called by the dnd framework when the drag hovers over the view
253         public void dragOver(DropTargetDragEvent dtde) {
254           dragEnter(dtde);
255         }
256       });
257     }
258 
259     /**
260      * Callback method that returns a String representation for use during the DnD operation
261      * of a NodeRealizer instance.
262      * @param selected the selected NodeRealizer
263      * @return a String representation or <code>null</code>
264      */
265     protected abstract String getTextValue(NodeRealizer selected);
266 
267     /**
268      * Callback method that converts the given String representation to a valid NodeRealizer
269      * instance
270      * @param s the String transferable
271      * @return a NodeRealizer instance or <code>null</code>
272      */
273     protected abstract NodeRealizer createNodeRealizerFromTextValue(String s);
274 
275     /**
276      * The code that performs the actual drop operation.
277      * Note that this method should return quickly since it may block the OS's drag-and-drop system.
278      * @return <code>true</code> iff the drop was successful.
279      */
280     protected boolean dropRealizer( Graph2DView view, NodeRealizer r, double worldCoordX, double worldCoordY ) {
281       final Graph2D graph = view.getGraph2D();
282       r = r.createCopy();
283 
284       if ( view.getGridMode() ) {
285         double gridSize = view.getGridResolution();
286         double x = Math.floor( worldCoordX / gridSize + 0.5 ) * gridSize;
287         double y = Math.floor( worldCoordY / gridSize + 0.5 ) * gridSize;
288 
289         r.setCenter( x, y );
290       } else {
291         r.setCenter( worldCoordX, worldCoordY );
292       }
293       graph.createNode( r );
294       view.updateView();
295       return true;
296     }
297 
298     /**
299      * Callback method that is triggered whenever the selection changes in the JList.
300      * This method sets the given NodeRealizer as the view's graph default node realizer.
301      */
302     protected void nodeRealizerSelected(Graph2DView view) {
303       view.getGraph2D().setDefaultNodeRealizer((NodeRealizer) realizerList.getSelectedValue());
304     }
305 
306     /**
307      * Return the JList that has been configured by this support class.
308      */
309     public JList getList() {
310       return realizerList;
311     }
312   }
313 
314   /**
315    * ListCellRenderer implementation that handles NodeRealizer instances.
316    * Used internally by {@link DragAndDropSupport} and publicly available for others.
317    */
318   public static final class NodeRealizerCellRenderer implements ListCellRenderer {
319 
320     private DefaultListCellRenderer renderer = new DefaultListCellRenderer();
321     private NodeRealizerIcon icon = new NodeRealizerIcon();
322 
323     public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
324       JLabel label = (JLabel) renderer.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
325       icon.setRealizer((NodeRealizer) value);
326       label.setText("");
327       label.setIcon(icon);
328       return label;
329     }
330 
331     /**
332      * Icon implementation that renders a NodeRealizer
333      */
334     public static final class NodeRealizerIcon implements Icon {
335       private static final int inset = 10;
336       private NodeRealizer realizer;
337 
338       public void setRealizer(NodeRealizer realizer) {
339         this.realizer = realizer;
340       }
341 
342       public int getIconWidth() {
343         return (int) (realizer.getWidth() + inset);
344       }
345 
346       public int getIconHeight() {
347         return (int) (realizer.getHeight() + inset);
348       }
349 
350       public void paintIcon(Component c, Graphics g, int x, int y) {
351         realizer.setLocation(x + inset * 0.5d, y + inset * 0.5d);
352         g = g.create();
353         try {
354           final Graphics2D gfx = (Graphics2D) g;
355           gfx.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
356           realizer.paint(gfx);
357         } finally {
358           g.dispose();
359         }
360       }
361     }
362   }
363 
364   /**
365    * Instantiates and starts this demo.
366    */
367   public static void main(String[] args)
368   {
369     initLnF();
370     DragAndDropDemo demo = new DragAndDropDemo();
371     demo.start("Drag and Drop Demo");
372   }
373 }
374