From 9cd83c40257e9815f528fee9e78b696d2583ed6b Mon Sep 17 00:00:00 2001 From: hstyi Date: Fri, 15 Aug 2025 15:25:22 +0800 Subject: [PATCH] feat: host tree supports copy and paste --- .../kotlin/app/termora/tree/NewHostTree.kt | 73 +++++++++++++++++++ .../kotlin/app/termora/tree/SimpleTreeNode.kt | 12 +++ 2 files changed, 85 insertions(+) diff --git a/src/main/kotlin/app/termora/tree/NewHostTree.kt b/src/main/kotlin/app/termora/tree/NewHostTree.kt index 26a38c3..ef3fa15 100644 --- a/src/main/kotlin/app/termora/tree/NewHostTree.kt +++ b/src/main/kotlin/app/termora/tree/NewHostTree.kt @@ -3,6 +3,8 @@ package app.termora.tree import app.termora.* import app.termora.Application.ohMyJson import app.termora.account.AccountManager +import app.termora.actions.AnAction +import app.termora.actions.AnActionEvent import app.termora.actions.OpenHostAction import app.termora.database.DatabaseChangedExtension import app.termora.database.DatabaseManager @@ -32,6 +34,10 @@ import org.jdesktop.swingx.action.ActionManager import org.slf4j.LoggerFactory import org.w3c.dom.Element import org.w3c.dom.NodeList +import java.awt.datatransfer.DataFlavor +import java.awt.datatransfer.StringSelection +import java.awt.datatransfer.Transferable +import java.awt.datatransfer.UnsupportedFlavorException import java.awt.event.* import java.io.* import java.util.* @@ -140,6 +146,41 @@ class NewHostTree : SimpleTree(), Disposable { } }) + actionMap.put("copy", object : AnAction() { + override fun actionPerformed(evt: AnActionEvent) { + toolkit.systemClipboard.setContents(StringSelection(StringUtils.EMPTY), null) + val nodes = getSelectionSimpleTreeNodes(false).toMutableList() + nodes.removeIf { e -> e.getParents().any { nodes.contains(it) } } + if (nodes.isEmpty() || nodes.any { it is TeamTreeNode }) return + if (nodes.any { it.id == "0" || it.id.isBlank() }) return + toolkit.systemClipboard.setContents(NodesTransferable(nodes), null) + } + }) + + actionMap.put("paste", object : AnAction() { + override fun actionPerformed(evt: AnActionEvent) { + val lastNode = getLastSelectedPathNode() ?: return + val folder = if (lastNode.isFolder) lastNode.parent ?: simpleTreeModel.root + else lastNode.parent ?: return + + if (toolkit.systemClipboard.isDataFlavorAvailable(NodesTransferable.FLAVOR).not()) return + val nodes = (toolkit.systemClipboard.getData(NodesTransferable.FLAVOR) as? List<*>) + ?.filterIsInstance() ?: return + + for (node in nodes) { + val newNode = copyNode(node, folder.id) + // 复制的是文件夹,就在最后面 + if (newNode.isFolder) { + simpleTreeModel.insertNodeInto(newNode, folder, folder.folderCount) + } else if (lastNode.isFolder) { // 用户选的节点是文件夹,那就在最后一个child下面 + simpleTreeModel.insertNodeInto(newNode, folder, folder.childCount) + } else { // 用户选的是主机并且复制的是主机 + simpleTreeModel.insertNodeInto(newNode, folder, folder.getIndex(lastNode) + 1) + } + } + } + }) + } fun restoreExpansions() { @@ -391,6 +432,20 @@ class NewHostTree : SimpleTree(), Disposable { }) + val mnemonics = mapOf( + refresh to KeyEvent.VK_R, + newMenu to KeyEvent.VK_W, + newFolder to KeyEvent.VK_F, + rename to KeyEvent.VK_M, + remove to KeyEvent.VK_D, + property to KeyEvent.VK_I, + ) + + for ((item, mnemonic) in mnemonics) { + item.text = "${item.text}(${KeyEvent.getKeyText(mnemonic)})" + item.setMnemonic(mnemonic) + } + popupMenu.show(this, evt.x, evt.y) } @@ -1077,5 +1132,23 @@ class NewHostTree : SimpleTree(), Disposable { electerm, } + private class NodesTransferable(val nodes: List) : Transferable { + companion object { + val FLAVOR = DataFlavor("termora/host-tree", "Termora host tree transfers") + } + + override fun getTransferDataFlavors(): Array { + return arrayOf(FLAVOR) + } + + override fun isDataFlavorSupported(flavor: DataFlavor?): Boolean { + return flavor == FLAVOR + } + + override fun getTransferData(flavor: DataFlavor?): Any { + return if (flavor == FLAVOR) nodes else throw UnsupportedFlavorException(flavor) + } + + } } \ No newline at end of file diff --git a/src/main/kotlin/app/termora/tree/SimpleTreeNode.kt b/src/main/kotlin/app/termora/tree/SimpleTreeNode.kt index 58790d5..978d98a 100644 --- a/src/main/kotlin/app/termora/tree/SimpleTreeNode.kt +++ b/src/main/kotlin/app/termora/tree/SimpleTreeNode.kt @@ -2,6 +2,7 @@ package app.termora.tree import javax.swing.Icon import javax.swing.tree.DefaultMutableTreeNode +import javax.swing.tree.TreeNode abstract class SimpleTreeNode(data: T) : DefaultMutableTreeNode(data) { @Suppress("UNCHECKED_CAST") @@ -35,4 +36,15 @@ abstract class SimpleTreeNode(data: T) : DefaultMutableTreeNode(data) { return children } + open fun getParents(): List> { + val parents = mutableListOf>() + var p = parent as TreeNode? + while (p != null) { + if (p is SimpleTreeNode) { + parents.add(p) + } + p = p.parent + } + return parents + } } \ No newline at end of file