tkfbox.tcl 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251
  1. # tkfbox.tcl --
  2. #
  3. # Implements the "TK" standard file selection dialog box. This dialog
  4. # box is used on the Unix platforms whenever the tk_strictMotif flag is
  5. # not set.
  6. #
  7. # The "TK" standard file selection dialog box is similar to the file
  8. # selection dialog box on Win95(TM). The user can navigate the
  9. # directories by clicking on the folder icons or by selecting the
  10. # "Directory" option menu. The user can select files by clicking on the
  11. # file icons or by entering a filename in the "Filename:" entry.
  12. #
  13. # Copyright © 1994-1998 Sun Microsystems, Inc.
  14. #
  15. # See the file "license.terms" for information on usage and redistribution
  16. # of this file, and for a DISCLAIMER OF ALL WARRANTIES.
  17. #
  18. namespace eval ::tk::dialog {}
  19. namespace eval ::tk::dialog::file {
  20. namespace import -force ::tk::msgcat::*
  21. variable showHiddenBtn 0
  22. variable showHiddenVar 1
  23. # Based on Vimix/16/actions/go-up.svg
  24. # See https://github.com/vinceliuice/vimix-icon-theme
  25. variable updirImageData {
  26. <?xml version="1.0" encoding="UTF-8"?>
  27. <svg width="16" height="16" version="1.1" xmlns="http://www.w3.org/2000/svg">
  28. <path d="m7 14v-9l-4 4-1-1 6-6 6 6-1 1-4-4v9z" fill="#000000"/>
  29. </svg>
  30. }
  31. proc UpdateUpdirImageData {} {
  32. variable updirImageData
  33. set idx1 [string first "#000000" $updirImageData]
  34. set idx2 [expr {$idx1 + 6}]
  35. set fgColor [ttk::style lookup . -foreground {} black]
  36. lassign [winfo rgb . $fgColor] r g b
  37. set fgColor [format "#%02x%02x%02x" \
  38. [expr {$r >> 8}] [expr {$g >> 8}] [expr {$b >> 8}]]
  39. return [string replace $updirImageData $idx1 $idx2 $fgColor]
  40. }
  41. # Based on https://icons8.com/icon/JXYalxb9XWWd/folder
  42. variable folderImageData {
  43. <?xml version="1.0" encoding="UTF-8"?>
  44. <svg width="16" height="16" version="1.1" xmlns="http://www.w3.org/2000/svg">
  45. <path d="m0.5 13.5v-12h4.293l2 2h8.707v10z" fill="#59afff"/>
  46. <path d="m4.586 2 2 2h8.414v9h-14v-11h3.586m0.414-1h-5v13h16v-11h-9l-2-2z" fill="#2d8cff"/>
  47. <path d="m0.5 14.5v-10h4.618l2-1h8.382v11z" fill="#8cc5ff"/>
  48. <path d="m15 4v10h-14v-9h4.236l0.211-0.106 1.789-0.894h7.764m1-1h-9l-2 1h-5v11h16z" fill="#2d8cff"/>
  49. </svg>
  50. }
  51. # Based on https://icons8.com/icon/mEF_vyjYlnE3/file
  52. variable fileImageData {
  53. <?xml version="1.0" encoding="UTF-8"?>
  54. <svg width="16" height="16" version="1.1" xmlns="http://www.w3.org/2000/svg">
  55. <path d="m2 1h8l4 4v11h-12z" fill="#808080"/>
  56. <path d="m3 2h6.5l3.5 3.5v9.5h-10z" fill="#f0f0f0"/>
  57. <path d="m9 1v5h5v-1h-4v-4h-1z" fill="#808080"/>
  58. </svg>
  59. }
  60. # Create the images if they did not already exist.
  61. if {![info exists ::tk::Priv(updirImage)] ||
  62. $::tk::Priv(updirImage) ni [image names]} {
  63. set ::tk::Priv(updirImage) [image create photo \
  64. -format $::tk::svgFmt -data [UpdateUpdirImageData]]
  65. if {"TkFileDialog" ni [bindtags .]} {
  66. bindtags . [linsert [bindtags .] 1 TkFileDialog]
  67. bind TkFileDialog <<ThemeChanged>> {
  68. if {$::tk::Priv(updirImage) in [image names]} {
  69. $::tk::Priv(updirImage) configure \
  70. -data [::tk::dialog::file::UpdateUpdirImageData]
  71. }
  72. }
  73. }
  74. }
  75. if {![info exists ::tk::Priv(folderImage)] ||
  76. $::tk::Priv(folderImage) ni [image names]} {
  77. set ::tk::Priv(folderImage) [image create photo \
  78. -format $::tk::svgFmt -data $folderImageData]
  79. }
  80. if {![info exists ::tk::Priv(fileImage)] ||
  81. $::tk::Priv(fileImage) ni [image names]} {
  82. set ::tk::Priv(fileImage) [image create photo \
  83. -format $::tk::svgFmt -data $fileImageData]
  84. }
  85. }
  86. # ::tk::dialog::file:: --
  87. #
  88. # Implements the TK file selection dialog. This dialog is used when the
  89. # tk_strictMotif flag is set to false. This procedure shouldn't be
  90. # called directly. Call tk_getOpenFile or tk_getSaveFile instead.
  91. #
  92. # Arguments:
  93. # type "open" or "save"
  94. # args Options parsed by the procedure.
  95. #
  96. proc ::tk::dialog::file:: {type args} {
  97. variable ::tk::Priv
  98. variable showHiddenBtn
  99. set dataName __tk_filedialog
  100. upvar ::tk::dialog::file::$dataName data
  101. Config $dataName $type $args
  102. if {$data(-parent) eq "."} {
  103. set w .$dataName
  104. } else {
  105. set w $data(-parent).$dataName
  106. }
  107. # (re)create the dialog box if necessary
  108. #
  109. if {![winfo exists $w]} {
  110. Create $w TkFDialog
  111. } elseif {[winfo class $w] ne "TkFDialog"} {
  112. destroy $w
  113. Create $w TkFDialog
  114. } else {
  115. set data(dirMenuBtn) $w.contents.f1.menu
  116. set data(dirMenu) $w.contents.f1.menu.menu
  117. set data(upBtn) $w.contents.f1.up
  118. set data(icons) $w.contents.icons
  119. set data(ent) $w.contents.f2.ent
  120. set data(typeMenuLab) $w.contents.f2.lab2
  121. set data(typeMenuBtn) $w.contents.f2.menu
  122. set data(typeMenu) $data(typeMenuBtn).m
  123. set data(okBtn) $w.contents.f2.ok
  124. set data(cancelBtn) $w.contents.f2.cancel
  125. set data(hiddenBtn) $w.contents.f2.hidden
  126. SetSelectMode $w $data(-multiple)
  127. }
  128. if {$showHiddenBtn} {
  129. $data(hiddenBtn) configure -state normal
  130. grid $data(hiddenBtn)
  131. } else {
  132. $data(hiddenBtn) configure -state disabled
  133. grid remove $data(hiddenBtn)
  134. }
  135. # Make sure subseqent uses of this dialog are independent [Bug 845189]
  136. unset -nocomplain data(extUsed)
  137. # Dialog boxes should be transient with respect to their parent, so that
  138. # they will always stay on top of their parent window. However, some
  139. # window managers will create the window as withdrawn if the parent window
  140. # is withdrawn or iconified. Combined with the grab we put on the window,
  141. # this can hang the entire application. Therefore we only make the dialog
  142. # transient if the parent is viewable.
  143. if {[winfo viewable [winfo toplevel $data(-parent)]]} {
  144. wm transient $w $data(-parent)
  145. }
  146. # Add traces on the selectPath variable
  147. #
  148. trace add variable data(selectPath) write \
  149. [list ::tk::dialog::file::SetPath $w]
  150. $data(dirMenuBtn) configure \
  151. -textvariable ::tk::dialog::file::${dataName}(selectPath)
  152. # Cleanup previous menu
  153. #
  154. $data(typeMenu) delete 0 end
  155. $data(typeMenuBtn) configure -state normal -text ""
  156. # Initialize the file types menu
  157. #
  158. if {[llength $data(-filetypes)]} {
  159. # Default type and name to first entry
  160. set initialtype [lindex $data(-filetypes) 0]
  161. set initialTypeName [lindex $initialtype 0]
  162. if {$data(-typevariable) ne ""} {
  163. upvar #0 $data(-typevariable) typeVariable
  164. if {[info exists typeVariable]} {
  165. set initialTypeName $typeVariable
  166. }
  167. }
  168. foreach type $data(-filetypes) {
  169. set title [lindex $type 0]
  170. set filter [lindex $type 1]
  171. $data(typeMenu) add command -label $title \
  172. -command [list ::tk::dialog::file::SetFilter $w $type]
  173. # [string first] avoids glob-pattern char issues
  174. if {[string first ${initialTypeName} $title] == 0} {
  175. set initialtype $type
  176. }
  177. }
  178. SetFilter $w $initialtype
  179. $data(typeMenuBtn) configure -state normal
  180. $data(typeMenuLab) configure -state normal
  181. } else {
  182. set data(filter) "*"
  183. $data(typeMenuBtn) configure -state disabled -takefocus 0
  184. $data(typeMenuLab) configure -state disabled
  185. }
  186. UpdateWhenIdle $w
  187. # Withdraw the window, then update all the geometry information
  188. # so we know how big it wants to be, then center the window in the
  189. # display (Motif style) and de-iconify it.
  190. ::tk::PlaceWindow $w widget $data(-parent)
  191. wm title $w $data(-title)
  192. # Set a grab and claim the focus too.
  193. ::tk::SetFocusGrab $w $data(ent)
  194. $data(ent) delete 0 end
  195. $data(ent) insert 0 $data(selectFile)
  196. $data(ent) selection range 0 end
  197. $data(ent) icursor end
  198. # Wait for the user to respond, then restore the focus and return the
  199. # index of the selected button. Restore the focus before deleting the
  200. # window, since otherwise the window manager may take the focus away so we
  201. # can't redirect it. Finally, restore any grab that was in effect.
  202. vwait ::tk::Priv(selectFilePath)
  203. ::tk::RestoreFocusGrab $w $data(ent) withdraw
  204. # Cleanup traces on selectPath variable
  205. #
  206. foreach trace [trace info variable data(selectPath)] {
  207. trace remove variable data(selectPath) {*}$trace
  208. }
  209. if {[winfo exists $data(dirMenuBtn)]} {
  210. $data(dirMenuBtn) configure -textvariable {}
  211. }
  212. return $Priv(selectFilePath)
  213. }
  214. # ::tk::dialog::file::Config --
  215. #
  216. # Configures the TK filedialog according to the argument list
  217. #
  218. proc ::tk::dialog::file::Config {dataName type argList} {
  219. upvar ::tk::dialog::file::$dataName data
  220. set data(type) $type
  221. # 0: Delete all variable that were set on data(selectPath) the
  222. # last time the file dialog is used. The traces may cause troubles
  223. # if the dialog is now used with a different -parent option.
  224. foreach trace [trace info variable data(selectPath)] {
  225. trace remove variable data(selectPath) {*}$trace
  226. }
  227. # 1: the configuration specs
  228. #
  229. set specs {
  230. {-defaultextension "" "" ""}
  231. {-filetypes "" "" ""}
  232. {-initialdir "" "" ""}
  233. {-initialfile "" "" ""}
  234. {-parent "" "" "."}
  235. {-title "" "" ""}
  236. {-typevariable "" "" ""}
  237. }
  238. # The "-multiple" option is only available for the "open" file dialog.
  239. #
  240. if {$type eq "open"} {
  241. lappend specs {-multiple "" "" "0"}
  242. }
  243. # The "-confirmoverwrite" option is only for the "save" file dialog.
  244. #
  245. if {$type eq "save"} {
  246. lappend specs {-confirmoverwrite "" "" "1"}
  247. }
  248. # 2: default values depending on the type of the dialog
  249. #
  250. if {![info exists data(selectPath)]} {
  251. # first time the dialog has been popped up
  252. set data(selectPath) [pwd]
  253. set data(selectFile) ""
  254. }
  255. # 3: parse the arguments
  256. #
  257. tclParseConfigSpec ::tk::dialog::file::$dataName $specs "" $argList
  258. if {$data(-title) eq ""} {
  259. if {$type eq "open"} {
  260. set data(-title) [mc "Open"]
  261. } else {
  262. set data(-title) [mc "Save As"]
  263. }
  264. }
  265. # 4: set the default directory and selection according to the -initial
  266. # settings
  267. #
  268. if {$data(-initialdir) ne ""} {
  269. # Ensure that initialdir is an absolute path name.
  270. if {[file isdirectory $data(-initialdir)]} {
  271. set old [pwd]
  272. cd $data(-initialdir)
  273. set data(selectPath) [pwd]
  274. cd $old
  275. } else {
  276. set data(selectPath) [pwd]
  277. }
  278. }
  279. set data(selectFile) $data(-initialfile)
  280. # 5. Parse the -filetypes option
  281. #
  282. set data(origfiletypes) $data(-filetypes)
  283. set data(-filetypes) [::tk::FDGetFileTypes $data(-filetypes)]
  284. if {![winfo exists $data(-parent)]} {
  285. return -code error -errorcode [list TK LOOKUP WINDOW $data(-parent)] \
  286. "bad window path name \"$data(-parent)\""
  287. }
  288. # Set -multiple to a one or zero value (not other boolean types like
  289. # "yes") so we can use it in tests more easily.
  290. if {$type eq "save"} {
  291. set data(-multiple) 0
  292. } elseif {$data(-multiple)} {
  293. set data(-multiple) 1
  294. } else {
  295. set data(-multiple) 0
  296. }
  297. }
  298. proc ::tk::dialog::file::Create {w class} {
  299. set dataName [lindex [split $w .] end]
  300. upvar ::tk::dialog::file::$dataName data
  301. variable ::tk::Priv
  302. global tk_library
  303. toplevel $w -class $class
  304. if {[tk windowingsystem] eq "x11"} {wm attributes $w -type dialog}
  305. pack [ttk::frame $w.contents] -expand 1 -fill both
  306. #set w $w.contents
  307. # f1: the frame with the directory option menu
  308. #
  309. set f1 [ttk::frame $w.contents.f1]
  310. bind [::tk::AmpWidget ttk::label $f1.lab -text [mc "&Directory:"]] \
  311. <<AltUnderlined>> [list focus $f1.menu]
  312. set data(dirMenuBtn) $f1.menu
  313. if {![info exists data(selectPath)]} {
  314. set data(selectPath) ""
  315. }
  316. set data(dirMenu) $f1.menu.menu
  317. ttk::menubutton $f1.menu -menu $data(dirMenu) -direction flush \
  318. -textvariable [format %s(selectPath) ::tk::dialog::file::$dataName]
  319. menu $data(dirMenu) -tearoff 0
  320. $data(dirMenu) add radiobutton -label "" -variable \
  321. [format %s(selectPath) ::tk::dialog::file::$dataName]
  322. set data(upBtn) [ttk::button $f1.up]
  323. $data(upBtn) configure -image $Priv(updirImage)
  324. $f1.menu configure -takefocus 1;# -highlightthickness 2
  325. pack $data(upBtn) -side right -padx 3p -fill both
  326. pack $f1.lab -side left -padx 3p -fill both
  327. pack $f1.menu -expand yes -fill both -padx 3p
  328. # data(icons): the IconList that list the files and directories.
  329. #
  330. if {$class eq "TkFDialog"} {
  331. if { $data(-multiple) } {
  332. set fNameCaption [mc "File &names:"]
  333. } else {
  334. set fNameCaption [mc "File &name:"]
  335. }
  336. set fTypeCaption [mc "Files of &type:"]
  337. set iconListCommand [list ::tk::dialog::file::OkCmd $w]
  338. } else {
  339. set fNameCaption [mc "&Selection:"]
  340. set iconListCommand [list ::tk::dialog::file::chooseDir::DblClick $w]
  341. }
  342. set data(icons) [::tk::IconList $w.contents.icons \
  343. -command $iconListCommand -multiple $data(-multiple)]
  344. bind $data(icons) <<ListboxSelect>> \
  345. [list ::tk::dialog::file::ListBrowse $w]
  346. # f2: the frame with the OK button, cancel button, "file name" field
  347. # and file types field.
  348. #
  349. set f2 [ttk::frame $w.contents.f2]
  350. bind [::tk::AmpWidget ttk::label $f2.lab -text $fNameCaption -anchor e]\
  351. <<AltUnderlined>> [list focus $f2.ent]
  352. # -pady 0
  353. set data(ent) [ttk::entry $f2.ent]
  354. # The font to use for the icons. The default Canvas font on Unix is just
  355. # deviant.
  356. set ::tk::$w.contents.icons(font) [$data(ent) cget -font]
  357. # Make the file types bits only if this is a File Dialog
  358. if {$class eq "TkFDialog"} {
  359. set data(typeMenuLab) [::tk::AmpWidget ttk::label $f2.lab2 \
  360. -text $fTypeCaption -anchor e]
  361. # -pady [$f2.lab cget -pady]
  362. set data(typeMenuBtn) [ttk::menubutton $f2.menu \
  363. -menu $f2.menu.m]
  364. # -indicatoron 1
  365. set data(typeMenu) [menu $data(typeMenuBtn).m -tearoff 0]
  366. # $data(typeMenuBtn) configure -takefocus 1 -relief raised -anchor w
  367. bind $data(typeMenuLab) <<AltUnderlined>> [list \
  368. focus $data(typeMenuBtn)]
  369. }
  370. # The hidden button is displayed when ::tk::dialog::file::showHiddenBtn is
  371. # true. Create it disabled so the binding doesn't trigger if it isn't
  372. # shown.
  373. if {$class eq "TkFDialog"} {
  374. set text [mc "Show &Hidden Files and Directories"]
  375. } else {
  376. set text [mc "Show &Hidden Directories"]
  377. }
  378. set data(hiddenBtn) [::tk::AmpWidget ttk::checkbutton $f2.hidden \
  379. -text $text -state disabled \
  380. -variable ::tk::dialog::file::showHiddenVar \
  381. -command [list ::tk::dialog::file::UpdateWhenIdle $w]]
  382. # -anchor w -padx 2p
  383. # the okBtn is created after the typeMenu so that the keyboard traversal
  384. # is in the right order, and add binding so that we find out when the
  385. # dialog is destroyed by the user (added here instead of to the overall
  386. # window so no confusion about how much <Destroy> gets called; exactly
  387. # once will do). [Bug 987169]
  388. set data(okBtn) [::tk::AmpWidget ttk::button $f2.ok \
  389. -text [mc "&OK"] -default active];# -pady 2p]
  390. bind $data(okBtn) <Destroy> [list ::tk::dialog::file::Destroyed $w]
  391. set data(cancelBtn) [::tk::AmpWidget ttk::button $f2.cancel \
  392. -text [mc "&Cancel"] -default normal];# -pady 2p]
  393. # grid the widgets in f2
  394. #
  395. grid $f2.lab $f2.ent $data(okBtn) -padx 3p -pady 2p -sticky ew
  396. grid configure $f2.ent -padx 1.5p
  397. if {$class eq "TkFDialog"} {
  398. grid $data(typeMenuLab) $data(typeMenuBtn) $data(cancelBtn) \
  399. -padx 3p -sticky ew
  400. grid configure $data(typeMenuBtn) -padx 0
  401. grid $data(hiddenBtn) -columnspan 2 -padx 3p -sticky ew
  402. } else {
  403. grid $data(hiddenBtn) - $data(cancelBtn) -padx 3p -sticky ew
  404. }
  405. grid columnconfigure $f2 1 -weight 1
  406. # Pack all the frames together. We are done with widget construction.
  407. #
  408. pack $f1 -side top -fill x -pady 3p
  409. pack $f2 -side bottom -pady 3p -fill x
  410. pack $data(icons) -expand yes -fill both -padx 3p -pady 1p
  411. # Set up the event handlers that are common to Directory and File Dialogs
  412. #
  413. wm protocol $w WM_DELETE_WINDOW [list ::tk::dialog::file::CancelCmd $w]
  414. $data(upBtn) configure -command [list ::tk::dialog::file::UpDirCmd $w]
  415. $data(cancelBtn) configure -command [list ::tk::dialog::file::CancelCmd $w]
  416. bind $w <Escape> [list $data(cancelBtn) invoke]
  417. bind $w <Alt-Key> [list tk::AltKeyInDialog $w %A]
  418. # Set up event handlers specific to File or Directory Dialogs
  419. #
  420. if {$class eq "TkFDialog"} {
  421. bind $data(ent) <Return> [list ::tk::dialog::file::ActivateEnt $w]
  422. $data(okBtn) configure -command [list ::tk::dialog::file::OkCmd $w]
  423. bind $w <Alt-t> [format {
  424. if {[%s cget -state] eq "normal"} {
  425. focus %s
  426. }
  427. } $data(typeMenuBtn) $data(typeMenuBtn)]
  428. } else {
  429. set okCmd [list ::tk::dialog::file::chooseDir::OkCmd $w]
  430. bind $data(ent) <Return> $okCmd
  431. $data(okBtn) configure -command $okCmd
  432. bind $w <Alt-s> [list focus $data(ent)]
  433. bind $w <Alt-o> [list $data(okBtn) invoke]
  434. }
  435. bind $w <Alt-h> [list $data(hiddenBtn) invoke]
  436. bind $data(ent) <Tab> [list ::tk::dialog::file::CompleteEnt $w]
  437. # Build the focus group for all the entries
  438. #
  439. ::tk::FocusGroup_Create $w
  440. ::tk::FocusGroup_BindIn $w $data(ent) [list \
  441. ::tk::dialog::file::EntFocusIn $w]
  442. ::tk::FocusGroup_BindOut $w $data(ent) [list \
  443. ::tk::dialog::file::EntFocusOut $w]
  444. }
  445. # ::tk::dialog::file::SetSelectMode --
  446. #
  447. # Set the select mode of the dialog to single select or multi-select.
  448. #
  449. # Arguments:
  450. # w The dialog path.
  451. # multi 1 if the dialog is multi-select; 0 otherwise.
  452. #
  453. # Results:
  454. # None.
  455. proc ::tk::dialog::file::SetSelectMode {w multi} {
  456. set dataName __tk_filedialog
  457. upvar ::tk::dialog::file::$dataName data
  458. if { $multi } {
  459. set fNameCaption [mc "File &names:"]
  460. } else {
  461. set fNameCaption [mc "File &name:"]
  462. }
  463. set iconListCommand [list ::tk::dialog::file::OkCmd $w]
  464. ::tk::SetAmpText $w.contents.f2.lab $fNameCaption
  465. $data(icons) configure -multiple $multi -command $iconListCommand
  466. return
  467. }
  468. # ::tk::dialog::file::UpdateWhenIdle --
  469. #
  470. # Creates an idle event handler which updates the dialog in idle time.
  471. # This is important because loading the directory may take a long time
  472. # and we don't want to load the same directory for multiple times due to
  473. # multiple concurrent events.
  474. #
  475. proc ::tk::dialog::file::UpdateWhenIdle {w} {
  476. upvar ::tk::dialog::file::[winfo name $w] data
  477. if {[info exists data(updateId)]} {
  478. return
  479. }
  480. set data(updateId) [after idle [list ::tk::dialog::file::Update $w]]
  481. }
  482. # ::tk::dialog::file::Update --
  483. #
  484. # Loads the files and directories into the IconList widget. Also sets up
  485. # the directory option menu for quick access to parent directories.
  486. #
  487. proc ::tk::dialog::file::Update {w} {
  488. # This proc may be called within an idle handler. Make sure that the
  489. # window has not been destroyed before this proc is called
  490. if {![winfo exists $w]} {
  491. return
  492. }
  493. set class [winfo class $w]
  494. if {($class ne "TkFDialog") && ($class ne "TkChooseDir")} {
  495. return
  496. }
  497. set dataName [winfo name $w]
  498. upvar ::tk::dialog::file::$dataName data
  499. variable ::tk::Priv
  500. variable showHiddenVar
  501. global tk_library
  502. unset -nocomplain data(updateId)
  503. set folder $Priv(folderImage)
  504. set file $Priv(fileImage)
  505. set appPWD [pwd]
  506. if {[catch {
  507. cd $data(selectPath)
  508. }]} then {
  509. # We cannot change directory to $data(selectPath). $data(selectPath)
  510. # should have been checked before ::tk::dialog::file::Update is
  511. # called, so we normally won't come to here. Anyways, give an error
  512. # and abort action.
  513. tk_messageBox -type ok -parent $w -icon warning -message [mc \
  514. "Cannot change to the directory \"%1\$s\".\nPermission denied."\
  515. $data(selectPath)]
  516. cd $appPWD
  517. return
  518. }
  519. # Turn on the busy cursor. BUG?? We haven't disabled X events, though,
  520. # so the user may still click and cause havoc ...
  521. #
  522. set entCursor [$data(ent) cget -cursor]
  523. set dlgCursor [$w cget -cursor]
  524. $data(ent) configure -cursor watch
  525. $w configure -cursor watch
  526. update idletasks
  527. $data(icons) deleteall
  528. set showHidden $showHiddenVar
  529. # Make the dir list. Note that using an explicit [pwd] (instead of '.') is
  530. # better in some VFS cases.
  531. $data(icons) add $folder [GlobFiltered [pwd] d 1]
  532. if {$class eq "TkFDialog"} {
  533. # Make the file list if this is a File Dialog, selecting all but
  534. # 'd'irectory type files.
  535. #
  536. $data(icons) add $file [GlobFiltered [pwd] {f b c l p s}]
  537. }
  538. # Update the Directory: option menu
  539. #
  540. set list ""
  541. set dir ""
  542. foreach subdir [file split $data(selectPath)] {
  543. set dir [file join $dir $subdir]
  544. lappend list $dir
  545. }
  546. $data(dirMenu) delete 0 end
  547. set var [format %s(selectPath) ::tk::dialog::file::$dataName]
  548. foreach path $list {
  549. $data(dirMenu) add command -label $path -command [list set $var $path]
  550. }
  551. # Restore the PWD to the application's PWD
  552. #
  553. cd $appPWD
  554. if {$class eq "TkFDialog"} {
  555. # Restore the Open/Save Button if this is a File Dialog
  556. #
  557. if {$data(type) eq "open"} {
  558. ::tk::SetAmpText $data(okBtn) [mc "&Open"]
  559. } else {
  560. ::tk::SetAmpText $data(okBtn) [mc "&Save"]
  561. }
  562. }
  563. # turn off the busy cursor.
  564. #
  565. $data(ent) configure -cursor $entCursor
  566. $w configure -cursor $dlgCursor
  567. }
  568. # ::tk::dialog::file::SetPathSilently --
  569. #
  570. # Sets data(selectPath) without invoking the trace procedure
  571. #
  572. proc ::tk::dialog::file::SetPathSilently {w path} {
  573. upvar ::tk::dialog::file::[winfo name $w] data
  574. set cb [list ::tk::dialog::file::SetPath $w]
  575. trace remove variable data(selectPath) write $cb
  576. set data(selectPath) $path
  577. trace add variable data(selectPath) write $cb
  578. }
  579. # This proc gets called whenever data(selectPath) is set
  580. #
  581. proc ::tk::dialog::file::SetPath {w name1 name2 op} {
  582. if {[winfo exists $w]} {
  583. upvar ::tk::dialog::file::[winfo name $w] data
  584. UpdateWhenIdle $w
  585. # On directory dialogs, we keep the entry in sync with the currentdir.
  586. if {[winfo class $w] eq "TkChooseDir"} {
  587. $data(ent) delete 0 end
  588. $data(ent) insert end $data(selectPath)
  589. }
  590. }
  591. }
  592. # This proc gets called whenever data(filter) is set
  593. #
  594. proc ::tk::dialog::file::SetFilter {w type} {
  595. upvar ::tk::dialog::file::[winfo name $w] data
  596. set data(filterType) $type
  597. set data(filter) [lindex $type 1]
  598. $data(typeMenuBtn) configure -text [lindex $type 0] ;#-indicatoron 1
  599. # If we aren't using a default extension, use the one suppled by the
  600. # filter.
  601. if {![info exists data(extUsed)]} {
  602. if {[string length $data(-defaultextension)]} {
  603. set data(extUsed) 1
  604. } else {
  605. set data(extUsed) 0
  606. }
  607. }
  608. if {!$data(extUsed)} {
  609. # Get the first extension in the list that matches {^\*\.\w+$} and
  610. # remove all * from the filter.
  611. set index [lsearch -regexp $data(filter) {^\*\.\w+$}]
  612. if {$index >= 0} {
  613. set data(-defaultextension) \
  614. [string trimleft [lindex $data(filter) $index] "*"]
  615. } else {
  616. # Couldn't find anything! Reset to a safe default...
  617. set data(-defaultextension) ""
  618. }
  619. }
  620. $data(icons) see 0
  621. UpdateWhenIdle $w
  622. }
  623. # tk::dialog::file::ResolveFile --
  624. #
  625. # Interpret the user's text input in a file selection dialog. Performs:
  626. #
  627. # (1) ~ substitution
  628. # (2) resolve all instances of . and ..
  629. # (3) check for non-existent files/directories
  630. # (4) check for chdir permissions
  631. # (5) conversion of environment variable references to their
  632. # contents (once only)
  633. #
  634. # Arguments:
  635. # context: the current directory you are in
  636. # text: the text entered by the user
  637. # defaultext: the default extension to add to files with no extension
  638. # expandEnv: whether to expand environment variables (yes by default)
  639. #
  640. # Return vaue:
  641. # [list $flag $directory $file]
  642. #
  643. # flag = OK : valid input
  644. # = PATTERN : valid directory/pattern
  645. # = PATH : the directory does not exist
  646. # = FILE : the directory exists by the file doesn't exist
  647. # = CHDIR : Cannot change to the directory
  648. # = ERROR : Invalid entry
  649. #
  650. # directory : valid only if flag = OK or PATTERN or FILE
  651. # file : valid only if flag = OK or PATTERN
  652. #
  653. # directory may not be the same as context, because text may contain a
  654. # subdirectory name
  655. #
  656. proc ::tk::dialog::file::ResolveFile {context text defaultext {expandEnv 1}} {
  657. set appPWD [pwd]
  658. set path [JoinFile $context $text]
  659. # If the file has no extension, append the default. Be careful not to do
  660. # this for directories, otherwise typing a dirname in the box will give
  661. # back "dirname.extension" instead of trying to change dir.
  662. if {
  663. ![file isdirectory $path] && ([file ext $path] eq "") &&
  664. ![string match {$*} [file tail $path]]
  665. } then {
  666. set path "$path$defaultext"
  667. }
  668. if {[catch {file exists $path}]} {
  669. # This "if" block can be safely removed if the following code stop
  670. # generating errors.
  671. #
  672. # file exists ~nonsuchuser
  673. #
  674. return [list ERROR $path ""]
  675. }
  676. if {[file exists $path]} {
  677. if {[file isdirectory $path]} {
  678. if {[catch {cd $path}]} {
  679. return [list CHDIR $path ""]
  680. }
  681. set directory [pwd]
  682. set file ""
  683. set flag OK
  684. cd $appPWD
  685. } else {
  686. if {[catch {cd [file dirname $path]}]} {
  687. return [list CHDIR [file dirname $path] ""]
  688. }
  689. set directory [pwd]
  690. set file [file tail $path]
  691. set flag OK
  692. cd $appPWD
  693. }
  694. } else {
  695. set dirname [file dirname $path]
  696. if {[file exists $dirname]} {
  697. if {[catch {cd $dirname}]} {
  698. return [list CHDIR $dirname ""]
  699. }
  700. set directory [pwd]
  701. cd $appPWD
  702. set file [file tail $path]
  703. # It's nothing else, so check to see if it is an env-reference
  704. if {$expandEnv && [string match {$*} $file]} {
  705. set var [string range $file 1 end]
  706. if {[info exist ::env($var)]} {
  707. return [ResolveFile $context $::env($var) $defaultext 0]
  708. }
  709. }
  710. if {[regexp {[*?]} $file]} {
  711. set flag PATTERN
  712. } else {
  713. set flag FILE
  714. }
  715. } else {
  716. set directory $dirname
  717. set file [file tail $path]
  718. set flag PATH
  719. # It's nothing else, so check to see if it is an env-reference
  720. if {$expandEnv && [string match {$*} $file]} {
  721. set var [string range $file 1 end]
  722. if {[info exist ::env($var)]} {
  723. return [ResolveFile $context $::env($var) $defaultext 0]
  724. }
  725. }
  726. }
  727. }
  728. return [list $flag $directory $file]
  729. }
  730. # Gets called when the entry box gets keyboard focus. We clear the selection
  731. # from the icon list . This way the user can be certain that the input in the
  732. # entry box is the selection.
  733. #
  734. proc ::tk::dialog::file::EntFocusIn {w} {
  735. upvar ::tk::dialog::file::[winfo name $w] data
  736. if {[$data(ent) get] ne ""} {
  737. $data(ent) selection range 0 end
  738. $data(ent) icursor end
  739. } else {
  740. $data(ent) selection clear
  741. }
  742. if {[winfo class $w] eq "TkFDialog"} {
  743. # If this is a File Dialog, make sure the buttons are labeled right.
  744. if {$data(type) eq "open"} {
  745. ::tk::SetAmpText $data(okBtn) [mc "&Open"]
  746. } else {
  747. ::tk::SetAmpText $data(okBtn) [mc "&Save"]
  748. }
  749. }
  750. }
  751. proc ::tk::dialog::file::EntFocusOut {w} {
  752. upvar ::tk::dialog::file::[winfo name $w] data
  753. $data(ent) selection clear
  754. }
  755. # Gets called when user presses Return in the "File name" entry.
  756. #
  757. proc ::tk::dialog::file::ActivateEnt {w} {
  758. upvar ::tk::dialog::file::[winfo name $w] data
  759. set text [$data(ent) get]
  760. if {$data(-multiple)} {
  761. foreach t $text {
  762. VerifyFileName $w $t
  763. }
  764. } else {
  765. VerifyFileName $w $text
  766. }
  767. }
  768. # Verification procedure
  769. #
  770. proc ::tk::dialog::file::VerifyFileName {w filename} {
  771. upvar ::tk::dialog::file::[winfo name $w] data
  772. set list [ResolveFile $data(selectPath) $filename $data(-defaultextension)]
  773. foreach {flag path file} $list {
  774. break
  775. }
  776. switch -- $flag {
  777. OK {
  778. if {$file eq ""} {
  779. # user has entered an existing (sub)directory
  780. set data(selectPath) $path
  781. $data(ent) delete 0 end
  782. } else {
  783. SetPathSilently $w $path
  784. if {$data(-multiple)} {
  785. lappend data(selectFile) $file
  786. } else {
  787. set data(selectFile) $file
  788. }
  789. Done $w
  790. }
  791. }
  792. PATTERN {
  793. set data(selectPath) $path
  794. set data(filter) $file
  795. }
  796. FILE {
  797. if {$data(type) eq "open"} {
  798. tk_messageBox -icon warning -type ok -parent $w \
  799. -message [mc "File \"%1\$s\" does not exist." \
  800. [file join $path $file]]
  801. $data(ent) selection range 0 end
  802. $data(ent) icursor end
  803. } else {
  804. SetPathSilently $w $path
  805. if {$data(-multiple)} {
  806. lappend data(selectFile) $file
  807. } else {
  808. set data(selectFile) $file
  809. }
  810. Done $w
  811. }
  812. }
  813. PATH {
  814. tk_messageBox -icon warning -type ok -parent $w -message \
  815. [mc "Directory \"%1\$s\" does not exist." $path]
  816. $data(ent) selection range 0 end
  817. $data(ent) icursor end
  818. }
  819. CHDIR {
  820. tk_messageBox -type ok -parent $w -icon warning -message \
  821. [mc "Cannot change to the directory\
  822. \"%1\$s\".\nPermission denied." $path]
  823. $data(ent) selection range 0 end
  824. $data(ent) icursor end
  825. }
  826. ERROR {
  827. tk_messageBox -type ok -parent $w -icon warning -message \
  828. [mc "Invalid file name \"%1\$s\"." $path]
  829. $data(ent) selection range 0 end
  830. $data(ent) icursor end
  831. }
  832. }
  833. }
  834. # Gets called when user presses the Alt-s or Alt-o keys.
  835. #
  836. proc ::tk::dialog::file::InvokeBtn {w key} {
  837. upvar ::tk::dialog::file::[winfo name $w] data
  838. if {[$data(okBtn) cget -text] eq $key} {
  839. $data(okBtn) invoke
  840. }
  841. }
  842. # Gets called when user presses the "parent directory" button
  843. #
  844. proc ::tk::dialog::file::UpDirCmd {w} {
  845. upvar ::tk::dialog::file::[winfo name $w] data
  846. if {$data(selectPath) ne "/"} {
  847. set data(selectPath) [file dirname $data(selectPath)]
  848. }
  849. }
  850. # Join a file name to a path name. The "file join" command will break if the
  851. # filename begins with ~
  852. #
  853. proc ::tk::dialog::file::JoinFile {path file} {
  854. if {[string match {~*} $file] && [file exists $path/$file]} {
  855. return [file join $path ./$file]
  856. } else {
  857. return [file join $path $file]
  858. }
  859. }
  860. # Gets called when user presses the "OK" button
  861. #
  862. proc ::tk::dialog::file::OkCmd {w} {
  863. upvar ::tk::dialog::file::[winfo name $w] data
  864. set filenames {}
  865. foreach item [$data(icons) selection get] {
  866. lappend filenames [$data(icons) get $item]
  867. }
  868. if {
  869. ([llength $filenames] && !$data(-multiple)) ||
  870. ($data(-multiple) && ([llength $filenames] == 1))
  871. } then {
  872. set filename [lindex $filenames 0]
  873. set file [JoinFile $data(selectPath) $filename]
  874. if {[file isdirectory $file]} {
  875. ListInvoke $w [list $filename]
  876. return
  877. }
  878. }
  879. ActivateEnt $w
  880. }
  881. # Gets called when user presses the "Cancel" button
  882. #
  883. proc ::tk::dialog::file::CancelCmd {w} {
  884. upvar ::tk::dialog::file::[winfo name $w] data
  885. variable ::tk::Priv
  886. bind $data(okBtn) <Destroy> {}
  887. set Priv(selectFilePath) ""
  888. }
  889. # Gets called when user destroys the dialog directly [Bug 987169]
  890. #
  891. proc ::tk::dialog::file::Destroyed {w} {
  892. upvar ::tk::dialog::file::[winfo name $w] data
  893. variable ::tk::Priv
  894. set Priv(selectFilePath) ""
  895. }
  896. # Gets called when user browses the IconList widget (dragging mouse, arrow
  897. # keys, etc)
  898. #
  899. proc ::tk::dialog::file::ListBrowse {w} {
  900. upvar ::tk::dialog::file::[winfo name $w] data
  901. set text {}
  902. foreach item [$data(icons) selection get] {
  903. lappend text [$data(icons) get $item]
  904. }
  905. if {[llength $text] == 0} {
  906. return
  907. }
  908. if {$data(-multiple)} {
  909. set newtext {}
  910. foreach file $text {
  911. set fullfile [JoinFile $data(selectPath) $file]
  912. if { ![file isdirectory $fullfile] } {
  913. lappend newtext $file
  914. }
  915. }
  916. set text $newtext
  917. set isDir 0
  918. } else {
  919. set text [lindex $text 0]
  920. set file [JoinFile $data(selectPath) $text]
  921. set isDir [file isdirectory $file]
  922. }
  923. if {!$isDir} {
  924. $data(ent) delete 0 end
  925. $data(ent) insert 0 $text
  926. if {[winfo class $w] eq "TkFDialog"} {
  927. if {$data(type) eq "open"} {
  928. ::tk::SetAmpText $data(okBtn) [mc "&Open"]
  929. } else {
  930. ::tk::SetAmpText $data(okBtn) [mc "&Save"]
  931. }
  932. }
  933. } elseif {[winfo class $w] eq "TkFDialog"} {
  934. ::tk::SetAmpText $data(okBtn) [mc "&Open"]
  935. }
  936. }
  937. # Gets called when user invokes the IconList widget (double-click, Return key,
  938. # etc)
  939. #
  940. proc ::tk::dialog::file::ListInvoke {w filenames} {
  941. upvar ::tk::dialog::file::[winfo name $w] data
  942. if {[llength $filenames] == 0} {
  943. return
  944. }
  945. set file [JoinFile $data(selectPath) [lindex $filenames 0]]
  946. set class [winfo class $w]
  947. if {$class eq "TkChooseDir" || [file isdirectory $file]} {
  948. set appPWD [pwd]
  949. if {[catch {cd $file}]} {
  950. tk_messageBox -type ok -parent $w -icon warning -message \
  951. [mc "Cannot change to the directory \"%1\$s\".\nPermission denied." $file]
  952. } else {
  953. cd $appPWD
  954. set data(selectPath) $file
  955. }
  956. } else {
  957. if {$data(-multiple)} {
  958. set data(selectFile) $filenames
  959. } else {
  960. set data(selectFile) $file
  961. }
  962. Done $w
  963. }
  964. }
  965. # ::tk::dialog::file::Done --
  966. #
  967. # Gets called when user has input a valid filename. Pops up a dialog
  968. # box to confirm selection when necessary. Sets the
  969. # tk::Priv(selectFilePath) variable, which will break the "vwait" loop
  970. # in ::tk::dialog::file:: and return the selected filename to the script
  971. # that calls tk_getOpenFile or tk_getSaveFile
  972. #
  973. proc ::tk::dialog::file::Done {w {selectFilePath ""}} {
  974. upvar ::tk::dialog::file::[winfo name $w] data
  975. variable ::tk::Priv
  976. if {$selectFilePath eq ""} {
  977. if {$data(-multiple)} {
  978. set selectFilePath {}
  979. foreach f $data(selectFile) {
  980. lappend selectFilePath [JoinFile $data(selectPath) $f]
  981. }
  982. } else {
  983. set selectFilePath [JoinFile $data(selectPath) $data(selectFile)]
  984. }
  985. set Priv(selectFile) $data(selectFile)
  986. set Priv(selectPath) $data(selectPath)
  987. if {($data(type) eq "save") && $data(-confirmoverwrite) && [file exists $selectFilePath]} {
  988. set reply [tk_messageBox -icon warning -type yesno -parent $w \
  989. -message [mc "File \"%1\$s\" already exists.\nDo you want\
  990. to overwrite it?" $selectFilePath]]
  991. if {$reply eq "no"} {
  992. return
  993. }
  994. }
  995. if {
  996. [info exists data(-typevariable)] && $data(-typevariable) ne ""
  997. && [info exists data(-filetypes)] && [llength $data(-filetypes)]
  998. && [info exists data(filterType)] && $data(filterType) ne ""
  999. } then {
  1000. upvar #0 $data(-typevariable) typeVariable
  1001. set typeVariable [lindex $data(origfiletypes) \
  1002. [lsearch -exact $data(-filetypes) $data(filterType)] 0]
  1003. }
  1004. }
  1005. bind $data(okBtn) <Destroy> {}
  1006. set Priv(selectFilePath) $selectFilePath
  1007. }
  1008. # ::tk::dialog::file::GlobFiltered --
  1009. #
  1010. # Gets called to do globbing, returning the results and filtering them
  1011. # according to the current filter (and removing the entries for '.' and
  1012. # '..' which are never shown). Deals with evil cases such as where the
  1013. # user is supplying a filter which is an invalid list or where it has an
  1014. # unbalanced brace. The resulting list will be dictionary sorted.
  1015. #
  1016. # Arguments:
  1017. # dir Which directory to search
  1018. # type List of filetypes to look for ('d' or 'f b c l p s')
  1019. # overrideFilter Whether to ignore the filter for this search.
  1020. #
  1021. # NB: Assumes that the caller has mapped the state variable to 'data'.
  1022. #
  1023. proc ::tk::dialog::file::GlobFiltered {dir type {overrideFilter 0}} {
  1024. variable showHiddenVar
  1025. upvar 1 data(filter) filter
  1026. if {$filter eq "*" || $overrideFilter} {
  1027. set patterns [list *]
  1028. if {$showHiddenVar} {
  1029. lappend patterns .*
  1030. }
  1031. } elseif {[string is list $filter]} {
  1032. set patterns $filter
  1033. } else {
  1034. # Invalid list; assume we can use non-whitespace sequences as words
  1035. set patterns [regexp -inline -all {\S+} $filter]
  1036. }
  1037. set opts [list -tails -directory $dir -type $type -nocomplain]
  1038. set result {}
  1039. catch {
  1040. # We have a catch because we might have a really bad pattern (e.g.,
  1041. # with an unbalanced brace); even [glob -nocomplain] doesn't like it.
  1042. # Using a catch ensures that it just means we match nothing instead of
  1043. # throwing a nasty error at the user...
  1044. foreach f [glob {*}$opts -- {*}$patterns] {
  1045. if {$f eq "." || $f eq ".."} {
  1046. continue
  1047. }
  1048. # See ticket [1641721], $f might be a link pointing to a dir
  1049. if {$type != "d" && [file isdir [file join $dir $f]]} {
  1050. continue
  1051. }
  1052. lappend result $f
  1053. }
  1054. }
  1055. return [lsort -dictionary -unique $result]
  1056. }
  1057. proc ::tk::dialog::file::CompleteEnt {w} {
  1058. upvar ::tk::dialog::file::[winfo name $w] data
  1059. set f [$data(ent) get]
  1060. if {$data(-multiple)} {
  1061. if {![string is list $f] || [llength $f] != 1} {
  1062. return -code break
  1063. }
  1064. set f [lindex $f 0]
  1065. }
  1066. # Get list of matching filenames and dirnames
  1067. set files [if {[winfo class $w] eq "TkFDialog"} {
  1068. GlobFiltered $data(selectPath) {f b c l p s}
  1069. }]
  1070. set dirs2 {}
  1071. foreach d [GlobFiltered $data(selectPath) d] {lappend dirs2 $d/}
  1072. set targets [concat \
  1073. [lsearch -glob -all -inline $files $f*] \
  1074. [lsearch -glob -all -inline $dirs2 $f*]]
  1075. if {[llength $targets] == 1} {
  1076. # We have a winner!
  1077. set f [lindex $targets 0]
  1078. } elseif {$f in $targets || [llength $targets] == 0} {
  1079. if {[string length $f] > 0} {
  1080. bell
  1081. }
  1082. return
  1083. } elseif {[llength $targets] > 1} {
  1084. # Multiple possibles
  1085. if {[string length $f] == 0} {
  1086. return
  1087. }
  1088. set t0 [lindex $targets 0]
  1089. for {set len [string length $t0]} {$len>0} {} {
  1090. set allmatch 1
  1091. foreach s $targets {
  1092. if {![string equal -length $len $s $t0]} {
  1093. set allmatch 0
  1094. break
  1095. }
  1096. }
  1097. incr len -1
  1098. if {$allmatch} break
  1099. }
  1100. set f [string range $t0 0 $len]
  1101. }
  1102. if {$data(-multiple)} {
  1103. set f [list $f]
  1104. }
  1105. $data(ent) delete 0 end
  1106. $data(ent) insert 0 $f
  1107. return -code break
  1108. }