sticky documentation popup [take 1]
[fedora-idea.git] / platform / platform-impl / src / com / intellij / openapi / wm / impl / FocusManagerImpl.java
blob69c8cd6d29dab6731768de4cb3beb0883d6df68b
1 /*
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;
37 import javax.swing.*;
38 import java.awt.*;
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;
44 import java.util.Map;
45 import java.util.Set;
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()) {
76 flushIdleRequests();
78 else {
79 restartIdleAlarm();
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();
92 myWindowManager = wm;
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);
114 return false;
116 }, this);
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();
127 if (!forced) {
128 if (!myFocusRequests.contains(command)) {
129 myFocusRequests.add(command);
132 SwingUtilities.invokeLater(new Runnable() {
133 public void run() {
134 resetUnforcedCommand(command);
135 _requestFocus(command, forced, result);
139 else {
140 _requestFocus(command, forced, result);
143 result.doWhenProcessed(new Runnable() {
144 public void run() {
145 restartIdleAlarm();
149 return result;
152 private void _requestFocus(final FocusCommand command, final boolean forced, final ActionCallback result) {
153 if (checkForRejectOrByPass(command, forced, result)) return;
155 setCommand(command);
156 command.setCallback(result);
158 if (forced) {
159 myForcedFocusRequestsAlarm.cancelAllRequests();
160 setLastEffectiveForcedRequest(command);
163 SwingUtilities.invokeLater(new Runnable() {
164 public void run() {
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) {
171 @Override
172 protected void onTimeout() {
173 forceFinishFocusSettledown(command, result);
177 myCmdTimestamp++;
178 if (forced) {
179 myForcedCmdTimestamp++;
182 command.run().doWhenDone(new Runnable() {
183 public void run() {
184 SwingUtilities.invokeLater(new Runnable() {
185 public void run() {
186 result.setDone();
190 }).doWhenRejected(new Runnable() {
191 public void run() {
192 result.setRejected();
194 }).doWhenProcessed(new Runnable() {
195 public void run() {
196 resetCommand(command, false);
198 if (forced) {
199 myForcedFocusRequestsAlarm.addRequest(new EdtRunnable() {
200 public void runEdt() {
201 setLastEffectiveForcedRequest(null);
203 }, 250);
206 }).notify(focusTimeout);
208 else {
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);
218 return true;
221 final FocusCommand lastRequest = getLastEffectiveForcedRequest();
223 if (!forced && !isUnforcedRequestAllowed()) {
224 if (cmd.equals(lastRequest)) {
225 resetCommand(cmd, false);
226 result.setDone();
228 else {
229 rejectCommand(cmd, result);
231 return true;
235 if (lastRequest != null && lastRequest.dominatesOver(cmd)) {
236 rejectCommand(cmd, result);
237 return true;
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;
254 return true;
257 return false;
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);
280 if (reject) {
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);
300 @Nullable
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() {
325 public void run() {
326 myToDispatchOnDone.addAll(events);
327 restartIdleAlarm();
333 public void doWhenFocusSettlesDown(@NotNull final Runnable runnable) {
334 final boolean needsRestart = isIdleQueueEmpty();
335 myIdleRequests.add(runnable);
336 if (needsRestart) {
337 restartIdleAlarm();
341 private void restartIdleAlarm() {
342 myIdleAlarm.cancelAllRequests();
343 myIdleAlarm.addRequest(myIdleRunnable, Registry.intValue("actionSystem.focusIdleTimeout"));
346 private void flushIdleRequests() {
347 try {
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;
362 else {
363 myToDispatchOnDone.remove(each);
364 continue;
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()));
375 else {
376 break;
381 if (isPendingKeyEventsRedispatched()) {
382 final Runnable[] all = myIdleRequests.toArray(new Runnable[myIdleRequests.size()]);
383 myIdleRequests.clear();
384 for (Runnable each : all) {
385 each.run();
389 finally {
390 myFlushingIdleRequestsEntryCount--;
391 if (!isIdleQueueEmpty()) {
392 restartIdleAlarm();
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);
414 wasChanged = true;
418 if (wasChanged && myFocusRequests.size() == 0) {
419 restartIdleAlarm();
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);
448 restartIdleAlarm();
450 return true;
452 else {
453 return false;
457 public void suspendKeyProcessingUntil(final ActionCallback done) {
458 requestFocus(new FocusCommand(done) {
459 public ActionCallback run() {
460 return done;
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 {
505 @Override
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);
515 @Override
516 public void applicationActivated(final IdeFrame ideFrame) {
517 final FocusCommand cmd = myFocusCommandOnAppActivation;
518 ActionCallback callback = myCallbackOnActivation;
519 myFocusCommandOnAppActivation = null;
520 myCallbackOnActivation = null;
522 if (cmd != null) {
523 requestFocus(cmd, true).notify(callback).doWhenRejected(new Runnable() {
524 public void run() {
525 focusLastFocusedComponent(ideFrame);
529 else {
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);
551 @Override
552 public JComponent getFocusTargetFor(@NotNull JComponent comp) {
553 return IdeFocusTraversalPolicy.getPreferredFocusedComponent(comp);
556 @Override
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;
568 return null;
571 @Override
572 public boolean isFocusBeingTransferred() {
573 return !isFocusTransferReady();
576 @Override
577 public ActionCallback requestDefaultFocus(boolean forced) {
578 return new ActionCallback.Done();