home *** CD-ROM | disk | FTP | other *** search
/ PC World 2000 December / PCWorld_2000-12_cd.bin / Komunikace / Comanche / comanche.exe / lib / iwidgets3.0.0 / scripts / calendar.itk < prev    next >
Text File  |  1999-02-24  |  31KB  |  939 lines

  1. #
  2. # Calendar
  3. # ----------------------------------------------------------------------
  4. # Implements a calendar widget for the selection of a date.  It displays
  5. # a single month at a time.  Buttons exist on the top to change the 
  6. # month in effect turning th pages of a calendar.  As a page is turned, 
  7. # the dates for the month are modified.  Selection of a date visually 
  8. # marks that date.  The selected value can be monitored via the 
  9. # -command option or just retrieved using the get method.  Methods also
  10. # exist to select a date and show a particular month.  The option set
  11. # allows the calendars appearance to take on many forms.
  12. # ----------------------------------------------------------------------
  13. # AUTHOR:  Mark L. Ulferts             E-mail: mulferts@austin.dsccc.com
  14. #            
  15. # ACKNOWLEDGEMENTS: Michael McLennan   E-mail: mmclennan@lucent.com
  16. #
  17. # This code is an [incr Tk] port of the calendar code shown in Michael 
  18. # J. McLennan's book "Effective Tcl" from Addison Wesley.  Small 
  19. # modificiations were made to the logic here and there to make it a 
  20. # mega-widget and the command and option interface was expanded to make 
  21. # it even more configurable, but the underlying logic is the same.
  22. #
  23. # @(#) $Id: calendar.itk,v 1.1 1998/07/27 18:53:01 stanton Exp $
  24. # ----------------------------------------------------------------------
  25. #            Copyright (c) 1997 DSC Technologies Corporation
  26. # ======================================================================
  27. # Permission to use, copy, modify, distribute and license this software 
  28. # and its documentation for any purpose, and without fee or written 
  29. # agreement with DSC, is hereby granted, provided that the above copyright 
  30. # notice appears in all copies and that both the copyright notice and 
  31. # warranty disclaimer below appear in supporting documentation, and that 
  32. # the names of DSC Technologies Corporation or DSC Communications 
  33. # Corporation not be used in advertising or publicity pertaining to the 
  34. # software without specific, written prior permission.
  35. # DSC DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING 
  36. # ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, AND NON-
  37. # INFRINGEMENT. THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, AND THE
  38. # AUTHORS AND DISTRIBUTORS HAVE NO OBLIGATION TO PROVIDE MAINTENANCE, 
  39. # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. IN NO EVENT SHALL 
  40. # DSC BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR 
  41. # ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, 
  42. # WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTUOUS ACTION,
  43. # ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS 
  44. # SOFTWARE.
  45. # ======================================================================
  46.  
  47. #
  48. # Usual options.
  49. #
  50. itk::usual Calendar {
  51.     keep -background -cursor 
  52. }
  53.  
  54. # ------------------------------------------------------------------
  55. #                            CALENDAR
  56. # ------------------------------------------------------------------
  57. class iwidgets::Calendar {
  58.     inherit itk::Widget
  59.     
  60.     constructor {args} {}
  61.  
  62.     itk_option define -days days Days {Su Mo Tu We Th Fr Sa}
  63.     itk_option define -command command Command {}
  64.     itk_option define -forwardimage forwardImage Image {}
  65.     itk_option define -backwardimage backwardImage Image {}
  66.     itk_option define -weekdaybackground weekdayBackground Background \#d9d9d9
  67.     itk_option define -weekendbackground weekendBackground Background \#d9d9d9
  68.     itk_option define -outline outline Outline \#d9d9d9
  69.     itk_option define -buttonforeground buttonForeground Foreground blue
  70.     itk_option define -foreground foreground Foreground black
  71.     itk_option define -selectcolor selectColor Foreground red
  72.     itk_option define -selectthickness selectThickness SelectThickness 3
  73.     itk_option define -titlefont titleFont Font \
  74.     -*-helvetica-bold-r-normal--*-140-*
  75.     itk_option define -dayfont dayFont Font \
  76.     -*-helvetica-medium-r-normal--*-120-*
  77.     itk_option define -datefont dateFont Font \
  78.     -*-helvetica-medium-r-normal--*-120-*
  79.     itk_option define -currentdatefont currentDateFont Font \
  80.     -*-helvetica-bold-r-normal--*-120-*
  81.     itk_option define -startday startDay Day sunday
  82.  
  83.     public method get {{format "-string"}} ;# Returns the selected date
  84.     public method select {{date_ "now"}}   ;# Selects date, moving select ring
  85.     public method show {{date_ "now"}}     ;# Displays a specific date
  86.  
  87.     protected method _drawtext {canvas_ day_ date_ now_ x0_ y0_ x1_ y1_} 
  88.  
  89.     private method _change {delta_}
  90.     private method _configureHandler {}
  91.     private method _redraw {}
  92.     private method _days {{wmax {}}}
  93.     private method _layout {time_}
  94.     private method _select {date_}
  95.     private method _selectEvent {date_}
  96.     private method _adjustday {day_}
  97.     private method _percentSubst {pattern_ string_ subst_}
  98.  
  99.     private variable _time {}
  100.     private variable _selected {}
  101.     private variable _initialized 0
  102.     private variable _offset 0
  103. }
  104.  
  105. #
  106. # Provide a lowercased access method for the Calendar class.
  107. proc ::iwidgets::calendar {pathName args} {
  108.     uplevel ::iwidgets::Calendar $pathName $args
  109. }
  110.  
  111. #
  112. # Use option database to override default resources of base classes.
  113. #
  114. option add *Calendar.width 200 widgetDefault
  115. option add *Calendar.height 165 widgetDefault
  116.  
  117. # ------------------------------------------------------------------
  118. #                        CONSTRUCTOR
  119. # ------------------------------------------------------------------
  120. body iwidgets::Calendar::constructor {args} {
  121.     #
  122.     # Create the canvas which displays each page of the calendar.
  123.     #
  124.     itk_component add page {
  125.     canvas $itk_interior.page
  126.     } {
  127.     keep -background -cursor -width -height
  128.     }
  129.     pack $itk_component(page) -expand yes -fill both
  130.     
  131.     #
  132.     # Create the forward and backward buttons.  Rather than pack
  133.     # them directly in the hull, we'll waittill later and make
  134.     # them canvas window items.
  135.     #
  136.     itk_component add backward {
  137.     button $itk_component(page).backward \
  138.         -command [code $this _change -1]
  139.     } {
  140.     keep -background -cursor 
  141.     }
  142.  
  143.     itk_component add forward {
  144.     button $itk_component(page).forward \
  145.         -command [code $this _change +1]
  146.     } {
  147.     keep -background -cursor 
  148.     }
  149.  
  150.     #
  151.     # Set the initial time to now.
  152.     #
  153.     set _time [clock seconds]
  154.  
  155.     # 
  156.     # Bind to the configure event which will be used to redraw
  157.     # the calendar and display the month.
  158.     #
  159.     bind $itk_component(page) <Configure> [code $this _configureHandler]
  160.     
  161.     #
  162.     # Evaluate the option arguments.
  163.     #
  164.     eval itk_initialize $args
  165. }
  166.  
  167. # ------------------------------------------------------------------
  168. #                             OPTIONS
  169. # ------------------------------------------------------------------
  170.  
  171. # ------------------------------------------------------------------
  172. # OPTION: -command
  173. #
  174. # Sets the selection command for the calendar.  When the user 
  175. # selects a date on the calendar, the date is substituted in
  176. # place of "%d" in this command, and the command is executed.
  177. # ------------------------------------------------------------------
  178. configbody iwidgets::Calendar::command {}
  179.  
  180. # ------------------------------------------------------------------
  181. # OPTION: -days
  182. #
  183. # The days option takes a list of values to set the text used to display the 
  184. # days of the week header above the dates.  The default value is 
  185. # {Su Mo Tu We Th Fr Sa}.
  186. # ------------------------------------------------------------------
  187. configbody iwidgets::Calendar::days {
  188.     if {$_initialized} {
  189.     if {[$itk_component(page) find withtag days] != {}} {
  190.         $itk_component(page) delete days
  191.         _days
  192.     }
  193.     }
  194. }
  195.  
  196. # ------------------------------------------------------------------
  197. # OPTION: -backwardimage
  198. #
  199. # Specifies a image to be displayed on the backwards calendar 
  200. # button.  If none is specified, a default is provided.
  201. # ------------------------------------------------------------------
  202. configbody iwidgets::Calendar::backwardimage {
  203.  
  204.     #
  205.     # If no image is given, then we'll use the default image.
  206.     #
  207.     if {$itk_option(-backwardimage) == {}} {
  208.  
  209.     #
  210.     # If the default image hasn't yet been created, then we
  211.     # need to create it.
  212.     #
  213.     if {[lsearch [image names] $this-backward] == -1} {
  214.         image create bitmap $this-backward \
  215.             -foreground $itk_option(-buttonforeground) -data {
  216.         #define back_width 16
  217.         #define back_height 16
  218.         static unsigned char back_bits[] = {
  219.             0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x30, 
  220.             0xe0, 0x38, 0xf0, 0x3c, 0xf8, 0x3e, 0xfc, 0x3f, 
  221.             0xfc, 0x3f, 0xf8, 0x3e, 0xf0, 0x3c, 0xe0, 0x38,
  222.             0xc0, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
  223.         }
  224.     }
  225.  
  226.     #
  227.     # Configure the button to use the default image.
  228.     #
  229.     $itk_component(backward) configure -image $this-backward
  230.     
  231.     #
  232.     # Else, an image has been specified.  First, we'll need to make sure
  233.     # the image really exists before configuring the button to use it.  
  234.     # If it doesn't generate an error.
  235.     #
  236.     } else {
  237.     if {[lsearch [image names] $itk_option(-backwardimage)] != -1} {
  238.         $itk_component(backward) configure \
  239.             -image $itk_option(-backwardimage)
  240.     } else {
  241.         error "bad image name \"$itk_option(-backwardimage)\":\
  242.             image does not exist"
  243.     }
  244.  
  245.     #
  246.     # If we previously created a default image, we'll just remove it.
  247.     #
  248.     if {[lsearch [image names] $this-backward] != -1} {
  249.         image delete $this-backward
  250.     }
  251.     }
  252. }
  253.  
  254.  
  255. # ------------------------------------------------------------------
  256. # OPTION: -forwardimage
  257. #
  258. # Specifies a image to be displayed on the forwards calendar 
  259. # button.  If none is specified, a default is provided.
  260. # ------------------------------------------------------------------
  261. configbody iwidgets::Calendar::forwardimage {
  262.  
  263.     #
  264.     # If no image is given, then we'll use the default image.
  265.     #
  266.     if {$itk_option(-forwardimage) == {}} {
  267.  
  268.     #
  269.     # If the default image hasn't yet been created, then we
  270.     # need to create it.
  271.     #
  272.     if {[lsearch [image names] $this-forward] == -1} {
  273.         image create bitmap $this-forward \
  274.             -foreground $itk_option(-buttonforeground) -data {
  275.         #define fwd_width 16
  276.         #define fwd_height 16
  277.         static unsigned char fwd_bits[] = {
  278.             0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x03, 
  279.             0x1c, 0x07, 0x3c, 0x0f, 0x7c, 0x1f, 0xfc, 0x3f, 
  280.             0xfc, 0x3f, 0x7c, 0x1f, 0x3c, 0x0f, 0x1c, 0x07,
  281.             0x0c, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
  282.         }
  283.     }
  284.  
  285.     #
  286.     # Configure the button to use the default image.
  287.     #
  288.     $itk_component(forward) configure -image $this-forward
  289.     
  290.     #
  291.     # Else, an image has been specified.  First, we'll need to make sure
  292.     # the image really exists before configuring the button to use it.  
  293.     # If it doesn't generate an error.
  294.     #
  295.     } else {
  296.     if {[lsearch [image names] $itk_option(-forwardimage)] != -1} {
  297.         $itk_component(forward) configure \
  298.             -image $itk_option(-forwardimage)
  299.     } else {
  300.         error "bad image name \"$itk_option(-forwardimage)\":\
  301.             image does not exist"
  302.     }
  303.  
  304.     #
  305.     # If we previously created a default image, we'll just remove it.
  306.     #
  307.     if {[lsearch [image names] $this-forward] != -1} {
  308.         image delete $this-forward
  309.     }
  310.     }
  311. }
  312.  
  313. # ------------------------------------------------------------------
  314. # OPTION: -weekdaybackground
  315. #
  316. # Specifies the background for the weekdays which allows it to
  317. # be visually distinguished from the weekend.
  318. # ------------------------------------------------------------------
  319. configbody iwidgets::Calendar::weekdaybackground {
  320.     if {$_initialized} {
  321.     $itk_component(page) itemconfigure weekday \
  322.         -fill $itk_option(-weekdaybackground)
  323.     }
  324. }
  325.  
  326. # ------------------------------------------------------------------
  327. # OPTION: -weekendbackground
  328. #
  329. # Specifies the background for the weekdays which allows it to
  330. # be visually distinguished from the weekdays.
  331. # ------------------------------------------------------------------
  332. configbody iwidgets::Calendar::weekendbackground {
  333.     if {$_initialized} {
  334.     $itk_component(page) itemconfigure weekend \
  335.         -fill $itk_option(-weekendbackground)
  336.     }
  337. }
  338.  
  339. # ------------------------------------------------------------------
  340. # OPTION: -foreground
  341. #
  342. # Specifies the foreground color for the textual items, buttons,
  343. # and divider on the calendar.
  344. # ------------------------------------------------------------------
  345. configbody iwidgets::Calendar::foreground {
  346.     if {$_initialized} {
  347.     $itk_component(page) itemconfigure text \
  348.         -fill $itk_option(-foreground)
  349.     $itk_component(page) itemconfigure line \
  350.         -fill $itk_option(-foreground)
  351.     }
  352. }
  353.  
  354. # ------------------------------------------------------------------
  355. # OPTION: -outline
  356. #
  357. # Specifies the outline color used to surround the date text.
  358. # ------------------------------------------------------------------
  359. configbody iwidgets::Calendar::outline {
  360.     if {$_initialized} {
  361.     $itk_component(page) itemconfigure square \
  362.         -outline $itk_option(-outline)
  363.     }
  364. }
  365.  
  366. # ------------------------------------------------------------------
  367. # OPTION: -buttonforeground
  368. #
  369. # Specifies the foreground color of the forward and backward buttons.
  370. # ------------------------------------------------------------------
  371. configbody iwidgets::Calendar::buttonforeground {
  372.     if {$_initialized} {
  373.     if {$itk_option(-forwardimage) == {}} {
  374.         if {[lsearch [image names] $this-forward] != -1} {
  375.         $this-forward configure \
  376.             -foreground $itk_option(-buttonforeground)
  377.         }
  378.     } else {
  379.         $itk_option(-forwardimage) configure \
  380.             -foreground $itk_option(-buttonforeground)
  381.     }
  382.     
  383.     if {$itk_option(-backwardimage) == {}} {
  384.         if {[lsearch [image names] $this-backward] != -1} {
  385.         $this-backward configure \
  386.             -foreground $itk_option(-buttonforeground)
  387.         }
  388.     } else {
  389.         $itk_option(-backwardimage) configure \
  390.             -foreground $itk_option(-buttonforeground)
  391.     }
  392.     }
  393. }
  394.  
  395. # ------------------------------------------------------------------
  396. # OPTION: -selectcolor
  397. #
  398. # Specifies the color of the ring displayed that distinguishes the 
  399. # currently selected date.  
  400. # ------------------------------------------------------------------
  401. configbody iwidgets::Calendar::selectcolor {
  402.     if {$_initialized} {
  403.     $itk_component(page) itemconfigure $_selected-sensor \
  404.         -outline $itk_option(-selectcolor) 
  405.     }
  406. }
  407.  
  408. # ------------------------------------------------------------------
  409. # OPTION: -selectthickness
  410. #
  411. # Specifies the thickness of the ring displayed that distinguishes 
  412. # the currently selected date.  
  413. # ------------------------------------------------------------------
  414. configbody iwidgets::Calendar::selectthickness {
  415.     if {$_initialized} {
  416.     $itk_component(page) itemconfigure $_selected-sensor \
  417.         -width $itk_option(-selectthickness) 
  418.     }
  419. }
  420.  
  421. # ------------------------------------------------------------------
  422. # OPTION: -titlefont
  423. #
  424. # Specifies the font used for the title text that consists of the 
  425. # month and year.
  426. # ------------------------------------------------------------------
  427. configbody iwidgets::Calendar::titlefont {
  428.     if {$_initialized} {
  429.     $itk_component(page) itemconfigure title \
  430.         -font $itk_option(-titlefont)
  431.     }
  432. }
  433.  
  434. # ------------------------------------------------------------------
  435. # OPTION: -datefont
  436. #
  437. # Specifies the font used for the date text that consists of the 
  438. # day of the month.
  439. # ------------------------------------------------------------------
  440. configbody iwidgets::Calendar::datefont {
  441.     if {$_initialized} {
  442.     $itk_component(page) itemconfigure date \
  443.         -font $itk_option(-datefont)
  444.     }
  445. }
  446.  
  447. # ------------------------------------------------------------------
  448. # OPTION: -currentdatefont
  449. #
  450. # Specifies the font used for the current date text.
  451. # ------------------------------------------------------------------
  452. configbody iwidgets::Calendar::currentdatefont {
  453.     if {$_initialized} {
  454.     $itk_component(page) itemconfigure now \
  455.         -font $itk_option(-currentdatefont)
  456.     }
  457. }
  458.  
  459. # ------------------------------------------------------------------
  460. # OPTION: -dayfont
  461. #
  462. # Specifies the font used for the day of the week text.
  463. # ------------------------------------------------------------------
  464. configbody iwidgets::Calendar::dayfont {
  465.     if {$_initialized} {
  466.     $itk_component(page) itemconfigure days \
  467.         -font $itk_option(-dayfont)
  468.     }
  469. }
  470.  
  471. # ------------------------------------------------------------------
  472. # OPTION: -startday
  473. #
  474. # Specifies the starting day for the week.  The value must be a day of the
  475. # week: sunday, monday, tuesday, wednesday, thursday, friday, or
  476. # saturday.  The default is sunday.
  477. # ------------------------------------------------------------------
  478. configbody iwidgets::Calendar::startday {
  479.     set day [string tolower $itk_option(-startday)]
  480.  
  481.     switch $day {
  482.     sunday {set _offset 0}
  483.     monday {set _offset 1}
  484.     tuesday {set _offset 2}
  485.     wednesday {set _offset 3}
  486.     thursday {set _offset 4}
  487.     friday {set _offset 5}
  488.     saturday {set _offset 6}
  489.     default {
  490.         error "bad startday option \"$itk_option(-startday)\":\
  491.                    should be sunday, monday, tuesday, wednesday,\
  492.                    thursday, friday, or saturday"
  493.     }
  494.     }
  495.  
  496.     if {$_initialized} {
  497.     $itk_component(page) delete all-page
  498.     _redraw
  499.     }
  500. }
  501.  
  502. # ------------------------------------------------------------------
  503. #                            METHODS
  504. # ------------------------------------------------------------------
  505.  
  506. # ------------------------------------------------------------------
  507. # PUBLIC METHOD: get ?format?
  508. #
  509. # Returns the currently selected date in one of two formats, string 
  510. # or as an integer clock value using the -string and -clicks
  511. # options respectively.  The default is by string.  Reference the 
  512. # clock command for more information on obtaining dates and their 
  513. # formats.
  514. # ------------------------------------------------------------------
  515. body iwidgets::Calendar::get {{format "-string"}} {
  516.     switch -- $format {
  517.     "-string" {
  518.         return $_selected
  519.     }
  520.     "-clicks" {
  521.         return [clock scan $_selected]
  522.     }
  523.     default {
  524.         error "bad format option \"$format\":\
  525.                    should be -string or -clicks"
  526.     }
  527.     }
  528. }
  529.  
  530. # ------------------------------------------------------------------
  531. # PUBLIC METHOD: select date_
  532. #
  533. # Changes the currently selected date to the value specified.
  534. # ------------------------------------------------------------------
  535. body iwidgets::Calendar::select {{date_ "now"}} {
  536.     if {$date_ == "now"} {
  537.     set time [clock seconds]
  538.     } else {
  539.     if {[catch {clock format $date_}] == 0} {
  540.         set time $date_
  541.     } elseif {[catch {set time [clock scan $date_]}] != 0} {
  542.         error "bad date: \"$date_\", must be a valid date string, clock clicks value or the keyword now"
  543.     }
  544.     }
  545.  
  546.     _select [clock format $time -format "%m/%d/%Y"]
  547. }
  548.  
  549. # ------------------------------------------------------------------
  550. # PUBLIC METHOD: show date_
  551. #
  552. # Changes the currently display month to be that of the specified 
  553. # date.
  554. # ------------------------------------------------------------------
  555. body iwidgets::Calendar::show {{date_ "now"}} {
  556.     if {$date_ == "now"} {
  557.     set _time [clock seconds]
  558.     } else {
  559.     if {[catch {clock format $date_}] == 0} {
  560.         set _time $date_
  561.     } elseif {[catch {set _time [clock scan $date_]}] != 0} {
  562.         error "bad date: \"$date_\", must be a valid date string, clock clicks value or the keyword now"
  563.     }
  564.     }
  565.  
  566.     $itk_component(page) delete all-page
  567.     _redraw
  568. }
  569.  
  570. # ------------------------------------------------------------------
  571. # PROTECTED METHOD: _drawtext canvas_ day_ date_ now_
  572. #                             x0_ y0_ x1_ y1_
  573. #
  574. # Draws the text in the date square.  The method is protected such that
  575. # it can be overridden in derived classes that may wish to add their
  576. # own unique text.  The method receives the day to draw along with
  577. # the coordinates of the square.
  578. # ------------------------------------------------------------------
  579. body iwidgets::Calendar::_drawtext {canvas_ day_ date_ now_ x0_ y0_ x1_ y1_} {
  580.     set item [$canvas_ create text \
  581.           [expr (($x1_ - $x0_) / 2) + $x0_] \
  582.           [expr (($y1_ -$y0_) / 2) + $y0_ + 1] \
  583.           -anchor center -text "$day_" \
  584.           -fill $itk_option(-foreground)]
  585.  
  586.     if {$date_ == $now_} {
  587.     $canvas_ itemconfigure $item \
  588.         -font $itk_option(-currentdatefont) \
  589.         -tags [list all-page date text now]
  590.     } else {
  591.     $canvas_ itemconfigure $item \
  592.         -font $itk_option(-datefont) \
  593.         -tags [list all-page date text]
  594.     }
  595. }
  596.  
  597. # ------------------------------------------------------------------
  598. # PRIVATE METHOD: _configureHandler
  599. #
  600. # Processes a configure event received on the canvas.  The method
  601. # deletes all the current canvas items and forces a redraw.
  602. # ------------------------------------------------------------------
  603. body iwidgets::Calendar::_configureHandler {} {
  604.     set _initialized 1
  605.  
  606.     $itk_component(page) delete all
  607.     _redraw
  608. }
  609.  
  610. # ------------------------------------------------------------------
  611. # PRIVATE METHOD: _change delta_
  612. #
  613. # Changes the current month displayed in the calendar, moving
  614. # forward or backward by <delta_> months where <delta_> is +/-
  615. # some number.
  616. # ------------------------------------------------------------------
  617. body iwidgets::Calendar::_change {delta_} {
  618.     set dir [expr ($delta_ > 0) ? 1 : -1]
  619.     set month [clock format $_time -format "%m"]
  620.     set month [string trimleft $month 0]
  621.     set year [clock format $_time -format "%Y"]
  622.  
  623.     for {set i 0} {$i < abs($delta_)} {incr i} {
  624.         incr month $dir
  625.         if {$month < 1} {
  626.             set month 12
  627.             incr year -1
  628.         } elseif {$month > 12} {
  629.             set month 1
  630.             incr year 1
  631.         }
  632.     }
  633.     if {[catch {set _time [clock scan "$month/1/$year"]}]} {
  634.     bell
  635.     } else {
  636.     _redraw 
  637.     }
  638. }
  639.  
  640. # ------------------------------------------------------------------
  641. # PRIVATE METHOD: _redraw
  642. #
  643. # Redraws the calendar.  This method is invoked whenever the 
  644. # calendar changes size or we need to effect a change such as draw
  645. # it with a new month.
  646. # ------------------------------------------------------------------
  647. body iwidgets::Calendar::_redraw {} {
  648.     #
  649.     # Remove all the items that typically change per redraw request
  650.     # such as the title and dates.  Also, get the maximum width and
  651.     # height of the page.
  652.     #
  653.     $itk_component(page) delete all-page
  654.  
  655.     set wmax [winfo width $itk_component(page)]
  656.     set hmax [winfo height $itk_component(page)]
  657.  
  658.     #
  659.     # If we haven't yet created the forward and backwards buttons,
  660.     # then dot it; otherwise, skip it.
  661.     #
  662.     if {[$itk_component(page) find withtag button] == {}} {
  663.     $itk_component(page) create window 3 3 -anchor nw \
  664.         -window $itk_component(backward) -tags button
  665.     $itk_component(page) create window [expr $wmax-3] 3 -anchor ne \
  666.         -window $itk_component(forward) -tags button
  667.     }
  668.  
  669.     #
  670.     # Create the title centered between the buttons.
  671.     #
  672.     foreach {x0 y0 x1 y1} [$itk_component(page) bbox button] {
  673.     set x [expr (($x1-$x0)/2)+$x0]
  674.     set y [expr (($y1-$y0)/2)+$y0]
  675.     }
  676.  
  677.     set title [clock format $_time -format "%B %Y"]
  678.     $itk_component(page) create text $x $y -anchor center \
  679.         -text $title -font $itk_option(-titlefont) \
  680.     -fill $itk_option(-foreground) \
  681.     -tags [list title text all-page]
  682.  
  683.     #
  684.     # Add the days of the week labels if they haven't yet been created.
  685.     #
  686.     if {[$itk_component(page) find withtag days] == {}} {
  687.     _days $wmax
  688.     }
  689.  
  690.     #
  691.     # Add a line between the calendar header and the dates if needed.
  692.     #
  693.     set bottom [expr [lindex [$itk_component(page) bbox all] 3] + 3]
  694.  
  695.     if {[$itk_component(page) find withtag line] == {}} {
  696.     $itk_component(page) create line 0 $bottom $wmax $bottom \
  697.         -width 2 -tags line
  698.     }
  699.  
  700.     incr bottom 3
  701.  
  702.     #
  703.     # Get the layout for the time value and create the date squares.
  704.     # This includes the surrounding date rectangle, the date text,
  705.     # and the sensor.  Bind selection to the sensor.
  706.     #
  707.     set current ""
  708.     set now [clock format [clock seconds] -format "%m/%d/%Y"]
  709.  
  710.     set layout [_layout $_time]
  711.     set weeks [expr [lindex $layout end] + 1]
  712.  
  713.     foreach {day date kind dcol wrow} $layout {
  714.         set x0 [expr $dcol*($wmax-7)/7+3]
  715.         set y0 [expr $wrow*($hmax-$bottom-4)/$weeks+$bottom]
  716.         set x1 [expr ($dcol+1)*($wmax-7)/7+3]
  717.         set y1 [expr ($wrow+1)*($hmax-$bottom-4)/$weeks+$bottom]
  718.  
  719.         if {$date == $_selected} {
  720.             set current $date
  721.         }
  722.  
  723.     #
  724.     # Create the rectangle that surrounds the date and configure
  725.     # its background based on the wheather it is a weekday or
  726.     # a weekend.
  727.     #
  728.     set item [$itk_component(page) create rectangle $x0 $y0 $x1 $y1 \
  729.         -outline $itk_option(-outline)]
  730.  
  731.     if {$kind == "weekend"} {
  732.         $itk_component(page) itemconfigure $item \
  733.             -fill $itk_option(-weekendbackground) \
  734.             -tags [list all-page square weekend]
  735.     } else {
  736.         $itk_component(page) itemconfigure $item \
  737.             -fill $itk_option(-weekdaybackground) \
  738.             -tags [list all-page square weekday]
  739.     }
  740.  
  741.     #
  742.     # Create the date text and configure its font based on the 
  743.     # wheather or not it is the current date.
  744.     #
  745.     _drawtext $itk_component(page) $day $date $now $x0 $y0 $x1 $y1
  746.  
  747.     #
  748.     # Create a sensor area to detect selections.  Bind the 
  749.     # sensor and pass the date to the bind script.
  750.     #
  751.         $itk_component(page) create rectangle $x0 $y0 $x1 $y1 \
  752.             -outline "" -fill "" \
  753.             -tags [list $date-sensor all-sensor all-page]
  754.  
  755.         $itk_component(page) bind $date-sensor <ButtonPress-1> \
  756.             [code $this _selectEvent $date]
  757.     }
  758.  
  759.     #
  760.     # Highlight the selected date if it is on this page.
  761.     #
  762.     if {$current != ""} {
  763.         $itk_component(page) itemconfigure $current-sensor \
  764.             -outline $itk_option(-selectcolor) \
  765.         -width $itk_option(-selectthickness)
  766.  
  767.         $itk_component(page) raise $current-sensor
  768.  
  769.     } elseif {$_selected == ""} {
  770.         set date [clock format $_time -format "%m/%d/%Y"]
  771.         _select $date
  772.     }
  773. }
  774.  
  775. # ------------------------------------------------------------------
  776. # PRIVATE METHOD: _days
  777. #
  778. # Used to rewite the days of the week label just below the month 
  779. # title string.  The days are given in the -days option.
  780. # ------------------------------------------------------------------
  781. body iwidgets::Calendar::_days {{wmax {}}} {
  782.     if {$wmax == {}} {
  783.     set wmax [winfo width $itk_component(page)]
  784.     }
  785.  
  786.     set col 0
  787.     set bottom [expr [lindex [$itk_component(page) bbox title buttons] 3] + 7]
  788.  
  789.     foreach dayoweek $itk_option(-days) {
  790.     set x0 [expr $col*($wmax/7)]
  791.     set x1 [expr ($col+1)*($wmax/7)]
  792.  
  793.     $itk_component(page) create text \
  794.         [expr (($x1 - $x0) / 2) + $x0] $bottom \
  795.         -anchor n -text "$dayoweek" \
  796.         -fill $itk_option(-foreground) \
  797.         -font $itk_option(-dayfont) \
  798.         -tags [list days text]
  799.  
  800.     incr col
  801.     }
  802. }
  803.  
  804. # ------------------------------------------------------------------
  805. # PRIVATE METHOD: _layout time_
  806. #
  807. # Used whenever the calendar is redrawn.  Finds the month containing
  808. # a <time_> in seconds, and returns a list for all of the days in 
  809. # that month.  The list looks like this:
  810. #
  811. #    {day1 date1 kind1 c1 r1  day2 date2 kind2 c2 r2  ...}
  812. #
  813. # where dayN is a day number like 1,2,3,..., dateN is the date for
  814. # dayN, kindN is the day type of weekday or weekend, and cN,rN 
  815. # are the column/row indices for the square containing that date.
  816. # ------------------------------------------------------------------
  817. body iwidgets::Calendar::_layout {time_} {
  818.     set month [clock format $time_ -format "%m"]
  819.     set year  [clock format $time_ -format "%Y"]
  820.  
  821.     foreach lastday {31 30 29 28} {
  822.         if {[catch {clock scan "$month/$lastday/$year"}] == 0} {
  823.             break
  824.         }
  825.     }
  826.     set seconds [clock scan "$month/1/$year"]
  827.     set firstday [_adjustday [clock format $seconds -format %w]]
  828.  
  829.     set weeks [expr ceil(double($lastday+$firstday)/7)]
  830.  
  831.     set rlist ""
  832.     for {set day 1} {$day <= $lastday} {incr day} {
  833.         set seconds [clock scan "$month/$day/$year"]
  834.         set date [clock format $seconds -format "%m/%d/%Y"]
  835.     set dayoweek [clock format $seconds -format %w]
  836.  
  837.     if {$dayoweek == 0 || $dayoweek == 6} {
  838.         set kind "weekend"
  839.     } else {
  840.         set kind "weekday"
  841.     }
  842.  
  843.         set daycol [_adjustday $dayoweek]
  844.  
  845.         set weekrow [expr ($firstday+$day-1)/7]
  846.         lappend rlist $day $date $kind $daycol $weekrow 
  847.     }
  848.     return $rlist
  849. }
  850.  
  851. # ------------------------------------------------------------------
  852. # PRIVATE METHOD: _adjustday day_
  853. #
  854. # Modifies the day to be in accordance with the startday option.
  855. # ------------------------------------------------------------------
  856. body iwidgets::Calendar::_adjustday {day_} {
  857.     set retday [expr $day_ - $_offset]
  858.  
  859.     if {$retday < 0} {
  860.     set retday [expr $retday + 7]
  861.     }
  862.  
  863.     return $retday
  864. }
  865.  
  866. # ------------------------------------------------------------------
  867. # PRIVATE METHOD: _select date_
  868. #
  869. # Selects the current <date_> on the calendar.  Highlights the date 
  870. # on the calendar, and executes the command associated with the 
  871. # calendar, with the selected date substituted in place of "%d".
  872. # ------------------------------------------------------------------
  873. body iwidgets::Calendar::_select {date_} {
  874.     set time [clock scan $date_]
  875.     set date [clock format $time -format "%m/%d/%Y"]
  876.  
  877.     set _selected $date
  878.  
  879.     set current [clock format $_time -format "%m %Y"]
  880.     set selected [clock format $time -format "%m %Y"]
  881.  
  882.     if {$current == $selected} {
  883.         $itk_component(page) itemconfigure all-sensor \
  884.             -outline "" -width 1
  885.  
  886.         $itk_component(page) itemconfigure $date-sensor \
  887.             -outline $itk_option(-selectcolor) \
  888.         -width $itk_option(-selectthickness)
  889.         $itk_component(page) raise $date-sensor
  890.     } else {
  891.         set $_time $time
  892.         _redraw 
  893.     }
  894. }
  895.  
  896. # ------------------------------------------------------------------
  897. # PRIVATE METHOD: _selectEvent date_
  898. #
  899. # Selects the current <date_> on the calendar.  Highlights the date 
  900. # on the calendar, and executes the command associated with the 
  901. # calendar, with the selected date substituted in place of "%d".
  902. # ------------------------------------------------------------------
  903. body iwidgets::Calendar::_selectEvent {date_} {
  904.     _select $date_
  905.  
  906.     if {[string trim $itk_option(-command)] != ""} {
  907.         set cmd $itk_option(-command)
  908.         set cmd [_percentSubst %d $cmd [get]]
  909.         uplevel #0 $cmd
  910.     }
  911. }
  912.  
  913. # ------------------------------------------------------------------
  914. # PRIVATE METHOD: _percentSubst pattern_ string_ subst_
  915. #
  916. # This command is a "safe" version of regsub, for substituting
  917. # each occurance of <%pattern_> in <string_> with <subst_>.  The
  918. # usual Tcl "regsub" command does the same thing, but also
  919. # converts characters like "&" and "\0", "\1", etc. that may
  920. # be present in the <subst_> string.
  921. #
  922. # Returns <string_> with <subst_> substituted in place of each
  923. # <%pattern_>.
  924. # ------------------------------------------------------------------
  925. body iwidgets::Calendar::_percentSubst {pattern_ string_ subst_} {
  926.     if {![string match %* $pattern_]} {
  927.         error "bad pattern \"$pattern_\": should be %something"
  928.     }
  929.  
  930.     set rval ""
  931.     while {[regexp "(.*)${pattern_}(.*)" $string_ all head tail]} {
  932.         set rval "$subst_$tail$rval"
  933.         set string_ $head
  934.     }
  935.     set rval "$string_$rval"
  936. }
  937.