From eddc7ef0c613be8c67c10d2e7d9a1e2a29dbb8b5 Mon Sep 17 00:00:00 2001 From: hstyi Date: Tue, 8 Jul 2025 16:00:53 +0800 Subject: [PATCH] refactor: frame toolbar --- .../kotlin/app/termora/ApplicationRunner.kt | 8 - .../app/termora/CustomizeToolBarDialog.kt | 25 +-- .../app/termora/MultipleTerminalListener.kt | 2 +- .../kotlin/app/termora/MyTermoraToolbar.kt | 153 ++++++++++++++ .../app/termora/TerminalPanelFactory.kt | 4 +- src/main/kotlin/app/termora/TerminalTabbed.kt | 14 +- src/main/kotlin/app/termora/TermoraFrame.kt | 37 +++- src/main/kotlin/app/termora/TermoraToolBar.kt | 192 ------------------ .../kotlin/app/termora/TermoraToolbarModel.kt | 103 ++++++++++ src/main/kotlin/app/termora/ToolBarAction.kt | 9 + .../app/termora/actions/ActionManager.kt | 1 + .../app/termora/actions/MultipleAction.kt | 29 ++- .../kotlin/app/termora/actions/StateAction.kt | 8 + .../QuickActionsFindEverywhereProvider.kt | 4 +- .../termora/plugin/internal/badge/Badge.kt | 18 +- 15 files changed, 359 insertions(+), 248 deletions(-) create mode 100644 src/main/kotlin/app/termora/MyTermoraToolbar.kt delete mode 100644 src/main/kotlin/app/termora/TermoraToolBar.kt create mode 100644 src/main/kotlin/app/termora/TermoraToolbarModel.kt create mode 100644 src/main/kotlin/app/termora/ToolBarAction.kt create mode 100644 src/main/kotlin/app/termora/actions/StateAction.kt diff --git a/src/main/kotlin/app/termora/ApplicationRunner.kt b/src/main/kotlin/app/termora/ApplicationRunner.kt index 0a5aadb..68d83d7 100644 --- a/src/main/kotlin/app/termora/ApplicationRunner.kt +++ b/src/main/kotlin/app/termora/ApplicationRunner.kt @@ -1,8 +1,6 @@ package app.termora -import app.termora.actions.ActionManager import app.termora.database.DatabaseManager -import app.termora.keymap.KeymapManager import app.termora.plugin.ExtensionManager import app.termora.plugin.PluginManager import com.formdev.flatlaf.FlatClientProperties @@ -54,12 +52,6 @@ class ApplicationRunner { // 统计 enableAnalytics() - // init ActionManager、KeymapManager、VFS - swingCoroutineScope.launch(Dispatchers.IO) { - ActionManager.getInstance() - KeymapManager.getInstance() - } - // 设置 LAF setupLaf() diff --git a/src/main/kotlin/app/termora/CustomizeToolBarDialog.kt b/src/main/kotlin/app/termora/CustomizeToolBarDialog.kt index 8b73b73..0ed4371 100644 --- a/src/main/kotlin/app/termora/CustomizeToolBarDialog.kt +++ b/src/main/kotlin/app/termora/CustomizeToolBarDialog.kt @@ -1,8 +1,5 @@ package app.termora -import app.termora.Application.ohMyJson -import app.termora.actions.MultipleAction -import app.termora.database.DatabaseManager import com.jgoodies.forms.builder.FormBuilder import com.jgoodies.forms.layout.FormLayout import org.apache.commons.lang3.StringUtils @@ -18,10 +15,10 @@ import javax.swing.event.ListDataListener import kotlin.math.max import kotlin.math.min -class CustomizeToolBarDialog( +internal class CustomizeToolBarDialog( owner: Window, private val windowScope: WindowScope, - private val toolbar: TermoraToolBar + private val model: TermoraToolbarModel, ) : DialogWrapper(owner) { private val moveTopBtn = JButton(Icons.moveUp) @@ -40,6 +37,7 @@ class CustomizeToolBarDialog( private val actionManager get() = ActionManager.getInstance() private var isOk = false + private val actions = mutableListOf() init { size = Dimension(UIManager.getInt("Dialog.width") - 150, UIManager.getInt("Dialog.height") - 100) @@ -147,7 +145,7 @@ class CustomizeToolBarDialog( resetBtn.addActionListener { leftList.model.removeAllElements() rightList.model.removeAllElements() - for (action in toolbar.getAllActions()) { + for (action in model.getAllActions()) { getActionHolder(action.id)?.let { rightList.model.addElement(it) } } } @@ -258,7 +256,7 @@ class CustomizeToolBarDialog( override fun windowOpened(e: WindowEvent) { removeWindowListener(this) - for (action in toolbar.getActions()) { + for (action in model.getActions()) { if (action.visible) { getActionHolder(action.id)?.let { rightList.model.addElement(it) } } else { @@ -271,12 +269,7 @@ class CustomizeToolBarDialog( } private fun getActionHolder(actionId: String): ActionHolder? { - var action = actionManager.getAction(actionId) - if (action == null) { - if (actionId == MultipleAction.MULTIPLE) { - action = MultipleAction.getInstance(windowScope) - } - } + val action = actionManager.getAction(actionId) if (action == null) return null return ActionHolder(actionId, action) } @@ -365,12 +358,14 @@ class CustomizeToolBarDialog( actions.add(ToolBarAction(leftList.model.getElementAt(i).id, false)) } - DatabaseManager.getInstance() - .properties.putString("Termora.ToolBar.Actions", ohMyJson.encodeToString(actions)) + this.actions.clear() + this.actions.addAll(actions) super.doOKAction() } + fun getActions()=actions + fun open(): Boolean { isModal = true isVisible = true diff --git a/src/main/kotlin/app/termora/MultipleTerminalListener.kt b/src/main/kotlin/app/termora/MultipleTerminalListener.kt index a76a58c..982f7f9 100644 --- a/src/main/kotlin/app/termora/MultipleTerminalListener.kt +++ b/src/main/kotlin/app/termora/MultipleTerminalListener.kt @@ -27,7 +27,7 @@ class MultipleTerminalListener : TerminalPaintListener { ) { val windowScope = AnActionEvent(terminalPanel, StringUtils.EMPTY, EventObject(terminalPanel)) .getData(DataProviders.WindowScope) ?: return - if (!MultipleAction.getInstance(windowScope).isSelected) return + if (MultipleAction.getInstance().isSelected(windowScope).not()) return val oldFont = g.font val colorPalette = terminal.getTerminalModel().getColorPalette() diff --git a/src/main/kotlin/app/termora/MyTermoraToolbar.kt b/src/main/kotlin/app/termora/MyTermoraToolbar.kt new file mode 100644 index 0000000..5739e93 --- /dev/null +++ b/src/main/kotlin/app/termora/MyTermoraToolbar.kt @@ -0,0 +1,153 @@ +package app.termora + +import app.termora.actions.StateAction +import app.termora.plugin.internal.badge.Badge +import com.formdev.flatlaf.extras.components.FlatPopupMenu +import com.formdev.flatlaf.extras.components.FlatToolBar +import java.awt.AWTEvent +import java.awt.Rectangle +import java.awt.event.AWTEventListener +import java.awt.event.ActionEvent +import java.awt.event.MouseEvent +import java.beans.PropertyChangeEvent +import java.beans.PropertyChangeListener +import javax.swing.* + +internal class MyTermoraToolbar(private val windowScope: WindowScope) : FlatToolBar() { + + + private val customizeToolBarAWTEventListener = CustomizeToolBarAWTEventListener() + private val model get() = TermoraToolbarModel.getInstance() + private val actionManager get() = model.getActionManager() + private val toolbar get() = this + + /** + * 一次性生命周期 每次刷新都会重置 + */ + private var disposable = Disposer.newDisposable() + + init { + initView() + initEvents() + refreshActions() + } + + private fun initView() { + isFloatable = false + } + + private fun initEvents() { + Disposer.register(windowScope, object : Disposable { + override fun dispose() { + Disposer.dispose(disposable) + } + }) + + + // 监听全局事件 + toolkit.addAWTEventListener(customizeToolBarAWTEventListener, AWTEvent.MOUSE_EVENT_MASK) + Disposer.register(windowScope, customizeToolBarAWTEventListener) + + // 监听变化 + model.addTermoraToolbarModelListener(object : TermoraToolbarModel.TermoraToolbarModelListener { + override fun onChanged() { + refreshActions() + } + }).let { Disposer.register(windowScope, it) } + + } + + private fun refreshActions() { + Disposer.dispose(disposable) + disposable = Disposer.newDisposable() + + removeAll() + add(Box.createHorizontalGlue()) + + for (action in model.getActions()) { + if (action.visible.not()) continue + val action = actionManager.getAction(action.id) ?: continue + add(redirectAction(action, disposable)) + } + + revalidate() + repaint() + } + + private fun redirectAction(action: Action, disposable: Disposable): AbstractButton { + val button = if (action is StateAction) JToggleButton() else JButton() + button.toolTipText = action.getValue(Action.SHORT_DESCRIPTION) as? String + button.icon = action.getValue(Action.SMALL_ICON) as? Icon + button.addActionListener(object : AbstractAction() { + override fun actionPerformed(e: ActionEvent) { + action.actionPerformed(e) + if (action is StateAction) { + button.isSelected = action.isSelected(windowScope) + } + } + }) + + val listener = object : PropertyChangeListener, Disposable { + private val badge get() = Badge.getInstance(windowScope) + override fun propertyChange(evt: PropertyChangeEvent) { + if (evt.propertyName == "Badge") { + if (action.getValue("Badge") == true) { + badge.addBadge(button) + } else { + badge.removeBadge(button) + } + } + + if (action is StateAction) { + button.isSelected = action.isSelected(windowScope) + } + } + + override fun dispose() { + action.removePropertyChangeListener(this) + } + } + + action.addPropertyChangeListener(listener) + Disposer.register(disposable, listener) + + return button + } + + /** + * 对着 ToolBar 右键 + */ + private inner class CustomizeToolBarAWTEventListener : AWTEventListener, Disposable { + override fun eventDispatched(event: AWTEvent) { + if (event !is MouseEvent || event.id != MouseEvent.MOUSE_CLICKED + || SwingUtilities.isRightMouseButton(event).not() + ) return + + // 如果 ToolBar 没有显示 + if (toolbar.isShowing.not()) return + + // 如果不是作用于在 ToolBar 上面 + if (Rectangle(toolbar.locationOnScreen, toolbar.size).contains(event.locationOnScreen).not()) return + + // 显示右键菜单 + showContextMenu(event) + } + + private fun showContextMenu(event: MouseEvent) { + val popupMenu = FlatPopupMenu() + popupMenu.add(I18n.getString("termora.toolbar.customize-toolbar")).addActionListener { + val owner = windowScope.window + val dialog = CustomizeToolBarDialog(owner, windowScope, model) + dialog.setLocationRelativeTo(owner) + if (dialog.open()) { + model.setActions(dialog.getActions()) + } + } + popupMenu.show(event.component, event.x, event.y) + } + + override fun dispose() { + toolkit.removeAWTEventListener(this) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/termora/TerminalPanelFactory.kt b/src/main/kotlin/app/termora/TerminalPanelFactory.kt index 1b3a3d7..d3b6ef4 100644 --- a/src/main/kotlin/app/termora/TerminalPanelFactory.kt +++ b/src/main/kotlin/app/termora/TerminalPanelFactory.kt @@ -133,8 +133,8 @@ class TerminalPanelFactory : Disposable { return } - val multipleAction = MultipleAction.getInstance(windowScope) - if (!multipleAction.isSelected) { + val multipleAction = MultipleAction.getInstance() + if (multipleAction.isSelected(windowScope).not()) { ptyConnector.write(request.buffer) return } diff --git a/src/main/kotlin/app/termora/TerminalTabbed.kt b/src/main/kotlin/app/termora/TerminalTabbed.kt index 1f80de7..f04503c 100644 --- a/src/main/kotlin/app/termora/TerminalTabbed.kt +++ b/src/main/kotlin/app/termora/TerminalTabbed.kt @@ -28,13 +28,15 @@ import kotlin.math.min class TerminalTabbed( private val windowScope: WindowScope, - private val termoraToolBar: TermoraToolBar, private val tabbedPane: FlatTabbedPane, private val layout: TermoraLayout, ) : JPanel(BorderLayout()), Disposable, TerminalTabbedManager, DataProvider { private val tabs = mutableListOf() - private val customizeToolBarAWTEventListener = CustomizeToolBarAWTEventListener() - private val toolbar = termoraToolBar.getJToolBar() + private val customizeToolBarAWTEventListener = object : AWTEventListener, Disposable { + override fun eventDispatched(event: AWTEvent?) { + + } + } private val actionManager = ActionManager.getInstance() private val dataProviderSupport = DataProviderSupport() private val appearance get() = DatabaseManager.getInstance().appearance @@ -60,8 +62,6 @@ class TerminalTabbed( tabbedPane.isTabsClosable = true tabbedPane.tabType = FlatTabbedPane.TabType.card - tabbedPane.trailingComponent = toolbar - add(tabbedPane, BorderLayout.CENTER) windowScope.getOrCreate(TerminalTabbedManager::class) { this } @@ -388,7 +388,7 @@ class TerminalTabbed( /** * 对着 ToolBar 右键 */ - private inner class CustomizeToolBarAWTEventListener : AWTEventListener, Disposable { + /*private inner class CustomizeToolBarAWTEventListener : AWTEventListener, Disposable { override fun eventDispatched(event: AWTEvent) { if (event !is MouseEvent || event.id != MouseEvent.MOUSE_CLICKED || !SwingUtilities.isRightMouseButton(event)) return // 如果 ToolBar 没有显示 @@ -416,7 +416,7 @@ class TerminalTabbed( override fun dispose() { toolkit.removeAWTEventListener(this) } - } + }*/ /*private inner class CustomizeToolBarDialog(owner: Window) : DialogWrapper(owner) { init { diff --git a/src/main/kotlin/app/termora/TermoraFrame.kt b/src/main/kotlin/app/termora/TermoraFrame.kt index 941cf6a..f481852 100644 --- a/src/main/kotlin/app/termora/TermoraFrame.kt +++ b/src/main/kotlin/app/termora/TermoraFrame.kt @@ -42,8 +42,8 @@ class TermoraFrame : JFrame(), DataProvider { private val id = UUID.randomUUID().toString() private val windowScope = ApplicationScope.forWindowScope(this) private val tabbedPane = MyTabbedPane().apply { tabHeight = titleBarHeight } - private val toolbar = TermoraToolBar(windowScope, this) - private val terminalTabbed = TerminalTabbed(windowScope, toolbar, tabbedPane, layout) + private val toolbar = MyTermoraToolbar(windowScope) + private val terminalTabbed = TerminalTabbed(windowScope, tabbedPane, layout) private val dataProviderSupport = DataProviderSupport() private var notifyListeners = emptyArray() private val moveMouseAdapter = createMoveMouseAdaptor() @@ -59,8 +59,8 @@ class TermoraFrame : JFrame(), DataProvider { private fun initEvents() { if (SystemInfo.isLinux) { - toolbar.getJToolBar().addMouseListener(moveMouseAdapter) - toolbar.getJToolBar().addMouseMotionListener(moveMouseAdapter) + toolbar.addMouseListener(moveMouseAdapter) + toolbar.addMouseMotionListener(moveMouseAdapter) } else if (SystemInfo.isMacOS) { terminalTabbed.addMouseListener(moveMouseAdapter) terminalTabbed.addMouseMotionListener(moveMouseAdapter) @@ -68,8 +68,8 @@ class TermoraFrame : JFrame(), DataProvider { tabbedPane.addMouseListener(moveMouseAdapter) tabbedPane.addMouseMotionListener(moveMouseAdapter) - toolbar.getJToolBar().addMouseListener(moveMouseAdapter) - toolbar.getJToolBar().addMouseMotionListener(moveMouseAdapter) + toolbar.addMouseListener(moveMouseAdapter) + toolbar.addMouseMotionListener(moveMouseAdapter) } // 快捷键变动时重新监听 @@ -168,6 +168,8 @@ class TermoraFrame : JFrame(), DataProvider { tabbedPane.tabAreaInsets = Insets(1, 2, 0, 0) } + tabbedPane.trailingComponent = toolbar + val height = UIManager.getInt("TabbedPane.tabHeight") + tabbedPane.tabAreaInsets.top if (SystemInfo.isWindows || SystemInfo.isLinux) { @@ -228,11 +230,26 @@ class TermoraFrame : JFrame(), DataProvider { for ((shortcut, actionIds) in keymap.getShortcuts()) { if (shortcut !is KeyShortcut) continue - val keyShortcutActionId = "KeyShortcutAction_${randomUUID()}" - actionMap.put(keyShortcutActionId, redirectAction(actionIds)) - inputMap.put(shortcut.keyStroke, keyShortcutActionId) + if (actionIds.contains(SwitchTabAction.SWITCH_TAB)) continue + registerKeyStroke(actionMap, inputMap, shortcut.keyStroke, actionIds) } + for (shortcut in keymap.getShortcut(SwitchTabAction.SWITCH_TAB)) { + if (shortcut !is KeyShortcut) continue + registerKeyStroke(actionMap, inputMap, shortcut.keyStroke, listOf(SwitchTabAction.SWITCH_TAB)) + } + + } + + private fun registerKeyStroke( + actionMap: ActionMap, + inputMap: InputMap, + keyStroke: KeyStroke, + actionIds: List + ) { + val keyShortcutActionId = "KeyShortcutAction_${randomUUID()}" + actionMap.put(keyShortcutActionId, redirectAction(actionIds)) + inputMap.put(keyStroke, keyShortcutActionId) } private fun redirectAction(actionIds: List): Action { @@ -249,7 +266,7 @@ class TermoraFrame : JFrame(), DataProvider { for (actionId in actionIds) { val action = actionManager.getAction(actionId) ?: continue - action.actionPerformed(RedirectAnActionEvent(source, e.actionCommand, e)) + action.actionPerformed(RedirectAnActionEvent(source, e.actionCommand, EventQueue.getCurrentEvent())) } } } diff --git a/src/main/kotlin/app/termora/TermoraToolBar.kt b/src/main/kotlin/app/termora/TermoraToolBar.kt deleted file mode 100644 index b953c97..0000000 --- a/src/main/kotlin/app/termora/TermoraToolBar.kt +++ /dev/null @@ -1,192 +0,0 @@ -package app.termora - -import app.termora.Application.ohMyJson -import app.termora.actions.* -import app.termora.database.DatabaseManager -import app.termora.findeverywhere.FindEverywhereAction -import app.termora.snippet.SnippetAction -import com.formdev.flatlaf.FlatClientProperties -import com.formdev.flatlaf.util.SystemInfo -import kotlinx.serialization.Serializable -import org.apache.commons.lang3.StringUtils -import org.jdesktop.swingx.action.ActionContainerFactory -import java.awt.Rectangle -import java.awt.event.ComponentAdapter -import java.awt.event.ComponentEvent -import javax.swing.Box -import javax.swing.JToolBar - - -@Serializable -data class ToolBarAction( - val id: String, - val visible: Boolean, -) - -class TermoraToolBar( - private val windowScope: WindowScope, - private val frame: TermoraFrame, -) { - - companion object { - fun rebuild() { - for (frame in TermoraFrameManager.getInstance().getWindows()) { - val toolbars = SwingUtils.getDescendantsOfClass(MyToolBar::class.java, frame) - for (toolbar in toolbars) { - toolbar.rebuild() - } - } - } - } - - private val properties get() = DatabaseManager.getInstance().properties - private val toolbar by lazy { MyToolBar().apply { rebuild() } } - - - fun getJToolBar(): JToolBar { - return toolbar - } - - /** - * 获取到所有的 Action - */ - fun getAllActions(): List { - return listOf( - ToolBarAction(SnippetAction.SNIPPET, true), - ToolBarAction(Actions.SFTP, true), - ToolBarAction(Actions.TERMINAL_LOGGER, true), - ToolBarAction(Actions.MACRO, true), - ToolBarAction(Actions.KEYWORD_HIGHLIGHT, true), - ToolBarAction(Actions.KEY_MANAGER, true), - ToolBarAction(MultipleAction.MULTIPLE, true), - ToolBarAction(FindEverywhereAction.FIND_EVERYWHERE, true), - ToolBarAction(SettingsAction.SETTING, true), - ) - } - - - /** - * 获取到所有 Action,会根据用户个性化排序/显示 - */ - fun getActions(): List { - val text = properties.getString( - "Termora.ToolBar.Actions", - StringUtils.EMPTY - ) - - val actions = getAllActions() - - if (text.isBlank()) { - return actions - } - - // 存储的 action - val storageActions = (ohMyJson.runCatching { - ohMyJson.decodeFromString>(text) - }.getOrNull() ?: return actions).toMutableList() - - for (action in actions) { - // 如果存储的 action 不包含这个,那么这个可能是新增的,新增的默认显示出来 - if (storageActions.none { it.id == action.id }) { - storageActions.addFirst(ToolBarAction(action.id, true)) - } - } - - // 如果存储的 Action 在所有 Action 里没有,那么移除 - storageActions.removeIf { e -> actions.none { e.id == it.id } } - - return storageActions - } - - private inner class MyToolBar : JToolBar() { - init { - // 监听窗口大小变动,然后修改边距避开控制按钮 - addComponentListener(object : ComponentAdapter() { - override fun componentResized(e: ComponentEvent) { - adjust() - } - }) - } - - fun adjust() { - if (SystemInfo.isWindows || SystemInfo.isLinux) { - val rectangle = - frame.rootPane.getClientProperty(FlatClientProperties.FULL_WINDOW_CONTENT_BUTTONS_BOUNDS) - as? Rectangle ?: return - val right = rectangle.width - val toolbar = this@MyToolBar - for (i in 0 until toolbar.componentCount) { - val c = toolbar.getComponent(i) - if (c.name == "spacing") { - if (c.width == right) { - return - } - toolbar.remove(i) - break - } - } - - if (right > 0) { - val spacing = Box.createHorizontalStrut(right) - spacing.name = "spacing" - toolbar.add(spacing) - } - } - } - - - fun rebuild() { - val toolbar: JToolBar = this - val actionManager = ActionManager.getInstance() - val actionContainerFactory = ActionContainerFactory(actionManager) - - toolbar.removeAll() - - toolbar.add(actionContainerFactory.createButton(object : AnAction(StringUtils.EMPTY, Icons.add) { - override fun actionPerformed(evt: AnActionEvent) { - actionManager.getAction(FindEverywhereAction.FIND_EVERYWHERE)?.actionPerformed(evt) - } - - override fun isEnabled(): Boolean { - return actionManager.getAction(FindEverywhereAction.FIND_EVERYWHERE)?.isEnabled ?: false - } - })) - - toolbar.add(Box.createHorizontalGlue()) - - if (SystemInfo.isLinux || SystemInfo.isWindows) { - toolbar.add(Box.createHorizontalStrut(16)) - } - - - // update btn - val updateBtn = actionContainerFactory.createButton(actionManager.getAction(Actions.APP_UPDATE)) - updateBtn.isVisible = updateBtn.isEnabled - updateBtn.addChangeListener { updateBtn.isVisible = updateBtn.isEnabled } - toolbar.add(updateBtn) - - - // 获取显示的Action,如果不是 false 那么就是显示出来 - for (action in getActions()) { - if (action.visible) { - val ac = actionManager.getAction(action.id) - if (ac == null) { - if (action.id == MultipleAction.MULTIPLE) { - toolbar.add(actionContainerFactory.createButton(MultipleAction.getInstance(windowScope))) - } - } else { - toolbar.add(actionContainerFactory.createButton(ac)) - } - } - } - - - if (toolbar is MyToolBar) { - toolbar.adjust() - } - - toolbar.revalidate() - toolbar.repaint() - } - } -} \ No newline at end of file diff --git a/src/main/kotlin/app/termora/TermoraToolbarModel.kt b/src/main/kotlin/app/termora/TermoraToolbarModel.kt new file mode 100644 index 0000000..cfef375 --- /dev/null +++ b/src/main/kotlin/app/termora/TermoraToolbarModel.kt @@ -0,0 +1,103 @@ +package app.termora + +import app.termora.Application.ohMyJson +import app.termora.actions.ActionManager +import app.termora.actions.MultipleAction +import app.termora.actions.SettingsAction +import app.termora.database.DatabaseManager +import app.termora.findeverywhere.FindEverywhereAction +import app.termora.snippet.SnippetAction +import org.apache.commons.lang3.StringUtils +import java.util.* +import javax.swing.event.EventListenerList + +internal class TermoraToolbarModel private constructor() { + companion object { + fun getInstance(): TermoraToolbarModel { + return ApplicationScope.forApplicationScope() + .getOrCreate(TermoraToolbarModel::class) { TermoraToolbarModel() } + } + } + + private val properties get() = DatabaseManager.getInstance().properties + private val eventListener = EventListenerList() + + + fun getActionManager() = ActionManager.getInstance() + + /** + * 获取到所有的 Action + */ + fun getAllActions(): List { + return listOf( + ToolBarAction(SnippetAction.SNIPPET, true), + ToolBarAction(Actions.SFTP, true), + ToolBarAction(Actions.TERMINAL_LOGGER, true), + ToolBarAction(Actions.MACRO, true), + ToolBarAction(Actions.KEYWORD_HIGHLIGHT, true), + ToolBarAction(Actions.KEY_MANAGER, true), + ToolBarAction(MultipleAction.MULTIPLE, true), + ToolBarAction(FindEverywhereAction.FIND_EVERYWHERE, true), + ToolBarAction(SettingsAction.SETTING, true), + ) + } + + /** + * 获取到所有 Action,会根据用户个性化排序/显示 + */ + fun getActions(): List { + val text = properties.getString( + "Termora.ToolBar.Actions", + StringUtils.EMPTY + ) + + val actions = getAllActions() + + if (text.isBlank()) { + return actions + } + + // 存储的 action + val storageActions = (ohMyJson.runCatching { + ohMyJson.decodeFromString>(text) + }.getOrNull() ?: return actions).toMutableList() + + for (action in actions) { + // 如果存储的 action 不包含这个,那么这个可能是新增的,新增的默认显示出来 + if (storageActions.none { it.id == action.id }) { + storageActions.addFirst(ToolBarAction(action.id, true)) + } + } + + // 如果存储的 Action 在所有 Action 里没有,那么移除 + storageActions.removeIf { e -> actions.none { e.id == it.id } } + + return storageActions + } + + fun setActions(actions: List) { + assertEventDispatchThread() + properties.putString("Termora.ToolBar.Actions", ohMyJson.encodeToString(actions)) + for (listener in eventListener.getListeners(TermoraToolbarModelListener::class.java)) { + listener.onChanged() + } + } + + + fun addTermoraToolbarModelListener(listener: TermoraToolbarModelListener): Disposable { + eventListener.add(TermoraToolbarModelListener::class.java, listener) + return object : Disposable { + override fun dispose() { + removeTermoraToolbarModelListener(listener) + } + } + } + + fun removeTermoraToolbarModelListener(listener: TermoraToolbarModelListener) { + eventListener.remove(TermoraToolbarModelListener::class.java, listener) + } + + interface TermoraToolbarModelListener : EventListener { + fun onChanged() + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/termora/ToolBarAction.kt b/src/main/kotlin/app/termora/ToolBarAction.kt new file mode 100644 index 0000000..090ffe5 --- /dev/null +++ b/src/main/kotlin/app/termora/ToolBarAction.kt @@ -0,0 +1,9 @@ +package app.termora + +import kotlinx.serialization.Serializable + +@Serializable +data class ToolBarAction( + val id: String, + val visible: Boolean, +) \ No newline at end of file diff --git a/src/main/kotlin/app/termora/actions/ActionManager.kt b/src/main/kotlin/app/termora/actions/ActionManager.kt index cb08236..e14b0a6 100644 --- a/src/main/kotlin/app/termora/actions/ActionManager.kt +++ b/src/main/kotlin/app/termora/actions/ActionManager.kt @@ -34,6 +34,7 @@ class ActionManager : org.jdesktop.swingx.action.ActionManager() { addAction(Actions.TERMINAL_LOGGER, TerminalLoggerAction()) addAction(Actions.SFTP, TransferAnAction()) addAction(SFTPCommandAction.SFTP_COMMAND, SFTPCommandAction()) + addAction(MultipleAction.MULTIPLE, MultipleAction.getInstance()) addAction(SnippetAction.SNIPPET, SnippetAction.getInstance()) addAction(Actions.MACRO, MacroAction()) addAction(Actions.KEY_MANAGER, KeyManagerAction()) diff --git a/src/main/kotlin/app/termora/actions/MultipleAction.kt b/src/main/kotlin/app/termora/actions/MultipleAction.kt index 76aee39..a6de375 100644 --- a/src/main/kotlin/app/termora/actions/MultipleAction.kt +++ b/src/main/kotlin/app/termora/actions/MultipleAction.kt @@ -1,14 +1,11 @@ package app.termora.actions -import app.termora.I18n -import app.termora.Icons -import app.termora.TerminalPanelFactory -import app.termora.WindowScope +import app.termora.* class MultipleAction private constructor() : AnAction( I18n.getString("termora.tools.multiple"), Icons.vcs -) { +), StateAction { companion object { @@ -17,8 +14,8 @@ class MultipleAction private constructor() : AnAction( */ const val MULTIPLE = "MultipleAction" - fun getInstance(windowScope: WindowScope): MultipleAction { - return windowScope.getOrCreate(MultipleAction::class) { MultipleAction() } + fun getInstance(): MultipleAction { + return ApplicationScope.forApplicationScope().getOrCreate(MultipleAction::class) { MultipleAction() } } } @@ -27,6 +24,24 @@ class MultipleAction private constructor() : AnAction( } override fun actionPerformed(evt: AnActionEvent) { + super.setSelected(false) + val windowScope = evt.getData(DataProviders.WindowScope) ?: return + setSelected(windowScope, isSelected(windowScope).not()) TerminalPanelFactory.getInstance().repaintAll() } + + override fun isSelected(): Boolean { + throw UnsupportedOperationException() + } + + override fun isSelected(windowScope: WindowScope): Boolean { + return windowScope.getBoolean("MultipleAction.isSelected", false) + } + + override fun setSelected(windowScope: WindowScope, selected: Boolean) { + windowScope.putBoolean("MultipleAction.isSelected", selected) + putValue("MultipleAction.isSelected", selected) + } + + } \ No newline at end of file diff --git a/src/main/kotlin/app/termora/actions/StateAction.kt b/src/main/kotlin/app/termora/actions/StateAction.kt new file mode 100644 index 0000000..3d56e4b --- /dev/null +++ b/src/main/kotlin/app/termora/actions/StateAction.kt @@ -0,0 +1,8 @@ +package app.termora.actions + +import app.termora.WindowScope + +interface StateAction { + fun isSelected(windowScope: WindowScope): Boolean + fun setSelected(windowScope: WindowScope, selected: Boolean) +} \ No newline at end of file diff --git a/src/main/kotlin/app/termora/findeverywhere/QuickActionsFindEverywhereProvider.kt b/src/main/kotlin/app/termora/findeverywhere/QuickActionsFindEverywhereProvider.kt index dbe9d47..7007d22 100644 --- a/src/main/kotlin/app/termora/findeverywhere/QuickActionsFindEverywhereProvider.kt +++ b/src/main/kotlin/app/termora/findeverywhere/QuickActionsFindEverywhereProvider.kt @@ -22,9 +22,7 @@ class QuickActionsFindEverywhereProvider(private val windowScope: WindowScope) : for (action in actions) { val ac = actionManager.getAction(action) if (ac == null) { - if (action == MultipleAction.MULTIPLE) { - results.add(ActionFindEverywhereResult(MultipleAction.getInstance(windowScope))) - } + continue } else { results.add(ActionFindEverywhereResult(ac)) } diff --git a/src/main/kotlin/app/termora/plugin/internal/badge/Badge.kt b/src/main/kotlin/app/termora/plugin/internal/badge/Badge.kt index 614a305..8da2001 100644 --- a/src/main/kotlin/app/termora/plugin/internal/badge/Badge.kt +++ b/src/main/kotlin/app/termora/plugin/internal/badge/Badge.kt @@ -1,18 +1,30 @@ package app.termora.plugin.internal.badge -import app.termora.WindowScope +import app.termora.* import java.awt.Color import java.util.* import javax.swing.JComponent import javax.swing.UIManager -class Badge private constructor() { +class Badge private constructor(scope: Scope) { companion object { fun getInstance(scope: WindowScope): Badge { - return scope.getOrCreate(Badge::class) { Badge() } + return scope.getOrCreate(Badge::class) { Badge(scope) } + } + + fun getInstance(): Badge { + val scope = ApplicationScope.forApplicationScope() + return scope.getOrCreate(Badge::class) { Badge(scope) } } } + init { + Disposer.register(scope, object : Disposable { + override fun dispose() { + map.clear() + } + }) + } private val map = WeakHashMap()