importcalendarimportsystry:importTkinterimporttkFontexceptImportError:# py3kimporttkinterasTkinterimporttkinter.fontastkFontimportttkdefget_calendar(locale,fwday):# instantiate proper calendar classiflocaleisNone:returncalendar.TextCalendar(fwday)else:returncalendar.LocaleTextCalendar(fwday,locale)classCalendar(ttk.Frame):# XXX ToDo: cget and configuredatetime=calendar.datetime.datetime
timedelta=calendar.datetime.timedeltadef__init__(self,master=None,**kw):"""
WIDGET-SPECIFIC OPTIONS
locale, firstweekday, year, month, selectbackground,
selectforeground
"""# remove custom options from kw before initializating ttk.Framefwday=kw.pop('firstweekday',calendar.MONDAY)year=kw.pop('year',self.datetime.now().year)month=kw.pop('month',self.datetime.now().month)locale=kw.pop('locale',None)sel_bg=kw.pop('selectbackground','#ecffc4')sel_fg=kw.pop('selectforeground','#05640e')self._date=self.datetime(year,month,1)self._selection=None# no date selectedttk.Frame.__init__(self,master,**kw)self._cal=get_calendar(locale,fwday)self.__setup_styles()# creates custom stylesself.__place_widgets()# pack/grid used widgetsself.__config_calendar()# adjust calendar columns and setup tags# configure a canvas, and proper bindings, for selecting datesself.__setup_selection(sel_bg,sel_fg)# store items ids, used for insertion laterself._items=[self._calendar.insert('','end',values='')for_inrange(6)]# insert dates in the currently empty calendarself._build_calendar()# set the minimal size for the widgetself._calendar.bind('',self.__minsize)def__setitem__(self,item,value):ifitemin('year','month'):raiseAttributeError("attribute '%s' is not writeable"%item)elifitem=='selectbackground':self._canvas['background']=valueelifitem=='selectforeground':self._canvas.itemconfigure(self._canvas.text,item=value)else:ttk.Frame.__setitem__(self,item,value)def__getitem__(self,item):ifitemin('year','month'):returngetattr(self._date,item)elifitem=='selectbackground':returnself._canvas['background']elifitem=='selectforeground':returnself._canvas.itemcget(self._canvas.text,'fill')else:r=ttk.tclobjs_to_py({item:ttk.Frame.__getitem__(self,item)})returnr[item]def__setup_styles(self):# custom ttk stylesstyle=ttk.Style(self.master)arrow_layout=lambdadir:([('Button.focus',{'children':[('Button.%sarrow'%dir,None)]})])style.layout('L.TButton',arrow_layout('left'))style.layout('R.TButton',arrow_layout('right'))def__place_widgets(self):# header frame and its widgetshframe=ttk.Frame(self)lbtn=ttk.Button(hframe,style='L.TButton',command=self._prev_month)rbtn=ttk.Button(hframe,style='R.TButton',command=self._next_month)self._header=ttk.Label(hframe,width=15,anchor='center')# the calendarself._calendar=ttk.Treeview(show='',selectmode='none',height=7)# pack the widgetshframe.pack(in_=self,side='top',pady=4,anchor='center')lbtn.grid(in_=hframe)self._header.grid(in_=hframe,column=1,row=0,padx=12)rbtn.grid(in_=hframe,column=2,row=0)self._calendar.pack(in_=self,expand=1,fill='both',side='bottom')def__config_calendar(self):cols=self._cal.formatweekheader(3).split()self._calendar['columns']=cols
self._calendar.tag_configure('header',background='grey90')self._calendar.insert('','end',values=cols,tag='header')# adjust its columns widthfont=tkFont.Font()maxwidth=max(font.measure(col)forcolincols)forcolincols:self._calendar.column(col,width=maxwidth,minwidth=maxwidth,anchor='e')def__setup_selection(self,sel_bg,sel_fg):self._font=tkFont.Font()self._canvas=canvas=Tkinter.Canvas(self._calendar,background=sel_bg,borderwidth=0,highlightthickness=0)canvas.text=canvas.create_text(0,0,fill=sel_fg,anchor='w')canvas.bind('',lambdaevt:canvas.place_forget())self._calendar.bind('',lambdaevt:canvas.place_forget())self._calendar.bind('',self._pressed)def__minsize(self,evt):width,height=self._calendar.master.geometry().split('x')height=height[:height.index('+')]self._calendar.master.minsize(width,height)def_build_calendar(self):year,month=self._date.year,self._date.month# update header text (Month, YEAR)header=self._cal.formatmonthname(year,month,0)self._header['text']=header.title()# update calendar shown datescal=self._cal.monthdayscalendar(year,month)forindx,iteminenumerate(self._items):week=cal[indx]ifindx
textw=self._font.measure(text)canvas=self._canvas
canvas.configure(width=width,height=height)canvas.coords(canvas.text,width-textw,height/2-1)canvas.itemconfigure(canvas.text,text=text)canvas.place(in_=self._calendar,x=x,y=y)# Callbacksdef_pressed(self,evt):"""Clicked somewhere in the calendar."""x,y,widget=evt.x,evt.y,evt.widget
item=widget.identify_row(y)column=widget.identify_column(x)ifnotcolumnornotiteminself._items:# clicked in the weekdays row or just outside the columnsreturnitem_values=widget.item(item)['values']ifnotlen(item_values):# row is empty for this monthreturntext=item_values[int(column[1])-1]ifnottext:# date is emptyreturnbbox=widget.bbox(item,column)ifnotbbox:# calendar not visible yetreturn# update and then show selectiontext='%02d'%text
self._selection=(text,item,column)self._show_selection(text,bbox)def_prev_month(self):"""Updated calendar to show the previous month."""self._canvas.place_forget()self._date=self._date-self.timedelta(days=1)self._date=self.datetime(self._date.year,self._date.month,1)self._build_calendar()# reconstuct calendardef_next_month(self):"""Update calendar to show the next month."""self._canvas.place_forget()year,month=self._date.year,self._date.month
self._date=self._date+self.timedelta(days=calendar.monthrange(year,month)[1]+1)self._date=self.datetime(self._date.year,self._date.month,1)self._build_calendar()# reconstruct calendar# Properties@propertydefselection(self):"""Return a datetime representing the current selected date."""ifnotself._selection:returnNoneyear,month=self._date.year,self._date.monthreturnself.datetime(year,month,int(self._selection[0]))defmyfunction():root2=Tkinter.Toplevel(root)ttkcal=Calendar(root2,firstweekday=calendar.SUNDAY)ttkcal.pack(expand=1,fill='both')root=Tkinter.Tk()frame=Tkinter.Frame(root)frame.pack(side="left")button=Tkinter.Button(root,text="Top level",command=myfunction)button.pack(side="right")ttkcal=Calendar(frame,firstweekday=calendar.SUNDAY)ttkcal.pack(expand=1,fill='both')root.mainloop()