/*
 * 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 <string.h>

#include "utils/utils.h"
#include "utils/log.h"
#include "utils/nsurl.h"
#include "utils/nsoption.h"
#include "utils/config.h"
#include "netsurf/bitmap.h"
#include "netsurf/content.h"
#include "netsurf/plotters.h"
#include "netsurf/clipboard.h"
#include "netsurf/layout.h"
#include "netsurf/keypress.h"
#include "netsurf/core_window.h"
#include "content/hlcache.h"
#include "css/utils.h"

#include "desktop/system_colour.h"
#include "desktop/knockout.h"
#include "desktop/textarea.h"
#include "desktop/treeview.h"
#include "desktop/gui_internal.h"

/**
 * The maximum horizontal size a treeview can possibly be.
 *
 * \todo get rid of REDRAW_MAX -- need to be able to know window size
 */
#define REDRAW_MAX 8000


/**
 * Treeview handling global context
 */
struct treeview_globals {
	unsigned initialised;
	int line_height;
	int furniture_width;
	int step_width;
	int window_padding;
	int icon_size;
	int icon_step;
	int move_offset;
} tree_g;


/**
 * Section type of a treeview at a point
 */
enum treeview_node_part {
	TV_NODE_PART_TOGGLE,	/**< Expansion toggle */
	TV_NODE_PART_ON_NODE,	/**< Node content (text, icon) */
	TV_NODE_PART_NONE	/**< Empty area */
};


/**
 * Text within a treeview field or node
 */
struct treeview_text {
	const char *data;	/**< Text string */
	uint32_t len;		/**< Length of string in bytes */
	int width;		/**< Width of text in px */
};


/**
 * a treeview field
 */
struct treeview_field {
	/** flags controlling how field is interpreted */
	enum treeview_field_flags flags;

	lwc_string *field; /**< field contents */
	struct treeview_text value; /**< field text */
};


/**
 * flags indicating render state of node.
 */
enum treeview_node_flags {
	TV_NFLAGS_NONE     = 0,		/**< No node flags set */
	TV_NFLAGS_EXPANDED = (1 << 0),	/**< Whether node is expanded */
	TV_NFLAGS_SELECTED = (1 << 1),	/**< Whether node is selected */
	TV_NFLAGS_SPECIAL  = (1 << 2),	/**< Render as special node */
	TV_NFLAGS_MATCHED  = (1 << 3),	/**< Whether node matches search */
};


/**
 * Treeview target position
 */
enum treeview_target_pos {
	TV_TARGET_ABOVE,
	TV_TARGET_INSIDE,
	TV_TARGET_BELOW,
	TV_TARGET_NONE
};


/**
 * Treeview node
 */
struct treeview_node {
	enum treeview_node_flags flags;	/**< Node flags */
	enum treeview_node_type type;	/**< Node type */

	int height;	/**< Includes height of any descendants (pixels) */
	int inset;	/**< Node's inset depending on tree depth (pixels) */

	treeview_node *parent; /**< parent node */
	treeview_node *prev_sib; /**< previous sibling node */
	treeview_node *next_sib; /**< next sibling node */
	treeview_node *children; /**< first child node */

	void *client_data;  /**< Passed to client on node event msg callback */

	struct treeview_text text; /** Text to show for node (default field) */
};


/**
 * Node entry
 *
 * node entry contains a base node at the beginning allowing for
 * trivial containerof by cast and some number of fields.
 */
struct treeview_node_entry {
	treeview_node base; /**< Entry class inherits node base class */
	struct treeview_field fields[FLEX_ARRAY_LEN_DECL];
};


/**
 * A mouse position wrt treeview
 */
struct treeview_pos {
	int x;		/**< Mouse X coordinate */
	int y;		/**< Mouse Y coordinate */
	int node_y;	/**< Top of node at y */
	int node_h;	/**< Height of node at y */
};


/**
 * Treeview drag state
 */
struct treeview_drag {
	enum {
		TV_DRAG_NONE,
		TV_DRAG_SELECTION,
		TV_DRAG_MOVE,
		TV_DRAG_TEXTAREA,
		TV_DRAG_SEARCH,
	} type;	/**< Drag type */
	treeview_node *start_node;	/**< Start node */
	bool selected;			/**< Start node is selected */
	enum treeview_node_part part;	/**< Node part at start */
	struct treeview_pos start;	/**< Start pos */
	struct treeview_pos prev;	/**< Previous pos */
};


/**
 * Treeview node move details
 */
struct treeview_move {
	treeview_node *root;		/**< Head of yanked node list */
	treeview_node *target;		/**< Move target */
	struct rect target_area;	/**< Pos/size of target indicator */
	enum treeview_target_pos target_pos;	/**< Pos wrt render node */
};


/**
 * Treeview node edit details
 */
struct treeview_edit {
	treeview_node *node;		/**< Node being edited, or NULL */
	struct textarea *textarea;	/**< Textarea for edit, or NULL */
	lwc_string *field;		/**< The field being edited, or NULL */
	int x;		/**< Textarea x position */
	int y;		/**< Textarea y position */
	int w;		/**< Textarea width */
	int h;		/**< Textarea height */
};


/**
 * Treeview search box details
 */
struct treeview_search {
	struct textarea *textarea;  /**< Search box. */
	bool active;                /**< Whether the search box has focus. */
	bool search;                /**< Whether we have a search term. */
	int height;                 /**< Current search display height. */
};


/**
 * The treeview context
 */
struct treeview {
	uint32_t view_width; /**< Viewport horizontal size */

	treeview_flags flags; /**< Treeview behaviour settings */

	treeview_node *root; /**< Root node */

	struct treeview_field *fields;	/**< Array of fields */
	int n_fields; /**< fields[n_fields] is folder, lower are entry fields */
	int field_width; /**< Max width of shown field names */

	struct treeview_drag drag; /**< Drag state */
	struct treeview_move move; /**< Move drag details */
	struct treeview_edit edit; /**< Edit details */

	struct treeview_search search; /**< Treeview search box */

	const struct treeview_callback_table *callbacks; /**< For node events */

	const struct core_window_callback_table *cw_t; /**< Window cb table */
	struct core_window *cw_h; /**< Core window handle */
};


/**
 * Treeview furniture states.
 */
enum treeview_furniture_id {
	TREE_FURN_EXPAND = 0,
	TREE_FURN_CONTRACT,
	TREE_FURN_LAST
};


/**
 * style for a node
 */
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 {
		int size;
		struct bitmap *bmp;
		struct bitmap *sel;
	} furn[TREE_FURN_LAST];
};


/**
 * Plot style for odd rows
 */
struct treeview_node_style plot_style_odd;


/**
 * Plot style for even rows
 */
struct treeview_node_style plot_style_even;


/**
 * Treeview content resource data
 */
struct treeview_resource {
	const char *url;
	struct hlcache_handle *c;
	int height;
	bool ready;
};


/**
 * treeview resource indexes
 */
enum treeview_resource_id {
	TREE_RES_ARROW = 0,
	TREE_RES_CONTENT,
	TREE_RES_FOLDER,
	TREE_RES_FOLDER_SPECIAL,
	TREE_RES_SEARCH,
	TREE_RES_LAST
};


/**
 * Treeview content resources
 */
static struct treeview_resource treeview_res[TREE_RES_LAST] = {
	{ "resource:icons/arrow-l.png", NULL, 0, false },
	{ "resource:icons/content.png", NULL, 0, false },
	{ "resource:icons/directory.png", NULL, 0, false },
	{ "resource:icons/directory2.png", NULL, 0, false },
	{ "resource:icons/search.png", NULL, 0, false }
};


/**
 * Get the display height of the treeview data component of the display.
 *
 * \param[in] tree  Treeview to get the height of.
 * \return the display height in pixels.
 */
static inline int treeview__get_display_height(const treeview *tree)
{
	return (tree->search.search == false) ?
			tree->root->height :
			tree->search.height;
}


/**
 * Corewindow callback wrapper: Request a redraw of the window
 *
 * \param[in] tree The treeview to request redraw on.
 * \param[in] r rectangle to redraw
 */
static inline void treeview__cw_invalidate_area(
		const struct treeview *tree,
		const struct rect *r)
{
	if (tree->cw_t != NULL) {
		tree->cw_t->invalidate(tree->cw_h, r);
	}
}


/**
 * Corewindow callback wrapper: Update the limits of the window
 *
 * \param[in] tree The treeview to update size for.
 * \param[in] width the width in px, or negative if don't care
 * \param[in] height the height in px, or negative if don't care
 */
static inline void treeview__cw_update_size(
	const struct treeview *tree,
	int width, int height)
{
	int search_height = (tree->flags & TREEVIEW_SEARCHABLE) ?
			tree_g.line_height : 0;

	if (tree->cw_t != NULL) {
		tree->cw_t->update_size(tree->cw_h, width,
				height + search_height);
	}
}


/**
 * Corewindow callback_wrapper: Scroll to top of window.
 *
 * \param[in] tree  The treeview to scroll.
 */
static inline void treeview__cw_scroll_top(
		const struct treeview *tree)
{
	struct rect r = {
		.x0 = 0,
		.y0 = 0,
		.x1 = tree_g.window_padding,
		.y1 = tree_g.line_height,
	};

	if (tree->cw_t != NULL) {
		tree->cw_t->scroll_visible(tree->cw_h, &r);
	}
}


/**
 * Corewindow callback wrapper: Get window viewport dimensions
 *
 * \param[in] tree The treeview to get dimensions for.
 * \param[out] width to be set to viewport width in px
 * \param[out] height to be set to viewport height in px
 */
static inline void treeview__cw_get_window_dimensions(
	const struct treeview *tree,
	int *width, int *height)
{
	if (tree->cw_t != NULL) {
		tree->cw_t->get_window_dimensions(tree->cw_h, width, height);
	}
}


/**
 * Corewindow callback wrapper: Inform corewindow owner of drag status
 *
 * \param[in] tree The treeview to report status on.
 * \param[in] ds the current drag status
 */
static inline void treeview__cw_drag_status(
	const struct treeview *tree,
	core_window_drag_status ds)
{
	if (tree->cw_t != NULL) {
		tree->cw_t->drag_status(tree->cw_h, ds);
	}
}


/**
 * Helper function to access the given field of a node
 *
 * \param tree Treeview that node belongs to
 * \param n Node to get field from
 * \param i Index of field of interest
 * \return text entry for field or NULL.
 */
static inline struct treeview_text *
treeview_get_text_for_field(treeview *tree, treeview_node *n, int i)
{
	if (i == 0) {
		return &n->text;

	} else if (i < tree->n_fields && n->type == TREE_NODE_ENTRY) {
		struct treeview_node_entry *e = (struct treeview_node_entry *)n;
		return &e->fields[i - 1].value;
	}

	assert(0 && "Bad field index for node");
	return NULL;
}


/**
 * Find the next node in depth first tree order
 *
 * \param node Start node
 * \param full Iff true, visit children of collapsed nodes
 * \return next node, or NULL if \a node is last node
 */
static inline treeview_node * treeview_node_next(treeview_node *node, bool full)
{
	assert(node != NULL);

	if ((full || (node->flags & TV_NFLAGS_EXPANDED)) &&
	    node->children != NULL) {
		/* Next node is child */
		node = node->children;
	} 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->parent != NULL && node->next_sib == NULL) {
			node = node->parent;
		}

		if (node->type == TREE_NODE_ROOT) {
			node = NULL;

		} else {
			node = node->next_sib;
		}
	}

	return node;
}


/**
 * Find node at given y-position
 *
 * \param tree Treeview object to delete node from
 * \param target_y Target y-position
 * \return node at y_target
 */
static treeview_node * treeview_y_node(treeview *tree, int target_y)
{
	int y = (tree->flags & TREEVIEW_SEARCHABLE) ? tree_g.line_height : 0;
	treeview_node *n;

	assert(tree != NULL);
	assert(tree->root != NULL);

	n = treeview_node_next(tree->root, false);

	while (n != NULL) {
		int h = (n->type == TREE_NODE_ENTRY) ?
			n->height : tree_g.line_height;
		if (target_y >= y && target_y < y + h)
			return n;
		y += h;

		n = treeview_node_next(n, false);
	}

	return NULL;
}


/**
 * Find y position of the top of a node
 *
 * \param tree Treeview object to delete node from
 * \param node Node to get position of
 * \return node's y position
 */
static int treeview_node_y(
		const treeview *tree,
		const treeview_node *node)
{
	treeview_node *n;
	int y = (tree->flags & TREEVIEW_SEARCHABLE) ? tree_g.line_height : 0;

	assert(tree != NULL);
	assert(tree->root != NULL);

	n = treeview_node_next(tree->root, false);

	while (n != NULL && n != node) {
		y += (n->type == TREE_NODE_ENTRY) ?
			n->height : tree_g.line_height;

		n = treeview_node_next(n, false);
	}

	return y;
}


/**
 * Redraw tree from given node to the bottom.
 *
 * \param[in] tree  Tree to redraw from node in.
 * \param[in] node  Node to redraw from.
 */
static void treeview__redraw_from_node(
		const treeview *tree,
		const treeview_node *node)
{
	int search_height = (tree->flags & TREEVIEW_SEARCHABLE) ?
			tree_g.line_height : 0;
	struct rect r = {
		.x0 = 0,
		.y0 = treeview_node_y(tree, node),
		.x1 = REDRAW_MAX,
		.y1 = treeview__get_display_height(tree) + search_height,
	};

	assert(tree != NULL);

	treeview__cw_invalidate_area(tree, &r);
}


/**
 * The treeview walk mode.  Controls which nodes are visited in a walk.
 */
enum treeview_walk_mode {
	/**
	 * Walk to all nodes in the (sub)tree.
	 */
	TREEVIEW_WALK_MODE_LOGICAL_COMPLETE,

	/**
	 * Walk to expanded nodes in the (sub)tree only.  Children of
	 * collapsed nodes are not visited.
	 */
	TREEVIEW_WALK_MODE_LOGICAL_EXPANDED,

	/**
	 * Walk displayed nodes.  This differs from the
	 * `TREEVIEW_WALK_MODE_LOGICAL_EXPANDED` mode when there is
	 * an active search filter display.
	 */
	TREEVIEW_WALK_MODE_DISPLAY,
};


/**
 * Walk a treeview subtree, calling a callback at each node (depth first)
 *
 * \param tree		Treeview being walked.
 * \param root		Root to walk tree from (doesn't get a callback call)
 * \param mode		The treeview walk mode to use.
 * \param callback_bwd	Function to call on each node in backwards order
 * \param callback_fwd	Function to call on each node in forwards order
 * \param ctx		Context to pass to callback
 * \return NSERROR_OK on success, or appropriate error otherwise
 *
 * \note Any node deletion must happen in callback_bwd.
 */
static nserror treeview_walk_internal(
		treeview *tree,
		treeview_node *root,
		enum treeview_walk_mode mode,
		nserror (*callback_bwd)(
				treeview_node *n,
				void *ctx,
				bool *end),
		nserror (*callback_fwd)(
				treeview_node *n,
				void *ctx,
				bool *skip_children,
				bool *end),
		void *ctx)
{
	treeview_node *node, *child, *parent, *next_sibling;
	bool walking_search = (mode == TREEVIEW_WALK_MODE_DISPLAY &&
			tree->search.search == true);
	bool skip_children = false;
	bool abort = false;
	bool full = false;
	nserror err;
	bool entry;

	assert(root != NULL);

	if (mode == TREEVIEW_WALK_MODE_LOGICAL_COMPLETE || walking_search) {
		/* We need to visit children of collapsed folders. */
		full = true;
	}

	node = root;
	parent = node->parent;
	next_sibling = node->next_sib;
	child = (full || (node->flags & TV_NFLAGS_EXPANDED)) ?
		node->children : NULL;

	while (node != NULL) {

		if (child != NULL && !skip_children) {
			/* Down to children */
			node = child;
		} 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 && next_sibling == NULL) {
				entry = (node->type == TREE_NODE_ENTRY);
				if (callback_bwd != NULL &&
						(entry || !walking_search)) {
					/* Backwards callback */
					err = callback_bwd(node, ctx, &abort);

					if (err != NSERROR_OK) {
						return err;

					} else if (abort) {
						/* callback requested early
						 * termination */
						return NSERROR_OK;
					}
				}
				node = parent;
				parent = node->parent;
				next_sibling = node->next_sib;
			}

			if (node == root)
				break;

			if (callback_bwd != NULL) {
				/* Backwards callback */
				err = callback_bwd(node, ctx, &abort);

				if (err != NSERROR_OK) {
					return err;

				} else if (abort) {
					/* callback requested early
					 * termination */
					return NSERROR_OK;
				}
			}
			node = next_sibling;
		}

		assert(node != NULL);
		assert(node != root);

		entry = (node->type == TREE_NODE_ENTRY);

		parent = node->parent;
		next_sibling = node->next_sib;
		child = (full || (node->flags & TV_NFLAGS_EXPANDED)) ?
			node->children : NULL;

		if (walking_search && (!entry ||
				!(node->flags & TV_NFLAGS_MATCHED))) {
			continue;
		}

		if (callback_fwd != NULL) {
			/* Forwards callback */
			err = callback_fwd(node, ctx, &skip_children, &abort);

			if (err != NSERROR_OK) {
				return err;

			} else if (abort) {
				/* callback requested early termination */
				return NSERROR_OK;
			}
		}
	}
	return NSERROR_OK;
}


/**
 * Data used when doing a treeview walk for search.
 */
struct treeview_search_walk_data {
	treeview *tree;          /**< The treeview to search. */
	const char *text;        /**< The string being searched for. */
	const unsigned int len;  /**< Length of string being searched for. */
	int window_height;       /**< Accumulate height for matching entries. */
};


/**
 * Treewalk node callback for handling search.
 *
 * \param[in]     n              Current node.
 * \param[in]     ctx            Treeview search context.
 * \param[in,out] skip_children  Flag to allow children to be skipped.
 * \param[in,out] end            Flag to allow iteration to be finished early.
 * \return NSERROR_OK on success else error code.
 */
static nserror treeview__search_walk_cb(
		treeview_node *n,
		void *ctx,
		bool *skip_children,
		bool *end)
{
	struct treeview_search_walk_data *sw = ctx;

	if (n->type != TREE_NODE_ENTRY) {
		return NSERROR_OK;
	}

	if (sw->len == 0) {
		n->flags &= ~TV_NFLAGS_MATCHED;
	} else {
		struct treeview_node_entry *entry =
				(struct treeview_node_entry *)n;
		bool matched = false;

		for (int i = 0; i < sw->tree->n_fields; i++) {
			struct treeview_field *ef = &(sw->tree->fields[i + 1]);
			if (ef->flags & TREE_FLAG_SEARCHABLE) {
				if (strcasestr(entry->fields[i].value.data,
						sw->text) != NULL) {
					matched = true;
					break;
				}
			}
		}

		if (!matched && strcasestr(n->text.data, sw->text) != NULL) {
			matched = true;
		}

		if (matched) {
			n->flags |= TV_NFLAGS_MATCHED;
			sw->window_height += n->height;
		} else {
			n->flags &= ~TV_NFLAGS_MATCHED;
		}
	}

	return NSERROR_OK;
}


/**
 * Search treeview for text.
 *
 * \param[in] tree  Treeview to search.
 * \param[in] text  UTF-8 string to search for.  (NULL-terminated.)
 * \param[in] len   Byte length of UTF-8 string.
 * \return NSERROR_OK on success, appropriate error otherwise.
 */
static nserror treeview__search(
		treeview *tree,
		const char *text,
		unsigned int len)
{
	nserror err;
	uint32_t height;
	uint32_t prev_height = treeview__get_display_height(tree);
	int search_height = (tree->flags & TREEVIEW_SEARCHABLE) ?
			tree_g.line_height : 0;
	struct treeview_search_walk_data sw = {
		.len = len,
		.text = text,
		.tree = tree,
		.window_height = 0,
	};
	struct rect r = {
		.x0 = 0,
		.y0 = search_height,
		.x1 = REDRAW_MAX,
	};

	assert(text[len] == '\0');

	if (tree->root == NULL) {
		return NSERROR_OK;
	}

	err = treeview_walk_internal(tree, tree->root,
			TREEVIEW_WALK_MODE_LOGICAL_COMPLETE, NULL,
			treeview__search_walk_cb, &sw);
	if (err != NSERROR_OK) {
		return err;
	}

	if (len > 0) {
		tree->search.height = sw.window_height;
		tree->search.search = true;
		height = sw.window_height;
	} else {
		tree->search.search = false;
		height = tree->root->height;
	}

	r.y1 = ((height > prev_height) ? height : prev_height) + search_height;
	treeview__cw_invalidate_area(tree, &r);
	treeview__cw_update_size(tree, -1, height);
	treeview__cw_scroll_top(tree);

	return NSERROR_OK;
}


/**
 * Cancel a treeview search, optionally droping focus from search widget.
 *
 * \param[in] tree        Treeview to cancel search in.
 * \param[in] drop_focus  Iff true, drop input focus from search widget.
 */
static void treeview__search_cancel(treeview *tree, bool drop_focus)
{
	struct rect r = {
		.x0 = tree_g.window_padding + tree_g.icon_size,
		.x1 = 600,
		.y0 = 0,
		.y1 = tree_g.line_height,
	};

	tree->search.search = false;
	if (tree->search.active == false) {
		return;
	}

	if (drop_focus) {
		tree->search.active = false;
		textarea_set_caret(tree->search.textarea, -1);
	} else {
		textarea_set_caret(tree->search.textarea, 0);
	}

	textarea_set_text(tree->search.textarea, "");
	treeview__cw_invalidate_area(tree, &r);
}


/**
 * Callback for textarea_create, in desktop/treeview.h
 *
 * \param data treeview context
 * \param msg textarea message
 */
static void treeview_textarea_search_callback(void *data,
		struct textarea_msg *msg)
{
	treeview *tree = data;
	struct rect *r;

	if (tree->search.active == false || tree->root == NULL) {
		return;
	}

	switch (msg->type) {
	case TEXTAREA_MSG_DRAG_REPORT:
		if (msg->data.drag == TEXTAREA_DRAG_NONE) {
			/* Textarea drag finished */
			tree->drag.type = TV_DRAG_NONE;
		} else {
			/* Textarea drag started */
			tree->drag.type = TV_DRAG_SEARCH;
		}
		treeview__cw_drag_status(tree, tree->drag.type);
		break;

	case TEXTAREA_MSG_REDRAW_REQUEST:
		r = &msg->data.redraw;
		r->x0 += tree_g.window_padding + tree_g.icon_size;
		r->y0 += 0;
		r->x1 += 600;
		r->y1 += tree_g.line_height;

		/* Redraw the textarea */
		treeview__cw_invalidate_area(tree, r);
		break;

	case TEXTAREA_MSG_TEXT_MODIFIED:
		/* Textarea length includes trailing NULL, so subtract it. */
		treeview__search(tree,
				msg->data.modified.text,
				msg->data.modified.len - 1);
		break;

	default:
		break;
	}
}


/**
 * Update the layout for any active search.
 *
 * \param[in] tree  The tree to update.
 */
static void treeview__search_update_display(
		treeview *tree)
{
	const char *string;
	unsigned int len;

	if (tree->search.search == false) {
		/* No active search to update view for. */
		return;
	}

	string = textarea_data(tree->search.textarea, &len);
	if (string == NULL || len == 0) {
		return;
	}

	treeview__search(tree, string, len - 1);
}


/**
 * Create treeview's root node
 *
 * \param[out] root Returns root node
 * \return NSERROR_OK on success, appropriate error otherwise
 */
static nserror treeview_create_node_root(treeview_node **root)
{
	treeview_node *n;

	n = malloc(sizeof(struct treeview_node));
	if (n == NULL) {
		return NSERROR_NOMEM;
	}

	n->flags = TV_NFLAGS_EXPANDED;
	n->type = TREE_NODE_ROOT;

	n->height = 0;
	n->inset = tree_g.window_padding - tree_g.step_width;

	n->text.data = NULL;
	n->text.len = 0;
	n->text.width = 0;

	n->parent = NULL;
	n->next_sib = NULL;
	n->prev_sib = NULL;
	n->children = NULL;

	n->client_data = NULL;

	*root = n;

	return NSERROR_OK;
}


/**
 * Set a node's inset from its parent
 *
 * This may be used as treeview walk callback
 *
 * \param[in] n node to set inset on
 * \param[in] ctx context unused
 * \param[out] skip_children set to false so child nodes are not skipped.
 * \param[out] end unused flag so treewalk in not terminated early.
 */
static nserror
treeview_set_inset_from_parent(treeview_node *n,
			       void *ctx,
			       bool *skip_children,
			       bool *end)
{
	if (n->parent != NULL)
		n->inset = n->parent->inset + tree_g.step_width;

	*skip_children = false;
	return NSERROR_OK;
}


/**
 * Insert a treeview node into a treeview
 *
 * \param tree  the treeview to insert node into.
 * \param a     parentless node to insert
 * \param b     tree node to insert a as a relation of
 * \param rel The relationship between \a a and \a b
 */
static inline void
treeview_insert_node(
		treeview *tree,
		treeview_node *a,
		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->next_sib = b->children;
		if (a->next_sib)
			a->next_sib->prev_sib = a;
		b->children = a;
		break;

	case TREE_REL_NEXT_SIBLING:
		assert(b->type != TREE_NODE_ROOT);
		a->prev_sib = b;
		a->next_sib = b->next_sib;
		a->parent = b->parent;
		b->next_sib = a;
		if (a->next_sib)
			a->next_sib->prev_sib = a;
		break;

	default:
		assert(0);
		break;
	}

	assert(a->parent != NULL);

	a->inset = a->parent->inset + tree_g.step_width;
	if (a->children != NULL) {
		treeview_walk_internal(tree, a,
				TREEVIEW_WALK_MODE_LOGICAL_COMPLETE, NULL,
				treeview_set_inset_from_parent, NULL);
	}

	if (a->parent->flags & TV_NFLAGS_EXPANDED) {
		int height = a->height;
		/* Parent is expanded, so inserted node will be visible and
		 * affect layout */
		if (a->text.width == 0) {
			guit->layout->width(&plot_style_odd.text,
					    a->text.data,
					    a->text.len,
					    &(a->text.width));
		}

		do {
			a->parent->height += height;
			a = a->parent;
		} while (a->parent != NULL);
	}
}


/* Exported interface, documented in treeview.h */
nserror
treeview_create_node_folder(treeview *tree,
			    treeview_node **folder,
			    treeview_node *relation,
			    enum treeview_relationship rel,
			    const struct treeview_field_data *field,
			    void *data,
			    treeview_node_options_flags flags)
{
	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 = (flags & TREE_OPTION_SPECIAL_DIR) ?
		TV_NFLAGS_SPECIAL : TV_NFLAGS_NONE;
	n->type = TREE_NODE_FOLDER;

	n->height = tree_g.line_height;

	n->text.data = field->value;
	n->text.len = field->value_len;
	n->text.width = 0;

	n->parent = NULL;
	n->next_sib = NULL;
	n->prev_sib = NULL;
	n->children = NULL;

	n->client_data = data;

	treeview_insert_node(tree, n, relation, rel);

	if (n->parent->flags & TV_NFLAGS_EXPANDED) {
		/* Inform front end of change in dimensions */
		if (!(flags & TREE_OPTION_SUPPRESS_RESIZE))
			treeview__cw_update_size(tree, -1,
						 tree->root->height);

		/* Redraw */
		if (!(flags & TREE_OPTION_SUPPRESS_REDRAW)) {
			struct rect r;
			r.x0 = 0;
			r.y0 = treeview_node_y(tree, n);
			r.x1 = REDRAW_MAX;
			r.y1 = tree->root->height;
			treeview__cw_invalidate_area(tree, &r);
		}
	}

	*folder = n;

	return NSERROR_OK;
}


/* Exported interface, documented in treeview.h */
nserror
treeview_update_node_folder(treeview *tree,
			    treeview_node *folder,
			    const struct treeview_field_data *field,
			    void *data)
{
	bool match;

	assert(data != NULL);
	assert(tree != NULL);
	assert(folder != NULL);
	assert(data == folder->client_data);
	assert(folder->parent != NULL);

	assert(field != NULL);
	assert(lwc_string_isequal(tree->fields[tree->n_fields].field,
				  field->field, &match) == lwc_error_ok &&
	       match == true);
	folder->text.data = field->value;
	folder->text.len = field->value_len;
	folder->text.width = 0;

	if (folder->parent->flags & TV_NFLAGS_EXPANDED) {
		/* Text will be seen, get its width */
		guit->layout->width(&plot_style_odd.text,
				    folder->text.data,
				    folder->text.len,
				    &(folder->text.width));
	} else {
		/* Just invalidate the width, since it's not needed now */
		folder->text.width = 0;
	}

	/* Redraw */
	if (folder->parent->flags & TV_NFLAGS_EXPANDED) {
		struct rect r;
		r.x0 = 0;
		r.y0 = treeview_node_y(tree, folder);
		r.x1 = REDRAW_MAX;
		r.y1 = r.y0 + tree_g.line_height;
		treeview__cw_invalidate_area(tree, &r);
	}

	return NSERROR_OK;
}


/* Exported interface, documented in treeview.h */
nserror
treeview_update_node_entry(treeview *tree,
			   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.data = fields[0].value;
	entry->text.len = fields[0].value_len;
	entry->text.width = 0;

	if (entry->parent->flags & TV_NFLAGS_EXPANDED) {
		/* Text will be seen, get its width */
		guit->layout->width(&plot_style_odd.text,
				    entry->text.data,
				    entry->text.len,
				    &(entry->text.width));
	} else {
		/* Just invalidate the width, since it's not needed now */
		entry->text.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 & TV_NFLAGS_EXPANDED) {
			/* Text will be seen, get its width */
			guit->layout->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;
		}
	}

	treeview__search_update_display(tree);

	/* Redraw */
	if (entry->parent->flags & TV_NFLAGS_EXPANDED) {
		struct rect r;
		r.x0 = 0;
		r.y0 = treeview_node_y(tree, entry);
		r.x1 = REDRAW_MAX;
		r.y1 = r.y0 + entry->height;
		treeview__cw_invalidate_area(tree, &r);
	}

	return NSERROR_OK;
}


/* Exported interface, documented in treeview.h */
nserror
treeview_create_node_entry(treeview *tree,
			   treeview_node **entry,
			   treeview_node *relation,
			   enum treeview_relationship rel,
			   const struct treeview_field_data fields[],
			   void *data,
			   treeview_node_options_flags flags)
{
	bool match;
	struct treeview_node_entry *e;
	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 = (treeview_node *) e;

	n->flags = TV_NFLAGS_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.data = fields[0].value;
	n->text.len = fields[0].value_len;
	n->text.width = 0;

	n->parent = NULL;
	n->next_sib = NULL;
	n->prev_sib = 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(tree, n, relation, rel);

	if (n->parent->flags & TV_NFLAGS_EXPANDED) {
		/* Inform front end of change in dimensions */
		if (!(flags & TREE_OPTION_SUPPRESS_RESIZE))
			treeview__cw_update_size(tree, -1,
						 tree->root->height);

		/* Redraw */
		if (!(flags & TREE_OPTION_SUPPRESS_REDRAW)) {
			struct rect r;
			r.x0 = 0;
			r.y0 = treeview_node_y(tree, n);
			r.x1 = REDRAW_MAX;
			r.y1 = tree->root->height;
			treeview__cw_invalidate_area(tree, &r);
		}
	}

	treeview__search_update_display(tree);

	*entry = n;

	return NSERROR_OK;
}


/**
 * Treewalk iterator context
 */
struct treeview_walk_ctx {
	treeview_walk_cb enter_cb;
	treeview_walk_cb leave_cb;
	void *ctx;
	enum treeview_node_type type;
};


/**
 * Treewalk node enter callback.
 *
 * \param n current node
 * \param ctx treewalk context
 * \param skip_children set if child nodes should be skipped
 * \param end set if iteration should end early
 */
static nserror
treeview_walk_fwd_cb(treeview_node *n,
		     void *ctx,
		     bool *skip_children,
		     bool *end)
{
	struct treeview_walk_ctx *tw = ctx;

	if (n->type & tw->type) {
		return tw->enter_cb(tw->ctx, n->client_data, n->type, end);
	}

	return NSERROR_OK;
}


/**
 * Treewalk node leave callback.
 *
 * \param n current node
 * \param ctx treewalk context
 * \param end set if iteration should end early
 */
static nserror treeview_walk_bwd_cb(treeview_node *n, void *ctx, bool *end)
{
	struct treeview_walk_ctx *tw = ctx;

	if (n->type & tw->type) {
		return tw->leave_cb(tw->ctx, n->client_data, n->type, end);
	}

	return NSERROR_OK;
}


/* Exported interface, documented in treeview.h */
nserror
treeview_walk(treeview *tree,
	      treeview_node *root,
	      treeview_walk_cb enter_cb,
	      treeview_walk_cb leave_cb,
	      void *ctx,
	      enum treeview_node_type type)
{
	struct treeview_walk_ctx tw = {
		.enter_cb = enter_cb,
		.leave_cb = leave_cb,
		.ctx = ctx,
		.type = type
	};

	assert(tree != NULL);
	assert(tree->root != NULL);

	if (root == NULL)
		root = tree->root;

	return treeview_walk_internal(tree, root,
			TREEVIEW_WALK_MODE_LOGICAL_COMPLETE,
			(leave_cb != NULL) ? treeview_walk_bwd_cb : NULL,
			(enter_cb != NULL) ? treeview_walk_fwd_cb : NULL,
			&tw);
}


/**
 * Unlink a treeview node
 *
 * \param n Node to unlink
 * \return true iff ancestor heights need to be reduced
 */
static inline bool treeview_unlink_node(treeview_node *n)
{
	/* Unlink node from tree */
	if (n->parent != NULL && n->parent->children == n) {
		/* Node is a first child */
		n->parent->children = n->next_sib;

	} else if (n->prev_sib != NULL) {
		/* Node is not first child */
		n->prev_sib->next_sib = n->next_sib;
	}

	if (n->next_sib != NULL) {
		/* Always need to do this */
		n->next_sib->prev_sib = n->prev_sib;
	}

	/* Reduce ancestor heights */
	if ((n->parent != NULL) &&
	    (n->parent->flags & TV_NFLAGS_EXPANDED)) {
		return true;
	}

	return false;
}


/**
 * Cancel the editing of a treeview node
 *
 * \param tree Treeview object to cancel node editing in
 * \param redraw Set true iff redraw of removed textarea area required
 */
static void treeview_edit_cancel(treeview *tree, bool redraw)
{
	struct rect r;

	if (tree->edit.textarea == NULL)
		return;

	textarea_destroy(tree->edit.textarea);

	tree->edit.textarea = NULL;
	tree->edit.node = NULL;

	if (tree->drag.type == TV_DRAG_TEXTAREA)
		tree->drag.type = TV_DRAG_NONE;

	if (redraw) {
		r.x0 = tree->edit.x;
		r.y0 = tree->edit.y;
		r.x1 = tree->edit.x + tree->edit.w;
		r.y1 = tree->edit.y + tree->edit.h;
		treeview__cw_invalidate_area(tree, &r);
	}
}


/**
 * Complete a treeview edit
 *
 * Complete edit by informing the client with a change request msg
 *
 * \param tree Treeview object to complete edit in
 */
static void treeview_edit_done(treeview *tree)
{
	int len, error;
	char* new_text;
	treeview_node *n = tree->edit.node;
	struct treeview_node_msg msg;
	msg.msg = TREE_MSG_NODE_EDIT;

	if (tree->edit.textarea == NULL) {
		return;
	}

	assert(n != NULL);

	/* Get new text length */
	len = textarea_get_text(tree->edit.textarea, NULL, 0);

	new_text = malloc(len);
	if (new_text == NULL) {
		/* TODO: don't just silently ignore */
		return;
	}

	/* Get the new text from textarea */
	error = textarea_get_text(tree->edit.textarea, new_text, len);
	if (error == -1) {
		/* TODO: don't just silently ignore */
		free(new_text);
		return;
	}

	/* Inform the treeview client with change request message */
	msg.data.node_edit.field = tree->edit.field;
	msg.data.node_edit.text = new_text;

	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:
		break;
	}

	/* Finished with the new text */
	free(new_text);

	/* Finally, destroy the treeview, and redraw */
	treeview_edit_cancel(tree, true);
}


/**
 * context for treeview node deletion iterator
 */
struct treeview_node_delete {
	treeview *tree;
	int h_reduction;
	bool user_interaction;
};


/**
 * Treewalk node callback deleting nodes.
 */
static nserror
treeview_delete_node_walk_cb(treeview_node *n, void *ctx, bool *end)
{
	struct treeview_node_delete *nd = (struct treeview_node_delete *)ctx;
	struct treeview_node_msg msg;

	msg.msg = TREE_MSG_NODE_DELETE;
	msg.data.delete.user = nd->user_interaction;

	assert(n->children == NULL);

	if (treeview_unlink_node(n))
		nd->h_reduction += (n->type == TREE_NODE_ENTRY) ?
			n->height : tree_g.line_height;

	/* Handle any special treatment */
	switch (n->type) {
	case TREE_NODE_ENTRY:
		nd->tree->callbacks->entry(msg, n->client_data);
		break;

	case TREE_NODE_FOLDER:
		nd->tree->callbacks->folder(msg, n->client_data);
		break;

	case TREE_NODE_ROOT:
		break;

	default:
		return NSERROR_BAD_PARAMETER;
	}

	/* Cancel any edit of this node */
	if (nd->tree->edit.textarea != NULL &&
	    nd->tree->edit.node == n) {
		treeview_edit_cancel(nd->tree, false);
	}

	/* Free the node */
	free(n);

	return NSERROR_OK;
}


/**
 * Delete a treeview node
 *
 * Will emit folder or entry deletion msg callback.
 *
 * \note this can be called from inside a treeview_walk fwd callback.
 * For example walking the tree and calling this for any node that's selected.
 *
 * This function does not delete empty nodes, so if TREEVIEW_DEL_EMPTY_DIRS is
 * set, caller must also call treeview_delete_empty.
 *
 * \param tree		Treeview object to delete node from
 * \param n		Node to delete
 * \param interaction	Delete is result of user interaction with treeview
 * \param flags		Treeview node options flags
 * \return NSERROR_OK on success, appropriate error otherwise
 */
static nserror
treeview_delete_node_internal(treeview *tree,
			      treeview_node *n,
			      bool interaction,
			      treeview_node_options_flags flags)
{
	nserror err;
	treeview_node *p = n->parent;
	struct treeview_node_delete nd = {
		.tree = tree,
		.h_reduction = 0,
		.user_interaction = interaction
	};

	if (interaction && (tree->flags & TREEVIEW_NO_DELETES)) {
		return NSERROR_OK;
	}

	/* Delete any children first */
	err = treeview_walk_internal(tree, n,
			TREEVIEW_WALK_MODE_LOGICAL_COMPLETE,
			treeview_delete_node_walk_cb, NULL, &nd);
	if (err != NSERROR_OK) {
		return err;
	}

	/* Now delete node */
	if (n == tree->root)
		tree->root = NULL;
	err = treeview_delete_node_walk_cb(n, &nd, false);
	if (err != NSERROR_OK) {
		return err;
	}

	n = p;
	/* Reduce ancestor heights */
	while (n != NULL && n->flags & TV_NFLAGS_EXPANDED) {
		n->height -= nd.h_reduction;
		n = n->parent;
	}

	/* Inform front end of change in dimensions */
	if (tree->root != NULL && p != NULL && p->flags & TV_NFLAGS_EXPANDED &&
	    nd.h_reduction > 0 &&
	    !(flags & TREE_OPTION_SUPPRESS_RESIZE)) {
		treeview__cw_update_size(tree, -1,
					 tree->root->height);
	}

	treeview__search_update_display(tree);

	return NSERROR_OK;
}


/**
 * Delete any empty treeview folder nodes
 *
 * \param tree Treeview object to delete empty nodes from
 * \param interaction Delete is result of user interaction with treeview
 * \return NSERROR_OK on success, appropriate error otherwise
 *
 * Note this must not be called within a treeview_walk.  It may delete the
 * walker's 'current' node, making it impossible to move on without invalid
 * reads.
 */
static nserror treeview_delete_empty_nodes(treeview *tree, bool interaction)
{
	treeview_node *node, *child, *parent, *next_sibling, *p;
	bool abort = false;
	nserror err;
	struct treeview_node_delete nd = {
		.tree = tree,
		.h_reduction = 0,
		.user_interaction = interaction
	};

	assert(tree != NULL);
	assert(tree->root != NULL);

	node = tree->root;
	parent = node->parent;
	next_sibling = node->next_sib;
	child = (node->flags & TV_NFLAGS_EXPANDED) ? node->children : NULL;

	while (node != NULL) {

		if (child != NULL) {
			/* Down to children */
			node = child;
		} 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->parent != NULL &&
			       next_sibling == NULL) {
				if (node->type == TREE_NODE_FOLDER &&
				    node->children == NULL) {
					/* Delete node */
					p = node->parent;
					err = treeview_delete_node_walk_cb(
						node, &nd, &abort);
					if (err != NSERROR_OK) {
						return err;
					}

					/* Reduce ancestor heights */
					while (p != NULL &&
					       p->flags &
					       TV_NFLAGS_EXPANDED) {
						p->height -= nd.h_reduction;
						p = p->parent;
					}
					nd.h_reduction = 0;
				}
				node = parent;
				parent = node->parent;
				next_sibling = node->next_sib;
			}

			if (node->parent == NULL)
				break;

			if (node->type == TREE_NODE_FOLDER &&
			    node->children == NULL) {
				/* Delete node */
				p = node->parent;
				err = treeview_delete_node_walk_cb(
					node, &nd, &abort);
				if (err != NSERROR_OK) {
					return err;
				}

				/* Reduce ancestor heights */
				while (p != NULL &&
				       p->flags & TV_NFLAGS_EXPANDED) {
					p->height -= nd.h_reduction;
					p = p->parent;
				}
				nd.h_reduction = 0;
			}
			node = next_sibling;
		}

		assert(node != NULL);
		assert(node->parent != NULL);

		parent = node->parent;
		next_sibling = node->next_sib;
		child = (node->flags & TV_NFLAGS_EXPANDED) ?
			node->children : NULL;
	}

	return NSERROR_OK;
}


/* Exported interface, documented in treeview.h */
nserror
treeview_delete_node(treeview *tree,
		     treeview_node *n,
		     treeview_node_options_flags flags)
{
	nserror err;
	struct rect r;
	bool visible;

	assert(tree != NULL);
	assert(n != NULL);
	assert(n->parent != NULL);

	visible = n->parent->flags & TV_NFLAGS_EXPANDED;

	r.y0 = treeview_node_y(tree, n);
	r.y1 = tree->root->height;

	err = treeview_delete_node_internal(tree, n, false, flags);
	if (err != NSERROR_OK)
		return err;

	if (tree->flags & TREEVIEW_DEL_EMPTY_DIRS) {
		int h = tree->root->height;
		/* Delete any empty nodes */
		err = treeview_delete_empty_nodes(tree, false);
		if (err != NSERROR_OK)
			return err;

		/* Inform front end of change in dimensions */
		if (tree->root->height != h) {
			r.y0 = 0;
			if (!(flags & TREE_OPTION_SUPPRESS_RESIZE)) {
				treeview__cw_update_size(tree, -1,
							 tree->root->height);
			}
		}
	}

	/* Redraw */
	if (visible && !(flags & TREE_OPTION_SUPPRESS_REDRAW)) {
		r.x0 = 0;
		r.x1 = REDRAW_MAX;
		treeview__cw_invalidate_area(tree, &r);
	}

	return NSERROR_OK;
}


/**
 * Helper to create a textarea.
 *
 * \param[in] tree         The treeview we're creating the textarea for.
 * \param[in] width        The width of the textarea.
 * \param[in] height       The height of the textarea.
 * \param[in] border       The border colour to use.
 * \param[in] background   The background colour to use.
 * \param[in] foreground   The foreground colour to use.
 * \param[in] text         The text style to use for the text area.
 * \param[in] ta_callback  The textarea callback function to give the textarea.
 * \return the textarea pointer on success, or NULL on failure.
 */
static struct textarea *treeview__create_textarea(
		treeview *tree,
		int width,
		int height,
		colour border,
		colour background,
		colour foreground,
		plot_font_style_t text,
		textarea_client_callback ta_callback)
{
	/* Configure the textarea */
	textarea_flags ta_flags = TEXTAREA_INTERNAL_CARET;
	textarea_setup ta_setup = {
		.text = text,
		.width = width,
		.height = height,
		.pad_top = 0,
		.pad_left = 2,
		.pad_right = 2,
		.pad_bottom = 0,
		.border_width = 1,
		.border_col = border,
		.selected_bg = foreground,
		.selected_text = background,
	};

	ta_setup.text.foreground = foreground;
	ta_setup.text.background = background;

	/* Create text area */
	return textarea_create(ta_flags, &ta_setup, ta_callback, tree);
}


/* Exported interface, documented in treeview.h */
nserror
treeview_create(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,
		treeview_flags flags)
{
	nserror error;
	int i;

	assert((cw_t == NULL && cw == NULL) || (cw_t != NULL && 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);

		guit->layout->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)->drag.type = TV_DRAG_NONE;
	(*tree)->drag.start_node = NULL;
	(*tree)->drag.start.x = 0;
	(*tree)->drag.start.y = 0;
	(*tree)->drag.start.node_y = 0;
	(*tree)->drag.start.node_h = 0;
	(*tree)->drag.prev.x = 0;
	(*tree)->drag.prev.y = 0;
	(*tree)->drag.prev.node_y = 0;
	(*tree)->drag.prev.node_h = 0;

	(*tree)->move.root = NULL;
	(*tree)->move.target = NULL;
	(*tree)->move.target_pos = TV_TARGET_NONE;

	(*tree)->edit.textarea = NULL;
	(*tree)->edit.node = NULL;

	if (flags & TREEVIEW_SEARCHABLE) {
		(*tree)->search.textarea = treeview__create_textarea(
				*tree, 600, tree_g.line_height,
				plot_style_even.text.background,
				plot_style_even.text.background,
				plot_style_even.text.foreground,
				plot_style_odd.text,
				treeview_textarea_search_callback);
		if ((*tree)->search.textarea == NULL) {
			treeview_destroy(*tree);
			return NSERROR_NOMEM;
		}
	} else {
		(*tree)->search.textarea = NULL;
	}
	(*tree)->search.active = false;
	(*tree)->search.search = false;

	(*tree)->flags = flags;

	(*tree)->cw_t = cw_t;
	(*tree)->cw_h = cw;

	return NSERROR_OK;
}


/* Exported interface, documented in treeview.h */
nserror
treeview_cw_attach(treeview *tree,
		   const struct core_window_callback_table *cw_t,
		   struct core_window *cw)
{
	assert(cw_t != NULL);
	assert(cw != NULL);

	if (tree->cw_t != NULL || tree->cw_h != NULL) {
		NSLOG(netsurf, INFO, "Treeview already attached.");
		return NSERROR_UNKNOWN;
	}
	tree->cw_t = cw_t;
	tree->cw_h = cw;

	return NSERROR_OK;
}


/* Exported interface, documented in treeview.h */
nserror treeview_cw_detach(treeview *tree)
{
	tree->cw_t = NULL;
	tree->cw_h = NULL;

	treeview__search_cancel(tree, true);

	return NSERROR_OK;
}


/* Exported interface, documented in treeview.h */
nserror treeview_destroy(treeview *tree)
{
	int f;

	assert(tree != NULL);

	if (tree->search.textarea != NULL) {
		tree->search.active = false;
		tree->search.search = false;
		textarea_destroy(tree->search.textarea);
	}

	/* Destroy nodes */
	treeview_delete_node_internal(tree, tree->root, false,
				      TREE_OPTION_SUPPRESS_RESIZE |
				      TREE_OPTION_SUPPRESS_REDRAW);

	/* 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;
}


/**
 * Expand a treeview's nodes
 *
 * \param tree Treeview object to expand nodes in
 * \param node The node to expand.
 * \return NSERROR_OK on success, appropriate error otherwise.
 */
static nserror
treeview_node_expand_internal(treeview *tree, treeview_node *node)
{
	treeview_node *child;
	struct treeview_node_entry *e;
	int additional_height = 0;
	int i;

	assert(tree != NULL);
	assert(node != NULL);

	if (node->flags & TV_NFLAGS_EXPANDED) {
		/* What madness is this? */
		NSLOG(netsurf, INFO, "Tried to expand an expanded node.");
		return NSERROR_OK;
	}

	switch (node->type) {
	case TREE_NODE_FOLDER:
		child = node->children;
		if (child == NULL) {
			/* Allow expansion of empty folders */
			break;
		}

		do {
			if (child->text.width == 0) {
				guit->layout->width(&plot_style_odd.text,
						    child->text.data,
						    child->text.len,
						    &(child->text.width));
			}

			additional_height += child->height;

			child = child->next_sib;
		} 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) {
				guit->layout->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:
	case TREE_NODE_NONE:
		assert(node->type != TREE_NODE_ROOT);
		assert(node->type != TREE_NODE_NONE);
		break;
	}

	/* Update the node */
	node->flags |= TV_NFLAGS_EXPANDED;

	/* And node heights */
	for (struct treeview_node *n = node;
			(n != NULL) && (n->flags & TV_NFLAGS_EXPANDED);
			n = n->parent) {
		n->height += additional_height;
	}

	if (tree->search.search &&
			node->type == TREE_NODE_ENTRY &&
			node->flags & TV_NFLAGS_MATCHED) {
		tree->search.height += additional_height;
	}

	/* Inform front end of change in dimensions */
	if (additional_height != 0) {
		treeview__cw_update_size(tree, -1,
				treeview__get_display_height(tree));
	}

	return NSERROR_OK;
}


/* Exported interface, documented in treeview.h */
nserror treeview_node_expand(treeview *tree, treeview_node *node)
{
	nserror res;

	res = treeview_node_expand_internal(tree, node);
	NSLOG(netsurf, INFO, "Expanding!");
	if (res == NSERROR_OK) {
		/* expansion was successful, attempt redraw */
		treeview__redraw_from_node(tree, node);
		NSLOG(netsurf, INFO, "Expanded!");
	}

	return res;
}


/**
 * context for treeview contraction callback
 */
struct treeview_contract_data {
	treeview *tree;
	bool only_entries;
};


/**
 * Treewalk node callback for handling node contraction.
 *
 * \param n node
 * \param ctx contract iterator context
 * \param end flag to end iteration now
 * \return NSERROR_OK on success else appropriate error code
 */
static nserror treeview_node_contract_cb(treeview_node *n, void *ctx, bool *end)
{
	struct treeview_contract_data *data = ctx;
	int h_reduction;

	assert(n != NULL);
	assert(n->type != TREE_NODE_ROOT);

	n->flags &= ~TV_NFLAGS_SELECTED;

	if ((n->flags & TV_NFLAGS_EXPANDED) == false ||
	    (n->type == TREE_NODE_FOLDER && data->only_entries)) {
		/* Nothing to do. */
		return NSERROR_OK;
	}

	h_reduction = n->height - tree_g.line_height;

	assert(h_reduction >= 0);
	for (struct treeview_node *node = n;
			(node != NULL) && (node->flags & TV_NFLAGS_EXPANDED);
			node = node->parent) {
		node->height -= h_reduction;
	}

	if (data->tree->search.search) {
		data->tree->search.height -= h_reduction;
	}

	n->flags ^= TV_NFLAGS_EXPANDED;

	return NSERROR_OK;
}


/**
 * Contract a treeview node
 *
 * \param tree Treeview object to contract node in
 * \param node Node to contract
 * \return NSERROR_OK on success, appropriate error otherwise
 */
static nserror
treeview_node_contract_internal(treeview *tree, treeview_node *node)
{
	struct treeview_contract_data data;
	bool selected;
	assert(node != NULL);

	if ((node->flags & TV_NFLAGS_EXPANDED) == false) {
		/* What madness is this? */
		NSLOG(netsurf, INFO, "Tried to contract a contracted node.");
		return NSERROR_OK;
	}

	data.tree = tree;
	data.only_entries = false;
	selected = node->flags & TV_NFLAGS_SELECTED;

	/* Contract children. */
	treeview_walk_internal(tree, node, TREEVIEW_WALK_MODE_LOGICAL_EXPANDED,
			treeview_node_contract_cb, NULL, &data);

	/* Contract node */
	treeview_node_contract_cb(node, &data, false);

	if (selected)
		node->flags |= TV_NFLAGS_SELECTED;

	/* Inform front end of change in dimensions */
	treeview__cw_update_size(tree, -1, treeview__get_display_height(tree));

	return NSERROR_OK;
}


/* Exported interface, documented in treeview.h */
nserror treeview_node_contract(treeview *tree, treeview_node *node)
{
	nserror res;

	assert(tree != NULL);

	res = treeview_node_contract_internal(tree, node);
	NSLOG(netsurf, INFO, "Contracting!");
	if (res == NSERROR_OK) {
		/* successful contraction, request redraw */
		treeview__redraw_from_node(tree, node);
		NSLOG(netsurf, INFO, "Contracted!");
	}

	return res;
}


/* Exported interface, documented in treeview.h */
nserror treeview_contract(treeview *tree, bool all)
{
	int search_height = (tree->flags & TREEVIEW_SEARCHABLE) ?
			tree_g.line_height : 0;
	struct treeview_contract_data data;
	bool selected;
	treeview_node *n;
	struct rect r;

	assert(tree != NULL);
	assert(tree->root != NULL);

	r.x0 = 0;
	r.y0 = 0;
	r.x1 = REDRAW_MAX;
	r.y1 = tree->root->height + search_height;

	data.tree = tree;
	data.only_entries = !all;

	for (n = tree->root->children; n != NULL; n = n->next_sib) {
		if ((n->flags & TV_NFLAGS_EXPANDED) == false) {
			continue;
		}

		selected = n->flags & TV_NFLAGS_SELECTED;

		/* Contract children. */
		treeview_walk_internal(tree, n,
				TREEVIEW_WALK_MODE_LOGICAL_EXPANDED,
				treeview_node_contract_cb, NULL, &data);

		/* Contract node */
		treeview_node_contract_cb(n, &data, false);

		if (selected)
			n->flags |= TV_NFLAGS_SELECTED;
	}

	/* Inform front end of change in dimensions */
	treeview__cw_update_size(tree, -1, tree->root->height);

	/* Redraw */
	treeview__cw_invalidate_area(tree, &r);

	return NSERROR_OK;
}


/**
 * context data for treeview expansion
 */
struct treeview_expand_data {
	treeview *tree;
	bool only_folders;
};


/**
 * Treewalk node callback for handling recursive node expansion.
 *
 * \param n current node
 * \param ctx node expansion context
 * \param skip_children flag to allow children to be skipped
 * \param end flag to allow iteration to be finished early.
 * \return NSERROR_OK on success else error code.
 */
static nserror
treeview_expand_cb(treeview_node *n,
		   void *ctx,
		   bool *skip_children,
		   bool *end)
{
	struct treeview_expand_data *data = ctx;
	nserror err;

	assert(n != NULL);
	assert(n->type != TREE_NODE_ROOT);

	if (n->flags & TV_NFLAGS_EXPANDED ||
	    (data->only_folders && n->type != TREE_NODE_FOLDER)) {
		/* Nothing to do. */
		return NSERROR_OK;
	}

	err = treeview_node_expand_internal(data->tree, n);

	return err;
}


/* Exported interface, documented in treeview.h */
nserror treeview_expand(treeview *tree, bool only_folders)
{
	struct treeview_expand_data data;
	nserror res;
	struct rect r;

	assert(tree != NULL);
	assert(tree->root != NULL);

	data.tree = tree;
	data.only_folders = only_folders;

	res = treeview_walk_internal(tree, tree->root,
			TREEVIEW_WALK_MODE_LOGICAL_COMPLETE,
			NULL, treeview_expand_cb, &data);
	if (res == NSERROR_OK) {
		/* expansion succeeded, schedule redraw */

		r.x0 = 0;
		r.y0 = 0;
		r.x1 = REDRAW_MAX;
		r.y1 = tree->root->height;

		treeview__cw_invalidate_area(tree, &r);
	}
	return res;
}


/**
 * Draw a treeview normally, in tree mode.
 *
 * \param[in]     tree      The treeview we're rendering.
 * \param[in]     x         X coordinate we're rendering the treeview at.
 * \param[in]     y         Y coordinate we're rendering the treeview at.
 * \param[in,out] render_y  Current vertical position in tree, updated on exit.
 * \param[in]     r         Clip rectangle.
 * \param[in]     data      Redraw data for rendering contents.
 * \param[in]     ctx       Current render context.
 */
static void treeview_redraw_tree(
		treeview *tree,
		const int x,
		const int y,
		int *render_y_in_out,
		struct rect *r,
		struct content_redraw_data *data,
		const struct redraw_context *ctx)
{
	struct treeview_node_style *style = &plot_style_odd;
	enum treeview_resource_id res = TREE_RES_CONTENT;
	int baseline = (tree_g.line_height * 3 + 2) / 4;
	plot_font_style_t *infotext_style;
	treeview_node *root = tree->root;
	treeview_node *node = tree->root;
	int render_y = *render_y_in_out;
	plot_font_style_t *text_style;
	plot_style_t *bg_style;
	int sel_min, sel_max;
	uint32_t count = 0;
	struct rect rect;
	int inset;
	int x0;

	if (tree->drag.start.y > tree->drag.prev.y) {
		sel_min = tree->drag.prev.y;
		sel_max = tree->drag.start.y;
	} else {
		sel_min = tree->drag.start.y;
		sel_max = tree->drag.prev.y;
	}

	while (node != NULL) {
		struct treeview_node_entry *entry;
		struct bitmap *furniture;
		bool invert_selection;
		treeview_node *next;
		int height;
		int i;

		next = (node->flags & TV_NFLAGS_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->next_sib == NULL) {
				node = node->parent;
			}

			if (node == root)
				break;

			node = node->next_sib;
		}

		assert(node != NULL);
		assert(node != root);
		assert(node->type == TREE_NODE_FOLDER ||
		       node->type == TREE_NODE_ENTRY);

		count++;
		inset = x + 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 (tree->drag.type == TV_DRAG_SELECTION &&
		    (render_y + height >= sel_min &&
		     render_y < sel_max)) {
			invert_selection = true;
		} else {
			invert_selection = false;
		}
		if ((node->flags & TV_NFLAGS_SELECTED && !invert_selection) ||
		    (!(node->flags & TV_NFLAGS_SELECTED) &&
		     invert_selection)) {
			bg_style = &style->sbg;
			text_style = &style->stext;
			infotext_style = &style->sitext;
			furniture = (node->flags & TV_NFLAGS_EXPANDED) ?
				style->furn[TREE_FURN_CONTRACT].sel :
				style->furn[TREE_FURN_EXPAND].sel;
		} else {
			bg_style = &style->bg;
			text_style = &style->text;
			infotext_style = &style->itext;
			furniture = (node->flags & TV_NFLAGS_EXPANDED) ?
				style->furn[TREE_FURN_CONTRACT].bmp :
				style->furn[TREE_FURN_EXPAND].bmp;
		}

		/* Render background */
		rect.x0 = r->x0;
		rect.y0 = render_y;
		rect.x1 = r->x1;
		rect.y1 = render_y + height;
		ctx->plot->rectangle(ctx, bg_style, &rect);

		/* Render toggle */
		ctx->plot->bitmap(ctx,
				furniture,
				inset,
				render_y + tree_g.line_height / 4,
				style->furn[TREE_FURN_EXPAND].size,
				style->furn[TREE_FURN_EXPAND].size,
				bg_style->fill_colour,
				BITMAPF_NONE);

		/* Render icon */
		if (node->type == TREE_NODE_ENTRY) {
			res = TREE_RES_CONTENT;
		} else if (node->flags & TV_NFLAGS_SPECIAL) {
			res = TREE_RES_FOLDER_SPECIAL;
		} else {
			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, ctx);
		}

		/* Render text */
		x0 = inset + tree_g.step_width + tree_g.icon_step;
		ctx->plot->text(ctx,
				text_style,
				x0, render_y + baseline,
				node->text.data,
				node->text.len);

		/* 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 & TV_NFLAGS_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;

				ctx->plot->text(ctx,
						infotext_style,
						x0 + max_width - ef->value.width - tree_g.step_width,
						render_y + baseline,
						ef->value.data,
						ef->value.len);

				ctx->plot->text(ctx,
						infotext_style,
						x0 + max_width,
						render_y + baseline,
						entry->fields[i].value.data,
						entry->fields[i].value.len);
			} else {
				ctx->plot->text(ctx,
						infotext_style,
						x0, render_y + baseline,
						entry->fields[i].value.data,
						entry->fields[i].value.len);
			}

			/* Rendered the expanded entry field */
			render_y += tree_g.line_height;
		}

		/* Finished rendering expanded entry */

		if (render_y > r->y1) {
			/* Passed the bottom of what's in the clip region.
			 * Done. */
			break;
		}
	}

	*render_y_in_out = render_y;
}


/**
 * Draw a treeview normally, in tree mode.
 *
 * \param[in]     tree      The treeview we're rendering.
 * \param[in]     x         X coordinate we're rendering the treeview at.
 * \param[in]     y         Y coordinate we're rendering the treeview at.
 * \param[in,out] render_y  Current vertical position in tree, updated on exit.
 * \param[in]     r         Clip rectangle.
 * \param[in]     data      Redraw data for rendering contents.
 * \param[in]     ctx       Current render context.
 */
static void treeview_redraw_search(
		treeview *tree,
		const int x,
		const int y,
		int *render_y_in_out,
		struct rect *r,
		struct content_redraw_data *data,
		const struct redraw_context *ctx)
{
	struct treeview_node_style *style = &plot_style_odd;
	enum treeview_resource_id res = TREE_RES_CONTENT;
	int baseline = (tree_g.line_height * 3 + 2) / 4;
	plot_font_style_t *infotext_style;
	treeview_node *root = tree->root;
	treeview_node *node = tree->root;
	int render_y = *render_y_in_out;
	plot_font_style_t *text_style;
	plot_style_t *bg_style;
	int sel_min, sel_max;
	uint32_t count = 0;
	struct rect rect;
	int inset;
	int x0;

	if (tree->drag.start.y > tree->drag.prev.y) {
		sel_min = tree->drag.prev.y;
		sel_max = tree->drag.start.y;
	} else {
		sel_min = tree->drag.start.y;
		sel_max = tree->drag.prev.y;
	}

	while (node != NULL) {
		struct treeview_node_entry *entry;
		struct bitmap *furniture;
		bool invert_selection;
		treeview_node *next;
		int height;
		int i;

		next = node->children;

		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->next_sib == NULL) {
				node = node->parent;
			}

			if (node == root)
				break;

			node = node->next_sib;
		}

		assert(node != NULL);
		assert(node != root);
		assert(node->type == TREE_NODE_FOLDER ||
		       node->type == TREE_NODE_ENTRY);

		if (node->type == TREE_NODE_FOLDER ||
				!(node->flags & TV_NFLAGS_MATCHED)) {
			continue;
		}

		count++;
		inset = x + tree_g.window_padding;
		height = node->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 (tree->drag.type == TV_DRAG_SELECTION &&
		    (render_y + height >= sel_min &&
		     render_y < sel_max)) {
			invert_selection = true;
		} else {
			invert_selection = false;
		}
		if ((node->flags & TV_NFLAGS_SELECTED && !invert_selection) ||
		    (!(node->flags & TV_NFLAGS_SELECTED) &&
		     invert_selection)) {
			bg_style = &style->sbg;
			text_style = &style->stext;
			infotext_style = &style->sitext;
			furniture = (node->flags & TV_NFLAGS_EXPANDED) ?
				style->furn[TREE_FURN_CONTRACT].sel :
				style->furn[TREE_FURN_EXPAND].sel;
		} else {
			bg_style = &style->bg;
			text_style = &style->text;
			infotext_style = &style->itext;
			furniture = (node->flags & TV_NFLAGS_EXPANDED) ?
				style->furn[TREE_FURN_CONTRACT].bmp :
				style->furn[TREE_FURN_EXPAND].bmp;
		}

		/* Render background */
		rect.x0 = r->x0;
		rect.y0 = render_y;
		rect.x1 = r->x1;
		rect.y1 = render_y + height;
		ctx->plot->rectangle(ctx, bg_style, &rect);

		/* Render toggle */
		ctx->plot->bitmap(ctx,
				furniture,
				inset,
				render_y + tree_g.line_height / 4,
				style->furn[TREE_FURN_EXPAND].size,
				style->furn[TREE_FURN_EXPAND].size,
				bg_style->fill_colour,
				BITMAPF_NONE);

		/* Render icon */
		if (node->type == TREE_NODE_ENTRY) {
			res = TREE_RES_CONTENT;
		} else if (node->flags & TV_NFLAGS_SPECIAL) {
			res = TREE_RES_FOLDER_SPECIAL;
		} else {
			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, ctx);
		}

		/* Render text */
		x0 = inset + tree_g.step_width + tree_g.icon_step;
		ctx->plot->text(ctx,
				text_style,
				x0, render_y + baseline,
				node->text.data,
				node->text.len);

		/* 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 & TV_NFLAGS_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;

				ctx->plot->text(ctx,
						infotext_style,
						x0 + max_width - ef->value.width - tree_g.step_width,
						render_y + baseline,
						ef->value.data,
						ef->value.len);

				ctx->plot->text(ctx,
						infotext_style,
						x0 + max_width,
						render_y + baseline,
						entry->fields[i].value.data,
						entry->fields[i].value.len);
			} else {
				ctx->plot->text(ctx,
						infotext_style,
						x0, render_y + baseline,
						entry->fields[i].value.data,
						entry->fields[i].value.len);
			}

			/* Rendered the expanded entry field */
			render_y += tree_g.line_height;
		}

		/* Finished rendering expanded entry */

		if (render_y > r->y1) {
			/* Passed the bottom of what's in the clip region.
			 * Done. */
			break;
		}
	}

	*render_y_in_out = render_y;
}


/* Exported interface, documented in treeview.h */
void
treeview_redraw(treeview *tree,
		const int x,
		const int y,
		struct rect *clip,
		const struct redraw_context *ctx)
{
	struct redraw_context new_ctx = *ctx;
	struct content_redraw_data data;
	struct rect r;
	struct rect rect;
	int render_y = y;

	assert(tree != NULL);
	assert(tree->root != NULL);
	assert(tree->root->flags & TV_NFLAGS_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(&new_ctx, &r);

	/* Setup common content redraw data */
	data.width = tree_g.icon_size;
	data.height = tree_g.icon_size;
	data.scale = 1;
	data.repeat_x = false;
	data.repeat_y = false;

	if (tree->flags & TREEVIEW_SEARCHABLE) {
		if (render_y < r.y1) {
			enum treeview_resource_id icon = TREE_RES_SEARCH;

			/* Fill the blank area at the bottom */
			rect.x0 = r.x0;
			rect.y0 = render_y;
			rect.x1 = r.x1;
			rect.y1 = render_y + tree_g.line_height;
			new_ctx.plot->rectangle(&new_ctx, &plot_style_even.bg,
					&rect);

			if (treeview_res[icon].ready) {
				/* Icon resource is available */
				data.x = tree_g.window_padding;
				data.y = render_y + ((tree_g.line_height -
						treeview_res[icon].height + 1) /
						2);
				data.background_colour = plot_style_even.bg.
						fill_colour;

				content_redraw(treeview_res[icon].c,
						&data, &r, &new_ctx);
			}

			textarea_redraw(tree->search.textarea,
					x + tree_g.window_padding +
							tree_g.icon_step, y,
					plot_style_even.bg.fill_colour, 1.0,
					&r, &new_ctx);
		}
		render_y += tree_g.line_height;
	}

	/* Render the treeview data */
	if (tree->search.search == true) {
		treeview_redraw_search(tree, x, y,
				&render_y, &r, &data, &new_ctx);
	} else {
		treeview_redraw_tree(tree, x, y,
				&render_y, &r, &data, &new_ctx);
	}

	if (render_y < r.y1) {
		/* Fill the blank area at the bottom */
		rect.x0 = r.x0;
		rect.y0 = render_y;
		rect.x1 = r.x1;
		rect.y1 = r.y1;
		new_ctx.plot->rectangle(&new_ctx, &plot_style_even.bg, &rect);
	}

	/* All normal treeview rendering is done; render any overlays */
	if ((tree->move.target_pos != TV_TARGET_NONE) &&
	    (treeview_res[TREE_RES_ARROW].ready)) {
		/* Got a MOVE drag; render move indicator arrow */
		data.x = tree->move.target_area.x0 + x;
		data.y = tree->move.target_area.y0 + y;
		data.background_colour = plot_style_even.bg.fill_colour;

		content_redraw(treeview_res[TREE_RES_ARROW].c, &data, &r, &new_ctx);

	} else if (tree->edit.textarea != NULL) {
		/* Edit in progress; render textarea */
		textarea_redraw(tree->edit.textarea,
				tree->edit.x + x, tree->edit.y + y,
				plot_style_even.bg.fill_colour, 1.0,
				&r, &new_ctx);
	}

	/* Rendering complete */
	if (ctx->plot->option_knockout) {
		knockout_plot_end(ctx);
	}
}


/**
 * context for treeview selection
 */
struct treeview_selection_walk_data {
	enum {
		TREEVIEW_WALK_HAS_SELECTION,
		TREEVIEW_WALK_GET_FIRST_SELECTED,
		TREEVIEW_WALK_CLEAR_SELECTION,
		TREEVIEW_WALK_SELECT_ALL,
		TREEVIEW_WALK_COMMIT_SELECT_DRAG,
		TREEVIEW_WALK_DELETE_SELECTION,
		TREEVIEW_WALK_PROPAGATE_SELECTION,
		TREEVIEW_WALK_YANK_SELECTION,
		TREEVIEW_WALK_COPY_SELECTION
	} purpose;
	union {
		bool has_selection;
		struct {
			bool required;
			struct rect *rect;
		} redraw;
		struct {
			int sel_min;
			int sel_max;
		} drag;
		struct {
			treeview_node *prev;
		} yank;
		struct {
			treeview_node *n;
		} first;
		struct {
			char *text;
			uint32_t len;
		} copy;
	} data;
	int current_y;
	treeview *tree;
};


/**
 * Treewalk node callback for handling selection related actions.
 *
 * \param n current node
 * \param ctx node selection context
 * \param skip_children flag to allow children to be skipped
 * \param end flag to allow iteration to be finished early.
 * \return NSERROR_OK on success else error code.
 */
static nserror
treeview_node_selection_walk_cb(treeview_node *n,
				void *ctx,
				bool *skip_children,
				bool *end)
{
	struct treeview_selection_walk_data *sw = ctx;
	int height;
	bool changed = false;
	nserror err;

	height = (n->type == TREE_NODE_ENTRY) ? n->height : tree_g.line_height;
	sw->current_y += height;

	switch (sw->purpose) {
	case TREEVIEW_WALK_HAS_SELECTION:
		if (n->flags & TV_NFLAGS_SELECTED) {
			sw->data.has_selection = true;
			*end = true; /* Can abort tree walk */
			return NSERROR_OK;
		}
		break;

	case TREEVIEW_WALK_GET_FIRST_SELECTED:
		if (n->flags & TV_NFLAGS_SELECTED) {
			sw->data.first.n = n;
			*end = true; /* Can abort tree walk */
			return NSERROR_OK;
		}
		break;

	case TREEVIEW_WALK_DELETE_SELECTION:
		if (n->flags & TV_NFLAGS_SELECTED) {
			err = treeview_delete_node_internal(sw->tree, n, true,
							    TREE_OPTION_NONE);
			if (err != NSERROR_OK) {
				return err;
			}
			*skip_children = true;
			changed = true;
		}
		break;

	case TREEVIEW_WALK_PROPAGATE_SELECTION:
		if (n->parent != NULL &&
		    n->parent->flags & TV_NFLAGS_SELECTED &&
		    !(n->flags & TV_NFLAGS_SELECTED)) {
			n->flags ^= TV_NFLAGS_SELECTED;
			changed = true;
		}
		break;

	case TREEVIEW_WALK_CLEAR_SELECTION:
		if (n->flags & TV_NFLAGS_SELECTED) {
			n->flags ^= TV_NFLAGS_SELECTED;
			changed = true;
		}
		break;

	case TREEVIEW_WALK_SELECT_ALL:
		if (!(n->flags & TV_NFLAGS_SELECTED)) {
			n->flags ^= TV_NFLAGS_SELECTED;
			changed = true;
		}
		break;

	case TREEVIEW_WALK_COMMIT_SELECT_DRAG:
		if (sw->current_y >= sw->data.drag.sel_min &&
		    sw->current_y - height <
		    sw->data.drag.sel_max) {
			n->flags ^= TV_NFLAGS_SELECTED;
		}
		return NSERROR_OK;

	case TREEVIEW_WALK_YANK_SELECTION:
		if (n->flags & TV_NFLAGS_SELECTED) {
			treeview_node *p = n->parent;
			int h = 0;

			if (treeview_unlink_node(n))
				h = n->height;

			/* Reduce ancestor heights */
			while (p != NULL && p->flags & TV_NFLAGS_EXPANDED) {
				p->height -= h;
				p = p->parent;
			}
			if (sw->data.yank.prev == NULL) {
				sw->tree->move.root = n;
				n->parent = NULL;
				n->prev_sib = NULL;
				n->next_sib = NULL;
			} else {
				n->parent = NULL;
				n->prev_sib = sw->data.yank.prev;
				n->next_sib = NULL;
				sw->data.yank.prev->next_sib = n;
			}
			sw->data.yank.prev = n;

			*skip_children = true;
		}
		break;

	case TREEVIEW_WALK_COPY_SELECTION:
		if (n->flags & TV_NFLAGS_SELECTED &&
		    n->type == TREE_NODE_ENTRY) {
			int i;
			char *temp;
			uint32_t len;
			const char *text;
			struct treeview_field *ef;
			struct treeview_text *val;

			for (i = 0; i < sw->tree->n_fields; i++) {
				ef = &(sw->tree->fields[i]);

				if (!(ef->flags & TREE_FLAG_COPY_TEXT)) {
					continue;
				}
				val = treeview_get_text_for_field(sw->tree,
								  n, i);
				text = val->data;
				len  = val->len;

				temp = realloc(sw->data.copy.text,
					       sw->data.copy.len + len + 1);
				if (temp == NULL) {
					free(sw->data.copy.text);
					sw->data.copy.text = NULL;
					sw->data.copy.len = 0;
					return NSERROR_NOMEM;
				}

				if (sw->data.copy.len != 0) {
					temp[sw->data.copy.len - 1] = '\n';
				}
				memcpy(temp + sw->data.copy.len, text, len);
				temp[sw->data.copy.len + len] = '\0';
				sw->data.copy.len += len + 1;
				sw->data.copy.text = temp;
			}
		}
		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 NSERROR_OK;
}


/* Exported interface, documented in treeview.h */
bool treeview_has_selection(treeview *tree)
{
	struct treeview_selection_walk_data sw;

	sw.purpose = TREEVIEW_WALK_HAS_SELECTION;
	sw.data.has_selection = false;

	treeview_walk_internal(tree, tree->root,
			TREEVIEW_WALK_MODE_DISPLAY, NULL,
			treeview_node_selection_walk_cb, &sw);

	return sw.data.has_selection;
}


/**
 * Get first selected node (in any)
 *
 * \param tree Treeview object in which to create folder
 * \return the first selected treeview node, or NULL
 */
static treeview_node * treeview_get_first_selected(treeview *tree)
{
	struct treeview_selection_walk_data sw;

	sw.purpose = TREEVIEW_WALK_GET_FIRST_SELECTED;
	sw.data.first.n = NULL;

	treeview_walk_internal(tree, tree->root,
			TREEVIEW_WALK_MODE_DISPLAY, NULL,
			treeview_node_selection_walk_cb, &sw);

	return sw.data.first.n;
}


/* Exported interface, documented in treeview.h */
enum treeview_node_type treeview_get_selection(treeview *tree,
					       void **node_data)
{
	treeview_node *n;

	assert(tree != NULL);

	n = treeview_get_first_selected(tree);

	if (n != NULL && n->type & (TREE_NODE_ENTRY | TREE_NODE_FOLDER)) {
		*node_data = n->client_data;
		return n->type;
	}

	*node_data = NULL;
	return TREE_NODE_NONE;
}


/**
 * Clear any selection in a treeview
 *
 * \param tree Treeview object to clear selection in
 * \param rect Redraw rectangle (if redraw required)
 * \return true iff redraw required
 */
static bool treeview_clear_selection(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 = (tree->flags & TREEVIEW_SEARCHABLE) ?
			tree_g.line_height : 0;

	treeview_walk_internal(tree, tree->root,
			TREEVIEW_WALK_MODE_DISPLAY, NULL,
			treeview_node_selection_walk_cb, &sw);

	return sw.data.redraw.required;
}


/**
 * Select all in a treeview
 *
 * \param tree Treeview object to select all in
 * \param rect Redraw rectangle (if redraw required)
 * \return true iff redraw required
 */
static bool treeview_select_all(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 = (tree->flags & TREEVIEW_SEARCHABLE) ?
			tree_g.line_height : 0;

	treeview_walk_internal(tree, tree->root,
			TREEVIEW_WALK_MODE_DISPLAY, NULL,
			treeview_node_selection_walk_cb, &sw);

	return sw.data.redraw.required;
}


/**
 * Commit a current selection drag, modifying the node's selection state.
 *
 * \param tree Treeview object to commit drag selection in
 */
static void treeview_commit_selection_drag(treeview *tree)
{
	struct treeview_selection_walk_data sw;

	sw.purpose = TREEVIEW_WALK_COMMIT_SELECT_DRAG;
	sw.current_y = (tree->flags & TREEVIEW_SEARCHABLE) ?
			tree_g.line_height : 0;

	if (tree->drag.start.y > tree->drag.prev.y) {
		sw.data.drag.sel_min = tree->drag.prev.y;
		sw.data.drag.sel_max = tree->drag.start.y;
	} else {
		sw.data.drag.sel_min = tree->drag.start.y;
		sw.data.drag.sel_max = tree->drag.prev.y;
	}

	treeview_walk_internal(tree, tree->root,
			TREEVIEW_WALK_MODE_DISPLAY, NULL,
			treeview_node_selection_walk_cb, &sw);
}


/**
 * Yank a selection to the node move list.
 *
 * \param tree Treeview object to yank selection from
 */
static void treeview_move_yank_selection(treeview *tree)
{
	struct treeview_selection_walk_data sw;

	sw.purpose = TREEVIEW_WALK_YANK_SELECTION;
	sw.data.yank.prev = NULL;
	sw.tree = tree;

	treeview_walk_internal(tree, tree->root,
			TREEVIEW_WALK_MODE_DISPLAY, NULL,
			treeview_node_selection_walk_cb, &sw);
}


/**
 * Copy a selection to the clipboard.
 *
 * \param tree Treeview object to yank selection from
 */
static void treeview_copy_selection(treeview *tree)
{
	struct treeview_selection_walk_data sw;
	nserror err;

	sw.purpose = TREEVIEW_WALK_COPY_SELECTION;
	sw.data.copy.text = NULL;
	sw.data.copy.len = 0;
	sw.tree = tree;

	err = treeview_walk_internal(tree, tree->root,
			TREEVIEW_WALK_MODE_DISPLAY, NULL,
			treeview_node_selection_walk_cb, &sw);
	if (err != NSERROR_OK) {
		return;
	}

	if (sw.data.copy.text != NULL) {
		guit->clipboard->set(sw.data.copy.text,
				     sw.data.copy.len - 1, NULL, 0);
		free(sw.data.copy.text);
	}
}


/**
 * Delete a selection.
 *
 * \param tree Treeview object to delete selected nodes from
 * \param rect Updated to redraw rectangle
 * \return true iff redraw required.
 */
static bool treeview_delete_selection(treeview *tree, struct rect *rect)
{
	struct treeview_selection_walk_data sw;

	assert(tree != NULL);
	assert(tree->root != NULL);

	rect->x0 = 0;
	rect->y0 = 0;
	rect->x1 = REDRAW_MAX;
	rect->y1 = tree->root->height;

	sw.purpose = TREEVIEW_WALK_DELETE_SELECTION;
	sw.data.redraw.required = false;
	sw.data.redraw.rect = rect;
	sw.current_y = 0;
	sw.tree = tree;

	treeview_walk_internal(tree, tree->root,
			TREEVIEW_WALK_MODE_DISPLAY, NULL,
			treeview_node_selection_walk_cb, &sw);

	return sw.data.redraw.required;
}


/**
 * Propagate selection to visible descendants of selected nodes.
 *
 * \param tree Treeview object to propagate selection in
 * \param rect Redraw rectangle (if redraw required)
 * \return true iff redraw required
 */
static bool treeview_propagate_selection(treeview *tree, struct rect *rect)
{
	struct treeview_selection_walk_data sw;

	assert(tree != NULL);
	assert(tree->root != NULL);

	rect->x0 = 0;
	rect->y0 = 0;
	rect->x1 = REDRAW_MAX;
	rect->y1 = 0;

	sw.purpose = TREEVIEW_WALK_PROPAGATE_SELECTION;
	sw.data.redraw.required = false;
	sw.data.redraw.rect = rect;
	sw.current_y = 0;
	sw.tree = tree;

	treeview_walk_internal(tree, tree->root,
			TREEVIEW_WALK_MODE_DISPLAY, NULL,
			treeview_node_selection_walk_cb, &sw);

	return sw.data.redraw.required;
}


/**
 * Move a selection according to the current move drag.
 *
 * \param tree Treeview object to move selected nodes in
 * \param rect Redraw rectangle
 * \return NSERROR_OK on success else appropriate error code
 */
static nserror treeview_move_selection(treeview *tree, struct rect *rect)
{
	treeview_node *node, *next, *parent;
	treeview_node *relation;
	enum treeview_relationship relationship;
	int height;

	assert(tree != NULL);
	assert(tree->root != NULL);
	assert(tree->root->children != NULL);
	assert(tree->move.target_pos != TV_TARGET_NONE);

	height = tree->root->height;

	/* Identify target location */
	switch (tree->move.target_pos) {
	case TV_TARGET_ABOVE:
		if (tree->move.target == NULL) {
			/* Target: After last child of root */
			relation = tree->root->children;
			while (relation->next_sib != NULL) {
				relation = relation->next_sib;
			}
			relationship = TREE_REL_NEXT_SIBLING;

		} else if (tree->move.target->prev_sib != NULL) {
			/* Target: After previous sibling */
			relation = tree->move.target->prev_sib;
			relationship = TREE_REL_NEXT_SIBLING;

		} else {
			/* Target: Target: First child of parent */
			assert(tree->move.target->parent != NULL);
			relation = tree->move.target->parent;
			relationship = TREE_REL_FIRST_CHILD;
		}
		break;

	case TV_TARGET_INSIDE:
		assert(tree->move.target != NULL);
		relation = tree->move.target;
		relationship = TREE_REL_FIRST_CHILD;
		break;

	case TV_TARGET_BELOW:
		assert(tree->move.target != NULL);
		relation = tree->move.target;
		relationship = TREE_REL_NEXT_SIBLING;
		break;

	default:
		NSLOG(netsurf, INFO, "Bad drop target for move.");
		return NSERROR_BAD_PARAMETER;
	}

	if (relationship == TREE_REL_FIRST_CHILD) {
		parent = relation;
	} else {
		parent = relation->parent;
	}

	/* The node that we're moving selection to can't itself be selected */
	assert(!(relation->flags & TV_NFLAGS_SELECTED));

	/* Move all selected nodes from treeview to tree->move.root */
	treeview_move_yank_selection(tree);

	/* Move all nodes on tree->move.root to target location */
	for (node = tree->move.root; node != NULL; node = next) {
		next = node->next_sib;

		if (!(parent->flags & TV_NFLAGS_EXPANDED)) {
			if (node->flags & TV_NFLAGS_EXPANDED)
				treeview_node_contract_internal(tree, node);
			node->flags &= ~TV_NFLAGS_SELECTED;
		}

		treeview_insert_node(tree, node, relation, relationship);

		relation = node;
		relationship = TREE_REL_NEXT_SIBLING;
	}
	tree->move.root = NULL;

	/* Tell window, if height has changed */
	if (height != tree->root->height)
		treeview__cw_update_size(tree, -1, tree->root->height);

	/* TODO: Deal with redraw area properly */
	rect->x0 = 0;
	rect->y0 = 0;
	rect->x1 = REDRAW_MAX;
	rect->y1 = REDRAW_MAX;

	return NSERROR_OK;
}


/**
 * context for treeview launch action
 */
struct treeview_launch_walk_data {
	int selected_depth;
	treeview *tree;
};


/**
 * Treewalk node walk backward callback for tracking folder selection.
 */
static nserror
treeview_node_launch_walk_bwd_cb(treeview_node *n, void *ctx, bool *end)
{
	struct treeview_launch_walk_data *lw = ctx;

	if (n->type == TREE_NODE_FOLDER && n->flags == TV_NFLAGS_SELECTED) {
		lw->selected_depth--;
	}

	return NSERROR_OK;
}


/**
 * Treewalk node walk forward callback for launching nodes.
 *
 * \param n current node
 * \param ctx node launch context
 * \param skip_children flag to allow children to be skipped
 * \param end flag to allow iteration to be finished early.
 * \return NSERROR_OK on success else error code.
 */
static nserror
treeview_node_launch_walk_fwd_cb(treeview_node *n,
				 void *ctx,
				 bool *skip_children,
				 bool *end)
{
	struct treeview_launch_walk_data *lw = ctx;
	nserror ret = NSERROR_OK;

	if (n->type == TREE_NODE_FOLDER && n->flags & TV_NFLAGS_SELECTED) {
		lw->selected_depth++;

	} else if (n->type == TREE_NODE_ENTRY &&
		   (n->flags & TV_NFLAGS_SELECTED ||
		    lw->selected_depth > 0)) {
		struct treeview_node_msg msg;
		msg.msg = TREE_MSG_NODE_LAUNCH;
		msg.data.node_launch.mouse = BROWSER_MOUSE_HOVER;
		ret = lw->tree->callbacks->entry(msg, n->client_data);
	}

	return ret;
}


/**
 * Launch a selection.
 *
 * \note Selected entries are launched. Entries that are descendants
 *        of selected folders are also launched.
 *
 * \param tree Treeview object to launch selected nodes in
 * \return NSERROR_OK on success, appropriate error otherwise
 */
static nserror treeview_launch_selection(treeview *tree)
{
	struct treeview_launch_walk_data lw;

	assert(tree != NULL);
	assert(tree->root != NULL);

	lw.selected_depth = 0;
	lw.tree = tree;

	return treeview_walk_internal(tree, tree->root,
			TREEVIEW_WALK_MODE_LOGICAL_COMPLETE,
			treeview_node_launch_walk_bwd_cb,
			treeview_node_launch_walk_fwd_cb, &lw);
}


/* Exported interface, documented in treeview.h */
nserror
treeview_get_relation(treeview *tree,
		      treeview_node **relation,
		      enum treeview_relationship *rel,
		      bool at_y,
		      int y)
{
	treeview_node *n;

	assert(tree != NULL);

	if (at_y) {
		n = treeview_y_node(tree, y);

	} else {
		n = treeview_get_first_selected(tree);
	}

	if (n != NULL && n->parent != NULL) {
		if (n == n->parent->children) {
			/* First child */
			*relation = n->parent;
			*rel = TREE_REL_FIRST_CHILD;
		} else {
			/* Not first child */
			*relation = n->prev_sib;
			*rel = TREE_REL_NEXT_SIBLING;
		}
	} else {
		if (tree->root->children == NULL) {
			/* First child of root */
			*relation = tree->root;
			*rel = TREE_REL_FIRST_CHILD;
		} else {
			/* Last child of root */
			n = tree->root->children;
			while (n->next_sib != NULL)
				n = n->next_sib;
			*relation = n;
			*rel = TREE_REL_NEXT_SIBLING;
		}
	}

	return NSERROR_OK;
}


/**
 * context for treeview keyboard action
 */
struct treeview_nav_state {
	treeview *tree;
	treeview_node *prev;
	treeview_node *curr;
	treeview_node *next;
	treeview_node *last;
	int n_selected;
	int prev_n_selected;
};


/**
 * Treewalk node callback for handling mouse action.
 *
 * \param node current node
 * \param ctx node context
 * \param skip_children flag to allow children to be skipped
 * \param end flag to allow iteration to be finished early.
 * \return NSERROR_OK on success else error code.
 */
static nserror
treeview_node_nav_cb(treeview_node *node,
		     void *ctx,
		     bool *skip_children,
		     bool *end)
{
	struct treeview_nav_state *ns = ctx;

	if (node == ns->tree->root)
		return NSERROR_OK;

	if (node->flags & TV_NFLAGS_SELECTED) {
		ns->n_selected++;
		if (ns->curr == NULL) {
			ns->curr = node;
		}

	} else {
		if (ns->n_selected == 0) {
			ns->prev = node;

		} else if (ns->prev_n_selected < ns->n_selected) {
			ns->next = node;
			ns->prev_n_selected = ns->n_selected;
		}
	}
	ns->last = node;

	return NSERROR_OK;
}


/**
 * Handle keyboard navigation.
 *
 * \note Selected entries are launched.
 *       Entries that are descendants of selected folders are also launched.
 *
 * \param tree Treeview object to launch selected nodes in
 * \param key The ucs4 character codepoint
 * \param rect Updated to redraw rectangle
 * \return true if treeview needs redraw, false otherwise
 */
static bool
treeview_keyboard_navigation(treeview *tree, uint32_t key, struct rect *rect)
{
	struct treeview_nav_state ns = {
		.tree = tree,
		.prev = NULL,
		.curr = NULL,
		.next = NULL,
		.last = NULL,
		.n_selected = 0,
		.prev_n_selected = 0
	};
	int search_height = (tree->flags & TREEVIEW_SEARCHABLE) ?
			tree_g.line_height : 0;
	int h = treeview__get_display_height(tree) + search_height;
	bool redraw = false;

	/* Fill out the nav. state struct, by examining the current selection
	 * state */
	treeview_walk_internal(tree, tree->root,
			TREEVIEW_WALK_MODE_DISPLAY, NULL,
			treeview_node_nav_cb, &ns);

	if (tree->search.search == false) {
		if (ns.next == NULL)
			ns.next = tree->root->children;
		if (ns.prev == NULL)
			ns.prev = ns.last;
	}

	/* Clear any existing selection */
	redraw = treeview_clear_selection(tree, rect);

	switch (key) {
	case NS_KEY_LEFT:
		if (tree->search.search == true) {
			break;
		}
		if (ns.curr != NULL &&
		    ns.curr->parent != NULL &&
		    ns.curr->parent->type != TREE_NODE_ROOT) {
			/* Step to parent */
			ns.curr->parent->flags |= TV_NFLAGS_SELECTED;

		} else if (ns.curr != NULL && tree->root->children != NULL) {
			/* Select first node in tree */
			tree->root->children->flags |= TV_NFLAGS_SELECTED;
		}
		break;

	case NS_KEY_RIGHT:
		if (ns.curr != NULL) {
			if (!(ns.curr->flags & TV_NFLAGS_EXPANDED)) {
				/* Toggle node to expanded */
				treeview_node_expand_internal(tree, ns.curr);
				if (ns.curr->children != NULL) {
					/* Step to first child */
					ns.curr->children->flags |=
						TV_NFLAGS_SELECTED;
				} else {
					/* Retain current node selection */
					ns.curr->flags |= TV_NFLAGS_SELECTED;
				}
			} else {
				/* Toggle node to contracted */
				treeview_node_contract_internal(tree, ns.curr);
				/* Retain current node selection */
				ns.curr->flags |= TV_NFLAGS_SELECTED;
			}

		} else if (ns.curr != NULL) {
			/* Retain current node selection */
			ns.curr->flags |= TV_NFLAGS_SELECTED;
		}
		break;

	case NS_KEY_UP:
		if (ns.prev != NULL) {
			/* Step to previous node */
			ns.prev->flags |= TV_NFLAGS_SELECTED;
		}
		break;

	case NS_KEY_DOWN:
		if (ns.next != NULL) {
			/* Step to next node */
			ns.next->flags |= TV_NFLAGS_SELECTED;
		}
		break;

	default:
		break;
	}

	/* TODO: Deal with redraw area properly */
	rect->x0 = 0;
	rect->y0 = 0;
	rect->x1 = REDRAW_MAX;
	if (treeview__get_display_height(tree) + search_height > h)
		rect->y1 = treeview__get_display_height(tree) + search_height;
	else
		rect->y1 = h;
	redraw = true;

	return redraw;
}


/* Exported interface, documented in treeview.h */
bool treeview_keypress(treeview *tree, uint32_t key)
{
	struct rect r;	/**< Redraw rectangle */
	bool redraw = false;

	assert(tree != NULL);

	/* Pass to any textarea, if editing in progress */
	if (tree->edit.textarea != NULL) {
		switch (key) {
		case NS_KEY_ESCAPE:
			treeview_edit_cancel(tree, true);
			return true;
		case NS_KEY_NL:
		case NS_KEY_CR:
			treeview_edit_done(tree);
			return true;
		default:
			return textarea_keypress(tree->edit.textarea, key);
		}
	} else if (tree->search.active == true) {
		switch (key) {
		case NS_KEY_ESCAPE:
			treeview__search_cancel(tree, false);
			return true;
		case NS_KEY_NL:
		case NS_KEY_CR:
			return true;
		default:
			return textarea_keypress(tree->search.textarea, key);
		}
	}

	/* Keypress to be handled by treeview */
	switch (key) {
	case NS_KEY_SELECT_ALL:
		redraw = treeview_select_all(tree, &r);
		break;
	case NS_KEY_COPY_SELECTION:
		treeview_copy_selection(tree);
		break;
	case NS_KEY_DELETE_LEFT:
	case NS_KEY_DELETE_RIGHT:
		redraw = treeview_delete_selection(tree, &r);

		if (tree->flags & TREEVIEW_DEL_EMPTY_DIRS) {
			int h = tree->root->height;
			/* Delete any empty nodes */
			treeview_delete_empty_nodes(tree, false);

			/* Inform front end of change in dimensions */
			if (tree->root->height != h) {
				r.y0 = 0;
				treeview__cw_update_size(tree, -1,
							 tree->root->height);
			}
		}
		break;
	case NS_KEY_CR:
	case NS_KEY_NL:
		treeview_launch_selection(tree);
		break;
	case NS_KEY_ESCAPE:
	case NS_KEY_CLEAR_SELECTION:
		redraw = treeview_clear_selection(tree, &r);
		break;
	case NS_KEY_LEFT:
	case NS_KEY_RIGHT:
	case NS_KEY_UP:
	case NS_KEY_DOWN:
		redraw = treeview_keyboard_navigation(tree, key, &r);
		break;
	default:
		return false;
	}

	if (redraw) {
		treeview__cw_invalidate_area(tree, &r);
	}

	return true;
}


/**
 * Set the drag&drop drop indicator
 *
 * \param tree		Treeview object to set node indicator in
 * \param need_redraw	True iff we already have a redraw region
 * \param target	The treeview node with mouse pointer over it
 * \param node_height	The height of node
 * \param node_y	The Y coord of the top of target node
 * \param mouse_y	Y coord of mouse position
 * \param rect		Redraw rectangle (if redraw required)
 * \return true iff redraw required
 */
static bool
treeview_set_move_indicator(treeview *tree,
			    bool need_redraw,
			    treeview_node *target,
			    int node_height,
			    int node_y,
			    int mouse_y,
			    struct rect *rect)
{
	treeview_node *orig = target;
	enum treeview_target_pos target_pos;
	int mouse_pos = mouse_y - node_y;
	int x;

	assert(tree != NULL);
	assert(tree->root != NULL);
	assert(tree->root->children != NULL);
	assert(target != NULL);

	if (target->flags & TV_NFLAGS_SELECTED) {
		/* Find top selected ancestor */
		while (target->parent &&
		       target->parent->flags & TV_NFLAGS_SELECTED) {
			target = target->parent;
		}

		/* Find top adjacent selected sibling */
		while (target->prev_sib &&
		       target->prev_sib->flags & TV_NFLAGS_SELECTED) {
			target = target->prev_sib;
		}
		target_pos = TV_TARGET_ABOVE;

	} else switch (target->type) {
		case TREE_NODE_FOLDER:
			if (mouse_pos <= node_height / 4) {
				target_pos = TV_TARGET_ABOVE;
			} else if (mouse_pos <= (3 * node_height) / 4 ||
				   target->flags & TV_NFLAGS_EXPANDED) {
				target_pos = TV_TARGET_INSIDE;
			} else {
				target_pos = TV_TARGET_BELOW;
			}
			break;

		case TREE_NODE_ENTRY:
			if (mouse_pos <= node_height / 2) {
				target_pos = TV_TARGET_ABOVE;
			} else {
				target_pos = TV_TARGET_BELOW;
			}
			break;

		default:
			assert(target->type != TREE_NODE_ROOT);
			return false;
		}

	if (target_pos == tree->move.target_pos &&
	    target == tree->move.target) {
		/* No change */
		return need_redraw;
	}

	if (tree->move.target_pos != TV_TARGET_NONE) {
		/* Need to clear old indicator position */
		if (need_redraw) {
			if (rect->x0 > tree->move.target_area.x0)
				rect->x0 = tree->move.target_area.x0;
			if (tree->move.target_area.x1 > rect->x1)
				rect->x1 = tree->move.target_area.x1;
			if (rect->y0 > tree->move.target_area.y0)
				rect->y0 = tree->move.target_area.y0;
			if (tree->move.target_area.y1 > rect->y1)
				rect->y1 = tree->move.target_area.y1;
		} else {
			*rect = tree->move.target_area;
			need_redraw = true;
		}
	}

	/* Offset for ABOVE / BELOW */
	if (target_pos == TV_TARGET_ABOVE) {
		if (target != orig) {
			node_y = treeview_node_y(tree, target);
		}
		node_y -= (tree_g.line_height + 1) / 2;
	} else if (target_pos == TV_TARGET_BELOW) {
		node_y += node_height - (tree_g.line_height + 1) / 2;
	}

	/* Oftsets are all relative to centred (INSIDE) */
	node_y += (tree_g.line_height -
		   treeview_res[TREE_RES_ARROW].height + 1) / 2;

	x = target->inset + tree_g.move_offset;

	/* Update target details */
	tree->move.target = target;
	tree->move.target_pos = target_pos;
	tree->move.target_area.x0 = x;
	tree->move.target_area.y0 = node_y;
	tree->move.target_area.x1 = tree_g.icon_size + x;
	tree->move.target_area.y1 = tree_g.icon_size + node_y;

	if (target_pos != TV_TARGET_NONE) {
		/* Need to draw new indicator position */
		if (need_redraw) {
			if (rect->x0 > tree->move.target_area.x0)
				rect->x0 = tree->move.target_area.x0;
			if (tree->move.target_area.x1 > rect->x1)
				rect->x1 = tree->move.target_area.x1;
			if (rect->y0 > tree->move.target_area.y0)
				rect->y0 = tree->move.target_area.y0;
			if (tree->move.target_area.y1 > rect->y1)
				rect->y1 = tree->move.target_area.y1;
		} else {
			*rect = tree->move.target_area;
			need_redraw = true;
		}
	}

	return need_redraw;
}


/**
 * Callback for textarea_create, in desktop/treeview.h
 *
 * \param data treeview context
 * \param msg textarea message
 */
static void treeview_textarea_callback(void *data, struct textarea_msg *msg)
{
	treeview *tree = data;
	struct rect *r;

	switch (msg->type) {
	case TEXTAREA_MSG_DRAG_REPORT:
		if (msg->data.drag == TEXTAREA_DRAG_NONE) {
			/* Textarea drag finished */
			tree->drag.type = TV_DRAG_NONE;
		} else {
			/* Textarea drag started */
			tree->drag.type = TV_DRAG_TEXTAREA;
		}
		treeview__cw_drag_status(tree, tree->drag.type);
		break;

	case TEXTAREA_MSG_REDRAW_REQUEST:
		r = &msg->data.redraw;
		r->x0 += tree->edit.x;
		r->y0 += tree->edit.y;
		r->x1 += tree->edit.x;
		r->y1 += tree->edit.y;

		/* Redraw the textarea */
		treeview__cw_invalidate_area(tree, r);
		break;

	default:
		break;
	}
}


/**
 * Start edit of node field, at given y-coord, if editable
 *
 * \param tree Treeview object to consider editing in
 * \param n The treeview node to try editing
 * \param node_y The Y coord of the top of n
 * \param mouse_x X coord of mouse position
 * \param mouse_y Y coord of mouse position
 * \param rect Redraw rectangle (if redraw required)
 * \return true iff redraw required
 */
static bool
treeview_edit_node_at_point(treeview *tree,
			    treeview_node *n,
			    int node_y,
			    int mouse_x,
			    int mouse_y,
			    struct rect *rect)
{
	struct treeview_text *field_data = NULL;
	struct treeview_field *ef, *field_desc = NULL;
	int pos = node_y + tree_g.line_height;
	int field_y = node_y;
	int field_x;
	int width, height;
	bool success;

	/* If the main field is editable, make field_data point to it */
	if (n->type == TREE_NODE_ENTRY)
		ef = &(tree->fields[0]);
	else
		ef = &(tree->fields[tree->n_fields]);
	if (ef->flags & TREE_FLAG_ALLOW_EDIT) {
		field_data = &n->text;
		field_desc = ef;
		field_y = node_y;
	}

	/* Check for editable entry fields */
	if (n->type == TREE_NODE_ENTRY && n->height != tree_g.line_height) {
		struct treeview_node_entry *e = (struct treeview_node_entry *)n;
		int i;

		for (i = 0; i < tree->n_fields - 1; i++) {
			if (mouse_y <= pos)
				continue;

			ef = &(tree->fields[i + 1]);
			pos += tree_g.line_height;
			if (mouse_y <= pos && (ef->flags &
					       TREE_FLAG_ALLOW_EDIT)) {
				field_data = &e->fields[i].value;
				field_desc = ef;
				field_y = pos - tree_g.line_height;
			}
		}
	}

	if (field_data == NULL || field_desc == NULL) {
		/* No editable field */
		return false;
	}

	/* Get window width/height */
	treeview__cw_get_window_dimensions(tree, &width, &height);

	/* Calculate textarea width/height */
	field_x = n->inset + tree_g.step_width + tree_g.icon_step - 3;
	width -= field_x;
	height = tree_g.line_height;

	/* Create text area */
	tree->edit.textarea = treeview__create_textarea(tree, width, height,
			0x000000, 0xffffff, 0x000000, plot_style_odd.text,
			treeview_textarea_callback);
	if (tree->edit.textarea == NULL) {
		return false;
	}

	success = textarea_set_text(tree->edit.textarea, field_data->data);
	if (!success) {
		textarea_destroy(tree->edit.textarea);
		return false;
	}

	tree->edit.node = n;
	tree->edit.field = field_desc->field;

	/* Position the caret */
	mouse_x -= field_x;
	if (mouse_x < 0)
		mouse_x = 0;
	else if (mouse_x >= width)
		mouse_x = width - 1;

	textarea_mouse_action(tree->edit.textarea,
			      BROWSER_MOUSE_PRESS_1 | BROWSER_MOUSE_CLICK_1,
			      mouse_x, tree_g.line_height / 2);

	/* Position the textarea */
	tree->edit.x = field_x;
	tree->edit.y = field_y;
	tree->edit.w = width;
	tree->edit.h = height;

	/* Setup redraw rectangle */
	if (rect->x0 > field_x)
		rect->x0 = field_x;
	if (rect->y0 > field_y)
		rect->y0 = field_y;
	if (rect->x1 < field_x + width)
		rect->x1 = field_x + width;
	if (rect->y1 < field_y + height)
		rect->y1 = field_y + height;

	return true;
}


/* Exported interface, documented in treeview.h */
void treeview_edit_selection(treeview *tree)
{
	struct rect rect;
	treeview_node *n;
	bool redraw;
	int y;

	assert(tree != NULL);
	assert(tree->root != NULL);

	/* Get first selected node */
	n = treeview_get_first_selected(tree);

	if (n == NULL)
		return;

	/* Get node's y-position */
	y = treeview_node_y(tree, n);

	/* Edit node at y */
	redraw = treeview_edit_node_at_point(tree, n, y,
					     0, y + tree_g.line_height / 2, &rect);

	if (redraw == false)
		return;

	/* Redraw */
	rect.x0 = 0;
	rect.y0 = y;
	rect.x1 = REDRAW_MAX;
	rect.y1 = y + tree_g.line_height;
	treeview__cw_invalidate_area(tree, &rect);
}


/**
 * context for treeview mouse handling
 */
struct treeview_mouse_action {
	treeview *tree;
	browser_mouse_state mouse;
	int x;
	int y;
	int current_y;	/* Y coordinate value of top of current node */
	int search_height;
};


/**
 * Treewalk node callback for handling mouse action.
 *
 * \param node current node
 * \param ctx node context
 * \param skip_children flag to allow children to be skipped
 * \param end flag to allow iteration to be finished early.
 * \return NSERROR_OK on success else error code.
 */
static nserror
treeview_node_mouse_action_cb(treeview_node *node,
			      void *ctx,
			      bool *skip_children,
			      bool *end)
{
	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 treeview_node_part part = TV_NODE_PART_NONE;
	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 NSERROR_OK; /* Don't want to abort tree walk */
	}

	/* Find where the mouse is */
	if (ma->y <= ma->current_y + tree_g.line_height) {
		int inset = node->inset;
		if (ma->tree->search.search == true) {
			inset = tree_g.window_padding;
		}
		if (ma->x >= inset - 1 &&
				ma->x < inset + tree_g.step_width) {
			/* Over expansion toggle */
			part = TV_NODE_PART_TOGGLE;

		} else if (ma->x >= inset + tree_g.step_width &&
				ma->x < inset + tree_g.step_width +
				tree_g.icon_step + node->text.width) {
			/* On node */
			part = TV_NODE_PART_ON_NODE;
		}
	} else if (node->type == TREE_NODE_ENTRY &&
		   height > tree_g.line_height) {
		/* Expanded entries */
		int x = node->inset + tree_g.step_width + tree_g.icon_step;
		int y = ma->current_y + tree_g.line_height;
		int i;
		struct treeview_node_entry *entry =
			(struct treeview_node_entry *)node;
		for (i = 0; i < ma->tree->n_fields - 1; i++) {
			struct treeview_field *ef = &(ma->tree->fields[i + 1]);

			if (ma->y > y + tree_g.line_height) {
				y += tree_g.line_height;
				continue;
			}

			if (ef->flags & TREE_FLAG_SHOW_NAME) {
				int max_width = ma->tree->field_width;

				if (ma->x >= x + max_width - ef->value.width -
				    tree_g.step_width &&
				    ma->x < x + max_width -
				    tree_g.step_width) {
					/* On a field name */
					part = TV_NODE_PART_ON_NODE;

				} else if (ma->x >= x + max_width &&
					   ma->x < x + max_width +
					   entry->fields[i].value.width) {
					/* On a field value */
					part = TV_NODE_PART_ON_NODE;
				}
			} else {
				if (ma->x >= x && ma->x < x +
				    entry->fields[i].value.width) {
					/* On a field value */
					part = TV_NODE_PART_ON_NODE;
				}
			}

			break;
		}
	}

	/* Record what position / part a drag started on */
	if (ma->mouse & (BROWSER_MOUSE_PRESS_1 | BROWSER_MOUSE_PRESS_2) &&
	    ma->tree->drag.type == TV_DRAG_NONE) {
		ma->tree->drag.selected = node->flags & TV_NFLAGS_SELECTED;
		ma->tree->drag.start_node = node;
		ma->tree->drag.part = part;
		ma->tree->drag.start.x = ma->x;
		ma->tree->drag.start.y = ma->y;
		ma->tree->drag.start.node_y = ma->current_y;
		ma->tree->drag.start.node_h = height;

		ma->tree->drag.prev.x = ma->x;
		ma->tree->drag.prev.y = ma->y;
		ma->tree->drag.prev.node_y = ma->current_y;
		ma->tree->drag.prev.node_h = height;
	}

	/* Handle drag start */
	if (ma->tree->drag.type == TV_DRAG_NONE) {
		if (ma->mouse & BROWSER_MOUSE_DRAG_1 &&
		    ma->tree->drag.selected == false &&
		    ma->tree->drag.part == TV_NODE_PART_NONE) {
			ma->tree->drag.type = TV_DRAG_SELECTION;
			treeview__cw_drag_status(ma->tree,
						 CORE_WINDOW_DRAG_SELECTION);

		} else if (ma->tree->search.search == false &&
			   !(ma->tree->flags & TREEVIEW_NO_MOVES) &&
			   ma->mouse & BROWSER_MOUSE_DRAG_1 &&
			   (ma->tree->drag.selected == true ||
			    ma->tree->drag.part == TV_NODE_PART_ON_NODE)) {
			ma->tree->drag.type = TV_DRAG_MOVE;
			treeview__cw_drag_status(ma->tree,
						 CORE_WINDOW_DRAG_MOVE);
			redraw |= treeview_propagate_selection(ma->tree, &r);

		} else if (ma->mouse & BROWSER_MOUSE_DRAG_2) {
			ma->tree->drag.type = TV_DRAG_SELECTION;
			treeview__cw_drag_status(ma->tree,
						 CORE_WINDOW_DRAG_SELECTION);
		}

		if (ma->tree->drag.start_node != NULL &&
		    ma->tree->drag.type == TV_DRAG_SELECTION) {
			ma->tree->drag.start_node->flags ^= TV_NFLAGS_SELECTED;
		}
	}

	/* Handle active drags */
	switch (ma->tree->drag.type) {
	case TV_DRAG_SELECTION:
	{
		int curr_y1 = ma->current_y + height;
		int prev_y1 = ma->tree->drag.prev.node_y +
			ma->tree->drag.prev.node_h;

		r.y0 = (ma->current_y < ma->tree->drag.prev.node_y) ?
			ma->current_y : ma->tree->drag.prev.node_y;
		r.y1 = (curr_y1 > prev_y1) ? curr_y1 : prev_y1;

		redraw = true;

		ma->tree->drag.prev.x = ma->x;
		ma->tree->drag.prev.y = ma->y;
		ma->tree->drag.prev.node_y = ma->current_y;
		ma->tree->drag.prev.node_h = height;
	}
		break;

	case TV_DRAG_MOVE:
		redraw |= treeview_set_move_indicator(ma->tree, redraw,
						      node, height,
						      ma->current_y, ma->y, &r);
		break;

	default:
		break;
	}

	click = ma->mouse & (BROWSER_MOUSE_CLICK_1 | BROWSER_MOUSE_CLICK_2);

	if (((node->type == TREE_NODE_FOLDER) &&
	     (ma->mouse & BROWSER_MOUSE_DOUBLE_CLICK) && click) ||
	    (part == TV_NODE_PART_TOGGLE && click)) {
		int h = treeview__get_display_height(ma->tree) +
				ma->search_height;

		/* Clear any existing selection */
		redraw |= treeview_clear_selection(ma->tree, &r);

		/* Toggle node expansion */
		if (node->flags & TV_NFLAGS_EXPANDED) {
			err = treeview_node_contract_internal(ma->tree, node);
		} else {
			err = treeview_node_expand_internal(ma->tree, node);
		}
		if (err != NSERROR_OK) {
			return err;
		}

		/* Set up redraw */
		if (!redraw || r.y0 > ma->current_y)
			r.y0 = ma->current_y;
		if (h > treeview__get_display_height(ma->tree) +
				ma->search_height) {
			r.y1 = h;
		} else {
			r.y1 = treeview__get_display_height(ma->tree) +
				ma->search_height;
		}
		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_2 ||
		   (ma->mouse & BROWSER_MOUSE_PRESS_1 &&
		    ma->mouse & BROWSER_MOUSE_MOD_2)) {
		/* Toggle selection of node */
		action |= TV_NODE_ACTION_SELECTION;

	} else if (ma->mouse & BROWSER_MOUSE_CLICK_1 &&
		   ma->mouse &
		   (BROWSER_MOUSE_MOD_1 | BROWSER_MOUSE_MOD_3) &&
		   part != TV_NODE_PART_TOGGLE) {

		/* Clear any existing selection */
		redraw |= treeview_clear_selection(ma->tree, &r);

		/* Edit node */
		redraw |= treeview_edit_node_at_point(ma->tree, node,
						      ma->current_y, ma->x,
						      ma->y, &r);

	} else if (ma->mouse & BROWSER_MOUSE_PRESS_1 &&
		   !(ma->mouse &
		     (BROWSER_MOUSE_MOD_1 | BROWSER_MOUSE_MOD_3)) &&
		   !(node->flags & TV_NFLAGS_SELECTED) &&
		   part != TV_NODE_PART_TOGGLE) {
		/* Clear any existing selection */
		redraw |= treeview_clear_selection(ma->tree, &r);

		/* Select node */
		action |= TV_NODE_ACTION_SELECTION;

	}

	if (action & TV_NODE_ACTION_SELECTION) {
		/* Handle change in selection */
		node->flags ^= TV_NFLAGS_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) {
		treeview__cw_invalidate_area(ma->tree, &r);
	}

	*end = true; /* Reached line with click; stop walking tree */
	return NSERROR_OK;
}


/* Exported interface, documented in treeview.h */
void
treeview_mouse_action(treeview *tree, browser_mouse_state mouse, int x, int y)
{
	struct rect r;
	bool redraw = false;
	int search_height = (tree->flags & TREEVIEW_SEARCHABLE) ?
			tree_g.line_height : 0;

	assert(tree != NULL);
	assert(tree->root != NULL);

	/* Not interested in whether mouse leaves window. */
	if (mouse == BROWSER_MOUSE_LEAVE) {
		return;
	}

	/* Handle mouse drag captured by textarea */
	if (tree->drag.type == TV_DRAG_TEXTAREA) {
		textarea_mouse_action(tree->edit.textarea, mouse,
				      x - tree->edit.x, y - tree->edit.y);
		return;
	} else if (tree->drag.type == TV_DRAG_SEARCH ||
			(y < search_height &&
			 tree->drag.type == TV_DRAG_NONE)) {
		if (tree->search.active == false) {
			tree->search.active = true;
			if (treeview_clear_selection(tree, &r)) {
				treeview__cw_invalidate_area(tree, &r);
			}
		}
		textarea_mouse_action(tree->search.textarea, mouse,
				x - tree_g.window_padding - tree_g.icon_size,
				y);
		return;
	} else if (mouse & (BROWSER_MOUSE_PRESS_1 | BROWSER_MOUSE_PRESS_2) &&
			tree->search.active == true) {

		tree->search.active = false;
		textarea_set_caret(tree->search.textarea, -1);
		r.x0 = 0;
		r.y0 = 0;
		r.x1 = REDRAW_MAX;
		r.y1 = tree_g.line_height;
		treeview__cw_invalidate_area(tree, &r);
	}

	/* Handle textarea related mouse action */
	if (tree->edit.textarea != NULL) {
		int ta_x = x - tree->edit.x;
		int ta_y = y - tree->edit.y;

		if (ta_x > 0 && ta_x < tree->edit.w &&
		    ta_y > 0 && ta_y < tree->edit.h) {
			/* Inside textarea */
			textarea_mouse_action(tree->edit.textarea, mouse,
					      ta_x, ta_y);
			return;

		} else if (mouse != BROWSER_MOUSE_HOVER) {
			/* Action outside textarea */
			treeview_edit_cancel(tree, true);
		}
	}

	/* Handle drag ends */
	if (mouse == BROWSER_MOUSE_HOVER) {
		switch (tree->drag.type) {
		case TV_DRAG_SELECTION:
			treeview_commit_selection_drag(tree);
			tree->drag.type = TV_DRAG_NONE;
			tree->drag.start_node = NULL;

			treeview__cw_drag_status(tree, CORE_WINDOW_DRAG_NONE);
			return;
		case TV_DRAG_MOVE:
			treeview_move_selection(tree, &r);
			tree->drag.type = TV_DRAG_NONE;
			tree->drag.start_node = NULL;

			tree->move.target = NULL;
			tree->move.target_pos = TV_TARGET_NONE;

			treeview__cw_drag_status(tree, CORE_WINDOW_DRAG_NONE);
			treeview__cw_invalidate_area(tree, &r);
			return;
		default:
			/* No drag to end */
			break;
		}
	}

	if (y > treeview__get_display_height(tree) + search_height) {
		/* Below tree */

		r.x0 = 0;
		r.x1 = REDRAW_MAX;

		/* Record what position / part a drag started on */
		if (mouse & (BROWSER_MOUSE_PRESS_1 | BROWSER_MOUSE_PRESS_2) &&
		    tree->drag.type == TV_DRAG_NONE) {
			tree->drag.selected = false;
			tree->drag.start_node = NULL;
			tree->drag.part = TV_NODE_PART_NONE;
			tree->drag.start.x = x;
			tree->drag.start.y = y;
			tree->drag.start.node_y = tree->root->height;
			tree->drag.start.node_h = 0;

			tree->drag.prev.x = x;
			tree->drag.prev.y = y;
			tree->drag.prev.node_y = tree->root->height;
			tree->drag.prev.node_h = 0;
		}

		/* Handle drag start */
		if (tree->drag.type == TV_DRAG_NONE) {
			if (mouse & BROWSER_MOUSE_DRAG_1 &&
			    tree->drag.selected == false &&
			    tree->drag.part == TV_NODE_PART_NONE) {
				tree->drag.type = TV_DRAG_SELECTION;
				treeview__cw_drag_status(tree,
						CORE_WINDOW_DRAG_SELECTION);
			} else if (mouse & BROWSER_MOUSE_DRAG_2) {
				tree->drag.type = TV_DRAG_SELECTION;
				treeview__cw_drag_status(tree,
						CORE_WINDOW_DRAG_SELECTION);
			}

			if (tree->drag.start_node != NULL &&
			    tree->drag.type == TV_DRAG_SELECTION) {
				tree->drag.start_node->flags ^=
					TV_NFLAGS_SELECTED;
			}
		}

		/* Handle selection drags */
		if (tree->drag.type == TV_DRAG_SELECTION) {
			int curr_y1 = tree->root->height;
			int prev_y1 = tree->drag.prev.node_y +
				tree->drag.prev.node_h;

			r.y0 = tree->drag.prev.node_y;
			r.y1 = (curr_y1 > prev_y1) ? curr_y1 : prev_y1;

			redraw = true;

			tree->drag.prev.x = x;
			tree->drag.prev.y = y;
			tree->drag.prev.node_y = curr_y1;
			tree->drag.prev.node_h = 0;
		}

		if (mouse & BROWSER_MOUSE_PRESS_1) {
			/* Clear any existing selection */
			redraw |= treeview_clear_selection(tree, &r);
		}

		if (redraw) {
			treeview__cw_invalidate_area(tree, &r);
		}

	} else {
		/* On tree */
		struct treeview_mouse_action ma = {
			ma.tree = tree,
			ma.mouse = mouse,
			ma.x = x,
			ma.y = y,
			ma.current_y = search_height,
			ma.search_height = search_height,
		};

		treeview_walk_internal(tree, tree->root,
				TREEVIEW_WALK_MODE_DISPLAY, NULL,
				treeview_node_mouse_action_cb, &ma);
	}
}


/* Exported interface, documented in treeview.h */
int treeview_get_height(treeview *tree)
{
	int search_height = (tree->flags & TREEVIEW_SEARCHABLE) ?
			tree_g.line_height : 0;
	int height = treeview__get_display_height(tree);

	assert(tree != NULL);
	assert(tree->root != NULL);

	treeview__cw_update_size(tree, -1, height);

	return height + search_height;
}


/**
 * Initialise the plot styles from CSS system colour values.
 *
 * \param font_pt_size font size to use
 * \return NSERROR_OK on success else appropriate error code
 */
static nserror treeview_init_plot_styles(int font_pt_size)
{
	nserror res;

	/* 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;
	res = ns_system_colour_char("Window", &plot_style_even.bg.fill_colour);
	if (res != NSERROR_OK) {
		return res;
	}

	/* Text colour */
	plot_style_even.text.family = PLOT_FONT_FAMILY_SANS_SERIF;
	plot_style_even.text.size = font_pt_size;
	plot_style_even.text.weight = 400;
	plot_style_even.text.flags = FONTF_NONE;
	res = ns_system_colour_char("WindowText", &plot_style_even.text.foreground);
	if (res != NSERROR_OK) {
		return res;
	}
	res = ns_system_colour_char("Window", &plot_style_even.text.background);
	if (res != NSERROR_OK) {
		return res;
	}

	/* 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;
	res = ns_system_colour_char("Highlight", &plot_style_even.sbg.fill_colour);
	if (res != NSERROR_OK) {
		return res;
	}

	/* Selected text colour */
	plot_style_even.stext = plot_style_even.text;
	res = ns_system_colour_char("HighlightText", &plot_style_even.stext.foreground);
	if (res != NSERROR_OK) {
		return res;
	}
	res = ns_system_colour_char("Highlight", &plot_style_even.stext.background);
	if (res != NSERROR_OK) {
		return res;
	}

	/* 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;

	return NSERROR_OK;
}


/**
 * Callback for hlcache retrieving resources.
 *
 * \param handle content hlcache handle
 * \param event The event that occurred on the content
 * \param pw treeview resource context
 */
static nserror
treeview_res_cb(struct 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;
}


/**
 * Fetch content resources used by treeview.
 */
static void treeview_init_resources(void)
{
	int i;

	for (i = 0; i < TREE_RES_LAST; i++) {
		nsurl *url;
		treeview_res[i].ready = false;
		treeview_res[i].height = 0;
		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);
		}
	}
}


/**
 * Create a right-pointing anti-aliased triangle bitmap
 *
 * \param bg background colour
 * \param fg foreground colour
 * \param size required bitmap size
 */
static struct bitmap *
treeview_generate_triangle_bitmap(colour bg, colour fg, int size)
{
	struct bitmap *b = NULL;
	int x, y;
	unsigned char *rpos;
	unsigned char *pos;
	size_t stride;

	/* Set up required colour graduations.  Ignores screen gamma. */
	colour colour0 = bg;
	colour colour1 = mix_colour(bg, fg, 255 * 3 / 4);
	colour colour2 = blend_colour(bg, fg);
	colour colour3 = mix_colour(bg, fg, 255 * 1 / 4);
	colour colour4 = fg;

	/* Create the bitmap */
	b = guit->bitmap->create(size, size, BITMAP_NEW | BITMAP_OPAQUE);
	if (b == NULL)
		return NULL;

	rpos = guit->bitmap->get_buffer(b);
	stride = guit->bitmap->get_rowstride(b);

	/* Draw the triangle */
	for (y = 0; y < size; y++) {
		pos = rpos;

		if (y < size / 2) {
			/* Top half */
			for (x = 0; x < y * 2; x++) {
				*(pos++) = red_from_colour(colour4);
				*(pos++) = green_from_colour(colour4);
				*(pos++) = blue_from_colour(colour4);
				*(pos++) = 0xff;
			}
			*(pos++) = red_from_colour(colour3);
			*(pos++) = green_from_colour(colour3);
			*(pos++) = blue_from_colour(colour3);
			*(pos++) = 0xff;
			*(pos++) = red_from_colour(colour1);
			*(pos++) = green_from_colour(colour1);
			*(pos++) = blue_from_colour(colour1);
			*(pos++) = 0xff;
			for (x = y * 2 + 2; x < size ; x++) {
				*(pos++) = red_from_colour(colour0);
				*(pos++) = green_from_colour(colour0);
				*(pos++) = blue_from_colour(colour0);
				*(pos++) = 0xff;
			}
		} else if ((y == size / 2) && (size & 0x1)) {
			/* Middle row */
			for (x = 0; x < size - 1; x++) {
				*(pos++) = red_from_colour(colour4);
				*(pos++) = green_from_colour(colour4);
				*(pos++) = blue_from_colour(colour4);
				*(pos++) = 0xff;
			}
			*(pos++) = red_from_colour(colour2);
			*(pos++) = green_from_colour(colour2);
			*(pos++) = blue_from_colour(colour2);
			*(pos++) = 0xff;
		} else {
			/* Bottom half */
			for (x = 0; x < (size - y - 1) * 2; x++) {
				*(pos++) = red_from_colour(colour4);
				*(pos++) = green_from_colour(colour4);
				*(pos++) = blue_from_colour(colour4);
				*(pos++) = 0xff;
			}
			*(pos++) = red_from_colour(colour3);
			*(pos++) = green_from_colour(colour3);
			*(pos++) = blue_from_colour(colour3);
			*(pos++) = 0xff;
			*(pos++) = red_from_colour(colour1);
			*(pos++) = green_from_colour(colour1);
			*(pos++) = blue_from_colour(colour1);
			*(pos++) = 0xff;
			for (x = (size - y) * 2; x < size ; x++) {
				*(pos++) = red_from_colour(colour0);
				*(pos++) = green_from_colour(colour0);
				*(pos++) = blue_from_colour(colour0);
				*(pos++) = 0xff;
			}
		}

		rpos += stride;
	}

	guit->bitmap->modified(b);

	return b;
}


/**
 * Create bitmap copy of another bitmap
 *
 * \param orig bitmap to copy
 * \param size required bitmap size
 */
static struct bitmap *
treeview_generate_copy_bitmap(struct bitmap *orig, int size)
{
	struct bitmap *b = NULL;
	unsigned char *data;
	unsigned char *orig_data;
	size_t stride;

	if (orig == NULL)
		return NULL;

	assert(size == guit->bitmap->get_width(orig));
	assert(size == guit->bitmap->get_height(orig));

	/* Create the bitmap */
	b = guit->bitmap->create(size, size, BITMAP_NEW | BITMAP_OPAQUE);
	if (b == NULL)
		return NULL;

	stride = guit->bitmap->get_rowstride(b);
	assert(stride == guit->bitmap->get_rowstride(orig));

	data = guit->bitmap->get_buffer(b);
	orig_data = guit->bitmap->get_buffer(orig);

	/* Copy the bitmap */
	memcpy(data, orig_data, stride * size);

	guit->bitmap->modified(b);

	/* We've not modified the original image, but we called
	 * bitmap_get_buffer(), so we need to pair that with a
	 * bitmap_modified() call to appease certain front ends. */
	guit->bitmap->modified(orig);

	return b;
}


/**
 * Create bitmap from rotation of another bitmap
 *
 * \param orig bitmap to create rotation of
 * \param size required bitmap size
 */
static struct bitmap *
treeview_generate_rotate_bitmap(struct bitmap *orig, int size)
{
	struct bitmap *b = NULL;
	int x, y;
	unsigned char *rpos;
	unsigned char *pos;
	unsigned char *orig_data;
	unsigned char *orig_pos;
	size_t stride;

	if (orig == NULL)
		return NULL;

	assert(size == guit->bitmap->get_width(orig));
	assert(size == guit->bitmap->get_height(orig));

	/* Create the bitmap */
	b = guit->bitmap->create(size, size, BITMAP_NEW | BITMAP_OPAQUE);
	if (b == NULL)
		return NULL;

	stride = guit->bitmap->get_rowstride(b);
	assert(stride == guit->bitmap->get_rowstride(orig));

	rpos = guit->bitmap->get_buffer(b);
	orig_data = guit->bitmap->get_buffer(orig);

	/* Copy the rotated bitmap */
	for (y = 0; y < size; y++) {
		pos = rpos;

		for (x = 0; x < size; x++) {
			orig_pos = orig_data + x * stride + y * 4;
			*(pos++) = *(orig_pos++);
			*(pos++) = *(orig_pos++);
			*(pos++) = *(orig_pos);
			*(pos++) = 0xff;

		}

		rpos += stride;
	}

	guit->bitmap->modified(b);

	/* We've not modified the original image, but we called
	 * bitmap_get_buffer(), so we need to pair that with a
	 * bitmap_modified() call to appease certain front ends.
	 */
	guit->bitmap->modified(orig);

	return b;
}


/**
 * Measures width of characters used to represent treeview furniture.
 *
 * \return NSERROR_OK on success else error code
 */
static nserror treeview_init_furniture(void)
{
	int size = tree_g.line_height / 2;

	plot_style_odd.furn[TREE_FURN_EXPAND].size = size;
	plot_style_odd.furn[TREE_FURN_EXPAND].bmp =
		treeview_generate_triangle_bitmap(
			plot_style_odd.bg.fill_colour,
			plot_style_odd.itext.foreground, size);
	plot_style_odd.furn[TREE_FURN_EXPAND].sel =
		treeview_generate_triangle_bitmap(
			plot_style_odd.sbg.fill_colour,
			plot_style_odd.sitext.foreground, size);

	plot_style_even.furn[TREE_FURN_EXPAND].size = size;
	plot_style_even.furn[TREE_FURN_EXPAND].bmp =
		treeview_generate_triangle_bitmap(
			plot_style_even.bg.fill_colour,
			plot_style_even.itext.foreground, size);
	plot_style_even.furn[TREE_FURN_EXPAND].sel =
		treeview_generate_copy_bitmap(
			plot_style_odd.furn[TREE_FURN_EXPAND].sel, size);

	plot_style_odd.furn[TREE_FURN_CONTRACT].size = size;
	plot_style_odd.furn[TREE_FURN_CONTRACT].bmp =
		treeview_generate_rotate_bitmap(
			plot_style_odd.furn[TREE_FURN_EXPAND].bmp, size);
	plot_style_odd.furn[TREE_FURN_CONTRACT].sel =
		treeview_generate_rotate_bitmap(
			plot_style_odd.furn[TREE_FURN_EXPAND].sel, size);

	plot_style_even.furn[TREE_FURN_CONTRACT].size = size;
	plot_style_even.furn[TREE_FURN_CONTRACT].bmp =
		treeview_generate_rotate_bitmap(
			plot_style_even.furn[TREE_FURN_EXPAND].bmp, size);
	plot_style_even.furn[TREE_FURN_CONTRACT].sel =
		treeview_generate_rotate_bitmap(
			plot_style_even.furn[TREE_FURN_EXPAND].sel, size);

	if (plot_style_odd.furn[TREE_FURN_EXPAND].bmp == NULL ||
	    plot_style_odd.furn[TREE_FURN_EXPAND].sel == NULL ||
	    plot_style_even.furn[TREE_FURN_EXPAND].bmp == NULL ||
	    plot_style_even.furn[TREE_FURN_EXPAND].sel == NULL ||
	    plot_style_odd.furn[TREE_FURN_CONTRACT].bmp == NULL ||
	    plot_style_odd.furn[TREE_FURN_CONTRACT].sel == NULL ||
	    plot_style_even.furn[TREE_FURN_CONTRACT].bmp == NULL ||
	    plot_style_even.furn[TREE_FURN_CONTRACT].sel == NULL)
		return NSERROR_NOMEM;

	tree_g.furniture_width = size + tree_g.line_height / 4;

	return NSERROR_OK;
}


/* Exported interface, documented in treeview.h */
nserror treeview_init(void)
{
	long long font_px_size;
	long long font_pt_size;
	nserror res;

	if (tree_g.initialised > 0) {
		tree_g.initialised++;
		return NSERROR_OK;
	}

	NSLOG(netsurf, INFO, "Initialising treeview module");

	font_pt_size = nsoption_int(treeview_font_size);
	if (font_pt_size <= 0) {
		font_pt_size = 11 * 10;
	}

	font_px_size = (font_pt_size * FIXTOINT(nscss_screen_dpi) /
			10 + 36) / 72;
	tree_g.line_height = (font_px_size * 8 + 3) / 6;

	res = treeview_init_plot_styles(font_pt_size * PLOT_STYLE_SCALE / 10);
	if (res != NSERROR_OK) {
		return res;
	}

	treeview_init_resources();

	res = treeview_init_furniture();
	if (res != NSERROR_OK) {
		return res;
	}

	tree_g.step_width = tree_g.furniture_width;
	tree_g.window_padding = 6;
	tree_g.icon_size = 17;
	tree_g.icon_step = 23;
	tree_g.move_offset = 18;

	tree_g.initialised++;

	NSLOG(netsurf, INFO, "Initialised treeview module");

	return NSERROR_OK;
}


/* Exported interface, documented in treeview.h */
nserror treeview_fini(void)
{
	int i;

	if (tree_g.initialised > 1) {
		tree_g.initialised--;
		return NSERROR_OK;

	} else if (tree_g.initialised == 0) {
		NSLOG(netsurf, INFO,
		      "Warning: tried to finalise uninitialised treeview module");
		return NSERROR_OK;
	}

	NSLOG(netsurf, INFO, "Finalising treeview module");

	for (i = 0; i < TREE_RES_LAST; i++) {
		hlcache_handle_release(treeview_res[i].c);
	}

	guit->bitmap->destroy(plot_style_odd.furn[TREE_FURN_EXPAND].bmp);
	guit->bitmap->destroy(plot_style_odd.furn[TREE_FURN_EXPAND].sel);
	guit->bitmap->destroy(plot_style_even.furn[TREE_FURN_EXPAND].bmp);
	guit->bitmap->destroy(plot_style_even.furn[TREE_FURN_EXPAND].sel);
	guit->bitmap->destroy(plot_style_odd.furn[TREE_FURN_CONTRACT].bmp);
	guit->bitmap->destroy(plot_style_odd.furn[TREE_FURN_CONTRACT].sel);
	guit->bitmap->destroy(plot_style_even.furn[TREE_FURN_CONTRACT].bmp);
	guit->bitmap->destroy(plot_style_even.furn[TREE_FURN_CONTRACT].sel);

	tree_g.initialised--;

	NSLOG(netsurf, INFO, "Finalised treeview module");

	return NSERROR_OK;
}