diff --git a/src/main/java/nl/utwente/groove/grammar/aspect/AspectGraph.java b/src/main/java/nl/utwente/groove/grammar/aspect/AspectGraph.java index 7211b57f5..e39cdc70d 100644 --- a/src/main/java/nl/utwente/groove/grammar/aspect/AspectGraph.java +++ b/src/main/java/nl/utwente/groove/grammar/aspect/AspectGraph.java @@ -313,10 +313,9 @@ public AspectGraph relabel(TypeLabel oldLabel, TypeLabel newLabel) { */ public AspectGraph colour(TypeLabel label, Aspect colour) { assert getRole() == TYPE; - // create a plain graph under relabelling PlainGraph result = createPlainGraph(); AspectToPlainMap elementMap = new AspectToPlainMap(); - // flag registering if anything changed due to relabelling + // flag registering if anything changed due to the colour change boolean graphChanged = false; // construct the plain graph for the aspect nodes, // except for the colour aspects @@ -360,6 +359,56 @@ public AspectGraph colour(TypeLabel label, Aspect colour) { } } + /** + * Returns an aspect graph obtained from this one by changing the colour aspect + * of one or more nodes. + * @param changedNodes the nodes to be changed (guaranteed to be nodes of this graph) + * @param colour the new colour for the node; may be {@code null} + * if the colour is to be reset to default + * @return a clone of this aspect graph with changed node colour, or this graph + * if {@code node} already had the required colour + */ + public AspectGraph colour(Collection changedNodes, Aspect colour) { + boolean graphChanged = false; + // create a plain graph + PlainGraph result = createPlainGraph(); + AspectToPlainMap elementMap = new AspectToPlainMap(); + // construct the plain graph for the aspect nodes, + // except for the colour aspects of the changed node + for (AspectNode node : nodeSet()) { + boolean isChangedNode = changedNodes.contains(node); + graphChanged |= isChangedNode && !Objects.equals(node.get(COLOR), colour); + PlainNode image = result.addNode(node.getNumber()); + elementMap.putNode(node, image); + node + .getNodeLabels() + .stream() + .filter(l -> !isChangedNode || !l.has(COLOR)) + .forEach(l -> result.addEdge(image, l.toString(), image)); + if (isChangedNode && colour != null) { + result.addEdge(image, colour.toString(), image); + } + } + if (graphChanged) { + // copy the edges over + for (AspectEdge edge : edgeSet()) { + AspectLabel edgeLabel = edge.label(); + PlainNode sourceImage = elementMap.getNode(edge.source()); + assert sourceImage != null; + PlainNode targetImage = elementMap.getNode(edge.target()); + assert targetImage != null; + PlainEdge edgeImage + = result.addEdge(sourceImage, edgeLabel.toString(), targetImage); + elementMap.putEdge(edge, edgeImage); + } + GraphInfo.transferProperties(this, result, elementMap); + result.setFixed(); + return newInstance(result); + } else { + return this; + } + } + @Override public boolean addNode(AspectNode node) { assert !node.hasId() || !this.sortMap.isSet() : String diff --git a/src/main/java/nl/utwente/groove/gui/action/SelectColorAction.java b/src/main/java/nl/utwente/groove/gui/action/SelectColorAction.java index 7c4009028..f7c3ebefc 100644 --- a/src/main/java/nl/utwente/groove/gui/action/SelectColorAction.java +++ b/src/main/java/nl/utwente/groove/gui/action/SelectColorAction.java @@ -2,12 +2,12 @@ import java.awt.Color; import java.io.IOException; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; import javax.swing.JColorChooser; import javax.swing.JDialog; -import javax.swing.event.TreeSelectionEvent; -import javax.swing.event.TreeSelectionListener; -import javax.swing.tree.TreePath; import org.jgraph.event.GraphSelectionEvent; import org.jgraph.event.GraphSelectionListener; @@ -15,25 +15,21 @@ import nl.utwente.groove.grammar.aspect.Aspect; import nl.utwente.groove.grammar.aspect.AspectGraph; import nl.utwente.groove.grammar.aspect.AspectKind; +import nl.utwente.groove.grammar.aspect.AspectNode; import nl.utwente.groove.grammar.model.ResourceKind; -import nl.utwente.groove.grammar.type.TypeLabel; -import nl.utwente.groove.grammar.type.TypeNode; import nl.utwente.groove.graph.GraphRole; -import nl.utwente.groove.graph.Label; import nl.utwente.groove.gui.Options; import nl.utwente.groove.gui.Simulator; -import nl.utwente.groove.gui.jgraph.JCell; +import nl.utwente.groove.gui.jgraph.AspectJGraph; +import nl.utwente.groove.gui.jgraph.AspectJVertex; import nl.utwente.groove.gui.jgraph.JGraph; -import nl.utwente.groove.gui.tree.LabelTree; -import nl.utwente.groove.gui.tree.TypeTree.TypeTreeNode; import nl.utwente.groove.util.Exceptions; import nl.utwente.groove.util.parse.FormatException; /** * Action for selecting a colour for a type node. */ -public class SelectColorAction extends SimulatorAction - implements GraphSelectionListener, TreeSelectionListener { +public class SelectColorAction extends SimulatorAction implements GraphSelectionListener { /** Constructs an instance of the action. */ public SelectColorAction(Simulator simulator) { super(simulator, Options.SELECT_COLOR_ACTION_NAME, null); @@ -43,43 +39,25 @@ public SelectColorAction(Simulator simulator) { } /** Checks if in a given JGraph a type label is selected. */ - private void checkJGraph(JGraph jGraph) { - this.label = null; + private void checkJGraph(AspectJGraph jGraph) { + this.graph = null; + this.nodes.clear(); Object[] selection = jGraph.getSelectionCells(); if (selection != null) { - choose: for (Object cell : selection) { - for (Label entry : ((JCell) cell).getKeys()) { - if (entry instanceof TypeNode) { - this.label = ((TypeNode) entry).label(); - break choose; - } - } - } - } - refresh(); - } - - /** Checks if in a given {@link LabelTree} a type label is selected. */ - private void checkLabelTree(LabelTree tree) { - this.label = null; - TreePath[] selection = tree.getSelectionPaths(); - if (selection != null) { - for (TreePath path : selection) { - Object treeNode = path.getLastPathComponent(); - if (treeNode instanceof TypeTreeNode n && n.getEntry().isForNode()) { - this.label = n.getEntry().getType().label(); - break; - } - } + this.graph = jGraph.getGraph(); + var selectionStream = Arrays.stream(selection); + // find the relevant nodes + selectionStream + .filter(c -> c instanceof AspectJVertex) + .map(v -> ((AspectJVertex) v).getNode()) + .forEach(this.nodes::add); } refresh(); } @Override public void execute() { - TypeNode typeNode = getGrammarModel().getTypeGraph().getNode(this.label); - assert typeNode != null; // ensured by the label - Color initColour = typeNode.getDeclaredColor(); + Color initColour = this.nodes.stream().findFirst().get().getColor(); if (initColour != null) { this.chooser.setColor(initColour); } @@ -102,37 +80,35 @@ private void setColour(Color newColour) { throw Exceptions.UNREACHABLE; } } - for (AspectGraph typeGraph : getGrammarStore().getGraphs(ResourceKind.TYPE).values()) { - AspectGraph newTypeGraph = typeGraph.colour(this.label, colourAspect); - if (newTypeGraph != typeGraph) { - try { - getSimulatorModel().doAddGraph(ResourceKind.TYPE, newTypeGraph, false); - } catch (IOException exc) { - showErrorDialog(exc, String - .format("Error while saving type graph '%s'", typeGraph.getName())); - } + var hostGraph = this.graph; + assert hostGraph != null; + var newHostGraph = hostGraph.colour(this.nodes, colourAspect); + if (newHostGraph != hostGraph) { + try { + getSimulatorModel() + .doAddGraph(ResourceKind.toResource(this.graph.getRole()), newHostGraph, false); + } catch (IOException exc) { + showErrorDialog(exc, String + .format("Error while saving host graph '%s'", hostGraph.getName())); } } } - @Override - public void valueChanged(TreeSelectionEvent e) { - checkLabelTree((LabelTree) e.getSource()); - } - - /** Sets {@link #label} based on the {@link JGraph} selection. */ + /** Sets {@link #nodes} based on the {@link JGraph} selection. */ @Override public void valueChanged(GraphSelectionEvent e) { - checkJGraph((JGraph) e.getSource()); + checkJGraph((AspectJGraph) e.getSource()); } @Override public void refresh() { - super.setEnabled(this.label != null); + super.setEnabled(!this.nodes.isEmpty()); } - /** The label for which a colour is chosen; may be {@code null}. */ - private TypeLabel label; + /** The graph to be changed. */ + private AspectGraph graph; + /** The selected nodes to be changed */ + private Set nodes = new HashSet<>(); private final JColorChooser chooser; } \ No newline at end of file diff --git a/src/main/java/nl/utwente/groove/gui/jgraph/AspectJGraph.java b/src/main/java/nl/utwente/groove/gui/jgraph/AspectJGraph.java index 0e494d2b0..5922d2e88 100644 --- a/src/main/java/nl/utwente/groove/gui/jgraph/AspectJGraph.java +++ b/src/main/java/nl/utwente/groove/gui/jgraph/AspectJGraph.java @@ -96,10 +96,17 @@ public AspectJGraph(Simulator simulator, DisplayKind kind, boolean editing) { @Override protected void installListeners() { super.installListeners(); + addGraphSelectionListener(getActions().getSelectColorAction()); addOptionListener(SHOW_ASPECTS_OPTION); addOptionListener(SHOW_VALUE_NODES_OPTION); } + @Override + public void removeListeners() { + super.removeListeners(); + removeGraphSelectionListener(getActions().getSelectColorAction()); + } + @Override public void setModel(GraphModel model) { GraphModel oldModel = getModel(); diff --git a/src/main/java/nl/utwente/groove/gui/jgraph/JGraph.java b/src/main/java/nl/utwente/groove/gui/jgraph/JGraph.java index 479223085..f9dfbca8a 100755 --- a/src/main/java/nl/utwente/groove/gui/jgraph/JGraph.java +++ b/src/main/java/nl/utwente/groove/gui/jgraph/JGraph.java @@ -151,7 +151,6 @@ protected void installListeners() { addMouseListener(getMouseListener()); addKeyListener(getCancelEditListener()); getSelectionModel().addGraphSelectionListener(getGraphSelectionListener()); - addGraphSelectionListener(getActions().getSelectColorAction()); addOptionListener(SHOW_INTERNAL_NODE_IDS_OPTION); addOptionListener(SHOW_USER_NODE_IDS_OPTION); addOptionListener(SHOW_ANCHORS_OPTION); @@ -167,7 +166,6 @@ public void removeListeners() { removeMouseListener(getMouseListener()); removeKeyListener(getCancelEditListener()); getSelectionModel().removeGraphSelectionListener(getGraphSelectionListener()); - removeGraphSelectionListener(getActions().getSelectColorAction()); getActions().removeRefreshable(getExportAction()); for (Pair record : this.optionListeners) { record.one().removeItemListener(record.two());