summaryrefslogtreecommitdiff
path: root/Master/texmf-dist/scripts/tlshell
diff options
context:
space:
mode:
Diffstat (limited to 'Master/texmf-dist/scripts/tlshell')
-rwxr-xr-xMaster/texmf-dist/scripts/tlshell/tlshell.tcl774
1 files changed, 473 insertions, 301 deletions
diff --git a/Master/texmf-dist/scripts/tlshell/tlshell.tcl b/Master/texmf-dist/scripts/tlshell/tlshell.tcl
index 534acb4f1de..a46d1b4bab9 100755
--- a/Master/texmf-dist/scripts/tlshell/tlshell.tcl
+++ b/Master/texmf-dist/scripts/tlshell/tlshell.tcl
@@ -15,6 +15,8 @@ package require Tk
# This directory will be prepended to the searchpath.
# kpsewhich will disentangle symlinks.
+##### general housekeeping #####
+
# security: disable send
catch {rename send {}}
@@ -27,7 +29,7 @@ proc do_debug {s} {
}
}
-proc get_stack {} {
+proc get_stacktrace {} {
set level [info level]
set s ""
for {set i 1} {$i < $level} {incr i} {
@@ -36,80 +38,53 @@ proc get_stack {} {
return $s
}
-# unicode characters as tag indicators, in hex. This is pretty safe,
-# since Tk will use glyphs from other fonts if necessary.
-# With bitmaps, we would have to worry about HiDpi displays.
+proc maketemp {ext} {
+ set fname ""
+ foreach i {0 1 2 3 4 5 6 7 8 9} { ; # ten tries
+ set fname [file join $::tempsub "[expr int(10000.0*rand())]$ext"]
+ if {[file exist $fname]} {set fname ""; continue}
+ # create empty file. although we just want a name,
+ # we must make sure that it can be created.
+ set fid [open $fname w]
+ close $fid
+ if {! [file exists $fname]} {error "Cannot create temporary file"}
+ if {$::tcl_platform(platform) eq "unix"} {
+ file attributes $fname -permissions 0600
+ }
+ break
+ }
+ if {$fname eq ""} {error "Cannot create temporary file"}
+ return $fname
+} ; # maketemp
-set mrk [format %c 0x25a3] ; # 'white square containing black small square'
-set nomrk [format %c 0x25a1] ; # 'white square'
+set tempsub "" ; # subdir for temp files, set during initialization
+
+##### tl global status variables #####
set progname [info script]
regexp {^.*[\\/]([^\\/\.]*)(?:\....)?$} $progname dummy progname
set procid [pid]
-set tempsub "" ; # subdirectory for temporary files
+# package repository
+set repo ""
# the stderr and stdout of tlmgr are each read into a list of strings
set err_log {}
set out_log {}
-# callback for reading tlmgr pipe
-set pipe_cb ""
-set lnum -1
-
# dict of (local and global) package dicts
set pkgs [dict create]
-set do_remote 0 ; # get local packages
-set have_remote 0 ; # remote packages loaded
+set have_remote 0 ; # remote packages info loaded
+set need_update_self 0
+set n_updates 0
+set tlshell_updatable 0
-# filtering the package list, parameter should be package NAME
-set filt ""
-
-proc filt_local {p} {
- return [dict get $::pkgs $p localrev]
-}
-
-proc filt_collections {p} {
- set c [dict get $::pkgs $p "category"]
- return [expr \"$c\" eq \"Collection\"]
-}
-
-proc filt_upgradable {p} {
- do_debug "Is $p upgradable?"
- set lr [dict get $::pkgs $p localrev]
- set rr [dict get $::pkgs $p remoterev]
- do_debug "$p: comparing $lr and $rr"
- if {$lr == 0} {
- return 0
- } elseif {$rr == 0} {
- return 0
- }
- return [expr $rr > $lr]
-}
+##### handling tlmgr via pipe and stderr tempfile #####
set prmpt "tlmgr>"
set busy 0
-proc maketemp {ext} {
- set fname ""
- foreach i {0 1 2 3 4 5 6 7 8 9} { ; # ten tries
- set fname [file join $::tempsub "[expr int(10000.0*rand())]$ext"]
- if {[file exist $fname]} {set fname ""; continue}
- # create empty file. although we just want a name,
- # we must make sure that it can be created.
- set fid [open $fname w]
- close $fid
- if {! [file exists $fname]} {error "Cannot create temporary file"}
- if {$::tcl_platform(platform) eq "unix"} {
- file attributes $fname -permissions 0600
- }
- break
- }
- if {$fname eq ""} {error "Cannot create temporary file"}
- return $fname
-}
-
# TODO:
# replace messagebox with a custom toplevel with a text widget
# in case there is a lot of text
@@ -134,11 +109,13 @@ proc read_err {} {
}
# about [chan] gets:
-# if a second parameter is supplied
+# if a second parameter, in this case l, is supplied
# then this variable receives the result, with EOL stripped,
# and the return value is the string length, possibly 0
# EOF is indicated by a return value of -1.
+# a caller of run_cmd needs to explicitly invoke 'vwait ::done_waiting'
+# if it wants to wait for the command to finish
proc read_line {} {
incr ::lnum
set l "" ; # will contain the line to be read
@@ -149,14 +126,16 @@ proc read_line {} {
# note. the right way to terminate is terminating the shell
} elseif {$len >= 0} {
# do_debug "read: $l"
+ if $::ddebug {puts $::flid $l}
if {[string first $::prmpt $l] == 0} {
# prompt line: done with command
- enable_widgets 1
+ enable_widgets 1 ; # this may have to be redone later
read_err
if {$::pipe_cb ne ""} {
do_debug "$::lnum: prompt found, $l"
$::pipe_cb "finish"
}
+ set ::done_waiting 1
} else {
lappend ::out_log $l
if {$::pipe_cb ne ""} {$::pipe_cb "line" "$l"}
@@ -164,7 +143,11 @@ proc read_line {} {
}
} ; # read_line
-# copy error strings to error page, which is sent to top.
+##### displaying stuff in GUI #####
+
+## stderr ##
+
+# copy error strings to error log page, which is sent to top.
# This by itself does not map the logs toplevel .lw
proc show_err_log {} {
#do_debug "show_err_log"
@@ -181,59 +164,12 @@ proc show_err_log {} {
}
} ; # show_err_log
-# package info popup for the package having focus
-proc popup_focused {itm} {
- run_cmd "info $itm" package_popup_cb
-}
-
-# display packages: always display both local and remote revision columns.
-# the caller should have set a filter for the packages to be displayed.
-# ::pkgs should already be up to date
-
-proc display_packages {} {
-
- .pkglist delete [.pkglist children {}]
-
- dict for {nm pk} $::pkgs {
- if {$::filt eq "" || [$::filt $nm]} {
- set vl [list $::nomrk $nm]
- foreach k {localrev remoterev shortdesc} {
- set vv [dict get $pk $k]
- if {$vv eq "0" || $vv == 0} {set vv ""}
- lappend vl $vv
- }
- .pkglist insert {} end -id $nm -values $vl
- } ; # else do not display
- }
- update ; # uncomment if necessary
-} ; # display_packages
-
-##### selection tags in package list ############################
-
-# 1. selections themselves get lost too easily
-# 2. the -image option for ttk::treeview tags does not look right
-# Therefore a do-it-yourself implementation with column #1 reserved for
-# a marker, package name in column #2, and column #0 not displayed.
-# the marker could have been prefixed to the package name in column #0,
-# but I think a separate marker column is a cleaner solution.
-
-proc toggle_marked {itm cl} {
- # if toggle_marked is triggered by a mouse click
- # then it should do nothing unless it was a click in column #1
- if {$cl ne "#1"} {
- return
- }
- if [.pkglist tag has marked $itm] {
- .pkglist tag remove marked $itm
- .pkglist set $itm mk $::nomrk
- } else {
- .pkglist tag add marked $itm
- .pkglist set $itm mk $::mrk
- }
-} ; # toggle_marked
-
##### callbacks for file events of tlmgr pipe ::tlshl (names *_cb) #####
+# callback for reading tlmgr pipe
+set pipe_cb ""
+set lnum -1
+
## template for pipe callback:
#proc template_cb {mode {l ""}} {
# if {$mode eq "line"} {
@@ -241,20 +177,13 @@ proc toggle_marked {itm cl} {
# } elseif {$mode eq "init"} {
# # do something
# } elseif {$mode eq "finish"} {
-# # do something
+# # do something BUT DO NOT TRIGGER ANOTHER EVENT LOOP
# } else {
# lappend ::err_log "Illegal call of whatever_cb"
# err_exit
# }
#}
-proc early_cb {mode {l ""}} {
- # for reading initial output tlmgr
- if {$mode eq "finish"} {
- set ::started 1
- }
-}
-
proc log_widget_cb {mode {l ""}} {
if {$mode eq "line"} {
.lw.log.tx configure -state normal
@@ -262,6 +191,9 @@ proc log_widget_cb {mode {l ""}} {
} elseif {$mode eq "init"} {
.lw.log.tx configure -state normal
.lw.log.tx delete 1.0 end
+ .lw.status configure -text "Running"
+ .lw.close configure -state disabled
+ wm state .lw normal
} elseif {$mode eq "finish"} {
.lw.log.tx yview moveto 1
.lw.logs select .lw.log
@@ -270,75 +202,38 @@ proc log_widget_cb {mode {l ""}} {
if {$::tcl_platform(os) ne "Darwin"} {
.lw.log.tx configure -state disabled
}
- .ent.e configure -state normal
+ .lw.status configure -text ""
+ .lw.close configure -state !disabled
} else {
lappend ::err_log "Illegal call of log_widget_cb"
err_exit
}
} ; # log_widget_cb
-proc packages_cb {mode {l ""}} {
- if $::do_remote {
- set re {^([^,]+),([0-9]+),([0-9]+),([^,]*),(.*)$}
- } else {
- set re {^([^,]+),([0-9]+),([^,]*),(.*)$}
- }
- if {$mode eq "line"} {
- #do_debug $l
- if {($::do_remote && [regexp $re $l m pname lrev rrev catg pdescr]) || \
- ((! $::do_remote) && [regexp $re $l m pname lrev catg pdescr])} {
- # unknown revision: 0 in database, " " in displayed list
- if {! $::do_remote} {set rrev 0}
- # double-quotes: remove outer, unescape inner
- if {[string index $pdescr 0] eq "\""} {
- set pdescr [string range $pdescr 1 end-1]
- }
- set pdescr [regsub -all "\\\"" $pdescr "\""]
- dict set ::pkgs $pname \
- [list "localrev" $lrev "remoterev" $rrev \
- "category" $catg shortdesc $pdescr]
- } else {
- #do_debug "$::lnum no match: $l"
- }
- return
- } elseif {$mode eq "init"} {
- # is it useful to remove items one by one?
- # or will the garbage collector take care of it?
- foreach nm [dict keys $::pkgs] {dict unset ::pkgs $nm}
- do_debug "packages_cb init with do_remote $::do_remote and filt $::filt"
- return
- } elseif {$mode eq "finish"} {
- do_debug "packages_cb finish with do_remote $::do_remote and filt $::filt"
- set ::have_remote $::do_remote
- #do_debug [get_stack]
- display_packages
- return
- } else {
- lappend ::err_log "Illegal call of packages_cb"
- err_exit
- }
-} ; # packages_cb
-
-proc package_popup_cb {mode {l ""}} {
- if {$mode eq "finish"} {
- tk_messageBox -message [join $::out_log "\n"]
- }
-}
+##### running tlmgr commands #####
-# procs involving running tlmgr commands #########################
+## general and various:
proc run_cmd {cmd {cb ""}} {
set ::pipe_cb $cb
do_debug "run_cmd \"$cmd\""
+ if $::ddebug {puts $::flid "\n$cmd"}
+ .topf.lcmd configure -text $cmd
enable_widgets 0
set ::out_log {}
set ::err_log {}
set ::lnum 0
+ unset -nocomplain ::done_waiting
if {$::pipe_cb ne ""} {$::pipe_cb "init"}
chan puts $::tlshl $cmd
chan flush $::tlshl
}
+proc run_cmd_waiting {cmd} {
+ run_cmd $cmd ; # "waiting_cb"
+ vwait ::done_waiting
+}
+
proc run_entry {} {
# TODO: some validation of $cmd
do_debug "run_entry"
@@ -350,163 +245,242 @@ proc run_entry {} {
run_cmd $cmd log_widget_cb
}
+proc get_repo {} {
+ run_cmd_waiting "option repository"
+ set re {repository\t(.*)$}
+ foreach l $::out_log {
+ if [regexp $re $l m ::repo] break
+ }
+} ; # get_repo
+
+## package-related:
+
proc package_popup {itm} {
- # tk_messageBox -message $itm
- run_cmd "info $itm" package_popup_cb
+ run_cmd_waiting "info $itm"
+ tk_messageBox -message [join $::out_log "\n"]
}
-# package list; use previously set values for ::do_remote and ::filt
-# we already know that we need a package list from tlmgr
-proc get_and_display_packages {} {
- do_debug "get_and_display_packages: filt $::filt and remote $::do_remote"
- foreach k [dict keys $::pkgs] {dict unset ::pkgs $k}
- set ::have_remote 0
- if {$::do_remote} {
- run_cmd "info --data name,localrev,remoterev,category,shortdesc" \
- packages_cb
- } else {
- set ::have_remote 0
- run_cmd "info --only-installed --data name,localrev,category,shortdesc" \
- packages_cb
+proc check_tlmgr_updatable {} {
+ run_cmd_waiting "update --self --list"
+ foreach l $::out_log {
+ if [regexp {^total-bytes[ \t]+([0-9]+)$} $l m b] {
+ do_debug "matches, $b"
+ set ::need_update_self [expr {$b > 0} ? 1 : 0]
+ return
+ }
}
-}
+ do_debug "check_tlmgr_uptodate: should not get here"
+} ; # check_tlmgr_uptodate
-proc show_all_packages {} {
- set ::filt ""
- if $::have_remote {
- display_packages
- } else {
- set ::do_remote 1
- get_and_display_packages ; # calls display_packages as finish callback
- }
-} ; # show_all_packages
-
-proc show_local_packages {} {
- set ::filt filt_local
- set ::do_remote 0
- if {[llength [dict keys $::pkgs]] == 0} {
- do_debug "show_local_packages: no packages loaded"
- set ::have_remote 0
- get_and_display_packages
- } else {
- do_debug "show_local_packages: packages already loaded"
- display_packages
+proc update_globals {} {
+ if {! $::have_remote} return
+ set ::n_updates 0
+ foreach nm [dict keys $::pkgs] {
+ if [is_updatable $nm] {incr ::n_updates}
}
-} ; # show_local_packages
+ check_tlmgr_updatable
+ set ::tlshell_updatable [is_updatable tlshell]
+}
-proc show_collections {} {
- set ::filt filt_collections
- if {! $::have_remote} {
- set ::do_remote 1
- get_and_display_packages
- } else {
- display_packages
- }
-} ; # show_collections
+# get fresh package list
+# some local packages may not be available online.
+# to test, create local dual-platform installation from dvd, try to update
+# from more recent linux-only installation
-proc show_upgradable {} {
- set ::filt filt_upgradable
- if {! $::have_remote} {
- # get a complete package list
- set ::do_remote 1
- do_debug "show_upgradable: remote not yet loaded"
- get_and_display_packages
- } else {
- do_debug "show_upgradable: remote already loaded"
- display_packages
+# local: start from scratch
+proc get_packages_info_local {} {
+ foreach nm [dict keys $::pkgs] {
+ dict unset ::pkgs $nm
}
-} ; # show_upgradable
+ set ::have_remote 0
+ set ::need_update_self 0
+ set ::updatable 0
+ set ::tlshell_updatable 0
+
+ run_cmd_waiting \
+ "info --only-installed --data name,localrev,category,shortdesc"
+ set re {^([^,]+),([0-9]+),([^,]*),(.*)$}
+ foreach l $::out_log {
+ if [regexp $re $l m nm lrev catg pdescr] {
+ # double-quotes: remove outer, unescape inner
+ if {[string index $pdescr 0] eq "\""} {
+ set pdescr [string range $pdescr 1 end-1]
+ }
+ set pdescr [regsub -all "\\\"" $pdescr "\""]
+ dict set ::pkgs $nm \
+ [list "localrev" $lrev "remoterev" 0 \
+ "category" $catg shortdesc $pdescr]
+ }
+ }
+} ; # get_packages_info_local
-# (re)initialization procs ############################
+# remote: preserve information on installed packages
+proc get_packages_info_remote {} {
+ # remove non-local database entries
+ foreach k [dict keys $::pkgs] {
+ if {! [dict get $::pkgs $k localrev]} {
+ dict unset ::pkgs $k
+ }
+ }
+ set ::need_update_self 0
+ set ::updatable 0
+ set ::tlshell_updatable 0
-proc start_tlmgr {} {
- # start the TeX Live Manager shell interface
- # capture stdout into the pipe, stderr into a temp file
- # below, early_cb and vwait ::started together forces tlshell
- # to process initial tlmgr output before continuing
- set ::pipe_cb early_cb
- unset -nocomplain ::started
- set ::tlshl [open "|tlmgr --machine-readable shell 2>>$::err_file" w+]
- set ::err [open $::err_file r]
- chan configure $::tlshl -buffering line -blocking 0
- chan event $::tlshl readable read_line
- vwait ::started
- show_local_packages
-}
+ run_cmd_waiting "info --data name,localrev,remoterev,category,shortdesc"
+ set re {^([^,]+),([0-9]+),([0-9]+),([^,]*),(.*)$}
+ foreach l $::out_log {
+ if [regexp $re $l m nm lrev rrev catg pdescr] {
+ # double-quotes in short description: remove outer, unescape inner
+ if {[string index $pdescr 0] eq "\""} {
+ set pdescr [string range $pdescr 1 end-1]
+ }
+ set pdescr [regsub -all "\\\"" $pdescr "\""]
+ if [catch {dict get $::pkgs $nm} pk] {
+ # package entry does not exist
+ dict set ::pkgs $nm [dict create "localrev" 0 \
+ "remoterev" $rrev "category" $catg shortdesc $pdescr]
+ } else {
+ dict set ::pkgs $nm "remoterev" $rrev
+ dict set ::pkgs $nm "category" $catg
+ dict set ::pkgs $nm "shortdesc" $pdescr
+ }
+ }
+ }
+ set ::have_remote 1
+ update_globals
+} ; # get_packages_info_remote
+
+## update ::pkgs after installing packages without going online.
+## this should be considered a shortcut version of get_packages_info_xxx,
+## and should similarly take care of the globals; see above
+proc update_local_revnumbers {} {
+ run_cmd_waiting "info --only-installed --data name,localrev"
+ set re {^([^,]+),([0-9]+)$}
+ foreach nm [dict keys $::pkgs] {
+ dict set ::pkgs $nm "localrev" 0
+ }
+ foreach l $::out_log {
+ if [regexp $re $l m nm lr] {
+ dict set ::pkgs $nm "localrev" $lr
+ }
+ }
+ update_globals
+} ; # update_local_revnumbers
-proc restart_self {} {
- do_debug "trying to restart"
- if {$::progname eq ""} {
- tk_messageBox -message "progname not found; not restarting"
+proc update_self {} {
+ if {! $::need_update_self} {
+ tk_messageBox -message "Nothing to do!"
return
}
- catch {chan close $::tlshl}
- catch {chan close $::err}
- exec $::progname &
- # on windows, it may take several seconds before
- # the old tlshell disappears.
- # oh well, windows is still windows....
- exit
-}
+ run_cmd "update --self" log_widget_cb
+ vwait ::done_waiting
+ # tlmgr restarts itself automatically, reload remote
+ get_packages_info_remote
+ set ::sel_opt "inst"
+ display_packages_info is_local
+} ; # update_self
+
+proc update_all {} {
+ if $::need_update_self {
+ tk_messageBox -message "Update self first!"
+ return
+ } elseif {! $::n_updates} {
+ tk_messageBox -message "Nothing to do!"
+ return
+ }
+ run_cmd "update --all" log_widget_cb
+ vwait ::done_waiting
+ #wm withdraw .lw
+ update_local_revnumbers
+ set ::sel_opt "inst"
+ display_packages_info is_local
+} ; # update_all
+
+##### building GUI #####
-# dummy widgets for vertical spacing
+# dummy widgets for vertical spacing within $w
set idummy -1
proc spacing {w} {
incr ::idummy
pack [label $w.$::idummy -text " "]
}
-#proc pgrid {wdg args} { ; # padded grid
-# set l [list grid $wdg -padx 3 -pady 3]
-# foreach v $args {lappend l $v}
-# eval [join $l ""]
-#}
+proc pgrid {wdg args} { ; # grid command with padding
+ set l [list grid $wdg -padx 3 -pady 3 -sticky w]
+ foreach v $args {lappend l $v}
+ eval [join $l " "]
+}
+
+proc ppack {wdg args} { ; # pack command with padding
+ set l [list pack $wdg -padx 3 -pady 3]
+ foreach v $args {lappend l $v}
+ eval [join $l " "]
+}
proc make_widgets {} {
wm title . "$::progname $::procid"
- # width of '0', as a rough estimate of character width
+ # width of '0', as a rough estimate of average character width
set cw [font measure TkTextFont "0"]
# various info
frame .topf
- pack [label .busy -textvariable ::busy] -in .topf -side right -padx 3
- pack [label .more -justify left -text "Buttons (more to come)"] \
- -in .topf -side left -padx 3
- pack .topf -side top -fill x -expand 1
-
- # main buttons block
- frame .buttons
- grid [ttk::button .pkgl -text "Show all packages" \
- -command show_all_packages] \
- -in .buttons -column 0 -row 1 -sticky w -padx 3 -pady 3
- grid [ttk::button .locals -text "Show installed packages" \
- -command {show_local_packages}] \
- -in .buttons -column 1 -row 1 -sticky w -padx 3 -pady 3
- grid [ttk::button .coll -text "Show collections" \
- -command {show_collections}] \
- -in .buttons -column 2 -row 1 -sticky w -padx 3 -pady 3
- grid [ttk::button .upgr -text "Show upgradable" \
- -command {show_upgradable}] \
- -in .buttons -column 3 -row 1 -sticky w
- pack .buttons -side top -fill x -expand 1 -padx 3 -pady 3
+
+ pgrid [label .topf.llrepo -text Repository -anchor w] -row 0 -column 0
+ pgrid [label .topf.lrepo -textvariable ::repo] -row 0 -column 1
+
+ pgrid [label .topf.lluptodate -text "TL Manager up to date?" -anchor w] \
+ -row 1 -column 0
+ label .topf.luptodate -anchor w
+ pgrid .topf.luptodate -row 1 -column 1
+
+ pgrid [label .topf.llcmd -anchor w -text "Last command: "] \
+ -row 2 -column 0
+ pgrid [label .topf.lcmd -anchor w] -row 2 -column 1
+ pack .topf -side top -anchor w
+
+ # some buttons
+ spacing .
+ frame .butf
+ ttk::button .butf.all -text "Update all" -command update_all
+ ppack .butf.all -side left
+ .butf.all configure -state disabled
+ ttk::button .butf.self -text "Update self" -command update_self
+ .butf.self configure -state disabled
+ ppack .butf.self -side left
+ pack .butf -side top -anchor w
# command entry
spacing .
frame .ent
- pack [label .ent.l -text "Type command:"] -side left -padx 3
- pack [entry .ent.e -width 40] -side left -padx 3
- pack [ttk::button .ent.b -text Go -command run_entry] -side left -padx 3
+ ppack [label .ent.l -text "Type command:"] -side left
+ ppack [entry .ent.e -width 40] -side left -padx 3
+ ppack [ttk::button .ent.b -text Go -command run_entry] -side left
bind .ent.e <Return> run_entry
pack .ent -fill x -side top -expand 1
- #grid [label .ent.lprv -justify left -text "Last command entry: "] \
- # -row 1 -column 0
- #grid [label .ent.prv -justify left] -row 1 -column 1 -sticky w
+ spacing .
+
+ # controlling package list
+ ttk::frame .fp_ctrl
+ ppack [ttk::label .fp_ctrl.pkgl -text "Show packages:"] -side left
+ ttk::radiobutton .fp_ctrl.inst -text Installed -value inst \
+ -variable ::sel_opt -command show_packages_info
+ ttk::radiobutton .fp_ctrl.all -text All -value all \
+ -variable ::sel_opt -command show_packages_info
+ ttk::radiobutton .fp_ctrl.upd -text Updatable -value upd \
+ -variable ::sel_opt -command show_packages_info
+ ttk::radiobutton .fp_ctrl.coll -text Collections -value coll \
+ -variable ::sel_opt -command show_packages_info
+ ppack .fp_ctrl.inst -side left
+ ppack .fp_ctrl.all -side left
+ ppack .fp_ctrl.upd -side left
+ ppack .fp_ctrl.coll -side left
+ set ::sel_opt inst
+ pack .fp_ctrl -side top -fill x
# packages list (tlmgrgui uses an old HList widget)
- spacing .
frame .fpkg
ttk::treeview .pkglist -columns \
{mk name localrev remoterev shortdesc} \
@@ -525,7 +499,7 @@ proc make_widgets {} {
ttk::scrollbar .pkhsb -orient horizontal -command {.pkglist xview}
ttk::scrollbar .pkvsb -orient vertical -command {.pkglist yview}
- grid .pkglist -in .fpkg -row 0 -column 0 -sticky news
+ pgrid .pkglist -in .fpkg -row 0 -column 0 -sticky news
grid .pkvsb -in .fpkg -row 0 -column 1 -sticky ns
grid .pkhsb -in .fpkg -row 1 -column 0 -sticky ew
grid columnconfigure .fpkg 0 -weight 1
@@ -546,12 +520,16 @@ proc make_widgets {} {
# bottom of main window
frame .endbuttons
- pack [ttk::button .q -text Quit -command exit] \
- -in .endbuttons -side right -padx 3 -pady 3
- pack [ttk::button .r -text "Restart self" -command restart_self] \
- -in .endbuttons -side right -padx 3 -pady 3
+ label .busy -textvariable ::busy -font TkHeadingFont -anchor w
+ ppack .busy -in .endbuttons -side left
+ ppack [ttk::button .q -text Quit -command exit] \
+ -in .endbuttons -side right
+ ppack [ttk::button .r -text "Restart self" -command restart_self] \
+ -in .endbuttons -side right
+ ppack [ttk::button .t -text "Restart tlmgr" -command restart_tlmgr] \
+ -in .endbuttons -side right
ttk::button .showlogs -text "Show logs" -command {wm state .lw normal}
- pack .showlogs -in .endbuttons -side right -padx 3
+ ppack .showlogs -in .endbuttons -side right
pack .endbuttons -side bottom -fill x -expand 1
# log displays: new toplevel
@@ -559,7 +537,7 @@ proc make_widgets {} {
frame .lw.log
pack [ttk::scrollbar .lw.log.scroll -command ".lw.log.tx yview"] \
-side right -fill y
- pack [text .lw.log.tx -height 10 -bd 2 -relief groove -wrap word \
+ ppack [text .lw.log.tx -height 10 -bd 2 -relief groove -wrap word \
-yscrollcommand ".lw.log.scroll set"] \
-expand 1 -fill both
.lw.log.tx yview moveto 1
@@ -567,7 +545,7 @@ proc make_widgets {} {
frame .lw.err
pack [ttk::scrollbar .lw.err.scroll -command ".lw.err.tx yview"] \
-side right -fill y
- pack [text .lw.err.tx -height 10 -bd 2 -relief groove -wrap word \
+ ppack [text .lw.err.tx -height 10 -bd 2 -relief groove -wrap word \
-yscrollcommand ".lw.err.scroll set"] \
-expand 1 -fill both
.lw.err.tx yview moveto 1
@@ -576,7 +554,7 @@ proc make_widgets {} {
frame .lw.dbg
pack [ttk::scrollbar .lw.dbg.scroll -command ".lw.dbg.tx yview"] \
-side right -fill y
- pack [text .lw.dbg.tx -height 10 -bd 2 -relief groove -wrap word \
+ ppack [text .lw.dbg.tx -height 10 -bd 2 -relief groove -wrap word \
-yscrollcommand ".lw.dbg.scroll set"] \
-expand 1 -fill both
.lw.dbg.tx yview moveto 1
@@ -591,14 +569,30 @@ proc make_widgets {} {
}
raise .lw.err .lw.logs
raise .lw.log .lw.logs
- pack .lw.logs -in .lw -side top -fill both -expand 1 -padx 3
+ pack .lw.logs -in .lw -side top -fill both -expand 1
+ frame .lw.bottom
ttk::button .lw.close -text close -command {wm withdraw .lw}
- pack .lw.close -side bottom -anchor e -padx 3 -pady 3
+ ppack .lw.close -in .lw.bottom -side right -anchor e
+ ppack [label .lw.status -anchor w] -in .lw.bottom -side left
+ pack .lw.bottom -side top -expand 1 -fill x
wm withdraw .lw
} ; # make_widgets
+proc enable_update_buttons {yesno} {
+ if {! $yesno || ! $::n_updates} {
+ .butf.all configure -state disabled
+ .butf.self configure -state disabled
+ } elseif $::need_update_self {
+ .butf.all configure -state disabled
+ .butf.self configure -state !disabled
+ } else {
+ .butf.all configure -state !disabled
+ .butf.self configure -state disabled
+ }
+}
+
proc enable_widgets {yesno} {
if $yesno {
set st normal
@@ -609,20 +603,190 @@ proc enable_widgets {yesno} {
set ttk_st disabled
set ::busy "BUSY"
}
- # buttons
- .pkgl configure -state $ttk_st
+
+ enable_update_buttons $yesno
+
# command entry
.ent.b configure -state $st
.ent.e configure -state $st
- # package list
- .pkglist state $ttk_st
- # do not touch the log windows
+
# final buttons
.q configure -state $ttk_st
.r configure -state $ttk_st
+ .t configure -state $ttk_st
.showlogs configure -state $ttk_st
+
+ .lw.close configure -state $ttk_st
+ if $yesno {
+ .lw.status configure -text "Done"
+ } else {
+ .lw.status configure -text "Please wait..."
+ }
} ; # enable_widgets
+## single-package info ##
+
+proc popup_focused {itm} {
+ run_cmd_waiting "info $itm"
+ tk_messageBox -message [join $::out_log "\n"]
+}
+
+# filter procs; parameter is package name
+
+proc is_local {p} {
+ return [dict get $::pkgs $p localrev]
+}
+
+proc is_collection {p} {
+ set c [dict get $::pkgs $p "category"]
+ return [expr \"$c\" eq \"Collection\"]
+}
+
+proc is_updatable {p} {
+ #do_debug "Is $p updatable"
+ if [catch {dict get $::pkgs $p} pk] {
+ return 0
+ } else {
+ if [catch {dict get $::pkgs $p localrev} lr] {set lr 0}
+ if [catch {dict get $::pkgs $p remoterev} rr] {set rr 0}
+ # do_debug "Revisions $p are local $lr and remote $rr"
+ return [expr $lr > 0 && $rr > 0 && $rr > $lr]
+ }
+}
+
+# display packages: always display both local and remote revision columns.
+# ::pkgs should already be up to date
+
+# manual selection tags: unicode chars as tags are resolution-independent.
+# ATM we only display tags but do not use them otherwise.
+set mrk [format %c 0x25a3] ; # 'white square containing black small square'
+set nomrk [format %c 0x25a1] ; # 'white square'
+
+# sel_opt: which packages to show from package list;
+# after updates it will always be set to "inst", after installations "all"
+set sel_opt inst
+
+proc toggle_marked {itm cl} {
+ # if toggle_marked is triggered by a mouse click
+ # then it should do nothing unless it was a click in column #1
+ if {$cl ne "#1"} {
+ return
+ }
+ if [.pkglist tag has marked $itm] {
+ .pkglist tag remove marked $itm
+ .pkglist set $itm mk $::nomrk
+ } else {
+ .pkglist tag add marked $itm
+ .pkglist set $itm mk $::mrk
+ }
+} ; # toggle_marked
+
+proc display_packages_info {{filt ""}} {
+ do_debug "display_packages_info with remote $::have_remote and filt >$filt<"
+ .pkglist delete [.pkglist children {}]
+
+ foreach nm [lsort [dict keys $::pkgs]] {
+ if {$filt eq "" || [$filt $nm]} {
+ # collect data to be displayed for $nm in vl
+ set vl [list $::nomrk $nm]
+ foreach k {localrev remoterev shortdesc} {
+ set vv [dict get $::pkgs $nm $k]
+ if {$vv eq "0" || $vv == 0} {set vv ""}
+ lappend vl $vv
+ }
+ .pkglist insert {} end -id $nm -values $vl
+ } ; # else do not display
+ }
+ # also update displayed status info
+ if {$::have_remote && $::need_update_self} {
+ .topf.luptodate configure -text "Needs updating"
+ } elseif $::have_remote {
+ .topf.luptodate configure -text "Up to date"
+ } else {
+ .topf.luptodate configure -text "Unknown"
+ }
+ # ... and status of update buttons
+ enable_update_buttons 1
+} ; # display_packages_info
+
+# selection tags in package list
+# 1. selections themselves get lost too easily
+# 2. the -image option for ttk::treeview tags does not look right
+# Therefore a do-it-yourself implementation with column #1 reserved for
+# a marker, package name in column #2, and column #0 not displayed at all.
+# the marker could have been prefixed to the package name in column #0,
+# but I think a separate marker column is a cleaner solution.
+
+# display packages, invoking get_packages_info if necessary.
+# called at end of initialization and when a selection radio button
+# is clicked. Otherwise, get_packages_info_... and display_packages_info
+# are invoked separately.
+proc show_packages_info {} {
+ switch $::sel_opt {
+ "inst" {
+ if {! [llength [dict keys $::pkgs]]} {get_packages_info_local}
+ set filt "is_local"
+ }
+ "all" {
+ if {! $::have_remote} {get_packages_info_remote}
+ set filt ""
+ }
+ "upd" {
+ if {! $::have_remote} {get_packages_info_remote}
+ set filt "is_updatable"
+ }
+ "coll" {
+ if {! $::have_remote} {get_packages_info _remote}
+ set filt "is_collection"
+ }
+ }
+ display_packages_info $filt
+} ; # show_packages_info
+
+##### (re)initialization procs #####
+
+proc start_tlmgr {} {
+ # start the TeX Live Manager shell interface
+ # capture stdout into the pipe, stderr into a temp file
+ # below, vwait ::done_waiting forces tlshell
+ # to process initial tlmgr output before continuing
+ unset -nocomplain ::done_waiting
+ set ::tlshl [open "|tlmgr --machine-readable shell 2>>$::err_file" w+]
+ set ::err [open $::err_file r]
+ chan configure $::tlshl -buffering line -blocking 0
+ chan event $::tlshl readable read_line
+ vwait ::done_waiting
+ #get_repo
+ #if {$::argc && [lindex $::argv 0] eq "remote"} {
+ # show_packages_info 0 is_local
+ #} else {
+ # show_packages_info 1 is_local
+ #}
+}
+
+proc restart_tlmgr {} {
+ catch {chan close $::tlshl}
+ catch {chan close $::err}
+ start_tlmgr
+ get_packages_info_remote
+ display_packages_info
+}
+
+proc restart_self {{param ""}} {
+ do_debug "trying to restart"
+ if {$::progname eq ""} {
+ tk_messageBox -message "progname not found; not restarting"
+ return
+ }
+ catch {chan close $::tlshl}
+ catch {chan close $::err}
+ exec $::progname &
+ # on windows, it may take several seconds before
+ # the old tlshell disappears.
+ # oh well, windows is still windows....
+ exit
+}
+
proc initialize {} {
# prepend TL to process searchpath (not needed on windows)
if {$::tcl_platform(platform) ne "windows"} {
@@ -680,9 +844,17 @@ proc initialize {} {
# temp file for stderr
set ::err_file [maketemp ".err_tlshl"]
+ # logfile
+ set fname [file join $::tempsub \
+ [clock format [clock seconds] -format {%H:%M}]]
+ set ::flid [open $fname w]
+
make_widgets
start_tlmgr
+ get_repo
+ set ::sel_opt "inst"
+ show_packages_info
}; # initialize
initialize