mirror of
https://github.com/TermoraDev/termora.git
synced 2026-01-16 02:12:58 +08:00
feat: add tab index
This commit is contained in:
@@ -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")
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -164,6 +164,8 @@ class TermoraFrame : JFrame(), DataProvider {
|
||||
|
||||
}).let { Disposer.register(windowScope, it) }
|
||||
|
||||
Disposer.register(windowScope, tabbedPane)
|
||||
|
||||
}
|
||||
|
||||
private fun initView() {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -226,7 +226,7 @@ class SSHTerminalTab(
|
||||
}
|
||||
|
||||
override fun getIcon(): Icon {
|
||||
return if (unread) Icons.terminalUnread else Icons.terminal
|
||||
return Icons.terminal
|
||||
}
|
||||
|
||||
override fun beforeClose() {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<!-- Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -->
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8.33214 2.63203L13.3321 7.07539C13.4389 7.17028 13.5 7.3063 13.5 7.44914V13C13.5 13.2761 13.2761 13.5 13 13.5H10C9.72386 13.5 9.5 13.2761 9.5 13V11C9.5 10.1716 8.82843 9.5 8 9.5C7.17157 9.5 6.5 10.1716 6.5 11V13C6.5 13.2761 6.27614 13.5 6 13.5H3C2.72386 13.5 2.5 13.2761 2.5 13V7.44914C2.5 7.3063 2.56109 7.17028 2.66786 7.07539L7.66786 2.63203C7.85729 2.46369 8.14271 2.46369 8.33214 2.63203Z" fill="#EBECF0" stroke="#6C707E" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M8.33214 2.63203L13.3321 7.07539C13.4389 7.17028 13.5 7.3063 13.5 7.44914V13C13.5 13.2761 13.2761 13.5 13 13.5H10C9.72386 13.5 9.5 13.2761 9.5 13V11C9.5 10.1716 8.82843 9.5 8 9.5C7.17157 9.5 6.5 10.1716 6.5 11V13C6.5 13.2761 6.27614 13.5 6 13.5H3C2.72386 13.5 2.5 13.2761 2.5 13V7.44914C2.5 7.3063 2.56109 7.17028 2.66786 7.07539L7.66786 2.63203C7.85729 2.46369 8.14271 2.46369 8.33214 2.63203Z" stroke="#6C707E" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 716 B After Width: | Height: | Size: 701 B |
@@ -1,4 +1,4 @@
|
||||
<!-- Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -->
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8.33214 2.63203L13.3321 7.07539C13.4389 7.17028 13.5 7.3063 13.5 7.44914V13C13.5 13.2761 13.2761 13.5 13 13.5H10C9.72386 13.5 9.5 13.2761 9.5 13V11C9.5 10.1716 8.82843 9.5 8 9.5C7.17157 9.5 6.5 10.1716 6.5 11V13C6.5 13.2761 6.27614 13.5 6 13.5H3C2.72386 13.5 2.5 13.2761 2.5 13V7.44914C2.5 7.3063 2.56109 7.17028 2.66786 7.07539L7.66786 2.63203C7.85729 2.46369 8.14271 2.46369 8.33214 2.63203Z" fill="#43454A" stroke="#CED0D6" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M8.33214 2.63203L13.3321 7.07539C13.4389 7.17028 13.5 7.3063 13.5 7.44914V13C13.5 13.2761 13.2761 13.5 13 13.5H10C9.72386 13.5 9.5 13.2761 9.5 13V11C9.5 10.1716 8.82843 9.5 8 9.5C7.17157 9.5 6.5 10.1716 6.5 11V13C6.5 13.2761 6.27614 13.5 6 13.5H3C2.72386 13.5 2.5 13.2761 2.5 13V7.44914C2.5 7.3063 2.56109 7.17028 2.66786 7.07539L7.66786 2.63203C7.85729 2.46369 8.14271 2.46369 8.33214 2.63203Z" stroke="#CED0D6" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 716 B After Width: | Height: | Size: 701 B |
Reference in New Issue
Block a user