aboutsummaryrefslogblamecommitdiffstats
path: root/e-util/e-calendar.c
blob: cb989bf72fe6a1a6007a98f2b4bb72df8ef6b7d4 (plain) (tree)
1
2
3
4
5
6
7
8
9
  


                                                                           
  



                                                                             
  

                                                                           






                                                        
  









                                                                             

                       


                       
                    

                                          
                                               
 

                                      
                               
                                                                  
                                        

                                                                   




                                                                            
                      







                                                                             

                                                                  

                                                   
 
                                                                 


                                                                         





                                                                        

                                                                     







                                                                 

                                                              
                                                              


                                                              
                                                              

                                                              
                                                              













                                                                          
 



                      
 


                                             
                                     

                                     
                                              

                                                
                                                   
 


                                                                           
                                                                             



                                                               

 



                                       
                                              
                                   
                        

                                    
 




                                                                    
 

                                                                     





                                                                 
 
                                                      
 
                                                                          
                                   
                                                                     
                                 








                                                               
 
                                                                 
                                 

                                                           




                                                
                                                  
                                                        

                                   
                                                                     
                                 








                                                               
 
                                                                  
                                 

                                                           




                                                
                                                  





                                                                         








                                                                    




                                                                 




                                                     





                                                                     








                                                                    




                                                                  




                                                     

                                                   




                           

                            

 






                              
                     

                       
                        
 
                                                          

                                                        



                   
           
                                    







                                                  
                                   
                                                  

                                    
 

                                                                   

 


                                      


                          
                                                                        

                                                                       
                                                              


                                                                         

 
           

                                               
 

                              
                                         

                                                                                 
                                                                               

                                                                       
                                                              
                                               






                                                                                 
                                                                

 
           
                                                  

                                              


                        
                       

                                  
                                                        
 
                                                                        
 




                                                                                

                                               








                                                                       
 
                                                                                 

 
           

                                                    

                       
                        
                                     


                                        
                                               

                                                                                  

                                  


                                              
 
                                                                                          
 


                                                              


                                                            
 
                                                                      


                                              


                                                            
                                                 


                                                
 
                                                         






                                                 
 





                                                                                     
                                         


                                                                              

                                                       





                                                                                                    
 






                                                                
 

                                                                                                                        






                                                                




                                                                                                   






                                                                


                                                                                                                       






                                                                

                                                

 
    


                                            





                                               




                                                 



                                                   
    


                                            





                                               




                                                 



                                                   

                                                                 




                                           




                                               
                                                        

                    



                                            




                                                    
           
                                           
 

                                                
 
           
                                           



                                               
           











                                                     

                                                    
 
                                   
                                                       
                                                     
                                                           
         
 

                                                                
 

 
           

                                                         

                                   
                                                       
                                                     
                                                                
         
 















                                                                



                                                        


                                                 

         


                    
               
                                            









                                                           



                                                      


                                                 

         
                    

 
           
                                            
 

                                        
 
           
                                            



                                        
           











                                                 
                                          

                                   
                                                  
                                    
         
 
 
           
                                           
 


                                                 


           
                                           
 


                                                 




                                                


                                                 




                                                


                                                 

 
           




                                                
 


                     
           


                                               
 

 
               
                                            





                                                          

                                                                   

                                                       



                       

                                             
 
                                       










                                                                 
                                               




                                                       

                                          

                                                                   
                                                                               



                                                                                 






















                                                                          






                                                                               

                                             
 
                          
                                             
                            


                                               
                                  

                                                                   

                        


                                                             

              

                                                              
                                                                    


                                                                 


                                                              

         
/*
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, see <http://www.gnu.org/licenses/>.
 *
 *
 * Authors:
 *      Damon Chaplin <damon@ximian.com>
 *      Bolian Yin <bolian.yin@sun.com>
 *
 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
 *
 */

/*
 * ECalendar - displays a table of monthly calendars, allowing highlighting
 * and selection of one or more days. Like GtkCalendar with more features.
 * Most of the functionality is in the ECalendarItem canvas item, though
 * we also add GnomeCanvasWidget buttons to go to the previous/next month and
 * to got to the current day.
 */

#include "e-calendar.h"

#include <config.h>
#include <glib/gi18n.h>

#include <gtk/gtk.h>
#include <libedataserver/libedataserver.h>

#include <libgnomecanvas/gnome-canvas-widget.h>

#define E_CALENDAR_SMALL_FONT_PTSIZE 6

#define E_CALENDAR_SMALL_FONT \
    "-adobe-utopia-regular-r-normal-*-*-100-*-*-p-*-iso8859-*"
#define E_CALENDAR_SMALL_FONT_FALLBACK \
    "-adobe-helvetica-medium-r-normal-*-*-80-*-*-p-*-iso8859-*"

/* The space between the arrow buttons and the edge of the widget. */
#define E_CALENDAR_ARROW_BUTTON_X_PAD   2
#define E_CALENDAR_ARROW_BUTTON_Y_PAD   0

/* Vertical padding. The padding above the button includes the space for the
 * horizontal line. */
#define E_CALENDAR_YPAD_ABOVE_LOWER_BUTTONS     4
#define E_CALENDAR_YPAD_BELOW_LOWER_BUTTONS     3

/* Horizontal padding inside & between buttons. */
#define E_CALENDAR_IXPAD_BUTTONS            4
#define E_CALENDAR_XPAD_BUTTONS             8

/* The time between steps when the prev/next buttons is pressed, in 1/1000ths
 * of a second, and the number of timeouts we skip before we start
 * automatically moving back/forward. */
#define E_CALENDAR_AUTO_MOVE_TIMEOUT        150
#define E_CALENDAR_AUTO_MOVE_TIMEOUT_DELAY  2

static void e_calendar_dispose      (GObject    *object);
static void e_calendar_realize      (GtkWidget  *widget);
static void e_calendar_style_set    (GtkWidget  *widget,
                     GtkStyle   *previous_style);
static void e_calendar_get_preferred_width (GtkWidget *widget,
                        gint      *minimal_width,
                        gint      *natural_width);
static void e_calendar_get_preferred_height (GtkWidget *widget,
                         gint      *minimal_height,
                         gint      *natural_height);
static void e_calendar_size_allocate    (GtkWidget  *widget,
                     GtkAllocation  *allocation);
static gint e_calendar_drag_motion  (GtkWidget      *widget,
                     GdkDragContext *context,
                     gint            x,
                     gint            y,
                     guint           time);
static void e_calendar_drag_leave   (GtkWidget      *widget,
                     GdkDragContext *context,
                     guint           time);
static gboolean e_calendar_button_has_focus (ECalendar  *cal);
static gboolean e_calendar_focus (GtkWidget *widget,
                  GtkDirectionType direction);

static void e_calendar_on_prev_pressed  (ECalendar  *cal);
static void e_calendar_on_prev_released (ECalendar  *cal);
static void e_calendar_on_prev_clicked  (ECalendar      *cal);
static void e_calendar_on_next_pressed  (ECalendar  *cal);
static void e_calendar_on_next_released (ECalendar  *cal);
static void e_calendar_on_next_clicked  (ECalendar      *cal);
static void e_calendar_on_prev_year_pressed  (ECalendar *cal);
static void e_calendar_on_prev_year_released (ECalendar *cal);
static void e_calendar_on_prev_year_clicked  (ECalendar *cal);
static void e_calendar_on_next_year_pressed  (ECalendar *cal);
static void e_calendar_on_next_year_released (ECalendar *cal);
static void e_calendar_on_next_year_clicked  (ECalendar *cal);

static void e_calendar_start_auto_move  (ECalendar *cal,
                         gboolean moving_forward);
static gboolean e_calendar_auto_move_handler    (gpointer data);
static void e_calendar_start_auto_move_year (ECalendar *cal,
                         gboolean moving_forward);
static gboolean e_calendar_auto_move_year_handler (gpointer data);
static void e_calendar_stop_auto_move       (ECalendar *cal);

G_DEFINE_TYPE (
    ECalendar,
    e_calendar,
    E_TYPE_CANVAS)

static void
e_calendar_class_init (ECalendarClass *class)
{
    GObjectClass   *object_class;
    GtkWidgetClass *widget_class;

    object_class = (GObjectClass *) class;
    widget_class = (GtkWidgetClass *) class;

    object_class->dispose = e_calendar_dispose;

    widget_class->realize = e_calendar_realize;
    widget_class->style_set = e_calendar_style_set;
    widget_class->get_preferred_width = e_calendar_get_preferred_width;
    widget_class->get_preferred_height = e_calendar_get_preferred_height;
    widget_class->size_allocate = e_calendar_size_allocate;
    widget_class->drag_motion = e_calendar_drag_motion;
    widget_class->drag_leave = e_calendar_drag_leave;
    widget_class->focus = e_calendar_focus;
}

static void
e_calendar_init (ECalendar *cal)
{
    GnomeCanvasGroup *canvas_group;
    PangoFontDescription *small_font_desc;
    GtkWidget *button, *pixmap;
    AtkObject *a11y;

    /* Create the small font. */

    small_font_desc = pango_font_description_copy (
        gtk_widget_get_style (GTK_WIDGET (cal))->font_desc);
    pango_font_description_set_size (
        small_font_desc,
        E_CALENDAR_SMALL_FONT_PTSIZE * PANGO_SCALE);

    canvas_group = GNOME_CANVAS_GROUP (GNOME_CANVAS (cal)->root);

    cal->calitem = E_CALENDAR_ITEM (
        gnome_canvas_item_new (
            canvas_group,
            e_calendar_item_get_type (),
            "week_number_font_desc", small_font_desc,
            NULL));

    pango_font_description_free (small_font_desc);

    /* Create the arrow buttons to move to the previous/next month. */
    button = gtk_button_new ();
    gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NONE);
    gtk_widget_show (button);
    g_signal_connect_swapped (
        button, "pressed",
        G_CALLBACK (e_calendar_on_prev_pressed), cal);
    g_signal_connect_swapped (
        button, "released",
        G_CALLBACK (e_calendar_on_prev_released), cal);
    g_signal_connect_swapped (
        button, "clicked",
        G_CALLBACK (e_calendar_on_prev_clicked), cal);

    pixmap = gtk_arrow_new (GTK_ARROW_LEFT, GTK_SHADOW_NONE);
    gtk_widget_show (pixmap);
    gtk_container_add (GTK_CONTAINER (button), pixmap);

    cal->prev_item = gnome_canvas_item_new (
        canvas_group,
        gnome_canvas_widget_get_type (),
        "widget", button,
        NULL);
    a11y = gtk_widget_get_accessible (button);
    atk_object_set_name (a11y, _("Previous month"));

    button = gtk_button_new ();
    gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NONE);
    gtk_widget_show (button);
    g_signal_connect_swapped (
        button, "pressed",
        G_CALLBACK (e_calendar_on_next_pressed), cal);
    g_signal_connect_swapped (
        button, "released",
        G_CALLBACK (e_calendar_on_next_released), cal);
    g_signal_connect_swapped (
        button, "clicked",
        G_CALLBACK (e_calendar_on_next_clicked), cal);

    pixmap = gtk_arrow_new (GTK_ARROW_RIGHT, GTK_SHADOW_NONE);
    gtk_widget_show (pixmap);
    gtk_container_add (GTK_CONTAINER (button), pixmap);

    cal->next_item = gnome_canvas_item_new (
        canvas_group,
        gnome_canvas_widget_get_type (),
        "widget", button,
        NULL);
    a11y = gtk_widget_get_accessible (button);
    atk_object_set_name (a11y, _("Next month"));

    /* Create the arrow buttons to move to the previous/next year. */
    button = gtk_button_new ();
    gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NONE);
    gtk_widget_show (button);
    g_signal_connect_swapped (
        button, "pressed",
        G_CALLBACK (e_calendar_on_prev_year_pressed), cal);
    g_signal_connect_swapped (
        button, "released",
        G_CALLBACK (e_calendar_on_prev_year_released), cal);
    g_signal_connect_swapped (
        button, "clicked",
        G_CALLBACK (e_calendar_on_prev_year_clicked), cal);

    pixmap = gtk_arrow_new (GTK_ARROW_LEFT, GTK_SHADOW_NONE);
    gtk_widget_show (pixmap);
    gtk_container_add (GTK_CONTAINER (button), pixmap);

    cal->prev_item_year = gnome_canvas_item_new (
        canvas_group,
        gnome_canvas_widget_get_type (),
        "widget", button,
        NULL);
    a11y = gtk_widget_get_accessible (button);
    atk_object_set_name (a11y, _("Previous year"));

    button = gtk_button_new ();
    gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NONE);
    gtk_widget_show (button);
    g_signal_connect_swapped (
        button, "pressed",
        G_CALLBACK (e_calendar_on_next_year_pressed), cal);
    g_signal_connect_swapped (
        button, "released",
        G_CALLBACK (e_calendar_on_next_year_released), cal);
    g_signal_connect_swapped (
        button, "clicked",
        G_CALLBACK (e_calendar_on_next_year_clicked), cal);

    pixmap = gtk_arrow_new (GTK_ARROW_RIGHT, GTK_SHADOW_NONE);
    gtk_widget_show (pixmap);
    gtk_container_add (GTK_CONTAINER (button), pixmap);

    cal->next_item_year = gnome_canvas_item_new (
        canvas_group,
        gnome_canvas_widget_get_type (),
        "widget", button,
        NULL);
    a11y = gtk_widget_get_accessible (button);
    atk_object_set_name (a11y, _("Next year"));

    cal->min_rows = 1;
    cal->min_cols = 1;
    cal->max_rows = -1;
    cal->max_cols = -1;

    cal->timeout_id = 0;
}

/**
 * e_calendar_new:
 * @Returns: a new #ECalendar.
 *
 * Creates a new #ECalendar.
 **/
GtkWidget *
e_calendar_new (void)
{
    GtkWidget *cal;
    AtkObject *a11y;

    cal = g_object_new (e_calendar_get_type (), NULL);
    a11y = gtk_widget_get_accessible (cal);
    atk_object_set_name (a11y, _("Month Calendar"));

    return cal;
}

static void
e_calendar_dispose (GObject *object)
{
    ECalendar *cal;

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

    cal = E_CALENDAR (object);

    if (cal->timeout_id != 0) {
        g_source_remove (cal->timeout_id);
        cal->timeout_id = 0;
    }

    /* Chain up to parent's dispose() method. */
    G_OBJECT_CLASS (e_calendar_parent_class)->dispose (object);
}

static void
e_calendar_realize (GtkWidget *widget)
{
    GtkStyle *style;
    GdkWindow *window;

    (*GTK_WIDGET_CLASS (e_calendar_parent_class)->realize) (widget);

    /* Set the background of the canvas window to the normal color,
     * or the arrow buttons are not displayed properly. */
    style = gtk_widget_get_style (widget);
    window = gtk_layout_get_bin_window (GTK_LAYOUT (widget));
    gdk_window_set_background (window, &style->bg[GTK_STATE_NORMAL]);
}

static void
e_calendar_style_set (GtkWidget *widget,
                      GtkStyle *previous_style)
{
    ECalendar *e_calendar;

    e_calendar = E_CALENDAR (widget);
    if (GTK_WIDGET_CLASS (e_calendar_parent_class)->style_set)
        (*GTK_WIDGET_CLASS (e_calendar_parent_class)->style_set) (widget,
                                   previous_style);

    /* Set the background of the canvas window to the normal color,
     * or the arrow buttons are not displayed properly. */
    if (gtk_widget_get_realized (widget)) {
        GtkStyle *style;
        GdkWindow *window;

        style = gtk_widget_get_style (widget);
        window = gtk_layout_get_bin_window (GTK_LAYOUT (widget));
        gdk_window_set_background (window, &style->bg[GTK_STATE_NORMAL]);
    }
    e_calendar_item_style_set (widget, e_calendar->calitem);
}

static void
e_calendar_get_preferred_width (GtkWidget *widget,
                                gint *minimum,
                                gint *natural)
{
    ECalendar *cal;
    GtkStyle *style;
    gint col_width;

    cal = E_CALENDAR (widget);
    style = gtk_widget_get_style (GTK_WIDGET (cal));

    g_object_get ((cal->calitem), "column_width", &col_width, NULL);

    *minimum = *natural = col_width * cal->min_cols + style->xthickness * 2;
}

static void
e_calendar_get_preferred_height (GtkWidget *widget,
                                 gint *minimum,
                                 gint *natural)
{
    ECalendar *cal;
    GtkStyle *style;
    gint row_height;

    cal = E_CALENDAR (widget);
    style = gtk_widget_get_style (GTK_WIDGET (cal));

    g_object_get ((cal->calitem), "row_height", &row_height, NULL);

    *minimum = *natural = row_height * cal->min_rows + style->ythickness * 2;
}

static void
e_calendar_size_allocate (GtkWidget *widget,
                          GtkAllocation *allocation)
{
    ECalendar *cal;
    GtkStyle *style;
    GtkAllocation old_allocation;
    PangoFontDescription *font_desc;
    PangoContext *pango_context;
    PangoFontMetrics *font_metrics;
    gdouble old_x2, old_y2, new_x2, new_y2;
    gdouble xthickness, ythickness, arrow_button_size, current_x, month_width;
    gboolean is_rtl;

    cal = E_CALENDAR (widget);
    style = gtk_widget_get_style (widget);
    xthickness = style->xthickness;
    ythickness = style->ythickness;

    (*GTK_WIDGET_CLASS (e_calendar_parent_class)->size_allocate) (widget, allocation);

    /* Set up Pango prerequisites */
    font_desc = gtk_widget_get_style (widget)->font_desc;
    pango_context = gtk_widget_get_pango_context (widget);
    font_metrics = pango_context_get_metrics (
        pango_context, font_desc,
        pango_context_get_language (pango_context));

    /* Set the scroll region to its allocated size, if changed. */
    gnome_canvas_get_scroll_region (
        GNOME_CANVAS (cal),
        NULL, NULL, &old_x2, &old_y2);
    gtk_widget_get_allocation (widget, &old_allocation);
    new_x2 = old_allocation.width - 1;
    new_y2 = old_allocation.height - 1;
    if (old_x2 != new_x2 || old_y2 != new_y2)
        gnome_canvas_set_scroll_region (
            GNOME_CANVAS (cal),
            0, 0, new_x2, new_y2);

    /* Take off space for line & buttons if shown. */
    gnome_canvas_item_set (
        GNOME_CANVAS_ITEM (cal->calitem),
        "x1", 0.0,
        "y1", 0.0,
        "x2", new_x2,
        "y2", new_y2,
        NULL);

    if (cal->calitem->month_width > 0)
        month_width = cal->calitem->month_width;
    else
        month_width = new_x2;
    month_width -= E_CALENDAR_ITEM_MIN_CELL_XPAD + E_CALENDAR_ARROW_BUTTON_X_PAD;

    /* Position the arrow buttons. */
    arrow_button_size =
        PANGO_PIXELS (pango_font_metrics_get_ascent (font_metrics))
        + PANGO_PIXELS (pango_font_metrics_get_descent (font_metrics))
        + E_CALENDAR_ITEM_YPAD_ABOVE_MONTH_NAME
        + E_CALENDAR_ITEM_YPAD_BELOW_MONTH_NAME
        - E_CALENDAR_ARROW_BUTTON_Y_PAD * 2 - 2;

    is_rtl = gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL;
    current_x = is_rtl ?
        (month_width - 2 * xthickness - E_CALENDAR_ARROW_BUTTON_X_PAD - arrow_button_size) :
        (xthickness);

    gnome_canvas_item_set (
        cal->prev_item,
        "x", current_x,
        "y", ythickness + E_CALENDAR_ARROW_BUTTON_Y_PAD,
        "width", arrow_button_size,
        "height", arrow_button_size,
        NULL);

    current_x += (is_rtl ? -1.0 : +1.0) * (cal->calitem->max_month_name_width - xthickness + 2 * arrow_button_size);

    gnome_canvas_item_set (
        cal->next_item,
        "x", current_x,
        "y", ythickness + E_CALENDAR_ARROW_BUTTON_Y_PAD,
        "width", arrow_button_size,
        "height", arrow_button_size,
        NULL);

    current_x = is_rtl ?
        (xthickness) :
        (month_width - 2 * xthickness - E_CALENDAR_ARROW_BUTTON_X_PAD - arrow_button_size);

    gnome_canvas_item_set (
        cal->next_item_year,
        "x", current_x,
        "y", ythickness + E_CALENDAR_ARROW_BUTTON_Y_PAD,
        "width", arrow_button_size,
        "height", arrow_button_size,
        NULL);

    current_x += (is_rtl ? +1.0 : -1.0) * (cal->calitem->max_digit_width * 5 - xthickness + 2 * arrow_button_size);

    gnome_canvas_item_set (
        cal->prev_item_year,
        "x", current_x,
        "y", ythickness + E_CALENDAR_ARROW_BUTTON_Y_PAD,
        "width", arrow_button_size,
        "height", arrow_button_size,
        NULL);

    pango_font_metrics_unref (font_metrics);
}

void
e_calendar_set_minimum_size (ECalendar *cal,
                             gint rows,
                             gint cols)
{
    g_return_if_fail (E_IS_CALENDAR (cal));

    cal->min_rows = rows;
    cal->min_cols = cols;

    gnome_canvas_item_set (
        GNOME_CANVAS_ITEM (cal->calitem),
        "minimum_rows", rows,
        "minimum_columns", cols,
        NULL);

    gtk_widget_queue_resize (GTK_WIDGET (cal));
}

void
e_calendar_set_maximum_size (ECalendar *cal,
                             gint rows,
                             gint cols)
{
    g_return_if_fail (E_IS_CALENDAR (cal));

    cal->max_rows = rows;
    cal->max_cols = cols;

    gnome_canvas_item_set (
        GNOME_CANVAS_ITEM (cal->calitem),
        "maximum_rows", rows,
        "maximum_columns", cols,
        NULL);

    gtk_widget_queue_resize (GTK_WIDGET (cal));
}

/* Returns the border size on each side of the month displays. */
void
e_calendar_get_border_size (ECalendar *cal,
                            gint *top,
                            gint *bottom,
                            gint *left,
                            gint *right)
{
    GtkStyle *style;

    g_return_if_fail (E_IS_CALENDAR (cal));

    style = gtk_widget_get_style (GTK_WIDGET (cal));

    if (style) {
        *top    = style->ythickness;
        *bottom = style->ythickness;
        *left   = style->xthickness;
        *right  = style->xthickness;
    } else {
        *top = *bottom = *left = *right = 0;
    }
}

static void
e_calendar_on_prev_pressed (ECalendar *cal)
{
    e_calendar_start_auto_move (cal, FALSE);
}

static void
e_calendar_on_next_pressed (ECalendar *cal)
{
    e_calendar_start_auto_move (cal, TRUE);
}

static void
e_calendar_on_prev_year_pressed (ECalendar *cal)
{
    e_calendar_start_auto_move_year (cal, FALSE);
}

static void
e_calendar_on_next_year_pressed (ECalendar *cal)
{
    e_calendar_start_auto_move_year (cal, TRUE);
}

static void
e_calendar_start_auto_move (ECalendar *cal,
                            gboolean moving_forward)
{
    if (cal->timeout_id == 0) {
        cal->timeout_id = e_named_timeout_add (
            E_CALENDAR_AUTO_MOVE_TIMEOUT,
            e_calendar_auto_move_handler, cal);
    }

    cal->timeout_delay = E_CALENDAR_AUTO_MOVE_TIMEOUT_DELAY;
    cal->moving_forward = moving_forward;

}

static void
e_calendar_start_auto_move_year (ECalendar *cal,
                                 gboolean moving_forward)
{
    if (cal->timeout_id == 0) {
        cal->timeout_id = e_named_timeout_add (
            E_CALENDAR_AUTO_MOVE_TIMEOUT,
            e_calendar_auto_move_year_handler, cal);
    }

    cal->timeout_delay = E_CALENDAR_AUTO_MOVE_TIMEOUT_DELAY;
    cal->moving_forward = moving_forward;
}

static gboolean
e_calendar_auto_move_year_handler (gpointer data)
{
    ECalendar *cal;
    ECalendarItem *calitem;
    gint offset;

    g_return_val_if_fail (E_IS_CALENDAR (data), FALSE);

    cal = E_CALENDAR (data);
    calitem = cal->calitem;

    if (cal->timeout_delay > 0) {
        cal->timeout_delay--;
    } else {
        offset = cal->moving_forward ? 12 : -12;
        e_calendar_item_set_first_month (
            calitem, calitem->year,
            calitem->month + offset);
    }

    return TRUE;
}

static gboolean
e_calendar_auto_move_handler (gpointer data)
{
    ECalendar *cal;
    ECalendarItem *calitem;
    gint offset;

    g_return_val_if_fail (E_IS_CALENDAR (data), FALSE);

    cal = E_CALENDAR (data);
    calitem = cal->calitem;

    if (cal->timeout_delay > 0) {
        cal->timeout_delay--;
    } else {
        offset = cal->moving_forward ? 1 : -1;
        e_calendar_item_set_first_month (
            calitem, calitem->year,
            calitem->month + offset);
    }

    return TRUE;
}

static void
e_calendar_on_prev_released (ECalendar *cal)
{
    e_calendar_stop_auto_move (cal);
}

static void
e_calendar_on_next_released (ECalendar *cal)
{
    e_calendar_stop_auto_move (cal);
}

static void
e_calendar_on_prev_year_released (ECalendar *cal)
{
    e_calendar_stop_auto_move (cal);
}

static void
e_calendar_on_next_year_released (ECalendar *cal)
{
    e_calendar_stop_auto_move (cal);
}

static void
e_calendar_stop_auto_move (ECalendar *cal)
{
    if (cal->timeout_id != 0) {
        g_source_remove (cal->timeout_id);
        cal->timeout_id = 0;
    }
}

static void
e_calendar_on_prev_clicked (ECalendar *cal)
{
    e_calendar_item_set_first_month (
        cal->calitem, cal->calitem->year,
        cal->calitem->month - 1);
}

static void
e_calendar_on_next_clicked (ECalendar *cal)
{
    e_calendar_item_set_first_month (
        cal->calitem, cal->calitem->year,
        cal->calitem->month + 1);
}

static void
e_calendar_on_prev_year_clicked (ECalendar *cal)
{
    e_calendar_item_set_first_month (
        cal->calitem, cal->calitem->year,
        cal->calitem->month - 12);
}

static void
e_calendar_on_next_year_clicked (ECalendar *cal)
{
    e_calendar_item_set_first_month (
        cal->calitem, cal->calitem->year,
        cal->calitem->month + 12);
}

static gint
e_calendar_drag_motion (GtkWidget *widget,
                        GdkDragContext *context,
                        gint x,
                        gint y,
                        guint time)
{
    return FALSE;
}

static void
e_calendar_drag_leave (GtkWidget *widget,
                       GdkDragContext *context,
                       guint time)
{
}

static gboolean
e_calendar_button_has_focus (ECalendar *cal)
{
    GtkWidget *prev_widget, *next_widget;
    gboolean ret_val;

    g_return_val_if_fail (E_IS_CALENDAR (cal), FALSE);

    prev_widget = GNOME_CANVAS_WIDGET (cal->prev_item)->widget;
    next_widget = GNOME_CANVAS_WIDGET (cal->next_item)->widget;
    ret_val = gtk_widget_has_focus (prev_widget) ||
        gtk_widget_has_focus (next_widget);
    return ret_val;
}

static gboolean
e_calendar_focus (GtkWidget *widget,
                  GtkDirectionType direction)
{
#define E_CALENDAR_FOCUS_CHILDREN_NUM 5
    ECalendar *cal;
    GnomeCanvas *canvas;
    GnomeCanvasItem *children[E_CALENDAR_FOCUS_CHILDREN_NUM];
    gint focused_index = -1;
    gint index;

    g_return_val_if_fail (widget != NULL, FALSE);
    g_return_val_if_fail (E_IS_CALENDAR (widget), FALSE);
    cal = E_CALENDAR (widget);
    canvas = GNOME_CANVAS (widget);

    if (!gtk_widget_get_can_focus (widget))
        return FALSE;

    children[0] = GNOME_CANVAS_ITEM (cal->calitem);
    children[1] = cal->prev_item;
    children[2] = cal->next_item;
    children[3] = cal->prev_item_year;
    children[4] = cal->next_item_year;

    /* get current focused item, if e-calendar has had focus */
    if (gtk_widget_has_focus (widget) || e_calendar_button_has_focus (cal))
        for (index = 0; index < E_CALENDAR_FOCUS_CHILDREN_NUM; ++index) {
            if (canvas->focused_item == NULL)
                break;

            if (children[index] == canvas->focused_item) {
                focused_index = index;
                break;
            }
        }

    if (focused_index == -1)
        if (direction == GTK_DIR_TAB_FORWARD)
            focused_index = 0;
        else
            focused_index = E_CALENDAR_FOCUS_CHILDREN_NUM - 1;
    else
        if (direction == GTK_DIR_TAB_FORWARD)
            ++focused_index;
        else
            --focused_index;

    if (focused_index < 0 ||
        focused_index >= E_CALENDAR_FOCUS_CHILDREN_NUM)
        /* move out of e-calendar */
        return FALSE;
    gnome_canvas_item_grab_focus (children[focused_index]);
    if (GNOME_IS_CANVAS_WIDGET (children[focused_index])) {
        widget = GNOME_CANVAS_WIDGET (children[focused_index])->widget;
        gtk_widget_grab_focus (widget);
    }
    return TRUE;
}

void
e_calendar_set_focusable (ECalendar *cal,
                          gboolean focusable)
{
    GtkWidget *widget;
    GtkWidget *prev_widget, *next_widget;
    GtkWidget *toplevel;

    g_return_if_fail (E_IS_CALENDAR (cal));

    widget = GTK_WIDGET (cal);
    prev_widget = GNOME_CANVAS_WIDGET (cal->prev_item)->widget;
    next_widget = GNOME_CANVAS_WIDGET (cal->next_item)->widget;

    if (focusable) {
        gtk_widget_set_can_focus (widget, TRUE);
        gtk_widget_set_can_focus (prev_widget, TRUE);
        gtk_widget_set_can_focus (next_widget, TRUE);
    }
    else {
        if (gtk_widget_has_focus (GTK_WIDGET (cal)) ||
            e_calendar_button_has_focus (cal)) {
            toplevel = gtk_widget_get_toplevel (widget);
            if (toplevel)
                gtk_widget_grab_focus (toplevel);
        }
        gtk_widget_set_can_focus (widget, FALSE);
        gtk_widget_set_can_focus (prev_widget, FALSE);
        gtk_widget_set_can_focus (next_widget, FALSE);
    }
}