mirror of
https://github.com/TermoraDev/termora.git
synced 2026-01-15 18:02: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.AnActionEvent
|
||||||
import app.termora.actions.DataProvider
|
import app.termora.actions.DataProvider
|
||||||
import app.termora.actions.DataProviders
|
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.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.SupervisorJob
|
import kotlinx.coroutines.SupervisorJob
|
||||||
import kotlinx.coroutines.cancel
|
import kotlinx.coroutines.cancel
|
||||||
import kotlinx.coroutines.swing.Swing
|
import kotlinx.coroutines.swing.Swing
|
||||||
import org.apache.commons.lang3.StringUtils
|
import org.apache.commons.lang3.StringUtils
|
||||||
import java.beans.PropertyChangeEvent
|
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import javax.swing.Icon
|
import javax.swing.Icon
|
||||||
|
|
||||||
@@ -29,11 +31,6 @@ abstract class HostTerminalTab(
|
|||||||
.getData(DataProviders.TerminalTabbedManager)
|
.getData(DataProviders.TerminalTabbedManager)
|
||||||
protected val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Swing)
|
protected val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Swing)
|
||||||
protected val terminalModel get() = terminal.getTerminalModel()
|
protected val terminalModel get() = terminal.getTerminalModel()
|
||||||
protected var unread = false
|
|
||||||
set(value) {
|
|
||||||
field = value
|
|
||||||
firePropertyChange(PropertyChangeEvent(this, "icon", null, null))
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* visualTerminal */
|
/* visualTerminal */
|
||||||
@@ -45,15 +42,6 @@ abstract class HostTerminalTab(
|
|||||||
terminal.getTerminalModel().setData(Host, host)
|
terminal.getTerminalModel().setData(Host, host)
|
||||||
terminal.getTerminalModel().addDataListener(object : DataListener {
|
terminal.getTerminalModel().addDataListener(object : DataListener {
|
||||||
override fun onChanged(key: DataKey<*>, data: Any) {
|
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() {
|
override fun onGrabFocus() {
|
||||||
super.onGrabFocus()
|
super.onGrabFocus()
|
||||||
if (!unread) return
|
|
||||||
unread = false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
|||||||
@@ -2,19 +2,20 @@ package app.termora
|
|||||||
|
|
||||||
import app.termora.actions.AnActionEvent
|
import app.termora.actions.AnActionEvent
|
||||||
import app.termora.actions.DataProviders
|
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.extras.components.FlatTabbedPane
|
||||||
|
import com.formdev.flatlaf.ui.FlatTabbedPaneUI
|
||||||
import org.apache.commons.lang3.StringUtils
|
import org.apache.commons.lang3.StringUtils
|
||||||
import java.awt.*
|
import java.awt.*
|
||||||
import java.awt.event.*
|
import java.awt.event.*
|
||||||
import java.awt.image.BufferedImage
|
import java.awt.image.BufferedImage
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import javax.swing.ImageIcon
|
import javax.swing.*
|
||||||
import javax.swing.JDialog
|
|
||||||
import javax.swing.JLabel
|
|
||||||
import javax.swing.SwingUtilities
|
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
|
|
||||||
class MyTabbedPane : FlatTabbedPane() {
|
class MyTabbedPane : FlatTabbedPane(), Disposable {
|
||||||
|
|
||||||
private val dragMouseAdaptor = DragMouseAdaptor()
|
private val dragMouseAdaptor = DragMouseAdaptor()
|
||||||
private val terminalTabbedManager
|
private val terminalTabbedManager
|
||||||
@@ -23,6 +24,14 @@ class MyTabbedPane : FlatTabbedPane() {
|
|||||||
private val owner
|
private val owner
|
||||||
get() = AnActionEvent(this, StringUtils.EMPTY, EventObject(this))
|
get() = AnActionEvent(this, StringUtils.EMPTY, EventObject(this))
|
||||||
.getData(DataProviders.TermoraFrame) as TermoraFrame
|
.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 {
|
init {
|
||||||
isFocusable = false
|
isFocusable = false
|
||||||
@@ -38,6 +47,16 @@ class MyTabbedPane : FlatTabbedPane() {
|
|||||||
private fun initEvents() {
|
private fun initEvents() {
|
||||||
addMouseListener(dragMouseAdaptor)
|
addMouseListener(dragMouseAdaptor)
|
||||||
addMouseMotionListener(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) {
|
override fun processMouseEvent(e: MouseEvent) {
|
||||||
@@ -70,6 +89,29 @@ class MyTabbedPane : FlatTabbedPane() {
|
|||||||
firePropertyChange("selectedIndex", oldIndex, index)
|
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 inner class DragMouseAdaptor : MouseAdapter(), KeyEventDispatcher {
|
||||||
private var mousePressedPoint = Point()
|
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 c = tab.getJComponent()
|
||||||
val title = (c.getClientProperty(titleProperty) ?: tab.getTitle()).toString()
|
val title = (c.getClientProperty(titleProperty) ?: tab.getTitle()).toString()
|
||||||
|
|
||||||
tabbedPane.insertTab(
|
tabbedPane.insertTab(title, tab.getIcon(), c, StringUtils.EMPTY, index)
|
||||||
title,
|
|
||||||
tab.getIcon(),
|
|
||||||
c,
|
|
||||||
StringUtils.EMPTY,
|
|
||||||
index
|
|
||||||
)
|
|
||||||
|
|
||||||
// 设置标题
|
// 设置标题
|
||||||
c.putClientProperty(titleProperty, title)
|
c.putClientProperty(titleProperty, title)
|
||||||
|
|||||||
@@ -164,6 +164,8 @@ class TermoraFrame : JFrame(), DataProvider {
|
|||||||
|
|
||||||
}).let { Disposer.register(windowScope, it) }
|
}).let { Disposer.register(windowScope, it) }
|
||||||
|
|
||||||
|
Disposer.register(windowScope, tabbedPane)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun initView() {
|
private fun initView() {
|
||||||
|
|||||||
@@ -224,7 +224,7 @@ class WelcomePanel() : JPanel(BorderLayout()), Disposable, TerminalTab, DataProv
|
|||||||
|
|
||||||
|
|
||||||
override fun getTitle(): String {
|
override fun getTitle(): String {
|
||||||
return I18n.getString("termora.title")
|
return StringUtils.EMPTY
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getIcon(): Icon {
|
override fun getIcon(): Icon {
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ class LocalTerminalTab(windowScope: WindowScope, host: Host) :
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun getIcon(): Icon {
|
override fun getIcon(): Icon {
|
||||||
return if (unread) Icons.terminalUnread else Icons.terminal
|
return Icons.terminal
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun willBeClose(): Boolean {
|
override fun willBeClose(): Boolean {
|
||||||
|
|||||||
@@ -226,7 +226,7 @@ class SSHTerminalTab(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun getIcon(): Icon {
|
override fun getIcon(): Icon {
|
||||||
return if (unread) Icons.terminalUnread else Icons.terminal
|
return Icons.terminal
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun beforeClose() {
|
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. -->
|
<!-- 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">
|
<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>
|
</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. -->
|
<!-- 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">
|
<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>
|
</svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 716 B After Width: | Height: | Size: 701 B |
Reference in New Issue
Block a user