org.graphstream.ui.graphicGraph.StyleGroupSet Maven / Gradle / Ivy
/*
* This file is part of GraphStream .
*
* GraphStream is a library whose purpose is to handle static or dynamic
* graph, create them from scratch, file or any source and display them.
*
* This program is free software distributed under the terms of two licenses, the
* CeCILL-C license that fits European law, and the GNU Lesser General Public
* License. You can use, modify and/ or redistribute the software under the terms
* of the CeCILL-C license as circulated by CEA, CNRS and INRIA at the following
* URL or under the terms of the GNU LGPL as published by
* the Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see .
*
* The fact that you are presently reading this means that you have had
* knowledge of the CeCILL-C and LGPL licenses and that you accept their terms.
*/
/**
* @since 2009-07-05
*
* @author Antoine Dutot
* @author Yoann Pigné
* @author Guilhelm Savin
* @author Alex Bowen
* @author Hicham Brahimi
*/
package org.graphstream.ui.graphicGraph;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.TreeMap;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.graphstream.graph.Edge;
import org.graphstream.graph.Element;
import org.graphstream.graph.Graph;
import org.graphstream.graph.Node;
import org.graphstream.ui.graphicGraph.stylesheet.Rule;
import org.graphstream.ui.graphicGraph.stylesheet.Selector;
import org.graphstream.ui.graphicGraph.stylesheet.StyleConstants.ShadowMode;
import org.graphstream.ui.graphicGraph.stylesheet.StyleSheet;
import org.graphstream.ui.graphicGraph.stylesheet.StyleSheetListener;
/**
* A set of style groups.
*
*
* This class is in charge or storing all the style groups and to update them.
* Each time an element is added or removed the groups are updated. Each time
* the style sheet changes the groups are updated.
*
*
* @author Antoine Dutot
*/
public class StyleGroupSet implements StyleSheetListener {
// Attribute
/**
* The style sheet.
*/
protected StyleSheet stylesheet;
/**
* All the groups indexed by their unique identifier.
*/
protected final Map groups = new TreeMap();
/**
* Allows to retrieve the group containing a node knowing the node id.
*/
protected final Map byNodeIdGroups = new TreeMap();
/**
* Allows to retrieve the group containing an edge knowing the node id.
*/
protected final Map byEdgeIdGroups = new TreeMap();
/**
* Allows to retrieve the group containing a sprite knowing the node id.
*/
protected final Map bySpriteIdGroups = new TreeMap();
/**
* Allows to retrieve the group containing a graph knowing the node id.
*/
protected final Map byGraphIdGroups = new TreeMap();
/**
* Virtual set of nodes. This set provides fake methods to make it appear as a
* set of nodes whereas it only maps on the node style groups.
*/
protected NodeSet nodeSet = new NodeSet();
/**
* Virtual set of edges. This set provides fake methods to make it appear as a
* set of edges whereas it only maps on the edge style groups.
*/
protected EdgeSet edgeSet = new EdgeSet();
/**
* Virtual set of sprites. This set provides fake methods to make it appear as a
* set of sprites whereas it only maps on the sprite style groups.
*/
protected SpriteSet spriteSet = new SpriteSet();
/**
* Virtual set of graphs. This set provides fake methods to make it appear as a
* set of graphs whereas it only maps on the graph style groups.
*/
protected GraphSet graphSet = new GraphSet();
/**
* The set of events actually occurring.
*/
protected EventSet eventSet = new EventSet();
/**
* The groups sorted by their Z index.
*/
protected ZIndex zIndex = new ZIndex();
/**
* Set of groups that cast shadow.
*/
protected ShadowSet shadow = new ShadowSet();
/**
* Remove groups if they become empty?.
*/
protected boolean removeEmptyGroups = true;
/**
* Set of listeners.
*/
protected ArrayList listeners = new ArrayList<>();
// Construction
/**
* New empty style group set, using the given style sheet to create style
* groups. The group set installs itself as a listener of the style sheet. So in
* order to completely stop using such a group, you must call
* {@link #release()}.
*
* @param stylesheet
* The style sheet to use to create groups.
*/
public StyleGroupSet(StyleSheet stylesheet) {
this.stylesheet = stylesheet;
stylesheet.addListener(this);
}
// Access
/**
* Number of groups.
*
* @return The number of groups.
*/
public int getGroupCount() {
return groups.size();
}
/**
* Return a group by its unique identifier. The way group identifier are
* constructed reflects their contents.
*
* @param groupId
* The group identifier.
* @return The corresponding group or null if not found.
*/
public StyleGroup getGroup(String groupId) {
return groups.get(groupId);
}
/**
* Iterator on the set of groups in no particular order.
*
* @return An iterator on the group set.
*/
public Iterator getGroupIterator() {
return groups.values().iterator();
}
/**
* Iterable set of groups elements, in no particular order.
*
* @return An iterable on the set of groups.
*/
public Iterable groups() {
return groups.values();
}
/**
* Iterator on the Z index.
*
* @return The z index iterator.
*/
public Iterator> getZIterator() {
return zIndex.getIterator();
}
/**
* Iterable set of "subsets of groups" sorted by Z level. Each subset of groups
* is at the same Z level.
*
* @return The z levels.
*/
public Iterable> zIndex() {
return zIndex;
}
/**
* Iterator on the style groups that cast a shadow.
*
* @return The shadow groups iterator.
*/
public Iterator getShadowIterator() {
return shadow.getIterator();
}
/**
* Iterable set of groups that cast shadow.
*
* @return All the groups that cast a shadow.
*/
public Iterable shadows() {
return shadow;
}
/**
* True if the set contains and styles the node whose identifier is given.
*
* @param id
* The node identifier.
* @return True if the node is in this set.
*/
public boolean containsNode(String id) {
return byNodeIdGroups.containsKey(id);
}
/**
* True if the set contains and styles the edge whose identifier is given.
*
* @param id
* The edge identifier.
* @return True if the edge is in this set.
*/
public boolean containsEdge(String id) {
return byEdgeIdGroups.containsKey(id);
}
/**
* True if the set contains and styles the sprite whose identifier is given.
*
* @param id
* The sprite identifier.
* @return True if the sprite is in this set.
*/
public boolean containsSprite(String id) {
return bySpriteIdGroups.containsKey(id);
}
/**
* True if the set contains and styles the graph whose identifier is given.
*
* @param id
* The graph identifier.
* @return True if the graph is in this set.
*/
public boolean containsGraph(String id) {
return byGraphIdGroups.containsKey(id);
}
/**
* Get an element.
*
* @param id
* The element id.
* @param elt2grp
* The kind of element.
* @return The element or null if not found.
*/
protected Element getElement(String id, Map elt2grp) {
String gid = elt2grp.get(id);
if (gid != null) {
StyleGroup group = groups.get(gid);
return group.getElement(id);
}
return null;
}
/**
* Get a node element knowing its identifier.
*
* @param id
* The node identifier.
* @return The node if it is in this set, else null.
*/
public Node getNode(String id) {
return (Node) getElement(id, byNodeIdGroups);
}
/**
* Get an edge element knowing its identifier.
*
* @param id
* The edge identifier.
* @return The edge if it is in this set, else null.
*/
public Edge getEdge(String id) {
return (Edge) getElement(id, byEdgeIdGroups);
}
/**
* Get a sprite element knowing its identifier.
*
* @param id
* The sprite identifier.
* @return The sprite if it is in this set, else null.
*/
public GraphicSprite getSprite(String id) {
return (GraphicSprite) getElement(id, bySpriteIdGroups);
}
/**
* Get a graph element knowing its identifier.
*
* @param id
* The graph identifier.
* @return The graph if it is in this set, else null.
*/
public Graph getGraph(String id) {
return (Graph) getElement(id, byGraphIdGroups);
}
/**
* The number of nodes referenced.
*
* @return The node count.
*/
public int getNodeCount() {
return byNodeIdGroups.size();
}
/**
* The number of edges referenced.
*
* @return The edge count.
*/
public int getEdgeCount() {
return byEdgeIdGroups.size();
}
/**
* The number of sprites referenced.
*
* @return The sprite count.
*/
public int getSpriteCount() {
return bySpriteIdGroups.size();
}
/**
* Iterator on the set of nodes.
*
* @return An iterator on all node elements contained in style groups.
*/
public Iterator getNodeIterator() {
return new ElementIterator(byNodeIdGroups);
}
/**
* Iterator on the set of graphs.
*
* @return An iterator on all graph elements contained in style groups.
*/
public Iterator getGraphIterator() {
return new ElementIterator(byGraphIdGroups);
}
public Stream nodes() {
return byNodeIdGroups.entrySet().stream().map(entry -> {
return (Node) groups.get(entry.getValue()).getElement(entry.getKey());
});
}
public Stream edges() {
return byEdgeIdGroups.entrySet().stream().map(entry -> {
return (Edge) groups.get(entry.getValue()).getElement(entry.getKey());
});
}
public Stream sprites() {
return bySpriteIdGroups.entrySet().stream().map(entry -> {
return (GraphicSprite) groups.get(entry.getValue()).getElement(entry.getKey());
});
}
/**
* Iterable set of graphs.
*
* @return The set of all graphs.
*/
public Iterable graphs() {
return graphSet;
}
/**
* Iterator on the set of edges.
*
* @return An iterator on all edge elements contained in style groups.
*/
public Iterator getEdgeIterator() {
return new ElementIterator(byEdgeIdGroups);
}
/**
* Iterator on the set of sprite.
*
* @return An iterator on all sprite elements contained in style groups.
*/
public Iterator getSpriteIterator() {
return new ElementIterator(bySpriteIdGroups);
}
/**
* Retrieve the group identifier of an element knowing the element identifier.
*
* @param element
* The element to search for.
* @return Identifier of the group containing the element.
*/
public String getElementGroup(Element element) {
if (element instanceof Node) {
return byNodeIdGroups.get(element.getId());
} else if (element instanceof Edge) {
return byEdgeIdGroups.get(element.getId());
} else if (element instanceof GraphicSprite) {
return bySpriteIdGroups.get(element.getId());
} else if (element instanceof Graph) {
return byGraphIdGroups.get(element.getId());
} else {
throw new RuntimeException("What ?");
}
}
/**
* Get the style of an element.
*
* @param element
* The element to search for.
* @return The style group of the element (which is also a style).
*/
public StyleGroup getStyleForElement(Element element) {
String gid = getElementGroup(element);
return groups.get(gid);
}
/**
* Get the style of a given node.
*
* @param node
* The node to search for.
* @return The node style.
*/
public StyleGroup getStyleFor(Node node) {
String gid = byNodeIdGroups.get(node.getId());
return groups.get(gid);
}
/**
* Get the style of a given edge.
*
* @param edge
* The edge to search for.
* @return The edge style.
*/
public StyleGroup getStyleFor(Edge edge) {
String gid = byEdgeIdGroups.get(edge.getId());
return groups.get(gid);
}
/**
* Get the style of a given sprite.
*
* @param sprite
* The node to search for.
* @return The sprite style.
*/
public StyleGroup getStyleFor(GraphicSprite sprite) {
String gid = bySpriteIdGroups.get(sprite.getId());
return groups.get(gid);
}
/**
* Get the style of a given graph.
*
* @param graph
* The node to search for.
* @return The graph style.
*/
public StyleGroup getStyleFor(Graph graph) {
String gid = byGraphIdGroups.get(graph.getId());
return groups.get(gid);
}
/**
* True if groups are removed when becoming empty. This setting allows to keep
* empty group when the set of elements is quite dynamic. This allows to avoid
* recreting groups when an element appears and disappears regularly.
*
* @return True if the groups are removed when empty.
*/
public boolean areEmptyGroupRemoved() {
return removeEmptyGroups;
}
/**
* The Z index object.
*
* @return The Z index.
*/
public ZIndex getZIndex() {
return zIndex;
}
/**
* The set of style groups that cast a shadow.
*
* @return The set of shadowed style groups.
*/
public ShadowSet getShadowSet() {
return shadow;
}
// Command
/**
* Release any dependency to the style sheet.
*/
public void release() {
stylesheet.removeListener(this);
}
/**
* Empties this style group set. The style sheet is listener is not removed, use
* {@link #release()} to do that.
*/
public void clear() {
byEdgeIdGroups.clear();
byNodeIdGroups.clear();
bySpriteIdGroups.clear();
byGraphIdGroups.clear();
groups.clear();
zIndex.clear();
shadow.clear();
}
/**
* Remove or keep groups that becomes empty, if true the groups are removed. If
* this setting was set to false, and is now true, the group set is purged of
* the empty groups.
*
* @param on
* If true the groups will be removed.
*/
public void setRemoveEmptyGroups(boolean on) {
if (removeEmptyGroups == false && on == true) {
Iterator i = groups.values().iterator();
while (i.hasNext()) {
StyleGroup g = i.next();
if (g.isEmpty())
i.remove();
}
}
removeEmptyGroups = on;
}
protected StyleGroup addGroup(String id, ArrayList rules, Element firstElement) {
StyleGroup group = new StyleGroup(id, rules, firstElement, eventSet);
groups.put(id, group);
zIndex.groupAdded(group);
shadow.groupAdded(group);
return group;
}
protected void removeGroup(StyleGroup group) {
zIndex.groupRemoved(group);
shadow.groupRemoved(group);
groups.remove(group.getId());
group.release();
}
/**
* Add an element and bind it to its style group. The group is created if
* needed.
*
* @param element
* The element to add.
* @return The style group where the element was added.
*/
public StyleGroup addElement(Element element) {
StyleGroup group = addElement_(element);
for (StyleGroupListener listener : listeners)
listener.elementStyleChanged(element, null, group);
return group;
}
protected StyleGroup addElement_(Element element) {
ArrayList rules = stylesheet.getRulesFor(element);
String gid = stylesheet.getStyleGroupIdFor(element, rules);
StyleGroup group = groups.get(gid);
if (group == null)
group = addGroup(gid, rules, element);
else
group.addElement(element);
addElementToReverseSearch(element, gid);
return group;
}
/**
* Remove an element from the group set. If the group becomes empty after the
* element removal, depending on the setting of {@link #areEmptyGroupRemoved()},
* the group is deleted or kept. Keeping groups allows to handle faster elements
* that constantly appear and disappear.
*
* @param element
* The element to remove.
*/
public void removeElement(Element element) {
String gid = getElementGroup(element);
if (null == gid) {
return;
}
StyleGroup group = groups.get(gid);
if (group != null) {
group.removeElement(element);
removeElementFromReverseSearch(element);
if (removeEmptyGroups && group.isEmpty())
removeGroup(group);
}
}
/**
* Check if an element need to change from a style group to another.
*
*
* When an element can have potentially changed style due to some of its
* attributes (ui.class for example), instead of removing it then reading it,
* use this method to move the element from its current style group to a
* potentially different style group.
*
*
*
* Explanation of this method : checking the style of an element may be done by
* removing it ({@link #removeElement(Element)}) and then re-adding it (
* {@link #addElement(Element)}). This must be done by the element since it
* knows when to check this. However you cannot only remove and add, since the
* style group inside which the element is can have events occurring on it, and
* these events must be passed from its old style to its new style. This method
* does all this information passing.
*
*
* @param element
* The element to move.
*/
public void checkElementStyleGroup(Element element) {
StyleGroup oldGroup = getGroup(getElementGroup(element));
// Get the old element "dynamic" status.
boolean isDyn = false;
// Get the old event set for the given element.
StyleGroup.ElementEvents events = null;
if (oldGroup != null) {
isDyn = oldGroup.isElementDynamic(element);
events = oldGroup.getEventsFor(element);
}
// Remove the element from its old style and add it to insert it in the
// correct style.
removeElement(element);
addElement_(element);
// Eventually push the events on the new style group.
StyleGroup newGroup = getGroup(getElementGroup(element));
if (newGroup != null && events != null) {
for (String event : events.events)
pushEventFor(element, event);
}
for (StyleGroupListener listener : listeners)
listener.elementStyleChanged(element, oldGroup, newGroup);
// Eventually set the element as dynamic, if it was.
if (newGroup != null && isDyn)
newGroup.pushElementAsDynamic(element);
}
protected void addElementToReverseSearch(Element element, String groupId) {
if (element instanceof Node) {
byNodeIdGroups.put(element.getId(), groupId);
} else if (element instanceof Edge) {
byEdgeIdGroups.put(element.getId(), groupId);
} else if (element instanceof GraphicSprite) {
bySpriteIdGroups.put(element.getId(), groupId);
} else if (element instanceof Graph) {
byGraphIdGroups.put(element.getId(), groupId);
} else {
throw new RuntimeException("What ?");
}
}
protected void removeElementFromReverseSearch(Element element) {
if (element instanceof Node) {
byNodeIdGroups.remove(element.getId());
} else if (element instanceof Edge) {
byEdgeIdGroups.remove(element.getId());
} else if (element instanceof GraphicSprite) {
bySpriteIdGroups.remove(element.getId());
} else if (element instanceof Graph) {
byGraphIdGroups.remove(element.getId());
} else {
throw new RuntimeException("What ?");
}
}
/**
* Push a global event on the event stack. Events trigger the replacement of a
* style by an alternative style (or meta-class) when possible. If an event is
* on the event stack, each time a style has an alternative corresponding to the
* event, the alternative is used instead of the style.
*
* @param event
* The event to push.
*/
public void pushEvent(String event) {
eventSet.pushEvent(event);
}
/**
* Push an event specifically for a given element. This is normally done
* automatically by the graphic element.
*
* @param element
* The element considered.
* @param event
* The event to push.
*/
public void pushEventFor(Element element, String event) {
StyleGroup group = getGroup(getElementGroup(element));
if (group != null)
group.pushEventFor(element, event);
}
/**
* Pop a global event from the event set.
*
* @param event
* The event to remove.
*/
public void popEvent(String event) {
eventSet.popEvent(event);
}
/**
* Pop an event specifically for a given element. This is normally done
* automatically by the graphic element.
*
* @param element
* The element considered.
* @param event
* The event to pop.
*/
public void popEventFor(Element element, String event) {
StyleGroup group = getGroup(getElementGroup(element));
if (group != null)
group.popEventFor(element, event);
}
/**
* Specify the given element has dynamic style attribute values. This is
* normally done automatically by the graphic element.
*
* @param element
* The element to add to the dynamic subset.
*/
public void pushElementAsDynamic(Element element) {
StyleGroup group = getGroup(getElementGroup(element));
if (group != null)
group.pushElementAsDynamic(element);
}
/**
* Remove the given element from the subset of elements having dynamic style
* attribute values. This is normally done automatically by the graphic element.
*
* @param element
* The element to remove from the dynamic subset.
*/
public void popElementAsDynamic(Element element) {
StyleGroup group = getGroup(getElementGroup(element));
if (group != null)
group.popElementAsDynamic(element);
}
/**
* Add a listener for element style changes.
*
* @param listener
* The listener to add.
*/
public void addListener(StyleGroupListener listener) {
listeners.add(listener);
}
/**
* Remove a style change listener.
*
* @param listener
* The listener to remove.
*/
public void removeListener(StyleGroupListener listener) {
int index = listeners.lastIndexOf(listener);
if (index >= 0) {
listeners.remove(index);
}
}
// Listener -- What to do when a change occurs in the style sheet.
public void styleAdded(Rule oldRule, Rule newRule) {
// When a style change, we need to update groups.
// Several cases :
// 1. The style already exists
// * Nothing to do in fact. All the elements are still in place.
// No style rule (selectors) changed, and therefore we do not have
// to change the groups since they are built using the selectors.
// 2. The style is new
// * we need to check all the groups concerning this kind of element (we
// can
// restrict our search to these groups, since other will not be
// impacted),
// and check all elements of these groups.
if (oldRule == null)
checkForNewStyle(newRule); // no need to check Z and shadow, done
// when adding/changing group.
else
checkZIndexAndShadow(oldRule, newRule);
}
public void styleSheetCleared() {
ArrayList elements = new ArrayList();
for (Element element : graphs())
elements.add(element);
nodes().forEach(elements::add);
edges().forEach(elements::add);
sprites().forEach(elements::add);
clear();
elements.forEach(this::removeElement);
elements.forEach(this::addElement);
}
/**
* Check each group that may have changed, for example to rebuild the Z index
* and the shadow set.
*
* @param oldRule
* The old rule that changed.
* @param newRule
* The new rule that participated in the change.
*/
protected void checkZIndexAndShadow(Rule oldRule, Rule newRule) {
if (oldRule != null) {
if (oldRule.selector.getId() != null || oldRule.selector.getClazz() != null) {
// We may accelerate things a bit when a class or id style is
// modified,
// since only the groups listed in the style are concerned (we
// are at the
// bottom of the inheritance tree).
if (oldRule.getGroups() != null)
for (String s : oldRule.getGroups()) {
StyleGroup group = groups.get(s);
if (group != null) {
zIndex.groupChanged(group);
shadow.groupChanged(group);
}
}
} else {
// For kind styles "NODE", "EDGE", "GRAPH", "SPRITE", we must
// reset
// the whole Z and shadows for the kind, since several styles
// may
// have changed.
Selector.Type type = oldRule.selector.type;
for (StyleGroup group : groups.values()) {
if (group.getType() == type) {
zIndex.groupChanged(group);
shadow.groupChanged(group);
}
}
}
}
}
/**
* We try to avoid at most to affect anew styles to elements and to recreate
* groups, which is time consuming.
*
* Two cases :
*
* - The style is an specific (id) style. In this case a new group may be
* added.
*
* - check an element matches the style and in this case create the group by
* adding the element.
* - else do nothing.
*
*
* - The style is a kind or class style.
*
* - check all the groups in the kind of the style (graph, node, edge, sprite)
* and only in this kind (since other will never be affected).
* - remove all groups of this kind.
* - add all elements of this kind anew to recreate the group.
*
*
*
*/
protected void checkForNewStyle(Rule newRule) {
switch (newRule.selector.type) {
case GRAPH:
if (newRule.selector.getId() != null)
checkForNewIdStyle(newRule, byGraphIdGroups);
else
checkForNewStyle(newRule, byGraphIdGroups);
break;
case NODE:
if (newRule.selector.getId() != null)
checkForNewIdStyle(newRule, byNodeIdGroups);
else
checkForNewStyle(newRule, byNodeIdGroups);
break;
case EDGE:
if (newRule.selector.getId() != null)
checkForNewIdStyle(newRule, byEdgeIdGroups);
else
checkForNewStyle(newRule, byEdgeIdGroups);
break;
case SPRITE:
if (newRule.selector.getId() != null)
checkForNewIdStyle(newRule, bySpriteIdGroups);
else
checkForNewStyle(newRule, bySpriteIdGroups);
break;
case ANY:
default:
throw new RuntimeException("What ?");
}
}
/**
* Check for a new specific style (applies only to one element).
*
* @param newRule
* The new style rule.
* @param elt2grp
* The name space.
*/
protected void checkForNewIdStyle(Rule newRule, Map elt2grp) {
// There is only one element that matches the identifier.
Element element = getElement(newRule.selector.getId(), elt2grp);
if (element != null) {
checkElementStyleGroup(element);
// removeElement( element ); // Remove the element from its old
// group. Potentially delete a group.
// addElement( element ); // Add the element to its new own group
// (since this is an ID style).
}
}
/**
* Check for a new kind or class style in a given name space (node, edge,
* sprite, graph).
*
* @param newRule
* The new style rule.
* @param elt2grp
* The name space.
*/
protected void checkForNewStyle(Rule newRule, Map elt2grp) {
elt2grp.keySet().stream().map(eltId -> getElement(eltId, elt2grp)).collect(Collectors.toList())
.forEach(this::checkElementStyleGroup);
}
// Utility
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append(String.format("Style groups (%d) :%n", groups.size()));
for (StyleGroup group : groups.values()) {
builder.append(group.toString(1));
builder.append(String.format("%n"));
}
return builder.toString();
}
// Inner classes
/**
* Set of events (meta-classes) actually active.
*
*
* The event set contains the set of events actually occurring. This is used to
* select alternate styles. The events actually occurring are in precedence
* order. The last one is the most important.
*
*
* @author Antoine Dutot
*/
public class EventSet {
public ArrayList eventSet = new ArrayList();
public String events[] = new String[0];
/**
* Add an event to the set.
*
* @param event
* The event to add.
*/
public void pushEvent(String event) {
eventSet.add(event);
events = eventSet.toArray(events);
}
/**
* Remove an event from the set.
*
* @param event
* The event to remove.
*/
public void popEvent(String event) {
int index = eventSet.lastIndexOf(event);
if (index >= 0)
eventSet.remove(index);
events = eventSet.toArray(events);
}
/**
* The set of events in order, the most important at the end.
*
* @return The event set.
*/
public String[] getEvents() {
return events;
}
}
/**
* All the style groups sorted by their Z index.
*
*
* This structure is maintained by each time a group is added or removed, or
* when the style of a group changed.
*
*
* @author Antoine Dutot
*/
public class ZIndex implements Iterable> {
/**
* Ordered set of groups.
*/
public ArrayList> zIndex = new ArrayList>();
/**
* Knowing a group, tell if its Z index.
*/
public HashMap reverseZIndex = new HashMap();
/**
* New empty Z index.
*/
public ZIndex() {
initZIndex();
}
protected void initZIndex() {
zIndex.ensureCapacity(256);
for (int i = 0; i < 256; i++)
zIndex.add(null);
}
/**
* Iterator on the set of Z index cells. Each item is a set of style groups that
* pertain to the same Z index.
*
* @return Iterator on the Z index.
*/
protected Iterator> getIterator() {
return new ZIndexIterator();
}
public Iterator> iterator() {
return getIterator();
}
/**
* A new group appeared, put it in the z index.
*
* @param group
* The group to add.
*/
protected void groupAdded(StyleGroup group) {
int z = convertZ(group.getZIndex());
if (zIndex.get(z) == null)
zIndex.set(z, new HashSet());
zIndex.get(z).add(group);
reverseZIndex.put(group.getId(), z);
}
/**
* A group eventually changed, check its location.
*
* @param group
* The group to check.
*/
protected void groupChanged(StyleGroup group) {
int oldZ = reverseZIndex.get(group.getId());
int newZ = convertZ(group.getZIndex());
if (oldZ != newZ) {
HashSet map = zIndex.get(oldZ);
if (map != null) {
map.remove(group);
reverseZIndex.remove(group.getId());
if (map.isEmpty())
zIndex.set(oldZ, null);
}
groupAdded(group);
}
}
/**
* A group was removed, remove it from the Z index.
*
* @param group
* The group to remove.
*/
protected void groupRemoved(StyleGroup group) {
int z = convertZ(group.getZIndex());
HashSet map = zIndex.get(z);
if (map != null) {
map.remove(group);
reverseZIndex.remove(group.getId());
if (map.isEmpty())
zIndex.set(z, null);
} else {
throw new RuntimeException("Inconsistency in Z-index");
}
}
public void clear() {
zIndex.clear();
reverseZIndex.clear();
initZIndex();
}
/**
* Convert a [-127,127] value into a [0,255] value and check bounds.
*
* @param z
* The Z value to convert.
* @return The Z value converted and bounded to [0,255].
*/
protected int convertZ(int z) {
z += 127;
if (z < 0)
z = 0;
else if (z > 255)
z = 255;
return z;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(String.format("Z index :%n"));
for (int i = 0; i < 256; i++) {
if (zIndex.get(i) != null) {
sb.append(String.format(" * %d -> ", i - 127));
HashSet map = zIndex.get(i);
for (StyleGroup g : map)
sb.append(String.format("%s ", g.getId()));
sb.append(String.format("%n"));
}
}
return sb.toString();
}
public class ZIndexIterator implements Iterator> {
public int index = 0;
public ZIndexIterator() {
zapUntilACell();
}
protected void zapUntilACell() {
while (index < 256 && zIndex.get(index) == null)
index++;
}
public boolean hasNext() {
return (index < 256);
}
public HashSet next() {
if (hasNext()) {
HashSet cell = zIndex.get(index);
index++;
zapUntilACell();
return cell;
}
return null;
}
public void remove() {
throw new RuntimeException("This iterator does not support removal.");
}
}
}
/**
* Set of groups that cast a shadow.
*
* @author Antoine Dutot
*/
public class ShadowSet implements Iterable {
/**
* The set of groups casting shadow.
*/
protected HashSet shadowSet = new HashSet();
/**
* Iterator on the set of groups that cast a shadow.
*
* @return An iterator on the shadow style group set.
*/
protected Iterator getIterator() {
return shadowSet.iterator();
}
public Iterator iterator() {
return getIterator();
}
/**
* A group appeared, check its shadow status.
*
* @param group
* The group added.
*/
protected void groupAdded(StyleGroup group) {
if (group.getShadowMode() != ShadowMode.NONE)
shadowSet.add(group);
}
/**
* A group eventually changed, check its shadow status.
*
* @param group
* The group that changed.
*/
protected void groupChanged(StyleGroup group) {
if (group.getShadowMode() == ShadowMode.NONE)
shadowSet.remove(group);
else
shadowSet.add(group);
}
/**
* A group was removed, remove it from the shadow if needed.
*
* @param group
* The group removed.
*/
protected void groupRemoved(StyleGroup group) {
// Faster than to first test its existence or shadow status :
shadowSet.remove(group);
}
protected void clear() {
shadowSet.clear();
}
}
/**
* Iterator that allows to browse all graph elements of a given kind (nodes,
* edges, sprites, graphs) as if they where in a single set, whereas they are in
* style groups.
*
* @author Antoine Dutot
* @param
* The kind of graph element.
*/
protected class ElementIterator implements Iterator {
protected Map elt2grp;
protected Iterator elts;
public ElementIterator(final Map elements2groups) {
elt2grp = elements2groups;
elts = elements2groups.keySet().iterator();
}
public boolean hasNext() {
return elts.hasNext();
}
@SuppressWarnings("unchecked")
public E next() {
String eid = elts.next();
String gid = elt2grp.get(eid);
StyleGroup grp = groups.get(gid);
return (E) grp.getElement(eid);
}
public void remove() {
throw new RuntimeException("remove not implemented in this iterator");
}
}
/**
* Dummy set of nodes.
*/
protected class NodeSet implements Iterable {
@SuppressWarnings("unchecked")
public Iterator iterator() {
return (Iterator) getNodeIterator();
}
}
/**
* Dummy set of edges.
*/
protected class EdgeSet implements Iterable {
@SuppressWarnings("unchecked")
public Iterator iterator() {
return (Iterator) getEdgeIterator();
}
}
/**
* Dummy set of sprites.
*/
protected class SpriteSet implements Iterable {
@SuppressWarnings("unchecked")
public Iterator iterator() {
return (Iterator) getSpriteIterator();
}
}
protected class GraphSet implements Iterable {
@SuppressWarnings("unchecked")
public Iterator iterator() {
return (Iterator) getGraphIterator();
}
}
}