1
2
3
4
5
6
7
8
9
10
11
12 """A calendar control that shows several weeks in one go
13
14 The design is inspired by the Alan Cooper article U{http://www.cooper.com/articles/art_goal_directed_design.htm}
15 about goal directed design. I also have to apologise for it not quite living up to that vision :-)
16
17 It is fairly feature complete and supports all sorts of interaction, scrolling and customization
18 of appearance"""
19
20 import wx
21 import wx.lib.rcsizer
22 import wx.calendar
23 import cStringIO
24 import calendar
25 import time
26 import widgets
27 import tipwin
28
30 """A cache used internally to remember how much to shrink fonts by"""
31
32 - def get(self, y, attr, numentries):
33 return dict.get(self, (y, id(attr), numentries), 1)
34 - def set(self, y, attr, numentries, scale):
35 self[(y, id(attr), numentries)]=scale
37
38 keys=self.keys()
39 l2=[id(x) for x in args]
40 for y, idattr, numentries in keys:
41 if idattr in l2:
42 del self[ (y, idattr, numentries) ]
43
44 thefontscalecache=FontscaleCache()
45
47 """A class represnting appearance attributes for an individual day.
48
49 You should subclass this if you wish to change the appearance from
50 the defaults"""
52
53
54 self.cellbackground=wx.Brush(wx.Colour(197,255,255), wx.SOLID)
55 self.labelfont=wx.Font(14, wx.SWISS, wx.NORMAL, wx.NORMAL )
56 self.labelforeground=wx.NamedColour("CORNFLOWER BLUE")
57 self.labelalign=wx.ALIGN_RIGHT
58 self.timefont=wx.Font(8, wx.SWISS, wx.NORMAL, wx.NORMAL )
59 self.timeforeground=wx.NamedColour("ORCHID")
60 self.entryfont=wx.Font(9, wx.SWISS, wx.NORMAL, wx.NORMAL )
61 self.entryforeground=wx.NamedColour("BLACK")
62 self.miltime=False
63 self.initdone=True
64
69
71 """Is the number representing the day right aligned within the cell?
72
73 @rtype: Bool
74 @return: True is it should be shown right aligned"""
75 return self.labelalign==wx.ALIGN_RIGHT
76
78 """Are times shown in military (aka 24 hour) time?
79
80 @rtype: Bool
81 @return: True is militart/24 hour format should be used"""
82 return self.miltime
83
85 """Set the cell background attributes
86
87 Colour
88 @type dc: wx.DC"""
89 dc.SetBackground(self.cellbackground)
90
92 """Set the attributes for the day label
93
94 Colour, font
95 @type dc: wx.DC
96 @param fontscale: You should multiply the font point size
97 by this number
98 @type fontscale: float
99 """
100
101 dc.SetTextForeground(self.labelforeground)
102 return self.setscaledfont(dc, self.labelfont, fontscale)
103
105 """Set the attributes for the time of an event text
106
107 Colour, font
108 @type dc: wx.DC
109 @param fontscale: You should multiply the font point size
110 by this number
111 @type fontscale: float
112 """
113 dc.SetTextForeground(self.timeforeground)
114 return self.setscaledfont(dc, self.timefont, fontscale)
115
116 - def setforentry(self, dc, fontscale=1):
117 """Set the attributes for the label of an event text
118
119 Colour, font
120 @type dc: wx.DC
121 @param fontscale: You should multiply the font point size
122 by this number
123 @type fontscale: float
124 """
125 dc.SetTextForeground(self.entryforeground)
126 return self.setscaledfont(dc, self.entryfont, fontscale)
127
129 """Changes the in the device context to the supplied font suitably scaled
130
131 @type dc: wx.DC
132 @type font: wx.Font
133 @type fontscale: float
134 @return: Returns True if the scaling succeeded, and False if the font was already
135 too small to scale smaller (the smallest size will still have been
136 selected into the device context)
137 @rtype: Bool"""
138 if fontscale==1:
139 dc.SetFont(font)
140 return True
141 ps=int(font.GetPointSize()*fontscale)
142 if ps<2:
143 ps=2
144 f=wx.TheFontList.FindOrCreateFont(int(ps), font.GetFamily(), font.GetStyle(), font.GetWeight())
145 dc.SetFont(f)
146 if ps==2:
147 return False
148 return True
149
150
151
152
153 DefaultCalendarCellAttributes=None
154
162
164 """A control that is used for each day in the calendar
165
166 As the user scrolls around the calendar, each cell is updated with new dates rather
167 than creating new CalendarCell objects. Internally it uses a backing buffer so
168 that redraws are quick and flicker free."""
169
170 fontscalecache=FontscaleCache()
171
173 wx.PyWindow.__init__(self, parent, id, style=style|wx.WANTS_CHARS|wx.FULL_REPAINT_ON_RESIZE)
174 self.attr=GetCalendarCellAttributes(attr)
175 self.day=33
176 self.year=2033
177 self.month=3
178 self.buffer=None
179 self.needsupdate=True
180 self.entries=()
181 self._tipwindow=None
182
183 wx.EVT_PAINT(self, self.OnPaint)
184 wx.EVT_SIZE(self, self.OnSize)
185 wx.EVT_ENTER_WINDOW(self, self.OnEnterWindow)
186 wx.EVT_LEAVE_WINDOW(self, self.OnLeaveWindow)
187 self._timer=wx.Timer(self)
188 wx.EVT_TIMER(self, self._timer.GetId(), self.OnTimer)
189 self.OnSize(None)
190
193
194 - def setdate(self, year, month, day):
195 """Set the date we are"""
196 self.year=year
197 self.month=month
198 self.day=day
199 self.needsupdate=True
200 self.Refresh(False)
201
203 """Sets what CalendarCellAtrributes we use for appearance
204
205 @type attr: CalendarCellAtrributes"""
206 self.attr=GetCalendarCellAttributes(attr)
207 self.needsupdate=True
208 self.Refresh(False)
209
211 """Sets the entries we will display
212
213 @type entries: list
214 @param entries: A list of entries. Format is ( ( hour, minute, description), (hour, minute, decription) ... ). hour is in 24 hour
215 """
216 self.entries=entries
217 self.needsupdate=True
218 self.Refresh(False)
219
221 """Returns what date we are currently displaying
222
223 @rtype: tuple
224 @return: tuple of (year, month, day)"""
225 return (self.year, self.month, self.day)
226
228 """Callback for when we are resized"""
229 self.width, self.height = self.GetClientSizeTuple()
230 self.needsupdate=True
231 if evt is not None:
232 evt.Skip()
233
234
236 """Causes a forced redraw into our back buffer"""
237 if self.buffer is None or \
238 self.buffer.GetWidth()!=self.width or \
239 self.buffer.GetHeight()!=self.height:
240 if self.buffer is not None:
241 del self.buffer
242 self.buffer=wx.EmptyBitmap(self.width, self.height)
243
244 mdc=wx.MemoryDC()
245 mdc.SelectObject(self.buffer)
246 self.attr.setforcellbackground(mdc)
247 mdc.Clear()
248 self.draw(mdc)
249 mdc.SelectObject(wx.NullBitmap)
250 del mdc
251
253
254 _res=[]
255 lastap=""
256 for h,m,desc in self.entries:
257 if h is None:
258 _res.append('\t'+desc)
259 else:
260 _text, lastap=self._timestr(h, m, lastap)
261 _res.append('%s\t%s'%(_text, desc))
262 return '\n'.join(_res)
263
265 """Callback for when we need to repaint"""
266 if self.needsupdate:
267 self.needsupdate=False
268 self.redraw()
269 dc=wx.PaintDC(self)
270 dc.DrawBitmap(self.buffer, 0, 0, False)
271
273 text=""
274 if self.attr.ismiltime():
275 ap=""
276 else:
277 ap="a"
278 if h>=12: ap="p"
279 h%=12
280 if h==0: h=12
281 if ap==lastap:
282 ap=""
283 else:
284 lastap=ap
285 if h<10: text+=" "
286 return (text+"%d:%02d%s" % (h,m,ap), lastap)
287
288 - def draw(self, dc):
289 """Draw ourselves
290
291 @type dc: wx.DC"""
292
293
294 self.attr.setforlabel(dc)
295 w,h=dc.GetTextExtent(`self.day`)
296 x=1
297 if self.attr.isrightaligned():
298 x=self.width-(w+5)
299 dc.DrawText(`self.day`, x, 0)
300
301 if len(self.entries)==0:
302 return
303
304 entrystart=h
305 dc.DestroyClippingRegion()
306 dc.SetClippingRegion( 0, entrystart, self.width, self.height-entrystart)
307
308 fontscale=thefontscalecache.get(self.height-entrystart, self.attr, len(self.entries))
309 iteration=0
310
311
312
313
314 while 1:
315 y=entrystart
316 iteration+=1
317
318 self.attr.setfortime(dc, fontscale)
319 boundingspace=2
320 space,_=dc.GetTextExtent("i")
321 timespace,timeheight=dc.GetTextExtent("mm:mm")
322 if self.attr.ismiltime():
323 ampm=0
324 else:
325 ampm,_=dc.GetTextExtent("a")
326
327 r=self.attr.setforentry(dc, fontscale)
328 if not r: iteration=-1
329 _,entryheight=dc.GetTextExtent("I")
330 firstrowheight=max(timeheight, entryheight)
331
332
333 lastap=""
334 for h,m,desc in self.entries:
335 x=0
336 if h is not None:
337 self.attr.setfortime(dc, fontscale)
338
339 x+=boundingspace
340 timey=y
341 if timeheight<firstrowheight:
342 timey+=(firstrowheight-timeheight)/2
343 text, lastap=self._timestr(h, m, lastap)
344 dc.DrawText(text, x, timey)
345 x+=timespace
346 if not self.attr.ismiltime: x+=ampm
347
348 self.attr.setforentry(dc, fontscale)
349 ey=y
350 if entryheight<firstrowheight:
351 ey+=(firstrowheight-entryheight)/2
352 dc.DrawText(desc, x, ey)
353
354 y+=firstrowheight
355 if iteration==1 and fontscale!=1:
356
357 break
358 if iteration==-1:
359
360 thefontscalecache.set(self.height-entrystart, self.attr, len(self.entries), fontscale)
361 break
362 if iteration<10 and y>self.height:
363 dc.Clear()
364
365 fontscale=fontscale*float(self.height-entrystart)/(y-entrystart)
366
367 else:
368 thefontscalecache.set(self.height-entrystart, self.attr, len(self.entries), fontscale)
369 break
370
372 if self.entries:
373 self._timer.Start(1000, wx.TIMER_ONE_SHOT)
377 if not self.entries or wx.GetApp().critical.isSet():
378 return
379 _rect=self.GetRect()
380 _x,_y=self.GetParent().ClientToScreen(_rect[:2])
381 _rect.SetX(_x)
382 _rect.SetY(_y)
383 if self._tipwindow:
384 self._tipwindow.Destroy()
385 self._tipwindow=tipwin.TipWindow(self, self._tipstr(), 1024, _rect)
386
388 """The label window on the left of the day cells that shows the month with rotated text
389
390 It uses double buffering etc for a flicker free experience"""
391
392 - def __init__(self, parent, cells, id=-1):
393 wx.PyWindow.__init__(self, parent, id, style=wx.FULL_REPAINT_ON_RESIZE)
394 self.needsupdate=True
395 self.buffer=None
396 self.cells=cells
397 wx.EVT_PAINT(self, self.OnPaint)
398 wx.EVT_SIZE(self, self.OnSize)
399 self.setfont(wx.Font(20, wx.SWISS, wx.NORMAL, wx.BOLD ))
400 self.settextcolour(wx.NamedColour("BLACK"))
401 self.OnSize(None)
402
405
407 self.width, self.height = self.GetClientSizeTuple()
408 self.needsupdate=True
409
411 if self.needsupdate:
412 self.needsupdate=False
413 self.redraw()
414 dc=wx.PaintDC(self)
415 dc.DrawBitmap(self.buffer, 0, 0, False)
416
419
420 - def settextcolour(self, colour):
422
424 self.needsupdate=True
425 self.Refresh()
426
428 if self.buffer is None or \
429 self.buffer.GetWidth()!=self.width or \
430 self.buffer.GetHeight()!=self.height:
431 if self.buffer is not None:
432 del self.buffer
433 self.buffer=wx.EmptyBitmap(self.width, self.height)
434
435 mdc=wx.MemoryDC()
436 mdc.SelectObject(self.buffer)
437 mdc.SetBackground(wx.TheBrushList.FindOrCreateBrush(self.GetBackgroundColour(), wx.SOLID))
438 mdc.Clear()
439 self.draw(mdc)
440 mdc.SelectObject(wx.NullBitmap)
441 del mdc
442
443 - def draw(self, dc):
444
445 row=0
446 while row<len(self.cells):
447 month=self.cells[row].month
448 endrow=row
449 for r2 in range(row+1,len(self.cells)):
450 if month==self.cells[r2].month:
451 endrow=r2
452 else:
453 break
454
455
456
457
458
459 x=0
460 y=self.cells[row].GetPositionTuple()[1]-self.cells[0].GetPositionTuple()[1]
461 w=self.width
462 h=self.cells[endrow].GetPositionTuple()[1]+self.cells[endrow].GetRect().height \
463 -self.cells[row].GetPositionTuple()[1]
464
465
466 dc.DestroyClippingRegion()
467 dc.SetClippingRegion(x,y,w,h)
468 dc.SetPen(wx.ThePenList.FindOrCreatePen("BLACK", 3, wx.SOLID))
469
470 if row!=0:
471 dc.DrawLine(x, y, x+w, y)
472 if endrow!=len(self.cells)-1:
473 dc.DrawLine(x, y+h, x+w, y+h)
474 month=calendar.month_name[month]
475 dc.SetFont(self.font)
476 dc.SetTextForeground(self.colour)
477 tw,th=dc.GetTextExtent(month)
478
479 if tw<h:
480
481 dc.DrawRotatedText(month, w/2-th/2, y + h/2 + tw/2, 90)
482 else:
483
484 if row==0:
485
486 dc.DrawRotatedText(month, w/2-th/2, y + h -5, 90)
487 else:
488
489 dc.DrawRotatedText(month, w/2-th/2, y + 5 + tw, 90)
490
491
492 row=endrow+1
493
494
495 -class Calendar(wx.Panel, widgets.BitPimWidget):
496 """The main calendar control.
497
498 You should subclass this clas and need to
499 implement the following methods:
500
501 L{OnGetEntries}
502 L{OnEdit}
503
504 The following methods you may want to call at some point:
505
506 L{RefreshEntry}
507 L{RefreshAllEntries}
508
509
510 """
511
512
513 ID_UP=wx.NewId()
514 ID_DOWN=wx.NewId()
515 ID_YEARBUTTON=wx.NewId()
516 ID_TODAYBUTTON=wx.NewId()
517
518 attrevenmonth=None
519 attroddmonth=None
520 attrselectedcell=None
521
536
537 - def __init__(self, parent, rows=5, id=-1):
538 self._initvars()
539 wx.Panel.__init__(self, parent, id, style=wx.WANTS_CHARS|wx.FULL_REPAINT_ON_RESIZE)
540 sizer=wx.GridBagSizer()
541 self.upbutt=wx.BitmapButton(self, self.ID_UP, getupbitmapBitmap())
542 sizer.Add(self.upbutt, flag=wx.EXPAND, pos=(0,1), span=(1,7))
543 self.year=wx.Button(self, self.ID_YEARBUTTON, "2003")
544 sizer.Add(self.year, flag=wx.EXPAND, pos=(1,0))
545 sizer.Add(wx.Button(self, self.ID_TODAYBUTTON, "Today"), flag=wx.EXPAND, pos=(0,0))
546 p=1
547 calendar.setfirstweekday(calendar.SUNDAY)
548 for i in ( "Sun", "Mon", "Tue", "Wed" , "Thu", "Fri", "Sat" ):
549 sizer.Add( wx.StaticText( self, -1, i, style=wx.ALIGN_CENTER|wx.ALIGN_CENTER_VERTICAL),
550 flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_CENTER_HORIZONTAL|wx.EXPAND, pos=(1,p))
551 sizer.AddGrowableCol(p)
552 p+=1
553 self.numrows=rows
554 self.showrow=rows/2
555 self.rows=[]
556 for i in range(0, rows):
557 self.rows.append( self.makerow(sizer, i+2) )
558 self.downbutt=wx.BitmapButton(self, self.ID_DOWN, getdownbitmapBitmap())
559 sizer.Add(self.downbutt, flag=wx.EXPAND, pos=(2+rows, 0), span=(1,8))
560 self.label=CalendarLabel(self, map(lambda x: x[0], self.rows))
561 sizer.Add(self.label, flag=wx.EXPAND, pos=(2,0), span=(self.numrows,1))
562 self.sizer=sizer
563
564 self.popupcalendar=PopupCalendar(self, self)
565
566 wx.EVT_BUTTON(self, self.ID_UP, self.OnScrollUp)
567 wx.EVT_BUTTON(self, self.ID_DOWN, self.OnScrollDown)
568 wx.EVT_BUTTON(self, self.ID_YEARBUTTON, self.OnYearButton)
569 wx.EVT_BUTTON(self, self.ID_TODAYBUTTON, self.OnTodayButton)
570
571 map(lambda child: wx.EVT_KEY_DOWN(child, self.OnKeyDown), self.GetChildren())
572
573 map(lambda child: wx.EVT_MOUSEWHEEL(child, self.OnMouseWheel), self.GetChildren())
574
575 for r in self.rows:
576 map(lambda cell: wx.EVT_LEFT_DOWN(cell, self.OnLeftDown), r)
577 map(lambda cell: wx.EVT_LEFT_DCLICK(cell, self.OnLeftDClick), r)
578
579 self.selectedcell=(-1,-1)
580 self.selecteddate=(-1,-1,-1)
581
582 self.showday(*time.localtime()[:3]+(self.showrow,))
583 self.setday(*time.localtime()[:3])
584
585 self.SetSizer(sizer)
586 sizer.Fit(self)
587 self.SetAutoLayout(True)
588
590 key=event.GetKeyCode()
591 if key==wx.WXK_NEXT:
592 self.scrollby( (self.numrows-1)*7)
593 elif key==wx.WXK_PRIOR:
594 self.scrollby( (self.numrows-1)*-7)
595 elif key==wx.WXK_LEFT:
596 self.setday(*normalizedate( self.selecteddate[0], self.selecteddate[1], self.selecteddate[2]-1) )
597 elif key==wx.WXK_RIGHT:
598 self.setday(*normalizedate( self.selecteddate[0], self.selecteddate[1], self.selecteddate[2]+1) )
599 elif key==wx.WXK_UP:
600 self.setday(*normalizedate( self.selecteddate[0], self.selecteddate[1], self.selecteddate[2]-7) )
601 elif key==wx.WXK_DOWN:
602 self.setday(*normalizedate( self.selecteddate[0], self.selecteddate[1], self.selecteddate[2]+7) )
603 elif key==wx.WXK_HOME:
604 self.setday(*normalizedate( self.selecteddate[0], self.selecteddate[1]-1, self.selecteddate[2]) )
605 elif key==wx.WXK_END:
606 self.setday(*normalizedate( self.selecteddate[0], self.selecteddate[1]+1, self.selecteddate[2]) )
607
608 else:
609 event.Skip()
610
612 delta=event.GetWheelDelta()
613 if delta==0:
614 delta=120
615 lines=event.GetWheelRotation()/delta
616 self.scrollby(-7*lines)
617
619 cell=event.GetEventObject()
620 self.setselection(cell.year, cell.month, cell.day)
621
623 cell=event.GetEventObject()
624 self.OnEdit(cell.year, cell.month, cell.day)
625
628
631
633 res=[]
634 sizer.AddGrowableRow(row)
635 for i in range(0,7):
636 res.append( CalendarCell(self, -1) )
637 sizer.Add( res[-1], flag=wx.EXPAND, pos=(row,i+1))
638 return res
639
649
653
657
661
662 - def setday(self, year, month, day):
666
667 - def showday(self, year, month, day, rowtoshow=-1):
668 """Ensures specified date is onscreen
669
670 @param rowtoshow: if is >=0 then it will be forced to appear in that row
671 """
672
673 y,m,d=self.rows[0][0].year, self.rows[0][0].month, self.rows[0][0].day
674 if rowtoshow==-1:
675 if year<y or (year<=y and month<m) or (year<=y and month<=m and day<d):
676 rowtoshow=0
677
678 y,m,d=self.rows[-1][-1].year, self.rows[-1][-1].month, self.rows[-1][-1].day
679 if rowtoshow==-1:
680 if year>y or (year>=y and month>m) or (year>=y and month>=m and day>d):
681 rowtoshow=self.numrows-1
682 if rowtoshow!=-1:
683 d=calendar.weekday(year, month, day)
684 d=(d+1)%7
685
686 d=day-d
687 d-=7*rowtoshow
688 y,m,d=normalizedate(year, month, d)
689 for row in range(0,self.numrows):
690 self.updaterow(row, *normalizedate(y, m, d+7*row))
691 self.label.changenotify()
692 self.ensureallpainted()
693
695 """Tests if the date is visible to the user
696
697 @rtype: Bool
698 """
699 y,m,d=self.rows[0][0].year, self.rows[0][0].month, self.rows[0][0].day
700 if year<y or (year<=y and month<m) or (year<=y and month<=m and day<d):
701 return False
702 y,m,d=self.rows[-1][-1].year, self.rows[-1][-1].month, self.rows[-1][-1].day
703 if year>y or (year>=y and month>m) or (year>=y and month>=m and day>d):
704 return False
705 return True
706
707 - def RefreshEntry(self, year, month, day):
708 """Causes that date's entries to be refreshed.
709
710 Call this if you have changed the data for one day.
711 Note that your OnGetEntries will only be called if the date is
712 currently visible."""
713 if self.isvisible(year,month,day):
714
715 self.RefreshAllEntries()
716
718 """Call this if you have completely changed all your data.
719
720 OnGetEntries will be called for each visible day."""
721
722 for row in self.rows:
723 for cell in row:
724 cell.setentries(self.OnGetEntries(cell.year, cell.month, cell.day))
725
726
728 """Selects the specifed date if it is visible"""
729 self.selecteddate=(year,month,day)
730 d=calendar.weekday(year, month, day)
731 d=(d+1)%7
732 for row in range(0, self.numrows):
733 cell=self.rows[row][d]
734 if cell.year==year and cell.month==month and cell.day==day:
735 self._unselect()
736 self.rows[row][d].setattr(self.attrselectedcell)
737 self.selectedcell=(row,d)
738 self.ensureallpainted()
739 return
740
742 if self.selectedcell!=(-1,-1):
743 self.updatecell(*self.selectedcell)
744 self.selectedcell=(-1,-1)
745
746 - def updatecell(self, row, column, y=-1, m=-1, d=-1):
757
759 daysinmonth=monthrange(y, m)
760 for c in range(0,7):
761 self.updatecell(row, c, y, m, d)
762 if d==daysinmonth:
763 d=1
764 m+=1
765 if m==13:
766 m=1
767 y+=1
768 daysinmonth=monthrange(y, m)
769 else:
770 d+=1
771
772
773
774
776 """Return a list of entries for the specified y,m,d.
777
778 B{You must implement this method in a derived class}
779
780 The format is ( (hour,min,desc), (hour,min,desc)... ) Hour
781 should be in 24 hour format. You should sort the entries.
782
783 Note that Calendar does not cache any results so you will be
784 asked for the same dates as the user scrolls around."""
785
786 return (
787 (1, 88, "Early morning"),
788 (10,15, "Some explanatory text" ),
789 (10,30, "It is %04d-%02d-%02d" % (year,month,day)),
790 (11,11, "Look at me!"),
791 (12,30, "More text here"),
792 (15,30, "A very large amount of text that will never fit"),
793 (20,30, "Evening drinks"),
794 )
795
796 - def OnEdit(self, year, month, day):
797 """The user wishes to edit the entries for the specified date
798
799 B{You should implement this method in a derived class}
800 """
801 print "The user wants to edit %04d-%02d-%02d" % (year,month,day)
802
803
805 """The control that pops up when you click the year button"""
807 wx.Dialog.__init__(self, parent, -1, '', style=wx.STAY_ON_TOP|style)
808 self.calendar=calendar
809 self.control=wx.calendar.CalendarCtrl(self, 1, style=wx.calendar.CAL_SUNDAY_FIRST, pos=(0,0))
810 sz=self.control.GetBestSize()
811 self.SetSize(sz)
812 wx.calendar.EVT_CALENDAR(self, self.control.GetId(), self.OnCalSelected)
813
815 d=wx.DateTimeFromDMY(day, month-1, year)
816 self.control.SetDate(d)
817 btn=event.GetEventObject()
818 pos=btn.ClientToScreen( (0,0) )
819 sz=btn.GetSize()
820 self.Move( (pos[0], pos[1]+sz.height ) )
821 self.ShowModal()
822
824 dt=evt.GetDate()
825 self.calendar.setday(dt.GetYear(), dt.GetMonth()+1, dt.GetDay())
826 self.calendar.ensureallpainted()
827 self.EndModal(1)
828
829
830 _monthranges=[0, 31, -1, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
831
833 """How many days are in the specified month?
834
835 @rtype: int"""
836 if month==2:
837 return calendar.monthrange(year, month)[1]
838 return _monthranges[month]
839
841 """Return a valid date (and an excellent case for metric time)
842
843 And example is the 32nd of January is first of Feb, or Jan -2 is
844 December 29th of previous year. You should call this after doing
845 arithmetic on dates (for example you can just subtract 14 from the
846 current day and then call this to get the correct date for two weeks
847 ago.
848
849 @rtype: tuple
850 @return: (year, month, day)
851 """
852
853 while day<1 or month<1 or month>12 or (day>28 and day>monthrange(year, month)):
854 if day<1:
855 month-=1
856 if month<1:
857 month=12
858 year-=1
859 num=monthrange(year, month)
860 day=num+day
861 continue
862 if day>28 and day>monthrange(year, month):
863 num=calendar.monthrange(year, month)[1]
864 month+=1
865 if month>12:
866 month=1
867 year+=1
868 day=day-num
869 continue
870 if month<1:
871 year-=1
872 month=month+12
873 continue
874 if month>12:
875 year+=1
876 month-=12
877 continue
878 assert False, "can't get here"
879
880 return year, month, day
881
882
883
885 """Returns raw data for the up icon"""
886 return \
887 '\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00 \x00\x00\x00\x10\x08\x06\
888 \x00\x00\x00w\x00}Y\x00\x00\x00\x04sBIT\x08\x08\x08\x08|\x08d\x88\x00\x00\
889 \x00}IDATx\x9c\xbd\xd5K\x0e\xc0 \x08\x04P\xe0\x04\xdc\xff\x94\xdc\xa0\xdd6\
890 \xad\xca\xf0)n\\\xa83/1FVU\xca\x0e3\xbbT\x95\xd3\x01D$\x95\xf2\xe7<\nx\x97V\
891 \x10a\xc0\xae,\x8b\x08\x01\xbc\x92\x0c\x02\x06\xa0\xe1Q\x04\x04\x88\x86F\xf6\
892 \xbb\x80\xec\xdd\xa2\xe7\x8e\x80\xea\x13C\xceo\x01\xd5r4g\t\xe8*G\xf2>\x80\
893 \xeer/W\x90M\x7f"\xe4\xb48\x81\x90\xc9\xf2\x15\x82+\xdfq\xc7\xb8\x01;]o#\xdc\
894 D \x03\x00\x00\x00\x00IEND\xaeB`\x82'
895
897 """Returns a wx.Bitmap of the up icon"""
898 return wx.BitmapFromImage(getupbitmapImage())
899
901 """Returns wx.Image of the up icon"""
902 stream = cStringIO.StringIO(getupbitmapData())
903 return wx.ImageFromStream(stream)
904
906 """Returns raw data for the down icon"""
907 return \
908 '\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00 \x00\x00\x00\x10\x08\x06\
909 \x00\x00\x00w\x00}Y\x00\x00\x00\x04sBIT\x08\x08\x08\x08|\x08d\x88\x00\x00\
910 \x00\x80IDATx\x9c\xc5\xd3\xd1\r\x80 \x0c\x04\xd0B\x1c\xe0\xf6\x9f\xb2\x1b\
911 \xe8\x97\t\x91R\xda\x02\x95/!r\xf7bj\x01@\x7f\xae\xeb}`\xe6;\xbb\x1c@\xa9\
912 \xed&\xbb\x9c\x88\xa8J\x87Y\xe5\x1d \x03\xf1\xcd\xef\x00\'\x11R\xae\x088\x81\
913 \x18\xe5\r\x01;\x11Z\x8e\n\xd8\x81\x98\xdd\x9f\x02V\x10\x96{&@\x04a}\xdf\x0c\
914 \xf0\x84z\xb0.\x80%\xdc\xfb\xa5\xdc\x00\xad$2+!\x80T\x16\x1d\xd40\xa0-]\xf9U\
915 \x1f\xf8\xca\t\xael-\x16\x86\x00\x00\x00\x00IEND\xaeB`\x82'
916
918 """Returns a wx.Bitmap of the down icon"""
919 return wx.BitmapFromImage(getdownbitmapImage())
920
922 """Returns wx.Image of the down icon"""
923 stream = cStringIO.StringIO(getdownbitmapData())
924 return wx.ImageFromStream(stream)
925
926
927
928
929
930
931 if __name__=="__main__":
932 - class MainWindow(wx.Frame):
933 - def __init__(self, parent, id, title):
934 wx.Frame.__init__(self, parent, id, title, size=(640,480),
935 style=wx.DEFAULT_FRAME_STYLE)
936
937
938 self.control=Calendar(self)
939
940
941 self.Show(True)
942
943 app=wx.PySimpleApp()
944 frame=MainWindow(None, -1, "Calendar Test")
945 if False:
946 import profile
947 profile.run("app.MainLoop()", "fooprof")
948 else:
949 app.MainLoop()
950