feat: support for restoring virtual windows

This commit is contained in:
hstyi
2025-04-07 11:45:54 +08:00
committed by hstyi
parent 4c8944d248
commit 54e0f621ce
10 changed files with 108 additions and 20 deletions

View File

@@ -52,6 +52,7 @@ class SSHTerminalTab(windowScope: WindowScope, host: Host) :
init { init {
terminalPanel.dropFiles = false terminalPanel.dropFiles = false
terminalPanel.dataProviderSupport.addData(DataProviders.TerminalTab, this)
} }
override fun getJComponent(): JComponent { override fun getJComponent(): JComponent {
@@ -222,6 +223,11 @@ class SSHTerminalTab(windowScope: WindowScope, host: Host) :
} }
} }
override fun willBeClose(): Boolean {
// 保存窗口状态
terminalPanel.storeVisualWindows(host.id)
return super.willBeClose()
}
private inner class MySessionListener : SessionListener, Disposable { private inner class MySessionListener : SessionListener, Disposable {
override fun sessionEvent(session: Session, event: Event) { override fun sessionEvent(session: Session, event: Event) {

View File

@@ -14,11 +14,16 @@ import com.formdev.flatlaf.extras.components.FlatToolBar
import com.formdev.flatlaf.ui.FlatRoundBorder import com.formdev.flatlaf.ui.FlatRoundBorder
import org.apache.commons.lang3.StringUtils import org.apache.commons.lang3.StringUtils
import java.awt.event.ActionListener import java.awt.event.ActionListener
import java.beans.PropertyChangeEvent
import java.beans.PropertyChangeListener
import java.util.*
import javax.swing.JButton import javax.swing.JButton
import javax.swing.SwingUtilities
class FloatingToolbarPanel : FlatToolBar(), Disposable { class FloatingToolbarPanel : FlatToolBar(), Disposable {
private val floatingToolbarEnable get() = Database.getDatabase().terminal.floatingToolbar private val floatingToolbarEnable get() = Database.getDatabase().terminal.floatingToolbar
private var closed = false private var closed = false
private val anEvent get() = AnActionEvent(this, StringUtils.EMPTY, EventObject(this))
companion object { companion object {
@@ -72,6 +77,7 @@ class FloatingToolbarPanel : FlatToolBar(), Disposable {
} }
initActions() initActions()
initEvents()
} }
override fun updateUI() { override fun updateUI() {
@@ -123,12 +129,38 @@ class FloatingToolbarPanel : FlatToolBar(), Disposable {
add(initCloseActionButton()) add(initCloseActionButton())
} }
private fun initEvents() {
// 被添加到组件后
addPropertyChangeListener("ancestor", object : PropertyChangeListener {
override fun propertyChange(evt: PropertyChangeEvent) {
removePropertyChangeListener("ancestor", this)
SwingUtilities.invokeLater { resumeVisualWindows() }
}
})
}
@Suppress("UNCHECKED_CAST")
private fun resumeVisualWindows() {
val tab = anEvent.getData(DataProviders.TerminalTab) ?: return
if (tab !is SSHTerminalTab) return
val terminalPanel = tab.getData(DataProviders.TerminalPanel) ?: return
terminalPanel.resumeVisualWindows(tab.host.id, object : DataProvider {
override fun <T : Any> getData(dataKey: DataKey<T>): T? {
if (dataKey == DataProviders.TerminalTab) {
return tab as T
}
return super.getData(dataKey)
}
})
}
private fun initServerInfoActionButton(): JButton { private fun initServerInfoActionButton(): JButton {
val btn = JButton(Icons.infoOutline) val btn = JButton(Icons.infoOutline)
btn.toolTipText = I18n.getString("termora.visual-window.system-information") btn.toolTipText = I18n.getString("termora.visual-window.system-information")
btn.addActionListener(object : AnAction() { btn.addActionListener(object : AnAction() {
override fun actionPerformed(evt: AnActionEvent) { override fun actionPerformed(evt: AnActionEvent) {
val tab = evt.getData(DataProviders.TerminalTab) ?: return val tab = anEvent.getData(DataProviders.TerminalTab) ?: return
val terminalPanel = (tab as DataProvider?)?.getData(DataProviders.TerminalPanel) ?: return val terminalPanel = (tab as DataProvider?)?.getData(DataProviders.TerminalPanel) ?: return
if (tab !is SSHTerminalTab) { if (tab !is SSHTerminalTab) {
@@ -156,7 +188,7 @@ class FloatingToolbarPanel : FlatToolBar(), Disposable {
btn.toolTipText = I18n.getString("termora.snippet.title") btn.toolTipText = I18n.getString("termora.snippet.title")
btn.addActionListener(object : AnAction() { btn.addActionListener(object : AnAction() {
override fun actionPerformed(evt: AnActionEvent) { override fun actionPerformed(evt: AnActionEvent) {
val tab = evt.getData(DataProviders.TerminalTab) ?: return val tab = anEvent.getData(DataProviders.TerminalTab) ?: return
val writer = tab.getData(DataProviders.TerminalWriter) ?: return val writer = tab.getData(DataProviders.TerminalWriter) ?: return
val dialog = SnippetTreeDialog(evt.window) val dialog = SnippetTreeDialog(evt.window)
dialog.setLocationRelativeTo(btn) dialog.setLocationRelativeTo(btn)
@@ -174,7 +206,7 @@ class FloatingToolbarPanel : FlatToolBar(), Disposable {
btn.toolTipText = I18n.getString("termora.visual-window.nvidia-smi") btn.toolTipText = I18n.getString("termora.visual-window.nvidia-smi")
btn.addActionListener(object : AnAction() { btn.addActionListener(object : AnAction() {
override fun actionPerformed(evt: AnActionEvent) { override fun actionPerformed(evt: AnActionEvent) {
val tab = evt.getData(DataProviders.TerminalTab) ?: return val tab = anEvent.getData(DataProviders.TerminalTab) ?: return
val terminalPanel = (tab as DataProvider?)?.getData(DataProviders.TerminalPanel) ?: return val terminalPanel = (tab as DataProvider?)?.getData(DataProviders.TerminalPanel) ?: return
if (tab !is SSHTerminalTab) { if (tab !is SSHTerminalTab) {
@@ -233,7 +265,7 @@ class FloatingToolbarPanel : FlatToolBar(), Disposable {
btn.addActionListener(object : AnAction() { btn.addActionListener(object : AnAction() {
override fun actionPerformed(evt: AnActionEvent) { override fun actionPerformed(evt: AnActionEvent) {
val tab = evt.getData(DataProviders.TerminalTab) ?: return val tab = anEvent.getData(DataProviders.TerminalTab) ?: return
if (tab.canReconnect()) { if (tab.canReconnect()) {
tab.reconnect() tab.reconnect()
} }
@@ -242,8 +274,4 @@ class FloatingToolbarPanel : FlatToolBar(), Disposable {
return btn return btn
} }
override fun dispose() {
}
} }

View File

@@ -1,13 +1,14 @@
package app.termora.terminal.panel package app.termora.terminal.panel
import app.termora.Database
import app.termora.Disposable import app.termora.Disposable
import app.termora.Disposer import app.termora.Disposer
import app.termora.SSHTerminalTab
import app.termora.actions.DataProvider import app.termora.actions.DataProvider
import app.termora.actions.DataProviderSupport import app.termora.actions.DataProviderSupport
import app.termora.actions.DataProviders import app.termora.actions.DataProviders
import app.termora.terminal.* import app.termora.terminal.*
import app.termora.terminal.panel.vw.VisualWindow import app.termora.terminal.panel.vw.*
import app.termora.terminal.panel.vw.VisualWindowManager
import com.formdev.flatlaf.util.SystemInfo import com.formdev.flatlaf.util.SystemInfo
import org.apache.commons.lang3.ArrayUtils import org.apache.commons.lang3.ArrayUtils
import org.apache.commons.lang3.StringUtils import org.apache.commons.lang3.StringUtils
@@ -44,15 +45,15 @@ class TerminalPanel(val terminal: Terminal, private val writer: TerminalWriter)
val SelectCopy = DataKey(Boolean::class) val SelectCopy = DataKey(Boolean::class)
} }
private val properties get() = Database.getDatabase().properties
private val terminalBlink = TerminalBlink(terminal) private val terminalBlink = TerminalBlink(terminal)
private val terminalFindPanel = TerminalFindPanel(this, terminal) private val terminalFindPanel = TerminalFindPanel(this, terminal)
private val floatingToolbar = FloatingToolbarPanel() private val floatingToolbar = FloatingToolbarPanel()
private val terminalDisplay = TerminalDisplay(this, terminal, terminalBlink) private val terminalDisplay = TerminalDisplay(this, terminal, terminalBlink)
private val dataProviderSupport = DataProviderSupport()
private val layeredPane = TerminalLayeredPane() private val layeredPane = TerminalLayeredPane()
private var visualWindows = emptyArray<VisualWindow>() private var visualWindows = emptyArray<VisualWindow>()
val scrollBar = TerminalScrollBar(this@TerminalPanel, terminalFindPanel, terminal) val scrollBar = TerminalScrollBar(this, terminalFindPanel, terminal)
var enableFloatingToolbar = true var enableFloatingToolbar = true
set(value) { set(value) {
field = value field = value
@@ -63,6 +64,8 @@ class TerminalPanel(val terminal: Terminal, private val writer: TerminalWriter)
} }
} }
val dataProviderSupport = DataProviderSupport()
/** /**
* 键盘事件 * 键盘事件
@@ -585,6 +588,37 @@ class TerminalPanel(val terminal: Terminal, private val writer: TerminalWriter)
requestFocusInWindow() requestFocusInWindow()
} }
override fun resumeVisualWindows(id: String, dataProvider: DataProvider) {
val windows = properties.getString("VisualWindow.${id}.store") ?: return
for (name in windows.split(",")) {
if (name == "NVIDIA-SMI") {
addVisualWindow(
NvidiaSMIVisualWindow(
dataProvider.getData(DataProviders.TerminalTab) as SSHTerminalTab,
this
)
)
} else if (name == "SystemInformation") {
addVisualWindow(
SystemInformationVisualWindow(
dataProvider.getData(DataProviders.TerminalTab) as SSHTerminalTab,
this
)
)
}
}
}
override fun storeVisualWindows(id: String) {
val windows = mutableListOf<String>()
for (window in getVisualWindows()) {
if (window is Resumeable) {
windows.add(window.getWindowName())
}
}
properties.putString("VisualWindow.${id}.store", windows.joinToString(","))
}
override fun getDimension(): Dimension { override fun getDimension(): Dimension {
return Dimension( return Dimension(
terminalDisplay.size.width + padding.left + padding.right, terminalDisplay.size.width + padding.left + padding.right,

View File

@@ -47,6 +47,7 @@ class NvidiaSMIVisualWindow(tab: SSHTerminalTab, visualWindowManager: VisualWind
private val percentageBtn by lazy { JButton(if (isPercentage) Icons.text else Icons.percentage) } private val percentageBtn by lazy { JButton(if (isPercentage) Icons.text else Icons.percentage) }
init { init {
Disposer.register(tab, this)
initViews() initViews()
initEvents() initEvents()
initVisualWindowPanel() initVisualWindowPanel()

View File

@@ -0,0 +1,3 @@
package app.termora.terminal.panel.vw
interface Resumeable

View File

@@ -1,6 +1,5 @@
package app.termora.terminal.panel.vw package app.termora.terminal.panel.vw
import app.termora.Disposer
import app.termora.SSHTerminalTab import app.termora.SSHTerminalTab
import app.termora.actions.AnActionEvent import app.termora.actions.AnActionEvent
import app.termora.actions.DataProviders import app.termora.actions.DataProviders
@@ -11,11 +10,7 @@ abstract class SSHVisualWindow(
protected val tab: SSHTerminalTab, protected val tab: SSHTerminalTab,
id: String, id: String,
visualWindowManager: VisualWindowManager visualWindowManager: VisualWindowManager
) : VisualWindowPanel(id, visualWindowManager) { ) : VisualWindowPanel(id, visualWindowManager), Resumeable {
init {
Disposer.register(tab, this)
}
override fun toggleWindow() { override fun toggleWindow() {
val evt = AnActionEvent(tab.getJComponent(), StringUtils.EMPTY, EventObject(this)) val evt = AnActionEvent(tab.getJComponent(), StringUtils.EMPTY, EventObject(this))

View File

@@ -25,6 +25,7 @@ class SystemInformationVisualWindow(tab: SSHTerminalTab, visualWindowManager: Vi
private val systemInformationPanel by lazy { SystemInformationPanel() } private val systemInformationPanel by lazy { SystemInformationPanel() }
init { init {
Disposer.register(tab, this)
initViews() initViews()
initEvents() initEvents()
initVisualWindowPanel() initVisualWindowPanel()
@@ -137,7 +138,7 @@ class SystemInformationVisualWindow(tab: SSHTerminalTab, visualWindowManager: Vi
private suspend fun refreshCPUAndMem(session: ClientSession) { private suspend fun refreshCPUAndMem(session: ClientSession) {
// top // top
var pair = SshClients.execChannel(session, "top -bn1") val pair = SshClients.execChannel(session, "top -bn1")
if (pair.first != 0) { if (pair.first != 0) {
return return
} }
@@ -236,7 +237,7 @@ class SystemInformationVisualWindow(tab: SSHTerminalTab, visualWindowManager: Vi
private suspend fun refreshDisk(session: ClientSession) { private suspend fun refreshDisk(session: ClientSession) {
// df -h // df -h
var pair = SshClients.execChannel(session, "df -B1") val pair = SshClients.execChannel(session, "df -B1")
if (pair.first != 0) { if (pair.first != 0) {
return return
} }

View File

@@ -28,4 +28,9 @@ interface VisualWindow : Disposable {
* 切换独立模式 * 切换独立模式
*/ */
fun toggleWindow() fun toggleWindow()
/**
* 同一个类,返回的相同
*/
fun getWindowName(): String
} }

View File

@@ -1,5 +1,6 @@
package app.termora.terminal.panel.vw package app.termora.terminal.panel.vw
import app.termora.actions.DataProvider
import java.awt.Dimension import java.awt.Dimension
interface VisualWindowManager { interface VisualWindowManager {
@@ -33,4 +34,14 @@ interface VisualWindowManager {
* 获取管理器的宽高 * 获取管理器的宽高
*/ */
fun getDimension(): Dimension fun getDimension(): Dimension
/**
* 恢复所有窗口
*/
fun resumeVisualWindows(id: String, dataProvider: DataProvider)
/**
* 存储所有窗口
*/
fun storeVisualWindows(id: String)
} }

View File

@@ -374,4 +374,8 @@ open class VisualWindowPanel(protected val id: String, protected val visualWindo
return null return null
} }
} }
override fun getWindowName(): String {
return id
}
} }