sdiagram

Simple diagram tool — suckless-style node graph editor
git clone git clone https://git.krisyotam.com/krisyotam/sdiagram.git
Log | Files | Refs | LICENSE

data.c (13436B)


      1 /* sdiagram - simple diagram tool
      2  * See LICENSE file for copyright and license details. */
      3 
      4 #include <errno.h>
      5 #include <math.h>
      6 #include <stdio.h>
      7 #include <stdlib.h>
      8 #include <string.h>
      9 #include <sys/stat.h>
     10 #include <unistd.h>
     11 
     12 #include "sdiagram.h"
     13 #include "config.h"
     14 
     15 #define INIT_CAP 32
     16 
     17 static int
     18 count_lines(const char *s)
     19 {
     20 	int n;
     21 
     22 	if (!s || !s[0])
     23 		return 0;
     24 	n = 1;
     25 	while (*s) {
     26 		if (*s == '\n')
     27 			n++;
     28 		s++;
     29 	}
     30 	return n;
     31 }
     32 
     33 void
     34 data_init(Diagram *d)
     35 {
     36 	memset(d, 0, sizeof(*d));
     37 	d->nodes = calloc(INIT_CAP, sizeof(Node));
     38 	d->ncap = INIT_CAP;
     39 	d->conns = calloc(INIT_CAP, sizeof(Conn));
     40 	d->ccap = INIT_CAP;
     41 	d->zoom = 1.0;
     42 	d->sel = -1;
     43 	d->selconn = -1;
     44 	d->connfrom = -1;
     45 	d->nextid = 1;
     46 	d->amode = default_asset_mode;
     47 	d->dark = default_dark;
     48 	d->theme = d->dark ? &theme_dark : &theme_light;
     49 	d->editing = -1;
     50 }
     51 
     52 void
     53 data_free(Diagram *d)
     54 {
     55 	int i;
     56 
     57 	for (i = 0; i < d->nnodes; i++) {
     58 		if (d->nodes[i].thumb)
     59 			g_object_unref(d->nodes[i].thumb);
     60 	}
     61 	free(d->nodes);
     62 	free(d->conns);
     63 }
     64 
     65 static void
     66 grow_nodes(Diagram *d)
     67 {
     68 	if (d->nnodes < d->ncap)
     69 		return;
     70 	d->ncap *= 2;
     71 	d->nodes = realloc(d->nodes, d->ncap * sizeof(Node));
     72 }
     73 
     74 static void
     75 grow_conns(Diagram *d)
     76 {
     77 	if (d->nconns < d->ccap)
     78 		return;
     79 	d->ccap *= 2;
     80 	d->conns = realloc(d->conns, d->ccap * sizeof(Conn));
     81 }
     82 
     83 int
     84 node_add(Diagram *d, double x, double y, const char *text)
     85 {
     86 	Node *n;
     87 	const Theme *t;
     88 	int ci;
     89 
     90 	grow_nodes(d);
     91 	n = &d->nodes[d->nnodes];
     92 	memset(n, 0, sizeof(*n));
     93 	n->id = d->nextid++;
     94 	n->x = x;
     95 	n->y = y;
     96 	n->w = node_min_w;
     97 	n->h = node_min_h;
     98 	if (text)
     99 		snprintf(n->text, sizeof(n->text), "%s", text);
    100 	else
    101 		snprintf(n->text, sizeof(n->text), "Node %d", n->id);
    102 
    103 	t = d->theme;
    104 	ci = d->hdr_idx % t->nhdr;
    105 	n->hdr[0] = t->hdr[ci][0];
    106 	n->hdr[1] = t->hdr[ci][1];
    107 	n->hdr[2] = t->hdr[ci][2];
    108 	d->hdr_idx++;
    109 
    110 	d->modified = 1;
    111 	return d->nnodes++;
    112 }
    113 
    114 void
    115 node_del(Diagram *d, int idx)
    116 {
    117 	int i, id;
    118 
    119 	if (idx < 0 || idx >= d->nnodes)
    120 		return;
    121 	id = d->nodes[idx].id;
    122 	if (d->nodes[idx].thumb)
    123 		g_object_unref(d->nodes[idx].thumb);
    124 
    125 	for (i = d->nconns - 1; i >= 0; i--) {
    126 		if (d->conns[i].from == id || d->conns[i].to == id)
    127 			conn_del(d, i);
    128 	}
    129 
    130 	memmove(&d->nodes[idx], &d->nodes[idx + 1],
    131 		(d->nnodes - idx - 1) * sizeof(Node));
    132 	d->nnodes--;
    133 	d->sel = -1;
    134 	d->modified = 1;
    135 }
    136 
    137 int
    138 node_by_id(Diagram *d, int id)
    139 {
    140 	int i;
    141 
    142 	for (i = 0; i < d->nnodes; i++) {
    143 		if (d->nodes[i].id == id)
    144 			return i;
    145 	}
    146 	return -1;
    147 }
    148 
    149 double
    150 node_height(Node *n)
    151 {
    152 	double h;
    153 
    154 	h = node_hdr_h;
    155 	if (n->thumb) {
    156 		h += gdk_pixbuf_get_height(n->thumb);
    157 	} else if (!n->desc[0]) {
    158 		h += node_pad * 2;
    159 	}
    160 	if (n->desc[0])
    161 		h += count_lines(n->desc) * node_comment_lh
    162 			+ 8.0;
    163 	return h < node_min_h ? node_min_h : h;
    164 }
    165 
    166 int
    167 node_at(Diagram *d, double x, double y)
    168 {
    169 	int i;
    170 	Node *n;
    171 
    172 	for (i = d->nnodes - 1; i >= 0; i--) {
    173 		n = &d->nodes[i];
    174 		n->h = node_height(n);
    175 		if (x >= n->x && x <= n->x + n->w &&
    176 		    y >= n->y && y <= n->y + n->h)
    177 			return i;
    178 	}
    179 	return -1;
    180 }
    181 
    182 int
    183 conn_add(Diagram *d, int from, int to, const char *label)
    184 {
    185 	Conn *c;
    186 	int i;
    187 
    188 	for (i = 0; i < d->nconns; i++) {
    189 		if (d->conns[i].from == from && d->conns[i].to == to)
    190 			return -1;
    191 		if (d->conns[i].from == to && d->conns[i].to == from)
    192 			return -1;
    193 	}
    194 
    195 	grow_conns(d);
    196 	c = &d->conns[d->nconns];
    197 	memset(c, 0, sizeof(*c));
    198 	c->id = d->nextid++;
    199 	c->from = from;
    200 	c->to = to;
    201 	if (label)
    202 		snprintf(c->label, sizeof(c->label), "%s", label);
    203 
    204 	d->modified = 1;
    205 	return d->nconns++;
    206 }
    207 
    208 void
    209 conn_del(Diagram *d, int idx)
    210 {
    211 	if (idx < 0 || idx >= d->nconns)
    212 		return;
    213 	memmove(&d->conns[idx], &d->conns[idx + 1],
    214 		(d->nconns - idx - 1) * sizeof(Conn));
    215 	d->nconns--;
    216 	d->selconn = -1;
    217 	d->modified = 1;
    218 }
    219 
    220 static double
    221 point_line_dist(double px, double py,
    222 	double x1, double y1, double x2, double y2)
    223 {
    224 	double dx, dy, t, cx, cy;
    225 
    226 	dx = x2 - x1;
    227 	dy = y2 - y1;
    228 	if (dx == 0 && dy == 0)
    229 		return hypot(px - x1, py - y1);
    230 
    231 	t = ((px - x1) * dx + (py - y1) * dy) / (dx * dx + dy * dy);
    232 	if (t < 0) t = 0;
    233 	if (t > 1) t = 1;
    234 	cx = x1 + t * dx;
    235 	cy = y1 + t * dy;
    236 	return hypot(px - cx, py - cy);
    237 }
    238 
    239 int
    240 conn_at(Diagram *d, double x, double y)
    241 {
    242 	int i, fi, ti;
    243 	Node *fn, *tn;
    244 	double x1, y1, x2, y2, dist;
    245 
    246 	for (i = 0; i < d->nconns; i++) {
    247 		fi = node_by_id(d, d->conns[i].from);
    248 		ti = node_by_id(d, d->conns[i].to);
    249 		if (fi < 0 || ti < 0)
    250 			continue;
    251 		fn = &d->nodes[fi];
    252 		tn = &d->nodes[ti];
    253 		x1 = fn->x + fn->w / 2;
    254 		y1 = fn->y + node_height(fn) / 2;
    255 		x2 = tn->x + tn->w / 2;
    256 		y2 = tn->y + node_height(tn) / 2;
    257 		dist = point_line_dist(x, y, x1, y1, x2, y2);
    258 		if (dist < 8.0)
    259 			return i;
    260 	}
    261 	return -1;
    262 }
    263 
    264 /* asset management */
    265 
    266 static int
    267 mkdirp(const char *path)
    268 {
    269 	struct stat st;
    270 
    271 	if (stat(path, &st) == 0)
    272 		return S_ISDIR(st.st_mode) ? 0 : -1;
    273 	return mkdir(path, 0755);
    274 }
    275 
    276 static int
    277 copy_file(const char *src, const char *dst)
    278 {
    279 	FILE *in, *out;
    280 	char buf[8192];
    281 	size_t n;
    282 
    283 	in = fopen(src, "rb");
    284 	if (!in)
    285 		return -1;
    286 	out = fopen(dst, "wb");
    287 	if (!out) {
    288 		fclose(in);
    289 		return -1;
    290 	}
    291 	while ((n = fread(buf, 1, sizeof(buf), in)) > 0)
    292 		fwrite(buf, 1, n, out);
    293 	fclose(in);
    294 	fclose(out);
    295 	return 0;
    296 }
    297 
    298 int
    299 asset_import(Diagram *d, int nidx, const char *filepath)
    300 {
    301 	Node *n;
    302 	const char *base;
    303 	char dst[1024];
    304 
    305 	if (nidx < 0 || nidx >= d->nnodes)
    306 		return -1;
    307 	if (!d->adir[0])
    308 		return -1;
    309 
    310 	mkdirp(d->adir);
    311 
    312 	n = &d->nodes[nidx];
    313 	base = strrchr(filepath, '/');
    314 	base = base ? base + 1 : filepath;
    315 	snprintf(dst, sizeof(dst), "%s/%s", d->adir, base);
    316 
    317 	if (d->amode == ASSET_SYMLINK) {
    318 		if (symlink(filepath, dst) < 0 && errno != EEXIST)
    319 			return -1;
    320 	} else {
    321 		if (copy_file(filepath, dst) < 0)
    322 			return -1;
    323 	}
    324 
    325 	snprintf(n->asset, sizeof(n->asset), "%s", base);
    326 
    327 	if (n->thumb) {
    328 		g_object_unref(n->thumb);
    329 		n->thumb = NULL;
    330 	}
    331 	thumb_load(n, d->adir);
    332 
    333 	/* expand node to fit image */
    334 	if (n->thumb) {
    335 		double tw;
    336 		tw = gdk_pixbuf_get_width(n->thumb);
    337 		if (tw > n->w)
    338 			n->w = tw;
    339 	}
    340 
    341 	d->modified = 1;
    342 	return 0;
    343 }
    344 
    345 void
    346 asset_remove(Diagram *d, int nidx)
    347 {
    348 	Node *n;
    349 
    350 	if (nidx < 0 || nidx >= d->nnodes)
    351 		return;
    352 	n = &d->nodes[nidx];
    353 	if (n->thumb) {
    354 		g_object_unref(n->thumb);
    355 		n->thumb = NULL;
    356 	}
    357 	n->asset[0] = '\0';
    358 	d->modified = 1;
    359 }
    360 
    361 void
    362 thumb_load(Node *n, const char *adir)
    363 {
    364 	char path[1024];
    365 
    366 	if (!n->asset[0] || n->thumb)
    367 		return;
    368 
    369 	snprintf(path, sizeof(path), "%s/%s", adir, n->asset);
    370 	n->thumb = gdk_pixbuf_new_from_file_at_scale(
    371 		path, (int)node_max_w, (int)node_img_h,
    372 		TRUE, NULL);
    373 }
    374 
    375 /* SQLite persistence */
    376 
    377 int
    378 db_new(Diagram *d, const char *path)
    379 {
    380 	sqlite3 *db;
    381 	int rc;
    382 
    383 	snprintf(d->path, sizeof(d->path), "%s", path);
    384 	snprintf(d->dbpath, sizeof(d->dbpath),
    385 		"%s/diagram.db", path);
    386 	snprintf(d->adir, sizeof(d->adir),
    387 		"%s/assets", path);
    388 
    389 	if (mkdirp(path) < 0)
    390 		return -1;
    391 	if (mkdirp(d->adir) < 0)
    392 		return -1;
    393 
    394 	rc = sqlite3_open(d->dbpath, &db);
    395 	if (rc != SQLITE_OK)
    396 		return -1;
    397 
    398 	sqlite3_exec(db,
    399 		"CREATE TABLE IF NOT EXISTS nodes ("
    400 		"  id INTEGER PRIMARY KEY,"
    401 		"  x REAL, y REAL, w REAL,"
    402 		"  text TEXT,"
    403 		"  asset TEXT,"
    404 		"  desc TEXT,"
    405 		"  hdr_r REAL, hdr_g REAL, hdr_b REAL"
    406 		");"
    407 		"CREATE TABLE IF NOT EXISTS conns ("
    408 		"  id INTEGER PRIMARY KEY,"
    409 		"  src INTEGER, dst INTEGER,"
    410 		"  label TEXT"
    411 		");"
    412 		"CREATE TABLE IF NOT EXISTS meta ("
    413 		"  key TEXT PRIMARY KEY,"
    414 		"  val TEXT"
    415 		");",
    416 		NULL, NULL, NULL);
    417 
    418 	sqlite3_close(db);
    419 	d->modified = 0;
    420 	return 0;
    421 }
    422 
    423 int
    424 db_save(Diagram *d)
    425 {
    426 	sqlite3 *db;
    427 	sqlite3_stmt *stmt;
    428 	int i, rc;
    429 	Node *n;
    430 	Conn *c;
    431 	char buf[64];
    432 
    433 	if (!d->dbpath[0])
    434 		return -1;
    435 
    436 	rc = sqlite3_open(d->dbpath, &db);
    437 	if (rc != SQLITE_OK)
    438 		return -1;
    439 
    440 	sqlite3_exec(db, "BEGIN;", NULL, NULL, NULL);
    441 	sqlite3_exec(db, "DELETE FROM nodes;", NULL, NULL, NULL);
    442 	sqlite3_exec(db, "DELETE FROM conns;", NULL, NULL, NULL);
    443 	sqlite3_exec(db, "DELETE FROM meta;", NULL, NULL, NULL);
    444 
    445 	sqlite3_prepare_v2(db,
    446 		"INSERT INTO nodes VALUES(?,?,?,?,?,?,?,?,?,?);",
    447 		-1, &stmt, NULL);
    448 	for (i = 0; i < d->nnodes; i++) {
    449 		n = &d->nodes[i];
    450 		sqlite3_bind_int(stmt, 1, n->id);
    451 		sqlite3_bind_double(stmt, 2, n->x);
    452 		sqlite3_bind_double(stmt, 3, n->y);
    453 		sqlite3_bind_double(stmt, 4, n->w);
    454 		sqlite3_bind_text(stmt, 5, n->text,
    455 			-1, SQLITE_STATIC);
    456 		sqlite3_bind_text(stmt, 6, n->asset,
    457 			-1, SQLITE_STATIC);
    458 		sqlite3_bind_text(stmt, 7, n->desc,
    459 			-1, SQLITE_STATIC);
    460 		sqlite3_bind_double(stmt, 8, n->hdr[0]);
    461 		sqlite3_bind_double(stmt, 9, n->hdr[1]);
    462 		sqlite3_bind_double(stmt, 10, n->hdr[2]);
    463 		sqlite3_step(stmt);
    464 		sqlite3_reset(stmt);
    465 	}
    466 	sqlite3_finalize(stmt);
    467 
    468 	sqlite3_prepare_v2(db,
    469 		"INSERT INTO conns VALUES(?,?,?,?);",
    470 		-1, &stmt, NULL);
    471 	for (i = 0; i < d->nconns; i++) {
    472 		c = &d->conns[i];
    473 		sqlite3_bind_int(stmt, 1, c->id);
    474 		sqlite3_bind_int(stmt, 2, c->from);
    475 		sqlite3_bind_int(stmt, 3, c->to);
    476 		sqlite3_bind_text(stmt, 4, c->label,
    477 			-1, SQLITE_STATIC);
    478 		sqlite3_step(stmt);
    479 		sqlite3_reset(stmt);
    480 	}
    481 	sqlite3_finalize(stmt);
    482 
    483 	sqlite3_prepare_v2(db,
    484 		"INSERT INTO meta VALUES(?,?);",
    485 		-1, &stmt, NULL);
    486 
    487 	snprintf(buf, sizeof(buf), "%.2f", d->panx);
    488 	sqlite3_bind_text(stmt, 1, "panx", -1, SQLITE_STATIC);
    489 	sqlite3_bind_text(stmt, 2, buf, -1, SQLITE_TRANSIENT);
    490 	sqlite3_step(stmt); sqlite3_reset(stmt);
    491 
    492 	snprintf(buf, sizeof(buf), "%.2f", d->pany);
    493 	sqlite3_bind_text(stmt, 1, "pany", -1, SQLITE_STATIC);
    494 	sqlite3_bind_text(stmt, 2, buf, -1, SQLITE_TRANSIENT);
    495 	sqlite3_step(stmt); sqlite3_reset(stmt);
    496 
    497 	snprintf(buf, sizeof(buf), "%.4f", d->zoom);
    498 	sqlite3_bind_text(stmt, 1, "zoom", -1, SQLITE_STATIC);
    499 	sqlite3_bind_text(stmt, 2, buf, -1, SQLITE_TRANSIENT);
    500 	sqlite3_step(stmt); sqlite3_reset(stmt);
    501 
    502 	snprintf(buf, sizeof(buf), "%d", d->nextid);
    503 	sqlite3_bind_text(stmt, 1, "nextid", -1, SQLITE_STATIC);
    504 	sqlite3_bind_text(stmt, 2, buf, -1, SQLITE_TRANSIENT);
    505 	sqlite3_step(stmt); sqlite3_reset(stmt);
    506 
    507 	snprintf(buf, sizeof(buf), "%d", d->amode);
    508 	sqlite3_bind_text(stmt, 1, "amode", -1, SQLITE_STATIC);
    509 	sqlite3_bind_text(stmt, 2, buf, -1, SQLITE_TRANSIENT);
    510 	sqlite3_step(stmt); sqlite3_reset(stmt);
    511 
    512 	snprintf(buf, sizeof(buf), "%d", d->dark);
    513 	sqlite3_bind_text(stmt, 1, "dark", -1, SQLITE_STATIC);
    514 	sqlite3_bind_text(stmt, 2, buf, -1, SQLITE_TRANSIENT);
    515 	sqlite3_step(stmt); sqlite3_reset(stmt);
    516 
    517 	sqlite3_finalize(stmt);
    518 	sqlite3_exec(db, "COMMIT;", NULL, NULL, NULL);
    519 	sqlite3_close(db);
    520 
    521 	d->modified = 0;
    522 	return 0;
    523 }
    524 
    525 static void
    526 load_meta(Diagram *d, sqlite3 *db)
    527 {
    528 	sqlite3_stmt *stmt;
    529 	const char *key, *val;
    530 
    531 	sqlite3_prepare_v2(db, "SELECT key, val FROM meta;",
    532 		-1, &stmt, NULL);
    533 	while (sqlite3_step(stmt) == SQLITE_ROW) {
    534 		key = (const char *)sqlite3_column_text(stmt, 0);
    535 		val = (const char *)sqlite3_column_text(stmt, 1);
    536 		if (!key || !val)
    537 			continue;
    538 		if (strcmp(key, "panx") == 0)
    539 			d->panx = atof(val);
    540 		else if (strcmp(key, "pany") == 0)
    541 			d->pany = atof(val);
    542 		else if (strcmp(key, "zoom") == 0)
    543 			d->zoom = atof(val);
    544 		else if (strcmp(key, "nextid") == 0)
    545 			d->nextid = atoi(val);
    546 		else if (strcmp(key, "amode") == 0)
    547 			d->amode = atoi(val);
    548 		else if (strcmp(key, "dark") == 0)
    549 			d->dark = atoi(val);
    550 	}
    551 	sqlite3_finalize(stmt);
    552 }
    553 
    554 int
    555 db_load(Diagram *d, const char *path)
    556 {
    557 	sqlite3 *db;
    558 	sqlite3_stmt *stmt;
    559 	int rc, i;
    560 	Node *n;
    561 	Conn *c;
    562 
    563 	snprintf(d->path, sizeof(d->path), "%s", path);
    564 	snprintf(d->dbpath, sizeof(d->dbpath),
    565 		"%s/diagram.db", path);
    566 	snprintf(d->adir, sizeof(d->adir),
    567 		"%s/assets", path);
    568 
    569 	rc = sqlite3_open(d->dbpath, &db);
    570 	if (rc != SQLITE_OK)
    571 		return -1;
    572 
    573 	for (i = 0; i < d->nnodes; i++) {
    574 		if (d->nodes[i].thumb) {
    575 			g_object_unref(d->nodes[i].thumb);
    576 			d->nodes[i].thumb = NULL;
    577 		}
    578 	}
    579 	d->nnodes = 0;
    580 	d->nconns = 0;
    581 	d->sel = -1;
    582 	d->selconn = -1;
    583 
    584 	sqlite3_prepare_v2(db,
    585 		"SELECT id, x, y, w, text, asset, desc,"
    586 		"  hdr_r, hdr_g, hdr_b "
    587 		"FROM nodes;", -1, &stmt, NULL);
    588 	while (sqlite3_step(stmt) == SQLITE_ROW) {
    589 		grow_nodes(d);
    590 		n = &d->nodes[d->nnodes];
    591 		memset(n, 0, sizeof(*n));
    592 		n->id = sqlite3_column_int(stmt, 0);
    593 		n->x = sqlite3_column_double(stmt, 1);
    594 		n->y = sqlite3_column_double(stmt, 2);
    595 		n->w = sqlite3_column_double(stmt, 3);
    596 		if (sqlite3_column_text(stmt, 4))
    597 			snprintf(n->text, sizeof(n->text), "%s",
    598 				(const char *)
    599 				sqlite3_column_text(stmt, 4));
    600 		if (sqlite3_column_text(stmt, 5))
    601 			snprintf(n->asset, sizeof(n->asset), "%s",
    602 				(const char *)
    603 				sqlite3_column_text(stmt, 5));
    604 		if (sqlite3_column_text(stmt, 6))
    605 			snprintf(n->desc, sizeof(n->desc),
    606 				"%s", (const char *)
    607 				sqlite3_column_text(stmt, 6));
    608 		n->hdr[0] = sqlite3_column_double(stmt, 7);
    609 		n->hdr[1] = sqlite3_column_double(stmt, 8);
    610 		n->hdr[2] = sqlite3_column_double(stmt, 9);
    611 		thumb_load(n, d->adir);
    612 		if (n->thumb) {
    613 			double tw;
    614 			tw = gdk_pixbuf_get_width(n->thumb);
    615 			if (tw > n->w)
    616 				n->w = tw;
    617 		}
    618 		n->h = node_height(n);
    619 		d->nnodes++;
    620 	}
    621 	sqlite3_finalize(stmt);
    622 
    623 	sqlite3_prepare_v2(db,
    624 		"SELECT id, src, dst, label FROM conns;",
    625 		-1, &stmt, NULL);
    626 	while (sqlite3_step(stmt) == SQLITE_ROW) {
    627 		grow_conns(d);
    628 		c = &d->conns[d->nconns];
    629 		memset(c, 0, sizeof(*c));
    630 		c->id = sqlite3_column_int(stmt, 0);
    631 		c->from = sqlite3_column_int(stmt, 1);
    632 		c->to = sqlite3_column_int(stmt, 2);
    633 		if (sqlite3_column_text(stmt, 3))
    634 			snprintf(c->label, sizeof(c->label), "%s",
    635 				(const char *)
    636 				sqlite3_column_text(stmt, 3));
    637 		d->nconns++;
    638 	}
    639 	sqlite3_finalize(stmt);
    640 
    641 	load_meta(d, db);
    642 	sqlite3_close(db);
    643 
    644 	d->theme = d->dark ? &theme_dark : &theme_light;
    645 	if (d->zoom < zoom_min)
    646 		d->zoom = 1.0;
    647 	d->modified = 0;
    648 	return 0;
    649 }