blob: 856a4906fbbd19ff88c35fa1f0908b72fd6371ba [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2006, 2008 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
* Tom Schindl <tom.schindl@bestsolution.at> - refactoring (bug 153993)
* fix in bug: 151295,178946,166500,195908,201906,207676,180504,216706,218336
*******************************************************************************/
package org.eclipse.jface.viewers;
import org.eclipse.core.runtime.ListenerList;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.FocusAdapter;
import org.eclipse.swt.events.FocusEvent;
import org.eclipse.swt.events.FocusListener;
import org.eclipse.swt.events.MouseAdapter;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseListener;
import org.eclipse.swt.events.TraverseEvent;
import org.eclipse.swt.events.TraverseListener;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Item;
/**
* This is the base for all editor implementations of Viewers. ColumnViewer
* implementors have to subclass this class and implement the missing methods
*
* @since 3.3
* @see TableViewerEditor
* @see TreeViewerEditor
*/
public abstract class ColumnViewerEditor {
private CellEditor cellEditor;
private ICellEditorListener cellEditorListener;
private FocusListener focusListener;
private MouseListener mouseListener;
private ColumnViewer viewer;
private TraverseListener tabeditingListener;
private ViewerCell cell;
private ListenerList editorActivationListener;
private ColumnViewerEditorActivationStrategy editorActivationStrategy;
private boolean inEditorDeactivation;
private DisposeListener disposeListener;
/**
* Tabbing from cell to cell is turned off
*/
public static final int DEFAULT = 1;
/**
* Should if the end of the row is reach started from the start/end of the
* row below/above
*/
public static final int TABBING_MOVE_TO_ROW_NEIGHBOR = 1 << 1;
/**
* Should if the end of the row is reach started from the beginning in the
* same row
*/
public static final int TABBING_CYCLE_IN_ROW = 1 << 2;
/**
* Support tabbing to Cell above/below the current cell
*/
public static final int TABBING_VERTICAL = 1 << 3;
/**
* Should tabbing from column to column with in one row be supported
*/
public static final int TABBING_HORIZONTAL = 1 << 4;
/**
* Style mask used to enable keyboard activation
*/
public static final int KEYBOARD_ACTIVATION = 1 << 5;
/**
* Style mask used to turn <b>off</b> the feature that an editor activation
* is canceled on double click. It is also possible to turn off this feature
* per cell-editor using {@link CellEditor#getDoubleClickTimeout()}
*/
public static final int KEEP_EDITOR_ON_DOUBLE_CLICK = 1 << 6;
private int feature;
/**
* @param viewer
* the viewer this editor is attached to
* @param editorActivationStrategy
* the strategy used to decide about editor activation
* @param feature
* bit mask controlling the editor
* <ul>
* <li>{@link ColumnViewerEditor#DEFAULT}</li>
* <li>{@link ColumnViewerEditor#TABBING_CYCLE_IN_ROW}</li>
* <li>{@link ColumnViewerEditor#TABBING_HORIZONTAL}</li>
* <li>{@link ColumnViewerEditor#TABBING_MOVE_TO_ROW_NEIGHBOR}</li>
* <li>{@link ColumnViewerEditor#TABBING_VERTICAL}</li>
* </ul>
*/
protected ColumnViewerEditor(final ColumnViewer viewer,
ColumnViewerEditorActivationStrategy editorActivationStrategy,
int feature) {
this.viewer = viewer;
this.editorActivationStrategy = editorActivationStrategy;
if ((feature & KEYBOARD_ACTIVATION) == KEYBOARD_ACTIVATION) {
this.editorActivationStrategy
.setEnableEditorActivationWithKeyboard(true);
}
this.feature = feature;
this.disposeListener = new DisposeListener() {
public void widgetDisposed(DisposeEvent e) {
if( viewer.isCellEditorActive() ) {
cancelEditing();
}
}
};
initCellEditorListener();
}
private void initCellEditorListener() {
cellEditorListener = new ICellEditorListener() {
public void editorValueChanged(boolean oldValidState,
boolean newValidState) {
// Ignore.
}
public void cancelEditor() {
ColumnViewerEditor.this.cancelEditing();
}
public void applyEditorValue() {
ColumnViewerEditor.this.applyEditorValue();
}
};
}
private boolean activateCellEditor(final ColumnViewerEditorActivationEvent activationEvent) {
ViewerColumn part = viewer.getViewerColumn(cell.getColumnIndex());
Object element = cell.getElement();
if (part != null && part.getEditingSupport() != null
&& part.getEditingSupport().canEdit(element)) {
cellEditor = part.getEditingSupport().getCellEditor(element);
if (cellEditor != null) {
int timeout = cellEditor.getDoubleClickTimeout();
final int activationTime;
if (timeout != 0) {
activationTime = activationEvent.time + timeout;
} else {
activationTime = 0;
}
if (editorActivationListener != null
&& !editorActivationListener.isEmpty()) {
Object[] ls = editorActivationListener.getListeners();
for (int i = 0; i < ls.length; i++) {
((ColumnViewerEditorActivationListener) ls[i])
.beforeEditorActivated(activationEvent);
// Was the activation canceled ?
if (activationEvent.cancel) {
return false;
}
}
}
updateFocusCell(cell, activationEvent);
cellEditor.addListener(cellEditorListener);
part.getEditingSupport().initializeCellEditorValue(cellEditor,
cell);
// Tricky flow of control here:
// activate() can trigger callback to cellEditorListener which
// will clear cellEditor
// so must get control first, but must still call activate()
// even if there is no control.
final Control control = cellEditor.getControl();
cellEditor.activate(activationEvent);
if (control == null) {
return false;
}
setLayoutData(cellEditor.getLayoutData());
setEditor(control, (Item) cell.getItem(), cell.getColumnIndex());
cellEditor.setFocus();
if (cellEditor.dependsOnExternalFocusListener()) {
if (focusListener == null) {
focusListener = new FocusAdapter() {
public void focusLost(FocusEvent e) {
applyEditorValue();
}
};
}
control.addFocusListener(focusListener);
}
mouseListener = new MouseAdapter() {
public void mouseDown(MouseEvent e) {
// time wrap?
// check for expiration of doubleClickTime
if (shouldFireDoubleClick(activationTime, e.time, activationEvent) && e.button == 1) {
control.removeMouseListener(mouseListener);
cancelEditing();
handleDoubleClickEvent();
} else if (mouseListener != null) {
control.removeMouseListener(mouseListener);
}
}
};
if (activationTime != 0
&& (feature & KEEP_EDITOR_ON_DOUBLE_CLICK) == 0) {
control.addMouseListener(mouseListener);
}
if (tabeditingListener == null) {
tabeditingListener = new TraverseListener() {
public void keyTraversed(TraverseEvent e) {
if ((feature & DEFAULT) != DEFAULT) {
processTraverseEvent(cell.getColumnIndex(),
viewer.getViewerRowFromItem(cell
.getItem()), e);
}
}
};
}
control.addTraverseListener(tabeditingListener);
if (editorActivationListener != null
&& !editorActivationListener.isEmpty()) {
Object[] ls = editorActivationListener.getListeners();
for (int i = 0; i < ls.length; i++) {
((ColumnViewerEditorActivationListener) ls[i])
.afterEditorActivated(activationEvent);
}
}
this.cell.getItem().addDisposeListener(disposeListener);
return true;
}
}
return false;
}
private boolean shouldFireDoubleClick(int activationTime, int mouseTime,
ColumnViewerEditorActivationEvent activationEvent) {
return mouseTime <= activationTime
&& activationEvent.eventType != ColumnViewerEditorActivationEvent.KEY_PRESSED
&& activationEvent.eventType != ColumnViewerEditorActivationEvent.PROGRAMMATIC
&& activationEvent.eventType != ColumnViewerEditorActivationEvent.TRAVERSAL;
}
/**
* Applies the current value and deactivates the currently active cell
* editor.
*/
void applyEditorValue() {
// avoid re-entering
if (!inEditorDeactivation) {
try {
inEditorDeactivation = true;
CellEditor c = this.cellEditor;
if (c != null && this.cell != null) {
ColumnViewerEditorDeactivationEvent tmp = new ColumnViewerEditorDeactivationEvent(
cell);
tmp.eventType = ColumnViewerEditorDeactivationEvent.EDITOR_SAVED;
if (editorActivationListener != null
&& !editorActivationListener.isEmpty()) {
Object[] ls = editorActivationListener.getListeners();
for (int i = 0; i < ls.length; i++) {
((ColumnViewerEditorActivationListener) ls[i])
.beforeEditorDeactivated(tmp);
}
}
Item t = (Item) this.cell.getItem();
// don't null out table item -- same item is still selected
if (t != null && !t.isDisposed()) {
saveEditorValue(c);
}
if (!viewer.getControl().isDisposed()) {
setEditor(null, null, 0);
}
c.removeListener(cellEditorListener);
Control control = c.getControl();
if (control != null && !control.isDisposed()) {
if (mouseListener != null) {
control.removeMouseListener(mouseListener);
// Clear the instance not needed any more
mouseListener = null;
}
if (focusListener != null) {
control.removeFocusListener(focusListener);
}
if (tabeditingListener != null) {
control.removeTraverseListener(tabeditingListener);
}
}
c.deactivate(tmp);
if (editorActivationListener != null
&& !editorActivationListener.isEmpty()) {
Object[] ls = editorActivationListener.getListeners();
for (int i = 0; i < ls.length; i++) {
((ColumnViewerEditorActivationListener) ls[i])
.afterEditorDeactivated(tmp);
}
}
if( ! this.cell.getItem().isDisposed() ) {
this.cell.getItem().removeDisposeListener(disposeListener);
}
}
this.cellEditor = null;
this.cell = null;
} finally {
inEditorDeactivation = false;
}
}
}
/**
* Cancel editing
*/
void cancelEditing() {
// avoid re-entering
if (!inEditorDeactivation) {
try {
inEditorDeactivation = true;
if (cellEditor != null) {
ColumnViewerEditorDeactivationEvent tmp = new ColumnViewerEditorDeactivationEvent(
cell);
tmp.eventType = ColumnViewerEditorDeactivationEvent.EDITOR_CANCELED;
if (editorActivationListener != null
&& !editorActivationListener.isEmpty()) {
Object[] ls = editorActivationListener.getListeners();
for (int i = 0; i < ls.length; i++) {
((ColumnViewerEditorActivationListener) ls[i])
.beforeEditorDeactivated(tmp);
}
}
if (!viewer.getControl().isDisposed()) {
setEditor(null, null, 0);
}
cellEditor.removeListener(cellEditorListener);
Control control = cellEditor.getControl();
if (control != null && !viewer.getControl().isDisposed()) {
if (mouseListener != null) {
control.removeMouseListener(mouseListener);
// Clear the instance not needed any more
mouseListener = null;
}
if (focusListener != null) {
control.removeFocusListener(focusListener);
}
if (tabeditingListener != null) {
control.removeTraverseListener(tabeditingListener);
}
}
CellEditor oldEditor = cellEditor;
oldEditor.deactivate(tmp);
if (editorActivationListener != null
&& !editorActivationListener.isEmpty()) {
Object[] ls = editorActivationListener.getListeners();
for (int i = 0; i < ls.length; i++) {
((ColumnViewerEditorActivationListener) ls[i])
.afterEditorDeactivated(tmp);
}
}
if( ! this.cell.getItem().isDisposed() ) {
this.cell.getItem().addDisposeListener(disposeListener);
}
this.cellEditor = null;
this.cell = null;
}
} finally {
inEditorDeactivation = false;
}
}
}
/**
* Enable the editor by mouse down
*
* @param event
*/
void handleEditorActivationEvent(ColumnViewerEditorActivationEvent event) {
// Only activate if the event isn't tagged as canceled
if (!event.cancel
&& editorActivationStrategy.isEditorActivationEvent(event)) {
if (cellEditor != null) {
applyEditorValue();
}
this.cell = (ViewerCell) event.getSource();
if( ! activateCellEditor(event) ) {
this.cell = null;
this.cellEditor = null;
}
}
}
private void saveEditorValue(CellEditor cellEditor) {
ViewerColumn part = viewer.getViewerColumn(cell.getColumnIndex());
if (part != null && part.getEditingSupport() != null) {
part.getEditingSupport().saveCellEditorValue(cellEditor, cell);
}
}
/**
* Return whether there is an active cell editor.
*
* @return <code>true</code> if there is an active cell editor; otherwise
* <code>false</code> is returned.
*/
boolean isCellEditorActive() {
return cellEditor != null;
}
void handleDoubleClickEvent() {
viewer.fireDoubleClick(new DoubleClickEvent(viewer, viewer
.getSelection()));
viewer.fireOpen(new OpenEvent(viewer, viewer.getSelection()));
}
/**
* Adds the given listener, it is to be notified when the cell editor is
* activated or deactivated.
*
* @param listener
* the listener to add
*/
public void addEditorActivationListener(
ColumnViewerEditorActivationListener listener) {
if (editorActivationListener == null) {
editorActivationListener = new ListenerList();
}
editorActivationListener.add(listener);
}
/**
* Removes the given listener.
*
* @param listener
* the listener to remove
*/
public void removeEditorActivationListener(
ColumnViewerEditorActivationListener listener) {
if (editorActivationListener != null) {
editorActivationListener.remove(listener);
}
}
/**
* Process the traverse event and opens the next available editor depending
* of the implemented strategy. The default implementation uses the style
* constants
* <ul>
* <li>{@link ColumnViewerEditor#TABBING_MOVE_TO_ROW_NEIGHBOR}</li>
* <li>{@link ColumnViewerEditor#TABBING_CYCLE_IN_ROW}</li>
* <li>{@link ColumnViewerEditor#TABBING_VERTICAL}</li>
* <li>{@link ColumnViewerEditor#TABBING_HORIZONTAL}</li>
* </ul>
*
* <p>
* Subclasses may overwrite to implement their custom logic to edit the next
* cell
* </p>
*
* @param columnIndex
* the index of the current column
* @param row
* the current row - may only be used for the duration of this
* method call
* @param event
* the traverse event
*/
protected void processTraverseEvent(int columnIndex, ViewerRow row,
TraverseEvent event) {
ViewerCell cell2edit = null;
if (event.detail == SWT.TRAVERSE_TAB_PREVIOUS) {
event.doit = false;
if ((event.stateMask & SWT.CTRL) == SWT.CTRL
&& (feature & TABBING_VERTICAL) == TABBING_VERTICAL) {
cell2edit = searchCellAboveBelow(row, viewer, columnIndex, true);
} else if ((feature & TABBING_HORIZONTAL) == TABBING_HORIZONTAL) {
cell2edit = searchPreviousCell(row, row.getCell(columnIndex),
row.getCell(columnIndex), viewer);
}
} else if (event.detail == SWT.TRAVERSE_TAB_NEXT) {
event.doit = false;
if ((event.stateMask & SWT.CTRL) == SWT.CTRL
&& (feature & TABBING_VERTICAL) == TABBING_VERTICAL) {
cell2edit = searchCellAboveBelow(row, viewer, columnIndex,
false);
} else if ((feature & TABBING_HORIZONTAL) == TABBING_HORIZONTAL) {
cell2edit = searchNextCell(row, row.getCell(columnIndex), row
.getCell(columnIndex), viewer);
}
}
if (cell2edit != null) {
viewer.getControl().setRedraw(false);
ColumnViewerEditorActivationEvent acEvent = new ColumnViewerEditorActivationEvent(
cell2edit, event);
viewer.triggerEditorActivationEvent(acEvent);
viewer.getControl().setRedraw(true);
}
}
private ViewerCell searchCellAboveBelow(ViewerRow row, ColumnViewer viewer,
int columnIndex, boolean above) {
ViewerCell rv = null;
ViewerRow newRow = null;
if (above) {
newRow = row.getNeighbor(ViewerRow.ABOVE, false);
} else {
newRow = row.getNeighbor(ViewerRow.BELOW, false);
}
if (newRow != null) {
ViewerColumn column = viewer.getViewerColumn(columnIndex);
if (column != null
&& column.getEditingSupport() != null
&& column.getEditingSupport().canEdit(
newRow.getItem().getData())) {
rv = newRow.getCell(columnIndex);
} else {
rv = searchCellAboveBelow(newRow, viewer, columnIndex, above);
}
}
return rv;
}
private boolean isCellEditable(ColumnViewer viewer, ViewerCell cell) {
ViewerColumn column = viewer.getViewerColumn(cell.getColumnIndex());
return column != null && column.getEditingSupport() != null
&& column.getEditingSupport().canEdit(cell.getElement());
}
private ViewerCell searchPreviousCell(ViewerRow row,
ViewerCell currentCell, ViewerCell originalCell, ColumnViewer viewer) {
ViewerCell rv = null;
ViewerCell previousCell;
if (currentCell != null) {
previousCell = currentCell.getNeighbor(ViewerCell.LEFT, true);
} else {
if (row.getColumnCount() != 0) {
previousCell = row.getCell(row.getCreationIndex(row
.getColumnCount() - 1));
} else {
previousCell = row.getCell(0);
}
}
// No endless loop
if (originalCell.equals(previousCell)) {
return null;
}
if (previousCell != null) {
if (isCellEditable(viewer, previousCell)) {
rv = previousCell;
} else {
rv = searchPreviousCell(row, previousCell, originalCell, viewer);
}
} else {
if ((feature & TABBING_CYCLE_IN_ROW) == TABBING_CYCLE_IN_ROW) {
rv = searchPreviousCell(row, null, originalCell, viewer);
} else if ((feature & TABBING_MOVE_TO_ROW_NEIGHBOR) == TABBING_MOVE_TO_ROW_NEIGHBOR) {
ViewerRow rowAbove = row.getNeighbor(ViewerRow.ABOVE, false);
if (rowAbove != null) {
rv = searchPreviousCell(rowAbove, null, originalCell,
viewer);
}
}
}
return rv;
}
private ViewerCell searchNextCell(ViewerRow row, ViewerCell currentCell,
ViewerCell originalCell, ColumnViewer viewer) {
ViewerCell rv = null;
ViewerCell nextCell;
if (currentCell != null) {
nextCell = currentCell.getNeighbor(ViewerCell.RIGHT, true);
} else {
nextCell = row.getCell(row.getCreationIndex(0));
}
// No endless loop
if (originalCell.equals(nextCell)) {
return null;
}
if (nextCell != null) {
if (isCellEditable(viewer, nextCell)) {
rv = nextCell;
} else {
rv = searchNextCell(row, nextCell, originalCell, viewer);
}
} else {
if ((feature & TABBING_CYCLE_IN_ROW) == TABBING_CYCLE_IN_ROW) {
rv = searchNextCell(row, null, originalCell, viewer);
} else if ((feature & TABBING_MOVE_TO_ROW_NEIGHBOR) == TABBING_MOVE_TO_ROW_NEIGHBOR) {
ViewerRow rowBelow = row.getNeighbor(ViewerRow.BELOW, false);
if (rowBelow != null) {
rv = searchNextCell(rowBelow, null, originalCell, viewer);
}
}
}
return rv;
}
/**
* Position the editor inside the control
*
* @param w
* the editor control
* @param item
* the item (row) in which the editor is drawn in
* @param fColumnNumber
* the column number in which the editor is shown
*/
protected abstract void setEditor(Control w, Item item, int fColumnNumber);
/**
* set the layout data for the editor
*
* @param layoutData
* the layout data used when editor is displayed
*/
protected abstract void setLayoutData(CellEditor.LayoutData layoutData);
/**
* @param focusCell
* updates the cell with the current input focus
* @param event
* the event requesting to update the focusCell
*/
protected abstract void updateFocusCell(ViewerCell focusCell,
ColumnViewerEditorActivationEvent event);
/**
* @return the cell currently holding the focus if no cell has the focus or
* the viewer implementation doesn't support <code>null</code> is
* returned
*
*/
public ViewerCell getFocusCell() {
return null;
}
/**
* @return the viewer working for
*/
protected ColumnViewer getViewer() {
return viewer;
}
}