/**
* Copyright (c) 2009 Daniel Kruegler and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Daniel Kruegler - initial API and implementation
*/
package org.eclipse.jface.snippets.viewers;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.resource.JFaceColors;
import org.eclipse.jface.viewers.ColumnLabelProvider;
import org.eclipse.jface.viewers.ColumnViewer;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.StyledCellLabelProvider;
import org.eclipse.jface.viewers.StyledString;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.TreeViewerColumn;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerCell;
import org.eclipse.jface.viewers.ViewerColumn;
import org.eclipse.jface.viewers.StyledString.Styler;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.MouseAdapter;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseListener;
import org.eclipse.swt.events.MouseMoveListener;
import org.eclipse.swt.events.MouseTrackListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Cursor;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.TextStyle;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Tree;
/**
* Demonstrate usage of {@link HyperlinkColumnLabelProvider}.
*
* @author Daniel Kruegler
* @since 3.4.2
*/
public class TreeViewerLinkColumnSnippet {
public TreeViewerLinkColumnSnippet(final Shell shell) {
final TreeViewer v = new TreeViewer(shell, SWT.BORDER
| SWT.FULL_SELECTION);
Tree tree = v.getTree();
GridData data = new GridData(SWT.FILL, SWT.FILL, true, true);
data.heightHint = 250;
tree.setLayoutData(data);
tree.setLinesVisible(true);
tree.setHeaderVisible(true);
TreeViewerColumn column = new TreeViewerColumn(v, SWT.NONE);
column.getColumn().setWidth(200);
column.getColumn().setMoveable(true);
column.getColumn().setText("People");
column.setLabelProvider(new ColumnLabelProvider() {
public String getText(Object element) {
if (element instanceof Person) {
Person datum = (Person) element;
return datum.name;
} else {
Project datum = (Project) element;
return datum.name;
}
}
});
column = new TreeViewerColumn(v, SWT.NONE);
column.getColumn().setWidth(200);
column.getColumn().setMoveable(true);
column.getColumn().setText("E-mail");
column.setLabelProvider(new HyperlinkColumnLabelProvider() {
protected String getLinkText(Object element) {
if (element instanceof Person) {
Person datum = (Person) element;
return datum.email;
} else {
return null;
}
}
protected void linkActivated(Object element) {
MessageDialog.openInformation(getViewer().getControl()
.getShell(), null, "Selected element: " + element);
}
});
column = new TreeViewerColumn(v, SWT.NONE);
column.getColumn().setWidth(100);
column.getColumn().setMoveable(true);
column.getColumn().setText("Age");
column.setLabelProvider(new ColumnLabelProvider() {
public String getText(Object element) {
if (element instanceof Person) {
Person datum = (Person) element;
return String.valueOf(datum.age);
} else {
return null;
}
}
});
v.setContentProvider(new MyContentProvider());
v.setInput(createModel());
}
private static class Person {
String name;
String email;
int age;
}
private static class Project {
String name;
Person[] people;
}
private static Project[] createModel() {
Project[] result = new Project[2];
Person[] persons;
{
Project item = new Project();
item.name = "Project Pizza";
persons = new Person[2];
Person p1 = new Person();
p1.name = "Jane";
p1.age = 24;
p1.email = "jane@different";
persons[0] = p1;
Person p2 = new Person();
p2.name = "Bill";
p2.age = 53;
p2.email = "bill.sunshine@northpole";
persons[1] = p2;
item.people = persons;
result[0] = item;
}
{
Project item = new Project();
item.name = "Project Ice Cream";
persons = new Person[2];
Person p1 = new Person();
p1.name = "Herb";
p1.age = 12;
p1.email = "hb@santaclaus";
persons[0] = p1;
Person p2 = new Person();
p2.name = "Susan";
p2.age = 33;
p2.email = "susan.withness@something";
persons[1] = p2;
item.people = persons;
result[1] = item;
}
return result;
}
public static void main(String[] args) {
Display display = new Display();
Shell shell = new Shell(display);
shell.setLayout(new GridLayout(1, false));
GridData data = new GridData(SWT.FILL, SWT.FILL, true, true);
shell.setLayoutData(data);
new TreeViewerLinkColumnSnippet(shell);
shell.pack();
shell.open();
while (!shell.isDisposed()) {
if (!display.readAndDispatch())
display.sleep();
}
display.dispose();
}
private static class MyContentProvider implements ITreeContentProvider {
public Object[] getElements(Object inputElement) {
return ((Object[]) inputElement);
}
public void dispose() {
}
public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
}
public Object[] getChildren(Object parentElement) {
if (parentElement instanceof Project)
return ((Project) parentElement).people;
else
return getElements(parentElement);
}
public Object getParent(Object element) {
return null;
}
public boolean hasChildren(Object element) {
if (element instanceof Person)
return false;
Project result = (Project) element;
return result.people.length > 1;
}
}
}
/**
* A specialized {@link org.eclipse.jface.viewers.CellLabelProvider}, which
* simulates the look-and-feel and the behavior of a normal hyper-link. The link
* behavior may be optional for some cells, therefore subclasses have to
* implement {@link #getLinkText(Object)}, which has to return a non
* null
value for any link cell, or {@code null} otherwise. In
* addition to the adaption of the mouse cursor, link activation behavior has to
* be implemented by overriding {@link #linkActivated(Object)}.
*
* This label provider implementation contains column-specific state, therefore * it cannot be used as a single label provider for a * {@link ColumnViewer}. * * @author Daniel Kruegler */ abstract class HyperlinkColumnLabelProvider extends StyledCellLabelProvider { /** * Convenience short-cuts for typical link colors (Could be a Java 1.5 * enum). * * @see HyperlinkColumnLabelProvider#linkColor(Display) */ public static final class LinkColor { private LinkColor() { } /** * The standard hyper-link color */ public static final LinkColor Normal = new LinkColor(); /** * The color for active hyper-links */ public static final LinkColor Active = new LinkColor(); /** * The color {@link SWT#COLOR_BLUE} */ public static final LinkColor Blue = new LinkColor(); }; private Control viewerCtrl; private Color linkColor; private Styler styler; private Cursor linkCursor; private Cursor busyCursor; /** * Internal state variable for mouse movements that specifies whether the * current mouse cursor shows the typical hand. */ private boolean linkEntered; /** * Lazy evaluation of the actual column index of this label provider. During * the first invocation of {@link #update(ViewerCell)} the proper column * index will be determined and set. This value is required to correctly * detect columns different from this column to ensure that the correct * mouse cursor is set. */ private int colIndex = -1; private static class LinkStyler extends Styler { private final boolean underline; public LinkStyler(boolean underline) { this.underline = underline; } public final void applyStyles(TextStyle textStyle) { textStyle.underline = underline; } } private MouseTrackListener listener = new MouseTrackListener() { public void mouseHover(MouseEvent e) { } public void mouseExit(MouseEvent e) { resetCursor(); } public void mouseEnter(MouseEvent e) { handleCursor(e); } }; private MouseMoveListener listener2 = new MouseMoveListener() { public void mouseMove(MouseEvent e) { handleCursor(e); } }; private MouseListener listener3 = new MouseAdapter() { public void mouseDown(MouseEvent e) { if (e.count != 2) { handleMouseDown(e); } } public void mouseDoubleClick(MouseEvent e) { handleMouseDown(e); } private final void handleMouseDown(MouseEvent e) { Point pt = new Point(e.x, e.y); ViewerCell cell = getViewer().getCell(pt); if (cell != null && isLinkCell(cell)) { viewerCtrl.setCursor(busyCursor); linkActivated(cell.getElement()); viewerCtrl.setCursor(linkCursor); } } }; private final void handleCursor(MouseEvent e) { Point pt = new Point(e.x, e.y); ViewerCell cell = getViewer().getCell(pt); if (cell != null && isLinkCell(cell)) { if (!linkEntered) { viewerCtrl.setCursor(linkCursor); linkEntered = true; } } else { resetCursor(); } } private final void resetCursor() { if (linkEntered) { viewerCtrl.setCursor(null); linkEntered = false; } } private final boolean isLinkCell(ViewerCell cell) { final int colIdx = cell.getColumnIndex(); if (colIdx == colIndex) { Object element = cell.getElement(); return isLink(element); } else { return false; } } /** * Subclasses may override. If their implementation is stateful, they need * to ensure that the function return value is set, before a call to * {@link #initialize(ColumnViewer, ViewerColumn)} has finished. A possible * strategy is to override this {@code initialize} method as well and to set * the state which determines the result of this function before * the super class implementation has been called. * * @return whether links will be underlined or not. The default * implementation returns {@code true}. * @see #initialize(ColumnViewer, ViewerColumn) */ public boolean underlineLink() { return true; } /** * Subclasses may override. If their implementation is stateful, they need * to ensure that the function return value is set, before a call to * {@link #initialize(ColumnViewer, ViewerColumn)} has finished. A possible * strategy is to override this {@code initialize} method as well and to set * the state which determines the result of this function before * the super class implementation has been called. This class does * not automatically free the color resources, implementors have to * provide their own resource management to ensure that. It is recommended * to use {@link #toColor(LinkColor, Display)} where possible, which takes * care of the management. * * @return The color used for links. The default implementation uses the * color corresponding to {@code LinkColor.Normal}. * @see #initialize(ColumnViewer, ViewerColumn) * @see #toColor(LinkColor, Display) */ public Color linkColor(Display disp) { return toColor(LinkColor.Normal, disp); } /** * Initializes the label provider. Subclasses may override, but shall call * the super class method. * * @see #underlineLink() * @see #linkColor(Display) */ public void initialize(ColumnViewer viewer, ViewerColumn column) { super.initialize(viewer, column); boolean theUnderlineLink = underlineLink(); Control theControl = viewer.getControl(); Display disp = theControl.getDisplay(); Color theLinkColor = linkColor(disp); viewerCtrl = theControl; linkColor = theLinkColor; if (linkColor == null) linkColor = disp.getSystemColor(SWT.COLOR_BLUE); linkCursor = disp.getSystemCursor(SWT.CURSOR_HAND); busyCursor = disp.getSystemCursor(SWT.CURSOR_WAIT); viewerCtrl.addMouseTrackListener(listener); viewerCtrl.addMouseMoveListener(listener2); viewerCtrl.addMouseListener(listener3); styler = new LinkStyler(theUnderlineLink); } public final void update(ViewerCell cell) { if (colIndex == -1) { colIndex = cell.getColumnIndex(); } Object element = cell.getElement(); String text = getLinkText(element); if (text != null) { StyledString styledString = new StyledString(text, styler); cell.setText(styledString.toString()); cell.setStyleRanges(styledString.getStyleRanges()); cell.setForeground(linkColor); } else { updateNonLink(cell); } super.update(cell); } /** * A helper function that automatically ensures that any null or empty * string returns {@code null} and thus users don't have to provide their * own logic that ensures that an empty string is presented with hyper-link * mouse cursor. * * @param s * The link text or {@code null} * @return Returns {@code null}, if the text is {@code null} or an empty * string, otherwise the text. * @see #getLinkText(Object) */ public static final String emptyToNull(String s) { return (s == null || s.length() == 0) ? null : s; } /** * Convenience function to get a typical hyper link color value. Callers * shall not dispose the returned color value. * * @param c * the color specifier * @param disp * The display the color shall retrieved from * @return the color value corresponding to the given enumerator, which * shall not be {@code null}. */ public static final Color toColor(LinkColor c, Display disp) { if (c == LinkColor.Active) return JFaceColors.getActiveHyperlinkText(disp); else if (c == LinkColor.Normal) return JFaceColors.getHyperlinkText(disp); else return disp.getSystemColor(SWT.COLOR_BLUE); } /** * Used to determine whether a model element corresponds to a link. The * method return value shall be equivalent to the test {@code * getLinkText(element) != null}, which is also the default behavior. * Subclasses may override, but shall satisfy the above mentioned * equivalence; this allows an implementation to provide an optimized test. * * @param element * a model element corresponding to the column of this label * provider. * @return Whether the element corresponds to a link or not. * @see #getLinkText(Object) */ protected boolean isLink(Object element) { String text = getLinkText(element); return text != null; } /** * @param element * a model element corresponding to the column of this label * provider. * @return the link text representation of the element, or {@code null}, if * no such link should be shown (This mean that a link text of * length 0 will also show a link-specific cursor representation). * @see #isLink(Object) */ protected abstract String getLinkText(Object element); /** * This method is invoked, if a link cell is activated by a mouse click * inside the link client area, i.e. if the column index corresponds to that * of this label provider and if {@link #isLink(Object)} returns {@code * true}. * * @param element * the model element */ protected abstract void linkActivated(Object element); /** * This method is invoked as part of {@link #update(ViewerCell)}, when * {@link #getLinkText(Object)} returns {@code null}. The default * implementation just sets empty text, style range, and fore ground color. * Subclasses may override and are not required to invoke the super class * method. * * @param cell * @see #update(ViewerCell) */ protected void updateNonLink(ViewerCell cell) { cell.setText(null); cell.setStyleRanges(null); cell.setForeground(null); } public void dispose() { if (viewerCtrl != null && !viewerCtrl.isDisposed()) { if (listener != null) { viewerCtrl.removeMouseTrackListener(listener); listener = null; } if (listener2 != null) { viewerCtrl.removeMouseMoveListener(listener2); listener2 = null; } if (listener3 != null) { viewerCtrl.removeMouseListener(listener3); listener3 = null; } linkCursor = null; busyCursor = null; linkColor = null; viewerCtrl = null; } super.dispose(); } }