In earlier posts I showed how to modify the Command Window (CW) text and background color, and a very limited method of displaying red (error) and blue (hyperlinked) CW messages. Since then, I was obsessed with finding a better way to CW display text in any color. After lots of trial-and-errors, frustrations and blind alleys – many more than I imagined when I started out – I am now very proud to say that I have solved the problem. My cprintf utility is now available in the File Exchange.
Before I describe the internal implementation, let me show the end result:
cprintf relies on ideas presented in the previous two posts mentioned above. Since the CW is a JTextArea which does not enable style segments, I was curious to understand how Matlab is able to display syntax highlighting in it. This is probably done using a dedicated UI class (com.mathworks.mde.cmdwin.CmdWinSyntaxUI), as suggested in the second post. This is an internal Matlab Java class, so we cannot modify it. It seemed like a dead end for a while.
But what if we could fool the UI class to think that our text should be syntax highlighted? at least then we’d have a few more colors to play with (comments=green, strings=purple etc.). So I took a look at the CW’s Document component (that holds all text and style info) and there I saw that Matlab uses several custom attributes with the style and hyperlink information:
- the SyntaxTokens attribute holds style color strings like ‘Colors_M_Strings’ for strings or ‘CWLink’ for hyperlinks
- the LinkStartTokens attribute holds the segment start offsets for hyperlinks (-1 for non-hyperlinked, 0+ for hyperlink)
- the HtmlLink attribute holds the URL target (java.lang.String object) for hyperlinks, or null () for non-hyperlink.
I played a hunch and modified the style of a specific text segment and lo-and-behold, its CW color changed! Unfortunately, I found out that I can’t just fprintf(text) and then modify its style – for some unknown reason Matlab first needs to place the relevant segment in a “styled” mode (or something like this). I tried to fprintf(2,text) to set the red (error) style, but this did not help. But when I prepended a simple hyperlink space character I got what I wanted – I could now modify the subsequent text to any of the predefined syntax highlighting colors/styles.
But is it possible to use any user-defined colors, not just the predefined syntax highlighting colors? I then remembered my reported finding from the first post about CW colors that ‘Colors_M_Strings’ and friends are simply preference color objects that can be set using code like:
>> import com.mathworks.services.*; >> Prefs.setColorPref('Colors_M_Strings',java.awt.Color(...));
So I played another hunch and tried to set a new custom preference:
>> Prefs.setColorPref('yair',java.awt.Color.green); >> Prefs.getColorPref('yair') ans = java.awt.Color[r=0,g=255,b=0]
So far so good. I now played the hunch and changed the CW text element’s style name from ‘Colors_M_Strings’ to ‘yair’ and luckily the new green color took effect!
So we can now set any style color (and underline it by enclosing the text in a non-target-url hyperlink), as long as we define a style name for it using Prefs.setColorPref. How can we ensure the color uniqueness for multiple colors? The answer was to simply use the integer RGB values of the requested color, something like ‘[47,0,255]’.
But we still have the hyperlinked (underlined) space before our text – how do we get rid of it? I tried to set the relevant LinkStartTokens entry to -1 but could not: unlike SyntaxTokens which are modifiable Java objects, LinkStartTokens is an immutable numeric vector. I could however set its URL target to null () to prevent the mouse cursor to change when hovering over the space character, but cannot remove the underline. I then had an idea to simply hide the underline by setting the character style to the CW’s background color. The hard part was to come up with this idea – implementation was then relatively easy:
% Get a handle to the Command Window component mde = com.mathworks.mde.desk.MLDesktop.getInstance; cw = mde.getClient('Command Window'); xCmdWndView = cw.getComponent(0).getViewport.getComponent(0); % Store the CW background color as a special color pref % This way, if the CW bg color changes (via File/Preferences), % it will also affect existing rendered strs cwBgColor = xCmdWndView.getBackground; com.mathworks.services.Prefs.setColorPref('CW_BG_Color',cwBgColor); % Now update the space character's style to 'CW_BG_Color' % See within the code: setElementStyle(docElement,'CW_BG_Color',...)
Having thus completed the bulk of the hard detective/inductive work, I now had to contend with several other obstacles before the code could be released to the public:
- Older Matlab versions (e.g., 7.1 R14) uses the Document style elements slightly differently and I needed to find a solution that will work well on all Matlab 7 versions (this took quite some time…)
- If the text is not newline (‘\n’)-terminated, sometimes it is not rendered properly. Adding a forced CW repaint() invocation helped solve much of this problem, but some quirks still remain (see cprintf‘s help section)
- Multi-line text (‘abra \n kadbra’) creates several style elements which needed to be processed separately
- Debugging the code was very difficult because whenever the debugger stopped at a breakpoint, ‘k>>’ is written to the CW thereby ruining the displayed element! I had to devise non-trivial instrumentation and post-processing (see within the code).
- Taking care of exception handling, argument processing etc. to ensure foolproof behavior. For example, accepting case-insensitive and partial (unique) style names.
Bottom line: we now have a very simple and intuitive utility that is deceivingly simple, but took a few dozen hours of investigation to develop. I never imagined it would be so difficult when I started, but this just makes the engineering satisfaction greater
Any sufficiently advanced technology is indistinguishable from magic – Arthur C. Clarke
As usual, your comments and feedback are most welcome
Addendum (May 15, 2009): CPRINTF was today chosen as the Matlab Central File Exchange Pick of the Week. That was fast! – thanks Brett
Addendum (May 18, 2009): CPRINTF was today removed from the Pick of the Week list, after internal MathWorks discussions that came to a conclusion that by posting CPRINTF on an official Matlab blog it might appear as an official endorsement of undocumented features. I can certainly understand this, so no hard feelings…