fighting the linux focus problem
[fedora-idea.git] / platform / platform-impl / src / com / intellij / openapi / wm / impl / FocusManagerImpl.java
blob552c15a08bc559a95a614a90f8f3d4cf75a2ea67
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.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;
38 import javax.swing.*;
39 import java.awt.*;
40 import java.awt.event.FocusEvent;
41 import java.awt.event.KeyEvent;
42 import java.lang.ref.WeakReference;
43 import java.util.ArrayList;
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 myFocusRequests.add(command);
130 SwingUtilities.invokeLater(new Runnable() {
131 public void run() {
132 resetUnforcedCommand(command);
133 _requestFocus(command, forced, result);
137 else {
138 _requestFocus(command, forced, result);
141 result.doWhenProcessed(new Runnable() {
142 public void run() {
143 restartIdleAlarm();
147 return result;
150 private void _requestFocus(final FocusCommand command, final boolean forced, final ActionCallback result) {
151 if (checkForRejectOrByPass(command, forced, result)) return;
153 setCommand(command);
155 if (forced) {
156 myForcedFocusRequestsAlarm.cancelAllRequests();
157 setLastEffectiveForcedRequest(command);
160 SwingUtilities.invokeLater(new Runnable() {
161 public void run() {
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) {
168 @Override
169 protected void onTimeout() {
170 forceFinishFocusSettledown(command, result);
174 myCmdTimestamp++;
175 if (forced) {
176 myForcedCmdTimestamp++;
179 command.run().doWhenDone(new Runnable() {
180 public void run() {
181 SwingUtilities.invokeLater(new Runnable() {
182 public void run() {
183 result.setDone();
187 }).doWhenRejected(new Runnable() {
188 public void run() {
189 result.setRejected();
191 }).doWhenProcessed(new Runnable() {
192 public void run() {
193 resetCommand(command);
195 if (forced) {
196 myForcedFocusRequestsAlarm.addRequest(new EdtRunnable() {
197 public void runEdt() {
198 setLastEffectiveForcedRequest(null);
200 }, 250);
203 }).notify(focusTimeout);
205 else {
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);
215 return true;
218 final FocusCommand lastRequest = getLastEffectiveForcedRequest();
220 if (!forced && !isUnforcedRequestAllowed()) {
221 if (cmd.equals(lastRequest)) {
222 result.setDone();
224 else {
225 rejectCommand(cmd, result);
227 return true;
231 if (lastRequest != null && lastRequest.dominatesOver(cmd)) {
232 rejectCommand(cmd, result);
233 return true;
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;
247 return true;
250 return false;
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);
286 @Nullable
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() {
311 public void run() {
312 myToDispatchOnDone.addAll(events);
313 restartIdleAlarm();
319 public void doWhenFocusSettlesDown(@NotNull final Runnable runnable) {
320 final boolean needsRestart = isIdleQueueEmpty();
321 myIdleRequests.add(runnable);
322 if (needsRestart) {
323 restartIdleAlarm();
327 private void restartIdleAlarm() {
328 myIdleAlarm.cancelAllRequests();
329 myIdleAlarm.addRequest(myIdleRunnable, Registry.intValue("actionSystem.focusIdleTimeout"));
332 private void flushIdleRequests() {
333 try {
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;
348 else {
349 myToDispatchOnDone.remove(each);
350 continue;
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()));
361 else {
362 break;
367 if (isPendingKeyEventsRedispatched()) {
368 final Runnable[] all = myIdleRequests.toArray(new Runnable[myIdleRequests.size()]);
369 myIdleRequests.clear();
370 for (Runnable each : all) {
371 each.run();
375 finally {
376 myFlushingIdleRequestsEntryCount--;
377 if (!isIdleQueueEmpty()) {
378 restartIdleAlarm();
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);
415 restartIdleAlarm();
417 return true;
419 else {
420 return false;
424 public void suspendKeyProcessingUntil(final ActionCallback done) {
425 requestFocus(new FocusCommand(done) {
426 public ActionCallback run() {
427 return done;
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) {
464 resetCommand(cmd);
465 resetUnforcedCommand(cmd);
467 callback.setRejected();
470 private class AppListener extends ApplicationAdapter {
472 @Override
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);
482 @Override
483 public void applicationActivated(final IdeFrame ideFrame) {
484 final FocusCommand cmd = myFocusCommandOnAppActivation;
485 ActionCallback callback = myCallbackOnActivation;
486 myFocusCommandOnAppActivation = null;
487 myCallbackOnActivation = null;
489 if (cmd != null) {
490 requestFocus(cmd, true).notify(callback).doWhenRejected(new Runnable() {
491 public void run() {
492 focusLastFocusedComponent(ideFrame);
496 else {
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);
518 @Override
519 public JComponent getFocusTargetFor(@NotNull JComponent comp) {
520 return IdeFocusTraversalPolicy.getPreferredFocusedComponent(comp);
523 @Override
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;
535 return null;
538 @Override
539 public boolean isFocusBeingTransferred() {
540 return !isFocusTransferReady();
543 @Override
544 public ActionCallback requestDefaultFocus(boolean forced) {
545 return new ActionCallback.Done();