Merge branch 'master' of git@git.labs.intellij.net:idea/community into tool-window
[fedora-idea.git] / platform / platform-impl / src / com / intellij / ide / IdeEventQueue.java
blob8eccf233039cafdf03a501e73788f578162f5339
1 /*
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;
44 import javax.swing.*;
45 import java.awt.*;
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;
51 import java.util.*;
54 /**
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");
62 /**
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;
89 /**
90 * We exit from suspend mode when focus owner changes and no more WindowEvent.WINDOW_OPENED events
91 * <p/>
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();
99 /**
100 * We exit from suspend mode when this alarm is triggered and no mode WindowEvent.WINDOW_OPENED
101 * <p/>
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
108 * <p/>
109 * Swing event.
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
150 return;
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)) {
156 exitSuspendMode();
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() {
175 public void run() {
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);
211 else {
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);
234 if (!wasRemoved) {
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);
260 if (!wasRemoved) {
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) {
288 set.add(dispatcher);
289 if (parent != null) {
290 Disposer.register(parent, new Disposable() {
291 public void dispose() {
292 removeDispatcher(dispatcher);
298 public int getEventCount() {
299 return myEventCount;
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
322 try {
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);
352 super.postEvent(e);
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;
362 myCurrentEvent = e;
364 JobSchedulerImpl.suspend();
365 try {
366 _dispatchEvent(e);
368 finally {
369 myIsInInputEvent = wasInputEvent;
370 myCurrentEvent = oldEvent;
371 JobSchedulerImpl.resume();
373 for (EventDispatcher each : myPostprocessors) {
374 each.dispatch(e);
377 if (e instanceof KeyEvent) {
378 maybeReady();
383 @SuppressWarnings({"ALL"})
384 private static String toDebugString(final AWTEvent e) {
385 if (e instanceof InvocationEvent) {
386 try {
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) {
398 return e.toString();
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);
411 myEventCount++;
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)) {
460 return;
463 for (EventDispatcher eachDispatcher : myDispatchers) {
464 if (eachDispatcher.dispatch(e)) {
465 return;
469 if (e instanceof InputMethodEvent) {
470 if (SystemInfo.isMac && myKeyEventDispatcher.isWaitingForSecondKeyStroke()) {
471 return;
474 if (e instanceof InputEvent && Patches.SPECIAL_WINPUT_METHOD_PROCESSING) {
475 final InputEvent inputEvent = (InputEvent)e;
476 if (!inputEvent.getComponent().isShowing()) {
477 return;
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);
487 else {
488 ((KeyEvent)e).consume();
489 defaultDispatchEvent(e);
492 else if (e instanceof MouseEvent) {
493 if (!myMouseEventDispatcher.dispatchMouseEvent((MouseEvent)e)) {
494 defaultDispatchEvent(e);
497 else {
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;
516 break;
522 if (showingWindow != null && showingWindow != wnd) {
523 final Method setActive =
524 ReflectionUtil.findMethod(KeyboardFocusManager.class.getDeclaredMethods(), resetMethod, Window.class);
525 if (setActive != null) {
526 try {
527 setActive.setAccessible(true);
528 setActive.invoke(mgr, (Window)showingWindow);
530 catch (Exception exc) {
531 LOG.info(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() {
555 public void run() {
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()) {
572 enterSuspendMode();
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());
602 return false;
606 private void defaultDispatchEvent(final AWTEvent e) {
607 try {
608 super.dispatchEvent(e);
610 catch (ProcessCanceledException pce) {
611 throw 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);
627 return false;
631 public void flushQueue() {
632 while (true) {
633 AWTEvent event = peekEvent();
634 if (event == null) return;
635 try {
636 AWTEvent event1 = getNextEvent();
637 dispatchEvent(event1);
639 catch (Exception e) {
640 LOG.error(e); //?
645 public void pumpEventsForHierarchy(Component modalComponent, Condition<AWTEvent> exitCondition) {
646 AWTEvent event;
647 do {
648 try {
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();
657 if (c == null) {
658 eventOk = false;
659 ((InputEvent)event).consume();
664 if (eventOk) {
665 dispatchEvent(event);
668 catch (Throwable e) {
669 LOG.error(e);
670 event = null;
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) {
688 myTimeout = timeout;
689 myRunnable = runnable;
693 public void run() {
694 myRunnable.run();
695 synchronized (myLock) {
696 myIdleRequestsAlarm.addRequest(this, myTimeout, ModalityState.NON_MODAL);
700 public int getTimeout() {
701 return myTimeout;
706 private final class ExitSuspendModeRunnable implements Runnable {
708 public void run() {
709 if (mySuspendMode) {
710 exitSuspendMode();
716 public long getIdleTime() {
717 return myIdleTime;
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() {
746 flushReady();
749 private void flushReady() {
750 if (myReady.isEmpty() || !isReady()) return;
752 Runnable[] ready = myReady.toArray(new Runnable[myReady.size()]);
753 myReady.clear();
755 for (Runnable each : ready) {
756 each.run();
760 public void doWhenReady(final Runnable runnable) {
761 if (EventQueue.isDispatchThread()) {
762 myReady.add(runnable);
763 maybeReady();
764 } else {
765 SwingUtilities.invokeLater(new Runnable() {
766 public void run() {
767 myReady.add(runnable);
768 maybeReady();