1 /*******************************************************************************
2 * Copyright (c) 2007, 2010 Association for Decentralized Information Management
4 * All rights reserved. This program and the accompanying materials
5 * are made available under the terms of the Eclipse Public License v1.0
6 * which accompanies this distribution, and is available at
7 * http://www.eclipse.org/legal/epl-v10.html
10 * VTT Technical Research Centre of Finland - initial API and implementation
11 *******************************************************************************/
12 package org.simantics.diagram.handler;
14 import java.util.ArrayDeque;
15 import java.util.ArrayList;
16 import java.util.Collection;
17 import java.util.Deque;
18 import java.util.HashMap;
19 import java.util.HashSet;
21 import java.util.function.Consumer;
23 import org.eclipse.core.runtime.IProgressMonitor;
24 import org.eclipse.core.runtime.IStatus;
25 import org.eclipse.core.runtime.Status;
26 import org.eclipse.core.runtime.SubMonitor;
27 import org.eclipse.jface.action.IStatusLineManager;
28 import org.eclipse.swt.widgets.Display;
29 import org.simantics.DatabaseJob;
30 import org.simantics.Simantics;
31 import org.simantics.db.ReadGraph;
32 import org.simantics.db.Resource;
33 import org.simantics.db.WriteGraph;
34 import org.simantics.db.common.request.WriteRequest;
35 import org.simantics.db.exception.DatabaseException;
36 import org.simantics.db.layer0.adapter.Remover;
37 import org.simantics.db.layer0.exception.CannotRemoveException;
38 import org.simantics.db.layer0.util.RemoverUtil;
39 import org.simantics.diagram.content.ConnectionUtil;
40 import org.simantics.diagram.content.EdgeResource;
41 import org.simantics.diagram.internal.Activator;
42 import org.simantics.diagram.synchronization.graph.RemoveBranchpoint;
43 import org.simantics.diagram.synchronization.graph.RemoveElement;
44 import org.simantics.diagram.ui.DiagramModelHints;
45 import org.simantics.g2d.canvas.impl.DependencyReflection.Dependency;
46 import org.simantics.g2d.connection.ConnectionEntity;
47 import org.simantics.g2d.connection.handler.ConnectionHandler;
48 import org.simantics.g2d.diagram.IDiagram;
49 import org.simantics.g2d.diagram.handler.PickRequest.PickFilter;
50 import org.simantics.g2d.diagram.handler.Relationship;
51 import org.simantics.g2d.diagram.handler.RelationshipHandler;
52 import org.simantics.g2d.diagram.handler.RelationshipHandler.Relation;
53 import org.simantics.g2d.diagram.handler.Topology;
54 import org.simantics.g2d.diagram.handler.Topology.Connection;
55 import org.simantics.g2d.diagram.handler.Topology.Terminal;
56 import org.simantics.g2d.diagram.participant.AbstractDiagramParticipant;
57 import org.simantics.g2d.diagram.participant.Selection;
58 import org.simantics.g2d.element.ElementClass;
59 import org.simantics.g2d.element.ElementHints;
60 import org.simantics.g2d.element.ElementUtils;
61 import org.simantics.g2d.element.IElement;
62 import org.simantics.g2d.element.handler.BendsHandler;
63 import org.simantics.g2d.element.handler.EdgeVisuals.EdgeEnd;
64 import org.simantics.g2d.element.handler.TerminalTopology;
65 import org.simantics.scenegraph.g2d.events.EventHandlerReflection.EventHandler;
66 import org.simantics.scenegraph.g2d.events.command.CommandEvent;
67 import org.simantics.scenegraph.g2d.events.command.Commands;
68 import org.simantics.utils.logging.TimeLogger;
69 import org.simantics.utils.strings.EString;
70 import org.simantics.utils.threads.SWTThread;
71 import org.simantics.utils.threads.ThreadUtils;
72 import org.simantics.utils.ui.dialogs.ShowMessage;
75 * DeleteHandler is a canvas handler for Commands.DELETE commands for an
79 * The handler attempts to delete the current selection for pointer 0, meaning
80 * {@link Selection#SELECTION0}.
84 * The handler logic goes as follows:
87 * <li>Separate nodes and edges form the the removed selection</li>
88 * <li>Find all edges attached to the removed nodes and remove them too</li>
89 * <li>Delete connections that contain less than 2 terminal connections</li>
92 * @see Selection for the current diagram selection source
94 * @author Tuukka Lehtonen
96 * TODO: start using WorkbenchStatusLine participant
98 public class DeleteHandler extends AbstractDiagramParticipant {
100 public static final boolean DEBUG_DELETE = false;
102 @Dependency Selection sel;
104 private final IStatusLineManager statusLine;
106 public DeleteHandler(IStatusLineManager statusLine) {
107 this.statusLine = statusLine;
110 @EventHandler(priority = 0)
111 public boolean handleCommand(CommandEvent e) {
112 if (Commands.DELETE.equals( e.command )) {
113 IDiagram d = diagram;
117 Set<IElement> ss = sel.getSelection(0);
123 if (ex instanceof CannotRemoveException) {
124 ShowMessage.showInformation("Delete Selection Was Denied", ex.getLocalizedMessage());
139 public static boolean delete(final IDiagram d, Collection<IElement> ss, Consumer<Exception> exceptionListener, Consumer<String> errorListener) {
140 TimeLogger.resetTimeAndLog(DeleteHandler.class, "delete");
142 Topology topology = d.getDiagramClass().getAtMostOneItemOfClass(Topology.class);
143 RelationshipHandler erh = d.getDiagramClass().getAtMostOneItemOfClass(RelationshipHandler.class);
146 System.out.println("diagram: " + d);
147 for (IElement e : d.getSnapshot()) {
148 ElementClass ec = e.getElementClass();
149 System.out.println("\t-element " + e);
150 System.out.println("\t -class " + e.getElementClass());
151 if (ec.containsClass(ConnectionHandler.class)) {
152 ConnectionEntity ce = e.getHint(ElementHints.KEY_CONNECTION_ENTITY);
153 for (IElement child : ce.getBranchPoints(null)) {
154 System.out.println("\t\t-branch " + child);
155 System.out.println("\t\t -class " + child.getElementClass());
157 for (IElement child : ce.getSegments(null)) {
158 System.out.println("\t\t-segment " + child);
159 System.out.println("\t\t -class " + child.getElementClass());
163 System.out.println("delete requested for elements:");
164 for (IElement e : ss)
165 System.out.println("\t-element " + e);
169 // - separate elements and connections
170 // - find all connections attached to the elements and remove them too
171 Deque<IElement> elementsToProcess = new ArrayDeque<IElement>(ss);
172 Set<IElement> processedElements = new HashSet<IElement>();
173 Set<IElement> relationshipsProcessedForElement = new HashSet<IElement>();
175 final Collection<IElement> elements = new ArrayList<IElement>();
176 final Set<IElement> edges = new HashSet<IElement>();
177 Collection<Connection> connections = new ArrayList<Connection>();
178 Collection<Terminal> terminals = new ArrayList<Terminal>();
179 Collection<Relation> relations = new ArrayList<Relation>();
180 while (!elementsToProcess.isEmpty()) {
181 IElement el = elementsToProcess.pollFirst();
183 if (relationshipsProcessedForElement.add(el)) {
184 // Check for relationships to other elements and mark child
185 // elements to be removed before the parent element.
187 erh.getRelations(d, el, relations);
188 if (!relations.isEmpty()) {
189 boolean restart = false;
190 for (Relation r : relations) {
191 //System.out.println("FOUND RELATION: " + r);
192 if (r.getRelationship() == Relationship.PARENT_OF) {
193 if ((r.getObject() instanceof IElement)) {
194 IElement ee = (IElement) r.getObject();
195 if (d.containsElement(ee)) {
196 //System.out.println("DIAGRAM CONTAINS OBJECT: " + r.getObject());
198 // Mark the object also to be processed for removal.
199 elementsToProcess.addFirst(ee);
206 // Only process this element after we're sure that
207 // all its children have been processed.
208 elementsToProcess.addLast(el);
214 if (!processedElements.add(el))
217 TerminalTopology tt = el.getElementClass().getAtMostOneItemOfClass(TerminalTopology.class);
218 BendsHandler bh = el.getElementClass().getAtMostOneItemOfClass(BendsHandler.class);
221 // Verify that the edge is NOT between two branch points.
222 // If it is, do not allow deletion because it is the only case
223 // which can break a connection tree into a connection forest.
224 // We do not want that to happen.
225 Connection begin = topology.getConnection(el, EdgeEnd.Begin);
226 Connection end = topology.getConnection(el, EdgeEnd.End);
228 // Try to work with cases where the model is somewhat corrupt.
229 if (begin != null && end != null) {
230 if (PickFilter.FILTER_BRANCH_POINT.accept(begin.node) && PickFilter.FILTER_BRANCH_POINT.accept(end.node)) {
231 if (errorListener != null) {
232 errorListener.accept("Deletion of branch point connecting edges is not allowed. Must be connected to a node terminal.");
239 System.out.println("ADDED EDGE FOR REMOVAL: " + el);
243 System.out.println("ADDED ELEMENT FOR REMOVAL: " + el);
248 tt.getTerminals(el, terminals);
250 for (Terminal terminal : terminals)
251 topology.getConnections(el, terminal, connections);
252 for (Connection c : connections) {
253 if (c.edge != null) {
254 if (c.edge.getElementClass().containsClass(BendsHandler.class))
257 System.out.println("TERMINAL CONNECTION WILL BE DISCONNECTED: " + c);
264 if (elements.isEmpty() && edges.isEmpty())
268 System.out.println("gathered elements to delete:");
269 System.out.println("\telements:");
270 if (!elements.isEmpty())
271 for (IElement e : elements)
272 System.out.println("\t\t" + e);
273 System.out.println("\tedges:");
274 if (!edges.isEmpty())
275 for (IElement e : edges)
276 System.out.println("\t\t" + e);
279 new DatabaseJob("Delete selection") {
281 protected IStatus run(IProgressMonitor monitor) {
284 return Status.OK_STATUS;
285 } catch (CannotRemoveException e) {
286 if (exceptionListener != null) exceptionListener.accept(e);
287 return new Status(IStatus.CANCEL, Activator.PLUGIN_ID, e.getLocalizedMessage(), e);
288 } catch (DatabaseException e) {
289 if (exceptionListener != null) exceptionListener.accept(e);
290 return new Status(IStatus.ERROR, Activator.PLUGIN_ID, "Unexpected error in delete.", e);
292 if (errorListener != null) {
293 errorListener.accept(null);
299 private void delete(IProgressMonitor monitor) throws DatabaseException {
300 SubMonitor subMonitor = SubMonitor.convert(monitor, 4);
301 Simantics.getSession().syncRequest(new WriteRequest() {
302 Set<Resource> connectionsToRemove = new HashSet<Resource>();
303 Set<Resource> touchedConnections = new HashSet<Resource>();
306 public void perform(WriteGraph graph) throws DatabaseException {
307 validateRemoval(graph);
308 graph.markUndoPoint();
310 ConnectionUtil cu = new ConnectionUtil(graph);
311 SubMonitor edgeElementMonitor = subMonitor.split(2); // Consume 50 % of the monitor
312 edgeElementMonitor.setWorkRemaining(edges.size() + elements.size()); // set work remaining to size of edges and elements
313 edgeElementMonitor.setTaskName("Removing edges");
315 for (IElement edge : edges) {
316 ConnectionEntity ce = edge.getHint(ElementHints.KEY_CONNECTION_ENTITY);
317 touchConnection( ce.getConnection() );
320 System.out.println("REMOVING EDGE: " + edge);
321 Object obj = ElementUtils.getObject(edge);
322 if (obj instanceof EdgeResource) {
323 cu.remove((EdgeResource) obj);
325 edgeElementMonitor.worked(1);
327 edgeElementMonitor.setTaskName("Removing elements");
329 for (IElement element : elements) {
330 ConnectionHandler ch = element.getElementClass().getAtMostOneItemOfClass(ConnectionHandler.class);
333 System.out.println("MARKING CONNECTION TO BE REMOVED: " + element);
334 connectionsToRemove.add( (Resource) ElementUtils.getObject(element) );
336 ConnectionEntity ce = element.getHint(ElementHints.KEY_CONNECTION_ENTITY);
339 System.out.println("REMOVING BRANCH POINT: " + element);
340 new RemoveBranchpoint(element).perform(graph);
341 touchConnection( ce.getConnection() );
344 System.out.println("REMOVING ELEMENT: " + element);
346 Object obj = ElementUtils.getObject(element);
347 if (obj instanceof Resource) {
348 // Get terminal connections for element
349 Collection<Resource> connectors = cu.getTerminalConnectors((Resource) obj, null);
350 for (Resource connector : connectors) {
351 Resource connection = ConnectionUtil.tryGetConnection(graph, connector);
352 if (connection != null)
353 touchConnection( connection );
354 cu.disconnectFromAllRouteNodes(connector);
357 new RemoveElement((Resource)d.getHint(DiagramModelHints.KEY_DIAGRAM_RESOURCE), (Resource)element.getHint(ElementHints.KEY_OBJECT)).perform(graph);
361 edgeElementMonitor.worked(1);
363 SubMonitor connectionsMonitor = subMonitor.split(1); // Consume 50 % of the monitor
364 connectionsMonitor.setWorkRemaining(touchedConnections.size()); // set work remaining to size of edges and elements
365 connectionsMonitor.setTaskName("Pruning connectors");
366 // Check all touched connections to see if they are empty.
367 for (Resource connection : touchedConnections) {
368 int removedConnectors = cu.removeUnusedConnectors(connection);
370 System.out.println("PRUNED " + removedConnectors + " CONNECTORS FROM TOUCHED CONNECTION " + connection);
372 int removedInteriorRouteNodes = cu.removeExtraInteriorRouteNodes(connection);
374 System.out.println("PRUNED " + removedInteriorRouteNodes + " INTERIOR ROUTE NODES FROM TOUCHED CONNECTION " + connection);
375 if (removedInteriorRouteNodes == 0)
378 int connectors = cu.getConnectedConnectors(connection, null).size();
380 System.out.println("\t" + connectors + " CONNECTORS LEFT");
381 if (connectors < 2) {
382 connectionsToRemove.add(connection);
384 connectionsMonitor.worked(1);
386 SubMonitor connectionsMonitor2 = subMonitor.split(1); // Consume 50 % of the monitor
387 connectionsMonitor2.setWorkRemaining(connectionsToRemove.size()); // set work remaini
388 connectionsMonitor2.setTaskName("Removing leftover connections");
389 // Remove selected/left-over empty connections
390 for (Resource connection : connectionsToRemove) {
392 System.out.println("REMOVING CONNECTION: " + connection);
393 RemoveElement.removeElement(graph, (Resource)d.getHint(DiagramModelHints.KEY_DIAGRAM_RESOURCE), connection);
394 connectionsMonitor2.worked(1);
398 private void validateRemoval(ReadGraph graph) throws DatabaseException, CannotRemoveException {
399 ArrayList<String> problems = new ArrayList<String>(elements.size());
400 for (IElement element : elements) {
401 Object obj = ElementUtils.getObject(element);
402 if (obj instanceof Resource) {
403 Remover remover = RemoverUtil.getPossibleRemover(graph, (Resource) obj);
404 if (remover != null) {
405 String problem = remover.canRemove(graph, new HashMap<Object, Object>(4));
406 if (problem != null) {
407 problems.add(problem);
412 if (!problems.isEmpty()) {
413 throw new CannotRemoveException(EString.implode(problems));
417 void touchConnection(Object c) {
419 System.out.println("TOUCHED CONNECTION: " + c);
420 if (c instanceof IElement) {
421 Object obj = ElementUtils.getObject((IElement) c);
422 if (obj instanceof Resource)
423 touchedConnections.add((Resource) obj);
424 } else if (c instanceof Resource) {
425 touchedConnections.add((Resource) c);
435 void message(final String message) {
436 if (statusLine == null)
438 swtExec(new Runnable() {
441 statusLine.setMessage(message);
442 statusLine.setErrorMessage(null);
447 void error(final String message) {
448 if (statusLine == null)
450 swtExec(new Runnable() {
453 statusLine.setErrorMessage(message);
458 void swtExec(Runnable r) {
459 ThreadUtils.asyncExec(SWTThread.getThreadAccess(Display.getDefault()), r);