"""
Calendar Widget (Month View)
"""
#  Copyright (C) 2004  Henning Jacobs <henning@srcco.de>
#
#  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, '<Button-1>',
                    lambda event, self=self, x=x, y=y: self._cellClick(x, y, event))    
                self.canvas.tag_bind(text_id, '<Button-1>',
                    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()
