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() {