# ---------------------------------------------------------------------- # COMPONENT: FileChoiceEntry - widget for entering a choice of strings # # This widget represents a entry on a control panel. # It is used to choose one of several mutually-exclusive strings. # ====================================================================== # AUTHOR: Michael McLennan, Purdue University # Copyright (c) 2004-2005 Purdue Research Foundation # # See the file "license.terms" for information on usage and # redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES. # ====================================================================== package require Itk itcl::class Rappture::FileChoiceEntry { inherit itk::Widget itk_option define -state state State "normal" private variable _rebuildPending 0 constructor {owner path args} { # defined below } destructor { # defined below } public method value {args} public method label {} public method tooltip {} protected method _rebuild {} protected method _newValue {} protected method _tooltip {} protected method _whenidle {} private variable _owner "" ;# thing managing this control private variable _path "" ;# path in XML to this number private variable _str2val ;# maps option label => option value } itk::usual FileChoiceEntry { keep -cursor -font keep -foreground -background keep -textforeground -textbackground keep -selectbackground -selectforeground -selectborderwidth } # ---------------------------------------------------------------------- # CONSTRUCTOR # ---------------------------------------------------------------------- itcl::body Rappture::FileChoiceEntry::constructor {owner path args} { if {[catch {$owner isa Rappture::ControlOwner} valid] != 0 || !$valid} { error "bad object \"$owner\": should be Rappture::ControlOwner" } set _owner $owner set _path $path # # Create the widget and configure it properly based on other # hints in the XML. # itk_component add choice { Rappture::Combobox $itk_interior.choice -editable no } pack $itk_component(choice) -expand yes -fill both bind $itk_component(choice) <> [itcl::code $this _newValue] # First time, parse the elements to generate notify callbacks # for each template found. foreach cname [$_owner xml children -type pattern $_path] { set glob [string trim [$_owner xml get $_path.$cname]] # Successively replace each template with its value. while { [regexp -indices {@@[^@]*@@} $glob range] } { foreach {first last} $range break set i1 [expr $first + 2] set i2 [expr $last - 2] set path [string range $glob $i1 $i2] set value [$_owner xml get $_path.$cname] set value [$_owner xml get $path] set glob [string replace $glob $first $last $value] $_owner notify add $this $path [itcl::code $this _whenidle] } } $_owner notify sync eval itk_initialize $args _rebuild } # ---------------------------------------------------------------------- # DESTRUCTOR # ---------------------------------------------------------------------- itcl::body Rappture::FileChoiceEntry::destructor {} { $_owner notify remove $this } # ---------------------------------------------------------------------- # USAGE: value ?-check? ?? # # Clients use this to query/set the value for this widget. With # no args, it returns the current value for the widget. If the # is specified, it sets the value of the widget and # sends a <> event. If the -check flag is included, the # new value is not actually applied, but just checked for correctness. # ---------------------------------------------------------------------- itcl::body Rappture::FileChoiceEntry::value {args} { set onlycheck 0 set i [lsearch -exact $args -check] if {$i >= 0} { set onlycheck 1 set args [lreplace $args $i $i] } if {[llength $args] == 1} { if {$onlycheck} { # someday we may add validation... return } set newval [lindex $args 0] if {[info exists _str2val($newval)]} { # this is a label -- use it directly $itk_component(choice) value $newval set newval $_str2val($newval) ;# report the actual value } else { # this is a value -- search for corresponding label foreach str [array names _str2val] { if {$_str2val($str) == $newval} { $itk_component(choice) value $str break } } } return $newval } elseif {[llength $args] != 0} { error "wrong # args: should be \"value ?-check? ?newval?\"" } # # Query the value and return. # set str [$itk_component(choice) value] if {[info exists _str2val($str)]} { return $_str2val($str) } return $str } # ---------------------------------------------------------------------- # USAGE: label # # Clients use this to query the label associated with this widget. # Reaches into the XML and pulls out the appropriate label string. # ---------------------------------------------------------------------- itcl::body Rappture::FileChoiceEntry::label {} { set label [$_owner xml get $_path.about.label] if {"" == $label} { set label "Choice" } return $label } # ---------------------------------------------------------------------- # USAGE: tooltip # # Clients use this to query the tooltip associated with this widget. # Reaches into the XML and pulls out the appropriate description # string. Returns the string that should be used with the # Rappture::Tooltip facility. # ---------------------------------------------------------------------- itcl::body Rappture::FileChoiceEntry::tooltip {} { # query tooltip on-demand based on current choice return "@[itcl::code $this _tooltip]" } # ---------------------------------------------------------------------- # USAGE: _rebuild # # Used internally to rebuild the contents of this choice widget # whenever something that it depends on changes. Scans through the # information in the XML spec and builds a list of choices for the # widget. # ---------------------------------------------------------------------- itcl::body Rappture::FileChoiceEntry::_rebuild {} { puts stderr "filechoiceentry rebuild" set _rebuildPending 0 # get rid of any existing choices $itk_component(choice) choices delete 0 end array unset _str2val # # Plug in the various options for the choice. # set max 10 $_owner notify sync set allfiles {} foreach cname [$_owner xml children -type pattern $_path] { set glob [string trim [$_owner xml get $_path.$cname]] # Successively replace each template with its value. puts stderr "before glob=$glob" while { [regexp -indices {@@[^@]*@@} $glob range] } { foreach {first last} $range break set i1 [expr $first + 2] set i2 [expr $last - 2] set path [string range $glob $i1 $i2] set value [$_owner xml get $path.current] puts stderr "$path.current: value=$value" if { $value == "" } { set value [$_owner xml get $path.default] puts stderr "$path.default: value=$value" } set glob [string replace $glob $first $last $value] } puts stderr "after glob=$glob" # Replace the template with the substituted value. set files [glob -nocomplain $glob] puts stderr "files=$files" set allfiles [concat $allfiles $files] } set first "" foreach file $allfiles { set tail [file tail $file] if { $first == "" } { set first $tail } $itk_component(choice) choices insert end $file $tail set _str2val($file) $tail set len [string length $tail] if {$len > $max} { set max $len } } $itk_component(choice) configure -width $max $itk_component(choice) value $tail } # ---------------------------------------------------------------------- # USAGE: _newValue # # Invoked automatically whenever the value in the choice changes. # Sends a <> event to notify clients of the change. # ---------------------------------------------------------------------- itcl::body Rappture::FileChoiceEntry::_newValue {} { event generate $itk_component(hull) <> } # ---------------------------------------------------------------------- # USAGE: _tooltip # # Returns the tooltip for this widget, given the current choice in # the selector. This is normally called by the Rappture::Tooltip # facility whenever it is about to pop up a tooltip for this widget. # ---------------------------------------------------------------------- itcl::body Rappture::FileChoiceEntry::_tooltip {} { set tip [string trim [$_owner xml get $_path.about.description]] # get the description for the current choice, if there is one set str [$itk_component(choice) value] set path [$itk_component(choice) translate $str] set desc "" if {$path != ""} { set desc [$_owner xml get $path.about.description] } if {[string length $str] > 0 && [string length $desc] > 0} { append tip "\n\n$str:\n$desc" } return $tip } # ---------------------------------------------------------------------- # CONFIGURATION OPTION: -state # ---------------------------------------------------------------------- itcl::configbody Rappture::FileChoiceEntry::state { set valid {normal disabled} if {[lsearch -exact $valid $itk_option(-state)] < 0} { error "bad value \"$itk_option(-state)\": should be [join $valid {, }]" } $itk_component(choice) configure -state $itk_option(-state) } itcl::body Rappture::FileChoiceEntry::_whenidle {} { if { !$_rebuildPending } { after 10 [itcl::code $this _rebuild] set _rebuildPending 1 } }