allow to add idle listener from any thread (http://crucible.labs.intellij.net/cru...
[idea/community.git] / platform / platform-impl / src / com / intellij / ide / IdeEventQueue.java
1 /*
2  * Copyright 2000-2015 JetBrains s.r.o.
3  *
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
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16 package com.intellij.ide;
17
18 import com.intellij.ide.dnd.DnDManager;
19 import com.intellij.ide.dnd.DnDManagerImpl;
20 import com.intellij.ide.plugins.PluginManager;
21 import com.intellij.ide.ui.UISettings;
22 import com.intellij.idea.IdeaApplication;
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.ex.ApplicationEx;
28 import com.intellij.openapi.application.impl.LaterInvocator;
29 import com.intellij.openapi.diagnostic.FrequentEventDetector;
30 import com.intellij.openapi.diagnostic.Logger;
31 import com.intellij.openapi.keymap.KeyboardSettingsExternalizable;
32 import com.intellij.openapi.keymap.impl.IdeKeyEventDispatcher;
33 import com.intellij.openapi.keymap.impl.IdeMouseEventDispatcher;
34 import com.intellij.openapi.keymap.impl.KeyState;
35 import com.intellij.openapi.util.Condition;
36 import com.intellij.openapi.util.Disposer;
37 import com.intellij.openapi.util.ExpirableRunnable;
38 import com.intellij.openapi.util.SystemInfo;
39 import com.intellij.openapi.util.registry.Registry;
40 import com.intellij.openapi.wm.IdeFocusManager;
41 import com.intellij.openapi.wm.IdeFrame;
42 import com.intellij.openapi.wm.WindowManager;
43 import com.intellij.openapi.wm.ex.WindowManagerEx;
44 import com.intellij.openapi.wm.impl.FocusManagerImpl;
45 import com.intellij.util.Alarm;
46 import com.intellij.util.ReflectionUtil;
47 import com.intellij.util.containers.ContainerUtil;
48 import com.intellij.util.containers.HashMap;
49 import com.intellij.util.ui.MouseEventAdapter;
50 import com.intellij.util.ui.UIUtil;
51 import org.jetbrains.annotations.NotNull;
52 import org.jetbrains.annotations.Nullable;
53
54 import javax.swing.*;
55 import javax.swing.plaf.basic.ComboPopup;
56 import java.awt.*;
57 import java.awt.event.*;
58 import java.beans.PropertyChangeEvent;
59 import java.beans.PropertyChangeListener;
60 import java.lang.reflect.Field;
61 import java.lang.reflect.Method;
62 import java.util.LinkedHashSet;
63 import java.util.List;
64 import java.util.Map;
65 import java.util.Set;
66
67 /**
68  * @author Vladimir Kondratyev
69  * @author Anton Katilin
70  */
71 public class IdeEventQueue extends EventQueue {
72   private static final Logger LOG = Logger.getInstance("#com.intellij.ide.IdeEventQueue");
73
74   /**
75    * Adding/Removing of "idle" listeners should be thread safe.
76    */
77   private final Object myLock = new Object();
78
79   private final List<Runnable> myIdleListeners = ContainerUtil.createLockFreeCopyOnWriteList();
80
81   private final List<Runnable> myActivityListeners = ContainerUtil.createLockFreeCopyOnWriteList();
82
83   private final Alarm myIdleRequestsAlarm = new Alarm();
84
85   private final Alarm myIdleTimeCounterAlarm = new Alarm();
86
87   private long myIdleTime;
88
89   private final Map<Runnable, MyFireIdleRequest> myListener2Request = new HashMap<Runnable, MyFireIdleRequest>();
90   // IdleListener -> MyFireIdleRequest
91
92   private final IdeKeyEventDispatcher myKeyEventDispatcher = new IdeKeyEventDispatcher(this);
93
94   private final IdeMouseEventDispatcher myMouseEventDispatcher = new IdeMouseEventDispatcher();
95
96   private final IdePopupManager myPopupManager = new IdePopupManager();
97
98
99   private final ToolkitBugsProcessor myToolkitBugsProcessor = new ToolkitBugsProcessor();
100
101   private boolean mySuspendMode;
102
103   /**
104    * We exit from suspend mode when focus owner changes and no more WindowEvent.WINDOW_OPENED events
105    * <p/>
106    * in the queue. If WINDOW_OPENED event does exists in the queues then we restart the alarm.
107    */
108   private Component myFocusOwner;
109
110   private final Runnable myExitSuspendModeRunnable = new ExitSuspendModeRunnable();
111
112   /**
113    * We exit from suspend mode when this alarm is triggered and no mode WindowEvent.WINDOW_OPENED
114    * <p/>
115    * events in the queue. If WINDOW_OPENED event does exist then we restart the alarm.
116    */
117   private final Alarm mySuspendModeAlarm = new Alarm();
118
119   /**
120    * Counter of processed events. It is used to assert that data context lives only inside single
121    * <p/>
122    * Swing event.
123    */
124   private int myEventCount;
125
126   private boolean myIsInInputEvent;
127
128   private AWTEvent myCurrentEvent;
129
130   private long myLastActiveTime;
131
132   private WindowManagerEx myWindowManager;
133
134   private final Set<EventDispatcher> myDispatchers = new LinkedHashSet<EventDispatcher>();
135   private final Set<EventDispatcher> myPostProcessors = new LinkedHashSet<EventDispatcher>();
136   private final Set<Runnable> myReady = ContainerUtil.newHashSet();
137
138   private boolean myKeyboardBusy;
139   private boolean myDispatchingFocusEvent;
140
141   private int myInputMethodLock;
142
143   private static class IdeEventQueueHolder {
144     private static final IdeEventQueue INSTANCE = new IdeEventQueue();
145   }
146
147   public static IdeEventQueue getInstance() {
148     return IdeEventQueueHolder.INSTANCE;
149   }
150
151   private IdeEventQueue() {
152     EventQueue systemEventQueue = Toolkit.getDefaultToolkit().getSystemEventQueue();
153     assert !(systemEventQueue instanceof IdeEventQueue) : systemEventQueue;
154     systemEventQueue.push(this);
155     addIdleTimeCounterRequest();
156
157     final KeyboardFocusManager keyboardFocusManager = KeyboardFocusManager.getCurrentKeyboardFocusManager();
158     keyboardFocusManager.addPropertyChangeListener("permanentFocusOwner", new PropertyChangeListener() {
159
160       @Override
161       public void propertyChange(@NotNull final PropertyChangeEvent e) {
162         final Application application = ApplicationManager.getApplication();
163         if (application == null) {
164           // We can get focus event before application is initialized
165           return;
166         }
167         application.assertIsDispatchThread();
168         final Window focusedWindow = keyboardFocusManager.getFocusedWindow();
169         final Component focusOwner = keyboardFocusManager.getFocusOwner();
170         if (mySuspendMode && focusedWindow != null && focusOwner != null && focusOwner != myFocusOwner && !(focusOwner instanceof Window)) {
171           exitSuspendMode();
172         }
173       }
174     });
175
176     addDispatcher(new WindowsAltSuppressor(), null);
177   }
178
179
180   public void setWindowManager(final WindowManagerEx windowManager) {
181     myWindowManager = windowManager;
182   }
183
184
185   private void addIdleTimeCounterRequest() {
186     if (isTestMode()) return;
187
188     myIdleTimeCounterAlarm.cancelAllRequests();
189     myLastActiveTime = System.currentTimeMillis();
190     myIdleTimeCounterAlarm.addRequest(new Runnable() {
191       @Override
192       public void run() {
193         myIdleTime += System.currentTimeMillis() - myLastActiveTime;
194         addIdleTimeCounterRequest();
195       }
196     }, 20000, ModalityState.NON_MODAL);
197   }
198
199   /**
200    * This class performs special processing in order to have {@link #getIdleTime()} return more or less up-to-date data.
201    * <p/>
202    * This method allows to stop that processing (convenient in non-intellij environment like upsource).
203    */
204   @SuppressWarnings("unused") // Used in upsource.
205   public void stopIdleTimeCalculation() {
206     myIdleTimeCounterAlarm.cancelAllRequests();
207   }
208
209   public boolean shouldNotTypeInEditor() {
210     return myKeyEventDispatcher.isWaitingForSecondKeyStroke() || mySuspendMode;
211   }
212
213
214   private void enterSuspendMode() {
215     mySuspendMode = true;
216     myFocusOwner = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner();
217     mySuspendModeAlarm.cancelAllRequests();
218     mySuspendModeAlarm.addRequest(myExitSuspendModeRunnable, 750);
219   }
220
221   /**
222    * Exits suspend mode and pumps all suspended events.
223    */
224   private void exitSuspendMode() {
225     if (shallEnterSuspendMode()) {
226       // We have to exit from suspend mode (focus owner changes or alarm is triggered) but
227       // WINDOW_OPENED isn't dispatched yet. In this case we have to restart the alarm until
228       // all WINDOW_OPENED event will be processed.
229       mySuspendModeAlarm.cancelAllRequests();
230       mySuspendModeAlarm.addRequest(myExitSuspendModeRunnable, 250);
231     }
232     else {
233       // Now we can pump all suspended events.
234       mySuspendMode = false;
235       myFocusOwner = null; // to prevent memory leaks
236     }
237   }
238
239
240   public void addIdleListener(@NotNull final Runnable runnable, final int timeout) {
241     LOG.assertTrue(timeout > 0);
242     synchronized (myLock) {
243       myIdleListeners.add(runnable);
244       final MyFireIdleRequest request = new MyFireIdleRequest(runnable, timeout);
245       myListener2Request.put(runnable, request);
246       UIUtil.invokeLaterIfNeeded(new Runnable() {
247         @Override
248         public void run() {
249           myIdleRequestsAlarm.addRequest(request, timeout);
250         }
251       });
252     }
253   }
254
255   public void removeIdleListener(@NotNull final Runnable runnable) {
256     synchronized (myLock) {
257       final boolean wasRemoved = myIdleListeners.remove(runnable);
258       if (!wasRemoved) {
259         LOG.error("unknown runnable: " + runnable);
260       }
261       final MyFireIdleRequest request = myListener2Request.remove(runnable);
262       LOG.assertTrue(request != null);
263       myIdleRequestsAlarm.cancelRequest(request);
264     }
265   }
266
267   /** @deprecated use {@link #addActivityListener(Runnable, Disposable)} (to be removed in IDEA 17) */
268   @SuppressWarnings("unused")
269   public void addActivityListener(@NotNull final Runnable runnable) {
270     synchronized (myLock) {
271       myActivityListeners.add(runnable);
272     }
273   }
274
275   public void addActivityListener(@NotNull final Runnable runnable, Disposable parentDisposable) {
276     synchronized (myLock) {
277       ContainerUtil.add(runnable, myActivityListeners, parentDisposable);
278     }
279   }
280
281   public void removeActivityListener(@NotNull final Runnable runnable) {
282     synchronized (myLock) {
283       myActivityListeners.remove(runnable);
284     }
285   }
286
287
288   public void addDispatcher(final EventDispatcher dispatcher, Disposable parent) {
289     _addProcessor(dispatcher, parent, myDispatchers);
290   }
291
292   public void removeDispatcher(EventDispatcher dispatcher) {
293     myDispatchers.remove(dispatcher);
294   }
295
296   public boolean containsDispatcher(EventDispatcher dispatcher) {
297     return myDispatchers.contains(dispatcher);
298   }
299
300   public void addPostprocessor(EventDispatcher dispatcher, @Nullable Disposable parent) {
301     _addProcessor(dispatcher, parent, myPostProcessors);
302   }
303
304   public void removePostprocessor(EventDispatcher dispatcher) {
305     myPostProcessors.remove(dispatcher);
306   }
307
308   private static void _addProcessor(final EventDispatcher dispatcher, Disposable parent, final Set<EventDispatcher> set) {
309     set.add(dispatcher);
310     if (parent != null) {
311       Disposer.register(parent, new Disposable() {
312         @Override
313         public void dispose() {
314           set.remove(dispatcher);
315         }
316       });
317     }
318   }
319
320   public int getEventCount() {
321     return myEventCount;
322   }
323
324   public void setEventCount(int evCount) {
325     myEventCount = evCount;
326   }
327
328   public AWTEvent getTrueCurrentEvent() {
329     return myCurrentEvent;
330   }
331
332   private static class InertialMouseRouter {
333     private static final int MOUSE_WHEEL_RESTART_THRESHOLD = 50;
334     private static Component wheelDestinationComponent;
335     private static long lastMouseWheel;
336
337     private static AWTEvent changeSourceIfNeeded(AWTEvent awtEvent) {
338       if (SystemInfo.isMac && Registry.is("ide.inertial.mouse.fix") && awtEvent instanceof MouseWheelEvent) {
339         MouseWheelEvent mwe = (MouseWheelEvent) awtEvent;
340         if (mwe.getWhen() - lastMouseWheel > MOUSE_WHEEL_RESTART_THRESHOLD) {
341           wheelDestinationComponent = SwingUtilities.getDeepestComponentAt(mwe.getComponent(), mwe.getX(), mwe.getY());
342         }
343         lastMouseWheel = System.currentTimeMillis();
344
345         int modifiers = mwe.getModifiers() | mwe.getModifiersEx();
346         return MouseEventAdapter.convert(mwe, wheelDestinationComponent, mwe.getID(), lastMouseWheel, modifiers, mwe.getX(), mwe.getY());
347       }
348       return awtEvent;
349     }
350   }
351
352   private static boolean ourAppIsLoaded;
353
354   private static boolean appIsLoaded() {
355     if (ourAppIsLoaded) return true;
356     boolean loaded = IdeaApplication.isLoaded();
357     if (loaded) ourAppIsLoaded = true;
358     return loaded;
359   }
360
361   @Override
362   public void dispatchEvent(@NotNull AWTEvent e) {
363     if (!appIsLoaded()) {
364       try {
365         super.dispatchEvent(e);
366       }
367       catch (Throwable t) {
368         processException(t);
369       }
370       return;
371     }
372
373     e = InertialMouseRouter.changeSourceIfNeeded(e);
374
375     e = fixNonEnglishKeyboardLayouts(e);
376
377     e = mapEvent(e);
378
379     boolean wasInputEvent = myIsInInputEvent;
380     myIsInInputEvent = e instanceof InputEvent || e instanceof InputMethodEvent || e instanceof WindowEvent || e instanceof ActionEvent;
381     AWTEvent oldEvent = myCurrentEvent;
382     myCurrentEvent = e;
383
384     try {
385       _dispatchEvent(e, false);
386     }
387     catch (Throwable t) {
388       processException(t);
389     }
390     finally {
391       myIsInInputEvent = wasInputEvent;
392       myCurrentEvent = oldEvent;
393
394       for (EventDispatcher each : myPostProcessors) {
395         each.dispatch(e);
396       }
397
398       if (e instanceof KeyEvent) {
399         maybeReady();
400       }
401     }
402   }
403
404   private void processException(Throwable t) {
405     if (!myToolkitBugsProcessor.process(t)) {
406       PluginManager.processException(t);
407     }
408   }
409
410   private static int ctrlIsPressedCount;
411   private static boolean leftAltIsPressed;
412   //private static boolean altGrIsPressed = false;
413
414   private static AWTEvent fixNonEnglishKeyboardLayouts(AWTEvent e) {
415     if (!(e instanceof KeyEvent)) return e;
416
417     KeyboardSettingsExternalizable externalizable = KeyboardSettingsExternalizable.getInstance();
418     if (externalizable == null || !externalizable.isNonEnglishKeyboardSupportEnabled()) return e;
419
420     KeyEvent ke = (KeyEvent)e;
421
422     // Try to get it from editor
423     Component sourceComponent = WindowManagerEx.getInstanceEx().getMostRecentFocusedWindow();
424
425     if (ke.getID() == KeyEvent.KEY_PRESSED) {
426       switch (ke.getKeyCode()) {
427         case KeyEvent.VK_CONTROL:
428           if ((ke.getModifiersEx() & (InputEvent.ALT_DOWN_MASK | InputEvent.CTRL_DOWN_MASK)) != (InputEvent.ALT_DOWN_MASK | InputEvent.CTRL_DOWN_MASK)) {
429             ctrlIsPressedCount++;
430           }
431           break;
432         case KeyEvent.VK_ALT:
433           if (ke.getKeyLocation() == KeyEvent.KEY_LOCATION_LEFT) {
434             if ((ke.getModifiersEx() & (InputEvent.ALT_DOWN_MASK | InputEvent.CTRL_DOWN_MASK)) != (InputEvent.ALT_DOWN_MASK | InputEvent.CTRL_DOWN_MASK)) {
435               leftAltIsPressed = true;
436             }
437           }
438           break;
439       }
440     }
441     else if (ke.getID() == KeyEvent.KEY_RELEASED) {
442       switch (ke.getKeyCode()) {
443         case KeyEvent.VK_CONTROL:
444           ctrlIsPressedCount--;
445           break;
446         case KeyEvent.VK_ALT:
447           if (ke.getKeyLocation() == KeyEvent.KEY_LOCATION_LEFT) {
448             leftAltIsPressed = false;
449           }
450           break;
451       }
452     }
453
454     if (!leftAltIsPressed && KeyboardSettingsExternalizable.getInstance().isUkrainianKeyboard(sourceComponent)) {
455       if ('ґ' == ke.getKeyChar() || ke.getKeyCode() == KeyEvent.VK_U) {
456         ke = new KeyEvent(ke.getComponent(), ke.getID(), ke.getWhen(), 0,
457                           KeyEvent.VK_UNDEFINED, 'ґ', ke.getKeyLocation());
458         ke.setKeyCode(KeyEvent.VK_U);
459         ke.setKeyChar('ґ');
460         return ke;
461       }
462     }
463
464     Integer keyCodeFromChar = CharToVKeyMap.get(ke.getKeyChar());
465     if (keyCodeFromChar != null) {
466       if (keyCodeFromChar != ke.getKeyCode()) {
467         // non-english layout
468         ke.setKeyCode(keyCodeFromChar);
469       }
470
471       //for (int i = 0; sourceComponent == null && i < WindowManagerEx.getInstanceEx().getAllProjectFrames().length; i++) {
472       //  sourceComponent = WindowManagerEx.getInstanceEx().getAllProjectFrames()[i].getComponent();
473       //}
474
475       if (sourceComponent != null) {
476         if (KeyboardSettingsExternalizable.isSupportedKeyboardLayout(sourceComponent)) {
477           if ((ke.getModifiersEx() & (InputEvent.ALT_DOWN_MASK | InputEvent.CTRL_DOWN_MASK)) != 0 /*&& ke.getKeyLocation() == KeyEvent.KEY_LOCATION_RIGHT*/) {
478             // On German keyboard layout on Windows  we are getting on key press
479             // ctrl + alt instead of AltGr
480
481             int modifiers = ke.getModifiersEx() ^ InputEvent.ALT_DOWN_MASK ^ InputEvent.CTRL_DOWN_MASK;
482
483             if (ctrlIsPressedCount > 1) {
484               modifiers |= InputEvent.CTRL_DOWN_MASK;
485             }
486
487             if (leftAltIsPressed) {
488               modifiers |= InputEvent.ALT_MASK;
489             }
490
491             int oldKeyCode = ke.getKeyCode();
492
493             //noinspection MagicConstant
494             ke = new KeyEvent(ke.getComponent(), ke.getID(), ke.getWhen(), modifiers,
495                              KeyEvent.VK_UNDEFINED, ke.getKeyChar(), KeyEvent.KEY_LOCATION_UNKNOWN);
496
497             ke.setKeyCode(oldKeyCode);
498           }
499         }
500       }
501     }
502
503     return ke;
504   }
505
506   private static AWTEvent mapEvent(AWTEvent e) {
507     if (SystemInfo.isXWindow && e instanceof MouseEvent && ((MouseEvent)e).getButton() > 3) {
508       MouseEvent src = (MouseEvent)e;
509       if (src.getButton() < 6) {
510         // Convert these events(buttons 4&5 in are produced by touchpad, they must be converted to horizontal scrolling events
511         e = new MouseWheelEvent(src.getComponent(), src.getID(), src.getWhen(),
512                                 src.getModifiers() | InputEvent.SHIFT_DOWN_MASK, src.getX(), src.getY(),
513                                 0, false, MouseWheelEvent.WHEEL_UNIT_SCROLL, src.getClickCount(), src.getButton() == 4 ? -1 : 1);
514       }
515       else {
516         // Here we "shift" events with buttons 6 and 7 to similar events with buttons 4 and 5
517         // See java.awt.InputEvent#BUTTON_DOWN_MASK, 1<<14 is 4th physical button, 1<<15 is 5th.
518         //noinspection MagicConstant
519         e = new MouseEvent(src.getComponent(), src.getID(), src.getWhen(), src.getModifiers() | (1 << 8 + src.getButton()),
520                            src.getX(), src.getY(), 1, src.isPopupTrigger(), src.getButton() - 2);
521       }
522     }
523     return e;
524   }
525
526   public void _dispatchEvent(@NotNull AWTEvent e, boolean typeAheadFlushing) {
527     if (e.getID() == MouseEvent.MOUSE_DRAGGED) {
528       DnDManagerImpl dndManager = (DnDManagerImpl)DnDManager.getInstance();
529       if (dndManager != null) {
530         dndManager.setLastDropHandler(null);
531       }
532     }
533
534     myEventCount++;
535     
536     traceClipboardEvents(e);
537
538     if (processAppActivationEvents(e)) return;
539
540     if (!typeAheadFlushing) {
541       fixStickyFocusedComponents(e);
542     }
543
544     if (!myPopupManager.isPopupActive()) {
545       enterSuspendModeIfNeeded(e);
546     }
547
548     myKeyboardBusy = e instanceof KeyEvent ||
549                      peekEvent(KeyEvent.KEY_PRESSED) != null ||
550                      peekEvent(KeyEvent.KEY_RELEASED) != null ||
551                      peekEvent(KeyEvent.KEY_TYPED) != null;
552
553     if (e instanceof KeyEvent) {
554       if (e.getID() == KeyEvent.KEY_RELEASED && ((KeyEvent)e).getKeyCode() == KeyEvent.VK_SHIFT) {
555         myMouseEventDispatcher.resetHorScrollingTracker();
556       }
557     }
558
559     if (!typeAheadFlushing && typeAheadDispatchToFocusManager(e)) {
560       return;
561     }
562
563     if (e instanceof WindowEvent) {
564       ActivityTracker.getInstance().inc();
565     }
566
567     if (e instanceof MouseWheelEvent) {
568       final MenuElement[] selectedPath = MenuSelectionManager.defaultManager().getSelectedPath();
569       if (selectedPath.length > 0 && !(selectedPath[0] instanceof ComboPopup)) {
570         ((MouseWheelEvent)e).consume();
571         return;
572       }
573     }
574
575
576     // Process "idle" and "activity" listeners
577     if (e instanceof KeyEvent || e instanceof MouseEvent) {
578       ActivityTracker.getInstance().inc();
579
580       synchronized (myLock) {
581         myIdleRequestsAlarm.cancelAllRequests();
582         for (Runnable idleListener : myIdleListeners) {
583           final MyFireIdleRequest request = myListener2Request.get(idleListener);
584           if (request == null) {
585             LOG.error("There is no request for " + idleListener);
586           }
587           else {
588             myIdleRequestsAlarm.addRequest(request, request.getTimeout(), ModalityState.NON_MODAL);
589           }
590         }
591         if (KeyEvent.KEY_PRESSED == e.getID() ||
592             KeyEvent.KEY_TYPED == e.getID() ||
593             MouseEvent.MOUSE_PRESSED == e.getID() ||
594             MouseEvent.MOUSE_RELEASED == e.getID() ||
595             MouseEvent.MOUSE_CLICKED == e.getID()) {
596           addIdleTimeCounterRequest();
597           for (Runnable activityListener : myActivityListeners) {
598             activityListener.run();
599           }
600         }
601       }
602     }
603     if (myPopupManager.isPopupActive() && myPopupManager.dispatch(e)) {
604       if (myKeyEventDispatcher.isWaitingForSecondKeyStroke()) {
605         myKeyEventDispatcher.setState(KeyState.STATE_INIT);
606       }
607
608       return;
609     }
610
611     for (EventDispatcher eachDispatcher : myDispatchers) {
612       if (eachDispatcher.dispatch(e)) {
613         return;
614       }
615     }
616
617     if (e instanceof InputMethodEvent) {
618       if (SystemInfo.isMac && myKeyEventDispatcher.isWaitingForSecondKeyStroke()) {
619         return;
620       }
621     }
622     if (e instanceof ComponentEvent && myWindowManager != null) {
623       myWindowManager.dispatchComponentEvent((ComponentEvent)e);
624     }
625     if (e instanceof KeyEvent) {
626       if (mySuspendMode || !myKeyEventDispatcher.dispatchKeyEvent((KeyEvent)e)) {
627         defaultDispatchEvent(e);
628       }
629       else {
630         ((KeyEvent)e).consume();
631         defaultDispatchEvent(e);
632       }
633     }
634     else if (e instanceof MouseEvent) {
635       MouseEvent me = (MouseEvent)e;
636       if (IdeMouseEventDispatcher.patchClickCount(me) && me.getID() == MouseEvent.MOUSE_CLICKED) {
637         final MouseEvent toDispatch =
638           new MouseEvent(me.getComponent(), me.getID(), System.currentTimeMillis(), me.getModifiers(), me.getX(), me.getY(), 1,
639                          me.isPopupTrigger(), me.getButton());
640         //noinspection SSBasedInspection
641         SwingUtilities.invokeLater(new Runnable() {
642           @Override
643           public void run() {
644             dispatchEvent(toDispatch);
645           }
646         });
647       }
648       if (!myMouseEventDispatcher.dispatchMouseEvent(me)) {
649         defaultDispatchEvent(e);
650       }
651     }
652     else {
653       defaultDispatchEvent(e);
654     }
655   }
656
657   private static final Field ourInvocationEventRunnableAccessor;
658   
659   static {
660     Field field = null;
661     if (Registry.is("trace.clipboard.events") && SystemInfo.isJavaVersionAtLeast("1.8.0_60")) {
662       try {
663         field = InvocationEvent.class.getDeclaredField("runnable");
664         field.setAccessible(true);
665       }
666       catch (Exception e) {
667         LOG.warn("Error creating accessor for java.awt.event.InvocationEvent.runnable field", e);
668       }
669     }
670     ourInvocationEventRunnableAccessor = field;
671   }
672   
673   private static void traceClipboardEvents(AWTEvent e) {
674     if (ourInvocationEventRunnableAccessor != null && e instanceof InvocationEvent && e.getClass().getName().equals("sun.awt.PeerEvent")) {
675       try {
676         Object r = ourInvocationEventRunnableAccessor.get(e);
677         if (r != null) {
678           String className = r.getClass().getName();
679           // This check for event constructed in SunClipboard.lostOwnershipLater() (known to work with JDK 8u60) 
680           if (className.contains("sun.awt.datatransfer.SunClipboard") && className.contains("Lambda")) {
681             LOG.info("Clipboard has been set by other application");
682           }
683         }
684       }
685       catch (Exception ex) {
686         LOG.warn("Error accessing java.awt.event.InvocationEvent.runnable field");
687       }
688     }
689   }
690
691   private static void fixStickyWindow(KeyboardFocusManager mgr, Window wnd, String resetMethod) {
692     Window showingWindow = wnd;
693
694     if (wnd != null && !wnd.isShowing()) {
695       while (showingWindow != null) {
696         if (showingWindow.isShowing()) break;
697         showingWindow = (Window)showingWindow.getParent();
698       }
699
700       if (showingWindow == null) {
701         final Frame[] allFrames = Frame.getFrames();
702         for (Frame each : allFrames) {
703           if (each.isShowing()) {
704             showingWindow = each;
705             break;
706           }
707         }
708       }
709
710
711       if (showingWindow != null && showingWindow != wnd) {
712         final Method setActive = ReflectionUtil.findMethod(ReflectionUtil.getClassDeclaredMethods(KeyboardFocusManager.class, false), resetMethod, Window.class);
713         if (setActive != null) {
714           try {
715             setActive.invoke(mgr, (Window)showingWindow);
716           }
717           catch (Exception exc) {
718             LOG.info(exc);
719           }
720         }
721       }
722     }
723   }
724
725   public void fixStickyFocusedComponents(@Nullable AWTEvent e) {
726     if (e != null && !(e instanceof InputEvent)) return;
727
728     final KeyboardFocusManager mgr = KeyboardFocusManager.getCurrentKeyboardFocusManager();
729
730     if (Registry.is("actionSystem.fixStickyFocusedWindows")) {
731       fixStickyWindow(mgr, mgr.getActiveWindow(), "setGlobalActiveWindow");
732       fixStickyWindow(mgr, mgr.getFocusedWindow(), "setGlobalFocusedWindow");
733     }
734
735     if (Registry.is("actionSystem.fixNullFocusedComponent")) {
736       final Component focusOwner = mgr.getFocusOwner();
737       if (focusOwner == null || !focusOwner.isShowing() || focusOwner instanceof JFrame || focusOwner instanceof JDialog) {
738
739         final Application app = ApplicationManager.getApplication();
740         if (app instanceof ApplicationEx && !((ApplicationEx) app).isLoaded()) {
741           return;
742         }
743
744         boolean mouseEventsAhead = isMouseEventAhead(e);
745         boolean focusTransferredNow = IdeFocusManager.getGlobalInstance().isFocusBeingTransferred();
746
747         boolean okToFixFocus = !mouseEventsAhead && !focusTransferredNow;
748
749         if (okToFixFocus) {
750           Window showingWindow = mgr.getActiveWindow();
751           if (showingWindow == null) {
752             Method getNativeFocusOwner = ReflectionUtil.getDeclaredMethod(KeyboardFocusManager.class, "getNativeFocusOwner");
753             if (getNativeFocusOwner != null) {
754               try {
755                 Object owner = getNativeFocusOwner.invoke(mgr);
756                 if (owner instanceof Component) {
757                   showingWindow = UIUtil.getWindow((Component)owner);
758                 }
759               }
760               catch (Exception e1) {
761                 LOG.debug(e1);
762               }
763             }
764           }
765           if (showingWindow != null) {
766             final IdeFocusManager fm = IdeFocusManager.findInstanceByComponent(showingWindow);
767             ExpirableRunnable maybeRequestDefaultFocus = new ExpirableRunnable() {
768               @Override
769               public void run() {
770                 if (getPopupManager().requestDefaultFocus(false)) return;
771
772                 final Application app = ApplicationManager.getApplication();
773                 if (app != null && app.isActive()) {
774                   fm.requestDefaultFocus(false);
775                 }
776               }
777
778               @Override
779               public boolean isExpired() {
780                 return !UIUtil.isMeaninglessFocusOwner(mgr.getFocusOwner());
781               }
782             };
783             fm.revalidateFocus(maybeRequestDefaultFocus);
784           }
785         }
786       }
787     }
788   }
789
790   public static boolean isMouseEventAhead(@Nullable AWTEvent e) {
791     IdeEventQueue queue = getInstance();
792     return e instanceof MouseEvent ||
793            queue.peekEvent(MouseEvent.MOUSE_PRESSED) != null ||
794            queue.peekEvent(MouseEvent.MOUSE_RELEASED) != null ||
795            queue.peekEvent(MouseEvent.MOUSE_CLICKED) != null;
796   }
797
798   private void enterSuspendModeIfNeeded(AWTEvent e) {
799     if (e instanceof KeyEvent) {
800       if (!mySuspendMode && shallEnterSuspendMode()) {
801         enterSuspendMode();
802       }
803     }
804   }
805
806   private boolean shallEnterSuspendMode() {
807     return peekEvent(WindowEvent.WINDOW_OPENED) != null;
808   }
809
810   private static boolean processAppActivationEvents(AWTEvent e) {
811
812     if (e instanceof WindowEvent) {
813       final WindowEvent we = (WindowEvent)e;
814
815       ApplicationActivationStateManager.get().updateState(we);
816
817       storeLastFocusedComponent(we);
818     }
819
820     return false;
821   }
822
823   private static void storeLastFocusedComponent(WindowEvent we) {
824     final Window eventWindow = we.getWindow();
825
826     if (we.getID() == WindowEvent.WINDOW_DEACTIVATED || we.getID() == WindowEvent.WINDOW_LOST_FOCUS) {
827       Component frame = UIUtil.findUltimateParent(eventWindow);
828       Component focusOwnerInDeactivatedWindow = eventWindow.getMostRecentFocusOwner();
829       IdeFrame[] allProjectFrames = WindowManager.getInstance().getAllProjectFrames();
830
831       if (focusOwnerInDeactivatedWindow != null) {
832         for (IdeFrame ideFrame : allProjectFrames) {
833           JFrame aFrame = WindowManager.getInstance().getFrame(ideFrame.getProject());
834           if (aFrame.equals(frame)) {
835             IdeFocusManager focusManager = IdeFocusManager.getGlobalInstance();
836             if (focusManager instanceof FocusManagerImpl) {
837               ((FocusManagerImpl)focusManager).setLastFocusedAtDeactivation(ideFrame, focusOwnerInDeactivatedWindow);
838             }
839           }
840         }
841       }
842     }
843   }
844
845   private void defaultDispatchEvent(final AWTEvent e) {
846     try {
847       myDispatchingFocusEvent = e instanceof FocusEvent;
848
849       maybeReady();
850       fixStickyAlt(e);
851
852       super.dispatchEvent(e);
853     }
854     catch (Throwable t) {
855       processException(t);
856     }
857     finally {
858       myDispatchingFocusEvent = false;
859     }
860   }
861
862   private static Field ourStickyAltField;
863
864   private static void fixStickyAlt(AWTEvent e) {
865     if (Registry.is("actionSystem.win.suppressAlt.new")) {
866       if (UIUtil.isUnderWindowsLookAndFeel() &&
867           e instanceof InputEvent &&
868           (((InputEvent)e).getModifiers() & (InputEvent.ALT_MASK | InputEvent.ALT_DOWN_MASK)) != 0 &&
869           !(e instanceof KeyEvent && ((KeyEvent)e).getKeyCode() == KeyEvent.VK_ALT)) {
870         try {
871           if (ourStickyAltField == null) {
872             Class<?> aClass = Class.forName("com.sun.java.swing.plaf.windows.WindowsRootPaneUI$AltProcessor");
873             ourStickyAltField = ReflectionUtil.getDeclaredField(aClass, "menuCanceledOnPress");
874           }
875           if (ourStickyAltField != null) {
876             ourStickyAltField.set(null, true);
877           }
878         }
879         catch (Exception exception) {
880           LOG.error(exception);
881         }
882       }
883     }
884     else if (SystemInfo.isWinXpOrNewer && !SystemInfo.isWinVistaOrNewer && e instanceof KeyEvent && ((KeyEvent)e).getKeyCode() == KeyEvent.VK_ALT) {
885       ((KeyEvent)e).consume();  // IDEA-17359
886     }
887   }
888
889   public boolean isDispatchingFocusEvent() {
890     return myDispatchingFocusEvent;
891   }
892
893   private static boolean typeAheadDispatchToFocusManager(AWTEvent e) {
894     if (e instanceof KeyEvent) {
895       final KeyEvent event = (KeyEvent)e;
896       if (!event.isConsumed()) {
897         final IdeFocusManager focusManager = IdeFocusManager.findInstanceByComponent(event.getComponent());
898         return focusManager.dispatch(event);
899       }
900     }
901
902     return false;
903   }
904
905   public void flushQueue() {
906     while (true) {
907       AWTEvent event = peekEvent();
908       if (event == null) return;
909       try {
910         AWTEvent event1 = getNextEvent();
911         dispatchEvent(event1);
912       }
913       catch (Exception e) {
914         LOG.error(e); //?
915       }
916     }
917   }
918
919   public void pumpEventsForHierarchy(Component modalComponent, Condition<AWTEvent> exitCondition) {
920     AWTEvent event;
921     do {
922       try {
923         event = getNextEvent();
924         boolean eventOk = true;
925         if (event instanceof InputEvent) {
926           final Object s = event.getSource();
927           if (s instanceof Component) {
928             Component c = (Component)s;
929             Window modalWindow = modalComponent == null ? null : SwingUtilities.windowForComponent(modalComponent);
930             while (c != null && c != modalWindow) c = c.getParent();
931             if (c == null) {
932               eventOk = false;
933               ((InputEvent)event).consume();
934             }
935           }
936         }
937
938         if (eventOk) {
939           dispatchEvent(event);
940         }
941       }
942       catch (Throwable e) {
943         LOG.error(e);
944         event = null;
945       }
946     }
947     while (!exitCondition.value(event));
948   }
949
950
951   public interface EventDispatcher {
952     boolean dispatch(AWTEvent e);
953   }
954
955
956   private final class MyFireIdleRequest implements Runnable {
957     private final Runnable myRunnable;
958     private final int myTimeout;
959
960
961     public MyFireIdleRequest(@NotNull Runnable runnable, final int timeout) {
962       myTimeout = timeout;
963       myRunnable = runnable;
964     }
965
966
967     @Override
968     public void run() {
969       myRunnable.run();
970       synchronized (myLock) {
971         if (myIdleListeners.contains(myRunnable)) // do not reschedule if not interested anymore
972         {
973           myIdleRequestsAlarm.addRequest(this, myTimeout, ModalityState.NON_MODAL);
974         }
975       }
976     }
977
978     public int getTimeout() {
979       return myTimeout;
980     }
981
982     @Override
983     public String toString() {
984       return "Fire idle request. delay: "+getTimeout()+"; runnable: "+myRunnable;
985     }
986   }
987
988   private final class ExitSuspendModeRunnable implements Runnable {
989
990     @Override
991     public void run() {
992       if (mySuspendMode) {
993         exitSuspendMode();
994       }
995     }
996   }
997
998
999   public long getIdleTime() {
1000     return myIdleTime;
1001   }
1002
1003
1004   public IdePopupManager getPopupManager() {
1005     return myPopupManager;
1006   }
1007
1008   public IdeKeyEventDispatcher getKeyEventDispatcher() {
1009     return myKeyEventDispatcher;
1010   }
1011
1012   /**
1013    * Same as {@link #blockNextEvents(MouseEvent, IdeEventQueue.BlockMode)} with <code>blockMode</code> equal to <code>COMPLETE</code>.
1014    */
1015   public void blockNextEvents(final MouseEvent e) {
1016     blockNextEvents(e, BlockMode.COMPLETE);
1017   }
1018
1019   /**
1020    * When <code>blockMode</code> is <code>COMPLETE</code>, blocks following related mouse events completely, when <code>blockMode</code> is
1021    * <code>ACTIONS</code> only blocks performing actions bound to corresponding mouse shortcuts.
1022    */
1023   public void blockNextEvents(final MouseEvent e, BlockMode blockMode) {
1024     myMouseEventDispatcher.blockNextEvents(e, blockMode);
1025   }
1026
1027   public boolean isSuspendMode() {
1028     return mySuspendMode;
1029   }
1030
1031   public boolean hasFocusEventsPending() {
1032     return peekEvent(FocusEvent.FOCUS_GAINED) != null || peekEvent(FocusEvent.FOCUS_LOST) != null;
1033   }
1034
1035   private boolean isReady() {
1036     return !myKeyboardBusy && myKeyEventDispatcher.isReady();
1037   }
1038
1039   public void maybeReady() {
1040     flushReady();
1041   }
1042
1043   private void flushReady() {
1044     if (myReady.isEmpty() || !isReady()) return;
1045
1046     Runnable[] ready = myReady.toArray(new Runnable[myReady.size()]);
1047     myReady.clear();
1048
1049     for (Runnable each : ready) {
1050       each.run();
1051     }
1052   }
1053
1054   public void doWhenReady(final Runnable runnable) {
1055     if (EventQueue.isDispatchThread()) {
1056       myReady.add(runnable);
1057       maybeReady();
1058     }
1059     else {
1060       //noinspection SSBasedInspection
1061       SwingUtilities.invokeLater(new Runnable() {
1062         @Override
1063         public void run() {
1064           myReady.add(runnable);
1065           maybeReady();
1066         }
1067       });
1068     }
1069   }
1070
1071   public boolean isPopupActive() {
1072     return myPopupManager.isPopupActive();
1073   }
1074
1075   private static class WindowsAltSuppressor implements EventDispatcher {
1076     private boolean myWaitingForAltRelease;
1077     private Robot myRobot;
1078
1079     @Override
1080     public boolean dispatch(AWTEvent e) {
1081       boolean dispatch = true;
1082       if (e instanceof KeyEvent) {
1083         KeyEvent ke = (KeyEvent)e;
1084         final Component component = ke.getComponent();
1085         boolean pureAlt = ke.getKeyCode() == KeyEvent.VK_ALT && (ke.getModifiers() | InputEvent.ALT_MASK) == InputEvent.ALT_MASK;
1086         if (!pureAlt) {
1087           myWaitingForAltRelease = false;
1088         }
1089         else {
1090           if (ApplicationManager.getApplication() == null ||
1091               UISettings.getInstance() == null ||
1092               !SystemInfo.isWindows ||
1093               !Registry.is("actionSystem.win.suppressAlt") ||
1094               !(UISettings.getInstance().HIDE_TOOL_STRIPES || UISettings.getInstance().PRESENTATION_MODE)) {
1095             return false;
1096           }
1097
1098           if (ke.getID() == KeyEvent.KEY_PRESSED) {
1099             dispatch = !myWaitingForAltRelease;
1100           }
1101           else if (ke.getID() == KeyEvent.KEY_RELEASED) {
1102             if (myWaitingForAltRelease) {
1103               myWaitingForAltRelease = false;
1104               dispatch = false;
1105             }
1106             else if (component != null) {
1107               //noinspection SSBasedInspection
1108               SwingUtilities.invokeLater(new Runnable() {
1109                 @Override
1110                 public void run() {
1111                   try {
1112                     final Window window = UIUtil.getWindow(component);
1113                     if (window == null || !window.isActive()) {
1114                       return;
1115                     }
1116                     myWaitingForAltRelease = true;
1117                     if (myRobot == null) {
1118                       myRobot = new Robot();
1119                     }
1120                     myRobot.keyPress(KeyEvent.VK_ALT);
1121                     myRobot.keyRelease(KeyEvent.VK_ALT);
1122                   }
1123                   catch (AWTException e1) {
1124                     LOG.debug(e1);
1125                   }
1126                 }
1127               });
1128             }
1129           }
1130         }
1131       }
1132
1133       return !dispatch;
1134     }
1135   }
1136
1137   public boolean isInputMethodEnabled() {
1138     return !SystemInfo.isMac || myInputMethodLock == 0;
1139   }
1140
1141   public void disableInputMethods(Disposable parentDisposable) {
1142     myInputMethodLock++;
1143     Disposer.register(parentDisposable, new Disposable() {
1144       @Override
1145       public void dispose() {
1146         myInputMethodLock--;
1147       }
1148     });
1149   }
1150
1151   private final FrequentEventDetector myFrequentEventDetector = new FrequentEventDetector(1009, 100);
1152   @Override
1153   public void postEvent(@NotNull AWTEvent theEvent) {
1154     myFrequentEventDetector.eventHappened();
1155     super.postEvent(theEvent);
1156   }
1157
1158   @Override
1159   public AWTEvent peekEvent() {
1160     AWTEvent event = super.peekEvent();
1161     if (event != null) {
1162       return event;
1163     }
1164     if (isTestMode() && LaterInvocator.ensureFlushRequested()) {
1165       return super.peekEvent();
1166     }
1167     return null;
1168   }
1169
1170   private Boolean myTestMode;
1171   private boolean isTestMode() {
1172     Boolean testMode = myTestMode;
1173     if (testMode != null) return testMode;
1174     
1175     Application application = ApplicationManager.getApplication();
1176     if (application == null) return false;
1177
1178     testMode = application.isUnitTestMode();
1179     myTestMode = testMode;
1180     return testMode;
1181   }
1182
1183   /**
1184    * @see IdeEventQueue#blockNextEvents(MouseEvent, IdeEventQueue.BlockMode)
1185    */
1186   public enum BlockMode {
1187     COMPLETE, ACTIONS
1188   }
1189 }