diff --git a/src/main/kotlin/app/termora/TermoraFrame.kt b/src/main/kotlin/app/termora/TermoraFrame.kt index f4a4654..941cf6a 100644 --- a/src/main/kotlin/app/termora/TermoraFrame.kt +++ b/src/main/kotlin/app/termora/TermoraFrame.kt @@ -1,13 +1,14 @@ package app.termora -import app.termora.actions.DataProvider -import app.termora.actions.DataProviderSupport -import app.termora.actions.DataProviders -import app.termora.actions.OpenHostAction +import app.termora.actions.* +import app.termora.database.DatabaseChangedExtension +import app.termora.database.DatabasePropertiesChangedExtension import app.termora.findeverywhere.FindEverywhereProvider import app.termora.findeverywhere.FindEverywhereProviderExtension import app.termora.findeverywhere.FindEverywhereResult +import app.termora.keymap.KeyShortcut +import app.termora.keymap.KeymapManager import app.termora.plugin.ExtensionManager import app.termora.plugin.internal.extension.DynamicExtensionHandler import app.termora.plugin.internal.ssh.SSHProtocolProvider @@ -21,16 +22,12 @@ import com.formdev.flatlaf.util.SystemInfo import com.jetbrains.JBR import org.apache.commons.lang3.ArrayUtils import org.apache.commons.lang3.StringUtils -import org.jdesktop.swingx.action.ActionManager import java.awt.* import java.awt.event.* import java.util.* import javax.imageio.ImageIO -import javax.swing.Icon -import javax.swing.JComponent -import javax.swing.JFrame +import javax.swing.* import javax.swing.SwingUtilities.isEventDispatchThread -import javax.swing.UIManager fun assertEventDispatchThread() { @@ -50,11 +47,14 @@ class TermoraFrame : JFrame(), DataProvider { private val dataProviderSupport = DataProviderSupport() private var notifyListeners = emptyArray() private val moveMouseAdapter = createMoveMouseAdaptor() - + private val keymapManager get() = KeymapManager.getInstance() + private val actionManager get() = ActionManager.getInstance() + private val dynamicExtensionHandler get() = DynamicExtensionHandler.getInstance() init { initView() initEvents() + initKeymap() } private fun initEvents() { @@ -72,8 +72,16 @@ class TermoraFrame : JFrame(), DataProvider { toolbar.getJToolBar().addMouseMotionListener(moveMouseAdapter) } + // 快捷键变动时重新监听 + val refresher = KeymapRefresher() + dynamicExtensionHandler.register(DatabasePropertiesChangedExtension::class.java, refresher) + .let { Disposer.register(windowScope, it) } + dynamicExtensionHandler.register(DatabaseChangedExtension::class.java, refresher) + .let { Disposer.register(windowScope, it) } + + // FindEverywhere - DynamicExtensionHandler.getInstance() + dynamicExtensionHandler .register(FindEverywhereProviderExtension::class.java, object : FindEverywhereProviderExtension { private val hostTreeModel get() = NewHostTreeModel.getInstance() @@ -115,8 +123,7 @@ class TermoraFrame : JFrame(), DataProvider { private val showMoreInfo get() = EnableManager.getInstance().isShowMoreInfo() override fun actionPerformed(e: ActionEvent) { - ActionManager.getInstance() - .getAction(OpenHostAction.OPEN_HOST) + actionManager.getAction(OpenHostAction.OPEN_HOST) ?.actionPerformed(OpenHostActionEvent(e.source, host, e)) } @@ -149,7 +156,6 @@ class TermoraFrame : JFrame(), DataProvider { } - private fun initView() { // macOS 要避开左边的控制栏 @@ -209,6 +215,46 @@ class TermoraFrame : JFrame(), DataProvider { dataProviderSupport.addData(DataProviders.WindowScope, windowScope) } + private fun initKeymap() { + assertEventDispatchThread() + + val keymap = keymapManager.getActiveKeymap() + val inputMap = rootPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW) + val actionMap = rootPane.actionMap + + // 移除之前所有的快捷键 + inputMap.clear() + actionMap.clear() + + 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) + } + + } + + private fun redirectAction(actionIds: List): Action { + return object : AbstractAction() { + private val keyboardFocusManager get() = KeyboardFocusManager.getCurrentKeyboardFocusManager() + override fun actionPerformed(e: ActionEvent) { + var source = e.source + if (source == rootPane) { + val focusOwner = keyboardFocusManager.focusOwner + if (focusOwner is JComponent) { + source = focusOwner + } + } + + for (actionId in actionIds) { + val action = actionManager.getAction(actionId) ?: continue + action.actionPerformed(RedirectAnActionEvent(source, e.actionCommand, e)) + } + } + } + } + override fun getData(dataKey: DataKey): T? { return dataProviderSupport.getData(dataKey) ?: terminalTabbed.getData(dataKey) } @@ -355,6 +401,35 @@ class TermoraFrame : JFrame(), DataProvider { return object : MouseAdapter() {} } + private inner class KeymapRefresher : DatabasePropertiesChangedExtension, DatabaseChangedExtension { + + override fun onDataChanged( + id: String, + type: String, + action: DatabaseChangedExtension.Action, + source: DatabaseChangedExtension.Source + ) { + if (type != "Keymap") return + refresh() + } + + override fun onPropertyChanged(name: String, key: String, value: String) { + if (name != "Setting.Properties") return + if (key != "Keymap.Active") return + refresh() + } + + private fun refresh() { + initKeymap() + } + + } + + private inner class RedirectAnActionEvent( + source: Any, + command: String, + event: EventObject + ) : AnActionEvent(source, command, event) private inner class GlassPane : JComponent() { diff --git a/src/main/kotlin/app/termora/database/DatabaseManager.kt b/src/main/kotlin/app/termora/database/DatabaseManager.kt index 52f0af1..e91ffbd 100644 --- a/src/main/kotlin/app/termora/database/DatabaseManager.kt +++ b/src/main/kotlin/app/termora/database/DatabaseManager.kt @@ -504,6 +504,8 @@ class DatabaseManager private constructor() : Disposable { protected open fun putString(key: String, value: String) { databaseManager.setSetting("${name}.$key", value) + // 触发变动 + DatabasePropertiesChangedExtension.onPropertyChanged(name, key, value) } diff --git a/src/main/kotlin/app/termora/database/DatabasePropertiesChangedExtension.kt b/src/main/kotlin/app/termora/database/DatabasePropertiesChangedExtension.kt new file mode 100644 index 0000000..3d3c9de --- /dev/null +++ b/src/main/kotlin/app/termora/database/DatabasePropertiesChangedExtension.kt @@ -0,0 +1,38 @@ +package app.termora.database + +import app.termora.database.DatabaseManager.Companion.log +import app.termora.plugin.Extension +import app.termora.plugin.ExtensionManager +import javax.swing.SwingUtilities + +internal interface DatabasePropertiesChangedExtension : Extension { + + companion object { + fun onPropertyChanged(name: String, key: String, value: String) { + if (SwingUtilities.isEventDispatchThread()) { + for (extension in ExtensionManager.getInstance() + .getExtensions(DatabasePropertiesChangedExtension::class.java)) { + try { + extension.onPropertyChanged(name, key, value) + } catch (e: Exception) { + if (log.isErrorEnabled) { + log.error(e.message, e) + } + } + } + } else { + SwingUtilities.invokeLater { onPropertyChanged(name, key, value) } + } + } + } + + + /** + * 属性数据变动 + * + * @param name 属性名 + * @param key key + */ + fun onPropertyChanged(name: String, key: String, value: String) + +} \ No newline at end of file diff --git a/src/main/kotlin/app/termora/keymap/KeymapManager.kt b/src/main/kotlin/app/termora/keymap/KeymapManager.kt index 484cf35..1399950 100644 --- a/src/main/kotlin/app/termora/keymap/KeymapManager.kt +++ b/src/main/kotlin/app/termora/keymap/KeymapManager.kt @@ -1,27 +1,14 @@ package app.termora.keymap import app.termora.ApplicationScope -import app.termora.DialogWrapper import app.termora.Disposable -import app.termora.SwingUtils import app.termora.account.AccountManager -import app.termora.actions.AnActionEvent import app.termora.database.Data import app.termora.database.DataType import app.termora.database.DatabaseManager import app.termora.database.OwnerType import com.formdev.flatlaf.util.SystemInfo -import org.apache.commons.lang3.StringUtils -import org.jdesktop.swingx.action.ActionManager import org.slf4j.LoggerFactory -import java.awt.Container -import java.awt.KeyEventDispatcher -import java.awt.KeyboardFocusManager -import java.awt.event.KeyEvent -import javax.swing.JComponent -import javax.swing.JDialog -import javax.swing.JPopupMenu -import javax.swing.KeyStroke class KeymapManager private constructor() : Disposable { @@ -34,17 +21,13 @@ class KeymapManager private constructor() : Disposable { } } - private val keymapKeyEventDispatcher = KeymapKeyEventDispatcher() private val database get() = DatabaseManager.getInstance() private val properties get() = DatabaseManager.getInstance().properties private val keymaps = linkedMapOf() private val accountManager get() = AccountManager.getInstance() private val activeKeymap get() = properties.getString("Keymap.Active") - private val keyboardFocusManager by lazy { KeyboardFocusManager.getCurrentKeyboardFocusManager() } init { - keyboardFocusManager.addKeyEventDispatcher(keymapKeyEventDispatcher) - try { for (data in database.rawData(DataType.Keymap)) { try { @@ -63,13 +46,8 @@ class KeymapManager private constructor() : Disposable { } } - MacOSKeymap.getInstance().let { - keymaps[it.name] = it - } - - WindowsKeymap.getInstance().let { - keymaps[it.name] = it - } + MacOSKeymap.getInstance().let { keymaps[it.name] = it } + WindowsKeymap.getInstance().let { keymaps[it.name] = it } } @@ -102,7 +80,7 @@ class KeymapManager private constructor() : Disposable { keymaps.putFirst(keymap.name, keymap) val accountId = accountManager.getAccountId() - database.save( + database.saveAndIncrementVersion( Data( id = keymap.id, ownerId = accountId, @@ -122,84 +100,4 @@ class KeymapManager private constructor() : Disposable { database.delete(id, DataType.Keymap.name) } - private inner class KeymapKeyEventDispatcher : KeyEventDispatcher { - - override fun dispatchKeyEvent(e: KeyEvent): Boolean { - if (e.isConsumed || e.id != KeyEvent.KEY_PRESSED || e.modifiersEx == 0) { - return false - } - - val keyStroke = KeyStroke.getKeyStrokeForEvent(e) - val component = e.source - - if (component is JComponent) { - // 如果这个键已经被组件注册了,那么忽略 - if (getConditionForKeyStroke(component, keyStroke) != JComponent.UNDEFINED_CONDITION) { - return false - } - } - - - val shortcuts = getActiveKeymap() - val actionIds = shortcuts.getActionIds(KeyShortcut(keyStroke)) - if (actionIds.isEmpty()) { - return false - } - - val focusedWindow = keyboardFocusManager.focusedWindow - if (focusedWindow is DialogWrapper) { - if (!focusedWindow.processGlobalKeymap) { - return false - } - } else if (focusedWindow is JDialog) { - return false - } - - // 如果当前有 Popup ,那么不派发事件 - val c = KeyboardFocusManager.getCurrentKeyboardFocusManager().focusOwner - if (c is Container) { - val popups: List = SwingUtils.getDescendantsOfType( - JPopupMenu::class.java, - c, true - ) - if (popups.isNotEmpty()) { - return false - } - } - - val evt = AnActionEvent(e.source, StringUtils.EMPTY, e) - for (actionId in actionIds) { - val action = ActionManager.getInstance().getAction(actionId) ?: continue - if (!action.isEnabled) { - continue - } - action.actionPerformed(evt) - if (evt.isConsumed) { - return true - } - } - - return false - } - - private fun getConditionForKeyStroke(c: JComponent, keyStroke: KeyStroke): Int { - val condition = c.getConditionForKeyStroke(keyStroke) - - // 如果这个键已经被组件注册了,那么忽略 - if (condition != JComponent.UNDEFINED_CONDITION) { - return condition - } - - if (c.parent is JComponent) { - return getConditionForKeyStroke(c.parent as JComponent, keyStroke) - } - - return JComponent.UNDEFINED_CONDITION - } - - } - - override fun dispose() { - keyboardFocusManager.removeKeyEventDispatcher(keymapKeyEventDispatcher) - } } \ No newline at end of file