mirror of
https://github.com/TermoraDev/termora.git
synced 2026-01-16 02:12:58 +08:00
feat: transfer supports copy and paste
This commit is contained in:
@@ -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() {
|
table.actionMap.put("Reload", object : AbstractAction() {
|
||||||
override fun actionPerformed(e: ActionEvent) {
|
override fun actionPerformed(e: ActionEvent) {
|
||||||
reload()
|
reload()
|
||||||
@@ -514,7 +523,6 @@ internal class TransportPanel(
|
|||||||
data class TransferData(
|
data class TransferData(
|
||||||
// true 就是本地拖拽上传
|
// true 就是本地拖拽上传
|
||||||
val locally: Boolean,
|
val locally: Boolean,
|
||||||
val row: Int,
|
|
||||||
val insertRow: Boolean,
|
val insertRow: Boolean,
|
||||||
val workdir: Path,
|
val workdir: Path,
|
||||||
val files: List<Pair<Path, Attributes>>
|
val files: List<Pair<Path, Attributes>>
|
||||||
@@ -540,18 +548,22 @@ internal class TransportPanel(
|
|||||||
|
|
||||||
private fun getTransferData(support: TransferSupport, load: Boolean): TransferData? {
|
private fun getTransferData(support: TransferSupport, load: Boolean): TransferData? {
|
||||||
val workdir = workdir ?: return null
|
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>>()
|
val paths = mutableListOf<Pair<Path, Attributes>>()
|
||||||
var locally = false
|
var locally = false
|
||||||
|
|
||||||
if (support.isDataFlavorSupported(TransferTransferable.FLAVOR)) {
|
if (support.isDataFlavorSupported(TransferTransferable.FLAVOR)) {
|
||||||
val transferTransferable = support.transferable.getTransferData(TransferTransferable.FLAVOR)
|
val transferTransferable = support.transferable.getTransferData(TransferTransferable.FLAVOR)
|
||||||
as? TransferTransferable ?: return null
|
as? TransferTransferable ?: return null
|
||||||
if (transferTransferable.component == panel) 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)
|
paths.addAll(transferTransferable.files)
|
||||||
} else if (support.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) {
|
} else if (support.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) {
|
||||||
if (loader.isLoaded() && loader.getSyncTransportSupport().getFileSystem().isLocallyFileSystem())
|
if (loader.isLoaded() && loader.getSyncTransportSupport().getFileSystem().isLocallyFileSystem())
|
||||||
@@ -569,13 +581,27 @@ internal class TransportPanel(
|
|||||||
return null
|
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,
|
||||||
|
insertRow = dropLocation.isInsertRow,
|
||||||
|
workdir = if (dropLocation.isInsertRow) workdir else model.getPath(row),
|
||||||
|
files = paths
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return TransferData(
|
return TransferData(
|
||||||
locally = locally,
|
locally = locally,
|
||||||
row = row,
|
insertRow = false,
|
||||||
insertRow = dropLocation.isInsertRow,
|
workdir = workdir,
|
||||||
workdir = if (dropLocation.isInsertRow) workdir else model.getPath(row),
|
|
||||||
files = paths
|
files = paths
|
||||||
)
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getSourceActions(c: JComponent?): Int {
|
override fun getSourceActions(c: JComponent?): Int {
|
||||||
@@ -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 {
|
Transferable {
|
||||||
companion object {
|
companion object {
|
||||||
val FLAVOR = DataFlavor("termora/transfers", "Termora transfers")
|
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 {
|
private inner class PopupMenuActionListener(private val files: List<Pair<Path, Attributes>>) : ActionListener {
|
||||||
@Suppress("CascadeIf")
|
|
||||||
override fun actionPerformed(e: ActionEvent) {
|
override fun actionPerformed(e: ActionEvent) {
|
||||||
val actionCommand = TransportPopupMenu.ActionCommand.valueOf(e.actionCommand)
|
val actionCommand = TransportPopupMenu.ActionCommand.valueOf(e.actionCommand)
|
||||||
if (actionCommand == TransportPopupMenu.ActionCommand.Transfer) {
|
if (actionCommand == TransportPopupMenu.ActionCommand.Transfer) {
|
||||||
@@ -1089,6 +1114,12 @@ internal class TransportPanel(
|
|||||||
Files.setPosixFilePermissions(path, c.permissions)
|
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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ import javax.swing.JMenu
|
|||||||
import javax.swing.JMenuItem
|
import javax.swing.JMenuItem
|
||||||
import javax.swing.JOptionPane
|
import javax.swing.JOptionPane
|
||||||
import javax.swing.event.EventListenerList
|
import javax.swing.event.EventListenerList
|
||||||
|
import javax.swing.event.PopupMenuEvent
|
||||||
|
import javax.swing.event.PopupMenuListener
|
||||||
import kotlin.io.path.absolutePathString
|
import kotlin.io.path.absolutePathString
|
||||||
import kotlin.io.path.name
|
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 transferMenu = JMenuItem(I18n.getString("termora.transport.table.contextmenu.transfer"))
|
||||||
private val editMenu = JMenuItem(I18n.getString("termora.transport.table.contextmenu.edit"))
|
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 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 openInFinderMenu = JMenuItem(I18n.getString("termora.transport.table.contextmenu.open-in-folder"))
|
||||||
private val renameMenu = JMenuItem(I18n.getString("termora.transport.table.contextmenu.rename"))
|
private val renameMenu = JMenuItem(I18n.getString("termora.transport.table.contextmenu.rename"))
|
||||||
private val deleteMenu = JMenuItem(I18n.getString("termora.transport.table.contextmenu.delete"))
|
private val deleteMenu = JMenuItem(I18n.getString("termora.transport.table.contextmenu.delete"))
|
||||||
@@ -82,6 +86,9 @@ internal class TransportPopupMenu(
|
|||||||
add(transferMenu)
|
add(transferMenu)
|
||||||
add(editMenu)
|
add(editMenu)
|
||||||
addSeparator()
|
addSeparator()
|
||||||
|
add(copyMenu)
|
||||||
|
add(pasteMenu)
|
||||||
|
addSeparator()
|
||||||
add(copyPathMenu)
|
add(copyPathMenu)
|
||||||
if (fileSystem?.isLocallyFileSystem() == true) {
|
if (fileSystem?.isLocallyFileSystem() == true) {
|
||||||
add(openInFinderMenu)
|
add(openInFinderMenu)
|
||||||
@@ -133,6 +140,7 @@ internal class TransportPopupMenu(
|
|||||||
renameMenu.isEnabled = hasParent.not() && files.size == 1
|
renameMenu.isEnabled = hasParent.not() && files.size == 1
|
||||||
deleteMenu.isEnabled = hasParent.not() && files.isNotEmpty()
|
deleteMenu.isEnabled = hasParent.not() && files.isNotEmpty()
|
||||||
changePermissionsMenu.isVisible = hasParent.not() && fileSystem is SftpFileSystem && files.size == 1
|
changePermissionsMenu.isVisible = hasParent.not() && fileSystem is SftpFileSystem && files.size == 1
|
||||||
|
copyMenu.isEnabled = hasParent.not() && files.isNotEmpty()
|
||||||
|
|
||||||
for ((item, mnemonic) in mnemonics) {
|
for ((item, mnemonic) in mnemonics) {
|
||||||
item.text = "${item.text}(${KeyEvent.getKeyText(mnemonic)})"
|
item.text = "${item.text}(${KeyEvent.getKeyText(mnemonic)})"
|
||||||
@@ -166,6 +174,22 @@ internal class TransportPopupMenu(
|
|||||||
sb.deleteCharAt(sb.length - 1)
|
sb.deleteCharAt(sb.length - 1)
|
||||||
toolkit.systemClipboard.setContents(StringSelection(sb.toString()), null)
|
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) {
|
fun fireActionPerformed(evt: ActionEvent, command: ActionCommand) {
|
||||||
@@ -241,6 +265,8 @@ internal class TransportPopupMenu(
|
|||||||
ChangePermissions,
|
ChangePermissions,
|
||||||
Rmrf,
|
Rmrf,
|
||||||
Reconnect,
|
Reconnect,
|
||||||
|
Paste,
|
||||||
|
Copy,
|
||||||
}
|
}
|
||||||
|
|
||||||
data class ChangePermission(val permissions: Set<PosixFilePermission>, val includeSubFolder: Boolean)
|
data class ChangePermission(val permissions: Set<PosixFilePermission>, val includeSubFolder: Boolean)
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ termora.confirm=OK
|
|||||||
termora.exit=退出
|
termora.exit=退出
|
||||||
termora.cancel=Cancel
|
termora.cancel=Cancel
|
||||||
termora.copy=Copy
|
termora.copy=Copy
|
||||||
|
termora.paste=Paste
|
||||||
termora.apply=Apply
|
termora.apply=Apply
|
||||||
termora.save=Save
|
termora.save=Save
|
||||||
termora.remove=Delete
|
termora.remove=Delete
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ termora.confirm=Ок
|
|||||||
termora.exit=покидать
|
termora.exit=покидать
|
||||||
termora.cancel=Отмена
|
termora.cancel=Отмена
|
||||||
termora.copy=Копировать
|
termora.copy=Копировать
|
||||||
|
termora.paste=Вставить
|
||||||
termora.apply=Применить
|
termora.apply=Применить
|
||||||
termora.save=Сохранить
|
termora.save=Сохранить
|
||||||
termora.remove=Удалить
|
termora.remove=Удалить
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ termora.confirm=确认
|
|||||||
termora.exit=退出
|
termora.exit=退出
|
||||||
termora.cancel=取消
|
termora.cancel=取消
|
||||||
termora.copy=复制
|
termora.copy=复制
|
||||||
|
termora.paste=粘贴
|
||||||
termora.apply=应用
|
termora.apply=应用
|
||||||
termora.save=保存
|
termora.save=保存
|
||||||
termora.remove=删除
|
termora.remove=删除
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
termora.confirm=確定
|
termora.confirm=確定
|
||||||
termora.exit=Exit
|
termora.exit=Exit
|
||||||
termora.cancel=取消
|
termora.cancel=取消
|
||||||
|
termora.copy=複製
|
||||||
|
termora.paste=貼上
|
||||||
termora.apply=应用
|
termora.apply=应用
|
||||||
termora.save=儲存
|
termora.save=儲存
|
||||||
termora.remove=刪除
|
termora.remove=刪除
|
||||||
|
|||||||
Reference in New Issue
Block a user