aboutsummaryrefslogblamecommitdiffstats
path: root/calendar/gui/gnome-month-item.c
blob: ec799112de297d64edbebdfdab90197711fd38d1 (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11
12
13
14













                                                         
                                                                     
 

                                                          
                                         






                                                                                           
                                  










                                                                                                


                                                                                  

 








                   

                         


                           
                       

                            
                         

                          
                        


                           



                              

                         


























































                                                                                                                
                                                                                                                          
                                                                                                                     
                                                                                                                 

                                                                                                                                
                                                                                                                        
                                                                                                                           
                                                                                                                   
                                                                                                                         

                                                                                                                              
                                                                                                                    


                                                                                                                                 



                                                                                                                                    

                                                                                                                            





                                                         











































































































































                                                                                                                          


























































                                                                                                         
                                                                                     




                                                      
                                                                                   







                                                                



                                                                           

                                          
                                                                                     






                                                                    
















                                                          
                                                                                            




                                                                                      
                                                                                          













                                                                      
                                                                                            








                                                                           





                                                                                           

                            
                                 
                             

 











                                                                                     
                                         



                                      

                         
 






                                                                                   
 
                                                                                     
                                                               
                                             
         

 









                                                                                                  
                                                               




                                                                              

                                                                                                                  
                                                                             


                                                     

                                                                                                                  
                                                                             

                                                     


                               

 
























































                                                                                                                
                                                                                       























                                                                      


                                          









                                            



                                                 
              
                       

                      
                                                                                                          


                                              
                                   
                                                                                    





                                                    

                                                                                    


                                                   



                                           
                                                                                    

                                                    

 











                                                                                    
                                          



                                     


                         
 








                                                                                  
 
                                                                                    
                                                              
                                             
         

 




                                    




                                                                                                  
                                                              




                                                                              

                                                                                                                 
                                                                             


                                                     

                                                                                                                 
                                                                             


                                                     

                              


                         






                                              
                                                                                     
                                                                                                            






                                                            
                                                                        

                                
                            


                                                             






                                                  























                                                                     

                                  

                                               
                                              


                                    
                                                        




                                                           
                                                       



                                                          





















                                                                                      

                       


                                                       

                                                                                            
                                           

                                                                                    
                                           

                                       




























                                                                      























                                                                                                                
           










                                                                       
                                 



                                                     
                                 













                                                              




                                                               







                                                      






                                                                     




















                                                                                                   
                                











                                                                    




                                                          


                                                               
                                 

                      









                                                                           
                                

                      













                                                                                                      





                                                          
                                











                                                                          
                                

                      













                                                                                                      





                                                         
                                


                            
                                                                                   


                                
                                                                                  

                      


                                                                                     
 













                                                                                      


                               
                                                                                 

                      




                      













                                                                                                 



































                                                                       





                                                              









                                                             



                                                          



                                                               








                                                          








                                                                 


                               
                                                             

                      




                                             

                 
                                                                 






                                                                 
   
















                                                                          
                                                               


































                                                                                               
/* General-purpose monthly calendar canvas item for GNOME
 *
 * Copyright (C) 1998 Red Hat Software, Inc.
 *
 * Author: Federico Mena <federico@nuclecu.unam.mx>
 */

#include <config.h>
#include <math.h>
#include <time.h>
#include <gnome.h>
#include "gnome-month-item.h"


#define DEFAULT_FONT "-*-helvetica-medium-r-normal--10-*-*-*-p-*-*-*"


/* Number of days in a month, for normal and leap years */
static const int days_in_month[2][12] = {
    { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 },
    { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }
};

/* The weird month of September 1752, where 3 Sep through 13 Sep were eliminated due to the
 * Gregorian reformation.
 */
static const int sept_1752[42] = {
     0,  0,  1,  2, 14, 15, 16,
    17, 18, 19, 20, 21, 22, 23,
    24, 25, 26, 27, 28, 29, 30,
     0,  0,  0,  0,  0,  0,  0,
     0,  0,  0,  0,  0,  0,  0,
     0,  0,  0,  0,  0,  0,  0
};

#define REFORMATION_DAY 639787      /* First day of the reformation, counted from 1 Jan 1 */
#define MISSING_DAYS 11         /* They corrected out 11 days */
#define THURSDAY 4          /* First day of reformation */
#define SATURDAY 6          /* Offset value; 1 Jan 1 was a Saturday */
#define SEPT_1752_START 2       /* Start day within month */
#define SEPT_1752_END 20        /* End day within month */


enum {
    ARG_0,
    ARG_YEAR,
    ARG_MONTH,
    ARG_X,
    ARG_Y,
    ARG_WIDTH,
    ARG_HEIGHT,
    ARG_ANCHOR,
    ARG_HEAD_PADDING,
    ARG_DAY_PADDING,
    ARG_DAY_NAMES,
    ARG_HEADING_HEIGHT,
    ARG_HEADING_ANCHOR,
    ARG_DAY_ANCHOR,
    ARG_START_ON_MONDAY,
    ARG_HEAD_FONT,
    ARG_HEAD_FONTSET,
    ARG_HEAD_FONT_GDK,
    ARG_DAY_FONT,
    ARG_DAY_FONTSET,
    ARG_DAY_FONT_GDK,
    ARG_HEAD_COLOR,
    ARG_HEAD_COLOR_GDK,
    ARG_OUTLINE_COLOR,
    ARG_OUTLINE_COLOR_GDK,
    ARG_DAY_BOX_COLOR,
    ARG_DAY_BOX_COLOR_GDK,
    ARG_DAY_COLOR,
    ARG_DAY_COLOR_GDK
};


static void gnome_month_item_class_init (GnomeMonthItemClass *class);
static void gnome_month_item_init       (GnomeMonthItem      *mitem);
static void gnome_month_item_destroy    (GtkObject           *object);
static void gnome_month_item_set_arg    (GtkObject           *object,
                     GtkArg              *arg,
                     guint                arg_id);
static void gnome_month_item_get_arg    (GtkObject           *object,
                     GtkArg              *arg,
                     guint                arg_id);



static GnomeCanvasGroupClass *parent_class;


GtkType
gnome_month_item_get_type (void)
{
    static GtkType month_item_type = 0;

    if (!month_item_type) {
        GtkTypeInfo month_item_info = {
            "GnomeMonthItem",
            sizeof (GnomeMonthItem),
            sizeof (GnomeMonthItemClass),
            (GtkClassInitFunc) gnome_month_item_class_init,
            (GtkObjectInitFunc) gnome_month_item_init,
            NULL, /* reserved_1 */
            NULL, /* reserved_2 */
            (GtkClassInitFunc) NULL
        };

        month_item_type = gtk_type_unique (gnome_canvas_group_get_type (), &month_item_info);
    }

    return month_item_type;
}

static void
gnome_month_item_class_init (GnomeMonthItemClass *class)
{
    GtkObjectClass *object_class;
    GnomeCanvasItemClass *item_class;

    object_class = (GtkObjectClass *) class;
    item_class = (GnomeCanvasItemClass *) class;

    parent_class = gtk_type_class (gnome_canvas_group_get_type ());

    gtk_object_add_arg_type ("GnomeMonthItem::year", GTK_TYPE_UINT, GTK_ARG_READWRITE, ARG_YEAR);
    gtk_object_add_arg_type ("GnomeMonthItem::month", GTK_TYPE_UINT, GTK_ARG_READWRITE, ARG_MONTH);
    gtk_object_add_arg_type ("GnomeMonthItem::x", GTK_TYPE_DOUBLE, GTK_ARG_READWRITE, ARG_X);
    gtk_object_add_arg_type ("GnomeMonthItem::y", GTK_TYPE_DOUBLE, GTK_ARG_READWRITE, ARG_Y);
    gtk_object_add_arg_type ("GnomeMonthItem::width", GTK_TYPE_DOUBLE, GTK_ARG_READWRITE, ARG_WIDTH);
    gtk_object_add_arg_type ("GnomeMonthItem::height", GTK_TYPE_DOUBLE, GTK_ARG_READWRITE, ARG_HEIGHT);
    gtk_object_add_arg_type ("GnomeMonthItem::anchor", GTK_TYPE_ANCHOR_TYPE, GTK_ARG_READWRITE, ARG_ANCHOR);
    gtk_object_add_arg_type ("GnomeMonthItem::heading_padding", GTK_TYPE_DOUBLE, GTK_ARG_READWRITE, ARG_HEAD_PADDING);
    gtk_object_add_arg_type ("GnomeMonthItem::day_padding", GTK_TYPE_DOUBLE, GTK_ARG_READWRITE, ARG_DAY_PADDING);
    gtk_object_add_arg_type ("GnomeMonthItem::day_names", GTK_TYPE_POINTER, GTK_ARG_WRITABLE, ARG_DAY_NAMES);
    gtk_object_add_arg_type ("GnomeMonthItem::heading_height", GTK_TYPE_DOUBLE, GTK_ARG_READWRITE, ARG_HEADING_HEIGHT);
    gtk_object_add_arg_type ("GnomeMonthItem::heading_anchor", GTK_TYPE_ANCHOR_TYPE, GTK_ARG_READWRITE, ARG_HEADING_ANCHOR);
    gtk_object_add_arg_type ("GnomeMonthItem::day_anchor", GTK_TYPE_ANCHOR_TYPE, GTK_ARG_READWRITE, ARG_DAY_ANCHOR);
    gtk_object_add_arg_type ("GnomeMonthItem::start_on_monday", GTK_TYPE_BOOL, GTK_ARG_READWRITE, ARG_START_ON_MONDAY);
    gtk_object_add_arg_type ("GnomeMonthItem::heading_font", GTK_TYPE_STRING, GTK_ARG_WRITABLE, ARG_HEAD_FONT);
    gtk_object_add_arg_type ("GnomeMonthItem::heading_fontset", GTK_TYPE_STRING, GTK_ARG_WRITABLE, ARG_HEAD_FONTSET);
    gtk_object_add_arg_type ("GnomeMonthItem::heading_font_gdk", GTK_TYPE_GDK_FONT, GTK_ARG_READWRITE, ARG_HEAD_FONT_GDK);
    gtk_object_add_arg_type ("GnomeMonthItem::day_font", GTK_TYPE_STRING, GTK_ARG_WRITABLE, ARG_DAY_FONT);
    gtk_object_add_arg_type ("GnomeMonthItem::day_fontset", GTK_TYPE_STRING, GTK_ARG_WRITABLE, ARG_DAY_FONTSET);
    gtk_object_add_arg_type ("GnomeMonthItem::day_font_gdk", GTK_TYPE_GDK_FONT, GTK_ARG_READWRITE, ARG_DAY_FONT_GDK);
    gtk_object_add_arg_type ("GnomeMonthItem::heading_color", GTK_TYPE_STRING, GTK_ARG_WRITABLE, ARG_HEAD_COLOR);
    gtk_object_add_arg_type ("GnomeMonthItem::heading_color_gdk", GTK_TYPE_GDK_COLOR, GTK_ARG_READWRITE, ARG_HEAD_COLOR_GDK);
    gtk_object_add_arg_type ("GnomeMonthItem::outline_color", GTK_TYPE_STRING, GTK_ARG_WRITABLE, ARG_OUTLINE_COLOR);
    gtk_object_add_arg_type ("GnomeMonthItem::outline_color_gdk", GTK_TYPE_GDK_COLOR, GTK_ARG_READWRITE, ARG_OUTLINE_COLOR_GDK);
    gtk_object_add_arg_type ("GnomeMonthItem::day_box_color", GTK_TYPE_STRING, GTK_ARG_WRITABLE, ARG_DAY_BOX_COLOR);
    gtk_object_add_arg_type ("GnomeMonthItem::day_box_color_gdk", GTK_TYPE_GDK_COLOR, GTK_ARG_READWRITE, ARG_DAY_BOX_COLOR_GDK);
    gtk_object_add_arg_type ("GnomeMonthItem::day_color", GTK_TYPE_STRING, GTK_ARG_WRITABLE, ARG_DAY_COLOR);
    gtk_object_add_arg_type ("GnomeMonthItem::day_color_gdk", GTK_TYPE_GDK_COLOR, GTK_ARG_READWRITE, ARG_DAY_COLOR_GDK);

    object_class->destroy = gnome_month_item_destroy;
    object_class->set_arg = gnome_month_item_set_arg;
    object_class->get_arg = gnome_month_item_get_arg;
}

/* Calculates the minimum heading height based on the heading font size and padding.  It also
 * calculates the minimum width of the month item based on the width of the headings.
 */
static void
check_heading_sizes (GnomeMonthItem *mitem)
{
    double m_height;
    double m_width;
    int width;
    int max_width;
    int i;

    /* Calculate minimum height */

    m_height = mitem->head_font->ascent + mitem->head_font->descent + 2 * mitem->head_padding;

    if (mitem->head_height < m_height)
        mitem->head_height = m_height;
    
    /* Go through each heading and remember the widest one */

    max_width = 0;

    for (i = 0; i < 7; i++) {
        width = gdk_string_width (mitem->head_font, mitem->day_names[i]);
        if (max_width < width)
            max_width = width;
    }

    m_width = 7 * (max_width + 2 * mitem->head_padding);

    if (mitem->width < m_width)
        mitem->width = m_width;
}

/* Calculates the minimum width and height of the month item based on the day font size and padding.
 * Assumes that the minimum heading height has already been computed.
 */
static void
check_day_sizes (GnomeMonthItem *mitem)
{
    double m_height;
    double m_width;
    int width;
    int max_width;
    char buf[100];
    int i;

    /* Calculate minimum height */

    m_height = mitem->head_height + 6 * (mitem->day_font->ascent + mitem->day_font->descent + 2 * mitem->day_padding);

    if (mitem->height < m_height)
        mitem->height = m_height;

    /* Calculate minimum width */

    max_width = 0;

    for (i = 1; i < 32; i++) {
        sprintf (buf, "%d", i);
        width = gdk_string_width (mitem->day_font, buf);
        if (max_width < width)
            max_width = width;
    }

    m_width = 7 * (max_width + 2 * mitem->day_padding);

    if (mitem->width < m_width)
        mitem->width = m_width;
}

/* Calculates the minimum size of the month item based on the font sizes and paddings.  If the
 * current size of the month item is smaller than the required minimum size, this function will
 * change the size to the appropriate values.
 */
static void
check_sizes (GnomeMonthItem *mitem)
{
    check_heading_sizes (mitem);
    check_day_sizes (mitem);
}

/* Recalculates the position of the toplevel calendar group based on the logical position and anchor */
static void
reanchor (GnomeMonthItem *mitem)
{
    double x, y;

    x = mitem->x;
    y = mitem->y;

    switch (mitem->anchor) {
    case GTK_ANCHOR_NW:
    case GTK_ANCHOR_W:
    case GTK_ANCHOR_SW:
        break;

    case GTK_ANCHOR_N:
    case GTK_ANCHOR_CENTER:
    case GTK_ANCHOR_S:
        x -= mitem->width / 2;
        break;

    case GTK_ANCHOR_NE:
    case GTK_ANCHOR_E:
    case GTK_ANCHOR_SE:
        x -= mitem->width;
        break;
    }

    switch (mitem->anchor) {
    case GTK_ANCHOR_NW:
    case GTK_ANCHOR_N:
    case GTK_ANCHOR_NE:
        break;

    case GTK_ANCHOR_W:
    case GTK_ANCHOR_CENTER:
    case GTK_ANCHOR_E:
        y -= mitem->height / 2;
        break;

    case GTK_ANCHOR_SW:
    case GTK_ANCHOR_S:
    case GTK_ANCHOR_SE:
        y -= mitem->height;
        break;
    }

    /* Explicitly use the canvas group class prefix since the month item class has x and y
     * arguments as well.
     */

    gnome_canvas_item_set (GNOME_CANVAS_ITEM (mitem),
                   "GnomeCanvasGroup::x", x,
                   "GnomeCanvasGroup::y", y,
                   NULL);
}

/* Takes an anchor specification and the corners of a rectangle, and returns an anchored point with
 * respect to that rectangle.
 */
static void
get_label_anchor (GtkAnchorType anchor, double x1, double y1, double x2, double y2, double *x, double *y)
{
    switch (anchor) {
    case GTK_ANCHOR_NW:
    case GTK_ANCHOR_W:
    case GTK_ANCHOR_SW:
        *x = x1;
        break;

    case GTK_ANCHOR_N:
    case GTK_ANCHOR_CENTER:
    case GTK_ANCHOR_S:
        *x = (x1 + x2) / 2.0;
        break;

    case GTK_ANCHOR_NE:
    case GTK_ANCHOR_E:
    case GTK_ANCHOR_SE:
        *x = x2;
        break;
    }

    switch (anchor) {
    case GTK_ANCHOR_NW:
    case GTK_ANCHOR_N:
    case GTK_ANCHOR_NE:
        *y = y1;
        break;

    case GTK_ANCHOR_W:
    case GTK_ANCHOR_CENTER:
    case GTK_ANCHOR_E:
        *y = (y1 + y2) / 2.0;
        break;

    case GTK_ANCHOR_SW:
    case GTK_ANCHOR_S:
    case GTK_ANCHOR_SE:
        *y = y2;
        break;
    }
}

/* Resets the position of the day name headings in the calendar */
static void
reshape_headings (GnomeMonthItem *mitem)
{
    double width;
    int i;
    double x, y;

    width = mitem->width / 7;

    for (i = 0; i < 7; i++) {
        /* Group */
        gnome_canvas_item_set (mitem->items[GNOME_MONTH_ITEM_HEAD_GROUP + i],
                       "x", width * i,
                       "y", 0.0,
                       NULL);

        /* Box */
        gnome_canvas_item_set (mitem->items[GNOME_MONTH_ITEM_HEAD_BOX + i],
                       "x1", 0.0,
                       "y1", 0.0,
                       "x2", width,
                       "y2", mitem->head_height,
                       NULL);

        /* Label */
        get_label_anchor (mitem->head_anchor,
                  mitem->head_padding,
                  mitem->head_padding,
                  width - mitem->head_padding,
                  mitem->head_height - mitem->head_padding,
                  &x, &y);

        gnome_canvas_item_set (mitem->items[GNOME_MONTH_ITEM_HEAD_LABEL + i],
                       "x", x,
                       "y", y,
                       "anchor", mitem->head_anchor,
                       NULL);
    }
}

/* Resets the position of the days in the calendar */
static void
reshape_days (GnomeMonthItem *mitem)
{
    double width, height;
    double x, y;
    int row, col;
    int i;

    width = mitem->width / 7;
    height = (mitem->height - mitem->head_height) / 6;

    i = 0;

    for (row = 0; row < 6; row++)
        for (col = 0; col < 7; col++) {
            /* Group */
            gnome_canvas_item_set (mitem->items[GNOME_MONTH_ITEM_DAY_GROUP + i],
                           "x", width * col,
                           "y", mitem->head_height + height * row,
                           NULL);

            /* Box */
            gnome_canvas_item_set (mitem->items[GNOME_MONTH_ITEM_DAY_BOX + i],
                           "x1", 0.0,
                           "y1", 0.0,
                           "x2", width,
                           "y2", height,
                           NULL);

            /* Label */
            get_label_anchor (mitem->day_anchor,
                      mitem->day_padding,
                      mitem->day_padding,
                      width - mitem->day_padding,
                      height - mitem->day_padding,
                      &x, &y);

            gnome_canvas_item_set (mitem->items[GNOME_MONTH_ITEM_DAY_LABEL + i],
                           "x", x,
                           "y", y,
                           "anchor", mitem->day_anchor,
                           NULL);

            i++;
        }
}

/* Changes the positions and resizes the items in the calendar to match the new size of the
 * calendar.
 */
static void
reshape (GnomeMonthItem *mitem)
{
    check_sizes (mitem);
    reanchor (mitem);
    reshape_headings (mitem);
    reshape_days (mitem);
}

/* Sets the font for all the day headings */
static void
set_head_font (GnomeMonthItem *mitem)
{
    int i;

    for (i = 0; i < 7; i++)
        gnome_canvas_item_set (mitem->items[GNOME_MONTH_ITEM_HEAD_LABEL + i],
                       "font_gdk", mitem->head_font,
                       NULL);
}

/* Sets the color for all the headings */
static void
set_head_color (GnomeMonthItem *mitem)
{
    int i;
    GdkColor outline;
    GdkColor head;

    outline.pixel = mitem->outline_pixel;
    head.pixel = mitem->head_pixel;

    for (i = 0; i < 7; i++) {
        gnome_canvas_item_set (mitem->items[GNOME_MONTH_ITEM_HEAD_BOX + i],
                       "fill_color_gdk", &outline,
                       NULL);

        gnome_canvas_item_set (mitem->items[GNOME_MONTH_ITEM_HEAD_LABEL + i],
                       "fill_color_gdk", &head,
                       NULL);
    }
}

/* Creates the items for the day name headings */
static void
create_headings (GnomeMonthItem *mitem)
{
    int i;

    /* Just create the items; they will be positioned and configured by a call to reshape() */

    for (i = 0; i < 7; i++) {
        /* Group */
        mitem->items[GNOME_MONTH_ITEM_HEAD_GROUP + i] =
            gnome_canvas_item_new (GNOME_CANVAS_GROUP (mitem),
                           gnome_canvas_group_get_type (),
                           NULL);

        /* Box */
        mitem->items[GNOME_MONTH_ITEM_HEAD_BOX + i] =
            gnome_canvas_item_new (GNOME_CANVAS_GROUP (mitem->items[GNOME_MONTH_ITEM_HEAD_GROUP + i]),
                           gnome_canvas_rect_get_type (),
                           NULL);

        /* Label */
        mitem->items[GNOME_MONTH_ITEM_HEAD_LABEL + i] =
            gnome_canvas_item_new (GNOME_CANVAS_GROUP (mitem->items[GNOME_MONTH_ITEM_HEAD_GROUP + i]),
                           gnome_canvas_text_get_type (),
                           NULL);
    }

    set_head_font (mitem);
    set_head_color (mitem);
}

/* Returns the number of leap years since year 1 up to (but not including) the specified year */
static int
leap_years_up_to (int year)
{
    return (year / 4                    /* trivial leapness */
        - ((year > 1700) ? (year / 100 - 17) : 0)   /* minus centuries since 1700 */
        + ((year > 1600) ? ((year - 1600) / 400) : 0)); /* plus centuries since 1700 divisible by 400 */
}

/* Returns whether the specified year is a leap year */
static int
is_leap_year (int year)
{
    if (year <= 1752)
        return !(year % 4);
    else
        return (!(year % 4) && (year % 100)) || !(year % 400);
}

/* Returns the 1-based day number within the year of the specified date */
static int
day_in_year (int day, int month, int year)
{
    int is_leap, i;

    is_leap = is_leap_year (year);

    for (i = 0; i < month; i++)
        day += days_in_month [is_leap][i];

    return day;
}

/* Returns the day of the week (zero-based, zero is Sunday) for the specified date.  For the days
 * that were removed on the Gregorian reformation, it returns Thursday.
 */
static int
day_in_week (int day, int month, int year)
{
    int n;

    n = (year - 1) * 365 + leap_years_up_to (year - 1) + day_in_year (day, month, year);

    if (n < REFORMATION_DAY)
        return (n - 1 + SATURDAY) % 7;

    if (n >= (REFORMATION_DAY + MISSING_DAYS))
        return (n - 1 + SATURDAY - MISSING_DAYS) % 7;

    return THURSDAY;
}

/* Fills the 42-element days array with the day numbers for the specified month.  Slots outside the
 * bounds of the month are filled with zeros.  The starting and ending indexes of the days are
 * returned in the start and end arguments.
 */
static void
build_month (int month, int year, int start_on_monday, int *days, int *start, int *end)
{
    int i;
    int d_month, d_week;

    /* Note that months are zero-based, so September is month 8 */

    if ((year == 1752) && (month == 8)) {
        memcpy (days, sept_1752, 42 * sizeof (int));

        if (start)
            *start = SEPT_1752_START;

        if (end)
            *end = SEPT_1752_END;

        return;
    }

    for (i = 0; i < 42; i++)
        days[i] = 0;

    d_month = days_in_month[is_leap_year (year)][month];
    d_week = day_in_week (1, month, year);

    if (start_on_monday)
        d_week = (d_week + 6) % 7;

    for (i = 0; i < d_month; i++)
        days[d_week + i] = i + 1;

    if (start)
        *start = d_week;

    if (end)
        *end = d_week + d_month - 1;
}

/* Set the day numbers in the monthly calendar */
static void
set_days (GnomeMonthItem *mitem)
{
    int i;
    int start, end;
    char buf[100];

    build_month (mitem->month, mitem->year, mitem->start_on_monday, mitem->day_numbers, &start, &end);

    /* Clear days before start of month */

    for (i = 0; i < start; i++)
        gnome_canvas_item_set (mitem->items[GNOME_MONTH_ITEM_DAY_LABEL + i],
                       "text", NULL,
                       NULL);

    /* Set days of month */

    for (; start <= end; start++, i++) {
        sprintf (buf, "%d", mitem->day_numbers[start]);
        gnome_canvas_item_set (mitem->items[GNOME_MONTH_ITEM_DAY_LABEL + i],
                       "text", buf,
                       NULL);
    }

    /* Clear days after end of month */

    for (; i < 42; i++)
        gnome_canvas_item_set (mitem->items[GNOME_MONTH_ITEM_DAY_LABEL + i],
                       "text", NULL,
                       NULL);
}

/* Sets the font for all the day numbers */
static void
set_day_font (GnomeMonthItem *mitem)
{
    int i;

    for (i = 0; i < 42; i++)
        gnome_canvas_item_set (mitem->items[GNOME_MONTH_ITEM_DAY_LABEL + i],
                       "font_gdk", mitem->day_font,
                       NULL);
}

/* Sets the color for all the day items */
static void
set_day_color (GnomeMonthItem *mitem)
{
    int i;
    GdkColor outline;
    GdkColor day_box;
    GdkColor day;

    outline.pixel = mitem->outline_pixel;
    day_box.pixel = mitem->day_box_pixel;
    day.pixel = mitem->day_pixel;

    for (i = 0; i < 42; i++) {
        gnome_canvas_item_set (mitem->items[GNOME_MONTH_ITEM_DAY_BOX + i],
                       "outline_color_gdk", &outline,
                       "fill_color_gdk", &day_box,
                       NULL);

        gnome_canvas_item_set (mitem->items[GNOME_MONTH_ITEM_DAY_LABEL + i],
                       "fill_color_gdk", &day,
                       NULL);
    }
}

/* Creates the items for the days */
static void
create_days (GnomeMonthItem *mitem)
{
    int i;

    /* Just create the items; they will be positioned and configured by a call to reshape() */

    for (i = 0; i < 42; i++) {
        /* Group */
        mitem->items[GNOME_MONTH_ITEM_DAY_GROUP + i] =
            gnome_canvas_item_new (GNOME_CANVAS_GROUP (mitem),
                           gnome_canvas_group_get_type (),
                           NULL);

        /* Box */
        mitem->items[GNOME_MONTH_ITEM_DAY_BOX + i] =
            gnome_canvas_item_new (GNOME_CANVAS_GROUP (mitem->items[GNOME_MONTH_ITEM_DAY_GROUP + i]),
                           gnome_canvas_rect_get_type (),
                           NULL);

        /* Label */
        mitem->items[GNOME_MONTH_ITEM_DAY_LABEL + i] =
            gnome_canvas_item_new (GNOME_CANVAS_GROUP (mitem->items[GNOME_MONTH_ITEM_DAY_GROUP + i]),
                           gnome_canvas_text_get_type (),
                           NULL);
    }

    set_day_font (mitem);
    set_day_color (mitem);
    set_days (mitem);
}

/* Resets the text of the day name headings */
static void
set_day_names (GnomeMonthItem *mitem)
{
    int i;

    for (i = 0; i < 7; i++)
        gnome_canvas_item_set (mitem->items[GNOME_MONTH_ITEM_HEAD_LABEL + i],
                       "text", mitem->day_names[mitem->start_on_monday ? ((i + 1) % 7) : i],
                       NULL);
}

/* Creates all the canvas items that make up the calendar */
static void
create_items (GnomeMonthItem *mitem)
{
    mitem->items = g_new (GnomeCanvasItem *, GNOME_MONTH_ITEM_LAST);

    create_headings (mitem);
    create_days (mitem);

    /* Initialize by default to three-letter day names */

    mitem->day_names[0] = g_strdup (_("Sun"));
    mitem->day_names[1] = g_strdup (_("Mon"));
    mitem->day_names[2] = g_strdup (_("Tue"));
    mitem->day_names[3] = g_strdup (_("Wed"));
    mitem->day_names[4] = g_strdup (_("Thu"));
    mitem->day_names[5] = g_strdup (_("Fri"));
    mitem->day_names[6] = g_strdup (_("Sat"));
    
    set_day_names (mitem);
    reshape (mitem);
}

static void
gnome_month_item_init (GnomeMonthItem *mitem)
{
    time_t t;
    struct tm *tm;

    /* Initialize to the current month by default */

    t = time (NULL);
    tm = localtime (&t);

    mitem->year = tm->tm_year + 1900;
    mitem->month = tm->tm_mon;

    mitem->x = 0.0;
    mitem->y = 0.0;
    mitem->width = 150.0; /* not unreasonable defaults, I hope */
    mitem->height = 100.0;
    mitem->anchor = GTK_ANCHOR_NW;
    mitem->head_padding = 0.0;
    mitem->day_padding = 2.0;
    mitem->head_height = 14.0;
    mitem->head_anchor = GTK_ANCHOR_CENTER;
    mitem->day_anchor = GTK_ANCHOR_CENTER;

    /* Load the default fonts */

    mitem->head_font = gdk_font_load (DEFAULT_FONT);
    if (!mitem->head_font) {
        mitem->head_font = gdk_font_load ("fixed");
        g_assert (mitem->head_font != NULL);
    }

    mitem->day_font = gdk_font_load (DEFAULT_FONT);
    if (!mitem->day_font) {
        mitem->day_font = gdk_font_load ("fixed");
        g_assert (mitem->day_font != NULL);
    }
}

GnomeCanvasItem *
gnome_month_item_new (GnomeCanvasGroup *parent)
{
    GnomeMonthItem *mitem;

    g_return_val_if_fail (parent != NULL, NULL);
    g_return_val_if_fail (GNOME_IS_CANVAS_GROUP (parent), NULL);

    mitem = GNOME_MONTH_ITEM (gnome_canvas_item_new (parent,
                             gnome_month_item_get_type (),
                             NULL));

    gnome_month_item_construct (mitem);

    return GNOME_CANVAS_ITEM (mitem);
}

void
gnome_month_item_construct (GnomeMonthItem *mitem)
{
    GdkColor color;

    g_return_if_fail (mitem != NULL);
    g_return_if_fail (GNOME_IS_MONTH_ITEM (mitem));

    gnome_canvas_get_color (GNOME_CANVAS_ITEM (mitem)->canvas, "#d6d6d6d6d6d6", &color);
    mitem->head_pixel = color.pixel;
    mitem->day_box_pixel = color.pixel;

    gnome_canvas_get_color (GNOME_CANVAS_ITEM (mitem)->canvas, "black", &color);
    mitem->outline_pixel = color.pixel;
    mitem->day_pixel = color.pixel;

    create_items (mitem);
}

static void
free_day_names (GnomeMonthItem *mitem)
{
    int i;

    if (mitem->day_names[0])
        for (i = 0; i < 7; i++)
            g_free (mitem->day_names[i]);
}

static void
gnome_month_item_destroy (GtkObject *object)
{
    GnomeMonthItem *mitem;

    g_return_if_fail (object != NULL);
    g_return_if_fail (GNOME_IS_MONTH_ITEM (object));

    mitem = GNOME_MONTH_ITEM (object);

    free_day_names (mitem);

    if (GTK_OBJECT_CLASS (parent_class)->destroy)
        (* GTK_OBJECT_CLASS (parent_class)->destroy) (object);
}

/* Sets the color of the specified pixel value to that of the specified argument, which must be in
 * GdkColor format if format is TRUE, otherwise it must be in string format.
 */
static void
set_color_arg (GnomeMonthItem *mitem, gulong *pixel, GtkArg *arg, int gdk_format, int set_head, int set_day)
{
    GdkColor color;

    if (gdk_format)
        *pixel = ((GdkColor *) GTK_VALUE_BOXED (*arg))->pixel;
    else {
        if (gnome_canvas_get_color (GNOME_CANVAS_ITEM (mitem)->canvas, GTK_VALUE_STRING (*arg), &color))
            *pixel = color.pixel;
        else
            *pixel = 0;
    }

    if (set_head)
        set_head_color (mitem);

    if (set_day)
        set_day_color (mitem);
}

static void
gnome_month_item_set_arg (GtkObject *object, GtkArg *arg, guint arg_id)
{
    GnomeMonthItem *mitem;
    char **day_names;
    int i;

    mitem = GNOME_MONTH_ITEM (object);

    switch (arg_id) {
    case ARG_YEAR:
        mitem->year = GTK_VALUE_UINT (*arg);
        set_days (mitem);
        break;

    case ARG_MONTH:
        mitem->month = GTK_VALUE_UINT (*arg);
        set_days (mitem);
        break;

    case ARG_X:
        mitem->x = GTK_VALUE_DOUBLE (*arg);
        reanchor (mitem);
        break;

    case ARG_Y:
        mitem->y = GTK_VALUE_DOUBLE (*arg);
        reanchor (mitem);
        break;

    case ARG_WIDTH:
        mitem->width = fabs (GTK_VALUE_DOUBLE (*arg));
        reshape (mitem);
        break;

    case ARG_HEIGHT:
        mitem->height = fabs (GTK_VALUE_DOUBLE (*arg));
        reshape (mitem);
        break;

    case ARG_ANCHOR:
        mitem->anchor = GTK_VALUE_ENUM (*arg);
        reanchor (mitem);
        break;

    case ARG_HEAD_PADDING:
        mitem->head_padding = fabs (GTK_VALUE_DOUBLE (*arg));
        reshape (mitem);
        break;

    case ARG_DAY_PADDING:
        mitem->day_padding = fabs (GTK_VALUE_DOUBLE (*arg));
        reshape (mitem);
        break;

    case ARG_DAY_NAMES:
        day_names = GTK_VALUE_POINTER (*arg);

        /* First, check that none of the names is null */

        for (i = 0; i < 7; i++)
            if (!day_names[i]) {
                g_warning ("Day number %d was NULL; day names cannot be NULL!", i);
                return;
            }

        /* Set the new names */

        free_day_names (mitem);
        for (i = 0; i < 7; i++)
            mitem->day_names[i] = g_strdup (day_names[i]);

        set_day_names (mitem);
        reshape (mitem);
        break;

    case ARG_HEADING_HEIGHT:
        mitem->head_height = fabs (GTK_VALUE_DOUBLE (*arg));
        reshape (mitem);
        break;

    case ARG_HEADING_ANCHOR:
        mitem->head_anchor = GTK_VALUE_ENUM (*arg);
        reshape (mitem);
        break;

    case ARG_DAY_ANCHOR:
        mitem->day_anchor = GTK_VALUE_ENUM (*arg);
        reshape (mitem);
        break;

    case ARG_START_ON_MONDAY:
        mitem->start_on_monday = GTK_VALUE_BOOL (*arg);
        set_day_names (mitem);
        set_days (mitem);
        break;

    case ARG_HEAD_FONT:
        gdk_font_unref (mitem->head_font);

        mitem->head_font = gdk_font_load (GTK_VALUE_STRING (*arg));
        if (!mitem->head_font) {
            mitem->head_font = gdk_font_load ("fixed");
            g_assert (mitem->head_font != NULL);
        }

        set_head_font (mitem);
        reshape (mitem);
        break;

    case ARG_HEAD_FONTSET:
        gdk_font_unref (mitem->head_font);

        mitem->head_font = gdk_fontset_load (GTK_VALUE_STRING (*arg));
        if (!mitem->head_font) {
            mitem->head_font =
              gdk_fontset_load ("-*-fixed-medium-r-semicondensed--13-120-75-75-c-60-*-*");
            g_assert (mitem->head_font != NULL);
        }

        set_head_font (mitem);
        reshape (mitem);
        break;

    case ARG_HEAD_FONT_GDK:
        gdk_font_unref (mitem->head_font);

        mitem->head_font = GTK_VALUE_BOXED (*arg);
        gdk_font_ref (mitem->head_font);
        set_head_font (mitem);
        reshape (mitem);
        break;

    case ARG_DAY_FONT:
        gdk_font_unref (mitem->day_font);

        mitem->day_font = gdk_font_load (GTK_VALUE_STRING (*arg));
        if (!mitem->day_font) {
            mitem->day_font = gdk_font_load ("fixed");
            g_assert (mitem->day_font != NULL);
        }

        set_day_font (mitem);
        reshape (mitem);
        break;

    case ARG_DAY_FONTSET:
        gdk_font_unref (mitem->day_font);

        mitem->head_font = gdk_fontset_load (GTK_VALUE_STRING (*arg));
        if (!mitem->day_font) {
            mitem->day_font =
              gdk_fontset_load ("-*-fixed-medium-r-semicondensed--13-120-75-75-c-60-*-*");
            g_assert (mitem->day_font != NULL);
        }

        set_day_font (mitem);
        reshape (mitem);
        break;

    case ARG_DAY_FONT_GDK:
        gdk_font_unref (mitem->day_font);

        mitem->day_font = GTK_VALUE_BOXED (*arg);
        gdk_font_ref (mitem->day_font);
        set_day_font (mitem);
        reshape (mitem);
        break;

    case ARG_HEAD_COLOR:
        set_color_arg (mitem, &mitem->head_pixel, arg, FALSE, TRUE, FALSE);
        break;

    case ARG_HEAD_COLOR_GDK:
        set_color_arg (mitem, &mitem->head_pixel, arg, TRUE, TRUE, FALSE);
        break;

    case ARG_OUTLINE_COLOR:
        set_color_arg (mitem, &mitem->outline_pixel, arg, FALSE, TRUE, TRUE);
        break;

    case ARG_OUTLINE_COLOR_GDK:
        set_color_arg (mitem, &mitem->outline_pixel, arg, TRUE, TRUE, TRUE);
        break;

    case ARG_DAY_BOX_COLOR:
        set_color_arg (mitem, &mitem->day_box_pixel, arg, FALSE, FALSE, TRUE);
        break;

    case ARG_DAY_BOX_COLOR_GDK:
        set_color_arg (mitem, &mitem->day_box_pixel, arg, TRUE, FALSE, TRUE);
        break;

    case ARG_DAY_COLOR:
        set_color_arg (mitem, &mitem->day_pixel, arg, FALSE, FALSE, TRUE);
        break;

    case ARG_DAY_COLOR_GDK:
        set_color_arg (mitem, &mitem->day_pixel, arg, TRUE, FALSE, TRUE);
        break;

    default:
        break;
    }
}

/* Allocates a GdkColor structure filled with the specified pixel, and puts it into the specified
 * arg for returning it in the get_arg method.
 */
static void
get_color_arg (GnomeMonthItem *mitem, gulong pixel, GtkArg *arg)
{
    GdkColor *color;

    color = g_new (GdkColor, 1);
    color->pixel = pixel;
    gdk_color_context_query_color (GNOME_CANVAS_ITEM (mitem)->canvas->cc, color);
    GTK_VALUE_BOXED (*arg) = color;
}

static void
gnome_month_item_get_arg (GtkObject *object, GtkArg *arg, guint arg_id)
{
    GnomeMonthItem *mitem;

    mitem = GNOME_MONTH_ITEM (object);

    switch (arg_id) {
    case ARG_YEAR:
        GTK_VALUE_UINT (*arg) = mitem->year;
        break;

    case ARG_MONTH:
        GTK_VALUE_UINT (*arg) = mitem->month;
        break;

    case ARG_X:
        GTK_VALUE_DOUBLE (*arg) = mitem->x;
        break;

    case ARG_Y:
        GTK_VALUE_DOUBLE (*arg) = mitem->y;
        break;

    case ARG_WIDTH:
        GTK_VALUE_DOUBLE (*arg) = mitem->width;
        break;

    case ARG_HEIGHT:
        GTK_VALUE_DOUBLE (*arg) = mitem->height;
        break;

    case ARG_ANCHOR:
        GTK_VALUE_ENUM (*arg) = mitem->anchor;
        break;

    case ARG_HEAD_PADDING:
        GTK_VALUE_DOUBLE (*arg) = mitem->head_padding;
        break;

    case ARG_DAY_PADDING:
        GTK_VALUE_DOUBLE (*arg) = mitem->day_padding;
        break;

    case ARG_HEADING_HEIGHT:
        GTK_VALUE_DOUBLE (*arg) = mitem->head_height;
        break;

    case ARG_HEADING_ANCHOR:
        GTK_VALUE_ENUM (*arg) = mitem->head_anchor;
        break;

    case ARG_DAY_ANCHOR:
        GTK_VALUE_ENUM (*arg) = mitem->day_anchor;
        break;

    case ARG_START_ON_MONDAY:
        GTK_VALUE_BOOL (*arg) = mitem->start_on_monday;
        break;

    case ARG_HEAD_FONT_GDK:
        GTK_VALUE_BOXED (*arg) = mitem->head_font;
        break;

    case ARG_DAY_FONT_GDK:
        GTK_VALUE_BOXED (*arg) = mitem->day_font;
        break;

    case ARG_HEAD_COLOR_GDK:
        get_color_arg (mitem, mitem->head_pixel, arg);
        break;

    case ARG_OUTLINE_COLOR_GDK:
        get_color_arg (mitem, mitem->outline_pixel, arg);
        break;

    case ARG_DAY_BOX_COLOR_GDK:
        get_color_arg (mitem, mitem->day_box_pixel, arg);
        break;

    case ARG_DAY_COLOR_GDK:
        get_color_arg (mitem, mitem->day_pixel, arg);
        break;

    default:
        arg->type = GTK_TYPE_INVALID;
        break;
    }
}

GnomeCanvasItem *
gnome_month_item_num2child (GnomeMonthItem *mitem, int child_num)
{
    g_return_val_if_fail (mitem != NULL, NULL);
    g_return_val_if_fail (GNOME_IS_MONTH_ITEM (mitem), NULL);

    return mitem->items[child_num];
}

int
gnome_month_item_child2num (GnomeMonthItem *mitem, GnomeCanvasItem *child)
{
    int i;

    g_return_val_if_fail (mitem != NULL, -1);
    g_return_val_if_fail (GNOME_IS_MONTH_ITEM (mitem), -1);
    g_return_val_if_fail (child != NULL, -1);
    g_return_val_if_fail (GNOME_IS_CANVAS_ITEM (child), -1);

    for (i = 0; i < GNOME_MONTH_ITEM_LAST; i++)
        if (mitem->items[i] == child)
            return i;

    return -1;
}

int
gnome_month_item_num2day (GnomeMonthItem *mitem, int child_num)
{
    g_return_val_if_fail (mitem != NULL, 0);
    g_return_val_if_fail (GNOME_IS_MONTH_ITEM (mitem), 0);

    if ((child_num >= GNOME_MONTH_ITEM_DAY_GROUP) && (child_num < GNOME_MONTH_ITEM_LAST)) {
        child_num = (child_num - GNOME_MONTH_ITEM_DAY_GROUP) % 42;
        return mitem->day_numbers[child_num];
    } else
        return 0;
}

int
gnome_month_item_day2index (GnomeMonthItem *mitem, int day_num)
{
    int i;

    g_return_val_if_fail (mitem != NULL, -1);
    g_return_val_if_fail (GNOME_IS_MONTH_ITEM (mitem), -1);
    g_return_val_if_fail (day_num >= 1, -1);

    /* Find first day of month */

    for (i = 0; mitem->day_numbers[i] == 0; i++)
        ;

    /* Find the specified day */

    for (; (mitem->day_numbers[i] != 0) && (i < 42); i++)
        if (mitem->day_numbers[i] == day_num)
            return i;

    /* Bail out */

    return -1;
}