Eclipse Corner Article |
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
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.
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.
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.
<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> |
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; } } |
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.
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.
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.
Before delving deep into actual methods that provide decorations, it is important to understand the concepts needed while decorating a resource.
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
// Set the persistent properties
// Get the value of persistent property q1... The value
|
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.
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
// Method to create the overlay image data |
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()
|
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
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
// Fire a label provider changed event
to decorate the
private void fireLabelEvent(final LabelProviderChangedEvent
event)
|
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)).
/**
* 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
// If the decorator is disabled, a null value is returned
} |
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 ().
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.
The interface provides two utility methods to decorate the text and image.
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;
// The image should be disposed when the plug-in is
|
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) // Decorate the label of the resource with the admin name
|
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.
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.
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> |
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.
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)> |
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>
|
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)
|
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)
void addPrefix (String
prefix)
void addSuffix (String
suffix)
|
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.
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.