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.uml;
29  
30  import y.base.Edge;
31  import y.base.Node;
32  import y.base.NodeCursor;
33  import y.base.NodeList;
34  import y.layout.router.polyline.EdgeRouter;
35  import y.util.DataProviderAdapter;
36  import y.view.Drawable;
37  import y.view.Graph2D;
38  import y.view.Graph2DLayoutExecutor;
39  import y.view.MoveSelectionMode;
40  import y.view.NodeRealizer;
41  
42  import java.awt.Graphics2D;
43  import java.awt.Rectangle;
44  import java.awt.geom.Rectangle2D;
45  
46  /**
47   * A {@link y.view.ViewMode} that creates an edge with its own target node if a source node exists.
48   * The target node can be dragged around to a desired position. When its center lies within another node at the time,
49   * the edge is connected to that node and the target node is deleted.
50   */
51  class UmlCreateEdgeMode extends MoveSelectionMode {
52    private final EdgeRouter edgeRouter;
53    private Node sourceNode;
54    private Node targetNode;
55  
56    private Node draggedNode;
57    private Drawable targetNodeIndicator;
58  
59    UmlCreateEdgeMode(final EdgeRouter edgeRouter) {
60      this.edgeRouter = edgeRouter;
61      // create a new drawable for the target node
62      targetNodeIndicator = new Drawable() {
63        public void paint(Graphics2D graphics) {
64          if (targetNode != null) {
65            drawTargetNodeIndicator(graphics, getGraph2D().getRealizer(targetNode));
66          }
67        }
68  
69        public Rectangle getBounds() {
70          if (targetNode != null) {
71            return getTargetNodeIndicatorBounds(getGraph2D().getRealizer(targetNode)).getBounds();
72          } else {
73            return new Rectangle(0, 0, -1, -1);
74          }
75        }
76      };
77    }
78  
79    /**
80     * Overwritten to add an edge with a new target node to the current graph when a drag starts. Afterwards, this target
81     * node will be dragged around.
82     */
83    public void mousePressedLeft(double x, double y) {
84      if (sourceNode != null) {
85        final Graph2D graph = getGraph2D();
86        graph.firePreEvent();
87  
88        // create the target node and the new edge
89        // the target node will be dragged around until a mouse release
90        draggedNode = graph.createNode();
91        UmlRealizerFactory.setNodeOpacity(graph.getRealizer(draggedNode), UmlRealizerFactory.TRANSPARENT);
92        graph.setCenter(draggedNode, x, y);
93        graph.createEdge(sourceNode, draggedNode);
94  
95        super.mousePressedLeft(x, y);
96      }
97    }
98  
99    /**
100    * Overwritten to just move the target node of the new edge.
101    *
102    * @return a {@link NodeList} with the target node as the only element.
103    */
104   protected NodeList getNodesToBeMoved() {
105     final NodeList nodesToBeMoved = new NodeList();
106     if (draggedNode != null) {
107       nodesToBeMoved.add(draggedNode);
108     }
109     return nodesToBeMoved;
110   }
111 
112   /**
113    * Overwritten to keep track of the nodes over which the new target node moves. Nodes that contain the center of the
114    * target node are possible new target nodes for the edge and will be marked.
115    */
116   public void mouseDraggedLeft(final double x, final double y) {
117     if (sourceNode != null) {
118       // if the center of the dragged node lies within other nodes, the node in front gets selected as target node
119       updateTargetNode();
120 
121       super.mouseDraggedLeft(x, y);
122     }
123   }
124 
125   /**
126    * Updates the target node when the currently dragged node moves over other nodes. Nodes that contain the center of
127    * the target node are possible new target nodes for the edge and will be marked.
128    */
129   private void updateTargetNode() {
130     final Graph2D graph = getGraph2D();
131 
132     // find a node that contains the center of the dragged node
133     // if there are several nodes the node in front is taken
134     Node newTargetNode = null;
135     for (NodeCursor nc = graph.nodes(); nc.ok(); nc.next()) {
136       final Node node = nc.node();
137       if (node != draggedNode) {
138         final NodeRealizer realizer = graph.getRealizer(node);
139         final Rectangle2D.Double bounds = realizer.getBoundingBox();
140         if (bounds.contains(graph.getCenterX(draggedNode), graph.getCenterY(draggedNode))
141             && (newTargetNode == null || newTargetNode.index() < node.index())) {
142           newTargetNode = node;
143         }
144       }
145     }
146 
147     // if the target node changed, the target node indicator gets updated
148     if (newTargetNode != targetNode) {
149       targetNode = newTargetNode;
150       updateTargetNodeIndicator();
151     }
152   }
153 
154   /**
155    * Updates the drawable that displays indication marks for the current target node.
156    */
157   private void updateTargetNodeIndicator() {
158     view.removeDrawable(targetNodeIndicator);
159     if (targetNode != null) {
160       view.addDrawable(targetNodeIndicator);
161     }
162   }
163 
164   /**
165    * Overwritten to properly end edge creation. If the node was dragged onto another node, this other node becomes the
166    * new target node for the edge and the former target node gets removed.
167    */
168   public void mouseReleasedLeft(double x, double y) {
169     if (sourceNode != null) {
170       final Graph2D graph = getGraph2D();
171       final Edge draggedEdge = draggedNode.lastInEdge();
172       if (targetNode != null) {
173         // if the dragged node is dropped on another node, the other node becomes the new target and the dragged node
174         // is removed
175         graph.changeEdge(draggedEdge, draggedEdge.source(), targetNode);
176         graph.removeNode(draggedNode);
177       } else {
178         UmlRealizerFactory.setNodeOpacity(graph.getRealizer(draggedNode), UmlRealizerFactory.OPAQUE);
179       }
180 
181       // clean up target node indicator
182       view.removeDrawable(targetNodeIndicator);
183 
184       // end edge creation step for undo/redo
185       graph.firePostEvent();
186 
187       edgeCreated(draggedEdge);
188 
189       draggedNode = null;
190       targetNode = null;
191       sourceNode = null;
192 
193       super.mouseReleasedLeft(x, y);
194     }
195   }
196 
197   /**
198    * Overwritten to disable event handling from the right mouse button.
199    */
200   public void mouseDraggedRight(double x, double y) {
201   }
202 
203   /**
204    * Overwritten to disable event handling from the right mouse button.
205    */
206   public void mouseReleasedRight(double x, double y) {
207   }
208 
209   /**
210    * Overwritten to clean up this {@link y.view.ViewMode} in case edge creation was aborted.
211    */
212   public void cancelEditing() throws UnsupportedOperationException {
213     // resets all nodes and drawables
214     if (draggedNode != null) {
215       getGraph2D().removeNode(draggedNode);
216     }
217     draggedNode = null;
218     targetNode = null;
219     sourceNode = null;
220     view.removeDrawable(targetNodeIndicator);
221 
222     super.cancelEditing();
223   }
224 
225   /**
226    * Sets the source node for the newly created edge.
227    */
228   public void setSourceNode(Node sourceNode) {
229     this.sourceNode = sourceNode;
230   }
231 
232   /**
233    * Callback to be able to react when edge creation is finished.
234    */
235   protected void edgeCreated(final Edge edge) {
236     final Graph2D graph = getGraph2D();
237     final DataProviderAdapter selectedEdges = new DataProviderAdapter() {
238       public boolean getBool(Object dataHolder) {
239         return dataHolder == edge;
240       }
241     };
242     edgeRouter.setSphereOfAction(EdgeRouter.ROUTE_SELECTED_EDGES);
243     graph.addDataProvider(EdgeRouter.SELECTED_EDGES, selectedEdges);
244     try {
245       final Graph2DLayoutExecutor executor = new Graph2DLayoutExecutor();
246       executor.getLayoutMorpher().setKeepZoomFactor(true);
247       executor.doLayout(view, edgeRouter);
248     } finally {
249       graph.removeDataProvider(EdgeRouter.SELECTED_EDGES);
250     }
251   }
252 }
253