# -*- mode: tcl; indent-tabs-mode: nil -*- # ---------------------------------------------------------------------- # COMPONENT: videodistance - specify a distance in a video canvas # # ====================================================================== # AUTHOR: Derrick Kearney, 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 package require Img package require Rappture package require RapptureGUI itcl::class Rappture::VideoDistance { inherit itk::Widget itk_option define -color color Color "green" itk_option define -fncallback fncallback Fncallback "" itk_option define -bindentercb bindentercb Bindentercb "" itk_option define -bindleavecb bindleavecb Bindleavecb "" itk_option define -writetextcb writetextcb Writetextcb "" itk_option define -px2dist px2dist Px2dist "" itk_option define -units units Units "m" itk_option define -bindings bindings Bindings "enable" itk_option define -ondelete ondelete Ondelete "" itk_option define -onframe onframe Onframe "" constructor { name win args } { # defined below } destructor { # defined below } public method Show {args} public method Hide {args} public method Coords {args} public method Frame {args} public method Move {status x y} public method Menu {args} public variable fncallback "" ;# framenumber callback - tells what frame we are on public variable bindentercb "" ;# enter binding callback - call this when entering the object public variable bindleavecb "" ;# leave binding callback - call this when leaving the object public variable writetextcb "" ;# write text callback - call this to write text to the canvas protected method Enter {} protected method Leave {} protected method CatchEvent {event} protected method _fixValue {args} protected method _fixPx2Dist {px2dist} protected method _fixBindings {status} private variable _canvas "" ;# canvas which owns the object private variable _name "" ;# id of the object private variable _color "" ;# color of the object private variable _frame 0 ;# frame number where the object lives private variable _coords "" ;# coords of the object, x0 y0 x1 y1 private variable _x 0 ;# x coord when "pressed" for motion private variable _y 0 ;# y coord when "pressed" for motion private variable _px2dist "" ;# variable associated with -px2dist private variable _units "" ;# private variable _dist 0 ;# distance of the measured space } itk::usual VideoDistance { keep -background -foreground -cursor -font keep -plotbackground -plotforeground } # ---------------------------------------------------------------------- # CONSTRUCTOR # ---------------------------------------------------------------------- itcl::body Rappture::VideoDistance::constructor {name win args} { set _name $name set _canvas $win # setup the control menu set menu $itk_component(hull).distancecontrols itk_component add menu { Rappture::Balloon $itk_interior.menu -title "Controls" } set controls [$itk_component(menu) component inner] set fg [option get $itk_component(hull) font Font] label $controls.propertiesl -text "Properties" -font $fg \ -highlightthickness 0 # Measurement control label $controls.measurementl -text "Value" -font $fg \ -highlightthickness 0 entry $controls.measuremente -width 5 -background white # Frame number control label $controls.framenuml -text "Frame" -font "Arial 9"\ -highlightthickness 0 Rappture::Spinint $controls.framenume \ -min 0 -width 5 -font "arial 9" # x0 label $controls.x0l -text "x0" -font $fg -highlightthickness 0 #FIXME: if the canvas width increases after the distance widget is created, # this max is not updated. Rappture::Spinint $controls.x0e \ -min 0 -max [winfo width ${_canvas}] -width 4 -font "arial 9" # y0 label $controls.y0l -text "y0" -font $fg -highlightthickness 0 #FIXME: if the canvas height increases after the distance widget is created, # this max is not updated. Rappture::Spinint $controls.y0e \ -min 0 -max [winfo height ${_canvas}] -width 4 -font "arial 9" # x1 label $controls.x1l -text "x1" -font $fg -highlightthickness 0 #FIXME: if the canvas width increases after the distance widget is created, # this max is not updated. Rappture::Spinint $controls.x1e \ -min 0 -max [winfo width ${_canvas}] -width 4 -font "arial 9" # y1 label $controls.y1l -text "y1" -font $fg -highlightthickness 0 #FIXME: if the canvas height increases after the distance widget is created, # this max is not updated. Rappture::Spinint $controls.y1e \ -min 0 -max [winfo height ${_canvas}] -width 4 -font "arial 9" # Delete control label $controls.deletel -text "Delete" -font $fg \ -highlightthickness 0 Rappture::Switch $controls.deleteb -showtext "false" $controls.deleteb value false button $controls.saveb -text Save \ -relief raised -pady 0 -padx 0 -font "Arial 9" \ -command [itcl::code $this Menu deactivate save] \ -activebackground grey90 button $controls.cancelb -text Cancel \ -relief raised -pady 0 -padx 0 -font "Arial 9" \ -command [itcl::code $this Menu deactivate cancel] \ -activebackground grey90 grid $controls.measurementl -column 0 -row 0 -sticky e grid $controls.measuremente -column 1 -row 0 -sticky w grid $controls.framenuml -column 2 -row 0 -sticky e grid $controls.framenume -column 3 -row 0 -sticky w grid $controls.x0l -column 0 -row 1 -sticky e grid $controls.x0e -column 1 -row 1 -sticky w grid $controls.y0l -column 2 -row 1 -sticky e grid $controls.y0e -column 3 -row 1 -sticky w grid $controls.x1l -column 0 -row 2 -sticky e grid $controls.x1e -column 1 -row 2 -sticky w grid $controls.y1l -column 2 -row 2 -sticky e grid $controls.y1e -column 3 -row 2 -sticky w grid $controls.deletel -column 2 -row 3 -sticky e grid $controls.deleteb -column 3 -row 3 -sticky w grid $controls.saveb -column 0 -row 4 -sticky e -columnspan 2 grid $controls.cancelb -column 2 -row 4 -sticky w -columnspan 2 # finish configuring the object eval itk_initialize $args # set the frame for the particle Frame [uplevel \#0 $fncallback] bind ${_name}-FrameEvent <> [itcl::code $this CatchEvent Frame] } # ---------------------------------------------------------------------- # DESTRUCTOR # ---------------------------------------------------------------------- itcl::body Rappture::VideoDistance::destructor {} { configure -px2dist "" ;# remove variable trace Hide object _fixBindings disable if {"" != $itk_option(-ondelete)} { uplevel \#0 $itk_option(-ondelete) } } # ---------------------------------------------------------------------- # Frame ?? - update the frame this object is in # ---------------------------------------------------------------------- itcl::body Rappture::VideoDistance::Frame {args} { if {[llength $args] == 1} { set val [lindex $args 0] if {([string is integer $val] != 1)} { error "bad value: \"$val\": frame number should be an integer" } set _frame $val if {"" != $itk_option(-onframe)} { uplevel \#0 $itk_option(-onframe) ${_frame} } } elseif {[llength $args] != 0} { error "wrong # args: should be \"Frame ??\"" } return ${_frame} } # ---------------------------------------------------------------------- # Coords ? ? - update the coordinates of this object # ---------------------------------------------------------------------- itcl::body Rappture::VideoDistance::Coords {args} { if {[llength $args] == 0} { return ${_coords} } elseif {[llength $args] == 1} { foreach {x0 y0 x1 y1} [lindex $args 0] break } elseif {[llength $args] == 4} { foreach {x0 y0 x1 y1} $args break } else { error "wrong # args: should be \"Coords ? ?\"" } if {([string is double $x0] != 1)} { error "bad value: \"$x0\": x coordinate should be a double" } if {([string is double $y0] != 1)} { error "bad value: \"$y0\": y coordinate should be a double" } if {([string is double $x1] != 1)} { error "bad value: \"$x1\": x coordinate should be a double" } if {([string is double $y1] != 1)} { error "bad value: \"$y1\": y coordinate should be a double" } set _coords [list $x0 $y0 $x1 $y1] if {[llength [${_canvas} find withtag ${_name}-line]] > 0} { eval ${_canvas} coords ${_name}-line ${_coords} } _fixValue return ${_coords} } # ---------------------------------------------------------------------- # Enter - bindings if the mouse enters the object's space # ---------------------------------------------------------------------- itcl::body Rappture::VideoDistance::Enter {} { uplevel \#0 $bindentercb } # ---------------------------------------------------------------------- # Leave - bindings if the mouse leaves the object's space # ---------------------------------------------------------------------- itcl::body Rappture::VideoDistance::Leave {} { uplevel \#0 $bindleavecb } # ---------------------------------------------------------------------- # CatchEvent - bindings for caught events # ---------------------------------------------------------------------- itcl::body Rappture::VideoDistance::CatchEvent {event} { switch -- $event { "Frame" { if {[uplevel \#0 $fncallback] == ${_frame}} { ${_canvas} itemconfigure ${_name}-line -fill red } else { ${_canvas} itemconfigure ${_name}-line -fill ${_color} } } default { error "bad event \"$event\": should be one of Frame." } } } # ---------------------------------------------------------------------- # Show - put properties of the object on the canvas # object - draw the object on the canvas # name - popup a ballon with the name of this object # ---------------------------------------------------------------------- itcl::body Rappture::VideoDistance::Show {args} { set option [lindex $args 0] switch -- $option { "object" { if {[llength $args] != 1} { error "wrong # args: should be \"object\"" } ${_canvas} create line ${_coords} \ -fill ${_color}\ -width 2 \ -tags "measure ${_name} ${_name}-line" \ -dash {4 4} \ -arrow both } "name" { } default { error "bad option \"$option\": should be one of object, name." } } } # ---------------------------------------------------------------------- # Hide # object - remove the particle from where it is drawn # name - remove the popup with the name # ---------------------------------------------------------------------- itcl::body Rappture::VideoDistance::Hide {args} { set option [lindex $args 0] switch -- $option { "object" { if {[llength $args] != 1} { error "wrong # args: should be \"object\"" } ${_canvas} delete "${_name}" } "name" { } default { error "bad option \"$option\": should be one of object, name." } } } # ---------------------------------------------------------------------- # Move - move the object to a new location # ---------------------------------------------------------------------- itcl::body Rappture::VideoDistance::Move {status x y} { switch -- $status { "press" { set _x $x set _y $y } "motion" { ${_canvas} move ${_name} [expr $x-${_x}] [expr $y-${_y}] set _coords [${_canvas} coords ${_name}-line] set _x $x set _y $y } "release" { } default { error "bad option \"$option\": should be one of press, motion, release." } } } # ---------------------------------------------------------------------- # Menu - popup a menu with the particle controls # create # activate x y # deactivate status # ---------------------------------------------------------------------- itcl::body Rappture::VideoDistance::Menu {args} { set option [lindex $args 0] switch -- $option { "activate" { if {[llength $args] != 3} { error "wrong # args: should be \"activate \"" } foreach {x y} [lrange $args 1 end] break set dir "left" set x0 [winfo rootx ${_canvas}] set y0 [winfo rooty ${_canvas}] set w0 [winfo width ${_canvas}] set h0 [winfo height ${_canvas}] set x [expr $x0+$x] set y [expr $y0+$y] $itk_component(menu) activate @$x,$y $dir # update the values in the menu set controls [$itk_component(menu) component inner] foreach {x0 y0 x1 y1} ${_coords} break $controls.measuremente delete 0 end $controls.measuremente insert 0 "${_dist} ${_units}" $controls.framenume value ${_frame} $controls.x0e value $x0 $controls.y0e value $y0 $controls.x1e value $x1 $controls.y1e value $y1 $controls.deleteb value false } "deactivate" { $itk_component(menu) deactivate if {[llength $args] != 2} { error "wrong # args: should be \"deactivate \"" } set status [lindex $args 1] switch -- $status { "save" { set controls [$itk_component(menu) component inner] set newframenum [$controls.framenume value] if {${_frame} != $newframenum} { Frame $newframenum } foreach {oldx0 oldy0 oldx1 oldy1} ${_coords} break set newx0 [$controls.x0e value] set newy0 [$controls.y0e value] set newx1 [$controls.x1e value] set newy1 [$controls.y1e value] if {$oldx0 != $newx0 || $oldy0 != $newy0 || $oldx1 != $newx1 || $oldy1 != $newy1} { Coords $newx0 $newy0 $newx1 $newy1 } set newdist [Rappture::Units::convert \ [$controls.measuremente get] \ -context ${_units} -units off] if {$newdist != ${_dist}} { # update the distance displayed set px [expr sqrt(pow(($newx1-$newx0),2)+pow(($newy1-$newy0),2))] set px2dist [expr $newdist/$px] _fixPx2Dist $px2dist } if {[$controls.deleteb value]} { itcl::delete object $this } } "cancel" { } "default" { error "bad value \"$status\": should be one of save, cancel" } } } default { error "bad option \"$option\": should be one of activate, deactivate." } } } # ---------------------------------------------------------------------- # _fixBindings - enable/disable bindings # enable # disable # ---------------------------------------------------------------------- itcl::body Rappture::VideoDistance::_fixBindings {status} { switch -- $status { "enable" { ${_canvas} bind ${_name} [itcl::code $this Move press %x %y] ${_canvas} bind ${_name} [itcl::code $this Move motion %x %y] ${_canvas} bind ${_name} [itcl::code $this Move release %x %y] ${_canvas} bind ${_name} [itcl::code $this Menu activate %x %y] ${_canvas} bind ${_name} [itcl::code $this Enter] ${_canvas} bind ${_name} [itcl::code $this Leave] ${_canvas} bind ${_name} { } ${_canvas} bind ${_name} { } bindtags ${_canvas} [concat "${_name}-FrameEvent" [bindtags ${_canvas}]] } "disable" { ${_canvas} bind ${_name} { } ${_canvas} bind ${_name} { } ${_canvas} bind ${_name} { } ${_canvas} bind ${_name} { } ${_canvas} bind ${_name} { } ${_canvas} bind ${_name} { } ${_canvas} bind ${_name} { } ${_canvas} bind ${_name} { } set tagnum [lsearch [bindtags ${_canvas}] "${_name}-FrameEvent"] if {$tagnum >= 0} { bindtags ${_canvas} [lreplace [bindtags ${_canvas}] $tagnum $tagnum] } } default { error "bad option \"$status\": should be one of enable, disable." } } } # ---------------------------------------------------------------------- # USAGE: _fixPx2Dist # Invoked whenever the value for this object is changed by the user # via the popup menu. # ---------------------------------------------------------------------- itcl::body Rappture::VideoDistance::_fixPx2Dist {px2dist} { if {"" == $itk_option(-px2dist)} { return } upvar #0 $itk_option(-px2dist) var set var $px2dist } # ---------------------------------------------------------------------- # USAGE: _fixValue # Invoked automatically whenever the -px2dist associated with this # widget is modified. Copies the value to the current settings for # the widget. # ---------------------------------------------------------------------- itcl::body Rappture::VideoDistance::_fixValue {args} { if {"" == $itk_option(-px2dist)} { return } upvar #0 $itk_option(-px2dist) var if {"" == ${_coords}} { # no coords, skip calculation return } # calculate the length foreach {x0 y0 x1 y1} ${_coords} break set px [expr sqrt(pow(($x1-$x0),2)+pow(($y1-$y0),2))] set _dist [expr $px*$var] # run the new value through units conversion to round # it off so when we show it in the menu and compare it # to the value that comes back from the menu, we don't # get differences in value due to rounding. set _dist [Rappture::Units::convert ${_dist} -context ${_units} -units off] set x [expr "$x0 + (($x1-$x0)/2)"] set y [expr "$y0 + (($y1-$y0)/2)"] set tt "${_dist} ${_units}" set tags "meastext ${_name} ${_name}-val" set width [expr sqrt(pow(abs($x1-$x0),2)+pow(abs($y1-$y0),2))] set args [list $x $y "$tt" "${_color}" "$tags" $width] # remove old text ${_canvas} delete ${_name}-val set controls [$itk_component(menu) component inner] if {![$controls.deleteb value]} { # if the object is not hidden, write _dist to the canvas uplevel \#0 $writetextcb $args } } # ---------------------------------------------------------------------- # CONFIGURATION OPTION: -color # ---------------------------------------------------------------------- itcl::configbody Rappture::VideoDistance::color { if {[string compare "" $itk_option(-color)] != 0} { # FIXME how to tell if the color is valid? set _color $itk_option(-color) } else { error "bad value: \"$itk_option(-color)\": should be a valid color" } } # ---------------------------------------------------------------------- # CONFIGURE: -px2dist # ---------------------------------------------------------------------- itcl::configbody Rappture::VideoDistance::px2dist { if {"" != $_px2dist} { upvar #0 $_px2dist var trace remove variable var write [itcl::code $this _fixValue] } set _px2dist $itk_option(-px2dist) if {"" != $_px2dist} { upvar #0 $_px2dist var trace add variable var write [itcl::code $this _fixValue] # sync to the current value of this variable if {[info exists var]} { _fixValue } } } # ---------------------------------------------------------------------- # CONFIGURE: -units # ---------------------------------------------------------------------- itcl::configbody Rappture::VideoDistance::units { set _units $itk_option(-units) # _fixValue } # ---------------------------------------------------------------------- # CONFIGURE: -bindings # ---------------------------------------------------------------------- itcl::configbody Rappture::VideoDistance::bindings { _fixBindings $itk_option(-bindings) } # ----------------------------------------------------------------------