SeparatorList
I’ve been espousing the power of Glazed Lists for a long time, and won’t be stopping any time soon! I recently needed a JList that visually grouped items into categories. GL made this super simple.

Here’s how you can create a SeparatorList, which will auto-magically insert separators into your JList for you. We’ll create a simple list of items that we want grouped by their first letter.

public class SeparatorListTest {

    /**
     * Creates a {@link Comparator} that compares the first letter of two given strings.
     */
    private static Comparator createComparator() {
        return new Comparator() {
            public int compare(String stringOne, String stringTwo) {
                return stringOne.substring(0,1).compareTo(stringTwo.substring(0,1));
            }
        };
    }

    /**
     * Creates a renderer that can render both separators and regular items.
     */
    private static ListCellRenderer createListCellRenderer() {
        return new DefaultListCellRenderer() {
            @Override
            public Component getListCellRendererComponent(
                    JList list, Object value, int index, boolean isSelected,
                    boolean cellHasFocus) {

                // call the super renderer to take care of setting the foreground and
                // background colors.
                JLabel label = (JLabel) super.getListCellRendererComponent(
                        list, value, index, isSelected, cellHasFocus);

                // if the item being renderered is a separator, then bold it, and shift
                //    in slightly.
                // else if the item being rendered is an actual list item, make it plain
                //    and shift it in more.
                if (value instanceof SeparatorList.Separator) {
                    SeparatorList.Separator separator = (SeparatorList.Separator) value;
                    label.setText(separator.getGroup().get(0).toString().substring(0,1));
                    label.setFont(label.getFont().deriveFont(Font.BOLD));
                    label.setBorder(BorderFactory.createEmptyBorder(0,5,0,0));
                } else {
                    label.setFont(label.getFont().deriveFont(Font.PLAIN));
                    label.setBorder(BorderFactory.createEmptyBorder(0,15,0,0));
                }

                return label;
            }
        };
    }

    public static void main(String[] args) {
        // create a list of items.
        EventList rawList = GlazedLists.eventListOf(
                "apple", "appricot", "acorn", "blueberry", "coconut", "chesnut", "grape");
        // create a SeparatorList based on the raw list of items using the "first-letter"
        // comparator to group them.
        SeparatorList separatorList =
                new SeparatorList(rawList, createComparator(), 0, 1000);

        JList list = new JList(new EventListModel(separatorList));
        list.setCellRenderer(createListCellRenderer());
        JScrollPane scrollPane = new JScrollPane(list);
        scrollPane.setBorder(null);

        JFrame frame = new JFrame();
        frame.add(scrollPane, BorderLayout.CENTER);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setSize(200,200);
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }
}

iAppScrollbar_top_and_bottom
Thanks to Kathryn Huxtable, Mac Widgets for Java now offers iApp scrollbars with buttons at the top-and-bottom/left-and-right (instead of together at the bottom/right).

I’ve incorporated Kathryn’s changes, which you can get via the Subversion repository or via the latest developer build. To split the buttons, as seen in the screen shot above, call IAppWidgetFactory.setIAppScrollBarButtonsSeparate(true).

mighty_mouse
Mighty Mouses have a single debilitating flaw. Over time, that little tiny scroll ball, (reminicent of the ball that used to be underneath the mouse) stops working. I don’t know about you, but I might as well pack it up when this happens, because I am not effective without my scroll ball!

It turns out that there’s an easy fix for this, which I just sought out after being crippled with a mouse that wouldn’t scroll down. Here’s what you do:

  1. Lay a piece of paper on a desk
  2. Flip your Mighty Mouse upside down
  3. Press the little ball down firmly on the piece of paper and roll the ball around
  4. Do this a couple of times, flipping rightside up every so often to blow of the gunk that comes out

It’s that easy!

As I mentioned back here, my collegue, Jared MacDonald, also spoke at JavaOne 2009. Besides being an entertaining speaker, he delivered a great message about applying Test Driven Development (TDD) to user interface development. TDD is oft regarded as too difficult to apply in the UI space. Jared did a great job demonstrating why that’s simply not the case.

pdf Bullet Proof User Interface slides (PDF)

Until next JavaOne!

If you’ve ever extended BasicTableUI, you may have struggled a bit with it’s inability to hook into the cell rendering process. JTable has the prepareRenderer method through which you can inject yourself into the cell painting pipeline. BasicTableUI, however, has no such mechanism.

Why would we even need this capability from a BasicTableUI? In ITunesTableUI, for example, I install a special border on the containing JScrollPane, which paints the row striping (the subject of a future blog entry). In order for the striping to show through, each renderer must be non-opaque (transparent). By default, renderers are opaque, which results in a table that looks like this:

iTunesTable-bad
We have a couple of options to work around this. First, we could simply grab each of the default renderers and make it non-opaque. This isn’t a great solution, though, because there’s no easy way to get a list of all these renderers — we’d end up harding coding a set into our UI delegate. And if downstream consumers added their own renderers, they’d be responsible for making sure they were non-opaque.

The second (and better) option, is to create a custom CellRendererPane that essentially gives us a prepareRenderer method. CellRendererPane is the class used to stamp out each table cell onto the screen. The renderer for the cell is prepared, and then added to the CellRendererPane, which manually invokes paintComponent. Overriding paintComponent gives us the hook into the cell painting process that we’re looking for. Here’s what the custom CellRendererPane looks like:

    /**
     * Creates a custom {@link CellRendererPane} that sets the renderer component to
     * be non-opaque if the associated row isn't selected. This custom
     * {@code CellRendererPane} is needed because a table UI delegate has no prepare
     * renderer like {@link JTable} has.
     */
    private CellRendererPane createCustomCellRendererPane() {
        return new CellRendererPane() {
            @Override
            public void paintComponent(Graphics graphics, Component component,
                                       Container container, int x, int y, int w, int h,
                                       boolean shouldValidate) {
                // figure out what row we're rendering a cell for.
                int rowAtPoint = table.rowAtPoint(new Point(x, y));
                boolean isSelected = table.isRowSelected(rowAtPoint);
                // if the component to render is a JComponent, add our tweaks.
                if (component instanceof JComponent) {
                    JComponent jcomponent = (JComponent) component;
                    jcomponent.setOpaque(isSelected);
                }

                super.paintComponent(graphics, component, container, x, y, w, h,
                    shouldValidate);
            }
        };
    }

Here’s how we create and install this custom CellRendererPane in our extension of BasicTableUI:

CustomTableUI extends BasicTableUI {
    // ...
    @Override
    public void installUI(JComponent c) {
        super.installUI(c);

        table.remove(rendererPane);
        rendererPane = createCustomCellRendererPane();
        table.add(rendererPane);

        // ...
    }
}

and that lets us make all our non-selected cells non-opaque!

iTunesTable-good

iTunes_Table-promo-new
HUDControlsUpdate1-promo-updated
I’ve just released Mac Widgets for Java 0.9.5 (download it here). The major addition in this release is an iTunes style table. I’ve also added a HUD style radio button, made HUDs transparent on non-Mac platforms and completed a number of SourceList enhancements and bug fixes.

Users of the Unified Tool Bar and Bottom Bar will note that these components have been promoted to full classes rather than the awkward factory methods (which had been located in MacWidgetFactory). The API is still evolving, most liking breaking API consumers on upgrade, which is why I’m keeping the library pre-1.0.

You’ll find the full list of enhancements and fixes here, or you can browse the API here. Also, you can see an example that uses the new iTunes table here.

hud_on_windows

I finally got around to making the Heads Up Display (HUD) use transparency on non-Mac platforms. I wanted to keep compatibility with Java 5, so I decided to use reflection to try and gracefully use the com.sun.awt.AWTUtilities.setWindowOpaque method. Here are the two utility methods I use to make a window non-opque:

    /**
     * Try's to make the given {@link Window} non-opqaue (transparent) across
     * platforms and JREs. This method is not guaranteed to succeed, and will fail
     * silently if the given {@code Window} cannot be made non-opaque.
     *
     * This method is useful, for example, when creating a HUD style window that
     * is semi-transparent, and thus doesn't want the window background to be
     * drawn.
     * @param window the {@code Window} to make non-opaque.
     */
    public static void makeWindowNonOpaque(Window window) {
        // on the mac, simply setting the window's background color to be fully
        // transparent makes the window non-opaque.
        window.setBackground(new Color(0, 0, 0, 0));
        // on non-mac platforms, try to use the facilities of Java 6 update 10.
        if (!PlatformUtils.isMac()) {
            quietlyTryToMakeWindowNonOqaque(window);
        }
    }

    /**
     * Trys to invoke {@code com.sun.awt.AWTUtilities.setWindowOpaque(window, false)}
     * on the given {@link Window}. This will only work when running with JRE 6 update 10
     * or higher. This method will silently fail if the method cannot be invoked.
     * @param window the {@code Window} to try and make non-opaque.
     */
    private static void quietlyTryToMakeWindowNonOqaque(Window window) {
        try {
            Class clazz = Class.forName("com.sun.awt.AWTUtilities");
            Method method =
                    clazz.getMethod("setWindowOpaque", java.awt.Window.class, Boolean.TYPE);
            method.invoke(clazz, window, false);
        } catch (Exception e) {
            // silently ignore this exception.
        }
    }

This enhancement will be part of Mac Widgets for Java 0.9.5, which will hopefully be out in the next few weeks. If you want access earlier, you can either download the code and build it from the Subversion repository, or you can email me and I’ll send you the latest jar file.

JWebPane screen shots

June 16, 2009

Alexey Ushakov has just posted screen shots of the JWebPane Java web browser that he showed in his BOF at JavaOne 2009 (which I talked about here). Below is a screen shot I pulled from Alexey’s latest blog post:
JWPScr1
Seems like we’re getting closer!

I thought Safari 4 beta was headed in the right direction with it’s tab location. The Safari design team had moved the tabs to the the top of the window and thus made them up-right like this:
safari_4_beta
Now I realize there was a bit of a usability problem, as the tabs were now doubling as the mechanism to move the window, but I liked the direction the UI was moving in. I greatly disliked the upside down tabs of Safari 3, because they make no visual sense — the tabs are completely disconnected from the content they are tabbing.

As you can imagine, I was dismayed when I installed the final version of Safari 4 only to see the tabs revert to their version 3 visuals:
safari_4
I’m not sure why the Safari visual design team chose this broken tab metaphor to begin with as it’s never made any sense, and it’s clear that they realize this. So why keep around this broken metaphor and make us suffer for another full version?

This is not a hard visual design problem to solve. Google Chrome has a great solution that meets the visual demands of the Mac platform:
chrome_beta
Come on Apple — lets get this fixed.

Here are the slides for my JavaOne 2009 presentation, Simply Sweet Components, TS-4559 (read the abstract). Enjoy!

keynote Keynote version (the format I authored the presentation in)
quick_time QuickTime version (with full animations)
pdf PDF version (with notes, but no animations)

To get the most out of the presentation, I’d recommend clicking through the QuickTime version (the one with the animations), with the PDF version up on the side (which has the notes).