/******************************************************************************* * Copyright (c) 2007, 2010 Association for Decentralized Information Management * in Industry THTH ry. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * VTT Technical Research Centre of Finland - initial API and implementation *******************************************************************************/ package org.simantics.diagram.participant; import static org.simantics.g2d.diagram.handler.PickRequest.PickFilter.FILTER_CONNECTIONS; import static org.simantics.g2d.diagram.handler.PickRequest.PickFilter.FILTER_CONNECTION_EDGES; import static org.simantics.g2d.diagram.handler.PickRequest.PickFilter.FILTER_NODES; import java.awt.Shape; import java.awt.geom.AffineTransform; import java.awt.geom.Line2D; import java.awt.geom.Point2D; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Set; import java.util.concurrent.atomic.AtomicReference; import org.simantics.Simantics; import org.simantics.db.Resource; import org.simantics.db.WriteGraph; import org.simantics.db.common.request.WriteRequest; import org.simantics.db.exception.DatabaseException; import org.simantics.diagram.content.ConnectionUtil; import org.simantics.diagram.content.EdgeResource; import org.simantics.diagram.stubs.DiagramResource; import org.simantics.diagram.ui.DiagramModelHints; import org.simantics.g2d.canvas.ICanvasContext; import org.simantics.g2d.canvas.impl.DependencyReflection.Dependency; import org.simantics.g2d.diagram.DiagramHints; import org.simantics.g2d.diagram.IDiagram; import org.simantics.g2d.diagram.handler.DataElementMap; import org.simantics.g2d.diagram.handler.PickContext; import org.simantics.g2d.diagram.handler.PickRequest; import org.simantics.g2d.diagram.handler.PickRequest.PickPolicy; import org.simantics.g2d.diagram.handler.PickRequest.PickSorter; import org.simantics.g2d.diagram.participant.AbstractDiagramParticipant; import org.simantics.g2d.diagram.participant.Selection; import org.simantics.g2d.diagram.participant.pointertool.AbstractMode; import org.simantics.g2d.diagram.participant.pointertool.PointerInteractor; import org.simantics.g2d.diagram.participant.pointertool.TranslateMode; import org.simantics.g2d.element.ElementUtils; import org.simantics.g2d.element.IElement; import org.simantics.g2d.element.handler.Children; import org.simantics.g2d.participant.TransformUtil; import org.simantics.g2d.participant.WorkbenchStatusLine; import org.simantics.scenegraph.g2d.events.EventHandlerReflection.EventHandler; import org.simantics.scenegraph.g2d.events.MouseEvent; import org.simantics.scenegraph.g2d.events.MouseEvent.MouseButtonPressedEvent; import org.simantics.scenegraph.g2d.events.MouseEvent.MouseButtonReleasedEvent; import org.simantics.scenegraph.g2d.events.MouseEvent.MouseDragBegin; import org.simantics.scenegraph.g2d.events.MouseEvent.MouseMovedEvent; import org.simantics.scenegraph.g2d.snap.ISnapAdvisor; import org.simantics.utils.datastructures.hints.HintListenerAdapter; import org.simantics.utils.datastructures.hints.IHintContext.Key; import org.simantics.utils.datastructures.hints.IHintListener; import org.simantics.utils.datastructures.hints.IHintObservable; import org.simantics.utils.ui.ErrorLogger; /** * @author Tuukka Lehtonen */ public class ConnectionEditingSupport extends AbstractDiagramParticipant { private static final boolean DEBUG = false; private static final int TOOL_PRIORITY = 100; @Dependency PointerInteractor pi; @Dependency PickContext pickContext; @Dependency Selection selection; @Dependency WorkbenchStatusLine statusLine; private static final PickSorter NODES_LAST = new PickSorter() { @Override public void sort(List elements) { Collections.sort(elements, new Comparator() { @Override public int compare(IElement e1, IElement e2) { boolean is1 = FILTER_NODES.accept(e1); boolean is2 = FILTER_NODES.accept(e2); if (!is1 && is2) return -1; if (is1 && !is2) return 1; return 0; } }); } }; private boolean routePointsEnabled() { return Boolean.TRUE.equals(diagram.getHint(DiagramHints.KEY_ALLOW_ROUTE_POINTS)); } @EventHandler(priority = TOOL_PRIORITY) public boolean handleMouse(MouseEvent e) { if (!routePointsEnabled()) return false; if (e instanceof MouseButtonPressedEvent) return handlePress((MouseButtonPressedEvent) e); return false; } private boolean handlePress(MouseButtonPressedEvent me) { if (me.button != MouseEvent.LEFT_BUTTON || me.hasAnyModifier(MouseEvent.ALL_MODIFIERS_MASK)) return false; //System.out.println("button pressed: " + me); Shape shape = pi.getCanvasPickShape(me.controlPosition); if (shape == null) return false; PickRequest req = new PickRequest(shape).context(getContext()); req.pickPolicy = PickPolicy.PICK_INTERSECTING_OBJECTS; req.pickFilter = null; req.pickSorter = NODES_LAST; List pick = new ArrayList(); pickContext.pick(diagram, req, pick); if (pick.isEmpty()) return false; //System.out.println("selection pick returns " + pick); // If current mouse selection contains only a connection edge or // complete connection and pick result contains the same connection or // edge as the first hit, start dragging new route point. Set sel = selection.getSelection(me.mouseId); if (!Collections.disjoint(pick, sel)) { if (sel.size() == 1) { IElement e = sel.iterator().next(); if (FILTER_CONNECTIONS.accept(e) || FILTER_CONNECTION_EDGES.accept(e)) { IElement edge = findSingleConnectionEdge(pick, e); if (edge != null) { getContext().add(new ConnectionRoutingMode(me.mouseId, edge)); return true; } } } return false; } IElement edge = findSingleConnectionEdge(pick, null); if (edge != null) { getContext().add(new ConnectionRoutingMode(me.mouseId, edge)); return true; } return false; } /** * @param pick * @param selected * @return */ private IElement findSingleConnectionEdge(List pick, IElement selected) { for (int i = pick.size() - 1; i >= 0; --i) { IElement p = pick.get(i); boolean pickedSelected = selected == null ? true : p == selected; if (FILTER_NODES.accept(p)) return null; if (pickedSelected && FILTER_CONNECTION_EDGES.accept(p)) { return p; } } for (int i = pick.size() - 1; i >= 0; --i) { IElement p = pick.get(i); boolean pickedSelected = selected == null ? true : p == selected; if (FILTER_CONNECTIONS.accept(p)) { Children ch = p.getElementClass().getAtMostOneItemOfClass(Children.class); if (ch == null) return null; Collection children = ch.getChildren(p, null); int childCount = children.size(); if (childCount == 1) { for (IElement child : children) { if (pickedSelected && FILTER_CONNECTION_EDGES.accept(child)) return child; } } else if (childCount > 1) { for (IElement child : children) { if (pickedSelected && FILTER_CONNECTION_EDGES.accept(child) && pick.contains(child)) return child; } } } } return null; } static class ConnectionRoutingMode extends AbstractMode { @Dependency TransformUtil tr; @Dependency Selection sel; private boolean dragging; private final IElement edge; public ConnectionRoutingMode(int mouseId, IElement edge) { super(mouseId); if (DEBUG) System.out.println("Start routing mode (" + mouseId + ")"); this.edge = edge; } @Override public void addedToContext(ICanvasContext ctx) { super.addedToContext(ctx); if (DEBUG) System.out.println(this + " added"); } @Override public void removedFromContext(ICanvasContext ctx) { if (DEBUG) System.out.println(this + " removed"); super.removedFromContext(ctx); } @Override protected void onDiagramSet(IDiagram newDiagram, IDiagram oldDiagram) { if (oldDiagram != null) { oldDiagram.removeKeyHintListener(DiagramModelHints.KEY_DIAGRAM_CONTENTS_UPDATED, diagramHintListener); } if (newDiagram != null) { newDiagram.addKeyHintListener(DiagramModelHints.KEY_DIAGRAM_CONTENTS_UPDATED, diagramHintListener); } } @EventHandler(priority = TOOL_PRIORITY + 10) public boolean handleMouse(MouseEvent e) { if (!isModeMouse(e)) return false; //System.out.println("mouse event: " + e); if (e instanceof MouseMovedEvent) return handleMove((MouseMovedEvent) e); if (e instanceof MouseDragBegin) return handleDrag((MouseDragBegin) e); if (e instanceof MouseButtonReleasedEvent) return handleRelease((MouseButtonReleasedEvent) e); // Ignore all other events return true; } private boolean handleDrag(MouseDragBegin e) { // Mark dragging as started. dragging = true; splitConnection(e.startCanvasPos, tr.controlToCanvas(e.controlPosition, null)); return true; } private boolean handleMove(MouseMovedEvent e) { if (!dragging) return true; if (DEBUG) System.out.println("routing move: " + e); return false; } private boolean handleRelease(MouseButtonReleasedEvent e) { // setDirty(); remove(); return false; } boolean splitConnection(final Point2D startingPos, Point2D currentPos) { final IDiagram diagram = ElementUtils.peekDiagram(edge); if (diagram == null) return false; final EdgeResource segment = (EdgeResource) ElementUtils.getObject(edge); if (segment == null) return false; Point2D snapPos = new Point2D.Double(startingPos.getX(), startingPos.getY()); ISnapAdvisor snap = getHint(DiagramHints.SNAP_ADVISOR); if (snap != null) snap.snap(snapPos); final AffineTransform splitPos = AffineTransform.getTranslateInstance(snapPos.getX(), snapPos.getY()); final AtomicReference newBp = new AtomicReference(); try { Simantics.getSession().syncRequest(new WriteRequest() { @Override public void perform(WriteGraph graph) throws DatabaseException { DiagramResource DIA = DiagramResource.getInstance(graph); // Split the edge with a new branch point ConnectionUtil cu = new ConnectionUtil(graph); Resource bp = cu.split(segment, splitPos); Line2D nearestLine = ConnectionUtil.resolveNearestEdgeLineSegment(startingPos, edge); if (nearestLine != null) { double angle = Math.atan2( Math.abs(nearestLine.getY2() - nearestLine.getY1()), Math.abs(nearestLine.getX2() - nearestLine.getX1()) ); if (angle >= 0 && angle < Math.PI / 4) { graph.claim(bp, DIA.Horizontal, bp); } else if (angle > Math.PI / 4 && angle <= Math.PI / 2) { graph.claim(bp, DIA.Vertical, bp); } } newBp.set(bp); } }); dragData.set(new DragData(Collections.singleton(newBp.get()), startingPos, currentPos)); } catch (DatabaseException e) { ErrorLogger.defaultLogError(e); } return false; } static class DragData { Set data; Point2D startingPoint; Point2D currentPoint; public DragData(Set data, Point2D startingPoint, Point2D currentPoint) { this.data = data; this.startingPoint = startingPoint; this.currentPoint = currentPoint; } } private final AtomicReference dragData = new AtomicReference(); IHintListener diagramHintListener = new HintListenerAdapter() { @Override public void hintChanged(IHintObservable sender, Key key, Object oldValue, Object newValue) { if (isRemoved()) return; if (key == DiagramModelHints.KEY_DIAGRAM_CONTENTS_UPDATED) { final DragData data = dragData.getAndSet(null); if (data != null) { asyncExec(new Runnable() { @Override public void run() { // Safety first. if (isRemoved()) return; setDiagramSelectionToData(data); } }); } } } private void setDiagramSelectionToData(final DragData data) { DataElementMap dem = diagram.getDiagramClass().getAtMostOneItemOfClass(DataElementMap.class); if (dem != null) { final Collection newSelection = new ArrayList(data.data.size()); for (Object datum : data.data) { IElement element = dem.getElement(diagram, datum); if (element != null) { newSelection.add(element); } } if (!newSelection.isEmpty()) { sel.setSelection(0, newSelection); getContext().add( new TranslateMode(data.startingPoint, data.currentPoint, getMouseId(), newSelection) ); remove(); } } } }; } }