Updated 2017-10-03 08:55:28 by JOB

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:

  • In case, a function or an application can handle either a single file or maybe a directory (which contains one or more files) the getfileordirectory dialog might be useful.
  • The getfileordirectory dialog e.g. is integrated in the HelpViewer application.

Features:

  • Autodetects the file type - in other words: just select whatever filetype you want with the Explorer (WIN) / Finder (OSX) and D&D it onto the boxed dialog area.
  • In addition to the file/directory selection functionality, there is also a listbox availabe, which manages the **Most recently used files or directories**.

The package has (hopefully) reached a mature state. Nevertheless, anyone is invited to share ideas for some more improvements whatsoever.

Source code:

  • getfileordirectory.tcl
# ------------------------------------------------------------------------
# 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
# ------------------------------------------------------------------------
# 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\""

  • pkgIndex.tcl
# Tcl package index file

package ifneeded getfileordirectory 0.1 \
        [list source [file join $dir getfileordirectory.tcl]]

  • ImageLib.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
}]