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

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

/*
 *  TimelineActions.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.gui.impl.timeline

import javax.swing.undo.UndoableEdit

import de.sciss.desktop.edit.CompoundEdit
import de.sciss.desktop.{KeyStrokes, Window}
import de.sciss.lucre.expr.SpanLikeObj
import de.sciss.lucre.stm.Obj
import de.sciss.lucre.swing.edit.EditVar
import de.sciss.lucre.synth.Sys
import de.sciss.mellite.gui.edit.{EditAttrMap, EditTimelineInsertObj, Edits}
import de.sciss.mellite.gui.impl.ProcGUIActions
import de.sciss.mellite.gui.{ActionBounceTimeline, TimelineObjView, TimelineView}
import de.sciss.mellite.{Mellite, ProcActions}
import de.sciss.span.{Span, SpanLike}
import de.sciss.synth.proc
import de.sciss.synth.proc.{ObjKeys, Timeline}

import scala.swing.Action
import scala.swing.event.Key

/** Implements the actions defined for the timeline-view. */
trait TimelineActions[S <: Sys[S]] {
  _: TimelineView[S] =>

  object actionStopAllSound extends Action("StopAllSound") {
    def apply(): Unit =
      cursor.step { implicit tx =>
        transportView.transport.stop()  // XXX TODO - what else could we do?
        // auralView.stopAll
      }
  }

  object actionBounce extends Action("Bounce") {
    private var settings = ActionBounceTimeline.QuerySettings[S]()

    def apply(): Unit = {
      import ActionBounceTimeline._
      val window  = Window.find(component)
      val setUpd  = settings.copy(span = timelineModel.selection)
      implicit val undo = undoManager
      val (_settings, ok) = query(setUpd, workspace, timelineModel, window = window)
      settings = _settings
      _settings.file match {
        case Some(file) if ok =>
          import Mellite.compiler
          performGUI(workspace, _settings, timelineH, file, window = window)
        case _ =>
      }
    }
  }

  object actionDelete extends Action("Delete") {
    def apply(): Unit = {
      val editOpt = withSelection { implicit tx => views =>
        timelineMod.flatMap { groupMod =>
          ProcGUIActions.removeProcs(groupMod, views) // XXX TODO - probably should be replaced by Edits.unlinkAndRemove
        }
      }
      editOpt.foreach(undoManager.add)
    }
  }

  object actionSplitObjects extends Action("Split Selected Objects") {
    import KeyStrokes.menu2
    accelerator = Some(menu2 + Key.Y)
    enabled     = false

    def apply(): Unit = {
      val pos     = timelineModel.position
      val pos1    = pos - TimelineView.MinDur
      val pos2    = pos + TimelineView.MinDur
      val editOpt = withFilteredSelection(pv => pv.spanValue.contains(pos1) && pv.spanValue.contains(pos2)) { implicit tx =>
        splitObjects(pos)
      }
      editOpt.foreach(undoManager.add)
    }
  }

  object actionClearSpan extends Action("Clear Selected Span") {
    import KeyStrokes._
    accelerator = Some(menu1 + Key.BackSlash)
    enabled     = false

    def apply(): Unit =
      timelineModel.selection.nonEmptyOption.foreach { selSpan =>
        val editOpt = cursor.step { implicit tx =>
          timelineMod.flatMap { groupMod =>
            editClearSpan(groupMod, selSpan)
          }
        }
        editOpt.foreach(undoManager.add)
      }
  }

  object actionRemoveSpan extends Action("Remove Selected Span") {
    import KeyStrokes._
    accelerator = Some(menu1 + shift + Key.BackSlash)
    enabled = false

    def apply(): Unit = {
      timelineModel.selection.nonEmptyOption.foreach { selSpan =>
        val minStart = timelineModel.bounds.start
        val editOpt = cursor.step { implicit tx =>
          timelineMod.flatMap { groupMod =>
            // ---- remove ----
            // - first call 'clear'
            // - then move everything right of the selection span's stop to the left
            //   by the selection span's length
            val editClear = editClearSpan(groupMod, selSpan)
            val affected  = groupMod.intersect(Span.From(selSpan.stop))
            val amount    = ProcActions.Move(deltaTime = -selSpan.length, deltaTrack = 0, copy = false)
            val editsMove = affected.flatMap {
              case (_ /* elemSpan */, elems) =>
                elems.flatMap { timed =>
                  Edits.move(timed.span, timed.value, amount, minStart = minStart)
                }
            } .toList

            CompoundEdit(editClear.toList ++ editsMove, title)
          }
        }
        editOpt.foreach(undoManager.add)
        timelineModel.modifiableOption.foreach { tlm =>
          tlm.selection = Span.Void
          tlm.position  = selSpan.start
        }
      }
    }
  }

  object actionAlignObjectsToCursor extends Action("Align Objects Start To Cursor") {
    enabled = false

    def apply(): Unit = {
      val pos = timelineModel.position
      val edits = withSelection { implicit tx => views =>
        val list = views.flatMap { view =>
          val span = view.span
          span.value match {
            case hs: Span.HasStart if hs.start != pos =>
              val delta   = pos - hs.start
              val amount  = ProcActions.Move(deltaTime = delta, deltaTrack = 0, copy = false)
              Edits.move(span, view.obj, amount = amount, minStart = 0L)
            case _ => None
          }
        }
        if (list.isEmpty) None else Some(list.toList)
      } .getOrElse(Nil)
      val editOpt = CompoundEdit(edits, title)
      editOpt.foreach(undoManager.add)
    }
  }

  // -----------

  protected def timelineMod(implicit tx: S#Tx): Option[Timeline.Modifiable[S]] =
    timeline.modifiableOption

  // ---- clear ----
  // - find the objects that overlap with the selection span
  // - if the object is contained in the span, remove it
  // - if the object overlaps the span, split it once or twice,
  //   then remove the fragments that are contained in the span
  protected def editClearSpan(groupMod: proc.Timeline.Modifiable[S], selSpan: Span)
                             (implicit tx: S#Tx): Option[UndoableEdit] = {
    val allEdits = groupMod.intersect(selSpan).flatMap {
      case (elemSpan, elems) =>
        elems.flatMap { timed =>
          if (selSpan contains elemSpan) {
            Edits.unlinkAndRemove(groupMod, timed.span, timed.value) :: Nil
          } else {
            timed.span match {
              case SpanLikeObj.Var(oldSpan) =>
                val (edits1, span2, obj2) = splitObject(groupMod, selSpan.start, oldSpan, timed.value)
                val edits3 = if (selSpan contains span2.value) edits1 else {
                  val (edits2, _, _) = splitObject(groupMod, selSpan.stop, span2, obj2)
                  edits1 ++ edits2
                }
                val edit4 = Edits.unlinkAndRemove(groupMod, span2, obj2)
                edits3 ++ List(edit4)

              case _ => Nil
            }
          }
        }
    } .toList
    CompoundEdit(allEdits, "Clear Span")
  }

  protected def splitObjects(time: Long)(views: TraversableOnce[TimelineObjView[S]])
                  (implicit tx: S#Tx): Option[UndoableEdit] = timelineMod.flatMap { groupMod =>
    val edits: List[UndoableEdit] = views.flatMap { pv =>
      pv.span match {
        case SpanLikeObj.Var(oldSpan) =>
          val (edits, _, _) = splitObject(groupMod, time, oldSpan, pv.obj)
          edits
        case _ => Nil
      }
    } .toList

    CompoundEdit(edits, "Split Objects")
  }

  private def splitObject(groupMod: proc.Timeline.Modifiable[S], time: Long, oldSpan: SpanLikeObj.Var[S],
                          obj: Obj[S])(implicit tx: S#Tx): (List[UndoableEdit], SpanLikeObj.Var[S], Obj[S]) = {
    // val imp = ExprImplicits[S]
    val leftObj   = obj // pv.obj()
    val rightObj  = ProcActions.copy[S](leftObj /*, Some(oldSpan) */)
    rightObj.attr.remove(ObjKeys.attrFadeIn)

    val oldVal    = oldSpan.value
    val rightSpan = oldVal match {
      case Span.HasStart(leftStart) =>
        val _rightSpan  = SpanLikeObj.newVar(oldSpan())
        val resize      = ProcActions.Resize(time - leftStart, 0L)
        val minStart    = timelineModel.bounds.start
        // println("----BEFORE RIGHT----")
        // debugPrintAudioGrapheme(rightObj)
        ProcActions.resize(_rightSpan, rightObj, resize, minStart = minStart)
        // println("----AFTER RIGHT ----")
        // debugPrintAudioGrapheme(rightObj)
        _rightSpan

      case Span.HasStop(rightStop) =>
        SpanLikeObj.newVar[S](Span(time, rightStop))
    }

    val editRemoveFadeOut = EditAttrMap("Remove Fade Out", leftObj, ObjKeys.attrFadeOut, None)

    val editLeftSpan: Option[UndoableEdit] = oldVal match {
      case Span.HasStop(rightStop) =>
        val minStart  = timelineModel.bounds.start
        val resize    = ProcActions.Resize(0L, time - rightStop)
        Edits.resize(oldSpan, leftObj, resize, minStart = minStart)

      case Span.HasStart(leftStart) =>
        val leftSpan  = Span(leftStart, time)
        // oldSpan()     = leftSpan
        implicit val spanLikeTpe = SpanLikeObj
        val edit = EditVar.Expr[S, SpanLike, SpanLikeObj]("Resize", oldSpan, leftSpan)
        Some(edit)
    }

    // group.add(rightSpan, rightObj)
    val editAdd = EditTimelineInsertObj("Region", groupMod, rightSpan, rightObj)

    // debugCheckConsistency(s"Split left = $leftObj, oldSpan = $oldVal; right = $rightObj, rightSpan = ${rightSpan.value}")
    val list1 = editAdd :: Nil
    val list2 = editLeftSpan.fold(list1)(_ :: list1)
    val list3 = editRemoveFadeOut :: list2
    (list3, rightSpan, rightObj)
  }

  protected def withSelection[A](fun: S#Tx => TraversableOnce[TimelineObjView[S]] => Option[A]): Option[A] =
    if (selectionModel.isEmpty) None else {
      val sel = selectionModel.iterator
      cursor.step { implicit tx => fun(tx)(sel) }
    }

  protected def withFilteredSelection[A](p: TimelineObjView[S] => Boolean)
                                      (fun: S#Tx => TraversableOnce[TimelineObjView[S]] => Option[A]): Option[A] = {
    val sel = selectionModel.iterator
    val flt = sel.filter(p)
    if (flt.hasNext) cursor.step { implicit tx => fun(tx)(flt) } else None
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy