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

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

/*
 *  FolderViewImpl.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 java.io.File
import javax.swing.event.{CellEditorListener, ChangeEvent}
import javax.swing.undo.UndoableEdit
import javax.swing.{CellEditor, DropMode}

import de.sciss.desktop.UndoManager
import de.sciss.lucre.artifact.Artifact
import de.sciss.lucre.expr.StringObj
import de.sciss.lucre.stm
import de.sciss.lucre.stm.{Disposable, Obj}
import de.sciss.lucre.swing.TreeTableView.ModelUpdate
import de.sciss.lucre.swing.impl.ComponentHolder
import de.sciss.lucre.swing.{TreeTableView, deferTx}
import de.sciss.lucre.synth.Sys
import de.sciss.mellite.gui.edit.EditAttrMap
import de.sciss.model.impl.ModelImpl
import de.sciss.synth.proc.{Folder, ObjKeys}
import de.sciss.treetable.j.{DefaultTreeTableCellEditor, TreeTableCellEditor}
import de.sciss.treetable.{TreeTableCellRenderer, TreeTableSelectionChanged}

import scala.collection.breakOut
import scala.collection.immutable.{IndexedSeq => Vec}
import scala.swing.Component
import scala.util.control.NonFatal

object FolderViewImpl {
  def apply[S <: Sys[S]](root0: Folder[S])
                        (implicit tx: S#Tx, workspace: Workspace[S],
                         cursor: stm.Cursor[S], undoManager: UndoManager): FolderView[S] = {
    implicit val folderSer = Folder.serializer[S]

    new Impl[S] {
      val mapViews  = tx.newInMemoryIDMap[ObjView[S]]  // folder IDs to renderers
      val treeView  = TreeTableView[S, Obj[S], Folder[S], ListObjView[S]](root0, TTHandler)

      deferTx {
        guiInit()
      }
    }
  }

  private abstract class Impl[S <: Sys[S]](implicit val undoManager: UndoManager, val workspace: Workspace[S],
                                           val cursor: stm.Cursor[S])
    extends ComponentHolder[Component]
    with FolderView[S]
    with ModelImpl[FolderView.Update[S]]
    with FolderViewTransferHandler[S] {

    view =>

    private type Data     = ListObjView[S]
    private type NodeView = TreeTableView.NodeView[S, Obj[S], Folder[S], Data]

    protected object TTHandler
      extends TreeTableView.Handler[S, Obj[S], Folder[S], ListObjView[S]] {

      def branchOption(node: Obj[S]): Option[Folder[S]] = node match {
        case fe: Folder[S] => Some(fe)
        case _ => None
      }

      def children(branch: Folder[S])(implicit tx: S#Tx): Iterator[Obj[S]] =
        branch.iterator

      private def updateObjectName(obj: Obj[S], nameOption: Option[String])(implicit tx: S#Tx): Boolean = {
        treeView.nodeView(obj).exists { nv =>
          val objView = nv.renderData
          deferTx {
            objView.nameOption = nameOption
          }
          true
        }
      }

      private type MUpdate = ModelUpdate[Obj[S], Folder[S]]

      // XXX TODO - d'oh this because ugly
      def observe(obj: Obj[S], dispatch: S#Tx => MUpdate => Unit)
                 (implicit tx: S#Tx): Disposable[S#Tx] = {
        val objH      = tx.newHandle(obj)
        val objReact  = obj.changed.react { implicit tx => u1 =>
          // theoretically, we don't need to refresh the object,
          // because `treeView` uses an id-map for lookup.
          // however, there might be a problem with objects
          // created in the same transaction as the call to `observe`.
          //
          val obj = objH()
          val isDirty = treeView.nodeView(obj).exists { nv =>
            // val objView = nv.renderData
            false // XXX TODO RRR ELEM objView.isUpdateVisible(u1)
          }
          if (isDirty) dispatch(tx)(TreeTableView.NodeChanged(obj): MUpdate)
        }
        val attr      = obj.attr
        implicit val stringTpe = StringObj
        val nameView  = AttrCellView[S, String, StringObj](attr, ObjKeys.attrName)
        val attrReact = nameView.react { implicit tx => nameOpt =>
          val isDirty = updateObjectName(obj, nameOpt)
          if (isDirty) dispatch(tx)(TreeTableView.NodeChanged(obj): MUpdate)
        }

        val folderReact = obj match {
          case f: Folder[S] =>
            val res = f.changed.react { implicit tx => u2 =>
              u2.list.modifiableOption.foreach { folder =>
                val m = updateBranch(folder.asInstanceOf[Folder[S]] /* XXX TODO -- d'oh forgot this one */, u2.changes)
                m.foreach(dispatch(tx)(_))
              }
            }
            Some(res)

          case _ => None
        }

        new Disposable[S#Tx] {
          def dispose()(implicit tx: S#Tx): Unit = {
            objReact .dispose()
            attrReact.dispose()
            folderReact.foreach(_.dispose())
          }
        }
      }

      private def updateBranch(parent: Folder[S], changes: Vec[Folder.Change[S]])(implicit tx: S#Tx): Vec[MUpdate] =
        changes.flatMap {
          case Folder.Added  (idx, obj) => Vec(TreeTableView.NodeAdded  (parent, idx, obj): MUpdate)
          case Folder.Removed(idx, obj) => Vec(TreeTableView.NodeRemoved(parent, idx, obj): MUpdate)
        }

      private lazy val component = TreeTableCellRenderer.Default

      def renderer(tt: TreeTableView[S, Obj[S], Folder[S], Data], node: NodeView, row: Int, column: Int,
                   state: TreeTableCellRenderer.State): Component = {
        val data    = node.renderData
        val value1  = if (column == 0) data.name else "" // data.value
        // val value1  = if (value != {}) value else null
        val res = component.getRendererComponent(tt.treeTable, value1, row = row, column = column, state = state)
        if (column == 0) {
          if (row >= 0 && node.isLeaf) {
            try {
              // val node = t.getNode(row)
              component.icon = data.icon
            } catch {
              case NonFatal(_) => // XXX TODO -- currently NPE problems; seems renderer is called before tree expansion with node missing
            }
          }
          res // component
        } else {
          data.configureRenderer(component)
        }
      }

      private var editView    = Option.empty[ListObjView[S]]
      private var editColumn  = 0

      private lazy val defaultEditorJ = new javax.swing.JTextField
      private lazy val defaultEditor: TreeTableCellEditor = {
        val res = new DefaultTreeTableCellEditor(defaultEditorJ)
        res.addCellEditorListener(new CellEditorListener {
          def editingCanceled(e: ChangeEvent) = ()
          def editingStopped (e: ChangeEvent): Unit = editView.foreach { objView =>
            editView = None
            val editOpt: Option[UndoableEdit] = cursor.step { implicit tx =>
              val text = defaultEditorJ.getText
              if (editColumn == 0) {
                val valueOpt: Option[StringObj[S]] /* Obj[S] */ = if (text.isEmpty || text.toLowerCase == "") None else {
                  val expr = StringObj.newConst[S](text)
                  // Some(Obj(StringObj(elem)))
                  Some(expr)
                }
                // val ed = EditAttrMap[S](s"Rename ${objView.prefix} Element", objView.obj(), ObjKeys.attrName, valueOpt)
                implicit val stringTpe = StringObj
                val ed = EditAttrMap.expr[S, String, StringObj](s"Rename ${objView.humanName} Element", objView.obj, ObjKeys.attrName,
                  valueOpt) // (StringObj[S](_))
                Some(ed)
              } else {
                objView.tryEdit(text)
              }
            }
            editOpt.foreach(undoManager.add)
          }
        })
        res
      }
      private lazy val defaultEditorC = Component.wrap(defaultEditorJ)

      def isEditable(data: Data, column: Int): Boolean = column == 0 || data.isEditable

      val columnNames = Vec[String]("Name", "Value")

      def editor(tt: TreeTableView[S, Obj[S], Folder[S], Data], node: NodeView, row: Int, column: Int,
                 selected: Boolean): (Component, CellEditor) = {
        val data    = node.renderData
        editView    = Some(data)
        editColumn  = column
        val value   = if (column == 0) data.name else data.value.toString
        defaultEditor.getTreeTableCellEditorComponent(tt.treeTable.peer, value, selected, row, column)
        (defaultEditorC, defaultEditor)
      }

      def data(node: Obj[S])(implicit tx: S#Tx): Data = ListObjView(node)
    }

    protected def treeView: TreeTableView[S, Obj[S], Folder[S], ListObjView[S]]

    def dispose()(implicit tx: S#Tx): Unit = {
      treeView.dispose()
    }

    def root = treeView.root

    protected def guiInit(): Unit = {
      val t = treeView.treeTable
      t.rootVisible = false

      val tabCM = t.peer.getColumnModel
      tabCM.getColumn(0).setPreferredWidth(176)
      tabCM.getColumn(1).setPreferredWidth(272)

      t.listenTo(t.selection)
      t.reactions += {
        case e: TreeTableSelectionChanged[_, _] =>  // this crappy untyped event doesn't help us at all
          // println(s"selection: $e")
          dispatch(FolderView.SelectionChanged(view, selection))
        // case e => println(s"other: $e")
      }
      t.showsRootHandles  = true
      // t.expandPath(TreeTable.Path(_model.root))
      t.dragEnabled       = true
      t.dropMode          = DropMode.ON_OR_INSERT_ROWS
      t.peer.setTransferHandler(FolderTransferHandler)
      val tc        = treeView.component
//      tc.peer.putClientProperty("styleId", "nofocus")
      tc.peer.putClientProperty("styleId", "undecorated")
      component     = tc

    }

    def selection: FolderView.Selection[S] = treeView.selection

    def insertionPoint(implicit tx: S#Tx): (Folder[S], Int) = treeView.insertionPoint

    def locations: Vec[ArtifactLocationObjView[S]] = selection.flatMap { nodeView =>
      nodeView.renderData match {
        case view: ArtifactLocationObjView[S] => Some(view)
        case _ => None
      }
    } (breakOut)

    def findLocation(f: File): Option[ActionArtifactLocation.QueryResult[S]] = {
      val locationsOk = locations.flatMap { view =>
        try {
          Artifact.relativize(view.directory, f)
          Some(view)
        } catch {
          case NonFatal(_) => None
        }
      } .headOption

      locationsOk match {
        case Some(loc)  => Some(Left(loc.objH))
        case _          =>
          //          val parent = selection.flatMap { nodeView =>
          //            nodeView.renderData match {
          //              case f: ObjView.Folder[S] => Some(f.obj)
          //              case _ => None
          //            }
          //          } .headOption
          ActionArtifactLocation.query[S](treeView.root, file = f /*, folder = parent */) // , window = Some(comp))
      }
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy