/** @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));
}
/** @} */