/*******************************************************************************
 * Copyright (c) 2000, 2014 IBM Corporation and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.ui.part;

import java.util.HashMap;
import java.util.Iterator;

import org.eclipse.core.runtime.Assert;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.jface.viewers.CellEditor;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.ui.IActionBars;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.actions.ActionFactory;
import org.eclipse.ui.internal.IWorkbenchHelpContextIds;
import org.eclipse.ui.internal.WorkbenchMessages;

/**
 * Handles the redirection of the global actions Cut, Copy, Paste,
 * Delete, Select All, Find, Undo and Redo to either the current
 * inline cell editor or the part's supplied action handler.
 * <p>
 * This class may be instantiated; it is not intended to be subclassed.
 * </p><p>
 * Example usage:
 * <pre>
 * actionHandler = new CellEditorActionHandler(this.getViewSite().getActionBars());
 * actionHandler.addCellEditor(textCellEditor1);
 * actionHandler.addCellEditor(textCellEditor2);
 * actionHandler.setSelectAllAction(selectAllAction);
 * </pre>
 * </p>
 * @noextend This class is not intended to be subclassed by clients.
 */
public class CellEditorActionHandler {
    private CutActionHandler cellCutAction = new CutActionHandler();

    private CopyActionHandler cellCopyAction = new CopyActionHandler();

    private PasteActionHandler cellPasteAction = new PasteActionHandler();

    private DeleteActionHandler cellDeleteAction = new DeleteActionHandler();

    private SelectAllActionHandler cellSelectAllAction = new SelectAllActionHandler();

    private FindActionHandler cellFindAction = new FindActionHandler();

    private UndoActionHandler cellUndoAction = new UndoActionHandler();

    private RedoActionHandler cellRedoAction = new RedoActionHandler();

    private IAction cutAction;

    private IAction copyAction;

    private IAction pasteAction;

    private IAction deleteAction;

    private IAction selectAllAction;

    private IAction findAction;

    private IAction undoAction;

    private IAction redoAction;

    private IPropertyChangeListener cutActionListener = new ActionEnabledChangeListener(
            cellCutAction);

    private IPropertyChangeListener copyActionListener = new ActionEnabledChangeListener(
            cellCopyAction);

    private IPropertyChangeListener pasteActionListener = new ActionEnabledChangeListener(
            cellPasteAction);

    private IPropertyChangeListener deleteActionListener = new ActionEnabledChangeListener(
            cellDeleteAction);

    private IPropertyChangeListener selectAllActionListener = new ActionEnabledChangeListener(
            cellSelectAllAction);

    private IPropertyChangeListener findActionListener = new ActionEnabledChangeListener(
            cellFindAction);

    private IPropertyChangeListener undoActionListener = new ActionEnabledChangeListener(
            cellUndoAction);

    private IPropertyChangeListener redoActionListener = new ActionEnabledChangeListener(
            cellRedoAction);

    private CellEditor activeEditor;

    private IPropertyChangeListener cellListener = new CellChangeListener();

    private Listener controlListener = new ControlListener();

    private HashMap controlToEditor = new HashMap();

    private class ControlListener implements Listener {
        @Override
		public void handleEvent(Event event) {
            switch (event.type) {
            case SWT.Activate:
                activeEditor = (CellEditor) controlToEditor.get(event.widget);
                if (activeEditor != null) {
					activeEditor.addPropertyChangeListener(cellListener);
				}
                updateActionsEnableState();
                break;
            case SWT.Deactivate:
                if (activeEditor != null) {
					activeEditor.removePropertyChangeListener(cellListener);
				}
                activeEditor = null;
                updateActionsEnableState();
                break;
            default:
                break;
            }
        }
    }

    private class ActionEnabledChangeListener implements
            IPropertyChangeListener {
        private IAction actionHandler;

        protected ActionEnabledChangeListener(IAction actionHandler) {
            super();
            this.actionHandler = actionHandler;
        }

        @Override
		public void propertyChange(PropertyChangeEvent event) {
            if (activeEditor != null) {
				return;
			}
            if (event.getProperty().equals(IAction.ENABLED)) {
                Boolean bool = (Boolean) event.getNewValue();
                actionHandler.setEnabled(bool.booleanValue());
                return;
            }
            // If the underlying action's text has changed, we need to
            // change the text.  See
            // https://bugs.eclipse.org/bugs/show_bug.cgi?id=154410
            if (event.getProperty().equals(IAction.TEXT)) {
                String text = (String) event.getNewValue();
                actionHandler.setText(text);
                return;
            }
            if (event.getProperty().equals(IAction.TOOL_TIP_TEXT)) {
                String text = (String) event.getNewValue();
                actionHandler.setToolTipText(text);
                return;
            }
        }
    }

    private class CellChangeListener implements IPropertyChangeListener {
        @Override
		public void propertyChange(PropertyChangeEvent event) {
            if (activeEditor == null) {
				return;
			}
            if (event.getProperty().equals(CellEditor.CUT)) {
                cellCutAction.setEnabled(activeEditor.isCutEnabled());
                return;
            }
            if (event.getProperty().equals(CellEditor.COPY)) {
                cellCopyAction.setEnabled(activeEditor.isCopyEnabled());
                return;
            }
            if (event.getProperty().equals(CellEditor.PASTE)) {
                cellPasteAction.setEnabled(activeEditor.isPasteEnabled());
                return;
            }
            if (event.getProperty().equals(CellEditor.DELETE)) {
                cellDeleteAction.setEnabled(activeEditor.isDeleteEnabled());
                return;
            }
            if (event.getProperty().equals(CellEditor.SELECT_ALL)) {
                cellSelectAllAction.setEnabled(activeEditor
                        .isSelectAllEnabled());
                return;
            }
            if (event.getProperty().equals(CellEditor.FIND)) {
                cellFindAction.setEnabled(activeEditor.isFindEnabled());
                return;
            }
            if (event.getProperty().equals(CellEditor.UNDO)) {
                cellUndoAction.setEnabled(activeEditor.isUndoEnabled());
                return;
            }
            if (event.getProperty().equals(CellEditor.REDO)) {
                cellRedoAction.setEnabled(activeEditor.isRedoEnabled());
                return;
            }
        }
    }

    private class CutActionHandler extends Action {
        protected CutActionHandler() {
            setId("CellEditorCutActionHandler");//$NON-NLS-1$
            setEnabled(false);
            PlatformUI.getWorkbench().getHelpSystem().setHelp(this, IWorkbenchHelpContextIds.CELL_CUT_ACTION);
        }

        @Override
		public void runWithEvent(Event event) {
            if (activeEditor != null) {
                activeEditor.performCut();
                return;
            }
            if (cutAction != null) {
                cutAction.runWithEvent(event);
                return;
            }
        }

        public void updateEnabledState() {
            if (activeEditor != null) {
                setEnabled(activeEditor.isCutEnabled());
                return;
            }
            if (cutAction != null) {
                setEnabled(cutAction.isEnabled());
                return;
            }
            setEnabled(false);
        }
    }

    private class CopyActionHandler extends Action {
        protected CopyActionHandler() {
            setId("CellEditorCopyActionHandler");//$NON-NLS-1$
            setEnabled(false);
            PlatformUI.getWorkbench().getHelpSystem().setHelp(this, IWorkbenchHelpContextIds.CELL_COPY_ACTION);
        }

        @Override
		public void runWithEvent(Event event) {
            if (activeEditor != null) {
                activeEditor.performCopy();
                return;
            }
            if (copyAction != null) {
                copyAction.runWithEvent(event);
                return;
            }
        }

        public void updateEnabledState() {
            if (activeEditor != null) {
                setEnabled(activeEditor.isCopyEnabled());
                return;
            }
            if (copyAction != null) {
                setEnabled(copyAction.isEnabled());
                return;
            }
            setEnabled(false);
        }
    }

    private class PasteActionHandler extends Action {
        protected PasteActionHandler() {
            setId("CellEditorPasteActionHandler");//$NON-NLS-1$
            setEnabled(false);
            PlatformUI.getWorkbench().getHelpSystem().setHelp(this, IWorkbenchHelpContextIds.CELL_PASTE_ACTION);
        }

        @Override
		public void runWithEvent(Event event) {
            if (activeEditor != null) {
                activeEditor.performPaste();
                return;
            }
            if (pasteAction != null) {
                pasteAction.runWithEvent(event);
                return;
            }
        }

        public void updateEnabledState() {
            if (activeEditor != null) {
                setEnabled(activeEditor.isPasteEnabled());
                return;
            }
            if (pasteAction != null) {
                setEnabled(pasteAction.isEnabled());
                return;
            }
            setEnabled(false);
        }
    }

    private class DeleteActionHandler extends Action {
        protected DeleteActionHandler() {
            setId("CellEditorDeleteActionHandler");//$NON-NLS-1$
            setEnabled(false);
            PlatformUI.getWorkbench().getHelpSystem().setHelp(this, IWorkbenchHelpContextIds.CELL_DELETE_ACTION);
        }

        @Override
		public void runWithEvent(Event event) {
            if (activeEditor != null) {
                activeEditor.performDelete();
                return;
            }
            if (deleteAction != null) {
                deleteAction.runWithEvent(event);
                return;
            }
        }

        public void updateEnabledState() {
            if (activeEditor != null) {
                setEnabled(activeEditor.isDeleteEnabled());
                return;
            }
            if (deleteAction != null) {
                setEnabled(deleteAction.isEnabled());
                return;
            }
            setEnabled(false);
        }
    }

    private class SelectAllActionHandler extends Action {
        protected SelectAllActionHandler() {
            setId("CellEditorSelectAllActionHandler");//$NON-NLS-1$
            setEnabled(false);
            PlatformUI.getWorkbench().getHelpSystem().setHelp(this, IWorkbenchHelpContextIds.CELL_SELECT_ALL_ACTION);
        }

        @Override
		public void runWithEvent(Event event) {
            if (activeEditor != null) {
                activeEditor.performSelectAll();
                return;
            }
            if (selectAllAction != null) {
                selectAllAction.runWithEvent(event);
                return;
            }
        }

        public void updateEnabledState() {
            if (activeEditor != null) {
                setEnabled(activeEditor.isSelectAllEnabled());
                return;
            }
            if (selectAllAction != null) {
                setEnabled(selectAllAction.isEnabled());
                return;
            }
            setEnabled(false);
        }
    }

    private class FindActionHandler extends Action {
        protected FindActionHandler() {
            setId("CellEditorFindActionHandler");//$NON-NLS-1$
            setEnabled(false);
            PlatformUI.getWorkbench().getHelpSystem().setHelp(this, IWorkbenchHelpContextIds.CELL_FIND_ACTION);
        }

        @Override
		public void runWithEvent(Event event) {
            if (activeEditor != null) {
                activeEditor.performFind();
                return;
            }
            if (findAction != null) {
                findAction.runWithEvent(event);
                return;
            }
        }

        public void updateEnabledState() {
            if (activeEditor != null) {
                setEnabled(activeEditor.isFindEnabled());
                return;
            }
            if (findAction != null) {
                setEnabled(findAction.isEnabled());
                return;
            }
            setEnabled(false);
        }
    }

    private class UndoActionHandler extends Action {
        protected UndoActionHandler() {
            setId("CellEditorUndoActionHandler");//$NON-NLS-1$
            setEnabled(false);
            PlatformUI.getWorkbench().getHelpSystem().setHelp(this, IWorkbenchHelpContextIds.CELL_UNDO_ACTION);
        }

        @Override
		public void runWithEvent(Event event) {
            if (activeEditor != null) {
                activeEditor.performUndo();
                return;
            }
            if (undoAction != null) {
                undoAction.runWithEvent(event);
                return;
            }
        }

        public void updateEnabledState() {
            if (activeEditor != null) {
                setEnabled(activeEditor.isUndoEnabled());
                setText(WorkbenchMessages.Workbench_undo);
                setToolTipText(WorkbenchMessages.Workbench_undoToolTip);
                return;
            }
            if (undoAction != null) {
                setEnabled(undoAction.isEnabled());
                setText(undoAction.getText());
                setToolTipText(undoAction.getToolTipText());
                return;
            }
            setEnabled(false);
        }
    }

    private class RedoActionHandler extends Action {
        protected RedoActionHandler() {
            setId("CellEditorRedoActionHandler");//$NON-NLS-1$
            setEnabled(false);
            PlatformUI.getWorkbench().getHelpSystem().setHelp(this, IWorkbenchHelpContextIds.CELL_REDO_ACTION);
        }

        @Override
		public void runWithEvent(Event event) {
            if (activeEditor != null) {
                activeEditor.performRedo();
                return;
            }
            if (redoAction != null) {
                redoAction.runWithEvent(event);
                return;
            }
        }

        public void updateEnabledState() {
            if (activeEditor != null) {
                setEnabled(activeEditor.isRedoEnabled());
                setText(WorkbenchMessages.Workbench_redo);
                setToolTipText(WorkbenchMessages.Workbench_redoToolTip);
                return;
            }
            if (redoAction != null) {
                setEnabled(redoAction.isEnabled());
                setText(redoAction.getText());
                setToolTipText(redoAction.getToolTipText());
                return;
            }
            setEnabled(false);
        }
    }

    /**
     * Creates a <code>CellEditor</code> action handler
     * for the global Cut, Copy, Paste, Delete, Select All,
     * Find, Undo, and Redo of the action bar.
     *
     * @param actionBar the action bar to register global
     *    action handlers.
     */
    public CellEditorActionHandler(IActionBars actionBar) {
        super();
        actionBar.setGlobalActionHandler(ActionFactory.CUT.getId(),
                cellCutAction);
        actionBar.setGlobalActionHandler(ActionFactory.COPY.getId(),
                cellCopyAction);
        actionBar.setGlobalActionHandler(ActionFactory.PASTE.getId(),
                cellPasteAction);
        actionBar.setGlobalActionHandler(ActionFactory.DELETE.getId(),
                cellDeleteAction);
        actionBar.setGlobalActionHandler(ActionFactory.SELECT_ALL.getId(),
                cellSelectAllAction);
        actionBar.setGlobalActionHandler(ActionFactory.FIND.getId(),
                cellFindAction);
        actionBar.setGlobalActionHandler(ActionFactory.UNDO.getId(),
                cellUndoAction);
        actionBar.setGlobalActionHandler(ActionFactory.REDO.getId(),
                cellRedoAction);
    }

    /**
     * Adds a <code>CellEditor</code> to the handler so that the
     * Cut, Copy, Paste, Delete, Select All, Find, Undo, and Redo
     * actions are redirected to it when active.
     *
     * @param editor the <code>CellEditor</code>
     */
    public void addCellEditor(CellEditor editor) {
        if (editor == null) {
			return;
		}

        Control control = editor.getControl();
        Assert.isNotNull(control);
        controlToEditor.put(control, editor);
        control.addListener(SWT.Activate, controlListener);
        control.addListener(SWT.Deactivate, controlListener);

        if (control.isFocusControl()) {
            activeEditor = editor;
            editor.addPropertyChangeListener(cellListener);
            updateActionsEnableState();
        }
    }

    /**
     * Disposes of this action handler
     */
    public void dispose() {
        setCutAction(null);
        setCopyAction(null);
        setPasteAction(null);
        setDeleteAction(null);
        setSelectAllAction(null);
        setFindAction(null);
        setUndoAction(null);
        setRedoAction(null);

        Iterator itr = controlToEditor.keySet().iterator();
        while (itr.hasNext()) {
            Control control = (Control) itr.next();
            if (!control.isDisposed()) {
                control.removeListener(SWT.Activate, controlListener);
                control.removeListener(SWT.Deactivate, controlListener);
            }
        }
        controlToEditor.clear();

        if (activeEditor != null) {
			activeEditor.removePropertyChangeListener(cellListener);
		}
        activeEditor = null;

    }

    /**
     * Removes a <code>CellEditor</code> from the handler
     * so that the Cut, Copy, Paste, Delete, Select All, Find
     * Undo, and Redo actions are no longer redirected to it.
     *
     * @param editor the <code>CellEditor</code>
     */
    public void removeCellEditor(CellEditor editor) {
        if (editor == null) {
			return;
		}

        if (activeEditor == editor) {
            activeEditor.removePropertyChangeListener(cellListener);
            activeEditor = null;
        }

        Control control = editor.getControl();
        if (control != null) {
            controlToEditor.remove(control);
            if (!control.isDisposed()) {
                control.removeListener(SWT.Activate, controlListener);
                control.removeListener(SWT.Deactivate, controlListener);
            }
        }
    }

    /**
     * Sets the default <code>IAction</code> handler for the Copy
     * action. This <code>IAction</code> is run only if no active
     * cell editor control.
     *
     * @param action the <code>IAction</code> to run for the
     *    Copy action, or <code>null</code> if not interested.
     */
    public void setCopyAction(IAction action) {
        if (copyAction == action) {
			return;
		}

        if (copyAction != null) {
			copyAction.removePropertyChangeListener(copyActionListener);
		}

        copyAction = action;

        if (copyAction != null) {
			copyAction.addPropertyChangeListener(copyActionListener);
		}

        cellCopyAction.updateEnabledState();
    }

    /**
     * Sets the default <code>IAction</code> handler for the Cut
     * action. This <code>IAction</code> is run only if no active
     * cell editor control.
     *
     * @param action the <code>IAction</code> to run for the
     *    Cut action, or <code>null</code> if not interested.
     */
    public void setCutAction(IAction action) {
        if (cutAction == action) {
			return;
		}

        if (cutAction != null) {
			cutAction.removePropertyChangeListener(cutActionListener);
		}

        cutAction = action;

        if (cutAction != null) {
			cutAction.addPropertyChangeListener(cutActionListener);
		}

        cellCutAction.updateEnabledState();
    }

    /**
     * Sets the default <code>IAction</code> handler for the Delete
     * action. This <code>IAction</code> is run only if no active
     * cell editor control.
     *
     * @param action the <code>IAction</code> to run for the
     *    Delete action, or <code>null</code> if not interested.
     */
    public void setDeleteAction(IAction action) {
        if (deleteAction == action) {
			return;
		}

        if (deleteAction != null) {
			deleteAction.removePropertyChangeListener(deleteActionListener);
		}

        deleteAction = action;

        if (deleteAction != null) {
			deleteAction.addPropertyChangeListener(deleteActionListener);
		}

        cellDeleteAction.updateEnabledState();
    }

    /**
     * Sets the default <code>IAction</code> handler for the Find
     * action. This <code>IAction</code> is run only if no active
     * cell editor control.
     *
     * @param action the <code>IAction</code> to run for the
     *    Find action, or <code>null</code> if not interested.
     */
    public void setFindAction(IAction action) {
        if (findAction == action) {
			return;
		}

        if (findAction != null) {
			findAction.removePropertyChangeListener(findActionListener);
		}

        findAction = action;

        if (findAction != null) {
			findAction.addPropertyChangeListener(findActionListener);
		}

        cellFindAction.updateEnabledState();
    }

    /**
     * Sets the default <code>IAction</code> handler for the Paste
     * action. This <code>IAction</code> is run only if no active
     * cell editor control.
     *
     * @param action the <code>IAction</code> to run for the
     *    Paste action, or <code>null</code> if not interested.
     */
    public void setPasteAction(IAction action) {
        if (pasteAction == action) {
			return;
		}

        if (pasteAction != null) {
			pasteAction.removePropertyChangeListener(pasteActionListener);
		}

        pasteAction = action;

        if (pasteAction != null) {
			pasteAction.addPropertyChangeListener(pasteActionListener);
		}

        cellPasteAction.updateEnabledState();
    }

    /**
     * Sets the default <code>IAction</code> handler for the Redo
     * action. This <code>IAction</code> is run only if no active
     * cell editor control.
     *
     * @param action the <code>IAction</code> to run for the
     *    Redo action, or <code>null</code> if not interested.
     */
    public void setRedoAction(IAction action) {
        if (redoAction == action) {
			return;
		}

        if (redoAction != null) {
			redoAction.removePropertyChangeListener(redoActionListener);
		}

        redoAction = action;

        if (redoAction != null) {
			redoAction.addPropertyChangeListener(redoActionListener);
		}

        cellRedoAction.updateEnabledState();
    }

    /**
     * Sets the default <code>IAction</code> handler for the Select All
     * action. This <code>IAction</code> is run only if no active
     * cell editor control.
     *
     * @param action the <code>IAction</code> to run for the
     *    Select All action, or <code>null</code> if not interested.
     */
    public void setSelectAllAction(IAction action) {
        if (selectAllAction == action) {
			return;
		}

        if (selectAllAction != null) {
			selectAllAction
                    .removePropertyChangeListener(selectAllActionListener);
		}

        selectAllAction = action;

        if (selectAllAction != null) {
			selectAllAction.addPropertyChangeListener(selectAllActionListener);
		}

        cellSelectAllAction.updateEnabledState();
    }

    /**
     * Sets the default <code>IAction</code> handler for the Undo
     * action. This <code>IAction</code> is run only if no active
     * cell editor control.
     *
     * @param action the <code>IAction</code> to run for the
     *    Undo action, or <code>null</code> if not interested.
     */
    public void setUndoAction(IAction action) {
        if (undoAction == action) {
			return;
		}

        if (undoAction != null) {
			undoAction.removePropertyChangeListener(undoActionListener);
		}

        undoAction = action;

        if (undoAction != null) {
			undoAction.addPropertyChangeListener(undoActionListener);
		}

        cellUndoAction.updateEnabledState();
    }

    /**
     * Updates the enable state of the Cut, Copy,
     * Paste, Delete, Select All, Find, Undo, and
     * Redo action handlers
     */
    private void updateActionsEnableState() {
        cellCutAction.updateEnabledState();
        cellCopyAction.updateEnabledState();
        cellPasteAction.updateEnabledState();
        cellDeleteAction.updateEnabledState();
        cellSelectAllAction.updateEnabledState();
        cellFindAction.updateEnabledState();
        cellUndoAction.updateEnabledState();
        cellRedoAction.updateEnabledState();
    }
}
