2 * Copyright 2000-2009 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 package com.intellij.ide;
19 import com.intellij.Patches;
20 import com.intellij.concurrency.JobSchedulerImpl;
21 import com.intellij.ide.dnd.DnDManager;
22 import com.intellij.ide.dnd.DnDManagerImpl;
23 import com.intellij.openapi.Disposable;
24 import com.intellij.openapi.application.Application;
25 import com.intellij.openapi.application.ApplicationManager;
26 import com.intellij.openapi.application.ModalityState;
27 import com.intellij.openapi.application.impl.ApplicationImpl;
28 import com.intellij.openapi.diagnostic.Logger;
29 import com.intellij.openapi.keymap.impl.IdeKeyEventDispatcher;
30 import com.intellij.openapi.keymap.impl.IdeMouseEventDispatcher;
31 import com.intellij.openapi.progress.ProcessCanceledException;
32 import com.intellij.openapi.util.ActionCallback;
33 import com.intellij.openapi.util.Condition;
34 import com.intellij.openapi.util.Disposer;
35 import com.intellij.openapi.util.SystemInfo;
36 import com.intellij.openapi.util.registry.Registry;
37 import com.intellij.openapi.wm.IdeFocusManager;
38 import com.intellij.openapi.wm.ex.WindowManagerEx;
39 import com.intellij.util.Alarm;
40 import com.intellij.util.ReflectionUtil;
41 import com.intellij.util.containers.ContainerUtil;
42 import com.intellij.util.containers.HashMap;
43 import org.jetbrains.annotations.NotNull;
47 import java.awt.event.*;
48 import java.beans.PropertyChangeEvent;
49 import java.beans.PropertyChangeListener;
50 import java.lang.reflect.Field;
51 import java.lang.reflect.Method;
56 * @author Vladimir Kondratyev
57 * @author Anton Katilin
60 public class IdeEventQueue extends EventQueue {
62 private static final Logger LOG = Logger.getInstance("#com.intellij.ide.IdeEventQueue");
64 private static final boolean DEBUG = LOG.isDebugEnabled();
67 * Adding/Removing of "idle" listeners should be thread safe.
69 private final Object myLock = new Object();
71 private final ArrayList<Runnable> myIdleListeners = new ArrayList<Runnable>(2);
73 private final ArrayList<Runnable> myActivityListeners = new ArrayList<Runnable>(2);
75 private final Alarm myIdleRequestsAlarm = new Alarm();
77 private final Alarm myIdleTimeCounterAlarm = new Alarm(Alarm.ThreadToUse.SWING_THREAD);
79 private long myIdleTime;
81 private final Map<Runnable, MyFireIdleRequest> myListener2Request = new HashMap<Runnable, MyFireIdleRequest>();
82 // IdleListener -> MyFireIdleRequest
84 private final IdeKeyEventDispatcher myKeyEventDispatcher = new IdeKeyEventDispatcher(this);
86 private final IdeMouseEventDispatcher myMouseEventDispatcher = new IdeMouseEventDispatcher();
88 private final IdePopupManager myPopupManager = new IdePopupManager();
91 private boolean mySuspendMode;
94 * We exit from suspend mode when focus owner changes and no more WindowEvent.WINDOW_OPENED events
96 * in the queue. If WINDOW_OPENED event does exists in the queus then we restart the alarm.
99 private Component myFocusOwner;
101 private final Runnable myExitSuspendModeRunnable = new ExitSuspendModeRunnable();
104 * We exit from suspend mode when this alarm is triggered and no mode WindowEvent.WINDOW_OPENED
106 * events in the queue. If WINDOW_OPENED event does exist then we restart the alarm.
108 private final Alarm mySuspendModeAlarm = new Alarm();
111 * Counter of processed events. It is used to assert that data context lives only inside single
116 private int myEventCount;
119 private boolean myIsInInputEvent = false;
121 private AWTEvent myCurrentEvent = null;
123 private long myLastActiveTime;
125 private WindowManagerEx myWindowManager;
128 private final Set<EventDispatcher> myDispatchers = new LinkedHashSet<EventDispatcher>();
129 private final Set<EventDispatcher> myPostprocessors = new LinkedHashSet<EventDispatcher>();
131 private Set<Runnable> myReady = new HashSet<Runnable>();
132 private boolean myKeyboardBusy;
134 private static class IdeEventQueueHolder {
135 private static final IdeEventQueue INSTANCE = new IdeEventQueue();
138 public static IdeEventQueue getInstance() {
139 return IdeEventQueueHolder.INSTANCE;
142 private IdeEventQueue() {
143 addIdleTimeCounterRequest();
144 final KeyboardFocusManager keyboardFocusManager = KeyboardFocusManager.getCurrentKeyboardFocusManager();
146 //noinspection HardCodedStringLiteral
147 keyboardFocusManager.addPropertyChangeListener("permanentFocusOwner", new PropertyChangeListener() {
149 public void propertyChange(final PropertyChangeEvent e) {
150 final Application application = ApplicationManager.getApplication();
151 if (application == null) {
153 // We can get focus event before application is initialized
156 application.assertIsDispatchThread();
157 final Window focusedWindow = keyboardFocusManager.getFocusedWindow();
158 final Component focusOwner = keyboardFocusManager.getFocusOwner();
159 if (mySuspendMode && focusedWindow != null && focusOwner != null && focusOwner != myFocusOwner && !(focusOwner instanceof Window)) {
167 public void setWindowManager(final WindowManagerEx windowManager) {
168 myWindowManager = windowManager;
172 private void addIdleTimeCounterRequest() {
173 Application application = ApplicationManager.getApplication();
174 if (application != null && application.isUnitTestMode()) return;
176 myIdleTimeCounterAlarm.cancelAllRequests();
177 myLastActiveTime = System.currentTimeMillis();
178 myIdleTimeCounterAlarm.addRequest(new Runnable() {
180 myIdleTime += System.currentTimeMillis() - myLastActiveTime;
181 addIdleTimeCounterRequest();
183 }, 20000, ModalityState.NON_MODAL);
187 public boolean shouldNotTypeInEditor() {
188 return myKeyEventDispatcher.isWaitingForSecondKeyStroke() || mySuspendMode;
192 private void enterSuspendMode() {
193 mySuspendMode = true;
194 myFocusOwner = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner();
195 mySuspendModeAlarm.cancelAllRequests();
196 mySuspendModeAlarm.addRequest(myExitSuspendModeRunnable, 750);
201 * Exits supend mode and pumps all suspended events.
204 private void exitSuspendMode() {
205 if (shallEnterSuspendMode()) {
207 // We have to exit from suspend mode (focus owner changes or alarm is triggered) but
209 // WINDOW_OPENED isn't dispatched yet. In this case we have to restart the alarm until
211 // all WINDOW_OPENED event will be processed.
212 mySuspendModeAlarm.cancelAllRequests();
213 mySuspendModeAlarm.addRequest(myExitSuspendModeRunnable, 250);
217 // Now we can pump all suspended events.
218 mySuspendMode = false;
219 myFocusOwner = null; // to prevent memory leaks
224 public void addIdleListener(@NotNull final Runnable runnable, final int timeout) {
225 LOG.assertTrue(timeout > 0);
226 synchronized (myLock) {
227 myIdleListeners.add(runnable);
228 final MyFireIdleRequest request = new MyFireIdleRequest(runnable, timeout);
229 myListener2Request.put(runnable, request);
230 myIdleRequestsAlarm.addRequest(request, timeout);
235 public void removeIdleListener(@NotNull final Runnable runnable) {
236 synchronized (myLock) {
237 final boolean wasRemoved = myIdleListeners.remove(runnable);
239 LOG.assertTrue(false, "unknown runnable: " + runnable);
241 final MyFireIdleRequest request = myListener2Request.remove(runnable);
242 LOG.assertTrue(request != null);
243 myIdleRequestsAlarm.cancelRequest(request);
248 public void addActivityListener(@NotNull final Runnable runnable) {
249 synchronized (myLock) {
250 myActivityListeners.add(runnable);
254 public void addActivityListener(@NotNull final Runnable runnable, Disposable parentDisposable) {
255 synchronized (myLock) {
256 ContainerUtil.add(runnable, myActivityListeners, parentDisposable);
261 public void removeActivityListener(@NotNull final Runnable runnable) {
262 synchronized (myLock) {
263 final boolean wasRemoved = myActivityListeners.remove(runnable);
265 LOG.assertTrue(false, "unknown runnable: " + runnable);
271 public void addDispatcher(final EventDispatcher dispatcher, Disposable parent) {
272 _addProcessor(dispatcher, parent, myDispatchers);
275 public void removeDispatcher(EventDispatcher dispatcher) {
276 myDispatchers.remove(dispatcher);
279 public boolean containsDispatcher(EventDispatcher dispatcher) {
280 return myDispatchers.contains(dispatcher);
283 public void addPostprocessor(EventDispatcher dispatcher, Disposable parent) {
284 _addProcessor(dispatcher, parent, myPostprocessors);
287 public void removePostprocessor(EventDispatcher dispatcher) {
288 myPostprocessors.remove(dispatcher);
291 private void _addProcessor(final EventDispatcher dispatcher, Disposable parent, Set<EventDispatcher> set) {
293 if (parent != null) {
294 Disposer.register(parent, new Disposable() {
295 public void dispose() {
296 removeDispatcher(dispatcher);
302 public int getEventCount() {
307 public void setEventCount(int evCount) {
308 myEventCount = evCount;
312 public AWTEvent getTrueCurrentEvent() {
313 return myCurrentEvent;
316 //[jeka] commented for performance reasons
320 public void postEvent(final AWTEvent e) {
322 // [vova] sometime people call SwingUtilities.invokeLater(null). To
324 // find such situations we will specially check InvokationEvents
328 if (e instanceof InvocationEvent) {
330 //noinspection HardCodedStringLiteral
332 final Field field = InvocationEvent.class.getDeclaredField("runnable");
334 field.setAccessible(true);
336 final Object runnable = field.get(e);
338 if (runnable == null) {
340 //noinspection HardCodedStringLiteral
342 throw new IllegalStateException("InvocationEvent contains null runnable: " + e);
350 catch (final Exception exc) {
352 throw new Error(exc);
362 public void dispatchEvent(final AWTEvent e) {
365 boolean wasInputEvent = myIsInInputEvent;
366 myIsInInputEvent = e instanceof InputEvent || e instanceof InputMethodEvent || e instanceof WindowEvent || e instanceof ActionEvent;
367 AWTEvent oldEvent = myCurrentEvent;
370 JobSchedulerImpl.suspend();
375 myIsInInputEvent = wasInputEvent;
376 myCurrentEvent = oldEvent;
377 JobSchedulerImpl.resume();
379 for (EventDispatcher each : myPostprocessors) {
383 if (e instanceof KeyEvent) {
388 final long processTime = System.currentTimeMillis() - t;
389 if (processTime > 100) {
390 LOG.debug("Long event: " + processTime + "ms - " + toDebugString(e));
396 @SuppressWarnings({"ALL"})
397 private static String toDebugString(final AWTEvent e) {
398 if (e instanceof InvocationEvent) {
400 final Field f = InvocationEvent.class.getDeclaredField("runnable");
401 f.setAccessible(true);
402 Object runnable = f.get(e);
404 return "Invoke Later[" + runnable.toString() + "]";
406 catch (NoSuchFieldException e1) {
408 catch (IllegalAccessException e1) {
415 private void _dispatchEvent(final AWTEvent e) {
416 if (e.getID() == MouseEvent.MOUSE_DRAGGED) {
417 DnDManagerImpl dndManager = (DnDManagerImpl)DnDManager.getInstance();
418 if (dndManager != null) {
419 dndManager.setLastDropHandler(null);
427 if (processAppActivationEvents(e)) return;
429 fixStickyFocusedComponents(e);
431 if (!myPopupManager.isPopupActive()) {
432 enterSuspendModeIfNeeded(e);
435 if (e instanceof KeyEvent) {
436 myKeyboardBusy = e.getID() != KeyEvent.KEY_RELEASED || ((KeyEvent)e).getModifiers() != 0;
439 if (typeAheadDispatchToFocusManager(e)) return;
441 if (e instanceof WindowEvent) {
442 ActivityTracker.getInstance().inc();
446 // Process "idle" and "activity" listeners
447 if (e instanceof KeyEvent || e instanceof MouseEvent) {
448 ActivityTracker.getInstance().inc();
450 synchronized (myLock) {
451 myIdleRequestsAlarm.cancelAllRequests();
452 for (Runnable idleListener : myIdleListeners) {
453 final MyFireIdleRequest request = myListener2Request.get(idleListener);
454 if (request == null) {
455 LOG.error("There is no request for " + idleListener);
457 int timeout = request.getTimeout();
458 myIdleRequestsAlarm.addRequest(request, timeout, ModalityState.NON_MODAL);
460 if (KeyEvent.KEY_PRESSED == e.getID() ||
461 KeyEvent.KEY_TYPED == e.getID() ||
462 MouseEvent.MOUSE_PRESSED == e.getID() ||
463 MouseEvent.MOUSE_RELEASED == e.getID() ||
464 MouseEvent.MOUSE_CLICKED == e.getID()) {
465 addIdleTimeCounterRequest();
466 for (Runnable activityListener : myActivityListeners) {
467 activityListener.run();
472 if (myPopupManager.isPopupActive() && myPopupManager.dispatch(e)) {
476 for (EventDispatcher eachDispatcher : myDispatchers) {
477 if (eachDispatcher.dispatch(e)) {
482 if (e instanceof InputMethodEvent) {
483 if (SystemInfo.isMac && myKeyEventDispatcher.isWaitingForSecondKeyStroke()) {
487 if (e instanceof InputEvent && Patches.SPECIAL_WINPUT_METHOD_PROCESSING) {
488 final InputEvent inputEvent = (InputEvent)e;
489 if (!inputEvent.getComponent().isShowing()) {
493 if (e instanceof ComponentEvent && myWindowManager != null) {
494 myWindowManager.dispatchComponentEvent((ComponentEvent)e);
496 if (e instanceof KeyEvent) {
497 if (mySuspendMode || !myKeyEventDispatcher.dispatchKeyEvent((KeyEvent)e)) {
498 defaultDispatchEvent(e);
501 ((KeyEvent)e).consume();
502 defaultDispatchEvent(e);
505 else if (e instanceof MouseEvent) {
506 if (!myMouseEventDispatcher.dispatchMouseEvent((MouseEvent)e)) {
507 defaultDispatchEvent(e);
511 defaultDispatchEvent(e);
515 private void fixStickyFocusedComponents(AWTEvent e) {
516 if (!(e instanceof InputEvent)) return;
518 final KeyboardFocusManager mgr = KeyboardFocusManager.getCurrentKeyboardFocusManager();
519 final Window wnd = mgr.getActiveWindow();
520 Window showingWindow = wnd;
522 if (Registry.is("actionSystem.fixStickyFocusedWindows")) {
523 if (wnd != null && !wnd.isShowing()) {
524 while (showingWindow != null) {
525 if (showingWindow.isShowing()) break;
526 showingWindow = (Window)showingWindow.getParent();
529 if (showingWindow == null) {
530 final Frame[] allFrames = Frame.getFrames();
531 for (Frame each : allFrames) {
532 if (each.isShowing()) {
533 showingWindow = each;
540 if (showingWindow != null && showingWindow != wnd) {
541 final Method setActive =
542 ReflectionUtil.findMethod(KeyboardFocusManager.class.getDeclaredMethods(), "setGlobalActiveWindow", Window.class);
543 if (setActive != null) {
545 setActive.setAccessible(true);
546 setActive.invoke(mgr, (Window)showingWindow);
548 catch (Exception exc) {
556 if (Registry.is("actionSystem.fixNullFocusedComponent")) {
557 final Component focusOwner = mgr.getFocusOwner();
558 if (focusOwner == null) {
559 if (showingWindow != null) {
560 final IdeFocusManager fm = IdeFocusManager.findInstanceByComponent(showingWindow);
561 fm.doWhenFocusSettlesDown(new Runnable() {
563 if (mgr.getFocusOwner() == null) {
564 final Application app = ApplicationManager.getApplication();
565 if (app != null && app.isActive()) {
566 fm.requestDefaultFocus(false);
576 private void enterSuspendModeIfNeeded(AWTEvent e) {
577 if (e instanceof KeyEvent) {
578 if (!mySuspendMode && shallEnterSuspendMode()) {
584 private boolean shallEnterSuspendMode() {
585 return peekEvent(WindowEvent.WINDOW_OPENED) != null;
588 private static boolean processAppActivationEvents(AWTEvent e) {
589 final Application app = ApplicationManager.getApplication();
590 if (!(app instanceof ApplicationImpl)) return false;
592 ApplicationImpl appImpl = (ApplicationImpl)app;
594 boolean consumed = false;
595 if (e instanceof WindowEvent) {
596 WindowEvent we = (WindowEvent)e;
597 if (we.getID() == WindowEvent.WINDOW_GAINED_FOCUS && we.getWindow() != null) {
598 if (we.getOppositeWindow() == null && !appImpl.isActive()) {
599 consumed = appImpl.tryToApplyActivationState(true, we.getWindow());
602 else if (we.getID() == WindowEvent.WINDOW_LOST_FOCUS && we.getWindow() != null) {
603 if (we.getOppositeWindow() == null && appImpl.isActive()) {
604 consumed = appImpl.tryToApplyActivationState(false, we.getWindow());
613 private void defaultDispatchEvent(final AWTEvent e) {
615 super.dispatchEvent(e);
617 catch (ProcessCanceledException pce) {
620 catch (Throwable exc) {
621 LOG.error("Error during dispatching of " + e, exc);
625 private static boolean typeAheadDispatchToFocusManager(AWTEvent e) {
626 if (e instanceof KeyEvent) {
627 final KeyEvent event = (KeyEvent)e;
628 if (!event.isConsumed()) {
629 final IdeFocusManager focusManager = IdeFocusManager.findInstanceByComponent(event.getComponent());
630 return focusManager.dispatch(event);
638 public void flushQueue() {
640 AWTEvent event = peekEvent();
641 if (event == null) return;
643 AWTEvent event1 = getNextEvent();
644 dispatchEvent(event1);
646 catch (Exception e) {
652 public void pumpEventsForHierarchy(Component modalComponent, Condition<AWTEvent> exitCondition) {
656 event = getNextEvent();
657 boolean eventOk = true;
658 if (event instanceof InputEvent) {
659 final Object s = event.getSource();
660 if (s instanceof Component) {
661 Component c = (Component)s;
662 Window modalWindow = SwingUtilities.windowForComponent(modalComponent);
663 while (c != null && c != modalWindow) c = c.getParent();
666 ((InputEvent)event).consume();
672 dispatchEvent(event);
675 catch (Throwable e) {
680 while (!exitCondition.value(event));
684 public interface EventDispatcher {
685 boolean dispatch(AWTEvent e);
689 private final class MyFireIdleRequest implements Runnable {
690 private final Runnable myRunnable;
691 private final int myTimeout;
694 public MyFireIdleRequest(final Runnable runnable, final int timeout) {
696 myRunnable = runnable;
702 synchronized (myLock) {
703 myIdleRequestsAlarm.addRequest(this, myTimeout, ModalityState.NON_MODAL);
707 public int getTimeout() {
713 private final class ExitSuspendModeRunnable implements Runnable {
723 public long getIdleTime() {
728 public IdePopupManager getPopupManager() {
729 return myPopupManager;
732 public IdeKeyEventDispatcher getKeyEventDispatcher() {
733 return myKeyEventDispatcher;
736 public void blockNextEvents(final MouseEvent e) {
737 myMouseEventDispatcher.blockNextEvents(e);
740 public boolean isSuspendMode() {
741 return mySuspendMode;
744 public boolean hasFocusEventsPending() {
745 return peekEvent(FocusEvent.FOCUS_GAINED) != null || peekEvent(FocusEvent.FOCUS_LOST) != null;
748 private boolean isReady() {
749 return !myKeyboardBusy && myKeyEventDispatcher.isReady();
752 public void maybeReady() {
756 private void flushReady() {
757 if (myReady.size() == 0 || !isReady()) return;
759 Runnable[] ready = myReady.toArray(new Runnable[myReady.size()]);
762 for (Runnable each : ready) {
767 public void doWhenReady(final Runnable runnable) {
768 if (EventQueue.isDispatchThread()) {
769 myReady.add(runnable);
772 SwingUtilities.invokeLater(new Runnable() {
774 myReady.add(runnable);