Thursday, January 17, 2013

A closer look at decorators

Decorators are widely used throughout the eclipse platform. No wonder we want to use them for our projects too. As long as we stick to icon overlays decorators are really simple to use. When we want to add text decorations things get a bit more complicated.

Source code for this tutorial is available on googlecode as a single zip archive, as a Team Project Set or you can checkout the SVN projects directly.

Preparations: Initial project

As a starting point I use a simple view with a TreeViewer. You can grab the initial source from my repository.
Run the plug-in com.example.decorators as an eclipse application and activate the Code & Me/Decorator Sample View to see that everything works.


Step 1: A simple icon decorator

To add an overlay icon decorator open you plugin.xml file and switch to the Extensions tab. Add a new extension of type org.eclipse.ui.decorators. The decorator needs its unique id and a nice label.

Decorators may be enabled/disabled by the user through Preferences/General/Appearance/Label Decorations. The state flag indicates the initial enablement of our decorator.

Set lightweight to true as we have a declarative decorator. Finally provide an icon and a location where the icon should be placed regarding the base image. Unfortunately you cannot use platform:/plugin/... style paths for your icon until bug 232171 is resolved. Instead you have to copy it to your own plug-in. The icon should be exactly 7x8 pixels (for overlays).


As you run your application you can see that your decorator is applied to the Project Explorer (if you created any projects there) but not to our sample view. To enable decorators within a viewer we need to adapt the LabelProvider. Open SampleView.java and change createPartControl to:

 @Override
 public void createPartControl(Composite parent) {

  TreeViewer treeViewer = new TreeViewer(parent, SWT.BORDER);
  tree = treeViewer.getTree();
  treeViewer.setContentProvider(new SampleTreeContentProvider());

  SampleTreeLabelProvider baseLabelprovider = new SampleTreeLabelProvider();
  ILabelDecorator decorator = PlatformUI.getWorkbench().getDecoratorManager().getLabelDecorator();
  treeViewer.setLabelProvider(new DecoratingLabelProvider(baseLabelprovider, decorator));
                ... 

Run your application again to see the decorator in action.


Step 2: Enablements for decorators

Our decorator will be visible for all viewers in our RCP. Typically that is not what we want. To bind a decorator to certain types we use enablements. To bind our decorator to String objects within viewers we can use:
   <extension
         point="org.eclipse.ui.decorators">
      <decorator
            icon="images/warning_ovr.gif"
            id="com.example.decorators.decorator"
            label="Demo decorator"
            lightweight="true"
            location="TOP_RIGHT"
            state="true">
         <enablement>
            <objectClass
                  name="java.lang.String">
            </objectClass>
         </enablement>
      </decorator>
   </extension>
I am sure you will create more powerful enablements than I did.

Step 3: Text decorations

If we want to add text to viewer elements (like the team provider plug-ins do) we need to create an implementation of ILightweightLabelDecorator. Create a new class com.example.decorators.DemoDecorator

package com.example.decorators;

import org.eclipse.jface.viewers.IDecoration;
import org.eclipse.jface.viewers.ILabelProviderListener;
import org.eclipse.jface.viewers.ILightweightLabelDecorator;

public class DemoDecorator implements ILightweightLabelDecorator {

 @Override
 public void decorate(Object element, IDecoration decoration) {
  decoration.addSuffix(" [decorated]");
 }

 @Override
 public void addListener(ILabelProviderListener listener) {
 }

 @Override
 public void dispose() {
  // nothing to do
 }

 @Override
 public boolean isLabelProperty(Object element, String property) {
  return false;
 }

 @Override
 public void removeListener(ILabelProviderListener listener) {
 }
}
and add the class to your decorator definition in plugin.xml. When you run your code you will see the suffix added to each element in our sample view. Unfortunately the overlay image is gone. By providing a class taking care of the decoration our declarative parameters will not be honored anymore.

To get back our overlay, we need to add it programmatically by adding/updating our decorator:
 private static final ImageDescriptor WARNING;

 static {
  WARNING = AbstractUIPlugin.imageDescriptorFromPlugin("com.example.decorators", "images/warning_ovr.gif"); 
 }

 @Override
 public void decorate(Object element, IDecoration decoration) {
  decoration.addSuffix(" [decorated]");
  decoration.addOverlay(WARNING, IDecoration.BOTTOM_RIGHT);
 }

Alternatively you could extend LightWeightDecorator, a default implementation of an ILightweightLabelDecorator that honors delarative image settings. Get it from my repository, it is available under EPL.

Step 4: Text decorations with different color

Our text suffix is rendered using the same color as the main text. If you remove the decorator enablement, you can see that the decorated text looks differently in the Project Explorer. The color used can be adjusted in Preferences/General/Appearance/Colors and Fonts under Basic/Decoration color.

To achieve the same behavior we need to use the same code as NavigatorDecoratingLabelProvider. Unfortunately it is an internal class, so access is discouraged. Our best option is to copy over the code and put it into our own class. Create a new class DecoratedLabelProvider and copy over the code from NavigatorDecoratingLabelProvider. Do not forget to adjust the name of the constructor.

Afterwards we can use our new provider in our SampleView:
 @Override
 public void createPartControl(Composite parent) {

  TreeViewer treeViewer = new TreeViewer(parent, SWT.BORDER);
  tree = treeViewer.getTree();
  treeViewer.setContentProvider(new SampleTreeContentProvider());

  SampleTreeLabelProvider baseLabelprovider = new SampleTreeLabelProvider();
  ILabelDecorator decorator = PlatformUI.getWorkbench().getDecoratorManager().getLabelDecorator();
  treeViewer.setLabelProvider(new DecoratedLabelProvider(baseLabelprovider));

Step 5: Using custom colors & fonts

To add custom colors to our main label text we can extend SampleTreeLabelProvider and implement IColorProvider (code contains only the changed parts):
public class SampleTreeLabelProvider extends LabelProvider implements IColorProvider{

 @Override
 public Color getForeground(Object element) {
  return Display.getDefault().getSystemColor(SWT.COLOR_DARK_BLUE);
 }

 @Override
 public Color getBackground(Object element) {
  return Display.getDefault().getSystemColor(SWT.COLOR_YELLOW);
 }

For custom fonts let SampleTreeLabelProvider implement IFontProvider:
public class SampleTreeLabelProvider extends LabelProvider implements IFontProvider{

 @Override
 public Font getFont(Object element) {
  return new Font(Display.getDefault(), new FontData("Arial", 12, SWT.BOLD));
 }
}
You have to use DecoratedLabelProvider to make custom fonts and colors visible.


Remember to use registries for frequently used system resources like fonts or colors.

Further reading

Check out Understanding Decorators in Eclipse and the FAQ topics.

2 comments:

  1. Other good articles on Jface Tree Viewer –

    http://www.vogella.com/articles/EclipseJFaceTree/article.html
    http://www.buggybread.com/2013/11/java-jface-treeviewer-steps-to-create.html
    http://www.programcreek.com/java-api-examples/index.php?api=org.eclipse.jface.viewers.TreeViewer

    ReplyDelete