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 boolean mySuspendMode
;
90 * We exit from suspend mode when focus owner changes and no more WindowEvent.WINDOW_OPENED events
92 * in the queue. If WINDOW_OPENED event does exists in the queus then we restart the alarm.
95 private Component myFocusOwner
;
97 private final Runnable myExitSuspendModeRunnable
= new ExitSuspendModeRunnable();
100 * We exit from suspend mode when this alarm is triggered and no mode WindowEvent.WINDOW_OPENED
102 * events in the queue. If WINDOW_OPENED event does exist then we restart the alarm.
104 private final Alarm mySuspendModeAlarm
= new Alarm();
107 * Counter of processed events. It is used to assert that data context lives only inside single
112 private int myEventCount
;
115 private boolean myIsInInputEvent
= false;
117 private AWTEvent myCurrentEvent
= null;
119 private long myLastActiveTime
;
121 private WindowManagerEx myWindowManager
;
124 private final Set
<EventDispatcher
> myDispatchers
= new LinkedHashSet
<EventDispatcher
>();
125 private final Set
<EventDispatcher
> myPostprocessors
= new LinkedHashSet
<EventDispatcher
>();
127 private final Set
<Runnable
> myReady
= new HashSet
<Runnable
>();
128 private boolean myKeyboardBusy
;
130 private static class IdeEventQueueHolder
{
131 private static final IdeEventQueue INSTANCE
= new IdeEventQueue();
134 public static IdeEventQueue
getInstance() {
135 return IdeEventQueueHolder
.INSTANCE
;
138 private IdeEventQueue() {
139 addIdleTimeCounterRequest();
140 final KeyboardFocusManager keyboardFocusManager
= KeyboardFocusManager
.getCurrentKeyboardFocusManager();
142 //noinspection HardCodedStringLiteral
143 keyboardFocusManager
.addPropertyChangeListener("permanentFocusOwner", new PropertyChangeListener() {
145 public void propertyChange(final PropertyChangeEvent e
) {
146 final Application application
= ApplicationManager
.getApplication();
147 if (application
== null) {
149 // We can get focus event before application is initialized
152 application
.assertIsDispatchThread();
153 final Window focusedWindow
= keyboardFocusManager
.getFocusedWindow();
154 final Component focusOwner
= keyboardFocusManager
.getFocusOwner();
155 if (mySuspendMode
&& focusedWindow
!= null && focusOwner
!= null && focusOwner
!= myFocusOwner
&& !(focusOwner
instanceof Window
)) {
163 public void setWindowManager(final WindowManagerEx windowManager
) {
164 myWindowManager
= windowManager
;
168 private void addIdleTimeCounterRequest() {
169 Application application
= ApplicationManager
.getApplication();
170 if (application
!= null && application
.isUnitTestMode()) return;
172 myIdleTimeCounterAlarm
.cancelAllRequests();
173 myLastActiveTime
= System
.currentTimeMillis();
174 myIdleTimeCounterAlarm
.addRequest(new Runnable() {
176 myIdleTime
+= System
.currentTimeMillis() - myLastActiveTime
;
177 addIdleTimeCounterRequest();
179 }, 20000, ModalityState
.NON_MODAL
);
183 public boolean shouldNotTypeInEditor() {
184 return myKeyEventDispatcher
.isWaitingForSecondKeyStroke() || mySuspendMode
;
188 private void enterSuspendMode() {
189 mySuspendMode
= true;
190 myFocusOwner
= KeyboardFocusManager
.getCurrentKeyboardFocusManager().getFocusOwner();
191 mySuspendModeAlarm
.cancelAllRequests();
192 mySuspendModeAlarm
.addRequest(myExitSuspendModeRunnable
, 750);
197 * Exits supend mode and pumps all suspended events.
200 private void exitSuspendMode() {
201 if (shallEnterSuspendMode()) {
203 // We have to exit from suspend mode (focus owner changes or alarm is triggered) but
205 // WINDOW_OPENED isn't dispatched yet. In this case we have to restart the alarm until
207 // all WINDOW_OPENED event will be processed.
208 mySuspendModeAlarm
.cancelAllRequests();
209 mySuspendModeAlarm
.addRequest(myExitSuspendModeRunnable
, 250);
213 // Now we can pump all suspended events.
214 mySuspendMode
= false;
215 myFocusOwner
= null; // to prevent memory leaks
220 public void addIdleListener(@NotNull final Runnable runnable
, final int timeout
) {
221 LOG
.assertTrue(timeout
> 0);
222 synchronized (myLock
) {
223 myIdleListeners
.add(runnable
);
224 final MyFireIdleRequest request
= new MyFireIdleRequest(runnable
, timeout
);
225 myListener2Request
.put(runnable
, request
);
226 myIdleRequestsAlarm
.addRequest(request
, timeout
);
231 public void removeIdleListener(@NotNull final Runnable runnable
) {
232 synchronized (myLock
) {
233 final boolean wasRemoved
= myIdleListeners
.remove(runnable
);
235 LOG
.assertTrue(false, "unknown runnable: " + runnable
);
237 final MyFireIdleRequest request
= myListener2Request
.remove(runnable
);
238 LOG
.assertTrue(request
!= null);
239 myIdleRequestsAlarm
.cancelRequest(request
);
244 public void addActivityListener(@NotNull final Runnable runnable
) {
245 synchronized (myLock
) {
246 myActivityListeners
.add(runnable
);
250 public void addActivityListener(@NotNull final Runnable runnable
, Disposable parentDisposable
) {
251 synchronized (myLock
) {
252 ContainerUtil
.add(runnable
, myActivityListeners
, parentDisposable
);
257 public void removeActivityListener(@NotNull final Runnable runnable
) {
258 synchronized (myLock
) {
259 final boolean wasRemoved
= myActivityListeners
.remove(runnable
);
261 LOG
.assertTrue(false, "unknown runnable: " + runnable
);
267 public void addDispatcher(final EventDispatcher dispatcher
, Disposable parent
) {
268 _addProcessor(dispatcher
, parent
, myDispatchers
);
271 public void removeDispatcher(EventDispatcher dispatcher
) {
272 myDispatchers
.remove(dispatcher
);
275 public boolean containsDispatcher(EventDispatcher dispatcher
) {
276 return myDispatchers
.contains(dispatcher
);
279 public void addPostprocessor(EventDispatcher dispatcher
, Disposable parent
) {
280 _addProcessor(dispatcher
, parent
, myPostprocessors
);
283 public void removePostprocessor(EventDispatcher dispatcher
) {
284 myPostprocessors
.remove(dispatcher
);
287 private void _addProcessor(final EventDispatcher dispatcher
, Disposable parent
, Set
<EventDispatcher
> set
) {
289 if (parent
!= null) {
290 Disposer
.register(parent
, new Disposable() {
291 public void dispose() {
292 removeDispatcher(dispatcher
);
298 public int getEventCount() {
303 public void setEventCount(int evCount
) {
304 myEventCount
= evCount
;
308 public AWTEvent
getTrueCurrentEvent() {
309 return myCurrentEvent
;
312 //[jeka] commented for performance reasons
316 public void postEvent(final AWTEvent e) {
318 // [vova] sometime people call SwingUtilities.invokeLater(null). To
320 // find such situations we will specially check InvokationEvents
324 if (e instanceof InvocationEvent) {
326 //noinspection HardCodedStringLiteral
328 final Field field = InvocationEvent.class.getDeclaredField("runnable");
330 field.setAccessible(true);
332 final Object runnable = field.get(e);
334 if (runnable == null) {
336 //noinspection HardCodedStringLiteral
338 throw new IllegalStateException("InvocationEvent contains null runnable: " + e);
346 catch (final Exception exc) {
348 throw new Error(exc);
358 public void dispatchEvent(final AWTEvent e
) {
359 boolean wasInputEvent
= myIsInInputEvent
;
360 myIsInInputEvent
= e
instanceof InputEvent
|| e
instanceof InputMethodEvent
|| e
instanceof WindowEvent
|| e
instanceof ActionEvent
;
361 AWTEvent oldEvent
= myCurrentEvent
;
364 JobSchedulerImpl
.suspend();
369 myIsInInputEvent
= wasInputEvent
;
370 myCurrentEvent
= oldEvent
;
371 JobSchedulerImpl
.resume();
373 for (EventDispatcher each
: myPostprocessors
) {
377 if (e
instanceof KeyEvent
) {
383 @SuppressWarnings({"ALL"})
384 private static String
toDebugString(final AWTEvent e
) {
385 if (e
instanceof InvocationEvent
) {
387 final Field f
= InvocationEvent
.class.getDeclaredField("runnable");
388 f
.setAccessible(true);
389 Object runnable
= f
.get(e
);
391 return "Invoke Later[" + runnable
.toString() + "]";
393 catch (NoSuchFieldException e1
) {
395 catch (IllegalAccessException e1
) {
402 private void _dispatchEvent(final AWTEvent e
) {
403 if (e
.getID() == MouseEvent
.MOUSE_DRAGGED
) {
404 DnDManagerImpl dndManager
= (DnDManagerImpl
)DnDManager
.getInstance();
405 if (dndManager
!= null) {
406 dndManager
.setLastDropHandler(null);
414 if (processAppActivationEvents(e
)) return;
416 fixStickyFocusedComponents(e
);
418 if (!myPopupManager
.isPopupActive()) {
419 enterSuspendModeIfNeeded(e
);
422 if (e
instanceof KeyEvent
) {
423 myKeyboardBusy
= e
.getID() != KeyEvent
.KEY_RELEASED
|| ((KeyEvent
)e
).getModifiers() != 0;
426 if (typeAheadDispatchToFocusManager(e
)) return;
428 if (e
instanceof WindowEvent
) {
429 ActivityTracker
.getInstance().inc();
433 // Process "idle" and "activity" listeners
434 if (e
instanceof KeyEvent
|| e
instanceof MouseEvent
) {
435 ActivityTracker
.getInstance().inc();
437 synchronized (myLock
) {
438 myIdleRequestsAlarm
.cancelAllRequests();
439 for (Runnable idleListener
: myIdleListeners
) {
440 final MyFireIdleRequest request
= myListener2Request
.get(idleListener
);
441 if (request
== null) {
442 LOG
.error("There is no request for " + idleListener
);
444 int timeout
= request
.getTimeout();
445 myIdleRequestsAlarm
.addRequest(request
, timeout
, ModalityState
.NON_MODAL
);
447 if (KeyEvent
.KEY_PRESSED
== e
.getID() ||
448 KeyEvent
.KEY_TYPED
== e
.getID() ||
449 MouseEvent
.MOUSE_PRESSED
== e
.getID() ||
450 MouseEvent
.MOUSE_RELEASED
== e
.getID() ||
451 MouseEvent
.MOUSE_CLICKED
== e
.getID()) {
452 addIdleTimeCounterRequest();
453 for (Runnable activityListener
: myActivityListeners
) {
454 activityListener
.run();
459 if (myPopupManager
.isPopupActive() && myPopupManager
.dispatch(e
)) {
463 for (EventDispatcher eachDispatcher
: myDispatchers
) {
464 if (eachDispatcher
.dispatch(e
)) {
469 if (e
instanceof InputMethodEvent
) {
470 if (SystemInfo
.isMac
&& myKeyEventDispatcher
.isWaitingForSecondKeyStroke()) {
474 if (e
instanceof InputEvent
&& Patches
.SPECIAL_WINPUT_METHOD_PROCESSING
) {
475 final InputEvent inputEvent
= (InputEvent
)e
;
476 if (!inputEvent
.getComponent().isShowing()) {
480 if (e
instanceof ComponentEvent
&& myWindowManager
!= null) {
481 myWindowManager
.dispatchComponentEvent((ComponentEvent
)e
);
483 if (e
instanceof KeyEvent
) {
484 if (mySuspendMode
|| !myKeyEventDispatcher
.dispatchKeyEvent((KeyEvent
)e
)) {
485 defaultDispatchEvent(e
);
488 ((KeyEvent
)e
).consume();
489 defaultDispatchEvent(e
);
492 else if (e
instanceof MouseEvent
) {
493 if (!myMouseEventDispatcher
.dispatchMouseEvent((MouseEvent
)e
)) {
494 defaultDispatchEvent(e
);
498 defaultDispatchEvent(e
);
502 private static void fixStickyWindow(KeyboardFocusManager mgr
, Window wnd
, String resetMethod
) {
503 Window showingWindow
= wnd
;
505 if (wnd
!= null && !wnd
.isShowing()) {
506 while (showingWindow
!= null) {
507 if (showingWindow
.isShowing()) break;
508 showingWindow
= (Window
)showingWindow
.getParent();
511 if (showingWindow
== null) {
512 final Frame
[] allFrames
= Frame
.getFrames();
513 for (Frame each
: allFrames
) {
514 if (each
.isShowing()) {
515 showingWindow
= each
;
522 if (showingWindow
!= null && showingWindow
!= wnd
) {
523 final Method setActive
=
524 ReflectionUtil
.findMethod(KeyboardFocusManager
.class.getDeclaredMethods(), resetMethod
, Window
.class);
525 if (setActive
!= null) {
527 setActive
.setAccessible(true);
528 setActive
.invoke(mgr
, (Window
)showingWindow
);
530 catch (Exception exc
) {
538 private static void fixStickyFocusedComponents(AWTEvent e
) {
539 if (!(e
instanceof InputEvent
)) return;
541 final KeyboardFocusManager mgr
= KeyboardFocusManager
.getCurrentKeyboardFocusManager();
543 if (Registry
.is("actionSystem.fixStickyFocusedWindows")) {
544 fixStickyWindow(mgr
, mgr
.getActiveWindow(), "setGlobalActiveWindow");
545 fixStickyWindow(mgr
, mgr
.getFocusedWindow(), "setGlobalFocusedWindow");
548 if (Registry
.is("actionSystem.fixNullFocusedComponent")) {
549 final Component focusOwner
= mgr
.getFocusOwner();
550 if (focusOwner
== null) {
551 Window showingWindow
= mgr
.getActiveWindow();
552 if (showingWindow
!= null) {
553 final IdeFocusManager fm
= IdeFocusManager
.findInstanceByComponent(showingWindow
);
554 fm
.doWhenFocusSettlesDown(new Runnable() {
556 if (mgr
.getFocusOwner() == null) {
557 final Application app
= ApplicationManager
.getApplication();
558 if (app
!= null && app
.isActive()) {
559 fm
.requestDefaultFocus(false);
569 private void enterSuspendModeIfNeeded(AWTEvent e
) {
570 if (e
instanceof KeyEvent
) {
571 if (!mySuspendMode
&& shallEnterSuspendMode()) {
577 private boolean shallEnterSuspendMode() {
578 return peekEvent(WindowEvent
.WINDOW_OPENED
) != null;
581 private static boolean processAppActivationEvents(AWTEvent e
) {
582 final Application app
= ApplicationManager
.getApplication();
583 if (!(app
instanceof ApplicationImpl
)) return false;
585 ApplicationImpl appImpl
= (ApplicationImpl
)app
;
587 boolean consumed
= false;
588 if (e
instanceof WindowEvent
) {
589 WindowEvent we
= (WindowEvent
)e
;
590 if (we
.getID() == WindowEvent
.WINDOW_GAINED_FOCUS
&& we
.getWindow() != null) {
591 if (we
.getOppositeWindow() == null && !appImpl
.isActive()) {
592 consumed
= appImpl
.tryToApplyActivationState(true, we
.getWindow());
595 else if (we
.getID() == WindowEvent
.WINDOW_LOST_FOCUS
&& we
.getWindow() != null) {
596 if (we
.getOppositeWindow() == null && appImpl
.isActive()) {
597 consumed
= appImpl
.tryToApplyActivationState(false, we
.getWindow());
606 private void defaultDispatchEvent(final AWTEvent e
) {
608 super.dispatchEvent(e
);
610 catch (ProcessCanceledException pce
) {
613 catch (Throwable exc
) {
614 LOG
.error("Error during dispatching of " + e
, exc
);
618 private static boolean typeAheadDispatchToFocusManager(AWTEvent e
) {
619 if (e
instanceof KeyEvent
) {
620 final KeyEvent event
= (KeyEvent
)e
;
621 if (!event
.isConsumed()) {
622 final IdeFocusManager focusManager
= IdeFocusManager
.findInstanceByComponent(event
.getComponent());
623 return focusManager
.dispatch(event
);
631 public void flushQueue() {
633 AWTEvent event
= peekEvent();
634 if (event
== null) return;
636 AWTEvent event1
= getNextEvent();
637 dispatchEvent(event1
);
639 catch (Exception e
) {
645 public void pumpEventsForHierarchy(Component modalComponent
, Condition
<AWTEvent
> exitCondition
) {
649 event
= getNextEvent();
650 boolean eventOk
= true;
651 if (event
instanceof InputEvent
) {
652 final Object s
= event
.getSource();
653 if (s
instanceof Component
) {
654 Component c
= (Component
)s
;
655 Window modalWindow
= SwingUtilities
.windowForComponent(modalComponent
);
656 while (c
!= null && c
!= modalWindow
) c
= c
.getParent();
659 ((InputEvent
)event
).consume();
665 dispatchEvent(event
);
668 catch (Throwable e
) {
673 while (!exitCondition
.value(event
));
677 public interface EventDispatcher
{
678 boolean dispatch(AWTEvent e
);
682 private final class MyFireIdleRequest
implements Runnable
{
683 private final Runnable myRunnable
;
684 private final int myTimeout
;
687 public MyFireIdleRequest(final Runnable runnable
, final int timeout
) {
689 myRunnable
= runnable
;
695 synchronized (myLock
) {
696 myIdleRequestsAlarm
.addRequest(this, myTimeout
, ModalityState
.NON_MODAL
);
700 public int getTimeout() {
706 private final class ExitSuspendModeRunnable
implements Runnable
{
716 public long getIdleTime() {
721 public IdePopupManager
getPopupManager() {
722 return myPopupManager
;
725 public IdeKeyEventDispatcher
getKeyEventDispatcher() {
726 return myKeyEventDispatcher
;
729 public void blockNextEvents(final MouseEvent e
) {
730 myMouseEventDispatcher
.blockNextEvents(e
);
733 public boolean isSuspendMode() {
734 return mySuspendMode
;
737 public boolean hasFocusEventsPending() {
738 return peekEvent(FocusEvent
.FOCUS_GAINED
) != null || peekEvent(FocusEvent
.FOCUS_LOST
) != null;
741 private boolean isReady() {
742 return !myKeyboardBusy
&& myKeyEventDispatcher
.isReady();
745 public void maybeReady() {
749 private void flushReady() {
750 if (myReady
.isEmpty() || !isReady()) return;
752 Runnable
[] ready
= myReady
.toArray(new Runnable
[myReady
.size()]);
755 for (Runnable each
: ready
) {
760 public void doWhenReady(final Runnable runnable
) {
761 if (EventQueue
.isDispatchThread()) {
762 myReady
.add(runnable
);
765 SwingUtilities
.invokeLater(new Runnable() {
767 myReady
.add(runnable
);