# # Watch # ---------------------------------------------------------------------- # Implements a a clock widget in a canvas. # # ---------------------------------------------------------------------- # AUTHOR: John A. Tucker EMAIL: jatucker@spd.dsccc.com # # ====================================================================== # Copyright (c) 1997 DSC Technologies Corporation # ====================================================================== # Permission to use, copy, modify, distribute and license this software # and its documentation for any purpose, and without fee or written # agreement with DSC, is hereby granted, provided that the above copyright # notice appears in all copies and that both the copyright notice and # warranty disclaimer below appear in supporting documentation, and that # the names of DSC Technologies Corporation or DSC Communications # Corporation not be used in advertising or publicity pertaining to the # software without specific, written prior permission. # # DSC DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING # ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, AND NON- # INFRINGEMENT. THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, AND THE # AUTHORS AND DISTRIBUTORS HAVE NO OBLIGATION TO PROVIDE MAINTENANCE, # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. IN NO EVENT SHALL # DSC BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR # ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, # WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTUOUS ACTION, # ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS # SOFTWARE. # ====================================================================== # # Default resources. # option add *Watch.labelFont \ -*-Courier-Medium-R-Normal--*-120-*-*-*-*-*-* widgetDefault # # Usual options. # itk::usual Watch { keep -background -cursor -labelfont -foreground } class iwidgets::Watch { inherit itk::Widget itk_option define -hourradius hourRadius Radius .50 itk_option define -hourcolor hourColor Color red itk_option define -minuteradius minuteRadius Radius .80 itk_option define -minutecolor minuteColor Color yellow itk_option define -pivotradius pivotRadius Radius .10 itk_option define -pivotcolor pivotColor Color white itk_option define -secondradius secondRadius Radius .90 itk_option define -secondcolor secondColor Color black itk_option define -clockcolor clockColor Color white itk_option define -clockstipple clockStipple ClockStipple {} itk_option define -state state State normal itk_option define -showampm showAmPm ShowAmPm true itk_option define -tickcolor tickColor Color black constructor {args} {} destructor {} # # Public methods # public { method get {{format "-string"}} method show {{time "now"}} method watch {args} } # # Private methods # private { method _handMotionCB {tag x y} method _drawHand {tag} method _handReleaseCB {tag x y} method _displayClock {{when "later"}} variable _interior variable _radius variable _theta variable _extent variable _reposition "" ;# non-null => _displayClock pending variable _timeVar variable _x0 1 variable _y0 1 common _ampmVar common PI [expr 2*asin(1.0)] } } # # Provide a lowercased access method for the Watch class. # proc ::iwidgets::watch {pathName args} { uplevel ::iwidgets::Watch $pathName $args } # # Use option database to override default resources of base classes. # option add *Watch.width 155 widgetDefault option add *Watch.height 175 widgetDefault # ----------------------------------------------------------------------------- # CONSTRUCTOR # ----------------------------------------------------------------------------- body iwidgets::Watch::constructor { args } { # # Add back to the hull width and height options and make the # borderwidth zero since we don't need it. # set _interior $itk_interior itk_option add hull.width hull.height component hull configure -borderwidth 0 grid propagate $itk_component(hull) no set _ampmVar($this) "AM" set _radius(outer) 1 set _radius(hour) 1 set _radius(minute) 1 set _radius(second) 1 set _theta(hour) 30 set _theta(minute) 6 set _theta(second) 6 set _extent(hour) 14 set _extent(minute) 14 set _extent(second) 2 set _timeVar(hour) 12 set _timeVar(minute) 0 set _timeVar(second) 0 # # Create the frame in which the "AM" and "PM" radiobuttons will be drawn # itk_component add frame { frame $itk_interior.frame } # # Create the canvas in which the clock will be drawn # itk_component add canvas { canvas $itk_interior.canvas } bind $itk_component(canvas) +[code $this _displayClock] bind $itk_component(canvas) +[code $this _displayClock] # # Create the "AM" and "PM" radiobuttons to be drawn in the canvas # itk_component add am { radiobutton $itk_component(frame).am \ -text "AM" \ -value "AM" \ -variable [scope _ampmVar($this)] } { usual rename -font -labelfont labelFont Font } itk_component add pm { radiobutton $itk_component(frame).pm \ -text "PM" \ -value "PM" \ -variable [scope _ampmVar($this)] } { usual rename -font -labelfont labelFont Font } # # Create the canvas item for displaying the main oval which encapsulates # the entire clock. # watch create oval 0 0 2 2 -width 5 -tags clock # # Create the canvas items for displaying the 60 ticks marks around the # inner perimeter of the watch. # set extent 3 for {set i 0} {$i < 60} {incr i} { set start [expr $i*6-1] set tag [expr {[expr $i%5] == 0 ? "big" : "little"}] watch create arc 0 0 0 0 \ -style arc \ -extent $extent \ -start $start \ -tags "tick$i tick $tag" } # # Create the canvas items for displaying the hour, minute, and second hands # of the watch. Add bindings to allow the mouse to move and set the # clock hands. # watch create arc 1 1 1 1 -extent 30 -tags minute watch create arc 1 1 1 1 -extent 30 -tags hour watch create arc 1 1 1 1 -tags second # # Create the canvas item for displaying the center of the watch in which # the hour, minute, and second hands will pivot. # watch create oval 0 0 1 1 -width 5 -fill black -tags pivot # # Position the "AM/PM" button frame and watch canvas. # grid $itk_component(frame) -row 0 -column 0 -sticky new grid $itk_component(canvas) -row 1 -column 0 -sticky nsew grid rowconfigure $itk_interior 0 -weight 0 grid rowconfigure $itk_interior 1 -weight 1 grid columnconfigure $itk_interior 0 -weight 1 eval itk_initialize $args } # ----------------------------------------------------------------------------- # DESTURCTOR # ----------------------------------------------------------------------------- body iwidgets::Watch::destructor {} { if {$_reposition != ""} { after cancel $_reposition } } # ----------------------------------------------------------------------------- # METHODS # ----------------------------------------------------------------------------- # ----------------------------------------------------------------------------- # METHOD: _handReleaseCB tag x y # # ----------------------------------------------------------------------------- body iwidgets::Watch::_handReleaseCB {tag x y} { set atanab [expr atan2(double($y-$_y0),double($x-$_x0))*(180/$PI)] set degrees [expr {$atanab > 0 ? [expr 360-$atanab] : abs($atanab)}] set ticks [expr round($degrees/$_theta($tag))] set _timeVar($tag) [expr ((450-$ticks*$_theta($tag))%360)/$_theta($tag)] if {$tag == "hour" && $_timeVar(hour) == 0} { set _timeVar($tag) 12 } _drawHand $tag } # ----------------------------------------------------------------------------- # PROTECTED METHOD: _handMotionCB tag x y # # ----------------------------------------------------------------------------- body iwidgets::Watch::_handMotionCB {tag x y} { if {$x == $_x0 || $y == $_y0} { return } set a [expr $y-$_y0] set b [expr $x-$_x0] set c [expr hypot($a,$b)] set atanab [expr atan2(double($a),double($b))*(180/$PI)] set degrees [expr {$atanab > 0 ? [expr 360-$atanab] : abs($atanab)}] set x2 [expr $_x0+$_radius($tag)*($b/double($c))] set y2 [expr $_y0+$_radius($tag)*($a/double($c))] watch coords $tag \ [expr $x2-$_radius($tag)] \ [expr $y2-$_radius($tag)] \ [expr $x2+$_radius($tag)] \ [expr $y2+$_radius($tag)] set start [expr $degrees-180-($_extent($tag)/2)] watch itemconfigure $tag -start $start -extent $_extent($tag) } # ----------------------------------------------------------------------------- # PROTECTED METHOD: get ?format? # # ----------------------------------------------------------------------------- body iwidgets::Watch::get {{format "-string"}} { set timestr [format "%02d:%02d:%02d %s" \ $_timeVar(hour) $_timeVar(minute) \ $_timeVar(second) $_ampmVar($this)] switch -- $format { "-string" { return $timestr } "-clicks" { return [clock scan $timestr] } default { error "bad format option \"$format\":\ should be -string or -clicks" } } } # ----------------------------------------------------------------------------- # METHOD: watch ?args? # # Evaluates the specified args against the canvas component. # ----------------------------------------------------------------------------- body iwidgets::Watch::watch {args} { return [eval $itk_component(canvas) $args] } # ----------------------------------------------------------------------------- # METHOD: _drawHand tag # # ----------------------------------------------------------------------------- body iwidgets::Watch::_drawHand {tag} { set degrees [expr abs(450-($_timeVar($tag)*$_theta($tag)))%360] set radians [expr $degrees*($PI/180)] set x [expr $_x0+$_radius($tag)*cos($radians)] set y [expr $_y0+$_radius($tag)*sin($radians)*(-1)] watch coords $tag \ [expr $x-$_radius($tag)] \ [expr $y-$_radius($tag)] \ [expr $x+$_radius($tag)] \ [expr $y+$_radius($tag)] set start [expr $degrees-180-($_extent($tag)/2)] watch itemconfigure $tag -start $start } # ------------------------------------------------------------------ # PUBLIC METHOD: show time # # Changes the currently displayed time to be that of the time # argument. The time may be specified either as a string or an # integer clock value. Reference the clock command for more # information on obtaining times and their formats. # ------------------------------------------------------------------ body iwidgets::Watch::show {{time "now"}} { if {$time == "now"} { set seconds [clock seconds] } elseif {![catch {clock format $time}]} { set seconds $time } elseif {[catch {set seconds [clock scan $time]}]} { error "bad time: \"$time\", must be a valid time\ string, clock clicks value or the keyword now" } set timestring [clock format $seconds -format "%I %M %S %p"] set _timeVar(hour) [expr int(1[lindex $timestring 0] - 100)] set _timeVar(minute) [expr int(1[lindex $timestring 1] - 100)] set _timeVar(second) [expr int(1[lindex $timestring 2] - 100)] set _ampmVar($this) [lindex $timestring 3] _drawHand hour _drawHand minute _drawHand second } # ----------------------------------------------------------------------------- # PROTECTED METHOD: _displayClock ?when? # # Places the hour, minute, and second dials in the canvas. If "when" is "now", # the change is applied immediately. If it is "later" or it is not specified, # then the change is applied later, when the application is idle. # ----------------------------------------------------------------------------- body iwidgets::Watch::_displayClock {{when "later"}} { if {$when == "later"} { if {$_reposition == ""} { set _reposition [after idle [code $this _displayClock now]] } return } # # Compute the center coordinates for the clock based on the # with and height of the canvas. # set width [winfo width $itk_component(canvas)] set height [winfo height $itk_component(canvas)] set _x0 [expr $width/2] set _y0 [expr $height/2] # # Set the radius of the watch, pivot, hour, minute and second items. # set _radius(outer) [expr {$_x0 < $_y0 ? $_x0 : $_y0}] set _radius(pivot) [expr $itk_option(-pivotradius)*$_radius(outer)] set _radius(hour) [expr $itk_option(-hourradius)*$_radius(outer)] set _radius(minute) [expr $itk_option(-minuteradius)*$_radius(outer)] set _radius(second) [expr $itk_option(-secondradius)*$_radius(outer)] set outerWidth [watch itemcget clock -width] # # Set the coordinates of the clock item # set x1Outer $outerWidth set y1Outer $outerWidth set x2Outer [expr $width-$outerWidth] set y2Outer [expr $height-$outerWidth] watch coords clock $x1Outer $y1Outer $x2Outer $y2Outer # # Set the coordinates of the tick items # set offset [expr $outerWidth*2] set x1Tick [expr $x1Outer+$offset] set y1Tick [expr $y1Outer+$offset] set x2Tick [expr $x2Outer-$offset] set y2Tick [expr $y2Outer-$offset] for {set i 0} {$i < 60} {incr i} { watch coords tick$i $x1Tick $y1Tick $x2Tick $y2Tick } set maxTickWidth [expr $_radius(outer)-$_radius(second)+1] set minTickWidth [expr round($maxTickWidth/2)] watch itemconfigure big -width $maxTickWidth watch itemconfigure little -width [expr round($maxTickWidth/2)] # # Set the coordinates of the pivot item # set x1Center [expr $_x0-$_radius(pivot)] set y1Center [expr $_y0-$_radius(pivot)] set x2Center [expr $_x0+$_radius(pivot)] set y2Center [expr $_y0+$_radius(pivot)] watch coords pivot $x1Center $y1Center $x2Center $y2Center # # Set the coordinates of the hour, minute, and second dial items # watch itemconfigure hour -extent $_extent(hour) _drawHand hour watch itemconfigure minute -extent $_extent(minute) _drawHand minute watch itemconfigure second -extent $_extent(second) _drawHand second set _reposition "" } # ----------------------------------------------------------------------------- # OPTIONS # ----------------------------------------------------------------------------- # ------------------------------------------------------------------ # OPTION: state # # Configure the editable state of the widget. Valid values are # normal and disabled. In a disabled state, the hands of the # watch are not selectabled. # ------------------------------------------------------------------ configbody ::iwidgets::Watch::state { if {$itk_option(-state) == "normal"} { watch bind minute \ [code $this _handMotionCB minute %x %y] watch bind minute \ [code $this _handReleaseCB minute %x %y] watch bind hour \ [code $this _handMotionCB hour %x %y] watch bind hour \ [code $this _handReleaseCB hour %x %y] watch bind second \ [code $this _handMotionCB second %x %y] watch bind second \ [code $this _handReleaseCB second %x %y] $itk_component(am) configure -state normal $itk_component(pm) configure -state normal } elseif {$itk_option(-state) == "disabled"} { watch bind minute {} watch bind minute {} watch bind hour {} watch bind hour {} watch bind second {} watch bind second {} $itk_component(am) configure -state disabled \ -disabledforeground [$itk_component(am) cget -background] $itk_component(pm) configure -state normal \ -disabledforeground [$itk_component(am) cget -background] } else { error "bad state option \"$itk_option(-state)\":\ should be normal or disabled" } } # ------------------------------------------------------------------ # OPTION: showampm # # Configure the display of the AM/PM radio buttons. # ------------------------------------------------------------------ configbody ::iwidgets::Watch::showampm { switch -- $itk_option(-showampm) { 0 - no - false - off { pack forget $itk_component(am) pack forget $itk_component(pm) } 1 - yes - true - on { pack $itk_component(am) -side left -fill both -expand 1 pack $itk_component(pm) -side right -fill both -expand 1 } default { error "bad showampm option \"$itk_option(-showampm)\":\ should be boolean" } } } # ------------------------------------------------------------------ # OPTION: pivotcolor # # Configure the color of the clock pivot. # configbody ::iwidgets::Watch::pivotcolor { watch itemconfigure pivot -fill $itk_option(-pivotcolor) } # ------------------------------------------------------------------ # OPTION: clockstipple # # Configure the stipple pattern for the clock fill color. # configbody ::iwidgets::Watch::clockstipple { watch itemconfigure clock -stipple $itk_option(-clockstipple) } # ------------------------------------------------------------------ # OPTION: clockcolor # # Configure the color of the clock. # configbody ::iwidgets::Watch::clockcolor { watch itemconfigure clock -fill $itk_option(-clockcolor) } # ------------------------------------------------------------------ # OPTION: hourcolor # # Configure the color of the hour hand. # configbody ::iwidgets::Watch::hourcolor { watch itemconfigure hour -fill $itk_option(-hourcolor) } # ------------------------------------------------------------------ # OPTION: minutecolor # # Configure the color of the minute hand. # configbody ::iwidgets::Watch::minutecolor { watch itemconfigure minute -fill $itk_option(-minutecolor) } # ------------------------------------------------------------------ # OPTION: secondcolor # # Configure the color of the second hand. # configbody ::iwidgets::Watch::secondcolor { watch itemconfigure second -fill $itk_option(-secondcolor) } # ------------------------------------------------------------------ # OPTION: tickcolor # # Configure the color of the ticks. # configbody ::iwidgets::Watch::tickcolor { watch itemconfigure tick -outline $itk_option(-tickcolor) } # ------------------------------------------------------------------ # OPTION: hourradius # # Configure the radius of the hour hand. # configbody ::iwidgets::Watch::hourradius { _displayClock } # ------------------------------------------------------------------ # OPTION: minuteradius # # Configure the radius of the minute hand. # configbody ::iwidgets::Watch::minuteradius { _displayClock } # ------------------------------------------------------------------ # OPTION: secondradius # # Configure the radius of the second hand. # configbody ::iwidgets::Watch::secondradius { _displayClock }