From ce99bf0c1dc1dbbeaab74e822c7592381bff9550 Mon Sep 17 00:00:00 2001 From: Sergei Dubov Date: Thu, 30 Jan 2025 14:23:06 +0100 Subject: [PATCH] [rdct] IJPL-171305. Add check for closing tabbed project view in closeWindow API - Remote Development has a special handlers for the case when all projects are closed to ask a user if they need to shut down a backend process. On macOS when tabbed project view feature is enabled, we need to separate cases when projects are closed via tab or application process close. We need it to keep all opened projects open if a user declines the operation in the confirmation dialog. Updated check in windowClosing() API to support check for tabbed project view; - Add tests to verify macOS tabbed project view cases. (cherry picked from commit 826fb0605b47f89ee9c43315deb99b615daa12b7) IJ-CR-154038 GitOrigin-RevId: 56f092de57486d566d5fb6f76f541c993c09b8f3 --- .../wm/impl/CloseProjectWindowHelper.kt | 42 ++++++++++++-- .../intellij/ui/mac/WindowTabsComponent.java | 2 + .../wm/CloseProjectWindowHelperTest.kt | 56 ++++++++++++++++++- 3 files changed, 95 insertions(+), 5 deletions(-) diff --git a/platform/platform-impl/src/com/intellij/openapi/wm/impl/CloseProjectWindowHelper.kt b/platform/platform-impl/src/com/intellij/openapi/wm/impl/CloseProjectWindowHelper.kt index 13c210b42803..331f18ea6bd6 100644 --- a/platform/platform-impl/src/com/intellij/openapi/wm/impl/CloseProjectWindowHelper.kt +++ b/platform/platform-impl/src/com/intellij/openapi/wm/impl/CloseProjectWindowHelper.kt @@ -13,10 +13,15 @@ import com.intellij.openapi.project.Project import com.intellij.openapi.project.ProjectManager import com.intellij.openapi.util.Key import com.intellij.openapi.util.SystemInfo +import com.intellij.openapi.wm.WindowManager import com.intellij.openapi.wm.impl.welcomeScreen.WelcomeFrame +import com.intellij.ui.mac.MergeAllWindowsAction +import com.intellij.ui.mac.WindowTabsComponent import com.intellij.util.PlatformUtils import com.intellij.util.SystemProperties import com.intellij.util.concurrency.annotations.RequiresEdt +import org.jetbrains.annotations.ApiStatus +import org.jetbrains.annotations.TestOnly open class CloseProjectWindowHelper { companion object { @@ -33,16 +38,44 @@ open class CloseProjectWindowHelper { protected open val isShowWelcomeScreenFromSettings: Boolean get() = GeneralSettings.getInstance().isShowWelcomeScreen + @ApiStatus.Internal + protected open fun couldReturnToWelcomeScreen(projects: Array): Boolean { + return projects.any { project -> couldReturnToWelcomeScreen(project) } + } + + @ApiStatus.Internal + protected open fun isMacOsTabbedProjectView(project: Project?): Boolean { + if (!SystemInfo.isMac) { + return false + } + val projectFrame = WindowManager.getInstance().getFrame(project) ?: return false + return MergeAllWindowsAction.isTabbedWindow(projectFrame) + } + + @ApiStatus.Internal + protected open fun isCloseTab(project: Project?): Boolean { + project ?: return false + + val frame = WindowManager.getInstance().getFrame(project) ?: return false + return frame.rootPane.getClientProperty(WindowTabsComponent.CLOSE_TAB_KEY) == true + } + @RequiresEdt open fun windowClosing(project: Project?) { WriteIntentReadAction.run { + val numberOfOpenedProjects = getNumberOfOpenedProjects() + val isMacOsTabbedProjectView = isMacOsTabbedProjectView(project) + // Exit on Linux and Windows if the only opened project frame is closed. - // On macOS behaviour is different - to exit app, quit action should be used, otherwise welcome frame is shown. - // If welcome screen is disabled, behaviour on all OS is the same. - if (numberOfOpenedProjects > 1 || + // On macOS behavior is different - to exit app, quit action should be used, otherwise welcome frame is shown. + // If the Welcome screen is disabled, behavior on all OS is the same. + if (!isMacOsTabbedProjectView && numberOfOpenedProjects > 1 || + isMacOsTabbedProjectView && isCloseTab(project) || + isMacOsTabbedProjectView && couldReturnToWelcomeScreen(projects = WindowManager.getInstance().allProjectFrames.mapNotNull { it.project }.toTypedArray()) || serviceIfCreated()?.project != null || - (numberOfOpenedProjects == 1 && couldReturnToWelcomeScreen(project))) { + (numberOfOpenedProjects == 1 && couldReturnToWelcomeScreen(project)) + ) { closeProjectAndShowWelcomeFrameIfNoProjectOpened(project) } else { @@ -74,4 +107,5 @@ open class CloseProjectWindowHelper { return project?.let { SHOW_WELCOME_FRAME_FOR_PROJECT.get(project) } ?: (isShowWelcomeScreen && !PlatformUtils.isDataSpell() && !PlatformUtils.isDataGrip()) } + } \ No newline at end of file diff --git a/platform/platform-impl/src/com/intellij/ui/mac/WindowTabsComponent.java b/platform/platform-impl/src/com/intellij/ui/mac/WindowTabsComponent.java index 87046554daff..9388879e7588 100644 --- a/platform/platform-impl/src/com/intellij/ui/mac/WindowTabsComponent.java +++ b/platform/platform-impl/src/com/intellij/ui/mac/WindowTabsComponent.java @@ -59,6 +59,7 @@ import java.util.*; @ApiStatus.Internal public final class WindowTabsComponent extends JBTabsImpl { private static final String TITLE_LISTENER_KEY = "TitleListener"; + public static final String CLOSE_TAB_KEY = "CloseTab"; private static final int TAB_HEIGHT = 30; @@ -506,6 +507,7 @@ public final class WindowTabsComponent extends JBTabsImpl { } private static void closeTab(@NotNull IdeFrameImpl tabFrame, boolean closeOthers) { + tabFrame.getRootPane().putClientProperty(CLOSE_TAB_KEY, Boolean.TRUE); Foundation.executeOnMainThread(true, false, () -> { ID window = MacUtil.getWindowFromJavaWindow(tabFrame); Foundation.invoke(window, closeOthers ? "performCloseOtherTabs:" : "performClose:", ID.NIL); diff --git a/platform/platform-tests/testSrc/com/intellij/wm/CloseProjectWindowHelperTest.kt b/platform/platform-tests/testSrc/com/intellij/wm/CloseProjectWindowHelperTest.kt index 017c08687853..5c239dd04869 100644 --- a/platform/platform-tests/testSrc/com/intellij/wm/CloseProjectWindowHelperTest.kt +++ b/platform/platform-tests/testSrc/com/intellij/wm/CloseProjectWindowHelperTest.kt @@ -3,10 +3,19 @@ package com.intellij.wm import com.intellij.openapi.project.Project import com.intellij.openapi.wm.impl.CloseProjectWindowHelper +import com.intellij.testFramework.ProjectRule import com.intellij.testFramework.assertions.Assertions.assertThat +import org.junit.ClassRule import org.junit.Test class CloseProjectWindowHelperTest { + + companion object { + @JvmField + @ClassRule + val projectRule = ProjectRule() + } + @Test fun `on Windows closing last project leads to exit`() { val helper = object : TestCloseProjectWindowHelper() { @@ -33,7 +42,7 @@ class CloseProjectWindowHelperTest { assertThat(helper.wasShowWelcomeFrameIfNoProjectOpenedCalled).isTrue() } - // well, not clear is listener will be called for case when no opened projects atl all, but just t o be sure + // well, not clear is listener will be called for the case when no opened projects at all, but just to be sure @Test fun `on Windows closing if no opened projects leads to exit`() { val helper = object : TestCloseProjectWindowHelper() { @@ -59,6 +68,51 @@ class CloseProjectWindowHelperTest { assertThat(helper.wasQuitAppCalled).isTrue() assertThat(helper.wasShowWelcomeFrameIfNoProjectOpenedCalled).isFalse() } + + @Test + fun `on macOS closing a tab with tabbed project view`() { + val helper = object : TestCloseProjectWindowHelper() { + override val isMacSystemMenu = true + + override fun isMacOsTabbedProjectView(project: Project?): Boolean = true + override fun isCloseTab(project: Project?): Boolean = true + override fun couldReturnToWelcomeScreen(projects: Array): Boolean = false + } + + helper.windowClosing(null) + assertThat(helper.wasQuitAppCalled).isFalse() + assertThat(helper.wasShowWelcomeFrameIfNoProjectOpenedCalled).isTrue() + } + + @Test + fun `on macOS closing an application with tabbed project view when should show welcome screen`() { + val helper = object : TestCloseProjectWindowHelper() { + override val isMacSystemMenu = true + + override fun isMacOsTabbedProjectView(project: Project?): Boolean = true + override fun isCloseTab(project: Project?): Boolean = false + override fun couldReturnToWelcomeScreen(projects: Array): Boolean = true + } + + helper.windowClosing(null) + assertThat(helper.wasQuitAppCalled).isFalse() + assertThat(helper.wasShowWelcomeFrameIfNoProjectOpenedCalled).isTrue() + } + + @Test + fun `on macOS closing an application with tabbed project view when should not show welcome screen`() { + val helper = object : TestCloseProjectWindowHelper() { + override val isMacSystemMenu = true + + override fun isMacOsTabbedProjectView(project: Project?): Boolean = true + override fun isCloseTab(project: Project?): Boolean = false + override fun couldReturnToWelcomeScreen(projects: Array): Boolean = false + } + + helper.windowClosing(null) + assertThat(helper.wasQuitAppCalled).isTrue() + assertThat(helper.wasShowWelcomeFrameIfNoProjectOpenedCalled).isFalse() + } } open class TestCloseProjectWindowHelper : CloseProjectWindowHelper() {