summaryrefslogtreecommitdiff
path: root/Master
diff options
context:
space:
mode:
authorNorbert Preining <preining@logic.at>2017-09-28 06:17:52 +0000
committerNorbert Preining <preining@logic.at>2017-09-28 06:17:52 +0000
commitd71a0698b1309b3ff094b32532f041659b585a9b (patch)
tree892cc1ea29617533cf66c685623e8be9b718327c /Master
parentc75aeab745112f1462b1ef6ebf00de3204d5bd06 (diff)
update tlcockpit
git-svn-id: svn://tug.org/texlive/trunk@45424 c570f23f-e606-0410-a88d-b1316a301751
Diffstat (limited to 'Master')
-rw-r--r--Master/texmf-dist/doc/support/tlcockpit/README.md25
-rw-r--r--Master/texmf-dist/doc/support/tlcockpit/screenshots/backup-view.jpgbin0 -> 47544 bytes
-rw-r--r--Master/texmf-dist/doc/support/tlcockpit/screenshots/mainwindow.pngbin100565 -> 0 bytes
-rw-r--r--Master/texmf-dist/doc/support/tlcockpit/screenshots/pkgs-view.jpgbin0 -> 57766 bytes
-rw-r--r--Master/texmf-dist/doc/support/tlcockpit/screenshots/update-view.jpgbin0 -> 22768 bytes
-rw-r--r--Master/texmf-dist/scripts/tlcockpit/tlcockpit.jarbin11620031 -> 11646457 bytes
-rw-r--r--Master/texmf-dist/source/support/tlcockpit/build.sbt2
-rw-r--r--Master/texmf-dist/source/support/tlcockpit/src/main/scala/TLCockpit/ApplicationMain.scala418
-rw-r--r--Master/texmf-dist/source/support/tlcockpit/src/main/scala/TeXLive/TLBackup.scala9
-rw-r--r--Master/texmf-dist/source/support/tlcockpit/src/main/scala/TeXLive/TLPackage.scala23
-rw-r--r--Master/texmf-dist/source/support/tlcockpit/src/main/scala/TeXLive/TlmgrProcess.scala26
11 files changed, 339 insertions, 164 deletions
diff --git a/Master/texmf-dist/doc/support/tlcockpit/README.md b/Master/texmf-dist/doc/support/tlcockpit/README.md
index 7d6d09b3e4c..40e85c26f4d 100644
--- a/Master/texmf-dist/doc/support/tlcockpit/README.md
+++ b/Master/texmf-dist/doc/support/tlcockpit/README.md
@@ -3,18 +3,9 @@ TLCockpit - GUI frontend to tlmgr
TLCockpit aims at being a GUI for [tlmgr](https://www.tug.org/texlive/tlmgr.html),
the [TeX Live](https://www.tug.org/texlive/) Manager, with a modern look and feel.
+We take inspiration from the [TeX Live Utility](http://amaxwell.github.io/tlutility/).
-Supported functionality
------------------------
-
-While functionality is by now restricted to listing of packages, getting information
-on packages, and updating/installing/removing packages, further features will be added
-over time.
-
-For advanced users there is an input field allowing to enter arbitrary commands of the
-*tlmgr shell*, see [tlmgr manual](https://www.tug.org/texlive/doc/tlmgr.html#shell) for details.
-
Installing/Running
------------------
@@ -25,7 +16,7 @@ should suffice.
We are providing new jar files on a daily bases in the github page, but update
the copy in TeX Live only at certain intervals. If you want to try out the latest
and greatest, get [tlcockpit.jar](jar/tlcockpit.jar) and put it into
-`TEXLIVEROOT/temxf-dist/scripts/tlcockpit` replacing the current `tlcockpit.jar`.
+`TEXLIVEROOT/texmf-dist/scripts/tlcockpit` replacing the current `tlcockpit.jar`.
After this you can run it by simply typing `tlcockpit`, or via a direct `java`
call as in `java -jar PATH/TO/tlcockpit.jar`.
@@ -67,9 +58,17 @@ Screenshots
(permanently outdated)
-Main window
+Package listing
+
+![Package listing view](screenshots/pkgs-view.jpg)
+
+Updates listing
+
+![Updates listing view](screenshots/update-view.jpg)
+
+Backup/Restore listing
-![Main window](screenshots/mainwindow.png)
+![Backup listing view](screenshots/backup-view.jpg)
Package details
diff --git a/Master/texmf-dist/doc/support/tlcockpit/screenshots/backup-view.jpg b/Master/texmf-dist/doc/support/tlcockpit/screenshots/backup-view.jpg
new file mode 100644
index 00000000000..552ec831b14
--- /dev/null
+++ b/Master/texmf-dist/doc/support/tlcockpit/screenshots/backup-view.jpg
Binary files differ
diff --git a/Master/texmf-dist/doc/support/tlcockpit/screenshots/mainwindow.png b/Master/texmf-dist/doc/support/tlcockpit/screenshots/mainwindow.png
deleted file mode 100644
index b74fd22499d..00000000000
--- a/Master/texmf-dist/doc/support/tlcockpit/screenshots/mainwindow.png
+++ /dev/null
Binary files differ
diff --git a/Master/texmf-dist/doc/support/tlcockpit/screenshots/pkgs-view.jpg b/Master/texmf-dist/doc/support/tlcockpit/screenshots/pkgs-view.jpg
new file mode 100644
index 00000000000..ef0ca60ef4f
--- /dev/null
+++ b/Master/texmf-dist/doc/support/tlcockpit/screenshots/pkgs-view.jpg
Binary files differ
diff --git a/Master/texmf-dist/doc/support/tlcockpit/screenshots/update-view.jpg b/Master/texmf-dist/doc/support/tlcockpit/screenshots/update-view.jpg
new file mode 100644
index 00000000000..1d10628df52
--- /dev/null
+++ b/Master/texmf-dist/doc/support/tlcockpit/screenshots/update-view.jpg
Binary files differ
diff --git a/Master/texmf-dist/scripts/tlcockpit/tlcockpit.jar b/Master/texmf-dist/scripts/tlcockpit/tlcockpit.jar
index c581adb0880..4fe251977c0 100644
--- a/Master/texmf-dist/scripts/tlcockpit/tlcockpit.jar
+++ b/Master/texmf-dist/scripts/tlcockpit/tlcockpit.jar
Binary files differ
diff --git a/Master/texmf-dist/source/support/tlcockpit/build.sbt b/Master/texmf-dist/source/support/tlcockpit/build.sbt
index 75d9d200771..10d46ad0d15 100644
--- a/Master/texmf-dist/source/support/tlcockpit/build.sbt
+++ b/Master/texmf-dist/source/support/tlcockpit/build.sbt
@@ -1,6 +1,6 @@
name := "tlcockpit"
-version := "0.2"
+version := "0.3"
scalaVersion := "2.12.3"
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 f76a34d2f75..c281308d6b4 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,14 +6,21 @@
package TLCockpit
-import TeXLive.{TLPackage, TlmgrProcess}
+import javafx.collections.ObservableList
+import javafx.scene.control
+import TeXLive.{TLBackup, TLPackage, TlmgrProcess}
+
+import scala.collection.mutable
+import scala.collection.mutable.ArrayBuffer
import scala.concurrent.{Future, SyncVar}
import scala.concurrent.ExecutionContext.Implicits.global
-import scala.util.{Failure, Success}
+import scala.util.{Failure, Sorting, Success}
+import scalafx.beans.Observable
+import scalafx.beans.property.{ObjectProperty, ReadOnlyStringWrapper}
import scalafx.geometry.{HPos, Pos, VPos}
import scalafx.scene.control.Alert.AlertType
-
+import scalafx.scene.input.{KeyCode, KeyEvent}
// needed see https://github.com/scalafx/scalafx/issues/137
import scalafx.scene.control.TableColumn._
import scalafx.scene.control.Menu._
@@ -27,31 +34,30 @@ import scalafx.scene.control._
import scalafx.event.ActionEvent
import scalafx.collections.ObservableBuffer
import scala.sys.process._
-import scala.collection.mutable.ArrayBuffer
import scalafx.scene.text._
+import scalafx.collections.ObservableBuffer
object ApplicationMain extends JFXApp {
+ val version: String = getClass.getPackage.getImplementationVersion
+
// necessary action when Window is closed with X or some other operation
override def stopApp(): Unit = {
tlmgr.cleanup()
}
+ val pkgs: ArrayBuffer[TLPackage] = ArrayBuffer()
- val pkgs = ArrayBuffer[TLPackage]()
- val viewpkgs = ObservableBuffer[TLPackage]()
- val updpkgs = ObservableBuffer[TLPackage]()
+ val errorText: ObservableBuffer[String] = ObservableBuffer[String]()
+ val outputText: ObservableBuffer[String] = ObservableBuffer[String]()
- val errorText = ObservableBuffer[String]()
- val outputText = ObservableBuffer[String]()
-
- val outputfield = new TextArea {
+ val outputfield: TextArea = new TextArea {
editable = false
wrapText = true
text = ""
}
- val errorfield = new TextArea {
+ val errorfield: TextArea = new TextArea {
editable = false
wrapText = true
text = ""
@@ -65,18 +71,16 @@ object ApplicationMain extends JFXApp {
outputfield.scrollTop = Double.MaxValue
})
- val update_self_button = new Button {
- text = "Update self"
+ val update_all_menu: MenuItem = new MenuItem("Update all") {
+ onAction = (ae) => callback_update_all()
disable = true
- onAction = (e: ActionEvent) => callback_update_self()
}
- val update_all_button = new Button {
- text = "Update all"
+ val update_self_menu: MenuItem = new MenuItem("Update self") {
+ onAction = (ae) => callback_update_self()
disable = true
- onAction = (e: ActionEvent) => callback_update_all()
}
- val outerrtabs = new TabPane {
+ val outerrtabs: TabPane = new TabPane {
minWidth = 400
tabs = Seq(
new Tab {
@@ -91,7 +95,7 @@ object ApplicationMain extends JFXApp {
}
)
}
- val outerrpane = new TitledPane {
+ val outerrpane: TitledPane = new TitledPane {
text = "Debug"
collapsible = true
expanded = false
@@ -99,14 +103,19 @@ object ApplicationMain extends JFXApp {
}
val cmdline = new TextField()
+ cmdline.onKeyPressed = {
+ (ae: KeyEvent) => if (ae.code == KeyCode.Enter) callback_run_cmdline()
+ }
val tlmgr = new TlmgrProcess(
// (s:String) => outputfield.text = s,
(s: Array[String]) => {
- outputText.clear()
- outputText.appendAll(s)
+ // we don't wont normal output to be displayed
+ // as it is anyway returned
+ // outputText.clear()
+ // outputText.appendAll(s)
},
(s: String) => {
- errorText.clear()
+ // errorText.clear()
errorText.append(s)
if (s != "") {
outerrpane.expanded = true
@@ -118,27 +127,108 @@ object ApplicationMain extends JFXApp {
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)
+ 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 update_update_button_state(): Unit = {
- update_all_button.disable = !pkgs.foldLeft(false)(
+ def update_update_menu_state(): Unit = {
+ update_all_menu.disable = !pkgs.foldLeft(false)(
(a, b) => a || (if (b.name.value == "texlive.infra") false else b.lrev.value.toInt > 0 && b.lrev.value.toInt < b.rrev.value.toInt)
)
// TODO we should change pkgs to a Map with keys are the names + repository (for multi repo support)
// and the values are llrev/rrev or some combination
- update_self_button.disable = !pkgs.foldLeft(false)(
+ update_self_menu.disable = !pkgs.foldLeft(false)(
(a, b) => a || (if (b.name.value == "texlive.infra") b.lrev.value.toInt < b.rrev.value.toInt else false)
)
+ updateTable.refresh()
+ packageTable.refresh()
+ }
+
+ def update_pkg_lists(): Unit = {
+ tlmgr_async_command("info --data name,localrev,remoterev,shortdesc,size,installed", (s: Array[String]) => {
+ pkgs.clear()
+ 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"
+ pkgs += new TLPackage(fields(0), fields(1), fields(2), shortdesc, fields(4), inst)
+ })
+ val pkgbuf: ArrayBuffer[TLPackage] = ArrayBuffer.empty[TLPackage]
+ val binbuf = scala.collection.mutable.Map.empty[String, ArrayBuffer[TLPackage]]
+ val updbuf: ArrayBuffer[TLPackage] = ArrayBuffer.empty[TLPackage]
+ pkgs.foreach(pkg => {
+ if (pkg.lrev.value > 0 && pkg.lrev.value < pkg.rrev.value) {
+ // easy part: in the update tab show all packages in raw format
+ // TODO if the main package is updated, too, use a child item?
+ updbuf += 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.name.value.stripPrefix("texlive.").contains(".")) {
+ val foo: Array[String] = pkg.name.value.stripPrefix("texlive.infra").split('.')
+ val pkgname = foo(0)
+ val binname = foo(1)
+ if (binbuf.keySet.contains(pkgname)) {
+ binbuf(pkgname) += pkg
+ } else {
+ binbuf(pkgname) = ArrayBuffer[TLPackage](pkg)
+ }
+ } else {
+ pkgbuf += pkg
+ }
+ })
+ // now we have all normal packages in pkgbuf, and its sub-packages in binbuf
+ // we need to create TreeItems
+ val viewkids = pkgbuf.map { p => {
+ val kids: Seq[TLPackage] = if (binbuf.keySet.contains(p.name.value)) {
+ binbuf(p.name.value)
+ } else {
+ Seq()
+ }
+ // 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)
+ if (!allinstalled) {
+ // 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](_))
+ }
+ } else {
+ new TreeItem[TLPackage](p) {
+ children = kids.map(new TreeItem[TLPackage](_))
+ }
+ }
+ }}
+ val updkids = updbuf.map { p => new TreeItem[TLPackage](p) }
+ Platform.runLater {
+ packageTable.root = new TreeItem[TLPackage](new TLPackage("root","0","0","","0","")) {
+ expanded = true
+ children = viewkids
+ }
+ updateTable.root = new TreeItem[TLPackage](new TLPackage("root","0","0","","0","")) {
+ expanded = true
+ children = updkids
+ }
+ }
+ updateTable.refresh()
+ packageTable.refresh()
+ update_update_menu_state()
+ })
}
def callback_quit(): Unit = {
@@ -152,9 +242,11 @@ object ApplicationMain extends JFXApp {
}
def callback_run_cmdline(): Unit = {
- callback_run_text(cmdline.text.value)
- outerrpane.expanded = true
- outerrtabs.selectionModel().select(0)
+ tlmgr_async_command(cmdline.text.value, s => {
+ outputText.appendAll(s)
+ outerrpane.expanded = true
+ outerrtabs.selectionModel().select(0)
+ })
}
def not_implemented_info(): Unit = {
@@ -194,53 +286,61 @@ object ApplicationMain extends JFXApp {
new Alert(AlertType.Information) {
initOwner(stage)
title = "About TLCockpit"
- headerText = "TLCockpit version 0.2\nLicense: GPL3+"
+ headerText = "TLCockpit version " + version + "\nLicense: GPL3+"
contentText = "Brought to you by Norbert\nSources: https://github.com/TeX-Live/tlcockpit"
}.showAndWait()
}
- /* def callback_show_all(): Unit = {
- viewpkgs.clear()
- pkgs.map(viewpkgs += _)
+ def callback_update_all(): Unit = {
+ // TODO parse table and update line by line
+ tlmgr_async_command("update --all", _ => { Platform.runLater { update_pkg_lists() } })
}
- def callback_show_installed(): Unit = {
- viewpkgs.clear()
- pkgs.map(p => if (p.lrev.value.toInt > 0) viewpkgs += p)
+ def callback_update_self(): Unit = {
+ // TODO should we restart tlmgr here - it might be necessary!!!
+ tlmgr_async_command("update --self", _ => { Platform.runLater { update_pkg_lists() } })
}
- def callback_show_updates(): Unit = {
- val newpkgs = ArrayBuffer[TLPackage]()
- pkgs.map(p =>
- // TODO we are not dealing with not integer values by now!
- if (p.lrev.value.toInt > 0 && p.lrev.value.toInt < p.rrev.value.toInt) {
- newpkgs += p
- }
- )
- viewpkgs.clear()
- newpkgs.map(viewpkgs += _)
+ def do_one_pkg(what: String, pkg: String): Unit = {
+ tlmgr_async_command(s"$what $pkg", _ => { Platform.runLater { update_pkg_lists() } })
}
- */
- def callback_update_all(): Unit = {
- tlmgr_async_command("update --all", s => {
- update_update_button_state()
- // callback_show_all()
- })
+ def callback_restore_pkg(str: String, rev: String): Unit = {
+ not_implemented_info()
}
- def callback_update_self(): Unit = {
- // TODO
- // should we restart tlmgr here - it might be necessary!!!
- tlmgr_async_command("update --self", s => {
- update_update_button_state()
- // callback_show_all()
- })
+ def callback_populate_backup_tab(): Unit = {
+ // only run the restore command once
+ if (backupTable.root.value.children.length == 0) {
+
+ tlmgr_async_command("restore", lines => {
+ // lines.drop(1).foreach(println(_))
+ val foo = lines.drop(1).map { l =>
+ val fields = l.split("[ ():]", -1).filter(_.nonEmpty)
+ val pkgname = fields(0)
+ val rests: Array[Array[String]] = fields.drop(1).sliding(4, 4).toArray
+ if (rests.length == 1) {
+ val p = rests(0)
+ new TreeItem[TLBackup](new TLBackup(pkgname, p(0), p(1) + " " + p(2) + ":" + p(3)))
+ } else {
+ new TreeItem[TLBackup](new TLBackup(pkgname, "", "")) {
+ children = rests.map(p => new TreeItem[TLBackup](
+ new TLBackup(pkgname, p(0), p(1) + " " + p(2) + ":" + p(3))
+ )).toSeq
+ }
+ }
+ }
+ val newroot = new TreeItem[TLBackup](new TLBackup("root", "", "")) {
+ children = foo
+ }
+ Platform.runLater {
+ backupTable.root = newroot
+ }
+ })
+ }
}
-
def callback_show_pkg_info(pkg: String): Unit = {
- // val pkginfo = tlmgr_command(s"info $pkg")
tlmgr_async_command(s"info $pkg", pkginfo => {
// need to call runLater to go back to main thread!
Platform.runLater {
@@ -276,8 +376,12 @@ object ApplicationMain extends JFXApp {
})
}
+
val mainMenu = new Menu("TLCockpit") {
items = List(
+ update_all_menu,
+ update_self_menu,
+ new SeparatorMenuItem,
new MenuItem("Update filename database ...") {
onAction = (ae) => callback_run_external("mktexlsr")
},
@@ -346,6 +450,9 @@ object ApplicationMain extends JFXApp {
},
)
}
+ val statusMenu = new Menu("Status: Idle") {
+ disable = true
+ }
val expertPane = new TitledPane {
text = "Experts only"
collapsible = true
@@ -355,11 +462,9 @@ object ApplicationMain extends JFXApp {
children = List(
new HBox {
spacing = 10
+ alignment = Pos.CenterLeft
children = List(
- new Label("tlmgr shell command:") {
- // TODO vertical text alignment does not work
- textAlignment = TextAlignment.Center
- },
+ new Label("tlmgr shell command:"),
cmdline,
new Button {
text = "Go"
@@ -391,51 +496,59 @@ object ApplicationMain extends JFXApp {
}
}
val updateTable = {
- val colName = new TableColumn[TLPackage, String] {
+ val colName = new TreeTableColumn[TLPackage, String] {
text = "Package"
- cellValueFactory = { _.value.name }
+ cellValueFactory = { _.value.value.value.name }
prefWidth = 150
}
- val colDesc = new TableColumn[TLPackage, String] {
+ val colDesc = new TreeTableColumn[TLPackage, String] {
text = "Description"
- cellValueFactory = { _.value.shortdesc }
+ cellValueFactory = { _.value.value.value.shortdesc }
prefWidth = 300
}
- val colLRev = new TableColumn[TLPackage, String] {
+ val colLRev = new TreeTableColumn[TLPackage, Int] {
text = "Local rev"
- cellValueFactory = { _.value.lrev }
+ cellValueFactory = { _.value.value.value.lrev }
prefWidth = 100
}
- val colRRev = new TableColumn[TLPackage, String] {
+ val colRRev = new TreeTableColumn[TLPackage, Int] {
text = "Remote rev"
- cellValueFactory = { _.value.rrev }
+ cellValueFactory = { _.value.value.value.rrev }
prefWidth = 100
}
- val colSize = new TableColumn[TLPackage, String] {
+ val colSize = new TreeTableColumn[TLPackage, Int] {
text = "Size"
- cellValueFactory = { _.value.size }
+ cellValueFactory = { _.value.value.value.size }
prefWidth = 100
}
- val table = new TableView[TLPackage](updpkgs) {
+ val table = new TreeTableView[TLPackage](
+ new TreeItem[TLPackage](new TLPackage("root","0","0","","0","")) {
+ expanded = false
+ }) {
columns ++= List(colName, colDesc, colLRev, colRRev, colSize)
}
- colDesc.prefWidth.bind(table.width - colName.width - colLRev.width - colRRev.width - colSize.width)
+ colDesc.prefWidth.bind(table.width - colName.width - colLRev.width - colRRev.width - colSize.width - 15)
table.prefHeight = 300
table.vgrow = Priority.Always
+ table.placeholder = new Label("No updates available")
+ table.showRoot = false
table.rowFactory = { _ =>
- val row = new TableRow[TLPackage] {}
+ 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) => callback_run_text("install " + row.item.value.name.value)
+ // 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) => 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_run_text("update " + row.item.value.name.value)
+ onAction = (ae) => do_one_pkg("update", row.item.value.name.value)
}
)
row.contextMenu = ctm
@@ -444,11 +557,12 @@ object ApplicationMain extends JFXApp {
table
}
val packageTable = {
- val colName = new TableColumn[TLPackage, String] {
+ val colName = new TreeTableColumn[TLPackage, String] {
text = "Package"
- cellValueFactory = { _.value.name }
+ cellValueFactory = { _.value.value.value.name }
prefWidth = 150
}
+ /*
val colLRev = new TableColumn[TLPackage, String] {
text = "Local rev"
cellValueFactory = { _.value.lrev }
@@ -459,36 +573,82 @@ object ApplicationMain extends JFXApp {
cellValueFactory = { _.value.rrev }
prefWidth = 100
}
- val colDesc = new TableColumn[TLPackage, String] {
+ */
+ val colDesc = new TreeTableColumn[TLPackage, String] {
text = "Description"
- cellValueFactory = { _.value.shortdesc }
+ cellValueFactory = { _.value.value.value.shortdesc }
prefWidth = 300
}
- val colInst = new TableColumn[TLPackage, String] {
+ val colInst = new TreeTableColumn[TLPackage, String] {
text = "Installed"
- cellValueFactory = { _.value.installed }
- prefWidth = 30
+ cellValueFactory = { _.value.value.value.installed }
+ prefWidth = 100
}
- val table = new TableView[TLPackage](viewpkgs) {
- columns ++= List(colName, colDesc, colInst, colLRev, colRRev)
+ val table = new TreeTableView[TLPackage](
+ new TreeItem[TLPackage](new TLPackage("root","0","0","","0","")) {
+ expanded = false
+ }) {
+ columns ++= List(colName, colDesc, colInst)
}
- colDesc.prefWidth.bind(table.width - colLRev.width - colRRev.width - colInst.width - colName.width)
+ colDesc.prefWidth.bind(table.width - colInst.width - colName.width - 15)
table.prefHeight = 300
+ table.showRoot = false
table.vgrow = Priority.Always
table.rowFactory = { _ =>
- val row = new TableRow[TLPackage] {}
+ 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) => 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) => do_one_pkg("update", row.item.value.name.value)
+ }
+ )
+ row.contextMenu = ctm
+ row
+ }
+ table
+ }
+ val backupTable = {
+ val colName = new TreeTableColumn[TLBackup, String] {
+ text = "Package"
+ cellValueFactory = { _.value.value.value.name }
+ prefWidth = 150
+ }
+ val colRev = new TreeTableColumn[TLBackup, String] {
+ text = "Revision"
+ cellValueFactory = { _.value.value.value.rev }
+ prefWidth = 100
+ }
+ val colDate = new TreeTableColumn[TLBackup, String] {
+ text = "Date"
+ cellValueFactory = { _.value.value.value.date }
+ prefWidth = 300
+ }
+ val table = new TreeTableView[TLBackup](
+ new TreeItem[TLBackup](new TLBackup("root","","")) {
+ expanded = false
+ }) {
+ columns ++= List(colName, colRev, colDate)
+ }
+ colDate.prefWidth.bind(table.width - colRev.width - colName.width - 15)
+ table.prefHeight = 300
+ table.showRoot = false
+ table.vgrow = Priority.Always
+ table.rowFactory = { _ =>
+ val row = new TreeTableRow[TLBackup] {}
+ val ctm = new ContextMenu(
+ new MenuItem("Info") {
+ onAction = (ae) => callback_show_pkg_info(row.item.value.name.value)
+ },
+ new MenuItem("Restore") {
+ onAction = (ae) => callback_restore_pkg(row.item.value.name.value, row.item.value.rev.value)
}
)
row.contextMenu = ctm
@@ -509,18 +669,38 @@ object ApplicationMain extends JFXApp {
text = "Packages"
closable = false
content = packageTable
+ },
+ new Tab {
+ text = "Backups"
+ closable = false
+ content = backupTable
}
)
}
+ pkgstabs.selectionModel().selectedItem.onChange(
+ (a,b,c) => {
+ if (a.value.text() == "Backups") {
+ callback_populate_backup_tab()
+ // println("Entering Backup Tab")
+ }
+ }
+ )
+ val menuBar = new MenuBar {
+ useSystemMenuBar = true
+ // menus.addAll(mainMenu, optionsMenu, helpMenu)
+ menus.addAll(mainMenu, statusMenu)
+ }
+
stage = new PrimaryStage {
title = "TLCockpit"
scene = new Scene {
root = {
- val topBox = new MenuBar {
- useSystemMenuBar = true
- // menus.addAll(mainMenu, optionsMenu, helpMenu)
- menus.addAll(mainMenu)
- }
+ // val topBox = new HBox {
+ // children = List(menuBar, statusLabel)
+ // }
+ // topBox.hgrow = Priority.Always
+ // topBox.maxWidth = Double.MaxValue
+ val topBox = menuBar
val centerBox = new VBox {
padding = Insets(10)
children = List(pkgstabs, expertPane, outerrpane)
@@ -543,6 +723,7 @@ object ApplicationMain extends JFXApp {
startalert.show()
*/
+ /*
var testmode = false
if (parameters.unnamed.nonEmpty) {
if (parameters.unnamed.head == "-test" || parameters.unnamed.head == "--test") {
@@ -550,38 +731,11 @@ object ApplicationMain extends JFXApp {
testmode = true
}
}
+ */
- // Thread.sleep(3000)
-
- if (!testmode) {
- tlmgr.start_process()
-
- // wait until we got a prompt
- val foo: Array[String] = tlmgr.get_output_till_prompt()
- // println("current output: ")
- // foo.map(println(_))
- }
-
- // load the initial set of packages
- var pkglines: Array[String] = Array()
-
- // tlmgr loads local and remote if we request localrev and remoterev!
- tlmgr_async_command("info --data name,localrev,remoterev,shortdesc,size,installed", (s: Array[String]) => {
- val newpkgs = ArrayBuffer[TLPackage]()
- 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("""\"""",""""""")
- pkgs += new TLPackage(fields(0), fields(1), fields(2), shortdesc, fields(4), fields(5))
- })
- pkgs.map(pkg => {
- viewpkgs += pkg
- if (pkg.installed.value == "1" && pkg.lrev.value.toInt < pkg.rrev.value.toInt) updpkgs += pkg
- })
- // check for updates available
- update_update_button_state()
- // Platform.runLater { startalert.close() }
- })
+ tlmgr.start_process()
+ tlmgr.get_output_till_prompt()
+ update_pkg_lists() // this is async done
} // object ApplicationMain
diff --git a/Master/texmf-dist/source/support/tlcockpit/src/main/scala/TeXLive/TLBackup.scala b/Master/texmf-dist/source/support/tlcockpit/src/main/scala/TeXLive/TLBackup.scala
new file mode 100644
index 00000000000..a00c1464f30
--- /dev/null
+++ b/Master/texmf-dist/source/support/tlcockpit/src/main/scala/TeXLive/TLBackup.scala
@@ -0,0 +1,9 @@
+package TeXLive
+
+import scalafx.beans.property.StringProperty
+
+
+case class TLBackup(name: StringProperty, rev: StringProperty, date: StringProperty) {
+ def this(_name: String, _rev: String, _date: String) =
+ this(StringProperty(_name), StringProperty(_rev), StringProperty(_date))
+}
diff --git a/Master/texmf-dist/source/support/tlcockpit/src/main/scala/TeXLive/TLPackage.scala b/Master/texmf-dist/source/support/tlcockpit/src/main/scala/TeXLive/TLPackage.scala
index cb9d8c7e88e..9a797d9c8ae 100644
--- a/Master/texmf-dist/source/support/tlcockpit/src/main/scala/TeXLive/TLPackage.scala
+++ b/Master/texmf-dist/source/support/tlcockpit/src/main/scala/TeXLive/TLPackage.scala
@@ -1,14 +1,15 @@
package TeXLive
-import scalafx.beans.property.StringProperty
-import scalafx.beans.property.IntegerProperty
-import scalafx.beans.property.BooleanProperty
+import scalafx.beans.property.{ObjectProperty, StringProperty}
-class TLPackage(name_ : String, lrev_ : String, rrev_ : String, shortdesc_ : String, size_ : String, installed_ : String) {
- val name = new StringProperty(this, "name", name_)
- val shortdesc = new StringProperty(this, "shortdesc", shortdesc_)
- val lrev = new StringProperty(this, "revision", lrev_)
- val rrev = new StringProperty(this, "revision", rrev_)
- val size = new StringProperty(this, "size", size_)
- val installed = new StringProperty(this, "installed", installed_)
-}
+// Note!!! we have to use ObjectProperty[Int] here instead of IntegerProperty
+// since IntegerProperty does NOT implement Observable[Int,Int]
+// see https://github.com/scalafx/scalafx/issues/243
+case class TLPackage(name: StringProperty, lrev: ObjectProperty[Int], rrev: ObjectProperty[Int],
+ shortdesc: StringProperty, size: ObjectProperty[Int], installed: StringProperty) {
+ def this(_name: String, _lrev: String, _rrev: String, _shortdesc: String, _size: String, _installed: String) =
+ this(
+ StringProperty(_name), ObjectProperty[Int](_lrev.toInt), ObjectProperty[Int](_rrev.toInt),
+ StringProperty(_shortdesc), ObjectProperty[Int](_size.toInt), StringProperty(_installed)
+ )
+} \ No newline at end of file
diff --git a/Master/texmf-dist/source/support/tlcockpit/src/main/scala/TeXLive/TlmgrProcess.scala b/Master/texmf-dist/source/support/tlcockpit/src/main/scala/TeXLive/TlmgrProcess.scala
index 8984ab7a1af..360ad9bc433 100644
--- a/Master/texmf-dist/source/support/tlcockpit/src/main/scala/TeXLive/TlmgrProcess.scala
+++ b/Master/texmf-dist/source/support/tlcockpit/src/main/scala/TeXLive/TlmgrProcess.scala
@@ -17,18 +17,32 @@ class TlmgrProcess(updout: Array[String] => Unit, upderr: String => Unit) {
var process: Process = _
var lastInput: String = ""
var lastOk: Boolean = false
+ var isBusy = false
+
def send_command(input: String): Array[String] = {
lastInput = input
+ val maxWaitTime = 5000
+ var waited = 0
+ while (isBusy) {
+ // do busy waiting here in case some other tlmgr command is running
+ // println("Debug: waiting for tlmgr being ready, want to send " + input)
+ Thread.sleep(300)
+ waited += 300
+ if (waited > maxWaitTime) {
+ throw new Exception("tlmgr busy, waited too long, aborting calling: " + input)
+ }
+ }
try {
// pass the input and wait for the output
assert(!inputString.isSet)
assert(!outputString.isSet)
- errorBuffer.setLength(0)
+ // errorBuffer.setLength(0)
+ synchronized(isBusy = true)
inputString.put(input)
- var ret = if (input != "quit") {
+ val ret = if (input != "quit") {
get_output_till_prompt()
} else {
new Array[String](0)
@@ -36,6 +50,7 @@ class TlmgrProcess(updout: Array[String] => Unit, upderr: String => Unit) {
// updout(ret.mkString("\n"))
updout(ret)
upderr(errorBuffer.toString)
+ synchronized( isBusy = false )
ret
} catch {
case exc: Throwable =>
@@ -58,6 +73,7 @@ class TlmgrProcess(updout: Array[String] => Unit, upderr: String => Unit) {
var ret = ArrayBuffer[String]()
var result = ""
var found = false
+ synchronized(isBusy = true)
while (!found) {
result = outputString.take()
if (result == "tlmgr> ") {
@@ -71,6 +87,7 @@ class TlmgrProcess(updout: Array[String] => Unit, upderr: String => Unit) {
ret += result
}
}
+ synchronized(isBusy = false)
ret.toArray
}
@@ -78,10 +95,6 @@ class TlmgrProcess(updout: Array[String] => Unit, upderr: String => Unit) {
if (process != null) {
send_command("quit")
process.destroy()
- // we'll need to unblock the input again
- // TODO what is that needed for???
- // if (!inputString.isSet) inputString.put("")
- // if (outputString.isSet) outputString.take()
}
}
@@ -111,7 +124,6 @@ class TlmgrProcess(updout: Array[String] => Unit, upderr: String => Unit) {
private[this] def outputFn(stdOut: InputStream): Unit = {
val reader = new BufferedReader(new InputStreamReader(stdOut))
- val buffer: StringBuilder = new StringBuilder()
try {
var line: String = ""
while (true) {