/******************************************************************************* * 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.g2d.utils; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Deque; import java.util.HashSet; import java.util.Queue; import java.util.Set; import org.simantics.g2d.connection.ConnectionEntity; import org.simantics.g2d.diagram.IDiagram; import org.simantics.g2d.diagram.handler.DataElementMap; 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.element.ElementHints; import org.simantics.g2d.element.IElement; import org.simantics.g2d.element.handler.TerminalTopology; import org.simantics.g2d.elementclass.FlagHandler; /** * This class tries to expand the selection provided by the specified elements * by a single expansion step. Its purpose is to provide a way for the user to * easily select a larger range of elements based on the diagram connectivity. * This can be useful e.g. when preparing for a copy-paste operation or simply * for visualizing the connectivity of a diagram.

* *

* The expansion logic is as follows: *

*
    *
  1. If connections are included in the current selection, make sure that no * connection entity is only partly selected. If only partly selected connection * entities are found, complete those and stop there. Otherwise continue to the * next step.
  2. *
  3. Expand the current selection by one step. For connections this means * selecting all nodes that are attached by the connection but not yet in the * current selection. For nodes this means expanding the selection to all the * connections reachable from that particular node.
  4. *
* * @author Tuukka Lehtonen */ public class TopologicalSelectionExpander { public static final boolean DEBUG = false; IDiagram diagram; Set startSelection; Set resultSelection; Set processedConnections = new HashSet(); Topology topology; DataElementMap dem; public static Set expandSelection(IDiagram diagram, Set elements) { return new TopologicalSelectionExpander(diagram, elements).expanded(); } public TopologicalSelectionExpander(IDiagram diagram, Set startSelection) { assert diagram != null; this.diagram = diagram; this.startSelection = startSelection; this.resultSelection = new HashSet(startSelection); this.topology = diagram.getDiagramClass().getAtMostOneItemOfClass(Topology.class); this.dem = diagram.getDiagramClass().getAtMostOneItemOfClass(DataElementMap.class); } /** * @return null if the selection did not change in the * expansion, another set of elements otherwise */ public Set expandedIfChanged() { Set result = expanded(); if (DEBUG) System.out.println("result selection: " + result); if (result.equals(startSelection)) return null; if (DEBUG) System.out.println("setting new selection"); return result; } /** * @return */ public Set expanded() { if (topology == null || dem == null || startSelection.isEmpty()) return startSelection; if (DEBUG) System.out.println("expand start selection: " + startSelection); Deque work = new ArrayDeque(startSelection.size() + 4); work.addAll(startSelection); // 1. Iterate the start selection to see if there are any partly // selected connection entities. If so, then only complete the // selection of those entities before expanding the selection in // any other way. boolean connectionPartsSelected = false; for (IElement e : work) { IElement connection = getConnectionOfConnectionPart(e); if (connection != null) { // There was a mere connection part selection among the selection. Set connectionParts = getAllConnectionEntityParts(e); if (!connectionParts.isEmpty()) { if (DEBUG) System.out.println("\tconnection part selected: " + e + ", replacing with connection " + connection); resultSelection.add(connection); resultSelection.removeAll(connectionParts); connectionPartsSelected = true; } } } if (!connectionPartsSelected) { // No connection entities were partly selected. Go ahead with // the normal selection expansion procedure. while (!work.isEmpty()) { IElement e = work.poll(); if (DEBUG) System.out.println("\texpanding at element: " + e); @SuppressWarnings("unused") boolean expanded = expandConnection(e, work) || expandNode(e, work); } } if (DEBUG) System.out.println("expanded selection: " + resultSelection); return resultSelection; } boolean expandConnection(IElement connection, Queue workQueue) { ConnectionEntity ce = connection.getHint(ElementHints.KEY_CONNECTION_ENTITY); if (ce == null) return false; if (!processedConnections.add(ce)) return true; // Expand the selection to all the nodes attached to this connection. if (DEBUG) System.out.println("\texpanding at connection " + ce); Collection terminals = new ArrayList(); ce.getTerminalConnections(terminals); if (DEBUG) System.out.println("\t\tfound " + terminals.size() + " terminal connections: " + terminals); for (Connection terminal : terminals) { if (resultSelection.add(terminal.node)) { if (DEBUG) System.out.println("\t\t\tadding node '" + terminal.node + "' at terminal '" + terminal.terminal + "'"); } } return true; } boolean expandNode(IElement e, Queue workQueue) { // This is a node. TerminalTopology tt = e.getElementClass().getAtMostOneItemOfClass(TerminalTopology.class); if (tt == null) return false; if (DEBUG) System.out.println("\texpanding selection to node terminal connections: " + e); Collection terminals = new ArrayList(); tt.getTerminals(e, terminals); Collection connections = new ArrayList(); for (Terminal terminal : terminals) { topology.getConnections(e, terminal, connections); } if (DEBUG) System.out.println("\t\tfound " + connections.size() + " connected terminals: " + connections); for (Connection connection : connections) { IElement conn = getConnectionEntityConnection(connection.edge); if (conn != null) { if (DEBUG) System.out.println("\t\t\tadding connection: " + conn); resultSelection.add(conn); } } boolean expanded = !connections.isEmpty(); // We want to: // * expand selection to monitors and other related "sub-elements" of the selection // We don't want to: // * expand selection through flags FlagHandler fh = e.getElementClass().getAtMostOneItemOfClass(FlagHandler.class); if (fh == null) { RelationshipHandler rh = diagram.getDiagramClass().getAtMostOneItemOfClass(RelationshipHandler.class); if (rh != null) { for(Relation rel : rh.getRelations(diagram, e, new ArrayList())) { if(rel.getSubject() instanceof IElement) { expanded |= resultSelection.add((IElement)rel.getSubject()); } if(rel.getObject() instanceof IElement) { expanded |= resultSelection.add((IElement)rel.getObject()); } } } } return expanded; } static IElement getConnectionOfConnectionPart(IElement e) { ConnectionEntity ce = e.getHint(ElementHints.KEY_CONNECTION_ENTITY); if (ce == null) return null; IElement c = ce.getConnection(); if (c == e) return null; return c; } static IElement getConnectionEntityConnection(IElement e) { ConnectionEntity ce = e.getHint(ElementHints.KEY_CONNECTION_ENTITY); if (ce == null) return null; return ce.getConnection(); } static Set getAllConnectionEntityParts(IElement e) { ConnectionEntity ce = e.getHint(ElementHints.KEY_CONNECTION_ENTITY); if (ce == null) return Collections.emptySet(); Set result = new HashSet(); result.add(e); ce.getBranchPoints(result); ce.getSegments(result); return result; } }