sticky documentation popup [take 1]
[fedora-idea.git] / platform / platform-impl / src / com / intellij / ide / IdeEventQueue.java
blob4ad3015d48fe1d703153d6db4f56fffd77f3cf81
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 final ToolkitBugsProcessor myToolkitBugsProcessor = new ToolkitBugsProcessor();
89 private boolean mySuspendMode;
91 /**
92 * We exit from suspend mode when focus owner changes and no more WindowEvent.WINDOW_OPENED events
93 * <p/>
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
103 * <p/>
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
110 * <p/>
111 * Swing event.
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
152 return;
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)) {
158 exitSuspendMode();
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() {
177 public void run() {
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);
213 else {
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);
236 if (!wasRemoved) {
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);
262 if (!wasRemoved) {
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) {
290 set.add(dispatcher);
291 if (parent != null) {
292 Disposer.register(parent, new Disposable() {
293 public void dispose() {
294 removeDispatcher(dispatcher);
300 public int getEventCount() {
301 return myEventCount;
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
324 try {
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);
354 super.postEvent(e);
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;
364 myCurrentEvent = e;
366 JobSchedulerImpl.suspend();
367 try {
368 _dispatchEvent(e);
370 finally {
371 myIsInInputEvent = wasInputEvent;
372 myCurrentEvent = oldEvent;
373 JobSchedulerImpl.resume();
375 for (EventDispatcher each : myPostprocessors) {
376 each.dispatch(e);
379 if (e instanceof KeyEvent) {
380 maybeReady();
385 @SuppressWarnings({"ALL"})
386 private static String toDebugString(final AWTEvent e) {
387 if (e instanceof InvocationEvent) {
388 try {
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) {
400 return e.toString();
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);
413 myEventCount++;
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)) {
462 return;
465 for (EventDispatcher eachDispatcher : myDispatchers) {
466 if (eachDispatcher.dispatch(e)) {
467 return;
471 if (e instanceof InputMethodEvent) {
472 if (SystemInfo.isMac && myKeyEventDispatcher.isWaitingForSecondKeyStroke()) {
473 return;
476 if (e instanceof InputEvent && Patches.SPECIAL_WINPUT_METHOD_PROCESSING) {
477 final InputEvent inputEvent = (InputEvent)e;
478 if (!inputEvent.getComponent().isShowing()) {
479 return;
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);
489 else {
490 ((KeyEvent)e).consume();
491 defaultDispatchEvent(e);
494 else if (e instanceof MouseEvent) {
495 if (!myMouseEventDispatcher.dispatchMouseEvent((MouseEvent)e)) {
496 defaultDispatchEvent(e);
499 else {
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;
518 break;
524 if (showingWindow != null && showingWindow != wnd) {
525 final Method setActive =
526 ReflectionUtil.findMethod(KeyboardFocusManager.class.getDeclaredMethods(), resetMethod, 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 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() {
557 public void run() {
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()) {
574 enterSuspendMode();
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());
604 return false;
608 private void defaultDispatchEvent(final AWTEvent e) {
609 try {
610 super.dispatchEvent(e);
612 catch (ProcessCanceledException pce) {
613 throw 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);
631 return false;
635 public void flushQueue() {
636 while (true) {
637 AWTEvent event = peekEvent();
638 if (event == null) return;
639 try {
640 AWTEvent event1 = getNextEvent();
641 dispatchEvent(event1);
643 catch (Exception e) {
644 LOG.error(e); //?
649 public void pumpEventsForHierarchy(Component modalComponent, Condition<AWTEvent> exitCondition) {
650 AWTEvent event;
651 do {
652 try {
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();
661 if (c == null) {
662 eventOk = false;
663 ((InputEvent)event).consume();
668 if (eventOk) {
669 dispatchEvent(event);
672 catch (Throwable e) {
673 LOG.error(e);
674 event = null;
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) {
692 myTimeout = timeout;
693 myRunnable = runnable;
697 public void run() {
698 myRunnable.run();
699 synchronized (myLock) {
700 myIdleRequestsAlarm.addRequest(this, myTimeout, ModalityState.NON_MODAL);
704 public int getTimeout() {
705 return myTimeout;
710 private final class ExitSuspendModeRunnable implements Runnable {
712 public void run() {
713 if (mySuspendMode) {
714 exitSuspendMode();
720 public long getIdleTime() {
721 return myIdleTime;
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() {
750 flushReady();
753 private void flushReady() {
754 if (myReady.isEmpty() || !isReady()) return;
756 Runnable[] ready = myReady.toArray(new Runnable[myReady.size()]);
757 myReady.clear();
759 for (Runnable each : ready) {
760 each.run();
764 public void doWhenReady(final Runnable runnable) {
765 if (EventQueue.isDispatchThread()) {
766 myReady.add(runnable);
767 maybeReady();
768 } else {
769 SwingUtilities.invokeLater(new Runnable() {
770 public void run() {
771 myReady.add(runnable);
772 maybeReady();