diff options
Diffstat (limited to 'tk8.6/library/tkfbox.tcl')
-rw-r--r-- | tk8.6/library/tkfbox.tcl | 1240 |
1 files changed, 1240 insertions, 0 deletions
diff --git a/tk8.6/library/tkfbox.tcl b/tk8.6/library/tkfbox.tcl new file mode 100644 index 0000000..f73fdc5 --- /dev/null +++ b/tk8.6/library/tkfbox.tcl @@ -0,0 +1,1240 @@ +# tkfbox.tcl -- +# +# Implements the "TK" standard file selection dialog box. This dialog +# box is used on the Unix platforms whenever the tk_strictMotif flag is +# not set. +# +# The "TK" standard file selection dialog box is similar to the file +# selection dialog box on Win95(TM). The user can navigate the +# directories by clicking on the folder icons or by selecting the +# "Directory" option menu. The user can select files by clicking on the +# file icons or by entering a filename in the "Filename:" entry. +# +# Copyright (c) 1994-1998 Sun Microsystems, Inc. +# +# See the file "license.terms" for information on usage and redistribution +# of this file, and for a DISCLAIMER OF ALL WARRANTIES. +# + +namespace eval ::tk::dialog {} +namespace eval ::tk::dialog::file { + namespace import -force ::tk::msgcat::* + variable showHiddenBtn 0 + variable showHiddenVar 1 + + # Create the images if they did not already exist. + if {![info exists ::tk::Priv(updirImage)]} { + set ::tk::Priv(updirImage) [image create photo -data { + iVBORw0KGgoAAAANSUhEUgAAABYAAAAWCAYAAADEtGw7AAAABmJLR0QA/gD+AP7rGN + SCAAAACXBIWXMAAA3WAAAN1gGQb3mcAAAACXZwQWcAAAAWAAAAFgDcxelYAAAENUlE + QVQ4y7WUbWiVZRjHf/f9POcc9+Kc5bC2aIq5sGG0XnTzNU13zAIlFMNc9CEhTCKwCC + JIgt7AglaR0RcrolAKg14+GBbiGL6xZiYyy63cmzvu7MVznnOe537rw7bDyvlBoT/c + n+6L3/3nf13XLZLJJP+HfICysjKvqqpq+rWKysvLR1tbW+11g+fPn/+bEGIe4KYqCs + Owu66u7oG2trah6wJrrRc0NTVhjME5h7Vj5pxzCCE4duxYZUdHx/aGhoZmgJ+yb+wF + uCO19RmAffv25f8LFslkktraWtvU1CS6u7vRWmOtxVpbAPu+T0tLS04pFU/J34Wd3S + cdFtlfZWeZBU4IcaS5uXn1ZLAEMMY4ay1aa4wx/zpKKYIgoL6+vmjxqoXe5ZLTcsPq + bTyycjODpe1y3WMrvDAMV14jCuW0VhhjiJQpOJ5w7Zwjk8/y9R+vsHHNNq6oFMrkeX + BxI+8d2sktap3YvOPD0lRQrH+Z81fE7t3WB4gihVKazsuaA20aKSUgAG/seQdy2l6W + 37+EyopqTv39I6HJUT2zlnlza2jLdgiTaxwmDov6alLHcZUTzXPGGAauWJbfO4dHl9 + bgJs3HyfNf0N4ZsOa+jbT3/ownY/hO09p1kBULtjBw+Tvq7xzwauds4dWPDleAcP5E + xlprgtBRUZRgYCRPTzoHwEi2g6OnX+eFrW/RM9qBE4p43CeTz5ATaU6nDrFm2cPs/+ + E1SopqkZ7MFJqntXZaa7IKppckwIEvJbg8LWd28OT6nVihCPQQ8UScWCLGqO4hXuQx + qDtJ204eWrqWb1ufRspwtABWaqx5gRKUFSdwDnxPcuLcyyxbuIyaqntIBV34MY9YzC + Owg+S9YeJFkniRpGPkCLMrZzG3+jbktA/KClMxFoUhiKC0OAbAhd79CO8i6xe/STyW + 4O7KVRgUJ/sP0heeJV4kEVKw/vZd40sFKxat4mLvp6VLdvnb/XHHGGPIKwBBpC1/9n + 3DpfRZnn9/AwCxRII9O79kVPdjvByxuET6Ai8mePeTt4lyheXzhOSpCcdWa00uckTG + kckbGu76nEhbIm2xznH4VB3OWYaiXqQn8GKSWGIMHuXyPL76LBcupmhp69pz4uMnXi + w4VloTGcdQRtGdzmHs1f+RdYZslMZJhzUOHVnceN1ooEiP5JUzdqCQMWCD0JCIeQzn + NNpO+clhrCYf5rC+A2cxWmDUWG2oHEOZMEKIwclgMnnLrTeXUV7sUzpNXgU9DmijWV + v9LEKCkAIhKIBnlvpks6F21qUZ31u/sbExPa9h0/RzwzMov2nGlG5TmW1YOzzlnSfL + mVnyGf19Q7lwZHBp+1fPtflAIgiC7389n9qkihP+lWyeqfUO15ZwQTqlw9H+o2cOvN + QJCAHEgEqgYnI0NyALjAJdyWQy7wMa6AEujUdzo3LjcAXwD/XCTKIRjWytAAAAJXRF + WHRjcmVhdGUtZGF0ZQAyMDA5LTA0LTA2VDIxOjI1OjQxLTAzOjAw8s+uCAAAACV0RV + h0bW9kaWZ5LWRhdGUAMjAwOC0wMS0wM1QxNTowODoyMS0wMjowMJEc/44AAAAZdEVY + dFNvZnR3YXJlAHd3dy5pbmtzY2FwZS5vcmeb7jwaAAAAAElFTkSuQmCC + }] + } + if {![info exists ::tk::Priv(folderImage)]} { + set ::tk::Priv(folderImage) [image create photo -data { + iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiA + AAAAlwSFlzAAAN1wAADdcBQiibeAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBl + Lm9yZ5vuPBoAAAHCSURBVDiNpZAxa5NRFIafc+9XLCni4BC6FBycMnbrLpkcgtDVX6 + C70D/g4lZX/4coxLlgxFkpiiSSUGm/JiXfveee45AmNlhawXc53HvPee55X+l2u/yP + qt3d3Tfu/viatwt3fzIYDI5uBJhZr9fr3TMzzAx3B+D09PR+v98/7HQ6z5fNOWdCCG + U4HH6s67oAVDlnV1UmkwmllBUkhMD29nYHeLuEAkyn06qU8qqu64MrgIyqYmZrkHa7 + 3drc3KTVahFjJITAaDRiPB4/XFlQVVMtHH5IzJo/P4EA4MyB+erWPQB7++zs7ccYvl + U5Z08pMW2cl88eIXLZeDUpXzsBkNQ5eP1+p0opmaoCTgzw6fjs6gLLsp58FB60t0Dc + K1Ul54yIEIMQ43Uj68pquDmCeJVztpwzuBNE2LgBoMVpslHMCUEAFgDVxQbzVAiA+a + K5uGPmmDtZF3VpoUm2ArhqQaRiUjcMf81p1G60UEVhcjZfAFTVUkrgkS+jc06mDX9n + vq4YhJ9nlxZExMwMEaHJRutOdWuIIsJFUoBSuTvHJ4YIfP46unV4qdlsjsBRZRtb/X + fHd5+C8+P7+J8BIoxFwovfRxYhnhxjpzEAAAAASUVORK5CYII= + }] + } + if {![info exists ::tk::Priv(fileImage)]} { + set ::tk::Priv(fileImage) [image create photo -data { + iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gva + eTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH1QQWFA84umAmQgAAANpJREFU + OMutkj1uhDAQhb8HSLtbISGfgZ+zbJkix0HmFhwhUdocBnMBGvqtTIqIFSReWKK8ai + x73nwzHrVt+zEMwwvH9FrX9TsA1trpqKy10+yUzME4jnjvAZB0LzXHkojjmDRNVyh3 + A+89zrlVwlKSqKrqVy/J8lAUxSZBSMny4ZLgp54iyPM8UPHGNJ2IomibAKDv+9VlWZ + bABbgB5/0WQgSSkC4PF2JF4JzbHN430c4vhAm0TyCJruuClefph4yCBCGT3T3Isoy/ + KDHGfDZNcz2SZIx547/0BVRRX7n8uT/sAAAAAElFTkSuQmCC + }] + } +} + +# ::tk::dialog::file:: -- +# +# Implements the TK file selection dialog. This dialog is used when the +# tk_strictMotif flag is set to false. This procedure shouldn't be +# called directly. Call tk_getOpenFile or tk_getSaveFile instead. +# +# Arguments: +# type "open" or "save" +# args Options parsed by the procedure. +# + +proc ::tk::dialog::file:: {type args} { + variable ::tk::Priv + variable showHiddenBtn + set dataName __tk_filedialog + upvar ::tk::dialog::file::$dataName data + + Config $dataName $type $args + + if {$data(-parent) eq "."} { + set w .$dataName + } else { + set w $data(-parent).$dataName + } + + # (re)create the dialog box if necessary + # + if {![winfo exists $w]} { + Create $w TkFDialog + } elseif {[winfo class $w] ne "TkFDialog"} { + destroy $w + Create $w TkFDialog + } else { + set data(dirMenuBtn) $w.contents.f1.menu + set data(dirMenu) $w.contents.f1.menu.menu + set data(upBtn) $w.contents.f1.up + set data(icons) $w.contents.icons + set data(ent) $w.contents.f2.ent + set data(typeMenuLab) $w.contents.f2.lab2 + set data(typeMenuBtn) $w.contents.f2.menu + set data(typeMenu) $data(typeMenuBtn).m + set data(okBtn) $w.contents.f2.ok + set data(cancelBtn) $w.contents.f2.cancel + set data(hiddenBtn) $w.contents.f2.hidden + SetSelectMode $w $data(-multiple) + } + if {$showHiddenBtn} { + $data(hiddenBtn) configure -state normal + grid $data(hiddenBtn) + } else { + $data(hiddenBtn) configure -state disabled + grid remove $data(hiddenBtn) + } + + # Make sure subseqent uses of this dialog are independent [Bug 845189] + unset -nocomplain data(extUsed) + + # Dialog boxes should be transient with respect to their parent, so that + # they will always stay on top of their parent window. However, some + # window managers will create the window as withdrawn if the parent window + # is withdrawn or iconified. Combined with the grab we put on the window, + # this can hang the entire application. Therefore we only make the dialog + # transient if the parent is viewable. + + if {[winfo viewable [winfo toplevel $data(-parent)]]} { + wm transient $w $data(-parent) + } + + # Add traces on the selectPath variable + # + + trace add variable data(selectPath) write \ + [list ::tk::dialog::file::SetPath $w] + $data(dirMenuBtn) configure \ + -textvariable ::tk::dialog::file::${dataName}(selectPath) + + # Cleanup previous menu + # + $data(typeMenu) delete 0 end + $data(typeMenuBtn) configure -state normal -text "" + + # Initialize the file types menu + # + if {[llength $data(-filetypes)]} { + # Default type and name to first entry + set initialtype [lindex $data(-filetypes) 0] + set initialTypeName [lindex $initialtype 0] + if {$data(-typevariable) ne ""} { + upvar #0 $data(-typevariable) typeVariable + if {[info exists typeVariable]} { + set initialTypeName $typeVariable + } + } + foreach type $data(-filetypes) { + set title [lindex $type 0] + set filter [lindex $type 1] + $data(typeMenu) add command -label $title \ + -command [list ::tk::dialog::file::SetFilter $w $type] + # [string first] avoids glob-pattern char issues + if {[string first ${initialTypeName} $title] == 0} { + set initialtype $type + } + } + SetFilter $w $initialtype + $data(typeMenuBtn) configure -state normal + $data(typeMenuLab) configure -state normal + } else { + set data(filter) "*" + $data(typeMenuBtn) configure -state disabled -takefocus 0 + $data(typeMenuLab) configure -state disabled + } + UpdateWhenIdle $w + + # Withdraw the window, then update all the geometry information + # so we know how big it wants to be, then center the window in the + # display (Motif style) and de-iconify it. + + ::tk::PlaceWindow $w widget $data(-parent) + wm title $w $data(-title) + + # Set a grab and claim the focus too. + + ::tk::SetFocusGrab $w $data(ent) + $data(ent) delete 0 end + $data(ent) insert 0 $data(selectFile) + $data(ent) selection range 0 end + $data(ent) icursor end + + # Wait for the user to respond, then restore the focus and return the + # index of the selected button. Restore the focus before deleting the + # window, since otherwise the window manager may take the focus away so we + # can't redirect it. Finally, restore any grab that was in effect. + + vwait ::tk::Priv(selectFilePath) + + ::tk::RestoreFocusGrab $w $data(ent) withdraw + + # Cleanup traces on selectPath variable + # + + foreach trace [trace info variable data(selectPath)] { + trace remove variable data(selectPath) {*}$trace + } + $data(dirMenuBtn) configure -textvariable {} + + return $Priv(selectFilePath) +} + +# ::tk::dialog::file::Config -- +# +# Configures the TK filedialog according to the argument list +# +proc ::tk::dialog::file::Config {dataName type argList} { + upvar ::tk::dialog::file::$dataName data + + set data(type) $type + + # 0: Delete all variable that were set on data(selectPath) the + # last time the file dialog is used. The traces may cause troubles + # if the dialog is now used with a different -parent option. + + foreach trace [trace info variable data(selectPath)] { + trace remove variable data(selectPath) {*}$trace + } + + # 1: the configuration specs + # + set specs { + {-defaultextension "" "" ""} + {-filetypes "" "" ""} + {-initialdir "" "" ""} + {-initialfile "" "" ""} + {-parent "" "" "."} + {-title "" "" ""} + {-typevariable "" "" ""} + } + + # The "-multiple" option is only available for the "open" file dialog. + # + if {$type eq "open"} { + lappend specs {-multiple "" "" "0"} + } + + # The "-confirmoverwrite" option is only for the "save" file dialog. + # + if {$type eq "save"} { + lappend specs {-confirmoverwrite "" "" "1"} + } + + # 2: default values depending on the type of the dialog + # + if {![info exists data(selectPath)]} { + # first time the dialog has been popped up + set data(selectPath) [pwd] + set data(selectFile) "" + } + + # 3: parse the arguments + # + tclParseConfigSpec ::tk::dialog::file::$dataName $specs "" $argList + + if {$data(-title) eq ""} { + if {$type eq "open"} { + set data(-title) [mc "Open"] + } else { + set data(-title) [mc "Save As"] + } + } + + # 4: set the default directory and selection according to the -initial + # settings + # + if {$data(-initialdir) ne ""} { + # Ensure that initialdir is an absolute path name. + if {[file isdirectory $data(-initialdir)]} { + set old [pwd] + cd $data(-initialdir) + set data(selectPath) [pwd] + cd $old + } else { + set data(selectPath) [pwd] + } + } + set data(selectFile) $data(-initialfile) + + # 5. Parse the -filetypes option + # + set data(origfiletypes) $data(-filetypes) + set data(-filetypes) [::tk::FDGetFileTypes $data(-filetypes)] + + if {![winfo exists $data(-parent)]} { + return -code error -errorcode [list TK LOOKUP WINDOW $data(-parent)] \ + "bad window path name \"$data(-parent)\"" + } + + # Set -multiple to a one or zero value (not other boolean types like + # "yes") so we can use it in tests more easily. + if {$type eq "save"} { + set data(-multiple) 0 + } elseif {$data(-multiple)} { + set data(-multiple) 1 + } else { + set data(-multiple) 0 + } +} + +proc ::tk::dialog::file::Create {w class} { + set dataName [lindex [split $w .] end] + upvar ::tk::dialog::file::$dataName data + variable ::tk::Priv + global tk_library + + toplevel $w -class $class + if {[tk windowingsystem] eq "x11"} {wm attributes $w -type dialog} + pack [ttk::frame $w.contents] -expand 1 -fill both + #set w $w.contents + + # f1: the frame with the directory option menu + # + set f1 [ttk::frame $w.contents.f1] + bind [::tk::AmpWidget ttk::label $f1.lab -text [mc "&Directory:"]] \ + <<AltUnderlined>> [list focus $f1.menu] + + set data(dirMenuBtn) $f1.menu + if {![info exists data(selectPath)]} { + set data(selectPath) "" + } + set data(dirMenu) $f1.menu.menu + ttk::menubutton $f1.menu -menu $data(dirMenu) -direction flush \ + -textvariable [format %s(selectPath) ::tk::dialog::file::$dataName] + menu $data(dirMenu) -tearoff 0 + $data(dirMenu) add radiobutton -label "" -variable \ + [format %s(selectPath) ::tk::dialog::file::$dataName] + set data(upBtn) [ttk::button $f1.up] + $data(upBtn) configure -image $Priv(updirImage) + + $f1.menu configure -takefocus 1;# -highlightthickness 2 + + pack $data(upBtn) -side right -padx 4 -fill both + pack $f1.lab -side left -padx 4 -fill both + pack $f1.menu -expand yes -fill both -padx 4 + + # data(icons): the IconList that list the files and directories. + # + if {$class eq "TkFDialog"} { + if { $data(-multiple) } { + set fNameCaption [mc "File &names:"] + } else { + set fNameCaption [mc "File &name:"] + } + set fTypeCaption [mc "Files of &type:"] + set iconListCommand [list ::tk::dialog::file::OkCmd $w] + } else { + set fNameCaption [mc "&Selection:"] + set iconListCommand [list ::tk::dialog::file::chooseDir::DblClick $w] + } + set data(icons) [::tk::IconList $w.contents.icons \ + -command $iconListCommand -multiple $data(-multiple)] + bind $data(icons) <<ListboxSelect>> \ + [list ::tk::dialog::file::ListBrowse $w] + + # f2: the frame with the OK button, cancel button, "file name" field + # and file types field. + # + set f2 [ttk::frame $w.contents.f2] + bind [::tk::AmpWidget ttk::label $f2.lab -text $fNameCaption -anchor e]\ + <<AltUnderlined>> [list focus $f2.ent] + # -pady 0 + set data(ent) [ttk::entry $f2.ent] + + # The font to use for the icons. The default Canvas font on Unix is just + # deviant. + set ::tk::$w.contents.icons(font) [$data(ent) cget -font] + + # Make the file types bits only if this is a File Dialog + if {$class eq "TkFDialog"} { + set data(typeMenuLab) [::tk::AmpWidget ttk::label $f2.lab2 \ + -text $fTypeCaption -anchor e] + # -pady [$f2.lab cget -pady] + set data(typeMenuBtn) [ttk::menubutton $f2.menu \ + -menu $f2.menu.m] + # -indicatoron 1 + set data(typeMenu) [menu $data(typeMenuBtn).m -tearoff 0] + # $data(typeMenuBtn) configure -takefocus 1 -relief raised -anchor w + bind $data(typeMenuLab) <<AltUnderlined>> [list \ + focus $data(typeMenuBtn)] + } + + # The hidden button is displayed when ::tk::dialog::file::showHiddenBtn is + # true. Create it disabled so the binding doesn't trigger if it isn't + # shown. + if {$class eq "TkFDialog"} { + set text [mc "Show &Hidden Files and Directories"] + } else { + set text [mc "Show &Hidden Directories"] + } + set data(hiddenBtn) [::tk::AmpWidget ttk::checkbutton $f2.hidden \ + -text $text -state disabled \ + -variable ::tk::dialog::file::showHiddenVar \ + -command [list ::tk::dialog::file::UpdateWhenIdle $w]] +# -anchor w -padx 3 + + # the okBtn is created after the typeMenu so that the keyboard traversal + # is in the right order, and add binding so that we find out when the + # dialog is destroyed by the user (added here instead of to the overall + # window so no confusion about how much <Destroy> gets called; exactly + # once will do). [Bug 987169] + + set data(okBtn) [::tk::AmpWidget ttk::button $f2.ok \ + -text [mc "&OK"] -default active];# -pady 3] + bind $data(okBtn) <Destroy> [list ::tk::dialog::file::Destroyed $w] + set data(cancelBtn) [::tk::AmpWidget ttk::button $f2.cancel \ + -text [mc "&Cancel"] -default normal];# -pady 3] + + # grid the widgets in f2 + # + grid $f2.lab $f2.ent $data(okBtn) -padx 4 -pady 3 -sticky ew + grid configure $f2.ent -padx 2 + if {$class eq "TkFDialog"} { + grid $data(typeMenuLab) $data(typeMenuBtn) $data(cancelBtn) \ + -padx 4 -sticky ew + grid configure $data(typeMenuBtn) -padx 0 + grid $data(hiddenBtn) -columnspan 2 -padx 4 -sticky ew + } else { + grid $data(hiddenBtn) - $data(cancelBtn) -padx 4 -sticky ew + } + grid columnconfigure $f2 1 -weight 1 + + # Pack all the frames together. We are done with widget construction. + # + pack $f1 -side top -fill x -pady 4 + pack $f2 -side bottom -pady 4 -fill x + pack $data(icons) -expand yes -fill both -padx 4 -pady 1 + + # Set up the event handlers that are common to Directory and File Dialogs + # + + wm protocol $w WM_DELETE_WINDOW [list ::tk::dialog::file::CancelCmd $w] + $data(upBtn) configure -command [list ::tk::dialog::file::UpDirCmd $w] + $data(cancelBtn) configure -command [list ::tk::dialog::file::CancelCmd $w] + bind $w <KeyPress-Escape> [list $data(cancelBtn) invoke] + bind $w <Alt-Key> [list tk::AltKeyInDialog $w %A] + + # Set up event handlers specific to File or Directory Dialogs + # + if {$class eq "TkFDialog"} { + bind $data(ent) <Return> [list ::tk::dialog::file::ActivateEnt $w] + $data(okBtn) configure -command [list ::tk::dialog::file::OkCmd $w] + bind $w <Alt-t> [format { + if {[%s cget -state] eq "normal"} { + focus %s + } + } $data(typeMenuBtn) $data(typeMenuBtn)] + } else { + set okCmd [list ::tk::dialog::file::chooseDir::OkCmd $w] + bind $data(ent) <Return> $okCmd + $data(okBtn) configure -command $okCmd + bind $w <Alt-s> [list focus $data(ent)] + bind $w <Alt-o> [list $data(okBtn) invoke] + } + bind $w <Alt-h> [list $data(hiddenBtn) invoke] + bind $data(ent) <Tab> [list ::tk::dialog::file::CompleteEnt $w] + + # Build the focus group for all the entries + # + ::tk::FocusGroup_Create $w + ::tk::FocusGroup_BindIn $w $data(ent) [list \ + ::tk::dialog::file::EntFocusIn $w] + ::tk::FocusGroup_BindOut $w $data(ent) [list \ + ::tk::dialog::file::EntFocusOut $w] +} + +# ::tk::dialog::file::SetSelectMode -- +# +# Set the select mode of the dialog to single select or multi-select. +# +# Arguments: +# w The dialog path. +# multi 1 if the dialog is multi-select; 0 otherwise. +# +# Results: +# None. + +proc ::tk::dialog::file::SetSelectMode {w multi} { + set dataName __tk_filedialog + upvar ::tk::dialog::file::$dataName data + if { $multi } { + set fNameCaption [mc "File &names:"] + } else { + set fNameCaption [mc "File &name:"] + } + set iconListCommand [list ::tk::dialog::file::OkCmd $w] + ::tk::SetAmpText $w.contents.f2.lab $fNameCaption + $data(icons) configure -multiple $multi -command $iconListCommand + return +} + +# ::tk::dialog::file::UpdateWhenIdle -- +# +# Creates an idle event handler which updates the dialog in idle time. +# This is important because loading the directory may take a long time +# and we don't want to load the same directory for multiple times due to +# multiple concurrent events. +# +proc ::tk::dialog::file::UpdateWhenIdle {w} { + upvar ::tk::dialog::file::[winfo name $w] data + + if {[info exists data(updateId)]} { + return + } + set data(updateId) [after idle [list ::tk::dialog::file::Update $w]] +} + +# ::tk::dialog::file::Update -- +# +# Loads the files and directories into the IconList widget. Also sets up +# the directory option menu for quick access to parent directories. +# +proc ::tk::dialog::file::Update {w} { + # This proc may be called within an idle handler. Make sure that the + # window has not been destroyed before this proc is called + if {![winfo exists $w]} { + return + } + set class [winfo class $w] + if {($class ne "TkFDialog") && ($class ne "TkChooseDir")} { + return + } + + set dataName [winfo name $w] + upvar ::tk::dialog::file::$dataName data + variable ::tk::Priv + variable showHiddenVar + global tk_library + unset -nocomplain data(updateId) + + set folder $Priv(folderImage) + set file $Priv(fileImage) + + set appPWD [pwd] + if {[catch { + cd $data(selectPath) + }]} then { + # We cannot change directory to $data(selectPath). $data(selectPath) + # should have been checked before ::tk::dialog::file::Update is + # called, so we normally won't come to here. Anyways, give an error + # and abort action. + tk_messageBox -type ok -parent $w -icon warning -message [mc \ + "Cannot change to the directory \"%1\$s\".\nPermission denied."\ + $data(selectPath)] + cd $appPWD + return + } + + # Turn on the busy cursor. BUG?? We haven't disabled X events, though, + # so the user may still click and cause havoc ... + # + set entCursor [$data(ent) cget -cursor] + set dlgCursor [$w cget -cursor] + $data(ent) configure -cursor watch + $w configure -cursor watch + update idletasks + + $data(icons) deleteall + + set showHidden $showHiddenVar + + # Make the dir list. Note that using an explicit [pwd] (instead of '.') is + # better in some VFS cases. + $data(icons) add $folder [GlobFiltered [pwd] d 1] + + if {$class eq "TkFDialog"} { + # Make the file list if this is a File Dialog, selecting all but + # 'd'irectory type files. + # + $data(icons) add $file [GlobFiltered [pwd] {f b c l p s}] + } + + # Update the Directory: option menu + # + set list "" + set dir "" + foreach subdir [file split $data(selectPath)] { + set dir [file join $dir $subdir] + lappend list $dir + } + + $data(dirMenu) delete 0 end + set var [format %s(selectPath) ::tk::dialog::file::$dataName] + foreach path $list { + $data(dirMenu) add command -label $path -command [list set $var $path] + } + + # Restore the PWD to the application's PWD + # + cd $appPWD + + if {$class eq "TkFDialog"} { + # Restore the Open/Save Button if this is a File Dialog + # + if {$data(type) eq "open"} { + ::tk::SetAmpText $data(okBtn) [mc "&Open"] + } else { + ::tk::SetAmpText $data(okBtn) [mc "&Save"] + } + } + + # turn off the busy cursor. + # + $data(ent) configure -cursor $entCursor + $w configure -cursor $dlgCursor +} + +# ::tk::dialog::file::SetPathSilently -- +# +# Sets data(selectPath) without invoking the trace procedure +# +proc ::tk::dialog::file::SetPathSilently {w path} { + upvar ::tk::dialog::file::[winfo name $w] data + + set cb [list ::tk::dialog::file::SetPath $w] + trace remove variable data(selectPath) write $cb + set data(selectPath) $path + trace add variable data(selectPath) write $cb +} + + +# This proc gets called whenever data(selectPath) is set +# +proc ::tk::dialog::file::SetPath {w name1 name2 op} { + if {[winfo exists $w]} { + upvar ::tk::dialog::file::[winfo name $w] data + UpdateWhenIdle $w + # On directory dialogs, we keep the entry in sync with the currentdir. + if {[winfo class $w] eq "TkChooseDir"} { + $data(ent) delete 0 end + $data(ent) insert end $data(selectPath) + } + } +} + +# This proc gets called whenever data(filter) is set +# +proc ::tk::dialog::file::SetFilter {w type} { + upvar ::tk::dialog::file::[winfo name $w] data + + set data(filterType) $type + set data(filter) [lindex $type 1] + $data(typeMenuBtn) configure -text [lindex $type 0] ;#-indicatoron 1 + + # If we aren't using a default extension, use the one suppled by the + # filter. + if {![info exists data(extUsed)]} { + if {[string length $data(-defaultextension)]} { + set data(extUsed) 1 + } else { + set data(extUsed) 0 + } + } + + if {!$data(extUsed)} { + # Get the first extension in the list that matches {^\*\.\w+$} and + # remove all * from the filter. + set index [lsearch -regexp $data(filter) {^\*\.\w+$}] + if {$index >= 0} { + set data(-defaultextension) \ + [string trimleft [lindex $data(filter) $index] "*"] + } else { + # Couldn't find anything! Reset to a safe default... + set data(-defaultextension) "" + } + } + + $data(icons) see 0 + + UpdateWhenIdle $w +} + +# tk::dialog::file::ResolveFile -- +# +# Interpret the user's text input in a file selection dialog. Performs: +# +# (1) ~ substitution +# (2) resolve all instances of . and .. +# (3) check for non-existent files/directories +# (4) check for chdir permissions +# (5) conversion of environment variable references to their +# contents (once only) +# +# Arguments: +# context: the current directory you are in +# text: the text entered by the user +# defaultext: the default extension to add to files with no extension +# expandEnv: whether to expand environment variables (yes by default) +# +# Return vaue: +# [list $flag $directory $file] +# +# flag = OK : valid input +# = PATTERN : valid directory/pattern +# = PATH : the directory does not exist +# = FILE : the directory exists by the file doesn't exist +# = CHDIR : Cannot change to the directory +# = ERROR : Invalid entry +# +# directory : valid only if flag = OK or PATTERN or FILE +# file : valid only if flag = OK or PATTERN +# +# directory may not be the same as context, because text may contain a +# subdirectory name +# +proc ::tk::dialog::file::ResolveFile {context text defaultext {expandEnv 1}} { + set appPWD [pwd] + + set path [JoinFile $context $text] + + # If the file has no extension, append the default. Be careful not to do + # this for directories, otherwise typing a dirname in the box will give + # back "dirname.extension" instead of trying to change dir. + if { + ![file isdirectory $path] && ([file ext $path] eq "") && + ![string match {$*} [file tail $path]] + } then { + set path "$path$defaultext" + } + + if {[catch {file exists $path}]} { + # This "if" block can be safely removed if the following code stop + # generating errors. + # + # file exists ~nonsuchuser + # + return [list ERROR $path ""] + } + + if {[file exists $path]} { + if {[file isdirectory $path]} { + if {[catch {cd $path}]} { + return [list CHDIR $path ""] + } + set directory [pwd] + set file "" + set flag OK + cd $appPWD + } else { + if {[catch {cd [file dirname $path]}]} { + return [list CHDIR [file dirname $path] ""] + } + set directory [pwd] + set file [file tail $path] + set flag OK + cd $appPWD + } + } else { + set dirname [file dirname $path] + if {[file exists $dirname]} { + if {[catch {cd $dirname}]} { + return [list CHDIR $dirname ""] + } + set directory [pwd] + cd $appPWD + set file [file tail $path] + # It's nothing else, so check to see if it is an env-reference + if {$expandEnv && [string match {$*} $file]} { + set var [string range $file 1 end] + if {[info exist ::env($var)]} { + return [ResolveFile $context $::env($var) $defaultext 0] + } + } + if {[regexp {[*?]} $file]} { + set flag PATTERN + } else { + set flag FILE + } + } else { + set directory $dirname + set file [file tail $path] + set flag PATH + # It's nothing else, so check to see if it is an env-reference + if {$expandEnv && [string match {$*} $file]} { + set var [string range $file 1 end] + if {[info exist ::env($var)]} { + return [ResolveFile $context $::env($var) $defaultext 0] + } + } + } + } + + return [list $flag $directory $file] +} + + +# Gets called when the entry box gets keyboard focus. We clear the selection +# from the icon list . This way the user can be certain that the input in the +# entry box is the selection. +# +proc ::tk::dialog::file::EntFocusIn {w} { + upvar ::tk::dialog::file::[winfo name $w] data + + if {[$data(ent) get] ne ""} { + $data(ent) selection range 0 end + $data(ent) icursor end + } else { + $data(ent) selection clear + } + + if {[winfo class $w] eq "TkFDialog"} { + # If this is a File Dialog, make sure the buttons are labeled right. + if {$data(type) eq "open"} { + ::tk::SetAmpText $data(okBtn) [mc "&Open"] + } else { + ::tk::SetAmpText $data(okBtn) [mc "&Save"] + } + } +} + +proc ::tk::dialog::file::EntFocusOut {w} { + upvar ::tk::dialog::file::[winfo name $w] data + + $data(ent) selection clear +} + + +# Gets called when user presses Return in the "File name" entry. +# +proc ::tk::dialog::file::ActivateEnt {w} { + upvar ::tk::dialog::file::[winfo name $w] data + + set text [$data(ent) get] + if {$data(-multiple)} { + foreach t $text { + VerifyFileName $w $t + } + } else { + VerifyFileName $w $text + } +} + +# Verification procedure +# +proc ::tk::dialog::file::VerifyFileName {w filename} { + upvar ::tk::dialog::file::[winfo name $w] data + + set list [ResolveFile $data(selectPath) $filename $data(-defaultextension)] + foreach {flag path file} $list { + break + } + + switch -- $flag { + OK { + if {$file eq ""} { + # user has entered an existing (sub)directory + set data(selectPath) $path + $data(ent) delete 0 end + } else { + SetPathSilently $w $path + if {$data(-multiple)} { + lappend data(selectFile) $file + } else { + set data(selectFile) $file + } + Done $w + } + } + PATTERN { + set data(selectPath) $path + set data(filter) $file + } + FILE { + if {$data(type) eq "open"} { + tk_messageBox -icon warning -type ok -parent $w \ + -message [mc "File \"%1\$s\" does not exist." \ + [file join $path $file]] + $data(ent) selection range 0 end + $data(ent) icursor end + } else { + SetPathSilently $w $path + if {$data(-multiple)} { + lappend data(selectFile) $file + } else { + set data(selectFile) $file + } + Done $w + } + } + PATH { + tk_messageBox -icon warning -type ok -parent $w \ + -message [mc "Directory \"%1\$s\" does not exist." $path] + $data(ent) selection range 0 end + $data(ent) icursor end + } + CHDIR { + tk_messageBox -type ok -parent $w -icon warning -message \ + [mc "Cannot change to the directory\ + \"%1\$s\".\nPermission denied." $path] + $data(ent) selection range 0 end + $data(ent) icursor end + } + ERROR { + tk_messageBox -type ok -parent $w -icon warning -message \ + [mc "Invalid file name \"%1\$s\"." $path] + $data(ent) selection range 0 end + $data(ent) icursor end + } + } +} + +# Gets called when user presses the Alt-s or Alt-o keys. +# +proc ::tk::dialog::file::InvokeBtn {w key} { + upvar ::tk::dialog::file::[winfo name $w] data + + if {[$data(okBtn) cget -text] eq $key} { + $data(okBtn) invoke + } +} + +# Gets called when user presses the "parent directory" button +# +proc ::tk::dialog::file::UpDirCmd {w} { + upvar ::tk::dialog::file::[winfo name $w] data + + if {$data(selectPath) ne "/"} { + set data(selectPath) [file dirname $data(selectPath)] + } +} + +# Join a file name to a path name. The "file join" command will break if the +# filename begins with ~ +# +proc ::tk::dialog::file::JoinFile {path file} { + if {[string match {~*} $file] && [file exists $path/$file]} { + return [file join $path ./$file] + } else { + return [file join $path $file] + } +} + +# Gets called when user presses the "OK" button +# +proc ::tk::dialog::file::OkCmd {w} { + upvar ::tk::dialog::file::[winfo name $w] data + + set filenames {} + foreach item [$data(icons) selection get] { + lappend filenames [$data(icons) get $item] + } + + if { + ([llength $filenames] && !$data(-multiple)) || + ($data(-multiple) && ([llength $filenames] == 1)) + } then { + set filename [lindex $filenames 0] + set file [JoinFile $data(selectPath) $filename] + if {[file isdirectory $file]} { + ListInvoke $w [list $filename] + return + } + } + + ActivateEnt $w +} + +# Gets called when user presses the "Cancel" button +# +proc ::tk::dialog::file::CancelCmd {w} { + upvar ::tk::dialog::file::[winfo name $w] data + variable ::tk::Priv + + bind $data(okBtn) <Destroy> {} + set Priv(selectFilePath) "" +} + +# Gets called when user destroys the dialog directly [Bug 987169] +# +proc ::tk::dialog::file::Destroyed {w} { + upvar ::tk::dialog::file::[winfo name $w] data + variable ::tk::Priv + + set Priv(selectFilePath) "" +} + +# Gets called when user browses the IconList widget (dragging mouse, arrow +# keys, etc) +# +proc ::tk::dialog::file::ListBrowse {w} { + upvar ::tk::dialog::file::[winfo name $w] data + + set text {} + foreach item [$data(icons) selection get] { + lappend text [$data(icons) get $item] + } + if {[llength $text] == 0} { + return + } + if {$data(-multiple)} { + set newtext {} + foreach file $text { + set fullfile [JoinFile $data(selectPath) $file] + if { ![file isdirectory $fullfile] } { + lappend newtext $file + } + } + set text $newtext + set isDir 0 + } else { + set text [lindex $text 0] + set file [JoinFile $data(selectPath) $text] + set isDir [file isdirectory $file] + } + if {!$isDir} { + $data(ent) delete 0 end + $data(ent) insert 0 $text + + if {[winfo class $w] eq "TkFDialog"} { + if {$data(type) eq "open"} { + ::tk::SetAmpText $data(okBtn) [mc "&Open"] + } else { + ::tk::SetAmpText $data(okBtn) [mc "&Save"] + } + } + } elseif {[winfo class $w] eq "TkFDialog"} { + ::tk::SetAmpText $data(okBtn) [mc "&Open"] + } +} + +# Gets called when user invokes the IconList widget (double-click, Return key, +# etc) +# +proc ::tk::dialog::file::ListInvoke {w filenames} { + upvar ::tk::dialog::file::[winfo name $w] data + + if {[llength $filenames] == 0} { + return + } + + set file [JoinFile $data(selectPath) [lindex $filenames 0]] + + set class [winfo class $w] + if {$class eq "TkChooseDir" || [file isdirectory $file]} { + set appPWD [pwd] + if {[catch {cd $file}]} { + tk_messageBox -type ok -parent $w -icon warning -message \ + [mc "Cannot change to the directory \"%1\$s\".\nPermission denied." $file] + } else { + cd $appPWD + set data(selectPath) $file + } + } else { + if {$data(-multiple)} { + set data(selectFile) $filenames + } else { + set data(selectFile) $file + } + Done $w + } +} + +# ::tk::dialog::file::Done -- +# +# Gets called when user has input a valid filename. Pops up a dialog +# box to confirm selection when necessary. Sets the +# tk::Priv(selectFilePath) variable, which will break the "vwait" loop +# in ::tk::dialog::file:: and return the selected filename to the script +# that calls tk_getOpenFile or tk_getSaveFile +# +proc ::tk::dialog::file::Done {w {selectFilePath ""}} { + upvar ::tk::dialog::file::[winfo name $w] data + variable ::tk::Priv + + if {$selectFilePath eq ""} { + if {$data(-multiple)} { + set selectFilePath {} + foreach f $data(selectFile) { + lappend selectFilePath [JoinFile $data(selectPath) $f] + } + } else { + set selectFilePath [JoinFile $data(selectPath) $data(selectFile)] + } + + set Priv(selectFile) $data(selectFile) + set Priv(selectPath) $data(selectPath) + + if {($data(type) eq "save") && $data(-confirmoverwrite) && [file exists $selectFilePath]} { + set reply [tk_messageBox -icon warning -type yesno -parent $w \ + -message [mc "File \"%1\$s\" already exists.\nDo you want\ + to overwrite it?" $selectFilePath]] + if {$reply eq "no"} { + return + } + } + if { + [info exists data(-typevariable)] && $data(-typevariable) ne "" + && [info exists data(-filetypes)] && [llength $data(-filetypes)] + && [info exists data(filterType)] && $data(filterType) ne "" + } then { + upvar #0 $data(-typevariable) typeVariable + set typeVariable [lindex $data(origfiletypes) \ + [lsearch -exact $data(-filetypes) $data(filterType)] 0] + + } + } + bind $data(okBtn) <Destroy> {} + set Priv(selectFilePath) $selectFilePath +} + +# ::tk::dialog::file::GlobFiltered -- +# +# Gets called to do globbing, returning the results and filtering them +# according to the current filter (and removing the entries for '.' and +# '..' which are never shown). Deals with evil cases such as where the +# user is supplying a filter which is an invalid list or where it has an +# unbalanced brace. The resulting list will be dictionary sorted. +# +# Arguments: +# dir Which directory to search +# type List of filetypes to look for ('d' or 'f b c l p s') +# overrideFilter Whether to ignore the filter for this search. +# +# NB: Assumes that the caller has mapped the state variable to 'data'. +# +proc ::tk::dialog::file::GlobFiltered {dir type {overrideFilter 0}} { + variable showHiddenVar + upvar 1 data(filter) filter + + if {$filter eq "*" || $overrideFilter} { + set patterns [list *] + if {$showHiddenVar} { + lappend patterns .* + } + } elseif {[string is list $filter]} { + set patterns $filter + } else { + # Invalid list; assume we can use non-whitespace sequences as words + set patterns [regexp -inline -all {\S+} $filter] + } + + set opts [list -tails -directory $dir -type $type -nocomplain] + + set result {} + catch { + # We have a catch because we might have a really bad pattern (e.g., + # with an unbalanced brace); even [glob -nocomplain] doesn't like it. + # Using a catch ensures that it just means we match nothing instead of + # throwing a nasty error at the user... + foreach f [glob {*}$opts -- {*}$patterns] { + if {$f eq "." || $f eq ".."} { + continue + } + # See ticket [1641721], $f might be a link pointing to a dir + if {$type != "d" && [file isdir [file join $dir $f]]} { + continue + } + lappend result $f + } + } + return [lsort -dictionary -unique $result] +} + +proc ::tk::dialog::file::CompleteEnt {w} { + upvar ::tk::dialog::file::[winfo name $w] data + set f [$data(ent) get] + if {$data(-multiple)} { + if {![string is list $f] || [llength $f] != 1} { + return -code break + } + set f [lindex $f 0] + } + + # Get list of matching filenames and dirnames + set files [if {[winfo class $w] eq "TkFDialog"} { + GlobFiltered $data(selectPath) {f b c l p s} + }] + set dirs2 {} + foreach d [GlobFiltered $data(selectPath) d] {lappend dirs2 $d/} + + set targets [concat \ + [lsearch -glob -all -inline $files $f*] \ + [lsearch -glob -all -inline $dirs2 $f*]] + + if {[llength $targets] == 1} { + # We have a winner! + set f [lindex $targets 0] + } elseif {$f in $targets || [llength $targets] == 0} { + if {[string length $f] > 0} { + bell + } + return + } elseif {[llength $targets] > 1} { + # Multiple possibles + if {[string length $f] == 0} { + return + } + set t0 [lindex $targets 0] + for {set len [string length $t0]} {$len>0} {} { + set allmatch 1 + foreach s $targets { + if {![string equal -length $len $s $t0]} { + set allmatch 0 + break + } + } + incr len -1 + if {$allmatch} break + } + set f [string range $t0 0 $len] + } + + if {$data(-multiple)} { + set f [list $f] + } + $data(ent) delete 0 end + $data(ent) insert 0 $f + return -code break +} |