aboutsummaryrefslogblamecommitdiffstats
path: root/calendar/gui/gncal-full-day.c
blob: c3151f6dcf8502e09bce428819813fe46cc3c434 (plain) (tree)
1
2
3
4
5
6
7
8
9



                                                  
                                           

   
                   
                           
                               
                        
                      
                           
                       


                     
                     
                     
                        
                                     











                                                                
                               







                           
              
                          



                                                                                  
                    

                     


                            



                          
                           

  



                               
                      

  
 





                        








                                                                       
                                                                      











                                                                           

                                                                     



                                                                     


                                                                             
 

                                                    
 
                                       
 
                                                
 
 




















































                                                                                                            
                                                                    






                                                 
                                              



                                                 
                                 

                                 
                      
 





                                                                                


























                                                                                                          

                                                               









                                                                                                
                                   













                                                                                    
                      




                            

                                             



                              


                                                         

                   
                                             
                                 
                                                                                    



                                                                                                            

                                
 
                           
                           
                                          
                                          
 









                                                                                                                    

                         

                                                       




                                                      







                                                         
                                                                                               






                                                                                               














































































                                                                                                  
                                                    







                                                                                             
                                                       
 




                                                  

                                                                     





                                                             



















































                                                                                           
           





                                                                       
                               




                     


                                                                        
                              


                     

                                               




                                                                                    
                               


                                       
                                                  

                                                                                      
 
                     

 













                                                                                    

















                                                                                 
              
                                                                            

                     









                                                  


                              
                                             
 
                                                                                    

                                                                  


                                                                 


                                                                               
 


                                                                                
 



                                                                          



                                                                             
                          


                                                                
 
                                                                    






                                                   














                                                                                 

 
                          
                                                      









                                                                                          
                           






















                                                                                 
                                                      

              
 
                                    

                                       



                      




























































                                                                                          
                                                                                       
 
                          


                                      
                          


                                                                                
                                                                                              


                            
                               






                                       
                          
                             
 
                                                    
 



                                                                                                    
                                                                               
                                                                     
 
                                          
 

























                                                                                            
                                        


                                           
                                                




                                                                  









                                                                                       



                                                       
                                                       

                                                           
                                                             

                                                                   


                                                                           
                                                           
                                                                 

                                                                 

                                                          

                                                 





                                                        
                                                      





                                                           

                                 
                                                          








                                          

                        





                                                      







                                                                                 




                                                                      


















                                                                        





































                                                                               

                                          
                              

                                 
                        





                                                      

                                          








                                                               




                                                                 







                                                                                                              
 
                                                                    
                                                             
 


                                                                               
 




                                            
 

                                                      
 
                                          
 

                                                                               
 

                                                     
 

                                                  
 

                                                                        

 
           


                                                      

                                       
                                  

                               
                 
                  




                                      








                                                         




                                                    



                                                                                         
 
                          
 
                                       
 
                                              



                                                    




                                                             







                                                                           
                                         
 

                                                                
 





                                                                             
 
                            
 
                                     
                            
                                                             
                                   
                                                             





                                                                                     
                                                                     

         















                                                                                   
 













                                                                                     




                                                      
                                                             


                                  

                                
                                      











                                                                                                       


                                                    

                                     
                                      




                                                                   






                                                                              

                                                             
                                            












                                                                         






















                                                                     
                                                           
 
                              


                                
 


                                                      
 

                                          
 
                                          
 
















                                                                                 


           























                                                                   




















                                                                                                               
                                                                                        







                                                                                                                     

                              





                                                      

                                          




                                                                               



                                  
                                                                             


                        
                         
 

                         

                                                               




                                                                                 
 
                                             


                                        










                                     
                       
                       





                                                                              

                                                      

                                

                                               




                                                            
                                                                                      
                                                                 
                                                                                  




                                                                             

































                                                                                          
          

                                                       
                          
                     
                    

                             
                       
                      


                                             
 
                                      
 





                                                       
                                      
 
                                        
 
                                            
 


                                                  


                                                                              
 
                                             
 




                                                                 
                                                          
                                               
 









                                                                                                 


                                         
                                                                                
 
                                                           

                                     
                                      
 
                                        


                                                          









                                                                           
                                                  
 

                                                       

                                  
                                             

                                                       





                                                                 
                                                          







                                               



                                                       
                                                                                         



















                                                                                                   
                                      

                                     
                                                               































                                                                      

                                               
                             

                   
                      
 
                                
 
                                                                                          
 


                                                         
 

                                         
 






                                                                        
 
                      
 
                       


                                                                                                            








                                                           
                                                                      
 

                                                                            
 

                                                                                        

                      
 
                              
                                                                      
 

                                                      


                                      
                                                                







                                        
           
                                                                                                          
 
                     
                       
 
                                                                                      
 
                                               
 

                                                   
 













                                                   
                                                                              

                                                     
 

                                                 
                                       
 
                                                                                        

 














                                                                        



                                                  

                             

                                                                      
                                              




                                                      
                                                                                         



                       

                              





                                                 
                                        

                      
 


                                        
 
                                  










                                                                


                                         







                                                                 

                                                  




                                


                                                                    
                                              







                                                                           



                       

                              








                                              

                     

 



                                                                
                     
                    











                                                                 

                                                                                
 


                                                                         



                     
           
























                                                                                          
























                                                                  



















                                                                                              










                                                    
          






                                                                                                    

                 

 
    
                                                                         
 
                        
                     
        


                                                       


                                    

















                                                                                                            




                                                                               







                                                 











                                                                                 
                                                     




                                                                             

                             






                                                                     



                                                            
                                                         

         
 



                                                                                    




















                                                                                             
    
                                                                   





                                           
                                       



                                                                                 
                                        





















                                                                                            



                                       
 

                                                       


                                




                                                                                              
 
/* Full day widget for gncal
 *
 * Copyright (C) 1998 The Free Software Foundation
 *
 * Author: Federico Mena <quartic@gimp.org>
 */

#include <string.h>
#include <gdk/gdkkeysyms.h>
#include <gtk/gtkdrawingarea.h>
#include <gtk/gtktext.h>
#include "eventedit.h"
#include "gncal-full-day.h"
#include "view-utils.h"


#define TEXT_BORDER 2
#define HANDLE_SIZE 8
#define MIN_WIDTH 300
#define XOR_RECT_WIDTH 2
#define UNSELECT_TIMEOUT 150 /* ms */


typedef struct {
    iCalObject *ico;
    GtkWidget  *widget;
    GdkWindow  *window;
    int         lower_row; /* zero is first displayed row */
    int         rows_used;
    int         x;         /* coords of child's window */
    int         y;
    int         width;
    int         height;
    time_t      start, end;
} Child;

struct layout_row {
    int  intersections;
    int *slots;
};

struct drag_info {
    enum {
        DRAG_NONE,
        DRAG_SELECT,        /* selecting a range in the main window */
        DRAG_MOVE,      /* moving a child */
        DRAG_SIZE_TOP,      /* resizing a child */
        DRAG_SIZE_BOTTOM
    } drag_mode;

    Child *child;
    int child_click_y;
    int child_start_row;
    int child_rows_used;

    int sel_click_row;
    int sel_start_row;
    int sel_rows_used;
    guint32 click_time;
};

struct menu_item {
    char *text;
    GtkSignalFunc callback;
    gpointer data;
    int sensitive;
};


enum {
    RANGE_ACTIVATED,
    LAST_SIGNAL
};


static void gncal_full_day_class_init     (GncalFullDayClass *class);
static void gncal_full_day_init           (GncalFullDay      *fullday);
static void gncal_full_day_destroy        (GtkObject         *object);
static void gncal_full_day_map            (GtkWidget         *widget);
static void gncal_full_day_unmap          (GtkWidget         *widget);
static void gncal_full_day_realize        (GtkWidget         *widget);
static void gncal_full_day_unrealize      (GtkWidget         *widget);
static void gncal_full_day_draw           (GtkWidget         *widget,
                       GdkRectangle      *area);
static void gncal_full_day_draw_focus     (GtkWidget         *widget);
static void gncal_full_day_size_request   (GtkWidget         *widget,
                       GtkRequisition    *requisition);
static void gncal_full_day_size_allocate  (GtkWidget         *widget,
                       GtkAllocation     *allocation);
static gint gncal_full_day_button_press   (GtkWidget         *widget,
                       GdkEventButton    *event);
static gint gncal_full_day_button_release (GtkWidget         *widget,
                       GdkEventButton    *event);
static gint gncal_full_day_motion         (GtkWidget         *widget,
                       GdkEventMotion    *event);
static gint gncal_full_day_expose         (GtkWidget         *widget,
                       GdkEventExpose    *event);
static gint gncal_full_day_key_press      (GtkWidget         *widget,
                       GdkEventKey       *event);
static gint gncal_full_day_focus_in       (GtkWidget         *widget,
                       GdkEventFocus     *event);
static gint gncal_full_day_focus_out      (GtkWidget         *widget,
                       GdkEventFocus     *event);
static void gncal_full_day_foreach        (GtkContainer      *container,
                       GtkCallback        callback,
                       gpointer           callback_data);

static void range_activated (GncalFullDay *fullday);


static GtkContainerClass *parent_class;

static int fullday_signals[LAST_SIGNAL] = { 0 };


static void
get_tm_range (GncalFullDay *fullday,
          time_t time_lower, time_t time_upper,
          struct tm *lower, struct tm *upper,
          int *lower_row, int *rows_used)
{
    struct tm tm_lower, tm_upper;
    int lmin, umin;
    int lrow;

    /* Lower */

    tm_lower = *localtime (&time_lower);

    if ((tm_lower.tm_min % fullday->interval) != 0) {
        tm_lower.tm_min -= tm_lower.tm_min % fullday->interval; /* round down */
        mktime (&tm_lower);
    }

    /* Upper */

    tm_upper = *localtime (&time_upper);

    if ((tm_upper.tm_min % fullday->interval) != 0) {
        tm_upper.tm_min += fullday->interval - (tm_upper.tm_min % fullday->interval); /* round up */
        mktime (&tm_upper);
    }

    if (lower)
        *lower = tm_lower;

    if (upper)
        *upper = tm_upper;

    lmin = 60 * tm_lower.tm_hour + tm_lower.tm_min;
    umin = 60 * tm_upper.tm_hour + tm_upper.tm_min;

    if (umin == 0) /* midnight of next day? */
        umin = 60 * 24;

    lrow = lmin / fullday->interval;

    if (lower_row)
        *lower_row = lrow;

    if (rows_used)
        *rows_used = (umin - lmin) / fullday->interval;
}

static void
child_map (GncalFullDay *fullday, Child *child)
{
    gdk_window_show (child->window);
    gtk_widget_show (child->widget); /* OK, not just a map... */
}

static void
child_unmap (GncalFullDay *fullday, Child *child)
{
    gdk_window_hide (child->window);

    if (GTK_WIDGET_MAPPED (child->widget))
        gtk_widget_unmap (child->widget);
}

static void
child_set_text_pos (Child *child)
{
    GtkAllocation allocation;
    int has_focus;

    has_focus = GTK_WIDGET_HAS_FOCUS (child->widget);

    allocation.x = HANDLE_SIZE;
    allocation.y = has_focus ? HANDLE_SIZE : 0;
    allocation.width = child->width - HANDLE_SIZE;
    allocation.height = child->height - (has_focus ? (2 * HANDLE_SIZE) : 0);

    gtk_widget_size_request (child->widget, &child->widget->requisition); /* FIXME: is this needed? */
    gtk_widget_size_allocate (child->widget, &allocation);
}

static void
child_realize (GncalFullDay *fullday, Child *child)
{
    GdkWindowAttr attributes;
    gint attributes_mask;
    GtkWidget *widget;

    widget = GTK_WIDGET (fullday);

    attributes.window_type = GDK_WINDOW_CHILD;
    attributes.x = child->x;
    attributes.y = child->y;
    attributes.width = child->width;
    attributes.height = child->height;
    attributes.wclass = GDK_INPUT_OUTPUT;
    attributes.visual = gtk_widget_get_visual (widget);
    attributes.colormap = gtk_widget_get_colormap (widget);
    attributes.cursor = fullday->up_down_cursor;
    attributes.event_mask = (GDK_EXPOSURE_MASK
                 | GDK_BUTTON_PRESS_MASK
                 | GDK_BUTTON_RELEASE_MASK
                 | GDK_BUTTON_MOTION_MASK
                 | GDK_POINTER_MOTION_HINT_MASK
                 | GDK_KEY_PRESS_MASK);

    attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP | GDK_WA_CURSOR;

    child->window = gdk_window_new (widget->window, &attributes, attributes_mask);
    gdk_window_set_user_data (child->window, widget);

    gtk_style_set_background (widget->style, child->window, GTK_STATE_NORMAL);

    gtk_widget_set_parent_window (child->widget, child->window);

    child_set_text_pos (child);
}

static void
child_unrealize (GncalFullDay *fullday, Child *child)
{
    gdk_window_set_user_data (child->window, NULL);
    gdk_window_destroy (child->window);
    child->window = NULL;
}

static void
child_draw (GncalFullDay *fullday, Child *child, GdkRectangle *area, int draw_child)
{
    GdkRectangle arect, rect, dest;
    int has_focus;

    if (!area) {
        arect.x = 0;
        arect.y = 0;

        arect.width = child->width;
        arect.height = child->height;

        area = &arect;
    }

    has_focus = GTK_WIDGET_HAS_FOCUS (child->widget);

    /* Left handle */

    rect.x = 0;
    rect.y = has_focus ? HANDLE_SIZE : 0;
    rect.width = HANDLE_SIZE;
    rect.height = has_focus ? (child->height - 2 * HANDLE_SIZE) : child->height;

    if (gdk_rectangle_intersect (&rect, area, &dest))
        view_utils_draw_textured_frame (GTK_WIDGET (fullday), child->window, &rect, GTK_SHADOW_OUT);

    if (has_focus) {
        /* Top handle */

        rect.x = 0;
        rect.y = 0;
        rect.width = child->width;
        rect.height = HANDLE_SIZE;

        if (gdk_rectangle_intersect (&rect, area, &dest))
            view_utils_draw_textured_frame (GTK_WIDGET (fullday), child->window, &rect, GTK_SHADOW_OUT);

        /* Bottom handle */

        rect.y = child->height - HANDLE_SIZE;

        if (gdk_rectangle_intersect (&rect, area, &dest))
            view_utils_draw_textured_frame (GTK_WIDGET (fullday), child->window, &rect, GTK_SHADOW_OUT);
    }

    if (draw_child) {
        area->x -= HANDLE_SIZE;
        area->y -= has_focus ? HANDLE_SIZE : 0;
        gtk_widget_draw (child->widget, area);
    }
}

static void
child_range_changed (GncalFullDay *fullday, Child *child)
{
    struct tm start, end;
    int lower_row, rows_used;
    int f_lower_row;

    /* Calc display range for event */

    get_tm_range (fullday, child->start, child->end, &start, &end, &lower_row, &rows_used);
    get_tm_range (fullday, fullday->lower, fullday->upper, NULL, NULL, &f_lower_row, NULL);

    child->lower_row = lower_row - f_lower_row;
    child->rows_used = rows_used;
}

static void
popup_menu (struct menu_item *items, int nitems, guint32 time)
{
    GtkWidget *menu;
    GtkWidget *item;
    int i;

    menu = gtk_menu_new (); /* FIXME: this baby is never freed */

    for (i = 0; i < nitems; i++) {
        if (items[i].text) {
            item = gtk_menu_item_new_with_label (_(items[i].text));
            gtk_signal_connect (GTK_OBJECT (item), "activate",
                        items[i].callback,
                        items[i].data);
            gtk_widget_set_sensitive (item, items[i].sensitive);
        } else
            item = gtk_menu_item_new ();

        gtk_widget_show (item);
        gtk_menu_append (GTK_MENU (menu), item);
    }

    gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, 3, time);
}

static void
new_appointment (GtkWidget *widget, gpointer data)
{
    GncalFullDay *fullday;

    fullday = GNCAL_FULL_DAY (data);

    /* FIXME: this should set up the start/end times in the event
     * editor to whatever the selection range is.  If there is no
     * selection, then default to something sensible, like the row
     * at which the button was clicked on when popping up the menu.
     */

    event_editor_new (fullday->calendar, NULL);
}

static void
edit_appointment (GtkWidget *widget, gpointer data)
{
    Child *child;

    child = data;

    event_editor_new (GNCAL_FULL_DAY (child->widget->parent)->calendar, child->ico);
}

static void
delete_appointment (GtkWidget *widget, gpointer data)
{
    Child *child;

    child = data;

    /* FIXME */

    printf ("Yay!  delete_appointment() not yet implemented\n");
}

static void
child_popup_menu (GncalFullDay *fullday, Child *child, guint32 event_time)
{
    int sensitive;

    static struct menu_item child_items[] = {
        { N_("Edit this appointment..."), (GtkSignalFunc) edit_appointment, NULL, TRUE },
        { N_("Delete this appointment"), (GtkSignalFunc) delete_appointment, NULL, TRUE },
        { NULL, NULL, NULL, TRUE },
        { N_("New appointment..."), (GtkSignalFunc) new_appointment, NULL, TRUE }
    };

    child_items[0].data = child;
    child_items[1].data = child;
    child_items[3].data = fullday;

    sensitive = (child->ico->user_data == NULL);

    child_items[0].sensitive = sensitive;
    child_items[1].sensitive = sensitive;

    popup_menu (child_items, sizeof (child_items) / sizeof (child_items[0]), event_time);
}

static void
child_realized_setup (GtkWidget *widget, gpointer data)
{
    Child *child;
    GncalFullDay *fullday;

    child = data;
    fullday = GNCAL_FULL_DAY (widget->parent);

    gdk_window_set_cursor (widget->window, fullday->beam_cursor);

    gtk_text_insert (GTK_TEXT (widget), NULL, NULL, NULL,
             child->ico->summary,
             strlen (child->ico->summary));
}

static void
child_set_pos (GncalFullDay *fullday, Child *child, int x, int y, int width, int height)
{
    child->x = x;
    child->y = y;
    child->width = width;
    child->height = height;

    if (!child->window) /* realized? */
        return;

    child_set_text_pos (child);
    gdk_window_move_resize (child->window, x, y, width, height);
}

static int
calc_row_height (GncalFullDay *fullday)
{
    int f_rows;
    GtkWidget *widget;

    get_tm_range (fullday, fullday->lower, fullday->upper, NULL, NULL, NULL, &f_rows);

    widget = GTK_WIDGET (fullday);

    return (widget->allocation.height - 2 * widget->style->klass->ythickness) / f_rows;
}

static void
child_set_size (Child *child)
{
    int row_height;
    int x, y, width, height;
    GncalFullDay *fullday;

    fullday = GNCAL_FULL_DAY (child->widget->parent);

    row_height = calc_row_height (fullday);

    x = child->x;
    y = child->lower_row * row_height + GTK_WIDGET (fullday)->style->klass->ythickness;
    width = child->width;
    height = child->rows_used * row_height;

    if (GTK_WIDGET_HAS_FOCUS (child->widget)) {
        y -= HANDLE_SIZE;
        height += 2 * HANDLE_SIZE;
    }

    child_set_pos (fullday, child, x, y, width, height);
}

static gint
child_focus_in (GtkWidget *widget, GdkEventFocus *event, gpointer data)
{
    Child *child;

    child = data;

    child_set_size (child);

    return FALSE;
}

static gint
child_focus_out (GtkWidget *widget, GdkEventFocus *event, gpointer data)
{
    Child *child;
    GncalFullDay *fullday;

    child = data;

    /* Update summary in calendar object */

    if (child->ico->summary)
        g_free (child->ico->summary);

    child->ico->summary = gtk_editable_get_chars (GTK_EDITABLE (widget), 0, -1);

    child_set_size (child);

    /* Notify calendar of change */

    fullday = GNCAL_FULL_DAY (widget->parent);

    gnome_calendar_object_changed (fullday->calendar, child->ico, CHANGE_SUMMARY);

    return FALSE;
}

static gint
child_key_press (GtkWidget *widget, GdkEventKey *event, gpointer data)
{
    if (event->keyval != GDK_Escape)
        return FALSE;

    /* If user pressed Esc, un-focus the child by focusing the fullday widget */

    gtk_signal_emit_stop_by_name (GTK_OBJECT (widget), "key_press_event");
    gtk_widget_grab_focus (widget->parent);

    return FALSE;
}

static gint
child_button_press (GtkWidget *widget, GdkEventButton *event, gpointer data)
{
    Child *child;
    GncalFullDay *fullday;

    if (event->button != 3)
        return FALSE;

    child = data;
    fullday = GNCAL_FULL_DAY (widget->parent);

    gtk_signal_emit_stop_by_name (GTK_OBJECT (widget), "button_press_event");
    child_popup_menu (fullday, child, event->time);

    return TRUE;
}

static Child *
child_new (GncalFullDay *fullday, time_t start, time_t end, iCalObject *ico)
{
    Child *child;

    child = g_new (Child, 1);

    child->ico = ico;
    child->widget = gtk_text_new (NULL, NULL);
    child->window = NULL;
    child->x = 0;
    child->y = 0;
    child->width = 0;
    child->height = 0;
    child->start  = start;
    child->end    = end;
    
    child_range_changed (fullday, child);

    /* We set the i-beam cursor and the initial summary text upon realization */

    gtk_signal_connect (GTK_OBJECT (child->widget), "realize",
                (GtkSignalFunc) child_realized_setup,
                child);

    gtk_signal_connect_after (GTK_OBJECT (child->widget), "focus_in_event",
                  (GtkSignalFunc) child_focus_in,
                  child);

    gtk_signal_connect_after (GTK_OBJECT (child->widget), "focus_out_event",
                  (GtkSignalFunc) child_focus_out,
                  child);

    gtk_signal_connect (GTK_OBJECT (child->widget), "key_press_event",
                (GtkSignalFunc) child_key_press,
                child);

    gtk_signal_connect (GTK_OBJECT (child->widget), "button_press_event",
                (GtkSignalFunc) child_button_press,
                child);

    /* Finish setup */

    gtk_text_set_editable (GTK_TEXT (child->widget), TRUE);
    gtk_text_set_word_wrap (GTK_TEXT (child->widget), TRUE);

    gtk_widget_set_parent (child->widget, GTK_WIDGET (fullday));

    return child;
}

static void
child_destroy (GncalFullDay *fullday, Child *child)
{
    /* Unparent the child widget manually as we don't have a remove method */

    gtk_widget_ref (child->widget);

    gtk_widget_unparent (child->widget);

    if (GTK_WIDGET_MAPPED (fullday))
        child_unmap (fullday, child);

    if (GTK_WIDGET_REALIZED (fullday))
        child_unrealize (fullday, child);

    gtk_widget_unref (child->widget);

    g_free (child);
}

static struct layout_row *
layout_get_rows (GncalFullDay *fullday, int *rowcount)
{
    struct layout_row *rows;
    int max_i;
    int f_rows;
    GList *children;
    Child *child;
    int i, n;

    get_tm_range (fullday, fullday->lower, fullday->upper, NULL, NULL, NULL, &f_rows);

    *rowcount = f_rows;
    rows = g_new0 (struct layout_row, f_rows);
    max_i = 0;

    for (children = fullday->children; children; children = children->next) {
        child = children->data;

        for (i = 0; i < child->rows_used; i++) {
            n = child->lower_row + i;

            rows[n].intersections++;
            
            if (rows[n].intersections > max_i)
                max_i = rows[n].intersections;
        }
    }

    for (i = 0; i < f_rows; i++)
        rows[i].slots = g_new0 (int, max_i);

    return rows;
}

static void
layout_free_rows (struct layout_row *rows, int f_rows)
{
    int i;

    for (i = 0; i < f_rows; i++)
        g_free (rows[i].slots);

    g_free (rows);
}

static void
layout_get_child_intersections (Child *child, struct layout_row *rows, int *min, int *max)
{
    int i, n;
    int imin, imax;

    imax = 0;

    for (i = 0; i < child->rows_used; i++) {
        n = child->lower_row + i;

        if (rows[n].intersections > imax)
            imax = rows[n].intersections;
    }

    imin = imax;

    for (i = 0; i < child->rows_used; i++) {
        n = child->lower_row + i;

        if (rows[n].intersections < imin)
            imin = rows[n].intersections;
    }

    if (min)
        *min = imin;

    if (max)
        *max = imax;
}

static int
calc_labels_width (GncalFullDay *fullday)
{
    struct tm cur, upper;
    time_t tim, time_upper;
    int width, max_w;
    char buf[256];

    get_tm_range (fullday, fullday->lower, fullday->upper, &cur, &upper, NULL, NULL);

    max_w = 0;

    tim = mktime (&cur);
    time_upper = mktime (&upper);

    while (tim < time_upper) {
        strftime (buf, 256, "%X", &cur);

        width = gdk_string_width (GTK_WIDGET (fullday)->style->font, buf);

        if (width > max_w)
            max_w = width;

        cur.tm_min += fullday->interval;
        tim = mktime (&cur);
    }

    return max_w;
}

static void
layout_child (GncalFullDay *fullday, Child *child, struct layout_row *rows, int left_x)
{
    GtkWidget *widget;

    widget = GTK_WIDGET (fullday);

    child->x = left_x;

    /* FIXME: for now, the children overlap.  Make it layout them nicely. */

    child->width = widget->allocation.width - (widget->style->klass->xthickness + left_x);

    /* Position child */

    child_set_size (child);
}

static void
layout_children (GncalFullDay *fullday)
{
    struct layout_row *rows;
    GList *children;
    GtkWidget *widget;
    int left_x, rowcount;

    rows = layout_get_rows (fullday, &rowcount);

    widget = GTK_WIDGET (fullday);

    left_x = 2 * (widget->style->klass->xthickness + TEXT_BORDER) + calc_labels_width (fullday);

    for (children = fullday->children; children; children = children->next)
        layout_child (fullday, children->data, rows, left_x);

    layout_free_rows (rows, rowcount);
}

guint
gncal_full_day_get_type (void)
{
    static guint full_day_type = 0;

    if (!full_day_type) {
        GtkTypeInfo full_day_info = {
            "GncalFullDay",
            sizeof (GncalFullDay),
            sizeof (GncalFullDayClass),
            (GtkClassInitFunc) gncal_full_day_class_init,
            (GtkObjectInitFunc) gncal_full_day_init,
            (GtkArgSetFunc) NULL,
            (GtkArgGetFunc) NULL
        };

        full_day_type = gtk_type_unique (gtk_container_get_type (), &full_day_info);
    }

    return full_day_type;
}

static void
gncal_full_day_class_init (GncalFullDayClass *class)
{
    GtkObjectClass    *object_class;
    GtkWidgetClass    *widget_class;
    GtkContainerClass *container_class;

    object_class = (GtkObjectClass *) class;
    widget_class = (GtkWidgetClass *) class;
    container_class = (GtkContainerClass *) class;

    parent_class = gtk_type_class (gtk_container_get_type ());

    fullday_signals[RANGE_ACTIVATED] =
        gtk_signal_new ("range_activated",
                GTK_RUN_LAST,
                object_class->type,
                GTK_SIGNAL_OFFSET (GncalFullDayClass, range_activated),
                gtk_signal_default_marshaller,
                GTK_TYPE_NONE, 0);

    gtk_object_class_add_signals (object_class, fullday_signals, LAST_SIGNAL);

    object_class->destroy = gncal_full_day_destroy;

    widget_class->map = gncal_full_day_map;
    widget_class->unmap = gncal_full_day_unmap;
    widget_class->realize = gncal_full_day_realize;
    widget_class->unrealize = gncal_full_day_unrealize;
    widget_class->draw = gncal_full_day_draw;
    widget_class->draw_focus = gncal_full_day_draw_focus;
    widget_class->size_request = gncal_full_day_size_request;
    widget_class->size_allocate = gncal_full_day_size_allocate;
    widget_class->button_press_event = gncal_full_day_button_press;
    widget_class->button_release_event = gncal_full_day_button_release;
    widget_class->motion_notify_event = gncal_full_day_motion;
    widget_class->expose_event = gncal_full_day_expose;
    widget_class->key_press_event = gncal_full_day_key_press;
    widget_class->focus_in_event = gncal_full_day_focus_in;
    widget_class->focus_out_event = gncal_full_day_focus_out;

    container_class->foreach = gncal_full_day_foreach;

    class->range_activated = range_activated;
}

static void
gncal_full_day_init (GncalFullDay *fullday)
{
    GTK_WIDGET_UNSET_FLAGS (fullday, GTK_NO_WINDOW);
    GTK_WIDGET_SET_FLAGS (fullday, GTK_CAN_FOCUS);

    fullday->calendar = NULL;

    fullday->lower = 0;
    fullday->upper = 0;
    fullday->interval = 30; /* 30 minutes by default */

    fullday->children = NULL;
    fullday->drag_info = g_new0 (struct drag_info, 1);

    fullday->up_down_cursor = NULL;
    fullday->beam_cursor = NULL;
}

static void
gncal_full_day_destroy (GtkObject *object)
{
    GncalFullDay *fullday;
    GList *children;
    Child *child;

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

    fullday = GNCAL_FULL_DAY (object);

    /* Unparent the children manually as we don't have a remove method */

    for (children = fullday->children; children; children = children->next) {
        child = children->data;

        gtk_widget_unparent (child->widget);
    }

    g_list_free (fullday->children);
    g_free (fullday->drag_info);

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

GtkWidget *
gncal_full_day_new (GnomeCalendar *calendar, time_t lower, time_t upper)
{
    GncalFullDay *fullday;

    g_return_val_if_fail (calendar != NULL, NULL);

    fullday = gtk_type_new (gncal_full_day_get_type ());

    fullday->calendar = calendar;

    gncal_full_day_set_bounds (fullday, lower, upper);

    return GTK_WIDGET (fullday);
}

static void
gncal_full_day_map (GtkWidget *widget)
{
    GncalFullDay *fullday;
    GList *children;

    g_return_if_fail (widget != NULL);
    g_return_if_fail (GNCAL_IS_FULL_DAY (widget));

    GTK_WIDGET_SET_FLAGS (widget, GTK_MAPPED);

    fullday = GNCAL_FULL_DAY (widget);

    gdk_window_show (widget->window);

    for (children = fullday->children; children; children = children->next)
        child_map (fullday, children->data);
}

static void
gncal_full_day_unmap (GtkWidget *widget)
{
    GncalFullDay *fullday;
    GList *children;

    g_return_if_fail (widget != NULL);
    g_return_if_fail (GNCAL_IS_FULL_DAY (widget));

    GTK_WIDGET_UNSET_FLAGS (widget, GTK_MAPPED);

    fullday = GNCAL_FULL_DAY (widget);

    gdk_window_hide (widget->window);

    for (children = fullday->children; children; children = children->next)
        child_unmap (fullday, children->data);
}

static void
gncal_full_day_realize (GtkWidget *widget)
{
    GncalFullDay *fullday;
    GdkWindowAttr attributes;
    gint attributes_mask;
    GList *children;

    g_return_if_fail (widget != NULL);
    g_return_if_fail (GNCAL_IS_FULL_DAY (widget));

    GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);

    fullday = GNCAL_FULL_DAY (widget);

    attributes.window_type = GDK_WINDOW_CHILD;
    attributes.x = widget->allocation.x;
    attributes.y = widget->allocation.y;
    attributes.width = widget->allocation.width;
    attributes.height = widget->allocation.height;
    attributes.wclass = GDK_INPUT_OUTPUT;
    attributes.visual = gtk_widget_get_visual (widget);
    attributes.colormap = gtk_widget_get_colormap (widget);
    attributes.event_mask = (gtk_widget_get_events (widget)
                 | GDK_EXPOSURE_MASK
                 | GDK_BUTTON_PRESS_MASK
                 | GDK_BUTTON_RELEASE_MASK
                 | GDK_BUTTON_MOTION_MASK
                 | GDK_POINTER_MOTION_HINT_MASK);

    attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP;

    widget->window = gdk_window_new (gtk_widget_get_parent_window (widget), &attributes, attributes_mask);
    gdk_window_set_user_data (widget->window, widget);

    widget->style = gtk_style_attach (widget->style, widget->window);
    gdk_window_set_background (widget->window, &widget->style->bg[GTK_STATE_PRELIGHT]);

    fullday->up_down_cursor = gdk_cursor_new (GDK_DOUBLE_ARROW);
    fullday->beam_cursor    = gdk_cursor_new (GDK_XTERM);

    for (children = fullday->children; children; children = children->next)
        child_realize (fullday, children->data);
}

static void
gncal_full_day_unrealize (GtkWidget *widget)
{
    GncalFullDay *fullday;
    GList *children;

    g_return_if_fail (widget != NULL);
    g_return_if_fail (GNCAL_IS_FULL_DAY (widget));

    fullday = GNCAL_FULL_DAY (widget);

    for (children = fullday->children; children; children = children->next)
        child_unrealize (fullday, children->data);

    gdk_cursor_destroy (fullday->up_down_cursor);
    fullday->up_down_cursor = NULL;

    gdk_cursor_destroy (fullday->beam_cursor);
    fullday->beam_cursor = NULL;

    if (GTK_WIDGET_CLASS (parent_class)->unrealize)
        (* GTK_WIDGET_CLASS (parent_class)->unrealize) (widget);
}

static void
paint_back (GncalFullDay *fullday, GdkRectangle *area)
{
    GtkWidget *widget;
    GdkRectangle rect, dest, aarea;
    struct drag_info *di;
    int x1, y1, width, height;
    int labels_width;
    int f_rows, row_height;
    int i, y;
    GdkGC *gc;
    struct tm tm;
    char buf[256];

    widget = GTK_WIDGET (fullday);

    if (!area) {
        area = &aarea;

        area->x = 0;
        area->y = 0;
        area->width = widget->allocation.width;
        area->height = widget->allocation.height;
    }

    x1 = widget->style->klass->xthickness;
    y1 = widget->style->klass->ythickness;
    width = widget->allocation.width - 2 * x1;
    height = widget->allocation.height - 2 * y1;

    di = fullday->drag_info;
    labels_width = calc_labels_width (fullday);
    row_height = calc_row_height (fullday);
    get_tm_range (fullday, fullday->lower, fullday->upper, &tm, NULL, NULL, &f_rows);

    /* Frame shadow */

    gtk_widget_draw_focus (widget);

    /* Area for labels before selection */

    rect.x = x1;
    rect.y = y1;
    rect.width = 2 * TEXT_BORDER + labels_width;

    if (di->sel_rows_used == 0)
        rect.height = height;
    else
        rect.height = row_height * di->sel_start_row;

    if (gdk_rectangle_intersect (&rect, area, &dest))
        gdk_draw_rectangle (widget->window,
                    widget->style->bg_gc[GTK_STATE_NORMAL],
                    TRUE,
                    dest.x, dest.y,
                    dest.width, dest.height);

    /* Blank area before selection */

    rect.x += rect.width + widget->style->klass->xthickness;
    rect.width = width - (rect.x - x1);

    if (gdk_rectangle_intersect (&rect, area, &dest))
        gdk_draw_rectangle (widget->window,
                    widget->style->bg_gc[GTK_STATE_PRELIGHT],
                    TRUE,
                    dest.x, dest.y,
                    dest.width, dest.height);

    /* Selection area */

    if (di->sel_rows_used != 0) {
        rect.x = x1;
        rect.y = y1 + row_height * di->sel_start_row;
        rect.width = width;
        rect.height = row_height * di->sel_rows_used;

        if (gdk_rectangle_intersect (&rect, area, &dest))
            gdk_draw_rectangle (widget->window,
                        widget->style->bg_gc[GTK_STATE_SELECTED],
                        TRUE,
                        dest.x, dest.y,
                        dest.width, dest.height);
    }

    /* Areas under selection */

    if (di->sel_rows_used != 0) {
        /* Area for labels */

        rect.x = x1;
        rect.y = y1 + row_height * (di->sel_start_row + di->sel_rows_used);
        rect.width = 2 * TEXT_BORDER + labels_width;
        rect.height = height - rect.y;

        if (gdk_rectangle_intersect (&rect, area, &dest))
            gdk_draw_rectangle (widget->window,
                        widget->style->bg_gc[GTK_STATE_NORMAL],
                        TRUE,
                        dest.x, dest.y,
                        dest.width, dest.height);

        /* Blank area */

        rect.x += rect.width + widget->style->klass->xthickness;
        rect.width = width - (rect.x - x1);
        
        if (gdk_rectangle_intersect (&rect, area, &dest))
            gdk_draw_rectangle (widget->window,
                        widget->style->bg_gc[GTK_STATE_PRELIGHT],
                        TRUE,
                        dest.x, dest.y,
                        dest.width, dest.height);
    }

    /* Vertical division */

    gtk_draw_vline (widget->style, widget->window,
            GTK_STATE_NORMAL,
            y1,
            y1 + height - 1,
            x1 + 2 * TEXT_BORDER + labels_width);

    /* Horizontal divisions */

    y = y1 + row_height - 1;

    for (i = 1; i < f_rows; i++) {
        gdk_draw_line (widget->window,
                   widget->style->black_gc,
                   x1, y,
                   x1 + width - 1, y);

        y += row_height;
    }

    /* Labels */

    y = y1 + ((row_height - 1) - (widget->style->font->ascent + widget->style->font->descent)) / 2;

    rect.x = x1;
    rect.y = y1;
    rect.width = 2 * TEXT_BORDER + labels_width;
    rect.height = row_height - 1;

    for (i = 0; i < f_rows; i++) {
        mktime (&tm);

        if (gdk_rectangle_intersect (&rect, area, &dest)) {
            strftime (buf, 256, "%X", &tm);

            if ((di->sel_rows_used != 0)
                && (i >= di->sel_start_row)
                && (i < (di->sel_start_row + di->sel_rows_used)))
                gc = widget->style->fg_gc[GTK_STATE_SELECTED];
            else
                gc = widget->style->fg_gc[GTK_STATE_NORMAL];

            gdk_draw_string (widget->window,
                     widget->style->font,
                     gc,
                     x1 + TEXT_BORDER,
                     y + widget->style->font->ascent,
                     buf);
        }

        rect.y += row_height;
        y += row_height;

        tm.tm_min += fullday->interval;
    }
}

static void
paint_back_rows (GncalFullDay *fullday, int start_row, int rows_used)
{
    int row_height;
    int xthickness, ythickness;
    GtkWidget *widget;
    GdkRectangle area;

    widget = GTK_WIDGET (fullday);

    row_height = calc_row_height (fullday);

    xthickness = widget->style->klass->xthickness;
    ythickness = widget->style->klass->ythickness;

    area.x = xthickness;
    area.y = ythickness + start_row * row_height;
    area.width = widget->allocation.width - 2 * xthickness;
    area.height = rows_used * row_height;

    paint_back (fullday, &area);
}

static void
gncal_full_day_draw (GtkWidget *widget, GdkRectangle *area)
{
    GncalFullDay *fullday;
    GList *children;
    Child *child;
    GdkRectangle rect, dest;

    g_return_if_fail (widget != NULL);
    g_return_if_fail (GNCAL_IS_FULL_DAY (widget));
    g_return_if_fail (area != NULL);

    if (!GTK_WIDGET_DRAWABLE (widget))
        return;

    fullday = GNCAL_FULL_DAY (widget);

    paint_back (fullday, area);

    for (children = fullday->children; children; children = children->next) {
        child = children->data;

        rect.x = child->x;
        rect.y = child->y;
        rect.width = child->width;
        rect.height = child->height;

        if (gdk_rectangle_intersect (&rect, area, &dest)) {
            dest.x -= child->x;
            dest.y -= child->y;

            child_draw (fullday, child, &dest, TRUE);
        }
    }
}

static void
gncal_full_day_draw_focus (GtkWidget *widget)
{
    g_return_if_fail (widget != NULL);
    g_return_if_fail (GNCAL_IS_FULL_DAY (widget));

    if (!GTK_WIDGET_DRAWABLE (widget))
        return;

    gtk_draw_shadow (widget->style, widget->window,
             GTK_STATE_NORMAL, GTK_SHADOW_ETCHED_IN,
             0, 0,
             widget->allocation.width,
             widget->allocation.height);

    if (GTK_WIDGET_HAS_FOCUS (widget))
        gdk_draw_rectangle (widget->window,
                    widget->style->black_gc,
                    FALSE,
                    0, 0,
                    widget->allocation.width - 1,
                    widget->allocation.height - 1);
}

static void
gncal_full_day_size_request (GtkWidget *widget, GtkRequisition *requisition)
{
    GncalFullDay *fullday;
    int labels_width;
    int rows;

    g_return_if_fail (widget != NULL);
    g_return_if_fail (GNCAL_IS_FULL_DAY (widget));
    g_return_if_fail (requisition != NULL);

    fullday = GNCAL_FULL_DAY (widget);

    /* Border and min width */

    labels_width = calc_labels_width (fullday);

    requisition->width = 2 * widget->style->klass->xthickness + 4 * TEXT_BORDER + labels_width + MIN_WIDTH;
    requisition->height = 2 * widget->style->klass->ythickness;

    /* Rows */

    get_tm_range (fullday, fullday->lower, fullday->upper, NULL, NULL, NULL, &rows);

    requisition->height += (rows * (2 * TEXT_BORDER + widget->style->font->ascent + widget->style->font->descent)
                + (rows - 1)); /* division lines */
}

static void
gncal_full_day_size_allocate (GtkWidget *widget, GtkAllocation *allocation)
{
    GncalFullDay *fullday;

    g_return_if_fail (widget != NULL);
    g_return_if_fail (GNCAL_IS_FULL_DAY (widget));
    g_return_if_fail (allocation != NULL);

    widget->allocation = *allocation;

    fullday = GNCAL_FULL_DAY (widget);

    if (GTK_WIDGET_REALIZED (widget))
        gdk_window_move_resize (widget->window,
                    allocation->x, allocation->y,
                    allocation->width, allocation->height);

    layout_children (fullday);
}

static Child *
find_child_by_window (GncalFullDay *fullday, GdkWindow *window, int *on_text)
{
    GList *children;
    Child *child;
    GtkWidget *owner;

    *on_text = FALSE;

    gdk_window_get_user_data (window, (gpointer *) &owner);

    for (children = fullday->children; children; children = children->next) {
        child = children->data;

        if (child->window == window)
            return child;

        if (child->widget == owner) {
            *on_text = TRUE;
            return child;
        }
    }

    return NULL;
}

static void
draw_xor_rect (GncalFullDay *fullday)
{
    GtkWidget *widget;
    struct drag_info *di;
    int i;
    int row_height;
    int ythickness;

    widget = GTK_WIDGET (fullday);

    gdk_gc_set_function (widget->style->white_gc, GDK_INVERT);
    gdk_gc_set_subwindow (widget->style->white_gc, GDK_INCLUDE_INFERIORS);

    ythickness = widget->style->klass->ythickness;

    di = fullday->drag_info;

    row_height = calc_row_height (fullday);

    for (i = 0; i < XOR_RECT_WIDTH; i++)
        gdk_draw_rectangle (widget->window,
                    widget->style->white_gc,
                    FALSE,
                    di->child->x + i,
                    di->child_start_row * row_height + ythickness + i,
                    di->child->width - 2 * i - 1,
                    di->child_rows_used * row_height - 2 - 2 * i);

    gdk_gc_set_function (widget->style->white_gc, GDK_COPY);
    gdk_gc_set_subwindow (widget->style->white_gc, GDK_CLIP_BY_CHILDREN);
}

static int
get_row_from_y (GncalFullDay *fullday, int y, int round)
{
    GtkWidget *widget;
    int row_height;
    int f_rows;
    int ythickness;

    get_tm_range (fullday, fullday->lower, fullday->upper, NULL, NULL, NULL, &f_rows);

    row_height = calc_row_height (fullday);

    widget = GTK_WIDGET (fullday);

    ythickness = widget->style->klass->ythickness;

    y -= ythickness;

    if (y < 0)
        y = 0;
    else if (y >= (f_rows * row_height))
        y = f_rows * row_height - 1;

    if (round)
        y += row_height / 2;

    y /= row_height;

    if (y > f_rows)
        y = f_rows; /* note that this is 1 more than the last row's index */

    return y;
}

static int
button_1 (GncalFullDay *fullday, GdkEventButton *event)
{
    GtkWidget *widget;
    Child *child;
    int on_text;
    struct drag_info *di;
    gint y;
    int row_height;
    int has_focus;
    int old_start_row, old_rows_used;
    int old_max;
    int paint_start_row, paint_rows_used;

    widget = GTK_WIDGET (fullday);

    if (event->window == widget->window) {
        /* Clicked on main window */

        if (!GTK_WIDGET_HAS_FOCUS (widget))
            gtk_widget_grab_focus (widget);

        /* Prepare for drag */

        di = fullday->drag_info;

        di->drag_mode = DRAG_SELECT;

        old_start_row = di->sel_start_row;
        old_rows_used = di->sel_rows_used;

        di->sel_click_row = get_row_from_y (fullday, event->y, FALSE);
        di->sel_start_row = di->sel_click_row;
        di->sel_rows_used = 1;

        di->click_time = event->time;

        gdk_pointer_grab (widget->window, FALSE,
                  (GDK_BUTTON_MOTION_MASK
                   | GDK_POINTER_MOTION_HINT_MASK
                   | GDK_BUTTON_RELEASE_MASK),
                  NULL,
                  fullday->up_down_cursor,
                  event->time);

        if (old_rows_used == 0) {
            paint_start_row = di->sel_start_row;
            paint_rows_used = di->sel_rows_used;
        } else {
            paint_start_row = MIN (old_start_row, di->sel_start_row);
            old_max = old_start_row + old_rows_used - 1;
            paint_rows_used = MAX (old_max, di->sel_start_row) - paint_start_row + 1;
        }

        paint_back_rows (fullday, paint_start_row, paint_rows_used);
    } else {
        /* Clicked on a child? */

        child = find_child_by_window (fullday, event->window, &on_text);

        if (!child || on_text || child->ico->recur)
            return FALSE;

        /* Prepare for drag */

        di = fullday->drag_info;

        gtk_widget_get_pointer (widget, NULL, &y);

        has_focus = GTK_WIDGET_HAS_FOCUS (child->widget);

        if (has_focus) {
            if (event->y < HANDLE_SIZE)
                di->drag_mode = DRAG_SIZE_TOP;
            else if (event->y >= (child->height - HANDLE_SIZE))
                di->drag_mode = DRAG_SIZE_BOTTOM;
            else
                di->drag_mode = DRAG_MOVE;
        } else
            di->drag_mode = DRAG_MOVE;

        row_height = calc_row_height (fullday);

        di->child = child;

        di->child_click_y = event->y;
        di->child_start_row = child->lower_row;
        di->child_rows_used = child->rows_used;

        gdk_pointer_grab (child->window, FALSE,
                  (GDK_BUTTON_MOTION_MASK
                   | GDK_POINTER_MOTION_HINT_MASK
                   | GDK_BUTTON_RELEASE_MASK),
                  NULL,
                  fullday->up_down_cursor,
                  event->time);

        draw_xor_rect (fullday);
    }

    return FALSE;
}

static int
button_3 (GncalFullDay *fullday, GdkEventButton *event)
{
    static struct menu_item main_items[] = {
        { N_("New appointment..."), (GtkSignalFunc) new_appointment, NULL, TRUE }
    };

    GtkWidget *widget;
    Child *child;
    int on_text;

    widget = GTK_WIDGET (fullday);

    if (event->window == widget->window) {
        /* Clicked on main window */

        if (!GTK_WIDGET_HAS_FOCUS (widget))
            gtk_widget_grab_focus (widget);

        main_items[0].data = fullday;

        popup_menu (main_items, sizeof (main_items) / sizeof (main_items[0]), event->time);
    } else {
        child = find_child_by_window (fullday, event->window, &on_text);

        if (!child || on_text)
            return FALSE;

        child_popup_menu (fullday, child, event->time);
        return TRUE;
    }

    return FALSE;
}

static gint
gncal_full_day_button_press (GtkWidget *widget, GdkEventButton *event)
{
    GncalFullDay *fullday;

    g_return_val_if_fail (widget != NULL, FALSE);
    g_return_val_if_fail (GNCAL_IS_FULL_DAY (widget), FALSE);
    g_return_val_if_fail (event != NULL, FALSE);

    fullday = GNCAL_FULL_DAY (widget);

    switch (event->button) {
    case 1:
        return button_1 (fullday, event);

    case 3:
        return button_3 (fullday, event);

    default:
        break;
    }

    return FALSE;
}

static void
recompute_motion (GncalFullDay *fullday, int y)
{
    struct drag_info *di;
    int f_rows;
    int row;
    int has_focus;

    di = fullday->drag_info;

    get_tm_range (fullday, fullday->lower, fullday->upper, NULL, NULL, NULL, &f_rows);

    switch (di->drag_mode) {
    case DRAG_SELECT:
        row = get_row_from_y (fullday, y, FALSE);

        if (row >= f_rows)
            row = f_rows - 1;

        if (row < di->sel_click_row) {
            di->sel_start_row = row;
            di->sel_rows_used = di->sel_click_row - row + 1;
        } else {
            di->sel_start_row = di->sel_click_row;
            di->sel_rows_used = row - di->sel_start_row + 1;
        }

        break;

    case DRAG_MOVE:
        has_focus = GTK_WIDGET_HAS_FOCUS (di->child->widget);

        row = get_row_from_y (fullday, y - di->child_click_y + (has_focus ? HANDLE_SIZE : 0), TRUE);

        if (row > (f_rows - di->child_rows_used))
            row = f_rows - di->child_rows_used;

        di->child_start_row = row;

        break;

    case DRAG_SIZE_TOP:
        row = get_row_from_y (fullday, y + HANDLE_SIZE, TRUE);

        if (row > (di->child_start_row + di->child_rows_used - 1))
            row = di->child_start_row + di->child_rows_used - 1;

        di->child_rows_used = (di->child_start_row + di->child_rows_used) - row;
        di->child_start_row = row;

        break;

    case DRAG_SIZE_BOTTOM:
        row = get_row_from_y (fullday, y - HANDLE_SIZE, TRUE);

        if (row <= di->child_start_row)
            row = di->child_start_row + 1;
        else if (row > f_rows)
            row = f_rows;

        di->child_rows_used = row - di->child_start_row;

        break;

    default:
        g_assert_not_reached ();
    }
}

static void
get_time_from_rows (GncalFullDay *fullday, int start_row, int rows_used, time_t *t_lower, time_t *t_upper)
{
    struct tm tm;
    int row_height;

    get_tm_range (fullday, fullday->lower, fullday->upper, &tm, NULL, NULL, NULL);

    row_height = calc_row_height (fullday);

    tm.tm_min += fullday->interval * start_row;
    *t_lower = mktime (&tm);

    tm.tm_min += fullday->interval * rows_used;
    *t_upper = mktime (&tm);
}

static void
update_from_drag_info (GncalFullDay *fullday)
{
    struct drag_info *di;
    GtkWidget *widget;

    di = fullday->drag_info;

    widget = GTK_WIDGET (fullday);

    get_time_from_rows (fullday, di->child_start_row, di->child_rows_used,
                &di->child->ico->dtstart,
                &di->child->ico->dtend);

    child_range_changed (fullday, di->child);

    /* Notify calendar of change */

    gnome_calendar_object_changed (fullday->calendar, di->child->ico, CHANGE_DATES);
}

static gint
gncal_full_day_button_release (GtkWidget *widget, GdkEventButton *event)
{
    GncalFullDay *fullday;
    struct drag_info *di;
    gint y;

    g_return_val_if_fail (widget != NULL, FALSE);
    g_return_val_if_fail (GNCAL_IS_FULL_DAY (widget), FALSE);
    g_return_val_if_fail (event != NULL, FALSE);

    fullday = GNCAL_FULL_DAY (widget);

    di = fullday->drag_info;

    gtk_widget_get_pointer (widget, NULL, &y);

    switch (di->drag_mode) {
    case DRAG_NONE:
        return FALSE;

    case DRAG_SELECT:
        if ((event->time - di->click_time) < UNSELECT_TIMEOUT)
            di->sel_rows_used = 0;
        else
            recompute_motion (fullday, y);

        gdk_pointer_ungrab (event->time);

        paint_back_rows (fullday, di->sel_start_row, MAX (di->sel_rows_used, 1));

        break;

    case DRAG_MOVE:
    case DRAG_SIZE_TOP:
    case DRAG_SIZE_BOTTOM:
        draw_xor_rect (fullday);
        recompute_motion (fullday, y);
        gdk_pointer_ungrab (event->time);

        update_from_drag_info (fullday);

        di->child_rows_used = 0;

        break;

    default:
        g_assert_not_reached ();
    }

    di->drag_mode = DRAG_NONE;
    di->child = NULL;

    return FALSE;
}

static gint
gncal_full_day_motion (GtkWidget *widget, GdkEventMotion *event)
{
    GncalFullDay *fullday;
    struct drag_info *di;
    gint y;
    int old_min, old_max;
    int new_min, new_max;
    int new_start_row, new_rows_used;

    g_return_val_if_fail (widget != NULL, FALSE);
    g_return_val_if_fail (GNCAL_IS_FULL_DAY (widget), FALSE);
    g_return_val_if_fail (event != NULL, FALSE);

    fullday = GNCAL_FULL_DAY (widget);
    di = fullday->drag_info;

    gtk_widget_get_pointer (widget, NULL, &y);
    
    switch (di->drag_mode) {
    case DRAG_NONE:
        break;

    case DRAG_SELECT:
        old_min = di->sel_start_row;
        old_max = di->sel_start_row + di->sel_rows_used - 1;

        recompute_motion (fullday, y);

        new_min = di->sel_start_row;
        new_max = di->sel_start_row + di->sel_rows_used - 1;

        new_start_row = MIN (old_min, new_min);
        new_rows_used = MAX (old_max, new_max) - new_start_row + 1;

        paint_back_rows (fullday, new_start_row, new_rows_used);

        break;

    case DRAG_MOVE:
    case DRAG_SIZE_TOP:
    case DRAG_SIZE_BOTTOM:
        draw_xor_rect (fullday);
        recompute_motion (fullday, y);
        draw_xor_rect (fullday);

        break;

    default:
        g_assert_not_reached ();
    }

    return FALSE;
}

static gint
gncal_full_day_expose (GtkWidget *widget, GdkEventExpose *event)
{
    GncalFullDay *fullday;
    Child *child;
    int on_text;

    g_return_val_if_fail (widget != NULL, FALSE);
    g_return_val_if_fail (GNCAL_IS_FULL_DAY (widget), FALSE);
    g_return_val_if_fail (event != NULL, FALSE);

    if (!GTK_WIDGET_DRAWABLE (widget))
        return FALSE;

    fullday = GNCAL_FULL_DAY (widget);

    if (event->window == widget->window)
        paint_back (fullday, &event->area);
    else {
        child = find_child_by_window (fullday, event->window, &on_text);

        if (child && !on_text)
            child_draw (fullday, child, &event->area, FALSE);
    }

    return FALSE;
}

static gint
gncal_full_day_key_press (GtkWidget *widget, GdkEventKey *event)
{
    GncalFullDay *fullday;
    struct drag_info *di;

    g_return_val_if_fail (widget != NULL, FALSE);
    g_return_val_if_fail (GNCAL_IS_FULL_DAY (widget), FALSE);
    g_return_val_if_fail (event != NULL, FALSE);

    fullday = GNCAL_FULL_DAY (widget);

    di = fullday->drag_info;

    if (di->sel_rows_used == 0)
        return FALSE;

    if (event->keyval == GDK_Return) {
        gtk_signal_emit (GTK_OBJECT (fullday), fullday_signals [RANGE_ACTIVATED]);
        return TRUE;
    }

    return FALSE;
}

static gint
gncal_full_day_focus_in (GtkWidget *widget, GdkEventFocus *event)
{
    g_return_val_if_fail (widget != NULL, FALSE);
    g_return_val_if_fail (GNCAL_IS_FULL_DAY (widget), FALSE);
    g_return_val_if_fail (event != NULL, FALSE);

    GTK_WIDGET_SET_FLAGS (widget, GTK_HAS_FOCUS);
    gtk_widget_draw_focus (widget);

    return FALSE;
}

static gint
gncal_full_day_focus_out (GtkWidget *widget, GdkEventFocus *event)
{
    g_return_val_if_fail (widget != NULL, FALSE);
    g_return_val_if_fail (GNCAL_IS_FULL_DAY (widget), FALSE);
    g_return_val_if_fail (event != NULL, FALSE);

    GTK_WIDGET_UNSET_FLAGS (widget, GTK_HAS_FOCUS);
    gtk_widget_draw_focus (widget);

    return FALSE;
}

static void
gncal_full_day_foreach (GtkContainer *container, GtkCallback callback, gpointer callback_data)
{
    GncalFullDay *fullday;
    GList *children;
    Child *child;

    g_return_if_fail (container != NULL);
    g_return_if_fail (GNCAL_IS_FULL_DAY (container));
    g_return_if_fail (callback != NULL);

    fullday = GNCAL_FULL_DAY (container);

    for (children = fullday->children; children; children = children->next) {
        child = children->data;

        (*callback) (child->widget, callback_data);
    }
}

static gint
child_compare_by_start (gpointer a, gpointer b)
{
    Child *ca = a;
    Child *cb = b;
    time_t diff;
    
    diff = ca->start - cb->start;
    return (diff < 0) ? -1 : (diff > 0) ? 1 : 0;
}

static int
fullday_add_children (iCalObject *obj, time_t start, time_t end, void *c)
{
    GncalFullDay *fullday = c;
    Child *child;
    
    child = child_new (fullday, start, end, obj);
    fullday->children = g_list_insert_sorted (fullday->children, child, child_compare_by_start);

    return 1;
}

void
gncal_full_day_update (GncalFullDay *fullday, iCalObject *ico, int flags)
{
    GList *children;
    Child *child;
    
    g_return_if_fail (fullday != NULL);
    g_return_if_fail (GNCAL_IS_FULL_DAY (fullday));

    if (!fullday->calendar->cal)
        return;

    /* Try to find child that changed */

    for (children = fullday->children; children; children = children->next) {
        child = children->data;

        if (child->ico == ico)
            break;
    }

    /* If child was found and nothing but the summary changed, we can just paint the child and return */

    if (children && !(flags & ~CHANGE_SUMMARY)) {
        child_draw (fullday, child, NULL, TRUE);
        return;
    }

    /* We have to regenerate and layout our list of children */

    for (children = fullday->children; children; children = children->next)
        child_destroy (fullday, children->data);

    g_list_free (fullday->children);

    fullday->children = NULL;
    
    calendar_iterate (fullday->calendar->cal,
              fullday->lower,
              fullday->upper,
              fullday_add_children,
              fullday);
    
    layout_children (fullday);

    /* Realize and map children */

    for (children = fullday->children; children; children = children->next) {
        if (GTK_WIDGET_REALIZED (fullday))
            child_realize (fullday, children->data);

        if (GTK_WIDGET_MAPPED (fullday))
            child_map (fullday, children->data);
    }

    gtk_widget_draw (GTK_WIDGET (fullday), NULL);
}

void
gncal_full_day_set_bounds (GncalFullDay *fullday, time_t lower, time_t upper)
{
    struct drag_info *di;

    g_return_if_fail (fullday != NULL);
    g_return_if_fail (GNCAL_IS_FULL_DAY (fullday));

    if ((lower != fullday->lower) || (upper != fullday->upper)) {
        fullday->lower = lower;
        fullday->upper = upper;

        di = fullday->drag_info;

        di->sel_rows_used = 0; /* clear selection */

        gncal_full_day_update (fullday, NULL, 0);
    }
}

int
gncal_full_day_selection_range (GncalFullDay *fullday, time_t *lower, time_t *upper)
{
    struct drag_info *di;
    time_t alower, aupper;

    g_return_val_if_fail (fullday != NULL, FALSE);
    g_return_val_if_fail (GNCAL_IS_FULL_DAY (fullday), FALSE);

    di = fullday->drag_info;

    if (di->sel_rows_used == 0)
        return FALSE;

    get_time_from_rows (fullday, di->sel_start_row, di->sel_rows_used, &alower, &aupper);

    if (lower)
        *lower = alower;

    if (upper)
        *upper= aupper;

    return TRUE;
}

void
gncal_full_day_focus_child (GncalFullDay *fullday, iCalObject *ico)
{
    GList *children;
    Child *child;
    GdkEvent event;

    g_return_if_fail (fullday != NULL);
    g_return_if_fail (ico != NULL);

    for (children = fullday->children; children; children = children->next) {
        child = children->data;

        if (child->ico == ico) {
            gtk_widget_grab_focus (child->widget);

            /* We synthesize a click because GtkText will not set the cursor and
             * the user will not be able to type-- this has to be fixed in
             * GtkText.  */

            memset (&event, 0, sizeof (event));

            event.type = GDK_BUTTON_PRESS;
            event.button.window = child->widget->window;
            event.button.time = GDK_CURRENT_TIME;
            event.button.x = 0;
            event.button.y = 0;
            event.button.button = 1;

            gtk_widget_event (child->widget, &event);

            break;
        }
    }
}

static void
range_activated (GncalFullDay *fullday)
{
    struct drag_info *di;

    g_return_if_fail (fullday != NULL);
    g_return_if_fail (GNCAL_IS_FULL_DAY (fullday));

    di = fullday->drag_info;

    /* Remove selection; at this point someone should already have added an appointment */

    di->sel_rows_used = 0;

    paint_back (fullday, NULL);
}