]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.g2d/src/org/simantics/g2d/utils/TopologicalSelectionExpander.java
Two rendering glitch fixes for time series charts
[simantics/platform.git] / bundles / org.simantics.g2d / src / org / simantics / g2d / utils / TopologicalSelectionExpander.java
1 /*******************************************************************************
2  * Copyright (c) 2007, 2010 Association for Decentralized Information Management
3  * in Industry THTH ry.
4  * All rights reserved. This program and the accompanying materials
5  * are made available under the terms of the Eclipse Public License v1.0
6  * which accompanies this distribution, and is available at
7  * http://www.eclipse.org/legal/epl-v10.html
8  *
9  * Contributors:
10  *     VTT Technical Research Centre of Finland - initial API and implementation
11  *******************************************************************************/
12 package org.simantics.g2d.utils;
13
14 import java.util.ArrayDeque;
15 import java.util.ArrayList;
16 import java.util.Collection;
17 import java.util.Collections;
18 import java.util.Deque;
19 import java.util.HashSet;
20 import java.util.Queue;
21 import java.util.Set;
22
23 import org.simantics.g2d.connection.ConnectionEntity;
24 import org.simantics.g2d.diagram.IDiagram;
25 import org.simantics.g2d.diagram.handler.DataElementMap;
26 import org.simantics.g2d.diagram.handler.RelationshipHandler;
27 import org.simantics.g2d.diagram.handler.RelationshipHandler.Relation;
28 import org.simantics.g2d.diagram.handler.Topology;
29 import org.simantics.g2d.diagram.handler.Topology.Connection;
30 import org.simantics.g2d.diagram.handler.Topology.Terminal;
31 import org.simantics.g2d.element.ElementHints;
32 import org.simantics.g2d.element.IElement;
33 import org.simantics.g2d.element.handler.TerminalTopology;
34 import org.simantics.g2d.elementclass.FlagHandler;
35
36 /**
37  * This class tries to expand the selection provided by the specified elements
38  * by a single expansion step. Its purpose is to provide a way for the user to
39  * easily select a larger range of elements based on the diagram connectivity.
40  * This can be useful e.g. when preparing for a copy-paste operation or simply
41  * for visualizing the connectivity of a diagram.</p>
42  * 
43  * <p>
44  * The expansion logic is as follows:
45  * </p>
46  * <ol>
47  * <li>If connections are included in the current selection, make sure that no
48  * connection entity is only partly selected. If only partly selected connection
49  * entities are found, complete those and stop there. Otherwise continue to the
50  * next step.</li>
51  * <li>Expand the current selection by one step. For connections this means
52  * selecting all nodes that are attached by the connection but not yet in the
53  * current selection. For nodes this means expanding the selection to all the
54  * connections reachable from that particular node.</li>
55  * </ol>
56  * 
57  * @author Tuukka Lehtonen
58  */
59 public class TopologicalSelectionExpander {
60
61     public static final boolean DEBUG = false;
62
63     IDiagram              diagram;
64     Set<IElement>         startSelection;
65     Set<IElement>         resultSelection;
66     Set<ConnectionEntity> processedConnections = new HashSet<ConnectionEntity>();
67
68     Topology              topology;
69     DataElementMap        dem;
70
71     public static Set<IElement> expandSelection(IDiagram diagram, Set<IElement> elements) {
72         return new TopologicalSelectionExpander(diagram, elements).expanded();
73     }
74
75     public TopologicalSelectionExpander(IDiagram diagram, Set<IElement> startSelection) {
76         assert diagram != null;
77
78         this.diagram = diagram;
79         this.startSelection = startSelection;
80         this.resultSelection = new HashSet<IElement>(startSelection);
81
82         this.topology = diagram.getDiagramClass().getAtMostOneItemOfClass(Topology.class);
83         this.dem = diagram.getDiagramClass().getAtMostOneItemOfClass(DataElementMap.class);
84     }
85
86     /**
87      * @return <code>null</code> if the selection did not change in the
88      *         expansion, another set of elements otherwise
89      */
90     public Set<IElement> expandedIfChanged() {
91         Set<IElement> result = expanded();
92         if (DEBUG)
93             System.out.println("result selection: " + result);
94         if (result.equals(startSelection))
95             return null;
96         if (DEBUG)
97             System.out.println("setting new selection");
98         return result;
99     }
100
101     /**
102      * @return
103      */
104     public Set<IElement> expanded() {
105         if (topology == null || dem == null || startSelection.isEmpty())
106             return startSelection;
107
108         if (DEBUG)
109             System.out.println("expand start selection: " + startSelection);
110
111         Deque<IElement> work = new ArrayDeque<IElement>(startSelection.size() + 4);
112         work.addAll(startSelection);
113
114         // 1. Iterate the start selection to see if there are any partly
115         // selected connection entities. If so, then only complete the
116         // selection of those entities before expanding the selection in
117         // any other way.
118         boolean connectionPartsSelected = false;
119         for (IElement e : work) {
120             IElement connection = getConnectionOfConnectionPart(e);
121             if (connection != null) {
122                 // There was a mere connection part selection among the selection.
123                 Set<IElement> connectionParts = getAllConnectionEntityParts(e);
124                 if (!connectionParts.isEmpty()) {
125                     if (DEBUG)
126                         System.out.println("\tconnection part selected: " + e + ", replacing with connection " + connection);
127                     resultSelection.add(connection);
128                     resultSelection.removeAll(connectionParts);
129                     connectionPartsSelected = true;
130                 }
131             }
132         }
133
134         if (!connectionPartsSelected) {
135             // No connection entities were partly selected. Go ahead with
136             // the normal selection expansion procedure.
137             while (!work.isEmpty()) {
138                 IElement e = work.poll();
139                 if (DEBUG)
140                     System.out.println("\texpanding at element: " + e);
141                 @SuppressWarnings("unused")
142                 boolean expanded = expandConnection(e, work) || expandNode(e, work);
143             }
144         }
145
146         if (DEBUG)
147             System.out.println("expanded selection: " + resultSelection);
148         return resultSelection;
149     }
150
151     boolean expandConnection(IElement connection, Queue<IElement> workQueue) {
152         ConnectionEntity ce = connection.getHint(ElementHints.KEY_CONNECTION_ENTITY);
153         if (ce == null)
154             return false;
155
156         if (!processedConnections.add(ce))
157             return true;
158
159         // Expand the selection to all the nodes attached to this connection.
160         if (DEBUG)
161             System.out.println("\texpanding at connection " + ce);
162         Collection<Connection> terminals = new ArrayList<Connection>();
163         ce.getTerminalConnections(terminals);
164         if (DEBUG)
165             System.out.println("\t\tfound " + terminals.size() + " terminal connections: " + terminals);
166         for (Connection terminal : terminals) {
167             if (resultSelection.add(terminal.node)) {
168                 if (DEBUG)
169                     System.out.println("\t\t\tadding node '" + terminal.node + "' at terminal '" + terminal.terminal + "'");
170             }
171         }
172         return true;
173     }
174
175     boolean expandNode(IElement e, Queue<IElement> workQueue) {
176         // This is a node.
177         TerminalTopology tt = e.getElementClass().getAtMostOneItemOfClass(TerminalTopology.class);
178         if (tt == null)
179             return false;
180         if (DEBUG)
181             System.out.println("\texpanding selection to node terminal connections: " + e);
182
183         Collection<Terminal> terminals = new ArrayList<Terminal>();
184         tt.getTerminals(e, terminals);
185         Collection<Connection> connections = new ArrayList<Connection>();
186         for (Terminal terminal : terminals) {
187             topology.getConnections(e, terminal, connections);
188         }
189         if (DEBUG)
190             System.out.println("\t\tfound " + connections.size() + " connected terminals: " + connections);
191         for (Connection connection : connections) {
192             IElement conn = getConnectionEntityConnection(connection.edge);
193             if (conn != null) {
194                 if (DEBUG)
195                     System.out.println("\t\t\tadding connection: " + conn);
196                 resultSelection.add(conn);
197             }
198         }
199         
200         boolean expanded = !connections.isEmpty(); 
201
202         // We want to:
203         // * expand selection to monitors and other related "sub-elements" of the selection
204         // We don't want to:
205         // * expand selection through flags
206         FlagHandler fh = e.getElementClass().getAtMostOneItemOfClass(FlagHandler.class);
207         if (fh == null) {
208             RelationshipHandler rh = diagram.getDiagramClass().getAtMostOneItemOfClass(RelationshipHandler.class);
209             if (rh != null) {
210                 for(Relation rel : rh.getRelations(diagram, e, new ArrayList<Relation>())) {
211                     if(rel.getSubject() instanceof IElement) {
212                         expanded |= resultSelection.add((IElement)rel.getSubject());
213                     }
214                     if(rel.getObject() instanceof IElement) {
215                         expanded |= resultSelection.add((IElement)rel.getObject());
216                     }
217                 }
218             }
219         }
220
221         return expanded;
222     }
223
224     static IElement getConnectionOfConnectionPart(IElement e) {
225         ConnectionEntity ce = e.getHint(ElementHints.KEY_CONNECTION_ENTITY);
226         if (ce == null)
227             return null;
228         IElement c = ce.getConnection();
229         if (c == e)
230             return null;
231         return c;
232     }
233
234     static IElement getConnectionEntityConnection(IElement e) {
235         ConnectionEntity ce = e.getHint(ElementHints.KEY_CONNECTION_ENTITY);
236         if (ce == null)
237             return null;
238         return ce.getConnection();
239     }
240
241     static Set<IElement> getAllConnectionEntityParts(IElement e) {
242         ConnectionEntity ce = e.getHint(ElementHints.KEY_CONNECTION_ENTITY);
243         if (ce == null)
244             return Collections.emptySet();
245         Set<IElement> result = new HashSet<IElement>();
246         result.add(e);
247         ce.getBranchPoints(result);
248         ce.getSegments(result);
249         return result;
250     }
251
252 }