mirror of
https://github.com/TermoraDev/termora.git
synced 2026-01-15 18:02:58 +08:00
refactor: key shortcuts
This commit is contained in:
@@ -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<NotifyListener>()
|
||||
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<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? {
|
||||
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() {
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
}
|
||||
@@ -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<String, Keymap>()
|
||||
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<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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user