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 }