A simple calendar using tktable.
1 import platform
2 import calendar
3 from datetime import datetime
4
5 import ttk
6 import tktable
7
8 LINUX = platform.system() == 'Linux'
9
10 def get_calendar(locale, fwday):
11 if locale is None:
12 return calendar.TextCalendar(fwday)
13 else:
14 return calendar.LocaleTextCalendar(fwday, locale)
15
16
17 class ArrowButton(ttk.Button):
18 arrow_layout = lambda self, direc: (
19 [('Button.focus', {'children': [('Button.%sarrow' % direc, None)]})]
20 )
21
22 def __init__(self, master, **kw):
23 direction = kw.pop('direction', 'left')
24 style = ttk.Style(master)
25
26 # XXX urgh
27 if LINUX:
28 style.layout('L.TButton', self.arrow_layout('left'))
29 style.layout('R.TButton', self.arrow_layout('right'))
30 kw['style'] = 'L.TButton' if direction == 'left' else 'R.TButton'
31 else:
32 kw['text'] = u'\u25C0' if direction == 'left' else u'\u25B6'
33 kw['style'] = 'Arrow.TButton'
34 style.configure(kw['style'], width=2, padding=0)
35 # urgh end
36
37 ttk.Button.__init__(self, master, **kw)
38
39
40 class Calendar(ttk.Frame, object):
41 def __init__(self, master=None, **kw):
42 ttk.Frame.__init__(self, master)
43
44 params = {'locale': None, 'titlebg': 'blue', 'titlefg': 'white',
45 'calendarbg': 'white'}
46 params.update(kw)
47 for arg, val in params.iteritems():
48 setattr(self, "_%s" % arg, val)
49
50 date = datetime.now()
51 self._year, self._month = date.year, date.month
52
53 self._setup_style()
54 self._build_topbar()
55
56 # calendar
57 self._cal = get_calendar(self._locale, calendar.SUNDAY)
58 self._tclarray = tktable.ArrayVar(self)
59 cols = self._cal.formatweekheader(3).split()
60 self.table = tktable.Table(self, variable=self._tclarray,
61 highlightthickness=4, highlightcolor=self._calendarbg,
62 highlightbackground=self._calendarbg,
63 cols=len(cols) + 1, rows=7, background=self._calendarbg,
64 titlerows=1, titlecols=1, roworigin=-1, colorigin=-1,
65 bd=0, cursor='arrow', resizeborders='none', colwidth=5,
66 state='disabled', browsecommand=self._set_selection)
67 self.table.pack(side='bottom')
68 self.table.bind('<Map>', self._set_minsize)
69
70 self._setup_table(cols)
71 # update calendar
72 self._yeardates = self._year
73
74
75 def next_month(self):
76 if self._month == 12:
77 self._month = 1
78 self._year += 1
79 self._yeardates = self._year
80 else:
81 self._month += 1
82 self._adjust_calendar(self._month)
83
84
85 def prev_month(self):
86 if self._month == 1:
87 self._month = 12
88 self._year -= 1
89 self._yeardates = self._year
90 else:
91 self._month -= 1
92 self._adjust_calendar(self._month)
93
94
95 def next_year(self):
96 self._year += 1
97 self._yeardates = self._year
98
99
100 def prev_year(self):
101 self._year -= 1
102 self._yeardates = self._year
103
104
105
106 def _setup_style(self):
107 style = ttk.Style(self)
108 if LINUX:
109 style.theme_use('clam')
110
111 def _build_topbar(self):
112 bar = ttk.Frame(self, relief='raised', padding=4)
113 bar.pack(side='top', fill='x')
114 lbtn = ArrowButton(bar, direction='left', command=self.prev_month)
115 rbtn = ArrowButton(bar, direction='right', command=self.next_month)
116 self._monthlbl = ttk.Label(bar, text=calendar.month_name[self._month],
117 width=len(max(calendar.month_name)), anchor='center')
118 lbtn.grid(row=0, column=0, sticky='w')
119 self._monthlbl.grid(row=0, column=1, padx=6)
120 rbtn.grid(row=0, column=2, sticky='w')
121
122 spacer = ttk.Label(bar, text='')
123 spacer.grid(row=0, column=3, sticky='ew')
124
125 lbtn2 = ArrowButton(bar, direction='left', command=self.prev_year)
126 rbtn2 = ArrowButton(bar, direction='right', command=self.next_year)
127 self._yearlbl = ttk.Label(bar, text=self._year)
128 lbtn2.grid(row=0, column=4, sticky='e')
129 self._yearlbl.grid(row=0, column=5, padx=6, sticky='e')
130 rbtn2.grid(row=0, column=6, sticky='e')
131
132 bar.grid_columnconfigure(3, weight=1)
133
134 def _setup_table(self, cols):
135 table = self.table
136 table.tag_configure('title', bg=self._titlebg, fg=self._titlefg)
137
138 array = self._tclarray
139 for indx, col in enumerate(cols):
140 table_indx = '-1,%d' % indx
141 array[table_indx] = col
142
143 def _adjust_calendar(self, month_now):
144 array = self._tclarray
145 table = self.table
146 month_0 = month_now - 1
147 # remove the 'not_this_month' tag from items that were using it and
148 # possibly won't be redisplayed now.
149 table.tag_delete('not_this_month')
150 table.tag_configure('not_this_month', fg='grey70')
151 # XXX clear selection
152 table.selection_clear('all')
153
154 # update values in calendar
155 self._monthlbl['text'] = calendar.month_name[month_now]
156 for week_indx, week in enumerate(self._yeardates[month_0]):
157 array['%d,-1' % week_indx] = week[0].strftime('%U')
158 for day_indx, date in enumerate(week):
159 table_indx = '%d,%d' % (week_indx, day_indx)
160 array[table_indx] = date.day
161 if date.month != month_now:
162 table.tag_cell('not_this_month', table_indx)
163
164 # erase data in rows that weren't overrwritten
165 for row in range(len(self._yeardates[month_0]), 6):
166 for i in range(-1, 7):
167 array.unset('%d,%d' % (row, i))
168
169 def _set_minsize(self, event):
170 self.master.wm_minsize(self.winfo_width(), self.winfo_height())
171
172 def _get_year_dates(self):
173 return self.__year_dates
174
175 def _set_year_dates(self, year):
176 self.__year_dates = [
177 self._cal.monthdatescalendar(year, i)
178 for i in range(calendar.January, calendar.January+12)
179 ]
180 self._yearlbl['text'] = year
181 self._adjust_calendar(self._month)
182
183 def _get_selected(self):
184 week, day = self.__selected
185 date = self._yeardates[self._month - 1][week][day]
186 return (date.year, date.month, date.day)
187
188 def _set_selection(self, event):
189 if event.r == -1 or event.c == -1 or not self._tclarray.get(event.C):
190 return
191
192 self.__selected = (event.r, event.c)
193 self.event_generate('<<date-selected>>')
194
195 _yeardates = property(_get_year_dates, _set_year_dates)
196 selected = property(_get_selected, _set_selection)
197
198
199 def sample():
200 def print_date(event):
201 print event.widget.selected
202
203 cal = Calendar(titlebg='#2077ed', titlefg='white')
204 cal.pack()
205 cal.bind('<<date-selected>>', print_date)
206 cal.mainloop()
207
208 if __name__ == "__main__":
209 sample()
To run this you will ned the tktable wrapper: http://tkinter.unpy.net/wiki/TkTableWrapper
And the the ttk wrapper for python: http://pypi.python.org/pypi/pyttk, or optionally apply this patch: tkcalendar_nottk.diff to run it without ttk