1 /*******************************************************************************
2 * Copyright (c) 2007, 2010 Association for Decentralized Information Management
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
10 * VTT Technical Research Centre of Finland - initial API and implementation
11 *******************************************************************************/
12 package org.simantics.diagram.participant;
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;
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;
28 import java.util.concurrent.atomic.AtomicReference;
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;
72 * @author Tuukka Lehtonen
74 public class ConnectionEditingSupport extends AbstractDiagramParticipant {
76 private static final boolean DEBUG = false;
78 private static final int TOOL_PRIORITY = 100;
80 @Dependency PointerInteractor pi;
81 @Dependency PickContext pickContext;
82 @Dependency Selection selection;
83 @Dependency WorkbenchStatusLine statusLine;
85 private static final PickSorter NODES_LAST = new PickSorter() {
87 public void sort(List<IElement> elements) {
88 Collections.sort(elements, new Comparator<IElement>() {
90 public int compare(IElement e1, IElement e2) {
91 boolean is1 = FILTER_NODES.accept(e1);
92 boolean is2 = FILTER_NODES.accept(e2);
103 private boolean routePointsEnabled() {
104 return Boolean.TRUE.equals(diagram.getHint(DiagramHints.KEY_ALLOW_ROUTE_POINTS));
107 @EventHandler(priority = TOOL_PRIORITY)
108 public boolean handleMouse(MouseEvent e) {
109 if (!routePointsEnabled())
111 if (e instanceof MouseButtonPressedEvent)
112 return handlePress((MouseButtonPressedEvent) e);
116 private boolean handlePress(MouseButtonPressedEvent me) {
117 if (me.button != MouseEvent.LEFT_BUTTON || me.hasAnyModifier(MouseEvent.ALL_MODIFIERS_MASK))
120 //System.out.println("button pressed: " + me);
122 Shape shape = pi.getCanvasPickShape(me.controlPosition);
126 PickRequest req = new PickRequest(shape);
127 req.pickPolicy = PickPolicy.PICK_INTERSECTING_OBJECTS;
128 req.pickFilter = null;
129 req.pickSorter = NODES_LAST;
131 List<IElement> pick = new ArrayList<IElement>();
132 pickContext.pick(diagram, req, pick);
137 //System.out.println("selection pick returns " + pick);
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);
149 getContext().add(new ConnectionRoutingMode(me.mouseId, edge));
157 IElement edge = findSingleConnectionEdge(pick, null);
159 getContext().add(new ConnectionRoutingMode(me.mouseId, edge));
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))
177 if (pickedSelected && FILTER_CONNECTION_EDGES.accept(p)) {
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);
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))
196 } else if (childCount > 1) {
197 for (IElement child : children) {
198 if (pickedSelected && FILTER_CONNECTION_EDGES.accept(child) && pick.contains(child))
207 static class ConnectionRoutingMode extends AbstractMode {
209 @Dependency TransformUtil tr;
210 @Dependency Selection sel;
212 private boolean dragging;
213 private final IElement edge;
215 public ConnectionRoutingMode(int mouseId, IElement edge) {
218 System.out.println("Start routing mode (" + mouseId + ")");
223 public void addedToContext(ICanvasContext ctx) {
224 super.addedToContext(ctx);
226 System.out.println(this + " added");
230 public void removedFromContext(ICanvasContext ctx) {
232 System.out.println(this + " removed");
233 super.removedFromContext(ctx);
237 protected void onDiagramSet(IDiagram newDiagram, IDiagram oldDiagram) {
238 if (oldDiagram != null) {
239 oldDiagram.removeKeyHintListener(DiagramModelHints.KEY_DIAGRAM_CONTENTS_UPDATED, diagramHintListener);
241 if (newDiagram != null) {
242 newDiagram.addKeyHintListener(DiagramModelHints.KEY_DIAGRAM_CONTENTS_UPDATED, diagramHintListener);
246 @EventHandler(priority = TOOL_PRIORITY + 10)
247 public boolean handleMouse(MouseEvent e) {
251 //System.out.println("mouse event: " + e);
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);
260 // Ignore all other events
264 private boolean handleDrag(MouseDragBegin e) {
265 // Mark dragging as started.
267 splitConnection(e.startCanvasPos, tr.controlToCanvas(e.controlPosition, null));
271 private boolean handleMove(MouseMovedEvent e) {
275 System.out.println("routing move: " + e);
279 private boolean handleRelease(MouseButtonReleasedEvent e) {
285 boolean splitConnection(final Point2D startingPos, Point2D currentPos) {
286 final IDiagram diagram = ElementUtils.peekDiagram(edge);
289 final EdgeResource segment = (EdgeResource) ElementUtils.getObject(edge);
293 Point2D snapPos = new Point2D.Double(startingPos.getX(), startingPos.getY());
294 ISnapAdvisor snap = getHint(DiagramHints.SNAP_ADVISOR);
298 final AffineTransform splitPos = AffineTransform.getTranslateInstance(snapPos.getX(), snapPos.getY());
299 final AtomicReference<Resource> newBp = new AtomicReference<Resource>();
302 SimanticsUI.getSession().syncRequest(new WriteRequest() {
304 public void perform(WriteGraph graph) throws DatabaseException {
305 DiagramResource DIA = DiagramResource.getInstance(graph);
307 // Split the edge with a new branch point
308 ConnectionUtil cu = new ConnectionUtil(graph);
309 Resource bp = cu.split(segment, splitPos);
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())
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);
330 dragData.set(new DragData(Collections.singleton(newBp.get()), startingPos, currentPos));
332 } catch (DatabaseException e) {
333 ErrorLogger.defaultLogError(e);
339 static class DragData {
341 Point2D startingPoint;
342 Point2D currentPoint;
343 public DragData(Set<?> data, Point2D startingPoint, Point2D currentPoint) {
345 this.startingPoint = startingPoint;
346 this.currentPoint = currentPoint;
350 private final AtomicReference<DragData> dragData = new AtomicReference<DragData>();
352 IHintListener diagramHintListener = new HintListenerAdapter() {
354 public void hintChanged(IHintObservable sender, Key key, Object oldValue, Object newValue) {
357 if (key == DiagramModelHints.KEY_DIAGRAM_CONTENTS_UPDATED) {
358 final DragData data = dragData.getAndSet(null);
360 asyncExec(new Runnable() {
366 setDiagramSelectionToData(data);
373 private void setDiagramSelectionToData(final DragData data) {
374 DataElementMap dem = diagram.getDiagramClass().getAtMostOneItemOfClass(DataElementMap.class);
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);
384 if (!newSelection.isEmpty()) {
385 sel.setSelection(0, newSelection);
386 getContext().add( new TranslateMode(data.startingPoint, data.currentPoint, getMouseId(), newSelection) );