aboutsummaryrefslogblamecommitdiffstats
path: root/e-util/ea-calendar-item.c
blob: fe52134891eeae9fe66f29214bff050b58aeab19 (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:
 *      Bolian Yin <bolian.yin@sun.com>
 *
 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
 *
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <stdio.h>
#include <time.h>
#include <string.h>
#include <libgnomecanvas/gnome-canvas.h>
#include <glib/gi18n.h>

#include <libedataserver/libedataserver.h>

#include "ea-calendar-item.h"
#include "ea-calendar-cell.h"
#include "ea-cell-table.h"

#include "e-misc-utils.h"

#define EA_CALENDAR_COLUMN_NUM E_CALENDAR_COLS_PER_MONTH

/* EaCalendarItem */
static void ea_calendar_item_class_init (EaCalendarItemClass *class);
static void ea_calendar_item_finalize (GObject *object);

static const gchar * ea_calendar_item_get_name (AtkObject *accessible);
static const gchar * ea_calendar_item_get_description (AtkObject *accessible);
static gint ea_calendar_item_get_n_children (AtkObject *accessible);
static AtkObject *ea_calendar_item_ref_child (AtkObject *accessible, gint index);
static AtkStateSet * ea_calendar_item_ref_state_set (AtkObject *accessible);

/* atk table interface */
static void atk_table_interface_init (AtkTableIface *iface);
static gint table_interface_get_index_at (AtkTable *table,
                      gint     row,
                      gint     column);
static gint table_interface_get_column_at_index (AtkTable *table,
                         gint     index);
static gint table_interface_get_row_at_index (AtkTable *table,
                          gint     index);
static AtkObject * table_interface_ref_at (AtkTable *table,
                      gint     row,
                      gint     column);
static gint table_interface_get_n_rows (AtkTable *table);
static gint table_interface_get_n_columns (AtkTable *table);
static gint table_interface_get_column_extent_at (AtkTable      *table,
                          gint          row,
                          gint          column);
static gint table_interface_get_row_extent_at (AtkTable      *table,
                           gint          row,
                           gint          column);

static gboolean table_interface_is_row_selected (AtkTable *table,
                         gint     row);
static gboolean table_interface_is_column_selected (AtkTable *table,
                            gint     row);
static gboolean table_interface_is_selected (AtkTable *table,
                         gint     row,
                         gint     column);
static gint table_interface_get_selected_rows (AtkTable *table,
                           gint **rows_selected);
static gint table_interface_get_selected_columns (AtkTable *table,
                          gint     **columns_selected);
static gboolean table_interface_add_row_selection (AtkTable *table, gint row);
static gboolean table_interface_remove_row_selection (AtkTable *table,
                              gint row);
static gboolean table_interface_add_column_selection (AtkTable *table,
                              gint column);
static gboolean table_interface_remove_column_selection (AtkTable *table,
                             gint column);
static AtkObject * table_interface_get_row_header (AtkTable *table, gint row);
static AtkObject * table_interface_get_column_header (AtkTable *table,
                             gint in_col);
static AtkObject * table_interface_get_caption (AtkTable *table);

static const gchar *
table_interface_get_column_description (AtkTable *table,
                                        gint in_col);

static const gchar *
table_interface_get_row_description (AtkTable *table,
                                     gint row);

static AtkObject *table_interface_get_summary (AtkTable *table);

/* atk selection interface */
static void atk_selection_interface_init (AtkSelectionIface *iface);
static gboolean selection_interface_add_selection (AtkSelection *selection,
                                                   gint i);
static gboolean selection_interface_clear_selection (AtkSelection *selection);
static AtkObject *selection_interface_ref_selection (AtkSelection *selection,
                                                     gint i);
static gint selection_interface_get_selection_count (AtkSelection *selection);
static gboolean selection_interface_is_child_selected (AtkSelection *selection,
                                                       gint i);

/* callbacks */
static void selection_preview_change_cb (ECalendarItem *calitem);
static void date_range_changed_cb (ECalendarItem *calitem);

/* helpers */
static EaCellTable *ea_calendar_item_get_cell_data (EaCalendarItem *ea_calitem);
static void ea_calendar_item_destory_cell_data (EaCalendarItem *ea_calitem);
static gboolean ea_calendar_item_get_column_label (EaCalendarItem *ea_calitem,
                                                   gint column,
                                                   gchar *buffer,
                                                   gint buffer_size);
static gboolean ea_calendar_item_get_row_label (EaCalendarItem *ea_calitem,
                                                gint row,
                                                gchar *buffer,
                                                gint buffer_size);
static gboolean e_calendar_item_get_offset_for_date (ECalendarItem *calitem,
                                                     gint year,
                                                     gint month,
                                                     gint day,
                                                     gint *offset);
static void ea_calendar_set_focus_object (EaCalendarItem *ea_calitem,
                                          AtkObject *item_cell);

#ifdef ACC_DEBUG
static gint n_ea_calendar_item_created = 0;
static gint n_ea_calendar_item_destroyed = 0;
#endif

static gpointer parent_class = NULL;

GType
ea_calendar_item_get_type (void)
{
    static GType type = 0;
    AtkObjectFactory *factory;
    GTypeQuery query;
    GType derived_atk_type;

    if (!type) {
        static GTypeInfo tinfo = {
            sizeof (EaCalendarItemClass),
            (GBaseInitFunc) NULL, /* base init */
            (GBaseFinalizeFunc) NULL, /* base finalize */
            (GClassInitFunc) ea_calendar_item_class_init, /* class init */
            (GClassFinalizeFunc) NULL, /* class finalize */
            NULL, /* class data */
            sizeof (EaCalendarItem), /* instance size */
            0, /* nb preallocs */
            (GInstanceInitFunc) NULL, /* instance init */
            NULL /* value table */
        };

        static const GInterfaceInfo atk_table_info = {
            (GInterfaceInitFunc) atk_table_interface_init,
            (GInterfaceFinalizeFunc) NULL,
            NULL
        };
        static const GInterfaceInfo atk_selection_info = {
            (GInterfaceInitFunc) atk_selection_interface_init,
            (GInterfaceFinalizeFunc) NULL,
            NULL
        };

        /*
         * Figure out the size of the class and instance
         * we are run-time deriving from (GailCanvasItem, in this case)
         */

        factory = atk_registry_get_factory (
            atk_get_default_registry (),
            GNOME_TYPE_CANVAS_ITEM);
        derived_atk_type = atk_object_factory_get_accessible_type (factory);
        g_type_query (derived_atk_type, &query);

        tinfo.class_size = query.class_size;
        tinfo.instance_size = query.instance_size;

        type = g_type_register_static (
            derived_atk_type,
            "EaCalendarItem", &tinfo, 0);
        g_type_add_interface_static (
            type, ATK_TYPE_TABLE,
            &atk_table_info);
        g_type_add_interface_static (
            type, ATK_TYPE_SELECTION,
            &atk_selection_info);
    }

    return type;
}

static void
ea_calendar_item_class_init (EaCalendarItemClass *klass)
{
    GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
    AtkObjectClass *class = ATK_OBJECT_CLASS (klass);

    gobject_class->finalize = ea_calendar_item_finalize;
    parent_class = g_type_class_peek_parent (klass);

    class->get_name = ea_calendar_item_get_name;
    class->get_description = ea_calendar_item_get_description;
    class->ref_state_set = ea_calendar_item_ref_state_set;

    class->get_n_children = ea_calendar_item_get_n_children;
    class->ref_child = ea_calendar_item_ref_child;
}

AtkObject *
ea_calendar_item_new (GObject *obj)
{
    gpointer object;
    AtkObject *atk_object;
    AtkObject *item_cell;

    g_return_val_if_fail (E_IS_CALENDAR_ITEM (obj), NULL);
    object = g_object_new (EA_TYPE_CALENDAR_ITEM, NULL);
    atk_object = ATK_OBJECT (object);
    atk_object_initialize (atk_object, obj);
    atk_object->role = ATK_ROLE_CALENDAR;

    item_cell = atk_selection_ref_selection (
        ATK_SELECTION (atk_object), 0);
    if (item_cell)
        ea_calendar_set_focus_object (EA_CALENDAR_ITEM (atk_object), item_cell);

#ifdef ACC_DEBUG
    ++n_ea_calendar_item_created;
    g_print (
        "ACC_DEBUG: n_ea_calendar_item_created = %d\n",
        n_ea_calendar_item_created);
#endif
    /* connect signal handlers */
    g_signal_connect (
        obj, "selection_preview_changed",
        G_CALLBACK (selection_preview_change_cb), atk_object);
    g_signal_connect (
        obj, "date_range_changed",
        G_CALLBACK (date_range_changed_cb), atk_object);

    return atk_object;
}

static void
ea_calendar_item_finalize (GObject *object)
{
    EaCalendarItem *ea_calitem;

    g_return_if_fail (EA_IS_CALENDAR_ITEM (object));

    ea_calitem = EA_CALENDAR_ITEM (object);

    /* Free the allocated cell data */
    ea_calendar_item_destory_cell_data (ea_calitem);

    G_OBJECT_CLASS (parent_class)->finalize (object);
#ifdef ACC_DEBUG
    ++n_ea_calendar_item_destroyed;
    printf (
        "ACC_DEBUG: n_ea_calendar_item_destroyed = %d\n",
        n_ea_calendar_item_destroyed);
#endif
}

static const gchar *
ea_calendar_item_get_name (AtkObject *accessible)
{
    GObject *g_obj;
    ECalendarItem *calitem;
    gint start_year, start_month, start_day;
    gint end_year, end_month, end_day;
    gchar *name_str = NULL;
    gchar buffer_start[128] = "";
    gchar buffer_end[128] = "";
    struct tm day_start = { 0 };
    struct tm day_end = { 0 };

    g_return_val_if_fail (EA_IS_CALENDAR_ITEM (accessible), NULL);

    g_obj = atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (accessible));
    if (!g_obj)
        return NULL;
    g_return_val_if_fail (E_IS_CALENDAR_ITEM (g_obj), NULL);

    calitem = E_CALENDAR_ITEM (g_obj);
    if (e_calendar_item_get_date_range (
        calitem,
        &start_year, &start_month, &start_day,
        &end_year, &end_month, &end_day)) {

        day_start.tm_year = start_year - 1900;
        day_start.tm_mon = start_month;
        day_start.tm_mday = start_day;
        day_start.tm_isdst = -1;

        e_utf8_strftime (
            buffer_start, sizeof (buffer_start),
            _("%d %B %Y"), &day_start);

        day_end.tm_year = end_year - 1900;
        day_end.tm_mon = end_month;
        day_end.tm_mday = end_day;
        day_end.tm_isdst = -1;

        e_utf8_strftime (
            buffer_end, sizeof (buffer_end),
            _("%d %B %Y"), &day_end);

        name_str = g_strdup_printf (
            _("Calendar: from %s to %s"),
            buffer_start, buffer_end);
    }

#if 0
    if (e_calendar_item_get_selection (calitem, &select_start, &select_end)) {
        GDate select_start, select_end;
        gint year1, year2, month1, month2, day1, day2;

        year1 = g_date_get_year (&select_start);
        month1 = g_date_get_month (&select_start);
        day1 = g_date_get_day (&select_start);

        year2 = g_date_get_year (&select_end);
        month2 = g_date_get_month (&select_end);
        day2 = g_date_get_day (&select_end);

        sprintf (
            new_name + strlen (new_name),
            " : current selection: from %d-%d-%d to %d-%d-%d.",
            year1, month1, day1,
            year2, month2, day2);
    }
#endif

    ATK_OBJECT_CLASS (parent_class)->set_name (accessible, name_str);
    g_free (name_str);

    return accessible->name;
}

static const gchar *
ea_calendar_item_get_description (AtkObject *accessible)
{
    if (accessible->description)
        return accessible->description;

    return _("evolution calendar item");
}

static AtkStateSet *
ea_calendar_item_ref_state_set (AtkObject *accessible)
{
    AtkStateSet *state_set;
    GObject *g_obj;

    state_set = ATK_OBJECT_CLASS (parent_class)->ref_state_set (accessible);
    g_obj = atk_gobject_accessible_get_object (
        ATK_GOBJECT_ACCESSIBLE (accessible));
    if (!g_obj)
        return state_set;

    atk_state_set_add_state (state_set, ATK_STATE_ENABLED);
    atk_state_set_add_state (state_set, ATK_STATE_SENSITIVE);

    return state_set;
}

static gint
ea_calendar_item_get_n_children (AtkObject *accessible)
{
    AtkGObjectAccessible *atk_gobj;
    GObject *g_obj;
    ECalendarItem *calitem;
    gint n_children = 0;
    gint start_year, start_month, start_day;
    gint end_year, end_month, end_day;
    GDate *start_date, *end_date;

    g_return_val_if_fail (EA_IS_CALENDAR_ITEM (accessible), -1);

    atk_gobj = ATK_GOBJECT_ACCESSIBLE (accessible);
    g_obj = atk_gobject_accessible_get_object (atk_gobj);
    if (!g_obj)
        return -1;

    calitem = E_CALENDAR_ITEM (g_obj);
    if (!e_calendar_item_get_date_range (calitem, &start_year,
                         &start_month, &start_day,
                         &end_year, &end_month,
                         &end_day))
        return 0;

    start_date = g_date_new_dmy (start_day, start_month + 1, start_year);
    end_date = g_date_new_dmy (end_day, end_month + 1, end_year);

    n_children = g_date_days_between (start_date, end_date) + 1;
    g_free (start_date);
    g_free (end_date);
    return n_children;
}

static AtkObject *
ea_calendar_item_ref_child (AtkObject *accessible,
                            gint index)
{
    AtkGObjectAccessible *atk_gobj;
    GObject *g_obj;
    ECalendarItem *calitem;
    gint n_children;
    ECalendarCell *cell;
    EaCellTable *cell_data;
    EaCalendarItem *ea_calitem;

    g_return_val_if_fail (EA_IS_CALENDAR_ITEM (accessible), NULL);

    atk_gobj = ATK_GOBJECT_ACCESSIBLE (accessible);
    g_obj = atk_gobject_accessible_get_object (atk_gobj);
    if (!g_obj)
        return NULL;

    calitem = E_CALENDAR_ITEM (g_obj);

    n_children = ea_calendar_item_get_n_children (accessible);
    if (index < 0 || index >= n_children)
        return NULL;

    ea_calitem = EA_CALENDAR_ITEM (accessible);
    cell_data = ea_calendar_item_get_cell_data (ea_calitem);
    if (!cell_data)
        return NULL;

    cell = ea_cell_table_get_cell_at_index (cell_data, index);
    if (!cell) {
        cell = e_calendar_cell_new (
            calitem,
            index / EA_CALENDAR_COLUMN_NUM,
            index % EA_CALENDAR_COLUMN_NUM);
        ea_cell_table_set_cell_at_index (cell_data, index, cell);
        g_object_unref (cell);
    }

#ifdef ACC_DEBUG
    g_print (
        "AccDebug: ea_calendar_item children[%d]=%p\n", index,
        (gpointer) cell);
#endif
    return g_object_ref (atk_gobject_accessible_for_object (G_OBJECT (cell)));
}

/* atk table interface */

static void
atk_table_interface_init (AtkTableIface *iface)
{
    g_return_if_fail (iface != NULL);

    iface->ref_at = table_interface_ref_at;

    iface->get_n_rows = table_interface_get_n_rows;
    iface->get_n_columns = table_interface_get_n_columns;
    iface->get_index_at = table_interface_get_index_at;
    iface->get_column_at_index = table_interface_get_column_at_index;
    iface->get_row_at_index = table_interface_get_row_at_index;
    iface->get_column_extent_at = table_interface_get_column_extent_at;
    iface->get_row_extent_at = table_interface_get_row_extent_at;

    iface->is_selected = table_interface_is_selected;
    iface->get_selected_rows = table_interface_get_selected_rows;
    iface->get_selected_columns = table_interface_get_selected_columns;
    iface->is_row_selected = table_interface_is_row_selected;
    iface->is_column_selected = table_interface_is_column_selected;
    iface->add_row_selection = table_interface_add_row_selection;
    iface->remove_row_selection = table_interface_remove_row_selection;
    iface->add_column_selection = table_interface_add_column_selection;
    iface->remove_column_selection = table_interface_remove_column_selection;

    iface->get_row_header = table_interface_get_row_header;
    iface->get_column_header = table_interface_get_column_header;
    iface->get_caption = table_interface_get_caption;
    iface->get_summary = table_interface_get_summary;
    iface->get_row_description = table_interface_get_row_description;
    iface->get_column_description = table_interface_get_column_description;
}

static AtkObject *
table_interface_ref_at (AtkTable *table,
                        gint row,
                        gint column)
{
    gint index;

    EaCalendarItem * ea_calitem = EA_CALENDAR_ITEM (table);
    index = EA_CALENDAR_COLUMN_NUM * row + column;
    return ea_calendar_item_ref_child (ATK_OBJECT (ea_calitem), index);
}

static gint
table_interface_get_n_rows (AtkTable *table)
{
    AtkGObjectAccessible *atk_gobj;
    GObject *g_obj;
    EaCalendarItem * ea_calitem = EA_CALENDAR_ITEM (table);
    gint n_children;

    atk_gobj = ATK_GOBJECT_ACCESSIBLE (ea_calitem);
    g_obj = atk_gobject_accessible_get_object (atk_gobj);
    if (!g_obj)
        return -1;

    n_children = ea_calendar_item_get_n_children (ATK_OBJECT (ea_calitem));
    return (n_children - 1) / EA_CALENDAR_COLUMN_NUM + 1;
}

static gint
table_interface_get_n_columns (AtkTable *table)
{
    AtkGObjectAccessible *atk_gobj;
    GObject *g_obj;
    EaCalendarItem * ea_calitem = EA_CALENDAR_ITEM (table);

    atk_gobj = ATK_GOBJECT_ACCESSIBLE (ea_calitem);
    g_obj = atk_gobject_accessible_get_object (atk_gobj);
    if (!g_obj)
        return -1;

    return EA_CALENDAR_COLUMN_NUM;
}

static gint
table_interface_get_index_at (AtkTable *table,
                              gint row,
                              gint column)
{
    AtkGObjectAccessible *atk_gobj;
    GObject *g_obj;
    EaCalendarItem * ea_calitem = EA_CALENDAR_ITEM (table);

    atk_gobj = ATK_GOBJECT_ACCESSIBLE (ea_calitem);
    g_obj = atk_gobject_accessible_get_object (atk_gobj);
    if (!g_obj)
        return -1;

    return row * EA_CALENDAR_COLUMN_NUM + column;
}

static gint
table_interface_get_column_at_index (AtkTable *table,
                                     gint index)
{
    AtkGObjectAccessible *atk_gobj;
    GObject *g_obj;
    EaCalendarItem * ea_calitem = EA_CALENDAR_ITEM (table);
    gint n_children;

    atk_gobj = ATK_GOBJECT_ACCESSIBLE (ea_calitem);
    g_obj = atk_gobject_accessible_get_object (atk_gobj);
    if (!g_obj)
        return -1;

    n_children = ea_calendar_item_get_n_children (ATK_OBJECT (ea_calitem));
    if (index >= 0 && index < n_children)
        return index % EA_CALENDAR_COLUMN_NUM;
    return -1;
}

static gint
table_interface_get_row_at_index (AtkTable *table,
                                  gint index)
{
    AtkGObjectAccessible *atk_gobj;
    GObject *g_obj;
    EaCalendarItem * ea_calitem = EA_CALENDAR_ITEM (table);
    gint n_children;

    atk_gobj = ATK_GOBJECT_ACCESSIBLE (ea_calitem);
    g_obj = atk_gobject_accessible_get_object (atk_gobj);
    if (!g_obj)
        return -1;

    n_children = ea_calendar_item_get_n_children (ATK_OBJECT (ea_calitem));
    if (index >= 0 && index < n_children)
        return index / EA_CALENDAR_COLUMN_NUM;
    return -1;
}

static gint
table_interface_get_column_extent_at (AtkTable *table,
                                      gint row,
                                      gint column)
{
    AtkGObjectAccessible *atk_gobj;
    GObject *g_obj;
    ECalendarItem *calitem;
    EaCalendarItem * ea_calitem = EA_CALENDAR_ITEM (table);

    atk_gobj = ATK_GOBJECT_ACCESSIBLE (ea_calitem);
    g_obj = atk_gobject_accessible_get_object (atk_gobj);
    if (!g_obj)
        return FALSE;

    calitem = E_CALENDAR_ITEM (g_obj);
    return calitem->cell_width;
}

static gint
table_interface_get_row_extent_at (AtkTable *table,
                                   gint row,
                                   gint column)
{
    AtkGObjectAccessible *atk_gobj;
    GObject *g_obj;
    ECalendarItem *calitem;
    EaCalendarItem * ea_calitem = EA_CALENDAR_ITEM (table);

    atk_gobj = ATK_GOBJECT_ACCESSIBLE (ea_calitem);
    g_obj = atk_gobject_accessible_get_object (atk_gobj);
    if (!g_obj)
        return FALSE;

    calitem = E_CALENDAR_ITEM (g_obj);
    return calitem->cell_height;
}

/* any day in the row is selected, the row is selected */
static gboolean
table_interface_is_row_selected (AtkTable *table,
                                 gint row)
{
    AtkGObjectAccessible *atk_gobj;
    GObject *g_obj;
    gint n_rows;
    ECalendarItem *calitem;
    gint row_index_start, row_index_end;
    gint sel_index_start, sel_index_end;

    GDate start_date, end_date;

    g_return_val_if_fail (EA_IS_CALENDAR_ITEM (table), FALSE);

    atk_gobj = ATK_GOBJECT_ACCESSIBLE (table);
    g_obj = atk_gobject_accessible_get_object (atk_gobj);
    if (!g_obj)
        return FALSE;

    n_rows = table_interface_get_n_rows (table);
    if (row < 0 || row >= n_rows)
        return FALSE;

    row_index_start = row * EA_CALENDAR_COLUMN_NUM;
    row_index_end = row_index_start + EA_CALENDAR_COLUMN_NUM - 1;

    calitem = E_CALENDAR_ITEM (g_obj);
    if (!e_calendar_item_get_selection (calitem, &start_date, &end_date))
        return FALSE;

    e_calendar_item_get_offset_for_date (calitem,
                         g_date_get_year (&start_date),
                         g_date_get_month (&start_date),
                         g_date_get_day (&start_date),
                         &sel_index_start);
    e_calendar_item_get_offset_for_date (calitem,
                         g_date_get_year (&end_date),
                         g_date_get_month (&end_date),
                         g_date_get_day (&end_date),
                         &sel_index_end);

    if ((sel_index_start < row_index_start &&
         sel_index_end >= row_index_start) ||
        (sel_index_start >= row_index_start &&
         sel_index_start <= row_index_end))
        return TRUE;
    return FALSE;
}

static gboolean
table_interface_is_selected (AtkTable *table,
                             gint row,
                             gint column)
{
    AtkGObjectAccessible *atk_gobj;
    GObject *g_obj;
    gint n_rows, n_columns;
    ECalendarItem *calitem;
    gint index;
    gint sel_index_start, sel_index_end;

    GDate start_date, end_date;

    g_return_val_if_fail (EA_IS_CALENDAR_ITEM (table), FALSE);

    atk_gobj = ATK_GOBJECT_ACCESSIBLE (table);
    g_obj = atk_gobject_accessible_get_object (atk_gobj);
    if (!g_obj)
        return FALSE;

    n_rows = table_interface_get_n_rows (table);
    if (row < 0 || row >= n_rows)
        return FALSE;
    n_columns = table_interface_get_n_columns (table);
    if (column < 0 || column >= n_columns)
        return FALSE;

    index = table_interface_get_index_at (table, row, column);

    calitem = E_CALENDAR_ITEM (g_obj);
    if (!e_calendar_item_get_selection (calitem, &start_date, &end_date))
        return FALSE;

    e_calendar_item_get_offset_for_date (calitem,
                         g_date_get_year (&start_date),
                         g_date_get_month (&start_date),
                         g_date_get_day (&start_date),
                         &sel_index_start);
    e_calendar_item_get_offset_for_date (calitem,
                         g_date_get_year (&end_date),
                         g_date_get_month (&end_date),
                         g_date_get_day (&end_date), &sel_index_end);

    if (sel_index_start <= index && sel_index_end >= index)
        return TRUE;
    return FALSE;
}

static gboolean
table_interface_is_column_selected (AtkTable *table,
                                    gint column)
{
    return FALSE;
}

static gint
table_interface_get_selected_rows (AtkTable *table,
                                   gint **rows_selected)
{
    *rows_selected = NULL;
    return -1;
}

static gint
table_interface_get_selected_columns (AtkTable *table,
                                      gint **columns_selected)
{
    *columns_selected = NULL;
    return -1;
}

static gboolean
table_interface_add_row_selection (AtkTable *table,
                                   gint row)
{
    return FALSE;
}

static gboolean
table_interface_remove_row_selection (AtkTable *table,
                                      gint row)
{
    return FALSE;
}

static gboolean
table_interface_add_column_selection (AtkTable *table,
                                      gint column)
{
    return FALSE;
}

static gboolean
table_interface_remove_column_selection (AtkTable *table,
                                         gint column)
{
    /* FIXME: NOT IMPLEMENTED */
    return FALSE;
}

static AtkObject *
table_interface_get_row_header (AtkTable *table,
                                gint row)
{
    /* FIXME: NOT IMPLEMENTED */
    return NULL;
}

static AtkObject *
table_interface_get_column_header (AtkTable *table,
                                   gint in_col)
{
    /* FIXME: NOT IMPLEMENTED */
    return NULL;
}

static AtkObject *
table_interface_get_caption (AtkTable *table)
{
    /* FIXME: NOT IMPLEMENTED */
    return NULL;
}

static const gchar *
table_interface_get_column_description (AtkTable *table,
                                        gint in_col)
{
    AtkGObjectAccessible *atk_gobj;
    GObject *g_obj;
    EaCalendarItem * ea_calitem = EA_CALENDAR_ITEM (table);
    const gchar *description = NULL;
    EaCellTable *cell_data;
    gint n_columns;

    atk_gobj = ATK_GOBJECT_ACCESSIBLE (ea_calitem);
    g_obj = atk_gobject_accessible_get_object (atk_gobj);
    if (!g_obj)
        return NULL;

    n_columns = table_interface_get_n_columns (table);
    if (in_col < 0 || in_col >= n_columns)
        return NULL;
    cell_data = ea_calendar_item_get_cell_data (ea_calitem);
    if (!cell_data)
        return NULL;

    description = ea_cell_table_get_column_label (cell_data, in_col);
    if (!description) {
        gchar buffer[128] = "column description";
        ea_calendar_item_get_column_label (
            ea_calitem, in_col,
            buffer, sizeof (buffer));
        ea_cell_table_set_column_label (cell_data, in_col, buffer);
        description = ea_cell_table_get_column_label (
            cell_data, in_col);
    }
    return description;
}

static const gchar *
table_interface_get_row_description (AtkTable *table,
                                     gint row)
{
    AtkGObjectAccessible *atk_gobj;
    GObject *g_obj;
    EaCalendarItem * ea_calitem = EA_CALENDAR_ITEM (table);
    const gchar *description = NULL;
    EaCellTable *cell_data;
    gint n_rows;

    atk_gobj = ATK_GOBJECT_ACCESSIBLE (ea_calitem);
    g_obj = atk_gobject_accessible_get_object (atk_gobj);
    if (!g_obj)
        return NULL;

    n_rows = table_interface_get_n_rows (table);
    if (row < 0 || row >= n_rows)
        return NULL;
    cell_data = ea_calendar_item_get_cell_data (ea_calitem);
    if (!cell_data)
        return NULL;

    description = ea_cell_table_get_row_label (cell_data, row);
    if (!description) {
        gchar buffer[128] = "row description";
        ea_calendar_item_get_row_label (
            ea_calitem, row,
                        buffer, sizeof (buffer));
        ea_cell_table_set_row_label (cell_data, row, buffer);
        description = ea_cell_table_get_row_label (
            cell_data,
            row);
    }
    return description;
}

static AtkObject *
table_interface_get_summary (AtkTable *table)
{
    /* FIXME: NOT IMPLEMENTED */
    return NULL;
}

/* atkselection interface */

static void
atk_selection_interface_init (AtkSelectionIface *iface)
{
    g_return_if_fail (iface != NULL);

    iface->add_selection = selection_interface_add_selection;
    iface->clear_selection = selection_interface_clear_selection;
    iface->ref_selection = selection_interface_ref_selection;
    iface->get_selection_count = selection_interface_get_selection_count;
    iface->is_child_selected = selection_interface_is_child_selected;
}

static gboolean
selection_interface_add_selection (AtkSelection *selection,
                                   gint index)
{
    AtkGObjectAccessible *atk_gobj;
    GObject *g_obj;
    ECalendarItem *calitem;
    EaCalendarItem * ea_calitem = EA_CALENDAR_ITEM (selection);
    gint year, month, day;
    GDate start_date, end_date;

    atk_gobj = ATK_GOBJECT_ACCESSIBLE (ea_calitem);
    g_obj = atk_gobject_accessible_get_object (atk_gobj);
    if (!g_obj)
        return FALSE;

    calitem = E_CALENDAR_ITEM (g_obj);
    if (!e_calendar_item_get_date_for_offset (calitem, index,
                          &year, &month, &day))
        return FALSE;

    /* FIXME: not support mulit-selection */
    g_date_set_dmy (&start_date, day, month + 1, year);
    end_date = start_date;
    e_calendar_item_set_selection (calitem, &start_date, &end_date);
    return TRUE;
}

static gboolean
selection_interface_clear_selection (AtkSelection *selection)
{
    AtkGObjectAccessible *atk_gobj;
    GObject *g_obj;
    ECalendarItem *calitem;
    EaCalendarItem * ea_calitem = EA_CALENDAR_ITEM (selection);

    atk_gobj = ATK_GOBJECT_ACCESSIBLE (ea_calitem);
    g_obj = atk_gobject_accessible_get_object (atk_gobj);
    if (!g_obj)
        return FALSE;

    calitem = E_CALENDAR_ITEM (g_obj);
    e_calendar_item_set_selection (calitem, NULL, NULL);

    return TRUE;
}

static AtkObject *
selection_interface_ref_selection (AtkSelection *selection,
                                   gint i)
{
    GObject *g_obj;
    ECalendarItem *calitem;
    EaCalendarItem * ea_calitem = EA_CALENDAR_ITEM (selection);
    gint count, sel_offset;
    GDate start_date, end_date;

    count = selection_interface_get_selection_count (selection);
    if (i < 0 || i >= count)
        return NULL;

    g_obj = atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (ea_calitem));

    calitem = E_CALENDAR_ITEM (g_obj);
    if (!e_calendar_item_get_selection (calitem, &start_date, &end_date))
        return NULL;
    if (!e_calendar_item_get_offset_for_date (calitem,
                          g_date_get_year (&start_date),
                          g_date_get_month (&start_date) - 1,
                          g_date_get_day (&start_date),
                          &sel_offset))
        return NULL;

    return ea_calendar_item_ref_child (ATK_OBJECT (selection), sel_offset + i);
}

static gint
selection_interface_get_selection_count (AtkSelection *selection)
{
    AtkGObjectAccessible *atk_gobj;
    GObject *g_obj;
    ECalendarItem *calitem;
    EaCalendarItem * ea_calitem = EA_CALENDAR_ITEM (selection);
    GDate start_date, end_date;

    atk_gobj = ATK_GOBJECT_ACCESSIBLE (ea_calitem);
    g_obj = atk_gobject_accessible_get_object (atk_gobj);
    if (!g_obj)
        return 0;

    calitem = E_CALENDAR_ITEM (g_obj);
    if (e_calendar_item_get_selection (calitem, &start_date, &end_date))
        return g_date_days_between (&start_date, &end_date) + 1;
    else
        return 0;
}

static gboolean
selection_interface_is_child_selected (AtkSelection *selection,
                                       gint index)
{
    AtkGObjectAccessible *atk_gobj;
    GObject *g_obj;
    EaCalendarItem * ea_calitem = EA_CALENDAR_ITEM (selection);
    gint row, column, n_children;

    atk_gobj = ATK_GOBJECT_ACCESSIBLE (ea_calitem);
    g_obj = atk_gobject_accessible_get_object (atk_gobj);
    if (!g_obj)
        return FALSE;

    n_children = atk_object_get_n_accessible_children (ATK_OBJECT (selection));
    if (index < 0 || index >= n_children)
        return FALSE;

    row = index / EA_CALENDAR_COLUMN_NUM;
    column = index % EA_CALENDAR_COLUMN_NUM;

    return table_interface_is_selected (ATK_TABLE (selection), row, column);
}

/* callbacks */

static void
selection_preview_change_cb (ECalendarItem *calitem)
{
    AtkObject *atk_obj;
    AtkObject *item_cell;

    g_return_if_fail (E_IS_CALENDAR_ITEM (calitem));
    atk_obj = atk_gobject_accessible_for_object (G_OBJECT (calitem));
    ea_calendar_item_destory_cell_data (EA_CALENDAR_ITEM (atk_obj));

    /* only deal with the first selected child, for now */
    item_cell = atk_selection_ref_selection (
        ATK_SELECTION (atk_obj), 0);

    if (item_cell)
        ea_calendar_set_focus_object (EA_CALENDAR_ITEM (atk_obj), item_cell);

    g_signal_emit_by_name (
        atk_obj,
        "active-descendant-changed",
        item_cell);
    g_signal_emit_by_name (atk_obj, "selection_changed");
}

static void
date_range_changed_cb (ECalendarItem *calitem)
{
    AtkObject *atk_obj;
    AtkObject *item_cell;

    g_return_if_fail (E_IS_CALENDAR_ITEM (calitem));
    atk_obj = atk_gobject_accessible_for_object (G_OBJECT (calitem));
    ea_calendar_item_destory_cell_data (EA_CALENDAR_ITEM (atk_obj));

    item_cell = atk_selection_ref_selection (
        ATK_SELECTION (atk_obj), 0);
    if (item_cell)
        ea_calendar_set_focus_object (EA_CALENDAR_ITEM (atk_obj), item_cell);

    g_signal_emit_by_name (atk_obj, "model_changed");
}

/* helpers */

static EaCellTable *
ea_calendar_item_get_cell_data (EaCalendarItem *ea_calitem)
{
    AtkGObjectAccessible *atk_gobj;
    GObject *g_obj;
    EaCellTable *cell_data;

    g_return_val_if_fail (ea_calitem, NULL);

    atk_gobj = ATK_GOBJECT_ACCESSIBLE (ea_calitem);
    g_obj = atk_gobject_accessible_get_object (atk_gobj);
    if (!g_obj)
        return NULL;

    cell_data = g_object_get_data (
        G_OBJECT (ea_calitem),
        "ea-calendar-cell-table");

    if (!cell_data) {
        gint n_cells = ea_calendar_item_get_n_children (ATK_OBJECT (ea_calitem));
        cell_data = ea_cell_table_create (
            n_cells / EA_CALENDAR_COLUMN_NUM,
            EA_CALENDAR_COLUMN_NUM,
            FALSE);
        g_object_set_data (
            G_OBJECT (ea_calitem),
            "ea-calendar-cell-table", cell_data);
    }
    return cell_data;
}

static void
ea_calendar_item_destory_cell_data (EaCalendarItem *ea_calitem)
{
    EaCellTable *cell_data;

    g_return_if_fail (ea_calitem);

    cell_data = g_object_get_data (
        G_OBJECT (ea_calitem),
        "ea-calendar-cell-table");
    if (cell_data) {
        g_object_set_data (
            G_OBJECT (ea_calitem),
            "ea-calendar-cell-table", NULL);
        ea_cell_table_destroy (cell_data);
    }
}

static gboolean
ea_calendar_item_get_row_label (EaCalendarItem *ea_calitem,
                                gint row,
                                gchar *buffer,
                                gint buffer_size)
{
    AtkGObjectAccessible *atk_gobj;
    GObject *g_obj;
    ECalendarItem *calitem;
    gint index, week_num;
    gint year, month, day;

    g_return_val_if_fail (ea_calitem, FALSE);

    atk_gobj = ATK_GOBJECT_ACCESSIBLE (ea_calitem);
    g_obj = atk_gobject_accessible_get_object (atk_gobj);
    if (!g_obj)
        return FALSE;

    calitem = E_CALENDAR_ITEM (g_obj);

    index = atk_table_get_index_at (ATK_TABLE (ea_calitem), row, 0);
    if (!e_calendar_item_get_date_for_offset (calitem, index,
                          &year, &month, &day))
        return FALSE;

    week_num = e_calendar_item_get_week_number (
        calitem, day, month, year);

    g_snprintf (buffer, buffer_size, "week number : %d", week_num);
    return TRUE;
}

static gboolean
ea_calendar_item_get_column_label (EaCalendarItem *ea_calitem,
                                   gint column,
                                   gchar *buffer,
                                   gint buffer_size)
{
    AtkGObjectAccessible *atk_gobj;
    GObject *g_obj;
    const gchar *abbr_name;

    g_return_val_if_fail (ea_calitem, FALSE);

    atk_gobj = ATK_GOBJECT_ACCESSIBLE (ea_calitem);
    g_obj = atk_gobject_accessible_get_object (atk_gobj);
    if (!g_obj)
        return FALSE;

    /* Columns are 0 = Monday ... 6 = Sunday */
    abbr_name = e_get_weekday_name (column + 1, TRUE);
    g_strlcpy (buffer, abbr_name, buffer_size);

    return TRUE;
}

/* the coordinate the e-calendar canvas coord */
gboolean
e_calendar_item_get_day_extents (ECalendarItem *calitem,
                                 gint year,
                                 gint month,
                                 gint date,
                                 gint *x,
                                 gint *y,
                                 gint *width,
                                 gint *height)
{
    GnomeCanvasItem *item;
    GtkWidget *widget;
    GtkStyle *style;
    PangoFontDescription *font_desc;
    PangoContext *pango_context;
    PangoFontMetrics *font_metrics;
    gint char_height, xthickness, ythickness, text_y;
    gint new_year, new_month, num_months, months_offset;
    gint month_x, month_y, month_cell_x, month_cell_y;
    gint month_row, month_col;
    gint day_row, day_col;
    gint days_from_week_start;

    g_return_val_if_fail (E_IS_CALENDAR_ITEM (calitem), FALSE);

    item = GNOME_CANVAS_ITEM (calitem);
    widget = GTK_WIDGET (item->canvas);
    style = gtk_widget_get_style (widget);

    /* Set up Pango prerequisites */
    font_desc = calitem->font_desc;
    if (!font_desc)
        font_desc = style->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));

    char_height =
        PANGO_PIXELS (pango_font_metrics_get_ascent (font_metrics)) +
        PANGO_PIXELS (pango_font_metrics_get_descent (font_metrics));

    xthickness = style->xthickness;
    ythickness = style->ythickness;

    new_year = year;
    new_month = month;
    e_calendar_item_normalize_date (calitem, &new_year, &new_month);
    num_months = calitem->rows * calitem->cols;
    months_offset = (new_year - calitem->year) * 12
        + new_month - calitem->month;

    if (months_offset > num_months || months_offset < 0)
        return FALSE;

    month_row = months_offset / calitem->cols;
    month_col = months_offset % calitem->cols;

    month_x = item->x1 + xthickness + calitem->x_offset
        + month_col * calitem->month_width;
    month_y = item->y1 + ythickness + month_row * calitem->month_height;

    month_cell_x = month_x + E_CALENDAR_ITEM_XPAD_BEFORE_WEEK_NUMBERS
        + calitem->month_lpad + E_CALENDAR_ITEM_XPAD_BEFORE_CELLS;
    text_y = month_y + ythickness * 2
        + E_CALENDAR_ITEM_YPAD_ABOVE_MONTH_NAME
        + char_height + E_CALENDAR_ITEM_YPAD_BELOW_MONTH_NAME
        + E_CALENDAR_ITEM_YPAD_ABOVE_DAY_LETTERS + calitem->month_tpad;

    month_cell_y = text_y + char_height
        + E_CALENDAR_ITEM_YPAD_BELOW_DAY_LETTERS + 1
        + E_CALENDAR_ITEM_YPAD_ABOVE_CELLS;

    days_from_week_start = e_calendar_item_get_n_days_from_week_start (
        calitem, new_year, new_month);
    day_row = (date + days_from_week_start - 1) / EA_CALENDAR_COLUMN_NUM;
    day_col = (date + days_from_week_start - 1) % EA_CALENDAR_COLUMN_NUM;

    *x = month_cell_x + day_col * calitem->cell_width;
    *y = month_cell_y + day_row * calitem->cell_height;
    *width = calitem->cell_width;
    *height = calitem->cell_height;

    return TRUE;
}

/* month is from 0 to 11 */
gboolean
e_calendar_item_get_date_for_offset (ECalendarItem *calitem,
                                     gint day_offset,
                                     gint *year,
                                     gint *month,
                                     gint *day)
{
    gint start_year, start_month, start_day;
    gint end_year, end_month, end_day;
    GDate *start_date;

    g_return_val_if_fail (E_IS_CALENDAR_ITEM (calitem), FALSE);

    if (!e_calendar_item_get_date_range (calitem, &start_year,
                         &start_month, &start_day,
                         &end_year, &end_month,
                         &end_day))
        return FALSE;

    start_date = g_date_new_dmy (start_day, start_month + 1, start_year);

    g_date_add_days (start_date, day_offset);

    *year = g_date_get_year (start_date);
    *month = g_date_get_month (start_date) - 1;
    *day = g_date_get_day (start_date);

    return TRUE;
}

/* the arg month is from 0 to 11 */
static gboolean
e_calendar_item_get_offset_for_date (ECalendarItem *calitem,
                                     gint year,
                                     gint month,
                                     gint day,
                                     gint *offset)
{
    gint start_year, start_month, start_day;
    gint end_year, end_month, end_day;
    GDate *start_date, *end_date;

    *offset = 0;
    g_return_val_if_fail (E_IS_CALENDAR_ITEM (calitem), FALSE);

    if (!e_calendar_item_get_date_range (calitem, &start_year,
                         &start_month, &start_day,
                         &end_year, &end_month,
                         &end_day))
        return FALSE;

    start_date = g_date_new_dmy (start_day, start_month + 1, start_year);
    end_date = g_date_new_dmy (day, month + 1, year);

    *offset = g_date_days_between (start_date, end_date);
    g_free (start_date);
    g_free (end_date);

    return TRUE;
}

gint
e_calendar_item_get_n_days_from_week_start (ECalendarItem *calitem,
                                            gint year,
                                            gint month)
{
    GDateWeekday weekday;
    struct tm tmp_tm;

    memset (&tmp_tm, 0, sizeof (tmp_tm));
    tmp_tm.tm_year = year - 1900;
    tmp_tm.tm_mon = month;
    tmp_tm.tm_mday = 1;
    tmp_tm.tm_isdst = -1;
    mktime (&tmp_tm);

    weekday = e_weekday_from_tm_wday (tmp_tm.tm_wday);

    return e_weekday_get_days_between (calitem->week_start_day, weekday);
}

static void
ea_calendar_set_focus_object (EaCalendarItem *ea_calitem,
                              AtkObject *item_cell)
{
    AtkStateSet *state_set, *old_state_set;
    AtkObject *old_cell;

    old_cell = (AtkObject *) g_object_get_data (
        G_OBJECT (ea_calitem), "gail-focus-object");
    if (old_cell && EA_IS_CALENDAR_CELL (old_cell)) {
        old_state_set = atk_object_ref_state_set (old_cell);
        atk_state_set_remove_state (old_state_set, ATK_STATE_FOCUSED);
        g_object_unref (old_state_set);
    }
    if (old_cell)
        g_object_unref (old_cell);

    state_set = atk_object_ref_state_set (item_cell);
    atk_state_set_add_state (state_set, ATK_STATE_FOCUSED);
    g_object_set_data (G_OBJECT (ea_calitem), "gail-focus-object", item_cell);
    g_object_unref (state_set);
}