From 969ddc36626f6007e373917cbbabd49b6145eebd Mon Sep 17 00:00:00 2001 From: hstyi Date: Thu, 7 Aug 2025 20:53:52 +0800 Subject: [PATCH] feat: add tab index --- .../kotlin/app/termora/HostTerminalTab.kt | 22 +-- src/main/kotlin/app/termora/MyTabbedPane.kt | 128 +++++++++++++++++- src/main/kotlin/app/termora/TerminalTabbed.kt | 8 +- src/main/kotlin/app/termora/TermoraFrame.kt | 2 + src/main/kotlin/app/termora/WelcomePanel.kt | 2 +- .../plugin/internal/local/LocalTerminalTab.kt | 2 +- .../plugin/internal/ssh/SSHTerminalTab.kt | 2 +- src/main/resources/icons/homeFolder.svg | 2 +- src/main/resources/icons/homeFolder_dark.svg | 2 +- 9 files changed, 135 insertions(+), 35 deletions(-) diff --git a/src/main/kotlin/app/termora/HostTerminalTab.kt b/src/main/kotlin/app/termora/HostTerminalTab.kt index 7cf8b26..4d629a5 100644 --- a/src/main/kotlin/app/termora/HostTerminalTab.kt +++ b/src/main/kotlin/app/termora/HostTerminalTab.kt @@ -3,14 +3,16 @@ package app.termora import app.termora.actions.AnActionEvent import app.termora.actions.DataProvider import app.termora.actions.DataProviders -import app.termora.terminal.* +import app.termora.terminal.ControlCharacters +import app.termora.terminal.DataKey +import app.termora.terminal.DataListener +import app.termora.terminal.Terminal import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.cancel import kotlinx.coroutines.swing.Swing import org.apache.commons.lang3.StringUtils -import java.beans.PropertyChangeEvent import java.util.* import javax.swing.Icon @@ -29,11 +31,6 @@ abstract class HostTerminalTab( .getData(DataProviders.TerminalTabbedManager) protected val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Swing) protected val terminalModel get() = terminal.getTerminalModel() - protected var unread = false - set(value) { - field = value - firePropertyChange(PropertyChangeEvent(this, "icon", null, null)) - } /* visualTerminal */ @@ -45,15 +42,6 @@ abstract class HostTerminalTab( terminal.getTerminalModel().setData(Host, host) terminal.getTerminalModel().addDataListener(object : DataListener { override fun onChanged(key: DataKey<*>, data: Any) { - if (key == VisualTerminal.Written) { - if (hasFocus || unread) { - return - } - // 如果当前选中的不是这个 Tab,那么设置成未读 - if (terminalTabbedManager?.getSelectedTerminalTab() != this@HostTerminalTab) { - unread = true - } - } } }) } @@ -75,8 +63,6 @@ abstract class HostTerminalTab( override fun onGrabFocus() { super.onGrabFocus() - if (!unread) return - unread = false } @Suppress("UNCHECKED_CAST") diff --git a/src/main/kotlin/app/termora/MyTabbedPane.kt b/src/main/kotlin/app/termora/MyTabbedPane.kt index d61fea0..1bc9e70 100644 --- a/src/main/kotlin/app/termora/MyTabbedPane.kt +++ b/src/main/kotlin/app/termora/MyTabbedPane.kt @@ -2,19 +2,20 @@ package app.termora import app.termora.actions.AnActionEvent import app.termora.actions.DataProviders +import app.termora.actions.SwitchTabAction +import app.termora.keymap.KeyShortcut +import app.termora.keymap.KeymapManager import com.formdev.flatlaf.extras.components.FlatTabbedPane +import com.formdev.flatlaf.ui.FlatTabbedPaneUI import org.apache.commons.lang3.StringUtils import java.awt.* import java.awt.event.* import java.awt.image.BufferedImage import java.util.* -import javax.swing.ImageIcon -import javax.swing.JDialog -import javax.swing.JLabel -import javax.swing.SwingUtilities +import javax.swing.* import kotlin.math.abs -class MyTabbedPane : FlatTabbedPane() { +class MyTabbedPane : FlatTabbedPane(), Disposable { private val dragMouseAdaptor = DragMouseAdaptor() private val terminalTabbedManager @@ -23,6 +24,14 @@ class MyTabbedPane : FlatTabbedPane() { private val owner get() = AnActionEvent(this, StringUtils.EMPTY, EventObject(this)) .getData(DataProviders.TermoraFrame) as TermoraFrame + private val keymap get() = KeymapManager.getInstance().getActiveKeymap() + private var isSwitchTabMode = false + set(value) { + field = value + repaint() + } + + private val isScreen get() = TermoraLayout.Layout == TermoraLayout.Screen init { isFocusable = false @@ -38,6 +47,16 @@ class MyTabbedPane : FlatTabbedPane() { private fun initEvents() { addMouseListener(dragMouseAdaptor) addMouseMotionListener(dragMouseAdaptor) + + val awtEventListener = MyAWTEventListener() + toolkit.addAWTEventListener(awtEventListener, AWTEvent.KEY_EVENT_MASK or AWTEvent.WINDOW_EVENT_MASK) + + Disposer.register(this, object : Disposable { + override fun dispose() { + toolkit.removeAWTEventListener(awtEventListener) + } + }) + } override fun processMouseEvent(e: MouseEvent) { @@ -70,6 +89,29 @@ class MyTabbedPane : FlatTabbedPane() { firePropertyChange("selectedIndex", oldIndex, index) } + override fun updateUI() { + super.updateUI() + setUI(MyMyTabbedPaneUI()) + } + + private inner class MyAWTEventListener : AWTEventListener { + override fun eventDispatched(event: AWTEvent) { + if (event is KeyEvent) { + if (isSwitchTabMode) isSwitchTabMode = false + val shortcuts = keymap.getShortcut(SwitchTabAction.SWITCH_TAB) + if (shortcuts.isEmpty()) return + val shortcut = shortcuts.first() as KeyShortcut + val modifiers = KeyStroke.getKeyStroke(event.keyCode, event.modifiersEx).modifiers + if (shortcut.keyStroke.modifiers != modifiers) return + if (SwingUtilities.getWindowAncestor(event.component) != owner) return + if (isSwitchTabMode.not()) isSwitchTabMode = true + } else if (event is WindowEvent) { + if (event.id == WindowEvent.WINDOW_LOST_FOCUS || event.id == WindowEvent.WINDOW_DEACTIVATED) { + if (isSwitchTabMode) isSwitchTabMode = false + } + } + } + } private inner class DragMouseAdaptor : MouseAdapter(), KeyEventDispatcher { private var mousePressedPoint = Point() @@ -267,5 +309,81 @@ class MyTabbedPane : FlatTabbedPane() { } } + private inner class MyMyTabbedPaneUI : FlatTabbedPaneUI() { + override fun paintIcon( + g: Graphics, + tabPlacement: Int, + tabIndex: Int, + icon: Icon, + iconRect: Rectangle?, + isSelected: Boolean + ) { + super.paintIcon(g, tabPlacement, tabIndex, MyIcon(icon, tabIndex, isSelected), iconRect, isSelected) + } + + + override fun createMoreTabsButton(): JButton { + return MyMoreTabsButton() + } + + private inner class MyMoreTabsButton : FlatMoreTabsButton() { + override fun createTabMenuItem(tabIndex: Int): JMenuItem? { + val item = super.createTabMenuItem(tabIndex) + if (tabIndex == 0 && isScreen) { + item.text = Application.getName() + } + return item + } + } + } + + + override fun getIconAt(index: Int): Icon? { + if (isSwitchTabMode) { + return MyIcon(super.getIconAt(index), index, selectedIndex == index) + } + return super.getIconAt(index) + } + + private inner class MyIcon(private val icon: Icon, private val tabIndex: Int, private val isSelected: Boolean) : + Icon { + override fun paintIcon(c: Component, g: Graphics, x: Int, y: Int) { + if (isScreen && tabIndex == 0) { + icon.paintIcon(c, g, x, y) + return + } + + if (isSwitchTabMode.not()) { + icon.paintIcon(c, g, x, y) + return + } + + if (g !is Graphics2D) return + + g.save() + setupAntialiasing(g) + + val fm = g.getFontMetrics(g.font) + val text = "${tabIndex + 1}" + val textWidth = fm.stringWidth(text) + val textHeight = fm.ascent + + val centerX = x + (icon.iconWidth - textWidth) / 2 + val centerY = y + (icon.iconHeight + textHeight) / 2 - 1 + + g.color = c.getForeground() + g.drawString(text, centerX, centerY) + + g.restore() + } + + override fun getIconWidth(): Int { + return icon.iconWidth + } + + override fun getIconHeight(): Int { + return icon.iconHeight + } + } } \ No newline at end of file diff --git a/src/main/kotlin/app/termora/TerminalTabbed.kt b/src/main/kotlin/app/termora/TerminalTabbed.kt index 75fcec8..4cf0a9b 100644 --- a/src/main/kotlin/app/termora/TerminalTabbed.kt +++ b/src/main/kotlin/app/termora/TerminalTabbed.kt @@ -337,13 +337,7 @@ class TerminalTabbed( val c = tab.getJComponent() val title = (c.getClientProperty(titleProperty) ?: tab.getTitle()).toString() - tabbedPane.insertTab( - title, - tab.getIcon(), - c, - StringUtils.EMPTY, - index - ) + tabbedPane.insertTab(title, tab.getIcon(), c, StringUtils.EMPTY, index) // 设置标题 c.putClientProperty(titleProperty, title) diff --git a/src/main/kotlin/app/termora/TermoraFrame.kt b/src/main/kotlin/app/termora/TermoraFrame.kt index 1764c37..902e2db 100644 --- a/src/main/kotlin/app/termora/TermoraFrame.kt +++ b/src/main/kotlin/app/termora/TermoraFrame.kt @@ -164,6 +164,8 @@ class TermoraFrame : JFrame(), DataProvider { }).let { Disposer.register(windowScope, it) } + Disposer.register(windowScope, tabbedPane) + } private fun initView() { diff --git a/src/main/kotlin/app/termora/WelcomePanel.kt b/src/main/kotlin/app/termora/WelcomePanel.kt index c5f4b20..8291e24 100644 --- a/src/main/kotlin/app/termora/WelcomePanel.kt +++ b/src/main/kotlin/app/termora/WelcomePanel.kt @@ -224,7 +224,7 @@ class WelcomePanel() : JPanel(BorderLayout()), Disposable, TerminalTab, DataProv override fun getTitle(): String { - return I18n.getString("termora.title") + return StringUtils.EMPTY } override fun getIcon(): Icon { diff --git a/src/main/kotlin/app/termora/plugin/internal/local/LocalTerminalTab.kt b/src/main/kotlin/app/termora/plugin/internal/local/LocalTerminalTab.kt index f42b694..519d292 100644 --- a/src/main/kotlin/app/termora/plugin/internal/local/LocalTerminalTab.kt +++ b/src/main/kotlin/app/termora/plugin/internal/local/LocalTerminalTab.kt @@ -31,7 +31,7 @@ class LocalTerminalTab(windowScope: WindowScope, host: Host) : } override fun getIcon(): Icon { - return if (unread) Icons.terminalUnread else Icons.terminal + return Icons.terminal } override fun willBeClose(): Boolean { diff --git a/src/main/kotlin/app/termora/plugin/internal/ssh/SSHTerminalTab.kt b/src/main/kotlin/app/termora/plugin/internal/ssh/SSHTerminalTab.kt index 8dbbe42..83778cc 100644 --- a/src/main/kotlin/app/termora/plugin/internal/ssh/SSHTerminalTab.kt +++ b/src/main/kotlin/app/termora/plugin/internal/ssh/SSHTerminalTab.kt @@ -226,7 +226,7 @@ class SSHTerminalTab( } override fun getIcon(): Icon { - return if (unread) Icons.terminalUnread else Icons.terminal + return Icons.terminal } override fun beforeClose() { diff --git a/src/main/resources/icons/homeFolder.svg b/src/main/resources/icons/homeFolder.svg index 93d056b..5d5a36e 100644 --- a/src/main/resources/icons/homeFolder.svg +++ b/src/main/resources/icons/homeFolder.svg @@ -1,4 +1,4 @@ - + diff --git a/src/main/resources/icons/homeFolder_dark.svg b/src/main/resources/icons/homeFolder_dark.svg index 05fe1ec..205e307 100644 --- a/src/main/resources/icons/homeFolder_dark.svg +++ b/src/main/resources/icons/homeFolder_dark.svg @@ -1,4 +1,4 @@ - +