1 /*******************************************************************************
\r
2 * Copyright (c) 2007, 2010 Association for Decentralized Information Management
\r
3 * in Industry THTH ry.
\r
4 * All rights reserved. This program and the accompanying materials
\r
5 * are made available under the terms of the Eclipse Public License v1.0
\r
6 * which accompanies this distribution, and is available at
\r
7 * http://www.eclipse.org/legal/epl-v10.html
\r
10 * VTT Technical Research Centre of Finland - initial API and implementation
\r
11 *******************************************************************************/
\r
12 package org.simantics.diagram.handler;
\r
14 import java.util.ArrayDeque;
\r
15 import java.util.ArrayList;
\r
16 import java.util.Collection;
\r
17 import java.util.Deque;
\r
18 import java.util.HashMap;
\r
19 import java.util.HashSet;
\r
20 import java.util.Set;
\r
22 import org.eclipse.core.runtime.IProgressMonitor;
\r
23 import org.eclipse.core.runtime.IStatus;
\r
24 import org.eclipse.core.runtime.Status;
\r
25 import org.eclipse.jface.action.IStatusLineManager;
\r
26 import org.eclipse.swt.widgets.Display;
\r
27 import org.simantics.DatabaseJob;
\r
28 import org.simantics.Simantics;
\r
29 import org.simantics.db.ReadGraph;
\r
30 import org.simantics.db.Resource;
\r
31 import org.simantics.db.WriteGraph;
\r
32 import org.simantics.db.common.request.WriteRequest;
\r
33 import org.simantics.db.exception.DatabaseException;
\r
34 import org.simantics.db.layer0.adapter.Remover;
\r
35 import org.simantics.db.layer0.exception.CannotRemoveException;
\r
36 import org.simantics.db.layer0.util.RemoverUtil;
\r
37 import org.simantics.diagram.adapter.ElementFactoryUtil;
\r
38 import org.simantics.diagram.content.ConnectionUtil;
\r
39 import org.simantics.diagram.content.EdgeResource;
\r
40 import org.simantics.diagram.internal.Activator;
\r
41 import org.simantics.diagram.synchronization.ISynchronizationContext;
\r
42 import org.simantics.diagram.synchronization.graph.RemoveBranchpoint;
\r
43 import org.simantics.diagram.synchronization.graph.RemoveElement;
\r
44 import org.simantics.diagram.ui.DiagramModelHints;
\r
45 import org.simantics.g2d.canvas.impl.DependencyReflection.Dependency;
\r
46 import org.simantics.g2d.connection.ConnectionEntity;
\r
47 import org.simantics.g2d.connection.handler.ConnectionHandler;
\r
48 import org.simantics.g2d.diagram.IDiagram;
\r
49 import org.simantics.g2d.diagram.handler.PickRequest.PickFilter;
\r
50 import org.simantics.g2d.diagram.handler.Relationship;
\r
51 import org.simantics.g2d.diagram.handler.RelationshipHandler;
\r
52 import org.simantics.g2d.diagram.handler.RelationshipHandler.Relation;
\r
53 import org.simantics.g2d.diagram.handler.Topology;
\r
54 import org.simantics.g2d.diagram.handler.Topology.Connection;
\r
55 import org.simantics.g2d.diagram.handler.Topology.Terminal;
\r
56 import org.simantics.g2d.diagram.participant.AbstractDiagramParticipant;
\r
57 import org.simantics.g2d.diagram.participant.Selection;
\r
58 import org.simantics.g2d.element.ElementClass;
\r
59 import org.simantics.g2d.element.ElementHints;
\r
60 import org.simantics.g2d.element.ElementUtils;
\r
61 import org.simantics.g2d.element.IElement;
\r
62 import org.simantics.g2d.element.handler.BendsHandler;
\r
63 import org.simantics.g2d.element.handler.EdgeVisuals.EdgeEnd;
\r
64 import org.simantics.g2d.element.handler.TerminalTopology;
\r
65 import org.simantics.scenegraph.g2d.events.EventHandlerReflection.EventHandler;
\r
66 import org.simantics.scenegraph.g2d.events.command.CommandEvent;
\r
67 import org.simantics.scenegraph.g2d.events.command.Commands;
\r
68 import org.simantics.utils.logging.TimeLogger;
\r
69 import org.simantics.utils.strings.EString;
\r
70 import org.simantics.utils.threads.SWTThread;
\r
71 import org.simantics.utils.threads.ThreadUtils;
\r
72 import org.simantics.utils.ui.dialogs.ShowMessage;
\r
75 * DeleteHandler is a canvas handler for Commands.DELETE commands for an
\r
79 * The handler attempts to delete the current selection for pointer 0, meaning
\r
80 * {@link Selection#SELECTION0}.
\r
84 * The handler logic goes as follows:
\r
87 * <li>Separate nodes and edges form the the removed selection</li>
\r
88 * <li>Find all edges attached to the removed nodes and remove them too</li>
\r
89 * <li>Delete connections that contain less than 2 terminal connections</li>
\r
92 * @see Selection for the current diagram selection source
\r
94 * @author Tuukka Lehtonen
\r
96 * TODO: start using WorkbenchStatusLine participant
\r
98 public class DeleteHandler extends AbstractDiagramParticipant {
\r
100 public static final boolean DEBUG_DELETE = false;
\r
102 @Dependency Selection sel;
\r
104 private final IStatusLineManager statusLine;
\r
106 public DeleteHandler(IStatusLineManager statusLine) {
\r
107 this.statusLine = statusLine;
\r
110 @EventHandler(priority = 0)
\r
111 public boolean handleCommand(CommandEvent e) {
\r
112 if (Commands.DELETE.equals( e.command )) {
\r
113 IDiagram d = diagram;
\r
117 Set<IElement> ss = sel.getSelection(0);
\r
121 if (delete(d, ss)) {
\r
130 public boolean delete(final IDiagram d, Collection<IElement> ss) {
\r
131 TimeLogger.resetTimeAndLog(getClass(), "delete");
\r
133 Topology topology = d.getDiagramClass().getAtMostOneItemOfClass(Topology.class);
\r
134 RelationshipHandler erh = d.getDiagramClass().getAtMostOneItemOfClass(RelationshipHandler.class);
\r
136 if (DEBUG_DELETE) {
\r
137 System.out.println("diagram: " + d);
\r
138 for (IElement e : d.getSnapshot()) {
\r
139 ElementClass ec = e.getElementClass();
\r
140 System.out.println("\t-element " + e);
\r
141 System.out.println("\t -class " + e.getElementClass());
\r
142 if (ec.containsClass(ConnectionHandler.class)) {
\r
143 ConnectionEntity ce = e.getHint(ElementHints.KEY_CONNECTION_ENTITY);
\r
144 for (IElement child : ce.getBranchPoints(null)) {
\r
145 System.out.println("\t\t-branch " + child);
\r
146 System.out.println("\t\t -class " + child.getElementClass());
\r
148 for (IElement child : ce.getSegments(null)) {
\r
149 System.out.println("\t\t-segment " + child);
\r
150 System.out.println("\t\t -class " + child.getElementClass());
\r
154 System.out.println("delete requested for elements:");
\r
155 for (IElement e : ss)
\r
156 System.out.println("\t-element " + e);
\r
159 // Analyze removals:
\r
160 // - separate elements and connections
\r
161 // - find all connections attached to the elements and remove them too
\r
162 Deque<IElement> elementsToProcess = new ArrayDeque<IElement>(ss);
\r
163 Set<IElement> processedElements = new HashSet<IElement>();
\r
164 Set<IElement> relationshipsProcessedForElement = new HashSet<IElement>();
\r
166 final Collection<IElement> elements = new ArrayList<IElement>();
\r
167 final Set<IElement> edges = new HashSet<IElement>();
\r
168 Collection<Connection> connections = new ArrayList<Connection>();
\r
169 Collection<Terminal> terminals = new ArrayList<Terminal>();
\r
170 Collection<Relation> relations = new ArrayList<Relation>();
\r
171 while (!elementsToProcess.isEmpty()) {
\r
172 IElement el = elementsToProcess.pollFirst();
\r
174 if (relationshipsProcessedForElement.add(el)) {
\r
175 // Check for relationships to other elements and mark child
\r
176 // elements to be removed before the parent element.
\r
178 erh.getRelations(d, el, relations);
\r
179 if (!relations.isEmpty()) {
\r
180 boolean restart = false;
\r
181 for (Relation r : relations) {
\r
182 //System.out.println("FOUND RELATION: " + r);
\r
183 if (r.getRelationship() == Relationship.PARENT_OF) {
\r
184 if ((r.getObject() instanceof IElement)) {
\r
185 IElement ee = (IElement) r.getObject();
\r
186 if (d.containsElement(ee)) {
\r
187 //System.out.println("DIAGRAM CONTAINS OBJECT: " + r.getObject());
\r
189 // Mark the object also to be processed for removal.
\r
190 elementsToProcess.addFirst(ee);
\r
197 // Only process this element after we're sure that
\r
198 // all its children have been processed.
\r
199 elementsToProcess.addLast(el);
\r
205 if (!processedElements.add(el))
\r
208 TerminalTopology tt = el.getElementClass().getAtMostOneItemOfClass(TerminalTopology.class);
\r
209 BendsHandler bh = el.getElementClass().getAtMostOneItemOfClass(BendsHandler.class);
\r
212 // Verify that the edge is NOT between two branch points.
\r
213 // If it is, do not allow deletion because it is the only case
\r
214 // which can break a connection tree into a connection forest.
\r
215 // We do not want that to happen.
\r
216 Connection begin = topology.getConnection(el, EdgeEnd.Begin);
\r
217 Connection end = topology.getConnection(el, EdgeEnd.End);
\r
219 // Try to work with cases where the model is somewhat corrupt.
\r
220 if (begin != null && end != null) {
\r
221 if (PickFilter.FILTER_BRANCH_POINT.accept(begin.node) && PickFilter.FILTER_BRANCH_POINT.accept(end.node)) {
\r
222 error("Deletion of branch point connecting edges is not allowed. Must be connected to a node terminal.");
\r
228 System.out.println("ADDED EDGE FOR REMOVAL: " + el);
\r
232 System.out.println("ADDED ELEMENT FOR REMOVAL: " + el);
\r
237 tt.getTerminals(el, terminals);
\r
238 connections.clear();
\r
239 for (Terminal terminal : terminals)
\r
240 topology.getConnections(el, terminal, connections);
\r
241 for (Connection c : connections) {
\r
242 if (c.edge != null) {
\r
243 if (c.edge.getElementClass().containsClass(BendsHandler.class))
\r
246 System.out.println("TERMINAL CONNECTION WILL BE DISCONNECTED: " + c);
\r
253 if (elements.isEmpty() && edges.isEmpty())
\r
256 if (DEBUG_DELETE) {
\r
257 System.out.println("gathered elements to delete:");
\r
258 System.out.println("\telements:");
\r
259 if (!elements.isEmpty())
\r
260 for (IElement e : elements)
\r
261 System.out.println("\t\t" + e);
\r
262 System.out.println("\tedges:");
\r
263 if (!edges.isEmpty())
\r
264 for (IElement e : edges)
\r
265 System.out.println("\t\t" + e);
\r
268 final IDiagram diagram = this.diagram;
\r
269 final ISynchronizationContext syncContext = ElementFactoryUtil.getContextChecked(diagram);
\r
271 new DatabaseJob("Delete selection") {
\r
273 protected IStatus run(IProgressMonitor monitor) {
\r
276 return Status.OK_STATUS;
\r
277 } catch (CannotRemoveException e) {
\r
278 ShowMessage.showInformation("Delete Selection Was Denied", e.getLocalizedMessage());
\r
279 return new Status(IStatus.CANCEL, Activator.PLUGIN_ID, e.getLocalizedMessage(), e);
\r
280 } catch (DatabaseException e) {
\r
281 return new Status(IStatus.ERROR, Activator.PLUGIN_ID, "Unexpected error in delete.", e);
\r
288 private void delete(IProgressMonitor monitor) throws DatabaseException {
\r
289 Simantics.getSession().syncRequest(new WriteRequest() {
\r
290 Set<Resource> connectionsToRemove = new HashSet<Resource>();
\r
291 Set<Resource> touchedConnections = new HashSet<Resource>();
\r
294 public void perform(WriteGraph graph) throws DatabaseException {
\r
295 validateRemoval(graph);
\r
296 graph.markUndoPoint();
\r
298 ConnectionUtil cu = new ConnectionUtil(graph);
\r
301 for (IElement edge : edges) {
\r
302 ConnectionEntity ce = edge.getHint(ElementHints.KEY_CONNECTION_ENTITY);
\r
303 touchConnection( ce.getConnection() );
\r
306 System.out.println("REMOVING EDGE: " + edge);
\r
307 Object obj = ElementUtils.getObject(edge);
\r
308 if (obj instanceof EdgeResource) {
\r
309 cu.remove((EdgeResource) obj);
\r
314 for (IElement element : elements) {
\r
315 ConnectionHandler ch = element.getElementClass().getAtMostOneItemOfClass(ConnectionHandler.class);
\r
318 System.out.println("MARKING CONNECTION TO BE REMOVED: " + element);
\r
319 connectionsToRemove.add( (Resource) ElementUtils.getObject(element) );
\r
321 ConnectionEntity ce = element.getHint(ElementHints.KEY_CONNECTION_ENTITY);
\r
324 System.out.println("REMOVING BRANCH POINT: " + element);
\r
325 new RemoveBranchpoint(element).perform(graph);
\r
326 touchConnection( ce.getConnection() );
\r
329 System.out.println("REMOVING ELEMENT: " + element);
\r
331 Object obj = ElementUtils.getObject(element);
\r
332 if (obj instanceof Resource) {
\r
333 // Get terminal connections for element
\r
334 Collection<Resource> connectors = cu.getTerminalConnectors((Resource) obj, null);
\r
335 for (Resource connector : connectors) {
\r
336 Resource connection = ConnectionUtil.tryGetConnection(graph, connector);
\r
337 if (connection != null)
\r
338 touchConnection( connection );
\r
339 cu.disconnectFromAllRouteNodes(connector);
\r
342 new RemoveElement((Resource)d.getHint(DiagramModelHints.KEY_DIAGRAM_RESOURCE), (Resource)element.getHint(ElementHints.KEY_OBJECT)).perform(graph);
\r
348 // Check all touched connections to see if they are empty.
\r
349 for (Resource connection : touchedConnections) {
\r
350 int removedConnectors = cu.removeUnusedConnectors(connection);
\r
352 System.out.println("PRUNED " + removedConnectors + " CONNECTORS FROM TOUCHED CONNECTION " + connection);
\r
354 int removedInteriorRouteNodes = cu.removeExtraInteriorRouteNodes(connection);
\r
356 System.out.println("PRUNED " + removedInteriorRouteNodes + " INTERIOR ROUTE NODES FROM TOUCHED CONNECTION " + connection);
\r
357 if (removedInteriorRouteNodes == 0)
\r
360 int connectors = cu.getConnectedConnectors(connection, null).size();
\r
362 System.out.println("\t" + connectors + " CONNECTORS LEFT");
\r
363 if (connectors < 2) {
\r
364 connectionsToRemove.add(connection);
\r
368 // Remove selected/left-over empty connections
\r
369 for (Resource connection : connectionsToRemove) {
\r
371 System.out.println("REMOVING CONNECTION: " + connection);
\r
372 RemoveElement.removeElement(graph, (Resource)d.getHint(DiagramModelHints.KEY_DIAGRAM_RESOURCE), connection);
\r
376 private void validateRemoval(ReadGraph graph) throws DatabaseException, CannotRemoveException {
\r
377 ArrayList<String> problems = new ArrayList<String>(elements.size());
\r
378 for (IElement element : elements) {
\r
379 Object obj = ElementUtils.getObject(element);
\r
380 if (obj instanceof Resource) {
\r
381 Remover remover = RemoverUtil.getPossibleRemover(graph, (Resource) obj);
\r
382 if (remover != null) {
\r
383 String problem = remover.canRemove(graph, new HashMap<Object, Object>(4));
\r
384 if (problem != null) {
\r
385 problems.add(problem);
\r
390 if (!problems.isEmpty()) {
\r
391 throw new CannotRemoveException(EString.implode(problems));
\r
395 void touchConnection(Object c) {
\r
397 System.out.println("TOUCHED CONNECTION: " + c);
\r
398 if (c instanceof IElement) {
\r
399 Object obj = ElementUtils.getObject((IElement) c);
\r
400 if (obj instanceof Resource)
\r
401 touchedConnections.add((Resource) obj);
\r
402 } else if (c instanceof Resource) {
\r
403 touchedConnections.add((Resource) c);
\r
413 void message(final String message) {
\r
414 if (statusLine == null)
\r
416 swtExec(new Runnable() {
\r
418 public void run() {
\r
419 statusLine.setMessage(message);
\r
420 statusLine.setErrorMessage(null);
\r
425 void error(final String message) {
\r
426 if (statusLine == null)
\r
428 swtExec(new Runnable() {
\r
430 public void run() {
\r
431 statusLine.setErrorMessage(message);
\r
436 void swtExec(Runnable r) {
\r
437 ThreadUtils.asyncExec(SWTThread.getThreadAccess(Display.getDefault()), r);
\r