/*
* Copyright 2000-2017 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.ide;
import com.intellij.ide.dnd.DnDManager;
import com.intellij.ide.dnd.DnDManagerImpl;
import com.intellij.ide.plugins.PluginManager;
import com.intellij.ide.ui.UISettings;
import com.intellij.idea.ApplicationStarter;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.application.*;
import com.intellij.openapi.application.ex.ApplicationEx;
import com.intellij.openapi.application.impl.LaterInvocator;
import com.intellij.openapi.diagnostic.FrequentEventDetector;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.keymap.KeyboardSettingsExternalizable;
import com.intellij.openapi.keymap.impl.IdeKeyEventDispatcher;
import com.intellij.openapi.keymap.impl.IdeMouseEventDispatcher;
import com.intellij.openapi.keymap.impl.KeyState;
import com.intellij.openapi.ui.JBPopupMenu;
import com.intellij.openapi.util.*;
import com.intellij.openapi.util.registry.Registry;
import com.intellij.openapi.wm.IdeFocusManager;
import com.intellij.openapi.wm.IdeFrame;
import com.intellij.openapi.wm.WindowManager;
import com.intellij.openapi.wm.ex.WindowManagerEx;
import com.intellij.openapi.wm.impl.FocusManagerImpl;
import com.intellij.util.Alarm;
import com.intellij.util.ReflectionUtil;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.HashMap;
import com.intellij.util.io.storage.HeavyProcessLatch;
import com.intellij.util.ui.UIUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import sun.awt.AppContext;
import sun.awt.SunToolkit;
import javax.swing.*;
import javax.swing.plaf.basic.ComboPopup;
import java.awt.*;
import java.awt.event.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import static java.awt.event.MouseEvent.MOUSE_MOVED;
import static java.awt.event.MouseEvent.MOUSE_PRESSED;
/**
* @author Vladimir Kondratyev
* @author Anton Katilin
*/
public class IdeEventQueue extends EventQueue {
private static final Logger LOG = Logger.getInstance("#com.intellij.ide.IdeEventQueue");
private static TransactionGuardImpl ourTransactionGuard;
/**
* Adding/Removing of "idle" listeners should be thread safe.
*/
private final Object myLock = new Object();
private final List<Runnable> myIdleListeners = ContainerUtil.createLockFreeCopyOnWriteList();
private final List<Runnable> myActivityListeners = ContainerUtil.createLockFreeCopyOnWriteList();
private final Alarm myIdleRequestsAlarm = new Alarm();
private final Alarm myIdleTimeCounterAlarm = new Alarm();
private long myIdleTime;
private final Map<Runnable, MyFireIdleRequest> myListener2Request = new HashMap<>();
// IdleListener -> MyFireIdleRequest
private final IdeKeyEventDispatcher myKeyEventDispatcher = new IdeKeyEventDispatcher(this);
private final IdeMouseEventDispatcher myMouseEventDispatcher = new IdeMouseEventDispatcher();
private final IdePopupManager myPopupManager = new IdePopupManager();
private final ToolkitBugsProcessor myToolkitBugsProcessor = new ToolkitBugsProcessor();
private boolean mySuspendMode;
/**
* We exit from suspend mode when focus owner changes and no more WindowEvent.WINDOW_OPENED events
* <p/>
* in the queue. If WINDOW_OPENED event does exists in the queues then we restart the alarm.
*/
private Component myFocusOwner;
private final Runnable myExitSuspendModeRunnable = new ExitSuspendModeRunnable();
/**
* We exit from suspend mode when this alarm is triggered and no mode WindowEvent.WINDOW_OPENED
* <p/>
* events in the queue. If WINDOW_OPENED event does exist then we restart the alarm.
*/
private final Alarm mySuspendModeAlarm = new Alarm();
/**
* Counter of processed events. It is used to assert that data context lives only inside single
* <p/>
* Swing event.
*/
private int myEventCount;
final AtomicInteger myKeyboardEventsPosted = new AtomicInteger();
final AtomicInteger myKeyboardEventsDispatched = new AtomicInteger();
private boolean myIsInInputEvent;
private AWTEvent myCurrentEvent;
private long myLastActiveTime;
private long myLastEventTime = System.currentTimeMillis();
private WindowManagerEx myWindowManager;
private final List<EventDispatcher> myDispatchers = ContainerUtil.createLockFreeCopyOnWriteList();
private final List<EventDispatcher> myPostProcessors = ContainerUtil.createLockFreeCopyOnWriteList();
private final Set<Runnable> myReady = ContainerUtil.newHashSet();
private boolean myKeyboardBusy;
private boolean myDispatchingFocusEvent;
private boolean myWinMetaPressed;
private int myInputMethodLock;
private final com.intellij.util.EventDispatcher<PostEventHook>
myPostEventListeners = com.intellij.util.EventDispatcher.create(PostEventHook.class);
private static class IdeEventQueueHolder {
private static final IdeEventQueue INSTANCE = new IdeEventQueue();
}
public static IdeEventQueue getInstance() {
return IdeEventQueueHolder.INSTANCE;
}
private IdeEventQueue() {
EventQueue systemEventQueue = Toolkit.getDefaultToolkit().getSystemEventQueue();
assert !(systemEventQueue instanceof IdeEventQueue) : systemEventQueue;
systemEventQueue.push(this);
addIdleTimeCounterRequest();
KeyboardFocusManager keyboardFocusManager = IdeKeyboardFocusManager.replaceDefault();
keyboardFocusManager.addPropertyChangeListener("permanentFocusOwner", e -> {
final Application application = ApplicationManager.getApplication();
if (application == null) {
// We can get focus event before application is initialized
return;
}
application.assertIsDispatchThread();
final Window focusedWindow = keyboardFocusManager.getFocusedWindow();
final Component focusOwner = keyboardFocusManager.getFocusOwner();
if (mySuspendMode && focusedWindow != null && focusOwner != null && focusOwner != myFocusOwner && !(focusOwner instanceof Window)) {
exitSuspendMode();
}
});
addDispatcher(new WindowsAltSuppressor(), null);
addDispatcher(new EditingCanceller(), null);
abracadabraDaberBoreh();
}
private void abracadabraDaberBoreh() {
// we need to track if there are KeyBoardEvents in IdeEventQueue
// so we want to intercept all events posted to IdeEventQueue and increment counters
// However, we regular control flow goes like this:
// PostEventQueue.flush() -> EventQueue.postEvent() -> IdeEventQueue.postEventPrivate() -> AAAA we missed event, because postEventPrivate() can't be overridden.
// Instead, we do following:
// - create new PostEventQueue holding our IdeEventQueue instead of old EventQueue
// - replace "PostEventQueue" value in AppContext with this new PostEventQueue
// since that the control flow goes like this:
// PostEventQueue.flush() -> IdeEventQueue.postEvent() -> We intercepted event, incremented counters.
try {
Class<?> aClass = Class.forName("sun.awt.PostEventQueue");
Constructor<?> constructor = aClass.getDeclaredConstructor(EventQueue.class);
constructor.setAccessible(true);
Object postEventQueue = constructor.newInstance(this);
AppContext.getAppContext().put("PostEventQueue", postEventQueue);
}
catch (Exception e) {
throw new RuntimeException(e);
}
}
public void setWindowManager(final WindowManagerEx windowManager) {
myWindowManager = windowManager;
}
private void addIdleTimeCounterRequest() {
if (isTestMode()) return;
myIdleTimeCounterAlarm.cancelAllRequests();
myLastActiveTime = System.currentTimeMillis();
myIdleTimeCounterAlarm.addRequest(() -> {
myIdleTime += System.currentTimeMillis() - myLastActiveTime;
addIdleTimeCounterRequest();
}, 20000, ModalityState.NON_MODAL);
}
/**
* This class performs special processing in order to have {@link #getIdleTime()} return more or less up-to-date data.
* <p/>
* This method allows to stop that processing (convenient in non-intellij environment like upsource).
*/
@SuppressWarnings("unused") // Used in upsource.
public void stopIdleTimeCalculation() {
myIdleTimeCounterAlarm.cancelAllRequests();
}
public boolean shouldNotTypeInEditor() {
return myKeyEventDispatcher.isWaitingForSecondKeyStroke() || mySuspendMode;
}
private void enterSuspendMode() {
mySuspendMode = true;
myFocusOwner = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner();
mySuspendModeAlarm.cancelAllRequests();
mySuspendModeAlarm.addRequest(myExitSuspendModeRunnable, 750);
}
/**
* Exits suspend mode and pumps all suspended events.
*/
private void exitSuspendMode() {
if (shallEnterSuspendMode()) {
// We have to exit from suspend mode (focus owner changes or alarm is triggered) but
// WINDOW_OPENED isn't dispatched yet. In this case we have to restart the alarm until
// all WINDOW_OPENED event will be processed.
mySuspendModeAlarm.cancelAllRequests();
mySuspendModeAlarm.addRequest(myExitSuspendModeRunnable, 250);
}
else {
// Now we can pump all suspended events.
mySuspendMode = false;
myFocusOwner = null; // to prevent memory leaks
}
}
public void addIdleListener(@NotNull final Runnable runnable, final int timeoutMillis) {
if(timeoutMillis <= 0 || TimeUnit.MILLISECONDS.toHours(timeoutMillis) >= 24) {
throw new IllegalArgumentException("This timeout value is unsupported: " + timeoutMillis);
}
synchronized (myLock) {
myIdleListeners.add(runnable);
final MyFireIdleRequest request = new MyFireIdleRequest(runnable, timeoutMillis);
myListener2Request.put(runnable, request);
UIUtil.invokeLaterIfNeeded(() -> myIdleRequestsAlarm.addRequest(request, timeoutMillis));
}
}
public void removeIdleListener(@NotNull final Runnable runnable) {
synchronized (myLock) {
final boolean wasRemoved = myIdleListeners.remove(runnable);
if (!wasRemoved) {
LOG.error("unknown runnable: " + runnable);
}
final MyFireIdleRequest request = myListener2Request.remove(runnable);
LOG.assertTrue(request != null);
myIdleRequestsAlarm.cancelRequest(request);
}
}
/** @deprecated use {@link #addActivityListener(Runnable, Disposable)} (to be removed in IDEA 17) */
public void addActivityListener(@NotNull final Runnable runnable) {
synchronized (myLock) {
myActivityListeners.add(runnable);
}
}
public void addActivityListener(@NotNull final Runnable runnable, Disposable parentDisposable) {
synchronized (myLock) {
ContainerUtil.add(runnable, myActivityListeners, parentDisposable);
}
}
public void removeActivityListener(@NotNull final Runnable runnable) {
synchronized (myLock) {
myActivityListeners.remove(runnable);
}
}
public void addDispatcher(@NotNull EventDispatcher dispatcher, Disposable parent) {
_addProcessor(dispatcher, parent, myDispatchers);
}
public void removeDispatcher(@NotNull EventDispatcher dispatcher) {
myDispatchers.remove(dispatcher);
}
public boolean containsDispatcher(@NotNull EventDispatcher dispatcher) {
return myDispatchers.contains(dispatcher);
}
public void addPostprocessor(@NotNull EventDispatcher dispatcher, @Nullable Disposable parent) {
_addProcessor(dispatcher, parent, myPostProcessors);
}
public void removePostprocessor(@NotNull EventDispatcher dispatcher) {
myPostProcessors.remove(dispatcher);
}
private static void _addProcessor(@NotNull EventDispatcher dispatcher, Disposable parent, @NotNull Collection<EventDispatcher> set) {
set.add(dispatcher);
if (parent != null) {
Disposer.register(parent, () -> set.remove(dispatcher));
}
}
public int getEventCount() {
return myEventCount;
}
public void setEventCount(int evCount) {
myEventCount = evCount;
}
public AWTEvent getTrueCurrentEvent() {
return myCurrentEvent;
}
private static boolean ourAppIsLoaded;
private static boolean appIsLoaded() {
if (ourAppIsLoaded) return true;
boolean loaded = ApplicationStarter.isLoaded();
if (loaded) {
ourAppIsLoaded = true;
}
return loaded;
}
//Use for GuiTests to stop IdeEventQueue when application is disposed already
public static void applicationClose(){
ourAppIsLoaded = false;
}
@Override
public void dispatchEvent(@NotNull AWTEvent e) {
checkForTimeJump();
if (!appIsLoaded()) {
try {
super.dispatchEvent(e);
}
catch (Throwable t) {
processException(t);
}
return;
}
e = fixNonEnglishKeyboardLayouts(e);
e = mapEvent(e);
if (Registry.is("keymap.windows.as.meta")) {
e = mapMetaState(e);
}
boolean wasInputEvent = myIsInInputEvent;
myIsInInputEvent = isInputEvent(e);
AWTEvent oldEvent = myCurrentEvent;
myCurrentEvent = e;
HeavyProcessLatch.INSTANCE.prioritizeUiActivity();
try (AccessToken ignored = startActivity(e)) {
_dispatchEvent(e, false);
}
catch (Throwable t) {
processException(t);
}
finally {
HeavyProcessLatch.INSTANCE.stopThreadPrioritizing();
myIsInInputEvent = wasInputEvent;
myCurrentEvent = oldEvent;
for (EventDispatcher each : myPostProcessors) {
each.dispatch(e);
}
if (e instanceof KeyEvent) {
maybeReady();
}
}
}
//As we rely on system time monotonicity in many places let's log anomalies at least.
private void checkForTimeJump() {
long now = System.currentTimeMillis();
if (myLastEventTime > now + 1000) {
LOG.warn("System clock's jumped back by ~" + (myLastEventTime - now) / 1000 + " sec");
}
myLastEventTime = now;
}
private static boolean isInputEvent(@NotNull AWTEvent e) {
return e instanceof InputEvent || e instanceof InputMethodEvent || e instanceof WindowEvent || e instanceof ActionEvent;
}
@Override
@NotNull
public AWTEvent getNextEvent() throws InterruptedException {
AWTEvent event = super.getNextEvent();
if (isKeyboardEvent(event) && myKeyboardEventsDispatched.incrementAndGet() > myKeyboardEventsPosted.get()) {
throw new RuntimeException(event + "; posted: " + myKeyboardEventsPosted + "; dispatched: " + myKeyboardEventsDispatched);
}
return event;
}
@Nullable
static AccessToken startActivity(@NotNull AWTEvent e) {
if (ourTransactionGuard == null && appIsLoaded()) {
if (ApplicationManager.getApplication() != null && !ApplicationManager.getApplication().isDisposed()) {
ourTransactionGuard = (TransactionGuardImpl)TransactionGuard.getInstance();
}
}
return ourTransactionGuard == null
? null
: ourTransactionGuard.startActivity(isInputEvent(e) || e instanceof ItemEvent || e instanceof FocusEvent);
}
private void processException(@NotNull Throwable t) {
if (!myToolkitBugsProcessor.process(t)) {
PluginManager.processException(t);
}
}
@NotNull
private static AWTEvent fixNonEnglishKeyboardLayouts(@NotNull AWTEvent e) {
if (!(e instanceof KeyEvent)) return e;
KeyboardSettingsExternalizable externalizable = KeyboardSettingsExternalizable.getInstance();
if (!Registry.is("ide.non.english.keyboard.layout.fix") || externalizable == null || !externalizable.isNonEnglishKeyboardSupportEnabled()) return e;
KeyEvent ke = (KeyEvent)e;
switch (ke.getID()) {
case KeyEvent.KEY_PRESSED:
break;
case KeyEvent.KEY_RELEASED:
break;
}
//if (!leftAltIsPressed && KeyboardSettingsExternalizable.getInstance().isUkrainianKeyboard(sourceComponent)) {
// if ('ґ' == ke.getKeyChar() || ke.getKeyCode() == KeyEvent.VK_U) {
// ke = new KeyEvent(ke.getComponent(), ke.getID(), ke.getWhen(), 0,
// KeyEvent.VK_UNDEFINED, 'ґ', ke.getKeyLocation());
// ke.setKeyCode(KeyEvent.VK_U);
// ke.setKeyChar('ґ');
// return ke;
// }
//}
// NB: Standard keyboard layout is an English keyboard layout. If such
// layout is active every KeyEvent that is received has
// a @{code KeyEvent.getKeyCode} key code corresponding to
// the @{code KeyEvent.getKeyChar} key char in the event.
// For example, VK_MINUS key code and '-' character
//
// We have a key char. On some non standard layouts it does not correspond to
// key code in the event.
int keyCodeFromChar = CharToVKeyMap.get(ke.getKeyChar());
// Now we have a correct key code as if we'd gotten a KeyEvent for
// standard English layout
if (keyCodeFromChar == ke.getKeyCode() || keyCodeFromChar == KeyEvent.VK_UNDEFINED) {
return e;
}
// Farther we handle a non standard layout
// non-english layout
ke.setKeyCode(keyCodeFromChar);
return ke;
}
@NotNull
private static AWTEvent mapEvent(@NotNull AWTEvent e) {
if (SystemInfo.isXWindow && e instanceof MouseEvent && ((MouseEvent)e).getButton() > 3) {
MouseEvent src = (MouseEvent)e;
if (src.getButton() < 6) {
// Convert these events(buttons 4&5 in are produced by touchpad, they must be converted to horizontal scrolling events
e = new MouseWheelEvent(src.getComponent(), MouseEvent.MOUSE_WHEEL, src.getWhen(),
src.getModifiers() | InputEvent.SHIFT_DOWN_MASK, src.getX(), src.getY(),
0, false, MouseWheelEvent.WHEEL_UNIT_SCROLL, src.getClickCount(), src.getButton() == 4 ? -1 : 1);
}
else {
// Here we "shift" events with buttons 6 and 7 to similar events with buttons 4 and 5
// See java.awt.InputEvent#BUTTON_DOWN_MASK, 1<<14 is 4th physical button, 1<<15 is 5th.
//noinspection MagicConstant
e = new MouseEvent(src.getComponent(), src.getID(), src.getWhen(), src.getModifiers() | (1 << 8 + src.getButton()),
src.getX(), src.getY(), 1, src.isPopupTrigger(), src.getButton() - 2);
}
}
return e;
}
@NotNull
private AWTEvent mapMetaState(@NotNull AWTEvent e) {
if (myWinMetaPressed) {
Application app = ApplicationManager.getApplication();
boolean weAreNotActive = app == null || !app.isActive();
weAreNotActive |= e instanceof FocusEvent && ((FocusEvent)e).getOppositeComponent() == null;
if (weAreNotActive) {
myWinMetaPressed = false;
return e;
}
}
if (e instanceof KeyEvent) {
KeyEvent ke = (KeyEvent)e;
if (ke.getKeyCode() == KeyEvent.VK_WINDOWS) {
if (ke.getID() == KeyEvent.KEY_PRESSED) myWinMetaPressed = true;
if (ke.getID() == KeyEvent.KEY_RELEASED) myWinMetaPressed = false;
return new KeyEvent(ke.getComponent(), ke.getID(), ke.getWhen(), ke.getModifiers() | ke.getModifiersEx(), KeyEvent.VK_META, ke.getKeyChar(),
ke.getKeyLocation());
}
if (myWinMetaPressed) {
return new KeyEvent(ke.getComponent(), ke.getID(), ke.getWhen(), ke.getModifiers() | ke.getModifiersEx() | InputEvent.META_MASK, ke.getKeyCode(),
ke.getKeyChar(), ke.getKeyLocation());
}
}
if (myWinMetaPressed && e instanceof MouseEvent && ((MouseEvent)e).getButton() != 0) {
MouseEvent me = (MouseEvent)e;
return new MouseEvent(me.getComponent(), me.getID(), me.getWhen(), me.getModifiers() | me.getModifiersEx() | InputEvent.META_MASK, me.getX(), me.getY(),
me.getClickCount(), me.isPopupTrigger(), me.getButton());
}
return e;
}
public void _dispatchEvent(@NotNull AWTEvent e, boolean typeAheadFlushing) {
if (e.getID() == MouseEvent.MOUSE_DRAGGED) {
DnDManagerImpl dndManager = (DnDManagerImpl)DnDManager.getInstance();
if (dndManager != null) {
dndManager.setLastDropHandler(null);
}
}
myEventCount++;
if (processAppActivationEvents(e)) return;
if (!typeAheadFlushing) {
fixStickyFocusedComponents(e);
}
if (!myPopupManager.isPopupActive()) {
enterSuspendModeIfNeeded(e);
}
myKeyboardBusy = e instanceof KeyEvent || myKeyboardEventsPosted.get() > myKeyboardEventsDispatched.get();
if (e instanceof KeyEvent) {
if (e.getID() == KeyEvent.KEY_RELEASED && ((KeyEvent)e).getKeyCode() == KeyEvent.VK_SHIFT) {
myMouseEventDispatcher.resetHorScrollingTracker();
}
}
if (!typeAheadFlushing && typeAheadDispatchToFocusManager(e)) {
LOG.debug("Typeahead dispatch for event ", e);
return;
}
if (e instanceof WindowEvent) {
ActivityTracker.getInstance().inc();
}
if (e instanceof MouseWheelEvent) {
final MenuElement[] selectedPath = MenuSelectionManager.defaultManager().getSelectedPath();
if (selectedPath.length > 0 && !(selectedPath[0] instanceof ComboPopup)) {
((MouseWheelEvent)e).consume();
Component component = selectedPath[0].getComponent();
if (component instanceof JBPopupMenu) {
((JBPopupMenu)component).processMouseWheelEvent((MouseWheelEvent)e);
}
return;
}
}
// Process "idle" and "activity" listeners
if (e instanceof KeyEvent || e instanceof MouseEvent) {
ActivityTracker.getInstance().inc();
synchronized (myLock) {
myIdleRequestsAlarm.cancelAllRequests();
for (Runnable idleListener : myIdleListeners) {
final MyFireIdleRequest request = myListener2Request.get(idleListener);
if (request == null) {
LOG.error("There is no request for " + idleListener);
}
else {
myIdleRequestsAlarm.addRequest(request, request.getTimeout(), ModalityState.NON_MODAL);
}
}
if (KeyEvent.KEY_PRESSED == e.getID() ||
KeyEvent.KEY_TYPED == e.getID() ||
MouseEvent.MOUSE_PRESSED == e.getID() ||
MouseEvent.MOUSE_RELEASED == e.getID() ||
MouseEvent.MOUSE_CLICKED == e.getID()) {
addIdleTimeCounterRequest();
for (Runnable activityListener : myActivityListeners) {
activityListener.run();
}
}
}
}
if (myPopupManager.isPopupActive() && myPopupManager.dispatch(e)) {
if (myKeyEventDispatcher.isWaitingForSecondKeyStroke()) {
myKeyEventDispatcher.setState(KeyState.STATE_INIT);
}
return;
}
if (dispatchByCustomDispatchers(e)) return;
if (e instanceof InputMethodEvent) {
if (SystemInfo.isMac && myKeyEventDispatcher.isWaitingForSecondKeyStroke()) {
return;
}
}
if (e instanceof ComponentEvent && myWindowManager != null) {
myWindowManager.dispatchComponentEvent((ComponentEvent)e);
}
if (e instanceof KeyEvent) {
if (mySuspendMode || !myKeyEventDispatcher.dispatchKeyEvent((KeyEvent)e)) {
defaultDispatchEvent(e);
}
else {
((KeyEvent)e).consume();
defaultDispatchEvent(e);
}
}
else if (e instanceof MouseEvent) {
MouseEvent me = (MouseEvent)e;
if (me.getID() == MOUSE_PRESSED && me.getModifiers() > 0 && me.getModifiersEx() == 0 ) {
// In case of these modifiers java.awt.Container#LightweightDispatcher.processMouseEvent() uses a recent 'active' component
// from inner WeakReference (see mouseEventTarget field) even if the component has been already removed from component hierarchy.
// So we have to reset this WeakReference with synthetic event just before processing of actual event
super.dispatchEvent(new MouseEvent(me.getComponent(), MOUSE_MOVED, me.getWhen(), 0, me.getX(), me.getY(), 0, false, 0));
}
if (IdeMouseEventDispatcher.patchClickCount(me) && me.getID() == MouseEvent.MOUSE_CLICKED) {
final MouseEvent toDispatch =
new MouseEvent(me.getComponent(), me.getID(), System.currentTimeMillis(), me.getModifiers(), me.getX(), me.getY(), 1,
me.isPopupTrigger(), me.getButton());
//noinspection SSBasedInspection
SwingUtilities.invokeLater(() -> dispatchEvent(toDispatch));
}
if (!myMouseEventDispatcher.dispatchMouseEvent(me)) {
defaultDispatchEvent(e);
}
}
else {
defaultDispatchEvent(e);
}
}
private boolean dispatchByCustomDispatchers(@NotNull AWTEvent e) {
for (EventDispatcher eachDispatcher : myDispatchers) {
if (eachDispatcher.dispatch(e)) {
return true;
}
}
return false;
}
private static void fixStickyWindow(@NotNull KeyboardFocusManager mgr, Window wnd, @NotNull String resetMethod) {
if (wnd != null && !wnd.isShowing()) {
Window showingWindow = wnd;
while (showingWindow != null) {
if (showingWindow.isShowing()) break;
showingWindow = (Window)showingWindow.getParent();
}
if (showingWindow == null) {
final Frame[] allFrames = Frame.getFrames();
for (Frame each : allFrames) {
if (each.isShowing()) {
showingWindow = each;
break;
}
}
}
if (showingWindow != null && showingWindow != wnd) {
final Method setActive = ReflectionUtil.findMethod(ReflectionUtil.getClassDeclaredMethods(KeyboardFocusManager.class, false), resetMethod, Window.class);
if (setActive != null) {
try {
setActive.invoke(mgr, (Window)showingWindow);
}
catch (Exception exc) {
LOG.info(exc);
}
}
}
}
}
public void fixStickyFocusedComponents(@Nullable AWTEvent e) {
if (e != null && !(e instanceof InputEvent)) return;
final KeyboardFocusManager mgr = KeyboardFocusManager.getCurrentKeyboardFocusManager();
if (Registry.is("actionSystem.fixStickyFocusedWindows")) {
fixStickyWindow(mgr, mgr.getActiveWindow(), "setGlobalActiveWindow");
fixStickyWindow(mgr, mgr.getFocusedWindow(), "setGlobalFocusedWindow");
}
if (Registry.is("actionSystem.fixNullFocusedComponent")) {
final Component focusOwner = mgr.getFocusOwner();
if (focusOwner == null || !focusOwner.isShowing() || focusOwner instanceof JFrame || focusOwner instanceof JDialog) {
final Application app = ApplicationManager.getApplication();
if (app instanceof ApplicationEx && !((ApplicationEx) app).isLoaded()) {
return;
}
boolean mouseEventsAhead = isMouseEventAhead(e);
boolean focusTransferredNow = IdeFocusManager.getGlobalInstance().isFocusBeingTransferred();
boolean okToFixFocus = !mouseEventsAhead && !focusTransferredNow;
if (okToFixFocus) {
Window showingWindow = mgr.getActiveWindow();
if (showingWindow == null) {
Method getNativeFocusOwner = ReflectionUtil.getDeclaredMethod(KeyboardFocusManager.class, "getNativeFocusOwner");
if (getNativeFocusOwner != null) {
try {
Object owner = getNativeFocusOwner.invoke(mgr);
if (owner instanceof Component) {
showingWindow = UIUtil.getWindow((Component)owner);
}
}
catch (Exception e1) {
LOG.debug(e1);
}
}
}
if (showingWindow != null) {
final IdeFocusManager fm = IdeFocusManager.findInstanceByComponent(showingWindow);
ExpirableRunnable maybeRequestDefaultFocus = new ExpirableRunnable() {
@Override
public void run() {
if (getPopupManager().requestDefaultFocus(false)) return;
final Application app = ApplicationManager.getApplication();
if (app != null && app.isActive()) {
fm.requestDefaultFocus(false);
}
}
@Override
public boolean isExpired() {
return !UIUtil.isMeaninglessFocusOwner(mgr.getFocusOwner());
}
};
fm.revalidateFocus(maybeRequestDefaultFocus);
}
}
}
}
}
public static boolean isMouseEventAhead(@Nullable AWTEvent e) {
IdeEventQueue queue = getInstance();
return e instanceof MouseEvent ||
queue.peekEvent(MouseEvent.MOUSE_PRESSED) != null ||
queue.peekEvent(MouseEvent.MOUSE_RELEASED) != null ||
queue.peekEvent(MouseEvent.MOUSE_CLICKED) != null;
}
private void enterSuspendModeIfNeeded(@NotNull AWTEvent e) {
if (e instanceof KeyEvent) {
if (!mySuspendMode && shallEnterSuspendMode()) {
enterSuspendMode();
}
}
}
private boolean shallEnterSuspendMode() {
return peekEvent(WindowEvent.WINDOW_OPENED) != null;
}
private static boolean processAppActivationEvents(@NotNull AWTEvent e) {
if (e instanceof WindowEvent) {
final WindowEvent we = (WindowEvent)e;
ApplicationActivationStateManager.updateState(we);
storeLastFocusedComponent(we);
}
return false;
}
private static void storeLastFocusedComponent(@NotNull WindowEvent we) {
final Window eventWindow = we.getWindow();
if (we.getID() == WindowEvent.WINDOW_DEACTIVATED || we.getID() == WindowEvent.WINDOW_LOST_FOCUS) {
Component frame = UIUtil.findUltimateParent(eventWindow);
Component focusOwnerInDeactivatedWindow = eventWindow.getMostRecentFocusOwner();
IdeFrame[] allProjectFrames = WindowManager.getInstance().getAllProjectFrames();
if (focusOwnerInDeactivatedWindow != null) {
for (IdeFrame ideFrame : allProjectFrames) {
JFrame aFrame = WindowManager.getInstance().getFrame(ideFrame.getProject());
if (aFrame.equals(frame)) {
IdeFocusManager focusManager = IdeFocusManager.getGlobalInstance();
if (focusManager instanceof FocusManagerImpl) {
((FocusManagerImpl)focusManager).setLastFocusedAtDeactivation(ideFrame, focusOwnerInDeactivatedWindow);
}
}
}
}
}
}
private void defaultDispatchEvent(@NotNull AWTEvent e) {
try {
myDispatchingFocusEvent = e instanceof FocusEvent;
maybeReady();
fixStickyAlt(e);
super.dispatchEvent(e);
}
catch (Throwable t) {
processException(t);
}
finally {
myDispatchingFocusEvent = false;
}
}
private static class FieldHolder {
private static final Field ourStickyAltField;
static {
Field field;
try {
Class<?> aClass = Class.forName("com.sun.java.swing.plaf.windows.WindowsRootPaneUI$AltProcessor");
field = ReflectionUtil.getDeclaredField(aClass, "menuCanceledOnPress");
}
catch (Exception e) {
field = null;
}
ourStickyAltField = field;
}
}
private static void fixStickyAlt(@NotNull AWTEvent e) {
if (Registry.is("actionSystem.win.suppressAlt.new")) {
if (UIUtil.isUnderWindowsLookAndFeel() &&
e instanceof InputEvent &&
(((InputEvent)e).getModifiers() & (InputEvent.ALT_MASK | InputEvent.ALT_DOWN_MASK)) != 0 &&
!(e instanceof KeyEvent && ((KeyEvent)e).getKeyCode() == KeyEvent.VK_ALT)) {
try {
if (FieldHolder.ourStickyAltField != null) {
FieldHolder.ourStickyAltField.set(null, true);
}
}
catch (Exception exception) {
LOG.error(exception);
}
}
}
else if (SystemInfo.isWinXpOrNewer && !SystemInfo.isWinVistaOrNewer && e instanceof KeyEvent && ((KeyEvent)e).getKeyCode() == KeyEvent.VK_ALT) {
((KeyEvent)e).consume(); // IDEA-17359
}
}
public boolean isDispatchingFocusEvent() {
return myDispatchingFocusEvent;
}
private static boolean typeAheadDispatchToFocusManager(@NotNull AWTEvent e) {
if (e instanceof KeyEvent) {
final KeyEvent event = (KeyEvent)e;
if (!event.isConsumed()) {
final IdeFocusManager focusManager = IdeFocusManager.findInstanceByComponent(event.getComponent());
return focusManager.dispatch(event);
}
}
return false;
}
public void flushQueue() {
while (true) {
AWTEvent event = peekEvent();
if (event == null) return;
try {
AWTEvent event1 = getNextEvent();
dispatchEvent(event1);
}
catch (Exception e) {
LOG.error(e); //?
}
}
}
public void pumpEventsForHierarchy(Component modalComponent, @NotNull Condition<AWTEvent> exitCondition) {
if (LOG.isDebugEnabled()) {
LOG.debug("pumpEventsForHierarchy(" + modalComponent + ", " + exitCondition + ")");
}
AWTEvent event;
do {
try {
event = getNextEvent();
boolean eventOk = true;
if (event instanceof InputEvent) {
final Object s = event.getSource();
if (s instanceof Component) {
Component c = (Component)s;
Window modalWindow = modalComponent == null ? null : SwingUtilities.windowForComponent(modalComponent);
while (c != null && c != modalWindow) c = c.getParent();
if (c == null) {
eventOk = false;
if (LOG.isDebugEnabled()) {
LOG.debug("pumpEventsForHierarchy.consumed: "+event);
}
((InputEvent)event).consume();
}
}
}
if (eventOk) {
dispatchEvent(event);
}
}
catch (Throwable e) {
LOG.error(e);
event = null;
}
}
while (!exitCondition.value(event));
if (LOG.isDebugEnabled()) {
LOG.debug("pumpEventsForHierarchy.exit(" + modalComponent + ", " + exitCondition + ")");
}
}
@FunctionalInterface
public interface EventDispatcher {
boolean dispatch(@NotNull AWTEvent e);
}
private final class MyFireIdleRequest implements Runnable {
private final Runnable myRunnable;
private final int myTimeout;
MyFireIdleRequest(@NotNull Runnable runnable, final int timeout) {
myTimeout = timeout;
myRunnable = runnable;
}
@Override
public void run() {
myRunnable.run();
synchronized (myLock) {
if (myIdleListeners.contains(myRunnable)) // do not reschedule if not interested anymore
{
myIdleRequestsAlarm.addRequest(this, myTimeout, ModalityState.NON_MODAL);
}
}
}
public int getTimeout() {
return myTimeout;
}
@Override
public String toString() {
return "Fire idle request. delay: "+getTimeout()+"; runnable: "+myRunnable;
}
}
private final class ExitSuspendModeRunnable implements Runnable {
@Override
public void run() {
if (mySuspendMode) {
exitSuspendMode();
}
}
}
public long getIdleTime() {
return myIdleTime;
}
@NotNull
public IdePopupManager getPopupManager() {
return myPopupManager;
}
@NotNull
public IdeKeyEventDispatcher getKeyEventDispatcher() {
return myKeyEventDispatcher;
}
/**
* Same as {@link #blockNextEvents(MouseEvent, IdeEventQueue.BlockMode)} with {@code blockMode} equal to {@code COMPLETE}.
*/
public void blockNextEvents(@NotNull MouseEvent e) {
blockNextEvents(e, BlockMode.COMPLETE);
}
/**
* When {@code blockMode} is {@code COMPLETE}, blocks following related mouse events completely, when {@code blockMode} is
* {@code ACTIONS} only blocks performing actions bound to corresponding mouse shortcuts.
*/
public void blockNextEvents(@NotNull MouseEvent e, @NotNull BlockMode blockMode) {
myMouseEventDispatcher.blockNextEvents(e, blockMode);
}
public boolean isSuspendMode() {
return mySuspendMode;
}
public boolean hasFocusEventsPending() {
return peekEvent(FocusEvent.FOCUS_GAINED) != null || peekEvent(FocusEvent.FOCUS_LOST) != null;
}
private boolean isReady() {
return !myKeyboardBusy && myKeyEventDispatcher.isReady();
}
public void maybeReady() {
if (myReady.isEmpty() || !isReady()) return;
Runnable[] ready = myReady.toArray(new Runnable[myReady.size()]);
myReady.clear();
for (Runnable each : ready) {
each.run();
}
}
public void doWhenReady(@NotNull Runnable runnable) {
if (EventQueue.isDispatchThread()) {
myReady.add(runnable);
maybeReady();
}
else {
//noinspection SSBasedInspection
SwingUtilities.invokeLater(() -> {
myReady.add(runnable);
maybeReady();
});
}
}
public boolean isPopupActive() {
return myPopupManager.isPopupActive();
}
private static class WindowsAltSuppressor implements EventDispatcher {
private boolean myWaitingForAltRelease;
private Robot myRobot;
@Override
public boolean dispatch(@NotNull AWTEvent e) {
boolean dispatch = true;
if (e instanceof KeyEvent) {
KeyEvent ke = (KeyEvent)e;
final Component component = ke.getComponent();
boolean pureAlt = ke.getKeyCode() == KeyEvent.VK_ALT && (ke.getModifiers() | InputEvent.ALT_MASK) == InputEvent.ALT_MASK;
if (!pureAlt) {
myWaitingForAltRelease = false;
}
else {
UISettings uiSettings = UISettings.getInstanceOrNull();
if (uiSettings == null ||
!SystemInfo.isWindows ||
!Registry.is("actionSystem.win.suppressAlt") ||
!(uiSettings.getHideToolStripes() || uiSettings.getPresentationMode())) {
return false;
}
if (ke.getID() == KeyEvent.KEY_PRESSED) {
dispatch = !myWaitingForAltRelease;
}
else if (ke.getID() == KeyEvent.KEY_RELEASED) {
if (myWaitingForAltRelease) {
myWaitingForAltRelease = false;
dispatch = false;
}
else if (component != null) {
//noinspection SSBasedInspection
SwingUtilities.invokeLater(() -> {
try {
final Window window = UIUtil.getWindow(component);
if (window == null || !window.isActive()) {
return;
}
myWaitingForAltRelease = true;
if (myRobot == null) {
myRobot = new Robot();
}
myRobot.keyPress(KeyEvent.VK_ALT);
myRobot.keyRelease(KeyEvent.VK_ALT);
}
catch (AWTException e1) {
LOG.debug(e1);
}
});
}
}
}
}
return !dispatch;
}
}
//We have to stop editing with <ESC> (if any) and consume the event to prevent any further processing (dialog closing etc.)
private static class EditingCanceller implements EventDispatcher {
@Override
public boolean dispatch(@NotNull AWTEvent e) {
if (e instanceof KeyEvent && e.getID() == KeyEvent.KEY_PRESSED && ((KeyEvent)e).getKeyCode() == KeyEvent.VK_ESCAPE) {
final Component owner = UIUtil.findParentByCondition(KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner(),
component -> component instanceof JTable || component instanceof JTree);
if (owner instanceof JTable && ((JTable)owner).isEditing()) {
((JTable)owner).editingCanceled(null);
return true;
}
if (owner instanceof JTree && ((JTree)owner).isEditing()) {
((JTree)owner).cancelEditing();
return true;
}
}
return false;
}
}
public boolean isInputMethodEnabled() {
return !SystemInfo.isMac || myInputMethodLock == 0;
}
public void disableInputMethods(@NotNull Disposable parentDisposable) {
myInputMethodLock++;
Disposer.register(parentDisposable, () -> myInputMethodLock--);
}
private final FrequentEventDetector myFrequentEventDetector = new FrequentEventDetector(1009, 100);
@Override
public void postEvent(@NotNull AWTEvent event) {
doPostEvent(event);
}
// return true if posted, false if consumed immediately
boolean doPostEvent(@NotNull AWTEvent event) {
for (PostEventHook listener : myPostEventListeners.getListeners()) {
if (listener.consumePostedEvent(event)) return false;
}
myFrequentEventDetector.eventHappened(event);
if (isKeyboardEvent(event)) {
myKeyboardEventsPosted.incrementAndGet();
}
super.postEvent(event);
return true;
}
private static boolean isKeyboardEvent(@NotNull AWTEvent event) {
return event instanceof KeyEvent;
}
@Override
public AWTEvent peekEvent() {
AWTEvent event = super.peekEvent();
if (event != null) {
return event;
}
if (isTestMode() && LaterInvocator.ensureFlushRequested()) {
return super.peekEvent();
}
return null;
}
private Boolean myTestMode;
private boolean isTestMode() {
Boolean testMode = myTestMode;
if (testMode != null) return testMode;
Application application = ApplicationManager.getApplication();
if (application == null) return false;
testMode = application.isUnitTestMode();
myTestMode = testMode;
return testMode;
}
/**
* @see IdeEventQueue#blockNextEvents(MouseEvent, IdeEventQueue.BlockMode)
*/
public enum BlockMode {
COMPLETE, ACTIONS
}
/**
* An absolutely guru API, please avoid using it at all cost.
*/
@FunctionalInterface
public interface PostEventHook extends EventListener {
/**
* @return true if event is handled by the listener and should't be added to event queue at all
*/
boolean consumePostedEvent(@NotNull AWTEvent event);
}
public void addPostEventListener(@NotNull PostEventHook listener, @NotNull Disposable parentDisposable) {
myPostEventListeners.addListener(listener, parentDisposable);
}
private static Ref<Method> unsafeNonBlockingExecuteRef;
/**
* Must be called on the Event Dispatching thread.
* Executes the runnable so that it can perform a non-blocking invocation on the toolkit thread.
* Not for general-purpose usage.
*
* @param r the runnable to execute
*/
public static void unsafeNonblockingExecute(Runnable r) {
assert EventQueue.isDispatchThread();
if (unsafeNonBlockingExecuteRef == null) {
// The method is available in JBSDK.
unsafeNonBlockingExecuteRef = Ref.create(ReflectionUtil.getDeclaredMethod(SunToolkit.class, "unsafeNonblockingExecute", Runnable.class));
}
if (unsafeNonBlockingExecuteRef.get() != null) {
try {
unsafeNonBlockingExecuteRef.get().invoke(Toolkit.getDefaultToolkit(), r);
return;
}
catch (Exception ignore) {
}
}
r.run();
}
}