/*******************************************************************************
* 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:
*
* - DELETE for deleting route points from DIA.Connection type connections
*
- SPLIT_CONNECTION for creating new route points into DIA.Connection type connections
*
- ROTATE_ELEMENT_CW & ROTATE_ELEMENT_CCW for rotating route points orientations.
*
*
* 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 {
SimanticsUI.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 {
SimanticsUI.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;
}
}