JOB Purpose: This supplement package for Tcl/Tk implements a drag&drop enabled file or directory selection dialog (GUI).
D&D is optional, as it is not supported on all platforms (e.g. for undroidwish/Androidwish,...).
Usage:
Features:
The package has (hopefully) reached a mature state. Nevertheless, anyone is invited to share ideas for some more improvements whatsoever.
Source code:
# ------------------------------------------------------------------------ # getfileordirectory.tcl --- # ------------------------------------------------------------------------ # (c) 2017, Johann Oberdorfer - Engineering Support | CAD | Software # johann.oberdorfer [at] gmail.com # www.johann-oberdorfer.eu # ------------------------------------------------------------------------ # This source file is distributed under the BSD license. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # See the BSD License for more details. # ------------------------------------------------------------------------ # Purpose: # This source implements a drag&drop enabled file or directory # selection dialog. # ------------------------------------------------------------------------ # 17-04-12: D&D is now optional as it is not supported on all platforms # change basicly was made to allow execution on # undroidwish/Androidwish # thank's to Christian Werner for his request package provide getfileordirectory 0.1 namespace eval getfileordirectory { variable widgetDefaults variable widgetImages variable widgetVars array set widgetDefaults { title "Drag&Drop file or directory:" \ initialdir "" selectmode "directory" mostrecentfiles "" } set widgetDefaults(filetypes) { {{Html Files} {.html .htm}} {{Text-Files} {*.txt* .text}} {{All Files} *} } array set widgetVars { dnd_isavailable 0 is_ok 0 dragndrop_dir "" select_mode 1 wlistbox "" } # initializing required images... set this_dir [file dirname [info script]] set image_dir [file join $this_dir "images"] set image_file [file join $this_dir "ImageLib.tcl"] proc LoadImages {image_dir {patterns {*.gif}}} { foreach p $patterns { foreach file [glob -nocomplain -directory $image_dir $p] { set img [file tail [file rootname $file]] if { ![info exists images($img)] } { set images($img) [image create photo -file $file] } }} return [array get images] } if { [file exists $image_file] } { source $image_file array set widgetImages [array get images] } else { array set widgetImages [LoadImages \ [file join $image_dir] {"*.gif" "*.png"}] } # customized styles... # myToggle - checkbutton with style ... ttk::style element create myToggle.Checkbutton.indicator \ image [list \ $widgetImages(checkbox-off) \ {disabled selected} $widgetImages(checkbox-off) \ {selected} $widgetImages(checkbox-on) \ {disabled} $widgetImages(checkbox-off) \ ] ttk::style layout myToggle.TCheckbutton [list \ Checkbutton.padding -sticky nswe -children [list \ myToggle.Checkbutton.indicator \ -side left -sticky {} \ Checkbutton.focus -side left -sticky w -children { \ Checkbutton.label -sticky nswe \ } \ ] \ ] ttk::style map myToggle.TCheckbutton \ -background [list active \ [ttk::style lookup myToggle.TCheckbutton -background]] # D&D support is optional # note: for Androwish/undroidwish tkdnd is not supported! if {[catch {package require tkdnd}] == 0 } { set widgetVars(dnd_isavailable) 1 } } proc getfileordirectory::OKButtonCmd {} { variable widgetVars set widgetVars(is_ok) 1 } proc getfileordirectory::EntryBindingsCmd {} { variable widgetVars set state disabled set fname [$widgetVars(input_entry) get] if { $fname != "" && ([file isfile $fname] || [file isdirectory $fname]) } { set state normal } foreach w [list $widgetVars(ok_button) $widgetVars(input_image)] { $w configure -state $state } } proc getfileordirectory::CommandOnDrop {drop_argument} { variable widgetVars variable widgetDefaults set is_valid 0 if { [winfo exists $widgetVars(wlistbox)] } { $widgetVars(wlistbox) selection clear 0 end } switch -- $widgetDefaults(selectmode) { "file" { if { [file isdirectory $drop_argument] } { # if a directory has been choosen, # just switch to "directory" mode... set widgetDefaults(selectmode) "directory" set widgetVars(select_mode) 1 RadioButtonCmd set is_valid 1 } if { [file isfile $drop_argument] } { set is_valid 1 } } "directory" { if { [file isfile $drop_argument] } { # if a file name has been choosen # just switch to "file" mode... set widgetDefaults(selectmode) "file" set widgetVars(select_mode) 0 RadioButtonCmd set is_valid 1 } if { [file isdirectory $drop_argument] } { set is_valid 1 } } default { return } } if { $is_valid == 0 } { set widgetVars(dragndrop_dir) "" } else { set widgetVars(dragndrop_dir) $drop_argument } EntryBindingsCmd } proc getfileordirectory::SelectFileCmd {} { variable widgetDefaults variable widgetImages variable widgetVars if { [winfo exists $widgetVars(wlistbox)] } { $widgetVars(wlistbox) selection clear 0 end } switch -- $widgetVars(select_mode) { 0 { # select file ... set fname [tk_getOpenFile \ -parent $widgetVars(this) \ -title "Select File:" \ -initialdir $widgetDefaults(initialdir) \ -filetypes $widgetDefaults(filetypes) \ ] } 1 { # select directory... set fname [tk_chooseDirectory \ -parent $widgetVars(this) \ -title "Select Directory:" \ -initialdir $widgetDefaults(initialdir) \ -mustexist 1 \ ] } default { return } } if {$fname != ""} { set widgetDefaults(initialdir) [file dirname $fname] set widgetVars(dragndrop_dir) $fname EntryBindingsCmd } } proc getfileordirectory::CancelCmd {} { variable widgetVars set widgetVars(is_ok) 2 } proc getfileordirectory::RadioButtonCmd {} { variable widgetVars if { [winfo exists $widgetVars(wlistbox)] } { $widgetVars(wlistbox) selection clear 0 end } switch -- $widgetVars(select_mode) { 0 { set txt "Select File ... " } 1 { set txt "Select Directory..." } default { return } } $widgetVars(selfile_bttn) configure -text $txt # clean previous selection: set widgetVars(dragndrop_dir) "" EntryBindingsCmd } proc getfileordirectory::ListboxSelectCmd {wlistbox} { variable widgetDefaults # this works for single selection mode: set idx [$wlistbox curselection] set fname [lindex $widgetDefaults(mostrecentfiles) $idx] # make sure, the file or directory still exists, # (just to be sure, file validation should have been done # in the caller program already) # if { ![file exists $fname] } { return } # we can use the D&D command to trigger entry & button state, etc... CommandOnDrop $fname } proc getfileordirectory::DoubleClickCmd {wlistbox} { variable widgetDefaults variable widgetVars # as we have cleared the current selection previously, # the curselection index returns an empty string # set idx [$wlistbox curselection] # set fname [lindex $widgetDefaults(mostrecentfiles) $idx] # this is why we retrieve the current fname from the entry widget: set fname $widgetVars(dragndrop_dir) # make sure, the file or directory still exists, # (just to be sure, file validation should have been done # in the caller program already) # if { ![file exists $fname] } { return } OKButtonCmd } # ------------------------------------------------------------------------- # gui declaration # ------------------------------------------------------------------------- proc getfileordirectory::getfileordirectory {args} { variable widgetDefaults variable widgetImages variable widgetVars set wparent "" set ind 0 while { $ind < [llength $args] } { switch -exact -- [lindex $args $ind] { "-parent" { incr ind set wparent [lindex $args $ind] incr ind } "-title" { incr ind set widgetDefaults(title) [lindex $args $ind] incr ind } "-initialdir" { incr ind set widgetDefaults(initialdir) [lindex $args $ind] incr ind } "-filetypes" { incr ind set widgetDefaults(filetypes) [lindex $args $ind] incr ind } "-selectmode" { incr ind set widgetDefaults(selectmode) [lindex $args $ind] # map radio button flag... switch -exact $widgetDefaults(selectmode) { "file" { set widgetVars(select_mode) 0 } "directory" { set widgetVars(select_mode) 1 } } incr ind } "-mostrecentfiles" { incr ind set widgetDefaults(mostrecentfiles) [lindex $args $ind] incr ind } default { puts "unknown option [lindex $args $ind]" return "" } } } set w $wparent.getfilename set widgetVars(this) $w catch {destroy $w} if {$widgetDefaults(mostrecentfiles) == ""} { set geometry "800x250+350+350" } else { set geometry "800x400+350+350" } toplevel $w wm title $w "File Selection Dialog" wm geometry $w $geometry wm transient $w $wparent bind $w <KeyPress-Escape> "[namespace current]::CancelCmd" set fmain [ttk::frame $w.main -relief groove] pack $fmain -side bottom -fill x ttk::button $fmain.chk \ -text "Continue" \ -compound left \ -command "[namespace current]::OKButtonCmd" \ -state disabled set widgetVars(ok_button) $fmain.chk ttk::button $fmain.cancel \ -text "Cancel" \ -image $widgetImages(dialog-close) \ -compound left \ -command "[namespace current]::CancelCmd" \ pack $fmain.chk $fmain.cancel -side left -expand true -padx 4 -pady 4 # drag'n drop area here ("#DFECFF"): # ---------------------------------- set bgcolor "white" ttk::style configure DnD.TLabel \ -background $bgcolor \ -foreground "DarkGrey" ttk::style configure DnD.TFrame \ -background $bgcolor \ -bd 2 set fmode [ttk::labelframe $w.toc -text " Selection Mode: "] pack $fmode -padx 5 -pady 2 -side top -fill x # ttk::label $fmode.lbl -text "Selection Mode:" ttk::radiobutton $fmode.b1 \ -text "File" \ -variable "[namespace current]::widgetVars(select_mode)" \ -value 0 \ -style myToggle.TCheckbutton \ -command "[namespace current]::RadioButtonCmd" ttk::radiobutton $fmode.b2 \ -text "Directory" \ -variable "[namespace current]::widgetVars(select_mode)" \ -value 1 \ -style myToggle.TCheckbutton \ -command "[namespace current]::RadioButtonCmd" pack $fmode.b1 $fmode.b2 -padx 5 -pady 5 -side left if { $widgetVars(dnd_isavailable) == 0} { set dnd_info_txt "Drag&Drop not supported on this OS. " } else { set dnd_info_txt "Use Drag&Drop to select a file or directory." } ttk::label $fmode.info -text $dnd_info_txt pack $fmode.info -side right -padx 5 set f [::rframe::rframe $w.dnd $bgcolor] pack $f -padx 4 -pady 4 -side top -fill both -expand true if {$::tcl_platform(platform) == "windows"} { ttk::label $f.logo \ -text $widgetDefaults(title) \ -image $widgetImages(system-log-out-3) \ -compound top \ -style DnD.TLabel pack $f.logo -side top -padx 5 -pady 4 -anchor center ttk::label $f.lbl \ -image $widgetImages(folder) \ -style DnD.TLabel \ -state disabled } else { # don't know, how to style this for OSX (?)... # need to figure out this issue later ... label $f.logo \ -text $widgetDefaults(title) \ -image $widgetImages(system-log-out-3) \ -compound top \ -foreground "DarkGrey" \ -background $bgcolor pack $f.logo -side top -padx 5 -pady 4 -anchor center label $f.lbl \ -image $widgetImages(folder) \ -background $bgcolor \ -state disabled } set widgetVars(input_image) $f.lbl ttk::entry $f.entry \ -width 40 \ -textvariable "[namespace current]::widgetVars(dragndrop_dir)" set widgetVars(input_entry) $f.entry ttk::button $f.selfile \ -text "..." \ -command "[namespace current]::SelectFileCmd" set widgetVars(selfile_bttn) $f.selfile # triggers the button text configuration RadioButtonCmd # slightly different "decoration" when DnD is available if {$widgetVars(dnd_isavailable) == 1} { $f.selfile configure -style Toolbutton } pack $f.lbl -side left -padx 5 -pady 5 pack $f.entry -side left -padx 5 -pady 5 -fill x -expand true pack $f.selfile -side right bind $widgetVars(input_entry) <Leave> "[namespace current]::EntryBindingsCmd" bind $widgetVars(input_entry) <Return> "[namespace current]::EntryBindingsCmd" # most recent files or directories ... if { $widgetDefaults(mostrecentfiles) != "" } { set bg ttk::style set flb [ ttk::labelframe $w.lb \ -text " Most recently used files or directories: " \ -height [expr {[llength $widgetDefaults(mostrecentfiles)] +1}] ] pack $flb -padx 4 -pady 4 -side bottom -fill x set lb $flb.lb ttk::scrollbar $flb.y -orient vertical -command [list $lb yview] listbox $lb \ -highlightthickness 0 \ -relief flat \ -selectmode "browse" \ -fg [ttk::style configure . -foreground] \ -bg [ttk::style configure . -background] \ -yscrollcommand [list $flb.y set] pack $lb -side left -padx 4 -pady 4 -fill x -expand true pack $flb.y -side left -fill y # fill listbox with values... foreach fname $widgetDefaults(mostrecentfiles) { $lb insert end $fname } # listbox callback ... bind $lb <<ListboxSelect>> "[namespace current]::ListboxSelectCmd %W" bind $lb <Double-Button-1> "[namespace current]::OKButtonCmd" set widgetVars(wlistbox) $lb } # optional tk dnd support # ----------------------- if {$widgetVars(dnd_isavailable) == 1} { foreach w [list $w.dnd $f.entry] { tkdnd::drop_target register $w "*" bind $w <<Drop>> "[namespace current]::CommandOnDrop %D" } } # wait user grab $widgetVars(this) tkwait variable "[namespace current]::widgetVars(is_ok)" grab release $widgetVars(this) if { $widgetVars(is_ok) == 1 } { set retval [$widgetVars(input_entry) get] } else { set retval "" } destroy $widgetVars(this) return $retval }
# ------------------------------------------------------------------------ # getfileordirectory_test.tcl --- # ------------------------------------------------------------------------ set dir [file dirname [info script]] set auto_path [linsert $auto_path 0 [file join $dir "."]] set auto_path [linsert $auto_path 0 [file join $dir ".."]] set auto_path [linsert $auto_path 0 [file join $dir "../../00-lib3"]] set auto_path [linsert $auto_path 0 [file join $dir "../../00-libbin"]] # test-run ... package require Tk package require tile # D&D support is optional # note: for Androwish/undroidwish tkdnd is not supported! catch { package require tkdnd } package require rframe package require getfileordirectory 0.1 # question: # how to determine Androidwish / undroidwish ? # set dnd_available 0 # # if { [tk windowingsystem] == "x11" && $tcl_platform(platform) == "windows" } { # # most likely, we are on "undroidwish" # # where there is no tkdnd available ... # } else { # if {[catch {package require tkdnd}] == 0 } { # set dnd_available 1 # } # } wm withdraw . if { $::tcl_platform(platform) == "windows"} { console show console eval {wm protocol . WM_DELETE_WINDOW {exit 0}} } set initialdir $dir set filetypes { {{Html Files} {.html .htm}} {{All Files} *} } set most_recentfiles_test { "R:/Projekte_SoftwareEntwicklung/03_HelpViewer_HTMLDocu" "C:/CodeByHans/Docu" "C:/CodeByHans/Docu/tkhtml3.0/tkhtml (n).html" "C:/CodeByHans/Docu/tablelist5.15/index.html" "C:/CodeByHans/Docu/BWidget1.9.10" } # use cases: # in fact, when D&D is available the file and directory modes # change by themselves based on what type is D&D'ed onto the dialog, # just give it a try, then the explanation 'll be clear... # ... this also means, setting the mode is only required, # if there is no D&D available ... set mode 1 switch -- $mode { 0 { set rval [getfileordirectory::getfileordirectory \ -parent "" \ -title "Select File:" \ -initialdir $initialdir \ -filetypes $filetypes \ -selectmode "file" \ ] } 1 - default { # default -selectmode is: "directory" set rval [getfileordirectory::getfileordirectory \ -parent "" \ -title "Select File or Directory:" \ -initialdir $initialdir \ -mostrecentfiles $most_recentfiles_test \ ] } } puts "Selection return value is: \"$rval\""
# Tcl package index file package ifneeded getfileordirectory 0.1 \ [list source [file join $dir getfileordirectory.tcl]]
# ImageLib.tcl --- # Automatically created by: CreateImageLib.tcl set images(folder) [image create photo -data { iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QAAAAAAAD5Q7t/AAAACXBI WXMAAA3XAAAN1wFCKJt4AAAACXZwQWcAAAAQAAAAEABcxq3DAAABwElEQVQ4y6WSMWuTURSGn3Pv Vywp4uAQuhQcnDJm6y6dHILQ1V+gu9A/4OJWV/+HKMS5YMRZKYokklBpv6Yl373nnuOQJiRQUsF3 Odx7z3k478uVg4MD/kdVt9t95+5Pb3m7dvdng8HgZCPAzHq9Xu+BmWFmuDsA5+fnD/v9/nGn03m5 aM45E0Iow+Hwc13XBaDKObuqMplMKKUsISEEdnd3O8D7BRRgOp1WpZQ3dV0frQAyqoqZrUHa7XZr e3ubVqtFjJEQAqPRiPF4/HhpQVVNtXD8KXHV+Io7AcCZAbPlrXsA9g/Z2z+MMfyocs6eUmLaOK9f PEHkpnE1KV87AZDUOXr7ca9KKZmqAk4M8OX0YnWBRVlPPgqP2jsg7pWqknNGRIhBiPG2kXVlNdwc QbzKOVvOGdwJImxtAGhxmmwUc0IQgDlAdb7BLBUCYD5vLu6YOeZO1nldWGiSLQGuWhCpmNQNwz8z GrXN3zcKk4vZHKCqllICj3wbXXI2bXA2Kwbh98WNBRExM0NEaLLRulfdGaKIcJ0UoFTuzumZIQJf v4/uHF7o6mqGwEllWzv9D6f3n4Pz6+f4nwEijEXCq79HFiGeCw9wuAAAACV0RVh0Y3JlYXRlLWRh dGUAMjAwOS0xMS0xMFQxOTozODoxNi0wNzowMG0ivygAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTAt MDItMjBUMjM6MjQ6MzQtMDc6MDDmnIB1AAAAJXRFWHRkYXRlOm1vZGlmeQAyMDEwLTAxLTExVDA4 OjU2OjE4LTA3OjAwZ4mnzAAAADJ0RVh0TGljZW5zZQBodHRwOi8vZW4ud2lraXBlZGlhLm9yZy93 aWtpL1B1YmxpY19kb21haW4//erPAAAAJXRFWHRtb2RpZnktZGF0ZQAyMDA5LTExLTEwVDE5OjM4 OjE2LTA3OjAwMpPJHAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAAZdEVY dFNvdXJjZQBUYW5nbyBJY29uIExpYnJhcnlUz+2CAAAAOnRFWHRTb3VyY2VfVVJMAGh0dHA6Ly90 YW5nby5mcmVlZGVza3RvcC5vcmcvVGFuZ29fSWNvbl9MaWJyYXJ5vMit1gAAAABJRU5ErkJggg== }] set images(dialog-close) [image create photo -data { iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QAAAAAAAD5Q7t/AAAACXBI WXMAAA3XAAAN1wFCKJt4AAAACXZwQWcAAAAQAAAAEABcxq3DAAACR0lEQVQ4y6VTQU/iQBh9U7qQ lrYYg7uLlOXA3kyMRy9rW7xx4LQHf2LPvRETbfXkgcSQGE8eQFi2Io20WCyd6ezBhYgHL36nyffe fPneezOEc47PlLg6uJYl5mYzG4SAadqJeX5O3xJdyxJzYWgD2MCFFUimU0euVltytdoi06njWtbG cBIETlHXW7Kut0gQrHERAPhkYiu1mlk7PpZACAadjjnv9x3XstoAgIcHp1ivm3qzKYFzDDodM7q/ twH8FgEgoxTZcolssUAWhtANQxqcnprRcOgAgKrrpm4YEvV9CJr2ymUMAEA453AJEdNGw1ErFbPe bEpZFEHQNNx73jMA1AyjmEURBEXB4OwsDsdj78vdXdvknJJVCi4hYlKvO2qlYvw4OpKz+Rw5VQUA sDCEoKoYXFzE0XjsFfr9tsk5XW+wNosQcaHrjra7+6t6eKiA/g9CFDG6upqHo9GlNByuL2/EuCpG KehyCbpYAFn22kxT0CQBo/Q9HRsSwp0dRy6XjerBgZwlCXKS9Dr05QVCPo/R9XUcPz562mSyKcEl RHza3nbkrS3j+/6+zJdLkHweDzc3cwD4urenrHp/e704fnrytoKgbXJOBQAISiU7Xywa5UZDTmcz MEox7Hbjme9fznz/ctjtxoxSpGGIcqMh54tFIyiV7LUHjDGwNEUaxwCAye1t/BJFXvn5uQ0Aj4w5 f3o9Y+fnTxmcI0tTsPfvwC8UnIKiGACQzOfetyRZ6/wI3zBxnMvZAFBh7ORtVB/h5LPf+R+0911L hE5qiwAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMC0wMi0xN1QwNzowMzozMy0wNzowMBY/EYkAAAAl dEVYdGRhdGU6bW9kaWZ5ADIwMTAtMDEtMTFUMDk6MTI6NDItMDc6MDDvvfybAAAANHRFWHRMaWNl bnNlAGh0dHA6Ly9jcmVhdGl2ZWNvbW1vbnMub3JnL2xpY2Vuc2VzL0dQTC8yLjAvbGoGqAAAABl0 RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAATdEVYdFNvdXJjZQBHTk9NRS1Db2xv cnOqmUTiAAAAMXRFWHRTb3VyY2VfVVJMAGh0dHA6Ly9jb2RlLmdvb2dsZS5jb20vcC9nbm9tZS1j b2xvcnMvUB216wAAAABJRU5ErkJggg== }] set images(system-log-out-3) [image create photo -data { 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 +zY9uT4AyB6fX7t34/ZrnavrZO3uiayTe/blVUM3neE74IOez2dFEn0SMri2OzryGl8/usYfB7b4 A3jphzX30frj2u4uE9X3veABEEOZzqMuMRWDQyQwXlvnyZU1fv3bHvy13+lik/rP/8BK8hNO8f97 loBpniF5/nVYK8LUfwEhukz7vupc2AAAACV0RVh0Y3JlYXRlLWRhdGUAMjAwOS0xMi0wOFQxMzow Mjo1OS0wNzowMPxcg3AAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTAtMDItMjBUMjM6MjY6MTgtMDc6 MDBn7D1BAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDEwLTAxLTExVDA5OjMwOjA5LTA3OjAwNWnPZwAA AGd0RVh0TGljZW5zZQBodHRwOi8vY3JlYXRpdmVjb21tb25zLm9yZy9saWNlbnNlcy9ieS1zYS8z LjAvIG9yIGh0dHA6Ly9jcmVhdGl2ZWNvbW1vbnMub3JnL2xpY2Vuc2VzL0xHUEwvMi4xL1uPPGMA AAAldEVYdG1vZGlmeS1kYXRlADIwMDktMTItMDhUMTM6MDI6NTktMDc6MDCj7fVEAAAAE3RFWHRT b3VyY2UAT3h5Z2VuIEljb25z7Biu6AAAACd0RVh0U291cmNlX1VSTABodHRwOi8vd3d3Lm94eWdl bi1pY29ucy5vcmcv7zeqywAAAABJRU5ErkJggg== }] set images(checkbox-off) [image create photo -data { iVBORw0KGgoAAAANSUhEUgAAACMAAAAWCAYAAABKbiVHAAAABmJLR0QA/wD/AP+gvaeTAAAACXBI WXMAABGwAAARsAHIJ/VUAAAAB3RJTUUH4AwPFSQzE9atXgAABBZJREFUSMeVVlluIzcQfcWl2a1e LAWyDRuYr1xlrjA5QuZMyREmR8hFDH/YH7JlybAcaOmFvZD5cMh0S7KUFCBAaBarXtWrhWStxWdi jPG/vhARjDFgjMEYAyLy+n0dAGCMwVoLIgLnHIyxAx/uG+2D6boOVVWhLMsfq9XqGxHBWot9PcYY iMg77d+31vrzvt5oNEIYhhRFEYIgAABvl4j+BWOtRdM0yPP863q9/rOua8RxDMYYpJTgnPss1XWN qqpgjPFAnWNnywXRB2uthZQSWZZ9j+P49yAIBnrkjFVVhe12+16W5QQAlFKYTCbgnEMIMTCotUae 59Bao65r1HUNAAO9fbHWous6AEAYhgjD8K84jn+KosgHIRxvVVV93W63EyLC7e0tpJQH/Lp0KqUg pURRFFiv12jbduDU1VGfLgCQUvrA67qeGGMsY4yiKPqwbYzBcrm0eZ5DKYWbmxtfdMfA9J3WdQ0p Jd7e3vD6+oqLiwtorQ/ocv8552iaxlPfti2UUri+viYhBBgAaK0hpUSSJD76U13mzhhj3rCU8mjX HcusKw1HnaOPVVXlwcRxPIjiHBinE0URlFIDuk7dIyJ0XQdjDLquQ57nvwIA226378YYhGEIKeXg 0jnDjo4wDDEajdC27dFs9O/sj4Ou61AUxW/WWrCqqiZE5IF8lt5TgxEAgiA4CaZPfX8QAkBZlh/B aa0RBMGAlv8DxlEjhEDbtt7ROZoYY96nrxk3iIhoENl+Me5P330w/a75LxQ7fbdarLVgSZKgLEts t1u/O7quO9vWDohSCgCw2+0OAtq/o5RyBTuY+kqpj2ylaUq73Q673c5nw6XtlLjFZ4xB27Zomubo IuxL0zS+4DnnvmvDMPwA48ZxVVXo76lzbdo0Ddq2hZQSTdPg9fUVxhgIIY5S1d/0nHN0XYe2bcE5 R5ZlRERgnHOMx2NorfHy8oKu66CUOhmhqxu3i1arFcqyBOf8bPG7AtdawxgDpRTCMPw4IyJcXl4S EWE2m2E+nx8U6bEohRAgIjw9PeHu7g7T6RRZlkFr/WnNOCCOriiKkGUZubHC3McvX758j6IIs9kM Dw8P0FqfjHCz2eD+/h6LxQJKKQRBMNjM52ZNFEWYTqfkVpBflE5ptVr9eHx8/FYUBdI0xe3tLUaj EZIkAWMMTdNgt9uhqiosFgssl0tcXV0hy7JBBlxxftaFQgiMx+Of0zR9cLqDx5WToijw/Pxs5/M5 8jxHkiSIoghCCDRNg6IofDumaYrRaIQgCHxX9bPQf3o6EEEQIE1TiuP4gM7BS88dtm2Lsizx/v5u N5sN1us1tNYIwxBZlsF1oBDCr5H+9nVA3KT9R/ePJEl+EUJ8WuhHwezvnT7X+4/u/Tv9Z+g+sHPy N32hrShk9cTaAAAAAElFTkSuQmCC }] set images(checkbox-on) [image create photo -data { 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 7o/TOb3h/5dcVPetRFgGAAAAAElFTkSuQmCC }]