X

Geertjan's Blog

  • September 8, 2010

UndoRedo in Visual Library Scenes

Geertjan Wielenga
Product Manager
In the recent training in Stellenbosch, we managed to include the NetBeans Platform UndoRedo support in an application we worked on, as described in this tutorial. However, when we looked at the Visual Library, the question was raised: "How would UndoRedo work in a Visual Library scene?"

As you can see below, I managed to get it working. I.e., when a widget is moved, the Undo action is enabled. When the Undo action is invoked, the widget is returned to where it was prior to the move. (And then the Redo action is enabled, with corresponding code.)

For this scenario, the widget needs its own MoveStrategy, which has been discussed elsewhere in this blog:

//widget.createActions(ACTION_MOVE).addAction(ActionFactory.createMoveAction());
MyMoveStrategyProvider provider = new MyMoveStrategyProvider();
widget.createActions(ACTION_MOVE).addAction(ActionFactory.createMoveAction(provider, provider));

Within the TopComponent, as always when working with UndoRedo, you need to return the UndoRedo.Manager from the getUndoRedo method, which is one of the methods defined in the TopComponent:

private UndoRedo.Manager manager = new UndoRedo.Manager();
@Override
public UndoRedo getUndoRedo() {
return manager;
}

Now that we have an UndoRedo.Manager, we can use it within our MoveStrategy:

public final class MyMoveStrategyProvider implements MoveStrategy, MoveProvider {
private HashMap originalLocations = new HashMap();
private Point originalLoc;
private Point suggestedLoc;
@Override
public Point locationSuggested(Widget widget, Point originalLocation, Point suggestedLocation) {
this.originalLoc = originalLocation;
this.suggestedLoc = suggestedLocation;
return suggestedLocation;
}
@Override
public void movementStarted(Widget widget) {
manager.addEdit(new MyAbstractUndoableEdit(widget));
}
@Override
public void movementFinished(Widget widget) {
MyAbstractUndoableEdit myAbstractUndoableEdit = new MyAbstractUndoableEdit(widget);
manager.undoableEditHappened(new UndoableEditEvent(widget, myAbstractUndoableEdit));
}
class MyAbstractUndoableEdit extends AbstractUndoableEdit {
private final Widget widget;
private MyAbstractUndoableEdit(Widget widget) {
this.widget = widget;
}
@Override
public boolean canRedo() {
return true;
}
@Override
public boolean canUndo() {
return true;
}
@Override
public void undo() throws CannotUndoException {
widget.setPreferredLocation(originalLoc);
originalLocations.clear();
scene.validate();
}
@Override
public void redo() throws CannotUndoException {
widget.setPreferredLocation(suggestedLoc);
scene.validate();
}
}
@Override
public Point getOriginalLocation(Widget widget) {
return ActionFactory.createDefaultMoveProvider().getOriginalLocation(widget);
}
@Override
public void setNewLocation(Widget widget, Point location) {
ActionFactory.createDefaultMoveProvider().setNewLocation(widget, location);
}
}

That's all you need. Note that each widget is handled separately. I.e., the Undo/Redo is available for each individual widget, which is pretty cool. You could now try to store many moves per widget, rather than one, so that the user will be able to undo/redo many times, instead of just once per widget, as is currently the case.

Join the discussion

Comments ( 1 )
  • guest Wednesday, June 10, 2015

    First of all, you don't need that manager.addEdit() in movementStarted(), this will be done for you when you call manager.undoableEditHappened().

    Secondly, there is a problem with this code - when the user invokes Undo, the widget will be moved by the undo() operation, and this move will be registered by the UndoRedo.Manager, thus causing this "only a single undo available" problem, moreover, I guess the Redo is never available at all. UndoRedo.Manager can handle multiple undos (100 by default), you just need to track who triggered the move - if the move was triggered by an UndoableEdit, then you should not add a new UndoableEdit to the UndoRedo.Manager, if triggered by anything else, it should be added.


Please enter your name.Please provide a valid email address.Please enter a comment.CAPTCHA challenge response provided was incorrect. Please try again.