1 # vim: foldmarker=<<<,>>>
4 keep -resultformat -monthnames -titlegap -titleheight -titlespacer \
5 -weekdayheaders -padx -pady -cellwidth -cellheight -dayheaderheight \
6 -validfrom -validto -titlefg -titlefont -fillerbg -weekdayheaderbg \
7 -availablebg -invalidbg -weekendbg -specialbg -dayfont \
8 -weekdayheaderfont -weekdayheaderfg -availablefg -invalidfg \
9 -weekendfg -specialfg -borders -background -command -special_cb \
14 inherit tlc::Mywidget tlc::Handlers
19 # Multiplicity of options <<<
20 itk_option define -resultformat resultFormat ResultFormat "%Y-%m-%d"
21 itk_option define -monthnames monthNames MonthNames {January February March April May June July August September October November December} eventually_rerender
22 itk_option define -titlegap titleGap TitleGap 4 eventually_rerender
23 itk_option define -titleheight titleHeight TitleHeight 20 eventually_rerender
24 itk_option define -titleskew titleSkew TitleSkew 0 eventually_rerender
25 itk_option define -titlespacer titleSpacer TitleSpacer 4 eventually_rerender
26 itk_option define -weekdayheaders weekdayHeaders WeekdayHeaders {Su Mo Tu We Th Fr Sa} eventually_rerender
27 itk_option define -validhours validHours Validhours {6 7 8 9 10 12 13 14 15 16 17 18 19 20}
28 itk_option define -padx padX PadX 5 eventually_rerender
29 itk_option define -pady padY PadY 5 eventually_rerender
30 itk_option define -cellwidth cellWidth CellWidth 40 eventually_rerender
31 itk_option define -cellheight cellHeight CellHeight 50 eventually_rerender
32 itk_option define -hourheight hourHeight HourHeight 20 eventually_rerender
33 itk_option define -hourwidth hourWidth HourWidth 60 eventually_rerender
34 itk_option define -dayheaderheight dayHeaderHeight DayHeaderHeight 23 eventually_rerender
35 itk_option define -validfrom validFrom ValidFrom "" eventually_rerender
36 itk_option define -validto validTo ValidTo "" eventually_rerender
38 itk_option define -titlefg titleFg TitleFg "black" configure_tags
39 itk_option define -titlefont titleFont TitleFont {Helvetica -14 bold} configure_tags
40 itk_option define -fillerbg fillerBg FillerBg "#999999" configure_tags
41 itk_option define -weekdayheaderbg weekdayHeaderBg WeekdayHeaderBg "#9cdefc" configure_tags
42 itk_option define -availablebg availableBg AvailableBg "white" configure_tags
43 itk_option define -invalidbg invalidBg InvalidBg "#c4c4c4" configure_tags
44 itk_option define -weekendbg weekendBg WeekendBg "#cefff9" configure_tags
45 itk_option define -specialbg specialBg SpecialBg "white" configure_tags
46 itk_option define -dayfont dayFont DayFont {Helvetica -12 bold} configure_tags
47 itk_option define -weekdayheaderfont weekdayHeaderFont WeekdayHeaderFont {Helvetica -12 bold} configure_tags
48 itk_option define -weekdayheaderfg weekdayHeaderFg WeekdayHeaderFg "black" configure_tags
49 itk_option define -availablefg availableFg AvailableFg "black" configure_tags
50 itk_option define -invalidfg invalidFg InvalidFg "#a0a0a0" configure_tags
51 itk_option define -weekendfg weekendFg WeekendFg "black" configure_tags
52 itk_option define -specialfg specialFg SpecialFg "black" configure_tags
53 itk_option define -todaybg todayBg TodayBg "#f1ff0a" configure_tags
54 itk_option define -borders borders Borders "black" configure_tags
55 itk_option define -command command Command {}
56 itk_option define -onlyweekdays onlyWeekdays OnlyWeekdays 1
57 itk_option define -textvariable textVariable TextVariable ""
58 itk_option define -newselection newSelection NewSelection {}
59 # Multiplicity of options >>>
62 variable date "" eventually_rerender
63 variable day "" eventually_rerender
64 variable month "" eventually_rerender
65 variable year "" eventually_rerender
66 variable today "" eventually_rerender
67 variable special_cb {} eventually_rerender
73 method scroll_month {delta}
74 method scroll_year {delta}
75 method force_redraw {}
76 method rerender_dom_ref {}
87 variable dayheader_height
92 variable valid_from_day
94 variable eventually_rerender_id ""
96 variable tooltip_txt ""
99 variable old_textvariable ""
103 method dayheader_xy {dnum}
108 method configure_tags {}
109 method dom_tags {pref dom}
110 method get_special {}
113 method enter_dom {dom}
114 method leave_dom {dom}
115 method available {dom}
116 method eventually_rerender {}
118 method day_rerender {}
119 method newvalue {args} {}
120 method newselection {newdate}
126 body tlc::Diary::constructor {args} { #<<<1
131 Domino #auto rerender_dom
132 $rerender_dom attach_output [code $this force_redraw]
134 if {![info exists imgs(current)]} {
135 foreach {name file} {
136 day_selected day_selected.gif
137 special_day special_day.gif
138 left_arrow left_arrow.gif
139 right_arrow right_arrow.gif
140 left_arrow_disabled left_arrow_disabled.gif
141 right_arrow_disabled right_arrow_disabled.gif
143 set imgs($name) [image create photo \
144 -file [file join $::tlc::guilibrary scripts images $file]]
148 Hoverbox $w.tooltip -textvariable [scope tooltip_txt] \
149 -background "#ffffe1"
151 itk_component add canvas {
158 set today [clock format [clock seconds] -format "%d/%m/%Y"]
160 set dat(new_date) $today
162 frame $w.frame -borderwidth 1 -relief groove
163 Dateentry $w.frame.date -textvariable [scope dat(new_date)]
168 $w.frame.day 2,1 -padx {3 3} -pady {3 3}
174 eval itk_initialize $args
175 $w.cal bind day_box <Enter> [code $this enter_day]
176 $w.cal bind day_box <Leave> [code $this leave_day]
177 $w.cal bind day_box <Button-1> [code $this select_day]
179 $w.cal bind left_arrow <Button-1> [code $this last_month]
180 $w.cal bind right_arrow <Button-1> [code $this next_month]
183 $w.cal bind monthname <MouseWheel> \
184 [code $this scroll_month \[expr \{- (%D / 120) * 4\}\]]
186 $w.cal bind monthname <Button-5> [code $this last_month]
187 $w.cal bind monthname <Button-4> [code $this next_month]
189 $w.cal bind yearname <MouseWheel> \
190 [code $this scroll_year \[expr \{- (%D / 120) * 4\}\]]
192 $w.cal bind yearname <Button-5> [code $this last_year]
193 $w.cal bind yearname <Button-4> [code $this next_year]
195 register_handler enter_dom [code $this enter_dom]
196 register_handler leave_dom [code $this leave_dom]
205 body tlc::Diary::destructor {} { #<<<1
206 after cancel $eventually_rerender_id
210 # get the (double)(x,y) of the center of the cell for the weekday header dnum
211 body tlc::Diary::dayheader_xy {dnum} { #<<<1
212 if {$dnum < 0 || $dnum > 6} {
213 # log error "Calender::dayheader_xy: dnum ($dnum) out of range (0-6)"
214 error "Calender::dayheader_xy: dnum ($dnum) out of range (0-6)"
217 set x [expr {$topleft_x}]
218 set y [expr {$topleft_y + $dayheader_height / 2.0}]
220 set x [expr {$x + $dnum * $cell_w + $cell_w / 2.0}]
226 # Get the (double)(x,y) of the center of the cell for day of month
227 body tlc::Diary::xy {dom} { #<<<1
230 set x [expr {$topleft_x}]
231 set y [expr {$topleft_y + $dayheader_height}]
233 foreach {r c} [rc $dom] break
235 set x [expr {$x + $cell_w * $c + $cell_w / 2.0}]
236 set y [expr {$y + $cell_h * $r + $cell_h / 2.0}]
242 # Get the (double)(x1,y1,x2,y2) bounding box of the cell for day of month $dom
243 body tlc::Diary::bbox {dom} { #<<<1
246 set x [expr {$topleft_x}]
247 set y [expr {$topleft_y + $dayheader_height}]
249 foreach {r c} [rc $dom] break
251 set x1 [expr {$x + $cell_w * $c}]
252 set y1 [expr {$y + $cell_h * $r}]
253 set x2 [expr {$x + $cell_w * $c + $cell_w}]
254 set y2 [expr {$y + $cell_h * $r + $cell_h}]
256 return [list $x1 $y1 $x2 $y2]
260 # Get the (row,column) for day of month
261 body tlc::Diary::rc {dom} { #<<<1
262 if {$dom < 1 || $dom > $days} {
263 # log error "tlc::Calendar::rc: dom ($dom) is out of range ($days)"
264 error "tlc::Calendar::rc: dom ($dom) is out of range ($days)"
269 set cell [expr {$colstart + $dom}]
270 set row [expr {$cell / 7}]
271 set col [expr {$cell % 7}]
273 return [list $row $col]
276 body tlc::Diary::day_rerender {} { #<<<1
277 $w.frame.day delete all
278 $w.frame.day configure -width 400 -height 400
279 $w.frame.day yview moveto 0.0
280 $w.frame.day xview moveto 0.0
301 # puts "Diary::day_rerender: width ($day_width) height ($day_height)"
303 set hour_width [expr {$day_width / 3}]
304 set hour_height [expr {$day_height / 14}]
307 for {set row 0} {$row < 14} {incr row} {
308 set y [expr {$row * $hour_height}]
309 set y2 [expr {$y + $hour_height}]
310 for {set column 0} {$column < 1} {incr column} {
311 set x [expr {$column * $hour_width}]
312 set x2 [expr {$x + $hour_width}]
313 set x3 [expr {$hour_width * 2 + $x2}]
314 # puts "Diary::day_rerender: x ($x) y ($y) w ($x2) h ($y2)"
315 $w.frame.day create rectangle $x $y $x2 $y2 -fill yellow
316 set tx [expr {$x + 10}]
317 set ty [expr {$y + 10}]
318 $w.frame.day create text $tx $ty -text "[lindex $times $row]" -anchor w
319 $w.frame.day create rectangle $x2 $y $x3 $y2 -fill white
323 if {$date_sel == ""} {
327 set new_date $date_sel
330 set dat(new_date) $new_date
334 body tlc::Diary::rerender {} { #<<<1
335 if {$constructing} return
336 # log debug "rerender profile [time \{
337 after cancel $eventually_rerender_id
338 set eventually_rerender_id ""
341 set date [clock seconds]
343 foreach {d m y} [clock format $date -format "%d %m %Y"] break
355 set month [string trimleft $month 0]
356 set year [string trimleft $year 0]
357 if {$year == ""} {set year 0}
360 # log debug "tlc::Calendar::rerender: ($w) after get_special"
362 set colstart [clock format [clock scan "$year-$month-01"] -format "%w"]
364 # Determine days in month
365 set tmp [clock format [clock scan "$year-$month-01 + 1 month"] -format "%Y-%m-01"]; # first day of next month
366 set delta [expr {[clock scan $tmp] - [clock scan "$year-$month-01"]}]
367 set days [expr {$delta / 86400}]
369 set rows [expr {int(ceil(($colstart + $days) / 7.0))}]
371 set topleft_x $itk_option(-padx)
372 set topleft_y [expr {$itk_option(-titleheight) + $itk_option(-pady) + $itk_option(-titlespacer)}]
373 set dayheader_height $itk_option(-dayheaderheight)
375 set cell_h $itk_option(-cellheight)
376 set cell_w $itk_option(-cellwidth)
378 # Calculate relative valid days <<<
379 if {$itk_option(-validfrom) != ""} {
380 set valid_from [clock scan \
382 [clock scan $itk_option(-validfrom)] \
385 [expr {($valid_from - [clock scan "$year-$month-01"]) / 86400 + 1}]
387 set valid_from_day -1000
390 if {$itk_option(-validto) != ""} {
391 set valid_to [clock scan \
393 [clock scan $itk_option(-validto)] \
396 [expr {$days - (([clock scan "$year-$month-$days"] - $valid_to) / 86400)}]
398 set valid_to_day 1000
400 # Calculate relative valid days >>>
402 # Calculate canvas bounding box <<<
405 incr width [expr {$itk_option(-padx) * 2}]
406 incr width [expr {7 * $cell_w}]
407 incr height [expr {$itk_option(-pady) * 2}]
408 incr height $itk_option(-titleheight)
409 incr height $itk_option(-titlespacer)
410 incr height $dayheader_height
411 incr height [expr {$rows * $cell_h}]
412 # Calculate canvas bounding box >>>
414 # log trivia "tlc::Calendar::rerender: $y-$m-$d\ncolstart: ($colstart)\ndays: ($days)\nrows: ($rows)\nwidth: ($width) height: ($height) rc(1): ([rc 1])\nvalid from, to: ($valid_from_day,$valid_to_day)"
416 $w.cal configure -width $width -height $height
417 $w.cal yview moveto 0.0
418 $w.cal xview moveto 0.0
423 # draw day area background
425 set y1 [expr {$topleft_y + $dayheader_height}]
426 set x2 [expr {$x1 + 7 * $cell_w}]
427 set y2 [expr {$y1 + $rows * $cell_h}]
428 $w.cal create rectangle $x1 $y1 $x2 $y2 -tags bg_filler
430 # draw weekday backgrounds
432 set y2 [expr {$y1 + $dayheader_height}]
433 for {set d 0} {$d < 7} {incr d} {
434 set x1 [expr {$topleft_x + $d * $cell_w}]
435 set x2 [expr {$x1 + $cell_w}]
436 $w.cal create rectangle $x1 $y1 $x2 $y2 -tags bg_weekday_header
439 # draw day backgrounds
440 for {set d 1} {$d <= $days} {incr d} {
441 foreach {x1 y1 x2 y2} [bbox $d] break
442 $w.cal create rectangle $x1 $y1 $x2 $y2 -tags [dom_tags bg $d]
445 # draw month selector
446 set y [expr {$itk_option(-pady) + $itk_option(-titleheight) / 2.0}]
447 set x [expr {$itk_option(-padx)}]
448 $w.cal create image $x $y \
449 -image $imgs(left_arrow) \
450 -disabledimage $imgs(left_arrow_disabled) \
451 -anchor w -tags left_arrow \
452 -state [lindex {disabled normal} [expr {$valid_from_day < 1}]]
453 set x [expr {$width - $itk_option(-padx)}]
454 $w.cal create image $x $y \
455 -image $imgs(right_arrow) \
456 -disabledimage $imgs(right_arrow_disabled) \
457 -anchor e -tags right_arrow \
458 -state [lindex {disabled normal} [expr {$valid_to_day > $days}]]
459 set x [expr {$width * 0.55}]
460 $w.cal create text [expr {$x - $itk_option(-titlegap) / 2.0 + $itk_option(-titleskew)}] $y \
462 -text [lindex $itk_option(-monthnames) [expr {$month - 1}]] \
463 -tags {monthname title}
464 $w.cal create text [expr {$x + $itk_option(-titlegap) / 2.0 + $itk_option(-titleskew)}] $y \
467 -tags {yearname title}
469 # draw weekday headers
470 for {set d 0} {$d < 7} {incr d} {
471 foreach {x y} [dayheader_xy $d] break
472 $w.cal create text $x $y \
473 -text [lindex $itk_option(-weekdayheaders) $d] \
474 -tags fg_weekday_header
478 for {set d 1} {$d <= $days} {incr d} {
479 foreach {x y} [xy $d] break
480 $w.cal create text $x $y -text $d -tags [dom_tags fg $d]
481 if {[info exists special($d)]} {
482 $w.cal create image $x $y -image $imgs(special_day) -anchor center
486 $w.cal create image -100 -100 -image $imgs(day_selected) -anchor center \
490 for {set d 1} {$d <= $days} {incr d} {
491 foreach {x1 y1 x2 y2} [bbox $d] break
492 $w.cal create rectangle $x1 $y1 $x2 $y2 -tags [list day_box "dom $d"] -fill "" -outline ""
504 body tlc::Diary::configure_tags {} { #<<<1
505 if {!$rendered} rerender
506 $w.cal itemconfigure bg_filler -fill $itk_option(-fillerbg)
507 $w.cal itemconfigure bg_weekday_header -fill $itk_option(-weekdayheaderbg)
508 $w.cal itemconfigure bg_available -fill $itk_option(-availablebg)
509 $w.cal itemconfigure bg_invalid -fill $itk_option(-invalidbg)
510 $w.cal itemconfigure bg_weekend -fill $itk_option(-weekendbg)
511 $w.cal itemconfigure bg_special -fill $itk_option(-specialbg)
512 $w.cal itemconfigure bg_today -fill $itk_option(-todaybg)
514 $w.cal itemconfigure fg_day -font $itk_option(-dayfont)
515 $w.cal itemconfigure fg_weekday_header -font $itk_option(-weekdayheaderfont)
517 $w.cal itemconfigure fg_weekday_header -fill $itk_option(-weekdayheaderfg)
518 $w.cal itemconfigure fg_available -fill $itk_option(-availablefg)
519 $w.cal itemconfigure fg_invalid -fill $itk_option(-invalidfg)
520 $w.cal itemconfigure fg_weekend -fill $itk_option(-weekendfg)
521 $w.cal itemconfigure fg_special -fill $itk_option(-specialfg)
523 $w.cal itemconfigure bg_day -outline $itk_option(-borders)
524 $w.cal itemconfigure bg_filler -outline $itk_option(-borders)
525 $w.cal itemconfigure bg_weekday_header -outline $itk_option(-borders)
527 $w.cal itemconfigure title -fill $itk_option(-titlefg)
528 $w.cal itemconfigure title -font $itk_option(-titlefont)
532 # Returns a list of tags applicable for day of month $dom
533 body tlc::Diary::dom_tags {pref dom} { #<<<1
534 if {$dom < 1 || $dom > $days} {
535 # log error "tlc::Calendar::dom_tags: dom ($dom) out of range (0-$days)"
536 error "tlc::Calendar::dom_tags: dom ($dom) out of range (0-$days)"
539 lappend build "${pref}_dom $dom"
540 lappend build ${pref}_day
542 set weekday [expr {($colstart + $dom - 1) % 7}]
543 if {$dom < $valid_from_day || $dom > $valid_to_day} {
544 lappend build ${pref}_invalid
545 } elseif {$weekday == 0 || $weekday == 6} {
546 lappend build ${pref}_weekend
547 } elseif {[info exists special($dom)]} {
548 lappend build ${pref}_special
550 if {[available $dom]} {
551 lappend build ${pref}_available
554 # log trivia "tlc::Calendar::dom_tags: ($dom) ($build)"
560 # Call the special_cb (if any) to get a list of special days
561 body tlc::Diary::get_special {} { #<<<1
562 catch {unset special}
564 if {$special_cb != {}} {
565 array set special [uplevel #0 $special_cb [list $year $month]]
570 body tlc::Diary::enter_day {} { #<<<1
572 foreach tag [$w.cal gettags current] {
573 if {[string range $tag 0 3] == "dom "} {
574 set dom [string range $tag 4 end]
579 set tags [$w.cal gettags current]
580 # log error "tlc::Calendar::enter_day: couldn't resolve dom ($tags)"
581 error "tlc::Calendar::enter_day: couldn't resolve dom ($tags)"
584 invoke_handlers enter_dom $dom
588 body tlc::Diary::leave_day {} { #<<<1
590 foreach tag [$w.cal gettags current] {
591 if {[string range $tag 0 3] == "dom "} {
592 set dom [string range $tag 4 end]
597 set tags [$w.cal gettags current]
598 # log error "tlc::Calendar::enter_day: couldn't resolve dom ($tags)"
599 error "tlc::Calendar::enter_day: couldn't resolve dom ($tags)"
602 invoke_handlers leave_dom $dom
606 body tlc::Diary::enter_dom {dom} { #<<<1
607 # log trivia "tlc::Calendar::enter_dom: ($dom)"
609 if {[available $dom]} {
610 $w.cal coords day_selected [xy $dom]
612 if {[info exists special($dom)]} {
613 set tooltip_txt $special($dom)
614 foreach {x y} [xy $dom] break
615 set x [expr {int([winfo rootx $w.cal] + $x - [winfo reqwidth $w.tooltip] / 2.0)}]
616 set y [expr {int([winfo rooty $w.cal] + $y + $itk_option(-cellheight) / 2.0)}]
617 $w.tooltip moveto $x $y
624 body tlc::Diary::leave_dom {dom} { #<<<1
625 # log trivia "tlc::Calendar::leave_dom: ($dom)"
627 $w.cal coords day_selected -100 -100
632 body tlc::Diary::available {dom} { #<<<1
633 set weekday [expr {($colstart + $dom - 1) % 7}]
634 if {$dom < $valid_from_day || $dom > $valid_to_day} {
636 } elseif {$itk_option(-onlyweekdays) && ($weekday == 0 || $weekday == 6)} {
638 } elseif {[info exists special($dom)]} {
646 body tlc::Diary::last_month {} { #<<<1
647 if {$valid_from_day < 1} {
648 set todate [clock scan "$year-$month-01 - 1 month"]
649 set month [clock format $todate -format "%m"]
650 set year [clock format $todate -format "%Y"]
658 body tlc::Diary::next_month {} { #<<<1
659 if {$valid_to_day > $days} {
660 set todate [clock scan "$year-$month-01 + 1 month"]
661 set month [clock format $todate -format "%m"]
662 set year [clock format $todate -format "%Y"]
670 body tlc::Diary::last_year {} { #<<<1
671 if {$valid_from_day < 1} {
672 set todate [clock scan "$year-$month-01 - 1 year"]
673 set month [clock format $todate -format "%m"]
674 set year [clock format $todate -format "%Y"]
682 body tlc::Diary::next_year {} { #<<<1
683 if {$valid_to_day > $days} {
684 set todate [clock scan "$year-$month-01 + 1 year"]
685 set month [clock format $todate -format "%m"]
686 set year [clock format $todate -format "%Y"]
694 body tlc::Diary::eventually_rerender {} { #<<<1
695 if {$eventually_rerender_id != ""} return
696 # log debug "tlc::Calendar::eventually_rerender: traceback rendered: ($rendered) eventually_rerender_id: ($eventually_rerender_id)\n$::errorInfo"
697 set eventually_rerender_id [after idle [code $this rerender]]
701 body tlc::Diary::scroll_month {delta} { #<<<1
716 body tlc::Diary::scroll_year {delta} { #<<<1
731 body tlc::Diary::select_day {} { #<<<1
733 foreach tag [$w.cal gettags current] {
734 if {[string range $tag 0 3] == "dom "} {
735 set dom [string range $tag 4 end]
740 set tags [$w.cal gettags current]
741 log error "tlc::Calendar::select_day: couldn't resolve dom ($tags)"
742 error "tlc::Calendar::select_day: couldn't resolve dom ($tags)"
745 if {![available $dom]} return
747 if {$itk_option(-command) != {}} {
748 uplevel #0 $itk_option(-command) [list [clock format [clock scan "$year-$month-$dom"] -format $itk_option(-resultformat)]]
751 set date_sel [list [clock format [clock scan "$year-$month-$dom"] -format "%d/%m/%Y"]]
752 puts "Diary:: date_sel $date_sel"
754 newselection $date_sel
760 body tlc::Diary::force_redraw {} { #<<<1
765 body tlc::Diary::rerender_dom_ref {} { #<<<1
769 body tlc::Diary::newselection {newdate} { #<<<1
770 set new_date $newdate
771 if {$itk_option(-textvariable) != ""} {
772 uplevel #0 [list set $itk_option(-textvariable) $date]
774 if {$itk_option(-command) != ""} {
775 uplevel #0 $itk_option(-command) [list $newdate]
777 if {$itk_option(-newselection) != {}} {
778 uplevel #0 $itk_option(-newselection) [list $newdate]
781 puts "Diary::newselection: date $new_date"
783 invoke_handlers onselect [list $newdate]
785 body tlc::Diary::newvalue {args} { #<<<1
786 if {$itk_option(-textvariable) != ""} {
787 upvar #0 $itk_option(-textvariable) invar
788 if {![info exists invar]} {
792 puts "newvalue: ($invar)"
795 body tlc::Diary::get_date {} { #<<<1
798 puts "Diary::get_date $date_sel"