/*******************************************************************************
* 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:
*
*
* - 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.
* - 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.
*
*
* @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;
}
}