summaryrefslogtreecommitdiff
path: root/desktop
diff options
context:
space:
mode:
Diffstat (limited to 'desktop')
-rw-r--r--desktop/browser.c1
-rw-r--r--desktop/cookies.c531
-rw-r--r--desktop/cookies.h36
-rw-r--r--desktop/history_global_core.c464
-rw-r--r--desktop/history_global_core.h44
-rw-r--r--desktop/hotlist.c457
-rw-r--r--desktop/hotlist.h54
-rw-r--r--desktop/options.c363
-rw-r--r--desktop/options.h5
-rw-r--r--desktop/sslcert.c276
-rw-r--r--desktop/sslcert.h43
-rw-r--r--desktop/tree.c3090
-rw-r--r--desktop/tree.h281
-rw-r--r--desktop/tree_url_node.c846
-rw-r--r--desktop/tree_url_node.h56
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