netsurf/desktop/tree.c
Michael Drake 8b83456c02 Beginnings of new hotlist module, implemented with new treeview.
Currently it can only open, display and launch entries in an existing hotlist file.

TODO:
  - Generate defualt hotlist when file not found.
  - Add hotlist saving.
  - Allow adding to hotlist.
  - Allow hotlist editing.
  - Allow hotlist nodes to be moved by dragging.
  - Allow hotlist visit data to be updated.
2013-08-16 08:25:25 +01:00

3375 lines
83 KiB
C

/*
* Copyright 2004 Richard Wilson <not_ginger_matt@users.sourceforge.net>
* Copyright 2009 Paul Blokus <paul_pl@users.sourceforge.net>
*
* This file is part of NetSurf, http://www.netsurf-browser.org/
*
* 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
* Generic tree handling (implementation).
*/
#include <assert.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "content/content.h"
#include "content/hlcache.h"
#include "css/utils.h"
#include "desktop/browser.h"
#include "desktop/knockout.h"
#include "desktop/textarea.h"
#include "desktop/textinput.h"
#include "desktop/tree.h"
#include "utils/nsoption.h"
#include "desktop/plotters.h"
#include "render/font.h"
#include "utils/log.h"
#include "utils/messages.h"
#include "utils/utils.h"
#include "utils/url.h"
#undef TREE_NOISY_DEBUG
#define MAXIMUM_URL_LENGTH 1024
#define TREE_TEXT_SIZE_PT 11
#define TREE_ICON_SIZE 17
#define NODE_INSTEP 20
static int tree_text_size_px;
static int TREE_LINE_HEIGHT;
static char *tree_icons_dir = NULL;
static plot_font_style_t plot_fstyle = {
.family = PLOT_FONT_FAMILY_SANS_SERIF,
.size = TREE_TEXT_SIZE_PT * FONT_SIZE_SCALE,
.weight = 400,
.flags = FONTF_NONE
};
static plot_font_style_t plot_fstyle_def_folder = {
.family = PLOT_FONT_FAMILY_SANS_SERIF,
.size = TREE_TEXT_SIZE_PT * FONT_SIZE_SCALE,
.weight = 700,
.flags = FONTF_NONE
};
static plot_font_style_t plot_fstyle_selected = {
.family = PLOT_FONT_FAMILY_SANS_SERIF,
.size = TREE_TEXT_SIZE_PT * FONT_SIZE_SCALE,
.weight = 400,
.flags = FONTF_NONE
};
static plot_font_style_t plot_fstyle_selected_def_folder = {
.family = PLOT_FONT_FAMILY_SANS_SERIF,
.size = TREE_TEXT_SIZE_PT * FONT_SIZE_SCALE,
.weight = 700,
.flags = FONTF_NONE
};
/** plot style for treeview backgrounds. */
static plot_style_t plot_style_fill_tree_background = {
.fill_type = PLOT_OP_TYPE_SOLID
};
/** plot style for treeview backgrounds. */
static plot_style_t plot_style_fill_tree_selected = {
.fill_type = PLOT_OP_TYPE_SOLID
};
/** plot style for treeview furniture lines. */
static plot_style_t plot_style_stroke_tree_furniture = {
.stroke_type = PLOT_OP_TYPE_SOLID
};
/** plot style for treeview furniture fills. */
static plot_style_t plot_style_fill_tree_furniture = {
.fill_type = PLOT_OP_TYPE_SOLID
};
struct node;
struct tree;
struct node_element_box {
int x; /**< X offset from origin */
int y; /**< Y offset from origin */
int width; /**< Element width */
int height; /**< Element height */
};
struct node_element {
struct node *parent; /**< Parent node */
node_element_type type; /**< Element type */
struct node_element_box box; /**< Element bounding box */
char *text; /**< Text for the element */
void *bitmap; /**< Bitmap for the element */
struct node_element *next; /**< Next node element */
unsigned int flag; /**< Client specified flag for data
being represented */
bool editable; /**< Whether the node text can be
* modified, editable text is deleted
* without noticing the tree user
*/
};
struct node {
bool selected; /**< Whether the node is selected */
bool expanded; /**< Whether the node is expanded */
bool folder; /**< Whether the node is a folder */
bool def_folder; /**< Whether the node is the default folder */
bool retain_in_memory; /**< Whether the node remains
in memory after deletion */
bool deleted; /**< Whether the node is currently
deleted */
bool processing; /**< Internal flag used when moving */
struct node_element_box box; /**< Bounding box of all elements */
struct node_element data; /**< Data to display */
struct node *parent; /**< Parent entry (NULL for root) */
struct node *child; /**< First child */
struct node *last_child; /**< Last child */
struct node *previous; /**< Previous child of the parent */
struct node *next; /**< Next child of the parent */
/** Sorting function for the node (for folder nodes only) */
int (*sort) (struct node *, struct node *);
/** Gets called for each deleted node_element and on node launch */
tree_node_user_callback user_callback;
/** User data to be passed to delete_callback */
void *callback_data;
};
struct tree {
struct node *root; /* Tree root element */
int width; /* Tree width */
int height; /* Tree height */
unsigned int flags; /* Tree flags */
struct textarea *textarea; /* Handle for UTF-8 textarea */
int ta_height; /* Textarea height */
struct node_element *editing; /* Node element being edited */
bool redraw; /* Flag indicating whether the tree
should be redrawn on layout
changes */
tree_drag_type drag;
const struct treeview_table *callbacks;
void *client_data; /* User assigned data for the
callbacks */
struct node *def_folder; /* Node to be used for additions by default */
};
#include "desktop/treeview.h"
#include "desktop/hotlist.h"
#include "desktop/cookie_manager.h"
#include "desktop/global_history.h"
#include "desktop/sslcert_viewer.h"
int treeview_inits;
static void treeview_test_redraw_request(struct core_window *cw, struct rect r)
{
struct tree *tree = (struct tree *)cw;
tree->callbacks->redraw_request(r.x0, r.y0,
r.x1 - r.x0, r.y1 - r.y0,
tree->client_data);
}
static void treeview_test_update_size(struct core_window *cw,
int width, int height)
{
struct tree *tree = (struct tree *)cw;
tree->callbacks->resized(tree, width, height, tree->client_data);
}
static void treeview_test_scroll_visible(struct core_window *cw, struct rect r)
{
}
static void treeview_test_get_window_dimensions(struct core_window *cw,
int *width, int *height)
{
}
static void treeview_test_drag_status(struct core_window *cw,
core_window_drag_status ds)
{
struct tree *tree = (struct tree *)cw;
switch (ds) {
case CORE_WINDOW_DRAG_NONE:
tree->drag = TREE_NO_DRAG;
break;
case CORE_WINDOW_DRAG_SELECTION:
tree->drag = TREE_SELECT_DRAG;
break;
default:
break;
}
}
struct core_window_callback_table cw_t = {
.redraw_request = treeview_test_redraw_request,
.update_size = treeview_test_update_size,
.scroll_visible = treeview_test_scroll_visible,
.get_window_dimensions = treeview_test_get_window_dimensions,
.drag_status = treeview_test_drag_status
};
static bool treeview_test_init(struct tree *tree)
{
nserror err;
if (tree->flags & TREE_SSLCERT) {
treeview_inits++;
if (treeview_inits == 1)
treeview_init();
err = sslcert_viewer_init(&cw_t, (struct core_window *)tree,
ssl_current_session);
if (err != NSERROR_OK) {
warn_user("Couldn't init new sslcert viewer.", 0);
}
return true;
}
/* Check if we're testing the new treeview */
if (nsoption_bool(temp_treeview_test) == false)
return false;
treeview_inits++;
if (treeview_inits == 1)
treeview_init();
if (tree->flags & TREE_COOKIES) {
err = cookie_manager_init(&cw_t, (struct core_window *)tree);
if (err != NSERROR_OK) {
warn_user("Couldn't init new cookie manager.", 0);
}
}
if (tree->flags & TREE_HISTORY) {
err = global_history_init(&cw_t, (struct core_window *)tree);
if (err != NSERROR_OK) {
warn_user("Couldn't init new global history.", 0);
}
}
if (tree->flags & TREE_HOTLIST) {
err = hotlist_init(&cw_t, (struct core_window *)tree,
tree_hotlist_path);
if (err != NSERROR_OK) {
warn_user("Couldn't init new hotlist.", 0);
}
}
return true;
}
static bool treeview_test_fini(struct tree *tree)
{
nserror err;
if (tree->flags & TREE_SSLCERT) {
err = sslcert_viewer_fini(ssl_current_session);
if (err != NSERROR_OK) {
warn_user("Couldn't finalise sslcert viewer.", 0);
}
if (treeview_inits == 1)
treeview_fini();
treeview_inits--;
return true;
}
/* Check if we're testing the new treeview */
if (nsoption_bool(temp_treeview_test) == false)
return false;
if (tree->flags & TREE_COOKIES) {
err = cookie_manager_fini();
if (err != NSERROR_OK) {
warn_user("Couldn't finalise cookie manager.", 0);
}
}
if (tree->flags & TREE_HISTORY) {
err = global_history_fini();
if (err != NSERROR_OK) {
warn_user("Couldn't finalise cookie manager.", 0);
}
}
if (tree->flags & TREE_HOTLIST) {
err = hotlist_fini(tree_hotlist_path);
if (err != NSERROR_OK) {
warn_user("Couldn't finalise hotlist.", 0);
}
}
if (treeview_inits == 1)
treeview_fini();
treeview_inits--;
return true;
}
static bool treeview_test_redraw(struct tree *tree, int x, int y,
int clip_x, int clip_y, int clip_width, int clip_height,
const struct redraw_context *ctx)
{
struct rect clip;
clip.x0 = clip_x;
clip.y0 = clip_y;
clip.x1 = clip_x + clip_width;
clip.y1 = clip_y + clip_height;
if (tree->flags & TREE_SSLCERT) {
sslcert_viewer_redraw(ssl_current_session, x, y, &clip, ctx);
return true;
}
/* Check if we're testing the new treeview */
if (nsoption_bool(temp_treeview_test) == false)
return false;
if (tree->flags & TREE_COOKIES) {
cookie_manager_redraw(x, y, &clip, ctx);
return true;
}
if (tree->flags & TREE_HISTORY) {
global_history_redraw(x, y, &clip, ctx);
return true;
}
if (tree->flags & TREE_HOTLIST) {
hotlist_redraw(x, y, &clip, ctx);
return true;
}
return false;
}
static bool treeview_test_mouse_action(struct tree *tree,
browser_mouse_state mouse, int x, int y)
{
if (tree->flags & TREE_SSLCERT) {
sslcert_viewer_mouse_action(ssl_current_session, mouse, x, y);
return true;
}
/* Check if we're testing the new treeview */
if (nsoption_bool(temp_treeview_test) == false)
return false;
if (tree->flags & TREE_COOKIES) {
cookie_manager_mouse_action(mouse, x, y);
return true;
}
if (tree->flags & TREE_HISTORY) {
global_history_mouse_action(mouse, x, y);
return true;
}
if (tree->flags & TREE_HOTLIST) {
hotlist_mouse_action(mouse, x, y);
return true;
}
return false;
}
static bool treeview_test_keypress(struct tree *tree, uint32_t key)
{
if (tree->flags & TREE_SSLCERT) {
sslcert_viewer_keypress(ssl_current_session, key);
return true;
}
/* Check if we're testing the new treeview */
if (nsoption_bool(temp_treeview_test) == false)
return false;
if (tree->flags & TREE_COOKIES) {
cookie_manager_keypress(key);
return true;
}
if (tree->flags & TREE_HISTORY) {
global_history_keypress(key);
return true;
}
if (tree->flags & TREE_HOTLIST) {
hotlist_keypress(key);
return true;
}
return false;
}
void tree_set_icon_dir(char *icon_dir)
{
LOG(("Tree icon directory set to %s", icon_dir));
tree_icons_dir = icon_dir;
}
/**
* Set up colours for plot styles used in tree redraw.
*/
void tree_setup_colours(void)
{
/* Background colour */
plot_style_fill_tree_background.fill_colour =
gui_system_colour_char("Window");
/* Selection background colour */
plot_style_fill_tree_selected.fill_colour =
gui_system_colour_char("Highlight");
/* Furniture line colour */
plot_style_stroke_tree_furniture.stroke_colour = blend_colour(
gui_system_colour_char("Window"),
gui_system_colour_char("WindowText"));
/* Furniture fill colour */
plot_style_fill_tree_furniture.fill_colour =
gui_system_colour_char("Window");
/* Text colour */
plot_fstyle.foreground = gui_system_colour_char("WindowText");
plot_fstyle.background = gui_system_colour_char("Window");
plot_fstyle_def_folder.foreground =
gui_system_colour_char("InfoText");
plot_fstyle_def_folder.background =
gui_system_colour_char("Window");
/* Selected text colour */
plot_fstyle_selected.foreground =
gui_system_colour_char("HighlightText");
plot_fstyle_selected.background =
gui_system_colour_char("Highlight");
plot_fstyle_selected_def_folder.foreground =
gui_system_colour_char("HighlightText");
plot_fstyle_selected_def_folder.background =
gui_system_colour_char("Highlight");
}
/**
* Creates and initialises a new tree.
*
* \param flags Flag word for flags to create the new tree with
* \param callbacks Callback functions to support the tree in the frontend.
* \param client_data Data to be passed to start_redraw and end_redraw
* \return The newly created tree, or NULL on memory exhaustion
*/
struct tree *tree_create(unsigned int flags,
const struct treeview_table *callbacks, void *client_data)
{
struct tree *tree;
tree = calloc(sizeof(struct tree), 1);
if (tree == NULL) {
LOG(("calloc failed"));
warn_user(messages_get_errorcode(NSERROR_NOMEM), 0);
return NULL;
}
tree->root = tree_create_folder_node(NULL,
NULL,
messages_get("Root"),
false, false, false);
if (tree->root == NULL) {
free(tree);
return NULL;
}
tree->root->expanded = true;
tree->width = 0;
tree->height = 0;
tree->flags = flags;
tree->textarea = NULL;
tree->editing = NULL;
tree->redraw = false;
tree->drag = TREE_NO_DRAG;
tree->callbacks = callbacks;
tree->client_data = client_data;
/* Set text height in pixels */
tree_text_size_px =
(TREE_TEXT_SIZE_PT * FIXTOINT(nscss_screen_dpi) + 36) /
72;
/* Set line height appropriate for this text height in pixels
* Using 4/3 text height */
TREE_LINE_HEIGHT = (tree_text_size_px * 8 + 3) / 6;
/* But if that's too small for the icons, base the line height on
* the icon height. */
if (TREE_LINE_HEIGHT < TREE_ICON_SIZE + 2)
TREE_LINE_HEIGHT = TREE_ICON_SIZE + 2;
tree_setup_colours();
treeview_test_init(tree);
return tree;
}
/**
* Recalculates the dimensions of a node element.
*
* \param tree the tree to which the element belongs, may be NULL
* \param element the element to recalculate
*/
static void tree_recalculate_node_element(struct tree *tree,
struct node_element *element)
{
struct bitmap *bitmap = NULL;
int width, height;
static char *cache_text = NULL;
static int cache_size = 0;
plot_font_style_t *fstyle;
static plot_font_style_t *cache_fstyle = NULL;
assert(element != NULL);
if (element->parent->def_folder)
fstyle = &plot_fstyle_def_folder;
else
fstyle = &plot_fstyle;
switch (element->type) {
case NODE_ELEMENT_TEXT_PLUS_ICON:
case NODE_ELEMENT_TEXT:
if(element->text == NULL)
break;
if (tree != NULL && element == tree->editing) {
textarea_get_dimensions(tree->textarea,
&element->box.width, NULL);
} else {
if ((cache_text != NULL) &&
(strcmp(cache_text, element->text) == 0) &&
(cache_fstyle == fstyle)) {
element->box.width = cache_size;
#ifdef TREE_NOISY_DEBUG
LOG(("Tree font width cache hit"));
#endif
} else {
if(cache_text != NULL) free(cache_text);
nsfont.font_width(fstyle,
element->text,
strlen(element->text),
&cache_size);
element->box.width = cache_size;
cache_text = strdup(element->text);
cache_fstyle = fstyle;
}
}
element->box.width += 8;
element->box.height = TREE_LINE_HEIGHT;
if (element->type == NODE_ELEMENT_TEXT_PLUS_ICON)
element->box.width += NODE_INSTEP;
break;
case NODE_ELEMENT_BITMAP:
bitmap = element->bitmap;
if (bitmap != NULL) {
width = bitmap_get_width(bitmap);
height = bitmap_get_height(bitmap);
element->box.width = width + 1;
element->box.height = height + 2;
} else {
element->box.width = 0;
element->box.height = 0;
}
break;
}
}
/**
* Calculates the height of a node including any children
*
* \param node the node to calculate the height of
* \return the total height of the node and children
*/
static int tree_get_node_height(struct node *node)
{
int y1;
assert(node != NULL);
if ((node->child == NULL) || (node->expanded == false)) {
return node->box.height;
}
y1 = node->box.y;
if (y1 < 0) {
y1 = 0;
}
node = node->child;
while ((node->next != NULL) ||
((node->child != NULL) && (node->expanded))) {
for (; node->next != NULL; node = node->next);
if ((node->child != NULL) && (node->expanded)) {
node = node->child;
}
}
return node->box.y + node->box.height - y1;
}
/**
* Calculates the width of a node including any children
*
* \param node the node to calculate the height of
* \return the total width of the node and children
*/
static int tree_get_node_width(struct node *node)
{
int width = 0;
int child_width;
assert(node != NULL);
for (; node != NULL; node = node->next) {
if (width < (node->box.x + node->box.width)) {
width = node->box.x + node->box.width;
}
if ((node->child != NULL) && (node->expanded)) {
child_width = tree_get_node_width(node->child);
if (width < child_width) {
width = child_width;
}
}
}
return width;
}
/**
* Recalculates the position of a node, its siblings and children.
*
* \param tree the tree to which 'root' belongs
* \param root the root node to update from
*/
static void tree_recalculate_node_positions(struct tree *tree,
struct node *root)
{
struct node *parent;
struct node *node;
struct node *child;
struct node_element *element;
int y;
bool has_icon;
for (node = root; node != NULL; node = node->next) {
parent = node->parent;
if (node->previous != NULL) {
node->box.x = node->previous->box.x;
node->box.y = node->previous->box.y +
tree_get_node_height(node->previous);
} else if (parent != NULL) {
node->box.x = parent->box.x + NODE_INSTEP;
node->box.y = parent->box.y +
parent->box.height;
for (child = parent->child; child != node;
child = child->next)
node->box.y += child->box.height;
} else {
node->box.x = tree->flags & TREE_NO_FURNITURE
? -NODE_INSTEP + 4 : 0;
node->box.y = -TREE_LINE_HEIGHT;
}
if (!node->expanded) {
node->data.box.x = node->box.x;
node->data.box.y = node->box.y;
continue;
}
if (node->folder) {
node->data.box.x = node->box.x;
node->data.box.y = node->box.y;
tree_recalculate_node_positions(tree, node->child);
} else {
y = node->box.y;
has_icon = false;
for (element = &node->data; element != NULL;
element = element->next)
if (element->type ==
NODE_ELEMENT_TEXT_PLUS_ICON) {
has_icon = true;
break;
}
for (element = &node->data; element != NULL;
element = element->next) {
element->box.x = node->box.x;
if (element->type !=
NODE_ELEMENT_TEXT_PLUS_ICON &&
has_icon)
element->box.x += NODE_INSTEP;
element->box.y = y;
y += element->box.height;
}
}
}
}
/**
* Recalculates the size of a node.
*
* \param tree the tree to which node belongs, may be NULL
* \param node the node to update
* \param recalculate_sizes whether the node elements have changed
*/
static void tree_recalculate_node_sizes(struct tree *tree, struct node *node,
bool recalculate_sizes)
{
struct node_element *element;
int height;
assert(node != NULL);
height = node->box.height;
node->box.width = 0;
node->box.height = 0;
if (node->expanded) {
for (element = &node->data; element != NULL;
element = element->next) {
if (recalculate_sizes) {
#ifdef TREE_NOISY_DEBUG
if(element->text) LOG(("%s", element->text));
#endif
tree_recalculate_node_element(tree, element);
}
node->box.width = (node->box.width > element->box.x +
element->box.width - node->box.x) ?
node->box.width :
element->box.width + element->box.x -
node->box.x;
node->box.height += element->box.height;
}
} else {
if (recalculate_sizes)
for (element = &node->data; element != NULL;
element = element->next) {
#ifdef TREE_NOISY_DEBUG
if(element->text) LOG(("%s", element->text));
#endif
tree_recalculate_node_element(tree, element);
}
node->box.width = node->data.box.width;
node->box.height = node->data.box.height;
}
if (tree != NULL && height != node->box.height)
tree_recalculate_node_positions(tree, tree->root);
}
/* exported interface documented in desktop/tree.h */
struct node *tree_create_folder_node(struct tree *tree, struct node *parent,
const char *title, bool editable, bool retain_in_memory,
bool deleted)
{
struct node *node;
assert(title != NULL);
node = calloc(sizeof(struct node), 1);
if (node == NULL) {
return NULL;
}
node->data.text = strdup(title);
if (node->data.text == NULL) {
free(node);
return NULL;
}
node->folder = true;
node->retain_in_memory = retain_in_memory;
node->deleted = deleted;
node->data.parent = node;
node->data.type = NODE_ELEMENT_TEXT;
node->data.flag = TREE_ELEMENT_TITLE;
node->data.editable = editable;
node->sort = NULL;
node->user_callback = NULL;
node->previous = NULL;
tree_recalculate_node_sizes(tree, node, true);
if (parent != NULL) {
tree_link_node(tree, parent, node, false);
}
return node;
}
/* exported interface documented in desktop/tree.h */
struct node *tree_create_leaf_node(struct tree *tree, struct node *parent,
const char *title, bool editable, bool retain_in_memory,
bool deleted)
{
struct node *node;
assert(title != NULL);
node = calloc(sizeof(struct node), 1);
if (node == NULL) {
return NULL;
}
node->data.text = strdup(title);
if (node->data.text == NULL) {
free(node);
return NULL;
}
node->folder = false;
node->retain_in_memory = retain_in_memory;
node->deleted = deleted;
node->data.parent = node;
node->data.type = NODE_ELEMENT_TEXT;
node->data.flag = TREE_ELEMENT_TITLE;
node->data.editable = editable;
node->sort = NULL;
node->user_callback = NULL;
node->previous = NULL;
tree_recalculate_node_sizes(tree, node, true);
if (parent != NULL) {
tree_link_node(tree, parent, node, false);
}
return node;
}
/**
* Creates an empty text node element and links it to a node.
*
* \param parent the parent node
* \param type the required element type
* \param flag user assigned flag used for searches
* \return the newly created element.
*/
struct node_element *tree_create_node_element(struct node *parent,
node_element_type type, unsigned int flag, bool editable)
{
struct node_element *element;
element = calloc(sizeof(struct node_element), 1);
if (element == NULL)
return NULL;
element->parent = parent;
element->flag = flag;
element->type = type;
element->editable = editable;
element->next = parent->data.next;
parent->data.next = element;
return element;
}
/**
* Inserts a node into the correct place according to the parent's sort function
*
* \param parent the node whose child node 'node' becomes
* \param node the node to be inserted
*/
static void tree_sort_insert(struct node *parent, struct node *node)
{
struct node *after;
assert(node != NULL);
assert(parent != NULL);
assert(parent->sort != NULL);
after = parent->last_child;
while ((after != NULL) &&
(parent->sort(node, after) == -1))
after = after->previous;
if (after != NULL) {
if (after->next != NULL)
after->next->previous = node;
node->next = after->next;
node->previous = after;
after->next = node;
} else {
node->previous = NULL;
node->next = parent->child;
if (parent->child != NULL) {
parent->child->previous = node;
}
parent->child = node;
}
if (node->next == NULL)
parent->last_child = node;
node->parent = parent;
}
/**
* Recalculates the size of a tree.
*
* \param tree the tree to recalculate
*/
static void tree_recalculate_size(struct tree *tree)
{
int width, height;
assert(tree != NULL);
width = tree->width;
height = tree->height;
tree->width = tree_get_node_width(tree->root);
tree->height = tree_get_node_height(tree->root);
if ((width != tree->width) || (height != tree->height))
tree->callbacks->resized(tree, tree->width, tree->height,
tree->client_data);
}
/**
* Recalculate the node data and redraw the relevant section of the tree.
*
* \param tree the tree to redraw, may be NULL
* \param node the node to update
* \param recalculate_sizes whether the elements have changed
* \param expansion the request is the result of a node expansion
*/
static void tree_handle_node_changed(struct tree *tree, struct node *node,
bool recalculate_sizes, bool expansion)
{
int node_width, node_height, tree_width, tree_height;
assert(node != NULL);
assert(tree != NULL);
node_width = node->box.width;
node_height = node->box.height;
tree_width = tree->width;
tree_height = tree->height;
if ((recalculate_sizes) || (expansion)) {
tree_recalculate_node_sizes(tree, node, true);
}
if (tree != NULL) {
if ((node->box.height != node_height) || (expansion)) {
tree_recalculate_node_positions(tree, tree->root);
tree_recalculate_size(tree);
if (tree->width > tree_width)
tree_width = tree->width;
if (tree->height > tree_height)
tree_height = tree->height;
if (tree->redraw) {
tree->callbacks->redraw_request(0, node->box.y,
tree_width,
tree_height - node->box.y,
tree->client_data);
}
} else {
if (node->box.width > node_width)
node_width = node->box.width;
if (tree->redraw)
tree->callbacks->redraw_request(node->box.x,
node->box.y,
node_width, node->box.height,
tree->client_data);
if (recalculate_sizes) {
tree_recalculate_size(tree);
}
}
}
}
/**
* Links a node to another node.
*
* \param tree the tree in which the link takes place, may be NULL
* \param link the node to link before/as a child (folders)
* or before/after (link)
* \param node the node to link
* \param before whether to link siblings before or after the supplied node
*/
void tree_link_node(struct tree *tree, struct node *link, struct node *node,
bool before)
{
struct node *parent;
bool sort = false;
assert(link != NULL);
assert(node != NULL);
if ((link->folder == 0) || (before)) {
parent = node->parent = link->parent;
if (parent->sort) {
sort = true;
} else {
if (before) {
node->next = link;
node->previous = link->previous;
if (link->previous != NULL)
link->previous->next = node;
link->previous = node;
if ((parent != NULL) && (parent->child == link))
parent->child = node;
} else {
node->previous = link;
node->next = link->next;
if (link->next != NULL)
link->next->previous = node;
link->next = node;
if ((parent != NULL) &&
(parent->last_child == link))
parent->last_child = node;
}
}
} else {
parent = node->parent = link;
if (parent->sort != NULL) {
sort = true;
} else {
node->next = NULL;
if (link->child == NULL) {
link->child = link->last_child = node;
node->previous = NULL;
} else {
link->last_child->next = node;
node->previous = link->last_child;
link->last_child = node;
}
}
}
if (sort) {
tree_sort_insert(parent, node);
}
tree_handle_node_changed(tree, link, false, true);
node->deleted = false;
}
/**
* Recalculate the node element and redraw the relevant section of the tree.
* The tree size is not updated.
*
* \param tree the tree to redraw, may be NULL
* \param element the node element to update
*/
static void tree_handle_node_element_changed(struct tree *tree,
struct node_element *element, bool text_changed)
{
int width, height;
assert(element != NULL);
width = element->box.width;
height = element->box.height;
if(text_changed == true) {
#ifdef TREE_NOISY_DEBUG
if(element->text) LOG(("%s", element->text));
#endif
tree_recalculate_node_element(tree, element);
}
if (element->box.height != height) {
tree_recalculate_node_sizes(tree, element->parent, false);
if ((tree != NULL) && (tree->redraw)) {
tree->callbacks->redraw_request(0, element->box.y,
tree->width + element->box.width -
width,
tree->height - element->box.y +
element->box.height - height,
tree->client_data);
}
} else {
if (element->box.width != width) {
tree_recalculate_node_sizes(tree, element->parent,
false);
}
if (tree != NULL) {
width = (width > element->box.width) ? width :
element->box.width;
if (tree->redraw) {
tree->callbacks->redraw_request(element->box.x,
element->box.y,
width,
element->box.height,
tree->client_data);
}
}
}
}
/**
* Stops editing a node_element
*
* \param tree The tree to stop editing for
* \param keep_changes If true the changes made to the text will be kept,
* if false they will be dropped
*/
static void tree_stop_edit(struct tree *tree, bool keep_changes)
{
int text_len;
char *text = NULL;
struct node_element *element;
struct node_msg_data msg_data;
node_callback_resp response;
assert(tree != NULL);
if (tree->editing == NULL || tree->textarea == NULL)
return;
element = tree->editing;
if (keep_changes) {
text_len = textarea_get_text(tree->textarea, NULL, 0);
text = malloc(text_len * sizeof(char));
if (text == NULL) {
LOG(("malloc failed"));
warn_user("NoMemory", 0);
textarea_destroy(tree->textarea);
tree->textarea = NULL;
return;
}
textarea_get_text(tree->textarea, text, text_len);
}
if (keep_changes && element->parent->user_callback != NULL) {
msg_data.msg = NODE_ELEMENT_EDIT_FINISHING;
msg_data.flag = element->flag;
msg_data.node = element->parent;
msg_data.data.text = text;
response = element->parent->user_callback(
element->parent->callback_data,
&msg_data);
switch (response) {
case NODE_CALLBACK_REJECT:
free(text);
text = NULL;
break;
case NODE_CALLBACK_CONTINUE:
free(text);
text = NULL;
return;
case NODE_CALLBACK_HANDLED:
case NODE_CALLBACK_NOT_HANDLED:
text = msg_data.data.text;
break;
}
}
textarea_destroy(tree->textarea);
tree->textarea = NULL;
tree->editing = NULL;
if (text != NULL)
tree_update_node_element(tree, element, text, NULL);
else
tree_handle_node_element_changed(tree, element, true);
tree_recalculate_size(tree);
if (element->parent->user_callback != NULL) {
msg_data.msg = keep_changes ? NODE_ELEMENT_EDIT_FINISHED :
NODE_ELEMENT_EDIT_CANCELLED;
msg_data.flag = element->flag;
msg_data.node = element->parent;
element->parent->user_callback(element->parent->callback_data,
&msg_data);
}
}
/**
* Delinks a node from the tree structures.
*
* \param tree the tree in which the delink takes place, may be NULL
* \param node the node to delink
*/
void tree_delink_node(struct tree *tree, struct node *node)
{
struct node *parent;
assert(node != NULL);
/* do not remove the root */
if (tree != NULL && node == tree->root)
return;
if ((tree != NULL) && (tree->editing != NULL)) {
parent = tree->editing->parent;
while (parent != NULL) {
if (node == parent) {
tree_stop_edit(tree, false);
break;
}
parent = parent->parent;
}
}
if (node->parent->child == node)
node->parent->child = node->next;
if (node->parent->last_child == node)
node->parent->last_child = node->previous;
parent = node->parent;
node->parent = NULL;
if (node->previous != NULL)
node->previous->next = node->next;
if (node->next != NULL)
node->next->previous = node->previous;
node->previous = NULL;
node->next = NULL;
tree_handle_node_changed(tree, parent, false, true);
}
/**
* Deletes a node from the tree.
*
* \param tree the tree to delete from, may be NULL
* \param node the node to delete
* \param siblings whether to delete all siblings
*/
static void tree_delete_node_internal(struct tree *tree, struct node *node,
bool siblings)
{
struct node *next, *child, *parent;
struct node_element *e, *f;
node_callback_resp response;
struct node_msg_data msg_data;
assert(node != NULL);
if (tree != NULL && tree->root == node)
return;
next = node->next;
parent = node->parent;
if (tree != NULL && parent == tree->root)
parent = NULL;
if ((tree != NULL) && (tree->def_folder == node))
tree->def_folder = NULL;
tree_delink_node(tree, node);
child = node->child;
node->child = NULL;
node->deleted = true;
if (child != NULL)
tree_delete_node_internal(tree, child, true);
if (!node->retain_in_memory) {
node->retain_in_memory = true;
for (e = &node->data; e != NULL; e = f) {
if (e->text != NULL) {
response = NODE_CALLBACK_NOT_HANDLED;
if (!e->editable &&
node->user_callback != NULL) {
msg_data.msg = NODE_DELETE_ELEMENT_TXT;
msg_data.flag = e->flag;
msg_data.node = node;
msg_data.data.text = (void *)e->text;
response = node->user_callback(
node->callback_data,
&msg_data);
}
if (response != NODE_CALLBACK_HANDLED)
free((void *)e->text);
e->text = NULL;
}
if (e->bitmap != NULL) {
response = NODE_CALLBACK_NOT_HANDLED;
if (node->user_callback != NULL) {
msg_data.msg = NODE_DELETE_ELEMENT_IMG;
msg_data.flag = e->flag;
msg_data.node = node;
msg_data.data.bitmap =
(void *)e->bitmap;
response = node->user_callback(
node->callback_data,
&msg_data);
}
/* TODO the type of this field is platform
dependent */
if (response != NODE_CALLBACK_HANDLED)
free(e->bitmap);
e->bitmap = NULL;
}
f = e->next;
if (e != &node->data)
free(e);
}
free(node);
}
if (siblings && next)
tree_delete_node_internal(tree, next, true);
if ((tree->flags & TREE_DELETE_EMPTY_DIRS) && parent != NULL &&
parent->child == NULL && !parent->deleted)
tree_delete_node_internal(tree, parent, false);
}
/**
* Deletes all nodes of a tree and the tree itself.
*
* \param tree the tree to be deleted
*/
void tree_delete(struct tree *tree)
{
tree->redraw = false;
treeview_test_fini(tree);
if (tree->root->child != NULL)
tree_delete_node_internal(tree, tree->root->child, true);
free((void *)tree->root->data.text);
free(tree->root);
free(tree);
}
/**
* Gets the redraw property of the given tree.
*
* \param tree the tree for which to retrieve the property
* \return the redraw property of the tree
*/
bool tree_get_redraw(struct tree *tree)
{
return tree->redraw;
}
/**
* Deletes a node from the tree.
*
* \param tree the tree to delete from, may be NULL
* \param node the node to delete
* \param siblings whether to delete all siblings
*/
void tree_delete_node(struct tree *tree, struct node *node, bool siblings)
{
int y = node->box.y;
int height = tree->height;
int width = tree->width;
bool redraw_setting = tree->redraw;
tree->redraw = false;
tree_delete_node_internal(tree, node, siblings);
tree_recalculate_node_positions(tree, tree->root);
tree->redraw = redraw_setting;
if (tree->redraw)
tree->callbacks->redraw_request(0, y,
width, height, tree->client_data);
tree_recalculate_size(tree);
}
/**
* Sets an icon for a node
*
* \param tree The tree to which node belongs, may be NULL
* \param node The node for which the icon is set
* \param icon the image to use
*/
void tree_set_node_icon(struct tree *tree, struct node *node,
hlcache_handle *icon)
{
node->data.type = NODE_ELEMENT_TEXT_PLUS_ICON;
tree_update_node_element(tree, &(node->data), NULL, icon);
}
/**
* Updates all siblings and descendants of a node to an expansion state.
* No update is performed for the tree changes.
*
* \param tree the tree to which 'node' belongs
* \param node the node to set all siblings and descendants of
* \param expanded the expansion state to set
*/
static void tree_set_node_expanded_all(struct tree *tree, struct node *node,
bool expanded)
{
for (; node != NULL; node = node->next) {
if (node->expanded != expanded) {
node->expanded = expanded;
tree_recalculate_node_sizes(tree, node, false);
}
if ((node->child != NULL) && (node->expanded))
tree_set_node_expanded_all(tree, node->child, expanded);
}
}
/**
* Updates [all siblings and descendants of] a node to an expansion state.
*
* \param tree the tree to update
* \param node the node to set [all siblings and descendants of]
* \param expanded the expansion state to set
* \param folder whether to update folders, if this together with leaf
* will be false only 'node' will be updated
* \param leaf whether to update leaves (check also description for folder)
* \return whether any changes were made
*/
static bool tree_set_node_expanded_internal(struct tree *tree,
struct node *node, bool expanded, bool folder, bool leaf)
{
bool redraw = false;
struct node *end = (folder == false && leaf == false) ?
node->next : NULL;
if (tree->editing != NULL && node == tree->editing->parent)
tree_stop_edit(tree, false);
for (; node != end; node = node->next) {
if ((node->expanded != expanded) && (node != tree->root) &&
((folder && (node->folder)) ||
(leaf && (!node->folder)) ||
(!folder && !leaf))) {
node->expanded = expanded;
if (node->child != NULL)
tree_set_node_expanded_all(tree,
node->child, false);
if ((node->data.next != NULL) &&
(node->data.next->box.height == 0))
tree_recalculate_node_sizes(tree, node, true);
else
tree_recalculate_node_sizes(tree, node, false);
redraw = true;
}
if ((folder || leaf) && (node->child != NULL) &&
(node->expanded))
redraw |= tree_set_node_expanded_internal(tree,
node->child, expanded, folder, leaf);
}
return redraw;
}
/**
* Updates [all siblings and descendants of] a node to an expansion state.
*
* \param tree the tree to update
* \param node the node to set [all siblings and descendants of]
* \param expanded the expansion state to set
* \param folder whether to update folders, if this together with leaf
* will be false only 'node' will be updated
* \param leaf whether to update leaves (check also description for folder)
*/
void tree_set_node_expanded(struct tree *tree, struct node *node, bool expanded,
bool folder, bool leaf)
{
if (tree_set_node_expanded_internal(tree, node, expanded, folder, leaf))
tree_handle_node_changed(tree, node, false, true);
}
/**
* Updates a node to an selected state. The required areas of the tree are
* redrawn.
*
* \param tree the tree to update nodes for, may be NULL
* \param node the node to set all siblings and descendants of
* \param all if true update node together with its siblings and
* descendants
* \param selected the selection state to set
*/
void tree_set_node_selected(struct tree *tree, struct node *node, bool all,
bool selected)
{
struct node *end;
if (tree != NULL && node == tree->root)
node = tree->root->child;
if (node == NULL)
return;
end = all ? NULL : node->next;
for (; node != end; node = node->next) {
if (node->selected != selected) {
node->selected = selected;
if (tree != NULL && tree->redraw)
tree->callbacks->redraw_request(
node->data.box.x,
node->data.box.y,
node->data.box.width,
node->data.box.height,
tree->client_data);
}
if (all && (node->child != NULL) && (node->expanded))
tree_set_node_selected(tree, node->child, all,
selected);
}
}
/**
* Sets the sort function for a node
*
* \param tree the tree to which 'node' belongs, may be NULL
* \param node the node to be inserted
* \param sort pointer to the sorting function
*/
void tree_set_node_sort_function(struct tree *tree, struct node *node,
int (*sort) (struct node *, struct node *))
{
struct node *child;
node->sort = sort;
if (tree != NULL && tree->editing != NULL)
tree_stop_edit(tree, false);
/* the node had already some children so they must get sorted */
if (node->child != NULL) {
child = node->child;
node->child = NULL;
while (child != NULL) {
tree_sort_insert(node, child);
child = child->next;
}
}
if (tree != NULL)
tree_recalculate_node_positions(tree, node->child);
}
/**
* Sets the delete callback for a node.
*
* \param node the node for which the callback is set
* \param callback the callback functions to be set
* \param data user data to be passed to callback
*/
void tree_set_node_user_callback(struct node *node,
tree_node_user_callback callback, void *data)
{
node->user_callback = callback;
node->callback_data = data;
}
/**
* Sets the redraw property to the given value. If redraw is true, the tree will
* be redrawn on layout/appearance changes.
*
* \param tree the tree for which the property is set
* \param redraw the value to set
*/
void tree_set_redraw(struct tree *tree, bool redraw)
{
/* the tree might have no graphical representation, do not set the
redraw flag in such case */
if (tree->callbacks == NULL)
return;
tree->redraw = redraw;
}
/**
* Checks whether a node, its siblings or any children are selected.
*
* \param node the root node to check from
* \return whether 'node', its siblings or any children are selected.
*/
bool tree_node_has_selection(struct node *node)
{
for (; node != NULL; node = node->next) {
if (node->selected)
return true;
if ((node->child != NULL) && (node->expanded) &&
(tree_node_has_selection(node->child)))
return true;
}
return false;
}
/**
* Returns the current value of the nodes deleted property.
*
* \param node the node to be checked
* \return the current value of the nodes deleted property
*/
bool tree_node_is_deleted(struct node *node)
{
return node->deleted;
}
/**
* Returns true if the node is a folder
*
* \param node the node to be checked
* \return true if the node is a folder, false otherwise
*/
bool tree_node_is_folder(struct node *node)
{
return node->folder;
}
/**
* Returns true if the node is the default folder for a tree
*
* \param node the node to be checked
* \return true if the node is a default folder, false otherwise
*/
bool tree_node_is_default(struct node *node)
{
return node->def_folder;
}
/**
* Update the text of a node element if it has changed.
*
* \param element The node element to update.
* \param text The text to update the element with. The ownership of
* this string is taken by this function and must not be
* referred to after the function exits.
*/
bool tree_update_element_text(struct tree *tree,
struct node_element *element, char *text)
{
const char *node_text; /* existing node text */
if (text == NULL)
return false;
if (element == NULL) {
free(text);
return false;
}
node_text = tree_node_element_get_text(element);
if ((node_text == NULL) || (strcmp(node_text, text) != 0)) {
tree_update_node_element(tree, element, text, NULL);
} else {
/* text does not need changing, free it */
free(text);
}
return true;
}
/**
* Updates the content of a node_element.
*
* \param tree the tree owning element, may be NULL
* \param element the element to be updated
* \param text new text to be set, may be NULL
* \param bitmap new bitmap to be set, may be NULL
*/
void tree_update_node_element(struct tree *tree, struct node_element *element,
const char *text, void *bitmap)
{
node_callback_resp response;
struct node_msg_data msg_data;
bool text_changed = false;
assert(element != NULL);
if ((tree != NULL) && (element == tree->editing)) {
tree_stop_edit(tree, false);
}
if ((text != NULL) &&
(element->type == NODE_ELEMENT_TEXT ||
element->type == NODE_ELEMENT_TEXT_PLUS_ICON)) {
if (element->text != NULL) {
if (strcmp(element->text, text) == 0) {
text_changed = true;
}
response = NODE_CALLBACK_NOT_HANDLED;
if ((!element->editable) &&
(element->parent->user_callback != NULL)) {
msg_data.msg = NODE_DELETE_ELEMENT_TXT;
msg_data.flag = element->flag;
msg_data.node = element->parent;
msg_data.data.text = (void *)element->text;
response = element->parent->user_callback(
element->parent->callback_data,
&msg_data);
}
if (response != NODE_CALLBACK_HANDLED) {
free(element->text);
}
}
element->text = (char *)text;
}
if ((bitmap != NULL) &&
((element->type == NODE_ELEMENT_BITMAP) ||
(element->type == NODE_ELEMENT_TEXT_PLUS_ICON))) {
if (element->bitmap != NULL) {
response = NODE_CALLBACK_NOT_HANDLED;
if (element->parent->user_callback != NULL) {
msg_data.msg = NODE_DELETE_ELEMENT_IMG;
msg_data.flag = element->flag;
msg_data.node = element->parent;
msg_data.data.bitmap = (void *)element->bitmap;
response = element->parent->user_callback(
element->parent->callback_data,
&msg_data);
}
if (response != NODE_CALLBACK_HANDLED) {
free(element->bitmap);
}
} else {
/* Increase the box width to accomodate the new icon */
element->box.width += NODE_INSTEP;
}
element->bitmap = bitmap;
}
tree_handle_node_element_changed(tree, element, text_changed);
}
/**
* Returns the node element's text
*
* \return the node element's text
*/
const char *tree_node_element_get_text(struct node_element *element)
{
return element->text;
}
/**
* Returns the node element's icon
*
* \return the node element's icon
*/
struct bitmap *tree_node_element_get_icon(struct node_element *element)
{
return element->bitmap;
}
/**
* Get the root node of a tree
*
* \param tree the tree to get the root of
* \return the root of the tree
*/
struct node *tree_get_root(struct tree *tree)
{
return tree->root;
}
/**
* Returns whether the current tree is being edited at this time
*
* \param tree the tree to be checked
* \return true if the tree is currently being edited
*/
bool tree_is_edited(struct tree *tree)
{
return tree->editing == NULL ? false : true;
}
/**
* Get the drag state of a tree
*
* \param tree the tree to get the state of
* \return drag type (defined in desktop/tree.h)
*/
tree_drag_type tree_drag_status(struct tree *tree)
{
return tree->drag;
}
/**
* Get the default node of a tree for additions
*
* \param tree the tree to get the default node of
* \return the default node
*/
struct node *tree_get_default_folder_node(struct tree *tree)
{
if (tree->def_folder != NULL) {
return tree->def_folder;
} else {
return tree_get_root(tree);
}
}
/**
* Set the default node of a tree to the selected node
*
* \param tree the tree to set the default node of
* \param node the node to set as default (NULL for selected node)
* \return success
*/
bool tree_set_default_folder_node(struct tree *tree, struct node *node)
{
struct node *sel_node;
if (node == NULL) {
sel_node = tree_get_selected_node(tree->root);
} else {
sel_node = node;
}
if((sel_node == NULL) ||
(tree_node_is_folder(sel_node) == false)) {
return false;
}
tree_clear_default_folder_node(tree);
tree->def_folder = sel_node;
sel_node->def_folder = true;
tree_handle_node_changed(tree, sel_node, true, false);
return true;
}
/**
* Clear the default node of a tree
*
* \param tree the tree to clear the default node of
*/
void tree_clear_default_folder_node(struct tree *tree)
{
struct node *def_node = NULL;
def_node = tree_get_default_folder_node(tree);
if (def_node != NULL) {
tree->def_folder = NULL;
def_node->def_folder = false;
tree_handle_node_changed(tree, def_node, true, false);
}
}
/**
* Returns the parent of a node
*
* \param node the node to get the parent of
* \return the node's parent
*/
struct node *tree_node_get_parent(struct node *node)
{
return node->parent;
}
/**
* Returns the first child of a node
*
* \param node the node to get the child of
* \return the nodes first child
*/
struct node *tree_node_get_child(struct node *node)
{
return node->child;
}
/**
* Returns the closest sibling a node
*
* \param node the node to get the sibling of
* \return the nodes sibling
*/
struct node *tree_node_get_next(struct node *node)
{
return node->next;
}
/**
* Draws an element's expansion icon
*
* \param tree the tree to draw the expansion for
* \param element the element to draw the expansion for
* \param tree_x X coordinate of the tree
* \param tree_y Y coordinate of the tree
* \param ctx current redraw context
*/
static void tree_draw_node_expansion_toggle(struct tree *tree,
struct node *node, int tree_x, int tree_y,
const struct redraw_context *ctx)
{
const struct plotter_table *plot = ctx->plot;
int x, y;
assert(tree != NULL);
assert(node != NULL);
if ((node->child != NULL) || (node->data.next != NULL)) {
x = tree_x + node->box.x - (NODE_INSTEP / 2) - 4;
y = tree_y + node->box.y + (TREE_LINE_HEIGHT - 9) / 2;
plot->rectangle(x, y, x + 9, y + 9,
&plot_style_fill_tree_furniture);
plot->rectangle(x , y, x + 8, y + 8,
&plot_style_stroke_tree_furniture);
plot->line(x + 2, y + 4, x + 7, y + 4,
&plot_style_stroke_tree_furniture);
if (!node->expanded)
plot->line(x + 4, y + 2, x + 4, y + 7,
&plot_style_stroke_tree_furniture);
}
}
/**
* Draws an element, including any expansion icons
*
* \param tree the tree to draw an element for
* \param element the element to draw
* \param tree_x X coordinate to draw the tree at (wrt plot origin)
* \param tree_y Y coordinate to draw the tree at (wrt plot origin)
* \param clip clipping rectangle (wrt plot origin)
* \param ctx current redraw context
*/
static void tree_draw_node_element(struct tree *tree,
struct node_element *element, int tree_x, int tree_y,
const struct rect *clip, const struct redraw_context *ctx)
{
const struct plotter_table *plot = ctx->plot;
struct bitmap *bitmap = NULL;
int x, y, width;
bool selected = false;
bool def_folder = false;
hlcache_handle *icon;
plot_font_style_t *fstyle;
const int icon_inset = (TREE_LINE_HEIGHT - TREE_ICON_SIZE) / 2;
assert(tree != NULL);
assert(element != NULL);
assert(element->parent != NULL);
x = tree_x + element->box.x;
y = tree_y + element->box.y;
width = element->box.width;
if (&element->parent->data == element) {
if (element->parent->selected)
selected = true;
if (element->parent->def_folder)
def_folder = true;
}
switch (element->type) {
case NODE_ELEMENT_TEXT_PLUS_ICON:
icon = element->bitmap;
if (icon != NULL && (content_get_status(icon) ==
CONTENT_STATUS_READY ||
content_get_status(icon) ==
CONTENT_STATUS_DONE) &&
x + TREE_ICON_SIZE > clip->x0 &&
x < clip->x1) {
struct rect c;
/* Clip to image area */
c.x0 = x;
c.y0 = y + icon_inset;
c.x1 = x + TREE_ICON_SIZE;
c.y1 = y + icon_inset + TREE_ICON_SIZE;
if (c.x0 < clip->x0) c.x0 = clip->x0;
if (c.y0 < clip->y0) c.y0 = clip->y0;
if (c.x1 > clip->x1) c.x1 = clip->x1;
if (c.y1 > clip->y1) c.y1 = clip->y1;
if (c.x1 > c.x0 && c.y1 > c.y0) {
/* Valid clip rectangles only */
struct content_redraw_data data;
plot->clip(&c);
data.x = x;
data.y = y + icon_inset;
data.width = TREE_ICON_SIZE;
data.height = TREE_ICON_SIZE;
data.background_colour = 0xFFFFFF;
data.scale = 1;
data.repeat_x = false;
data.repeat_y = false;
content_redraw(icon, &data, &c, ctx);
/* Restore previous clipping area */
plot->clip(clip);
}
}
x += NODE_INSTEP;
width -= NODE_INSTEP;
/* fall through */
case NODE_ELEMENT_TEXT:
if (element->text == NULL || clip->x1 < x)
break;
if (element == tree->editing)
return;
if (selected) {
if (def_folder == true)
fstyle = &plot_fstyle_selected_def_folder;
else
fstyle = &plot_fstyle_selected;
plot->rectangle(x, y, x + width,
y + element->box.height,
&plot_style_fill_tree_selected);
} else {
if (def_folder == true)
fstyle = &plot_fstyle_def_folder;
else
fstyle = &plot_fstyle;
}
plot->text(x + 4, y + (TREE_LINE_HEIGHT * 3 + 2) / 4,
element->text, strlen(element->text),
fstyle);
break;
case NODE_ELEMENT_BITMAP:
bitmap = element->bitmap;
if (bitmap == NULL)
break;
plot->bitmap(x, y, element->box.width - 1,
element->box.height - 2,
bitmap, 0xFFFFFF, BITMAPF_NONE);
if (!(tree->flags & TREE_NO_FURNITURE))
plot->rectangle(x, y, x + element->box.width - 1,
y + element->box.height - 3,
&plot_style_stroke_tree_furniture);
break;
}
}
/**
* Redraws a node.
*
* \param tree the tree to draw
* \param node the node to draw children and siblings of
* \param tree_x X coordinate to draw the tree at (wrt plot origin)
* \param tree_y Y coordinate to draw the tree at (wrt plot origin)
* \param clip clipping rectangle (wrt plot origin)
* \param ctx current redraw context
*/
static void tree_draw_node(struct tree *tree, struct node *node,
int tree_x, int tree_y, struct rect clip,
const struct redraw_context *ctx)
{
const struct plotter_table *plot = ctx->plot;
struct node_element *element;
struct node *parent;
int x0, y0, x1, y1;
struct rect node_extents;
assert(tree != NULL);
assert(node != NULL);
/* Find node's extents, including children's area */
node_extents.x0 = tree_x + node->box.x - NODE_INSTEP;
node_extents.y0 = tree_y + node->box.y;
node_extents.x1 = tree_x + node->box.x + node->box.width + NODE_INSTEP;
if (node->next != NULL)
node_extents.y1 = tree_y + node->next->box.y;
else
node_extents.y1 = tree_y + node->box.y + node->box.height;
/* Nothing to draw, if node is outside clip region */
if ((node_extents.x1 < clip.x0) && (node_extents.y1 < clip.y0) &&
(node_extents.x0 > clip.x1) &&
(node_extents.y0 > clip.y1)) {
return;
}
/* Intersect clip region with node's extents */
if (clip.x0 < node_extents.x0) clip.x0 = node_extents.x0;
if (clip.y0 < node_extents.y0) clip.y0 = node_extents.y0;
if (clip.x1 > node_extents.x1) clip.x1 = node_extents.x1;
if (clip.y1 > node_extents.y1) clip.y1 = node_extents.y1;
if (clip.x0 >= clip.x1 || clip.y0 >= clip.y1) {
/* Invalid clip rectangle */
return;
}
/* Set up the clipping area */
plot->clip(&clip);
/* Draw node's furniture */
if (!(tree->flags & TREE_NO_FURNITURE)) {
/* Display furniture */
if (node->previous != NULL) {
/* There is a node above this
* Display furniture; line connecting up to previous */
x0 = x1 = tree_x + node->box.x - (NODE_INSTEP / 2);
y0 = tree_y + node->previous->box.y;
y1 = tree_y + node->box.y + (TREE_LINE_HEIGHT / 2);
plot->line(x0, y0, x1, y1,
&plot_style_stroke_tree_furniture);
}
if (node->next != NULL) {
/* There is a node below this
* Display furniture; line connecting down to next */
x0 = x1 = tree_x + node->box.x - (NODE_INSTEP / 2);
y0 = tree_y + node->box.y + (TREE_LINE_HEIGHT / 2);
y1 = tree_y + node->next->box.y;
plot->line(x0, y0, x1, y1,
&plot_style_stroke_tree_furniture);
}
parent = node->parent;
if ((parent != NULL) && (parent != tree->root) &&
(parent->child == node)) {
/* Node is first child */
x0 = x1 = tree_x + parent->box.x + (NODE_INSTEP / 2);
y0 = tree_y + parent->data.box.y +
parent->data.box.height;
y1 = y0 + (TREE_LINE_HEIGHT / 2);
plot->line(x0, y0, x1, y1,
&plot_style_stroke_tree_furniture);
}
/* Line from expansion toggle to icon */
x0 = tree_x + node->box.x - (NODE_INSTEP / 2);
x1 = x0 + (NODE_INSTEP / 2) - 2;
y0 = y1 = tree_y + node->data.box.y + node->data.box.height -
(TREE_LINE_HEIGHT / 2);
plot->line(x0, y0, x1, y1, &plot_style_stroke_tree_furniture);
tree_draw_node_expansion_toggle(tree, node,
tree_x, tree_y, ctx);
}
/* Draw node's element(s)
* NOTE: node's children are handled later in tree_draw_tree() */
if (node->expanded) {
for (element = &node->data; element != NULL;
element = element->next) {
/* Draw each element of expanded node */
tree_draw_node_element(tree, element, tree_x, tree_y,
&clip, ctx);
}
} else {
/* Draw main title element of node */
tree_draw_node_element(tree, &node->data, tree_x, tree_y,
&clip, ctx);
}
}
/**
* Redraws a node's descendants.
*
* \param tree the tree to draw
* \param node the node to draw children and siblings of
* \param tree_x X coordinate to draw the tree at (wrt plot origin)
* \param tree_y Y coordinate to draw the tree at (wrt plot origin)
* \param clip clipping rectangle (wrt plot origin)
* \param ctx current redraw context
*/
static void tree_draw_tree(struct tree *tree, struct node *node,
int tree_x, int tree_y, struct rect clip,
const struct redraw_context *ctx)
{
struct node *child;
assert(tree != NULL);
assert(node != NULL);
for (child = node->child; child != NULL; child = child->next) {
/* Draw children that are inside the clip region */
if (child->next != NULL &&
(child->next->box.y + tree_y < clip.y0))
/* Child is above clip region */
continue;
if (child->box.y + tree_y > clip.y1)
/* Child is below clip region
* further siblings will be too */
return;
/* Draw current child */
tree_draw_node(tree, child, tree_x, tree_y, clip, ctx);
/* And its children */
if ((child->child != NULL) && (child->expanded)) {
/* Child has children and they are visible */
tree_draw_tree(tree, child, tree_x, tree_y, clip, ctx);
}
}
}
/**
* Redraws a tree.
*
* \param tree the tree to draw
* \param x X coordinate to draw the tree at (wrt plot origin)
* \param y Y coordinate to draw the tree at (wrt plot origin)
* \param clip_x minimum x of the clipping rectangle (wrt tree origin)
* \param clip_y minimum y of the clipping rectangle (wrt tree origin)
* \param clip_width width of the clipping rectangle
* \param clip_height height of the clipping rectangle
* \param ctx current redraw context
*/
void tree_draw(struct tree *tree, int x, int y,
int clip_x, int clip_y, int clip_width, int clip_height,
const struct redraw_context *ctx)
{
struct redraw_context new_ctx = *ctx;
struct rect clip;
assert(tree != NULL);
assert(tree->root != NULL);
if (treeview_test_redraw(tree, x, y, clip_x, clip_y,
clip_width, clip_height, ctx)) {
return;
}
/* Start knockout rendering if it's available for this plotter */
if (ctx->plot->option_knockout)
knockout_plot_start(ctx, &new_ctx);
/* Set up clip rectangle */
clip.x0 = x + clip_x;
clip.y0 = y + clip_y;
clip.x1 = clip.x0 + clip_width;
clip.y1 = clip.y0 + clip_height;
new_ctx.plot->clip(&clip);
/* Flat fill extents of clipping area */
new_ctx.plot->rectangle(clip.x0, clip.y0, clip.x1, clip.y1,
&plot_style_fill_tree_background);
/* don't draw empty trees or trees with redraw flag set to false */
if (tree->root->child != NULL && tree->redraw) {
/* Draw the tree */
tree_draw_tree(tree, tree->root, x, y, clip, &new_ctx);
/* Draw textarea, if present */
if (tree->editing != NULL) {
x = x + tree->editing->box.x;
y = y + tree->editing->box.y;
if (tree->editing->type == NODE_ELEMENT_TEXT_PLUS_ICON)
x += NODE_INSTEP;
textarea_redraw(tree->textarea, x, y,
plot_style_fill_tree_background.
fill_colour,
1.0, &clip, &new_ctx);
}
}
/* Rendering complete */
if (ctx->plot->option_knockout)
knockout_plot_end();
}
/**
* Finds a node element from a node with a specific user_type
*
* \param node the node to examine
* \param flag user assinged flag used is searches
* \param after if this is not NULL the search will start after the given
* node_element
* \return the corresponding element
*/
struct node_element *tree_node_find_element(struct node *node,
unsigned int flag, struct node_element *after)
{
struct node_element *element;
if (after == NULL)
element = &node->data;
else {
assert(after->parent == node);
element = after->next;
}
for (; element != NULL; element = element->next)
if (element->flag == flag) return element;
return NULL;
}
/**
* Deletes all selected nodes from the tree.
*
* \param tree the tree to delete from
* \param node the node to delete
*/
void tree_delete_selected_nodes(struct tree *tree, struct node *node)
{
struct node *next;
int y = node->box.y;
int height = tree->height;
int width = tree->width;
bool redraw_setting = tree->redraw;
tree->redraw = false;
if (node == tree->root) {
if (node->child != NULL)
tree_delete_selected_nodes(tree, node->child);
tree->redraw = redraw_setting;
if (tree->redraw)
tree->callbacks->redraw_request(0, y,
width, height,
tree->client_data);
return;
}
while (node != NULL) {
next = node->next;
if (node->selected)
tree_delete_node(tree, node, false);
else if (node->child != NULL)
tree_delete_selected_nodes(tree, node->child);
node = next;
}
tree->redraw = redraw_setting;
if (tree->redraw)
tree->callbacks->redraw_request(0, y,
width, height,
tree->client_data);
}
/**
* Returns the selected node, or NULL if multiple nodes are selected.
*
* \param node the node to search sibling and children
* \return the selected node, or NULL if multiple nodes are selected
*/
struct node *tree_get_selected_node(struct node *node)
{
struct node *result = NULL;
struct node *temp;
for (; node != NULL; node = node->next) {
if (node->selected) {
if (result != NULL)
return NULL;
result = node;
}
if ((node->child != NULL) && (node->expanded)) {
temp = tree_get_selected_node(node->child);
if (temp != NULL) {
if (result != NULL)
return NULL;
else
result = temp;
}
}
}
return result;
}
/**
* Finds a node element at a specific location.
*
* \param node the root node to check from
* \param x the x co-ordinate
* \param y the y co-ordinate
* \param expansion_toggle whether the coordinate was in an expansion toggle
* \return the node at the specified position, or NULL for none
*/
static struct node_element *tree_get_node_element_at(struct node *node,
int x, int y, bool *expansion_toggle)
{
struct node_element *element;
int x0, x1, y0, y1;
*expansion_toggle = false;
for (; node != NULL; node = node->next) {
if (node->box.y > y) return NULL;
if ((node->box.x - NODE_INSTEP < x) && (node->box.y < y) &&
(node->box.x + node->box.width >= x) &&
(node->box.y + node->box.height >= y)) {
if (node->expanded) {
for (element = &node->data; element != NULL;
element = element->next) {
x0 = element->box.x;
y0 = element->box.y;
x1 = element->box.x +
element->box.width;
y1 = element->box.y +
element->box.height;
if ((x0 < x) && (y0 < y) && (x1 >= x)
&& (y1 >= y))
return element;
}
} else {
x0 = node->data.box.x;
y0 = node->data.box.y;
x1 = node->data.box.x + node->data.box.width;
y1 = node->data.box.y + node->data.box.height;
if ((x0 < x) && (y0 < y) && (x1 >= x) &&
(y1>= y))
return &node->data;
}
if (((node->child != NULL) ||
(node->data.next != NULL)) &&
(node->data.box.x - NODE_INSTEP + 4 < x)
&& (node->data.box.y + 4 < y) &&
(node->data.box.x > x) &&
(node->data.box.y + TREE_LINE_HEIGHT > y)) {
/* Node either has node children, or node
* has more than one element.
* Coordinate is over node expansion toggle area
*/
*expansion_toggle = true;
return &node->data;
}
}
element = tree_get_node_element_at(node->child, x, y,
expansion_toggle);
if ((node->child != NULL) && (node->expanded) &&
(element != NULL))
return element;
}
return NULL;
}
/**
* Finds a node at a specific location.
*
* \param root the root node to check from
* \param x the x co-ordinate
* \param y the y co-ordinate
* \param expansion_toggle whether the coordinate was in an expansion toggle
* \return the node at the specified position, or NULL for none
*/
static struct node *tree_get_node_at(struct node *root, int x, int y,
bool *expansion_toggle)
{
struct node_element *result;
if ((result = tree_get_node_element_at(root, x, y, expansion_toggle)))
return result->parent;
return NULL;
}
/**
* Gets link characteristics to insert a node at a specified position.
*
* \param tree the tree to find link information for
* \param x the x co-ordinate
* \param y the y co-ordinate
* \param before set to whether the node should be linked before on exit
* \return the node to link with
*/
struct node *tree_get_link_details(struct tree *tree, int x, int y,
bool *before)
{
struct node *node = NULL;
bool expansion_toggle;
assert(tree != NULL);
assert(tree->root != NULL);
*before = false;
if (tree->root->child != NULL)
node = tree_get_node_at(tree->root->child, x, y,
&expansion_toggle);
if ((node == NULL) || (expansion_toggle))
return tree->root;
if (y < (node->box.y + (node->box.height / 2))) {
*before = true;
} else if ((node->folder) && (node->expanded) &&
(node->child != NULL)) {
node = node->child;
*before = true;
}
return node;
}
/**
* Launches all the selected nodes of the tree
*
* \param tree the tree for which all nodes will be launched
* \param node the node which will be checked together with its children
* \param tabs launch node in a new tab instead of a new window
*/
static void tree_launch_selected_internal(struct tree *tree, struct node *node,
bool tabs)
{
struct node_msg_data msg_data;
msg_data.data.bw = NULL;
for (; node != NULL; node = node->next) {
if (node->selected && node->user_callback != NULL) {
msg_data.msg = NODE_LAUNCH;
if (tabs == true) {
msg_data.flag = TREE_ELEMENT_LAUNCH_IN_TABS;
} else {
msg_data.flag = TREE_ELEMENT_TITLE;
}
msg_data.node = node;
node->user_callback(node->callback_data, &msg_data);
}
if (node->child != NULL)
tree_launch_selected_internal(tree, node->child, tabs);
}
}
/**
* Launches all the selected nodes of the tree
*
* \param tree the tree for which all nodes will be launched
* \param tabs launch nodes in new tabs instead of new windows
*/
void tree_launch_selected(struct tree *tree, bool tabs)
{
if (tree->root->child != NULL)
tree_launch_selected_internal(tree, tree->root->child, tabs);
}
/**
* Updates the node at position x,y to a selected state.
* The required areas of the tree are redrawn.
*
* \param tree the tree to update nodes for, may be NULL
* \param x x position in tree
* \param y y position in tree
* \param selected the selection state to set
*/
void tree_set_node_selected_at(struct tree *tree, int x, int y, bool selected)
{
bool expansion_toggle;
struct node *node;
node = tree_get_node_at(tree->root, x, y, &expansion_toggle);
if ((node == NULL) || (expansion_toggle == true))
return;
tree_set_node_selected(tree, node, false, selected);
}
/**
* Handles a mouse action for a tree
*
* \param tree the tree to handle a click for
* \param mouse the mouse state
* \param x X coordinate of mouse action
* \param y Y coordinate of mouse action
* \return whether the click was handled
*/
bool tree_mouse_action(struct tree *tree, browser_mouse_state mouse, int x,
int y)
{
bool expansion_toggle;
struct node *node;
struct node *last;
struct node_element *element;
struct node_msg_data msg_data;
bool double_click_1 = mouse & BROWSER_MOUSE_DOUBLE_CLICK &&
mouse & BROWSER_MOUSE_CLICK_1;
bool double_click_2 = mouse & BROWSER_MOUSE_DOUBLE_CLICK &&
mouse & BROWSER_MOUSE_CLICK_2;
assert(tree != NULL);
assert(tree->root != NULL);
if (treeview_test_mouse_action(tree, mouse, x, y)) {
return true;
}
if (tree->root->child == NULL)
return true;
element = tree_get_node_element_at(tree->root->child, x, y,
&expansion_toggle);
/* pass in-textarea mouse action and drags which started in it
to the textarea */
if (tree->editing != NULL) {
int x0, x1, y0, y1;
x0 = tree->editing->box.x;
if (tree->editing->type == NODE_ELEMENT_TEXT_PLUS_ICON)
x0 += NODE_INSTEP;
x1 = tree->editing->box.x + tree->editing->box.width;
y0 = tree->editing->box.y;
y1 = tree->editing->box.y + tree->ta_height;
if (tree->drag == TREE_TEXTAREA_DRAG &&
(mouse & (BROWSER_MOUSE_HOLDING_1 |
BROWSER_MOUSE_HOLDING_2))) {
/* Track the drag path */
textarea_mouse_action(tree->textarea, mouse,
x - x0, y - y0);
return true;
}
if ((x >= x0) && (x < x1) && (y >= y0) && (y < y1)) {
textarea_mouse_action(tree->textarea, mouse,
x - x0, y - y0);
return true;
}
}
/* we are not interested in the drag path, return */
if (mouse & (BROWSER_MOUSE_HOLDING_1 | BROWSER_MOUSE_HOLDING_2))
return true;
/* cancel edit */
if (tree->editing != NULL)
tree_stop_edit(tree, false);
/* no item either means cancel selection on (select) click or a drag */
if (element == NULL) {
if (tree->flags & TREE_SINGLE_SELECT) {
tree_set_node_selected(tree, tree->root->child, true,
false);
return true;
}
if (mouse & (BROWSER_MOUSE_CLICK_1 | BROWSER_MOUSE_DRAG_1))
tree_set_node_selected(tree, tree->root->child, true,
false);
if (mouse & (BROWSER_MOUSE_DRAG_1 | BROWSER_MOUSE_DRAG_2)) {
/** @todo the tree window has to scroll the tree when
* mouse reaches border while dragging this isn't
* solved for the browser window too.
*/
tree->drag = TREE_SELECT_DRAG;
}
return true;
}
node = element->parent;
/* A click on expansion toggle or double click on folder toggles node
* expansion */
if (((expansion_toggle) && (mouse & (BROWSER_MOUSE_CLICK_1 |
BROWSER_MOUSE_CLICK_2))) ||
(((!expansion_toggle) && (node->child != NULL)) &&
(double_click_1 || double_click_2))) {
/* clear any selection */
tree_set_node_selected(tree, tree->root->child, true, false);
/* expand / contract node and redraw */
tree_set_node_expanded(tree, node, !node->expanded,
false, false);
/* find the last child node if expanded */
last = node;
if ((last->child != NULL) && (last->expanded)) {
last = last->child;
while ((last->next != NULL) ||
((last->child != NULL) &&
(last->expanded))) {
if (last->next != NULL)
last = last->next;
else
last = last->child;
}
}
/* scroll to the bottom element then back to the top */
element = &last->data;
if (last->expanded)
for (; element->next != NULL; element = element->next);
tree->callbacks->scroll_visible(element->box.y,
element->box.height,
tree->client_data);
tree->callbacks->scroll_visible(node->data.box.y,
node->data.box.height,
tree->client_data);
return true;
}
/* no use for any other expansion toggle click */
if (expansion_toggle)
return true;
/* single/double ctrl+click or alt+click starts editing */
if ((element->editable) && (!tree->editing) &&
((element->type == NODE_ELEMENT_TEXT) ||
(element->type == NODE_ELEMENT_TEXT_PLUS_ICON)) &&
(mouse & BROWSER_MOUSE_CLICK_1 || double_click_1) &&
(mouse & BROWSER_MOUSE_MOD_2 ||
mouse & BROWSER_MOUSE_MOD_3)) {
tree_set_node_selected(tree, tree->root->child, true, false);
tree_start_edit(tree, element);
return true;
}
/* double click launches the leaf */
if (double_click_1 || double_click_2) {
if (node->user_callback == NULL)
return false;
msg_data.msg = NODE_LAUNCH;
msg_data.flag = TREE_ELEMENT_TITLE;
msg_data.node = node;
if (node->user_callback(node->callback_data, &msg_data) !=
NODE_CALLBACK_HANDLED)
return false;
return true;
}
/* single click (select) cancels current selection and selects item */
if (mouse & BROWSER_MOUSE_CLICK_1 || (mouse & BROWSER_MOUSE_CLICK_2 &&
tree->flags & TREE_SINGLE_SELECT)) {
if (tree->flags & TREE_NO_SELECT)
return true;
if (!node->selected) {
tree_set_node_selected(tree, tree->root->child, true,
false);
node->selected = true;
tree_handle_node_element_changed(tree, &node->data, false);
}
return true;
}
/* single click (adjust) toggles item selection */
if (mouse & BROWSER_MOUSE_CLICK_2) {
if (tree->flags & TREE_NO_SELECT)
return true;
node->selected = !node->selected;
tree_handle_node_element_changed(tree, &node->data, false);
return true;
}
/* drag starts a drag operation */
if ((!tree->editing) && (mouse & (BROWSER_MOUSE_DRAG_1 |
BROWSER_MOUSE_DRAG_2))) {
if (tree->flags & TREE_NO_DRAGS)
return true;
if (!node->selected) {
tree_set_node_selected(tree, tree->root->child, true,
false);
node->selected = true;
tree_handle_node_element_changed(tree, &node->data, false);
}
if (tree->flags & TREE_MOVABLE)
tree->drag = TREE_MOVE_DRAG;
else tree->drag = TREE_UNKNOWN_DRAG;
return true;
}
return false;
}
/**
* Updates the selected state for a region of nodes.
*
* \param tree the tree to update
* \param node the node to update children and siblings of
* \param y the minimum y of the selection rectangle
* \param height the height of the selection rectangle
* \param invert whether to invert the selected state
*/
static void tree_handle_selection_area_node(struct tree *tree,
struct node *node, int y, int height, bool invert)
{
struct node_element *element;
struct node *update;
int y_max;
int y0, y1;
assert(tree != NULL);
assert(node != NULL);
y_max = y + height;
for (; node != NULL; node = node->next) {
if (node->box.y > y_max) return;
y0 = node->box.y;
y1 = node->box.y + node->box.height;
if ((y0 < y_max) && (y1 >= y)) {
update = NULL;
if (node->expanded) {
for (element = &node->data; element != NULL;
element = element->next) {
y0 = element->box.y;
y1 = element->box.y +
element->box.height;
if ((y0 < y_max) && (y1 >= y)) {
update = element->parent;
break;
}
}
} else {
y0 = node->data.box.y;
y1 = node->data.box.y + node->data.box.height;
if ((y0 < y_max) && (y1 >= y))
update = node->data.parent;
}
if ((update) && (node != tree->root)) {
if (invert) {
node->selected = !node->selected;
tree_handle_node_element_changed(tree,
&node->data, false);
} else if (!node->selected) {
node->selected = true;
tree_handle_node_element_changed(tree,
&node->data, false);
}
}
}
if ((node->child != NULL) && (node->expanded))
tree_handle_selection_area_node(tree, node->child, y,
height, invert);
}
}
/**
* Updates the selected state for a region of nodes.
*
* \param tree the tree to update
* \param y the minimum y of the selection rectangle
* \param height the height of the selection rectangle
* \param invert whether to invert the selected state
*/
static void tree_handle_selection_area(struct tree *tree, int y, int height,
bool invert)
{
assert(tree != NULL);
assert(tree->root != NULL);
if (tree->root->child == NULL)
return;
if (height < 0) {
y += height;
height = -height;
}
tree_handle_selection_area_node(tree, tree->root->child, y, height,
invert);
}
/**
* Clears the processing flag.
*
* \param node the node to process siblings and children of
*/
static void tree_clear_processing(struct node *node)
{
for (; node != NULL; node = node->next) {
node->processing = false;
if (node->child != NULL)
tree_clear_processing(node->child);
}
}
/**
* Sets the processing flag to the selection state.
*
* \param node the node to process siblings and children of
*/
static void tree_selected_to_processing(struct node *node)
{
for (; node != NULL; node = node->next) {
node->processing = node->selected;
if ((node->child != NULL) && (node->expanded))
tree_selected_to_processing(node->child);
}
}
/**
* Moves the first node in a tree with the processing flag set.
*
* \param tree the tree in which the move takes place
* \param node the node to move siblings/children of
* \param link the node to link before/as a child (folders) or before/after
* (link)
* \param before whether to link siblings before or after the supplied node
* \param first whether to always link after the supplied node (ie not
* inside of folders)
* \return the node moved
*/
static struct node *tree_move_processing_node(struct tree *tree,
struct node *node, struct node *link, bool before, bool first)
{
struct node *result;
bool folder = link->folder;
for (; node != NULL; node = node->next) {
if (node->processing) {
node->processing = false;
tree_delink_node(tree, node);
if (!first)
link->folder = false;
tree_link_node(tree, link, node, before);
if (!first)
link->folder = folder;
return node;
}
if (node->child != NULL) {
result = tree_move_processing_node(tree, node->child,
link, before, first);
if (result != NULL)
return result;
}
}
return NULL;
}
/**
* Moves nodes within a tree.
*
* \param tree the tree to process
* \param destination the node to link before/as a child (folders)
* or before/after (link)
* \param before whether to link siblings before or after the supplied
* node
*/
static void tree_move_selected_nodes(struct tree *tree,
struct node *destination, bool before)
{
struct node *link;
struct node *test;
bool error;
tree_clear_processing(tree->root);
tree_selected_to_processing(tree->root);
/* the destination node cannot be a child of any node with
the processing flag set */
error = destination->processing;
for (test = destination; test != NULL; test = test->parent)
error |= test->processing;
if (error) {
tree_clear_processing(tree->root);
return;
}
if ((destination->folder) && (!destination->expanded) && (!before)) {
tree_set_node_expanded(tree, destination, true, false, false);
}
link = tree_move_processing_node(tree, tree->root, destination, before,
true);
while (link != NULL)
link = tree_move_processing_node(tree, tree->root, link, false,
false);
tree_clear_processing(tree->root);
tree_recalculate_node_positions(tree, tree->root);
if (tree->redraw)
tree->callbacks->redraw_request(0, 0, tree->width, tree->height,
tree->client_data);
}
/**
* Handle the end of a drag operation
*
* \param tree the tree on which the drag was performed
* \param mouse mouse state during drag end
* \param x0 x coordinate of drag start
* \param y0 y coordinate of drag start
* \param x1 x coordinate of drag end
* \param y1 y coordinate of drag end
*/
void tree_drag_end(struct tree *tree, browser_mouse_state mouse, int x0, int y0,
int x1, int y1)
{
bool before;
struct node *node;
int x, y;
if (tree->flags & TREE_MOVABLE) {
if (treeview_test_mouse_action(tree, BROWSER_MOUSE_HOVER,
x1, y1)) {
return;
}
}
switch (tree->drag) {
case TREE_NO_DRAG:
case TREE_UNKNOWN_DRAG:
break;
case TREE_TEXTAREA_DRAG:
x = tree->editing->box.x;
y = tree->editing->box.y;
if (tree->editing->type == NODE_ELEMENT_TEXT_PLUS_ICON)
x += NODE_INSTEP;
textarea_mouse_action(tree->textarea, BROWSER_MOUSE_HOVER,
x1 - x, y1 - y);
break;
case TREE_SELECT_DRAG:
tree_handle_selection_area(tree, y0, y1 - y0,
(mouse | BROWSER_MOUSE_HOLDING_2));
break;
case TREE_MOVE_DRAG:
if (!(tree->flags & TREE_MOVABLE))
return;
node = tree_get_link_details(tree, x1, y1, &before);
tree_move_selected_nodes(tree, node, before);
break;
}
tree->drag = TREE_NO_DRAG;
}
/**
* Key press handling for a tree.
*
* \param tree The tree which got the keypress
* \param key The ucs4 character codepoint
* \return true if the keypress is dealt with, false otherwise.
*/
bool tree_keypress(struct tree *tree, uint32_t key)
{
if (treeview_test_keypress(tree, key)) {
return true;
}
if (tree->editing != NULL)
switch (key) {
case KEY_ESCAPE:
tree_stop_edit(tree, false);
return true;
case KEY_NL:
case KEY_CR:
tree_stop_edit(tree, true);
return true;
default:
return textarea_keypress(tree->textarea, key);
}
return false;
}
/**
* Alphabetical comparison function for nodes
*
* \param n1 first node to compare
* \param n2 first node to compare
* \return 0 if equal, greater then zero if n1 > n2,
* less then zero if n2 < n1
*/
int tree_alphabetical_sort(struct node *n1, struct node *n2)
{
return strcmp(n1->data.text, n2->data.text);
}
/**
* Redraw requests from the textarea are piped through this because we have to
* check the redraw flag of the tree before requesting a redraw and change the
* position to tree origin relative.
*/
static void tree_textarea_callback(void *data, struct textarea_msg *msg)
{
struct tree *tree = data;
int x, y;
switch (msg->type) {
case TEXTAREA_MSG_DRAG_REPORT:
if (msg->data.drag == TEXTAREA_DRAG_NONE) {
/* Textarea drag finished */
tree->drag = TREE_NO_DRAG;
} else {
/* Textarea drag started */
tree->drag = TREE_TEXTAREA_DRAG;
}
break;
case TEXTAREA_MSG_REDRAW_REQUEST:
x = msg->data.redraw.x0 + tree->editing->box.x;
y = msg->data.redraw.y0 + tree->editing->box.y;
if (tree->editing->type == NODE_ELEMENT_TEXT_PLUS_ICON)
x += NODE_INSTEP;
/* Redraw the textarea */
if (tree->redraw)
tree->callbacks->redraw_request(x, y,
msg->data.redraw.x1 -
msg->data.redraw.x0,
msg->data.redraw.y1 -
msg->data.redraw.y0,
tree->client_data);
break;
default:
break;
}
}
/**
* Starts editing a node_element
*
* \param tree The tree to which element belongs
* \param element The element to start being edited
*/
void tree_start_edit(struct tree *tree, struct node_element *element)
{
struct node *parent;
int width, height;
textarea_setup ta_setup;
textarea_flags ta_flags;
assert(tree != NULL);
assert(element != NULL);
if (tree->editing != NULL)
tree_stop_edit(tree, true);
parent = element->parent;
if (&parent->data == element)
parent = parent->parent;
for (; parent != NULL; parent = parent->parent) {
if (!parent->expanded) {
tree_set_node_expanded(tree, parent, true,
false, false);
}
}
tree->editing = element;
tree->callbacks->get_window_dimensions(&width, NULL, tree->client_data);
width -= element->box.x;
height = element->box.height;
if (element->type == NODE_ELEMENT_TEXT_PLUS_ICON)
width -= NODE_INSTEP;
tree->ta_height = height;
ta_flags = TEXTAREA_INTERNAL_CARET;
ta_setup.width = width;
ta_setup.height = tree->ta_height;
ta_setup.pad_top = 0;
ta_setup.pad_right = 4;
ta_setup.pad_bottom = 0;
ta_setup.pad_left = 4;
ta_setup.border_width = 1;
ta_setup.border_col = 0x000000;
ta_setup.selected_text = 0xffffff;
ta_setup.selected_bg = 0x000000;
ta_setup.text = plot_fstyle;
ta_setup.text.foreground = 0x000000;
ta_setup.text.background = 0xffffff;
tree->textarea = textarea_create(ta_flags, &ta_setup,
tree_textarea_callback, tree);
if (tree->textarea == NULL) {
tree_stop_edit(tree, false);
return;
}
textarea_set_text(tree->textarea, element->text);
tree_handle_node_element_changed(tree, element, true);
tree_recalculate_size(tree);
tree->callbacks->scroll_visible(element->box.y, element->box.height,
tree->client_data);
}
/**
* Callback for fetchcache(). Should be removed once bitmaps get loaded directly
* from disc
*/
static nserror tree_icon_callback(hlcache_handle *handle,
const hlcache_event *event, void *pw)
{
return NSERROR_OK;
}
/**
* Tree utility function. Placed here so that this code doesn't have to be
* copied by each user.
*
* \param name the name of the loaded icon, if it's not a full path the icon is
* looked for in the directory specified by tree_icons_dir
* \return the icon in form of a content or NULL on failure
*/
hlcache_handle *tree_load_icon(const char *name)
{
char *url = NULL;
const char *icon_url = NULL;
int len;
hlcache_handle *c;
nserror err;
nsurl *icon_nsurl;
/** @todo something like bitmap_from_disc is needed here */
if (!strncmp(name, "file://", 7)) {
icon_url = name;
} else {
char *native_path;
if (tree_icons_dir == NULL)
return NULL;
/* path + separator + leafname + '\0' */
len = strlen(tree_icons_dir) + 1 + strlen(name) + 1;
native_path = malloc(len);
if (native_path == NULL) {
LOG(("malloc failed"));
warn_user("NoMemory", 0);
return NULL;
}
/* Build native path */
memcpy(native_path, tree_icons_dir,
strlen(tree_icons_dir) + 1);
path_add_part(native_path, len, name);
/* Convert native path to URL */
url = path_to_url(native_path);
free(native_path);
icon_url = url;
}
err = nsurl_create(icon_url, &icon_nsurl);
if (err != NSERROR_OK) {
if (url != NULL)
free(url);
return NULL;
}
/* Fetch the icon */
err = hlcache_handle_retrieve(icon_nsurl, 0, 0, 0,
tree_icon_callback, 0, 0,
CONTENT_IMAGE, &c);
nsurl_unref(icon_nsurl);
/* If we built the URL here, free it */
if (url != NULL)
free(url);
if (err != NSERROR_OK) {
return NULL;
}
return c;
}