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:

somatik said...

tha webstart is broken:

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

technomage said...

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

Anthony Perritano said...

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

technomage said...

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

GBUK said...

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

Anonymous said...

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.

technomage said...

Fixed the signing.

technomage said...

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

Anonymous said...

Is not working on linux, why ?

technomage said...

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?

Anonymous said...

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

technomage said...

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.

technomage said...

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

Kris said...

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

technomage said...

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

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

Kris said...

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.

technomage said...

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.

waqmax said...

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

waqmax said...

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

technomage said...

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

technomage said...

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