workaround for sticky focused components and windows
[fedora-idea.git] / platform-impl / src / com / intellij / ide / IdeEventQueue.java
blobbe19d17a628d702754ccc44c76bcbbefb3a1223f
1 package com.intellij.ide;
4 import com.intellij.Patches;
5 import com.intellij.concurrency.JobSchedulerImpl;
6 import com.intellij.ide.dnd.DnDManager;
7 import com.intellij.ide.dnd.DnDManagerImpl;
8 import com.intellij.openapi.Disposable;
9 import com.intellij.openapi.application.Application;
10 import com.intellij.openapi.application.ApplicationManager;
11 import com.intellij.openapi.application.ModalityState;
12 import com.intellij.openapi.application.impl.ApplicationImpl;
13 import com.intellij.openapi.diagnostic.Logger;
14 import com.intellij.openapi.keymap.impl.IdeKeyEventDispatcher;
15 import com.intellij.openapi.keymap.impl.IdeMouseEventDispatcher;
16 import com.intellij.openapi.progress.ProcessCanceledException;
17 import com.intellij.openapi.util.Condition;
18 import com.intellij.openapi.util.Disposer;
19 import com.intellij.openapi.util.SystemInfo;
20 import com.intellij.openapi.util.registry.Registry;
21 import com.intellij.openapi.wm.IdeFocusManager;
22 import com.intellij.openapi.wm.ex.WindowManagerEx;
23 import com.intellij.util.Alarm;
24 import com.intellij.util.ProfilingUtil;
25 import com.intellij.util.ReflectionUtil;
26 import com.intellij.util.containers.ContainerUtil;
27 import com.intellij.util.containers.HashMap;
28 import org.jetbrains.annotations.NotNull;
30 import javax.swing.*;
31 import java.awt.*;
32 import java.awt.event.*;
33 import java.beans.PropertyChangeEvent;
34 import java.beans.PropertyChangeListener;
35 import java.lang.reflect.Field;
36 import java.lang.reflect.Method;
37 import java.util.ArrayList;
38 import java.util.LinkedHashSet;
39 import java.util.Map;
40 import java.util.Set;
43 /**
44 * @author Vladimir Kondratyev
45 * @author Anton Katilin
48 public class IdeEventQueue extends EventQueue {
50 private static final Logger LOG = Logger.getInstance("#com.intellij.ide.IdeEventQueue");
52 private static final boolean DEBUG = LOG.isDebugEnabled();
54 /**
55 * Adding/Removing of "idle" listeners should be thread safe.
57 private final Object myLock = new Object();
59 private final ArrayList<Runnable> myIdleListeners = new ArrayList<Runnable>(2);
61 private final ArrayList<Runnable> myActivityListeners = new ArrayList<Runnable>(2);
63 private final Alarm myIdleRequestsAlarm = new Alarm();
65 private final Alarm myIdleTimeCounterAlarm = new Alarm(Alarm.ThreadToUse.SWING_THREAD);
67 private long myIdleTime;
69 private final Map<Runnable, MyFireIdleRequest> myListener2Request = new HashMap<Runnable, MyFireIdleRequest>();
70 // IdleListener -> MyFireIdleRequest
72 private final IdeKeyEventDispatcher myKeyEventDispatcher = new IdeKeyEventDispatcher();
74 private final IdeMouseEventDispatcher myMouseEventDispatcher = new IdeMouseEventDispatcher();
76 private final IdePopupManager myPopupManager = new IdePopupManager();
79 private boolean mySuspendMode;
81 /**
82 * We exit from suspend mode when focus owner changes and no more WindowEvent.WINDOW_OPENED events
83 * <p/>
84 * in the queue. If WINDOW_OPENED event does exists in the queus then we restart the alarm.
87 private Component myFocusOwner;
89 private final Runnable myExitSuspendModeRunnable = new ExitSuspendModeRunnable();
91 /**
92 * We exit from suspend mode when this alarm is triggered and no mode WindowEvent.WINDOW_OPENED
93 * <p/>
94 * events in the queue. If WINDOW_OPENED event does exist then we restart the alarm.
96 private final Alarm mySuspendModeAlarm = new Alarm();
98 /**
99 * Counter of processed events. It is used to assert that data context lives only inside single
100 * <p/>
101 * Swing event.
104 private int myEventCount;
107 private boolean myIsInInputEvent = false;
109 private AWTEvent myCurrentEvent = null;
111 private long myLastActiveTime;
113 private WindowManagerEx myWindowManager;
116 private final Set<EventDispatcher> myDispatchers = new LinkedHashSet<EventDispatcher>();
117 private final Set<EventDispatcher> myPostprocessors = new LinkedHashSet<EventDispatcher>();
119 private static class IdeEventQueueHolder {
120 private static final IdeEventQueue INSTANCE = new IdeEventQueue();
123 public static IdeEventQueue getInstance() {
124 return IdeEventQueueHolder.INSTANCE;
127 private IdeEventQueue() {
128 addIdleTimeCounterRequest();
129 final KeyboardFocusManager keyboardFocusManager = KeyboardFocusManager.getCurrentKeyboardFocusManager();
131 //noinspection HardCodedStringLiteral
132 keyboardFocusManager.addPropertyChangeListener("permanentFocusOwner", new PropertyChangeListener() {
134 public void propertyChange(final PropertyChangeEvent e) {
135 final Application application = ApplicationManager.getApplication();
136 if (application == null) {
138 // We can get focus event before application is initialized
139 return;
141 application.assertIsDispatchThread();
142 final Window focusedWindow = keyboardFocusManager.getFocusedWindow();
143 final Component focusOwner = keyboardFocusManager.getFocusOwner();
144 if (mySuspendMode && focusedWindow != null && focusOwner != null && focusOwner != myFocusOwner && !(focusOwner instanceof Window)) {
145 exitSuspendMode();
152 public void setWindowManager(final WindowManagerEx windowManager) {
153 myWindowManager = windowManager;
157 private void addIdleTimeCounterRequest() {
158 Application application = ApplicationManager.getApplication();
159 if (application != null && application.isUnitTestMode()) return;
161 myIdleTimeCounterAlarm.cancelAllRequests();
162 myLastActiveTime = System.currentTimeMillis();
163 myIdleTimeCounterAlarm.addRequest(new Runnable() {
164 public void run() {
165 myIdleTime += System.currentTimeMillis() - myLastActiveTime;
166 addIdleTimeCounterRequest();
168 }, 20000, ModalityState.NON_MODAL);
172 public boolean shouldNotTypeInEditor() {
173 return myKeyEventDispatcher.isWaitingForSecondKeyStroke() || mySuspendMode;
177 private void enterSuspendMode() {
178 mySuspendMode = true;
179 myFocusOwner = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner();
180 mySuspendModeAlarm.cancelAllRequests();
181 mySuspendModeAlarm.addRequest(myExitSuspendModeRunnable, 750);
186 * Exits supend mode and pumps all suspended events.
189 private void exitSuspendMode() {
190 if (shallEnterSuspendMode()) {
192 // We have to exit from suspend mode (focus owner changes or alarm is triggered) but
194 // WINDOW_OPENED isn't dispatched yet. In this case we have to restart the alarm until
196 // all WINDOW_OPENED event will be processed.
197 mySuspendModeAlarm.cancelAllRequests();
198 mySuspendModeAlarm.addRequest(myExitSuspendModeRunnable, 250);
200 else {
202 // Now we can pump all suspended events.
203 mySuspendMode = false;
204 myFocusOwner = null; // to prevent memory leaks
209 public void addIdleListener(@NotNull final Runnable runnable, final int timeout) {
210 LOG.assertTrue(timeout > 0);
211 synchronized (myLock) {
212 myIdleListeners.add(runnable);
213 final MyFireIdleRequest request = new MyFireIdleRequest(runnable, timeout);
214 myListener2Request.put(runnable, request);
215 myIdleRequestsAlarm.addRequest(request, timeout);
220 public void removeIdleListener(@NotNull final Runnable runnable) {
221 synchronized (myLock) {
222 final boolean wasRemoved = myIdleListeners.remove(runnable);
223 if (!wasRemoved) {
224 LOG.assertTrue(false, "unknown runnable: " + runnable);
226 final MyFireIdleRequest request = myListener2Request.remove(runnable);
227 LOG.assertTrue(request != null);
228 myIdleRequestsAlarm.cancelRequest(request);
233 public void addActivityListener(@NotNull final Runnable runnable) {
234 synchronized (myLock) {
235 myActivityListeners.add(runnable);
239 public void addActivityListener(@NotNull final Runnable runnable, Disposable parentDisposable) {
240 synchronized (myLock) {
241 ContainerUtil.add(runnable, myActivityListeners, parentDisposable);
246 public void removeActivityListener(@NotNull final Runnable runnable) {
247 synchronized (myLock) {
248 final boolean wasRemoved = myActivityListeners.remove(runnable);
249 if (!wasRemoved) {
250 LOG.assertTrue(false, "unknown runnable: " + runnable);
256 public void addDispatcher(final EventDispatcher dispatcher, Disposable parent) {
257 _addProcessor(dispatcher, parent, myDispatchers);
260 public void removeDispatcher(EventDispatcher dispatcher) {
261 myDispatchers.remove(dispatcher);
264 public void addPostprocessor(EventDispatcher dispatcher, Disposable parent) {
265 _addProcessor(dispatcher, parent, myPostprocessors);
268 public void removePostprocessor(EventDispatcher dispatcher) {
269 myPostprocessors.remove(dispatcher);
272 private void _addProcessor(final EventDispatcher dispatcher, Disposable parent, Set<EventDispatcher> set) {
273 set.add(dispatcher);
274 if (parent != null) {
275 Disposer.register(parent, new Disposable() {
276 public void dispose() {
277 removeDispatcher(dispatcher);
283 public int getEventCount() {
284 return myEventCount;
288 public void setEventCount(int evCount) {
289 myEventCount = evCount;
293 public AWTEvent getTrueCurrentEvent() {
294 return myCurrentEvent;
297 //[jeka] commented for performance reasons
301 public void postEvent(final AWTEvent e) {
303 // [vova] sometime people call SwingUtilities.invokeLater(null). To
305 // find such situations we will specially check InvokationEvents
307 try {
309 if (e instanceof InvocationEvent) {
311 //noinspection HardCodedStringLiteral
313 final Field field = InvocationEvent.class.getDeclaredField("runnable");
315 field.setAccessible(true);
317 final Object runnable = field.get(e);
319 if (runnable == null) {
321 //noinspection HardCodedStringLiteral
323 throw new IllegalStateException("InvocationEvent contains null runnable: " + e);
331 catch (final Exception exc) {
333 throw new Error(exc);
337 super.postEvent(e);
343 public void dispatchEvent(final AWTEvent e) {
344 long t = 0;
346 if (DEBUG) {
347 t = System.currentTimeMillis();
348 ProfilingUtil.startCPUProfiling();
351 boolean wasInputEvent = myIsInInputEvent;
352 myIsInInputEvent = e instanceof InputEvent || e instanceof InputMethodEvent || e instanceof WindowEvent || e instanceof ActionEvent;
353 AWTEvent oldEvent = myCurrentEvent;
354 myCurrentEvent = e;
356 JobSchedulerImpl.suspend();
357 try {
358 _dispatchEvent(e);
360 finally {
361 myIsInInputEvent = wasInputEvent;
362 myCurrentEvent = oldEvent;
363 JobSchedulerImpl.resume();
365 for (EventDispatcher each : myPostprocessors) {
366 each.dispatch(e);
369 if (DEBUG) {
370 final long processTime = System.currentTimeMillis() - t;
371 if (processTime > 100) {
372 final String path = ProfilingUtil.captureCPUSnapshot();
374 LOG.debug("Long event: " + processTime + "ms - " + toDebugString(e));
375 LOG.debug("Snapshot taken: " + path);
377 else {
378 ProfilingUtil.stopCPUProfiling();
384 @SuppressWarnings({"ALL"})
385 private static String toDebugString(final AWTEvent e) {
386 if (e instanceof InvocationEvent) {
387 try {
388 final Field f = InvocationEvent.class.getDeclaredField("runnable");
389 f.setAccessible(true);
390 Object runnable = f.get(e);
392 return "Invoke Later[" + runnable.toString() + "]";
394 catch (NoSuchFieldException e1) {
396 catch (IllegalAccessException e1) {
399 return e.toString();
403 private void _dispatchEvent(final AWTEvent e) {
404 if (e.getID() == MouseEvent.MOUSE_DRAGGED) {
405 DnDManagerImpl dndManager = (DnDManagerImpl)DnDManager.getInstance();
406 if (dndManager != null) {
407 dndManager.setLastDropHandler(null);
412 myEventCount++;
415 if (processAppActivationEvents(e)) return;
417 fixStickyFocusedComponents(e);
419 if (!myPopupManager.isPopupActive()) {
420 enterSuspendModeIfNeeded(e);
423 if (typeAheadDispatchToFocusManager(e)) return;
425 if (e instanceof WindowEvent) {
426 ActivityTracker.getInstance().inc();
430 // Process "idle" and "activity" listeners
431 if (e instanceof KeyEvent || e instanceof MouseEvent) {
432 ActivityTracker.getInstance().inc();
434 synchronized (myLock) {
435 myIdleRequestsAlarm.cancelAllRequests();
436 for (Runnable idleListener : myIdleListeners) {
437 final MyFireIdleRequest request = myListener2Request.get(idleListener);
438 if (request == null) {
439 LOG.error("There is no request for " + idleListener);
441 int timeout = request.getTimeout();
442 myIdleRequestsAlarm.addRequest(request, timeout, ModalityState.NON_MODAL);
444 if (KeyEvent.KEY_PRESSED == e.getID() ||
445 KeyEvent.KEY_TYPED == e.getID() ||
446 MouseEvent.MOUSE_PRESSED == e.getID() ||
447 MouseEvent.MOUSE_RELEASED == e.getID() ||
448 MouseEvent.MOUSE_CLICKED == e.getID()) {
449 addIdleTimeCounterRequest();
450 for (Runnable activityListener : myActivityListeners) {
451 activityListener.run();
456 if (myPopupManager.isPopupActive() && myPopupManager.dispatch(e)) {
457 return;
460 for (EventDispatcher eachDispatcher : myDispatchers) {
461 if (eachDispatcher.dispatch(e)) {
462 return;
466 if (e instanceof InputMethodEvent) {
467 if (SystemInfo.isMac && myKeyEventDispatcher.isWaitingForSecondKeyStroke()) {
468 return;
471 if (e instanceof InputEvent && Patches.SPECIAL_WINPUT_METHOD_PROCESSING) {
472 final InputEvent inputEvent = (InputEvent)e;
473 if (!inputEvent.getComponent().isShowing()) {
474 return;
477 if (e instanceof ComponentEvent && myWindowManager != null) {
478 myWindowManager.dispatchComponentEvent((ComponentEvent)e);
480 if (e instanceof KeyEvent) {
481 if (mySuspendMode || !myKeyEventDispatcher.dispatchKeyEvent((KeyEvent)e)) {
482 defaultDispatchEvent(e);
484 else {
485 ((KeyEvent)e).consume();
486 defaultDispatchEvent(e);
489 else if (e instanceof MouseEvent) {
490 if (!myMouseEventDispatcher.dispatchMouseEvent((MouseEvent)e)) {
491 defaultDispatchEvent(e);
494 else {
495 defaultDispatchEvent(e);
499 private void fixStickyFocusedComponents(AWTEvent e) {
500 if (!(e instanceof InputEvent)) return;
502 final KeyboardFocusManager mgr = KeyboardFocusManager.getCurrentKeyboardFocusManager();
503 final Window wnd = mgr.getActiveWindow();
504 Window showingWindow = wnd;
506 if (Registry.is("actionSystem.fixStickyFocusedWindows")) {
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;
518 break;
524 if (showingWindow != null && showingWindow != wnd) {
525 final Method setActive =
526 ReflectionUtil.findMethod(KeyboardFocusManager.class.getDeclaredMethods(), "setGlobalActiveWindow", Window.class);
527 if (setActive != null) {
528 try {
529 setActive.setAccessible(true);
530 setActive.invoke(mgr, (Window)showingWindow);
532 catch (Exception exc) {
533 LOG.info(exc);
540 if (Registry.is("actionSystem.fixNullFocusedComponent")) {
541 final Component focusOwner = mgr.getFocusOwner();
542 if (focusOwner == null) {
543 if (showingWindow != null) {
544 final IdeFocusManager fm = IdeFocusManager.findInstanceByComponent(showingWindow);
545 fm.doWhenFocusSettlesDown(new Runnable() {
546 public void run() {
547 if (mgr.getFocusOwner() == null) {
548 final Application app = ApplicationManager.getApplication();
549 if (app != null && app.isActive()) {
550 fm.requestDefaultFocus(false);
560 private void enterSuspendModeIfNeeded(AWTEvent e) {
561 if (e instanceof KeyEvent) {
562 if (!mySuspendMode && shallEnterSuspendMode()) {
563 enterSuspendMode();
568 private boolean shallEnterSuspendMode() {
569 return peekEvent(WindowEvent.WINDOW_OPENED) != null;
572 private static boolean processAppActivationEvents(AWTEvent e) {
573 final Application app = ApplicationManager.getApplication();
574 if (!(app instanceof ApplicationImpl)) return false;
576 ApplicationImpl appImpl = (ApplicationImpl)app;
578 boolean consumed = false;
579 if (e instanceof WindowEvent) {
580 WindowEvent we = (WindowEvent)e;
581 if (we.getID() == WindowEvent.WINDOW_GAINED_FOCUS && we.getWindow() != null) {
582 if (we.getOppositeWindow() == null && !appImpl.isActive()) {
583 consumed = appImpl.tryToApplyActivationState(true, we.getWindow());
586 else if (we.getID() == WindowEvent.WINDOW_LOST_FOCUS && we.getWindow() != null) {
587 if (we.getOppositeWindow() == null && appImpl.isActive()) {
588 consumed = appImpl.tryToApplyActivationState(false, we.getWindow());
593 return consumed && Patches.REQUEST_FOCUS_MAY_ACTIVATE_APP;
597 private void defaultDispatchEvent(final AWTEvent e) {
598 try {
599 super.dispatchEvent(e);
601 catch (ProcessCanceledException pce) {
602 throw pce;
604 catch (Throwable exc) {
605 LOG.error("Error during dispatching of " + e, exc);
609 private static boolean typeAheadDispatchToFocusManager(AWTEvent e) {
610 if (e instanceof KeyEvent) {
611 final KeyEvent event = (KeyEvent)e;
612 if (!event.isConsumed()) {
613 final IdeFocusManager focusManager = IdeFocusManager.findInstanceByComponent(event.getComponent());
614 return focusManager.dispatch(event);
618 return false;
622 public void flushQueue() {
623 while (true) {
624 AWTEvent event = peekEvent();
625 if (event == null) return;
626 try {
627 AWTEvent event1 = getNextEvent();
628 dispatchEvent(event1);
630 catch (Exception e) {
631 LOG.error(e); //?
636 public void pumpEventsForHierarchy(Component modalComponent, Condition<AWTEvent> exitCondition) {
637 AWTEvent event;
638 do {
639 try {
640 event = getNextEvent();
641 boolean eventOk = true;
642 if (event instanceof InputEvent) {
643 final Object s = event.getSource();
644 if (s instanceof Component) {
645 Component c = (Component)s;
646 Window modalWindow = SwingUtilities.windowForComponent(modalComponent);
647 while (c != null && c != modalWindow) c = c.getParent();
648 if (c == null) {
649 eventOk = false;
650 ((InputEvent)event).consume();
655 if (eventOk) {
656 dispatchEvent(event);
659 catch (Throwable e) {
660 LOG.error(e);
661 event = null;
664 while (!exitCondition.value(event));
668 public interface EventDispatcher {
669 boolean dispatch(AWTEvent e);
673 private final class MyFireIdleRequest implements Runnable {
674 private final Runnable myRunnable;
675 private final int myTimeout;
678 public MyFireIdleRequest(final Runnable runnable, final int timeout) {
679 myTimeout = timeout;
680 myRunnable = runnable;
684 public void run() {
685 myRunnable.run();
686 synchronized (myLock) {
687 myIdleRequestsAlarm.addRequest(this, myTimeout, ModalityState.NON_MODAL);
691 public int getTimeout() {
692 return myTimeout;
697 private final class ExitSuspendModeRunnable implements Runnable {
699 public void run() {
700 if (mySuspendMode) {
701 exitSuspendMode();
707 public long getIdleTime() {
708 return myIdleTime;
712 public IdePopupManager getPopupManager() {
713 return myPopupManager;
716 public IdeKeyEventDispatcher getKeyEventDispatcher() {
717 return myKeyEventDispatcher;
720 public void blockNextEvents(final MouseEvent e) {
721 myMouseEventDispatcher.blockNextEvents(e);
724 public boolean isSuspendMode() {
725 return mySuspendMode;