refactor: key shortcuts

This commit is contained in:
hstyi
2025-07-08 14:40:38 +08:00
committed by hstyi
parent 057da4e297
commit 45be9008fd
4 changed files with 132 additions and 119 deletions

View File

@@ -1,13 +1,14 @@
package app.termora package app.termora
import app.termora.actions.DataProvider import app.termora.actions.*
import app.termora.actions.DataProviderSupport import app.termora.database.DatabaseChangedExtension
import app.termora.actions.DataProviders import app.termora.database.DatabasePropertiesChangedExtension
import app.termora.actions.OpenHostAction
import app.termora.findeverywhere.FindEverywhereProvider import app.termora.findeverywhere.FindEverywhereProvider
import app.termora.findeverywhere.FindEverywhereProviderExtension import app.termora.findeverywhere.FindEverywhereProviderExtension
import app.termora.findeverywhere.FindEverywhereResult import app.termora.findeverywhere.FindEverywhereResult
import app.termora.keymap.KeyShortcut
import app.termora.keymap.KeymapManager
import app.termora.plugin.ExtensionManager import app.termora.plugin.ExtensionManager
import app.termora.plugin.internal.extension.DynamicExtensionHandler import app.termora.plugin.internal.extension.DynamicExtensionHandler
import app.termora.plugin.internal.ssh.SSHProtocolProvider import app.termora.plugin.internal.ssh.SSHProtocolProvider
@@ -21,16 +22,12 @@ import com.formdev.flatlaf.util.SystemInfo
import com.jetbrains.JBR import com.jetbrains.JBR
import org.apache.commons.lang3.ArrayUtils import org.apache.commons.lang3.ArrayUtils
import org.apache.commons.lang3.StringUtils import org.apache.commons.lang3.StringUtils
import org.jdesktop.swingx.action.ActionManager
import java.awt.* import java.awt.*
import java.awt.event.* import java.awt.event.*
import java.util.* import java.util.*
import javax.imageio.ImageIO import javax.imageio.ImageIO
import javax.swing.Icon import javax.swing.*
import javax.swing.JComponent
import javax.swing.JFrame
import javax.swing.SwingUtilities.isEventDispatchThread import javax.swing.SwingUtilities.isEventDispatchThread
import javax.swing.UIManager
fun assertEventDispatchThread() { fun assertEventDispatchThread() {
@@ -50,11 +47,14 @@ class TermoraFrame : JFrame(), DataProvider {
private val dataProviderSupport = DataProviderSupport() private val dataProviderSupport = DataProviderSupport()
private var notifyListeners = emptyArray<NotifyListener>() private var notifyListeners = emptyArray<NotifyListener>()
private val moveMouseAdapter = createMoveMouseAdaptor() private val moveMouseAdapter = createMoveMouseAdaptor()
private val keymapManager get() = KeymapManager.getInstance()
private val actionManager get() = ActionManager.getInstance()
private val dynamicExtensionHandler get() = DynamicExtensionHandler.getInstance()
init { init {
initView() initView()
initEvents() initEvents()
initKeymap()
} }
private fun initEvents() { private fun initEvents() {
@@ -72,8 +72,16 @@ class TermoraFrame : JFrame(), DataProvider {
toolbar.getJToolBar().addMouseMotionListener(moveMouseAdapter) 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 // FindEverywhere
DynamicExtensionHandler.getInstance() dynamicExtensionHandler
.register(FindEverywhereProviderExtension::class.java, object : FindEverywhereProviderExtension { .register(FindEverywhereProviderExtension::class.java, object : FindEverywhereProviderExtension {
private val hostTreeModel get() = NewHostTreeModel.getInstance() private val hostTreeModel get() = NewHostTreeModel.getInstance()
@@ -115,8 +123,7 @@ class TermoraFrame : JFrame(), DataProvider {
private val showMoreInfo get() = EnableManager.getInstance().isShowMoreInfo() private val showMoreInfo get() = EnableManager.getInstance().isShowMoreInfo()
override fun actionPerformed(e: ActionEvent) { override fun actionPerformed(e: ActionEvent) {
ActionManager.getInstance() actionManager.getAction(OpenHostAction.OPEN_HOST)
.getAction(OpenHostAction.OPEN_HOST)
?.actionPerformed(OpenHostActionEvent(e.source, host, e)) ?.actionPerformed(OpenHostActionEvent(e.source, host, e))
} }
@@ -149,7 +156,6 @@ class TermoraFrame : JFrame(), DataProvider {
} }
private fun initView() { private fun initView() {
// macOS 要避开左边的控制栏 // macOS 要避开左边的控制栏
@@ -209,6 +215,46 @@ class TermoraFrame : JFrame(), DataProvider {
dataProviderSupport.addData(DataProviders.WindowScope, windowScope) 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<String>): 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 <T : Any> getData(dataKey: DataKey<T>): T? { override fun <T : Any> getData(dataKey: DataKey<T>): T? {
return dataProviderSupport.getData(dataKey) ?: terminalTabbed.getData(dataKey) return dataProviderSupport.getData(dataKey) ?: terminalTabbed.getData(dataKey)
} }
@@ -355,6 +401,35 @@ class TermoraFrame : JFrame(), DataProvider {
return object : MouseAdapter() {} 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() { private inner class GlassPane : JComponent() {

View File

@@ -504,6 +504,8 @@ class DatabaseManager private constructor() : Disposable {
protected open fun putString(key: String, value: String) { protected open fun putString(key: String, value: String) {
databaseManager.setSetting("${name}.$key", value) databaseManager.setSetting("${name}.$key", value)
// 触发变动
DatabasePropertiesChangedExtension.onPropertyChanged(name, key, value)
} }

View File

@@ -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)
}

View File

@@ -1,27 +1,14 @@
package app.termora.keymap package app.termora.keymap
import app.termora.ApplicationScope import app.termora.ApplicationScope
import app.termora.DialogWrapper
import app.termora.Disposable import app.termora.Disposable
import app.termora.SwingUtils
import app.termora.account.AccountManager import app.termora.account.AccountManager
import app.termora.actions.AnActionEvent
import app.termora.database.Data import app.termora.database.Data
import app.termora.database.DataType import app.termora.database.DataType
import app.termora.database.DatabaseManager import app.termora.database.DatabaseManager
import app.termora.database.OwnerType import app.termora.database.OwnerType
import com.formdev.flatlaf.util.SystemInfo import com.formdev.flatlaf.util.SystemInfo
import org.apache.commons.lang3.StringUtils
import org.jdesktop.swingx.action.ActionManager
import org.slf4j.LoggerFactory 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 { 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 database get() = DatabaseManager.getInstance()
private val properties get() = DatabaseManager.getInstance().properties private val properties get() = DatabaseManager.getInstance().properties
private val keymaps = linkedMapOf<String, Keymap>() private val keymaps = linkedMapOf<String, Keymap>()
private val accountManager get() = AccountManager.getInstance() private val accountManager get() = AccountManager.getInstance()
private val activeKeymap get() = properties.getString("Keymap.Active") private val activeKeymap get() = properties.getString("Keymap.Active")
private val keyboardFocusManager by lazy { KeyboardFocusManager.getCurrentKeyboardFocusManager() }
init { init {
keyboardFocusManager.addKeyEventDispatcher(keymapKeyEventDispatcher)
try { try {
for (data in database.rawData(DataType.Keymap)) { for (data in database.rawData(DataType.Keymap)) {
try { try {
@@ -63,13 +46,8 @@ class KeymapManager private constructor() : Disposable {
} }
} }
MacOSKeymap.getInstance().let { MacOSKeymap.getInstance().let { keymaps[it.name] = it }
keymaps[it.name] = it WindowsKeymap.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) keymaps.putFirst(keymap.name, keymap)
val accountId = accountManager.getAccountId() val accountId = accountManager.getAccountId()
database.save( database.saveAndIncrementVersion(
Data( Data(
id = keymap.id, id = keymap.id,
ownerId = accountId, ownerId = accountId,
@@ -122,84 +100,4 @@ class KeymapManager private constructor() : Disposable {
database.delete(id, DataType.Keymap.name) 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<JPopupMenu> = 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)
}
} }