Tuesday, June 24, 2008

Metawidget and Tooltips

Update: the APIs shown in this blog entry have changed slightly in newer releases of Metawidget. Specifically you should now use a WidgetProcessor to call .setToolTipText. Please download the latest documentation from http://metawidget.org

I was recently asked whether Metawidget could 'add an annotation for field/methods to describe a field in the UI with a tooltip'. Here's how to add that:

Step 1: Write the Annotation

You can basically copy @UiLabel:

package com.myapp;

import java.lang.annotation.*;

@Retention( RetentionPolicy.RUNTIME )
@Target( { ElementType.FIELD, ElementType.METHOD } )
public @interface UiDescription {
   String value();

Step 2: Write an Inspector for the Annotation

Most of the work is done for you by BasePropertyInspector:

package com.myapp;

import java.util.*;
import org.metawidget.inspector.impl.*;
import org.metawidget.util.*;

public class DescriptionInspector extends BasePropertyInspector {
   public DescriptionInspector() {
      this( new BasePropertyInspectorConfig() );

   public DescriptionInspector( BasePropertyInspectorConfig config ) {
      super( config );

   protected Map<String, String> inspect( Property property, Object toInspect )
      throws Exception {
      Map<String, String> attributes = CollectionUtils.newHashMap();
      UiDescription description = property.getAnnotation( UiDescription.class );

      if ( description != null )
         attributes.put( "description", description.value() );

      return attributes;

Step 3: Extend the Metawidget to understand the Inspector

For this example, I'll extend SwingMetawidget:

package com.myapp;

import java.util.Map;
import javax.swing.JComponent;
import org.metawidget.swing.SwingMetawidget;

public class DescriptiveSwingMetawidget extends SwingMetawidget {
   protected JComponent buildActiveWidget( Map attributes )
      throws Exception {
      JComponent component = super.buildActiveWidget( attributes );

      if ( component == null )
         return null;

      component.setToolTipText( attributes.get( "description" ));
      return component;

Step 4: Annotate the Business Object

We're done! To test it, first annotate the business object. We'll reuse the business object from the Metawidget tutorial:

package com.myapp;

public class Person {
   @UiDescription( "The name of the person" )
   public String name;

   @UiDescription( "The age of the person" )
   public int age;

   @UiDescription( "Whether the person is retired" )
   public boolean retired;

Step 5: Use the Business Object

Now use the business object. Again, we'll reuse the Main class from the Metawidget tutorial:

package com.myapp;

import javax.swing.JFrame;

import org.metawidget.inspector.composite.*;
import org.metawidget.inspector.propertytype.*;

public class Main {
   public static void main( String[] args ) {
      DescriptiveSwingMetawidget metawidget = new DescriptiveSwingMetawidget();
      CompositeInspectorConfig config = new CompositeInspectorConfig();
      config.setInspectors( new PropertyTypeInspector(), new DescriptionInspector() );
      metawidget.setInspector( new CompositeInspector( config ) );
      metawidget.setToInspect( new Person() );

      JFrame frame = new JFrame();
      frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
      frame.getContentPane().add( metawidget );
      frame.setSize( 400, 210 );
      frame.setVisible( true );

Run it! You'll see the usual Swing tutorial app, but this time with tooltips on all the fields.

Of course, this simple example has its problems. Most notably, you'd probably want some localization on the descriptions. Both @UiLabel and @UiSection do this, but that's for another blog!