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

de.sciss.mellite.gui.impl.interpreter.CodeViewImpl.scala Maven / Gradle / Ivy

/*
 *  CodeViewImpl.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 interpreter

import java.awt.Color
import java.beans.{PropertyChangeEvent, PropertyChangeListener}
import javax.swing.Icon
import javax.swing.event.{UndoableEditEvent, UndoableEditListener}
import javax.swing.undo.UndoableEdit

import de.sciss.desktop.edit.CompoundEdit
import de.sciss.desktop.{KeyStrokes, UndoManager}
import de.sciss.icons.raphael
import de.sciss.lucre.expr.{Expr, StringObj}
import de.sciss.lucre.stm
import de.sciss.lucre.stm.Sys
import de.sciss.lucre.swing.edit.EditVar
import de.sciss.lucre.swing.impl.ComponentHolder
import de.sciss.lucre.swing.{defer, deferTx, requireEDT}
import de.sciss.model.impl.ModelImpl
import de.sciss.scalainterpreter.{CodePane, Interpreter, InterpreterPane}
import de.sciss.swingplus.Implicits._
import de.sciss.swingplus.SpinningProgressBar
import de.sciss.syntaxpane.SyntaxDocument
import de.sciss.synth.proc.Code

import scala.collection.mutable
import scala.concurrent.Future
import scala.swing.Swing._
import scala.swing.event.Key
import scala.swing.{Action, BorderPanel, Button, Component, FlowPanel, Swing}
import scala.util.{Failure, Success}
import scala.util.control.NonFatal

object CodeViewImpl {
  private val intpMap = mutable.WeakHashMap.empty[Int, Future[Interpreter]]

  /* We use one shared interpreter for all code frames of each context. */
  private def interpreter(id: Int): Future[Interpreter] = {
    requireEDT()
    intpMap.getOrElse(id, {
      val cfg     = Interpreter.Config()
      cfg.imports = Code.getImports(id)
      val res     = Interpreter.async(cfg)
      intpMap.put(id, res)
      res
    })
  }

  def apply[S <: Sys[S]](obj: Code.Obj[S], code0: Code, hasExecute: Boolean)
                        (handlerOpt: Option[CodeView.Handler[S, code0.In, code0.Out]])
                        (implicit tx: S#Tx, workspace: Workspace[S], cursor: stm.Cursor[S],
                         compiler: Code.Compiler,
                         undoManager: UndoManager): CodeView[S] = {
    // val source0 = sourceCode.value
    // val objH        = tx.newHandle(obj)
    val codeEx      = obj
    val codeVarHOpt = codeEx match {
      case Code.Obj.Var(vr) =>
        Some(tx.newHandle(vr))
      case _            => None
    }
    // val code0   = codeEx.value
    // val source0 = code0.source
    // val sourceH = tx.newHandle(sourceCode)(StringObj.varSerializer[S])
    val res     = new Impl[S, code0.In, code0.Out](codeVarHOpt, code0, handlerOpt, hasExecute = hasExecute)
    res.init()
    res
  }

  private final class Impl[S <: Sys[S], In0, Out0](codeVarHOpt: Option[stm.Source[S#Tx, Code.Obj.Var[S]]],
                                        private var code: Code { type In = In0; type Out = Out0 },
                                        handlerOpt: Option[CodeView.Handler[S, In0, Out0]],
                                        hasExecute: Boolean)
                                       (implicit undoManager: UndoManager, val workspace: Workspace[S],
                                        val cursor: stm.Cursor[S], compiler: Code.Compiler)
    extends ComponentHolder[Component] with CodeView[S] with ModelImpl[CodeView.Update] {

    // import ExecutionContext.Implicits.global

    private var _dirty = false
    def dirty = _dirty
    def dirty_=(value: Boolean): Unit = if (_dirty != value) {
      _dirty = value
      actionApply.enabled = value
      dispatch(CodeView.DirtyChange(value))
    }

    private type CodeT = Code { type In = In0; type Out = Out0 }

    private def loadText(idx: Int): Unit = {
      try {
        val inp  = io.Source.fromFile(s"codeview$idx.txt", "UTF-8")
        val text = inp.getLines().mkString("\n")
        inp.close()
        codePane.editor.text = text
      } catch {
        case NonFatal(e) => e.printStackTrace()
      }
    }

    private val codeCfg = {
      val b = CodePane.Config()
      b.text = code.source
      // XXX TODO - cheesy hack
      b.keyMap += (KeyStrokes.menu1 + Key.Key1 -> (() => loadText(1)))
      b.keyMap += (KeyStrokes.menu1 + Key.Key2 -> (() => loadText(2)))
      b.build
    }

    // import code.{id => codeID}

    private var codePane: CodePane = _
    private var futCompile = Option.empty[Future[Any]]
    private var actionApply: Action = _

    def isCompiling: Boolean = {
      requireEDT()
      futCompile.isDefined
    }

    protected def currentText: String = codePane.editor.text

    def dispose()(implicit tx: S#Tx) = ()

    def undoAction: Action = Action.wrap(codePane.editor.peer.getActionMap.get("undo"))
    def redoAction: Action = Action.wrap(codePane.editor.peer.getActionMap.get("redo"))

    private def saveSource(newSource: String)(implicit tx: S#Tx): Option[UndoableEdit] = {
      // val expr  = ExprImplicits[S]
      // import StringObj.{varSerializer, serializer}
      // val imp = ExprImplicits[S]
      codeVarHOpt.map { source =>
        val newCode = Code.Obj.newConst[S](code.updateSource(newSource))
        implicit val codeTpe = Code.Obj
        EditVar.Expr[S, Code, Code.Obj]("Change Source Code", source(), newCode)
      }
    }

    private def addEditAndClear(edit: UndoableEdit): Unit = {
      requireEDT()
      undoManager.add(edit)
      // this doesn't work properly
      // component.setDirty(value = false) // do not erase undo history

      // so let's clear the undo history now...
      codePane.editor.peer.getDocument.asInstanceOf[SyntaxDocument].clearUndos()
    }

    def save(): Future[Unit] = {
      requireEDT()
      val newCode = currentText
      if (handlerOpt.isDefined) {
        compileSource(newCode, save = true)
      } else {
        val editOpt = cursor.step { implicit tx =>
          saveSource(newCode)
        }
        editOpt.foreach(addEditAndClear)
        Future.successful[Unit] {}
      }
    }

    private def saveSourceAndObject(newCode: String, in: In0, out: Out0)(implicit tx: S#Tx): Option[UndoableEdit] = {
      val edit1 = saveSource(newCode)
      val edit2 = handlerOpt.map { handler =>
        handler.save(in, out)
      }
      val edits0  = edit2.toList
      val edits   = edit1.fold(edits0)(_ :: edits0)
      CompoundEdit(edits, "Save and Apply Code")
    }

    private def compile(): Unit = compileSource(currentText, save = false)

    private def compileSource(newCode: String, save: Boolean): Future[Unit] = {
      val saveObject = handlerOpt.isDefined && save
      if (futCompile.isDefined && !saveObject) return Future.successful[Unit] {}

      ggProgress          .spinning = true
      actionCompile       .enabled  = false
      if (saveObject) actionApply.enabled = false

      code = code.updateSource(newCode)

      val fut = handlerOpt match {
        case Some(handler) if save =>
          // val _fut = Library.compile(newCode)
          val _fut = Code.future {
            val in  = handler.in()
            val out = code.execute(in)
            (in, out)
          }
          _fut.onSuccess { case (in, out) =>
            defer {
              val editOpt = cursor.step { implicit tx =>
                saveSourceAndObject(newCode, in, out)
              }
              editOpt.foreach(addEditAndClear)
            }
          }
          _fut
        case _ =>
          code.compileBody()
      }

      futCompile = Some(fut)
      fut.onComplete { res =>
        defer {
          futCompile                    = None
          ggProgress          .spinning = false
          actionCompile       .enabled  = true
          if (saveObject) actionApply.enabled = true

          val iconColr = res match {
            case Success(_) =>
              clearGreen = true
              new Color(0x00, 0xC0, 0x00)                           // "\u2713"
            case Failure(Code.CompilationFailed()) => Color.red     // "error!"
            case Failure(Code.CodeIncomplete   ()) => Color.orange  // "incomplete!"
            case Failure(e) =>
              e.printStackTrace()
              Color.red
          }
          ggCompile.icon = compileIcon(Some(iconColr))
        }
      }
      fut.map(_ => ())
    }

    private lazy val ggProgress = new SpinningProgressBar

    private lazy val actionCompile = Action("Compile")(compile())

    private lazy val ggCompile: Button = GUI.toolButton(actionCompile, raphael.Shapes.Hammer,
      tooltip = "Verify that current buffer compiles")

    private def compileIcon(colr: Option[Color]): Icon =
      raphael.Icon(extent = 20, fill = colr.getOrElse(raphael.TexturePaint(24)),
        shadow = raphael.WhiteShadow)(raphael.Shapes.Hammer)

    private var clearGreen = false

    def init()(implicit tx: S#Tx): Unit = deferTx(guiInit())

    private def guiInit(): Unit = {
      codePane        = CodePane(codeCfg)
      codePane.component.peer.putClientProperty("styleId", "undecorated")
      val iPane       = InterpreterPane.wrapAsync(interpreter(code.id), codePane)

      actionApply = Action("Apply")(save())
      actionApply.enabled = false

      lazy val doc = codePane.editor.peer.getDocument.asInstanceOf[SyntaxDocument]
      doc.addUndoableEditListener(
        new UndoableEditListener {
          def undoableEditHappened(e: UndoableEditEvent): Unit =
            if (clearGreen) {
              clearGreen = false
              ggCompile.icon = compileIcon(None)
            }
        }
      )

      doc.addPropertyChangeListener(SyntaxDocument.CAN_UNDO, new PropertyChangeListener {
        def propertyChange(e: PropertyChangeEvent): Unit = dirty = doc.canUndo
      })

      lazy val ggApply: Button = GUI.toolButton(actionApply, raphael.Shapes.Check , tooltip = "Save text changes")

      val bot0: List[Component] = ggProgress :: Nil
      val bot1 = handlerOpt.fold(bot0) { handler =>
        if (!hasExecute) bot0
        else {
          val actionExecute = Action(null) {
            cursor.step { implicit tx =>
              handler.execute()
            }
          }
          val ggExecute = GUI.toolButton(actionExecute, raphael.Shapes.Bolt, tooltip = "Run body")
          ggExecute :: bot0
        }
      }
      val bot2 = HGlue :: ggApply :: ggCompile :: bot1
      val panelBottom = new FlowPanel(FlowPanel.Alignment.Trailing)(bot2: _*)

      val iPaneC  = iPane.component
      val top     = iPaneC.peer.getComponent(iPaneC.peer.getComponentCount - 1) match {
        case jc: javax.swing.JComponent =>
          jc.add(panelBottom.peer)
          iPaneC
        case _ => new BorderPanel {
          add(iPaneC     , BorderPanel.Position.Center)
          add(panelBottom, BorderPanel.Position.South )
        }
      }

      component = top
      iPane.component.requestFocus()
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy