mirror of
https://github.com/netsurf-browser/netsurf
synced 2024-12-26 22:09:43 +03:00
55aa7af80f
Only supports creation, destruction, and redraw atm. TODO: input handling (mouse, keyboard), editing, using the node callbacks. Also includes new core_window.h.
1105 lines
24 KiB
C
1105 lines
24 KiB
C
/*
|
|
* 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 "desktop/gui.h"
|
|
#include "desktop/knockout.h"
|
|
#include "desktop/plotters.h"
|
|
#include "desktop/treeview.h"
|
|
#include "render/font.h"
|
|
#include "utils/log.h"
|
|
|
|
#define FIELD_FOLDER 0
|
|
#define FIELD_FIRST_ENTRY 1
|
|
|
|
struct treeview_globals {
|
|
int line_height;
|
|
int furniture_width;
|
|
int step_width;
|
|
int window_padding;
|
|
int icon_step;
|
|
} tree_g;
|
|
|
|
enum treeview_node_type {
|
|
TREE_NODE_ROOT,
|
|
TREE_NODE_FOLDER,
|
|
TREE_NODE_ENTRY
|
|
};
|
|
|
|
struct treeview_text {
|
|
const char *data;
|
|
uint32_t len;
|
|
int width;
|
|
};
|
|
|
|
struct treeview_field {
|
|
enum treeview_field_flags flags;
|
|
|
|
lwc_string *field;
|
|
struct treeview_text value;
|
|
};
|
|
|
|
struct treeview_node {
|
|
bool expanded;
|
|
enum treeview_node_type type;
|
|
|
|
uint32_t height;
|
|
|
|
struct treeview_node *parent;
|
|
struct treeview_node *sibling_prev;
|
|
struct treeview_node *sibling_next;
|
|
struct treeview_node *children;
|
|
|
|
void *client_data;
|
|
|
|
struct treeview_field text;
|
|
};
|
|
|
|
struct treeview_node_entry {
|
|
struct treeview_node *base;
|
|
struct treeview_field fields[];
|
|
};
|
|
|
|
struct treeview {
|
|
uint32_t view_height;
|
|
uint32_t view_width;
|
|
|
|
struct treeview_node *root;
|
|
|
|
struct treeview_field *fields;
|
|
int n_fields; /* fields[n_fields] is folder, lower are entry fields */
|
|
int field_width;
|
|
|
|
const struct treeview_callback_table *callbacks;
|
|
const struct core_window_callback_table *cw_t; /**< Core window callback table */
|
|
const struct core_window *cw_h; /**< Core window handle */
|
|
};
|
|
|
|
|
|
struct treeview_node_style {
|
|
plot_style_t bg; /**< Background */
|
|
plot_font_style_t text; /**< Text */
|
|
|
|
plot_style_t sbg; /**< Selected background */
|
|
plot_font_style_t stext; /**< Selected text */
|
|
|
|
plot_style_t sabg; /**< Selection area background */
|
|
plot_font_style_t satext; /**< Selection area text */
|
|
};
|
|
|
|
struct treeview_node_style plot_style_odd;
|
|
struct treeview_node_style plot_style_even;
|
|
|
|
struct treeview_resource {
|
|
const char *url;
|
|
struct hlcache_handle *c;
|
|
int height;
|
|
bool ready;
|
|
};
|
|
enum treeview_resource_id {
|
|
TREE_RES_CONTENT = 0,
|
|
TREE_RES_FOLDER,
|
|
TREE_RES_SEARCH,
|
|
TREE_RES_LAST
|
|
};
|
|
static struct treeview_resource treeview_res[TREE_RES_LAST] = {
|
|
{ "resource:icons/content.png", NULL, 0, false },
|
|
{ "resource:icons/directory.png", NULL, 0, false },
|
|
{ "resource:icons/search.png", NULL, 0, false }
|
|
};
|
|
|
|
|
|
|
|
enum treeview_furniture_id {
|
|
TREE_FURN_EXPAND = 0,
|
|
TREE_FURN_CONTRACT,
|
|
TREE_FURN_LAST
|
|
};
|
|
static struct treeview_text treeview_furn[TREE_FURN_LAST] = {
|
|
{ "\xe2\x96\xb8", 3, 0 },
|
|
{ "\xe2\x96\xbe", 3, 0 }
|
|
};
|
|
|
|
|
|
static nserror treeview_create_node_root(struct treeview_node **root)
|
|
{
|
|
struct treeview_node *n;
|
|
|
|
n = malloc(sizeof(struct treeview_node));
|
|
if (n == NULL) {
|
|
return NSERROR_NOMEM;
|
|
}
|
|
|
|
n->expanded = true;
|
|
n->type = TREE_NODE_ROOT;
|
|
|
|
n->height = 0;
|
|
|
|
n->text.flags = TREE_FLAG_NONE;
|
|
n->text.field = NULL;
|
|
n->text.value.data = NULL;
|
|
n->text.value.len = 0;
|
|
n->text.value.width = 0;
|
|
|
|
n->parent = NULL;
|
|
n->sibling_next = NULL;
|
|
n->sibling_prev = NULL;
|
|
n->children = NULL;
|
|
|
|
n->client_data = NULL;
|
|
|
|
*root = n;
|
|
|
|
return NSERROR_OK;
|
|
}
|
|
|
|
/**
|
|
* Insert a treeview node into a treeview
|
|
*
|
|
* \param a parentless node to insert
|
|
* \param b tree node to insert a as a relation of
|
|
* \param rel a's relationship to b
|
|
*/
|
|
static inline void treeview_insert_node(struct treeview_node *a,
|
|
struct treeview_node *b,
|
|
enum treeview_relationship rel)
|
|
{
|
|
assert(a != NULL);
|
|
assert(a->parent == NULL);
|
|
assert(b != NULL);
|
|
|
|
switch (rel) {
|
|
case TREE_REL_CHILD:
|
|
assert(b->type != TREE_NODE_ENTRY);
|
|
a->parent = b;
|
|
a->sibling_next = b->children;
|
|
if (a->sibling_next)
|
|
a->sibling_next->sibling_prev = a;
|
|
b->children = a;
|
|
break;
|
|
|
|
case TREE_REL_SIBLING_NEXT:
|
|
assert(b->type != TREE_NODE_ROOT);
|
|
a->sibling_prev = b;
|
|
a->sibling_next = b->sibling_next;
|
|
a->parent = b->parent;
|
|
b->sibling_next = a;
|
|
if (a->sibling_next)
|
|
a->sibling_next->sibling_prev = a;
|
|
break;
|
|
|
|
default:
|
|
assert(0);
|
|
break;
|
|
}
|
|
|
|
assert(a->parent != NULL);
|
|
|
|
if (a->parent->expanded) {
|
|
/* Parent is expanded, so inserted node will be visible and
|
|
* affect layout */
|
|
b = a;
|
|
do {
|
|
b->parent->height += b->height;
|
|
b = b->parent;
|
|
} while (b->parent != NULL);
|
|
|
|
if (a->text.value.width == 0) {
|
|
nsfont.font_width(&plot_style_odd.text,
|
|
a->text.value.data,
|
|
a->text.value.len,
|
|
&(a->text.value.width));
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
nserror treeview_create_node_folder(struct treeview *tree,
|
|
struct treeview_node **folder,
|
|
struct treeview_node *relation,
|
|
enum treeview_relationship rel,
|
|
const struct treeview_field_data *field,
|
|
void *data)
|
|
{
|
|
struct treeview_node *n;
|
|
|
|
assert(data != NULL);
|
|
assert(tree != NULL);
|
|
assert(tree->root != NULL);
|
|
|
|
if (relation == NULL) {
|
|
relation = tree->root;
|
|
rel = TREE_REL_CHILD;
|
|
}
|
|
|
|
n = malloc(sizeof(struct treeview_node));
|
|
if (n == NULL) {
|
|
return NSERROR_NOMEM;
|
|
}
|
|
|
|
n->expanded = false;
|
|
n->type = TREE_NODE_FOLDER;
|
|
|
|
n->height = tree_g.line_height;
|
|
|
|
n->text.value.data = field->value;
|
|
n->text.value.len = field->value_len;
|
|
n->text.value.width = 0;
|
|
|
|
n->parent = NULL;
|
|
n->sibling_next = NULL;
|
|
n->sibling_prev = NULL;
|
|
n->children = NULL;
|
|
|
|
n->client_data = data;
|
|
|
|
treeview_insert_node(n, relation, rel);
|
|
|
|
*folder = n;
|
|
|
|
return NSERROR_OK;
|
|
}
|
|
|
|
|
|
|
|
nserror treeview_update_node_entry(struct treeview *tree,
|
|
struct treeview_node *entry,
|
|
const struct treeview_field_data fields[],
|
|
void *data)
|
|
{
|
|
bool match;
|
|
struct treeview_node_entry *e = (struct treeview_node_entry *)entry;
|
|
int i;
|
|
|
|
assert(data != NULL);
|
|
assert(tree != NULL);
|
|
assert(entry != NULL);
|
|
assert(data == entry->client_data);
|
|
assert(entry->parent != NULL);
|
|
|
|
assert(fields != NULL);
|
|
assert(fields[0].field != NULL);
|
|
assert(lwc_string_isequal(tree->fields[0].field,
|
|
fields[0].field, &match) == lwc_error_ok &&
|
|
match == true);
|
|
entry->text.value.data = fields[0].value;
|
|
entry->text.value.len = fields[0].value_len;
|
|
entry->text.value.width = 0;
|
|
|
|
if (entry->parent->expanded) {
|
|
/* Text will be seen, get its width */
|
|
nsfont.font_width(&plot_style_odd.text,
|
|
entry->text.value.data,
|
|
entry->text.value.len,
|
|
&(entry->text.value.width));
|
|
} else {
|
|
/* Just invalidate the width, since it's not needed now */
|
|
entry->text.value.width = 0;
|
|
}
|
|
|
|
for (i = 1; i < tree->n_fields; i++) {
|
|
assert(fields[i].field != NULL);
|
|
assert(lwc_string_isequal(tree->fields[i].field,
|
|
fields[i].field, &match) == lwc_error_ok &&
|
|
match == true);
|
|
|
|
e->fields[i].value.data = fields[i].value;
|
|
e->fields[i].value.len = fields[i].value_len;
|
|
|
|
if (entry->expanded) {
|
|
/* Text will be seen, get its width */
|
|
nsfont.font_width(&plot_style_odd.text,
|
|
e->fields[i].value.data,
|
|
e->fields[i].value.len,
|
|
&(e->fields[i].value.width));
|
|
} else {
|
|
/* Invalidate the width, since it's not needed yet */
|
|
entry->text.value.width = 0;
|
|
}
|
|
}
|
|
|
|
return NSERROR_OK;
|
|
}
|
|
|
|
|
|
nserror treeview_create_node_entry(struct treeview *tree,
|
|
struct treeview_node **entry,
|
|
struct treeview_node *relation,
|
|
enum treeview_relationship rel,
|
|
const struct treeview_field_data fields[],
|
|
void *data)
|
|
{
|
|
bool match;
|
|
struct treeview_node_entry *e;
|
|
struct treeview_node *n;
|
|
int i;
|
|
|
|
assert(data != NULL);
|
|
assert(tree != NULL);
|
|
assert(tree->root != NULL);
|
|
|
|
if (relation == NULL) {
|
|
relation = tree->root;
|
|
rel = TREE_REL_CHILD;
|
|
}
|
|
|
|
e = malloc(sizeof(struct treeview_node_entry) +
|
|
tree->n_fields * sizeof(struct treeview_field));
|
|
if (e == NULL) {
|
|
return NSERROR_NOMEM;
|
|
}
|
|
|
|
n = (struct treeview_node *) e;
|
|
|
|
n->expanded = false;
|
|
n->type = TREE_NODE_ENTRY;
|
|
|
|
n->height = tree_g.line_height;
|
|
|
|
assert(fields != NULL);
|
|
assert(fields[0].field != NULL);
|
|
assert(lwc_string_isequal(tree->fields[0].field,
|
|
fields[0].field, &match) == lwc_error_ok &&
|
|
match == true);
|
|
n->text.value.data = fields[0].value;
|
|
n->text.value.len = fields[0].value_len;
|
|
n->text.value.width = 0;
|
|
|
|
n->parent = NULL;
|
|
n->sibling_next = NULL;
|
|
n->sibling_prev = NULL;
|
|
n->children = NULL;
|
|
|
|
n->client_data = data;
|
|
|
|
for (i = 1; i < tree->n_fields; i++) {
|
|
assert(fields[i].field != NULL);
|
|
assert(lwc_string_isequal(tree->fields[i].field,
|
|
fields[i].field, &match) == lwc_error_ok &&
|
|
match == true);
|
|
|
|
e->fields[i].value.data = fields[i].value;
|
|
e->fields[i].value.len = fields[i].value_len;
|
|
e->fields[i].value.width = 0;
|
|
}
|
|
|
|
treeview_insert_node(n, relation, rel);
|
|
|
|
*entry = n;
|
|
|
|
return NSERROR_OK;
|
|
}
|
|
|
|
|
|
nserror treeview_delete_node(struct treeview_node *n)
|
|
{
|
|
/* Destroy children first */
|
|
while (n->children != NULL) {
|
|
treeview_delete_node(n->children);
|
|
}
|
|
|
|
/* Unlink node from tree */
|
|
if (n->parent != NULL && n->parent->children == n) {
|
|
/* Node is a first child */
|
|
n->parent->children = n->sibling_next;
|
|
|
|
} else if (n->sibling_prev != NULL) {
|
|
/* Node is not first child */
|
|
n->sibling_prev->sibling_next = n->sibling_next;
|
|
}
|
|
|
|
if (n->sibling_next != NULL) {
|
|
/* Always need to do this */
|
|
n->sibling_next->sibling_prev = n->sibling_prev;
|
|
}
|
|
|
|
/* Handle any special treatment */
|
|
switch (n->type) {
|
|
case TREE_NODE_ENTRY:
|
|
break;
|
|
case TREE_NODE_FOLDER:
|
|
break;
|
|
case TREE_NODE_ROOT:
|
|
break;
|
|
default:
|
|
return NSERROR_BAD_PARAMETER;
|
|
}
|
|
|
|
/* Free the node */
|
|
free(n);
|
|
|
|
return NSERROR_OK;
|
|
}
|
|
|
|
|
|
nserror treeview_create(struct treeview **tree,
|
|
const struct treeview_callback_table *callbacks,
|
|
int n_fields, struct treeview_field_desc fields[],
|
|
const struct core_window_callback_table *cw_t,
|
|
const struct core_window *cw)
|
|
{
|
|
nserror error;
|
|
int i;
|
|
|
|
assert(cw_t != NULL);
|
|
assert(cw != NULL);
|
|
assert(callbacks != NULL);
|
|
|
|
assert(fields != NULL);
|
|
assert(fields[0].flags & TREE_FLAG_DEFAULT);
|
|
assert(fields[n_fields - 1].flags & TREE_FLAG_DEFAULT);
|
|
assert(n_fields >= 2);
|
|
|
|
*tree = malloc(sizeof(struct treeview));
|
|
if (*tree == NULL) {
|
|
return NSERROR_NOMEM;
|
|
}
|
|
|
|
(*tree)->fields = malloc(sizeof(struct treeview_field) * n_fields);
|
|
if ((*tree)->fields == NULL) {
|
|
free(tree);
|
|
return NSERROR_NOMEM;
|
|
}
|
|
|
|
error = treeview_create_node_root(&((*tree)->root));
|
|
if (error != NSERROR_OK) {
|
|
free((*tree)->fields);
|
|
free(*tree);
|
|
return error;
|
|
}
|
|
|
|
(*tree)->field_width = 0;
|
|
for (i = 0; i < n_fields; i++) {
|
|
struct treeview_field *f = &((*tree)->fields[i]);
|
|
|
|
f->flags = fields[i].flags;
|
|
f->field = lwc_string_ref(fields[i].field);
|
|
f->value.data = lwc_string_data(fields[i].field);
|
|
f->value.len = lwc_string_length(fields[i].field);
|
|
|
|
nsfont.font_width(&plot_style_odd.text, f->value.data,
|
|
f->value.len, &(f->value.width));
|
|
|
|
if (f->flags & TREE_FLAG_SHOW_NAME)
|
|
if ((*tree)->field_width < f->value.width)
|
|
(*tree)->field_width = f->value.width;
|
|
}
|
|
|
|
(*tree)->field_width += tree_g.step_width;
|
|
|
|
(*tree)->callbacks = callbacks;
|
|
(*tree)->n_fields = n_fields - 1;
|
|
|
|
(*tree)->cw_t = cw_t;
|
|
(*tree)->cw_h = cw;
|
|
|
|
return NSERROR_OK;
|
|
}
|
|
|
|
nserror treeview_destroy(struct treeview *tree)
|
|
{
|
|
int f;
|
|
|
|
assert(tree != NULL);
|
|
|
|
/* Destroy nodes */
|
|
treeview_delete_node(tree->root);
|
|
|
|
/* Destroy feilds */
|
|
for (f = 0; f <= tree->n_fields; f++) {
|
|
lwc_string_unref(tree->fields[f].field);
|
|
}
|
|
free(tree->fields);
|
|
|
|
/* Free treeview */
|
|
free(tree);
|
|
|
|
return NSERROR_OK;
|
|
}
|
|
|
|
|
|
/* Walk a treeview subtree, calling a callback at each node (depth first)
|
|
*
|
|
* \param root Root to walk tree from (doesn't get a callback call)
|
|
* \param callback Function to call on each node
|
|
* \param ctx Context to pass to callback
|
|
* \return true iff callback caused premature abort
|
|
*/
|
|
static bool treeview_walk(struct treeview_node *root,
|
|
bool (*callback)(struct treeview_node *node,
|
|
int inset, void *ctx),
|
|
void *ctx)
|
|
{
|
|
struct treeview_node *node;
|
|
int inset = tree_g.window_padding;
|
|
|
|
node = root;
|
|
|
|
while (node != NULL) {
|
|
struct treeview_node *next = node->children;
|
|
|
|
if (next != NULL) {
|
|
/* down to children */
|
|
node = next;
|
|
inset += tree_g.step_width;
|
|
} else {
|
|
/* no children */
|
|
next = node->sibling_next;
|
|
|
|
if (next != NULL) {
|
|
/* on to next sibling */
|
|
node = next;
|
|
} else {
|
|
/* no next sibling */
|
|
while (node != root) {
|
|
next = node->sibling_next;
|
|
|
|
if (next != NULL) {
|
|
break;
|
|
}
|
|
|
|
node = node->parent;
|
|
inset -= tree_g.step_width;
|
|
}
|
|
|
|
if (node == root)
|
|
break;
|
|
|
|
node = node->sibling_next;
|
|
}
|
|
}
|
|
|
|
assert(node != NULL);
|
|
assert(node != root);
|
|
|
|
if (callback(node, inset, ctx)) {
|
|
/* callback caused early termination */
|
|
return true;
|
|
}
|
|
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
nserror treeview_node_expand(struct treeview *tree,
|
|
struct treeview_node *node)
|
|
{
|
|
struct treeview_node *child;
|
|
struct treeview_node_entry *e;
|
|
int additional_height = 0;
|
|
int i;
|
|
|
|
assert(tree != NULL);
|
|
assert(node != NULL);
|
|
|
|
if (node->expanded) {
|
|
/* What madness is this? */
|
|
LOG(("Tried to expand an expanded node."));
|
|
return NSERROR_OK;
|
|
}
|
|
|
|
switch (node->type) {
|
|
case TREE_NODE_FOLDER:
|
|
child = node->children;
|
|
if (child == NULL) {
|
|
/* Can't expand an empty node */
|
|
return NSERROR_OK;
|
|
}
|
|
|
|
do {
|
|
assert(child->expanded == false);
|
|
if (child->text.value.width == 0) {
|
|
nsfont.font_width(&plot_style_odd.text,
|
|
child->text.value.data,
|
|
child->text.value.len,
|
|
&(child->text.value.width));
|
|
}
|
|
|
|
additional_height += child->height;
|
|
|
|
child = child->sibling_next;
|
|
} while (child != NULL);
|
|
|
|
break;
|
|
|
|
case TREE_NODE_ENTRY:
|
|
assert(node->children == NULL);
|
|
|
|
e = (struct treeview_node_entry *)node;
|
|
|
|
for (i = 1; i < tree->n_fields; i++) {
|
|
|
|
if (e->fields[i].value.width == 0) {
|
|
nsfont.font_width(&plot_style_odd.text,
|
|
e->fields[i].value.data,
|
|
e->fields[i].value.len,
|
|
&(e->fields[i].value.width));
|
|
}
|
|
|
|
/* Add height for field */
|
|
additional_height += tree_g.line_height;
|
|
}
|
|
|
|
break;
|
|
|
|
case TREE_NODE_ROOT:
|
|
assert(node->type != TREE_NODE_ROOT);
|
|
break;
|
|
}
|
|
|
|
/* Update the node */
|
|
node->expanded = true;
|
|
|
|
/* And parent's heights */
|
|
do {
|
|
node->height += additional_height;
|
|
node = node->parent;
|
|
} while (node->parent != NULL);
|
|
|
|
node->height += additional_height;
|
|
|
|
return NSERROR_OK;
|
|
}
|
|
|
|
|
|
static bool treeview_node_contract_cb(struct treeview_node *node, int inset,
|
|
void *ctx)
|
|
{
|
|
int height_reduction;
|
|
|
|
assert(node != NULL);
|
|
assert(node->type != TREE_NODE_ROOT);
|
|
|
|
if (node->expanded == false) {
|
|
/* Nothing to do. */
|
|
return false;
|
|
}
|
|
|
|
height_reduction = node->height - tree_g.line_height;
|
|
|
|
assert(height_reduction >= 0);
|
|
|
|
do {
|
|
node->height -= height_reduction;
|
|
node = node->parent;
|
|
} while (node->parent != NULL);
|
|
|
|
return false; /* Don't want to abort tree walk */
|
|
}
|
|
nserror treeview_node_contract(struct treeview *tree,
|
|
struct treeview_node *node)
|
|
{
|
|
assert(node != NULL);
|
|
|
|
if (node->expanded == false) {
|
|
/* What madness is this? */
|
|
LOG(("Tried to contract a contracted node."));
|
|
return NSERROR_OK;
|
|
}
|
|
|
|
/* Contract children. */
|
|
treeview_walk(node, treeview_node_contract_cb, NULL);
|
|
|
|
/* Contract node */
|
|
treeview_node_contract_cb(node, 0, NULL);
|
|
|
|
return NSERROR_OK;
|
|
}
|
|
|
|
/**
|
|
* Redraws a treeview.
|
|
*
|
|
* \param tree the tree to draw
|
|
* \param x X coordinate to draw the tree at (wrt plot origin)
|
|
* \param y Y coordinate to draw the tree at (wrt plot origin)
|
|
* \param clip_x clipping rectangle (wrt tree origin)
|
|
* \param ctx current redraw context
|
|
*/
|
|
void treeview_redraw(struct treeview *tree, int x, int y, struct rect *clip,
|
|
const struct redraw_context *ctx)
|
|
{
|
|
struct redraw_context new_ctx = *ctx;
|
|
struct treeview_node *node, *root;
|
|
struct treeview_node_entry *entry;
|
|
struct treeview_node_style *style = &plot_style_odd;
|
|
struct content_redraw_data data;
|
|
struct rect r;
|
|
int inset = tree_g.window_padding - tree_g.step_width;
|
|
uint32_t count = 0;
|
|
int render_y = 0;
|
|
int x0, y0, y1;
|
|
int baseline = (tree_g.line_height * 3 + 2) / 4;
|
|
enum treeview_resource_id res;
|
|
|
|
assert(tree != NULL);
|
|
assert(tree->root != NULL);
|
|
assert(tree->root->expanded == true);
|
|
|
|
/* Start knockout rendering if it's available for this plotter */
|
|
if (ctx->plot->option_knockout)
|
|
knockout_plot_start(ctx, &new_ctx);
|
|
|
|
/* Set up clip rectangle */
|
|
r.x0 = clip->x0 + x;
|
|
r.y0 = clip->y0 + y;
|
|
r.x1 = clip->x1 + x;
|
|
r.y1 = clip->y1 + y;
|
|
new_ctx.plot->clip(&r);
|
|
|
|
/* Draw the tree */
|
|
node = root = tree->root;
|
|
|
|
/* Setup common content redraw data */
|
|
data.width = 17;
|
|
data.height = 17;
|
|
data.scale = 1;
|
|
data.repeat_x = false;
|
|
data.repeat_y = false;
|
|
|
|
while (node != NULL) {
|
|
int i;
|
|
struct treeview_node *next = node->expanded ?
|
|
node->children : NULL;
|
|
|
|
if (next != NULL) {
|
|
/* down to children */
|
|
node = next;
|
|
inset += tree_g.step_width;
|
|
} else {
|
|
/* no children */
|
|
next = node->sibling_next;
|
|
|
|
if (next != NULL) {
|
|
/* on to next sibling */
|
|
node = next;
|
|
} else {
|
|
/* no next sibling */
|
|
while (node != root) {
|
|
next = node->sibling_next;
|
|
|
|
if (next != NULL) {
|
|
break;
|
|
}
|
|
node = node->parent;
|
|
inset -= tree_g.step_width;
|
|
}
|
|
|
|
if (node == root)
|
|
break;
|
|
|
|
node = node->sibling_next;
|
|
}
|
|
}
|
|
|
|
assert(node != NULL);
|
|
assert(node != root);
|
|
|
|
count++;
|
|
|
|
if (render_y + tree_g.line_height < clip->y0) {
|
|
/* This node's line is above clip region */
|
|
render_y += tree_g.line_height;
|
|
continue;
|
|
}
|
|
|
|
style = (count & 0x1) ? &plot_style_odd : &plot_style_even;
|
|
|
|
/* Render background */
|
|
y0 = render_y;
|
|
y1 = render_y + tree_g.line_height;
|
|
new_ctx.plot->rectangle(r.x0, y0, r.x1, y1, &(style->bg));
|
|
|
|
/* Render toggle */
|
|
if (node->expanded) {
|
|
new_ctx.plot->text(inset, render_y + baseline,
|
|
treeview_furn[TREE_FURN_CONTRACT].data,
|
|
treeview_furn[TREE_FURN_CONTRACT].len,
|
|
&(style->text));
|
|
} else {
|
|
new_ctx.plot->text(inset, render_y + baseline,
|
|
treeview_furn[TREE_FURN_EXPAND].data,
|
|
treeview_furn[TREE_FURN_EXPAND].len,
|
|
&(style->text));
|
|
}
|
|
|
|
/* Render icon */
|
|
if (node->type == TREE_NODE_ENTRY)
|
|
res = TREE_RES_CONTENT;
|
|
else if (node->type == TREE_NODE_FOLDER)
|
|
res = TREE_NODE_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 = style->bg.fill_colour;
|
|
|
|
content_redraw(treeview_res[res].c,
|
|
&data, &r, &new_ctx);
|
|
}
|
|
|
|
/* Render text */
|
|
x0 = inset + tree_g.step_width + tree_g.icon_step;
|
|
new_ctx.plot->text(x0, render_y + baseline,
|
|
node->text.value.data, node->text.value.len,
|
|
&(style->text));
|
|
|
|
/* Rendered the node */
|
|
render_y += tree_g.line_height;
|
|
|
|
if (render_y > clip->y1) {
|
|
/* Passed the bottom of what's in the clip region.
|
|
* Done. */
|
|
break;
|
|
}
|
|
|
|
|
|
if (node->type != TREE_NODE_ENTRY || !node->expanded)
|
|
/* Done everything for this node */
|
|
continue;
|
|
|
|
|
|
/* Reneder expanded entry background */
|
|
y0 = render_y;
|
|
y1 = render_y + tree_g.line_height * tree->n_fields;
|
|
new_ctx.plot->rectangle(r.x0, y0, r.x1, y1, &(style->bg));
|
|
|
|
/* Render expanded entry fields */
|
|
entry = (struct treeview_node_entry *)node;
|
|
for (i = 0; i < tree->n_fields; i++) {
|
|
struct treeview_field *ef = &(tree->fields[i]);
|
|
int max_width = tree->field_width;
|
|
|
|
if (ef->flags & TREE_FLAG_SHOW_NAME) {
|
|
new_ctx.plot->text(x0 + max_width -
|
|
ef->value.width -
|
|
tree_g.step_width,
|
|
render_y + baseline,
|
|
ef->value.data,
|
|
ef->value.len,
|
|
&(style->text));
|
|
|
|
new_ctx.plot->text(x0 + max_width,
|
|
render_y + baseline,
|
|
entry->fields[i].value.data,
|
|
entry->fields[i].value.len,
|
|
&(style->text));
|
|
} else {
|
|
new_ctx.plot->text(x0, render_y + baseline,
|
|
entry->fields[i].value.data,
|
|
entry->fields[i].value.len,
|
|
&(style->text));
|
|
|
|
}
|
|
|
|
/* Rendered the expanded entry field */
|
|
render_y += tree_g.line_height;
|
|
}
|
|
|
|
/* Finshed rendering expanded entry */
|
|
|
|
if (render_y > clip->y1) {
|
|
/* Passed the bottom of what's in the clip region.
|
|
* Done. */
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (render_y < clip->y1) {
|
|
/* Fill the blank area at the bottom */
|
|
y0 = render_y;
|
|
new_ctx.plot->rectangle(r.x0, y0, r.x1, r.y1, &(style->bg));
|
|
|
|
}
|
|
|
|
/* Rendering complete */
|
|
if (ctx->plot->option_knockout)
|
|
knockout_plot_end();
|
|
}
|
|
|
|
|
|
|
|
/* Mix two colours according to the proportion given by p.
|
|
* Where 0 <= p <= 255
|
|
* p=0 gives result=c0
|
|
* p=255 gives result=c1
|
|
*/
|
|
#define mix_colour(c0, c1, p) \
|
|
((((((c1 & 0xff00ff) * (255 - p)) + \
|
|
((c0 & 0xff00ff) * ( p)) ) >> 8) & 0xff00ff) | \
|
|
(((((c1 & 0x00ff00) * (255 - p)) + \
|
|
((c0 & 0x00ff00) * ( p)) ) >> 8) & 0x00ff00))
|
|
|
|
|
|
static void treeview_init_plot_styles(void)
|
|
{
|
|
/* Background colour */
|
|
plot_style_even.bg.stroke_type = PLOT_OP_TYPE_NONE;
|
|
plot_style_even.bg.stroke_width = 0;
|
|
plot_style_even.bg.stroke_colour = 0;
|
|
plot_style_even.bg.fill_type = PLOT_OP_TYPE_SOLID;
|
|
plot_style_even.bg.fill_colour = gui_system_colour_char("Window");
|
|
|
|
/* Text colour */
|
|
plot_style_even.text.family = PLOT_FONT_FAMILY_SANS_SERIF;
|
|
plot_style_even.text.size = 11 * FONT_SIZE_SCALE;
|
|
plot_style_even.text.weight = 400;
|
|
plot_style_even.text.flags = FONTF_NONE;
|
|
plot_style_even.text.foreground = gui_system_colour_char("WindowText");
|
|
plot_style_even.text.background = gui_system_colour_char("Window");
|
|
|
|
/* Selected background colour */
|
|
plot_style_even.sbg = plot_style_even.bg;
|
|
plot_style_even.sbg.fill_colour = gui_system_colour_char("Highlight");
|
|
|
|
/* Selected text colour */
|
|
plot_style_even.stext = plot_style_even.text;
|
|
plot_style_even.stext.foreground =
|
|
gui_system_colour_char("HighlightText");
|
|
plot_style_even.stext.background = gui_system_colour_char("Highlight");
|
|
|
|
/* Selection area background colour */
|
|
plot_style_even.sabg = plot_style_even.bg;
|
|
plot_style_even.sabg.fill_colour = mix_colour(
|
|
plot_style_even.bg.fill_colour,
|
|
plot_style_even.sbg.fill_colour, 255 * 3 / 4);
|
|
|
|
/* Selection area text colour */
|
|
plot_style_even.satext = plot_style_even.text;
|
|
plot_style_even.satext.background = plot_style_even.sabg.fill_colour;
|
|
|
|
|
|
/* 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.sbg = plot_style_even.sbg;
|
|
plot_style_odd.stext = plot_style_even.stext;
|
|
|
|
plot_style_odd.sabg = plot_style_even.sabg;
|
|
plot_style_odd.sabg.fill_colour = mix_colour(
|
|
plot_style_even.sabg.fill_colour,
|
|
plot_style_even.satext.foreground, 255 * 15 / 16);
|
|
plot_style_odd.satext = plot_style_even.satext;
|
|
plot_style_odd.satext.background = plot_style_odd.sabg.fill_colour;
|
|
}
|
|
|
|
|
|
/**
|
|
* Callback for hlcache.
|
|
*/
|
|
static nserror treeview_res_cb(hlcache_handle *handle,
|
|
const hlcache_event *event, void *pw)
|
|
{
|
|
struct treeview_resource *r = pw;
|
|
|
|
switch (event->type) {
|
|
case CONTENT_MSG_READY:
|
|
case CONTENT_MSG_DONE:
|
|
r->ready = true;
|
|
r->height = content_get_height(handle);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return NSERROR_OK;
|
|
}
|
|
|
|
|
|
static void treeview_init_resources(void)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < TREE_RES_LAST; i++) {
|
|
nsurl *url;
|
|
if (nsurl_create(treeview_res[i].url, &url) == NSERROR_OK) {
|
|
hlcache_handle_retrieve(url, 0, NULL, NULL,
|
|
treeview_res_cb,
|
|
&(treeview_res[i]), NULL,
|
|
CONTENT_IMAGE, &(treeview_res[i].c));
|
|
nsurl_unref(url);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
static void treeview_init_furniture(void)
|
|
{
|
|
int i;
|
|
tree_g.furniture_width = 0;
|
|
|
|
for (i = 0; i < TREE_FURN_LAST; i++) {
|
|
nsfont.font_width(&plot_style_odd.text,
|
|
treeview_furn[i].data,
|
|
treeview_furn[i].len,
|
|
&(treeview_furn[i].width));
|
|
|
|
if (treeview_furn[i].width > tree_g.furniture_width)
|
|
tree_g.furniture_width = treeview_furn[i].width;
|
|
}
|
|
|
|
tree_g.furniture_width += 5;
|
|
}
|
|
|
|
|
|
nserror treeview_init(void)
|
|
{
|
|
treeview_init_plot_styles();
|
|
treeview_init_resources();
|
|
treeview_init_furniture();
|
|
|
|
tree_g.line_height = 20;
|
|
tree_g.step_width = tree_g.furniture_width;
|
|
tree_g.window_padding = 6;
|
|
tree_g.icon_step = 23;
|
|
|
|
return NSERROR_OK;
|
|
}
|
|
|
|
|
|
nserror treeview_fini(void)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < TREE_RES_LAST; i++) {
|
|
hlcache_handle_release(treeview_res[i].c);
|
|
}
|
|
|
|
return NSERROR_OK;
|
|
}
|
|
|
|
|
|
struct treeview_node * treeview_get_root(struct treeview *tree)
|
|
{
|
|
assert(tree != NULL);
|
|
assert(tree->root != NULL);
|
|
|
|
return tree->root;
|
|
}
|