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

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

/*
 *  FolderFrameImpl.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 document

import javax.swing.SpinnerNumberModel
import javax.swing.undo.UndoableEdit

import de.sciss.desktop
import de.sciss.desktop.edit.CompoundEdit
import de.sciss.desktop.impl.UndoManagerImpl
import de.sciss.desktop.{FileDialog, Desktop, KeyStrokes, Menu, UndoManager, Window}
import de.sciss.lucre.expr.StringObj
import de.sciss.lucre.{expr, stm}
import de.sciss.lucre.stm.Obj
import de.sciss.lucre.swing.{CellView, deferTx}
import de.sciss.lucre.synth.Sys
import de.sciss.mellite.gui.edit.{EditFolderInsertObj, EditFolderRemoveObj}
import de.sciss.mellite.gui.impl.component.CollectionViewImpl
import de.sciss.swingplus.{GroupPanel, Spinner}
import de.sciss.synth.proc.{Folder, ObjKeys}

import scala.collection.breakOut
import scala.swing.Swing.EmptyIcon
import scala.swing.event.Key
import scala.swing.{Action, Alignment, CheckBox, Dialog, Label, Swing, TextField}

object FolderFrameImpl {
  def apply[S <: Sys[S]](name: CellView[S#Tx, String],
                         folder: Folder[S],
                         isWorkspaceRoot: Boolean)(implicit tx: S#Tx,
                         workspace: Workspace[S], cursor: stm.Cursor[S]): FolderFrame[S] = {
    implicit val undoMgr  = new UndoManagerImpl
    val folderView      = FolderView(folder)
    val interceptQuit   = isWorkspaceRoot && workspace.folder.isEmpty
    val view            = new ViewImpl[S](folderView)
    view.init()

    val res = new FrameImpl[S](view, name = name, isWorkspaceRoot = isWorkspaceRoot, interceptQuit = interceptQuit)
    res.init()
    res
  }

  def addDuplicateAction[S <: Sys[S]](w: WindowImpl[S], action: Action): Unit =
    Application.windowHandler.menuFactory.get("edit") match {
      case Some(mEdit: Menu.Group) =>
        val itDup = Menu.Item("duplicate", action)
        mEdit.add(Some(w.window), itDup)    // XXX TODO - should be insert-after "Select All"
      case _ =>
    }

  private def addImportJSONAction[S <: Sys[S]](w: WindowImpl[S], action: Action): Unit =
    Application.windowHandler.menuFactory.get("actions") match {
      case Some(mEdit: Menu.Group) =>
        val itDup = Menu.Item("import-json", action)
        mEdit.add(Some(w.window), itDup)
      case _ =>
    }

  private final class FrameImpl[S <: Sys[S]](val view: ViewImpl[S], name: CellView[S#Tx, String],
                                             isWorkspaceRoot: Boolean, interceptQuit: Boolean)
    extends WindowImpl[S](name) with FolderFrame[S] {

    def workspace = view.workspace

    def folderView = view.peer

    private var quitAcceptor = Option.empty[() => Boolean]

    override protected def initGUI(): Unit = {
      addDuplicateAction (this, view.actionDuplicate )
      addImportJSONAction(this, view.actionImportJSON)
      if (interceptQuit) quitAcceptor = Some(Desktop.addQuitAcceptor(checkClose()))
    }

    override protected def placement: (Float, Float, Int) = (0.5f, 0.0f, 20)

    override protected def checkClose(): Boolean = !interceptQuit ||
      ActionCloseAllWorkspaces.check(workspace, Some(window))

    override protected def performClose(): Unit =
      if (isWorkspaceRoot) {
        ActionCloseAllWorkspaces.close(workspace)
      } else {
        super.performClose()
      }

    override def dispose()(implicit tx: S#Tx): Unit = {
      super.dispose()
      deferTx {
        quitAcceptor.foreach(Desktop.removeQuitAcceptor)
      }
    }
  }

  final class ViewImpl[S <: Sys[S]](val peer: FolderView[S])
                                   (implicit val workspace: Workspace[S],
                                    val cursor: stm.Cursor[S], val undoManager: UndoManager)
    extends CollectionViewImpl[S]
    {

    impl =>

    protected type InsertConfig = Unit

    protected def prepareInsert(f: ObjView.Factory): Option[InsertConfig] = Some(())

    protected def editInsert(f: ObjView.Factory, xs: List[Obj[S]], config: InsertConfig)
                            (implicit tx: S#Tx): Option[UndoableEdit] = {
      val (parent, idx) = impl.peer.insertionPoint
      val edits: List[UndoableEdit] = xs.zipWithIndex.map { case (x, j) =>
        EditFolderInsertObj(f.prefix, parent, idx + j, x)
      } (breakOut)
      CompoundEdit(edits, "Create Objects")
    }

    def dispose()(implicit tx: S#Tx): Unit =
      peer.dispose()

    protected lazy val actionDelete: Action = Action(null) {
      val sel = peer.selection
      val edits: List[UndoableEdit] = cursor.step { implicit tx =>
        sel.flatMap { nodeView =>
          val parent = nodeView.parent
          val childH  = nodeView.modelData
          val child   = childH()
          val idx     = parent.indexOf(child)
          if (idx < 0) {
            println("WARNING: Parent folder of object not found")
            None
          } else {
            implicit val folderSer = Folder.serializer[S]
            val edit = EditFolderRemoveObj[S](nodeView.renderData.humanName, parent, idx, child)
            Some(edit)
          }
        }
      }
      val ceOpt = CompoundEdit(edits, "Delete Elements")
      ceOpt.foreach(undoManager.add)
    }

    protected def initGUI2(): Unit = {
      peer.addListener {
        case FolderView.SelectionChanged(_, sel) =>
          selectionChanged(sel.map(_.renderData))
          actionDuplicate.enabled = sel.nonEmpty
      }
    }

    lazy val actionImportJSON: Action = new Action("Import Mellite v0.3.x JSON...") {
      def apply(): Unit = {
        FileDialog.open(title = title.substring(0, title.length - 3)).show(None).foreach { f =>
//          val fi = new FileInputStream(f)
//          try {
//            val bytes = new Array[Byte](fi.available())
//            fi.read(bytes)
//            val json  = Json.parse(bytes)
            cursor.step { implicit tx => ImportJSON[S](impl.peer.root(), f /* json */) }
//          } finally {
//            fi.close()
//          }
        }
      }
    }

    lazy val actionDuplicate: Action = new Action("Duplicate...") {
      accelerator = Some(KeyStrokes.menu1 + Key.D)
      enabled     = impl.peer.selection.nonEmpty

      private var appendText  = "-1"
      private var count       = 1
      private var append      = true

      private def incLast(x: String, c: Int): String = {
        val p = "\\d+".r
        val m = p.pattern.matcher(x)
        var start = -1
        var end   = -1
        while (m.find()) {
          start = m.start()
          end   = m.end()
        }
        if (start < 0) x else {
          val (pre, tmp) = x.splitAt(start)
          val (old, suf) = tmp.splitAt(end - start)
          s"$pre${old.toInt + c}$suf"
        }
      }

      def apply(): Unit = {
        val sel     = impl.peer.selection
        val numSel  = sel.size
        if (numSel == 0) return

        sel.map(view => view.renderData.name)

        val txtInfo = s"Duplicate ${if (numSel == 1) s"'${sel.head.renderData.name}'" else s"$numSel Objects"}"
        val lbInfo  = new Label(txtInfo)
        lbInfo.border = Swing.EmptyBorder(0, 0, 8, 0)
        val ggName  = new TextField(6)
        ggName.text = appendText
        val mCount  = new SpinnerNumberModel(count, 1, 0x4000, 1)
        val ggCount = new Spinner(mCount)
        val lbName  = new CheckBox("Append to Name:")
        lbName.selected = append
        val lbCount = new Label("Number of Copies:" , EmptyIcon, Alignment.Right)

        val box = new GroupPanel {
          horizontal  = Par(
            lbInfo,
            Seq(Par(Trailing)(lbCount, lbName), Par(ggName , ggCount))
          )
          vertical = Seq(lbInfo, Par(Baseline)(lbName, ggName ), Par(Baseline)(lbCount, ggCount))
        }

        val pane = desktop.OptionPane.confirmation(box, optionType = Dialog.Options.OkCancel,
          messageType = Dialog.Message.Question, focus = Some(ggCount))
        pane.title  = "Duplicate"
        val window  = Window.find(component)
        val res     = pane.show(window)

        import de.sciss.equal.Implicits._
        if (res === Dialog.Result.Ok) {
          // save state
          count       = mCount.getNumber.intValue()
          appendText  = ggName.text
          append      = lbName.selected

          // lazy val regex = "\\d+".r // do _not_ catch leading minus character

          val edits: List[UndoableEdit] = cursor.step { implicit tx =>
            sel.flatMap { nodeView =>
              val p       = nodeView.parent
              val orig    = nodeView.modelData()
              val idx     = p.indexOf(orig)
              val copies  = List.tabulate(count) { n =>
                val cpy = Obj.copy(orig)
                if (append) {
                  val suffix = incLast(appendText, n)
                  orig.attr.$[StringObj](ObjKeys.attrName).foreach { oldName =>
                    // val imp = ExprImplicits[S]
                    import expr.Ops._
                    val newName = oldName ++ suffix
                    cpy.attr.put(ObjKeys.attrName, StringObj.newVar(newName))
                  }
                  // cpy.attr.name = s"${cpy.attr.name}$suffix"
                }
                EditFolderInsertObj("Copy", p, idx + n + 1, cpy)
              }
              copies
            }
          }
          val editOpt = CompoundEdit(edits, txtInfo)
          editOpt.foreach(undoManager.add)
        }
      }
    }

    def selectedObjects: List[ObjView[S]] = peer.selection.map(_.renderData)
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy