source: trunk/gui/scripts/loader.tcl @ 3177

Last change on this file since 3177 was 3177, checked in by mmc, 12 years ago

Updated all of the copyright notices to reference the transfer to
the new HUBzero Foundation, LLC.

File size: 17.8 KB
Line 
1# ----------------------------------------------------------------------
2#  COMPONENT: loader - widget for loading examples and old runs
3#
4#  This widget is a glorified combobox that is used to load various
5#  example files into the application.
6# ======================================================================
7#  AUTHOR:  Michael McLennan, Purdue University
8#  Copyright (c) 2004-2012  HUBzero Foundation, LLC
9#
10#  See the file "license.terms" for information on usage and
11#  redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES.
12# ======================================================================
13package require Itk
14
15option add *Loader.textForeground black widgetDefault
16option add *Loader.textBackground white widgetDefault
17
18itcl::class Rappture::Loader {
19    inherit itk::Widget
20
21    itk_option define -tool tool Tool ""
22    itk_option define -state state State "normal"
23
24    constructor {owner path args} { # defined below }
25    destructor { # defined below }
26
27    public method value {args}
28
29    public method label {}
30    public method tooltip {}
31
32    protected method _newValue {}
33    protected method _uploadValue {path args}
34    protected method _downloadValues {}
35    protected method _tooltip {}
36
37    private method SetDefaultValue { value }
38
39    private variable _owner ""    ;# thing managing this control
40    private variable _path ""     ;# path in XML to this loader
41    private variable _lastlabel "";# label of last example loaded
42
43    private variable _uppath ""   ;# list: path label desc ...
44    private variable _dnpaths ""  ;# list of download element paths
45    private common _dnpath2state  ;# maps download path => yes/no state
46    private variable _copyfrom "" ;# copy xml objects from here in example lib
47    private variable _copyto ""   ;# copy xml objects here in example lib
48    private variable _label2file  ;# maps combobox labels to filenames
49}
50
51itk::usual Loader {
52    keep -cursor -font
53    keep -foreground -background
54    keep -textforeground -textbackground
55    keep -selectbackground -selectforeground -selectborderwidth
56}
57
58# ----------------------------------------------------------------------
59# CONSTRUCTOR
60# ----------------------------------------------------------------------
61itcl::body Rappture::Loader::constructor {owner path args} {
62    if {[catch {$owner isa Rappture::ControlOwner} valid] != 0 || !$valid} {
63        error "bad object \"$owner\": should be Rappture::ControlOwner"
64    }
65    set _owner $owner
66    set _path $path
67
68    itk_component add combo {
69        Rappture::Combobox $itk_interior.combo -editable no
70    } {
71        usual
72        keep -width
73    }
74    pack $itk_component(combo) -expand yes -fill both
75    bind $itk_component(combo) <<Value>> [itcl::code $this _newValue]
76
77    eval itk_initialize $args
78
79    # example files are stored here
80    if {$itk_option(-tool) != ""} {
81        set fdir [$itk_option(-tool) installdir]
82    } else {
83        set fdir "."
84    }
85    set defval [$_owner xml get $path.default]
86
87    #
88    # If this loader has a <new> section, then create that
89    # entry first.
90    #
91    set newfile ""
92    foreach comp [$_owner xml children -type new $path] {
93        set name [$_owner xml get $path.$comp]
94        set fname [file join $fdir examples $name]
95
96        if {[file exists $fname]} {
97            set newfile $fname
98
99            if {[catch {set obj [Rappture::library $fname]} result]} {
100                puts stderr "WARNING: can't load example file \"$fname\""
101                puts stderr "  $result"
102            } else {
103                set label "New"
104                $itk_component(combo) choices insert end $obj $label
105
106                # if this is new, add it to the label->file hash
107                if {![info exists entries($label)]} {
108                    set entries($label) $obj
109                    set _label2file($label) [file tail $fname]
110                }
111
112                # translate default file name => default label
113                if {[string equal $defval [file tail $fname]]} {
114                    $_owner xml put $path.default "New"
115                }
116            }
117            break
118        } else {
119            puts stderr "WARNING: missing example file \"$fname\""
120        }
121    }
122
123    #
124    # If this loader has an <upload> section, then create that
125    # entry next.
126    #
127    foreach comp [$_owner xml children -type upload $path] {
128        foreach tcomp [$_owner xml children -type to $path.$comp] {
129            set topath [$_owner xml get $path.$comp.$tcomp]
130            if { [$_owner xml element -as id $topath] == "" } {
131                puts stderr \
132                    "unknown <upload> path \"$topath\": please fix tool.xml"
133                continue
134            }
135            set label [$_owner xml get $topath.about.label]
136            set desc [$_owner xml get $topath.about.description]
137            lappend _uppath $topath $label $desc
138        }
139        break
140    }
141    if {[llength $_uppath] > 0} {
142        $itk_component(combo) choices insert end \
143            @upload [Rappture::filexfer::label upload]
144    }
145
146    #
147    # If this loader has a <download> section, then create that
148    # entry next.  Build a popup for choices if there is more than
149    # one download element.
150    #
151    Rappture::Balloon $itk_component(hull).download \
152        -title "Choose what to [string tolower [Rappture::filexfer::label downloadWord]]:"
153    set inner [$itk_component(hull).download component inner]
154
155    set i 0
156    foreach comp [$_owner xml children -type download $path] {
157        foreach dcomp [$_owner xml children -type from $path.$comp] {
158            set frompath [$_owner xml get $path.$comp.$dcomp]
159            if {"" != $frompath} {
160                lappend _dnpaths $frompath
161                set _dnpath2state($this-$frompath) [expr {$i == 0}]
162
163                set label [$_owner xml get $frompath.about.label]
164                checkbutton $inner.cb$i -text $label \
165                    -variable ::Rappture::Loader::_dnpath2state($this-$frompath)
166                pack $inner.cb$i -anchor w
167                incr i
168            }
169        }
170    }
171    button $inner.go -text [Rappture::filexfer::label download] \
172        -command [itcl::code $this _downloadValues]
173    pack $inner.go -side bottom -padx 50 -pady {4 2}
174
175    if {[llength $_dnpaths] > 0} {
176        $itk_component(combo) choices insert end \
177            @download [Rappture::filexfer::label download]
178    }
179
180    if {[$itk_component(combo) choices size] > 0} {
181        $itk_component(combo) choices insert end "---" "---"
182    }
183
184    #
185    # Scan through and extract example objects, and load them into
186    # the combobox.
187    #
188    set flist ""
189    foreach comp [$_owner xml children -type example $path] {
190        lappend flist [$_owner xml get $path.$comp]
191    }
192
193    # if there are no examples, then look for *.xml
194    if {[llength $flist] == 0} {
195        set flist *.xml
196    }
197
198    catch {unset entries}
199    set _counter 0
200    foreach ftail $flist {
201        set fpath [file join $fdir examples $ftail]
202
203        foreach fname [glob -nocomplain $fpath] {
204            if {[string equal $fname $newfile]} {
205                continue
206            }
207            if {[file exists $fname]} {
208                if {[catch {set obj [Rappture::library $fname]} result]} {
209                    puts stderr "WARNING: can't load example file \"$fname\""
210                    puts stderr "  $result"
211                } else {
212                    set label [$obj get about.label]
213                    if {$label == ""} {
214                        set label "Example #[incr _counter]"
215                    }
216
217                    # if this is new, add it to the label->file hash
218                    if {![info exists entries($label)]} {
219                        set entries($label) $obj
220                        set _label2file($label) [file tail $fname]
221                    }
222
223                    # translate default file name => default label
224                    if {[string equal $defval [file tail $fname]]} {
225                        $_owner xml put $path.default $label
226                    }
227                }
228            } else {
229                puts stderr "WARNING: missing example file \"$fname\""
230            }
231        }
232    }
233    foreach label [lsort -dictionary [array names entries]] {
234        $itk_component(combo) choices insert end $entries($label) $label
235    }
236
237    set _copyfrom [$_owner xml get $path.copy.from]
238    set _copyto [$_owner xml get $path.copy.to]
239
240    #
241    # Assign the default value to this widget, if there is one.
242    #
243    set str [$_owner xml get $path.default]
244    if { $str != "" } {
245        bind $itk_component(hull) <Map> [itcl::code $this SetDefaultValue $str]
246    }
247}
248
249# ----------------------------------------------------------------------
250# DESTRUCTOR
251# ----------------------------------------------------------------------
252itcl::body Rappture::Loader::destructor {} {
253    # be sure to clean up entries for this widget's download paths
254    foreach path $_dnpaths {
255        catch {unset _dnpath2state($this-$path)}
256    }
257}
258
259# ----------------------------------------------------------------------
260# USAGE: value ?-check? ?<newval>?
261#
262# Clients use this to query/set the value for this widget.  With
263# no args, it returns the current value for the widget.  If the
264# <newval> is specified, it sets the value of the widget and
265# sends a <<Value>> event.  If the -check flag is included, the
266# new value is not actually applied, but just checked for correctness.
267# ----------------------------------------------------------------------
268itcl::body Rappture::Loader::value {args} {
269    set onlycheck 0
270    set i [lsearch -exact $args -check]
271    if {$i >= 0} {
272        set onlycheck 1
273        set args [lreplace $args $i $i]
274    }
275
276    if {[llength $args] == 1} {
277        if {$onlycheck} {
278            # someday we may add validation...
279            return
280        }
281        set newval [lindex $args 0]
282        $itk_component(combo) value $newval
283        return $newval
284
285    } elseif {[llength $args] != 0} {
286        error "wrong # args: should be \"value ?-check? ?newval?\""
287    }
288
289    #
290    # Query the value and return.
291    #
292    return [$itk_component(combo) value]
293}
294
295# ----------------------------------------------------------------------
296# USAGE: label
297#
298# Clients use this to query the label associated with this widget.
299# Reaches into the XML and pulls out the appropriate label string.
300# ----------------------------------------------------------------------
301itcl::body Rappture::Loader::label {} {
302    set label [$_owner xml get $_path.about.label]
303    if {"" == $label} {
304        set label "Example"
305    }
306    return $label
307}
308
309# ----------------------------------------------------------------------
310# USAGE: tooltip
311#
312# Clients use this to query the tooltip associated with this widget.
313# Reaches into the XML and pulls out the appropriate description
314# string.  Returns the string that should be used with the
315# Rappture::Tooltip facility.
316# ----------------------------------------------------------------------
317itcl::body Rappture::Loader::tooltip {} {
318    # query tooltip on-demand based on current choice
319    return "@[itcl::code $this _tooltip]"
320}
321
322#
323# SetDefaultValue --
324#
325#       Sets the designated default value for the loader.  This must be done
326#       after the entire application is assembled, otherwise the default
327#       values set up by the loader will be overwritten by the various widgets
328#       themselves when they try to set their default values. 
329#
330#       This is called from a  <Map> event to the loader (combobox).  This
331#       will get trigger the first time the loader is displayed.  The binding
332#       is then removed.
333#
334itcl::body Rappture::Loader::SetDefaultValue { value } {
335    after idle [itcl::code $this value $value]
336    # We're done. Remove the binding.
337    bind $itk_component(hull) <Map> {}
338}
339
340# ----------------------------------------------------------------------
341# USAGE: _newValue
342#
343# Invoked automatically whenever the value in the combobox changes.
344# Tries to load the selected example into the tool's data structure.
345# Sends a <<Value>> event to notify clients of the change.
346# ----------------------------------------------------------------------
347itcl::body Rappture::Loader::_newValue {} {
348    set newval [$itk_component(combo) value]
349    set obj [$itk_component(combo) translate $newval]
350    if {$obj == "@upload"} {
351        set tool [Rappture::Tool::resources -appname]
352        Rappture::filexfer::upload \
353            $tool $_uppath [itcl::code $this _uploadValue]
354
355        # put the combobox back to its last value
356        $itk_component(combo) component entry configure -state normal
357        $itk_component(combo) component entry delete 0 end
358        $itk_component(combo) component entry insert end $_lastlabel
359        $itk_component(combo) component entry configure -state disabled
360
361    } elseif {$obj == "@download"} {
362        if {[llength $_dnpaths] == 1} {
363            _downloadValues
364        } else {
365            $itk_component(hull).download activate $itk_component(combo) below
366        }
367
368        # put the combobox back to its last value
369        $itk_component(combo) component entry configure -state normal
370        $itk_component(combo) component entry delete 0 end
371        $itk_component(combo) component entry insert end $_lastlabel
372        $itk_component(combo) component entry configure -state disabled
373
374    } elseif {$obj == "---"} {
375        # put the combobox back to its last value
376        $itk_component(combo) component entry configure -state normal
377        $itk_component(combo) component entry delete 0 end
378        $itk_component(combo) component entry insert end $_lastlabel
379        $itk_component(combo) component entry configure -state disabled
380    } elseif {$obj != "" && $itk_option(-tool) != ""} {
381        if {("" != $_copyfrom) && ("" != $_copyto)} {
382            $obj copy $_copyto from $_copyfrom
383        }
384        $_owner xml put $_path.file $_label2file($newval)
385        $itk_option(-tool) load $obj
386        set _lastlabel $newval
387    }
388
389    event generate $itk_component(hull) <<Value>>
390}
391
392# ----------------------------------------------------------------------
393# USAGE: _tooltip
394#
395# Returns the tooltip for this widget, given the current choice in
396# the selector.  This is normally called by the Rappture::Tooltip
397# facility whenever it is about to pop up a tooltip for this widget.
398# ----------------------------------------------------------------------
399itcl::body Rappture::Loader::_tooltip {} {
400    set str [string trim [$_owner xml get $_path.about.description]]
401
402    # get the description for the current choice, if there is one
403    set newval [$itk_component(combo) value]
404    set obj [$itk_component(combo) translate $newval]
405    if {$obj != ""} {
406        if {$obj == "@upload"} {
407            append str "\n\nUse this option to upload data from your desktop."
408        } else {
409            set label [$obj get about.label]
410            if {[string length $label] > 0} {
411                append str "\n\n$label"
412            }
413
414            set desc [$obj get about.description]
415            if {[string length $desc] > 0} {
416                if {[string length $label] > 0} {
417                    append str ":\n"
418                } else {
419                    append str "\n\n"
420                }
421                append str $desc
422            }
423        }
424    }
425    return [string trim $str]
426}
427
428# ----------------------------------------------------------------------
429# USAGE: _uploadValue ?<key> <value> <key> <value> ...?
430#
431# Invoked automatically whenever the user has uploaded data from
432# the "Upload..." option.  Takes the data value (passed as an
433# argument) and loads into the destination widget.
434# ----------------------------------------------------------------------
435itcl::body Rappture::Loader::_uploadValue {args} {
436    array set data $args
437
438    if {[info exists data(error)]} {
439        Rappture::Tooltip::cue $itk_component(combo) $data(error)
440    }
441
442    if {[info exists data(path)] && [info exists data(data)]} {
443        Rappture::Tooltip::cue hide  ;# take down note about the popup window
444        $itk_option(-tool) valuefor $data(path) $data(data)
445
446        $itk_component(combo) component entry configure -state normal
447        $itk_component(combo) component entry delete 0 end
448        $itk_component(combo) component entry insert end "Uploaded data"
449        $itk_component(combo) component entry configure -state disabled
450        set _lastlabel "Uploaded data"
451    }
452}
453
454# ----------------------------------------------------------------------
455# USAGE: _downloadValues
456#
457# Used internally to download all values checked by the popup that
458# controls downloading.  Sends the values for the various controls
459# out to the user by popping up separate browser windows.
460# ----------------------------------------------------------------------
461itcl::body Rappture::Loader::_downloadValues {} {
462    # take down the popup (in case it was posted)
463    $itk_component(hull).download deactivate
464
465    set mesg ""
466    foreach path $_dnpaths {
467        if {$_dnpath2state($this-$path)} {
468            set info [$itk_option(-tool) valuefor $path]
469            set mesg [Rappture::filexfer::download $info input.txt]
470            if {"" != $mesg} { break }
471        }
472    }
473
474    if {"" != $mesg} {
475        Rappture::Tooltip::cue $itk_component(combo) $mesg
476    }
477}
478
479# ----------------------------------------------------------------------
480# OPTION: -tool
481# ----------------------------------------------------------------------
482itcl::configbody Rappture::Loader::tool {
483    if {[catch {$itk_option(-tool) isa Rappture::Tool} valid] || !$valid} {
484        error "object \"$itk_option(-tool)\" is not a Rappture Tool"
485    }
486}
487
488# ----------------------------------------------------------------------
489# CONFIGURATION OPTION: -state
490# ----------------------------------------------------------------------
491itcl::configbody Rappture::Loader::state {
492    set valid {normal disabled}
493    if {[lsearch -exact $valid $itk_option(-state)] < 0} {
494        error "bad value \"$itk_option(-state)\": should be [join $valid {, }]"
495    }
496    $itk_component(combo) configure -state $itk_option(-state)
497}
Note: See TracBrowser for help on using the repository browser.