""" Calendar Widget (Month View) """ # Copyright (C) 2004 Henning Jacobs # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # $Id: CalendarWidget.py 82 2004-07-11 13:01:44Z henning $ from Tkinter import * import Pmw import time import calendar from InputWidgets import ArrowButton class CalendarWidget(Frame): cellwidth = 28 cellheight = 22 CURRENTMONTHBG = '#ffffff' CURRENTMONTHFG = '#000000' OTHERMONTHBG = '#ffffff' OTHERMONTHFG = '#bbbbbb' MARKBG = '#eeeeff' MARKFONT = ('Helvetica', -15, 'bold') SELECTBG = '#7777ee' SELECTFG = '#ffffff' CURRENTSUNDAYFG = '#ee0000' OTHERSUNDAYFG = '#eebbbb' DATEFONT = ('Helvetica', -15) WEEKLABELFONT = ('Helvetica', -12) def __init__(self, master, selectcommand=None, dblclickcommand=None): Frame.__init__(self, master, borderwidth=1, relief=RAISED) self.selectcommand = selectcommand self.dblclickcommand = dblclickcommand self.columnconfigure(2, weight=1) self.lblHeader = Label(self, font=('Helvetica', -14, 'bold')) self.lblHeader.grid(column=2, sticky=W+E) self.btnPrevYear = ArrowButton(self, direction='left', width=20, command=self.prevYear) self.btnPrevYear.grid(row=0, column=0, sticky=W) self.btnPrevMonth = ArrowButton(self, direction='left', command=self.prevMonth) self.btnPrevMonth.grid(row=0, column=1, sticky=W) self.btnNextMonth = ArrowButton(self, direction='right', command=self.nextMonth) self.btnNextMonth.grid(row=0, column=3, sticky=E) self.btnNextYear = ArrowButton(self, direction='right', width=20, command=self.nextYear) self.btnNextYear.grid(row=0, column=4, sticky=E) # Thin Separator (horiz. Line): sep = Frame(self, relief=SUNKEN, borderwidth=1, width=self.cellwidth*7, height=2) sep.grid(row=1, column=0, columnspan=5, sticky=W+E) self.canvas = Canvas(self, width=self.cellwidth*8, height=self.cellheight*7, highlightthickness=0, borderwidth=0) self.canvas.grid(row=2, column=0, columnspan=5, sticky=W+E+S+N) self.cells = [] today = time.gmtime() self.year = today.tm_year self.month = today.tm_mon self.day = today.tm_mday self.marks = {} # Create our 7x7 cell matrix: for y in range(7): row = [] for x in range(7): rect_id = self.canvas.create_rectangle((x+1)*self.cellwidth, y*self.cellheight, (x+2)*self.cellwidth, (y+1)*self.cellheight, width=0) text_id = self.canvas.create_text((x+1)*self.cellwidth + self.cellwidth/2, y*self.cellheight + self.cellheight/2) self.canvas.tag_bind(rect_id, '', lambda event, self=self, x=x, y=y: self._cellClick(x, y, event)) self.canvas.tag_bind(text_id, '', lambda event, self=self, x=x, y=y: self._cellClick(x, y, event)) row.append((rect_id, text_id)) self.cells.append(row) # Create 'Week Of Year' Labels: self.weeklabels = [] for y in range(1,7): text_id = self.canvas.create_text(self.cellwidth/2, y*self.cellheight + self.cellheight/2, font=self.WEEKLABELFONT) self.weeklabels.append(text_id) weekdays = ['Mo','Tu','We','Th','Fr','Sa','Su'] self.months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'] # fill the first row in our 7x7 cell matrix with weekdays: for day_no in range(7): rect_id, text_id = self.cells[0][day_no] textcolor = '#000000' self.canvas.itemconfigure(text_id, text=weekdays[day_no], fill=textcolor, font=('Helvetica', -15)) self.update() def prevYear(self): self.setmonth(self.year -1, self.month) def nextYear(self): self.setmonth(self.year +1, self.month) def prevMonth(self): self.setmonth(self.year, self.month -1) def nextMonth(self): self.setmonth(self.year, self.month +1) _lastmouseclicktime = 0 _lastmouseclickday = None def _dayClick(self, day, event): "called by _cellClick" self.setday(day) if self.dblclickcommand\ and event.time - self._lastmouseclicktime <= 400\ and self._lastmouseclickday == day: self.dblclickcommand('%04d-%02d-%02d' % (self.year, self.month, self.day)) elif self.selectcommand: self.selectcommand('%04d-%02d-%02d' % (self.year, self.month, self.day)) self._lastmouseclicktime = event.time self._lastmouseclickday = day def _cellClick(self, cellx, celly, event): day = self.weeks[celly-1][cellx] if day: # this month's date: self._dayClick(day, event) elif celly < 3: # Clicked on a date laying in prev. month self.setmonth(self.year, self.month -1, update=False) self._dayClick(self.otherweeks[(celly-1,cellx)], event) elif celly >= 3: # Clicked on a date laying in next. month self.setmonth(self.year, self.month +1, update=False) self._dayClick(self.otherweeks[(celly-1,cellx)], event) def _getWeekNoOfMonthStart(self): # Returns 1 for January for example dayofyear = 0 wkdayyearstart = 0 for m in range(1, self.month): wkday, numdays = calendar.monthrange(self.year, m) if m == 1: wkdayyearstart = wkday dayofyear += numdays return (dayofyear + wkdayyearstart) / 7 + 1 def _renderCurrentMonthDate(self, rect_id, text_id, day, weekday, today): if (self.year, self.month, day) == (today.tm_year, today.tm_mon, today.tm_mday): borderwidth = 2 else: borderwidth = 0 if day == self.day: # Day is selected: fill = self.SELECTBG textcolor = self.SELECTFG elif weekday == 6: fill = self.CURRENTMONTHBG # Sunday in red: textcolor = self.CURRENTSUNDAYFG else: fill = self.CURRENTMONTHBG textcolor = self.CURRENTMONTHFG if self.marks.has_key('%04d-%02d-%02d' % (self.year, self.month, day)): # Mark has been set for this day: textfont = self.MARKFONT if fill == self.CURRENTMONTHBG: fill = self.MARKBG else: textfont = self.DATEFONT self.canvas.itemconfigure(rect_id, fill=fill, width=borderwidth) if borderwidth: # Raise our box to make border completely visible: self.canvas.tag_raise(rect_id, 'all') self.canvas.itemconfigure(text_id, text=str(day), fill=textcolor, font=textfont) # we must raise our text above the box otherwise we won't see it: self.canvas.tag_raise(text_id, rect_id) def _renderOtherMonthDate(self, rect_id, text_id, day, weekday): "Draw Month Date Cell for dates not in our current month" self.canvas.itemconfigure(rect_id, width=0, fill=self.OTHERMONTHBG) if weekday == 6: textcolor = self.OTHERSUNDAYFG else: textcolor = self.OTHERMONTHFG self.canvas.itemconfigure(text_id, fill=textcolor, text=str(day), font=self.DATEFONT) def update(self): "Update our Calendar Canvas" today = time.gmtime() self.lblHeader.configure( text="%s %d" % (self.months[self.month-1], self.year)) # fill 6x7 matrix with zeros: self.weeks = [[0]*7]*6 # otherweeks holds the day of month for prev. and next months, # otherweeks[(i,j)] will be set where weeks[i][j] is zero: self.otherweeks = {} calweeks = calendar.monthcalendar(self.year, self.month) self.weeks[:len(calweeks)] = calweeks wkmonstart = self._getWeekNoOfMonthStart() lastday = 0 for week, week_no in zip(self.weeks, range(len(self.weeks))): # Set 'Week Number Of Year' Label: self.canvas.itemconfigure(self.weeklabels[week_no], text=str(wkmonstart+week_no)) for day, day_no in zip(week, range(len(week))): rect_id, text_id = self.cells[week_no+1][day_no] if day: self._renderCurrentMonthDate(rect_id, text_id, day, day_no, today) lastday = day elif lastday == 0: # Our Month has not yet started: year = self.year month = self.month - 1 if month < 1: year += -1 month = 12 prevmon_firstweekday, prevmon_numberdays = calendar.monthrange(year, month) dist = 0 for w in range(week_no, len(self.weeks)): for d in range(day_no, len(week)): if self.weeks[w][d]: break dist += 1 if self.weeks[w][d]: break dayofmon = prevmon_numberdays + 1 - dist self.otherweeks[(week_no,day_no)] = dayofmon self._renderOtherMonthDate(rect_id, text_id, dayofmon, day_no) elif lastday > 0: # Next Month: if lastday > 27: lastday = 1 else: lastday += 1 self.otherweeks[(week_no,day_no)] = lastday self._renderOtherMonthDate(rect_id, text_id, lastday, day_no) def setmonth(self, year, month, update=True): "Show this month" self.year = year self.month = month if self.month < 1: self.year += -1 self.month = 12 elif self.month > 12: self.year += 1 self.month = 1 self.setday(self.day, update) def setday(self, day, update=True): "Select this day" self.day = day # Check whether our month includes this day: wkday, numdays = calendar.monthrange(self.year, self.month) if self.day > numdays: self.day = numdays if update: self.update() def setmarks(self, marks): """marks is a dictionary with 'yyyy-mm-dd' strings as keys marked days will be displayed in bold font""" self.marks = marks self.update() def getmarks(self): "Return marks dictionary (can be changed)" return self.marks if __name__ == "__main__": tk = Tk() cal = CalendarWidget(tk) cal.pack() #cal.setmonth(2001,9) #cal.setmarks({(2001,9,11):None}) tk.mainloop()