summaryrefslogtreecommitdiff
path: root/Master/texmf-dist/source/support/tlcockpit/src/main/scala/TLCockpit/ApplicationMain.scala
diff options
context:
space:
mode:
Diffstat (limited to 'Master/texmf-dist/source/support/tlcockpit/src/main/scala/TLCockpit/ApplicationMain.scala')
-rw-r--r--Master/texmf-dist/source/support/tlcockpit/src/main/scala/TLCockpit/ApplicationMain.scala1228
1 files changed, 705 insertions, 523 deletions
diff --git a/Master/texmf-dist/source/support/tlcockpit/src/main/scala/TLCockpit/ApplicationMain.scala b/Master/texmf-dist/source/support/tlcockpit/src/main/scala/TLCockpit/ApplicationMain.scala
index 696744bbeb1..f37dc10806e 100644
--- a/Master/texmf-dist/source/support/tlcockpit/src/main/scala/TLCockpit/ApplicationMain.scala
+++ b/Master/texmf-dist/source/support/tlcockpit/src/main/scala/TLCockpit/ApplicationMain.scala
@@ -6,22 +6,21 @@
package TLCockpit
-import javafx.collections.ObservableList
-import javafx.scene.control
-
-import TLCockpit.ApplicationMain.getClass
+import TLCockpit.Utils._
import TeXLive._
-// import java.io.File
-import scalafx.beans.property.StringProperty
+import scala.collection.{immutable, mutable}
import scala.collection.mutable.ArrayBuffer
-import scala.concurrent.{Future, SyncVar}
+import scala.concurrent.{Future, Promise, SyncVar}
import scala.concurrent.ExecutionContext.Implicits.global
-// import scala.reflect.io
-// import scala.reflect.io.File
import scala.util.{Failure, Success}
-import scalafx.beans.property.ObjectProperty
-import scalafx.geometry.{HPos, Pos, VPos, Orientation}
+import scala.sys.process._
+import scalafx.beans.property.BooleanProperty
+import scalafx.scene.text.Font
+// ScalaFX imports
+import scalafx.event.Event
+import scalafx.beans.property.{ObjectProperty, StringProperty}
+import scalafx.geometry.{Pos, Orientation}
import scalafx.scene.Cursor
import scalafx.scene.control.Alert.AlertType
import scalafx.scene.image.{Image, ImageView}
@@ -33,8 +32,6 @@ import scalafx.scene.control.TreeTableColumn._
import scalafx.scene.control.TreeItem
import scalafx.scene.control.Menu._
import scalafx.scene.control.ListCell
-import scalafx.scene.control.cell._
-import scalafx.scene.text.Text
import scalafx.Includes._
import scalafx.application.{JFXApp, Platform}
import scalafx.application.JFXApp.PrimaryStage
@@ -43,21 +40,20 @@ import scalafx.scene.Scene
import scalafx.scene.layout._
import scalafx.scene.control._
import scalafx.event.ActionEvent
-import scala.sys.process._
import scalafx.collections.ObservableBuffer
import scalafx.collections.ObservableMap
-// import java.awt.Desktop
+// JSON support - important load TLPackageJsonProtocol later!
+import spray.json._
+import TeXLive.JsonProtocol._
-// TODO when installing a collection list the additionally installed packages, too
-// TODO idea pkg tree with collection -> packages
-// TODO pkg info listview adjust height according to font size and # of items
-// TODO TreeTableView indentation is lazy
object ApplicationMain extends JFXApp {
val version: String = getClass.getPackage.getImplementationVersion
+ var tlmgrBusy = BooleanProperty(false)
+
// necessary action when Window is closed with X or some other operation
override def stopApp(): Unit = {
tlmgr.cleanup()
@@ -66,26 +62,41 @@ object ApplicationMain extends JFXApp {
val iconImage = new Image(getClass.getResourceAsStream("tlcockpit-48.jpg"))
val logoImage = new Image(getClass.getResourceAsStream("tlcockpit-128.jpg"))
- val pkgs: ObservableMap[String, TLPackage] = ObservableMap[String, TLPackage]()
- val upds: ObservableMap[String, TLUpdate] = ObservableMap[String, TLUpdate]()
- val bkps: ObservableMap[String, Map[String, TLBackup]] = ObservableMap[String, Map[String,TLBackup]]() // pkgname -> (version -> TLBackup)*
+ val tlpkgs: ObservableMap[String, TLPackage] = ObservableMap[String,TLPackage]()
+ val pkgs: ObservableMap[String, TLPackageDisplay] = ObservableMap[String, TLPackageDisplay]()
+ val upds: ObservableMap[String, TLUpdateDisplay] = ObservableMap[String, TLUpdateDisplay]()
+ val bkps: ObservableMap[String, Map[String, TLBackupDisplay]] = ObservableMap[String, Map[String,TLBackupDisplay]]() // pkgname -> (version -> TLBackup)*
- val errorText: ObservableBuffer[String] = ObservableBuffer[String]()
+ val logText: ObservableBuffer[String] = ObservableBuffer[String]()
val outputText: ObservableBuffer[String] = ObservableBuffer[String]()
+ val errorText: ObservableBuffer[String] = ObservableBuffer[String]()
val outputfield: TextArea = new TextArea {
editable = false
wrapText = true
text = ""
}
+ val logfield: TextArea = new TextArea {
+ editable = false
+ wrapText = true
+ text = ""
+ }
val errorfield: TextArea = new TextArea {
editable = false
wrapText = true
text = ""
}
+ logText.onChange({
+ logfield.text = logText.mkString("\n")
+ logfield.scrollTop = Double.MaxValue
+ })
errorText.onChange({
errorfield.text = errorText.mkString("\n")
errorfield.scrollTop = Double.MaxValue
+ if (errorfield.text.value.nonEmpty) {
+ outerrpane.expanded = true
+ outerrtabs.selectionModel().select(2)
+ }
})
outputText.onChange({
outputfield.text = outputText.mkString("\n")
@@ -93,7 +104,11 @@ object ApplicationMain extends JFXApp {
})
val update_all_menu: MenuItem = new MenuItem("Update all") {
- onAction = (ae) => callback_update("--all")
+ val cmd = "--all" + {
+ if (disable_auto_install) " --no-auto-install" else "" } + {
+ if (disable_auto_removal) " --no-auto-remove" else "" } + {
+ if (enable_reinstall_forcible) " --reinstall-forcibly-removed" else "" }
+ onAction = (ae) => callback_update(cmd)
disable = true
}
val update_self_menu: MenuItem = new MenuItem("Update self") {
@@ -110,7 +125,12 @@ object ApplicationMain extends JFXApp {
content = outputfield
},
new Tab {
- text = "Error"
+ text = "Logging"
+ closable = false
+ content = logfield
+ },
+ new Tab {
+ text = "Errors"
closable = false
content = errorfield
}
@@ -128,26 +148,8 @@ object ApplicationMain extends JFXApp {
(ae: KeyEvent) => if (ae.code == KeyCode.Enter) callback_run_cmdline()
}
val outputLine = new SyncVar[String]
+ val errorLine = new SyncVar[String]
- def tlmgr_async_command(s: String, f: Array[String] => Unit): Unit = {
- errorText.clear()
- outputText.clear()
- outerrpane.expanded = false
- statusMenu.text = "Status: Busy"
- val foo = Future {
- tlmgr.send_command(s)
- }
- foo onComplete {
- case Success(ret) =>
- f(ret)
- Platform.runLater { statusMenu.text = "Status: Idle" }
- case Failure(t) =>
- errorText.append("An ERROR has occurred calling tlmgr: " + t.getMessage)
- outerrpane.expanded = true
- outerrtabs.selectionModel().select(1)
- Platform.runLater { statusMenu.text = "Status: Idle" }
- }
- }
def callback_quit(): Unit = {
tlmgr.cleanup()
@@ -156,12 +158,12 @@ object ApplicationMain extends JFXApp {
}
def callback_run_text(s: String): Unit = {
- tlmgr_async_command(s, (s: Array[String]) => {})
+ tlmgr_send(s, (a: String, b: Array[String]) => {})
}
def callback_run_cmdline(): Unit = {
- tlmgr_async_command(cmdline.text.value, s => {
- outputText.appendAll(s)
+ tlmgr_send(cmdline.text.value, (status,output) => {
+ outputText.append(output.mkString("\n"))
outerrpane.expanded = true
outerrtabs.selectionModel().select(0)
})
@@ -199,7 +201,7 @@ object ApplicationMain extends JFXApp {
def callback_run_external(s: String, unbuffered: Boolean = true): Unit = {
outputText.clear()
- errorText.clear()
+ // logText.clear()
outerrpane.expanded = true
outerrtabs.selectionModel().select(0)
outputText.append(s"Running $s" + (if (unbuffered) " (unbuffered)" else " (buffered)"))
@@ -207,7 +209,7 @@ object ApplicationMain extends JFXApp {
s ! ProcessLogger(
line => if (unbuffered) Platform.runLater( outputText.append(line) )
else OutputBuffer.synchronized( OutputBuffer.append(line + "\n") ),
- line => Platform.runLater( errorText.append(line) )
+ line => Platform.runLater( logText.append(line) )
)
}
foo.onComplete {
@@ -227,7 +229,7 @@ object ApplicationMain extends JFXApp {
errorText.append("An ERROR has occurred running $s: " + t.getMessage)
errorfield.scrollTop = Double.MaxValue
outerrpane.expanded = true
- outerrtabs.selectionModel().select(1)
+ outerrtabs.selectionModel().select(2)
}
}
}
@@ -242,11 +244,10 @@ object ApplicationMain extends JFXApp {
}.showAndWait()
}
- def callback_update(s: String): Unit = {
- var prevUpdName = ""
- var prevUpdPkg = new TLUpdate("","","","","","")
- lineUpdateFunc = (l:String) => {
- // println("line update: " + l + "=")
+ def set_line_update_function(mode: String) = {
+ var prevName = ""
+ stdoutLineUpdateFunc = (l:String) => {
+ // println("DEBUG line update: " + l + "=")
l match {
case u if u.startsWith("location-url") => None
case u if u.startsWith("total-bytes") => None
@@ -255,84 +256,115 @@ object ApplicationMain extends JFXApp {
case u if u == "OK" => None
case u if u.startsWith("tlmgr>") => None
case u =>
- if (prevUpdName != "") {
- // println("Removing " + prevUpdName + " from list!")
- upds.remove(prevUpdName)
- val op = pkgs(prevUpdName)
- pkgs(prevUpdName) = new TLPackage(prevUpdPkg.name.value, op.rrev.value.toString, op.rrev.value.toString,
- prevUpdPkg.shortdesc.value, op.size.value.toString, "Installed")
- Platform.runLater {
+ if (prevName != "") {
+ if (mode == "update") {
+ // parallelism is a pain, I get concurrent access here, but don't know with whom?
+ // ConcurrentModificationExceptions often occur when you're modifying
+ // a collection while you are iterating over its elements.
+ // val newkids: ObservableBuffer[TreeItem[TLUpdateDisplay]] =
+ // updateTable.root.value.children.filter(_.value.value.name.value != prevName)
+ // .map(_.asInstanceOf[TreeItem[TLUpdateDisplay]])
+ // the last map is only necessary becasue ScalaFX is buggy and does not produce
+ // proper types here!!! Probably similar to https://github.com/scalafx/scalafx/issues/137
+ // updateTable.root.value.children = newkids
+ upds.remove(prevName)
trigger_update("upds")
- trigger_update("pkgs")
+ } else if (mode == "remove") {
+ tlpkgs(prevName).installed = false
+ } else { // install
+ tlpkgs(prevName).installed = true
}
+ if (mode == "remove") {
+ pkgs(prevName).lrev = ObjectProperty[Int](0)
+ pkgs(prevName).installed = StringProperty("Not installed")
+ tlpkgs(prevName).lrev = 0
+ } else { // install and update
+ pkgs(prevName).lrev = pkgs(prevName).rrev
+ pkgs(prevName).installed = StringProperty("Installed") // TODO support Mixed!!!
+ tlpkgs(prevName).lrev = tlpkgs(prevName).rrev
+ }
+ packageTable.refresh()
}
if (u.startsWith("end-of-updates")) {
// nothing to be done, all has been done above
- // println("got end of updates")
+ // println("DEBUG got end of updates")
} else {
- // println("getting update line")
- val foo = parse_one_update_line(l)
- val pkgname = foo.name.value
- val PkgTreeItemOption = updateTable.root.value.children.find(_.value.value.name.value == pkgname)
- PkgTreeItemOption match {
- case Some(p) =>
- prevUpdName = pkgname
- prevUpdPkg = p.getValue
- // println("Setting status to Updating ... for " + pkgname)
- prevUpdPkg.status = StringProperty("Updating ...")
- Platform.runLater {
- // println("calling updateTable refresh")
- updateTable.refresh()
- }
- case None => println("Very strange: Cannot find TreeItem for upd package" + pkgname)
+ // println("DEBUG getting update line")
+ prevName = if (mode == "update") {
+ val foo = parse_one_update_line(l)
+ val pkgname = foo.name.value
+ upds(pkgname).status = StringProperty("Updating ...")
+ updateTable.refresh()
+ pkgname
+ } else if (mode == "install") {
+ val fields = l.split("\t")
+ val pkgname = fields(0)
+ pkgs(pkgname).installed = StringProperty("Installing ...")
+ packageTable.refresh()
+ pkgname
+ } else { // remove
+ val fields = l.split("\t")
+ val pkgname = fields(0)
+ pkgs(pkgname).installed = StringProperty("Removing ...")
+ packageTable.refresh()
+ pkgname
}
}
}
}
- // val cmd = if (s == "--self") "update --self --no-restart" else s"update $s"
+ }
+
+ def callback_update(s: String): Unit = {
+ set_line_update_function("update")
val cmd = if (s == "--self") "update --self" else s"update $s"
- tlmgr_async_command(cmd, _ => {
- Platform.runLater {
- lineUpdateFunc = { (s: String) => }
- if (s == "--self") {
- reinitialize_tlmgr()
- // this doesn't work seemingly
- // update_upds_list()
- }
+ tlmgr_send(cmd, (a,b) => {
+ stdoutLineUpdateFunc = defaultStdoutLineUpdateFunc
+ if (s == "--self") {
+ reinitialize_tlmgr()
+ // this doesn't work seemingly
+ // update_upds_list()
}
})
}
-
-
- def do_one_pkg(what: String, pkg: String): Unit = {
- tlmgr_async_command(s"$what $pkg", _ => { Platform.runLater { update_pkgs_lists() } })
+ def callback_remove(pkg: String): Unit = {
+ set_line_update_function("remove")
+ tlmgr_send(s"remove $pkg", (_, _) => { stdoutLineUpdateFunc = defaultStdoutLineUpdateFunc })
}
+ def callback_install(pkg: String): Unit = {
+ set_line_update_function("install")
+ tlmgr_send(s"install $pkg", (_,_) => { stdoutLineUpdateFunc = defaultStdoutLineUpdateFunc })
+ }
+
- def callback_restore_pkg(str: String, rev: String): Unit = {
- not_implemented_info()
+ def callback_restore(str: String, rev: String): Unit = {
+ tlmgr_send(s"restore --force $str $rev", (_,_) => {
+ tlpkgs(str).lrev = rev.toLong
+ pkgs(str).lrev = ObjectProperty[Int](rev.toInt)
+ packageTable.refresh()
+ })
}
bkps.onChange( (obs,chs) => {
- var doit = chs match {
+ val doit = chs match {
case ObservableMap.Add(k, v) => k.toString == "root"
case ObservableMap.Replace(k, va, vr) => k.toString == "root"
case ObservableMap.Remove(k, v) => k.toString == "root"
}
if (doit) {
// println("DEBUG bkps.onChange called new length = " + bkps.keys.toArray.length)
- val newroot = new TreeItem[TLBackup](new TLBackup("root", "", "")) {
+ val newroot = new TreeItem[TLBackupDisplay](new TLBackupDisplay("root", "", "")) {
children = bkps
.filter(_._1 != "root")
.map(p => {
val pkgname: String = p._1
// we sort by negative of revision number, which give inverse sort
- val versmap: Array[(String, TLBackup)] = p._2.toArray.sortBy(-_._2.rev.value.toInt)
+ val versmap: Array[(String, TLBackupDisplay)] = p._2.toArray.sortBy(-_._2.rev.value.toInt)
- val foo: Seq[TreeItem[TLBackup]] = versmap.tail.sortBy(-_._2.rev.value.toInt).map { q =>
- new TreeItem[TLBackup](q._2)
+ val foo: Seq[TreeItem[TLBackupDisplay]] = versmap.tail.sortBy(-_._2.rev.value.toInt).map { q =>
+ new TreeItem[TLBackupDisplay](q._2)
}.toSeq
- new TreeItem[TLBackup](versmap.head._2) {
+ new TreeItem[TLBackupDisplay](versmap.head._2) {
children = foo
}
}).toArray.sortBy(_.value.value.name.value)
@@ -342,63 +374,166 @@ object ApplicationMain extends JFXApp {
}
}
})
+
+ def view_pkgs_by_collections(pkgbuf: scala.collection.mutable.Map[String, TLPackageDisplay],
+ binbuf: scala.collection.mutable.Map[String, ArrayBuffer[TLPackageDisplay]],
+ colbuf: scala.collection.mutable.Map[String, ArrayBuffer[TLPackageDisplay]]): Seq[TreeItem[TLPackageDisplay]] = {
+ val bin_pkg_map = compute_bin_pkg_mapping(pkgbuf, binbuf)
+ colbuf.map(
+ p => {
+ val colname: String = p._1
+ val coldeps: Seq[TLPackageDisplay] = p._2
+ val coltlpd: TLPackageDisplay = pkgbuf(colname)
+
+ new TreeItem[TLPackageDisplay](coltlpd) {
+ children = coldeps.filter(q => tlpkgs(q.name.value).category != "Collection").sortBy(_.name.value).map(sub => {
+ val binmap: (Boolean, Seq[TLPackageDisplay]) = bin_pkg_map(sub.name.value)
+ val ismixed: Boolean = binmap._1
+ val kids: Seq[TLPackageDisplay] = binmap._2.sortBy(_.name.value)
+ val ti = if (ismixed) {
+ // replace installed status with "Mixed"
+ new TreeItem[TLPackageDisplay](
+ new TLPackageDisplay(sub.name.value, sub.lrev.value.toString, sub.rrev.value.toString, sub.shortdesc.value, sub.size.value.toString, "Mixed")
+ ) {
+ children = kids.map(new TreeItem[TLPackageDisplay](_))
+ }
+ } else {
+ new TreeItem[TLPackageDisplay](sub) {
+ children = kids.map(new TreeItem[TLPackageDisplay](_))
+ }
+ }
+ ti
+ }
+ )
+ }
+ }
+ ).toSeq
+ // ArrayBuffer.empty[TreeItem[TLPackageDisplay]]
+ }
+
+ def view_pkgs_by_names(pkgbuf: scala.collection.mutable.Map[String, TLPackageDisplay],
+ binbuf: scala.collection.mutable.Map[String, ArrayBuffer[TLPackageDisplay]]): Seq[TreeItem[TLPackageDisplay]] = {
+ val bin_pkg_map: Map[String, (Boolean, Seq[TLPackageDisplay])] = compute_bin_pkg_mapping(pkgbuf, binbuf)
+ pkgbuf.map{
+ p => {
+ val binmap: (Boolean, Seq[TLPackageDisplay]) = bin_pkg_map(p._1)
+ val pkgtlp: TLPackageDisplay = p._2
+ val ismixed: Boolean = binmap._1
+ val kids: Seq[TLPackageDisplay] = binmap._2.sortBy(_.name.value)
+ if (ismixed) {
+ new TreeItem[TLPackageDisplay](
+ new TLPackageDisplay(pkgtlp.name.value, pkgtlp.lrev.value.toString, pkgtlp.rrev.value.toString, pkgtlp.shortdesc.value, pkgtlp.size.value.toString, "Mixed")
+ ) {
+ children = kids.map(new TreeItem[TLPackageDisplay](_))
+ }
+ } else {
+ new TreeItem[TLPackageDisplay](pkgtlp) {
+ children = kids.map(new TreeItem[TLPackageDisplay](_))
+ }
+ }
+ }
+ }.toSeq
+ }
+
+ def compute_bin_pkg_mapping(pkgbuf: scala.collection.mutable.Map[String, TLPackageDisplay],
+ binbuf: scala.collection.mutable.Map[String, ArrayBuffer[TLPackageDisplay]]): Map[String, (Boolean, Seq[TLPackageDisplay])] = {
+ pkgbuf.map {
+ p => {
+ val kids: Seq[TLPackageDisplay] = if (binbuf.keySet.contains(p._2.name.value)) {
+ binbuf(p._2.name.value)
+ } else {
+ Seq()
+ }
+ // for ismixed we && all the installed status. If all are installed, we get true
+ val allinstalled = (kids :+ p._2).foldRight[Boolean](true)((k, b) => k.installed.value == "Installed" && b)
+ val someinstalled = (kids :+ p._2).exists(_.installed.value == "Installed")
+ val mixedinstalled = !allinstalled && someinstalled
+ (p._1, (mixedinstalled, kids))
+ }
+ }.toMap
+ }
pkgs.onChange( (obs,chs) => {
- var doit = chs match {
+ val doit = chs match {
case ObservableMap.Add(k, v) => k.toString == "root"
case ObservableMap.Replace(k, va, vr) => k.toString == "root"
- case ObservableMap.Remove(k, v) => k.toString == "root"
+ // don't call the trigger on root removal!
+ // case ObservableMap.Remove(k, v) => k.toString == "root"
+ case ObservableMap.Remove(k,v) => false
}
if (doit) {
- val pkgbuf: ArrayBuffer[TLPackage] = ArrayBuffer.empty[TLPackage]
- val binbuf = scala.collection.mutable.Map.empty[String, ArrayBuffer[TLPackage]]
+ // println("DEBUG: entering pkgs.onChange")
+ // val pkgbuf: ArrayBuffer[TLPackageDisplay] = ArrayBuffer.empty[TLPackageDisplay]
+ val pkgbuf = scala.collection.mutable.Map.empty[String, TLPackageDisplay]
+ val binbuf = scala.collection.mutable.Map.empty[String, ArrayBuffer[TLPackageDisplay]]
+ val colbuf = scala.collection.mutable.Map.empty[String, ArrayBuffer[TLPackageDisplay]]
pkgs.foreach(pkg => {
// complicated part, determine whether it is a sub package or not!
// we strip of initial texlive. prefixes to make sure we deal
// with real packages
- if (pkg._1.stripPrefix("texlive.").contains(".")) {
- val foo: Array[String] = pkg._1.stripPrefix("texlive.infra").split('.')
+ if ((pkg._1.startsWith("texlive.infra") && pkg._1.stripPrefix("texlive.infra").contains(".")) ||
+ pkg._1.stripPrefix("texlive.infra").contains(".")) {
+ val foo: Array[String] = if (pkg._1.startsWith("texlive.infra"))
+ Array("texlive.infra", pkg._1.stripPrefix("texlive.infra"))
+ else
+ pkg._1.split('.')
val pkgname = foo(0)
- val binname = foo(1)
- if (binbuf.keySet.contains(pkgname)) {
- binbuf(pkgname) += pkg._2
- } else {
- binbuf(pkgname) = ArrayBuffer[TLPackage](pkg._2)
+ if (pkgname != "") {
+ val binname = foo(1)
+ if (binbuf.keySet.contains(pkgname)) {
+ binbuf(pkgname) += pkg._2
+ } else {
+ binbuf(pkgname) = ArrayBuffer[TLPackageDisplay](pkg._2)
+ }
}
} else if (pkg._1 == "root") {
// ignore the dummy root element,
// only used for speeding up event handling
} else {
- pkgbuf += pkg._2
+ pkgbuf(pkg._1) = pkg._2
}
})
- // now we have all normal packages in pkgbuf, and its sub-packages in binbuf
- // we need to create TreeItems
- val viewkids: ArrayBuffer[TreeItem[TLPackage]] = pkgbuf.map { p => {
- val kids: Seq[TLPackage] = if (binbuf.keySet.contains(p.name.value)) {
- binbuf(p.name.value)
- } else {
- Seq()
+ // Another round to propagate purely .win32 packages like wintools.win32 or
+ // dviout.win32 from binpkg status to full pkg, since they don't have
+ // accompanying main packages
+ binbuf.foreach(p => {
+ if (!pkgbuf.contains(p._1)) {
+ if (p._2.length > 1) {
+ errorText += "THAT SHOULD NOT HAPPEN: >>" + p._1 + "<< >>" + p._2.length + "<<"
+ // p._2.foreach(f => println("-> " + f.name.value))
+ } else {
+ // println("DEBUG Moving " + p._2.head.name.value + " up to pkgbuf " + p._1)
+ pkgbuf(p._2.head.name.value) = p._2.head
+ // TODO will this work out with the foreach loop above???
+ binbuf -= p._1
+ }
}
- // for ismixed we && all the installed status. If all are installed, we get true
- val allinstalled = (kids :+ p).foldRight[Boolean](true)((k, b) => k.installed.value == "Installed" && b)
- val someinstalled = (kids :+ p).exists(_.installed.value == "Installed")
- val mixedinstalled = !allinstalled && someinstalled
- if (mixedinstalled) {
- // replace installed status with "Mixed"
- new TreeItem[TLPackage](new TreeItem[TLPackage](
- new TLPackage(p.name.value, p.lrev.value.toString, p.rrev.value.toString, p.shortdesc.value, p.size.value.toString, "Mixed")
- )) {
- children = kids.map(new TreeItem[TLPackage](_))
+ })
+ // another loop to collection and fill the collections buffer
+ pkgs.foreach(pkg => {
+ if (tlpkgs.contains(pkg._1)) {
+ if (tlpkgs(pkg._1).category == "Collection") {
+ val foo: immutable.Seq[String] = tlpkgs(pkg._1).depends
+ colbuf(pkg._1) = ArrayBuffer[TLPackageDisplay]()
+ // TODO we need to deal with packages that get removed!!!
+ // for now just make sure we don't add them here!
+ colbuf(pkg._1) ++= foo.filter(pkgbuf.contains(_)).map(pkgbuf(_))
}
+ } else if (pkg._1 == "root") {
+ // do nothing
} else {
- new TreeItem[TLPackage](p) {
- children = kids.map(new TreeItem[TLPackage](_))
- }
+ errorText += "Cannot find information for " + pkg._1
}
- }
- }
+ })
+ // now we have all normal packages in pkgbuf, and its sub-packages in binbuf
+ // we need to create TreeItems
+ val viewkids: Seq[TreeItem[TLPackageDisplay]] =
+ if (ViewByPkg.selected.value)
+ view_pkgs_by_names(pkgbuf, binbuf)
+ else
+ view_pkgs_by_collections(pkgbuf, binbuf, colbuf)
+ // println("DEBUG: leaving pkgs.onChange before runLater")
Platform.runLater {
- packageTable.root = new TreeItem[TLPackage](new TLPackage("root", "0", "0", "", "0", "")) {
+ packageTable.root = new TreeItem[TLPackageDisplay](new TLPackageDisplay("root", "0", "0", "", "0", "")) {
expanded = true
children = viewkids.sortBy(_.value.value.name.value)
}
@@ -415,10 +550,10 @@ object ApplicationMain extends JFXApp {
val infraAvailable = upds.keys.exists(_.startsWith("texlive.infra"))
// only allow for updates of other packages when no infra update available
val updatesAvailable = !infraAvailable && upds.keys.exists(p => !p.startsWith("texlive.infra") && !(p == "root"))
- val newroot = new TreeItem[TLUpdate](new TLUpdate("root", "", "", "", "", "")) {
+ val newroot = new TreeItem[TLUpdateDisplay](new TLUpdateDisplay("root", "", "", "", "", "")) {
children = upds
.filter(_._1 != "root")
- .map(p => new TreeItem[TLUpdate](p._2))
+ .map(p => new TreeItem[TLUpdateDisplay](p._2))
.toArray
.sortBy(_.value.value.name.value)
}
@@ -442,37 +577,65 @@ object ApplicationMain extends JFXApp {
}.showAndWait()
}
- def update_bkps_list(): Unit = {
- tlmgr_async_command("restore", lines => {
- // lines.drop(1).foreach(println(_))
- val newbkps: Map[String, Map[String, TLBackup]] = lines.drop(1).map { (l: String) =>
- val fields = l.split("[ ():]", -1).filter(_.nonEmpty)
- val pkgname = fields(0)
- val rests: Array[Array[String]] = fields.drop(1).sliding(4, 4).toArray
- (pkgname, rests.map({ p => (p(1), new TLBackup(pkgname, p(0), p(1) + " " + p(2) + ":" + p(3)))}).toMap)
- }.toMap
+ def load_backups_update_bkps_view(): Unit = {
+ val tmp = new Label("Loading backups, please wait ...")
+ tmp.wrapText = true
+ tmp.opacity = 0.4f
+ tmp.font = new Font(30f)
+ val prevph = backupTable.placeholder.value
+ backupTable.placeholder = tmp
+ tlmgr_send("restore --json", (status, lines) => {
+ val jsonAst = lines.mkString("").parseJson
+ val backups: Map[String, Map[String, TLBackupDisplay]] =
+ jsonAst
+ .convertTo[List[TLBackup]]
+ .groupBy[String](_.name)
+ .map(p => (p._1, p._2.map(q => (q.rev, new TLBackupDisplay(q.name, q.rev, q.date))).toMap))
bkps.clear()
- bkps ++= newbkps
+ bkps ++= backups
trigger_update("bkps")
+ backupTable.placeholder = prevph
})
}
- def update_pkgs_lists():Unit = {
- tlmgr_async_command("info --data name,localrev,remoterev,shortdesc,size,installed", (s: Array[String]) => {
- val newpkgs = s.map { (line: String) =>
- val fields: Array[String] = line.split(",(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)", -1)
- val sd = fields(3)
- val shortdesc = if (sd.isEmpty) "" else sd.substring(1).dropRight(1).replace("""\"""",""""""")
- val inst = if (fields(5) == "1") "Installed" else "Not installed"
- (fields(0), new TLPackage(fields(0), fields(1), fields(2), shortdesc, fields(4), inst))
- }.toMap
- pkgs.clear()
- pkgs ++= newpkgs
- trigger_update("pkgs")
- })
+
+ def update_pkgs_view(): Unit = {
+ val newpkgs: Map[String, TLPackageDisplay] =
+ tlpkgs
+ .filter { p =>
+ val searchTerm = searchEntry.text.value.toLowerCase
+ p._1.toLowerCase.contains(searchTerm) ||
+ p._2.shortdesc.getOrElse("").toLowerCase.contains(searchTerm)
+ }
+ .map { p =>
+ (p._2.name,
+ new TLPackageDisplay(
+ p._2.name, p._2.lrev.toString, p._2.rrev.toString,
+ p._2.shortdesc.getOrElse(""), "0", if (p._2.installed) "Installed" else "Not installed"
+ )
+ )
+ }.toMap
+ pkgs.clear()
+ pkgs ++= newpkgs
+ trigger_update("pkgs")
}
+ def load_tlpdb_update_pkgs_view():Unit = {
+ val tmp = new Label("Loading database, please wait ...")
+ tmp.wrapText = true
+ tmp.opacity = 0.4f
+ tmp.font = new Font(30f)
+ val prevph = packageTable.placeholder.value
+ packageTable.placeholder = tmp
+ tlmgr_send("info --json", (status, lines) => {
+ val jsonAst = lines.mkString("").parseJson
+ tlpkgs.clear()
+ tlpkgs ++= jsonAst.convertTo[List[TLPackage]].map { p => (p.name, p)}
+ update_pkgs_view()
+ packageTable.placeholder = prevph
+ })
+ }
- def parse_one_update_line(l: String): TLUpdate = {
+ def parse_one_update_line(l: String): TLUpdateDisplay = {
val fields = l.split("\t")
val pkgname = fields(0)
val status = fields(1) match {
@@ -486,15 +649,15 @@ object ApplicationMain extends JFXApp {
}
val localrev = fields(2)
val serverrev = fields(3)
- val size = humanReadableByteSize(fields(4).toLong)
+ val size = if (fields(1) == "d") "0" else humanReadableByteSize(fields(4).toLong)
val runtime = fields(5)
val esttot = fields(6)
val tag = fields(7)
val lctanv = fields(8)
val rctanv = fields(9)
- val tlpkg: TLPackage = pkgs(pkgname)
+ val tlpkg: TLPackageDisplay = pkgs(pkgname)
val shortdesc = tlpkg.shortdesc.value
- new TLUpdate(pkgname, status,
+ new TLUpdateDisplay(pkgname, status,
localrev + {
if (lctanv != "-") s" ($lctanv)" else ""
},
@@ -504,10 +667,17 @@ object ApplicationMain extends JFXApp {
shortdesc, size)
}
- def update_upds_list(): Unit = {
- tlmgr_async_command("update --list", lines => {
- // val newupds = scala.collection.mutable.Map.empty[String,TLUpdate]
- val newupds: Map[String, TLUpdate] = lines.filter { l =>
+ def load_updates_update_upds_view(): Unit = {
+ val tmp = new Label("Loading updates, please wait ...")
+ tmp.wrapText = true
+ tmp.opacity = 0.4f
+ tmp.font = new Font(30f)
+ val prevph = updateTable.placeholder.value
+ updateTable.placeholder = tmp
+ tlmgr_send("update --list", (status, lines) => {
+ // println(s"DEBUG got updates length ${lines.length}")
+ // println(s"DEBUG tlmgr last output = ${lines}")
+ val newupds: Map[String, TLUpdateDisplay] = lines.filter { l =>
l match {
case u if u.startsWith("location-url") => false
case u if u.startsWith("total-bytes") => false
@@ -527,17 +697,19 @@ object ApplicationMain extends JFXApp {
upds ++= newupds
}
trigger_update("upds")
+ updateTable.placeholder = prevph
})
}
def trigger_update(s:String): Unit = {
- // println("Triggering update of " + s)
- if (s == "pkgs")
- pkgs("root") = new TLPackage("root","0","0","","0","")
- else if (s == "upds")
- upds("root") = new TLUpdate("root", "", "", "", "", "")
- else if (s == "bkps")
- bkps("root") = Map[String,TLBackup](("0", new TLBackup("root","0","0")))
+ // println("DEBUG: Triggering update of " + s)
+ if (s == "pkgs") {
+ pkgs("root") = new TLPackageDisplay("root", "0", "0", "", "0", "")
+ } else if (s == "upds") {
+ upds("root") = new TLUpdateDisplay("root", "", "", "", "", "")
+ } else if (s == "bkps") {
+ bkps("root") = Map[String, TLBackupDisplay](("0", new TLBackupDisplay("root", "0", "0")))
+ }
}
def doListView(files: Seq[String], clickable: Boolean): scalafx.scene.Node = {
@@ -560,7 +732,6 @@ object ApplicationMain extends JFXApp {
vb.prefHeight = 150
vb.maxHeight = 200
vb.vgrow = Priority.Always
- // TODO tighter spacing for ListView
vb.orientation = Orientation.Vertical
vb.cellFactory = { p => {
val foo = new ListCell[String]
@@ -582,210 +753,18 @@ object ApplicationMain extends JFXApp {
}
}
- def callback_show_pkg_info(pkg: String): Unit = {
- tlmgr_async_command(s"info --list $pkg", pkginfo => {
- // need to call runLater to go back to main thread!
- Platform.runLater {
- val dialog = new Dialog() {
- initOwner(stage)
- title = s"Package Information for $pkg"
- headerText = s"Package Information for $pkg"
- resizable = true
- }
- dialog.dialogPane().buttonTypes = Seq(ButtonType.OK)
- val isInstalled = pkgs(pkg).installed.value == "Installed"
- val grid = new GridPane() {
- hgap = 10
- vgap = 10
- padding = Insets(20)
- }
- var crow = 0
- val runFiles = ArrayBuffer[String]()
- val docFiles = ArrayBuffer[String]()
- val binFiles = ArrayBuffer[String]()
- val srcFiles = ArrayBuffer[String]()
- var whichFiles = "run" // we assume run files if not found!
- pkginfo.foreach((line: String) => {
- if (line.startsWith(" ")) {
- // files are listed with two spaces in the beginning
- whichFiles match {
- case "run" => runFiles += line.trim
- case "doc" => docFiles += line.trim
- case "bin" => binFiles += line.trim
- case "src" => srcFiles += line.trim
- case _ => println("Don't know what to do?!?!")
- }
- } else {
- val keyval = line.split(":", 2).map(_.trim)
- whichFiles = if (keyval(0).startsWith("run files")) "run" else
- if (keyval(0).startsWith("doc files")) "doc" else
- if (keyval(0).startsWith("source files")) "src" else
- if (keyval(0).startsWith("bin files (all platforms)")) "bin" else
- "unknown"
- if (keyval.length == 2) {
- if (keyval(0) == "doc files" || keyval(0) == "source files" || keyval(0) == "run files" ||
- keyval(0).startsWith("depending package ") ||
- keyval(0).startsWith("bin files (all platforms)") || keyval(0).startsWith("Included files, by type")) {
- // do nothing
- } else {
- val keylabel = new Label(keyval(0))
- val vallabel = new Label(keyval(1))
- vallabel.wrapText = true
- grid.add(keylabel, 0, crow)
- grid.add(vallabel, 1, crow)
- crow += 1
- }
- }
- }
- })
- // add files section
- if (docFiles.nonEmpty) {
- grid.add(new Label("doc files"), 0, crow)
- //grid.add(new Label(docFiles.mkString("\n")) { wrapText = true },1, crow)
- grid.add(doListView(docFiles.map(s => s.replaceFirst("RELOC","texmf-dist")),isInstalled),1,crow)
- crow += 1
- }
- if (runFiles.nonEmpty) {
- grid.add(new Label("run files"), 0, crow)
- // grid.add(new Label(runFiles.mkString("\n")) { wrapText = true },1, crow)
- grid.add(doListView(runFiles.map(s => s.replaceFirst("RELOC","texmf-dist")),false),1,crow)
- crow += 1
- }
- if (srcFiles.nonEmpty) {
- grid.add(new Label("src files"), 0, crow)
- // grid.add(new Label(srcFiles.mkString("\n")) { wrapText = true },1, crow)
- grid.add(doListView(srcFiles.map(s => s.replaceFirst("RELOC","texmf-dist")),false),1,crow)
- crow += 1
- }
- if (binFiles.nonEmpty) {
- grid.add(new Label("bin files"), 0, crow)
- // grid.add(new Label(binFiles.mkString("\n")) { wrapText = true },1, crow)
- grid.add(doListView(binFiles.map(s => s.replaceFirst("RELOC","texmf-dist")),false),1,crow)
- crow += 1
- }
- grid.columnConstraints = Seq(new ColumnConstraints(100, 200, 200), new ColumnConstraints(100, 400, 5000, Priority.Always, new HPos(HPos.Left), true))
- dialog.dialogPane().content = grid
- dialog.width = 600
- dialog.height = 1500
- dialog.showAndWait()
- }
- })
- }
- def callback_show_pkg_info_dev(pkg: String): Unit = {
- tlmgr_async_command(s"info --list $pkg", pkginfo => {
- // need to call runLater to go back to main thread!
- Platform.runLater {
- val dialog = new Dialog() {
- initOwner(stage)
- title = s"Package Information for $pkg"
- // headerText = s"Package Information for $pkg"
- resizable = true
- }
- dialog.dialogPane().buttonTypes = Seq(ButtonType.OK)
- val keyCol = new TreeTableColumn[(String,String), String] {
- text = "Package"
- cellValueFactory = { p => StringProperty(p.value.value.value._1) }
- }
- val valCol = new TreeTableColumn[(String,String), String] {
- text = pkg
- cellValueFactory = { p => StringProperty(p.value.value.value._2) }
- }
- val infoTable = new TreeTableView[(String, String)](
- new TreeItem[(String, String)](("","")) {
- expanded = false
- }) {
- columns ++= List(keyCol, valCol)
- }
- valCol.prefWidth.bind(infoTable.width - keyCol.width - 15)
- infoTable.prefHeight = 300
- infoTable.vgrow = Priority.Always
- infoTable.showRoot = false
- infoTable.fixedCellSize = Region.USE_COMPUTED_SIZE
- val runFiles = ArrayBuffer[String]()
- val docFiles = ArrayBuffer[String]()
- val binFiles = ArrayBuffer[String]()
- val srcFiles = ArrayBuffer[String]()
- val kids = ArrayBuffer[TreeItem[(String,String)]]()
- var whichFiles = "run" // we assume run files if not found!
- pkginfo.foreach((line: String) => {
- if (line.startsWith(" ")) {
- // files are listed with two spaces in the beginning
- whichFiles match {
- case "run" => runFiles += line.trim
- case "doc" => docFiles += line.trim
- case "bin" => binFiles += line.trim
- case "src" => srcFiles += line.trim
- case _ => println("Don't know what to do?!?!")
- }
- } else {
- val keyval = line.split(":", 2).map(_.trim)
- whichFiles = if (keyval(0).startsWith("run files")) "run" else
- if (keyval(0).startsWith("doc files")) "doc" else
- if (keyval(0).startsWith("source files")) "src" else
- if (keyval(0).startsWith("bin files (all platforms)")) "bin" else
- "unknown"
- if (keyval.length == 2) {
- if (keyval(0) == "doc files" || keyval(0) == "source files" || keyval(0) == "run files" ||
- keyval(0).startsWith("bin files (all platforms)")) {
- // do nothing
- } else {
- kids += new TreeItem[(String,String)]((keyval(0), keyval(1)))
- }
- }
- }
- })
- // add files section
- if (docFiles.nonEmpty) {
- kids += new TreeItem[(String,String)](("Doc files","")) {
- children = docFiles.map { p => new TreeItem[(String,String)](("",p)) }
- }
- }
- if (runFiles.nonEmpty) {
- kids += new TreeItem[(String,String)](("Run files","")) {
- children = runFiles.map { p => new TreeItem[(String,String)](("",p)) }
- }
- }
- if (srcFiles.nonEmpty) {
- kids += new TreeItem[(String,String)](("Source files","")) {
- children = srcFiles.map { p => new TreeItem[(String,String)](("",p)) }
- }
- }
- if (binFiles.nonEmpty) {
- kids += new TreeItem[(String,String)](("Bin files","")) {
- children = binFiles.map { p => new TreeItem[(String,String)](("",p)) }
- }
- }
- infoTable.root = new TreeItem[(String, String)](("","")) {
- expanded = true
- children = kids
- }
- dialog.dialogPane().content = infoTable
- // dialog.width = 500
- // dialog.height = 800
- dialog.showAndWait()
- }
- })
- }
-
- /**
- * @see https://stackoverflow.com/questions/3263892/format-file-size-as-mb-gb-etc
- * @see https://en.wikipedia.org/wiki/Zettabyte
- * @param fileSize Up to Exabytes
- * @return
- */
- def humanReadableByteSize(fileSize: Long): String = {
- if(fileSize <= 0) return "0 B"
- // kilo, Mega, Giga, Tera, Peta, Exa, Zetta, Yotta
- val units: Array[String] = Array("B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB")
- val digitGroup: Int = (Math.log10(fileSize)/Math.log10(1024)).toInt
- f"${fileSize/Math.pow(1024, digitGroup)}%3.1f ${units(digitGroup)}"
- }
-
val mainMenu: Menu = new Menu("TLCockpit") {
items = List(
- update_all_menu,
- update_self_menu,
- new SeparatorMenuItem,
+ // temporarily move here as we disable the Help menu
+ new MenuItem("About") {
+ onAction = (ae) => callback_about()
+ },
+ new MenuItem("Exit") {
+ onAction = (ae: ActionEvent) => callback_quit()
+ })
+ }
+ val toolsMenu: Menu = new Menu("Tools") {
+ items = List(
new MenuItem("Update filename databases ...") {
onAction = (ae) => {
callback_run_external("mktexlsr")
@@ -797,49 +776,96 @@ object ApplicationMain extends JFXApp {
new MenuItem("Rebuild all formats ...") { onAction = (ae) => callback_run_external("fmtutil --sys --all", false) },
new MenuItem("Update font map database ...") {
onAction = (ae) => callback_run_external("updmap --sys")
- },
- /*
- new MenuItem("Restore packages from backup ...") {
- disable = true; onAction = (ae) => not_implemented_info()
- },
- new MenuItem("Handle symlinks in system dirs ...") {
- disable = true; onAction = (ae) => not_implemented_info()
- },
- new SeparatorMenuItem,
- new MenuItem("Remove TeX Live ...") {
- disable = true; onAction = (ae) => not_implemented_info()
- },
- */
+ }
+ )
+ }
+ val ViewByPkg = new RadioMenuItem("by package name") {
+ onAction = (ae) => {
+ searchEntry.text = ""
+ update_pkgs_view()
+ }
+ }
+ val ViewByCol = new RadioMenuItem("by collections") {
+ onAction = (ae) => {
+ searchEntry.text = ""
+ update_pkgs_view()
+ }
+ }
+ ViewByPkg.selected = true
+ ViewByCol.selected = false
+ val pkgsMenu: Menu = new Menu("Packages") {
+ val foo = new ToggleGroup
+ foo.toggles = Seq(ViewByPkg, ViewByCol)
+ items = List(ViewByPkg, ViewByCol)
+ }
+ var disable_auto_removal = false
+ var disable_auto_install = false
+ var enable_reinstall_forcible = false
+ val updMenu: Menu = new Menu("Updates") {
+ items = List(
+ update_all_menu,
+ update_self_menu,
new SeparatorMenuItem,
- // temporarily move here as we disable the Help menu
- new MenuItem("About") {
- onAction = (ae) => callback_about()
- },
- new MenuItem("Exit") {
- onAction = (ae: ActionEvent) => callback_quit()
- })
+ new CheckMenuItem("Disable auto removal") { onAction = (ae) => disable_auto_removal = selected.value },
+ new CheckMenuItem("Disable auto install") { onAction = (ae) => disable_auto_install = selected.value },
+ new CheckMenuItem("Reinstall forcibly removed") { onAction = (ae) => enable_reinstall_forcible = selected.value }
+ )
+ }
+
+
+ def callback_general_options(): Unit = {
+ tlmgr_send("option showall --json", (status, lines) => {
+ val jsonAst = lines.mkString("").parseJson
+ val tlpdopts: List[TLOption] = jsonAst.convertTo[List[TLOption]]
+ Platform.runLater {
+ val dg = new OptionsDialog(tlpdopts)
+ dg.showAndWait() match {
+ case Some(changedOpts) =>
+ changedOpts.foreach(p => {
+ // don't believe it or not, but \" does *NOT* work in Scala in
+ // interpolated strings, and it seems there is no better way
+ // than that one ...
+ tlmgr_send(s"option ${p._1} ${'"'}${p._2}${'"'}", (_,_) => None)
+ })
+ case None =>
+ }
+ }
+ })
}
+
+
+ def callback_paper(): Unit = {
+ tlmgr_send("paper --json", (status, lines) => {
+ val jsonAst = lines.mkString("").parseJson
+ val paperconfs: Map[String, TLPaperConf] = jsonAst.convertTo[List[TLPaperConf]].map { p => (p.program, p) }.toMap
+ val currentPapers: Map[String, String] = paperconfs.mapValues(p => p.options.head)
+ Platform.runLater {
+ val dg = new PaperDialog(paperconfs)
+ dg.showAndWait() match {
+ case Some(newPapers) =>
+ // println(s"Got result ${newPapers}")
+ // collect changed settings
+ val changedPapers = newPapers.filter(p => currentPapers(p._1) != p._2)
+ // println(s"Got changed papers ${changedPapers}")
+ changedPapers.foreach(p => {
+ tlmgr_send(s"paper ${p._1} paper ${p._2}", (_,_) => None)
+ })
+ case None =>
+ }
+ }
+ })
+ }
+
val optionsMenu: Menu = new Menu("Options") {
- items = List( new MenuItem("General ...") { disable = true; onAction = (ae) => not_implemented_info() },
- new MenuItem("Paper ...") { disable = true; onAction = (ae) => not_implemented_info() },
- new MenuItem("Platforms ...") { disable = true; onAction = (ae) => not_implemented_info() },
+ items = List(
+ new MenuItem("General ...") { onAction = (ae) => callback_general_options() },
+ new MenuItem("Paper ...") { onAction = (ae) => callback_paper() },
+ /* new MenuItem("Platforms ...") { disable = true; onAction = (ae) => not_implemented_info() },
new SeparatorMenuItem,
new CheckMenuItem("Expert options") { disable = true },
new CheckMenuItem("Enable debugging options") { disable = true },
new CheckMenuItem("Disable auto-install of new packages") { disable = true },
- new CheckMenuItem("Disable auto-removal of server-deleted packages") { disable = true }
- )
- }
- val helpMenu: Menu = new Menu("Help") {
- items = List(
- /*
- new MenuItem("Manual") {
- disable = true; onAction = (ae) => not_implemented_info()
- },
- */
- new MenuItem("About") {
- onAction = (ae) => callback_about()
- },
+ new CheckMenuItem("Disable auto-removal of server-deleted packages") { disable = true } */
)
}
val statusMenu: Menu = new Menu("Status: Idle") {
@@ -867,39 +893,39 @@ object ApplicationMain extends JFXApp {
)
}
}
- val updateTable: TreeTableView[TLUpdate] = {
- val colName = new TreeTableColumn[TLUpdate, String] {
+ val updateTable: TreeTableView[TLUpdateDisplay] = {
+ val colName = new TreeTableColumn[TLUpdateDisplay, String] {
text = "Package"
cellValueFactory = { _.value.value.value.name }
prefWidth = 150
}
- val colStatus = new TreeTableColumn[TLUpdate, String] {
+ val colStatus = new TreeTableColumn[TLUpdateDisplay, String] {
text = "Status"
cellValueFactory = { _.value.value.value.status }
prefWidth = 120
}
- val colDesc = new TreeTableColumn[TLUpdate, String] {
+ val colDesc = new TreeTableColumn[TLUpdateDisplay, String] {
text = "Description"
cellValueFactory = { _.value.value.value.shortdesc }
prefWidth = 300
}
- val colLRev = new TreeTableColumn[TLUpdate, String] {
+ val colLRev = new TreeTableColumn[TLUpdateDisplay, String] {
text = "Local rev"
cellValueFactory = { _.value.value.value.lrev }
prefWidth = 100
}
- val colRRev = new TreeTableColumn[TLUpdate, String] {
+ val colRRev = new TreeTableColumn[TLUpdateDisplay, String] {
text = "Remote rev"
cellValueFactory = { _.value.value.value.rrev }
prefWidth = 100
}
- val colSize = new TreeTableColumn[TLUpdate, String] {
+ val colSize = new TreeTableColumn[TLUpdateDisplay, String] {
text = "Size"
cellValueFactory = { _.value.value.value.size }
prefWidth = 70
}
- val table = new TreeTableView[TLUpdate](
- new TreeItem[TLUpdate](new TLUpdate("root","","","","","")) {
+ val table = new TreeTableView[TLUpdateDisplay](
+ new TreeItem[TLUpdateDisplay](new TLUpdateDisplay("root","","","","","")) {
expanded = false
}) {
columns ++= List(colName, colStatus, colDesc, colLRev, colRRev, colSize)
@@ -910,47 +936,60 @@ object ApplicationMain extends JFXApp {
table.placeholder = new Label("No updates available")
table.showRoot = false
table.rowFactory = { _ =>
- val row = new TreeTableRow[TLUpdate] {}
- val ctm = new ContextMenu(
- new MenuItem("Info") {
- onAction = (ae) => callback_show_pkg_info(row.item.value.name.value)
- },
- new MenuItem("Install") {
- // onAction = (ae) => callback_run_text("install " + row.item.value.name.value)
- onAction = (ae) => do_one_pkg("install", row.item.value.name.value)
- },
- new MenuItem("Remove") {
- // onAction = (ae) => callback_run_text("remove " + row.item.value.name.value)
- onAction = (ae) => do_one_pkg("remove", row.item.value.name.value)
- },
- new MenuItem("Update") {
- // onAction = (ae) => callback_run_text("update " + row.item.value.name.value)
- onAction = (ae) => callback_update(row.item.value.name.value)
+ val row = new TreeTableRow[TLUpdateDisplay] {}
+ val infoMI = new MenuItem("Info") {
+ onAction = (ae) => new PkgInfoDialog(row.item.value.name.value).showAndWait()
+ }
+ val updateMI = new MenuItem("Update") {
+ onAction = (ae) => callback_update(row.item.value.name.value)
+ }
+ val installMI = new MenuItem("Install") {
+ onAction = (ae) => callback_install(row.item.value.name.value)
+ }
+ val removeMI = new MenuItem("Remove") {
+ onAction = (ae) => callback_remove(row.item.value.name.value)
+ }
+ val ctm = new ContextMenu(infoMI, updateMI, installMI, removeMI)
+ row.item.onChange { (_,_,newTL) =>
+ if (newTL != null) {
+ if (newTL.status.value == "New on server") {
+ installMI.disable = false
+ removeMI.disable = true
+ updateMI.disable = true
+ } else if (newTL.status.value == "Removed on server") {
+ installMI.disable = true
+ removeMI.disable = false
+ updateMI.disable = true
+ } else {
+ installMI.disable = true
+ removeMI.disable = false
+ updateMI.disable = false
+ }
}
- )
+ }
row.contextMenu = ctm
row
}
table
}
- val packageTable: TreeTableView[TLPackage] = {
- val colName = new TreeTableColumn[TLPackage, String] {
+ val packageTable: TreeTableView[TLPackageDisplay] = {
+ val colName = new TreeTableColumn[TLPackageDisplay, String] {
text = "Package"
cellValueFactory = { _.value.value.value.name }
prefWidth = 150
}
- val colDesc = new TreeTableColumn[TLPackage, String] {
+ val colDesc = new TreeTableColumn[TLPackageDisplay, String] {
text = "Description"
cellValueFactory = { _.value.value.value.shortdesc }
prefWidth = 300
}
- val colInst = new TreeTableColumn[TLPackage, String] {
+ val colInst = new TreeTableColumn[TLPackageDisplay, String] {
text = "Installed"
cellValueFactory = { _.value.value.value.installed }
prefWidth = 100
}
- val table = new TreeTableView[TLPackage](
- new TreeItem[TLPackage](new TLPackage("root","0","0","","0","")) {
+ val table = new TreeTableView[TLPackageDisplay](
+ new TreeItem[TLPackageDisplay](new TLPackageDisplay("root","0","0","","0","")) {
expanded = false
}) {
columns ++= List(colName, colDesc, colInst)
@@ -959,42 +998,48 @@ object ApplicationMain extends JFXApp {
table.prefHeight = 300
table.showRoot = false
table.vgrow = Priority.Always
- table.rowFactory = { _ =>
- val row = new TreeTableRow[TLPackage] {}
- val ctm = new ContextMenu(
- new MenuItem("Info") {
- onAction = (ae) => callback_show_pkg_info(row.item.value.name.value)
- },
- new MenuItem("Install") {
- onAction = (ae) => do_one_pkg("install", row.item.value.name.value)
- },
- new MenuItem("Remove") {
- onAction = (ae) => do_one_pkg("remove", row.item.value.name.value)
+ table.rowFactory = { p =>
+ val row = new TreeTableRow[TLPackageDisplay] {}
+ val infoMI = new MenuItem("Info") {
+ onAction = (ae) => new PkgInfoDialog(row.item.value.name.value).showAndWait()
+ }
+ val installMI = new MenuItem("Install") {
+ onAction = (ae) => callback_install(row.item.value.name.value)
+ }
+ val removeMI = new MenuItem("Remove") {
+ onAction = (ae) => callback_remove(row.item.value.name.value)
+ }
+ val ctm = new ContextMenu(infoMI, installMI, removeMI)
+ row.item.onChange { (_,_,newTL) =>
+ if (newTL != null) {
+ val is_installed: Boolean = !(newTL.installed.value == "Not installed")
+ installMI.disable = is_installed
+ removeMI.disable = !is_installed
}
- )
+ }
row.contextMenu = ctm
row
}
table
}
- val backupTable: TreeTableView[TLBackup] = {
- val colName = new TreeTableColumn[TLBackup, String] {
+ val backupTable: TreeTableView[TLBackupDisplay] = {
+ val colName = new TreeTableColumn[TLBackupDisplay, String] {
text = "Package"
cellValueFactory = { _.value.value.value.name }
prefWidth = 150
}
- val colRev = new TreeTableColumn[TLBackup, String] {
+ val colRev = new TreeTableColumn[TLBackupDisplay, String] {
text = "Revision"
cellValueFactory = { _.value.value.value.rev }
prefWidth = 100
}
- val colDate = new TreeTableColumn[TLBackup, String] {
+ val colDate = new TreeTableColumn[TLBackupDisplay, String] {
text = "Date"
cellValueFactory = { _.value.value.value.date }
prefWidth = 300
}
- val table = new TreeTableView[TLBackup](
- new TreeItem[TLBackup](new TLBackup("root","","")) {
+ val table = new TreeTableView[TLBackupDisplay](
+ new TreeItem[TLBackupDisplay](new TLBackupDisplay("root","","")) {
expanded = false
}) {
columns ++= List(colName, colRev, colDate)
@@ -1004,13 +1049,13 @@ object ApplicationMain extends JFXApp {
table.showRoot = false
table.vgrow = Priority.Always
table.rowFactory = { _ =>
- val row = new TreeTableRow[TLBackup] {}
+ val row = new TreeTableRow[TLBackupDisplay] {}
val ctm = new ContextMenu(
new MenuItem("Info") {
- onAction = (ae) => callback_show_pkg_info(row.item.value.name.value)
+ onAction = (ae) => new PkgInfoDialog(row.item.value.name.value).showAndWait()
},
new MenuItem("Restore") {
- onAction = (ae) => callback_restore_pkg(row.item.value.name.value, row.item.value.rev.value)
+ onAction = (ae) => callback_restore(row.item.value.name.value, row.item.value.rev.value)
}
)
row.contextMenu = ctm
@@ -1018,6 +1063,36 @@ object ApplicationMain extends JFXApp {
}
table
}
+ val searchEntry = new TextField()
+ searchEntry.hgrow = Priority.Sometimes
+ searchEntry.onKeyPressed = {
+ (ae: KeyEvent) => if (ae.code == KeyCode.Enter) update_pkgs_view()
+ }
+ val searchBox = new HBox {
+ children = Seq(
+ new Label("Search:") {
+ vgrow = Priority.Always
+ alignmentInParent = Pos.CenterLeft
+ },
+ searchEntry,
+ new Button("Go") {
+ onAction = _ => update_pkgs_view()
+ },
+ new Button("Reset") {
+ onAction = _ => {
+ searchEntry.text = ""
+ update_pkgs_view()
+ }
+ }
+ )
+ alignment = Pos.Center
+ padding = Insets(10)
+ spacing = 10
+ }
+ val pkgsContainer = new VBox {
+ children = Seq(packageTable,searchBox)
+ }
+
val pkgstabs: TabPane = new TabPane {
minWidth = 400
vgrow = Priority.Always
@@ -1025,7 +1100,7 @@ object ApplicationMain extends JFXApp {
new Tab {
text = "Packages"
closable = false
- content = packageTable
+ content = pkgsContainer
},
new Tab {
text = "Updates"
@@ -1039,23 +1114,31 @@ object ApplicationMain extends JFXApp {
}
)
}
+ // val spacerMenu: Menu = new Menu(" ")
+ // spacerMenu.disable = true
+ // spacerMenu.hgrow = Priority.Always
+ val menuBar: MenuBar = new MenuBar {
+ useSystemMenuBar = true
+ // menus.addAll(mainMenu, optionsMenu, helpMenu)
+ menus.addAll(mainMenu, pkgsMenu, toolsMenu, optionsMenu, statusMenu)
+ }
pkgstabs.selectionModel().selectedItem.onChange(
(a,b,c) => {
if (a.value.text() == "Backups") {
if (backupTable.root.value.children.length == 0)
- update_bkps_list()
+ load_backups_update_bkps_view()
+ menuBar.menus = Seq(mainMenu, toolsMenu, optionsMenu, statusMenu)
} else if (a.value.text() == "Updates") {
// only update if not done already
if (updateTable.root.value.children.length == 0)
- update_upds_list()
+ load_updates_update_upds_view()
+ menuBar.menus = Seq(mainMenu, updMenu, toolsMenu, optionsMenu, statusMenu)
+ } else if (a.value.text() == "Packages") {
+ menuBar.menus = Seq(mainMenu, pkgsMenu, toolsMenu, optionsMenu, statusMenu)
}
}
)
- val menuBar: MenuBar = new MenuBar {
- useSystemMenuBar = true
- // menus.addAll(mainMenu, optionsMenu, helpMenu)
- menus.addAll(mainMenu, statusMenu)
- }
+
stage = new PrimaryStage {
title = "TLCockpit"
@@ -1083,42 +1166,131 @@ object ApplicationMain extends JFXApp {
icons.add(iconImage)
}
+ stage.onCloseRequest = (e: Event) => callback_quit()
+
+ stage.width = 800
+
+ var currentPromise = Promise[(String,Array[String])]()
+ val pendingJobs = scala.collection.mutable.Queue[(String,(String, Array[String]) => Unit)]()
def initialize_tlmgr(): TlmgrProcess = {
+ tlmgrBusy.value = true
val tt = new TlmgrProcess(
- // (s:String) => outputfield.text = s,
- (s: Array[String]) => {
- // we don't wont normal output to be displayed
- // as it is anyway returned
- // outputText.clear()
- // outputText.appendAll(s)
- },
- (s: String) => {
- // errorText.clear()
- errorText.append(s)
- if (s.trim != "") {
- outerrpane.expanded = true
- outerrtabs.selectionModel().select(1)
- }
- },
- (s: String) => outputLine.put(s)
+ (s: String) => outputLine.put(s),
+ (s: String) => errorLine.put(s)
)
- val bar = Future {
+ /* val tlmgrMonitor = Future {
while (true) {
+ if (!tlmgr.isAlive) {
+ println("TLMGR HAS DIED!!!!")
+ // Platform.exit()
+ // sys.exit(1)
+ }
+ Thread.sleep(5000)
+ }
+ }*/
+ val stdoutFuture = Future {
+ val tlmgrOutput = ArrayBuffer[String]()
+ var tlmgrStatus = ""
+ var alive = true
+ while (alive) {
val s = outputLine.take
- lineUpdateFunc(s)
+ if (s == null) {
+ alive = false
+ // println("GOT NULL from outputline tlmgr dead???")
+ } else {
+ //println(s"DEBUG: got " + s)
+ if (s == "OK") {
+ tlmgrStatus = s
+ } else if (s == "ERROR") {
+ tlmgrStatus = s
+ } else if (s == "tlmgr> ") {
+ // println("DEBUG: fulfilling current promise!")
+ currentPromise.success((tlmgrStatus, tlmgrOutput.toArray))
+ tlmgrStatus = ""
+ tlmgrOutput.clear()
+ tlmgrBusy.value = false
+ if (pendingJobs.nonEmpty) {
+ val nextCmd = pendingJobs.dequeue()
+ tlmgr_run_one_cmd(nextCmd._1, nextCmd._2)
+ }
+ } else {
+ tlmgrOutput += s
+ stdoutLineUpdateFunc(s)
+ }
+ }
+ }
+ }
+ tlmgrBusy.onChange({ Platform.runLater{ statusMenu.text = "Status: " + (if (tlmgrBusy.value) "Busy" else "Idle") }})
+ stdoutFuture.onComplete {
+ case Success(value) =>
+ // println(s"tlmgr stdout reader terminated: ${value}")
+ Platform.runLater {
+ outerrpane.expanded = true
+ outerrtabs.selectionModel().select(1)
+ errorfield.scrollTop = Double.MaxValue
+ logfield.scrollTop = Double.MaxValue
+ new Alert(AlertType.Error) {
+ initOwner(stage)
+ title = "TeX Live Manager tlmgr has terminated"
+ headerText = "TeX Live Manager tlmgr has terminated - we cannot continue"
+ contentText = "Please inspect the debug output in the main window lower pane!"
+ }.showAndWait()
+ // Platform.exit()
+ // sys.exit(1)
+ }
+ case Failure(e) =>
+ errorText += "lineUpdateFunc(stdout) thread got interrupted -- probably old tlmgr, ignoring it!"
+ e.printStackTrace
+ }
+ val stderrFuture = Future {
+ var alive = true
+ while (alive) {
+ val s = errorLine.take
+ if (s == null)
+ alive = false
+ else
+ stderrLineUpdateFunc(s)
}
}
- bar.onComplete {
- case Success(value) => println(s"Got the callback, meaning = $value")
+ stderrFuture.onComplete {
+ case Success(value) => // println(s"tlmgr stderr reader terminated: ${value}")
case Failure(e) =>
- println("lineUpdateFunc thread got interrupted -- probably old tlmgr, ignoring it!")
- //e.printStackTrace
+ errorText += "lineUpdateFunc(stderr) thread got interrupted -- probably old tlmgr, ignoring it!"
+ e.printStackTrace
}
tt
}
+ def tlmgr_run_one_cmd(s: String, onCompleteFunc: (String, Array[String]) => Unit): Unit = {
+ currentPromise = Promise[(String, Array[String])]()
+ tlmgrBusy.value = true
+ currentPromise.future.onComplete {
+ case Success((a, b)) =>
+ // println("DEBUG current future completed!")
+ Platform.runLater {
+ onCompleteFunc(a, b)
+ }
+ case Failure(ex) =>
+ errorText += "Running a tlmgr command did not succeed: " + ex.getMessage
+ }
+ // println(s"DEBUG sending ${s}")
+ tlmgr.send_command(s)
+ }
+
+ def tlmgr_send(s: String, onCompleteFunc: (String, Array[String]) => Unit): Unit = {
+ // logText.clear()
+ outputText.clear()
+ outerrpane.expanded = false
+ if (!currentPromise.isCompleted) {
+ // println(s"DEBUG tlmgr busy, put onto pending jobs: $s")
+ pendingJobs += ((s, onCompleteFunc))
+ } else {
+ tlmgr_run_one_cmd(s, onCompleteFunc)
+ }
+ }
+
def reinitialize_tlmgr(): Unit = {
tlmgr.cleanup()
// Thread.sleep(1000)
@@ -1128,37 +1300,47 @@ object ApplicationMain extends JFXApp {
}
def tlmgr_post_init():Unit = {
- tlmgr.start_process()
- tlmgr.get_output_till_prompt()
- // update_pkg_lists_to_be_renamed() // this is async done
- pkgs.clear()
- upds.clear()
- bkps.clear()
- update_pkgs_lists()
- }
-
-
- /* TODO implement splash screen - see example in ProScalaFX
- val startalert = new Alert(AlertType.Information)
- startalert.setTitle("Loading package database ...")
- startalert.setContentText("Loading package database, this might take a while. Please wait!")
- startalert.show()
- */
-
- /*
- var testmode = false
- if (parameters.unnamed.nonEmpty) {
- if (parameters.unnamed.head == "-test" || parameters.unnamed.head == "--test") {
- println("Testing mode enabled, not actually calling tlmgr!")
- testmode = true
+ if (!tlmgr.start_process()) {
+ println("Cannot start tlmgr process, terminating!")
+ Platform.exit()
+ sys.exit(1)
}
+
+ // check for tlmgr revision
+ tlmgr_send("version", (status,output) => {
+ output.foreach ( l => {
+ if (l.startsWith("revision ")) {
+ val tlmgrRev = l.stripPrefix("revision ")
+ if (tlmgrRev == "unknown") {
+ println("Unknown tlmgr revision, assuming git/svn version")
+ logText += "Unknown tlmgr revision, assuming git/svn version"
+ } else {
+ if (tlmgrRev.toInt < 45838) {
+ new Alert(AlertType.Error) {
+ initOwner(stage)
+ title = "TeX Live Manager tlmgr is too old"
+ headerText = "TeX Live Manager tlmgr is too old"
+ contentText = "Please update from the command line\nusing 'tlmgr update --self'\nTerminating!"
+ }.showAndWait()
+ callback_quit()
+ }
+ }
+ }
+ })
+ pkgs.clear()
+ upds.clear()
+ bkps.clear()
+ load_tlpdb_update_pkgs_view()
+ })
}
- */
- var lineUpdateFunc: String => Unit = { (l: String) => } // println(s"DEBUG: got ==$l== from tlmgr") }
+ def defaultStdoutLineUpdateFunc(l: String) : Unit = { } // println(s"DEBUG: got ==$l== from tlmgr") }
+ def defaultStderrLineUpdateFunc(l: String) : Unit = { Platform.runLater { logText.append(l) } }
+
+ var stdoutLineUpdateFunc: String => Unit = defaultStdoutLineUpdateFunc
+ var stderrLineUpdateFunc: String => Unit = defaultStderrLineUpdateFunc
var tlmgr = initialize_tlmgr()
tlmgr_post_init()
-
} // object ApplicationMain
// vim:set tabstop=2 expandtab : //