[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index] [Newsgroup Home]
[news.eclipse.platform.rcp] Re: [jface.viewers] May CellLabelProvider.update be called by clients?

Tom Schindl wrote:
Hi,

Would you mind sharing the code (I suppose it's building ontop of your
snippet) then I'll take a look. I'm currently deep into Webservice
project (that's why I didn't had access to the source code) but I'll
give it a spin on the evening.

<nod> the code is attached. Note that there is a static final boolean USE_UPDATE, which should be switched to see the difference. The current
value is true, so it uses update directly.


Thanks for your time,

Daniel

/**
 * 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.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
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.Rectangle;
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;
import org.eclipse.swt.widgets.Widget;

/**
 * Demonstrate usage of {@link HyperlinkColumnLabelProvider}.
 * 
 * @author Daniel Kruegler
 * @since 3.4.2
 */
public class TreeViewerLinkColumnSnippet2 {

	public TreeViewerLinkColumnSnippet2(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 HotspotlinkColumnLabelProvider() {

			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 TreeViewerLinkColumnSnippet2(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
 * <code>null</code> 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)}.
 * <p>
 * This label provider implementation contains column-specific state, therefore
 * it cannot be used as a <em>single</em> label provider for a
 * {@link ColumnViewer}.
 * 
 * @author Daniel Kruegler
 */
abstract class HotspotlinkColumnLabelProvider extends StyledCellLabelProvider {
	
	private static final boolean USE_UPDATE = true;

	/**
	 * 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 Color activeLinkColor;

	private Styler styler;

	private Cursor linkCursor;

	private Cursor busyCursor;

	private ViewerCell activeCell;
	
	private DisposeListener itemDeletionListener = new DisposeListener() {

		public void widgetDisposed(DisposeEvent e) {
			setActiveCell(null);
		}

	};

	/**
	 * 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 void setActiveCell(ViewerCell activeCell) {
		ViewerCell oldCell = this.activeCell;
		
		if (oldCell != null) {
			Widget item = oldCell.getItem();
			if (!item.isDisposed()) {
				item.removeDisposeListener(itemDeletionListener);
			} else {
				oldCell = null;
			}
		}

		this.activeCell = activeCell;

		if (activeCell != null) {
			Widget item = activeCell.getItem();
			if (!item.isDisposed()) {
				item.addDisposeListener(itemDeletionListener);
			}
		}
		
		if (oldCell != null /*&& oldCell != activeCell*/) {
			if (USE_UPDATE) {
				update(oldCell);
			} else {
				Rectangle rect = oldCell.getBounds();
				oldCell.getControl().redraw(rect.x, rect.y, rect.width,
						rect.height, true);
			}
		}
		
		if (activeCell != null) {
			if (USE_UPDATE) {
				update(activeCell);
			} else {
				Rectangle rect = activeCell.getBounds();
				activeCell.getControl().redraw(rect.x, rect.y, rect.width,
						rect.height, true);
			}
		}
	}

	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 (activeCell != null && !activeCell.getItem().isDisposed()) {
				if (!cell.equals(activeCell)) {
					setActiveCell(null);
				}
			} else {
				setActiveCell(cell);
			}
			if (!linkEntered) {
				viewerCtrl.setCursor(linkCursor);
				linkEntered = true;
			}
		} else {
			resetCursor();
		}
	}

	private final void resetCursor() {
		if (linkEntered) {
			viewerCtrl.setCursor(null);
			linkEntered = false;
		}
		setActiveCell(null);
	}

	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 <em>before</em>
	 * 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 <em>before</em>
	 * the super class implementation has been called. This class does
	 * <em>not</em> 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 {@link LinkColor#Normal}.
	 * @see #initialize(ColumnViewer, ViewerColumn)
	 * @see #toColor(LinkColor, Display)
	 */
	public Color linkColor(Display disp) {
		return toColor(LinkColor.Normal, disp);
	}

	/**
	 * 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 <em>before</em>
	 * the super class implementation has been called. This class does
	 * <em>not</em> 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 active links. The default implementation uses
	 *         the color corresponding to {@link LinkColor#Active}.
	 * @see #initialize(ColumnViewer, ViewerColumn)
	 * @see #toColor(LinkColor, Display)
	 */
	public Color activeLinkColor(Display disp) {
		return toColor(LinkColor.Active, 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);
		Color theActiveLinkColor = activeLinkColor(disp);
		viewerCtrl = theControl;
		linkColor = theLinkColor;
		if (linkColor == null)
			linkColor = disp.getSystemColor(SWT.COLOR_BLUE);
		activeLinkColor = theActiveLinkColor;
		if (activeLinkColor == null)
			activeLinkColor = disp.getSystemColor(SWT.COLOR_RED);
		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());
			Color fg = activeCell != null ? activeLinkColor : linkColor;
			cell.setForeground(fg);
		} 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 <em>not</em> 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();
	}

}