Copyright © 2003 International Business Machines Corp.
 Eclipse Corner Article

 

Understanding Decorators in Eclipse

Summary
Decorators, as the name suggests, are used for adorning/annotating resources with useful information. Decorators can be used by plug-ins to convey more information about a resource and other objects displayed in different workbench views.  This article, with the help of a simple plug-in example, will illustrate the steps involved in decorating resources, along with some best practice approaches for decorating resources. Finally, we will discuss performance issues that may arise when enabling decorators, and briefly go over the new Lightweight decorators found in Eclipse 2.1.

We assume the reader already has a basic understanding of Eclipse and knows how to create simple plug-ins.

Balaji Krish-Sampath, IBM
January 16, 2003


What are Decorators?

Decorators are visual cues that convey useful state information associated with objects or resources displayed in Eclipse views. Many of the standard workbench views participate in showing decorations, for example, the navigator view, the package explorer view and the outline view. With decorators, users can get valuable information about the resources in a particular view. The following figure illustrates a simple custom decoration.


Fig. 1: Simple Decorator Example

In the Fig. 1, a lock icon is superimposed on the Java icon image () of the file ImageDecoration.java. A prefix and a suffix label are added for the file TextDecoration.java (). The file NoDecoration.java does not have any custom decoration ().

With the help of a simple example, this article will provide a step by step approach to decorating resources. Since the performance of the UI is affected by the speed with which decorations are performed, we will concentrate on the approaches that should be followed to make decoration efficient. Decoration can be performed on resources and other objects displayed in Eclipse views. Although the article illustrates most of the concepts with reference to decorating a resource, decorations are not limited to resources and same technique can be applied to decorate all the objects displayed in Eclipse views.
 

Decorators are Everywhere

To better understand decorators, it is important to be able to spot some of the basic decorations provided by Eclipse. The introductory sentences about decorators and figure shown above (Fig. 1) might have given you the impression that decorators are just the custom images annotating the resources with more valuable information. However, some of the basic images that users see, like the problem marker that is shown to alert the user about compilation errors is a good example of basic decoration provided by Eclipse.

Now that we know some of the basic decorations provided by Eclipse, let's revisit the package explorer view.


Fig. 2: Basic Decorations

The figure shown above clearly illustrates the wealth of information that Eclipse provides on resources and other objects contained in the workspace.  This ranges from information regarding the type of resource (a file or a folder or a project), type of file (a Java file or a text file) and various Java elements. In the figure shown above, a problem marker is superimposed on the compilation unit (), Java class element () and method (). The problem marker decoration on the compilation unit indicates that the compilation unit named ProblemMarkerDecoration.java has compilation errors. The problem marker decoration on the compilationErrorMethod() method () indicates the method failed to compile. By viewing the Package Explorer view with basic decorations provided by Eclipse, users can get details like reasons for compilation errors in a Java file.

Types of Decorations

The display of a resource in a view has two components, the label (the name of the resource) and an image denoting the type of resource and possibly other state information. Using the Eclipse extension mechanism, we can change the label of the resource and the image of the resource. Hence, we have two different types of decorations: As the name suggests, text label decorators are decorations on resource labels and image decorators are decorations on icon images. The resource name is the base label provided by Eclipse. This is important because this is the base value to be decorated and the decorator developer can assume that it will at least be part of the input.  Plug-in developers can augment the base resource label with additional information.

Let's revisit the Package Explorer view to understand more on text and label decorators.


Fig. 3: Image and Text Decorators

In the figure shown above, custom decorations are applied to the base decorations provided by Eclipse. The file NoDecoration.java does not contain any contain any decoration (). The file ImageDecoration.java has a lock icon (image decoration) superimposed on the Java icon image (). The files PrefixAdded.java and PrefixAndSuffixAdded.java have text decorations added to their labels (). The file ImageAndTextDecorations.java has both image decoration (a lock icon superimposed on the compilation unit) and text decorations (a prefix and a suffix added to base label) (). The CustomDecorationsWithProblemMarker.java has a problem marker decoration (basic decoration provided by Eclipse) as well as custom image and text decorations ()

So, how did these decorations get there? Let's start our quest for creating custom decorators.
 

Defining Decorators in plugin.xml

The first step in providing custom decoration is to contribute to the org.eclipse.ui.decorators extension point.
 
<extension point="org.eclipse.ui.decorators">
<decorator
    id="com.ibm.decoratordemo.ui.decorator.demodecorator"
    label="Decorator Example"
    state="false"
    class= "com.ibm.DecoratorDemo.ui.decorators.DemoDecorator"
  objectClass="org.eclipse.core.resources.IResource"
    adaptable="true">
    <description>
      This is an example to illustrate the use of decorators
    </description>
  </decorator>
</extension>
Fig. 4: Plug-in manifest info (plugin.xml)


Enabling / Disabling Custom Decorators

Individual decorators can be turned on and off from the Label Decorations preference page. Users can access this page by selecting Window->Preferences->Workbench->Label Decorations ().


Fig. 5: Label Decorations Preference Page

The name of the decorator is the value of the label attribute of the decorator extension (Fig. 4) while the description is the text contained in the description sub-element. In the figure shown above, the "Decorator Example" decorator is turned on (), while the other decorations are turned off. Although the above-mentioned scenario might be typical when the project is not shared with CVS repository, enabling and disabling the decorators are extremely useful when the decorations performed by two or more decorators conflict with each other. For example, the CVS plug-in might decorate the base image by superimposing the base image with a custom image while the "Decorate Example" plug-in might superimpose a different custom image at the same position thereby conflicting with the CVS plug-in decoration. If the decoration performed by two different decorators on the same resource conflict, users should appropriately enable / disable different decorators to get the required decoration.

It is very important to design custom decorators that don't conflict with basic decorations provided by different Eclipse views. For example, the package explorer view decorates Java files with problem markers (a problem marker is placed at the bottom left hand corner) if there are compilation errors. It is a bad practice to decorate resources with custom decoration exactly at the position of a problem marker and developers should avoid this. If the custom decoration is performed at the bottom left corner, then custom decoration and the problem marker decoration, if any, conflict each other and hence users will not be able to view the decorations. The solution to the above mentioned problem is to provide a custom image decoration at the bottom right corner which does not conflict with the basic image decoration provided by Eclipse. The top left corner is the second best place although it conflicts with the binary project decorator. The bottom left and top right should be avoided as they are decorated outside of the decorator mechanism.

Before we proceed further, let's take a closer look at the class that provides custom decoration. As mentioned earlier, the name of the class should be same as the value specified in the class attribute. The class must implement ILabelDecorator.
 
// Class extends LabelProvider because LabelProvider
// provides methods for getting images and text labels from objects 
public class DemoDecorator extends LabelProvider 
  implements ILabelDecorator
{
  public DemoDecorator()
  {
    super();
  }
  // Method to decorate Image 
  public Image decorateImage(Image image, Object object)
  {
    // Return null to specify no decoration
    return null;
  }
  // Method to decorate Text
  public String decorateText(String label, Object object)
  {
    // return null to specify no decoration
    return null;
  }
}

Fig. 6: Decorator Implementation class

The default implementation can be used as a template to get started. The decorateImage() and decorateText() methods are used to decorate the image and text respectively. A detailed discussion on how to decorate a resource using these two methods is provided later.

Contribute your custom decoration via the decorator extension point in a plugin.xml manifest file (use the example shown in previous section ). Create a class to implement custom decoration (Fig. 6). Note that the Java class name should be the same as the text value of the "class" attribute of the decorator tag in plugin.xml. Compile and run. You should be able to see your custom decoration appear inside Window->Preferences->Workbench->Label Decorations as shown in Fig. 5.
 

Individual Preference Pages for Label Decoration

The label decorations preference page provides the user with only two options, either to use a decorator or not. To provide users more control over the decoration contents and use of a subset of decorations out of a pool of decorations provided by a particular decorator, a plug-ins can provide an individual preference page for its label decorators. For example, the CVS plug-in provides a preference page (Window->Preferences->Team->CVS-> Label Decorations) that allows users to control the presentation and content of CVS decorators.


Fig. 7: CVS Decoration Preference Page

The figure shown above illustrates how plug-ins can use the individual label decorator preference page to provide users the ultimate control over the decorations. The CVS plug-in gives a user the control over the choice of decorations, the richness in decorating different resources and the choice of types of resources that need to be decorated. They can even control the look and feel of decoration ().

One of the important considerations while providing custom decorations is the performance of UI with and without decorators. Users can effectively use the individual decorator preference page to avoid decorations that are expensive to compute and thus enhance the performance.

An individual decoration preference page is extremely useful when resources are decorated on receiving external events / notification. For example, a repository provider plug-in (e.g. org.eclipse.team.cvs.ui) might be collaborating with an external server to decorate resources with a lock icon, whenever resources are checked out by some other user. The repository provider plug-in might be listening to thousands of events and it would be better if the user has ultimate control on what to decorate (and what not to).

Since individual decoration preference pages are preference pages that are used to provide control to the users on decorations, they should be contributed via the preference pages extension point. Users can refer to the articles Preferences in the Eclipse Workbench UI  by Tod Creasey and Simplifying Preference Pages with Field Editors by Ryan Cooper to learn more about preference pages and ways to create them.

Example Plug-in Introduction

Now that we know how to declare a custom label decoration, and contribute individual label decoration preference pages, let's dive into our Example Plug-in.

The basic idea behind providing this example is to illustrate how to decorate images and text. To keep it as simple as possible, users are provided with a custom File Property page. The file property page has a custom page "DecoratorDemo File Property page" that provides a control for the users to set the "Busy" property (The "Busy" property indicates that the resource is busy and hence should not be modified). The page also provides controls for users to set the prefix and suffix values for the resource.


Fig. 8: Example Plug-in's File Property Page

Using the file property page (Fig. 8) (a file property page can be opened by right-clicking the file and selecting properties in the context menu), users can set the busy state for the file. The busy state when set is indicated by a lock icon superimposed on the base image provided by Eclipse. Users can set the prefix and suffix values of resource labels ().

An individual label decoration preference page is provided for users to manage the decorations (Fig. 9). They provide users control over prefix/suffix text decoration ) and project label decoration (). The project is decorated with a default text label decoration.


Fig. 9: Example Plug-in's Label Decoration Preference Page

Now that we have been introduced to the example plug-in, let's go into details.

Beyond the Basics

Before delving deep into actual methods that provide decorations, it is important to understand the concepts needed while decorating a resource.

Persistent Resource Properties

Decorators, as we mentioned before, are used to visually annotate resources with state information. The state information of a resource used for decoration should be persistent across sessions. In our example plug-in, the prefix, the suffix and the busy state of the resource should be persisted across sessions. There are two ways to persist resource properties. One is the traditional way of associating resources names with their corresponding properties, and storing them in a property file. The second preferred method is to use Eclipse API to store persistent resource properties.

Resources can have properties that hold state information. The properties of a resource are declared, accessed and maintained by various plug-ins and are not interpreted by the platform. There are two types of properties associated with a resource: persistent properties and session properties. Persistent properties, as the name suggests, are persistent across sessions while session properties are maintained in memory and are lost when the project or workspace is closed. Resource properties are deleted when the resources are deleted.

Depending on the utility of the plug-in, plug-in developers can use persistent or session properties. Persistent properties are stored on disk and hence are accessible across platform sessions. The example plug-in requires the busy state of the resource to be persisted across sessions. The persistent property resource API is used to store and retrieve property values by key. The following figure shows how to set and get the persistent properties.
 
// Create a qualified Name for Busy state of the resource
QualifiedName q1 = new QualifiedName 
 ("com.ibm.Decorator.DecoratorDemo", "Busy");

// Create a qualified Name for Prefix 
QualifiedName q2 = new QualifiedName
 ("com.ibm.Decorator.DecoratorDemo", "Prefix");

// Set the persistent properties
resource.setPersistentProperty (q1, "true");
resource.setPersistentProperty (q2, "Prefix Value");

// Get the value of persistent property q1... The value 
// retrieved is "true"
String busyNature = resource.getPersistentProperty (q1);

Fig. 10: Sample code to manage Resource properties

The code snippet (shown above) explains how to set / get persistent property of resources using qualified names.  A qualified name is analogous to a key used for accessing / storing property values. Qualified names are composed of two-part names, a qualifier and a local name. The local name (in the example shown above, the local names are "Busy" and "Prefix") could be used by any decorator. So it is extremely important to provide a unique URI value for the qualifier part. The simplest way to ensure a unique qualifier value is to use the id of your plug-in as the qualifier name.

Where does Eclipse store resource persistent properties? Eclipse stores resource persistent properties in an internal database at "Workspace Location/.metadata/.plugins/org.eclipse.core.resources/.projects/Project Name/.properties".

Eclipse provides a mechanism to store the sync information associated with a resource. Associating "sync info" with a resource and decorating a resource using the sync information is also an option plug-in developers might want to consider before proceeding with use of persistent properties. Sync info maintains all the information in memory and writes to disk only on save or a snapshot. Another advantage of using sync info is that changes to sync information are reported in the subsequent delta while it is difficult to keep track of the persistent property resource changes. Readers can refer to "org.eclipse.core.resources.ISynchronizer" to know more about sync info and ways to store sync information for a resource.

Overlay Images

In order to provide our custom decorations on a resource in addition to the basic decorations provided by Eclipse, we superimpose custom images on the base image. Eclipse provides utility methods to help with overlaying an image over another.

  It is a good idea to design your decorators so that they do not overlap or conflict with the existing platform SDK decorators. For example, Eclipse provides the problem marker decorator to alert users of compilation problems. The custom decoration should not superimpose images at the same position as the problem marker. The custom decoration also should not lose the problem marker information. Since different custom decorator providers don't have prior knowledge about one another, there is a good chance that custom decoration from two different plug-in providers will conflict each other. The Workbench label decoration page and the individual preference page provided by different custom decorators provide users the control over the choice of different decorations.

Eclipse provides an API for overlaying one image over other. The following code snippet explains how to overlay an icon image over another image.

protected void drawCompositeImage(int width, int height) 

   // To draw a composite image, the base image should be 
   // drawn first (first layer) and then the overlay image 
   // (second layer) 

   // Draw the base image using the base image's image data
drawImage(baseImage_.getImageData(), 0, 0); 

   // Method to create the overlay image data 
   // Get the image data from the Image store or by other means
   ImageData overlayImageData = demoImages.getLockImageData();

   // Overlaying the icon in the top left corner i.e. x and y 
   // coordinates are both zero
   int xValue = 0;
   int yValue = 0;
drawImage (overlayimageData, xValue, yValue) 
}

Fig. 11: Overlay Custom Image on the base Image

The base image is drawn () and then the image that needs to be superimposed is drawn at the top left corner of the base image (see ). The example plug-in code "OverlayImageIcon.java"  implements superimposing the custom images on the base image at different locations.

It is always good to create the overlay image icons (that need to be superimposed on the base image) once and share the same image across different views. In the example shown below (Fig. 12), an image descriptor for a lock icon is created and the image data is returned when requested by the drawImage() method of OverlayImageIcon (Fig. 11). In this way, custom images are shared among objects across different views. The best practice approaches section also talks about the image registry and how it can be best used to efficiently decorate resources with custom images.
 
public class DemoImages
{
  private static final ImageDescriptor lockDescriptor = 
    ImageDescriptor.createFromFile (DemoDecorator.class, "lock.gif");

  public ImageData getLockImageData()
  {
     return lockDescriptor.getImageData();
  }
}

Fig. 12: Illustration on how images can be shared among objects across views

In our example plug-in, a lock icon is superimposed on the base image if the file has its "busy" property set. The figure shown below shows how a resource's image icon would look in the package explorer view (Fig. 13). The lock icon is superimposed on the compilation unit and the runtime class instance of ImageDecoration.java ()


Fig. 13: Superimpose a lock decorator

Re-Decorate

When the workbench starts, the decorator manager checks for enabled decorators (decorators are enabled using the Workbench > Label Decorations page) and decorates the resources inside different views using the custom decorations provided by these decorators.

The properties of a resource might change at runtime, which will trigger the need for redecoration. For example, users might change a file's busy attribute using the file property page - and therefore the image decoration must be changed to reflect the change.  To re-decorate the resources, we fire a LabelProviderChangedEvent. The fired event notifies the different workbench views that the label provider for the resource has changed. Eclipse calls the decorateImage() and decorateText() methods for the resources whose label provider has changed. A LabelProviderChangedEvent should only be fired when some aspect of the element used to do the decoration changes. They can also be fired when the labels need to be updated due to a change in decoration presentation (e.g. due to a change in a preference page for the decorator). Sending these events will update all affected views.
 
public void refresh(List resourcesToBeUpdated)
{
  // resourcesToBeUpdated is a list of resources whose decorators
  // need to be changed. The persistent property of the resources 
  // has been changed and hence its decorators should change

  // Check to see whether the custom decoration is enabled 
  DemoDecorator demoDecorator = getDemoDecorator();
  if (demoDecorator == null)
  {
    // Decorator is not enabled.. Don't decorate the resources.
    return;
  }

  // Fire a label provider changed event to decorate the 
  // resources whose image needs to be updated
fireLabelEvent(new LabelProviderChangedEvent(demoDecorator,
      resourcesToBeUpdated.toArray()));
}

private void fireLabelEvent(final LabelProviderChangedEvent event)

  // Decorate using current UI thread
Display.getDefault().asyncExec(new Runnable()
  {
    public void run()
    {
      // Fire a LabelProviderChangedEvent to notify eclipse views
      // that label provider has been changed for the resources
   fireLabelProviderChanged(event);
    }
  });
}

Fig. 14: Re-Decorate Resources

A LabelProviderChangedEvent is triggered () to notify different views that the label provider for the resources (in the figure shown above, the resources list is stored in resourcesToBeUpdated) has been changed and hence they need to be re-decorated. The plug-in developers must provide a Runnable that fires a labelProviderChanged event ().

If users choose to change the decoration preference using the individual decoration preference page, all the resources in the workspace need to be re-decorated. This could be done easily by changing line  in Fig. 14 to fireLabelEvent (new LabelProviderChangedEvent (demoDecorator)).

IDecoratorManager Interface

IDecoratorManager manages custom decorators contributed via the decorator's extension point. Some of the utility methods provided by IDecoratorManager are: There could be many custom decorators contributed via the decorator's extension point. The id associated with the custom decorator is unique and should be used to distinguish between different custom decorators. Views that allow decoration of their elements should use the label decorator returned by the getLabelDecorator() method (). The custom decorator objects (instance of the class used for decorating resources) can be found using the decorator id (). A custom decorator can be enabled or disabled by default using the state sub-element in the plugin.xml manifest file.
 
/**
 * Gets the custom decorator object. This method should be called to get
 * the custom decorator object by all methods that try to decorate resources
 * @return Custom Decorator Instance if the custom decorator is enabled
 *         null if the custom decorator is not enabled 
 */
public static DemoDecorator getDemoDecorator()
{

  // In Eclipse 3.5, use: PlatformUI.getWorkbench().getDecoratorManager();

  IDecoratorManager decoratorManager =
    DecoratorPlugin.getDefault().getWorkbench().getDecoratorManager();

  // com.ibm.decoratordemo.ui.decorator.demodecorator is the id of the 
  // custom decorator

  // If the decorator is disabled, a null value is returned 
return (DemoDecorator) decoratorManager.getLabelDecorator(
   "com.ibm.decoratordemo.ui.decorator.demodecorator");

}

Fig. 15: How to get custom Decorator instance using DecoratorManager Interface

The custom decorator class used for decorating resources is a singleton. The decorator developers should not use the decorator object (instance of the class used for decorating resources) when the decorator is disabled. The decorator developers should never cache the decorator object. The decorator object is disposed when the decorator is disabled and is recreated when the decorator is re-enabled. The utility method getLabelDecorator() returns a null value if the custom decorator is disabled or a custom decorator with the given decoratorId does not exist ().

Best Practice Approaches

The time taken for decoration should be as small as possible because the performance of the UI will be adversely affected by slow decorator code. Some of the best practice approaches that should be followed to reduce the time involved in decorations are as follows:
 
  • Use image descriptors to store descriptors of image rather than storing the images

  • The org.eclipse.jface.resources.ImageDescriptor class, as the name suggests, is a lightweight descriptor for an image. It contains all the information required to create an image. Image Descriptors do not allocate an actual platform image unless specifically requested using the createImage() method. Use of image descriptors is one of the best strategies that should be used when your code is structured such that it defines all the icons in one place and allocates them when needed.
     
  • Use org.eclipse.jface.ImageRegistry to share images across different views

  • The ImageRegistry class is used to keep a list of named images. Clients can add image descriptors or SWT images directly to the list. When an image is requested by name from the registry, the registry will return the image if it has been created, or create one from the descriptor. This allows clients of the registry to share images. A well-written article on images Using Images in the Eclipse UI by John Arthorne is a good reference for learning how to manage images in Eclipse.

    Images that are added to or retrieved from the registry must not be disposed by any client. The registry is responsible for disposing of the image since the images are shared by multiple clients. The registry will dispose of the images when the platform GUI system shuts down. Appropriate use of image descriptors and the image registry is important while performing decorations. Since many views participate in decoration, it is important to share the images using the caching mechanism rather than creating images from scratch.
     

  • Use Lightweight Decorators when possible

  • Eclipse 2.1 introduces a lightweight decorator that will handle the image management issues associated with decoration. Changes to the decorator mechanism in Eclipse 2.1 and ways to create custom decorations using Lightweight decorator mechanism are discussed later.
     

    Decorate Resources

    Let us look at the actual methods that decorate the image or the text of resource labels. As mentioned before, the class that is responsible for decoration should implement the ILabelDecorator interface.

    The interface provides two utility methods to decorate the text and image.

    The decorateImage method is used to decorate the object image with additional state information of the resource. The current image of the object can be obtained using the getImage() method of LabelProvider. The method returns an annotated image or a null image if the object need not be decorated. The decorateText method is used to decorate the object label.
     
    public Image decorateImage(Image baseImage, Object object)
    {
       // This method returns an annotated image or null if the 
       // image need not be decorated. Returning a null image
       // decorates resource icon with basic decorations provided
       // by Eclipse

       IResource objectResource;
    objectResource = (IResource) object;
       if (objectResource == null)
       {
         return null;
       }
    if (objectResource.getType() != IResource.FILE)
       {
         // Only files are decorated
         return null;
       }
       // Overlay custom image over base image 
       Image image;
       OverlayImageIcon overlayIcon = new 
         OverlayImageIcon(baseImage,  "Lock");
    image = overlayIcon.getImage();
       return image;

       // The image should be disposed when the plug-in is 
       // disabled or on shutdown
    }

    Fig. 16: DecorateImage Method to decorate images icons

    The decorateImage() method (shown above) decorates only a file object and does not decorate a project or a folder (). The IResource object () is used for determining whether the object under consideration is a project / folder / file. Using the OverlayImageIcon class (not shown), a lock decorator is superimposed on the base image ()
     
     
    public String decorateText(String label, Object obj)
    {
      IResource objectResource;
      objectResource = (IResource) object;

      if (objectResource.getType() != IResource.FILE)
      {
        // Only files are decorated in this example
        return null;
      }

      // Decorate the label of the resource with the admin name 
      String ownerName = System.getProperties().getProperty("user.name");
    return label + " ( " + ownerName + " )";

    Fig. 17: DecorateText method to annotate the label of resource

    The decorateText() method returns null (no decoration) for non-files. It decorates the label of a file with owner information. Returning a null value signifies that the decorator is ignored - it does not clear out any existing decorations.
     

    When to Decorate

    Just because you know how to decorate does not mean you know when to decorate. There are no strict rules that could be followed to find out when to decorate and when not to. Human intuition is the best way to proceed, although there are various factors that could be taken into consideration. Factors that could affect the decision on decoration are as follows: Let us try to understand the above mentioned factors with an example. Let us assume an example plug-in which tries to emulate CVS behavior - i.e. trying to provide functionality for the developers to checkin / checkout files from repository. The plug-in collaborates with an external server to receive notification when resources are checked out by other users (checking out a file means someone has extracted the file and is making changes to the file). Let us assume the time spent on computing image decoration is 0.5 seconds. The number of times the plug-in receives notification from the external server would be huge since there are many files in the repository and many users are working concurrently.

    In our example, it is advantageous for the users to know about the files that are changed by others. But can they afford to lose 0.5 seconds for every decoration?  No.  So the plug-in developers, rather than decorating the image icon, can present the information to the user in a different way, for example, the file properties view. Improper use of decorators can lead to poor performance and will ultimately lead to plug-in decorators becoming useless.
     

    Caveat Lector (Reader Beware)

    We saw some of the best practice approaches that could be used to reduce the time taken to decorate image icons. Every image in Eclipse uses operating system resources. We don't want to create 1,000 copies of the same image. It would be nice to use some of the features in Eclipse to cache decorated images and use them for generating similar images. Let us assume we want to superimpose a lock icon on three text files. There are two different ways to do this using the methods described earlier to superimpose a lock icon on the base image. The lock icon should be superimposed on the base image for all three text files. It would be better to decorate the first text file, cache the resultant image in the image registry and use the cached image to decorate the remaining files. In this way, image caching can be used to avoid superimposing the same custom image over the same base image every time you perform decoration. Image caching would be advantageous when the frequency of decoration is huge and the amount of time spent on calculating the superimposed image is high. Image caching should be used if the developers have a prior knowledge about images that might appear multiple times.

    Decorations are performed when workbench starts initially. It is called when users open a resource, close a resource, expand the resource tree etc.

    Image caching, although a good technique to reduce the time involved in decorating resources is not without problems. Some of the inherent problems associated with decoration using image caching approach are as follows:

    <extension point="org.eclipse.ui.decorators">
      <decorator 
        id="YourDecorator" 
        label="Decorator Label" 
        state="false" 
        class="YourDecorator.class" 
        objectClass="org.eclipse.core.resources.IResource" 
        adaptable="true"> 
      </decorator> 
    </extension>
    Fig. 18: Sample plugin.xml manifest file to contribute decorators

    The attributes that are of interest are: objectClass and adaptable. ObjectClass indicates the class of resources that need to be decorated. The adaptable flag indicates whether the classes that adapt to the IResource object should also be decorated.

    If a user tries to decorate a Java file in a navigator view, the decorateImage() and decorateText() methods are called on the IFile object. If the user tries to decorate the Java file in a package explorer, the decorateImage() and decorateText() methods are called on all the Java elements (because all Java elements are adaptable to IFile objects).

    Let's see what happens when a user decorates a Java file and the adaptable attribute is set to true. The decorateImage() method is called on all the Java elements for the resource (JavaProject,  PackageFragmentRoot, PackageFragment, CompilationUnit (Java file), and runtime class). If the adaptable flag is true, the object parameter passed to the decorateImage() and decorateText() methods is an IResource object for compilationUnit and runtime class while a null is passed for all the other Java elements.

    So what's the problem? Let us assume we cache the image (lock icon superimposed on a Java (Compilation Unit) icon) with the property "Java Lock" to denote that it is Java file with a lock icon superimposed on the base image. You might have cached the image when decorateImage method was called on the compilation unit. When decorateImage() method is called on a class file, we get the same property information using the IResource object (Fig. 16) and hence we decorate the class file with the cached copy. So instead of getting a lock icon on top of a class icon, the class file image icon is represented by a custom lock decorator on top of a Java icon image. The following diagrams illustrate the aforesaid behavior.


    Fig. 19: Overlaying Image without Image cache
     


    Fig. 20: Overlay Image using Image Caching

    From Fig. 19 and Fig. 20, it is clear that users should be careful while using image caching with non-resource files. When image caching is used, the runtime class is represented by a lock icon on top of the Java icon ( in Fig. 20) instead of a lock icon on top of the runtime class icon. Image caching can't be used because there was no way to distinguish between the different Java elements using the IResource object and its associated properties. To distinguish between the Java elements, one has to depend on JDT core and write specific adapters for Java elements.

    Change plugin.xml provided with the example plug-in such that the class that implements decoration is DemoDecoratorWithImageCaching rather than DemoDecorator. The DemoDecorator object instance used in the "file property page" and "individual label decorations preference page" should be replaced with an instance of DemoDecoratorWithImageCaching. You should be able to see decoration like the one shown in Fig. 20.

    Due to the above mentioned problems, image caching, although a good technique to reduce the time involved in decorating resources should not be used.
     

    What's new in Eclipse 2.1?

    In Eclipse 2.0, plug-in developers had to programmatically overlay the custom images on top of the base image of the objects displayed in the Eclipse views. Eclipse 2.1 introduces a lightweight decorator that will handle the image management issues associated with decoration. It is also possible to declare a lightweight decorator that simply overlays an icon when enabled that requires no implementation from the plug-in. Lightweight decorators performs decorations in a background thread and hence the UI thread is not blocked when the decorations are performed.

    Let's look at the configuration markup for decorators in Eclipse 2.1.
     
    !ELEMENT decorator > 
       <!ATTLIST decorator 
           id      CDATA #REQUIRED 
           label   CDATA #REQUIRED 
         class   CDATA #OPTIONAL  // #REQUIRED if lightweight = false 
         objectClass CDATA #REQUIRED 
                       //deprecated. Make this part of the enablement 
         icon    CDATA #OPTIONAL // required if there is no class 
         location ("TOP_LEFT" | "TOP_RIGHT" | "BOTTOM_LEFT" | "BOTTOM_RIGHT"| 
                    "UNDERLAY") #OPTIONAL 
         lightweight  (true | false) #IMPLIED 
           adaptable    (true | false) #IMPLIED 
           state        (true | false) #IMPLIED 
       > 
       <!ELEMENT description (#PCDATA)> 
    <!ELEMENT enablement (#PCDATA)> 

    Fig. 21: Configuration Markup for decorators in Eclipse 2.1

    As you can see from Fig. 21, there are quite a few changes in the configuration markup for decorators in Eclipse 2.1. The class which was a required field in Eclipse 2.0 is an optional field in Eclipse 2.1(). The class attribute represents a fully qualified name of a class which implements org.eclipse.jface.viewers.ILabelDecorator if lightweight is false or org.eclipse.jface.viewers.ILightweightLabelDecorator if lightweight is true. The default value is false. If there is no class element it is assumed to be true. The objectClass attribute is deprecated in Eclipse 2.1 and is part of the enablement element (). The icon attribute is a new attribute in Eclipse 2.1 and it represents the path to the overlay image to apply if the decorator is lightweight (). Location attribute represents the location to apply the decorator if the decorator is lightweight (). The default value of location is BOTTOM_RIGHT. Lightweight attribute can be used to signify whether the decorator is lightweight or not (). Enablement sub-elements represent the actionExpression used to determine enabled state ().

    Let's look at an example to understand lightweight decorators.  
    <extension point="org.eclipse.ui.decorators"> 
      <decorator
         id="com.ibm.DemoLightweightDecorator" 
         label="DemoLightweightDecorator" 
         state="false" 
      class="com.ibm.Demo.LightweightDecorator" 
      lightweight="true" 
         location="TOP_LEFT"

         <enablement>
         <objectClass="org.eclipse.core.resources.IResource"/> 
         </enablement> 
      </decorator 

    Fig. 22: Decorator Extension using Lightweight Decorator

    Since the lightweight attribute has a true value (), the class com.ibm.Demo.LightweightDecorator () should implement org.eclipse.jface.viewers.ILightweightLabelDecorator. The class com.ibm.Demo.LightweightDecorator should provide the text decoration labels and the image descriptor and need not be concerned with the resource handling. Another advantage of using Lightweight decorators is that the decoration work is done in a background thread.

    Let's look at the ILightweightLabelDecorator interface to see how easy decorations can be performed in Eclipse 2.1.  
    org.eclipse.jface.viewers.ILightweightLabelDecorator interface:
    The ILightweightLabelDecorator is a decorator that decorates using a prefix, suffix and overlay image rather than doing all of the image and text management itself like an ILabelDecorator. 

    void decorate(Object element, IDecoration decoration) 
            calculates decorations based on element.

    Fig. 23: ILightweightLabelDecorator interface

    From Fig. 23, it is clear that plug-in developers should implement decorate() method () to perform both text and image decorations. This is a big difference from Eclipse 2.0 where plug-in developers had to implement decorateText() for performing text decorations and decorateImage() method for performing image decorations. One added advantage of the Eclipse 2.1 lightweight decorator mechanism is that plug-in developers need not be concerned with resource handling and need only to provide the text and image descriptors. When a plug-in developer tries to redecorate a resource by firing a LabelProviderChanged event, Eclipse calls the decorate() method for the object. The plug-in developers should appropriately set the overlay image descriptors, prefix label and the suffix label using the IDecoration object instance.
    org.eclipse.jface.viewers.IDecoration interface: 
    Defines the result of decorating an element. This interface is not meant to be implemented and will be provided to instances of ILightweightLabelDecorator. 

    void addOverlay (ImageDescriptor overlay)
            Adds an overlay to the element's image. 

    void addPrefix (String prefix) 
              Adds a prefix to the element's label. 

    void addSuffix (String suffix)
              Adds a suffix to the element's label. 

    Fig. 24: IDecoration interface

    For the Example Plug-in, the lock image descriptor (to signify the busy state of a resource) can be set using the addOverlay() method (). The prefix and suffix labels for an object element can be set using the addPrefix() and addSuffix() methods ().

    If a plug-in requires a developer to provide only image decoration and no text decorations, then the plug-in developer could make use of a declarative LightweightDecorator. This means that plug-in developer need not provide a class to implement ILightweightLabelDecorator but instead provide the path for the icon image and location where the icon needs to be placed (TOP_LEFT | BOTTOM_LEFT | TOP_RIGHT | BOTTOM_RIGHT | UNDERLAY). Eclipse LightweightDecorator mechanism takes care of resource handling and image decoration.

    As we have seen, the new LightweightDecorator mechanism is quite powerful and makes it easy for developers to decorate resources. The source code for the Example Plug-in implemented using lightweight decorators (Eclipse M4 stable version) is provided below.

    Source Code

    The example plug-in uses most of the best practice approaches that should be followed while decorating a resource. Some of the classes are as follows: To run the example or view the source code for this article, unzip sourceCodeOfDecoratorPlugin.zip into your plug-ins/ subdirectory. To run view the source code for the Example plug-in using lightweight decorators, unzip sourceCodeOfDecoratorPluginUsingLightweightDecorators.zip into your plug-ins/ subdirectory.

    Summary

    Decorators are visual cues that convey useful state information associated with objects or resources displayed in Eclipse views. Eclipse provides ways for users to change the image and label decorators.  The performance of the Eclipse UI can be affected by the efficiency with which decorations are performed. The best practice approaches mentioned in this article can be used to reduce the time involved in decoration. An old saying, "Pictures are worth 1000 words - but only if you know the words" aptly describes the use of decorators.

    Acknowledgements

    The author would like to thank Jan J. Kratky, Patrick McCarthy, Tod Creasey, and Nick Edgar (all at IBM) for providing constructive comments on the article.

    Java and all Java-based trademarks and logos are trademarks or registered trademarks of Sun Microsystems, Inc. in the United States, other countries, or both.