1 /*******************************************************************************
\r
2 * Copyright (c) 2007, 2010 Association for Decentralized Information Management
\r
3 * in Industry THTH ry.
\r
4 * All rights reserved. This program and the accompanying materials
\r
5 * are made available under the terms of the Eclipse Public License v1.0
\r
6 * which accompanies this distribution, and is available at
\r
7 * http://www.eclipse.org/legal/epl-v10.html
\r
10 * VTT Technical Research Centre of Finland - initial API and implementation
\r
11 *******************************************************************************/
\r
12 package org.simantics.diagram.participant;
\r
14 import static org.simantics.g2d.diagram.handler.PickRequest.PickFilter.FILTER_CONNECTIONS;
\r
15 import static org.simantics.g2d.diagram.handler.PickRequest.PickFilter.FILTER_CONNECTION_EDGES;
\r
16 import static org.simantics.g2d.diagram.handler.PickRequest.PickFilter.FILTER_NODES;
\r
18 import java.awt.Shape;
\r
19 import java.awt.geom.AffineTransform;
\r
20 import java.awt.geom.Line2D;
\r
21 import java.awt.geom.Point2D;
\r
22 import java.util.ArrayList;
\r
23 import java.util.Collection;
\r
24 import java.util.Collections;
\r
25 import java.util.Comparator;
\r
26 import java.util.List;
\r
27 import java.util.Set;
\r
28 import java.util.concurrent.atomic.AtomicReference;
\r
30 import org.simantics.db.Resource;
\r
31 import org.simantics.db.WriteGraph;
\r
32 import org.simantics.db.common.request.WriteRequest;
\r
33 import org.simantics.db.exception.DatabaseException;
\r
34 import org.simantics.diagram.content.ConnectionUtil;
\r
35 import org.simantics.diagram.content.EdgeResource;
\r
36 import org.simantics.diagram.stubs.DiagramResource;
\r
37 import org.simantics.diagram.ui.DiagramModelHints;
\r
38 import org.simantics.g2d.canvas.ICanvasContext;
\r
39 import org.simantics.g2d.canvas.impl.DependencyReflection.Dependency;
\r
40 import org.simantics.g2d.diagram.DiagramHints;
\r
41 import org.simantics.g2d.diagram.IDiagram;
\r
42 import org.simantics.g2d.diagram.handler.DataElementMap;
\r
43 import org.simantics.g2d.diagram.handler.PickContext;
\r
44 import org.simantics.g2d.diagram.handler.PickRequest;
\r
45 import org.simantics.g2d.diagram.handler.PickRequest.PickPolicy;
\r
46 import org.simantics.g2d.diagram.handler.PickRequest.PickSorter;
\r
47 import org.simantics.g2d.diagram.participant.AbstractDiagramParticipant;
\r
48 import org.simantics.g2d.diagram.participant.Selection;
\r
49 import org.simantics.g2d.diagram.participant.pointertool.AbstractMode;
\r
50 import org.simantics.g2d.diagram.participant.pointertool.PointerInteractor;
\r
51 import org.simantics.g2d.diagram.participant.pointertool.TranslateMode;
\r
52 import org.simantics.g2d.element.ElementUtils;
\r
53 import org.simantics.g2d.element.IElement;
\r
54 import org.simantics.g2d.element.handler.Children;
\r
55 import org.simantics.g2d.participant.TransformUtil;
\r
56 import org.simantics.g2d.participant.WorkbenchStatusLine;
\r
57 import org.simantics.scenegraph.g2d.events.MouseEvent;
\r
58 import org.simantics.scenegraph.g2d.events.EventHandlerReflection.EventHandler;
\r
59 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseButtonPressedEvent;
\r
60 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseButtonReleasedEvent;
\r
61 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseDragBegin;
\r
62 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseMovedEvent;
\r
63 import org.simantics.scenegraph.g2d.snap.ISnapAdvisor;
\r
64 import org.simantics.ui.SimanticsUI;
\r
65 import org.simantics.utils.datastructures.hints.HintListenerAdapter;
\r
66 import org.simantics.utils.datastructures.hints.IHintContext.Key;
\r
67 import org.simantics.utils.datastructures.hints.IHintListener;
\r
68 import org.simantics.utils.datastructures.hints.IHintObservable;
\r
69 import org.simantics.utils.ui.ErrorLogger;
\r
72 * @author Tuukka Lehtonen
\r
74 public class ConnectionEditingSupport extends AbstractDiagramParticipant {
\r
76 private static final boolean DEBUG = false;
\r
78 private static final int TOOL_PRIORITY = 100;
\r
80 @Dependency PointerInteractor pi;
\r
81 @Dependency PickContext pickContext;
\r
82 @Dependency Selection selection;
\r
83 @Dependency WorkbenchStatusLine statusLine;
\r
85 private static final PickSorter NODES_LAST = new PickSorter() {
\r
87 public void sort(List<IElement> elements) {
\r
88 Collections.sort(elements, new Comparator<IElement>() {
\r
90 public int compare(IElement e1, IElement e2) {
\r
91 boolean is1 = FILTER_NODES.accept(e1);
\r
92 boolean is2 = FILTER_NODES.accept(e2);
\r
103 private boolean routePointsEnabled() {
\r
104 return Boolean.TRUE.equals(diagram.getHint(DiagramHints.KEY_ALLOW_ROUTE_POINTS));
\r
107 @EventHandler(priority = TOOL_PRIORITY)
\r
108 public boolean handleMouse(MouseEvent e) {
\r
109 if (!routePointsEnabled())
\r
111 if (e instanceof MouseButtonPressedEvent)
\r
112 return handlePress((MouseButtonPressedEvent) e);
\r
116 private boolean handlePress(MouseButtonPressedEvent me) {
\r
117 if (me.button != MouseEvent.LEFT_BUTTON || me.hasAnyModifier(MouseEvent.ALL_MODIFIERS_MASK))
\r
120 //System.out.println("button pressed: " + me);
\r
122 Shape shape = pi.getCanvasPickShape(me.controlPosition);
\r
126 PickRequest req = new PickRequest(shape);
\r
127 req.pickPolicy = PickPolicy.PICK_INTERSECTING_OBJECTS;
\r
128 req.pickFilter = null;
\r
129 req.pickSorter = NODES_LAST;
\r
131 List<IElement> pick = new ArrayList<IElement>();
\r
132 pickContext.pick(diagram, req, pick);
\r
134 if (pick.isEmpty())
\r
137 //System.out.println("selection pick returns " + pick);
\r
139 // If current mouse selection contains only a connection edge or
\r
140 // complete connection and pick result contains the same connection or
\r
141 // edge as the first hit, start dragging new route point.
\r
142 Set<IElement> sel = selection.getSelection(me.mouseId);
\r
143 if (!Collections.disjoint(pick, sel)) {
\r
144 if (sel.size() == 1) {
\r
145 IElement e = sel.iterator().next();
\r
146 if (FILTER_CONNECTIONS.accept(e) || FILTER_CONNECTION_EDGES.accept(e)) {
\r
147 IElement edge = findSingleConnectionEdge(pick, e);
\r
148 if (edge != null) {
\r
149 getContext().add(new ConnectionRoutingMode(me.mouseId, edge));
\r
157 IElement edge = findSingleConnectionEdge(pick, null);
\r
158 if (edge != null) {
\r
159 getContext().add(new ConnectionRoutingMode(me.mouseId, edge));
\r
171 private IElement findSingleConnectionEdge(List<IElement> pick, IElement selected) {
\r
172 for (int i = pick.size() - 1; i >= 0; --i) {
\r
173 IElement p = pick.get(i);
\r
174 boolean pickedSelected = selected == null ? true : p == selected;
\r
175 if (FILTER_NODES.accept(p))
\r
177 if (pickedSelected && FILTER_CONNECTION_EDGES.accept(p)) {
\r
181 for (int i = pick.size() - 1; i >= 0; --i) {
\r
182 IElement p = pick.get(i);
\r
183 boolean pickedSelected = selected == null ? true : p == selected;
\r
184 if (FILTER_CONNECTIONS.accept(p)) {
\r
185 Children ch = p.getElementClass().getAtMostOneItemOfClass(Children.class);
\r
189 Collection<IElement> children = ch.getChildren(p, null);
\r
190 int childCount = children.size();
\r
191 if (childCount == 1) {
\r
192 for (IElement child : children) {
\r
193 if (pickedSelected && FILTER_CONNECTION_EDGES.accept(child))
\r
196 } else if (childCount > 1) {
\r
197 for (IElement child : children) {
\r
198 if (pickedSelected && FILTER_CONNECTION_EDGES.accept(child) && pick.contains(child))
\r
207 static class ConnectionRoutingMode extends AbstractMode {
\r
209 @Dependency TransformUtil tr;
\r
210 @Dependency Selection sel;
\r
212 private boolean dragging;
\r
213 private final IElement edge;
\r
215 public ConnectionRoutingMode(int mouseId, IElement edge) {
\r
218 System.out.println("Start routing mode (" + mouseId + ")");
\r
223 public void addedToContext(ICanvasContext ctx) {
\r
224 super.addedToContext(ctx);
\r
226 System.out.println(this + " added");
\r
230 public void removedFromContext(ICanvasContext ctx) {
\r
232 System.out.println(this + " removed");
\r
233 super.removedFromContext(ctx);
\r
237 protected void onDiagramSet(IDiagram newDiagram, IDiagram oldDiagram) {
\r
238 if (oldDiagram != null) {
\r
239 oldDiagram.removeKeyHintListener(DiagramModelHints.KEY_DIAGRAM_CONTENTS_UPDATED, diagramHintListener);
\r
241 if (newDiagram != null) {
\r
242 newDiagram.addKeyHintListener(DiagramModelHints.KEY_DIAGRAM_CONTENTS_UPDATED, diagramHintListener);
\r
246 @EventHandler(priority = TOOL_PRIORITY + 10)
\r
247 public boolean handleMouse(MouseEvent e) {
\r
248 if (!isModeMouse(e))
\r
251 //System.out.println("mouse event: " + e);
\r
253 if (e instanceof MouseMovedEvent)
\r
254 return handleMove((MouseMovedEvent) e);
\r
255 if (e instanceof MouseDragBegin)
\r
256 return handleDrag((MouseDragBegin) e);
\r
257 if (e instanceof MouseButtonReleasedEvent)
\r
258 return handleRelease((MouseButtonReleasedEvent) e);
\r
260 // Ignore all other events
\r
264 private boolean handleDrag(MouseDragBegin e) {
\r
265 // Mark dragging as started.
\r
267 splitConnection(e.startCanvasPos, tr.controlToCanvas(e.controlPosition, null));
\r
271 private boolean handleMove(MouseMovedEvent e) {
\r
275 System.out.println("routing move: " + e);
\r
279 private boolean handleRelease(MouseButtonReleasedEvent e) {
\r
285 boolean splitConnection(final Point2D startingPos, Point2D currentPos) {
\r
286 final IDiagram diagram = ElementUtils.peekDiagram(edge);
\r
287 if (diagram == null)
\r
289 final EdgeResource segment = (EdgeResource) ElementUtils.getObject(edge);
\r
290 if (segment == null)
\r
293 Point2D snapPos = new Point2D.Double(startingPos.getX(), startingPos.getY());
\r
294 ISnapAdvisor snap = getHint(DiagramHints.SNAP_ADVISOR);
\r
296 snap.snap(snapPos);
\r
298 final AffineTransform splitPos = AffineTransform.getTranslateInstance(snapPos.getX(), snapPos.getY());
\r
299 final AtomicReference<Resource> newBp = new AtomicReference<Resource>();
\r
302 SimanticsUI.getSession().syncRequest(new WriteRequest() {
\r
304 public void perform(WriteGraph graph) throws DatabaseException {
\r
305 DiagramResource DIA = DiagramResource.getInstance(graph);
\r
307 // Split the edge with a new branch point
\r
308 ConnectionUtil cu = new ConnectionUtil(graph);
\r
309 Resource bp = cu.split(segment, splitPos);
\r
311 Line2D nearestLine = ConnectionUtil.resolveNearestEdgeLineSegment(startingPos, edge);
\r
312 if (nearestLine != null) {
\r
313 double angle = Math.atan2(
\r
314 Math.abs(nearestLine.getY2() - nearestLine.getY1()),
\r
315 Math.abs(nearestLine.getX2() - nearestLine.getX1())
\r
318 if (angle >= 0 && angle < Math.PI / 4) {
\r
319 graph.claim(bp, DIA.Horizontal, bp);
\r
320 } else if (angle > Math.PI / 4 && angle <= Math.PI / 2) {
\r
321 graph.claim(bp, DIA.Vertical, bp);
\r
330 dragData.set(new DragData(Collections.singleton(newBp.get()), startingPos, currentPos));
\r
332 } catch (DatabaseException e) {
\r
333 ErrorLogger.defaultLogError(e);
\r
339 static class DragData {
\r
341 Point2D startingPoint;
\r
342 Point2D currentPoint;
\r
343 public DragData(Set<?> data, Point2D startingPoint, Point2D currentPoint) {
\r
345 this.startingPoint = startingPoint;
\r
346 this.currentPoint = currentPoint;
\r
350 private final AtomicReference<DragData> dragData = new AtomicReference<DragData>();
\r
352 IHintListener diagramHintListener = new HintListenerAdapter() {
\r
354 public void hintChanged(IHintObservable sender, Key key, Object oldValue, Object newValue) {
\r
357 if (key == DiagramModelHints.KEY_DIAGRAM_CONTENTS_UPDATED) {
\r
358 final DragData data = dragData.getAndSet(null);
\r
359 if (data != null) {
\r
360 asyncExec(new Runnable() {
\r
362 public void run() {
\r
366 setDiagramSelectionToData(data);
\r
373 private void setDiagramSelectionToData(final DragData data) {
\r
374 DataElementMap dem = diagram.getDiagramClass().getAtMostOneItemOfClass(DataElementMap.class);
\r
376 final Collection<IElement> newSelection = new ArrayList<IElement>(data.data.size());
\r
377 for (Object datum : data.data) {
\r
378 IElement element = dem.getElement(diagram, datum);
\r
379 if (element != null) {
\r
380 newSelection.add(element);
\r
384 if (!newSelection.isEmpty()) {
\r
385 sel.setSelection(0, newSelection);
\r
386 getContext().add( new TranslateMode(data.startingPoint, data.currentPoint, getMouseId(), newSelection) );
\r