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!

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!

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).

JWebPane update

June 5, 2009

I was one of the few attendees of the late-night JWebPane BOF (BOF-3992). Artem Ananiev and Alexey Ushakov gave a fantastic demo of a Java web browser based on the JWebPane component (I’m trying to get ahold of a screen shot of this). I did learn that WebKit doesn’t actually provide a rendering engine, but instead provides hooks such that you can be told when and what to paint. This works out well, because JWebPane’s content can be completely painted using Java 2D and Swing.

The team was of course asked when JWebPane would be released (this was my only motivation for attending!). As expected, no date was provided, but they did suggest that it may be available by the end of the year (2009). They also said that an actual date would be put forth within the next couple of months.

One interesting tidbit of information that slipped out was that JWebPane’s release date is being aligned with JavaFX. It’s not clear to exactly why this connection to JavaFX exists, but I’m sure we’ll find out soon.

** Update: screen shots available here. **

ticket

And the winners of the IntelliJ raffle are…

Animesh Jain
Craig Kelley
Greg Gerard

Congratulations to the winners! If you didn’t win, and are still interested in trying out IntelliJ, I encourage you to download their 30 day trial.

logo_jetbrains
I got in touch with JetBrains last week with a few of questions about IntelliJ. Dmitry Jemerov was kind enough to take the time to answer them. JetBrains also offered up 3 IntelliJ licenses to raffle off! To enter the raffle, email me by 8:30 a.m. on Tuesday, June 2nd, 2009 (that’s the official kick-off of JavaOne 2009) with the subject IntelliJ Raffle. I’ll announce the winners at some point on Wednesday, June 3rd.

Bold text = me (Ken)
Plain text = Dmitry

Tell us a little bit about yourself Dmitry.

dmitry_jemerov I’m one of the people leading the development
of IntelliJ IDEA and other products based on the IntelliJ IDEA platform. Most of my development efforts at this time are related to RubyMine, our new IDE for Ruby and Rails, and the platform components of IntelliJ IDEA. I also spend a lot of my time on “external communication” (processing incoming bug reports for IntelliJ IDEA, talking to users on the forums and so on).

I’m a UI guy, so I have to start off with my most pressing question — are there any plans for a major overhaul of IntelliJ’s user interface?

If you would like to see some specific areas overhauled, you’re welcome to send your suggestions. :) As for our own plans, we’re mostly working on small local cleanups rather than big overhauls. Some of the things we’ve recently added are a slimmer Project View toolbar and non-modal notifications for different operations where previously modal message boxes were used.

I may take you up on that and send in a few thoughts! By the way, I really like the reduction in non-modal notifications (e.g. when you perform a search that has no results, a green balloon pops up up indicating that nothing was found — very nice!).

Staying on the user interface topic, are there plans to support JavaFX in IntelliJ 9 (Maia)?

At the moment our general thinking is that Flex is a much more mature and robust platform for developing Rich Internet Applications, and that’s where we are investing a significant development effort. We’ll see how JavaFX adoption evolves later (particularly in the light of the Oracle acquisition), but at the moment we don’t have any plans to provide anything beyond the basic JavaFX support that’s currently available in IntelliJ IDEA 8.1.x.

When is IntelliJ 9 (the next major IntelliJ release) slated to ship?

IntelliJ IDEA generally follows a yearly release cycle, so the next major version will ship sometime in autumn 2009.

What would you say the biggest features or changes or that will make the upgrade worth it for developers?

The biggest new feature is support for Java EE 6 and Spring 3, followed by the support for several other new technologies like Tapestry, OSGi and Android. In terms of core functionality, the biggest user-visible change is IDEA’s new capability to perform indexing in background during initial project opening.

Indexing in the background sounds good — it’s agonizing watching that modal indexing dialog.

There were a couple of features I saw on the IntelliJ 9 preliminary roadmap that sounded interesting. Specifically, what are “Floating editor tabs” ? You also mentioned a more “tasked-based UI” — what do you mean ?

Floating editor tabs mean the possibility to detach some tabs and move them outside of the main IntelliJ IDEA window. This is very important when working with multiple monitors, as this will allow to put code editors on several monitors at the same time.

Task-based UI means the possibility to associate certain context (files, breakpoints, run configurations etc.) with a task that you’re working on. If you’re working on several things in parallel, switching between tasks will automatically switch the active change-list, deactivate the breakpoints from the old task and activate those from the new one, load the new run configurations (sets of unit tests), and so on.

I like the idea of being able to break out editor windows. I many times want to see files side by side, but I just can’t get into the current splitting functionality — sounds like floating editor tabs will solve my problem.

As a cross platform IDE, how hard is it to satisfy the different communities — in other words, how hard is it make Mac users happy?! Do developers complain that you don’t offer a native file chooser or print dialog?

First of all, I have to say that we love Macs, and several people on the team (including our UI guys) use Macs as their main development machines.

The users’ impression is actually more of a problem with RubyMine than with IntelliJ IDEA. Java developers are generally OK with the UI we have (I guess), but users switching from TextMate to RubyMine very often complain about our non-native-looking UI. The file chooser is often one of the first things they complain about, but actually I haven’t seen any complaints about the print dialog. :) I’d guess not so many users need to print their source code these days.

We definitely plan to address those complaints – for example, we plan to look into switching to Quaqua as the default L&F under MacOS X.

I’m glad to hear that you guys love Mac users, and even more glad to hear that your UI guys are developing on Macs!

IntelliJ IDEA has been around for a long time now . What do you think the differentiators are that help JetBrains keep such a loyal customer base when there are free IDEs like NetBeans and Eclipse?

IntelliJ IDEA has been in development since 2000. I’d say that what keeps our users loyal is our focus on productivity and attention to detail in the area of the core coding experience. We try to use every chance of helping the user accomplish his or her tasks without being annoying or intrusive.

When developing IntelliJ, does your team talk about or refer to NetBeans and Eclipse?

Not very often in fact. We look at the new features in the major releases of NetBeans and/or Eclipse, and once in a while we look at the UI for some specific tasks when designing our own user interface. The feedback we receive from our users and our own experience using the product matters much more to us.

Where and when can we see you at JavaOne 2009?

JetBrains will have a booth at JavaOne (booth #728), and we’re also giving a talk on MPS, our new Language Workbench technology (Tuesday, June 02, 3:20 PM – 4:20 PM)

If your going to be at JavaOne, go say hi to the JetBrains crew and tell them what you want to see in IntelliJ!

JTable looks a little rough around the edges straight out of the box. With a couple of tweaks, though, we can make it fit in better on the Mac (and Windows too).

Here’s a screen shot of a JTable that uses no auto-resize (the default on most platforms) with no customization:
jtable_bad

So what’s the most glaring problem here? Well for one, the table header doesn’t extend to the scrollbar. Second, there isn’t a scroll pane corner set on the upper right corner (in a perfect world, the scroll pane corner would merge into the table header, but we’ll let that go for now!).

Below, I’ve set the empty table header space to the right of the right-most column (if there is any) to paint the table header background. I’ve also installed a scroll pane corner and added row striping for effect. Here’s what it looks like:

jtable_good

Not bad.

Below is the implementation. Note that I’ve done the striping in a custom JViewport because it’s much easier that way. Also, the selection intentionally doesn’t span the entire width of the viewport, as the native table selection doesn’t do this.

public class BetterJTable extends JTable {

    private static final Color EVEN_ROW_COLOR = new Color(241, 245, 250);
    private static final Color TABLE_GRID_COLOR = new Color(0xd9d9d9);

    private static final CellRendererPane CELL_RENDER_PANE = new CellRendererPane();

    public BetterJTable(TableModel dm) {
        super(dm);
        init();
    }

    private void init() {
        setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
        setTableHeader(createTableHeader());
        getTableHeader().setReorderingAllowed(false);
        setOpaque(false);
        setGridColor(TABLE_GRID_COLOR);
        setIntercellSpacing(new Dimension(0, 0));
        // turn off grid painting as we'll handle this manually in order to paint
        // grid lines over the entire viewport.
        setShowGrid(false);
    }

    /**
     * Creates a JTableHeader that paints the table header background to the right
     * of the right-most column if neccesasry.
     */
    private JTableHeader createTableHeader() {
        return new JTableHeader(getColumnModel()) {
            @Override
            protected void paintComponent(Graphics g) {
                super.paintComponent(g);
                // if this JTableHEader is parented in a JViewport, then paint the
                // table header background to the right of the last column if
                // neccessary.
                JViewport viewport = (JViewport) table.getParent();
                if (viewport != null && table.getWidth() < viewport.getWidth()) {
                    int x = table.getWidth();
                    int width = viewport.getWidth() - table.getWidth();
                    paintHeader(g, getTable(), x, width);
                }
            }
        };
    }

    /**
     * Paints the given JTable's table default header background at given
     * x for the given width.
     */
    private static void paintHeader(Graphics g, JTable table, int x, int width) {
        TableCellRenderer renderer = table.getTableHeader().getDefaultRenderer();
        Component component = renderer.getTableCellRendererComponent(
                table, "", false, false, -1, 2);

        component.setBounds(0,0,width, table.getTableHeader().getHeight());

        ((JComponent)component).setOpaque(false);
        CELL_RENDER_PANE.paintComponent(g, component, null, x, 0,
                width, table.getTableHeader().getHeight(), true);
    }

    @Override
    public Component prepareRenderer(TableCellRenderer renderer, int row,
                                     int column) {
        Component component = super.prepareRenderer(renderer, row, column);
        // if the rendere is a JComponent and the given row isn't part of a
        // selection, make the renderer non-opaque so that striped rows show
        // through.
        if (component instanceof JComponent) {
            ((JComponent)component).setOpaque(getSelectionModel().isSelectedIndex(row));
        }
        return component;
    }

    // Stripe painting Viewport. //////////////////////////////////////////////

    /**
     * Creates a JViewport that draws a striped backgroud corresponding to the
     * row positions of the given JTable.
     */
    private static class StripedViewport extends JViewport {

        private final JTable fTable;

        public StripedViewport(JTable table) {
            fTable = table;
            setOpaque(false);
            initListeners();
        }

        private void initListeners() {
            // install a listener to cause the whole table to repaint when
            // a column is resized. we do this because the extended grid
            // lines may need to be repainted. this could be cleaned up,
            // but for now, it works fine.
            PropertyChangeListener listener = createTableColumnWidthListener();
            for (int i=0; i<fTable.getColumnModel().getColumnCount(); i++) {
                fTable.getColumnModel().getColumn(i).addPropertyChangeListener(listener);
            }
        }

        private PropertyChangeListener createTableColumnWidthListener() {
            return new PropertyChangeListener() {
                public void propertyChange(PropertyChangeEvent evt) {
                    repaint();
                }
            };
        }

        @Override
        protected void paintComponent(Graphics g) {
            paintStripedBackground(g);
            paintVerticalGridLines(g);
            super.paintComponent(g);
        }

        private void paintStripedBackground(Graphics g) {
            // get the row index at the top of the clip bounds (the first row
            // to paint).
            int rowAtPoint = fTable.rowAtPoint(g.getClipBounds().getLocation());
            // get the y coordinate of the first row to paint. if there are no
            // rows in the table, start painting at the top of the supplied
            // clipping bounds.
            int topY = rowAtPoint < 0
                    ? g.getClipBounds().y : fTable.getCellRect(rowAtPoint,0,true).y;

            // create a counter variable to hold the current row. if there are no
            // rows in the table, start the counter at 0.
            int currentRow = rowAtPoint < 0 ? 0 : rowAtPoint;
            while (topY < g.getClipBounds().y + g.getClipBounds().height) {
                int bottomY = topY + fTable.getRowHeight();
                g.setColor(getRowColor(currentRow));
                g.fillRect(g.getClipBounds().x, topY, g.getClipBounds().width, bottomY);
                topY = bottomY;
                currentRow ++;
            }
        }

        private Color getRowColor(int row) {
            return row % 2 == 0 ? EVEN_ROW_COLOR : getBackground();
        }

        private void paintVerticalGridLines(Graphics g) {
            // paint the column grid dividers for the non-existent rows.
            int x = 0;
            for (int i = 0; i < fTable.getColumnCount(); i++) {
                TableColumn column = fTable.getColumnModel().getColumn(i);
                // increase the x position by the width of the current column.
                x += column.getWidth();
                g.setColor(TABLE_GRID_COLOR);
                // draw the grid line (not sure what the -1 is for, but BasicTableUI
                // also does it.
                g.drawLine(x - 1, g.getClipBounds().y, x - 1, getHeight());
            }
        }
    }

    public static JScrollPane createStripedJScrollPane(JTable table) {
        JScrollPane scrollPane =  new JScrollPane(table);
        scrollPane.setViewport(new StripedViewport(table));
        scrollPane.getViewport().setView(table);
        scrollPane.setBorder(BorderFactory.createEmptyBorder());
        scrollPane.setCorner(JScrollPane.UPPER_RIGHT_CORNER,
                createCornerComponent(table));
        return scrollPane;
    }

    /**
     * Creates a component that paints the header background for use in a
     * JScrollPane corner.
     */
    private static JComponent createCornerComponent(final JTable table) {
        return new JComponent() {
            @Override
            protected void paintComponent(Graphics g) {
                paintHeader(g, table, 0, getWidth());
            }
        };
    }
}

Taking Jesse Wilson’s lead, here are some JavaOne talks that I plan on attending:

Extreme GUI Makeover (Hybrid Swing and JavaFX)
The classic JavaOne session (which I’m a sucker for) will start by using Nimbus to modernize an apps look and then add some whizzy animation using JavaFX.

Move Your Users: Animation Principles for Great User Experiences
The dynamic duo (Chet and Romain) will talk about animation principles that will help you create great user experiences. If past performance is any indicator, I’m sure this one will be entertaining!

Developing LimeWire
Sam and Michael from LimeWire will talk about how they recently rewrote their entire UI from scratch using Swing. LimeWire is one of the few mainstream commercial apps built on Swing, which means there’s probably something to learn here.

Nimbus: Making Swing Look Sexy!
Any talk with the word “sexy” has my attention! I’m interested in hearing more about creating branded apps using Nimbus, which Jasper and Peter from Sun plan on talking about. We’re seeing more and more custom looking apps these days, so if Nimbus can get us on the band-wagon (or should it be brand-wagon), then that’s a good thing.

Introduction to Google Guice
Jesse and Bob from Google will give us an intro to their dependency injection framework. I’m a novice in that department, so an introduction is just what I need.

What talks sound interesting to you?

JavaOne registration.

I was working with someone recently, and he ran into the bulging-concentric-rounded-rectangle problem, which you can see below (coincidently, Kirill posted about this very topic yesterday):
bulging
Notice that both rounded rectangles (outer and inner) have exactly the same roundedness (16 pixels in this example), which causes the illusion that the inner edge is drooping, or bulging.

Fixing this optical issue is easy — we just need to make the inner radius smaller than the outer radius. The exact way to calculate the inner radius is listed below. I’ve include how to calculate the value in the two ways you’ll likely need.

correct2

Follow

Get every new post delivered to your Inbox.

Join 27 other followers