| /* |
| * 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.ide; |
| |
| import com.intellij.Patches; |
| 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.IdeaApplication; |
| import com.intellij.openapi.Disposable; |
| import com.intellij.openapi.application.Application; |
| import com.intellij.openapi.application.ApplicationManager; |
| import com.intellij.openapi.application.ModalityState; |
| import com.intellij.openapi.application.ex.ApplicationEx; |
| import com.intellij.openapi.application.impl.ApplicationImpl; |
| import com.intellij.openapi.diagnostic.FrequentEventDetector; |
| import com.intellij.openapi.diagnostic.Logger; |
| 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.util.Condition; |
| import com.intellij.openapi.util.Disposer; |
| import com.intellij.openapi.util.ExpirableRunnable; |
| import com.intellij.openapi.util.SystemInfo; |
| import com.intellij.openapi.util.registry.Registry; |
| import com.intellij.openapi.wm.IdeFocusManager; |
| import com.intellij.openapi.wm.ex.WindowManagerEx; |
| 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.ui.UIUtil; |
| import com.sun.java.swing.plaf.windows.WindowsLookAndFeel; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import javax.swing.*; |
| import javax.swing.plaf.basic.ComboPopup; |
| import java.awt.*; |
| import java.awt.event.*; |
| import java.beans.PropertyChangeEvent; |
| import java.beans.PropertyChangeListener; |
| import java.lang.reflect.Field; |
| import java.lang.reflect.Method; |
| import java.util.*; |
| import java.util.List; |
| |
| /** |
| * @author Vladimir Kondratyev |
| * @author Anton Katilin |
| */ |
| public class IdeEventQueue extends EventQueue { |
| private static final Logger LOG = Logger.getInstance("#com.intellij.ide.IdeEventQueue"); |
| |
| /** |
| * 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(Alarm.ThreadToUse.SWING_THREAD); |
| |
| private long myIdleTime; |
| |
| private final Map<Runnable, MyFireIdleRequest> myListener2Request = new HashMap<Runnable, MyFireIdleRequest>(); |
| // 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; |
| |
| |
| private boolean myIsInInputEvent = false; |
| |
| private AWTEvent myCurrentEvent = null; |
| |
| private long myLastActiveTime; |
| |
| private WindowManagerEx myWindowManager; |
| |
| |
| private final Set<EventDispatcher> myDispatchers = new LinkedHashSet<EventDispatcher>(); |
| private final Set<EventDispatcher> myPostProcessors = new LinkedHashSet<EventDispatcher>(); |
| private final Set<Runnable> myReady = new HashSet<Runnable>(); |
| |
| private boolean myKeyboardBusy; |
| private boolean myDispatchingFocusEvent; |
| |
| private int myInputMethodLock; |
| |
| private static class IdeEventQueueHolder { |
| private static final IdeEventQueue INSTANCE = new IdeEventQueue(); |
| } |
| |
| public static IdeEventQueue getInstance() { |
| return IdeEventQueueHolder.INSTANCE; |
| } |
| |
| private IdeEventQueue() { |
| addIdleTimeCounterRequest(); |
| |
| final KeyboardFocusManager keyboardFocusManager = KeyboardFocusManager.getCurrentKeyboardFocusManager(); |
| keyboardFocusManager.addPropertyChangeListener("permanentFocusOwner", new PropertyChangeListener() { |
| |
| @Override |
| public void propertyChange(final PropertyChangeEvent 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 WindowsAltSupressor(), null); |
| |
| Application app = ApplicationManager.getApplication(); |
| if (app != null && app.isUnitTestMode()) { |
| //noinspection AssignmentToStaticFieldFromInstanceMethod |
| ourAppIsLoaded = true; |
| } |
| } |
| |
| |
| public void setWindowManager(final WindowManagerEx windowManager) { |
| myWindowManager = windowManager; |
| } |
| |
| |
| private void addIdleTimeCounterRequest() { |
| Application application = ApplicationManager.getApplication(); |
| if (application != null && application.isUnitTestMode()) return; |
| |
| myIdleTimeCounterAlarm.cancelAllRequests(); |
| myLastActiveTime = System.currentTimeMillis(); |
| myIdleTimeCounterAlarm.addRequest(new Runnable() { |
| @Override |
| public void run() { |
| myIdleTime += System.currentTimeMillis() - myLastActiveTime; |
| addIdleTimeCounterRequest(); |
| } |
| }, 20000, ModalityState.NON_MODAL); |
| } |
| |
| |
| public boolean shouldNotTypeInEditor() { |
| return myKeyEventDispatcher.isWaitingForSecondKeyStroke() || mySuspendMode; |
| } |
| |
| |
| private void enterSuspendMode() { |
| mySuspendMode = true; |
| myFocusOwner = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner(); |
| mySuspendModeAlarm.cancelAllRequests(); |
| mySuspendModeAlarm.addRequest(myExitSuspendModeRunnable, 750); |
| } |
| |
| |
| /** |
| * Exits supend 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 timeout) { |
| LOG.assertTrue(timeout > 0); |
| synchronized (myLock) { |
| myIdleListeners.add(runnable); |
| final MyFireIdleRequest request = new MyFireIdleRequest(runnable, timeout); |
| myListener2Request.put(runnable, request); |
| myIdleRequestsAlarm.addRequest(request, timeout); |
| } |
| } |
| |
| |
| 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); |
| } |
| } |
| |
| |
| 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(final EventDispatcher dispatcher, Disposable parent) { |
| _addProcessor(dispatcher, parent, myDispatchers); |
| } |
| |
| public void removeDispatcher(EventDispatcher dispatcher) { |
| myDispatchers.remove(dispatcher); |
| } |
| |
| public boolean containsDispatcher(EventDispatcher dispatcher) { |
| return myDispatchers.contains(dispatcher); |
| } |
| |
| public void addPostprocessor(EventDispatcher dispatcher, @Nullable Disposable parent) { |
| _addProcessor(dispatcher, parent, myPostProcessors); |
| } |
| |
| public void removePostprocessor(EventDispatcher dispatcher) { |
| myPostProcessors.remove(dispatcher); |
| } |
| |
| private static void _addProcessor(final EventDispatcher dispatcher, Disposable parent, final Set<EventDispatcher> set) { |
| set.add(dispatcher); |
| if (parent != null) { |
| Disposer.register(parent, new Disposable() { |
| @Override |
| public void dispose() { |
| set.remove(dispatcher); |
| } |
| }); |
| } |
| } |
| |
| public int getEventCount() { |
| return myEventCount; |
| } |
| |
| |
| public void setEventCount(int evCount) { |
| myEventCount = evCount; |
| } |
| |
| public AWTEvent getTrueCurrentEvent() { |
| return myCurrentEvent; |
| } |
| |
| private static class InertialMouseRouter { |
| private static int MOUSE_WHEEL_RESTART_THRESHOLD = 50; |
| private static Component wheelDestinationComponent = null; |
| private static long lastMouseWheel = 0; |
| |
| private static AWTEvent changeSourceIfNeeded(AWTEvent awtEvent) { |
| if (SystemInfo.isMac && Registry.is("ide.inertial.mouse.fix") && awtEvent instanceof MouseWheelEvent) { |
| MouseWheelEvent mwe = (MouseWheelEvent) awtEvent; |
| if (mwe.getWhen() - lastMouseWheel > MOUSE_WHEEL_RESTART_THRESHOLD) { |
| wheelDestinationComponent = SwingUtilities.getDeepestComponentAt(mwe.getComponent(), mwe.getX(), mwe.getY()); |
| } |
| lastMouseWheel = System.currentTimeMillis(); |
| |
| MouseWheelEvent newMouseWheelEvent = new MouseWheelEvent( |
| wheelDestinationComponent, mwe.getID(), lastMouseWheel, mwe.getModifiers(), mwe.getX(), mwe.getY(), |
| mwe.getClickCount(), mwe.isPopupTrigger(), mwe.getScrollType(), mwe.getScrollAmount(), mwe.getWheelRotation() |
| ); |
| return newMouseWheelEvent; |
| } |
| return awtEvent; |
| } |
| } |
| |
| private static boolean ourAppIsLoaded = false; |
| |
| private static boolean appIsLoaded() { |
| if (ourAppIsLoaded) return true; |
| boolean loaded = IdeaApplication.isLoaded(); |
| if (loaded) ourAppIsLoaded = true; |
| return loaded; |
| } |
| |
| @Override |
| public void dispatchEvent(AWTEvent e) { |
| if (!appIsLoaded()) { |
| try { |
| super.dispatchEvent(e); |
| } |
| catch (Throwable t) { |
| processException(t); |
| } |
| return; |
| } |
| |
| fixNonEnglishKeyboardLayouts(e); |
| |
| e = InertialMouseRouter.changeSourceIfNeeded(e); |
| |
| e = mapEvent(e); |
| |
| boolean wasInputEvent = myIsInInputEvent; |
| myIsInInputEvent = e instanceof InputEvent || e instanceof InputMethodEvent || e instanceof WindowEvent || e instanceof ActionEvent; |
| AWTEvent oldEvent = myCurrentEvent; |
| myCurrentEvent = e; |
| |
| try { |
| _dispatchEvent(e, false); |
| } |
| catch (Throwable t) { |
| processException(t); |
| } |
| finally { |
| myIsInInputEvent = wasInputEvent; |
| myCurrentEvent = oldEvent; |
| |
| for (EventDispatcher each : myPostProcessors) { |
| each.dispatch(e); |
| } |
| |
| if (e instanceof KeyEvent) { |
| maybeReady(); |
| } |
| } |
| } |
| |
| private void processException(Throwable t) { |
| if (!myToolkitBugsProcessor.process(t)) { |
| PluginManager.processException(t); |
| } |
| } |
| |
| private static void fixNonEnglishKeyboardLayouts(AWTEvent e) { |
| if (!Registry.is("ide.non.english.keyboard.layout.fix")) return; |
| if (e instanceof KeyEvent) { |
| KeyEvent ke = (KeyEvent)e; |
| Integer keyCodeFromChar = CharToVKeyMap.get(ke.getKeyChar()); |
| if (keyCodeFromChar != null) { |
| if (keyCodeFromChar != ke.getKeyCode()) { |
| // non-english layout |
| ke.setKeyCode(keyCodeFromChar); |
| } |
| } |
| } |
| } |
| |
| private static AWTEvent mapEvent(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(), src.getID(), 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; |
| } |
| |
| 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 || |
| peekEvent(KeyEvent.KEY_PRESSED) != null || |
| peekEvent(KeyEvent.KEY_RELEASED) != null || |
| peekEvent(KeyEvent.KEY_TYPED) != null; |
| |
| if (e instanceof KeyEvent) { |
| if (e.getID() == KeyEvent.KEY_RELEASED && ((KeyEvent)e).getKeyCode() == KeyEvent.VK_SHIFT) { |
| myMouseEventDispatcher.resetHorScrollingTracker(); |
| } |
| } |
| |
| if (!typeAheadFlushing && typeAheadDispatchToFocusManager(e)) { |
| return; |
| } |
| |
| if (e instanceof WindowEvent) { |
| ActivityTracker.getInstance().inc(); |
| } |
| |
| if (e instanceof MouseWheelEvent) { |
| final MenuElement[] selectedPath = MenuSelectionManager.defaultManager().getSelectedPath(); |
| if (selectedPath != null && selectedPath.length > 0 && !(selectedPath[0] instanceof ComboPopup)) { |
| ((MouseWheelEvent)e).consume(); |
| 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); |
| } |
| int timeout = request.getTimeout(); |
| myIdleRequestsAlarm.addRequest(request, timeout, 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; |
| } |
| |
| for (EventDispatcher eachDispatcher : myDispatchers) { |
| if (eachDispatcher.dispatch(e)) { |
| return; |
| } |
| } |
| |
| if (e instanceof InputMethodEvent) { |
| if (SystemInfo.isMac && myKeyEventDispatcher.isWaitingForSecondKeyStroke()) { |
| return; |
| } |
| } |
| if (e instanceof InputEvent && Patches.SPECIAL_INPUT_METHOD_PROCESSING) { |
| final InputEvent inputEvent = (InputEvent)e; |
| if (!inputEvent.getComponent().isShowing()) { |
| 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 (myMouseEventDispatcher.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()); |
| SwingUtilities.invokeLater(new Runnable() { |
| @Override |
| public void run() { |
| dispatchEvent(toDispatch); |
| } |
| }); |
| } |
| if (me.getButton() != 0) { |
| setLastClickEvent(me); |
| } else if (lastClickEvent != null && Math.abs(System.currentTimeMillis() - lastClickTime) > 200){ |
| setLastClickEvent(null);//Obsolete event |
| } |
| if (!myMouseEventDispatcher.dispatchMouseEvent(me)) { |
| defaultDispatchEvent(e); |
| } |
| } |
| else { |
| defaultDispatchEvent(e); |
| } |
| } |
| |
| private MouseEvent lastClickEvent = null; |
| private long lastClickTime = 0L; |
| |
| private void setLastClickEvent(@Nullable MouseEvent event) { |
| lastClickEvent = event; |
| lastClickTime = System.currentTimeMillis(); |
| } |
| |
| public boolean wasRootRecentlyClicked(Component component) { |
| if (component == null || lastClickEvent == null || lastClickEvent.getComponent() == null) |
| return false; |
| return SwingUtilities.getRoot(lastClickEvent.getComponent()) == SwingUtilities.getRoot(component); |
| } |
| |
| private static void fixStickyWindow(KeyboardFocusManager mgr, Window wnd, String resetMethod) { |
| Window showingWindow = wnd; |
| |
| if (wnd != null && !wnd.isShowing()) { |
| 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) { |
| Component nativeFocusOwner = (Component)owner; |
| if (nativeFocusOwner instanceof Window) { |
| showingWindow = (Window)nativeFocusOwner; |
| } else { |
| showingWindow = SwingUtilities.getWindowAncestor(nativeFocusOwner); |
| } |
| } |
| } |
| 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(AWTEvent e) { |
| if (e instanceof KeyEvent) { |
| if (!mySuspendMode && shallEnterSuspendMode()) { |
| enterSuspendMode(); |
| } |
| } |
| } |
| |
| private boolean shallEnterSuspendMode() { |
| return peekEvent(WindowEvent.WINDOW_OPENED) != null; |
| } |
| |
| private static boolean processAppActivationEvents(AWTEvent e) { |
| Application app = ApplicationManager.getApplication(); |
| if (!(app instanceof ApplicationImpl)) return false; |
| ApplicationImpl appImpl = (ApplicationImpl)app; |
| |
| if (e instanceof WindowEvent) { |
| WindowEvent we = (WindowEvent)e; |
| if (we.getID() == WindowEvent.WINDOW_GAINED_FOCUS && we.getWindow() != null) { |
| if (we.getOppositeWindow() == null && !appImpl.isActive()) { |
| appImpl.tryToApplyActivationState(true, we.getWindow()); |
| } |
| } |
| else if (we.getID() == WindowEvent.WINDOW_LOST_FOCUS && we.getWindow() != null) { |
| if (we.getOppositeWindow() == null && appImpl.isActive()) { |
| appImpl.tryToApplyActivationState(false, we.getWindow()); |
| } |
| } |
| } |
| |
| return false; |
| } |
| |
| private void defaultDispatchEvent(final AWTEvent e) { |
| try { |
| myDispatchingFocusEvent = e instanceof FocusEvent; |
| |
| maybeReady(); |
| fixStickyAlt(e); |
| |
| super.dispatchEvent(e); |
| } |
| catch (Throwable t) { |
| processException(t); |
| } |
| finally { |
| myDispatchingFocusEvent = false; |
| } |
| } |
| |
| private static Field stickyAltField; |
| //IDEA-17359 |
| private static void fixStickyAlt(AWTEvent e) { |
| if (Registry.is("actionSystem.win.suppressAlt.new")) { |
| if (SystemInfo.isWindows |
| && UIManager.getLookAndFeel() instanceof WindowsLookAndFeel |
| && 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 (stickyAltField == null) { |
| stickyAltField = ReflectionUtil.getDeclaredField(Class.forName("com.sun.java.swing.plaf.windows.WindowsRootPaneUI$AltProcessor"), "menuCanceledOnPress"); |
| } |
| stickyAltField.set(null, true); |
| } |
| catch (Exception exception) { |
| LOG.error(exception); |
| } |
| } |
| } else if (SystemInfo.isWindowsXP && e instanceof KeyEvent && ((KeyEvent)e).getKeyCode() == KeyEvent.VK_ALT) { |
| ((KeyEvent)e).consume(); |
| } |
| } |
| |
| public boolean isDispatchingFocusEvent() { |
| return myDispatchingFocusEvent; |
| } |
| |
| private static boolean typeAheadDispatchToFocusManager(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, Condition<AWTEvent> 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; |
| ((InputEvent)event).consume(); |
| } |
| } |
| } |
| |
| if (eventOk) { |
| dispatchEvent(event); |
| } |
| } |
| catch (Throwable e) { |
| LOG.error(e); |
| event = null; |
| } |
| } |
| while (!exitCondition.value(event)); |
| } |
| |
| |
| public interface EventDispatcher { |
| boolean dispatch(AWTEvent e); |
| } |
| |
| |
| private final class MyFireIdleRequest implements Runnable { |
| private final Runnable myRunnable; |
| private final int myTimeout; |
| |
| |
| public 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; |
| } |
| } |
| |
| |
| private final class ExitSuspendModeRunnable implements Runnable { |
| |
| @Override |
| public void run() { |
| if (mySuspendMode) { |
| exitSuspendMode(); |
| } |
| } |
| } |
| |
| |
| public long getIdleTime() { |
| return myIdleTime; |
| } |
| |
| |
| public IdePopupManager getPopupManager() { |
| return myPopupManager; |
| } |
| |
| public IdeKeyEventDispatcher getKeyEventDispatcher() { |
| return myKeyEventDispatcher; |
| } |
| |
| /** |
| * Same as {@link #blockNextEvents(java.awt.event.MouseEvent, com.intellij.ide.IdeEventQueue.BlockMode)} with <code>blockMode</code> equal |
| * to <code>COMPLETE</code>. |
| */ |
| public void blockNextEvents(final MouseEvent e) { |
| blockNextEvents(e, BlockMode.COMPLETE); |
| } |
| |
| /** |
| * When <code>blockMode</code> is <code>COMPLETE</code>, blocks following related mouse events completely, when <code>blockMode</code> is |
| * <code>ACTIONS</code> only blocks performing actions bound to corresponding mouse shortcuts. |
| */ |
| public void blockNextEvents(final MouseEvent e, 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() { |
| flushReady(); |
| } |
| |
| private void flushReady() { |
| if (myReady.isEmpty() || !isReady()) return; |
| |
| Runnable[] ready = myReady.toArray(new Runnable[myReady.size()]); |
| myReady.clear(); |
| |
| for (Runnable each : ready) { |
| each.run(); |
| } |
| } |
| |
| public void doWhenReady(final Runnable runnable) { |
| if (EventQueue.isDispatchThread()) { |
| myReady.add(runnable); |
| maybeReady(); |
| } |
| else { |
| SwingUtilities.invokeLater(new Runnable() { |
| @Override |
| public void run() { |
| myReady.add(runnable); |
| maybeReady(); |
| } |
| }); |
| } |
| } |
| |
| public boolean isPopupActive() { |
| return myPopupManager.isPopupActive(); |
| } |
| |
| private static class WindowsAltSupressor implements EventDispatcher { |
| private boolean myWaitingForAltRelease; |
| private Robot myRobot; |
| |
| @Override |
| public boolean dispatch(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 { |
| if (ApplicationManager.getApplication() == null || |
| UISettings.getInstance() == null || |
| !SystemInfo.isWindows || |
| !Registry.is("actionSystem.win.suppressAlt") || |
| !(UISettings.getInstance().HIDE_TOOL_STRIPES || UISettings.getInstance().PRESENTATION_MODE)) { |
| return true; |
| } |
| |
| 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(new Runnable() { |
| @Override |
| public void run() { |
| try { |
| final Window window = component instanceof Window ? (Window)component : SwingUtilities.windowForComponent(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; |
| } |
| } |
| |
| public boolean isInputMethodEnabled() { |
| return !SystemInfo.isMac || myInputMethodLock == 0; |
| } |
| |
| public void disableInputMethods(Disposable parentDisposable) { |
| myInputMethodLock++; |
| Disposer.register(parentDisposable, new Disposable() { |
| @Override |
| public void dispose() { |
| myInputMethodLock--; |
| } |
| }); |
| } |
| |
| private final FrequentEventDetector myFrequentEventDetector = new FrequentEventDetector(1009, 100); |
| @Override |
| public void postEvent(AWTEvent theEvent) { |
| myFrequentEventDetector.eventHappened(); |
| super.postEvent(theEvent); |
| } |
| |
| /** |
| * @see com.intellij.ide.IdeEventQueue#blockNextEvents(java.awt.event.MouseEvent, com.intellij.ide.IdeEventQueue.BlockMode) |
| */ |
| public enum BlockMode { |
| COMPLETE, ACTIONS |
| } |
| } |