#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <caca.h>
#include <gtcaca/tree.h>
#include <gtcaca/main.h>
typedef struct tnode {
void *node;
long count;
long flat;
long *kidx;
struct tnode **kids;
int nkid, kcap;
struct tnode *parent;
} tnode;
typedef struct {
void *node;
tnode *parent;
long index;
int depth;
tnode *self;
int has_kids;
long parent_row;
} rowinfo;
static void _free_tnode(tnode *t)
{
int i;
if (!t) return;
for (i = 0; i < t->nkid; i++) _free_tnode(t->kids[i]);
free(t->kidx); free(t->kids); free(t);
}
static void _add_flat(tnode *t, long d)
{
for (; t; t = t->parent) t->flat += d;
}
static tnode *_make_root(gtcaca_tree_widget_t *t)
{
tnode *r = calloc(1, sizeof *r);
if (!r) return NULL;
r->node = NULL;
r->count = t->model ? t->model->child_count(t->model, NULL) : 0;
r->flat = r->count;
return r;
}
static tnode *tree_expand(gtcaca_tree_widget_t *t, tnode *P, long ci)
{
tnode *C;
int pos, k;
for (k = 0; k < P->nkid; k++) if (P->kidx[k] == ci) return P->kids[k];
C = calloc(1, sizeof *C);
if (!C) return NULL;
C->node = t->model->child(t->model, P->node, ci);
C->count = t->model->child_count(t->model, C->node);
C->flat = C->count;
C->parent = P;
if (P->nkid == P->kcap) {
int nc = P->kcap ? P->kcap * 2 : 4;
long *ni = realloc(P->kidx, (size_t)nc * sizeof(long));
tnode **nk = realloc(P->kids, (size_t)nc * sizeof(tnode *));
if (!ni || !nk) { free(ni ? ni : NULL); free(C); return NULL; }
P->kidx = ni; P->kids = nk; P->kcap = nc;
}
for (pos = 0; pos < P->nkid && P->kidx[pos] < ci; pos++) ;
memmove(&P->kidx[pos + 1], &P->kidx[pos], (size_t)(P->nkid - pos) * sizeof(long));
memmove(&P->kids[pos + 1], &P->kids[pos], (size_t)(P->nkid - pos) * sizeof(tnode *));
P->kidx[pos] = ci; P->kids[pos] = C; P->nkid++;
_add_flat(P, C->flat);
return C;
}
static void _collapse(tnode *P, tnode *C)
{
int k, pos = -1;
for (k = 0; k < P->nkid; k++) if (P->kids[k] == C) { pos = k; break; }
if (pos < 0) return;
_add_flat(P, -C->flat);
_free_tnode(C);
memmove(&P->kidx[pos], &P->kidx[pos + 1], (size_t)(P->nkid - pos - 1) * sizeof(long));
memmove(&P->kids[pos], &P->kids[pos + 1], (size_t)(P->nkid - pos - 1) * sizeof(tnode *));
P->nkid--;
}
static int _resolve(gtcaca_tree_widget_t *t, long row, rowinfo *out)
{
tnode *P = (tnode *)t->root;
long rem = row;
int depth = 0;
long parent_row = -1;
if (!P || row < 0 || row >= P->flat) return 0;
for (;;) {
long child = 0;
int j = 0;
for (;;) {
long next_open = (j < P->nkid) ? P->kidx[j] : P->count;
long run = next_open - child;
if (rem < run) {
long ci = child + rem;
out->node = t->model->child(t->model, P->node, ci);
out->parent = P; out->index = ci; out->depth = depth;
out->self = NULL; out->parent_row = parent_row;
out->has_kids = t->model->has_children(t->model, out->node);
return 1;
}
rem -= run;
if (j >= P->nkid) return 0;
{
tnode *C = P->kids[j];
long hdr = row - rem;
if (rem == 0) {
out->node = C->node; out->parent = P; out->index = P->kidx[j];
out->depth = depth; out->self = C; out->parent_row = parent_row;
out->has_kids = t->model->has_children(t->model, C->node);
return 1;
}
rem -= 1;
if (rem < C->flat) { parent_row = hdr; P = C; depth++; break; }
rem -= C->flat;
child = P->kidx[j] + 1; j++;
}
}
}
}
gtcaca_tree_widget_t *gtcaca_tree_new(gtcaca_widget_t *parent, int x, int y, int width, int height)
{
gtcaca_tree_widget_t *t = malloc(sizeof(*t));
if (!t) { fprintf(stderr, "Error: Cannot allocate tree\n"); return NULL; }
t->id = gtcaca_get_newid();
t->has_focus = 0;
t->is_visible = 1;
t->type = GTCACA_WIDGET_TREE;
t->parent = parent;
t->children = NULL;
gtcaca_widget_position_size_parent(parent, GTCACA_WIDGET(t), x, y);
t->width = width; t->height = height;
t->color_focus_fg = t->color_nonfocus_fg = gmo.theme.textview.fg;
t->color_focus_bg = t->color_nonfocus_bg = gmo.theme.textview.bg;
t->model = NULL; t->root = NULL; t->top = 0; t->sel = 0; t->title = NULL;
t->sel_bg = CACA_BLUE;
CDL_APPEND(gmo.widgets_list, GTCACA_WIDGET(t));
return t;
}
void gtcaca_tree_set_model(gtcaca_tree_widget_t *t, gtcaca_tree_model_t *model)
{
_free_tnode((tnode *)t->root);
t->model = model;
t->root = _make_root(t);
t->top = t->sel = 0;
}
void gtcaca_tree_set_title(gtcaca_tree_widget_t *t, const char *title)
{
free(t->title); t->title = title ? strdup(title) : NULL;
}
void gtcaca_tree_free(gtcaca_tree_widget_t *t)
{
if (!t) return;
_free_tnode((tnode *)t->root); free(t->title); free(t);
}
long gtcaca_tree_visible_count(gtcaca_tree_widget_t *t)
{
return t->root ? ((tnode *)t->root)->flat : 0;
}
void *gtcaca_tree_selected_node(gtcaca_tree_widget_t *t)
{
rowinfo ri;
if (_resolve(t, t->sel, &ri)) return ri.node;
return NULL;
}
static int _find_path(gtcaca_tree_widget_t *t, void *node, void *target,
long *path, int depth, int max)
{
long cnt, i;
if (depth >= max) return -1;
cnt = t->model->child_count(t->model, node);
for (i = 0; i < cnt; i++) {
void *c = t->model->child(t->model, node, i);
path[depth] = i;
if (c == target) return depth + 1;
if (t->model->has_children(t->model, c)) {
int r = _find_path(t, c, target, path, depth + 1, max);
if (r > 0) return r;
}
}
return -1;
}
void gtcaca_tree_select(gtcaca_tree_widget_t *t, void *target)
{
long path[64];
int plen, d, inner_h;
tnode *P;
long base = 0, row = 0;
if (!t || !t->model || !target) return;
plen = _find_path(t, NULL, target, path, 0, (int)(sizeof path / sizeof path[0]));
if (plen <= 0) return;
_free_tnode((tnode *)t->root);
t->root = _make_root(t);
P = (tnode *)t->root;
for (d = 0; d < plen; d++) {
long ci = path[d];
row = base + ci;
if (d < plen - 1) {
tnode *C = tree_expand(t, P, ci);
if (!C) break;
P = C;
base = row + 1;
}
}
t->sel = row;
inner_h = t->height - 2;
if (t->sel < t->top) t->top = t->sel;
if (inner_h > 0 && t->sel >= t->top + inner_h) t->top = t->sel - inner_h + 1;
if (t->top < 0) t->top = 0;
}
void gtcaca_tree_draw(gtcaca_tree_widget_t *t)
{
uint8_t fg = gmo.theme.textview.fg, bg = gmo.theme.textview.bg;
int inner_x = t->x + 1, inner_y = t->y + 1;
int inner_w = t->width - 2, inner_h = t->height - 2;
long vis = gtcaca_tree_visible_count(t);
int i;
char buf[512];
caca_set_color_ansi(gmo.cv, fg, bg); caca_set_attr(gmo.cv, 0);
caca_fill_box(gmo.cv, t->x, t->y, t->width, t->height, ' ');
caca_draw_cp437_box(gmo.cv, t->x, t->y, t->width, t->height);
if (t->title) caca_printf(gmo.cv, t->x + 2, t->y, "| %s |", t->title);
if (inner_w <= 0 || inner_h <= 0) return;
for (i = 0; i < inner_h; i++) {
long row = t->top + i;
int y = inner_y + i, indent, col, selected;
rowinfo ri;
uint8_t rbg, rfg;
if (row >= vis || !_resolve(t, row, &ri)) break;
selected = (row == t->sel);
rbg = selected ? (t->has_focus ? t->sel_bg : CACA_DARKGRAY) : bg;
rfg = selected ? CACA_WHITE : fg;
caca_set_color_ansi(gmo.cv, rfg, rbg); caca_set_attr(gmo.cv, 0);
for (col = 0; col < inner_w; col++) caca_put_char(gmo.cv, inner_x + col, y, ' ');
indent = ri.depth * 2;
if (indent < inner_w) {
uint32_t mk = ri.has_kids ? (ri.self ? '-' : '+') : ' ';
caca_set_color_ansi(gmo.cv, ri.has_kids ? CACA_YELLOW : rfg, rbg);
caca_put_char(gmo.cv, inner_x + indent, y, mk);
}
{
int cx = inner_x + indent + 2;
int cw = inner_w - indent - 2; if (cw < 0) cw = 0;
caca_set_color_ansi(gmo.cv, rfg, rbg); caca_set_attr(gmo.cv, 0);
if (t->model->draw_row) {
t->model->draw_row(t->model, ri.node, cx, y, cw, selected);
} else {
t->model->label(t->model, ri.node, buf, sizeof buf);
caca_printf(gmo.cv, cx, y, "%-.*s", cw, buf);
}
}
}
}
int gtcaca_tree_key(gtcaca_tree_widget_t *t, int key, void *userdata)
{
long vis = gtcaca_tree_visible_count(t);
int inner_h = t->height - 2;
rowinfo ri;
(void)userdata;
if (!t->model || !t->root) return 0;
switch (key) {
case CACA_KEY_UP: if (t->sel > 0) t->sel--; break;
case CACA_KEY_DOWN: if (t->sel < vis - 1) t->sel++; break;
case CACA_KEY_PAGEUP: t->sel -= inner_h; break;
case CACA_KEY_PAGEDOWN: t->sel += inner_h; break;
case CACA_KEY_HOME: t->sel = 0; break;
case CACA_KEY_END: t->sel = vis - 1; break;
case CACA_KEY_RIGHT:
if (_resolve(t, t->sel, &ri)) {
if (ri.has_kids && !ri.self) tree_expand(t, ri.parent, ri.index);
else if (ri.self && t->sel < vis - 1) t->sel++;
}
break;
case CACA_KEY_LEFT:
if (_resolve(t, t->sel, &ri)) {
if (ri.self) _collapse(ri.parent, ri.self);
else if (ri.parent_row >= 0) t->sel = ri.parent_row;
}
break;
case CACA_KEY_RETURN:
case 10:
case ' ':
if (_resolve(t, t->sel, &ri) && ri.has_kids) {
if (ri.self) _collapse(ri.parent, ri.self);
else tree_expand(t, ri.parent, ri.index);
}
break;
default:
return 0;
}
vis = gtcaca_tree_visible_count(t);
if (t->sel < 0) t->sel = 0;
if (t->sel > vis - 1) t->sel = vis - 1;
if (t->sel < t->top) t->top = t->sel;
if (t->sel >= t->top + inner_h) t->top = t->sel - inner_h + 1;
if (t->top < 0) t->top = 0;
return 1;
}