source: trunk/gui/scripts/resultset.tcl @ 2035

Last change on this file since 2035 was 1929, checked in by gah, 14 years ago
File size: 60.0 KB
Line 
1# ----------------------------------------------------------------------
2#  COMPONENT: ResultSet - controls for a collection of related results
3#
4#  This widget stores a collection of results that all represent
5#  the same quantity, but for various ranges of input values.
6#  It also manages the controls to select and visualize the data.
7# ======================================================================
8#  AUTHOR:  Michael McLennan, Purdue University
9#  Copyright (c) 2004-2005  Purdue Research Foundation
10#
11#  See the file "license.terms" for information on usage and
12#  redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES.
13# ======================================================================
14package require Itk
15
16option add *ResultSet.width 4i widgetDefault
17option add *ResultSet.height 4i widgetDefault
18option add *ResultSet.missingData skip widgetDefault
19option add *ResultSet.controlbarBackground gray widgetDefault
20option add *ResultSet.controlbarForeground white widgetDefault
21option add *ResultSet.activeControlBackground #ffffcc widgetDefault
22option add *ResultSet.activeControlForeground black widgetDefault
23option add *ResultSet.controlActiveForeground blue widgetDefault
24option add *ResultSet.toggleBackground gray widgetDefault
25option add *ResultSet.toggleForeground white widgetDefault
26option add *ResultSet.textFont \
27    -*-helvetica-medium-r-normal-*-12-* widgetDefault
28option add *ResultSet.boldFont \
29    -*-helvetica-bold-r-normal-*-12-* widgetDefault
30
31itcl::class Rappture::ResultSet {
32    inherit itk::Widget
33
34    itk_option define -activecontrolbackground activeControlBackground Background ""
35    itk_option define -activecontrolforeground activeControlForeground Foreground ""
36    itk_option define -controlactiveforeground controlActiveForeground Foreground ""
37    itk_option define -togglebackground toggleBackground Background ""
38    itk_option define -toggleforeground toggleForeground Foreground ""
39    itk_option define -textfont textFont Font ""
40    itk_option define -boldfont boldFont Font ""
41    itk_option define -foreground foreground Foreground ""
42    itk_option define -missingdata missingData MissingData ""
43    itk_option define -clearcommand clearCommand ClearCommand ""
44    itk_option define -settingscommand settingsCommand SettingsCommand ""
45    itk_option define -promptcommand promptCommand PromptCommand ""
46
47    constructor {args} { # defined below }
48    destructor { # defined below }
49
50    public method add {xmlobj}
51    public method clear {}
52    public method activate {column}
53    public method contains {xmlobj}
54    public method size {{what -results}}
55
56    protected method _doClear {}
57    protected method _doSettings {{cmd ""}}
58    protected method _doPrompt {state}
59    protected method _control {option args}
60    protected method _fixControls {args}
61    protected method _fixLayout {args}
62    protected method _fixSettings {args}
63    protected method _fixExplore {}
64    protected method _fixValue {column why}
65    protected method _drawValue {column widget wmax}
66    protected method _toggleAll {{column "current"}}
67    protected method _getValues {column {which ""}}
68    protected method _getTooltip {role column}
69    protected method _getParamDesc {which {index "current"}}
70
71    private variable _dispatcher ""  ;# dispatchers for !events
72    private variable _results ""     ;# tuple of known results
73    private variable _recent ""      ;# most recent result in _results
74    private variable _active ""      ;# column with active control
75    private variable _plotall 0      ;# non-zero => plot all active results
76    private variable _layout         ;# info used in _fixLayout
77    private variable _counter 0      ;# counter for unique control names
78    private variable _settings 0     ;# non-zero => _fixSettings in progress
79    private variable _explore 0      ;# non-zero => explore all parameters
80
81    private common _cntlInfo         ;# maps column name => control info
82}
83                                                                               
84itk::usual ResultSet {
85    keep -background -foreground -cursor -font
86}
87
88# ----------------------------------------------------------------------
89# CONSTRUCTOR
90# ----------------------------------------------------------------------
91itcl::body Rappture::ResultSet::constructor {args} {
92    option add hull.width hull.height
93    pack propagate $itk_component(hull) no
94
95    # create a dispatcher for events
96    Rappture::dispatcher _dispatcher
97    $_dispatcher register !fixcntls
98    $_dispatcher dispatch $this !fixcntls \
99        [itcl::code $this _fixControls]
100    $_dispatcher register !layout
101    $_dispatcher dispatch $this !layout \
102        [itcl::code $this _fixLayout]
103    $_dispatcher register !settings
104    $_dispatcher dispatch $this !settings \
105        [itcl::code $this _fixSettings]
106
107    # initialize controls info
108    set _cntlInfo($this-all) ""
109
110    # initialize layout info
111    set _layout(mode) "usual"
112    set _layout(active) ""
113
114    # create a list of tuples for data
115    set _results [Rappture::Tuples ::#auto]
116    $_results column insert end -name xmlobj -label "top-level XML object"
117
118
119    itk_component add cntls {
120        frame $itk_interior.cntls
121    } {
122        usual
123        rename -background -controlbarbackground controlbarBackground Background
124        rename -highlightbackground -controlbarbackground controlbarBackground Background
125    }
126    pack $itk_component(cntls) -fill x -pady {0 2}
127
128    itk_component add clear {
129        button $itk_component(cntls).clear -text "Clear" -state disabled \
130            -padx 1 -pady 1 \
131            -relief flat -overrelief raised \
132            -command [itcl::code $this _doClear]
133    } {
134        usual
135        rename -background -controlbarbackground controlbarBackground Background
136        rename -foreground -controlbarforeground controlbarForeground Foreground
137        rename -highlightbackground -controlbarbackground controlbarBackground Background
138    }
139    pack $itk_component(clear) -side right -padx 2 -pady 1
140    Rappture::Tooltip::for $itk_component(clear) \
141        "Clears all results collected so far."
142
143    itk_component add status {
144        label $itk_component(cntls).status -anchor w \
145            -text "No results" -padx 0 -pady 0
146    } {
147        usual
148        rename -background -controlbarbackground controlbarBackground Background
149        rename -foreground -controlbarforeground controlbarForeground Foreground
150        rename -highlightbackground -controlbarbackground controlbarBackground Background
151    }
152    pack $itk_component(status) -side left -padx 2 -pady {2 0}
153
154    itk_component add parameters {
155        button $itk_component(cntls).params -text "Parameters..." \
156            -state disabled -padx 1 -pady 1 \
157            -relief flat -overrelief raised \
158            -command [list $itk_component(hull).popup activate $itk_component(cntls).params above]
159    } {
160        usual
161        rename -background -controlbarbackground controlbarBackground Background
162        rename -foreground -controlbarforeground controlbarForeground Foreground
163        rename -highlightbackground -controlbarbackground controlbarBackground Background
164    }
165    pack $itk_component(parameters) -side left -padx 8 -pady 1
166    Rappture::Tooltip::for $itk_component(parameters) \
167        "Click to access all parameters."
168
169    itk_component add dials {
170        frame $itk_interior.dials
171    }
172    pack $itk_component(dials) -expand yes -fill both
173    bind $itk_component(dials) <Configure> \
174        [list $_dispatcher event -after 10 !layout why resize]
175
176    # create the permanent controls in the "short list" area
177    set dials $itk_component(dials)
178    frame $dials.bg
179    Rappture::Radiodial $dials.dial -valuewidth 0
180    Rappture::Tooltip::for $dials.dial \
181        "@[itcl::code $this _getTooltip dial active]"
182
183    set fn [option get $itk_component(hull) textFont Font]
184    label $dials.all -text "All" -padx 8 \
185        -borderwidth 1 -relief raised -font $fn
186    Rappture::Tooltip::for $dials.all \
187        "@[itcl::code $this _getTooltip all active]"
188    bind $dials.all <ButtonRelease> [itcl::code $this _toggleAll]
189
190    frame $dials.labelmore
191    label $dials.labelmore.arrow -bitmap [Rappture::icon empty] -borderwidth 0
192    pack $dials.labelmore.arrow -side left -fill y
193    _control bind $dials.labelmore.arrow @more
194    label $dials.labelmore.name -text "more parameters..." -font $fn \
195        -borderwidth 0 -padx 0 -pady 1
196    pack $dials.labelmore.name -side left
197    label $dials.labelmore.value
198    pack $dials.labelmore.value -side left
199    _control bind $dials.labelmore.name @more
200    Rappture::Tooltip::for $dials.labelmore \
201        "@[itcl::code $this _getTooltip more more]"
202
203    # use this pop-up for access to all controls
204    Rappture::Balloon $itk_component(hull).popup \
205        -title "Change Parameters" -padx 0 -pady 0
206    set inner [$itk_component(hull).popup component inner]
207
208    frame $inner.cntls
209    pack $inner.cntls -side bottom -fill x
210    frame $inner.cntls.sep -height 2 -borderwidth 1 -relief sunken
211    pack $inner.cntls.sep -side top -fill x -padx 4 -pady 4
212    checkbutton $inner.cntls.explore -font $fn \
213        -text "Explore combinations with no results" \
214        -variable [itcl::scope _explore] \
215        -command [itcl::code $this _fixExplore]
216    pack $inner.cntls.explore -side top -anchor w
217    Rappture::Tooltip::for $inner.cntls.explore \
218        "When this option is turned on, you can set parameters to various combinations that have not yet been simulated.  The Simulate button will light up, and you can simulate these missing combinations.\n\nWhen turned off, controls will avoid missing combinations, and automatically snap to the closest available dataset."
219
220    itk_component add options {
221        Rappture::Scroller $inner.scrl -xscrollmode auto -yscrollmode auto
222    }
223    pack $itk_component(options) -expand yes -fill both
224
225    set popup [$itk_component(options) contents frame]
226    frame $popup.bg
227
228    eval itk_initialize $args
229}
230
231# ----------------------------------------------------------------------
232# DESTRUCTOR
233# ----------------------------------------------------------------------
234itcl::body Rappture::ResultSet::destructor {} {
235    itcl::delete object $_results
236}
237
238# ----------------------------------------------------------------------
239# USAGE: add <xmlobj>
240#
241# Adds a new result to this result set.  Scans through all existing
242# results to look for a difference compared to previous results.
243# Returns the index of this new result to the caller.  The various
244# data objects for this result set should be added to their result
245# viewers at the same index.
246# ----------------------------------------------------------------------
247itcl::body Rappture::ResultSet::add {xmlobj} {
248    # make sure we fix up controls at some point
249    $_dispatcher event -idle !fixcntls
250
251    #
252    # If this is the first result, then there are no diffs.
253    # Add it right in.
254    #
255    set xmlobj0 [$_results get -format xmlobj end]
256    if {"" == $xmlobj0} {
257        # first element -- always add
258        $_results insert end [list $xmlobj]
259        set _recent $xmlobj
260        $itk_component(status) configure -text "1 result"
261        $itk_component(clear) configure -state normal
262        if {[$_results size] >= 2} {
263            $itk_component(parameters) configure -state normal
264        } else {
265            $itk_component(parameters) configure -state disabled
266        }
267        return 0
268    }
269
270    #
271    # Compare this new object against the last XML object in the
272    # results set.  If it has a difference, make sure that there
273    # is a column to represent the quantity with the difference.
274    #
275    foreach {op vpath oldval newval} [$xmlobj0 diff $xmlobj] {
276        if {[$xmlobj get $vpath.about.diffs] == "ignore"} {
277            continue
278        }
279        if {$op == "+" || $op == "-"} {
280            # ignore differences where parameters come and go
281            # such differences make it hard to work controls
282            continue
283        }
284
285        # make sure that these values really are different
286        set oldval [lindex [Rappture::LibraryObj::value $xmlobj0 $vpath] 0]
287        set newval [lindex [Rappture::LibraryObj::value $xmlobj $vpath] 0]
288
289        if {$oldval != $newval && [$_results column names $vpath] == ""} {
290            # no column for this quantity yet
291            $_results column insert end -name $vpath -default $oldval
292        }
293    }
294
295    # build a tuple for this new object
296    set cols ""
297    set tuple ""
298    foreach col [lrange [$_results column names] 1 end] {
299        lappend cols $col
300        set raw [lindex [Rappture::LibraryObj::value $xmlobj $col] 0]
301        lappend tuple $raw  ;# use the "raw" (user-readable) label
302    }
303
304    # find a matching tuple? then replace it -- only need one
305    if {[llength $cols] > 0} {
306        set ilist [$_results find -format $cols -- $tuple]
307    } else {
308        set ilist 0  ;# no diffs -- must match first entry
309    }
310
311    # add all remaining columns for this new entry
312    set tuple [linsert $tuple 0 $xmlobj]
313
314    if {[llength $ilist] > 0} {
315        if {[llength $ilist] > 1} {
316            error "why so many matching results?"
317        }
318
319        # overwrite the first matching entry
320        set index [lindex $ilist 0]
321        $_results put $index $tuple
322        set _recent $xmlobj
323    } else {
324        set index [$_results size]
325        $_results insert end $tuple
326        set _recent $xmlobj
327    }
328
329    if {[$_results size] == 1} {
330        $itk_component(status) configure -text "1 result"
331    } else {
332        $itk_component(status) configure -text "[$_results size] results"
333        $itk_component(parameters) configure -state normal
334    }
335    $itk_component(clear) configure -state normal
336
337    return $index
338}
339
340# ----------------------------------------------------------------------
341# USAGE: clear
342#
343# Clears all results in this result set.
344# ----------------------------------------------------------------------
345itcl::body Rappture::ResultSet::clear {} {
346    _doSettings
347
348    # delete all adjuster controls
349    set popup [$itk_component(options) contents frame]
350    set shortlist $itk_component(dials)
351    foreach col $_cntlInfo($this-all) {
352        set id $_cntlInfo($this-$col-id)
353        destroy $popup.label$id $popup.dial$id $popup.all$id
354        destroy $shortlist.label$id
355    }
356
357    # clean up control info
358    foreach key [array names _cntlInfo $this-*] {
359        catch {unset _cntlInfo($key)}
360    }
361    set _cntlInfo($this-all) ""
362    set _counter 0
363
364    # clear out all results
365    $_results delete 0 end
366    eval $_results column delete [lrange [$_results column names] 1 end]
367    set _recent ""
368    set _active ""
369
370    set _plotall 0
371    $itk_component(dials).all configure -relief raised \
372        -background $itk_option(-background) \
373        -foreground $itk_option(-foreground)
374
375    # update status and Clear button
376    $itk_component(status) configure -text "No results"
377    $itk_component(parameters) configure -state disabled
378    $itk_component(clear) configure -state disabled
379    $_dispatcher event -idle !fixcntls
380
381    # let clients know that the number of controls has changed
382    event generate $itk_component(hull) <<Control>>
383}
384
385# ----------------------------------------------------------------------
386# USAGE: activate <column>
387#
388# Clients use this to activate a particular column in the set of
389# controls.  When a column is active, its label is bold and its
390# value has a radiodial in the "short list" area.
391# ----------------------------------------------------------------------
392itcl::body Rappture::ResultSet::activate {column} {
393    if {$column == "@more"} {
394        $itk_component(hull).popup activate \
395            $itk_component(dials).labelmore.name above
396        return
397    }
398
399    set allowed [$_results column names]
400    if {[lsearch $allowed $column] < 0} {
401        error "bad value \"$column\": should be one of [join $allowed {, }]"
402    }
403
404    # column is now active
405    set _active $column
406
407    # keep track of usage, so we know which controls are popular
408    incr _cntlInfo($this-$column-usage)
409
410    # fix controls at next idle point
411    $_dispatcher event -idle !layout why data
412    $_dispatcher event -idle !settings column $_active
413}
414
415# ----------------------------------------------------------------------
416# USAGE: contains <xmlobj>
417#
418# Checks to see if the given <xmlobj> is already represented by
419# some result in this result set.  This comes in handy when checking
420# to see if an input case is already covered.
421#
422# Returns 1 if the result set already contains this result, and
423# 0 otherwise.
424# ----------------------------------------------------------------------
425itcl::body Rappture::ResultSet::contains {xmlobj} {
426    # no results? then this must be new
427    if {[$_results size] == 0} {
428        return 0
429    }
430
431    #
432    # Compare this new object against the last XML object in the
433    # results set.  If it has a difference, make sure that there
434    # is a column to represent the quantity with the difference.
435    #
436    set xmlobj0 [$_results get -format xmlobj end]
437    foreach {op vpath oldval newval} [$xmlobj0 diff $xmlobj] {
438        if {[$xmlobj get $vpath.about.diffs] == "ignore"} {
439            continue
440        }
441        if {$op == "+" || $op == "-"} {
442            # ignore differences where parameters come and go
443            # such differences make it hard to work controls
444            continue
445        }
446        if {[$_results column names $vpath] == ""} {
447            # no column for this quantity yet
448            return 0
449        }
450    }
451
452    #
453    # If we got this far, then look through existing results for
454    # matching tuples, then check each one for diffs.
455    #
456    set format ""
457    set tuple ""
458    foreach col [lrange [$_results column names] 1 end] {
459        lappend format $col
460        set raw [lindex [Rappture::LibraryObj::value $xmlobj $col] 0]
461        lappend tuple $raw  ;# use the "raw" (user-readable) label
462    }
463    if {[llength $format] > 0} {
464        set ilist [$_results find -format $format -- $tuple]
465    } else {
466        set ilist 0  ;# no diffs -- must match first entry
467    }
468
469    foreach i $ilist {
470        set xmlobj0 [$_results get -format xmlobj $i]
471        set diffs [$xmlobj0 diff $xmlobj]
472        if {[llength $diffs] == 0} {
473            # no diffs -- already contained here
474            return 1
475        }
476    }
477
478    # must be some differences
479    return 0
480}
481
482
483# ----------------------------------------------------------------------
484# USAGE: size ?-results|-controls|-controlarea?
485#
486# Returns various measures for the size of this area:
487#   -results ....... number of results loaded
488#   -controls ...... number of distinct control parameters
489#   -controlarea ... minimum size of usable control area, in pixels
490# ----------------------------------------------------------------------
491itcl::body Rappture::ResultSet::size {{what -results}} {
492    switch -- $what {
493        -results {
494            return [$_results size]
495        }
496        -controls {
497            return [llength $_cntlInfo($this-all)]
498        }
499        -controlarea {
500            set ht [winfo reqheight $itk_component(cntls)]
501            incr ht 2  ;# padding below controls
502
503            set normalLine [font metrics $itk_option(-textfont) -linespace]
504            incr normalLine 2  ;# padding
505            set boldLine [font metrics $itk_option(-boldfont) -linespace]
506            incr boldLine 2  ;# padding
507
508            set numcntls [llength $_cntlInfo($this-all)]
509            switch -- $numcntls {
510                0 - 1 {
511                    # 0 = no controls (no data at all)
512                    # 1 = run control, but only 1 run so far
513                    # add nothing
514                }
515                default {
516                    # non-active controls
517                    incr ht [expr {($numcntls-1)*$normalLine}]
518                    # active control
519                    incr ht $boldLine
520                    # dial for active control
521                    incr ht [winfo reqheight $itk_component(dials).dial]
522                    # padding around active control
523                    incr ht 4
524                }
525            }
526            return $ht
527        }
528        default {
529            error "bad option \"$what\": should be -results, -controls, or -controlarea"
530        }
531    }
532}
533
534# ----------------------------------------------------------------------
535# USAGE: _doClear
536#
537# Invoked automatically when the user presses the Clear button.
538# Invokes the -clearcommand to clear all data from this resultset
539# and all other resultsets in an Analyzer.
540# ----------------------------------------------------------------------
541itcl::body Rappture::ResultSet::_doClear {} {
542    if {[string length $itk_option(-clearcommand)] > 0} {
543        uplevel #0 $itk_option(-clearcommand)
544    }
545}
546
547# ----------------------------------------------------------------------
548# USAGE: _doSettings ?<command>?
549#
550# Used internally whenever the result selection changes to invoke
551# the -settingscommand.  This will notify some external widget, which
552# with perform the plotting action specified in the <command>.
553# ----------------------------------------------------------------------
554itcl::body Rappture::ResultSet::_doSettings {{cmd ""}} {
555    if {[string length $itk_option(-settingscommand)] > 0} {
556        uplevel #0 $itk_option(-settingscommand) $cmd
557    }
558}
559
560# ----------------------------------------------------------------------
561# USAGE: _doPrompt <state>
562#
563# Used internally whenever the current settings represent a point
564# with no data.  Invokes the -promptcommand with an explanation of
565# the missing data, prompting the user to simulate it.
566# ----------------------------------------------------------------------
567itcl::body Rappture::ResultSet::_doPrompt {state} {
568    if {[string length $itk_option(-promptcommand)] > 0} {
569        if {$state} {
570            set message "No data for these settings"
571            set settings ""
572            foreach col [lrange [$_results column names] 1 end] {
573                set val $_cntlInfo($this-$col-value)
574                lappend settings $col $val
575            }
576            uplevel #0 $itk_option(-promptcommand) [list on $message $settings]
577        } else {
578            uplevel #0 $itk_option(-promptcommand) off
579        }
580    }
581}
582
583# ----------------------------------------------------------------------
584# USAGE: _control bind <widget> <column>
585# USAGE: _control hilite <state> <column> <panel>
586# USAGE: _control load <widget> <column>
587#
588# Used internally to manage the interactivity of controls.  The "bind"
589# operation sets up bindings on the label/value for each control, so
590# you can mouse over and click on a control to activate it.  The
591# "hilite" operation controls highlighting of the control.  The "load"
592# operation loads data into the specified radiodial <widget>.
593# ----------------------------------------------------------------------
594itcl::body Rappture::ResultSet::_control {option args} {
595    switch -- $option {
596        bind {
597            if {[llength $args] != 2} {
598                error "wrong # args: should be _control bind widget column"
599            }
600            set widget [lindex $args 0]
601            set col [lindex $args 1]
602
603            set panel [winfo parent $widget]
604            if {[string match label* [winfo name $panel]]} {
605                set panel [winfo parent $panel]
606            }
607
608            bind $widget <Enter> \
609                [itcl::code $this _control hilite on $col $panel]
610            bind $widget <Leave> \
611                [itcl::code $this _control hilite off $col $panel]
612            bind $widget <ButtonRelease> [itcl::code $this activate $col]
613        }
614        hilite {
615            if {[llength $args] != 3} {
616                error "wrong # args: should be _control hilite state column panel"
617            }
618            if {$_layout(mode) != "usual"} {
619                # abbreviated controls? then skip highlighting
620                return
621            }
622            set state [lindex $args 0]
623            set col [lindex $args 1]
624            set panel [lindex $args 2]
625
626            if {[string index $col 0] == "@"} {
627                # handle artificial names like "@more"
628                set id [string range $col 1 end]
629            } else {
630                # get id for ordinary columns
631                set id $_cntlInfo($this-$col-id)
632            }
633
634            # highlight any non-active entries
635            if {$col != $_active} {
636                if {$state} {
637                    set fg $itk_option(-controlactiveforeground)
638                    $panel.label$id.name configure -fg $fg
639                    $panel.label$id.value configure -fg $fg
640                    $panel.label$id.arrow configure -fg $fg \
641                        -bitmap [Rappture::icon rarrow2]
642                } else {
643                    set fg $itk_option(-foreground)
644                    $panel.label$id.name configure -fg $fg
645                    $panel.label$id.value configure -fg $fg
646                    $panel.label$id.arrow configure -fg $fg \
647                        -bitmap [Rappture::icon empty]
648                }
649            }
650        }
651        load {
652            if {[llength $args] != 2} {
653                error "wrong # args: should be _control load widget column"
654            }
655            set dial [lindex $args 0]
656            set col [lindex $args 1]
657
658            $dial clear
659            foreach {label val} [_getValues $col all] {
660                $dial add $label $val
661            }
662        }
663        default {
664            error "bad option \"$option\": should be bind, hilite, or load"
665        }
666    }
667}
668
669# ----------------------------------------------------------------------
670# USAGE: _fixControls ?<eventArgs...>?
671#
672# Called automatically at the idle point after one or more results
673# have been added to this result set.  Scans through all existing
674# data and updates controls used to select the data.
675# ----------------------------------------------------------------------
676itcl::body Rappture::ResultSet::_fixControls {args} {
677    if {[$_results size] == 0} {
678        return
679    }
680
681    set popup [$itk_component(options) contents frame]
682    grid columnconfigure $popup 0 -minsize 16
683    grid columnconfigure $popup 1 -weight 1
684
685    set shortlist $itk_component(dials)
686    grid columnconfigure $shortlist 1 -weight 1
687
688    #
689    # Scan through all columns in the data and create any
690    # controls that just appeared.
691    #
692    $shortlist.dial configure -variable ""
693
694    set nadded 0
695    foreach col [$_results column names] {
696        set xmlobj [$_results get -format xmlobj 0]
697
698        #
699        # If this column doesn't have a control yet, then
700        # create one.
701        #
702        if {![info exists _cntlInfo($this-$col-id)]} {
703            set row [lindex [grid size $popup] 1]
704            set row2 [expr {$row+1}]
705
706            set tip ""
707            if {$col == "xmlobj"} {
708                set quantity "Simulation"
709                set tip "List of all simulations that you have performed so far."
710            } else {
711                # search for the first XML object with this element defined
712                foreach xmlobj [$_results get -format xmlobj] {
713                    set quantity [$xmlobj get $col.about.label]
714                    set tip [$xmlobj get $col.about.description]
715                    if {"" != $quantity} {
716                        break
717                    }
718                }
719                if {"" == $quantity && "" != $xmlobj} {
720                    set quantity [$xmlobj element -as id $col]
721                }
722            }
723
724            #
725            # Build the main control in the pop-up panel.
726            #
727            set fn $itk_option(-textfont)
728            set w $popup.label$_counter
729            frame $w
730            grid $w -row $row -column 2 -sticky ew -padx 4 -pady {4 0}
731            label $w.arrow -bitmap [Rappture::icon empty] -borderwidth 0
732            pack $w.arrow -side left -fill y
733            _control bind $w.arrow $col
734
735            label $w.name -text $quantity -anchor w \
736                -borderwidth 0 -padx 0 -pady 1 -font $fn
737            pack $w.name -side left
738            bind $w.name <Configure> [itcl::code $this _fixValue $col resize]
739            _control bind $w.name $col
740
741            label $w.value -anchor w \
742                -borderwidth 0 -padx 0 -pady 1 -font $fn
743            pack $w.value -side left
744            bind $w.value <Configure> [itcl::code $this _fixValue $col resize]
745            _control bind $w.value $col
746
747            Rappture::Tooltip::for $w \
748                "@[itcl::code $this _getTooltip label $col]"
749
750            set w $popup.dial$_counter
751            Rappture::Radiodial $w -valuewidth 0
752            grid $w -row $row2 -column 2 -sticky ew -padx 4 -pady {0 4}
753            $w configure -variable ::Rappture::ResultSet::_cntlInfo($this-$col-value)
754            Rappture::Tooltip::for $w \
755                "@[itcl::code $this _getTooltip dial $col]"
756
757            set w $popup.all$_counter
758            label $w -text "All" -padx 8 \
759                -borderwidth 1 -relief raised -font $fn
760            grid $w -row $row -rowspan 2 -column 1 -sticky nsew -padx 2 -pady 4
761            Rappture::Tooltip::for $w \
762                "@[itcl::code $this _getTooltip all $col]"
763            bind $w <ButtonRelease> [itcl::code $this _toggleAll $col]
764
765            # Create the controls for the "short list" area.
766            set w $shortlist.label$_counter
767            frame $w
768            grid $w -row $row -column 1 -sticky ew
769            label $w.arrow -bitmap [Rappture::icon empty] -borderwidth 0
770            pack $w.arrow -side left -fill y
771            _control bind $w.arrow $col
772
773            label $w.name -text $quantity -anchor w \
774                -borderwidth 0 -padx 0 -pady 1 -font $fn
775            pack $w.name -side left
776            bind $w.name <Configure> [itcl::code $this _fixValue $col resize]
777            _control bind $w.name $col
778
779            label $w.value -anchor w \
780                -borderwidth 0 -padx 0 -pady 1 -font $fn
781            pack $w.value -side left
782            bind $w.value <Configure> [itcl::code $this _fixValue $col resize]
783            _control bind $w.value $col
784
785            Rappture::Tooltip::for $w \
786                "@[itcl::code $this _getTooltip label $col]"
787
788            # if this is the "Simulation #" control, add a separator
789            if {$col == "xmlobj"} {
790                grid $popup.all$_counter -column 0
791                grid $popup.label$_counter -column 1 -columnspan 2
792                grid $popup.dial$_counter -column 1 -columnspan 2
793
794                if {![winfo exists $popup.sep]} {
795                    frame $popup.sep -height 1 -borderwidth 0 -background black
796                }
797                grid $popup.sep -row [expr {$row+2}] -column 0 \
798                    -columnspan 3 -sticky ew -pady 4
799
800                if {![winfo exists $popup.paraml]} {
801                    label $popup.paraml -text "Parameters:" -font $fn
802                }
803                grid $popup.paraml -row [expr {$row+3}] -column 0 \
804                    -columnspan 3 -sticky w -padx 4 -pady {0 4}
805            }
806
807            # create a record for this control
808            lappend _cntlInfo($this-all) $col
809            set _cntlInfo($this-$col-id) $_counter
810            set _cntlInfo($this-$col-label) $quantity
811            set _cntlInfo($this-$col-tip) $tip
812            set _cntlInfo($this-$col-value) ""
813            set _cntlInfo($this-$col-usage) 0
814            set _cntlInfo($this-$col) ""
815
816            trace add variable _cntlInfo($this-$col-value) write \
817                "[itcl::code $this _fixValue $col value]; list"
818
819            incr _counter
820
821            # fix the shortlist layout to show as many controls as we can
822            $_dispatcher event -now !layout why data
823
824            # let clients know that a new control appeared
825            # so they can fix the overall size accordingly
826            event generate $itk_component(hull) <<Control>>
827
828            incr nadded
829        }
830
831        #
832        # Determine the unique values for this column and load
833        # them into the control.
834        #
835        set id $_cntlInfo($this-$col-id)
836        set popup [$itk_component(options) contents frame]
837        set dial $popup.dial$id
838
839        _control load $popup.dial$id $col
840
841        if {$col == $_layout(active)} {
842            _control load $shortlist.dial $col
843            $shortlist.dial configure -variable \
844                "::Rappture::ResultSet::_cntlInfo($this-$col-value)"
845        }
846    }
847
848    #
849    # Activate the most recent control.  If a bunch of controls
850    # were just added, then activate the "Simulation" control,
851    # since that's the easiest way to page through results.
852    #
853    if {$nadded > 0} {
854        if {[$_results column names] == 2 || $nadded == 1} {
855            activate [lindex $_cntlInfo($this-all) end]
856        } else {
857            activate xmlobj
858        }
859    }
860
861    #
862    # Set all controls to the settings of the most recent addition.
863    # Setting the value slot will trigger the !settings event, which
864    # will then fix all other controls to match the one that changed.
865    #
866    if {"" != $_recent} {
867        set raw [lindex [$_results find -format xmlobj $_recent] 0]
868        set raw "#[expr {$raw+1}]"
869        set _cntlInfo($this-xmlobj-value) $raw
870    }
871}
872
873# ----------------------------------------------------------------------
874# USAGE: _fixLayout ?<eventArgs...>?
875#
876# Called automatically at the idle point after the controls have
877# changed, or the size of the window has changed.  Fixes the layout
878# so that the active control is displayed, and other recent controls
879# are shown above and/or below.  At the very least, we must show the
880# "more options..." control, which pops up a panel of all controls.
881# ----------------------------------------------------------------------
882itcl::body Rappture::ResultSet::_fixLayout {args} {
883    array set eventdata $args
884
885    set popup [$itk_component(options) contents frame]
886    set shortlist $itk_component(dials)
887
888    # clear out the short list area
889    foreach w [grid slaves $shortlist] {
890        grid forget $w
891    }
892
893    # reset all labels back to an ordinary font/background
894    set fn $itk_option(-textfont)
895    set bg $itk_option(-background)
896    set fg $itk_option(-foreground)
897    foreach col $_cntlInfo($this-all) {
898        set id $_cntlInfo($this-$col-id)
899        $popup.label$id configure -background $bg
900        $popup.label$id.arrow configure -background $bg \
901            -bitmap [Rappture::icon empty]
902        $popup.label$id.name configure -font $fn -background $bg
903        $popup.label$id.value configure -background $bg
904        $popup.all$id configure -background $bg -foreground $fg \
905            -relief raised
906        $popup.dial$id configure -background $bg
907        $shortlist.label$id configure -background $bg
908        $shortlist.label$id.arrow configure -background $bg \
909            -bitmap [Rappture::icon empty]
910        $shortlist.label$id.name configure -font $fn -background $bg
911        $shortlist.label$id.value configure -background $bg
912    }
913
914    # only 1 result? then we don't need any controls
915    if {[$_results size] < 2} {
916        return
917    }
918
919    # compute the number of controls that will fit in the shortlist area
920    set dials $itk_component(dials)
921    set h [winfo height $dials]
922    set normalLine [font metrics $itk_option(-textfont) -linespace]
923    set boldLine [font metrics $itk_option(-boldfont) -linespace]
924    set active [expr {$boldLine+[winfo reqheight $dials.dial]+4}]
925
926    if {$h < $active+$normalLine} {
927        # active control kinda big? then show parameter values only
928        set _layout(mode) abbreviated
929        set ncntls [expr {int(floor(double($h)/$normalLine))}]
930    } else {
931        set _layout(mode) usual
932        set ncntls [expr {int(floor(double($h-$active)/$normalLine))+1}]
933    }
934
935    # find the controls with the most usage
936    set order ""
937    foreach col $_cntlInfo($this-all) {
938        lappend order [list $col $_cntlInfo($this-$col-usage)]
939    }
940    set order [lsort -integer -decreasing -index 1 $order]
941
942    set mostUsed ""
943    if {[llength $order] <= $ncntls} {
944        # plenty of space? then show all controls
945        foreach item $order {
946            lappend mostUsed [lindex $item 0]
947        }
948    } else {
949        # otherwise, limit to the most-used controls
950        foreach item [lrange $order 0 [expr {$ncntls-1}]] {
951            lappend mostUsed [lindex $item 0]
952        }
953
954        # make sure the active control is included
955        if {"" != $_active && [lsearch -exact $mostUsed $_active] < 0} {
956            set mostUsed [lreplace [linsert $mostUsed 0 $_active] end end]
957        }
958
959        # if there are more controls, add the "more parameters..." entry
960        if {$ncntls > 2} {
961            set mostUsed [lreplace $mostUsed end end @more]
962            set rest [expr {[llength $order]-($ncntls-1)}]
963            if {$rest == 1} {
964                $dials.labelmore.name configure -text "1 more parameter..."
965            } else {
966                $dials.labelmore.name configure -text "$rest more parameters..."
967            }
968        }
969    }
970
971    # draw the active control
972    set row 0
973    foreach col [concat $_cntlInfo($this-all) @more] {
974        # this control not on the short list? then ignore it
975        if {[lsearch $mostUsed $col] < 0} {
976            continue
977        }
978
979        if {[string index $col 0] == "@"} {
980            set id [string range $col 1 end]
981        } else {
982            set id $_cntlInfo($this-$col-id)
983        }
984        grid $shortlist.label$id -row $row -column 1 -sticky ew -padx 4
985
986        if {$col == $_active} {
987            # put the background behind the active control in the popup
988            set id $_cntlInfo($this-$_active-id)
989            array set ginfo [grid info $popup.label$id]
990            grid $popup.bg -row $ginfo(-row) -rowspan 2 \
991                -column 0 -columnspan 3 -sticky nsew
992            lower $popup.bg
993
994            if {$_layout(mode) == "usual"} {
995                # put the background behind the active control in the shortlist
996                grid $shortlist.bg -row $row -rowspan 2 \
997                    -column 0 -columnspan 2 -sticky nsew
998                lower $shortlist.bg
999
1000                # place the All and dial in the shortlist area
1001                grid $shortlist.all -row $row -rowspan 2 -column 0 \
1002                    -sticky nsew -padx 2 -pady 2
1003                grid $shortlist.dial -row [expr {$row+1}] -column 1 \
1004                    -sticky ew -padx 4
1005                incr row
1006
1007                if {$_layout(active) != $_active} {
1008                    $shortlist.dial configure -variable ""
1009                    _control load $shortlist.dial $col
1010                    $shortlist.dial configure -variable \
1011                        "::Rappture::ResultSet::_cntlInfo($this-$col-value)"
1012                    set _layout(active) $_active
1013                }
1014            }
1015        }
1016        incr row
1017    }
1018
1019    # highlight the active control
1020    if {[info exists _cntlInfo($this-$_active-id)]} {
1021        set id $_cntlInfo($this-$_active-id)
1022        set bf $itk_option(-boldfont)
1023        set fg $itk_option(-activecontrolforeground)
1024        set bg $itk_option(-activecontrolbackground)
1025
1026        $popup.label$id configure -background $bg
1027        $popup.label$id.arrow configure -foreground $fg -background $bg \
1028            -bitmap [Rappture::icon rarrow]
1029        $popup.label$id.name configure -foreground $fg -background $bg \
1030            -font $bf
1031        $popup.label$id.value configure -foreground $fg -background $bg
1032        $popup.dial$id configure -background $bg
1033        $popup.bg configure -background $bg
1034
1035        if {$_plotall} {
1036            $popup.all$id configure -relief sunken \
1037                -background $itk_option(-togglebackground) \
1038                -foreground $itk_option(-toggleforeground)
1039        } else {
1040            $popup.all$id configure -relief raised \
1041                -background $itk_option(-activecontrolbackground) \
1042                -foreground $itk_option(-activecontrolforeground)
1043        }
1044
1045        if {$_layout(mode) == "usual"} {
1046            $shortlist.label$id configure -background $bg
1047            $shortlist.label$id.arrow configure -foreground $fg \
1048                -background $bg -bitmap [Rappture::icon rarrow]
1049            $shortlist.label$id.name configure -foreground $fg \
1050                -background $bg -font $bf
1051            $shortlist.label$id.value configure -foreground $fg \
1052                -background $bg
1053            $shortlist.dial configure -background $bg
1054            $shortlist.bg configure -background $bg
1055
1056            if {[$shortlist.all cget -relief] == "raised"} {
1057                $shortlist.all configure -foreground $fg -background $bg
1058            }
1059        }
1060    }
1061}
1062
1063# ----------------------------------------------------------------------
1064# USAGE: _fixSettings ?<eventArgs...>?
1065#
1066# Called automatically at the idle point after a control has changed
1067# to load new data into the plotting area at the top of this result
1068# set.  Extracts the current tuple of control values from the control
1069# area, then finds the corresponding data values.  Loads the data
1070# by invoking a -settingscommand callback with parameters that
1071# describe what data should be plotted.
1072# ----------------------------------------------------------------------
1073itcl::body Rappture::ResultSet::_fixSettings {args} {
1074    array set eventdata $args
1075    if {[info exists eventdata(column)]} {
1076        set changed $eventdata(column)
1077    } else {
1078        set changed ""
1079    }
1080    _doPrompt off
1081
1082    if {[info exists _cntlInfo($this-$_active-label)]} {
1083        lappend params $_cntlInfo($this-$_active-label)
1084    } else {
1085        lappend params "???"
1086    }
1087    eval lappend params [_getValues $_active all]
1088
1089    switch -- [$_results size] {
1090        0 {
1091            # no data? then do nothing
1092            return
1093        }
1094        1 {
1095            # only one data set? then plot it
1096            _doSettings [list \
1097                0 [list -width 2 \
1098                        -param [_getValues $_active current] \
1099                        -description [_getParamDesc all] \
1100                  ] \
1101                params $params \
1102            ]
1103            return
1104        }
1105    }
1106
1107    #
1108    # Find the selected run.  If the run setting changed, then
1109    # look at its current value.  Otherwise, search the results
1110    # for a tuple that matches the current settings.
1111    #
1112    if {$changed == "xmlobj"} {
1113        # value is "#2" -- skip # and adjust range starting from 0
1114        set irun [string range $_cntlInfo($this-xmlobj-value) 1 end]
1115        if {"" != $irun} { set irun [expr {$irun-1}] }
1116    } else {
1117        set format ""
1118        set tuple ""
1119        foreach col [lrange [$_results column names] 1 end] {
1120            lappend format $col
1121            lappend tuple $_cntlInfo($this-$col-value)
1122        }
1123        set irun [lindex [$_results find -format $format -- $tuple] 0]
1124
1125        if {"" == $irun && "" != $changed
1126             && $itk_option(-missingdata) == "skip"} {
1127            #
1128            # No data for these settings.  Try leaving the next
1129            # column open, then the next, and so forth, until
1130            # we find some data.
1131            #
1132            # allcols:  foo bar baz qux
1133            #               ^^^changed
1134            #
1135            # search:   baz qux foo
1136            #
1137            set val $_cntlInfo($this-$changed-value)
1138            set allcols [lrange [$_results column names] 1 end]
1139            set i [lsearch -exact $allcols $changed]
1140            set search [concat \
1141                [lrange $allcols [expr {$i+1}] end] \
1142                [lrange $allcols 0 [expr {$i-1}]] \
1143            ]
1144            set nsearch [llength $search]
1145
1146            for {set i 0} {$i < $nsearch} {incr i} {
1147                set format $changed
1148                set tuple [list $val]
1149                for {set j [expr {$i+1}]} {$j < $nsearch} {incr j} {
1150                    set col [lindex $search $j]
1151                    lappend format $col
1152                    lappend tuple $_cntlInfo($this-$col-value)
1153                }
1154                set irun [lindex [$_results find -format $format -- $tuple] 0]
1155                if {"" != $irun} {
1156                    break
1157                }
1158            }
1159        }
1160    }
1161
1162    #
1163    # If we found a particular run, then load its values into all
1164    # controls.
1165    #
1166    if {"" != $irun} {
1167        # stop reacting to value changes
1168        set _settings 1
1169
1170        set format [lrange [$_results column names] 1 end]
1171        if {[llength $format] == 1} {
1172            set data [$_results get -format $format $irun]
1173        } else {
1174            set data [lindex [$_results get -format $format $irun] 0]
1175        }
1176
1177        foreach col $format val $data {
1178            set _cntlInfo($this-$col-value) $val
1179        }
1180        set _cntlInfo($this-xmlobj-value) "#[expr {$irun+1}]"
1181
1182        # okay, react to value changes again
1183        set _settings 0
1184    }
1185
1186    #
1187    # Search for tuples matching the current setting and
1188    # plot them.
1189    #
1190    if {$_plotall && $_active == "xmlobj"} {
1191        set format ""
1192    } else {
1193        set format ""
1194        set tuple ""
1195        foreach col [lrange [$_results column names] 1 end] {
1196            if {!$_plotall || $col != $_active} {
1197                lappend format $col
1198                lappend tuple $_cntlInfo($this-$col-value)
1199            }
1200        }
1201    }
1202
1203    if {"" != $format} {
1204        set ilist [$_results find -format $format -- $tuple]
1205    } else {
1206        set ilist [$_results find]
1207    }
1208
1209    if {[llength $ilist] > 0} {
1210        # search for the result for these settings
1211        set format ""
1212        set tuple ""
1213        foreach col [lrange [$_results column names] 1 end] {
1214            lappend format $col
1215            lappend tuple $_cntlInfo($this-$col-value)
1216        }
1217        set icurr [$_results find -format $format -- $tuple]
1218
1219        # no data for these settings? prompt the user to simulate
1220        if {"" == $icurr} {
1221            _doPrompt on
1222        }
1223
1224        if {[llength $ilist] == 1} {
1225            # single result -- always use active color
1226            set i [lindex $ilist 0]
1227            set plist [list \
1228                $i [list -width 2 \
1229                         -param [_getValues $_active $i] \
1230                         -description [_getParamDesc all $i] \
1231                   ] \
1232                params $params \
1233            ]
1234        } else {
1235            #
1236            # Get the color for all points according to
1237            # the color spectrum.
1238            #
1239            set plist [list params $params]
1240            foreach i $ilist {
1241                if {$i == $icurr} {
1242                    lappend plist $i [list -width 3 -raise 1 \
1243                        -param [_getValues $_active $i] \
1244                        -description [_getParamDesc all $i]]
1245                } else {
1246                    lappend plist $i [list -brightness 0.7 -width 1 \
1247                        -param [_getValues $_active $i] \
1248                        -description [_getParamDesc all $i]]
1249                }
1250            }
1251        }
1252
1253        #
1254        # Load up the matching plots
1255        #
1256        _doSettings $plist
1257
1258    } elseif {$itk_option(-missingdata) == "prompt"} {
1259        # prompt the user to simulate these settings
1260        _doPrompt on
1261        _doSettings  ;# clear plotting area
1262
1263        # clear the current run selection -- there is no run for this
1264        set _settings 1
1265        set _cntlInfo($this-xmlobj-value) ""
1266        set _settings 0
1267    }
1268}
1269
1270# ----------------------------------------------------------------------
1271# USAGE: _fixExplore
1272#
1273# Called automatically whenever the user toggles the "Explore" button
1274# on the parameter popup.  Changes the -missingdata option back and
1275# forth, to allow for missing data or skip it.
1276# ----------------------------------------------------------------------
1277itcl::body Rappture::ResultSet::_fixExplore {} {
1278    if {$_explore} {
1279        configure -missingdata prompt
1280    } else {
1281        configure -missingdata skip
1282    }
1283}
1284
1285# ----------------------------------------------------------------------
1286# USAGE: _fixValue <columnName> <why>
1287#
1288# Called automatically whenver a value for a parameter dial changes.
1289# Updates the interface to display the new value.  The <why> is a
1290# reason for the change, which may be "resize" (draw old value in
1291# new size) or "value" (value changed).
1292# ----------------------------------------------------------------------
1293itcl::body Rappture::ResultSet::_fixValue {col why} {
1294    if {[info exists _cntlInfo($this-$col-id)]} {
1295        set id $_cntlInfo($this-$col-id)
1296
1297        set popup [$itk_component(options) contents frame]
1298        set widget $popup.label$id
1299        set wmax [winfo width $popup.dial$id]
1300        _drawValue $col $widget $wmax
1301
1302        set widget $itk_component(dials).label$id
1303        set wmax [winfo width $itk_component(dials).dial]
1304        if {$wmax <= 1} {
1305            set wmax [expr {round(0.9*[winfo width $itk_component(cntls)])}]
1306        }
1307        _drawValue $col $widget $wmax
1308
1309        if {$why == "value" && !$_settings} {
1310            # keep track of usage, so we know which controls are popular
1311            incr _cntlInfo($this-$col-usage)
1312
1313            # adjust the settings according to the value in the column
1314            $_dispatcher event -idle !settings column $col
1315        }
1316    }
1317}
1318
1319# ----------------------------------------------------------------------
1320# USAGE: _drawValue <columnName> <widget> <widthMax>
1321#
1322# Used internally to fix the rendering of a "quantity = value" display.
1323# If the name/value in <widget> are smaller than <widthMax>, then the
1324# full "quantity = value" string is displayed.  Otherwise, an
1325# abbreviated form is displayed.
1326# ----------------------------------------------------------------------
1327itcl::body Rappture::ResultSet::_drawValue {col widget wmax} {
1328    set quantity $_cntlInfo($this-$col-label)
1329    regsub -all {\n} $quantity " " quantity  ;# take out newlines
1330
1331    set newval $_cntlInfo($this-$col-value)
1332    regsub -all {\n} $newval " " newval  ;# take out newlines
1333
1334    set lfont [$widget.name cget -font]
1335    set vfont [$widget.value cget -font]
1336
1337    set wn [font measure $lfont $quantity]
1338    set wv [font measure $lfont " = $newval"]
1339    set w [expr {$wn + $wv}]
1340
1341    if {$w <= $wmax} {
1342        # if the text fits, then shown "quantity = value"
1343        $widget.name configure -text $quantity
1344        $widget.value configure -text " = $newval"
1345    } else {
1346        # Otherwise, we'll have to appreviate.
1347        # If the value is really long, then just show a little bit
1348        # of it.  Otherwise, show as much of the value as we can.
1349        if {[string length $newval] > 30} {
1350            set frac 0.8
1351        } else {
1352            set frac 0.2
1353        }
1354        set wNameSpace [expr {round($frac*$wmax)}]
1355        set wValueSpace [expr {$wmax-$wNameSpace}]
1356
1357        # fit as much of the "quantity" label in the space available
1358        if {$wn < $wNameSpace} {
1359            $widget.name configure -text $quantity
1360            set wValueSpace [expr {$wmax-$wn}]
1361        } else {
1362            set wDots [font measure $lfont "..."]
1363            set wchar [expr {double($wn)/[string length $quantity]}]
1364            while {1} {
1365                # figure out a good size for the abbreviated string
1366                set cmax [expr {round(($wNameSpace-$wDots)/$wchar)}]
1367                if {$cmax < 0} {set cmax 0}
1368                set str "[string range $quantity 0 $cmax]..."
1369                if {[font measure $lfont $str] <= $wNameSpace
1370                      || $wDots >= $wNameSpace} {
1371                    break
1372                }
1373                # we're measuring with average chars, so we may have
1374                # to shave a little off and do this again
1375                set wDots [expr {$wDots+2*$wchar}]
1376            }
1377            $widget.name configure -text $str
1378            set wValueSpace [expr {$wmax-[font measure $lfont $str]}]
1379        }
1380
1381        if {$wv < $wValueSpace} {
1382            $widget.value configure -text " = $newval"
1383        } else {
1384            set wDots [font measure $vfont "..."]
1385            set wEq [font measure $vfont " = "]
1386            set wchar [expr {double($wv)/[string length " = $newval"]}]
1387            while {1} {
1388                # figure out a good size for the abbreviated string
1389                set cmax [expr {round(($wValueSpace-$wDots-$wEq)/$wchar)}]
1390                if {$cmax < 0} {set cmax 0}
1391                set str " = [string range $newval 0 $cmax]..."
1392                if {[font measure $vfont $str] <= $wValueSpace
1393                      || $wDots >= $wValueSpace} {
1394                    break
1395                }
1396                # we're measuring with average chars, so we may have
1397                # to shave a little off and do this again
1398                set wDots [expr {$wDots+2*$wchar}]
1399            }
1400            $widget.value configure -text $str
1401        }
1402    }
1403}
1404
1405# ----------------------------------------------------------------------
1406# USAGE: _toggleAll ?<columnName>?
1407#
1408# Called automatically whenever the user clicks on an "All" button.
1409# Toggles the button between its on/off states.  In the "on" state,
1410# all results associated with the current control are sent to the
1411# result viewer.
1412# ----------------------------------------------------------------------
1413itcl::body Rappture::ResultSet::_toggleAll {{col "current"}} {
1414    if {$col == "current"} {
1415        set col $_active
1416    }
1417    if {![info exists _cntlInfo($this-$col-id)]} {
1418        return
1419    }
1420    set id $_cntlInfo($this-$col-id)
1421    set popup [$itk_component(options) contents frame]
1422    set pbutton $popup.all$id
1423    set current [$pbutton cget -relief]
1424    set sbutton $itk_component(dials).all
1425
1426    foreach c $_cntlInfo($this-all) {
1427        set id $_cntlInfo($this-$c-id)
1428        $popup.all$id configure -relief raised \
1429            -background $itk_option(-background) \
1430            -foreground $itk_option(-foreground)
1431    }
1432
1433    if {$current == "sunken"} {
1434        $pbutton configure -relief raised \
1435            -background $itk_option(-activecontrolbackground) \
1436            -foreground $itk_option(-activecontrolforeground)
1437        $sbutton configure -relief raised \
1438            -background $itk_option(-activecontrolbackground) \
1439            -foreground $itk_option(-activecontrolforeground)
1440        set _plotall 0
1441    } else {
1442        $pbutton configure -relief sunken \
1443            -background $itk_option(-togglebackground) \
1444            -foreground $itk_option(-toggleforeground)
1445        $sbutton configure -relief sunken \
1446            -background $itk_option(-togglebackground) \
1447            -foreground $itk_option(-toggleforeground)
1448        set _plotall 1
1449
1450        if {$col != $_active} {
1451            # clicked on an inactive "All" button? then activate that column
1452            activate $col
1453        }
1454    }
1455    $_dispatcher event -idle !settings
1456}
1457
1458# ----------------------------------------------------------------------
1459# USAGE: _getValues <column> ?<which>?
1460#
1461# Called automatically whenever the user hovers a control within
1462# this widget.  Returns the tooltip associated with the control.
1463# ----------------------------------------------------------------------
1464itcl::body Rappture::ResultSet::_getValues {col {which ""}} {
1465    if {$col == "xmlobj"} {
1466        # load the Simulation # control
1467        set nruns [$_results size]
1468        for {set n 0} {$n < $nruns} {incr n} {
1469            set v "#[expr {$n+1}]"
1470            set label2val($v) $n
1471        }
1472    } else {
1473        set havenums 1
1474        set vlist ""
1475        foreach rec [$_results get -format [list xmlobj $col]] {
1476            set xo [lindex $rec 0]
1477            set v [lindex $rec 1]
1478
1479            if {![info exists label2val($v)]} {
1480                lappend vlist $v
1481                foreach {raw norm} [Rappture::LibraryObj::value $xo $col] break
1482                set label2val($v) $norm
1483
1484                if {$havenums && ![string is double $norm]} {
1485                    set havenums 0
1486                }
1487            }
1488        }
1489
1490        if {!$havenums} {
1491            # don't have normalized nums? then sort and create nums
1492            catch {unset label2val}
1493
1494            set n 0
1495            foreach v [lsort $vlist] {
1496                incr n
1497                set label2val($v) $n
1498            }
1499        }
1500    }
1501
1502    switch -- $which {
1503        current {
1504            set curr $_cntlInfo($this-$col-value)
1505            if {[info exists label2val($curr)]} {
1506                return [list $curr $label2val($curr)]
1507            }
1508            return ""
1509        }
1510        all {
1511            return [array get label2val]
1512        }
1513        default {
1514            if {[string is integer $which]} {
1515                if {$col == "xmlobj"} {
1516                    set val "#[expr {$which+1}]"
1517                } else {
1518                    set val [lindex [$_results get -format $col $which] 0]
1519                }
1520                if {[info exists label2val($val)]} {
1521                    return [list $val $label2val($val)]
1522                }
1523                return ""
1524            }
1525            error "bad option \"$which\": should be all, current, or an integer index"
1526        }
1527    }
1528}
1529
1530# ----------------------------------------------------------------------
1531# USAGE: _getTooltip <role> <column>
1532#
1533# Called automatically whenever the user hovers a control within
1534# this widget.  Returns the tooltip associated with the control.
1535# ----------------------------------------------------------------------
1536itcl::body Rappture::ResultSet::_getTooltip {role column} {
1537    set label ""
1538    set tip ""
1539    if {$column == "active"} {
1540        set column $_active
1541    }
1542    if {[info exists _cntlInfo($this-$column-label)]} {
1543        set label $_cntlInfo($this-$column-label)
1544    }
1545    if {[info exists _cntlInfo($this-$column-tip)]} {
1546        set tip $_cntlInfo($this-$column-tip)
1547    }
1548
1549    switch -- $role {
1550        label {
1551            if {$column != $_active} {
1552                append tip "\n\nClick to activate this control."
1553            }
1554        }
1555        dial {
1556            append tip "\n\nClick to change the value of this parameter."
1557        }
1558        all {
1559            if {$label == ""} {
1560                set tip "Plot all values for this quantity."
1561            } else {
1562                set tip "Plot all values for $label."
1563            }
1564            if {$_plotall} {
1565                set what "all values"
1566            } else {
1567                set what "one value"
1568            }
1569            append tip "\n\nCurrently, plotting $what.  Click to toggle."
1570        }
1571        more {
1572            set tip "Click to access all parameters."
1573        }
1574    }
1575    return [string trim $tip]
1576}
1577
1578# ----------------------------------------------------------------------
1579# USAGE: _getParamDesc <which> ?<index>?
1580#
1581# Used internally to build a descripton of parameters for the data
1582# tuple at the specified <index>.  This is passed on to the underlying
1583# results viewer, so it will know what data is being viewed.
1584# ----------------------------------------------------------------------
1585itcl::body Rappture::ResultSet::_getParamDesc {which {index "current"}} {
1586    if {$index == "current"} {
1587        # search for the result for these settings
1588        set format ""
1589        set tuple ""
1590        foreach col [lrange [$_results column names] 1 end] {
1591            lappend format $col
1592            lappend tuple $_cntlInfo($this-$col-value)
1593        }
1594        set index [$_results find -format $format -- $tuple]
1595        if {"" == $index} {
1596            return ""  ;# somethings wrong -- bail out!
1597        }
1598    }
1599
1600    switch -- $which {
1601        active {
1602            if {"" == $_active} {
1603                return ""
1604            }
1605        }
1606        all {
1607            set desc ""
1608            foreach col $_cntlInfo($this-all) {
1609                set quantity $_cntlInfo($this-$col-label)
1610                set val [lindex [$_results get -format $col $index] 0]
1611                if {$col == "xmlobj"} {
1612                    set num [lindex [$_results find -format xmlobj $val] 0]
1613                    set val "#[expr {$num+1}]"
1614                }
1615                append desc "$quantity = $val\n"
1616            }
1617            return [string trim $desc]
1618        }
1619        default {
1620            error "bad value \"$which\": should be active or all"
1621        }
1622    }
1623}
1624
1625# ----------------------------------------------------------------------
1626# OPTION: -missingdata
1627# ----------------------------------------------------------------------
1628itcl::configbody Rappture::ResultSet::missingdata {
1629    set opts {prompt skip}
1630    if {[lsearch -exact $opts $itk_option(-missingdata)] < 0} {
1631        error "bad value \"$itk_option(-missingdata)\": should be [join $opts {, }]"
1632    }
1633    set _explore [expr {$itk_option(-missingdata) != "skip"}]
1634}
1635
1636# ----------------------------------------------------------------------
1637# OPTION: -activecontrolbackground
1638# ----------------------------------------------------------------------
1639itcl::configbody Rappture::ResultSet::activecontrolbackground {
1640    $_dispatcher event -idle !layout
1641}
1642
1643# ----------------------------------------------------------------------
1644# OPTION: -activecontrolforeground
1645# ----------------------------------------------------------------------
1646itcl::configbody Rappture::ResultSet::activecontrolforeground {
1647    $_dispatcher event -idle !layout
1648}
Note: See TracBrowser for help on using the repository browser.