# -*- mode: tcl; indent-tabs-mode: nil -*- # ---------------------------------------------------------------------- # COMPONENT: Videodial1 - selector, like the dial on a flow # # This widget looks like the dial on an old-fashioned car flow. # It draws a series of values along an axis, and allows a selector # to move back and forth to select the values. # ====================================================================== # AUTHOR: Michael McLennan, Purdue University # Copyright (c) 2004-2012 HUBzero Foundation, LLC # # See the file "license.terms" for information on usage and # redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES. # ====================================================================== package require Itk package require BLT option add *Videodial1.dialProgressColor #6666cc widgetDefault option add *Videodial1.thickness 10 widgetDefault option add *Videodial1.length 2i widgetDefault option add *Videodial1.knobImage knob widgetDefault option add *Videodial1.knobPosition n@middle widgetDefault option add *Videodial1.dialOutlineColor black widgetDefault option add *Videodial1.dialFillColor white widgetDefault option add *Videodial1.lineColor gray widgetDefault option add *Videodial1.activeLineColor black widgetDefault option add *Videodial1.padding 0 widgetDefault option add *Videodial1.valueWidth 10 widgetDefault option add *Videodial1.valuePadding 0.1 widgetDefault option add *Videodial1.foreground black widgetDefault option add *Videodial1.font \ -*-helvetica-medium-r-normal-*-12-* widgetDefault itcl::class Rappture::Videodial1 { inherit itk::Widget itk_option define -min min Min 0 itk_option define -max max Max 1 itk_option define -variable variable Variable "" itk_option define -offset offset Offset 1 itk_option define -thickness thickness Thickness 0 itk_option define -length length Length 0 itk_option define -padding padding Padding 0 itk_option define -foreground foreground Foreground "black" itk_option define -dialoutlinecolor dialOutlineColor Color "black" itk_option define -dialfillcolor dialFillColor Color "white" itk_option define -dialprogresscolor dialProgressColor Color "" itk_option define -linecolor lineColor Color "black" itk_option define -activelinecolor activeLineColor Color "black" itk_option define -knobimage knobImage KnobImage "" itk_option define -knobposition knobPosition KnobPosition "" itk_option define -font font Font "" itk_option define -valuewidth valueWidth ValueWidth 0 itk_option define -valuepadding valuePadding ValuePadding 0 constructor {args} { # defined below } destructor { # defined below } public method current {value} public method clear {} protected method _redraw {} protected method _knob {x y} protected method _navigate {offset} protected method _fixSize {} protected method _fixValue {args} protected method _fixOffsets {} private method _current {value} private method _draw_major_timeline {} private method ms2rel {value} private method rel2ms {value} private common _click ;# x,y point where user clicked private variable _values "" ;# list of all values on the dial private variable _val2label ;# maps value => string label(s) private variable _current 0 ;# current value (where pointer is) private variable _variable "" ;# variable associated with -variable private variable _knob "" ;# image for knob private variable _spectrum "" ;# width allocated for values private variable _activecolor "" ;# width allocated for values private variable _vwidth 0 ;# width allocated for values private variable _offset_pos 1 ;# private variable _offset_neg -1 ;# private variable _imspace 10 ;# pixels between intermediate marks private variable _pmcnt 0 ;# particle marker count private variable _min 0 private variable _max 1 } itk::usual Videodial1 { keep -foreground -cursor -font } # ---------------------------------------------------------------------- # CONSTRUCTOR # ---------------------------------------------------------------------- itcl::body Rappture::Videodial1::constructor {args} { # bind $itk_component(hull) <> [itcl::code $this _updateCurrent] # ---------------------------------------------------------------------- # controls for the major timeline. # ---------------------------------------------------------------------- itk_component add majordial { canvas $itk_interior.majordial } bind $itk_component(majordial) [itcl::code $this _draw_major_timeline] bind $itk_component(majordial) [itcl::code $this _knob %x %y] bind $itk_component(majordial) [itcl::code $this _knob %x %y] bind $itk_component(majordial) [itcl::code $this _knob %x %y] #bind $itk_component(hull) [itcl::code $this _navigate $_offset_neg] #bind $itk_component(hull) [itcl::code $this _navigate $_offset_pos] $itk_component(majordial) bind "knob" \ [list $itk_component(majordial) configure -cursor sb_h_double_arrow] $itk_component(majordial) bind "knob" \ [list $itk_component(majordial) configure -cursor ""] # ---------------------------------------------------------------------- # place controls in widget. # ---------------------------------------------------------------------- blt::table $itk_interior \ 0,0 $itk_component(majordial) -fill x blt::table configure $itk_interior c* -resize both blt::table configure $itk_interior r0 -resize none eval itk_initialize $args $itk_component(majordial) configure -background green _fixSize _fixOffsets } # ---------------------------------------------------------------------- # DESTRUCTOR # ---------------------------------------------------------------------- itcl::body Rappture::Videodial1::destructor {} { configure -variable "" ;# remove variable trace after cancel [itcl::code $this _redraw] } # ---------------------------------------------------------------------- # USAGE: current ?? # # Clients use this to set a new value for the dial. Values are always # sorted in order along the dial. If the value is not specified, # then it is created automatically based on the number of elements # on the dial. # ---------------------------------------------------------------------- itcl::body Rappture::Videodial1::current {value} { if {"" == $value} { return } _current [ms2rel $value] } # ---------------------------------------------------------------------- # USAGE: _current ?? # # Clients use this to set a new value for the dial. Values are always # sorted in order along the dial. If the value is not specified, # then it is created automatically based on the number of elements # on the dial. # ---------------------------------------------------------------------- itcl::body Rappture::Videodial1::_current {relval} { if { $relval < 0.0 } { set relval 0.0 } if { $relval > 1.0 } { set relval 1.0 } set _current $relval after cancel [itcl::code $this _draw_major_timeline] after idle [itcl::code $this _draw_major_timeline] set framenum [expr round([rel2ms $_current])] # update the upvar variable if { $_variable != "" } { upvar #0 $_variable var set var $framenum } } # ---------------------------------------------------------------------- # USAGE: _draw_major_timeline # # ---------------------------------------------------------------------- itcl::body Rappture::Videodial1::_draw_major_timeline {} { set c $itk_component(majordial) $c delete all set fg $itk_option(-foreground) set w [winfo width $c] set h [winfo height $c] set p [winfo pixels $c $itk_option(-padding)] set t [expr {$itk_option(-thickness)+1}] # FIXME: hack to get the reduce spacing in widget set y1 [expr {$h-2}] if {"" != $_knob} { set kw [image width $_knob] set kh [image height $_knob] # anchor refers to where on knob # top/middle/bottom refers to where on the dial # leave room for the bottom of the knob if needed switch -- $itk_option(-knobposition) { n@top - nw@top - ne@top { set extra [expr {$t-$kh}] if {$extra < 0} {set extra 0} set y1 [expr {$y1-$extra}] } n@middle - nw@middle - ne@middle { set extra [expr {int(ceil($kh-0.5*$t))}] if {$extra < 0} {set extra 0} set y1 [expr {$y1-$extra}] } n@bottom - nw@bottom - ne@bottom { set y1 [expr {$y1-$kh}] } e@top - w@top - center@top - e@bottom - w@bottom - center@bottom { set extra [expr {int(ceil(0.5*$kh))}] set y1 [expr {$y1-$extra}] } e@middle - w@middle - center@middle { set extra [expr {int(ceil(0.5*($kh-$t)))}] if {$extra < 0} {set extra 0} set y1 [expr {$y1-$extra}] } s@top - sw@top - se@top - s@middle - sw@middle - se@middle - s@bottom - sw@bottom - se@bottom { set y1 [expr {$y1-1}] } } } set y0 [expr {$y1-$t}] set x0 [expr {$p+1}] set x1 [expr {$w-$_vwidth-$p-4}] # draw the background rectangle for the major time line $c create rectangle $x0 $y0 $x1 $y1 \ -outline $itk_option(-dialoutlinecolor) \ -fill $itk_option(-dialfillcolor) \ -tags "majorbg" # draw the optional progress bar for the major time line, # from start to current if {"" != $itk_option(-dialprogresscolor) } { set xx1 [expr {$_current*($x1-$x0) + $x0}] $c create rectangle [expr {$x0+1}] [expr {$y0+3}] $xx1 [expr {$y1-2}] \ -outline "" -fill $itk_option(-dialprogresscolor) } regexp {([nsew]+|center)@} $itk_option(-knobposition) match anchor switch -glob -- $itk_option(-knobposition) { *@top { set kpos $y0 } *@middle { set kpos [expr {int(ceil(0.5*($y1+$y0)))}] } *@bottom { set kpos $y1 } } set x [expr {$_current*($x1-$x0) + $x0}] set color $_activecolor set thick 3 if {"" != $color} { $c create line $x [expr {$y0+1}] $x $y1 -fill $color -width $thick } $c create image $x $kpos -anchor $anchor -image $_knob -tags "knob" } # ---------------------------------------------------------------------- # USAGE: _redraw # # Called automatically whenever the widget changes size to redraw # all elements within it. # ---------------------------------------------------------------------- itcl::body Rappture::Videodial1::_redraw {} { _draw_major_timeline } # ---------------------------------------------------------------------- # USAGE: _knob # # Called automatically whenever the user clicks or drags on the widget # to select a value. Moves the current value to the one nearest the # click point. If the value actually changes, it generates a <> # event to notify clients. # ---------------------------------------------------------------------- itcl::body Rappture::Videodial1::_knob {x y} { set c $itk_component(majordial) set w [winfo width $c] set h [winfo height $c] set x0 1 set x1 [expr {$w-$_vwidth-4}] focus $itk_component(hull) if {$x >= $x0 && $x <= $x1} { current [rel2ms [expr double($x - $x0) / double($x1 - $x0)]] } event generate $itk_component(hull) <> } # ---------------------------------------------------------------------- # USAGE: _navigate # # Called automatically whenever the user presses left/right keys # to nudge the current value left or right by some . If the # value actually changes, it generates a <> event to notify # clients. # ---------------------------------------------------------------------- #itcl::body Rappture::Videodial1::_navigate {offset} { # set index [lsearch -exact $_values $_current] # if {$index >= 0} { # incr index $offset # if {$index >= [llength $_values]} { # set index [expr {[llength $_values]-1}] # } elseif {$index < 0} { # set index 0 # } # # set newval [lindex $_values $index] # if {$newval != $_current} { # current $newval # _redraw # # event generate $itk_component(hull) <> # } # } #} # ---------------------------------------------------------------------- # USAGE: _navigate # # Called automatically whenever the user presses left/right keys # to nudge the current value left or right by some . If the # value actually changes, it generates a <> event to notify # clients. # ---------------------------------------------------------------------- itcl::body Rappture::Videodial1::_navigate {offset} { _current [ms2rel [expr [rel2ms ${_current}] + $offset]] event generate $itk_component(hull) <> } # ---------------------------------------------------------------------- # USAGE: _fixSize # # Used internally to compute the overall size of the widget based # on the -thickness and -length options. # ---------------------------------------------------------------------- itcl::body Rappture::Videodial1::_fixSize {} { set h [winfo pixels $itk_component(hull) $itk_option(-thickness)] if {"" != $_knob} { set kh [image height $_knob] switch -- $itk_option(-knobposition) { n@top - nw@top - ne@top - s@bottom - sw@bottom - se@bottom { if {$kh > $h} { set h $kh } } n@middle - nw@middle - ne@middle - s@middle - sw@middle - se@middle { set h [expr {int(ceil(0.5*$h + $kh))}] } n@bottom - nw@bottom - ne@bottom - s@top - sw@top - se@top { set h [expr {$h + $kh}] } e@middle - w@middle - center@middle { set h [expr {(($h > $kh) ? $h : ($kh+1))}] } n@middle - ne@middle - nw@middle - s@middle - se@middle - sw@middle { set extra [expr {int(ceil($kh-0.5*$h))}] if {$extra < 0} { set extra 0 } set h [expr {$h+$extra}] } } } # FIXME: hack to get the reduce spacing in widget incr h -1 set w [winfo pixels $itk_component(hull) $itk_option(-length)] # if the -valuewidth is > 0, then make room for the value if {$itk_option(-valuewidth) > 0} { set charw [font measure $itk_option(-font) "n"] set _vwidth [expr {$itk_option(-valuewidth)*$charw}] set w [expr {$w+$_vwidth+4}] } else { set _vwidth 0 } $itk_component(majordial) configure -width $w -height $h } # ---------------------------------------------------------------------- # USAGE: _fixValue ? ? # # Invoked automatically whenever the -variable associated with this # widget is modified. Copies the value to the current settings for # the widget. # ---------------------------------------------------------------------- itcl::body Rappture::Videodial1::_fixValue {args} { if {"" == $itk_option(-variable)} { return } upvar #0 $itk_option(-variable) var _current [ms2rel $var] } # ---------------------------------------------------------------------- # USAGE: _fixOffsets # # ---------------------------------------------------------------------- itcl::body Rappture::Videodial1::_fixOffsets {} { if {0 == $itk_option(-offset)} { return } set _offset_pos $itk_option(-offset) set _offset_neg [expr -1*$_offset_pos] bind $itk_component(hull) [itcl::code $this _navigate $_offset_neg] bind $itk_component(hull) [itcl::code $this _navigate $_offset_pos] } # ---------------------------------------------------------------------- # USAGE: ms2rel # # ---------------------------------------------------------------------- itcl::body Rappture::Videodial1::ms2rel { value } { if { ${_max} > ${_min} } { return [expr {1.0 * ($value - ${_min}) / (${_max} - ${_min})}] } return 0 } # ---------------------------------------------------------------------- # USAGE: rel2ms # # ---------------------------------------------------------------------- itcl::body Rappture::Videodial1::rel2ms { value } { return [expr $value * (${_max} - ${_min}) + ${_min}] } # ---------------------------------------------------------------------- # CONFIGURE: -thickness # ---------------------------------------------------------------------- itcl::configbody Rappture::Videodial1::thickness { _fixSize } # ---------------------------------------------------------------------- # CONFIGURE: -length # ---------------------------------------------------------------------- itcl::configbody Rappture::Videodial1::length { _fixSize } # ---------------------------------------------------------------------- # CONFIGURE: -font # ---------------------------------------------------------------------- itcl::configbody Rappture::Videodial1::font { _fixSize } # ---------------------------------------------------------------------- # CONFIGURE: -valuewidth # ---------------------------------------------------------------------- itcl::configbody Rappture::Videodial1::valuewidth { if {![string is integer $itk_option(-valuewidth)]} { error "bad value \"$itk_option(-valuewidth)\": should be integer" } _fixSize after cancel [itcl::code $this _redraw] after idle [itcl::code $this _redraw] } # ---------------------------------------------------------------------- # CONFIGURE: -foreground # ---------------------------------------------------------------------- itcl::configbody Rappture::Videodial1::foreground { after cancel [itcl::code $this _redraw] after idle [itcl::code $this _redraw] } # ---------------------------------------------------------------------- # CONFIGURE: -dialoutlinecolor # ---------------------------------------------------------------------- itcl::configbody Rappture::Videodial1::dialoutlinecolor { after cancel [itcl::code $this _redraw] after idle [itcl::code $this _redraw] } # ---------------------------------------------------------------------- # CONFIGURE: -dialfillcolor # ---------------------------------------------------------------------- itcl::configbody Rappture::Videodial1::dialfillcolor { after cancel [itcl::code $this _redraw] after idle [itcl::code $this _redraw] } # ---------------------------------------------------------------------- # CONFIGURE: -dialprogresscolor # ---------------------------------------------------------------------- itcl::configbody Rappture::Videodial1::dialprogresscolor { after cancel [itcl::code $this _redraw] after idle [itcl::code $this _redraw] } # ---------------------------------------------------------------------- # CONFIGURE: -linecolor # ---------------------------------------------------------------------- itcl::configbody Rappture::Videodial1::linecolor { after cancel [itcl::code $this _redraw] after idle [itcl::code $this _redraw] } # ---------------------------------------------------------------------- # CONFIGURE: -activelinecolor # ---------------------------------------------------------------------- itcl::configbody Rappture::Videodial1::activelinecolor { set val $itk_option(-activelinecolor) if {[catch {$val isa ::Rappture::Spectrum} valid] == 0 && $valid} { set _spectrum $val set _activecolor "" } elseif {[catch {winfo rgb $itk_component(hull) $val}] == 0} { set _spectrum "" set _activecolor $val } elseif {"" != $val} { error "bad value \"$val\": should be Spectrum object or color" } after cancel [itcl::code $this _redraw] after idle [itcl::code $this _redraw] } # ---------------------------------------------------------------------- # CONFIGURE: -knobimage # ---------------------------------------------------------------------- itcl::configbody Rappture::Videodial1::knobimage { if {[regexp {^image[0-9]+$} $itk_option(-knobimage)]} { set _knob $itk_option(-knobimage) } elseif {"" != $itk_option(-knobimage)} { set _knob [Rappture::icon $itk_option(-knobimage)] } else { set _knob "" } _fixSize after cancel [itcl::code $this _redraw] after idle [itcl::code $this _redraw] } # ---------------------------------------------------------------------- # CONFIGURE: -knobposition # ---------------------------------------------------------------------- itcl::configbody Rappture::Videodial1::knobposition { if {![regexp {^([nsew]+|center)@(top|middle|bottom)$} $itk_option(-knobposition)]} { error "bad value \"$itk_option(-knobposition)\": should be anchor@top|middle|bottom" } _fixSize after cancel [itcl::code $this _redraw] after idle [itcl::code $this _redraw] } # ---------------------------------------------------------------------- # CONFIGURE: -padding # This adds padding on left/right side of dial background. # ---------------------------------------------------------------------- itcl::configbody Rappture::Videodial1::padding { if {[catch {winfo pixels $itk_component(hull) $itk_option(-padding)}]} { error "bad value \"$itk_option(-padding)\": should be size in pixels" } } # ---------------------------------------------------------------------- # CONFIGURE: -valuepadding # This shifts min/max limits in by a fraction of the overall size. # ---------------------------------------------------------------------- itcl::configbody Rappture::Videodial1::valuepadding { if {![string is double $itk_option(-valuepadding)] || $itk_option(-valuepadding) < 0} { error "bad value \"$itk_option(-valuepadding)\": should be >= 0.0" } } # ---------------------------------------------------------------------- # CONFIGURE: -variable # ---------------------------------------------------------------------- itcl::configbody Rappture::Videodial1::variable { if {"" != $_variable} { upvar #0 $_variable var trace remove variable var write [itcl::code $this _fixValue] } set _variable $itk_option(-variable) if {"" != $_variable} { upvar #0 $_variable var trace add variable var write [itcl::code $this _fixValue] # sync to the current value of this variable if {[info exists var]} { _fixValue } } } # ---------------------------------------------------------------------- # CONFIGURE: -offset # ---------------------------------------------------------------------- itcl::configbody Rappture::Videodial1::offset { if {![string is double $itk_option(-offset)]} { error "bad value \"$itk_option(-offset)\": should be >= 0.0" } _fixOffsets } # ---------------------------------------------------------------------- # CONFIGURE: -min # ---------------------------------------------------------------------- itcl::configbody Rappture::Videodial1::min { if {![string is integer $itk_option(-min)]} { error "bad value \"$itk_option(-min)\": should be an integer" } if {$itk_option(-min) < 0} { error "bad value \"$itk_option(-min)\": should be >= 0" } set _min $itk_option(-min) } # ---------------------------------------------------------------------- # CONFIGURE: -max # ---------------------------------------------------------------------- itcl::configbody Rappture::Videodial1::max { if {![string is integer $itk_option(-max)]} { error "bad value \"$itk_option(-max)\": should be an integer" } if {$itk_option(-max) < 0} { error "bad value \"$itk_option(-max)\": should be >= 0" } set _max $itk_option(-max) }