1 /*******************************************************************************
2  * Copyright (c) 2006, 2016 IBM Corporation and others.
3  *
4  * This program and the accompanying materials
5  * are made available under the terms of the Eclipse Public License 2.0
6  * which accompanies this distribution, and is available at
7  * https://www.eclipse.org/legal/epl-2.0/
8  *
9  * SPDX-License-Identifier: EPL-2.0
10  *
11  * Contributors:
12  *     IBM Corporation - initial API and implementation
13  *     Tom Schindl <tom.schindl@bestsolution.at> - refactoring (bug 153993)
14  *     											   fix in bug: 151295,178946,166500,195908,201906,207676,180504,216706,218336
15  *     Tony Juckel -  Bug 130854 - JFace TableViewer ignoring ICellEditor validator state
16  *     Andrew Obuchowicz <aobuchow@redhat.com> - Bug 549194: Add TABBING_CYCLE_IN_VIEWER
17  *******************************************************************************/
18 
19 package org.eclipse.jface.viewers;
20 
21 import org.eclipse.core.runtime.ListenerList;
22 import org.eclipse.swt.SWT;
23 import org.eclipse.swt.events.DisposeListener;
24 import org.eclipse.swt.events.FocusAdapter;
25 import org.eclipse.swt.events.FocusEvent;
26 import org.eclipse.swt.events.FocusListener;
27 import org.eclipse.swt.events.MouseAdapter;
28 import org.eclipse.swt.events.MouseEvent;
29 import org.eclipse.swt.events.MouseListener;
30 import org.eclipse.swt.events.TraverseEvent;
31 import org.eclipse.swt.events.TraverseListener;
32 import org.eclipse.swt.widgets.Control;
33 import org.eclipse.swt.widgets.Item;
34 
35 /**
36  * This is the base for all editor implementations of Viewers. ColumnViewer
37  * implementors have to subclass this class and implement the missing methods
38  *
39  * @since 3.3
40  * @see TableViewerEditor
41  * @see TreeViewerEditor
42  */
43 public abstract class ColumnViewerEditor {
44 	private CellEditor cellEditor;
45 
46 	private ICellEditorListener cellEditorListener;
47 
48 	private FocusListener focusListener;
49 
50 	private MouseListener mouseListener;
51 
52 	private ColumnViewer viewer;
53 
54 	private TraverseListener tabeditingListener;
55 
56 	private ViewerCell cell;
57 
58 	private ListenerList<ColumnViewerEditorActivationListener> editorActivationListener;
59 
60 	private ColumnViewerEditorActivationStrategy editorActivationStrategy;
61 
62 	private boolean inEditorDeactivation;
63 
64 	private DisposeListener disposeListener;
65 
66 	/**
67 	 * Tabbing from cell to cell is turned off
68 	 */
69 	public static final int DEFAULT = 1;
70 
71 	/**
72 	 * Should if the end of the row is reach started from the start/end of the
73 	 * row below/above
74 	 */
75 	public static final int TABBING_MOVE_TO_ROW_NEIGHBOR = 1 << 1;
76 
77 	/**
78 	 * Should if the end of the row is reach started from the beginning in the
79 	 * same row
80 	 */
81 	public static final int TABBING_CYCLE_IN_ROW = 1 << 2;
82 
83 	/**
84 	 * Support tabbing to Cell above/below the current cell
85 	 */
86 	public static final int TABBING_VERTICAL = 1 << 3;
87 
88 	/**
89 	 * Should tabbing from column to column with in one row be supported
90 	 */
91 	public static final int TABBING_HORIZONTAL = 1 << 4;
92 
93 	/**
94 	 * Style mask used to enable keyboard activation
95 	 */
96 	public static final int KEYBOARD_ACTIVATION = 1 << 5;
97 
98 	/**
99 	 * Style mask used to turn <b>off</b> the feature that an editor activation
100 	 * is canceled on double click. It is also possible to turn off this feature
101 	 * per cell-editor using {@link CellEditor#getDoubleClickTimeout()}
102 	 * @since 3.4
103 	 */
104 	public static final int KEEP_EDITOR_ON_DOUBLE_CLICK = 1 << 6;
105 
106 	/**
107 	 * Should if the end of the viewer is reached, start from the first element in
108 	 * the viewer (conversely, hitting shift-tab in the first element of the viewer
109 	 * results in jumping to the last element of the viewer)
110 	 *
111 	 * @since 3.17
112 	 */
113 	public static final int TABBING_CYCLE_IN_VIEWER = 1 << 7;
114 
115 
116 	private int feature;
117 
118 	/**
119 	 * @param viewer                   the viewer this editor is attached to
120 	 * @param editorActivationStrategy the strategy used to decide about editor
121 	 *                                 activation
122 	 * @param feature                  bit mask controlling the editor
123 	 *                                 <ul>
124 	 *                                 <li>{@link ColumnViewerEditor#DEFAULT}</li>
125 	 *                                 <li>{@link ColumnViewerEditor#TABBING_CYCLE_IN_ROW}</li>
126 	 *                                 <li>{@link ColumnViewerEditor#TABBING_HORIZONTAL}</li>
127 	 *                                 <li>{@link ColumnViewerEditor#TABBING_MOVE_TO_ROW_NEIGHBOR}</li>
128 	 *                                 <li>{@link ColumnViewerEditor#TABBING_VERTICAL}</li>
129 	 *                                 <li>{@link ColumnViewerEditor#TABBING_CYCLE_IN_VIEWER}</li>
130 	 *                                 </ul>
131 	 */
ColumnViewerEditor(final ColumnViewer viewer, ColumnViewerEditorActivationStrategy editorActivationStrategy, int feature)132 	protected ColumnViewerEditor(final ColumnViewer viewer,
133 			ColumnViewerEditorActivationStrategy editorActivationStrategy,
134 			int feature) {
135 		this.viewer = viewer;
136 		this.editorActivationStrategy = editorActivationStrategy;
137 		if ((feature & KEYBOARD_ACTIVATION) == KEYBOARD_ACTIVATION) {
138 			this.editorActivationStrategy
139 					.setEnableEditorActivationWithKeyboard(true);
140 		}
141 		this.feature = feature;
142 		this.disposeListener = e -> {
143 			if( viewer.isCellEditorActive() ) {
144 				cancelEditing();
145 			}
146 		};
147 		initCellEditorListener();
148 	}
149 
initCellEditorListener()150 	private void initCellEditorListener() {
151 		cellEditorListener = new ICellEditorListener() {
152 			@Override
153 			public void editorValueChanged(boolean oldValidState,
154 					boolean newValidState) {
155 				// Ignore.
156 			}
157 
158 			@Override
159 			public void cancelEditor() {
160 				ColumnViewerEditor.this.cancelEditing();
161 			}
162 
163 			@Override
164 			public void applyEditorValue() {
165 				ColumnViewerEditor.this.applyEditorValue();
166 			}
167 		};
168 	}
169 
activateCellEditor(final ColumnViewerEditorActivationEvent activationEvent)170 	private boolean activateCellEditor(final ColumnViewerEditorActivationEvent activationEvent) {
171 
172 		ViewerColumn part = viewer.getViewerColumn(cell.getColumnIndex());
173 		Object element = cell.getElement();
174 
175 		if (part != null && part.getEditingSupport() != null
176 				&& part.getEditingSupport().canEdit(element)) {
177 			cellEditor = part.getEditingSupport().getCellEditor(element);
178 			if (cellEditor != null) {
179 				int timeout = cellEditor.getDoubleClickTimeout();
180 
181 				final int activationTime;
182 
183 				if (timeout != 0) {
184 					activationTime = activationEvent.time + timeout;
185 				} else {
186 					activationTime = 0;
187 				}
188 
189 				if (editorActivationListener != null && !editorActivationListener.isEmpty()) {
190 					for (ColumnViewerEditorActivationListener ls : editorActivationListener) {
191 						ls.beforeEditorActivated(activationEvent);
192 
193 						// Was the activation canceled ?
194 						if (activationEvent.cancel) {
195 							return false;
196 						}
197 					}
198 				}
199 
200 				updateFocusCell(cell, activationEvent);
201 
202 				cellEditor.addListener(cellEditorListener);
203 				part.getEditingSupport().initializeCellEditorValue(cellEditor,
204 						cell);
205 
206 				// Tricky flow of control here:
207 				// activate() can trigger callback to cellEditorListener which
208 				// will clear cellEditor
209 				// so must get control first, but must still call activate()
210 				// even if there is no control.
211 				final Control control = cellEditor.getControl();
212 				cellEditor.activate(activationEvent);
213 				if (control == null) {
214 					return false;
215 				}
216 				setLayoutData(cellEditor.getLayoutData());
217 				setEditor(control, (Item) cell.getItem(), cell.getColumnIndex());
218 				cellEditor.setFocus();
219 
220 				if (cellEditor.dependsOnExternalFocusListener()) {
221 					if (focusListener == null) {
222 						focusListener = new FocusAdapter() {
223 							@Override
224 							public void focusLost(FocusEvent e) {
225 								applyEditorValue();
226 							}
227 						};
228 					}
229 					control.addFocusListener(focusListener);
230 				}
231 
232 				mouseListener = new MouseAdapter() {
233 					@Override
234 					public void mouseDown(MouseEvent e) {
235 						// time wrap?
236 						// check for expiration of doubleClickTime
237 						if (shouldFireDoubleClick(activationTime, e.time, activationEvent) && e.button == 1) {
238 							control.removeMouseListener(mouseListener);
239 							cancelEditing();
240 							handleDoubleClickEvent();
241 						} else if (mouseListener != null) {
242 							control.removeMouseListener(mouseListener);
243 						}
244 					}
245 				};
246 
247 				if (activationTime != 0
248 						&& (feature & KEEP_EDITOR_ON_DOUBLE_CLICK) == 0) {
249 					control.addMouseListener(mouseListener);
250 				}
251 
252 				if (tabeditingListener == null) {
253 					tabeditingListener = e -> {
254 						if ((feature & DEFAULT) != DEFAULT) {
255 							processTraverseEvent(cell.getColumnIndex(),
256 									viewer.getViewerRowFromItem(cell
257 											.getItem()), e);
258 						}
259 					};
260 				}
261 
262 				control.addTraverseListener(tabeditingListener);
263 
264 				if (editorActivationListener != null && !editorActivationListener.isEmpty()) {
265 					for (ColumnViewerEditorActivationListener ls : editorActivationListener) {
266 						ls.afterEditorActivated(activationEvent);
267 					}
268 				}
269 
270 				this.cell.getItem().addDisposeListener(disposeListener);
271 
272 				return true;
273 			}
274 
275 		}
276 
277 		return false;
278 	}
279 
shouldFireDoubleClick(int activationTime, int mouseTime, ColumnViewerEditorActivationEvent activationEvent)280 	private boolean shouldFireDoubleClick(int activationTime, int mouseTime,
281 			ColumnViewerEditorActivationEvent activationEvent) {
282 		return mouseTime <= activationTime
283 				&& activationEvent.eventType != ColumnViewerEditorActivationEvent.KEY_PRESSED
284 				&& activationEvent.eventType != ColumnViewerEditorActivationEvent.PROGRAMMATIC
285 				&& activationEvent.eventType != ColumnViewerEditorActivationEvent.TRAVERSAL;
286 	}
287 
288 	/**
289 	 * Applies the current value and deactivates the currently active cell
290 	 * editor.
291 	 */
applyEditorValue()292 	void applyEditorValue() {
293 		// avoid re-entering
294 		if (!inEditorDeactivation) {
295 			try {
296 				inEditorDeactivation = true;
297 				CellEditor c = this.cellEditor;
298 				if (c != null && this.cell != null) {
299 					ColumnViewerEditorDeactivationEvent tmp = new ColumnViewerEditorDeactivationEvent(
300 							cell);
301 					tmp.eventType = ColumnViewerEditorDeactivationEvent.EDITOR_SAVED;
302 					if (editorActivationListener != null && !editorActivationListener.isEmpty()) {
303 						for (ColumnViewerEditorActivationListener ls : editorActivationListener) {
304 							ls.beforeEditorDeactivated(tmp);
305 						}
306 					}
307 
308 					Item t = (Item) this.cell.getItem();
309 
310 					// don't null out table item -- same item is still selected
311 					if (t != null && !t.isDisposed() && c.isValueValid()) {
312 						saveEditorValue(c);
313 					}
314 					if (!viewer.getControl().isDisposed()) {
315 						setEditor(null, null, 0);
316 					}
317 
318 					c.removeListener(cellEditorListener);
319 					Control control = c.getControl();
320 					if (control != null && !control.isDisposed()) {
321 						if (mouseListener != null) {
322 							control.removeMouseListener(mouseListener);
323 							// Clear the instance not needed any more
324 							mouseListener = null;
325 						}
326 						if (focusListener != null) {
327 							control.removeFocusListener(focusListener);
328 						}
329 
330 						if (tabeditingListener != null) {
331 							control.removeTraverseListener(tabeditingListener);
332 						}
333 					}
334 					c.deactivate(tmp);
335 
336 					if (editorActivationListener != null && !editorActivationListener.isEmpty()) {
337 						for (ColumnViewerEditorActivationListener ls : editorActivationListener) {
338 							ls.afterEditorDeactivated(tmp);
339 						}
340 					}
341 
342 					if( ! this.cell.getItem().isDisposed() ) {
343 						this.cell.getItem().removeDisposeListener(disposeListener);
344 					}
345 				}
346 
347 				this.cellEditor = null;
348 				this.cell = null;
349 			} finally {
350 				inEditorDeactivation = false;
351 			}
352 		}
353 	}
354 
355 	/**
356 	 * Cancel editing
357 	 */
cancelEditing()358 	void cancelEditing() {
359 		// avoid re-entering
360 		if (!inEditorDeactivation) {
361 			try {
362 				inEditorDeactivation = true;
363 				if (cellEditor != null) {
364 					ColumnViewerEditorDeactivationEvent tmp = new ColumnViewerEditorDeactivationEvent(
365 							cell);
366 					tmp.eventType = ColumnViewerEditorDeactivationEvent.EDITOR_CANCELED;
367 					if (editorActivationListener != null && !editorActivationListener.isEmpty()) {
368 						for (ColumnViewerEditorActivationListener ls : editorActivationListener) {
369 							ls.beforeEditorDeactivated(tmp);
370 						}
371 					}
372 
373 					if (!viewer.getControl().isDisposed()) {
374 						setEditor(null, null, 0);
375 					}
376 
377 					cellEditor.removeListener(cellEditorListener);
378 
379 					Control control = cellEditor.getControl();
380 					if (control != null && !viewer.getControl().isDisposed()) {
381 						if (mouseListener != null) {
382 							control.removeMouseListener(mouseListener);
383 							// Clear the instance not needed any more
384 							mouseListener = null;
385 						}
386 						if (focusListener != null) {
387 							control.removeFocusListener(focusListener);
388 						}
389 
390 						if (tabeditingListener != null) {
391 							control.removeTraverseListener(tabeditingListener);
392 						}
393 					}
394 
395 					CellEditor oldEditor = cellEditor;
396 					oldEditor.deactivate(tmp);
397 
398 					if (editorActivationListener != null && !editorActivationListener.isEmpty()) {
399 						for (ColumnViewerEditorActivationListener ls : editorActivationListener) {
400 							ls.afterEditorDeactivated(tmp);
401 						}
402 					}
403 
404 					if( ! this.cell.getItem().isDisposed() ) {
405 						this.cell.getItem().addDisposeListener(disposeListener);
406 					}
407 
408 					this.cellEditor = null;
409 					this.cell = null;
410 
411 				}
412 			} finally {
413 				inEditorDeactivation = false;
414 			}
415 		}
416 	}
417 
418 	/**
419 	 * Enable the editor by mouse down
420 	 *
421 	 * @param event
422 	 */
handleEditorActivationEvent(ColumnViewerEditorActivationEvent event)423 	void handleEditorActivationEvent(ColumnViewerEditorActivationEvent event) {
424 
425 		// Only activate if the event isn't tagged as canceled
426 		if (!event.cancel
427 				&& editorActivationStrategy.isEditorActivationEvent(event)) {
428 			if (cellEditor != null) {
429 				applyEditorValue();
430 			}
431 
432 			this.cell = (ViewerCell) event.getSource();
433 
434 			// Only null if we are not in a deactivation process see bug 260892
435 			if( ! activateCellEditor(event) && ! inEditorDeactivation ) {
436 				this.cell = null;
437 				this.cellEditor = null;
438 			}
439 		}
440 	}
441 
saveEditorValue(CellEditor cellEditor)442 	private void saveEditorValue(CellEditor cellEditor) {
443 		ViewerColumn part = viewer.getViewerColumn(cell.getColumnIndex());
444 
445 		if (part != null && part.getEditingSupport() != null) {
446 			part.getEditingSupport().saveCellEditorValue(cellEditor, cell);
447 		}
448 	}
449 
450 	/**
451 	 * Return whether there is an active cell editor.
452 	 *
453 	 * @return <code>true</code> if there is an active cell editor; otherwise
454 	 *         <code>false</code> is returned.
455 	 */
isCellEditorActive()456 	boolean isCellEditorActive() {
457 		return cellEditor != null;
458 	}
459 
handleDoubleClickEvent()460 	void handleDoubleClickEvent() {
461 		viewer.fireDoubleClick(new DoubleClickEvent(viewer, viewer
462 				.getSelection()));
463 		viewer.fireOpen(new OpenEvent(viewer, viewer.getStructuredSelection()));
464 	}
465 
466 	/**
467 	 * Adds the given listener, it is to be notified when the cell editor is
468 	 * activated or deactivated.
469 	 *
470 	 * @param listener
471 	 *            the listener to add
472 	 */
addEditorActivationListener( ColumnViewerEditorActivationListener listener)473 	public void addEditorActivationListener(
474 			ColumnViewerEditorActivationListener listener) {
475 		if (editorActivationListener == null) {
476 			editorActivationListener = new ListenerList<>();
477 		}
478 		editorActivationListener.add(listener);
479 	}
480 
481 	/**
482 	 * Removes the given listener.
483 	 *
484 	 * @param listener
485 	 *            the listener to remove
486 	 */
removeEditorActivationListener( ColumnViewerEditorActivationListener listener)487 	public void removeEditorActivationListener(
488 			ColumnViewerEditorActivationListener listener) {
489 		if (editorActivationListener != null) {
490 			editorActivationListener.remove(listener);
491 		}
492 	}
493 
494 	/**
495 	 * Process the traverse event and opens the next available editor depending
496 	 * of the implemented strategy. The default implementation uses the style
497 	 * constants
498 	 * <ul>
499 	 * <li>{@link ColumnViewerEditor#TABBING_MOVE_TO_ROW_NEIGHBOR}</li>
500 	 * <li>{@link ColumnViewerEditor#TABBING_CYCLE_IN_ROW}</li>
501 	 * <li>{@link ColumnViewerEditor#TABBING_VERTICAL}</li>
502 	 * <li>{@link ColumnViewerEditor#TABBING_HORIZONTAL}</li>
503 	 * </ul>
504 	 *
505 	 * <p>
506 	 * Subclasses may overwrite to implement their custom logic to edit the next
507 	 * cell
508 	 * </p>
509 	 *
510 	 * @param columnIndex
511 	 *            the index of the current column
512 	 * @param row
513 	 *            the current row - may only be used for the duration of this
514 	 *            method call
515 	 * @param event
516 	 *            the traverse event
517 	 */
processTraverseEvent(int columnIndex, ViewerRow row, TraverseEvent event)518 	protected void processTraverseEvent(int columnIndex, ViewerRow row,
519 			TraverseEvent event) {
520 
521 		ViewerCell cell2edit = null;
522 
523 		if (event.detail == SWT.TRAVERSE_TAB_PREVIOUS) {
524 			event.doit = false;
525 
526 			if ((event.stateMask & SWT.CTRL) == SWT.CTRL
527 					&& (feature & TABBING_VERTICAL) == TABBING_VERTICAL) {
528 				cell2edit = searchCellAboveBelow(row, viewer, columnIndex, true);
529 			} else if ((feature & TABBING_HORIZONTAL) == TABBING_HORIZONTAL) {
530 				cell2edit = searchPreviousCell(row, row.getCell(columnIndex),
531 						row.getCell(columnIndex), viewer);
532 			}
533 		} else if (event.detail == SWT.TRAVERSE_TAB_NEXT) {
534 			event.doit = false;
535 
536 			if ((event.stateMask & SWT.CTRL) == SWT.CTRL
537 					&& (feature & TABBING_VERTICAL) == TABBING_VERTICAL) {
538 				cell2edit = searchCellAboveBelow(row, viewer, columnIndex,
539 						false);
540 			} else if ((feature & TABBING_HORIZONTAL) == TABBING_HORIZONTAL) {
541 				cell2edit = searchNextCell(row, row.getCell(columnIndex), row
542 						.getCell(columnIndex), viewer);
543 			}
544 		}
545 
546 		if (cell2edit != null) {
547 
548 			viewer.getControl().setRedraw(false);
549 			ColumnViewerEditorActivationEvent acEvent = new ColumnViewerEditorActivationEvent(
550 					cell2edit, event);
551 			viewer.triggerEditorActivationEvent(acEvent);
552 			viewer.getControl().setRedraw(true);
553 		}
554 	}
555 
searchCellAboveBelow(ViewerRow row, ColumnViewer viewer, int columnIndex, boolean above)556 	private ViewerCell searchCellAboveBelow(ViewerRow row, ColumnViewer viewer,
557 			int columnIndex, boolean above) {
558 		ViewerCell rv = null;
559 
560 		ViewerRow newRow = null;
561 
562 		if (above) {
563 			newRow = row.getNeighbor(ViewerRow.ABOVE, false);
564 		} else {
565 			newRow = row.getNeighbor(ViewerRow.BELOW, false);
566 		}
567 
568 		if (newRow != null) {
569 			ViewerColumn column = viewer.getViewerColumn(columnIndex);
570 			if (column != null
571 					&& column.getEditingSupport() != null
572 					&& column.getEditingSupport().canEdit(
573 							newRow.getItem().getData())) {
574 				rv = newRow.getCell(columnIndex);
575 			} else {
576 				rv = searchCellAboveBelow(newRow, viewer, columnIndex, above);
577 			}
578 		}
579 
580 		return rv;
581 	}
582 
isCellEditable(ColumnViewer viewer, ViewerCell cell)583 	private boolean isCellEditable(ColumnViewer viewer, ViewerCell cell) {
584 		ViewerColumn column = viewer.getViewerColumn(cell.getColumnIndex());
585 		return column != null && column.getEditingSupport() != null
586 				&& column.getEditingSupport().canEdit(cell.getElement());
587 	}
588 
searchPreviousCell(ViewerRow row, ViewerCell currentCell, ViewerCell originalCell, ColumnViewer viewer)589 	private ViewerCell searchPreviousCell(ViewerRow row,
590 			ViewerCell currentCell, ViewerCell originalCell, ColumnViewer viewer) {
591 		ViewerCell rv = null;
592 		ViewerCell previousCell;
593 
594 		if (currentCell != null) {
595 			previousCell = currentCell.getNeighbor(ViewerCell.LEFT, true);
596 		} else {
597 			if (row.getColumnCount() != 0) {
598 				previousCell = row.getCell(row.getCreationIndex(row
599 						.getColumnCount() - 1));
600 			} else {
601 				previousCell = row.getCell(0);
602 			}
603 
604 		}
605 
606 		// No endless loop
607 		if (originalCell.equals(previousCell)) {
608 			return null;
609 		}
610 
611 		if (previousCell != null) {
612 			if (isCellEditable(viewer, previousCell)) {
613 				rv = previousCell;
614 			} else {
615 				rv = searchPreviousCell(row, previousCell, originalCell, viewer);
616 			}
617 		} else {
618 			if ((feature & TABBING_CYCLE_IN_ROW) == TABBING_CYCLE_IN_ROW) {
619 				rv = searchPreviousCell(row, null, originalCell, viewer);
620 			} else if ((feature & TABBING_MOVE_TO_ROW_NEIGHBOR) == TABBING_MOVE_TO_ROW_NEIGHBOR) {
621 				ViewerRow rowAbove = row.getNeighbor(ViewerRow.ABOVE, false);
622 				if (rowAbove != null) {
623 					rv = searchPreviousCell(rowAbove, null, originalCell,
624 							viewer);
625 				}
626 			}
627 			if ((feature & TABBING_CYCLE_IN_VIEWER) == TABBING_CYCLE_IN_VIEWER) {
628 				ViewerRow rowAbove = row.getNeighbor(ViewerRow.ABOVE, false);
629 				if (rowAbove == null) {
630 					return getLastCell(row);
631 				}
632 
633 			}
634 		}
635 
636 		return rv;
637 	}
638 
639 
searchNextCell(ViewerRow row, ViewerCell currentCell, ViewerCell originalCell, ColumnViewer viewer)640 	private ViewerCell searchNextCell(ViewerRow row, ViewerCell currentCell,
641 			ViewerCell originalCell, ColumnViewer viewer) {
642 		ViewerCell rv = null;
643 
644 		ViewerCell nextCell;
645 
646 		if (currentCell != null) {
647 			nextCell = currentCell.getNeighbor(ViewerCell.RIGHT, true);
648 		} else {
649 			nextCell = row.getCell(row.getCreationIndex(0));
650 		}
651 
652 		// No endless loop
653 		if (originalCell.equals(nextCell)) {
654 			return null;
655 		}
656 
657 		if (nextCell != null) {
658 			if (isCellEditable(viewer, nextCell)) {
659 				rv = nextCell;
660 			} else {
661 				rv = searchNextCell(row, nextCell, originalCell, viewer);
662 			}
663 		} else {
664 			if ((feature & TABBING_CYCLE_IN_ROW) == TABBING_CYCLE_IN_ROW) {
665 				rv = searchNextCell(row, null, originalCell, viewer);
666 			} else if ((feature & TABBING_MOVE_TO_ROW_NEIGHBOR) == TABBING_MOVE_TO_ROW_NEIGHBOR) {
667 				ViewerRow rowBelow = row.getNeighbor(ViewerRow.BELOW, false);
668 				if (rowBelow != null) {
669 					rv = searchNextCell(rowBelow, null, originalCell, viewer);
670 				}
671 			}
672 			if ((feature & TABBING_CYCLE_IN_VIEWER) == TABBING_CYCLE_IN_VIEWER) {
673 				ViewerRow rowBelow = row.getNeighbor(ViewerRow.BELOW, false);
674 				if (rowBelow == null) {
675 					return getFirstCell(row);
676 				}
677 
678 
679 			}
680 		}
681 
682 		return rv;
683 	}
684 
685 	/**
686 	 * Searches for the top-left-most cell and returns it
687 	 *
688 	 * @param row Row to search from
689 	 * @return Top-left-most cell in the viewer
690 	 *
691 	 *
692 	 */
getFirstCell(ViewerRow row)693 	private ViewerCell getFirstCell(ViewerRow row) {
694 		ViewerRow rowAbove = row.getNeighbor(ViewerRow.ABOVE, false);
695 		ViewerRow topMostRow = rowAbove;
696 		if (rowAbove == null) {
697 			// Already on the top most-row, return left-most cell
698 			return row.getCell(row.getCreationIndex(0));
699 		}
700 		while (rowAbove != null) {
701 			topMostRow = rowAbove;
702 			rowAbove = rowAbove.getNeighbor(ViewerRow.ABOVE, false);
703 		}
704 
705 		return topMostRow.getCell(row.getCreationIndex(0));
706 	}
707 
708 	/**
709 	 * Searches for the bottom-right-most cell and returns it
710 	 *
711 	 * @param row Row to search from
712 	 * @return bottom-right-most cell in the viewer
713 	 *
714 	 *
715 	 */
getLastCell(ViewerRow row)716 	private ViewerCell getLastCell(ViewerRow row) {
717 		ViewerRow rowBelow = row.getNeighbor(ViewerRow.BELOW, false);
718 		ViewerRow bottomMostRow = rowBelow;
719 		if (rowBelow == null) {
720 			// Already on the bottom most-row, return right-most cell
721 			return row.getCell(row.getCreationIndex(row.getColumnCount() - 1));
722 		}
723 		while (rowBelow != null) {
724 			bottomMostRow = rowBelow;
725 			rowBelow = rowBelow.getNeighbor(ViewerRow.BELOW, false);
726 		}
727 
728 		return bottomMostRow.getCell(row.getCreationIndex(bottomMostRow.getColumnCount() - 1));
729 	}
730 
731 
732 	/**
733 	 * Position the editor inside the control
734 	 *
735 	 * @param w             the editor control
736 	 * @param item          the item (row) in which the editor is drawn in
737 	 * @param fColumnNumber the column number in which the editor is shown
738 	 */
setEditor(Control w, Item item, int fColumnNumber)739 	protected abstract void setEditor(Control w, Item item, int fColumnNumber);
740 
741 	/**
742 	 * set the layout data for the editor
743 	 *
744 	 * @param layoutData
745 	 *            the layout data used when editor is displayed
746 	 */
setLayoutData(CellEditor.LayoutData layoutData)747 	protected abstract void setLayoutData(CellEditor.LayoutData layoutData);
748 
749 	/**
750 	 * @param focusCell
751 	 *            updates the cell with the current input focus
752 	 * @param event
753 	 *            the event requesting to update the focusCell
754 	 */
updateFocusCell(ViewerCell focusCell, ColumnViewerEditorActivationEvent event)755 	protected abstract void updateFocusCell(ViewerCell focusCell,
756 			ColumnViewerEditorActivationEvent event);
757 
758 	/**
759 	 * @return the cell currently holding the focus if no cell has the focus or
760 	 *         the viewer implementation doesn't support <code>null</code> is
761 	 *         returned
762 	 *
763 	 */
getFocusCell()764 	public ViewerCell getFocusCell() {
765 		return null;
766 	}
767 
768 	/**
769 	 * @return the viewer working for
770 	 */
getViewer()771 	protected ColumnViewer getViewer() {
772 		return viewer;
773 	}
774 }