mirror of
https://github.com/TermoraDev/termora.git
synced 2026-01-16 02:12:58 +08:00
feat: support custom layout
This commit is contained in:
@@ -23,7 +23,7 @@ class BannerPanel(fontSize: Int = 11, val beautiful: Boolean = false) : JCompone
|
|||||||
size = preferredSize
|
size = preferredSize
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun paintComponent(g: Graphics) {
|
public override fun paintComponent(g: Graphics) {
|
||||||
if (g is Graphics2D) {
|
if (g is Graphics2D) {
|
||||||
g.setRenderingHints(
|
g.setRenderingHints(
|
||||||
RenderingHints(
|
RenderingHints(
|
||||||
@@ -33,7 +33,7 @@ class BannerPanel(fontSize: Int = 11, val beautiful: Boolean = false) : JCompone
|
|||||||
}
|
}
|
||||||
|
|
||||||
g.font = font
|
g.font = font
|
||||||
g.color = UIManager.getColor("TextField.placeholderForeground")
|
g.color = foreground ?: UIManager.getColor("TextField.placeholderForeground")
|
||||||
|
|
||||||
val height = g.fontMetrics.height
|
val height = g.fontMetrics.height
|
||||||
val descent = g.fontMetrics.descent
|
val descent = g.fontMetrics.descent
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package app.termora
|
package app.termora
|
||||||
|
|
||||||
|
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.*
|
||||||
@@ -8,7 +9,9 @@ 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 java.beans.PropertyChangeEvent
|
import java.beans.PropertyChangeEvent
|
||||||
|
import java.util.*
|
||||||
import javax.swing.Icon
|
import javax.swing.Icon
|
||||||
|
|
||||||
abstract class HostTerminalTab(
|
abstract class HostTerminalTab(
|
||||||
@@ -20,6 +23,10 @@ abstract class HostTerminalTab(
|
|||||||
val Host = DataKey(app.termora.Host::class)
|
val Host = DataKey(app.termora.Host::class)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected val terminalTabbedManager
|
||||||
|
get() = AnActionEvent(getJComponent(), StringUtils.EMPTY, EventObject(getJComponent()))
|
||||||
|
.getData(DataProviders.TerminalTabbedManager)
|
||||||
protected val coroutineScope by lazy { CoroutineScope(SupervisorJob() + Dispatchers.Swing) }
|
protected val coroutineScope by lazy { CoroutineScope(SupervisorJob() + Dispatchers.Swing) }
|
||||||
protected val terminalModel get() = terminal.getTerminalModel()
|
protected val terminalModel get() = terminal.getTerminalModel()
|
||||||
protected var unread = false
|
protected var unread = false
|
||||||
@@ -42,9 +49,12 @@ abstract class HostTerminalTab(
|
|||||||
if (hasFocus || unread) {
|
if (hasFocus || unread) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// 如果当前选中的不是这个 Tab,那么设置成未读
|
||||||
|
if (terminalTabbedManager?.getSelectedTerminalTab() != this@HostTerminalTab) {
|
||||||
unread = true
|
unread = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package app.termora
|
|||||||
|
|
||||||
object Icons {
|
object Icons {
|
||||||
val bulletList by lazy { DynamicIcon("icons/bulletList.svg", "icons/bulletList_dark.svg") }
|
val bulletList by lazy { DynamicIcon("icons/bulletList.svg", "icons/bulletList_dark.svg") }
|
||||||
|
val dataColumn by lazy { DynamicIcon("icons/dataColumn.svg", "icons/dataColumn_dark.svg") }
|
||||||
val dbms by lazy { DynamicIcon("icons/dbms.svg", "icons/dbms_dark.svg") }
|
val dbms by lazy { DynamicIcon("icons/dbms.svg", "icons/dbms_dark.svg") }
|
||||||
val newUI by lazy { DynamicIcon("icons/newUI.svg", "icons/newUI.svg") }
|
val newUI by lazy { DynamicIcon("icons/newUI.svg", "icons/newUI.svg") }
|
||||||
val up by lazy { DynamicIcon("icons/up.svg", "icons/up_dark.svg") }
|
val up by lazy { DynamicIcon("icons/up.svg", "icons/up_dark.svg") }
|
||||||
|
|||||||
123
src/main/kotlin/app/termora/JSplitPaneWithZeroSizeDivider.kt
Normal file
123
src/main/kotlin/app/termora/JSplitPaneWithZeroSizeDivider.kt
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
package app.termora
|
||||||
|
|
||||||
|
import com.formdev.flatlaf.ui.FlatSplitPaneUI
|
||||||
|
import java.awt.BorderLayout
|
||||||
|
import java.awt.Cursor
|
||||||
|
import java.awt.Graphics
|
||||||
|
import java.awt.event.MouseAdapter
|
||||||
|
import java.awt.event.MouseEvent
|
||||||
|
import java.awt.event.MouseMotionAdapter
|
||||||
|
import java.util.function.Supplier
|
||||||
|
import javax.swing.*
|
||||||
|
|
||||||
|
class SplitPaneUI : FlatSplitPaneUI() {
|
||||||
|
public override fun startDragging() {
|
||||||
|
super.startDragging()
|
||||||
|
}
|
||||||
|
|
||||||
|
public override fun dragDividerTo(location: Int) {
|
||||||
|
super.dragDividerTo(location)
|
||||||
|
}
|
||||||
|
|
||||||
|
public override fun finishDraggingTo(location: Int) {
|
||||||
|
super.finishDraggingTo(location)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class JSplitPaneWithZeroSizeDivider(
|
||||||
|
private val splitPane: JSplitPane,
|
||||||
|
private val topOffset: Supplier<Int>,
|
||||||
|
) : JPanel(BorderLayout()) {
|
||||||
|
|
||||||
|
private val dividerDragSize = 7
|
||||||
|
private val layeredPane = LayeredPane()
|
||||||
|
private val divider = Divider()
|
||||||
|
|
||||||
|
init {
|
||||||
|
layeredPane.add(splitPane, JLayeredPane.DEFAULT_LAYER as Any)
|
||||||
|
layeredPane.add(divider, JLayeredPane.PALETTE_LAYER as Any)
|
||||||
|
add(layeredPane, BorderLayout.CENTER)
|
||||||
|
}
|
||||||
|
|
||||||
|
private inner class Divider : JComponent() {
|
||||||
|
private var dragging = false
|
||||||
|
private var dragStartX = 0
|
||||||
|
private var initialDividerLocation = 0
|
||||||
|
private val splitPaneUI get() = splitPane.ui as SplitPaneUI
|
||||||
|
|
||||||
|
init {
|
||||||
|
cursor = Cursor.getPredefinedCursor(Cursor.E_RESIZE_CURSOR)
|
||||||
|
addMouseListener(object : MouseAdapter() {
|
||||||
|
override fun mousePressed(e: MouseEvent) {
|
||||||
|
if (SwingUtilities.isLeftMouseButton(e)) {
|
||||||
|
dragging = true
|
||||||
|
dragStartX = e.xOnScreen
|
||||||
|
initialDividerLocation = splitPane.dividerLocation
|
||||||
|
splitPaneUI.startDragging()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun mouseReleased(e: MouseEvent) {
|
||||||
|
if (SwingUtilities.isLeftMouseButton(e)) {
|
||||||
|
if (dragging) {
|
||||||
|
val deltaX = e.xOnScreen - dragStartX
|
||||||
|
val newLocation = initialDividerLocation + deltaX
|
||||||
|
splitPaneUI.finishDraggingTo(newLocation)
|
||||||
|
}
|
||||||
|
dragging = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
addMouseMotionListener(object : MouseMotionAdapter() {
|
||||||
|
override fun mouseDragged(e: MouseEvent) {
|
||||||
|
if (dragging) {
|
||||||
|
val deltaX = e.xOnScreen - dragStartX
|
||||||
|
val newLocation = initialDividerLocation + deltaX
|
||||||
|
splitPaneUI.dragDividerTo(newLocation)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun paint(g: Graphics) {
|
||||||
|
g.color = UIManager.getColor("controlShadow")
|
||||||
|
g.fillRect(width / 2, 0, 1, height)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private inner class LayeredPane : JLayeredPane() {
|
||||||
|
private val w get() = (dividerDragSize - 1) / 2
|
||||||
|
|
||||||
|
override fun doLayout() {
|
||||||
|
synchronized(treeLock) {
|
||||||
|
for (c in components) {
|
||||||
|
if (c == divider) {
|
||||||
|
c.setBounds(
|
||||||
|
splitPane.dividerLocation - w,
|
||||||
|
topOffset.get(),
|
||||||
|
dividerDragSize,
|
||||||
|
height - topOffset.get()
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
c.setBounds(0, 0, width, height)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun paint(g: Graphics) {
|
||||||
|
super.paint(g)
|
||||||
|
g.color = UIManager.getColor("controlShadow")
|
||||||
|
g.fillRect(splitPane.dividerLocation, 0, 1, topOffset.get())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun doLayout() {
|
||||||
|
super.doLayout()
|
||||||
|
layeredPane.doLayout()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -112,6 +112,7 @@ class SettingsOptionsPane : OptionsPane() {
|
|||||||
private inner class AppearanceOption : JPanel(BorderLayout()), Option {
|
private inner class AppearanceOption : JPanel(BorderLayout()), Option {
|
||||||
val themeManager = ThemeManager.getInstance()
|
val themeManager = ThemeManager.getInstance()
|
||||||
val themeComboBox = FlatComboBox<String>()
|
val themeComboBox = FlatComboBox<String>()
|
||||||
|
val layoutComboBox = FlatComboBox<TermoraLayout>()
|
||||||
val languageComboBox = FlatComboBox<String>()
|
val languageComboBox = FlatComboBox<String>()
|
||||||
val backgroundComBoBox = YesOrNoComboBox()
|
val backgroundComBoBox = YesOrNoComboBox()
|
||||||
val confirmTabCloseComBoBox = YesOrNoComboBox()
|
val confirmTabCloseComBoBox = YesOrNoComboBox()
|
||||||
@@ -129,6 +130,38 @@ class SettingsOptionsPane : OptionsPane() {
|
|||||||
|
|
||||||
private fun initView() {
|
private fun initView() {
|
||||||
|
|
||||||
|
layoutComboBox.addItem(TermoraLayout.Screen)
|
||||||
|
layoutComboBox.addItem(TermoraLayout.Fence)
|
||||||
|
layoutComboBox.renderer = object : DefaultListCellRenderer() {
|
||||||
|
override fun getListCellRendererComponent(
|
||||||
|
list: JList<*>?,
|
||||||
|
value: Any?,
|
||||||
|
index: Int,
|
||||||
|
isSelected: Boolean,
|
||||||
|
cellHasFocus: Boolean
|
||||||
|
): Component {
|
||||||
|
var text = value
|
||||||
|
if (value == TermoraLayout.Screen) {
|
||||||
|
text = I18n.getString("termora.settings.appearance.layout.screen")
|
||||||
|
} else if (value == TermoraLayout.Fence) {
|
||||||
|
text = I18n.getString("termora.settings.appearance.layout.fence")
|
||||||
|
}
|
||||||
|
|
||||||
|
val c = super.getListCellRendererComponent(list, text, index, isSelected, cellHasFocus)
|
||||||
|
icon = null
|
||||||
|
|
||||||
|
if (value == TermoraLayout.Screen) {
|
||||||
|
icon = if (isSelected) Icons.uiForm.dark else Icons.uiForm
|
||||||
|
} else if (value == TermoraLayout.Fence) {
|
||||||
|
icon = if (isSelected) Icons.dataColumn.dark else Icons.dataColumn
|
||||||
|
}
|
||||||
|
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
}
|
||||||
|
layoutComboBox.selectedItem = runCatching { TermoraLayout.valueOf(appearance.layout) }
|
||||||
|
.getOrNull() ?: TermoraLayout.Layout
|
||||||
|
|
||||||
backgroundComBoBox.isEnabled = SystemInfo.isWindows || SystemInfo.isMacOS
|
backgroundComBoBox.isEnabled = SystemInfo.isWindows || SystemInfo.isMacOS
|
||||||
|
|
||||||
opacitySpinner.isEnabled = SystemInfo.isMacOS || SystemInfo.isWindows
|
opacitySpinner.isEnabled = SystemInfo.isMacOS || SystemInfo.isWindows
|
||||||
@@ -184,6 +217,17 @@ class SettingsOptionsPane : OptionsPane() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
layoutComboBox.addItemListener(object : ItemListener {
|
||||||
|
override fun itemStateChanged(e: ItemEvent) {
|
||||||
|
if (e.stateChange == ItemEvent.SELECTED) {
|
||||||
|
appearance.layout = layoutComboBox.selectedItem?.toString() ?: return
|
||||||
|
if (TermoraLayout.Layout.name != appearance.layout) {
|
||||||
|
SwingUtilities.invokeLater { TermoraRestarter.getInstance().scheduleRestart(owner) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
opacitySpinner.addChangeListener {
|
opacitySpinner.addChangeListener {
|
||||||
val opacity = opacitySpinner.value
|
val opacity = opacitySpinner.value
|
||||||
if (opacity is Double) {
|
if (opacity is Double) {
|
||||||
@@ -307,7 +351,7 @@ class SettingsOptionsPane : OptionsPane() {
|
|||||||
private fun getFormPanel(): JPanel {
|
private fun getFormPanel(): JPanel {
|
||||||
val layout = FormLayout(
|
val layout = FormLayout(
|
||||||
"left:pref, $FORM_MARGIN, default:grow, $FORM_MARGIN, default, default:grow",
|
"left:pref, $FORM_MARGIN, default:grow, $FORM_MARGIN, default, default:grow",
|
||||||
"pref, $FORM_MARGIN, pref, $FORM_MARGIN, pref, $FORM_MARGIN, pref, $FORM_MARGIN, pref"
|
"pref, $FORM_MARGIN, pref, $FORM_MARGIN, pref, $FORM_MARGIN, pref, $FORM_MARGIN, pref, $FORM_MARGIN, pref"
|
||||||
)
|
)
|
||||||
val box = FlatToolBar()
|
val box = FlatToolBar()
|
||||||
box.add(followSystemCheckBox)
|
box.add(followSystemCheckBox)
|
||||||
@@ -329,6 +373,9 @@ class SettingsOptionsPane : OptionsPane() {
|
|||||||
})).xy(5, rows).apply { rows += step }
|
})).xy(5, rows).apply { rows += step }
|
||||||
|
|
||||||
|
|
||||||
|
builder.add("${I18n.getString("termora.settings.appearance.layout")}:").xy(1, rows)
|
||||||
|
.add(layoutComboBox).xy(3, rows).apply { rows += step }
|
||||||
|
|
||||||
builder.add("${I18n.getString("termora.settings.appearance.opacity")}:").xy(1, rows)
|
builder.add("${I18n.getString("termora.settings.appearance.opacity")}:").xy(1, rows)
|
||||||
.add(opacitySpinner).xy(3, rows).apply { rows += step }
|
.add(opacitySpinner).xy(3, rows).apply { rows += step }
|
||||||
|
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ class TerminalTabbed(
|
|||||||
private val windowScope: WindowScope,
|
private val windowScope: WindowScope,
|
||||||
private val termoraToolBar: TermoraToolBar,
|
private val termoraToolBar: TermoraToolBar,
|
||||||
private val tabbedPane: FlatTabbedPane,
|
private val tabbedPane: FlatTabbedPane,
|
||||||
|
private val layout: TermoraLayout,
|
||||||
) : JPanel(BorderLayout()), Disposable, TerminalTabbedManager, DataProvider {
|
) : JPanel(BorderLayout()), Disposable, TerminalTabbedManager, DataProvider {
|
||||||
private val tabs = mutableListOf<TerminalTab>()
|
private val tabs = mutableListOf<TerminalTab>()
|
||||||
private val customizeToolBarAWTEventListener = CustomizeToolBarAWTEventListener()
|
private val customizeToolBarAWTEventListener = CustomizeToolBarAWTEventListener()
|
||||||
@@ -110,20 +111,6 @@ class TerminalTabbed(
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
// 点击
|
|
||||||
tabbedPane.addMouseListener(object : MouseAdapter() {
|
|
||||||
override fun mouseClicked(e: MouseEvent) {
|
|
||||||
if (SwingUtilities.isLeftMouseButton(e)) {
|
|
||||||
val index = tabbedPane.indexAtLocation(e.x, e.y)
|
|
||||||
if (index > 0) {
|
|
||||||
tabbedPane.getComponentAt(index).requestFocusInWindow()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
// 注册全局搜索
|
// 注册全局搜索
|
||||||
DynamicExtensionHandler.getInstance()
|
DynamicExtensionHandler.getInstance()
|
||||||
.register(FindEverywhereProviderExtension::class.java, object : FindEverywhereProviderExtension {
|
.register(FindEverywhereProviderExtension::class.java, object : FindEverywhereProviderExtension {
|
||||||
@@ -206,11 +193,13 @@ class TerminalTabbed(
|
|||||||
// remove ele
|
// remove ele
|
||||||
tabs.removeAt(index)
|
tabs.removeAt(index)
|
||||||
|
|
||||||
|
if (tabbedPane.tabCount > 0) {
|
||||||
// 新的获取到焦点
|
// 新的获取到焦点
|
||||||
tabs[tabbedPane.selectedIndex].onGrabFocus()
|
tabs[tabbedPane.selectedIndex].onGrabFocus()
|
||||||
|
|
||||||
// 新的真正获取焦点
|
// 新的真正获取焦点
|
||||||
tabbedPane.getComponentAt(tabbedPane.selectedIndex).requestFocusInWindow()
|
tabbedPane.getComponentAt(tabbedPane.selectedIndex).requestFocusInWindow()
|
||||||
|
}
|
||||||
|
|
||||||
if (disposable) {
|
if (disposable) {
|
||||||
Disposer.dispose(tab)
|
Disposer.dispose(tab)
|
||||||
@@ -497,6 +486,32 @@ class TerminalTabbed(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun paint(g: Graphics) {
|
||||||
|
super.paint(g)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val border = BorderFactory.createMatteBorder(1, 0, 0, 0, DynamicColor.BorderColor)
|
||||||
|
private val banner = BannerPanel(fontSize = 13).apply {
|
||||||
|
foreground = UIManager.getColor("textInactiveText")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun paintComponent(g: Graphics) {
|
||||||
|
super.paintComponent(g)
|
||||||
|
|
||||||
|
if (layout == TermoraLayout.Fence) {
|
||||||
|
if (g is Graphics2D) {
|
||||||
|
if (tabbedPane.tabCount < 1) {
|
||||||
|
border.paintBorder(this, g, 0, tabbedPane.tabHeight, width, tabbedPane.tabHeight)
|
||||||
|
banner.setBounds(0, 0, width, height)
|
||||||
|
g.save()
|
||||||
|
g.translate(0, 180)
|
||||||
|
banner.paintComponent(g)
|
||||||
|
g.restore()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
override fun dispose() {
|
override fun dispose() {
|
||||||
}
|
}
|
||||||
|
|||||||
107
src/main/kotlin/app/termora/TermoraFencePanel.kt
Normal file
107
src/main/kotlin/app/termora/TermoraFencePanel.kt
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
package app.termora
|
||||||
|
|
||||||
|
import app.termora.tree.NewHostTree
|
||||||
|
import com.formdev.flatlaf.extras.components.FlatTabbedPane
|
||||||
|
import com.formdev.flatlaf.util.SystemInfo
|
||||||
|
import java.awt.BorderLayout
|
||||||
|
import java.awt.Dimension
|
||||||
|
import java.awt.Font
|
||||||
|
import java.awt.event.MouseAdapter
|
||||||
|
import javax.swing.*
|
||||||
|
|
||||||
|
|
||||||
|
class TermoraFencePanel(
|
||||||
|
private val terminalTabbed: TerminalTabbed,
|
||||||
|
private val tabbed: FlatTabbedPane,
|
||||||
|
private val moveMouseAdapter: MouseAdapter,
|
||||||
|
) : JPanel(BorderLayout()), Disposable {
|
||||||
|
private val splitPane = object : JSplitPane() {
|
||||||
|
override fun updateUI() {
|
||||||
|
setUI(SplitPaneUI())
|
||||||
|
revalidate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private val leftTreePanel = LeftTreePanel()
|
||||||
|
private val mySplitPane = JSplitPaneWithZeroSizeDivider(splitPane) { tabbed.tabHeight }
|
||||||
|
private val enableManager get() = EnableManager.getInstance()
|
||||||
|
|
||||||
|
init {
|
||||||
|
initView()
|
||||||
|
initEvents()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initView() {
|
||||||
|
|
||||||
|
splitPane.border = null
|
||||||
|
|
||||||
|
splitPane.leftComponent = leftTreePanel
|
||||||
|
splitPane.rightComponent = terminalTabbed
|
||||||
|
splitPane.dividerSize = 1
|
||||||
|
splitPane.isEnabled = false
|
||||||
|
splitPane.dividerLocation = enableManager.getFlag("Termora.Fence.dividerLocation", 220)
|
||||||
|
|
||||||
|
leftTreePanel.preferredSize = Dimension(180, -1)
|
||||||
|
|
||||||
|
tabbed.tabType = FlatTabbedPane.TabType.underlined
|
||||||
|
tabbed.tabAreaInsets = null
|
||||||
|
|
||||||
|
add(mySplitPane, BorderLayout.CENTER)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initEvents() {
|
||||||
|
Disposer.register(this, leftTreePanel)
|
||||||
|
splitPane.addPropertyChangeListener("dividerLocation") { mySplitPane.doLayout() }
|
||||||
|
}
|
||||||
|
|
||||||
|
private inner class LeftTreePanel : JPanel(BorderLayout()), Disposable {
|
||||||
|
val hostTree = NewHostTree()
|
||||||
|
private val box = JToolBar().apply { isFloatable = false }
|
||||||
|
|
||||||
|
init {
|
||||||
|
initView()
|
||||||
|
initEvents()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initView() {
|
||||||
|
val scrollPane = JScrollPane(hostTree)
|
||||||
|
hostTree.name = "FenceHostTree"
|
||||||
|
hostTree.restoreExpansions()
|
||||||
|
box.preferredSize = Dimension(-1, tabbed.tabHeight)
|
||||||
|
|
||||||
|
val label = JLabel(Application.getName())
|
||||||
|
label.foreground = UIManager.getColor("textInactiveText")
|
||||||
|
label.font = label.font.deriveFont(Font.BOLD)
|
||||||
|
box.add(Box.createHorizontalGlue())
|
||||||
|
if (SystemInfo.isMacOS.not()) box.add(label)
|
||||||
|
box.add(Box.createHorizontalGlue())
|
||||||
|
|
||||||
|
if (SystemInfo.isMacOS || SystemInfo.isLinux) {
|
||||||
|
box.addMouseListener(moveMouseAdapter)
|
||||||
|
box.addMouseMotionListener(moveMouseAdapter)
|
||||||
|
}
|
||||||
|
|
||||||
|
scrollPane.verticalScrollBar.unitIncrement = 16
|
||||||
|
scrollPane.horizontalScrollBar.unitIncrement = 16
|
||||||
|
scrollPane.border = BorderFactory.createCompoundBorder(
|
||||||
|
BorderFactory.createMatteBorder(1, 0, 0, 0, DynamicColor.BorderColor),
|
||||||
|
BorderFactory.createEmptyBorder(4, 4, 0, 4)
|
||||||
|
)
|
||||||
|
|
||||||
|
add(box, BorderLayout.NORTH)
|
||||||
|
add(scrollPane, BorderLayout.CENTER)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initEvents() {
|
||||||
|
Disposer.register(this, hostTree)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun dispose() {
|
||||||
|
enableManager.setFlag("Termora.Fence.dividerLocation", splitPane.dividerLocation)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getHostTree(): NewHostTree {
|
||||||
|
return leftTreePanel.hostTree
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -4,21 +4,29 @@ package app.termora
|
|||||||
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.actions.OpenHostAction
|
||||||
|
import app.termora.findeverywhere.FindEverywhereProvider
|
||||||
|
import app.termora.findeverywhere.FindEverywhereProviderExtension
|
||||||
|
import app.termora.findeverywhere.FindEverywhereResult
|
||||||
import app.termora.plugin.ExtensionManager
|
import app.termora.plugin.ExtensionManager
|
||||||
|
import app.termora.plugin.internal.extension.DynamicExtensionHandler
|
||||||
|
import app.termora.plugin.internal.ssh.SSHProtocolProvider
|
||||||
import app.termora.terminal.DataKey
|
import app.termora.terminal.DataKey
|
||||||
|
import app.termora.tree.NewHostTreeModel
|
||||||
import com.formdev.flatlaf.FlatClientProperties
|
import com.formdev.flatlaf.FlatClientProperties
|
||||||
|
import com.formdev.flatlaf.FlatLaf
|
||||||
import com.formdev.flatlaf.ui.FlatRootPaneUI
|
import com.formdev.flatlaf.ui.FlatRootPaneUI
|
||||||
import com.formdev.flatlaf.ui.FlatTitlePane
|
import com.formdev.flatlaf.ui.FlatTitlePane
|
||||||
import com.formdev.flatlaf.util.SystemInfo
|
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.jdesktop.swingx.action.ActionManager
|
||||||
import java.awt.*
|
import java.awt.*
|
||||||
import java.awt.event.MouseAdapter
|
import java.awt.event.*
|
||||||
import java.awt.event.MouseEvent
|
|
||||||
import java.awt.event.MouseListener
|
|
||||||
import java.awt.event.MouseMotionListener
|
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import javax.imageio.ImageIO
|
import javax.imageio.ImageIO
|
||||||
|
import javax.swing.Icon
|
||||||
import javax.swing.JComponent
|
import javax.swing.JComponent
|
||||||
import javax.swing.JFrame
|
import javax.swing.JFrame
|
||||||
import javax.swing.SwingUtilities.isEventDispatchThread
|
import javax.swing.SwingUtilities.isEventDispatchThread
|
||||||
@@ -32,15 +40,16 @@ fun assertEventDispatchThread() {
|
|||||||
|
|
||||||
class TermoraFrame : JFrame(), DataProvider {
|
class TermoraFrame : JFrame(), DataProvider {
|
||||||
|
|
||||||
|
private val layout get() = TermoraLayout.Layout
|
||||||
|
private val titleBarHeight = computedTitleBarHeight()
|
||||||
private val id = UUID.randomUUID().toString()
|
private val id = UUID.randomUUID().toString()
|
||||||
private val windowScope = ApplicationScope.forWindowScope(this)
|
private val windowScope = ApplicationScope.forWindowScope(this)
|
||||||
private val tabbedPane = MyTabbedPane()
|
private val tabbedPane = MyTabbedPane().apply { tabHeight = titleBarHeight }
|
||||||
private val toolbar = TermoraToolBar(windowScope, this)
|
private val toolbar = TermoraToolBar(windowScope, this)
|
||||||
private val terminalTabbed = TerminalTabbed(windowScope, toolbar, tabbedPane)
|
private val terminalTabbed = TerminalTabbed(windowScope, toolbar, tabbedPane, layout)
|
||||||
private val dataProviderSupport = DataProviderSupport()
|
private val dataProviderSupport = DataProviderSupport()
|
||||||
private val welcomePanel = WelcomePanel(windowScope)
|
|
||||||
private var notifyListeners = emptyArray<NotifyListener>()
|
private var notifyListeners = emptyArray<NotifyListener>()
|
||||||
|
private val moveMouseAdapter = createMoveMouseAdaptor()
|
||||||
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
@@ -50,7 +59,209 @@ class TermoraFrame : JFrame(), DataProvider {
|
|||||||
|
|
||||||
private fun initEvents() {
|
private fun initEvents() {
|
||||||
if (SystemInfo.isLinux) {
|
if (SystemInfo.isLinux) {
|
||||||
val mouseAdapter = object : MouseAdapter() {
|
toolbar.getJToolBar().addMouseListener(moveMouseAdapter)
|
||||||
|
toolbar.getJToolBar().addMouseMotionListener(moveMouseAdapter)
|
||||||
|
} else if (SystemInfo.isMacOS) {
|
||||||
|
terminalTabbed.addMouseListener(moveMouseAdapter)
|
||||||
|
terminalTabbed.addMouseMotionListener(moveMouseAdapter)
|
||||||
|
|
||||||
|
tabbedPane.addMouseListener(moveMouseAdapter)
|
||||||
|
tabbedPane.addMouseMotionListener(moveMouseAdapter)
|
||||||
|
|
||||||
|
toolbar.getJToolBar().addMouseListener(moveMouseAdapter)
|
||||||
|
toolbar.getJToolBar().addMouseMotionListener(moveMouseAdapter)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindEverywhere
|
||||||
|
DynamicExtensionHandler.getInstance()
|
||||||
|
.register(FindEverywhereProviderExtension::class.java, object : FindEverywhereProviderExtension {
|
||||||
|
private val hostTreeModel get() = NewHostTreeModel.getInstance()
|
||||||
|
|
||||||
|
private val provider = object : FindEverywhereProvider {
|
||||||
|
override fun find(pattern: String, scope: Scope): List<FindEverywhereResult> {
|
||||||
|
if (scope != windowScope) return emptyList()
|
||||||
|
|
||||||
|
var filter = hostTreeModel.root.getAllChildren()
|
||||||
|
.map { it.host }
|
||||||
|
.filter { it.isFolder.not() }
|
||||||
|
|
||||||
|
if (pattern.isNotBlank()) {
|
||||||
|
filter = filter.filter {
|
||||||
|
if (it.protocol == SSHProtocolProvider.PROTOCOL) {
|
||||||
|
it.name.contains(pattern, true) || it.host.contains(pattern, true)
|
||||||
|
} else {
|
||||||
|
it.name.contains(pattern, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return filter.map { HostFindEverywhereResult(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun group(): String {
|
||||||
|
return I18n.getString("termora.find-everywhere.groups.open-new-hosts")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun order(): Int {
|
||||||
|
return Integer.MIN_VALUE + 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getFindEverywhereProvider(): FindEverywhereProvider {
|
||||||
|
return provider
|
||||||
|
}
|
||||||
|
|
||||||
|
private inner class HostFindEverywhereResult(val host: Host) : FindEverywhereResult {
|
||||||
|
private val showMoreInfo get() = EnableManager.getInstance().isShowMoreInfo()
|
||||||
|
|
||||||
|
override fun actionPerformed(e: ActionEvent) {
|
||||||
|
ActionManager.getInstance()
|
||||||
|
.getAction(OpenHostAction.OPEN_HOST)
|
||||||
|
?.actionPerformed(OpenHostActionEvent(e.source, host, e))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getIcon(isSelected: Boolean): Icon {
|
||||||
|
if (isSelected) {
|
||||||
|
if (!FlatLaf.isLafDark()) {
|
||||||
|
return Icons.terminal.dark
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Icons.terminal
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getText(isSelected: Boolean): String {
|
||||||
|
if (showMoreInfo) {
|
||||||
|
val color = UIManager.getColor(if (isSelected) "textHighlightText" else "textInactiveText")
|
||||||
|
val moreInfo = when (host.protocol) {
|
||||||
|
SSHProtocolProvider.PROTOCOL -> "${host.username}@${host.host}"
|
||||||
|
"Serial" -> host.options.serialComm.port
|
||||||
|
else -> StringUtils.EMPTY
|
||||||
|
}
|
||||||
|
if (moreInfo.isNotBlank()) {
|
||||||
|
return "<html>${host.name} <font color=rgb(${color.red},${color.green},${color.blue})>${moreInfo}</font></html>"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return host.name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}).let { Disposer.register(windowScope, it) }
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun initView() {
|
||||||
|
|
||||||
|
// macOS 要避开左边的控制栏
|
||||||
|
if (SystemInfo.isMacOS) {
|
||||||
|
tabbedPane.tabAreaInsets = Insets(0, 76, 0, 0)
|
||||||
|
} else if (SystemInfo.isWindows) {
|
||||||
|
// Windows 10 会有1像素误差
|
||||||
|
tabbedPane.tabAreaInsets = Insets(if (SystemInfo.isWindows_11_orLater) 1 else 2, 2, 0, 0)
|
||||||
|
} else if (SystemInfo.isLinux) {
|
||||||
|
tabbedPane.tabAreaInsets = Insets(1, 2, 0, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
val height = UIManager.getInt("TabbedPane.tabHeight") + tabbedPane.tabAreaInsets.top
|
||||||
|
|
||||||
|
if (SystemInfo.isWindows || SystemInfo.isLinux) {
|
||||||
|
rootPane.putClientProperty(FlatClientProperties.FULL_WINDOW_CONTENT, true)
|
||||||
|
rootPane.putClientProperty(FlatClientProperties.TITLE_BAR_SHOW_ICON, false)
|
||||||
|
rootPane.putClientProperty(FlatClientProperties.TITLE_BAR_SHOW_TITLE, false)
|
||||||
|
rootPane.putClientProperty(FlatClientProperties.TITLE_BAR_HEIGHT, height)
|
||||||
|
} else if (SystemInfo.isMacOS) {
|
||||||
|
rootPane.putClientProperty("apple.awt.windowTitleVisible", false)
|
||||||
|
rootPane.putClientProperty("apple.awt.fullWindowContent", true)
|
||||||
|
rootPane.putClientProperty("apple.awt.transparentTitleBar", true)
|
||||||
|
rootPane.putClientProperty(
|
||||||
|
FlatClientProperties.MACOS_WINDOW_BUTTONS_SPACING,
|
||||||
|
FlatClientProperties.MACOS_WINDOW_BUTTONS_SPACING_MEDIUM
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (SystemInfo.isWindows || SystemInfo.isLinux) {
|
||||||
|
val sizes = listOf(16, 20, 24, 28, 32, 48, 64, 128)
|
||||||
|
val loader = TermoraFrame::class.java.classLoader
|
||||||
|
val images = sizes.mapNotNull { e ->
|
||||||
|
loader.getResourceAsStream("icons/termora_${e}x${e}.png")?.use { ImageIO.read(it) }
|
||||||
|
}
|
||||||
|
iconImages = images
|
||||||
|
}
|
||||||
|
|
||||||
|
minimumSize = Dimension(640, 400)
|
||||||
|
|
||||||
|
val glassPane = GlassPane()
|
||||||
|
rootPane.glassPane = glassPane
|
||||||
|
glassPane.isOpaque = false
|
||||||
|
glassPane.isVisible = true
|
||||||
|
|
||||||
|
for (extension in ExtensionManager.getInstance().getExtensions(GlassPaneAwareExtension::class.java)) {
|
||||||
|
extension.setGlassPane(this, glassPane)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (layout == TermoraLayout.Fence) {
|
||||||
|
val fencePanel = TermoraFencePanel(terminalTabbed, tabbedPane, moveMouseAdapter)
|
||||||
|
add(fencePanel, BorderLayout.CENTER)
|
||||||
|
dataProviderSupport.addData(DataProviders.Welcome.HostTree, fencePanel.getHostTree())
|
||||||
|
Disposer.register(windowScope, fencePanel)
|
||||||
|
} else {
|
||||||
|
val screenPanel = TermoraScreenPanel(windowScope, terminalTabbed)
|
||||||
|
add(screenPanel, BorderLayout.CENTER)
|
||||||
|
dataProviderSupport.addData(DataProviders.Welcome.HostTree, screenPanel.getHostTree())
|
||||||
|
}
|
||||||
|
|
||||||
|
Disposer.register(windowScope, terminalTabbed)
|
||||||
|
|
||||||
|
dataProviderSupport.addData(DataProviders.TabbedPane, tabbedPane)
|
||||||
|
dataProviderSupport.addData(DataProviders.TermoraFrame, this)
|
||||||
|
dataProviderSupport.addData(DataProviders.WindowScope, windowScope)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun <T : Any> getData(dataKey: DataKey<T>): T? {
|
||||||
|
return dataProviderSupport.getData(dataKey) ?: terminalTabbed.getData(dataKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (this === other) return true
|
||||||
|
if (javaClass != other?.javaClass) return false
|
||||||
|
|
||||||
|
other as TermoraFrame
|
||||||
|
|
||||||
|
return id == other.id
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
return id.hashCode()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addNotifyListener(listener: NotifyListener) {
|
||||||
|
notifyListeners += listener
|
||||||
|
}
|
||||||
|
|
||||||
|
fun removeNotifyListener(listener: NotifyListener) {
|
||||||
|
notifyListeners = ArrayUtils.removeElements(notifyListeners, listener)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun addNotify() {
|
||||||
|
super.addNotify()
|
||||||
|
notifyListeners.forEach { it.addNotify() }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun computedTitleBarHeight(): Int {
|
||||||
|
val tabHeight = UIManager.getInt("TabbedPane.tabHeight")
|
||||||
|
if (SystemInfo.isWindows) {
|
||||||
|
// Windows 10 会有1像素误差
|
||||||
|
return tabHeight + if (SystemInfo.isWindows_11_orLater) 1 else 2
|
||||||
|
} else if (SystemInfo.isLinux) {
|
||||||
|
return tabHeight + 1
|
||||||
|
}
|
||||||
|
return tabHeight
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createMoveMouseAdaptor(): MouseAdapter {
|
||||||
|
if (SystemInfo.isLinux) {
|
||||||
|
return object : MouseAdapter() {
|
||||||
override fun mouseClicked(e: MouseEvent) {
|
override fun mouseClicked(e: MouseEvent) {
|
||||||
getMouseHandler()?.mouseClicked(e)
|
getMouseHandler()?.mouseClicked(e)
|
||||||
}
|
}
|
||||||
@@ -97,8 +308,6 @@ class TermoraFrame : JFrame(), DataProvider {
|
|||||||
return titlePaneField.get(ui) as? FlatTitlePane
|
return titlePaneField.get(ui) as? FlatTitlePane
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
toolbar.getJToolBar().addMouseListener(mouseAdapter)
|
|
||||||
toolbar.getJToolBar().addMouseMotionListener(mouseAdapter)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// force hit
|
/// force hit
|
||||||
@@ -145,111 +354,13 @@ class TermoraFrame : JFrame(), DataProvider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
terminalTabbed.addMouseListener(mouseAdapter)
|
|
||||||
terminalTabbed.addMouseMotionListener(mouseAdapter)
|
|
||||||
|
|
||||||
tabbedPane.addMouseListener(mouseAdapter)
|
|
||||||
tabbedPane.addMouseMotionListener(mouseAdapter)
|
|
||||||
|
|
||||||
toolbar.getJToolBar().addMouseListener(mouseAdapter)
|
|
||||||
toolbar.getJToolBar().addMouseMotionListener(mouseAdapter)
|
|
||||||
|
|
||||||
JBR.getWindowDecorations().setCustomTitleBar(this, customTitleBar)
|
JBR.getWindowDecorations().setCustomTitleBar(this, customTitleBar)
|
||||||
}
|
|
||||||
|
return mouseAdapter
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return object : MouseAdapter() {}
|
||||||
private fun initView() {
|
|
||||||
|
|
||||||
// macOS 要避开左边的控制栏
|
|
||||||
if (SystemInfo.isMacOS) {
|
|
||||||
tabbedPane.tabAreaInsets = Insets(0, 76, 0, 0)
|
|
||||||
} else if (SystemInfo.isWindows) {
|
|
||||||
// Windows 10 会有1像素误差
|
|
||||||
tabbedPane.tabAreaInsets = Insets(if (SystemInfo.isWindows_11_orLater) 1 else 2, 2, 0, 0)
|
|
||||||
} else if (SystemInfo.isLinux) {
|
|
||||||
tabbedPane.tabAreaInsets = Insets(1, 2, 0, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
val height = UIManager.getInt("TabbedPane.tabHeight") + tabbedPane.tabAreaInsets.top
|
|
||||||
|
|
||||||
if (SystemInfo.isWindows || SystemInfo.isLinux) {
|
|
||||||
rootPane.putClientProperty(FlatClientProperties.FULL_WINDOW_CONTENT, true)
|
|
||||||
rootPane.putClientProperty(FlatClientProperties.TITLE_BAR_SHOW_ICON, false)
|
|
||||||
rootPane.putClientProperty(FlatClientProperties.TITLE_BAR_SHOW_TITLE, false)
|
|
||||||
rootPane.putClientProperty(FlatClientProperties.TITLE_BAR_HEIGHT, height)
|
|
||||||
} else if (SystemInfo.isMacOS) {
|
|
||||||
rootPane.putClientProperty("apple.awt.windowTitleVisible", false)
|
|
||||||
rootPane.putClientProperty("apple.awt.fullWindowContent", true)
|
|
||||||
rootPane.putClientProperty("apple.awt.transparentTitleBar", true)
|
|
||||||
rootPane.putClientProperty(
|
|
||||||
FlatClientProperties.MACOS_WINDOW_BUTTONS_SPACING,
|
|
||||||
FlatClientProperties.MACOS_WINDOW_BUTTONS_SPACING_MEDIUM
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (SystemInfo.isWindows || SystemInfo.isLinux) {
|
|
||||||
val sizes = listOf(16, 20, 24, 28, 32, 48, 64, 128)
|
|
||||||
val loader = TermoraFrame::class.java.classLoader
|
|
||||||
val images = sizes.mapNotNull { e ->
|
|
||||||
loader.getResourceAsStream("icons/termora_${e}x${e}.png")?.use { ImageIO.read(it) }
|
|
||||||
}
|
|
||||||
iconImages = images
|
|
||||||
}
|
|
||||||
|
|
||||||
minimumSize = Dimension(640, 400)
|
|
||||||
terminalTabbed.addTerminalTab(welcomePanel)
|
|
||||||
|
|
||||||
val glassPane = GlassPane()
|
|
||||||
rootPane.glassPane = glassPane
|
|
||||||
glassPane.isOpaque = false
|
|
||||||
glassPane.isVisible = true
|
|
||||||
|
|
||||||
for (extension in ExtensionManager.getInstance().getExtensions(GlassPaneAwareExtension::class.java)) {
|
|
||||||
extension.setGlassPane(this, glassPane)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
Disposer.register(windowScope, terminalTabbed)
|
|
||||||
add(terminalTabbed, BorderLayout.CENTER)
|
|
||||||
|
|
||||||
dataProviderSupport.addData(DataProviders.TabbedPane, tabbedPane)
|
|
||||||
dataProviderSupport.addData(DataProviders.TermoraFrame, this)
|
|
||||||
dataProviderSupport.addData(DataProviders.WindowScope, windowScope)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun <T : Any> getData(dataKey: DataKey<T>): T? {
|
|
||||||
return dataProviderSupport.getData(dataKey)
|
|
||||||
?: terminalTabbed.getData(dataKey)
|
|
||||||
?: welcomePanel.getData(dataKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
|
||||||
if (this === other) return true
|
|
||||||
if (javaClass != other?.javaClass) return false
|
|
||||||
|
|
||||||
other as TermoraFrame
|
|
||||||
|
|
||||||
return id == other.id
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
|
||||||
return id.hashCode()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun addNotifyListener(listener: NotifyListener) {
|
|
||||||
notifyListeners += listener
|
|
||||||
}
|
|
||||||
|
|
||||||
fun removeNotifyListener(listener: NotifyListener) {
|
|
||||||
notifyListeners = ArrayUtils.removeElements(notifyListeners, listener)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun addNotify() {
|
|
||||||
super.addNotify()
|
|
||||||
notifyListeners.forEach { it.addNotify() }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
18
src/main/kotlin/app/termora/TermoraLayout.kt
Normal file
18
src/main/kotlin/app/termora/TermoraLayout.kt
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
package app.termora
|
||||||
|
|
||||||
|
import app.termora.database.DatabaseManager
|
||||||
|
|
||||||
|
enum class TermoraLayout {
|
||||||
|
/**
|
||||||
|
* Split
|
||||||
|
*/
|
||||||
|
Fence,
|
||||||
|
|
||||||
|
Screen, ;
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val Layout by lazy {
|
||||||
|
runCatching { TermoraLayout.valueOf(DatabaseManager.getInstance().appearance.layout) }.getOrNull() ?: Screen
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
30
src/main/kotlin/app/termora/TermoraScreenPanel.kt
Normal file
30
src/main/kotlin/app/termora/TermoraScreenPanel.kt
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
package app.termora
|
||||||
|
|
||||||
|
import app.termora.actions.DataProviders
|
||||||
|
import app.termora.tree.NewHostTree
|
||||||
|
import java.awt.BorderLayout
|
||||||
|
import java.util.*
|
||||||
|
import javax.swing.JPanel
|
||||||
|
|
||||||
|
class TermoraScreenPanel(private val windowScope: WindowScope, private val terminalTabbed: TerminalTabbed) :
|
||||||
|
JPanel(BorderLayout()) {
|
||||||
|
private val welcomePanel = WelcomePanel()
|
||||||
|
|
||||||
|
init {
|
||||||
|
initView()
|
||||||
|
initEvents()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initView() {
|
||||||
|
add(terminalTabbed, BorderLayout.CENTER)
|
||||||
|
terminalTabbed.addTerminalTab(welcomePanel, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initEvents() {
|
||||||
|
Disposer.register(windowScope, welcomePanel)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getHostTree(): NewHostTree {
|
||||||
|
return Objects.requireNonNull<NewHostTree>(welcomePanel.getData(DataProviders.Welcome.HostTree))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,14 +4,9 @@ package app.termora
|
|||||||
import app.termora.actions.*
|
import app.termora.actions.*
|
||||||
import app.termora.database.DatabaseManager
|
import app.termora.database.DatabaseManager
|
||||||
import app.termora.findeverywhere.FindEverywhereProvider
|
import app.termora.findeverywhere.FindEverywhereProvider
|
||||||
import app.termora.findeverywhere.FindEverywhereProviderExtension
|
|
||||||
import app.termora.findeverywhere.FindEverywhereResult
|
|
||||||
import app.termora.plugin.internal.extension.DynamicExtensionHandler
|
|
||||||
import app.termora.plugin.internal.ssh.SSHProtocolProvider
|
|
||||||
import app.termora.terminal.DataKey
|
import app.termora.terminal.DataKey
|
||||||
import app.termora.tree.*
|
import app.termora.tree.*
|
||||||
import com.formdev.flatlaf.FlatClientProperties
|
import com.formdev.flatlaf.FlatClientProperties
|
||||||
import com.formdev.flatlaf.FlatLaf
|
|
||||||
import com.formdev.flatlaf.extras.FlatSVGIcon
|
import com.formdev.flatlaf.extras.FlatSVGIcon
|
||||||
import com.formdev.flatlaf.extras.components.FlatButton
|
import com.formdev.flatlaf.extras.components.FlatButton
|
||||||
import org.apache.commons.lang3.StringUtils
|
import org.apache.commons.lang3.StringUtils
|
||||||
@@ -24,8 +19,7 @@ import java.awt.event.*
|
|||||||
import javax.swing.*
|
import javax.swing.*
|
||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
|
|
||||||
class WelcomePanel(private val windowScope: WindowScope) : JPanel(BorderLayout()), Disposable, TerminalTab,
|
class WelcomePanel() : JPanel(BorderLayout()), Disposable, TerminalTab, DataProvider {
|
||||||
DataProvider {
|
|
||||||
|
|
||||||
private val properties get() = DatabaseManager.getInstance().properties
|
private val properties get() = DatabaseManager.getInstance().properties
|
||||||
private val rootPanel = JPanel(BorderLayout())
|
private val rootPanel = JPanel(BorderLayout())
|
||||||
@@ -52,6 +46,7 @@ class WelcomePanel(private val windowScope: WindowScope) : JPanel(BorderLayout()
|
|||||||
val panel = JPanel(BorderLayout())
|
val panel = JPanel(BorderLayout())
|
||||||
panel.add(createSearchPanel(), BorderLayout.NORTH)
|
panel.add(createSearchPanel(), BorderLayout.NORTH)
|
||||||
panel.add(createHostPanel(), BorderLayout.CENTER)
|
panel.add(createHostPanel(), BorderLayout.CENTER)
|
||||||
|
bannerPanel.foreground = UIManager.getColor("TextField.placeholderForeground")
|
||||||
|
|
||||||
if (!fullContent) {
|
if (!fullContent) {
|
||||||
rootPanel.add(bannerPanel, BorderLayout.NORTH)
|
rootPanel.add(bannerPanel, BorderLayout.NORTH)
|
||||||
@@ -209,44 +204,6 @@ class WelcomePanel(private val windowScope: WindowScope) : JPanel(BorderLayout()
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
DynamicExtensionHandler.getInstance()
|
|
||||||
.register(FindEverywhereProviderExtension::class.java, object : FindEverywhereProviderExtension {
|
|
||||||
private val provider = object : FindEverywhereProvider {
|
|
||||||
override fun find(pattern: String, scope: Scope): List<FindEverywhereResult> {
|
|
||||||
if (scope != windowScope) return emptyList()
|
|
||||||
|
|
||||||
var filter = hostTreeModel.root.getAllChildren()
|
|
||||||
.map { it.host }
|
|
||||||
.filter { it.isFolder.not() }
|
|
||||||
|
|
||||||
if (pattern.isNotBlank()) {
|
|
||||||
filter = filter.filter {
|
|
||||||
if (it.protocol == SSHProtocolProvider.PROTOCOL) {
|
|
||||||
it.name.contains(pattern, true) || it.host.contains(pattern, true)
|
|
||||||
} else {
|
|
||||||
it.name.contains(pattern, true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return filter.map { HostFindEverywhereResult(it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun group(): String {
|
|
||||||
return I18n.getString("termora.find-everywhere.groups.open-new-hosts")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun order(): Int {
|
|
||||||
return Integer.MIN_VALUE + 2
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getFindEverywhereProvider(): FindEverywhereProvider {
|
|
||||||
return provider
|
|
||||||
}
|
|
||||||
|
|
||||||
}).let { Disposer.register(this, it) }
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun perform() {
|
private fun perform() {
|
||||||
@@ -302,40 +259,6 @@ class WelcomePanel(private val windowScope: WindowScope) : JPanel(BorderLayout()
|
|||||||
properties.putString("WelcomeFullContent", fullContent.toString())
|
properties.putString("WelcomeFullContent", fullContent.toString())
|
||||||
}
|
}
|
||||||
|
|
||||||
private inner class HostFindEverywhereResult(val host: Host) : FindEverywhereResult {
|
|
||||||
private val showMoreInfo get() = EnableManager.getInstance().isShowMoreInfo()
|
|
||||||
|
|
||||||
override fun actionPerformed(e: ActionEvent) {
|
|
||||||
ActionManager.getInstance()
|
|
||||||
.getAction(OpenHostAction.OPEN_HOST)
|
|
||||||
?.actionPerformed(OpenHostActionEvent(e.source, host, e))
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getIcon(isSelected: Boolean): Icon {
|
|
||||||
if (isSelected) {
|
|
||||||
if (!FlatLaf.isLafDark()) {
|
|
||||||
return Icons.terminal.dark
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Icons.terminal
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getText(isSelected: Boolean): String {
|
|
||||||
if (showMoreInfo) {
|
|
||||||
val color = UIManager.getColor(if (isSelected) "textHighlightText" else "textInactiveText")
|
|
||||||
val moreInfo = when (host.protocol) {
|
|
||||||
SSHProtocolProvider.PROTOCOL -> "${host.username}@${host.host}"
|
|
||||||
"Serial" -> host.options.serialComm.port
|
|
||||||
else -> StringUtils.EMPTY
|
|
||||||
}
|
|
||||||
if (moreInfo.isNotBlank()) {
|
|
||||||
return "<html>${host.name} <font color=rgb(${color.red},${color.green},${color.blue})>${moreInfo}</font></html>"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return host.name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun <T : Any> getData(dataKey: DataKey<T>): T? {
|
override fun <T : Any> getData(dataKey: DataKey<T>): T? {
|
||||||
return dataProviderSupport.getData(dataKey)
|
return dataProviderSupport.getData(dataKey)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -722,6 +722,11 @@ class DatabaseManager private constructor() : Disposable {
|
|||||||
*/
|
*/
|
||||||
var theme by StringPropertyDelegate("Light")
|
var theme by StringPropertyDelegate("Light")
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 布局
|
||||||
|
*/
|
||||||
|
var layout by StringPropertyDelegate(TermoraLayout.Screen.name)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 跟随系统
|
* 跟随系统
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package app.termora.plugin.internal.ssh
|
package app.termora.plugin.internal.ssh
|
||||||
|
|
||||||
import app.termora.*
|
import app.termora.*
|
||||||
import app.termora.actions.AnActionEvent
|
|
||||||
import app.termora.actions.DataProviders
|
import app.termora.actions.DataProviders
|
||||||
import app.termora.actions.TabReconnectAction
|
import app.termora.actions.TabReconnectAction
|
||||||
import app.termora.addons.zmodem.ZModemPtyConnectorAdaptor
|
import app.termora.addons.zmodem.ZModemPtyConnectorAdaptor
|
||||||
@@ -28,7 +27,6 @@ import org.apache.sshd.common.session.Session
|
|||||||
import org.apache.sshd.common.session.SessionListener
|
import org.apache.sshd.common.session.SessionListener
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
import java.nio.charset.StandardCharsets
|
import java.nio.charset.StandardCharsets
|
||||||
import java.util.*
|
|
||||||
import javax.swing.Icon
|
import javax.swing.Icon
|
||||||
import javax.swing.JComponent
|
import javax.swing.JComponent
|
||||||
import javax.swing.SwingUtilities
|
import javax.swing.SwingUtilities
|
||||||
@@ -47,9 +45,6 @@ class SSHTerminalTab(windowScope: WindowScope, host: Host) :
|
|||||||
private var sshClient: SshClient? = null
|
private var sshClient: SshClient? = null
|
||||||
private var sshSession: ClientSession? = null
|
private var sshSession: ClientSession? = null
|
||||||
private var sshChannelShell: ChannelShell? = null
|
private var sshChannelShell: ChannelShell? = null
|
||||||
private val terminalTabbedManager
|
|
||||||
get() = AnActionEvent(getJComponent(), StringUtils.EMPTY, EventObject(getJComponent()))
|
|
||||||
.getData(DataProviders.TerminalTabbedManager)
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
terminalPanel.dropFiles = false
|
terminalPanel.dropFiles = false
|
||||||
|
|||||||
@@ -51,6 +51,9 @@ termora.setting=Settings
|
|||||||
|
|
||||||
termora.settings.appearance=General
|
termora.settings.appearance=General
|
||||||
termora.settings.appearance.theme=Theme
|
termora.settings.appearance.theme=Theme
|
||||||
|
termora.settings.appearance.layout=Layout
|
||||||
|
termora.settings.appearance.layout.screen=Screen
|
||||||
|
termora.settings.appearance.layout.fence=Split
|
||||||
termora.settings.appearance.language=Language
|
termora.settings.appearance.language=Language
|
||||||
termora.settings.appearance.i-want-to-translate=I want to translate
|
termora.settings.appearance.i-want-to-translate=I want to translate
|
||||||
termora.settings.appearance.follow-system=Sync with OS
|
termora.settings.appearance.follow-system=Sync with OS
|
||||||
|
|||||||
@@ -54,6 +54,9 @@ termora.settings.restart.manually=请手动重启软件
|
|||||||
|
|
||||||
termora.settings.appearance=常规
|
termora.settings.appearance=常规
|
||||||
termora.settings.appearance.theme=主题
|
termora.settings.appearance.theme=主题
|
||||||
|
termora.settings.appearance.layout=布局
|
||||||
|
termora.settings.appearance.layout.screen=全屏
|
||||||
|
termora.settings.appearance.layout.fence=分割
|
||||||
termora.settings.appearance.language=语言
|
termora.settings.appearance.language=语言
|
||||||
termora.settings.appearance.i-want-to-translate=我想要翻译
|
termora.settings.appearance.i-want-to-translate=我想要翻译
|
||||||
termora.settings.appearance.follow-system=跟随系统
|
termora.settings.appearance.follow-system=跟随系统
|
||||||
|
|||||||
@@ -53,6 +53,9 @@ termora.settings.restart.manually=請手動重新啟動軟體
|
|||||||
|
|
||||||
termora.settings.appearance=一般
|
termora.settings.appearance=一般
|
||||||
termora.settings.appearance.theme=主题
|
termora.settings.appearance.theme=主题
|
||||||
|
termora.settings.appearance.layout=佈局
|
||||||
|
termora.settings.appearance.layout.screen=全螢幕
|
||||||
|
termora.settings.appearance.layout.fence=分割
|
||||||
termora.settings.appearance.language=語言
|
termora.settings.appearance.language=語言
|
||||||
termora.settings.appearance.i-want-to-translate=我想要翻譯
|
termora.settings.appearance.i-want-to-translate=我想要翻譯
|
||||||
termora.settings.appearance.follow-system=跟隨系統
|
termora.settings.appearance.follow-system=跟隨系統
|
||||||
|
|||||||
5
src/main/resources/icons/dataColumn.svg
Normal file
5
src/main/resources/icons/dataColumn.svg
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<!-- Copyright 2000-2022 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 fill-rule="evenodd" clip-rule="evenodd" d="M1 4C1 2.89543 1.89543 2 3 2H13C14.1046 2 15 2.89543 15 4V12C15 13.1046 14.1046 14 13 14H3C1.89543 14 1 13.1046 1 12V4ZM3 3H5V13H3C2.44772 13 2 12.5523 2 12V4C2 3.44772 2.44772 3 3 3ZM6 3V13H13C13.5523 13 14 12.5523 14 12V4C14 3.44772 13.5523 3 13 3H6Z" fill="#6C707E"/>
|
||||||
|
<path d="M2 4C2 3.44772 2.44772 3 3 3H5V13H3C2.44772 13 2 12.5523 2 12V4Z" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 628 B |
12
src/main/resources/icons/dataColumn_dark.svg
Normal file
12
src/main/resources/icons/dataColumn_dark.svg
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<!-- Copyright 2000-2022 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">
|
||||||
|
<g clip-path="url(#clip0_5436_53300)">
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M1 4C1 2.89543 1.89543 2 3 2H13C14.1046 2 15 2.89543 15 4V12C15 13.1046 14.1046 14 13 14H3C1.89543 14 1 13.1046 1 12V4ZM3 3H5V13H3C2.44772 13 2 12.5523 2 12V4C2 3.44772 2.44772 3 3 3ZM6 3V13H13C13.5523 13 14 12.5523 14 12V4C14 3.44772 13.5523 3 13 3H6Z" fill="#CED0D6"/>
|
||||||
|
<path d="M2 4C2 3.44772 2.44772 3 3 3H5V13H3C2.44772 13 2 12.5523 2 12V4Z" />
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<clipPath id="clip0_5436_53300">
|
||||||
|
<rect width="16" height="16" fill="white"/>
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 776 B |
Reference in New Issue
Block a user