blob: 2fc720d748d2b6de1377ef6d3a4cb67a66a9a469 [file] [log] [blame]
/*******************************************************************************
* 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;
}
}