2 * Copyright 2000-2010 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
.openapi
.wm
.impl
;
18 import com
.intellij
.ide
.IdeEventQueue
;
19 import com
.intellij
.openapi
.Disposable
;
20 import com
.intellij
.openapi
.application
.Application
;
21 import com
.intellij
.openapi
.application
.ApplicationAdapter
;
22 import com
.intellij
.openapi
.application
.ApplicationManager
;
23 import com
.intellij
.openapi
.ui
.popup
.JBPopup
;
24 import com
.intellij
.openapi
.util
.ActionCallback
;
25 import com
.intellij
.openapi
.util
.EdtRunnable
;
26 import com
.intellij
.openapi
.util
.Expirable
;
27 import com
.intellij
.openapi
.util
.registry
.Registry
;
28 import com
.intellij
.openapi
.wm
.*;
29 import com
.intellij
.openapi
.wm
.ex
.IdeFocusTraversalPolicy
;
30 import com
.intellij
.ui
.FocusTrackback
;
31 import com
.intellij
.util
.Alarm
;
32 import com
.intellij
.util
.containers
.WeakHashMap
;
33 import com
.intellij
.util
.ui
.UIUtil
;
34 import org
.jetbrains
.annotations
.NotNull
;
35 import org
.jetbrains
.annotations
.Nullable
;
39 import java
.awt
.event
.FocusEvent
;
40 import java
.awt
.event
.KeyEvent
;
41 import java
.lang
.ref
.WeakReference
;
42 import java
.util
.ArrayList
;
43 import java
.util
.Iterator
;
47 public class FocusManagerImpl
extends IdeFocusManager
implements Disposable
{
49 private Application myApp
;
51 private FocusCommand myRequestFocusCmd
;
52 private final ArrayList
<FocusCommand
> myFocusRequests
= new ArrayList
<FocusCommand
>();
54 private final ArrayList
<KeyEvent
> myToDispatchOnDone
= new ArrayList
<KeyEvent
>();
55 private int myFlushingIdleRequestsEntryCount
= 0;
57 private WeakReference
<FocusCommand
> myLastForcedRequest
= new WeakReference
<FocusCommand
>(null);
59 private FocusCommand myFocusCommandOnAppActivation
;
60 private ActionCallback myCallbackOnActivation
;
62 private final IdeEventQueue myQueue
;
63 private final KeyProcessorConext myKeyProcessorContext
= new KeyProcessorConext();
65 private long myCmdTimestamp
;
66 private long myForcedCmdTimestamp
;
68 final EdtAlarm myFocusedComponentAlaram
;
69 private final EdtAlarm myForcedFocusRequestsAlarm
;
71 private final EdtAlarm myIdleAlarm
;
72 private final Set
<Runnable
> myIdleRequests
= new com
.intellij
.util
.containers
.HashSet
<Runnable
>();
73 private final EdtRunnable myIdleRunnable
= new EdtRunnable() {
74 public void runEdt() {
75 if (isFocusTransferReady() && !isIdleQueueEmpty()) {
84 private WindowManager myWindowManager
;
86 private Map
<IdeFrame
, Component
> myLastFocused
= new WeakHashMap
<IdeFrame
, Component
>();
87 private Map
<IdeFrame
, Component
> myLastFocusedAtDeactivation
= new WeakHashMap
<IdeFrame
, Component
>();
89 public FocusManagerImpl(WindowManager wm
) {
90 myApp
= ApplicationManager
.getApplication();
91 myQueue
= IdeEventQueue
.getInstance();
94 myFocusedComponentAlaram
= new EdtAlarm(this);
95 myForcedFocusRequestsAlarm
= new EdtAlarm(this);
96 myIdleAlarm
= new EdtAlarm(this);
98 final AppListener myAppListener
= new AppListener();
99 myApp
.addApplicationListener(myAppListener
);
101 IdeEventQueue
.getInstance().addDispatcher(new IdeEventQueue
.EventDispatcher() {
102 public boolean dispatch(AWTEvent e
) {
103 if (e
instanceof FocusEvent
) {
104 final FocusEvent fe
= (FocusEvent
)e
;
105 final Component c
= fe
.getComponent();
106 if (c
instanceof Window
|| c
== null) return false;
108 Component parent
= UIUtil
.findUltimateParent(c
);
109 if (parent
instanceof IdeFrame
) {
110 myLastFocused
.put((IdeFrame
)parent
, c
);
120 public ActionCallback
requestFocus(final Component c
, final boolean forced
) {
121 return requestFocus(new FocusCommand
.ByComponent(c
), forced
);
124 public ActionCallback
requestFocus(final FocusCommand command
, final boolean forced
) {
125 final ActionCallback result
= new ActionCallback();
128 if (!myFocusRequests
.contains(command
)) {
129 myFocusRequests
.add(command
);
132 SwingUtilities
.invokeLater(new Runnable() {
134 resetUnforcedCommand(command
);
135 _requestFocus(command
, forced
, result
);
140 _requestFocus(command
, forced
, result
);
143 result
.doWhenProcessed(new Runnable() {
152 private void _requestFocus(final FocusCommand command
, final boolean forced
, final ActionCallback result
) {
153 if (checkForRejectOrByPass(command
, forced
, result
)) return;
156 command
.setCallback(result
);
159 myForcedFocusRequestsAlarm
.cancelAllRequests();
160 setLastEffectiveForcedRequest(command
);
163 SwingUtilities
.invokeLater(new Runnable() {
165 if (checkForRejectOrByPass(command
, forced
, result
)) return;
167 if (myRequestFocusCmd
== command
) {
168 final ActionCallback
.TimedOut focusTimeout
=
169 new ActionCallback
.TimedOut(Registry
.intValue("actionSystem.commandProcessingTimeout"),
170 "Focus command timed out, cmd=" + command
, command
.getAllocation(), true) {
172 protected void onTimeout() {
173 forceFinishFocusSettledown(command
, result
);
179 myForcedCmdTimestamp
++;
182 command
.run().doWhenDone(new Runnable() {
184 SwingUtilities
.invokeLater(new Runnable() {
190 }).doWhenRejected(new Runnable() {
192 result
.setRejected();
194 }).doWhenProcessed(new Runnable() {
196 resetCommand(command
, false);
199 myForcedFocusRequestsAlarm
.addRequest(new EdtRunnable() {
200 public void runEdt() {
201 setLastEffectiveForcedRequest(null);
206 }).notify(focusTimeout
);
209 rejectCommand(command
, result
);
215 private boolean checkForRejectOrByPass(final FocusCommand cmd
, final boolean forced
, final ActionCallback result
) {
216 if (cmd
.isExpired()) {
217 rejectCommand(cmd
, result
);
221 final FocusCommand lastRequest
= getLastEffectiveForcedRequest();
223 if (!forced
&& !isUnforcedRequestAllowed()) {
224 if (cmd
.equals(lastRequest
)) {
225 resetCommand(cmd
, false);
229 rejectCommand(cmd
, result
);
235 if (lastRequest
!= null && lastRequest
.dominatesOver(cmd
)) {
236 rejectCommand(cmd
, result
);
240 boolean doNotExecuteBecauseAppIsInactive
=
241 !myApp
.isActive() && (!canExecuteOnInactiveApplication(cmd
) && Registry
.is("actionSystem.suspendFocusTransferIfApplicationInactive"));
243 if (doNotExecuteBecauseAppIsInactive
) {
244 if (myCallbackOnActivation
!= null) {
245 myCallbackOnActivation
.setRejected();
246 if (myFocusCommandOnAppActivation
!= null) {
247 resetCommand(myFocusCommandOnAppActivation
, true);
251 myFocusCommandOnAppActivation
= cmd
;
252 myCallbackOnActivation
= result
;
260 private void setCommand(FocusCommand command
) {
261 myRequestFocusCmd
= command
;
263 if (!myFocusRequests
.contains(command
)) {
264 myFocusRequests
.add(command
);
268 private void resetCommand(FocusCommand cmd
, boolean reject
) {
269 if (cmd
== myRequestFocusCmd
) {
270 myRequestFocusCmd
= null;
273 final KeyEventProcessor processor
= cmd
.getProcessor();
274 if (processor
!= null) {
275 processor
.finish(myKeyProcessorContext
);
278 myFocusRequests
.remove(cmd
);
281 ActionCallback cb
= cmd
.getCallback();
282 if (cb
!= null && !cb
.isProcessed()) {
283 cmd
.getCallback().setRejected();
288 private void resetUnforcedCommand(FocusCommand cmd
) {
289 myFocusRequests
.remove(cmd
);
292 private static boolean canExecuteOnInactiveApplication(FocusCommand cmd
) {
293 return cmd
.canExecuteOnInactiveApp();
296 private void setLastEffectiveForcedRequest(FocusCommand command
) {
297 myLastForcedRequest
= new WeakReference
<FocusCommand
>(command
);
301 private FocusCommand
getLastEffectiveForcedRequest() {
302 if (myLastForcedRequest
== null) return null;
303 final FocusCommand request
= myLastForcedRequest
.get();
304 return request
!= null && !request
.isExpired() ? request
: null;
307 boolean isUnforcedRequestAllowed() {
308 return getLastEffectiveForcedRequest() == null;
311 public static FocusManagerImpl
getInstance() {
312 return (FocusManagerImpl
)ApplicationManager
.getApplication().getComponent(IdeFocusManager
.class);
315 public void dispose() {
318 private class KeyProcessorConext
implements KeyEventProcessor
.Context
{
319 public java
.util
.List
<KeyEvent
> getQueue() {
320 return myToDispatchOnDone
;
323 public void dispatch(final java
.util
.List
<KeyEvent
> events
) {
324 doWhenFocusSettlesDown(new Runnable() {
326 myToDispatchOnDone
.addAll(events
);
333 public void doWhenFocusSettlesDown(@NotNull final Runnable runnable
) {
334 final boolean needsRestart
= isIdleQueueEmpty();
335 myIdleRequests
.add(runnable
);
341 private void restartIdleAlarm() {
342 myIdleAlarm
.cancelAllRequests();
343 myIdleAlarm
.addRequest(myIdleRunnable
, Registry
.intValue("actionSystem.focusIdleTimeout"));
346 private void flushIdleRequests() {
348 myFlushingIdleRequestsEntryCount
++;
350 final KeyEvent
[] events
= myToDispatchOnDone
.toArray(new KeyEvent
[myToDispatchOnDone
.size()]);
351 IdeEventQueue
.getInstance().getKeyEventDispatcher().resetState();
353 boolean keyWasPressed
= false;
355 for (KeyEvent each
: events
) {
356 if (!isFocusTransferReady()) break;
358 if (!keyWasPressed
) {
359 if (each
.getID() == KeyEvent
.KEY_PRESSED
) {
360 keyWasPressed
= true;
363 myToDispatchOnDone
.remove(each
);
368 final Component owner
= KeyboardFocusManager
.getCurrentKeyboardFocusManager().getFocusOwner();
369 if (owner
!= null && SwingUtilities
.getWindowAncestor(owner
) != null) {
370 myToDispatchOnDone
.remove(each
);
371 IdeEventQueue
.getInstance().dispatchEvent(
372 new KeyEvent(owner
, each
.getID(), each
.getWhen(), each
.getModifiersEx(), each
.getKeyCode(), each
.getKeyChar(),
373 each
.getKeyLocation()));
381 if (isPendingKeyEventsRedispatched()) {
382 final Runnable
[] all
= myIdleRequests
.toArray(new Runnable
[myIdleRequests
.size()]);
383 myIdleRequests
.clear();
384 for (Runnable each
: all
) {
390 myFlushingIdleRequestsEntryCount
--;
391 if (!isIdleQueueEmpty()) {
397 public boolean isFocusTransferReady() {
398 invalidateFocusRequestsQueue();
400 if (!myFocusRequests
.isEmpty()) return false;
401 if (myQueue
== null) return true;
403 return !myQueue
.isSuspendMode() && !myQueue
.hasFocusEventsPending();
406 private void invalidateFocusRequestsQueue() {
407 if (myFocusRequests
.size() == 0) return;
409 FocusCommand
[] requests
= myFocusRequests
.toArray(new FocusCommand
[myFocusRequests
.size()]);
410 boolean wasChanged
= false;
411 for (FocusCommand each
: requests
) {
412 if (each
.isExpired()) {
413 resetCommand(each
, true);
418 if (wasChanged
&& myFocusRequests
.size() == 0) {
423 private boolean isIdleQueueEmpty() {
424 return isPendingKeyEventsRedispatched() && myIdleRequests
.isEmpty();
427 private boolean isPendingKeyEventsRedispatched() {
428 return myToDispatchOnDone
.isEmpty();
431 public boolean dispatch(KeyEvent e
) {
432 if (!Registry
.is("actionSystem.fixLostTyping")) return false;
434 if (myFlushingIdleRequestsEntryCount
> 0) return false;
436 if (!isFocusTransferReady() || !isPendingKeyEventsRedispatched()) {
437 for (FocusCommand each
: myFocusRequests
) {
438 final KeyEventProcessor processor
= each
.getProcessor();
439 if (processor
!= null) {
440 final Boolean result
= processor
.dispatch(e
, myKeyProcessorContext
);
441 if (result
!= null) {
442 return result
.booleanValue();
447 myToDispatchOnDone
.add(e
);
457 public void suspendKeyProcessingUntil(final ActionCallback done
) {
458 requestFocus(new FocusCommand(done
) {
459 public ActionCallback
run() {
462 }.saveAllocation(), true);
465 public Expirable
getTimestamp(final boolean trackOnlyForcedCommands
) {
466 return new Expirable() {
467 long myOwnStamp
= trackOnlyForcedCommands ? myForcedCmdTimestamp
: myCmdTimestamp
;
469 public boolean isExpired() {
470 return myOwnStamp
< (trackOnlyForcedCommands ? myForcedCmdTimestamp
: myCmdTimestamp
);
475 static class EdtAlarm
{
476 private final Alarm myAlarm
;
478 private EdtAlarm(Disposable parent
) {
479 myAlarm
= new Alarm(Alarm
.ThreadToUse
.OWN_THREAD
, parent
);
482 public void cancelAllRequests() {
483 myAlarm
.cancelAllRequests();
486 public void addRequest(EdtRunnable runnable
, int delay
) {
487 myAlarm
.addRequest(runnable
, delay
);
491 private void forceFinishFocusSettledown(FocusCommand cmd
, ActionCallback cmdCallback
) {
492 rejectCommand(cmd
, cmdCallback
);
496 private void rejectCommand(FocusCommand cmd
, ActionCallback callback
) {
497 resetCommand(cmd
, true);
498 resetUnforcedCommand(cmd
);
500 callback
.setRejected();
503 private class AppListener
extends ApplicationAdapter
{
506 public void applicationDeactivated(IdeFrame ideFrame
) {
507 final Component owner
= KeyboardFocusManager
.getCurrentKeyboardFocusManager().getFocusOwner();
508 Component parent
= UIUtil
.findUltimateParent(owner
);
510 if (parent
== ideFrame
) {
511 myLastFocusedAtDeactivation
.put(ideFrame
, owner
);
516 public void applicationActivated(final IdeFrame ideFrame
) {
517 final FocusCommand cmd
= myFocusCommandOnAppActivation
;
518 ActionCallback callback
= myCallbackOnActivation
;
519 myFocusCommandOnAppActivation
= null;
520 myCallbackOnActivation
= null;
523 requestFocus(cmd
, true).notify(callback
).doWhenRejected(new Runnable() {
525 focusLastFocusedComponent(ideFrame
);
530 focusLastFocusedComponent(ideFrame
);
534 private void focusLastFocusedComponent(IdeFrame ideFrame
) {
535 final KeyboardFocusManager mgr
= KeyboardFocusManager
.getCurrentKeyboardFocusManager();
536 if (mgr
.getFocusOwner() == null) {
537 Component c
= myLastFocusedAtDeactivation
.get(ideFrame
);
538 if (c
== null || !c
.isShowing()) {
539 c
= myLastFocused
.get(ideFrame
);
542 if (c
!= null && c
.isShowing()) {
543 requestFocus(c
, false);
547 myLastFocusedAtDeactivation
.remove(ideFrame
);
552 public JComponent
getFocusTargetFor(@NotNull JComponent comp
) {
553 return IdeFocusTraversalPolicy
.getPreferredFocusedComponent(comp
);
557 public Component
getFocusedDescendantFor(Component comp
) {
558 final Component focused
= KeyboardFocusManager
.getCurrentKeyboardFocusManager().getFocusOwner();
559 if (focused
== null) return null;
561 if (focused
== comp
|| SwingUtilities
.isDescendingFrom(focused
, comp
)) return focused
;
563 java
.util
.List
<JBPopup
> popups
= FocusTrackback
.getChildPopups(comp
);
564 for (JBPopup each
: popups
) {
565 if (each
.isFocused()) return focused
;
572 public boolean isFocusBeingTransferred() {
573 return !isFocusTransferReady();
577 public ActionCallback
requestDefaultFocus(boolean forced
) {
578 return new ActionCallback
.Done();