Wednesday, March 28, 2007

Give your application a speech bubble

As a result of an argument about the best way to present meta-information about complex application objects presented to the user, I had to come up with some concrete alternatives.

The problem is that for a given item in the application (in this case an SVN-based revision control client that aggregates and presents large amounts of meta-information about the repository), there may exist information that just doesn't fit in the display. You want to provide the user with a route to the desired information with as few inputs as possible.

Tooltips is one alternative, but aren't always appropriate. While you can set (globally) the delay before appearance and the time before dismissal, dismissing them is implicit. In my application, I need the meta information to be displayed or hidden explicitly, so that rules out tooltips (at least without substantial modification of the tooltip manager).

A dedicated "inspector" window is another alternative, but kind of heavyweight. We definitely don't want a component that can be activated, since that introduces focus issues. I also didn't want to make it appear to the user that there was a generic "container" that kept resizing and moving, as if it were a tool palette that couldn't decide on its purpose. At a minimum the window has to be undecorated and pretty much behave like a tooltip.

What I really wanted was something in between the two. A simple Popup with a unique appearance which could be shown or hidden at will. Apple introduced something like this as Balloon Help years ago, although I think the main problem was that they didn't get the trigger quite right. One reason a lot of expert users don't like tooltips (or balloon help) is that they show up when you don't expect it and you have to wave the mouse to make them go away (usually causing another one to be triggered).

Google maps does a nice job with their balloon tips. The trigger is obvious, and the tip itself provides a close button (and now I notice a maximize button as well).

So I started out with Popups via PopupFactory and wound up with another demonstration of non-rectangular clipping, brought to you by the JNA project. The API is pretty simple:


Component myComponent;
Component myBubble;
Point where; // location relative to myComponent
final Popup popup = BalloonManager.getBalloon(myComponent, myBubble, where.x, where.y);
popup.show();
// Hide the popup on mouse clicks
myBubble.addMouseListener(new MouseAdapter() {
public void mouseClicked(MouseEvent e) {
popup.hide();
}
});


The balloon is a simple rounded rectangle with a triangle stuck to the bottom, with the corresponding Area used as the window mask.

I originally tried using PopupFactory directly, but it doesn't provide enough control over the intermediate components (the ancestors of the content that you don't really see; there's a pesky medium-weight popup that uses java.awt.Panel, which can't be easily masked). This implementation uses a window for any popup, regardless of whether its fully contained within the parent component.

Drop shadows would be nice (maybe someone wants to contribute some compositing code?).




21 comments:

  1. tha webstart is broken:

    File does not exist: /home/groups/a/ab/abbot/htdocs/demo/ButtonManagerDemo.jnlp

    ReplyDelete
  2. Thanks, it's fixed now. I dyslexically typed "Button" where I meant "Balloon".

    ReplyDelete
  3. i get an error trying to launch it:

    An error occurred while launching/running the application.

    Title: Balloon Manager Demo
    Vendor: Technomage
    Category: Download Error

    Unable to load resource: http://abbot.sf.net/demo/darwin-i386.jar


    is this jdk 1.6 only. i am on a mac

    ReplyDelete
  4. This will work on jdk1.4+. darwin-i386.jar was missing but it's there now.

    ReplyDelete
  5. I have a nice error: JAR resources in JNLP file are not signed by same certificate

    ReplyDelete
  6. Anonymous1:36 PM

    Sigh... unable to launch.

    Java Web Start - Launch File error
    Unable to launch Balloon Manager Demo.

    An error occurred while launching/running the application.

    Title: Balloon manager Demo
    Vendor: Technomage
    Category: Launch File Error

    JAR resources in JNLP file are not signed by same certificate.

    ReplyDelete
  7. BTW, there's a newer link hosted on the JNA site, see the subsequent post.

    ReplyDelete
  8. Anonymous11:10 AM

    Is not working on linux, why ?

    ReplyDelete
  9. What exactly "doesn't work"? Does the app fail to launch, or fail to show bubbles, or show bubbles incorrectly?

    What linux distro are you using? What is the output of the "xdpyinfo" command? Where are libX11.so and libXext.so on your system?

    ReplyDelete
  10. Anonymous7:17 PM

    Sorry for my lack of information.

    Problem:
    - Launching application: OK
    - Showing ballons: FAIL without exceptions (ballons are not showed).

    I'm using FC7 with:
    - kernel: 2.6.19-1.2911.6.5.fc6
    - xorg version: 7.1.1
    - Java 6 update 1
    - libX11.so and libXext.so exists

    xdpyinfo output:
    name of display: :0.0
    version number: 11.0
    vendor string: The X.Org Foundation
    vendor release number: 70101000
    X.Org version: 7.1.1
    maximum request size: 16777212 bytes
    motion buffer size: 256
    bitmap unit, bit order, padding: 32, LSBFirst, 32
    image byte order: LSBFirst
    number of supported pixmap formats: 7
    supported pixmap formats:
    depth 1, bits_per_pixel 1, scanline_pad 32
    depth 4, bits_per_pixel 8, scanline_pad 32
    depth 8, bits_per_pixel 8, scanline_pad 32
    depth 15, bits_per_pixel 16, scanline_pad 32
    depth 16, bits_per_pixel 16, scanline_pad 32
    depth 24, bits_per_pixel 32, scanline_pad 32
    depth 32, bits_per_pixel 32, scanline_pad 32
    keycode range: minimum 8, maximum 255
    focus: window 0x360001f, revert to Parent
    number of extensions: 31
    BIG-REQUESTS
    Composite
    DAMAGE
    DOUBLE-BUFFER
    DPMS
    Extended-Visual-Information
    GLX
    MIT-SCREEN-SAVER
    MIT-SHM
    MIT-SUNDRY-NONSTANDARD
    RANDR
    RECORD
    RENDER
    SECURITY
    SGI-GLX
    SHAPE
    SYNC
    TOG-CUP
    X-Resource
    XC-APPGROUP
    XC-MISC
    XFIXES
    XFree86-Bigfont
    XFree86-DGA
    XFree86-DRI
    XFree86-Misc
    XFree86-VidModeExtension
    XInputExtension
    XKEYBOARD
    XTEST
    XVideo
    default screen number: 0
    number of screens: 1

    screen #0:
    dimensions: 1280x800 pixels (332x212 millimeters)
    resolution: 98x96 dots per inch
    depths (7): 24, 1, 4, 8, 15, 16, 32
    root window id: 0x5d
    depth of root window: 24 planes
    number of colormaps: minimum 1, maximum 1
    default colormap: 0x20
    default number of colormap cells: 256
    preallocated pixels: black 0, white 16777215
    options: backing-store NO, save-unders NO
    largest cursor: 64x64
    current input event mask: 0xfa2033
    KeyPressMask KeyReleaseMask EnterWindowMask
    LeaveWindowMask ButtonMotionMask StructureNotifyMask
    SubstructureNotifyMask SubstructureRedirectMask FocusChangeMask
    PropertyChangeMask ColormapChangeMask
    number of visuals: 17
    default visual id: 0x23
    visual:
    visual id: 0x23
    class: TrueColor
    depth: 24 planes
    available colormap entries: 256 per subfield
    red, green, blue masks: 0xff0000, 0xff00, 0xff
    significant bits in color specification: 8 bits
    visual:
    visual id: 0x24
    class: TrueColor
    depth: 24 planes
    available colormap entries: 256 per subfield
    red, green, blue masks: 0xff0000, 0xff00, 0xff
    significant bits in color specification: 8 bits
    visual:
    visual id: 0x25
    class: TrueColor
    depth: 24 planes
    available colormap entries: 256 per subfield
    red, green, blue masks: 0xff0000, 0xff00, 0xff
    significant bits in color specification: 8 bits
    visual:
    visual id: 0x26
    class: TrueColor
    depth: 24 planes
    available colormap entries: 256 per subfield
    red, green, blue masks: 0xff0000, 0xff00, 0xff
    significant bits in color specification: 8 bits
    visual:
    visual id: 0x27
    class: TrueColor
    depth: 24 planes
    available colormap entries: 256 per subfield
    red, green, blue masks: 0xff0000, 0xff00, 0xff
    significant bits in color specification: 8 bits
    visual:
    visual id: 0x28
    class: TrueColor
    depth: 24 planes
    available colormap entries: 256 per subfield
    red, green, blue masks: 0xff0000, 0xff00, 0xff
    significant bits in color specification: 8 bits
    visual:
    visual id: 0x29
    class: TrueColor
    depth: 24 planes
    available colormap entries: 256 per subfield
    red, green, blue masks: 0xff0000, 0xff00, 0xff
    significant bits in color specification: 8 bits
    visual:
    visual id: 0x2a
    class: TrueColor
    depth: 24 planes
    available colormap entries: 256 per subfield
    red, green, blue masks: 0xff0000, 0xff00, 0xff
    significant bits in color specification: 8 bits
    visual:
    visual id: 0x2b
    class: DirectColor
    depth: 24 planes
    available colormap entries: 256 per subfield
    red, green, blue masks: 0xff0000, 0xff00, 0xff
    significant bits in color specification: 8 bits
    visual:
    visual id: 0x2c
    class: DirectColor
    depth: 24 planes
    available colormap entries: 256 per subfield
    red, green, blue masks: 0xff0000, 0xff00, 0xff
    significant bits in color specification: 8 bits
    visual:
    visual id: 0x2d
    class: DirectColor
    depth: 24 planes
    available colormap entries: 256 per subfield
    red, green, blue masks: 0xff0000, 0xff00, 0xff
    significant bits in color specification: 8 bits
    visual:
    visual id: 0x2e
    class: DirectColor
    depth: 24 planes
    available colormap entries: 256 per subfield
    red, green, blue masks: 0xff0000, 0xff00, 0xff
    significant bits in color specification: 8 bits
    visual:
    visual id: 0x2f
    class: DirectColor
    depth: 24 planes
    available colormap entries: 256 per subfield
    red, green, blue masks: 0xff0000, 0xff00, 0xff
    significant bits in color specification: 8 bits
    visual:
    visual id: 0x30
    class: DirectColor
    depth: 24 planes
    available colormap entries: 256 per subfield
    red, green, blue masks: 0xff0000, 0xff00, 0xff
    significant bits in color specification: 8 bits
    visual:
    visual id: 0x31
    class: DirectColor
    depth: 24 planes
    available colormap entries: 256 per subfield
    red, green, blue masks: 0xff0000, 0xff00, 0xff
    significant bits in color specification: 8 bits
    visual:
    visual id: 0x32
    class: DirectColor
    depth: 24 planes
    available colormap entries: 256 per subfield
    red, green, blue masks: 0xff0000, 0xff00, 0xff
    significant bits in color specification: 8 bits
    visual:
    visual id: 0x5b
    class: TrueColor
    depth: 32 planes
    available colormap entries: 256 per subfield
    red, green, blue masks: 0xff0000, 0xff00, 0xff
    significant bits in color specification: 8 bits

    regards

    ReplyDelete
  11. You've got one 32-bit visual, so that's not the problem. Where are libX11.so and libXext.so in the filesystem?

    Does any extra output show up in the java console if you enable it? You can download the demo source from http://jna.dev.java.net if you want to run it from a terminal or from within an IDE.

    ReplyDelete
  12. The SF link probably doesn't support X11 anyway, it's an older post. The updated version is on the JNA site:

    https://jna.dev.java.net/demo/BalloonManagerDemo.jnlp

    ReplyDelete
  13. Hi,

    I've tried to make a speech bubble using a popup like you, but my Popup doesn't display the components properly if I've overwriiten the method paintComponent.

    Could you tell me if you've already got this problem and if you know what I should do?

    Thanks very much

    ReplyDelete
  14. @Kris: Are you using the JNA bubbles or your own implementation?

    Did you overload paintComponent in the popup or in the components within it?

    ReplyDelete
  15. Hi,

    Thanks for answering.
    I've got an instance of Popup, which contains a JEditorPane. I've overload the paintComponent method of this JEditorPane, trying to draw an ellipse.
    But I always see a white rectangle surrounding my ellipse.
    I think I should load some pictures instead of drawing in the paintComponent method.

    I've been using my own implementation so far, but I've just found the website of the balloon tip project. I'm going to look how it works.

    ReplyDelete
  16. You probably want to use setOpaque(false); the component's background is not part of paintComponent but performed in the paint method before painting children. Usually only JPanel is opaque by default, but it's possible JEditorPane is as well.

    A default white background fill would likely cause what you are seeing.

    ReplyDelete
  17. i tried your snippet but there the compiler flags an err, "cannot find symbol variable BalloonManager"... I believe i don't have the correct packages in place, can you please help me out, post a link to the necessary package... Thank you..

    ReplyDelete
  18. i tried your snippet but there the compiler flags an err, "cannot find symbol variable BalloonManager"... I believe i don't have the correct packages in place, can you please help me out, post a link to the necessary package... Thank you..

    ReplyDelete
  19. You can obtain the latest code from the contrib files in the JNA project (http://github.com/twall/jna).

    ReplyDelete
  20. The correct package is com.sun.jna.contrib.demo.BalloonTipManager for the latest code in JNA.

    ReplyDelete