Posts Tagged ‘Undocumented feature’

Matlab and the Event Dispatch Thread (EDT)

Wednesday, March 10th, 2010

Once again I welcome guest blogger Matt Whitaker, with the long awaited EDT article.

Java Swing’s Event Dispatch Thread (EDT)
or: why does my GUI foul up?

Matlab for the most part is a single threaded environment. That is, all commands are executed sequentially along a single execution thread. The main exception to this are the Handle Graphics (GUI) components whose operations execute on the Java Event Dispatch Thread (EDT). EDT effects are reflected even in mundane Matlab GUI operations.

If we execute the code below we will probably see nothing until the loop completes and the figure appears with the text label showing ‘10000′:

h = figure;
txt = uicontrol('Parent',h, 'Style','text', 'String','1');
for n = 1:10000
    set(txt,'String',int2str(n))
end %for

By adding a couple of drawnow commands we get the figure and text label to render and then we see the count progress to 10000.

h = figure;
txt = uicontrol('Parent',h, 'Style','text', 'String','1');
drawnow;
for n = 1:10000
    set(txt,'String',int2str(n));
    drawnow;
end %for

The drawnow function allows the EDT queue to be flushed and the pending graphics operations to be evaluated. This will also happen with pause and several other commands.

If we want to use Swing (or AWT) components in our user interfaces we need to take this multi-threaded environment into account. The Swing toolkit designers decided to make all the Swing components thread un-safe in order to decrease their complexity. As a consequence, all access to Swing components should be done from the event dispatch thread (EDT), to ensure the operations are executed sequentially, at the exact order in which they were dispatched. Any action on a Swing component done on another thread (Matlab’s main processing thread in our case) risks a race-condition or deadlock with the EDT, which could (and often does) result in weird, non-deterministic and non-repetitive behavior – all of which should be avoided in any application which should behave in a precisely deterministic manner.

In Java, the usual pattern to accomplish EDT dispatching is to create a Runnable object, encapsulate the GUI code in the run method of the Runnable object, then pass the Runnable object to the static EventQueue.invokeLater (or EventQueue.invokeAndWait if we need to block operations to get a return value) method.

Runnable runnable = new Runnable()
{
    public void run()
    {
        //GUI Code here
    }
}
 
EventQueue.invokeLater(runnable);

There are several functions in Matlab that implement this programming pattern for us: javaObjectEDT, javaMethodEDT, awtinvoke, awtcreate and javacomponent. JavaMethodEDT and javaObjectEDT were introduced in version R2008b (7.7) and are minimally and only partially documented although they have reasonably complete help comments. The other three are semi-documented (meaning they are unsupported but if you edit or type their m-file you’ll see a fairly detailed help section), and although there is some overlap in their functionality they are still available.

javaObjectEDT and javaMethodEDT

javaObjectEDT is the the preferred method since R2008b of creating swing components to be used on the EDT. An object created with javaObjectEDT will have all of its subsequent method calls run on the EDT. This is termed Auto Delegation. Auto-delegation greatly simplifies and increases the readability of code. Note that objects created as a result of method calls may not be implemented on the EDT.

If you have an existing Java object, you can pass it to javaObjectEDT at any time - all its subsequent calls will then onward run on the EDT. Note that this useful functionality is an under-documented javaObjectEDT feature: it is not mentioned in the main help section but only implied from the example.

% Create a button on the EDT
btn = javaObjectEDT('javax.swing.JButton');
% this will run on EDT since btn was javaObjectEDT-created
btn.setText('Button');
 
% Create a button NOT on the EDT
btn2 = javax.swing.JButton;
% Dangerous! call will run on main Matlab thread
btn2.setText('Button2');
% modify btn2 so its methods will start running on the EDT
javaObjectEDT(btn2);
btn2.setText('Button2');

The following example shows the use of javaObjectEDT and javaMethodEDT in a more complex situation using a JTable:

function tableExample
hFig = figure;
drawnow; %need to get figure rendered
 
%use Yair's createTable to add a javax.swing.JTable
%http://www.mathworks.com/matlabcentral/fileexchange/14225-java-based-data-table
%wrap ceateTable in javaObjectEDT to put the ensuing method calls on the EDT
f = java.awt.Font(java.lang.String('Dialog'),java.awt.Font.PLAIN,14);
headers = {'Selected','File','Analysis Routine','Task Status'};
tbl = javaObjectEDT(createTable(hFig,headers,[],false,'Font',f));
 
%set column 1 to use check boxes and set up a change callback
tbl.setCheckBoxEditor(1);
jtable = javaObjectEDT(tbl.getTable); %get the underlying Java Table. IMPORTANT: we need to put jtable on the EDT
columnModel = javaObjectEDT(jtable.getColumnModel); %now we can now do direct calls safely on jtable
selectColumn = javaObjectEDT(columnModel.getColumn(0));
selectColumnCellEditor = selectColumn.getCellEditor;
chk = javaMethodEDT('getComponent',selectColumnCellEditor);
set(chk,'ItemStateChangedCallback',@chkChange_Callback);
 
%make column three a combo drop down
analysisTable = {'Analysis1';'Analysis2';'Analysis3'};
cb = javaObjectEDT('com.mathworks.mwswing.MJComboBox',analysisTable);
cb.setEditable(false);
cb.setFont(f);
set(cb,'ItemStateChangedCallback',@cbChange_Callback);
editor = javaObjectEDT('javax.swing.DefaultCellEditor',cb);
analysisColumn = javaObjectEDT(columnModel.getColumn(2));
analysisColumn.setCellEditor(editor);
 
%set some column with restrictions
selectColumn.setMaxWidth(100);
analysisColumn.setPreferredWidth(300);
 
%set the data
SELECTED = java.awt.event.ItemEvent.SELECTED;
tbl.setData({false,'file1','Analysis2','Analysis2';...
             true,'file2','Analysis3','Analysis3'});
drawnow;
 
    function cbChange_Callback(src,ev) %#ok
        jRow = jtable.getSelectedRow;
        stateChange = javaMethodEDT('getStateChange',ev);
        if stateChange == SELECTED
            newData = javaMethodEDT('getItem',ev);
            model = jtable.getModel;
            javaMethodEDT('setValueAt',model,newData,jRow,3);
        end %if
    end %cbChange
 
    function chkChange_Callback(src,ev) %#ok
        chkBox = javaMethodEDT('getItem',ev);
        if logical(javaMethodEDT('isSelected',chkBox))
            beep; %put useful code here
        else
            beep;
            pause(0.1)
            beep; %put useful code here
        end %if
    end %chkChange_Callback
 
end %tableExample

If you are running Matlab R2008a or later, javacomponent uses the javaObjectEDT function to create the returned objects so you do not have to do anything further to these objects to have their calls dispatched on the EDT. Users need to take care that objects added directly to the components created by javacomponent are on the EDT as well as specialized sub-components (e.g. CellRenderers and CellEditors). The overhead of calling javaMethodEDT is fairly small so if in doubt, use it.

javaObjectEDT and its kin first appeared in R2008a, although they only became supported in R2008b. Unfortunately, using them on R2008a sometimes causes hangs and all sorts of other mis-behaviors. This problem was fixed in the R2008b release, when javaObjectEDT became a fully-supported function. The problem with using javaObjectEDT in our application is that if it ever runs on an R2008a platform it might hang! (on Matlab release R2007b and earlier we will get an informative message saying that the javaObjectEDT function does not exist)

For this reason, I am using the following method in my projects:

function result = javaObjEDT(varargin)
%Placeholder of Matlab's buggy javaObjectEDT function on R2008a
 
% Programmed by Yair M. Altman: altmany(at)gmail.com
% $Revision: 1.2 $  $Date: 2009/01/25 11:31:08 $
 
  try
      try
          result = varargin{1};
      catch
          result = [];
      end
      v = version;
      if str2double(v(1:3)) > 7.6
          result = builtin('javaObjectEDT',varargin{:});
      end
  catch
      % never mind
  end
end

Note that javaMethodEDT has the method name as its first input argument, and the object name or reference as its second arg. This is inconsistent with many other Matlab/Java functions, which normally accept the target object as the first argument (compare: invoke, awtinvoke, notify etc.). It also means that we cannot use the familiar obj.javaMethodEDT(methodName) format.

One final note: when javaObjectEDT and javaMethodEDT first appeared in R2008a, they were complemented by the javaObjectMT and javaMethodMT functions, which create and delegate Java objects on the main Matlab computational thread. Their internal documentation says that there are cases when execution must occur on the MT rather than EDT, although I am personally not aware of any such case.

awtcreate and awtinvoke

For users with versions prior to R2008b the user must use the awtcreate function to create objects on the EDT. One huge disadvantage of this older function is that if you have to pass java objects in the parameter list you must use the very cumbersome JNI style notation. For example, for the simple task of setting a button label, one has to use:

btn = awtcreate('javax.swing.JButton');
awtinvoke(btn,'setText(Ljava/lang/String;)','click me')

The other disadvantage is that creating the object using awtcreate does not ensure that its subsequent method calls will be executed on the EDT. The awtinvoke function must be used for each call.

Also, both awtcreate and awtinvoke have some limitations due to bugs in the private parseJavaSignature function (for example, invoking methods which accept a java.lang.Object) which forces one to use the direct call to the method, using the main Matlab thread. This can result in the undesired effects described above. In this situation the best workaround is to call pause(0.01) to allow the event queue to clear.

Versions of javacomponent earlier than R2008a use awtcreate and objects created by these versions must have their subsequent methods called by awtinvoke to be used on the EDT.

A very rare CSSM thread discusses the usage of awtcreate and awtinvoke with some very interesting remarks by MathWorks personnel.

There is an interesting option in awtinvoke that was not carried over into the newer javaMethodEDT. This option allows the user to pass a function handle in the argument list along with its parameters. This option creates an undocumented com.mathworks.jmi.Callback object that has a delayed callback. The delayed callback is dispatched on the EDT so that it will be called once the java method used in awtinvoke is finished. Note that the actual function will still execute on the main Matlab thread the delayed callback will just control when it is called. However this may be useful at times. It is possible to put this functionality into a separate function we can call to delay execution until the event queue is cleared.

%CALLBACKONEDTQUEUE will place a callback on the EDT to asynchronously
%run a function.
%CALLBBACKONEDTQUEUE(FCN) will run function handle FCN once all previous
%methods dispatched to the EDT have completed.
%CALLBBACKONEDTQUEUE(FCN,ARG1,ARG2,...) ill run function handle FCN with
%arguments ARG1,ARG2...once all previous methods dispatched to the EDT
%have completed.
%Note that the function is still executing on the main Matlab thread. This
%function just delays when it will be called.
function callbackOnEDTQueue(varargin)
    validateattributes(varargin{1},{'function_handle'},{});
    callbackObj = handle(com.mathworks.jmi.Callback,'callbackProperties');
    set(callbackObj,'delayedCallback',{@cbEval,varargin(:)});
    callbackObj.postCallback;
 
    function cbEval(src,evt,args) %#ok
        feval(args{:});
    end %cbEval
end %callbackOnEDTQueue
Bookmark and Share

Setting desktop tab completions

Wednesday, March 3rd, 2010

This site has lately focused on quite detailed Java-related topics. Next week I will present the promised EDT article, which will dive into even deeper Java territory. So I thought to take a short break and present an entirely pure-Matlab non-Java undocumented feature, which is simple and yet quite useful.

A few months ago, a CSSM reader asked whether it is possible to customize Matlab tab-completion for user-defined functions (see related). A similar question on StackOverflow provided the necessary solution lead:

Apparently, Matlab has a file called TC.xml in its [matlabroot '/toolbox/local/'] folder that contains the definitions of the tab-completable functions and their arguments. In order for a user-defined function’s arguments to support tab-completion, a new entry needs to be added to this XML file.

TC.xml & TC.xsd

The full syntax of the TC.xml file can be found in the TC.xsd file, which is located in the same folder as TC.xml. Here are some sample definitions from my TC.xml file (which might vary across Matlab releases):

<binding name="addpath" ctype="DIR"/>
<binding name="help"    ctype="FUN SUBFUN"/>
<binding name="clear"   ctype="FUN VAR"/>
 
<binding name="whos"    ctype="VAR">
  <arg previous="-file" ctype= "MATFILE"/>
</binding>
 
<binding name="open">
  <arg argn="1" ctype="VAR MATFILE FIGFILE MFILE MDLFILE FILE"/>
</binding>
 
<binding name="openfig">
  <arg argn="1" ctype="FIGFILE"/>
  <arg argn="2" ctype="VAR" value="new visible invisible reuse"/>
</binding>
 
<binding name="mlint"   ctype="FUN">
  <arg argn="2:10" ctype="VAR" value="-struct -string -id"/>
</binding>

The first example defines that an unlimited number of addpath arguments are all of type DIR. Therefore, when completing any argument of this function in the Command-Window, Matlab will present only relevant DIR (=folder) elements in the pop-up window (lexically sorted):

Tab-completion of type DIR

Tab-completion of type DIR

Similarly, help defines all its arguments to be a function or sub-function type, so the popup-up will only be populated with the function names currently visible in the desktop:

Tab-completion of types FUN & SUBFUN

Tab-completion of types FUN & SUBFUN

Similarly, clear defines all its arguments as function names or variables. Note that the list of available functions and variables may change depending on the current execution stack position. The full list of supported types is defined in the TC.xsd file. It is: VAR, FUN, SUBFUN, DIR, FILE, MFILE, MATFILE, FIGFILE and MDLFILE.

The whos function defines all its arguments as VAR, except the single MATFILE argument that follows a ‘-file’ argument (look at whos’s help page to understand why).

The open function defines tab completion only for its first argument (with plenty of possible types…). Likewise, openfig defines its first argument as a FIGFILE, and its second as VAR with a few extra special-purpose strings that are added to the popup-up menu.

Finally, the mlint example shows that multiple arguments can be defined using a single XML definition element. In this case, args #2-10 are defined as VAR (with three extra special-purpose strings), while arg #1 and 11+ are defined as FUN.

The careful user can edit the TC.xml file using any text editor (I strongly suggest saving a backup first):

edit(fullfile(matlabroot,'toolbox/local/TC.xml'))

User-defined functions can easily be added to TC.xml, and we can even add/modify the built-in Matlab functions that are already defined. Note that changes to TC.xml only take effect after a Matlab restart. From then on, all future Matlab sessions will use the modification, so a really simple one-time edit can improve our workflow for a long time - at least until we upgrade Matlab, when we’ll need to redo our edits…

TabComplete utility

In order to facilitate TC.xml editing, I have created a utility called TabComplete, which is now available on the Matlab File Exchange. The use of this utility is very simple. For example:

tabcomplete test file 'DIR +data -data nodata' VAR

defines a user-defined function test that accepts a FILE argument, followed by a DIR argument with three special-purpose strings, followed by any number of VAR arguments. If I wished to define specific argument types without any default type, I would use:

tabcomplete test file 'DIR +data -data nodata' ''

Using TabComplete for user-defined functions

Using TabComplete for user-defined functions

TabComplete can also be used to retrieve the current list of tab-completion definitions:

>> definitions = tabcomplete;
>> definitions(1)
ans = 
    functionName: 'addpath'
     defaultType: 'DIR'
     extraValues: ''
        platform: ''
    functionArgs: []
 
>> definitions(54)
ans = 
    functionName: 'openfig'
     defaultType: ''
     extraValues: ''
        platform: ''
    functionArgs: [1x2 struct]
>> definitions(54).functionArgs(1)
ans = 
    previousArg: ''
        argType: 'FIGFILE'
    extraValues: ''
>> definitions(54).functionArgs(2)
ans = 
    previousArg: ''
        argType: 'VAR'
    extraValues: 'new visible invisible reuse'

TabComplete has a few limitations: it does not support the -previous option described above (you can do this by manually editing TC.xml). There are also some inherent limitations in Matlab’s TC functionality: changes take effect only after a Matlab restart (there might be a way to reload the definitions in the current Matlab session, but I do not know of any); the list of standard types cannot be modified; and the default type does not support extra special-purpose strings as do the numbered arguments.

There is another very annoying limitation: by default, TC.xml only supports lowercase function names. This is stupid, since Matlab has many function names with UPPERCASE characters, and certainly user-defined function names also do. Luckily, this last limitation can easily be overcome by editing the TC.xsd file (note that this is the TC.XSD file, not the TC.XML file). Instead of:

<xsd:simpleType name="tcBindingNameType">
  <xsd:restriction base="xsd:token">
    <xsd:pattern value='[A-Za-z_0-9]+(/[a-z_0-9]+)?'/>
  </xsd:restriction>
</xsd:simpleType>

Change the xsd:pattern definition element to:

    <!-- Yair 21/2/2010: added A-Z -->
    <xsd:pattern value='[A-Za-z_0-9]+(/[A-Za-z_0-9]+)?'/>

(note the way comments can be added to the XSD/XML files)

P.S. an entirely different customization, for user-defined class members, was presented by Michal Kutil.

Bookmark and Share

Continuous slider callback

Monday, February 8th, 2010

Every few months, a CSSM forum reader asks how to set up a continuously-invoked slider callback: Matlab’s slider uicontrol invokes the user callback only when the mouse button is released, and not continuously while the slider’s thumb is dragged. This functionality was again referred-to yesterday, and I decided it merits a dedicated post.

There are three distinct simple ways to achieve continuous callbacks:

Using Java callbacks

As explained in an earlier article, Matlab uicontrols are basically Java Swing objects that possess a large number of useful callbacks. Matlab sliders’ underlying Java objects, which are really not JSliders but JScrollBars, have an AdjustmentValueChangedCallback property that is useful for our purposes and is accessible using the FindJObj utility. Simply download FindJObj from the File Exchange, and then:

hSlider = uicontrol('style','slider', ...);
jScrollBar = findjobj(hSlider);
jScrollBar.AdjustmentValueChangedCallback = @myCbFcn;
% or: set(jScrollBar,'AdjustmentValueChangedCallback',@myCbFcn)

Where myCbFcn is the Matlab callback function that will be invoked continuously when the arrow buttons are depressed or the slider’s thumb is dragged.

Using an event listener

An alternative to the Java route is to use Matlab’s undocumented handle.listener function to listen to the slider’s Action event, as follows:

hListener = handle.listener(hSlider,'ActionEvent',@myCbFcn);

This alternative is used by Matlab’s own imscrollpanel function:

if isJavaFigure
   % Must use these ActionEvents to get continuous events fired as slider
   % thumb is dragged. Regular callbacks on sliders give only one event
   % when the thumb is released.
   hSliderHorListener = handle.listener(hSliderHor,...
      'ActionEvent',@scrollHorizontal);
   hSliderVerListener = handle.listener(hSliderVer,...
      'ActionEvent',@scrollVertical);
   setappdata(hScrollpanel,'sliderListeners',...
      [hSliderHorListener hSliderVerListener]);
else
   % Unfortunately, the event route is only available with Java Figures,
   % so platforms without Java Figure support get discrete events only
   % when the mouse is released from dragging the slider thumb.
   set(hSliderHor,'callback',@scrollHorizontal)
   set(hSliderVer,'callback',@scrollVertical)
end

Using a property listener

The handle.listener function can also be used to listen to property value changes. In our case, set a post-set listener, that gets triggered immediately following Value property updates, as follows:

hhSlider = handle(hSlider);
hProp = findprop(hhSlider,'Value');  % a schema.prop object
hListener = handle.listener(hhSlider,hProp,'PropertyPostSet',@myCbFcn);

In addition to ‘PropertyPostSet’, we could also listen on ‘PropertyPreSet’, which is triggered immediately before the property is modified. There are also corresponding ‘*Get’ options. In relatively old Matlab releases (I believe R2007b and earlier, but I’m not certain), the option names were simply ‘PostSet’, ‘PreSet’ etc., without the ‘Property’ prefix.

Do you know of any other way to achieve continuous callbacks? If so, I would be delighted to hear in the comments section below.

Bookmark and Share

setPrompt - Setting the Matlab Desktop prompt

Monday, January 25th, 2010

A few days ago, a reader emailed me with a challenge to modify the standard matlab Command-Window prompt from “>> ” to some other string, preferably a dynamic prompt with the current timestamp. At first thought this cannot be done: The Command-Window prompts are hard-coded and to the best of my knowledge cannot be modified via properties or system preferences.

So the prompt can (probably) not be modified in advance, but what if it could be modified after being displayed? It is true that my cprintf utility modifies the Command-Window contents in order to display formatted text in a variety of font colors. But this case is different since cprintf runs once synchronously (user-invoked), whereas the prompt appears asynchronously multiple times.

There are two methods of handling multiple asynchronous events in Matlab: setting a callback on the object, and setting a PostSet handle.listener (or schema.listener) on the relevant object property. The first of these methods is a well-known Matlab practice, although we shall see that it uses an undocumented callback and functionality; the PostSet method is entirely undocumented and not well-known and shall be described in some later article. I decided to use the callback method to set the prompt - interested readers can try the PostSet method.

Setting the Command Window’s callback

The solution involved finding the Command-Window reference handle, and setting one of its many callbacks, in our case CaretUpdateCallback. This callback is fired whenever the desktop text is modified, which is an event we trap to replace the displayed prompt:

% Get the reference handle to the Command Window text area
jDesktop = com.mathworks.mde.desk.MLDesktop.getInstance;
try
  cmdWin = jDesktop.getClient('Command Window');
  jTextArea = cmdWin.getComponent(0).getViewport.getComponent(0);
catch
  commandwindow;
  jTextArea = jDesktop.getMainFrame.getFocusOwner;
end
 
% Instrument the text area's callback
if nargin && ~isempty(newPrompt) && ~strcmp(newPrompt,'>> ')
  set(jTextArea,'CaretUpdateCallback',{@setPromptFcn,newPrompt});
else
  set(jTextArea,'CaretUpdateCallback',[]);
end

Now that we have the Command-Window object callback set, we need to set the logic of prompt replacement - this is done in the internal Matlab function setPromptFcn. Here is its core code:

% Does the displayed text end with the default prompt?
% Note: catch a possible trailing newline
try jTextArea = jTextArea.java;  catch,  end  %#ok
cwText = get(jTextArea,'Text');
pos = strfind(cwText(max(1,end-3):end),'>> ');
if ~isempty(pos)
  % Short prompts need to be space-padded
  newLen = jTextArea.getCaretPosition;
  if length(newPrompt)<3
    newPrompt(end+1:3) = ' ';
  elseif length(newPrompt)>3
    fprintfStr = newPrompt(1:end-3);
    fprintf(fprintfStr);
    newLen = newLen + length(fprintfStr);
  end
 
  % The Command-Window text should be modified on the EDT
  awtinvoke(jTextArea,'replaceRange(Ljava.lang.String;II)',...
            newPrompt(end-2:end), newLen-3, newLen);
  awtinvoke(jTextArea,'repaint()');
end

In this code snippet, note that we space-pad prompt string that are shorter than 3 characters: this is done to prevent an internal-Matlab mixup when displaying additional text - Matlab “knows” the Command-Window’s text position and it gets mixed up if it turns out to be shorter than expected.

Also note that I use the semi-documented awtinvoke function to replace the default prompt (and an automatically-appended space) on the Event-Dispatch Thread (more on this in a future article). Since Matlab R2008a, I could use the more convenient javaMethodEDT function, but I wanted my code to work on all prior Matlab 7 versions, where javaMethodEDT was not yet available.

Preventing callback re-entry

The callback snippet above would enter an endless loop if not changed: whenever the prompt is modified the callback would have been re-fired, the prompt re-modified and so on endlessly. There are many methods of preventing callback re-entry - here’s the one I chose:

function setPromptFcn(jTextArea,eventData,newPrompt)
 
  % Prevent overlapping reentry due to prompt replacement
  persistent inProgress
  if isempty(inProgress)
    inProgress = 1;  %#ok unused
  else
    return;
  end
 
  try
    % *** Prompt modification code goes here ***
 
    % force prompt-change callback to fizzle-out...
    pause(0.02);
  catch
    % Never mind - ignore errors...
  end
 
  % Enable new callbacks now that the prompt has been modified
  inProgress = [];
 
end  % setPromptFcn

Handling multiple prompt types

I now wanted my function to handle both static prompt strings (like: ‘[Yair] ‘) and dynamic prompts (like: ‘[25-Jan-2010 01:00:51] ‘). This is done by accepting string-evaluable strings/functions:

% Try to evaluate the new prompt as a function
try
  origNewPrompt = newPrompt;
  newPrompt = feval(newPrompt);
catch
  try
    newPrompt = eval(newPrompt);
  catch
    % Never mind - probably a string...
  end
end
if ~ischar(newPrompt) && ischar(origNewPrompt)
  newPrompt = origNewPrompt;
end

File Exchange submission

I then added some edge-case error handling and wrapped everything in a single utility called setPrompt that is now available on the File Exchange.

In the future, if I find time, energy and interest, maybe I’ll combine cprintf’s font-styling capabilities, to enable setting colored prompts.

Setting a continuously-updated timestamp prompt

Using the code above, we can now display a dynamic timestamp prompt, as follows:

setPrompt usage examples

setPrompt usage examples

However, the displayed timestamp is somewhat problematic in the sense that it indicates the time of prompt creation rather than the time that the associated Command-Window command was executed. In the screenshot above, [25-Jan-2010 01:29:42] is the time that the 234 command was executed, not the time that the setPrompt command was executed. This is somewhat misleading. It would be better if the last (current) timestamp was continuously updated and would therefore always display the latest command’s execution time. This can be done using a Matlab timer as follows:

% This is entered in the main function before setting the prompt:
stopPromptTimers;
if nargin && strcmpi(newPrompt,'timestamp')
  % Update initial prompt & prepare a timer to continuously update it
  newPrompt = @()(['[',datestr(now),'] ']);
  start(timer('Tag','setPromptTimer', 'Name','setPromptTimer', ...
              'ExecutionMode','fixedDelay', 'ObjectVisibility','off', ...
              'Period',0.99, 'StartDelay',0.5, ...
              'TimerFcn',{@setPromptTimerFcn,jTextArea}));
end
 
% Stop & delete any existing prompt timer(s)
function stopPromptTimers
  try
    timers = timerfindall('tag','setPromptTimer');
    if ~isempty(timers)
      stop(timers);
      delete(timers);
    end
  catch
    % Never mind...
  end
end  % stopPromptTimers
 
% Internal timer callback function
function setPromptTimerFcn(timerObj,eventData,jTextArea)
  try
    try jTextArea = jTextArea.java;  catch,  end  %#ok
    pos = getappdata(jTextArea,'setPromptPos');
    newPrompt = datestr(now);
    awtinvoke(jTextArea,'replaceRange(Ljava.lang.String;II)',...
              newPrompt, pos, pos+length(newPrompt));
    awtinvoke(jTextArea,'repaint()');
  catch
    % Never mind...
  end
end  % setPromptTimerFcn

Can you come up with some innovative prompts? If so, please share them in a comment below.

Update 2010-Jan-26: The code in this article was updated since it was first published yesterday.

Bookmark and Share

FindJObj GUI - display container hierarchy

Tuesday, January 12th, 2010

In my previous post, I explained how the findjobj utility can be used to access a Matlab component’s underlying Java component. Findjobj has another role: displaying the component hierarchy of complex Matlab containers such as the figure window, GUIDE or the Editor.

When findjobj is called with no output arguments, the function infers that the user requests to see the GUI version, rather than to get the control’s Java handle:

>> findjobj(gcf);  % or: findjobj(gcf)

FindJObj GUI (click to zoom)

FindJObj GUI (click to zoom)

There are several note-worthy aspects in this graphical hierarchy presentation:

The hierarchy tree itself is displayed using the internal com.mathworks.hg.peer.UITreePeer Java object. This is the object that underlies the semi-documented uitree function. The hierarchy sub-components are presented as tree nodes, each having a separate icon based on the component type. In some cases (toolbar buttons for example), the component’s icon image is used for its corresponding tree node. A javax.swing.JProgressBar is presented while the tree is being populated, an action that can take a few seconds depending on the target figure’s complexity. Some tree branches which are normally uninteresting are automatically collapsed: hidden containers (these are also grayed-out), menubars, toolbars and scrollbars. In parallel to the Java container hierarchy, a separate tree branch is presented with the corresponding Matlab (Handle-Graphics, or HG) hierarchy.

Another GUI example - note the hidden (gray) items, the HG tree branch and the auto-collapsed MJToolBar container

Another GUI example - note the hidden (gray) items, the HG tree branch and the auto-collapsed MJToolBar container

Each node item gets a unique tooltip (see top screenshot above). Similarly, a unique context-menu (right-click menu) is attached to each node item with actions that are relevant for that node:

Item-specific context-menu

Item-specific context-menu

Finally, a node-selection callback is attached to the tree, that will flash a red border around the GUI control when its corresponding Java node-item is clicked/selected:

FindJObj - flashing red border around a toolbar icon

FindJObj - flashing red border around a toolbar icon

Once the tree was done, I set out to display and enable modifications of component properties and callbacks in separate adjacent panels. I used the internal com.mathworks.mlwidgets.inspector.PropertyView component to display the properties (this is the JIDE component that underlies the built-in inspect function). To prevent a JIDE run-time alert, I called com.mathworks.mwswing.MJUtilities.initJIDE. A label is added to the table’s header, displaying the currently selected sub-component’s class (e.g., “javax.swing.JButton”), and a tooltip with a color-coded list of all the control’s properties.

The callbacks table was implemented using com.jidesoft.grid.TreeTable to enable easy column resizing, but this is otherwise used as a simple data table. A checkbox was added to filter out the 30-odd standard Swing callbacks, which are non-unique to the selected sub-component (tree node). All the panels - tree, properties and callbacks - are then placed in resizable javax.swing.JSplitPanes and presented to the user.

I have omitted mention of some other undocumented features in findjobj. After all, space here is limited and the function is over 2500 lines long. I encourage you to download the utility and explore the code, and I gladly welcome your feedback.

Bookmark and Share

FindJObj - find a Matlab component’s underlying Java object

Wednesday, January 6th, 2010

In a previous post, I explained that all Matlab GUI (except the axes plotting engine) is based on Java components, and showed how we can use this information to display HTML contents in Matlab uicontrols. In other posts, I have shown how a utility called findjobj can be used to access the underlying Java components to enable customizations that are unavailable in standard Matlab: setting the line location in an edit-box, customizing button appearance, setting uicontrol callbacks, or setting list-box mouse actions. I have also shown how findjobj can be used to display the component hierarchy of complex Matlab containers such as the figure window, GUIDE or the Editor.

The time is therefore well overdue for a formal introduction of findjobj, explaining its uses and internal mechanism. Of course, readers are welcome to continue using findjobj as a black-box utility, but I think important insight can be gained from understanding its inner details. Findjobj’s code is available for free download on the MathWorks File Exchange. It is one of my favorite submissions and is apparently well-liked by users, being highly reviewed and highly downloaded.

Findjobj has two main purposes:

  1. Find the underlying Java object reference of a given Matlab handle - Historically this was the original purpose, hence the utility’s name. Findjobj was meant to extend Matlab’s standard findobj function, which does not expose Java components.
  2. Display a container’s internal components hierarchy in a graphical user interface, to facilitate visualization of complex containers. This was later extended to also display and allow modification of the sub-components’ properties and callbacks.

Today I will focus on the first (programmatic) aspect; next week I will describe the second (GUI) aspect.

Findjobj’s heart is finding a control’s underlying Java handle. Unfortunately, this is not exposed by Matlab except in very rare cases. As hard as I tried, I could not find a way to directly access the underlying Java-peer handle. I therefore resorted to getting the control’s enclosing Java frame (window) reference, and then working down its sub-components hierarchy until finding the Java object(s) which satisfy the position and/or class criteria. To get the enclosing Java frame (aka TopLevelAncestor), I use the Matlab figure’s undocumented JavaFrame property. Using this property issues a standard warning (since Matlab release R2008a) of becoming obsolete in some future Matlab release. Since it worked so far, I have turned off this warning in findjobj’s code, but note that this code may well fail in some future Matlab version. If and when JavaFrame does become obsolete, be sure to look in this blog for workarounds…

Traversing the frame’s hierarchy presents several challenges: Main-menu items are accessed using different functions than other Swing components or sub-containers, and are not automatically accessible until first displayed. I have overcome this latter challenge by simulating a menu-open action in case menus should be searched (this is off by default since it takes several seconds and also changes the GUI focus). For “regular” sub-containers, sometimes we need to loop over getComponent(…) and in some other cases over getChildAt(…).

Another challenge was presented by the fact that Java positions start at (0,0) in the top left corner increasing rightward and downward, rather than starting at (1,1) in the bottom left and increasing upward as in Matlab. Moreover, Java positions are always pixel-based and relative to their parent container, which is different from Matlab (if the Matlab units is ‘pixels’ then the value is absolute; if ‘normalized’ then it returns a non-pixel value). To further complicate matters, some Matlab controls have a different size than their Java counterparts: some controls have a 5-pixel margins while others not, some controls are shifted by a pixel or two from their container’s border (for a total offset of up to 7 pixels), while some controls (such as popup-menus) have an entirely different reported size. In theory, we could use the Matlab component’s undocumented PixelBounds property (much faster than getpixelposition), but unfortunately PixelBounds turns out to be unreliable and returns erroneous values in many cases. Finally, different Java containers/components have different ways of returning their position: for some it is a getLocation() method, for others it is getX()/getY() and for others it is the X and Y properties (that sometimes have no corresponding getX()/getY() accessor methods!).

Having finally overcome all these challenges (and quite a few smaller ones, documented within the source code), I have wrapped the algorithm in a function interface that tries to emulate findobj’s. Using findjobj can now be as easy as:

% Modify the mouse cursor when over the button
hButton = uicontrol('string','click me!');
jButton = findjobj(hButton);
jButton.setCursor(java.awt.Cursor(java.awt.Cursor.HAND_CURSOR))

Modified uicontrol cursor - a Java property

Modified uicontrol cursor - a Java property

…or as complex as:

% Find all non-button controls with the specified label
jControls = findjobj('property',{'text','click me!'}, 'not','class','button');

Space here is limited and findjobj is over 2500 lines long, so I have obviously not covered everything. I encourage you to download the utility and explore the code, and I gladly welcome your feedback.

Bookmark and Share

Customizing help popup contents

Monday, November 30th, 2009

A few days ago, I was asked by a reader how to programmatically display the popup help window and customize it with arbitrary contents. This help window displays the doc-page associated with the current Command Window or Editor text.

Help popup

Help popup

To programmatically display this help popup, a modeless MJDialog Java window, we need to run the following on Matlab releases that support these windows (R2007b onward):

jDesktop = com.mathworks.mde.desk.MLDesktop.getInstance;
jTextArea = jDesktop.getMainFrame.getFocusOwner;
jClassName = 'com.mathworks.mlwidgets.help.HelpPopup';
jPosition = java.awt.Rectangle(0,0,400,300);
helpTopic = 'surf';
javaMethodEDT('showHelp',jClassName,jTextArea,[],jPosition,helpTopic);

Where:
1) jPosition sets popup’s pixel size and position (X,Y,Width,Height). Remember that Java counts from the top down (contrary to Matlab) and is 0-based. Therefore, Rectangle(0,0,400,300) is a 400×300 window at the screen’s top-left corner.
2) helpTopic is the help topic of your choice (the output of the doc function). To display arbitrary text, you can create a simple .m file that only has a main help comment with your arbitrary text, which will be presented in the popup.
3) on R2007b release you need to use the equivalent but more cumbersome awtinvoke function instead of javaMethodEDT:

jniSig = 'showHelp(Ljavax.swing.JComponent;Lcom.mathworks.mwswing.binding.KeyStrokeList;Ljava.awt.Rectangle;Ljava.lang.String;)';
awtinvoke(jClassName,jniSig,jTextArea,[],jPosition,helpTopic);

For example, if we had a sample.m file with the following contents:

function sample
% The text in this function's main comment will be presented in the
% help popup. <a href="http://UndocumentedMatlab.com">Hyperlinks</a>
% are supported, but unfortunately not full-fledged HTML.

Then we would get this result:

User-created arbitrary text

User-created arbitrary text

Well, it does get the message across, but looks rather dull. It would be nice if this could be improved to provide full-scale HTML support. Unfortunately, Matlab documentation says this cannot be done:

The doc function is intended only for reference pages supplied by The MathWorks. The exception is the doc UserCreatedClassName syntax. doc does not display HTML files you create yourself. To display HTML files for functions you create, use the web function.

Luckily for us, there is an undocumented back-door to do this: The idea is to search all visible Java windows for the HelpPopup, then move down its component hierarchy to the internal web browser (a com.mathworks.mlwidgets.html.HTMLBrowserPanel object), then update the content with our arbitrary HTML or a webpage URL:

% Find the Help popup window
jWindows = com.mathworks.mwswing.MJDialog.getWindows;
jPopup = [];
for idx=1 : length(jWindows)
  if strcmp(get(jWindows(idx),'Name'),'HelpPopup')
    if jWindows(idx).isVisible
      jPopup = jWindows(idx);
      break;
    end
  end
end
 
% Update the popup with selected HTML
html=['Full HTML support: <b><font color=red>bold</font></b>, '...
      '<i>italic</i>, <a href="matlab:dir">hyperlink</a>, ' ...
      'symbols (&#8704;&#946;) etc.'];
if ~isempty(jPopup)
  browser = jPopup.getContentPane.getComponent(1).getComponent(0);
  browser.setHtmlText(html);
end

Help popup with HTML content

Help popup with HTML content

We can display HTML content and highlight certain keywords using the setHtmlTextAndHighlightKeywords method:

browser.setHtmlTextAndHighlightKeywords(html,{'support','symbols'});

HTML content with highlighting

HTML content with highlighting

Instead of specifying the HTML content, we can point this browser to a web-page URL (no need for the “http://” prefix):

browser.setCurrentLocation('UndocumentedMatlab.com');

Help popup browser displaying a URL web-page

Help popup browser displaying a URL web-page

The HTMLBrowserPanel includes a full-fledged browser (which may be different across Matlab releases/platforms). This browser supports HTML, CSS, JavaScript and other web-rendering aspect that we would expect from a modern browser. Being a full-fledged browser, we have some control over its appearance e.g., addAddressBox(1,1) and other internal methods. Interested readers may use my UiInspect utility to explore these options.

As a technical note, HTMLBrowserPanel is actually only a JPanel that contains the actual Mozilla browser. Luckily for us, MathWorks extended this panel class with the useful methods presented above, that forward the user requests to the actual internal browser. This way, we don’t need to get the actual browser reference (although you can, of course).

I have created a utility function that encapsulates all the above, and enables display of Matlab doc pages, as well as arbitrary text, HTML or webpages. This popupPanel utility can now be downloaded from the Matlab File Exchange.

An interesting exercise left for the readers, is adapting the main heavy-weight documentation window to display user-created HTML help pages. This can be achieved by means very similar to those shown in this article.

Of course, as the official documentation states, we could always use the fully-supported web function to display our HTML or URLs. Under the hood, web uses exactly the same HTMLBrowserPanel as out HelpPopup. The benefit of using the methods shown here is the use of a lightweight undecorated popup window which looks well-integrated with the existing Matlab help.

Please note that the HelpPopup implementation might change without warning between Matlab releases. An entirely separate, although related, implementation is Matlab’s built-in context-sensitive help system, which I described some months ago. That implementation did not rely on Java and worked on much earlier Matlab releases.

Bookmark and Share

Customizing print setup

Wednesday, November 25th, 2009

Last week, while consulting a client, I watched him use a Matlab application that processed data and presented the results in a figure window. I saw that he constantly needed to go to File/Print-Preview menu option to customize the figure’s print setup before being able to print. He would constantly click the “Fill Page” button, then go to the Color tab and set RGB rather than Black-and-White, and finally go to the Advanced tab to prevent printing UI controls. Only then would he actually print the figure using the menu (File/Print) or the toolbar Print button.

Print Preview window

Print Preview window

This is absurd, I thought to myself - there must be a better way. Unfortunately, Matlab only supports two documented ways to modify the print setup:

  • the print function enables setting print settings when actually printing the figure. It accepts a long list of optional parameters that specify a wide range of printing options. But if I want to allow users to print at their own time, using the default figure menu/toolbar, I need to separate the actions of (1) setting the print setup and (2) doing the actual printout action. Since print does both of these together, we can’t use it. Moreover, if we don’t call print, the setup remains at the default settings and the menu/toolbar printouts use this default setup.
  • the printopt.m file stores the user’s default print setup. This file can be modified (use the printopt function). However, it affects all Matlab printouts, and cannot be configured on a figure-by-figure basis.

Of course, lack of a documented method never stopped us before. Sure enough, after a short search I discovered the hidden potential of the undocumented/unsupported setprinttemplate function. This function, called internally by Matlab’s print-related functions, is responsible for setting the figure’s initial print setup. Once I had this key, unlocking the problem was easy. Here is the bottom line:

The figure’s default print setup is stored in the figure’s hidden ApplicationData property, accessible via the getappdata and setappdata functions or directly via get/set, as explained in a previous post. More importantly, the figure-specific setup is stored in another hidden property, PrintTemplate. Both of these setup data are stored in structure format, which is not available when a figure is first created, but only after printing or print-preview. Note that printtemplate.m contains detailed explanations about the meaning of some fields - to see them, simply display the file:

>> type printtemplate.m  % or: edit printtemplate.m

Unless we set PrintTemplate ourselves (or call print or printpreview to do this), printing the figure will use the default print setup. To set a non-standard setup, we just need to create the PrintTemplate structure with our non-default setup options. Matlab will then automatically use them when printing the figure. Here is a checklist for doing so:

  1. First, create a figure and get its default print setup data. Since this data is unavailable in newly-created figures, simply open the figure’s Print-Preview window without changing anything - this will create the missing print setup structure that we can retrieve:

    % Create a simple figure
    >> hFig = figure;  surf(peaks);
     
    % Open the print-preview window to create setup data
    >> printpreview(hFig);
     
    % Retrieve the original (default) print setup data
    >> oldSetup = get(hFig,'PrintTemplate')
    oldSetup = 
           VersionNumber: 2
                    Name: ''
               FrameName: ''
             DriverColor: 1
         AxesFreezeTicks: 0
               tickState: {}
        AxesFreezeLimits: 0
                limState: {}
                   Loose: 0
                    CMYK: 0
                  Append: 0
               Adobecset: 0
                 PrintUI: 1
                Renderer: 'auto'
          ResolutionMode: 'auto'
                     DPI: 0
                FileName: 'untitled'
             Destination: 'printer'
             PrintDriver: ''
               DebugMode: 0
              StyleSheet: 'default'
                FontName: ''
                FontSize: 0
            FontSizeType: 'screen'
               FontAngle: ''
              FontWeight: ''
               FontColor: ''
               LineWidth: 0
           LineWidthType: 'screen'
            LineMinWidth: 0
               LineStyle: ''
               LineColor: ''
            PrintActiveX: 0
               GrayScale: 0
                 BkColor: 'white'
                 FigSize: [14.8054083333333 11.10405625]
  2. Next, go to the File/Print-Preview menu option and modify the setup according to your specific needs, and retrieve the new (modified setup):
    >> newSetup = get(hFig,'PrintTemplate');
  3. Now compare the two structures and retrieve only the modified setup options. This can be done in several ways - I personally use the objdiff utility. In our case, we modified the DriverColor (B&W => color), FigSize (for “Fill page”), and PrintUI (for hiding UI controls) fields:
    >> objdiff(oldSetup,newSetup)
    ans = 
        DriverColor: {[0]  [1]}
            FigSize: {[14.8054083333333 11.10405625]  [2x1 double]}
            PrintUI: {[1]  [0]}
         StyleSheet: {'default'  'modified'}
           limState: {{}  ''}
          tickState: {{}  ''}
  4. Finally, use the undocumented printtemplate and setprinttemplate functions to prepare the default setup sub-structure, and override with the modified options that you have just discovered. Place this in the figure’s _OpeningFcn function (for GUIDE-generated figures) or in your figure’s initialization function (for non-GUIDE figures). For example, if we have a GUIDE-generated figure called “MyFig”, then place this code in the MyFig_OpeningFcn function in MyFig.m:
    function MyFig_OpeningFcn(hObject, eventdata, handles, varargin)
      ...
      % This was adapted from initprintexporttemplate.m
      pt = printtemplate;
      pt.StyleSheet = 'modified';
      pt.VersionNumber = 2;   % important (Note #1 below)
      pt.FigSize = [38.1, 21.0];
      pt.DriverColor = 1;
      pt.PrintUI = 0;
      % we must set the paper size *before* setprinttemplate
      set(hObject, 'PaperPositionMode','manual', ...
                   'PaperPosition',[0 0.5 29.5 20], ...
                   'PaperSize',[29.68 20.98], ...
                   'PaperType','A4');
      setprinttemplate(hObject, pt);
     
      % Choose default command line output for MyFig
      handles.output = hObject;
     
      % Update handles structure
      guidata(hObject, handles);

That’s all there is to it. So easy once we know how, isn’t it? The most annoying pain-in-the-so-and-so sometimes have simple solutions…

Note #1: it is very important to set pt.VersionNumber to 2, otherwise some modifications will not take effect.

Note #2: the internal implementation of printtemplate as well as the internal setup fields have changed between Matlab releases. These were often minor backward-compatible changes, but at least once this was a major change (VersionNumber 1=>2, I think around Matlab 7.2, but I’m not sure). Therefore, carefully test your code on the oldest release which is supposed to run it. Also, if you plan the code to run in future Matlab releases, you should note that the entire setup functionality might break without prior notice, since it is an internal unsupported implementation.

Bookmark and Share

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.

Bookmark and Share

uiundo - Matlab’s undocumented undo/redo manager

Thursday, October 29th, 2009

Whenever we have a Matlab GUI containing user-modifiable controls (edit boxes, sliders, toggle buttons etc.), we may wish to include an undo/redo feature. This would normally be a painful programming task. Luckily, there is an undocumented built-in Matlab support for this functionality via the uiundo function. Note that uiundo and its functionality is not Java-based but rather uses Matlab’s classes and the similarly-undocumented schema-based object-oriented approach.

A couple of months ago, I explained how to customize the figure toolbar. In that article, I used the undocumented uiundo function as a target for the toolbar customization and promised to explain its functionality later. I would now like to explain uiundo and its usage.

The uiundo function is basically an accessor for Matlab’s built-in undo/redo manager object. It is located in the uitools folder (%MATLABROOT%\toolbox\matlab\uitools) and its @uiundo sub-folder. To use uiundo, simply define within each uicontrol’s callback function (where we normally place our application GUI logic) the name of the undo/redo action, what should be done to undo the action, and what should be done if the user wished to redo the action after undoing it. uiundo then takes care of adding this data to the figure’s undo/redo options under Edit in the main figure menu.

For example, let’s build a simple GUI consisting of a slider that controls the value of an edit box:

hEditbox = uicontrol('style','edit', 'position',[20,60,40,40]); 
set(hEditbox, 'Enable','off', 'string','0');
hSlider = uicontrol('style','slider','userdata',hEditbox);
callbackStr = 'set(get(gcbo,''userdata''),''string'',num2str(get(gcbo,''value'')))';
set(hSlider,'Callback',callbackStr);

Simple GUI with slider update of a numeric value

Simple GUI with slider update of a numeric value

Now, let’s attach undo/redo actions to the slider’s callback. First, place the following in test_uiundo.m:

% Main callback function for slider updates
function test_uiundo(varargin)
 
  % Update the edit box with the new value
  hEditbox = get(gcbo,'userdata');
  newVal = get(gcbo,'value');
  set(hEditbox,'string',num2str(newVal));
 
  % Retrieve and update the stored previous value
  oldVal = getappdata(gcbo,'oldValue');
  if isempty(oldVal),  oldVal=0;  end
  setappdata(gcbo,'oldValue',newVal);
 
  % Prepare an undo/redo action
  cmd.Name = sprintf('slider update (%g to %g)',oldVal,newVal);
 
  % Note: the following is not enough since it only
  %       updates the slider and not the editbox...
  %cmd.Function        = @set;                  % Redo action
  %cmd.Varargin        = {gcbo,'value',newVal};
  %cmd.InverseFunction = @set;                  % Undo action
  %cmd.InverseVarargin = {gcbo,'value',oldVal};
 
  % This takes care of the update problem...
  cmd.Function        = @internal_update;       % Redo action
  cmd.Varargin        = {gcbo,newVal,hEditbox};
  cmd.InverseFunction = @internal_update;       % Undo action
  cmd.InverseVarargin = {gcbo,oldVal,hEditbox};
 
  % Register the undo/redo action with the figure
  uiundo(gcbf,'function',cmd);
end
 
% Internal update function to update slider & editbox
function internal_update(hSlider,newValue,hEditbox)
  set(hSlider,'value',newValue);
  set(hEditbox,'string',num2str(newValue));
end

And now let’s point the slider’s callback to our new function:

>> set(hSlider,'Callback',@test_uiundo);

Undo/redo functionality integrated in the figure

Undo/redo functionality integrated in the figure

We can also invoke the current Undo and Redo actions programmatically, by calling uiundo with the ‘execUndo’ and ‘execRedo’ arguments:

uiundo(hFig,'execUndo');
uiundo(hFig,'execRedo');

When invoking the current Undo and Redo actions programmatically, we can ensure that this action would be invoked only if it is a specific action that is intended:

uiundo(hFig,'execUndo','Save data');  % should equal cmd.Name

We can use this approach to attach programmatic undo/redo actions to new toolbar or GUI buttons. The code for this was given in the above-mentioned article. Here is the end-result:


Undo/redo functionality integrated in the figure toolbar

Undo/redo functionality integrated in the figure toolbar

Undo/redo functionality integrated in the figure toolbar


In my next post, due next week, I will explore advanced customizations of this functionality.

Bookmark and Share