1 #----------------------------------------------------------------------------
3 # Purpose: Calendar display control
5 # Author: Lorne White (email: lorne.white@telusplanet.net)
10 # Licence: wxWindows license
11 #----------------------------------------------------------------------------
12 # 12/01/2003 - Jeff Grimmett (grimmtooth@softhome.net)
14 # o Updated for wx namespace
15 # o Tested with updated demo
16 # o Added new event type EVT_CALENDAR. The reason for this is that the original
17 # library used a hardcoded ID of 2100 for generating events. This makes it
18 # very difficult to fathom when trying to decode the code since there's no
19 # published API. Creating the new event binder might seem like overkill -
20 # after all, you might ask, why not just use a new event ID and be done with
21 # it? However, a consistent interface is very useful at times; also it makes
22 # it clear that we're not just hunting for mouse clicks -- we're hunting
23 # wabbit^H^H^H^H (sorry bout that) for calender-driven mouse clicks. So
24 # that's my sad story. Shoot me if you must :-)
25 # o There's still one deprecation warning buried in here somewhere, but I
26 # haven't been able to find it yet. It only occurs when displaying a
27 # print preview, and only the first time. It *could* be an error in the
30 # Here's the traceback:
32 # C:\Python\lib\site-packages\wx\core.py:949: DeprecationWarning:
33 # integer argument expected, got float
34 # newobj = _core.new_Rect(*args, **kwargs)
36 # 12/17/2003 - Jeff Grimmett (grimmtooth@softhome.net)
38 # o A few style-guide nips and tucks
39 # o Renamed wxCalendar to Calendar
40 # o Couple of bugfixes
47 CalDays
= [6, 0, 1, 2, 3, 4, 5]
48 AbrWeekday
= {6:"Sun", 0:"Mon", 1:"Tue", 2:"Wed", 3:"Thu", 4:"Fri", 5:"Sat"}
51 BusCalDays
= [0, 1, 2, 3, 4, 5, 6]
53 # Calendar click event - added 12/1/03 by jmg (see above)
54 wxEVT_COMMAND_PYCALENDAR_DAY_CLICKED
= wx
.NewEventType()
55 EVT_CALENDAR
= wx
.PyEventBinder(wxEVT_COMMAND_PYCALENDAR_DAY_CLICKED
, 1)
62 monthlist
.append(name
)
65 # calendar drawing routing
68 def __init__(self
, parent
):
72 self
.scale
= parent
.scale
79 self
.num_auto
= True # auto scale of the cal number day size
80 self
.num_size
= 12 # default size of calendar if no auto size
81 self
.max_num_size
= 12 # maximum size for calendar number
83 self
.num_align_horz
= wx
.ALIGN_CENTRE
# alignment of numbers
84 self
.num_align_vert
= wx
.ALIGN_CENTRE
85 self
.num_indent_horz
= 0 # points indent from position, used to offset if not centered
86 self
.num_indent_vert
= 0
88 self
.week_auto
= True # auto scale of week font text
90 self
.max_week_size
= 12
92 self
.grid_color
= 'BLACK' # grid and selection colors
93 self
.back_color
= 'WHITE'
94 self
.sel_color
= 'RED'
96 self
.high_color
= 'LIGHT BLUE'
97 self
.border_color
= 'BLACK'
98 self
.week_color
= 'LIGHT GREY'
100 self
.week_font_color
= 'BLACK' # font colors
101 self
.day_font_color
= 'BLACK'
104 self
.bold
= wx
.NORMAL
106 self
.hide_title
= False
107 self
.hide_grid
= False
108 self
.outer_border
= True
110 self
.title_offset
= 0
111 self
.cal_week_scale
= 0.7
112 self
.show_weekend
= False
113 self
.cal_type
= "NORMAL"
115 def SetWeekColor(self
, font_color
, week_color
): # set font and background color for week title
116 self
.week_font_color
= font_color
117 self
.week_color
= week_color
119 def SetSize(self
, size
):
120 self
.set_sizew
= size
[0]
121 self
.set_sizeh
= size
[1]
123 def InitValues(self
): # default dimensions of various elements of the calendar
126 self
.set_cy_st
= 0 # start position
129 self
.set_y_mrg
= 15 # start of vertical draw default
133 def SetPos(self
, xpos
, ypos
):
134 self
.set_cx_st
= xpos
135 self
.set_cy_st
= ypos
137 def SetMarg(self
, xmarg
, ymarg
):
138 self
.set_x_st
= xmarg
139 self
.set_y_st
= ymarg
140 self
.set_y_end
= ymarg
142 def InitScale(self
): # scale position values
143 self
.sizew
= int(self
.set_sizew
* self
.pwidth
)
144 self
.sizeh
= int(self
.set_sizeh
* self
.pheight
)
146 self
.cx_st
= int(self
.set_cx_st
* self
.pwidth
) # draw start position
147 self
.cy_st
= int(self
.set_cy_st
* self
.pheight
)
149 self
.x_mrg
= int(self
.set_x_mrg
* self
.pwidth
) # calendar draw margins
150 self
.y_mrg
= int(self
.set_y_mrg
* self
.pheight
)
151 self
.y_end
= int(self
.set_y_end
* self
.pheight
)
153 def DrawCal(self
, DC
, sel_lst
=[]):
159 if self
.hide_title
is False:
167 if self
.show_weekend
is True: # highlight weekend dates
170 self
.AddSelect(sel_lst
) # overrides the weekend highlight
172 self
.DrawSel() # highlighted days
176 def AddSelect(self
, list, cfont
=None, cbackgrd
= None):
178 cfont
= self
.sel_color
# font digit color
181 cbackgrd
= self
.high_color
# select background color
184 self
.cal_sel
[val
] = (cfont
, cbackgrd
)
186 # draw border around the outside of the main display rectangle
187 def DrawBorder(self
):
188 brush
= wx
.Brush(wx
.NamedColour(self
.back_color
), wx
.SOLID
)
189 self
.DC
.SetBrush(brush
)
190 self
.DC
.SetPen(wx
.Pen(wx
.NamedColour(self
.border_color
), 1))
192 if self
.outer_border
is True:
193 # full display window area
194 rect
= wx
.Rect(self
.cx_st
, self
.cy_st
, self
.sizew
, self
.sizeh
)
195 self
.DC
.DrawRectangleRect(rect
)
197 def DrawNumVal(self
):
200 # calculate the calendar days and offset position
202 def SetCal(self
, year
, month
):
203 self
.InitValues() # reset initial values
209 t
= Date(year
, month
, day
)
210 dow
= self
.dow
= t
.day_of_week
# start day in month
211 dim
= self
.dim
= t
.days_in_month
# number of days in month
213 if self
.cal_type
== "NORMAL":
218 self
.st_pos
= start_pos
221 for i
in range(start_pos
):
226 self
.cal
.append(str(i
))
231 def SetWeekEnd(self
, font_color
='BLACK', backgrd
= 'LIGHT GREY'):
232 date
= 6 - int(self
.dow
) # start day of first saturday
234 while date
<= self
.dim
:
235 self
.cal_sel
[date
] = (font_color
, backgrd
) # Saturday
239 self
.cal_sel
[date
] = (font_color
, backgrd
) # Sunday
244 # get the display rectange list of the day grid
247 for y
in self
.gridy
[1:-1]:
248 for x
in self
.gridx
[:-1]:
249 assert type(y
) == int
250 assert type(x
) == int
251 rect
= wx
.Rect(x
, y
, self
.dl_w
, self
.dl_h
) # create rect region
263 # month and year title
265 month
= Month
[self
.month
]
268 if self
.sizeh
< _MIDSIZE
:
271 f
= wx
.Font(sizef
, self
.font
, wx
.NORMAL
, self
.bold
)
274 tw
,th
= self
.DC
.GetTextExtent(month
)
275 adjust
= self
.cx_st
+ (self
.sizew
-tw
)/2
276 self
.DC
.DrawText(month
, (adjust
, self
.cy_st
+ th
))
278 year
= str(self
.year
)
279 tw
,th
= self
.DC
.GetTextExtent(year
)
280 adjust
= self
.sizew
- tw
- self
.x_mrg
282 self
.title_offset
= th
* 2
284 f
= wx
.Font(sizef
, self
.font
, wx
.NORMAL
, self
.bold
)
286 self
.DC
.DrawText(year
, (self
.cx_st
+ adjust
, self
.cy_st
+ th
))
288 def DrawWeek(self
): # draw the week days
289 width
= self
.gridx
[1]-self
.gridx
[0]
290 height
= self
.gridy
[1] - self
.gridy
[0]
291 rect_w
= self
.gridx
[7]-self
.gridx
[0]
293 f
= wx
.Font(10, self
.font
, wx
.NORMAL
, self
.bold
) # initial font setting
295 if self
.week_auto
== True:
296 test_size
= self
.max_week_size
# max size
299 f
.SetPointSize(test_size
)
301 tw
,th
= self
.DC
.GetTextExtent(test_day
)
303 if tw
< width
and th
< height
:
306 test_size
= test_size
- 1
308 f
.SetPointSize(self
.week_size
) # set fixed size
311 self
.DC
.SetTextForeground(wx
.NamedColour(self
.week_font_color
))
316 brush
= wx
.Brush(wx
.NamedColour(self
.week_color
), wx
.SOLID
)
317 self
.DC
.SetBrush(brush
)
318 self
.DC
.DrawRectangle((self
.gridx
[0], self
.gridy
[0]), (rect_w
+1, height
))
320 if self
.cal_type
== "NORMAL":
323 cal_days
= BusCalDays
326 day
= AbrWeekday
[val
]
331 dw
,dh
= self
.DC
.GetTextExtent(day
)
333 diffy
= (height
-dh
)/2
335 x
= self
.gridx
[cnt_x
]
336 y
= self
.gridy
[cnt_y
]
337 self
.DC
.DrawRectangle((self
.gridx
[cnt_x
], self
.gridy
[0]), (width
+1, height
))
338 self
.DC
.DrawText(day
, (x
+diffx
, y
+diffy
))
341 # draw the day numbers
343 f
= wx
.Font(10, self
.font
, wx
.NORMAL
, self
.bold
) # initial font setting
345 if self
.num_auto
== True:
346 test_size
= self
.max_num_size
# max size
350 f
.SetPointSize(test_size
)
352 tw
,th
= self
.DC
.GetTextExtent(test_day
)
354 if tw
< self
.dl_w
and th
< self
.dl_h
:
357 test_size
= test_size
- 1
359 f
.SetPointSize(self
.num_size
) # set fixed size
365 x
= self
.gridx
[cnt_x
]
366 y
= self
.gridy
[cnt_y
]
370 num_color
= self
.cal_sel
[num_val
][0]
372 num_color
= self
.day_font_color
374 self
.DC
.SetTextForeground(wx
.NamedColour(num_color
))
377 tw
,th
= self
.DC
.GetTextExtent(val
)
379 if self
.num_align_horz
== wx
.ALIGN_CENTRE
:
380 adj_h
= (self
.dl_w
- tw
)/2
381 elif self
.num_align_horz
== wx
.ALIGN_RIGHT
:
382 adj_h
= self
.dl_w
- tw
384 adj_h
= 0 # left alignment
386 adj_h
= adj_h
+ self
.num_indent_horz
388 if self
.num_align_vert
== wx
.ALIGN_CENTRE
:
389 adj_v
= (self
.dl_h
- th
)/2
390 elif self
.num_align_horz
== wx
.ALIGN_RIGHT
:
391 adj_v
= self
.dl_h
- th
393 adj_v
= 0 # left alignment
395 adj_v
= adj_v
+ self
.num_indent_vert
397 self
.DC
.DrawText(val
, (x
+adj_h
, y
+adj_v
))
405 # calculate the dimensions in the center of the drawing area
408 bdh
= self
.y_mrg
+ self
.y_end
+ self
.title_offset
410 self
.dl_w
= int((self
.sizew
-bdw
)/7)
411 self
.dl_h
= int((self
.sizeh
-bdh
)/7)
413 # week title adjustment
414 self
.dl_th
= int(self
.dl_h
*self
.cal_week_scale
)
415 self
.cwidth
= self
.dl_w
* 7
416 self
.cheight
= self
.dl_h
* 6 + self
.dl_th
418 # highlighted selected days
420 for key
in self
.cal_sel
.keys():
421 sel_color
= self
.cal_sel
[key
][1]
422 brush
= wx
.Brush(wx
.NamedColour(sel_color
), wx
.SOLID
)
423 self
.DC
.SetBrush(brush
)
425 if self
.hide_grid
is False:
426 self
.DC
.SetPen(wx
.Pen(wx
.NamedColour(self
.grid_color
), 0))
428 self
.DC
.SetPen(wx
.Pen(wx
.NamedColour(self
.back_color
), 0))
430 nkey
= key
+ self
.st_pos
-1
432 self
.DC
.DrawRectangle((rect
.x
, rect
.y
), (rect
.width
+1, rect
.height
+1))
434 # calculate and draw the grid lines
436 self
.DC
.SetPen(wx
.Pen(wx
.NamedColour(self
.grid_color
), 0))
441 self
.x_st
= self
.cx_st
+ self
.x_mrg
442 # start postion of draw
443 self
.y_st
= self
.cy_st
+ self
.y_mrg
+ self
.title_offset
447 y2
= y1
+ self
.cheight
450 if self
.hide_grid
is False:
451 self
.DC
.DrawLine((x1
, y1
), (x1
, y2
))
452 self
.gridx
.append(x1
)
457 x2
= x1
+ self
.cwidth
460 if self
.hide_grid
is False:
461 self
.DC
.DrawLine((x1
, y1
), (x2
, y1
))
463 self
.gridy
.append(y1
)
471 class PrtCalDraw(CalDraw
):
472 def InitValues(self
):
475 # start draw border location
479 # draw offset position
484 # calculate the dimensions in the center of the drawing area
485 def SetPSize(self
, pwidth
, pheight
):
486 self
.pwidth
= int(pwidth
)/self
.scale
487 self
.pheight
= int(pheight
)/self
.scale
489 def SetPreview(self
, preview
):
490 self
.preview
= preview
492 class Calendar(wx
.Window
):
493 def __init__(self
, parent
, id, pos
=wx
.DefaultPosition
, size
=wx
.DefaultSize
):
494 wx
.Window
.__init
__(self
, parent
, id, pos
, size
)
496 # set the calendar control attributes
498 self
.grid_color
= 'BLACK'
499 self
.back_color
= 'WHITE'
500 self
.hide_grid
= False
501 self
.sel_color
= 'RED'
502 self
.hide_title
= False
503 self
.show_weekend
= False
504 self
.cal_type
= "NORMAL"
507 self
.week_color
= 'LIGHT GREY'
508 self
.week_font_color
= 'BLACK'
510 self
.select_list
= []
512 self
.SetBackgroundColour(self
.back_color
)
513 self
.Bind(wx
.EVT_LEFT_DOWN
, self
.OnLeftEvent
)
514 self
.Bind(wx
.EVT_LEFT_DCLICK
, self
.OnLeftDEvent
)
515 self
.Bind(wx
.EVT_RIGHT_DOWN
, self
.OnRightEvent
)
516 self
.Bind(wx
.EVT_RIGHT_DCLICK
, self
.OnRightDEvent
)
518 self
.sel_key
= None # last used by
519 self
.sel_lst
= [] # highlighted selected days
521 # default calendar for current month
527 self
.Bind(wx
.EVT_PAINT
, self
.OnPaint
)
528 self
.Bind(wx
.EVT_SIZE
, self
.OnSize
)
530 # control some of the main calendar attributes
533 self
.hide_title
= True
536 self
.hide_grid
= True
538 # determine the calendar rectangle click area and draw a selection
540 def ProcessClick(self
, event
):
541 self
.x
, self
.y
= event
.GetX(), event
.GetY()
542 key
= self
.GetDayHit(self
.x
, self
.y
)
545 # tab mouse click events and process
547 def OnLeftEvent(self
, event
):
549 self
.shiftkey
= event
.ShiftDown()
550 self
.ctrlkey
= event
.ControlDown()
551 self
.ProcessClick(event
)
553 def OnLeftDEvent(self
, event
):
555 self
.ProcessClick(event
)
557 def OnRightEvent(self
, event
):
559 self
.ProcessClick(event
)
561 def OnRightDEvent(self
, event
):
562 self
.click
= 'DRIGHT'
563 self
.ProcessClick(event
)
565 def SetSize(self
, set_size
):
568 def SetSelDay(self
, sel
):
569 # list of highlighted days
572 # get the current date
575 self
.month
= dt
.month
579 # set the current day
580 def SetCurrentDay(self
):
582 self
.set_day
= self
.day
584 # get the date, day, month, year set in calendar
587 return self
.day
, self
.month
, self
.year
598 # set the day, month, and year
600 def SetDayValue(self
, day
):
603 def SetMonth(self
, month
):
604 if month
>= 1 and month
<= 12:
610 def SetYear(self
, year
):
613 # increment year and month
616 self
.year
= self
.year
+ 1
620 self
.year
= self
.year
- 1
624 self
.month
= self
.month
+ 1
627 self
.year
= self
.year
+ 1
631 self
.month
= self
.month
- 1
634 self
.year
= self
.year
- 1
637 # test to see if the selection has a date and create event
639 def TestDay(self
, key
):
641 self
.day
= int(self
.cal
[key
])
647 # Changed 12/1/03 by jmg (see above) to support 2.5 event binding
648 evt
= wx
.PyCommandEvent(wxEVT_COMMAND_PYCALENDAR_DAY_CLICKED
, self
.GetId())
649 evt
.click
, evt
.day
, evt
.month
, evt
.year
= self
.click
, self
.day
, self
.month
, self
.year
650 evt
.shiftkey
= self
.shiftkey
651 evt
.ctrlkey
= self
.ctrlkey
652 self
.GetEventHandler().ProcessEvent(evt
)
654 self
.set_day
= self
.day
657 # find the clicked area rectangle
659 def GetDayHit(self
, mx
, my
):
660 for key
in self
.rg
.keys():
662 ms_rect
= wx
.Rect(mx
, my
, 1, 1)
663 if wx
.IntersectRect(ms_rect
, val
) is not None:
664 result
= self
.TestDay(key
)
671 def SetWeekColor(self
, font_color
, week_color
):
672 # set font and background color for week title
673 self
.week_font_color
= font_color
674 self
.week_color
= week_color
676 def AddSelect(self
, list, font_color
, back_color
):
677 list_val
= [list, font_color
, back_color
]
678 self
.select_list
.append(list_val
)
680 def ShowWeekEnd(self
):
682 self
.show_weekend
= True
684 def SetBusType(self
):
685 self
.cal_type
= "BUS"
687 def OnSize(self
, evt
):
691 def OnPaint(self
, event
):
692 DC
= wx
.PaintDC(self
)
695 def DoDrawing(self
, DC
):
696 DC
= wx
.PaintDC(self
)
699 self
.cal
= cal
= CalDraw(self
)
701 cal
.grid_color
= self
.grid_color
702 cal
.back_color
= self
.back_color
703 cal
.hide_grid
= self
.hide_grid
704 cal
.grid_color
= self
.grid_color
705 cal
.hide_title
= self
.hide_title
706 cal
.show_weekend
= self
.show_weekend
707 cal
.cal_type
= self
.cal_type
709 if self
.size
is None:
710 size
= self
.GetClientSize()
716 cal
.week_font_color
= self
.week_font_color
717 cal
.week_color
= self
.week_color
720 cal
.SetCal(self
.year
, self
.month
)
722 for val
in self
.select_list
:
723 cal
.AddSelect(val
[0], val
[1], val
[2])
725 cal
.DrawCal(DC
, self
.sel_lst
)
727 self
.rg
= cal
.GetRect()
728 self
.cal
= cal
.GetCal()
729 self
.st_pos
= cal
.GetOffset()
730 self
.ymax
= DC
.MaxY()
732 if self
.set_day
!= None:
733 self
.SetDay(self
.set_day
)
737 # draw the selection rectangle
738 def DrawRect(self
, key
, color
= 'BLACK', width
= 0):
742 DC
= wx
.ClientDC(self
)
745 brush
= wx
.Brush(wx
.Colour(0, 0xFF, 0x80), wx
.TRANSPARENT
)
747 DC
.SetPen(wx
.Pen(wx
.NamedColour(color
), width
))
750 DC
.DrawRectangle((rect
.x
, rect
.y
), (rect
.width
+1, rect
.height
+1))
754 # set the day selection
756 def SetDay(self
, day
):
757 day
= day
+ self
.st_pos
- 1
760 def SelectDay(self
, key
):
762 # clear large selection
763 self
.DrawRect(self
.sel_key
, self
.back_color
, sel_size
)
765 if self
.hide_grid
is False:
766 self
.DrawRect(self
.sel_key
, self
.grid_color
)
768 self
.DrawRect(key
, self
.sel_color
, sel_size
)
771 self
.select_day
= None
776 class CalenDlg(wx
.Dialog
):
777 def __init__(self
, parent
, month
=None, day
= None, year
=None):
778 wx
.Dialog
.__init
__(self
, parent
, -1, "Event Calendar", wx
.DefaultPosition
, (280, 360))
780 # set the calendar and attributes
781 self
.calend
= Calendar(self
, -1, (20, 60), (240, 200))
784 self
.calend
.SetCurrentDay()
785 start_month
= self
.calend
.GetMonth()
786 start_year
= self
.calend
.GetYear()
788 self
.calend
.month
= start_month
= month
789 self
.calend
.year
= start_year
= year
790 self
.calend
.SetDayValue(day
)
792 self
.calend
.HideTitle()
795 # get month list from DateTime
796 monthlist
= GetMonthList()
799 self
.date
= wx
.ComboBox(self
, -1, Month
[start_month
], (20, 20), (90, -1),
800 monthlist
, wx
.CB_DROPDOWN
)
801 self
.Bind(wx
.EVT_COMBOBOX
, self
.EvtComboBox
, self
.date
)
803 # alternate spin button to control the month
804 h
= self
.date
.GetSize().height
805 self
.m_spin
= wx
.SpinButton(self
, -1, (130, 20), (h
*2, h
), wx
.SP_VERTICAL
)
806 self
.m_spin
.SetRange(1, 12)
807 self
.m_spin
.SetValue(start_month
)
808 self
.Bind(wx
.EVT_SPIN
, self
.OnMonthSpin
, self
.m_spin
)
810 # spin button to control the year
811 self
.dtext
= wx
.TextCtrl(self
, -1, str(start_year
), (160, 20), (60, -1))
812 h
= self
.dtext
.GetSize().height
814 self
.y_spin
= wx
.SpinButton(self
, -1, (220, 20), (h
*2, h
), wx
.SP_VERTICAL
)
815 self
.y_spin
.SetRange(1980, 2010)
816 self
.y_spin
.SetValue(start_year
)
818 self
.Bind(wx
.EVT_SPIN
, self
.OnYrSpin
, self
.y_spin
)
819 self
.Bind(EVT_CALENDAR
, self
.MouseClick
, self
.calend
)
825 btn
= wx
.Button(self
, -1, ' Ok ', (x_pos
, y_pos
), but_size
)
826 self
.Bind(wx
.EVT_BUTTON
, self
.OnOk
, btn
)
828 btn
= wx
.Button(self
, -1, ' Close ', (x_pos
+ 120, y_pos
), but_size
)
829 self
.Bind(wx
.EVT_BUTTON
, self
.OnCancel
, btn
)
831 def OnOk(self
, event
):
832 self
.EndModal(wx
.ID_OK
)
834 def OnCancel(self
, event
):
835 self
.EndModal(wx
.ID_CANCEL
)
837 # log the mouse clicks
838 def MouseClick(self
, evt
):
839 self
.month
= evt
.month
840 # result click type and date
841 self
.result
= [evt
.click
, str(evt
.day
), Month
[evt
.month
], str(evt
.year
)]
843 if evt
.click
== 'DLEFT':
844 self
.EndModal(wx
.ID_OK
)
846 # month and year spin selection routines
847 def OnMonthSpin(self
, event
):
848 month
= event
.GetPosition()
849 self
.date
.SetValue(Month
[month
])
850 self
.calend
.SetMonth(month
)
851 self
.calend
.Refresh()
853 def OnYrSpin(self
, event
):
854 year
= event
.GetPosition()
855 self
.dtext
.SetValue(str(year
))
856 self
.calend
.SetYear(year
)
857 self
.calend
.Refresh()
859 def EvtComboBox(self
, event
):
860 name
= event
.GetString()
861 monthval
= self
.date
.FindString(name
)
862 self
.m_spin
.SetValue(monthval
+1)
864 self
.calend
.SetMonth(monthval
+1)
867 # set the calendar for highlighted days
869 def ResetDisplay(self
):
870 month
= self
.calend
.GetMonth()
871 self
.calend
.Refresh()