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