view dwtx/jface/viewers/ColumnViewerEditor.d @ 39:644f1334b451

fix anon classes
author Frank Benoit <benoit@tionex.de>
date Tue, 08 Apr 2008 22:05:42 +0200
parents b6c35faf97c8
children 07b9d96fd764
line wrap: on
line source

/*******************************************************************************
 * 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,166500,200337
 * Port to the D programming language:
 *     Frank Benoit <benoit@tionex.de>
 *******************************************************************************/

module dwtx.jface.viewers.ColumnViewerEditor;

import dwtx.jface.viewers.CellEditor;
import dwtx.jface.viewers.ICellEditorListener;
import dwtx.jface.viewers.ColumnViewer;
import dwtx.jface.viewers.ColumnViewerEditorActivationStrategy;
import dwtx.jface.viewers.ViewerCell;
import dwtx.jface.viewers.ViewerColumn;
import dwtx.jface.viewers.ColumnViewerEditorActivationEvent;
import dwtx.jface.viewers.ColumnViewerEditorActivationListener;
import dwtx.jface.viewers.ColumnViewerEditorDeactivationEvent;
import dwtx.jface.viewers.ViewerRow;
import dwtx.jface.viewers.DoubleClickEvent;
import dwtx.jface.viewers.OpenEvent;

import dwt.DWT;
import dwt.events.FocusAdapter;
import dwt.events.FocusEvent;
import dwt.events.FocusListener;
import dwt.events.MouseAdapter;
import dwt.events.MouseEvent;
import dwt.events.MouseListener;
import dwt.events.TraverseEvent;
import dwt.events.TraverseListener;
import dwt.widgets.Control;
import dwt.widgets.Display;
import dwt.widgets.Item;
import dwtx.core.runtime.ListenerList;

import dwt.dwthelper.utils;

/**
 * This is the base for all editor implementations of Viewers. ColumnViewer
 * implementators 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 int activationTime;

    private ViewerCell cell;

    private ColumnViewerEditorActivationEvent activationEvent;

    private ListenerList editorActivationListener;

    private ColumnViewerEditorActivationStrategy editorActivationStrategy;

    /**
     * Tabbing from cell to cell is turned off
     */
    public static const int DEFAULT = 1;

    /**
     * Should if the end of the row is reach started from the start/end of the
     * row below/above
     */
    public static const 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 const int TABBING_CYCLE_IN_ROW = 1 << 2;

    /**
     * Support tabbing to Cell above/below the current cell
     */
    public static const int TABBING_VERTICAL = 1 << 3;

    /**
     * Should tabbing from column to column with in one row be supported
     */
    public static const int TABBING_HORIZONTAL = 1 << 4;

    /**
     * Style mask used to enable keyboard activation
     */
    public static const int KEYBOARD_ACTIVATION = 1 << 5;

    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 this(ColumnViewer viewer,
            ColumnViewerEditorActivationStrategy editorActivationStrategy,
            int feature) {
        this.viewer = viewer;
        this.editorActivationStrategy = editorActivationStrategy;
        if ((feature & KEYBOARD_ACTIVATION) is KEYBOARD_ACTIVATION) {
            this.editorActivationStrategy
                    .setEnableEditorActivationWithKeyboard(true);
        }
        this.feature = feature;
        initCellEditorListener();
    }

    private void initCellEditorListener() {
        cellEditorListener = new class ICellEditorListener {
            public void editorValueChanged(bool oldValidState,
                    bool newValidState) {
                // Ignore.
            }

            public void cancelEditor() {
                this.outer.cancelEditing();
            }

            public void applyEditorValue() {
                this.outer.applyEditorValue();
            }
        };
    }

    void activateCellEditor() {

        ViewerColumn part = viewer.getViewerColumn(cell.getColumnIndex());
        Object element = cell.getElement();

        if (part !is null && part.getEditingSupport() !is null
                && part.getEditingSupport().canEdit_package(element)) {

            cellEditor = part.getEditingSupport().getCellEditor_package(element);
            if (cellEditor !is null) {
                if (editorActivationListener !is null
                        && !editorActivationListener.isEmpty()) {
                    Object[] ls = editorActivationListener.getListeners();
                    for (int i = 0; i < ls.length; i++) {

                        if (activationEvent.cancel) {
                            // Avoid leaking
                            this.cell = null;
                            return;
                        }

                        (cast(ColumnViewerEditorActivationListener) ls[i])
                                .beforeEditorActivated(activationEvent);
                    }
                }

                updateFocusCell(cell, activationEvent);

                cellEditor.addListener(cellEditorListener);
                part.getEditingSupport().initializeCellEditorValue_package(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.
                Control control = cellEditor.getControl();
                cellEditor.activate(activationEvent);
                if (control is null) {
                    return;
                }
                setLayoutData(cellEditor.getLayoutData());
                setEditor(control, cast(Item) cell.getItem(), cell.getColumnIndex());
                cellEditor.setFocus();

                if( cellEditor.dependsOnExternalFocusListener() ) {
                    if (focusListener is null) {
                        focusListener = new class FocusAdapter {
                            public void focusLost(FocusEvent e) {
                                applyEditorValue();
                            }
                        };
                    }
                    control.addFocusListener(focusListener);
                }

                mouseListener = new class(control) MouseAdapter {
                    Control control_;
                    this(Control a){
                        control_=a;
                    }
                    public void mouseDown(MouseEvent e) {
                        // time wrap?
                        // check for expiration of doubleClickTime
                        if (e.time <= activationTime) {
                            control_.removeMouseListener(mouseListener);
                            cancelEditing();
                            handleDoubleClickEvent();
                        } else if (mouseListener !is null) {
                            control_.removeMouseListener(mouseListener);
                        }
                    }
                };
                control.addMouseListener(mouseListener);

                if (tabeditingListener is null) {
                    tabeditingListener = new class TraverseListener {

                        public void keyTraversed(TraverseEvent e) {
                            if ((feature & DEFAULT) !is DEFAULT) {
                                processTraverseEvent(cell.getColumnIndex(),
                                        viewer.getViewerRowFromItem_package(cell
                                                .getItem()), e);
                            }
                        }
                    };
                }

                control.addTraverseListener(tabeditingListener);

                if (editorActivationListener !is null
                        && !editorActivationListener.isEmpty()) {
                    Object[] ls = editorActivationListener.getListeners();
                    for (int i = 0; i < ls.length; i++) {
                        (cast(ColumnViewerEditorActivationListener) ls[i])
                                .afterEditorActivated(activationEvent);
                    }
                }
            }
        } else {
            // Avoid leaking
            this.cell = null;
        }
    }

    /**
     * Applies the current value and deactivates the currently active cell
     * editor.
     */
    void applyEditorValue() {
        CellEditor c = this.cellEditor;
        if (c !is null && this.cell !is null) {
            // null out cell editor before calling save
            // in case save results in applyEditorValue being re-entered
            // see 1GAHI8Z: ITPUI:ALL - How to code event notification when
            // using cell editor ?
            ColumnViewerEditorDeactivationEvent tmp = new ColumnViewerEditorDeactivationEvent(
                    cell);
            if (editorActivationListener !is null
                    && !editorActivationListener.isEmpty()) {
                Object[] ls = editorActivationListener.getListeners();
                for (int i = 0; i < ls.length; i++) {

                    (cast(ColumnViewerEditorActivationListener) ls[i])
                            .beforeEditorDeactivated(tmp);
                }
            }

            this.cellEditor = null;
            this.activationEvent = null;
            Item t = cast(Item) this.cell.getItem();

            // don't null out table item -- same item is still selected
            if (t !is null && !t.isDisposed()) {
                saveEditorValue(c);
            }

            setEditor(null, null, 0);
            c.removeListener(cellEditorListener);
            Control control = c.getControl();
            if (control !is null) {
                if (mouseListener !is null) {
                    control.removeMouseListener(mouseListener);
                    // Clear the instance not needed any more
                    mouseListener = null;
                }
                if (focusListener !is null) {
                    control.removeFocusListener(focusListener);
                }

                if (tabeditingListener !is null) {
                    control.removeTraverseListener(tabeditingListener);
                }
            }
            c.deactivate();

            if (editorActivationListener !is null
                    && !editorActivationListener.isEmpty()) {
                Object[] ls = editorActivationListener.getListeners();
                for (int i = 0; i < ls.length; i++) {
                    (cast(ColumnViewerEditorActivationListener) ls[i])
                            .afterEditorDeactivated(tmp);
                }
            }

            this.cell = null;
        }
    }

    /**
     * Cancel editing
     */
    void cancelEditing() {
        if (cellEditor !is null) {
            ColumnViewerEditorDeactivationEvent tmp = new ColumnViewerEditorDeactivationEvent(
                    cell);
            if (editorActivationListener !is null
                    && !editorActivationListener.isEmpty()) {
                Object[] ls = editorActivationListener.getListeners();
                for (int i = 0; i < ls.length; i++) {

                    (cast(ColumnViewerEditorActivationListener) ls[i])
                            .beforeEditorDeactivated(tmp);
                }
            }

            setEditor(null, null, 0);
            cellEditor.removeListener(cellEditorListener);

            Control control = cellEditor.getControl();
            if (control !is null) {
                if (mouseListener !is null) {
                    control.removeMouseListener(mouseListener);
                    // Clear the instance not needed any more
                    mouseListener = null;
                }
                if (focusListener !is null) {
                    control.removeFocusListener(focusListener);
                }

                if (tabeditingListener !is null) {
                    control.removeTraverseListener(tabeditingListener);
                }
            }

            CellEditor oldEditor = cellEditor;
            oldEditor.deactivate();

            if (editorActivationListener !is null
                    && !editorActivationListener.isEmpty()) {
                Object[] ls = editorActivationListener.getListeners();
                for (int i = 0; i < ls.length; i++) {
                    (cast(ColumnViewerEditorActivationListener) ls[i])
                            .afterEditorDeactivated(tmp);
                }
            }

            this.cellEditor = null;
            this.activationEvent = null;
            this.cell = null;
        }
    }

    /**
     * Enable the editor by mouse down
     *
     * @param event
     */
    void handleEditorActivationEvent(ColumnViewerEditorActivationEvent event) {
        if (editorActivationStrategy.isEditorActivationEvent_package(event)) {
            if (cellEditor !is null) {
                applyEditorValue();
            }

            this.cell = cast(ViewerCell) event.getSource();

            activationEvent = event;
            activationTime = event.time
                    + Display.getCurrent().getDoubleClickTime();

            activateCellEditor();
        }
    }

    private void saveEditorValue(CellEditor cellEditor) {
        ViewerColumn part = viewer.getViewerColumn(cell.getColumnIndex());

        if (part !is null && part.getEditingSupport() !is null) {
            part.getEditingSupport().saveCellEditorValue_package(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.
     */
    bool isCellEditorActive() {
        return cellEditor !is null;
    }

    void handleDoubleClickEvent() {
        viewer.fireDoubleClick_package(new DoubleClickEvent(viewer, viewer
                .getSelection()));
        viewer.fireOpen_package(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 is null) {
            editorActivationListener = new ListenerList();
        }
        editorActivationListener.add(listener);
    }

    /**
     * Removes the given listener.
     *
     * @param listener
     *            the listener to remove
     */
    public void removeEditorActivationListener(
            ColumnViewerEditorActivationListener listener) {
        if (editorActivationListener !is 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 is DWT.TRAVERSE_TAB_PREVIOUS) {
            event.doit = false;

            if ((event.stateMask & DWT.CTRL) is DWT.CTRL
                    && (feature & TABBING_VERTICAL) is TABBING_VERTICAL) {
                cell2edit = searchCellAboveBelow(row, viewer, columnIndex, true);
            } else if ((feature & TABBING_HORIZONTAL) is TABBING_HORIZONTAL) {
                cell2edit = searchPreviousCell(row, viewer, columnIndex,
                        columnIndex);
            }
        } else if (event.detail is DWT.TRAVERSE_TAB_NEXT) {
            event.doit = false;

            if ((event.stateMask & DWT.CTRL) is DWT.CTRL
                    && (feature & TABBING_VERTICAL) is TABBING_VERTICAL) {
                cell2edit = searchCellAboveBelow(row, viewer, columnIndex,
                        false);
            } else if ((feature & TABBING_HORIZONTAL) is TABBING_HORIZONTAL) {
                cell2edit = searchNextCell(row, viewer, columnIndex,
                        columnIndex);
            }
        }

        if (cell2edit !is null) {

            viewer.getControl().setRedraw(false);
            ColumnViewerEditorActivationEvent acEvent = new ColumnViewerEditorActivationEvent(
                    cell2edit, event);
            viewer.triggerEditorActivationEvent_package(acEvent);
            viewer.getControl().setRedraw(true);
        }
    }

    private ViewerCell searchCellAboveBelow(ViewerRow row, ColumnViewer viewer,
            int columnIndex, bool above) {
        ViewerCell rv = null;

        ViewerRow newRow = null;

        if (above) {
            newRow = row.getNeighbor(ViewerRow.ABOVE, false);
        } else {
            newRow = row.getNeighbor(ViewerRow.BELOW, false);
        }

        if (newRow !is null) {
            ViewerColumn column = viewer.getViewerColumn(columnIndex);
            if (column !is null
                    && column.getEditingSupport() !is null
                    && column.getEditingSupport().canEdit_package(
                            newRow.getItem().getData())) {
                rv = newRow.getCell(columnIndex);
            } else {
                rv = searchCellAboveBelow(newRow, viewer, columnIndex, above);
            }
        }

        return rv;
    }

    private ViewerCell searchPreviousCell(ViewerRow row, ColumnViewer viewer,
            int columnIndex, int startIndex) {
        ViewerCell rv = null;

        if (columnIndex - 1 >= 0) {
            ViewerColumn column = viewer.getViewerColumn(columnIndex - 1);
            if (column !is null
                    && column.getEditingSupport() !is null
                    && column.getEditingSupport().canEdit_package(
                            row.getItem().getData())) {
                rv = row.getCell(columnIndex - 1);
            } else {
                rv = searchPreviousCell(row, viewer, columnIndex - 1,
                        startIndex);
            }
        } else {
            if ((feature & TABBING_CYCLE_IN_ROW) is TABBING_CYCLE_IN_ROW) {
                // Check that we don't get into endless loop
                if (columnIndex - 1 !is startIndex) {
                    // Don't subtract -1 from getColumnCount() we need to
                    // start in the virtual column
                    // next to it
                    rv = searchPreviousCell(row, viewer, row.getColumnCount(),
                            startIndex);
                }
            } else if ((feature & TABBING_MOVE_TO_ROW_NEIGHBOR) is TABBING_MOVE_TO_ROW_NEIGHBOR) {
                ViewerRow rowAbove = row.getNeighbor(ViewerRow.ABOVE, false);
                if (rowAbove !is null) {
                    rv = searchPreviousCell(rowAbove, viewer, rowAbove
                            .getColumnCount(), startIndex);
                }
            }
        }

        return rv;
    }

    private ViewerCell searchNextCell(ViewerRow row, ColumnViewer viewer,
            int columnIndex, int startIndex) {
        ViewerCell rv = null;

        if (columnIndex + 1 < row.getColumnCount()) {
            ViewerColumn column = viewer.getViewerColumn(columnIndex + 1);
            if (column !is null
                    && column.getEditingSupport() !is null
                    && column.getEditingSupport().canEdit_package(
                            row.getItem().getData())) {
                rv = row.getCell(columnIndex + 1);
            } else {
                rv = searchNextCell(row, viewer, columnIndex + 1, startIndex);
            }
        } else {
            if ((feature & TABBING_CYCLE_IN_ROW) is TABBING_CYCLE_IN_ROW) {
                // Check that we don't get into endless loop
                if (columnIndex + 1 !is startIndex) {
                    // Start from -1 from the virtual column before the
                    // first one
                    rv = searchNextCell(row, viewer, -1, startIndex);
                }
            } else if ((feature & TABBING_MOVE_TO_ROW_NEIGHBOR) is TABBING_MOVE_TO_ROW_NEIGHBOR) {
                ViewerRow rowBelow = row.getNeighbor(ViewerRow.BELOW, false);
                if (rowBelow !is null) {
                    rv = searchNextCell(rowBelow, viewer, -1, startIndex);
                }
            }
        }

        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;
    }
}