| /******************************************************************************* |
| * Copyright (c) 2012, 2020 Original authors and others. |
| * |
| * This program and the accompanying materials are made |
| * available under the terms of the Eclipse Public License 2.0 |
| * which is available at https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * Original authors and others - initial API and implementation |
| ******************************************************************************/ |
| package org.eclipse.nebula.widgets.nattable.ui.mode; |
| |
| import org.eclipse.nebula.widgets.nattable.NatTable; |
| import org.eclipse.nebula.widgets.nattable.ui.NatEventData; |
| import org.eclipse.nebula.widgets.nattable.ui.action.DragModeEventHandler; |
| import org.eclipse.nebula.widgets.nattable.ui.action.IDragMode; |
| import org.eclipse.nebula.widgets.nattable.ui.action.IMouseAction; |
| import org.eclipse.nebula.widgets.nattable.ui.action.IMouseClickAction; |
| import org.eclipse.nebula.widgets.nattable.ui.util.MouseEventHelper; |
| import org.eclipse.swt.events.KeyEvent; |
| import org.eclipse.swt.events.MouseEvent; |
| |
| public class MouseModeEventHandler extends AbstractModeEventHandler { |
| |
| private MouseEvent initialMouseDownEvent; |
| |
| private IMouseAction singleClickAction; |
| |
| private IMouseAction doubleClickAction; |
| |
| private boolean mouseDown; |
| |
| private IDragMode dragMode; |
| |
| private boolean doubleClick; |
| |
| private boolean skipProcessing = false; |
| |
| private Runnable delayedSingleClickRunnable = null; |
| |
| public MouseModeEventHandler( |
| ModeSupport modeSupport, NatTable natTable, MouseEvent initialMouseDownEvent, |
| IMouseAction singleClickAction, IMouseAction doubleClickAction, IDragMode dragMode) { |
| |
| super(modeSupport, natTable); |
| |
| this.mouseDown = true; |
| |
| this.initialMouseDownEvent = initialMouseDownEvent; |
| |
| this.singleClickAction = singleClickAction; |
| this.doubleClickAction = doubleClickAction; |
| this.dragMode = dragMode; |
| } |
| |
| @Override |
| public void mouseUp(final MouseEvent event) { |
| this.mouseDown = false; |
| // if the mouse is slightly moved between two clicks, it is not |
| // recognized as double click by the system. But as we support a click |
| // radius for high sensitive mouse movements, we check here if a double |
| // click runnable is scheduled and perform a double click if the single |
| // click is not executed yet. The click radius is handled in mouseMove() |
| // where we execute the single click if the mouse moves out of the click |
| // radius after mouseUp() |
| if (this.delayedSingleClickRunnable == null) { |
| this.doubleClick = false; |
| } else { |
| // avoid execution of single click action and trigger double click |
| mouseDoubleClick(event); |
| return; |
| } |
| |
| if (this.singleClickAction != null) { |
| // convert/validate/commit/close possible open editor needed in case |
| // of conversion/validation errors to cancel any action |
| if (this.natTable.commitAndCloseActiveCellEditor()) { |
| if (this.doubleClickAction != null |
| && (isActionExclusive(this.singleClickAction) || isActionExclusive(this.doubleClickAction))) { |
| // If a doubleClick action is registered and either the |
| // single click or the double click action is exclusive, |
| // wait to see if this mouseUp is part of a doubleClick or |
| // not. |
| this.delayedSingleClickRunnable = () -> { |
| MouseModeEventHandler.this.delayedSingleClickRunnable = null; |
| if (!MouseModeEventHandler.this.doubleClick && !MouseModeEventHandler.this.skipProcessing) { |
| executeClickAction(MouseModeEventHandler.this.singleClickAction, event); |
| } |
| }; |
| event.display.timerExec(event.display.getDoubleClickTime(), this.delayedSingleClickRunnable); |
| } else { |
| executeClickAction(this.singleClickAction, event); |
| } |
| } |
| } else if (this.doubleClickAction == null) { |
| // No single or double click action registered when mouseUp |
| // detected. Switch back to normal mode. |
| switchMode(Mode.NORMAL_MODE); |
| } |
| } |
| |
| @Override |
| public void mouseDown(MouseEvent event) { |
| // another mouse click was performed than initial |
| // This handling is necessary to react correctly e.g. if an action is |
| // registered for left double click and an action for right single |
| // click. Performing a left and right click in short sequence, nothing |
| // will happen without this handling. This is also true in case a click |
| // is performed without modifier key pressed and performing a mouse |
| // click with modifier key pressed shortly after. |
| if ((event.button != this.initialMouseDownEvent.button) |
| || (event.stateMask != this.initialMouseDownEvent.stateMask)) { |
| // ensure the double click runnable is not executed and process |
| // single click immediately |
| this.skipProcessing = true; |
| executeClickAction(this.singleClickAction, this.initialMouseDownEvent); |
| |
| // reset to the parent mode |
| switchMode(Mode.NORMAL_MODE); |
| // start the mouse event processing for the new button |
| getModeSupport().mouseDown(event); |
| } |
| } |
| |
| @Override |
| public void keyPressed(KeyEvent event) { |
| // ensure the double click runnable is not executed and process single |
| // click immediately |
| this.skipProcessing = true; |
| executeClickAction(this.singleClickAction, this.initialMouseDownEvent); |
| |
| // reset to the parent mode |
| switchMode(Mode.NORMAL_MODE); |
| // start the key event processing |
| getModeSupport().keyPressed(event); |
| } |
| |
| @Override |
| public void mouseDoubleClick(MouseEvent event) { |
| // double click event is fired after second mouse up event, so it needs |
| // to be set to true here |
| // this way the exclusive single click action knows that it should not |
| // execute as a double click was performed |
| this.doubleClick = true; |
| |
| executeClickAction(this.doubleClickAction, event); |
| } |
| |
| @Override |
| public synchronized void mouseMove(MouseEvent event) { |
| if (this.mouseDown && this.dragMode != null) { |
| if (this.natTable.commitAndCloseActiveCellEditor()) { |
| this.dragMode.mouseDown(this.natTable, this.initialMouseDownEvent); |
| switchMode(new DragModeEventHandler(getModeSupport(), this.natTable, this.dragMode, this, this.initialMouseDownEvent)); |
| } else { |
| switchMode(Mode.NORMAL_MODE); |
| } |
| } else if (!this.mouseDown |
| && !MouseEventHelper.treatAsClick(this.initialMouseDownEvent, event)) { |
| |
| if (this.doubleClickAction != null) { |
| // if mouseUp was called already and the mouse moves out of the |
| // click radius, ensure the double click runnable is not |
| // executed |
| // and process single click immediately as it can not become a |
| // double click |
| this.skipProcessing = true; |
| executeClickAction(this.singleClickAction, this.initialMouseDownEvent); |
| } |
| |
| // No drag mode registered when mouseMove detected. Switch back |
| // to normal mode. |
| switchMode(Mode.NORMAL_MODE); |
| } |
| } |
| |
| /** |
| * Executes the given IMouseAction and switches the DisplayMode back to |
| * normal. |
| * |
| * @param action |
| * The IMouseAction that should be executed. |
| * @param event |
| * The MouseEvent that triggers the action |
| */ |
| private void executeClickAction(IMouseAction action, MouseEvent event) { |
| // convert/validate/commit/close possible open editor |
| // needed in case of conversion/validation errors to cancel any action |
| if (this.natTable.commitAndCloseActiveCellEditor()) { |
| if (action != null && event != null) { |
| event.data = NatEventData.createInstanceFromEvent(event); |
| action.run(this.natTable, event); |
| // Single click action complete. Switch back to normal mode. |
| switchMode(Mode.NORMAL_MODE); |
| } |
| } |
| } |
| |
| /** |
| * Checks whether the given IMouseAction should be performed exclusive or |
| * not. If there is a single and a double click action configured, by |
| * default both the single and the double click will be performed. This |
| * behaviour can be modified if the given action is of type |
| * IMouseClickAction and configured to be exclusive. In this case the single |
| * or the double click action will be performed. |
| * |
| * @param action |
| * The IMouseAction to check |
| * @return <code>true</code> if the given IMouseAction should be called |
| * exclusively, <code>false</code> if not. |
| */ |
| private boolean isActionExclusive(IMouseAction action) { |
| if (action instanceof IMouseClickAction) { |
| return ((IMouseClickAction) action).isExclusive(); |
| } |
| return false; |
| } |
| } |