diff --git a/java/java-impl/src/com/intellij/unscramble/UnscrambleListener.java b/java/java-impl/src/com/intellij/unscramble/UnscrambleListener.java index 324200ea3548..a54cff49b2f1 100644 --- a/java/java-impl/src/com/intellij/unscramble/UnscrambleListener.java +++ b/java/java-impl/src/com/intellij/unscramble/UnscrambleListener.java @@ -27,7 +27,7 @@ import java.util.regex.Pattern; /** * @author Konstantin Bulenkov */ -public class UnscrambleListener implements ApplicationActivationListener { +public class UnscrambleListener extends ApplicationActivationListener.Adapter { private static final int MAX_STACKTRACE_SIZE = 100 * 1024; private String stacktrace = null; diff --git a/platform/lang-impl/src/com/intellij/codeInsight/documentation/QuickDocOnMouseOverManager.java b/platform/lang-impl/src/com/intellij/codeInsight/documentation/QuickDocOnMouseOverManager.java index e93d98b1c443..0d437e6b0d2e 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/documentation/QuickDocOnMouseOverManager.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/documentation/QuickDocOnMouseOverManager.java @@ -78,7 +78,7 @@ public class QuickDocOnMouseOverManager { ApplicationManager.getApplication().getMessageBus().connect().subscribe( ApplicationActivationListener.TOPIC, - new ApplicationActivationListener() { + new ApplicationActivationListener.Adapter() { @Override public void applicationActivated(IdeFrame ideFrame) { myApplicationActive = true; diff --git a/platform/platform-api/src/com/intellij/openapi/application/ApplicationActivationListener.java b/platform/platform-api/src/com/intellij/openapi/application/ApplicationActivationListener.java index ad67106c56eb..5ad5f6e569e8 100644 --- a/platform/platform-api/src/com/intellij/openapi/application/ApplicationActivationListener.java +++ b/platform/platform-api/src/com/intellij/openapi/application/ApplicationActivationListener.java @@ -34,11 +34,24 @@ 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) { } } } diff --git a/platform/platform-impl/src/com/intellij/ide/FrameStateManagerImpl.java b/platform/platform-impl/src/com/intellij/ide/FrameStateManagerImpl.java index 726c8e5e1fb5..150166b86bf4 100644 --- a/platform/platform-impl/src/com/intellij/ide/FrameStateManagerImpl.java +++ b/platform/platform-impl/src/com/intellij/ide/FrameStateManagerImpl.java @@ -50,7 +50,7 @@ public class FrameStateManagerImpl extends FrameStateManager { myShouldSynchronize = false; mySyncAlarm = new Alarm(); - app.getMessageBus().connect().subscribe(ApplicationActivationListener.TOPIC, new ApplicationActivationListener() { + app.getMessageBus().connect().subscribe(ApplicationActivationListener.TOPIC, new ApplicationActivationListener.Adapter() { @Override public void applicationActivated(IdeFrame ideFrame) { myActive.onReady(); diff --git a/platform/platform-impl/src/com/intellij/ide/IdeEventQueue.java b/platform/platform-impl/src/com/intellij/ide/IdeEventQueue.java index 5f9fe3d0ede2..c43e9ef946f2 100644 --- a/platform/platform-impl/src/com/intellij/ide/IdeEventQueue.java +++ b/platform/platform-impl/src/com/intellij/ide/IdeEventQueue.java @@ -811,12 +811,15 @@ public class IdeEventQueue extends EventQueue { if (we.getID() == WindowEvent.WINDOW_ACTIVATED || we.getID() == WindowEvent.WINDOW_GAINED_FOCUS) { appImpl.myCancelDeactivation = true; if (!appImpl.isActive()) { - appImpl.tryToApplyActivationState(true, eventWindow); + OpenAPIAccessor.getApplicationImplAccessor().applyActivation(appImpl, eventWindow); } } else if (we.getID() == WindowEvent.WINDOW_DEACTIVATED) { requestToDeactivateTime.getAndSet(System.currentTimeMillis()); + // For stuf that cannot wait timeout we notify about deactivation + OpenAPIAccessor.getApplicationImplAccessor().applyDeactivation(appImpl, eventWindow); + // 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 @@ -824,8 +827,8 @@ public class IdeEventQueue extends EventQueue { Timer timer = new Timer(Registry.intValue("app.deactivation.timeout"), new ActionListener() { public void actionPerformed(ActionEvent evt) { - if (appImpl.isActive() && !appImpl.isDeactivationCanceled()) { - appImpl.tryToApplyActivationState(false, eventWindow); + if (appImpl.isActiveDelayed() && !appImpl.isDeactivationCanceled()) { + OpenAPIAccessor.getApplicationImplAccessor().applyDelayedDeactivation(appImpl, eventWindow); } } }); diff --git a/platform/platform-impl/src/com/intellij/ide/OpenAPIAccessor.java b/platform/platform-impl/src/com/intellij/ide/OpenAPIAccessor.java new file mode 100644 index 000000000000..9ec4adfb80a0 --- /dev/null +++ b/platform/platform-impl/src/com/intellij/ide/OpenAPIAccessor.java @@ -0,0 +1,64 @@ +/* + * 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.ide; + +import com.intellij.openapi.application.impl.ApplicationImpl; +import com.intellij.util.concurrency.AtomicFieldUpdater; +import sun.misc.Unsafe; + +import java.awt.*; + +/** + * Provides ability to access ApplicationImpl package-private methods + * without reflection + */ +public final class OpenAPIAccessor { + + + private OpenAPIAccessor() {} + + private static ApplicationImplAccessor applicationImplAccessor; + private static final Unsafe unsafe = AtomicFieldUpdater.getUnsafe(); + + public interface ApplicationImplAccessor { + + boolean applyActivation (ApplicationImpl app, Window window); + + boolean applyDeactivation (ApplicationImpl app, Window window); + + boolean applyDelayedDeactivation (ApplicationImpl app, Window window); + + } + + /* + * Set an accessor object for the com.intellij.openapi.application.impl.ApplicationImpl class. + */ + public static void setApplicationImplAccessor(ApplicationImplAccessor aia) { + applicationImplAccessor = aia; + } + + /* + * Retrieve the accessor object for the com.intellij.openapi.application.impl.ApplicationImpl class. + */ + public static ApplicationImplAccessor getApplicationImplAccessor() { + if (applicationImplAccessor == null) { + unsafe.ensureClassInitialized(ApplicationImpl.class); + } + + return applicationImplAccessor; + } + +} diff --git a/platform/platform-impl/src/com/intellij/openapi/actionSystem/impl/ActionManagerImpl.java b/platform/platform-impl/src/com/intellij/openapi/actionSystem/impl/ActionManagerImpl.java index 72dc1ee4d063..a64a2eff492d 100644 --- a/platform/platform-impl/src/com/intellij/openapi/actionSystem/impl/ActionManagerImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/actionSystem/impl/ActionManagerImpl.java @@ -1420,7 +1420,7 @@ public final class ActionManagerImpl extends ActionManagerEx implements Applicat addActionListener(this); setRepeats(true); final MessageBusConnection connection = ApplicationManager.getApplication().getMessageBus().connect(); - connection.subscribe(ApplicationActivationListener.TOPIC, new ApplicationActivationListener() { + connection.subscribe(ApplicationActivationListener.TOPIC, new ApplicationActivationListener.Adapter() { @Override public void applicationActivated(IdeFrame ideFrame) { setDelay(TIMER_DELAY); diff --git a/platform/platform-impl/src/com/intellij/openapi/actionSystem/impl/ActionPopupMenuImpl.java b/platform/platform-impl/src/com/intellij/openapi/actionSystem/impl/ActionPopupMenuImpl.java index de41534d68f8..5d5b8d3b1f30 100644 --- a/platform/platform-impl/src/com/intellij/openapi/actionSystem/impl/ActionPopupMenuImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/actionSystem/impl/ActionPopupMenuImpl.java @@ -42,7 +42,7 @@ import java.awt.*; * @author Anton Katilin * @author Vladimir Kondratyev */ -public final class ActionPopupMenuImpl implements ActionPopupMenu, ApplicationActivationListener { +public final class ActionPopupMenuImpl extends ApplicationActivationListener.Adapter implements ActionPopupMenu { private final MyMenu myMenu; private final ActionManagerImpl myManager; diff --git a/platform/platform-impl/src/com/intellij/openapi/application/impl/ApplicationImpl.java b/platform/platform-impl/src/com/intellij/openapi/application/impl/ApplicationImpl.java index 72a576aee7b3..d4b3ecbf94ea 100644 --- a/platform/platform-impl/src/com/intellij/openapi/application/impl/ApplicationImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/application/impl/ApplicationImpl.java @@ -131,10 +131,36 @@ public class ApplicationImpl extends PlatformComponentManagerImpl implements App @NonNls 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; + + static { + OpenAPIAccessor.setApplicationImplAccessor(new OpenAPIAccessor.ApplicationImplAccessor() { + + @Override + public boolean applyActivation(ApplicationImpl app, Window window) { + return app.applyActivation(window); + } + + @Override + public boolean applyDeactivation(ApplicationImpl app, Window window) { + return app.applyDeactivation(window); + } + + @Override + public boolean applyDelayedDeactivation(ApplicationImpl app, Window window) { + return app.applyDelayedDeactivation(window); + } + }); + } + + public boolean isActiveDelayed() { + return myActiveDelayed; + } + private static class Status { // higher three bits are for IS_* flags // lower bits are for edtSafe counter @@ -1177,28 +1203,47 @@ public class ApplicationImpl extends PlatformComponentManagerImpl implements App return myCancelDeactivation; } - public boolean tryToApplyActivationState(boolean active, Window window) { - final Component frame = UIUtil.findUltimateParent(window); + boolean applyActivation (Window window) { + if (!isActive()) { + myActive = true; + myActiveDelayed = true; + IdeFrame ideFrame = getIdeFrameFromWindow(window); + if (ideFrame != null) { + getMessageBus().syncPublisher(ApplicationActivationListener.TOPIC).applicationActivated(ideFrame); + } + } + return false; + } - if (frame instanceof IdeFrame) { - final IdeFrame ideFrame = (IdeFrame)frame; - if (isActive() != active) { - myActive = active; - System.setProperty("idea.active", String.valueOf(myActive)); - ApplicationActivationListener publisher = getMessageBus().syncPublisher(ApplicationActivationListener.TOPIC); - if (active) { - publisher.applicationActivated(ideFrame); - } - else { - publisher.applicationDeactivated(ideFrame); - } + boolean applyDeactivation (Window window) { + if (isActive()) { + myActive = false; + IdeFrame ideFrame = getIdeFrameFromWindow(window); + if (ideFrame != null) { + getMessageBus().syncPublisher(ApplicationActivationListener.TOPIC).applicationDeactivated(ideFrame); return true; } } - return false; } + 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; + } + @Override public boolean isActive() { if (isUnitTestMode()) return true; diff --git a/platform/platform-impl/src/com/intellij/openapi/wm/impl/FocusManagerImpl.java b/platform/platform-impl/src/com/intellij/openapi/wm/impl/FocusManagerImpl.java index 545a12d416c3..874786db021e 100644 --- a/platform/platform-impl/src/com/intellij/openapi/wm/impl/FocusManagerImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/wm/impl/FocusManagerImpl.java @@ -1068,16 +1068,7 @@ public class FocusManagerImpl extends IdeFocusManager implements Disposable { callback.setRejected(); } - private class AppListener implements ApplicationActivationListener { - @Override - public void applicationDeactivated(IdeFrame ideFrame) { - final Component owner = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner(); - Component parent = UIUtil.findUltimateParent(owner); - - if (parent == ideFrame) { - myLastFocusedAtDeactivation.put(ideFrame, owner); - } - } + private class AppListener extends ApplicationActivationListener.Adapter { @Override public void applicationActivated(final IdeFrame ideFrame) { @@ -1093,6 +1084,16 @@ public class FocusManagerImpl extends IdeFocusManager implements Disposable { } } + @Override + public void delayedApplicationDeactivated(IdeFrame ideFrame) { + final Component owner = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner(); + Component parent = UIUtil.findUltimateParent(owner); + + if (parent == ideFrame) { + myLastFocusedAtDeactivation.put(ideFrame, owner); + } + } + private void focusLastFocusedComponent(IdeFrame ideFrame) { final KeyboardFocusManager mgr = KeyboardFocusManager.getCurrentKeyboardFocusManager(); if (mgr.getFocusOwner() == null) {