diff --git a/platform/lang-impl/src/com/intellij/ide/actions/SearchEverywhereAction.java b/platform/lang-impl/src/com/intellij/ide/actions/SearchEverywhereAction.java index e1d5582204df..ae7ee4363247 100644 --- a/platform/lang-impl/src/com/intellij/ide/actions/SearchEverywhereAction.java +++ b/platform/lang-impl/src/com/intellij/ide/actions/SearchEverywhereAction.java @@ -56,6 +56,7 @@ import com.intellij.openapi.fileEditor.impl.EditorHistoryManager; import com.intellij.openapi.keymap.KeymapManager; import com.intellij.openapi.keymap.KeymapUtil; import com.intellij.openapi.keymap.MacKeymapUtil; +import com.intellij.openapi.keymap.impl.ModifierKeyDoubleClickHandler; import com.intellij.openapi.options.Configurable; import com.intellij.openapi.options.SearchableConfigurable; import com.intellij.openapi.progress.ProcessCanceledException; @@ -151,106 +152,25 @@ public class SearchEverywhereAction extends AnAction implements CustomComponentA private Component myContextComponent; private CalcThread myCalcThread; private static AtomicBoolean ourShiftIsPressed = new AtomicBoolean(false); - private final static Couple ourPressed = Couple.of(new AtomicBoolean(false), new AtomicBoolean(false)); - private final static Couple ourReleased = Couple.of(new AtomicBoolean(false), new AtomicBoolean(false)); - private static AtomicBoolean ourOtherKeyWasPressed = new AtomicBoolean(false); - private static AtomicLong ourLastTimePressed = new AtomicLong(0); private static AtomicBoolean showAll = new AtomicBoolean(false); private volatile ActionCallback myCurrentWorker = ActionCallback.DONE; private int myHistoryIndex = 0; boolean mySkipFocusGain = false; static { + ModifierKeyDoubleClickHandler.getInstance().registerAction(IdeActions.ACTION_SEARCH_EVERYWHERE, KeyEvent.VK_SHIFT, -1); + IdeEventQueue.getInstance().addPostprocessor(new IdeEventQueue.EventDispatcher() { @Override public boolean dispatch(AWTEvent event) { if (event instanceof KeyEvent) { - final KeyEvent keyEvent = (KeyEvent)event; - final int keyCode = keyEvent.getKeyCode(); - + final int keyCode = ((KeyEvent)event).getKeyCode(); if (keyCode == KeyEvent.VK_SHIFT) { ourShiftIsPressed.set(event.getID() == KeyEvent.KEY_PRESSED); - - if (keyEvent.isControlDown() || keyEvent.isAltDown() || keyEvent.isMetaDown()) { - resetState(); - return false; - } - if (ourOtherKeyWasPressed.get() && System.currentTimeMillis() - ourLastTimePressed.get() < 500) { - resetState(); - return false; - } - ourOtherKeyWasPressed.set(false); - if (ourPressed.first.get() && System.currentTimeMillis() - ourLastTimePressed.get() > 500) { - resetState(); - } - handleShift((KeyEvent)event); - return false; - } else { - ourLastTimePressed.set(System.currentTimeMillis()); - ourOtherKeyWasPressed.set(true); - if (keyCode == KeyEvent.VK_ESCAPE || keyCode == KeyEvent.VK_TAB) { - ourLastTimePressed.set(0); - } } - resetState(); } return false; } - - private void resetState() { - ourPressed.first.set(false); - ourPressed.second.set(false); - ourReleased.first.set(false); - ourReleased.second.set(false); - } - - private void handleShift(KeyEvent event) { - if (ourPressed.first.get() && System.currentTimeMillis() - ourLastTimePressed.get() > 300) { - resetState(); - return; - } - - if (event.getID() == KeyEvent.KEY_PRESSED) { - if (!ourPressed.first.get()) { - resetState(); - ourPressed.first.set(true); - ourLastTimePressed.set(System.currentTimeMillis()); - return; - } else { - if (ourPressed.first.get() && ourReleased.first.get()) { - ourPressed.second.set(true); - ourLastTimePressed.set(System.currentTimeMillis()); - return; - } - } - } else if (event.getID() == KeyEvent.KEY_RELEASED) { - if (ourPressed.first.get() && !ourReleased.first.get()) { - ourReleased.first.set(true); - ourLastTimePressed.set(System.currentTimeMillis()); - return; - } else if (ourPressed.first.get() && ourReleased.first.get() && ourPressed.second.get()) { - resetState(); - run(event); - return; - } - } - resetState(); - } - - private void run(KeyEvent event) { - final ActionManager actionManager = ActionManager.getInstance(); - final AnAction action = actionManager.getAction(IdeActions.ACTION_SEARCH_EVERYWHERE); - if (KeymapManager.getInstance().getActiveKeymap().getShortcuts(IdeActions.ACTION_SEARCH_EVERYWHERE).length > 0) { - return; - } - final AnActionEvent anActionEvent = new AnActionEvent(event, - DataManager.getInstance().getDataContext(IdeFocusManager.findInstance().getFocusOwner()), - ActionPlaces.MAIN_MENU, - action.getTemplatePresentation(), - actionManager, - 0); - action.actionPerformed(anActionEvent); - } }, null); } diff --git a/platform/platform-api/src/com/intellij/openapi/actionSystem/IdeActions.java b/platform/platform-api/src/com/intellij/openapi/actionSystem/IdeActions.java index d4ac053e994e..fddd07785540 100644 --- a/platform/platform-api/src/com/intellij/openapi/actionSystem/IdeActions.java +++ b/platform/platform-api/src/com/intellij/openapi/actionSystem/IdeActions.java @@ -50,6 +50,8 @@ public interface IdeActions { @NonNls String ACTION_EDITOR_COMPLETE_STATEMENT = "EditorCompleteStatement"; @NonNls String ACTION_EDITOR_USE_SOFT_WRAPS = "EditorToggleUseSoftWraps"; @NonNls String ACTION_EDITOR_ADD_OR_REMOVE_CARET= "EditorAddOrRemoveCaret"; + @NonNls String ACTION_EDITOR_CLONE_CARET_BELOW= "EditorCloneCaretBelow"; + @NonNls String ACTION_EDITOR_CLONE_CARET_ABOVE= "EditorCloneCaretAbove"; @NonNls String ACTION_EDITOR_NEXT_TEMPLATE_VARIABLE = "NextTemplateVariable"; @NonNls String ACTION_EDITOR_PREVIOUS_TEMPLATE_VARIABLE = "PreviousTemplateVariable"; diff --git a/platform/platform-impl/src/com/intellij/openapi/keymap/impl/IdeKeyEventDispatcher.java b/platform/platform-impl/src/com/intellij/openapi/keymap/impl/IdeKeyEventDispatcher.java index 98498df8ecdc..9a566e9915ef 100644 --- a/platform/platform-impl/src/com/intellij/openapi/keymap/impl/IdeKeyEventDispatcher.java +++ b/platform/platform-impl/src/com/intellij/openapi/keymap/impl/IdeKeyEventDispatcher.java @@ -634,7 +634,9 @@ public final class IdeKeyEventDispatcher implements Disposable { processor.onUpdatePassed(e, action, actionEvent); - ((DataManagerImpl.MyDataContext)myContext.getDataContext()).setEventCount(IdeEventQueue.getInstance().getEventCount(), this); + if (myContext.getDataContext() instanceof DataManagerImpl.MyDataContext) { // this is not true for test data contexts + ((DataManagerImpl.MyDataContext)myContext.getDataContext()).setEventCount(IdeEventQueue.getInstance().getEventCount(), this); + } actionManager.fireBeforeActionPerformed(action, actionEvent.getDataContext(), actionEvent); Component component = PlatformDataKeys.CONTEXT_COMPONENT.getData(actionEvent.getDataContext()); if (component != null && !component.isShowing()) { diff --git a/platform/platform-impl/src/com/intellij/openapi/keymap/impl/KeymapManagerImpl.java b/platform/platform-impl/src/com/intellij/openapi/keymap/impl/KeymapManagerImpl.java index 4c093211cfd6..122d13fdee9b 100644 --- a/platform/platform-impl/src/com/intellij/openapi/keymap/impl/KeymapManagerImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/keymap/impl/KeymapManagerImpl.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2013 JetBrains s.r.o. + * Copyright 2000-2014 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,6 +15,7 @@ */ package com.intellij.openapi.keymap.impl; +import com.intellij.openapi.actionSystem.IdeActions; import com.intellij.openapi.application.PathManager; import com.intellij.openapi.components.*; import com.intellij.openapi.diagnostic.Logger; @@ -35,6 +36,7 @@ import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.awt.event.KeyEvent; import java.io.File; import java.io.IOException; import java.util.*; @@ -92,6 +94,10 @@ public class KeymapManagerImpl extends KeymapManagerEx implements PersistentStat } } load(); + + ModifierKeyDoubleClickHandler.getInstance().registerAction(IdeActions.ACTION_EDITOR_CLONE_CARET_ABOVE, KeyEvent.VK_CONTROL, KeyEvent.VK_UP); + ModifierKeyDoubleClickHandler.getInstance().registerAction(IdeActions.ACTION_EDITOR_CLONE_CARET_BELOW, KeyEvent.VK_CONTROL, KeyEvent.VK_DOWN); + ourKeymapManagerInitialized = true; } diff --git a/platform/platform-impl/src/com/intellij/openapi/keymap/impl/ModifierKeyDoubleClickHandler.java b/platform/platform-impl/src/com/intellij/openapi/keymap/impl/ModifierKeyDoubleClickHandler.java new file mode 100644 index 000000000000..2b71711e7538 --- /dev/null +++ b/platform/platform-impl/src/com/intellij/openapi/keymap/impl/ModifierKeyDoubleClickHandler.java @@ -0,0 +1,210 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.openapi.keymap.impl; + +import com.intellij.ide.DataManager; +import com.intellij.ide.IdeEventQueue; +import com.intellij.openapi.actionSystem.ActionManager; +import com.intellij.openapi.actionSystem.ActionPlaces; +import com.intellij.openapi.actionSystem.AnAction; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.keymap.KeymapManager; +import com.intellij.openapi.util.Clock; +import com.intellij.openapi.util.Couple; +import com.intellij.openapi.wm.IdeFocusManager; +import com.intellij.util.containers.ConcurrentHashMap; +import gnu.trove.TIntIntHashMap; +import gnu.trove.TIntIntProcedure; +import org.jetbrains.annotations.NotNull; + +import java.awt.*; +import java.awt.event.InputEvent; +import java.awt.event.KeyEvent; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; + +/** + * Support for keyboard shortcuts like Control-double-click or Control-double-click+A + */ +public class ModifierKeyDoubleClickHandler { + private static final ModifierKeyDoubleClickHandler INSTANCE = new ModifierKeyDoubleClickHandler(); + + private final ConcurrentMap myDispatchers = new ConcurrentHashMap(); + + private ModifierKeyDoubleClickHandler() { } + + public static ModifierKeyDoubleClickHandler getInstance() { + return INSTANCE; + } + + /** + * @param actionId Id of action to be triggered on modifier+modifier[+actionKey] + * @param modifierKeyCode keyCode for modifier, e.g. KeyEvent.VK_SHIFT + * @param actionKeyCode keyCode for actionKey, or -1 if action should be triggered on bare modifier double click + */ + public void registerAction(@NotNull String actionId, + int modifierKeyCode, + int actionKeyCode) { + final MyDispatcher dispatcher = new MyDispatcher(actionId, modifierKeyCode, actionKeyCode); + IdeEventQueue.EventDispatcher oldDispatcher = myDispatchers.put(actionId, dispatcher); + IdeEventQueue.getInstance().addDispatcher(dispatcher, null); + if (oldDispatcher != null) { + IdeEventQueue.getInstance().removeDispatcher(oldDispatcher); + } + } + + public void unregisterAction(@NotNull String actionId) { + IdeEventQueue.EventDispatcher oldDispatcher = myDispatchers.remove(actionId); + if (oldDispatcher != null) { + IdeEventQueue.getInstance().removeDispatcher(oldDispatcher); + } + } + + private static class MyDispatcher implements IdeEventQueue.EventDispatcher { + private static final TIntIntHashMap KEY_CODE_TO_MODIFIER_MAP = new TIntIntHashMap(); + static { + KEY_CODE_TO_MODIFIER_MAP.put(KeyEvent.VK_ALT, InputEvent.ALT_MASK); + KEY_CODE_TO_MODIFIER_MAP.put(KeyEvent.VK_CONTROL, InputEvent.CTRL_MASK); + KEY_CODE_TO_MODIFIER_MAP.put(KeyEvent.VK_META, InputEvent.META_MASK); + KEY_CODE_TO_MODIFIER_MAP.put(KeyEvent.VK_SHIFT, InputEvent.SHIFT_MASK); + } + + private final String myActionId; + private final int myModifierKeyCode; + private final int myActionKeyCode; + + private final Couple ourPressed = Couple.of(new AtomicBoolean(false), new AtomicBoolean(false)); + private final Couple ourReleased = Couple.of(new AtomicBoolean(false), new AtomicBoolean(false)); + private final AtomicBoolean ourOtherKeyWasPressed = new AtomicBoolean(false); + private final AtomicLong ourLastTimePressed = new AtomicLong(0); + + public MyDispatcher(@NotNull String actionId, int modifierKeyCode, int actionKeyCode) { + myActionId = actionId; + myModifierKeyCode = modifierKeyCode; + myActionKeyCode = actionKeyCode; + } + + @Override + public boolean dispatch(AWTEvent event) { + if (event instanceof KeyEvent) { + final KeyEvent keyEvent = (KeyEvent)event; + final int keyCode = keyEvent.getKeyCode(); + + if (keyCode == myModifierKeyCode) { + if (hasOtherModifiers(keyEvent)) { + resetState(); + return false; + } + if (ourOtherKeyWasPressed.get() && Clock.getTime() - ourLastTimePressed.get() < 500) { + resetState(); + return false; + } + ourOtherKeyWasPressed.set(false); + if (ourPressed.first.get() && Clock.getTime() - ourLastTimePressed.get() > 500) { + resetState(); + } + handleModifier((KeyEvent)event); + return false; + } else if (ourPressed.first.get() && ourReleased.first.get() && ourPressed.second.get() && myActionKeyCode != -1 && !isActionBound()) { + if (keyCode == myActionKeyCode) { + if (event.getID() == KeyEvent.KEY_RELEASED) { + run(keyEvent); + } + return true; + } + return false; + } else { + ourLastTimePressed.set(Clock.getTime()); + ourOtherKeyWasPressed.set(true); + if (keyCode == KeyEvent.VK_ESCAPE || keyCode == KeyEvent.VK_TAB) { + ourLastTimePressed.set(0); + } + } + resetState(); + } + return false; + } + + private boolean hasOtherModifiers(KeyEvent keyEvent) { + final int modifiers = keyEvent.getModifiers(); + return !KEY_CODE_TO_MODIFIER_MAP.forEachEntry(new TIntIntProcedure() { + @Override + public boolean execute(int keyCode, int modifierMask) { + return keyCode == myModifierKeyCode || (modifiers & modifierMask) == 0; + } + }); + } + + private void handleModifier(KeyEvent event) { + if (ourPressed.first.get() && Clock.getTime() - ourLastTimePressed.get() > 300) { + resetState(); + return; + } + + if (event.getID() == KeyEvent.KEY_PRESSED) { + if (!ourPressed.first.get()) { + resetState(); + ourPressed.first.set(true); + ourLastTimePressed.set(Clock.getTime()); + return; + } else { + if (ourPressed.first.get() && ourReleased.first.get()) { + ourPressed.second.set(true); + ourLastTimePressed.set(Clock.getTime()); + return; + } + } + } else if (event.getID() == KeyEvent.KEY_RELEASED) { + if (ourPressed.first.get() && !ourReleased.first.get()) { + ourReleased.first.set(true); + ourLastTimePressed.set(Clock.getTime()); + return; + } else if (ourPressed.first.get() && ourReleased.first.get() && ourPressed.second.get()) { + resetState(); + if (myActionKeyCode == -1 && !isActionBound()) { + run(event); + } + return; + } + } + resetState(); + } + + private void resetState() { + ourPressed.first.set(false); + ourPressed.second.set(false); + ourReleased.first.set(false); + ourReleased.second.set(false); + } + + private void run(KeyEvent event) { + final ActionManager actionManager = ActionManager.getInstance(); + final AnAction action = actionManager.getAction(myActionId); + final AnActionEvent anActionEvent = new AnActionEvent(event, + DataManager.getInstance().getDataContext(IdeFocusManager.findInstance().getFocusOwner()), + ActionPlaces.MAIN_MENU, + action.getTemplatePresentation(), + actionManager, + 0); + action.actionPerformed(anActionEvent); + } + + private boolean isActionBound() { + return KeymapManager.getInstance().getActiveKeymap().getShortcuts(myActionId).length > 0; + } + } +} diff --git a/platform/platform-tests/testSrc/com/intellij/openapi/keymap/impl/ModifierKeyDoubleClickHandlerTest.java b/platform/platform-tests/testSrc/com/intellij/openapi/keymap/impl/ModifierKeyDoubleClickHandlerTest.java new file mode 100644 index 000000000000..490aa294974d --- /dev/null +++ b/platform/platform-tests/testSrc/com/intellij/openapi/keymap/impl/ModifierKeyDoubleClickHandlerTest.java @@ -0,0 +1,169 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.openapi.keymap.impl; + +import com.intellij.ide.IdeEventQueue; +import com.intellij.openapi.actionSystem.ActionManager; +import com.intellij.openapi.actionSystem.AnAction; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.actionSystem.KeyboardShortcut; +import com.intellij.openapi.keymap.KeymapManager; +import com.intellij.openapi.util.Clock; +import com.intellij.testFramework.LightPlatformTestCase; + +import javax.swing.*; +import java.awt.event.InputEvent; +import java.awt.event.KeyEvent; + +public class ModifierKeyDoubleClickHandlerTest extends LightPlatformTestCase { + private static final String MY_SHIFT_SHIFT_ACTION = "ModifierKeyDoubleClickHandlerTest.action1"; + private static final String MY_SHIFT_KEY_ACTION = "ModifierKeyDoubleClickHandlerTest.action2"; + private static final String MY_SHIFT_SHIFT_KEY_ACTION = "ModifierKeyDoubleClickHandlerTest.action3"; + + public static final KeyboardShortcut SHIFT_KEY_SHORTCUT = new KeyboardShortcut(KeyStroke.getKeyStroke(KeyEvent.VK_BACK_SPACE, + InputEvent.SHIFT_MASK), + null); + + private final JComponent myComponent = new JPanel(); + + private long myCurrentTime; + private int myShiftShiftActionInvocationCount; + private int myShiftKeyActionInvocationCount; + private int myShiftShiftKeyActionInvocationCount; + + @Override + public void setUp() throws Exception { + super.setUp(); + Clock.setTime(0); + ActionManager.getInstance().registerAction(MY_SHIFT_SHIFT_ACTION, new AnAction() { + @Override + public void actionPerformed(AnActionEvent e) { + myShiftShiftActionInvocationCount++; + } + }); + ActionManager.getInstance().registerAction(MY_SHIFT_KEY_ACTION, new AnAction() { + @Override + public void actionPerformed(AnActionEvent e) { + myShiftKeyActionInvocationCount++; + } + }); + ActionManager.getInstance().registerAction(MY_SHIFT_SHIFT_KEY_ACTION, new AnAction() { + @Override + public void actionPerformed(AnActionEvent e) { + myShiftShiftKeyActionInvocationCount++; + } + }); + KeymapManager.getInstance().getActiveKeymap().addShortcut(MY_SHIFT_KEY_ACTION, SHIFT_KEY_SHORTCUT); + ModifierKeyDoubleClickHandler.getInstance().registerAction(MY_SHIFT_SHIFT_ACTION, KeyEvent.VK_SHIFT, -1); + ModifierKeyDoubleClickHandler.getInstance().registerAction(MY_SHIFT_SHIFT_KEY_ACTION, KeyEvent.VK_SHIFT, KeyEvent.VK_BACK_SPACE); + } + + @Override + public void tearDown() throws Exception { + ModifierKeyDoubleClickHandler.getInstance().unregisterAction(MY_SHIFT_SHIFT_KEY_ACTION); + ModifierKeyDoubleClickHandler.getInstance().unregisterAction(MY_SHIFT_SHIFT_ACTION); + KeymapManager.getInstance().getActiveKeymap().removeShortcut(MY_SHIFT_KEY_ACTION, SHIFT_KEY_SHORTCUT); + ActionManager.getInstance().unregisterAction(MY_SHIFT_SHIFT_KEY_ACTION); + ActionManager.getInstance().unregisterAction(MY_SHIFT_KEY_ACTION); + ActionManager.getInstance().unregisterAction(MY_SHIFT_SHIFT_ACTION); + Clock.reset(); + super.tearDown(); + } + + public void testShiftShiftSuccessfulCase() { + press(); + release(); + press(); + assertInvocationCounts(0, 0, 0); + release(); + assertInvocationCounts(0, 1, 0); + } + + public void testLongSecondClick() { + press(); + release(); + press(); + timeStep(400); + release(); + assertInvocationCounts(0, 0, 0); + } + + public void testShiftShiftKeySuccessfulCase() { + press(); + release(); + press(); + key(); + assertInvocationCounts(0, 0, 1); + release(); + assertInvocationCounts(0, 0, 1); + } + + public void testShiftKey() { + press(); + key(); + assertInvocationCounts(1, 0, 0); + release(); + } + + public void assertInvocationCounts(int shiftKeyCount, int shiftShiftCount, int shiftShiftKeyCount) { + assertEquals(shiftKeyCount, myShiftKeyActionInvocationCount); + assertEquals(shiftShiftCount, myShiftShiftActionInvocationCount); + assertEquals(shiftShiftKeyCount, myShiftShiftKeyActionInvocationCount); + } + + private void press() { + IdeEventQueue.getInstance().dispatchEvent(new KeyEvent(myComponent, + KeyEvent.KEY_PRESSED, + Clock.getTime(), + InputEvent.SHIFT_MASK, + KeyEvent.VK_SHIFT, + KeyEvent.CHAR_UNDEFINED)); + } + + private void release() { + IdeEventQueue.getInstance().dispatchEvent(new KeyEvent(myComponent, + KeyEvent.KEY_RELEASED, + Clock.getTime(), + 0, + KeyEvent.VK_SHIFT, + KeyEvent.CHAR_UNDEFINED)); + } + + private void key() { + IdeEventQueue.getInstance().dispatchEvent(new KeyEvent(myComponent, + KeyEvent.KEY_PRESSED, + Clock.getTime(), + InputEvent.SHIFT_MASK, + KeyEvent.VK_BACK_SPACE, + '\b')); + IdeEventQueue.getInstance().dispatchEvent(new KeyEvent(myComponent, + KeyEvent.KEY_TYPED, + Clock.getTime(), + InputEvent.SHIFT_MASK, + 0, + '\b')); + IdeEventQueue.getInstance().dispatchEvent(new KeyEvent(myComponent, + KeyEvent.KEY_RELEASED, + Clock.getTime(), + InputEvent.SHIFT_MASK, + KeyEvent.VK_BACK_SPACE, + '\b')); + } + + private void timeStep(long step) { + Clock.setTime(myCurrentTime += step); + } +}