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
.Patches
;
19 import com
.intellij
.ide
.IdeEventQueue
;
20 import com
.intellij
.openapi
.Disposable
;
21 import com
.intellij
.openapi
.application
.Application
;
22 import com
.intellij
.openapi
.application
.ApplicationAdapter
;
23 import com
.intellij
.openapi
.application
.ApplicationManager
;
24 import com
.intellij
.openapi
.ui
.popup
.JBPopup
;
25 import com
.intellij
.openapi
.util
.ActionCallback
;
26 import com
.intellij
.openapi
.util
.EdtRunnable
;
27 import com
.intellij
.openapi
.util
.Expirable
;
28 import com
.intellij
.openapi
.util
.registry
.Registry
;
29 import com
.intellij
.openapi
.wm
.*;
30 import com
.intellij
.openapi
.wm
.ex
.IdeFocusTraversalPolicy
;
31 import com
.intellij
.ui
.FocusTrackback
;
32 import com
.intellij
.util
.Alarm
;
33 import com
.intellij
.util
.containers
.WeakHashMap
;
34 import com
.intellij
.util
.ui
.UIUtil
;
35 import org
.jetbrains
.annotations
.NotNull
;
36 import org
.jetbrains
.annotations
.Nullable
;
40 import java
.awt
.event
.FocusEvent
;
41 import java
.awt
.event
.KeyEvent
;
42 import java
.lang
.ref
.WeakReference
;
43 import java
.util
.ArrayList
;
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 myFocusRequests
.add(command
);
130 SwingUtilities
.invokeLater(new Runnable() {
132 resetUnforcedCommand(command
);
133 _requestFocus(command
, forced
, result
);
138 _requestFocus(command
, forced
, result
);
141 result
.doWhenProcessed(new Runnable() {
150 private void _requestFocus(final FocusCommand command
, final boolean forced
, final ActionCallback result
) {
151 if (checkForRejectOrByPass(command
, forced
, result
)) return;
156 myForcedFocusRequestsAlarm
.cancelAllRequests();
157 setLastEffectiveForcedRequest(command
);
160 SwingUtilities
.invokeLater(new Runnable() {
162 if (checkForRejectOrByPass(command
, forced
, result
)) return;
164 if (myRequestFocusCmd
== command
) {
165 final ActionCallback
.TimedOut focusTimeout
=
166 new ActionCallback
.TimedOut(Registry
.intValue("actionSystem.commandProcessingTimeout"),
167 "Focus command timed out, cmd=" + command
, command
.getAllocation(), true) {
169 protected void onTimeout() {
170 forceFinishFocusSettledown(command
, result
);
176 myForcedCmdTimestamp
++;
179 command
.run().doWhenDone(new Runnable() {
181 SwingUtilities
.invokeLater(new Runnable() {
187 }).doWhenRejected(new Runnable() {
189 result
.setRejected();
191 }).doWhenProcessed(new Runnable() {
193 resetCommand(command
);
196 myForcedFocusRequestsAlarm
.addRequest(new EdtRunnable() {
197 public void runEdt() {
198 setLastEffectiveForcedRequest(null);
203 }).notify(focusTimeout
);
206 rejectCommand(command
, result
);
212 private boolean checkForRejectOrByPass(final FocusCommand cmd
, final boolean forced
, final ActionCallback result
) {
213 if (cmd
.isExpired()) {
214 rejectCommand(cmd
, result
);
218 final FocusCommand lastRequest
= getLastEffectiveForcedRequest();
220 if (!forced
&& !isUnforcedRequestAllowed()) {
221 if (cmd
.equals(lastRequest
)) {
225 rejectCommand(cmd
, result
);
231 if (lastRequest
!= null && lastRequest
.dominatesOver(cmd
)) {
232 rejectCommand(cmd
, result
);
236 boolean doNotExecuteBecauseAppIsInactive
=
237 !myApp
.isActive() && (!canExecuteOnInactiveApplication(cmd
) && Registry
.is("actionSystem.suspendFocusTransferIfApplicationInactive"));
239 if (doNotExecuteBecauseAppIsInactive
) {
240 if (myCallbackOnActivation
!= null) {
241 myCallbackOnActivation
.setRejected();
244 myFocusCommandOnAppActivation
= cmd
;
245 myCallbackOnActivation
= result
;
253 private void setCommand(FocusCommand command
) {
254 myRequestFocusCmd
= command
;
256 if (!myFocusRequests
.contains(command
)) {
257 myFocusRequests
.add(command
);
261 private void resetCommand(FocusCommand cmd
) {
262 if (cmd
== myRequestFocusCmd
) {
263 myRequestFocusCmd
= null;
266 final KeyEventProcessor processor
= cmd
.getProcessor();
267 if (processor
!= null) {
268 processor
.finish(myKeyProcessorContext
);
271 myFocusRequests
.remove(cmd
);
274 private void resetUnforcedCommand(FocusCommand cmd
) {
275 myFocusRequests
.remove(cmd
);
278 private static boolean canExecuteOnInactiveApplication(FocusCommand cmd
) {
279 return cmd
.canExecuteOnInactiveApp();
282 private void setLastEffectiveForcedRequest(FocusCommand command
) {
283 myLastForcedRequest
= new WeakReference
<FocusCommand
>(command
);
287 private FocusCommand
getLastEffectiveForcedRequest() {
288 if (myLastForcedRequest
== null) return null;
289 final FocusCommand request
= myLastForcedRequest
.get();
290 return request
!= null && !request
.isExpired() ? request
: null;
293 boolean isUnforcedRequestAllowed() {
294 return getLastEffectiveForcedRequest() == null;
297 public static FocusManagerImpl
getInstance() {
298 return (FocusManagerImpl
)ApplicationManager
.getApplication().getComponent(IdeFocusManager
.class);
301 public void dispose() {
304 private class KeyProcessorConext
implements KeyEventProcessor
.Context
{
305 public java
.util
.List
<KeyEvent
> getQueue() {
306 return myToDispatchOnDone
;
309 public void dispatch(final java
.util
.List
<KeyEvent
> events
) {
310 doWhenFocusSettlesDown(new Runnable() {
312 myToDispatchOnDone
.addAll(events
);
319 public void doWhenFocusSettlesDown(@NotNull final Runnable runnable
) {
320 final boolean needsRestart
= isIdleQueueEmpty();
321 myIdleRequests
.add(runnable
);
327 private void restartIdleAlarm() {
328 myIdleAlarm
.cancelAllRequests();
329 myIdleAlarm
.addRequest(myIdleRunnable
, Registry
.intValue("actionSystem.focusIdleTimeout"));
332 private void flushIdleRequests() {
334 myFlushingIdleRequestsEntryCount
++;
336 final KeyEvent
[] events
= myToDispatchOnDone
.toArray(new KeyEvent
[myToDispatchOnDone
.size()]);
337 IdeEventQueue
.getInstance().getKeyEventDispatcher().resetState();
339 boolean keyWasPressed
= false;
341 for (KeyEvent each
: events
) {
342 if (!isFocusTransferReady()) break;
344 if (!keyWasPressed
) {
345 if (each
.getID() == KeyEvent
.KEY_PRESSED
) {
346 keyWasPressed
= true;
349 myToDispatchOnDone
.remove(each
);
354 final Component owner
= KeyboardFocusManager
.getCurrentKeyboardFocusManager().getFocusOwner();
355 if (owner
!= null && SwingUtilities
.getWindowAncestor(owner
) != null) {
356 myToDispatchOnDone
.remove(each
);
357 IdeEventQueue
.getInstance().dispatchEvent(
358 new KeyEvent(owner
, each
.getID(), each
.getWhen(), each
.getModifiersEx(), each
.getKeyCode(), each
.getKeyChar(),
359 each
.getKeyLocation()));
367 if (isPendingKeyEventsRedispatched()) {
368 final Runnable
[] all
= myIdleRequests
.toArray(new Runnable
[myIdleRequests
.size()]);
369 myIdleRequests
.clear();
370 for (Runnable each
: all
) {
376 myFlushingIdleRequestsEntryCount
--;
377 if (!isIdleQueueEmpty()) {
383 public boolean isFocusTransferReady() {
384 if (!myFocusRequests
.isEmpty()) return false;
385 if (myQueue
== null) return true;
387 return !myQueue
.isSuspendMode() && !myQueue
.hasFocusEventsPending();
390 private boolean isIdleQueueEmpty() {
391 return isPendingKeyEventsRedispatched() && myIdleRequests
.isEmpty();
394 private boolean isPendingKeyEventsRedispatched() {
395 return myToDispatchOnDone
.isEmpty();
398 public boolean dispatch(KeyEvent e
) {
399 if (!Registry
.is("actionSystem.fixLostTyping")) return false;
401 if (myFlushingIdleRequestsEntryCount
> 0) return false;
403 if (!isFocusTransferReady() || !isPendingKeyEventsRedispatched()) {
404 for (FocusCommand each
: myFocusRequests
) {
405 final KeyEventProcessor processor
= each
.getProcessor();
406 if (processor
!= null) {
407 final Boolean result
= processor
.dispatch(e
, myKeyProcessorContext
);
408 if (result
!= null) {
409 return result
.booleanValue();
414 myToDispatchOnDone
.add(e
);
424 public void suspendKeyProcessingUntil(final ActionCallback done
) {
425 requestFocus(new FocusCommand(done
) {
426 public ActionCallback
run() {
429 }.saveAllocation(), true);
432 public Expirable
getTimestamp(final boolean trackOnlyForcedCommands
) {
433 return new Expirable() {
434 long myOwnStamp
= trackOnlyForcedCommands ? myForcedCmdTimestamp
: myCmdTimestamp
;
436 public boolean isExpired() {
437 return myOwnStamp
< (trackOnlyForcedCommands ? myForcedCmdTimestamp
: myCmdTimestamp
);
442 static class EdtAlarm
{
443 private final Alarm myAlarm
;
445 private EdtAlarm(Disposable parent
) {
446 myAlarm
= new Alarm(Alarm
.ThreadToUse
.OWN_THREAD
, parent
);
449 public void cancelAllRequests() {
450 myAlarm
.cancelAllRequests();
453 public void addRequest(EdtRunnable runnable
, int delay
) {
454 myAlarm
.addRequest(runnable
, delay
);
458 private void forceFinishFocusSettledown(FocusCommand cmd
, ActionCallback cmdCallback
) {
459 rejectCommand(cmd
, cmdCallback
);
463 private void rejectCommand(FocusCommand cmd
, ActionCallback callback
) {
465 resetUnforcedCommand(cmd
);
467 callback
.setRejected();
470 private class AppListener
extends ApplicationAdapter
{
473 public void applicationDeactivated(IdeFrame ideFrame
) {
474 final Component owner
= KeyboardFocusManager
.getCurrentKeyboardFocusManager().getFocusOwner();
475 Component parent
= UIUtil
.findUltimateParent(owner
);
477 if (parent
== ideFrame
) {
478 myLastFocusedAtDeactivation
.put(ideFrame
, owner
);
483 public void applicationActivated(final IdeFrame ideFrame
) {
484 final FocusCommand cmd
= myFocusCommandOnAppActivation
;
485 ActionCallback callback
= myCallbackOnActivation
;
486 myFocusCommandOnAppActivation
= null;
487 myCallbackOnActivation
= null;
490 requestFocus(cmd
, true).notify(callback
).doWhenRejected(new Runnable() {
492 focusLastFocusedComponent(ideFrame
);
497 focusLastFocusedComponent(ideFrame
);
501 private void focusLastFocusedComponent(IdeFrame ideFrame
) {
502 final KeyboardFocusManager mgr
= KeyboardFocusManager
.getCurrentKeyboardFocusManager();
503 if (mgr
.getFocusOwner() == null) {
504 Component c
= myLastFocusedAtDeactivation
.get(ideFrame
);
505 if (c
== null || !c
.isShowing()) {
506 c
= myLastFocused
.get(ideFrame
);
509 if (c
!= null && c
.isShowing()) {
510 requestFocus(c
, false);
514 myLastFocusedAtDeactivation
.remove(ideFrame
);
519 public JComponent
getFocusTargetFor(@NotNull JComponent comp
) {
520 return IdeFocusTraversalPolicy
.getPreferredFocusedComponent(comp
);
524 public Component
getFocusedDescendantFor(Component comp
) {
525 final Component focused
= KeyboardFocusManager
.getCurrentKeyboardFocusManager().getFocusOwner();
526 if (focused
== null) return null;
528 if (focused
== comp
|| SwingUtilities
.isDescendingFrom(focused
, comp
)) return focused
;
530 java
.util
.List
<JBPopup
> popups
= FocusTrackback
.getChildPopups(comp
);
531 for (JBPopup each
: popups
) {
532 if (each
.isFocused()) return focused
;
539 public boolean isFocusBeingTransferred() {
540 return !isFocusTransferReady();
544 public ActionCallback
requestDefaultFocus(boolean forced
) {
545 return new ActionCallback
.Done();