X-Git-Url: https://gerrit.simantics.org/r/gitweb?a=blobdiff_plain;f=bundles%2Forg.simantics.g2d%2Fsrc%2Forg%2Fsimantics%2Fg2d%2Fdiagram%2Fparticipant%2Fpointertool%2FTranslateMode.java;h=814afe5195af48d065cec44d011268592fdea7b2;hb=72542227673047d71c3c3f281e478c1bea82eb81;hp=fee686f26ce1ea301a88ccaf1aabfbe36cc61133;hpb=969bd23cab98a79ca9101af33334000879fb60c5;p=simantics%2Fplatform.git diff --git a/bundles/org.simantics.g2d/src/org/simantics/g2d/diagram/participant/pointertool/TranslateMode.java b/bundles/org.simantics.g2d/src/org/simantics/g2d/diagram/participant/pointertool/TranslateMode.java index fee686f26..814afe519 100644 --- a/bundles/org.simantics.g2d/src/org/simantics/g2d/diagram/participant/pointertool/TranslateMode.java +++ b/bundles/org.simantics.g2d/src/org/simantics/g2d/diagram/participant/pointertool/TranslateMode.java @@ -1,492 +1,492 @@ -/******************************************************************************* - * 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.g2d.diagram.participant.pointertool; - -import java.awt.AlphaComposite; -import java.awt.Cursor; -import java.awt.geom.AffineTransform; -import java.awt.geom.Point2D; -import java.util.ArrayDeque; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashSet; -import java.util.Queue; -import java.util.Set; -import java.util.UUID; - -import org.simantics.g2d.canvas.Hints; -import org.simantics.g2d.canvas.ICanvasContext; -import org.simantics.g2d.canvas.IMouseCursorContext; -import org.simantics.g2d.canvas.IMouseCursorHandle; -import org.simantics.g2d.canvas.impl.DependencyReflection.Dependency; -import org.simantics.g2d.canvas.impl.DependencyReflection.Reference; -import org.simantics.g2d.canvas.impl.SGNodeReflection.SGCleanup; -import org.simantics.g2d.canvas.impl.SGNodeReflection.SGInit; -import org.simantics.g2d.connection.ConnectionEntity; -import org.simantics.g2d.connection.handler.ConnectionHandler; -import org.simantics.g2d.diagram.DiagramHints; -import org.simantics.g2d.diagram.DiagramUtils; -import org.simantics.g2d.diagram.IDiagram; -import org.simantics.g2d.diagram.handler.Relationship; -import org.simantics.g2d.diagram.handler.RelationshipHandler; -import org.simantics.g2d.diagram.handler.RelationshipHandler.Relation; -import org.simantics.g2d.diagram.handler.Topology; -import org.simantics.g2d.diagram.handler.Topology.Connection; -import org.simantics.g2d.diagram.handler.Topology.Terminal; -import org.simantics.g2d.element.ElementClass; -import org.simantics.g2d.element.ElementHints; -import org.simantics.g2d.element.ElementUtils; -import org.simantics.g2d.element.IElement; -import org.simantics.g2d.element.handler.Move; -import org.simantics.g2d.element.handler.TerminalTopology; -import org.simantics.g2d.participant.RenderingQualityInteractor; -import org.simantics.g2d.participant.TransformUtil; -import org.simantics.scenegraph.ILookupService; -import org.simantics.scenegraph.Node; -import org.simantics.scenegraph.g2d.G2DParentNode; -import org.simantics.scenegraph.g2d.events.Event; -import org.simantics.scenegraph.g2d.events.EventHandlerReflection.EventHandler; -import org.simantics.scenegraph.g2d.events.MouseEvent; -import org.simantics.scenegraph.g2d.events.MouseEvent.MouseButtonEvent; -import org.simantics.scenegraph.g2d.events.MouseEvent.MouseButtonReleasedEvent; -import org.simantics.scenegraph.g2d.events.MouseEvent.MouseMovedEvent; -import org.simantics.scenegraph.g2d.events.command.CommandEvent; -import org.simantics.scenegraph.g2d.events.command.Commands; -import org.simantics.scenegraph.g2d.nodes.LinkNode; -import org.simantics.scenegraph.g2d.nodes.SingleElementNode; -import org.simantics.scenegraph.g2d.snap.ISnapAdvisor; -import org.simantics.scenegraph.utils.NodeUtil; -import org.simantics.scenegraph.utils.Quality; -import org.simantics.utils.ObjectUtils; -import org.simantics.utils.logging.TimeLogger; - -/** - * This participant handles the diagram in translate elements mode. - * - * @author Toni Kalajainen - * @author Tuukka Lehtonen - */ -public class TranslateMode extends AbstractMode { - - @Reference - protected RenderingQualityInteractor quality; - - @Dependency - protected TransformUtil util; - - protected Point2D startingPoint; - protected Point2D currentPoint; - protected IMouseCursorHandle cursor; - - protected Collection elementsToTranslate = Collections.emptyList(); - protected Collection elementsToReallyTranslate = Collections.emptyList(); - protected Collection translatedConnections = Collections.emptyList(); - - /** - * A set of elements gathered during - * {@link #getElementsToReallyTranslate(IDiagram, Collection)} that is to be - * marked dirty after the translation operation has been completed. - * - *

- * This exists to cover cases where indirectly related (not topologically) - * elements are not properly updated after translation operations. - */ - protected Collection elementsToDirty = new HashSet(); - - /** - * The node under which the mutated diagram is ghosted in the scene graph. - */ - protected G2DParentNode parent; - - /** - * This stays null until the translated diagram parts have been initialized - * into the scene graph. After that, only the translations of the nodes in - * the scene graph are modified. - */ - protected SingleElementNode node = null; - - protected double dx; - protected double dy; - - public TranslateMode(Point2D startingPoint, Point2D currentPoint, int mouseId, Collection elements) { - super(mouseId); - if (startingPoint == null) - throw new NullPointerException("null startingPoint"); - if (currentPoint == null) - throw new NullPointerException("null currentPoint"); - if (elements == null) - throw new NullPointerException("null elements"); - - this.startingPoint = startingPoint; - this.currentPoint = currentPoint; - this.elementsToTranslate = elements; - } - - @Override - protected void onDiagramSet(IDiagram newDiagram, IDiagram oldDiagram) { - if (newDiagram != null) { -// for (IElement e : elementsToTranslate) { -// System.out.println("element: " + e); -// } - processTranslatedSelection(newDiagram, elementsToTranslate); -// for (IElement e : elementsToReallyTranslate) { -// System.out.println("REAL element: " + e); -// } - - int i = 0; - ILookupService lookup = NodeUtil.getLookupService(node); - for (IElement e : elementsToReallyTranslate) { - Node n = e.getHint(ElementHints.KEY_SG_NODE); - if (n != null) { - LinkNode link = node.addNode("" + (i), LinkNode.class); - link.setZIndex(i); - - String id = lookup.lookupId(n); - if (id == null) { - id = UUID.randomUUID().toString(); - lookup.map(id, n); - link.setLookupIdOwner(true); - } - link.setDelegateId(id); - - ++i; - } - } - } - } - - protected void processTranslatedSelection(IDiagram diagram, Collection elementsToTranslate) { - // Only translate elements that are not parents of another elements that - // is in the translated set also. Otherwise we would end up doing double - // translation for the parented elements. - - // Don't move "connections only" selections. - int connectionCount = 0; - for (IElement e : elementsToTranslate) { - if (e.getElementClass().containsClass(ConnectionHandler.class)) { - ++connectionCount; - } - } - if (connectionCount == elementsToTranslate.size()) - return; - - elementsToReallyTranslate = new HashSet(elementsToTranslate); - translatedConnections = new HashSet(); - - // 1st: find out if some elements should not be translated - // because their parent elements are being translated also. - // - // Post-invariants: - // * elementsToReallyTranslate does not contain any elements whose parents are also translated - // * elementsToDirty contains all elements whose parents are also translated - RelationshipHandler erh = diagram.getDiagramClass().getAtMostOneItemOfClass(RelationshipHandler.class); - if (erh != null) { - Queue todo = new ArrayDeque(elementsToTranslate); - Set visited = new HashSet(); - Collection relations = new ArrayList(); - while (!todo.isEmpty()) { - Object e = todo.poll(); - if (!visited.add(e)) - continue; - - // Check PARENT_OF relationships - relations.clear(); - erh.getRelations(diagram, e, relations); - for (Relation r : relations) { - if (Relationship.PARENT_OF.equals(r.getRelationship())) { - elementsToReallyTranslate.remove(r.getObject()); - if (r.getObject() instanceof IElement) - elementsToDirty.add((IElement) r.getObject()); - if (!visited.contains(r.getObject())) - todo.add(r.getObject()); - } - } - - if (e instanceof IElement) { - IElement el = (IElement) e; - - // Do not try to translate non-moveable elements. - if (!el.getElementClass().containsClass(Move.class)) { - elementsToReallyTranslate.remove(el); - continue; - } - - // Check Parent handler - Collection parents = ElementUtils.getParents(el); - boolean parentIsTranslated = false; - for (IElement parent : parents) { - if (elementsToTranslate.contains(parent)) { - parentIsTranslated = true; - break; - } - } - if (parentIsTranslated) { - elementsToReallyTranslate.remove(el); - elementsToDirty.add(el); - } - } - } - } - - // 2nd: Include those connections in the translation for which all - // all terminal connected elements are also included in the selection. - - Collection terminals = new ArrayList(); - Collection connections = new ArrayList(); - Collection connections2 = new ArrayList(); - - Topology topology = diagram.getDiagramClass().getSingleItem(Topology.class); - for (IElement el : new ArrayList(elementsToReallyTranslate)) { - // Check if the selection is a connection. - // If it is, translate all of its branchpoints too, - // but only if all of its terminal connected elements - // are about to be translated too. - ElementClass ec = el.getElementClass(); - TerminalTopology tt = ec.getAtMostOneItemOfClass(TerminalTopology.class); - if (tt != null) { - terminals.clear(); - tt.getTerminals(el, terminals); - for (Terminal terminal : terminals) { - topology.getConnections(el, terminal, connections); - for (Connection conn : connections) { - ConnectionEntity ce = conn.edge.getHint(ElementHints.KEY_CONNECTION_ENTITY); - if (ce == null) - continue; - IElement connection = ce.getConnection(); - if (connection == null) - continue; - ConnectionHandler ch = connection.getElementClass().getAtMostOneItemOfClass(ConnectionHandler.class); - if (ch == null) - continue; - - connections2.clear(); - ch.getTerminalConnections(connection, connections2); - - boolean allConnectedNodesSelected = true; - for (Connection conn2 : connections2) { - if (!elementsToReallyTranslate.contains(conn2.node)) { - allConnectedNodesSelected = false; - break; - } - } - - if (allConnectedNodesSelected) { - // Finally! It seems like all nodes connected - // with 'connection' are about to be translated. - // We should also include the whole connection - // in the translation. - elementsToReallyTranslate.add(connection); - translatedConnections.add(connection); - } - } - } - } - } - - // Include all non-selected branch points in the translation that - // are either: - // a) among connections that have been either selected - // b) exist in a connection whose all terminal connected - // elements are selected. - - for (IElement el : new ArrayList(elementsToReallyTranslate)) { - // Check if the selection is a connection. - // If it is, translate all of its branchpoints too, - // but only if all of its terminal connected elements - // are about to be translated too. - ConnectionHandler ch = el.getElementClass().getAtMostOneItemOfClass(ConnectionHandler.class); - if (ch != null) { - boolean anyTerminalConnectionsSelected = false; - Collection terminalConnections = new ArrayList(); - ch.getTerminalConnections(el, terminalConnections); - for (Connection conn : terminalConnections) { - if (elementsToTranslate.contains(conn.node)) { - anyTerminalConnectionsSelected = true; - break; - } - } - - if (anyTerminalConnectionsSelected) { - translatedConnections.add(el); - Collection branchPoints = new ArrayList(); - ch.getBranchPoints(el, branchPoints); - elementsToReallyTranslate.addAll(branchPoints); - } - } - } - } - - @Override - public void addedToContext(ICanvasContext ctx) { - super.addedToContext(ctx); - if (quality != null) - quality.setStaticQuality(Quality.LOW); - IMouseCursorContext mcc = getContext().getMouseCursorContext(); - cursor = mcc == null ? null : mcc.setCursor(mouseId, new Cursor(Cursor.MOVE_CURSOR)); - } - - @Override - public void removedFromContext(ICanvasContext ctx) { - if (cursor != null) { - cursor.remove(); - cursor = null; - } - if (quality != null) - quality.setStaticQuality(null); - super.removedFromContext(ctx); - } - - public AffineTransform getTransform() { - double dx = currentPoint.getX() - startingPoint.getX(); - double dy = currentPoint.getY() - startingPoint.getY(); - return AffineTransform.getTranslateInstance(dx, dy); - } - - /** - * return translate in control coordinate - * @return - */ - public Point2D getTranslateVector() - { - double dx = currentPoint.getX() - startingPoint.getX(); - double dy = currentPoint.getY() - startingPoint.getY(); - return new Point2D.Double(dx, dy); - } - - @EventHandler(priority = 30) - public boolean handleEvent(Event e) { - // Reject all mouse events not for this mouse. - if (e instanceof MouseEvent) { - MouseEvent me = (MouseEvent) e; - if (me.mouseId != mouseId) - return false; - } - - if (e instanceof CommandEvent) { - CommandEvent event = (CommandEvent) e; - if (event.command.equals( Commands.CANCEL)) { - setDirty(); - remove(); - return true; - } else if (event.command.equals( Commands.ROTATE_ELEMENT_CCW ) - || event.command.equals( Commands.DELETE ) - || event.command.equals( Commands.ROTATE_ELEMENT_CW ) - || event.command.equals( Commands.FLIP_ELEMENT_HORIZONTAL ) - || event.command.equals( Commands.FLIP_ELEMENT_VERTICAL) ) { - // Just eat these commands to disable - // rotation and scaling during translation. - return true; - } - } else if (e instanceof MouseMovedEvent) { - return move((MouseMovedEvent) e); - } else if (e instanceof MouseButtonReleasedEvent) { - if (((MouseButtonEvent)e).button == MouseEvent.LEFT_BUTTON) { - TimeLogger.resetTimeAndLog(getClass(), "handleEvent"); - return commit(); - } - } - return false; - } - - protected boolean move(MouseMovedEvent event) { - Point2D canvasPos = util.controlToCanvas(event.controlPosition, null); - if (ObjectUtils.objectEquals(currentPoint, canvasPos)) return true; - - ISnapAdvisor snapAdvisor = getHint(DiagramHints.SNAP_ADVISOR); - if (snapAdvisor != null) { - IElement someElement = null; - - for (IElement elem : elementsToReallyTranslate) { - someElement = elem; - break; - } - - if (someElement != null) { - AffineTransform at = node.getTransform(); - Move m = someElement.getElementClass().getSingleItem(Move.class); - Point2D oldPos = m.getPosition(someElement); - oldPos.setLocation(oldPos.getX() + at.getTranslateX(), oldPos.getY() + at.getTranslateY()); - - snapAdvisor.snap(canvasPos, - new Point2D[] { - new Point2D.Double( - -oldPos.getX() + currentPoint.getX(), - -oldPos.getY() + currentPoint.getY() - ) - }); - } - } - - dx = canvasPos.getX()-startingPoint.getX(); - dy = canvasPos.getY()-startingPoint.getY(); - if ((event.stateMask & MouseEvent.CTRL_MASK) != 0) { - // Forced horizontal/vertical -only movement - if (Math.abs(dx) >= Math.abs(dy)) { - dy = 0; - } else { - dx = 0; - } - } - - AffineTransform nat = AffineTransform.getTranslateInstance(dx, dy); - node.setTransform(nat); - - currentPoint = canvasPos; - - setDirty(); - return true; - } - - protected boolean commit() { - TimeLogger.resetTimeAndLog(getClass(), "commit"); - for (IElement el : elementsToReallyTranslate) { - Move move = el.getElementClass().getAtMostOneItemOfClass(Move.class); - if (move != null) { - Point2D oldPos = move.getPosition(el); - move.moveTo(el, oldPos.getX() + dx, oldPos.getY() + dy); - } - } - - // Persist all translation modifications. - DiagramUtils.mutateDiagram(diagram, m -> { - for (IElement e : elementsToReallyTranslate) - m.modifyTransform(e); - }); - - for (IElement dirty : elementsToDirty) - dirty.setHint(Hints.KEY_DIRTY, Hints.VALUE_SG_DIRTY); - - setDirty(); - remove(); - return false; - } - - @SGInit - public void initSG(G2DParentNode parent) { - this.parent = parent; - node = parent.addNode("translate ghost", SingleElementNode.class); - node.setZIndex(1000); - node.setVisible(Boolean.TRUE); - node.setComposite(AlphaComposite.SrcOver.derive(0.4f)); - } - - @SGCleanup - public void cleanupSG() { - if (node != null) { - node.remove(); - node = null; - } - parent = null; - } - -} +/******************************************************************************* + * 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.g2d.diagram.participant.pointertool; + +import java.awt.AlphaComposite; +import java.awt.Cursor; +import java.awt.geom.AffineTransform; +import java.awt.geom.Point2D; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Queue; +import java.util.Set; +import java.util.UUID; + +import org.simantics.g2d.canvas.Hints; +import org.simantics.g2d.canvas.ICanvasContext; +import org.simantics.g2d.canvas.IMouseCursorContext; +import org.simantics.g2d.canvas.IMouseCursorHandle; +import org.simantics.g2d.canvas.impl.DependencyReflection.Dependency; +import org.simantics.g2d.canvas.impl.DependencyReflection.Reference; +import org.simantics.g2d.canvas.impl.SGNodeReflection.SGCleanup; +import org.simantics.g2d.canvas.impl.SGNodeReflection.SGInit; +import org.simantics.g2d.connection.ConnectionEntity; +import org.simantics.g2d.connection.handler.ConnectionHandler; +import org.simantics.g2d.diagram.DiagramHints; +import org.simantics.g2d.diagram.DiagramUtils; +import org.simantics.g2d.diagram.IDiagram; +import org.simantics.g2d.diagram.handler.Relationship; +import org.simantics.g2d.diagram.handler.RelationshipHandler; +import org.simantics.g2d.diagram.handler.RelationshipHandler.Relation; +import org.simantics.g2d.diagram.handler.Topology; +import org.simantics.g2d.diagram.handler.Topology.Connection; +import org.simantics.g2d.diagram.handler.Topology.Terminal; +import org.simantics.g2d.element.ElementClass; +import org.simantics.g2d.element.ElementHints; +import org.simantics.g2d.element.ElementUtils; +import org.simantics.g2d.element.IElement; +import org.simantics.g2d.element.handler.Move; +import org.simantics.g2d.element.handler.TerminalTopology; +import org.simantics.g2d.participant.RenderingQualityInteractor; +import org.simantics.g2d.participant.TransformUtil; +import org.simantics.scenegraph.ILookupService; +import org.simantics.scenegraph.Node; +import org.simantics.scenegraph.g2d.G2DParentNode; +import org.simantics.scenegraph.g2d.events.Event; +import org.simantics.scenegraph.g2d.events.EventHandlerReflection.EventHandler; +import org.simantics.scenegraph.g2d.events.MouseEvent; +import org.simantics.scenegraph.g2d.events.MouseEvent.MouseButtonEvent; +import org.simantics.scenegraph.g2d.events.MouseEvent.MouseButtonReleasedEvent; +import org.simantics.scenegraph.g2d.events.MouseEvent.MouseMovedEvent; +import org.simantics.scenegraph.g2d.events.command.CommandEvent; +import org.simantics.scenegraph.g2d.events.command.Commands; +import org.simantics.scenegraph.g2d.nodes.LinkNode; +import org.simantics.scenegraph.g2d.nodes.SingleElementNode; +import org.simantics.scenegraph.g2d.snap.ISnapAdvisor; +import org.simantics.scenegraph.utils.NodeUtil; +import org.simantics.scenegraph.utils.Quality; +import org.simantics.utils.ObjectUtils; +import org.simantics.utils.logging.TimeLogger; + +/** + * This participant handles the diagram in translate elements mode. + * + * @author Toni Kalajainen + * @author Tuukka Lehtonen + */ +public class TranslateMode extends AbstractMode { + + @Reference + protected RenderingQualityInteractor quality; + + @Dependency + protected TransformUtil util; + + protected Point2D startingPoint; + protected Point2D currentPoint; + protected IMouseCursorHandle cursor; + + protected Collection elementsToTranslate = Collections.emptyList(); + protected Collection elementsToReallyTranslate = Collections.emptyList(); + protected Collection translatedConnections = Collections.emptyList(); + + /** + * A set of elements gathered during + * {@link #getElementsToReallyTranslate(IDiagram, Collection)} that is to be + * marked dirty after the translation operation has been completed. + * + *

+ * This exists to cover cases where indirectly related (not topologically) + * elements are not properly updated after translation operations. + */ + protected Collection elementsToDirty = new HashSet(); + + /** + * The node under which the mutated diagram is ghosted in the scene graph. + */ + protected G2DParentNode parent; + + /** + * This stays null until the translated diagram parts have been initialized + * into the scene graph. After that, only the translations of the nodes in + * the scene graph are modified. + */ + protected SingleElementNode node = null; + + protected double dx; + protected double dy; + + public TranslateMode(Point2D startingPoint, Point2D currentPoint, int mouseId, Collection elements) { + super(mouseId); + if (startingPoint == null) + throw new NullPointerException("null startingPoint"); + if (currentPoint == null) + throw new NullPointerException("null currentPoint"); + if (elements == null) + throw new NullPointerException("null elements"); + + this.startingPoint = startingPoint; + this.currentPoint = currentPoint; + this.elementsToTranslate = elements; + } + + @Override + protected void onDiagramSet(IDiagram newDiagram, IDiagram oldDiagram) { + if (newDiagram != null) { +// for (IElement e : elementsToTranslate) { +// System.out.println("element: " + e); +// } + processTranslatedSelection(newDiagram, elementsToTranslate); +// for (IElement e : elementsToReallyTranslate) { +// System.out.println("REAL element: " + e); +// } + + int i = 0; + ILookupService lookup = NodeUtil.getLookupService(node); + for (IElement e : elementsToReallyTranslate) { + Node n = e.getHint(ElementHints.KEY_SG_NODE); + if (n != null) { + LinkNode link = node.addNode("" + (i), LinkNode.class); + link.setZIndex(i); + + String id = lookup.lookupId(n); + if (id == null) { + id = UUID.randomUUID().toString(); + lookup.map(id, n); + link.setLookupIdOwner(true); + } + link.setDelegateId(id); + + ++i; + } + } + } + } + + protected void processTranslatedSelection(IDiagram diagram, Collection elementsToTranslate) { + // Only translate elements that are not parents of another elements that + // is in the translated set also. Otherwise we would end up doing double + // translation for the parented elements. + + // Don't move "connections only" selections. + int connectionCount = 0; + for (IElement e : elementsToTranslate) { + if (e.getElementClass().containsClass(ConnectionHandler.class)) { + ++connectionCount; + } + } + if (connectionCount == elementsToTranslate.size()) + return; + + elementsToReallyTranslate = new HashSet(elementsToTranslate); + translatedConnections = new HashSet(); + + // 1st: find out if some elements should not be translated + // because their parent elements are being translated also. + // + // Post-invariants: + // * elementsToReallyTranslate does not contain any elements whose parents are also translated + // * elementsToDirty contains all elements whose parents are also translated + RelationshipHandler erh = diagram.getDiagramClass().getAtMostOneItemOfClass(RelationshipHandler.class); + if (erh != null) { + Queue todo = new ArrayDeque(elementsToTranslate); + Set visited = new HashSet(); + Collection relations = new ArrayList(); + while (!todo.isEmpty()) { + Object e = todo.poll(); + if (!visited.add(e)) + continue; + + // Check PARENT_OF relationships + relations.clear(); + erh.getRelations(diagram, e, relations); + for (Relation r : relations) { + if (Relationship.PARENT_OF.equals(r.getRelationship())) { + elementsToReallyTranslate.remove(r.getObject()); + if (r.getObject() instanceof IElement) + elementsToDirty.add((IElement) r.getObject()); + if (!visited.contains(r.getObject())) + todo.add(r.getObject()); + } + } + + if (e instanceof IElement) { + IElement el = (IElement) e; + + // Do not try to translate non-moveable elements. + if (!el.getElementClass().containsClass(Move.class)) { + elementsToReallyTranslate.remove(el); + continue; + } + + // Check Parent handler + Collection parents = ElementUtils.getParents(el); + boolean parentIsTranslated = false; + for (IElement parent : parents) { + if (elementsToTranslate.contains(parent)) { + parentIsTranslated = true; + break; + } + } + if (parentIsTranslated) { + elementsToReallyTranslate.remove(el); + elementsToDirty.add(el); + } + } + } + } + + // 2nd: Include those connections in the translation for which all + // all terminal connected elements are also included in the selection. + + Collection terminals = new ArrayList(); + Collection connections = new ArrayList(); + Collection connections2 = new ArrayList(); + + Topology topology = diagram.getDiagramClass().getSingleItem(Topology.class); + for (IElement el : new ArrayList(elementsToReallyTranslate)) { + // Check if the selection is a connection. + // If it is, translate all of its branchpoints too, + // but only if all of its terminal connected elements + // are about to be translated too. + ElementClass ec = el.getElementClass(); + TerminalTopology tt = ec.getAtMostOneItemOfClass(TerminalTopology.class); + if (tt != null) { + terminals.clear(); + tt.getTerminals(el, terminals); + for (Terminal terminal : terminals) { + topology.getConnections(el, terminal, connections); + for (Connection conn : connections) { + ConnectionEntity ce = conn.edge.getHint(ElementHints.KEY_CONNECTION_ENTITY); + if (ce == null) + continue; + IElement connection = ce.getConnection(); + if (connection == null) + continue; + ConnectionHandler ch = connection.getElementClass().getAtMostOneItemOfClass(ConnectionHandler.class); + if (ch == null) + continue; + + connections2.clear(); + ch.getTerminalConnections(connection, connections2); + + boolean allConnectedNodesSelected = true; + for (Connection conn2 : connections2) { + if (!elementsToReallyTranslate.contains(conn2.node)) { + allConnectedNodesSelected = false; + break; + } + } + + if (allConnectedNodesSelected) { + // Finally! It seems like all nodes connected + // with 'connection' are about to be translated. + // We should also include the whole connection + // in the translation. + elementsToReallyTranslate.add(connection); + translatedConnections.add(connection); + } + } + } + } + } + + // Include all non-selected branch points in the translation that + // are either: + // a) among connections that have been either selected + // b) exist in a connection whose all terminal connected + // elements are selected. + + for (IElement el : new ArrayList(elementsToReallyTranslate)) { + // Check if the selection is a connection. + // If it is, translate all of its branchpoints too, + // but only if all of its terminal connected elements + // are about to be translated too. + ConnectionHandler ch = el.getElementClass().getAtMostOneItemOfClass(ConnectionHandler.class); + if (ch != null) { + boolean anyTerminalConnectionsSelected = false; + Collection terminalConnections = new ArrayList(); + ch.getTerminalConnections(el, terminalConnections); + for (Connection conn : terminalConnections) { + if (elementsToTranslate.contains(conn.node)) { + anyTerminalConnectionsSelected = true; + break; + } + } + + if (anyTerminalConnectionsSelected) { + translatedConnections.add(el); + Collection branchPoints = new ArrayList(); + ch.getBranchPoints(el, branchPoints); + elementsToReallyTranslate.addAll(branchPoints); + } + } + } + } + + @Override + public void addedToContext(ICanvasContext ctx) { + super.addedToContext(ctx); + if (quality != null) + quality.setStaticQuality(Quality.LOW); + IMouseCursorContext mcc = getContext().getMouseCursorContext(); + cursor = mcc == null ? null : mcc.setCursor(mouseId, new Cursor(Cursor.MOVE_CURSOR)); + } + + @Override + public void removedFromContext(ICanvasContext ctx) { + if (cursor != null) { + cursor.remove(); + cursor = null; + } + if (quality != null) + quality.setStaticQuality(null); + super.removedFromContext(ctx); + } + + public AffineTransform getTransform() { + double dx = currentPoint.getX() - startingPoint.getX(); + double dy = currentPoint.getY() - startingPoint.getY(); + return AffineTransform.getTranslateInstance(dx, dy); + } + + /** + * return translate in control coordinate + * @return + */ + public Point2D getTranslateVector() + { + double dx = currentPoint.getX() - startingPoint.getX(); + double dy = currentPoint.getY() - startingPoint.getY(); + return new Point2D.Double(dx, dy); + } + + @EventHandler(priority = 30) + public boolean handleEvent(Event e) { + // Reject all mouse events not for this mouse. + if (e instanceof MouseEvent) { + MouseEvent me = (MouseEvent) e; + if (me.mouseId != mouseId) + return false; + } + + if (e instanceof CommandEvent) { + CommandEvent event = (CommandEvent) e; + if (event.command.equals( Commands.CANCEL)) { + setDirty(); + remove(); + return true; + } else if (event.command.equals( Commands.ROTATE_ELEMENT_CCW ) + || event.command.equals( Commands.DELETE ) + || event.command.equals( Commands.ROTATE_ELEMENT_CW ) + || event.command.equals( Commands.FLIP_ELEMENT_HORIZONTAL ) + || event.command.equals( Commands.FLIP_ELEMENT_VERTICAL) ) { + // Just eat these commands to disable + // rotation and scaling during translation. + return true; + } + } else if (e instanceof MouseMovedEvent) { + return move((MouseMovedEvent) e); + } else if (e instanceof MouseButtonReleasedEvent) { + if (((MouseButtonEvent)e).button == MouseEvent.LEFT_BUTTON) { + TimeLogger.resetTimeAndLog(getClass(), "handleEvent"); + return commit(); + } + } + return false; + } + + protected boolean move(MouseMovedEvent event) { + Point2D canvasPos = util.controlToCanvas(event.controlPosition, null); + if (ObjectUtils.objectEquals(currentPoint, canvasPos)) return true; + + ISnapAdvisor snapAdvisor = getHint(DiagramHints.SNAP_ADVISOR); + if (snapAdvisor != null) { + IElement someElement = null; + + for (IElement elem : elementsToReallyTranslate) { + someElement = elem; + break; + } + + if (someElement != null) { + AffineTransform at = node.getTransform(); + Move m = someElement.getElementClass().getSingleItem(Move.class); + Point2D oldPos = m.getPosition(someElement); + oldPos.setLocation(oldPos.getX() + at.getTranslateX(), oldPos.getY() + at.getTranslateY()); + + snapAdvisor.snap(canvasPos, + new Point2D[] { + new Point2D.Double( + -oldPos.getX() + currentPoint.getX(), + -oldPos.getY() + currentPoint.getY() + ) + }); + } + } + + dx = canvasPos.getX()-startingPoint.getX(); + dy = canvasPos.getY()-startingPoint.getY(); + if ((event.stateMask & MouseEvent.CTRL_MASK) != 0) { + // Forced horizontal/vertical -only movement + if (Math.abs(dx) >= Math.abs(dy)) { + dy = 0; + } else { + dx = 0; + } + } + + AffineTransform nat = AffineTransform.getTranslateInstance(dx, dy); + node.setTransform(nat); + + currentPoint = canvasPos; + + setDirty(); + return true; + } + + protected boolean commit() { + TimeLogger.resetTimeAndLog(getClass(), "commit"); + for (IElement el : elementsToReallyTranslate) { + Move move = el.getElementClass().getAtMostOneItemOfClass(Move.class); + if (move != null) { + Point2D oldPos = move.getPosition(el); + move.moveTo(el, oldPos.getX() + dx, oldPos.getY() + dy); + } + } + + // Persist all translation modifications. + DiagramUtils.mutateDiagram(diagram, m -> { + for (IElement e : elementsToReallyTranslate) + m.modifyTransform(e); + }); + + for (IElement dirty : elementsToDirty) + dirty.setHint(Hints.KEY_DIRTY, Hints.VALUE_SG_DIRTY); + + setDirty(); + remove(); + return false; + } + + @SGInit + public void initSG(G2DParentNode parent) { + this.parent = parent; + node = parent.addNode("translate ghost", SingleElementNode.class); + node.setZIndex(1000); + node.setVisible(Boolean.TRUE); + node.setComposite(AlphaComposite.SrcOver.derive(0.4f)); + } + + @SGCleanup + public void cleanupSG() { + if (node != null) { + node.remove(); + node = null; + } + parent = null; + } + +}