Continuous slider callback

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

Customizing listbox & editbox scrollbars

February 1st, 2010

A few days ago, a CSSM forum reader asked how to modify Matlab’s listbox scrollbars. Another user asked how to configure line-wrapping. I thought this is a good opportunity to describe how listbox and editbox scrollbars can be customized. The timing is particularly opportune, after I have recently described how the Matlab Editbox can be customized by accessing its underlying Java object using the FindJObj utility.

Both the listbox and the multi-line editbox uicontrols share a similar design: a multi-line Java control embedded within a JViewport within a JScrollPane (note that for historical reasons, the Java view-port class is called JViewport rather than the more standard camel-cased JViewPort). In addition to the view-port, the containing scroll-pane also contains two scrollbars (horizontal and vertical), as expected from standard Java scroll-panes.

JScrollPane components

JScrollPane components

Scrollbar policies

Control of the scroll-pane’s scrollbar behavior is done via the JScrollPane’s VerticalScrollBarPolicy and HorizontalScrollBarPolicy properties.

VerticalScrollBarPolicy accepts the self-explanatory values of:

  • VERTICAL_SCROLLBAR_ALWAYS (=22)
  • VERTICAL_SCROLLBAR_NEVER (=21)
  • and VERTICAL_SCROLLBAR_AS_NEEDED (=20)

HorizontalScrollBarPolicy accepts:

  • HORIZONTAL_SCROLLBAR_ALWAYS (=32)
  • HORIZONTAL_SCROLLBAR_NEVER (=31)
  • and HORIZONTAL_SCROLLBAR_AS_NEEDED (=30)

All these properties are static enumerated constants that can be referred using either their Java notation (e.g., JScrollPane.VERTICAL_SCROLLBAR_ALWAYS) or their equivalent numeric values. Using the non-numeric format is better, since it is more readable and the numeric values may change, but the choice is yours.

By default, Matlab implements a VerticalScrollBarPolicy of VERTICAL_SCROLLBAR_ALWAYS for sufficiently tall uicontrols (>20-25 pixels, which practically means always) and VERTICAL_SCROLLBAR_NEVER for shorter uicontrols.

For the horizontal scrollbar, Matlab implements a HorizontalScrollBarPolicy of HORIZONTAL_SCROLLBAR_NEVER for all editboxes and for narrow listboxes (<35 pixels), and HORIZONTAL_SCROLLBAR_AS_NEEDED for wide listboxes.

These settings are generally satisfactory. However, in some cases users may wish to modify the settings. For example, the default VerticalScrollBarPolicy setting of VERTICAL_SCROLLBAR_ALWAYS causes the vertical scrollbar to appear even when unneeded (the entire editbox content is visible). Also, we may wish to have a horizontal scrollbar on narrow listboxes and editboxes, something that the standard HORIZONTAL_SCROLLBAR_NEVER prevents. In both cases, a *_SCROLLBAR_AS_NEEDED policy might be more appropriate.

To modify these settings, we simply need to get the uicontrol’s underlying Java reference handle (using the FindJObj utility), and modify the appropriate property. For example:

% Create a multi-line (Max>1) editbox uicontrol
hEditbox = uicontrol('style','edit', 'max',5, ...);
 
% Get the Java scroll-pane container reference
jScrollPane = findjobj(hEditbox);
 
% Modify the scroll-pane's scrollbar policies
% (note the equivalent alternative methods used below)
set(jScrollPane,'VerticalScrollBarPolicy',20);  % or: jScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED
jScrollPane.setHorizontalScrollBarPolicy(30);  % or: jScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED

default scrollbars (VERTICAL_SCROLLBAR_ALWAYS)

default scrollbars (VERTICAL_SCROLLBAR_ALWAYS)

non-default scrollbars (VERTICAL_SCROLLBAR_AS_NEEDED)     non-default scrollbars (VERTICAL_SCROLLBAR_AS_NEEDED)

non-default scrollbars (VERTICAL_SCROLLBAR_AS_NEEDED)

Note that updating the uicontrol handle (hEditbox)’s Position property has the side-effect of automatically reverting the scrollbar policies to their default values (HORIZONTAL_SCROLLBAR_NEVER and VERTICAL_SCROLLBAR_ALWAYS/NEVER). This also happens whenever the uicontrol is resized interactively (by resizing its container figure window, for example). It is therefore advisable to set jScrollPane’s ComponentResizedCallback property to “unrevert” the policies:

cbStr = sprintf('set(gcbo,''VerticalScrollBarPolicy'',%d)', ...
                jScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
hjScrollPane = handle(jScrollPane,'CallbackProperties');
set(hjScrollPane,'ComponentResizedCallback',cbStr);

Line-wrapping

By default, line-wrapping is turned on, effectively disabling horizontal scrolling (which is why Matlab set the HorizontalScrollBarPolicy to HORIZONTAL_SCROLLBAR_NEVER. However, in some cases it may be more useful to turn line-wrapping off and horizontal scrolling on using the TextArea’s setWrapping() method. Here’s a usage example:

jViewPort = jScrollPane.getViewport;
jEditbox = jViewPort.getComponent(0);
jEditbox.setWrapping(false);  % do *NOT* use set(...)!!!
newPolicy = jScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED;
set(jScrollPane,'HorizontalScrollBarPolicy',newPolicy);

multi-line editbox with wrapping on

multi-line editbox with wrapping on

multi-line editbox with wrapping off

multi-line editbox with wrapping off

Notes:

  1. setWrapping() only works for the default EditorKit, and fails for HTMLEditorKit – This is due to HTML’s inherent wrapping behavior, as can easily be seen in any browser webpage.
  2. while setWrapping() may seem like a regular setter method for a Wrapping property, in reality it is not. Actually, set(jEditbox,’wrapping’,flag) may crash Matlab. So, always use the setWrapping(flag) method variant, which is entirely safe.
Bookmark and Share

setPrompt - Setting the Matlab Desktop prompt

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

Rich Matlab editbox contents

January 20th, 2010

In an earlier post, I mentioned that most Matlab uicontrols support HTML strings. Unfortunately, HTML is not supported in multi-line editbox contents. Today I will show how this limitation can be removed for a multi-line editbox, thereby enabling rich contents (enabling HTML for a single-line editbox needs a different solution).

We first need to get the editbox’s underlying Java object, as explained in my previous article about the findjobj utility. Since a multi-line editbox is contained within a scroll-pane, we need to dig within the scrollpane container to find the actual editable area object:

% Create a multi-line (Max>1) editbox uicontrol
hEditbox = uicontrol('style','edit', 'max',5, ...);
 
% Get the Java scroll-pane container reference
jScrollPane = findjobj(hEditbox);
 
% List the scroll-pane's contents:
>> jScrollPane.list
com.mathworks.hg.peer.utils.UIScrollPane[,0,0,100x50,...]
 javax.swing.JViewport[,1,1,81x48,...]
  com.mathworks.hg.peer.EditTextPeer$hgTextEditMultiline[,0,0,81x48,...,kit=javax.swing.text.StyledEditorKit@ce05fc,...]
 com.mathworks.hg.peer.utils.UIScrollPane$1[,82,1,17x48,...]
  com.sun.java.swing.plaf.windows.WindowsScrollBarUI$WindowsArrowButton[,0,31,17x17,...]
  com.sun.java.swing.plaf.windows.WindowsScrollBarUI$WindowsArrowButton[,0,0,17x17,...]
 com.mathworks.hg.peer.utils.UIScrollPane$2[,0,0,0x0,...]
  com.sun.java.swing.plaf.windows.WindowsScrollBarUI$WindowsArrowButton[,0,0,0x0,...]
  com.sun.java.swing.plaf.windows.WindowsScrollBarUI$WindowsArrowButton[,0,0,0x0,...]

In this listing, we see that jScrollPane contains a JViewport and two scrollbars (horizontal and vertical), as expected from standard Java scroll-panes. We need the internal hgTextEditMultiline object:

jViewPort = jScrollPane.getViewport;
jEditbox = jViewPort.getComponent(0);

The retrieved jEditbox reference, is an object of class com.mathworks.hg.peer.EditTextPeer$hgTextEditMultiline, which indirectly extends the standard javax.swing.JTextPane. The default Matlab implementation of the editbox uicontrol simply enables a multi-line vertical-scrollable text area using the system font. However, the underlying JTextPane object enables many important customizations, including the ability to specify different font attributes (size/color/bold/italic etc.) and paragraph attributes (alignment etc.) for text segments (called style runs) and the ability to embed images, HTML and other controls.

Setting rich contents can be done in several alternative ways. From easiest to hardest:

Setting page URL

Use the setPage(url) method to load a text page from the specified URL (any pre-existing editbox content will be erased). The page contents may be plain text, HTML or RTF. The content type will automatically be determined and the relevant StyledEditorKit and StyledDocument will be chosen for that content. Additional StyledEditorKit content parsers can be registered to handle additional content types. Here’s an example loading an HTML page:

jEditbox.setPage('http://tinyurl.com/c27zpt');

where the URL’s contents are:

<html><body>
<img src="images/dukeWaveRed.gif" width="64" height="64">
This is an uneditable <code>JEditorPane</code>, which was
<em>initialized</em> with <strong>HTML</strong> text 
<font size=-2>from</font> a <font size=+2">URL</font>.
<p>An editor pane uses specialized editor kits to read, write,
display, and edit text of different formats. The Swing text 
package includes editor kits for plain text, HTML, and RTF. 
You can also develop custom editor kits for other formats. 
<script language="JavaScript" 
  src="/js/omi/jsc/s_code_remote.js"></script>
</body></html>

Matlab editbox initialized from an HTML webpage URL

Matlab editbox initialized from an HTML webpage URL

Setting the EditorKit and ContentType

Set the requested StyledEditorKit (via setEditorKit()) or ContentType properties and then use setText() to set the text, which should be of the appropriate content type. Note that setting EditorKit or ContentType clears any existing text and left-aligns the contents (hgTextEditMultiline is center aligned by default). Also note that HTML <div>s get their own separate lines and that <html> and <body> opening and closing tags are accepted but unnecessary. For example:

jEditbox.setEditorKit(javax.swing.text.html.HTMLEditorKit);
% alternative: jEditbox.setContentType('text/html');
htmlStr = ['<b><div style="font-family:impact;color:green">'...
           'Matlab</div></b> GUI is <i>' ...
           '<font color="red">highly</font></i> customizable'];
jEditbox.setText(htmlStr)

HTML contents in a Matlab editbox

HTML contents in a Matlab editbox

Let’s show another usage example, of an event log file, spiced with icons and colored text based on event severity. First, define the logging utility function (the icon filenames may need to be changed based on your Matlab release):

function logMessage(jEditbox,text,severity)
   % Ensure we have an HTML-ready editbox
   HTMLclassname = 'javax.swing.text.html.HTMLEditorKit';
   if ~isa(jEditbox.getEditorKit,HTMLclassname)
      jEditbox.setContentType('text/html');
   end
 
   % Parse the severity and prepare the HTML message segment
   if nargin<3,  severity='info';  end
   switch lower(severity(1))
      case 'i',  icon = 'greenarrowicon.gif'; color='gray';
      case 'w',  icon = 'demoicon.gif';       color='black';
      otherwise, icon = 'warning.gif';        color='red';
   end
   icon = fullfile(matlabroot,'toolbox/matlab/icons',icon);
   iconTxt =['<img src="file:///',icon,'" height=16 width=16>'];
   msgTxt = ['&nbsp;<font color=',color,'>',text,'</font>'];
   newText = [iconTxt,msgTxt];
   endPosition = jEditbox.getDocument.getLength;
   if endPosition>0, newText=['<br/>' newText];  end
 
   % Place the HTML message segment at the bottom of the editbox
   currentHTML = char(jEditbox.getText);
   jEditbox.setText(strrep(currentHTML,'</body>',newText));
   endPosition = jEditbox.getDocument.getLength;
   jEditbox.setCaretPosition(endPosition); % end of content
end

Now, let’s use this logging utility function to log some messages:

logMessage(jEditbox, 'a regular info message...');
logMessage(jEditbox, 'a warning message...', 'warn');
logMessage(jEditbox, 'an error message!!!', 'error');
logMessage(jEditbox, 'a regular message again...', 'info');

Rich editbox contents (a log file)

Rich editbox contents (a log file)

HTML editboxes are normally editable, images included. In actual applications, we may wish to prevent editing the display log. To do this, simply call jEditbox.setEditable(false).

Setting a hyperlink handler is easy: first we need to ensure that we’re using an HTML content-type document. Next, set the editbox to be uneditable (hyperlinks display correctly when the editbox is editable, but are unclickable), using jEditbox.setEditable(false). Finally, set the callback function in the editbox’s HyperlinkUpdateCallback property:

jEditbox.setContentType('text/html');
jEditbox.setText('link: <a href= "http://UndocumentedMatlab.com">UndocumentedMatlab.com</a>');
jEditbox.setEditable(false);
hjEditbox = handle(jEditbox,'CallbackProperties');
set(hjEditbox,'HyperlinkUpdateCallback',@linkCallbackFcn);
 
function linkCallbackFcn(src,eventData)
   url = eventData.getURL;      % a java.net.URL object
   description = eventData.getDescription; % URL string
   jEditbox = eventData.getSource;
   switch char(eventData.getEventType)
      case char(eventData.getEventType.ENTERED)
               disp('link hover enter');
      case char(eventData.getEventType.EXITED)
               disp('link hover exit');
      case char(eventData.getEventType.ACTIVATED)
               jEditbox.setPage(url);
   end
end

Hyperlink in editbox

Hyperlink in editbox

Setting the style runs programmatically

Setting the styles programmatically, one style run after another, can be done via the text-pane’s Document property object. Individual character ranges can be set using the Document’s setCharacterAttributes method, or entire style runs can be inserted via insertString. Attributes are updated using the static methods available in javax.swing.text.StyleConstants. These methods include setting character attributes (font/size/bold/italic/strike-through/underline/subscript/superscript and foreground/background colors), paragraph attributes (indentation/spacing/tab-stops/bidi), image icons and any Swing Component (buttons etc.). Here is the end result:

Rich editbox contents: images, controls & font styles

Rich editbox contents: images, controls & font styles

Note that if a styled multi-line editbox is converted to a single-line editbox (by setting hEditbox’s Max property to 1), it loses all style information, embedded images and components. Returning to multi-line mode will therefore show only the plain-text.

Bookmark and Share

FindJObj GUI - display container hierarchy

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

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 Matlab’s Workspace table

January 2nd, 2010

A few days ago, a CSSM user asked whether it is possible to modify the appearance of the Bytes column in the Workspace pane, so that it will present data in KBytes rather than in Bytes. Although I promised that my next post will explain FindJObj and its uses, I couldn’t resist the challenge. Here’s the solution to the request:

In this post I will assume Matlab release R2008a (7.6) - the adaptations for other releases should be minor at worst. First, we need to retrieve the Workspace table’s Java reference handle. In the past I’ve already shown several uses for the Matlab Desktop’s Java handle. Today we’ll use this handle to get the Workspace pane’s handle:

>> jDesktop = com.mathworks.mde.desk.MLDesktop.getInstance
jDesktop =
com.mathworks.mde.desk.MLDesktop@42d390
 
>> jWSBrowser = jDesktop.getClient('Workspace')
jWSBrowser =
com.mathworks.mde.workspace.WorkspaceBrowser[Workspace,0,24,355x707,...]
 
>> jWSTable = jWSBrowser.getComponent(0).getComponent(0).getComponent(0)
jWSTable =
com.mathworks.mlwidgets.workspace.WorkspaceTable[WorkspaceTable,0,0,352x102,...]

Next, we note that jWSTable is a simple java Swing JTable, and as such we can easily modify its column header:

jWSTable.getColumn('Bytes').setHeaderValue('KBytes');
jWSBrowser.repaint;

Modifying the column’s behavior to display 1/1024 of the initial values is more tricky. We can use a simple TableCellRenderer, replacing WorkspaceTable’s DefaultTableCellRenderer. We first create the following KBytesCellRenderer.java file:

import java.awt.*;
import javax.swing.*;
import javax.swing.SwingConstants.*;
import javax.swing.table.*;
 
public class KBytesCellRenderer extends DefaultTableCellRenderer
                                implements TableCellRenderer
{
  public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column)
  {
    Component cell = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
    //System.out.println(row + "," + column + " => " + value);
    ((KBytesCellRenderer)cell).setHorizontalAlignment(TRAILING);  // TRAILING = right
 
    int bytes = Integer.parseInt(value.toString());
    ((KBytesCellRenderer)cell).setText(bytes/1024 + "");          // Bytes => KBytes
    return cell;
  }
 
  public KBytesCellRenderer()
  {
    super();
  }
}

Next, we find out our Matlab’s Java version:

>> version -java
ans =
Java 1.6.0 with Sun Microsystems Inc. Java HotSpot(TM) Client VM mixed mode

Next, we download and install a JDK version compatible with our Matlab’s Java version. You can download the latest JDK from here or here. Previous JDK versions can be downloaded from here or here (which even lets you select your requested update number). Development versions, usually fixing reported bugs, are described and can be downloaded from here. Note that you need the full JDK, not just the JVM/JRE runtime versions.

Next, we compile this file using the JDK’s Java compiler (javac) utility: In your system’s command-line (outside Matlab), type the following in the folder containing your KBytesCellRenderer.java file:

javac KBytesCellRenderer.java

If all goes well, javac will report no error and will create a KBytesCellRenderer.class file in the current folder (or you can download it directly from here).

Now copy this KBytesCellRenderer.class file to one of the folders in your Matlab’s javaclasspath (for example, C:\Program Files\Matlab\R2008a\java\patch\) and restart Matlab. Don’t worry - all this is only a one-time operation.

After restarting Matlab, we have all the chips in place, so we can place the following code in our startup.m script:

jDesktop = com.mathworks.mde.desk.MLDesktop.getInstance;
jWSBrowser = jDesktop.getClient('Workspace');
jWSTable = jWSBrowser.getComponent(0).getComponent(0).getComponent(0);
jWSTable.getColumn('Bytes').setHeaderValue('KBytes');
jWSTable.getColumn('Bytes').setCellRenderer(KBytesCellRenderer);
jWSBrowser.repaint;


Before - bytes

Before - bytes


After - KBytes

After - KBytes


We can use a slightly more complex CellRenderer to highlight cells with too high a value or to present thousands (comma) separator by simply modifying and recompiling KBytesCellRenderer.java, updating the class file in our javaclasspath folder and restarting Matlab. Here’s the version for the thousands separator (American locale):

import java.awt.*;
import javax.swing.*;
import javax.swing.SwingConstants.*;
import javax.swing.table.*;
import java.text.NumberFormat;
 
public class KBytesCellRenderer extends DefaultTableCellRenderer
                                implements TableCellRenderer
{
  public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column)
  {
    Component cell = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
    //System.out.println(row + "," + column + " => " + value);
    ((KBytesCellRenderer)cell).setHorizontalAlignment(TRAILING);  // TRAILING = Right
 
    int bytes = Integer.parseInt(value.toString());
    NumberFormat nf = NumberFormat.getInstance();
    ((KBytesCellRenderer)cell).setText(nf.format(bytes/1024));    // Bytes => KBytes
    return cell;
  }
 
  public KBytesCellRenderer()
  {
    super();
  }
}

After - formatted KBytes

After - formatted KBytes

If you have created an interesting CellRenderer, I will be happy to hear about it in the comments section below.

Bookmark and Share

GUI integrated HTML panel

December 15th, 2009

Last week, I explained how a browser control can be integrated in Matlab GUI applications. Sometimes we only need to display simple HTML, for which a full browser seems like overkill. Moreover, we may wish to edit the displayed contents, which cannot be done using the browser control. The solution is to use a standard Java Swing JEditorPane control, which is an editable HTML-aware control.

Oddly enough, it was only yesterday that Mikhail, a known Matlab Java specialist on the CSSM newsgroup, posted an example for this as answer to a question on the StackOverflow forum (slightly edited for clarity):

mytext = ['<html><body><table border="1">' ...
          '<tr><th>Month</th><th>Savings</th></tr>' ...
          '<tr><td>January</td><td>$100</td></tr>' ...
          '</table></body></html>'];
 
% Create a figure with a scrollable JEditorPane
hfig = figure();
je = javax.swing.JEditorPane('text/html', mytext);
jp = javax.swing.JScrollPane(je);
[hcomponent, hcontainer] = javacomponent(jp, [], hfig);
set(hcontainer, 'units', 'normalized', 'position', [0,0,1,1]);
 
% Turn anti-aliasing on (R2006a, Java 5.0)
java.lang.System.setProperty('awt.useSystemAAFontSettings', 'on');
je.setFont(java.awt.Font('Arial', java.awt.Font.PLAIN, 13));
je.putClientProperty(javax.swing.JEditorPane.HONOR_DISPLAY_PROPERTIES, true);
 
% This only works on Java 1.5 (Matlab R14SP2 to R2007a):
je.putClientProperty(com.sun.java.swing.SwingUtilities2.AA_TEXT_PROPERTY_KEY, true);

Editable HTML-aware JEditorPane

Editable HTML-aware JEditorPane

Mikhail’s code included setting SwingUtilities2’s AA_TEXT_PROPERTY_KEY property for anti-aliasing. Unfortunately, SwingUtilities2 was an unsupported and undocumented internal class in Java 1.5 (undocumented/unsupported by Sun, not MathWorks for a change…) and completely disappeared in Java 1.6 (which is bundled with Matlab R2007b onward). Therefore, SwingUtilities2 can only be used on Matlab releases R14SP2 (7.0.4) through R2007a (7.4) - on any other Matlab version this will throw an error.

Alternately, use JIDE’s AA_TEXT_PROPERTY_KEY (JIDE is bundled with Matlab and this is supported even on new Matlab releases - I will present JIDE in future articles).

property = com.jidesoft.swing.JideSwingUtilities.AA_TEXT_PROPERTY_KEY;
je.putClientProperty(property, true);

Or, simply add the following switch to your java.opt file:

-Dswing.aatext=true

With this switch, you no longer need to set anti-aliasing separately for each component. It is entirely harmless to set this switch even on Matlab/Java versions that do not support it (the switch is simply ignored in these cases).

Note that while JEditorPane’s support for HTML is extensive, it is incomplete. It also does not contain a JavaScript engine or other web-related features we have come to expect in a browser. For the more complex stuff we can use the browser control as explained in last week’s article.

Matlab’s own multi-line editbox uicontrol uses JEditorPane (or actually its derived-class JTextPane) as an underlying component. This means that the simple-looking Matlab editbox is actually a powerful HTML-aware component. In order to use these hidden undocumented features we need the editbox’s underlying JTextPane handle. This is done using the FindJObj utility, which will be described in my next article. Following that, I will show how to customize Matlab’s dull-looking editbox into something much more powerful. Here’s a sample, to help you stay tuned:

HTML contents in a regular Matlab editbox

HTML contents in a regular Matlab editbox

Bookmark and Share

GUI integrated browser control

December 9th, 2009

Last week, I described the built-in PopupPanel object, and showed how it can be used to present popup messages with HTML content and even entire webpages. I explained that PopupPanel uses an internal browser object to achieve this. In fact, Matlab’s browser object predates PopupPanel by many years and quite a few releases. This browser object can be used as a stand-alone component that we can easily embed in our Matlab GUI applications.

Here is a simple example in which a Matlab Listbox uicontrol is used to select the contents of an adjacent browser component:

% Create a blank figure window
f=figure('Name','Browser GUI demo','Num','off','Units','norm');
 
% Add the browser object on the right
jObject = com.mathworks.mlwidgets.html.HTMLBrowserPanel;
[browser,container] = javacomponent(jObject, [], f);
set(container, 'Units','norm', 'Pos',[0.3,0.05,0.65,0.9]);
 
% Add the URLs listbox on the left
urls = {'www.cnn.com','www.bbc.co.uk','myLocalwebpage.html',...
        'www.Mathworks.com', 'UndocumentedMatlab.com'};
hListbox = uicontrol('style','listbox', 'string',urls, ...
        'units','norm', 'pos',[0.05,0.05,0.2,0.9], ...
        'userdata',browser);
 
% Set the listbox's callback to update the browser contents
cbStr=['strs = get(gcbo,''string''); ' ...
      'url = strs{get(gcbo,''value'')};' ...
      'browser = get(gcbo,''userdata''); ' ...
      'msg=[''<html><h2>Loading '' url '' - please wait''];'...
      'browser.setHtmlText(msg); pause(0.1); drawnow;'...
      'browser.setCurrentLocation(url);'];
set(hListbox,'Callback',cbStr);

Browser object integrated in Matlab GUI (click for large image)

Browser object integrated in Matlab GUI

In this simple example, we can see how the Java browser object can easily be controlled by Matlab. Specifically, we use two modes of the browser: first we present an HTML message (’Loading www.cnn.com - please wait‘) and then replacing this content with the actual webpage, if accessible. If the webpage is not accessible, an error message is displayed:

Browser message when webpage is missing (click for large image)

Browser message when webpage is missing

We can easily expand this simple example to display any HTML message or webpage, in a seamless integration within our GUI.

Now, who ever said that Matlab GUI looks static or boring???

In an unrelated note, I would like to extend good wishes to Ken Orr, who has left the Mathworks Desktop development team to join Apple a few days ago. You probably know Ken from his good work on the Desktop and the official Matlab Desktop blog. Hopefully, in his new position Ken will be able to influence Mac Java in a way that will reduce the numerous recurring issues that afflict Matlab Mac releases.

Bookmark and Share

Customizing help popup contents

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