]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.diagram/src/org/simantics/diagram/handler/DeleteHandler.java
5fa94ebc7a2787d1c9b3a51db9fe508ef04106dc
[simantics/platform.git] / bundles / org.simantics.diagram / src / org / simantics / diagram / handler / DeleteHandler.java
1 /*******************************************************************************
2  * Copyright (c) 2007, 2010 Association for Decentralized Information Management
3  * in Industry THTH ry.
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
8  *
9  * Contributors:
10  *     VTT Technical Research Centre of Finland - initial API and implementation
11  *******************************************************************************/
12 package org.simantics.diagram.handler;
13
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;
20 import java.util.Set;
21
22 import org.eclipse.core.runtime.IProgressMonitor;
23 import org.eclipse.core.runtime.IStatus;
24 import org.eclipse.core.runtime.Status;
25 import org.eclipse.core.runtime.SubMonitor;
26 import org.eclipse.jface.action.IStatusLineManager;
27 import org.eclipse.swt.widgets.Display;
28 import org.simantics.DatabaseJob;
29 import org.simantics.Simantics;
30 import org.simantics.db.ReadGraph;
31 import org.simantics.db.Resource;
32 import org.simantics.db.WriteGraph;
33 import org.simantics.db.common.request.WriteRequest;
34 import org.simantics.db.exception.DatabaseException;
35 import org.simantics.db.layer0.adapter.Remover;
36 import org.simantics.db.layer0.exception.CannotRemoveException;
37 import org.simantics.db.layer0.util.RemoverUtil;
38 import org.simantics.diagram.adapter.ElementFactoryUtil;
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.ISynchronizationContext;
43 import org.simantics.diagram.synchronization.graph.RemoveBranchpoint;
44 import org.simantics.diagram.synchronization.graph.RemoveElement;
45 import org.simantics.diagram.ui.DiagramModelHints;
46 import org.simantics.g2d.canvas.impl.DependencyReflection.Dependency;
47 import org.simantics.g2d.connection.ConnectionEntity;
48 import org.simantics.g2d.connection.handler.ConnectionHandler;
49 import org.simantics.g2d.diagram.IDiagram;
50 import org.simantics.g2d.diagram.handler.PickRequest.PickFilter;
51 import org.simantics.g2d.diagram.handler.Relationship;
52 import org.simantics.g2d.diagram.handler.RelationshipHandler;
53 import org.simantics.g2d.diagram.handler.RelationshipHandler.Relation;
54 import org.simantics.g2d.diagram.handler.Topology;
55 import org.simantics.g2d.diagram.handler.Topology.Connection;
56 import org.simantics.g2d.diagram.handler.Topology.Terminal;
57 import org.simantics.g2d.diagram.participant.AbstractDiagramParticipant;
58 import org.simantics.g2d.diagram.participant.Selection;
59 import org.simantics.g2d.element.ElementClass;
60 import org.simantics.g2d.element.ElementHints;
61 import org.simantics.g2d.element.ElementUtils;
62 import org.simantics.g2d.element.IElement;
63 import org.simantics.g2d.element.handler.BendsHandler;
64 import org.simantics.g2d.element.handler.EdgeVisuals.EdgeEnd;
65 import org.simantics.g2d.element.handler.TerminalTopology;
66 import org.simantics.scenegraph.g2d.events.EventHandlerReflection.EventHandler;
67 import org.simantics.scenegraph.g2d.events.command.CommandEvent;
68 import org.simantics.scenegraph.g2d.events.command.Commands;
69 import org.simantics.utils.logging.TimeLogger;
70 import org.simantics.utils.strings.EString;
71 import org.simantics.utils.threads.SWTThread;
72 import org.simantics.utils.threads.ThreadUtils;
73 import org.simantics.utils.ui.dialogs.ShowMessage;
74
75 /**
76  * DeleteHandler is a canvas handler for Commands.DELETE commands for an
77  * IDiagram.
78  * 
79  * <p>
80  * The handler attempts to delete the current selection for pointer 0, meaning
81  * {@link Selection#SELECTION0}.
82  * </p>
83  * 
84  * <p>
85  * The handler logic goes as follows:
86  * </p>
87  * <ol>
88  * <li>Separate nodes and edges form the the removed selection</li>
89  * <li>Find all edges attached to the removed nodes and remove them too</li>
90  * <li>Delete connections that contain less than 2 terminal connections</li>
91  * </ol>
92  * 
93  * @see Selection for the current diagram selection source
94  * 
95  * @author Tuukka Lehtonen
96  * 
97  * TODO: start using WorkbenchStatusLine participant
98  */
99 public class DeleteHandler extends AbstractDiagramParticipant {
100
101     public static final boolean DEBUG_DELETE = false;
102
103     @Dependency Selection sel;
104
105     private final IStatusLineManager statusLine;
106
107     public DeleteHandler(IStatusLineManager statusLine) {
108         this.statusLine = statusLine;
109     }
110
111     @EventHandler(priority = 0)
112     public boolean handleCommand(CommandEvent e) {
113         if (Commands.DELETE.equals( e.command )) {
114             IDiagram d = diagram;
115             if (d == null)
116                 return true;
117
118             Set<IElement> ss = sel.getSelection(0);
119             if (ss.isEmpty())
120                 return true;
121
122             if (delete(d, ss)) {
123                 sel.clear(0);
124             }
125
126             return true;
127         }
128         return false;
129     }
130
131     public boolean delete(final IDiagram d, Collection<IElement> ss) {
132         TimeLogger.resetTimeAndLog(getClass(), "delete");
133         
134         Topology topology = d.getDiagramClass().getAtMostOneItemOfClass(Topology.class);
135         RelationshipHandler erh = d.getDiagramClass().getAtMostOneItemOfClass(RelationshipHandler.class);
136
137         if (DEBUG_DELETE) {
138             System.out.println("diagram: " + d);
139             for (IElement e : d.getSnapshot()) {
140                 ElementClass ec = e.getElementClass();
141                 System.out.println("\t-element " + e);
142                 System.out.println("\t  -class " + e.getElementClass());
143                 if (ec.containsClass(ConnectionHandler.class)) {
144                     ConnectionEntity ce = e.getHint(ElementHints.KEY_CONNECTION_ENTITY);
145                     for (IElement child : ce.getBranchPoints(null)) {
146                         System.out.println("\t\t-branch " + child);
147                         System.out.println("\t\t  -class " + child.getElementClass());
148                     }
149                     for (IElement child : ce.getSegments(null)) {
150                         System.out.println("\t\t-segment " + child);
151                         System.out.println("\t\t  -class " + child.getElementClass());
152                     }
153                 }
154             }
155             System.out.println("delete requested for elements:");
156             for (IElement e : ss)
157                 System.out.println("\t-element " + e);
158         }
159
160         // Analyze removals:
161         //  - separate elements and connections
162         //  - find all connections attached to the elements and remove them too
163         Deque<IElement> elementsToProcess = new ArrayDeque<IElement>(ss);
164         Set<IElement> processedElements = new HashSet<IElement>();
165         Set<IElement> relationshipsProcessedForElement = new HashSet<IElement>();
166
167         final Collection<IElement> elements = new ArrayList<IElement>();
168         final Set<IElement> edges = new HashSet<IElement>();
169         Collection<Connection> connections = new ArrayList<Connection>();
170         Collection<Terminal> terminals = new ArrayList<Terminal>();
171         Collection<Relation> relations = new ArrayList<Relation>();
172         while (!elementsToProcess.isEmpty()) {
173             IElement el = elementsToProcess.pollFirst();
174
175             if (relationshipsProcessedForElement.add(el)) {
176                 // Check for relationships to other elements and mark child
177                 // elements to be removed before the parent element.
178                 relations.clear();
179                 erh.getRelations(d, el, relations);
180                 if (!relations.isEmpty()) {
181                     boolean restart = false;
182                     for (Relation r : relations) {
183                         //System.out.println("FOUND RELATION: " + r);
184                         if (r.getRelationship() == Relationship.PARENT_OF) {
185                             if ((r.getObject() instanceof IElement)) {
186                                 IElement ee = (IElement) r.getObject();
187                                 if (d.containsElement(ee)) {
188                                     //System.out.println("DIAGRAM CONTAINS OBJECT: " + r.getObject());
189
190                                     // Mark the object also to be processed for removal.
191                                     elementsToProcess.addFirst(ee);
192                                     restart = true;
193                                 }
194                             }
195                         }
196                     }
197                     if (restart) {
198                         // Only process this element after we're sure that
199                         // all its children have been processed.
200                         elementsToProcess.addLast(el);
201                         continue;
202                     }
203                 }
204             }
205
206             if (!processedElements.add(el))
207                 continue;
208
209             TerminalTopology tt = el.getElementClass().getAtMostOneItemOfClass(TerminalTopology.class);
210             BendsHandler bh = el.getElementClass().getAtMostOneItemOfClass(BendsHandler.class);
211
212             if (bh != null) {
213                 // Verify that the edge is NOT between two branch points.
214                 // If it is, do not allow deletion because it is the only case
215                 // which can break a connection tree into a connection forest.
216                 // We do not want that to happen.
217                 Connection begin = topology.getConnection(el, EdgeEnd.Begin);
218                 Connection end = topology.getConnection(el, EdgeEnd.End);
219
220                 // Try to work with cases where the model is somewhat corrupt.
221                 if (begin != null && end != null) {
222                     if (PickFilter.FILTER_BRANCH_POINT.accept(begin.node) && PickFilter.FILTER_BRANCH_POINT.accept(end.node)) {
223                         error("Deletion of branch point connecting edges is not allowed. Must be connected to a node terminal.");
224                         return false;
225                     }
226                 }
227
228                 if (DEBUG_DELETE)
229                     System.out.println("ADDED EDGE FOR REMOVAL: " + el);
230                 edges.add(el);
231             } else {
232                 if (DEBUG_DELETE)
233                     System.out.println("ADDED ELEMENT FOR REMOVAL: " + el);
234                 elements.add(el);
235
236                 if (tt != null) {
237                     terminals.clear();
238                     tt.getTerminals(el, terminals);
239                     connections.clear();
240                     for (Terminal terminal : terminals)
241                         topology.getConnections(el, terminal, connections);
242                     for (Connection c : connections) {
243                         if (c.edge != null) {
244                             if (c.edge.getElementClass().containsClass(BendsHandler.class))
245                                 edges.add(c.edge);
246                             if (DEBUG_DELETE)
247                                 System.out.println("TERMINAL CONNECTION WILL BE DISCONNECTED: " + c);
248                         }
249                     }
250                 }
251             }
252         }
253
254         if (elements.isEmpty() && edges.isEmpty())
255             return false;
256
257         if (DEBUG_DELETE) {
258             System.out.println("gathered elements to delete:");
259             System.out.println("\telements:");
260             if (!elements.isEmpty())
261                 for (IElement e : elements)
262                     System.out.println("\t\t" + e);
263             System.out.println("\tedges:");
264             if (!edges.isEmpty())
265                 for (IElement e : edges)
266                     System.out.println("\t\t" + e);
267         }
268
269         final IDiagram diagram = this.diagram;
270         final ISynchronizationContext syncContext = ElementFactoryUtil.getContextChecked(diagram); 
271
272         new DatabaseJob("Delete selection") {
273             @Override
274             protected IStatus run(IProgressMonitor monitor) {
275                 try {
276                     delete(monitor);
277                     return Status.OK_STATUS;
278                 } catch (CannotRemoveException e) {
279                         ShowMessage.showInformation("Delete Selection Was Denied", e.getLocalizedMessage());
280                     return new Status(IStatus.CANCEL, Activator.PLUGIN_ID, e.getLocalizedMessage(), e);
281                 } catch (DatabaseException e) {
282                     return new Status(IStatus.ERROR, Activator.PLUGIN_ID, "Unexpected error in delete.", e);
283                 } finally {
284                     error(null);
285                     monitor.done();
286                 }
287             }
288
289             private void delete(IProgressMonitor monitor) throws DatabaseException {
290                 SubMonitor subMonitor = SubMonitor.convert(monitor, 4);
291                 Simantics.getSession().syncRequest(new WriteRequest() {
292                     Set<Resource> connectionsToRemove = new HashSet<Resource>();
293                     Set<Resource> touchedConnections = new HashSet<Resource>();
294
295                     @Override
296                     public void perform(WriteGraph graph) throws DatabaseException {
297                         validateRemoval(graph);
298                         graph.markUndoPoint();
299
300                         ConnectionUtil cu = new ConnectionUtil(graph);
301                         SubMonitor edgeElementMonitor = subMonitor.split(2); // Consume 50 % of the monitor
302                         edgeElementMonitor.setWorkRemaining(edges.size() + elements.size()); // set work remaining to size of edges and elements
303                         edgeElementMonitor.setTaskName("Removing edges");
304                         // Remove edges
305                         for (IElement edge : edges) {
306                             ConnectionEntity ce = edge.getHint(ElementHints.KEY_CONNECTION_ENTITY);
307                             touchConnection( ce.getConnection() );
308
309                             if (DEBUG_DELETE)
310                                 System.out.println("REMOVING EDGE: " + edge);
311                             Object obj = ElementUtils.getObject(edge);
312                             if (obj instanceof EdgeResource) {
313                                 cu.remove((EdgeResource) obj);
314                             }
315                             edgeElementMonitor.worked(1);
316                         }
317                         edgeElementMonitor.setTaskName("Removing elements");
318                         // Remove elements
319                         for (IElement element : elements) {
320                             ConnectionHandler ch = element.getElementClass().getAtMostOneItemOfClass(ConnectionHandler.class);
321                             if (ch != null) {
322                                 if (DEBUG_DELETE)
323                                     System.out.println("MARKING CONNECTION TO BE REMOVED: " + element);
324                                 connectionsToRemove.add( (Resource) ElementUtils.getObject(element) );
325                             } else {
326                                 ConnectionEntity ce = element.getHint(ElementHints.KEY_CONNECTION_ENTITY);
327                                 if(ce != null) {
328                                     if (DEBUG_DELETE)
329                                         System.out.println("REMOVING BRANCH POINT: " + element);
330                                     new RemoveBranchpoint(element).perform(graph);
331                                     touchConnection( ce.getConnection() );
332                                 } else {
333                                     if (DEBUG_DELETE)
334                                         System.out.println("REMOVING ELEMENT: " + element);
335
336                                     Object obj = ElementUtils.getObject(element);
337                                     if (obj instanceof Resource) {
338                                         // Get terminal connections for element
339                                         Collection<Resource> connectors = cu.getTerminalConnectors((Resource) obj, null);
340                                         for (Resource connector : connectors) {
341                                             Resource connection = ConnectionUtil.tryGetConnection(graph, connector);
342                                             if (connection != null)
343                                                 touchConnection( connection );
344                                             cu.disconnectFromAllRouteNodes(connector);
345                                         }
346
347                                         new RemoveElement((Resource)d.getHint(DiagramModelHints.KEY_DIAGRAM_RESOURCE), (Resource)element.getHint(ElementHints.KEY_OBJECT)).perform(graph);
348                                     }
349                                 }
350                             }
351                             edgeElementMonitor.worked(1);
352                         }
353                         SubMonitor connectionsMonitor = subMonitor.split(1); // Consume 50 % of the monitor
354                         connectionsMonitor.setWorkRemaining(touchedConnections.size()); // set work remaining to size of edges and elements
355                         connectionsMonitor.setTaskName("Pruning connectors");
356                         // Check all touched connections to see if they are empty.
357                         for (Resource connection : touchedConnections) {
358                             int removedConnectors = cu.removeUnusedConnectors(connection);
359                             if (DEBUG_DELETE)
360                                 System.out.println("PRUNED " + removedConnectors + " CONNECTORS FROM TOUCHED CONNECTION " + connection);
361                             while (true) {
362                                 int removedInteriorRouteNodes = cu.removeExtraInteriorRouteNodes(connection);
363                                 if (DEBUG_DELETE)
364                                     System.out.println("PRUNED " + removedInteriorRouteNodes + " INTERIOR ROUTE NODES FROM TOUCHED CONNECTION " + connection);
365                                 if (removedInteriorRouteNodes == 0)
366                                     break;
367                             }
368                             int connectors = cu.getConnectedConnectors(connection, null).size();
369                             if (DEBUG_DELETE)
370                                 System.out.println("\t" + connectors + " CONNECTORS LEFT");
371                             if (connectors < 2) {
372                                 connectionsToRemove.add(connection);
373                             }
374                             connectionsMonitor.worked(1);
375                         }
376                         SubMonitor connectionsMonitor2 = subMonitor.split(1); // Consume 50 % of the monitor
377                         connectionsMonitor2.setWorkRemaining(connectionsToRemove.size()); // set work remaini
378                         connectionsMonitor2.setTaskName("Removing leftover connections");
379                         // Remove selected/left-over empty connections
380                         for (Resource connection : connectionsToRemove) {
381                             if (DEBUG_DELETE)
382                                 System.out.println("REMOVING CONNECTION: " + connection);
383                             RemoveElement.removeElement(graph, (Resource)d.getHint(DiagramModelHints.KEY_DIAGRAM_RESOURCE), connection);
384                             connectionsMonitor2.worked(1);
385                         }
386                     }
387
388                     private void validateRemoval(ReadGraph graph) throws DatabaseException, CannotRemoveException {
389                         ArrayList<String> problems = new ArrayList<String>(elements.size());
390                         for (IElement element : elements) {
391                             Object obj = ElementUtils.getObject(element);
392                             if (obj instanceof Resource) {
393                                 Remover remover = RemoverUtil.getPossibleRemover(graph, (Resource) obj);
394                                 if (remover != null) {
395                                     String problem = remover.canRemove(graph, new HashMap<Object, Object>(4));
396                                     if (problem != null) {
397                                         problems.add(problem);
398                                     }
399                                 }
400                             }
401                         }
402                         if (!problems.isEmpty()) {
403                             throw new CannotRemoveException(EString.implode(problems));
404                         }
405                     }
406
407                     void touchConnection(Object c) {
408                         if (DEBUG_DELETE)
409                             System.out.println("TOUCHED CONNECTION: " + c);
410                         if (c instanceof IElement) {
411                             Object obj = ElementUtils.getObject((IElement) c);
412                             if (obj instanceof Resource)
413                                 touchedConnections.add((Resource) obj);
414                         } else if (c instanceof Resource) {
415                             touchedConnections.add((Resource) c);
416                         }
417                     }
418                 });
419             }
420         }.schedule();
421
422         return true;
423     }
424
425     void message(final String message) {
426         if (statusLine == null)
427             return;
428         swtExec(new Runnable() {
429             @Override
430             public void run() {
431                 statusLine.setMessage(message);
432                 statusLine.setErrorMessage(null);
433             }
434         });
435     }
436
437     void error(final String message) {
438         if (statusLine == null)
439             return;
440         swtExec(new Runnable() {
441             @Override
442             public void run() {
443                 statusLine.setErrorMessage(message);
444             }
445         });
446     }
447
448     void swtExec(Runnable r) {
449         ThreadUtils.asyncExec(SWTThread.getThreadAccess(Display.getDefault()), r);
450     }
451
452 }