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

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

/*
 *  FolderViewImpl.scala
 *  (Mellite)
 *
 *  Copyright (c) 2012-2023 Hanns Holger Rutz. All rights reserved.
 *
 *  This software is published under the GNU Affero General Public License v3+
 *
 *
 *  For further information, please contact Hanns Holger Rutz at
 *  [email protected]
 */

package de.sciss.mellite.impl.document

import de.sciss.kollflitz.ISeq
import de.sciss.lucre.edit.UndoManager
import de.sciss.lucre.expr.CellView
import de.sciss.lucre.swing.LucreSwing.{deferTx, requireEDT}
import de.sciss.lucre.swing.TreeTableView
import de.sciss.lucre.swing.TreeTableView.ModelUpdate
import de.sciss.lucre.swing.impl.ComponentHolder
import de.sciss.lucre.synth.Txn
import de.sciss.lucre.{Artifact, Disposable, Folder, Obj, Source, StringObj, Txn => LTxn}
import de.sciss.mellite.FolderView.Selection
import de.sciss.mellite.edit.EditAttrMapExpr
import de.sciss.mellite.impl.state.TableViewState
import de.sciss.mellite.{ArtifactLocationObjView, FolderView, ObjListView, ObjView, ViewState, ActionArtifactLocation => Loc}
import de.sciss.model.impl.ModelImpl
import de.sciss.proc.{ObjKeys, Universe}
import de.sciss.treetable.j.{DefaultTreeTableCellEditor, TreeTableCellEditor}
import de.sciss.treetable.{TreeTableCellRenderer, TreeTableSelectionChanged}

import java.net.URI
import javax.swing.event.{CellEditorListener, ChangeEvent}
import javax.swing.{CellEditor, DropMode}
import scala.annotation.tailrec
import scala.collection.immutable.{IndexedSeq => Vec}
import scala.swing.Component
import scala.util.Try
import scala.util.control.NonFatal

object FolderViewImpl extends FolderView.Companion {
  def install(): Unit =
    FolderView.peer = this

  def apply[T <: Txn[T]](root0: Folder[T])
                        (implicit tx: T, universe: Universe[T], undoManager: UndoManager[T]): FolderView[T] =
    new Impl[T](root0, tx)

  def cleanSelection[T <: LTxn[T]](in: Selection[T]): Selection[T] = {
    type NodeView = FolderView.NodeView[T]
    type Sel      = Selection[T]

    @tailrec
    def loop(set: Set[NodeView], rem: Sel, res: Sel): Sel = rem match {
      case Nil => res
      case head :: tail =>
        head.parentView match {
          case Some(p) if set.contains(p) => loop(set = set       , rem = tail, res =         res)
          case _                          => loop(set = set + head, rem = tail, res = head :: res)
        }
    }

    @tailrec
    def countParents(n: NodeView, res: Int = 0): Int = n.parentView match {
      case None     => res
      case Some(p)  => countParents(p, res = res + 1)
    }

    val inS = in.sortBy(countParents(_))
    val resRev = loop(Set.empty, rem = inS, res = Nil)
    resRev.reverse
  }

  private final class Impl[T <: Txn[T]](root0: Folder[T], tx0: T)
                                       (implicit val undoManager: UndoManager[T], val universe: Universe[T])
    extends ComponentHolder[Component]
    with FolderView[T]
    with ModelImpl[FolderView.Update[T]]
    with FolderViewTransferHandler[T] {

    view =>

    override def obj(implicit tx: T): Folder[T] = root()

    override def viewState: Set[ViewState] = stateTable.entries()

    type C = Component

    private type Data     = ObjListView[T]
    private type NodeView = FolderView.NodeView[T]

    private object TTHandler
      extends TreeTableView.Handler[T, Obj[T], Folder[T], ObjListView[T]] {

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

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

      // side-effect: synchronize the view
      private def updateObjectName(obj: Obj[T], nameOption: Option[String])(implicit tx: T): Boolean = {
        val nvs = treeView.nodeViews(obj)
        nvs.nonEmpty && {
          nvs.foreach { nv =>
            val objView = nv.renderData
            deferTx {
              objView.nameOption = nameOption
            }
          }
          true
        }
      }

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

//      override def observeRoot(n: Folder[T], dispatch: T => MUpdate => Unit)(implicit tx: T): Disposable[T] =
//        observe(n, null, dispatch)

      override def observe(obj: Obj[T], data: Data, dispatch: T => MUpdate => Unit)(implicit tx: T): Disposable[T] = {
        val viewObs = data.react { implicit tx => {
          case ObjView.Repaint(_) =>
            val obj = data.obj  // refresh
            dispatch(tx)(TreeTableView.NodeChanged(obj): MUpdate)

          case _ =>
        }}

        val attr      = obj.attr
        val nameView  = CellView.attr[T, String, StringObj](attr, ObjKeys.attrName)
        val attrReact = nameView.react { implicit tx => nameOpt =>
          val obj     = data.obj  // refresh
          val isDirty = updateObjectName(obj, nameOpt)
          if (isDirty) dispatch(tx)(TreeTableView.NodeChanged(obj): MUpdate)
        }

        val folderReact = obj match {
          case f: Folder[T] =>
            val res = f.changed.react { implicit tx => u2 =>
              u2.list.modifiableOption.foreach { folder =>
                val m = updateBranch(folder, u2.changes)
                m.foreach(dispatch(tx)(_))
              }
            }
            res

          case _ => Disposable.empty[T]
        }

        Disposable.seq[T](viewObs, attrReact, folderReact, data)
      }

      private def updateBranch(parent: Folder[T], changes: Vec[Folder.Change[T]]): 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 isWebLaF = UIManager.getLookAndFeel.getID == "submin"

      private lazy val component = {
        val res = TreeTableCellRenderer.Default
        // XXX TODO: somehow has no effect?
//        res.peer.putClientProperty("styleId", "renderer")
        res
      }

      def renderer(tt: TreeTableView[T, Obj[T], Folder[T], Data], node: NodeView, row: Int, column: Int,
                   state: TreeTableCellRenderer.State): Component = {
        val data    = node.renderData
        val isFirst = column == 0
        val value1  = if (isFirst) data.name else "" // data.value
        // val value1  = if (value != {}) value else null
        // XXX TODO --- a bit ugly work-around for web-laf renderer
        // val state1 = if (!isFirst /*isWebLaF*/ && state.selected) state.copy(selected = false) else state
        val res = component.getRendererComponent(tt.treeTable, value1, row = row, column = column, state = state)
        if (isFirst) {
          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 {
          // XXX TODO --- this doesn't work yet
//          if (isWebLaF) component.opaque = false
          data.configureListCellRenderer(component)
        }
      }

      private var editView    = Option.empty[ObjListView[T]]
      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): Unit = ()
          def editingStopped (e: ChangeEvent): Unit = editView.foreach { objView =>
            editView = None
            cursor.step { implicit tx =>
              val text = defaultEditorJ.getText
              if (editColumn == 0) {
                val valueOpt: Option[StringObj[T]] /* Obj[T] */ = if (text.isEmpty || text.toLowerCase == "") None else {
                  val expr = StringObj.newConst[T](text)
                  Some(expr)
                }
                EditAttrMapExpr[T, String, StringObj](s"Rename ${objView.humanName} Element", objView.obj, ObjKeys.attrName,
                  valueOpt)
              } else {
                objView.tryEditListCell(text)
                ()
              }
            }
          }
        })
        res
      }
      private lazy val defaultEditorC = Component.wrap(defaultEditorJ)

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

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

      def editor(tt: TreeTableView[T, Obj[T], Folder[T], 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[T])(implicit tx: T): Data = ObjListView(node)
    }

    override def treeView: TreeTableView[T, Obj[T], Folder[T], ObjListView[T]] = _treeView

    /*private*/ val _treeView: TreeTableView[T, Obj[T], Folder[T], ObjListView[T]] = {
      implicit val tx: T = tx0
      TreeTableView(root0, TTHandler)
    }

    private val stateTable = new TableViewState[T]()

    {
      implicit val tx: T = tx0
      ViewState.map(root0).foreach(stateTable.init)
      deferTx {
        initGUI()
      }
    }

    override def dispose()(implicit tx: T): Unit =
      treeView.dispose()

    override def select(child: Obj[T])(implicit tx: T): Unit = {
      val t = treeView
      t.selection = t.nodeViews(child)
    }

    def root: Source[T, Folder[T]] = treeView.root

    private def initGUI(): Unit = {
      val tt = treeView.treeTable
      tt.rootVisible = false
      tt.rowHeight   = 22  // XXX TODO : times font scale

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

      tt.listenTo(tt.selection)
      tt.reactions += {
        case _: 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")
      }
      tt.showsRootHandles  = true
      // t.expandPath(TreeTable.Path(_model.root))
      tt.dragEnabled       = true
      tt.dropMode          = DropMode.ON_OR_INSERT_ROWS
      tt.peer.setTransferHandler(FolderTransferHandler)
      val tc        = treeView.component
//      tc.peer.putClientProperty("styleId", "nofocus")
      tc.peer.putClientProperty("styleId", "undecorated")

      val tj = tt.peer.getTableHeader.getTable
      stateTable.initGUI_J(tj)

      component = tc
    }

    override def selection: Selection[T] = _treeView.selection

    type SelectionImpl = List[_treeView.NodeView]

    override def selectionImpl: SelectionImpl = _treeView.selection

    override def insertionPoint(implicit tx: T): (Folder[T], Int) = treeView.insertionPoint

    override def insertionPointOf(sel: SelectionImpl)(implicit tx: T): (Folder[T], Int) =
      _treeView.insertionPointOf(sel)

    override def locations: Vec[ArtifactLocationObjView[T]] = locationsIt.toIndexedSeq

    private def locationsIt: Iterator[ArtifactLocationObjView[T]] = selection.iterator.flatMap { nodeView =>
      nodeView.renderData match {
        case view: ArtifactLocationObjView[T] => Some(view)
        case _ => None
      }
    }

    override def findLocation(uri: URI, suggestions: ISeq[Loc.QueryResult[T]]): Option[Loc.QueryResult[T]] = {
      requireEDT()

      def tryDir(dir: URI): Boolean =
        Try(Artifact.Value.relativize(dir, uri)).isSuccess

      val sugOk: Iterator[Loc.QueryResult[T]] = suggestions.iterator.filter { q => tryDir(q._2) }
      val locationsOk: Iterator[Loc.QueryResult[T]] = locationsIt.flatMap { view =>
        val dir = view.directory
        if (tryDir(dir)) Some((Left(view.objH), dir)) else None
      }

      val locationOpt = {
        val it = sugOk ++ locationsOk
        if (it.hasNext) Some(it.next()) else None // Scala 2.12: `nextOption` not defined
      }

      locationOpt.orElse {
        Loc.query[T](file = uri /*, folder = parent */)(implicit tx => treeView.root()) // , window = Some(comp))
      }
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy