8290010: IGV: Fix UndoRedo Action

Reviewed-by: thartmann, rcastanedalo
This commit is contained in:
Tobias Holenstein 2022-10-24 15:04:35 +00:00
parent c055dfc3ce
commit 5ac6f185ee
6 changed files with 125 additions and 248 deletions

View File

@ -45,23 +45,6 @@ public class RangeSliderModel implements ChangedEventProvider<RangeSliderModel>
private int secondPosition;
private List<Color> colors;
public void setData(RangeSliderModel model) {
boolean changed = (positions != model.positions);
positions = model.positions;
changed |= (firstPosition != model.firstPosition);
firstPosition = model.firstPosition;
changed |= (secondPosition != model.secondPosition);
secondPosition = model.secondPosition;
boolean colorChanged = (colors != model.colors);
colors = model.colors;
if (changed) {
changedEvent.fire();
}
if (colorChanged) {
colorChangedEvent.fire();
}
}
public RangeSliderModel(List<String> positions) {
assert positions.size() > 0;
this.changedEvent = new ChangedEvent<>(this);
@ -90,12 +73,6 @@ public class RangeSliderModel implements ChangedEventProvider<RangeSliderModel>
return colors;
}
public RangeSliderModel copy() {
RangeSliderModel newModel = new RangeSliderModel(positions);
newModel.setData(this);
return newModel;
}
public List<String> getPositions() {
return Collections.unmodifiableList(positions);
}

View File

@ -84,12 +84,9 @@ public class DiagramScene extends ObjectScene implements DiagramViewer {
private UndoRedo.Manager undoRedoManager;
private final LayerWidget mainLayer;
private final LayerWidget blockLayer;
private DiagramViewModel model;
private DiagramViewModel modelCopy;
private final DiagramViewModel model;
private ModelState modelState;
private boolean rebuilding;
private boolean undoRedoEnabled = true;
/**
* The alpha level of partially visible figures.
@ -189,11 +186,27 @@ public class DiagramScene extends ObjectScene implements DiagramViewer {
}
@Override
public void centerFigures(List<Figure> list) {
boolean enableUndoRedo = undoRedoEnabled;
undoRedoEnabled = false;
gotoFigures(list);
undoRedoEnabled = enableUndoRedo;
public void centerFigures(List<Figure> figures) {
Rectangle overall = null;
getModel().showFigures(figures);
for (Figure f : figures) {
FigureWidget fw = getWidget(f);
if (fw != null) {
Rectangle r = fw.getBounds();
Point p = fw.getLocation();
assert r != null;
Rectangle r2 = new Rectangle(p.x, p.y, r.width, r.height);
if (overall == null) {
overall = r2;
} else {
overall = overall.union(r2);
}
}
}
if (overall != null) {
centerRectangle(overall);
}
}
private final ControllableChangedListener<SelectionCoordinator> highlightedCoordinatorListener = new ControllableChangedListener<SelectionCoordinator>() {
@ -366,10 +379,6 @@ public class DiagramScene extends ObjectScene implements DiagramViewer {
};
getActions().addAction(ActionFactory.createRectangularSelectAction(rectangularSelectDecorator, selectLayer, rectangularSelectProvider));
boolean enableUndoRedo = undoRedoEnabled;
undoRedoEnabled = false;
setNewModel(model);
undoRedoEnabled = enableUndoRedo;
ObjectSceneListener selectionChangedListener = new ObjectSceneListener() {
@Override
@ -455,6 +464,14 @@ public class DiagramScene extends ObjectScene implements DiagramViewer {
}
};
addObjectSceneListener(selectionChangedListener, ObjectSceneEventType.OBJECT_SELECTION_CHANGED, ObjectSceneEventType.OBJECT_HIGHLIGHTING_CHANGED, ObjectSceneEventType.OBJECT_HOVER_CHANGED);
this.model = model;
this.modelState = new ModelState(model);
this.model.getDiagramChangedEvent().addListener(m -> update());
this.model.getGraphChangedEvent().addListener(m -> addUndo());
this.model.getSelectedNodesChangedEvent().addListener(m -> selectedNodesChanged());
this.model.getHiddenNodesChangedEvent().addListener(m -> hiddenNodesChanged());
update();
}
public DiagramViewModel getModel() {
@ -512,18 +529,6 @@ public class DiagramScene extends ObjectScene implements DiagramViewer {
return a;
}
private void setNewModel(DiagramViewModel model) {
assert this.model == null : "can set model only once!";
this.model = model;
this.modelCopy = null;
model.getDiagramChangedEvent().addListener(fullChange);
model.getViewPropertiesChangedEvent().addListener(fullChange);
model.getViewChangedEvent().addListener(selectionChange);
model.getHiddenNodesChangedEvent().addListener(hiddenNodesChange);
update();
}
private void update() {
mainLayer.removeChildren();
blockLayer.removeChildren();
@ -591,21 +596,22 @@ public class DiagramScene extends ObjectScene implements DiagramViewer {
}
rebuilding = false;
smallUpdate(true);
updateHiddenNodes(model.getHiddenNodes(), true);
}
private void hiddenNodesChanged() {
updateHiddenNodes(model.getHiddenNodes(), true);
addUndo();
}
private void selectedNodesChanged() {
updateHiddenNodes(model.getHiddenNodes(), false);
}
protected boolean isRebuilding() {
return rebuilding;
}
private void smallUpdate(boolean relayout) {
updateHiddenNodes(model.getHiddenNodes(), relayout);
boolean enableUndoRedo = undoRedoEnabled;
undoRedoEnabled = false;
undoRedoEnabled = enableUndoRedo;
validate();
}
private boolean isVisible(Connection c) {
// Generally, a connection is visible if its source and destination
// widgets are visible. An exception is Figure connections in the CFG
@ -903,30 +909,6 @@ public class DiagramScene extends ObjectScene implements DiagramViewer {
return lookup;
}
private void gotoFigures(final List<Figure> figures) {
Rectangle overall = null;
getModel().showFigures(figures);
for (Figure f : figures) {
FigureWidget fw = getWidget(f);
if (fw != null) {
Rectangle r = fw.getBounds();
Point p = fw.getLocation();
assert r != null;
Rectangle r2 = new Rectangle(p.x, p.y, r.width, r.height);
if (overall == null) {
overall = r2;
} else {
overall = overall.union(r2);
}
}
}
if (overall != null) {
centerRectangle(overall);
}
}
private void gotoBlock(final Block block) {
BlockWidget bw = getWidget(block.getInputBlock());
if (bw != null) {
@ -1138,7 +1120,6 @@ public class DiagramScene extends ObjectScene implements DiagramViewer {
relayout(oldVisibleWidgets);
}
validate();
addUndo();
}
private void showFigure(Figure f) {
@ -1183,18 +1164,21 @@ public class DiagramScene extends ObjectScene implements DiagramViewer {
return menu;
}
private static class DiagramUndoRedo extends AbstractUndoableEdit implements ChangedListener<DiagramViewModel> {
private boolean undoRedoEnabled = true;
private final DiagramViewModel oldModel;
private final DiagramViewModel newModel;
private final Point oldScrollPosition;
private static class DiagramUndoRedo extends AbstractUndoableEdit {
private final ModelState oldState;
private final ModelState newState;
private Point oldScrollPosition;
private Point newScrollPosition;
private final DiagramScene scene;
public DiagramUndoRedo(DiagramScene scene, Point oldScrollPosition, DiagramViewModel oldModel, DiagramViewModel newModel) {
assert oldModel != null;
assert newModel != null;
this.oldModel = oldModel;
this.newModel = newModel;
public DiagramUndoRedo(DiagramScene scene, Point oldScrollPosition, ModelState oldState, ModelState newState) {
assert oldState != null;
assert newState != null;
this.oldState = oldState;
this.newState = newState;
this.scene = scene;
this.oldScrollPosition = oldScrollPosition;
}
@ -1202,69 +1186,44 @@ public class DiagramScene extends ObjectScene implements DiagramViewer {
@Override
public void redo() throws CannotRedoException {
super.redo();
boolean enableUndoRedo = scene.undoRedoEnabled;
scene.undoRedoEnabled = false;
scene.getModel().getViewChangedEvent().addListener(this);
scene.getModel().setData(newModel);
scene.getModel().getViewChangedEvent().removeListener(this);
scene.undoRedoEnabled = enableUndoRedo;
oldScrollPosition = scene.getScrollPosition();
scene.getModel().setHiddenNodes(newState.hiddenNodes);
scene.getModel().setPositions(newState.firstPos, newState.secondPos);
scene.setScrollPosition(newScrollPosition);
scene.undoRedoEnabled = true;
}
@Override
public void undo() throws CannotUndoException {
super.undo();
boolean enableUndoRedo = scene.undoRedoEnabled;
scene.undoRedoEnabled = false;
scene.getModel().getViewChangedEvent().addListener(this);
scene.getModel().setData(oldModel);
scene.getModel().getViewChangedEvent().removeListener(this);
SwingUtilities.invokeLater(() -> scene.setScrollPosition(oldScrollPosition));
scene.undoRedoEnabled = enableUndoRedo;
}
@Override
public void changed(DiagramViewModel source) {
scene.getModel().getViewChangedEvent().removeListener(this);
scene.smallUpdate(!oldModel.getHiddenNodes().equals(newModel.getHiddenNodes()));
newScrollPosition = scene.getScrollPosition();
scene.getModel().setHiddenNodes(oldState.hiddenNodes);
scene.getModel().setPositions(oldState.firstPos, oldState.secondPos);
scene.setScrollPosition(oldScrollPosition);
scene.undoRedoEnabled = true;
}
}
private final ChangedListener<DiagramViewModel> fullChange = new ChangedListener<DiagramViewModel>() {
@Override
public void changed(DiagramViewModel source) {
assert source == model : "Receive only changed event from current model!";
assert source != null;
update();
}
};
private static class ModelState {
public final Set<Integer> hiddenNodes;
public final int firstPos;
public final int secondPos;
private final ChangedListener<DiagramViewModel> hiddenNodesChange = new ChangedListener<DiagramViewModel>() {
@Override
public void changed(DiagramViewModel source) {
assert source == model : "Receive only changed event from current model!";
assert source != null;
smallUpdate(true);
public ModelState(DiagramViewModel model) {
hiddenNodes = new HashSet<>(model.getHiddenNodes());
firstPos = model.getFirstPosition();
secondPos = model.getSecondPosition();
}
};
private final ChangedListener<DiagramViewModel> selectionChange = new ChangedListener<DiagramViewModel>() {
@Override
public void changed(DiagramViewModel source) {
assert source == model : "Receive only changed event from current model!";
assert source != null;
smallUpdate(false);
}
};
}
private void addUndo() {
DiagramViewModel newModelCopy = model.copy();
ModelState newModelState = new ModelState(model);
if (undoRedoEnabled) {
getUndoRedoManager().undoableEditHappened(new UndoableEditEvent(this, new DiagramUndoRedo(this, getScrollPosition(), modelCopy, newModelCopy)));
DiagramUndoRedo undoRedo = new DiagramUndoRedo(this, getScrollPosition(), modelState, newModelState);
getUndoRedoManager().undoableEditHappened(new UndoableEditEvent(this, undoRedo));
}
modelCopy = newModelCopy;
modelState = newModelState;
}
}

View File

@ -46,18 +46,18 @@ import org.openide.util.Lookup;
public class DiagramViewModel extends RangeSliderModel implements ChangedListener<RangeSliderModel> {
// Warning: Update setData method if fields are added
private Group group;
private final Group group;
private ArrayList<InputGraph> graphs;
private Set<Integer> hiddenNodes;
private Set<Integer> selectedNodes;
private FilterChain filterChain;
private FilterChain sequenceFilterChain;
private final FilterChain filterChain;
private final FilterChain sequenceFilterChain;
private Diagram diagram;
private InputGraph cachedInputGraph;
private final ChangedEvent<DiagramViewModel> diagramChangedEvent;
private final ChangedEvent<DiagramViewModel> viewChangedEvent;
private final ChangedEvent<DiagramViewModel> graphChangedEvent;
private final ChangedEvent<DiagramViewModel> selectedNodesChangedEvent;
private final ChangedEvent<DiagramViewModel> hiddenNodesChangedEvent;
private final ChangedEvent<DiagramViewModel> viewPropertiesChangedEvent;
private boolean showSea;
private boolean showBlocks;
private boolean showCFG;
@ -65,73 +65,19 @@ public class DiagramViewModel extends RangeSliderModel implements ChangedListene
private boolean showEmptyBlocks;
private boolean hideDuplicates;
private final ChangedListener<FilterChain> filterChainChangedListener = source -> updateDiagram();
@Override
public DiagramViewModel copy() {
DiagramViewModel result = new DiagramViewModel(cachedInputGraph, filterChain, sequenceFilterChain);
result.setData(this);
return result;
}
private final ChangedListener<FilterChain> filterChainChangedListener = source -> rebuildDiagram();
public Group getGroup() {
return group;
}
public void setData(DiagramViewModel newModel) {
super.setData(newModel);
if (group != newModel.group) {
if (group != null) {
group.getChangedEvent().removeListener(groupContentChangedListener);
}
group = newModel.group;
if (group != null) {
group.getChangedEvent().addListener(groupContentChangedListener);
}
filterGraphs();
}
boolean diagramChanged = filterChain != newModel.filterChain;
diagramChanged |= (sequenceFilterChain != newModel.sequenceFilterChain);
diagramChanged |= (diagram != newModel.diagram);
boolean viewChanged = hiddenNodes != newModel.hiddenNodes;
viewChanged |= (selectedNodes != newModel.selectedNodes);
boolean viewPropertiesChanged = (showSea != newModel.showSea);
viewPropertiesChanged |= (showBlocks != newModel.showBlocks);
viewPropertiesChanged |= (showCFG != newModel.showCFG);
viewPropertiesChanged |= (showNodeHull != newModel.showNodeHull);
filterChain = newModel.filterChain;
sequenceFilterChain = newModel.sequenceFilterChain;
diagram = newModel.diagram;
hiddenNodes = newModel.hiddenNodes;
selectedNodes = newModel.selectedNodes;
showSea = newModel.showSea;
showBlocks = newModel.showBlocks;
showCFG = newModel.showCFG;
showNodeHull = newModel.showNodeHull;
if (diagramChanged) {
diagramChangedEvent.fire();
}
if (viewPropertiesChanged) {
viewPropertiesChangedEvent.fire();
}
if (viewChanged) {
viewChangedEvent.fire();
}
}
public boolean getShowSea() {
return showSea;
}
public void setShowSea(boolean b) {
showSea = b;
viewPropertiesChangedEvent.fire();
diagramChangedEvent.fire();
}
public boolean getShowBlocks() {
@ -140,7 +86,7 @@ public class DiagramViewModel extends RangeSliderModel implements ChangedListene
public void setShowBlocks(boolean b) {
showBlocks = b;
viewPropertiesChangedEvent.fire();
diagramChangedEvent.fire();
}
public boolean getShowCFG() {
@ -149,7 +95,7 @@ public class DiagramViewModel extends RangeSliderModel implements ChangedListene
public void setShowCFG(boolean b) {
showCFG = b;
viewPropertiesChangedEvent.fire();
diagramChangedEvent.fire();
}
public boolean getShowNodeHull() {
@ -158,7 +104,7 @@ public class DiagramViewModel extends RangeSliderModel implements ChangedListene
public void setShowNodeHull(boolean b) {
showNodeHull = b;
viewPropertiesChangedEvent.fire();
diagramChangedEvent.fire();
}
public boolean getShowEmptyBlocks() {
@ -167,7 +113,7 @@ public class DiagramViewModel extends RangeSliderModel implements ChangedListene
public void setShowEmptyBlocks(boolean b) {
showEmptyBlocks = b;
viewPropertiesChangedEvent.fire();
diagramChangedEvent.fire();
}
public void setHideDuplicates(boolean hideDuplicates) {
@ -182,44 +128,37 @@ public class DiagramViewModel extends RangeSliderModel implements ChangedListene
}
filterGraphs();
selectGraph(currentGraph);
viewPropertiesChangedEvent.fire();
diagramChangedEvent.fire();
}
public DiagramViewModel(InputGraph graph, FilterChain filterChain, FilterChain sequenceFilterChain) {
super(Collections.singletonList("default"));
assert filterChain != null;
assert sequenceFilterChain != null;
this.filterChain = filterChain;
this.sequenceFilterChain = sequenceFilterChain;
showSea = Settings.get().getInt(Settings.DEFAULT_VIEW, Settings.DEFAULT_VIEW_DEFAULT) == Settings.DefaultView.SEA_OF_NODES;
showBlocks = Settings.get().getInt(Settings.DEFAULT_VIEW, Settings.DEFAULT_VIEW_DEFAULT) == Settings.DefaultView.CLUSTERED_SEA_OF_NODES;
showCFG = Settings.get().getInt(Settings.DEFAULT_VIEW, Settings.DEFAULT_VIEW_DEFAULT) == Settings.DefaultView.CONTROL_FLOW_GRAPH;
showNodeHull = true;
showEmptyBlocks = true;
group = graph.getGroup();
group.getChangedEvent().addListener(groupContentChangedListener);
filterGraphs();
assert filterChain != null;
this.filterChain = filterChain;
assert sequenceFilterChain != null;
this.sequenceFilterChain = sequenceFilterChain;
hiddenNodes = new HashSet<>();
selectedNodes = new HashSet<>();
super.getChangedEvent().addListener(this);
diagramChangedEvent = new ChangedEvent<>(this);
viewChangedEvent = new ChangedEvent<>(this);
graphChangedEvent = new ChangedEvent<>(this);
selectedNodesChangedEvent = new ChangedEvent<>(this);
hiddenNodesChangedEvent = new ChangedEvent<>(this);
viewPropertiesChangedEvent = new ChangedEvent<>(this);
filterChain.getChangedEvent().addListener(filterChainChangedListener);
sequenceFilterChain.getChangedEvent().addListener(filterChainChangedListener);
super.getChangedEvent().addListener(this);
selectGraph(graph);
}
private final ChangedListener<Group> groupContentChangedListener = new ChangedListener<Group>() {
@Override
public void changed(Group source) {
assert source == group;
// If the group has been emptied, all corresponding graph views
// will be closed, so do nothing.
ChangedListener<Group> groupContentChangedListener = g -> {
assert g == group;
if (group.getGraphs().isEmpty()) {
// If the group has been emptied, all corresponding graph views
// will be closed, so do nothing.
@ -227,25 +166,32 @@ public class DiagramViewModel extends RangeSliderModel implements ChangedListene
}
filterGraphs();
setSelectedNodes(selectedNodes);
}
};
};
group.getChangedEvent().addListener(groupContentChangedListener);
filterChain.getChangedEvent().addListener(filterChainChangedListener);
sequenceFilterChain.getChangedEvent().addListener(filterChainChangedListener);
filterGraphs();
selectGraph(graph);
}
public ChangedEvent<DiagramViewModel> getDiagramChangedEvent() {
return diagramChangedEvent;
}
public ChangedEvent<DiagramViewModel> getViewChangedEvent() {
return viewChangedEvent;
public ChangedEvent<DiagramViewModel> getGraphChangedEvent() {
return graphChangedEvent;
}
public ChangedEvent<DiagramViewModel> getSelectedNodesChangedEvent() {
return selectedNodesChangedEvent;
}
public ChangedEvent<DiagramViewModel> getHiddenNodesChangedEvent() {
return hiddenNodesChangedEvent;
}
public ChangedEvent<DiagramViewModel> getViewPropertiesChangedEvent() {
return viewPropertiesChangedEvent;
}
public Set<Integer> getSelectedNodes() {
return selectedNodes;
}
@ -292,7 +238,7 @@ public class DiagramViewModel extends RangeSliderModel implements ChangedListene
}
}
setColors(colors);
viewChangedEvent.fire();
selectedNodesChangedEvent.fire();
}
public void showFigures(Collection<Figure> figures) {
@ -314,10 +260,6 @@ public class DiagramViewModel extends RangeSliderModel implements ChangedListene
return result;
}
public void showAll(final Collection<Figure> f) {
showFigures(f);
}
public void showOnly(final Set<Integer> nodes) {
final HashSet<Integer> allNodes = new HashSet<>(getGroup().getAllNodes());
allNodes.removeAll(nodes);
@ -333,7 +275,7 @@ public class DiagramViewModel extends RangeSliderModel implements ChangedListene
return filterChain;
}
private void updateDiagram() {
private void rebuildDiagram() {
// clear diagram
InputGraph graph = getGraph();
if (graph.getBlocks().isEmpty()) {
@ -356,7 +298,7 @@ public class DiagramViewModel extends RangeSliderModel implements ChangedListene
f.apply(diagram);
}
getDiagramChangedEvent().fire();
diagramChangedEvent.fire();
}
public FilterChain getFilterChain() {
@ -454,7 +396,8 @@ public class DiagramViewModel extends RangeSliderModel implements ChangedListene
} else {
cachedInputGraph = getFirstGraph();
}
updateDiagram();
rebuildDiagram();
graphChangedEvent.fire();
}
void close() {

View File

@ -238,8 +238,6 @@ public final class EditorTopComponent extends TopComponent {
topPanel.add(toolbarPanel);
topPanel.add(quickSearchToolbar);
container.add(BorderLayout.NORTH, topPanel);
getModel().getDiagramChangedEvent().fire();
}
public DiagramViewModel getModel() {

View File

@ -58,7 +58,7 @@ abstract public class ExpandAdjacentAction extends CallableSystemAction {
}
}
editor.getModel().showAll(figures);
editor.getModel().showFigures(figures);
}
}

View File

@ -54,17 +54,17 @@ abstract public class ModelAwareAction extends ContextAction<DiagramViewModel> {
@Override
public void addContextListener(DiagramViewModel model) {
model.getViewChangedEvent().addListener(this);
model.getSelectedNodesChangedEvent().addListener(this);
model.getDiagramChangedEvent().addListener(this);
model.getViewPropertiesChangedEvent().addListener(this);
model.getGraphChangedEvent().addListener(this);
model.getHiddenNodesChangedEvent().addListener(this);
}
@Override
public void removeContextListener(DiagramViewModel model) {
model.getViewChangedEvent().removeListener(this);
model.getSelectedNodesChangedEvent().removeListener(this);
model.getDiagramChangedEvent().removeListener(this);
model.getViewPropertiesChangedEvent().removeListener(this);
model.getGraphChangedEvent().removeListener(this);
model.getHiddenNodesChangedEvent().removeListener(this);
}