mirror of
https://github.com/TermoraDev/termora.git
synced 2026-01-15 18:02:58 +08:00
feat: support drag and drop sorting
This commit is contained in:
@@ -1,12 +1,162 @@
|
||||
package app.termora
|
||||
|
||||
import app.termora.actions.AnActionEvent
|
||||
import app.termora.actions.DataProviders
|
||||
import com.formdev.flatlaf.extras.components.FlatTabbedPane
|
||||
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 kotlin.math.abs
|
||||
|
||||
class MyTabbedPane : FlatTabbedPane() {
|
||||
|
||||
private val owner: Window get() = SwingUtilities.getWindowAncestor(this)
|
||||
private val dragMouseAdaptor = DragMouseAdaptor()
|
||||
private val terminalTabbedManager
|
||||
get() = AnActionEvent(this, StringUtils.EMPTY, EventObject(this))
|
||||
.getData(DataProviders.TerminalTabbedManager)
|
||||
|
||||
init {
|
||||
initEvents()
|
||||
}
|
||||
|
||||
|
||||
private fun initEvents() {
|
||||
addMouseListener(dragMouseAdaptor)
|
||||
addMouseMotionListener(dragMouseAdaptor)
|
||||
}
|
||||
|
||||
override fun setSelectedIndex(index: Int) {
|
||||
val oldIndex = selectedIndex
|
||||
super.setSelectedIndex(index)
|
||||
firePropertyChange("selectedIndex", oldIndex, index)
|
||||
}
|
||||
|
||||
|
||||
private inner class DragMouseAdaptor : MouseAdapter(), KeyEventDispatcher {
|
||||
private var mousePressedPoint = Point()
|
||||
private var tabIndex = 0 - 1
|
||||
private var cancelled = false
|
||||
private var window: Window? = null
|
||||
private var terminalTab: TerminalTab? = null
|
||||
private var isDragging = false
|
||||
private var lastVisitTabIndex = -1
|
||||
|
||||
override fun mousePressed(e: MouseEvent) {
|
||||
val index = indexAtLocation(e.x, e.y)
|
||||
if (index < 0 || !isTabClosable(index)) {
|
||||
return
|
||||
}
|
||||
tabIndex = index
|
||||
mousePressedPoint = e.point
|
||||
}
|
||||
|
||||
override fun mouseDragged(e: MouseEvent) {
|
||||
// 如果正在拖拽中,那么修改 Window 的位置
|
||||
if (isDragging) {
|
||||
window?.location = e.locationOnScreen
|
||||
lastVisitTabIndex = indexAtLocation(e.x, e.y)
|
||||
} else if (tabIndex >= 0) { // 这里之所以判断是确保在 mousePressed 时已经确定了 Tab
|
||||
// 有的时候会太灵敏,这里容错一下
|
||||
val diff = 5
|
||||
if (abs(mousePressedPoint.y - e.y) >= diff || abs(mousePressedPoint.x - e.x) >= diff) {
|
||||
startDrag(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun startDrag(e: MouseEvent) {
|
||||
if (isDragging) return
|
||||
val terminalTabbedManager = terminalTabbedManager ?: return
|
||||
val window = JDialog(owner).also { this.window = it }
|
||||
window.isUndecorated = true
|
||||
val image = createTabImage(tabIndex)
|
||||
window.size = Dimension(image.width, image.height)
|
||||
window.add(JLabel(ImageIcon(image)))
|
||||
window.location = e.locationOnScreen
|
||||
window.addWindowListener(object : WindowAdapter() {
|
||||
override fun windowClosed(e: WindowEvent) {
|
||||
KeyboardFocusManager.getCurrentKeyboardFocusManager()
|
||||
.removeKeyEventDispatcher(this@DragMouseAdaptor)
|
||||
}
|
||||
|
||||
override fun windowOpened(e: WindowEvent) {
|
||||
KeyboardFocusManager.getCurrentKeyboardFocusManager()
|
||||
.addKeyEventDispatcher(this@DragMouseAdaptor)
|
||||
}
|
||||
})
|
||||
|
||||
// 暂时关闭 Tab
|
||||
terminalTabbedManager.closeTerminalTab(terminalTabbedManager.getTerminalTabs()[tabIndex].also {
|
||||
terminalTab = it
|
||||
}, false)
|
||||
|
||||
window.isVisible = true
|
||||
|
||||
isDragging = true
|
||||
cancelled = false
|
||||
}
|
||||
|
||||
private fun stopDrag() {
|
||||
if (!isDragging) {
|
||||
return
|
||||
}
|
||||
|
||||
val tab = this.terminalTab
|
||||
val terminalTabbedManager = terminalTabbedManager
|
||||
|
||||
if (tab != null && terminalTabbedManager != null) {
|
||||
// 如果是手动取消
|
||||
if (cancelled) {
|
||||
terminalTabbedManager.addTerminalTab(tabIndex, tab)
|
||||
} else if (lastVisitTabIndex > 0) {
|
||||
terminalTabbedManager.addTerminalTab(lastVisitTabIndex, tab)
|
||||
} else if (lastVisitTabIndex == 0) {
|
||||
terminalTabbedManager.addTerminalTab(1, tab)
|
||||
} else {
|
||||
terminalTabbedManager.addTerminalTab(tab)
|
||||
}
|
||||
}
|
||||
|
||||
// reset
|
||||
window?.dispose()
|
||||
isDragging = false
|
||||
tabIndex = -1
|
||||
cancelled = false
|
||||
lastVisitTabIndex = -1
|
||||
}
|
||||
|
||||
override fun mouseReleased(e: MouseEvent) {
|
||||
stopDrag()
|
||||
}
|
||||
|
||||
private fun createTabImage(index: Int): BufferedImage {
|
||||
val tabBounds = getBoundsAt(index)
|
||||
val image = BufferedImage(tabBounds.width, tabBounds.height, BufferedImage.TYPE_INT_ARGB)
|
||||
val g2 = image.createGraphics()
|
||||
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON)
|
||||
g2.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY)
|
||||
g2.translate(-tabBounds.x, -tabBounds.y)
|
||||
paint(g2)
|
||||
g2.dispose()
|
||||
return image
|
||||
}
|
||||
|
||||
override fun dispatchKeyEvent(e: KeyEvent): Boolean {
|
||||
if (e.keyCode == KeyEvent.VK_ESCAPE) {
|
||||
cancelled = true
|
||||
stopDrag()
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -10,12 +10,14 @@ import app.termora.transport.TransportPanel
|
||||
import com.formdev.flatlaf.FlatLaf
|
||||
import com.formdev.flatlaf.extras.components.FlatPopupMenu
|
||||
import com.formdev.flatlaf.extras.components.FlatTabbedPane
|
||||
import org.apache.commons.lang3.StringUtils
|
||||
import java.awt.*
|
||||
import java.awt.event.AWTEventListener
|
||||
import java.awt.event.ActionEvent
|
||||
import java.awt.event.MouseAdapter
|
||||
import java.awt.event.MouseEvent
|
||||
import java.beans.PropertyChangeListener
|
||||
import java.util.*
|
||||
import javax.swing.*
|
||||
import javax.swing.JTabbedPane.SCROLL_TAB_LAYOUT
|
||||
import kotlin.math.min
|
||||
@@ -30,7 +32,7 @@ class TerminalTabbed(
|
||||
private val toolbar = termoraToolBar.getJToolBar()
|
||||
private val actionManager = ActionManager.getInstance()
|
||||
private val dataProviderSupport = DataProviderSupport()
|
||||
|
||||
private val titleProperty = UUID.randomUUID().toSimpleString()
|
||||
private val iconListener = PropertyChangeListener { e ->
|
||||
val source = e.source
|
||||
if (e.propertyName == "icon" && source is TerminalTab) {
|
||||
@@ -190,16 +192,16 @@ class TerminalTabbed(
|
||||
// 修改名称
|
||||
val rename = popupMenu.add(I18n.getString("termora.tabbed.contextmenu.rename"))
|
||||
rename.addActionListener {
|
||||
val index = tabbedPane.selectedIndex
|
||||
if (index > 0) {
|
||||
if (tabIndex > 0) {
|
||||
val dialog = InputDialog(
|
||||
SwingUtilities.getWindowAncestor(this),
|
||||
title = rename.text,
|
||||
text = tabbedPane.getTitleAt(index),
|
||||
text = tabbedPane.getTitleAt(tabIndex),
|
||||
)
|
||||
val text = dialog.getText()
|
||||
if (!text.isNullOrBlank()) {
|
||||
tabbedPane.setTitleAt(index, text)
|
||||
tabbedPane.setTitleAt(tabIndex, text)
|
||||
c.putClientProperty(titleProperty, text)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -276,9 +278,8 @@ class TerminalTabbed(
|
||||
popupMenu.addSeparator()
|
||||
val reconnect = popupMenu.add(I18n.getString("termora.tabbed.contextmenu.reconnect"))
|
||||
reconnect.addActionListener {
|
||||
val index = tabbedPane.selectedIndex
|
||||
if (index > 0) {
|
||||
tabs[index].reconnect()
|
||||
if (tabIndex > 0) {
|
||||
tabs[tabIndex].reconnect()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -289,18 +290,24 @@ class TerminalTabbed(
|
||||
}
|
||||
|
||||
|
||||
fun addTab(tab: TerminalTab) {
|
||||
tabbedPane.addTab(
|
||||
tab.getTitle(),
|
||||
private fun addTab(index: Int, tab: TerminalTab) {
|
||||
val c = tab.getJComponent()
|
||||
val title = (c.getClientProperty(titleProperty) ?: tab.getTitle()).toString()
|
||||
|
||||
tabbedPane.insertTab(
|
||||
title,
|
||||
tab.getIcon(),
|
||||
tab.getJComponent()
|
||||
c,
|
||||
StringUtils.EMPTY,
|
||||
index
|
||||
)
|
||||
c.putClientProperty(titleProperty, title)
|
||||
|
||||
// 监听 icons 变化
|
||||
tab.addPropertyChangeListener(iconListener)
|
||||
|
||||
tabs.add(tab)
|
||||
tabbedPane.selectedIndex = tabbedPane.tabCount - 1
|
||||
tabs.add(index, tab)
|
||||
tabbedPane.selectedIndex = index
|
||||
Disposer.register(this, tab)
|
||||
}
|
||||
|
||||
@@ -393,7 +400,11 @@ class TerminalTabbed(
|
||||
}
|
||||
|
||||
override fun addTerminalTab(tab: TerminalTab) {
|
||||
addTab(tab)
|
||||
addTab(tabs.size, tab)
|
||||
}
|
||||
|
||||
override fun addTerminalTab(index: Int, tab: TerminalTab) {
|
||||
addTab(index, tab)
|
||||
}
|
||||
|
||||
override fun getSelectedTerminalTab(): TerminalTab? {
|
||||
@@ -418,10 +429,10 @@ class TerminalTabbed(
|
||||
}
|
||||
}
|
||||
|
||||
override fun closeTerminalTab(tab: TerminalTab) {
|
||||
override fun closeTerminalTab(tab: TerminalTab, disposable: Boolean) {
|
||||
for (i in 0 until tabs.size) {
|
||||
if (tabs[i] == tab) {
|
||||
removeTabAt(i, true)
|
||||
removeTabAt(i, disposable)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,9 @@ package app.termora
|
||||
|
||||
interface TerminalTabbedManager {
|
||||
fun addTerminalTab(tab: TerminalTab)
|
||||
fun addTerminalTab(index: Int, tab: TerminalTab)
|
||||
fun getSelectedTerminalTab(): TerminalTab?
|
||||
fun getTerminalTabs(): List<TerminalTab>
|
||||
fun setSelectedTerminalTab(tab: TerminalTab)
|
||||
fun closeTerminalTab(tab: TerminalTab)
|
||||
fun closeTerminalTab(tab: TerminalTab, disposable: Boolean = true)
|
||||
}
|
||||
@@ -101,7 +101,7 @@ class TermoraFrame : JFrame(), DataProvider {
|
||||
}
|
||||
|
||||
minimumSize = Dimension(640, 400)
|
||||
terminalTabbed.addTab(welcomePanel)
|
||||
terminalTabbed.addTerminalTab(welcomePanel)
|
||||
|
||||
// macOS 要避开左边的控制栏
|
||||
if (SystemInfo.isMacOS) {
|
||||
|
||||
Reference in New Issue
Block a user