/******************************************************************************* * 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.util.ArrayDeque; import java.util.ArrayList; import java.util.Collection; import java.util.Deque; import java.util.HashMap; import java.util.HashSet; import java.util.Set; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.jface.action.IStatusLineManager; import org.eclipse.swt.widgets.Display; import org.simantics.DatabaseJob; import org.simantics.Simantics; import org.simantics.db.ReadGraph; 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.db.layer0.adapter.Remover; import org.simantics.db.layer0.exception.CannotRemoveException; import org.simantics.db.layer0.util.RemoverUtil; import org.simantics.diagram.adapter.ElementFactoryUtil; import org.simantics.diagram.content.ConnectionUtil; import org.simantics.diagram.content.EdgeResource; import org.simantics.diagram.internal.Activator; import org.simantics.diagram.synchronization.ISynchronizationContext; import org.simantics.diagram.synchronization.graph.RemoveBranchpoint; import org.simantics.diagram.synchronization.graph.RemoveElement; import org.simantics.diagram.ui.DiagramModelHints; import org.simantics.g2d.canvas.impl.DependencyReflection.Dependency; import org.simantics.g2d.connection.ConnectionEntity; import org.simantics.g2d.connection.handler.ConnectionHandler; import org.simantics.g2d.diagram.IDiagram; import org.simantics.g2d.diagram.handler.PickRequest.PickFilter; 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.diagram.participant.AbstractDiagramParticipant; import org.simantics.g2d.diagram.participant.Selection; 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.BendsHandler; import org.simantics.g2d.element.handler.EdgeVisuals.EdgeEnd; import org.simantics.g2d.element.handler.TerminalTopology; 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.utils.logging.TimeLogger; import org.simantics.utils.strings.EString; import org.simantics.utils.threads.SWTThread; import org.simantics.utils.threads.ThreadUtils; import org.simantics.utils.ui.dialogs.ShowMessage; /** * DeleteHandler is a canvas handler for Commands.DELETE commands for an * IDiagram. * *

* The handler attempts to delete the current selection for pointer 0, meaning * {@link Selection#SELECTION0}. *

* *

* The handler logic goes as follows: *

*
    *
  1. Separate nodes and edges form the the removed selection
  2. *
  3. Find all edges attached to the removed nodes and remove them too
  4. *
  5. Delete connections that contain less than 2 terminal connections
  6. *
* * @see Selection for the current diagram selection source * * @author Tuukka Lehtonen * * TODO: start using WorkbenchStatusLine participant */ public class DeleteHandler extends AbstractDiagramParticipant { public static final boolean DEBUG_DELETE = false; @Dependency Selection sel; private final IStatusLineManager statusLine; public DeleteHandler(IStatusLineManager statusLine) { this.statusLine = statusLine; } @EventHandler(priority = 0) public boolean handleCommand(CommandEvent e) { if (Commands.DELETE.equals( e.command )) { IDiagram d = diagram; if (d == null) return true; Set ss = sel.getSelection(0); if (ss.isEmpty()) return true; if (delete(d, ss)) { sel.clear(0); } return true; } return false; } public boolean delete(final IDiagram d, Collection ss) { TimeLogger.resetTimeAndLog(getClass(), "delete"); Topology topology = d.getDiagramClass().getAtMostOneItemOfClass(Topology.class); RelationshipHandler erh = d.getDiagramClass().getAtMostOneItemOfClass(RelationshipHandler.class); if (DEBUG_DELETE) { System.out.println("diagram: " + d); for (IElement e : d.getSnapshot()) { ElementClass ec = e.getElementClass(); System.out.println("\t-element " + e); System.out.println("\t -class " + e.getElementClass()); if (ec.containsClass(ConnectionHandler.class)) { ConnectionEntity ce = e.getHint(ElementHints.KEY_CONNECTION_ENTITY); for (IElement child : ce.getBranchPoints(null)) { System.out.println("\t\t-branch " + child); System.out.println("\t\t -class " + child.getElementClass()); } for (IElement child : ce.getSegments(null)) { System.out.println("\t\t-segment " + child); System.out.println("\t\t -class " + child.getElementClass()); } } } System.out.println("delete requested for elements:"); for (IElement e : ss) System.out.println("\t-element " + e); } // Analyze removals: // - separate elements and connections // - find all connections attached to the elements and remove them too Deque elementsToProcess = new ArrayDeque(ss); Set processedElements = new HashSet(); Set relationshipsProcessedForElement = new HashSet(); final Collection elements = new ArrayList(); final Set edges = new HashSet(); Collection connections = new ArrayList(); Collection terminals = new ArrayList(); Collection relations = new ArrayList(); while (!elementsToProcess.isEmpty()) { IElement el = elementsToProcess.pollFirst(); if (relationshipsProcessedForElement.add(el)) { // Check for relationships to other elements and mark child // elements to be removed before the parent element. relations.clear(); erh.getRelations(d, el, relations); if (!relations.isEmpty()) { boolean restart = false; for (Relation r : relations) { //System.out.println("FOUND RELATION: " + r); if (r.getRelationship() == Relationship.PARENT_OF) { if ((r.getObject() instanceof IElement)) { IElement ee = (IElement) r.getObject(); if (d.containsElement(ee)) { //System.out.println("DIAGRAM CONTAINS OBJECT: " + r.getObject()); // Mark the object also to be processed for removal. elementsToProcess.addFirst(ee); restart = true; } } } } if (restart) { // Only process this element after we're sure that // all its children have been processed. elementsToProcess.addLast(el); continue; } } } if (!processedElements.add(el)) continue; TerminalTopology tt = el.getElementClass().getAtMostOneItemOfClass(TerminalTopology.class); BendsHandler bh = el.getElementClass().getAtMostOneItemOfClass(BendsHandler.class); if (bh != null) { // Verify that the edge is NOT between two branch points. // If it is, do not allow deletion because it is the only case // which can break a connection tree into a connection forest. // We do not want that to happen. Connection begin = topology.getConnection(el, EdgeEnd.Begin); Connection end = topology.getConnection(el, EdgeEnd.End); // Try to work with cases where the model is somewhat corrupt. if (begin != null && end != null) { if (PickFilter.FILTER_BRANCH_POINT.accept(begin.node) && PickFilter.FILTER_BRANCH_POINT.accept(end.node)) { error("Deletion of branch point connecting edges is not allowed. Must be connected to a node terminal."); return false; } } if (DEBUG_DELETE) System.out.println("ADDED EDGE FOR REMOVAL: " + el); edges.add(el); } else { if (DEBUG_DELETE) System.out.println("ADDED ELEMENT FOR REMOVAL: " + el); elements.add(el); if (tt != null) { terminals.clear(); tt.getTerminals(el, terminals); connections.clear(); for (Terminal terminal : terminals) topology.getConnections(el, terminal, connections); for (Connection c : connections) { if (c.edge != null) { if (c.edge.getElementClass().containsClass(BendsHandler.class)) edges.add(c.edge); if (DEBUG_DELETE) System.out.println("TERMINAL CONNECTION WILL BE DISCONNECTED: " + c); } } } } } if (elements.isEmpty() && edges.isEmpty()) return false; if (DEBUG_DELETE) { System.out.println("gathered elements to delete:"); System.out.println("\telements:"); if (!elements.isEmpty()) for (IElement e : elements) System.out.println("\t\t" + e); System.out.println("\tedges:"); if (!edges.isEmpty()) for (IElement e : edges) System.out.println("\t\t" + e); } final IDiagram diagram = this.diagram; final ISynchronizationContext syncContext = ElementFactoryUtil.getContextChecked(diagram); new DatabaseJob("Delete selection") { @Override protected IStatus run(IProgressMonitor monitor) { try { delete(monitor); return Status.OK_STATUS; } catch (CannotRemoveException e) { ShowMessage.showInformation("Delete Selection Was Denied", e.getLocalizedMessage()); return new Status(IStatus.CANCEL, Activator.PLUGIN_ID, e.getLocalizedMessage(), e); } catch (DatabaseException e) { return new Status(IStatus.ERROR, Activator.PLUGIN_ID, "Unexpected error in delete.", e); } finally { error(null); monitor.done(); } } private void delete(IProgressMonitor monitor) throws DatabaseException { Simantics.getSession().syncRequest(new WriteRequest() { Set connectionsToRemove = new HashSet(); Set touchedConnections = new HashSet(); @Override public void perform(WriteGraph graph) throws DatabaseException { validateRemoval(graph); graph.markUndoPoint(); ConnectionUtil cu = new ConnectionUtil(graph); // Remove edges for (IElement edge : edges) { ConnectionEntity ce = edge.getHint(ElementHints.KEY_CONNECTION_ENTITY); touchConnection( ce.getConnection() ); if (DEBUG_DELETE) System.out.println("REMOVING EDGE: " + edge); Object obj = ElementUtils.getObject(edge); if (obj instanceof EdgeResource) { cu.remove((EdgeResource) obj); } } // Remove elements for (IElement element : elements) { ConnectionHandler ch = element.getElementClass().getAtMostOneItemOfClass(ConnectionHandler.class); if (ch != null) { if (DEBUG_DELETE) System.out.println("MARKING CONNECTION TO BE REMOVED: " + element); connectionsToRemove.add( (Resource) ElementUtils.getObject(element) ); } else { ConnectionEntity ce = element.getHint(ElementHints.KEY_CONNECTION_ENTITY); if(ce != null) { if (DEBUG_DELETE) System.out.println("REMOVING BRANCH POINT: " + element); new RemoveBranchpoint(element).perform(graph); touchConnection( ce.getConnection() ); } else { if (DEBUG_DELETE) System.out.println("REMOVING ELEMENT: " + element); Object obj = ElementUtils.getObject(element); if (obj instanceof Resource) { // Get terminal connections for element Collection connectors = cu.getTerminalConnectors((Resource) obj, null); for (Resource connector : connectors) { Resource connection = ConnectionUtil.tryGetConnection(graph, connector); if (connection != null) touchConnection( connection ); cu.disconnectFromAllRouteNodes(connector); } new RemoveElement((Resource)d.getHint(DiagramModelHints.KEY_DIAGRAM_RESOURCE), (Resource)element.getHint(ElementHints.KEY_OBJECT)).perform(graph); } } } } // Check all touched connections to see if they are empty. for (Resource connection : touchedConnections) { int removedConnectors = cu.removeUnusedConnectors(connection); if (DEBUG_DELETE) System.out.println("PRUNED " + removedConnectors + " CONNECTORS FROM TOUCHED CONNECTION " + connection); while (true) { int removedInteriorRouteNodes = cu.removeExtraInteriorRouteNodes(connection); if (DEBUG_DELETE) System.out.println("PRUNED " + removedInteriorRouteNodes + " INTERIOR ROUTE NODES FROM TOUCHED CONNECTION " + connection); if (removedInteriorRouteNodes == 0) break; } int connectors = cu.getConnectedConnectors(connection, null).size(); if (DEBUG_DELETE) System.out.println("\t" + connectors + " CONNECTORS LEFT"); if (connectors < 2) { connectionsToRemove.add(connection); } } // Remove selected/left-over empty connections for (Resource connection : connectionsToRemove) { if (DEBUG_DELETE) System.out.println("REMOVING CONNECTION: " + connection); RemoveElement.removeElement(graph, (Resource)d.getHint(DiagramModelHints.KEY_DIAGRAM_RESOURCE), connection); } } private void validateRemoval(ReadGraph graph) throws DatabaseException, CannotRemoveException { ArrayList problems = new ArrayList(elements.size()); for (IElement element : elements) { Object obj = ElementUtils.getObject(element); if (obj instanceof Resource) { Remover remover = RemoverUtil.getPossibleRemover(graph, (Resource) obj); if (remover != null) { String problem = remover.canRemove(graph, new HashMap(4)); if (problem != null) { problems.add(problem); } } } } if (!problems.isEmpty()) { throw new CannotRemoveException(EString.implode(problems)); } } void touchConnection(Object c) { if (DEBUG_DELETE) System.out.println("TOUCHED CONNECTION: " + c); if (c instanceof IElement) { Object obj = ElementUtils.getObject((IElement) c); if (obj instanceof Resource) touchedConnections.add((Resource) obj); } else if (c instanceof Resource) { touchedConnections.add((Resource) c); } } }); } }.schedule(); return true; } void message(final String message) { if (statusLine == null) return; swtExec(new Runnable() { @Override public void run() { statusLine.setMessage(message); statusLine.setErrorMessage(null); } }); } void error(final String message) { if (statusLine == null) return; swtExec(new Runnable() { @Override public void run() { statusLine.setErrorMessage(message); } }); } void swtExec(Runnable r) { ThreadUtils.asyncExec(SWTThread.getThreadAccess(Display.getDefault()), r); } }