feat: transfer supports copy and paste

This commit is contained in:
hstyi
2025-07-14 11:52:21 +08:00
committed by hstyi
parent a7aec52f2a
commit 8acfdb8bca
6 changed files with 74 additions and 12 deletions

View File

@@ -467,6 +467,15 @@ internal class TransportPanel(
}
})
table.actionMap.put("copy", object : AbstractAction() {
override fun actionPerformed(e: ActionEvent) {
val rows = table.selectedRows.map { sorter.convertRowIndexToModel(it) }.toTypedArray()
val files = rows.map { model.getPath(it) to model.getAttributes(it) }
if (files.any { it.second.isParent }) return
toolkit.systemClipboard.setContents(TransferTransferable(panel, files), null)
}
})
table.actionMap.put("Reload", object : AbstractAction() {
override fun actionPerformed(e: ActionEvent) {
reload()
@@ -514,7 +523,6 @@ internal class TransportPanel(
data class TransferData(
// true 就是本地拖拽上传
val locally: Boolean,
val row: Int,
val insertRow: Boolean,
val workdir: Path,
val files: List<Pair<Path, Attributes>>
@@ -540,18 +548,22 @@ internal class TransportPanel(
private fun getTransferData(support: TransferSupport, load: Boolean): TransferData? {
val workdir = workdir ?: return null
val dropLocation = support.dropLocation as? JTable.DropLocation ?: return null
val row = if (dropLocation.isInsertRow) 0 else sorter.convertRowIndexToModel(dropLocation.row)
if (dropLocation.isInsertRow.not() && dropLocation.column != TransportTableModel.COLUMN_NAME) return null
if (dropLocation.isInsertRow.not() && model.getAttributes(row).isDirectory.not()) return null
if (hasParent && dropLocation.row == 0) return null
val paths = mutableListOf<Pair<Path, Attributes>>()
var locally = false
if (support.isDataFlavorSupported(TransferTransferable.FLAVOR)) {
val transferTransferable = support.transferable.getTransferData(TransferTransferable.FLAVOR)
as? TransferTransferable ?: return null
if (support.isDrop) {
if (transferTransferable.component == panel) return null
} else {
// 如果在一个目录,那么是不允许粘贴的
for (pair in transferTransferable.files) {
if (pair.first.parent?.pathString == workdir.pathString) {
return null
}
}
}
paths.addAll(transferTransferable.files)
} else if (support.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) {
if (loader.isLoaded() && loader.getSyncTransportSupport().getFileSystem().isLocallyFileSystem())
@@ -569,15 +581,29 @@ internal class TransportPanel(
return null
}
if (support.isDrop) {
val dropLocation = support.dropLocation as? JTable.DropLocation ?: return null
val row = if (dropLocation.isInsertRow) 0 else sorter.convertRowIndexToModel(dropLocation.row)
if (dropLocation.isInsertRow.not() && dropLocation.column != TransportTableModel.COLUMN_NAME) return null
if (dropLocation.isInsertRow.not() && model.getAttributes(row).isDirectory.not()) return null
if (hasParent && dropLocation.row == 0) return null
return TransferData(
locally = locally,
row = row,
insertRow = dropLocation.isInsertRow,
workdir = if (dropLocation.isInsertRow) workdir else model.getPath(row),
files = paths
)
}
return TransferData(
locally = locally,
insertRow = false,
workdir = workdir,
files = paths
)
}
override fun getSourceActions(c: JComponent?): Int {
return COPY
}
@@ -899,7 +925,7 @@ internal class TransportPanel(
}
}
private class TransferTransferable(val component: TransportPanel, val files: List<Pair<Path, Attributes>>) :
class TransferTransferable(val component: TransportPanel, val files: List<Pair<Path, Attributes>>) :
Transferable {
companion object {
val FLAVOR = DataFlavor("termora/transfers", "Termora transfers")
@@ -1041,7 +1067,6 @@ internal class TransportPanel(
}
private inner class PopupMenuActionListener(private val files: List<Pair<Path, Attributes>>) : ActionListener {
@Suppress("CascadeIf")
override fun actionPerformed(e: ActionEvent) {
val actionCommand = TransportPopupMenu.ActionCommand.valueOf(e.actionCommand)
if (actionCommand == TransportPopupMenu.ActionCommand.Transfer) {
@@ -1089,6 +1114,12 @@ internal class TransportPanel(
Files.setPosixFilePermissions(path, c.permissions)
}
}
} else if (actionCommand == TransportPopupMenu.ActionCommand.Copy) {
val transferable = TransferTransferable(panel, files)
toolkit.systemClipboard.setContents(transferable, null)
} else if (actionCommand == TransportPopupMenu.ActionCommand.Paste) {
val transferable = toolkit.systemClipboard.getContents(null) ?: return
table.transferHandler.importData(TransferHandler.TransferSupport(table, transferable))
}
}

View File

@@ -22,6 +22,8 @@ import javax.swing.JMenu
import javax.swing.JMenuItem
import javax.swing.JOptionPane
import javax.swing.event.EventListenerList
import javax.swing.event.PopupMenuEvent
import javax.swing.event.PopupMenuListener
import kotlin.io.path.absolutePathString
import kotlin.io.path.name
@@ -39,6 +41,8 @@ internal class TransportPopupMenu(
private val transferMenu = JMenuItem(I18n.getString("termora.transport.table.contextmenu.transfer"))
private val editMenu = JMenuItem(I18n.getString("termora.transport.table.contextmenu.edit"))
private val copyPathMenu = JMenuItem(I18n.getString("termora.transport.table.contextmenu.copy-path"))
private val copyMenu = JMenuItem(I18n.getString("termora.copy"))
private val pasteMenu = JMenuItem(I18n.getString("termora.paste"))
private val openInFinderMenu = JMenuItem(I18n.getString("termora.transport.table.contextmenu.open-in-folder"))
private val renameMenu = JMenuItem(I18n.getString("termora.transport.table.contextmenu.rename"))
private val deleteMenu = JMenuItem(I18n.getString("termora.transport.table.contextmenu.delete"))
@@ -82,6 +86,9 @@ internal class TransportPopupMenu(
add(transferMenu)
add(editMenu)
addSeparator()
add(copyMenu)
add(pasteMenu)
addSeparator()
add(copyPathMenu)
if (fileSystem?.isLocallyFileSystem() == true) {
add(openInFinderMenu)
@@ -133,6 +140,7 @@ internal class TransportPopupMenu(
renameMenu.isEnabled = hasParent.not() && files.size == 1
deleteMenu.isEnabled = hasParent.not() && files.isNotEmpty()
changePermissionsMenu.isVisible = hasParent.not() && fileSystem is SftpFileSystem && files.size == 1
copyMenu.isEnabled = hasParent.not() && files.isNotEmpty()
for ((item, mnemonic) in mnemonics) {
item.text = "${item.text}(${KeyEvent.getKeyText(mnemonic)})"
@@ -166,6 +174,22 @@ internal class TransportPopupMenu(
sb.deleteCharAt(sb.length - 1)
toolkit.systemClipboard.setContents(StringSelection(sb.toString()), null)
}
copyMenu.addActionListener { fireActionPerformed(it, ActionCommand.Copy) }
pasteMenu.addActionListener { fireActionPerformed(it, ActionCommand.Paste) }
addPopupMenuListener(object : PopupMenuListener {
override fun popupMenuWillBecomeVisible(e: PopupMenuEvent?) {
pasteMenu.isEnabled = toolkit.systemClipboard
.isDataFlavorAvailable(TransportPanel.TransferTransferable.FLAVOR)
}
override fun popupMenuWillBecomeInvisible(e: PopupMenuEvent?) {
}
override fun popupMenuCanceled(e: PopupMenuEvent?) {
}
})
}
fun fireActionPerformed(evt: ActionEvent, command: ActionCommand) {
@@ -241,6 +265,8 @@ internal class TransportPopupMenu(
ChangePermissions,
Rmrf,
Reconnect,
Paste,
Copy,
}
data class ChangePermission(val permissions: Set<PosixFilePermission>, val includeSubFolder: Boolean)

View File

@@ -3,6 +3,7 @@ termora.confirm=OK
termora.exit=退出
termora.cancel=Cancel
termora.copy=Copy
termora.paste=Paste
termora.apply=Apply
termora.save=Save
termora.remove=Delete

View File

@@ -3,6 +3,7 @@ termora.confirm=Ок
termora.exit=покидать
termora.cancel=Отмена
termora.copy=Копировать
termora.paste=Вставить
termora.apply=Применить
termora.save=Сохранить
termora.remove=Удалить

View File

@@ -2,6 +2,7 @@ termora.confirm=确认
termora.exit=退出
termora.cancel=取消
termora.copy=复制
termora.paste=粘贴
termora.apply=应用
termora.save=保存
termora.remove=删除

View File

@@ -1,6 +1,8 @@
termora.confirm=確定
termora.exit=Exit
termora.cancel=取消
termora.copy=複製
termora.paste=貼上
termora.apply=应用
termora.save=儲存
termora.remove=刪除