aboutsummaryrefslogblamecommitdiffstats
path: root/calendar/gui/e-meeting-time-sel-item.c
blob: aaa86230ccda2e23089d3a73e8312d0d083afe62 (plain) (tree)
1
2
3
4
5
6
7
8
9
10
  




                                                                               
                                                                


                                                               


                                                                  



                                                                    
                                                                             
  



                                                        
   
 




                    
                       
 
                            



                                                                              


                                                                            

                                                           
                                                                   
 



                                                                           
                                                                       

                                                                           
                                                                     
                                                           

                                                                        


                                                                                  









                                                                                            
                                                                    
                                                                    

                                                                                 
                                                                                                                                                                                  
                                                                                       
                                                                
                                                                

                                                                             
                                                                                                                                                                           
                                                                                                                            
                                                                                                                                                                                                                








                                                                                                                                        
      

                                  

  
                                                                                              

           
                                                                              
 
                                   

                                         




                                                                               












                                                                 

 



                                                                      
 

                             


                                                                         
                                                           







                                         
           
                                                      




                                                         
                                      
                                                         


                                               
                                                         

                                               
                                    
                                                       


                                             
                                                                                     

 
           



                                                               
 

                                           
                                                         
 



                                                            
         

                                                                       

 
           


                                                               
 
                                                                                        
                                                                                                                   







                                                     




                                                         





                                                       
 

                                                                   
 
                        
 

                                                  
 


                                                                     
 
                           


           





                                                         







                                                                      



                                                       




                                                                        





                                                            

                                                                          
                        
                             
                                                                              


                                                       
                                
                


                                                                
         
                           







                                                                                                                       

                                                                             
                                     


                                                                                      

                                        


                                                                             


                                        

         
                                                                                  
                                                
                              
                                                                  

                                                  

                                                                                                     

                                                                            
                                                                                                                               
                                                                             
                                                                                                                    

                                                                   
                                                                                                                              


                                                                                                             

                                                                     
                                                                                                                                        


                                                                                                           



                                                 
                                                                                                    



                                                 





                                                                          

                                                                        
                                   
                                                                                                                          
                    
                                                                                                                      








                                                                    
                                                                                                                         
            
                                                                                                           
 









                                                                     

                                                                  


                                                           


                                                                   
                                        



                                                         


                                                                 

                                        
                 
                                   


         

                                                                               





                                                          

                                  
                       
                   
                                  
                            
                          
 
                        
 
                                       
 
                            
 
                                                                         


                                           


                                                          

                                                      


                                                                 

                                                      

                                                          
                             

                                                      
                             

                                                      
 



                                                      

                                                                                               

                                    

                                                  
                 


                                                  
 




                                                                                             
                                                                            
                       





                                                                                               
 



                                                           
 
                        
 

                                                                                    
 
                                                

                                                
 
                           

                             
                                     
                       
                                                
                                             
                                                                         
                                                                                            
                    
                                                                                              
 

                                                     



                                                

                                
                           
                     

 
                                                                          

                                                                         
           






                                                                                                  

                                  

                                       


                            




                                                                             
                                                                                          
                                                                                       
                                                                                                               



                                                  
                                                                              
                                                                                                 

                                                     
                                                                                                                                                 





                               

                                                                           





                                                      

                                  

                                                      

                                       
 
                            

                                                                              
                                










                                                                         
                                                                                              
                                                                            
                          
                                                                                   
                   


                                                   



                                           
                                                                                                      
                            
                                                                  



                                                              
                                                                  
                                                                             
                         
                                  
 



                                                  
                                


                                
                                                                         



                                                              
                                                                         
                                                                       
                         
                                  
 



                                                         
                                

         
                           

 
                                                                            
                
           






                                                                                    


                                       
                                  

                            
 






                                                                             
                                                                                         

                                                         
                                                                                                         

                                                                        

                                                                      


                                                                  
                                                                                              
                                                                                                                                                   






                                     
                                                                           


                                                                            
           


                                                                                        








                                       
                                                                    





                                                                          
                          







                                                                                   
                                                                               

                                                                             
           







                                                                                             


                                  
                                   
                                       


                                                              
 
                                                                    












                                                                                           
                                   








                                                                                         



                                                                              











                                                                         

                                                                             

                                                     


                                                              
     


                                                              
      
                                        



                 



                                                            
                        




                                                          
 
                    

 
           

                                                          






                                                       


                                                                  
                                


                                                                    
                               


                                                                   






                      
                                                                           



                                                                           

                                                                              
                                                           











                                                                               


                                                                   
                                                           















                                                            










                                                                            
                                
                            

                                                                                       
                                  

                                     











                                                          
                                    
                                              
                    
                                                                    




















                                                                                                                
                 

                                        
                

                                      
                                      
                                                    
         
 



                                                               











                                                  
 


                     
                                                                            
                         

                                                                                
                                                             








                                                                          


                                                     




                     
                                                                            


                                                                              

                                                                               
                                                            














                                                                         


                                                                   

                                                    
                                                         
                                                 




                                                                


                                                                            


                                    
                                                   



                                                                     




                     

                                                                                   

                                                       























                                                                                                                

                                                                             




                                                                 



                                        
 
                                                                    

                                                                          
 












                                                                                                      
 
    

                                                                                   


                            

                                                                      

                                                             

                                                                        
 
/*
 * EMeetingTimeSelectorItem - A GnomeCanvasItem which is used for both the main
 * display canvas and the top display (with the dates, times & All Attendees).
 * I didn't make these separate GnomeCanvasItems since they share a lot of
 * code.
 *
 * 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; either
 * version 2 of the License, or (at your option) version 3.
 *
 * 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with the program; if not, see <http://www.gnu.org/licenses/>
 *
 * Authors:
 *      Damon Chaplin <damon@gtk.org>
 *
 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
 */

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

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

#include "calendar-config.h"
#include "e-meeting-time-sel-item.h"
#include "e-meeting-time-sel.h"

/* Initially the grid lines were drawn at the bottom of cells, but this didn't
 * line up well with the GtkEntry widgets, which in the default theme draw a
 * black shadow line across the top. So I've switched our code to draw the
 * lines across the top of cells. */
#define E_MEETING_TIME_SELECTOR_DRAW_GRID_LINES_AT_BOTTOM 0

static void e_meeting_time_selector_item_dispose (GObject *object);

static void e_meeting_time_selector_item_set_property (GObject *object,
                                                       guint property_id,
                                                       const GValue *value,
                                                       GParamSpec *pspec);
static void e_meeting_time_selector_item_update (GnomeCanvasItem *item,
                         const cairo_matrix_t *i2c,
                         gint flags);
static void e_meeting_time_selector_item_draw (GnomeCanvasItem *item,
                           cairo_t *cr,
                           gint x, gint y,
                           gint width, gint height);
static GnomeCanvasItem *e_meeting_time_selector_item_point (GnomeCanvasItem *item,
                                                            double x, double y,
                                                            gint cx, gint cy);
static gint e_meeting_time_selector_item_event (GnomeCanvasItem *item,
                        GdkEvent *event);
static gint e_meeting_time_selector_item_button_press (EMeetingTimeSelectorItem *mts_item,
                               GdkEvent *event);
static gint e_meeting_time_selector_item_button_release (EMeetingTimeSelectorItem *mts_item,
                             GdkEvent *event);
static gint e_meeting_time_selector_item_motion_notify (EMeetingTimeSelectorItem *mts_item,
                            GdkEvent *event);

static void e_meeting_time_selector_item_paint_day_top (EMeetingTimeSelectorItem *mts_item,
                            cairo_t *cr,
                            GDate *date,
                            gint x, gint scroll_y,
                            gint width, gint height);
static void e_meeting_time_selector_item_paint_all_attendees_busy_periods (EMeetingTimeSelectorItem *mts_item, cairo_t *cr, GDate *date, gint x, gint y, gint width, gint height);
static void e_meeting_time_selector_item_paint_day (EMeetingTimeSelectorItem *mts_item,
                            cairo_t *cr,
                            GDate *date,
                            gint x, gint scroll_y,
                            gint width, gint height);
static void e_meeting_time_selector_item_paint_busy_periods (EMeetingTimeSelectorItem *mts_item, cairo_t *cr, GDate *date, gint x, gint scroll_y, gint width, gint height);
static gint e_meeting_time_selector_item_find_first_busy_period (EMeetingTimeSelectorItem *mts_item, GDate *date, gint row);
static void e_meeting_time_selector_item_paint_attendee_busy_periods (EMeetingTimeSelectorItem *mts_item, cairo_t *cr, gint row, gint x, gint y, gint width, gint first_period, EMeetingFreeBusyType busy_type);

static EMeetingTimeSelectorPosition e_meeting_time_selector_item_get_drag_position (EMeetingTimeSelectorItem *mts_item, gint x, gint y);
static gboolean e_meeting_time_selector_item_calculate_busy_range (EMeetingTimeSelector *mts,
                                   gint row,
                                   gint x,
                                   gint width,
                                   gint *start_x,
                                   gint *end_x);

enum {
    PROP_0,
    PROP_MEETING_TIME_SELECTOR
};

G_DEFINE_TYPE (EMeetingTimeSelectorItem, e_meeting_time_selector_item, GNOME_TYPE_CANVAS_ITEM)

static void
e_meeting_time_selector_item_class_init (EMeetingTimeSelectorItemClass *class)
{
    GObjectClass *object_class;
    GnomeCanvasItemClass *item_class;

    object_class = G_OBJECT_CLASS (class);
    object_class->dispose = e_meeting_time_selector_item_dispose;
    object_class->set_property = e_meeting_time_selector_item_set_property;

    item_class = GNOME_CANVAS_ITEM_CLASS (class);
    item_class->update = e_meeting_time_selector_item_update;
    item_class->draw = e_meeting_time_selector_item_draw;
    item_class->point = e_meeting_time_selector_item_point;
    item_class->event = e_meeting_time_selector_item_event;

    g_object_class_install_property (
        object_class,
        PROP_MEETING_TIME_SELECTOR,
        g_param_spec_pointer (
            "meeting_time_selector",
            NULL,
            NULL,
            G_PARAM_WRITABLE));
}

static void
e_meeting_time_selector_item_init (EMeetingTimeSelectorItem *mts_item)
{
    GnomeCanvasItem *item = GNOME_CANVAS_ITEM (mts_item);

    mts_item->mts = NULL;

    /* Create the cursors. */
    mts_item->normal_cursor = gdk_cursor_new (GDK_LEFT_PTR);
    mts_item->resize_cursor = gdk_cursor_new (GDK_SB_H_DOUBLE_ARROW);
    mts_item->busy_cursor = gdk_cursor_new (GDK_WATCH);
    mts_item->last_cursor_set = NULL;

    item->x1 = 0;
    item->y1 = 0;
    item->x2 = 0;
    item->y2 = 0;
}

static void
e_meeting_time_selector_item_dispose (GObject *object)
{
    EMeetingTimeSelectorItem *mts_item;

    mts_item = E_MEETING_TIME_SELECTOR_ITEM (object);

    if (mts_item->normal_cursor) {
        g_object_unref (mts_item->normal_cursor);
        mts_item->normal_cursor = NULL;
    }
    if (mts_item->resize_cursor) {
        g_object_unref (mts_item->resize_cursor);
        mts_item->resize_cursor = NULL;
    }
    if (mts_item->busy_cursor) {
        g_object_unref (mts_item->busy_cursor);
        mts_item->busy_cursor = NULL;
    }

    G_OBJECT_CLASS (e_meeting_time_selector_item_parent_class)->dispose (object);
}

static void
e_meeting_time_selector_item_set_property (GObject *object,
                                           guint property_id,
                                           const GValue *value,
                                           GParamSpec *pspec)
{
    EMeetingTimeSelectorItem *mts_item;

    mts_item = E_MEETING_TIME_SELECTOR_ITEM (object);

    switch (property_id) {
    case PROP_MEETING_TIME_SELECTOR:
        mts_item->mts = g_value_get_pointer (value);
        return;
    }

    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}

static void
e_meeting_time_selector_item_update (GnomeCanvasItem *item,
                                     const cairo_matrix_t *i2c,
                                     gint flags)
{
    if (GNOME_CANVAS_ITEM_CLASS (e_meeting_time_selector_item_parent_class)->update)
        (* GNOME_CANVAS_ITEM_CLASS (e_meeting_time_selector_item_parent_class)->update) (item, i2c, flags);

    /* The grid covers the entire canvas area. */
    item->x1 = 0;
    item->y1 = 0;
    item->x2 = INT_MAX;
    item->y2 = INT_MAX;
}

/*
 * DRAWING ROUTINES - functions to paint the canvas item.
 */

static void
draw_strikeout_box (EMeetingTimeSelectorItem *mts_item,
                    cairo_t *cr,
                    gint x,
                    gint y,
                    gint width,
                    gint height)
{
    GnomeCanvas *canvas = GNOME_CANVAS_ITEM (mts_item)->canvas;
    EMeetingTimeSelector *mts = mts_item->mts;

    cairo_save (cr);

    cairo_rectangle (cr, x, y, width, height);
    cairo_clip (cr);

    cairo_translate (cr, -canvas->draw_xofs, -canvas->draw_yofs);
    cairo_set_source (cr, mts->no_info_pattern);
    cairo_paint (cr);

    cairo_restore (cr);
}

static void
e_meeting_time_selector_item_draw (GnomeCanvasItem *item,
                                   cairo_t *cr,
                                   gint x,
                                   gint y,
                                   gint width,
                                   gint height)
{
    EMeetingTimeSelector *mts;
    EMeetingTimeSelectorItem *mts_item;
    EMeetingAttendee *ia;
    gint day_x, meeting_start_x, meeting_end_x, bar_y, bar_height;
    gint row, row_y, start_x, end_x;
    GDate date, last_date, current_date;
    gboolean is_display_top, show_meeting_time;

    mts_item = E_MEETING_TIME_SELECTOR_ITEM (item);
    mts = mts_item->mts;
    g_return_if_fail (mts != NULL);

    is_display_top = (GTK_WIDGET (item->canvas) == mts->display_top)
        ? TRUE : FALSE;

    /* Calculate the first and last visible days and positions. */
    e_meeting_time_selector_calculate_day_and_position (
        mts, x,
        &date, &day_x);
    e_meeting_time_selector_calculate_day_and_position (
        mts, x + width,
        &last_date, NULL);

    /* For the top display draw the 'All Attendees' row background. */
    cairo_save (cr);
    if (is_display_top) {
        gdk_cairo_set_source_color (cr, &mts->all_attendees_bg_color);
        cairo_rectangle (
            cr, 0, mts->row_height * 2 - y,
            width, mts->row_height);
        cairo_fill (cr);
    } else {
        gdk_cairo_set_source_color (cr, &mts->bg_color);
        cairo_rectangle (cr,  0, 0, width, height);
        cairo_fill (cr);
    }
    cairo_restore (cr);

    /* Calculate the x coordinates of the meeting time. */
    show_meeting_time = e_meeting_time_selector_get_meeting_time_positions (mts, &meeting_start_x, &meeting_end_x);

    /* Draw the meeting time background. */
    if (show_meeting_time
        && (meeting_end_x - 1 >= x) && (meeting_start_x + 1 < x + width)
        && (meeting_end_x - meeting_start_x > 2)) {
        cairo_save (cr);
        gdk_cairo_set_source_color (cr, &mts->meeting_time_bg_color);
        if (is_display_top) {
            cairo_rectangle (
                cr, meeting_start_x + 1 - x, mts->row_height * 2 - y,
                meeting_end_x - meeting_start_x - 2, mts->row_height);
            cairo_fill (cr);
        } else {
            cairo_rectangle (
                cr, meeting_start_x + 1 - x, 0,
                meeting_end_x - meeting_start_x - 2, height);
            cairo_fill (cr);
        }
        cairo_restore (cr);
    }

    /* For the main display draw the no-info pattern background for attendee's
     * that have no calendar information. */
    if (!is_display_top) {
        gdk_cairo_set_source_color (cr, &mts->grid_color);
        row = y / mts->row_height;
        row_y = row * mts->row_height - y;
        while (row < e_meeting_store_count_actual_attendees (mts->model) && row_y < height) {
            ia = e_meeting_store_find_attendee_at_row (mts->model, row);

            if (e_meeting_attendee_get_has_calendar_info (ia)) {
                if (e_meeting_time_selector_item_calculate_busy_range (mts, row, x, width, &start_x, &end_x)) {
                    if (start_x >= width || end_x <= 0) {
                        draw_strikeout_box (mts_item, cr, 0, row_y, width, mts->row_height);
                    } else {
                        if (start_x >= 0) {
                            draw_strikeout_box (mts_item, cr, 0, row_y, start_x, mts->row_height);
                            cairo_move_to (cr, start_x, row_y);
                            cairo_line_to (cr, start_x, row_y + mts->row_height);
                            cairo_stroke (cr);
                        }
                        if (end_x <= width) {
                            draw_strikeout_box (mts_item, cr, end_x, row_y, width - end_x, mts->row_height);
                            cairo_move_to (cr, end_x, row_y);
                            cairo_line_to (cr, end_x, row_y + mts->row_height);
                            cairo_stroke (cr);
                        }
                    }
                }
            } else {
                draw_strikeout_box (mts_item, cr, 0, row_y, width, mts->row_height);
            }
            row++;
            row_y += mts->row_height;
        }
    }

    /* Now paint the visible days one by one. */
    current_date = date;
    for (;;) {
        /* Currently we use the same GnomeCanvasItem class for the
         * top display and the main display. We may use separate
         * classes in future if necessary. */
        if (is_display_top)
            e_meeting_time_selector_item_paint_day_top (mts_item, cr, &current_date, day_x, y, width, height);
        else
            e_meeting_time_selector_item_paint_day (mts_item, cr, &current_date, day_x, y, width, height);

        day_x += mts_item->mts->day_width;
        if (g_date_compare (&current_date, &last_date) == 0)
            break;
        g_date_add_days (&current_date, 1);
    }

    /* Draw the busy periods. */
    if (is_display_top)
        e_meeting_time_selector_item_paint_all_attendees_busy_periods (mts_item, cr, &date, x, y, width, height);
    else
        e_meeting_time_selector_item_paint_busy_periods (mts_item, cr, &date, x, y, width, height);

    /* Draw the currently-selected meeting time vertical bars. */
    if (show_meeting_time) {
        if (is_display_top) {
            bar_y = mts->row_height * 2 - y;
            bar_height = mts->row_height;
        } else {
            bar_y = 0;
            bar_height = height;
        }

        cairo_save (cr);
        gdk_cairo_set_source_color (cr, &mts->grid_color);

        if ((meeting_start_x + 2 >= x)
            && (meeting_start_x - 2 < x + width)) {
            cairo_rectangle (
                cr, meeting_start_x - 2 - x, bar_y,
                5, bar_height);
            cairo_fill (cr);
        }

        if ((meeting_end_x + 2 >= x)
            && (meeting_end_x - 2 < x + width)) {
            cairo_rectangle (
                cr, meeting_end_x - 2 - x, bar_y,
                5, bar_height);
            cairo_fill (cr);

        }
        cairo_restore (cr);
    }
}

static void
e_meeting_time_selector_item_paint_day_top (EMeetingTimeSelectorItem *mts_item,
                                            cairo_t *cr,
                                            GDate *date,
                                            gint x,
                                            gint scroll_y,
                                            gint width,
                                            gint height)
{
    EMeetingTimeSelector *mts;
    gint y, grid_x;
    gchar *str;
    gint hour, hour_x, hour_y;
    PangoLayout *layout;
    struct tm tm_time;

    cairo_save (cr);

    cairo_set_line_width (cr, 1.0);

    mts = mts_item->mts;

    layout = gtk_widget_create_pango_layout (GTK_WIDGET (mts), NULL);

    /* Draw the horizontal lines. */
    y = mts->row_height - 1 - scroll_y;

    gdk_cairo_set_source_color (cr, &mts->grid_color);
    cairo_move_to (cr, x + 0.5, y + 0.5);
    cairo_rel_line_to (cr, mts->day_width - 1, 0);
    cairo_stroke (cr);

    gdk_cairo_set_source_color (cr, &mts->grid_shadow_color);
    cairo_move_to (cr, x + 0.5, y + 1.5);
    cairo_rel_line_to (cr, mts->day_width - 1, 0);
    cairo_stroke (cr);

    gdk_cairo_set_source_color (cr, &mts->grid_color);
    y += mts->row_height;
    cairo_move_to (cr, x + 0.5, y + 0.5);
    cairo_rel_line_to (cr, mts->day_width - 1, 0);
    y += mts->row_height;
    cairo_move_to (cr, x + 0.5, y + 0.5);
    cairo_rel_line_to (cr, mts->day_width - 1, 0);

    /* Draw the vertical grid lines. */
    for (grid_x = mts->col_width - 1;
         grid_x < mts->day_width - mts->col_width;
         grid_x += mts->col_width) {
        cairo_move_to (cr, x + grid_x + 0.5, mts->row_height * 2 - 1 - scroll_y + 0.5);
        cairo_line_to (cr, x + grid_x + 0.5, height + 0.5);
    }
    grid_x = mts->day_width - 2;
    cairo_move_to (cr, x + grid_x + 0.5, 0.5);
    cairo_rel_line_to (cr, 0, height);
    grid_x++;
    cairo_move_to (cr, x + grid_x + 0.5, 0.5);
    cairo_rel_line_to (cr, 0, height);
    cairo_stroke (cr);

    g_date_to_struct_tm (date, &tm_time);
    str = e_datetime_format_format_tm ("calendar", "table",  DTFormatKindDate, &tm_time);

    g_return_if_fail (str != NULL);

    /* Draw the date. Set a clipping rectangle so we don't draw over the
     * next day. */
    if (mts->date_format == E_MEETING_TIME_SELECTOR_DATE_ABBREVIATED_DAY
        && !e_datetime_format_includes_day_name ("calendar", "table",  DTFormatKindDate)) {
        gchar buffer[128];
        gchar *tmp;

        g_date_strftime (buffer, sizeof (buffer), "%a", date);

        tmp = str;
        str = g_strconcat (buffer, " ", str, NULL);
        g_free (tmp);
    }

    cairo_save (cr);

    cairo_rectangle (cr, x, -scroll_y, mts->day_width - 2, mts->row_height - 2);
    cairo_clip (cr);

    pango_layout_set_text (layout, str, -1);
    cairo_move_to (cr, x + 2, 4 - scroll_y);
    pango_cairo_show_layout (cr, layout);

    cairo_restore (cr);

    /* Draw the hours. */
    hour = mts->first_hour_shown;
    hour_x = x + 2;
    hour_y = mts->row_height + 4 - scroll_y;
    while (hour < mts->last_hour_shown) {
        if (e_meeting_time_selector_get_use_24_hour_format (mts))
            pango_layout_set_text (layout, EMeetingTimeSelectorHours[hour], -1);
        else
            pango_layout_set_text (layout, EMeetingTimeSelectorHours12[hour], -1);

        cairo_move_to (cr, hour_x, hour_y);
        pango_cairo_show_layout (cr, layout);

        hour += mts->zoomed_out ? 3 : 1;
        hour_x += mts->col_width;
    }

    g_object_unref (layout);
    cairo_restore (cr);
    g_free (str);
}

/* This paints the colored bars representing busy periods for the combined
 * list of attendees. For now we just paint the bars for each attendee of
 * each other. If we want to speed it up we could optimise it later. */
static void
e_meeting_time_selector_item_paint_all_attendees_busy_periods (EMeetingTimeSelectorItem *mts_item,
                                                               cairo_t *cr,
                                                               GDate *date,
                                                               gint x,
                                                               gint scroll_y,
                                                               gint width,
                                                               gint height)
{
    EMeetingTimeSelector *mts;
    EMeetingFreeBusyType busy_type;
    gint row, y;
    gint *first_periods;

    mts = mts_item->mts;

    /* Calculate the y coordinate to paint the row at in the drawable. */
    y = 2 * mts->row_height - scroll_y - 1;

    /* Get the first visible busy periods for all the attendees. */
    first_periods = g_new (gint, e_meeting_store_count_actual_attendees (mts->model));
    for (row = 0; row < e_meeting_store_count_actual_attendees (mts->model); row++)
        first_periods[row] = e_meeting_time_selector_item_find_first_busy_period (mts_item, date, row);

    for (busy_type = 0;
         busy_type < E_MEETING_FREE_BUSY_LAST;
         busy_type++) {
        gdk_cairo_set_source_color (cr, &mts->busy_colors[busy_type]);
        for (row = 0; row < e_meeting_store_count_actual_attendees (mts->model); row++) {
            if (first_periods[row] == -1)
                continue;
            e_meeting_time_selector_item_paint_attendee_busy_periods (mts_item, cr, x, y, width, row, first_periods[row], busy_type);
        }
    }

    g_free (first_periods);
}

static void
e_meeting_time_selector_item_paint_day (EMeetingTimeSelectorItem *mts_item,
                                        cairo_t *cr,
                                        GDate *date,
                                        gint x,
                                        gint scroll_y,
                                        gint width,
                                        gint height)
{
    EMeetingTimeSelector *mts;
    gint grid_x, grid_y, attendee_index, unused_y;

    cairo_save (cr);
    cairo_set_line_width (cr, 1.0);

    mts = mts_item->mts;

    /* Draw the grid lines. The grid lines around unused rows are drawn in
     * a different color. */

    /* Draw the horizontal grid lines. */
    attendee_index = scroll_y / mts->row_height;
#if E_MEETING_TIME_SELECTOR_DRAW_GRID_LINES_AT_BOTTOM
    for (grid_y = mts->row_height - 1 - (scroll_y % mts->row_height);
#else
    for (grid_y = - (scroll_y % mts->row_height);
#endif
         grid_y < height;
         grid_y += mts->row_height)
      {
          if (attendee_index <= e_meeting_store_count_actual_attendees (mts->model)) {
              gdk_cairo_set_source_color (cr, &mts->grid_color);
          } else {
              gdk_cairo_set_source_color (cr, &mts->grid_unused_color);
          }
          cairo_move_to (cr, 0, grid_y);
          cairo_rel_line_to (cr, width, 0);
          cairo_stroke (cr);
          attendee_index++;
      }

    /* Draw the vertical grid lines. */
    unused_y = (e_meeting_store_count_actual_attendees (mts->model) * mts->row_height) - scroll_y;
    if (unused_y >= 0) {
        gdk_cairo_set_source_color (cr, &mts->grid_color);
        for (grid_x = mts->col_width - 1;
             grid_x < mts->day_width - mts->col_width;
             grid_x += mts->col_width)
            {
                cairo_move_to (cr, x + grid_x, 0);
                cairo_line_to (cr, x + grid_x, unused_y - 1);
            }
        cairo_stroke (cr);

        cairo_rectangle (
            cr,
            x + mts->day_width - 2, 0,
            2, unused_y);
        cairo_fill (cr);
    }

    if (unused_y < height) {
        gdk_cairo_set_source_color (cr, &mts->grid_unused_color);
        for (grid_x = mts->col_width - 1;
             grid_x < mts->day_width - mts->col_width;
             grid_x += mts->col_width)
            {
                cairo_move_to (cr, x + grid_x, unused_y);
                cairo_line_to (cr, x + grid_x, height);
            }
        cairo_stroke (cr);

        cairo_rectangle (
            cr,
            x + mts->day_width - 2, unused_y,
            2, height - unused_y);
        cairo_fill (cr);
    }

    cairo_restore (cr);
}

/* This paints the colored bars representing busy periods for the individual
 * attendees. */
static void
e_meeting_time_selector_item_paint_busy_periods (EMeetingTimeSelectorItem *mts_item,
                                                 cairo_t *cr,
                                                 GDate *date,
                                                 gint x,
                                                 gint scroll_y,
                                                 gint width,
                                                 gint height)
{
    EMeetingTimeSelector *mts;
    EMeetingFreeBusyType busy_type;
    gint row, y, first_period;

    mts = mts_item->mts;

    /* Calculate the first visible attendee row. */
    row = scroll_y / mts->row_height;

    /* Calculate the y coordinate to paint the row at in the drawable. */
    y = row * mts->row_height - scroll_y;

    /* Step through the attendees painting the busy periods. */
    while (y < height && row < e_meeting_store_count_actual_attendees (mts->model)) {

        /* Find the first visible busy period. */
        first_period = e_meeting_time_selector_item_find_first_busy_period (mts_item, date, row);
        if (first_period != -1) {
            /* Paint the different types of busy periods, in
             * reverse order of precedence, so the highest
             * precedences are displayed. */
            for (busy_type = 0;
                 busy_type < E_MEETING_FREE_BUSY_LAST;
                 busy_type++) {
                gdk_cairo_set_source_color (cr, &mts->busy_colors[busy_type]);
                e_meeting_time_selector_item_paint_attendee_busy_periods (mts_item, cr, x, y, width, row, first_period, busy_type);
            }
        }
        y += mts->row_height;
        row++;
    }
}

/* This subtracts the attendees longest_period_in_days from the given date,
 * and does a binary search of the attendee's busy periods array to find the
 * first one which could possible end on the given day or later.
 * If none are found it returns -1. */
static gint
e_meeting_time_selector_item_find_first_busy_period (EMeetingTimeSelectorItem *mts_item,
                                                     GDate *date,
                                                     gint row)
{
    EMeetingTimeSelector *mts;
    EMeetingAttendee *ia;
    const GArray *busy_periods;
    EMeetingFreeBusyPeriod *period;
    gint period_num;

    mts = mts_item->mts;

    ia = e_meeting_store_find_attendee_at_row (mts->model, row);

    period_num = e_meeting_attendee_find_first_busy_period (ia, date);
    if (period_num == -1)
        return -1;

    /* Check if the period starts after the end of the current canvas
     * scroll area. */
    busy_periods = e_meeting_attendee_get_busy_periods (ia);
    period = &g_array_index (busy_periods, EMeetingFreeBusyPeriod, period_num);
    if (g_date_compare (&mts->last_date_shown, &period->start.date) < 0)
        return -1;

    return period_num;
}

/* This paints the visible busy periods for one attendee which are of a certain
 * busy type, e.g out of office. It is passed the index of the first visible
 * busy period of the attendee and continues until it runs off the screen. */
static void
e_meeting_time_selector_item_paint_attendee_busy_periods (EMeetingTimeSelectorItem *mts_item,
                                                          cairo_t *cr,
                                                          gint x,
                                                          gint y,
                                                          gint width,
                                                          gint row,
                                                          gint first_period,
                                                          EMeetingFreeBusyType busy_type)
{
    EMeetingTimeSelector *mts;
    EMeetingAttendee *ia;
    const GArray *busy_periods;
    EMeetingFreeBusyPeriod *period;
    gint period_num, x1, x2, x2_within_day, x2_within_col;

    mts = mts_item->mts;

    ia = e_meeting_store_find_attendee_at_row (mts->model, row);

    busy_periods = e_meeting_attendee_get_busy_periods (ia);
    for (period_num = first_period;
         period_num < busy_periods->len;
         period_num++) {
        period = &g_array_index (busy_periods, EMeetingFreeBusyPeriod, period_num);

        if (period->busy_type != busy_type)
            continue;

        /* Convert the period start and end times to x coordinates. */
        x1 = e_meeting_time_selector_calculate_time_position (mts, &period->start);
        /* If the period is off the right of the area being drawn, we
         * are finished. */
        if (x1 >= x + width)
            return;

        x2 = e_meeting_time_selector_calculate_time_position (mts, &period->end);
        /* If the period is off the left edge of the area skip it. */
        if (x2 <= x)
            continue;

        /* We paint from x1 to x2 - 1, so that for example a time
         * from 5:00-6:00 is distinct from 6:00-7:00.
         * We never finish on a grid line separating days, and we only
         * ever paint on a normal vertical grid line if the period is
         * only 1 pixel wide. */
        x2_within_day = x2 % mts->day_width;
        if (x2_within_day == 0) {
            x2 -= 2;
        } else if (x2_within_day == mts->day_width - 1) {
            x2 -= 1;
        } else {
            x2_within_col = x2_within_day % mts->col_width;
            if (x2_within_col == 0 && x2 > x1 + 1)
                x2 -= 1;
        }

        /* Paint the rectangle. We leave a gap of 2 pixels at the
         * top and bottom, remembering that the grid is painted along
         * the top/bottom line of each row. */
        if (x2 - x1 > 0) {
#if E_MEETING_TIME_SELECTOR_DRAW_GRID_LINES_AT_BOTTOM
            cairo_rectangle (
                cr, x1 - x, y + 2,
                x2 - x1, mts->row_height - 5);
#else
            cairo_rectangle (
                cr, x1 - x, y + 3,
                x2 - x1, mts->row_height - 5);
#endif
            cairo_fill (cr);
        }
    }
}

/*
 * CANVAS ITEM ROUTINES - functions to be a GnomeCanvasItem.
 */

static GnomeCanvasItem *
e_meeting_time_selector_item_point (GnomeCanvasItem *item,
                                    gdouble x,
                                    gdouble y,
                                    gint cx,
                                    gint cy)
{
    return item;
}

static gint
e_meeting_time_selector_item_event (GnomeCanvasItem *item,
                                    GdkEvent *event)
{
    EMeetingTimeSelectorItem *mts_item;

    mts_item = E_MEETING_TIME_SELECTOR_ITEM (item);

    switch (event->type) {
    case GDK_BUTTON_PRESS:
        return e_meeting_time_selector_item_button_press (
            mts_item,
            event);
    case GDK_BUTTON_RELEASE:
        return e_meeting_time_selector_item_button_release (
            mts_item,
            event);
    case GDK_MOTION_NOTIFY:
        return e_meeting_time_selector_item_motion_notify (
            mts_item,
            event);
    default:
        break;
    }

    return FALSE;
}

/* This handles all button press events for the item. If the cursor is over
 * one of the meeting time vertical bars we start a drag. If not we set the
 * meeting time to the nearest half-hour interval.
 * Note that GnomeCanvas converts the event coords to world coords,
 * i.e. relative to the entire canvas scroll area. */
static gint
e_meeting_time_selector_item_button_press (EMeetingTimeSelectorItem *mts_item,
                                           GdkEvent *event)
{
    EMeetingTimeSelector *mts;
    EMeetingTime start_time, end_time;
    EMeetingTimeSelectorPosition position;
    GDate *start_date, *end_date;
    gint x, y;

    mts = mts_item->mts;
    x = (gint) event->button.x;
    y = (gint) event->button.y;

    /* Check if we are starting a drag of the vertical meeting time bars.*/
    position = e_meeting_time_selector_item_get_drag_position (
        mts_item,
        x, y);
    if (position != E_MEETING_TIME_SELECTOR_POS_NONE) {
        GdkGrabStatus grab_status;
        GdkDevice *event_device;
        guint32 event_time;

        event_device = gdk_event_get_device (event);
        event_time = gdk_event_get_time (event);

        grab_status = gnome_canvas_item_grab (
            GNOME_CANVAS_ITEM (mts_item),
            GDK_POINTER_MOTION_MASK |
            GDK_BUTTON_RELEASE_MASK,
            mts_item->resize_cursor,
            event_device,
            event_time);

        if (grab_status == GDK_GRAB_SUCCESS) {
            mts->dragging_position = position;
            return TRUE;
        }
    }

    /* Convert the x coordinate into a EMeetingTimeSelectorTime. */
    e_meeting_time_selector_calculate_time (mts, x, &start_time);
    start_date = &start_time.date;
    end_date = &end_time.date;

    /* Find the nearest half-hour or hour interval, depending on whether
     * zoomed_out is set. */
    if (!mts->all_day) {
        gint astart_year, astart_month, astart_day, astart_hour, astart_minute;
        gint aend_year, aend_month, aend_day, aend_hour, aend_minute;
        gint hdiff, mdiff;
        GDate asdate, aedate;

        e_meeting_time_selector_get_meeting_time (
            mts_item->mts,
            &astart_year,
            &astart_month,
            &astart_day,
            &astart_hour,
            &astart_minute,
            &aend_year,
            &aend_month,
            &aend_day,
            &aend_hour,
            &aend_minute);
        if (mts->zoomed_out)
            start_time.minute = 0;
        else
            start_time.minute -= start_time.minute % 30;

        g_date_set_dmy (&asdate, astart_day, astart_month, astart_year);
        g_date_set_dmy (&aedate, aend_day, aend_month, aend_year);
        end_time = start_time;
        mdiff = end_time.minute + aend_minute - astart_minute;
        hdiff = end_time.hour + aend_hour - astart_hour + (24 * g_date_days_between (&asdate, &aedate));
        while (mdiff < 0) {
            mdiff += 60;
            hdiff -= 1;
        }
        while (mdiff > 60) {
            mdiff -= 60;
            hdiff += 1;
        }
        while (hdiff < 0) {
            hdiff += 24;
            g_date_subtract_days (end_date, 1);
        }
        while (hdiff >= 24) {
            hdiff -= 24;
            g_date_add_days (end_date, 1);
        }
        end_time.minute = mdiff;
        end_time.hour = hdiff;
    } else {
        start_time.hour = 0;
        start_time.minute = 0;
        end_time = start_time;
        g_date_add_days (&end_time.date, 1);
    }

    /* Fix any overflows. */
    e_meeting_time_selector_fix_time_overflows (&end_time);

    /* Set the new meeting time. */
    e_meeting_time_selector_set_meeting_time (
        mts_item->mts,
        g_date_get_year (start_date),
        g_date_get_month (start_date),
        g_date_get_day (start_date),
        start_time.hour,
        start_time.minute,
        g_date_get_year (end_date),
        g_date_get_month (end_date),
        g_date_get_day (end_date),
        end_time.hour,
        end_time.minute);

    return FALSE;
}

/* This handles all button release events for the item. If we were dragging,
 * we finish the drag. */
static gint
e_meeting_time_selector_item_button_release (EMeetingTimeSelectorItem *mts_item,
                                             GdkEvent *event)
{
    EMeetingTimeSelector *mts;

    mts = mts_item->mts;

    /* Reset any drag. */
    if (mts->dragging_position != E_MEETING_TIME_SELECTOR_POS_NONE) {
        mts->dragging_position = E_MEETING_TIME_SELECTOR_POS_NONE;
        e_meeting_time_selector_remove_timeout (mts);
        gnome_canvas_item_ungrab (
            GNOME_CANVAS_ITEM (mts_item),
            event->button.time);
    }

    return FALSE;
}

/* This handles all motion notify events for the item. If button1 is pressed
 * we check if a drag is in progress. If not, we set the cursor if we are over
 * the meeting time vertical bars. Note that GnomeCanvas doesn't use motion
 * hints, which may affect performance. */
static gint
e_meeting_time_selector_item_motion_notify (EMeetingTimeSelectorItem *mts_item,
                                            GdkEvent *event)
{
    EMeetingTimeSelector *mts;
    EMeetingTimeSelectorPosition position;
    GdkCursor *cursor;
    gint x, y;

    mts = mts_item->mts;
    x = (gint) event->motion.x;
    y = (gint) event->motion.y;

    if (mts->dragging_position != E_MEETING_TIME_SELECTOR_POS_NONE) {
        e_meeting_time_selector_drag_meeting_time (mts, x);
        return TRUE;
    }

    position = e_meeting_time_selector_item_get_drag_position (
        mts_item,
        x, y);

    /* Determine which cursor should be used. */
    if (position != E_MEETING_TIME_SELECTOR_POS_NONE)
        cursor = mts_item->resize_cursor;
    /* If the Main window shows busy cursor show the same */
    else if (mts_item->mts->last_cursor_set == GDK_WATCH)
        cursor = mts_item->busy_cursor;
    else
        cursor = mts_item->normal_cursor;

    /* Only set the cursor if it is different to the last one we set. */
    if (mts_item->last_cursor_set != cursor) {
        GdkWindow *window;
        GnomeCanvas *canvas;

        mts_item->last_cursor_set = cursor;

        canvas = GNOME_CANVAS_ITEM (mts_item)->canvas;
        window = gtk_widget_get_window (GTK_WIDGET (canvas));
        gdk_window_set_cursor (window, cursor);
    }

    return FALSE;
}

static EMeetingTimeSelectorPosition
e_meeting_time_selector_item_get_drag_position (EMeetingTimeSelectorItem *mts_item,
                                                gint x,
                                                gint y)
{
    EMeetingTimeSelector *mts;
    gboolean is_display_top;
    gint meeting_start_x, meeting_end_x;

    mts = mts_item->mts;

    is_display_top = (GTK_WIDGET (GNOME_CANVAS_ITEM (mts_item)->canvas) == mts->display_top) ? TRUE : FALSE;

    if (is_display_top && y < mts->row_height * 2)
        return E_MEETING_TIME_SELECTOR_POS_NONE;

    if (!e_meeting_time_selector_get_meeting_time_positions (mts, &meeting_start_x, &meeting_end_x))
        return E_MEETING_TIME_SELECTOR_POS_NONE;

    if (x >= meeting_end_x - 2 && x <= meeting_end_x + 2)
        return E_MEETING_TIME_SELECTOR_POS_END;

    if (x >= meeting_start_x - 2 && x <= meeting_start_x + 2)
        return E_MEETING_TIME_SELECTOR_POS_START;

    return E_MEETING_TIME_SELECTOR_POS_NONE;
}

static gboolean
e_meeting_time_selector_item_calculate_busy_range (EMeetingTimeSelector *mts,
                                                   gint row,
                                                   gint x,
                                                   gint width,
                                                   gint *start_x,
                                                   gint *end_x)
{
    EMeetingAttendee *ia;
    EMeetingTime busy_periods_start;
    EMeetingTime busy_periods_end;

    ia = e_meeting_store_find_attendee_at_row (mts->model, row);
    busy_periods_start = e_meeting_attendee_get_start_busy_range (ia);
    busy_periods_end = e_meeting_attendee_get_end_busy_range (ia);

    *start_x = -1;
    *end_x = -1;

    if (!g_date_valid (&busy_periods_start.date)
        || !g_date_valid (&busy_periods_end.date))
        return FALSE;

    *start_x = e_meeting_time_selector_calculate_time_position (mts, &busy_periods_start) - x - 1;

    *end_x = e_meeting_time_selector_calculate_time_position (mts, &busy_periods_end) - x;

    return TRUE;
}

void
e_meeting_time_selector_item_set_normal_cursor (EMeetingTimeSelectorItem *mts_item)
{
    GnomeCanvas *canvas;
    GdkWindow *window;

    g_return_if_fail (IS_E_MEETING_TIME_SELECTOR_ITEM (mts_item));

    canvas = GNOME_CANVAS_ITEM (mts_item)->canvas;
    window = gtk_widget_get_window (GTK_WIDGET (canvas));
    if (window)
        gdk_window_set_cursor (window, mts_item->normal_cursor);
}