All pastes #370679 Raw Edit

Mine

public text v1 · immutable
#370679 ·published 2007-02-24 20:04 UTC
rendered paste body
/** @file etk_tree.c */
#include "etk_tree.h"
#include <stdlib.h>
#include <string.h>
#include <Edje.h>
#include "etk_tree_model.h"
#include "etk_scrolled_view.h"
#include "etk_button.h"
#include "etk_toplevel.h"
#include "etk_event.h"
#include "etk_theme.h"
#include "etk_signal.h"
#include "etk_signal_callback.h"
#include "etk_utils.h"

/**
 * @addtogroup Etk_Tree
 * @{
 */

#define TREE_GET(widget) \
   ETK_TREE(etk_object_data_get(ETK_OBJECT(widget), "_Etk_Tree::Tree"))

#define COL_MIN_WIDTH 24
#define COL_RESIZE_THRESHOLD 3
#define MIN_ROW_HEIGHT 12
#define DEFAULT_ROW_HEIGHT 24
#define CELL_HMARGINS 4
#define CELL_VMARGINS 2
#define MODEL_INTERSPACE 8


typedef struct Etk_Tree_Cell_Objects
{
   Evas_Object *objects[MAX_MODELS_PER_COL][MAX_OBJECTS_PER_MODEL];
} Etk_Tree_Cell_Objects;

typedef struct Etk_Tree_Row_Object
{
   Evas_Object *expander;
   Evas_Object *background;
   Etk_Tree_Cell_Objects *cells;
   Etk_Tree_Row *row;
} Etk_Tree_Row_Object;

enum Etk_Tree_Signal_Id
{
   ETK_TREE_ALL_SELECTED_SIGNAL,
   ETK_TREE_ALL_UNSELECTED_SIGNAL,
   ETK_TREE_ROW_SELECTED_SIGNAL,
   ETK_TREE_ROW_UNSELECTED_SIGNAL,
   ETK_TREE_ROW_CLICKED_SIGNAL,
   ETK_TREE_ROW_ACTIVATED_SIGNAL,
   ETK_TREE_ROW_UNFOLDED_SIGNAL,
   ETK_TREE_ROW_FOLDED_SIGNAL,
   ETK_TREE_ROW_SHOWN_SIGNAL,
   ETK_TREE_ROW_HIDDEN_SIGNAL,
   ETK_TREE_NUM_SIGNALS
};

enum Etk_Tree_Property_Id
{
   ETK_TREE_MODE_PROPERTY,
   ETK_TREE_MULTIPLE_SELECT_PROPERTY,
   ETK_TREE_HEADERS_VISIBLE_PROPERTY,
   ETK_TREE_ROWS_HEIGHT_PROPERTY
};

enum Etk_Tree_Col_Signal_Id
{
   ETK_TREE_COL_CELL_VALUE_CHANGED,
   ETK_TREE_COL_NUM_SIGNALS
};

enum Etk_Tree_Col_Property_Id
{
   ETK_TREE_COL_TITLE_PROPERTY,
   ETK_TREE_COL_VISIBLE_PROPERTY,
   ETK_TREE_COL_RESIZABLE_PROPERTY,
   ETK_TREE_COL_POSITION_PROPERTY,
   ETK_TREE_COL_EXPAND_PROPERTY,
   ETK_TREE_COL_WIDTH_PROPERTY,
   ETK_TREE_COL_MIN_WIDTH_PROPERTY,
   ETK_TREE_COL_ALIGN_PROPERTY
};


static void _etk_tree_constructor(Etk_Tree *tree);
static void _etk_tree_destructor(Etk_Tree *tree);
static void _etk_tree_property_set(Etk_Object *object, int property_id, Etk_Property_Value *value);
static void _etk_tree_property_get(Etk_Object *object, int property_id, Etk_Property_Value *value);
static void _etk_tree_size_request(Etk_Widget *widget, Etk_Size *size);
static void _etk_tree_size_allocate(Etk_Widget *widget, Etk_Geometry geometry);
static void _etk_tree_realize_cb(Etk_Object *object, void *data);

static void _etk_tree_col_constructor(Etk_Tree_Col *tree_col);
static void _etk_tree_col_destructor(Etk_Tree_Col *tree_col);
static void _etk_tree_col_property_set(Etk_Object *object, int property_id, Etk_Property_Value *value);
static void _etk_tree_col_property_get(Etk_Object *object, int property_id, Etk_Property_Value *value);

static void _etk_tree_headers_size_allocate(Etk_Tree *tree, Etk_Geometry geometry);
static void _etk_tree_scroll_content_size_allocate(Etk_Widget *widget, Etk_Geometry geometry);
static void _etk_tree_scroll_content_scroll(Etk_Widget *widget, int x, int y);
static void _etk_tree_scroll_content_scroll_size_get(Etk_Widget *widget, Etk_Size scrollview_size, Etk_Size scrollbar_size, Etk_Size *scroll_size);
static void _etk_tree_grid_size_allocate(Etk_Widget *widget, Etk_Geometry geometry);

static void _etk_tree_realize_cb(Etk_Object *object, void *data);
static void _etk_tree_focus_cb(Etk_Object *object, void *event, void *data);
static void _etk_tree_unfocus_cb(Etk_Object *object, void *event, void *data);
static void _etk_tree_key_down_cb(Etk_Object *object, Etk_Event_Key_Down *event, void *data);
static void _etk_tree_scroll_content_realize_cb(Etk_Object *object, void *data);
static void _etk_tree_grid_realize_cb(Etk_Object *object, void *data);
static void _etk_tree_grid_unrealize_cb(Etk_Object *object, void *data);

static void _etk_tree_headers_mouse_down_cb(void *data, Evas *e, Evas_Object *obj, void *event_info);
static void _etk_tree_headers_mouse_up_cb(void *data, Evas *e, Evas_Object *obj, void *event_info);
static void _etk_tree_headers_mouse_move_cb(void *data, Evas *e, Evas_Object *obj, void *event_info);
static void _etk_tree_headers_mouse_in_cb(void *data, Evas *e, Evas_Object *obj, void *event_info);
static void _etk_tree_headers_mouse_out_cb(void *data, Evas *e, Evas_Object *obj, void *event_info);

static void _etk_tree_row_mouse_down_cb(void *data, Evas *e, Evas_Object *obj, void *event_info);
static void _etk_tree_row_mouse_up_cb(void *data, Evas *e, Evas_Object *obj, void *event_info);
static void _etk_tree_row_expander_mouse_up_cb(void *data, Evas *e, Evas_Object *obj, void *event_info);

static void _etk_tree_purge_job(void *data);
static void _etk_tree_purge(Etk_Tree *tree);
static void _etk_tree_row_move_to_purge_pool(Etk_Tree_Row *row);

static void _etk_tree_col_realize(Etk_Tree *tree, int col_nth);
static Etk_Tree_Col *etk_tree_col_to_resize_get(Etk_Tree *tree, int x);
static void _etk_tree_headers_rect_create(Etk_Tree *tree, Etk_Widget *parent);

static Etk_Tree_Row *_etk_tree_row_next_to_render_get(Etk_Tree_Row *row, int *depth);
static Etk_Tree_Row_Object *_etk_tree_row_object_create(Etk_Tree *tree);
static void _etk_tree_row_object_destroy(Etk_Tree *tree, Etk_Tree_Row_Object *row_object);
static void _etk_tree_row_signal_emit(Etk_Tree_Row *row, Etk_Tree_Row_Object *row_object, const char *signal);
static void _etk_tree_expanders_clip(Etk_Tree *tree);

static void _etk_tree_row_select(Etk_Tree *tree, Etk_Tree_Row *row, Etk_Modifiers modifiers);


static Etk_Signal *_etk_tree_signals[ETK_TREE_NUM_SIGNALS];
static Etk_Signal *_etk_tree_col_signals[ETK_TREE_COL_NUM_SIGNALS];


/**************************
 *
 * Implementation
 *
 **************************/

/**
 * @internal
 * @brief Gets the type of an Etk_Tree
 * @return Returns the type of an Etk_Tree
 */
Etk_Type *etk_tree_type_get()
{
   static Etk_Type *tree_type = NULL;

   if (!tree_type)
   {
      tree_type = etk_type_new("Etk_Tree", ETK_WIDGET_TYPE, sizeof(Etk_Tree),
         ETK_CONSTRUCTOR(_etk_tree_constructor), ETK_DESTRUCTOR(_etk_tree_destructor));
      
      _etk_tree_signals[ETK_TREE_ALL_SELECTED_SIGNAL] = etk_signal_new("all_selected",
         tree_type, -1, etk_marshaller_VOID__VOID, NULL, NULL);
      _etk_tree_signals[ETK_TREE_ALL_UNSELECTED_SIGNAL] = etk_signal_new("all_unselected",
         tree_type, -1, etk_marshaller_VOID__VOID, NULL, NULL);
      _etk_tree_signals[ETK_TREE_ROW_SELECTED_SIGNAL] = etk_signal_new("row_selected",
         tree_type, -1, etk_marshaller_VOID__POINTER, NULL, NULL);
      _etk_tree_signals[ETK_TREE_ROW_UNSELECTED_SIGNAL] = etk_signal_new("row_unselected",
         tree_type, -1, etk_marshaller_VOID__POINTER, NULL, NULL);
      _etk_tree_signals[ETK_TREE_ROW_CLICKED_SIGNAL] = etk_signal_new("row_clicked",
         tree_type, -1, etk_marshaller_VOID__POINTER_POINTER, NULL, NULL);
      _etk_tree_signals[ETK_TREE_ROW_ACTIVATED_SIGNAL] = etk_signal_new("row_activated",
         tree_type, -1, etk_marshaller_VOID__POINTER, NULL, NULL);
      _etk_tree_signals[ETK_TREE_ROW_UNFOLDED_SIGNAL] = etk_signal_new("row_unfolded",
         tree_type, -1, etk_marshaller_VOID__POINTER, NULL, NULL);
      _etk_tree_signals[ETK_TREE_ROW_FOLDED_SIGNAL] = etk_signal_new("row_folded",
         tree_type, -1, etk_marshaller_VOID__POINTER, NULL, NULL);
      _etk_tree_signals[ETK_TREE_ROW_SHOWN_SIGNAL] = etk_signal_new("row_shown",
         tree_type, -1, etk_marshaller_VOID__POINTER, NULL, NULL);
      _etk_tree_signals[ETK_TREE_ROW_HIDDEN_SIGNAL] = etk_signal_new("row_hidden",
         tree_type, -1, etk_marshaller_VOID__POINTER, NULL, NULL);

      etk_type_property_add(tree_type, "mode", ETK_TREE_MODE_PROPERTY,
         ETK_PROPERTY_INT, ETK_PROPERTY_READABLE_WRITABLE, etk_property_value_int(ETK_TREE_MODE_LIST));
      etk_type_property_add(tree_type, "multiple_select", ETK_TREE_MULTIPLE_SELECT_PROPERTY,
         ETK_PROPERTY_BOOL, ETK_PROPERTY_READABLE_WRITABLE, etk_property_value_bool(ETK_TRUE));
      etk_type_property_add(tree_type, "headers_visible", ETK_TREE_HEADERS_VISIBLE_PROPERTY,
         ETK_PROPERTY_BOOL, ETK_PROPERTY_READABLE_WRITABLE, etk_property_value_bool(ETK_TRUE));
      etk_type_property_add(tree_type, "rows_height", ETK_TREE_ROWS_HEIGHT_PROPERTY,
         ETK_PROPERTY_INT, ETK_PROPERTY_READABLE_WRITABLE, etk_property_value_int(DEFAULT_ROW_HEIGHT));
      
      tree_type->property_set = _etk_tree_property_set;
      tree_type->property_get = _etk_tree_property_get;      
   }

   return tree_type;
}

/**
 * @internal
 * @brief Gets the type of an Etk_Tree_Col
 * @return Returns the type of an Etk_Tree_Col
 */
Etk_Type *etk_tree_col_type_get()
{
   static Etk_Type *tree_col_type = NULL;

   if (!tree_col_type)
   {
      tree_col_type = etk_type_new("Etk_Tree_Col", ETK_OBJECT_TYPE, sizeof(Etk_Tree_Col),
         ETK_CONSTRUCTOR(_etk_tree_col_constructor), ETK_DESTRUCTOR(_etk_tree_col_destructor));

      _etk_tree_col_signals[ETK_TREE_COL_CELL_VALUE_CHANGED] = etk_signal_new("cell_value_changed",
         tree_col_type, -1, etk_marshaller_VOID__POINTER, NULL, NULL);
      
      etk_type_property_add(tree_col_type, "title", ETK_TREE_COL_TITLE_PROPERTY,
         ETK_PROPERTY_STRING, ETK_PROPERTY_READABLE_WRITABLE, etk_property_value_string(NULL));
      etk_type_property_add(tree_col_type, "visible", ETK_TREE_COL_VISIBLE_PROPERTY,
         ETK_PROPERTY_BOOL, ETK_PROPERTY_READABLE_WRITABLE, etk_property_value_bool(ETK_TRUE));
      etk_type_property_add(tree_col_type, "resizable", ETK_TREE_COL_RESIZABLE_PROPERTY,
         ETK_PROPERTY_BOOL, ETK_PROPERTY_READABLE_WRITABLE, etk_property_value_bool(ETK_TRUE));
      etk_type_property_add(tree_col_type, "position", ETK_TREE_COL_POSITION_PROPERTY,
         ETK_PROPERTY_INT, ETK_PROPERTY_READABLE_WRITABLE, etk_property_value_int(0));
      etk_type_property_add(tree_col_type, "expand", ETK_TREE_COL_EXPAND_PROPERTY,
         ETK_PROPERTY_BOOL, ETK_PROPERTY_READABLE_WRITABLE, etk_property_value_bool(ETK_FALSE));
      etk_type_property_add(tree_col_type, "width", ETK_TREE_COL_WIDTH_PROPERTY,
         ETK_PROPERTY_INT, ETK_PROPERTY_READABLE_WRITABLE, etk_property_value_int(COL_MIN_WIDTH));
      etk_type_property_add(tree_col_type, "min_width", ETK_TREE_COL_MIN_WIDTH_PROPERTY,
         ETK_PROPERTY_INT, ETK_PROPERTY_READABLE_WRITABLE, etk_property_value_int(COL_MIN_WIDTH));
      etk_type_property_add(tree_col_type, "align", ETK_TREE_COL_ALIGN_PROPERTY,
         ETK_PROPERTY_FLOAT, ETK_PROPERTY_READABLE_WRITABLE, etk_property_value_float(0.0));
      
      tree_col_type->property_set = _etk_tree_col_property_set;
      tree_col_type->property_get = _etk_tree_col_property_get;
   }

   return tree_col_type;
}

/**
 * @brief Creates a new tree
 * @return Returns the new tree
 */
Etk_Widget *etk_tree_new()
{
   return etk_widget_new(ETK_TREE_TYPE, "theme_group", "tree",
      "focusable", ETK_TRUE, "focus_on_click", ETK_TRUE, NULL);
}

/**
 * @brief Sets the mode of the tree. The tree must not be built
 * @param tree a tree
 * @param mode the mode the tree should use (ETK_TREE_MODE_LIST or ETK_TREE_MODE_TREE)
 */
void etk_tree_mode_set(Etk_Tree *tree, Etk_Tree_Mode mode)
{
   if (!tree)
      return;
   if (tree->built)
   {
      ETK_WARNING("Unable to change the mode of the tree because the tree is already built");
      return;
   }
   
   tree->mode = mode;
   etk_object_notify(ETK_OBJECT(tree), "mode");
}

/**
 * @brief Gets the mode of tree
 * @param tree a tree
 * @return Returns the mode of tree (ETK_TREE_MODE_LIST or ETK_TREE_MODE_TREE)
 */
Etk_Tree_Mode etk_tree_mode_get(Etk_Tree *tree)
{
   if (!tree)
      return ETK_TREE_MODE_LIST;
   return tree->mode;
}

/**
 * @brief Sets whether the user can select several rows of the tree
 * @param tree a tree
 * @param multiple_select ETK_TRUE to allow the user to select several rows, ETK_FALSE otherwise
 */
void etk_tree_multiple_select_set(Etk_Tree *tree, Etk_Bool multiple_select)
{
   if (!tree || tree->multiple_select == multiple_select)
      return;

   if (!multiple_select)
      etk_tree_unselect_all(tree);
   tree->multiple_select = multiple_select;
   etk_object_notify(ETK_OBJECT(tree), "multiple_select");
}

/**
 * @brief Gets whether or not several rows can be selected in the tree
 * @param tree a tree
 * @return ETK_TRUE if several rows can be selected, ETK_FALSE otherwise
 */
Etk_Bool etk_tree_multiple_select_get(Etk_Tree *tree)
{
   if (!tree)
      return ETK_FALSE;
   return tree->multiple_select;
}

/**
 * @brief Sets whether the column-headers should be visible or not
 * @param tree a tree
 * @param headers_visible ETK_TRUE to show the column-headers, ETK_FALSE to hide them
 */
void etk_tree_headers_visible_set(Etk_Tree *tree, Etk_Bool headers_visible)
{
   if (!tree || (tree->headers_visible == headers_visible))
      return;

   tree->headers_visible = headers_visible;
   etk_object_notify(ETK_OBJECT(tree), "headers_visible");
   etk_widget_redraw_queue(ETK_WIDGET(tree));
}

/**
 * @brief Gets whether or not the column-headers are visible
 * @param tree a tree
 * @return Returns ETK_TRUE if the column-headers are visible, ETK_FALSE otherwise
 */
Etk_Bool etk_tree_headers_visible_get(Etk_Tree *tree)
{
   if (!tree)
      return ETK_FALSE;
   return tree->headers_visible;
}

/**
 * @brief Sets the height of the rows of the tree
 * @param tree a tree
 * @param rows_height the rows height to set. The minimum value is 12
 */
void etk_tree_rows_height_set(Etk_Tree *tree, int rows_height)
{
   if (!tree)
      return;
   
   rows_height = ETK_MAX(rows_height, MIN_ROW_HEIGHT);
   if (tree->rows_height != rows_height)
   {
      Etk_Range *vscrollbar;
      
      tree->rows_height = rows_height;
      vscrollbar = etk_scrolled_view_vscrollbar_get(ETK_SCROLLED_VIEW(tree->scrolled_view));
      etk_range_increments_set(vscrollbar, rows_height, 5 * rows_height);
      
      etk_object_notify(ETK_OBJECT(tree), "rows_height");
      etk_signal_emit_by_name("scroll_size_changed", ETK_OBJECT(tree->scroll_content), NULL);
      etk_widget_redraw_queue(ETK_WIDGET(tree));
   }
}

/**
 * @brief Gets the height of the rows of the tree
 * @param tree a tree
 * @return Returns the height of the rows of the tree
 */
int etk_tree_rows_height_get(Etk_Tree *tree)
{
   if (!tree)
      return DEFAULT_ROW_HEIGHT;
   return tree->rows_height;
}

/**
 * @brief Builds the tree. You have to call this function after you have added all the columns
 * and before you add the rows to the tree. Once the tree is built, it can not be unbuilt,
 * and you could not add new columns anymore
 * @param tree a tree
 */
void etk_tree_build(Etk_Tree *tree)
{
   if (!tree || tree->built)
      return;
   tree->built = ETK_TRUE;
}

/**
 * @brief Freezes the tree: the won't be updated until it is thawed. It is useful to add
 * a lot (> 1000) of rows efficiently. To add a few rows, freeze/thaw() is not necessary
 * @param tree a tree
 * @see etk_tree_thaw()
 */
void etk_tree_freeze(Etk_Tree *tree)
{
   if (!tree || tree->frozen)
      return;
   tree->frozen = ETK_TRUE;
}

/**
 * @brief Thaws the tree: the tree will be updated the tree if it was frozen
 * @param tree a tree
 * @see etk_tree_freeze()
 */
void etk_tree_thaw(Etk_Tree *tree)
{
   if (!tree || !tree->frozen)
      return;
   
   tree->frozen = ETK_FALSE;
   etk_signal_emit_by_name("scroll_size_changed", ETK_OBJECT(tree->scroll_content), NULL);
   etk_widget_redraw_queue(ETK_WIDGET(tree));
}

/**
 * @brief Inserts a new column into a tree
 * @param tree a tree
 * @param title the tile of the column
 * @param width the requested width of the column. It won't be necessary the visible width
 * of the column since it can be expanded to fit the available space
 * @param alignment the horizontal alignment of the objects inside the column, from 0.0 (left alignment)
 * to 1.0 (right alignment)
 * @return Returns the new column
 */
Etk_Tree_Col *etk_tree_col_new(Etk_Tree *tree, const char *title, int width, float alignment)
{
   Etk_Tree_Col *new_col;
   Etk_Widget *new_header;

   if (!tree)
      return NULL;
   if (tree->built)
   {
      ETK_WARNING("The tree is built, you can not add a new column");
      return NULL;
   }

   tree->columns = realloc(tree->columns, sizeof(Etk_Tree_Col *) * (tree->num_cols + 1));
   new_col = ETK_TREE_COL(etk_object_new(ETK_TREE_COL_TYPE, "title", title,
      "width", width, "visible", ETK_TRUE, "resizable", ETK_TRUE, "align", alignment, NULL));
   tree->columns[tree->num_cols] = new_col;

   new_col->id = tree->num_cols;
   new_col->tree = tree;
   new_col->position = tree->num_cols;

   /* Creates the header widget */
   new_header = etk_widget_new(ETK_BUTTON_TYPE, "theme_group", "header","theme_parent", tree,
      "label", title, "xalign", 0.0, "internal", ETK_TRUE, NULL);
   if (tree->tree_contains_headers)
      etk_widget_parent_set(new_header, ETK_WIDGET(tree));
   else
      etk_widget_parent_set(new_header, tree->scroll_content);
   new_col->header = new_header;

   tree->num_cols++;
   _etk_tree_col_realize(tree, tree->num_cols - 1);

   return new_col;
}

/**
 * @brief Gets the number of columns of the tree
 * @param tree a tree
 * @return Returns the number of columns of the tree
 */
int etk_tree_num_cols_get(Etk_Tree *tree)
{
   if (!tree)
      return 0;
   return tree->num_cols;
}

/**
 * @brief Gets the "nth" column of the tree
 * @param tree a tree
 * @param nth the index of the column to get. Since the columns can be reordered or hidden,
 * @a nth corresponds to the "nth" created column, which is not necessarily the "nth" visible column
 * @return Returns the "nth" column of the tree
 */
Etk_Tree_Col *etk_tree_nth_col_get(Etk_Tree *tree, int nth)
{
   if (!tree || nth < 0 || nth >= tree->num_cols)
      return NULL;
   return tree->columns[nth];
}

/**
 * @brief Gets the tree which contains the given column
 * @param col a column
 * @return Returns the tree which contains the given column, or NULL on failure
 */
Etk_Tree *etk_tree_col_tree_get(Etk_Tree_Col *col)
{
   if (!col)
      return NULL;
   return col->tree;
}

/**
 * @brief Adds a model to a column of the tree. You can add several models to the same column in order to combine them.
 * For example, if you want the column's content to be an icon followed by a text, add the "image" model and then the
 * "text" model
 * @param col a column
 * @param model the model to add to the column @a col
 * @warning the number of models per column is limited to 5
 */
void etk_tree_col_model_add(Etk_Tree_Col *col, Etk_Tree_Model *model)
{
   if (!col || !model || !col->tree)
      return;
   if (col->tree->built)
   {
      ETK_WARNING("You cannot add a model to a column once the tree is built");
      return;
   }
   if (col->num_models >= MAX_MODELS_PER_COL)
   {
      ETK_WARNING("The number of models per column is limited to %d. Unable to add the model", MAX_MODELS_PER_COL);
      return;
   }
   if (model->col)
   {
      ETK_WARNING("The tree-model to add to that column is already used by another column");
      return;
   }
   
   col->models[col->num_models] = model;
   model->tree = col->tree;
   model->col = col;
   model->index = col->num_models;
   col->num_models++;
}

/**
 * @brief Gets the column's header widget. This can be used if you want to detect clicks on it for example.
 * The header widget is an Etk_Button
 * @param col a column
 * @return Returns the column's header widget
 */
Etk_Widget *etk_tree_col_header_get(Etk_Tree_Col *col)
{
   if (!col)
      return NULL;
   return col->header;
}

/**
 * @brief Sets the title of the column
 * @param col a column of a tree
 * @param title the title to set
 */
void etk_tree_col_title_set(Etk_Tree_Col *col, const char *title)
{
   if (!col || !col->header)
      return;
   
   etk_button_label_set(ETK_BUTTON(col->header), title);
   etk_object_notify(ETK_OBJECT(col), "title");
}

/**
 * @brief Gets the title of the column
 * @param col a column of a tree
 * @return Returns the title of the column
 */
const char *etk_tree_col_title_get(Etk_Tree_Col *col)
{
   if (!col || !col->header)
      return NULL;
   return etk_button_label_get(ETK_BUTTON(col->header));
}

/**
 * @brief Sets the width of the column. It won't be necessarily the visible width of the column since
 * the column can expand
 * @param col a column of a tree
 * @param width the width to set
 */
void etk_tree_col_width_set(Etk_Tree_Col *col, int width)
{
   if (!col || col->width == width)
      return;
   
   col->width = ETK_MAX(width, col->min_width);
   etk_object_notify(ETK_OBJECT(col), "width");
   
   if (col->tree)
   {
      etk_signal_emit_by_name("scroll_size_changed", ETK_OBJECT(col->tree->scroll_content), NULL);
      etk_widget_redraw_queue(ETK_WIDGET(col->tree));
   }
}

/**
 * @brief Gets the width of the column. TODO: (what if the col expands??)
 * @param col a column of a tree
 * @return Returns the width of the column
 */
int etk_tree_col_width_get(Etk_Tree_Col *col)
{
   if (!col)
      return 0;
   return col->width;
}

/**
 * @brief Sets the minimum width of the column. The column can not be smaller than this width
 * @param col a column of a tree
 * @param min_width the minimum width to set. -1 to use the default value
 */
void etk_tree_col_min_width_set(Etk_Tree_Col *col, int min_width)
{
   if (!col || (col->min_width == min_width))
      return;
   
   col->min_width = ETK_MAX(min_width, COL_MIN_WIDTH);
   if (col->width < col->min_width)
      etk_tree_col_width_set(col, col->min_width);
   
   etk_object_notify(ETK_OBJECT(col), "min_width");
}

/**
 * @brief Gets the minimum width of the column
 * @param col a column of a tree
 * @return Returns the minimum width of the column
 */
int etk_tree_col_min_width_get(Etk_Tree_Col *col)
{
   if (!col)
      return 0;
   return col->min_width;
}

/**
 * @brief Sets whether the column can be resized by the user
 * @param col a column of a tree
 * @param resizable ETK_TRUE to make the column resizable, ETK_FALSE otherwise
 */
void etk_tree_col_resizable_set(Etk_Tree_Col *col, Etk_Bool resizable)
{
   if (!col || (col->resizable == resizable))
      return;
   
   col->resizable = resizable;
   etk_object_notify(ETK_OBJECT(col), "resizable");
}

/**
 * @brief Gets whether the column can be resized by the user
 * @param col a column of a tree
 * @return Returns ETK_TRUE if the column is resizable
 */
Etk_Bool etk_tree_col_resizable_get(Etk_Tree_Col *col)
{
   if (!col)
      return ETK_FALSE;
   return col->resizable;
}

/**
 * @brief Sets whether the column should expand if there is some free space in the tree
 * @param col a column of a tree
 * @param expand ETK_TRUE to make the column expand, ETK_FALSE otherwise
 */
void etk_tree_col_expand_set(Etk_Tree_Col *col, Etk_Bool expand)
{
   if (!col || (col->expand == expand))
      return;
   
   col->expand = expand;
   etk_object_notify(ETK_OBJECT(col), "expand");
   etk_widget_redraw_queue(ETK_WIDGET(col->tree));
}

/**
 * @brief Gets whether the column expands
 * @param col a column of a tree
 * @return Returns ETK_TRUE if the column expands, ETK_FALSE otherwise
 */
Etk_Bool etk_tree_col_expand_get(Etk_Tree_Col *col)
{
   if (!col)
      return ETK_FALSE;
   return col->expand;
}

/**
 * @brief Sets the horizontal alignment of the objects inside the column
 * @param col a column of a tree
 * @param alignment the horizontal alignment to use, from 0.0 (left alignment) to 1.0 (right alignment)
 */
void etk_tree_col_alignment_set(Etk_Tree_Col *col, float alignment)
{
   if (!col || col->align == alignment)
      return;
   
   col->align = ETK_CLAMP(alignment, 0.0, 1.0);
   etk_object_notify(ETK_OBJECT(col), "align");
   
   if (col->tree)
      etk_widget_redraw_queue(ETK_WIDGET(col->tree));
}

/**
 * @brief Gets the horizontal alignment of the objects inside the column
 * @param col a column of a tree
 * @return Returns the horizontal alignment of the column, from 0.0 (left alignment) to 1.0 (right alignment)
 */
float etk_tree_col_alignment_get(Etk_Tree_Col *col)
{
   if (!col)
      return 0.0;
   return col->align;
}

/**
 * @brief Sets whether or not the column is visible
 * @param col a column of a tree
 * @param visible ETK_TRUE to show the column, ETK_FALSE to hide it
 */
void etk_tree_col_visible_set(Etk_Tree_Col *col, Etk_Bool visible)
{
   if (!col || (col->visible == visible))
      return;
   
   col->visible = visible;
   etk_object_notify(ETK_OBJECT(col), "visible");
   
   if (col->tree)
   {
      _etk_tree_expanders_clip(col->tree);
      etk_signal_emit_by_name("scroll_size_changed", ETK_OBJECT(col->tree->scroll_content), NULL);
      etk_widget_redraw_queue(ETK_WIDGET(col->tree));
   }
}

/**
 * @brief Gets whether or not the column is visible
 * @param col a column of a tree
 * @return Returns ETK_TRUE if the column is visible
 */
Etk_Bool etk_tree_col_visible_get(Etk_Tree_Col *col)
{
   if (!col)
      return ETK_FALSE;
   return col->visible;
}

/**
 * @brief Changes the position of the column
 * @param col a column of a tree
 * @param position the new position the column should take (0 is the first column on the left of the tree,
 * "etk_tree_num_cols_get(tree) - 1" is the last one on the right)
 */
void etk_tree_col_position_set(Etk_Tree_Col *col, int position)
{
   int i;

   if (!col || !col->tree || (col->position == position))
      return;

   position = ETK_CLAMP(position, 0, col->tree->num_cols - 1);
   if (position < col->position)
   {
      for (i = 0; i < col->tree->num_cols; i++)
      {
         if (col->tree->columns[i]->position >= position && col->tree->columns[i]->position < col->position)
         {
            col->tree->columns[i]->position++;
            etk_object_notify(ETK_OBJECT(col->tree->columns[i]), "position");
         }
      }
   }
   else if (position > col->position)
   {
      for (i = 0; i < col->tree->num_cols; i++)
      {
         if (col->tree->columns[i]->position > col->position && col->tree->columns[i]->position <= position)
         {
            col->tree->columns[i]->position--;
            etk_object_notify(ETK_OBJECT(col->tree->columns[i]), "position");
         }
      }
   }
   col->position = position;
   etk_object_notify(ETK_OBJECT(col), "place");
   
   _etk_tree_expanders_clip(col->tree);
   etk_widget_redraw_queue(ETK_WIDGET(col->tree));
}

/**
 * @brief Gets the position of the column (0 is the first column on the left of the tree,
 * "etk_tree_num_cols_get(tree) - 1" is the last one on the right)
 * @param col a column of a tree
 * @return Returns the position of the column
 */
int etk_tree_col_position_get(Etk_Tree_Col *col)
{
   if (!col)
      return 0;
   return col->position;
}

/**
 * @brief Sets the sorting function of a column. This function will be called when
 * the header of the column is clicked or when you call etk_tree_col_sort()
 * @param col a column of a tree
 * @param compare_cb the function to call to compare two rows. It should return a negative
 * value if the cell of "row1" has a lower value than the cell of "row2", 0 if they have the
 * same value, and a positive value if the cell of "row2" has a greater value than the cell of "row1"
 * @param data a pointer that will be passed to @a compare_cb when it is called
 */
void etk_tree_col_sort_set(Etk_Tree_Col *col, int (*compare_cb)(Etk_Tree *tree, Etk_Tree_Row *row1, Etk_Tree_Row *row2, Etk_Tree_Col *col, void *data), void *data)
{  
   if (!col)
      return;
   
   col->sort.compare_cb = compare_cb;
   col->sort.data = data;
}

/**
 * Inserts a new row at the beginning of the tree (if @a parent is NULL) or as the first child of an existing row
 * (if @a parent is not NULL and if the tree is in the "tree" mode)
 * @param tree a tree
 * @param parent the parent row of the row to insert. NULL means the row will be inserted at the tree's root
 * @param ... an "Etk_Tree_Col *" followed by the value of the cell, then any number of "Etk_Tree_Col *"/Value pairs,
 * and terminated by NULL. Note that, according to the model used by the column, a cell value can use several parameters
 * @return Returns the new row, or NULL on failure
 */
Etk_Tree_Row *etk_tree_row_prepend(Etk_Tree *tree, Etk_Tree_Row *parent, ...)
{
   Etk_Tree_Row *row;
   va_list args;
   
   if (!tree)
      return NULL;
   
   va_start(args, parent);
   row = etk_tree_row_insert_valist(tree, parent, NULL, args);
   va_end(args);
   
   return row;
}

/**
 * Inserts a new row at the end of the tree (if @a parent is NULL) or as the last child of an existing row
 * (if @a parent is not NULL and if the tree is in the "tree" mode)
 * @param tree a tree
 * @param parent the parent row of the row to insert. NULL means the row will be inserted at the tree's root
 * @param ... an "Etk_Tree_Col *" followed by the value of the cell, then any number of "Etk_Tree_Col *"/Value pairs,
 * and terminated by NULL. Note that, according to the model used by the column, a cell value can use several parameters
 * @return Returns the new row, or NULL on failure
 */
Etk_Tree_Row *etk_tree_row_append(Etk_Tree *tree, Etk_Tree_Row *parent, ...)
{
   Etk_Tree_Row *row;
   va_list args;
   
   if (!tree)
      return NULL;
   
   va_start(args, parent);
   if (!parent)
      parent = &tree->root;
   row = etk_tree_row_insert_valist(tree, parent, parent->last_child, args);
   va_end(args);
   
   return row;
}

/**
 * @brief Inserts a new row after an existing row
 * @param tree a tree
 * @param parent the parent row of the row to insert. If @a after is not NULL, @a parent is optionnal (the parent of the
 * new row will be the parent of @a after)
 * @param after the row after which the new row will be inserted. NULL means the new row will be inserted at the
 * beginning of the tree (if @a parent is NULL) or as the first child of @a parent (if @a parent is not NULL and if the
 * tree is in the "tree" mode)
 * @param ... an "Etk_Tree_Col *" followed by the value of the cell, then any number of "Etk_Tree_Col *"/Value pairs,
 * and terminated by NULL. Note that, according to the model used by the column, a cell value can use several parameters
 * @return Returns the new row, or NULL on failure
 */
Etk_Tree_Row *etk_tree_row_insert(Etk_Tree *tree, Etk_Tree_Row *parent, Etk_Tree_Row *after, ...)
{
   Etk_Tree_Row *row;
   va_list args;
   
   if (!tree)
      return NULL;
   
   va_start(args, after);
   row = etk_tree_row_insert_valist(tree, parent, after, args);
   va_end(args);
   
   return row;
}

/**
 * @brief Inserts a new row after an existing row
 * @param tree a tree
 * @param parent the parent row of the row to insert. If @a after is not NULL, @a parent is optionnal (the parent of the
 * new row will be the parent of @a after)
 * @param after the row after which the new row will be inserted. NULL means the new row will be inserted at the
 * beginning of the tree (if @a parent is NULL) or as the first child of @a parent (if @a parent is not NULL and if the
 * tree is in the "tree" mode)
 * @param args an "Etk_Tree_Col *" followed by the value of the cell, then any number of "Etk_Tree_Col *"/Value pairs,
 * and terminated by NULL. Note that, according to the model used by the column, a cell value can use several parameters
 * @return Returns the new row, or NULL on failure
 */
Etk_Tree_Row *etk_tree_row_insert_valist(Etk_Tree *tree, Etk_Tree_Row *parent, Etk_Tree_Row *after, va_list args)
{
   Etk_Tree_Row *new_row;
   Etk_Tree_Col *col;
   va_list args2;
   int i, j;

   if (!tree)
      return NULL;
   if (!tree->built)
   {
      ETK_WARNING("The tree is not built yet, you can not add a row to the tree");
      return NULL;
   }
   
   if (after)
      parent = after->parent;
   if (!parent)
      parent = &tree->root;
   
   new_row = malloc(sizeof(Etk_Tree_Row));
   new_row->data = NULL;
   new_row->data_free_cb = NULL;
   new_row->delete_me = ETK_FALSE;
   new_row->selected = ETK_FALSE;
   new_row->unfolded = ETK_FALSE;
   
   /* Insert the row in the tree hierarchy */
   new_row->tree = tree;
   new_row->parent = parent;
   new_row->first_child = NULL;
   new_row->last_child = NULL;
   new_row->num_children = 0;
   new_row->num_visible_children = 0;
   if (after)
   {
      new_row->prev = after;
      new_row->next = after->next;
      if (after->next)
         after->next->prev = new_row;
      after->next = new_row;
      if (after == parent->last_child)
         parent->last_child = new_row;
   }
   else
   {
      new_row->prev = NULL;
      new_row->next = parent->first_child;
      if (parent->first_child)
         parent->first_child->prev = new_row;
      parent->first_child = new_row;
      if (!parent->last_child)
         parent->last_child = new_row;
   }
   parent->num_children++;
   for ( ; parent && parent->unfolded; parent = parent->parent)
      parent->num_visible_children++;
   tree->total_rows++;

   /* Initializes the data of the row's cells */
   new_row->cells_data = malloc(sizeof(void **) * tree->num_cols);
   for (i = 0; i < tree->num_cols; i++)
   {
      col = tree->columns[i];
      
      new_row->cells_data[i] = malloc(sizeof(void *) * col->num_models);
      for (j = 0; j < col->num_models; j++)
      {
         new_row->cells_data[i][j] = calloc(1, col->models[j]->cell_data_size);
         if (col->models[j]->cell_data_init)
            col->models[j]->cell_data_init(col->models[j], new_row->cells_data[i][j]);
      }
   }
   va_copy(args2, args);
   etk_tree_row_fields_set_valist(new_row, ETK_FALSE, args2);
   va_end(args2);
   
   
   if (!tree->frozen)
   {
      etk_signal_emit_by_name("scroll_size_changed", ETK_OBJECT(tree->scroll_content), NULL);
      etk_widget_redraw_queue(ETK_WIDGET(tree));
   }
   return new_row;
}

/**
 * @brief Deletes an existing row and all its children. Note that the row will just be marked as deleted.
 * It will be effectively deleted only during the next iteration of Ecore's main loop. Thus, you can still manipulate
 * safely a row marked as deleted.
 * @param row the row to delete
 */
void etk_tree_row_delete(Etk_Tree_Row *row)
{
   if (!row || row->delete_me)
      return;
   
   _etk_tree_row_move_to_purge_pool(row);
   
   if (!row->tree->frozen)
   {
      etk_signal_emit_by_name("scroll_size_changed", ETK_OBJECT(row->tree->scroll_content), NULL);
      etk_widget_redraw_queue(ETK_WIDGET(row->tree));
   }
}

/**
 * @brief Deletes all the rows of the tree. Note that the rows will just be marked as deleted. They will be effectively
 * deleted during the next iteration of Ecore's main loop. Thus, you can still manipulate safely the rows immediately
 * after a clear.
 */
void etk_tree_clear(Etk_Tree *tree)
{
   if (!tree)
      return;
   
   while (tree->root.first_child)
      _etk_tree_row_move_to_purge_pool(tree->root.first_child);
   
   etk_signal_emit_by_name("scroll_size_changed", ETK_OBJECT(tree->scroll_content), NULL);
   etk_widget_redraw_queue(ETK_WIDGET(tree));
}

/**
 * @brief Sets the values of the cells of the row
 * @param row a row of the tree
 * @param emit_signal whether or not the "cell_value_changed" signal should be emitted on the modified columns.
 * Most of the time, the signal don't need to be emitted (so @a emit_signal should be ETK_FALSE), except if you have a
 * callback connected on this signal
 * @param ... an "Etk_Tree_Col *" followed by the value of the cell,
 * then any number of "Etk_Tree_Col *"/Value pairs, and terminated by NULL.
 * Note that, according to the models used by the column, a cell value can use several parameters
 */
void etk_tree_row_fields_set(Etk_Tree_Row *row, Etk_Bool emit_signal, ...)
{
   va_list args;
   
   if (!row)
      return;
   
   va_start(args, emit_signal);
   etk_tree_row_fields_set_valist(row, emit_signal, args);
   va_end(args);
}

/**
 * @brief Sets the values of the cells of the row
 * @param row a row of the tree
 * @param emit_signal whether or not the "cell_value_changed" signal should be emitted on the modified columns.
 * Most of the time, the signal don't need to be emitted (so @a emit_signal should be ETK_FALSE), except if you have a
 * callback connected on this signal
 * @param args an "Etk_Tree_Col *" followed by the value of the cell,
 * then any number of "Etk_Tree_Col *"/Value pairs, and terminated by NULL.
 * Note that, according to the models used by the column, a cell value can use several parameters
 */
void etk_tree_row_fields_set_valist(Etk_Tree_Row *row, Etk_Bool emit_signal, va_list args)
{
   Etk_Tree_Col *col;
   va_list args2;
   int i;
   
   if (!row)
      return;
   
   va_copy(args2, args);
   while ((col = va_arg(args2, Etk_Tree_Col *)))
   {
      for (i = 0; i < col->num_models; i++)
      {
         if (col->models[i]->cell_data_set)
            col->models[i]->cell_data_set(col->models[i], row->cells_data[col->id][i], &args2);
      }
      if (emit_signal)
         etk_signal_emit(_etk_tree_col_signals[ETK_TREE_COL_CELL_VALUE_CHANGED], ETK_OBJECT(col), NULL, row);
   }
   va_end(args2);
   
   if (!row->tree->frozen)
      etk_widget_redraw_queue(ETK_WIDGET(row->tree));
}

/**
 * @brief Gets the values of the cells of the row
 * @param row a row of the tree
 * @param ... an "Etk_Tree_Col *" followed by the location where to store the value of the cell,
 * then any number of "Etk_Tree_Col *"/Location pairs, and terminated by NULL.
 * Note that some models may require several locations to store the cell value
 */
void etk_tree_row_fields_get(Etk_Tree_Row *row, ...)
{
   va_list args;
   
   if (!row)
      return;
   
   va_start(args, row);
   etk_tree_row_fields_get_valist(row, args);
   va_end(args);
}

/**
 * @brief Gets the values of the cells of the row
 * @param row a row of the tree
 * @param args an "Etk_Tree_Col *" followed by the location where to store the value of the cell,
 * then any number of "Etk_Tree_Col *"/Location pairs, and terminated by NULL.
 * Note that some models may require several locations to store the cell value
 */
void etk_tree_row_fields_get_valist(Etk_Tree_Row *row, va_list args)
{
   Etk_Tree_Col *col;
   va_list args2;
   int i;
   
   if (!row)
      return;
   
   va_copy(args2, args);
   while ((col = va_arg(args, Etk_Tree_Col *)))
   {
      for (i = 0; i < col->num_models; i++)
      {
         if (col->models[i]->cell_data_get)
            col->models[i]->cell_data_get(col->models[i], row->cells_data[col->id][i], &args);
      }
   }
   va_end(args2);
}

/**
 * @brief Sets the values of the models of the row
 * @param row a row of the tree
 * @param emit_signal whether or not the "cell_value_changed" signal should be emitted on the modified columns.
 * Most of the time, the signal don't need to be emitted (so @a emit_signal should be ETK_FALSE), except if you have a
 * callback connected on this signal
 * @param args an "Etk_Tree_Model *" followed by the value of the model, then any number of
 * "Etk_Tree_Model *"/Value pairs, and terminated by NULL.
 * Note that, according to the models, a cell value can use several parameters
 */
void etk_tree_row_model_fields_set(Etk_Tree_Row *row, Etk_Bool emit_signal, ...)
{
   va_list args;
   
   if (!row)
      return;
   
   va_start(args, emit_signal);
   etk_tree_row_model_fields_set_valist(row, emit_signal, args);
   va_end(args);
}

/**
 * @brief Sets the values of the models of the row
 * @param row a row of the tree
 * @param emit_signal whether or not the "cell_value_changed" signal should be emitted on the modified columns.
 * Most of the time, the signal don't need to be emitted (so @a emit_signal should be ETK_FALSE), except if you have a
 * callback connected on this signal
 * @param args an "Etk_Tree_Model *" followed by the value of the model, then any number of
 * "Etk_Tree_Model *"/Value pairs, and terminated by NULL.
 * Note that, according to the models, a cell value can use several parameters
 */
void etk_tree_row_model_fields_set_valist(Etk_Tree_Row *row, Etk_Bool emit_signal, va_list args)
{
   Etk_Tree_Model *model;
   va_list args2;
   
   if (!row)
      return;
   
   va_copy(args2, args);
   while ((model = va_arg(args2, Etk_Tree_Model *)))
   {
      if (model->cell_data_set)
         model->cell_data_set(model, row->cells_data[model->col->id][model->index], &args2);
      if (emit_signal)
         etk_signal_emit(_etk_tree_col_signals[ETK_TREE_COL_CELL_VALUE_CHANGED], ETK_OBJECT(model->col), NULL, row);
   }
   va_end(args2);
   
   if (!row->tree->frozen)
      etk_widget_redraw_queue(ETK_WIDGET(row->tree));
}

/**
 * @brief Gets the values of the models of the row
 * @param row a row of the tree
 * @param ... an "Etk_Tree_Model *" followed by the location where to store the value of this model,
 * then any number of "Etk_Tree_Model *"/Location pairs, and terminated by NULL.
 * Note that some models may require several locations to store their value
 */
void etk_tree_row_model_fields_get(Etk_Tree_Row *row, ...)
{
   va_list args;
   
   if (!row)
      return;
   
   va_start(args, row);
   etk_tree_row_model_fields_get_valist(row, args);
   va_end(args);
}

/**
 * @brief Gets the values of the models of the row
 * @param row a row of the tree
 * @param args an "Etk_Tree_Model *" followed by the location where to store the value of this model,
 * then any number of "Etk_Tree_Model *"/Location pairs, and terminated by NULL.
 * Note that some models may require several locations to store their value
 */
void etk_tree_row_model_fields_get_valist(Etk_Tree_Row *row, va_list args)
{
   Etk_Tree_Model *model;
   va_list args2;
   
   if (!row)
      return;
   
   va_copy(args2, args);
   while ((model = va_arg(args, Etk_Tree_Model *)))
   {
      if (model->cell_data_get)
         model->cell_data_get(model, row->cells_data[model->col->id][model->index], &args);
   }
   va_end(args2);
}

/**
 * @brief Sets the user data associated to the row. The data could be retrieved later with etk_tree_row_data_get()
 * @param row a row of the tree
 * @param data the data to associate to the row
 * @note This is equivalent to etk_tree_row_data_set_full(row, data, NULL);
 */
void  etk_tree_row_data_set(Etk_Tree_Row *row, void *data)
{
   etk_tree_row_data_set_full(row, data, NULL);
}

/**
 * @brief Sets the user data associated to the row. The data could be retrieved later with etk_tree_row_data_get().
 * You can also set a function to call to free automatically the data when the row is deleted
 * @param row a row of the tree
 * @param data the data to associate to the row
 * @param free_cb a function to call to free the data automatically when the row is deleted
 */
void  etk_tree_row_data_set_full(Etk_Tree_Row *row, void *data, void (*free_cb)(void *data))
{
   if (!row)
      return;
   
   if (row->data && row->data_free_cb)
      row->data_free_cb(row->data);
   
   row->data = data;
   row->data_free_cb = free_cb;
}

/**
 * @brief Gets the user data associated to the row (previously set with etk_tree_row_data_set()).
 * @param row a row of the tree
 * @return Returns the user data associated to the row
 */
void *etk_tree_row_data_get(Etk_Tree_Row *row)
{
   if (!row)
      return NULL;
   return row->data;
}

/**
 * @brief Gets the row that was selected at last
 * @param tree a tree
 * @return Returns the row that was selected at last, or NULL if no row is selected
 * @note If you want to get all the selected rows (if multiple-selection is enabled), you have to walk through
 * all the rows, and see if they are selected with etk_tree_row_is_selected()
 */
Etk_Tree_Row *etk_tree_selected_row_get(Etk_Tree *tree)
{
   if (!tree || !tree->last_selected_row || !tree->last_selected_row->selected)
      return NULL;
   return tree->last_selected_row;
}

/**
 * @brief Selects all the rows of the tree
 * @param tree a tree
 * @note When you call etk_tree_select_all(), for performance reasons, the signal "row_selected" is not emitted for
 * each row of the tree. Only the signal "all_selected" is emitted on the tree.
 */
void etk_tree_select_all(Etk_Tree *tree)
{
   Etk_Tree_Row *row;
   
   if (!tree)
      return;
   
   for (row = tree->root.first_child; row; row = etk_tree_row_walk_next(row, ETK_TRUE))
      row->selected = ETK_TRUE;
   
   etk_signal_emit(_etk_tree_signals[ETK_TREE_ALL_SELECTED_SIGNAL], ETK_OBJECT(tree), NULL);
   etk_widget_redraw_queue(ETK_WIDGET(tree));
}

/**
 * @brief Unselects all the rows of the tree
 * @param tree a tree
 * @note When you call etk_tree_unselect_all(), for performance reasons, the signal "row_unselected" is not emitted for
 * each row of the tree. Only the signal "all_unselected" is emitted on the tree.
 */
void etk_tree_unselect_all(Etk_Tree *tree)
{
   Etk_Tree_Row *row;
   
   if (!tree)
      return;
   
   for (row = tree->root.first_child; row; row = etk_tree_row_walk_next(row, ETK_TRUE))
      row->selected = ETK_FALSE;
   
   etk_signal_emit(_etk_tree_signals[ETK_TREE_ALL_SELECTED_SIGNAL], ETK_OBJECT(tree), NULL);
   etk_widget_redraw_queue(ETK_WIDGET(tree));
}

/**
 * @brief Selects a row of the tree
 * @param row the row to select
 */
void etk_tree_row_select(Etk_Tree_Row *row)
{
   if (!row || row->selected || row->delete_me)
      return;
   _etk_tree_row_select(row->tree, row, ETK_MODIFIER_NONE);
}

/**
 * @brief Unselects a row of the tree
 * @param row the row to unselect
 */
void etk_tree_row_unselect(Etk_Tree_Row *row)
{
   if (!row || !row->selected || row->delete_me)
      return;
   
   row->selected = ETK_FALSE;
   etk_signal_emit(_etk_tree_signals[ETK_TREE_ROW_UNSELECTED_SIGNAL], ETK_OBJECT(row->tree), NULL, row);
   
   if (!row->tree->frozen)
      etk_widget_redraw_queue(ETK_WIDGET(row->tree));
}

/**
 * @brief Gets whether or not the row is selected
 * @param row a row
 * @return Returns ETK_TRUE if the row is selected, ETK_FALSE otherwise
 */
Etk_Bool etk_tree_row_is_selected(Etk_Tree_Row *row)
{
   if (!row)
      return ETK_FALSE;
   return row->selected;
}

/**
 * @brief Folds a row of the tree: if the tree is in the "tree" mode, the children of the row won't be visible anymore
 * @param row the row to fold
 */
void etk_tree_row_fold(Etk_Tree_Row *row)
{
   Etk_Tree_Row *parent;
   
   if (!row || !row->unfolded || row->tree->mode != ETK_TREE_MODE_TREE)
      return;
   
   for (parent = row->parent; parent && parent->unfolded; parent = parent->parent)
      parent->num_visible_children -= row->num_visible_children;
   row->num_visible_children = 0;
   row->unfolded = ETK_FALSE;
   
   etk_signal_emit(_etk_tree_signals[ETK_TREE_ROW_FOLDED_SIGNAL], ETK_OBJECT(row->tree), NULL, row);
   if (!row->tree->frozen)
   {
      etk_signal_emit_by_name("scroll_size_changed", ETK_OBJECT(row->tree->scroll_content), NULL);
      etk_widget_redraw_queue(ETK_WIDGET(row->tree));
   }
}

/**
 * @brief Unfolds a row of the tree: if the tree is in the "tree" mode, the children of the row will be visible
 * @param row the row to unfold
 */
void etk_tree_row_unfold(Etk_Tree_Row *row)
{
   Etk_Tree_Row *parent, *child;
   
   if (!row || row->unfolded || row->tree->mode != ETK_TREE_MODE_TREE)
      return;
   
   for (child = row->first_child; child; child = child->next)
      row->num_visible_children += 1 + child->num_visible_children;
   for (parent = row->parent; parent && parent->unfolded; parent = parent->parent)
      parent->num_visible_children += row->num_visible_children;
   row->unfolded = ETK_TRUE;
   
   etk_signal_emit(_etk_tree_signals[ETK_TREE_ROW_UNFOLDED_SIGNAL], ETK_OBJECT(row->tree), NULL, row);
   if (!row->tree->frozen)
   {
      etk_signal_emit_by_name("scroll_size_changed", ETK_OBJECT(row->tree->scroll_content), NULL);
      etk_widget_redraw_queue(ETK_WIDGET(row->tree));
   }
}

/**
 * @brief Gets whether the row is folded or not
 * @param row a row
 * @return Returns ETK_TRUE if the row is folded, ETK_FALSE otherwise
 */
Etk_Bool etk_tree_row_is_folded(Etk_Tree_Row *row)
{
   if (!row)
      return ETK_TRUE;
   return !row->unfolded;
}

/**
 * @brief Gets the first row of the tree
 * @param tree a tree
 * @return Returns the first row of the tree, or NULL if the tree is empty
 */
Etk_Tree_Row *etk_tree_first_row_get(Etk_Tree *tree)
{
   if (!tree)
      return NULL;
   return tree->root.first_child;
}

/**
 * @brief Gets the last row of the tree
 * @param tree a tree
 * @return Returns the last row of the tree, or NULL if the tree is empty
 */
Etk_Tree_Row *etk_tree_last_row_get(Etk_Tree *tree)
{
   if (!tree)
      return NULL;
   return tree->root.last_child;
}

/**
 * @brief Gets the parent row of the specified row
 * @param row a row
 * @return Returns the parent row of the specified row, or NULL if the row is at the tree's root
 */
Etk_Tree_Row *etk_tree_row_parent_get(Etk_Tree_Row *row)
{
   if (!row)
      return NULL;
   if (!row->parent || row->parent == &row->tree->root)
      return NULL;
   return row->parent;
}

/**
 * @brief Gets the first child of the specified row
 * @param row a row
 * @return Returns the first child of the row
 */
Etk_Tree_Row *etk_tree_row_first_child_get(Etk_Tree_Row *row)
{
   if (!row)
      return NULL;
   return row->first_child;
}

/**
 * @brief Gets the last child of the specified row
 * @param row a row
 * @return Returns the last child of the row
 */
Etk_Tree_Row *etk_tree_row_last_child_get(Etk_Tree_Row *row)
{
   if (!row)
      return NULL;
   return row->last_child;
}

/* TODO: better doc of row_next_get()... (with a note about deleted rows...) */
/**
 * @brief Gets the previous row before the specified row
 * @param row a row
 * @return Returns the previous row before the specified row, or NULL if the row is the first row of its parent
 * @note This function does not return the previous visible row, but only the previous row that is on the same
 * level as the specified row. So if some rows of the tree have children, you'll have to use etk_tree_row_walk_prev()
 * if you want to walk through all the rows of the tree.
 * @see etk_tree_row_walk_prev()
 */
Etk_Tree_Row *etk_tree_row_prev_get(Etk_Tree_Row *row)
{
   if (!row)
      return NULL;
   return row->prev;
}

/**
 * @brief Gets the next row after the specified row
 * @param row a row
 * @return Returns the next row after the specified row, or NULL if the row is the last row of its parent
 * @note This function does not return the next visible row, but only the next row that is on the same
 * level as the specified row. So if some rows of the tree have children, you'll have to use etk_tree_row_walk_next()
 * if you want to walk through all the rows of the tree.
 * @see etk_tree_row_walk_next()
 */
Etk_Tree_Row *etk_tree_row_next_get(Etk_Tree_Row *row)
{
   if (!row)
      return NULL;
   return row->next;
}

/**
 * @brief Walks to the previous "visible" row. Unlike etk_tree_row_prev_get(), etk_tree_row_walk_prev() can return a
 * row that is not on the same level as the specified row. It is useful if you want to walk easily through all the rows
 * of the tree.
 * @param row a row
 * @param include_folded if @a include_folded is ETK_TRUE, the function will return a row, even if this row is a child
 * of a folded row
 * @return Returns the previous "visible" row
 * @note if the tree is in the list mode, this is equivalent to etk_tree_row_prev_get(row)
 */
Etk_Tree_Row *etk_tree_row_walk_prev(Etk_Tree_Row *row, Etk_Bool include_folded)
{
   if (!row)
      return NULL;
   
   if (row->prev)
   {
      row = row->prev;
      while ((row->unfolded || include_folded) && row->last_child)
         row = row->last_child;
      return row;
   }
   else if (row->parent && row->parent != &row->tree->root)
      return row->parent;
   
   return NULL;
}

/**
 * @brief Walks to the next "visible" row. Unlike etk_tree_row_next_get(), etk_tree_row_walk_next() can return a row
 * that is not on the same level as the specified row. It is useful if you want to walk easily through all the rows of
 * the tree.
 * @param row a row
 * @param include_folded if @a include_folded is ETK_TRUE, the function will return a row, even if this row is a child
 * of a folded row
 * @return Returns the next "visible" row
 * @note if the tree is in the list mode, this is equivalent to etk_tree_row_next_get(row)
 */
Etk_Tree_Row *etk_tree_row_walk_next(Etk_Tree_Row *row, Etk_Bool include_folded)
{
   if (!row)
      return NULL;
   
   if ((row->unfolded || include_folded) && row->first_child)
      return row->first_child;
   else
   {
      while (row && !row->next)
         row = row->parent;
      return row ? row->next : NULL;
   }
}

/**
 * @brief Gets the tree which contains the given row
 * @param row a row
 * @return Returns the tree which contains the given row, or NULL on failure
 */
Etk_Tree *etk_tree_row_tree_get(Etk_Tree_Row *row)
{
   if (!row)
      return NULL;
   return row->tree;
}

/**
 * @brief Gets the scrolled view of the tree.
 * It can be used to change the scrollbars' policy, or to get the scroll-value
 * @param tree a tree
 * @return Returns the scrolled view of the tree
 */
Etk_Scrolled_View *etk_tree_scrolled_view_get(Etk_Tree *tree)
{
   if (!tree)
      return NULL;
   return ETK_SCROLLED_VIEW(tree->scrolled_view);
}

/**
 * @brief Scrolls to the given row of the tree, in order to make it visible
 * @param row the row to scroll to
 * @param center whether or not the row should be centered in the view
 */
void etk_tree_row_scroll_to(Etk_Tree_Row *row, Etk_Bool center)
{
   Etk_Tree *tree;
   Etk_Tree_Row *r;
   int i;
   
   if (!row || !(tree = row->tree))
      return;
   
   for (r = etk_tree_first_row_get(tree), i = 0; r; r = etk_tree_row_walk_next(r, ETK_FALSE), i++)
   {
      if (r == row)
      {
         int row_offset, new_offset;
         int tree_height;
         
         row_offset = i * tree->rows_height;
         etk_widget_inner_geometry_get(tree->grid, NULL, NULL, NULL, &tree_height);
         
         /* If the row is already entirely visible and the row should not be centered, we do nothing */
         if (center || (row_offset < tree->scroll_y)
            || ((row_offset + tree->rows_height) > (tree->scroll_y + tree_height)))
         {
            if (center)
               new_offset = row_offset + (tree->rows_height - tree_height) / 2;
            else if (row_offset < tree->scroll_y)
               new_offset = row_offset;
            else
               new_offset = row_offset - tree_height + tree->rows_height;
            
            etk_range_value_set(etk_scrolled_view_vscrollbar_get(ETK_SCROLLED_VIEW(tree->scrolled_view)), new_offset);
         }
         
         return;
      }
   }
}

/**************************
 *
 * Etk specific functions
 *
 **************************/

/**************************
 * Tree
 **************************/

/* Initializes the tree */
static void _etk_tree_constructor(Etk_Tree *tree)
{
   Etk_Range *vscrollbar;
   
   if (!tree)
      return;
   
   tree->scrolled_view = etk_scrolled_view_new();
   vscrollbar = etk_scrolled_view_vscrollbar_get(ETK_SCROLLED_VIEW(tree->scrolled_view));
   etk_range_increments_set(vscrollbar, DEFAULT_ROW_HEIGHT, 5 * DEFAULT_ROW_HEIGHT);
   etk_widget_theme_parent_set(tree->scrolled_view, ETK_WIDGET(tree));
   etk_widget_parent_set(tree->scrolled_view, ETK_WIDGET(tree));
   etk_widget_internal_set(tree->scrolled_view, ETK_TRUE);
   etk_widget_repeat_mouse_events_set(tree->scrolled_view, ETK_TRUE);
   etk_widget_show(tree->scrolled_view);
   
   tree->scroll_content = etk_widget_new(ETK_WIDGET_TYPE, "repeat_mouse_events", ETK_TRUE,
      "internal", ETK_TRUE, "visible", ETK_TRUE, NULL);
   etk_container_add(ETK_CONTAINER(tree->scrolled_view), tree->scroll_content);
   etk_object_data_set(ETK_OBJECT(tree->scroll_content), "_Etk_Tree::Tree", tree);
   tree->scroll_content->size_allocate = _etk_tree_scroll_content_size_allocate;
   tree->scroll_content->scroll = _etk_tree_scroll_content_scroll;
   tree->scroll_content->scroll_size_get = _etk_tree_scroll_content_scroll_size_get;
   etk_signal_connect("realize", ETK_OBJECT(tree->scroll_content),
      ETK_CALLBACK(_etk_tree_scroll_content_realize_cb), NULL);
   
   tree->grid = etk_widget_new(ETK_WIDGET_TYPE, "theme_group", "grid", "theme_parent", tree,
       "repeat_mouse_events", ETK_TRUE, "internal", ETK_TRUE, "visible", ETK_TRUE, NULL);
   etk_widget_parent_set(tree->grid, tree->scroll_content);
   etk_object_data_set(ETK_OBJECT(tree->grid), "_Etk_Tree::Tree", tree);
   tree->grid->size_allocate = _etk_tree_grid_size_allocate;
   etk_signal_connect("realize", ETK_OBJECT(tree->grid),
      ETK_CALLBACK(_etk_tree_grid_realize_cb), NULL);
   etk_signal_connect("unrealize", ETK_OBJECT(tree->grid),
      ETK_CALLBACK(_etk_tree_grid_unrealize_cb), NULL);
   
   tree->num_cols = 0;
   tree->columns = NULL;
   tree->headers_rect = NULL;
   tree->over_col = NULL;
   tree->col_to_resize = NULL;
   tree->headers_visible = ETK_TRUE;
   tree->grid_clip = NULL;
   
   tree->root.tree = tree;
   tree->root.delete_me = ETK_FALSE;
   tree->root.selected = ETK_FALSE;
   tree->root.unfolded = ETK_TRUE;
   tree->root.data = NULL;
   tree->root.data_free_cb = NULL;
   tree->root.cells_data = NULL;
   
   tree->root.prev = NULL;
   tree->root.next = NULL;
   tree->root.parent = NULL;
   tree->root.first_child = NULL;
   tree->root.last_child = NULL;
   tree->root.num_children = 0;
   tree->root.num_visible_children = 0;
   
   tree->total_rows = 0;
   tree->last_selected_row = NULL;
   tree->purge_pool = NULL;
   tree->row_objects = NULL;
   
   tree->rows_height = DEFAULT_ROW_HEIGHT;
   tree->scroll_x = 0;
   tree->scroll_y = 0;
   
   tree->purge_job = NULL;
   tree->mode = ETK_TREE_MODE_LIST;
   tree->multiple_select = ETK_FALSE;
   tree->tree_contains_headers = ETK_TRUE;
   tree->col_resize_pointer_set = ETK_FALSE;
   tree->frozen = ETK_FALSE;
   tree->built = ETK_FALSE;
   
   ETK_WIDGET(tree)->size_request = _etk_tree_size_request;
   ETK_WIDGET(tree)->size_allocate = _etk_tree_size_allocate;
   
   etk_signal_connect("realize", ETK_OBJECT(tree), ETK_CALLBACK(_etk_tree_realize_cb), NULL);
   etk_signal_connect("focus", ETK_OBJECT(tree), ETK_CALLBACK(_etk_tree_focus_cb), NULL);
   etk_signal_connect("unfocus", ETK_OBJECT(tree), ETK_CALLBACK(_etk_tree_unfocus_cb), NULL);
   etk_signal_connect("key_down", ETK_OBJECT(tree), ETK_CALLBACK(_etk_tree_key_down_cb), NULL);
}

/* Destroys the tree */
static void _etk_tree_destructor(Etk_Tree *tree)
{
   if (!tree)
      return;

   /* TODO: more efficient cleaning?? */
   etk_tree_clear(tree);
   _etk_tree_purge(tree);
   
   if (tree->purge_job)
      ecore_job_del(tree->purge_job);

   free(tree->columns);
}

/* Sets the property whose id is "property_id" to the value "value" */
static void _etk_tree_property_set(Etk_Object *object, int property_id, Etk_Property_Value *value)
{
   Etk_Tree *tree;

   if (!(tree = ETK_TREE(object)) || !value)
      return;

   switch (property_id)
   {
      case ETK_TREE_MODE_PROPERTY:
         etk_tree_mode_set(tree, etk_property_value_int_get(value));
         break;
      case ETK_TREE_MULTIPLE_SELECT_PROPERTY:
         etk_tree_multiple_select_set(tree, etk_property_value_bool_get(value));
         break;
      case ETK_TREE_HEADERS_VISIBLE_PROPERTY:
         etk_tree_headers_visible_set(tree, etk_property_value_bool_get(value));
         break;
      default:
         break;
   }
}

/* Gets the value of the property whose id is "property_id" */
static void _etk_tree_property_get(Etk_Object *object, int property_id, Etk_Property_Value *value)
{
   Etk_Tree *tree;

   if (!(tree = ETK_TREE(object)) || !value)
      return;

   switch (property_id)
   {
      case ETK_TREE_MODE_PROPERTY:
         etk_property_value_int_set(value, tree->mode);
         break;
      case ETK_TREE_MULTIPLE_SELECT_PROPERTY:
         etk_property_value_bool_set(value, tree->multiple_select);
         break;
      case ETK_TREE_HEADERS_VISIBLE_PROPERTY:
         etk_property_value_bool_set(value, tree->headers_visible);
         break;
      default:
         break;
   }
}

/* Calculates the ideal size of the tree */
static void _etk_tree_size_request(Etk_Widget *widget, Etk_Size *size)
{
   Etk_Tree *tree;
   Etk_Size header_size;
   int headers_height = 0;
   int i;
   
   if (!(tree = ETK_TREE(widget)) || !size)
      return;
   
   etk_widget_size_request(tree->scrolled_view, size);
   
   for (i = 0; i < tree->num_cols; i++)
   {
      etk_widget_size_request_full(tree->columns[i]->header, &header_size, ETK_FALSE);
      headers_height = ETK_MAX(headers_height, header_size.h);
   }
   size->h += headers_height;
}

/* Resizes the tree to the allocated size */
static void _etk_tree_size_allocate(Etk_Widget *widget, Etk_Geometry geometry)
{
   Etk_Tree *tree;
   Etk_Size header_size;
   Etk_Geometry view_geometry;
   int max_header_height;
   int i;
   
   if (!(tree = ETK_TREE(widget)))
      return;
   
   view_geometry = geometry;
   max_header_height = 0;
   
   /* Allocate size for the scrolled view */
   if (tree->tree_contains_headers)
   {
      if (tree->headers_visible)
      {
         for (i = 0; i < tree->num_cols; i++)
         {
            etk_widget_size_request_full(tree->columns[i]->header, &header_size, ETK_FALSE);
            if (header_size.h > max_header_height)
               max_header_height = header_size.h;
         }
         
         view_geometry.y += max_header_height;
         view_geometry.h -= max_header_height;
      }
   }
   etk_widget_size_allocate(tree->scrolled_view, view_geometry);
   
   /* Allocate size for the headers */
   if (tree->tree_contains_headers)
   {
      geometry.h = max_header_height;
      _etk_tree_headers_size_allocate(tree, geometry);
   }
}

/**************************
 * Tree Col
 **************************/

/* Initializes the tree column */
static void _etk_tree_col_constructor(Etk_Tree_Col *tree_col)
{
   if (!tree_col)
      return;

   tree_col->tree = NULL;
   tree_col->num_models = 0;
   tree_col->id = 0;
   tree_col->position = 0;
   tree_col->xoffset = 0;
   tree_col->min_width = COL_MIN_WIDTH;
   tree_col->width = COL_MIN_WIDTH;
   tree_col->visible_width = COL_MIN_WIDTH;
   tree_col->visible = ETK_TRUE;
   tree_col->resizable = ETK_TRUE;
   tree_col->expand = ETK_FALSE;
   tree_col->align = 0.0;
   tree_col->header = NULL;
   tree_col->clip = NULL;
   tree_col->separator = NULL;
   tree_col->sort.compare_cb = NULL;
   tree_col->sort.data = NULL;      
}

/* Destroys the tree column */
static void _etk_tree_col_destructor(Etk_Tree_Col *tree_col)
{
   int i;
   
   if (!tree_col)
      return;
   
   for (i = 0; i < tree_col->num_models; i++)
      etk_tree_model_free(tree_col->models[i]);
}

/* Sets the property whose id is "property_id" to the value "value" */
static void _etk_tree_col_property_set(Etk_Object *object, int property_id, Etk_Property_Value *value)
{
   Etk_Tree_Col *tree_col;

   if (!(tree_col = ETK_TREE_COL(object)) || !value)
      return;

   switch (property_id)
   {
      case ETK_TREE_COL_TITLE_PROPERTY:
         etk_tree_col_title_set(tree_col, etk_property_value_string_get(value));
         break;
      case ETK_TREE_COL_RESIZABLE_PROPERTY:
         etk_tree_col_resizable_set(tree_col, etk_property_value_bool_get(value));
         break;
      case ETK_TREE_COL_EXPAND_PROPERTY:
         etk_tree_col_expand_set(tree_col, etk_property_value_bool_get(value));
         break;
      case ETK_TREE_COL_VISIBLE_PROPERTY:
         etk_tree_col_visible_set(tree_col, etk_property_value_bool_get(value));
         break;
      case ETK_TREE_COL_POSITION_PROPERTY:
         etk_tree_col_position_set(tree_col, etk_property_value_int_get(value));
         break;
      case ETK_TREE_COL_WIDTH_PROPERTY:
         etk_tree_col_width_set(tree_col, etk_property_value_int_get(value));
         break;
      case ETK_TREE_COL_MIN_WIDTH_PROPERTY:
         etk_tree_col_min_width_set(tree_col, etk_property_value_int_get(value));
         break;
      case ETK_TREE_COL_ALIGN_PROPERTY:
         etk_tree_col_alignment_set(tree_col, etk_property_value_float_get(value));
         break;
      default:
         break;
   }
}

/* Gets the value of the property whose id is "property_id" */
static void _etk_tree_col_property_get(Etk_Object *object, int property_id, Etk_Property_Value *value)
{
   Etk_Tree_Col *tree_col;

   if (!(tree_col = ETK_TREE_COL(object)) || !value)
      return;

   switch (property_id)
   {
      case ETK_TREE_COL_TITLE_PROPERTY:
         etk_property_value_string_set(value, etk_tree_col_title_get(tree_col));
         break;
      case ETK_TREE_COL_RESIZABLE_PROPERTY:
         etk_property_value_bool_set(value, tree_col->resizable);
         break;
      case ETK_TREE_COL_EXPAND_PROPERTY:
         etk_property_value_bool_set(value, tree_col->expand);
         break;
      case ETK_TREE_COL_VISIBLE_PROPERTY:
         etk_property_value_bool_set(value, tree_col->visible);
         break;
      case ETK_TREE_COL_POSITION_PROPERTY:
         etk_property_value_int_set(value, tree_col->position);
         break;
      case ETK_TREE_COL_WIDTH_PROPERTY:
         etk_property_value_int_set(value, tree_col->width);
         break;
      case ETK_TREE_COL_MIN_WIDTH_PROPERTY:
         etk_property_value_int_set(value, tree_col->min_width);
         break;
      case ETK_TREE_COL_ALIGN_PROPERTY:
         etk_property_value_float_set(value, tree_col->align);
         break;
      default:
         break;
   }
}

/**************************
 * Tree Headers
 **************************/

/* Allocates size for the headers of the tree */
static void _etk_tree_headers_size_allocate(Etk_Tree *tree, Etk_Geometry geometry)
{
   int i;
   
   if (!tree)
      return;
   
   if (tree->headers_visible)
   {
      Etk_Tree_Col *col;
      Etk_Tree_Col *first_visible_col;
      Etk_Tree_Col *last_visible_col;
      Etk_Geometry header_geometry;
      Etk_Geometry header_bar_geometry;
      int header_bar_xoffset;
      
      etk_widget_inner_geometry_get(tree->grid, &header_bar_geometry.x, NULL, &header_bar_geometry.w, NULL);
      header_bar_geometry.y = geometry.y;
      header_bar_geometry.h = geometry.h;
      header_bar_xoffset = header_bar_geometry.x - geometry.x;
      
      first_visible_col = NULL;
      last_visible_col = NULL;
      for (i = 0; i < tree->num_cols; i++)
      {
         col = tree->columns[i];
         if (col->visible
            && header_bar_geometry.x + col->xoffset <= geometry.x + geometry.w
            && header_bar_geometry.x + col->xoffset + col->visible_width >= geometry.x)
         {
            if (!first_visible_col || first_visible_col->position > col->position)
               first_visible_col = col;
            if (!last_visible_col || last_visible_col->position < col->position)
               last_visible_col = col;
         }
      }
      
      header_geometry.y = header_bar_geometry.y;
      header_geometry.h = header_bar_geometry.h;
      
      for (i = 0; i < tree->num_cols; i++)
      {
         if (tree->columns[i]->visible
            && first_visible_col->position <= tree->columns[i]->position
            && last_visible_col->position >= tree->columns[i]->position)
         {
            header_geometry.x = header_bar_geometry.x + tree->columns[i]->xoffset;
            header_geometry.w = tree->columns[i]->visible_width;
            if (tree->columns[i] == first_visible_col)
            {
               header_geometry.w += (header_geometry.x - geometry.x);
               header_geometry.x = geometry.x;
            }
            if (tree->columns[i] == last_visible_col)
               header_geometry.w = geometry.x + geometry.w - header_geometry.x;
            
            /* TODO: what if the group doesn't exist... */
            if (tree->columns[i] == first_visible_col && tree->columns[i] == last_visible_col)
               etk_widget_theme_group_set(tree->columns[i]->header, "header_unique");
            else if (tree->columns[i] == first_visible_col)
               etk_widget_theme_group_set(tree->columns[i]->header, "header_first");
            else if (tree->columns[i] == last_visible_col)
               etk_widget_theme_group_set(tree->columns[i]->header, "header_last");
            else
               etk_widget_theme_group_set(tree->columns[i]->header, "header");
            
            etk_widget_show(tree->columns[i]->header);
            etk_widget_raise(tree->columns[i]->header);
            etk_widget_size_allocate(tree->columns[i]->header, header_geometry);
         }
         else
            etk_widget_hide(tree->columns[i]->header);
      }
      
      if (tree->headers_rect)
      {
         evas_object_show(tree->headers_rect);
         evas_object_raise(tree->headers_rect);
         evas_object_move(tree->headers_rect, geometry.x, geometry.y);
         evas_object_resize(tree->headers_rect, geometry.w, geometry.h);
      }
   }
   else
   {
      for (i = 0; i < tree->num_cols; i++)
         etk_widget_hide(tree->columns[i]->header);
      if (tree->headers_rect)
         evas_object_hide(tree->headers_rect);
   }
}

/**************************
 * Tree Scroll Content
 **************************/

/* Allocates size for the tree's grid, and for the headers if they are not contained by the tree widget */
static void _etk_tree_scroll_content_size_allocate(Etk_Widget *widget, Etk_Geometry geometry)
{
   Etk_Tree *tree;
   Etk_Size header_size;
   Etk_Geometry grid_geometry;
   int max_header_height;
   int i;
   
   if (!(tree = TREE_GET(widget)))
      return;
   
   grid_geometry = geometry;
   max_header_height = 0;
   
   /* Allocate size for the grid */
   if (!tree->tree_contains_headers)
   {
      if (tree->headers_visible)
      {
         for (i = 0; i < tree->num_cols; i++)
         {
            etk_widget_size_request(tree->columns[i]->header, &header_size);
            if (header_size.h > max_header_height)
               max_header_height = header_size.h;
         }
         
         grid_geometry.y += max_header_height;
         grid_geometry.h -= max_header_height;
      }
   }
   etk_widget_size_allocate(tree->grid, grid_geometry);
   
   /* Allocate size for the headers */
   if (!tree->tree_contains_headers)
   {
      geometry.h = max_header_height;
      _etk_tree_headers_size_allocate(tree, geometry);
   }
}

/* Sets the scroll offset of the tree scroll-content */
static void _etk_tree_scroll_content_scroll(Etk_Widget *widget, int x, int y)
{
   Etk_Tree *tree;
   
   if (!(tree = TREE_GET(widget)))
      return;
   
   tree->scroll_x = x;
   tree->scroll_y = y;
   etk_widget_redraw_queue(ETK_WIDGET(tree));
}

/* Gets the scrolling size of the tree scroll-content */
static void _etk_tree_scroll_content_scroll_size_get(Etk_Widget *widget, Etk_Size scrollview_size, Etk_Size scrollbar_size, Etk_Size *scroll_size)
{
   Etk_Tree *tree;
   int i;
   
   if (!(tree = TREE_GET(widget)) || !scroll_size)
      return;
   
   scroll_size->w = 0;
   for (i = 0; i < tree->num_cols; i++)
   {
      if (tree->columns[i]->visible)
         scroll_size->w += tree->columns[i]->width;
   }
   scroll_size->h = tree->root.num_visible_children * tree->rows_height;
}

/**************************
 * Tree Grid
 **************************/

/* Allocates size for the grid: it first calculates the width of the visible cols,
 * then creates/destroys the rows objects according to the new height of the tree grid,
 * and finally it updates the content of the rows */
static void _etk_tree_grid_size_allocate(Etk_Widget *widget, Etk_Geometry geometry)
{
   Etk_Tree *tree;
   Etk_Tree_Col *first_visible_col;
   Etk_Tree_Col *last_visible_col;
   Etk_Tree_Col *col;
   Evas_List *prev_visible_rows;
   Evas_List *new_visible_rows;
   Evas_List *l;
   Evas *evas;
   int i, j, k;
   
   if (!(tree = TREE_GET(widget)) || !(evas = etk_widget_toplevel_evas_get(ETK_WIDGET(tree))))
      return;
   
   /* First, we calculate the size of the visible cols */
   {
      int num_visible_cols;
      int num_expand_cols;
      int columns_width;
      int freespace;
      int xoffset;
      
      first_visible_col = NULL;
      last_visible_col = NULL;
      columns_width = 0;
      num_visible_cols = 0;
      num_expand_cols = 0;
      for (i = 0; i < tree->num_cols; i++)
      {
         col = tree->columns[i];
         if (col->visible)
         {
            if (!first_visible_col || first_visible_col->position > col->position)
               first_visible_col = col;
            if (!last_visible_col || last_visible_col->position < col->position)
               last_visible_col = col;
            
            col->visible_width = col->width;
            columns_width += col->width;
            num_visible_cols++;
            if (col->expand)
               num_expand_cols++;
         }
      }
      
      /* At least one column is visible */
      if (first_visible_col)
      {
         /* Calculate the width of the visible columns */
         freespace = geometry.w - columns_width;
         if (freespace > 0)
         {
            if (num_expand_cols > 0)
            {
               for (i = 0; i < tree->num_cols; i++)
               {
                  col = tree->columns[i];
                  if (col->visible && col->expand)
                  {
                     columns_width += freespace / num_expand_cols;
                     col->visible_width += freespace / num_expand_cols;
                  }
               }
            }
            last_visible_col->visible_width += (geometry.w - columns_width);
         }
         
         /* Calculate the horizontal position of the visible columns */
         xoffset = -tree->scroll_x;
         for (i = 0; i < tree->num_cols; i++)
         {
            for (j = 0; j < tree->num_cols; j++)
            {
               col = tree->columns[j];
               if (col->position == i)
               {
                  if (col->visible)
                  {
                     col->xoffset = xoffset;
                     xoffset += col->visible_width;
                  }
                  break;
               }
            }
         }
      }
   }
   
   /* Move, resize, and show/hide the column separators and clips */
   for (i = 0; i < tree->num_cols; i++)
   {
      col = tree->columns[i];
      
      if (!col->visible)
      {
         evas_object_hide(col->clip);
         evas_object_hide(col->separator);
      }
      else
      {
         int col_x1, col_x2;
         
         col_x1 = ETK_MAX(0, col->xoffset);
         col_x2 = ETK_MIN(geometry.w, col->xoffset + col->visible_width);
         
         if (col_x1 <= col_x2)
         {
            evas_object_move(col->clip, geometry.x + col_x1, geometry.y);
            evas_object_resize(col->clip, col_x2 - col_x1 + 1, geometry.h);
            evas_object_show(col->clip);
            
            if (col_x2 >= 0 && col_x2 < geometry.w && col != last_visible_col)
            {
               evas_object_move(col->separator, geometry.x + col_x2, geometry.y);
               evas_object_resize(col->separator, 1, geometry.h);
               evas_object_show(col->separator);
            }
            else
               evas_object_hide(col->separator);
         }
         else
         {
            evas_object_hide(col->clip);
            evas_object_hide(col->separator);
         }
      }
   }
   if (tree->root.num_children > 0)
   {
      evas_object_move(tree->grid_clip, geometry.x, geometry.y);
      evas_object_resize(tree->grid_clip, geometry.w, geometry.h);
      evas_object_show(tree->grid_clip);
   }
   else
      evas_object_hide(tree->grid_clip);
   
   if (!tree->built)
      return;
   
   /* Remove the rows marked as "deleted" */
   if (tree->purge_job)
   {
      _etk_tree_purge(tree);
      tree->purge_job = NULL;
   }
   
   /* Save the rows currently rendered so we could emit the "row_shown/hidden" signals later */
   prev_visible_rows = NULL;
   new_visible_rows = NULL;
   for (l = tree->row_objects; l; l = l->next)
   {
      Etk_Tree_Row_Object *row_object;
      
      row_object = l->data;
      if (row_object->row)
         prev_visible_rows = evas_list_append(prev_visible_rows, row_object->row);
   }
   
   /* Cache the row objects */
   for (i = 0; i < tree->num_cols; i++)
   {
      Etk_Tree_Row_Object *row_object;
      
      col = tree->columns[i];
      for (j = 0; j < col->num_models; j++)
      {
         if (col->models[j]->objects_cache)
         {
            for (l = tree->row_objects; l; l = l->next)
            {
               row_object = l->data;
               if (row_object->row)
               {
                  col->models[j]->objects_cache(col->models[j], row_object->row->cells_data[i][j],
                     row_object->cells[i].objects[j]);
               }
               else
                  col->models[j]->objects_cache(col->models[j], NULL, row_object->cells[i].objects[j]);
            }
         }
      }
   }
   
   /* Create or destroy row objects if the height of the grid has changed */
   {
      int num_visible_rows;
      int current_num_rows;
      
      num_visible_rows = (geometry.h / tree->rows_height) + 2;
      current_num_rows = evas_list_count(tree->row_objects);
      
      if (current_num_rows < num_visible_rows)
      {
         /* We add more row objects */
         for (i = 0; i < (num_visible_rows - current_num_rows); i++)
         {
            Etk_Tree_Row_Object *row_object;
            
            if ((row_object = _etk_tree_row_object_create(tree)))
               tree->row_objects = evas_list_append(tree->row_objects, row_object);
         }
      }
      else if (current_num_rows > num_visible_rows)
      {
         Evas_List *last, *prev;
         
         /* We destroy the row objects that are no longer needed */
         last = evas_list_last(tree->row_objects);
         for (i = 0; i < (current_num_rows - num_visible_rows) && last; i++)
         {
            _etk_tree_row_object_destroy(tree, last->data);
            prev = last->prev;
            tree->row_objects = evas_list_remove_list(tree->row_objects, last);
            last = prev;
         }
      }
   }
   
   /* Update the position and the content of the row objects */
   {
      Etk_Tree_Row *row;
      Etk_Tree_Row_Object *row_object;
      Etk_Geometry cell_geometry, model_geometry;
      Etk_Bool show_expanders;
      Etk_Bool objects_created;
      Evas_List *l2;
      int expander_w, expander_h;
      int x, y;
      int total_width, w;
      int row_id;
      int row_y;
      int depth;
      
      depth = 0;
      row_id = tree->scroll_y / tree->rows_height;
      row_y = -(tree->scroll_y % tree->rows_height);
      for (row = tree->root.first_child, i = 0; i < row_id && row; i++)
         row = _etk_tree_row_next_to_render_get(row, &depth);
      show_expanders = (tree->total_rows > tree->root.num_children && first_visible_col);
      
      for (l = tree->row_objects; l; l = l->next)
      {
         row_object = l->data;
         
         if (row)
         {
            /* If there is still a row to render, we render the content of the row */
            evas_object_move(row_object->background, geometry.x, geometry.y + row_y);
            evas_object_resize(row_object->background, geometry.w, tree->rows_height);
            evas_object_show(row_object->background);
            
            if ((l2 = evas_list_find_list(prev_visible_rows, row)))
               prev_visible_rows = evas_list_remove_list(prev_visible_rows, l2);
            else
               new_visible_rows = evas_list_append(new_visible_rows, row);
            
            if (show_expanders && row->num_children > 0)
            {
               edje_object_signal_emit(row_object->expander,
                  row->unfolded ? "etk,action,unfold" : "etk,action,fold", "etk");
               edje_object_message_signal_process(row_object->expander);
               evas_object_show(row_object->expander);
            }
            else
               evas_object_hide(row_object->expander);
            
            /* Render the sub-objects of the row's cells */
            for (i = 0; i < tree->num_cols; i++)
            {
               col = tree->columns[i];
               if (col->visible)
               {
                  cell_geometry.x = geometry.x + col->xoffset + CELL_HMARGINS;
                  cell_geometry.y = geometry.y + row_y + CELL_VMARGINS;
                  cell_geometry.w = col->width - (2 * CELL_HMARGINS);
                  cell_geometry.h = tree->rows_height - (2 * CELL_VMARGINS);
                  
                  /* Render the expander of the row */
                  if (col == first_visible_col && show_expanders)
                  {
                     edje_object_size_min_get(row_object->expander, &expander_w, &expander_h);
                     if (row->num_children > 0)
                     {
                        evas_object_move(row_object->expander,
                           cell_geometry.x + (depth * expander_w), cell_geometry.y);
                        evas_object_resize(row_object->expander, expander_w, expander_h);
                     }
                     cell_geometry.x += ((depth + 1) * expander_w) + CELL_HMARGINS;
                     cell_geometry.w -= ((depth + 1) * expander_w) + CELL_HMARGINS;
                  }
                  
                  /* Render the sub-objects of the cell */
                  total_width = 0;
                  model_geometry = cell_geometry;
                  for (j = 0; j < col->num_models; j++)
                  {
                     if (col->models[j]->render)
                     {
                        objects_created = col->models[j]->render(col->models[j], row, model_geometry,
                           row->cells_data[i][j], row_object->cells[i].objects[j], evas);
                        
                        /* Objects have been created by the render() method, we add them to the tree */
                        if (objects_created)
                        {
                           for (k = 0; k < MAX_OBJECTS_PER_MODEL; k++)
                           {
                              if (row_object->cells[i].objects[j][k]
                                 && !evas_object_smart_parent_get(row_object->cells[i].objects[j][k]))
                              {
                                 evas_object_clip_set(row_object->cells[i].objects[j][k], col->clip);
                                 etk_widget_member_object_add(tree->grid, row_object->cells[i].objects[j][k]);
                              }
                           }
                        }
                        
                        if (col->models[j]->width_get)
                        {
                           w = col->models[j]->width_get(col->models[j],
                              row->cells_data[i][j], row_object->cells[i].objects[j]);
                        }
                        else
                           w = 0;
                        
                        if ((j + 1) != col->num_models && w > 0)
                           w += MODEL_INTERSPACE;
                        
                        model_geometry.x += w;
                        model_geometry.w -= w;
                        total_width += w;
                     }
                  }
                  
                  /* Align the cell objects */
                  if (col->align != 0.0)
                  {
                     for (j = 0; j < col->num_models; j++)
                     {
                        for (k = 0; k < MAX_OBJECTS_PER_MODEL; k++)
                        {
                           if (row_object->cells[i].objects[j][k])
                           {
                              evas_object_geometry_get(row_object->cells[i].objects[j][k], &x, &y, NULL, NULL);
                              evas_object_move(row_object->cells[i].objects[j][k],
                                 x + (col->align * (cell_geometry.w - total_width)), y);
                           }
                        }
                     }
                  }
               }
               else
               {
                  for (j = 0; j < col->num_models; j++)
                  {
                     for (k = 0; k < MAX_OBJECTS_PER_MODEL; k++)
                     {
                        if (row_object->cells[i].objects[j][k])
                           evas_object_hide(row_object->cells[i].objects[j][k]);
                     }
                  }
               }
            }
            
            _etk_tree_row_signal_emit(row, row_object, (row_id % 2 == 0) ? "etk,state,odd" : "etk,state,even");
            _etk_tree_row_signal_emit(row, row_object, row->selected ? "etk,state,selected" : "etk,state,unselected");
            edje_object_message_signal_process(row_object->background);
            
            evas_object_lower(row_object->background);
            row_object->row = row;
         }
         else
         {
            /* If there is no more row to render, we hide the row objects */
            for (i = 0; i < tree->num_cols; i++)
            {
               for (j = 0; j < tree->columns[i]->num_models; j++)
               {
                  for (k = 0; k < MAX_OBJECTS_PER_MODEL; k++)
                  {
                     if (row_object->cells[i].objects[j][k])
                        evas_object_hide(row_object->cells[i].objects[j][k]);
                  }
               }
            }
            evas_object_hide(row_object->expander);
            evas_object_hide(row_object->background);
            row_object->row = NULL;
         }
         
         row = _etk_tree_row_next_to_render_get(row, &depth);
         row_y += tree->rows_height;
         row_id++;
      }
   }
   
   /* Emit the "row_shown/hidden" signals */
   while (prev_visible_rows)
   {
      etk_signal_emit(_etk_tree_signals[ETK_TREE_ROW_HIDDEN_SIGNAL], ETK_OBJECT(tree), NULL, prev_visible_rows->data);
      prev_visible_rows = evas_list_remove_list(prev_visible_rows, prev_visible_rows);
   }
   while (new_visible_rows)
   {
      etk_signal_emit(_etk_tree_signals[ETK_TREE_ROW_SHOWN_SIGNAL], ETK_OBJECT(tree), NULL, new_visible_rows->data);
      new_visible_rows = evas_list_remove_list(new_visible_rows, new_visible_rows);
   }
}

/**************************
 *
 * Callbacks and handlers
 *
 **************************/

/**************************
 * Tree
 **************************/

/* Called when the tree is realized */
static void _etk_tree_realize_cb(Etk_Object *object, void *data)
{
   Etk_Tree *tree;
   Evas *evas;
   int tree_contains_headers;
   int i;
   
   if (!(tree = ETK_TREE(object)) || !(evas = etk_widget_toplevel_evas_get(ETK_WIDGET(tree))))
      return;
   
   /* Read the data from the theme */
   if (etk_widget_theme_data_get(ETK_WIDGET(tree), "tree_contains_headers", "%d", &tree_contains_headers) != 1)
      tree_contains_headers = 1;
   if (etk_widget_theme_data_get(ETK_WIDGET(tree), "separator_color", "%d %d %d %d", &tree->separator_color.r,
      &tree->separator_color.g, &tree->separator_color.b, &tree->separator_color.a) != 4)
   {
      tree->separator_color.r = 0;
      tree->separator_color.g = 0;
      tree->separator_color.b = 0;
      tree->separator_color.a = 0;
   }
   else
   {
      evas_color_argb_premul(tree->separator_color.a, &tree->separator_color.r,
         &tree->separator_color.g, &tree->separator_color.b);
   }
   
   /* Reparent the column headers if the "tree-contains-headers" setting has changed */
   if (tree->tree_contains_headers != tree_contains_headers)
   {
      for (i = 0; i < tree->num_cols; i++)
      {
         if (tree_contains_headers)
            etk_widget_parent_set(tree->columns[i]->header, ETK_WIDGET(tree));
         else
            etk_widget_parent_set(tree->columns[i]->header, tree->scroll_content);
      }
      tree->tree_contains_headers = (tree_contains_headers != 0);
   }
   
   /* Create an invisible rectangle used to catch the events on the tree headers */
   if (tree->tree_contains_headers)
      _etk_tree_headers_rect_create(tree, ETK_WIDGET(tree));
}

/* Called when the tree is focused */
static void _etk_tree_focus_cb(Etk_Object *object, void *event, void *data)
{
   Etk_Tree *tree;
   
   if (!(tree = ETK_TREE(object)))
      return;
   
   etk_widget_theme_signal_emit(tree->scrolled_view, "etk,state,focused", ETK_FALSE);
   etk_widget_theme_signal_emit(tree->grid, "etk,state,focused", ETK_FALSE);
}

/* Called when the tree is unfocused */
static void _etk_tree_unfocus_cb(Etk_Object *object, void *event, void *data)
{
   Etk_Tree *tree;
   
   if (!(tree = ETK_TREE(object)))
      return;
   
   etk_widget_theme_signal_emit(tree->scrolled_view, "etk,state,unfocused", ETK_FALSE);
   etk_widget_theme_signal_emit(tree->grid, "etk,state,unfocused", ETK_FALSE);
}

/* TODO: row_clicked event: sometimes it's a Etk_Event_Mouse_Up, sometimes a Etk_Event_Mouse_Down... */
/* TODO: get_row_at_xy... */

/* Called when a key is pressed while the tree is focused */
static void _etk_tree_key_down_cb(Etk_Object *object, Etk_Event_Key_Down *event, void *data)
{
   Etk_Tree *tree;
   Etk_Tree_Row *selected_row, *row_to_select;
   
   if (!(tree = ETK_TREE(object)))
      return;
   
   /* CTRL + A: Select all the rows */
   if (strcmp(event->keyname, "a") == 0 && (event->modifiers & ETK_MODIFIER_CTRL))
   {
      etk_tree_select_all(tree);
      etk_signal_stop();
   }
   /* Up arrow: Select the previous row */
   else if (strcmp(event->keyname, "Up") == 0)
   {
      if (!(selected_row = etk_tree_selected_row_get(tree)))
      {
         row_to_select = etk_tree_last_row_get(tree);
         while (!etk_tree_row_is_folded(row_to_select) && etk_tree_row_last_child_get(row_to_select))
            row_to_select = etk_tree_row_last_child_get(row_to_select);
      }
      else
         row_to_select = etk_tree_row_walk_prev(selected_row, ETK_FALSE);
      
      _etk_tree_row_select(tree, row_to_select, event->modifiers);
      etk_tree_row_scroll_to(row_to_select, ETK_FALSE);
      etk_signal_stop();
   }
   /* Down arrow: Select the next row */
   else if (strcmp(event->keyname, "Down") == 0)
   {
      if (!(selected_row = etk_tree_selected_row_get(tree)))
         row_to_select = etk_tree_first_row_get(tree);
      else
         row_to_select = etk_tree_row_walk_next(selected_row, ETK_FALSE);
      
      _etk_tree_row_select(tree, row_to_select, event->modifiers);
      etk_tree_row_scroll_to(row_to_select, ETK_FALSE);
      etk_signal_stop();
   }
   /* Left arrow: Fold the selected row */
   else if (strcmp(event->keyname, "Left") == 0)
   {
      if ((selected_row = etk_tree_selected_row_get(tree)))
         etk_tree_row_fold(selected_row);
      etk_signal_stop();
   }
   /* Right arrow: Unfold the selected row */
   else if (strcmp(event->keyname, "Right") == 0)
   {
      if ((selected_row = etk_tree_selected_row_get(tree)))
         etk_tree_row_unfold(selected_row);
      etk_signal_stop();
   }
}

/**************************
 * Tree Scroll-Content
 **************************/

/* Called when the scroll-content is realized */
static void _etk_tree_scroll_content_realize_cb(Etk_Object *object, void *data)
{
   Etk_Widget *scroll_content;
   Etk_Tree *tree;
   
   if (!(scroll_content = ETK_WIDGET(object)) || !(tree = ETK_TREE(data)))
      return;
   
   /* Create an invisible rectangle used to catch the events on the tree headers */
   if (!tree->tree_contains_headers)
      _etk_tree_headers_rect_create(tree, scroll_content);
}

/**************************
 * Tree Grid
 **************************/

/* Called when the tree grid is realized */
static void _etk_tree_grid_realize_cb(Etk_Object *object, void *data)
{
   Etk_Tree *tree;
   Evas *evas;
   int i;
   
   if (!(tree = TREE_GET(object)) || !(evas = etk_widget_toplevel_evas_get(ETK_WIDGET(object))))
      return;
   
   for (i = 0; i < tree->num_cols; i++)
      _etk_tree_col_realize(tree, i);
   
   tree->grid_clip = evas_object_rectangle_add(evas);
   etk_widget_member_object_add(tree->grid, tree->grid_clip);
}

/* Called when the tree grid is unrealized */
static void _etk_tree_grid_unrealize_cb(Etk_Object *object, void *data)
{
   Etk_Tree *tree;
   int i;
   
   if (!(tree = TREE_GET(object)))
      return;
   
   tree->grid_clip = NULL;
   
   for (i = 0; i < tree->num_cols; i++)
   {
      tree->columns[i]->clip = NULL;
      tree->columns[i]->separator = NULL;
   }
   
   while (tree->row_objects)
   {
      _etk_tree_row_object_destroy(tree, tree->row_objects->data);
      tree->row_objects = evas_list_remove_list(tree->row_objects, tree->row_objects);
   }
}

/**************************
 * Tree Headers
 **************************/

/* Called when the mouse presses the rectangle that is over the column headers */
static void _etk_tree_headers_mouse_down_cb(void *data, Evas *e, Evas_Object *obj, void *event_info)
{
   Etk_Tree *tree;
   Evas_Event_Mouse_Down *event = event_info;
   
   if (!(tree = ETK_TREE(data)) || event->button != 1)
      return;
   
   /* Checks if we should resize a column */
   if ((tree->col_to_resize = etk_tree_col_to_resize_get(tree, event->canvas.x)))
   {
      tree->col_resize_orig_width = tree->col_to_resize->width;
      tree->col_resize_orig_mouse_x = event->canvas.x;
   }
   /* TODO: the mouse events should not always be propagated... */
}

/* Called when the mouse releases the rectangle that is over the column headers */
static void _etk_tree_headers_mouse_up_cb(void *data, Evas *e, Evas_Object *obj, void *event_info)
{
   Etk_Tree *tree;
   Evas_Event_Mouse_Down *event = event_info;
   
   if (!(tree = ETK_TREE(data)) || event->button != 1)
      return;
   
   if (tree->col_to_resize)
      tree->col_to_resize = NULL;
}

/* Called when the mouse moves over the column headers */
static void _etk_tree_headers_mouse_move_cb(void *data, Evas *e, Evas_Object *obj, void *event_info)
{
   Etk_Tree *tree;
   Etk_Toplevel *toplevel;
   Evas_Event_Mouse_Move *event = event_info;
   
   if (!(tree = ETK_TREE(data)) || !(toplevel = etk_widget_toplevel_parent_get(ETK_WIDGET(tree))))
      return;
   
   if (tree->col_to_resize)
   {
      int delta;
      
      /* Resize the column to resize */
      delta = event->cur.canvas.x - tree->col_resize_orig_mouse_x;
      etk_tree_col_width_set(tree->col_to_resize, tree->col_resize_orig_width + delta);
   }
   else
   {
      Etk_Tree_Col *col_to_resize;
      
      /* Set/Unset the resize mouse pointer if the pointer is/was between two column headers */
      col_to_resize = etk_tree_col_to_resize_get(tree, event->cur.canvas.x);
      if (col_to_resize && !tree->col_resize_pointer_set)
      {
         etk_toplevel_pointer_push(toplevel, ETK_POINTER_H_DOUBLE_ARROW);
         tree->col_resize_pointer_set = ETK_TRUE;
      }
      else if (!col_to_resize && tree->col_resize_pointer_set)
      {
         etk_toplevel_pointer_pop(toplevel, ETK_POINTER_H_DOUBLE_ARROW);
         tree->col_resize_pointer_set = ETK_FALSE;
      }
   }
}

/* Called when the mouse enters the rectangle that is over the column headers */
static void _etk_tree_headers_mouse_in_cb(void *data, Evas *e, Evas_Object *obj, void *event_info)
{
   Etk_Tree *tree;
   Etk_Toplevel *toplevel;
   Etk_Tree_Col *col_to_resize;
   Evas_Event_Mouse_In *event = event_info;
   
   if (!(tree = ETK_TREE(data)) || !(toplevel = etk_widget_toplevel_parent_get(ETK_WIDGET(tree))))
      return;
   
   /* Set the resize mouse pointer if the pointer is between two column headers */
   col_to_resize = etk_tree_col_to_resize_get(tree, event->canvas.x);
   if (col_to_resize && !tree->col_resize_pointer_set)
   {
      etk_toplevel_pointer_push(toplevel, ETK_POINTER_H_DOUBLE_ARROW);
      tree->col_resize_pointer_set = ETK_TRUE;
   }
}

/* Called when the mouse leaves the rectangle that is over the column headers */
static void _etk_tree_headers_mouse_out_cb(void *data, Evas *e, Evas_Object *obj, void *event_info)
{
   Etk_Tree *tree;
   Etk_Toplevel *toplevel;
   
   if (!(tree = ETK_TREE(data)) || !(toplevel = etk_widget_toplevel_parent_get(ETK_WIDGET(tree))))
      return;
   
   /* Unset the resize mouse pointer if it was set */
   if (tree->col_resize_pointer_set)
   {
      etk_toplevel_pointer_pop(toplevel, ETK_POINTER_H_DOUBLE_ARROW);
      tree->col_resize_pointer_set = ETK_FALSE;
   }
}

/* Called when the background of a row is pressed by the mouse */
static void _etk_tree_row_mouse_down_cb(void *data, Evas *e, Evas_Object *obj, void *event_info)
{
   Etk_Tree_Row_Object *row_object;
   Etk_Event_Mouse_Down event;
   
   if (!(row_object = data) || !row_object->row)
      return;
   
   etk_event_mouse_down_wrap(ETK_WIDGET(row_object->row->tree), event_info, &event);
   
   /* We do not select a row on the "key-down" event if the row is already selected
    * to allow the user to drag and drop several rows. The selection will be done on "key-up" */
   if (!row_object->row->selected || event.modifiers != ETK_MODIFIER_NONE)
      _etk_tree_row_select(row_object->row->tree, row_object->row, event.modifiers);
   
   if (event.flags != ETK_MOUSE_NONE)
   {
      etk_signal_emit(_etk_tree_signals[ETK_TREE_ROW_CLICKED_SIGNAL], ETK_OBJECT(row_object->row->tree),
         NULL, row_object->row, &event);
   }
}

/* Called when the background of a row is released by the mouse */
static void _etk_tree_row_mouse_up_cb(void *data, Evas *e, Evas_Object *obj, void *event_info)
{
   Etk_Tree_Row_Object *row_object;
   Etk_Event_Mouse_Up event;
   int x, y, w, h;
   
   if (!(row_object = data) || !row_object->row)
      return;
   
   evas_object_geometry_get(obj, &x, &y, &w, &h);
   etk_event_mouse_up_wrap(ETK_WIDGET(row_object->row->tree), event_info, &event);
   
   /* We make sure the mouse button has been released over the row */
   if (ETK_INSIDE(event.canvas.x, event.canvas.y, x, y, w, h))
   {
      if (row_object->row->selected && event.modifiers == ETK_MODIFIER_NONE)
         _etk_tree_row_select(row_object->row->tree, row_object->row, ETK_MODIFIER_NONE);
      
      if (event.flags == ETK_MOUSE_NONE)
      {
         etk_signal_emit(_etk_tree_signals[ETK_TREE_ROW_CLICKED_SIGNAL], ETK_OBJECT(row_object->row->tree),
            NULL, row_object->row, &event);
      }
      if (event.flags == ETK_MOUSE_DOUBLE_CLICK)
      {
         etk_signal_emit(_etk_tree_signals[ETK_TREE_ROW_ACTIVATED_SIGNAL], ETK_OBJECT(row_object->row->tree),
            NULL, row_object->row, &event);
      }
   }
}

/* Called when the expander of a row is released by the mouse */
static void _etk_tree_row_expander_mouse_up_cb(void *data, Evas *e, Evas_Object *obj, void *event_info)
{
   Etk_Tree_Row_Object *row_object;
   Evas_Event_Mouse_Up *event = event_info;
   Evas_Coord x, y, w, h;
   
   if (!(row_object = data) || !row_object->row)
      return;
   
   evas_object_geometry_get(obj, &x, &y, &w, &h);
   if (ETK_INSIDE(event->canvas.x, event->canvas.y, x, y, w, h))
   {
      if (row_object->row->unfolded)
         etk_tree_row_fold(row_object->row);
      else
         etk_tree_row_unfold(row_object->row);
   }
}

/**************************
 *
 * Private functions
 *
 **************************/

/* A job called when rows need to be deleted */
static void _etk_tree_purge_job(void *data)
{
   Etk_Tree *tree;
   
   if (!(tree = ETK_TREE(data)))
      return;
   
   _etk_tree_purge(tree);
   tree->purge_job = NULL;
}

/* Deletes effectively all the rows marked as deleted */
static void _etk_tree_purge(Etk_Tree *tree)
{
   Evas_List *l;
   Etk_Tree_Row *row, *r, *next;
   Etk_Tree_Col *col;
   int i, j;
   
   if (!tree)
      return;
   
   /* First we dissociate the row objects from the rows that will be deleted,
    * and we cache their row objects */
   for (l = tree->row_objects; l; l = l->next)
   {
      Etk_Tree_Row_Object *row_object;
      
      row_object = l->data;
      if (row_object->row && row_object->row->delete_me)
      {
         for (i = 0; i < tree->num_cols; i++)
         {
            col = tree->columns[i];
            for (j = 0; j < col->num_models; j++)
            {
               if (col->models[j]->objects_cache)
               {
                  col->models[j]->objects_cache(col->models[j], row_object->row->cells_data[i][j],
                     row_object->cells[i].objects[j]);
               }
            }
         }
         
         etk_signal_emit(_etk_tree_signals[ETK_TREE_ROW_HIDDEN_SIGNAL], ETK_OBJECT(tree), NULL, row_object->row);
         row_object->row = NULL;
      }
   }
   
   /* Then we delete all the rows of the purge pool */
   while (tree->purge_pool)
   {
      row = tree->purge_pool->data;
      row->next = NULL;
      for (r = row; r; r = next)
      {
         /* Get the next row to free after this one */
         if (r->first_child)
         {
            r->last_child->next = r->next;
            r->next = r->first_child;
         }
         next = r->next;
         
         /* Free the row */
         if (r->cells_data)
         {
            for (i = 0; i < tree->num_cols; i++)
            {
               col = tree->columns[i];
               for (j = 0; j < col->num_models; j++)
               {
                  if (col->models[j]->cell_data_free)
                     col->models[j]->cell_data_free(col->models[j], r->cells_data[i][j]);
                  free(r->cells_data[i][j]);
               }
               free(r->cells_data[i]);
            }
            free(r->cells_data);
         }
         
         if (r->data && r->data_free_cb)
            r->data_free_cb(r->data);
         
         free(r);
      }
      tree->purge_pool = evas_list_remove_list(tree->purge_pool, tree->purge_pool);
   }
}

/* Moves a row of the tree to the list of rows to delete during the next iteration */
static void _etk_tree_row_move_to_purge_pool(Etk_Tree_Row *row)
{
   Etk_Tree *tree;
   Etk_Tree_Row *r;
   
   if (!row || row->delete_me)
      return;
   
   tree = row->tree;
   
   /* Mark the row and all its children as deleted */
   row->delete_me = ETK_TRUE;
   tree->total_rows--;
   for (r = row->first_child; r; )
   {
      r->delete_me = ETK_TRUE;
      tree->total_rows--;
      
      if (r->first_child)
         r = r->first_child;
      else
      {
         while (r && !r->next)
         {
            if (r->parent == row)
               r = NULL;
            else
               r = r->parent;
         }
         r = r ? r->next : NULL;
      }
   }
   
   /* Move the row to the purge_pool */
   if (row->prev)
      row->prev->next = row->next;
   if (row->next)
      row->next->prev = row->prev;
   if (row->parent)
   {
      if (row->parent->first_child == row)
         row->parent->first_child = row->next;
      if (row->parent->last_child == row)
         row->parent->last_child = row->prev;
      row->parent->num_children--;
   }
   for (r = row->parent; r && r->unfolded; r = r->parent)
      r->num_visible_children -= row->num_visible_children + 1;
   
   if (tree->last_selected_row == row)
      tree->last_selected_row = NULL;
   tree->purge_pool = evas_list_append(tree->purge_pool, row);
   
   if (!tree->purge_job)
      tree->purge_job = ecore_job_add(_etk_tree_purge_job, tree);
}

/* Creates the Evas objects of the corresponding column */
static void _etk_tree_col_realize(Etk_Tree *tree, int col_nth)
{
   Evas *evas;
   Etk_Tree_Col *col;

   if (!tree || col_nth < 0 || col_nth >= tree->num_cols)
      return;
   if (!(evas = etk_widget_toplevel_evas_get(tree->grid)))
      return;

   col = tree->columns[col_nth];
   col->clip = evas_object_rectangle_add(evas);
   etk_widget_member_object_add(tree->grid, col->clip);

   col->separator = evas_object_rectangle_add(evas);
   evas_object_color_set(col->separator, tree->separator_color.r,
      tree->separator_color.g, tree->separator_color.b, tree->separator_color.a);
   evas_object_pass_events_set(col->separator, 1);
   evas_object_clip_set(col->separator, col->clip);
   etk_widget_member_object_add(tree->grid, col->separator);
}

/* Returns the column to resize according to the position of the mouse pointer */
static Etk_Tree_Col *etk_tree_col_to_resize_get(Etk_Tree *tree, int x)
{
   Etk_Tree_Col *col;
   int header_x = 0, header_w = 0;
   int i;
   
   if (!tree || !tree->headers_visible)
      return NULL;
   
   /* First we find the column over which the mouse pointer is */
   col = NULL;
   for (i = 0; i < tree->num_cols; i++)
   {
      if (!tree->columns[i]->visible)
         continue;
      
      etk_widget_geometry_get(tree->columns[i]->header, &header_x, NULL, &header_w, NULL);
      if (header_x <= x && x < (header_x + header_w))
      {
         col = tree->columns[i];
         x -= header_x;
         break;
      }
   }
   
   if (!col)
      return NULL;
   
   /* Once the column is found, we look if the mouse pointer is over
    * one of the two edges of the header (x is now relative to the column's header) */
   if (x <= COL_RESIZE_THRESHOLD)
   {
      Etk_Tree_Col *prev_visible_col, *col2;
      int i;
      
      prev_visible_col = NULL;
      for (i = 0; i < col->tree->num_cols; i++)
      {
         col2 = col->tree->columns[i];
         if (!col2->visible)
            continue;
         
         if ((col2->position < col->position) && (!prev_visible_col || (prev_visible_col->position < col2->position)))
            prev_visible_col = col2;
      }
      
      return (prev_visible_col && prev_visible_col->resizable) ? prev_visible_col : NULL;
   }
   else if (header_w - x <= COL_RESIZE_THRESHOLD)
      return (col && col->resizable) ? col : NULL;

   return NULL;
}

/* Creates the invisible rectangle which is ovet the column headers. It is used to catch mouse events */
static void _etk_tree_headers_rect_create(Etk_Tree *tree, Etk_Widget *parent)
{
   Evas *evas;
   
   if (!tree || !parent || !(evas = etk_widget_toplevel_evas_get(parent)))
      return;
   
   tree->headers_rect = evas_object_rectangle_add(evas);
   evas_object_color_set(tree->headers_rect, 0, 0, 0, 0);
   evas_object_repeat_events_set(tree->headers_rect, 1);
   etk_widget_member_object_add(parent, tree->headers_rect);
   
   evas_object_event_callback_add(tree->headers_rect, EVAS_CALLBACK_MOUSE_DOWN,
      _etk_tree_headers_mouse_down_cb, tree);
   evas_object_event_callback_add(tree->headers_rect, EVAS_CALLBACK_MOUSE_UP,
      _etk_tree_headers_mouse_up_cb, tree);
   evas_object_event_callback_add(tree->headers_rect, EVAS_CALLBACK_MOUSE_MOVE,
      _etk_tree_headers_mouse_move_cb, tree);
   evas_object_event_callback_add(tree->headers_rect, EVAS_CALLBACK_MOUSE_IN,
      _etk_tree_headers_mouse_in_cb, tree);
   evas_object_event_callback_add(tree->headers_rect, EVAS_CALLBACK_MOUSE_OUT,
      _etk_tree_headers_mouse_out_cb, tree);
}

/* Gets the next row to render */
static Etk_Tree_Row *_etk_tree_row_next_to_render_get(Etk_Tree_Row *row, int *depth)
{
   if (!row)
      return NULL;
   
   if (row->first_child && row->unfolded)
   {
      if (depth)
         (*depth)++;
      return row->first_child;
   }
   else
   {
      while (row && !row->next)
      {
         if (depth)
            (*depth)--;
         row = row->parent;
      }
      return row ? row->next : NULL;
   }
}

/* Creates a new row object and its subobjects */
static Etk_Tree_Row_Object *_etk_tree_row_object_create(Etk_Tree *tree)
{
   Etk_Tree_Row_Object *row_object;
   Etk_Tree_Col *col, *first_visible_col;
   Evas *evas;
   int i, j, k;
   
   if (!tree || !tree->built || !(evas = etk_widget_toplevel_evas_get(tree->grid)))
      return NULL;
   
   row_object = malloc(sizeof(Etk_Tree_Row_Object));
   row_object->row = NULL;
   
   /* Create the background object of the row */
   row_object->background = edje_object_add(evas);
   etk_theme_edje_object_set_from_parent(row_object->background, "row", ETK_WIDGET(tree));
   evas_object_repeat_events_set(row_object->background, 1);
   evas_object_clip_set(row_object->background, tree->grid_clip);
   etk_widget_member_object_add(tree->grid, row_object->background);
   evas_object_event_callback_add(row_object->background, EVAS_CALLBACK_MOUSE_DOWN,
      _etk_tree_row_mouse_down_cb, row_object);
   evas_object_event_callback_add(row_object->background, EVAS_CALLBACK_MOUSE_UP,
      _etk_tree_row_mouse_up_cb, row_object);
   
   /* Create the expander object of the row */
   if (tree->mode == ETK_TREE_MODE_TREE)
   {
      row_object->expander = edje_object_add(evas);
      etk_theme_edje_object_set_from_parent(row_object->expander, "expander", ETK_WIDGET(tree));
      
      /* Clip it to the first visible row */
      first_visible_col = NULL;
      for (i = 0; i < tree->num_cols; i++)
      {
         col = tree->columns[i];
         if (col->visible)
         {
            if (!first_visible_col || first_visible_col->position > col->position)
               first_visible_col = col;
         }
      }
      if (first_visible_col)
         evas_object_clip_set(row_object->expander, first_visible_col->clip);
      
      etk_widget_member_object_add(tree->grid, row_object->expander);
      evas_object_event_callback_add(row_object->expander, EVAS_CALLBACK_MOUSE_UP,
         _etk_tree_row_expander_mouse_up_cb, row_object);
   }
   else
      row_object->expander = NULL;
   
   /* Create the sub-objects of the row's cells */
   row_object->cells = calloc(tree->num_cols, sizeof(Etk_Tree_Cell_Objects));
   for (i = 0; i < tree->num_cols; i++)
   {
      col = tree->columns[i];
      for (j = 0; j < col->num_models; j++)
      {
         if (col->models[j]->objects_create)
         {
            col->models[j]->objects_create(col->models[j], row_object->cells[i].objects[j], evas);
            for (k = 0; k < MAX_OBJECTS_PER_MODEL; k++)
            {
               if (row_object->cells[i].objects[j][k]
                  && !evas_object_smart_parent_get(row_object->cells[i].objects[j][k]))
               {
                  evas_object_clip_set(row_object->cells[i].objects[j][k], col->clip);
                  etk_widget_member_object_add(tree->grid, row_object->cells[i].objects[j][k]);
               }
            }
         }
      }
      evas_object_raise(col->separator);
   }
   
   return row_object;
}

/* Destroys the row object and its subobjects */
static void _etk_tree_row_object_destroy(Etk_Tree *tree, Etk_Tree_Row_Object *row_object)
{
   int i, j, k;
   
   if (!tree || !row_object)
      return;
   
   for (i = 0; i < tree->num_cols; i++)
   {
      for (j = 0; j < tree->columns[i]->num_models; j++)
      {
         for (k = 0; k < MAX_OBJECTS_PER_MODEL; k++)
            evas_object_del(row_object->cells[i].objects[j][k]);
      }
   }
   free(row_object->cells);
   
   evas_object_del(row_object->expander);
   evas_object_del(row_object->background);
   free(row_object);
}

/* Emits a theme-signal to all the row objects (background, expander, model-objects) */
static void _etk_tree_row_signal_emit(Etk_Tree_Row *row, Etk_Tree_Row_Object *row_object, const char *signal)
{
   if (!row || !row_object || !signal)
      return;
   
   if (row_object->background)
      edje_object_signal_emit(row_object->background, signal, "etk");
   if (row_object->expander)
      edje_object_signal_emit(row_object->expander, signal, "etk");
}

/* Clips all the expanders against the clip object of the first visible column */
static void _etk_tree_expanders_clip(Etk_Tree *tree)
{
   Etk_Tree_Row_Object *row_object;
   Etk_Tree_Col *col, *first_visible_col;
   Evas_List *l;
   int i;
   
   if (!tree || tree->mode != ETK_TREE_MODE_TREE || !tree->built)
      return;
   
   /* Clip it to the first visible row */
   first_visible_col = NULL;
   for (i = 0; i < tree->num_cols; i++)
   {
      col = tree->columns[i];
      if (col->visible)
      {
         if (!first_visible_col || first_visible_col->position > col->position)
            first_visible_col = col;
      }
   }
   
   if (!first_visible_col)
      return;
   
   for (l = tree->row_objects; l; l = l->next)
   {
      row_object = l->data;
      if (row_object->expander)
         evas_object_clip_set(row_object->expander, first_visible_col->clip);
   }
}

/* Selects/Unselects the corresponding rows according to the modifiers */
static void _etk_tree_row_select(Etk_Tree *tree, Etk_Tree_Row *row, Etk_Modifiers modifiers)
{
   if (!tree || !row || row->delete_me)
      return;
   
   if (!tree->multiple_select)
   {
      if (row->selected)
      {
         /* Unselect the row */
         if (modifiers & ETK_MODIFIER_CTRL)
            etk_tree_row_unselect(row);
      }
      else
      {
         /* Unselect the selected row and select only the given row */
         if (tree->last_selected_row && tree->last_selected_row->selected)
            etk_tree_row_unselect(tree->last_selected_row);
         else
            etk_tree_unselect_all(tree);
         
         row->selected = ETK_TRUE;
         row->tree->last_selected_row = row;
         etk_signal_emit(_etk_tree_signals[ETK_TREE_ROW_SELECTED_SIGNAL], ETK_OBJECT(row->tree), NULL, row);
      }
   }
   else
   {
      if ((modifiers & ETK_MODIFIER_SHIFT) && tree->last_selected_row)
      {
         Etk_Tree_Row *r;
         int cnt = 0;
         
         etk_tree_unselect_all(tree);
         
         /* Walk through all the rows of the tree and select the rows
          * between the last selected row and the given row */
         for (r = tree->root.first_child; r; r = etk_tree_row_walk_next(r, ETK_TRUE))
         {
            if (r == tree->last_selected_row)
               cnt++;
            if (r == row)
               cnt++;
            
            if (cnt >= 1)
               r->selected = ETK_TRUE;
            if (cnt >= 2)
               break;
         }
         
         etk_signal_emit(_etk_tree_signals[ETK_TREE_ROW_SELECTED_SIGNAL], ETK_OBJECT(row->tree), NULL, row);
      }
      /* Invert the selected state of the given row */
      else if (modifiers & ETK_MODIFIER_CTRL)
      {
         if (row->selected)
            etk_tree_row_unselect(row);
         else
         {
            row->selected = ETK_TRUE;
            row->tree->last_selected_row = row;
            etk_signal_emit(_etk_tree_signals[ETK_TREE_ROW_SELECTED_SIGNAL], ETK_OBJECT(row->tree), NULL, row);
         }
      }
      else
      {
         /* Unselect all the rows and select only the given row */
         etk_tree_unselect_all(tree);
         row->selected = ETK_TRUE;
         row->tree->last_selected_row = row;
         etk_signal_emit(_etk_tree_signals[ETK_TREE_ROW_SELECTED_SIGNAL], ETK_OBJECT(row->tree), NULL, row);
      }
   }
   
   if (!row->tree->frozen)
      etk_widget_redraw_queue(ETK_WIDGET(row->tree));
}

/** @} */