aboutsummaryrefslogblamecommitdiffstats
path: root/widgets/table/e-table-item.c
blob: 639d8196cfaacf49df96b5283e8b1d209ba942d4 (plain) (tree)
1
2
3
4
5
6
7
8
9
                                                                           
  
                                                                      

          
                                                

                                     
                                           



                                                                

                   


                           
                 
                         
                           
                   


                                       


                                                        

                        


                                              
                      
                         
                     
                    
              
                  





                                               


                         
                                  
                            
                             
                        
                             
                       
        
                          

                   

  



                                  
                                            
                                                   
                                                     
                                                                                          
                                                                                                   
                                                                                                      





                                                                                    
                                                                                                                                                    
 
                  


















                                                                          
                  











                                                                      















                                                                             
                                         

 








                                   







                                                                       
           

                                        

              
                                          
                                                     


                                     







                                       


                                         
                                 


                                                            
                                                                             
                
                                                                                           
         
 
                                      
                                                               
                              
                                                                   

 

                                                                              
                                              
   




                                          



                                          
                                                       









                                                       

                                           
                
                                 
                               
                         


           
                                                                                  
 





                                                              

                                                                    










                                         
 
 



                                              

                                        
                                                      

                                                
                                                 


                                                                                      


                                              










                                                                                      
 






                                                                                
                        

                                              

                                                                                                      
 



                        
 
                                                                      


                               

                                                                                   
                                      
         



                                                                              

                                      

 




                                                                     





                                        
                                                             

                                                               
                                                           

                                                               

                                                                



                                                                 
                                                         

                                                                  
 
                                           
                                       
                                           
                                            

                                             
                                

                                   

 
  













                                                                     

                                                           


                                                       
                                     



                              



                                                                      





                                         



                                                                


                                                             



                                               

                                                    
 

                                            
                                         


                           
  
                       



                                                                            
          
                                              




                                                            
                                                
        


                                         
                                                                                                 






                                  























                                                                    





                                           



                                                                                                                        








                                                  
                                          















                                                                            









                                                                             


                                      











                                                                         


                                
                                   
                
                   
 

                         
 

                                                  



                                                                  



                                                                                          




                                                                                     





                                             
         

                   
                                        
                                                        



                      












                                                                               
           












                                                                        
 


                                                                                       
  









                                                                        

                                                                       


                                                                   
     
                     

      
                                                               
 

                               
                                      
                                                               
                              
                                                                   
 
     


                                                                                                    
      

 
  

                                                                  





                                                          
 





                                                       









                                                                                          
           


                                                                
 
                                  












                                                                                      

 








                                                                                   
                                                             







                                                                                



                                                                                               

 


                                                                                


                                                                      


                                                           
 
                                                                       
 
 


                                                                                          


                                                                      


                                                           
 
                                                                       

 


                                                                                 











                                                                                                                   




                                                                                








                                                                                                               

 










                                                                      



                                                        
 
                   
                                   


                                                 









                                                                



                           
                                                                                       


           




                                                               
                                                       




                                                                   


                                                                
 


                                                                    
 



                                                                     







                                                                     
                          


                                            



                                                                        
                                      
                                                                      
         
        
                                                   


           














                                                                                



                                                             



                                             

                                                                    

                                                               




                                                                 
                                                       
                                                              
 

                                              
                                                                                  
           
                                      
 

                                               

                                            
                                             
                
                                       


                                                    
         

                                                               

 











                                                                                                  






                                                            
 
                                                   





                                                               
                                                        
                                                                     



                                                                  

 


                            






                                                
                                               
 

                                                           
 



                                           
 

                                                          



                                                         

                              








                                                                         
                       





                                     
                                              
                                                                                    



                                             
                                                                                  

                      





                                                                                                              
                                  


                                                             

                                                       




                                                        
 

                                                        
                      
 
                               
                       





                                                                                             
                            



                                                          
                                                                                                                             
                      
         
                              
                                                                  


           



                                                     
                










                                                      


                                                             
                            



                                                                   
                      





                                             


                                              
 








                                              
        
                                            
                                           
 

                                              
        

                                                               
 

                                           
                                              



                                              
 

                                           



                                                                   
 

                                            
 
                                                                                

 




                                   














                                                          


                                   


                                                             
     
                          
      
        


                                                                             


                                  
                                       
 
                                           
 
                                           



                                                                                              
                                                                                             
      
                                            





                                                                                                    
 








                                                                               

                                            
        
                                     
 
                                      
                                                               
                              
                                                                   




                                     
                                              
        


                                              



                                    

                                     

                                        
        
                                       

                        





                                                                               
                                                                                            
 
                                              

                                   

                                          
                                              
                   

                                   
                       
                                                      

                                                     
        


                               
     
                            

                                                                   
      








                                                          



                                                             


                                                    
                       
                                
                                     









                                                                               
                                          



                                        
 
          




                            
          
                            
           

                       
                                         
                                                  
 
                                                    





                                    














                                          

                                       



                                               
                                                                                     


             

                                                     
                                  
                                            
                
                                                   
 
                              
                                                              
                
                                                                                                               




                                                          
                

                                                                                       
                                                                      
                                                         
                                         

                                                   
                                                        
                                                                                                             





                                                                     
 
                                          



                                                                                             
                                
     
                                            
      
                                                                                             
     

                                                                                               
      





                                                                         
                                                                   
                                                                                   















                                                                                          
                                                                            
                        











                                                                                        
                                                                








                                                                                                                                     
                         
 
                                          
                 
                             



                                                       
                                                                                     
                     
         
 


                                  
                                                              



                                                                                       
                                                          
 




                                                                            


                 


                     
                                         
                                                                 
                                                                   
                                                                                  
         










                                                                     
               
                                                                                                           
 

                                   


                               
                                                           







                                                                                                   


                                   




                     
                                                      






                                                                               

                              

         
                    
                                                      


                                     
                                                    
 

                              
         





                                 


                    
           
                                                        

                                      
                                                                                                 




                                      





                                                  
                                                                                                          




                                       





                                                  
                                                                                                          

 
     


                                    





                                                  
                                                                                                          




                                      





                                                  
                                                                                                          
 
      
 
     






                              




                                                          


                              






                                                        
                                                   
                            

                                                           



                                                                        
                                                                            


                                           
      
 






















                                                                                                                                                 
                   


                                              
                                              
                              
                               
 
                         
                                
                              
                                      
                             
                                            
 



                                                                 
 


                                           
                                                                         
                                                                                 
 


                                                                                               
                                            
                         
 



                                                           
 
                                                                                                                                                     

                                            
 


                                                                                                 


                                            
                                                                                                                                                     





                                                                  
                                                                                                                    
 



                                                                                                                                           


                                                             
 

                                                                                                                      
                         
                                                  
 

                              
                                                                         


                                                                                             
 
                                                                                                                                                

                                                                                     
                                                                                           





                                     
                 

                      
 


                                  
                                            
 







                                                                  




                                                                                 

                                                                                             
 



                                                                  
 

                                                                                                                    
                                                                   
 




                                                             
 

                                                                                                                      
                         
                                              









                                     
 
                                 
                             
                                      






                                                                         
     

                                                                                     




                                                          
 
     


                                              
      
 
                                                                              
                                                    
                                          

                      


                                 
                                            
 
                                                                         
 
                                                                                     

                                    




                                                          




                                                                  

                                                                 
     



                                               
                                                                                           
      
 
                                                                                                            




                                                           

                                         
                        

                                                                                                              


                      
                

                                            
                                    
 




                                                          




                                                                  
                                     

                                     

                                         

                                       

                                                
                                      
                         
                        
                                                                                                               
                                                           

                              
                               

                                                
                                      

                         
                                                                                                                           
                                                            

                              
                            




                                                                                                          
                                                                    
                                                         


                                                   

                              
                                                                                
                                                           


                                                   
      

                                 
                                                                      






                                                                                                                  
                                                                      




                                                                                                                  
                             

                                      



























                                                                                                                                              


                                                                  
                         





                                               

                                                              
                                                                                


                                                                                                                   
      
                         



                                                                                                                  
                              
                        
                        











                                                                                                                                              
                                                
                                                                                           
                                                                                                                 

                                                                                                             
                                
                                                                                


                                                                                                                   

                                                                                                             
                         
                 
                                          
                      
         
        








                                                          
                                     
 

                                                                        


                                                                                                           
                 
                      
         
 



                                                                 
                                        

                      





                                                              
                
                                   
         
                          
 
 



                                                                                 
                                                                       
        
                                                                          
        









                                                    
        







                                                                                 
                                                                     
                                                                               
                                                                    
                                                                                                   
                                                                              

                                                                        

                                                                         

                                                                         

                                                                              
 

                                                                               



                                                                        
                                                                        
                                                                    
 







                                                                                   







                                                                                      




                                                                                  

                                                                                                
 







                                                                                               







                                                                                               







                                                                                               

                                                                              
























                                                                   







                                                               
    

                                                              
                                                                     


           
                                                                             
 


                                                 
                        
                                                            


                        
                                    
         
 
                        

                                                                    
                                                            
         
 
 







                                                               


                                                 

                       

                                                         



                                                  
 
                          

 
           
                                                                                      
 
                                                   






                                                   
                                 
                                                                                                              
                
                                                                                         

         
                                                         

                                              

                                                                       

                                                                 
 
 
           
















                                                                                         
                                                                       
 

                                                                 

 









                                                                      




                                                           


                                             



                               
                                                                                                         

 





                                                         


                                         


                       


                                                 

                               
 



                                 


                              



                                                       

 
    
                                











                                                                      






                                                      




                                                                      


                                                             

                                       

 































                                                                                                              
                                                                                               

                
                             

                                          

                                                                                                                       



























                                                                                              


                                            









                                                                                        
                            




                                                       
                                           
                
                                                                               










                                                                             
                                                                      


















                                                                                           
                                                                                                  








                                                                   
                                    






                                                               
                            



































                                                                          
                                            



















                                                                                        







                                                                                                          







                                 


                                                               




                                                               

















































                                                                                                          







                                                                    








                                                                      



























                                                                     



                                                                   





                                                                            
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
 * E-table-item.c: A GnomeCanvasItem that is a view of an ETableModel.
 *
 * Author:
 *   Christopher James Lahey <clahey@ximian.com>
 *   Miguel de Icaza (miguel@gnu.org)
 *
 * Copyright 1999, 2000, 2001, Ximian, Inc.
 *
 * TODO:
 *   Add a border to the thing, so that focusing works properly.
 *
 */
#include <config.h>
#include <stdio.h>
#include <gtk/gtksignal.h>
#include <gdk/gdkkeysyms.h>
#include <math.h>
#include "e-table-item.h"
#include "e-table-subset.h"
#include "e-cell.h"
#include "gal/widgets/e-canvas.h"
#include "gal/widgets/e-canvas-utils.h"
#include "gal/util/e-util.h"

#define PARENT_OBJECT_TYPE gnome_canvas_item_get_type ()

#define FOCUSED_BORDER 2

static GnomeCanvasItemClass *eti_parent_class;

enum {
    CURSOR_CHANGE,
    CURSOR_ACTIVATED,
    DOUBLE_CLICK,
    RIGHT_CLICK,
    CLICK,
    KEY_PRESS,
    LAST_SIGNAL
};

static gint eti_signals [LAST_SIGNAL] = { 0, };

enum {
    ARG_0,
    ARG_TABLE_HEADER,
    ARG_TABLE_MODEL,
    ARG_TABLE_SELECTION_MODEL,
    ARG_TABLE_DRAW_GRID,
    ARG_TABLE_DRAW_FOCUS,
    ARG_CURSOR_MODE,
    ARG_LENGTH_THRESHOLD,
    ARG_CURSOR_ROW,
    
    ARG_MINIMUM_WIDTH,
    ARG_WIDTH,
    ARG_HEIGHT,
};

#define DOUBLE_CLICK_TIME      250
#define TRIPLE_CLICK_TIME      500


static int eti_get_height (ETableItem *eti);
static int eti_get_minimum_width (ETableItem *eti);
static int eti_row_height (ETableItem *eti, int row);
static void e_table_item_focus (ETableItem *eti, int col, int row, GdkModifierType state);
static void eti_cursor_change (ETableSelectionModel *selection, int row, int col, ETableItem *eti);
static void eti_cursor_activated (ETableSelectionModel *selection, int row, int col, ETableItem *eti);
static void eti_selection_change (ETableSelectionModel *selection, ETableItem *eti);
#if 0
static void eti_request_region_show (ETableItem *eti,
                     int start_col, int start_row,
                     int end_col, int end_row);
#endif
#define ETI_ROW_HEIGHT(eti,row) ((eti)->height_cache && (eti)->height_cache[(row)] != -1 ? (eti)->height_cache[(row)] : eti_row_height((eti),(row)))

inline static gint
model_to_view_row(ETableItem *eti, int row)
{
    int i;
    if (eti->uses_source_model) {
        ETableSubset *etss = E_TABLE_SUBSET(eti->table_model);
        if (eti->row_guess >= 0 && eti->row_guess < etss->n_map) {
            if (etss->map_table[eti->row_guess] == row) {
                return eti->row_guess;
            }
        }
        for (i = 0; i < etss->n_map; i++) {
            if (etss->map_table[i] == row)
                return i;
        }
        return -1;
    } else
        return row;
}

inline static gint
view_to_model_row(ETableItem *eti, int row)
{
    if (eti->uses_source_model) {
        ETableSubset *etss = E_TABLE_SUBSET(eti->table_model);
        if (row >= 0 && row < etss->n_map)
            return etss->map_table[row];
        else
            return -1;
    } else
        return row;
}

inline static gint
model_to_view_col(ETableItem *eti, int col)
{
    int i;
    for (i = 0; i < eti->cols; i++) {
        ETableCol *ecol = e_table_header_get_column (eti->header, i);
        if (ecol->col_idx == col)
            return i;
    }
    return -1;
}

inline static gint
view_to_model_col(ETableItem *eti, int col)
{
    ETableCol *ecol = e_table_header_get_column (eti->header, col);
    return ecol ? ecol->col_idx : -1;
}

static gboolean
eti_editing (ETableItem *eti)
{
    if (eti->editing_col == -1)
        return FALSE;
    else
        return TRUE;
}

/*
 * During realization, we have to invoke the per-ecell realize routine
 * (On our current setup, we have one e-cell per column.
 *
 * We might want to optimize this to only realize the unique e-cells:
 * ie, a strings-only table, uses the same e-cell for every column, and
 * we might want to avoid realizing each e-cell.
 */
static void
eti_realize_cell_views (ETableItem *eti)
{
    int i;
    
    for (i = 0; i < eti->n_cells; i++)
        e_cell_realize (eti->cell_views [i]);
    eti->cell_views_realized = 1;
}

static void
eti_attach_cell_views (ETableItem *eti)
{
    int i;

    g_assert (eti->header);
    g_assert (eti->table_model);
    
    /*
     * Now realize the various ECells
     */
    eti->n_cells = eti->cols;
    eti->cell_views = g_new (ECellView *, eti->n_cells);

    for (i = 0; i < eti->n_cells; i++){
        ETableCol *ecol = e_table_header_get_column (eti->header, i);
        
        eti->cell_views [i] = e_cell_new_view (ecol->ecell, eti->table_model, eti);
    }

    eti->needs_compute_height = 1;
    e_canvas_item_request_reflow (GNOME_CANVAS_ITEM (eti));
    eti->needs_redraw = 1;
    gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (eti));
}

/*
 * During unrealization: we invoke every e-cell (one per column in the current
 * setup) to dispose all X resources allocated
 */
static void
eti_unrealize_cell_views (ETableItem *eti)
{
    int i;

    if (eti->cell_views_realized == 0)
        return;
    
    for (i = 0; i < eti->n_cells; i++)
        e_cell_unrealize (eti->cell_views [i]);
    eti->cell_views_realized = 0;
}

static void
eti_detach_cell_views (ETableItem *eti)
{
    int i;
    
    for (i = 0; i < eti->n_cells; i++){
        e_cell_kill_view (eti->cell_views [i]);
        eti->cell_views [i] = NULL;
    }
        
    g_free (eti->cell_views);
    eti->cell_views = NULL;
    eti->n_cells = 0;
}

static void
eti_bounds (GnomeCanvasItem *item, double *x1, double *y1, double *x2, double *y2)
{
    double   i2c [6];
    ArtPoint c1, c2, i1, i2;
    ETableItem *eti = E_TABLE_ITEM (item);

    /* Wrong BBox's are the source of redraw nightmares */

    gnome_canvas_item_i2c_affine (GNOME_CANVAS_ITEM (eti), i2c);
    
    i1.x = eti->x1;
    i1.y = eti->y1;
    i2.x = eti->x1 + eti->width;
    i2.y = eti->y1 + eti->height;
    art_affine_point (&c1, &i1, i2c);
    art_affine_point (&c2, &i2, i2c);
    
    *x1 = c1.x;
    *y1 = c1.y;
    *x2 = c2.x + 1;
    *y2 = c2.y + 1;
}

static void
eti_reflow (GnomeCanvasItem *item, gint flags)
{
    ETableItem *eti = E_TABLE_ITEM (item);

    if (eti->needs_compute_height) {
        int new_height = eti_get_height (eti);

        if (new_height != eti->height) {
            eti->height = new_height;
            e_canvas_item_request_parent_reflow (GNOME_CANVAS_ITEM (eti));
            eti->needs_redraw = 1;
            gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (eti));
        }
        eti->needs_compute_height = 0;
    }
    if (eti->needs_compute_width) {
        int new_width = eti_get_minimum_width (eti);
        new_width = MAX(new_width, eti->minimum_width);
        if (new_width != eti->width) {
            eti->width = new_width;
            e_canvas_item_request_parent_reflow (GNOME_CANVAS_ITEM (eti));
            eti->needs_redraw = 1;
            gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (eti));
        }
        eti->needs_compute_width = 0;
    }
}

/*
 * GnomeCanvasItem::update method
 */
static void
eti_update (GnomeCanvasItem *item, double *affine, ArtSVP *clip_path, int flags)
{
    ArtPoint o1, o2;
    ETableItem *eti = E_TABLE_ITEM (item);

    if (GNOME_CANVAS_ITEM_CLASS (eti_parent_class)->update)
        (*GNOME_CANVAS_ITEM_CLASS (eti_parent_class)->update)(item, affine, clip_path, flags);

    o1.x = item->x1;
    o1.y = item->y1;
    o2.x = item->x2;
    o2.y = item->y2;

    eti_bounds (item, &item->x1, &item->y1, &item->x2, &item->y2);
    if (item->x1 != o1.x ||
        item->y1 != o1.y ||
        item->x2 != o2.x ||
        item->y2 != o2.y) {
        gnome_canvas_request_redraw (item->canvas, o1.x, o1.y, o2.x, o2.y);
        eti->needs_redraw = 1;
    }

    if (eti->needs_redraw) {
        gnome_canvas_request_redraw (item->canvas, item->x1, item->y1,
                         item->x2, item->y2);
        eti->needs_redraw = 0;
    }
}

/*
 * eti_remove_table_model:
 *
 * Invoked to release the table model associated with this ETableItem
 */
static void
eti_remove_table_model (ETableItem *eti)
{
    if (!eti->table_model)
        return;

    gtk_signal_disconnect (GTK_OBJECT (eti->table_model),
                   eti->table_model_pre_change_id);
    gtk_signal_disconnect (GTK_OBJECT (eti->table_model),
                   eti->table_model_change_id);
    gtk_signal_disconnect (GTK_OBJECT (eti->table_model),
                   eti->table_model_row_change_id);
    gtk_signal_disconnect (GTK_OBJECT (eti->table_model),
                   eti->table_model_cell_change_id);
    gtk_signal_disconnect (GTK_OBJECT (eti->table_model),
                   eti->table_model_row_inserted_id);
    gtk_signal_disconnect (GTK_OBJECT (eti->table_model),
                   eti->table_model_row_deleted_id);
    gtk_object_unref (GTK_OBJECT (eti->table_model));
    if (eti->source_model)
        gtk_object_unref (GTK_OBJECT (eti->source_model));

    eti->table_model_pre_change_id = 0;
    eti->table_model_change_id = 0;
    eti->table_model_row_change_id = 0;
    eti->table_model_cell_change_id = 0;
    eti->table_model_row_inserted_id = 0;
    eti->table_model_row_deleted_id = 0;
    eti->table_model = NULL;
    eti->source_model = NULL;
    eti->uses_source_model = 0;
}

/*
 * eti_remove_table_model:
 *
 * Invoked to release the table model associated with this ETableItem
 */
static void
eti_remove_table_selection_model (ETableItem *eti)
{
    if (!eti->selection)
        return;

    gtk_signal_disconnect (GTK_OBJECT (eti->selection),
                   eti->selection_change_id);
    gtk_signal_disconnect (GTK_OBJECT (eti->selection),
                   eti->cursor_change_id);
    gtk_signal_disconnect (GTK_OBJECT (eti->selection),
                   eti->cursor_activated_id);
    gtk_object_unref (GTK_OBJECT (eti->selection));

    eti->selection_change_id = 0;
    eti->cursor_activated_id = 0;
    eti->selection = NULL;
}

/*
 * eti_remove_header_model:
 *
 * Invoked to release the header model associated with this ETableItem
 */
static void
eti_remove_header_model (ETableItem *eti)
{
    if (!eti->header)
        return;

    gtk_signal_disconnect (GTK_OBJECT (eti->header),
                   eti->header_structure_change_id);
    gtk_signal_disconnect (GTK_OBJECT (eti->header),
                   eti->header_dim_change_id);
    gtk_signal_disconnect (GTK_OBJECT (eti->header),
                   eti->header_request_width_id);
    
    if (eti->cell_views){
        eti_unrealize_cell_views (eti);
        eti_detach_cell_views (eti);
    }
    gtk_object_unref (GTK_OBJECT (eti->header));


    eti->header_structure_change_id = 0;
    eti->header_dim_change_id = 0;
    eti->header_request_width_id = 0;
    eti->header = NULL;
}

/*
 * eti_row_height_real:
 *
 * Returns the height used by row @row.  This does not include the one-pixel
 * used as a separator between rows
 */
static int
eti_row_height_real (ETableItem *eti, int row)
{
    const int cols = e_table_header_count (eti->header);
    int col;
    int h, max_h;

    g_assert (cols == 0 || eti->cell_views);
    
    max_h = 0;
    
    for (col = 0; col < cols; col++){
        h = e_cell_height (eti->cell_views [col], view_to_model_col(eti, col), col, row);

        if (h > max_h)
            max_h = h;
    }
    return max_h;
}

static gboolean
height_cache_idle(ETableItem *eti)
{
    int changed = 0;
    int i;
    if (!eti->height_cache) {
        eti->height_cache = g_new(int, eti->rows);
    }
    for (i = eti->height_cache_idle_count; i < eti->rows; i++) {
        if (eti->height_cache[i] == -1) {
            eti_row_height(eti, i);
            changed ++;
            if (changed >= 20)
                break;
        }
    }
    if (changed >= 20) {
        eti->height_cache_idle_count = i;
        return TRUE;
    }
    eti->height_cache_idle_id = 0;
    return FALSE;   
}

static void
free_height_cache (ETableItem *eti)
{
    if (eti->height_cache)
        g_free (eti->height_cache);
    eti->height_cache = NULL;
    eti->height_cache_idle_count = 0;
    
    if (eti->height_cache_idle_id == 0)
        eti->height_cache_idle_id = g_idle_add_full(G_PRIORITY_LOW, (GSourceFunc) height_cache_idle, eti, NULL);
}

static void
calculate_height_cache (ETableItem *eti)
{
    int i;
    free_height_cache(eti);
    eti->height_cache = g_new(int, eti->rows);
    for (i = 0; i < eti->rows; i++) {
        eti->height_cache[i] = -1;
    }
}


/*
 * eti_row_height:
 *
 * Returns the height used by row @row.  This does not include the one-pixel
 * used as a separator between rows
 */
static int
eti_row_height (ETableItem *eti, int row)
{
    if (!eti->height_cache) {
        calculate_height_cache (eti);
    }
    if (eti->height_cache[row] == -1) {
        eti->height_cache[row] = eti_row_height_real(eti, row);
        if (row > 0 && 
            eti->length_threshold != -1 && 
            eti->rows > eti->length_threshold &&
            eti->height_cache[row] != eti_row_height(eti, 0)) {
            eti->needs_compute_height = 1;
            e_canvas_item_request_reflow(GNOME_CANVAS_ITEM(eti));
        }
    }
    return eti->height_cache[row];
}

/*
 * eti_get_height:
 *
 * Returns the height of the ETableItem.
 *
 * The ETableItem might compute the whole height by asking every row its
 * size.  There is a special mode (designed to work when there are too
 * many rows in the table that performing the previous step could take 
 * too long) set by the ETableItem->length_threshold that would determine
 * when the height is computed by using the first row as the size for
 * every other row in the ETableItem.
 */
static int
eti_get_height (ETableItem *eti)
{
    const int rows = eti->rows;
    int row;
    int height;

    if (rows == 0)
        return 0;

    if (eti->length_threshold != -1){
        if (rows > eti->length_threshold){
            int row_height = eti_row_height(eti, 0);
            if (eti->height_cache) {
                height = 0;
                for (row = 0; row < rows; row++) {
                    if (eti->height_cache[row] == -1) {
                        height += (row_height + 1) * (rows - row);
                        break;
                    }
                    else
                        height += eti->height_cache[row] + 1;
                }
            } else
                height = (eti_row_height (eti, 0) + 1) * rows;

            /*
             * 1 pixel at the top
             */
            return height + 1;
        }
    }

    height = 1;
    for (row = 0; row < rows; row++)
        height += eti_row_height (eti, row) + 1;

    return height;
}

static int
eti_get_minimum_width (ETableItem *eti)
{
    int width = 0;
    int col;
    for (col = 0; col < eti->cols; col++){
        ETableCol *ecol = e_table_header_get_column (eti->header, col);
        
        width += ecol->min_width;
    }
    return width;
}

static void
eti_item_region_redraw (ETableItem *eti, int x0, int y0, int x1, int y1)
{
    GnomeCanvasItem *item = GNOME_CANVAS_ITEM (eti);
    ArtDRect rect;
    double i2c [6];
    
    rect.x0 = x0;
    rect.y0 = y0;
    rect.x1 = x1;
    rect.y1 = y1;

    gnome_canvas_item_i2c_affine (item, i2c);
    art_drect_affine_transform (&rect, &rect, i2c);

    gnome_canvas_request_redraw (item->canvas, rect.x0, rect.y0, rect.x1, rect.y1);
}

/*
 * Callback routine: invoked before the ETableModel has suffers a change
 */
static void
eti_table_model_pre_change (ETableModel *table_model, ETableItem *eti)
{
    if (eti_editing (eti))
        e_table_item_leave_edit (eti);
}

/*
 * Callback routine: invoked when the ETableModel has suffered a change
 */
static void
eti_table_model_changed (ETableModel *table_model, ETableItem *eti)
{
#if 0
    int view_row;
#endif

    eti->rows = e_table_model_row_count (eti->table_model);

    free_height_cache(eti);

    eti->needs_compute_height = 1;
    e_canvas_item_request_reflow (GNOME_CANVAS_ITEM (eti));
    eti->needs_redraw = 1;
    gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (eti));

#if 0
    view_row = model_to_view_row(eti, eti->cursor_row);
    if (view_row >= 0 && eti->cursor_col >= 0)
        eti_request_region_show (eti, eti->cursor_col, view_row, eti->cursor_col, view_row);
#endif
}

/*
 * Computes the distance between @start_row and @end_row in pixels
 */
static int
eti_row_diff (ETableItem *eti, int start_row, int end_row)
{
    int row, total;

    total = 0;

    for (row = start_row; row < end_row; row++)
        total += eti_row_height (eti, row) + 1;

    return total;
}

/*
 * eti_request_region_redraw:
 *
 * Request a canvas redraw on the range (start_col, start_row) to (end_col, end_row).
 * This is inclusive (ie, you can use: 0,0-0,0 to redraw the first cell).
 *
 * The @border argument is a number of pixels around the region that should also be queued
 * for redraw.   This is typically used by the focus routines to queue a redraw for the
 * border as well.
 */
static void
eti_request_region_redraw (ETableItem *eti,
               int start_col, int start_row,
               int end_col, int end_row, int border)
{
    int x1, y1, width, height;

    if (eti->rows > 0) {
        
        x1 = e_table_header_col_diff (eti->header, 0, start_col);
        y1 = eti_row_diff (eti, 0, start_row);
        width = e_table_header_col_diff (eti->header, start_col, end_col + 1);
        height = eti_row_diff (eti, start_row, end_row + 1);
        
        eti_item_region_redraw (eti, eti->x1 + x1 - border,
                    eti->y1 + y1 - border,
                    eti->x1 + x1 + width + 1 + border,
                    eti->y1 + y1 + height + 1 + border);
    }
}

/*
 * eti_request_region_show
 *
 * Request a canvas show on the range (start_col, start_row) to (end_col, end_row).
 * This is inclusive (ie, you can use: 0,0-0,0 to show the first cell).
 */
static void
eti_request_region_show (ETableItem *eti,
             int start_col, int start_row,
             int end_col, int end_row, int delay)
{
    int x1, y1, x2, y2;
    
    x1 = e_table_header_col_diff (eti->header, 0, start_col);
    y1 = eti_row_diff (eti, 0, start_row);
    x2 = x1 + e_table_header_col_diff (eti->header, start_col, end_col + 1);
    y2 = y1 + eti_row_diff (eti, start_row, end_row + 1);

    if (delay)
        e_canvas_item_show_area_delayed(GNOME_CANVAS_ITEM(eti), x1, y1, x2, y2, delay);
    else
        e_canvas_item_show_area(GNOME_CANVAS_ITEM(eti), x1, y1, x2, y2);
}

static void
eti_table_model_row_changed (ETableModel *table_model, int row, ETableItem *eti)
{
    if (eti->renderers_can_change_size &&
        eti->height_cache && eti->height_cache[row] != -1 &&
        eti_row_height_real(eti, row) != eti->height_cache[row]) {
        eti_table_model_changed (table_model, eti);
        return;
    }

    eti_request_region_redraw (eti, 0, row, eti->cols - 1, row, 0);
}

static void
eti_table_model_cell_changed (ETableModel *table_model, int col, int row, ETableItem *eti)
{
    if (eti->renderers_can_change_size &&
        eti->height_cache && eti->height_cache[row] != -1 &&
        eti_row_height_real(eti, row) != eti->height_cache[row]) {
        eti_table_model_changed (table_model, eti);
        return;
    }

    eti_request_region_redraw (eti, 0, row, eti->cols - 1, row, 0);
}

static void
eti_table_model_row_inserted (ETableModel *table_model, int row, ETableItem *eti)
{
    eti->rows = e_table_model_row_count (eti->table_model);

    if (eti->height_cache) {
        eti->height_cache = g_renew(int, eti->height_cache, eti->rows);
        memmove(eti->height_cache + row + 1, eti->height_cache + row, (eti->rows - 1 - row) * sizeof(int));
        eti->height_cache[row] = -1;
    }

    eti->needs_compute_height = 1;
    e_canvas_item_request_reflow (GNOME_CANVAS_ITEM (eti));
    eti->needs_redraw = 1;
    gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (eti));
}

static void
eti_table_model_row_deleted (ETableModel *table_model, int row, ETableItem *eti)
{
    eti->rows = e_table_model_row_count (eti->table_model);

    if (eti->height_cache)
        memmove(eti->height_cache + row, eti->height_cache + row + 1, (eti->rows - row) * sizeof(int));

    eti->needs_compute_height = 1;
    e_canvas_item_request_reflow (GNOME_CANVAS_ITEM (eti));
    eti->needs_redraw = 1;
    gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (eti));
}

/** 
 * e_table_item_redraw_range
 * @eti: %ETableItem which will be redrawn
 * @start_col: The first col to redraw.
 * @start_row: The first row to redraw.
 * @end_col: The last col to redraw.
 * @end_row: The last row to redraw.
 *
 * This routine redraws the given %ETableItem in the range given.  The
 * range is inclusive at both ends.
 */
void
e_table_item_redraw_range (ETableItem *eti,
               int start_col, int start_row,
               int end_col, int end_row)
{
    int border;
    int cursor_col, cursor_row;
    
    g_return_if_fail (eti != NULL);
    g_return_if_fail (E_IS_TABLE_ITEM (eti));
    
    gtk_object_get(GTK_OBJECT(eti->selection),
               "cursor_col", &cursor_col,
               "cursor_row", &cursor_row,
               NULL);

    if ((start_col == cursor_col) ||
        (end_col   == cursor_col) ||
        (view_to_model_row(eti, start_row) == cursor_row) ||
        (view_to_model_row(eti, end_row)   == cursor_row))
        border = 2;
    else
        border = 0;

    eti_request_region_redraw(eti, start_col, start_row, end_col, end_row, border);
}

static void
eti_add_table_model (ETableItem *eti, ETableModel *table_model)
{
    g_assert (eti->table_model == NULL);
    
    eti->table_model = table_model;
    gtk_object_ref (GTK_OBJECT (eti->table_model));

    eti->table_model_pre_change_id = gtk_signal_connect (
        GTK_OBJECT (table_model), "model_pre_change",
        GTK_SIGNAL_FUNC (eti_table_model_pre_change), eti);

    eti->table_model_change_id = gtk_signal_connect (
        GTK_OBJECT (table_model), "model_changed",
        GTK_SIGNAL_FUNC (eti_table_model_changed), eti);

    eti->table_model_row_change_id = gtk_signal_connect (
        GTK_OBJECT (table_model), "model_row_changed",
        GTK_SIGNAL_FUNC (eti_table_model_row_changed), eti);

    eti->table_model_cell_change_id = gtk_signal_connect (
        GTK_OBJECT (table_model), "model_cell_changed",
        GTK_SIGNAL_FUNC (eti_table_model_cell_changed), eti);

    eti->table_model_row_inserted_id = gtk_signal_connect (
        GTK_OBJECT (table_model), "model_row_inserted",
        GTK_SIGNAL_FUNC (eti_table_model_row_inserted), eti);

    eti->table_model_row_deleted_id = gtk_signal_connect (
        GTK_OBJECT (table_model), "model_row_deleted",
        GTK_SIGNAL_FUNC (eti_table_model_row_deleted), eti);

    if (eti->header) {
        eti_detach_cell_views (eti);
        eti_attach_cell_views (eti);
    }

    if (E_IS_TABLE_SUBSET(table_model)) {
        eti->uses_source_model = 1;
        eti->source_model = E_TABLE_SUBSET(table_model)->source;
        if (eti->source_model)
            gtk_object_ref(GTK_OBJECT(eti->source_model));
    }
    
    eti_table_model_changed (table_model, eti);
}

static void
eti_add_table_selection_model (ETableItem *eti, ETableSelectionModel *selection)
{
    g_assert (eti->selection == NULL);
    
    eti->selection = selection;
    gtk_object_ref (GTK_OBJECT (eti->selection));

    eti->selection_change_id = gtk_signal_connect (
        GTK_OBJECT (selection), "selection_changed",
        GTK_SIGNAL_FUNC (eti_selection_change), eti);

    eti->cursor_change_id = gtk_signal_connect (
        GTK_OBJECT (selection), "cursor_changed",
        GTK_SIGNAL_FUNC (eti_cursor_change), eti);

    eti->cursor_activated_id = gtk_signal_connect (
        GTK_OBJECT (selection), "cursor_activated",
        GTK_SIGNAL_FUNC (eti_cursor_activated), eti);

    eti_selection_change(selection, eti);
}

static void
eti_header_dim_changed (ETableHeader *eth, int col, ETableItem *eti)
{
    eti->needs_compute_width = 1;
    e_canvas_item_request_reflow (GNOME_CANVAS_ITEM (eti));
}

static void
eti_header_structure_changed (ETableHeader *eth, ETableItem *eti)
{
    eti->cols = e_table_header_count (eti->header);
    eti->width = e_table_header_total_width (eti->header);

    /*
     * There should be at least one column
     *  BUT: then you can't remove all columns from a header and add new ones.
     */
    /*g_assert (eti->cols != 0);*/

    if (eti->cell_views){
        eti_unrealize_cell_views (eti);
        eti_detach_cell_views (eti);
        eti_attach_cell_views (eti);
        eti_realize_cell_views (eti);
    } else {
        if (eti->table_model) {
            eti_detach_cell_views (eti);
            eti_attach_cell_views (eti);
        }
    }
    eti->needs_compute_width = 1;
    e_canvas_item_request_reflow (GNOME_CANVAS_ITEM (eti));
}

static int
eti_request_column_width (ETableHeader *eth, int col, ETableItem *eti)
{
    int width = 0;
    
    if (eti->cell_views) {
        width = e_cell_max_width (eti->cell_views[col], view_to_model_col(eti, col), col);
    }

    return width;
}

static void
eti_add_header_model (ETableItem *eti, ETableHeader *header)
{
    g_assert (eti->header == NULL);
    
    eti->header = header;
    gtk_object_ref (GTK_OBJECT (header));

    eti_header_structure_changed (header, eti);
    
    eti->header_dim_change_id = gtk_signal_connect (
        GTK_OBJECT (header), "dimension_change",
        GTK_SIGNAL_FUNC (eti_header_dim_changed), eti);

    eti->header_structure_change_id = gtk_signal_connect (
        GTK_OBJECT (header), "structure_change",
        GTK_SIGNAL_FUNC (eti_header_structure_changed), eti);

    eti->header_request_width_id = gtk_signal_connect 
        (GTK_OBJECT (header), "request_width",
         GTK_SIGNAL_FUNC (eti_request_column_width), eti);
}

/*
 * GtkObject::destroy method
 */
static void
eti_destroy (GtkObject *object)
{
    ETableItem *eti = E_TABLE_ITEM (object);

    eti_remove_header_model (eti);
    eti_remove_table_model (eti);
    eti_remove_table_selection_model (eti);

    if (eti->height_cache_idle_id)
        g_source_remove(eti->height_cache_idle_id);

    if (eti->height_cache)
        g_free (eti->height_cache);
    eti->height_cache = NULL;
    eti->height_cache_idle_count = 0;

    if (eti->tooltip->window)
        gtk_widget_destroy (eti->tooltip->window);
    if (eti->tooltip->timer) {
        gtk_timeout_remove (eti->tooltip->timer);
        eti->tooltip->timer = 0;
    }
    g_free (eti->tooltip);

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

static void
eti_set_arg (GtkObject *o, GtkArg *arg, guint arg_id)
{
    GnomeCanvasItem *item;
    ETableItem *eti;
    int cursor_col;

    item = GNOME_CANVAS_ITEM (o);
    eti = E_TABLE_ITEM (o);

    switch (arg_id){
    case ARG_TABLE_HEADER:
        eti_remove_header_model (eti);
        eti_add_header_model (eti, E_TABLE_HEADER(GTK_VALUE_OBJECT (*arg)));
        break;

    case ARG_TABLE_MODEL:
        eti_remove_table_model (eti);
        eti_add_table_model (eti, E_TABLE_MODEL(GTK_VALUE_OBJECT (*arg)));
        break;
        
    case ARG_TABLE_SELECTION_MODEL:
        eti_remove_table_selection_model (eti);
        if (GTK_VALUE_OBJECT (*arg))
            eti_add_table_selection_model (eti, E_TABLE_SELECTION_MODEL(GTK_VALUE_OBJECT (*arg)));
        break;
        
    case ARG_LENGTH_THRESHOLD:
        eti->length_threshold = GTK_VALUE_INT (*arg);
        break;

    case ARG_TABLE_DRAW_GRID:
        eti->draw_grid = GTK_VALUE_BOOL (*arg);
        break;

    case ARG_TABLE_DRAW_FOCUS:
        eti->draw_focus = GTK_VALUE_BOOL (*arg);
        break;

    case ARG_CURSOR_MODE:
        eti->cursor_mode = GTK_VALUE_INT (*arg);
        break;

    case ARG_MINIMUM_WIDTH:
    case ARG_WIDTH:
        if (eti->minimum_width == eti->width && GTK_VALUE_DOUBLE (*arg) > eti->width)
            e_canvas_item_request_reflow (GNOME_CANVAS_ITEM (eti));
        eti->minimum_width = GTK_VALUE_DOUBLE (*arg);
        if (eti->minimum_width < eti->width)
            e_canvas_item_request_reflow (GNOME_CANVAS_ITEM (eti));
        break;
    case ARG_CURSOR_ROW:
        gtk_object_get(GTK_OBJECT(eti->selection),
                   "cursor_col", &cursor_col,
                   NULL);

        e_table_item_focus (eti, cursor_col != -1 ? cursor_col : 0, view_to_model_row(eti, GTK_VALUE_INT (*arg)), 0);
        break;
    }
    eti->needs_redraw = 1;
    gnome_canvas_item_request_update (GNOME_CANVAS_ITEM(eti));
}

static void
eti_get_arg (GtkObject *o, GtkArg *arg, guint arg_id)
{
    GnomeCanvasItem *item;
    ETableItem *eti;
    int row;

    item = GNOME_CANVAS_ITEM (o);
    eti = E_TABLE_ITEM (o);

    switch (arg_id){
    case ARG_WIDTH:
        GTK_VALUE_DOUBLE (*arg) = eti->width;
        break;
    case ARG_HEIGHT:
        GTK_VALUE_DOUBLE (*arg) = eti->height;
        break;
    case ARG_MINIMUM_WIDTH:
        GTK_VALUE_DOUBLE (*arg) = eti->minimum_width;
        break;
    case ARG_CURSOR_ROW:
        gtk_object_get(GTK_OBJECT(eti->selection),
                   "cursor_row", &row,
                   NULL);
        GTK_VALUE_INT (*arg) = model_to_view_row(eti, row);
        break;
    default:
        arg->type = GTK_TYPE_INVALID;
    }
}

static void
eti_init (GnomeCanvasItem *item)
{
    ETableItem *eti = E_TABLE_ITEM (item);

    eti->editing_col               = -1;
    eti->editing_row               = -1;
    eti->height                    = 0;
    eti->width                     = 0;
    eti->minimum_width             = 0;

    eti->height_cache              = NULL;
    eti->height_cache_idle_id      = 0;
    eti->height_cache_idle_count   = 0;
    
    eti->length_threshold          = -1;
    eti->renderers_can_change_size = 1;

    eti->uses_source_model         = 0;
    eti->source_model              = NULL;
    
    eti->row_guess                 = -1;
    eti->cursor_mode               = E_TABLE_CURSOR_SIMPLE;

    eti->selection_change_id       = 0;
    eti->cursor_change_id          = 0;
    eti->cursor_activated_id          = 0;
    eti->selection                 = NULL;

    eti->needs_redraw              = 0;
    eti->needs_compute_height      = 0;

    eti->in_key_press              = 0;

    eti->tooltip                   = g_new0 (ETableTooltip, 1);
    eti->tooltip->timer            = 0;
    eti->tooltip->window           = NULL;
    eti->tooltip->eti              = GNOME_CANVAS_ITEM (eti);

    eti->grabbed_col               = -1;
    eti->grabbed_row               = -1;

    e_canvas_item_set_reflow_callback (GNOME_CANVAS_ITEM (eti), eti_reflow);
}

#define gray50_width 2
#define gray50_height 2
static const char gray50_bits[] = {
    0x02, 0x01, };

#if 0
static gint
eti_visibility_notify(GtkWidget          *widget,
              GdkEventVisibility *event,
              ETableItem         *eti)
{
    if (eti->tooltip->window) {
        gtk_widget_destroy (eti->tooltip->window);
        eti->tooltip->window = NULL;
    }

    return FALSE;
}
#endif

static void
eti_realize (GnomeCanvasItem *item)
{
    ETableItem *eti = E_TABLE_ITEM (item);
    GtkWidget *canvas_widget = GTK_WIDGET (item->canvas);
    GdkWindow *window;
#if 0
    GdkEventMask mask;
#endif
    
    if (GNOME_CANVAS_ITEM_CLASS (eti_parent_class)->realize)
                (*GNOME_CANVAS_ITEM_CLASS (eti_parent_class)->realize)(item);

    /*
     * Gdk Resource allocation
     */
    window = canvas_widget->window;

    eti->fill_gc = gdk_gc_new (window);

    eti->grid_gc = gdk_gc_new (window);
#if 0
    /* This sets it to gray */
/*  gdk_gc_set_foreground (eti->grid_gc, &canvas_widget->style->bg [GTK_STATE_NORMAL]); */
#else
    gdk_gc_set_foreground (eti->grid_gc, &canvas_widget->style->dark [GTK_STATE_NORMAL]);
#endif
    eti->focus_gc = gdk_gc_new (window);
    gdk_gc_set_foreground (eti->focus_gc, &canvas_widget->style->bg [GTK_STATE_NORMAL]);
    gdk_gc_set_background (eti->focus_gc, &canvas_widget->style->fg [GTK_STATE_NORMAL]);
    eti->stipple = gdk_bitmap_create_from_data (NULL, gray50_bits, gray50_width, gray50_height);
    gdk_gc_set_ts_origin (eti->focus_gc, 0, 0);
    gdk_gc_set_stipple (eti->focus_gc, eti->stipple);
    gdk_gc_set_fill (eti->focus_gc, GDK_OPAQUE_STIPPLED);

#if 0
    mask = gtk_widget_get_events(GTK_WIDGET(item->canvas));
    mask |= GDK_VISIBILITY_NOTIFY_MASK;
    gtk_widget_set_events(GTK_WIDGET(item->canvas), mask);

    gtk_signal_connect(GTK_OBJECT(item->canvas), "visibility_notify_event",
               GTK_SIGNAL_FUNC(eti_visibility_notify), eti);
#endif

    if (eti->cell_views == NULL)
        eti_attach_cell_views (eti);
    
    eti_realize_cell_views (eti);

    eti->needs_compute_height = 1;
    e_canvas_item_request_reflow (GNOME_CANVAS_ITEM (eti));
    eti->needs_redraw = 1;
    gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (eti));
}

static void
eti_unrealize (GnomeCanvasItem *item)
{
    ETableItem *eti = E_TABLE_ITEM (item);
    
    if (eti_editing (eti))
        e_table_item_leave_edit (eti);

    gdk_gc_unref (eti->fill_gc);
    eti->fill_gc = NULL;
    gdk_gc_unref (eti->grid_gc);
    eti->grid_gc = NULL;
    gdk_gc_unref (eti->focus_gc);
    eti->focus_gc = NULL;
    gdk_bitmap_unref (eti->stipple);
    eti->stipple = NULL;
    
    eti_unrealize_cell_views (eti);

    eti->height = 0;
    
    if (GNOME_CANVAS_ITEM_CLASS (eti_parent_class)->unrealize)
                (*GNOME_CANVAS_ITEM_CLASS (eti_parent_class)->unrealize)(item);
}

static void
eti_draw (GnomeCanvasItem *item, GdkDrawable *drawable, int x, int y, int width, int height)
{
    ETableItem *eti = E_TABLE_ITEM (item);
    const int rows = eti->rows;
    const int cols = eti->cols;
    int row, col, y1, y2;
    int first_col, last_col, x_offset;
    int first_row, last_row, y_offset, yd;
    int x1, x2;
    int f_x1, f_x2, f_y1, f_y2;
    gboolean f_found;
    double i2c [6];
    ArtPoint eti_base, eti_base_item, lower_right;
    GtkWidget *canvas = GTK_WIDGET(item->canvas);
    GdkColor *background;
    
    /*
     * Clear the background
     */
#if 0
    gdk_draw_rectangle (
        drawable, eti->fill_gc, TRUE,
        eti->x1 - x, eti->y1 - y, eti->width, eti->height);
#endif

    /*
     * Find out our real position after grouping
     */
    gnome_canvas_item_i2c_affine (item, i2c);
    eti_base_item.x = eti->x1;
    eti_base_item.y = eti->y1;
    art_affine_point (&eti_base, &eti_base_item, i2c);

    eti_base_item.x = eti->x1 + eti->width;
    eti_base_item.y = eti->y1 + eti->height;
    art_affine_point (&lower_right, &eti_base_item, i2c);

    /*
     * First column to draw, last column to draw
     */
    first_col = -1;
    last_col = x_offset = 0;
    x1 = x2 = floor (eti_base.x);
    for (col = 0; col < cols; col++, x1 = x2){
        ETableCol *ecol = e_table_header_get_column (eti->header, col);

        x2 = x1 + ecol->width;
        
        if (x1 > (x + width))
            break;
        if (x2 < x)
            continue;
        if (first_col == -1){
            x_offset = x1 - x;
            first_col = col;
        }
    }
    last_col = col;

    /*
     * Nothing to paint
     */
    if (first_col == -1)
        return;

    /*
     * Compute row span.
     */
    first_row = -1;
    y_offset = 0;
    y1 = y2 = floor (eti_base.y) + 1;
    for (row = 0; row < rows; row++, y1 = y2){

        y2 += ETI_ROW_HEIGHT (eti, row) + 1;

        if (y1 > y + height)
            break;

        if (y2 < y)
            continue;

        if (first_row == -1){
            y_offset = y1 - y;
            first_row = row;
        }
    }
    last_row = row;

    if (first_row == -1)
        return;

    /*
     * Draw cells
     */
    yd = y_offset;
    f_x1 = f_x2 = f_y1 = f_y2 = -1;
    f_found = FALSE;

    if (eti->draw_grid && first_row == 0){
        gdk_draw_line (
            drawable, eti->grid_gc,
                eti_base.x - x, yd, eti_base.x + eti->width - x, yd);
    }
    yd++;
    
    for (row = first_row; row < last_row; row++){
        int xd, height;
        gboolean selected;
        gint cursor_col, cursor_row;
        
        height = ETI_ROW_HEIGHT (eti, row);

        xd = x_offset;
/*      printf ("paint: %d %d\n", yd, yd + height); */
        
        selected = e_table_selection_model_is_row_selected(eti->selection, view_to_model_row(eti,row));
        
        gtk_object_get(GTK_OBJECT(eti->selection),
                   "cursor_col", &cursor_col,
                   "cursor_row", &cursor_row,
                   NULL);
        
        for (col = first_col; col < last_col; col++){
            ETableCol *ecol = e_table_header_get_column (eti->header, col);
            ECellView *ecell_view = eti->cell_views [col];
            gboolean col_selected = selected;
            ECellFlags flags;
            switch (eti->cursor_mode) {
            case E_TABLE_CURSOR_SIMPLE:
            case E_TABLE_CURSOR_SPREADSHEET:
                if (cursor_col == ecol->col_idx && cursor_row == view_to_model_row(eti, row))
                    col_selected = !col_selected;
                break;
            case E_TABLE_CURSOR_LINE:
                /* Nothing */
                break;
            }

            if (col_selected){
                if (GTK_WIDGET_HAS_FOCUS(canvas))
                    background = &canvas->style->bg [GTK_STATE_SELECTED];
                else
                    background = &canvas->style->bg [GTK_STATE_ACTIVE];
            } else {
#if 0
                if (row % 2)
#endif
                    background = &canvas->style->base [GTK_STATE_NORMAL];
#if 0
                else
                    background = &canvas->style->base [GTK_STATE_SELECTED];
#endif
            }

            gdk_gc_set_foreground (eti->fill_gc, background);
            gdk_draw_rectangle (drawable, eti->fill_gc, TRUE,
                        xd, yd, ecol->width, height);

            flags = col_selected ? E_CELL_SELECTED : 0;
            flags |= GTK_WIDGET_HAS_FOCUS(canvas) ? E_CELL_FOCUSED : 0;
            switch (ecol->justification) {
            case GTK_JUSTIFY_LEFT:
                flags |= E_CELL_JUSTIFY_LEFT;
                break;
            case GTK_JUSTIFY_RIGHT:
                flags |= E_CELL_JUSTIFY_RIGHT;
                break;
            case GTK_JUSTIFY_CENTER:
                flags |= E_CELL_JUSTIFY_CENTER;
                break;
            case GTK_JUSTIFY_FILL:
                flags |= E_CELL_JUSTIFY_FILL;
                break;
            }

            e_cell_draw (ecell_view, drawable, ecol->col_idx, col, row, flags,
                     xd, yd, xd + ecol->width, yd + height);
            
            if (!f_found) {
                switch (eti->cursor_mode) {
                case E_TABLE_CURSOR_LINE:
                    if (view_to_model_row(eti, row) == cursor_row) {
                        f_x1 = floor (eti_base.x) - x;
                        f_x2 = floor (lower_right.x) - x;
                        f_y1 = yd;
                        f_y2 = yd + height;
                        f_found = TRUE;
                    }
                    break;
                case E_TABLE_CURSOR_SIMPLE:
                case E_TABLE_CURSOR_SPREADSHEET:
                    if (view_to_model_col(eti, col) == cursor_col && view_to_model_row(eti, row) == cursor_row) {
                        f_x1 = xd;
                        f_x2 = xd + ecol->width;
                        f_y1 = yd;
                        f_y2 = yd + height;
                        f_found = TRUE;
                    }
                    break;
                }
            }

            xd += ecol->width;
        }
        yd += height;

        if (eti->draw_grid)
            gdk_draw_line (
                drawable, eti->grid_gc,
                eti_base.x - x, yd, eti_base.x + eti->width - x, yd);
        yd++;
    }

    if (eti->draw_grid){
        int xd = x_offset;
        
        for (col = first_col; col <= last_col; col++){
            ETableCol *ecol = e_table_header_get_column (eti->header, col);

            gdk_draw_line (
                drawable, eti->grid_gc,
                xd, y_offset, xd, yd - 1);

            /*
             * This looks wierd, but it is to draw the last line
             */
            if (ecol)
                xd += ecol->width;
        }
    }
    
    /*
     * Draw focus
     */
    if (eti->draw_focus && f_found) {
        gdk_gc_set_ts_origin (eti->focus_gc, f_x1, f_y1);
        gdk_draw_rectangle (drawable, eti->focus_gc, FALSE,
                    f_x1, f_y1, f_x2 - f_x1 - 1, f_y2 - f_y1 - 1);
    }
}

static double
eti_point (GnomeCanvasItem *item, double x, double y, int cx, int cy,
       GnomeCanvasItem **actual_item)
{
    *actual_item = item;

    return 0.0;
}

static gboolean
find_cell (ETableItem *eti, double x, double y, int *col_res, int *row_res, double *x1_res, double *y1_res)
{
    const int cols = eti->cols;
    const int rows = eti->rows;
    gdouble x1, y1, x2, y2;
    int col, row;
    
    /* FIXME: this routine is inneficient, fix later */

    if (eti->grabbed_col >= 0 && eti->grabbed_row >= 0) {
        *col_res = eti->grabbed_col;
        *row_res = eti->grabbed_row;
        *x1_res = x - eti->x1 - e_table_header_col_diff (eti->header, 0, eti->grabbed_col);
        *y1_res = y - eti->y1 - eti_row_diff (eti, 0, eti->grabbed_row);
        return TRUE;
    }
    
    if (cols == 0 || rows == 0)
        return FALSE;

    x -= eti->x1;
    y -= eti->y1;
    
    x1 = 0;
    for (col = 0; col < cols - 1; col++, x1 = x2){
        ETableCol *ecol = e_table_header_get_column (eti->header, col);

        if (x < x1)
            return FALSE;
        
        x2 = x1 + ecol->width;

        if (x <= x2)
            break;
    }

    y1 = y2 = 0;
    for (row = 0; row < rows - 1; row++, y1 = y2){
        if (y < y1)
            return FALSE;
        
        y2 += ETI_ROW_HEIGHT (eti, row) + 1;

        if (y <= y2)
            break;
    }
    *col_res = col;
    if (x1_res)
        *x1_res = x - x1;
    *row_res = row;
    if (y1_res)
        *y1_res = y - y1;
    return TRUE;
}

static void
eti_cursor_move (ETableItem *eti, gint row, gint column)
{
    e_table_item_leave_edit (eti);
    e_table_item_focus (eti, view_to_model_col(eti, column), view_to_model_row(eti, row), 0);
}

static void
eti_cursor_move_left (ETableItem *eti)
{
    int cursor_col, cursor_row;
    gtk_object_get(GTK_OBJECT(eti->selection),
               "cursor_col", &cursor_col,
               "cursor_row", &cursor_row,
               NULL);

    eti_cursor_move (eti, model_to_view_row(eti, cursor_row), model_to_view_col(eti, cursor_col) - 1);
}

static void
eti_cursor_move_right (ETableItem *eti)
{
    int cursor_col, cursor_row;
    gtk_object_get(GTK_OBJECT(eti->selection),
               "cursor_col", &cursor_col,
               "cursor_row", &cursor_row,
               NULL);

    eti_cursor_move (eti, model_to_view_row(eti, cursor_row), model_to_view_col(eti, cursor_col) + 1);
}

#if 0
static void
eti_cursor_move_up (ETableItem *eti)
{
    int cursor_col, cursor_row;
    gtk_object_get(GTK_OBJECT(eti->selection),
               "cursor_col", &cursor_col,
               "cursor_row", &cursor_row,
               NULL);

    eti_cursor_move (eti, model_to_view_row(eti, cursor_row) - 1, model_to_view_col(eti, cursor_col));
}

static void
eti_cursor_move_down (ETableItem *eti)
{
    int cursor_col, cursor_row;
    gtk_object_get(GTK_OBJECT(eti->selection),
               "cursor_col", &cursor_col,
               "cursor_row", &cursor_row,
               NULL);

    eti_cursor_move (eti, model_to_view_row(eti, cursor_row) + 1, model_to_view_col(eti, cursor_col));
}
#endif

#if 0
static int
_do_tooltip (ETableItem *eti)
{
    ECellView *ecell_view;
    int x = 0, y = 0;
    int i;

    if (eti->tooltip->window) {
        gtk_widget_destroy (eti->tooltip->window);
        eti->tooltip->window = NULL;
    }

    if (eti_editing (eti))
        return FALSE;

    ecell_view = eti->cell_views[eti->tooltip->col];

    for (i = 0; i < eti->tooltip->col; i++)
        x += eti->header->columns[i]->width;
    eti->tooltip->x = x;

    for (i = 0; i < eti->tooltip->row; i++)
        y += (ETI_ROW_HEIGHT (eti, i) + 1);
    eti->tooltip->y = y;
    eti->tooltip->row_height = ETI_ROW_HEIGHT (eti, i);
    
    e_cell_show_tooltip (ecell_view, 
                 view_to_model_col (eti, eti->tooltip->col),
                 eti->tooltip->col,
                 eti->tooltip->row,
                 eti->header->columns[eti->tooltip->col]->width,
                 eti->tooltip);
    return FALSE;
}
#endif

static gint
eti_e_cell_event     (ETableItem *item, ECellView *ecell_view, GdkEvent *event, int time, int model_col, int view_col, int row, ECellFlags flags)
{
    ECellActions actions = 0;
    gint ret_val;

    ret_val = e_cell_event (ecell_view, event, model_col, view_col, row, flags, &actions);

    if (actions & E_CELL_GRAB) {
        gnome_canvas_item_grab(GNOME_CANVAS_ITEM(item), GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK, NULL, time);
        item->grabbed_col = view_col;
        item->grabbed_row = row;
    }

    if (actions & E_CELL_UNGRAB) {
        gnome_canvas_item_ungrab(GNOME_CANVAS_ITEM(item), time);
        item->grabbed_col = -1;
        item->grabbed_row = -1;
    }

    return ret_val;
}

/* FIXME: cursor */
static int
eti_event (GnomeCanvasItem *item, GdkEvent *e)
{
    ETableItem *eti = E_TABLE_ITEM (item);
    ECellView *ecell_view;
    gint return_val = TRUE;

    switch (e->type){
    case GDK_BUTTON_PRESS: {
        double x1, y1;
        GdkEventButton button;
        int col, row;
        gint cursor_row, cursor_col;

        if (eti->tooltip->timer) {
            gtk_timeout_remove (eti->tooltip->timer);
            eti->tooltip->timer = 0;
        }

        switch (e->button.button) {
        case 1: /* Fall through. */
        case 2:
            e_canvas_item_grab_focus(GNOME_CANVAS_ITEM(eti));
            gnome_canvas_item_w2i (item, &e->button.x, &e->button.y);

            if (!find_cell (eti, e->button.x, e->button.y, &col, &row, &x1, &y1)) {
                if (eti_editing (eti))
                    e_table_item_leave_edit (eti);
                return TRUE;
            }

            ecell_view = eti->cell_views [col];
            button = *(GdkEventButton *)e;
            button.x = x1;
            button.y = y1;

            return_val = eti_e_cell_event (eti, ecell_view, (GdkEvent *) &button, button.time, view_to_model_col(eti, col), col, row, 0);
            if (return_val)
                return TRUE;

            return_val = FALSE;
            gtk_signal_emit (GTK_OBJECT (eti), eti_signals [CLICK],
                     row, view_to_model_col(eti, col), &button, &return_val);
            if (return_val)
                return TRUE;

            e_table_selection_model_do_something(eti->selection, view_to_model_row(eti, row), view_to_model_col(eti, col), button.state);

            gtk_object_get(GTK_OBJECT(eti->selection),
                       "cursor_row", &cursor_row,
                       "cursor_col", &cursor_col,
                       NULL);
            
            if (cursor_row == view_to_model_row(eti, row) && cursor_col == view_to_model_col(eti, col)){

                if ((!eti_editing(eti)) && e_table_model_is_cell_editable(eti->selection->model, cursor_col, cursor_row)) {
                    e_table_item_enter_edit (eti, col, row);
                }

                /*
                 * Adjust the event positions
                 */

                return_val = eti_e_cell_event (eti, ecell_view, (GdkEvent *) &button, button.time, 
                                   view_to_model_col(eti, col), col, row, E_CELL_EDITING);
            }
            g_print("Single click\n");

            break;
        case 3:
            e_canvas_item_grab_focus(GNOME_CANVAS_ITEM(eti));
            gnome_canvas_item_w2i (item, &e->button.x, &e->button.y);
            if (!find_cell (eti, e->button.x, e->button.y, &col, &row, &x1, &y1))
                return TRUE;

            e_table_selection_model_maybe_do_something(eti->selection, view_to_model_row(eti, row), view_to_model_col(eti, col), 0);
            
            gtk_signal_emit (GTK_OBJECT (eti), eti_signals [RIGHT_CLICK],
                     row, view_to_model_col(eti, col), e, &return_val);
            break;
        case 4:
        case 5:
            return FALSE;
            break;
            
        }
        break;
    }

    case GDK_BUTTON_RELEASE: {
        double x1, y1;
        int col, row;
        gint cursor_row, cursor_col;

        if (eti->tooltip->timer) {
            gtk_timeout_remove (eti->tooltip->timer);
            eti->tooltip->timer = 0;
        }
        if (eti->tooltip->window) {
            gtk_widget_destroy (eti->tooltip->window);
            eti->tooltip->window = NULL;
        }
        switch (e->button.button) {
        case 1: /* Fall through. */
        case 2:
            gnome_canvas_item_w2i (item, &e->button.x, &e->button.y);

            if (!find_cell (eti, e->button.x, e->button.y, &col, &row, &x1, &y1))
                return TRUE;

            gtk_object_get(GTK_OBJECT(eti->selection),
                       "cursor_row", &cursor_row,
                       "cursor_col", &cursor_col,
                       NULL);

            if (cursor_row == view_to_model_row(eti, row) && cursor_col == view_to_model_col(eti, col)){

                ecell_view = eti->cell_views [col];

                /*
                 * Adjust the event positions
                 */
                e->button.x = x1;
                e->button.y = y1;

                return_val = eti_e_cell_event (eti, ecell_view, e, e->button.time,
                                   view_to_model_col(eti, col), col, row, E_CELL_EDITING);
            }
            g_print ("Release\n");
            break;
        case 3:
        case 4:
        case 5:
            return FALSE;
            break;
            
        }
        break;
    }

    case GDK_2BUTTON_PRESS: {
        int col, row;
        GdkEventButton button;

        if (e->button.button == 5 ||
            e->button.button == 4)
            return FALSE;

        gnome_canvas_item_w2i (item, &e->button.x, &e->button.y);

#if 0
        if (!find_cell (eti, e->button.x, e->button.y, &col, &row, &x1, &y1))
            return TRUE;
#endif
        gtk_object_get(GTK_OBJECT(eti->selection),
                   "cursor_row", &row,
                   "cursor_col", &col,
                   NULL);

#if 0
        button = *(GdkEventButton *)e;
        button.x = x1;
        button.y = y1;
#endif

        gtk_signal_emit (GTK_OBJECT (eti), eti_signals [DOUBLE_CLICK],
                 row, col, &button);
        g_print("Double click\n");
        break;
    }
    case GDK_MOTION_NOTIFY: {
        int col, row;
        double x1, y1;
        gint cursor_col, cursor_row;

        gnome_canvas_item_w2i (item, &e->motion.x, &e->motion.y);

        if (!find_cell (eti, e->motion.x, e->motion.y, &col, &row, &x1, &y1))
            return TRUE;

        gtk_object_get(GTK_OBJECT(eti->selection),
                   "cursor_row", &cursor_row,
                   "cursor_col", &cursor_col,
                   NULL);

        if (eti->tooltip->window) {
            gtk_widget_destroy (eti->tooltip->window);
            eti->tooltip->window = NULL;
        }

        if (eti->tooltip->timer > 0)
            gtk_timeout_remove (eti->tooltip->timer);
#if 0
        eti->tooltip->col = col;
        eti->tooltip->row = row;
        eti->tooltip->cx = e->motion.x;
        eti->tooltip->cy = e->motion.y;
        eti->tooltip->timer = gtk_timeout_add (100, (GSourceFunc)_do_tooltip, eti);
#endif

        if (cursor_row == view_to_model_row(eti, row) && cursor_col == view_to_model_col(eti, col)){
            ecell_view = eti->cell_views [col];

            /*
             * Adjust the event positions
             */
            e->motion.x = x1;
            e->motion.y = y1;
            
            return_val = eti_e_cell_event (eti, ecell_view, e, e->motion.time,
                               view_to_model_col(eti, col), col, row, E_CELL_EDITING);
        }
        break;
    }
        
    case GDK_KEY_PRESS: {
        gint cursor_row, cursor_col;
        gint handled = TRUE;

        gtk_object_get(GTK_OBJECT(eti->selection),
                   "cursor_row", &cursor_row,
                   "cursor_col", &cursor_col,
                   NULL);

        if (eti->tooltip->window) {
            gtk_widget_destroy (eti->tooltip->window);
            eti->tooltip->window = NULL;
        }

        if (cursor_col == -1)
            return FALSE;

        eti->in_key_press = TRUE;

        switch (e->key.keyval){
        case GDK_Left:
            if (eti_editing (eti)) {
                handled = FALSE;
                break;
            }
            
            if (eti->cursor_mode != E_TABLE_CURSOR_LINE && cursor_col != view_to_model_col(eti, 0))
                eti_cursor_move_left (eti);
            break;
            
        case GDK_Right:
            if (eti_editing (eti)) {
                handled = FALSE;
                break;
            }

            if (eti->cursor_mode != E_TABLE_CURSOR_LINE && cursor_col != view_to_model_col(eti, eti->cols - 1))
                eti_cursor_move_right (eti);
            break;
            
        case GDK_Up:
        case GDK_Down:
            return_val = e_table_selection_model_key_press(eti->selection, (GdkEventKey *) e);
            break;
#if 0
        case GDK_Up:
            if (cursor_row != view_to_model_row(eti, 0))
                eti_cursor_move_up (eti);
            else
                return_val = FALSE;
            break;
            
        case GDK_Down:
            if (cursor_row != view_to_model_row(eti, eti->rows - 1))
                eti_cursor_move_down (eti);
            else
                return_val = FALSE;
            break;
#endif
        case GDK_Home:
        case GDK_KP_Home:
            if (eti->cursor_mode != E_TABLE_CURSOR_LINE) {
                eti_cursor_move (eti, model_to_view_row(eti, cursor_row), 0);
                return_val = TRUE;
            } else 
                return_val = e_table_selection_model_key_press(eti->selection, (GdkEventKey *) e);
            break;
        case GDK_End:
        case GDK_KP_End:
            if (eti->cursor_mode != E_TABLE_CURSOR_LINE) {
                eti_cursor_move (eti, model_to_view_row(eti, cursor_row), eti->cols - 1);
                return_val = TRUE;
            } else 
                return_val = e_table_selection_model_key_press(eti->selection, (GdkEventKey *) e);
            break;
        case GDK_Tab:
        case GDK_KP_Tab:
        case GDK_ISO_Left_Tab:
            if (eti->cursor_mode == E_TABLE_CURSOR_SPREADSHEET) {
                if ((e->key.state & GDK_SHIFT_MASK) != 0){
                /* shift tab */
                    if (cursor_col != view_to_model_col(eti, 0))
                        eti_cursor_move_left (eti);
                    else if (cursor_row != view_to_model_row(eti, 0))
                        eti_cursor_move (eti, model_to_view_row(eti, cursor_row) - 1, eti->cols - 1);
                    else
                        return_val = FALSE;
                } else {
                    if (cursor_col != view_to_model_col (eti, eti->cols - 1))
                        eti_cursor_move_right (eti);
                    else if (cursor_row != view_to_model_row(eti, eti->rows - 1))
                        eti_cursor_move (eti, model_to_view_row(eti, cursor_row) + 1, 0);
                    else 
                        return_val = FALSE;
                }
                gtk_object_get(GTK_OBJECT(eti->selection),
                           "cursor_row", &cursor_row,
                           "cursor_col", &cursor_col,
                           NULL);

                if (cursor_col >= 0 && cursor_row >= 0 && return_val &&
                    (!eti_editing(eti)) && e_table_model_is_cell_editable(eti->selection->model, cursor_col, cursor_row)) {
                    e_table_item_enter_edit (eti, model_to_view_col(eti, cursor_col), model_to_view_row(eti, cursor_row));
                }
                break;
            } else {
            /* Let tab send you to the next widget. */
            return_val = FALSE;
            break;
            }

        case GDK_Return:
        case GDK_KP_Enter:
        case GDK_ISO_Enter:
        case GDK_3270_Enter:
            if (eti_editing (eti)){
                e_table_item_leave_edit (eti);
#if 0
                ecell_view = eti->cell_views [eti->editing_col];
                return_val = eti_e_cell_event (eti, ecell_view, e, e->key.time,
                                   view_to_model_col(eti, eti->editing_col),
                                   eti->editing_col, eti->editing_row, E_CELL_EDITING);
#endif
            }
            gtk_signal_emit (GTK_OBJECT (eti), eti_signals [KEY_PRESS],
                     model_to_view_row(eti, cursor_row), cursor_col, e, &return_val);
            if (!return_val)
                return_val = e_table_selection_model_key_press(eti->selection, (GdkEventKey *) e);
            break;
            
        default:
            handled = FALSE;
            break;
        }
        if (!handled) {
            if (!eti_editing (eti)){
                gint col, row;
                row = model_to_view_row(eti, cursor_row);
                col = model_to_view_col(eti, cursor_col);
                if (col != -1 && row != -1 && e_table_model_is_cell_editable(eti->selection->model, cursor_col, cursor_row)) {
                    e_table_item_enter_edit (eti, col, row);
                }
            }
            if (!eti_editing (eti)){
                gtk_signal_emit (GTK_OBJECT (eti), eti_signals [KEY_PRESS],
                         model_to_view_row(eti, cursor_row), cursor_col, e, &return_val);
                if (!return_val)
                    e_table_selection_model_key_press(eti->selection, (GdkEventKey *) e);
            } else {
                ecell_view = eti->cell_views [eti->editing_col];
                return_val = eti_e_cell_event (eti, ecell_view, e, e->key.time,
                                   view_to_model_col(eti, eti->editing_col),
                                   eti->editing_col, eti->editing_row, E_CELL_EDITING);
                if (!return_val)
                    e_table_selection_model_key_press(eti->selection, (GdkEventKey *) e);
            }
        }
        eti->in_key_press = FALSE;
        break;
    }
    
    case GDK_KEY_RELEASE: {
        gint cursor_row, cursor_col;

        gtk_object_get(GTK_OBJECT(eti->selection),
                   "cursor_row", &cursor_row,
                   "cursor_col", &cursor_col,
                   NULL);

        if (cursor_col == -1)
            return FALSE;

        if (eti_editing (eti)){
            ecell_view = eti->cell_views [eti->editing_col];
            return_val = eti_e_cell_event (eti, ecell_view, e, e->key.time,
                               view_to_model_col(eti, eti->editing_col),
                               eti->editing_col, eti->editing_row, E_CELL_EDITING);
        }
        break;
    }

    case GDK_LEAVE_NOTIFY:
    case GDK_ENTER_NOTIFY:
        if (eti->tooltip->timer > 0)
            gtk_timeout_remove (eti->tooltip->timer);
        eti->tooltip->timer = 0;
        break;

    case GDK_FOCUS_CHANGE:
        if (! e->focus_change.in) {
            if (eti_editing (eti))
                e_table_item_leave_edit (eti);
        }

    default:
        return_val = FALSE;
    }
    return return_val;
}

static void
eti_class_init (GtkObjectClass *object_class)
{
    GnomeCanvasItemClass *item_class = (GnomeCanvasItemClass *) object_class;
    ETableItemClass *eti_class  = (ETableItemClass *) object_class;
    
    eti_parent_class            = gtk_type_class (PARENT_OBJECT_TYPE);
    
    object_class->destroy       = eti_destroy;
    object_class->set_arg       = eti_set_arg;
    object_class->get_arg       = eti_get_arg;

    item_class->update          = eti_update;
    item_class->realize         = eti_realize;
    item_class->unrealize       = eti_unrealize;
    item_class->draw            = eti_draw;
    item_class->point           = eti_point;
    item_class->event           = eti_event;
    
    eti_class->cursor_change    = NULL;
    eti_class->cursor_activated = NULL;
    eti_class->double_click     = NULL;
    eti_class->right_click      = NULL;
    eti_class->click            = NULL;
    eti_class->key_press        = NULL;

    gtk_object_add_arg_type ("ETableItem::ETableHeader", E_TABLE_HEADER_TYPE,
                 GTK_ARG_WRITABLE, ARG_TABLE_HEADER);
    gtk_object_add_arg_type ("ETableItem::ETableModel", E_TABLE_MODEL_TYPE,
                 GTK_ARG_WRITABLE, ARG_TABLE_MODEL);
    gtk_object_add_arg_type ("ETableItem::table_selection_model", E_TABLE_SELECTION_MODEL_TYPE,
                 GTK_ARG_WRITABLE, ARG_TABLE_SELECTION_MODEL);
    gtk_object_add_arg_type ("ETableItem::drawgrid", GTK_TYPE_BOOL,
                 GTK_ARG_WRITABLE, ARG_TABLE_DRAW_GRID);
    gtk_object_add_arg_type ("ETableItem::drawfocus", GTK_TYPE_BOOL,
                 GTK_ARG_WRITABLE, ARG_TABLE_DRAW_FOCUS);
    gtk_object_add_arg_type ("ETableItem::cursor_mode", GTK_TYPE_INT,
                 GTK_ARG_WRITABLE, ARG_CURSOR_MODE);
    gtk_object_add_arg_type ("ETableItem::length_threshold", GTK_TYPE_INT,
                 GTK_ARG_WRITABLE, ARG_LENGTH_THRESHOLD);

    gtk_object_add_arg_type ("ETableItem::minimum_width", GTK_TYPE_DOUBLE, 
                 GTK_ARG_READWRITE, ARG_MINIMUM_WIDTH); 
    gtk_object_add_arg_type ("ETableItem::width", GTK_TYPE_DOUBLE, 
                 GTK_ARG_READWRITE, ARG_WIDTH); 
    gtk_object_add_arg_type ("ETableItem::height", GTK_TYPE_DOUBLE, 
                 GTK_ARG_READABLE, ARG_HEIGHT);
    gtk_object_add_arg_type ("ETableItem::cursor_row", GTK_TYPE_INT,
                 GTK_ARG_READWRITE, ARG_CURSOR_ROW);

    eti_signals [CURSOR_CHANGE] =
        gtk_signal_new ("cursor_change",
                GTK_RUN_LAST,
                object_class->type,
                GTK_SIGNAL_OFFSET (ETableItemClass, cursor_change),
                gtk_marshal_NONE__INT,
                GTK_TYPE_NONE, 1, GTK_TYPE_INT);

    eti_signals [CURSOR_ACTIVATED] =
        gtk_signal_new ("cursor_activated",
                GTK_RUN_LAST,
                object_class->type,
                GTK_SIGNAL_OFFSET (ETableItemClass, cursor_activated),
                gtk_marshal_NONE__INT,
                GTK_TYPE_NONE, 1, GTK_TYPE_INT);

    eti_signals [DOUBLE_CLICK] =
        gtk_signal_new ("double_click",
                GTK_RUN_LAST,
                object_class->type,
                GTK_SIGNAL_OFFSET (ETableItemClass, double_click),
                gtk_marshal_NONE__INT_INT_POINTER,
                GTK_TYPE_NONE, 3, GTK_TYPE_INT, GTK_TYPE_INT, GTK_TYPE_POINTER);

    eti_signals [RIGHT_CLICK] =
        gtk_signal_new ("right_click",
                GTK_RUN_LAST,
                object_class->type,
                GTK_SIGNAL_OFFSET (ETableItemClass, right_click),
                e_marshal_INT__INT_INT_POINTER,
                GTK_TYPE_INT, 3, GTK_TYPE_INT, GTK_TYPE_INT, GTK_TYPE_POINTER);

    eti_signals [CLICK] =
        gtk_signal_new ("click",
                GTK_RUN_LAST,
                object_class->type,
                GTK_SIGNAL_OFFSET (ETableItemClass, click),
                e_marshal_INT__INT_INT_POINTER,
                GTK_TYPE_INT, 3, GTK_TYPE_INT, GTK_TYPE_INT, GTK_TYPE_POINTER);

    eti_signals [KEY_PRESS] =
        gtk_signal_new ("key_press",
                GTK_RUN_LAST,
                object_class->type,
                GTK_SIGNAL_OFFSET (ETableItemClass, key_press),
                e_marshal_INT__INT_INT_POINTER,
                GTK_TYPE_INT, 3, GTK_TYPE_INT, GTK_TYPE_INT, GTK_TYPE_POINTER);

    gtk_object_class_add_signals (object_class, eti_signals, LAST_SIGNAL);

}

GtkType
e_table_item_get_type (void)
{
    static GtkType type = 0;

    if (!type){
        GtkTypeInfo info = {
            "ETableItem",
            sizeof (ETableItem),
            sizeof (ETableItemClass),
            (GtkClassInitFunc) eti_class_init,
            (GtkObjectInitFunc) eti_init,
            NULL, /* reserved 1 */
            NULL, /* reserved 2 */
            (GtkClassInitFunc) NULL
        };

        type = gtk_type_unique (PARENT_OBJECT_TYPE, &info);
    }

    return type;
}

/** 
 * e_table_item_set_cursor:
 * @eti: %ETableItem which will have the cursor set.
 * @col: Column to select.  -1 means the last column.
 * @row: Row to select.  -1 means the last row.
 *
 * This routine sets the cursor of the %ETableItem canvas item.
 */
void
e_table_item_set_cursor    (ETableItem *eti, int col, int row)
{
    e_table_item_focus(eti, col, view_to_model_row(eti, row), 0);
}

static void
e_table_item_focus (ETableItem *eti, int col, int row, GdkModifierType state)
{
    g_return_if_fail (eti != NULL);
    g_return_if_fail (E_IS_TABLE_ITEM (eti));
    
    if (row == -1) {
        row = view_to_model_row(eti, eti->rows - 1);
    }

    if (col == -1) {
        col = eti->cols - 1;
    }

    if (row != -1) {
        e_table_selection_model_do_something(eti->selection,
                             row, col,
                             state);
    }
}

/** 
 * e_table_item_get_focused_column:
 * @eti: %ETableItem which will have the cursor retrieved.
 *
 * This routine gets the cursor of the %ETableItem canvas item.
 *
 * Returns: The current cursor column.
 */
gint
e_table_item_get_focused_column (ETableItem *eti)
{   
    int cursor_col;

    g_return_val_if_fail (eti != NULL, -1);
    g_return_val_if_fail (E_IS_TABLE_ITEM (eti), -1);
    
    gtk_object_get(GTK_OBJECT(eti->selection),
               "cursor_col", &cursor_col,
               NULL);

    return cursor_col;
}

static void
eti_cursor_change (ETableSelectionModel *selection, int row, int col, ETableItem *eti)
{
    int view_row = model_to_view_row(eti, row);
    int view_col = model_to_view_col(eti, col);

    if (view_row == -1 || view_col == -1) {
        e_table_item_leave_edit (eti);
        return;
    }

    if (!eti->in_key_press) {
        eti_request_region_show (eti, view_col, view_row, view_col, view_row, DOUBLE_CLICK_TIME + 10);
    } else {
        eti_request_region_show (eti, view_col, view_row, view_col, view_row, 0);
    }

    e_canvas_item_grab_focus(GNOME_CANVAS_ITEM(eti));
    if (eti_editing(eti))
        e_table_item_leave_edit (eti);
    gtk_signal_emit (GTK_OBJECT (eti), eti_signals [CURSOR_CHANGE],
             view_row);
    eti->needs_redraw = TRUE;
    gnome_canvas_item_request_update(GNOME_CANVAS_ITEM(eti));
}

static void
eti_cursor_activated (ETableSelectionModel *selection, int row, int col, ETableItem *eti)
{
    int view_row = model_to_view_row(eti, row);
    int view_col = model_to_view_col(eti, col);

    if (view_row == -1 || view_col == -1) {
        e_table_item_leave_edit (eti);
        return;
    }

    if (eti_editing(eti))
        e_table_item_leave_edit (eti);
    gtk_signal_emit (GTK_OBJECT (eti), eti_signals [CURSOR_ACTIVATED],
             view_row);
}

static void
eti_selection_change (ETableSelectionModel *selection, ETableItem *eti)
{
    eti->needs_redraw = TRUE;
    gnome_canvas_item_request_update(GNOME_CANVAS_ITEM(eti));
}


/** 
 * e_table_item_enter_edit
 * @eti: %ETableItem which will start being edited
 * @col: The view col to edit.
 * @row: The view row to edit.
 *
 * This routine starts the given %ETableItem editing at the given view
 * column and row.
 */
void
e_table_item_enter_edit (ETableItem *eti, int col, int row)
{
    g_return_if_fail (eti != NULL);
    g_return_if_fail (E_IS_TABLE_ITEM (eti));
    
    if (eti_editing (eti))
        e_table_item_leave_edit(eti);

    eti->editing_col = col;
    eti->editing_row = row;

    eti->edit_ctx = e_cell_enter_edit (eti->cell_views [col], view_to_model_col(eti, col), col, row);
}

/** 
 * e_table_item_leave_edit
 * @eti: %ETableItem which will stop being edited
 *
 * This routine stops the given %ETableItem from editing.
 */
void
e_table_item_leave_edit (ETableItem *eti)
{
    int col, row;
    void *edit_ctx;
    
    g_return_if_fail (eti != NULL);
    g_return_if_fail (E_IS_TABLE_ITEM (eti));

    if (!eti_editing (eti))
        return;

    col = eti->editing_col;
    row = eti->editing_row;
    edit_ctx = eti->edit_ctx;

    eti->editing_col = -1;
    eti->editing_row = -1;
    eti->edit_ctx = NULL;

    e_cell_leave_edit (eti->cell_views [col],
               view_to_model_col(eti, col),
               col, row, edit_ctx);
}

/** 
 * e_table_item_compute_location
 * @eti: %ETableItem to look in.
 * @x: A pointer to the x location to find in the %ETableItem.
 * @y: A pointer to the y location to find in the %ETableItem.
 * @row: A pointer to the location to store the found row in.
 * @col: A pointer to the location to store the found col in.
 *
 * This routine locates the pixel location (*x, *y) in the
 * %ETableItem.  If that location is in the %ETableItem, *row and *col
 * are set to the view row and column where it was found.  If that
 * location is not in the %ETableItem, the height of the %ETableItem
 * is removed from the value y points to.
 */
void 
e_table_item_compute_location (ETableItem        *eti,
                   int               *x,
                   int               *y,
                   int               *row,
                   int               *col)
{
    /* Save the grabbed row but make sure that we don't get flawed
           results because the cursor is grabbed. */
    int grabbed_row = eti->grabbed_row;
    eti->grabbed_row = -1;

    if (!find_cell (eti, *x, *y, col, row, NULL, NULL)) {
        *y -= eti_get_height(eti);
    }

    eti->grabbed_row = grabbed_row;
}

typedef struct {
    ETableItem *item;
    int rows_printed;
} ETableItemPrintContext;

static gdouble *
e_table_item_calculate_print_widths (ETableHeader *eth, gdouble width)
{
    int i;
    double extra;
    double expansion;
    int last_resizable = -1;
    gdouble scale = 300.0L / 70.0L;
    gdouble *widths = g_new(gdouble, e_table_header_count(eth));
    /* - 1 to account for the last pixel border. */
    extra = width - 1;
    expansion = 0;
    for (i = 0; i < eth->col_count; i++) {
        extra -= eth->columns[i]->min_width * scale;
        if (eth->columns[i]->resizeable && eth->columns[i]->expansion > 0)
            last_resizable = i;
        expansion += eth->columns[i]->resizeable ? eth->columns[i]->expansion : 0;
        widths[i] = eth->columns[i]->min_width * scale;
    }
    for (i = 0; i <= last_resizable; i++) {
        widths[i] += extra * (eth->columns[i]->resizeable ? eth->columns[i]->expansion : 0)/expansion;
    }

    return widths;
}

static gdouble
eti_printed_row_height (ETableItem *eti, gdouble *widths, GnomePrintContext *context, gint row)
{
    int col;
    int cols = eti->cols;
    gdouble height = 0;
    for (col = 0; col < cols; col++) {
        ECellView *ecell_view = eti->cell_views [col];
        gdouble this_height = e_cell_print_height (ecell_view, context, view_to_model_col(eti, col), col, row, 
                               widths[col] - 1);
        if (this_height > height)
            height = this_height;
    }
    return height;
}

#define CHECK(x) if((x) == -1) return -1;

static gint
gp_draw_rect (GnomePrintContext *context, gdouble x, gdouble y, gdouble width, gdouble height)
{
    CHECK(gnome_print_moveto(context, x, y));
    CHECK(gnome_print_lineto(context, x + width, y));
    CHECK(gnome_print_lineto(context, x + width, y - height));
    CHECK(gnome_print_lineto(context, x, y - height));
    CHECK(gnome_print_lineto(context, x, y));
    return gnome_print_fill(context);
}

static void
e_table_item_print_page  (EPrintable *ep,
              GnomePrintContext *context,
              gdouble width,
              gdouble height,
              gboolean quantize,
              ETableItemPrintContext *itemcontext)
{
    ETableItem *eti = itemcontext->item;
    const int rows = eti->rows;
    const int cols = eti->cols;
    int rows_printed = itemcontext->rows_printed;
    gdouble *widths;
    int row, col;
    gdouble yd = height;
    
    widths = e_table_item_calculate_print_widths (itemcontext->item->header, width);

    /*
     * Draw cells
     */
    if (eti->draw_grid){
        gp_draw_rect(context, 0, yd, width, 1);
    }
    yd--;
    
    for (row = rows_printed; row < rows; row++){
        gdouble xd = 1, row_height;
        
        row_height = eti_printed_row_height(eti, widths, context, row);
        if (quantize) {
            if (yd - row_height - 1 < 0 && row != rows_printed) {
                break;
            }
        } else {
            if (yd < 0) {
                break;
            }
        }

        for (col = 0; col < cols; col++){
            ECellView *ecell_view = eti->cell_views [col];

            if (gnome_print_gsave(context) == -1)
                /* FIXME */;
            if (gnome_print_translate(context, xd, yd - row_height) == -1)
                /* FIXME */;

            if (gnome_print_moveto(context, 0, 0) == -1)
                /* FIXME */;
            if (gnome_print_lineto(context, widths[col] - 1, 0) == -1)
                /* FIXME */;
            if (gnome_print_lineto(context, widths[col] - 1, row_height) == -1)
                /* FIXME */;
            if (gnome_print_lineto(context, 0, row_height) == -1)
                /* FIXME */;
            if (gnome_print_lineto(context, 0, 0) == -1)
                /* FIXME */;
            if (gnome_print_clip(context) == -1)
                /* FIXME */;

            e_cell_print (ecell_view, context, view_to_model_col(eti, col), col, row, 
                      widths[col] - 1, row_height);

            if (gnome_print_grestore(context) == -1)
                /* FIXME */;
            
            xd += widths[col];
        }
        yd -= row_height;

        if (eti->draw_grid){
            gp_draw_rect(context, 0, yd, width, 1);
        }
        yd--;
    }

    itemcontext->rows_printed = row;

    if (eti->draw_grid){
        gdouble xd = 0;
        
        for (col = 0; col < cols; col++){
            gp_draw_rect(context, xd, height, 1, height - yd);
            
            xd += widths[col];
        }
        gp_draw_rect(context, xd, height, 1, height - yd);
    }

    g_free (widths);
}

static gboolean
e_table_item_data_left   (EPrintable *ep,
              ETableItemPrintContext *itemcontext)
{
    ETableItem *item = itemcontext->item;
    int rows_printed = itemcontext->rows_printed;

    gtk_signal_emit_stop_by_name(GTK_OBJECT(ep), "data_left");
    return rows_printed < item->rows;
}

static void
e_table_item_reset       (EPrintable *ep,
              ETableItemPrintContext *itemcontext)
{
    itemcontext->rows_printed = 0;
}

static gdouble
e_table_item_height      (EPrintable *ep,
              GnomePrintContext *context,
              gdouble width,
              gdouble max_height,
              gboolean quantize,
              ETableItemPrintContext *itemcontext)
{
    ETableItem *item = itemcontext->item;
    const int rows = item->rows;
    int rows_printed = itemcontext->rows_printed;
    gdouble *widths;
    int row;
    gdouble yd = 0;
    
    widths = e_table_item_calculate_print_widths (itemcontext->item->header, width);

    /*
     * Draw cells
     */
    yd++;
    
    for (row = rows_printed; row < rows; row++){
        gdouble row_height;
        
        row_height = eti_printed_row_height(item, widths, context, row);
        if (quantize) {
            if (max_height != -1 && yd + row_height + 1 > max_height && row != rows_printed) {
                break;
            }
        } else {
            if (max_height != -1 && yd > max_height) {
                break;
            }
        }

        yd += row_height;

        yd++;
    }

    g_free (widths);
    
    if (max_height != -1 && (!quantize) && yd > max_height)
        yd = max_height;

    gtk_signal_emit_stop_by_name(GTK_OBJECT(ep), "height");
    return yd;
}

static gboolean
e_table_item_will_fit     (EPrintable *ep,
               GnomePrintContext *context,
               gdouble width,
               gdouble max_height,
               gboolean quantize,
               ETableItemPrintContext *itemcontext)
{
    ETableItem *item = itemcontext->item;
    const int rows = item->rows;
    int rows_printed = itemcontext->rows_printed;
    gdouble *widths;
    int row;
    gdouble yd = 0;
    gboolean ret_val = TRUE;
    
    widths = e_table_item_calculate_print_widths (itemcontext->item->header, width);

    /*
     * Draw cells
     */
    yd++;
    
    for (row = rows_printed; row < rows; row++){
        gdouble row_height;
        
        row_height = eti_printed_row_height(item, widths, context, row);
        if (quantize) {
            if (max_height != -1 && yd + row_height + 1 > max_height && row != rows_printed) {
                ret_val = FALSE;
                break;
            }
        } else {
            if (max_height != -1 && yd > max_height) {
                ret_val = FALSE;
                break;
            }
        }

        yd += row_height;

        yd++;
    }

    g_free (widths);

    gtk_signal_emit_stop_by_name(GTK_OBJECT(ep), "will_fit");
    return ret_val;
}

static void
e_table_item_printable_destroy (GtkObject *object,
                ETableItemPrintContext *itemcontext)
{
    gtk_object_unref(GTK_OBJECT(itemcontext->item));
    g_free(itemcontext);
}

/** 
 * e_table_item_get_printable
 * @eti: %ETableItem which will be printed
 *
 * This routine creates and returns an %EPrintable that can be used to
 * print the given %ETableItem.
 *
 * Returns: The %EPrintable. 
 */
EPrintable *
e_table_item_get_printable (ETableItem *item)
{
    EPrintable *printable = e_printable_new();
    ETableItemPrintContext *itemcontext;

    itemcontext = g_new(ETableItemPrintContext, 1);
    itemcontext->item = item;
    gtk_object_ref(GTK_OBJECT(item));
    itemcontext->rows_printed = 0;

    gtk_signal_connect (GTK_OBJECT(printable),
                "print_page",
                GTK_SIGNAL_FUNC(e_table_item_print_page),
                itemcontext);
    gtk_signal_connect (GTK_OBJECT(printable),
                "data_left",
                GTK_SIGNAL_FUNC(e_table_item_data_left),
                itemcontext);
    gtk_signal_connect (GTK_OBJECT(printable),
                "reset",
                GTK_SIGNAL_FUNC(e_table_item_reset),
                itemcontext);
    gtk_signal_connect (GTK_OBJECT(printable),
                "height",
                GTK_SIGNAL_FUNC(e_table_item_height),
                itemcontext);
    gtk_signal_connect (GTK_OBJECT(printable),
                "will_fit",
                GTK_SIGNAL_FUNC(e_table_item_will_fit),
                itemcontext);
    gtk_signal_connect (GTK_OBJECT(printable),
                "destroy",
                GTK_SIGNAL_FUNC(e_table_item_printable_destroy),
                itemcontext);

    return printable;
}