Don’t forget the attachment

November 17, 2010

I’m rarely wowed by UIs these days, but Gmail impressed me today. I was writing up an email, with the intent of attaching a document. Of course, by the time I finished writing the email, I had forgotten to actually attach the document. But Gmail informed me of this mistake when I pressed send:

I wonder if they can figure out a way to warn me even before I press send?

Time for a new keyboard

June 13, 2010

The keyboard is an absolutely fundamental piece of equipment for us software developers. I’ve always used stock keyboards, but my fingers are in need of a keyboard designed for intense and constant use. There have been plenty of web articles on choosing the right keyboard but I’m specifically interested in satisfying a few specific needs (listed in priority order):

  1. Minimal stress on my fingers. When I’m really going at it, my pinkies can end up soar, which ends up slowing me down. Not to mention I don’t want to end up with carpal tunnel!
  2. 1st class support for Mac. I want a keyboard designed to be used with a Mac.
  3. Can’t be distracting. I don’t mind a keyboard with a bit of noise, but it can’t be loud enough that it distracts me or those working around me.

The keyboards that appear to meet my criteria are unfortunately not physically available in any stores to try out, which makes it a bit difficult because they aren’t cheap! So I’m throwing the question out there to here about the keyboard that you love and use every day. Here are a few keyboards that I’m interested in:

Matias Tactile Pro Keyboard

Matias OS X Keyboard

Das Keyboard, Model S (silent)

What do you think?

WWDC 2010

June 6, 2010


(gorgeous photo courtesy of Adam Jackson)

WWDC 2010 is only a day away! Check out the sessions if you haven’t yet. There are a number of neat special events too. I’m looking forward to the lunchtime talk “Making Movies is Hard Fun — Building Tools for Telling Stories” by Michael Johnson of Pixar — I love those Pixar movies! I’ll be giving a talk on Friday, and staffing a couple of labs — if you’re planning on attending, be sure and stop by!

JavaFX Menus

May 13, 2010

I knew those JavaFX menus looked familiar!

So I was plodding along in my quest to learn Objective-C, and I ran into a compile error that I just didn’t understand. My code looked something like this:

switch(foo) {
   case bar:
        NSString *message = @"Hello there.";
}

which yielded a compiler error like this:

unexpected interface name 'NSString': expected expression
             NSString *message = @Hello there.";

To a Java programmer, there doesn’t look to be anything wrong. But Objective-C, birthed from C, is a different beast. My first inclination was to fire up Google, and pose the first question that came to my mind:



And here were the results I was given:

The first result was the same question I was asking, with an answer on Stack Overflow!

Stack Overflow went into public beta in September of 2008 (I got the answer for this question by asking “how old is stackoverflow” on Meta Stack Overflow) — that’s not very long ago. What a game changer (not to mention a time saver) Stack Overflow has become.

1Password is indispensable

December 14, 2009

1Password is an absolutely indispensable tool. My family is currently in the process of moving, so we had to pack up all our paper work. I needed a way to securely store my account numbers, logins and serial numbers so that I would have access to that info during the move — I went searching for a tool. 1Password was by far the front runner in my search. It is the most polished of the apps I came across, it is super secure and they’ve just released a fresh 3.0 version.

1Password wouldn’t be of much use if you couldn’t access your info from any computer with zero configuration. Fortunately, they integrate perfectly with Dropbox, so setting up 1Password on all your computers to point to the same data is a cinch.

In the Getting Things Done ethos, 1Password will help you get your info out of your head and into a secure tool. You’ll never have to click an “I forgot my password” link again!

Heading to Apple

December 4, 2009

apple_plus_me
Big news! I’ve accepted a job at Apple and will be starting there on Monday, December 7th, 2009. This is an absolutely fantastic opportunity and I am very much looking forward to contributing to the Mac platform. I don’t yet know what this will mean for Mac Widgets for Java or this blog, but I’ll keep you posted.

Sea Glass version 0.1

December 4, 2009

Sea Glass version 0.1 is ready for you to try! This is a very preliminary version of the look and feel, and is missing artwork for tabs and component focused states. Also note that the artwork will likely change as we move forward based on your feedback.

Don’t expect rendering of components to be perfect in this release — text alignment, for example, might not be quite right. The goal with this very early preview is to get feedback on fundamental aspects of the look and feel (e.g. colors).

You can download seaglass-0.1.jar or check out the Google Code Download page.

You can download the demo (based on Laffy) here.

JavaOne 2010?

December 3, 2009

Anyone care to venture a guess as to whether there will be a JavaOne 2010? I know it’s pointless to speculate, but it sure is fun! Here are some facts to inform your guess:

Conference Year Call for Papers Conference Dates
2008 Oct. 26th – Nov. 16th, 2007 May 6th – 9th, 2008
2009 Nov. 19th – Dec. 19th, 2008 June 2nd – 5th, 2009
2010 ? June 22nd – 25th, 2010

According to the Moscone Center’s website, JavaOne 2010 has been scheduled for June 22nd – 25th, 2010. There hasn’t been a call for papers yet, but maybe that’s because the conference is a little later than usual.

What do you think?


As promised, here is the code to create the iTunes navigation header button. It’s not a perfect replica, but it’s as close as is practical.

iTunes uses hand drawn artwork, which is not easy to replicate in code. The inner shadows, for example, are simulated in the my code and look decent, but are not a perfect facsimile of the original. These subtle details are almost invisible when you look at the component, but without these details, the component looks cheap and amateurish.

Some highlights of what’s going on in the code:

  • Inner shadow simulation on the left, top and bottom sides of the selected button.
  • Upward pointing shadow under the text.

See the comments in the code provide further explanation of these items. To actually create the iTunes navigation header component, you can adapt the code from my last post, with TriAreaComponent.

public class ITunesHeaderButtonUI extends BasicButtonUI {

    private static Color TEXT_COLOR = Color.WHITE;
    private static Color TEXT_SHADOW_COLOR = Color.BLACK;

    // the gradient colors for when the button is selected.
    private static Color SELECTED_BACKGROUND_COLOR_1 = new Color(0x141414);
    private static Color SELECTED_BACKGROUND_COLOR_2 = new Color(0x1e1e1e);
    private static Color SELECTED_BACKGROUND_COLOR_3 = new Color(0x191919);
    private static Color SELECTED_BACKGROUND_COLOR_4 = new Color(0x1e1e1e);

    // the border colors for the button.
    private static Color SELECTED_TOP_BORDER = new Color(0x030303);
    private static Color SELECTED_BOTTOM_BORDER = new Color(0x292929);

    // the border colors between buttons.
    private static Color LEFT_BORDER = new Color(255,255,255,21);
    private static Color RIGHT_BORDER = new Color(0,0,0,125);

    private static final Color SELECTED_INNER_SHADOW_COLOR_1 = new Color(0x161616);
    private static final Color SELECTED_INNER_SHADOW_COLOR_2 = new Color(0x171717);
    private static final Color SELECTED_INNER_SHADOW_COLOR_3 = new Color(0x191919);

    @Override
    protected void installDefaults(AbstractButton button) {
        super.installDefaults(button);
        button.setBackground(new Color(0,0,0,0));
        button.setOpaque(false);
    }

    @Override
    public void paint(Graphics g, JComponent c) {
        // if the button is selected, paint the special background now.
        // if it is not selected paint the left and right highlight border.
        AbstractButton button = (AbstractButton) c;
        if (button.isSelected()) {
            paintButtonSelected(g, button);
        } else {
            // paint the border and border highlight if the button isn't
            // selected.
            g.setColor(LEFT_BORDER);
            g.drawLine(0, 1, 0, button.getHeight()-2);
            g.setColor(RIGHT_BORDER);
            g.drawLine(button.getWidth()-1, 1,
                    button.getWidth()-1, button.getHeight()-2);
        }

        super.paint(g, c);
    }

    @Override
    protected void paintText(Graphics g, AbstractButton button,
                             Rectangle textRect, String text) {
        // we need to override the paintText method so that we can paint
        // the text shadow. the paintText method in BasicButtonUI pulls
        // the color to use from the foreground property -- there is no
        // way to change this during the painting process without causing
        // an infinite sequence of events, so we must implement our own 
        // text painting.

        FontMetrics fontMetrics = g.getFontMetrics(button.getFont());
        int mnemonicIndex = button.getDisplayedMnemonicIndex();

        // paint the shadow text.
        g.setColor(TEXT_SHADOW_COLOR);
        BasicGraphicsUtils.drawStringUnderlineCharAt(g, text, mnemonicIndex,
                textRect.x + getTextShiftOffset(),
                textRect.y + fontMetrics.getAscent() + getTextShiftOffset() - 1);

        // paint the actual text.
        g.setColor(TEXT_COLOR);
        BasicGraphicsUtils.drawStringUnderlineCharAt(g, text, mnemonicIndex,
                textRect.x + getTextShiftOffset(),
                textRect.y + fontMetrics.getAscent() + getTextShiftOffset());
    }

    /**
     * Paints the selected buttons state, also used as the pressed state.
     */
    private void paintButtonSelected(Graphics graphics, AbstractButton button) {
        // calculate the middle of the area to paint.
        int midY = button.getHeight()/2;

        Paint topPaint = new GradientPaint(0, 0, SELECTED_BACKGROUND_COLOR_1,
                0, midY, SELECTED_BACKGROUND_COLOR_2);
        ((Graphics2D) graphics).setPaint(topPaint);
        graphics.fillRect(0, 0, button.getWidth(), midY);

        // paint the top half of the background with the corresponding
        // gradient.
        Paint bottomPaint =
                new GradientPaint(0, midY + 1, SELECTED_BACKGROUND_COLOR_3,
                        0, button.getHeight(), SELECTED_BACKGROUND_COLOR_4);
        ((Graphics2D) graphics).setPaint(bottomPaint);
        graphics.fillRect(0, midY, button.getWidth(), button.getHeight());

        // draw the top and bottom border.
        graphics.setColor(SELECTED_TOP_BORDER);
        graphics.drawLine(0, 0, button.getWidth(), 0);
        graphics.setColor(SELECTED_BOTTOM_BORDER);
        graphics.drawLine(0, button.getHeight() - 1,
                button.getWidth(), button.getHeight() - 1);

        // paint the outter part of the inner shadow.
        graphics.setColor(SELECTED_INNER_SHADOW_COLOR_1);
        graphics.drawLine(0, 1, 0, button.getHeight()-2);
        graphics.drawLine(0, 1, button.getWidth(), 1);
        graphics.drawLine(button.getWidth()-1, 1,
                button.getWidth()-1, button.getHeight()-2);

        // paint the middle part of the inner shadow.
        graphics.setColor(SELECTED_INNER_SHADOW_COLOR_2);
        graphics.drawLine(1, 1, 1, button.getHeight()-2);
        graphics.drawLine(0, 2, button.getWidth(), 2);
        graphics.drawLine(button.getWidth()-2, 1,
                button.getWidth()-2, button.getHeight()-2);

        // paint the inner part of the inner shadow.
        graphics.setColor(SELECTED_INNER_SHADOW_COLOR_3);
        graphics.drawLine(2, 1, 2, button.getHeight()-2);
        graphics.drawLine(0, 3, button.getWidth(), 3);
        graphics.drawLine(button.getWidth()-3, 1,
                button.getWidth()-3, button.getHeight()-2);
    }

    @Override
    protected void paintButtonPressed(Graphics graphics, AbstractButton button) {
        paintButtonSelected(graphics, button);
    }
}