Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
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))
}
}
}
}