1# Spintime
2# ----------------------------------------------------------------------
3# Implements a Time spinner widget.  A time spinner contains three
4# integer spinners:  one for hours, one for minutes and one for
5# seconds.  Options exist to manage to behavior, appearance, and
6# format of each component spinner.
7#
8# ----------------------------------------------------------------------
9#  AUTHOR: Sue Yockey                  EMAIL: yockey@actc.com
10#          Mark L. Ulferts                    mulferts@austin.dsccc.com
11#
12#   @(#) $Id: spintime.itk,v 1.3 2001/08/17 19:04:45 smithc Exp $
13# ----------------------------------------------------------------------
14#            Copyright (c) 1997 DSC Technologies Corporation
15# ======================================================================
16# Permission to use, copy, modify, distribute and license this software
17# and its documentation for any purpose, and without fee or written
18# agreement with DSC, is hereby granted, provided that the above copyright
19# notice appears in all copies and that both the copyright notice and
20# warranty disclaimer below appear in supporting documentation, and that
21# the names of DSC Technologies Corporation or DSC Communications
22# Corporation not be used in advertising or publicity pertaining to the
23# software without specific, written prior permission.
24#
25# DSC DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26# ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, AND NON-
27# INFRINGEMENT. THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, AND THE
28# AUTHORS AND DISTRIBUTORS HAVE NO OBLIGATION TO PROVIDE MAINTENANCE,
29# SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. IN NO EVENT SHALL
30# DSC BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
31# ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
32# WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTUOUS ACTION,
33# ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
34# SOFTWARE.
35# ======================================================================
36
37#
38# Default resources.
39#
40option add *Spintime.hourLabel "Hour" widgetDefault
41option add *Spintime.minuteLabel "Minute" widgetDefault
42option add *Spintime.secondLabel "Second" widgetDefault
43option add *Spintime.hourWidth 3 widgetDefault
44option add *Spintime.minuteWidth 3 widgetDefault
45option add *Spintime.secondWidth 3 widgetDefault
46
47#
48# Usual options.
49#
50itk::usual Spintime {
51    keep -background -cursor -foreground -labelfont -textbackground -textfont
52}
53
54# ------------------------------------------------------------------
55#                            SPINTIME
56# ------------------------------------------------------------------
57itcl::class iwidgets::Spintime {
58    inherit itk::Widget
59
60    constructor {args} {}
61    destructor {}
62
63    itk_option define -orient orient Orient vertical
64    itk_option define -labelpos labelPos Position w
65    itk_option define -houron hourOn HourOn true
66    itk_option define -minuteon minuteOn MinuteOn true
67    itk_option define -secondon secondOn SecondOn true
68    itk_option define -timemargin timeMargin Margin 1
69    itk_option define -militaryon militaryOn MilitaryOn true
70
71    public {
72	method get {{format "-string"}}
73	method show {{date now}}
74    }
75
76    protected {
77	method _packTime {{when later}}
78	method _down60 {comp}
79
80	variable _repack {}             ;# Reconfiguration flag.
81	variable _interior
82    }
83}
84
85#
86# Provide a lowercased access method for the Spintime class.
87#
88proc ::iwidgets::spintime {pathName args} {
89    uplevel ::iwidgets::Spintime $pathName $args
90}
91
92# ------------------------------------------------------------------
93#                        CONSTRUCTOR
94# ------------------------------------------------------------------
95itcl::body iwidgets::Spintime::constructor {args} {
96    set _interior $itk_interior
97    set clicks [clock seconds]
98
99    #
100    # Create Hour Spinner
101    #
102    itk_component add hour {
103	iwidgets::Spinint $itk_interior.hour -fixed 2 -range {0 23} -justify right
104    } {
105	keep -background -cursor -arroworient -foreground \
106		-labelfont -labelmargin -relief -textbackground \
107		-textfont -repeatdelay -repeatinterval
108
109	rename -labeltext -hourlabel hourLabel Text
110	rename -width -hourwidth hourWidth Width
111    }
112
113    #
114    # Take off the default bindings for selction and motion.
115    #
116    bind [$itk_component(hour) component entry] <1> {break}
117    bind [$itk_component(hour) component entry] <Button1-Motion> {break}
118
119    #
120    # Create Minute Spinner
121    #
122    itk_component add minute {
123	iwidgets::Spinint $itk_interior.minute \
124		-decrement [itcl::code $this _down60 minute] \
125		-fixed 2 -range {0 59} -justify right
126    } {
127	keep -background -cursor -arroworient -foreground \
128		-labelfont -labelmargin -relief -textbackground \
129		-textfont -repeatdelay -repeatinterval
130
131	rename -labeltext -minutelabel minuteLabel Text
132	rename -width -minutewidth minuteWidth Width
133    }
134
135    #
136    # Take off the default bindings for selction and motion.
137    #
138    bind [$itk_component(minute) component entry] <1> {break}
139    bind [$itk_component(minute) component entry] <Button1-Motion> {break}
140
141    #
142    # Create Second Spinner
143    #
144    itk_component add second {
145	iwidgets::Spinint $itk_interior.second  \
146		-decrement [itcl::code $this _down60 second] \
147		-fixed 2 -range {0 59} -justify right
148    } {
149	keep -background -cursor -arroworient -foreground \
150		-labelfont -labelmargin -relief -textbackground \
151		-textfont -repeatdelay -repeatinterval
152
153	rename -labeltext -secondlabel secondLabel Text
154	rename -width -secondwidth secondWidth Width
155    }
156
157    #
158    # Take off the default bindings for selction and motion.
159    #
160    bind [$itk_component(second) component entry] <1> {break}
161    bind [$itk_component(second) component entry] <Button1-Motion> {break}
162
163    #
164    # Initialize the widget based on the command line options.
165    #
166    eval itk_initialize $args
167
168    #
169    # Show the current time.
170    #
171    show now
172}
173
174# ------------------------------------------------------------------
175#                           DESTRUCTOR
176# ------------------------------------------------------------------
177itcl::body iwidgets::Spintime::destructor {} {
178    if {$_repack != ""} {after cancel $_repack}
179}
180
181# ------------------------------------------------------------------
182#                             OPTIONS
183# ------------------------------------------------------------------
184
185# ------------------------------------------------------------------
186# OPTION: -orient
187#
188# Specifies the orientation of the 3 spinners for Hour, Minute
189# and second.
190# ------------------------------------------------------------------
191itcl::configbody iwidgets::Spintime::orient {
192    _packTime
193}
194
195# ------------------------------------------------------------------
196# OPTION: -labelpos
197#
198# Specifies the location of all 3 spinners' labels.
199# Overloaded
200# ------------------------------------------------------------------
201itcl::configbody iwidgets::Spintime::labelpos {
202    switch $itk_option(-labelpos) {
203	n {
204	    $itk_component(hour) configure -labelpos n
205	    $itk_component(minute) configure -labelpos n
206	    $itk_component(second) configure -labelpos n
207
208	    #
209	    # Un-align labels
210	    #
211	    $itk_component(hour) configure -labelmargin 1
212	    $itk_component(minute) configure -labelmargin 1
213	    $itk_component(second) configure -labelmargin 1
214	}
215
216	s {
217	    $itk_component(hour) configure -labelpos s
218	    $itk_component(minute) configure -labelpos s
219	    $itk_component(second) configure -labelpos s
220
221	    #
222	    # Un-align labels
223	    #
224	    $itk_component(hour) configure -labelmargin 1
225	    $itk_component(minute) configure -labelmargin 1
226	    $itk_component(second) configure -labelmargin 1
227	}
228
229	w {
230	    $itk_component(hour) configure -labelpos w
231	    $itk_component(minute) configure -labelpos w
232	    $itk_component(second) configure -labelpos w
233	}
234
235	e {
236	    $itk_component(hour) configure -labelpos e
237	    $itk_component(minute) configure -labelpos e
238	    $itk_component(second) configure -labelpos e
239
240	    #
241	    # Un-align labels
242	    #
243	    $itk_component(hour) configure -labelmargin 1
244	    $itk_component(minute) configure -labelmargin 1
245	    $itk_component(second) configure -labelmargin 1
246	}
247
248	default {
249	    error "bad labelpos option \"$itk_option(-labelpos)\",\
250		    should be n, s, w or e"
251	}
252    }
253
254    _packTime
255}
256
257# ------------------------------------------------------------------
258# OPTION: -houron
259#
260# Specifies whether or not to display the hour spinner.
261# ------------------------------------------------------------------
262itcl::configbody iwidgets::Spintime::houron {
263    _packTime
264}
265
266# ------------------------------------------------------------------
267# OPTION: -minuteon
268#
269# Specifies whether or not to display the minute spinner.
270# ------------------------------------------------------------------
271itcl::configbody iwidgets::Spintime::minuteon {
272    _packTime
273}
274
275# ------------------------------------------------------------------
276# OPTION: -secondon
277#
278# Specifies whether or not to display the second spinner.
279# ------------------------------------------------------------------
280itcl::configbody iwidgets::Spintime::secondon {
281    _packTime
282}
283
284
285# ------------------------------------------------------------------
286# OPTION: -timemargin
287#
288# Specifies the margin space between the hour and minute spinners
289# and the minute and second spinners.
290# ------------------------------------------------------------------
291itcl::configbody iwidgets::Spintime::timemargin {
292    _packTime
293}
294
295# ------------------------------------------------------------------
296# OPTION: -militaryon
297#
298# Specifies 24-hour clock or 12-hour.
299# ------------------------------------------------------------------
300itcl::configbody iwidgets::Spintime::militaryon {
301    set clicks [clock seconds]
302
303    if {$itk_option(-militaryon)} {
304	$itk_component(hour) configure -range {0 23}
305	$itk_component(hour) delete 0 end
306	$itk_component(hour) insert end [clock format $clicks -format "%H"]
307    } else {
308	$itk_component(hour) configure -range {1 12}
309	$itk_component(hour) delete 0 end
310	$itk_component(hour) insert end [clock format $clicks -format "%I"]
311    }
312}
313
314# ------------------------------------------------------------------
315#                            METHODS
316# ------------------------------------------------------------------
317
318# ------------------------------------------------------------------
319# METHOD: get ?format?
320#
321# Get the value of the time spinner in one of two formats string or
322# as an integer clock value using the -string and -clicks options
323# respectively.  The default is by string.  Reference the clock
324# command for more information on obtaining time and its formats.
325# ------------------------------------------------------------------
326itcl::body iwidgets::Spintime::get {{format "-string"}} {
327    set hour [$itk_component(hour) get]
328    set minute [$itk_component(minute) get]
329    set second [$itk_component(second) get]
330
331    switch -- $format {
332	"-string" {
333	    return "$hour:$minute:$second"
334	}
335	"-clicks" {
336	    return [clock scan "$hour:$minute:$second"]
337	}
338	default {
339	    error "bad format option \"$format\":\
340                   should be -string or -clicks"
341	}
342    }
343}
344
345# ------------------------------------------------------------------
346# PUBLIC METHOD: show time
347#
348# Changes the currently displayed time to be that of the time
349# argument.  The time may be specified either as a string or an
350# integer clock value.  Reference the clock command for more
351# information on obtaining time and its format.
352# ------------------------------------------------------------------
353itcl::body iwidgets::Spintime::show {{time "now"}} {
354    if {$time == "now"} {
355	set seconds [clock seconds]
356    } else {
357	if {[catch {clock format $time}] == 0} {
358	    set seconds $time
359	} elseif {[catch {set seconds [clock scan $time]}] != 0} {
360	    error "bad time: \"$time\", must be a valid time\
361               string, clock clicks value or the keyword now"
362	}
363    }
364
365    $itk_component(hour) delete 0 end
366
367    if {$itk_option(-militaryon)} {
368	scan [clock format $seconds -format "%H"] "%d" hour
369    } else {
370	scan hour [clock format $seconds -format "%I"] "%d" hour
371    }
372
373    $itk_component(hour) insert end $hour
374
375    $itk_component(minute) delete 0 end
376    scan [clock format $seconds -format "%M"] "%d" minute
377    $itk_component(minute) insert end $minute
378
379    $itk_component(second) delete 0 end
380    scan [clock format $seconds -format "%S"] "%d" seconds
381    $itk_component(second) insert end $seconds
382
383    return
384}
385
386# ------------------------------------------------------------------
387# PROTECTED METHOD: _packTime ?when?
388#
389# Pack components of time spinner.  If "when" is "now", the change
390# is applied immediately.  If it is "later" or it is not specified,
391# then the change is applied later, when the application is idle.
392# ------------------------------------------------------------------
393itcl::body iwidgets::Spintime::_packTime {{when later}} {
394    if {$when == "later"} {
395	if {$_repack == ""} {
396	    set _repack [after idle [itcl::code $this _packTime now]]
397	}
398	return
399    } elseif {$when != "now"} {
400	error "bad option \"$when\": should be now or later"
401    }
402
403    for {set i 0} {$i < 5} {incr i} {
404	grid rowconfigure $_interior $i -minsize 0
405	grid columnconfigure $_interior $i -minsize 0
406    }
407
408    if {$itk_option(-minuteon)} {
409	set minuteon 1
410    } else {
411	set minuteon 0
412    }
413    if {$itk_option(-secondon)} {
414	set secondon 1
415    } else {
416	set secondon 0
417    }
418
419    set _repack ""
420
421    switch $itk_option(-orient) {
422	vertical {
423	    set row -1
424
425	    if {$itk_option(-houron)} {
426		grid $itk_component(hour) -row [incr row] -column 0 \
427		    -sticky nsew
428	    } else {
429		grid forget $itk_component(hour)
430	    }
431
432	    if {$itk_option(-minuteon)} {
433		if {$itk_option(-houron)} {
434		    grid rowconfigure $_interior [incr row] \
435			-minsize $itk_option(-timemargin)
436		}
437
438		grid $itk_component(minute) -row [incr row] -column 0 \
439		    -sticky nsew
440	    } else {
441		grid forget $itk_component(minute)
442	    }
443
444	    if {$itk_option(-secondon)} {
445		if {$minuteon || $secondon} {
446		    grid rowconfigure $_interior [incr row] \
447			-minsize $itk_option(-timemargin)
448		}
449
450		grid $itk_component(second) -row [incr row] -column 0 \
451		    -sticky nsew
452	    } else {
453		grid forget $itk_component(second)
454	    }
455
456	    if {$itk_option(-labelpos) == "w"} {
457		iwidgets::Labeledwidget::alignlabels $itk_component(hour) \
458			$itk_component(minute) $itk_component(second)
459	    }
460	}
461
462	horizontal {
463	    set column -1
464
465	    if {$itk_option(-houron)} {
466		grid $itk_component(hour) -row 0 -column [incr column] \
467		    -sticky nsew
468	    } else {
469		grid forget $itk_component(hour)
470	    }
471
472	    if {$itk_option(-minuteon)} {
473		if {$itk_option(-houron)} {
474		    grid columnconfigure $_interior [incr column] \
475			-minsize $itk_option(-timemargin)
476		}
477
478		grid $itk_component(minute) -row 0 -column [incr column] \
479		    -sticky nsew
480	    } else {
481		grid forget $itk_component(minute)
482	    }
483
484	    if {$itk_option(-secondon)} {
485		if {$minuteon || $secondon} {
486		    grid columnconfigure $_interior [incr column] \
487			-minsize $itk_option(-timemargin)
488		}
489
490		grid $itk_component(second) -row 0 -column [incr column] \
491		    -sticky nsew
492	    } else {
493		grid forget $itk_component(second)
494	    }
495
496	    #
497	    # Un-align labels
498	    #
499	    $itk_component(hour) configure -labelmargin 1
500	    $itk_component(minute) configure -labelmargin 1
501	    $itk_component(second) configure -labelmargin 1
502	}
503
504	default {
505	    error "bad orient option \"$itk_option(-orient)\", should\
506		    be \"vertical\" or \"horizontal\""
507	}
508    }
509}
510
511# ------------------------------------------------------------------
512# METHOD: down60
513#
514# Down arrow button press event.  Decrement value in the minute
515# or second entry.
516# ------------------------------------------------------------------
517itcl::body iwidgets::Spintime::_down60 {comp} {
518	set step [$itk_component($comp) cget -step]
519	set val [$itk_component($comp) get]
520
521	incr val -$step
522	if {$val < 0} {
523	   set val [expr {60-$step}]
524        }
525	$itk_component($comp) delete 0 end
526	$itk_component($comp) insert 0 $val
527}
528