home *** CD-ROM | disk | FTP | other *** search
/ PC World 2005 June / PCWorld_2005-06_cd.bin / software / vyzkuste / firewally / firewally.exe / framework-2.3.exe / calendar.itk < prev    next >
Text File  |  2003-09-01  |  33KB  |  984 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.7 2002/09/05 19:33:06 smithc 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. itcl::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.     itk_option define -int int DateFormat no
  83.  
  84.     public method get {{format "-string"}} ;# Returns the selected date
  85.     public method select {{date_ "now"}}   ;# Selects date, moving select ring
  86.     public method show {{date_ "now"}}     ;# Displays a specific date
  87.  
  88.     protected method _drawtext {canvas_ day_ date_ now_ x0_ y0_ x1_ y1_} 
  89.  
  90.     private method _change {delta_}
  91.     private method _configureHandler {}
  92.     private method _redraw {}
  93.     private method _days {{wmax {}}}
  94.     private method _layout {time_}
  95.     private method _select {date_}
  96.     private method _selectEvent {date_}
  97.     private method _adjustday {day_}
  98.     private method _percentSubst {pattern_ string_ subst_}
  99.  
  100.     private variable _time {}
  101.     private variable _selected {}
  102.     private variable _initialized 0
  103.     private variable _offset 0
  104.     private variable _format {}
  105. }
  106.  
  107. #
  108. # Provide a lowercased access method for the Calendar class.
  109. proc ::iwidgets::calendar {pathName args} {
  110.     uplevel ::iwidgets::Calendar $pathName $args
  111. }
  112.  
  113. #
  114. # Use option database to override default resources of base classes.
  115. #
  116. option add *Calendar.width 200 widgetDefault
  117. option add *Calendar.height 165 widgetDefault
  118.  
  119. # ------------------------------------------------------------------
  120. #                        CONSTRUCTOR
  121. # ------------------------------------------------------------------
  122. itcl::body iwidgets::Calendar::constructor {args} {
  123.     #
  124.     # Create the canvas which displays each page of the calendar.
  125.     #
  126.     itk_component add page {
  127.     canvas $itk_interior.page
  128.     } {
  129.     keep -background -cursor -width -height
  130.     }
  131.     pack $itk_component(page) -expand yes -fill both
  132.     
  133.     #
  134.     # Create the forward and backward buttons.  Rather than pack
  135.     # them directly in the hull, we'll waittill later and make
  136.     # them canvas window items.
  137.     #
  138.     itk_component add backward {
  139.     button $itk_component(page).backward \
  140.         -command [itcl::code $this _change -1]
  141.     } {
  142.     keep -background -cursor 
  143.     }
  144.  
  145.     itk_component add forward {
  146.     button $itk_component(page).forward \
  147.         -command [itcl::code $this _change +1]
  148.     } {
  149.     keep -background -cursor 
  150.     }
  151.  
  152.     #
  153.     # Set the initial time to now.
  154.     #
  155.     set _time [clock seconds]
  156.  
  157.     # 
  158.     # Bind to the configure event which will be used to redraw
  159.     # the calendar and display the month.
  160.     #
  161.     bind $itk_component(page) <Configure> [itcl::code $this _configureHandler]
  162.     
  163.     #
  164.     # Evaluate the option arguments.
  165.     #
  166.     eval itk_initialize $args
  167. }
  168.  
  169. # ------------------------------------------------------------------
  170. #                             OPTIONS
  171. # ------------------------------------------------------------------
  172. # ------------------------------------------------------------------
  173. # OPTION: -int
  174. #
  175. # Added by Mark Alston 2001/10/21
  176. #
  177. # Allows for the use of dates in "international" format: YYYY-MM-DD.
  178. # It must be a boolean value.
  179. # ------------------------------------------------------------------
  180. itcl::configbody iwidgets::Calendar::int { 
  181.     switch $itk_option(-int) {
  182.     1 - yes - true - on {
  183.       set itk_option(-int) yes
  184.     }
  185.     0 - no - false - off {
  186.       set itk_option(-int) no
  187.     }
  188.     default {
  189.         error "bad int option \"$itk_option(-int)\": should be boolean"
  190.     }
  191.     }
  192. }
  193.  
  194. # ------------------------------------------------------------------
  195. # OPTION: -command
  196. #
  197. # Sets the selection command for the calendar.  When the user 
  198. # selects a date on the calendar, the date is substituted in
  199. # place of "%d" in this command, and the command is executed.
  200. # ------------------------------------------------------------------
  201. itcl::configbody iwidgets::Calendar::command {}
  202.  
  203. # ------------------------------------------------------------------
  204. # OPTION: -days
  205. #
  206. # The days option takes a list of values to set the text used to display the 
  207. # days of the week header above the dates.  The default value is 
  208. # {Su Mo Tu We Th Fr Sa}.
  209. # ------------------------------------------------------------------
  210. itcl::configbody iwidgets::Calendar::days {
  211.     if {$_initialized} {
  212.     if {[$itk_component(page) find withtag days] != {}} {
  213.         $itk_component(page) delete days
  214.         _days
  215.     }
  216.     }
  217. }
  218.  
  219. # ------------------------------------------------------------------
  220. # OPTION: -backwardimage
  221. #
  222. # Specifies a image to be displayed on the backwards calendar 
  223. # button.  If none is specified, a default is provided.
  224. # ------------------------------------------------------------------
  225. itcl::configbody iwidgets::Calendar::backwardimage {
  226.  
  227.     #
  228.     # If no image is given, then we'll use the default image.
  229.     #
  230.     if {$itk_option(-backwardimage) == {}} {
  231.  
  232.     #
  233.     # If the default image hasn't yet been created, then we
  234.     # need to create it.
  235.     #
  236.     if {[lsearch [image names] $this-backward] == -1} {
  237.         image create bitmap $this-backward \
  238.             -foreground $itk_option(-buttonforeground) -data {
  239.         #define back_width 16
  240.         #define back_height 16
  241.         static unsigned char back_bits[] = {
  242.             0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x30, 
  243.             0xe0, 0x38, 0xf0, 0x3c, 0xf8, 0x3e, 0xfc, 0x3f, 
  244.             0xfc, 0x3f, 0xf8, 0x3e, 0xf0, 0x3c, 0xe0, 0x38,
  245.             0xc0, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
  246.         }
  247.     }
  248.  
  249.     #
  250.     # Configure the button to use the default image.
  251.     #
  252.     $itk_component(backward) configure -image $this-backward
  253.     
  254.     #
  255.     # Else, an image has been specified.  First, we'll need to make sure
  256.     # the image really exists before configuring the button to use it.  
  257.     # If it doesn't generate an error.
  258.     #
  259.     } else {
  260.     if {[lsearch [image names] $itk_option(-backwardimage)] != -1} {
  261.         $itk_component(backward) configure \
  262.             -image $itk_option(-backwardimage)
  263.     } else {
  264.         error "bad image name \"$itk_option(-backwardimage)\":\
  265.             image does not exist"
  266.     }
  267.  
  268.     #
  269.     # If we previously created a default image, we'll just remove it.
  270.     #
  271.     if {[lsearch [image names] $this-backward] != -1} {
  272.         image delete $this-backward
  273.     }
  274.     }
  275. }
  276.  
  277.  
  278. # ------------------------------------------------------------------
  279. # OPTION: -forwardimage
  280. #
  281. # Specifies a image to be displayed on the forwards calendar 
  282. # button.  If none is specified, a default is provided.
  283. # ------------------------------------------------------------------
  284. itcl::configbody iwidgets::Calendar::forwardimage {
  285.  
  286.     #
  287.     # If no image is given, then we'll use the default image.
  288.     #
  289.     if {$itk_option(-forwardimage) == {}} {
  290.  
  291.     #
  292.     # If the default image hasn't yet been created, then we
  293.     # need to create it.
  294.     #
  295.     if {[lsearch [image names] $this-forward] == -1} {
  296.         image create bitmap $this-forward \
  297.             -foreground $itk_option(-buttonforeground) -data {
  298.         #define fwd_width 16
  299.         #define fwd_height 16
  300.         static unsigned char fwd_bits[] = {
  301.             0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x03, 
  302.             0x1c, 0x07, 0x3c, 0x0f, 0x7c, 0x1f, 0xfc, 0x3f, 
  303.             0xfc, 0x3f, 0x7c, 0x1f, 0x3c, 0x0f, 0x1c, 0x07,
  304.             0x0c, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
  305.         }
  306.     }
  307.  
  308.     #
  309.     # Configure the button to use the default image.
  310.     #
  311.     $itk_component(forward) configure -image $this-forward
  312.     
  313.     #
  314.     # Else, an image has been specified.  First, we'll need to make sure
  315.     # the image really exists before configuring the button to use it.  
  316.     # If it doesn't generate an error.
  317.     #
  318.     } else {
  319.     if {[lsearch [image names] $itk_option(-forwardimage)] != -1} {
  320.         $itk_component(forward) configure \
  321.             -image $itk_option(-forwardimage)
  322.     } else {
  323.         error "bad image name \"$itk_option(-forwardimage)\":\
  324.             image does not exist"
  325.     }
  326.  
  327.     #
  328.     # If we previously created a default image, we'll just remove it.
  329.     #
  330.     if {[lsearch [image names] $this-forward] != -1} {
  331.         image delete $this-forward
  332.     }
  333.     }
  334. }
  335.  
  336. # ------------------------------------------------------------------
  337. # OPTION: -weekdaybackground
  338. #
  339. # Specifies the background for the weekdays which allows it to
  340. # be visually distinguished from the weekend.
  341. # ------------------------------------------------------------------
  342. itcl::configbody iwidgets::Calendar::weekdaybackground {
  343.     if {$_initialized} {
  344.     $itk_component(page) itemconfigure weekday \
  345.         -fill $itk_option(-weekdaybackground)
  346.     }
  347. }
  348.  
  349. # ------------------------------------------------------------------
  350. # OPTION: -weekendbackground
  351. #
  352. # Specifies the background for the weekdays which allows it to
  353. # be visually distinguished from the weekdays.
  354. # ------------------------------------------------------------------
  355. itcl::configbody iwidgets::Calendar::weekendbackground {
  356.     if {$_initialized} {
  357.     $itk_component(page) itemconfigure weekend \
  358.         -fill $itk_option(-weekendbackground)
  359.     }
  360. }
  361.  
  362. # ------------------------------------------------------------------
  363. # OPTION: -foreground
  364. #
  365. # Specifies the foreground color for the textual items, buttons,
  366. # and divider on the calendar.
  367. # ------------------------------------------------------------------
  368. itcl::configbody iwidgets::Calendar::foreground {
  369.     if {$_initialized} {
  370.     $itk_component(page) itemconfigure text \
  371.         -fill $itk_option(-foreground)
  372.     $itk_component(page) itemconfigure line \
  373.         -fill $itk_option(-foreground)
  374.     }
  375. }
  376.  
  377. # ------------------------------------------------------------------
  378. # OPTION: -outline
  379. #
  380. # Specifies the outline color used to surround the date text.
  381. # ------------------------------------------------------------------
  382. itcl::configbody iwidgets::Calendar::outline {
  383.     if {$_initialized} {
  384.     $itk_component(page) itemconfigure square \
  385.         -outline $itk_option(-outline)
  386.     }
  387. }
  388.  
  389. # ------------------------------------------------------------------
  390. # OPTION: -buttonforeground
  391. #
  392. # Specifies the foreground color of the forward and backward buttons.
  393. # ------------------------------------------------------------------
  394. itcl::configbody iwidgets::Calendar::buttonforeground {
  395.     if {$_initialized} {
  396.     if {$itk_option(-forwardimage) == {}} {
  397.         if {[lsearch [image names] $this-forward] != -1} {
  398.         $this-forward configure \
  399.             -foreground $itk_option(-buttonforeground)
  400.         }
  401.     } else {
  402.         $itk_component(forward) configure \
  403.             -foreground $itk_option(-buttonforeground)
  404.     }
  405.     
  406.     if {$itk_option(-backwardimage) == {}} {
  407.         if {[lsearch [image names] $this-backward] != -1} {
  408.         $this-backward configure \
  409.             -foreground $itk_option(-buttonforeground)
  410.         }
  411.     } else {
  412.         $itk_component(-backward) configure \
  413.             -foreground $itk_option(-buttonforeground)
  414.     }
  415.     }
  416. }
  417.  
  418. # ------------------------------------------------------------------
  419. # OPTION: -selectcolor
  420. #
  421. # Specifies the color of the ring displayed that distinguishes the 
  422. # currently selected date.  
  423. # ------------------------------------------------------------------
  424. itcl::configbody iwidgets::Calendar::selectcolor {
  425.     if {$_initialized} {
  426.     $itk_component(page) itemconfigure $_selected-sensor \
  427.         -outline $itk_option(-selectcolor) 
  428.     }
  429. }
  430.  
  431. # ------------------------------------------------------------------
  432. # OPTION: -selectthickness
  433. #
  434. # Specifies the thickness of the ring displayed that distinguishes 
  435. # the currently selected date.  
  436. # ------------------------------------------------------------------
  437. itcl::configbody iwidgets::Calendar::selectthickness {
  438.     if {$_initialized} {
  439.     $itk_component(page) itemconfigure $_selected-sensor \
  440.         -width $itk_option(-selectthickness) 
  441.     }
  442. }
  443.  
  444. # ------------------------------------------------------------------
  445. # OPTION: -titlefont
  446. #
  447. # Specifies the font used for the title text that consists of the 
  448. # month and year.
  449. # ------------------------------------------------------------------
  450. itcl::configbody iwidgets::Calendar::titlefont {
  451.     if {$_initialized} {
  452.     $itk_component(page) itemconfigure title \
  453.         -font $itk_option(-titlefont)
  454.     }
  455. }
  456.  
  457. # ------------------------------------------------------------------
  458. # OPTION: -datefont
  459. #
  460. # Specifies the font used for the date text that consists of the 
  461. # day of the month.
  462. # ------------------------------------------------------------------
  463. itcl::configbody iwidgets::Calendar::datefont {
  464.     if {$_initialized} {
  465.     $itk_component(page) itemconfigure date \
  466.         -font $itk_option(-datefont)
  467.     }
  468. }
  469.  
  470. # ------------------------------------------------------------------
  471. # OPTION: -currentdatefont
  472. #
  473. # Specifies the font used for the current date text.
  474. # ------------------------------------------------------------------
  475. itcl::configbody iwidgets::Calendar::currentdatefont {
  476.     if {$_initialized} {
  477.     $itk_component(page) itemconfigure now \
  478.         -font $itk_option(-currentdatefont)
  479.     }
  480. }
  481.  
  482. # ------------------------------------------------------------------
  483. # OPTION: -dayfont
  484. #
  485. # Specifies the font used for the day of the week text.
  486. # ------------------------------------------------------------------
  487. itcl::configbody iwidgets::Calendar::dayfont {
  488.     if {$_initialized} {
  489.     $itk_component(page) itemconfigure days \
  490.         -font $itk_option(-dayfont)
  491.     }
  492. }
  493.  
  494. # ------------------------------------------------------------------
  495. # OPTION: -startday
  496. #
  497. # Specifies the starting day for the week.  The value must be a day of the
  498. # week: sunday, monday, tuesday, wednesday, thursday, friday, or
  499. # saturday.  The default is sunday.
  500. # ------------------------------------------------------------------
  501. itcl::configbody iwidgets::Calendar::startday {
  502.     set day [string tolower $itk_option(-startday)]
  503.  
  504.     switch $day {
  505.     sunday {set _offset 0}
  506.     monday {set _offset 1}
  507.     tuesday {set _offset 2}
  508.     wednesday {set _offset 3}
  509.     thursday {set _offset 4}
  510.     friday {set _offset 5}
  511.     saturday {set _offset 6}
  512.     default {
  513.         error "bad startday option \"$itk_option(-startday)\":\
  514.                    should be sunday, monday, tuesday, wednesday,\
  515.                    thursday, friday, or saturday"
  516.     }
  517.     }
  518.  
  519.     if {$_initialized} {
  520.     $itk_component(page) delete all-page
  521.     _redraw
  522.     }
  523. }
  524.  
  525. # ------------------------------------------------------------------
  526. #                            METHODS
  527. # ------------------------------------------------------------------
  528.  
  529. # ------------------------------------------------------------------
  530. # PUBLIC METHOD: get ?format?
  531. #
  532. # Returns the currently selected date in one of two formats, string 
  533. # or as an integer clock value using the -string and -clicks
  534. # options respectively.  The default is by string.  Reference the 
  535. # clock command for more information on obtaining dates and their 
  536. # formats.
  537. # ------------------------------------------------------------------
  538. itcl::body iwidgets::Calendar::get {{format "-string"}} {
  539.     switch -- $format {
  540.     "-string" {
  541.         return $_selected
  542.     }
  543.     "-clicks" {
  544.         return [clock scan $_selected]
  545.     }
  546.     default {
  547.         error "bad format option \"$format\":\
  548.                    should be -string or -clicks"
  549.     }
  550.     }
  551. }
  552.  
  553. # ------------------------------------------------------------------
  554. # PUBLIC METHOD: select date_
  555. #
  556. # Changes the currently selected date to the value specified.
  557. # ------------------------------------------------------------------
  558. itcl::body iwidgets::Calendar::select {{date_ "now"}} {
  559.     if {$date_ == "now"} {
  560.     set time [clock seconds]
  561.     } else {
  562.     if {[catch {clock format $date_}] == 0} {
  563.         set time $date_
  564.     } elseif {[catch {set time [clock scan $date_]}] != 0} {
  565.         error "bad date: \"$date_\", must be a valid date string, clock clicks value or the keyword now"
  566.     }
  567.     }
  568.     switch $itk_option(-int) {
  569.     yes { set _format "%Y-%m-%d" }
  570.     no { set _format "%m/%d/%Y" }
  571.     }
  572.     _select [clock format $time -format "$_format"]
  573. }
  574.  
  575. # ------------------------------------------------------------------
  576. # PUBLIC METHOD: show date_
  577. #
  578. # Changes the currently display month to be that of the specified 
  579. # date.
  580. # ------------------------------------------------------------------
  581. itcl::body iwidgets::Calendar::show {{date_ "now"}} {
  582.     if {$date_ == "now"} {
  583.     set _time [clock seconds]
  584.     } else {
  585.     if {[catch {clock format $date_}] == 0} {
  586.         set _time $date_
  587.     } elseif {[catch {set _time [clock scan $date_]}] != 0} {
  588.         error "bad date: \"$date_\", must be a valid date string, clock clicks value or the keyword now"
  589.     }
  590.     }
  591.  
  592.     $itk_component(page) delete all-page
  593.     _redraw
  594. }
  595.  
  596. # ------------------------------------------------------------------
  597. # PROTECTED METHOD: _drawtext canvas_ day_ date_ now_
  598. #                             x0_ y0_ x1_ y1_
  599. #
  600. # Draws the text in the date square.  The method is protected such that
  601. # it can be overridden in derived classes that may wish to add their
  602. # own unique text.  The method receives the day to draw along with
  603. # the coordinates of the square.
  604. # ------------------------------------------------------------------
  605. itcl::body iwidgets::Calendar::_drawtext {canvas_ day_ date_ now_ x0_ y0_ x1_ y1_} {
  606.     set item [$canvas_ create text \
  607.           [expr {(($x1_ - $x0_) / 2) + $x0_}] \
  608.           [expr {(($y1_ -$y0_) / 2) + $y0_ + 1}] \
  609.           -anchor center -text "$day_" \
  610.           -fill $itk_option(-foreground)]
  611.  
  612.     if {$date_ == $now_} {
  613.     $canvas_ itemconfigure $item \
  614.         -font $itk_option(-currentdatefont) \
  615.         -tags [list all-page date text now]
  616.     } else {
  617.     $canvas_ itemconfigure $item \
  618.         -font $itk_option(-datefont) \
  619.         -tags [list all-page date text]
  620.     }
  621. }
  622.  
  623. # ------------------------------------------------------------------
  624. # PRIVATE METHOD: _configureHandler
  625. #
  626. # Processes a configure event received on the canvas.  The method
  627. # deletes all the current canvas items and forces a redraw.
  628. # ------------------------------------------------------------------
  629. itcl::body iwidgets::Calendar::_configureHandler {} {
  630.     set _initialized 1
  631.  
  632.     $itk_component(page) delete all
  633.     _redraw
  634. }
  635.  
  636. # ------------------------------------------------------------------
  637. # PRIVATE METHOD: _change delta_
  638. #
  639. # Changes the current month displayed in the calendar, moving
  640. # forward or backward by <delta_> months where <delta_> is +/-
  641. # some number.
  642. # ------------------------------------------------------------------
  643. itcl::body iwidgets::Calendar::_change {delta_} {
  644.     set dir [expr {($delta_ > 0) ? 1 : -1}]
  645.     set month [clock format $_time -format "%m"]
  646.     set month [string trimleft $month 0]
  647.     set year [clock format $_time -format "%Y"]
  648.  
  649.     for {set i 0} {$i < abs($delta_)} {incr i} {
  650.         incr month $dir
  651.         if {$month < 1} {
  652.             set month 12
  653.             incr year -1
  654.         } elseif {$month > 12} {
  655.             set month 1
  656.             incr year 1
  657.         }
  658.     }
  659.     if {[catch {set _time [clock scan "$month/1/$year"]}]} {
  660.     bell
  661.     } else {
  662.     _redraw 
  663.     }
  664. }
  665.  
  666. # ------------------------------------------------------------------
  667. # PRIVATE METHOD: _redraw
  668. #
  669. # Redraws the calendar.  This method is invoked whenever the 
  670. # calendar changes size or we need to effect a change such as draw
  671. # it with a new month.
  672. # ------------------------------------------------------------------
  673. itcl::body iwidgets::Calendar::_redraw {} {
  674.     #
  675.     # Set the format based on the option -int
  676.     #
  677.     switch $itk_option(-int) {
  678.     yes { set _format "%Y-%m-%d" }
  679.     no { set _format "%m/%d/%Y" }
  680.     }
  681.     #
  682.     # Remove all the items that typically change per redraw request
  683.     # such as the title and dates.  Also, get the maximum width and
  684.     # height of the page.
  685.     #
  686.     $itk_component(page) delete all-page
  687.  
  688.     set wmax [winfo width $itk_component(page)]
  689.     set hmax [winfo height $itk_component(page)]
  690.  
  691.     #
  692.     # If we haven't yet created the forward and backwards buttons,
  693.     # then dot it; otherwise, skip it.
  694.     #
  695.     if {[$itk_component(page) find withtag button] == {}} {
  696.     $itk_component(page) create window 3 3 -anchor nw \
  697.         -window $itk_component(backward) -tags button
  698.     $itk_component(page) create window [expr {$wmax-3}] 3 -anchor ne \
  699.         -window $itk_component(forward) -tags button
  700.     }
  701.  
  702.     #
  703.     # Create the title centered between the buttons.
  704.     #
  705.     foreach {x0 y0 x1 y1} [$itk_component(page) bbox button] {
  706.     set x [expr {(($x1-$x0)/2)+$x0}]
  707.     set y [expr {(($y1-$y0)/2)+$y0}]
  708.     }
  709.  
  710.     set title [clock format $_time -format "%B %Y"]
  711.     $itk_component(page) create text $x $y -anchor center \
  712.         -text $title -font $itk_option(-titlefont) \
  713.     -fill $itk_option(-foreground) \
  714.     -tags [list title text all-page]
  715.  
  716.     #
  717.     # Add the days of the week labels if they haven't yet been created.
  718.     #
  719.     if {[$itk_component(page) find withtag days] == {}} {
  720.     _days $wmax
  721.     }
  722.  
  723.     #
  724.     # Add a line between the calendar header and the dates if needed.
  725.     #
  726.     set bottom [expr {[lindex [$itk_component(page) bbox all] 3] + 3}]
  727.  
  728.     if {[$itk_component(page) find withtag line] == {}} {
  729.     $itk_component(page) create line 0 $bottom $wmax $bottom \
  730.         -width 2 -tags line
  731.     }
  732.  
  733.     incr bottom 3
  734.  
  735.     #
  736.     # Get the layout for the time value and create the date squares.
  737.     # This includes the surrounding date rectangle, the date text,
  738.     # and the sensor.  Bind selection to the sensor.
  739.     #
  740.     set current ""
  741.     set now [clock format [clock seconds] -format "$_format"]
  742.  
  743.     set layout [_layout $_time]
  744.     set weeks [expr {[lindex $layout end] + 1}]
  745.  
  746.     foreach {day date kind dcol wrow} $layout {
  747.         set x0 [expr {$dcol*($wmax-7)/7+3}]
  748.         set y0 [expr {$wrow*($hmax-$bottom-4)/$weeks+$bottom}]
  749.         set x1 [expr {($dcol+1)*($wmax-7)/7+3}]
  750.         set y1 [expr {($wrow+1)*($hmax-$bottom-4)/$weeks+$bottom}]
  751.  
  752.         if {$date == $_selected} {
  753.             set current $date
  754.         }
  755.  
  756.     #
  757.     # Create the rectangle that surrounds the date and configure
  758.     # its background based on the wheather it is a weekday or
  759.     # a weekend.
  760.     #
  761.     set item [$itk_component(page) create rectangle $x0 $y0 $x1 $y1 \
  762.         -outline $itk_option(-outline)]
  763.  
  764.     if {$kind == "weekend"} {
  765.         $itk_component(page) itemconfigure $item \
  766.             -fill $itk_option(-weekendbackground) \
  767.             -tags [list all-page square weekend]
  768.     } else {
  769.         $itk_component(page) itemconfigure $item \
  770.             -fill $itk_option(-weekdaybackground) \
  771.             -tags [list all-page square weekday]
  772.     }
  773.  
  774.     #
  775.     # Create the date text and configure its font based on the 
  776.     # wheather or not it is the current date.
  777.     #
  778.     _drawtext $itk_component(page) $day $date $now $x0 $y0 $x1 $y1
  779.  
  780.     #
  781.     # Create a sensor area to detect selections.  Bind the 
  782.     # sensor and pass the date to the bind script.
  783.     #
  784.         $itk_component(page) create rectangle $x0 $y0 $x1 $y1 \
  785.             -outline "" -fill "" \
  786.             -tags [list $date-sensor all-sensor all-page]
  787.  
  788.         $itk_component(page) bind $date-sensor <ButtonPress-1> \
  789.             [itcl::code $this _selectEvent $date]
  790.     }
  791.  
  792.     #
  793.     # Highlight the selected date if it is on this page.
  794.     #
  795.     if {$current != ""} {
  796.         $itk_component(page) itemconfigure $current-sensor \
  797.             -outline $itk_option(-selectcolor) \
  798.         -width $itk_option(-selectthickness)
  799.  
  800.         $itk_component(page) raise $current-sensor
  801.  
  802.     } elseif {$_selected == ""} {
  803.         set date [clock format $_time -format "$_format"]
  804.         _select $date
  805.     }
  806. }
  807.  
  808. # ------------------------------------------------------------------
  809. # PRIVATE METHOD: _days
  810. #
  811. # Used to rewite the days of the week label just below the month 
  812. # title string.  The days are given in the -days option.
  813. # ------------------------------------------------------------------
  814. itcl::body iwidgets::Calendar::_days {{wmax {}}} {
  815.     if {$wmax == {}} {
  816.     set wmax [winfo width $itk_component(page)]
  817.     }
  818.  
  819.     set col 0
  820.     set bottom [expr {[lindex [$itk_component(page) bbox title buttons] 3] + 7}]
  821.  
  822.     foreach dayoweek $itk_option(-days) {
  823.     set x0 [expr {$col*($wmax/7)}]
  824.     set x1 [expr {($col+1)*($wmax/7)}]
  825.  
  826.     $itk_component(page) create text \
  827.         [expr {(($x1 - $x0) / 2) + $x0}] $bottom \
  828.         -anchor n -text "$dayoweek" \
  829.         -fill $itk_option(-foreground) \
  830.         -font $itk_option(-dayfont) \
  831.         -tags [list days text]
  832.  
  833.     incr col
  834.     }
  835. }
  836.  
  837. # ------------------------------------------------------------------
  838. # PRIVATE METHOD: _layout time_
  839. #
  840. # Used whenever the calendar is redrawn.  Finds the month containing
  841. # a <time_> in seconds, and returns a list for all of the days in 
  842. # that month.  The list looks like this:
  843. #
  844. #    {day1 date1 kind1 c1 r1  day2 date2 kind2 c2 r2  ...}
  845. #
  846. # where dayN is a day number like 1,2,3,..., dateN is the date for
  847. # dayN, kindN is the day type of weekday or weekend, and cN,rN 
  848. # are the column/row indices for the square containing that date.
  849. # ------------------------------------------------------------------
  850. itcl::body iwidgets::Calendar::_layout {time_} {
  851.  
  852.     switch $itk_option(-int) {
  853.     yes { set _format "%Y-%m-%d" }
  854.     no { set _format "%m/%d/%Y" }
  855.     }
  856.  
  857.     set month [clock format $time_ -format "%m"]
  858.     set year  [clock format $time_ -format "%Y"]
  859.  
  860.     foreach lastday {31 30 29 28} {
  861.         if {[catch {clock scan "$month/$lastday/$year"}] == 0} {
  862.             break
  863.         }
  864.     }
  865.     set seconds [clock scan "$month/1/$year"]
  866.     set firstday [_adjustday [clock format $seconds -format %w]]
  867.  
  868.     set weeks [expr {ceil(double($lastday+$firstday)/7)}]
  869.  
  870.     set rlist ""
  871.     for {set day 1} {$day <= $lastday} {incr day} {
  872.         set seconds [clock scan "$month/$day/$year"]
  873.         set date [clock format $seconds -format "$_format"]
  874.     set dayoweek [clock format $seconds -format %w]
  875.  
  876.     if {$dayoweek == 0 || $dayoweek == 6} {
  877.         set kind "weekend"
  878.     } else {
  879.         set kind "weekday"
  880.     }
  881.  
  882.         set daycol [_adjustday $dayoweek]
  883.  
  884.         set weekrow [expr {($firstday+$day-1)/7}]
  885.         lappend rlist $day $date $kind $daycol $weekrow 
  886.     }
  887.     return $rlist
  888. }
  889.  
  890. # ------------------------------------------------------------------
  891. # PRIVATE METHOD: _adjustday day_
  892. #
  893. # Modifies the day to be in accordance with the startday option.
  894. # ------------------------------------------------------------------
  895. itcl::body iwidgets::Calendar::_adjustday {day_} {
  896.     set retday [expr {$day_ - $_offset}]
  897.  
  898.     if {$retday < 0} {
  899.     set retday [expr {$retday + 7}]
  900.     }
  901.  
  902.     return $retday
  903. }
  904.  
  905. # ------------------------------------------------------------------
  906. # PRIVATE METHOD: _select date_
  907. #
  908. # Selects the current <date_> on the calendar.  Highlights the date 
  909. # on the calendar, and executes the command associated with the 
  910. # calendar, with the selected date substituted in place of "%d".
  911. # ------------------------------------------------------------------
  912. itcl::body iwidgets::Calendar::_select {date_} {
  913.  
  914.     switch $itk_option(-int) {
  915.     yes { set _format "%Y-%m-%d" }
  916.     no { set _format "%m/%d/%Y" }
  917.     }
  918.  
  919.  
  920.     set time [clock scan $date_]
  921.     set date [clock format $time -format "$_format"]
  922.  
  923.     set _selected $date
  924.     set current [clock format $_time -format "%m %Y"]
  925.     set selected [clock format $time -format "%m %Y"]
  926.  
  927.     if {$current == $selected} {
  928.         $itk_component(page) itemconfigure all-sensor \
  929.             -outline "" -width 1
  930.  
  931.         $itk_component(page) itemconfigure $date-sensor \
  932.             -outline $itk_option(-selectcolor) \
  933.         -width $itk_option(-selectthickness)
  934.         $itk_component(page) raise $date-sensor
  935.     } else {
  936.         set _time $time
  937.         _redraw 
  938.     }
  939. }
  940.  
  941. # ------------------------------------------------------------------
  942. # PRIVATE METHOD: _selectEvent date_
  943. #
  944. # Selects the current <date_> on the calendar.  Highlights the date 
  945. # on the calendar, and executes the command associated with the 
  946. # calendar, with the selected date substituted in place of "%d".
  947. # ------------------------------------------------------------------
  948. itcl::body iwidgets::Calendar::_selectEvent {date_} {
  949.     _select $date_
  950.  
  951.     if {[string trim $itk_option(-command)] != ""} {
  952.         set cmd $itk_option(-command)
  953.         set cmd [_percentSubst %d $cmd [get]]
  954.         uplevel #0 $cmd
  955.     }
  956. }
  957.  
  958. # ------------------------------------------------------------------
  959. # PRIVATE METHOD: _percentSubst pattern_ string_ subst_
  960. #
  961. # This command is a "safe" version of regsub, for substituting
  962. # each occurance of <%pattern_> in <string_> with <subst_>.  The
  963. # usual Tcl "regsub" command does the same thing, but also
  964. # converts characters like "&" and "\0", "\1", etc. that may
  965. # be present in the <subst_> string.
  966. #
  967. # Returns <string_> with <subst_> substituted in place of each
  968. # <%pattern_>.
  969. # ------------------------------------------------------------------
  970. itcl::body iwidgets::Calendar::_percentSubst {pattern_ string_ subst_} {
  971.     if {![string match %* $pattern_]} {
  972.         error "bad pattern \"$pattern_\": should be %something"
  973.     }
  974.  
  975.     set rval ""
  976.     while {[regexp "(.*)${pattern_}(.*)" $string_ all head tail]} {
  977.         set rval "$subst_$tail$rval"
  978.         set string_ $head
  979.     }
  980.     set rval "$string_$rval"
  981. }
  982.