/******************************************************************************* * Copyright (c) 2000, 2016 IBM Corporation 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: * IBM Corporation - initial API and implementation * Lars Vogel <Lars.Vogel@vogella.com> - Bug 430873, 475689 *******************************************************************************/ package org.eclipse.jface.viewers; import org.eclipse.core.runtime.Assert; import org.eclipse.core.runtime.ListenerList; import org.eclipse.jface.util.SafeRunnable; import org.eclipse.swt.events.HelpEvent; import org.eclipse.swt.events.HelpListener; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Item; /** * A viewer is a model-based adapter on a widget. * <p> * A viewer can be created as an adapter on a pre-existing control (e.g., * creating a <code>ListViewer</code> on an existing <code>List</code> control). * All viewers also provide a convenience constructor for creating the control. * </p> * <p> * Implementing a concrete viewer typically involves the following steps: * <ul> * <li> * create SWT controls for viewer (in constructor) (optional) * </li> * <li> * initialize SWT controls from input (inputChanged) * </li> * <li> * define viewer-specific update methods * </li> * <li> * support selections (<code>setSelection</code>, <code>getSelection</code>) * </li> * </ul> * </p> */ public abstract class Viewer implements IInputSelectionProvider { /** * List of selection change listeners (element type: <code>ISelectionChangedListener</code>). * * @see #fireSelectionChanged */ private ListenerList<ISelectionChangedListener> selectionChangedListeners = new ListenerList<>(); /** * List of help request listeners (element type: <code>org.eclipse.swt.events.HelpListener</code>). * Help request listeners. * * @see #handleHelpRequest */ private ListenerList<HelpListener> helpListeners = new ListenerList<>(); /** * The names of this viewer's properties. * <code>null</code> if this viewer has no properties. * * @see #setData */ private String[] keys; /** * The values of this viewer's properties. * <code>null</code> if this viewer has no properties. * This array parallels the value of the <code>keys</code> field. * * @see #setData */ private Object[] values; /** * Remembers whether we've hooked the help listener on the control or not. */ private boolean helpHooked = false; /** * Help listener for the control, created lazily when client's first help listener is added. */ private HelpListener helpListener = null; /** * Unique key for associating element data with widgets. * @see org.eclipse.swt.widgets.Widget#setData(String, Object) */ protected static final String WIDGET_DATA_KEY = "org.eclipse.jface.viewers.WIDGET_DATA";//$NON-NLS-1$ /** * Creates a new viewer. */ protected Viewer() { } /** * Adds a listener for help requests in this viewer. * Has no effect if an identical listener is already registered. * * @param listener a help listener */ public void addHelpListener(HelpListener listener) { helpListeners.add(listener); if (!helpHooked) { Control control = getControl(); if (control != null && !control.isDisposed()) { if (this.helpListener == null) { this.helpListener = this::handleHelpRequest; } control.addHelpListener(this.helpListener); helpHooked = true; } } } @Override public void addSelectionChangedListener(ISelectionChangedListener listener) { selectionChangedListeners.add(listener); } /** * Notifies any help listeners that help has been requested. * Only listeners registered at the time this method is called are notified. * * @param event a help event * * @see HelpListener#helpRequested(org.eclipse.swt.events.HelpEvent) */ protected void fireHelpRequested(HelpEvent event) { for (HelpListener listener : helpListeners) { listener.helpRequested(event); } } /** * Notifies any selection changed listeners that the viewer's selection has changed. * Only listeners registered at the time this method is called are notified. * * @param event a selection changed event * * @see ISelectionChangedListener#selectionChanged */ protected void fireSelectionChanged(final SelectionChangedEvent event) { for (ISelectionChangedListener l : selectionChangedListeners) { SafeRunnable.run(new SafeRunnable() { @Override public void run() { l.selectionChanged(event); } }); } } /** * Returns the primary control associated with this viewer. * * @return the SWT control which displays this viewer's content */ public abstract Control getControl(); /** * Returns the value of the property with the given name, * or <code>null</code> if the property is not found. * <p> * The default implementation performs a (linear) search of * an internal table. Overriding this method is generally not * required if the number of different keys is small. If a more * efficient representation of a viewer's properties is required, * override both <code>getData</code> and <code>setData</code>. * </p> * * @param key the property name * @return the property value, or <code>null</code> if * the property is not found */ public Object getData(String key) { Assert.isNotNull(key); if (keys == null) { return null; } for (int i = 0; i < keys.length; i++) { if (keys[i].equals(key)) { return values[i]; } } return null; } @Override public abstract Object getInput(); @Override public abstract ISelection getSelection(); /** * Handles a help request from the underlying SWT control. * The default behavior is to fire a help request, * with the event's data modified to hold this viewer. * @param event the event * */ protected void handleHelpRequest(HelpEvent event) { Object oldData = event.data; event.data = this; fireHelpRequested(event); event.data = oldData; } /** * Internal hook method called when the input to this viewer is * initially set or subsequently changed. * <p> * The default implementation does nothing. Subclassers may override * this method to do something when a viewer's input is set. * A typical use is populate the viewer. * </p> * * @param input the new input of this viewer, or <code>null</code> if none * @param oldInput the old input element or <code>null</code> if there * was previously no input */ protected void inputChanged(Object input, Object oldInput) { } /** * Refreshes this viewer completely with information freshly obtained from this * viewer's model. */ public abstract void refresh(); /** * Removes the given help listener from this viewer. * Has no effect if an identical listener is not registered. * * @param listener a help listener */ public void removeHelpListener(HelpListener listener) { helpListeners.remove(listener); if (helpListeners.size() == 0) { Control control = getControl(); if (control != null && !control.isDisposed()) { control.removeHelpListener(this.helpListener); helpHooked = false; } } } @Override public void removeSelectionChangedListener( ISelectionChangedListener listener) { selectionChangedListeners.remove(listener); } /** * Scrolls the viewer's control down by one item from the given * display-relative coordinates. Returns the newly revealed Item, * or <code>null</code> if no scrolling occurred or if the viewer * doesn't represent an item-based widget. * * @param x horizontal coordinate * @param y vertical coordinate * @return the item scrolled down to */ public Item scrollDown(int x, int y) { return null; } /** * Scrolls the viewer's control up by one item from the given * display-relative coordinates. Returns the newly revealed Item, * or <code>null</code> if no scrolling occurred or if the viewer * doesn't represent an item-based widget. * * @param x horizontal coordinate * @param y vertical coordinate * @return the item scrolled up to */ public Item scrollUp(int x, int y) { return null; } /** * Sets the value of the property with the given name to the * given value, or to <code>null</code> if the property is to be * removed. If this viewer has such a property, its value is * replaced; otherwise a new property is added. * <p> * The default implementation records properties in an internal * table which is searched linearly. Overriding this method is generally not * required if the number of different keys is small. If a more * efficient representation of a viewer's properties is required, * override both <code>getData</code> and <code>setData</code>. * </p> * * @param key the property name * @param value the property value, or <code>null</code> if * the property is not found */ public void setData(String key, Object value) { Assert.isNotNull(key); /* Remove the key/value pair */ if (value == null) { if (keys == null) { return; } int index = 0; while (index < keys.length && !keys[index].equals(key)) { index++; } if (index == keys.length) { return; } if (keys.length == 1) { keys = null; values = null; } else { String[] newKeys = new String[keys.length - 1]; Object[] newValues = new Object[values.length - 1]; System.arraycopy(keys, 0, newKeys, 0, index); System.arraycopy(keys, index + 1, newKeys, index, newKeys.length - index); System.arraycopy(values, 0, newValues, 0, index); System.arraycopy(values, index + 1, newValues, index, newValues.length - index); keys = newKeys; values = newValues; } return; } /* Add the key/value pair */ if (keys == null) { keys = new String[] { key }; values = new Object[] { value }; return; } for (int i = 0; i < keys.length; i++) { if (keys[i].equals(key)) { values[i] = value; return; } } String[] newKeys = new String[keys.length + 1]; Object[] newValues = new Object[values.length + 1]; System.arraycopy(keys, 0, newKeys, 0, keys.length); System.arraycopy(values, 0, newValues, 0, values.length); newKeys[keys.length] = key; newValues[values.length] = value; keys = newKeys; values = newValues; } /** * Sets or clears the input for this viewer. * * @param input the input of this viewer, or <code>null</code> if none */ public abstract void setInput(Object input); /** * The viewer implementation of this <code>ISelectionProvider</code> * method make the new selection for this viewer without making it visible. * <p> * This method is equivalent to <code>setSelection(selection,false)</code>. * </p> * <p> * Note that some implementations may not be able to set the selection * without also revealing it, for example (as of 3.3) TreeViewer. * </p> */ @Override public void setSelection(ISelection selection) { setSelection(selection, false); } /** * Sets a new selection for this viewer and optionally makes it visible. * <p> * Subclasses must implement this method. * </p> * * @param selection the new selection * @param reveal <code>true</code> if the selection is to be made * visible, and <code>false</code> otherwise */ public abstract void setSelection(ISelection selection, boolean reveal); }