[ui] macOS frame decorator cleanup

- dropping pre-10.7 code
- moving custom URL handler to a more appropriate place

GitOrigin-RevId: f066884c0ed288c614fcc17a30d67def936a2373
This commit is contained in:
Roman Shevchenko
2019-12-16 20:36:06 +00:00
committed by intellij-monorepo-bot
parent 46b8dbe4c2
commit f69d7123ce
3 changed files with 130 additions and 266 deletions
@@ -10,7 +10,6 @@ import com.intellij.openapi.util.registry.Registry;
import com.intellij.ui.ComponentUtil;
import com.intellij.ui.ScreenUtil;
import com.intellij.ui.mac.MacMainFrameDecorator;
import com.intellij.util.PlatformUtils;
import com.intellij.util.ui.UIUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -48,7 +47,7 @@ public abstract class IdeFrameDecorator implements IdeFrameImpl.FrameDecorator {
public static IdeFrameDecorator decorate(@NotNull JFrame frame, @NotNull Disposable parentDisposable) {
try {
if (SystemInfo.isMac) {
return new MacMainFrameDecorator(frame, PlatformUtils.isAppCode(), parentDisposable);
return new MacMainFrameDecorator(frame, parentDisposable);
}
else if (SystemInfo.isWindows) {
return new WinMainFrameDecorator(frame);
@@ -191,4 +190,4 @@ public abstract class IdeFrameDecorator implements IdeFrameImpl.FrameDecorator {
public static boolean isCustomDecorationActive() {
return SystemInfo.isWindows && Registry.is("ide.win.frame.decoration") && JdkEx.isCustomDecorationSupported();
}
}
}
@@ -3,25 +3,15 @@ package com.intellij.ui.mac;
import com.apple.eawt.*;
import com.intellij.ide.ActiveWindowsWatcher;
import com.intellij.ide.ui.UISettings;
import com.intellij.ide.ui.UISettingsListener;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.impl.ApplicationInfoImpl;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.BuildNumber;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.util.registry.Registry;
import com.intellij.openapi.wm.impl.IdeFrameDecorator;
import com.intellij.openapi.wm.impl.IdeRootPane;
import com.intellij.ui.CustomProtocolHandler;
import com.intellij.ui.mac.foundation.Foundation;
import com.intellij.ui.mac.foundation.ID;
import com.intellij.ui.mac.foundation.MacUtil;
import com.intellij.util.EventDispatcher;
import com.intellij.util.ui.UIUtil;
import com.sun.jna.Callback;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.concurrency.AsyncPromise;
import org.jetbrains.concurrency.Promise;
@@ -32,62 +22,49 @@ import java.awt.*;
import java.lang.reflect.Method;
import java.util.EventListener;
import java.util.LinkedList;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Supplier;
import static com.intellij.ui.mac.foundation.Foundation.invoke;
public final class MacMainFrameDecorator extends IdeFrameDecorator {
private static final Logger LOG = Logger.getInstance(MacMainFrameDecorator.class);
private final FullscreenQueue<Runnable> myFullscreenQueue = new FullscreenQueue<>();
private final EventDispatcher<FSListener> myDispatcher = EventDispatcher.create(FSListener.class);
private interface FSListener extends FullScreenListener, EventListener {}
private static class FSAdapter extends FullScreenAdapter implements FSListener {}
private static class FullscreenQueue <T extends Runnable> {
private boolean waitingForAppKit = false;
private final LinkedList<Runnable> queueModel = new LinkedList<>();
private static class FullScreenQueue {
private final LinkedList<Runnable> myQueue = new LinkedList<>();
private boolean myWaitingForAppKit = false;
synchronized void runOrEnqueue(@NotNull T runnable) {
if (waitingForAppKit) {
enqueue(runnable);
synchronized void runOrEnqueue(Runnable runnable) {
if (myWaitingForAppKit) {
myQueue.add(runnable);
}
else {
ApplicationManager.getApplication().invokeLater(runnable);
waitingForAppKit = true;
myWaitingForAppKit = true;
}
}
synchronized private void enqueue (final T runnable) {
queueModel.add(runnable);
}
synchronized void runFromQueue () {
if (!queueModel.isEmpty()) {
queueModel.remove().run();
waitingForAppKit = true;
} else {
waitingForAppKit = false;
synchronized void runFromQueue() {
if (!myQueue.isEmpty()) {
myQueue.remove().run();
myWaitingForAppKit = true;
}
else {
myWaitingForAppKit = false;
}
}
}
private void enterFullscreen() {
private void enterFullScreen() {
myInFullScreen = true;
storeFullScreenStateIfNeeded();
myFullscreenQueue.runFromQueue();
myFullScreenQueue.runFromQueue();
}
private void exitFullscreen() {
private void exitFullScreen() {
myInFullScreen = false;
storeFullScreenStateIfNeeded();
JRootPane rootPane = myFrame.getRootPane();
if (rootPane != null) rootPane.putClientProperty(FULL_SCREEN, null);
myFullscreenQueue.runFromQueue();
myFullScreenQueue.runFromQueue();
}
private void storeFullScreenStateIfNeeded() {
@@ -96,7 +73,6 @@ public final class MacMainFrameDecorator extends IdeFrameDecorator {
}
public static final String FULL_SCREEN = "Idea.Is.In.FullScreen.Mode.Now";
private static boolean HAS_FULLSCREEN_UTILITIES;
private static Method requestToggleFullScreenMethod;
private static Method enterFullScreenMethod;
@@ -104,211 +80,89 @@ public final class MacMainFrameDecorator extends IdeFrameDecorator {
static {
try {
//noinspection SpellCheckingInspection
Class.forName("com.apple.eawt.FullScreenUtilities");
//noinspection JavaReflectionMemberAccess
enterFullScreenMethod = Application.class.getMethod("requestEnterFullScreen", Window.class);
leaveFullScreenMethod = Application.class.getMethod("requestLeaveFullScreen", Window.class);
HAS_FULLSCREEN_UTILITIES = true;
}
catch (Exception e) {
HAS_FULLSCREEN_UTILITIES = false;
}
// temporary solution for the old Runtime
if (!HAS_FULLSCREEN_UTILITIES) {
try {
Class.forName("com.apple.eawt.FullScreenUtilities");
//noinspection JavaReflectionMemberAccess
enterFullScreenMethod = Application.class.getMethod("requestEnterFullScreen", Window.class);
//noinspection JavaReflectionMemberAccess
leaveFullScreenMethod = Application.class.getMethod("requestLeaveFullScreen", Window.class);
}
catch (NoSuchMethodException e) {
// temporary solution for the old runtime
//noinspection JavaReflectionMemberAccess
requestToggleFullScreenMethod = Application.class.getMethod("requestToggleFullScreen", Window.class);
HAS_FULLSCREEN_UTILITIES = true;
}
catch (Exception e) {
HAS_FULLSCREEN_UTILITIES = false;
}
}
catch (Exception e) {
Logger.getInstance(MacMainFrameDecorator.class).debug(e);
}
}
public static final boolean FULL_SCREEN_AVAILABLE = HAS_FULLSCREEN_UTILITIES;
private static boolean SHOWN = false;
private static final Callback SET_VISIBLE_CALLBACK = new Callback() {
@SuppressWarnings("unused")
public void callback(ID caller, ID selector, ID value) {
SHOWN = value.intValue() == 1;
SwingUtilities.invokeLater(CURRENT_SETTER);
}
};
private static final Callback IS_VISIBLE = new Callback() {
@SuppressWarnings("unused")
public boolean callback(ID caller) {
return SHOWN;
}
};
private static final AtomicInteger UNIQUE_COUNTER = new AtomicInteger(0);
public static final Runnable TOOLBAR_SETTER = () -> {
final UISettings settings = UISettings.getInstance();
settings.setShowMainToolbar(SHOWN);
settings.fireUISettingsChanged();
};
public static final Runnable NAVBAR_SETTER = () -> {
final UISettings settings = UISettings.getInstance();
settings.setShowNavigationBar(SHOWN);
settings.fireUISettingsChanged();
};
public static final Supplier<Boolean> NAV_BAR_GETTER = () -> UISettings.getInstance().getShowNavigationBar();
public static final Supplier<Boolean> TOOLBAR_GETTER = () -> UISettings.getInstance().getShowMainToolbar();
private static Runnable CURRENT_SETTER = null;
private static Supplier<Boolean> CURRENT_GETTER = null;
private static CustomProtocolHandler ourProtocolHandler = null;
private final FullScreenQueue myFullScreenQueue = new FullScreenQueue();
private final EventDispatcher<FSListener> myDispatcher = EventDispatcher.create(FSListener.class);
private boolean myInFullScreen;
public MacMainFrameDecorator(@NotNull JFrame frame, boolean navBar, @NotNull Disposable parentDisposable) {
public MacMainFrameDecorator(@NotNull JFrame frame, @NotNull Disposable parentDisposable) {
super(frame);
if (CURRENT_SETTER == null) {
//noinspection AssignmentToStaticFieldFromInstanceMethod
CURRENT_SETTER = navBar ? NAVBAR_SETTER : TOOLBAR_SETTER;
//noinspection AssignmentToStaticFieldFromInstanceMethod
CURRENT_GETTER = navBar ? NAV_BAR_GETTER : TOOLBAR_GETTER;
//noinspection AssignmentToStaticFieldFromInstanceMethod
SHOWN = CURRENT_GETTER.get();
}
if (leaveFullScreenMethod != null || requestToggleFullScreenMethod != null) {
FullScreenUtilities.setWindowCanFullScreen(frame, true);
//noinspection Convert2Lambda
ApplicationManager.getApplication().getMessageBus().connect(parentDisposable).subscribe(UISettingsListener.TOPIC, new UISettingsListener() {
@Override
public void uiSettingsChanged(@NotNull UISettings uiSettings) {
if (CURRENT_GETTER != null) {
//noinspection AssignmentToStaticFieldFromInstanceMethod
SHOWN = CURRENT_GETTER.get();
}
}
});
final ID pool = invoke("NSAutoreleasePool", "new");
int v = UNIQUE_COUNTER.incrementAndGet();
try {
if (SystemInfo.isMacOSLion) {
if (!FULL_SCREEN_AVAILABLE) {
return;
}
FullScreenUtilities.setWindowCanFullScreen(frame, true);
// Native fullscreen listener can be set only once
FullScreenUtilities.addFullScreenListenerTo(frame, new FullScreenListener() {
@Override
public void windowEnteringFullScreen(AppEvent.FullScreenEvent event) {
myDispatcher.getMulticaster().windowEnteringFullScreen(event);
}
@Override
public void windowEnteredFullScreen(AppEvent.FullScreenEvent event) {
myDispatcher.getMulticaster().windowEnteredFullScreen(event);
}
@Override
public void windowExitingFullScreen(AppEvent.FullScreenEvent event) {
myDispatcher.getMulticaster().windowExitingFullScreen(event);
}
@Override
public void windowExitedFullScreen(AppEvent.FullScreenEvent event) {
myDispatcher.getMulticaster().windowExitedFullScreen(event);
}
});
myDispatcher.addListener(new FSAdapter() {
@Override
public void windowEnteringFullScreen(AppEvent.FullScreenEvent event) {
JRootPane rootPane = frame.getRootPane();
if (rootPane != null && rootPane.getBorder() != null && Registry.is("ide.mac.transparentTitleBarAppearance")) {
rootPane.setBorder(null);
}
}
@Override
public void windowEnteredFullScreen(AppEvent.FullScreenEvent event) {
// We can get the notification when the frame has been disposed
JRootPane rootPane = frame.getRootPane();
if (rootPane != null) rootPane.putClientProperty(FULL_SCREEN, Boolean.TRUE);
enterFullscreen();
myFrame.validate();
}
@Override
public void windowExitedFullScreen(AppEvent.FullScreenEvent event) {
// We can get the notification when the frame has been disposed
if (myFrame == null/* || ORACLE_BUG_ID_8003173*/) return;
JRootPane rootPane = frame.getRootPane();
if (rootPane instanceof IdeRootPane && Registry.is("ide.mac.transparentTitleBarAppearance")) {
IdeRootPane ideRootPane = (IdeRootPane)rootPane;
UIUtil.setCustomTitleBar(frame, ideRootPane, runnable -> {
Disposer.register(parentDisposable, () -> runnable.run());
});
}
exitFullscreen();
ActiveWindowsWatcher.addActiveWindow(frame);
myFrame.validate();
}
});
}
else {
final ID window = MacUtil.findWindowForTitle(frame.getTitle());
if (window == null) return;
// toggle toolbar
String className = "IdeaToolbar" + v;
final ID ownToolbar = Foundation.allocateObjcClassPair(Foundation.getObjcClass("NSToolbar"), className);
Foundation.registerObjcClassPair(ownToolbar);
final ID toolbar = invoke(invoke(className, "alloc"), "initWithIdentifier:", Foundation.nsString(className));
Foundation.cfRetain(toolbar);
invoke(toolbar, "setVisible:", 0); // hide native toolbar by default
Foundation.addMethod(ownToolbar, Foundation.createSelector("setVisible:"), SET_VISIBLE_CALLBACK, "v*");
Foundation.addMethod(ownToolbar, Foundation.createSelector("isVisible"), IS_VISIBLE, "B*");
Foundation.executeOnMainThread(true, true, () -> {
invoke(window, "setToolbar:", toolbar);
invoke(window, "setShowsToolbarButton:", 1);
});
}
}
finally {
invoke(pool, "release");
}
// extract to static method for exclude this from OpenURIHandler() {} anonymous class
createProtocolHandler();
}
private static void createProtocolHandler() {
if (ourProtocolHandler == null) {
// install uri handler
final ID mainBundle = invoke("NSBundle", "mainBundle");
final ID urlTypes = invoke(mainBundle, "objectForInfoDictionaryKey:", Foundation.nsString("CFBundleURLTypes"));
final BuildNumber build = ApplicationInfoImpl.getShadowInstance().getBuild();
if (urlTypes.equals(ID.NIL) && build != null && !build.isSnapshot()) {
LOG.warn("no url bundle present. \n" +
"To use platform protocol handler to open external links specify required protocols in the mac app layout section of the build file\n" +
"Example: args.urlSchemes = [\"your-protocol\"] will handle following links: your-protocol://open?file=file&line=line");
return;
}
ourProtocolHandler = new CustomProtocolHandler();
Application.getApplication().setOpenURIHandler(new OpenURIHandler() {
// Native full screen listener can be set only once
FullScreenUtilities.addFullScreenListenerTo(frame, new FullScreenListener() {
@Override
public void openURI(AppEvent.OpenURIEvent event) {
ourProtocolHandler.openLink(event.getURI());
public void windowEnteringFullScreen(AppEvent.FullScreenEvent event) {
myDispatcher.getMulticaster().windowEnteringFullScreen(event);
}
@Override
public void windowEnteredFullScreen(AppEvent.FullScreenEvent event) {
myDispatcher.getMulticaster().windowEnteredFullScreen(event);
}
@Override
public void windowExitingFullScreen(AppEvent.FullScreenEvent event) {
myDispatcher.getMulticaster().windowExitingFullScreen(event);
}
@Override
public void windowExitedFullScreen(AppEvent.FullScreenEvent event) {
myDispatcher.getMulticaster().windowExitedFullScreen(event);
}
});
myDispatcher.addListener(new FSAdapter() {
@Override
public void windowEnteringFullScreen(AppEvent.FullScreenEvent event) {
JRootPane rootPane = myFrame.getRootPane();
if (rootPane != null && rootPane.getBorder() != null && Registry.is("ide.mac.transparentTitleBarAppearance")) {
rootPane.setBorder(null);
}
}
@Override
public void windowEnteredFullScreen(AppEvent.FullScreenEvent event) {
// We can get the notification when the frame has been disposed
JRootPane rootPane = myFrame.getRootPane();
if (rootPane != null) rootPane.putClientProperty(FULL_SCREEN, Boolean.TRUE);
enterFullScreen();
myFrame.validate();
}
@Override
public void windowExitedFullScreen(AppEvent.FullScreenEvent event) {
// We can get the notification when the frame has been disposed
JRootPane rootPane = myFrame.getRootPane();
if (rootPane instanceof IdeRootPane && Registry.is("ide.mac.transparentTitleBarAppearance")) {
IdeRootPane ideRootPane = (IdeRootPane)rootPane;
UIUtil.setCustomTitleBar(myFrame, ideRootPane, runnable -> {
Disposer.register(parentDisposable, () -> runnable.run());
});
}
exitFullScreen();
ActiveWindowsWatcher.addActiveWindow(myFrame);
myFrame.validate();
}
});
}
@@ -322,9 +176,6 @@ public final class MacMainFrameDecorator extends IdeFrameDecorator {
@NotNull
@Override
public Promise<Boolean> toggleFullScreen(boolean state) {
if (!SystemInfo.isMacOSLion || myFrame == null) {
return Promises.rejectedPromise();
}
if (myInFullScreen == state) {
return Promises.resolvedPromise(state);
}
@@ -344,40 +195,26 @@ public final class MacMainFrameDecorator extends IdeFrameDecorator {
}
});
// temporary solution for the old Runtime
if (enterFullScreenMethod == null || leaveFullScreenMethod == null) {
myFullscreenQueue.runOrEnqueue(this::toggleFullScreenNow);
} else {
myFullscreenQueue.runOrEnqueue(state ? this::enterFullScreenNow : this::leaveFullScreenNow);
// temporary solution for the old runtime
if (requestToggleFullScreenMethod != null) {
myFullScreenQueue.runOrEnqueue(() -> invokeAppMethod(requestToggleFullScreenMethod));
}
else if (state) {
myFullScreenQueue.runOrEnqueue(() -> invokeAppMethod(enterFullScreenMethod));
}
else {
myFullScreenQueue.runOrEnqueue(() -> invokeAppMethod(leaveFullScreenMethod));
}
return promise;
}
public void toggleFullScreenNow() {
private void invokeAppMethod(Method method) {
try {
requestToggleFullScreenMethod.invoke(Application.getApplication(), myFrame);
method.invoke(Application.getApplication(), myFrame);
}
catch (Exception e) {
LOG.warn(e);
Logger.getInstance(MacMainFrameDecorator.class).warn(e);
}
}
private void enterFullScreenNow() {
try {
enterFullScreenMethod.invoke(Application.getApplication(), myFrame);
}
catch (Exception e) {
LOG.warn(e);
}
}
private void leaveFullScreenNow() {
try {
leaveFullScreenMethod.invoke(Application.getApplication(), myFrame);
}
catch (Exception e) {
LOG.warn(e);
}
}
}
}
@@ -1,7 +1,9 @@
// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.ui.mac;
import com.apple.eawt.AppEvent;
import com.apple.eawt.Application;
import com.apple.eawt.OpenURIHandler;
import com.intellij.diagnostic.LoadingState;
import com.intellij.ide.DataManager;
import com.intellij.ide.actions.AboutAction;
@@ -13,12 +15,15 @@ import com.intellij.openapi.actionSystem.ActionManager;
import com.intellij.openapi.actionSystem.CommonDataKeys;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.TransactionGuard;
import com.intellij.openapi.application.impl.ApplicationInfoImpl;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.keymap.impl.IdeKeyEventDispatcher;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ProjectManager;
import com.intellij.openapi.util.BuildNumber;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.wm.IdeFocusManager;
import com.intellij.ui.CustomProtocolHandler;
import com.intellij.ui.mac.foundation.Foundation;
import com.intellij.ui.mac.foundation.ID;
import com.sun.jna.Callback;
@@ -91,6 +96,7 @@ public final class MacOSApplicationProvider {
if (JnaLoader.isLoaded()) {
installAutoUpdateMenu();
installProtocolHandler();
}
}
@@ -169,5 +175,27 @@ public final class MacOSApplicationProvider {
}
}
}
private static void installProtocolHandler() {
ID mainBundle = Foundation.invoke("NSBundle", "mainBundle");
ID urlTypes = Foundation.invoke(mainBundle, "objectForInfoDictionaryKey:", Foundation.nsString("CFBundleURLTypes"));
if (!urlTypes.equals(ID.NIL)) {
CustomProtocolHandler handler = new CustomProtocolHandler();
Application.getApplication().setOpenURIHandler(new OpenURIHandler() {
@Override
public void openURI(AppEvent.OpenURIEvent event) {
handler.openLink(event.getURI());
}
});
}
else {
BuildNumber build = ApplicationInfoImpl.getShadowInstance().getBuild();
if (!(build == null || build.isSnapshot())) {
LOG.warn("No URL bundle (CFBundleURLTypes) is defined in the main bundle.\n" +
"To be able to open external links, specify protocols in the app layout section of the build file.\n" +
"Example: args.urlSchemes = [\"your-protocol\"] will handle following links: your-protocol://open?file=file&line=line");
}
}
}
}
}