summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--content/fetchers/resource.c5
-rw-r--r--desktop/Makefile2
-rw-r--r--desktop/core_window.h51
-rw-r--r--desktop/global_history.c685
-rw-r--r--desktop/global_history.h37
-rw-r--r--desktop/tree.c114
-rw-r--r--desktop/treeview.c1363
-rw-r--r--desktop/treeview.h154
-rw-r--r--utils/types.h2
9 files changed, 2411 insertions, 2 deletions
diff --git a/content/fetchers/resource.c b/content/fetchers/resource.c
index 4afd44310..2c31da2d4 100644
--- a/content/fetchers/resource.c
+++ b/content/fetchers/resource.c
@@ -81,7 +81,10 @@ static const char *fetch_resource_paths[] = {
"licence.html",
"welcome.html",
"favicon.ico",
- "netsurf.png"
+ "netsurf.png",
+ "icons/content.png",
+ "icons/directory.png",
+ "icons/search.png"
};
static struct fetch_resource_map_entry {
lwc_string *path;
diff --git a/desktop/Makefile b/desktop/Makefile
index f91754eb9..f787fd295 100644
--- a/desktop/Makefile
+++ b/desktop/Makefile
@@ -3,7 +3,7 @@
S_DESKTOP := cookies.c history_global_core.c hotlist.c knockout.c \
mouse.c plot_style.c print.c search.c searchweb.c \
scrollbar.c sslcert.c textarea.c thumbnail.c tree.c \
- tree_url_node.c version.c system_colour.c
+ tree_url_node.c version.c system_colour.c global_history.c treeview.c
S_DESKTOP := $(addprefix desktop/,$(S_DESKTOP))
diff --git a/desktop/core_window.h b/desktop/core_window.h
new file mode 100644
index 000000000..9e68e2679
--- /dev/null
+++ b/desktop/core_window.h
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2012 Michael Drake <tlsa@netsurf-browser.org>
+ *
+ * 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
+ * Core window handling (interface).
+ */
+
+#ifndef _NETSURF_DESKTOP_CORE_WINDOW_H_
+#define _NETSURF_DESKTOP_CORE_WINDOW_H_
+
+#include "utils/types.h"
+
+struct core_window;
+
+/** Callbacks to achieve various core window functionality. */
+struct core_window_callback_table {
+ /** Request a redraw of the window. */
+ void (*redraw_request)(struct core_window *cw, struct rect r);
+
+ /** Update the limits of the window */
+ void (*update_size)(struct core_window *cw, int width, int height);
+
+ /** Scroll the window to make area visible */
+ void (*scroll_visible)(struct core_window *cw, struct rect r);
+
+ /** Get window viewport dimensions */
+ void (*get_window_dimensions)(struct core_window *cw,
+ int *width, int *height);
+};
+
+
+void core_window_draw(struct core_window *cw, int x, int y, struct rect r,
+ const struct redraw_context *ctx);
+
+
+#endif
diff --git a/desktop/global_history.c b/desktop/global_history.c
new file mode 100644
index 000000000..27b8e47b4
--- /dev/null
+++ b/desktop/global_history.c
@@ -0,0 +1,685 @@
+/*
+ * Copyright 2012 - 2013 Michael Drake <tlsa@netsurf-browser.org>
+ *
+ * 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/urldb.h"
+#include "desktop/browser.h"
+#include "desktop/global_history.h"
+#include "desktop/treeview.h"
+#include "utils/messages.h"
+#include "utils/utils.h"
+#include "utils/log.h"
+
+#define N_FIELDS 5
+#define N_DAYS 28
+#define N_SEC_PER_DAY (60 * 60 * 24)
+
+enum global_history_folders {
+ GH_TODAY = 0,
+ GH_YESTERDAY,
+ GH_2_DAYS_AGO,
+ GH_3_DAYS_AGO,
+ GH_4_DAYS_AGO,
+ GH_5_DAYS_AGO,
+ GH_6_DAYS_AGO,
+ GH_LAST_WEEK,
+ GH_2_WEEKS_AGO,
+ GH_3_WEEKS_AGO,
+ GH_N_FOLDERS
+};
+
+struct global_history_folder {
+ struct treeview_node *folder;
+ struct treeview_field_data data;
+};
+
+struct global_history_ctx {
+ struct treeview *tree;
+ struct treeview_field_desc fields[N_FIELDS];
+ struct global_history_folder folders[GH_N_FOLDERS];
+ time_t today;
+ int weekday;
+};
+struct global_history_ctx gh_ctx;
+
+struct global_history_entry {
+ int slot;
+ nsurl *url;
+ time_t t;
+ struct treeview_node *entry;
+ struct global_history_entry *next;
+ struct global_history_entry *prev;
+
+ struct treeview_field_data data[N_FIELDS - 1];
+};
+struct global_history_entry *gh_list[N_DAYS];
+
+
+/**
+ * Find an entry in the global history
+ *
+ * \param url The URL to find
+ * \return Pointer to node, or NULL if not found
+ */
+static struct global_history_entry *global_history_find(nsurl *url)
+{
+ int i;
+ struct global_history_entry *e;
+
+ for (i = 0; i < N_DAYS; i++) {
+ e = gh_list[i];
+
+ while (e != NULL) {
+ if (nsurl_compare(e->url, url,
+ NSURL_COMPLETE) == true) {
+ return e;
+ }
+ e = e->next;
+ }
+
+ }
+ return NULL;
+}
+
+static inline nserror global_history_get_parent_treeview_node(
+ struct treeview_node **parent, int slot)
+{
+ int folder_index;
+ struct global_history_folder *f;
+
+ if (slot < 7) {
+ folder_index = slot;
+
+ } else if (slot < 14) {
+ folder_index = GH_LAST_WEEK;
+
+ } else if (slot < 21) {
+ folder_index = GH_2_WEEKS_AGO;
+
+ } else if (slot < N_DAYS) {
+ folder_index = GH_3_WEEKS_AGO;
+
+ } else {
+ /* Slot value is invalid */
+ return NSERROR_BAD_PARAMETER;
+ }
+
+ /* Get the folder */
+ f = &(gh_ctx.folders[folder_index]);
+
+ /* Return the parent treeview folder */
+ *parent = f->folder;
+ return NSERROR_OK;
+}
+
+
+static nserror global_history_create_treeview_field_data(
+ struct global_history_entry *e,
+ const struct url_data *data)
+{
+ const char *title = (data->title != NULL) ? data->title : "<No title>";
+ char buffer[16];
+ const char *last_visited;
+ char *last_visited2;
+ int len;
+
+ e->data[0].field = gh_ctx.fields[0].field;
+ e->data[0].value = strdup(title);
+ e->data[0].value_len = (e->data[0].value != NULL) ? strlen(title) : 0;
+
+ e->data[1].field = gh_ctx.fields[1].field;
+ e->data[1].value = nsurl_access(e->url);
+ e->data[1].value_len = nsurl_length(e->url);
+
+ last_visited = ctime(&data->last_visit);
+ last_visited2 = strdup(last_visited);
+ if (last_visited2 != NULL) {
+ assert(last_visited2[24] == '\n');
+ last_visited2[24] = '\0';
+ }
+
+ e->data[2].field = gh_ctx.fields[2].field;
+ e->data[2].value = last_visited2;
+ e->data[2].value_len = (last_visited2 != NULL) ? 24 : 0;
+
+ len = snprintf(buffer, 16, "%u", data->visits);
+ if (len == 16) {
+ len--;
+ buffer[len] = '\0';
+ }
+
+ e->data[3].field = gh_ctx.fields[3].field;
+ e->data[3].value = strdup(buffer);
+ e->data[3].value_len = len;
+
+ return NSERROR_OK;
+}
+
+/**
+ * Add a global history entry to the treeview
+ *
+ * \param e entry to add to treeview
+ * \param slot global history slot containing entry
+ * \return NSERROR_OK on success, or appropriate error otherwise
+ *
+ * It is assumed that the entry is unique (for its URL) in the global
+ * history table
+ */
+static nserror global_history_entry_insert(struct global_history_entry *e,
+ int slot)
+{
+ nserror err;
+
+ struct treeview_node *parent;
+ err = global_history_get_parent_treeview_node(&parent, slot);
+ if (err != NSERROR_OK) {
+ return err;
+ }
+
+ err = treeview_create_node_entry(gh_ctx.tree, &(e->entry),
+ parent, TREE_REL_FIRST_CHILD, e->data, e);
+ if (err != NSERROR_OK) {
+ return err;
+ }
+
+ return NSERROR_OK;
+}
+
+
+static nserror global_history_add_entry_internal(nsurl *url, int slot,
+ const struct url_data *data, bool got_treeview)
+{
+ nserror err;
+ struct global_history_entry *e;
+
+ /* Create new local history entry */
+ e = malloc(sizeof(struct global_history_entry));
+ if (e == NULL) {
+ return false;
+ }
+
+ e->slot = slot;
+ e->url = nsurl_ref(url);
+ e->t = data->last_visit;
+ e->entry = NULL;
+ e->next = NULL;
+ e->prev = NULL;
+
+ err = global_history_create_treeview_field_data(e, data);
+ if (err != NSERROR_OK) {
+ return err;
+ }
+
+ if (gh_list[slot] == NULL) {
+ /* list empty */
+ gh_list[slot] = e;
+
+ } else if (gh_list[slot]->t < e->t) {
+ /* Insert at list head */
+ e->next = gh_list[slot];
+ gh_list[slot]->prev = e;
+ gh_list[slot] = e;
+ } else {
+ struct global_history_entry *prev = gh_list[slot];
+ struct global_history_entry *curr = prev->next;
+ while (curr != NULL) {
+ if (curr->t < e->t) {
+ break;
+ }
+ prev = curr;
+ curr = curr->next;
+ }
+
+ /* insert after prev */
+ e->next = curr;
+ e->prev = prev;
+ prev->next = e;
+
+ if (curr != NULL)
+ curr->prev = e;
+ }
+
+ if (got_treeview) {
+ err = global_history_entry_insert(e, slot);
+ if (err != NSERROR_OK) {
+ return err;
+ }
+ }
+
+ return NSERROR_OK;
+}
+
+static void global_history_delete_entry_internal(
+ struct global_history_entry *e)
+{
+ /* Unlink */
+ if (gh_list[e->slot] == e) {
+ /* e is first entry */
+ gh_list[e->slot] = e->next;
+
+ if (e->next != NULL)
+ e->next->prev = NULL;
+
+ } else if (e->next == NULL) {
+ /* e is last entry */
+ e->prev->next = NULL;
+
+ } else {
+ /* e has an entry before and after */
+ e->prev->next = e->next;
+ e->next->prev = e->prev;
+ }
+
+ /* Destroy */
+ free((void *)e->data[0].value); /* Eww */
+ free((void *)e->data[2].value); /* Eww */
+ free((void *)e->data[3].value); /* Eww */
+ nsurl_unref(e->url);
+ free(e);
+}
+
+/**
+ * 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_entry(nsurl *url,
+ const struct url_data *data)
+{
+ int slot;
+ struct global_history_entry *e;
+ time_t visit_date;
+ time_t earliest_date = gh_ctx.today - (N_DAYS - 1) * N_SEC_PER_DAY;
+ bool got_treeview = gh_ctx.tree != NULL;
+
+ assert((url != NULL) && (data != NULL));
+
+ visit_date = data->last_visit;
+
+ /* Find day array slot for entry */
+ if (visit_date >= gh_ctx.today) {
+ slot = 0;
+ } else if (visit_date >= earliest_date) {
+ slot = (gh_ctx.today - visit_date) / N_SEC_PER_DAY + 1;
+ } else {
+ /* too old */
+ return true;
+ }
+
+ if (got_treeview == true) {
+ /* The treeview for global history already exists */
+
+ /* See if there's already an entry for this URL */
+ e = global_history_find(url);
+ if (e != NULL) {
+ /* Existing entry. Delete it. */
+ treeview_delete_node(gh_ctx.tree, e->entry);
+ return true;
+ }
+ }
+
+ if (global_history_add_entry_internal(url, slot, data,
+ got_treeview) != NSERROR_OK) {
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * Initialise the treeview entry feilds
+ *
+ * \return true on success, false on memory exhaustion
+ */
+static nserror global_history_initialise_entry_fields(void)
+{
+ int i;
+
+ for (i = 0; i < N_FIELDS; i++)
+ gh_ctx.fields[i].field = NULL;
+
+ /* TODO: use messages */
+ gh_ctx.fields[0].flags = TREE_FLAG_DEFAULT;
+ if (lwc_intern_string("Title", SLEN("Title"),
+ &gh_ctx.fields[0].field) !=
+ lwc_error_ok) {
+ goto error;
+ }
+
+ gh_ctx.fields[1].flags = TREE_FLAG_NONE;
+ if (lwc_intern_string("URL", SLEN("URL"),
+ &gh_ctx.fields[1].field) !=
+ lwc_error_ok) {
+ goto error;
+ }
+
+ gh_ctx.fields[2].flags = TREE_FLAG_SHOW_NAME;
+ if (lwc_intern_string("Last visit", SLEN("Last visit"),
+ &gh_ctx.fields[2].field) !=
+ lwc_error_ok) {
+ goto error;
+ }
+
+ gh_ctx.fields[3].flags = TREE_FLAG_SHOW_NAME;
+ if (lwc_intern_string("Visits", SLEN("Visits"),
+ &gh_ctx.fields[3].field) !=
+ lwc_error_ok) {
+ goto error;
+ }
+
+ gh_ctx.fields[4].flags = TREE_FLAG_DEFAULT;
+ if (lwc_intern_string("Period", SLEN("Period"),
+ &gh_ctx.fields[4].field) !=
+ lwc_error_ok) {
+ return false;
+ }
+
+ return NSERROR_OK;
+
+error:
+ for (i = 0; i < N_FIELDS; i++)
+ if (gh_ctx.fields[i].field != NULL)
+ lwc_string_unref(gh_ctx.fields[i].field);
+
+ return NSERROR_UNKNOWN;
+}
+
+
+/**
+ * Initialise the time
+ *
+ * \return true on success, false on memory exhaustion
+ */
+static nserror global_history_initialise_time(void)
+{
+ struct tm *full_time;
+ time_t t;
+
+ /* get the current time */
+ t = time(NULL);
+ if (t == -1) {
+ LOG(("time info unaviable"));
+ return NSERROR_UNKNOWN;
+ }
+
+ /* get the time at the start of today */
+ full_time = localtime(&t);
+ 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 NSERROR_UNKNOWN;
+ }
+
+ gh_ctx.today = t;
+ gh_ctx.weekday = full_time->tm_wday;
+
+ return NSERROR_OK;
+}
+
+
+/**
+ * Initialise the treeview directories
+ *
+ * \return true on success, false on memory exhaustion
+ */
+static nserror global_history_init_dir(enum global_history_folders f,
+ const char *label, int age)
+{
+ nserror err;
+ time_t t = gh_ctx.today;
+ struct treeview_node *relation = NULL;
+ enum treeview_relationship rel = TREE_REL_FIRST_CHILD;
+
+ t -= age * N_SEC_PER_DAY;
+
+ label = messages_get(label);
+
+ if (f != GH_TODAY) {
+ relation = gh_ctx.folders[f - 1].folder;
+ rel = TREE_REL_NEXT_SIBLING;
+ }
+
+ gh_ctx.folders[f].data.field = gh_ctx.fields[N_FIELDS - 1].field;
+ gh_ctx.folders[f].data.value = label;
+ gh_ctx.folders[f].data.value_len = strlen(label);
+ err = treeview_create_node_folder(gh_ctx.tree,
+ &gh_ctx.folders[f].folder,
+ relation, rel,
+ &gh_ctx.folders[f].data,
+ &gh_ctx.folders[f]);
+
+ return err;
+}
+
+
+/**
+ * Initialise the treeview directories
+ *
+ * \return true on success, false on memory exhaustion
+ */
+static nserror global_history_init_dirs(void)
+{
+ nserror err;
+
+ err = global_history_init_dir(GH_TODAY, "DateToday", 0);
+ if (err != NSERROR_OK) return err;
+
+ err = global_history_init_dir(GH_YESTERDAY, "DateYesterday", 1);
+ if (err != NSERROR_OK) return err;
+
+ err = global_history_init_dir(GH_2_DAYS_AGO, "Date2Days", 2);
+ if (err != NSERROR_OK) return err;
+
+ err = global_history_init_dir(GH_3_DAYS_AGO, "Date3Days", 3);
+ if (err != NSERROR_OK) return err;
+
+ err = global_history_init_dir(GH_4_DAYS_AGO, "Date4Days", 4);
+ if (err != NSERROR_OK) return err;
+
+ err = global_history_init_dir(GH_5_DAYS_AGO, "Date5Days", 5);
+ if (err != NSERROR_OK) return err;
+
+ err = global_history_init_dir(GH_6_DAYS_AGO, "Date6Days", 6);
+ if (err != NSERROR_OK) return err;
+
+ err = global_history_init_dir(GH_LAST_WEEK, "Date1Week", 7);
+ if (err != NSERROR_OK) return err;
+
+ err = global_history_init_dir(GH_2_WEEKS_AGO, "Date2Week", 14);
+ if (err != NSERROR_OK) return err;
+
+ err = global_history_init_dir(GH_3_WEEKS_AGO, "Date3Week", 21);
+ if (err != NSERROR_OK) return err;
+
+ return NSERROR_OK;
+}
+
+
+/**
+ * Initialise the treeview entries
+ *
+ * \return true on success, false on memory exhaustion
+ */
+static nserror global_history_init_entries(void)
+{
+ int i;
+ nserror err;
+
+ /* Itterate over all global history data, inserting it into treeview */
+ for (i = 0; i < N_DAYS; i++) {
+ struct global_history_entry *l = NULL;
+ struct global_history_entry *e = gh_list[i];
+
+ /* Insert in reverse order; find last */
+ while (e != NULL) {
+ l = e;
+ e = e->next;
+ }
+
+ /* Insert the entries into the treeview */
+ while (l != NULL) {
+ err = global_history_entry_insert(l, i);
+ if (err != NSERROR_OK) {
+ return err;
+ }
+ l = l->prev;
+ }
+ }
+
+ return NSERROR_OK;
+}
+
+
+static nserror global_history_tree_node_folder_cb(
+ struct treeview_node_msg msg, void *data)
+{
+ return NSERROR_OK;
+}
+static nserror global_history_tree_node_entry_cb(
+ struct treeview_node_msg msg, void *data)
+{
+ struct global_history_entry *e = (struct global_history_entry *)data;
+
+ switch (msg.msg) {
+ case TREE_MSG_NODE_DELETE:
+ global_history_delete_entry_internal(e);
+ break;
+
+ case TREE_MSG_NODE_EDIT:
+ break;
+
+ case TREE_MSG_NODE_LAUNCH:
+ break;
+ }
+ return NSERROR_OK;
+}
+struct treeview_callback_table tree_cb_t = {
+ .folder = global_history_tree_node_folder_cb,
+ .entry = global_history_tree_node_entry_cb
+};
+
+/**
+ * Initialises the global history module.
+ *
+ * \param
+ * \param
+ * \return true on success, false on memory exhaustion
+ */
+nserror global_history_init(struct core_window_callback_table *cw_t,
+ void *core_window_handle)
+{
+ nserror err;
+
+ LOG(("Loading global history"));
+
+ /* Init. global history treeview time */
+ err = global_history_initialise_time();
+ if (err != NSERROR_OK) {
+ gh_ctx.tree = NULL;
+ return err;
+ }
+
+ /* Init. global history treeview entry fields */
+ err = global_history_initialise_entry_fields();
+ if (err != NSERROR_OK) {
+ gh_ctx.tree = NULL;
+ return err;
+ }
+
+ /* Load the entries */
+ urldb_iterate_entries(global_history_add_entry);
+
+ /* Create the global history treeview */
+ err = treeview_create(&gh_ctx.tree, &tree_cb_t,
+ N_FIELDS, gh_ctx.fields,
+ cw_t, core_window_handle);
+ if (err != NSERROR_OK) {
+ gh_ctx.tree = NULL;
+ return err;
+ }
+
+ /* Add the folders to the treeview */
+ err = global_history_init_dirs();
+ if (err != NSERROR_OK) {
+ return err;
+ }
+
+ /* Add the history to the treeview */
+ err = global_history_init_entries();
+ if (err != NSERROR_OK) {
+ return err;
+ }
+
+ /* Expand the "Today" folder node */
+ err = treeview_node_expand(gh_ctx.tree,
+ gh_ctx.folders[GH_TODAY].folder);
+ if (err != NSERROR_OK) {
+ return err;
+ }
+
+ LOG(("Loaded global history"));
+
+ return NSERROR_OK;
+}
+
+/**
+ * Finalises the global history module.
+ *
+ * \param
+ * \param
+ * \return true on success, false on memory exhaustion
+ */
+nserror global_history_fini(struct core_window_callback_table *cw_t,
+ void *core_window_handle)
+{
+ int i;
+ nserror err;
+
+ LOG(("Finalising global history"));
+
+ /* Destroy the global history treeview */
+ err = treeview_destroy(gh_ctx.tree);
+
+ /* Free global history treeview entry fields */
+ for (i = 0; i < N_FIELDS; i++)
+ if (gh_ctx.fields[i].field != NULL)
+ lwc_string_unref(gh_ctx.fields[i].field);
+
+ LOG(("Finalised global history"));
+
+ return NSERROR_OK;
+}
+
+void global_history_redraw(int x, int y, struct rect *clip,
+ const struct redraw_context *ctx)
+{
+ treeview_redraw(gh_ctx.tree, x, y, clip, ctx);
+}
+
+void global_history_mouse_action(browser_mouse_state mouse, int x, int y)
+{
+ treeview_mouse_action(gh_ctx.tree, mouse, x, y);
+}
+
diff --git a/desktop/global_history.h b/desktop/global_history.h
new file mode 100644
index 000000000..960eb1e7a
--- /dev/null
+++ b/desktop/global_history.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2012 - 2013 Michael Drake <tlsa@netsurf-browser.org>
+ *
+ * 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_GLOBAL_HISTORY_H_
+#define _NETSURF_DESKTOP_GLOBAL_HISTORY_H_
+
+#include <stdbool.h>
+
+#include "desktop/core_window.h"
+
+nserror global_history_init(struct core_window_callback_table *cw_t,
+ void *core_window_handle);
+
+nserror global_history_fini(struct core_window_callback_table *cw_t,
+ void *core_window_handle);
+
+void global_history_redraw(int x, int y, struct rect *clip,
+ const struct redraw_context *ctx);
+
+void global_history_mouse_action(browser_mouse_state mouse, int x, int y);
+
+#endif
diff --git a/desktop/tree.c b/desktop/tree.c
index 2e35b5bf8..92a25109c 100644
--- a/desktop/tree.c
+++ b/desktop/tree.c
@@ -173,6 +173,101 @@ struct tree {
struct node *def_folder; /* Node to be used for additions by default */
};
+
+
+
+#include "desktop/treeview.h"
+#include "desktop/global_history.h"
+
+static void treeview_test_redraw_request(struct core_window *cw, struct rect r)
+{
+ struct tree *tree = (struct tree *)cw;
+
+ tree->callbacks->redraw_request(r.x0, r.y0,
+ r.x1 - r.x0, r.y1 - r.y0,
+ tree->client_data);
+}
+
+static void treeview_test_update_size(struct core_window *cw,
+ int width, int height)
+{
+}
+
+static void treeview_test_scroll_visible(struct core_window *cw, struct rect r)
+{
+}
+
+static void treeview_test_get_window_dimensions(struct core_window *cw,
+ int *width, int *height)
+{
+}
+
+struct core_window_callback_table cw_t = {
+ .redraw_request = treeview_test_redraw_request,
+ .update_size = treeview_test_update_size,
+ .scroll_visible = treeview_test_scroll_visible,
+ .get_window_dimensions = treeview_test_get_window_dimensions
+};
+
+static void treeview_test_init(struct tree *tree)
+{
+ nserror err;
+
+ treeview_init();
+
+ err = global_history_init(&cw_t, (struct core_window *)tree);
+
+ if (err != NSERROR_OK) {
+ warn_user("Duffed it.", 0);
+ }
+}
+
+static void treeview_test_fini(struct tree *tree)
+{
+ nserror err;
+
+ err = global_history_fini(&cw_t, (struct core_window *)tree);
+
+ treeview_fini();
+
+ if (err != NSERROR_OK) {
+ warn_user("Duffed it.", 0);
+ }
+}
+
+static void treeview_test_redraw(struct tree *tree, int x, int y,
+ int clip_x, int clip_y, int clip_width, int clip_height,
+ const struct redraw_context *ctx)
+{
+ struct rect clip;
+ clip.x0 = clip_x;
+ clip.y0 = clip_y;
+ clip.x1 = clip_x + clip_width;
+ clip.y1 = clip_y + clip_height;
+
+ global_history_redraw(x, y, &clip, ctx);
+}
+
+static void treeview_test_mouse_action(struct tree *tree,
+ browser_mouse_state mouse, int x, int y)
+{
+ global_history_mouse_action(mouse, x, y);
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
void tree_set_icon_dir(char *icon_dir)
{
LOG(("Tree icon directory set to %s", icon_dir));
@@ -276,6 +371,10 @@ struct tree *tree_create(unsigned int flags,
tree_setup_colours();
+ if (flags == TREE_MOVABLE) {
+ treeview_test_init(tree);
+ }
+
return tree;
}
@@ -1119,6 +1218,10 @@ void tree_delete(struct tree *tree)
{
tree->redraw = false;
+ if (tree->flags == TREE_MOVABLE) {
+ treeview_test_fini(tree);
+ }
+
if (tree->root->child != NULL)
tree_delete_node_internal(tree, tree->root->child, true);
@@ -2044,6 +2147,12 @@ void tree_draw(struct tree *tree, int x, int y,
assert(tree != NULL);
assert(tree->root != NULL);
+ if (tree->flags == TREE_MOVABLE) {
+ treeview_test_redraw(tree, x, y, clip_x, clip_y,
+ clip_width, clip_height, ctx);
+ return;
+ }
+
/* Start knockout rendering if it's available for this plotter */
if (ctx->plot->option_knockout)
knockout_plot_start(ctx, &new_ctx);
@@ -2408,6 +2517,11 @@ bool tree_mouse_action(struct tree *tree, browser_mouse_state mouse, int x,
assert(tree != NULL);
assert(tree->root != NULL);
+ if (tree->flags == TREE_MOVABLE) {
+ treeview_test_mouse_action(tree, mouse, x, y);
+ return true;
+ }
+
if (tree->root->child == NULL)
return true;
diff --git a/desktop/treeview.c b/desktop/treeview.c
new file mode 100644
index 000000000..ae2d030b8
--- /dev/null
+++ b/desktop/treeview.c
@@ -0,0 +1,1363 @@
+/*
+ * Copyright 2012 - 2013 Michael Drake <tlsa@netsurf-browser.org>
+ *
+ * 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
+ * Treeview handling (implementation).
+ */
+
+#include "css/utils.h"
+#include "desktop/gui.h"
+#include "desktop/knockout.h"
+#include "desktop/plotters.h"
+#include "desktop/treeview.h"
+#include "render/font.h"
+#include "utils/log.h"
+
+/* TODO: get rid of REDRAW_MAX -- need to be able to know window size */
+#define REDRAW_MAX 8000
+
+struct treeview_globals {
+ int line_height;
+ int furniture_width;
+ int step_width;
+ int window_padding;
+ int icon_step;
+} tree_g;
+
+enum treeview_node_type {
+ TREE_NODE_ROOT,
+ TREE_NODE_FOLDER,
+ TREE_NODE_ENTRY
+};
+
+struct treeview_text {
+ const char *data;
+ uint32_t len;
+ int width;
+};
+
+struct treeview_field {
+ enum treeview_field_flags flags;
+
+ lwc_string *field;
+ struct treeview_text value;
+};
+
+enum treeview_node_flags {
+ TREE_NODE_NONE = 0, /**< No node flags set */
+ TREE_NODE_EXPANDED = (1 << 0), /**< Whether node is expanded */
+ TREE_NODE_SELECTED = (1 << 1) /**< Whether node is selected */
+
+};
+
+struct treeview_node {
+ enum treeview_node_flags flags;
+ enum treeview_node_type type;
+
+ int height;
+ int inset;
+
+ struct treeview_node *parent;
+ struct treeview_node *sibling_prev;
+ struct treeview_node *sibling_next;
+ struct treeview_node *children;
+
+ void *client_data;
+
+ struct treeview_field text;
+};
+
+struct treeview_node_entry {
+ struct treeview_node base;
+ struct treeview_field fields[];
+};
+
+struct treeview {
+ uint32_t view_height;
+ uint32_t view_width;
+
+ struct treeview_node *root;
+
+ struct treeview_field *fields;
+ int n_fields; /* fields[n_fields] is folder, lower are entry fields */
+ int field_width;
+
+ const struct treeview_callback_table *callbacks;
+ const struct core_window_callback_table *cw_t; /**< Core window callback table */
+ struct core_window *cw_h; /**< Core window handle */
+};
+
+
+struct treeview_node_style {
+ plot_style_t bg; /**< Background */
+ plot_font_style_t text; /**< Text */
+ plot_font_style_t itext; /**< Entry field text */
+
+ plot_style_t sbg; /**< Selected background */
+ plot_font_style_t stext; /**< Selected text */
+ plot_font_style_t sitext; /**< Selected entry field text */
+};
+
+struct treeview_node_style plot_style_odd;
+struct treeview_node_style plot_style_even;
+
+struct treeview_resource {
+ const char *url;
+ struct hlcache_handle *c;
+ int height;
+ bool ready;
+};
+enum treeview_resource_id {
+ TREE_RES_CONTENT = 0,
+ TREE_RES_FOLDER,
+ TREE_RES_SEARCH,
+ TREE_RES_LAST
+};
+static struct treeview_resource treeview_res[TREE_RES_LAST] = {
+ { "resource:icons/content.png", NULL, 0, false },
+ { "resource:icons/directory.png", NULL, 0, false },
+ { "resource:icons/search.png", NULL, 0, false }
+};
+
+
+
+enum treeview_furniture_id {
+ TREE_FURN_EXPAND = 0,
+ TREE_FURN_CONTRACT,
+ TREE_FURN_LAST
+};
+static struct treeview_text treeview_furn[TREE_FURN_LAST] = {
+ { "\xe2\x96\xb8", 3, 0 },
+ { "\xe2\x96\xbe", 3, 0 }
+};
+
+
+static nserror treeview_create_node_root(struct treeview_node **root)
+{
+ struct treeview_node *n;
+
+ n = malloc(sizeof(struct treeview_node));
+ if (n == NULL) {
+ return NSERROR_NOMEM;
+ }
+
+ n->flags = TREE_NODE_EXPANDED;
+ n->type = TREE_NODE_ROOT;
+
+ n->height = 0;
+ n->inset = tree_g.window_padding - tree_g.step_width;
+
+ n->text.flags = TREE_FLAG_NONE;
+ n->text.field = NULL;
+ n->text.value.data = NULL;
+ n->text.value.len = 0;
+ n->text.value.width = 0;
+
+ n->parent = NULL;
+ n->sibling_next = NULL;
+ n->sibling_prev = NULL;
+ n->children = NULL;
+
+ n->client_data = NULL;
+
+ *root = n;
+
+ return NSERROR_OK;
+}
+
+/**
+ * Insert a treeview node into a treeview
+ *
+ * \param a parentless node to insert
+ * \param b tree node to insert a as a relation of
+ * \param rel a's relationship to b
+ */
+static inline void treeview_insert_node(struct treeview_node *a,
+ struct treeview_node *b,
+ enum treeview_relationship rel)
+{
+ assert(a != NULL);
+ assert(a->parent == NULL);
+ assert(b != NULL);
+
+ switch (rel) {
+ case TREE_REL_FIRST_CHILD:
+ assert(b->type != TREE_NODE_ENTRY);
+ a->parent = b;
+ a->sibling_next = b->children;
+ if (a->sibling_next)
+ a->sibling_next->sibling_prev = a;
+ b->children = a;
+ break;
+
+ case TREE_REL_NEXT_SIBLING:
+ assert(b->type != TREE_NODE_ROOT);
+ a->sibling_prev = b;
+ a->sibling_next = b->sibling_next;
+ a->parent = b->parent;
+ b->sibling_next = a;
+ if (a->sibling_next)
+ a->sibling_next->sibling_prev = a;
+ break;
+
+ default:
+ assert(0);
+ break;
+ }
+
+ assert(a->parent != NULL);
+
+ a->inset = a->parent->inset + tree_g.step_width;
+
+ if (a->parent->flags & TREE_NODE_EXPANDED) {
+ /* Parent is expanded, so inserted node will be visible and
+ * affect layout */
+ b = a;
+ do {
+ b->parent->height += b->height;
+ b = b->parent;
+ } while (b->parent != NULL);
+
+ if (a->text.value.width == 0) {
+ nsfont.font_width(&plot_style_odd.text,
+ a->text.value.data,
+ a->text.value.len,
+ &(a->text.value.width));
+ }
+ }
+}
+
+
+nserror treeview_create_node_folder(struct treeview *tree,
+ struct treeview_node **folder,
+ struct treeview_node *relation,
+ enum treeview_relationship rel,
+ const struct treeview_field_data *field,
+ void *data)
+{
+ struct treeview_node *n;
+
+ assert(data != NULL);
+ assert(tree != NULL);
+ assert(tree->root != NULL);
+
+ if (relation == NULL) {
+ relation = tree->root;
+ rel = TREE_REL_FIRST_CHILD;
+ }
+
+ n = malloc(sizeof(struct treeview_node));
+ if (n == NULL) {
+ return NSERROR_NOMEM;
+ }
+
+ n->flags = TREE_NODE_NONE;
+ n->type = TREE_NODE_FOLDER;
+
+ n->height = tree_g.line_height;
+
+ n->text.value.data = field->value;
+ n->text.value.len = field->value_len;
+ n->text.value.width = 0;
+
+ n->parent = NULL;
+ n->sibling_next = NULL;
+ n->sibling_prev = NULL;
+ n->children = NULL;
+
+ n->client_data = data;
+
+ treeview_insert_node(n, relation, rel);
+
+ *folder = n;
+
+ return NSERROR_OK;
+}
+
+
+
+nserror treeview_update_node_entry(struct treeview *tree,
+ struct treeview_node *entry,
+ const struct treeview_field_data fields[],
+ void *data)
+{
+ bool match;
+ struct treeview_node_entry *e = (struct treeview_node_entry *)entry;
+ int i;
+
+ assert(data != NULL);
+ assert(tree != NULL);
+ assert(entry != NULL);
+ assert(data == entry->client_data);
+ assert(entry->parent != NULL);
+
+ assert(fields != NULL);
+ assert(fields[0].field != NULL);
+ assert(lwc_string_isequal(tree->fields[0].field,
+ fields[0].field, &match) == lwc_error_ok &&
+ match == true);
+ entry->text.value.data = fields[0].value;
+ entry->text.value.len = fields[0].value_len;
+ entry->text.value.width = 0;
+
+ if (entry->parent->flags & TREE_NODE_EXPANDED) {
+ /* Text will be seen, get its width */
+ nsfont.font_width(&plot_style_odd.text,
+ entry->text.value.data,
+ entry->text.value.len,
+ &(entry->text.value.width));
+ } else {
+ /* Just invalidate the width, since it's not needed now */
+ entry->text.value.width = 0;
+ }
+
+ for (i = 1; i < tree->n_fields; i++) {
+ assert(fields[i].field != NULL);
+ assert(lwc_string_isequal(tree->fields[i].field,
+ fields[i].field, &match) == lwc_error_ok &&
+ match == true);
+
+ e->fields[i - 1].value.data = fields[i].value;
+ e->fields[i - 1].value.len = fields[i].value_len;
+
+ if (entry->flags & TREE_NODE_EXPANDED) {
+ /* Text will be seen, get its width */
+ nsfont.font_width(&plot_style_odd.text,
+ e->fields[i - 1].value.data,
+ e->fields[i - 1].value.len,
+ &(e->fields[i - 1].value.width));
+ } else {
+ /* Invalidate the width, since it's not needed yet */
+ e->fields[i - 1].value.width = 0;
+ }
+ }
+
+ return NSERROR_OK;
+}
+
+
+nserror treeview_create_node_entry(struct treeview *tree,
+ struct treeview_node **entry,
+ struct treeview_node *relation,
+ enum treeview_relationship rel,
+ const struct treeview_field_data fields[],
+ void *data)
+{
+ bool match;
+ struct treeview_node_entry *e;
+ struct treeview_node *n;
+ int i;
+
+ assert(data != NULL);
+ assert(tree != NULL);
+ assert(tree->root != NULL);
+
+ if (relation == NULL) {
+ relation = tree->root;
+ rel = TREE_REL_FIRST_CHILD;
+ }
+
+ e = malloc(sizeof(struct treeview_node_entry) +
+ (tree->n_fields - 1) * sizeof(struct treeview_field));
+ if (e == NULL) {
+ return NSERROR_NOMEM;
+ }
+
+
+ n = (struct treeview_node *) e;
+
+ n->flags = TREE_NODE_NONE;
+ n->type = TREE_NODE_ENTRY;
+
+ n->height = tree_g.line_height;
+
+ assert(fields != NULL);
+ assert(fields[0].field != NULL);
+ assert(lwc_string_isequal(tree->fields[0].field,
+ fields[0].field, &match) == lwc_error_ok &&
+ match == true);
+ n->text.value.data = fields[0].value;
+ n->text.value.len = fields[0].value_len;
+ n->text.value.width = 0;
+
+ n->parent = NULL;
+ n->sibling_next = NULL;
+ n->sibling_prev = NULL;
+ n->children = NULL;
+
+ n->client_data = data;
+
+ for (i = 1; i < tree->n_fields; i++) {
+ assert(fields[i].field != NULL);
+ assert(lwc_string_isequal(tree->fields[i].field,
+ fields[i].field, &match) == lwc_error_ok &&
+ match == true);
+
+ e->fields[i - 1].value.data = fields[i].value;
+ e->fields[i - 1].value.len = fields[i].value_len;
+ e->fields[i - 1].value.width = 0;
+ }
+
+ treeview_insert_node(n, relation, rel);
+
+ *entry = n;
+
+ return NSERROR_OK;
+}
+
+
+nserror treeview_delete_node(struct treeview *tree, struct treeview_node *n)
+{
+ struct treeview_node_msg msg;
+ msg.msg = TREE_MSG_NODE_DELETE;
+
+ /* Destroy children first */
+ while (n->children != NULL) {
+ treeview_delete_node(tree, n->children);
+ }
+
+ /* Unlink node from tree */
+ if (n->parent != NULL && n->parent->children == n) {
+ /* Node is a first child */
+ n->parent->children = n->sibling_next;
+
+ } else if (n->sibling_prev != NULL) {
+ /* Node is not first child */
+ n->sibling_prev->sibling_next = n->sibling_next;
+ }
+
+ if (n->sibling_next != NULL) {
+ /* Always need to do this */
+ n->sibling_next->sibling_prev = n->sibling_prev;
+ }
+
+ /* Handle any special treatment */
+ switch (n->type) {
+ case TREE_NODE_ENTRY:
+ tree->callbacks->entry(msg, n->client_data);
+ break;
+ case TREE_NODE_FOLDER:
+ tree->callbacks->folder(msg, n->client_data);
+ break;
+ case TREE_NODE_ROOT:
+ break;
+ default:
+ return NSERROR_BAD_PARAMETER;
+ }
+
+ /* Free the node */
+ free(n);
+
+ return NSERROR_OK;
+}
+
+
+nserror treeview_create(struct treeview **tree,
+ const struct treeview_callback_table *callbacks,
+ int n_fields, struct treeview_field_desc fields[],
+ const struct core_window_callback_table *cw_t,
+ struct core_window *cw)
+{
+ nserror error;
+ int i;
+
+ assert(cw_t != NULL);
+ assert(cw != NULL);
+ assert(callbacks != NULL);
+
+ assert(fields != NULL);
+ assert(fields[0].flags & TREE_FLAG_DEFAULT);
+ assert(fields[n_fields - 1].flags & TREE_FLAG_DEFAULT);
+ assert(n_fields >= 2);
+
+ *tree = malloc(sizeof(struct treeview));
+ if (*tree == NULL) {
+ return NSERROR_NOMEM;
+ }
+
+ (*tree)->fields = malloc(sizeof(struct treeview_field) * n_fields);
+ if ((*tree)->fields == NULL) {
+ free(tree);
+ return NSERROR_NOMEM;
+ }
+
+ error = treeview_create_node_root(&((*tree)->root));
+ if (error != NSERROR_OK) {
+ free((*tree)->fields);
+ free(*tree);
+ return error;
+ }
+
+ (*tree)->field_width = 0;
+ for (i = 0; i < n_fields; i++) {
+ struct treeview_field *f = &((*tree)->fields[i]);
+
+ f->flags = fields[i].flags;
+ f->field = lwc_string_ref(fields[i].field);
+ f->value.data = lwc_string_data(fields[i].field);
+ f->value.len = lwc_string_length(fields[i].field);
+
+ nsfont.font_width(&plot_style_odd.text, f->value.data,
+ f->value.len, &(f->value.width));
+
+ if (f->flags & TREE_FLAG_SHOW_NAME)
+ if ((*tree)->field_width < f->value.width)
+ (*tree)->field_width = f->value.width;
+ }
+
+ (*tree)->field_width += tree_g.step_width;
+
+ (*tree)->callbacks = callbacks;
+ (*tree)->n_fields = n_fields - 1;
+
+ (*tree)->cw_t = cw_t;
+ (*tree)->cw_h = cw;
+
+ return NSERROR_OK;
+}
+
+nserror treeview_destroy(struct treeview *tree)
+{
+ int f;
+
+ assert(tree != NULL);
+
+ /* Destroy nodes */
+ treeview_delete_node(tree, tree->root);
+
+ /* Destroy feilds */
+ for (f = 0; f <= tree->n_fields; f++) {
+ lwc_string_unref(tree->fields[f].field);
+ }
+ free(tree->fields);
+
+ /* Free treeview */
+ free(tree);
+
+ return NSERROR_OK;
+}
+
+
+/* Walk a treeview subtree, calling a callback at each node (depth first)
+ *
+ * \param root Root to walk tree from (doesn't get a callback call)
+ * \param full Iff true, visit children of collapsed nodes
+ * \param callback Function to call on each node
+ * \param ctx Context to pass to callback
+ * \return true iff callback caused premature abort
+ */
+static bool treeview_walk_internal(struct treeview_node *root, bool full,
+ bool (*callback)(struct treeview_node *node, void *ctx),
+ void *ctx)
+{
+ struct treeview_node *node, *next;
+
+ node = root;
+
+ while (node != NULL) {
+ next = (full || (node->flags & TREE_NODE_EXPANDED)) ?
+ node->children : NULL;
+
+ if (next != NULL) {
+ /* Down to children */
+ node = next;
+ } else {
+ /* No children. As long as we're not at the root,
+ * go to next sibling if present, or nearest ancestor
+ * with a next sibling. */
+
+ while (node != root &&
+ node->sibling_next == NULL) {
+ node = node->parent;
+ }
+
+ if (node == root)
+ break;
+
+ node = node->sibling_next;
+ }
+
+ assert(node != NULL);
+ assert(node != root);
+
+ if (callback(node, ctx)) {
+ /* callback caused early termination */
+ return true;
+ }
+
+ }
+ return false;
+}
+
+
+nserror treeview_node_expand(struct treeview *tree,
+ struct treeview_node *node)
+{
+ struct treeview_node *child;
+ struct treeview_node_entry *e;
+ int additional_height = 0;
+ int i;
+
+ assert(tree != NULL);
+ assert(node != NULL);
+
+ if (node->flags & TREE_NODE_EXPANDED) {
+ /* What madness is this? */
+ LOG(("Tried to expand an expanded node."));
+ return NSERROR_OK;
+ }
+
+ switch (node->type) {
+ case TREE_NODE_FOLDER:
+ child = node->children;
+ if (child == NULL) {
+ /* Can't expand an empty node */
+ return NSERROR_OK;
+ }
+
+ do {
+ assert((child->flags & TREE_NODE_EXPANDED) == false);
+ if (child->text.value.width == 0) {
+ nsfont.font_width(&plot_style_odd.text,
+ child->text.value.data,
+ child->text.value.len,
+ &(child->text.value.width));
+ }
+
+ additional_height += child->height;
+
+ child = child->sibling_next;
+ } while (child != NULL);
+
+ break;
+
+ case TREE_NODE_ENTRY:
+ assert(node->children == NULL);
+
+ e = (struct treeview_node_entry *)node;
+
+ for (i = 0; i < tree->n_fields - 1; i++) {
+
+ if (e->fields[i].value.width == 0) {
+ nsfont.font_width(&plot_style_odd.text,
+ e->fields[i].value.data,
+ e->fields[i].value.len,
+ &(e->fields[i].value.width));
+ }
+
+ /* Add height for field */
+ additional_height += tree_g.line_height;
+ }
+
+ break;
+
+ case TREE_NODE_ROOT:
+ assert(node->type != TREE_NODE_ROOT);
+ break;
+ }
+
+ /* Update the node */
+ node->flags |= TREE_NODE_EXPANDED;
+
+ /* And parent's heights */
+ do {
+ node->height += additional_height;
+ node = node->parent;
+ } while (node->parent != NULL);
+
+ node->height += additional_height;
+
+ return NSERROR_OK;
+}
+
+
+static bool treeview_node_contract_cb(struct treeview_node *node, void *ctx)
+{
+ int height_reduction;
+
+ assert(node != NULL);
+ assert(node->type != TREE_NODE_ROOT);
+
+ if ((node->flags & TREE_NODE_EXPANDED) == false) {
+ /* Nothing to do. */
+ return false;
+ }
+
+ node->flags ^= TREE_NODE_EXPANDED;
+ height_reduction = node->height - tree_g.line_height;
+
+ assert(height_reduction >= 0);
+
+ do {
+ node->height -= height_reduction;
+ node = node->parent;
+ } while (node->parent != NULL &&
+ node->parent->flags & TREE_NODE_EXPANDED);
+
+ return false; /* Don't want to abort tree walk */
+}
+nserror treeview_node_contract(struct treeview *tree,
+ struct treeview_node *node)
+{
+ assert(node != NULL);
+
+ if ((node->flags & TREE_NODE_EXPANDED) == false) {
+ /* What madness is this? */
+ LOG(("Tried to contract a contracted node."));
+ return NSERROR_OK;
+ }
+
+ /* Contract children. */
+ treeview_walk_internal(node, false, treeview_node_contract_cb, NULL);
+
+ /* Contract node */
+ treeview_node_contract_cb(node, NULL);
+
+ return NSERROR_OK;
+}
+
+/**
+ * Redraws a treeview.
+ *
+ * \param tree the tree to draw
+ * \param x X coordinate to draw the tree at (wrt plot origin)
+ * \param y Y coordinate to draw the tree at (wrt plot origin)
+ * \param clip_x clipping rectangle (wrt tree origin)
+ * \param ctx current redraw context
+ */
+void treeview_redraw(struct treeview *tree, int x, int y, struct rect *clip,
+ const struct redraw_context *ctx)
+{
+ struct redraw_context new_ctx = *ctx;
+ struct treeview_node *node, *root, *next;
+ struct treeview_node_entry *entry;
+ struct treeview_node_style *style = &plot_style_odd;
+ struct content_redraw_data data;
+ struct rect r;
+ uint32_t count = 0;
+ int render_y = y;
+ int inset;
+ int x0, y0, y1;
+ int baseline = (tree_g.line_height * 3 + 2) / 4;
+ enum treeview_resource_id res = TREE_RES_CONTENT;
+ plot_style_t *bg_style;
+ plot_font_style_t *text_style;
+ plot_font_style_t *infotext_style;
+ int height;
+
+ assert(tree != NULL);
+ assert(tree->root != NULL);
+ assert(tree->root->flags & TREE_NODE_EXPANDED);
+
+ /* Start knockout rendering if it's available for this plotter */
+ if (ctx->plot->option_knockout)
+ knockout_plot_start(ctx, &new_ctx);
+
+ /* Set up clip rectangle */
+ r.x0 = clip->x0 + x;
+ r.y0 = clip->y0 + y;
+ r.x1 = clip->x1 + x;
+ r.y1 = clip->y1 + y;
+ new_ctx.plot->clip(&r);
+
+ /* Draw the tree */
+ node = root = tree->root;
+
+ /* Setup common content redraw data */
+ data.width = 17;
+ data.height = 17;
+ data.scale = 1;
+ data.repeat_x = false;
+ data.repeat_y = false;
+
+ while (node != NULL) {
+ int i;
+ next = (node->flags & TREE_NODE_EXPANDED) ?
+ node->children : NULL;
+
+ if (next != NULL) {
+ /* down to children */
+ node = next;
+ } else {
+ /* No children. As long as we're not at the root,
+ * go to next sibling if present, or nearest ancestor
+ * with a next sibling. */
+
+ while (node != root &&
+ node->sibling_next == NULL) {
+ node = node->parent;
+ }
+
+ if (node == root)
+ break;
+
+ node = node->sibling_next;
+ }
+
+ assert(node != NULL);
+ assert(node != root);
+ assert(node->type == TREE_NODE_FOLDER ||
+ node->type == TREE_NODE_ENTRY);
+
+ count++;
+ inset = node->inset;
+ height = (node->type == TREE_NODE_ENTRY) ? node->height :
+ tree_g.line_height;
+
+ if ((render_y + height) < r.y0) {
+ /* This node's line is above clip region */
+ render_y += height;
+ continue;
+ }
+
+ style = (count & 0x1) ? &plot_style_odd : &plot_style_even;
+ if (node->flags & TREE_NODE_SELECTED) {
+ bg_style = &style->sbg;
+ text_style = &style->stext;
+ infotext_style = &style->sitext;
+ } else {
+ bg_style = &style->bg;
+ text_style = &style->text;
+ infotext_style = &style->itext;
+ }
+
+ /* Render background */
+ y0 = render_y;
+ y1 = render_y + height;
+ new_ctx.plot->rectangle(r.x0, y0, r.x1, y1, bg_style);
+
+ /* Render toggle */
+ if (node->flags & TREE_NODE_EXPANDED) {
+ new_ctx.plot->text(inset, render_y + baseline,
+ treeview_furn[TREE_FURN_CONTRACT].data,
+ treeview_furn[TREE_FURN_CONTRACT].len,
+ text_style);
+ } else {
+ new_ctx.plot->text(inset, render_y + baseline,
+ treeview_furn[TREE_FURN_EXPAND].data,
+ treeview_furn[TREE_FURN_EXPAND].len,
+ text_style);
+ }
+
+ /* Render icon */
+ if (node->type == TREE_NODE_ENTRY)
+ res = TREE_RES_CONTENT;
+ else if (node->type == TREE_NODE_FOLDER)
+ res = TREE_RES_FOLDER;
+
+ if (treeview_res[res].ready) {
+ /* Icon resource is available */
+ data.x = inset + tree_g.step_width;
+ data.y = render_y + ((tree_g.line_height -
+ treeview_res[res].height + 1) / 2);
+ data.background_colour = bg_style->fill_colour;
+
+ content_redraw(treeview_res[res].c,
+ &data, &r, &new_ctx);
+ }
+
+ /* Render text */
+ x0 = inset + tree_g.step_width + tree_g.icon_step;
+ new_ctx.plot->text(x0, render_y + baseline,
+ node->text.value.data, node->text.value.len,
+ text_style);
+
+ /* Rendered the node */
+ render_y += tree_g.line_height;
+ if (render_y > r.y1) {
+ /* Passed the bottom of what's in the clip region.
+ * Done. */
+ break;
+ }
+
+
+ if (node->type != TREE_NODE_ENTRY ||
+ !(node->flags & TREE_NODE_EXPANDED))
+ /* Done everything for this node */
+ continue;
+
+ /* Render expanded entry fields */
+ entry = (struct treeview_node_entry *)node;
+ for (i = 0; i < tree->n_fields - 1; i++) {
+ struct treeview_field *ef = &(tree->fields[i + 1]);
+
+ if (ef->flags & TREE_FLAG_SHOW_NAME) {
+ int max_width = tree->field_width;
+
+ new_ctx.plot->text(x0 + max_width -
+ ef->value.width -
+ tree_g.step_width,
+ render_y + baseline,
+ ef->value.data,
+ ef->value.len,
+ infotext_style);
+
+ new_ctx.plot->text(x0 + max_width,
+ render_y + baseline,
+ entry->fields[i].value.data,
+ entry->fields[i].value.len,
+ infotext_style);
+ } else {
+ new_ctx.plot->text(x0, render_y + baseline,
+ entry->fields[i].value.data,
+ entry->fields[i].value.len,
+ infotext_style);
+
+ }
+
+ /* Rendered the expanded entry field */
+ render_y += tree_g.line_height;
+ }
+
+ /* Finshed rendering expanded entry */
+
+ if (render_y > r.y1) {
+ /* Passed the bottom of what's in the clip region.
+ * Done. */
+ break;
+ }
+ }
+
+ if (render_y < r.y1) {
+ /* Fill the blank area at the bottom */
+ y0 = render_y;
+ new_ctx.plot->rectangle(r.x0, y0, r.x1, r.y1,
+ &plot_style_even.bg);
+
+ }
+
+ /* Rendering complete */
+ if (ctx->plot->option_knockout)
+ knockout_plot_end();
+}
+
+struct treeview_selection_walk_data {
+ enum {
+ TREEVIEW_WALK_HAS_SELECTION,
+ TREEVIEW_WALK_CLEAR_SELECTION,
+ TREEVIEW_WALK_SELECT_ALL
+ } purpose;
+ union {
+ bool has_selection;
+ struct {
+ bool required;
+ struct rect *rect;
+ } redraw;
+ } data;
+ int current_y;
+};
+static bool treeview_node_selection_walk_cb(struct treeview_node *node,
+ void *ctx)
+{
+ struct treeview_selection_walk_data *sw = ctx;
+ int height;
+ bool changed = false;
+
+ height = (node->type == TREE_NODE_ENTRY) ? node->height :
+ tree_g.line_height;
+ sw->current_y += height;
+
+ switch (sw->purpose) {
+ case TREEVIEW_WALK_HAS_SELECTION:
+ if (node->flags & TREE_NODE_SELECTED) {
+ sw->data.has_selection = true;
+ return true; /* Can abort tree walk */
+ }
+ break;
+
+ case TREEVIEW_WALK_CLEAR_SELECTION:
+ if (node->flags & TREE_NODE_SELECTED) {
+ node->flags ^= TREE_NODE_SELECTED;
+ changed = true;
+ }
+ break;
+
+ case TREEVIEW_WALK_SELECT_ALL:
+ if (!(node->flags & TREE_NODE_SELECTED)) {
+ node->flags ^= TREE_NODE_SELECTED;
+ changed = true;
+ }
+ break;
+ }
+
+ if (changed) {
+ if (sw->data.redraw.required == false) {
+ sw->data.redraw.required = true;
+ sw->data.redraw.rect->y0 = sw->current_y - height;
+ }
+
+ if (sw->current_y > sw->data.redraw.rect->y1) {
+ sw->data.redraw.rect->y1 = sw->current_y;
+ }
+ }
+
+ return false; /* Don't stop walk */
+}
+
+bool treeview_has_selection(struct treeview *tree)
+{
+ struct treeview_selection_walk_data sw;
+
+ sw.purpose = TREEVIEW_WALK_HAS_SELECTION;
+ sw.data.has_selection = false;
+
+ treeview_walk_internal(tree->root, false,
+ treeview_node_selection_walk_cb, &sw);
+
+ return sw.data.has_selection;
+}
+
+bool treeview_clear_selection(struct treeview *tree, struct rect *rect)
+{
+ struct treeview_selection_walk_data sw;
+
+ rect->x0 = 0;
+ rect->y0 = 0;
+ rect->x1 = REDRAW_MAX;
+ rect->y1 = 0;
+
+ sw.purpose = TREEVIEW_WALK_CLEAR_SELECTION;
+ sw.data.redraw.required = false;
+ sw.data.redraw.rect = rect;
+ sw.current_y = 0;
+
+ treeview_walk_internal(tree->root, false,
+ treeview_node_selection_walk_cb, &sw);
+
+ return sw.data.redraw.required;
+}
+
+bool treeview_select_all(struct treeview *tree, struct rect *rect)
+{
+ struct treeview_selection_walk_data sw;
+
+ rect->x0 = 0;
+ rect->y0 = 0;
+ rect->x1 = REDRAW_MAX;
+ rect->y1 = 0;
+
+ sw.purpose = TREEVIEW_WALK_SELECT_ALL;
+ sw.data.redraw.required = false;
+ sw.data.redraw.rect = rect;
+ sw.current_y = 0;
+
+ treeview_walk_internal(tree->root, false,
+ treeview_node_selection_walk_cb, &sw);
+
+ return sw.data.redraw.required;
+}
+
+struct treeview_mouse_action {
+ struct treeview *tree;
+ browser_mouse_state mouse;
+ int x;
+ int y;
+ int current_y;
+};
+static bool treeview_node_mouse_action_cb(struct treeview_node *node, void *ctx)
+{
+ struct treeview_mouse_action *ma = ctx;
+ struct rect r;
+ bool redraw = false;
+ bool click;
+ int height;
+ enum {
+ TV_NODE_ACTION_NONE = 0,
+ TV_NODE_ACTION_SELECTION = (1 << 0)
+ } action = TV_NODE_ACTION_NONE;
+ enum {
+ TV_NODE_SECTION_TOGGLE,
+ TV_NODE_SECTION_NODE
+ } section = TV_NODE_SECTION_NODE;
+ nserror err;
+
+ r.x0 = 0;
+ r.x1 = REDRAW_MAX;
+
+ height = (node->type == TREE_NODE_ENTRY) ? node->height :
+ tree_g.line_height;
+
+ /* Skip line if we've not reached mouse y */
+ if (ma->y > ma->current_y + height) {
+ ma->current_y += height;
+ return false; /* Don't want to abort tree walk */
+ }
+
+ /* Find where the mouse is */
+ if (ma->x >= node->inset - 1 &&
+ ma->x < node->inset + tree_g.step_width) {
+ section = TV_NODE_SECTION_TOGGLE;
+ }
+
+ click = ma->mouse & (BROWSER_MOUSE_CLICK_1 | BROWSER_MOUSE_CLICK_2);
+
+ if (((node->type == TREE_NODE_FOLDER) &&
+ (ma->mouse & BROWSER_MOUSE_DOUBLE_CLICK) && click) ||
+ (section == TV_NODE_SECTION_TOGGLE && click)) {
+ /* Clear any existing selection */
+ redraw |= treeview_clear_selection(ma->tree, &r);
+
+ /* Toggle node expansion */
+ if (node->flags & TREE_NODE_EXPANDED) {
+ err = treeview_node_contract(ma->tree, node);
+ } else {
+ err = treeview_node_expand(ma->tree, node);
+ }
+
+ /* Set up redraw */
+ if (!redraw || r.y0 > ma->current_y)
+ r.y0 = ma->current_y;
+ r.y1 = REDRAW_MAX;
+ redraw = true;
+
+ } else if ((node->type == TREE_NODE_ENTRY) &&
+ (ma->mouse & BROWSER_MOUSE_DOUBLE_CLICK) && click) {
+ struct treeview_node_msg msg;
+ msg.msg = TREE_MSG_NODE_LAUNCH;
+ msg.data.node_launch.mouse = ma->mouse;
+
+ /* Clear any existing selection */
+ redraw |= treeview_clear_selection(ma->tree, &r);
+
+ /* Tell client an entry was launched */
+ ma->tree->callbacks->entry(msg, node->client_data);
+
+ } else if (ma->mouse & BROWSER_MOUSE_PRESS_1 &&
+ !(node->flags & TREE_NODE_SELECTED) &&
+ section != TV_NODE_SECTION_TOGGLE) {
+ /* Clear any existing selection */
+ redraw |= treeview_clear_selection(ma->tree, &r);
+
+ /* Select node */
+ action |= TV_NODE_ACTION_SELECTION;
+
+ } else if (ma->mouse & BROWSER_MOUSE_PRESS_2 ||
+ (ma->mouse & BROWSER_MOUSE_PRESS_1 &&
+ ma->mouse & BROWSER_MOUSE_MOD_2)) {
+ /* Toggle selection of node */
+ action |= TV_NODE_ACTION_SELECTION;
+ }
+
+ if (action & TV_NODE_ACTION_SELECTION) {
+ /* Handle change in selection */
+ node->flags ^= TREE_NODE_SELECTED;
+
+ /* Redraw */
+ if (!redraw) {
+ r.y0 = ma->current_y;
+ r.y1 = ma->current_y + height;
+ redraw = true;
+ } else {
+ if (r.y0 > ma->current_y)
+ r.y0 = ma->current_y;
+ if (r.y1 < ma->current_y + height)
+ r.y1 = ma->current_y + height;
+ }
+ }
+
+ if (redraw) {
+ ma->tree->cw_t->redraw_request(ma->tree->cw_h, r);
+ }
+
+ return true; /* Reached line with click; stop walking tree */
+}
+void treeview_mouse_action(struct treeview *tree,
+ browser_mouse_state mouse, int x, int y)
+{
+ struct treeview_mouse_action ma;
+
+ ma.tree = tree;
+ ma.mouse = mouse;
+ ma.x = x;
+ ma.y = y;
+ ma.current_y = 0;
+
+ treeview_walk_internal(tree->root, false,
+ treeview_node_mouse_action_cb, &ma);
+}
+
+
+
+/* Mix two colours according to the proportion given by p.
+ * Where 0 <= p <= 255
+ * p=0 gives result=c0
+ * p=255 gives result=c1
+ */
+#define mix_colour(c0, c1, p) \
+ ((((((c1 & 0xff00ff) * (255 - p)) + \
+ ((c0 & 0xff00ff) * ( p)) ) >> 8) & 0xff00ff) | \
+ (((((c1 & 0x00ff00) * (255 - p)) + \
+ ((c0 & 0x00ff00) * ( p)) ) >> 8) & 0x00ff00))
+
+
+static void treeview_init_plot_styles(int font_pt_size)
+{
+ /* Background colour */
+ plot_style_even.bg.stroke_type = PLOT_OP_TYPE_NONE;
+ plot_style_even.bg.stroke_width = 0;
+ plot_style_even.bg.stroke_colour = 0;
+ plot_style_even.bg.fill_type = PLOT_OP_TYPE_SOLID;
+ plot_style_even.bg.fill_colour = gui_system_colour_char("Window");
+
+ /* Text colour */
+ plot_style_even.text.family = PLOT_FONT_FAMILY_SANS_SERIF;
+ plot_style_even.text.size = font_pt_size * FONT_SIZE_SCALE;
+ plot_style_even.text.weight = 400;
+ plot_style_even.text.flags = FONTF_NONE;
+ plot_style_even.text.foreground = gui_system_colour_char("WindowText");
+ plot_style_even.text.background = gui_system_colour_char("Window");
+
+ /* Entry field text colour */
+ plot_style_even.itext = plot_style_even.text;
+ plot_style_even.itext.foreground = mix_colour(
+ plot_style_even.text.foreground,
+ plot_style_even.text.background, 255 * 10 / 16);
+
+ /* Selected background colour */
+ plot_style_even.sbg = plot_style_even.bg;
+ plot_style_even.sbg.fill_colour = gui_system_colour_char("Highlight");
+
+ /* Selected text colour */
+ plot_style_even.stext = plot_style_even.text;
+ plot_style_even.stext.foreground =
+ gui_system_colour_char("HighlightText");
+ plot_style_even.stext.background = gui_system_colour_char("Highlight");
+
+ /* Selected entry field text colour */
+ plot_style_even.sitext = plot_style_even.stext;
+ plot_style_even.sitext.foreground = mix_colour(
+ plot_style_even.stext.foreground,
+ plot_style_even.stext.background, 255 * 25 / 32);
+
+
+ /* Odd numbered node styles */
+ plot_style_odd.bg = plot_style_even.bg;
+ plot_style_odd.bg.fill_colour = mix_colour(
+ plot_style_even.bg.fill_colour,
+ plot_style_even.text.foreground, 255 * 15 / 16);
+ plot_style_odd.text = plot_style_even.text;
+ plot_style_odd.text.background = plot_style_odd.bg.fill_colour;
+ plot_style_odd.itext = plot_style_odd.text;
+ plot_style_odd.itext.foreground = mix_colour(
+ plot_style_odd.text.foreground,
+ plot_style_odd.text.background, 255 * 10 / 16);
+
+ plot_style_odd.sbg = plot_style_even.sbg;
+ plot_style_odd.stext = plot_style_even.stext;
+ plot_style_odd.sitext = plot_style_even.sitext;
+}
+
+
+/**
+ * Callback for hlcache.
+ */
+static nserror treeview_res_cb(hlcache_handle *handle,
+ const hlcache_event *event, void *pw)
+{
+ struct treeview_resource *r = pw;
+
+ switch (event->type) {
+ case CONTENT_MSG_READY:
+ case CONTENT_MSG_DONE:
+ r->ready = true;
+ r->height = content_get_height(handle);
+ break;
+
+ default:
+ break;
+ }
+
+ return NSERROR_OK;
+}
+
+
+static void treeview_init_resources(void)
+{
+ int i;
+
+ for (i = 0; i < TREE_RES_LAST; i++) {
+ nsurl *url;
+ if (nsurl_create(treeview_res[i].url, &url) == NSERROR_OK) {
+ hlcache_handle_retrieve(url, 0, NULL, NULL,
+ treeview_res_cb,
+ &(treeview_res[i]), NULL,
+ CONTENT_IMAGE, &(treeview_res[i].c));
+ nsurl_unref(url);
+ }
+ }
+}
+
+
+static void treeview_init_furniture(void)
+{
+ int i;
+ tree_g.furniture_width = 0;
+
+ for (i = 0; i < TREE_FURN_LAST; i++) {
+ nsfont.font_width(&plot_style_odd.text,
+ treeview_furn[i].data,
+ treeview_furn[i].len,
+ &(treeview_furn[i].width));
+
+ if (treeview_furn[i].width > tree_g.furniture_width)
+ tree_g.furniture_width = treeview_furn[i].width;
+ }
+
+ tree_g.furniture_width += 5;
+}
+
+
+nserror treeview_init(void)
+{
+ int font_px_size;
+ int font_pt_size = 11;
+
+ treeview_init_plot_styles(font_pt_size);
+ treeview_init_resources();
+ treeview_init_furniture();
+
+ font_px_size = (font_pt_size * FIXTOINT(nscss_screen_dpi) + 36) / 72;
+
+ tree_g.line_height = (font_px_size * 8 + 3) / 6;
+ tree_g.step_width = tree_g.furniture_width;
+ tree_g.window_padding = 6;
+ tree_g.icon_step = 23;
+
+ return NSERROR_OK;
+}
+
+
+nserror treeview_fini(void)
+{
+ int i;
+
+ for (i = 0; i < TREE_RES_LAST; i++) {
+ hlcache_handle_release(treeview_res[i].c);
+ }
+
+ return NSERROR_OK;
+}
+
+
+struct treeview_node * treeview_get_root(struct treeview *tree)
+{
+ assert(tree != NULL);
+ assert(tree->root != NULL);
+
+ return tree->root;
+}
diff --git a/desktop/treeview.h b/desktop/treeview.h
new file mode 100644
index 000000000..d21a8a43b
--- /dev/null
+++ b/desktop/treeview.h
@@ -0,0 +1,154 @@
+/*
+ * Copyright 2012 - 2013 Michael Drake <tlsa@netsurf-browser.org>
+ *
+ * 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
+ * Treeview handling (interface).
+ */
+
+#ifndef _NETSURF_DESKTOP_TREEVIEW_H_
+#define _NETSURF_DESKTOP_TREEVIEW_H_
+
+#include <stdbool.h>
+#include <stdint.h>
+
+#include "desktop/core_window.h"
+#include "utils/types.h"
+
+struct treeview;
+struct treeview_node;
+
+enum treeview_relationship {
+ TREE_REL_FIRST_CHILD,
+ TREE_REL_NEXT_SIBLING
+};
+
+enum treeview_msg {
+ TREE_MSG_NODE_DELETE,
+ TREE_MSG_NODE_EDIT,
+ TREE_MSG_NODE_LAUNCH
+};
+struct treeview_node_msg {
+ enum treeview_msg msg; /**< The message type */
+ union {
+ struct {
+ lwc_string *feild; /* The field being edited */
+ const char *text; /* The proposed new value */
+ } node_edit; /* Client may call treeview_update_node_* */
+ struct {
+ browser_mouse_state mouse; /* Button / modifier used */
+ } node_launch;
+ } data; /**< The message data. */
+};
+
+enum treeview_field_flags {
+ TREE_FLAG_NONE = 0, /**< No flags set */
+ TREE_FLAG_ALLOW_EDIT = (1 << 0), /**< Whether allow edit field */
+ TREE_FLAG_DEFAULT = (1 << 1), /**< Whether field is default */
+ TREE_FLAG_SHOW_NAME = (1 << 2) /**< Whether field name shown */
+
+};
+struct treeview_field_desc {
+ lwc_string *field;
+ enum treeview_field_flags flags;
+};
+
+struct treeview_field_data {
+ lwc_string *field;
+ const char *value;
+ size_t value_len;
+};
+
+
+struct treeview_callback_table {
+ nserror (*folder)(struct treeview_node_msg msg, void *data);
+ nserror (*entry)(struct treeview_node_msg msg, void *data);
+};
+
+nserror treeview_init(void);
+nserror treeview_fini(void);
+
+nserror treeview_create(struct treeview **tree,
+ const struct treeview_callback_table *callbacks,
+ int n_fields, struct treeview_field_desc fields[],
+ const struct core_window_callback_table *cw_t,
+ struct core_window *cw);
+
+nserror treeview_destroy(struct treeview *tree);
+
+nserror treeview_create_node_folder(struct treeview *tree,
+ struct treeview_node **folder,
+ struct treeview_node *relation,
+ enum treeview_relationship rel,
+ const struct treeview_field_data *field,
+ void *data);
+nserror treeview_create_node_entry(struct treeview *tree,
+ struct treeview_node **entry,
+ struct treeview_node *relation,
+ enum treeview_relationship rel,
+ const struct treeview_field_data fields[],
+ void *data);
+
+nserror treeview_update_node_entry(struct treeview *tree,
+ struct treeview_node *entry,
+ const struct treeview_field_data fields[],
+ void *data);
+
+nserror treeview_delete_node(struct treeview *tree, struct treeview_node *n);
+
+nserror treeview_node_expand(struct treeview *tree,
+ struct treeview_node *node);
+nserror treeview_node_contract(struct treeview *tree,
+ struct treeview_node *node);
+
+void treeview_redraw(struct treeview *tree, int x, int y, struct rect *clip,
+ const struct redraw_context *ctx);
+
+/**
+ * Handles all kinds of mouse action
+ *
+ * \param tree Treeview
+ * \param mouse the mouse state at action moment
+ * \param x X coordinate
+ * \param y Y coordinate
+ */
+void treeview_mouse_action(struct treeview *tree,
+ browser_mouse_state mouse, int x, int y);
+
+struct treeview_node * treeview_get_root(struct treeview *tree);
+
+bool treeview_has_selection(struct treeview *tree);
+
+/**
+ * Clear any selection in a treeview
+ *
+ * \param tree treeview to clear selection in
+ * \param rect redraw rectangle (if redraw required)
+ * \return true iff redraw required
+ */
+bool treeview_clear_selection(struct treeview *tree, struct rect *rect);
+
+/**
+ * Select all in a treeview
+ *
+ * \param tree treeview to select all in
+ * \param rect redraw rectangle (if redraw required)
+ * \return true iff redraw required
+ */
+bool treeview_select_all(struct treeview *tree, struct rect *rect);
+
+#endif
diff --git a/utils/types.h b/utils/types.h
index 617b4938c..e3f2e838c 100644
--- a/utils/types.h
+++ b/utils/types.h
@@ -23,6 +23,8 @@
#ifndef _NETSURF_UTILS_TYPES_H_
#define _NETSURF_UTILS_TYPES_H_
+#include <stdbool.h>
+
struct plotter_table;
struct hlcache_handle;