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.anim.AnimationFactory;
31  import y.anim.AnimationObject;
32  import y.anim.AnimationPlayer;
33  import y.anim.CompositeAnimationObject;
34  import y.base.Edge;
35  import y.base.GraphEvent;
36  import y.base.GraphListener;
37  import y.base.Node;
38  import y.base.NodeList;
39  import y.layout.router.polyline.EdgeRouter;
40  import y.util.DataProviderAdapter;
41  import y.view.CreateEdgeMode;
42  import y.view.EdgeRealizer;
43  import y.view.EditMode;
44  import y.view.Graph2D;
45  import y.view.Graph2DLayoutExecutor;
46  import y.view.Graph2DTraversal;
47  import y.view.Graph2DView;
48  import y.view.Graph2DViewRepaintManager;
49  import y.view.HitInfo;
50  import y.view.HitInfoFactory;
51  import y.view.HotSpotMode;
52  import y.view.MouseInputMode;
53  import y.view.MoveSelectionMode;
54  import y.view.NodeRealizer;
55  import y.view.ViewMode;
56  import y.view.YLabel;
57  
58  import javax.swing.Timer;
59  import java.awt.Component;
60  import java.awt.event.ActionEvent;
61  import java.awt.event.ActionListener;
62  import java.awt.event.MouseEvent;
63  import java.beans.PropertyChangeEvent;
64  import java.beans.PropertyChangeListener;
65  
66  /**
67   * Custom {@link y.view.EditMode} that considers special requirements of the UML diagram:
68   * <ul>
69   *    <li>Multi selection of node labels should not be allowed.</li>
70   *    <li>Edge creation is only possible using {@link UmlEdgeCreationButtons}.</li>
71   *    <li>Editing elements of a node are only displayed if the mouse is located above it.</li>
72   * </ul>
73   */
74  class UmlEditMode extends EditMode {
75    private static final String ZOOM_PROPERTY = "Zoom";
76  
77    private static final int DELAY = 500;
78  
79    private static final EdgeRealizer ASSOCIATION_REALIZER = UmlRealizerFactory.createAssociationRealizer();
80    private static final EdgeRealizer DEPENDENCY_REALIZER = UmlRealizerFactory.createDependencyRealizer();
81    private static final EdgeRealizer GENERALIZATION_REALIZER = UmlRealizerFactory.createGeneralizationRealizer();
82    private static final EdgeRealizer REALIZATION_REALIZER = UmlRealizerFactory.createRealizationRealizer();
83    private static final EdgeRealizer AGGREGATION_REALIZER = UmlRealizerFactory.createAggregationRealizer();
84    private static final EdgeRealizer COMPOSITION_REALIZER = UmlRealizerFactory.createCompositionRealizer();
85  
86    private final Timer showingTimer;
87    private final Timer hidingTimer;
88    private final AnimationPlayer player;
89    private Graph2DViewRepaintManager repaintManager;
90    private UmlCreateEdgeMode umlCreateEdgeMode;
91  
92    private Node lastNode;
93    private Node currentNode;
94    private UmlEdgeCreationButtons lastButtons;
95    private UmlEdgeCreationButtons currentButtons;
96    private UmlEdgeCreationButtons rollingOutButtons;
97    private UmlEdgeCreationButtons rollingInButtons;
98  
99    private boolean lastPressHitButton;
100 
101   /**
102    * Creates an {@link EditMode} that is adjusted for the {@link UmlDemo}.
103    *
104    * This edit mode gets the view it will be registered to, so it can register listeners to it.
105    * NOTE: To remain consistent, this edit mode must not be registered to another view afterwards.
106    *
107    * @param view the view this edit mode will be registered to.
108    * @param edgeRouter the edge router that is used to reroute the edges after they got changed by a child mode.
109    */
110   public UmlEditMode(final Graph2DView view, EdgeRouter edgeRouter) {
111     // initialize state variables
112     lastNode = null;
113     currentNode = null;
114     lastButtons = null;
115     currentButtons = null;
116     lastPressHitButton = false;
117 
118     player = new AnimationPlayer(false);
119 
120     addListeners(view);
121     addChildModes(edgeRouter);
122 
123     // Add a repaint manager to the current view to handle repainting effectively.
124     repaintManager = new Graph2DViewRepaintManager(view);
125     player.addAnimationListener(repaintManager);
126 
127     // add a timer that fires once when invoked and starts the animations for showing edge creation buttons and node
128     // editing elements
129     showingTimer = new Timer(0, null);
130     showingTimer.setInitialDelay(DELAY);
131     showingTimer.setRepeats(false);
132     showingTimer.addActionListener(new ActionListener() {
133       public void actionPerformed(ActionEvent event) {
134         if (lastButtons == null || lastButtons.getNode() != currentNode) {
135           // edge creation buttons and node editing elements will be animated simultaneously
136           final CompositeAnimationObject animations = AnimationFactory.createConcurrency();
137 
138           // only add roll-in animation if the mouse is above a node
139           if (currentNode != null && paintDetailed(view.getZoom())) {
140             currentButtons = new UmlEdgeCreationButtons(view, currentNode);
141             animations.addAnimation(
142                 AnimationFactory.createEasedAnimation(new EditingElementsAnimation(currentButtons, true), 1, 1));
143           }
144 
145           // execute animations
146           player.animate(animations);
147 
148           // store current buttons so they can be rolled-in later
149           lastButtons = currentButtons;
150         }
151       }
152     });
153 
154     // add a timer that fires once when invoked and starts the animations for hiding edge creation buttons and node
155     // editing elements
156     hidingTimer = new Timer(0, null);
157     hidingTimer.setInitialDelay(DELAY);
158     hidingTimer.setRepeats(false);
159     hidingTimer.addActionListener(new ActionListener() {
160       public void actionPerformed(ActionEvent event) {
161         if (lastButtons == null || lastButtons.getNode() != currentNode) {
162           // edge creation buttons and node editing elements will be animated simultaneously
163           final CompositeAnimationObject animations = AnimationFactory.createConcurrency();
164 
165           // only add roll-out animation if there are old buttons from another node
166           if (lastButtons != null) {
167             animations.addAnimation(
168                 AnimationFactory.createEasedAnimation(new EditingElementsAnimation(lastButtons, false), 1, 1));
169           }
170 
171           // execute animations
172           player.animate(animations);
173 
174           // old buttons are gone
175           lastButtons = null;
176         }
177       }
178     });
179   }
180 
181   /**
182    * Determines whether or not the graph will be painted with details or sloppy at the given zoom-level.
183    */
184   private boolean paintDetailed(double zoom) {
185     return zoom > view.getPaintDetailThreshold();
186   }
187 
188   private void addListeners(final Graph2DView view) {
189     // Add graph listener to the current graph to delete the button drawable on node deletion.
190     view.getGraph2D().addGraphListener(createNodeDeletionListener());
191 
192     // add a property change listener that observes zooming
193     // if zoom is below the view's threshold already existing buttons are rolled in and no new buttons are shown
194     view.getCanvasComponent().addPropertyChangeListener(new PropertyChangeListener() {
195       public void propertyChange(PropertyChangeEvent evt) {
196         if (ZOOM_PROPERTY.equals(evt.getPropertyName())) {
197           final double zoom = ((Double) evt.getNewValue()).doubleValue();
198           if (!paintDetailed(zoom)) {
199             // no buttons when sloppy painting
200             currentNode = null;
201             currentButtons = null;
202             if (lastNode != currentNode) {
203               hidingTimer.setInitialDelay(0);
204               hidingTimer.restart();
205               showingTimer.stop();
206               lastNode = currentNode;
207             }
208           } else {
209             // no sloppy painting, so maybe show edge creation buttons
210             // get last mouse motion event to check if it is necessary to schedule an animation for this mouse position
211             // because zooming does not invoke mouse events and the zoom level might just have risen above paint detail
212             // level
213             MouseEvent lastMouseMotionEvent = getLastMoveEvent();
214             if (lastMouseMotionEvent == null) {
215               lastMouseMotionEvent = getLastDragEvent();
216             } else if (getLastDragEvent() != null && lastMouseMotionEvent.getWhen() < getLastDragEvent().getWhen()) {
217               lastMouseMotionEvent = getLastDragEvent();
218             }
219 
220             if (lastMouseMotionEvent != null) {
221               scheduleButtonAnimations(view.toWorldCoordX(lastMouseMotionEvent.getX()),
222                   view.toWorldCoordY(lastMouseMotionEvent.getY()));
223             }
224           }
225         }
226       }
227     });
228   }
229 
230   /**
231    * Adds child modes to this editor that invoke rerouting of changed edges after they are executed.
232    *
233    * @param edgeRouter the edge router for the rerouting.
234    */
235   private void addChildModes(EdgeRouter edgeRouter) {
236     setMoveSelectionMode(new ReroutingMoveSelectionMode(edgeRouter));
237     final ReroutingCreateEdgeMode createEdgeMode = new ReroutingCreateEdgeMode(edgeRouter);
238     createEdgeMode.setIndicatingTargetNode(true);
239     setCreateEdgeMode(createEdgeMode);
240     setHotSpotMode(new ReroutingHotSpotMode(edgeRouter));
241     setMoveLabelMode(null);
242     umlCreateEdgeMode = new UmlCreateEdgeMode(edgeRouter);
243   }
244 
245   /**
246    * Returns a {@link GraphListener} that removes the button drawable of a just deleted node.
247    *
248    * @return a <code>GraphListener</code> that removes the button drawable of a just deleted node.
249    */
250   private GraphListener createNodeDeletionListener() {
251     return new GraphListener() {
252       public void onGraphEvent(GraphEvent event) {
253         // if there are still edge creation buttons visible at the removed node, remove them
254         if (lastButtons != null) {
255           if ((event.getType()) == GraphEvent.POST_NODE_REMOVAL
256               && event.getData().equals(lastButtons.getNode())) {
257             view.removeDrawable(lastButtons);
258             lastButtons = null;
259             view.updateView();
260           }
261         }
262 
263         // if there are edge creation buttons that roll out at the moment, remove them
264         if (rollingOutButtons != null) {
265           if ((event.getType()) == GraphEvent.POST_NODE_REMOVAL
266               && event.getData().equals(rollingOutButtons.getNode())) {
267             view.removeDrawable(rollingOutButtons);
268             rollingOutButtons = null;
269             view.updateView();
270           }
271         }
272 
273         // if there are edge creation buttons that roll out at the moment, remove them
274         if (rollingInButtons != null) {
275           if ((event.getType()) == GraphEvent.POST_NODE_REMOVAL
276               && event.getData().equals(rollingInButtons.getNode())) {
277             view.removeDrawable(rollingInButtons);
278             rollingInButtons = null;
279             view.updateView();
280           }
281         }
282       }
283     };
284   }
285 
286   /**
287    * Overwritten to additionally set snapping for the {@link UmlCreateEdgeMode}.
288    *
289    * @param snapping Whether to enable snapping.
290    */
291   public void setSnappingEnabled(boolean snapping) {
292     super.setSnappingEnabled(snapping);
293 
294     umlCreateEdgeMode.setSnappingEnabled(snapping);
295     umlCreateEdgeMode.getSnapContext().setUsingOrthogonalMovementConstraints(snapping);
296     umlCreateEdgeMode.getSnapContext().setSnappingSegmentsToSnapLines(snapping);
297   }
298 
299   /**
300    * Prevents multi selection of labels.
301    *
302    * @param graph       the graph the label's associated node or edge resides in.
303    * @param label       the label which has been clicked
304    * @param wasSelected whether the element is already selected
305    * @param x           the x-coordinate where the mouse was clicked
306    * @param y           the y-coordinate where the mouse was clicked
307    * @param modifierSet <code>true</code> if the caller is {@link #mouseShiftReleasedLeft(double, double)},
308    */
309   protected void labelClicked(
310       final Graph2D graph,
311       final YLabel label,
312       final boolean wasSelected,
313       final double x,
314       final double y,
315       final boolean modifierSet
316   ) {
317     // To avoid multi selection the modifier flag is always set to false.
318     super.labelClicked(graph, label, wasSelected, x, y, false);
319   }
320 
321   /**
322    * Overwritten to avoid hiding edge creation buttons on right-click on their node. Now, on this particular node it is
323    * not possible to initiate movement of the whole view port.
324    */
325   public void mousePressedRight(double x, double y) {
326     final HitInfo hit = getHitInfo(x, y);
327     final boolean isOnNodeWithButtons = (lastButtons != null) && (hit.getHitNode() == lastButtons.getNode());
328     final boolean isOnButtons = (lastButtons != null) && (lastButtons.hasButtonAt(x, y));
329     if (!isOnNodeWithButtons && !isOnButtons) {
330       super.mousePressedRight(x, y);
331     }
332   }
333 
334   /**
335    * Overwritten to adjust the default edge realizer when an edge creation button is hit by the current coordinates.
336    *
337    * @param x the x-coordinate of the mouse event in world coordinates.
338    * @param y the y-coordinate of the mouse event in world coordinates.
339    */
340   public void mousePressedLeft(final double x, final double y) {
341     lastPressHitButton = false;
342 
343     if (hitsEdgeCreationButton(x, y)) {
344       lastPressHitButton = true;
345       lastButtons.selectButtonAt(x, y);
346       final int selectedButtonIndex = lastButtons.getSelectedButtonIndex();
347       switch (selectedButtonIndex) {
348         case UmlEdgeCreationButtons.TYPE_ASSOCIATION:
349           getGraph2D().setDefaultEdgeRealizer(ASSOCIATION_REALIZER);
350           break;
351         case UmlEdgeCreationButtons.TYPE_DEPENDENCY:
352           getGraph2D().setDefaultEdgeRealizer(DEPENDENCY_REALIZER);
353           break;
354         case UmlEdgeCreationButtons.TYPE_GENERALIZATION:
355           getGraph2D().setDefaultEdgeRealizer(GENERALIZATION_REALIZER);
356           break;
357         case UmlEdgeCreationButtons.TYPE_REALIZATION:
358           getGraph2D().setDefaultEdgeRealizer(REALIZATION_REALIZER);
359           break;
360         case UmlEdgeCreationButtons.TYPE_AGGREGATION:
361           getGraph2D().setDefaultEdgeRealizer(AGGREGATION_REALIZER);
362           break;
363         case UmlEdgeCreationButtons.TYPE_COMPOSITION:
364           getGraph2D().setDefaultEdgeRealizer(COMPOSITION_REALIZER);
365           break;
366       }
367     }
368 
369     super.mousePressedLeft(x, y);
370   }
371 
372   /**
373    * Overwritten to start {@link y.view.CreateEdgeMode} in case the last press event was on an edge creation button.
374    * <code>CreateEdgeMode</code> will start the edge at the node associated with the button.
375    *
376    * @param x x-coordinate in world coordinates.
377    * @param y y-coordinate in world coordinates.
378    */
379   public void mouseReleasedLeft(final double x, final double y) {
380     final Graph2D graph = getGraph2D();
381     if (lastPressHitButton) {
382       startCreateEdgeMode(graph);
383     } else {
384       super.mouseReleasedLeft(x, y);
385     }
386   }
387 
388   /**
389    * Overwritten to show/hide the edge creation buttons when moving over a node and to push the node at the given
390    * coordinates to the front.
391    *
392    * @param x x-coordinate in world coordinates.
393    * @param y y-coordinate in world coordinates.
394    */
395   public void mouseMoved(final double x, final double y) {
396     final Graph2D graph = getGraph2D();
397     if (hitsButtonsDrawable(lastButtons, x, y)) {
398       // move/keep the node that belongs to the hit buttons to the front
399       graph.moveToLast(lastButtons.getNode());
400     } else {
401       final HitInfoFactory hitInfoFactory = view.getHitInfoFactory();
402       final HitInfo hit = hitInfoFactory.createHitInfo(x, y, Graph2DTraversal.NODES, true);
403       if (hit.hasHitNodes()) {
404         // move the hit node to the front
405         final Node node = hit.getHitNode();
406         graph.moveToLast(node);
407       }
408     }
409 
410     // show/hide edge creation buttons
411     scheduleButtonAnimations(x, y);
412 
413     // continue with default behavior
414     super.mouseMoved(x, y);
415   }
416 
417   /**
418    * Overwritten to create an edge with its target node in case the drag started in an edge creation button.
419    * The edge will start at the node associated with the button.
420    *
421    * @param x current x-coordinate of the mouse in world coordinates.
422    * @param y current y-coordinate of the mouse in world coordinates.
423    */
424   public void mouseDraggedLeft(final double x, final double y) {
425     scheduleButtonAnimations(x, y);
426 
427     final Graph2D graph = getGraph2D();
428     if (lastPressHitButton) {
429       // update create edge mode to have the right source node
430       umlCreateEdgeMode.setSourceNode(lastButtons.getNode());
431       // when the drag started at an edge creation button, use UmlCreateEdgeMode to create a new edge
432       setChild(umlCreateEdgeMode, lastPressEvent, lastDragEvent);
433     } else {
434       final HitInfo hit = getHitInfo(lastPressEvent);
435       // prevent edge creation other than from the edge creation buttons
436       if (!hit.hasHitNodes() || graph.isSelected(hit.getHitNode())) {
437         // drag started neither at an edge creation button nor on a non-selected node, use default behavior
438         super.mouseDraggedLeft(x, y);
439       }
440     }
441   }
442 
443   /**
444    * Schedules animations for showing and hiding of the edge creation buttons considering the given coordinates.
445    *
446    * @param x x-coordinate in world coordinates.
447    * @param y y-coordinate in world coordinates.
448    */
449   private void scheduleButtonAnimations(final double x, final double y) {
450     if (hitsButtonsDrawable(lastButtons, x, y)) {
451       // update button selection
452       updateButtonSelection(x, y);
453 
454       currentNode = lastButtons.getNode();
455     } else if (paintDetailed(view.getZoom())) {
456       final HitInfo hitInfo = view.getHitInfoFactory().createHitInfo(x, y, Graph2DTraversal.NODES, true);
457       currentNode = hitInfo.getHitNode();
458     }
459 
460     // node (+buttons) is entered or left, respectively
461     if (lastNode != currentNode) {
462       currentButtons = currentNode != null ? new UmlEdgeCreationButtons(view, currentNode) : null;
463       lastNode = currentNode;
464       hidingTimer.setInitialDelay(DELAY);
465       hidingTimer.start();
466       showingTimer.setInitialDelay(DELAY);
467       showingTimer.restart();
468     }
469   }
470 
471   /**
472    * Selects the button at the given position and updates the view if the selection has changed.
473    */
474   private void updateButtonSelection(double x, double y) {
475     int lastIndex = lastButtons.getSelectedButtonIndex();
476     lastButtons.selectButtonAt(x, y);
477     int currentIndex = lastButtons.getSelectedButtonIndex();
478     if (currentIndex != lastIndex) {
479       view.updateView();
480     }
481   }
482 
483   /**
484    * Starts {@link y.view.CreateEdgeMode} with an adjusted start event that is located on the current node. That way,
485    * the edge will be created although the last press event was outside the node.
486    */
487   private void startCreateEdgeMode(final Graph2D graph) {
488     if (lastButtons != null) {
489       // fake last drag event because the current last drag event occurred before the last press event and is not
490       // associated with this release event
491       lastDragEvent = new MouseEvent((Component) lastPressEvent.getSource(), MouseEvent.MOUSE_DRAGGED,
492           lastPressEvent.getWhen(), lastPressEvent.getModifiers(), lastPressEvent.getX(), lastPressEvent.getY(),
493           lastPressEvent.getClickCount(), lastPressEvent.isPopupTrigger());
494 
495       // drag started at an edge creation button, create a press event with manipulated coordinates that lie on the node.
496       // note: the coordinates have to be translated in view coordinates
497       final Node associatedNode = lastButtons.getNode();
498       final MouseEvent pressEvent = new MouseEvent((Component) lastPressEvent.getSource(), lastPressEvent.getID(),
499           lastPressEvent.getWhen(), lastPressEvent.getModifiers(),
500           view.toViewCoordX(graph.getCenterX(associatedNode)),
501           view.toViewCoordY(graph.getCenterY(associatedNode)),
502           lastPressEvent.getClickCount(), lastPressEvent.isPopupTrigger());
503 
504       // start create edge mode
505       setChild(getCreateEdgeMode(), pressEvent, lastDragEvent);
506     }
507   }
508 
509   /**
510    * Overwritten to update the edge creation buttons after a node was created.
511    */
512   protected void nodeCreated(Node node) {
513     Graph2D graph = getGraph2D();
514     scheduleButtonAnimations(graph.getCenterX(node), graph.getCenterY(node));
515   }
516 
517   /**
518    * Overwritten to hide the current edge creation buttons when a child mode gets active.
519    */
520   public void setChild(final ViewMode child,
521                        final MouseEvent pressEvent, final MouseEvent dragEvent, final MouseEvent releaseEvent) {
522     if (child != null && !(child instanceof MouseInputMode)) {
523       // remove current buttons drawable when starting a child mode except the MouseInputMode because it handles events
524       // from the buttons on the node (i.e. from inside the node's bounds) and the edge creation buttons shouldn't change
525       currentNode = null;
526       currentButtons = null;
527       if (lastNode != currentNode) {
528         hidingTimer.setInitialDelay(0);
529         hidingTimer.restart();
530         showingTimer.stop();
531         lastNode = currentNode;
532       }
533     }
534 
535     super.setChild(child, pressEvent, dragEvent, releaseEvent);
536   }
537 
538   /**
539    * Checks if an edge creation button was hit by the given coordinates.
540    *
541    * @param x x-coordinate in world coordinates.
542    * @param y y-coordinate in world coordinates.
543    *
544    * @return <code>true</code> if an edge creation button was hit by the given coordinates, <code>false</code>
545    *         otherwise.
546    */
547   private boolean hitsEdgeCreationButton(final double x, final double y) {
548     return lastButtons != null && lastButtons.hasButtonAt(x, y);
549   }
550 
551   /**
552    * Checks if the given union of the edge creation buttons and the area between them contains the passed coordinates.
553    *
554    * @return <code>true</code> if the given buttons drawable meets the passed coordinates, <code>false</code>
555    *         otherwise.
556    */
557   private boolean hitsButtonsDrawable(final UmlEdgeCreationButtons buttons, final double x, final double y) {
558     return buttons != null && buttons.contains(x, y);
559   }
560 
561   /**
562    * An {@link y.anim.AnimationObject} that adjusts the {@link UmlEdgeCreationButtons} time step so they either roll in
563    * or roll out and the node editing elements (add item/remove item) so they either fade in or fade out.
564    */
565   private class EditingElementsAnimation implements AnimationObject {
566     private static final int MOVE_DURATION = 300;
567 
568     private final UmlEdgeCreationButtons drawable;
569     private final boolean in;
570     private double progress;
571 
572     private final NodeRealizer realizer;
573     private double attributeButtonOpacity;
574     private double operationButtonOpacity;
575     private double selectionOpacity;
576 
577 
578     EditingElementsAnimation(final UmlEdgeCreationButtons drawable, final boolean in) {
579       this.drawable = drawable;
580       this.in = in;
581       this.realizer = getGraph2D().getRealizer(drawable.getNode());
582     }
583 
584     public void initAnimation() {
585       if (repaintManager != null) {
586         repaintManager.add(drawable);
587         repaintManager.add(realizer);
588       }
589 
590       if (in) {
591         rollingInButtons = drawable;
592         view.addDrawable(drawable);
593         drawable.setProgress(0);
594 
595         UmlRealizerFactory.setAttributeButtonOpacity(realizer, 0);
596         UmlRealizerFactory.setOperationButtonOpacity(realizer, 0);
597         UmlRealizerFactory.setSelectionOpacity(realizer, 0);
598       } else {
599         rollingOutButtons = drawable;
600         progress = drawable.getProgress();
601 
602         attributeButtonOpacity = UmlRealizerFactory.getAttributeButtonOpacity(realizer);
603         operationButtonOpacity = UmlRealizerFactory.getOperationButtonOpacity(realizer);
604         selectionOpacity = UmlRealizerFactory.getSelectionOpacity(realizer);
605       }
606     }
607 
608     public void calcFrame(final double time) {
609       if (in) {
610         drawable.setProgress(time);
611 
612         UmlRealizerFactory.setAttributeButtonOpacity(realizer, (float) time);
613         UmlRealizerFactory.setOperationButtonOpacity(realizer, (float) time);
614         UmlRealizerFactory.setSelectionOpacity(realizer, (float) time);
615       } else {
616         drawable.setProgress(progress * (1 - time));
617 
618         UmlRealizerFactory.setAttributeButtonOpacity(realizer, (float) (attributeButtonOpacity * (1 - time)));
619         UmlRealizerFactory.setOperationButtonOpacity(realizer, (float) (operationButtonOpacity * (1 - time)));
620         UmlRealizerFactory.setSelectionOpacity(realizer, (float) (selectionOpacity * (1 - time)));
621       }
622     }
623 
624     public void disposeAnimation() {
625       if (repaintManager != null) {
626         repaintManager.remove(drawable);
627         repaintManager.remove(realizer);
628       }
629       if (in) {
630         rollingInButtons = null;
631       } else {
632         view.removeDrawable(drawable);
633         rollingOutButtons = null;
634       }
635     }
636 
637     public long preferredDuration() {
638       if (in) {
639         return MOVE_DURATION;
640       } else {
641         if (progress > 0) {
642           return (long) (MOVE_DURATION * progress);
643         } else {
644           return MOVE_DURATION;
645         }
646       }
647     }
648   }
649 
650   /**
651    * A customized {@link y.view.MoveSelectionMode} that invokes edge routing after a set of nodes was moved.
652    */
653   private static class ReroutingMoveSelectionMode extends MoveSelectionMode {
654     private final EdgeRouter edgeRouter;
655 
656     public ReroutingMoveSelectionMode(EdgeRouter edgeRouter) {
657       this.edgeRouter = edgeRouter;
658     }
659 
660     public void mouseShiftPressedLeft(final double x, final double y) {
661       getGraph2D().firePreEvent();
662       super.mouseShiftPressedLeft(x, y);
663     }
664 
665     public void mousePressedLeft(final double x, final double y) {
666       getGraph2D().firePreEvent();
667       super.mousePressedLeft(x, y);
668     }
669 
670     public void mouseShiftReleasedLeft(final double x, final double y) {
671       super.mouseShiftReleasedLeft(x, y);
672       routeEdgesAtMovedNodes();
673       getGraph2D().firePostEvent();
674     }
675 
676     public void mouseReleasedLeft(final double x, final double y) {
677       super.mouseReleasedLeft(x, y);
678       routeEdgesAtMovedNodes();
679       getGraph2D().firePostEvent();
680     }
681 
682     private void routeEdgesAtMovedNodes() {
683       final Graph2D graph = getGraph2D();
684       final NodeList nodesToBeMoved = getNodesToBeMoved();
685       final DataProviderAdapter selectedNodes = new DataProviderAdapter() {
686         public boolean getBool(Object dataHolder) {
687           return nodesToBeMoved.contains(dataHolder);
688         }
689       };
690       edgeRouter.setSphereOfAction(EdgeRouter.ROUTE_EDGES_AT_SELECTED_NODES);
691       graph.addDataProvider(EdgeRouter.SELECTED_NODES, selectedNodes);
692       try {
693         final Graph2DLayoutExecutor executor = new Graph2DLayoutExecutor();
694         executor.getLayoutMorpher().setKeepZoomFactor(true);
695         executor.doLayout(view, edgeRouter);
696       } finally {
697         graph.removeDataProvider(EdgeRouter.SELECTED_NODES);
698       }
699     }
700   }
701 
702   /**
703    * A customized {@link y.view.CreateEdgeMode} that invokes edge routing after an edge was created.
704    */
705   private static class ReroutingCreateEdgeMode extends CreateEdgeMode {
706     private final EdgeRouter edgeRouter;
707 
708     public ReroutingCreateEdgeMode(EdgeRouter edgeRouter) {
709       this.edgeRouter = edgeRouter;
710     }
711 
712     protected Edge createEdge(final Graph2D graph, final Node startNode, final Node targetNode,
713                               final EdgeRealizer realizer) {
714       getGraph2D().firePreEvent();
715       return super.createEdge(graph, startNode, targetNode, realizer);
716     }
717 
718     protected void edgeCreated(final Edge edge) {
719       super.edgeCreated(edge);
720 
721       final Graph2D graph = getGraph2D();
722       final DataProviderAdapter selectedEdges = new DataProviderAdapter() {
723         public boolean getBool(Object dataHolder) {
724           return dataHolder == edge;
725         }
726       };
727       edgeRouter.setSphereOfAction(EdgeRouter.ROUTE_SELECTED_EDGES);
728       graph.addDataProvider(EdgeRouter.SELECTED_EDGES, selectedEdges);
729       try {
730         final Graph2DLayoutExecutor executor = new Graph2DLayoutExecutor();
731         executor.getLayoutMorpher().setKeepZoomFactor(true);
732         executor.doLayout(view, edgeRouter);
733       } finally {
734         graph.removeDataProvider(EdgeRouter.SELECTED_EDGES);
735       }
736       graph.firePostEvent();
737     }
738   }
739 
740   /**
741    * A customized {@link y.view.HotSpotMode} that invokes edge routing after a node was resized.
742    */
743   private static final class ReroutingHotSpotMode extends HotSpotMode {
744     private final EdgeRouter edgeRouter;
745 
746     public ReroutingHotSpotMode(EdgeRouter edgeRouter) {
747       this.edgeRouter = edgeRouter;
748     }
749 
750     public void mousePressedLeft(double x, double y) {
751       getGraph2D().firePreEvent();
752       super.mousePressedLeft(x, y);
753     }
754 
755     public void mouseReleasedLeft(double x, double y) {
756       super.mouseReleasedLeft(x, y);
757       edgeRouter.setSphereOfAction(EdgeRouter.ROUTE_EDGES_AT_SELECTED_NODES);
758       getGraph2D().addDataProvider(EdgeRouter.SELECTED_NODES, new DataProviderAdapter() {
759         public boolean getBool(Object dataHolder) {
760           return dataHolder instanceof Node && getGraph2D().isSelected((Node) dataHolder);
761         }
762       });
763       try {
764         final Graph2DLayoutExecutor executor = new Graph2DLayoutExecutor();
765         executor.getLayoutMorpher().setKeepZoomFactor(true);
766         executor.doLayout(view, edgeRouter);
767       } finally {
768         getGraph2D().firePostEvent();
769         edgeRouter.setSphereOfAction(EdgeRouter.ROUTE_SELECTED_EDGES);
770       }
771     }
772   }
773 }
774