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.code.CodeFrameImpl.scala Maven / Gradle / Ivy
/*
* CodeFrameImpl.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.code
import de.sciss.desktop.{Menu, Util}
import de.sciss.icons.raphael
import de.sciss.lucre.edit.{EditProgram, UndoManager}
import de.sciss.lucre.expr.CellView
import de.sciss.lucre.expr.graph.Ex
import de.sciss.lucre.swing.LucreSwing.deferTx
import de.sciss.lucre.swing.View
import de.sciss.lucre.swing.edit.EditVar
import de.sciss.lucre.swing.impl.ComponentHolder
import de.sciss.lucre.synth.Txn
import de.sciss.lucre.{BooleanObj, Disposable, Expr, Obj, Source, Txn => LTxn}
import de.sciss.mellite.edit.EditAddProcOutput
import de.sciss.mellite.impl.WorkspaceWindowImpl
import de.sciss.mellite.{ActionBounce, AttrMapView, CanBounce, CodeFrame, CodeView, GUI, ProcOutputsView, RunnerToggleButton, SplitPaneView, UniverseHandler, UniverseObjView, ViewState}
import de.sciss.model.Model
import de.sciss.proc.Code.Example
import de.sciss.proc.impl.MkSynthGraphSource
import de.sciss.proc.{Action, Code, Control, Proc, SoundProcesses, Universe, Widget}
import de.sciss.synth.SynthGraph
import de.sciss.synth.proc.graph.ScanOut
import java.awt.event.{ComponentAdapter, ComponentEvent, ComponentListener}
import scala.collection.immutable.{Seq => ISeq}
import scala.swing.{BorderPanel, Button, Component, FlowPanel, Orientation, TabbedPane}
import scala.util.control.NonFatal
object CodeFrameImpl extends CodeFrame.Companion {
def install(): Unit =
CodeFrame.peer = this
// ---- adapter for editing a Proc's source ----
override def proc[T <: Txn[T]](obj: Proc[T])
(implicit tx: T, universeHandler: UniverseHandler[T],
compiler: Code.Compiler): CodeFrame[T] =
universeHandler(obj, CodeFrame) {
import universeHandler.universe
val codeObj = mkSource(obj = obj, codeTpe = Code.Proc)({
val gv: SynthGraph = obj.graph.value
val txt = /*if (gv.isEmpty) "" else*/ try {
MkSynthGraphSource(gv)
} catch {
case NonFatal(ex) =>
s"// $ex"
}
if (txt.isEmpty)
Code.Proc.defaultSource
else
s"// source code automatically extracted\n\n$txt"
})
val swapObjOpt = codeObj.attr.$[Code.Obj](CodeView.attrSwap)
val built0 = swapObjOpt.isEmpty
val objH = tx.newHandle(obj)
val code0 = (swapObjOpt getOrElse codeObj).value match {
case cs: Code.Proc => cs
case other => sys.error(s"Proc source code does not produce SynthGraph: ${other.tpe.humanName}")
}
val handler = new CodeView.Handler[T, Unit, SynthGraph] {
override def in(): Unit = ()
override def save(in: Unit, out: SynthGraph)(implicit tx: T, undo: UndoManager[T]): Unit = {
val obj = objH()
// we automatically create missing outputs for the
// ScanOut elements we see here
lazy val oldKeys = obj.outputs.keys
val newKeys = out.sources.collect {
case ScanOut(key, _) if !oldKeys.contains(key) => key
}
val editName = "Change SynthGraph"
val editNameC = if (newKeys.isEmpty) editName else s"$editName and Outputs"
undo.capture(editNameC) {
EditVar.exprUndo[T, SynthGraph, Proc.GraphObj](editName, obj.graph,
Proc.GraphObj.newConst[T](out))
if (newKeys.nonEmpty) {
newKeys.iterator.foreach(EditAddProcOutput[T](obj, _))
}
}
}
def dispose()(implicit tx: T): Unit = ()
}
implicit val undo: UndoManager[T] = UndoManager()
val outputsView = ProcOutputsView [T](obj)
val attrView = AttrMapView [T](obj)
val viewPower = RunnerToggleButton[T](obj)
val rightView = SplitPaneView(attrView, outputsView, Orientation.Vertical)
newInstance(obj, objH, codeObj, code0, built0 = built0, handler = Some(handler), bottom = viewPower :: Nil,
rightViewOpt = Some(("In/Out", rightView)), canBounce = true,
)
}
// ---- adapter for editing a Control.Graph's source ----
override def control[T <: Txn[T]](obj: Control[T])
(implicit tx: T, universeHandler: UniverseHandler[T],
compiler: Code.Compiler): CodeFrame[T] =
universeHandler(obj, CodeFrame) {
import universeHandler.universe
val codeObj = mkSource(obj = obj, codeTpe = Code.Control)({
val gv: Control.Graph = obj.graph.value
if (gv.controls.isEmpty)
Code.Control.defaultSource
else
s"// Warning: source code could not be automatically extracted!\n\n"
})
val swapObjOpt = codeObj.attr.$[Code.Obj](CodeView.attrSwap)
val built0 = swapObjOpt.isEmpty
val objH = tx.newHandle(obj)
val code0 = (swapObjOpt getOrElse codeObj).value match {
case cs: Code.Control => cs
case other => sys.error(s"Control source code does not produce Control.Graph: ${other.tpe.humanName}")
}
val handler = new CodeView.Handler[T, Unit, Control.Graph] {
override def in(): Unit = ()
override def save(in: Unit, out: Control.Graph)(implicit tx: T, undo: UndoManager[T]): Unit = {
val obj = objH()
EditVar.exprUndo[T, Control.Graph, Control.GraphObj]("Change Control Graph", obj.graph,
Control.GraphObj.newConst[T](out))
}
def dispose()(implicit tx: T): Unit = ()
}
implicit val undo: UndoManager[T] = UndoManager()
val attrView = AttrMapView [T](obj)
val viewPower = RunnerToggleButton[T](obj)
val rightView = attrView // SplitPaneView(attrView, outputsView, Orientation.Vertical)
newInstance(obj, objH, codeObj, code0, built0 = built0, handler = Some(handler), bottom = viewPower :: Nil,
rightViewOpt = Some(("In/Out", rightView)), canBounce = true,
)
}
// ---- adapter for editing an Action.Graph's source ----
override def action[T <: Txn[T]](obj: Action[T])
(implicit tx: T, universeHandler: UniverseHandler[T],
compiler: Code.Compiler): CodeFrame[T] =
universeHandler(obj, CodeFrame) {
import universeHandler.universe
val codeObj = mkSource(obj = obj, codeTpe = Code.Action)({
val gv: Action.Graph = obj.graph.value
if (gv.controls.isEmpty)
Code.Action.defaultSource
else
s"// Warning: source code could not be automatically extracted!\n\n"
})
val swapObjOpt = codeObj.attr.$[Code.Obj](CodeView.attrSwap)
val built0 = swapObjOpt.isEmpty
val objH = tx.newHandle(obj)
val code0 = (swapObjOpt getOrElse codeObj).value match {
case cs: Code.Action => cs
case other => sys.error(s"Action source code does not produce Action.Graph: ${other.tpe.humanName}")
}
val handler = new CodeView.Handler[T, Unit, Action.Graph] {
override def in(): Unit = ()
override def save(in: Unit, out: Action.Graph)(implicit tx: T, undo: UndoManager[T]): Unit = {
val obj = objH()
EditVar.exprUndo[T, Action.Graph, Action.GraphObj]("Change Action Graph", obj.graph,
Action.GraphObj.newConst[T](out))
}
def dispose()(implicit tx: T): Unit = ()
}
implicit val undo: UndoManager[T] = UndoManager()
val attrView = AttrMapView[T](obj)
val viewPower = RunnerToggleButton[T](obj, isAction = true)
val rightView = attrView // SplitPaneView(attrView, outputsView, Orientation.Vertical)
newInstance(obj, objH, codeObj, code0, built0 = built0, handler = Some(handler), bottom = viewPower :: Nil,
rightViewOpt = Some(("In/Out", rightView)), canBounce = false,
)
}
// ---- adapter for editing an Expr.Program's source ----
override def program[T <: Txn[T], A, E[~ <: LTxn[~]] <: Expr[~, A]](obj: E[T] with Expr.Program[T, A])
(implicit tx: T, universeHandler: UniverseHandler[T],
compiler: Code.Compiler, tpe: Expr.Type[A, E]): CodeFrame[T] =
universeHandler(obj, CodeFrame) {
import universeHandler.universe
val codeTpe = Code.Program.find(tpe).getOrElse(sys.error(s"Now code type for $tpe"))
val codeObj = mkSource(obj = obj, codeTpe = codeTpe)()
val swapObjOpt = codeObj.attr.$[Code.Obj](CodeView.attrSwap)
val built0 = swapObjOpt.isEmpty
val objH = tx.newHandle(obj)(tpe.programFormat[T])
val code0: Code.T[Unit, Ex[A]] = (swapObjOpt getOrElse codeObj).value match {
case cs if cs.tpe == codeTpe => cs.asInstanceOf[codeTpe.Repr]
case other => sys.error(s"Expression source code does not produce program: ${other.tpe.humanName}")
}
val handler = new CodeView.Handler[T, Unit, Ex[A]] {
override def in(): Unit = ()
override def save(in: Unit, out: Ex[A])(implicit tx: T, undo: UndoManager[T]): Unit = {
val obj = objH()
EditProgram(s"Change ${codeTpe.humanName}", tpe)(obj, out)
}
def dispose()(implicit tx: T): Unit = ()
}
implicit val undo: UndoManager[T] = UndoManager()
val attrView = AttrMapView[T](obj)
val viewEval = View.wrap[T, Button] {
val actionEval = new swing.Action("Evaluate") { self =>
import universe.cursor
def apply(): Unit = {
val res = cursor.step { implicit tx =>
val obj = objH()
obj.value
}
println(res)
}
}
GUI.toolButton(actionEval, raphael.Shapes.Quote)
}
val bottom = viewEval :: Nil
val rightView = attrView // SplitPaneView(attrView, outputsView, Orientation.Vertical)
type In = Unit
type Out = Ex[A]
newInstance[T, In, Out](obj: Obj[T], objH, codeObj, code0, built0 = built0, handler = Some(handler),
bottom = bottom, numLines = 6, rightViewOpt = Some(("In", rightView)), canBounce = true,
)
}
// ---- general constructor ----
override def apply[T <: Txn[T]](obj: Code.Obj[T], bottom: ISeq[View[T]])
(implicit tx: T, handler: UniverseHandler[T], compiler: Code.Compiler): CodeFrame[T] =
apply[T](obj, bottom, canBounce = false)
def apply[T <: Txn[T]](obj: Code.Obj[T], bottom: ISeq[View[T]], canBounce: Boolean)
(implicit tx: T, universeHandler: UniverseHandler[T],
compiler: Code.Compiler): CodeFrame[T] =
universeHandler(obj, CodeFrame) {
val codeObj = obj
val swapObjOpt = codeObj.attr.$[Code.Obj](CodeView.attrSwap)
val built0 = swapObjOpt.isEmpty
val code0 = (swapObjOpt getOrElse codeObj).value
implicit val undo: UndoManager[T] = UndoManager()
val objH = tx.newHandle(obj)
newInstance[T, code0.In, code0.Out](
pObj = obj,
pObjH = objH,
cObj = obj,
code0 = code0.asInstanceOf[Code.T[code0.In, code0.Out]], // Scala 2.12 woes
built0 = built0,
handler = None,
bottom = bottom,
rightViewOpt = None,
debugMenuItems = Nil,
canBounce = canBounce
)
}
private class PlainView[T <: Txn[T]](objH: Source[T, Obj[T]], codeView: View[T],
rightViewOpt: Option[(String, View[T])],
showEditor: Boolean, bottom: ISeq[View[T]])
(implicit val universe: Universe[T],
val undoManager: UndoManager[T])
extends View.Editable[T] with UniverseObjView[T] with ComponentHolder[Component] {
type C = Component
override def obj(implicit tx: T): Obj[T] = objH()
override def viewState: Set[ViewState] = Set.empty
def init()(implicit tx: T): this.type = {
deferTx(initGUI())
this
}
private def initGUI(): Unit = {
val pane = rightViewOpt.fold[C](codeView.component) { case (rightTitle, rightView) =>
val _tabs = new TabbedPane
_tabs.peer.putClientProperty("styleId", "attached") // XXX TODO: obsolete
_tabs.focusable = false
val pageEditC = if (showEditor) codeView.component else {
new BorderPanel {
private def addEditor(): Unit =
add(codeView.component, BorderPanel.Position.Center)
lazy val cl: ComponentListener = new ComponentAdapter {
override def componentShown(e: ComponentEvent): Unit = {
if (peer.isShowing) {
peer.removeComponentListener(cl)
addEditor()
revalidate()
}
}
}
peer.addComponentListener(cl)
}
}
val pageEdit = new TabbedPane.Page("Editor" , pageEditC, null)
val pageRender = new TabbedPane.Page(rightTitle, rightView .component, null)
_tabs.pages += pageEdit
_tabs.pages += pageRender
Util.addTabNavigation(_tabs)
if (showEditor) {
codeView.component.requestFocus()
} else {
_tabs.selection.index = 1
// paneEdit.preferredSize = renderer.component.preferredSize
}
_tabs
}
component = if (bottom.isEmpty) pane else new BorderPanel {
add(pane, BorderPanel.Position.Center)
{
val botC = bottom.map(_.component)
val panelBottom = new FlowPanel(FlowPanel.Alignment.Trailing)(botC: _*)
panelBottom.hGap = 4
panelBottom.vGap = 2
add(panelBottom, BorderPanel.Position.South)
}
}
}
def dispose()(implicit tx: T): Unit = {
codeView.dispose()
rightViewOpt.foreach(_._2.dispose())
bottom.foreach(_.dispose())
}
}
private final class CanBounceView[T <: Txn[T]](objH: Source[T, Obj[T]], codeView: View[T],
rightViewOpt: Option[(String, View[T])],
showEditor: Boolean, bottom: ISeq[View[T]])
(implicit universe: Universe[T],
undoManager: UndoManager[T])
extends PlainView[T](objH, codeView, rightViewOpt, showEditor = showEditor, bottom = bottom) with CanBounce {
object actionBounce extends ActionBounce[T](this, objH)
}
// trying to minimize IntelliJ false error highlights
private final type CodeT[In0, Out0] = Code { type In = In0; type Out = Out0 }
/**
* @param rightViewOpt optional title and component for a second tab view
*/
def make[T <: Txn[T], In0, Out0](pObj : Obj[T],
pObjH : Source[T, Obj[T]],
cObj : Code.Obj[T],
code0 : CodeT[In0, Out0],
built0 : Boolean,
handler : Option[CodeView.Handler[T, In0, Out0]],
bottom : ISeq[View[T]],
rightViewOpt : Option[(String, View[T])] = None,
debugMenuItems : ISeq[swing.Action] = Nil,
canBounce : Boolean
)
(implicit tx: T, universeHandler: UniverseHandler[T],
undoManager: UndoManager[T], compiler: Code.Compiler): CodeFrame[T] =
universeHandler(pObj, CodeFrame) {
newInstance[T, In0, Out0](
pObj = pObj,
pObjH = pObjH,
cObj = cObj,
code0 = code0,
built0 = built0,
handler = handler,
bottom = bottom,
rightViewOpt = rightViewOpt,
debugMenuItems = debugMenuItems,
canBounce = canBounce
)
}
def titleView[T <: Txn[T]](obj: Obj[T], peer: CodeView[T, _], contextName: String)
(implicit tx: T): CellView[T, String] =
new TitleView[T](obj, peer, contextName = contextName, tx0 = tx)
private class TitleView[T <: Txn[T]](obj: Obj[T], peer: CodeView[T, _], contextName: String, tx0: T)
extends CellView[T, String] { title =>
private[this] val name = CellView.name(obj)(tx0)
private[this] val postfix = if (contextName.isEmpty) contextName else s" : $contextName Code"
private class React(fun: T => String => Unit, tx0: T) extends Disposable[T] {
private[this] val obsName = name.react { implicit tx => _ =>
fun(tx)(title())
} (tx0)
private def fireFromUI(): Unit = {
import peer.cursor
SoundProcesses.step[T]("Update title") { implicit tx =>
fun(tx)(title())
}
}
private[this] val obsPeer: Model.Listener[CodeView.Update] = {
case CodeView.DirtyChange(_) => fireFromUI()
case CodeView.BuiltChange(_) => fireFromUI()
}
deferTx {
peer.addListener(obsPeer)
} (tx0)
override def dispose()(implicit tx: T): Unit = {
obsName.dispose()
deferTx {
peer.removeListener(obsPeer)
}
}
}
override def react(fun: T => String => Unit)(implicit tx: T): Disposable[T] = new React(fun, tx)
override def apply()(implicit tx: T): String = {
val dirty = peer.dirty
val built = !dirty && peer.built
val prefix = if (dirty) "*" else if (built) "" else "~"
s"$prefix${name()}$postfix"
}
}
/** Same as `make`, but does not return existing views from handler */
def newInstance[T <: Txn[T], In0, Out0](pObj : Obj[T],
pObjH : Source[T, Obj[T]],
cObj : Code.Obj[T],
code0 : CodeT[In0, Out0],
built0 : Boolean,
handler : Option[CodeView.Handler[T, In0, Out0]],
bottom : ISeq[View[T]],
numLines : Int = 24,
rightViewOpt : Option[(String, View[T])] = None,
debugMenuItems : ISeq[swing.Action] = Nil,
canBounce : Boolean
)
(implicit tx: T, universeHandler: UniverseHandler[T],
undoManager: UndoManager[T], compiler: Code.Compiler): CodeFrame[T] = {
import universeHandler.universe
val showEditor = pObj.attr.$[BooleanObj](Widget.attrEditMode).forall(_.value)
val bottomCode = if (showEditor) bottom else Nil
val bottomView = if (showEditor) Nil else bottom
val codeView = CodeView(cObj, code0, built0 = built0, bottom = bottomCode, numLines = numLines)(handler)
val view = if (canBounce)
new CanBounceView (pObjH, codeView, rightViewOpt, showEditor = showEditor, bottom = bottomView)
else
new PlainView (pObjH, codeView, rightViewOpt, showEditor = showEditor, bottom = bottomView)
view.init()
val contextName = code0.tpe.humanName
val res = new FrameImpl(codeView = codeView, view = view, debugMenuItems = debugMenuItems,
examples = code0.tpe.examples
)
res.init().setTitle(titleView(pObj, codeView, contextName = contextName))
res
}
// ---- util ----
def mkSource[T <: Txn[T]](obj: Obj[T], codeTpe: Code.Type, key: String = Code.attrSource)(init: => String = codeTpe.defaultSource)
(implicit tx: T): Code.Obj[T] = {
// if there is no source code attached,
// create a new code object and add it to the attribute map.
// let's just do that without undo manager
val codeObj = obj.attr.get(key) match {
case Some(c: Code.Obj[T]) => c
case _ =>
val source = init
val code = Code(codeTpe.id, source)
val c = Code.Obj.newVar(Code.Obj.newConst[T](code))
obj.attr.put(key, c)
c
}
codeObj
}
// ---- frame impl ----
private final class FrameImpl[T <: Txn[T]](val codeView : CodeView[T, _],
val view : UniverseObjView[T],
debugMenuItems : ISeq[swing.Action],
examples : ISeq[Example]
)(implicit val handler: UniverseHandler[T])
extends CodeFrame[T] with WorkspaceWindowImpl[T] with CodeFrameBase[T] {
// XXX TODO
override def supportsNewWindow: Boolean = false
override def newWindow()(implicit tx: T): Repr[T] = throw new UnsupportedOperationException
override protected def initGUI(): Unit = {
super.initGUI()
if (debugMenuItems.nonEmpty) {
val mf = window.handler.menuFactory
mf.get("actions").foreach {
case g: Menu.Group =>
val winOpt = Some(window)
debugMenuItems.iterator.zipWithIndex.foreach { case (a, ai) =>
g.add(winOpt, Menu.Item(s"debug-${ai + 1}", a))
}
case _ =>
}
}
mkExamplesMenu(examples)
}
}
}