/******************************************************************************* * 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.handler; 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.HashSet; import java.util.Set; 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.synchronization.graph.RemoveBranchpoint; import org.simantics.g2d.canvas.impl.DependencyReflection.Dependency; import org.simantics.g2d.connection.ConnectionEntity; import org.simantics.g2d.diagram.DiagramHints; import org.simantics.g2d.diagram.IDiagram; import org.simantics.g2d.diagram.handler.PickRequest; import org.simantics.g2d.diagram.handler.Topology; import org.simantics.g2d.diagram.handler.Topology.Connection; import org.simantics.g2d.diagram.participant.AbstractDiagramParticipant; import org.simantics.g2d.diagram.participant.Selection; import org.simantics.g2d.element.ElementHints; import org.simantics.g2d.element.ElementUtils; import org.simantics.g2d.element.IElement; import org.simantics.g2d.elementclass.BranchPoint; import org.simantics.g2d.elementclass.BranchPoint.Direction; import org.simantics.g2d.participant.MouseUtil; import org.simantics.g2d.participant.MouseUtil.MouseInfo; import org.simantics.g2d.participant.WorkbenchStatusLine; import org.simantics.scenegraph.g2d.events.EventHandlerReflection.EventHandler; import org.simantics.scenegraph.g2d.events.command.CommandEvent; import org.simantics.scenegraph.g2d.events.command.Commands; import org.simantics.scenegraph.g2d.snap.ISnapAdvisor; import org.simantics.ui.SimanticsUI; import org.simantics.utils.ui.ErrorLogger; /** * A participant for handling: * * * NOTE: this participant does nothing useful with DIA.RouteGraphConnection type connections. * * @author Tuukka Lehtonen * * TODO: start using {@link WorkbenchStatusLine} */ public class ConnectionCommandHandler extends AbstractDiagramParticipant { @Dependency MouseUtil mouseUtil; @Dependency Selection selection; //@Dependency WorkbenchStatusLine statusLine; @EventHandler(priority = 100) public boolean handleCommand(CommandEvent event) { if (Commands.DELETE.equals(event.command)) { try { return joinConnection(); } catch (DatabaseException e) { ErrorLogger.defaultLogError(e); return false; } } else if (Commands.SPLIT_CONNECTION.equals(event.command) && routePointsEnabled()) { return splitConnection(); } else if (Commands.ROTATE_ELEMENT_CW.equals(event.command) || Commands.ROTATE_ELEMENT_CCW.equals(event.command)) { boolean clockWise = Commands.ROTATE_ELEMENT_CW.equals(event.command); try { return rotateBranchPoint(clockWise); } catch (DatabaseException e) { ErrorLogger.defaultLogError(e); return false; } } return false; } private boolean routePointsEnabled() { return Boolean.TRUE.equals(diagram.getHint(DiagramHints.KEY_ALLOW_ROUTE_POINTS)); } boolean joinConnection() throws DatabaseException { final Set routePoints = getBranchPoints(2); if (routePoints.isEmpty()) return false; selection.clear(0); Simantics.getSession().sync(new WriteRequest() { @Override public void perform(WriteGraph graph) throws DatabaseException { for (IElement routePoint : routePoints) { Object node = ElementUtils.getObject(routePoint); if (node instanceof Resource) { new RemoveBranchpoint(routePoint).perform(graph); } } } }); setDirty(); //statusLine.message("Deleted " + routePoints.size() + " route points"); return true; } private boolean rotateBranchPoint(final boolean clockWise) throws DatabaseException { final Set routePoints = getBranchPoints(Integer.MAX_VALUE); if (routePoints.isEmpty()) return false; // Rotate branch points. try { Simantics.getSession().syncRequest(new WriteRequest() { @Override public void perform(WriteGraph graph) throws DatabaseException { DiagramResource DIA = DiagramResource.getInstance(graph); for (IElement bp : routePoints) { Resource bpr = (Resource) ElementUtils.getObject(bp); boolean vertical = graph.hasStatement(bpr, DIA.Vertical, bpr); boolean horizontal = graph.hasStatement(bpr, DIA.Horizontal, bpr); Direction dir = Direction.toDirection(horizontal, vertical); Direction newDir = clockWise ? dir.cycleNext() : dir.cyclePrevious(); switch (newDir) { case Any: graph.deny(bpr, DIA.Vertical); graph.deny(bpr, DIA.Horizontal); break; case Horizontal: graph.deny(bpr, DIA.Vertical); graph.claim(bpr, DIA.Horizontal, bpr); break; case Vertical: graph.deny(bpr, DIA.Horizontal); graph.claim(bpr, DIA.Vertical, bpr); break; } } } }); } catch (DatabaseException e) { ErrorLogger.defaultLogError(e); } return true; } boolean splitConnection() { final IElement edge = getSingleConnectionSegment(); if (edge == null) return false; final IDiagram diagram = ElementUtils.peekDiagram(edge); if (diagram == null) return false; MouseInfo mi = mouseUtil.getMouseInfo(0); final Point2D mousePos = mi.canvasPosition; ISnapAdvisor snap = getHint(DiagramHints.SNAP_ADVISOR); if (snap != null) snap.snap(mousePos); final AffineTransform splitPos = AffineTransform.getTranslateInstance(mousePos.getX(), mousePos.getY()); 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); EdgeResource segment = (EdgeResource) ElementUtils.getObject(edge); Resource bp = cu.split(segment, splitPos); Line2D nearestLine = ConnectionUtil.resolveNearestEdgeLineSegment(mousePos, 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); } } } }); selection.clear(0); } catch (DatabaseException e) { ErrorLogger.defaultLogError(e); } return false; } /** * @return all route points in the current diagram selection if and only if * only route points are selected. If anything else is selected, * even branch points, an empty set will be returned. */ Set getBranchPoints(int maxDegree) throws DatabaseException { Set ss = selection.getSelection(0); if (ss.isEmpty()) return Collections.emptySet(); final Set result = new HashSet(); Collection connections = new ArrayList(); for (IElement e : ss) { if (!e.getElementClass().containsClass(BranchPoint.class)) return Collections.emptySet(); ConnectionEntity ce = e.getHint(ElementHints.KEY_CONNECTION_ENTITY); if (ce == null) return Collections.emptySet(); IDiagram diagram = ElementUtils.peekDiagram(e); if (diagram == null) return Collections.emptySet(); for (Topology topology : diagram.getDiagramClass().getItemsByClass(Topology.class)) { connections.clear(); topology.getConnections(e, ElementUtils.getSingleTerminal(e), connections); int degree = connections.size(); if (degree < 2 || degree > maxDegree) return Collections.emptySet(); result.add(e); } } return result; } /** * @return if a single edge is selected, return the edge. If a single * connection is selected and it contains only one edge segment, * return it. Otherwise return null. */ IElement getSingleConnectionSegment() { Set s = selection.getSelection(0); if (s.size() != 1) return null; IElement e = s.iterator().next(); if (PickRequest.PickFilter.FILTER_CONNECTIONS.accept(e)) { // Does this connection have only one edge and no branch points? ConnectionEntity ce = e.getHint(ElementHints.KEY_CONNECTION_ENTITY); Collection coll = ce.getBranchPoints(null); if (!coll.isEmpty()) return null; ce.getSegments(coll); if (coll.size() != 1) return null; // Return the only edge element of the whole connection. return coll.iterator().next(); } if (PickRequest.PickFilter.FILTER_CONNECTION_EDGES.accept(e)) return e; return null; } /** * @return the selected top-level connection element if and only if the * selection contains the connection or parts of the same connection * (edge segments or branch/route points) and nothing else. */ IElement getSingleConnection() { Set ss = selection.getSelection(0); if (ss.isEmpty()) return null; IElement result = null; for (IElement e : ss) { ConnectionEntity ce = e.getHint(ElementHints.KEY_CONNECTION_ENTITY); if (ce == null) continue; if (result != null && !ce.getConnection().equals(result)) return null; result = ce.getConnection(); } return result; } }