[ui] fixes app activation/deactivation tracking via window focus events (IDEA-142728)

This commit is contained in:
Roman Shevchenko
2015-07-29 21:38:46 +02:00
parent 9adc419f44
commit 96d888f2d0
5 changed files with 40 additions and 131 deletions
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2014 JetBrains s.r.o.
* Copyright 2000-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -34,24 +34,11 @@ public interface ApplicationActivationListener {
*/
void applicationDeactivated(IdeFrame ideFrame);
/**
* This is more precise notification than {code applicationDeactivated} callback.
* It is intended for focus subsystem and purposes where we do not want
* to be bothered by false application deactivation events.
*
* The shortcoming of the method is that a notification is delivered
* with a delay. See {code app.deactivation.timeout} key in the registry
*/
void delayedApplicationDeactivated(IdeFrame ideFrame);
abstract class Adapter implements ApplicationActivationListener {
@Override
public void applicationActivated(IdeFrame ideFrame) { }
@Override
public void applicationDeactivated(IdeFrame ideFrame) { }
@Override
public void delayedApplicationDeactivated(IdeFrame ideFrame) { }
}
}
@@ -39,10 +39,7 @@ import com.intellij.openapi.util.ExpirableRunnable;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.util.registry.Registry;
import com.intellij.openapi.wm.IdeFocusManager;
import com.intellij.openapi.wm.IdeFrame;
import com.intellij.openapi.wm.WindowManager;
import com.intellij.openapi.wm.ex.WindowManagerEx;
import com.intellij.openapi.wm.impl.FocusManagerImpl;
import com.intellij.util.Alarm;
import com.intellij.util.ReflectionUtil;
import com.intellij.util.containers.ContainerUtil;
@@ -65,7 +62,6 @@ import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicLong;
/**
* @author Vladimir Kondratyev
@@ -540,7 +536,9 @@ public class IdeEventQueue extends EventQueue {
myEventCount++;
if (processAppActivationEvents(e)) return;
if (e instanceof WindowEvent) {
processAppActivationEvents((WindowEvent)e);
}
if (!typeAheadFlushing) {
fixStickyFocusedComponents(e);
@@ -794,72 +792,35 @@ public class IdeEventQueue extends EventQueue {
return peekEvent(WindowEvent.WINDOW_OPENED) != null;
}
private static AtomicLong requestToDeactivateTime = new AtomicLong(System.currentTimeMillis());
private static final int APP_DEACTIVATION_DELAY = 50;
private static final Alarm ourDeactivationAlarm = new Alarm(Alarm.ThreadToUse.SWING_THREAD);
private static boolean processAppActivationEvents(AWTEvent e) {
Application app = ApplicationManager.getApplication();
if (!(app instanceof ApplicationImpl)) return false;
final ApplicationImpl appImpl = (ApplicationImpl)app;
if (e instanceof WindowEvent) {
final WindowEvent we = (WindowEvent)e;
// Only Dialogs and Frames get window activated events.
// Let's handle Windows this way.
final Window eventWindow = we.getWindow();
if (we.getID() == WindowEvent.WINDOW_ACTIVATED || we.getID() == WindowEvent.WINDOW_GAINED_FOCUS) {
//if () {
// getPopupManager().closeAllPopups();
//}
appImpl.myCancelDeactivation = true;
if (!appImpl.isActive()) {
appImpl.tryToApplyActivationState(eventWindow, true, false);
/*
* This method processes "external" focus events (i.e. those induced by a window manager) and tracks
* an application activation/deactivation state from them (an application is active if at least one it's windows has the focus).
*
* One special case is closing a child dialog: it generates a focus-lost event immediately followed by a focus-gained one.
* To avoid (or rather reduce a chance of) unneeded app-deactivation event the processing of the focus-lost event
* is slightly delayed.
*/
private static void processAppActivationEvents(final WindowEvent e) {
if ((e.getID() == WindowEvent.WINDOW_GAINED_FOCUS || e.getID() == WindowEvent.WINDOW_LOST_FOCUS) && e.getOppositeWindow() == null) {
final Application app = ApplicationManager.getApplication();
if (app instanceof ApplicationImpl) {
if (e.getID() == WindowEvent.WINDOW_GAINED_FOCUS) {
ourDeactivationAlarm.cancelAllRequests();
((ApplicationImpl)app).tryToApplyActivationState(e.getWindow(), true);
}
}
else if (we.getID() == WindowEvent.WINDOW_DEACTIVATED) {
requestToDeactivateTime.getAndSet(System.currentTimeMillis());
// For stuff that cannot wait we notify about deactivation immediately
appImpl.tryToApplyActivationState(eventWindow, false, true);
// We do not know for sure that application is going to be inactive,
// we could just be showing a popup or another transient window.
// So let's postpone the application deactivation for a while
appImpl.myCancelDeactivation = false;
Timer timer = new Timer(Registry.intValue("app.deactivation.timeout"), new ActionListener() {
public void actionPerformed(ActionEvent evt) {
if (appImpl.isActiveDelayed() && !appImpl.isDeactivationCanceled()) {
appImpl.tryToApplyActivationState(eventWindow, false, false);
else {
ourDeactivationAlarm.addRequest(new Runnable() {
@Override
public void run() {
((ApplicationImpl)app).tryToApplyActivationState(e.getWindow(), false);
}
}
});
timer.setRepeats(false);
timer.start();
}
if (we.getID() == WindowEvent.WINDOW_DEACTIVATED || we.getID() == WindowEvent.WINDOW_LOST_FOCUS) {
Component frame = UIUtil.findUltimateParent(eventWindow);
Component focusOwnerInDeactivatedWindow = eventWindow.getMostRecentFocusOwner();
IdeFrame[] allProjectFrames = WindowManager.getInstance().getAllProjectFrames();
if (focusOwnerInDeactivatedWindow != null) {
for (IdeFrame ideFrame : allProjectFrames) {
JFrame aFrame = WindowManager.getInstance().getFrame(ideFrame.getProject());
if (aFrame.equals(frame)) {
IdeFocusManager focusManager = IdeFocusManager.getGlobalInstance();
if (focusManager instanceof FocusManagerImpl) {
((FocusManagerImpl)focusManager).setLastFocusedAtDeactivation(ideFrame, focusOwnerInDeactivatedWindow);
}
}
}
}, APP_DEACTIVATION_DELAY);
}
}
}
return false;
}
private void defaultDispatchEvent(final AWTEvent e) {
@@ -129,16 +129,10 @@ public class ApplicationImpl extends PlatformComponentManagerImpl implements App
private static final String WAS_EVER_SHOWN = "was.ever.shown";
private volatile boolean myActive;
private volatile boolean myActiveDelayed;
public volatile boolean myCancelDeactivation;
private static final int IS_EDT_FLAG = 1<<30; // we don't mess with sign bit since we want to do arithmetic
private static final int IS_READ_LOCK_ACQUIRED_FLAG = 1<<29;
public boolean isActiveDelayed() {
return myActiveDelayed;
}
private static class Status {
// higher three bits are for IS_* flags
// lower bits are for edtSafe counter
@@ -1193,10 +1187,6 @@ public class ApplicationImpl extends PlatformComponentManagerImpl implements App
return true;
}
public boolean isDeactivationCanceled() {
return myCancelDeactivation;
}
/**
* !!!!! CAUTION !!!!!
* !!!!! CAUTION !!!!!
@@ -1212,58 +1202,31 @@ public class ApplicationImpl extends PlatformComponentManagerImpl implements App
*
* There are no legitimate usages of the method outside of IdeEventQueue.processAppActivationEvents()
*/
public boolean tryToApplyActivationState(Window window, boolean activation, boolean immediate) {
return activation ? applyActivation(window) :
immediate ? applyDeactivation(window)
: applyDelayedDeactivation(window);
}
private boolean applyActivation(Window window) {
if (!isActive()) {
public void tryToApplyActivationState(Window window, boolean activation) {
if (activation && !isActive()) {
myActive = true;
myActiveDelayed = true;
IdeFrame ideFrame = getIdeFrameFromWindow(window);
if (ideFrame != null) {
getMessageBus().syncPublisher(ApplicationActivationListener.TOPIC).applicationActivated(ideFrame);
}
}
return false;
}
private boolean applyDeactivation(Window window) {
if (isActive()) {
else if (!activation && isActive()) {
myActive = false;
IdeFrame ideFrame = getIdeFrameFromWindow(window);
if (ideFrame != null) {
getMessageBus().syncPublisher(ApplicationActivationListener.TOPIC).applicationDeactivated(ideFrame);
return true;
}
}
return false;
}
private boolean applyDelayedDeactivation(Window window) {
if (isActiveDelayed()) {
myActiveDelayed = false;
IdeFrame ideFrame = getIdeFrameFromWindow(window);
if (ideFrame != null) {
getMessageBus().syncPublisher(ApplicationActivationListener.TOPIC).delayedApplicationDeactivated(ideFrame);
return true;
}
}
return false;
}
IdeFrame getIdeFrameFromWindow (Window window) {
final Component frame = UIUtil.findUltimateParent(window);
return (frame instanceof IdeFrame) ? (IdeFrame)frame : null;
private static IdeFrame getIdeFrameFromWindow(Window window) {
Component frame = UIUtil.findUltimateParent(window);
return frame instanceof IdeFrame ? (IdeFrame)frame : null;
}
@Override
public boolean isActive() {
if (isUnitTestMode()) return true;
return KeyboardFocusManager.getCurrentKeyboardFocusManager().getActiveWindow() != null || myActive;
return isUnitTestMode() || myActive;
}
@NotNull
@@ -1069,7 +1069,6 @@ public class FocusManagerImpl extends IdeFocusManager implements Disposable {
}
private class AppListener extends ApplicationActivationListener.Adapter {
@Override
public void applicationActivated(final IdeFrame ideFrame) {
final FocusCommand cmd = myFocusCommandOnAppActivation;
@@ -1085,13 +1084,13 @@ public class FocusManagerImpl extends IdeFocusManager implements Disposable {
}
@Override
public void delayedApplicationDeactivated(IdeFrame ideFrame) {
final Component owner = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner();
Component parent = UIUtil.findUltimateParent(owner);
public void applicationDeactivated(IdeFrame ideFrame) {
final Component owner = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner();
Component parent = UIUtil.findUltimateParent(owner);
if (parent == ideFrame) {
myLastFocusedAtDeactivation.put(ideFrame, owner);
}
if (parent == ideFrame) {
myLastFocusedAtDeactivation.put(ideFrame, owner);
}
}
private void focusLastFocusedComponent(IdeFrame ideFrame) {
@@ -621,7 +621,6 @@ tests.view.old.statistics.panel=false
tfs.set.connection.timeout=false
app.deactivation.timeout=1500
ide.mac.yosemite.laf=false
ide.mac.yosemite.laf.restartRequired=true
ide.mac.yosemite.laf.description=Replaces Aqua LaF to enhanced IntelliJ LaF