Search code examples
scalaubuntujavafxclipboardscalafx

JavaFX/ScalaFX & Clipboard: Cannot copy files?


Copying files does not work:

  def toClipboard(selectedLinesOnly: Boolean = false): Unit = {
    val clipboard = Clipboard.systemClipboard
    val content = new ClipboardContent
    val items: Iterable[FileRecord] = selectedLinesOnly match {
      case true => tableView.selectionModel.value.selectedItems.toSeq
      case false => tableView.items.value
    }
    val files = items.map(_.file)
    println(s"COPY $files")
    content.putFiles(files.toSeq)
    clipboard.content = content
  }

Output: [info] COPY [SFX][/tmp/test/a.txt, /tmp/test/b.txt]

No files to paste.

  def toClipboard(selectedLinesOnly: Boolean = false): Unit = {
    val clipboard = Clipboard.systemClipboard
    val content = new ClipboardContent
    val items: Iterable[FileRecord] = selectedLinesOnly match {
      case true => tableView.selectionModel.value.selectedItems.toSeq
      case false => tableView.items.value
    }
    val files = items.map(_.file.getPath)
    println(s"COPY $files")
    content.putFilesByPath(files.toSeq)
    clipboard.content = content
  }

Output: [info] COPY [SFX][/tmp/test/a.txt, /tmp/test/b.txt]

No files to paste.

  def toClipboard(selectedLinesOnly: Boolean = false): Unit = {
    val clipboard = Clipboard.systemClipboard
    val content = new ClipboardContent
    val items: Iterable[FileRecord] = selectedLinesOnly match {
      case true => tableView.selectionModel.value.selectedItems.toSeq
      case false => tableView.items.value
    }
    val files = items.map("file://" + _.file.getPath)
    println(s"COPY $files")
    content.putFilesByPath(files.toSeq)
    clipboard.content = content
  }

Output: [info] COPY [SFX][file:///tmp/test/a.txt, file:///tmp/test/b.txt]

No files to paste.

But copying the paths to the string clipboard is possible:

  def toClipboard(selectedLinesOnly: Boolean = false): Unit = {
    val clipboard = Clipboard.systemClipboard
    val content = new ClipboardContent
    val items: Iterable[FileRecord] = selectedLinesOnly match {
      case true => tableView.selectionModel.value.selectedItems.toSeq
      case false => tableView.items.value
    }
    val files = items.map(_.file.getPath)
    println(s"COPY $files")
    content.putString(files.mkString(" "))
    clipboard.content = content
  }

Now this is in my clipboard: "/tmp/test/a.txt /tmp/test/b.txt"

But I need it in the form of files, not a string.

How can I make copying files work in my application?

I am working with OpenJFX 8 on Ubuntu.


Solution

  • My solution which I tested successfully with Ubuntu:

    FilesTransferable.scala

    package myapp.clipboard
    
    import java.awt.datatransfer._
    
    case class FilesTransferable(files: Iterable[String]) extends Transferable {
      val clipboardString: String = "copy\n" + files.map(path => s"file://$path").mkString("\n")
      val dataFlavor = new DataFlavor("x-special/gnome-copied-files")
      def getTransferDataFlavors(): Array[DataFlavor] = {
        Seq(dataFlavor).toArray
      }
      def isDataFlavorSupported(flavor: DataFlavor): Boolean = {
        dataFlavor.equals(flavor)
      }
      @throws(classOf[UnsupportedFlavorException])
      @throws(classOf[java.io.IOException])
      def getTransferData(flavor: DataFlavor): Object = {
        if(flavor.getRepresentationClass() == classOf[java.io.InputStream]) {
          new java.io.ByteArrayInputStream(clipboardString.getBytes())
        } else {
          return null;
        }
      }
    }
    

    Clipboard.scala

    package myapp.clipboard
    
    import java.awt.Toolkit
    import java.awt.datatransfer._
    
    object Clipboard {
      def toClipboard(
        transferable: Transferable,
        lostOwnershipCallback: (Clipboard, Transferable) => Unit =
          { (clipboard: Clipboard, contents: Transferable) => Unit }
      ): Unit = {
        Toolkit.getDefaultToolkit.getSystemClipboard.setContents(
          transferable,
          new ClipboardOwner {
            def lostOwnership(clipboard: Clipboard, contents: Transferable): Unit = {
              lostOwnershipCallback(clipboard, contents)
            }
          }
        )
      }
    }
    

    Quite a lot code for such a basic functionality (and still not OS independent).

    Usage Example:

    val transferable = FilesTransferable(filePaths)
    myapp.clipboard.Clipboard.toClipboard(transferable, { (cb, contents) =>
      println(s"lost clipboard ownership (clipboard: $cb)")
    })
    

    If it is true that there still do not exist any solutions which are part of Java-AWT/Swing/JavaFX/Scala-Swing/ScalaFX (actually, I still can't believe it), my plan is to build an easy-to-use clipboard library which supports at least OS X, Ubuntu and Windows.