Archive for the ‘High risk of breaking in future versions’ Category

setPrompt - Setting the Matlab Desktop prompt

Monday, January 25th, 2010

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

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

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

Setting the Command Window’s callback

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

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

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

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

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

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

Preventing callback re-entry

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

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

Handling multiple prompt types

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

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

File Exchange submission

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

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

Setting a continuously-updated timestamp prompt

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

setPrompt usage examples

setPrompt usage examples

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

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

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

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

Bookmark and Share

Rich Matlab editbox contents

Wednesday, 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

Tuesday, January 12th, 2010

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

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

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

FindJObj GUI (click to zoom)

FindJObj GUI (click to zoom)

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

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

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

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

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

Item-specific context-menu

Item-specific context-menu

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

FindJObj - flashing red border around a toolbar icon

FindJObj - flashing red border around a toolbar icon

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

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

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

Bookmark and Share

FindJObj - find a Matlab component’s underlying Java object

Wednesday, January 6th, 2010

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

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

Findjobj has two main purposes:

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

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

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

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

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

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

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

Modified uicontrol cursor - a Java property

Modified uicontrol cursor - a Java property

…or as complex as:

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

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

Bookmark and Share

Customizing Matlab’s Workspace table

Saturday, 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

Customizing help popup contents

Monday, November 30th, 2009

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

Help popup

Help popup

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

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

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

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

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

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

Then we would get this result:

User-created arbitrary text

User-created arbitrary text

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

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

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

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

Help popup with HTML content

Help popup with HTML content

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

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

HTML content with highlighting

HTML content with highlighting

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

browser.setCurrentLocation('UndocumentedMatlab.com');

Help popup browser displaying a URL web-page

Help popup browser displaying a URL web-page

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

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

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

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

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

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

Bookmark and Share

Customizing uiundo

Wednesday, November 4th, 2009

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

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

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

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

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

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

Menu before customization

Menu after customization

Figure menu before and after customization

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

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

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

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

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

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

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

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

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

undoObj.CommandManager.empty;

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

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

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

Bookmark and Share

uiundo - Matlab’s undocumented undo/redo manager

Thursday, October 29th, 2009

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

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

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

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

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

Simple GUI with slider update of a numeric value

Simple GUI with slider update of a numeric value

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

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

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

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

Undo/redo functionality integrated in the figure

Undo/redo functionality integrated in the figure

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

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

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

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

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


Undo/redo functionality integrated in the figure toolbar

Undo/redo functionality integrated in the figure toolbar

Undo/redo functionality integrated in the figure toolbar


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

Bookmark and Share

Accessing the Matlab Editor

Monday, October 19th, 2009

Matlab’s built-in editor, like most other Matlab GUI, is Java-based. As such, it can easily be accessed programmatically. ImageAnalyst, a well-respected member of the Matlab community and a frequent CSSM (newsgroup) and FEX (File Exchange) contributor, recently asked whether it is possible to retrieve the name of the Editor’s currently edited file. The answer is that this is very easy, but I decided to use this opportunity to show how other interesting things can be done with the Editor.

Before we start, it should be made clear that this entire article relies on MathWorks internal implementation of the Editor and Desktop, which may change without prior notice in future Matlab releases. The code below appears to work under Matlab 6 & 7, but users who rely on forward compatibility should be aware of this warning.

We start by retrieving the Editor handle. This can be done in a number of ways. The easiest is via the Matlab desktop:

try
    % Matlab 7
    desktop = com.mathworks.mde.desk.MLDesktop.getInstance;
    jEditor = desktop.getGroupContainer('Editor').getTopLevelAncestor;
    % we get a com.mathworks.mde.desk.MLMultipleClientFrame object
catch
    % Matlab 6
    % Unfortunately, we can't get the Editor handle from the Desktop handle in Matlab 6:
    %desktop = com.mathworks.ide.desktop.MLDesktop.getMLDesktop;
 
    % So here's the workaround for Matlab 6:
    openDocs = com.mathworks.ide.editor.EditorApplication.getOpenDocuments;  % a java.util.Vector
    firstDoc = openDocs.elementAt(0);  % a com.mathworks.ide.editor.EditorViewContainer object
    jEditor = firstDoc.getParent.getParent.getParent;
    % we get a com.mathworks.mwt.MWTabPanel or com.mathworks.ide.desktop.DTContainer object
end

Now that we have the Editor handle, let’s retrieve its currently open (active) file name from the Editor’s title:

title = jEditor.getTitle;
currentFilename = char(title.replaceFirst('Editor - ',''));

The entire list of open file names can be retrieved in several ways:

% Alternative #1:
edhandle = com.mathworks.mlservices.MLEditorServices;
allEditorFilenames = char(edhandle.builtinGetOpenDocumentNames);
 
% Alternative #2:
openFiles = desktop.getWindowRegistry.getClosers.toArray.cell;
allEditorFilenames = cellfun(@(c)c.getTitle.char,openFiles,'un',0);

At the top-level Editor-window level, we can prevent its resizing, update its status bar, modify its toolbar/menu-bar, control docking and do other similar fun things:

% Actions via built-in methods:
jEditor.setResizable(0);
jEditor.setStatusText('testing 123...');
jEditor.setTitle('This is the Matlab Editor');
 
% Equivalent actions via properties:
set(jEditor, 'Resizable', 'off');
set(jEditor, 'StatusText', 'testing 123...');
set(jEditor, 'Title', 'This is the Matlab Editor');

Actually, the jEditor handle has over 300 invokable methods and close to 200 properties that we can get/set. Perhaps the easiest way to find interesting things we can programmatically do with the Editor handle, is to use my UIInspect utility on the File Exchange:

uiinspect(jEditor);  % or: jEditor.uiinspect
Matlab Editor methods, callbacks and properties as seen by uiinspect (click to zoom)

Matlab Editor methods, callbacks and properties as seen by uiinspect
(click to zoom)

The Editor handle is actually a container for many internal panels (toolbars etc.) and documents. The entire object hierarchy can be seen with another of my File Exchange utilities, FindJObj:

findjobj(jEditor);  % or: jEditor.findjobj
Matlab Editor object hierarchy as seen by findjboj (click to zoom)

Matlab Editor object hierarchy as seen by findjboj (click to zoom)

We can modify text within the open Editor documents, and instrument these document to handle event callbacks. To see how, I refer users to my EditorMacro utility on the Matlab File Exchange.

If you find some other nifty and/or useful things that can be done using the Editor handle, please post them in the comments section below.

Bookmark and Share

R2009b keyboard bindings

Sunday, September 6th, 2009

This weekend, Kenn Orr, Matlab’s desktop development team leader, announced an important and long-awaited feature that becomes available in the upcoming R2009b release: the ability to customize keyboard bindings in the system preferences.

This is indeed an important preference customization, which has often been requested. At first look, it appears that the desktop design team has done a good job of enabling easy keyboard shortcuts customization, saving and loading sets of shortcuts etc. - all in a new easy-to-use preference page.

This need has been addressed in the past by my EditorMacro utility, its expansions (here and here), and Perttu Ranta-aho’s KeyBindings derivative utility.

Kudos for a job apparently well-done for the Matlab dev team aside, there may still be reasons for using EditorMacro and/or KeyBindings:

  • Earlier Matlab versions - those who have a Matlab release earlier than R2009b have no option but to use these utilities. The first version of EditorMacro even supported Matlab 6.0, now almost a decade old!
  • Programmatic access - some users may wish to have programmatic access to the keyboard bindings. For example, by saving sets of bindings in an m-file and accessing any of these sets via GUI or the desktop shortcuts toolbar. Note that in R2009b, these preferences can probably be accessed programmatically via the preferences interface, explained here.
  • Understanding the underlying workings of the Matlab desktop and editor, for those interested in exploring and using these undocumented subjects.

User-contributed utilities, especially those relying on undocumented features like EditorMacro and KeyBindings, will always pale next to sleek GUI preferences which are well-integrated by design. They have a place in niche usages, as explained above, but the hope is that all these needs will eventually be addressed by well-documented integrated features, exactly as happened in this particular case.

Bookmark and Share