diff --git a/bundles/org.eclipse.e4.ui.bindings/src/org/eclipse/e4/ui/bindings/keys/KeyBindingDispatcher.java b/bundles/org.eclipse.e4.ui.bindings/src/org/eclipse/e4/ui/bindings/keys/KeyBindingDispatcher.java index 36f1905234c..47260981b90 100644 --- a/bundles/org.eclipse.e4.ui.bindings/src/org/eclipse/e4/ui/bindings/keys/KeyBindingDispatcher.java +++ b/bundles/org.eclipse.e4.ui.bindings/src/org/eclipse/e4/ui/bindings/keys/KeyBindingDispatcher.java @@ -267,6 +267,21 @@ private static boolean isOutOfOrderKey(List keyStrokes) { @Optional private Logger logger; + /** + * The command executed the last time {@link #executeCommand(ParameterizedCommand, Event)} was + * called, together with the native timestamp of the triggering event. On some platforms (e.g. + * GTK on Linux) a single physical key press can be delivered to the dispatcher more than once + * for the same native event (for example, once as an SWT.Traverse event and once + * as a redundant SWT.KeyDown event), which would otherwise execute the resulting + * command twice, e.g. skipping over an extra tab when navigating with Ctrl+PageUp/PageDown. + * These fields are used to detect and suppress such a duplicate re-delivery of the very same + * native key event, identified by its (platform-provided) timestamp. + * + * @see #isDuplicateKeyEvent(ParameterizedCommand, Event) + */ + private ParameterizedCommand lastExecutedCommand; + private int lastExecutedEventTime; + /** * Performs the actual execution of the command by looking up the current handler from the * command manager. If there is a handler and it is enabled, then it tries the actual execution. @@ -292,6 +307,14 @@ public final boolean executeCommand(final ParameterizedCommand parameterizedComm throw new NotEnabledException("Command should have been disabled via activity: " + parameterizedCommand); //$NON-NLS-1$ } + if (isDuplicateKeyEvent(parameterizedCommand, trigger)) { + if (isTracingEnabled()) { + logger.trace("Suppressed duplicate execution of " + parameterizedCommand + " for the same native event " //$NON-NLS-1$ //$NON-NLS-2$ + + "(time=" + trigger.time + ") in " + describe(context)); //$NON-NLS-1$ //$NON-NLS-2$ + } + return true; + } + final EHandlerService handlerService = getHandlerService(); final Command command = parameterizedCommand.getCommand(); @@ -324,6 +347,37 @@ public final boolean executeCommand(final ParameterizedCommand parameterizedComm return (commandDefined && commandHandled); } + /** + * Detects whether the given command execution is a duplicate re-delivery of the same native + * key event that was already used to execute this very command. On some platforms (currently + * known to affect GTK on Linux) a single physical key press can reach the dispatcher twice, + * e.g. once via an SWT.Traverse event and once more via a subsequently generated + * SWT.KeyDown event, both carrying the native event's original timestamp. Without + * this guard, such duplicate delivery would execute the resulting command (e.g. "Next Editor" + * bound to Ctrl+PageDown) twice for a single key press, which is, for example, visible as + * navigation skipping over an extra tab. + *

+ * As a side effect, this method records the given command/trigger pair as the last executed + * one, so that a subsequent call can be compared against it. + * + * @param parameterizedCommand + * the command about to be executed; must not be null. + * @param trigger + * the triggering event; may be null, in which case no event was + * involved and de-duplication does not apply. + * @return true if this looks like a duplicate delivery of the same native event + * that already triggered the same command; false otherwise. + */ + private boolean isDuplicateKeyEvent(final ParameterizedCommand parameterizedCommand, final Event trigger) { + if (trigger == null) { + return false; + } + boolean duplicate = parameterizedCommand == lastExecutedCommand && trigger.time == lastExecutedEventTime; + lastExecutedCommand = parameterizedCommand; + lastExecutedEventTime = trigger.time; + return duplicate; + } + private boolean handleCommandExecution(final ParameterizedCommand parameterizedCommand, final EHandlerService handlerService, final Event trigger, Object handler) { final IEclipseContext staticContext = createContext(trigger);