001: /*******************************************************************************
002: * Copyright (c) 2006, 2007 IBM Corporation and others.
003: * All rights reserved. This program and the accompanying materials
004: * are made available under the terms of the Eclipse Public License v1.0
005: * which accompanies this distribution, and is available at
006: * http://www.eclipse.org/legal/epl-v10.html
007: *
008: * Contributors:
009: * IBM Corporation - initial API and implementation
010: * Tom Schindl <tom.schindl@bestsolution.at> - refactoring (bug 153993)
011: * fix in bug: 151295,178946,166500,195908,201906
012: *******************************************************************************/package org.eclipse.jface.viewers;
013:
014: import org.eclipse.core.runtime.ListenerList;
015: import org.eclipse.swt.SWT;
016: import org.eclipse.swt.events.FocusAdapter;
017: import org.eclipse.swt.events.FocusEvent;
018: import org.eclipse.swt.events.FocusListener;
019: import org.eclipse.swt.events.MouseAdapter;
020: import org.eclipse.swt.events.MouseEvent;
021: import org.eclipse.swt.events.MouseListener;
022: import org.eclipse.swt.events.TraverseEvent;
023: import org.eclipse.swt.events.TraverseListener;
024: import org.eclipse.swt.widgets.Control;
025: import org.eclipse.swt.widgets.Display;
026: import org.eclipse.swt.widgets.Item;
027:
028: /**
029: * This is the base for all editor implementations of Viewers. ColumnViewer
030: * implementators have to subclass this class and implement the missing methods
031: *
032: * @since 3.3
033: * @see TableViewerEditor
034: * @see TreeViewerEditor
035: */
036: public abstract class ColumnViewerEditor {
037: private CellEditor cellEditor;
038:
039: private ICellEditorListener cellEditorListener;
040:
041: private FocusListener focusListener;
042:
043: private MouseListener mouseListener;
044:
045: private ColumnViewer viewer;
046:
047: private TraverseListener tabeditingListener;
048:
049: private int activationTime;
050:
051: private ViewerCell cell;
052:
053: private ColumnViewerEditorActivationEvent activationEvent;
054:
055: private ListenerList editorActivationListener;
056:
057: private ColumnViewerEditorActivationStrategy editorActivationStrategy;
058:
059: /**
060: * Tabbing from cell to cell is turned off
061: */
062: public static final int DEFAULT = 1;
063:
064: /**
065: * Should if the end of the row is reach started from the start/end of the
066: * row below/above
067: */
068: public static final int TABBING_MOVE_TO_ROW_NEIGHBOR = 1 << 1;
069:
070: /**
071: * Should if the end of the row is reach started from the beginning in the
072: * same row
073: */
074: public static final int TABBING_CYCLE_IN_ROW = 1 << 2;
075:
076: /**
077: * Support tabbing to Cell above/below the current cell
078: */
079: public static final int TABBING_VERTICAL = 1 << 3;
080:
081: /**
082: * Should tabbing from column to column with in one row be supported
083: */
084: public static final int TABBING_HORIZONTAL = 1 << 4;
085:
086: /**
087: * Style mask used to enable keyboard activation
088: */
089: public static final int KEYBOARD_ACTIVATION = 1 << 5;
090:
091: private int feature;
092:
093: /**
094: * @param viewer
095: * the viewer this editor is attached to
096: * @param editorActivationStrategy
097: * the strategy used to decide about editor activation
098: * @param feature
099: * bit mask controlling the editor
100: * <ul>
101: * <li>{@link ColumnViewerEditor#DEFAULT}</li>
102: * <li>{@link ColumnViewerEditor#TABBING_CYCLE_IN_ROW}</li>
103: * <li>{@link ColumnViewerEditor#TABBING_HORIZONTAL}</li>
104: * <li>{@link ColumnViewerEditor#TABBING_MOVE_TO_ROW_NEIGHBOR}</li>
105: * <li>{@link ColumnViewerEditor#TABBING_VERTICAL}</li>
106: * </ul>
107: */
108: protected ColumnViewerEditor(
109: ColumnViewer viewer,
110: ColumnViewerEditorActivationStrategy editorActivationStrategy,
111: int feature) {
112: this .viewer = viewer;
113: this .editorActivationStrategy = editorActivationStrategy;
114: if ((feature & KEYBOARD_ACTIVATION) == KEYBOARD_ACTIVATION) {
115: this .editorActivationStrategy
116: .setEnableEditorActivationWithKeyboard(true);
117: }
118: this .feature = feature;
119: initCellEditorListener();
120: }
121:
122: private void initCellEditorListener() {
123: cellEditorListener = new ICellEditorListener() {
124: public void editorValueChanged(boolean oldValidState,
125: boolean newValidState) {
126: // Ignore.
127: }
128:
129: public void cancelEditor() {
130: ColumnViewerEditor.this .cancelEditing();
131: }
132:
133: public void applyEditorValue() {
134: ColumnViewerEditor.this .applyEditorValue();
135: }
136: };
137: }
138:
139: void activateCellEditor() {
140:
141: ViewerColumn part = viewer.getViewerColumn(cell
142: .getColumnIndex());
143: Object element = cell.getElement();
144:
145: if (part != null && part.getEditingSupport() != null
146: && part.getEditingSupport().canEdit(element)) {
147:
148: cellEditor = part.getEditingSupport()
149: .getCellEditor(element);
150: if (cellEditor != null) {
151: if (editorActivationListener != null
152: && !editorActivationListener.isEmpty()) {
153: Object[] ls = editorActivationListener
154: .getListeners();
155: for (int i = 0; i < ls.length; i++) {
156:
157: if (activationEvent.cancel) {
158: return;
159: }
160:
161: ((ColumnViewerEditorActivationListener) ls[i])
162: .beforeEditorActivated(activationEvent);
163: }
164: }
165:
166: updateFocusCell(cell, activationEvent);
167:
168: cellEditor.addListener(cellEditorListener);
169: part.getEditingSupport().initializeCellEditorValue(
170: cellEditor, cell);
171:
172: // Tricky flow of control here:
173: // activate() can trigger callback to cellEditorListener which
174: // will clear cellEditor
175: // so must get control first, but must still call activate()
176: // even if there is no control.
177: final Control control = cellEditor.getControl();
178: cellEditor.activate(activationEvent);
179: if (control == null) {
180: return;
181: }
182: setLayoutData(cellEditor.getLayoutData());
183: setEditor(control, (Item) cell.getItem(), cell
184: .getColumnIndex());
185: cellEditor.setFocus();
186:
187: if (cellEditor.dependsOnExternalFocusListener()) {
188: if (focusListener == null) {
189: focusListener = new FocusAdapter() {
190: public void focusLost(FocusEvent e) {
191: applyEditorValue();
192: }
193: };
194: }
195: control.addFocusListener(focusListener);
196: }
197:
198: mouseListener = new MouseAdapter() {
199: public void mouseDown(MouseEvent e) {
200: // time wrap?
201: // check for expiration of doubleClickTime
202: if (e.time <= activationTime) {
203: control.removeMouseListener(mouseListener);
204: cancelEditing();
205: handleDoubleClickEvent();
206: } else if (mouseListener != null) {
207: control.removeMouseListener(mouseListener);
208: }
209: }
210: };
211: control.addMouseListener(mouseListener);
212:
213: if (tabeditingListener == null) {
214: tabeditingListener = new TraverseListener() {
215:
216: public void keyTraversed(TraverseEvent e) {
217: if ((feature & DEFAULT) != DEFAULT) {
218: processTraverseEvent(cell
219: .getColumnIndex(), viewer
220: .getViewerRowFromItem(cell
221: .getItem()), e);
222: }
223: }
224: };
225: }
226:
227: control.addTraverseListener(tabeditingListener);
228:
229: if (editorActivationListener != null
230: && !editorActivationListener.isEmpty()) {
231: Object[] ls = editorActivationListener
232: .getListeners();
233: for (int i = 0; i < ls.length; i++) {
234: ((ColumnViewerEditorActivationListener) ls[i])
235: .afterEditorActivated(activationEvent);
236: }
237: }
238: }
239: }
240: }
241:
242: /**
243: * Applies the current value and deactivates the currently active cell
244: * editor.
245: */
246: void applyEditorValue() {
247: CellEditor c = this .cellEditor;
248: if (c != null && this .cell != null) {
249: // null out cell editor before calling save
250: // in case save results in applyEditorValue being re-entered
251: // see 1GAHI8Z: ITPUI:ALL - How to code event notification when
252: // using cell editor ?
253: ColumnViewerEditorDeactivationEvent tmp = new ColumnViewerEditorDeactivationEvent(
254: cell);
255: tmp.eventType = ColumnViewerEditorDeactivationEvent.EDITOR_SAVED;
256: if (editorActivationListener != null
257: && !editorActivationListener.isEmpty()) {
258: Object[] ls = editorActivationListener.getListeners();
259: for (int i = 0; i < ls.length; i++) {
260:
261: ((ColumnViewerEditorActivationListener) ls[i])
262: .beforeEditorDeactivated(tmp);
263: }
264: }
265:
266: Item t = (Item) this .cell.getItem();
267:
268: // don't null out table item -- same item is still selected
269: if (t != null && !t.isDisposed()) {
270: saveEditorValue(c);
271: }
272: setEditor(null, null, 0);
273: c.removeListener(cellEditorListener);
274: Control control = c.getControl();
275: if (control != null) {
276: if (mouseListener != null) {
277: control.removeMouseListener(mouseListener);
278: // Clear the instance not needed any more
279: mouseListener = null;
280: }
281: if (focusListener != null) {
282: control.removeFocusListener(focusListener);
283: }
284:
285: if (tabeditingListener != null) {
286: control.removeTraverseListener(tabeditingListener);
287: }
288: }
289: c.deactivate();
290:
291: if (editorActivationListener != null
292: && !editorActivationListener.isEmpty()) {
293: Object[] ls = editorActivationListener.getListeners();
294: for (int i = 0; i < ls.length; i++) {
295: ((ColumnViewerEditorActivationListener) ls[i])
296: .afterEditorDeactivated(tmp);
297: }
298: }
299: }
300:
301: this .cellEditor = null;
302: this .activationEvent = null;
303: this .cell = null;
304: }
305:
306: /**
307: * Cancel editing
308: */
309: void cancelEditing() {
310: if (cellEditor != null) {
311: ColumnViewerEditorDeactivationEvent tmp = new ColumnViewerEditorDeactivationEvent(
312: cell);
313: tmp.eventType = ColumnViewerEditorDeactivationEvent.EDITOR_CANCELED;
314: if (editorActivationListener != null
315: && !editorActivationListener.isEmpty()) {
316: Object[] ls = editorActivationListener.getListeners();
317: for (int i = 0; i < ls.length; i++) {
318:
319: ((ColumnViewerEditorActivationListener) ls[i])
320: .beforeEditorDeactivated(tmp);
321: }
322: }
323:
324: setEditor(null, null, 0);
325: cellEditor.removeListener(cellEditorListener);
326:
327: Control control = cellEditor.getControl();
328: if (control != null) {
329: if (mouseListener != null) {
330: control.removeMouseListener(mouseListener);
331: // Clear the instance not needed any more
332: mouseListener = null;
333: }
334: if (focusListener != null) {
335: control.removeFocusListener(focusListener);
336: }
337:
338: if (tabeditingListener != null) {
339: control.removeTraverseListener(tabeditingListener);
340: }
341: }
342:
343: CellEditor oldEditor = cellEditor;
344: oldEditor.deactivate();
345:
346: if (editorActivationListener != null
347: && !editorActivationListener.isEmpty()) {
348: Object[] ls = editorActivationListener.getListeners();
349: for (int i = 0; i < ls.length; i++) {
350: ((ColumnViewerEditorActivationListener) ls[i])
351: .afterEditorDeactivated(tmp);
352: }
353: }
354:
355: this .cellEditor = null;
356: this .activationEvent = null;
357: this .cell = null;
358:
359: }
360: }
361:
362: /**
363: * Enable the editor by mouse down
364: *
365: * @param event
366: */
367: void handleEditorActivationEvent(
368: ColumnViewerEditorActivationEvent event) {
369: if (editorActivationStrategy.isEditorActivationEvent(event)) {
370: if (cellEditor != null) {
371: applyEditorValue();
372: }
373:
374: this .cell = (ViewerCell) event.getSource();
375:
376: activationEvent = event;
377: activationTime = event.time
378: + Display.getCurrent().getDoubleClickTime();
379:
380: activateCellEditor();
381: }
382: }
383:
384: private void saveEditorValue(CellEditor cellEditor) {
385: ViewerColumn part = viewer.getViewerColumn(cell
386: .getColumnIndex());
387:
388: if (part != null && part.getEditingSupport() != null) {
389: part.getEditingSupport().saveCellEditorValue(cellEditor,
390: cell);
391: }
392: }
393:
394: /**
395: * Return whether there is an active cell editor.
396: *
397: * @return <code>true</code> if there is an active cell editor; otherwise
398: * <code>false</code> is returned.
399: */
400: boolean isCellEditorActive() {
401: return cellEditor != null;
402: }
403:
404: void handleDoubleClickEvent() {
405: viewer.fireDoubleClick(new DoubleClickEvent(viewer, viewer
406: .getSelection()));
407: viewer.fireOpen(new OpenEvent(viewer, viewer.getSelection()));
408: }
409:
410: /**
411: * Adds the given listener, it is to be notified when the cell editor is
412: * activated or deactivated.
413: *
414: * @param listener
415: * the listener to add
416: */
417: public void addEditorActivationListener(
418: ColumnViewerEditorActivationListener listener) {
419: if (editorActivationListener == null) {
420: editorActivationListener = new ListenerList();
421: }
422: editorActivationListener.add(listener);
423: }
424:
425: /**
426: * Removes the given listener.
427: *
428: * @param listener
429: * the listener to remove
430: */
431: public void removeEditorActivationListener(
432: ColumnViewerEditorActivationListener listener) {
433: if (editorActivationListener != null) {
434: editorActivationListener.remove(listener);
435: }
436: }
437:
438: /**
439: * Process the traverse event and opens the next available editor depending
440: * of the implemented strategy. The default implementation uses the style
441: * constants
442: * <ul>
443: * <li>{@link ColumnViewerEditor#TABBING_MOVE_TO_ROW_NEIGHBOR}</li>
444: * <li>{@link ColumnViewerEditor#TABBING_CYCLE_IN_ROW}</li>
445: * <li>{@link ColumnViewerEditor#TABBING_VERTICAL}</li>
446: * <li>{@link ColumnViewerEditor#TABBING_HORIZONTAL}</li>
447: * </ul>
448: *
449: * <p>
450: * Subclasses may overwrite to implement their custom logic to edit the next
451: * cell
452: * </p>
453: *
454: * @param columnIndex
455: * the index of the current column
456: * @param row
457: * the current row - may only be used for the duration of this
458: * method call
459: * @param event
460: * the traverse event
461: */
462: protected void processTraverseEvent(int columnIndex, ViewerRow row,
463: TraverseEvent event) {
464:
465: ViewerCell cell2edit = null;
466:
467: if (event.detail == SWT.TRAVERSE_TAB_PREVIOUS) {
468: event.doit = false;
469:
470: if ((event.stateMask & SWT.CTRL) == SWT.CTRL
471: && (feature & TABBING_VERTICAL) == TABBING_VERTICAL) {
472: cell2edit = searchCellAboveBelow(row, viewer,
473: columnIndex, true);
474: } else if ((feature & TABBING_HORIZONTAL) == TABBING_HORIZONTAL) {
475: cell2edit = searchPreviousCell(row, row
476: .getCell(columnIndex),
477: row.getCell(columnIndex), viewer);
478: }
479: } else if (event.detail == SWT.TRAVERSE_TAB_NEXT) {
480: event.doit = false;
481:
482: if ((event.stateMask & SWT.CTRL) == SWT.CTRL
483: && (feature & TABBING_VERTICAL) == TABBING_VERTICAL) {
484: cell2edit = searchCellAboveBelow(row, viewer,
485: columnIndex, false);
486: } else if ((feature & TABBING_HORIZONTAL) == TABBING_HORIZONTAL) {
487: cell2edit = searchNextCell(row, row
488: .getCell(columnIndex),
489: row.getCell(columnIndex), viewer);
490: }
491: }
492:
493: if (cell2edit != null) {
494:
495: viewer.getControl().setRedraw(false);
496: ColumnViewerEditorActivationEvent acEvent = new ColumnViewerEditorActivationEvent(
497: cell2edit, event);
498: viewer.triggerEditorActivationEvent(acEvent);
499: viewer.getControl().setRedraw(true);
500: }
501: }
502:
503: private ViewerCell searchCellAboveBelow(ViewerRow row,
504: ColumnViewer viewer, int columnIndex, boolean above) {
505: ViewerCell rv = null;
506:
507: ViewerRow newRow = null;
508:
509: if (above) {
510: newRow = row.getNeighbor(ViewerRow.ABOVE, false);
511: } else {
512: newRow = row.getNeighbor(ViewerRow.BELOW, false);
513: }
514:
515: if (newRow != null) {
516: ViewerColumn column = viewer.getViewerColumn(columnIndex);
517: if (column != null
518: && column.getEditingSupport() != null
519: && column.getEditingSupport().canEdit(
520: newRow.getItem().getData())) {
521: rv = newRow.getCell(columnIndex);
522: } else {
523: rv = searchCellAboveBelow(newRow, viewer, columnIndex,
524: above);
525: }
526: }
527:
528: return rv;
529: }
530:
531: private boolean isCellEditable(ColumnViewer viewer, ViewerCell cell) {
532: ViewerColumn column = viewer.getViewerColumn(cell
533: .getColumnIndex());
534: return column != null
535: && column.getEditingSupport() != null
536: && column.getEditingSupport()
537: .canEdit(cell.getElement());
538: }
539:
540: private ViewerCell searchPreviousCell(ViewerRow row,
541: ViewerCell currentCell, ViewerCell originalCell,
542: ColumnViewer viewer) {
543: ViewerCell rv = null;
544: ViewerCell previousCell;
545:
546: if (currentCell != null) {
547: previousCell = currentCell.getNeighbor(ViewerCell.LEFT,
548: true);
549: } else {
550: if (row.getColumnCount() != 0) {
551: previousCell = row.getCell(row.getCreationIndex(row
552: .getColumnCount() - 1));
553: } else {
554: previousCell = row.getCell(0);
555: }
556:
557: }
558:
559: // No endless loop
560: if (originalCell.equals(previousCell)) {
561: return null;
562: }
563:
564: if (previousCell != null) {
565: if (isCellEditable(viewer, previousCell)) {
566: rv = previousCell;
567: } else {
568: rv = searchPreviousCell(row, previousCell,
569: originalCell, viewer);
570: }
571: } else {
572: if ((feature & TABBING_CYCLE_IN_ROW) == TABBING_CYCLE_IN_ROW) {
573: rv = searchPreviousCell(row, null, originalCell, viewer);
574: } else if ((feature & TABBING_MOVE_TO_ROW_NEIGHBOR) == TABBING_MOVE_TO_ROW_NEIGHBOR) {
575: ViewerRow rowAbove = row.getNeighbor(ViewerRow.ABOVE,
576: false);
577: if (rowAbove != null) {
578: rv = searchPreviousCell(rowAbove, null,
579: originalCell, viewer);
580: }
581: }
582: }
583:
584: return rv;
585: }
586:
587: private ViewerCell searchNextCell(ViewerRow row,
588: ViewerCell currentCell, ViewerCell originalCell,
589: ColumnViewer viewer) {
590: ViewerCell rv = null;
591:
592: ViewerCell nextCell;
593:
594: if (currentCell != null) {
595: nextCell = currentCell.getNeighbor(ViewerCell.RIGHT, true);
596: } else {
597: nextCell = row.getCell(row.getCreationIndex(0));
598: }
599:
600: // No endless loop
601: if (originalCell.equals(nextCell)) {
602: return null;
603: }
604:
605: if (nextCell != null) {
606: if (isCellEditable(viewer, nextCell)) {
607: rv = nextCell;
608: } else {
609: rv = searchNextCell(row, nextCell, originalCell, viewer);
610: }
611: } else {
612: if ((feature & TABBING_CYCLE_IN_ROW) == TABBING_CYCLE_IN_ROW) {
613: rv = searchNextCell(row, null, originalCell, viewer);
614: } else if ((feature & TABBING_MOVE_TO_ROW_NEIGHBOR) == TABBING_MOVE_TO_ROW_NEIGHBOR) {
615: ViewerRow rowBelow = row.getNeighbor(ViewerRow.BELOW,
616: false);
617: if (rowBelow != null) {
618: rv = searchNextCell(rowBelow, null, originalCell,
619: viewer);
620: }
621: }
622: }
623:
624: return rv;
625: }
626:
627: /**
628: * Position the editor inside the control
629: *
630: * @param w
631: * the editor control
632: * @param item
633: * the item (row) in which the editor is drawn in
634: * @param fColumnNumber
635: * the column number in which the editor is shown
636: */
637: protected abstract void setEditor(Control w, Item item,
638: int fColumnNumber);
639:
640: /**
641: * set the layout data for the editor
642: *
643: * @param layoutData
644: * the layout data used when editor is displayed
645: */
646: protected abstract void setLayoutData(
647: CellEditor.LayoutData layoutData);
648:
649: /**
650: * @param focusCell
651: * updates the cell with the current input focus
652: * @param event
653: * the event requesting to update the focusCell
654: */
655: protected abstract void updateFocusCell(ViewerCell focusCell,
656: ColumnViewerEditorActivationEvent event);
657:
658: /**
659: * @return the cell currently holding the focus if no cell has the focus or
660: * the viewer implementation doesn't support <code>null</code> is
661: * returned
662: *
663: */
664: public ViewerCell getFocusCell() {
665: return null;
666: }
667:
668: /**
669: * @return the viewer working for
670: */
671: protected ColumnViewer getViewer() {
672: return viewer;
673: }
674: }
|