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