From 17859be3c5db95bc1ce00a87945963ffab4839a4 Mon Sep 17 00:00:00 2001 From: hstyi Date: Fri, 30 May 2025 09:48:48 +0800 Subject: [PATCH] feat: confirm tab close (#605) --- src/main/kotlin/app/termora/Database.kt | 5 ++++ src/main/kotlin/app/termora/SSHTerminalTab.kt | 3 +-- .../kotlin/app/termora/SettingsOptionsPane.kt | 19 +++++++++++++-- src/main/kotlin/app/termora/TerminalTab.kt | 8 ++++++- src/main/kotlin/app/termora/TerminalTabbed.kt | 24 ++++++++++++++++++- src/main/resources/i18n/messages.properties | 2 ++ .../resources/i18n/messages_zh_CN.properties | 2 ++ .../resources/i18n/messages_zh_TW.properties | 3 ++- 8 files changed, 59 insertions(+), 7 deletions(-) diff --git a/src/main/kotlin/app/termora/Database.kt b/src/main/kotlin/app/termora/Database.kt index 8e9e09b..1f7af62 100644 --- a/src/main/kotlin/app/termora/Database.kt +++ b/src/main/kotlin/app/termora/Database.kt @@ -648,6 +648,11 @@ class Database private constructor(private val env: Environment) : Disposable { */ var backgroundRunning by BooleanPropertyDelegate(false) + /** + * 标签关闭前确认 + */ + var confirmTabClose by BooleanPropertyDelegate(false) + /** * 背景图片的地址 */ diff --git a/src/main/kotlin/app/termora/SSHTerminalTab.kt b/src/main/kotlin/app/termora/SSHTerminalTab.kt index f453f1e..4da84bb 100644 --- a/src/main/kotlin/app/termora/SSHTerminalTab.kt +++ b/src/main/kotlin/app/termora/SSHTerminalTab.kt @@ -223,10 +223,9 @@ class SSHTerminalTab(windowScope: WindowScope, host: Host) : } } - override fun willBeClose(): Boolean { + override fun beforeClose() { // 保存窗口状态 terminalPanel.storeVisualWindows(host.id) - return super.willBeClose() } private inner class MySessionListener : SessionListener, Disposable { diff --git a/src/main/kotlin/app/termora/SettingsOptionsPane.kt b/src/main/kotlin/app/termora/SettingsOptionsPane.kt index a9ad64f..d7d9bf5 100644 --- a/src/main/kotlin/app/termora/SettingsOptionsPane.kt +++ b/src/main/kotlin/app/termora/SettingsOptionsPane.kt @@ -132,6 +132,7 @@ class SettingsOptionsPane : OptionsPane() { val themeComboBox = FlatComboBox() val languageComboBox = FlatComboBox() val backgroundComBoBox = YesOrNoComboBox() + val confirmTabCloseComBoBox = YesOrNoComboBox() val followSystemCheckBox = JCheckBox(I18n.getString("termora.settings.appearance.follow-system")) val preferredThemeBtn = JButton(Icons.settings) val opacitySpinner = NumberSpinner(100, 0, 100) @@ -180,6 +181,7 @@ class SettingsOptionsPane : OptionsPane() { followSystemCheckBox.isSelected = appearance.followSystem preferredThemeBtn.isEnabled = followSystemCheckBox.isSelected backgroundComBoBox.selectedItem = appearance.backgroundRunning + confirmTabCloseComBoBox.selectedItem = appearance.confirmTabClose themeComboBox.isEnabled = !followSystemCheckBox.isSelected themeManager.themes.keys.forEach { themeComboBox.addItem(it) } @@ -230,6 +232,13 @@ class SettingsOptionsPane : OptionsPane() { } } + + confirmTabCloseComBoBox.addItemListener { + if (it.stateChange == ItemEvent.SELECTED) { + appearance.confirmTabClose = confirmTabCloseComBoBox.selectedItem as Boolean + } + } + followSystemCheckBox.addActionListener { appearance.followSystem = followSystemCheckBox.isSelected themeComboBox.isEnabled = !followSystemCheckBox.isSelected @@ -368,7 +377,7 @@ class SettingsOptionsPane : OptionsPane() { private fun getFormPanel(): JPanel { val layout = FormLayout( "left:pref, $formMargin, default:grow, $formMargin, default, default:grow", - "pref, $formMargin, pref, $formMargin, pref, $formMargin, pref, $formMargin, pref" + "pref, $formMargin, pref, $formMargin, pref, $formMargin, pref, $formMargin, pref, $formMargin, pref" ) val box = FlatToolBar() box.add(followSystemCheckBox) @@ -401,7 +410,13 @@ class SettingsOptionsPane : OptionsPane() { .add(opacitySpinner).xy(3, rows).apply { rows += step } builder.add("${I18n.getString("termora.settings.appearance.background-running")}:").xy(1, rows) - .add(backgroundComBoBox).xy(3, rows) + .add(backgroundComBoBox).xy(3, rows).apply { rows += step } + + val confirmTabCloseBox = Box.createHorizontalBox() + confirmTabCloseBox.add(JLabel("${I18n.getString("termora.settings.appearance.confirm-tab-close")}:")) + confirmTabCloseBox.add(Box.createHorizontalStrut(8)) + confirmTabCloseBox.add(confirmTabCloseComBoBox) + builder.add(confirmTabCloseBox).xyw(1, rows,3).apply { rows += step } return builder.build() } diff --git a/src/main/kotlin/app/termora/TerminalTab.kt b/src/main/kotlin/app/termora/TerminalTab.kt index 6933cd0..95b60a5 100644 --- a/src/main/kotlin/app/termora/TerminalTab.kt +++ b/src/main/kotlin/app/termora/TerminalTab.kt @@ -1,5 +1,6 @@ package app.termora +import app.termora.Database.Appearance import app.termora.actions.DataProvider import java.beans.PropertyChangeListener import javax.swing.Icon @@ -44,10 +45,15 @@ interface TerminalTab : Disposable, DataProvider { fun canClose(): Boolean = true /** - * 返回 true 表示可以关闭 + * 返回 true 表示可以关闭,只有当 [Appearance.confirmTabClose] 为 false 时才会调用 */ fun willBeClose(): Boolean = true + /** + * 即将关闭,已经无法挽回 + */ + fun beforeClose() {} + /** * 是否可以克隆 */ diff --git a/src/main/kotlin/app/termora/TerminalTabbed.kt b/src/main/kotlin/app/termora/TerminalTabbed.kt index 55bf89c..243546f 100644 --- a/src/main/kotlin/app/termora/TerminalTabbed.kt +++ b/src/main/kotlin/app/termora/TerminalTabbed.kt @@ -32,6 +32,7 @@ class TerminalTabbed( private val actionManager = ActionManager.getInstance() private val dataProviderSupport = DataProviderSupport() private val titleProperty = UUID.randomUUID().toSimpleString() + private val appearance get() = Database.getDatabase().appearance private val iconListener = PropertyChangeListener { e -> val source = e.source if (e.propertyName == "icon" && source is TerminalTab) { @@ -153,8 +154,29 @@ class TerminalTabbed( if (tabbedPane.isTabClosable(index)) { val tab = tabs[index] + // 询问是否可以关闭 if (disposable) { - if (!tab.willBeClose()) { + // 如果开启了关闭确认,那么直接询问用户 + if (appearance.confirmTabClose) { + if (OptionPane.showConfirmDialog( + windowScope.window, + I18n.getString("termora.tabbed.tab.close-prompt"), + messageType = JOptionPane.QUESTION_MESSAGE, + optionType = JOptionPane.OK_CANCEL_OPTION + ) != JOptionPane.OK_OPTION + ) { + return + } + } else if (!tab.willBeClose()) { // 如果没有开启则询问用户 + return + } + } + + // 通知即将关闭 + if (disposable) { + try { + tab.beforeClose() + } catch (_: Exception) { return } } diff --git a/src/main/resources/i18n/messages.properties b/src/main/resources/i18n/messages.properties index 86de93e..1a8fd5a 100644 --- a/src/main/resources/i18n/messages.properties +++ b/src/main/resources/i18n/messages.properties @@ -57,6 +57,7 @@ termora.settings.appearance.follow-system=Sync with OS termora.settings.appearance.opacity=Opacity termora.settings.appearance.background-image=BG Image termora.settings.appearance.background-running=Backgrounding +termora.settings.appearance.confirm-tab-close=Confirm tab close termora.setting.security=Security termora.setting.security.enter-password=Enter password @@ -233,6 +234,7 @@ termora.tabbed.contextmenu.close-other-tabs=Close Other Tabs termora.tabbed.contextmenu.close-all-tabs=Close All Tabs termora.tabbed.contextmenu.reconnect=Reconnect termora.tabbed.local-tab.close-prompt=Do you want to terminal a running process in this terminal? +termora.tabbed.tab.close-prompt=Are you sure you want to close this tab? # Terminal logger termora.terminal-logger=Terminal Logger diff --git a/src/main/resources/i18n/messages_zh_CN.properties b/src/main/resources/i18n/messages_zh_CN.properties index 2580351..2f78d6d 100644 --- a/src/main/resources/i18n/messages_zh_CN.properties +++ b/src/main/resources/i18n/messages_zh_CN.properties @@ -54,6 +54,7 @@ termora.settings.appearance.follow-system=跟随系统 termora.settings.appearance.opacity=透明度 termora.settings.appearance.background-image=背景图 termora.settings.appearance.background-running=后台运行 +termora.settings.appearance.confirm-tab-close=标签关闭前确认 termora.setting.security=安全 termora.setting.security.enter-password=请输入密码 @@ -222,6 +223,7 @@ termora.tabbed.contextmenu.close-other-tabs=关闭其他标签页 termora.tabbed.contextmenu.close-all-tabs=关闭所有标签页 termora.tabbed.contextmenu.reconnect=重新连接 termora.tabbed.local-tab.close-prompt=你想要终止这个终端中正在运行的进程吗? +termora.tabbed.tab.close-prompt=你确定要关闭这个标签页吗? # Terminal logger diff --git a/src/main/resources/i18n/messages_zh_TW.properties b/src/main/resources/i18n/messages_zh_TW.properties index c4a5cf6..eb0b8ac 100644 --- a/src/main/resources/i18n/messages_zh_TW.properties +++ b/src/main/resources/i18n/messages_zh_TW.properties @@ -55,6 +55,7 @@ termora.settings.appearance.follow-system=跟隨系統 termora.settings.appearance.opacity=透明度 termora.settings.appearance.background-image=背景圖 termora.settings.appearance.background-running=後台運行 +termora.settings.appearance.confirm-tab-close=關閉分頁確認 termora.setting.security=安全 termora.setting.security.enter-password=請輸入密碼 @@ -218,7 +219,7 @@ termora.tabbed.contextmenu.close-other-tabs=關閉其他標籤頁 termora.tabbed.contextmenu.close-all-tabs=關閉所有標籤 termora.tabbed.contextmenu.reconnect=重新連接 termora.tabbed.local-tab.close-prompt=你想要終止這個終端機中正在運作的進程嗎? - +termora.tabbed.tab.close-prompt=你確定要關閉這個分頁嗎? # Terminal logger