Customizing uiundo

Last week I discussed uiundo – Matlab’s undocumented undo/redo manager. Today, I will show how this object can be customized for some specific needs. However, we first need to understand a little more about how uiundo works beneath the hood.

Matlab stores all of a figure’s undo/redo data in a hidden figure object, referenced by getappdata(hFig,’uitools_FigureToolManager’). This means that by default uiundo works at the figure level, rather than the application level or the GUI component level. If we wish to modify this default behavior, we need to programmatically inspect and filter the undo/redo actions stack based on the action source. Read below to see how this can be done.

The hidden uitools_FigureToolManager object, defined in %MATLABROOT%\toolbox\matlab\uitools\@uiundo\, uses a stack to store instances of the undo/redo cmd data structure introduced in last week’s post:

% Retrieve redo/undo object
undoObj = getappdata(hFig,'uitools_FigureToolManager');
if isempty(undoObj)
   try
      % R2014a and earlier
      undoObj = uitools.FigureToolManager(hFig);
   catch
      % R2014b and newer
      undoObj = matlab.uitools.internal.FigureToolManager(hFig);
   end
   setappdata(hFig,'uitools_FigureToolManager',undoObj);
end
 
>> get(undoObj)
    CommandManager: [1x1 uiundo.CommandManager]
            Figure: [1x1 figure]
        UndoUIMenu: [1x1 uimenu]
        RedoUIMenu: [1x1 uimenu]

There are several interesting things we can do with this undoObj. First, let’s modify the main-menu items (I will discuss menu customization in more detail in another post):

% Modify the main menu item (similarly for redo/undo)
if ~isempty(undoObj.RedoUIMenu)
   undoObj.RedoUIMenu.Position =1; %default=2 (undo above redo)
   undoObj.RedoUIMenu.Enable = 'off';     % default='on'
   undoObj.RedoUIMenu.Checked = 'on';     % default='off'
   undoObj.RedoUIMenu.ForegroundColor = [1,0,0];  % =red
end
if ~isempty(undoObj.UndoUIMenu)
   undoObj.UndoUIMenu.Label = '<html><b><i>Undo action';
   % Note: &Undo underlines 'U' and adds a keyboard accelerator
   % but unfortunately only if the label is non-HTML ...
   undoObj.UndoUIMenu.Separator = 'on';   % default='off'
   undoObj.UndoUIMenu.Checked = 'on';     % default='off'
   undoObj.UndoUIMenu.ForegroundColor = 'blue'; % default=black
end
</i></b></html>

Menu before customization

Menu after customization

Figure menu before and after customization

Now, let’s take a look at undoObj’s CommandManager child (the Figure child object is simply handle(hFig), and so is not very interesting):

>> undoObj.CommandManager.get
             UndoStack: [13x1 uiundo.FunctionCommand]
             RedoStack: [1x1 uiundo.FunctionCommand]
    MaxUndoStackLength: []
               Verbose: []
 
>> undoObj.CommandManager.UndoStack(end).get
             Parent: []
       MCodeComment: []
               Name: 'slider update (0.48 to 0.38)'
           Function: @internal_update
           Varargin: {[53.0037841796875]  [0.38]  [1x1 double]}
    InverseFunction: @internal_update
    InverseVarargin: {[53.0037841796875]  [0.48]  [1x1 double]}

This looks familiar: In fact, it is exactly the cmd data structure being passed to the uiundo function, with the additional (apparently unused) properties Parent and MCodeComment. CommandManager‘s UndoStack and RedoStack child objects contain all stored undo/redo actions such that the latest action is at the end of these arrays. In the snippet above, there are 13 undo-able actions, with the latest action in UndoStack(end). UndoStack and RedoStack have the same structure:

  • Name contains the action description (presented in the figure’s menu)
  • Function is the function handle that will be invoked if the action is redone
  • Varargin are the arguments passed to Function during redo
  • InverseFunction is the function handle that will be invoked if the action is undone
  • InverseVarargin are the arguments passed to InverseFunction during undo
  • Parent and MCodeComment – I could not determine what these are used for

We can inspect the latest undo/redo actions, without activating them, by using CommandManager‘s peekundo() and peekredo() methods (which return empty [] if no undo/redo action is available):

>> undoObj.CommandManager.peekredo.get % first check if isempty
             Parent: []
       MCodeComment: []
               Name: 'slider update (0.38 to 0.28)'
           Function: @internal_update
           Varargin: {[53.0037841796875]  [0.28]  [1x1 double]}
    InverseFunction: @internal_update
    InverseVarargin: {[53.0037841796875]  [0.38]  [1x1 double]}
 
>> undoObj.CommandManager.peekundo.get
             Parent: []
       MCodeComment: []
               Name: 'slider update (0.48 to 0.38)'
           Function: @internal_update
           Varargin: {[53.0037841796875]  [0.38]  [1x1 double]}
    InverseFunction: @internal_update
    InverseVarargin: {[53.0037841796875]  [0.48]  [1x1 double]}
 
>> undoObj.CommandManager.peekundo.Name
ans =
slider update (0.48 to 0.38)

We can undo/redo the latest action (last element of the UndoStack/RedoStack) by invoking CommandManager‘s undo()/redo() methods. This is actually what uiundo is doing behind the scenes when it is called with the ‘execUndo’ and ‘execRedo’ arguments:

undoObj.CommandManager.undo;
undoObj.CommandManager.redo;

We can clear the entire actions stack by using CommandManager‘s empty() method. This can be useful, for example, after a ‘Save’ or ‘Apply’ operation in our GUI:

undoObj.CommandManager.empty;

If we set CommandManager‘s Verbose property to any non-empty value, debug information is spilled onto the Command Window when new uiundo actions are added:

>> undoObj.CommandManager.Verbose = 1;
 
% now move the slider and see the debug info below:
internal_update(h_uicontrol, [0.48,], h_uicontrol); % Called by slider update (0.28 to 0.48)
internal_update(h_uicontrol, [0.58,], h_uicontrol); % Called by slider update (0.48 to 0.58)

Finally, CommandManager uses its MaxUndoStackLength property to limit the size of the undo/redo stacks. This property is defined as read-only in %matlabroot%\toolbox\matlab\uitools\@uiundo\@CommandManager\schema.m line #12, so if you wish to programmatically modify this property from its default value of empty (=unlimited), you will need to comment out that line.

Categories: Handle graphics, Hidden property, High risk of breaking in future versions, Stock Matlab function, UI controls, Undocumented feature

Tags: , , , , , ,

Bookmark and SharePrint Print

7 Responses to Customizing uiundo

  1. Venkat says:

    Hi Yair,
    Can you please answer my query at
    http://groups.google.com/group/comp.soft-sys.matlab/browse_thread/thread/8bba37279a78b926#

    sorry for the irrevalent post here

    thanks

  2. Jason McMains says:

    Hey Yair,
    I’m trying to implement this in a GUI, and I have the basic functionality working, but I’m having trouble jumping to an undo point earlier than the most recent, i.e. if there are 5 possible undo actions, I would like to jump to the 3rd or 4th instead of the last.

    Also, Do you know how to create the list box used in the function button or the cell button in the editor? its a context menu with a scroll bar on the side. I’d like to use that functionality for an undo dropdown.

    Thanks for any help!
    Jason

    • Jason McMains says:

      small update: I’ve gotten this to work partially by using:

      undoObj.CommandManager.UndoStack(num).execute;

      but the undoObj structure wont update to reflect the changes.

      Thanks Again

    • Jason McMains says:

      Sorry for more updates, but here is my current solution

      for i=length(undoObj.CommandManager.UndoStack):-1:num
         undoObj.CommandManager.undo;
      end

      This does what I need, but it requires cycling through all the previous changes. I’d rather just skip to the desired step, because for my gui, this process is pretty slow.

    • @Jason – when you undo steps, you first need to undo all the actions that followed your desired undo-action, because they might affect the way your undone action behaves. Effectively, you are unrolling the actions at the exact reverse order in which they happened. If you look at any program with an undo list, they all behave the same way (try any editor, for example). I actually think it’s very intuitive.

      Regarding the scrollable undo/redo lists – see the previous post for an example. The scrollbar appears automatically when more than 8 list items are available. This is standard JComboBox behavior (the number 8 is a modifiable property of the JComboBox).

  3. John says:

    Hello Yair,

    thank you for your great guide! I implemented a redo/undo functionality in a small tool a couple of years ago, and it worked great … until now. Unfortunately, newer Matlab versions than 2014a don’t have the uitools_FigureToolManager object anymore, and I couldn’t find any alternatives. Do you (or anybody else) know if just the location has changed, or has the object completely been removed? Are there already any alternatives found to access Matlab’s redo/undo manager?

    Best regards

    • @John – I think you mean R2014b, not R2014a. In 14b the entire graphics engine changed (HG2) so this is not surprising. I updated the main post, but here is the relevant fix:

      try
         % R2014a and earlier
         undoObj = uitools.FigureToolManager(hFig);
      catch
         % R2014b and newer
         undoObj = matlab.uitools.internal.FigureToolManager(hFig);
      end

Leave a Reply


Your email address will not be published. Required fields are marked *