All Downloads are FREE. Search and download functionalities are using the official Maven repository.

de.sciss.mellite.gui.impl.document.FolderViewTransferHandler.scala Maven / Gradle / Ivy

/*
 *  FolderViewTransferHandler.scala
 *  (Mellite)
 *
 *  Copyright (c) 2012-2016 Hanns Holger Rutz. All rights reserved.
 *
 *  This software is published under the GNU General Public License v3+
 *
 *
 *  For further information, please contact Hanns Holger Rutz at
 *  [email protected]
 */

package de.sciss.mellite
package gui
package impl.document

import java.awt.datatransfer.{DataFlavor, Transferable}
import java.io.File
import javax.swing.TransferHandler.TransferSupport
import javax.swing.undo.UndoableEdit
import javax.swing.{JComponent, TransferHandler}

import de.sciss.desktop.UndoManager
import de.sciss.desktop.edit.CompoundEdit
import de.sciss.lucre.stm
import de.sciss.lucre.stm.{Copy, Txn, Obj, Sys}
import de.sciss.lucre.swing.TreeTableView
import de.sciss.mellite.gui.edit.{EditFolderInsertObj, EditFolderRemoveObj}
import de.sciss.synth.io.{AudioFile, AudioFileSpec}
import de.sciss.synth.proc._

import scala.language.existentials
import scala.util.Try

/** Mixin that provides a transfer handler for the folder view. */
trait FolderViewTransferHandler[S <: Sys[S]] { fv =>
  protected def workspace       : Workspace[S]
  protected def undoManager     : UndoManager
  protected implicit def cursor : stm.Cursor[S]

  protected def treeView: TreeTableView[S, Obj[S], Folder[S], ListObjView[S]]
  protected def selection: FolderView.Selection[S]

  protected def findLocation(f: File): Option[ActionArtifactLocation.QueryResult[S]]

  protected object FolderTransferHandler extends TransferHandler {
    // ---- export ----

    override def getSourceActions(c: JComponent): Int =
      TransferHandler.COPY | TransferHandler.MOVE | TransferHandler.LINK // dragging only works when MOVE is included. Why?

    override def createTransferable(c: JComponent): Transferable = {
      val sel     = selection
      val trans0  = DragAndDrop.Transferable(FolderView.SelectionFlavor) {
        new FolderView.SelectionDnDData[S](fv.workspace, fv.cursor, sel)
      }
      val trans1 = if (sel.size == 1) {
        val _res = DragAndDrop.Transferable(ListObjView.Flavor) {
          new ListObjView.Drag[S](fv.workspace, sel.head.renderData)
        }
        DragAndDrop.Transferable.seq(trans0, _res)
      } else trans0

      trans1
    }

    // ---- import ----
    override def canImport(support: TransferSupport): Boolean =
      treeView.dropLocation match {
        case Some(tdl) =>
          val locOk = tdl.index >= 0 || (tdl.column == 0 && !tdl.path.lastOption.exists(_.isLeaf))
          if (locOk) {
            val isSelection = support.isDataFlavorSupported(FolderView.SelectionFlavor)
            if (isSelection) {
              val data = support.getTransferable.getTransferData(FolderView.SelectionFlavor)
                .asInstanceOf[FolderView.SelectionDnDData[_]]
              if (data.workspace != workspace) {
                // no linking between sessions
                support.setDropAction(TransferHandler.COPY)
              }
              true
            } else {
              support.isDataFlavorSupported(DataFlavor.javaFileListFlavor)
            }

          } else {
            false
          }

        case _ => false
      }

    // XXX TODO: not sure whether removal should be in exportDone or something
    private def insertData(sel: FolderView.Selection[S], newParent: Folder[S], idx: Int, dropAction: Int)
                          (implicit tx: S#Tx): Option[UndoableEdit] = {
      // println(s"insert into $parent at index $idx")

      import de.sciss.equal.Implicits._
      def isNested(c: Obj[S]): Boolean = c match {
        case objT: Folder[S] =>
          objT === newParent || objT.iterator.toList.exists(isNested)
        case _ => false
      }

      val isMove = dropAction === TransferHandler.MOVE
      val isCopy = dropAction === TransferHandler.COPY

      // make sure we are not moving a folder within itself (it will magically disappear :)
      val sel1 = if (!isMove) sel else sel.filterNot(nv => isNested(nv.modelData()))

      // if we move children within the same folder, adjust the insertion index by
      // decrementing it for any child which is above the insertion index, because
      // we will first remove all children, then re-insert them.
      val idx0 = if (idx >= 0) idx else newParent /* .children */ .size
      val idx1 = if (!isMove) idx0
      else idx0 - sel1.count { nv =>
        val isInNewParent = nv.parent === newParent
        val child = nv.modelData()
        isInNewParent && newParent.indexOf(child) <= idx0
      }

      val editRemove: List[UndoableEdit] = if (!isMove) Nil
      else sel1.flatMap { nv =>
        val parent: Folder[S] = nv.parent
        val childH = nv.modelData
        val idx = parent.indexOf(childH())
        if (idx < 0) {
          println("WARNING: Parent of drag object not found")
          None
        } else {
          val edit = EditFolderRemoveObj[S](nv.renderData.humanName, parent, idx, childH())
          Some(edit)
        }
      }

      val selZip = sel1.zipWithIndex
      val editInsert = if (isCopy) {
        val context = Copy[S, S]
        val res = selZip.map { case (nv, off) =>
          val in  = nv.modelData()
          val out = context(in)
          EditFolderInsertObj[S](nv.renderData.humanName, newParent, idx1 + off, child = out)
        }
        context.finish()
        res
      } else {
        selZip.map { case (nv, off) =>
          EditFolderInsertObj[S](nv.renderData.humanName, newParent, idx1 + off, child = nv.modelData())
        }
      }

      val edits: List[UndoableEdit] = editRemove ++ editInsert
      val name = sel1 match {
        case single :: Nil  => single.renderData.humanName
        case _              => "Elements"
      }

      val prefix = if (isMove) "Move" else if (isCopy) "Copy" else "Link"
      CompoundEdit(edits, s"$prefix $name")
    }

    private def importSelection(support: TransferSupport, parent: Folder[S], index: Int)
                               (implicit tx: S#Tx): Option[UndoableEdit] = {
      val data = support.getTransferable.getTransferData(FolderView.SelectionFlavor)
        .asInstanceOf[FolderView.SelectionDnDData[S]]

      // we have performed this check already before:
      // if (data.workspace === workspace) {
        insertData(data.selection, parent, idx = index, dropAction = support.getDropAction)
      // } else {
      //  None
      // }
    }

    private def copyData(support: TransferSupport): Option[UndoableEdit] = {
      // cf. https://stackoverflow.com/questions/20982681
      val data  = support.getTransferable.getTransferData(FolderView.SelectionFlavor)
        .asInstanceOf[FolderView.SelectionDnDData[In] forSome { type In <: Sys[In] }]
      copyData1(data)
    }

    private def copyData1[In <: Sys[In]](data: FolderView.SelectionDnDData[In]): Option[UndoableEdit] = {
      Txn.copy[In, S, Option[UndoableEdit]] { (txIn: In#Tx, tx: S#Tx) => {
        parentOption(tx).flatMap { case (parent, idx) =>
          copyData2(data.selection, parent, idx)(txIn, tx)
        }
      }} (data.cursor, fv.cursor)
    }

    private def copyData2[In <: Sys[In]](sel: FolderView.Selection[In], newParent: Folder[S], idx: Int)
                                       (implicit txIn: In#Tx, tx: S#Tx): Option[UndoableEdit] = {
      val idx1 = if (idx >= 0) idx else newParent.size

      val context = Copy[In, S]
      val edits = sel.zipWithIndex.map { case (nv, off) =>
        val in  = nv.modelData()
        val out = context(in)
        EditFolderInsertObj[S](nv.renderData.humanName, newParent, idx1 + off, child = out)
      }
      context.finish()
      val name = sel match {
        case single :: Nil  => single.renderData.humanName
        case _              => "Elements"
      }
      CompoundEdit(edits, s"Import $name From Other Workspace")
    }

    private def importFiles(support: TransferSupport, parent: Folder[S], index: Int)
                           (implicit tx: S#Tx): Option[UndoableEdit] = {
      import scala.collection.JavaConversions._
      val data: List[File] = support.getTransferable.getTransferData(DataFlavor.javaFileListFlavor)
        .asInstanceOf[java.util.List[File]].toList
      val tup: List[(File, AudioFileSpec)] = data.flatMap { f =>
        Try(AudioFile.readSpec(f)).toOption.map(f -> _)
      }
      val trip: List[(File, AudioFileSpec, ActionArtifactLocation.QueryResult[S])] =
        tup.flatMap { case (f, spec) =>
          findLocation(f).map { loc => (f, spec, loc) }
        }

      // damn, this is annoying threading of state
      val (_, edits: List[UndoableEdit]) = ((index, List.empty[UndoableEdit]) /: trip) {
        case ((idx0, list0), (f, spec, either)) =>
          ActionArtifactLocation.merge(either).fold((idx0, list0)) { case (xs, locM) =>
            val (idx2, list2) = ((idx0, list0) /: xs) { case ((idx1, list1), x) =>
              val edit1 = EditFolderInsertObj[S]("Location", parent, idx1, x)
              (idx1 + 1, list0 :+ edit1)
            }
            val obj   = ObjectActions.mkAudioFile(locM, f, spec)
            val edit2 = EditFolderInsertObj[S]("Audio File", parent, idx2, obj)
            (idx2 + 1, list2 :+ edit2)
          }
      }
      CompoundEdit(edits, "Insert Audio Files")
    }

    private def parentOption(implicit tx: S#Tx): Option[(Folder[S], Int)] =
      treeView.dropLocation.flatMap { tdl =>
        val parentOpt = tdl.path.lastOption.fold(Option(treeView.root())) { nodeView =>
          nodeView.modelData() match {
            case objT: Folder[S]  => Some(objT)
            case _                => None
          }
        }
        parentOpt.map(_ -> tdl.index)
      }

    override def importData(support: TransferSupport): Boolean =
      treeView.dropLocation.exists { tdl =>
        val editOpt = {
          val crossSession = support.isDataFlavorSupported(FolderView.SelectionFlavor) &&
            (support.getTransferable.getTransferData(FolderView.SelectionFlavor)
              .asInstanceOf[FolderView.SelectionDnDData[_]].workspace != workspace)

          if (crossSession)
            copyData(support)
          else cursor.step { implicit tx =>
            parentOption.flatMap { case (parent,idx) =>
              if (support.isDataFlavorSupported(FolderView.SelectionFlavor))
                importSelection(support, parent, idx)
              else if (support.isDataFlavorSupported(DataFlavor.javaFileListFlavor))
                importFiles(support, parent, idx)
              else None
            }
          }
        }
        editOpt.foreach(undoManager.add)
        editOpt.isDefined
      }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy