How to add both an image decoration and a custom tooltip to a JFace ViewerColumn

Recently i had to add some image decorations and a custom tooltip to a column of a Jface StructuredViewer. This wasn’t too hard, but i lost a fair amount of time figuring out how to put the pieces together.

This is how i did it:

Setup

To avoid unneeded elements i’ll start with a plug-in project created with the eclipse wizard “RCP application with a view”.

When you run the application created by the wizard you will get the following:

RCP application generated by the eclipse wizard

RCP application generated by the eclipse wizard

Unfortunately the generated view doesn’t use viewercolumns, so we have to change that:

Adapting the generated view

Change ViewLabelProvider

Since the ViewerColumn accepts only a CellLabelProvider change the ViewLabelProvider to the following:

class ViewLabelProvider extends ColumnLabelProvider {
    @Override
    public String getText(Object obj) {
        return super.getText(obj);
    }
 
    @Override
    public Image getImage(Object obj) {
        return PlatformUI.getWorkbench().getSharedImages().getImage(
                ISharedImages.IMG_OBJ_ELEMENT);
    }
}
Change method createPartControl(Composite parent)

we need to create a ViewerColumn and add the labelprovider to it.

To make sure the column takes the whole width of the table, we use the TableColumnLayout.

/**
* This is a callback that will allow us to create the viewer and initialize
* it.
*/
@Override
public void createPartControl(Composite parent) {
	viewer = new TableViewer(parent, SWT.MULTI | SWT.H_SCROLL
	        | SWT.V_SCROLL);
 
	TableViewerColumn viewerColumn = new TableViewerColumn(viewer, SWT.NONE);
	viewerColumn.setLabelProvider(new ViewLabelProvider());
 
	TableColumnLayout tableColumnLayout = new TableColumnLayout();
	tableColumnLayout.setColumnData(viewerColumn.getColumn(),
	        new ColumnWeightData(1));
	parent.setLayout(tableColumnLayout);
 
	viewer.setContentProvider(new ViewContentProvider());
	viewer.setInput(getViewSite());
}

Generating a model

The model is going to be used later on, to decide wether to decorate or not.

Create the model class
class ViewModel {
	private boolean active = true;
	private final String name;
 
	public ViewModel(String name) {
		super();
		this.name = name;
	}
 
	protected ViewModel(String name, boolean active) {
		this(name);
		this.active = active;
	}
 
	public boolean isActive() {
		return active;
	}
 
	public String getName() {
		return name;
	}
}
Adapt the labelprovider

Change the method getText(Object obj).

@Override
public String getText(Object obj) {
	if (obj instanceof ViewModel) {
		ViewModel viewModel = (ViewModel) obj;
		return viewModel.getName();
	}
	return super.getText(obj);
}
Adapt the contentprovider

Change the method getElements(Object parent).

public Object[] getElements(Object parent) {
	return new ViewModel[] { new ViewModel("One", false),
	        new ViewModel("Two"), new ViewModel("Three", false)};
}

Adding decorations

We are going to use the DecoratingStyledCellLabelProvider to add decorations.

The DecoratingStyledCellLabelProvider needs a ILabelDecorator and a IStyledLabelProvider.

Creating a labelDecorator

We only want to decorate the image, therefore we implement the method decorateImage(Image image, Object element, IDecorationContext context and the method dispose (to dispose the generated image).

It would be nice though if there existed a LabelDecoratorAdapter in JFace so we didn’t have to add the other methods.

class ViewLabelDecorator extends LabelDecorator {
	private final ImageDescriptor warningImageDescriptor = Activator
	        .getImageDescriptor("icons/bullet_error.png");
	private Image decoratedImage = null;
 
	@Override
	public Image decorateImage(Image image, Object element, IDecorationContext context) {
		if (element instanceof ViewModel && !((ViewModel) element).isActive()) {
			if (decoratedImage == null) {
				decoratedImage = new DecorationOverlayIcon(image, warningImageDescriptor, IDecoration.BOTTOM_RIGHT)
				        .createImage();
			}
			return decoratedImage;
		}
		return null;
	}
 
	@Override
	public void dispose() {
		decoratedImage.dispose();
		decoratedImage = null;
	}
 
	@Override
	public String decorateText(String text, Object element,
	        IDecorationContext context) {
		return null;
	}
 
	@Override
	public boolean prepareDecoration(Object element, String originalText,
	        IDecorationContext context) {
		return false;
	}
 
	@Override
	public Image decorateImage(Image image, Object element) {
		return null;
	}
 
	@Override
	public String decorateText(String text, Object element) {
		return null;
	}
 
	@Override
	public void addListener(ILabelProviderListener listener) {
	}
 
	@Override
	public boolean isLabelProperty(Object element, String property) {
		return false;
	}
 
	@Override
	public void removeListener(ILabelProviderListener listener) {
	}
}

Adapting the labelProvider

We need to make the LabelProvider implement the interface IStyledLabelProvider.

class ViewLabelProvider extends ColumnLabelProvider implements
        IStyledLabelProvider {
	.....
 
	@Override
	public StyledString getStyledText(Object element) {
		return new StyledString(getText(element));
	}
}

Using the DecoratingStyledCellLabelProvider

Change the method createPartControl(Composite parent) of the view as follows:

@Override
public void createPartControl(Composite parent) {
 
	.....
 
	TableViewerColumn viewerColumn = new TableViewerColumn(viewer, SWT.NONE);
	viewerColumn.setLabelProvider(new DecoratingStyledCellLabelProvider(
	        new ViewLabelProvider(), new ViewLabelDecorator(), null));
 
	.....
 
	}

Result

When you run the application now you will get the following:

decorated RCP application

decorated RCP application

Adding tooltips

Adding tooltips is a two-step process: the CellabelProvider provides methods for tooltips that we can implement, and by using the ColumnViewerToolTipSupport we have tooltips. Well, that would it be if the DelegatingStyledCellLabelProvider would try to delegate all the methods.

It seems that the DelegatingStyledCellLabelProvider has support for IFontProvider and IColorProvider but not for tooltip methods defined in CellLabelProvider.

Adapting the labelprovider

Implement the method getTooltipText(Object element):

@Override
public String getToolTipText(Object element) {
	return getText(element) + ", shown in a tooltip";
}

Extend the DecoratingStyledCellLabelProvider

class ViewDecoratingStyledCellLabelProvider extends
        DecoratingStyledCellLabelProvider {
	private final IStyledLabelProvider labelProvider;
 
	public ViewDecoratingStyledCellLabelProvider(
	        IStyledLabelProvider labelProvider, ILabelDecorator decorator,
	        IDecorationContext decorationContext) {
		super(labelProvider, decorator, decorationContext);
		this.labelProvider = labelProvider;
	}
 
	@Override
	public Color getToolTipBackgroundColor(Object object) {
		if (labelProvider instanceof CellLabelProvider) {
			return ((CellLabelProvider) labelProvider)
			        .getToolTipBackgroundColor(object);
		}
		return super.getToolTipBackgroundColor(object);
	}
 
	@Override
	public int getToolTipDisplayDelayTime(Object object) {
		if (labelProvider instanceof CellLabelProvider) {
			return ((CellLabelProvider) labelProvider)
			        .getToolTipDisplayDelayTime(object);
		}
		return super.getToolTipDisplayDelayTime(object);
	}
 
	@Override
	public Font getToolTipFont(Object object) {
		if (labelProvider instanceof CellLabelProvider) {
			return ((CellLabelProvider) labelProvider)
			        .getToolTipFont(object);
		}
		return super.getToolTipFont(object);
	}
 
	@Override
	public Color getToolTipForegroundColor(Object object) {
		if (labelProvider instanceof CellLabelProvider) {
			return ((CellLabelProvider) labelProvider)
			        .getToolTipForegroundColor(object);
		}
		return super.getToolTipForegroundColor(object);
	}
 
	@Override
	public Image getToolTipImage(Object object) {
		if (labelProvider instanceof CellLabelProvider) {
			return ((CellLabelProvider) labelProvider)
			        .getToolTipImage(object);
		}
		return super.getToolTipImage(object);
	}
 
	@Override
	public Point getToolTipShift(Object object) {
		if (labelProvider instanceof CellLabelProvider) {
			return ((CellLabelProvider) labelProvider)
			        .getToolTipShift(object);
		}
		return super.getToolTipShift(object);
	}
 
	@Override
	public int getToolTipStyle(Object object) {
		if (labelProvider instanceof CellLabelProvider) {
			return ((CellLabelProvider) labelProvider)
			        .getToolTipStyle(object);
		}
		return super.getToolTipStyle(object);
	}
 
	@Override
	public String getToolTipText(Object element) {
		if (labelProvider instanceof CellLabelProvider) {
			return ((CellLabelProvider) labelProvider)
			        .getToolTipText(element);
		}
		return super.getToolTipText(element);
	}
 
	@Override
	public int getToolTipTimeDisplayed(Object object) {
		if (labelProvider instanceof CellLabelProvider) {
			return ((CellLabelProvider) labelProvider)
			        .getToolTipTimeDisplayed(object);
		}
		return super.getToolTipTimeDisplayed(object);
	}
 
	@Override
	public boolean useNativeToolTip(Object object) {
		if (labelProvider instanceof CellLabelProvider) {
			return ((CellLabelProvider) labelProvider)
			        .useNativeToolTip(object);
		}
		return super.useNativeToolTip(object);
	}
}

Enabling the tooltips

Change the method createPartControl(Composite parent) of the view as follows:

@Override
public void createPartControl(Composite parent) {
 
	.....
 
	viewerColumn
	        .setLabelProvider(new ViewDecoratingStyledCellLabelProvider(
	                new ViewLabelProvider(), new ViewLabelDecorator(), null));
 
	.....
 
	viewer.setInput(getViewSite());
	ColumnViewerToolTipSupport.enableFor(viewer);
}

Result

When you run the application now you will get the following:

decorated RCP application with tooltip

decorated RCP application with tooltip

Custom tooltips

By extending the ColumnViewerToolTipSupport it is possible to provide custom tooltips.

Extending ColumnViewerToolTipSupport

static class ViewColumnViewerToolTipSupport extends
        ColumnViewerToolTipSupport {
 
	protected ViewColumnViewerToolTipSupport(ColumnViewer viewer,
	        int style, boolean manualActivation) {
		super(viewer, style, manualActivation);
	}
 
	@Override
	protected Composite createViewerToolTipContentArea(Event event,
	        ViewerCell cell, Composite parent) {
		final Composite composite = new Composite(parent, SWT.NONE);
		composite.setLayout(new RowLayout(SWT.VERTICAL));
		Text text = new Text(composite, SWT.SINGLE);
		text.setText(getText(event));
		text.setSize(100, 60);
		DateTime calendar = new DateTime(composite, SWT.CALENDAR);
		calendar.setEnabled(false);
		calendar.setSize(100, 100);
		composite.pack();
		return composite;
	}
 
	public static final void enableFor(final ColumnViewer viewer) {
		new ViewColumnViewerToolTipSupport(viewer, ToolTip.NO_RECREATE,
		        false);
	}
}

Using the ViewColumnViewerToolTipSupport

Change the method createPartControl(Composite parent) of the view as follows:

@Override
public void createPartControl(Composite parent) {
 
	.....
 
	ViewColumnViewerToolTipSupport.enableFor(viewer);
}

Result

When you run the application now you will get the following:

decorated RCP application with custom tooltip

decorated RCP application with custom tooltip

Source code

Following is the view class. For simplicity i added all classes as internal class to the generated view class. Make sure the image “icons/bullet_error.png”exists.

package net.davymeers.examples.jface.columnviewerdecorationandtooltip.internal;
 
import org.eclipse.jface.layout.TableColumnLayout;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.viewers.CellLabelProvider;
import org.eclipse.jface.viewers.ColumnLabelProvider;
import org.eclipse.jface.viewers.ColumnViewer;
import org.eclipse.jface.viewers.ColumnViewerToolTipSupport;
import org.eclipse.jface.viewers.ColumnWeightData;
import org.eclipse.jface.viewers.DecoratingStyledCellLabelProvider;
import org.eclipse.jface.viewers.DecorationOverlayIcon;
import org.eclipse.jface.viewers.IDecoration;
import org.eclipse.jface.viewers.IDecorationContext;
import org.eclipse.jface.viewers.ILabelDecorator;
import org.eclipse.jface.viewers.ILabelProviderListener;
import org.eclipse.jface.viewers.IStructuredContentProvider;
import org.eclipse.jface.viewers.LabelDecorator;
import org.eclipse.jface.viewers.StyledString;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.viewers.TableViewerColumn;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerCell;
import org.eclipse.jface.viewers.DelegatingStyledCellLabelProvider.IStyledLabelProvider;
import org.eclipse.jface.window.ToolTip;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.layout.RowLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.DateTime;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.ISharedImages;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.part.ViewPart;
 
public class View extends ViewPart {
	public static final String ID = "net.davymeers.examples.jface.columnviewerDecorationAndTooltip.view";
 
	private TableViewer viewer;
 
	/**
	 * The content provider class is responsible for providing objects to the
	 * view. It can wrap existing objects in adapters or simply return objects
	 * as-is. These objects may be sensitive to the current input of the view,
	 * or ignore it and always show the same content (like Task List, for
	 * example).
	 */
	class ViewContentProvider implements IStructuredContentProvider {
		public void inputChanged(Viewer v, Object oldInput, Object newInput) {
		}
 
		public void dispose() {
		}
 
		public Object[] getElements(Object parent) {
			return new ViewModel[] { new ViewModel("One", false),
			        new ViewModel("Two"), new ViewModel("Three", false) };
		}
	}
 
	class ViewLabelProvider extends ColumnLabelProvider implements
	        IStyledLabelProvider {
		@Override
		public String getText(Object obj) {
			if (obj instanceof ViewModel) {
				ViewModel viewModel = (ViewModel) obj;
				return viewModel.getName();
			}
			return super.getText(obj);
		}
 
		@Override
		public Image getImage(Object obj) {
			return PlatformUI.getWorkbench().getSharedImages().getImage(
			        ISharedImages.IMG_OBJ_ELEMENT);
		}
 
		@Override
		public StyledString getStyledText(Object element) {
			return new StyledString(getText(element));
		}
 
		@Override
		public String getToolTipText(Object element) {
			return getText(element) + ", shown in a tooltip";
		}
 
	}
 
	class ViewModel {
		private boolean active = true;
		private final String name;
 
		public ViewModel(String name) {
			super();
			this.name = name;
		}
 
		protected ViewModel(String name, boolean active) {
			this(name);
			this.active = active;
		}
 
		public boolean isActive() {
			return active;
		}
 
		public String getName() {
			return name;
		}
 
	}
 
	class ViewLabelDecorator extends LabelDecorator {
		private final ImageDescriptor warningImageDescriptor = Activator
		        .getImageDescriptor("icons/bullet_error.png");
		private Image decoratedImage = null;
 
		@Override
		public Image decorateImage(Image image, Object element,
		        IDecorationContext context) {
			if (element instanceof ViewModel
			        && !((ViewModel) element).isActive()) {
				if (decoratedImage == null) {
					decoratedImage = new DecorationOverlayIcon(image,
					        warningImageDescriptor, IDecoration.BOTTOM_RIGHT)
					        .createImage();
				}
				return decoratedImage;
			}
			return null;
		}
 
		@Override
		public void dispose() {
			decoratedImage.dispose();
			decoratedImage = null;
		}
 
		@Override
		public String decorateText(String text, Object element,
		        IDecorationContext context) {
			return null;
		}
 
		@Override
		public boolean prepareDecoration(Object element, String originalText,
		        IDecorationContext context) {
			return false;
		}
 
		@Override
		public Image decorateImage(Image image, Object element) {
			return null;
		}
 
		@Override
		public String decorateText(String text, Object element) {
			return null;
		}
 
		@Override
		public void addListener(ILabelProviderListener listener) {
		}
 
		@Override
		public boolean isLabelProperty(Object element, String property) {
			return false;
		}
 
		@Override
		public void removeListener(ILabelProviderListener listener) {
		}
	}
 
	class ViewDecoratingStyledCellLabelProvider extends
	        DecoratingStyledCellLabelProvider {
		private final IStyledLabelProvider labelProvider;
 
		public ViewDecoratingStyledCellLabelProvider(
		        IStyledLabelProvider labelProvider, ILabelDecorator decorator,
		        IDecorationContext decorationContext) {
			super(labelProvider, decorator, decorationContext);
			this.labelProvider = labelProvider;
		}
 
		@Override
		public Color getToolTipBackgroundColor(Object object) {
			if (labelProvider instanceof CellLabelProvider) {
				return ((CellLabelProvider) labelProvider)
				        .getToolTipBackgroundColor(object);
			}
			return super.getToolTipBackgroundColor(object);
		}
 
		@Override
		public int getToolTipDisplayDelayTime(Object object) {
			if (labelProvider instanceof CellLabelProvider) {
				return ((CellLabelProvider) labelProvider)
				        .getToolTipDisplayDelayTime(object);
			}
			return super.getToolTipDisplayDelayTime(object);
		}
 
		@Override
		public Font getToolTipFont(Object object) {
			if (labelProvider instanceof CellLabelProvider) {
				return ((CellLabelProvider) labelProvider)
				        .getToolTipFont(object);
			}
			return super.getToolTipFont(object);
		}
 
		@Override
		public Color getToolTipForegroundColor(Object object) {
			if (labelProvider instanceof CellLabelProvider) {
				return ((CellLabelProvider) labelProvider)
				        .getToolTipForegroundColor(object);
			}
			return super.getToolTipForegroundColor(object);
		}
 
		@Override
		public Image getToolTipImage(Object object) {
			if (labelProvider instanceof CellLabelProvider) {
				return ((CellLabelProvider) labelProvider)
				        .getToolTipImage(object);
			}
			return super.getToolTipImage(object);
		}
 
		@Override
		public Point getToolTipShift(Object object) {
			if (labelProvider instanceof CellLabelProvider) {
				return ((CellLabelProvider) labelProvider)
				        .getToolTipShift(object);
			}
			return super.getToolTipShift(object);
		}
 
		@Override
		public int getToolTipStyle(Object object) {
			if (labelProvider instanceof CellLabelProvider) {
				return ((CellLabelProvider) labelProvider)
				        .getToolTipStyle(object);
			}
			return super.getToolTipStyle(object);
		}
 
		@Override
		public String getToolTipText(Object element) {
			if (labelProvider instanceof CellLabelProvider) {
				return ((CellLabelProvider) labelProvider)
				        .getToolTipText(element);
			}
			return super.getToolTipText(element);
		}
 
		@Override
		public int getToolTipTimeDisplayed(Object object) {
			if (labelProvider instanceof CellLabelProvider) {
				return ((CellLabelProvider) labelProvider)
				        .getToolTipTimeDisplayed(object);
			}
			return super.getToolTipTimeDisplayed(object);
		}
 
		@Override
		public boolean useNativeToolTip(Object object) {
			if (labelProvider instanceof CellLabelProvider) {
				return ((CellLabelProvider) labelProvider)
				        .useNativeToolTip(object);
			}
			return super.useNativeToolTip(object);
		}
	}
 
	static class ViewColumnViewerToolTipSupport extends
	        ColumnViewerToolTipSupport {
 
		protected ViewColumnViewerToolTipSupport(ColumnViewer viewer,
		        int style, boolean manualActivation) {
			super(viewer, style, manualActivation);
		}
 
		@Override
		protected Composite createViewerToolTipContentArea(Event event,
		        ViewerCell cell, Composite parent) {
			final Composite composite = new Composite(parent, SWT.NONE);
			composite.setLayout(new RowLayout(SWT.VERTICAL));
			Text text = new Text(composite, SWT.SINGLE);
			text.setText(getText(event));
			text.setSize(100, 60);
			DateTime calendar = new DateTime(composite, SWT.CALENDAR);
			calendar.setEnabled(false);
			calendar.setSize(100, 100);
			composite.pack();
			return composite;
		}
 
		public static final void enableFor(final ColumnViewer viewer) {
			new ViewColumnViewerToolTipSupport(viewer, ToolTip.NO_RECREATE,
			        false);
		}
 
	}
 
	/**
	 * This is a callback that will allow us to create the viewer and initialize
	 * it.
	 */
	@Override
	public void createPartControl(Composite parent) {
		viewer = new TableViewer(parent, SWT.MULTI | SWT.H_SCROLL
		        | SWT.V_SCROLL);
 
		TableViewerColumn viewerColumn = new TableViewerColumn(viewer, SWT.NONE);
		viewerColumn
		        .setLabelProvider(new ViewDecoratingStyledCellLabelProvider(
		                new ViewLabelProvider(), new ViewLabelDecorator(), null));
 
		TableColumnLayout tableColumnLayout = new TableColumnLayout();
		tableColumnLayout.setColumnData(viewerColumn.getColumn(),
		        new ColumnWeightData(1));
		parent.setLayout(tableColumnLayout);
 
		viewer.setContentProvider(new ViewContentProvider());
		viewer.setInput(getViewSite());
		ViewColumnViewerToolTipSupport.enableFor(viewer);
	}
 
	/**
	 * Passing the focus request to the viewer's control.
	 */
	@Override
	public void setFocus() {
		viewer.getControl().setFocus();
	}
}

Tags: , , ,

4 Responses to “How to add both an image decoration and a custom tooltip to a JFace ViewerColumn”

  1. Michael Says:

    Nice example, helped me to find a solution for my problem.

  2. Lars Vogel Says:

    Nice blog post Davy. You write more and ask to get aggregate at PlanetEclipse. 🙂

  3. mudelta Says:

    Thank you Lars!

    Currently i am trying to balance time between my work and my master study.
    But i hope to blog a bit more in the future (i have some posts in the pipeline).

  4. Lawrence Says:

    Mr. Meers,

    Great post!. Your examples are an excellent way for educating people. They’ve helped me tremendously.

    I look forward to your future entries.

    Lawrence

Leave a Reply