source: trunk/gui/scripts/radiodial.tcl @ 64

Last change on this file since 64 was 64, checked in by mmc, 19 years ago

Lots of fixes for app-pntoy and other tools:

  • Fixed plotting to recognize "-color name" in the style section, and to use auto colors for overlayed plots.
  • Fixed x-y plotting to keep axes instead of resetting when flipping back and forth between plots.
  • Fixed 1D fields to support new lin/log limits queries, so it plots properly.
  • Added support for <string> output values.
  • Fixed molecular viewer so that 3D rotation is unconstrained and more intuitive.
  • Fixed Rappture::exec to handle newlines properly. Sometimes output would get all mixed together without newlines. Works better now.
File size: 18.3 KB
Line 
1# ----------------------------------------------------------------------
2#  COMPONENT: Radiodial - selector, like the dial on a car radio
3#
4#  This widget looks like the dial on an old-fashioned car radio.
5#  It draws a series of values along an axis, and allows a selector
6#  to move back and forth to select the values.
7# ======================================================================
8#  AUTHOR:  Michael McLennan, Purdue University
9#  Copyright (c) 2004-2005
10#  Purdue Research Foundation, West Lafayette, IN
11# ======================================================================
12package require Itk
13
14option add *Radiodial.thickness 10 widgetDefault
15option add *Radiodial.length 2i widgetDefault
16option add *Radiodial.dialOutlineColor black widgetDefault
17option add *Radiodial.dialFillColor white widgetDefault
18option add *Radiodial.lineColor gray widgetDefault
19option add *Radiodial.activeLineColor black widgetDefault
20option add *Radiodial.valueWidth 10 widgetDefault
21option add *Radiodial.font \
22    -*-helvetica-medium-r-normal-*-*-120-* widgetDefault
23
24itcl::class Rappture::Radiodial {
25    inherit itk::Widget
26
27    itk_option define -min min Min ""
28    itk_option define -max max Max ""
29    itk_option define -thickness thickness Thickness 0
30    itk_option define -length length Length 0
31
32    itk_option define -dialoutlinecolor dialOutlineColor Color "black"
33    itk_option define -dialfillcolor dialFillColor Color "white"
34    itk_option define -linecolor lineColor Color "black"
35    itk_option define -activelinecolor activeLineColor Color "black"
36
37    itk_option define -font font Font ""
38    itk_option define -valuewidth valueWidth ValueWidth 0
39
40
41    constructor {args} { # defined below }
42    destructor { # defined below }
43
44    public method add {label {value ""}}
45    public method clear {}
46    public method get {args}
47    public method current {args}
48    public method color {value}
49                                                                               
50    protected method _redraw {}
51    protected method _click {x y}
52    protected method _navigate {offset}
53    protected method _limits {}
54    protected method _fixSize {}
55
56    private variable _values ""       ;# list of all values on the dial
57    private variable _val2label       ;# maps value => label
58    private variable _current ""      ;# current value (where pointer is)
59
60    private variable _spectrum ""     ;# width allocated for values
61    private variable _activecolor ""  ;# width allocated for values
62    private variable _vwidth 0        ;# width allocated for values
63
64    #
65    # Load the image for the knob.
66    #
67    private common images
68    set images(knob) [image create photo -data {
69R0lGODlhCQAMAMIEAAQCBJyanNza3Pz+/P///////////////yH5BAEKAAQALAAAAAAJAAwAAAMj
70SEqwDqO9MYJkVASLh/gbAHmgNX6amZXimrbVFkKyLN44kAAAOw==
71}]
72}
73                                                                               
74itk::usual Radiodial {
75}
76
77# ----------------------------------------------------------------------
78# CONSTRUCTOR
79# ----------------------------------------------------------------------
80itcl::body Rappture::Radiodial::constructor {args} {
81    itk_component add dial {
82        canvas $itk_interior.dial
83    }
84    pack $itk_component(dial) -expand yes -fill both
85    bind $itk_component(dial) <Configure> [itcl::code $this _redraw]
86
87    bind $itk_component(dial) <ButtonPress-1> [itcl::code $this _click %x %y]
88    bind $itk_component(dial) <B1-Motion> [itcl::code $this _click %x %y]
89    bind $itk_component(dial) <ButtonRelease-1> [itcl::code $this _click %x %y]
90
91    bind $itk_component(hull) <KeyPress-Left> [itcl::code $this _navigate -1]
92    bind $itk_component(hull) <KeyPress-Right> [itcl::code $this _navigate 1]
93
94    eval itk_initialize $args
95
96    _fixSize
97}
98
99# ----------------------------------------------------------------------
100# DESTRUCTOR
101# ----------------------------------------------------------------------
102itcl::body Rappture::Radiodial::destructor {} {
103    after cancel [itcl::code $this _redraw]
104}
105
106# ----------------------------------------------------------------------
107# USAGE: add <label> ?<value>?
108#
109# Clients use this to add new values to the dial.  Values are always
110# sorted in order along the dial.  If the value is not specified,
111# then it is created automatically based on the number of elements
112# on the dial.
113# ----------------------------------------------------------------------
114itcl::body Rappture::Radiodial::add {label {value ""}} {
115    if {"" == $value} {
116        set value [llength $_values]
117    }
118    lappend _values $value
119    set _values [lsort -real $_values]
120    set _val2label($value) $label
121
122    if {"" == $_current} {
123        set _current $value
124    }
125
126    after cancel [itcl::code $this _redraw]
127    after idle [itcl::code $this _redraw]
128}
129
130# ----------------------------------------------------------------------
131# USAGE: clear
132#
133# Clients use this to remove all existing values from the dial.
134# ----------------------------------------------------------------------
135itcl::body Rappture::Radiodial::clear {} {
136    set _values ""
137    set _current ""
138    catch {unset _val2label}
139
140    after cancel [itcl::code $this _redraw]
141    after idle [itcl::code $this _redraw]
142}
143
144# ----------------------------------------------------------------------
145# USAGE: get ?-format what? ?current|@index?
146#
147# Clients use this to query values within this radiodial.  With no
148# args, it returns a list of all values stored in the widget.  The
149# "current" arg requests only the current value on the radiodial.
150# The @index syntax can be used to request a particular value at
151# an index within the list of values.
152#
153# By default, this method returns the label for each value.  The
154# format option can be used to request the label, the value, or
155# both.
156# ----------------------------------------------------------------------
157itcl::body Rappture::Radiodial::get {args} {
158    Rappture::getopts args params {
159        value -format "label"
160    }
161    if {[llength $args] > 1} {
162        error "wrong # args: should be \"get ?-format f? ?current|@index\""
163    }
164    set index [lindex $args 0]
165    if {"" == $index} {
166        set ilist ""
167        for {set i 0} {$i < [llength $_values]} {incr i} {
168            append ilist $i
169        }
170    } elseif {"current" == $index} {
171        set ilist [lsearch -exact $_values $_current]
172        if {$ilist < 0} {
173            set ilist ""
174        }
175    } elseif {[regexp {^@([0-9]+|end)$} $index match i]} {
176        set ilist $i
177    }
178    if {[llength $ilist] == 1} {
179        set op set
180    } else {
181        set op lappend
182    }
183
184    set rlist ""
185    foreach i $ilist {
186        switch -- $params(-format) {
187            label {
188                set v [lindex $_values $i]
189                $op rlist $_val2label($v)
190            }
191            value {
192                $op rlist [lindex $_values $i]
193            }
194            position {
195                foreach {min max} [_limits] break
196                set v [lindex $_values $i]
197                set frac [expr {double($v-$min)/($max-$min)}]
198                $op rlist $frac
199            }
200            all {
201                set v [lindex $_values $i]
202                foreach {min max} [_limits] break
203                set frac [expr {double($v-$min)/($max-$min)}]
204                $op rlist [list $_val2label($v) $v $frac]
205            }
206            default {
207                error "bad value \"$v\": should be label, value, position, all"
208            }
209        }
210    }
211    return $rlist
212}
213
214# ----------------------------------------------------------------------
215# USAGE: current ?<newval>?
216#
217# Clients use this to get/set the current value for this widget.
218# ----------------------------------------------------------------------
219itcl::body Rappture::Radiodial::current {args} {
220    if {[llength $args] == 0} {
221        return $_current
222    } elseif {[llength $args] == 1} {
223        set newval [lindex $args 0]
224        set found 0
225        foreach v $_values {
226            if {[string equal $_val2label($v) $newval]} {
227                set newval $v
228                set found 1
229                break
230            }
231        }
232        if {!$found} {
233            error "bad value \"$newval\""
234        }
235        set _current $newval
236        return $_current
237    }
238    error "wrong # args: should be \"current ?newval?\""
239}
240
241# ----------------------------------------------------------------------
242# USAGE: color <value>
243#
244# Clients use this to query the color associated with a <value>
245# along the dial.
246# ----------------------------------------------------------------------
247itcl::body Rappture::Radiodial::color {value} {
248    set found 0
249    foreach v $_values {
250        if {[string equal $_val2label($v) $value]} {
251            set value $v
252            set found 1
253            break
254        }
255    }
256    if {!$found} {
257        error "bad value \"$value\""
258    }
259
260    if {"" != $_spectrum} {
261        foreach {min max} [_limits] break
262        set frac [expr {double($value-$min)/($max-$min)}]
263        set color [$_spectrum get $frac]
264    } else {
265        if {$value == $_current} {
266            set color $_activecolor
267        } else {
268            set color $itk_option(-linecolor)
269        }
270    }
271    return $color
272}
273
274# ----------------------------------------------------------------------
275# USAGE: _redraw
276#
277# Called automatically whenever the widget changes size to redraw
278# all elements within it.
279# ----------------------------------------------------------------------
280itcl::body Rappture::Radiodial::_redraw {} {
281    set c $itk_component(dial)
282    $c delete all
283
284    set w [winfo width $c]
285    set h [winfo height $c]
286    set y1 [expr {$h-[image height $images(knob)]/2-1}]
287    set y0 [expr {$y1 - $itk_option(-thickness)-1}]
288    set x0 1
289    set x1 [expr {$w-$_vwidth-4}]
290
291    # draw the background rectangle
292    $c create rectangle $x0 $y0 $x1 $y1 \
293        -outline $itk_option(-dialoutlinecolor) \
294        -fill $itk_option(-dialfillcolor)
295
296    # draw lines for all values
297    foreach {min max} [_limits] break
298    if {$max > $min} {
299        foreach v $_values {
300            set frac [expr {double($v-$min)/($max-$min)}]
301            if {"" != $_spectrum} {
302                set color [$_spectrum get $frac]
303            } else {
304                if {$v == $_current} {
305                    set color $_activecolor
306                } else {
307                    set color $itk_option(-linecolor)
308                }
309            }
310            set thick [expr {($v == $_current) ? 3 : 1}]
311
312            set x [expr {$frac*($x1-$x0) + $x0}]
313            $c create line $x [expr {$y0+1}] $x $y1 -fill $color -width $thick
314        }
315
316        if {"" != $_current} {
317            set x [expr {double($_current-$min)/($max-$min)*($x1-$x0) + $x0}]
318            $c create image $x [expr {$h-1}] -anchor s -image $images(knob)
319        }
320    }
321
322    # if the -valuewidth is > 0, then make room for the value
323    set vw $itk_option(-valuewidth)
324    if {$vw > 0 && "" != $_current} {
325        set str $_val2label($_current)
326        if {[string length $str] >= $vw} {
327            set str "[string range $str 0 [expr {$vw-3}]]..."
328        }
329
330        set dy [expr {([font metrics $itk_option(-font) -linespace]
331                        - [font metrics $itk_option(-font) -ascent])/2}]
332
333        set id [$c create text [expr {$x1+4}] [expr {($y1+$y0)/2+$dy}] \
334            -anchor w -text $str -font $itk_option(-font)]
335        foreach {x0 y0 x1 y1} [$c bbox $id] break
336        set x0 [expr {$x0 + 10}]
337
338        # set up a tooltip so you can mouse over truncated values
339        Rappture::Tooltip::text $c $_val2label($_current)
340        $c bind $id <Enter> \
341            [list ::Rappture::Tooltip::tooltip pending %W +$x0,$y1]
342        $c bind $id <Leave> \
343            [list ::Rappture::Tooltip::tooltip cancel]
344        $c bind $id <ButtonPress> \
345            [list ::Rappture::Tooltip::tooltip cancel]
346        $c bind $id <KeyPress> \
347            [list ::Rappture::Tooltip::tooltip cancel]
348    }
349}
350
351# ----------------------------------------------------------------------
352# USAGE: _click <x> <y>
353#
354# Called automatically whenever the user clicks or drags on the widget
355# to select a value.  Moves the current value to the one nearest the
356# click point.  If the value actually changes, it generates a <<Value>>
357# event to notify clients.
358# ----------------------------------------------------------------------
359itcl::body Rappture::Radiodial::_click {x y} {
360    set c $itk_component(dial)
361    set w [winfo width $c]
362    set h [winfo height $c]
363    set x0 1
364    set x1 [expr {$w-$_vwidth-4}]
365
366    focus $itk_component(hull)
367
368    # draw lines for all values
369    foreach {min max} [_limits] break
370    if {$max > $min && $x >= $x0 && $x <= $x1} {
371        set dmin $w
372        set xnearest 0
373        set vnearest ""
374        foreach v $_values {
375            set xv [expr {double($v-$min)/($max-$min)*($x1-$x0) + $x0}]
376            if {abs($xv-$x) < $dmin} {
377                set dmin [expr {abs($xv-$x)}]
378                set xnearest $xv
379                set vnearest $v
380            }
381        }
382
383        if {$vnearest != $_current} {
384            set _current $vnearest
385            _redraw
386
387            event generate $itk_component(hull) <<Value>>
388        }
389    }
390}
391
392# ----------------------------------------------------------------------
393# USAGE: _navigate <offset>
394#
395# Called automatically whenever the user presses left/right keys
396# to nudge the current value left or right by some <offset>.  If the
397# value actually changes, it generates a <<Value>> event to notify
398# clients.
399# ----------------------------------------------------------------------
400itcl::body Rappture::Radiodial::_navigate {offset} {
401    set index [lsearch -exact $_values $_current]
402    if {$index >= 0} {
403        incr index $offset
404        if {$index >= [llength $_values]} {
405            set index [expr {[llength $_values]-1}]
406        } elseif {$index < 0} {
407            set index 0
408        }
409
410        set newval [lindex $_values $index]
411        if {$newval != $_current} {
412            set _current $newval
413            _redraw
414
415            event generate $itk_component(hull) <<Value>>
416        }
417    }
418}
419
420# ----------------------------------------------------------------------
421# USAGE: _limits
422#
423# Used internally to compute the overall min/max limits for the
424# radio dial.  Returns {min max}, representing the end values for
425# the scale.
426# ----------------------------------------------------------------------
427itcl::body Rappture::Radiodial::_limits {} {
428    if {[llength $_values] == 0} {
429        set min 0
430        set max 0
431    } else {
432        set min [lindex $_values 0]
433        set max $min
434        foreach v [lrange $_values 1 end] {
435            if {$v < $min} { set min $v }
436            if {$v > $max} { set max $v }
437        }
438        set del [expr {$max-$min}]
439        set min [expr {$min-0.1*$del}]
440        set max [expr {$max+0.1*$del}]
441    }
442
443    if {"" != $itk_option(-min)} {
444        set min $itk_option(-min)
445    }
446    if {"" != $itk_option(-max)} {
447        set max $itk_option(-max)
448    }
449    return [list $min $max]
450}
451
452# ----------------------------------------------------------------------
453# USAGE: _fixSize
454#
455# Used internally to compute the overall size of the widget based
456# on the -thickness and -length options.
457# ----------------------------------------------------------------------
458itcl::body Rappture::Radiodial::_fixSize {} {
459    set h [winfo pixels $itk_component(hull) $itk_option(-thickness)]
460    set h [expr {$h/2 + [image height $images(knob)]}]
461
462    set w [winfo pixels $itk_component(hull) $itk_option(-length)]
463
464    # if the -valuewidth is > 0, then make room for the value
465    if {$itk_option(-valuewidth) > 0} {
466        set charw [font measure $itk_option(-font) "n"]
467        set _vwidth [expr {$itk_option(-valuewidth)*$charw}]
468        set w [expr {$w+$_vwidth+4}]
469    } else {
470        set _vwidth 0
471    }
472
473    $itk_component(dial) configure -width $w -height $h
474}
475
476# ----------------------------------------------------------------------
477# CONFIGURE: -thickness
478# ----------------------------------------------------------------------
479itcl::configbody Rappture::Radiodial::thickness {
480    _fixSize
481}
482
483# ----------------------------------------------------------------------
484# CONFIGURE: -length
485# ----------------------------------------------------------------------
486itcl::configbody Rappture::Radiodial::length {
487    _fixSize
488}
489
490# ----------------------------------------------------------------------
491# CONFIGURE: -font
492# ----------------------------------------------------------------------
493itcl::configbody Rappture::Radiodial::font {
494    _fixSize
495}
496
497# ----------------------------------------------------------------------
498# CONFIGURE: -valuewidth
499# ----------------------------------------------------------------------
500itcl::configbody Rappture::Radiodial::valuewidth {
501    if {![string is integer $itk_option(-valuewidth)]} {
502        error "bad value \"$itk_option(-valuewidth)\": should be integer"
503    }
504    after cancel [itcl::code $this _redraw]
505    after idle [itcl::code $this _redraw]
506}
507
508# ----------------------------------------------------------------------
509# CONFIGURE: -dialoutlinecolor
510# ----------------------------------------------------------------------
511itcl::configbody Rappture::Radiodial::dialoutlinecolor {
512    after cancel [itcl::code $this _redraw]
513    after idle [itcl::code $this _redraw]
514}
515
516# ----------------------------------------------------------------------
517# CONFIGURE: -dialfillcolor
518# ----------------------------------------------------------------------
519itcl::configbody Rappture::Radiodial::dialfillcolor {
520    after cancel [itcl::code $this _redraw]
521    after idle [itcl::code $this _redraw]
522}
523
524# ----------------------------------------------------------------------
525# CONFIGURE: -linecolor
526# ----------------------------------------------------------------------
527itcl::configbody Rappture::Radiodial::linecolor {
528    after cancel [itcl::code $this _redraw]
529    after idle [itcl::code $this _redraw]
530}
531
532# ----------------------------------------------------------------------
533# CONFIGURE: -activelinecolor
534# ----------------------------------------------------------------------
535itcl::configbody Rappture::Radiodial::activelinecolor {
536    set val $itk_option(-activelinecolor)
537    if {[catch {$val isa ::Rappture::Spectrum} valid] == 0 && $valid} {
538        set _spectrum $val
539        set _activecolor ""
540    } elseif {[catch {winfo rgb $itk_component(hull) $val}] == 0} {
541        set _spectrum ""
542        set _activecolor $val
543    } else {
544        error "bad value \"$val\": should be Spectrum object or color"
545    }
546    after cancel [itcl::code $this _redraw]
547    after idle [itcl::code $this _redraw]
548}
Note: See TracBrowser for help on using the repository browser.