diff options
author | Richard Wilson <rjw@netsurf-browser.org> | 2004-12-09 10:30:44 +0000 |
---|---|---|
committer | Richard Wilson <rjw@netsurf-browser.org> | 2004-12-09 10:30:44 +0000 |
commit | 32db7e04d0c3bd255b2e8aa7dbd7c2b884b35614 (patch) | |
tree | 312984b2b972916fd10e1efc338d08e437d55b70 /desktop | |
parent | 77a96712244ad4a8b4bde235aa051372a64a8789 (diff) | |
download | netsurf-32db7e04d0c3bd255b2e8aa7dbd7c2b884b35614.tar.gz netsurf-32db7e04d0c3bd255b2e8aa7dbd7c2b884b35614.tar.bz2 |
[project @ 2004-12-09 10:30:43 by rjw]
Re-implementation of hotlist via general tree code. Animations can be stopped once more. Purged a few xcalloc() calls.
svn path=/import/netsurf/; revision=1394
Diffstat (limited to 'desktop')
-rw-r--r-- | desktop/options.c | 376 | ||||
-rw-r--r-- | desktop/options.h | 6 | ||||
-rw-r--r-- | desktop/tree.c | 1113 | ||||
-rw-r--r-- | desktop/tree.h | 137 |
4 files changed, 1632 insertions, 0 deletions
diff --git a/desktop/options.c b/desktop/options.c index a777f6d12..3ad212a14 100644 --- a/desktop/options.c +++ b/desktop/options.c @@ -5,6 +5,7 @@ * Copyright 2003 Phil Mellor <monkeyson@users.sourceforge.net> * Copyright 2003 John M Bell <jmb202@ecs.soton.ac.uk> * Copyright 2004 James Bursa <bursa@users.sourceforge.net> + * Copyright 2004 Richard Wilson <not_ginger_matt@users.sourceforge.net> */ /** \file @@ -14,11 +15,16 @@ * value is "0" or "1". */ +#include <assert.h> #include <stdbool.h> #include <stdio.h> #include <string.h> +#include "libxml/HTMLparser.h" +#include "libxml/HTMLtree.h" #include "netsurf/desktop/options.h" +#include "netsurf/desktop/tree.h" #include "netsurf/utils/log.h" +#include "netsurf/utils/messages.h" #include "netsurf/utils/utils.h" #ifdef riscos @@ -57,6 +63,9 @@ bool option_block_ads = false; int option_minimum_gif_delay = 10; /** Whether to send the referer HTTP header */ bool option_send_referer = true; +/** Whether to animate images */ +bool option_animate_images = true; + EXTRA_OPTION_DEFINE @@ -80,12 +89,21 @@ struct { { "block_advertisements", OPTION_BOOL, &option_block_ads }, { "minimum_gif_delay", OPTION_INTEGER, &option_minimum_gif_delay }, { "send_referer", OPTION_BOOL, &option_send_referer }, + { "animate_images", OPTION_BOOL, &option_animate_images }, \ EXTRA_OPTION_TABLE }; #define option_table_entries (sizeof option_table / sizeof option_table[0]) +static void options_load_hotlist_directory(xmlNode *ul, struct node *directory); +static void options_load_hotlist_entry(xmlNode *li, struct node *directory); +xmlNode *options_find_hotlist_element(xmlNode *node, const char *name); +bool options_save_hotlist_directory(struct node *directory, xmlNode *node); +bool options_save_hotlist_entry(struct node *entry, xmlNode *node); +bool options_save_hotlist_entry_comment(xmlNode *node, const char *name, int value); + + /** * Read options from a file. * @@ -201,3 +219,361 @@ void options_write(const char *path) fclose(fp); } + + +/** + * Loads a hotlist as a tree from a specified file. + * + * \param filename name of file to read + * \return the hotlist file represented as a tree, or NULL on failure + */ +struct tree *options_load_hotlist(const char *filename) { + xmlDoc *doc; + xmlNode *html, *body, *ul; + struct tree *tree; + + doc = htmlParseFile(filename, "iso-8859-1"); + if (!doc) { + warn_user("HotlistLoadError", messages_get("ParsingFail")); + return NULL; + } + + html = options_find_hotlist_element((xmlNode *) doc, "html"); + body = options_find_hotlist_element(html, "body"); + ul = options_find_hotlist_element(body, "ul"); + if (!ul) { + xmlFreeDoc(doc); + warn_user("HotlistLoadError", + "(<html>...<body>...<ul> not found.)"); + return NULL; + } + + tree = calloc(sizeof(struct tree), 1); + if (!tree) { + xmlFreeDoc(doc); + warn_user("NoMemory", 0); + return NULL; + } + tree->root = tree_create_folder_node(NULL, "Root"); + if (!tree->root) return NULL; + + options_load_hotlist_directory(ul, tree->root); + tree->root->expanded = true; + tree_initialise(tree); + + xmlFreeDoc(doc); + return tree; +} + + +/** + * Parse a directory represented as a ul. + * + * \param ul xmlNode for parsed ul + * \param directory directory to add this directory to + */ +void options_load_hotlist_directory(xmlNode *ul, struct node *directory) { + char *title; + struct node *dir; + xmlNode *n; + + assert(ul); + assert(directory); + + for (n = ul->children; n; n = n->next) { + /* The ul may contain entries as a li, or directories as + * an h4 followed by a ul. Non-element nodes may be present + * (eg. text, comments), and are ignored. */ + + if (n->type != XML_ELEMENT_NODE) + continue; + + if (strcmp(n->name, "li") == 0) { + /* entry */ + options_load_hotlist_entry(n, directory); + + } else if (strcmp(n->name, "h4") == 0) { + /* directory */ + title = (char *) xmlNodeGetContent(n); + if (!title) { + warn_user("HotlistLoadError", "(Empty <h4> " + "or memory exhausted.)"); + return; + } + + for (n = n->next; + n && n->type != XML_ELEMENT_NODE; + n = n->next) + ; + if (!n || strcmp(n->name, "ul") != 0) { + /* next element isn't expected ul */ + free(title); + warn_user("HotlistLoadError", "(Expected " + "<ul> not present.)"); + return; + } + + dir = tree_create_folder_node(directory, title); + if (!dir) + return; + options_load_hotlist_directory(n, dir); + } + } +} + + +/** + * Parse an entry represented as a li. + * + * \param li xmlNode for parsed li + * \param directory directory to add this entry to + */ +void options_load_hotlist_entry(xmlNode *li, struct node *directory) { + char *url = 0; + char *title = 0; + int filetype = 0xfaf; + int add_date = -1; + int last_date = -1; + int visits = 0; + char *comment; + struct node *entry; + xmlNode *n; + + for (n = li->children; n; n = n->next) { + /* The li must contain an "a" element, and may contain + * some additional data as comments. */ + + if (n->type == XML_ELEMENT_NODE && + strcmp(n->name, "a") == 0) { + url = (char *) xmlGetProp(n, (const xmlChar *) "href"); + title = (char *) xmlNodeGetContent(n); + + } else if (n->type == XML_COMMENT_NODE) { + comment = (char *) xmlNodeGetContent(n); + if (!comment) + continue; + if (strncmp("Type:", comment, 5) == 0) + filetype = atoi(comment + 5); + else if (strncmp("Added:", comment, 6) == 0) + add_date = atoi(comment + 6); + else if (strncmp("LastVisit:", comment, 10) == 0) + last_date = atoi(comment + 10); + else if (strncmp("Visits:", comment, 7) == 0) + visits = atoi(comment + 7); + } + } + + if (!url || !title) { + warn_user("HotlistLoadError", "(Missing <a> in <li> or " + "memory exhausted.)"); + return; + } + + entry = tree_create_URL_node(directory, title, url, filetype, add_date, + last_date, visits); +} + + +/** + * Search the children of an xmlNode for an element. + * + * \param node xmlNode to search children of, or 0 + * \param name name of element to find + * \return first child of node which is an element and matches name, or + * 0 if not found or parameter node is 0 + */ +xmlNode *options_find_hotlist_element(xmlNode *node, const char *name) { + xmlNode *n; + if (!node) + return 0; + for (n = node->children; + n && !(n->type == XML_ELEMENT_NODE && + strcmp(n->name, name) == 0); + n = n->next) + ; + return n; +} + + +/** + * Perform a save to a specified file + * + * /param filename the file to save to + */ +bool options_save_hotlist(struct tree *tree, const char *filename) { + int res; + xmlDoc *doc; + xmlNode *html, *head, *title, *body; + + /* Unfortunately the Browse Hotlist format is invalid HTML, + * so this is a lie. */ + doc = htmlNewDoc("http://www.w3.org/TR/html4/strict.dtd", + "-//W3C//DTD HTML 4.01//EN"); + if (!doc) { + warn_user("NoMemory", 0); + return false; + } + + html = xmlNewNode(NULL, "html"); + if (!html) { + warn_user("NoMemory", 0); + xmlFreeDoc(doc); + return false; + } + xmlDocSetRootElement(doc, html); + + head = xmlNewChild(html, NULL, "head", NULL); + if (!head) { + warn_user("NoMemory", 0); + xmlFreeDoc(doc); + return false; + } + + title = xmlNewTextChild(head, NULL, "title", "NetSurf Hotlist"); + if (!title) { + warn_user("NoMemory", 0); + xmlFreeDoc(doc); + return false; + } + + body = xmlNewChild(html, NULL, "body", NULL); + if (!body) { + warn_user("NoMemory", 0); + xmlFreeDoc(doc); + return false; + } + + if (!options_save_hotlist_directory(tree->root, body)) { + warn_user("NoMemory", 0); + xmlFreeDoc(doc); + return false; + } + + doc->charset = XML_CHAR_ENCODING_UTF8; + res = htmlSaveFileEnc(filename, doc, "iso-8859-1"); + if (res == -1) { + warn_user("HotlistSaveError", 0); + xmlFreeDoc(doc); + return false; + } + + xmlFreeDoc(doc); + return true; +} + + +/** + * Add a directory to the HTML tree for saving. + * + * \param directory hotlist directory to add + * \param node node to add ul to + * \return true on success, false on memory exhaustion + */ +bool options_save_hotlist_directory(struct node *directory, xmlNode *node) { + struct node *child; + xmlNode *ul, *h4; + + ul = xmlNewChild(node, NULL, "ul", NULL); + if (!ul) + return false; + + for (child = directory->child; child; child = child->next) { + if (!child->folder) { + /* entry */ + if (!options_save_hotlist_entry(child, ul)) + return false; + } else { + /* directory */ + /* invalid HTML */ + h4 = xmlNewTextChild(ul, NULL, "h4", child->data.text); + if (!h4) + return false; + + if (!options_save_hotlist_directory(child, ul)) + return false; + } } + + return true; +} + + +/** + * Add an entry to the HTML tree for saving. + * + * The node must contain a sequence of node_elements in the following order: + * + * \param entry hotlist entry to add + * \param node node to add li to + * \return true on success, false on memory exhaustion + */ +bool options_save_hotlist_entry(struct node *entry, xmlNode *node) { + xmlNode *li, *a; + xmlAttr *href; + struct node_element *element; + + li = xmlNewChild(node, NULL, "li", NULL); + if (!li) + return false; + + a = xmlNewTextChild(li, NULL, "a", entry->data.text); + if (!a) + return false; + + element = tree_find_element(entry, TREE_ELEMENT_URL); + if (!element) + return false; + href = xmlNewProp(a, "href", element->text); + if (!href) + return false; + + if (element->user_data != 0xfaf) + if (!options_save_hotlist_entry_comment(li, + "Type", element->user_data)) + return false; + + element = tree_find_element(entry, TREE_ELEMENT_ADDED); + if ((element) && (element->user_data != -1)) + if (!options_save_hotlist_entry_comment(li, + "Added", element->user_data)) + return false; + + element = tree_find_element(entry, TREE_ELEMENT_LAST_VISIT); + if ((element) && (element->user_data != -1)) + if (!options_save_hotlist_entry_comment(li, + "LastVisit", element->user_data)) + return false; + + element = tree_find_element(entry, TREE_ELEMENT_VISITS); + if ((element) && (element->user_data != 0)) + if (!options_save_hotlist_entry_comment(li, + "Visits", element->user_data)) + return false; + return true; +} + + +/** + * Add a special comment node to the HTML tree for saving. + * + * \param node node to add comment to + * \param name name of special comment + * \param value value of special comment + * \return true on success, false on memory exhaustion + */ +bool options_save_hotlist_entry_comment(xmlNode *node, const char *name, int value) { + char s[40]; + xmlNode *comment; + + snprintf(s, sizeof s, "%s:%i", name, value); + s[sizeof s - 1] = 0; + + comment = xmlNewComment(s); + if (!comment) + return false; + if (!xmlAddChild(node, comment)) { + xmlFreeNode(comment); + return false; + } + + return true; +} diff --git a/desktop/options.h b/desktop/options.h index 0724f8c65..d811ffb7e 100644 --- a/desktop/options.h +++ b/desktop/options.h @@ -24,6 +24,8 @@ #ifndef _NETSURF_DESKTOP_OPTIONS_H_ #define _NETSURF_DESKTOP_OPTIONS_H_ +#include "netsurf/desktop/tree.h" + enum { OPTION_HTTP_PROXY_AUTH_NONE = 0, OPTION_HTTP_PROXY_AUTH_BASIC = 1, OPTION_HTTP_PROXY_AUTH_NTLM = 2 }; @@ -41,8 +43,12 @@ extern int option_memory_cache_size; extern bool option_block_ads; extern int option_minimum_gif_delay; extern bool option_send_referer; +extern bool option_animate_images; void options_read(const char *path); void options_write(const char *path); +struct tree *options_load_hotlist(const char *filename); +bool options_save_hotlist(struct tree *tree, const char *filename); + #endif diff --git a/desktop/tree.c b/desktop/tree.c new file mode 100644 index 000000000..d504ddcba --- /dev/null +++ b/desktop/tree.c @@ -0,0 +1,1113 @@ +/* + * This file is part of NetSurf, http://netsurf.sourceforge.net/ + * Licensed under the GNU General Public License, + * http://www.opensource.org/licenses/gpl-license + * Copyright 2004 Richard Wilson <not_ginger_matt@users.sourceforge.net> + */ + +/** \file + * Generic tree handling (implementation). + */ + +#include <assert.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include "netsurf/desktop/tree.h" +#include "netsurf/desktop/options.h" +#include "netsurf/utils/log.h" +#include "netsurf/utils/utils.h" + +static void tree_recalculate_node(struct node *node, bool recalculate_sizes); +static void tree_recalculate_node_positions(struct node *root); +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, int user_type); +static int tree_get_node_width(struct node *node); +static int tree_get_node_height(struct node *node); +static void tree_reset_URL_node(struct tree *tree, 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); +struct node *tree_move_processing_node(struct node *node, struct node *link, bool before, + bool first); + +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->root, true); + tree_initialise_nodes(tree->root); + tree_recalculate_node_positions(tree->root); + tree_set_node_expanded(tree->root, false); + tree->root->expanded = true; + tree_recalculate_node_positions(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 node *root) { + struct node *node; + + assert(root); + + tree_initialising++; + for (node = root; node; node = node->next) { + tree_recalculate_node(node, true); + if (node->child) { + tree_initialise_nodes(node->child); + } + } + tree_initialising--; + + if (tree_initialising == 0) + tree_recalculate_node_positions(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(node->child, false); + tree_set_node_selected(tree, node->child, false); + } + + width = node->box.width; + height = node->box.height; + if (recalculate_sizes) + tree_recalculate_node(node, true); + else if (expansion) + tree_recalculate_node(node, false); + if ((node->box.height != height) || (expansion)) { + tree_recalculate_node_positions(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(element->parent, false); + tree_redraw_area(tree, 0, element->box.y, 16384, 16384); + } else { + if (element->box.width != width) + tree_recalculate_node(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 node *node, bool recalculate_sizes) { + struct node_element *element; + int width, height; + + assert(node); + + width = node->box.width; + 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) + 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(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 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 = 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(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 node *node, bool expanded) { + for (; node; node = node->next) { + if (node->expanded != expanded) { + node->expanded = expanded; + tree_recalculate_node(node, false); + } + if ((node->child) && (node->expanded)) + tree_set_node_expanded(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(node->child, false); + tree_recalculate_node(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->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, int user_type) { + struct node_element *element; + for (element = &node->data; element; element = element->next) + if (element->user_type == user_type) 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; + + tree_selected_to_processing(tree->root); + if (destination->processing) + 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_recalculate_node_positions(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) + tree_selected_to_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_draw_line(tree, 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 ((node->expanded) && (node->child)) + tree_draw_line(tree, node->box.x + (NODE_INSTEP / 2), + node->data.box.y + node->data.box.height, 0, + (40 / 2)); + tree_draw_line(tree, 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) { + struct node *sibling; + + 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 { + sibling = link->child; + if (!sibling) { + link->child = node; + node->previous = NULL; + } else { + while (sibling->next) + sibling = sibling->next; + sibling->next = node; + node->previous = sibling; + } + node->parent = link; + node->next = NULL; + } +} + + +/** + * 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->child == NULL) + 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); + 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) { + struct node *next; + struct node *parent; + struct node_element *element; + + assert(node); + + while (node) { + next = node->next; + if (node->child) + tree_delete_node(tree, node->child, true); + parent = node->parent; + tree_delink_node(node); + for (element = &node->data; element; element = element->next) { + if (element->text) + free(element->text); + if (element->sprite) + free(element->sprite); /* \todo platform specific bits */ + } + while (node->data.next) { + element = node->data.next->next; + free(node->data.next); + node->data.next = element; + } + free(node); + + if (!siblings) + node = NULL; + else + node = next; + } + tree_recalculate_node_positions(tree->root); + tree_redraw_area(tree, 0, 0, 16384, 16384); /* \todo correct area */ + tree_recalculate_size(tree); +} + + +/** + * 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); + tree_set_node_sprite_folder(node); + if (parent) + tree_link_node(parent, node, false); + tree_recalculate_node(node, true); + 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 = squash_whitespace(title); + 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 title the node title + * \param url the node URL + * \param filetype the node filetype + * \param add_date the date added + * \param last_date the last visited date + * \param visits the number of visits + * \return the node created, or NULL for failure + */ +struct node *tree_create_URL_node(struct node *parent, const char *title, + const char *url, int filetype, int add_date, int last_date, int visits) { + struct node *node; + struct node_element *element; + + assert(title); + assert(url); + + node = tree_create_leaf_node(parent, title); + if (!node) + return NULL; + node->editable = true; + + element = tree_create_node_element(node, TREE_ELEMENT_URL); + if (element) { + element->user_data = filetype; + element->type = NODE_ELEMENT_TEXT; + element->text = squash_whitespace(url); + } + element = tree_create_node_element(node, TREE_ELEMENT_ADDED); + if (element) { + element->type = NODE_ELEMENT_TEXT; + element->user_data = add_date; + } + element = tree_create_node_element(node, TREE_ELEMENT_LAST_VISIT); + if (element) { + element->type = NODE_ELEMENT_TEXT; + element->user_data = last_date; + } + element = tree_create_node_element(node, TREE_ELEMENT_VISITS); + if (element) { + element->type = NODE_ELEMENT_TEXT; + element->user_data = visits; + } + + tree_update_URL_node(node); + + node->expanded = true; + tree_recalculate_node(node, true); + node->expanded = false; + + return node; +} + + +/** + * Resets all selected URL nodes from the tree. + * + * \param tree the tree to reset from + * \param node the node to reset + * \param selected whether to only reset selected nodes + */ +void tree_reset_URL_nodes(struct tree *tree, struct node *node, bool selected) { + for (; node; node = node->next) { + if (((node->selected) || (!selected)) && (!node->folder)) + tree_reset_URL_node(tree, node); + if (node->child) + tree_reset_URL_nodes(tree, node->child, + !((node->selected) | selected)); + } +} + + +/** + * Resets a tree entry for a URL + */ +void tree_reset_URL_node(struct tree *tree, struct node *node) { + struct node_element *element; + + assert(tree); + assert(node); + + element = tree_find_element(node, TREE_ELEMENT_LAST_VISIT); + if (element) + element->user_data = -1; + element = tree_find_element(node, TREE_ELEMENT_VISITS); + if (element) + element->user_data = 0; + tree_update_URL_node(node); + tree_recalculate_node(node, true); + + if (node->expanded) + tree_redraw_area(tree, node->box.x, node->box.y + node->data.box.height, + node->box.width, node->box.height - node->data.box.height); + +} + + +/** + * Creates an empty 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, int user_type) { + struct node_element *element; + struct node_element *link; + + assert(parent); + + element = calloc(sizeof(struct node_element), 1); + if (!element) return NULL; + element->parent = parent; + element->user_type = user_type; + + for (link = parent->data.next; ((link) && (link->user_type < user_type)); + link = link->next); + if (link) { + element->next = link->next; + link->next = element; + } else { + for (link = &parent->data; link->next; link = link->next); + link->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; + tree->width = tree_get_node_width(tree->root); + tree->height = tree_get_node_height(tree->root); + 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; +} diff --git a/desktop/tree.h b/desktop/tree.h new file mode 100644 index 000000000..632667ad0 --- /dev/null +++ b/desktop/tree.h @@ -0,0 +1,137 @@ +/* + * This file is part of NetSurf, http://netsurf.sourceforge.net/ + * Licensed under the GNU General Public License, + * http://www.opensource.org/licenses/gpl-license + * Copyright 2004 Richard Wilson <not_ginger_matt@users.sourceforge.net> + */ + +/** \file + * Generic tree handling (interface). + */ + +#ifndef _NETSURF_DESKTOP_TREE_H_ +#define _NETSURF_DESKTOP_TREE_H_ + +#include <stdbool.h> + +#define TREE_ELEMENT_URL 1 +#define TREE_ELEMENT_ADDED 2 +#define TREE_ELEMENT_LAST_VISIT 3 +#define TREE_ELEMENT_VISITS 4 + +#define NODE_INSTEP 40 + +struct node_sprite; + +typedef enum { + NODE_ELEMENT_TEXT, /* <-- Text only */ + NODE_ELEMENT_TEXT_PLUS_SPRITE, /* <-- Text and sprite */ + NODE_ELEMENT_SPRITE, /* <-- Sprite only */ +} node_element_type; + + +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 */ + char *text; /* <-- Text for the element */ + struct node_sprite *sprite; /* <-- Sprite for the element */ + struct node_element *next; /* <-- Next node element */ + int user_data; /* <-- Private user data */ + int user_type; /* <-- Private user data */ +}; + + +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 editable; /* <-- Whether the node is editable */ + 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 *previous; /* <-- Previous child of the parent */ + struct node *next; /* <-- Next child of the parent */ + +}; + +struct tree { + unsigned int handle; /* <-- User assigned handle */ + int offset_x; /* <-- User assigned tree x offset */ + int offset_y; /* <-- User assigned tree y offset */ + struct node *root; /* <-- Tree root element */ + int width; /* <-- Tree width */ + int height; /* <-- Tree height */ + int window_width; /* <-- Tree window width */ + int window_height; /* <-- Tree window height */ + int edit_handle; /* <-- Handle for editing information */ + bool movable; /* <-- Whether nodes can be moved */ + struct node_element *editing; /* <-- Node element being edited */ + char edit_buffer[256]; /* <-- Editing buffer */ + struct node *temp_selection; /* <-- Temporarily selected node */ +}; + + +/* Non-platform specific code */ +void tree_initialise(struct tree *tree); +void tree_initialise_nodes(struct node *root); +void tree_handle_node_changed(struct tree *tree, struct node *node, + bool recalculate_sizes, bool expansion); +void tree_handle_node_element_changed(struct tree *tree, + struct node_element *element); +struct node *tree_get_node_at(struct node *root, int x, int y, bool *furniture); +struct node_element *tree_get_node_element_at(struct node *node, int x, int y, + bool *furniture); +struct node_element *tree_find_element(struct node *node, int user_type); +void tree_move_selected_nodes(struct tree *tree, struct node *destination, + bool before); +bool tree_has_selection(struct node *node); +void tree_draw(struct tree *tree, int clip_x, int clip_y, int clip_width, + int clip_height); +void tree_link_node(struct node *link, struct node *node, bool before); +void tree_delink_node(struct node *node); +struct node *tree_create_folder_node(struct node *parent, const char *title); +struct node *tree_create_leaf_node(struct node *parent, const char *title); +void tree_set_node_sprite(struct node *node, const char *sprite, + const char *expanded); +void tree_set_node_sprite_folder(struct node *node); +struct node *tree_create_URL_node(struct node *parent, const char *title, + const char *url, int filetype, int add_date, int last_date, + int visits); +void tree_reset_URL_nodes(struct tree *tree, struct node *node, bool selected); +void tree_set_node_expanded(struct node *node, bool expanded); +void tree_set_node_selected(struct tree *tree, struct node *node, + bool selected); +void tree_handle_selection_area(struct tree *tree, int x, int y, int width, + int height, bool invert); +void tree_delete_selected_nodes(struct tree *tree, struct node *node); +void tree_delete_node(struct tree *tree, struct node *node, bool siblings); +void tree_recalculate_size(struct tree *tree); +bool tree_handle_expansion(struct tree *tree, struct node *node, bool expanded, + bool folder, bool leaf); +struct node *tree_get_selected_node(struct node *node); +struct node *tree_get_link_details(struct tree *tree, int x, int y, + bool *before); + + +/* Platform specific code */ +void tree_initialise_redraw(struct tree *tree); +void tree_redraw_area(struct tree *tree, int x, int y, int width, int height); +void tree_draw_line(struct tree *tree, int x, int y, int width, int height); +void tree_draw_node_element(struct tree *tree, struct node_element *element); +void tree_draw_node_expansion(struct tree *tree, struct node *node); +void tree_recalculate_node_element(struct node_element *element); +void tree_update_URL_node(struct node *node); +void tree_resized(struct tree *tree); + +#endif |