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