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
.Condition
;
33 import com
.intellij
.openapi
.util
.Disposer
;
34 import com
.intellij
.openapi
.util
.SystemInfo
;
35 import com
.intellij
.openapi
.util
.registry
.Registry
;
36 import com
.intellij
.openapi
.wm
.IdeFocusManager
;
37 import com
.intellij
.openapi
.wm
.ex
.WindowManagerEx
;
38 import com
.intellij
.util
.Alarm
;
39 import com
.intellij
.util
.ReflectionUtil
;
40 import com
.intellij
.util
.containers
.ContainerUtil
;
41 import com
.intellij
.util
.containers
.HashMap
;
42 import org
.jetbrains
.annotations
.NotNull
;
46 import java
.awt
.event
.*;
47 import java
.beans
.PropertyChangeEvent
;
48 import java
.beans
.PropertyChangeListener
;
49 import java
.lang
.reflect
.Field
;
50 import java
.lang
.reflect
.Method
;
55 * @author Vladimir Kondratyev
56 * @author Anton Katilin
59 public class IdeEventQueue
extends EventQueue
{
60 private static final Logger LOG
= Logger
.getInstance("#com.intellij.ide.IdeEventQueue");
63 * Adding/Removing of "idle" listeners should be thread safe.
65 private final Object myLock
= new Object();
67 private final ArrayList
<Runnable
> myIdleListeners
= new ArrayList
<Runnable
>(2);
69 private final ArrayList
<Runnable
> myActivityListeners
= new ArrayList
<Runnable
>(2);
71 private final Alarm myIdleRequestsAlarm
= new Alarm();
73 private final Alarm myIdleTimeCounterAlarm
= new Alarm(Alarm
.ThreadToUse
.SWING_THREAD
);
75 private long myIdleTime
;
77 private final Map
<Runnable
, MyFireIdleRequest
> myListener2Request
= new HashMap
<Runnable
, MyFireIdleRequest
>();
78 // IdleListener -> MyFireIdleRequest
80 private final IdeKeyEventDispatcher myKeyEventDispatcher
= new IdeKeyEventDispatcher(this);
82 private final IdeMouseEventDispatcher myMouseEventDispatcher
= new IdeMouseEventDispatcher();
84 private final IdePopupManager myPopupManager
= new IdePopupManager();
87 private final ToolkitBugsProcessor myToolkitBugsProcessor
= new ToolkitBugsProcessor();
89 private boolean mySuspendMode
;
92 * We exit from suspend mode when focus owner changes and no more WindowEvent.WINDOW_OPENED events
94 * in the queue. If WINDOW_OPENED event does exists in the queus then we restart the alarm.
97 private Component myFocusOwner
;
99 private final Runnable myExitSuspendModeRunnable
= new ExitSuspendModeRunnable();
102 * We exit from suspend mode when this alarm is triggered and no mode WindowEvent.WINDOW_OPENED
104 * events in the queue. If WINDOW_OPENED event does exist then we restart the alarm.
106 private final Alarm mySuspendModeAlarm
= new Alarm();
109 * Counter of processed events. It is used to assert that data context lives only inside single
114 private int myEventCount
;
117 private boolean myIsInInputEvent
= false;
119 private AWTEvent myCurrentEvent
= null;
121 private long myLastActiveTime
;
123 private WindowManagerEx myWindowManager
;
126 private final Set
<EventDispatcher
> myDispatchers
= new LinkedHashSet
<EventDispatcher
>();
127 private final Set
<EventDispatcher
> myPostprocessors
= new LinkedHashSet
<EventDispatcher
>();
129 private final Set
<Runnable
> myReady
= new HashSet
<Runnable
>();
130 private boolean myKeyboardBusy
;
132 private static class IdeEventQueueHolder
{
133 private static final IdeEventQueue INSTANCE
= new IdeEventQueue();
136 public static IdeEventQueue
getInstance() {
137 return IdeEventQueueHolder
.INSTANCE
;
140 private IdeEventQueue() {
141 addIdleTimeCounterRequest();
142 final KeyboardFocusManager keyboardFocusManager
= KeyboardFocusManager
.getCurrentKeyboardFocusManager();
144 //noinspection HardCodedStringLiteral
145 keyboardFocusManager
.addPropertyChangeListener("permanentFocusOwner", new PropertyChangeListener() {
147 public void propertyChange(final PropertyChangeEvent e
) {
148 final Application application
= ApplicationManager
.getApplication();
149 if (application
== null) {
151 // We can get focus event before application is initialized
154 application
.assertIsDispatchThread();
155 final Window focusedWindow
= keyboardFocusManager
.getFocusedWindow();
156 final Component focusOwner
= keyboardFocusManager
.getFocusOwner();
157 if (mySuspendMode
&& focusedWindow
!= null && focusOwner
!= null && focusOwner
!= myFocusOwner
&& !(focusOwner
instanceof Window
)) {
165 public void setWindowManager(final WindowManagerEx windowManager
) {
166 myWindowManager
= windowManager
;
170 private void addIdleTimeCounterRequest() {
171 Application application
= ApplicationManager
.getApplication();
172 if (application
!= null && application
.isUnitTestMode()) return;
174 myIdleTimeCounterAlarm
.cancelAllRequests();
175 myLastActiveTime
= System
.currentTimeMillis();
176 myIdleTimeCounterAlarm
.addRequest(new Runnable() {
178 myIdleTime
+= System
.currentTimeMillis() - myLastActiveTime
;
179 addIdleTimeCounterRequest();
181 }, 20000, ModalityState
.NON_MODAL
);
185 public boolean shouldNotTypeInEditor() {
186 return myKeyEventDispatcher
.isWaitingForSecondKeyStroke() || mySuspendMode
;
190 private void enterSuspendMode() {
191 mySuspendMode
= true;
192 myFocusOwner
= KeyboardFocusManager
.getCurrentKeyboardFocusManager().getFocusOwner();
193 mySuspendModeAlarm
.cancelAllRequests();
194 mySuspendModeAlarm
.addRequest(myExitSuspendModeRunnable
, 750);
199 * Exits supend mode and pumps all suspended events.
202 private void exitSuspendMode() {
203 if (shallEnterSuspendMode()) {
205 // We have to exit from suspend mode (focus owner changes or alarm is triggered) but
207 // WINDOW_OPENED isn't dispatched yet. In this case we have to restart the alarm until
209 // all WINDOW_OPENED event will be processed.
210 mySuspendModeAlarm
.cancelAllRequests();
211 mySuspendModeAlarm
.addRequest(myExitSuspendModeRunnable
, 250);
215 // Now we can pump all suspended events.
216 mySuspendMode
= false;
217 myFocusOwner
= null; // to prevent memory leaks
222 public void addIdleListener(@NotNull final Runnable runnable
, final int timeout
) {
223 LOG
.assertTrue(timeout
> 0);
224 synchronized (myLock
) {
225 myIdleListeners
.add(runnable
);
226 final MyFireIdleRequest request
= new MyFireIdleRequest(runnable
, timeout
);
227 myListener2Request
.put(runnable
, request
);
228 myIdleRequestsAlarm
.addRequest(request
, timeout
);
233 public void removeIdleListener(@NotNull final Runnable runnable
) {
234 synchronized (myLock
) {
235 final boolean wasRemoved
= myIdleListeners
.remove(runnable
);
237 LOG
.assertTrue(false, "unknown runnable: " + runnable
);
239 final MyFireIdleRequest request
= myListener2Request
.remove(runnable
);
240 LOG
.assertTrue(request
!= null);
241 myIdleRequestsAlarm
.cancelRequest(request
);
246 public void addActivityListener(@NotNull final Runnable runnable
) {
247 synchronized (myLock
) {
248 myActivityListeners
.add(runnable
);
252 public void addActivityListener(@NotNull final Runnable runnable
, Disposable parentDisposable
) {
253 synchronized (myLock
) {
254 ContainerUtil
.add(runnable
, myActivityListeners
, parentDisposable
);
259 public void removeActivityListener(@NotNull final Runnable runnable
) {
260 synchronized (myLock
) {
261 final boolean wasRemoved
= myActivityListeners
.remove(runnable
);
263 LOG
.assertTrue(false, "unknown runnable: " + runnable
);
269 public void addDispatcher(final EventDispatcher dispatcher
, Disposable parent
) {
270 _addProcessor(dispatcher
, parent
, myDispatchers
);
273 public void removeDispatcher(EventDispatcher dispatcher
) {
274 myDispatchers
.remove(dispatcher
);
277 public boolean containsDispatcher(EventDispatcher dispatcher
) {
278 return myDispatchers
.contains(dispatcher
);
281 public void addPostprocessor(EventDispatcher dispatcher
, Disposable parent
) {
282 _addProcessor(dispatcher
, parent
, myPostprocessors
);
285 public void removePostprocessor(EventDispatcher dispatcher
) {
286 myPostprocessors
.remove(dispatcher
);
289 private void _addProcessor(final EventDispatcher dispatcher
, Disposable parent
, Set
<EventDispatcher
> set
) {
291 if (parent
!= null) {
292 Disposer
.register(parent
, new Disposable() {
293 public void dispose() {
294 removeDispatcher(dispatcher
);
300 public int getEventCount() {
305 public void setEventCount(int evCount
) {
306 myEventCount
= evCount
;
310 public AWTEvent
getTrueCurrentEvent() {
311 return myCurrentEvent
;
314 //[jeka] commented for performance reasons
318 public void postEvent(final AWTEvent e) {
320 // [vova] sometime people call SwingUtilities.invokeLater(null). To
322 // find such situations we will specially check InvokationEvents
326 if (e instanceof InvocationEvent) {
328 //noinspection HardCodedStringLiteral
330 final Field field = InvocationEvent.class.getDeclaredField("runnable");
332 field.setAccessible(true);
334 final Object runnable = field.get(e);
336 if (runnable == null) {
338 //noinspection HardCodedStringLiteral
340 throw new IllegalStateException("InvocationEvent contains null runnable: " + e);
348 catch (final Exception exc) {
350 throw new Error(exc);
360 public void dispatchEvent(final AWTEvent e
) {
361 boolean wasInputEvent
= myIsInInputEvent
;
362 myIsInInputEvent
= e
instanceof InputEvent
|| e
instanceof InputMethodEvent
|| e
instanceof WindowEvent
|| e
instanceof ActionEvent
;
363 AWTEvent oldEvent
= myCurrentEvent
;
366 JobSchedulerImpl
.suspend();
371 myIsInInputEvent
= wasInputEvent
;
372 myCurrentEvent
= oldEvent
;
373 JobSchedulerImpl
.resume();
375 for (EventDispatcher each
: myPostprocessors
) {
379 if (e
instanceof KeyEvent
) {
385 @SuppressWarnings({"ALL"})
386 private static String
toDebugString(final AWTEvent e
) {
387 if (e
instanceof InvocationEvent
) {
389 final Field f
= InvocationEvent
.class.getDeclaredField("runnable");
390 f
.setAccessible(true);
391 Object runnable
= f
.get(e
);
393 return "Invoke Later[" + runnable
.toString() + "]";
395 catch (NoSuchFieldException e1
) {
397 catch (IllegalAccessException e1
) {
404 private void _dispatchEvent(final AWTEvent e
) {
405 if (e
.getID() == MouseEvent
.MOUSE_DRAGGED
) {
406 DnDManagerImpl dndManager
= (DnDManagerImpl
)DnDManager
.getInstance();
407 if (dndManager
!= null) {
408 dndManager
.setLastDropHandler(null);
416 if (processAppActivationEvents(e
)) return;
418 fixStickyFocusedComponents(e
);
420 if (!myPopupManager
.isPopupActive()) {
421 enterSuspendModeIfNeeded(e
);
424 if (e
instanceof KeyEvent
) {
425 myKeyboardBusy
= e
.getID() != KeyEvent
.KEY_RELEASED
|| ((KeyEvent
)e
).getModifiers() != 0;
428 if (typeAheadDispatchToFocusManager(e
)) return;
430 if (e
instanceof WindowEvent
) {
431 ActivityTracker
.getInstance().inc();
435 // Process "idle" and "activity" listeners
436 if (e
instanceof KeyEvent
|| e
instanceof MouseEvent
) {
437 ActivityTracker
.getInstance().inc();
439 synchronized (myLock
) {
440 myIdleRequestsAlarm
.cancelAllRequests();
441 for (Runnable idleListener
: myIdleListeners
) {
442 final MyFireIdleRequest request
= myListener2Request
.get(idleListener
);
443 if (request
== null) {
444 LOG
.error("There is no request for " + idleListener
);
446 int timeout
= request
.getTimeout();
447 myIdleRequestsAlarm
.addRequest(request
, timeout
, ModalityState
.NON_MODAL
);
449 if (KeyEvent
.KEY_PRESSED
== e
.getID() ||
450 KeyEvent
.KEY_TYPED
== e
.getID() ||
451 MouseEvent
.MOUSE_PRESSED
== e
.getID() ||
452 MouseEvent
.MOUSE_RELEASED
== e
.getID() ||
453 MouseEvent
.MOUSE_CLICKED
== e
.getID()) {
454 addIdleTimeCounterRequest();
455 for (Runnable activityListener
: myActivityListeners
) {
456 activityListener
.run();
461 if (myPopupManager
.isPopupActive() && myPopupManager
.dispatch(e
)) {
465 for (EventDispatcher eachDispatcher
: myDispatchers
) {
466 if (eachDispatcher
.dispatch(e
)) {
471 if (e
instanceof InputMethodEvent
) {
472 if (SystemInfo
.isMac
&& myKeyEventDispatcher
.isWaitingForSecondKeyStroke()) {
476 if (e
instanceof InputEvent
&& Patches
.SPECIAL_WINPUT_METHOD_PROCESSING
) {
477 final InputEvent inputEvent
= (InputEvent
)e
;
478 if (!inputEvent
.getComponent().isShowing()) {
482 if (e
instanceof ComponentEvent
&& myWindowManager
!= null) {
483 myWindowManager
.dispatchComponentEvent((ComponentEvent
)e
);
485 if (e
instanceof KeyEvent
) {
486 if (mySuspendMode
|| !myKeyEventDispatcher
.dispatchKeyEvent((KeyEvent
)e
)) {
487 defaultDispatchEvent(e
);
490 ((KeyEvent
)e
).consume();
491 defaultDispatchEvent(e
);
494 else if (e
instanceof MouseEvent
) {
495 if (!myMouseEventDispatcher
.dispatchMouseEvent((MouseEvent
)e
)) {
496 defaultDispatchEvent(e
);
500 defaultDispatchEvent(e
);
504 private static void fixStickyWindow(KeyboardFocusManager mgr
, Window wnd
, String resetMethod
) {
505 Window showingWindow
= wnd
;
507 if (wnd
!= null && !wnd
.isShowing()) {
508 while (showingWindow
!= null) {
509 if (showingWindow
.isShowing()) break;
510 showingWindow
= (Window
)showingWindow
.getParent();
513 if (showingWindow
== null) {
514 final Frame
[] allFrames
= Frame
.getFrames();
515 for (Frame each
: allFrames
) {
516 if (each
.isShowing()) {
517 showingWindow
= each
;
524 if (showingWindow
!= null && showingWindow
!= wnd
) {
525 final Method setActive
=
526 ReflectionUtil
.findMethod(KeyboardFocusManager
.class.getDeclaredMethods(), resetMethod
, Window
.class);
527 if (setActive
!= null) {
529 setActive
.setAccessible(true);
530 setActive
.invoke(mgr
, (Window
)showingWindow
);
532 catch (Exception exc
) {
540 private static void fixStickyFocusedComponents(AWTEvent e
) {
541 if (!(e
instanceof InputEvent
)) return;
543 final KeyboardFocusManager mgr
= KeyboardFocusManager
.getCurrentKeyboardFocusManager();
545 if (Registry
.is("actionSystem.fixStickyFocusedWindows")) {
546 fixStickyWindow(mgr
, mgr
.getActiveWindow(), "setGlobalActiveWindow");
547 fixStickyWindow(mgr
, mgr
.getFocusedWindow(), "setGlobalFocusedWindow");
550 if (Registry
.is("actionSystem.fixNullFocusedComponent")) {
551 final Component focusOwner
= mgr
.getFocusOwner();
552 if (focusOwner
== null) {
553 Window showingWindow
= mgr
.getActiveWindow();
554 if (showingWindow
!= null) {
555 final IdeFocusManager fm
= IdeFocusManager
.findInstanceByComponent(showingWindow
);
556 fm
.doWhenFocusSettlesDown(new Runnable() {
558 if (mgr
.getFocusOwner() == null) {
559 final Application app
= ApplicationManager
.getApplication();
560 if (app
!= null && app
.isActive()) {
561 fm
.requestDefaultFocus(false);
571 private void enterSuspendModeIfNeeded(AWTEvent e
) {
572 if (e
instanceof KeyEvent
) {
573 if (!mySuspendMode
&& shallEnterSuspendMode()) {
579 private boolean shallEnterSuspendMode() {
580 return peekEvent(WindowEvent
.WINDOW_OPENED
) != null;
583 private static boolean processAppActivationEvents(AWTEvent e
) {
584 final Application app
= ApplicationManager
.getApplication();
585 if (!(app
instanceof ApplicationImpl
)) return false;
587 ApplicationImpl appImpl
= (ApplicationImpl
)app
;
589 boolean consumed
= false;
590 if (e
instanceof WindowEvent
) {
591 WindowEvent we
= (WindowEvent
)e
;
592 if (we
.getID() == WindowEvent
.WINDOW_GAINED_FOCUS
&& we
.getWindow() != null) {
593 if (we
.getOppositeWindow() == null && !appImpl
.isActive()) {
594 consumed
= appImpl
.tryToApplyActivationState(true, we
.getWindow());
597 else if (we
.getID() == WindowEvent
.WINDOW_LOST_FOCUS
&& we
.getWindow() != null) {
598 if (we
.getOppositeWindow() == null && appImpl
.isActive()) {
599 consumed
= appImpl
.tryToApplyActivationState(false, we
.getWindow());
608 private void defaultDispatchEvent(final AWTEvent e
) {
610 super.dispatchEvent(e
);
612 catch (ProcessCanceledException pce
) {
615 catch (Throwable exc
) {
616 if (!myToolkitBugsProcessor
.process(exc
)) {
617 LOG
.error("Error during dispatching of " + e
, exc
);
622 private static boolean typeAheadDispatchToFocusManager(AWTEvent e
) {
623 if (e
instanceof KeyEvent
) {
624 final KeyEvent event
= (KeyEvent
)e
;
625 if (!event
.isConsumed()) {
626 final IdeFocusManager focusManager
= IdeFocusManager
.findInstanceByComponent(event
.getComponent());
627 return focusManager
.dispatch(event
);
635 public void flushQueue() {
637 AWTEvent event
= peekEvent();
638 if (event
== null) return;
640 AWTEvent event1
= getNextEvent();
641 dispatchEvent(event1
);
643 catch (Exception e
) {
649 public void pumpEventsForHierarchy(Component modalComponent
, Condition
<AWTEvent
> exitCondition
) {
653 event
= getNextEvent();
654 boolean eventOk
= true;
655 if (event
instanceof InputEvent
) {
656 final Object s
= event
.getSource();
657 if (s
instanceof Component
) {
658 Component c
= (Component
)s
;
659 Window modalWindow
= SwingUtilities
.windowForComponent(modalComponent
);
660 while (c
!= null && c
!= modalWindow
) c
= c
.getParent();
663 ((InputEvent
)event
).consume();
669 dispatchEvent(event
);
672 catch (Throwable e
) {
677 while (!exitCondition
.value(event
));
681 public interface EventDispatcher
{
682 boolean dispatch(AWTEvent e
);
686 private final class MyFireIdleRequest
implements Runnable
{
687 private final Runnable myRunnable
;
688 private final int myTimeout
;
691 public MyFireIdleRequest(final Runnable runnable
, final int timeout
) {
693 myRunnable
= runnable
;
699 synchronized (myLock
) {
700 myIdleRequestsAlarm
.addRequest(this, myTimeout
, ModalityState
.NON_MODAL
);
704 public int getTimeout() {
710 private final class ExitSuspendModeRunnable
implements Runnable
{
720 public long getIdleTime() {
725 public IdePopupManager
getPopupManager() {
726 return myPopupManager
;
729 public IdeKeyEventDispatcher
getKeyEventDispatcher() {
730 return myKeyEventDispatcher
;
733 public void blockNextEvents(final MouseEvent e
) {
734 myMouseEventDispatcher
.blockNextEvents(e
);
737 public boolean isSuspendMode() {
738 return mySuspendMode
;
741 public boolean hasFocusEventsPending() {
742 return peekEvent(FocusEvent
.FOCUS_GAINED
) != null || peekEvent(FocusEvent
.FOCUS_LOST
) != null;
745 private boolean isReady() {
746 return !myKeyboardBusy
&& myKeyEventDispatcher
.isReady();
749 public void maybeReady() {
753 private void flushReady() {
754 if (myReady
.isEmpty() || !isReady()) return;
756 Runnable
[] ready
= myReady
.toArray(new Runnable
[myReady
.size()]);
759 for (Runnable each
: ready
) {
764 public void doWhenReady(final Runnable runnable
) {
765 if (EventQueue
.isDispatchThread()) {
766 myReady
.add(runnable
);
769 SwingUtilities
.invokeLater(new Runnable() {
771 myReady
.add(runnable
);