From f69d7123cec95b320b4f66eb6cbf4cc76a288823 Mon Sep 17 00:00:00 2001 From: Roman Shevchenko Date: Mon, 16 Dec 2019 21:19:21 +0100 Subject: [PATCH] [ui] macOS frame decorator cleanup - dropping pre-10.7 code - moving custom URL handler to a more appropriate place GitOrigin-RevId: f066884c0ed288c614fcc17a30d67def936a2373 --- .../openapi/wm/impl/IdeFrameDecorator.java | 5 +- .../ui/mac/MacMainFrameDecorator.java | 363 +++++------------- .../ui/mac/MacOSApplicationProvider.java | 28 ++ 3 files changed, 130 insertions(+), 266 deletions(-) diff --git a/platform/platform-impl/src/com/intellij/openapi/wm/impl/IdeFrameDecorator.java b/platform/platform-impl/src/com/intellij/openapi/wm/impl/IdeFrameDecorator.java index 2153b5df4131..def38cc5a2ee 100644 --- a/platform/platform-impl/src/com/intellij/openapi/wm/impl/IdeFrameDecorator.java +++ b/platform/platform-impl/src/com/intellij/openapi/wm/impl/IdeFrameDecorator.java @@ -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(); } -} +} \ No newline at end of file diff --git a/platform/platform-impl/src/com/intellij/ui/mac/MacMainFrameDecorator.java b/platform/platform-impl/src/com/intellij/ui/mac/MacMainFrameDecorator.java index 7c9b5a4fd3a1..70343fc8a034 100644 --- a/platform/platform-impl/src/com/intellij/ui/mac/MacMainFrameDecorator.java +++ b/platform/platform-impl/src/com/intellij/ui/mac/MacMainFrameDecorator.java @@ -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 myFullscreenQueue = new FullscreenQueue<>(); - - private final EventDispatcher myDispatcher = EventDispatcher.create(FSListener.class); - private interface FSListener extends FullScreenListener, EventListener {} private static class FSAdapter extends FullScreenAdapter implements FSListener {} - private static class FullscreenQueue { - private boolean waitingForAppKit = false; - private final LinkedList queueModel = new LinkedList<>(); + private static class FullScreenQueue { + private final LinkedList 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 NAV_BAR_GETTER = () -> UISettings.getInstance().getShowNavigationBar(); - - public static final Supplier TOOLBAR_GETTER = () -> UISettings.getInstance().getShowMainToolbar(); - - private static Runnable CURRENT_SETTER = null; - private static Supplier CURRENT_GETTER = null; - private static CustomProtocolHandler ourProtocolHandler = null; - + private final FullScreenQueue myFullScreenQueue = new FullScreenQueue(); + private final EventDispatcher 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 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); - } - } -} +} \ No newline at end of file diff --git a/platform/platform-impl/src/com/intellij/ui/mac/MacOSApplicationProvider.java b/platform/platform-impl/src/com/intellij/ui/mac/MacOSApplicationProvider.java index df43e8d5e317..0dfa84ea4389 100644 --- a/platform/platform-impl/src/com/intellij/ui/mac/MacOSApplicationProvider.java +++ b/platform/platform-impl/src/com/intellij/ui/mac/MacOSApplicationProvider.java @@ -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"); + } + } + } } } \ No newline at end of file