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