/** * 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(); } }