]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.diagram/src/org/simantics/diagram/participant/ConnectionEditingSupport.java
Merge "(refs #7102) Fixed comparator NodeEventHandler.TreePreOrderComparator"
[simantics/platform.git] / bundles / org.simantics.diagram / src / org / simantics / diagram / participant / ConnectionEditingSupport.java
1 /*******************************************************************************
2  * Copyright (c) 2007, 2010 Association for Decentralized Information Management
3  * in Industry THTH ry.
4  * All rights reserved. This program and the accompanying materials
5  * are made available under the terms of the Eclipse Public License v1.0
6  * which accompanies this distribution, and is available at
7  * http://www.eclipse.org/legal/epl-v10.html
8  *
9  * Contributors:
10  *     VTT Technical Research Centre of Finland - initial API and implementation
11  *******************************************************************************/
12 package org.simantics.diagram.participant;
13
14 import static org.simantics.g2d.diagram.handler.PickRequest.PickFilter.FILTER_CONNECTIONS;
15 import static org.simantics.g2d.diagram.handler.PickRequest.PickFilter.FILTER_CONNECTION_EDGES;
16 import static org.simantics.g2d.diagram.handler.PickRequest.PickFilter.FILTER_NODES;
17
18 import java.awt.Shape;
19 import java.awt.geom.AffineTransform;
20 import java.awt.geom.Line2D;
21 import java.awt.geom.Point2D;
22 import java.util.ArrayList;
23 import java.util.Collection;
24 import java.util.Collections;
25 import java.util.Comparator;
26 import java.util.List;
27 import java.util.Set;
28 import java.util.concurrent.atomic.AtomicReference;
29
30 import org.simantics.db.Resource;
31 import org.simantics.db.WriteGraph;
32 import org.simantics.db.common.request.WriteRequest;
33 import org.simantics.db.exception.DatabaseException;
34 import org.simantics.diagram.content.ConnectionUtil;
35 import org.simantics.diagram.content.EdgeResource;
36 import org.simantics.diagram.stubs.DiagramResource;
37 import org.simantics.diagram.ui.DiagramModelHints;
38 import org.simantics.g2d.canvas.ICanvasContext;
39 import org.simantics.g2d.canvas.impl.DependencyReflection.Dependency;
40 import org.simantics.g2d.diagram.DiagramHints;
41 import org.simantics.g2d.diagram.IDiagram;
42 import org.simantics.g2d.diagram.handler.DataElementMap;
43 import org.simantics.g2d.diagram.handler.PickContext;
44 import org.simantics.g2d.diagram.handler.PickRequest;
45 import org.simantics.g2d.diagram.handler.PickRequest.PickPolicy;
46 import org.simantics.g2d.diagram.handler.PickRequest.PickSorter;
47 import org.simantics.g2d.diagram.participant.AbstractDiagramParticipant;
48 import org.simantics.g2d.diagram.participant.Selection;
49 import org.simantics.g2d.diagram.participant.pointertool.AbstractMode;
50 import org.simantics.g2d.diagram.participant.pointertool.PointerInteractor;
51 import org.simantics.g2d.diagram.participant.pointertool.TranslateMode;
52 import org.simantics.g2d.element.ElementUtils;
53 import org.simantics.g2d.element.IElement;
54 import org.simantics.g2d.element.handler.Children;
55 import org.simantics.g2d.participant.TransformUtil;
56 import org.simantics.g2d.participant.WorkbenchStatusLine;
57 import org.simantics.scenegraph.g2d.events.MouseEvent;
58 import org.simantics.scenegraph.g2d.events.EventHandlerReflection.EventHandler;
59 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseButtonPressedEvent;
60 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseButtonReleasedEvent;
61 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseDragBegin;
62 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseMovedEvent;
63 import org.simantics.scenegraph.g2d.snap.ISnapAdvisor;
64 import org.simantics.ui.SimanticsUI;
65 import org.simantics.utils.datastructures.hints.HintListenerAdapter;
66 import org.simantics.utils.datastructures.hints.IHintContext.Key;
67 import org.simantics.utils.datastructures.hints.IHintListener;
68 import org.simantics.utils.datastructures.hints.IHintObservable;
69 import org.simantics.utils.ui.ErrorLogger;
70
71 /**
72  * @author Tuukka Lehtonen
73  */
74 public class ConnectionEditingSupport extends AbstractDiagramParticipant {
75
76     private static final boolean DEBUG = false;
77
78     private static final int TOOL_PRIORITY = 100;
79
80     @Dependency PointerInteractor pi;
81     @Dependency PickContext pickContext;
82     @Dependency Selection selection;
83     @Dependency WorkbenchStatusLine statusLine;
84
85     private static final PickSorter NODES_LAST = new PickSorter() {
86         @Override
87         public void sort(List<IElement> elements) {
88             Collections.sort(elements, new Comparator<IElement>() {
89                 @Override
90                 public int compare(IElement e1, IElement e2) {
91                     boolean is1 = FILTER_NODES.accept(e1);
92                     boolean is2 = FILTER_NODES.accept(e2);
93                     if (!is1 && is2)
94                         return -1;
95                     if (is1 && !is2)
96                         return 1;
97                     return 0;
98                 }
99             });
100         }
101     };
102
103     private boolean routePointsEnabled() {
104         return Boolean.TRUE.equals(diagram.getHint(DiagramHints.KEY_ALLOW_ROUTE_POINTS));
105     }
106
107     @EventHandler(priority = TOOL_PRIORITY)
108     public boolean handleMouse(MouseEvent e) {
109         if (!routePointsEnabled())
110             return false;
111         if (e instanceof MouseButtonPressedEvent)
112             return handlePress((MouseButtonPressedEvent) e);
113         return false;
114     }
115
116     private boolean handlePress(MouseButtonPressedEvent me) {
117         if (me.button != MouseEvent.LEFT_BUTTON || me.hasAnyModifier(MouseEvent.ALL_MODIFIERS_MASK))
118             return false;
119
120         //System.out.println("button pressed: " + me);
121
122         Shape shape = pi.getCanvasPickShape(me.controlPosition);
123         if (shape == null)
124             return false;
125
126         PickRequest req = new PickRequest(shape);
127         req.pickPolicy = PickPolicy.PICK_INTERSECTING_OBJECTS;
128         req.pickFilter = null;
129         req.pickSorter = NODES_LAST;
130
131         List<IElement> pick = new ArrayList<IElement>();
132         pickContext.pick(diagram, req, pick);
133
134         if (pick.isEmpty())
135             return false;
136
137         //System.out.println("selection pick returns " + pick);
138
139         // If current mouse selection contains only a connection edge or
140         // complete connection and pick result contains the same connection or
141         // edge as the first hit, start dragging new route point.
142         Set<IElement> sel = selection.getSelection(me.mouseId);
143         if (!Collections.disjoint(pick, sel)) {
144             if (sel.size() == 1) {
145                 IElement e = sel.iterator().next();
146                 if (FILTER_CONNECTIONS.accept(e) || FILTER_CONNECTION_EDGES.accept(e)) {
147                     IElement edge = findSingleConnectionEdge(pick, e);
148                     if (edge != null) {
149                         getContext().add(new ConnectionRoutingMode(me.mouseId, edge));
150                         return true;
151                     }
152                 }
153             }
154             return false;
155         }
156
157         IElement edge = findSingleConnectionEdge(pick, null);
158         if (edge != null) {
159             getContext().add(new ConnectionRoutingMode(me.mouseId, edge));
160             return true;
161         }
162
163         return false;
164     }
165
166     /**
167      * @param pick
168      * @param selected
169      * @return
170      */
171     private IElement findSingleConnectionEdge(List<IElement> pick, IElement selected) {
172         for (int i = pick.size() - 1; i >= 0; --i) {
173             IElement p = pick.get(i);
174             boolean pickedSelected = selected == null ? true : p == selected;
175             if (FILTER_NODES.accept(p))
176                 return null;
177             if (pickedSelected && FILTER_CONNECTION_EDGES.accept(p)) {
178                 return p;
179             }
180         }
181         for (int i = pick.size() - 1; i >= 0; --i) {
182             IElement p = pick.get(i);
183             boolean pickedSelected = selected == null ? true : p == selected;
184             if (FILTER_CONNECTIONS.accept(p)) {
185                 Children ch = p.getElementClass().getAtMostOneItemOfClass(Children.class);
186                 if (ch == null)
187                     return null;
188
189                 Collection<IElement> children = ch.getChildren(p, null);
190                 int childCount = children.size();
191                 if (childCount == 1) {
192                     for (IElement child : children) {
193                         if (pickedSelected && FILTER_CONNECTION_EDGES.accept(child))
194                             return child;
195                     }
196                 } else if (childCount > 1) {
197                     for (IElement child : children) {
198                         if (pickedSelected && FILTER_CONNECTION_EDGES.accept(child) && pick.contains(child))
199                             return child;
200                     }
201                 }
202             }
203         }
204         return null;
205     }
206
207     static class ConnectionRoutingMode extends AbstractMode {
208
209         @Dependency TransformUtil tr;
210         @Dependency Selection sel;
211
212         private boolean dragging;
213         private final IElement edge;
214
215         public ConnectionRoutingMode(int mouseId, IElement edge) {
216             super(mouseId);
217             if (DEBUG)
218                 System.out.println("Start routing mode (" + mouseId + ")");
219             this.edge = edge;
220         }
221
222         @Override
223         public void addedToContext(ICanvasContext ctx) {
224             super.addedToContext(ctx);
225             if (DEBUG)
226                 System.out.println(this + " added");
227         }
228
229         @Override
230         public void removedFromContext(ICanvasContext ctx) {
231             if (DEBUG)
232                 System.out.println(this + " removed");
233             super.removedFromContext(ctx);
234         }
235
236         @Override
237         protected void onDiagramSet(IDiagram newDiagram, IDiagram oldDiagram) {
238             if (oldDiagram != null) {
239                 oldDiagram.removeKeyHintListener(DiagramModelHints.KEY_DIAGRAM_CONTENTS_UPDATED, diagramHintListener);
240             }
241             if (newDiagram != null) {
242                 newDiagram.addKeyHintListener(DiagramModelHints.KEY_DIAGRAM_CONTENTS_UPDATED, diagramHintListener);
243             }
244         }
245
246         @EventHandler(priority = TOOL_PRIORITY + 10)
247         public boolean handleMouse(MouseEvent e) {
248             if (!isModeMouse(e))
249                 return false;
250
251             //System.out.println("mouse event: " + e);
252
253             if (e instanceof MouseMovedEvent)
254                 return handleMove((MouseMovedEvent) e);
255             if (e instanceof MouseDragBegin)
256                 return handleDrag((MouseDragBegin) e);
257             if (e instanceof MouseButtonReleasedEvent)
258                 return handleRelease((MouseButtonReleasedEvent) e);
259
260             // Ignore all other events
261             return true;
262         }
263
264         private boolean handleDrag(MouseDragBegin e) {
265             // Mark dragging as started.
266             dragging = true;
267             splitConnection(e.startCanvasPos, tr.controlToCanvas(e.controlPosition, null));
268             return true;
269         }
270
271         private boolean handleMove(MouseMovedEvent e) {
272             if (!dragging)
273                 return true;
274             if (DEBUG)
275                 System.out.println("routing move: " + e);
276             return false;
277         }
278
279         private boolean handleRelease(MouseButtonReleasedEvent e) {
280 //            setDirty();
281             remove();
282             return false;
283         }
284
285         boolean splitConnection(final Point2D startingPos, Point2D currentPos) {
286             final IDiagram diagram = ElementUtils.peekDiagram(edge);
287             if (diagram == null)
288                 return false;
289             final EdgeResource segment = (EdgeResource) ElementUtils.getObject(edge);
290             if (segment == null)
291                 return false;
292
293             Point2D snapPos = new Point2D.Double(startingPos.getX(), startingPos.getY());
294             ISnapAdvisor snap = getHint(DiagramHints.SNAP_ADVISOR);
295             if (snap != null)
296                 snap.snap(snapPos);
297
298             final AffineTransform splitPos = AffineTransform.getTranslateInstance(snapPos.getX(), snapPos.getY());
299             final AtomicReference<Resource> newBp = new AtomicReference<Resource>();
300
301             try {
302                 SimanticsUI.getSession().syncRequest(new WriteRequest() {
303                     @Override
304                     public void perform(WriteGraph graph) throws DatabaseException {
305                         DiagramResource DIA = DiagramResource.getInstance(graph);
306
307                         // Split the edge with a new branch point
308                         ConnectionUtil cu = new ConnectionUtil(graph);
309                         Resource bp = cu.split(segment, splitPos);
310
311                         Line2D nearestLine = ConnectionUtil.resolveNearestEdgeLineSegment(startingPos, edge);
312                         if (nearestLine != null) {
313                             double angle = Math.atan2(
314                                     Math.abs(nearestLine.getY2() - nearestLine.getY1()),
315                                     Math.abs(nearestLine.getX2() - nearestLine.getX1())
316                             );
317
318                             if (angle >= 0 && angle < Math.PI / 4) {
319                                 graph.claim(bp, DIA.Horizontal, bp);
320                             } else if (angle > Math.PI / 4 && angle <= Math.PI / 2) {
321                                 graph.claim(bp, DIA.Vertical, bp);
322                             }
323                         }
324
325                         newBp.set(bp);
326                     }
327
328                 });
329
330                 dragData.set(new DragData(Collections.singleton(newBp.get()), startingPos, currentPos));
331
332             } catch (DatabaseException e) {
333                 ErrorLogger.defaultLogError(e);
334             }
335
336             return false;
337         }
338
339         static class DragData {
340             Set<?> data;
341             Point2D startingPoint;
342             Point2D currentPoint;
343             public DragData(Set<?> data, Point2D startingPoint, Point2D currentPoint) {
344                 this.data = data;
345                 this.startingPoint = startingPoint;
346                 this.currentPoint = currentPoint;
347             }
348         }
349
350         private final AtomicReference<DragData> dragData = new AtomicReference<DragData>();
351
352         IHintListener diagramHintListener = new HintListenerAdapter() {
353             @Override
354             public void hintChanged(IHintObservable sender, Key key, Object oldValue, Object newValue) {
355                 if (isRemoved())
356                     return;
357                 if (key == DiagramModelHints.KEY_DIAGRAM_CONTENTS_UPDATED) {
358                     final DragData data = dragData.getAndSet(null);
359                     if (data != null) {
360                         asyncExec(new Runnable() {
361                             @Override
362                             public void run() {
363                                 // Safety first.
364                                 if (isRemoved())
365                                     return;
366                                 setDiagramSelectionToData(data);
367                             }
368                         });
369                     }
370                 }
371             }
372
373             private void setDiagramSelectionToData(final DragData data) {
374                 DataElementMap dem = diagram.getDiagramClass().getAtMostOneItemOfClass(DataElementMap.class);
375                 if (dem != null) {
376                     final Collection<IElement> newSelection = new ArrayList<IElement>(data.data.size());
377                     for (Object datum : data.data) {
378                         IElement element = dem.getElement(diagram, datum);
379                         if (element != null) {
380                             newSelection.add(element);
381                         }
382                     }
383
384                     if (!newSelection.isEmpty()) {
385                         sel.setSelection(0, newSelection);
386                         getContext().add( new TranslateMode(data.startingPoint, data.currentPoint, getMouseId(), newSelection) );
387                         remove();
388                     }
389                 }
390             }
391         };
392
393     }
394
395 }