--- /dev/null
+/*******************************************************************************\r
+ * Copyright (c) 2007, 2010 Association for Decentralized Information Management\r
+ * in Industry THTH ry.\r
+ * All rights reserved. This program and the accompanying materials\r
+ * are made available under the terms of the Eclipse Public License v1.0\r
+ * which accompanies this distribution, and is available at\r
+ * http://www.eclipse.org/legal/epl-v10.html\r
+ *\r
+ * Contributors:\r
+ * VTT Technical Research Centre of Finland - initial API and implementation\r
+ *******************************************************************************/\r
+package org.simantics.diagram.handler;\r
+\r
+import java.util.ArrayDeque;\r
+import java.util.ArrayList;\r
+import java.util.Collection;\r
+import java.util.Deque;\r
+import java.util.HashMap;\r
+import java.util.HashSet;\r
+import java.util.Set;\r
+\r
+import org.eclipse.core.runtime.IProgressMonitor;\r
+import org.eclipse.core.runtime.IStatus;\r
+import org.eclipse.core.runtime.Status;\r
+import org.eclipse.jface.action.IStatusLineManager;\r
+import org.eclipse.swt.widgets.Display;\r
+import org.simantics.DatabaseJob;\r
+import org.simantics.Simantics;\r
+import org.simantics.db.ReadGraph;\r
+import org.simantics.db.Resource;\r
+import org.simantics.db.WriteGraph;\r
+import org.simantics.db.common.request.WriteRequest;\r
+import org.simantics.db.exception.DatabaseException;\r
+import org.simantics.db.layer0.adapter.Remover;\r
+import org.simantics.db.layer0.exception.CannotRemoveException;\r
+import org.simantics.db.layer0.util.RemoverUtil;\r
+import org.simantics.diagram.adapter.ElementFactoryUtil;\r
+import org.simantics.diagram.content.ConnectionUtil;\r
+import org.simantics.diagram.content.EdgeResource;\r
+import org.simantics.diagram.internal.Activator;\r
+import org.simantics.diagram.synchronization.ISynchronizationContext;\r
+import org.simantics.diagram.synchronization.graph.RemoveBranchpoint;\r
+import org.simantics.diagram.synchronization.graph.RemoveElement;\r
+import org.simantics.diagram.ui.DiagramModelHints;\r
+import org.simantics.g2d.canvas.impl.DependencyReflection.Dependency;\r
+import org.simantics.g2d.connection.ConnectionEntity;\r
+import org.simantics.g2d.connection.handler.ConnectionHandler;\r
+import org.simantics.g2d.diagram.IDiagram;\r
+import org.simantics.g2d.diagram.handler.PickRequest.PickFilter;\r
+import org.simantics.g2d.diagram.handler.Relationship;\r
+import org.simantics.g2d.diagram.handler.RelationshipHandler;\r
+import org.simantics.g2d.diagram.handler.RelationshipHandler.Relation;\r
+import org.simantics.g2d.diagram.handler.Topology;\r
+import org.simantics.g2d.diagram.handler.Topology.Connection;\r
+import org.simantics.g2d.diagram.handler.Topology.Terminal;\r
+import org.simantics.g2d.diagram.participant.AbstractDiagramParticipant;\r
+import org.simantics.g2d.diagram.participant.Selection;\r
+import org.simantics.g2d.element.ElementClass;\r
+import org.simantics.g2d.element.ElementHints;\r
+import org.simantics.g2d.element.ElementUtils;\r
+import org.simantics.g2d.element.IElement;\r
+import org.simantics.g2d.element.handler.BendsHandler;\r
+import org.simantics.g2d.element.handler.EdgeVisuals.EdgeEnd;\r
+import org.simantics.g2d.element.handler.TerminalTopology;\r
+import org.simantics.scenegraph.g2d.events.EventHandlerReflection.EventHandler;\r
+import org.simantics.scenegraph.g2d.events.command.CommandEvent;\r
+import org.simantics.scenegraph.g2d.events.command.Commands;\r
+import org.simantics.utils.logging.TimeLogger;\r
+import org.simantics.utils.strings.EString;\r
+import org.simantics.utils.threads.SWTThread;\r
+import org.simantics.utils.threads.ThreadUtils;\r
+import org.simantics.utils.ui.dialogs.ShowError;\r
+import org.simantics.utils.ui.dialogs.ShowMessage;\r
+\r
+/**\r
+ * DeleteHandler is a canvas handler for Commands.DELETE commands for an\r
+ * IDiagram.\r
+ * \r
+ * <p>\r
+ * The handler attempts to delete the current selection for pointer 0, meaning\r
+ * {@link Selection#SELECTION0}.\r
+ * </p>\r
+ * \r
+ * <p>\r
+ * The handler logic goes as follows:\r
+ * </p>\r
+ * <ol>\r
+ * <li>Separate nodes and edges form the the removed selection</li>\r
+ * <li>Find all edges attached to the removed nodes and remove them too</li>\r
+ * <li>Delete connections that contain less than 2 terminal connections</li>\r
+ * </ol>\r
+ * \r
+ * @see Selection for the current diagram selection source\r
+ * \r
+ * @author Tuukka Lehtonen\r
+ * \r
+ * TODO: start using WorkbenchStatusLine participant\r
+ */\r
+public class DeleteHandler extends AbstractDiagramParticipant {\r
+\r
+ public static final boolean DEBUG_DELETE = false;\r
+\r
+ @Dependency Selection sel;\r
+\r
+ private final IStatusLineManager statusLine;\r
+\r
+ public DeleteHandler(IStatusLineManager statusLine) {\r
+ this.statusLine = statusLine;\r
+ }\r
+\r
+ @EventHandler(priority = 0)\r
+ public boolean handleCommand(CommandEvent e) {\r
+ if (Commands.DELETE.equals( e.command )) {\r
+ IDiagram d = diagram;\r
+ if (d == null)\r
+ return true;\r
+\r
+ Set<IElement> ss = sel.getSelection(0);\r
+ if (ss.isEmpty())\r
+ return true;\r
+\r
+ if (delete(d, ss)) {\r
+ sel.clear(0);\r
+ }\r
+\r
+ return true;\r
+ }\r
+ return false;\r
+ }\r
+\r
+ public boolean delete(final IDiagram d, Collection<IElement> ss) {\r
+ TimeLogger.resetTimeAndLog(getClass(), "delete");\r
+ \r
+ Topology topology = d.getDiagramClass().getAtMostOneItemOfClass(Topology.class);\r
+ RelationshipHandler erh = d.getDiagramClass().getAtMostOneItemOfClass(RelationshipHandler.class);\r
+\r
+ if (DEBUG_DELETE) {\r
+ System.out.println("diagram: " + d);\r
+ for (IElement e : d.getSnapshot()) {\r
+ ElementClass ec = e.getElementClass();\r
+ System.out.println("\t-element " + e);\r
+ System.out.println("\t -class " + e.getElementClass());\r
+ if (ec.containsClass(ConnectionHandler.class)) {\r
+ ConnectionEntity ce = e.getHint(ElementHints.KEY_CONNECTION_ENTITY);\r
+ for (IElement child : ce.getBranchPoints(null)) {\r
+ System.out.println("\t\t-branch " + child);\r
+ System.out.println("\t\t -class " + child.getElementClass());\r
+ }\r
+ for (IElement child : ce.getSegments(null)) {\r
+ System.out.println("\t\t-segment " + child);\r
+ System.out.println("\t\t -class " + child.getElementClass());\r
+ }\r
+ }\r
+ }\r
+ System.out.println("delete requested for elements:");\r
+ for (IElement e : ss)\r
+ System.out.println("\t-element " + e);\r
+ }\r
+\r
+ // Analyze removals:\r
+ // - separate elements and connections\r
+ // - find all connections attached to the elements and remove them too\r
+ Deque<IElement> elementsToProcess = new ArrayDeque<IElement>(ss);\r
+ Set<IElement> processedElements = new HashSet<IElement>();\r
+ Set<IElement> relationshipsProcessedForElement = new HashSet<IElement>();\r
+\r
+ final Collection<IElement> elements = new ArrayList<IElement>();\r
+ final Set<IElement> edges = new HashSet<IElement>();\r
+ Collection<Connection> connections = new ArrayList<Connection>();\r
+ Collection<Terminal> terminals = new ArrayList<Terminal>();\r
+ Collection<Relation> relations = new ArrayList<Relation>();\r
+ while (!elementsToProcess.isEmpty()) {\r
+ IElement el = elementsToProcess.pollFirst();\r
+\r
+ if (relationshipsProcessedForElement.add(el)) {\r
+ // Check for relationships to other elements and mark child\r
+ // elements to be removed before the parent element.\r
+ relations.clear();\r
+ erh.getRelations(d, el, relations);\r
+ if (!relations.isEmpty()) {\r
+ boolean restart = false;\r
+ for (Relation r : relations) {\r
+ //System.out.println("FOUND RELATION: " + r);\r
+ if (r.getRelationship() == Relationship.PARENT_OF) {\r
+ if ((r.getObject() instanceof IElement)) {\r
+ IElement ee = (IElement) r.getObject();\r
+ if (d.containsElement(ee)) {\r
+ //System.out.println("DIAGRAM CONTAINS OBJECT: " + r.getObject());\r
+\r
+ // Mark the object also to be processed for removal.\r
+ elementsToProcess.addFirst(ee);\r
+ restart = true;\r
+ }\r
+ }\r
+ }\r
+ }\r
+ if (restart) {\r
+ // Only process this element after we're sure that\r
+ // all its children have been processed.\r
+ elementsToProcess.addLast(el);\r
+ continue;\r
+ }\r
+ }\r
+ }\r
+\r
+ if (!processedElements.add(el))\r
+ continue;\r
+\r
+ TerminalTopology tt = el.getElementClass().getAtMostOneItemOfClass(TerminalTopology.class);\r
+ BendsHandler bh = el.getElementClass().getAtMostOneItemOfClass(BendsHandler.class);\r
+\r
+ if (bh != null) {\r
+ // Verify that the edge is NOT between two branch points.\r
+ // If it is, do not allow deletion because it is the only case\r
+ // which can break a connection tree into a connection forest.\r
+ // We do not want that to happen.\r
+ Connection begin = topology.getConnection(el, EdgeEnd.Begin);\r
+ Connection end = topology.getConnection(el, EdgeEnd.End);\r
+\r
+ // Try to work with cases where the model is somewhat corrupt.\r
+ if (begin != null && end != null) {\r
+ if (PickFilter.FILTER_BRANCH_POINT.accept(begin.node) && PickFilter.FILTER_BRANCH_POINT.accept(end.node)) {\r
+ error("Deletion of branch point connecting edges is not allowed. Must be connected to a node terminal.");\r
+ return false;\r
+ }\r
+ }\r
+\r
+ if (DEBUG_DELETE)\r
+ System.out.println("ADDED EDGE FOR REMOVAL: " + el);\r
+ edges.add(el);\r
+ } else {\r
+ if (DEBUG_DELETE)\r
+ System.out.println("ADDED ELEMENT FOR REMOVAL: " + el);\r
+ elements.add(el);\r
+\r
+ if (tt != null) {\r
+ terminals.clear();\r
+ tt.getTerminals(el, terminals);\r
+ connections.clear();\r
+ for (Terminal terminal : terminals)\r
+ topology.getConnections(el, terminal, connections);\r
+ for (Connection c : connections) {\r
+ if (c.edge != null) {\r
+ if (c.edge.getElementClass().containsClass(BendsHandler.class))\r
+ edges.add(c.edge);\r
+ if (DEBUG_DELETE)\r
+ System.out.println("TERMINAL CONNECTION WILL BE DISCONNECTED: " + c);\r
+ }\r
+ }\r
+ }\r
+ }\r
+ }\r
+\r
+ if (elements.isEmpty() && edges.isEmpty())\r
+ return false;\r
+\r
+ if (DEBUG_DELETE) {\r
+ System.out.println("gathered elements to delete:");\r
+ System.out.println("\telements:");\r
+ if (!elements.isEmpty())\r
+ for (IElement e : elements)\r
+ System.out.println("\t\t" + e);\r
+ System.out.println("\tedges:");\r
+ if (!edges.isEmpty())\r
+ for (IElement e : edges)\r
+ System.out.println("\t\t" + e);\r
+ }\r
+\r
+ final IDiagram diagram = this.diagram;\r
+ final ISynchronizationContext syncContext = ElementFactoryUtil.getContextChecked(diagram); \r
+\r
+ new DatabaseJob("Delete selection") {\r
+ @Override\r
+ protected IStatus run(IProgressMonitor monitor) {\r
+ try {\r
+ delete(monitor);\r
+ return Status.OK_STATUS;\r
+ } catch (CannotRemoveException e) {\r
+ ShowMessage.showInformation("Delete Selection Was Denied", e.getLocalizedMessage());\r
+ return new Status(IStatus.CANCEL, Activator.PLUGIN_ID, e.getLocalizedMessage(), e);\r
+ } catch (DatabaseException e) {\r
+ return new Status(IStatus.ERROR, Activator.PLUGIN_ID, "Unexpected error in delete.", e);\r
+ } finally {\r
+ error(null);\r
+ monitor.done();\r
+ }\r
+ }\r
+\r
+ private void delete(IProgressMonitor monitor) throws DatabaseException {\r
+ Simantics.getSession().syncRequest(new WriteRequest() {\r
+ Set<Resource> connectionsToRemove = new HashSet<Resource>();\r
+ Set<Resource> touchedConnections = new HashSet<Resource>();\r
+\r
+ @Override\r
+ public void perform(WriteGraph graph) throws DatabaseException {\r
+ validateRemoval(graph);\r
+ graph.markUndoPoint();\r
+\r
+ ConnectionUtil cu = new ConnectionUtil(graph);\r
+\r
+ // Remove edges\r
+ for (IElement edge : edges) {\r
+ ConnectionEntity ce = edge.getHint(ElementHints.KEY_CONNECTION_ENTITY);\r
+ touchConnection( ce.getConnection() );\r
+\r
+ if (DEBUG_DELETE)\r
+ System.out.println("REMOVING EDGE: " + edge);\r
+ Object obj = ElementUtils.getObject(edge);\r
+ if (obj instanceof EdgeResource) {\r
+ cu.remove((EdgeResource) obj);\r
+ }\r
+ }\r
+\r
+ // Remove elements\r
+ for (IElement element : elements) {\r
+ ConnectionHandler ch = element.getElementClass().getAtMostOneItemOfClass(ConnectionHandler.class);\r
+ if (ch != null) {\r
+ if (DEBUG_DELETE)\r
+ System.out.println("MARKING CONNECTION TO BE REMOVED: " + element);\r
+ connectionsToRemove.add( (Resource) ElementUtils.getObject(element) );\r
+ } else {\r
+ ConnectionEntity ce = element.getHint(ElementHints.KEY_CONNECTION_ENTITY);\r
+ if(ce != null) {\r
+ if (DEBUG_DELETE)\r
+ System.out.println("REMOVING BRANCH POINT: " + element);\r
+ new RemoveBranchpoint(element).perform(graph);\r
+ touchConnection( ce.getConnection() );\r
+ } else {\r
+ if (DEBUG_DELETE)\r
+ System.out.println("REMOVING ELEMENT: " + element);\r
+\r
+ Object obj = ElementUtils.getObject(element);\r
+ if (obj instanceof Resource) {\r
+ // Get terminal connections for element\r
+ Collection<Resource> connectors = cu.getTerminalConnectors((Resource) obj, null);\r
+ for (Resource connector : connectors) {\r
+ Resource connection = ConnectionUtil.tryGetConnection(graph, connector);\r
+ if (connection != null)\r
+ touchConnection( connection );\r
+ cu.disconnectFromAllRouteNodes(connector);\r
+ }\r
+\r
+ new RemoveElement((Resource)d.getHint(DiagramModelHints.KEY_DIAGRAM_RESOURCE), (Resource)element.getHint(ElementHints.KEY_OBJECT)).perform(graph);\r
+ }\r
+ }\r
+ }\r
+ }\r
+\r
+ // Check all touched connections to see if they are empty.\r
+ for (Resource connection : touchedConnections) {\r
+ int removedConnectors = cu.removeUnusedConnectors(connection);\r
+ if (DEBUG_DELETE)\r
+ System.out.println("PRUNED " + removedConnectors + " CONNECTORS FROM TOUCHED CONNECTION " + connection);\r
+ while (true) {\r
+ int removedInteriorRouteNodes = cu.removeExtraInteriorRouteNodes(connection);\r
+ if (DEBUG_DELETE)\r
+ System.out.println("PRUNED " + removedInteriorRouteNodes + " INTERIOR ROUTE NODES FROM TOUCHED CONNECTION " + connection);\r
+ if (removedInteriorRouteNodes == 0)\r
+ break;\r
+ }\r
+ int connectors = cu.getConnectedConnectors(connection, null).size();\r
+ if (DEBUG_DELETE)\r
+ System.out.println("\t" + connectors + " CONNECTORS LEFT");\r
+ if (connectors < 2) {\r
+ connectionsToRemove.add(connection);\r
+ }\r
+ }\r
+\r
+ // Remove selected/left-over empty connections\r
+ for (Resource connection : connectionsToRemove) {\r
+ if (DEBUG_DELETE)\r
+ System.out.println("REMOVING CONNECTION: " + connection);\r
+ RemoveElement.removeElement(graph, (Resource)d.getHint(DiagramModelHints.KEY_DIAGRAM_RESOURCE), connection);\r
+ }\r
+ }\r
+\r
+ private void validateRemoval(ReadGraph graph) throws DatabaseException, CannotRemoveException {\r
+ ArrayList<String> problems = new ArrayList<String>(elements.size());\r
+ for (IElement element : elements) {\r
+ Object obj = ElementUtils.getObject(element);\r
+ if (obj instanceof Resource) {\r
+ Remover remover = RemoverUtil.getPossibleRemover(graph, (Resource) obj);\r
+ if (remover != null) {\r
+ String problem = remover.canRemove(graph, new HashMap<Object, Object>(4));\r
+ if (problem != null) {\r
+ problems.add(problem);\r
+ }\r
+ }\r
+ }\r
+ }\r
+ if (!problems.isEmpty()) {\r
+ throw new CannotRemoveException(EString.implode(problems));\r
+ }\r
+ }\r
+\r
+ void touchConnection(Object c) {\r
+ if (DEBUG_DELETE)\r
+ System.out.println("TOUCHED CONNECTION: " + c);\r
+ if (c instanceof IElement) {\r
+ Object obj = ElementUtils.getObject((IElement) c);\r
+ if (obj instanceof Resource)\r
+ touchedConnections.add((Resource) obj);\r
+ } else if (c instanceof Resource) {\r
+ touchedConnections.add((Resource) c);\r
+ }\r
+ }\r
+ });\r
+ }\r
+ }.schedule();\r
+\r
+ return true;\r
+ }\r
+\r
+ void message(final String message) {\r
+ if (statusLine == null)\r
+ return;\r
+ swtExec(new Runnable() {\r
+ @Override\r
+ public void run() {\r
+ statusLine.setMessage(message);\r
+ statusLine.setErrorMessage(null);\r
+ }\r
+ });\r
+ }\r
+\r
+ void error(final String message) {\r
+ if (statusLine == null)\r
+ return;\r
+ swtExec(new Runnable() {\r
+ @Override\r
+ public void run() {\r
+ statusLine.setErrorMessage(message);\r
+ }\r
+ });\r
+ }\r
+\r
+ void swtExec(Runnable r) {\r
+ ThreadUtils.asyncExec(SWTThread.getThreadAccess(Display.getDefault()), r);\r
+ }\r
+\r
+}\r