diff options
author | John Mark Bell <jmb@netsurf-browser.org> | 2010-10-05 19:14:46 +0000 |
---|---|---|
committer | John Mark Bell <jmb@netsurf-browser.org> | 2010-10-05 19:14:46 +0000 |
commit | 6173bb0e6c3bf51cd463f7bc4f725429d9087b2b (patch) | |
tree | de3e013699742960b97ee4a5eda240908d0ea8e6 /desktop | |
parent | 195c1ea3193f169c6825eca1fc6207e138126e98 (diff) | |
download | netsurf-6173bb0e6c3bf51cd463f7bc4f725429d9087b2b.tar.gz netsurf-6173bb0e6c3bf51cd463f7bc4f725429d9087b2b.tar.bz2 |
Merge treeview-redux to trunk
svn path=/trunk/netsurf/; revision=10865
Diffstat (limited to 'desktop')
-rw-r--r-- | desktop/browser.c | 1 | ||||
-rw-r--r-- | desktop/cookies.c | 531 | ||||
-rw-r--r-- | desktop/cookies.h | 36 | ||||
-rw-r--r-- | desktop/history_global_core.c | 464 | ||||
-rw-r--r-- | desktop/history_global_core.h | 44 | ||||
-rw-r--r-- | desktop/hotlist.c | 457 | ||||
-rw-r--r-- | desktop/hotlist.h | 54 | ||||
-rw-r--r-- | desktop/options.c | 363 | ||||
-rw-r--r-- | desktop/options.h | 5 | ||||
-rw-r--r-- | desktop/sslcert.c | 276 | ||||
-rw-r--r-- | desktop/sslcert.h | 43 | ||||
-rw-r--r-- | desktop/tree.c | 3090 | ||||
-rw-r--r-- | desktop/tree.h | 281 | ||||
-rw-r--r-- | desktop/tree_url_node.c | 846 | ||||
-rw-r--r-- | desktop/tree_url_node.h | 56 |
15 files changed, 5142 insertions, 1405 deletions
diff --git a/desktop/browser.c b/desktop/browser.c index 8c53813a6..b301b5fa3 100644 --- a/desktop/browser.c +++ b/desktop/browser.c @@ -46,6 +46,7 @@ #include "desktop/download.h" #include "desktop/frames.h" #include "desktop/history_core.h" +#include "desktop/hotlist.h" #include "desktop/gui.h" #include "desktop/options.h" #include "desktop/selection.h" diff --git a/desktop/cookies.c b/desktop/cookies.c new file mode 100644 index 000000000..c5dac5101 --- /dev/null +++ b/desktop/cookies.c @@ -0,0 +1,531 @@ +/* + * Copyright 2006 Richard Wilson <info@tinct.net> + * Copyright 2009 Paul Blokus <paul_pl@users.sourceforge.net> + * + * 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 <http://www.gnu.org/licenses/>. + */ + +/** \file + * Cookies (implementation). + */ + +#include <assert.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include "content/content.h" +#include "content/hlcache.h" +#include "content/urldb.h" +#include "desktop/cookies.h" +#include "desktop/options.h" +#include "desktop/tree.h" +#include "utils/messages.h" +#include "utils/log.h" +#include "utils/url.h" +#include "utils/utils.h" + +/** Flags for each type of cookie tree node. */ +enum tree_element_cookie { + TREE_ELEMENT_PERSISTENT = 0x01, + TREE_ELEMENT_VERSION = 0x02, + TREE_ELEMENT_SECURE = 0x03, + TREE_ELEMENT_LAST_USED = 0x04, + TREE_ELEMENT_EXPIRES = 0x05, + TREE_ELEMENT_PATH = 0x06, + TREE_ELEMENT_DOMAIN = 0x07, + TREE_ELEMENT_COMMENT = 0x08, + TREE_ELEMENT_VALUE = 0x09, +}; + +static struct tree *cookies_tree; +static struct node *cookies_tree_root; +static bool user_delete; +static hlcache_handle *folder_icon; +static hlcache_handle *cookie_icon; + + +/** + * Find an entry in the cookie tree + * + * \param node the node to check the children of + * \param title The title to find + * \return Pointer to node, or NULL if not found + */ +static struct node *cookies_find(struct node *node, const char *title) +{ + struct node *search; + struct node_element *element; + + for (search = tree_node_get_child(node); search; + search = tree_node_get_next(search)) { + element = tree_node_find_element(search, TREE_ELEMENT_TITLE, + NULL); + if (strcmp(title, tree_node_element_get_text(element)) == 0) + return search; + } + return NULL; +} + +/** + * Callback for all cookie tree nodes. + */ +static node_callback_resp cookies_node_callback(void *user_data, struct node_msg_data *msg_data) +{ + struct node *node = msg_data->node; + struct node_element *domain, *path; + const char *domain_t, *path_t, *name_t; + char *space; + bool is_folder = tree_node_is_folder(node); + + /* we don't remove any icons here */ + if (msg_data->msg == NODE_DELETE_ELEMENT_IMG) + return NODE_CALLBACK_HANDLED; + + /* let the tree handle events other than text data removal */ + if (msg_data->msg != NODE_DELETE_ELEMENT_TXT) + return NODE_CALLBACK_NOT_HANDLED; + + /* check if it's a domain folder */ + if (is_folder) + return NODE_CALLBACK_NOT_HANDLED; + + switch (msg_data->flag) { + case TREE_ELEMENT_TITLE: + if (!user_delete) + break; + /* get the rest of the cookie data */ + domain = tree_node_find_element(node, + TREE_ELEMENT_DOMAIN, NULL); + path = tree_node_find_element(node, TREE_ELEMENT_PATH, + NULL); + + if ((domain != NULL) && + (path != NULL)) { + domain_t = tree_node_element_get_text(domain) + + strlen(messages_get( + "TreeDomain")) - 4; + space = strchr(domain_t, ' '); + if (space != NULL) + *space = '\0'; + path_t = tree_node_element_get_text(path) + + strlen(messages_get("TreePath")) + - 4; + space = strchr(path_t, ' '); + if (space != NULL) + *space = '\0'; + name_t = msg_data->data.text; + urldb_delete_cookie(domain_t, path_t, name_t); + } + break; + default: + break; + } + + free(msg_data->data.text); + + return NODE_CALLBACK_HANDLED; +} + + +/** + * Updates a tree entry for a cookie. + * + * All information is copied from the cookie_data, and as such can + * be edited and should be freed. + * + * \param node The node to update + * \param data The cookie data to use + * \return true if node updated, or false for failure + */ +static bool cookies_update_cookie_node(struct node *node, + const struct cookie_data *data) +{ + struct node_element *element; + char buffer[32]; + + assert(data != NULL); + + /* update the value text */ + element = tree_node_find_element(node, TREE_ELEMENT_VALUE, NULL); + tree_update_element_text(cookies_tree, + element, + messages_get_buff("TreeValue", + data->value != NULL ? + data->value : + messages_get("TreeUnused"))); + + + /* update the comment text */ + if ((data->comment != NULL) && + (strcmp(data->comment, "") != 0)) { + element = tree_node_find_element(node, TREE_ELEMENT_COMMENT, NULL); + tree_update_element_text(cookies_tree, + element, + messages_get_buff("TreeComment", + data->comment)); + } + + /* update domain text */ + element = tree_node_find_element(node, TREE_ELEMENT_DOMAIN, element); + tree_update_element_text(cookies_tree, + element, + messages_get_buff("TreeDomain", + data->domain, + data->domain_from_set ? + messages_get("TreeHeaders") : + "")); + + /* update path text */ + element = tree_node_find_element(node, TREE_ELEMENT_PATH, element); + tree_update_element_text(cookies_tree, + element, + messages_get_buff("TreePath", data->path, + data->path_from_set ? + messages_get("TreeHeaders") : + "")); + + /* update expiry text */ + element = tree_node_find_element(node, TREE_ELEMENT_EXPIRES, element); + tree_update_element_text(cookies_tree, + element, + messages_get_buff("TreeExpires", + (data->expires > 0) + ? (data->expires == 1) + ? messages_get("TreeSession") + : ctime(&data->expires) + : messages_get("TreeUnknown"))); + + /* update last used text */ + element = tree_node_find_element(node, TREE_ELEMENT_LAST_USED, element); + tree_update_element_text(cookies_tree, + element, + messages_get_buff("TreeLastUsed", + (data->last_used > 0) ? + ctime(&data->last_used) : + messages_get("TreeUnknown"))); + + /* update secure text */ + element = tree_node_find_element(node, TREE_ELEMENT_SECURE, element); + tree_update_element_text(cookies_tree, + element, + messages_get_buff("TreeSecure", + data->secure ? + messages_get("Yes") : + messages_get("No"))); + + /* update version text */ + element = tree_node_find_element(node, TREE_ELEMENT_VERSION, element); + snprintf(buffer, sizeof(buffer), "TreeVersion%i", data->version); + tree_update_element_text(cookies_tree, + element, + messages_get_buff("TreeVersion", + messages_get(buffer))); + + /* update persistant text */ + element = tree_node_find_element(node, TREE_ELEMENT_PERSISTENT, element); + tree_update_element_text(cookies_tree, + element, + messages_get_buff("TreePersistent", + data->no_destroy ? + messages_get("Yes") : + messages_get("No"))); + + return node; +} + +/** + * Creates an empty 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 data the cookie data to use + * \return the node created, or NULL for failure + */ +static struct node *cookies_create_cookie_node(struct node *parent, + const struct cookie_data *data) +{ + struct node *node; + char *name; + + name = strdup(data->name); + if (name == NULL) { + LOG(("malloc failed")); + warn_user("NoMemory", 0); + return NULL; + } + + node = tree_create_leaf_node(cookies_tree, NULL, name, + false, false, false); + if (node == NULL) { + free(name); + return NULL; + } + + tree_set_node_user_callback(node, cookies_node_callback, NULL); + + tree_create_node_element(node, NODE_ELEMENT_TEXT, + TREE_ELEMENT_PERSISTENT, false); + + tree_create_node_element(node, NODE_ELEMENT_TEXT, + TREE_ELEMENT_VERSION, false); + + tree_create_node_element(node, NODE_ELEMENT_TEXT, + TREE_ELEMENT_SECURE, false); + + tree_create_node_element(node, NODE_ELEMENT_TEXT, + TREE_ELEMENT_LAST_USED, false); + + tree_create_node_element(node, NODE_ELEMENT_TEXT, + TREE_ELEMENT_EXPIRES, false); + + tree_create_node_element(node, NODE_ELEMENT_TEXT, + TREE_ELEMENT_PATH, false); + + tree_create_node_element(node, NODE_ELEMENT_TEXT, + TREE_ELEMENT_DOMAIN, false); + + if ((data->comment) && (strcmp(data->comment, ""))) + tree_create_node_element(node, NODE_ELEMENT_TEXT, + TREE_ELEMENT_COMMENT, false); + tree_create_node_element(node, NODE_ELEMENT_TEXT, + TREE_ELEMENT_VALUE, false); + tree_set_node_icon(cookies_tree, node, cookie_icon); + + if (!cookies_update_cookie_node(node, data)) + { + tree_delete_node(NULL, node, false); + return NULL; + } + + tree_link_node(cookies_tree, parent, node, false); + return node; +} + + +/** + * Called when scheduled event gets fired. Actually performs the update. + */ +static void cookies_schedule_callback(void *scheduled_data) +{ + const struct cookie_data *data = scheduled_data; + struct node *node = NULL; + struct node *cookie_node = NULL; + char *domain_cp; + + assert(data != NULL); + + node = cookies_find(cookies_tree_root, data->domain); + + if (node == NULL) { + domain_cp = strdup(data->domain); + if (domain_cp == NULL) { + LOG(("malloc failed")); + warn_user("NoMemory", 0); + return; + } + node = tree_create_folder_node(cookies_tree, + cookies_tree_root, domain_cp, + false, false, false); + if (node != NULL) { + tree_set_node_user_callback(node, cookies_node_callback, + NULL); + tree_set_node_icon(cookies_tree, node, folder_icon); + } + } + + if (node == NULL) + return; + + cookie_node = cookies_find(node, data->name); + if (cookie_node == NULL) + cookies_create_cookie_node(node, data); + else + cookies_update_cookie_node(cookie_node, data); + + return; +} + +/** + * Initialises cookies tree. + * + * \param data user data for the callbacks + * \param start_redraw callback function called before every redraw + * \param end_redraw callback function called after every redraw + * \return true on success, false on memory exhaustion + */ +bool cookies_initialise(struct tree *tree) +{ + + if (tree == NULL) + return false; + + folder_icon = tree_load_icon(tree_directory_icon_name); + cookie_icon = tree_load_icon(tree_content_icon_name); + + /* Create an empty tree */ + cookies_tree = tree; + cookies_tree_root = tree_get_root(cookies_tree); + + user_delete = false; + urldb_iterate_cookies(cookies_schedule_update); + tree_set_node_expanded(cookies_tree, cookies_tree_root, + true, true, true); + + return true; +} + + +/** + * Get flags with which the cookies tree should be created; + * + * \return the flags + */ +unsigned int cookies_get_tree_flags(void) +{ + return TREE_DELETE_EMPTY_DIRS; +} + + +/* exported interface documented in cookies.h */ +bool cookies_schedule_update(const struct cookie_data *data) +{ + assert(data != NULL); + assert(user_delete == false); + + schedule(100, cookies_schedule_callback, (void *)data); + + return true; +} + + +/* exported interface documented in cookies.h */ +void cookies_remove(const struct cookie_data *data) +{ + assert(data != NULL); + + schedule_remove(cookies_schedule_callback, (void *)data); +} + + +/** + * Free memory and release all other resources. + */ +void cookies_cleanup(void) +{ +} + +/* Actions to be connected to front end specific toolbars */ + +/** + * Delete nodes which are currently selected. + */ +void cookies_delete_selected(void) +{ + user_delete = true; + tree_delete_selected_nodes(cookies_tree, cookies_tree_root); + user_delete = false; +} + +/** + * Delete all nodes. + */ +void cookies_delete_all(void) +{ + bool needs_redraw = tree_get_redraw(cookies_tree); + if (needs_redraw) + tree_set_redraw(cookies_tree, false); + + user_delete = true; + tree_set_node_selected(cookies_tree, cookies_tree_root, true, true); + tree_delete_selected_nodes(cookies_tree, cookies_tree_root); + user_delete = false; + + if (needs_redraw) + tree_set_redraw(cookies_tree, true); +} + +/** + * Select all nodes in the tree. + */ +void cookies_select_all(void) +{ + tree_set_node_selected(cookies_tree, cookies_tree_root, true, true); +} + +/** + * Unselect all nodes. + */ +void cookies_clear_selection(void) +{ + tree_set_node_selected(cookies_tree, cookies_tree_root, true, false); +} + +/** + * Expand both domain and cookie nodes. + */ +void cookies_expand_all(void) +{ + tree_set_node_expanded(cookies_tree, cookies_tree_root, + true, true, true); +} + +/** + * Expand domain nodes only. + */ +void cookies_expand_domains(void) +{ + tree_set_node_expanded(cookies_tree, cookies_tree_root, + true, true, false); +} + +/** + * Expand cookie nodes only. + */ +void cookies_expand_cookies(void) +{ + tree_set_node_expanded(cookies_tree, cookies_tree_root, + true, false, true); +} + +/** + * Collapse both domain and cookie nodes. + */ +void cookies_collapse_all(void) +{ + tree_set_node_expanded(cookies_tree, cookies_tree_root, + false, true, true); +} + +/** + * Collapse domain nodes only. + */ +void cookies_collapse_domains(void) +{ + tree_set_node_expanded(cookies_tree, cookies_tree_root, + false, true, false); +} + +/** + * Collapse cookie nodes only. + */ +void cookies_collapse_cookies(void) +{ + tree_set_node_expanded(cookies_tree, cookies_tree_root, + false, false, true); +} diff --git a/desktop/cookies.h b/desktop/cookies.h index 4311957df..06278c006 100644 --- a/desktop/cookies.h +++ b/desktop/cookies.h @@ -25,8 +25,42 @@ #include <stdbool.h> +#include "desktop/tree.h" + struct cookie_data; -bool cookies_update(const char *domain, const struct cookie_data *data); +bool cookies_initialise(struct tree *tree); +unsigned int cookies_get_tree_flags(void); + +/** + * Perform cookie updates and addition. The update is only scheduled here. + * The actual update is performed in the callback function. + * + * \param data Data of cookie being updated. + * \return true (for urldb_iterate_entries) + */ +bool cookies_schedule_update(const struct cookie_data *data); + +/** + * Remove a cookie from the active set. + * The cookie is to be removed from the active set and no futher + * references made to the cookie data. + * + * \param data Data of cookie being removed. + */ +void cookies_remove(const struct cookie_data *data); + +void cookies_cleanup(void); + +void cookies_delete_selected(void); +void cookies_delete_all(void); +void cookies_select_all(void); +void cookies_clear_selection(void); +void cookies_expand_all(void); +void cookies_expand_domains(void); +void cookies_expand_cookies(void); +void cookies_collapse_all(void); +void cookies_collapse_domains(void); +void cookies_collapse_cookies(void); #endif diff --git a/desktop/history_global_core.c b/desktop/history_global_core.c new file mode 100644 index 000000000..b8cd9a5b0 --- /dev/null +++ b/desktop/history_global_core.c @@ -0,0 +1,464 @@ +/* + * Copyright 2005 Richard Wilson <info@tinct.net> + * Copyright 2009 Paul Blokus <paul_pl@users.sourceforge.net> + * + * 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 <http://www.gnu.org/licenses/>. + */ + + +#include <stdlib.h> + +#include "content/content.h" +#include "content/hlcache.h" +#include "content/urldb.h" +#include "desktop/browser.h" +#include "desktop/history_global_core.h" +#include "desktop/plotters.h" +#include "desktop/tree.h" +#include "desktop/tree_url_node.h" + +#ifdef riscos +#include "riscos/gui.h" +#endif +#include "utils/messages.h" +#include "utils/utils.h" +#include "utils/log.h" + +#define MAXIMUM_BASE_NODES 16 +#define GLOBAL_HISTORY_RECENT_URLS 16 +#define URL_CHUNK_LENGTH 512 + +static struct node *global_history_base_node[MAXIMUM_BASE_NODES]; +static int global_history_base_node_time[MAXIMUM_BASE_NODES]; +static int global_history_base_node_count = 0; + +static bool global_history_initialised; + +static struct tree *global_history_tree; +static struct node *global_history_tree_root; + +static hlcache_handle *folder_icon; + +static const char *const weekday_msg_name [] = +{ + "Sunday", + "Monday", + "Tuesday", + "Wednesday", + "Thursday", + "Friday", + "Saturday" +}; + +/** + * Find an entry in the global history + * + * \param url The URL to find + * \return Pointer to node, or NULL if not found + */ +static struct node *history_global_find(const char *url) +{ + int i; + struct node *node; + const char *text; + + for (i = 0; i < global_history_base_node_count; i++) { + if (!tree_node_is_deleted(global_history_base_node[i])) { + node = tree_node_get_child(global_history_base_node[i]); + for (; node != NULL; node = tree_node_get_next(node)) { + text = tree_url_node_get_url(node); + if ((text != NULL) && !strcmp(url, text)) + return node; + } + } + } + return NULL; +} + +/** + * Internal routine to actually perform global history addition + * + * \param url The URL to add + * \param data URL data associated with URL + * \return true (for urldb_iterate_entries) + */ +static bool global_history_add_internal(const char *url, + const struct url_data *data) +{ + int i, j; + struct node *parent = NULL; + struct node *link; + struct node *node; + bool before = false; + int visit_date; + + assert((url != NULL) && (data != NULL)); + + visit_date = data->last_visit; + + /* find parent node */ + for (i = 0; i < global_history_base_node_count; i++) { + if (global_history_base_node_time[i] <= visit_date) { + parent = global_history_base_node[i]; + break; + } + } + + /* the entry is too old to care about */ + if (parent == NULL) + return true; + + if (tree_node_is_deleted(parent)) { + /* parent was deleted, so find place to insert it */ + link = global_history_tree_root; + + for (j = global_history_base_node_count - 1; j >= 0; j--) { + if (!tree_node_is_deleted(global_history_base_node[j]) && + global_history_base_node_time[j] > + global_history_base_node_time[i]) { + link = global_history_base_node[j]; + before = true; + break; + } + } + + tree_set_node_selected(global_history_tree, + parent, true, false); + tree_set_node_expanded(global_history_tree, + parent, false, true, true); + tree_link_node(global_history_tree, link, parent, before); + } + + /* find any previous occurance */ + if (global_history_initialised == false) { + node = history_global_find(url); + if (node != NULL) { + tree_update_URL_node(global_history_tree, + node, url, data, true); + tree_delink_node(global_history_tree, node); + tree_link_node(global_history_tree, parent, node, + false); + return true; + } + } + + /* Add the node at the bottom */ + node = tree_create_URL_node_shared(global_history_tree, + parent, url, data, + tree_url_node_callback, NULL); + + return true; +} + +static node_callback_resp +history_global_node_callback(void *user_data, + struct node_msg_data *msg_data) +{ + if (msg_data->msg == NODE_DELETE_ELEMENT_IMG) + return NODE_CALLBACK_HANDLED; + return NODE_CALLBACK_NOT_HANDLED; +} + +/** + * Initialises a single grouping node for the global history tree. + * + * \return false on memory exhaustion, true otherwise + */ +static bool history_global_initialise_node(const char *title, + time_t base, int days_back) +{ + struct tm *full_time; + char *buffer; + struct node *node; + + base += days_back * 60 * 60 * 24; + if (title == NULL) { + full_time = localtime(&base); + buffer = strdup(messages_get(weekday_msg_name[full_time->tm_wday])); + } else { + buffer = strdup(title); + } + + if (buffer == NULL) { + LOG(("malloc failed")); + warn_user("NoMemory", 0); + return false; + } + + node = tree_create_folder_node(NULL, NULL, buffer, + false, true, true); + if (node == NULL) { + LOG(("malloc failed")); + warn_user("NoMemory", 0); + free(buffer); + return false; + } + if (folder_icon != NULL) + tree_set_node_icon(global_history_tree, node, folder_icon); + tree_set_node_user_callback(node, history_global_node_callback, NULL); + + global_history_base_node[global_history_base_node_count] = node; + global_history_base_node_time[global_history_base_node_count] = base; + global_history_base_node_count++; + + return true; +} + +/** + * Initialises the grouping nodes(Today, Yesterday etc.) for the global history + * tree. + * + * \return false on memory exhaustion, true otherwise + */ +static bool history_global_initialise_nodes(void) +{ + struct tm *full_time; + time_t t; + int weekday; + int i; + + /* get the current time */ + t = time(NULL); + if (t == -1) { + LOG(("time info unaviable")); + return false; + } + + /* get the time at the start of today */ + full_time = localtime(&t); + weekday = full_time->tm_wday; + full_time->tm_sec = 0; + full_time->tm_min = 0; + full_time->tm_hour = 0; + t = mktime(full_time); + if (t == -1) { + LOG(("mktime failed")); + return false; + } + + history_global_initialise_node(messages_get("DateToday"), t, 0); + if (weekday > 0) + if (!history_global_initialise_node( + messages_get("DateYesterday"), t, -1)) + return false; + for (i = 2; i <= weekday; i++) + if (!history_global_initialise_node(NULL, t, -i)) + return false; + + if (!history_global_initialise_node(messages_get("Date1Week"), + t, -weekday - 7)) + return false; + if (!history_global_initialise_node(messages_get("Date2Week"), + t, -weekday - 14)) + return false; + if (!history_global_initialise_node(messages_get("Date3Week"), + t, -weekday - 21)) + return false; + + return true; +} + +/** + * Initialises the global history tree. + * + * \param data user data for the callbacks + * \param start_redraw callback function called before every redraw + * \param end_redraw callback function called after every redraw + * \return true on success, false on memory exhaustion + */ +bool history_global_initialise(struct tree *tree) +{ + struct node *first; + + folder_icon = tree_load_icon(tree_directory_icon_name); + tree_url_node_init(); + + if (tree == NULL) + return false; + + global_history_tree = tree; + global_history_tree_root = tree_get_root(global_history_tree); + + if (!history_global_initialise_nodes()) + return false; + + global_history_initialised = true; + urldb_iterate_entries(global_history_add_internal); + global_history_initialised = false; + tree_set_node_expanded(global_history_tree, global_history_tree_root, + false, true, true); + first = tree_node_get_child(global_history_tree_root); + if (first != NULL) + tree_set_node_expanded(global_history_tree, first, + true, false, false); + + return true; +} + + +/** + * Get flags with which the global history tree should be created; + * + * \return the flags + */ +unsigned int history_global_get_tree_flags(void) +{ + return TREE_NO_FLAGS; +} + + +/** + * Deletes the global history tree. + */ +void history_global_cleanup(void) +{ +} + + +/** + * Adds a url to the global history. + * + * \param url the url to be added + */ +void global_history_add(const char *url) +{ + const struct url_data *data; + + data = urldb_get_url_data(url); + if (data == NULL) + return; + + global_history_add_internal(url, data); +} + + +/* Actions to be connected to front end specific toolbars */ + +/** + * Save the global history in a human-readable form under the given location. + * + * \param path the path where the history will be saved + */ +bool history_global_export(const char *path) +{ + return tree_urlfile_save(global_history_tree, path, "NetSurf history"); +} + +/** + * Delete nodes which are currently selected. + */ +void history_global_delete_selected(void) +{ + tree_delete_selected_nodes(global_history_tree, + global_history_tree_root); +} + +/** + * Delete all nodes. + */ +void history_global_delete_all(void) +{ + bool redraw_needed = tree_get_redraw(global_history_tree); + if (redraw_needed) + tree_set_redraw(global_history_tree, false); + + tree_set_node_selected(global_history_tree, global_history_tree_root, + true, true); + tree_delete_selected_nodes(global_history_tree, + global_history_tree_root); + + if (redraw_needed) + tree_set_redraw(global_history_tree, true); +} + +/** + * Select all nodes in the tree. + */ +void history_global_select_all(void) +{ + tree_set_node_selected(global_history_tree, global_history_tree_root, + true, true); +} + +/** + * Unselect all nodes. + */ +void history_global_clear_selection(void) +{ + tree_set_node_selected(global_history_tree, global_history_tree_root, + true, false); +} + +/** + * Expand grouping folders and history entries. + */ +void history_global_expand_all(void) +{ + tree_set_node_expanded(global_history_tree, global_history_tree_root, + true, true, true); +} + +/** + * Expand grouping folders only. + */ +void history_global_expand_directories(void) +{ + tree_set_node_expanded(global_history_tree, global_history_tree_root, + true, true, false); +} + +/** + * Expand history entries only. + */ +void history_global_expand_addresses(void) +{ + tree_set_node_expanded(global_history_tree, global_history_tree_root, + true, false, true); +} + +/** + * Collapse grouping folders and history entries. + */ +void history_global_collapse_all(void) +{ + tree_set_node_expanded(global_history_tree, global_history_tree_root, + false, true, true); +} + +/** + * Collapse grouping folders only. + */ +void history_global_collapse_directories(void) +{ + tree_set_node_expanded(global_history_tree, global_history_tree_root, + false, true, false); +} + +/** + * Collapse history entries only. + */ +void history_global_collapse_addresses(void) +{ + tree_set_node_expanded(global_history_tree, global_history_tree_root, + false, false, true); +} + +/** + * Open the selected entries in seperate browser windows. + */ +void history_global_launch_selected(void) +{ + tree_launch_selected(global_history_tree); +} diff --git a/desktop/history_global_core.h b/desktop/history_global_core.h new file mode 100644 index 000000000..97c578f3d --- /dev/null +++ b/desktop/history_global_core.h @@ -0,0 +1,44 @@ +/* + * Copyright 2005 Richard Wilson <info@tinct.net> + * Copyright 2009 Paul Blokus <paul_pl@users.sourceforge.net> + * + * 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef _NETSURF_DESKTOP_HISTORY_GLOBAL_H_ +#define _NETSURF_DESKTOP_HISTORY_GLOBAL_H_ + +#include <stdbool.h> + +#include "desktop/tree.h" + +bool history_global_initialise(struct tree *tree); +unsigned int history_global_get_tree_flags(void); +void history_global_cleanup(void); + +bool history_global_export(const char *path); +void history_global_delete_selected(void); +void history_global_delete_all(void); +void history_global_select_all(void); +void history_global_clear_selection(void); +void history_global_expand_all(void); +void history_global_expand_directories(void); +void history_global_expand_addresses(void); +void history_global_collapse_all(void); +void history_global_collapse_directories(void); +void history_global_collapse_addresses(void); +void history_global_launch_selected(void); + +#endif diff --git a/desktop/hotlist.c b/desktop/hotlist.c new file mode 100644 index 000000000..09be05709 --- /dev/null +++ b/desktop/hotlist.c @@ -0,0 +1,457 @@ +/* + * Copyright 2004, 2005 Richard Wilson <info@tinct.net> + * Copyright 2009 Paul Blokus <paul_pl@users.sourceforge.net> + * + * 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 <http://www.gnu.org/licenses/>. + */ + +#include <ctype.h> +#include <stdlib.h> + +#include "content/content.h" +#include "content/hlcache.h" +#include "content/urldb.h" +#include "desktop/browser.h" +#include "desktop/hotlist.h" +#include "desktop/plotters.h" +#include "desktop/tree.h" +#include "desktop/tree_url_node.h" + +#include "utils/messages.h" +#include "utils/utils.h" +#include "utils/log.h" + +#define URL_CHUNK_LENGTH 512 + +static struct tree *hotlist_tree; +static struct node *hotlist_tree_root; + +static bool creating_node; +static hlcache_handle *folder_icon; + +static const struct { + const char *url; + const char *msg_key; +} hotlist_default_entries[] = { + { "http://www.netsurf-browser.org/", "HotlistHomepage" }, + { "http://www.netsurf-browser.org/downloads/riscos/testbuilds", + "HotlistTestBuild" }, + { "http://www.netsurf-browser.org/documentation", + "HotlistDocumentation" }, + { "http://sourceforge.net/tracker/?atid=464312&group_id=51719", + "HotlistBugTracker" }, + { "http://sourceforge.net/tracker/?atid=464315&group_id=51719", + "HotlistFeatureRequest" } +}; +#define HOTLIST_ENTRIES_COUNT (sizeof(hotlist_default_entries) / sizeof(hotlist_default_entries[0])) + +static node_callback_resp hotlist_node_callback(void *user_data, + struct node_msg_data *msg_data) +{ + struct node *node = msg_data->node; + const char *text; + char *norm_text; + bool is_folder = tree_node_is_folder(node); + + switch (msg_data->msg) { + case NODE_ELEMENT_EDIT_FINISHED: + if (creating_node && + (is_folder == false) && + (msg_data->flag == TREE_ELEMENT_TITLE)) { + tree_url_node_edit_url(hotlist_tree, node); + } else { + creating_node = false; + } + return NODE_CALLBACK_HANDLED; + + case NODE_ELEMENT_EDIT_FINISHING: + if (creating_node && (is_folder == false)) + return tree_url_node_callback(hotlist_tree, msg_data); + + if (is_folder == true) { + text = msg_data->data.text; + while (isspace(*text)) + text++; + norm_text = strdup(text); + if (norm_text == NULL) { + LOG(("malloc failed")); + warn_user("NoMemory", 0); + return NODE_CALLBACK_REJECT; + } + /* don't allow zero length entry text, return false */ + if (norm_text[0] == '\0') { + warn_user("NoNameError", 0); + msg_data->data.text = NULL; + return NODE_CALLBACK_CONTINUE; + } + msg_data->data.text = norm_text; + } + break; + + case NODE_DELETE_ELEMENT_IMG: + return NODE_CALLBACK_HANDLED; + + default: + if (is_folder == false) + return tree_url_node_callback(hotlist_tree, msg_data); + } + + return NODE_CALLBACK_NOT_HANDLED; +} + + +bool hotlist_initialise(struct tree *tree, const char *hotlist_path) +{ + struct node *node; + const struct url_data *url_data; + char *name; + int hlst_loop; + + /* Either load or create a hotlist */ + + creating_node = false; + + folder_icon = tree_load_icon(tree_directory_icon_name); + + tree_url_node_init(); + + if (tree == NULL) + return false; + + hotlist_tree = tree; + hotlist_tree_root = tree_get_root(hotlist_tree); + + if (tree_urlfile_load(hotlist_path, + hotlist_tree, + hotlist_node_callback, + NULL)) { + return true; + } + + + /* failed to load hotlist file, use default list */ + name = strdup("NetSurf"); + if (name == NULL) { + LOG(("malloc failed")); + warn_user("NoMemory", 0); + return false; + } + node = tree_create_folder_node(hotlist_tree, hotlist_tree_root, + name, true, false, false); + if (node == NULL) { + free(name); + return false; + } + + tree_set_node_user_callback(node, hotlist_node_callback, NULL); + tree_set_node_icon(hotlist_tree, node, folder_icon); + + for (hlst_loop = 0; hlst_loop != HOTLIST_ENTRIES_COUNT; hlst_loop++) { + url_data = urldb_get_url_data(hotlist_default_entries[hlst_loop].url); + if (url_data == NULL) { + urldb_add_url(hotlist_default_entries[hlst_loop].url); + urldb_set_url_persistence( + hotlist_default_entries[hlst_loop].url, + true); + url_data = urldb_get_url_data( + hotlist_default_entries[hlst_loop].url); + } + if (url_data != NULL) { + tree_create_URL_node(hotlist_tree, node, + hotlist_default_entries[hlst_loop].url, + messages_get(hotlist_default_entries[hlst_loop].msg_key), + hotlist_node_callback, NULL); + tree_update_URL_node(hotlist_tree, node, + hotlist_default_entries[hlst_loop].url, + url_data, false); + } + } + + + + return true; +} + + +/** + * Get flags with which the hotlist tree should be created; + * + * \return the flags + */ +unsigned int hotlist_get_tree_flags(void) +{ + return TREE_MOVABLE; +} + + +/** + * Deletes the global history tree and saves the hotlist. + * \param hotlist_path the path where the hotlist should be saved + */ +void hotlist_cleanup(const char *hotlist_path) +{ + hotlist_export(hotlist_path); +} + + +/** + * Informs the hotlist that some content has been visited. Internal procedure. + * + * \param content the content visited + * \param node the node to update siblings and children of + */ +static void hotlist_visited_internal(hlcache_handle *content, struct node *node) +{ + struct node *child; + const char *text; + const char *url; + + if (content == NULL || + content_get_url(content) == NULL || + hotlist_tree == NULL) + return; + + url = content_get_url(content); + + for (; node; node = tree_node_get_next(node)) { + if (!tree_node_is_folder(node)) { + text = tree_url_node_get_url(node); + if (strcmp(text, url) == 0) { + tree_update_URL_node(hotlist_tree, node, + url, NULL, false); + } + } + child = tree_node_get_child(node); + if (child != NULL) { + hotlist_visited_internal(content, child); + } + } +} + +/** + * Informs the hotlist that some content has been visited + * + * \param content the content visited + */ +void hotlist_visited(hlcache_handle *content) +{ + if (hotlist_tree != NULL) { + hotlist_visited_internal(content, tree_get_root(hotlist_tree)); + } +} + +/** + * Save the hotlist in a human-readable form under the given location. + * + * \param path the path where the hotlist will be saved + */ +bool hotlist_export(const char *path) +{ + return tree_urlfile_save(hotlist_tree, path, "NetSurf hotlist"); +} + +/** + * Edit the node which is currently selected. Works only if one node is + * selected. + */ +void hotlist_edit_selected(void) +{ + struct node *node; + struct node_element *element; + + node = tree_get_selected_node(hotlist_tree_root); + + if (node != NULL) { + creating_node = true; + element = tree_node_find_element(node, TREE_ELEMENT_TITLE, NULL); + tree_start_edit(hotlist_tree, element); + } +} + +/** + * Delete nodes which are currently selected. + */ +void hotlist_delete_selected(void) +{ + tree_delete_selected_nodes(hotlist_tree, hotlist_tree_root); +} + +/** + * Select all nodes in the tree. + */ +void hotlist_select_all(void) +{ + tree_set_node_selected(hotlist_tree, hotlist_tree_root, + true, true); +} + +/** + * Unselect all nodes. + */ +void hotlist_clear_selection(void) +{ + tree_set_node_selected(hotlist_tree, hotlist_tree_root, + true, false); +} + +/** + * Expand grouping folders and history entries. + */ +void hotlist_expand_all(void) +{ + tree_set_node_expanded(hotlist_tree, hotlist_tree_root, + true, true, true); +} + +/** + * Expand grouping folders only. + */ +void hotlist_expand_directories(void) +{ + tree_set_node_expanded(hotlist_tree, hotlist_tree_root, + true, true, false); +} + +/** + * Expand history entries only. + */ +void hotlist_expand_addresses(void) +{ + tree_set_node_expanded(hotlist_tree, hotlist_tree_root, + true, false, true); +} + +/** + * Collapse grouping folders and history entries. + */ +void hotlist_collapse_all(void) +{ + tree_set_node_expanded(hotlist_tree, hotlist_tree_root, + false, true, true); +} + +/** + * Collapse grouping folders only. + */ +void hotlist_collapse_directories(void) +{ + tree_set_node_expanded(hotlist_tree, hotlist_tree_root, + false, true, false); +} + +/** + * Collapse history entries only. + */ +void hotlist_collapse_addresses(void) +{ + tree_set_node_expanded(hotlist_tree, + hotlist_tree_root, false, false, true); +} + +/** + * Add a folder node. + */ +void hotlist_add_folder(void) +{ + struct node *node; + struct node_element *element; + char *title = strdup("Untitled"); + + if (title == NULL) { + LOG(("malloc failed")); + warn_user("NoMemory", 0); + return; + } + creating_node = true; + node = tree_create_folder_node(hotlist_tree, hotlist_tree_root, title, + true, false, false); + if (node == NULL) { + free(title); + return; + } + tree_set_node_user_callback(node, hotlist_node_callback, NULL); + tree_set_node_icon(hotlist_tree, node, folder_icon); + element = tree_node_find_element(node, TREE_ELEMENT_TITLE, NULL); + tree_start_edit(hotlist_tree, element); +} + +/** + * Add an entry node. + */ +void hotlist_add_entry(void) +{ + struct node *node; + creating_node = true; + node = tree_create_URL_node(hotlist_tree, hotlist_tree_root, "Address", + "Untitled", hotlist_node_callback, NULL); + + if (node == NULL) + return; + tree_set_node_user_callback(node, hotlist_node_callback, NULL); + tree_url_node_edit_title(hotlist_tree, node); +} + +/** + * Adds the currently viewed page to the hotlist + */ +void hotlist_add_page(const char *url) +{ + const struct url_data *data; + struct node *node; + + if (url == NULL) + return; + data = urldb_get_url_data(url); + if (data == NULL) + return; + + node = tree_create_URL_node(hotlist_tree, hotlist_tree_root, url, NULL, + hotlist_node_callback, NULL); + tree_update_URL_node(hotlist_tree, node, url, data, false); +} + +/** + * Adds the currently viewed page to the hotlist at the given cooridinates + * \param url url of the page + * \param x X cooridinate with respect to tree origin + * \param y Y cooridinate with respect to tree origin + */ +void hotlist_add_page_xy(const char *url, int x, int y) +{ + const struct url_data *data; + struct node *link, *node; + bool before; + + data = urldb_get_url_data(url); + if (data == NULL) { + urldb_add_url(url); + urldb_set_url_persistence(url, true); + data = urldb_get_url_data(url); + } + if (data != NULL) { + link = tree_get_link_details(hotlist_tree, x, y, &before); + node = tree_create_URL_node(NULL, NULL, url, + NULL, hotlist_node_callback, NULL); + tree_link_node(hotlist_tree, link, node, before); + } +} + +/** + * Open the selected entries in separate browser windows. + */ +void hotlist_launch_selected(void) +{ + tree_launch_selected(hotlist_tree); +} diff --git a/desktop/hotlist.h b/desktop/hotlist.h new file mode 100644 index 000000000..84f573a90 --- /dev/null +++ b/desktop/hotlist.h @@ -0,0 +1,54 @@ +/* + * Copyright 2004, 2005 Richard Wilson <info@tinct.net> + * Copyright 2009 Paul Blokus <paul_pl@users.sourceforge.net> + * + * 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 <http://www.gnu.org/licenses/>. + */ + + + +/** \file + * Hotlist (interface). + */ + +#ifndef _NETSURF_DESKTOP_HOTLIST_H_ +#define _NETSURF_DESKTOP_HOTLIST_H_ + +#include <stdbool.h> + +#include "desktop/tree.h" + +bool hotlist_initialise(struct tree *tree, const char *hotlist_path); +unsigned int hotlist_get_tree_flags(void); +void hotlist_cleanup(const char *hotlist_path); + +bool hotlist_export(const char *path); +void hotlist_edit_selected(void); +void hotlist_delete_selected(void); +void hotlist_select_all(void); +void hotlist_clear_selection(void); +void hotlist_expand_all(void); +void hotlist_expand_directories(void); +void hotlist_expand_addresses(void); +void hotlist_collapse_all(void); +void hotlist_collapse_directories(void); +void hotlist_collapse_addresses(void); +void hotlist_add_folder(void); +void hotlist_add_entry(void); +void hotlist_add_page(const char *url); +void hotlist_add_page_xy(const char *url, int x, int y); +void hotlist_launch_selected(void); + +#endif diff --git a/desktop/options.c b/desktop/options.c index 9ed8b192c..47b42dc3b 100644 --- a/desktop/options.c +++ b/desktop/options.c @@ -31,16 +31,10 @@ #include <stdio.h> #include <string.h> #include <strings.h> -#include <libxml/HTMLparser.h> -#include <libxml/HTMLtree.h> -#include "content/urldb.h" #include "css/css.h" #include "desktop/options.h" #include "desktop/plot_style.h" -#include "desktop/tree.h" #include "utils/log.h" -#include "utils/messages.h" -#include "utils/url.h" #include "utils/utils.h" #if defined(riscos) @@ -145,6 +139,7 @@ unsigned int option_min_reflow_period = 100; /* time in cs */ #else unsigned int option_min_reflow_period = 25; /* time in cs */ #endif +char *option_tree_icons_dir = NULL; bool option_core_select_menu = false; /** top margin of exported page*/ int option_margin_top = DEFAULT_MARGIN_TOP_MM; @@ -247,6 +242,7 @@ struct { { "scale", OPTION_INTEGER, &option_scale }, { "incremental_reflow", OPTION_BOOL, &option_incremental_reflow }, { "min_reflow_period", OPTION_INTEGER, &option_min_reflow_period }, + { "tree_icons_dir", OPTION_STRING, &option_tree_icons_dir }, { "core_select_menu", OPTION_BOOL, &option_core_select_menu }, /* Fetcher options */ { "max_fetchers", OPTION_INTEGER, &option_max_fetchers }, @@ -276,13 +272,6 @@ struct { #define option_table_entries (sizeof option_table / sizeof option_table[0]) -static void options_load_tree_directory(xmlNode *ul, struct node *directory); -static void options_load_tree_entry(xmlNode *li, struct node *directory); -xmlNode *options_find_tree_element(xmlNode *node, const char *name); -bool options_save_tree_directory(struct node *directory, xmlNode *node); -bool options_save_tree_entry(struct node *entry, xmlNode *node); - - /** * Read options from a file. * @@ -429,351 +418,3 @@ void options_dump(void) fprintf(stderr, "\n"); } } - -/** - * 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_tree(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_tree_element((xmlNode *) doc, "html"); - body = options_find_tree_element(html, "body"); - ul = options_find_tree_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) { - free(tree); - xmlFreeDoc(doc); - - return NULL; - } - - options_load_tree_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_tree_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((const char *) n->name, "li") == 0) { - /* entry */ - options_load_tree_entry(n, directory); - - } else if (strcmp((const char *) 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((const char *) 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) { - free(title); - - return; - } - options_load_tree_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_tree_entry(xmlNode *li, struct node *directory) { - char *url = NULL, *url1 = NULL; - char *title = NULL; - struct node *entry; - xmlNode *n; - const struct url_data *data; - url_func_result res; - - for (n = li->children; n; n = n->next) { - /* The li must contain an "a" element */ - if (n->type == XML_ELEMENT_NODE && - strcmp((const char *) n->name, "a") == 0) { - url1 = (char *) xmlGetProp(n, (const xmlChar *) "href"); - title = (char *) xmlNodeGetContent(n); - } - } - - if (!url1 || !title) { - warn_user("HotlistLoadError", "(Missing <a> in <li> or " - "memory exhausted.)"); - return; - } - - /* We're loading external input. - * This may be garbage, so attempt to normalise - */ - res = url_normalize(url1, &url); - if (res != URL_FUNC_OK) { - LOG(("Failed normalising '%s'", url1)); - - if (res == URL_FUNC_NOMEM) - warn_user("NoMemory", NULL); - - xmlFree(url1); - xmlFree(title); - - return; - } - - /* No longer need this */ - xmlFree(url1); - - data = urldb_get_url_data(url); - if (!data) { - /* No entry in database, so add one */ - urldb_add_url(url); - /* now attempt to get url data */ - data = urldb_get_url_data(url); - } - if (!data) { - xmlFree(title); - free(url); - - return; - } - - /* Make this URL persistent */ - urldb_set_url_persistence(url, true); - - if (!data->title) - urldb_set_url_title(url, title); - - entry = tree_create_URL_node(directory, url, data, title); - if (entry == NULL) { - /** \todo why isn't this fatal? */ - warn_user("NoMemory", 0); - } - - xmlFree(title); - free(url); -} - - -/** - * 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_tree_element(xmlNode *node, const char *name) { - xmlNode *n; - if (!node) - return 0; - for (n = node->children; - n && !(n->type == XML_ELEMENT_NODE && - strcmp((const char *) 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_tree(struct tree *tree, const char *filename, const char *page_title) { - int res; - xmlDoc *doc; - xmlNode *html, *head, *title, *body; - - /* Unfortunately the Browse Hotlist format is invalid HTML, - * so this is a lie. */ - doc = htmlNewDoc( - (const xmlChar *) "http://www.w3.org/TR/html4/strict.dtd", - (const xmlChar *) "-//W3C//DTD HTML 4.01//EN"); - if (!doc) { - warn_user("NoMemory", 0); - return false; - } - - html = xmlNewNode(NULL, (const xmlChar *) "html"); - if (!html) { - warn_user("NoMemory", 0); - xmlFreeDoc(doc); - return false; - } - xmlDocSetRootElement(doc, html); - - head = xmlNewChild(html, NULL, (const xmlChar *) "head", NULL); - if (!head) { - warn_user("NoMemory", 0); - xmlFreeDoc(doc); - return false; - } - - title = xmlNewTextChild(head, NULL, (const xmlChar *) "title", - (const xmlChar *) page_title); - if (!title) { - warn_user("NoMemory", 0); - xmlFreeDoc(doc); - return false; - } - - body = xmlNewChild(html, NULL, (const xmlChar *) "body", NULL); - if (!body) { - warn_user("NoMemory", 0); - xmlFreeDoc(doc); - return false; - } - - if (!options_save_tree_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_tree_directory(struct node *directory, xmlNode *node) { - struct node *child; - xmlNode *ul, *h4; - - ul = xmlNewChild(node, NULL, (const xmlChar *) "ul", NULL); - if (!ul) - return false; - - for (child = directory->child; child; child = child->next) { - if (!child->folder) { - /* entry */ - if (!options_save_tree_entry(child, ul)) - return false; - } else { - /* directory */ - /* invalid HTML */ - h4 = xmlNewTextChild(ul, NULL, - (const xmlChar *) "h4", - (const xmlChar *) child->data.text); - if (!h4) - return false; - - if (!options_save_tree_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_tree_entry(struct node *entry, xmlNode *node) { - xmlNode *li, *a; - xmlAttr *href; - struct node_element *element; - - li = xmlNewChild(node, NULL, (const xmlChar *) "li", NULL); - if (!li) - return false; - - a = xmlNewTextChild(li, NULL, (const xmlChar *) "a", - (const xmlChar *) entry->data.text); - if (!a) - return false; - - element = tree_find_element(entry, TREE_ELEMENT_URL); - if (!element) - return false; - href = xmlNewProp(a, (const xmlChar *) "href", - (const xmlChar *) element->text); - if (!href) - return false; - return true; -} diff --git a/desktop/options.h b/desktop/options.h index ca92ee90a..2779692b6 100644 --- a/desktop/options.h +++ b/desktop/options.h @@ -85,6 +85,7 @@ extern int option_toolbar_status_width; extern int option_scale; extern bool option_incremental_reflow; extern unsigned int option_min_reflow_period; +extern char *option_tree_icons_dir; extern bool option_core_select_menu; extern int option_margin_top; @@ -114,8 +115,4 @@ void options_read(const char *path); void options_write(const char *path); void options_dump(void); -struct tree *options_load_tree(const char *filename); -bool options_save_tree(struct tree *tree, const char *filename, - const char *page_title); - #endif diff --git a/desktop/sslcert.c b/desktop/sslcert.c new file mode 100644 index 000000000..2d10b4719 --- /dev/null +++ b/desktop/sslcert.c @@ -0,0 +1,276 @@ +/* + * Copyright 2009 Paul Blokus <paul_pl@users.sourceforge.net> + * + * 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 <http://www.gnu.org/licenses/>. + */ + +/** \file + * SSL Certificate verification UI (implementation) + */ + +#include "utils/config.h" + +#include <assert.h> +#include <stdbool.h> +#include <string.h> +#include "content/content.h" +#include "content/fetch.h" +#include "content/hlcache.h" +#include "content/urldb.h" +#include "desktop/browser.h" +#include "desktop/sslcert.h" +#include "desktop/tree.h" +#include "utils/log.h" +#include "utils/messages.h" +#include "utils/utils.h" + +/** Flags for each type of ssl tree node. */ +enum tree_element_ssl { + TREE_ELEMENT_SSL_VERSION = 0x01, + TREE_ELEMENT_SSL_VALID_FROM = 0x02, + TREE_ELEMENT_SSL_VALID_TO = 0x03, + TREE_ELEMENT_SSL_CERT_TYPE = 0x04, + TREE_ELEMENT_SSL_SERIAL = 0x05, + TREE_ELEMENT_SSL_ISSUER = 0x06, +}; + +/** ssl certificate verification context. */ +struct sslcert_session_data { + unsigned long num; /**< The number of ssl certificates in the chain */ + char *url; /**< The url of the certificate */ + struct tree *tree; /**< The root of the treeview */ + llcache_query_response cb; /**< callback when cert is accepted or rejected */ + void *cbpw; /**< context passed to callback */ +}; + +/** Handle for the window icon. */ +static hlcache_handle *sslcert_icon; + +/** Initialise ssl certificate window. */ +void sslcert_init(void) +{ + sslcert_icon = tree_load_icon(tree_content_icon_name); +} + + +/** + * Get flags with which the sslcert tree should be created; + * + * \return the flags + */ +unsigned int sslcert_get_tree_flags(void) +{ + return TREE_NO_DRAGS | TREE_NO_SELECT; +} + + +void sslcert_cleanup(void) +{ + return; +} + +struct sslcert_session_data * +sslcert_create_session_data(unsigned long num, + const char *url, + llcache_query_response cb, + void *cbpw) +{ + struct sslcert_session_data *data; + + data = malloc(sizeof(struct sslcert_session_data)); + if (data == NULL) { + warn_user("NoMemory", 0); + return NULL; + } + data->url = strdup(url); + if (data->url == NULL) { + free(data); + warn_user("NoMemory", 0); + return NULL; + } + data->num = num; + data->cb = cb; + data->cbpw = cbpw; + + return data; +} + +static node_callback_resp sslcert_node_callback(void *user_data, + struct node_msg_data *msg_data) +{ + if (msg_data->msg == NODE_DELETE_ELEMENT_IMG) + return NODE_CALLBACK_HANDLED; + return NODE_CALLBACK_NOT_HANDLED; +} + +static struct node *sslcert_create_node(const struct ssl_cert_info *cert) +{ + struct node *node; + struct node_element *element; + char *text; + + text = messages_get_buff("SSL_Certificate_Subject", cert->subject); + if (text == NULL) + return NULL; + + node = tree_create_leaf_node(NULL, NULL, text, false, false, false); + if (node == NULL) { + free(text); + return NULL; + } + tree_set_node_user_callback(node, sslcert_node_callback, NULL); + + /* add issuer node */ + element = tree_create_node_element(node, NODE_ELEMENT_TEXT, + TREE_ELEMENT_SSL_ISSUER, false); + if (element != NULL) { + text = messages_get_buff("SSL_Certificate_Issuer", cert->issuer); + if (text == NULL) { + tree_delete_node(NULL, node, false); + return NULL; + } + tree_update_node_element(NULL, element, text, NULL); + } + + /* add version node */ + element = tree_create_node_element(node, NODE_ELEMENT_TEXT, + TREE_ELEMENT_SSL_VERSION, false); + if (element != NULL) { + text = messages_get_buff("SSL_Certificate_Version", cert->version); + if (text == NULL) { + tree_delete_node(NULL, node, false); + return NULL; + } + tree_update_node_element(NULL, element, text, NULL); + } + + /* add valid from node */ + element = tree_create_node_element(node, NODE_ELEMENT_TEXT, + TREE_ELEMENT_SSL_VALID_FROM, false); + if (element != NULL) { + text = messages_get_buff("SSL_Certificate_ValidFrom", cert->not_before); + if (text == NULL) { + tree_delete_node(NULL, node, false); + return NULL; + } + tree_update_node_element(NULL, element, text, NULL); + } + + + /* add valid to node */ + element = tree_create_node_element(node, NODE_ELEMENT_TEXT, + TREE_ELEMENT_SSL_VALID_TO, false); + if (element != NULL) { + text = messages_get_buff("SSL_Certificate_ValidTo", cert->not_after); + if (text == NULL) { + tree_delete_node(NULL, node, false); + return NULL; + } + tree_update_node_element(NULL, element, text, NULL); + } + + /* add certificate type */ + element = tree_create_node_element(node, NODE_ELEMENT_TEXT, + TREE_ELEMENT_SSL_CERT_TYPE, false); + if (element != NULL) { + text = messages_get_buff("SSL_Certificate_Type", cert->cert_type); + if (text == NULL) { + tree_delete_node(NULL, node, false); + return NULL; + } + tree_update_node_element(NULL, element, text, NULL); + } + + /* add serial node */ + element = tree_create_node_element(node, NODE_ELEMENT_TEXT, + TREE_ELEMENT_SSL_SERIAL, false); + if (element != NULL) { + text = messages_get_buff("SSL_Certificate_Serial", cert->serial); + if (text == NULL) { + tree_delete_node(NULL, node, false); + return NULL; + } + tree_update_node_element(NULL, element, text, NULL); + } + + /* set the display icon */ + tree_set_node_icon(NULL, node, sslcert_icon); + + return node; +} + +bool sslcert_load_tree(struct tree *tree, + const struct ssl_cert_info *certs, + struct sslcert_session_data *data) +{ + struct node *tree_root; + struct node *node; + unsigned long cert_loop; + + assert(data != NULL && certs != NULL && tree != NULL); + + tree_root = tree_get_root(tree); + + for (cert_loop = 0; cert_loop < data->num; cert_loop++) { + node = sslcert_create_node(&(certs[cert_loop])); + if (node != NULL) { + /* There is no problem creating the node + * add an entry for it in the root of the + * treeview . + */ + tree_link_node(tree, tree_root, node, false); + } + } + + data->tree = tree; + + return tree; + +} + + +static void sslcert_cleanup_session(struct sslcert_session_data *session) +{ + assert(session != NULL); + + free(session->url); + free(session); +} + + + +bool sslcert_reject(struct sslcert_session_data *session) +{ + session->cb(false, session->cbpw); + sslcert_cleanup_session(session); + return true; +} + + +/** + * Handle acceptance of certificate + */ +bool sslcert_accept(struct sslcert_session_data *session) +{ + assert(session != NULL); + + urldb_set_cert_permissions(session->url, true); + + session->cb(true, session->cbpw); + + sslcert_cleanup_session(session); + + return true; +} diff --git a/desktop/sslcert.h b/desktop/sslcert.h new file mode 100644 index 000000000..bc1b8bef8 --- /dev/null +++ b/desktop/sslcert.h @@ -0,0 +1,43 @@ +/* + * Copyright 2009 Paul Blokus <paul_pl@users.sourceforge.net> + * + * 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 <http://www.gnu.org/licenses/>. + */ + + +#ifndef _NETSURF_DESKTOP_SSLCERT_H_ +#define _NETSURF_DESKTOP_SSLCERT_H_ + +#include <stdbool.h> + +#include "desktop/tree.h" + +struct sslcert_session_data; + +void sslcert_init(void); +unsigned int sslcert_get_tree_flags(void); +void sslcert_cleanup(void); + +struct sslcert_session_data *sslcert_create_session_data(unsigned long num, + const char *url, llcache_query_response cb, void *cbpw); +bool sslcert_load_tree(struct tree *tree, + const struct ssl_cert_info *certs, + struct sslcert_session_data *data); + +bool sslcert_reject(struct sslcert_session_data *session); +bool sslcert_accept(struct sslcert_session_data *session); + + +#endif 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 <not_ginger_matt@users.sourceforge.net> + * Copyright 2009 Paul Blokus <paul_pl@users.sourceforge.net> * * This file is part of NetSurf, http://www.netsurf-browser.org/ * @@ -25,135 +26,367 @@ #include <stdio.h> #include <stdlib.h> #include <string.h> -#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" +#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 */ +}; -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; +/** + * Creates and initialises a new tree. + * + * \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 + */ +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; + } + + 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->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 tree + * Recalculates the dimensions of a node element. * - * \param tree the tree to initialise + * \param tree the tree to which the element belongs, may be NULL + * \param element the element to recalculate */ -void tree_initialise(struct tree *tree) { +static void tree_recalculate_node_element(struct tree *tree, + struct node_element *element) +{ + struct bitmap *bitmap = NULL; + int width, height; - assert(tree); + assert(element != 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); - tree->root->expanded = true; - tree_recalculate_node_positions(tree, tree->root); - tree_recalculate_size(tree); + 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); + } + + element->box.width += 8; + element->box.height = TREE_TEXT_HEIGHT; + + if (element->type == NODE_ELEMENT_TEXT_PLUS_ICON) + element->box.width += NODE_INSTEP; + + break; + + 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; + } } /** - * Initialises a user-created node structure + * Calculates the height of a node including any children * - * \param root the root node to update from + * \param node the node to calculate the height of + * \return the total height of the node and children */ -void tree_initialise_nodes(struct tree *tree, struct node *root) { - struct node *node; +static int tree_get_node_height(struct node *node) +{ + int y1; - assert(root); + assert(node != NULL); - tree_initialising++; - for (node = root; node; node = node->next) { - tree_recalculate_node(tree, node, true); - if (node->child) { - tree_initialise_nodes(tree, node->child); - } + if ((node->child == NULL) || (node->expanded == false)) { + return node->box.height; + } + + y1 = node->box.y; + if (y1 < 0) { + y1 = 0; } - tree_initialising--; + node = node->child; - if (tree_initialising == 0) - tree_recalculate_node_positions(tree, root); + 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; } /** - * Recalculate the node data and redraw the relevant section of the tree. + * Calculates the width of a node including any children * - * \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 + * \param node the node to calculate the height of + * \return the total width of the node and children */ -void tree_handle_node_changed(struct tree *tree, struct node *node, - bool recalculate_sizes, bool expansion) { - int width, height; +static int tree_get_node_width(struct node *node) +{ + int width = 0; + int child_width; - assert(node); + assert(node != NULL); - if ((expansion) && (node->expanded) && (node->child)) { - tree_set_node_expanded(tree, node->child, false); - tree_set_node_selected(tree, node->child, false); - } + for (; node != NULL; node = node->next) { + if (width < (node->box.x + node->box.width)) { + width = node->box.x + node->box.width; + } - 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 ((node->child != NULL) && (node->expanded)) { + child_width = tree_get_node_width(node->child); + if (width < child_width) { + width = child_width; + } + } } - if ((recalculate_sizes) || (expansion)) - tree_recalculate_size(tree); + return width; } /** - * Recalculate the node element and redraw the relevant section of the tree. - * The tree size is not updated. + * Recalculates the position of a node, its siblings and children. * - * \param tree the tree to redraw - * \param element the node element to update + * \param tree the tree to which 'root' belongs + * \param root the root node to update from */ -void tree_handle_node_element_changed(struct tree *tree, struct node_element *element) { - int width, height; +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; - assert(element); + for (node = root; node != NULL; node = node->next) { - width = element->box.width; - height = element->box.height; - tree_recalculate_node_element(element); + 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 != NULL) { + 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->flags & TREE_NO_FURNITURE + ? -NODE_INSTEP + 4 : 0; + node->box.y = -20; + } + + 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; + } + } - 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); } } @@ -161,520 +394,1279 @@ void tree_handle_node_element_changed(struct tree *tree, struct node_element *el /** * Recalculates the size of a node. * - * \param node the node to update - * \param recalculate_sizes whether the node elements have changed + * \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 */ -void tree_recalculate_node(struct tree *tree, struct node *node, bool recalculate_sizes) { +static void tree_recalculate_node_sizes(struct tree *tree, struct node *node, + bool recalculate_sizes) +{ struct node_element *element; - int height; + int width, height; - assert(node); + assert(node != NULL); + 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) { + for (element = &node->data; element != NULL; + 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) ? + 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; + 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); + for (element = &node->data; element != NULL; + element = element->next) + tree_recalculate_node_element(tree, element); else - tree_recalculate_node_element(&node->data); + tree_recalculate_node_element(tree, &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 (tree != NULL && height != node->box.height) + tree_recalculate_node_positions(tree, tree->root); +} + + +/** + * Creates a folder node with the specified title, and optionally links it into + * the tree. + * + * \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. + */ +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(title != NULL); + + 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; } /** - * Recalculates the position of a node, its siblings and children. + * Creates a leaf node with the specified title, and optionally links it into + * the tree. * - * \param root the root node to update from + * \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_recalculate_node_positions(struct tree *tree, struct node *root) { - struct node *parent; +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; - struct node *child; + + 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; +} + + +/** + * Creates an empty text node element and links it to a node. + * + * \param parent the parent node + * \param type the required element type + * \param flag user assigned flag used for searches + * \return the newly created element. + */ +struct node_element *tree_create_node_element(struct node *parent, + node_element_type type, unsigned int flag, bool editable) +{ 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; + 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; +} + + +/** + * Inserts a node into the correct place according to the parent's sort function + * + * \param parent the node whose child node 'node' becomes + * \param node the node to be inserted + */ +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; + } + parent->child = node; + } + + if (node->next == NULL) + parent->last_child = node; + + node->parent = parent; +} + + +/** + * Recalculates the size of a tree. + * + * \param tree the tree to recalculate + */ +static void tree_recalculate_size(struct tree *tree) +{ + int width, height; + + 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); +} + +/** + * Recalculate the node data and redraw the relevant section of the tree. + * + * \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 + */ +static void tree_handle_node_changed(struct tree *tree, struct node *node, + bool recalculate_sizes, bool expansion) +{ + int width, height, tree_height; + + 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 { - node->box.x = tree->no_furniture ? -NODE_INSTEP + 4 : 0; - node->box.y = -40; + 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->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); + } +} + + +/** + * Links a node to another node. + * + * \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 + */ +void tree_link_node(struct tree *tree, struct node *link, struct node *node, + bool before) +{ + + struct node *parent; + bool sort = false; + + assert(link != NULL); + assert(node != NULL); + + 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 { - 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; - } + 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->data.box.x = node->box.x; - node->data.box.y = node->box.y; + 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; + } } + } + + if (sort) { + tree_sort_insert(parent, node); + } + + tree_handle_node_changed(tree, link, false, true); + + node->deleted = false; } /** - * Calculates the width of a node including any children + * Recalculate the node element and redraw the relevant section of the tree. + * The tree size is not updated. * - * \param node the node to calculate the height of - * \return the total width of the node and children + * \param tree the tree to redraw, may be NULL + * \param element the node element to update */ -int tree_get_node_width(struct node *node) { - int width = 0; - int child_width; +static void tree_handle_node_element_changed(struct tree *tree, + struct node_element *element) +{ + int width, height; - assert(node); + assert(element != 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 = 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); + } } } - return width; } /** - * Calculates the height of a node including any children + * Stops editing a node_element * - * \param node the node to calculate the height of - * \return the total height of the node and children + * \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 */ -int tree_get_node_height(struct node *node) { - int y1; +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; - assert(node); + assert(tree != 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; + 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; } - return node->box.y + node->box.height - y1; - } else { - return node->box.height; + textarea_get_text(tree->textarea, text, text_len); + } + + + 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); + } +} + + +/** + * Delinks a node from the tree structures. + * + * \param tree the tree in which the delink takes place, may be NULL + * \param node the node to delink + */ +void tree_delink_node(struct tree *tree, struct node *node) +{ + struct node *parent; + + assert(node != NULL); + + /* 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; + } + } + + 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); +} + + +/** + * Deletes a node from the tree. + * + * \param tree the tree to delete from, may be NULL + * \param node the node to delete + * \param siblings whether to delete all siblings + */ +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; + + assert(node != NULL); + + if (tree != NULL && tree->root == node) + return; + + 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; + + 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); + } + 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); + } + 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); +} + + +/** + * Deletes all nodes of a tree and the tree itself. + * + * \param tree the tree to be deleted + */ +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); +} + + +/** + * 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; } /** - * Updates all siblinds and descendants of a node to an expansion state. + * Deletes a node from the tree. + * + * \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_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); +} + + +/** + * 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); +} + + +/** + * Updates all siblings 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 + * \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 */ -void tree_set_node_expanded(struct tree *tree, struct node *node, bool expanded) { - for (; node; node = node->next) { +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(tree, node, false); + tree_recalculate_node_sizes(tree, node, false); } - if ((node->child) && (node->expanded)) - tree_set_node_expanded(tree, node->child, expanded); + if ((node->child != NULL) && (node->expanded)) + tree_set_node_expanded_all(tree, node->child, expanded); } } /** - * Updates all siblinds and descendants of a node to an expansion state. + * Updates [all siblings 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 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 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 */ -bool tree_handle_expansion(struct tree *tree, struct node *node, bool expanded, bool folder, - bool leaf) { - struct node *entry = node; +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; + + if (tree->editing != NULL && node == tree->editing->parent) + tree_stop_edit(tree, false); - for (; node; node = node->next) { + for (; node != end; node = node->next) { if ((node->expanded != expanded) && (node != tree->root) && - ((folder && (node->folder)) || (leaf && (!node->folder)))) { + ((folder && (node->folder)) || + (leaf && (!node->folder)) || + (!folder && !leaf))) { 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); + 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(tree, node, false); + tree_recalculate_node_sizes(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); + if ((folder || leaf) && (node->child != NULL) && + (node->expanded)) + redraw |= tree_set_node_expanded_internal(tree, + node->child, expanded, folder, leaf); } return redraw; } /** - * Updates all siblinds and descendants of a node to an selected state. - * The required areas of the tree are redrawn. + * Updates [all siblings 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, 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_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); +} + + +/** + * Updates a node to an selected state. The required areas of the tree are + * redrawn. * - * \param tree the tree to update nodes for + * \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_set_node_selected(struct tree *tree, struct node *node, bool selected) { - for (; node; node = node->next) { - if ((node->selected != selected) && (node != tree->root)) { +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; - tree_redraw_area(tree, node->box.x, node->box.y, node->box.width, - node->data.box.height); + 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); } - if ((node->child) && (node->expanded)) - tree_set_node_selected(tree, node->child, selected); + if (all && (node->child != NULL) && (node->expanded)) + tree_set_node_selected(tree, node->child, all, + selected); } } /** - * Finds a node at a specific location. + * Sets the sort function for a node * - * \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 which 'node' belongs, may be NULL + * \param node the node to be inserted + * \param sort pointer to the sorting function */ -struct node *tree_get_node_at(struct node *root, int x, int y, bool *furniture) { - struct node_element *result; +void tree_set_node_sort_function(struct tree *tree, struct node *node, + int (*sort) (struct node *, struct node *)) +{ + struct node *child; - if ((result = tree_get_node_element_at(root, x, y, furniture))) - return result->parent; - return NULL; + 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; + } + + } + + if (tree != NULL) + tree_recalculate_node_positions(tree, node->child); } /** - * Finds a node element at a specific location. + * Sets the delete callback for a node. * - * \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 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 */ -struct node_element *tree_get_node_element_at(struct node *node, int x, int y, - bool *furniture) { - struct node_element *element; +void tree_set_node_user_callback(struct node *node, + tree_node_user_callback callback, void *data) +{ + node->user_callback = callback; + node->callback_data = data; +} - *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; +/** + * 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 for which the property is set + * \param redraw the value to set + */ +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; +} + + +/** + * 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 NULL; + return false; } /** - * Finds a node element from a node with a specific user_type + * Returns the current value of the nodes deleted property. * - * \param node the node to examine - * \param user_type the user_type to check for - * \return the corresponding element + * \param node the node to be checked + * \return the current value of the nodes deleted property */ -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; +bool tree_node_is_deleted(struct node *node) +{ + return node->deleted; } /** - * Moves nodes within a tree. + * Returns true if the node is a folder * - * \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 + * \param node the node to be checked + * \return true if the node is a folder, false otherwise */ -void tree_move_selected_nodes(struct tree *tree, struct node *destination, bool before) { - struct node *link; - struct node *test; - bool error; +bool tree_node_is_folder(struct node *node) +{ + return node->folder; +} - 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); +/** + * 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; } - 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); + 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 { + /* text does not need changing, free it */ + free(text); + } + return true; } /** - * Sets the processing flag to the selection state. + * Updates the content of a node_element. * - * \param node the node to process siblings and children of + * \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 */ -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); +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; + } + + 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; } + + tree_handle_node_element_changed(tree, element); } /** - * Clears the processing flag. + * Returns the node element's text * - * \param node the node to process siblings and children of + * \return the node element's text */ -void tree_clear_processing(struct node *node) { - for (; node; node = node->next) { - node->processing = false; - if (node->child) - tree_clear_processing(node->child); - } +const char *tree_node_element_get_text(struct node_element *element) +{ + return element->text; } /** - * Moves the first node in a tree with the processing flag set. + * Get the root node of a tree * - * \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 get the root of + * \return the root of the tree */ -struct node *tree_move_processing_node(struct node *node, struct node *link, bool before, - bool first) { - struct node *result; +struct node *tree_get_root(struct tree *tree) +{ + return tree->root; +} - 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; + +/** + * Returns whether the current tree is being edited at this time + * + * \param tree the tree to be checked + * \return true if the tree is currently being edited + */ +bool tree_is_edited(struct tree *tree) +{ + return tree->editing == NULL ? false : true; } + /** - * Checks whether a node, its siblings or any children are selected. + * Returns the first child of a node * - * \param node the root node to check from + * \param node the node to get the child of + * \return the nodes first child */ -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; +struct node *tree_node_get_child(struct node *node) +{ + return node->child; } /** - * Updates the selected state for a region of nodes. + * Returns the closest sibling a node * - * \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 node the node to get the sibling of + * \return the nodes sibling */ -void tree_handle_selection_area(struct tree *tree, int x, int y, int width, int height, - bool invert) { - assert(tree); - assert(tree->root); +struct node *tree_node_get_next(struct node *node) +{ + return node->next; +} - if (!tree->root->child) return; - if (width < 0) { - x += width; - width = -width; - } - if (height < 0) { - y += height; - height = -height; +/** + * 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); + } - tree_handle_selection_area_node(tree, tree->root->child, x, y, width, height, invert); } /** - * Updates the selected state for a region of nodes. + * Draws an element, including any expansion icons * - * \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 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 */ -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_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); + } + + x += NODE_INSTEP; + width -= NODE_INSTEP; + + /* fall through */ + case NODE_ELEMENT_TEXT: + if (element->text == NULL) + break; + + 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; + } + +} + +/** + * Redraws a node. + * + * \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 + */ +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; - struct node *update; + struct node *parent; int x_max, y_max; + int x0, y0, x1, y1; - assert(tree); - assert(node); + assert(tree != NULL); + assert(node != NULL); - x_max = x + width; - y_max = y + height; - for (; node; node = node->next) { + 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 >= 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->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); + } - } 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); + 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) && (node->expanded)) - tree_handle_selection_area_node(tree, node->child, x, y, width, height, - invert); + if ((node->child != NULL) && (node->expanded)) + tree_draw_node(tree, node->child, tree_x, tree_y, + clip_x, clip_y, + clip_width, clip_height); } } @@ -682,87 +1674,214 @@ void tree_handle_selection_area_node(struct tree *tree, struct node *node, int x /** * 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 + * \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 clip_x, int clip_y, int clip_width, - int clip_height) { - assert(tree); - assert(tree->root); +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); + } +} + - if (!tree->root->child) return; +/** + * Finds a node element from a node with a specific user_type + * + * \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_node_find_element(struct node *node, + unsigned int flag, struct node_element *after) +{ + struct node_element *element; - tree_initialise_redraw(tree); - tree_draw_node(tree, tree->root->child, clip_x, - clip_y, clip_width, clip_height); + 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; } /** - * Redraws a node. + * Deletes all selected nodes 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 + * \param node the node to delete */ -void tree_draw_node(struct tree *tree, struct node *node, int clip_x, int clip_y, - int clip_width, int clip_height) { +void tree_delete_selected_nodes(struct tree *tree, struct node *node) +{ + struct node *next; - struct node_element *element; - int x_max, y_max; + if (node == tree->root) { + if (node->child != NULL) + tree_delete_selected_nodes(tree, node->child); + return; + } - assert(tree); - assert(node); + 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; + } +} - 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; +/** + * 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->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); + for (; node != NULL; node = node->next) { + if (node->selected) { + if (result != NULL) + return NULL; + result = node; + } + if ((node->child != NULL) && (node->expanded)) { + temp = tree_get_selected_node(node->child); + if (temp != NULL) { + if (result != NULL) + return NULL; + else + result = temp; + } + } + } + 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; } - 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); + + 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; } @@ -773,24 +1892,27 @@ void tree_draw_node(struct tree *tree, struct node *node, int clip_x, int clip_y * \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 + * \return the node to link with */ -struct node *tree_get_link_details(struct tree *tree, int x, int y, bool *before) { +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); + assert(tree != NULL); + assert(tree->root != NULL); *before = false; - if (tree->root->child) + if (tree->root->child != NULL) node = tree_get_node_at(tree->root->child, x, y, &furniture); - if ((!node) || (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)) { + } else if ((node->folder) && (node->expanded) && + (node->child != NULL)) { node = node->child; *before = true; } @@ -799,527 +1921,687 @@ struct node *tree_get_link_details(struct tree *tree, int x, int y, bool *before /** - * Links a node into the tree. + * Launches all the selected nodes of 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 + * \param tree the tree for which all nodes will be launched + * \param node the node which will be checked together with its children */ -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; +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); } - } 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; + if (node->child != NULL) + tree_launch_selected_internal(tree, node->child); } - node->deleted = false; } /** - * Delinks a node from the tree. + * Launches all the selected nodes of the tree * - * \param node the node to delink + * \param tree the tree for which all nodes will be launched */ -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; +void tree_launch_selected(struct tree *tree) +{ + if (tree->root->child != NULL) + tree_launch_selected_internal(tree, tree->root->child); } /** - * Deletes all selected node from the tree. + * Handles a mouse action for a tree * - * \param tree the tree to delete from - * \param node the node to delete + * \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 */ -void tree_delete_selected_nodes(struct tree *tree, struct node *node) { - struct node *next; +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; - 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; + 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; -/** - * 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); + /* 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; } /** - * Deletes a node from the tree. + * Updates the selected state for a region of nodes. * - * \param tree the tree to delete from - * \param node the node to delete - * \param siblings whether to delete all siblings + * \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 */ -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; +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; - next = node->next; - tree_delink_node(node); - child = node->child; - node->child = NULL; - if (child) - tree_delete_node_internal(tree, child, true); + assert(tree != NULL); + assert(node != NULL); - 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); - } - } - } + y_max = y + height; - if (e->data != TREE_ELEMENT_TITLE && - e->data != TREE_ELEMENT_URL) { - free((void *)e->text); - e->text = NULL; + 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 (e->sprite) { - /* TODO the type of this field is platform dependent */ - free(e->sprite); /* \todo platform specific bits */ - e->sprite = NULL; + 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); + } } - f = e->next; - if (e != &node->data) - free(e); } - free(node); - } else { - node->deleted = true; + if ((node->child != NULL) && (node->expanded)) + tree_handle_selection_area_node(tree, node->child, y, + height, invert); } - if (siblings && next) - tree_delete_node_internal(tree, next, true); } + /** - * Creates a folder node with the specified title, and links it into the tree. + * Updates the selected state for a region of nodes. * - * \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 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 */ -struct node *tree_create_folder_node(struct node *parent, const char *title) { - struct node *node; +static void tree_handle_selection_area(struct tree *tree, int y, int height, + bool invert) +{ + assert(tree != NULL); + assert(tree->root != NULL); - assert(title); + if (tree->root->child == NULL) + return; - 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; + if (height < 0) { + y += height; + height = -height; + } + tree_handle_selection_area_node(tree, tree->root->child, y, height, + invert); } /** - * Creates a leaf node with the specified title, and links it into the tree. + * Clears the processing flag. * - * \param parent the parent node, or NULL not to link - * \param title the node title (copied) - * \return the newly created node. + * \param node the node to process siblings and children of */ -struct node *tree_create_leaf_node(struct node *parent, const char *title) { - struct node *node; +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); + } +} - 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; +/** + * 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); + } } /** - * Creates a leaf node with the specified title, and links it into the tree. + * Moves the first node in a tree with the processing flag set. * - * \param parent the parent node, or NULL not to link - * \param title the node title - * \return the newly created node. + * \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 */ -struct node *tree_create_leaf_node_shared(struct node *parent, const char *title) { - struct node *node; - - assert(title); +static struct node *tree_move_processing_node(struct tree *tree, + struct node *node, struct node *link, bool before, bool first) +{ + struct node *result; - 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; + 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; } /** - * Creates a tree entry for a URL, and links it into the tree - * + * Moves nodes within a 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 + * \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 */ -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); +static void tree_move_selected_nodes(struct tree *tree, + struct node *destination, bool before) +{ + struct node *link; + struct node *test; + bool error; - node = tree_create_leaf_node(parent, title ? title : url); - if (!node) - return NULL; + tree_clear_processing(tree->root); + tree_selected_to_processing(tree->root); - 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); + /* 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_update_URL_node(node, url, NULL); - return node; + 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); } /** - * 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. + * Handle the end of a drag operation * - * \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 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 */ -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; +void tree_drag_end(struct tree *tree, browser_mouse_state mouse, int x0, int y0, + int x1, int y1) +{ - assert(url && data); - - if (data->title) - title = data->title; - else - title = url; - node = tree_create_leaf_node_shared(parent, title); - if (!node) - return NULL; + 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); + } - 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->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_update_URL_node(node, url, data); - return node; + tree->drag = TREE_NO_DRAG; } /** - * 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. + * Key press handling for a tree. * - * \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 which got the keypress + * \param key The ucs4 character codepoint + * \return true if the keypress is dealt with, false otherwise. */ -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; - +bool tree_keypress(struct tree *tree, uint32_t key) +{ - 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); + 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); } - } - 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; + return false; } /** - * Creates an empty text node element and links it to a node. + * Alphabetical comparison function for nodes * - * \param parent the parent node - * \param user_type the required user_type - * \return the newly created element. + * \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 */ -struct node_element *tree_create_node_element(struct node *parent, node_element_data data) { - struct node_element *element; +int tree_alphabetical_sort(struct node *n1, struct node *n2) +{ + return strcmp(n1->data.text, n2->data.text); +} - 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; + +/** + * 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); } /** - * Recalculates the size of a tree. + * Starts editing a node_element * - * \param tree the tree to recalculate + * \param tree The tree to which element belongs + * \param element The element to start being edited */ -void tree_recalculate_size(struct tree *tree) { +void tree_start_edit(struct tree *tree, struct node_element *element) +{ + struct node *parent; int width, height; - assert(tree); + 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; - if (!tree->handle) + tree->textarea = textarea_create(width, height, 0, + &plot_fstyle, tree_textarea_redraw_request, tree); + if (tree->textarea == NULL) { + tree_stop_edit(tree, false); 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); + 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); } /** - * 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 + * Callback for fetchcache(). Should be removed once bitmaps get loaded directly + * from disc */ -struct node *tree_get_selected_node(struct node *node) { - struct node *result = NULL; - struct node *temp; +static nserror tree_icon_callback(hlcache_handle *handle, + const hlcache_event *event, void *pw) +{ + return NSERROR_OK; +} - 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; - } + +/** + * 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; } - return result; + + /* 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; } diff --git a/desktop/tree.h b/desktop/tree.h index d02f1726e..707f3d126 100644 --- a/desktop/tree.h +++ b/desktop/tree.h @@ -1,5 +1,6 @@ /* * Copyright 2004 Richard Wilson <not_ginger_matt@users.sourceforge.net> + * Copyright 2009 Paul Blokus <paul_pl@users.sourceforge.net> * * This file is part of NetSurf, http://www.netsurf-browser.org/ * @@ -26,157 +27,167 @@ #include <stdbool.h> #include <stdint.h> -struct url_data; -struct cookie_data; - -typedef enum { - TREE_ELEMENT_URL, - TREE_ELEMENT_ADDED, - TREE_ELEMENT_LAST_VISIT, - TREE_ELEMENT_VISITS, - TREE_ELEMENT_VISITED, - TREE_ELEMENT_THUMBNAIL, - TREE_ELEMENT_TITLE, - TREE_ELEMENT_NAME, - TREE_ELEMENT_VALUE, - TREE_ELEMENT_COMMENT, - TREE_ELEMENT_DOMAIN, - TREE_ELEMENT_PATH, - TREE_ELEMENT_EXPIRES, - TREE_ELEMENT_LAST_USED, - TREE_ELEMENT_SECURE, - TREE_ELEMENT_VERSION, - TREE_ELEMENT_PERSISTENT, - TREE_ELEMENT_SSL -} node_element_data; - -#define NODE_INSTEP 40 - -struct node_sprite; -struct toolbar; - -typedef enum { - NODE_ELEMENT_TEXT, /* <-- Text only */ - NODE_ELEMENT_TEXT_PLUS_SPRITE, /* <-- Text and sprite */ - NODE_ELEMENT_THUMBNAIL, /* <-- Bitmap 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 */ +#include "desktop/browser.h" +#include "image/bitmap.h" + +struct hlcache_handle; + +/* Tree flags */ +enum tree_flags { + TREE_NO_FLAGS = 0, + TREE_NO_DRAGS = 1, + TREE_NO_FURNITURE = 2, + TREE_SINGLE_SELECT = 4, + TREE_NO_SELECT = 8, + TREE_MOVABLE = 16, + TREE_DELETE_EMPTY_DIRS = 32, /**< if the last child of a + * directory is deleted the + * directory will be deleted + * too. + */ }; +/** A "flag" value to indicate the element data contains title + * text. This value should be the first node_element in every + * node. All other values should be different than this one. The term + * flag is misused as it is actually a value used by the API consumer + * to indicate teh type of data a node element contains. + */ +#define TREE_ELEMENT_TITLE 0x00 -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 */ - struct node_sprite *sprite; /* <-- Sprite for the element */ - struct node_element *next; /* <-- Next node element */ - node_element_data data; /* <-- Data being represented */ -}; +/* these should be defined in front end code */ +extern const char tree_directory_icon_name[]; +extern const char tree_content_icon_name[]; + +struct tree; +struct node; +struct node_element; +typedef enum { + NODE_ELEMENT_TEXT, /**< Text only */ + NODE_ELEMENT_TEXT_PLUS_ICON, /**< Text and icon */ + NODE_ELEMENT_BITMAP /**< Bitmap only */ +} node_element_type; -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 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 */ +typedef enum { + NODE_DELETE_ELEMENT_TXT, /**< The text of an element of the + * node is being deleted */ + NODE_DELETE_ELEMENT_IMG, /**< The bitmap or icon of a node is + * being deleted */ + NODE_LAUNCH, /**< The node has been launched */ + NODE_ELEMENT_EDIT_FINISHING, /**< New text has to be accepted + * or rejected. */ + NODE_ELEMENT_EDIT_FINISHED /**< Editing of a node_element has + * been finished. */ +} node_msg; +typedef enum { + NODE_CALLBACK_HANDLED, + NODE_CALLBACK_NOT_HANDLED, + NODE_CALLBACK_REJECT, /**< reject new text for node element + * and leave editing mode. */ + NODE_CALLBACK_CONTINUE /**< don't leave editig mode. */ +} node_callback_resp; + +/** Internal node message. */ +struct node_msg_data { + node_msg msg; /**< The type of message. */ + unsigned int flag; /**< message flags. */ + struct node *node; /**< tree node messsage concerns. */ + union { + char *text; /**< textural data. */ + void *bitmap; /**< bitmap data. */ + } data; /**< The message data. */ }; -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 */ - bool no_drag; /* <-- Tree items can't be dragged out */ - bool no_vscroll; /* <-- Tree has a vertical scroll only when needed */ - bool no_furniture; /* <-- Tree does not have connecting lines */ - bool single_selection; /* <-- There can only be one item selected */ - int edit_handle; /* <-- Handle for editing information */ - uintptr_t textarea_handle; /* <-- Handle for UTF-8 textarea */ - bool movable; /* <-- Whether nodes can be moved */ - struct node_element *editing; /* <-- Node element being edited */ - struct node *temp_selection; /* <-- Temporarily selected node */ - struct toolbar *toolbar; /* <-- Tree toolbar */ +/** callbacks to perform necessary operations on treeview. */ +struct treeview_table { + void (*redraw_request)(int x, int y, int width, int height, + void *data); /**< request a redraw. */ + void (*resized)(struct tree *tree, int width, int height, + void *data); /**< resize treeview area. */ + void (*scroll_visible)(int y, int height, void *data); /**< scroll visible treeview area. */ + void (*get_window_dimensions)(int *width, int *height, void *data); /**< get dimensions of window */ }; +/** + * Informs the client about any events requiring his action + * + * \param user_data the user data which was passed at tree creation + * \param msg_data structure containing all the message information + * \return the appropriate node_callback_resp response + */ +typedef node_callback_resp (*tree_node_user_callback)(void *user_data, + struct node_msg_data *msg_data); /* Non-platform specific code */ -void tree_initialise(struct tree *tree); -void tree_initialise_nodes(struct tree *tree, 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); -void tree_recalculate_node(struct tree *tree, struct node *node, bool recalculate_sizes); -void tree_recalculate_node_positions(struct tree *tree, struct node *root); -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, node_element_data data); -void tree_move_selected_nodes(struct tree *tree, struct node *destination, + +/* Functions for creating/deleting tree primitives and for tree structure + manipulation */ +struct tree *tree_create(unsigned int flags, + const struct treeview_table *callbacks, + void *client_data); +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 *tree_create_leaf_node(struct tree *tree, struct node *parent, + const char *title, bool editable, bool retain_in_memory, + bool deleted); +struct node_element *tree_create_node_element(struct node *parent, + node_element_type type, unsigned int flag, bool editable); +void tree_link_node(struct tree *tree, struct node *link, struct node *node, 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); -struct node *tree_create_URL_node(struct node *parent, - const char *url, const struct url_data *data, - const char *title); -struct node *tree_create_URL_node_shared(struct node *parent, - const char *url, const struct url_data *data); -struct node *tree_create_cookie_node(struct node *parent, - const struct cookie_data *data); -void tree_set_node_sprite(struct node *node, const char *sprite, - const char *expanded); -void tree_set_node_expanded(struct tree *tree, 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_delink_node(struct tree *tree, struct node *node); +void tree_delete(struct tree *tree); 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, + +/* setters and getters for properties and data */ +void tree_set_node_icon(struct tree *tree, struct node *node, + struct hlcache_handle *icon); +void tree_set_node_expanded(struct tree *tree, struct node *node, bool expanded, bool folder, bool leaf); +void tree_set_node_selected(struct tree *tree, struct node *node, bool all, + bool selected); +void tree_set_node_sort_function(struct tree *tree, struct node *node, + int (*sort) (struct node *, struct node *)); +void tree_set_node_user_callback(struct node *node, + tree_node_user_callback callback, void *data); +void tree_set_redraw(struct tree *tree, bool redraw); +bool tree_get_redraw(struct tree *tree); +bool tree_node_has_selection(struct node *node); +bool tree_node_is_deleted(struct node *node); +bool tree_node_is_folder(struct node *node); +void tree_update_node_element(struct tree *tree, struct node_element *element, + const char *text, void *bitmap); +bool tree_update_element_text(struct tree *tree, struct node_element *element, char *text); +const char *tree_node_element_get_text(struct node_element *element); +struct node *tree_get_root(struct tree *tree); +bool tree_is_edited(struct tree *tree); + + +/* functions for traversing the tree */ +struct node *tree_node_get_child(struct node *node); +struct node *tree_node_get_next(struct node *node); + +void tree_draw(struct tree *tree, int x, int y, + int clip_x, int clip_y, int clip_width, int clip_height); + +struct node_element *tree_node_find_element(struct node *node, + unsigned int flag, struct node_element *after); +void tree_delete_selected_nodes(struct tree *tree, struct node *node); 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(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, const char *url, - const struct url_data *data); -void tree_resized(struct tree *tree); -void tree_set_node_sprite_folder(struct node *node); - +void tree_launch_selected(struct tree *tree); + +bool tree_mouse_action(struct tree *tree, browser_mouse_state mouse, + int x, int y); +void tree_drag_end(struct tree *tree, browser_mouse_state mouse, int x0, int y0, + int x1, int y1); +bool tree_keypress(struct tree *tree, uint32_t key); + +int tree_alphabetical_sort(struct node *, struct node *); +void tree_start_edit(struct tree *tree, struct node_element *element); +struct hlcache_handle *tree_load_icon(const char *name); + #endif diff --git a/desktop/tree_url_node.c b/desktop/tree_url_node.c new file mode 100644 index 000000000..3bc9f90fc --- /dev/null +++ b/desktop/tree_url_node.c @@ -0,0 +1,846 @@ +/* + * Copyright 2005 Richard Wilson <info@tinct.net> + * Copyright 2009 Paul Blokus <paul_pl@users.sourceforge.net> + * + * 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 <http://www.gnu.org/licenses/>. + */ + +/** \file + * Creation of URL nodes with use of trees (implementation) + */ + + +#include <assert.h> +#include <ctype.h> +#include <libxml/HTMLparser.h> +#include <libxml/HTMLtree.h> + +#include "content/content.h" +#include "content/hlcache.h" +#include "content/urldb.h" +#include "desktop/browser.h" +#include "desktop/options.h" +#include "desktop/tree_url_node.h" +#include "utils/log.h" +#include "utils/messages.h" +#include "utils/url.h" +#include "utils/utils.h" + +/** Flags for each type of url tree node. */ +enum tree_element_url { + TREE_ELEMENT_URL = 0x01, + TREE_ELEMENT_LAST_VISIT = 0x02, + TREE_ELEMENT_VISITS = 0x03, + TREE_ELEMENT_THUMBNAIL = 0x04, +}; + +#define MAX_ICON_NAME_LEN 256 + +static bool initialised = false; + +static hlcache_handle *folder_icon; + +struct icon_entry { + content_type type; + hlcache_handle *icon; +}; + +struct icon_entry icon_table[] = { + {CONTENT_HTML, NULL}, + {CONTENT_TEXTPLAIN, NULL}, + {CONTENT_CSS, NULL}, +#if defined(WITH_MNG) || defined(WITH_PNG) + {CONTENT_PNG, NULL}, +#endif +#ifdef WITH_MNG + {CONTENT_JNG, NULL}, + {CONTENT_MNG, NULL}, +#endif +#ifdef WITH_JPEG + {CONTENT_JPEG, NULL}, +#endif +#ifdef WITH_GIF + {CONTENT_GIF, NULL}, +#endif +#ifdef WITH_BMP + {CONTENT_BMP, NULL}, + {CONTENT_ICO, NULL}, +#endif +#ifdef WITH_SPRITE + {CONTENT_SPRITE, NULL}, +#endif +#ifdef WITH_DRAW + {CONTENT_DRAW, NULL}, +#endif +#ifdef WITH_ARTWORKS + {CONTENT_ARTWORKS, NULL}, +#endif +#ifdef WITH_NS_SVG + {CONTENT_SVG, NULL}, +#endif + {CONTENT_UNKNOWN, NULL}, + + /* this serves as a sentinel */ + {CONTENT_HTML, NULL} +}; + + +void tree_url_node_init(void) +{ + struct icon_entry *entry; + char icon_name[MAX_ICON_NAME_LEN]; + + if (initialised || option_tree_icons_dir == NULL) + return; + initialised = true; + + folder_icon = tree_load_icon(tree_directory_icon_name); + + entry = icon_table; + do { + + tree_icon_name_from_content_type(icon_name, entry->type); + entry->icon = tree_load_icon(icon_name); + + ++entry; + } while (entry->type != CONTENT_HTML); + +} + + +/** + * 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 tree *tree, struct node *parent, + const char *url, const char *title, + tree_node_user_callback user_callback, void *callback_data) +{ + struct node *node; + struct node_element *element; + char *text_cp, *squashed; + + squashed = squash_whitespace(title ? title : url); + text_cp = strdup(squashed); + if (text_cp == NULL) { + LOG(("malloc failed")); + warn_user("NoMemory", 0); + return NULL; + } + free(squashed); + node = tree_create_leaf_node(tree, parent, text_cp, true, false, + false); + if (node == NULL) { + free(text_cp); + return NULL; + } + + if (user_callback != NULL) + tree_set_node_user_callback(node, user_callback, + callback_data); + + tree_create_node_element(node, NODE_ELEMENT_BITMAP, + TREE_ELEMENT_THUMBNAIL, false); + tree_create_node_element(node, NODE_ELEMENT_TEXT, TREE_ELEMENT_VISITS, + false); + tree_create_node_element(node, NODE_ELEMENT_TEXT, + TREE_ELEMENT_LAST_VISIT, false); + element = tree_create_node_element(node, NODE_ELEMENT_TEXT, + TREE_ELEMENT_URL, true); + if (element != NULL) { + text_cp = strdup(url); + if (text_cp == NULL) { + tree_delete_node(tree, node, false); + LOG(("malloc failed")); + warn_user("NoMemory", 0); + return NULL; + } + tree_update_node_element(tree, element, text_cp, 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 tree *tree, struct node *parent, + const char *url, const struct url_data *data, + tree_node_user_callback user_callback, void *callback_data) +{ + struct node *node; + struct node_element *element; + const char *title; + + assert(url && data); + + if (data->title != NULL) { + title = data->title; + } else { + title = url; + } + + node = tree_create_leaf_node(tree, parent, title, false, false, false); + if (node == NULL) + return NULL; + + if (user_callback != NULL) { + tree_set_node_user_callback(node, user_callback, + callback_data); + } + + tree_create_node_element(node, NODE_ELEMENT_BITMAP, + TREE_ELEMENT_THUMBNAIL, false); + tree_create_node_element(node, NODE_ELEMENT_TEXT, TREE_ELEMENT_VISITS, + false); + tree_create_node_element(node, NODE_ELEMENT_TEXT, + TREE_ELEMENT_LAST_VISIT, false); + element = tree_create_node_element(node, NODE_ELEMENT_TEXT, + TREE_ELEMENT_URL, false); + if (element != NULL) { + tree_update_node_element(tree, element, url, NULL); + } + + tree_update_URL_node(tree, node, url, data, true); + return node; +} + + +/** + * Updates the node details for a URL node. + * + * \param node the node to update + */ +void tree_update_URL_node(struct tree *tree, struct node *node, + const char *url, const struct url_data *data, bool shared) +{ + struct node_element *element; + struct bitmap *bitmap = NULL; + struct icon_entry *entry; + char *text_cp; + + assert(node != NULL); + + element = tree_node_find_element(node, TREE_ELEMENT_URL, NULL); + if (element == NULL) + return; + + if (data != NULL) { + if (data->title == NULL) + urldb_set_url_title(url, url); + + if (data->title == NULL) + return; + + element = tree_node_find_element(node, TREE_ELEMENT_TITLE, + NULL); + if (shared) + tree_update_node_element(tree, element, data->title, + NULL); + else { + text_cp = strdup(data->title); + if (text_cp == NULL) { + LOG(("malloc failed")); + warn_user("NoMemory", 0); + return; + } + tree_update_node_element(tree, element, text_cp, NULL); + } + } else { + data = urldb_get_url_data(url); + if (data == NULL) + return; + } + + entry = icon_table; + do { + if (entry->type == data->type) { + if (entry->icon != NULL) + tree_set_node_icon(tree, node, entry->icon); + break; + } + ++entry; + } while (entry->type != CONTENT_HTML); + + /* update last visit text */ + element = tree_node_find_element(node, TREE_ELEMENT_LAST_VISIT, element); + tree_update_element_text(tree, + element, + messages_get_buff("TreeLast", + (data->last_visit > 0) ? + ctime((time_t *)&data->last_visit) : + messages_get("TreeUnknown"))); + + + /* update number of visits text */ + element = tree_node_find_element(node, TREE_ELEMENT_VISITS, element); + tree_update_element_text(tree, + element, + messages_get_buff("TreeVisits", data->visits)); + + + /* update thumbnail */ + element = tree_node_find_element(node, TREE_ELEMENT_THUMBNAIL, element); + if (element != NULL) { + bitmap = urldb_get_thumbnail(url); + + if (bitmap != NULL) { + tree_update_node_element(tree, element, NULL, bitmap); + } + } +} + + +const char *tree_url_node_get_title(struct node *node) +{ + struct node_element *element; + element = tree_node_find_element(node, TREE_ELEMENT_TITLE, NULL); + if (element == NULL) + return NULL; + return tree_node_element_get_text(element); +} + + +const char *tree_url_node_get_url(struct node *node) +{ + struct node_element *element; + element = tree_node_find_element(node, TREE_ELEMENT_URL, NULL); + if (element == NULL) + return NULL; + return tree_node_element_get_text(element); +} + +void tree_url_node_edit_title(struct tree *tree, struct node *node) +{ + struct node_element *element; + element = tree_node_find_element(node, TREE_ELEMENT_TITLE, NULL); + tree_start_edit(tree, element); +} + +void tree_url_node_edit_url(struct tree *tree, struct node *node) +{ + struct node_element *element; + element = tree_node_find_element(node, TREE_ELEMENT_URL, NULL); + tree_start_edit(tree, element); +} + +node_callback_resp tree_url_node_callback(void *user_data, + struct node_msg_data *msg_data) +{ + struct tree *tree; + struct node_element *element; + url_func_result res; + const char *text; + char *norm_text, *escaped_text; + const struct url_data *data; + + /** @todo memory leaks on non-shared folder deletion. */ + switch (msg_data->msg) { + case NODE_DELETE_ELEMENT_TXT: + switch (msg_data->flag) { + /* only history is using non-editable url + * elements so only history deletion will run + * this code + */ + case TREE_ELEMENT_URL: + /* reset URL characteristics */ + urldb_reset_url_visit_data( + msg_data->data.text); + return NODE_CALLBACK_HANDLED; + case TREE_ELEMENT_TITLE: + return NODE_CALLBACK_HANDLED; + } + break; + case NODE_DELETE_ELEMENT_IMG: + if (msg_data->flag == TREE_ELEMENT_THUMBNAIL || + msg_data->flag == TREE_ELEMENT_TITLE) + return NODE_CALLBACK_HANDLED; + break; + case NODE_LAUNCH: + element = tree_node_find_element(msg_data->node, + TREE_ELEMENT_URL, NULL); + if (element != NULL) { + text = tree_node_element_get_text(element); + browser_window_create(text, NULL, 0, + true, false); + return NODE_CALLBACK_HANDLED; + } + break; + case NODE_ELEMENT_EDIT_FINISHING: + + text = msg_data->data.text; + + if (msg_data->flag == TREE_ELEMENT_URL) { + res = url_escape(text, 0, false, NULL, + &escaped_text); + if (res == URL_FUNC_OK) + res = url_normalize(escaped_text, + &norm_text); + if (res != URL_FUNC_OK) { + if (res == URL_FUNC_FAILED) { + warn_user("NoURLError", 0); + return NODE_CALLBACK_CONTINUE; + } + else { + warn_user("NoMemory", 0); + return NODE_CALLBACK_REJECT; + } + + } + msg_data->data.text = norm_text; + + data = urldb_get_url_data(norm_text); + if (data == NULL) { + urldb_add_url(norm_text); + urldb_set_url_persistence(norm_text, + true); + data = urldb_get_url_data(norm_text); + if (data == NULL) + return NODE_CALLBACK_REJECT; + } + tree = user_data; + tree_update_URL_node(tree, msg_data->node, + norm_text, NULL, false); + } + else if (msg_data->flag == TREE_ELEMENT_TITLE) { + while (isspace(*text)) + text++; + norm_text = strdup(text); + if (norm_text == NULL) { + LOG(("malloc failed")); + warn_user("NoMemory", 0); + return NODE_CALLBACK_REJECT; + } + /* don't allow zero length entry text, return + false */ + if (norm_text[0] == '\0') { + warn_user("NoNameError", 0); + msg_data->data.text = NULL; + return NODE_CALLBACK_CONTINUE; + } + msg_data->data.text = norm_text; + } + + return NODE_CALLBACK_HANDLED; + default: + break; + } + return NODE_CALLBACK_NOT_HANDLED; +} + +/** + * 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 + */ +static xmlNode *tree_url_find_xml_element(xmlNode *node, const char *name) +{ + xmlNode *xmlnode; + if (node == NULL) + return NULL; + + for (xmlnode = node->children; + xmlnode && !(xmlnode->type == XML_ELEMENT_NODE && + strcmp((const char *) xmlnode->name, name) == 0); + xmlnode = xmlnode->next) + ; + + return xmlnode; +} + +/** + * Parse an entry represented as a li. + * + * \param li xmlNode for parsed li + * \param directory directory to add this entry to + */ +static void tree_url_load_entry(xmlNode *li, struct tree *tree, + struct node *directory, tree_node_user_callback callback, + void *callback_data) +{ + char *url = NULL, *url1 = NULL; + char *title = NULL; + struct node *entry; + xmlNode *xmlnode; + const struct url_data *data; + url_func_result res; + + for (xmlnode = li->children; xmlnode; xmlnode = xmlnode->next) { + /* The li must contain an "a" element */ + if (xmlnode->type == XML_ELEMENT_NODE && + strcmp((const char *)xmlnode->name, "a") == 0) { + url1 = (char *)xmlGetProp(xmlnode, (const xmlChar *) "href"); + title = (char *)xmlNodeGetContent(xmlnode); + } + } + + if ((url1 == NULL) || (title == NULL)) { + warn_user("TreeLoadError", "(Missing <a> in <li> or " + "memory exhausted.)"); + return; + } + + /* We're loading external input. + * This may be garbage, so attempt to normalise + */ + res = url_normalize(url1, &url); + if (res != URL_FUNC_OK) { + LOG(("Failed normalising '%s'", url1)); + + if (res == URL_FUNC_NOMEM) + warn_user("NoMemory", NULL); + + xmlFree(url1); + xmlFree(title); + + return; + } + + /* No longer need this */ + xmlFree(url1); + + data = urldb_get_url_data(url); + if (data == NULL) { + /* No entry in database, so add one */ + urldb_add_url(url); + /* now attempt to get url data */ + data = urldb_get_url_data(url); + } + if (data == NULL) { + xmlFree(title); + free(url); + + return; + } + + /* Make this URL persistent */ + urldb_set_url_persistence(url, true); + + if (data->title == NULL) + urldb_set_url_title(url, title); + + entry = tree_create_URL_node(tree, directory, url, title, + callback, callback_data); + + if (entry == NULL) { + /** \todo why isn't this fatal? */ + warn_user("NoMemory", 0); + } else { + tree_update_URL_node(tree, entry, url, data, false); + } + + + xmlFree(title); + free(url); +} + +/** + * Parse a directory represented as a ul. + * + * \param ul xmlNode for parsed ul + * \param directory directory to add this directory to + */ +static void tree_url_load_directory(xmlNode *ul, struct tree *tree, + struct node *directory, tree_node_user_callback callback, + void *callback_data) +{ + char *title; + struct node *dir; + xmlNode *xmlnode; + + assert(ul != NULL); + assert(directory != NULL); + + for (xmlnode = ul->children; xmlnode; xmlnode = xmlnode->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 (xmlnode->type != XML_ELEMENT_NODE) + continue; + + if (strcmp((const char *)xmlnode->name, "li") == 0) { + /* entry */ + tree_url_load_entry(xmlnode, tree, directory, callback, + callback_data); + + } else if (strcmp((const char *)xmlnode->name, "h4") == 0) { + /* directory */ + title = (char *) xmlNodeGetContent(xmlnode ); + if (!title) { + warn_user("TreeLoadError", "(Empty <h4> " + "or memory exhausted.)"); + return; + } + + for (xmlnode = xmlnode->next; + xmlnode && xmlnode->type != XML_ELEMENT_NODE; + xmlnode = xmlnode->next) + ; + if ((xmlnode == NULL) || + strcmp((const char *)xmlnode->name, "ul") != 0) { + /* next element isn't expected ul */ + free(title); + warn_user("TreeLoadError", "(Expected " + "<ul> not present.)"); + return; + } + + dir = tree_create_folder_node(tree, directory, title, + true, false, false); + if (dir == NULL) { + free(title); + return; + } + + if (callback != NULL) + tree_set_node_user_callback(dir, callback, + callback_data); + + if (folder_icon != NULL) + tree_set_node_icon(tree, dir, folder_icon); + + tree_url_load_directory(xmlnode, tree, dir, callback, + callback_data); + } + } +} + +/** + * Loads an url tree from a specified file. + * + * \param filename name of file to read + * \param tree empty tree which data will be read into + * \return the file represented as a tree, or NULL on failure + */ +bool tree_urlfile_load(const char *filename, struct tree *tree, + tree_node_user_callback callback, void *callback_data) +{ + xmlDoc *doc; + xmlNode *html, *body, *ul; + struct node *root; + FILE *fp = NULL; + + if (filename == NULL) { + return false; + } + + fp = fopen(filename, "r"); + if (fp == NULL) { + return false; + } + fclose(fp); + + doc = htmlParseFile(filename, "iso-8859-1"); + if (doc == NULL) { + warn_user("TreeLoadError", messages_get("ParsingFail")); + return false; + } + + html = tree_url_find_xml_element((xmlNode *) doc, "html"); + body = tree_url_find_xml_element(html, "body"); + ul = tree_url_find_xml_element(body, "ul"); + if (ul == NULL) { + xmlFreeDoc(doc); + warn_user("TreeLoadError", + "(<html>...<body>...<ul> not found.)"); + return false; + } + + root = tree_get_root(tree); + tree_url_load_directory(ul, tree, root, callback, callback_data); + tree_set_node_expanded(tree, root, true, false, false); + + xmlFreeDoc(doc); + 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 + */ +static bool tree_url_save_entry(struct node *entry, xmlNode *node) +{ + xmlNode *li, *a; + xmlAttr *href; + const char *text; + + li = xmlNewChild(node, NULL, (const xmlChar *) "li", NULL); + if (li == NULL) + return false; + + + text = tree_url_node_get_title(entry); + if (text == NULL) + return false; + a = xmlNewTextChild(li, NULL, (const xmlChar *) "a", + (const xmlChar *) text); + if (a == NULL) + return false; + + text = tree_url_node_get_url(entry); + if (text == NULL) + return false; + + href = xmlNewProp(a, (const xmlChar *) "href", (const xmlChar *) text); + if (href == NULL) + return false; + 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 + */ +static bool tree_url_save_directory(struct node *directory, xmlNode *node) +{ + struct node *child; + xmlNode *ul, *h4; + const char *text; + + ul = xmlNewChild(node, NULL, (const xmlChar *)"ul", NULL); + if (ul == NULL) + return false; + + for (child = tree_node_get_child(directory); child; + child = tree_node_get_next(child)) { + if (!tree_node_is_folder(child)) { + /* entry */ + if (!tree_url_save_entry(child, ul)) + return false; + } else { + /* directory */ + /* invalid HTML */ + + text = tree_url_node_get_title(child); + if (text == NULL) + return false; + + h4 = xmlNewTextChild(ul, NULL, + (const xmlChar *) "h4", + (const xmlChar *) text); + if (h4 == NULL) + return false; + + if (!tree_url_save_directory(child, ul)) + return false; + } } + + return true; +} + + + + + + + + +/** + * Perform a save to a specified file in the form of a html page + * + * \param filename the file to save to + * \param page_title title of the page + */ +bool tree_urlfile_save(struct tree *tree, const char *filename, + const char *page_title) +{ + int res; + xmlDoc *doc; + xmlNode *html, *head, *title, *body; + + /* Unfortunately the Browse Hotlist format is invalid HTML, + * so this is a lie. + */ + doc = htmlNewDoc( + (const xmlChar *) "http://www.w3.org/TR/html4/strict.dtd", + (const xmlChar *) "-//W3C//DTD HTML 4.01//EN"); + if (doc == NULL) { + warn_user("NoMemory", 0); + return false; + } + + html = xmlNewNode(NULL, (const xmlChar *) "html"); + if (html == NULL) { + warn_user("NoMemory", 0); + xmlFreeDoc(doc); + return false; + } + xmlDocSetRootElement(doc, html); + + head = xmlNewChild(html, NULL, (const xmlChar *) "head", NULL); + if (head == NULL) { + warn_user("NoMemory", 0); + xmlFreeDoc(doc); + return false; + } + + title = xmlNewTextChild(head, NULL, (const xmlChar *) "title", + (const xmlChar *) page_title); + if (title == NULL) { + warn_user("NoMemory", 0); + xmlFreeDoc(doc); + return false; + } + + body = xmlNewChild(html, NULL, (const xmlChar *) "body", NULL); + if (body == NULL) { + warn_user("NoMemory", 0); + xmlFreeDoc(doc); + return false; + } + + if (!tree_url_save_directory(tree_get_root(tree), 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; +} diff --git a/desktop/tree_url_node.h b/desktop/tree_url_node.h new file mode 100644 index 000000000..4bee73ebc --- /dev/null +++ b/desktop/tree_url_node.h @@ -0,0 +1,56 @@ +/* + * Copyright 2005 Richard Wilson <info@tinct.net> + * Copyright 2009 Paul Blokus <paul_pl@users.sourceforge.net> + * + * 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 <http://www.gnu.org/licenses/>. + */ + +/** \file + * Creation of URL nodes with use of trees public API + */ + +#ifndef _NETSURF_DESKTOP_TREE_URL_NODE_H_ +#define _NETSURF_DESKTOP_TREE_URL_NODE_H_ + + +#include "desktop/tree.h" + +void tree_url_node_init(void); +struct node *tree_create_URL_node(struct tree *tree, + struct node *parent, const char *url, const char *title, + tree_node_user_callback, void *callback_data); +struct node *tree_create_URL_node_shared(struct tree *tree, + struct node *parent, const char *url, + const struct url_data *data, + tree_node_user_callback, void *callback_data); +void tree_update_URL_node(struct tree *tree,struct node *node, + const char *url, const struct url_data *data, bool shared); +const char *tree_url_node_get_title(struct node *node); +const char *tree_url_node_get_url(struct node *node); +void tree_url_node_edit_title(struct tree *tree, struct node *node); +void tree_url_node_edit_url(struct tree *tree, struct node *node); + +node_callback_resp tree_url_node_callback(void *user_data, + struct node_msg_data *msg_data); + +bool tree_urlfile_load(const char *filename, struct tree *tree, + tree_node_user_callback, void *callback_data); +bool tree_urlfile_save(struct tree *tree, const char *filename, + const char *page_title); + +/* front end specific */ +void tree_icon_name_from_content_type(char *buffer, content_type type); + +#endif |