diff options
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.scala | 482 |
1 files changed, 482 insertions, 0 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 new file mode 100644 index 00000000000..d7cad7021af --- /dev/null +++ b/Master/texmf-dist/source/support/tlcockpit/src/main/scala/TLCockpit/ApplicationMain.scala @@ -0,0 +1,482 @@ +// TLCockpit +// Copyright 2017 Norbert Preining +// Licensed according to GPLv3+ +// +// Front end for tlmgr + +package TLCockpit + +import TeXLive.{TLPackage, TlmgrProcess} + +import scala.concurrent.Future +import scala.concurrent.ExecutionContext.Implicits.global +import scala.util.{Failure, Success} +import scalafx.geometry.HPos +import scalafx.scene.control.Alert.AlertType + +// needed see https://github.com/scalafx/scalafx/issues/137 +import scalafx.scene.control.TableColumn._ +import scalafx.scene.control.Menu._ +import scalafx.Includes._ +import scalafx.application.{JFXApp, Platform} +import scalafx.application.JFXApp.PrimaryStage +import scalafx.geometry.Insets +import scalafx.scene.Scene +import scalafx.scene.layout._ +import scalafx.scene.control._ +import scalafx.event.ActionEvent +import scalafx.collections.ObservableBuffer +import scala.sys.process._ +import scala.collection.mutable.ArrayBuffer + + +object ApplicationMain extends JFXApp { + + // necessary action when Window is closed with X or some other operation + override def stopApp(): Unit = { + tlmgr.cleanup() + } + + + val pkgs = ArrayBuffer[TLPackage]() + val viewpkgs = ObservableBuffer[TLPackage]() + + val outputfield = new TextArea { + editable = false + wrapText = true + } + val errorfield = new TextArea { + editable = false + wrapText = true + } + + val update_self_button = new Button { + text = "Update self" + disable = true + onAction = (e: ActionEvent) => callback_update_self() + } + val update_all_button = new Button { + text = "Update all" + disable = true + onAction = (e: ActionEvent) => callback_update_all() + } + + val outerrtabs = new TabPane { + minWidth = 400 + tabs = Seq( + new Tab { + text = "Output" + closable = false + content = outputfield + }, + new Tab { + text = "Error" + closable = false + content = errorfield + } + ) + } + val outerrpane = new TitledPane { + text = "Debug" + collapsible = true + expanded = false + content = outerrtabs + } + + val cmdline = new TextField() + val tlmgr = new TlmgrProcess((s:String) => outputfield.text = s, + (s:String) => { + errorfield.text = s + if (s != "") { + outerrpane.expanded = true + val selmod = outerrtabs.selectionModel() + selmod.select(1) + } + }) + + def tlmgr_async_command(s: String, f: Array[String] => Unit): Unit = { + errorfield.text = "" + outputfield.text = "" + outerrpane.expanded = false + val foo = Future { tlmgr.send_command(s) } + foo onComplete { + case Success(ret) => f(ret) + case Failure(t) => println("An ERROR has occurred: " + t.getMessage) + } + } + + def update_update_button_state(): Unit = { + update_all_button.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)( + (a,b) => a || (if (b.name.value == "texlive.infra") b.lrev.value.toInt < b.rrev.value.toInt else false) + ) + } + + def callback_quit(): Unit = { + tlmgr.cleanup() + Platform.exit() + sys.exit(0) + } + + def callback_load_update_info(s:String, strs: Array[String]): Unit = { + if (s == "remote") { + tlmgr_async_command("info --data name,localrev,remoterev,shortdesc", (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("""\"""",""""""") + newpkgs += new TLPackage(fields(0), fields(1), fields(2), shortdesc) + }) + pkgs.clear() + newpkgs.map(pkgs += _) + viewpkgs.clear() + pkgs.map(viewpkgs += _) + // check for updates available + update_update_button_state() + }) + } + } + + def callback_load(s: String): Unit = { + tlmgr_async_command("load " + s, callback_load_update_info(s, _) ) + } + + def callback_run_text(s: String): Unit = { + tlmgr_async_command(s, (s: Array[String]) => {}) + // tlmgr_command(s) + } + def callback_run_cmdline(): Unit = { + callback_run_text(cmdline.text.value) + } + + def callback_list_collections(): Unit = { + tlmgr_async_command("info collections", foo => { + val foo: Array[String] = Array() + // println("got result from info collections: ") + // foo.map(println(_)) + outputfield.text = foo.mkString("\n") + errorfield.text = tlmgr.errorBuffer.toString + }) + } + + def not_implemented_info(): Unit = { + new Alert(AlertType.Warning) { + initOwner(stage) + title = "Warning" + headerText = "This functionality is not implemented by now!" + contentText = "Sorry for the inconveniences." + }.showAndWait() + } + + def callback_run_external(s: String): Unit = { + val foo = Future { s.!! } + foo.onComplete { + case Success(ret) => outputfield.text = ret.mkString("\n") + case Failure(t) => println("An ERROR has occurred: " + t.getMessage) + } + } + + def callback_about(): Unit = { + new Alert(AlertType.Information) { + initOwner(stage) + title = "About TLCockpit" + headerText = "TLCockpit version 0.2\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_show_installed() : Unit = { + viewpkgs.clear() + pkgs.map(p => if (p.lrev.value.toInt > 0) viewpkgs += p) + } + 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 callback_update_all() : Unit = { + tlmgr_async_command("update --all", s => { + update_update_button_state() + callback_show_all() + }) + } + 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_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 { + 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 grid = new GridPane() { + hgap = 10 + vgap = 10 + padding = Insets(20) + } + var crow = 0 + pkginfo.foreach((line: String) => { + val keyval = line.split(":", 2).map(_.trim) + if (keyval.length == 2) { + 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 + } + }) + grid.columnConstraints = Seq(new ColumnConstraints(100, 150, 200), new ColumnConstraints(100, 300, 5000, Priority.Always, new HPos(HPos.Left), true)) + dialog.dialogPane().content = grid + dialog.width = 500 + dialog.showAndWait() + } + }) + } + + 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 (!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(_)) + } + + stage = new PrimaryStage { + title = "TLCockpit" + scene = new Scene { + root = { + val topBox = new MenuBar { + useSystemMenuBar = true + menus.add(new Menu("TLCockpit") { + items = List( + new MenuItem("Load default (from tlpdb) repository") { + onAction = (ae: ActionEvent) => callback_load("remote") + }, + new MenuItem("Load standard net repository") { + onAction = (ae: ActionEvent) => not_implemented_info() + disable = true + }, + new MenuItem("Load other repository ...") { + onAction = (ae: ActionEvent) => not_implemented_info() + disable = true + }, + new SeparatorMenuItem, + new MenuItem("Exit") { + onAction = (ae: ActionEvent) => callback_quit() + } + ) + }) + menus.add(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() }, + 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 } + ) + }) + menus.add(new Menu("Actions") { + items = List( + new MenuItem("Update filename database ...") { onAction = (ae) => callback_run_external("mktexlsr") }, + new MenuItem("Rebuild all formats ...") { onAction = (ae) => callback_run_external("fmtutil --sys --all") }, + 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() } + ) + }) + menus.add(new Menu("Help") { + items = List( + new MenuItem("Manual") { disable = true; onAction = (ae) => not_implemented_info() }, + new MenuItem("About") { onAction = (ae) => callback_about() }, + ) + }) + } + val centerBox = new VBox { + padding = Insets(10) + children = List( + new TitledPane { + text = "Actions" + collapsible = false + content = new VBox { + spacing = 10 + children = List( + new HBox { + spacing = 10 + children = List( + cmdline, + new Button { + text = "Go" + onAction = (event: ActionEvent) => callback_run_cmdline() + }, + new Button { + text = "Load repository"; + onAction = (event: ActionEvent) => callback_load("remote") + } + ) + }, + new HBox { + spacing = 10 + children = List( + new Button { + text = "Show updates" + onAction = (e: ActionEvent) => callback_show_updates() + }, + new Button { + text = "Show installed" + onAction = (e: ActionEvent) => callback_show_installed() + }, + new Button { + text = "Show all" + onAction = (e: ActionEvent) => callback_show_all() + }, + update_self_button, + update_all_button + ) + } + ) + } + }, + outerrpane, + { + val col1 = new TableColumn[TLPackage, String] { + text = "Package" + cellValueFactory = { + _.value.name + } + prefWidth = 150 + } + val col2 = new TableColumn[TLPackage, String] { + text = "Local rev" + cellValueFactory = { + _.value.lrev + } + prefWidth = 100 + } + val col3 = new TableColumn[TLPackage, String] { + text = "Remote rev" + cellValueFactory = { + _.value.rrev + } + prefWidth = 100 + } + val col4 = new TableColumn[TLPackage, String] { + text = "Description" + cellValueFactory = { + _.value.shortdesc + } + prefWidth = 300 + } + val table = new TableView[TLPackage](viewpkgs) { + columns ++= List(col1,col2,col3,col4) + } + col4.prefWidth.bind(table.width - col1.width - col2.width - col3.width) + table.prefHeight = 300 + table.vgrow = Priority.Always + table.rowFactory = { _ => + val row = new TableRow[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) + }, + new MenuItem("Remove") { + onAction = (ae) => callback_run_text("remove " + row.item.value.name.value) + }, + new MenuItem("Update") { + onAction = (ae) => callback_run_text("update " + row.item.value.name.value) + } + ) + row.contextMenu = ctm + row + } + table + }, + ) + } + /* val bottomBox = new HBox { + padding = Insets(10) + spacing = 10 + children = List( + new Button { + text = "Quit" + onAction = (event: ActionEvent) => callback_quit() + } + ) + } + */ + new BorderPane { + // padding = Insets(20) + top = topBox + // left = leftBox + center = centerBox + // bottom = bottomBox + } + } + } + } + + 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() + + // load the initial set of packages + var pkglines: Array[String] = Array() + + tlmgr_async_command("info --only-installed --data name,localrev,shortdesc", (s: Array[String]) => { + s.map(line => { + val fields: Array[String] = line.split(",", -1) + val sd = if (fields(2).isEmpty) "" else fields(2).substring(1).dropRight(1).replace("""\"""",""""""") + pkgs += new TLPackage(fields(0), fields(1), "0", sd) + }) + viewpkgs.clear() + pkgs.map(viewpkgs += _) + Platform.runLater { startalert.close() } + }) + + +} // object ApplicationMain + +// vim:set tabstop=2 expandtab : // |