From 9adcdf3bfccca878e4b92f04855402cd1ef0889c Mon Sep 17 00:00:00 2001 From: Aleksandra Olemskaia Date: Tue, 14 Jul 2026 13:34:40 +0200 Subject: [PATCH] [settings] IJPL-249138 Non-modal settings: always open as pinned dialog when invoked from a macOS full-screen frame When Settings were invoked from an IDE frame in macOS native full-screen (main frame or detached editor tab), they could spawn as a WindowFrame on a separate Space instead of a FloatDialog on the IDE's Space. Fix: force float/dialog mode when the invoking frame is in full-screen (isFloat || isInvokingFrameInFullScreen()), preserving the saved preference for non-full-screen sessions. MacFullScreenSupport now stores itself as a root-pane client property so full-screen state can be queried for FrameWrapper-based windows. The pin button and show() are updated accordingly. (cherry picked from commit 11a21abd23e64df2fb65e66624d6c482f55b77ea) IJ-CR-217470 GitOrigin-RevId: b307eef9307fe9d51322443a1a34e4a0318bea74 --- .../openapi/ui/NonModalWindowWrapper.kt | 40 ++++++++++++++++++- .../intellij/ui/mac/MacFullScreenSupport.java | 7 ++++ 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/platform/platform-impl/src/com/intellij/openapi/ui/NonModalWindowWrapper.kt b/platform/platform-impl/src/com/intellij/openapi/ui/NonModalWindowWrapper.kt index a81cedc96974..efeb564b0573 100644 --- a/platform/platform-impl/src/com/intellij/openapi/ui/NonModalWindowWrapper.kt +++ b/platform/platform-impl/src/com/intellij/openapi/ui/NonModalWindowWrapper.kt @@ -25,6 +25,7 @@ import com.intellij.openapi.project.Project import com.intellij.openapi.ui.popup.util.PopupUtil import com.intellij.openapi.util.Disposer import com.intellij.openapi.util.WindowStateService +import com.intellij.openapi.wm.IdeFrame import com.intellij.openapi.wm.WindowManager import com.intellij.openapi.wm.impl.IdeFrameDecorator import com.intellij.openapi.wm.impl.IdeGlassPaneImpl @@ -51,6 +52,7 @@ import java.awt.BorderLayout import java.awt.Dimension import java.awt.EventQueue import java.awt.Frame +import java.awt.KeyboardFocusManager import java.awt.Rectangle import java.awt.Toolkit import java.awt.Window @@ -123,6 +125,14 @@ abstract class NonModalWindowWrapper( /** Non-null only on macOS while [activeWindow] is a [WindowFrame]. Tracks native full-screen state. */ private var fullScreenSupport: FullScreenSupport? = null + /** + * The AWT window that had focus when this wrapper was constructed — i.e. the window the user + * was in when they invoked the action that opened Settings. Captured eagerly so that + * [isInvokingFrameInFullScreen] can identify the invoking IDE frame even after AWT focus + * has shifted to the newly created Settings window. + */ + private val invokingWindow: Window? = KeyboardFocusManager.getCurrentKeyboardFocusManager().activeWindow + protected var isFloat: Boolean get() = PropertiesComponent.getInstance().getBoolean(floatModeKey, true) set(value) { PropertiesComponent.getInstance().setValue(floatModeKey, value, true) } @@ -196,7 +206,8 @@ abstract class NonModalWindowWrapper( IdeBundle.messagePointer("action.ToggleAction.description.pin.window"), AllIcons.General.Pin_tab, ) { - override fun isSelected(e: AnActionEvent): Boolean = isFloat + override fun isSelected(e: AnActionEvent): Boolean = + if (::activeWindow.isInitialized) activeWindow is FloatDialog else isFloat override fun setSelected(e: AnActionEvent, state: Boolean) { isFloat = state switchWindowMode(state) @@ -216,7 +227,11 @@ abstract class NonModalWindowWrapper( protected fun initWindow(content: JComponent, minSize: Dimension, initialSize: Dimension) { this.content = content this.minWindowSize = minSize - activeWindow = createAwtWindow(isFloat, content, minSize, initialSize) + // When the invoking IDE frame is in full-screen, always open as a dialog (float/pinned) so + // the Settings window appears on the IDE's Space rather than spawning its own separate Space. + // The saved preference is not modified so it is honoured the next time the IDE is not in full-screen. + val effectiveFloat = isFloat || isInvokingFrameInFullScreen() + activeWindow = createAwtWindow(effectiveFloat, content, minSize, initialSize) loadAndRegisterWindowState(activeWindow) fitWindowToScreen(activeWindow) installWindowListeners() @@ -599,6 +614,27 @@ abstract class NonModalWindowWrapper( protected fun getIdeJFrame(): JFrame? = ComponentUtil.getWindow(WindowManager.getInstance().getIdeFrame(project)?.component) as? JFrame + /** + * Returns `true` if the window that triggered this invocation is in macOS native full-screen. + * + * Two mechanisms are used: + * - [IdeFrame.isInFullScreen] — reliable for the main IDE JFrame (handled by the macOS frame decorator). + * - [MacFullScreenSupport] stored as a root-pane client property — reliable for [FrameWrapper]-based + * windows (e.g. detached editor tabs) whose [IdeFrame.isInFullScreen] always returns `false`. + * + * Returns `false` on non-macOS. + */ + private fun isInvokingFrameInFullScreen(): Boolean { + val ultimateParent = ComponentUtil.findUltimateParent(invokingWindow ?: return false) + // Main IDE frame: IdeFrame.isInFullScreen() is reliable (MacMainFrameDecorator). + if ((ultimateParent as? IdeFrame)?.isInFullScreen == true) return true + // Detached editor tabs (FrameWrapper.MyJFrame): isInFullScreen() always returns false, + // so fall back to MacFullScreenSupport stored in the root pane by addListener. + val rootPane = (invokingWindow as? RootPaneContainer)?.rootPane ?: return false + val fss = rootPane.getClientProperty(MacFullScreenSupport.ROOT_PANE_KEY) as? MacFullScreenSupport + return fss?.isFullScreen == true + } + // ── Public API ─────────────────────────────────────────────────────────────── /** diff --git a/platform/platform-impl/src/com/intellij/ui/mac/MacFullScreenSupport.java b/platform/platform-impl/src/com/intellij/ui/mac/MacFullScreenSupport.java index 58703a0a36b8..b9e2845d73e4 100644 --- a/platform/platform-impl/src/com/intellij/ui/mac/MacFullScreenSupport.java +++ b/platform/platform-impl/src/com/intellij/ui/mac/MacFullScreenSupport.java @@ -32,8 +32,14 @@ public final class MacFullScreenSupport implements FullScreenSupport { return myIsFullScreen; } + /** Client-property key on the window's root pane; value is the {@link MacFullScreenSupport} for that window. */ + public static final String ROOT_PANE_KEY = "MacFullScreenSupport"; + @Override public void addListener(@NotNull Window window) { + if (window instanceof RootPaneContainer container && container.getRootPane() != null) { + container.getRootPane().putClientProperty(ROOT_PANE_KEY, this); + } myListener = new FullScreenListener() { @Override public void windowEnteringFullScreen(FullScreenEvent event) { @@ -82,6 +88,7 @@ public final class MacFullScreenSupport implements FullScreenSupport { public void removeListener(@NotNull Window window) { if (window instanceof RootPaneContainer container && container.getRootPane() != null) { FullScreenUtilities.removeFullScreenListenerFrom(window, myListener); + container.getRootPane().putClientProperty(ROOT_PANE_KEY, null); } } } \ No newline at end of file