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

de.sciss.mellite.gui.impl.ObjViewImpl.scala Maven / Gradle / Ivy

/*
 *  ObjViewImpl.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

import java.awt.{Color => AWTColor}
import java.awt.geom.Path2D
import javax.swing.undo.UndoableEdit
import javax.swing.{Icon, SpinnerNumberModel, UIManager}

import de.sciss.audiowidgets.AxisFormat
import de.sciss.desktop
import de.sciss.desktop.OptionPane
import de.sciss.file._
import de.sciss.icons.raphael
import de.sciss.lucre.artifact.{Artifact => _Artifact}
import de.sciss.lucre.event.impl.ObservableImpl
import de.sciss.lucre.expr.{BooleanObj, DoubleObj, LongObj, StringObj}
import de.sciss.lucre.stm
import de.sciss.lucre.stm.{Disposable, Obj}
import de.sciss.lucre.swing.edit.EditVar
import de.sciss.lucre.swing.{View, Window, deferTx}
import de.sciss.lucre.synth.Sys
import de.sciss.mellite.gui.edit.EditFolderInsertObj
import de.sciss.mellite.gui.impl.component.PaintIcon
import de.sciss.mellite.gui.impl.document.NuagesFolderFrameImpl
import de.sciss.swingplus.{ColorChooser, GroupPanel, Spinner}
import de.sciss.synth.proc.Implicits._
import de.sciss.synth.proc.{Confluent, ObjKeys, TimeRef}

import scala.collection.breakOut
import scala.collection.immutable.{IndexedSeq => Vec}
import scala.swing.Swing.EmptyIcon
import scala.swing.{Action, Alignment, BorderPanel, Button, CheckBox, Component, Dialog, FlowPanel, GridPanel, Label, Swing, TextField}
import scala.util.Try

object ObjViewImpl {
  import java.lang.{String => _String}

  import de.sciss.mellite.{Color => _Color}
  import de.sciss.lucre.expr.{DoubleVector => _DoubleVector}
  import de.sciss.nuages.{Nuages => _Nuages}
  import de.sciss.synth.proc.{Ensemble => _Ensemble, FadeSpec => _FadeSpec, Folder => _Folder, Timeline => _Timeline}
  import de.sciss.synth.proc.{Grapheme => _Grapheme}

  import scala.{Boolean => _Boolean, Double => _Double, Long => _Long}

  def nameOption[S <: stm.Sys[S]](obj: Obj[S])(implicit tx: S#Tx): Option[_String] =
    obj.attr.$[StringObj](ObjKeys.attrName).map(_.value)

  // -------- String --------

  object String extends ListObjView.Factory {
    type E[~ <: stm.Sys[~]] = StringObj[~]
    val icon      = raphaelIcon(raphael.Shapes.Font)
    val prefix    = "String"
    def humanName = prefix
    def tpe = StringObj

    def category = ObjView.categPrimitives

    def mkListView[S <: Sys[S]](obj: StringObj[S])(implicit tx: S#Tx): ListObjView[S] = {
      val ex          = obj
      val value       = ex.value
      val isEditable  = ex match {
        case StringObj.Var(_)  => true
        case _            => false
      }
      val isViewable  = tx.isInstanceOf[Confluent.Txn]
      new String.Impl[S](tx.newHandle(obj), value, isEditable = isEditable, isViewable = isViewable).init(obj)
    }

    type Config[S <: stm.Sys[S]] = PrimitiveConfig[_String]

    def hasMakeDialog = true

    def initMakeDialog[S <: Sys[S]](workspace: Workspace[S], window: Option[desktop.Window])
                               (implicit cursor: stm.Cursor[S]): Option[Config[S]] = {
      val ggValue   = new TextField(20)
      ggValue.text  = "Value"
      primitiveConfig(window, tpe = prefix, ggValue = ggValue, prepare = Some(ggValue.text))
    }

    def makeObj[S <: Sys[S]](config: (_String, _String))(implicit tx: S#Tx): List[Obj[S]] = {
      val (name, value) = config
      val obj = StringObj.newVar(StringObj.newConst[S](value))
      obj.name = name
      obj :: Nil
    }

    final class Impl[S <: Sys[S]](val objH: stm.Source[S#Tx, StringObj[S]],
                                 var value: _String,
                                 override val isEditable: _Boolean, val isViewable: _Boolean)
      extends ListObjView[S]
      with ObjViewImpl.Impl[S]
      with ListObjViewImpl.SimpleExpr[S, _String, StringObj]
      with ListObjViewImpl.StringRenderer {

      type E[~ <: stm.Sys[~]] = StringObj[~]

      def factory = String

      val exprType = StringObj

      def convertEditValue(v: Any): Option[_String] = Some(v.toString)

      def expr(implicit tx: S#Tx) = objH()
    }
  }

  // -------- Long --------

  object Long extends ListObjView.Factory {
    type E[S <: stm.Sys[S]] = LongObj[S]
    val icon          = raphaelIcon(Shapes.IntegerNumber)  // XXX TODO
    val prefix        = "Long"
    def humanName     = prefix
    def tpe           = LongObj
    def hasMakeDialog = true

    def category = ObjView.categPrimitives

    def mkListView[S <: Sys[S]](obj: LongObj[S])(implicit tx: S#Tx): ListObjView[S] = {
      val ex          = obj
      val value       = ex.value
      val isEditable  = ex match {
        case LongObj.Var(_)  => true
        case _            => false
      }
      val isViewable  = tx.isInstanceOf[Confluent.Txn]
      new Long.Impl[S](tx.newHandle(obj), value, isEditable = isEditable, isViewable = isViewable).init(obj)
    }

    type Config[S <: stm.Sys[S]] = PrimitiveConfig[_Long]

    def initMakeDialog[S <: Sys[S]](workspace: Workspace[S], window: Option[desktop.Window])
                               (implicit cursor: stm.Cursor[S]): Option[Config[S]] = {
      val model     = new SpinnerNumberModel(0L, _Long.MinValue, _Long.MaxValue, 1L)
      val ggValue   = new Spinner(model)
      primitiveConfig[S, _Long](window, tpe = prefix, ggValue = ggValue, prepare = Some(model.getNumber.longValue()))
    }

    def makeObj[S <: Sys[S]](config: (String, _Long))(implicit tx: S#Tx): List[Obj[S]] = {
      val (name, value) = config
      val obj = LongObj.newVar(LongObj.newConst[S](value))
      obj.name = name
      obj :: Nil
    }

    final class Impl[S <: Sys[S]](val objH: stm.Source[S#Tx, LongObj[S]],
                                  var value: _Long,
                                  override val isEditable: _Boolean, val isViewable: _Boolean)
      extends ListObjView /* .Long */[S]
      with ObjViewImpl.Impl[S]
      with ListObjViewImpl.SimpleExpr[S, _Long, LongObj]
      with ListObjViewImpl.StringRenderer {

      type E[~ <: stm.Sys[~]] = LongObj[~]

      def factory = Long

      val exprType = LongObj

      def expr(implicit tx: S#Tx): LongObj[S] = objH()

      def convertEditValue(v: Any): Option[_Long] = v match {
        case num: _Long => Some(num)
        case s: _String => Try(s.toLong).toOption
      }
    }
  }

  // -------- Double --------

  object Double extends ListObjView.Factory {
    type E[S <: stm.Sys[S]] = DoubleObj[S]
    val icon          = raphaelIcon(Shapes.RealNumber)
    val prefix        = "Double"
    def humanName     = prefix
    def tpe           = DoubleObj
    def hasMakeDialog = true

    def category = ObjView.categPrimitives

    def mkListView[S <: Sys[S]](obj: DoubleObj[S])(implicit tx: S#Tx): ListObjView[S] = {
      val ex          = obj
      val value       = ex.value
      val isEditable  = ex match {
        case DoubleObj.Var(_)  => true
        case _            => false
      }
      val isViewable  = tx.isInstanceOf[Confluent.Txn]
      new Double.Impl[S](tx.newHandle(obj), value, isEditable = isEditable, isViewable = isViewable).init(obj)
    }

    type Config[S <: stm.Sys[S]] = PrimitiveConfig[_Double]

    def initMakeDialog[S <: Sys[S]](workspace: Workspace[S], window: Option[desktop.Window])
                               (implicit cursor: stm.Cursor[S]): Option[Config[S]] = {
      val model     = new SpinnerNumberModel(0.0, _Double.NegativeInfinity, _Double.PositiveInfinity, 1.0)
      val ggValue   = new Spinner(model)
      primitiveConfig(window, tpe = prefix, ggValue = ggValue, prepare = Some(model.getNumber.doubleValue))
    }

    def makeObj[S <: Sys[S]](config: (String, _Double))(implicit tx: S#Tx): List[Obj[S]] = {
      val (name, value) = config
      val obj = DoubleObj.newVar(DoubleObj.newConst[S](value))
      obj.name = name
      obj :: Nil
    }

    final class Impl[S <: Sys[S]](val objH: stm.Source[S#Tx, DoubleObj[S]], var value: _Double,
                                  override val isEditable: _Boolean, val isViewable: _Boolean)
      extends ListObjView /* .Double */[S]
      with ObjViewImpl.Impl[S]
      with ListObjViewImpl.SimpleExpr[S, _Double, DoubleObj]
      with ListObjViewImpl.StringRenderer {

      type E[~ <: stm.Sys[~]] = DoubleObj[~]

      def factory = Double

      val exprType = DoubleObj

      def expr(implicit tx: S#Tx): DoubleObj[S] = objH()

      def convertEditValue(v: Any): Option[_Double] = v match {
        case num: _Double => Some(num)
        case s: _String => Try(s.toDouble).toOption
      }
    }
  }

  // -------- Boolean --------

  object Boolean extends ListObjView.Factory {
    type E[S <: stm.Sys[S]] = BooleanObj[S]
    val icon      = raphaelIcon(Shapes.BooleanNumber)
    val prefix    = "Boolean"
    def humanName = prefix
    def tpe   = BooleanObj
    def hasMakeDialog = true

    def category = ObjView.categPrimitives

    def mkListView[S <: Sys[S]](obj: BooleanObj[S])(implicit tx: S#Tx): ListObjView[S] = {
      val ex          = obj
      val value       = ex.value
      val isEditable  = ex match {
        case BooleanObj.Var(_)  => true
        case _            => false
      }
      val isViewable  = tx.isInstanceOf[Confluent.Txn]
      new Boolean.Impl[S](tx.newHandle(obj), value, isEditable = isEditable, isViewable = isViewable).init(obj)
    }

    type Config[S <: stm.Sys[S]] = PrimitiveConfig[_Boolean]

    def initMakeDialog[S <: Sys[S]](workspace: Workspace[S], window: Option[desktop.Window])
                               (implicit cursor: stm.Cursor[S]): Option[Config[S]] = {
      val ggValue = new CheckBox()
      primitiveConfig[S, _Boolean](window, tpe = prefix, ggValue = ggValue, prepare = Some(ggValue.selected))
    }

    def makeObj[S <: Sys[S]](config: (String, _Boolean))(implicit tx: S#Tx): List[Obj[S]] = {
      val (name, value) = config
      val obj = BooleanObj.newVar(BooleanObj.newConst[S](value))
      obj.name = name
      obj :: Nil
    }

    final class Impl[S <: Sys[S]](val objH: stm.Source[S#Tx, BooleanObj[S]],
                                  var value: _Boolean,
                                  override val isEditable: _Boolean, val isViewable: Boolean)
      extends ListObjView /* .Boolean */[S]
      with ObjViewImpl.Impl[S]
      with ListObjViewImpl.BooleanExprLike[S]
      with ListObjViewImpl.SimpleExpr[S, _Boolean, BooleanObj] {

      type E[~ <: stm.Sys[~]] = BooleanObj[~]

      def factory = Boolean

      def expr(implicit tx: S#Tx): BooleanObj[S] = objH()
    }
  }

  // -------- DoubleVector --------

  object DoubleVector extends ListObjView.Factory {
    type E[S <: stm.Sys[S]] = _DoubleVector[S]
    val icon          = raphaelIcon(Shapes.RealNumberVector)
    val prefix        = "DoubleVector"
    def humanName     = prefix
    def tpe           = _DoubleVector
    def hasMakeDialog = true

    def category = ObjView.categPrimitives

    def mkListView[S <: Sys[S]](obj: _DoubleVector[S])(implicit tx: S#Tx): ListObjView[S] = {
      val ex          = obj
      val value       = ex.value
      val isEditable  = ex match {
        case _DoubleVector.Var(_)  => true
        case _            => false
      }
      val isViewable  = tx.isInstanceOf[Confluent.Txn]
      new DoubleVector.Impl[S](tx.newHandle(obj), value, isEditable = isEditable, isViewable = isViewable).init(obj)
    }

    type Config[S <: stm.Sys[S]] = PrimitiveConfig[Vec[Double]]

    private def parseString(s: String): Option[Vec[Double]] =
      Try(s.split(" ").map(x => x.trim().toDouble)(breakOut): Vec[Double]).toOption

    def initMakeDialog[S <: Sys[S]](workspace: Workspace[S], window: Option[desktop.Window])
                                   (implicit cursor: stm.Cursor[S]): Option[Config[S]] = {
      val ggValue = new TextField("0.0 0.0")
      primitiveConfig(window, tpe = prefix, ggValue = ggValue, prepare = parseString(ggValue.text))
    }

    def makeObj[S <: Sys[S]](config: (String, Vec[Double]))(implicit tx: S#Tx): List[Obj[S]] = {
      val (name, value) = config
      val obj = _DoubleVector.newVar(_DoubleVector.newConst[S](value))
      obj.name = name
      obj :: Nil
    }

    final class Impl[S <: Sys[S]](val objH: stm.Source[S#Tx, _DoubleVector[S]], var value: Vec[Double],
                                  override val isEditable: _Boolean, val isViewable: _Boolean)
      extends ListObjView[S]
        with ObjViewImpl.Impl[S]
        with ListObjViewImpl.SimpleExpr[S, Vec[Double], _DoubleVector] {

      type E[~ <: stm.Sys[~]] = _DoubleVector[~]

      def factory = DoubleVector

      val exprType = _DoubleVector

      def expr(implicit tx: S#Tx): _DoubleVector[S] = objH()

      def convertEditValue(v: Any): Option[Vec[Double]] = v match {
        case num: Vec[_] => (Option(Vec.empty[Double]) /: num) {
          case (Some(prev), d: Double) => Some(prev :+ d)
          case _ => None
        }
        case s: _String  => DoubleVector.parseString(s)
      }

      def configureRenderer(label: Label): Component = {
        label.text = value.mkString(" ")
        label
      }
    }
  }

  // -------- Color --------

  object Color extends ListObjView.Factory {
    type E[~ <: stm.Sys[~]] = _Color.Obj[~]
    val icon      = raphaelIcon(raphael.Shapes.Paint)
    val prefix    = "Color"
    def humanName = prefix
    def tpe = _Color.Obj
    def category  = ObjView.categOrganisation

    def hasMakeDialog = true

    def mkListView[S <: Sys[S]](obj: _Color.Obj[S])(implicit tx: S#Tx): ListObjView[S] = {
      val ex          = obj
      val value       = ex.value
      val isEditable  = ex match {
        case _Color.Obj.Var(_)  => true
        case _            => false
      }
      new Color.Impl[S](tx.newHandle(obj), value, isEditable0 = isEditable).init(obj)
    }

    type Config[S <: stm.Sys[S]] = PrimitiveConfig[_Color]

    def initMakeDialog[S <: Sys[S]](workspace: Workspace[S], window: Option[desktop.Window])
                                   (implicit cursor: stm.Cursor[S]): Option[Config[S]] = {
      val (ggValue, ggChooser) = mkColorEditor()
      primitiveConfig[S, _Color](window, tpe = prefix, ggValue = ggValue, prepare = Some(fromAWT(ggChooser.color)))
    }

    private def mkColorEditor(): (Component, ColorChooser) = {
      val chooser = new ColorChooser()
      val bPredef = _Color.Palette.map { colr =>
        val action = new Action(null /* colr.name */) {
          private val awtColor = toAWT(colr)
          icon = new PaintIcon(awtColor, 32, 32)
          def apply(): Unit = chooser.color = awtColor
        }
        val b = new Button(action)
        // b.horizontalAlignment = Alignment.Left
        b.focusable = false
        b
      }
      val pPredef = new GridPanel(4, 4)
      pPredef.contents ++= bPredef
      val panel = new BorderPanel {
        add(pPredef, BorderPanel.Position.West  )
        add(chooser, BorderPanel.Position.Center)
      }
      (panel, chooser)
    }

    def toAWT(c: _Color): java.awt.Color = new java.awt.Color(c.rgba)
    def fromAWT(c: java.awt.Color): _Color = {
      val rgba = c.getRGB
      _Color.Palette.find(_.rgba == rgba).getOrElse(_Color.User(rgba))
    }

    def makeObj[S <: Sys[S]](config: (String, _Color))(implicit tx: S#Tx): List[Obj[S]] = {
      val (name, value) = config
      val obj = _Color.Obj.newVar(_Color.Obj.newConst[S](value))
      obj.name = name
      obj :: Nil
    }

    final class Impl[S <: Sys[S]](val objH: stm.Source[S#Tx, _Color.Obj[S]],
                                  var value: _Color, isEditable0: _Boolean)
      extends ListObjView /* .Color */[S]
      with ObjViewImpl.Impl[S]
      with ListObjViewImpl.SimpleExpr[S, _Color, _Color.Obj] {

      type E[~ <: stm.Sys[~]] = _Color.Obj[~]

      def isEditable = false    // not until we have proper editing components

      def factory = Color

      val exprType = _Color.Obj

      def expr(implicit tx: S#Tx): _Color.Obj[S] = objH()

      def configureRenderer(label: Label): Component = {
        // renderers are used for "stamping", so we can reuse a single object.
        label.icon = ListIcon
        ListIcon.paint = Color.toAWT(value)
        label
      }

      def convertEditValue(v: Any): Option[_Color] = v match {
        case c: _Color  => Some(c)
        case _          => None
      }

      def isViewable = isEditable0

      override def openView(parent: Option[Window[S]])
                           (implicit tx: S#Tx, workspace: Workspace[S], cursor: stm.Cursor[S]): Option[Window[S]] = {
//        val opt = OptionPane.confirmation(message = component, optionType = OptionPane.Options.OkCancel,
//          messageType = OptionPane.Message.Plain)
//        opt.show(parent) === OptionPane.Result.Ok
        val title = AttrCellView.name(obj)
        val w = new WindowImpl[S](title) { self =>
          val view: View[S] = View.wrap {
            val (compColor, chooser) = Color.mkColorEditor()
            chooser.color = Color.toAWT(value)
            val ggCancel = Button("Cancel") {
              closeMe() // self.handleClose()
            }

            def apply(): Unit = {
              val colr = Color.fromAWT(chooser.color)
              val editOpt = cursor.step { implicit tx =>
                objH() match {
                  case _Color.Obj.Var(vr) =>
                    implicit val colorTpe = _Color.Obj
                    Some(EditVar.Expr[S, _Color, _Color.Obj]("Change Color", vr, _Color.Obj.newConst[S](colr)))
                  case _ => None
                }
              }
              editOpt.foreach { edit =>
                parent.foreach { p =>
                  p.view match {
                    case e: View.Editable[S] => e.undoManager.add(edit)
                  }
                }
              }
            }

            val ggOk = Button("Ok") {
              apply()
              closeMe() // self.handleClose()
            }
            val ggApply = Button("Apply") {
              apply()
            }
            val pane = new BorderPanel {
              add(compColor, BorderPanel.Position.Center)
              add(new FlowPanel(ggOk, ggApply, Swing.HStrut(8), ggCancel), BorderPanel.Position.South)
            }
            pane
          }

          def closeMe(): Unit = cursor.step { implicit tx => self.dispose() }

          init()
        }
        Some(w)
      }
    }

    private val ListIcon = new PaintIcon(java.awt.Color.black, 48, 16)
  }

  // -------- Artifact --------

  object Artifact extends ListObjView.Factory {
    type E[~ <: stm.Sys[~]] = _Artifact[~]
    val icon      = raphaelIcon(raphael.Shapes.PagePortrait)
    val prefix    = "Artifact"
    def humanName = "File"
    def tpe = _Artifact
    def hasMakeDialog = false

    def category = ObjView.categResources

    def mkListView[S <: Sys[S]](obj: _Artifact[S])(implicit tx: S#Tx): ListObjView[S] = {
      val peer      = obj
      val value     = peer.value  // peer.child.path
      val editable  = false // XXX TODO -- peer.modifiableOption.isDefined
      new Artifact.Impl[S](tx.newHandle(obj), value, isEditable = editable).init(obj)
    }

    type Config[S <: stm.Sys[S]] = PrimitiveConfig[File]

    def initMakeDialog[S <: Sys[S]](workspace: Workspace[S], window: Option[desktop.Window])
                               (implicit cursor: stm.Cursor[S]): Option[Config[S]] = None // XXX TODO

    def makeObj[S <: Sys[S]](config: (_String, File))(implicit tx: S#Tx): List[Obj[S]] = ???!

    final class Impl[S <: Sys[S]](val objH: stm.Source[S#Tx, _Artifact[S]],
                                  var file: File, val isEditable: _Boolean)
      extends ListObjView /* .Artifact */[S]
      with ObjViewImpl.Impl[S]
      with ListObjViewImpl.StringRenderer
      with NonViewable[S] {

      type E[~ <: stm.Sys[~]] = _Artifact[~]

      def factory = Artifact

      def value   = file

      def init(obj: _Artifact[S])(implicit tx: S#Tx): this.type = {
        initAttrs(obj)
        disposables ::= obj.changed.react { implicit tx => upd =>
          deferAndRepaint {
            file = upd.now
          }
        }
        this
      }

      def tryEdit(value: Any)(implicit tx: S#Tx, cursor: stm.Cursor[S]): Option[UndoableEdit] = None // XXX TODO
    }
  }

  // -------- Folder --------

  object Folder extends ListObjView.Factory {
    type E[~ <: stm.Sys[~]] = _Folder[~]
    def icon      = UIManager.getIcon("Tree.openIcon")  // Swing.EmptyIcon
    val prefix    = "Folder"
    def humanName = prefix
    def tpe       = _Folder
    def category  = ObjView.categOrganisation

    def hasMakeDialog = true

    def mkListView[S <: Sys[S]](obj: _Folder[S])(implicit tx: S#Tx): ListObjView[S] =
      new Folder.Impl[S](tx.newHandle(obj)).initAttrs(obj)

    type Config[S <: stm.Sys[S]] = _String

    def initMakeDialog[S <: Sys[S]](workspace: Workspace[S],window: Option[desktop.Window])
                               (implicit cursor: stm.Cursor[S]): Option[Config[S]] = {
      val opt = OptionPane.textInput(message = s"Enter initial ${prefix.toLowerCase} name:",
        messageType = OptionPane.Message.Question, initial = prefix)
      opt.title = "New Folder"
      val res = opt.show(window)
      res
    }

    def makeObj[S <: Sys[S]](name: _String)(implicit tx: S#Tx): List[Obj[S]] = {
      val obj  = _Folder[S]
      obj.name = name
      obj :: Nil
    }

    // XXX TODO: could be viewed as a new folder view with this folder as root
    final class Impl[S <: Sys[S]](val objH: stm.Source[S#Tx, _Folder[S]])
      extends ListObjView /* .Folder */[S]
      with ObjViewImpl.Impl[S]
      with ListObjViewImpl.EmptyRenderer[S]
      with ListObjViewImpl.NonEditable[S] {

      type E[~ <: stm.Sys[~]] = _Folder[~]

      def factory = Folder

      def isViewable = true

      def openView(parent: Option[Window[S]])
                  (implicit tx: S#Tx, workspace: Workspace[S], cursor: stm.Cursor[S]): Option[Window[S]] = {
        val folderObj = objH()
        val nameView  = AttrCellView.name(folderObj)
        Some(FolderFrame(nameView, folderObj))
      }
    }
  }

  // -------- Timeline --------

  object Timeline extends ListObjView.Factory {
    type E[S <: stm.Sys[S]] = _Timeline[S]
    val icon      = raphaelIcon(raphael.Shapes.Ruler)
    val prefix    = "Timeline"
    def humanName = prefix
    def tpe = _Timeline
    def hasMakeDialog = true

    def category = ObjView.categComposition

    def mkListView[S <: Sys[S]](obj: _Timeline[S])(implicit tx: S#Tx): ListObjView[S] =
      new Timeline.Impl(tx.newHandle(obj)).initAttrs(obj)

    type Config[S <: stm.Sys[S]] = _String

    def initMakeDialog[S <: Sys[S]](workspace: Workspace[S], window: Option[desktop.Window])
                               (implicit cursor: stm.Cursor[S]): Option[Config[S]] = {
      val opt = OptionPane.textInput(message = s"Enter initial ${prefix.toLowerCase} name:",
        messageType = OptionPane.Message.Question, initial = prefix)
      opt.title = s"New $prefix"
      val res = opt.show(window)
      res
    }

    def makeObj[S <: Sys[S]](name: _String)(implicit tx: S#Tx): List[Obj[S]] = {
      val obj = _Timeline[S] // .Modifiable[S]
      obj.name = name
      obj :: Nil
    }

    final class Impl[S <: Sys[S]](val objH: stm.Source[S#Tx, _Timeline[S]])
      extends ListObjView /* .Timeline */[S]
      with ObjViewImpl.Impl[S]
      with ListObjViewImpl.EmptyRenderer[S]
      with ListObjViewImpl.NonEditable[S] {

      type E[~ <: stm.Sys[~]] = _Timeline[~]

      def factory = Timeline

      def isViewable = true

      def openView(parent: Option[Window[S]])
                  (implicit tx: S#Tx, workspace: Workspace[S], cursor: stm.Cursor[S]): Option[Window[S]] = {
        val frame = TimelineFrame[S](objH())
        Some(frame)
      }
    }
  }

  // -------- Grapheme --------

  object Grapheme extends ListObjView.Factory {
    type E[S <: stm.Sys[S]] = _Grapheme[S]
    val icon          = raphaelIcon(raphael.Shapes.LineChart)
    val prefix        = "Grapheme"
    def humanName     = prefix
    def tpe           = _Grapheme
    def hasMakeDialog = true
    def category      = ObjView.categComposition

    def mkListView[S <: Sys[S]](obj: _Grapheme[S])(implicit tx: S#Tx): ListObjView[S] =
      new Grapheme.Impl(tx.newHandle(obj)).initAttrs(obj)

    type Config[S <: stm.Sys[S]] = _String

    def initMakeDialog[S <: Sys[S]](workspace: Workspace[S], window: Option[desktop.Window])
                                   (implicit cursor: stm.Cursor[S]): Option[Config[S]] = {
      val opt = OptionPane.textInput(message = s"Enter initial ${prefix.toLowerCase} name:",
        messageType = OptionPane.Message.Question, initial = prefix)
      opt.title = s"New $prefix"
      val res = opt.show(window)
      res
    }

    def makeObj[S <: Sys[S]](name: _String)(implicit tx: S#Tx): List[Obj[S]] = {
      val obj = _Grapheme[S] // .Modifiable[S]
      obj.name = name
      obj :: Nil
    }

    final class Impl[S <: Sys[S]](val objH: stm.Source[S#Tx, _Grapheme[S]])
      extends ListObjView /* .Grapheme */[S]
        with ObjViewImpl.Impl[S]
        with ListObjViewImpl.EmptyRenderer[S]
        with ListObjViewImpl.NonEditable[S] {

      type E[~ <: stm.Sys[~]] = _Grapheme[~]

      def factory = Grapheme

      def isViewable = true

      def openView(parent: Option[Window[S]])
                  (implicit tx: S#Tx, workspace: Workspace[S], cursor: stm.Cursor[S]): Option[Window[S]] = {
        val frame = GraphemeFrame[S](objH())
        Some(frame)
      }
    }
  }

  // -------- FadeSpec --------

  object FadeSpec extends ListObjView.Factory {
    type E[~ <: stm.Sys[~]] = _FadeSpec.Obj[~]
    // val icon        = raphaelIcon(raphael.Shapes.Up)
    val icon        = raphaelIcon(Shapes.Aperture)
    val prefix      = "FadeSpec"
    val humanName   = "Fade"
    def tpe         = _FadeSpec.Obj
    def category    = ObjView.categComposition

    def hasMakeDialog   = false

    def mkListView[S <: Sys[S]](obj: _FadeSpec.Obj[S])(implicit tx: S#Tx): ListObjView[S] = {
      val value   = obj.value
      new FadeSpec.Impl[S](tx.newHandle(obj), value).init(obj)
    }

    type Config[S <: stm.Sys[S]] = Unit

    def initMakeDialog[S <: Sys[S]](workspace: Workspace[S], window: Option[desktop.Window])
                               (implicit cursor: stm.Cursor[S]): Option[Config[S]] = {
      None
//      val ggShape = new ComboBox()
//      Curve.cubed
//      val ggValue = new ComboBox(Seq(_Code.FileTransform.name, _Code.SynthGraph.name))
//      actionAddPrimitive(folderH, window, tpe = prefix, ggValue = ggValue, prepare = ...
//      ) { implicit tx =>
//        value =>
//          val peer = _FadeSpec.Expr(numFrames, shape, floor)
//          _FadeSpec.Obj(peer)
//      }
    }

    def makeObj[S <: Sys[S]](config: Config[S])(implicit tx: S#Tx): List[Obj[S]] = Nil

    private val timeFmt = AxisFormat.Time(hours = false, millis = true)

    final class Impl[S <: Sys[S]](val objH: stm.Source[S#Tx, _FadeSpec.Obj[S]], var value: _FadeSpec)
      extends ListObjView /* .FadeSpec */[S]
      with ObjViewImpl.Impl[S]
      with ListObjViewImpl.NonEditable[S]
      with NonViewable[S] {

      type E[~ <: stm.Sys[~]] = _FadeSpec.Obj[~]

      def factory = FadeSpec

      def init(obj: _FadeSpec.Obj[S])(implicit tx: S#Tx): this.type = {
        initAttrs(obj)
        disposables ::= obj.changed.react { implicit tx => upd =>
          deferAndRepaint {
            value = upd.now
          }
        }
        this
      }

      def configureRenderer(label: Label): Component = {
        val sr = TimeRef.SampleRate // 44100.0
        val dur = timeFmt.format(value.numFrames.toDouble / sr)
        label.text = s"$dur, ${value.curve}"
        label
      }
    }
  }

  // -------- Ensemble --------

  object Ensemble extends ListObjView.Factory {
    type E[~ <: stm.Sys[~]] = _Ensemble[~]
    val icon        = raphaelIcon(raphael.Shapes.Cube2)
    val prefix      = "Ensemble"
    def humanName   = prefix
    def tpe = _Ensemble
    def category    = ObjView.categComposition

    def hasMakeDialog   = true

    def mkListView[S <: Sys[S]](obj: _Ensemble[S])(implicit tx: S#Tx): ListObjView[S] = {
      val ens     = obj
      val playingEx = ens.playing
      val playing = playingEx.value
      val isEditable  = playingEx match {
        case BooleanObj.Var(_)  => true
        case _            => false
      }
      new Ensemble.Impl[S](tx.newHandle(obj), playing = playing, isEditable = isEditable).init(obj)
    }

    final case class Config[S <: stm.Sys[S]](name: String, offset: Long, playing: Boolean)

    def initMakeDialog[S <: Sys[S]](workspace: Workspace[S], window: Option[desktop.Window])
                               (implicit cursor: stm.Cursor[S]): Option[Config[S]] = {
      val ggName    = new TextField(10)
      ggName.text   = prefix
      val offModel  = new SpinnerNumberModel(0.0, 0.0, 1.0e6 /* _Double.MaxValue */, 0.1)
      val ggOff     = new Spinner(offModel)
      // doesn't work
      //      // using Double.MaxValue causes panic in spinner's preferred-size
      //      ggOff.preferredSize = new Dimension(ggName.preferredSize.width, ggOff.preferredSize.height)
      //      ggOff.maximumSize   = ggOff.preferredSize
      val ggPlay    = new CheckBox

      val lbName  = new Label(       "Name:", EmptyIcon, Alignment.Right)
      val lbOff   = new Label( "Offset [s]:", EmptyIcon, Alignment.Right)
      val lbPlay  = new Label(    "Playing:", EmptyIcon, Alignment.Right)

      val box = new GroupPanel {
        horizontal  = Seq(Par(Trailing)(lbName, lbOff, lbPlay), Par(ggName , ggOff, ggPlay))
        vertical    = Seq(Par(Baseline)(lbName, ggName),
          Par(Baseline)(lbOff , ggOff ),
          Par(Baseline)(lbPlay, ggPlay))
      }

      val pane = desktop.OptionPane.confirmation(box, optionType = Dialog.Options.OkCancel,
        messageType = Dialog.Message.Question, focus = Some(ggName))
      pane.title  = s"New $prefix"
      val res = pane.show(window)

      if (res != Dialog.Result.Ok) None else {
        val name      = ggName.text
        val seconds   = offModel.getNumber.doubleValue()
        val offset    = (seconds * TimeRef.SampleRate + 0.5).toLong
        val playing   = ggPlay.selected
        Some(Config(name = name, offset = offset, playing = playing))
      }
    }

    def makeObj[S <: Sys[S]](config: Config[S])(implicit tx: S#Tx): List[Obj[S]] = {
      val folder    = _Folder[S] // XXX TODO - can we ask the user to pick one?
      val offset    = LongObj   .newVar(LongObj   .newConst[S](config.offset ))
      val playing   = BooleanObj.newVar(BooleanObj.newConst[S](config.playing))
      val obj      = _Ensemble[S](folder, offset, playing)
      obj.name = config.name
      obj :: Nil
    }

    final class Impl[S <: Sys[S]](val objH: stm.Source[S#Tx, _Ensemble[S]],
                                  var playing: _Boolean, val isEditable: Boolean)
      extends ListObjView /* .Ensemble */[S]
      with ObjViewImpl.Impl[S]
      with ListObjViewImpl.BooleanExprLike[S] {

      type E[~ <: stm.Sys[~]] = _Ensemble[~]

      def factory = Ensemble

      def isViewable = true

      protected def exprValue: _Boolean = playing
      protected def exprValue_=(x: _Boolean): Unit = playing = x
      protected def expr(implicit tx: S#Tx): BooleanObj[S] = objH().playing

      def value: Any = ()

      def init(obj: _Ensemble[S])(implicit tx: S#Tx): this.type = {
        initAttrs(obj)
        disposables ::= obj.changed.react { implicit tx => upd =>
          upd.changes.foreach {
            case _Ensemble.Playing(ch) =>
              deferAndRepaint {
                playing = ch.now
              }

            case _ =>
          }
        }
        this
      }

      override def openView(parent: Option[Window[S]])
                           (implicit tx: S#Tx, workspace: Workspace[S], cursor: stm.Cursor[S]): Option[Window[S]] = {
        val ens   = objH()
        val w     = EnsembleFrame(ens)
        Some(w)
      }
    }
  }

  // -------- Nuages --------

  object Nuages extends ListObjView.Factory {
    type E[S <: stm.Sys[S]] = _Nuages[S]
    val icon        = raphaelIcon(raphael.Shapes.CloudWhite)
    val prefix      = "Nuages"
    val humanName   = "Wolkenpumpe"
    def tpe = _Nuages
    def hasMakeDialog   = true

    def category = ObjView.categComposition

    def mkListView[S <: Sys[S]](obj: _Nuages[S])(implicit tx: S#Tx): ListObjView[S] =
      new Nuages.Impl[S](tx.newHandle(obj)).initAttrs(obj)

    type Config[S <: stm.Sys[S]] = _String

    def initMakeDialog[S <: Sys[S]](workspace: Workspace[S], window: Option[desktop.Window])
                               (implicit cursor: stm.Cursor[S]): Option[Config[S]] = {
      val opt = OptionPane.textInput(message = s"Enter initial ${prefix.toLowerCase} name:",
        messageType = OptionPane.Message.Question, initial = prefix)
      opt.title = s"New $prefix"
      val res = opt.show(window)
      res
    }

    def makeObj[S <: Sys[S]](name: _String)(implicit tx: S#Tx): List[Obj[S]] = {
      val tl  = _Timeline[S]
      val obj = _Nuages[S](_Nuages.Surface.Timeline(tl))
      obj.name = name
      obj :: Nil
    }

    final class Impl[S <: Sys[S]](val objH: stm.Source[S#Tx, _Nuages[S]])
      extends ListObjView /* .Nuages */[S]
      with ObjViewImpl.Impl[S]
      with ListObjViewImpl.NonEditable[S]
      with ListObjViewImpl.EmptyRenderer[S] {

      type E[~ <: stm.Sys[~]] = _Nuages[~]

      def factory = Nuages

      def isViewable = true

      def openView(parent: Option[Window[S]])
                  (implicit tx: S#Tx, workspace: Workspace[S], cursor: stm.Cursor[S]): Option[Window[S]] = {
        val frame = NuagesFolderFrameImpl(objH())
        Some(frame)
      }
    }
  }

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

  def addObject[S <: Sys[S]](name: String, parent: _Folder[S], obj: Obj[S])
                            (implicit tx: S#Tx, cursor: stm.Cursor[S]): UndoableEdit = {
    // val parent = targetFolder
    // parent.addLast(obj)
    val idx = parent.size
    implicit val folderSer = _Folder.serializer[S]
    EditFolderInsertObj[S](name, parent, idx, obj)
  }

  type PrimitiveConfig[A] = (String, A)

  def primitiveConfig[S <: Sys[S], A](window: Option[desktop.Window], tpe: String, ggValue: Component,
                                      prepare: => Option[A]): Option[PrimitiveConfig[A]] = {
    val nameOpt = GUI.keyValueDialog(value = ggValue, title = s"New $tpe", defaultName = tpe, window = window)
    for {
      name  <- nameOpt
      value <- prepare
    } yield {
      (name, value)
    }
  }

  private[this] val colrIconDark = new AWTColor(200, 200, 200)

  def raphaelIcon(shape: Path2D => Unit): Icon = {
    val fill = if (Mellite.isDarkSkin) colrIconDark else AWTColor.black
    raphael.Icon(extent = 16, fill = fill)(shape)
  }

  trait Impl[S <: stm.Sys[S]] extends ObjView[S] /* with ModelImpl[ObjView.Update[S]] */
    with ObservableImpl[S, ObjView.Update[S]] {

    override def toString = s"ElementView.${factory.prefix}(name = $name)"

    def objH: stm.Source[S#Tx, Obj[S]]

    def obj(implicit tx: S#Tx): Obj[S] = objH()

    /** Forwards to factory. */
    def humanName: String = factory.humanName

    /** Forwards to factory. */
    def icon: Icon = factory.icon

    var nameOption : Option[String] = None
    var colorOption: Option[Color ] = None

    protected var disposables = List.empty[Disposable[S#Tx]]

    def dispose()(implicit tx: S#Tx): Unit = disposables.foreach(_.dispose())

    final protected def deferAndRepaint(body: => Unit)(implicit tx: S#Tx): Unit = {
      deferTx(body)
      fire(ObjView.Repaint(this))
    }

    /** Sets name and color. */
    def initAttrs(obj: Obj[S])(implicit tx: S#Tx): this.type = {
      val attr      = obj.attr

      implicit val stringTpe = StringObj
      val nameView  = AttrCellView[S, String, StringObj](attr, ObjKeys.attrName)
      disposables ::= nameView.react { implicit tx => opt =>
        deferAndRepaint {
          nameOption = opt
        }
      }
      nameOption   = nameView()

      implicit val colorTpe = _Color.Obj
      val colorView = AttrCellView[S, _Color, _Color.Obj](attr, ObjView.attrColor)
      disposables ::= colorView.react { implicit tx => opt =>
        deferAndRepaint {
          colorOption = opt
        }
      }
      colorOption  = colorView()
      this
    }
  }

  /** A trait that when mixed in provides `isViewable` and `openView` as non-op methods. */
  trait NonViewable[S <: stm.Sys[S]] {
    def isViewable: _Boolean = false

    def openView(parent: Option[Window[S]])
                (implicit tx: S#Tx, workspace: Workspace[S], cursor: stm.Cursor[S]): Option[Window[S]] = None
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy