Archive for the ‘Listeners’ Category

Inactive Control Tooltips & Event Chaining

Wednesday, February 24th, 2010

Once again, I welcome guest blogger Matt Whitaker, who continues his series of articles.

In my last post I explored some tips on tooltips. One of these tips involved displaying tooltips on disabled uicontrols. I explained that displaying tooltips on inactive controls is problematic since Matlab appears to intercept mouse events to these inactive controls, so even setting the tooltip on the underlying Java object will not work: The java object appears not to receive the mouse-hover event and therefore does not “know” that it’s time to display the tooltip.

When Yair and I deliberated this issue, he pointed me to his comment on a previous article showing an undocumented Java technique (Java also has some…) for forcing a tooltip to appear using the ActionMap of the uicontrol’s underlying Java object to get at a postTip action. We discussed using a WindowButtonMotionFcn callback to see if the mouse was above the inactive control, then triggering the forced tooltip display. Yair then went on to remind me and I quote: “you’ll need to chain existing WindowButtonMotionFcn callbacks and take into account ModeManagers that override them.”

Frankly, having written code previously that handles callback chaining, I would rather poke myself in the eye with a fork!

The Image Processing Toolbox has the nice pair of iptaddcallback and iptremovecallback functions that largely handle these issues. But for general Matlab, there seemed to be no alternative until I remembered that events trigger callbacks. I decided to use a listener for the WindowButtonMotion event to detect the mouse motion. Event listeners were briefly explained two weeks ago and deserve a dedicated future article. The advantage of using an event listener is that we don’t disturb any existing WindowButtonMotionFcn callback. We still need to be somewhat careful that our listeners don’t do conflicting things, but it’s a lot easier than trying to manage everything through the single WindowButtonMotionFcn.

A demonstration of this appears below with some comments following (note that this code uses the FindJObj utility):

function inactiveBtnToolTip
  %Illustrates how to make a tooltip appear on an inactive control
  h = figure('WindowButtonMotionFcn',@windowMotion,'Pos',[400,400,200,200]);
  col = get(h,'color');
  lbl = uicontrol('Style','text', 'Pos',[10,160,120,20], ...
                  'Background',col, 'HorizontalAlignment','left');
  btn = uicontrol('Parent',h, 'String','Button', ...
                  'Enable','inactive', 'Pos',[10,40,60,20]);
  uicontrol('Style','check', 'Parent',h, 'String','Enable button tooltip', ...
            'Callback',@chkTooltipEnable, 'Value',1, ...
            'Pos',[10,80,180,20], 'Background',col);
  drawnow;
 
  %create the tooltip and postTip action
  jBtn = findjobj(btn);
  import java.awt.event.ActionEvent;
  javaMethodEDT('setToolTipText',jBtn,'This button is inactive');
  actionMap = javaMethodEDT('getActionMap',jBtn);
  action = javaMethodEDT('get',actionMap,'postTip');
  actionEvent = ActionEvent(jBtn, ActionEvent.ACTION_PERFORMED, 'postTip');
 
  %get the extents plus 2 pixels of the control to compare to the mouse position
  btnPos = getpixelposition(btn)+[-2,-2,4,4]; %give a little band around the control
  left = btnPos(1);
  right = sum(btnPos([1,3]));
  btm = btnPos(2);
  top =  sum(btnPos([2,4]));
 
  % add a listener on mouse movement events
  tm = javax.swing.ToolTipManager.sharedInstance; %tooltip manager
  pointListener = handle.listener(h,'WindowButtonMotionEvent',@figMouseMove);
 
  %inControl is a flag to prevent multiple triggers of the postTip action
  %while mouse remains in the button
  inControl = false;
 
  function figMouseMove(src,evtData) %#ok
    %get the current point
    cPoint = evtData.CurrentPoint;
 
    if cPoint(1) >= left && cPoint(1) <= right &&...
       cPoint(2) >= btm  && cPoint(2) <= top
 
      if ~inControl %we just entered
        inControl = true;
        action.actionPerformed(actionEvent); %show the tooltip
      end %if
    else
      if inControl %we just existed
        inControl = false;
        %toggle to make it disappear when leaving button
        javaMethodEDT('setEnabled',tm,false);
        javaMethodEDT('setEnabled',tm,true);
      end %if
    end %if
  end %gpMouseMove
 
  function windowMotion(varargin)
    %illustrate that we can still do a regular window button motion callback
    set(lbl,'String',sprintf('Mouse position: %d, %d',get(h,'CurrentPoint')));
    drawnow;
  end %windowMotion
 
  function chkTooltipEnable(src,varargin)
    if get(src,'Value')
      set(pointListener,'Enable','on');
    else
      set(pointListener,'Enable','off');
    end %if
  end %chkTooltipEnable
end %inactiveBtnToolTip

Tooltip on an inactive button

Tooltip on an inactive button

Comments on the code:

  1. The code illustrates that we can successfully add an additional listener to listen for mouse motion events while still carrying out the original WindowButtonMotionFcn callback. This makes chaining callbacks much easier.
  2. The handle.listener object has an Enable property that we can use to temporarily turn the listener on and off. This can be seen in the chkTooltipEnable() callback for the check box in the code above. If we wanted to permanently remove the listener we would simply use delete(pointListener). Note that addlistener adds a hidden property to the object being listened to, so that the listener is tied to the object’s lifecycle. If you create a listener directly using handle.listener you are responsible for it’s disposition. Unfortunately, addlistener fails for HG handles on pre-R2009 Matlab releases, so we use handle.listener directly.
  3. The code illustrates a good practice when tracking rapidly firing events like mouse movement of handling reentry into the callback while it is still processing a previous callback. Here we use a flag called inControl to prevent the postTip action being continuously fired while the mouse remains in the control.
  4. I was unable to determine if there is any corresponding action for the postTip to dismiss tips so I resorted to using the ToolTipManager to toggle its own Enable property to cleanly hide the tooltip as the mouse leaves the control.

Each Matlab callback has an associated event with it. Some of the ones that might be immediately useful at the figure-level are WindowButtonDown, WindowButtonUp, WindowKeyPress, and WindowKeyRelease. They can all be accessed through handle.listener or addlistener as in the code above.

Unfortunately, events do not always have names that directly correspond to the callback names. In order to see the list of available events for a particular Matlab object, use the following code, which relies on another undocumented function – classhandle. Here we list the events for gcf:

>> get(get(classhandle(handle(gcf)),'Events'),'Name')
ans = 
    'SerializeEvent'
    'FigureUpdateEvent'
    'ResizeEvent'
    'WindowKeyReleaseEvent'
    'WindowKeyPressEvent'
    'WindowButtonUpEvent'
    'WindowButtonDownEvent'
    'WindowButtonMotionEvent'
    'WindowPostChangeEvent'
    'WindowPreChangeEvent'

Note that I have made extensive use of the javaMethodEDT function to execute Java methods that affect swing components on Swing’s Event Dispatch Thread. I plan to write about this and related functions in my next article.

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.

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.

EditorMacro v2 – setting Command Window key-bindings

Thursday, August 20th, 2009

Some weeks ago, I introduced my EditorMacro utility as a means of assigning user-defined text strings and runnable macros (callbacks) to keyboard shortcuts in the Matlab Editor. I later showed how EditorMacro can be used to set non-textual editor macros.

Since then, I was contacted by several users with questions, enhancement requests and improvement suggestions. Of these, the most notable was from Perttu Ranta-aho who suggested (and provided sample code for) using the editor’s built-in actions. The next logical step was to extend this to support the editor menus, and then extend again to support Command Window key-bindings. We bounced several increasingly-powerful versions of the utility between us, until arriving at the version I uploaded yesterday, which has the following significant improvements over the first version:

  • several fixes: bugs, edge cases, stability (EDT etc.) etc.
  • support for built-in (native) Editor actions
  • support for built-in (native) Command-Window actions

Built-in native actions

It turns out that the Editor and Command-Window both have some ~200 built-in (native) available actions, about half of them common. Actions are identified by name, which is a lowercase dash-separated description like ’selection-up’ (this format is familiar to Emacs users). Altogether, there are over 300 unique built-in actions which can be used, but only ~100 of them are assigned a default key-bindings. A few dozen actions have multiple assigned key-bindings. For example, the ’selection-up’ action is assigned to both ’shift pressed UP’ (=<shift>-<up>) and ’shift pressed KP_UP’ (=<shift>-<Keypad-up>):

>> [bindings, actions] = EditorMacro
actions = 
...[snip]
 'selection-page-down'     'shift pressed PAGE_DOWN'     'editor native action'
 'selection-page-up'       'shift pressed PAGE_UP'       'editor native action'
 'selection-previous-word'                   {2x1 cell}  'editor native action'
 'selection-up'                              {2x1 cell}  'editor native action'
 'set-read-only'                                     []  'editor native action'
 'set-writable'                                      []  'editor native action'
 'shift-insert-break'      'shift pressed ENTER'         'editor native action'
 'shift-line-left'         'ctrl pressed OPEN_BRACKET'   'editor native action'
 'shift-line-right'        'ctrl pressed CLOSE_BRACKET'  'editor native action'
 'shift-tab-pressed'       'shift pressed TAB'           'editor native action'
...[snip]...
 'toggle-typing-mode'      'pressed INSERT'              'editor native action'
 'uncomment'               'ctrl pressed T'              'editor native action'
 'undo'                    'ctrl pressed Z'              'editor native action'
 'unselect'                'pressed ESCAPE'              'editor native action'
 'adjust-window-bottom'                              []  'cmdwin native action'
 'adjust-window-top'                                 []  'cmdwin native action'
 'beep'                    'ctrl pressed G'              'cmdwin native action'
 'break-interrupt'         'ctrl pressed CANCEL'         'cmdwin native action'
...[snip]...
 'toggle-typing-mode'      'pressed INSERT'              'cmdwin native action'
 'unselect'                'ctrl pressed BACK_SLASH'     'cmdwin native action'
 'new-mfile'               'ctrl pressed N'              'editor menu action'  
 'Figure'                                            []  'editor menu action'  
...[snip]...

Even more interesting, apparently some 200 actions do not have any pre-assigned default key-bindings, such as ’set-read-only’ and ’set-writable’ in the snippet above. Let’s take the ‘match-brace’ action for example. This sounded promising so I assigned it an unused key-binding and indeed found that it can be very useful: if your cursor is placed on a beginning or end of some code, clicking the assigned key-binding will jump the cursor to the other end, and then back again. This works nicely for (..), [..], for..end, try..end, if..end, etc.

>> % Ensure that <Alt>-M is unassigned
>> bindings = EditorMacro('alt m')
bindings = 
   Empty cell array: 0-by-4
 
>> % Assign the key-binding and verify
>> EditorMacro('alt m','match-brace','run');
>> bindings = EditorMacro('alt m')
b = 
  'alt pressed M'  'match-brace'  'run'  'editor native action'

Implementation details

Interested readers of this post are encouraged to look within EditorMacro’s source code and see how the native actions and keybindings were retrieved and modified for the editor, Command-Window and menus. In a nutshell, the native action names and key-bindings are stored in a Java Map object. Here’s a code snippet:

%% Get all available actions even those without any key-binding
function actionNames = getNativeActions(hEditorPane)
  try
    actionNames = {};
    actionKeys = hEditorPane.getActionMap.allKeys;
    actionNames = cellfun(@char,cell(actionKeys),'Uniform',0);
    actionNames = sort(actionNames);
  catch
    % never mind...
  end
 
%% Get all active native shortcuts (key-bindings)
function accelerators = getAccelerators(hEditorPane)
  try
    accelerators = cell(0,2);
    inputMap = hEditorPane.getInputMap;
    inputKeys = inputMap.allKeys;
    accelerators = cell(numel(inputKeys),2);
    for ii = 1 : numel(inputKeys)
      thisKey = inputKeys(ii);
      thisAction = inputMap.get(thisKey);
      accelerators(ii,:) = {char(thisKey), char(thisAction)};
    end
    accelerators = sortrows(accelerators,1);
  catch
    % never mind...
  end

The menu retrieval was more difficult: while it is possible to directly access the menubar reference (jMainPane.getRootPane.getMenuBar), the menu items themselves are not visible until their main menu item is clicked (displayed). The only way I know to access menu actions/keybindings is to read them from the individual menu items (if anyone knows a better way please tell me – perhaps some central key-listener repository?). Therefore, a simulation of the menu-click events is done and the menu hierarchy is traveled recuresively to collect all its actions and key-bindings.

A final note relates to the use of EDT. EDT really deserves a separate post, but in a nutshell it means that any action that affects the GUI needs to be invoked asynchronously (via the EDT) rather than synchronously (on the main Matlab thread). This is no real problem in the editor, but it is indeed an issue in the Command Window: If we do not use EDT there, we get ugly red stack-trace exceptions thrown on the Command Window whenever we run our EditorMacro-assigned macro. Here’s the code snippet that solves this:

try
   % Matlab 7
   %jEditorPane.insert(caretPosition, macro);  % better to use replaceSelection() than insert()
   try
      % Try to dispatch on EDT
      awtinvoke(jEditorPane.java, 'replaceSelection', macro);
   catch
      % no good - try direct invocation
      jEditorPane.replaceSelection(macro);
   end
catch
   % Matlab 6
   %jEditorPane.insert(macro, caretPosition);  % note the reverse order of input args vs. Matlab 7...
   try
      % Try to dispatch on EDT
      awtinvoke(jEditorPane.java, 'replaceRange', macro, jEditorPane.getSelStart, jEditorPane.getSelEnd);
   catch
      % no good - try direct invocation
      jEditorPane.replaceRange(macro, jEditorPane.getSelStart, jEditorPane.getSelEnd);
   end
end

Known limitations

Some limitations remain in EditorMacro – here are the major ones:

  • Multi-key bindings are still not reported properly, nor can they be assigned. For example, the editor menu action ‘to-lower-case’ has a pre-assigned default key-binding of <Alt>-<U>-<L>, but this is reported as unassigned. Of course, you can always add another (single-key) assignment for this action, for example: <Alt>-<Ctrl>-<L>
  • Menus are dynamically recreated whenever the Editor is docked/undocked, or a separate type of file is edited (e.g., switching from an m-file to a c-file). Similarly, whenever the active desktop window changes from the Command Window to another desktop-docked window (e.g., Command History). In all these cases, the dynamically recreated menus override any conflicting key-binding previously done with EditorMacro.
  • Unfortunately, my Matlab 6-installed computer crashed, so while the first version of EditorMacro works on ML6, the new version might well not. If you have ML6 available, please email me so I can post a working version with your help. This also means I won’t be able to post much about undocumented ML6 stuff in the future. Perhaps this is Fate’s way of telling me to progress with the times…
  • Key bindings are sometimes lost when switching between a one-document editor and a two-document one (i.e., adding/closing the second doc)
  • Key bindings are not saved between editor sessions
  • In split-pane mode, when inserting a text macro on the secondary (right/bottom) editor pane, then both panes (and the actual document) are updated but the secondary pane does not display the inserted macro (the primary pane looks ok).

The first couple of limitations have a non-perfect workaround that Perttu came up with. He implemented this in his KeyBindings utility on the Matlab File Exchange. Perhaps one day Perttu or me will find the time to merge these utilities into one.

Have you designed some crafty user-defined macro? or found some important unassigned built-in action? Please share your experience using EditorMacro in the comments section below.

Context-Sensitive Help

Wednesday, August 5th, 2009

A recent CSSM thread about implementing a system-wide GUI help system got me working on a post to present Matlab’s built-in hidden/unsupported mechanism for context-sensitive help. There are many different ways in which such a system can be implemented (read that thread for some ideas), so Matlab users are by no means limited to Matlab’s built-in implementation. However, the built-in system certainly merits consideration for its simplicity and usefulness.

We start with Matlab’s cshelp function (%matlabroot%\toolbox\matlab\uitools\cshelp.m). cshelp is semi-documented, meaning that it has a help section but no doc, online help or official support. This useful function was grandfathered (made obsolete) in Matlab 7.4 (R2007a) for an unknown reason and to my knowledge without any replacement. cshelp is entirely based on m-code (no hidden internal or Java code) and is surprisingly compact and readable.

cshelp basically attaches two new properties (CSHelpMode and CSHelpData) to the specified figure (this is done using the schema.prop mechanism which will be described in a separate post), temporarily disables all active uicontrols, modifies the figure’s WindowButtonDownFcn callback and sets the mouse cursor (using setptr – another semi-documented function) to an arrow with a question mark.

Clicking any object in the figure’s main area (beneath the toolbar), causes the modified WindowButtonDownFcn callback to run whatever is stored in the figure’s HelpFcn property (string, @function_handle or {@function_handle, params, …} format). Here is a simple example taken from CSSM (thanks Jérôme):

Hfcn = 'str=get(gco,''type''); title([''Type :'' str])';
set(gcf,'HelpFcn',Hfcn);
th = 0:0.314:2*pi;
plot(th,sin(th),'r-','linewidth',4);
uicontrol('units','normalized', 'position',[.45 .02 .1 .05]);
cshelp(gcf);
set(gcf,'CSHelpMode','on');

Simple context-sensitive help system

Simple context-sensitive help system

In order to exit CSHelp mode, the figure’s CSHelpMode property must be set to ‘off’. However, remember that all the figure’s uicontrols are disabled in CSHelp mode. Therefore, the user may use one or more of the following methods (other tactics are also possible, but the ones below seem intuitive):

  • Set the figure’s KeyPressFcn callback property to catch events (e.g., <ESC> key presses) and reset the CSHelpMode property from within the callback
  • Reset the CSHelpMode property at the end of the HelpFcn callback
  • Add a CS Help entry/exit option to the figure’s Help main menu
  • Add a CS Help entry/exit button to the figure toolbar

The following code sample implements all of these suggested tactics (the code to synchronize the states of the menu item and toolbar button is not presented):

% Set the <ESC> key press to exit CSHelp mode
keyFcn = ['if strcmp(get(gcbf,''CurrentKey''),''escape''), ' ...
             'set(gcbf,''CSHelpMode'',''off''); ' ...
          'end'];
set(gcf,'keyPressFcn',keyFcn);
 
% Exit CSHelp mode at the end of the CSHelp callback
helpFcn = 'title([''Type :'' get(gco,''type'')]); set(gcbf,''CSHelpMode'',''off'');';
set(gcf,'HelpFcn',helpFcn);
 
% Add a CSHelp button to the figure toolbar
% Note: retrieve the button icon from the CSHelp cursor icon
hToolbar = findall(allchild(gcf),'flat','type','uitoolbar');
oldPtr = getptr(gcf);
ptrData = setptr('help');
set(gcf, oldPtr{:});
icon(:,:,1) = ptrData{4}/2;  % Convert into RGB TrueColor icon
icon(:,:,2) = ptrData{4}/2;
icon(:,:,3) = ptrData{4}/2;
cbFcn = 'set(gcbf,''CSHelpMode'',get(gcbo,''state''))';
csName = 'Context-sensitive help';
uitoggletool(hToolbar,'CData',icon, 'ClickedCallback',cbFcn, 'TooltipString',csName);
 
% Add a CSHelp menu option to the Help main menu
% Note: unlike other main menus, the Help menu tag is empty, so
% ^^^^  findall(gcf,'tag','figMenuHelp') is empty... Therefore,
%       we find this menu by accessing the Help/About menu item
helpAbout = findall(gcf,'tag','figMenuHelpAbout');
helpMenu = get(helpAbout,'parent');
cbFcn = ['if strcmp(get(gcbo,''Checked''),''on''), ' ...
             'set(gcbo,''Checked'',''off''); ' ...
         'else, ' ...
             'set(gcbo,''Checked'',''on''); ' ...
         'end; ' ...
         'set(gcbf,''CSHelpMode'',get(gcbo,''checked''))'];
uimenu(helpMenu,'Label',csName,'Callback',cbFcn,'Separator','on');

Figure with context-sensitive help action in the main toolbar & menu

Figure with context-sensitive help action in the main toolbar & menu

cshelp has an additional optional argument, accepting another figure handle. This handle, if specified and valid, indicates a parent figure whose CSHelpMode, HelpFcn and HelpTopicMap properties should be shared with this figure (this is done using the handle.listener mechanism which will be described in a separate post). This option is useful when creating a multi-window GUI-wide context-sensitive help system. The user may then activate context-sensitive help in figure A and select the requested context object in figure B.

cshelp would normally be coupled with Matlab’s help system for non-trivial GUI implementations. The undocumented and hidden properties HelpTopicKey (of all handles) and HelpTopicMap (of figures), enable easy tie-in to the CSHelp system. A simplified sample is presented below:

Hfcn=['helpview(get(gco,''HelpTopicKey''),''CSHelpWindow'');'...
      'set(gcbf,''CSHelpMode'',''off'');' ];
set(gcf,'HelpFcn',Hfcn);
th = 0:0.314:2*pi;
hLine = plot(th,sin(th),'r-','linewidth',4);
set(hLine,'HelpTopicKey','MyCSHelpFile.html#Line');
set(gca,  'HelpTopicKey','MyCSHelpFile.html#Axes');

cshelp is by no way limited to presenting Matlab documentation: Refer to helpview’s help section for an in-depth description of help maps and help topics. In a nutshell, helpview accepts any HTML webpage filepath (or webpage internal (#) reference), followed by optional parameter ‘CSHelpWindow’ (that indicates that the specified help page should be displayed in a stand-alone popup window rather than in the desktop’s standard Help tab), and optional extra parameters specifying the popup window’s figure handle and position. The webpage filepath parameter may be replaced by two string parameters, HelpTopicMap filepath and HelpTopicKey. Note that helpview itself is another semi-documented function.

Legend ‘-DynamicLegend’ semi-documented feature

Thursday, June 4th, 2009

In one of my projects, I had to build a GUI in which users could interactively add and remove plot lines from an axes. The problem was that the legend needed to be kept in constant sync with the currently-displayed plot lines. This can of course be done programmatically, but a much simpler solution was to use legend’s semi-documented ‘-DynamicLegend’ feature. Here’s a simple example:

x=0:.01:10;
plot(x, sin(x), 'DisplayName','sin');
legend('-DynamicLegend');
 
hold all;   % add new plot lines on top of previous ones
plot(x, cos(x), 'DisplayName','cos');

We can see how the dynamic legend automatically keeps in sync with its associated axes contents when plot lines are added/removed, even down to the zoom-box lines… The legend automatically uses the plot lines ‘DisplayName’ property where available, or a standard ‘line#’ nametag where not available:


Dynamic legend

Dynamic legend

DynamicLegend works by attaching a listener to the axes child addition/deletion callback (actually, it works on the scribe object, which is a large topic for several future posts). It is sometimes necessary to selectively disable the dynamic behavior. For example, in my GUI I needed to plot several event lines which looked alike, and so I only wanted the first line to be added to the legend. To temporarily disable the DynamicLegend listener, do the following:

% Try to disable this axes's legend plot-addition listener
legendAxListener = [];
try
   legendListeners = get(gca,'ScribeLegendListeners');
   legendAxListener = legendListeners.childadded;
   set(legendAxListener,'Enable','off');
catch
   % never mind...
end
 
% Update the axes - the legend will not be updated
...
 
% Re-enable the dynamic legend listener
set(legendAxListener,'Enable','on');

Unfortunately, this otherwise-useful DynamicLegend feature throws errors when zooming-in on bar or stairs graphs. This can be replicated by:

figure;
bar(magic(4));  %or: stairs(magic(3),magic(3));
legend('-DynamicLegend');
zoom on;
% Now zoom-in using the mouse to get the errors on the Command Window

The fix: modify %MATLABROOT%\toolbox\matlab\scribe\@scribe\@legend\init.m line #528 as follows:

%old:
str = [str(1:insertindex-1);{newstr};str(insertindex:length(str))];
 
%new:
if size(str,2)>size(str,1)
    str=[str(1:insertindex-1),{newstr},str(insertindex:length(str))];
else
    str=[str(1:insertindex-1);{newstr};str(insertindex:length(str))];
end

The origin of the bug is that bar and stairs generate hggroup plot-children, which saves the legend strings column-wise rather than the expected row-wise. My fix solves this, but I do not presume this solves all possible problems in all scenarios (please report if you find anything else).

Semi-documented

The DynamicLegend feature is semi-documented. This means that the feature is explained in a comment within the function (which can be seen via the edit(‘legend’) command), that is nonetheless not part of the official help or doc sections. It is an unsupported feature originally intended only for internal Matlab use (which of course doesn’t mean we can’t use it). This feature has existed many releases back (Matlab 7.1 for sure, perhaps earlier), so while it may be discontinued in some future Matlab release, it did have a very long life span… The down side is that it is not supported: I reported the bar/stairs issue back in mid-2007 and so far this has not been addressed (perhaps it will never be). Even my reported workaround in January this year went unanswered (no hard feelings…).

DynamicLegend is a good example of a useful semi-documented feature. Some other examples, which I may cover in future posts, include text(…,’sc’), drawnow(‘discard’), several options in pan and datacursormode etc. etc.

There are also entire semi-documented functions: many of the uitools (e.g., uitree, uiundo), as well as hgfeval and others.

Have you discovered any useful semi-documented feature or function? If so, then please share your finding in the comments section below.

UISplitPane

Saturday, March 28th, 2009

One of my many File Exchange submissions, UISplitPane, was chosen yesterday as Matlab Central’s Pick of the Week (thanks Jiro!). UISplitPane was probably my most challenging submission to date. I thought it would be a good point in time to share some of the undocumented features that I used in UISplitPane. Readers are encouraged to download UISplitPane.m and look at its code.

The basic problem which UISplitPane solves is the inability in Matlab to construct a dual pane separated by an interactive divider. Such split-panes are very common in modern GUIs, including Matlab tools, but were never available for Matlab GUIs. Java Swing has the JSplitPane control, but Matlab axes cannot (as yet) be added to Java containers. A similar problem exists for tabbed-panes, and Matlab provided a very innovative solution for this (the semi-documented uitabgroup function), which shall be described in a separate post. Unfortunately, uitabgroup’s solution cannot be applied to the split-pane problem since in JSplitPane’s case, the container overlaps the axes-containing panes.

To make a long story short, the solution was to create an off-screen (invisible) JSplitPane, extract only its narrow central divider sub-component, and place that onscreen using javacomponent. I encourage readers to look at the addDivider() function which does this, setting mouse cursor and other interesting properties.

The Java divider’s reference is then converted into a Matlab handle, so that some extra properties can be added using schema.prop (which [you guessed it] will be described in a later post) and will become visible when using regular get.

Pure-Matlab code then attaches standard Matlab uipanels as sub-panes on either side of the divider. Property linkages (this will be detailed in later posts about schema.prop’s getter/setter functions and handle.listener) ensure that whenever the divider is dragged or programmatically modified, the two sub-panes on its sides will be resized accordingly, together with all their content (axes and controls). Since the two split panes are simple uipanels, they can contain not only axes and controls but also other uisplitpanes, creating a hierarchy of split-panes:

Two levels of UISplitPane, with customized dividers

Two levels of UISplitPane, with customized dividers

UISplitPane makes use of some semi-documented internal helper functions: hgfeval is used in the mouse callbacks, in order to chain the original WindowButton callback (if available); setptr is used to set the mouse pointer (cursor).

UISplitPane behaves nicely in the presence of Mode Managers (zoom, pan, …) by using the figure’s undocumented ‘ModeManager’ property and setting its ‘ButtonDownFilter’ to bypass mode.

Finally, the figure’s undocumented ‘JavaFrame’ property is used to get a reference to the figure’s AxisComponent container, which is needed for setting mouse callbacks that behave better than similar callbacks at the figure level.

All-in-all, UISplitPane is perhaps a good example of combining a variety of unrelated undocumented Matlab features in order to achieve a coherent application.

An interesting side-note: Matlab 7.6 (R2008a) and onward contain a reference to uisplittool and uitogglesplittool in the javacomponent.m and %matlabroot%/bin/registry/hg.xml files. These are not valid functions (built-in or otherwise) and I could not figure out how this functionality can actually be used. At the very least it is certain that it is deeply undocumented and (of course) unsupported, leaving an open mystery for future investigation…