/*******************************************************************************
* 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:
*
*
*
Separate nodes and edges form the the removed selection
*
Find all edges attached to the removed nodes and remove them too
*
Delete connections that contain less than 2 terminal connections
*
*
* @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