9.12. Calendar

The Calendar widget is an effective way to display and retrieve monthly date related information. It is a very simple widget to create and work with.

Creating a gtk.Calendar widget is as simple as:

  calendar = gtk.Calendar()

The calendar will display the current month and year by default.

There might be times where you need to change a lot of information within this widget and the following methods allow you to make multiple changes to a Calendar widget without the user seeing multiple on-screen updates.

  calendar.freeze()

  calendar.thaw()

They work just like the freeze/thaw methods of every other widget.

The Calendar widget has a few options that allow you to change the way the widget both looks and operates by using the method:

  calendar.display_options(flags)

The flags argument can be formed by combining either of the following five options using the logical bitwise OR (|) operation:

CALENDAR_SHOW_HEADINGthis option specifies that the month and year should be shown when drawing the calendar.
CALENDAR_SHOW_DAY_NAMESthis option specifies that the three letter descriptions should be displayed for each day (e.g. Mon,Tue, etc.).
CALENDAR_NO_MONTH_CHANGEthis option states that the user should not and can not change the currently displayed month. This can be good if you only need to display a particular month such as if you are displaying 12 calendar widgets for every month in a particular year.
CALENDAR_SHOW_WEEK_NUMBERSthis option specifies that the number for each week should be displayed down the left side of the calendar. (e.g. Jan 1 = Week 1,Dec 31 = Week 52).
CALENDAR_WEEK_START_MONDAYthis option states that the calender week will start on Monday instead of Sunday which is the default. This only affects the order in which days are displayed from left to right. Note that in PyGTK 2.4 and above this option is deprecated.

The following methods are used to set the the currently displayed date:

  result = calendar.select_month(month, year)

  calendar.select_day(day)

The return value from the select_month() method is a boolean value indicating whether the selection was successful.

With the select_day() method the specified day number is selected within the current month, if that is possible. A day value of 0 will deselect any current selection.

In addition to having a day selected, any number of days in the month may be "marked". A marked day is highlighted within the calendar display. The following methods are provided to manipulate marked days:

  result = calendar.mark_day(day)

  result = calendar.unmark_day(day)

  calendar.clear_marks()

mark_day() and unmark_day() return a boolean indicating whether the method was successful. Note that marks are persistent across month and year changes.

The final Calendar widget method is used to retrieve the currently selected date, month and/or year.

  year, month, day = calendar.get_date()

The Calendar widget can generate a number of signals indicating date selection and change. The names of these signals are self explanatory, and are:

  month_changed

  day_selected

  day_selected_double_click

  prev_month

  next_month

  prev_year

  next_year

That just leaves us with the need to put all of this together into the calendar.py example program. Figure 9.12, “Calendar Example” illustrates the program operation:

Figure 9.12. Calendar Example

Calendar Example

The calendar.py source code is:

    1	#!/usr/bin/env python
    2	
    3	# example calendar.py
    4	#
    5	# Copyright (C) 1998 Cesar Miquel, Shawn T. Amundson, Mattias Gronlund
    6	# Copyright (C) 2000 Tony Gale
    7	# Copyright (C) 2001-2004 John Finlay
    8	#
    9	# This program is free software; you can redistribute it and/or modify
   10	# it under the terms of the GNU General Public License as published by
   11	# the Free Software Foundation; either version 2 of the License, or
   12	# (at your option) any later version.
   13	#
   14	# This program is distributed in the hope that it will be useful,
   15	# but WITHOUT ANY WARRANTY; without even the implied warranty of
   16	# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   17	# GNU General Public License for more details.
   18	#
   19	# You should have received a copy of the GNU General Public License
   20	# along with this program; if not, write to the Free Software
   21	# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
   22	
   23	import pygtk
   24	pygtk.require('2.0')
   25	import gtk, pango
   26	import time
   27	
   28	class CalendarExample:
   29	    DEF_PAD = 10
   30	    DEF_PAD_SMALL = 5
   31	    TM_YEAR_BASE = 1900
   32	
   33	    calendar_show_header = 0
   34	    calendar_show_days = 1
   35	    calendar_month_change = 2 
   36	    calendar_show_week = 3
   37	
   38	    def calendar_date_to_string(self):
   39	        year, month, day = self.window.get_date()
   40	        mytime = time.mktime((year, month+1, day, 0, 0, 0, 0, 0, -1))
   41	        return time.strftime("%x", time.localtime(mytime))
   42	
   43	    def calendar_set_signal_strings(self, sig_str):
   44	        prev_sig = self.prev_sig.get()
   45	        self.prev2_sig.set_text(prev_sig)
   46	
   47	        prev_sig = self.last_sig.get()
   48	        self.prev_sig.set_text(prev_sig)
   49	        self.last_sig.set_text(sig_str)
   50	
   51	    def calendar_month_changed(self, widget):
   52	        buffer = "month_changed: %s" % self.calendar_date_to_string()
   53	        self.calendar_set_signal_strings(buffer)
   54	
   55	    def calendar_day_selected(self, widget):
   56	        buffer = "day_selected: %s" % self.calendar_date_to_string()
   57	        self.calendar_set_signal_strings(buffer)
   58	
   59	    def calendar_day_selected_double_click(self, widget):
   60	        buffer = "day_selected_double_click: %s"
   61	        buffer = buffer % self.calendar_date_to_string()
   62	        self.calendar_set_signal_strings(buffer)
   63	
   64	        year, month, day = self.window.get_date()
   65	
   66	        if self.marked_date[day-1] == 0:
   67	            self.window.mark_day(day)
   68	            self.marked_date[day-1] = 1
   69	        else:
   70	            self.window.unmark_day(day)
   71	            self.marked_date[day-1] = 0
   72	
   73	    def calendar_prev_month(self, widget):
   74	        buffer = "prev_month: %s" % self.calendar_date_to_string()
   75	        self.calendar_set_signal_strings(buffer)
   76	
   77	    def calendar_next_month(self, widget):
   78	        buffer = "next_month: %s" % self.calendar_date_to_string()
   79	        self.calendar_set_signal_strings(buffer)
   80	
   81	    def calendar_prev_year(self, widget):
   82	        buffer = "prev_year: %s" % self.calendar_date_to_string()
   83	        self.calendar_set_signal_strings(buffer)
   84	
   85	    def calendar_next_year(self, widget):
   86	        buffer = "next_year: %s" % self.calendar_date_to_string()
   87	        self.calendar_set_signal_strings(buffer)
   88	
   89	    def calendar_set_flags(self):
   90	        options = 0
   91	        for i in range(5):
   92	            if self.settings[i]:
   93	                options = options + (1<<i)
   94	        if self.window:
   95	            self.window.display_options(options)
   96	
   97	    def calendar_toggle_flag(self, toggle):
   98	        j = 0
   99	        for i in range(5):
  100	            if self.flag_checkboxes[i] == toggle:
  101	                j = i
  102	
  103	        self.settings[j] = not self.settings[j]
  104	        self.calendar_set_flags()
  105	
  106	    def calendar_font_selection_ok(self, button):
  107	        self.font = self.font_dialog.get_font_name()
  108	        if self.window:
  109	            font_desc = pango.FontDescription(self.font)
  110	            if font_desc: 
  111	                self.window.modify_font(font_desc)
  112	
  113	    def calendar_select_font(self, button):
  114	        if not self.font_dialog:
  115	            window = gtk.FontSelectionDialog("Font Selection Dialog")
  116	            self.font_dialog = window
  117	    
  118	            window.set_position(gtk.WIN_POS_MOUSE)
  119	    
  120	            window.connect("destroy", self.font_dialog_destroyed)
  121	    
  122	            window.ok_button.connect("clicked",
  123	                                     self.calendar_font_selection_ok)
  124	            window.cancel_button.connect_object("clicked",
  125	                                                lambda wid: wid.destroy(),
  126	                                                self.font_dialog)
  127	        window = self.font_dialog
  128	        if not (window.flags() & gtk.VISIBLE):
  129	            window.show()
  130	        else:
  131	            window.destroy()
  132	            self.font_dialog = None
  133	
  134	    def font_dialog_destroyed(self, data=None):
  135	        self.font_dialog = None
  136	
  137	    def __init__(self):
  138	        flags = [
  139	            "Show Heading",
  140	            "Show Day Names",
  141	            "No Month Change",
  142	            "Show Week Numbers",
  143	            ]
  144	        self.window = None
  145	        self.font = None
  146	        self.font_dialog = None
  147	        self.flag_checkboxes = 5*[None]
  148	        self.settings = 5*[0]
  149	        self.marked_date = 31*[0]
  150	
  151	        window = gtk.Window(gtk.WINDOW_TOPLEVEL)
  152	        window.set_title("Calendar Example")
  153	        window.set_border_width(5)
  154	        window.connect("destroy", lambda x: gtk.main_quit())
  155	
  156	        window.set_resizable(False)
  157	
  158	        vbox = gtk.VBox(False, self.DEF_PAD)
  159	        window.add(vbox)
  160	
  161	        # The top part of the window, Calendar, flags and fontsel.
  162	        hbox = gtk.HBox(False, self.DEF_PAD)
  163	        vbox.pack_start(hbox, True, True, self.DEF_PAD)
  164	        hbbox = gtk.HButtonBox()
  165	        hbox.pack_start(hbbox, False, False, self.DEF_PAD)
  166	        hbbox.set_layout(gtk.BUTTONBOX_SPREAD)
  167	        hbbox.set_spacing(5)
  168	
  169	        # Calendar widget
  170	        frame = gtk.Frame("Calendar")
  171	        hbbox.pack_start(frame, False, True, self.DEF_PAD)
  172	        calendar = gtk.Calendar()
  173	        self.window = calendar
  174	        self.calendar_set_flags()
  175	        calendar.mark_day(19)
  176	        self.marked_date[19-1] = 1
  177	        frame.add(calendar)
  178	        calendar.connect("month_changed", self.calendar_month_changed)
  179	        calendar.connect("day_selected", self.calendar_day_selected)
  180	        calendar.connect("day_selected_double_click",
  181	                         self.calendar_day_selected_double_click)
  182	        calendar.connect("prev_month", self.calendar_prev_month)
  183	        calendar.connect("next_month", self.calendar_next_month)
  184	        calendar.connect("prev_year", self.calendar_prev_year)
  185	        calendar.connect("next_year", self.calendar_next_year)
  186	
  187	        separator = gtk.VSeparator()
  188	        hbox.pack_start(separator, False, True, 0)
  189	
  190	        vbox2 = gtk.VBox(False, self.DEF_PAD)
  191	        hbox.pack_start(vbox2, False, False, self.DEF_PAD)
  192	  
  193	        # Build the Right frame with the flags in 
  194	        frame = gtk.Frame("Flags")
  195	        vbox2.pack_start(frame, True, True, self.DEF_PAD)
  196	        vbox3 = gtk.VBox(True, self.DEF_PAD_SMALL)
  197	        frame.add(vbox3)
  198	
  199	        for i in range(len(flags)):
  200	            toggle = gtk.CheckButton(flags[i])
  201	            toggle.connect("toggled", self.calendar_toggle_flag)
  202	            vbox3.pack_start(toggle, True, True, 0)
  203	            self.flag_checkboxes[i] = toggle
  204	
  205	        # Build the right font-button 
  206	        button = gtk.Button("Font...")
  207	        button.connect("clicked", self.calendar_select_font)
  208	        vbox2.pack_start(button, False, False, 0)
  209	
  210	        #  Build the Signal-event part.
  211	        frame = gtk.Frame("Signal events")
  212	        vbox.pack_start(frame, True, True, self.DEF_PAD)
  213	
  214	        vbox2 = gtk.VBox(True, self.DEF_PAD_SMALL)
  215	        frame.add(vbox2)
  216	  
  217	        hbox = gtk.HBox (False, 3)
  218	        vbox2.pack_start(hbox, False, True, 0)
  219	        label = gtk.Label("Signal:")
  220	        hbox.pack_start(label, False, True, 0)
  221	        self.last_sig = gtk.Label("")
  222	        hbox.pack_start(self.last_sig, False, True, 0)
  223	
  224	        hbox = gtk.HBox (False, 3)
  225	        vbox2.pack_start(hbox, False, True, 0)
  226	        label = gtk.Label("Previous signal:")
  227	        hbox.pack_start(label, False, True, 0)
  228	        self.prev_sig = gtk.Label("")
  229	        hbox.pack_start(self.prev_sig, False, True, 0)
  230	
  231	        hbox = gtk.HBox (False, 3)
  232	        vbox2.pack_start(hbox, False, True, 0)
  233	        label = gtk.Label("Second previous signal:")
  234	        hbox.pack_start(label, False, True, 0)
  235	        self.prev2_sig = gtk.Label("")
  236	        hbox.pack_start(self.prev2_sig, False, True, 0)
  237	
  238	        bbox = gtk.HButtonBox ()
  239	        vbox.pack_start(bbox, False, False, 0)
  240	        bbox.set_layout(gtk.BUTTONBOX_END)
  241	
  242	        button = gtk.Button("Close")
  243	        button.connect("clicked", lambda w: gtk.main_quit())
  244	        bbox.add(button)
  245	        button.set_flags(gtk.CAN_DEFAULT)
  246	        button.grab_default()
  247	
  248	        window.show_all()
  249	
  250	def main():
  251	    gtk.main()
  252	    return 0
  253	
  254	if __name__ == "__main__":
  255	    CalendarExample()
  256	    main()