The Source for Java Technology Collaboration
User: Password:



Kirill Grouchnikov's Blog

September 2006 Archives


Listening to the users part II - pixel pushing

Posted by kirillcool on September 30, 2006 at 12:15 AM | Permalink | Comments (28)

I got a very unexpected mail following my previous entry from Romain himself. It contained a very detailed list of visual problems on the new (and old) skins. He also followed up on the changes that i made during the last couple of days with yet more advice - i'm very grateful for this. Here are the shots before:

And here are the shots after (note that there are still some things to be done and this entry will be updated accordingly):

If you're like me, you wouldn't see much difference. If you're like Romain, you'd be quick to whip up the following list:

  • The gradient is less dark in the middle part of buttons, scroll thumbs etc.
  • Combo arrow button has all-round corners fitting better in straight-corner textfield border.
  • Scrollbar track borders form seamless connection with scrollbar buttons borders.
  • Scrollbar track is flat and "sunken" instead of having the same gradient as the scrollbar thumb.
  • Scrollbar buttons are flat as well + they have a gradient reverse to that of the scrollbar track.
  • Mark icons (checkbox, radio button, combo arrow, scrollbar arrow) use theme colors instead of strong black.
  • Tabbed pane has theme-based border with extra margin. The border itself is slightly-rounded and less heavy (especially on the right and the bottom borders). The visual result is much less clutter around the content pane edges and continuity of the tab and the tab component itself.
  • Tab painting does not use as much color as before to provide enhanced continuity of the tab and the tab component itself.
  • Selection animations (you'll have to run the app) are twice as fast removing the perceived sluggishness.
  • Unselected checkboxes are painted with flat gradient so as not to attract too much attention.
  • Default scrollpane border integrates seamlessly with the scrollbars (where present). The visual result is much less clutter around the viewport edges.
  • Bottom-right (or bottom-left under RTL) corner of scrollpane is painted with the background (and the watermark) of the scrollpane itself. The visual result is much less clutter around that corner.
  • Split divider grip has different appearance. Romain says that it should be styled after NetBeans but i can't see too much difference :(
  • Insets of the default list cell renderer place the labels farther away from the list left / right edges. The visual result is more balanced spacing around each item.
  • Checkbox text is better aligned with the checkbox mark, especially on unselected checkboxes.
  • (new) Unselected tabs are partially translucent, drawing less attention from the selected tab.
  • (new) Lock icon on non-editable text components is partially translucent.
  • (new) Non-editable text components have grayed-out background.
  • (new) Slightly rounded corners on text component borders.
  • (new) Brighter background for the panels (actually it was a bug that introduced discrepancy of background color of complex themes vs. the identical simple themes).
  • (new) Search icon uses theme colors (identical to checkbox, radio buttons, scroll arrows, menu arrows, combo arrows etc).

As i've already mentioned, there are still things left to do before the final drop of version 3.1 (code freeze is in two weeks) - you're welcome to leave comments. And no, i have no intention to push for inclusion of Substance into the JDK (you'll have to brush up on your French) - the two-year release cycle doesn't sit well with Substance release cycle. By the way, wasn't Mark talking about removing MIDI from JRE instead of JDK?

And once again - many thanks to Romain.



Listening to the users

Posted by kirillcool on September 26, 2006 at 12:07 AM | Permalink | Comments (16)

Thanks to everybody for commenting on my previous entry. Apparently, Romain is not the only one aghast with the default Substance settings. I'm listening and i'm learning - thanks everybody for the feedback. The main complaints seem to be:

  • Loud color schemes
  • Inconsistent gradients
  • Sensible settings not coming out-of-the-box

Here are four new skins to start in the direction of Looks / Xerto / Pagosoft / Alloy bunch. They use toned-down color schemes, subdued gradient painter, flat title painter and solid borders. Hope you like it better and waiting to hear your thoughts.

The first skin is Sahara and can be set:

  • SubstanceLookAndFeel.setSkin(new SaharaSkin());
  • UIManager.setLookAndFeel(new SubstanceSaharaLookAndFeel());
  • UIManager.setLookAndFeel("org.jvnet.substance.skin.SubstanceSaharaLookAndFeel");
  • -Dswing.defaultlaf=org.jvnet.substance.skin.SubstanceSaharaLookAndFeel

The second skin is Moderate and can be set:

  • SubstanceLookAndFeel.setSkin(new ModerateSkin());
  • UIManager.setLookAndFeel(new SubstanceModerateLookAndFeel());
  • UIManager.setLookAndFeel("org.jvnet.substance.skin.SubstanceModerateLookAndFeel");
  • -Dswing.defaultlaf=org.jvnet.substance.skin.SubstanceModerateLookAndFeel

The third skin is Business and can be set:

  • SubstanceLookAndFeel.setSkin(new BusinessSkin());
  • UIManager.setLookAndFeel(new SubstanceBusinessLookAndFeel());
  • UIManager.setLookAndFeel("org.jvnet.substance.skin.SubstanceBusinessLookAndFeel");
  • -Dswing.defaultlaf=org.jvnet.substance.skin.SubstanceBusinessLookAndFeel

The fourth skin is Creme and can be set:

  • SubstanceLookAndFeel.setSkin(new CremeSkin());
  • UIManager.setLookAndFeel(new SubstanceCremeLookAndFeel());
  • UIManager.setLookAndFeel("org.jvnet.substance.skin.SubstanceCremeLookAndFeel");
  • -Dswing.defaultlaf=org.jvnet.substance.skin.SubstanceCremeLookAndFeel

Update: the screenshots have been updated following a very detailed feedback from Romain. This work is not finished.



Animating layouts part IV - demo

Posted by kirillcool on September 24, 2006 at 04:40 PM | Permalink | Comments (2)

This is the fourth part in a series about automatically animated layouts in Swing applications.

  • The first part introduced the TransitionLayout. This part showed animated versions of BorderLayout and FlowLayout.
  • The second part showed the TransitionLayout applied to a (at least partially) real-world image viewer that allows live thumbnail resizing.
  • The thirs part described the current implementation and alternatives that were considered.

On Tuesday the party is over (the wife and the baby get back home from a long overseas trip) and it's less than an hour before the Sunday night football. What does it mean? Not much time left :)

Click on the link below to launch a test application that shows different capabilities of the TransitionLayout (that has since been modified to handle multiple tracking). The application has three TransitionLayouts installed:

  • The first is on the tabbed pane itself, allowing cross-fades on tab selection (with no change to application code apart from installing the layout manager).
  • The second one is on the top panel of the first tab (buttons 1, 2 and 3).
  • The third one is on the bottom panel of the first tab (buttons 4, 5 and 6).

Use Add buttons on the first tab to add the buttons - note the cross-fade effects. Use the checkboxes to select the visibility of the buttons and click Toggle button to initiate the transition fades / animations. The source for this application is right here.



Passionate / screaming users

Posted by kirillcool on September 23, 2006 at 12:05 AM | Permalink | Comments (30)

Kathy Sierra writes on her blog that the best feedback you can get is a negative feedback. I fully agree, but with one condition - if the feedback is concrete (it doesn't have to be constructive).

You can download this mp3 file (390KB, 0:24 min) that contains Romain's thoughts on Substance (extracted from the full presentation available from Romain's blog. Here is the unabridged transcript:

... and it might be tempting to use a look and feel just to enhance the look of your application, but by any means please don't use the Substance look and feel. This one really sucks. How many of you are using the Substance look and feel? You can tell me, i won't kick your butt. Nobody? OK, that's OK. So, never use it

Now this is passionate and screaming. If only it was concrete...

I started to feel a little disappointed that nobody in the audience uses Substance, but then the next question was if anybody heard about Aerith and the reaction was the same. Oh well...



Animating layouts part III - beneath the hood

Posted by kirillcool on September 21, 2006 at 12:10 AM | Permalink | Comments (12)

This is the third part in a series about automatically animated layouts in Swing applications.

  • The first part introduced the TransitionLayout. This part showed animated versions of BorderLayout and FlowLayout.
  • The second part showed the TransitionLayout applied to a (at least partially) real-world image viewer that allows live thumbnail resizing.

This part describes the current implementation. Important note: - this may not be the best solution. It may not work in many cases. Most probably, there's better and more elegant solution which is no more intrusive than the current one. I'd be more than happy to hear about alternatives and discuss them either in the comments section or privately.

So, how does it work? The idea is simple - wrap the original layout manager and delegate all the layout / size computations requests to it. When are the animations made - after the call to the original doLayout. The wrapper layout manager keeps track of bounds of all container subcomponents before and after the call to the doLayout method. In addition, it keeps track of the visibility status between successive calls to the doLayout method. After the original layout manager is done setting the subcomponents bounds, there are four different cases that trigger different transition / animation routes:

  • A subcomponent wasn't visible before and isn't visible now. This is a no-brainer - do nothing.
  • A subcomponent wasn't visible before and is visible now. This means that it needs to "fade in" - a request for fade-in animation is dispatched on the fade tracker. On every fade cycle, the translucency of the component is interpolated (alpha goes from 0.0 to 1.0) and the component is repainted. See below on how the translucency is set and respected.
  • A subcomponent was visible before and is not visible now. This means that it needs to "fade out". The sequence is almost the same as before, but first the component is set back to visible status (otherwise it wouldn't paint itself). After the fade completes, the visibility status is set back to hidden.
  • A subcomponent was visible before and is visible now. First we check if the old bounds and new bounds differ. If not - nothing is done. If the differ, a request for transition / fade animation is dispatched. On each fade cycle, the component bounds is adjusted to account for the movement. In addition, the component translucency is interpolated with reverse parabolic to make it partially transparent during the middle section of the movement. This allows overlapping components to be visible at the same time, producing a visually pleasing effect. Needless to say that on each fade cycle the subcomponent is repainted.

There are two distinct animations being done - movement and fade. The movement is LAF-agnostic and works under Metal or any other core / third-party LAF. The fade part is trickier, since it involves changing the opacity of the Graphics that is used to paint the component (and its children) during the animation cycle (see below for the alternatives that have been considered). The current implementation requires custom support from either LAF or the component itself. In the first case, the LAF delegates need to be augmented as described below - no changes need to be done to the application code. In the second case the application code needs to be changed for proper fade animations.

When the fade animation begins, the entire subcomponent hierarchy is made non-opaque. At each fade cycle, a TransitionLayout.ALPHA client property is set on every component in that hierarchy. The value is a Float object that contains the current alpha for the painting. When the fade animation ends, this property is recursively cleared and the opacity is restored (recursively) to the previous values. All LAF delegates are augmented to introduce the following code in the update method (inherited from the ComponentUI):

  public void __update(Graphics g, JComponent c) {
    super.update(g, c);
  }

  public void update(Graphics g, JComponent c) {
    Graphics2D graphics = (Graphics2Dg;
    Composite old = graphics.getComposite();
    Object alpha = c.getClientProperty(TransitionLayout.ALPHA);
    if (alpha instanceof Float) {
      graphics.setComposite(AlphaComposite.getInstance(
          AlphaComposite.SRC_OVER, ((Floatalpha).floatValue()));
    }
    this.__update(graphics, c);
    graphics.setComposite(old);
  }

If a specific LAF delegate already has update implemented, this method is renamed and called from the new implementation. Otherwise, a forwarding implementation is synthesized (like in the code above). The augmentation process is done using the ASM bytecode manipulation framework in much the same way as described here.

Here are alternatives that have been considered:

  • Using a custom RepaintManager that sets the relevant composite on the getOffscreenBuffer and getVolatileOffscreenBuffer. The first disadvantage is that it would require setting a custom repaint manager in addition to setting a custom layout manager. In addition, this might interfere with the existing custom repaint managers already installed in a client application. Furthermore, this may not be forward-compatible is the underlying implementation of the painting pipeline is changed.
  • Using a glass pane to make the transitions and fades. The first disadvantage is that a client application may already have a glass pane installed. In addition, the entire container would have to be repainted by the glass pane in order to "hide" the final state of visible elements. Furthermore, looking forward to setting the TransitionLayout on multiple containers in the same screen may make the glass pane-based implementation more complex.

As already mentioned earlier, the current implementation may be far from best or most elegant. The two possible alternatives outlined above may prove to be better and more cross-LAF friendly. There might as well be another route that i don't see. You are more than welcome to suggest alternatives.

And now to the shortcomings of the current implementation (and possible stumbling blocks for alternative implementations):

  • The fading only works on "widgetized" LAF delegates. The process of "widgetizing" a LAF is fully-automated using supplied Ant tasks and has been successfully tested on six other third-party LAFs.
  • The application code should be careful in calling revalidate or repaint on the subcomponents before calling doLayout on the container. This will cause flickers since the subcomponent will be momentarily shown in its new location (revalidate eventually causes component repaint).
  • The application code should be careful in overriding various paint methods that may operate on a Graphics that ignores the fade translucency. As much as it's not recommended to provide custom paint logic, sometimes it can't be done in a different way. One option would be to change the application code, querying the TransitionManager.ALPHA client property on the component and setting the relevant AlphaComposite.
  • No fades are done on components that have been removed. At this stage, the only option (without glass pane) would be to temporarily add them back on fade start and remove them on fade end. However, this poses quite significant risk to the application code integrity and the robustness of the custom layout implementations. So - the components that have been removed just disappear.

What now? If you're using Substance, take the latest drop of 3.1dev that contains the TransitionLayout. If not, head right here to take the latest drop of 1.1dev of laf-widget. In order to set the new layout, just call:

    this.putClientProperty(LafWidget.ANIMATION_KIND, AnimationKind.DEBUG);
    TransitionLayoutManager.getInstance().track(this, true);

The first line is optional - it just sets the animation rate to be extra slow, so you'll be able to verify that the transitions and fades are done properly. The boolean parameter in the second line indicates whether the fade animations should be initiated. If you're running under Substance, set it to true. Otherwise you can set it to false to prevent unnecessary CPU use (that will do nothing) or leave it to true.

Try it on your applications and let me know if you find any problems (i'm most certain you will).



Experimenting with internal frames

Posted by kirillcool on September 20, 2006 at 09:49 AM | Permalink | Comments (3)

Once in a while i skim the Swing-related forums to see what people are asking. Most of the time it's pretty basic stuff that is documented in the tutorials, but sometimes the requests pose a non-trivial problem. After reading this entry about "immobilizing" internal frames and various proposed solutions, i decided to spend the weekend (actually i thought it would take less) adding this as an experimental feature to the latest dev drop of Substance look and feel.

The title pane of internal frames has an additional button that allows "pinning" the internal frame (moving and resizing are disabled). In addition, the SubstanceLookAndFeel.PERMANENTLY_PINNED property can be set on a JInternalFrame to indicate that the internal frame is permanently pinned (value should be Boolean.TRUE in this case).

The following screenshot shows an internal frame in six different states:

  1. Unpinned - the default.
  2. Pinned - user clicked on the pin button and it is selected.
  3. Permanently pinned - using the property above. The pin button can't be clicked.
  4. Unpinned with additional custom buttons (using SubstanceLookAndFeel.setRootPaneCustomTitleButtons API).
  5. Pinned with additional custom buttons (using SubstanceLookAndFeel.setRootPaneCustomTitleButtons API).
  6. Permanently pinned with additional custom buttons (using SubstanceLookAndFeel.setRootPaneCustomTitleButtons API).
internal-frames-pin.png

What do you think?



Animating layouts part II - a real application

Posted by kirillcool on September 20, 2006 at 12:03 AM | Permalink | Comments (3)

The first part introduced the TransitionLayout. This layout is set "on top" of the existing layout manager (delegating all the regular work to it) and handles the changes in component visibility and bounds. The technical implementation will be discussed in the next parts (it is being actively worked on to provide a robust out-of-the-box support and can be accessed right here).

This part shows the transition layout manager applied to a "real-world" application that has been detailed in the series about SVG integration. The application shows thumbnails of all images in a selected folder and allows resizing them using a slider. While the usual version jumps from the old thumbnail size to the new thumbnail size, the new version makes the experience more interactive:

Note the following effects (with no changes to application code except setting the layout manager):

  • The thumbnails are faded-in when they first appear.
  • The thumbnails are moved and resized on changing the global thumbnail size. The original layout manager is responsible only for the final location
  • During the move / resize, the thumbnails are part-time translucent to allow for smoother visual feedback.

Stay tuned for more (until the baby in the picture gets back next week from the overseas trip and the free time is over :)



Animating layouts part I - the teaser

Posted by kirillcool on September 19, 2006 at 12:02 AM | Permalink | Comments (21)

This is going to be the first part of a series about automatically animated layouts in Swing applications. The work on animation effects in Substance has begun a few months ago with the following design goals:

  • Make all animations on a single thread
  • Animate all Swing core components without any change to the application code
  • Make the animation as configurable as possible
  • Keep the animation overhead to the minimum
The animation layer originally written for Substance and later moved into the laf-widget project required minimal changes to provide animations on lists, tables and trees (by the way, i'm still waiting to hear how these can be done in timingframework), and hadn't to be changed a single bit in order to produce the following animated layouts (click on a green play button to start each one of the movies - i'm still learning how to use Wink properly):

Note that the movies show the SLOW animation kind - the default animation rate is twice as fast, and the animation itself can be turned off completely. There are no changes done between the two movies besides setting the layout on the main panel from Border to Flow. The layout animation itself is done with one single like in the application code. The animation rate in the real application is much more consistent than in the movie due to upload size constraints. Enjoy the next parts.

Here is the application code:

import java.awt.BorderLayout;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.*;

import org.jvnet.lafwidget.LafWidget;
import org.jvnet.lafwidget.layout.TransitionLayoutManager;
import org.jvnet.lafwidget.utils.LafConstants.AnimationKind;

public class SampleFrame extends JFrame {
  JButton b1, b2, b3;

  public SampleFrame() {
    super("Transition test");

    this.getContentPane().setLayout(new BorderLayout());

    JPanel buttons = new JPanel(new FlowLayout());
    this.getContentPane().add(buttons, BorderLayout.SOUTH);

    // for the first movie, change the following line to 
    // use the BorderLayout
    final JPanel mainPanel = new JPanel(new FlowLayout());
    // bring the magic in one single line
    TransitionLayoutManager.getInstance().track(mainPanel);
    this.b1 = new JButton("1");
    this.b2 = new JButton("2");
    this.b3 = new JButton("3");
    
    final JButton add = new JButton("add");
    add.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        SwingUtilities.invokeLater(new Runnable() {
          public void run() {
            mainPanel.add(b1, BorderLayout.WEST);
            mainPanel.add(b2, BorderLayout.CENTER);
            mainPanel.add(b3, BorderLayout.EAST);
            mainPanel.revalidate();
            add.setEnabled(false);
          }
        });
      }
    });
    buttons.add(add);

    this.getContentPane().add(mainPanel, BorderLayout.CENTER);

    final JCheckBox cb1 = new JCheckBox("1");
    cb1.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        b1.setVisible(!b1.isVisible());
        mainPanel.revalidate();
      }
    });
    buttons.add(cb1);

    final JCheckBox cb2 = new JCheckBox("2");
    cb2.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        b2.setVisible(!b2.isVisible());
        mainPanel.revalidate();
      }
    });
    buttons.add(cb2);

    final JCheckBox cb3 = new JCheckBox("3");
    cb3.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        b3.setVisible(!b3.isVisible());
        mainPanel.revalidate();
      }
    });
    buttons.add(cb3);

    final JCheckBox cb13 = new JCheckBox("13");
    cb13.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        b1.setVisible(!b1.isVisible());
        b3.setVisible(!b3.isVisible());
        mainPanel.revalidate();
      }
    });
    buttons.add(cb13);

    this.setSize(300100);
    this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    this.setLocationRelativeTo(null);

    mainPanel.putClientProperty(LafWidget.ANIMATION_KIND,
        AnimationKind.SLOW);
  }

  public static void main(String[] args) {
    SwingUtilities.invokeLater(new Runnable() {
      public void run() {
        new SampleFrame().setVisible(true);
      }
    });
  }
}


Animating your lists, tables and trees

Posted by kirillcool on September 16, 2006 at 12:38 AM | Permalink | Comments (11)

Most of the comments on Romain's blog were animation-related. Indeed, the latest trend in the UI world seems to be animations (on pretty much everything), including fade-ins on rollovers, selections, layout changes and what not. So, what better thing to add to the latest drop of denied Substance if not the support for animated fades on lists (was already there in release 3.0), tables and trees.

So, if you're running under the latest dev drop (and by all means, try it out on the real-world applications and let me know of the issues you encounter - i know of few of them already which i'll fix tomorrow between the Flying Irish and Longhorns games) - you'll see something similar to the movie linked below (which is my first experience with the Wink).

In order to view the Flash movie, click right here and wait for the movie to load (in a separate page). It's 1.6MB

Note that you have animations on rollovers and selections, with multiple animations running on the same list / tree / table (all on the same thread). In addition, the animations take into account the model changes and control changes (tree collapse e.g.) that happen during the animation cycles. Click the link below to launch the WebStart application shown in the demo video.



Improving the user experience with scroll bars

Posted by kirillcool on September 14, 2006 at 12:12 PM | Permalink | Comments (11)

One of the more interesting UI rules is the Fitts' law that estimates the time that it takes to complete a movement to a specific UI target. One of the consequences of this law is that if you have a small target located far away, it will take a long time to target it. One option is to make the target bigger, and another option is to make the target closer.

One of the examples of more "problematic" UI target are the scrollbar buttons. They are usually quite small (12-16 pixels) and are situated on the opposite sides of the scrollbar. We can pretty much rule out the option to make them bigger, so we're left with making them closer. Apple's scrollbars "fix" address this issue by putting the scroll buttons together (see the screenshots at this HIG topic.

Now you can get the same and more in the latest drop of Substance 3.1. The SubstanceLookAndFeel.SCROLL_PANE_BUTTONS_POLICY client property specifies the location for scrollbar buttons. The value should be one of the SubstanceConstants.ScrollPaneButtonPolicyKind enum. Available values:

  • NONE - no buttons are shown.
  • OPPOSITE - the default policy. The decrease button is on one side of the scroll bar, the increase button is on another side of the scroll bar.
  • ADJACENT - the decrease button is right next to the increase button of the scroll bar.
  • MULTIPLE - the combination of two. There are two decrease buttons, one on each side of the scroll bar (one of them is right next to the increase button).
  • MULTIPLE_BOTH - Two pairs of decrease / increase buttons on each side of the scroll bar.

The following screenshot shows horizontal scrollbar under different policies and orientations. The first scrollbar is under the default OPPOSITE policy. The second scrollbar is under the ADJACENT policy in LTR application. The third scrollbar is under the MULTIPLE policy in LTR application. The fourth scrollbar is under the ADJACENT policy in RTL application. The fifth scrollbar is under the MULTIPLE policy in RTL application. The sixth scrollbar is under the NONE policy. The seventh scrollbar is under the MULTIPLE_BOTH policy.

The following screenshot shows vertical scrollbar under different policies. The first scrollbar is under the default OPPOSITE policy. The second scrollbar is under the ADJACENT policy. The third scrollbar is under the MULTIPLE policy. The fourth scrollbar is under the NONE policy. The fifth scrollbar is under the MULTIPLE_BOTH policy.

Thanks for the comments and suggesting the NONE and MULTIPLE_BOTH policies. Thanks to Romain for his suggestion to make the default gradient painter less dark.



How color-blind people see your UIs

Posted by kirillcool on September 12, 2006 at 08:48 PM | Permalink | Comments (9)

The color blindness is the inability to perceive differences between some or all colors that other people can distinguish. According to the medical studies, eight to ten percent of male population suffers from some kind of color blindness (figure for female population is much lower). What does it mean to the average Swing developer? Well, if you rely too much on color differences, you may be not conveying the information as well as you thought. For example, red-green blindness (protanopia) may cause the user to miss validation information conveyed by the text component background (light pink for invalid value as opposed to light green for validated field).

One of the previous entries on skinning the Swing UIs using the new complex themes in Substance look and feel showed an easy way to bring some colors to your UI. Now you can run your application in debug UI mode (turned on by the -Dsubstancelag.debugUiMode VM flag) and have a live preview of your UI as viewed by the color-blind population.

Here is a screenshot of a sample UI running under Mango skin:

Once your application is configured with the above VM flag, clicking on the title pane brings the debug UI menu:

Select the Color blindness menu and then one of the color blindness kinds. This is how the same UI will look to protanopes (reduced shades of red, see how the pink turns into saturated blue):

This is how the same UI will look to deuteranopes (reduced shades of green, see how the greenish background turns into almost gray):

This is how the same UI will look to tritanopes (reduced shades of blue, see how the yellow turns into light pink):

Note that currently only the theme itself is changed. This means that if you use custom colors on borders, renderers, backgrounds etc, they will be preserved. The next version most probably will use glass pane in order to convert the entire UI into color-blind version (the UI itself will not be interactive).

The implementation is based on the algorithm described in PDF article



Multi-colored buttons - part 2

Posted by kirillcool on September 10, 2006 at 10:51 PM | Permalink | Comments (0)

The previous posting from half a year ago showed "mixed themes". The basic concept is simple - you take two themes and use some simple blending techniques to create bright and tasty buttons, just like these:

The next version of Substance takes this concept one step further, allowing you to mix more than 2 themes. The results are sometimes better and sometimes worse (4Pill means mix of four base themes):

Here are a few screenshots of bigger buttons to better appreciate the blending in the mixed themes. Larger screenshot of Bottle Green - Lime Green - Bottle Green:

Larger screenshot of Brown - Sunglare - Orange:

Larger screenshot of Barby Pink - Purple - Raspberry:

You can use as many themes as you want, but you'll have to have a pretty powerful CPU for more than 3 themes - i don't have a luxury of hardware acceleration like Java2D team does :)



Precise micro-design in modern look and feels

Posted by kirillcool on September 08, 2006 at 10:41 PM | Permalink | Comments (3)

JGoodies Looks was and undoubtedly remains the pioneer and the leader in the precise micro-design of Swing forms. It's easy to see but hard to achieve, and this comes at cost of elaborate manipulations with insets, borders, preferred sizes and custom layout managers. This entry by Joshua shows the same improvements applied to the Windows LAF in Mustang (at least for the text components, combo boxes and spinners).

Let's see a few screenshots from the core LAFs in Tiger and Mustang. First, we have Metal / Ocean under Tiger 5.0_08 with the combo box much higher than other controls and the bottom spinner button sticking out on the bottom side:

Now, we have Metal / Ocean under Mustang b96 with the same visual problems + grown password field (described here):

Next stop is Windows LAF. Under Tiger in Windows 2000 it looks even more inconsistent. Text field and spinner are 20 pixels high, combo and formatted text field are 22 pixels high, password field is 24 pixels high:

Under Mustang in Windows 2000 we have less inconsistency. Text field, formatted text field and password field are 20 pixels high, combo is 22 pixels high, spinner is 18 pixels high:

Under Tiger in Windows XP we have a total new set of incosistencies. Text field is 19 pixels high, formatted text field and combo are 22 pixels high, password field is 16 pixels high and spinner is 20 pixels high:

Under Mustang in Windows XP we have perfect consistency (everything is 20 pixels high) but somehow the combo text has been truncated:

Only recently one of the Substance users (thanks Raj) pointed my attention to the inconsistency in all the available Substance releases. The text fields and the spinner were 18 pixels high and the combo was 24 pixels high:

This has been fixed in the latest drop of the next version (3.1 code-named Honolulu), so that all the controls are 20 pixels high:

If you are highly concerned about precise micro-design, i highly recommend using the JGoodies look and feel and layout. Thanks to Karsten for his continuing efforts in this area (the rest of us are trying to catch up). By the way, you are welcome to read this entry on my blog that dates back more than a year ago. Frankly, i have absolutely no recollection of having written that (almost), and i start to think that i might have been wrong back then :)



The default look and feel breaks visual compatibility in Mustang

Posted by kirillcool on September 08, 2006 at 04:28 PM | Permalink | Comments (6)

One of the requirements imposed on the default Ocean theme was to Not Break Stuff. So now adjustments were made to the l&f which would have broken existing applications; the sizing of widgets is the same and everything should just work out of the box; it's just the skin of that l&f that's different (and, I think you'll agree, better) (quoting Chet's original post).

Here are the screenshots of the same exact application under Tiger 5.0_08 and Mustang 6.0_b98:

Digging in the code of the MetalLookAndFeel locates this suspicious entry:


   "PasswordField.echoChar", (char)0x2022,

which appears to be the result of fixing this bug. So, somebody suggested making this change for Metal as well, and now the default echo char is 2 pixels wider...

Don't be alarmed if one of your forms will become a little garbled :)



Voice your concerns about closures with this FUD generator

Posted by kirillcool on September 07, 2006 at 12:25 PM | Permalink | Comments (7)

I that closures

I that closures

I that closures



New version of JAXB 2.1 Eclipse plugin

Posted by kirillcool on September 05, 2006 at 08:39 PM | Permalink | Comments (3)

Not a lot of you may be aware of this, but i'm a community lead in the Web Services & XML community right here on java.net. Mainly i'm responsible for updating the main page with the relevant news / weblogs / technical articles, but i also have a JAXB Workshop project that provides a collection of JAXB 2.0 related tools. This project has been largely dormant during the past year except the Eclipse and IDEA plugins. The Eclipse plugin provides a wizard for selecting the input schema, location for the generated files and the package name. After all the parameters have been selected, it invokes the XJC generator and that's pretty much it.

One of the things that have been missing from this wizard - an option to cancel it in the middle, which is quite a common feature for Eclipse wizards. I've filed the relevant issue on the JAXB tracker about six months ago, and once Kohsuke announced the beginning of version 2.1, all i needed was to give him a little push (read the comments on the linked entry). After taking the daily build and making changes to 4 (!!!) lines of code in the Eclipse plugin, now it's cancellable! Thanks to Kohsuke for adding this feature in JAXB 2.1 and to Eclipse engineers for making plugin development in Eclipse such a pleasurable experience.

The second feature that has been added is logging to Eclipse's own console (instead of to the System.out) - this has been requested by one of the users. All i had to do is input "eclipse console" to Google and come up with this link - thanks, Jevon.

So, if you need to generate classes from your schema definition, you can either write a little script, or head over here, download the Eclipse plugin 1.1, unzip it under Eclipse 3.1.0+ installation and follow the instructions in the tutorial.



Conspiracy theory about closures in Dolphin

Posted by kirillcool on September 04, 2006 at 04:17 PM | Permalink | Comments (33)

I was watching the (not so impressive for now) Flying Irish on Saturday and my mind wandered off. Neal Gafter's original proposal for introducing closures into Dolphin along with code samples and what not has been published only two weeks ago and already the blogosphere is abuzz (more on that later). And i started to think - it's like a crime movie and all those cheap police thrillers. When you try to solve a murder, think about who's the first to profit from that death and you have your killer. Now, who's going to profit from having closures in Java and all those corner cases? Of course, the writers of "Java puzzlers"!!! Hey, isn't Neal one of those? Of course!!! Isn't Neal behind the closure proposal? Of course!!!

And now on a more serious note. We have tens of discussions (notably this Javalobby thread initiated by Mikael Grev, this SwingLabs thread and Remi blog right here on java.net). We have scores of proposed syntax changes and hundreds of opinions. Somebody on one of those threads (don't have a link at hand) said "People either understand closures, or don't like it". Well, i understand closures but i can't say that i like them. I feel that it's too disruptive for a such widespread language (and i completely back up Mikael's original post on Javalobby). Does it make programs easier to read? In some cases - yes. Does it bring more clarity to programs? In some cases - yes. Is it easily abused by inexperienced programmers, novices and showoffs? Almost certainly. Does it add yet another way to do what can be done already with existing language constructs (this is what makes Swing so hard for the beginners)? Certainly. Can it make harder to join an existing project and fix somebody else's code? Almost certainly.

Now, language changes in Tiger were both a blessing and a curse. Generics offload some of the error screening to the compiler stage, while making code look more complicated. Autoboxing / unboxing make working with primitive types easier, while introducing hard-to-track bugs, especially in collections of primitive values (see "Java puzzlers" session from JavaOne 2006). Enhanced for-loop is less handy than i originally thought since it doesn't provide a way to remove / index entries. And the list goes on. But, and this is a major but. All these changes were minor compared to what is planned for Dolphin. In Dolphin, we are talking about inferred types, function pointers, omitting entire language constructs and so on. It's not eighties - most of this code is generated in IDE when you press Ctrl+Space, guys. And it's not a problem to read it as well.

Be that as it may, the widespread coverage of the proposed syntax is excellent for both the proposal and for the community. The only thing that makes me wonder is the seemingly selective choice of the community for this particular feature.

More than a year ago, Mark Reinhold outlined the proposed changes to Dolphin at JavaOne 2005. One of the major changes (no closures being mentioned at that time) was native XML support in the language. The community response was anemic at best. This entry on my own blog wasn't very popular (well, it may as well be my fault :), and the only other worthy entry was on Val's blog (which is kind of dormant lately). Sun's own XML engineers were politely shooshed during the Q&A section, and their opinion is nowhere to be seen on the web (and remember, that was more than a year ago). This year's JavaOne featured 158-slide presentation (downloadable here, session number 3441), and Mark followed up on his blog with only 3 (!!!) total comments. The server people seem to not care (well, most of the commercial applications still run on 1.4.2, so we're talking at least 7-8 more years until this gets to the application servers). In the meanwhile, the proposed features seem well-matured, and i'm sure that somebody at Sun already has working prototypes ready to go into Dolphin monthly builds.

Why everybody is such an expert at closures but not at XML? The potential for hard-to-read code is even greater with native XML support, yet nobody seems to care. If this is true (that nobody cares), perhaps we don't need it in the language at all... And i wonder yet again - why we don't hear about this from Sun's XML engineers (JAXWS / JAXB / FI / ... groups).

xml-dolphin.png



Substance LAF 3.0 official release

Posted by kirillcool on September 04, 2006 at 10:38 AM | Permalink | Comments (0)

Substance look-and-feel is pleased to announce the release of version 3.0 (Grenada). The list of new features includes:

  • Configurable flat buttons
  • Combo popup display prototype (to allow wider popups)
  • Tab icon animations - loading, error and warning states
  • Pluggable title painters
  • Saturated and desaturated color themes
  • Blended color themes
  • Complex color themes
  • Specifying color theme on per-component basis has been expanded to include more component types.
  • Tab pager in laf-widget layer.
  • Configurable "select all on focus gain" text components in laf-widget layer.
  • Configurable edit context menu (cut / copy / paste / select all / delete) in text components in laf-widget layer.
  • Configurable drag-and-drop support for trees in laf-widget layer.
  • Tab overview dialog in laf-widget layer is refreshable.
  • Fractal-base watermarks in the watermark pack plugin.
  • API for adding custom buttons on title panes.
  • Animation effects on list selection and rollover
  • Improved painting of sliders - icons are narrower
  • Improved painting of splitters (honor the FLAT_PROPERTY)
  • Improved painting of drag areas of toolbars
  • Improved performance of menu rendering (by two orders of magnitude)

In addition, a a number of bugs have been fixed. Due to code-split (see laf-widget project), some applications may need to follow the instructions in the migration guide.

The internationalization support now includes 17 languages with new contributions from Milan Misak (Czech) and Maciej Zwirski (Polish). Thanks to all other translators for your continued help. Thanks to all contributors, bug reporters, testers, users and the desktop community for this release. Special thanks to Mark Haag for testing the theme engine.

To launch a test application that shows Substance features, click the button below. This application is unsigned - the file dialog will not work.



SVG and Java UIs part 5: improving integration with Batik

Posted by kirillcool on September 03, 2006 at 05:33 PM | Permalink | Comments (6)

This entry is the fifth part in the ongoing series about using SVG-based icons in Swing-based applications.

  • The first part outlined the need for using scalable icons in next-generation Java UIs and proposed using SVG images as a possible format, showing the ribbon component which employs heavy icon rescaling.
  • The second entry introduced an SVG file previewer based on the breadcrumb bar component, a ribbon button from the ribbon component and SVG Salamander renderer
  • The third part showed how the Apache Batik SVG renderer can initialize and scale SVG-based application in an asynchronous and non-blocking fashion.
  • The fourth entry showed what happens beneath the hood and described some of the problems found with Batik integration.

Cameron McCormack from Batik project has responded to some of the bug reports that i have filed. The most interesting one is bug 40397 - GNOME icons can't be parsed by Batik. Cameron's comments suggest that it's a problem with GNOME icons and not with Batik - we'll wait to hear from GNOME people. The relevant issues are 354103, 354192, 354194, 354205 and 354221. Thanks to Cameron for a prompt response.

The most annoying for me was RFE 40394 - provide an Icon-extending implementation. Cameron was kind enough to attach a base implementation and it took me the better part of the afternoon (listening to the new Nelly Furtado's album sounding alarmingly Paula Abdulesque... may be she's planning to judge the next "American Idol"... but i digress again... must finish entry...) to take this implementation and add asynchronous loading and resizability support. Most of the code for asynchronous loading was borrowed from the JGVTComponent (still one thread per icon and no thread pool yet), while the resizability support is modeled after the SVG Salamander API. The current implementation of Batik-based resizable icon is:

public class SvgBatikResizableIcon extends SvgBatikIcon implements
    ResizableIcon, AsynchronousLoading {
  private Dimension initialDim;

  /**
   * The listeners.
   */
  protected List asyncListeners = Collections
      .synchronizedList(new LinkedList());

  public SvgBatikResizableIcon(URL location, final Dimension initialDim) {
    super(location.toString(), initialDim.width, initialDim.height);
    this.initialDim = initialDim;
    this.addGVTTreeRendererListener(new GVTTreeRendererAdapter() {
      @Override
      public void gvtRenderingCompleted(GVTTreeRendererEvent e) {
        fireAsyncEvent(asyncCompletedDispatcher, null);
      }
    });
  }

  public void revertToOriginalDimension() {
    this.setPreferredSize(this.initialDim);
  }

  public void setDimension(Dimension dim) {
    this.setPreferredSize(dim);
  }

  public void setHeight(int height) {
    this.setPreferredSize(new Dimension(height, height));
  }

  public void setWidth(int width) {
    this.setPreferredSize(new Dimension(width, width));
  }

  public void addAsynchronousLoadListener(AsynchronousLoadListener l) {
    asyncListeners.add(l);
  }

  public void removeAsynchronousLoadListener(AsynchronousLoadListener l) {
    asyncListeners.remove(l);
  }

  public void fireAsyncEvent(Dispatcher dispatcher, Object event) {
    EventDispatcher.fireEvent(dispatcher, asyncListeners, event, true);
  }

  static Dispatcher asyncCompletedDispatcher = new Dispatcher() {
    public void dispatch(Object listener, Object event) {
      ((AsynchronousLoadListenerlistener).completed();
    }
  };
}

The most interesting part is in the constructor - it registers an GVT tree renderer listener and fires a complete event when the rendering is complete. The reason for this is that the icon doesn't live on its own and has a paintIcon method that gets a graphic context from the owner component. This icon implements a new AsynchronousLoading interface - a component that uses such icon can check if the icon is asynchronously-loading and register a completion listener:

    ResizableIcon buttonIcon = this.ribbonButton.getIcon();
    this.iconLabel = new JLabel(this.ribbonButton.getIcon());
    if (buttonIcon instanceof AsynchronousLoading) {
      ((AsynchronousLoadingbuttonIcon)
          .addAsynchronousLoadListener(new AsynchronousLoadListener() {
            public void completed() {
              ribbonButton.repaint();
            }
          });
    }
    this.ribbonButton.add(this.iconLabel);

This implementation addresses most of the issues that i had with Batik - the Icon-implementing class, preserving asynchronous loading and correct resizing of both Tango and GNOME icons. To launch the SVG file viewer using Batik renderer, click on the image below - note how the icons are shown and resized asynchronously leaving the UI responsive. Play with the slider to resize all currently shown icons. Note that this application is signed since (obviously) it requires access to the local disk. If you don't want to run it, download and watch the video link below.

In case you don't want / can't run the WebStart demoes or download the stuff from the projects pages, this video (1.3MB, 0:35 min, original WMV format renamed to AVI) shows the SVG file previewer with various animation effects (from the Substance look-and-feel) and responsive UI thanks to Batik.



SVG and Java UIs part 4: beneath the hood

Posted by kirillcool on September 02, 2006 at 01:54 PM | Permalink | Comments (0)

This entry is the fourth part in the ongoing series about using SVG-based icons in Swing-based applications.

So, how it all works together in the flamingo test applications? The most important class is the org.jvnet.flamingo.common.ResizableIcon that defines the following extension of the Icon interface:

/**
 * Interface for icons that have resizability behaviour.
 
 @author Kirill Grouchnikov
 */
public interface ResizableIcon extends Icon {
  /**
   * Changes the dimension of <code>this</code> icon.
   
   @param newDimension
   *            New dimension for <code>this</code> icon.
   */
  public void setDimension(Dimension newDimension);

  /**
   * Changes the height of <code>this</code> icon.
   
   @param height
   *            New height for <code>this</code> icon.
   */
  public void setHeight(int height);

  /**
   * Changes the width of <code>this</code> icon.
   
   @param width
   *            New width for <code>this</code> icon.
   */
  public void setWidth(int width);

  /**
   * Requests <code>this</code> icon to revert to its original dimension.
   * May be safely ignored by the icon if it doesn't support such behaviour.
   */
  public void revertToOriginalDimension();
}

This interface is used extensively throughout the ribbon component (buttons, in-ribbon galleries, popup galleries). In addition, the SVG file previewer introduced in the previous entries uses this class as well. The implementation of this interface is up to the application - it may be Java2D-based, SVG-based or any other custom code.

The first sample SVG implementation is based on the SVG Salamander renderer and is quite straightforward thanks to the API provided by the base SVGIcon class in that library:

public class SvgSalamanderResizableIcon extends SVGIcon implements ResizableIcon {
  private Dimension origDim;

  public SvgSalamanderResizableIcon(URI svgUri, Dimension dim) {
    super();
    this.setSvgURI(svgUri);
    this.setAntiAlias(true);
    this.origDim = dim;
    this.setScaleToFit(true);
    this.setPreferredSize(this.origDim);
  }

  public SvgSalamanderResizableIcon(URL svgUrl, Dimension dim) {
    super();
    try {
      this.setSvgURI(svgUrl.toURI());
    catch (URISyntaxException urise) {
      throw new IllegalStateException(urise);
    }
    this.setAntiAlias(true);
    this.origDim = dim;
    this.setScaleToFit(true);
    this.setPreferredSize(this.origDim);
  }

  public <