/* * Copyright 2004 Richard Wilson * * This file is part of NetSurf, http://www.netsurf-browser.org/ * * NetSurf is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * NetSurf is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ /** \file * Generic tree handling (implementation). */ #include #include #include #include #include #include "content/urldb.h" #include "desktop/tree.h" #include "desktop/options.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; /** * Initialises a user-created tree * * \param tree the tree to initialise */ void tree_initialise(struct tree *tree) { assert(tree); 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); tree->root->expanded = true; tree_recalculate_node_positions(tree, tree->root); tree_recalculate_size(tree); } /** * Initialises a user-created node structure * * \param root the root node to update from */ void tree_initialise_nodes(struct tree *tree, struct node *root) { struct node *node; assert(root); 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--; if (tree_initialising == 0) tree_recalculate_node_positions(tree, root); } /** * 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; assert(node); if ((expansion) && (node->expanded) && (node->child)) { tree_set_node_expanded(tree, node->child, false); tree_set_node_selected(tree, node->child, false); } 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); } 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. * * \param tree the tree to redraw * \param element the node element to update */ void tree_handle_node_element_changed(struct tree *tree, struct node_element *element) { int width, height; assert(element); width = element->box.width; height = element->box.height; tree_recalculate_node_element(element); 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); } } /** * Recalculates the size of a node. * * \param node the node to update * \param recalculate_sizes whether the node elements have changed */ void tree_recalculate_node(struct tree *tree, struct node *node, bool recalculate_sizes) { struct node_element *element; int height; assert(node); 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; } } 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); } } /** * Recalculates the position of a node, its siblings and children. * * \param root the root node to update from */ 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; for (node = root; node; node = node->next) { if (node->previous) { 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)) { node->box.x = parent->box.x + NODE_INSTEP; node->box.y = parent->box.y + parent->box.height; for (child = parent->child; child != node; child = child->next) node->box.y += child->box.height; } else { node->box.x = tree->no_furniture ? -NODE_INSTEP + 4 : 0; node->box.y = -40; } 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 { node->data.box.x = node->box.x; node->data.box.y = node->box.y; } } } /** * Calculates the width of a node including any children * * \param node the node to calculate the height of * \return the total width of the node and children */ int tree_get_node_width(struct node *node) { int width = 0; int child_width; assert(node); 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; } } return width; } /** * Calculates the height of a node including any children * * \param node the node to calculate the height of * \return the total height of the node and children */ int tree_get_node_height(struct node *node) { int y1; assert(node); 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; } } /** * Updates all siblinds and descendants of a node to an expansion state. * No update is performed for the tree changes. * * \param node the node to set all siblings and descendants of * \param expanded the expansion state to set */ 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); } } /** * Updates all siblinds and descendants of a node to an expansion state. * * \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 */ bool tree_handle_expansion(struct tree *tree, struct node *node, bool expanded, bool folder, bool leaf) { struct node *entry = node; bool redraw = false; 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; } /** * Updates all siblinds and descendants of a node to an selected state. * The required areas of the tree are redrawn. * * \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 */ 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); } if ((node->child) && (node->expanded)) tree_set_node_selected(tree, node->child, selected); } } /** * 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 */ 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; } /** * 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 */ struct node_element *tree_get_node_element_at(struct node *node, int x, int y, bool *furniture) { struct node_element *element; *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; } } 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 * * \param node the node to examine * \param user_type the user_type to check for * \return the corresponding element */ 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; } /** * 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; 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; 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); } 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); } /** * Sets the processing flag to the selection state. * * \param node the node to process siblings and children of */ 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); } } /** * 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); } } /** * Moves the first node in a tree with the processing flag set. * * \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 */ struct node *tree_move_processing_node(struct node *node, struct node *link, bool before, bool first) { struct node *result; 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; } } 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; } return false; } /** * Updates the selected state for a region of nodes. * * \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 */ void tree_handle_selection_area(struct tree *tree, int x, int y, int width, int height, bool invert) { assert(tree); assert(tree->root); if (!tree->root->child) return; if (width < 0) { x += width; width =- width; } if (height < 0) { y += height; height =- height; } tree_handle_selection_area_node(tree, tree->root->child, x, y, width, height, invert); } /** * 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 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 */ void tree_handle_selection_area_node(struct tree *tree, struct node *node, int x, int y, int width, int height, bool invert) { struct node_element *element; struct node *update; int x_max, y_max; assert(tree); assert(node); x_max = x + width; y_max = y + height; 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; } } } 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 ((node->child) && (node->expanded)) tree_handle_selection_area_node(tree, node->child, x, y, width, height, invert); } } /** * Redraws a tree. * * \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 */ void tree_draw(struct tree *tree, int clip_x, int clip_y, int clip_width, int clip_height) { assert(tree); assert(tree->root); if (!tree->root->child) return; tree_initialise_redraw(tree); tree_draw_node(tree, tree->root->child, clip_x, clip_y, clip_width, clip_height); } /** * Redraws a node. * * \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 */ 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; assert(tree); assert(node); x_max = clip_x + clip_width + NODE_INSTEP; y_max = clip_y + clip_height; 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); } if ((node->child) && (node->expanded)) tree_draw_node(tree, node->child, clip_x, clip_y, clip_width, clip_height); } } /** * 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); assert(tree->root); *before = false; if (tree->root->child) node = tree_get_node_at(tree->root->child, x, y, &furniture); if ((!node) || (furniture)) return tree->root; if (y < (node->box.y + (node->box.height / 2))) { *before = true; } else if ((node->folder) && (node->expanded) && (node->child)) { node = node->child; *before = true; } return node; } /** * Links a node into the tree. * * \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 */ 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; } /** * Delinks a node from the tree. * * \param node the node to delink */ 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; } node->parent = NULL; } 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. * * \param tree the tree to delete from * \param node the node to delete */ void tree_delete_selected_nodes(struct tree *tree, struct node *node) { struct node *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; } } /** * Deletes a node from the tree. * * \param tree the tree to delete from * \param node the node to delete * \param siblings whether to delete all siblings */ 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); } /** * Deletes a node from the tree. * * \param tree the tree to delete from * \param node the node to delete * \param siblings whether to delete all siblings */ 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; assert(node); if (tree->temp_selection == node) tree->temp_selection = NULL; if (tree->root == node) tree->root = NULL; 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); } } } 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); } else { node->deleted = true; } if (siblings && next) tree_delete_node_internal(tree, next, true); } /** * Creates a folder node with the specified title, and links it into the tree. * * \param parent the parent node, or NULL not to link * \param title the node title (copied) * \return the newly created node. */ struct node *tree_create_folder_node(struct node *parent, const char *title) { struct node *node; assert(title); 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; } /** * Creates a leaf node with the specified title, and links it into the tree. * * \param parent the parent node, or NULL not to link * \param title the node title (copied) * \return the newly created node. */ struct node *tree_create_leaf_node(struct node *parent, const char *title) { struct node *node; 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; } /** * Creates a leaf node with the specified title, and links it into the tree. * * \param parent the parent node, or NULL not to link * \param title the node title * \return the newly created node. */ struct node *tree_create_leaf_node_shared(struct node *parent, const char *title) { struct node *node; 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; } /** * Creates a tree entry for a URL, and links it into the tree * * * \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 */ 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; assert(data); node = tree_create_leaf_node(parent, title ? title : url); if (!node) return NULL; 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. * * 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 */ 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; assert(url && data); if (data->title) title = data->title; else title = url; node = tree_create_leaf_node_shared(parent, title); if (!node) return NULL; 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; 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. * * \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 */ struct node *tree_create_cookie_node(struct node *parent, const struct cookie_data *data) { struct node *node; struct node_element *element; char buffer[256]; char buffer2[16]; node = tree_create_leaf_node(parent, data->name); if (!node) return NULL; node->data.data = TREE_ELEMENT_NAME; node->editable = false; 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); } } 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; } /** * Creates an empty text node element and links it to a node. * * \param parent the parent node * \param user_type the required user_type * \return the newly created element. */ struct node_element *tree_create_node_element(struct node *parent, node_element_data data) { 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; } /** * Recalculates the size of a tree. * * \param tree the tree to recalculate */ void tree_recalculate_size(struct tree *tree) { int width, height; assert(tree); if (!tree->handle) 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); } /** * 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 */ struct node *tree_get_selected_node(struct node *node) { struct node *result = NULL; struct node *temp; for (; node; node = node->next) { if (node->selected) { if (result) return NULL; result = node; } if ((node->child) && (node->expanded)) { temp = tree_get_selected_node(node->child); if (temp) { if (result) return NULL; else result = temp; } } } return result; }