From 6173bb0e6c3bf51cd463f7bc4f725429d9087b2b Mon Sep 17 00:00:00 2001 From: John Mark Bell Date: Tue, 5 Oct 2010 19:14:46 +0000 Subject: Merge treeview-redux to trunk svn path=/trunk/netsurf/; revision=10865 --- desktop/tree.c | 3168 +++++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 2225 insertions(+), 943 deletions(-) (limited to 'desktop/tree.c') diff --git a/desktop/tree.c b/desktop/tree.c index 295bef195..36ba38d7e 100644 --- a/desktop/tree.c +++ b/desktop/tree.c @@ -1,5 +1,6 @@ /* * Copyright 2004 Richard Wilson + * Copyright 2009 Paul Blokus * * This file is part of NetSurf, http://www.netsurf-browser.org/ * @@ -25,1273 +26,1747 @@ #include #include #include -#include "content/urldb.h" +#include "content/content.h" +#include "content/hlcache.h" +#include "desktop/browser.h" +#include "desktop/textarea.h" +#include "desktop/textinput.h" #include "desktop/tree.h" #include "desktop/options.h" +#include "desktop/plotters.h" +#include "render/font.h" #include "utils/log.h" #include "utils/messages.h" #include "utils/utils.h" - -static void tree_draw_node(struct tree *tree, struct node *node, int clip_x, - int clip_y, int clip_width, int clip_height); -static struct node_element *tree_create_node_element(struct node *parent, - node_element_data data); -static void tree_delete_node_internal(struct tree *tree, struct node *node, bool siblings); -static int tree_get_node_width(struct node *node); -static int tree_get_node_height(struct node *node); -static void tree_handle_selection_area_node(struct tree *tree, - struct node *node, int x, int y, int width, int height, - bool invert); -static void tree_selected_to_processing(struct node *node); -void tree_clear_processing(struct node *node); -struct node *tree_move_processing_node(struct node *node, struct node *link, - bool before, bool first); -struct node *tree_create_leaf_node_shared(struct node *parent, const char *title); - -static int tree_initialising = 0; +#include "utils/url.h" + +typedef enum { + TREE_NO_DRAG = 0, + TREE_SELECT_DRAG, + TREE_MOVE_DRAG +} tree_drag_type; + + +#define MAXIMUM_URL_LENGTH 1024 + +#define TREE_ICON_SIZE 16 +#define NODE_INSTEP 20 +#define TREE_TEXT_HEIGHT 20 +#define FURNITURE_COLOUR 0x888888 + +static plot_font_style_t plot_fstyle = { + .family = PLOT_FONT_FAMILY_SANS_SERIF, + .size = 10240, + .weight = 400, + .flags = FONTF_NONE, + .background = 0xFFFFFF, + .foreground = 0x000000 +}; + +static plot_font_style_t plot_fstyle_selected = { + .family = PLOT_FONT_FAMILY_SANS_SERIF, + .size = 10240, + .weight = 400, + .flags = FONTF_NONE, + .background = 0x000000, + .foreground = 0xEEEEEE +}; + +struct node; +struct tree; + +struct node_element_box { + int x; /**< X offset from origin */ + int y; /**< Y offset from origin */ + int width; /**< Element width */ + int height; /**< Element height */ +}; + +struct node_element { + struct node *parent; /**< Parent node */ + node_element_type type; /**< Element type */ + struct node_element_box box; /**< Element bounding box */ + const char *text; /**< Text for the element */ + void *bitmap; /**< Bitmap for the element */ + struct node_element *next; /**< Next node element */ + unsigned int flag; /**< Client specified flag for data + being represented */ + bool editable; /**< Whether the node text can be + * modified, editable text is deleted + * without noticing the tree user + */ +}; + +struct node { + bool selected; /**< Whether the node is selected */ + bool expanded; /**< Whether the node is expanded */ + bool folder; /**< Whether the node is a folder */ + bool retain_in_memory; /**< Whether the node remains + in memory after deletion */ + bool deleted; /**< Whether the node is currently + deleted */ + bool processing; /**< Internal flag used when moving */ + struct node_element_box box; /**< Bounding box of all elements */ + struct node_element data; /**< Data to display */ + struct node *parent; /**< Parent entry (NULL for root) */ + struct node *child; /**< First child */ + struct node *last_child; /**< Last child */ + struct node *previous; /**< Previous child of the parent */ + struct node *next; /**< Next child of the parent */ + + /** Sorting function for the node (for folder nodes only) */ + int (*sort) (struct node *, struct node *); + /** Gets called for each deleted node_element and on node launch */ + tree_node_user_callback user_callback; + /** User data to be passed to delete_callback */ + void *callback_data; +}; + +struct tree { + /* These coordinates are only added to the coordinates passed to the + plotters. This means they are invisible to the tree, what has to be + taken into account i.e in keyboard/mouse event passing */ + struct node *root; /* Tree root element */ + int width; /* Tree width */ + int height; /* Tree height */ + unsigned int flags; /* Tree flags */ + struct text_area *textarea; /* Handle for UTF-8 textarea */ + bool textarea_drag_start; /* whether the start of a mouse drag + was in the textarea */ + struct node_element *editing; /* Node element being edited */ + + bool redraw; /* Flag indicating whether the tree + should be redrawn on layout + changes */ + tree_drag_type drag; + const struct treeview_table *callbacks; + void *client_data; /* User assigned data for the + callbacks */ +}; /** - * Initialises a user-created tree + * Creates and initialises a new tree. * - * \param tree the tree to initialise + * \param flags flag word for flags to create the new tree with + * \param redraw_request function to be called each time the tree wants to + * be redrawn + * \param client_data data to be passed to start_redraw and end_redraw + * \param root gets updated to point at the root of the tree, + * if not NULL + * \return the newly created tree, or NULL on memory exhaustion */ -void tree_initialise(struct tree *tree) { - - assert(tree); +struct tree *tree_create(unsigned int flags, + const struct treeview_table *callbacks, void *client_data) +{ + struct tree *tree; + char *title; + + tree = calloc(sizeof(struct tree), 1); + if (tree == NULL) { + LOG(("calloc failed")); + warn_user("NoMemory", 0); + return NULL; + } - tree_set_node_expanded(tree, tree->root, true); - tree_initialise_nodes(tree, tree->root); - tree_recalculate_node_positions(tree, tree->root); - tree_set_node_expanded(tree, tree->root, false); + title = strdup("Root"); + if (title == NULL) { + LOG(("malloc failed")); + warn_user("NoMemory", 0); + free(tree); + return NULL; + } + tree->root = tree_create_folder_node(NULL, NULL, title, + false, false, false); + if (tree->root == NULL) { + free(title); + free(tree); + return NULL; + } tree->root->expanded = true; - tree_recalculate_node_positions(tree, tree->root); - tree_recalculate_size(tree); + + tree->width = 0; + tree->height = 0; + tree->flags = flags; + tree->textarea = NULL; + tree->textarea_drag_start = false; + tree->editing = NULL; + tree->redraw = false; + tree->drag = TREE_NO_DRAG; + tree->callbacks = callbacks; + tree->client_data = client_data; + + return tree; } /** - * Initialises a user-created node structure + * Recalculates the dimensions of a node element. * - * \param root the root node to update from + * \param tree the tree to which the element belongs, may be NULL + * \param element the element to recalculate */ -void tree_initialise_nodes(struct tree *tree, struct node *root) { - struct node *node; - - assert(root); +static void tree_recalculate_node_element(struct tree *tree, + struct node_element *element) +{ + struct bitmap *bitmap = NULL; + int width, height; - tree_initialising++; - for (node = root; node; node = node->next) { - tree_recalculate_node(tree, node, true); - if (node->child) { - tree_initialise_nodes(tree, node->child); - } - } - tree_initialising--; + assert(element != NULL); - if (tree_initialising == 0) - tree_recalculate_node_positions(tree, root); -} + switch (element->type) { + case NODE_ELEMENT_TEXT_PLUS_ICON: + case NODE_ELEMENT_TEXT: + if(element->text == NULL) + break; + if (tree != NULL && element == tree->editing) { + textarea_get_dimensions(tree->textarea, + &element->box.width, NULL); + } else { + nsfont.font_width(&plot_fstyle, + element->text, + strlen(element->text), + &element->box.width); + } -/** - * Recalculate the node data and redraw the relevant section of the tree. - * - * \param tree the tree to redraw - * \param node the node to update - * \param recalculate_sizes whether the elements have changed - * \param expansion the request is the result of a node expansion - */ -void tree_handle_node_changed(struct tree *tree, struct node *node, - bool recalculate_sizes, bool expansion) { - int width, height; + element->box.width += 8; + element->box.height = TREE_TEXT_HEIGHT; - assert(node); + if (element->type == NODE_ELEMENT_TEXT_PLUS_ICON) + element->box.width += NODE_INSTEP; - if ((expansion) && (node->expanded) && (node->child)) { - tree_set_node_expanded(tree, node->child, false); - tree_set_node_selected(tree, node->child, false); - } + break; - width = node->box.width; - height = node->box.height; - if ((recalculate_sizes) || (expansion)) - tree_recalculate_node(tree, node, true); - if ((node->box.height != height) || (expansion)) { - tree_recalculate_node_positions(tree, tree->root); - tree_redraw_area(tree, 0, node->box.y, 16384, 16384); - } else { - width = (width > node->box.width) ? width : node->box.width; - tree_redraw_area(tree, node->box.x, node->box.y, width, node->box.height); + case NODE_ELEMENT_BITMAP: + bitmap = element->bitmap; + if (bitmap != NULL) { + width = bitmap_get_width(bitmap); + height = bitmap_get_height(bitmap); + element->box.width = width + 1; + element->box.height = height + 2; + } else { + element->box.width = 0; + element->box.height = 0; + } + break; } - if ((recalculate_sizes) || (expansion)) - tree_recalculate_size(tree); } /** - * Recalculate the node element and redraw the relevant section of the tree. - * The tree size is not updated. + * Calculates the height of a node including any children * - * \param tree the tree to redraw - * \param element the node element to update + * \param node the node to calculate the height of + * \return the total height of the node and children */ -void tree_handle_node_element_changed(struct tree *tree, struct node_element *element) { - int width, height; +static int tree_get_node_height(struct node *node) +{ + int y1; - assert(element); + assert(node != NULL); - width = element->box.width; - height = element->box.height; - tree_recalculate_node_element(element); + if ((node->child == NULL) || (node->expanded == false)) { + return node->box.height; + } - if (element->box.height != height) { - tree_recalculate_node(tree, element->parent, false); - tree_redraw_area(tree, 0, element->box.y, 16384, 16384); - } else { - if (element->box.width != width) - tree_recalculate_node(tree, element->parent, false); - width = (width > element->box.width) ? width : - element->box.width; - tree_redraw_area(tree, element->box.x, element->box.y, width, element->box.height); + y1 = node->box.y; + if (y1 < 0) { + y1 = 0; + } + node = node->child; + + while ((node->next != NULL) || + ((node->child != NULL) && (node->expanded))) { + for (; node->next != NULL; node = node->next); + + if ((node->child != NULL) && (node->expanded)) { + node = node->child; + } } + return node->box.y + node->box.height - y1; } /** - * Recalculates the size of a node. + * Calculates the width of a node including any children * - * \param node the node to update - * \param recalculate_sizes whether the node elements have changed + * \param node the node to calculate the height of + * \return the total width of the node and children */ -void tree_recalculate_node(struct tree *tree, struct node *node, bool recalculate_sizes) { - struct node_element *element; - int height; +static int tree_get_node_width(struct node *node) +{ + int width = 0; + int child_width; - assert(node); + assert(node != NULL); - height = node->box.height; - node->box.width = 0; - node->box.height = 0; - if (node->expanded) { - for (element = &node->data; element; element = element->next) { - if (recalculate_sizes) - tree_recalculate_node_element(element); - node->box.width = (node->box.width > - element->box.x + element->box.width - node->box.x) ? - node->box.width : - element->box.width + element->box.x - node->box.x; - node->box.height += element->box.height; + for (; node != NULL; node = node->next) { + if (width < (node->box.x + node->box.width)) { + width = node->box.x + node->box.width; } - } else { - if (recalculate_sizes) - for (element = &node->data; element; element = element->next) - tree_recalculate_node_element(element); - else - tree_recalculate_node_element(&node->data); - node->box.width = node->data.box.width; - node->box.height = node->data.box.height; - } - if (height != node->box.height) { - for (; node->parent; node = node->parent); - if (tree_initialising == 0) - tree_recalculate_node_positions(tree, node); + if ((node->child != NULL) && (node->expanded)) { + child_width = tree_get_node_width(node->child); + if (width < child_width) { + width = child_width; + } + } } + return width; } /** * Recalculates the position of a node, its siblings and children. * + * \param tree the tree to which 'root' belongs * \param root the root node to update from */ -void tree_recalculate_node_positions(struct tree *tree, struct node *root) { +static void tree_recalculate_node_positions(struct tree *tree, + struct node *root) +{ struct node *parent; struct node *node; struct node *child; struct node_element *element; int y; + bool has_icon; + + for (node = root; node != NULL; node = node->next) { - for (node = root; node; node = node->next) { - if (node->previous) { + parent = node->parent; + + if (node->previous != NULL) { node->box.x = node->previous->box.x; node->box.y = node->previous->box.y + - tree_get_node_height(node->previous); - } else if ((parent = node->parent)) { + tree_get_node_height(node->previous); + } else if (parent != NULL) { node->box.x = parent->box.x + NODE_INSTEP; node->box.y = parent->box.y + - parent->box.height; + parent->box.height; for (child = parent->child; child != node; - child = child->next) + child = child->next) node->box.y += child->box.height; } else { - node->box.x = tree->no_furniture ? -NODE_INSTEP + 4 : 0; - node->box.y = -40; + node->box.x = tree->flags & TREE_NO_FURNITURE + ? -NODE_INSTEP + 4 : 0; + node->box.y = -20; } - if (node->expanded) { - if (node->folder) { - node->data.box.x = node->box.x; - node->data.box.y = node->box.y; - tree_recalculate_node_positions(tree, node->child); - } else { - y = node->box.y; - for (element = &node->data; element; - element = element->next) { - if (element->type == NODE_ELEMENT_TEXT_PLUS_SPRITE) { - element->box.x = node->box.x; - } else { - element->box.x = node->box.x + NODE_INSTEP; - } - element->box.y = y; - y += element->box.height; - } - } - } else { + + if (!node->expanded) { + node->data.box.x = node->box.x; + node->data.box.y = node->box.y; + continue; + } + + if (node->folder) { node->data.box.x = node->box.x; node->data.box.y = node->box.y; + tree_recalculate_node_positions(tree, node->child); + } else { + y = node->box.y; + has_icon = false; + for (element = &node->data; element != NULL; + element = element->next) + if (element->type == + NODE_ELEMENT_TEXT_PLUS_ICON) { + has_icon = true; + break; + } + + for (element = &node->data; element != NULL; + element = element->next) { + element->box.x = node->box.x; + if (element->type != + NODE_ELEMENT_TEXT_PLUS_ICON && + has_icon) + element->box.x += NODE_INSTEP; + element->box.y = y; + y += element->box.height; + } } + } } /** - * Calculates the width of a node including any children + * Recalculates the size of a node. * - * \param node the node to calculate the height of - * \return the total width of the node and children + * \param tree the tree to which node belongs, may be NULL + * \param node the node to update + * \param recalculate_sizes whether the node elements have changed */ -int tree_get_node_width(struct node *node) { - int width = 0; - int child_width; +static void tree_recalculate_node_sizes(struct tree *tree, struct node *node, + bool recalculate_sizes) +{ + struct node_element *element; + int width, height; - assert(node); + assert(node != NULL); - for (; node; node = node->next) { - if (width < (node->box.x + node->box.width)) - width = node->box.x + node->box.width; - if ((node->child) && (node->expanded)) { - child_width = tree_get_node_width(node->child); - if (width < child_width) - width = child_width; + width = node->box.width; + height = node->box.height; + node->box.width = 0; + node->box.height = 0; + if (node->expanded) { + for (element = &node->data; element != NULL; + element = element->next) { + if (recalculate_sizes) + tree_recalculate_node_element(tree, element); + node->box.width = (node->box.width > element->box.x + + element->box.width - node->box.x) ? + node->box.width : + element->box.width + element->box.x - + node->box.x; + node->box.height += element->box.height; } + } else { + if (recalculate_sizes) + for (element = &node->data; element != NULL; + element = element->next) + tree_recalculate_node_element(tree, element); + else + tree_recalculate_node_element(tree, &node->data); + node->box.width = node->data.box.width; + node->box.height = node->data.box.height; } - return width; + + if (tree != NULL && height != node->box.height) + tree_recalculate_node_positions(tree, tree->root); } /** - * Calculates the height of a node including any children + * Creates a folder node with the specified title, and optionally links it into + * the tree. * - * \param node the node to calculate the height of - * \return the total height of the node and children + * \param tree the owner tree of 'parent', may be NULL + * \param parent the parent node, or NULL not to link + * \param title the node title (not copied, used directly) + * \param editable if true, the node title will be editable + * \param retain_in_memory if true, the node will stay in memory after deletion + * \param deleted if true, the node is created with the deleted flag + * \return the newly created node. */ -int tree_get_node_height(struct node *node) { - int y1; +struct node *tree_create_folder_node(struct tree *tree, struct node *parent, + const char *title, bool editable, bool retain_in_memory, + bool deleted) +{ + struct node *node; - assert(node); + assert(title != NULL); - if ((node->child) && (node->expanded)) { - y1 = node->box.y; - if (y1 < 0) - y1 = 0; - node = node->child; - while ((node->next) || ((node->child) && (node->expanded))) { - for (; node->next; node = node->next); - if ((node->child) && (node->expanded)) - node = node->child; - } - return node->box.y + node->box.height - y1; - } else { - return node->box.height; + node = calloc(sizeof(struct node), 1); + if (node == NULL) { + LOG(("calloc failed")); + warn_user("NoMemory", 0); + return NULL; } + node->folder = true; + node->retain_in_memory = retain_in_memory; + node->deleted = deleted; + node->data.parent = node; + node->data.type = NODE_ELEMENT_TEXT; + node->data.text = title; + node->data.flag = TREE_ELEMENT_TITLE; + node->data.editable = editable; + node->sort = NULL; + node->user_callback = NULL; + + tree_recalculate_node_sizes(tree, node, true); + if (parent != NULL) + tree_link_node(tree, parent, node, false); + + return node; } /** - * Updates all siblinds and descendants of a node to an expansion state. - * No update is performed for the tree changes. + * Creates a leaf node with the specified title, and optionally links it into + * the tree. * - * \param node the node to set all siblings and descendants of - * \param expanded the expansion state to set + * \param tree the owner tree of 'parent', may be NULL + * \param parent the parent node, or NULL not to link + * \param title the node title (not copied, used directly) + * \param editable if true, the node title will be editable + * \param retain_in_memory if true, the node will stay in memory after deletion + * \param deleted if true, the node is created with the deleted flag + * \return the newly created node. */ -void tree_set_node_expanded(struct tree *tree, struct node *node, bool expanded) { - for (; node; node = node->next) { - if (node->expanded != expanded) { - node->expanded = expanded; - tree_recalculate_node(tree, node, false); - } - if ((node->child) && (node->expanded)) - tree_set_node_expanded(tree, node->child, expanded); +struct node *tree_create_leaf_node(struct tree *tree, struct node *parent, + const char *title, bool editable, bool retain_in_memory, + bool deleted) +{ + struct node *node; + + assert(title != NULL); + + node = calloc(sizeof(struct node), 1); + if (node == NULL) { + LOG(("calloc failed")); + warn_user("NoMemory", 0); + return NULL; } + + node->folder = false; + node->retain_in_memory = retain_in_memory; + node->deleted = deleted; + node->data.parent = node; + node->data.type = NODE_ELEMENT_TEXT; + node->data.text = title; + node->data.flag = TREE_ELEMENT_TITLE; + node->data.editable = editable; + node->sort = NULL; + node->user_callback = NULL; + + tree_recalculate_node_sizes(tree, node, true); + if (parent != NULL) + tree_link_node(tree, parent, node, false); + + return node; } /** - * Updates all siblinds and descendants of a node to an expansion state. + * Creates an empty text node element and links it to a node. * - * \param tree the tree to update - * \param node the node to set all siblings and descendants of - * \param expanded the expansion state to set - * \param folder whether to update folders - * \param leaf whether to update leaves - * \return whether any changes were made + * \param parent the parent node + * \param type the required element type + * \param flag user assigned flag used for searches + * \return the newly created element. */ -bool tree_handle_expansion(struct tree *tree, struct node *node, bool expanded, bool folder, - bool leaf) { - struct node *entry = node; - bool redraw = false; +struct node_element *tree_create_node_element(struct node *parent, + node_element_type type, unsigned int flag, bool editable) +{ + struct node_element *element; - for (; node; node = node->next) { - if ((node->expanded != expanded) && (node != tree->root) && - ((folder && (node->folder)) || (leaf && (!node->folder)))) { - node->expanded = expanded; - if (node->child) - tree_set_node_expanded(tree, node->child, false); - if ((node->data.next) && (node->data.next->box.height == 0)) - tree_recalculate_node(tree, node, true); - else - tree_recalculate_node(tree, node, false); - redraw = true; - } - if ((node->child) && (node->expanded)) - redraw |= tree_handle_expansion(tree, node->child, expanded, folder, leaf); - } - if ((entry == tree->root) && (redraw)) { - tree_recalculate_node_positions(tree, tree->root); - tree_redraw_area(tree, 0, 0, 16384, 16384); - tree_recalculate_size(tree); - } - return redraw; + element = calloc(sizeof(struct node_element), 1); + if (element == NULL) + return NULL; + + element->parent = parent; + element->flag = flag; + element->type = type; + element->editable = editable; + element->next = parent->data.next; + parent->data.next = element; + + return element; } /** - * Updates all siblinds and descendants of a node to an selected state. - * The required areas of the tree are redrawn. + * Inserts a node into the correct place according to the parent's sort function * - * \param tree the tree to update nodes for - * \param node the node to set all siblings and descendants of - * \param selected the selection state to set + * \param parent the node whose child node 'node' becomes + * \param node the node to be inserted */ -void tree_set_node_selected(struct tree *tree, struct node *node, bool selected) { - for (; node; node = node->next) { - if ((node->selected != selected) && (node != tree->root)) { - node->selected = selected; - tree_redraw_area(tree, node->box.x, node->box.y, node->box.width, - node->data.box.height); +static void tree_sort_insert(struct node *parent, struct node *node) +{ + struct node *after; + + assert(node != NULL); + assert(parent != NULL); + assert(parent->sort != NULL); + + after = parent->last_child; + while ((after != NULL) && + (parent->sort(node, after) == -1)) + after = after->previous; + + if (after != NULL) { + if (after->next != NULL) + after->next->previous = node; + node->next = after->next; + node->previous = after; + after->next = node; + } else { + node->previous = NULL; + node->next = parent->child; + if (parent->child != NULL) { + parent->child->previous = node; } - if ((node->child) && (node->expanded)) - tree_set_node_selected(tree, node->child, selected); + parent->child = node; } + + if (node->next == NULL) + parent->last_child = node; + + node->parent = parent; } /** - * Finds a node at a specific location. + * Recalculates the size of a tree. * - * \param root the root node to check from - * \param x the x co-ordinate - * \param y the y co-ordinate - * \param furniture whether the returned area was in an elements furniture - * \return the node at the specified position, or NULL for none + * \param tree the tree to recalculate */ -struct node *tree_get_node_at(struct node *root, int x, int y, bool *furniture) { - struct node_element *result; +static void tree_recalculate_size(struct tree *tree) +{ + int width, height; - if ((result = tree_get_node_element_at(root, x, y, furniture))) - return result->parent; - return NULL; -} + assert(tree != NULL); + + width = tree->width; + height = tree->height; + tree->width = tree_get_node_width(tree->root); + tree->height = tree_get_node_height(tree->root); + + if ((width != tree->width) || (height != tree->height)) + tree->callbacks->resized(tree, tree->width, tree->height, + tree->client_data); +} /** - * Finds a node element at a specific location. + * Recalculate the node data and redraw the relevant section of the tree. * - * \param node the root node to check from - * \param x the x co-ordinate - * \param y the y co-ordinate - * \param furniture whether the returned area was in an elements furniture - * \return the node at the specified position, or NULL for none + * \param tree the tree to redraw, may be NULL + * \param node the node to update + * \param recalculate_sizes whether the elements have changed + * \param expansion the request is the result of a node expansion */ -struct node_element *tree_get_node_element_at(struct node *node, int x, int y, - bool *furniture) { - struct node_element *element; +static void tree_handle_node_changed(struct tree *tree, struct node *node, + bool recalculate_sizes, bool expansion) +{ + int width, height, tree_height; - *furniture = false; - for (; node; node = node->next) { - if (node->box.y > y) return NULL; - if ((node->box.x - NODE_INSTEP < x) && (node->box.y < y) && - (node->box.x + node->box.width >= x) && - (node->box.y + node->box.height >= y)) { - if (node->expanded) { - for (element = &node->data; element; - element = element->next) { - if ((element->box.x < x) && (element->box.y < y) && - (element->box.x + element->box.width >= x) && - (element->box.y + element->box.height >= y)) - return element; - } - } else if ((node->data.box.x < x) && - (node->data.box.y < y) && - (node->data.box.x + node->data.box.width >= x) && - (node->data.box.y + node->data.box.height >= y)) - return &node->data; - if (((node->child) || (node->data.next)) && - (node->data.box.x - NODE_INSTEP + 8 < x) && - (node->data.box.y + 8 < y) && - (node->data.box.x > x) && - (node->data.box.y + 32 > y)) { - *furniture = true; - return &node->data; + assert(node != NULL); + + width = node->box.width; + height = node->box.height; + tree_height = tree->height; + + if ((recalculate_sizes) || (expansion)) { + tree_recalculate_node_sizes(tree, node, true); + } + + if (tree != NULL) { + if ((node->box.height != height) || (expansion)) { + tree_recalculate_node_positions(tree, tree->root); + tree_recalculate_size(tree); + tree_height = (tree_height > tree->height) ? + tree_height : tree->height; + if (tree->redraw) { + tree->callbacks->redraw_request(0, node->box.y, + tree->width, + tree_height - node->box.y, + tree->client_data); + } + } else { + width = (width > node->box.width) ? + width : node->box.width; + if (tree->redraw) + tree->callbacks->redraw_request(node->box.x, + node->box.y, + width, node->box.height, + tree->client_data); + if (recalculate_sizes) { + tree_recalculate_size(tree); } } - - if ((node->child) && (node->expanded) && - ((element = tree_get_node_element_at(node->child, x, y, - furniture)))) - return element; } - return NULL; } /** - * Finds a node element from a node with a specific user_type + * Links a node to another node. * - * \param node the node to examine - * \param user_type the user_type to check for - * \return the corresponding element + * \param tree the tree in which the link takes place, may be NULL + * \param link the node to link before/as a child (folders) + * or before/after (link) + * \param node the node to link + * \param before whether to link siblings before or after the supplied node */ -struct node_element *tree_find_element(struct node *node, node_element_data data) { - struct node_element *element; - for (element = &node->data; element; element = element->next) - if (element->data == data) return element; - return NULL; -} +void tree_link_node(struct tree *tree, struct node *link, struct node *node, + bool before) +{ + struct node *parent; + bool sort = false; -/** - * Moves nodes within a tree. - * - * \param tree the tree to process - * \param link the node to link before/as a child (folders) or before/after (link) - * \param before whether to link siblings before or after the supplied node - */ -void tree_move_selected_nodes(struct tree *tree, struct node *destination, bool before) { - struct node *link; - struct node *test; - bool error; + assert(link != NULL); + assert(node != NULL); - tree_clear_processing(tree->root); - tree_selected_to_processing(tree->root); + if ((link->folder == 0) || (before)) { + parent = node->parent = link->parent; + if (parent->sort) { + sort = true; + } else { + if (before) { + node->next = link; + node->previous = link->previous; + if (link->previous != NULL) + link->previous->next = node; + link->previous = node; + if ((parent != NULL) && (parent->child == link)) + parent->child = node; + } else { + node->previous = link; + node->next = link->next; + if (link->next != NULL) + link->next->previous = node; + link->next = node; + if ((parent != NULL) && + (parent->last_child == link)) + parent->last_child = node; + } + } + } else { + parent = node->parent = link; + if (parent->sort != NULL) { + sort = true; + } else { + node->next = NULL; + if (link->child == NULL) { + link->child = link->last_child = node; + node->previous = NULL; + } else { + link->last_child->next = node; + node->previous = link->last_child; + link->last_child = node; + } + } - /* the destination node cannot be a child of any node with the processing flag set */ - error = destination->processing; - for (test = destination; test; test = test->parent) - error |= test->processing; - if (error) { - tree_clear_processing(tree->root); - return; } - if ((destination->folder) && (!destination->expanded) && (!before)) { - destination->expanded = true; - tree_handle_node_changed(tree, destination, false, true); + + if (sort) { + tree_sort_insert(parent, node); } - link = tree_move_processing_node(tree->root, destination, before, true); - while (link) - link = tree_move_processing_node(tree->root, link, false, false); - tree_clear_processing(tree->root); - tree_recalculate_node_positions(tree, tree->root); - tree_redraw_area(tree, 0, 0, 16384, 16384); + tree_handle_node_changed(tree, link, false, true); + + node->deleted = false; } /** - * Sets the processing flag to the selection state. + * Recalculate the node element and redraw the relevant section of the tree. + * The tree size is not updated. * - * \param node the node to process siblings and children of + * \param tree the tree to redraw, may be NULL + * \param element the node element to update */ -void tree_selected_to_processing(struct node *node) { - for (; node; node = node->next) { - node->processing = node->selected; - if ((node->child) && (node->expanded)) - tree_selected_to_processing(node->child); - } -} +static void tree_handle_node_element_changed(struct tree *tree, + struct node_element *element) +{ + int width, height; + assert(element != NULL); -/** - * Clears the processing flag. - * - * \param node the node to process siblings and children of - */ -void tree_clear_processing(struct node *node) { - for (; node; node = node->next) { - node->processing = false; - if (node->child) - tree_clear_processing(node->child); + width = element->box.width; + height = element->box.height; + tree_recalculate_node_element(tree, element); + + if (element->box.height != height) { + tree_recalculate_node_sizes(tree, element->parent, false); + if ((tree != NULL) && (tree->redraw)) { + tree->callbacks->redraw_request(0, element->box.y, + tree->width + element->box.width - + width, + tree->height - element->box.y + + element->box.height - height, + tree->client_data); + } + } else { + if (element->box.width != width) { + tree_recalculate_node_sizes(tree, element->parent, + false); + } + + if (tree != NULL) { + width = (width > element->box.width) ? width : + element->box.width; + if (tree->redraw) { + tree->callbacks->redraw_request(element->box.x, + element->box.y, + width, + element->box.height, + tree->client_data); + } + } } } /** - * Moves the first node in a tree with the processing flag set. + * Stops editing a node_element * - * \param tree the node to move siblings/children of - * \param link the node to link before/as a child (folders) or before/after (link) - * \param before whether to link siblings before or after the supplied node - * \param first whether to always link after the supplied node (ie not inside of folders) - * \return the node moved + * \param tree The tree to stop editing for + * \param keep_changes If true the changes made to the text will be kept, + * if false they will be dropped */ -struct node *tree_move_processing_node(struct node *node, struct node *link, bool before, - bool first) { - struct node *result; +static void tree_stop_edit(struct tree *tree, bool keep_changes) +{ + int text_len; + char *text = NULL; + struct node_element *element; + struct node_msg_data msg_data; + node_callback_resp response; - bool folder = link->folder; - for (; node; node = node->next) { - if (node->processing) { - node->processing = false; - tree_delink_node(node); - if (!first) - link->folder = false; - tree_link_node(link, node, before); - if (!first) - link->folder = folder; - return node; - } - if (node->child) { - result = tree_move_processing_node(node->child, link, before, first); - if (result) - return result; + assert(tree != NULL); + + if (tree->editing == NULL || tree->textarea == NULL) + return; + + element = tree->editing; + + if (keep_changes) { + text_len = textarea_get_text(tree->textarea, NULL, 0); + text = malloc(text_len * sizeof(char)); + if (text == NULL) { + LOG(("malloc failed")); + warn_user("NoMemory", 0); + textarea_destroy(tree->textarea); + tree->textarea = NULL; + return; } + textarea_get_text(tree->textarea, text, text_len); } - return NULL; -} -/** - * Checks whether a node, its siblings or any children are selected. - * - * \param node the root node to check from - */ -bool tree_has_selection(struct node *node) { - for (; node; node = node->next) { - if (node->selected) - return true; - if ((node->child) && (node->expanded) && - (tree_has_selection(node->child))) - return true; + + if (keep_changes && element->parent->user_callback != NULL) { + msg_data.msg = NODE_ELEMENT_EDIT_FINISHING; + msg_data.flag = element->flag; + msg_data.node = element->parent; + msg_data.data.text = text; + response = element->parent->user_callback( + element->parent->callback_data, + &msg_data); + + switch (response) { + case NODE_CALLBACK_REJECT: + free(text); + text = NULL; + break; + case NODE_CALLBACK_CONTINUE: + free(text); + text = NULL; + return; + case NODE_CALLBACK_HANDLED: + case NODE_CALLBACK_NOT_HANDLED: + text = msg_data.data.text; + break; + } + } + + textarea_destroy(tree->textarea); + tree->textarea = NULL; + tree->editing = NULL; + + if (text != NULL) + tree_update_node_element(tree, element, text, NULL); + else + tree_handle_node_element_changed(tree, element); + + + tree_recalculate_size(tree); + if (element->parent->user_callback != NULL) { + msg_data.msg = NODE_ELEMENT_EDIT_FINISHED; + msg_data.flag = element->flag; + msg_data.node = element->parent; + element->parent->user_callback(element->parent->callback_data, + &msg_data); } - return false; } /** - * Updates the selected state for a region of nodes. + * Delinks a node from the tree structures. * - * \param tree the tree to update - * \param x the minimum x of the selection rectangle - * \param y the minimum y of the selection rectangle - * \param width the width of the selection rectangle - * \param height the height of the selection rectangle - * \param invert whether to invert the selected state + * \param tree the tree in which the delink takes place, may be NULL + * \param node the node to delink */ -void tree_handle_selection_area(struct tree *tree, int x, int y, int width, int height, - bool invert) { - assert(tree); - assert(tree->root); +void tree_delink_node(struct tree *tree, struct node *node) +{ + struct node *parent; - if (!tree->root->child) return; + assert(node != NULL); - if (width < 0) { - x += width; - width = -width; - } - if (height < 0) { - y += height; - height = -height; + /* do not remove the root */ + if (tree != NULL && node == tree->root) + return; + if ((tree != NULL) && (tree->editing != NULL)) { + parent = tree->editing->parent; + while (parent != NULL) { + if (node == parent) { + tree_stop_edit(tree, false); + break; + } + parent = parent->parent; + } } - tree_handle_selection_area_node(tree, tree->root->child, x, y, width, height, invert); + if (node->parent->child == node) + node->parent->child = node->next; + if (node->parent->last_child == node) + node->parent->last_child = node->previous; + parent = node->parent; + node->parent = NULL; + + if (node->previous != NULL) + node->previous->next = node->next; + if (node->next != NULL) + node->next->previous = node->previous; + node->previous = NULL; + node->next = NULL; + + tree_handle_node_changed(tree, parent, false, true); } /** - * Updates the selected state for a region of nodes. + * Deletes a node from the tree. * - * \param tree the tree to update - * \param node the node to update children and siblings of - * \param x the minimum x of the selection rectangle - * \param y the minimum y of the selection rectangle - * \param width the width of the selection rectangle - * \param height the height of the selection rectangle - * \param invert whether to invert the selected state + * \param tree the tree to delete from, may be NULL + * \param node the node to delete + * \param siblings whether to delete all siblings */ -void tree_handle_selection_area_node(struct tree *tree, struct node *node, int x, int y, - int width, int height, bool invert) { +static void tree_delete_node_internal(struct tree *tree, struct node *node, + bool siblings) +{ + struct node *next, *child, *parent; + struct node_element *e, *f; + node_callback_resp response; + struct node_msg_data msg_data; - struct node_element *element; - struct node *update; - int x_max, y_max; + assert(node != NULL); - assert(tree); - assert(node); + if (tree != NULL && tree->root == node) + return; - x_max = x + width; - y_max = y + height; + next = node->next; + parent = node->parent; + if (tree != NULL && parent == tree->root) + parent = NULL; + tree_delink_node(tree, node); + child = node->child; + node->child = NULL; - for (; node; node = node->next) { - if (node->box.y > y_max) return; - if ((node->box.x < x_max) && (node->box.y < y_max) && - (node->box.x + node->box.width + NODE_INSTEP >= x) && - (node->box.y + node->box.height >= y)) { - update = NULL; - if (node->expanded) { - for (element = &node->data; element; - element = element->next) { - if ((element->box.x < x_max) && (element->box.y < y_max) && - (element->box.x + element->box.width >= x) && - (element->box.y + element->box.height >= y)) { - update = element->parent; - break; - } + node->deleted = true; + if (child != NULL) + tree_delete_node_internal(tree, child, true); + + if (!node->retain_in_memory) { + node->retain_in_memory = true; + for (e = &node->data; e != NULL; e = f) { + if (e->text != NULL) { + response = NODE_CALLBACK_NOT_HANDLED; + if (!e->editable && + node->user_callback != NULL) { + msg_data.msg = NODE_DELETE_ELEMENT_TXT; + msg_data.flag = e->flag; + msg_data.node = node; + msg_data.data.text = (void *)e->text; + response = node->user_callback( + node->callback_data, + &msg_data); } - } else if ((node->data.box.x < x_max) && - (node->data.box.y < y_max) && - (node->data.box.x + node->data.box.width >= x) && - (node->data.box.y + node->data.box.height >= y)) - update = node->data.parent; - if ((update) && (node != tree->root)) { - if (invert) { - node->selected = !node->selected; - tree_handle_node_element_changed(tree, &node->data); - } else if (!node->selected) { - node->selected = true; - tree_handle_node_element_changed(tree, &node->data); + if (response != NODE_CALLBACK_HANDLED) + free((void *)e->text); + e->text = NULL; + } + if (e->bitmap != NULL) { + response = NODE_CALLBACK_NOT_HANDLED; + if (node->user_callback != NULL) { + msg_data.msg = NODE_DELETE_ELEMENT_IMG; + msg_data.flag = e->flag; + msg_data.node = node; + msg_data.data.bitmap = + (void *)e->bitmap; + response = node->user_callback( + node->callback_data, + &msg_data); } + /* TODO the type of this field is platform + dependent */ + if (response != NODE_CALLBACK_HANDLED) + free(e->bitmap); + e->bitmap = NULL; } + f = e->next; + if (e != &node->data) + free(e); } - if ((node->child) && (node->expanded)) - tree_handle_selection_area_node(tree, node->child, x, y, width, height, - invert); + free(node); } + + if (siblings && next) + tree_delete_node_internal(tree, next, true); + if ((tree->flags & TREE_DELETE_EMPTY_DIRS) && parent != NULL && + parent->child == NULL && !parent->deleted) + tree_delete_node_internal(tree, parent, false); } /** - * Redraws a tree. + * Deletes all nodes of a tree and the tree itself. * - * \param tree the tree to draw - * \param clip_x the minimum x of the clipping rectangle - * \param clip_y the minimum y of the clipping rectangle - * \param clip_width the width of the clipping rectangle - * \param clip_height the height of the clipping rectangle + * \param tree the tree to be deleted */ -void tree_draw(struct tree *tree, int clip_x, int clip_y, int clip_width, - int clip_height) { - assert(tree); - assert(tree->root); +void tree_delete(struct tree *tree) +{ + tree_set_redraw(tree, false); + if (tree->root->child != NULL) + tree_delete_node_internal(tree, tree->root->child, true); + + free((void *)tree->root->data.text); + free(tree->root); + free(tree); +} - if (!tree->root->child) return; - tree_initialise_redraw(tree); - tree_draw_node(tree, tree->root->child, clip_x, - clip_y, clip_width, clip_height); +/** + * Gets the redraw property of the given tree. + * + * \param tree the tree for which to retrieve the property + * \return the redraw property of the tree + */ +bool tree_get_redraw(struct tree *tree) +{ + return tree->redraw; } /** - * Redraws a node. + * Deletes a node from the tree. * - * \param tree the tree to draw - * \param node the node to draw children and siblings of - * \param clip_x the minimum x of the clipping rectangle - * \param clip_y the minimum y of the clipping rectangle - * \param clip_width the width of the clipping rectangle - * \param clip_height the height of the clipping rectangle + * \param tree the tree to delete from, may be NULL + * \param node the node to delete + * \param siblings whether to delete all siblings */ -void tree_draw_node(struct tree *tree, struct node *node, int clip_x, int clip_y, - int clip_width, int clip_height) { - - struct node_element *element; - int x_max, y_max; +void tree_delete_node(struct tree *tree, struct node *node, bool siblings) +{ + int y = node->box.y; + tree_delete_node_internal(tree, node, siblings); + tree_recalculate_node_positions(tree, tree->root); + if (tree->redraw) + tree->callbacks->redraw_request(0, y, tree->width, tree->height, + tree->client_data); + tree_recalculate_size(tree); +} - assert(tree); - assert(node); - x_max = clip_x + clip_width + NODE_INSTEP; - y_max = clip_y + clip_height; +/** + * Sets an icon for a node + * + * \param tree The tree to which node belongs, may be NULL + * \param node The node for which the icon is set + * \param icon the image to use + */ +void tree_set_node_icon(struct tree *tree, struct node *node, + hlcache_handle *icon) +{ + node->data.type = NODE_ELEMENT_TEXT_PLUS_ICON; + tree_update_node_element(tree, &(node->data), NULL, icon); +} - if ((node->parent->next) && (node->parent->next->box.y < clip_y)) - return; - for (; node; node = node->next) { - if (node->box.y > y_max) return; - if ((node->next) && (!tree->no_furniture)) - tree_draw_line(node->box.x - (NODE_INSTEP / 2), - node->box.y + (40 / 2), 0, - node->next->box.y - node->box.y); - if ((node->box.x < x_max) && (node->box.y < y_max) && - (node->box.x + node->box.width + NODE_INSTEP >= clip_x) && - (node->box.y + node->box.height >= clip_y)) { - if (!tree->no_furniture) { - if ((node->expanded) && (node->child)) - tree_draw_line(node->box.x + (NODE_INSTEP / 2), - node->data.box.y + node->data.box.height, 0, - (40 / 2)); - if ((node->parent) && (node->parent != tree->root) && - (node->parent->child == node)) - tree_draw_line(node->parent->box.x + (NODE_INSTEP / 2), - node->parent->data.box.y + - node->parent->data.box.height, 0, - (40 / 2)); - tree_draw_line(node->box.x - (NODE_INSTEP / 2), - node->data.box.y + - node->data.box.height - (40 / 2), - (NODE_INSTEP / 2) - 4, 0); - tree_draw_node_expansion(tree, node); - } - if (node->expanded) - for (element = &node->data; element; - element = element->next) - tree_draw_node_element(tree, element); - else - tree_draw_node_element(tree, &node->data); +/** + * Updates all siblings and descendants of a node to an expansion state. + * No update is performed for the tree changes. + * + * \param tree the tree to which 'node' belongs + * \param node the node to set all siblings and descendants of + * \param expanded the expansion state to set + */ +static void tree_set_node_expanded_all(struct tree *tree, struct node *node, + bool expanded) +{ + for (; node != NULL; node = node->next) { + if (node->expanded != expanded) { + node->expanded = expanded; + tree_recalculate_node_sizes(tree, node, false); } - if ((node->child) && (node->expanded)) - tree_draw_node(tree, node->child, clip_x, clip_y, clip_width, - clip_height); + if ((node->child != NULL) && (node->expanded)) + tree_set_node_expanded_all(tree, node->child, expanded); } } /** - * Gets link characteristics to insert a node at a specified position. + * Updates [all siblings and descendants of] a node to an expansion state. * - * \param tree the tree to find link information for - * \param x the x co-ordinate - * \param y the y co-ordinate - * \param before set to whether the node should be linked before on exit - * \return the node to link with + * \param tree the tree to update + * \param node the node to set [all siblings and descendants of] + * \param expanded the expansion state to set + * \param folder whether to update folders, if this together with leaf + * will be false only 'node' will be updated + * \param leaf whether to update leaves (check also description for folder) + * \return whether any changes were made */ -struct node *tree_get_link_details(struct tree *tree, int x, int y, bool *before) { - struct node *node = NULL; - bool furniture; - - assert(tree); - assert(tree->root); +static bool tree_set_node_expanded_internal(struct tree *tree, + struct node *node, bool expanded, bool folder, bool leaf) +{ + bool redraw = false; + struct node *end = (folder == false && leaf == false) ? + node->next : NULL; - *before = false; - if (tree->root->child) - node = tree_get_node_at(tree->root->child, x, y, &furniture); - if ((!node) || (furniture)) - return tree->root; + if (tree->editing != NULL && node == tree->editing->parent) + tree_stop_edit(tree, false); - if (y < (node->box.y + (node->box.height / 2))) { - *before = true; - } else if ((node->folder) && (node->expanded) && (node->child)) { - node = node->child; - *before = true; + for (; node != end; node = node->next) { + if ((node->expanded != expanded) && (node != tree->root) && + ((folder && (node->folder)) || + (leaf && (!node->folder)) || + (!folder && !leaf))) { + node->expanded = expanded; + if (node->child != NULL) + tree_set_node_expanded_all(tree, + node->child, false); + if ((node->data.next != NULL) && + (node->data.next->box.height == 0)) + tree_recalculate_node_sizes(tree, node, true); + else + tree_recalculate_node_sizes(tree, node, false); + redraw = true; + } + if ((folder || leaf) && (node->child != NULL) && + (node->expanded)) + redraw |= tree_set_node_expanded_internal(tree, + node->child, expanded, folder, leaf); } - return node; + return redraw; } /** - * Links a node into the tree. + * Updates [all siblings and descendants of] a node to an expansion state. * - * \param link the node to link before/as a child (folders) or before/after (link) - * \param node the node to link - * \param before whether to link siblings before or after the supplied node + * \param tree the tree to update + * \param node the node to set [all siblings and descendants of] + * \param expanded the expansion state to set + * \param folder whether to update folders, if this together with leaf + * will be false only 'node' will be updated + * \param leaf whether to update leaves (check also description for folder) */ -void tree_link_node(struct node *link, struct node *node, bool before) { - assert(link); - assert(node); - - if ((!link->folder) || (before)) { - node->parent = link->parent; - if (before) { - node->next = link; - node->previous = link->previous; - if (link->previous) link->previous->next = node; - link->previous = node; - if ((link->parent) && (link->parent->child == link)) - link->parent->child = node; - } else { - node->previous = link; - node->next = link->next; - if (link->next) link->next->previous = node; - link->next = node; - } - } else { - if (!link->child) { - link->child = link->last_child = node; - node->previous = NULL; - } else { - link->last_child->next = node; - node->previous = link->last_child; - link->last_child = node; - } - node->parent = link; - node->next = NULL; - } - node->deleted = false; +void tree_set_node_expanded(struct tree *tree, struct node *node, bool expanded, + bool folder, bool leaf) +{ + if (tree_set_node_expanded_internal(tree, node, expanded, folder, leaf)) + tree_handle_node_changed(tree, node, false, true); } /** - * Delinks a node from the tree. + * Updates a node to an selected state. The required areas of the tree are + * redrawn. * - * \param node the node to delink + * \param tree the tree to update nodes for, may be NULL + * \param node the node to set all siblings and descendants of + * \param all if true update node together with its siblings and + * descendants + * \param selected the selection state to set */ -void tree_delink_node(struct node *node) { - assert(node); - - if (node->parent) { - if (node->parent->child == node) - node->parent->child = node->next; - if (node->parent->last_child == node) - node->parent->last_child = node->previous; - if (node->parent->child == NULL) { - /* don't contract top-level node */ - if (node->parent->parent) - node->parent->expanded = false; +void tree_set_node_selected(struct tree *tree, struct node *node, bool all, + bool selected) +{ + struct node *end; + + if (tree != NULL && node == tree->root) + node = tree->root->child; + if (node == NULL) + return; + + end = all ? NULL : node->next; + + for (; node != end; node = node->next) { + if (node->selected != selected) { + node->selected = selected; + if (tree != NULL && tree->redraw) + tree->callbacks->redraw_request(node->box.x, + node->box.y, + node->box.width, + node->data.box.height, + tree->client_data); } - node->parent = NULL; + if (all && (node->child != NULL) && (node->expanded)) + tree_set_node_selected(tree, node->child, all, + selected); } - if (node->previous) - node->previous->next = node->next; - if (node->next) - node->next->previous = node->previous; - node->previous = NULL; - node->next = NULL; } /** - * Deletes all selected node from the tree. + * Sets the sort function for a node * - * \param tree the tree to delete from - * \param node the node to delete + * \param tree the tree to which 'node' belongs, may be NULL + * \param node the node to be inserted + * \param sort pointer to the sorting function */ -void tree_delete_selected_nodes(struct tree *tree, struct node *node) { - struct node *next; +void tree_set_node_sort_function(struct tree *tree, struct node *node, + int (*sort) (struct node *, struct node *)) +{ + struct node *child; + + node->sort = sort; + + if (tree != NULL && tree->editing != NULL) + tree_stop_edit(tree, false); + + /* the node had already some children so they must get sorted */ + if (node->child != NULL) { + + child = node->child; + node->child = NULL; + + while (child != NULL) { + tree_sort_insert(node, child); + child = child->next; + } - while (node) { - next = node->next; - if ((node->selected) && (node != tree->root)) - tree_delete_node(tree, node, false); - else if (node->child) - tree_delete_selected_nodes(tree, node->child); - node = next; } + + if (tree != NULL) + tree_recalculate_node_positions(tree, node->child); } /** - * Deletes a node from the tree. + * Sets the delete callback for a node. * - * \param tree the tree to delete from - * \param node the node to delete - * \param siblings whether to delete all siblings + * \param node the node for which the callback is set + * \param callback the callback functions to be set + * \param data user data to be passed to callback */ -void tree_delete_node(struct tree *tree, struct node *node, bool siblings) { - tree_delete_node_internal(tree, node, siblings); - if (tree->root) - tree_recalculate_node_positions(tree, tree->root); - tree_redraw_area(tree, 0, 0, 16384, 16384); /* \todo correct area */ - tree_recalculate_size(tree); +void tree_set_node_user_callback(struct node *node, + tree_node_user_callback callback, void *data) +{ + node->user_callback = callback; + node->callback_data = data; } /** - * Deletes a node from the tree. + * Sets the redraw property to the given value. If redraw is true, the tree will + * be redrawn on layout/appearance changes. * - * \param tree the tree to delete from - * \param node the node to delete - * \param siblings whether to delete all siblings + * \param tree the tree for which the property is set + * \param redraw the value to set */ -void tree_delete_node_internal(struct tree *tree, struct node *node, bool siblings) { - struct node *next, *child; - struct node_element *e, *f, *domain, *path; - const char *domain_t, *path_t, *name_t; - char *space; +void tree_set_redraw(struct tree *tree, bool redraw) +{ + /* the tree might have no graphical representation, do not set the + redraw flag in such case */ + if (tree->callbacks == NULL) + return; + tree->redraw = redraw; +} - assert(node); - if (tree->temp_selection == node) - tree->temp_selection = NULL; - if (tree->root == node) - tree->root = NULL; +/** + * Checks whether a node, its siblings or any children are selected. + * + * \param node the root node to check from + * \return whether 'node', its siblings or any children are selected. + */ +bool tree_node_has_selection(struct node *node) +{ + for (; node != NULL; node = node->next) { + if (node->selected) + return true; + if ((node->child != NULL) && (node->expanded) && + (tree_node_has_selection(node->child))) + return true; + } + return false; +} - next = node->next; - tree_delink_node(node); - child = node->child; - node->child = NULL; - if (child) - tree_delete_node_internal(tree, child, true); - if (!node->retain_in_memory) { - node->retain_in_memory = true; - for (e = &node->data; e; e = f) { - if (e->text) { - /* we don't free non-editable titles or URLs */ - if ((node->editable) || (node->folder)) - free((void *)e->text); - else { - /* only reset non-deleted items */ - if (!node->deleted) { - if (e->data == TREE_ELEMENT_URL) { - /* reset URL characteristics */ - urldb_reset_url_visit_data(e->text); - } else if (e->data == TREE_ELEMENT_NAME) { - /* get the rest of the cookie data */ - domain = tree_find_element(node, - TREE_ELEMENT_DOMAIN); - path = tree_find_element(node, - TREE_ELEMENT_PATH); - if (domain && path) { - domain_t = domain->text + - strlen(messages_get( - "TreeDomain")) - 4; - space = strchr(domain_t, ' '); - if (space) - *space = '\0'; - path_t = path->text + - strlen(messages_get( - "TreePath")) - 4; - space = strchr(path_t, ' '); - if (space) - *space = '\0'; - name_t = e->text; - urldb_delete_cookie( - domain_t, - path_t, - name_t); - } - } - } +/** + * Returns the current value of the nodes deleted property. + * + * \param node the node to be checked + * \return the current value of the nodes deleted property + */ +bool tree_node_is_deleted(struct node *node) +{ + return node->deleted; +} - if (e->data != TREE_ELEMENT_TITLE && - e->data != TREE_ELEMENT_URL) { - free((void *)e->text); - e->text = NULL; - } - } - } - if (e->sprite) { - /* TODO the type of this field is platform dependent */ - free(e->sprite); /* \todo platform specific bits */ - e->sprite = NULL; - } - f = e->next; - if (e != &node->data) - free(e); - } - free(node); + +/** + * Returns true if the node is a folder + * + * \param node the node to be checked + * \return true if the node is a folder, false otherwise + */ +bool tree_node_is_folder(struct node *node) +{ + return node->folder; +} + + +/** + * Update the text of a node element if it has changed. + * + * \param element The node element to update. + * \param text The text to update the element with. The ownership of + * this string is taken by this function and must not be + * referred to after the function exits. + */ +bool tree_update_element_text(struct tree *tree, + struct node_element *element, char *text) +{ + const char *node_text; /* existing node text */ + + if (text == NULL) + return false; + + if (element == NULL) { + free(text); + return false; + } + + node_text = tree_node_element_get_text(element); + + if ((node_text == NULL) || (strcmp(node_text, text) != 0)) { + tree_update_node_element(tree, element, text, NULL); } else { - node->deleted = true; + /* text does not need changing, free it */ + free(text); } - if (siblings && next) - tree_delete_node_internal(tree, next, true); + return true; } + /** - * Creates a folder node with the specified title, and links it into the tree. + * Updates the content of a node_element. * - * \param parent the parent node, or NULL not to link - * \param title the node title (copied) - * \return the newly created node. + * \param tree the tree owning element, may be NULL + * \param element the element to be updated + * \param text new text to be set, may be NULL + * \param bitmap new bitmap to be set, may be NULL */ -struct node *tree_create_folder_node(struct node *parent, const char *title) { - struct node *node; +void tree_update_node_element(struct tree *tree, struct node_element *element, + const char *text, void *bitmap) +{ + node_callback_resp response; + struct node_msg_data msg_data; + + assert(element != NULL); + + if (tree != NULL && element == tree->editing) + tree_stop_edit(tree, false); + + if (text != NULL && (element->type == NODE_ELEMENT_TEXT || + element->type == NODE_ELEMENT_TEXT_PLUS_ICON)) { + if (element->text != NULL) { + response = NODE_CALLBACK_NOT_HANDLED; + if (!element->editable && + element->parent->user_callback != + NULL) { + msg_data.msg = NODE_DELETE_ELEMENT_TXT; + msg_data.flag = element->flag; + msg_data.node = element->parent; + msg_data.data.text = (void *)element->text; + response = element->parent->user_callback( + element->parent->callback_data, + &msg_data); + } + if (response != NODE_CALLBACK_HANDLED) + free((void *)element->text); + } + element->text = text; + } - assert(title); + if (bitmap != NULL && (element->type == NODE_ELEMENT_BITMAP || + element->type == NODE_ELEMENT_TEXT_PLUS_ICON)) { + if (element->bitmap != NULL) { + response = NODE_CALLBACK_NOT_HANDLED; + if (element->parent->user_callback != NULL) { + msg_data.msg = NODE_DELETE_ELEMENT_IMG; + msg_data.flag = element->flag; + msg_data.node = element->parent; + msg_data.data.bitmap = (void *)element->bitmap; + response = element->parent->user_callback( + element->parent->callback_data, + &msg_data); + } + if (response != NODE_CALLBACK_HANDLED) + free(element->bitmap); + } + element->bitmap = bitmap; + } - node = calloc(sizeof(struct node), 1); - if (!node) return NULL; - node->editable = true; - node->folder = true; - node->data.parent = node; - node->data.type = NODE_ELEMENT_TEXT; - node->data.text = squash_whitespace(title); - node->data.data = TREE_ELEMENT_TITLE; - tree_set_node_sprite_folder(node); - if (parent) - tree_link_node(parent, node, false); - return node; + tree_handle_node_element_changed(tree, element); } /** - * Creates a leaf node with the specified title, and links it into the tree. + * Returns the node element's text * - * \param parent the parent node, or NULL not to link - * \param title the node title (copied) - * \return the newly created node. + * \return the node element's text */ -struct node *tree_create_leaf_node(struct node *parent, const char *title) { - struct node *node; +const char *tree_node_element_get_text(struct node_element *element) +{ + return element->text; +} - assert(title); - node = calloc(sizeof(struct node), 1); - if (!node) return NULL; - node->folder = false; - node->data.parent = node; - node->data.type = NODE_ELEMENT_TEXT; - node->data.text = strdup(squash_whitespace(title)); - node->data.data = TREE_ELEMENT_TITLE; - node->editable = true; - if (parent) - tree_link_node(parent, node, false); - return node; +/** + * Get the root node of a tree + * + * \param tree the tree to get the root of + * \return the root of the tree + */ +struct node *tree_get_root(struct tree *tree) +{ + return tree->root; } /** - * Creates a leaf node with the specified title, and links it into the tree. + * Returns whether the current tree is being edited at this time * - * \param parent the parent node, or NULL not to link - * \param title the node title - * \return the newly created node. + * \param tree the tree to be checked + * \return true if the tree is currently being edited */ -struct node *tree_create_leaf_node_shared(struct node *parent, const char *title) { - struct node *node; +bool tree_is_edited(struct tree *tree) +{ + return tree->editing == NULL ? false : true; +} - assert(title); - node = calloc(sizeof(struct node), 1); - if (!node) return NULL; - node->folder = false; - node->data.parent = node; - node->data.type = NODE_ELEMENT_TEXT; - node->data.text = title; - node->data.data = TREE_ELEMENT_TITLE; - node->editable = false; - if (parent) - tree_link_node(parent, node, false); - return node; +/** + * Returns the first child of a node + * + * \param node the node to get the child of + * \return the nodes first child + */ +struct node *tree_node_get_child(struct node *node) +{ + return node->child; } /** - * Creates a tree entry for a URL, and links it into the tree + * Returns the closest sibling a node * - * - * \param parent the node to link to - * \param url the URL (copied) - * \param data the URL data to use - * \param title the custom title to use - * \return the node created, or NULL for failure + * \param node the node to get the sibling of + * \return the nodes sibling */ -struct node *tree_create_URL_node(struct node *parent, - const char *url, const struct url_data *data, - const char *title) { - struct node *node; - struct node_element *element; +struct node *tree_node_get_next(struct node *node) +{ + return node->next; +} - assert(data); - node = tree_create_leaf_node(parent, title ? title : url); - if (!node) - return NULL; +/** + * Draws an elements expansion icon + * + * \param tree the tree to draw the expansion for + * \param element the element to draw the expansion for + * \param tree_x X coordinate of the tree + * \param tree_y Y coordinate of the tree + */ +static void tree_draw_node_expansion(struct tree *tree, struct node *node, + int tree_x, int tree_y) +{ + int x, y; + + assert(tree != NULL); + assert(node != NULL); + + if ((node->child != NULL) || (node->data.next != NULL)) { + x = tree_x + node->box.x - (NODE_INSTEP / 2) - 4; + y = tree_y + node->box.y - (TREE_TEXT_HEIGHT / 2) + 16; + plot.rectangle(x, y, x + 9, y + 9, plot_style_fill_white); + plot.rectangle(x , y, x + 8, y + 8, + plot_style_stroke_darkwbasec); + plot.line(x + 2, y + 4, x + 7, y + 4, + plot_style_stroke_darkwbasec); + if (!node->expanded) + plot.line(x + 4, y + 2, x + 4, y + 7, + plot_style_stroke_darkwbasec); - element = tree_create_node_element(node, TREE_ELEMENT_THUMBNAIL); - if (element) - element->type = NODE_ELEMENT_THUMBNAIL; - tree_create_node_element(node, TREE_ELEMENT_VISITS); - tree_create_node_element(node, TREE_ELEMENT_LAST_VISIT); - element = tree_create_node_element(node, TREE_ELEMENT_URL); - if (element) - element->text = strdup(url); + } - tree_update_URL_node(node, url, NULL); - return node; } /** - * Creates a tree entry for a URL, and links it into the tree. + * Draws an element, including any expansion icons * - * All information is used directly from the url_data, and as such cannot be - * edited and should never be freed. - * - * \param parent the node to link to - * \param url the URL - * \param data the URL data to use - * \return the node created, or NULL for failure + * \param tree the tree to draw an element for + * \param element the element to draw + * \param tree_x X coordinate of the tree + * \param tree_y Y coordinate of the tree */ -struct node *tree_create_URL_node_shared(struct node *parent, - const char *url, const struct url_data *data) { - struct node *node; - struct node_element *element; - const char *title; +static void tree_draw_node_element(struct tree *tree, + struct node_element *element, int tree_x, int tree_y) +{ + + struct bitmap *bitmap = NULL; + int x, y, width; + bool selected = false; + hlcache_handle *icon; + plot_font_style_t *fstyle; + + assert(tree != NULL); + assert(element != NULL); + assert(element->parent != NULL); + + x = tree_x + element->box.x; + y = tree_y + element->box.y; + width = element->box.width; + if (&element->parent->data == element) + if (element->parent->selected) + selected = true; + + switch (element->type) { + case NODE_ELEMENT_TEXT_PLUS_ICON: + icon = element->bitmap; + if (icon != NULL && + (content_get_status(icon) == + CONTENT_STATUS_READY || + content_get_status(icon) == + CONTENT_STATUS_DONE)) { + content_redraw(icon , x, y + 3, + TREE_ICON_SIZE, TREE_ICON_SIZE, + x, y, x + TREE_ICON_SIZE, + y + TREE_ICON_SIZE, 1, 0); + } - assert(url && data); + x += NODE_INSTEP; + width -= NODE_INSTEP; - if (data->title) - title = data->title; - else - title = url; - node = tree_create_leaf_node_shared(parent, title); - if (!node) - return NULL; + /* fall through */ + case NODE_ELEMENT_TEXT: + if (element->text == NULL) + break; - element = tree_create_node_element(node, TREE_ELEMENT_THUMBNAIL); - if (element) - element->type = NODE_ELEMENT_THUMBNAIL; - tree_create_node_element(node, TREE_ELEMENT_VISITS); - tree_create_node_element(node, TREE_ELEMENT_LAST_VISIT); - element = tree_create_node_element(node, TREE_ELEMENT_URL); - if (element) - element->text = url; + if (element == tree->editing) + return; + + if (selected) { + fstyle = &plot_fstyle_selected; + plot.rectangle(x, y, x + width, + y + element->box.height, + plot_style_fill_black); + } else { + fstyle = &plot_fstyle; + plot.rectangle(x, y, x + width, + y + element->box.height, + plot_style_fill_white); + } + + plot.text(x + 4, y + TREE_TEXT_HEIGHT * 0.75, + element->text, strlen(element->text), + fstyle); + break; + case NODE_ELEMENT_BITMAP: + bitmap = element->bitmap; + if (bitmap == NULL) + break; + plot.bitmap(x, y, element->box.width - 1, + element->box.height - 2, + bitmap, 0xFFFFFF, BITMAPF_NONE); + if (!(tree->flags & TREE_NO_FURNITURE)) + plot.rectangle(x, y, x + element->box.width - 1, + y + element->box.height - 3, + plot_style_stroke_darkwbasec); + + break; + } - tree_update_URL_node(node, url, data); - return node; } /** - * Creates a tree entry for a cookie, and links it into the tree. - * - * All information is copied from the cookie_data, and as such can - * be edited and should be freed. + * Redraws a node. * - * \param parent the node to link to - * \param url the URL - * \param data the cookie data to use - * \return the node created, or NULL for failure + * \param tree the tree to draw + * \param node the node to draw children and siblings of + * \param tree_x X coordinate of the tree + * \param tree_y Y coordinate of the tree + * \param clip_x the minimum x of the clipping rectangle + * \param clip_y the minimum y of the clipping rectangle + * \param clip_width the width of the clipping rectangle + * \param clip_height the height of the clipping rectangle */ -struct node *tree_create_cookie_node(struct node *parent, - const struct cookie_data *data) { - struct node *node; +static void tree_draw_node(struct tree *tree, struct node *node, + int tree_x, int tree_y, + int clip_x, int clip_y, + int clip_width, int clip_height) +{ struct node_element *element; - char buffer[256]; - char buffer2[16]; + struct node *parent; + int x_max, y_max; + int x0, y0, x1, y1; - node = tree_create_leaf_node(parent, data->name); - if (!node) - return NULL; - node->data.data = TREE_ELEMENT_NAME; - node->editable = false; + assert(tree != NULL); + assert(node != NULL); - element = tree_create_node_element(node, TREE_ELEMENT_PERSISTENT); - if (element) { - snprintf(buffer, 256, messages_get("TreePersistent"), - data->no_destroy ? messages_get("Yes") : messages_get("No")); - element->text = strdup(buffer); - } - element = tree_create_node_element(node, TREE_ELEMENT_VERSION); - if (element) { - snprintf(buffer2, 16, "TreeVersion%i", data->version); - snprintf(buffer, 256, messages_get("TreeVersion"), messages_get(buffer2)); - element->text = strdup(buffer); - } - element = tree_create_node_element(node, TREE_ELEMENT_SECURE); - if (element) { - snprintf(buffer, 256, messages_get("TreeSecure"), - data->secure ? messages_get("Yes") : messages_get("No")); - element->text = strdup(buffer); - } - element = tree_create_node_element(node, TREE_ELEMENT_LAST_USED); - if (element) { - snprintf(buffer, 256, messages_get("TreeLastUsed"), - (data->last_used > 0) ? - ctime(&data->last_used) : messages_get("TreeUnknown")); - if (data->last_used > 0) - buffer[strlen(buffer) - 1] = '\0'; - element->text = strdup(buffer); - } - element = tree_create_node_element(node, TREE_ELEMENT_EXPIRES); - if (element) { - snprintf(buffer, 256, messages_get("TreeExpires"), - (data->expires > 0) - ? (data->expires == 1) - ? messages_get("TreeSession") - : ctime(&data->expires) - : messages_get("TreeUnknown")); - if (data->expires > 0 && data->expires != 1) - buffer[strlen(buffer) - 1] = '\0'; - element->text = strdup(buffer); - } - element = tree_create_node_element(node, TREE_ELEMENT_PATH); - if (element) { - snprintf(buffer, 256, messages_get("TreePath"), data->path, - data->path_from_set ? messages_get("TreeHeaders") : ""); - element->text = strdup(buffer); - } - element = tree_create_node_element(node, TREE_ELEMENT_DOMAIN); - if (element) { - snprintf(buffer, 256, messages_get("TreeDomain"), data->domain, - data->domain_from_set ? messages_get("TreeHeaders") : ""); - element->text = strdup(buffer); - } - if ((data->comment) && (strcmp(data->comment, ""))) { - element = tree_create_node_element(node, TREE_ELEMENT_COMMENT); - if (element) { - snprintf(buffer, 256, messages_get("TreeComment"), data->comment); - element->text = strdup(buffer); + x_max = clip_x + clip_width + NODE_INSTEP; + y_max = clip_y + clip_height; + + if ((node->parent->next != NULL) && + (node->parent->next->box.y < clip_y)) + return; + + for (; node != NULL; node = node->next) { + if (node->box.y > y_max) return; + if ((node->next != NULL) && + (!(tree->flags & TREE_NO_FURNITURE))) { + x0 = x1 = tree_x + node->box.x - (NODE_INSTEP / 2); + y0 = tree_y + node->box.y + (20 / 2); + y1 = y0 + node->next->box.y - node->box.y; + plot.line(x0, y0, x1, y1, plot_style_stroke_darkwbasec); } + if ((node->box.x < x_max) && (node->box.y < y_max) && + (node->box.x + node->box.width + + NODE_INSTEP >= clip_x) && + (node->box.y + node->box.height >= clip_y)) { + if (!(tree->flags & TREE_NO_FURNITURE)) { + if ((node->expanded) && (node->child != NULL)) { + x0 = x1 = tree_x + node->box.x + + (NODE_INSTEP / 2); + y0 = tree_y + node->data.box.y + + node->data.box.height; + y1 = y0 + (20 / 2); + plot.line(x0, y0, x1, y1, + plot_style_stroke_darkwbasec); + + } + parent = node->parent; + if ((parent != NULL) && + (parent != tree->root) && + (parent->child == node)) { + x0 = x1 = tree_x + parent->box.x + + (NODE_INSTEP / 2); + y0 = tree_y + parent->data.box.y + + parent->data.box.height; + y1 = y0 + (20 / 2); + plot.line(x0, y0, x1, y1, + plot_style_stroke_darkwbasec); + } + x0 = tree_x + node->box.x - (NODE_INSTEP / 2); + x1 = x0 + (NODE_INSTEP / 2) - 2; + y0 = y1 = tree_y + node->data.box.y + + node->data.box.height - + (20 / 2); + plot.line(x0, y0, x1, y1, + plot_style_stroke_darkwbasec); + tree_draw_node_expansion(tree, node, + tree_x, tree_y); + } + if (node->expanded) + for (element = &node->data; element != NULL; + element = element->next) + tree_draw_node_element(tree, element, + tree_x, tree_y); + else + tree_draw_node_element(tree, &node->data, + tree_x, tree_y); + } + if ((node->child != NULL) && (node->expanded)) + tree_draw_node(tree, node->child, tree_x, tree_y, + clip_x, clip_y, + clip_width, clip_height); } - element = tree_create_node_element(node, TREE_ELEMENT_VALUE); - if (element) { - snprintf(buffer, 256, messages_get("TreeValue"), - data->value ? data->value : messages_get("TreeUnused")); - element->text = strdup(buffer); - } +} - tree_set_node_sprite(node, "small_xxx", "small_xxx"); - return node; + +/** + * Redraws a tree. + * + * \param tree the tree to draw + * \param x X coordinate to draw the tree at + * \param y Y coordinate to draw the tree at + * \param clip_x the minimum x of the clipping rectangle relative to + * the tree origin + * \param clip_y the minimum y of the clipping rectangle relative to + * the tree origin + * \param clip_width the width of the clipping rectangle + * \param clip_height the height of the clipping rectangle + */ +void tree_draw(struct tree *tree, int x, int y, + int clip_x, int clip_y, int clip_width, int clip_height) +{ + int absolute_x, absolute_y; + assert(tree != NULL); + assert(tree->root != NULL); + + /* don't draw empty trees or trees with redraw flag set to false */ + if (tree->root->child == NULL || !tree->redraw) return; + + absolute_x = x + clip_x; + absolute_y = y + clip_y; + plot.rectangle(absolute_x, absolute_y, + absolute_x + clip_width, absolute_y + clip_height, + plot_style_fill_white); + plot.clip(absolute_x, absolute_y, + absolute_x + clip_width, absolute_y + clip_height); + tree_draw_node(tree, tree->root->child, x, y, clip_x, + clip_y, clip_width, clip_height); + if (tree->editing != NULL) { + x = x + tree->editing->box.x; + y = y + tree->editing->box.y; + if (tree->editing->type == NODE_ELEMENT_TEXT_PLUS_ICON) + x += NODE_INSTEP; + textarea_redraw(tree->textarea, x, y, absolute_x, absolute_y, + absolute_x + clip_width, + absolute_y + clip_height); + } } /** - * Creates an empty text node element and links it to a node. + * Finds a node element from a node with a specific user_type * - * \param parent the parent node - * \param user_type the required user_type - * \return the newly created element. + * \param node the node to examine + * \param flag user assinged flag used is searches + * \param after if this is not NULL the search will start after the given + * node_element + * \return the corresponding element */ -struct node_element *tree_create_node_element(struct node *parent, node_element_data data) { +struct node_element *tree_node_find_element(struct node *node, + unsigned int flag, struct node_element *after) +{ struct node_element *element; - element = calloc(sizeof(struct node_element), 1); - if (!element) return NULL; - element->parent = parent; - element->data = data; - element->type = NODE_ELEMENT_TEXT; - element->next = parent->data.next; - parent->data.next = element; - return element; + if (after == NULL) + element = &node->data; + else { + assert(after->parent == node); + element = after->next; + } + + for (; element != NULL; element = element->next) + if (element->flag == flag) return element; + + return NULL; } /** - * Recalculates the size of a tree. + * Deletes all selected nodes from the tree. * - * \param tree the tree to recalculate + * \param tree the tree to delete from + * \param node the node to delete */ -void tree_recalculate_size(struct tree *tree) { - int width, height; - - assert(tree); +void tree_delete_selected_nodes(struct tree *tree, struct node *node) +{ + struct node *next; - if (!tree->handle) + if (node == tree->root) { + if (node->child != NULL) + tree_delete_selected_nodes(tree, node->child); return; - width = tree->width; - height = tree->height; - if (tree->root) { - tree->width = tree_get_node_width(tree->root); - tree->height = tree_get_node_height(tree->root); - } else { - tree->width = 0; - tree->height = 0; } - if ((width != tree->width) || (height != tree->height)) - tree_resized(tree); + + while (node != NULL) { + next = node->next; + if (node->selected) + tree_delete_node(tree, node, false); + else if (node->child != NULL) + tree_delete_selected_nodes(tree, node->child); + node = next; + } } @@ -1299,22 +1774,23 @@ void tree_recalculate_size(struct tree *tree) { * Returns the selected node, or NULL if multiple nodes are selected. * * \param node the node to search sibling and children - * \return the selected node, or NULL if multiple nodes are selected + * \return the selected node, or NULL if multiple nodes are selected */ -struct node *tree_get_selected_node(struct node *node) { +struct node *tree_get_selected_node(struct node *node) +{ struct node *result = NULL; struct node *temp; - for (; node; node = node->next) { + for (; node != NULL; node = node->next) { if (node->selected) { - if (result) + if (result != NULL) return NULL; result = node; } - if ((node->child) && (node->expanded)) { + if ((node->child != NULL) && (node->expanded)) { temp = tree_get_selected_node(node->child); - if (temp) { - if (result) + if (temp != NULL) { + if (result != NULL) return NULL; else result = temp; @@ -1323,3 +1799,809 @@ struct node *tree_get_selected_node(struct node *node) { } return result; } + + +/** + * Finds a node element at a specific location. + * + * \param node the root node to check from + * \param x the x co-ordinate + * \param y the y co-ordinate + * \param furniture whether the returned area was in an elements furniture + * \return the node at the specified position, or NULL for none + */ +static struct node_element *tree_get_node_element_at(struct node *node, + int x, int y, bool *furniture) +{ + struct node_element *element; + int x0, x1, y0, y1; + + *furniture = false; + for (; node != NULL; node = node->next) { + if (node->box.y > y) return NULL; + if ((node->box.x - NODE_INSTEP < x) && (node->box.y < y) && + (node->box.x + node->box.width >= x) && + (node->box.y + node->box.height >= y)) { + if (node->expanded) { + for (element = &node->data; element != NULL; + element = element->next) { + x0 = element->box.x; + y0 = element->box.y; + x1 = element->box.x + + element->box.width; + y1 = element->box.y + + element->box.height; + if ((x0 < x) && (y0 < y) && (x1 >= x) + && (y1 >= y)) + return element; + } + } else { + x0 = node->data.box.x; + y0 = node->data.box.y; + x1 = node->data.box.x + node->data.box.width; + y1 = node->data.box.y + node->data.box.height; + if ((x0 < x) && (y0 < y) && (x1 >= x) && + (y1>= y)) + return &node->data; + } + if (((node->child != NULL) || + (node->data.next != NULL)) && + (node->data.box.x - NODE_INSTEP + 4 < x) + && (node->data.box.y + 4 < y) && + (node->data.box.x > x) && + (node->data.box.y + 20 > y)) { + *furniture = true; + return &node->data; + } + } + + element = tree_get_node_element_at(node->child, x, y, + furniture); + if ((node->child != NULL) && (node->expanded) && + (element != NULL)) + return element; + } + return NULL; +} + + +/** + * Finds a node at a specific location. + * + * \param root the root node to check from + * \param x the x co-ordinate + * \param y the y co-ordinate + * \param furniture whether the returned area was in an elements furniture + * \return the node at the specified position, or NULL for none + */ +static struct node *tree_get_node_at(struct node *root, int x, int y, + bool *furniture) +{ + struct node_element *result; + + if ((result = tree_get_node_element_at(root, x, y, furniture))) + return result->parent; + return NULL; +} + + +/** + * Gets link characteristics to insert a node at a specified position. + * + * \param tree the tree to find link information for + * \param x the x co-ordinate + * \param y the y co-ordinate + * \param before set to whether the node should be linked before on exit + * \return the node to link with + */ +struct node *tree_get_link_details(struct tree *tree, int x, int y, + bool *before) +{ + struct node *node = NULL; + bool furniture; + + assert(tree != NULL); + assert(tree->root != NULL); + + *before = false; + if (tree->root->child != NULL) + node = tree_get_node_at(tree->root->child, x, y, &furniture); + if ((node == NULL) || (furniture)) + return tree->root; + + if (y < (node->box.y + (node->box.height / 2))) { + *before = true; + } else if ((node->folder) && (node->expanded) && + (node->child != NULL)) { + node = node->child; + *before = true; + } + return node; +} + + +/** + * Launches all the selected nodes of the tree + * + * \param tree the tree for which all nodes will be launched + * \param node the node which will be checked together with its children + */ +static void tree_launch_selected_internal(struct tree *tree, struct node *node) +{ + struct node_msg_data msg_data; + + for (; node != NULL; node = node->next) { + if (node->selected && node->user_callback != NULL) { + msg_data.msg = NODE_LAUNCH; + msg_data.flag = TREE_ELEMENT_TITLE; + msg_data.node = node; + node->user_callback(node->callback_data, &msg_data); + } + if (node->child != NULL) + tree_launch_selected_internal(tree, node->child); + } +} + + +/** + * Launches all the selected nodes of the tree + * + * \param tree the tree for which all nodes will be launched + */ +void tree_launch_selected(struct tree *tree) +{ + if (tree->root->child != NULL) + tree_launch_selected_internal(tree, tree->root->child); +} + + +/** + * Handles a mouse action for a tree + * + * \param tree the tree to handle a click for + * \param mouse the mouse state + * \param x X coordinate of mouse action + * \param y Y coordinate of mouse action + * \return whether the click was handled + */ +bool tree_mouse_action(struct tree *tree, browser_mouse_state mouse, int x, + int y) +{ + bool furniture; + struct node *node; + struct node *last; + struct node_element *element; + struct node_msg_data msg_data; + + assert(tree != NULL); + assert(tree->root != NULL); + + if (tree->root->child == NULL) + return true; + + element = tree_get_node_element_at(tree->root->child, x, y, &furniture); + + /* pass in-textarea mouse action and drags which started in it + to the textarea */ + if (tree->editing != NULL) { + int x0, x1, y0, y1; + x0 = tree->editing->box.x; + if (tree->editing->type == NODE_ELEMENT_TEXT_PLUS_ICON) + x0 += NODE_INSTEP; + x1 = tree->editing->box.x + tree->editing->box.width; + y0 = tree->editing->box.y; + y1 = tree->editing->box.y + tree->editing->box.height; + + if (tree->textarea_drag_start && + (mouse & (BROWSER_MOUSE_HOLDING_1 | + BROWSER_MOUSE_HOLDING_2))) { + + textarea_mouse_action(tree->textarea, mouse, + x - x0, y - y0); + return true; + } + + + + if ((x >= x0) && (x < x1) && (y >= y0) && (y < y1)) { + + if (mouse & (BROWSER_MOUSE_DRAG_1 | + BROWSER_MOUSE_DRAG_2)) + tree->textarea_drag_start = true; + else + tree->textarea_drag_start = false; + textarea_mouse_action(tree->textarea, mouse, + x - x0, y - y0); + return true; + + } + } + + tree->textarea_drag_start = false; + + /* we are not interested in the drag path or in mouse presses, return */ + if (mouse & (BROWSER_MOUSE_HOLDING_1 | BROWSER_MOUSE_HOLDING_2 | + BROWSER_MOUSE_PRESS_1 | BROWSER_MOUSE_PRESS_2)) + return true; + + /* cancel edit */ + if (tree->editing != NULL) + tree_stop_edit(tree, false); + + + + /* no item either means cancel selection on (select) click or a drag */ + if (element == NULL) { + if (tree->flags & TREE_SINGLE_SELECT) { + tree_set_node_selected(tree, tree->root->child, true, + false); + return true; + } + if (mouse & (BROWSER_MOUSE_CLICK_1 | BROWSER_MOUSE_DRAG_1)) + tree_set_node_selected(tree, tree->root->child, true, + false); + if (mouse & (BROWSER_MOUSE_DRAG_1 | BROWSER_MOUSE_DRAG_2)) { + + /** @todo the tree window has to scroll the tree when + * mouse reaches border while dragging this isn't + * solved for the browser window too. + */ + tree->drag = TREE_SELECT_DRAG; + } + return true; + } + + node = element->parent; + + /* click on furniture or double click on folder toggles node expansion + */ + if (((furniture) && (mouse & (BROWSER_MOUSE_CLICK_1 | + BROWSER_MOUSE_CLICK_2))) || + ((!furniture) && (node->child != NULL) && + (mouse & BROWSER_MOUSE_DOUBLE_CLICK))) { + + /* clear any selection */ + tree_set_node_selected(tree, tree->root->child, true, false); + + /* expand / contract node and redraw */ + tree_set_node_expanded(tree, node, !node->expanded, + false, false); + + /* find the last child node if expanded */ + last = node; + if ((last->child != NULL) && (last->expanded)) { + last = last->child; + while ((last->next != NULL) || + ((last->child != NULL) && + (last->expanded))) { + if (last->next != NULL) + last = last->next; + else + last = last->child; + } + } + /* scroll to the bottom element then back to the top */ + element = &last->data; + if (last->expanded) + for (; element->next != NULL; element = element->next); + tree->callbacks->scroll_visible(element->box.y, + element->box.height, + tree->client_data); + tree->callbacks->scroll_visible(node->data.box.y, + node->data.box.height, + tree->client_data); + return true; + } + + /* no use for any other furniture click */ + if (furniture) + return true; + + /* single/double ctrl+click or alt+click starts editing */ + if ((element->editable) && (!tree->editing) && + ((element->type == NODE_ELEMENT_TEXT) || + (element->type == NODE_ELEMENT_TEXT_PLUS_ICON)) && + (mouse & (BROWSER_MOUSE_CLICK_1 | + BROWSER_MOUSE_DOUBLE_CLICK)) && + (mouse & BROWSER_MOUSE_MOD_2 || + mouse & BROWSER_MOUSE_MOD_3)) { + tree_set_node_selected(tree, tree->root->child, true, false); + tree_start_edit(tree, element); + return true; + } + + /* double click launches the leaf */ + if (mouse & BROWSER_MOUSE_DOUBLE_CLICK) { + if (node->user_callback == NULL) + return false; + msg_data.msg = NODE_LAUNCH; + msg_data.flag = TREE_ELEMENT_TITLE; + msg_data.node = node; + if (node->user_callback(node->callback_data, &msg_data) != + NODE_CALLBACK_HANDLED) + return false; + + return true; + } + + /* single click (select) cancels current selection and selects item */ + if (mouse & BROWSER_MOUSE_CLICK_1 || (mouse & BROWSER_MOUSE_CLICK_2 && + tree->flags & TREE_SINGLE_SELECT)) { + if (tree->flags & TREE_NO_SELECT) + return true; + if (!node->selected) { + tree_set_node_selected(tree, tree->root->child, true, + false); + node->selected = true; + tree_handle_node_element_changed(tree, &node->data); + } + return true; + } + + /* single click (adjust) toggles item selection */ + if (mouse & BROWSER_MOUSE_CLICK_2) { + if (tree->flags & TREE_NO_SELECT) + return true; + node->selected = !node->selected; + tree_handle_node_element_changed(tree, &node->data); + return true; + } + + /* drag starts a drag operation */ + if ((!tree->editing) && (mouse & (BROWSER_MOUSE_DRAG_1 | + BROWSER_MOUSE_DRAG_2))) { + if (tree->flags & TREE_NO_DRAGS) + return true; + + if (!node->selected) { + tree_set_node_selected(tree, tree->root->child, true, + false); + node->selected = true; + tree_handle_node_element_changed(tree, &node->data); + } + + tree->drag = TREE_MOVE_DRAG; + + return true; + } + + + return false; +} + + +/** + * Updates the selected state for a region of nodes. + * + * \param tree the tree to update + * \param node the node to update children and siblings of + * \param y the minimum y of the selection rectangle + * \param height the height of the selection rectangle + * \param invert whether to invert the selected state + */ +static void tree_handle_selection_area_node(struct tree *tree, + struct node *node, int y, int height, bool invert) +{ + struct node_element *element; + struct node *update; + int y_max; + int y0, y1; + + assert(tree != NULL); + assert(node != NULL); + + y_max = y + height; + + for (; node != NULL; node = node->next) { + if (node->box.y > y_max) return; + y0 = node->box.y; + y1 = node->box.y + node->box.height; + if ((y0 < y_max) && (y1 >= y)) { + update = NULL; + if (node->expanded) { + for (element = &node->data; element != NULL; + element = element->next) { + y0 = element->box.y; + y1 = element->box.y + + element->box.height; + if ((y0 < y_max) && (y1 >= y)) { + update = element->parent; + break; + } + } + } else { + y0 = node->data.box.y; + y1 = node->data.box.y + node->data.box.height; + if ((y0 < y_max) && (y1 >= y)) + update = node->data.parent; + } + if ((update) && (node != tree->root)) { + if (invert) { + node->selected = !node->selected; + tree_handle_node_element_changed(tree, + &node->data); + } else if (!node->selected) { + node->selected = true; + tree_handle_node_element_changed(tree, + &node->data); + } + } + } + if ((node->child != NULL) && (node->expanded)) + tree_handle_selection_area_node(tree, node->child, y, + height, invert); + } +} + + +/** + * Updates the selected state for a region of nodes. + * + * \param tree the tree to update + * \param y the minimum y of the selection rectangle + * \param height the height of the selection rectangle + * \param invert whether to invert the selected state + */ +static void tree_handle_selection_area(struct tree *tree, int y, int height, + bool invert) +{ + assert(tree != NULL); + assert(tree->root != NULL); + + if (tree->root->child == NULL) + return; + + if (height < 0) { + y += height; + height = -height; + } + tree_handle_selection_area_node(tree, tree->root->child, y, height, + invert); +} + + +/** + * Clears the processing flag. + * + * \param node the node to process siblings and children of + */ +static void tree_clear_processing(struct node *node) +{ + for (; node != NULL; node = node->next) { + node->processing = false; + if (node->child != NULL) + tree_clear_processing(node->child); + } +} + + +/** + * Sets the processing flag to the selection state. + * + * \param node the node to process siblings and children of + */ +static void tree_selected_to_processing(struct node *node) +{ + for (; node != NULL; node = node->next) { + node->processing = node->selected; + if ((node->child != NULL) && (node->expanded)) + tree_selected_to_processing(node->child); + } +} + + +/** + * Moves the first node in a tree with the processing flag set. + * + * \param tree the tree in which the move takes place + * \param node the node to move siblings/children of + * \param link the node to link before/as a child (folders) or before/after + * (link) + * \param before whether to link siblings before or after the supplied node + * \param first whether to always link after the supplied node (ie not + * inside of folders) + * \return the node moved + */ +static struct node *tree_move_processing_node(struct tree *tree, + struct node *node, struct node *link, bool before, bool first) +{ + struct node *result; + + bool folder = link->folder; + for (; node != NULL; node = node->next) { + if (node->processing) { + node->processing = false; + tree_delink_node(tree, node); + if (!first) + link->folder = false; + tree_link_node(tree, link, node, before); + if (!first) + link->folder = folder; + return node; + } + if (node->child != NULL) { + result = tree_move_processing_node(tree, node->child, + link, before, first); + if (result != NULL) + return result; + } + } + return NULL; +} + + +/** + * Moves nodes within a tree. + * + * \param tree the tree to process + * \param destination the node to link before/as a child (folders) + * or before/after (link) + * \param before whether to link siblings before or after the supplied + * node + */ +static void tree_move_selected_nodes(struct tree *tree, + struct node *destination, bool before) +{ + struct node *link; + struct node *test; + bool error; + + tree_clear_processing(tree->root); + tree_selected_to_processing(tree->root); + + /* the destination node cannot be a child of any node with + the processing flag set */ + error = destination->processing; + for (test = destination; test != NULL; test = test->parent) + error |= test->processing; + if (error) { + tree_clear_processing(tree->root); + return; + } + if ((destination->folder) && (!destination->expanded) && (!before)) { + tree_set_node_expanded(tree, destination, true, false, false); + } + link = tree_move_processing_node(tree, tree->root, destination, before, + true); + while (link != NULL) + link = tree_move_processing_node(tree, tree->root, link, false, + false); + + tree_clear_processing(tree->root); + tree_recalculate_node_positions(tree, tree->root); + if (tree->redraw) + tree->callbacks->redraw_request(0, 0, tree->width, tree->height, + tree->client_data); +} + + +/** + * Handle the end of a drag operation + * + * \param tree the tree on which the drag was performed + * \param mouse mouse state during drag end + * \param x0 x coordinate of drag start + * \param y0 y coordinate of drag start + * \param x1 x coordinate of drag end + * \param y1 y coordinate of drag end + */ +void tree_drag_end(struct tree *tree, browser_mouse_state mouse, int x0, int y0, + int x1, int y1) +{ + + bool before; + struct node *node; + int x, y; + + if (tree->textarea_drag_start) { + x = tree->editing->box.x; + y = tree->editing->box.y; + if (tree->editing->type == NODE_ELEMENT_TEXT_PLUS_ICON) + x += NODE_INSTEP; + textarea_drag_end(tree->textarea, mouse, x1 - x, y1 - y); + } + + tree->textarea_drag_start = false; + + switch (tree->drag) { + case TREE_NO_DRAG: + break; + case TREE_SELECT_DRAG: + tree_handle_selection_area(tree, y0, y1 - y0, + (mouse | BROWSER_MOUSE_HOLDING_2)); + break; + case TREE_MOVE_DRAG: + if (!(tree->flags & TREE_MOVABLE)) + return; + node = tree_get_link_details(tree, x1, y1, &before); + tree_move_selected_nodes(tree, node, before); + break; + } + + tree->drag = TREE_NO_DRAG; +} + + +/** + * Key press handling for a tree. + * + * \param tree The tree which got the keypress + * \param key The ucs4 character codepoint + * \return true if the keypress is dealt with, false otherwise. + */ +bool tree_keypress(struct tree *tree, uint32_t key) +{ + + if (tree->editing != NULL) + switch (key) { + case KEY_ESCAPE: + tree_stop_edit(tree, false); + return true; + case KEY_NL: + tree_stop_edit(tree, true); + return true; + default: + return textarea_keypress(tree->textarea, key); + } + + return false; +} + + +/** + * Alphabetical comparison function for nodes + * + * \param n1 first node to compare + * \param n2 first node to compare + * \return 0 if equal, greater then zero if n1 > n2, + * less then zero if n2 < n1 + */ +int tree_alphabetical_sort(struct node *n1, struct node *n2) +{ + return strcmp(n1->data.text, n2->data.text); +} + + +/** + * Redraw requests from the textarea are piped through this because we have to + * check the redraw flag of the tree before requesting a redraw and change the + * position to tree origin relative. + */ +static void tree_textarea_redraw_request(void *data, int x, int y, + int width, int height) +{ + struct tree *tree = data; + x = x + tree->editing->box.x; + y = y + tree->editing->box.y; + if (tree->editing->type == NODE_ELEMENT_TEXT_PLUS_ICON) + x += NODE_INSTEP; + + if (tree->redraw) + tree->callbacks->redraw_request(x, y, + width, height, + tree->client_data); +} + + +/** + * Starts editing a node_element + * + * \param tree The tree to which element belongs + * \param element The element to start being edited + */ +void tree_start_edit(struct tree *tree, struct node_element *element) +{ + struct node *parent; + int width, height; + + assert(tree != NULL); + assert(element != NULL); + + if (tree->editing != NULL) + tree_stop_edit(tree, true); + + parent = element->parent; + if (&parent->data == element) + parent = parent->parent; + for (; parent != NULL; parent = parent->parent) { + if (!parent->expanded) { + tree_set_node_expanded(tree, parent, true, + false, false); + } + } + + tree->editing = element; + tree->callbacks->get_window_dimensions(&width, NULL, tree->client_data); + width -= element->box.x; + height = element->box.height; + if (element->type == NODE_ELEMENT_TEXT_PLUS_ICON) + width -= NODE_INSTEP; + + tree->textarea = textarea_create(width, height, 0, + &plot_fstyle, tree_textarea_redraw_request, tree); + if (tree->textarea == NULL) { + tree_stop_edit(tree, false); + return; + } + textarea_set_text(tree->textarea, element->text); + + tree_handle_node_element_changed(tree, element); + tree_recalculate_size(tree); + tree->callbacks->scroll_visible(element->box.y, element->box.height, + tree->client_data); +} + + +/** + * Callback for fetchcache(). Should be removed once bitmaps get loaded directly + * from disc + */ +static nserror tree_icon_callback(hlcache_handle *handle, + const hlcache_event *event, void *pw) +{ + return NSERROR_OK; +} + + +/** + * Tree utility function. Placed here so that this code doesn't have to be + * copied by each user. + * + * \param name the name of the loaded icon, if it's not a full path the icon is + * looked for in the directory specified by option_tree_icons_dir + * \return the icon in form of a content or NULL on failure + */ +hlcache_handle *tree_load_icon(const char *name) +{ + char *url = NULL; + const char *icon_url = NULL; + int len; + hlcache_handle *c; + nserror err; + + /** @todo something like bitmap_from_disc is needed here */ + + if (!strncmp(name, "file://", 7)) { + icon_url = name; + } else { + char *native_path; + + if (option_tree_icons_dir == NULL) + return NULL; + + /* path + separator + leafname + '\0' */ + len = strlen(option_tree_icons_dir) + 1 + strlen(name) + 1; + native_path = malloc(len); + if (native_path == NULL) { + LOG(("malloc failed")); + warn_user("NoMemory", 0); + return NULL; + } + + /* Build native path */ + memcpy(native_path, option_tree_icons_dir, + strlen(option_tree_icons_dir) + 1); + path_add_part(native_path, len, name); + + /* Convert native path to URL */ + url = path_to_url(native_path); + + free(native_path); + icon_url = url; + } + + /* Fetch the icon */ + err = hlcache_handle_retrieve(icon_url, 0, 0, 0, + tree_icon_callback, 0, 0, 0, &c); + + + /* If we built the URL here, free it */ + if (url != NULL) + free(url); + + if (err != NSERROR_OK) { + return NULL; + } + + return c; +} -- cgit v1.2.3