--- /dev/null
+/*******************************************************************************\r
+ * Copyright (c) 2007, 2010 Association for Decentralized Information Management\r
+ * in Industry THTH ry.\r
+ * All rights reserved. This program and the accompanying materials\r
+ * are made available under the terms of the Eclipse Public License v1.0\r
+ * which accompanies this distribution, and is available at\r
+ * http://www.eclipse.org/legal/epl-v10.html\r
+ *\r
+ * Contributors:\r
+ * VTT Technical Research Centre of Finland - initial API and implementation\r
+ *******************************************************************************/\r
+package org.simantics.g2d.utils;\r
+\r
+import java.util.ArrayDeque;\r
+import java.util.ArrayList;\r
+import java.util.Collection;\r
+import java.util.Collections;\r
+import java.util.Deque;\r
+import java.util.HashSet;\r
+import java.util.Queue;\r
+import java.util.Set;\r
+\r
+import org.simantics.g2d.connection.ConnectionEntity;\r
+import org.simantics.g2d.diagram.IDiagram;\r
+import org.simantics.g2d.diagram.handler.DataElementMap;\r
+import org.simantics.g2d.diagram.handler.RelationshipHandler;\r
+import org.simantics.g2d.diagram.handler.RelationshipHandler.Relation;\r
+import org.simantics.g2d.diagram.handler.Topology;\r
+import org.simantics.g2d.diagram.handler.Topology.Connection;\r
+import org.simantics.g2d.diagram.handler.Topology.Terminal;\r
+import org.simantics.g2d.element.ElementHints;\r
+import org.simantics.g2d.element.IElement;\r
+import org.simantics.g2d.element.handler.TerminalTopology;\r
+import org.simantics.g2d.elementclass.FlagHandler;\r
+\r
+/**\r
+ * This class tries to expand the selection provided by the specified elements\r
+ * by a single expansion step. Its purpose is to provide a way for the user to\r
+ * easily select a larger range of elements based on the diagram connectivity.\r
+ * This can be useful e.g. when preparing for a copy-paste operation or simply\r
+ * for visualizing the connectivity of a diagram.</p>\r
+ * \r
+ * <p>\r
+ * The expansion logic is as follows:\r
+ * </p>\r
+ * <ol>\r
+ * <li>If connections are included in the current selection, make sure that no\r
+ * connection entity is only partly selected. If only partly selected connection\r
+ * entities are found, complete those and stop there. Otherwise continue to the\r
+ * next step.</li>\r
+ * <li>Expand the current selection by one step. For connections this means\r
+ * selecting all nodes that are attached by the connection but not yet in the\r
+ * current selection. For nodes this means expanding the selection to all the\r
+ * connections reachable from that particular node.</li>\r
+ * </ol>\r
+ * \r
+ * @author Tuukka Lehtonen\r
+ */\r
+public class TopologicalSelectionExpander {\r
+\r
+ public static final boolean DEBUG = false;\r
+\r
+ IDiagram diagram;\r
+ Set<IElement> startSelection;\r
+ Set<IElement> resultSelection;\r
+ Set<ConnectionEntity> processedConnections = new HashSet<ConnectionEntity>();\r
+\r
+ Topology topology;\r
+ DataElementMap dem;\r
+\r
+ public static Set<IElement> expandSelection(IDiagram diagram, Set<IElement> elements) {\r
+ return new TopologicalSelectionExpander(diagram, elements).expanded();\r
+ }\r
+\r
+ public TopologicalSelectionExpander(IDiagram diagram, Set<IElement> startSelection) {\r
+ assert diagram != null;\r
+\r
+ this.diagram = diagram;\r
+ this.startSelection = startSelection;\r
+ this.resultSelection = new HashSet<IElement>(startSelection);\r
+\r
+ this.topology = diagram.getDiagramClass().getAtMostOneItemOfClass(Topology.class);\r
+ this.dem = diagram.getDiagramClass().getAtMostOneItemOfClass(DataElementMap.class);\r
+ }\r
+\r
+ /**\r
+ * @return <code>null</code> if the selection did not change in the\r
+ * expansion, another set of elements otherwise\r
+ */\r
+ public Set<IElement> expandedIfChanged() {\r
+ Set<IElement> result = expanded();\r
+ if (DEBUG)\r
+ System.out.println("result selection: " + result);\r
+ if (result.equals(startSelection))\r
+ return null;\r
+ if (DEBUG)\r
+ System.out.println("setting new selection");\r
+ return result;\r
+ }\r
+\r
+ /**\r
+ * @return\r
+ */\r
+ public Set<IElement> expanded() {\r
+ if (topology == null || dem == null || startSelection.isEmpty())\r
+ return startSelection;\r
+\r
+ if (DEBUG)\r
+ System.out.println("expand start selection: " + startSelection);\r
+\r
+ Deque<IElement> work = new ArrayDeque<IElement>(startSelection.size() + 4);\r
+ work.addAll(startSelection);\r
+\r
+ // 1. Iterate the start selection to see if there are any partly\r
+ // selected connection entities. If so, then only complete the\r
+ // selection of those entities before expanding the selection in\r
+ // any other way.\r
+ boolean connectionPartsSelected = false;\r
+ for (IElement e : work) {\r
+ IElement connection = getConnectionOfConnectionPart(e);\r
+ if (connection != null) {\r
+ // There was a mere connection part selection among the selection.\r
+ Set<IElement> connectionParts = getAllConnectionEntityParts(e);\r
+ if (!connectionParts.isEmpty()) {\r
+ if (DEBUG)\r
+ System.out.println("\tconnection part selected: " + e + ", replacing with connection " + connection);\r
+ resultSelection.add(connection);\r
+ resultSelection.removeAll(connectionParts);\r
+ connectionPartsSelected = true;\r
+ }\r
+ }\r
+ }\r
+\r
+ if (!connectionPartsSelected) {\r
+ // No connection entities were partly selected. Go ahead with\r
+ // the normal selection expansion procedure.\r
+ while (!work.isEmpty()) {\r
+ IElement e = work.poll();\r
+ if (DEBUG)\r
+ System.out.println("\texpanding at element: " + e);\r
+ @SuppressWarnings("unused")\r
+ boolean expanded = expandConnection(e, work) || expandNode(e, work);\r
+ }\r
+ }\r
+\r
+ if (DEBUG)\r
+ System.out.println("expanded selection: " + resultSelection);\r
+ return resultSelection;\r
+ }\r
+\r
+ boolean expandConnection(IElement connection, Queue<IElement> workQueue) {\r
+ ConnectionEntity ce = connection.getHint(ElementHints.KEY_CONNECTION_ENTITY);\r
+ if (ce == null)\r
+ return false;\r
+\r
+ if (!processedConnections.add(ce))\r
+ return true;\r
+\r
+ // Expand the selection to all the nodes attached to this connection.\r
+ if (DEBUG)\r
+ System.out.println("\texpanding at connection " + ce);\r
+ Collection<Connection> terminals = new ArrayList<Connection>();\r
+ ce.getTerminalConnections(terminals);\r
+ if (DEBUG)\r
+ System.out.println("\t\tfound " + terminals.size() + " terminal connections: " + terminals);\r
+ for (Connection terminal : terminals) {\r
+ if (resultSelection.add(terminal.node)) {\r
+ if (DEBUG)\r
+ System.out.println("\t\t\tadding node '" + terminal.node + "' at terminal '" + terminal.terminal + "'");\r
+ }\r
+ }\r
+ return true;\r
+ }\r
+\r
+ boolean expandNode(IElement e, Queue<IElement> workQueue) {\r
+ // This is a node.\r
+ TerminalTopology tt = e.getElementClass().getAtMostOneItemOfClass(TerminalTopology.class);\r
+ if (tt == null)\r
+ return false;\r
+ if (DEBUG)\r
+ System.out.println("\texpanding selection to node terminal connections: " + e);\r
+\r
+ Collection<Terminal> terminals = new ArrayList<Terminal>();\r
+ tt.getTerminals(e, terminals);\r
+ Collection<Connection> connections = new ArrayList<Connection>();\r
+ for (Terminal terminal : terminals) {\r
+ topology.getConnections(e, terminal, connections);\r
+ }\r
+ if (DEBUG)\r
+ System.out.println("\t\tfound " + connections.size() + " connected terminals: " + connections);\r
+ for (Connection connection : connections) {\r
+ IElement conn = getConnectionEntityConnection(connection.edge);\r
+ if (conn != null) {\r
+ if (DEBUG)\r
+ System.out.println("\t\t\tadding connection: " + conn);\r
+ resultSelection.add(conn);\r
+ }\r
+ }\r
+ \r
+ boolean expanded = !connections.isEmpty(); \r
+\r
+ // We want to:\r
+ // * expand selection to monitors and other related "sub-elements" of the selection\r
+ // We don't want to:\r
+ // * expand selection through flags\r
+ FlagHandler fh = e.getElementClass().getAtMostOneItemOfClass(FlagHandler.class);\r
+ if (fh == null) {\r
+ RelationshipHandler rh = diagram.getDiagramClass().getAtMostOneItemOfClass(RelationshipHandler.class);\r
+ if (rh != null) {\r
+ for(Relation rel : rh.getRelations(diagram, e, new ArrayList<Relation>())) {\r
+ if(rel.getSubject() instanceof IElement) {\r
+ expanded |= resultSelection.add((IElement)rel.getSubject());\r
+ }\r
+ if(rel.getObject() instanceof IElement) {\r
+ expanded |= resultSelection.add((IElement)rel.getObject());\r
+ }\r
+ }\r
+ }\r
+ }\r
+\r
+ return expanded;\r
+ }\r
+\r
+ static IElement getConnectionOfConnectionPart(IElement e) {\r
+ ConnectionEntity ce = e.getHint(ElementHints.KEY_CONNECTION_ENTITY);\r
+ if (ce == null)\r
+ return null;\r
+ IElement c = ce.getConnection();\r
+ if (c == e)\r
+ return null;\r
+ return c;\r
+ }\r
+\r
+ static IElement getConnectionEntityConnection(IElement e) {\r
+ ConnectionEntity ce = e.getHint(ElementHints.KEY_CONNECTION_ENTITY);\r
+ if (ce == null)\r
+ return null;\r
+ return ce.getConnection();\r
+ }\r
+\r
+ static Set<IElement> getAllConnectionEntityParts(IElement e) {\r
+ ConnectionEntity ce = e.getHint(ElementHints.KEY_CONNECTION_ENTITY);\r
+ if (ce == null)\r
+ return Collections.emptySet();\r
+ Set<IElement> result = new HashSet<IElement>();\r
+ result.add(e);\r
+ ce.getBranchPoints(result);\r
+ ce.getSegments(result);\r
+ return result;\r
+ }\r
+\r
+}\r