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

de.sciss.mellite.gui.impl.timeline.DnD.scala Maven / Gradle / Ivy

/*
 *  DnD.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
package timeline

import java.awt.Point
import java.awt.datatransfer.{DataFlavor, Transferable}
import java.awt.dnd.{DropTarget, DropTargetAdapter, DropTargetDragEvent, DropTargetDropEvent, DropTargetEvent}
import javax.swing.TransferHandler._

import de.sciss.audiowidgets.TimelineModel
import de.sciss.file._
import de.sciss.lucre.stm
import de.sciss.lucre.stm.Sys
import de.sciss.lucre.synth.{Sys => SSys}
import de.sciss.span.Span
import de.sciss.synth.proc.{AudioCue, Proc}

import scala.swing.Component
import scala.util.Try

object DnD {
  sealed trait Drag[S <: Sys[S]] {
    def workspace: Workspace[S]
    // def source: stm.Source[S#Tx, Element[S]]
  }
  sealed trait AudioDragLike[S <: Sys[S]] extends Drag[S] {
    def selection: Span
  }
  final case class AudioDrag[S <: Sys[S]](workspace: Workspace[S], source: stm.Source[S#Tx, AudioCue.Obj[S]],
                                          selection: Span)
    extends AudioDragLike[S]

  //  final case class IntDrag [S <: Sys[S]](document: File, source: stm.Source[S#Tx, Obj.T[S, IntObj  ]]) extends Drag[S]
  //  final case class CodeDrag[S <: Sys[S]](document: File, source: stm.Source[S#Tx, Code.Obj[S]]) extends Drag[S]
  //  final case class ProcDrag[S <: Sys[S]](document: File, source: stm.Source[S#Tx, Proc[S]]) extends Drag[S]

  final case class GlobalProcDrag[S <: Sys[S]](workspace: Workspace[S], source: stm.Source[S#Tx, Proc[S]])
    extends Drag[S]

  final case class ObjectDrag[S <: SSys[S]](workspace: Workspace[S], view: ObjView[S]) extends Drag[S]

  /** Drag and Drop from Eisenkraut */
  final case class ExtAudioRegionDrag[S <: Sys[S]](workspace: Workspace[S], file: File, selection: Span)
    extends AudioDragLike[S]

  final case class Drop[S <: Sys[S]](frame: Long, y: Int, drag: Drag[S])

  final val flavor = DragAndDrop.internalFlavor[Drag[_]]
}
trait DnD[S <: SSys[S]] {
  _: Component =>

  import DnD._

  protected def workspace: Workspace[S]
  protected def timelineModel: TimelineModel

  protected def updateDnD(drop: Option[Drop[S]]): Unit
  protected def acceptDnD(drop:        Drop[S] ): Boolean

  private object Adaptor extends DropTargetAdapter {
    override def dragEnter(e: DropTargetDragEvent): Unit = process(e)
    override def dragOver (e: DropTargetDragEvent): Unit = process(e)
    override def dragExit (e: DropTargetEvent    ): Unit = updateDnD(None)

    private def abortDrag (e: DropTargetDragEvent): Unit = {
      updateDnD(None)
      e.rejectDrag()
    }

    private def mkDrop(d: DnD.Drag[S], loc: Point): Drop[S] = {
      val visi  = timelineModel.visible
      val w     = peer.getWidth
      val frame = (loc.x.toDouble / w * visi.length + visi.start).toLong
      val y     = loc.y
      Drop(frame = frame, y = y, drag = d)
    }

    private def mkExtStringDrag(t: Transferable, isDragging: Boolean): Option[DnD.ExtAudioRegionDrag[S]] = {
      // stupid OS X doesn't give out the string data before drop actually happens
      if (isDragging) {
        return Some(ExtAudioRegionDrag(workspace, file(""), Span(0, 0)))
      }

      val data  = t.getTransferData(DataFlavor.stringFlavor)
      val str   = data.toString
      val arr   = str.split(":")
      if (arr.length == 3) {
        Try {
          val path = file(arr(0))
          val span = Span(arr(1).toLong, arr(2).toLong)
          ExtAudioRegionDrag[S](workspace, path, span)
        } .toOption
      } else None
    }

    private def acceptAndUpdate(e: DropTargetDragEvent, drag: Drag[S]): Unit = {
      val loc   = e.getLocation
      val drop  = mkDrop(drag, loc)
      updateDnD(Some(drop))
      e.acceptDrag(e.getDropAction) // COPY
    }

    private def isSupported(t: Transferable): Boolean =
      t.isDataFlavorSupported(DnD.flavor) ||
      t.isDataFlavorSupported(ListObjView.Flavor) ||
      t.isDataFlavorSupported(DataFlavor.stringFlavor)

    private def mkDrag(t: Transferable, isDragging: Boolean): Option[Drag[S]] =
      if (t.isDataFlavorSupported(DnD.flavor)) {
        t.getTransferData(DnD.flavor) match {
          case d: DnD.Drag[_] if d.workspace == workspace => Some(d.asInstanceOf[DnD.Drag[S]])
          case _ => None
        }
      } else if (t.isDataFlavorSupported(ListObjView.Flavor)) {
        t.getTransferData(ListObjView.Flavor) match {
          case ListObjView.Drag(ws, view) if ws == workspace =>
            Some(DnD.ObjectDrag(workspace, view.asInstanceOf[ObjView[S]]))
          case _ => None
        }

      } else if (t.isDataFlavorSupported(DataFlavor.stringFlavor)) {
        mkExtStringDrag(t, isDragging = isDragging)

      } else {
        None
      }

    private def process(e: DropTargetDragEvent): Unit = {
      val d = mkDrag(e.getTransferable, isDragging = true)
      // println(s"mkDrag = $d")
      d match {
        case Some(drag) => acceptAndUpdate(e, drag)
        case _          => abortDrag(e)
      }
    }

    def drop(e: DropTargetDropEvent): Unit = {
      updateDnD(None)

      val t = e.getTransferable
      if (!isSupported(t)) {
        e.rejectDrop()
        return
      }
      e.acceptDrop(e.getDropAction)
      mkDrag(t, isDragging = false) match {
        case Some(drag) =>
          val loc     = e.getLocation
          val drop    = mkDrop(drag, loc)
          val success = acceptDnD(drop)
          e.dropComplete(success)

        case _ => e.rejectDrop()
      }
    }
  }

  new DropTarget(peer, COPY | LINK, Adaptor)
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy