1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
|
#!/usr/bin/env wish
# Copyright 2018 Siep Kroonenberg
# This file is licensed under the GNU General Public License version 2
# or any later version.
# common declarations for tlshell.tcl and install-tl-gui.tcl
set ::plain_unix 0
if {$::tcl_platform(platform) eq "unix" && $::tcl_platform(os) ne "Darwin"} {
set ::plain_unix 1
}
# process ID of the perl program that will run in the background
set ::perlpid 0
set any_mirror "http://mirror.ctan.org/systems/texlive/tlnet"
proc get_stacktrace {} {
set level [info level]
set s ""
for {set i 1} {$i < $level} {incr i} {
append s [format "Level %u: %s\n" $i [info level $i]]
}
return $s
} ; # get_stacktrace
# what exit procs do we need?
# - plain error exit with messagebox and stacktrace
# - plain messagebox exit
# - showing log output, maybe with appended message,
# use log toplevel for lengthy output
# is closing the pipe $::inst guaranteed to kill perl? It should be
proc err_exit {{mess ""}} {
if {$mess eq ""} {set mess "Error"}
append mess "\n" [get_stacktrace]
if $::plain_unix {
# plain_unix: avoid a RenderBadPicture error on quitting.
# 'send' changes the shutdown sequence,
# which avoids triggering the bug.
# 'tk appname <something>' restores 'send' and avoids the bug
bind . <Destroy> {
catch {tk appname appname}
}
}
tk_messageBox -icon error -message $mess
# kill perl process, just in case
if $::perlpid {
catch {
if {$::tcl_platform(platform) eq "unix"} {
exec -ignorestderr "kill" $::perlpid
} else {
exec -ignorestderr "taskkill" "/pid" $::perlpid
}
}
}
exit
} ; # err_exit
# localization support
# tcl 8.5 observes LANG on Unix, but is overruled by LC_ALL.
# for 8.5 or on Mac OS, locale setting from within tcl may not work.
# so let the shell wrapper handle a language option by setting LANG
# and unsetting LC_ALL.
# $TEXMFCONFIG/tlmgr/config can have a setting for gui-lang.
# replace with shell script / batchfile which is sourced?
# for windows there is a bundled 8.6 in which locale can be set on-the-fly.
# consult registry for default locale if LANG is not set.
# The wrapper already does this, but here we do it again
# in case e.g. company policy blocked reg.exe.
# check the command-line for a lang parameter
unset -nocomplain lang
set i 0
while {$i < $::argc} {
set p [lindex $::argv $i]
incr i
if {$p eq "-lang" || $p eq "--lang"} {
if {$i < $::argc} {
set lang [lindex $::argv $i]
break
}
} elseif {[string range $p 0 5] eq "-lang="} {
set lang [string range $p 6 end]
break
} elseif {[string range $p 0 6] eq "--lang="} {
set lang [string range $p 7 end]
break
}
}
unset i
# first fallback: check config file if available
if {! [info exists lang] || $lang eq ""} {
foreach tmf {"TEXMFCONFIG" "TEXMFSYSCONFIG"} {
if [catch {exec kpsewhich -var-value $tmf} d] {
break; # apparently there is not yet a TL installation
}
if [catch {open [file join $d "tlmgr" "config"] r} fid] continue
while 1 {
if [catch {chan gets $fid} l] break
if {[regexp {^gui-lang\s*=\s*(\S+)$} $l m lang]} {
chan close $fid
break
}
}
if {[info exists lang] && $lang ne ""} break
}
}
# second fallback: check environment for LC_ALL, LC_MESSAGES and LANG
if {! [info exists lang] || $lang eq ""} {
foreach lng {"LC_ALL" "LC_MESSAGES" "LANG"} {
if {[info exists ::env($lng)] && $::env($lng) ne ""} {
set lang $::env($lng)
break
}
}
}
# third fallback, windows-only: check registry
if {(! [info exists lang] || $lang eq "") && \
$::tcl_platform(platform) eq "windows"} {
if {! [catch {package require registry}]} {
set regpath [join {HKEY_LOCAL_MACHINE system currentcontrolset \
control nls language} "\\"]
if {! [catch {registry get $regpath "Installlanguage"} lcode]} {
set regpath [join {HKEY_CLASSES_ROOT mime database rfc1766} "\\"]
if {! [catch {registry get $regpath $lcode} lng]} {
set l [string first ";" $lng]
if {$l > 0} {
incr l -1
set lng [string range $lng 0 $l]
}
set lang $lng
#tk_messageBox -message "Language $lang in registry found"
}
}
}
}
# load the message catalog
package require msgcat
if {[info exists lang] && $lang ne ""} {
::msgcat::mclocale $lang
#unset lang
}
::msgcat::mcload [file join $::instroot "tlpkg" "translations"]
proc __ {s args} {return [::msgcat::mc $s {*}$args]}
# string representation of booleans
proc yes_no {b} {
if $b {
set ans [::msgcat::mc "Yes"]
} else {
set ans [::msgcat::mc "No"]
}
return $ans
}
if {$::tcl_platform(os) eq "Darwin"} {
# avoid warnings 'tar: Failed to set default locale'
set ::env(LC_ALL) "en_US.UTF-8"
}
### fonts ###
# no bold text for messages; `userDefault' indicates priority
option add *Dialog.msg.font TkDefaultFont userDefault
# larger fonts: lfont and bold titlefont
font create lfont {*}[font configure TkDefaultFont]
font configure lfont -size [expr {round(1.2 * [font actual lfont -size])}]
font create bfont {*}[font configure TkDefaultFont]
font configure bfont -weight bold
font create hfont {*}[font configure lfont]
font configure hfont -weight bold
font create titlefont {*}[font configure TkDefaultFont]
font configure titlefont -weight bold \
-size [expr {round(1.5 * [font actual titlefont -size])}]
## italicized items; not used
#font create it_font {*}[font configure TkDefaultFont]
#font configure it_font -slant italic
# width of '0', as a very rough estimate of average character width
# assume height == width*2
set ::cw [font measure TkTextFont "0"]
# default foreground color and disabled foreground color
# may not be black in e.g. dark color schemes
set blk [ttk::style lookup TButton -foreground]
set gry [ttk::style lookup TButton -foreground disabled]
# 'default' padding
proc ppack {wdg args} { ; # pack command with padding
pack $wdg {*}$args -padx 3 -pady 3
}
proc pgrid {wdg args} { ; # grid command with padding
grid $wdg {*}$args -padx 3 -pady 3
}
# unicode symbols as fake checkboxes in ttk::treeview widgets
proc mark_sym {mrk} {
if $mrk {
return "\u25A3" ; # 'white square containing black small square'
} else {
return "\u25A1" ; # 'white square'
}
} ; # mark_sym
# for help output
set ::env(NOPERLDOC) 1
##### dialog support #####
# for example code, look at dialog.tcl, part of Tk itself
# global variable for dialog return value, in case the outcome
# must be handled by the caller rather than by the dialog itself
set ::dialog_ans {}
# start new toplevel with settings appropriate for a dialog
proc create_dlg {wnd {p .}} {
catch {destroy $wnd} ; # no error if it does not exist
toplevel $wnd -class Dialog
wm withdraw $wnd
if [winfo viewable $p] {wm transient $wnd $p}
if $::plain_unix {wm attributes $wnd -type dialog}
wm protocol $wnd WM_DELETE_WINDOW {destroy $wnd}
}
# Place a dialog centered wrt its parent.
# If its geometry is somehow not yet available,
# its upperleft corner will be centered.
proc place_dlg {wnd {p "."}} {
set g [wm geometry $p]
scan $g "%dx%d+%d+%d" pw ph px py
set hcenter [expr {$px + $pw / 2}]
set vcenter [expr {$py + $ph / 2}]
set g [wm geometry $wnd]
set wh [winfo reqheight $wnd]
set ww [winfo reqwidth $wnd]
set wx [expr {$hcenter - $ww / 2}]
if {$wx < 0} { set wx 0}
set wy [expr {$vcenter - $wh / 2}]
if {$wy < 0} { set wy 0}
wm geometry $wnd [format "+%d+%d" $wx $wy]
wm state $wnd normal
wm attributes $wnd -topmost
raise $wnd $p
tkwait visibility $wnd
focus $wnd
grab set $wnd
} ; # place_dlg
# place dialog answer in ::dialog_ans, raise parent, close dialog
proc end_dlg {ans wnd} {
set ::dialog_ans $ans
set p [winfo parent $wnd]
if {$p eq ""} {set p "."}
raise $p
destroy $wnd
} ; # end_dlg
##### directories #####
# slash flipping
proc forward_slashify {s} {
regsub -all {\\} $s {/} r
return $r
}
proc native_slashify {s} {
if {$::tcl_platform(platform) eq "windows"} {
regsub -all {/} $s {\\} r
} else {
regsub -all {\\} $s {/} r
}
return $r
}
# unix: choose_dir replacing native directory browser
if {$::tcl_platform(platform) eq "unix"} {
# Based on the tcl/tk widget demo.
# Also for MacOS, because we want to see /usr.
# For windows, the native browser widget is better.
## Code to populate a single directory node
proc populateTree {tree node} {
if {[$tree set $node type] ne "directory"} {
set type [$tree set $node type]
return
}
$tree delete [$tree children $node]
foreach f [lsort [glob -nocomplain -directory $node *]] {
set type [file type $f]
if {$type eq "directory"} {
$tree insert $node end \
-id $f -text [file tail $f] -values [list $type]
# Need at least one child to make this node openable,
# will be deleted when actually populating this node
$tree insert $f 0 -text "dummy"
}
}
# Stop this code from rerunning on the current node
$tree set $node type processedDirectory
}
proc choose_dir {initdir {parent .}} {
create_dlg .browser $parent
wm title .browser [__ "Browse..."]
# wallpaper
pack [ttk::frame .browser.bg -padding 3] -fill both -expand 1
## Create the tree and set it up
pack [ttk::frame .browser.fr0] -in .browser.bg -fill both -expand 1
set tree [ttk::treeview .browser.tree \
-columns {type} -displaycolumns {} -selectmode browse \
-yscroll ".browser.vsb set"]
.browser.tree column 0 -minwidth 500 -stretch 0
ttk::scrollbar .browser.vsb -orient vertical -command "$tree yview"
# hor. scrolling does not work, but toplevel and widget are resizable
$tree heading \#0 -text "/"
$tree insert {} end -id "/" -text "/" -values [list "directory"]
populateTree $tree "/"
bind $tree <<TreeviewOpen>> {
populateTree %W [%W focus]
}
bind $tree <ButtonRelease-1> {
.browser.tree heading \#0 -text [%W focus]
}
## Arrange the tree and its scrollbar in the toplevel
# horizontal scrolling does not work.
# possible solution: very wide treeview in smaller paned window
# (may as well use pack in the absence of a horizontal scrollbar)
grid $tree .browser.vsb -sticky nsew -in .browser.fr0
grid columnconfigure .browser.fr0 0 -weight 1
grid rowconfigure .browser.fr0 0 -weight 1
# ok and cancel buttons
pack [ttk::frame .browser.fr1] -in .browser.bg -fill x -expand 1
ppack [ttk::button .browser.ok -text [__ "Ok"]] \
-in .browser.fr1 -side right
ppack [ttk::button .browser.cancel -text [__ "Cancel"]] \
-in .browser.fr1 -side right
.browser.ok configure -command {
set ::dialog_ans [.browser.tree focus]
destroy .browser
}
.browser.cancel configure -command {
set ::dialog_ans ""
destroy .browser
}
unset -nocomplain ::dialog_ans
# navigate tree to $initdir
set chosenDir {}
foreach d [file split [file normalize $initdir]] {
set nextdir [file join $chosenDir $d]
if [file isdirectory $nextdir] {
if {! [$tree exists $nextdir]} {
$tree insert $chosenDir end -id $nextdir \
-text $d -values [list "directory"]
}
populateTree $tree $nextdir
set chosenDir $nextdir
} else {
break
}
}
$tree see $chosenDir
$tree selection set [list $chosenDir]
$tree focus $chosenDir
$tree heading \#0 -text $chosenDir
place_dlg .browser $parent
tkwait window .browser
return $::dialog_ans
}; # choose_dir
}; # if unix
proc browse4dir {inidir {parent .}} {
if {$::tcl_platform(platform) eq "unix"} {
return [choose_dir $inidir $parent]
} else {
return [tk_chooseDirectory \
-initialdir $inidir -title [__ "Select or type"] -parent $parent]
}
} ; # browse4dir
# browse for a directory and store in entry- or label widget $w
proc dirbrowser2widget {w} {
set wclass [winfo class $w]
if {$wclass eq "Entry" || $wclass eq "TEntry"} {
set is_entry 1
} elseif {$wclass eq "Label" || $wclass eq "TLabel"} {
set is_entry 0
} else {
err_exit "browse2widget invoked with unsupported widget class $wclass"
}
if $is_entry {
set retval [$w get]
} else {
set retval [$w cget -text]
}
set retval [browse4dir $retval [winfo parent $w]]
if {$retval eq ""} {
return 0
} else {
if {$wclass eq "Entry" || $wclass eq "TEntry"} {
$w delete 0 end
$w insert 0 $retval
} else {
$w configure -text $retval
}
return 1
}
}
|