#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <caca.h>
#include <gtcaca/mindmap.h>
#include <gtcaca/main.h>
enum { LU = 1, LD = 2, LL = 4, LR = 8 };
#define CELL_CAP 24
#define GAP 4
static gtcaca_mm_node_t *node_new(const char *text, gtcaca_mm_node_t *parent)
{
gtcaca_mm_node_t *n = calloc(1, sizeof *n);
if (!n) return NULL;
n->text = strdup(text ? text : "");
n->colour = CACA_LIGHTGREEN;
n->parent = parent;
return n;
}
static void node_attach(gtcaca_mm_node_t *parent, gtcaca_mm_node_t *child, int at)
{
int i;
if (parent->nkids == parent->capkids) {
parent->capkids = parent->capkids ? parent->capkids * 2 : 4;
parent->kids = realloc(parent->kids, (size_t)parent->capkids * sizeof *parent->kids);
}
if (at < 0 || at > parent->nkids) at = parent->nkids;
for (i = parent->nkids; i > at; i--) parent->kids[i] = parent->kids[i - 1];
parent->kids[at] = child;
parent->nkids++;
child->parent = parent;
}
static void node_free(gtcaca_mm_node_t *n)
{
int i;
if (!n) return;
for (i = 0; i < n->nkids; i++) node_free(n->kids[i]);
free(n->kids); free(n->text); free(n);
}
static int node_index(gtcaca_mm_node_t *n)
{
int i; gtcaca_mm_node_t *p = n->parent;
if (!p) return -1;
for (i = 0; i < p->nkids; i++) if (p->kids[i] == n) return i;
return -1;
}
static int node_is_descendant(gtcaca_mm_node_t *n, gtcaca_mm_node_t *anc)
{
for (; n; n = n->parent) if (n == anc) return 1;
return 0;
}
gtcaca_mindmap_widget_t *gtcaca_mindmap_new(gtcaca_widget_t *parent, int x, int y, int width, int height)
{
gtcaca_mindmap_widget_t *m = malloc(sizeof(*m));
if (!m) { fprintf(stderr, "Error: Cannot allocate mindmap\n"); return NULL; }
m->id = gtcaca_get_newid();
m->has_focus = 0; m->is_visible = 1; m->type = GTCACA_WIDGET_MINDMAP;
m->parent = parent; m->children = NULL;
gtcaca_widget_position_size_parent(parent, GTCACA_WIDGET(m), x, y);
m->width = width; m->height = height;
m->color_focus_fg = m->color_nonfocus_fg = gmo.theme.textview.fg;
m->color_focus_bg = m->color_nonfocus_bg = gmo.theme.textview.bg;
m->root = node_new("New Mindmap", NULL);
m->sel = m->root; m->top = 0; m->xoff = 0; m->title = NULL;
m->sel_fg = CACA_BLACK; m->sel_bg = CACA_CYAN;
CDL_APPEND(gmo.widgets_list, GTCACA_WIDGET(m));
return m;
}
gtcaca_mm_node_t *gtcaca_mindmap_root(gtcaca_mindmap_widget_t *m) { return m->root; }
gtcaca_mm_node_t *gtcaca_mindmap_selected(gtcaca_mindmap_widget_t *m) { return m->sel; }
void gtcaca_mindmap_select(gtcaca_mindmap_widget_t *m, gtcaca_mm_node_t *n) { if (n) m->sel = n; }
void gtcaca_mindmap_set_title(gtcaca_mindmap_widget_t *m, const char *t)
{ free(m->title); m->title = t ? strdup(t) : NULL; }
void gtcaca_mindmap_set_text(gtcaca_mm_node_t *n, const char *text)
{ if (!n) return; free(n->text); n->text = strdup(text ? text : ""); }
void gtcaca_mindmap_toggle_fold(gtcaca_mm_node_t *n)
{ if (n && n->nkids) n->folded = !n->folded; }
gtcaca_mm_node_t *gtcaca_mindmap_add_child(gtcaca_mindmap_widget_t *m, gtcaca_mm_node_t *parent, const char *text)
{
gtcaca_mm_node_t *n;
if (!parent) parent = m->root;
n = node_new(text, parent);
if (!n) return NULL;
node_attach(parent, n, -1);
return n;
}
gtcaca_mm_node_t *gtcaca_mindmap_add_sibling(gtcaca_mindmap_widget_t *m, gtcaca_mm_node_t *node, const char *text)
{
gtcaca_mm_node_t *n;
(void)m;
if (!node || !node->parent) return NULL;
n = node_new(text, node->parent);
if (!n) return NULL;
node_attach(node->parent, n, node_index(node) + 1);
return n;
}
void gtcaca_mindmap_delete(gtcaca_mindmap_widget_t *m, gtcaca_mm_node_t *node)
{
gtcaca_mm_node_t *p;
int i, idx;
if (!node || node == m->root) return;
p = node->parent; idx = node_index(node);
if (idx < 0) return;
if (node_is_descendant(m->sel, node))
m->sel = (p->nkids > 1) ? p->kids[idx > 0 ? idx - 1 : 1] : p;
for (i = idx; i < p->nkids - 1; i++) p->kids[i] = p->kids[i + 1];
p->nkids--;
node_free(node);
}
void gtcaca_mindmap_clear(gtcaca_mindmap_widget_t *m, const char *root_text)
{
node_free(m->root);
m->root = node_new(root_text ? root_text : "New Mindmap", NULL);
m->sel = m->root; m->top = m->xoff = 0;
}
void gtcaca_mindmap_free(gtcaca_mindmap_widget_t *m)
{
if (!m) return;
node_free(m->root); free(m->title); free(m);
}
static int g_maxdepth;
static int g_cellw[256];
static int g_colx[256];
static int node_cellw(gtcaca_mm_node_t *n)
{
int w = (int)strlen(n->text);
if (w > CELL_CAP) w = CELL_CAP;
if (w < 1) w = 1;
return w;
}
static int layout_rows(gtcaca_mm_node_t *n, int depth, int *next)
{
int i, first = -1, last = -1;
n->depth = depth;
if (depth > g_maxdepth) g_maxdepth = depth;
{ int w = node_cellw(n); if (w > g_cellw[depth]) g_cellw[depth] = w; }
if (n->folded || n->nkids == 0) { n->row = (*next)++; return n->row; }
for (i = 0; i < n->nkids; i++) {
int r = layout_rows(n->kids[i], depth + 1, next);
if (first < 0) first = r;
last = r;
}
n->row = (first + last) / 2;
return n->row;
}
static void collect_visible(gtcaca_mm_node_t *n, gtcaca_mm_node_t **out, int *cnt)
{
int i;
out[(*cnt)++] = n;
if (!n->folded) for (i = 0; i < n->nkids; i++) collect_visible(n->kids[i], out, cnt);
}
static int count_nodes(gtcaca_mm_node_t *n)
{
int i, c = 1;
for (i = 0; i < n->nkids; i++) c += count_nodes(n->kids[i]);
return c;
}
static uint32_t boxglyph(unsigned b)
{
switch (b) {
case LL|LR: return 0x2500;
case LU|LD: return 0x2502;
case LD|LR: return 0x250C;
case LD|LL: return 0x2510;
case LU|LR: return 0x2514;
case LU|LL: return 0x2518;
case LU|LD|LR: return 0x251C;
case LU|LD|LL: return 0x2524;
case LD|LL|LR: return 0x252C;
case LU|LL|LR: return 0x2534;
case LU|LD|LL|LR: return 0x253C;
case LU: case LD: return 0x2502;
case LL: case LR: return 0x2500;
default: return 0;
}
}
void gtcaca_mindmap_draw(gtcaca_mindmap_widget_t *m)
{
uint8_t fg = gmo.theme.textview.fg, bg = gmo.theme.textview.bg;
int inner_x = m->x + 1, inner_y = m->y + 1, inner_w = m->width - 2, inner_h = m->height - 2;
int rows = 0, total, nvis = 0, d, i, gw, gh, r, c;
gtcaca_mm_node_t **vis;
uint8_t *grid;
caca_set_color_ansi(gmo.cv, fg, bg); caca_set_attr(gmo.cv, 0);
caca_fill_box(gmo.cv, m->x, m->y, m->width, m->height, ' ');
caca_draw_cp437_box(gmo.cv, m->x, m->y, m->width, m->height);
if (m->title) caca_printf(gmo.cv, m->x + 2, m->y, "| %s |", m->title);
if (inner_w <= 0 || inner_h <= 0 || !m->root) return;
g_maxdepth = 0;
for (i = 0; i < 256; i++) { g_cellw[i] = 0; g_colx[i] = 0; }
layout_rows(m->root, 0, &rows);
g_colx[0] = 0;
for (d = 1; d <= g_maxdepth; d++) g_colx[d] = g_colx[d - 1] + g_cellw[d - 1] + GAP;
gw = g_colx[g_maxdepth] + g_cellw[g_maxdepth] + 1;
gh = rows;
total = count_nodes(m->root);
vis = malloc((size_t)total * sizeof *vis);
collect_visible(m->root, vis, &nvis);
if (m->sel) {
if (m->sel->row < m->top) m->top = m->sel->row;
if (m->sel->row >= m->top + inner_h) m->top = m->sel->row - inner_h + 1;
}
if (m->top < 0) m->top = 0;
grid = ((long)gw * gh > 0 && (long)gw * gh < 4000000) ? calloc((size_t)gw * gh, 1) : NULL;
if (grid) {
for (i = 0; i < nvis; i++) {
gtcaca_mm_node_t *p = vis[i];
int pr, sx, ce, r0, rn;
if (p->folded || p->nkids == 0) continue;
pr = p->row;
ce = g_colx[p->depth] + g_cellw[p->depth];
sx = ce + GAP - 2;
r0 = p->kids[0]->row; rn = p->kids[p->nkids - 1]->row;
for (c = ce; c <= sx; c++) {
if (c > ce) grid[pr * gw + c] |= LL;
if (c < sx) grid[pr * gw + c] |= LR;
}
{ int a = pr < r0 ? pr : r0, b = pr > rn ? pr : rn;
for (r = a; r <= b; r++) {
if (r > a) grid[r * gw + sx] |= LU;
if (r < b) grid[r * gw + sx] |= LD;
} }
for (r = 0; r < p->nkids; r++) {
int cr = p->kids[r]->row, cx = g_colx[p->depth + 1];
for (c = sx; c < cx; c++) {
if (c > sx) grid[cr * gw + c] |= LL;
if (c < cx - 0) grid[cr * gw + c] |= LR;
}
}
}
caca_set_color_ansi(gmo.cv, fg, bg); caca_set_attr(gmo.cv, 0);
for (r = 0; r < gh; r++) {
int sy = inner_y + (r - m->top);
if (r < m->top || sy >= inner_y + inner_h) continue;
for (c = 0; c < gw; c++) {
int sx2 = inner_x + (c - m->xoff);
uint32_t g;
if (c < m->xoff || sx2 >= inner_x + inner_w) continue;
g = boxglyph(grid[r * gw + c]);
if (g) caca_put_char(gmo.cv, sx2, sy, g);
}
}
free(grid);
}
for (i = 0; i < nvis; i++) {
gtcaca_mm_node_t *n = vis[i];
int sy = inner_y + (n->row - m->top);
int lx = g_colx[n->depth], k, avail, len, drawn;
int sel = (n == m->sel && m->has_focus);
if (n->row < m->top || sy >= inner_y + inner_h) continue;
if (sel) { caca_set_color_ansi(gmo.cv, m->sel_fg, m->sel_bg); caca_set_attr(gmo.cv, CACA_BOLD); }
else { caca_set_color_ansi(gmo.cv, n->colour ? n->colour : fg, bg); caca_set_attr(gmo.cv, 0); }
len = (int)strlen(n->text);
avail = g_cellw[n->depth];
for (k = 0; k < len && k < avail; k++) {
int cx = inner_x + (lx + k - m->xoff);
if (lx + k < m->xoff || cx >= inner_x + inner_w) continue;
caca_put_char(gmo.cv, cx, sy, (uint32_t)(unsigned char)n->text[k]);
}
drawn = len < avail ? len : avail;
if (n->folded && n->nkids) {
int mx = inner_x + (lx + drawn + 1 - m->xoff);
if (lx + drawn + 1 >= m->xoff && mx < inner_x + inner_w) {
caca_set_color_ansi(gmo.cv, CACA_YELLOW, bg); caca_set_attr(gmo.cv, CACA_BOLD);
caca_put_char(gmo.cv, mx, sy, '+');
}
}
}
caca_set_attr(gmo.cv, 0);
free(vis);
}
int gtcaca_mindmap_key(gtcaca_mindmap_widget_t *m, int key, void *userdata)
{
gtcaca_mm_node_t **vis;
int total, nvis = 0, idx = -1, i, rows = 0;
(void)userdata;
if (!m->root) return 0;
g_maxdepth = 0; for (i = 0; i < 256; i++) g_cellw[i] = 0;
layout_rows(m->root, 0, &rows);
total = count_nodes(m->root);
vis = malloc((size_t)total * sizeof *vis);
collect_visible(m->root, vis, &nvis);
for (i = 0; i < nvis; i++) if (vis[i] == m->sel) { idx = i; break; }
if (idx < 0) { m->sel = m->root; idx = 0; }
switch (key) {
case CACA_KEY_UP: if (idx > 0) m->sel = vis[idx - 1]; break;
case CACA_KEY_DOWN: if (idx < nvis - 1) m->sel = vis[idx + 1]; break;
case CACA_KEY_LEFT:
if (m->sel->nkids && !m->sel->folded) m->sel->folded = 1;
else if (m->sel->parent) m->sel = m->sel->parent;
break;
case CACA_KEY_RIGHT:
if (m->sel->nkids && m->sel->folded) m->sel->folded = 0;
else if (m->sel->nkids) m->sel = m->sel->kids[0];
break;
case ' ':
gtcaca_mindmap_toggle_fold(m->sel);
break;
case '+': case '=':
if (m->sel->nkids) m->sel->folded = 0;
break;
case '-': case '_':
if (m->sel->nkids) m->sel->folded = 1;
break;
default:
free(vis); return 0;
}
free(vis);
return 1;
}