1# 2# Watch 3# ---------------------------------------------------------------------- 4# Implements a a clock widget in a canvas. 5# 6# ---------------------------------------------------------------------- 7# AUTHOR: John A. Tucker EMAIL: jatucker@spd.dsccc.com 8# 9# ====================================================================== 10# Copyright (c) 1997 DSC Technologies Corporation 11# ====================================================================== 12# Permission to use, copy, modify, distribute and license this software 13# and its documentation for any purpose, and without fee or written 14# agreement with DSC, is hereby granted, provided that the above copyright 15# notice appears in all copies and that both the copyright notice and 16# warranty disclaimer below appear in supporting documentation, and that 17# the names of DSC Technologies Corporation or DSC Communications 18# Corporation not be used in advertising or publicity pertaining to the 19# software without specific, written prior permission. 20# 21# DSC DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING 22# ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, AND NON- 23# INFRINGEMENT. THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, AND THE 24# AUTHORS AND DISTRIBUTORS HAVE NO OBLIGATION TO PROVIDE MAINTENANCE, 25# SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. IN NO EVENT SHALL 26# DSC BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR 27# ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, 28# WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTUOUS ACTION, 29# ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS 30# SOFTWARE. 31# ====================================================================== 32 33# 34# Default resources. 35# 36option add *Watch.labelFont \ 37 -*-Courier-Medium-R-Normal--*-120-*-*-*-*-*-* widgetDefault 38 39# 40# Usual options. 41# 42itk::usual Watch { 43 keep -background -cursor -labelfont -foreground 44} 45 46itcl::class iwidgets::Watch { 47 48 inherit itk::Widget 49 50 itk_option define -hourradius hourRadius Radius .50 51 itk_option define -hourcolor hourColor Color red 52 53 itk_option define -minuteradius minuteRadius Radius .80 54 itk_option define -minutecolor minuteColor Color yellow 55 56 itk_option define -pivotradius pivotRadius Radius .10 57 itk_option define -pivotcolor pivotColor Color white 58 59 itk_option define -secondradius secondRadius Radius .90 60 itk_option define -secondcolor secondColor Color black 61 62 itk_option define -clockcolor clockColor Color white 63 itk_option define -clockstipple clockStipple ClockStipple {} 64 65 itk_option define -state state State normal 66 itk_option define -showampm showAmPm ShowAmPm true 67 68 itk_option define -tickcolor tickColor Color black 69 70 constructor {args} {} 71 destructor {} 72 73 # 74 # Public methods 75 # 76 public { 77 method get {{format "-string"}} 78 method show {{time "now"}} 79 method watch {args} 80 } 81 82 # 83 # Private methods 84 # 85 private { 86 method _handMotionCB {tag x y} 87 method _drawHand {tag} 88 method _handReleaseCB {tag x y} 89 method _displayClock {{when "later"}} 90 91 variable _interior 92 variable _radius 93 variable _theta 94 variable _extent 95 variable _reposition "" ;# non-null => _displayClock pending 96 variable _timeVar 97 variable _x0 1 98 variable _y0 1 99 100 common _ampmVar 101 common PI [expr {2*asin(1.0)}] 102 } 103} 104 105# 106# Provide a lowercased access method for the Watch class. 107# 108proc ::iwidgets::watch {pathName args} { 109 uplevel ::iwidgets::Watch $pathName $args 110} 111 112# 113# Use option database to override default resources of base classes. 114# 115option add *Watch.width 155 widgetDefault 116option add *Watch.height 175 widgetDefault 117 118# ----------------------------------------------------------------------------- 119# CONSTRUCTOR 120# ----------------------------------------------------------------------------- 121itcl::body iwidgets::Watch::constructor { args } { 122 # 123 # Add back to the hull width and height options and make the 124 # borderwidth zero since we don't need it. 125 # 126 set _interior $itk_interior 127 128 itk_option add hull.width hull.height 129 component hull configure -borderwidth 0 130 grid propagate $itk_component(hull) no 131 132 set _ampmVar($this) "AM" 133 set _radius(outer) 1 134 135 set _radius(hour) 1 136 set _radius(minute) 1 137 set _radius(second) 1 138 139 set _theta(hour) 30 140 set _theta(minute) 6 141 set _theta(second) 6 142 143 set _extent(hour) 14 144 set _extent(minute) 14 145 set _extent(second) 2 146 147 set _timeVar(hour) 12 148 set _timeVar(minute) 0 149 set _timeVar(second) 0 150 151 # 152 # Create the frame in which the "AM" and "PM" radiobuttons will be drawn 153 # 154 itk_component add frame { 155 frame $itk_interior.frame 156 } 157 158 # 159 # Create the canvas in which the clock will be drawn 160 # 161 itk_component add canvas { 162 canvas $itk_interior.canvas 163 } 164 bind $itk_component(canvas) <Map> +[itcl::code $this _displayClock] 165 bind $itk_component(canvas) <Configure> +[itcl::code $this _displayClock] 166 167 # 168 # Create the "AM" and "PM" radiobuttons to be drawn in the canvas 169 # 170 itk_component add am { 171 radiobutton $itk_component(frame).am \ 172 -text "AM" \ 173 -value "AM" \ 174 -variable [itcl::scope _ampmVar($this)] 175 } { 176 usual 177 rename -font -labelfont labelFont Font 178 } 179 180 itk_component add pm { 181 radiobutton $itk_component(frame).pm \ 182 -text "PM" \ 183 -value "PM" \ 184 -variable [itcl::scope _ampmVar($this)] 185 } { 186 usual 187 rename -font -labelfont labelFont Font 188 } 189 190 # 191 # Create the canvas item for displaying the main oval which encapsulates 192 # the entire clock. 193 # 194 watch create oval 0 0 2 2 -width 5 -tags clock 195 196 # 197 # Create the canvas items for displaying the 60 ticks marks around the 198 # inner perimeter of the watch. 199 # 200 set extent 3 201 for {set i 0} {$i < 60} {incr i} { 202 set start [expr {$i*6-1}] 203 set tag [expr {[expr {$i%5}] == 0 ? "big" : "little"}] 204 watch create arc 0 0 0 0 \ 205 -style arc \ 206 -extent $extent \ 207 -start $start \ 208 -tags "tick$i tick $tag" 209 } 210 211 # 212 # Create the canvas items for displaying the hour, minute, and second hands 213 # of the watch. Add bindings to allow the mouse to move and set the 214 # clock hands. 215 # 216 watch create arc 1 1 1 1 -extent 30 -tags minute 217 watch create arc 1 1 1 1 -extent 30 -tags hour 218 watch create arc 1 1 1 1 -tags second 219 220 # 221 # Create the canvas item for displaying the center of the watch in which 222 # the hour, minute, and second hands will pivot. 223 # 224 watch create oval 0 0 1 1 -width 5 -fill black -tags pivot 225 226 # 227 # Position the "AM/PM" button frame and watch canvas. 228 # 229 grid $itk_component(frame) -row 0 -column 0 -sticky new 230 grid $itk_component(canvas) -row 1 -column 0 -sticky nsew 231 232 grid rowconfigure $itk_interior 0 -weight 0 233 grid rowconfigure $itk_interior 1 -weight 1 234 grid columnconfigure $itk_interior 0 -weight 1 235 236 eval itk_initialize $args 237} 238 239# ----------------------------------------------------------------------------- 240# DESTURCTOR 241# ----------------------------------------------------------------------------- 242itcl::body iwidgets::Watch::destructor {} { 243 if {$_reposition != ""} { 244 after cancel $_reposition 245 } 246} 247 248# ----------------------------------------------------------------------------- 249# METHODS 250# ----------------------------------------------------------------------------- 251 252# ----------------------------------------------------------------------------- 253# METHOD: _handReleaseCB tag x y 254# 255# ----------------------------------------------------------------------------- 256itcl::body iwidgets::Watch::_handReleaseCB {tag x y} { 257 258 set atanab [expr {atan2(double($y-$_y0),double($x-$_x0))*(180/$PI)}] 259 set degrees [expr {$atanab > 0 ? [expr {360-$atanab}] : abs($atanab)}] 260 set ticks [expr {round($degrees/$_theta($tag))}] 261 set _timeVar($tag) [expr {((450-$ticks*$_theta($tag))%360)/$_theta($tag)}] 262 263 if {$tag == "hour" && $_timeVar(hour) == 0} { 264 set _timeVar($tag) 12 265 } 266 267 _drawHand $tag 268} 269 270# ----------------------------------------------------------------------------- 271# PROTECTED METHOD: _handMotionCB tag x y 272# 273# ----------------------------------------------------------------------------- 274itcl::body iwidgets::Watch::_handMotionCB {tag x y} { 275 if {$x == $_x0 || $y == $_y0} { 276 return 277 } 278 279 set a [expr {$y-$_y0}] 280 set b [expr {$x-$_x0}] 281 set c [expr {hypot($a,$b)}] 282 283 set atanab [expr {atan2(double($a),double($b))*(180/$PI)}] 284 set degrees [expr {$atanab > 0 ? [expr 360-$atanab] : abs($atanab)}] 285 286 set x2 [expr {$_x0+$_radius($tag)*($b/double($c))}] 287 set y2 [expr {$_y0+$_radius($tag)*($a/double($c))}] 288 watch coords $tag \ 289 [expr {$x2-$_radius($tag)}] \ 290 [expr {$y2-$_radius($tag)}] \ 291 [expr {$x2+$_radius($tag)}] \ 292 [expr {$y2+$_radius($tag)}] 293 set start [expr {$degrees-180-($_extent($tag)/2)}] 294 watch itemconfigure $tag -start $start -extent $_extent($tag) 295} 296 297# ----------------------------------------------------------------------------- 298# PROTECTED METHOD: get ?format? 299# 300# ----------------------------------------------------------------------------- 301itcl::body iwidgets::Watch::get {{format "-string"}} { 302 set timestr [format "%02d:%02d:%02d %s" \ 303 $_timeVar(hour) $_timeVar(minute) \ 304 $_timeVar(second) $_ampmVar($this)] 305 306 switch -- $format { 307 "-string" { 308 return $timestr 309 } 310 "-clicks" { 311 return [clock scan $timestr] 312 } 313 default { 314 error "bad format option \"$format\":\ 315 should be -string or -clicks" 316 } 317 } 318} 319 320# ----------------------------------------------------------------------------- 321# METHOD: watch ?args? 322# 323# Evaluates the specified args against the canvas component. 324# ----------------------------------------------------------------------------- 325itcl::body iwidgets::Watch::watch {args} { 326 return [eval $itk_component(canvas) $args] 327} 328 329# ----------------------------------------------------------------------------- 330# METHOD: _drawHand tag 331# 332# ----------------------------------------------------------------------------- 333itcl::body iwidgets::Watch::_drawHand {tag} { 334 335 set degrees [expr {abs(450-($_timeVar($tag)*$_theta($tag)))%360}] 336 set radians [expr {$degrees*($PI/180)}] 337 set x [expr {$_x0+$_radius($tag)*cos($radians)}] 338 set y [expr {$_y0+$_radius($tag)*sin($radians)*(-1)}] 339 watch coords $tag \ 340 [expr {$x-$_radius($tag)}] \ 341 [expr {$y-$_radius($tag)}] \ 342 [expr {$x+$_radius($tag)}] \ 343 [expr {$y+$_radius($tag)}] 344 set start [expr {$degrees-180-($_extent($tag)/2)}] 345 watch itemconfigure $tag -start $start 346} 347 348# ------------------------------------------------------------------ 349# PUBLIC METHOD: show time 350# 351# Changes the currently displayed time to be that of the time 352# argument. The time may be specified either as a string or an 353# integer clock value. Reference the clock command for more 354# information on obtaining times and their formats. 355# ------------------------------------------------------------------ 356itcl::body iwidgets::Watch::show {{time "now"}} { 357 if {$time == "now"} { 358 set seconds [clock seconds] 359 } elseif {![catch {clock format $time}]} { 360 set seconds $time 361 } elseif {[catch {set seconds [clock scan $time]}]} { 362 error "bad time: \"$time\", must be a valid time\ 363 string, clock clicks value or the keyword now" 364 } 365 366 set timestring [clock format $seconds -format "%I %M %S %p"] 367 set _timeVar(hour) [expr int(1[lindex $timestring 0] - 100)] 368 set _timeVar(minute) [expr int(1[lindex $timestring 1] - 100)] 369 set _timeVar(second) [expr int(1[lindex $timestring 2] - 100)] 370 set _ampmVar($this) [lindex $timestring 3] 371 372 _drawHand hour 373 _drawHand minute 374 _drawHand second 375} 376 377# ----------------------------------------------------------------------------- 378# PROTECTED METHOD: _displayClock ?when? 379# 380# Places the hour, minute, and second dials in the canvas. If "when" is "now", 381# the change is applied immediately. If it is "later" or it is not specified, 382# then the change is applied later, when the application is idle. 383# ----------------------------------------------------------------------------- 384itcl::body iwidgets::Watch::_displayClock {{when "later"}} { 385 386 if {$when == "later"} { 387 if {$_reposition == ""} { 388 set _reposition [after idle [itcl::code $this _displayClock now]] 389 } 390 return 391 } 392 393 # 394 # Compute the center coordinates for the clock based on the 395 # with and height of the canvas. 396 # 397 set width [winfo width $itk_component(canvas)] 398 set height [winfo height $itk_component(canvas)] 399 set _x0 [expr {$width/2}] 400 set _y0 [expr {$height/2}] 401 402 # 403 # Set the radius of the watch, pivot, hour, minute and second items. 404 # 405 set _radius(outer) [expr {$_x0 < $_y0 ? $_x0 : $_y0}] 406 set _radius(pivot) [expr {$itk_option(-pivotradius)*$_radius(outer)}] 407 set _radius(hour) [expr {$itk_option(-hourradius)*$_radius(outer)}] 408 set _radius(minute) [expr {$itk_option(-minuteradius)*$_radius(outer)}] 409 set _radius(second) [expr {$itk_option(-secondradius)*$_radius(outer)}] 410 set outerWidth [watch itemcget clock -width] 411 412 # 413 # Set the coordinates of the clock item 414 # 415 set x1Outer $outerWidth 416 set y1Outer $outerWidth 417 set x2Outer [expr {$width-$outerWidth}] 418 set y2Outer [expr {$height-$outerWidth}] 419 watch coords clock $x1Outer $y1Outer $x2Outer $y2Outer 420 421 # 422 # Set the coordinates of the tick items 423 # 424 set offset [expr {$outerWidth*2}] 425 set x1Tick [expr {$x1Outer+$offset}] 426 set y1Tick [expr {$y1Outer+$offset}] 427 set x2Tick [expr {$x2Outer-$offset}] 428 set y2Tick [expr {$y2Outer-$offset}] 429 for {set i 0} {$i < 60} {incr i} { 430 watch coords tick$i $x1Tick $y1Tick $x2Tick $y2Tick 431 } 432 set maxTickWidth [expr {$_radius(outer)-$_radius(second)+1}] 433 set minTickWidth [expr {round($maxTickWidth/2)}] 434 watch itemconfigure big -width $maxTickWidth 435 watch itemconfigure little -width [expr {round($maxTickWidth/2)}] 436 437 # 438 # Set the coordinates of the pivot item 439 # 440 set x1Center [expr {$_x0-$_radius(pivot)}] 441 set y1Center [expr {$_y0-$_radius(pivot)}] 442 set x2Center [expr {$_x0+$_radius(pivot)}] 443 set y2Center [expr {$_y0+$_radius(pivot)}] 444 watch coords pivot $x1Center $y1Center $x2Center $y2Center 445 446 # 447 # Set the coordinates of the hour, minute, and second dial items 448 # 449 watch itemconfigure hour -extent $_extent(hour) 450 _drawHand hour 451 452 watch itemconfigure minute -extent $_extent(minute) 453 _drawHand minute 454 455 watch itemconfigure second -extent $_extent(second) 456 _drawHand second 457 458 set _reposition "" 459} 460 461# ----------------------------------------------------------------------------- 462# OPTIONS 463# ----------------------------------------------------------------------------- 464 465# ------------------------------------------------------------------ 466# OPTION: state 467# 468# Configure the editable state of the widget. Valid values are 469# normal and disabled. In a disabled state, the hands of the 470# watch are not selectabled. 471# ------------------------------------------------------------------ 472itcl::configbody ::iwidgets::Watch::state { 473 if {$itk_option(-state) == "normal"} { 474 watch bind minute <B1-Motion> \ 475 [itcl::code $this _handMotionCB minute %x %y] 476 watch bind minute <ButtonRelease-1> \ 477 [itcl::code $this _handReleaseCB minute %x %y] 478 479 watch bind hour <B1-Motion> \ 480 [itcl::code $this _handMotionCB hour %x %y] 481 watch bind hour <ButtonRelease-1> \ 482 [itcl::code $this _handReleaseCB hour %x %y] 483 484 watch bind second <B1-Motion> \ 485 [itcl::code $this _handMotionCB second %x %y] 486 watch bind second <ButtonRelease-1> \ 487 [itcl::code $this _handReleaseCB second %x %y] 488 489 $itk_component(am) configure -state normal 490 $itk_component(pm) configure -state normal 491 492 } elseif {$itk_option(-state) == "disabled"} { 493 watch bind minute <B1-Motion> {} 494 watch bind minute <ButtonRelease-1> {} 495 496 watch bind hour <B1-Motion> {} 497 watch bind hour <ButtonRelease-1> {} 498 499 watch bind second <B1-Motion> {} 500 watch bind second <ButtonRelease-1> {} 501 502 $itk_component(am) configure -state disabled \ 503 -disabledforeground [$itk_component(am) cget -background] 504 $itk_component(pm) configure -state normal \ 505 -disabledforeground [$itk_component(am) cget -background] 506 507 } else { 508 error "bad state option \"$itk_option(-state)\":\ 509 should be normal or disabled" 510 } 511} 512 513# ------------------------------------------------------------------ 514# OPTION: showampm 515# 516# Configure the display of the AM/PM radio buttons. 517# ------------------------------------------------------------------ 518itcl::configbody ::iwidgets::Watch::showampm { 519 switch -- $itk_option(-showampm) { 520 0 - no - false - off { 521 pack forget $itk_component(am) 522 pack forget $itk_component(pm) 523 } 524 525 1 - yes - true - on { 526 pack $itk_component(am) -side left -fill both -expand 1 527 pack $itk_component(pm) -side right -fill both -expand 1 528 } 529 530 default { 531 error "bad showampm option \"$itk_option(-showampm)\":\ 532 should be boolean" 533 } 534 } 535} 536 537# ------------------------------------------------------------------ 538# OPTION: pivotcolor 539# 540# Configure the color of the clock pivot. 541# 542itcl::configbody ::iwidgets::Watch::pivotcolor { 543 watch itemconfigure pivot -fill $itk_option(-pivotcolor) 544} 545 546# ------------------------------------------------------------------ 547# OPTION: clockstipple 548# 549# Configure the stipple pattern for the clock fill color. 550# 551itcl::configbody ::iwidgets::Watch::clockstipple { 552 watch itemconfigure clock -stipple $itk_option(-clockstipple) 553} 554 555# ------------------------------------------------------------------ 556# OPTION: clockcolor 557# 558# Configure the color of the clock. 559# 560itcl::configbody ::iwidgets::Watch::clockcolor { 561 watch itemconfigure clock -fill $itk_option(-clockcolor) 562} 563 564# ------------------------------------------------------------------ 565# OPTION: hourcolor 566# 567# Configure the color of the hour hand. 568# 569itcl::configbody ::iwidgets::Watch::hourcolor { 570 watch itemconfigure hour -fill $itk_option(-hourcolor) 571} 572 573# ------------------------------------------------------------------ 574# OPTION: minutecolor 575# 576# Configure the color of the minute hand. 577# 578itcl::configbody ::iwidgets::Watch::minutecolor { 579 watch itemconfigure minute -fill $itk_option(-minutecolor) 580} 581 582# ------------------------------------------------------------------ 583# OPTION: secondcolor 584# 585# Configure the color of the second hand. 586# 587itcl::configbody ::iwidgets::Watch::secondcolor { 588 watch itemconfigure second -fill $itk_option(-secondcolor) 589} 590 591# ------------------------------------------------------------------ 592# OPTION: tickcolor 593# 594# Configure the color of the ticks. 595# 596itcl::configbody ::iwidgets::Watch::tickcolor { 597 watch itemconfigure tick -outline $itk_option(-tickcolor) 598} 599 600# ------------------------------------------------------------------ 601# OPTION: hourradius 602# 603# Configure the radius of the hour hand. 604# 605itcl::configbody ::iwidgets::Watch::hourradius { 606 _displayClock 607} 608 609# ------------------------------------------------------------------ 610# OPTION: minuteradius 611# 612# Configure the radius of the minute hand. 613# 614itcl::configbody ::iwidgets::Watch::minuteradius { 615 _displayClock 616} 617 618# ------------------------------------------------------------------ 619# OPTION: secondradius 620# 621# Configure the radius of the second hand. 622# 623itcl::configbody ::iwidgets::Watch::secondradius { 624 _displayClock 625} 626 627