Customizing uiundo

Wednesday, November 4th, 2009

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)
   undoObj = uitools.FigureToolManager(hFig);
   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

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.

Related posts:

  1. uiundo – Matlab’s undocumented undo/redo manager The built-in uiundo function provides easy yet undocumented access to Matlab's powerful undo/redo functionality. This article explains its usage....
  2. Customizing print setup Matlab figures print-setup can be customized to automatically prepare the figure for printing in a specific configuration...
  3. Figure toolbar components Matlab's toolbars can be customized using a combination of undocumented Matlab and Java hacks. This article describes how to access existing toolbar icons and how to add non-button toolbar components....
  4. Customizing Matlab labels Matlab's text uicontrol is not very customizable, and does not support HTML or Tex formatting. This article shows how to display HTML labels in Matlab and some undocumented customizations...
  5. Customizing help popup contents The built-in HelpPopup, available since Matlab R2007b, has a back-door that enables displaying arbitrary text, HTML and URL web-pages....
  6. Matlab’s HG2 mechanism HG2 is presumably the next generation of Matlab graphics. This article tries to explore its features....

Tags: , , , , ,

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

This entry was posted on Wednesday, November 4th, 2009 at 5:29 pm PST
You can follow any responses to this entry through the RSS 2.0 feed.
You can leave a response, or trackback from your own site.

PoorSo-soHelpfulVery helpfulExcellent! (No Ratings Yet)
Loading ... Loading ...
Bookmark and Share Print Print

5 Responses to “Customizing uiundo”

  1. Venkat 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 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

  3. Jason McMains 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

  4. Jason McMains 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.

  5. Yair Altman Yair Altman says:

    @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).

Leave a Reply


Wrap code fragments inside <pre lang="matlab"> tags, like this:

   <pre lang="matlab">
   a = magic(3);
   sum(a)
   </pre>