Jan 8, 2008 12:00 AM PT

Open source Java projects: Balloontip for Java

Add balloon tips to your Swing GUIs

You can save time and reduce other costs associated with Java development by taking advantage of open source Java projects. In this new series, Jeff Friesen introduces a variety of lesser known open source Java projects that can benefit your Java software. Jeff begins the series by looking at Bernhard Pauler's balloontip project, which you can use to add Windows XP-style informational "balloons" to your Swing-based Java applications.

Open source licenses
Open source software is usually subject to some form of license, which determines how the software can be used. The balloontip project is released under the GNU Lesser General Public License (LGPL). See this article's Resources section to learn more about the LGPL.

A balloon tip is a small window that presents an informational message to remind or otherwise notify application users of significant events. Its name is derived from the fact that balloon tips have a similar appearance to the speech balloons found in comic strips.

Windows XP supports balloon tips in the context of its taskbar notification area and this area's icons. You can see this in Figure 1, where XP displays a balloon tip above and partly over a notification area icon after something significant has happened.

The balloon tip in Figure 1 is positioned in such a way as to identify its associated notification area icon. The figure also reveals a Close button to close the balloon tip window, and a small "i" icon, which classifies the balloon tip as informational.

Figure 1. This balloon tip identifies a new high-speed Internet connection

Bernhard Pauler's open source balloontip project introduces XP-like balloon tips to Swing GUIs. In this first article in the new "Open source Java projects" series, I'll show you how to obtain and install the balloontip software. I'll also walk you through balloontip's example application, explore the balloontip API, and explain where balloon tips are useful in Swing-based Java development.

Obtain and install balloontip

The balloontip project's Balloon Tips for Java page, hosted on Java.net,

A shorter path home
On my XP platform, C:\unzipped\BalloonTip_2007-05-15\BalloonTip was the initial path to the BalloonTip home directory. Because I like to work at the command line, I found it tedious to navigate this path. To remedy this situation, I moved the home directory to my C:\jp (Java Projects) directory, resulting in C:\jp\BalloonTip as a simpler path. If you also like to work at the command line, you might want to do something similar on your platform.

introduces balloontip. Among other things, you'll find a link to the latest version of the software on this page.

At the time of writing this article, the latest version available is BalloonTip_2007-05-15.zip. After downloading and unzipping this archive, I discovered a BalloonTip_2007-05-15 directory containing BalloonTip as the home directory.

The BalloonTip home directory contains classes and src subdirectories. It also contains README.txt, which briefly describes how to run the BalloonTipTestDrive example application. The complete directory structure is shown in Listing 1.

Listing 1. Balloontip project directory structure

BalloonTip
  classes
    net
      java
        balloontip
          examples
            BalloonTipTestDrive$1.class
            BalloonTipTestDrive$2.class
            BalloonTipTestDrive$3.class
            BalloonTipTestDrive.class
          images
            closebutton_default.png
            closebutton_pressed.png
            closebutton_rollover.png
            frameicon.png
            infoicon.gif
          utils
            WindowUtils.class
          BalloonTip$1.class
          BalloonTip$2.class
          BalloonTip$3.class
          BalloonTip.class
          EdgedBalloonBorder.class
          RoundedBalloonBorder.class
  src
    net
      java
        balloontip
          examples
            BalloonTipTestDrive.java
          images
            closebutton_default.png
            closebutton_pressed.png
            closebutton_rollover.png
            frameicon.png
            infoicon.gif
          utils
            WindowUtils.java
          BalloonTip.java
          EdgedBalloonBorder.java
          RoundedBalloonBorder.java
  README.txt

The classes hierarchy provides Java SE 6-compiled code for the net.java.balloontip, net.java.balloontip.examples, and net.java.balloontip.utils packages. Only the former package and the three closebutton PNG images constitute the balloontip API.

Make a JAR file
Follow these steps to create a JAR file if you find it more convenient to work with a JAR file than a classes hierarchy of class files:
  1. Make sure that the classes directory is the current directory.
  2. Invoke jar cf balloontip.jar net to create the JAR file.
This command creates a balloontip.jar JAR file containing the entire directory structure located beneath the classes directory.

The src hierarchy provides all source code and a duplicate of the various image files. The source code for the balloontip API consists of BalloonTip.java, EdgedBalloonBorder.java, and RoundedBalloonBorder.java.

After the archive is unzipped (and BalloonTip possibly moved to another location), complete the installation by pointing the CLASSPATH environment variable to the classes directory (unless you create a JAR file that you specify via the java command's -cp option).

Test drive balloon tips

The BalloonTipTestDrive example application demonstrates balloon tips associated with a text field component. You should download it now if you haven't already done so. Once you've downloaded the test drive package, specify java net.java.balloontip.examples.BalloonTipTestDrive to run the program. A GUI like the one shown in Figure 2 appears.

Figure 2. Specify exactly five characters to see the balloon tip

The GUI presents labels, two radio buttons for determining the balloon tip's border (rounded or edged -- the rounded-border version is shown in Figure 2), and a text field. It also presents a balloon tip when you enter exactly five characters in the text field.

As you experiment with BalloonTipTestDrive, you will notice that the balloon tip disappears when you enter more than five characters in the text field. Backspacing to five characters causes the balloon tip to reappear.

The balloon tip also disappears when the text field loses focus. Focus is lost when you select a radio button or the balloon tip's Close button. Focus is also lost when you minimize and restore the application (at least under XP) or select another running application's main window.

Explore the balloontip API

Use balloontip's borders with other Swing components
Because RoundedBalloonBorder and EdgedBalloonBorder implement the javax.swing.border.Border interface and are marked public, you can use these classes to establish borders (via JComponent's public void setBorder(Border border) method) for other Swing components.

A balloon tip is an instance of the net.java.balloontip.BalloonTip class. This class subclasses JPanel, uses a JLabel component to render balloon tip contents, and uses a JButton component (whose action listener hides the balloon tip) as the Close button.

BalloonTip internally works with two other classes located in the same package: RoundedBalloonBorder and EdgedBalloonBorder. These classes are used to establish a border for the balloon tip.

Methods for creating balloon tip components

The BalloonTip class presents a small API consisting of six methods. Two of these methods are factory methods for creating balloon tip components. The factory method for creating a rounded balloon tip has the signature shown in Listing 2.

Listing 2. createRoundedBalloonTip(...)

public static BalloonTip createRoundedBalloonTip(Component attachedComponent, 
                                                 Color borderColor, 
                                                 Color fillColor,
                                                 int borderWidth, 
                                                 int horizontalOffset, 
                                                 int verticalOffset,
                                                 int arcWidth, 
                                                 int arcHeight, 
                                                 boolean useCloseButton)

The attachedComponent parameter identifies the component to which the balloon tip is associated. Whenever the balloon tip is made visible, it will appear in close proximity to the component, as previously illustrated in Figure 2.

Watch out for NullPointerExceptions!
A NullPointerException is thrown if attachedComponent does not have a JDialog, JFrame, or JInternalFrame ancestor -- each factory method calls an internal method that adds the balloon tip to JDialog's JFrame's, or JInternalFrame's layered pane.

The borderColor and fillColor parameters identify the colors used to paint the border and interior of the balloon tip. The arguments passed to these parameters are forwarded to the RoundedBalloonBorder and EdgedBalloonBorder classes, which take care of painting.

The next five parameters identify the number of pixels between the border and the balloon tip contents, the length and location of the tip's triangular anchor (which points to the associated component), and the size of the border's rounded corners. Figure 3 clarifies these parameters (image courtesy of Bernhard Pauler).

Figure 3. Rounded balloon tip layout (click for a larger image)

The final useCloseButton parameter determines whether or not a close button appears on the balloon tip. Pass true to this parameter to display this button. If the button is present, clicking it hides the balloon tip.

The second factory method is somewhat simpler than the first one. Because this latter method is responsible for creating balloon tips without rounded corners, it does not require arcWidth and arcHeight parameters. Its signature is shown in Listing 3.

Listing 3. createEdgedBalloonTip(...)

public static BalloonTip createEdgedBalloonTip(Component attachedComponent, 
                                               Color borderColor, 
                                               Color fillColor,
                                               int borderWidth, 
                                               int horizontalOffset, 
                                               int verticalOffset, 
                                               boolean useCloseButton)

This method's parameters are equivalent to the same-named parameters in the first factory method. The only difference between these factory methods is that the latter method creates a narrower border with square corners. This alternative border's layout is shown in Figure 4 (image courtesy of Bernhard Pauler).

Figure 4. Edged balloon tip layout (click for a larger image)

The following excerpt from BalloonTipTestDrive.java shows how the example application uses these factory methods to create its rounded and edged balloon tips -- a factory method is invoked when its associated radio button is selected.

Listing 4. Creating rounded and edged balloon tips

BalloonTip balloonTip;

// ...

if (buttonForRoundedLook.isSelected()) {
    balloonTip = BalloonTip.createRoundedBalloonTip(textField, Color.BLACK, new Color(255, 255, 225), 
                                                    10, 15, 20, 7, 7, true);
} 
else 
if (buttonForEdgedLook.isSelected()) {
    balloonTip = BalloonTip.createEdgedBalloonTip(textField, Color.BLACK, new Color(255, 255, 225), 
                                                  10, 15, 20, true);
}

Methods for configuring balloon tip components

Three of the remaining BalloonTip methods configure the balloon tip in terms of the underlying label's text, icon, and icon-text gap. These methods are described below:

  • public void setText(String text) specifies the balloon tip's text. Because this method invokes JLabel's setText() method, HTML can be used.
  • public void setIcon(Icon icon) specifies the balloon tip's icon. This method invokes JLabel's setIcon() method.
  • public void setIconTextGap(int iconTextGap) specifies how many pixels separate the icon from the text. This method invokes JLabel's setIconTextGap() method.

The BalloonTipTestDrive.java excerpt below uses setText(), setIcon(), and setIconTextGap() to configure a balloon tip with Nice! You inserted 5 characters! as its text, infoicon.gif as its icon, and 10 as its icon-text gap:

balloonTip.setText("Nice! You inserted 5 characters!");
balloonTip.setIcon(new ImageIcon(BalloonTipTestDrive.class.getResource
  ("/net/java/balloontip/images/infoicon.gif")));
balloonTip.setIconTextGap(10);

The final BalloonTip method, which is described below, determines the balloon tip's visibility and (if the balloon tip is about to be displayed) its above and left-aligned position relative to its attached component:

  • public void setVisible(boolean show) shows or hides the balloon tip. If true is passed, the balloon tip's position is established prior to being made visible.

This method is demonstrated by two BalloonTipTestDrive.java excerpts. The first excerpt installs a key listener on the text field to determine whether to show or hide a balloon tip on each key release. The balloon tip will only be shown if exactly five characters are present in the text field.

Listing 5. textField.addKeyListener(new KeyAdapter()

textField.addKeyListener(new KeyAdapter() {
  public void keyReleased(KeyEvent e) {
     if (textField.getText().length() == 5) {
         balloonTip.setVisible(true);
     } else {
         balloonTip.setVisible(false);
     }
  }
});

The second excerpt installs a focus listener on the text field to hide the balloon tip when this component loses the focus. A side effect of this listener is that it prevents the action listener of the balloon tip's Close button from being invoked to hide the balloon tip.

Listing 6. textField.addFocusListener(new FocusAdapter()

textField.addFocusListener(new FocusAdapter() {
  public void focusLost(FocusEvent e) {
     balloonTip.setVisible(false);
  }
});

When are balloon tips useful?

Suppose your GUI prevents users from shifting focus away from the current text field until an appropriate value is entered. To avoid user confusion when the focus shift fails, the GUI can show a balloon tip reminding the user to enter an appropriate value. Check out Figure 5 for an example.

Figure 5. Balloon tips are great for reminding the user to enter correct input

Figure 5 reveals the user having entered 0 into the top text field, and having attempted to switch focus to the bottom text field. Because 0 is not an appropriate value, the focus shift is prevented and a balloon tip is shown to remind the user to enter something valid.

The GUI in Figure 5 is created by VerifyAge1 -- see this article's code archive (in the Resources section) for the application's source code. The source code excerpt in Listing 7 shows how VerifyAge1 uses the balloontip API to integrate a balloon tip into its GUI.

Listing 7. integrating a balloon tip into a GUI

// Create the upper half of the GUI.

pnl = new JPanel (new FlowLayout (FlowLayout.LEFT));
pnl.setBorder (BorderFactory.createEmptyBorder (75, 0, 0, 0));
pnl.add (new JLabel ("Enter your age (1 - 130):"));
final JTextField txtAgeIn = new JTextField (10);
pnl.add (txtAgeIn);

txtAgeIn.addFocusListener (new FocusAdapter ()
                           {
                               public void focusLost (FocusEvent fe)
                               {
                                  // Copy age from upper text field to
                                  // lower text field when upper text
                                  // field loses the focus.

                                  txtAgeOut.setText (txtAgeIn.getText ());
                               }
                           });

// txtAgeIn's input verifier prevents focus from shifting away from this
// text field until the verify() method returns true.

txtAgeIn.setInputVerifier (new InputVerifier ()
                           {
                               public boolean verify (JComponent com)
                               {
                                  JTextField txt = (JTextField) com;
                                  try
                                  {
                                     // Parse digit characters from text
                                     // field into an integer.

                                     int value =
                                     Integer.parseInt (txt.getText ());

                                     // Integer out of range.

                                     if (value <= 0 || value > 130)
                                     {  
                                         bt.setVisible (true);
                                         return false;
                                     }
                                  }
                                  catch (NumberFormatException nfe)
                                  {     
                                     // Non-digit characters detected.

                                     bt.setVisible (true);
                                     return false;
                                  }

                                  // Input is valid. It's okay to yield
                                  // focus.
                                          
                                  bt.setVisible (false);
                                  return true;                                 
                               }
                           });

getContentPane ().add (pnl, BorderLayout.NORTH);

bt = BalloonTip.createRoundedBalloonTip (txtAgeIn, Color.BLACK,
                                         new Color (255, 255, 225), 10,
                                         15, 20, 7, 7, false);
bt.setText ("Enter 1 - 130");
bt.setVisible (false);

This code fragment creates the GUI's top text field and its associated balloon tip. An input verifier is attached to the text field to prevent a focus shift when the verifier's public boolean verify(JComponent com) method returns false.

Problems and solutions

The code fragment in Listing 7 reveals two interesting things about balloon tips:

  • Because BalloonTip adds a balloon tip to a JFrame's, JDialog's, or JInternalFrame's layered pane, it requires sufficient screen space to avoid being clipped. This is the reason for pnl.setBorder (BorderFactory.createEmptyBorder (75, 0, 0, 0));.
  • When a balloon tip is added to a layered pane, it is immediately shown when the GUI is made visible. Because this is not appropriate when the GUI is first revealed, the balloon tip is hidden via the final bt.setVisible (false); call before the GUI is made visible.

The need to reserve extra GUI space so that a balloon tip is not clipped against its containing layered pane is problematic. Although you could address this problem by placing a logo and/or help instructions in the extra space, Figure 6 reveals a better solution.

Figure 6. Using an external layered pane protects your balloon tip from being clipped.

Figure 6 reveals the output from a VerifyAge2 application. This application presents a balloon tip via an undecorated dialog box that is dynamically positioned over the text field. To see how this works, check out the VerifyAge2.java source code in this article's code archive.

What is an undecorated dialog box?
An undecorated dialog box has no title bar or border. The same is true for any undecorated window.

Figure 6 also reveals a second problem associated with balloon tips: part of the application's main window is hidden by the dialog. This lack of transparency is due to Java not supporting transparent windows (at least on XP). Fortunately, this problem can be easily fixed: the next article in this series shows how.

Note: Balloon tips and the system tray

You might be wondering if you can use the balloontip API with the System Tray API to display balloon tips in the vicinity of tray icons (see Figure 1). For this to happen, three problems need to be solved:

  • Java does not support transparent windows on platforms such as Windows XP -- you saw the result of this lack of support in Figure 6.
  • The balloontip API currently orients balloon tips to the right. Because platforms such as Windows XP place the system tray on the right side of the task bar, orienting a balloon tip to the right could result in part of the tip being clipped.
  • The System Tray API does not expose tray icon positions -- it does not even expose components that could be interrogated to obtain positions.

In the next article in this series, I'll introduce a solution to the first problem. Solving the second problem involves making changes to the balloontip API so that balloon tips are left-oriented. I have yet to find a solution to the third problem.

In conclusion

The balloontip project suffers from a couple of weaknesses. One weakness is the lack of documentation. Although one might argue that a six-method API does not need to be documented, I believe that good Javadoc would make the balloontip API easier to use -- you would not necessarily have to explore the source code to learn how to use this API.

Another weakness is the API's incompleteness. Although the balloontip API provides setter methods for establishing a balloon tip's text, icon, and icon-text gap, there are no equivalent getter methods that return these items. Methods to get/set the attached component (and other factory method arguments) might also be useful.

Overall, the expression "Good things come in small packages" is applicable to balloontip. This project's simplicity makes it fairly easy to enhance a Swing GUI to assist the user, as evidenced by the VerifyAge1 and VerifyAge2 applications. Also, the LGPL license allows commercial code to benefit from this project. Consider using balloontip for your next Java project involving a Swing GUI!

Addendum: A new version of balloontip

Shortly after completing this article, I discovered a new version of balloontip. This version is bundled in the BalloonTip_2007-12-30.zip archive and introduces the following new features:

  • A balloon tip can be displayed above or below and on either side of the component to which it is attached by using BalloonTip's new Alignment enumeration and enhanced createRoundedBalloonTip() and createEdgedBalloonTip() factory methods.
  • The location of the balloon tip's triangular anchor can be moved to the center or a corner of the attached component by using BalloonTip's new TriangleTipLocation enumeration and public void setTriangleTipLocation(TriangleTipLocation triangleTipLocation) method.
  • The width of the empty border surrounding the balloon tip's close button can be specified by using BalloonTip's new public void setCloseButtonBorderWidth(int top, int left, int bottom, int right) method. By default, an edged balloon tip leaves a one-pixel border around its close button.
  • The balloontip API's class files are bundled in a balloontip_2007-12-30.jar file. Furthermore, the updated BalloonTipTestDrive example application's class files are also bundled in this JAR file. You can easily run the example via java -jar balloontip_2007-12-30.jar.

Because BalloonTip uses the enumeration language feature introduced by J2SE 5.0, it can no longer be used with earlier versions of Java. If this is a problem, you need to change the source code.

Jeff Friesen is a freelance software developer and educator who specializes in Java technology. Check out his javajeff.mb.ca website to discover all of his published Java articles and more.

Learn more about this topic