ppedit.c 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603
  1. /*
  2. ppedit - A pattern plate editor for Spiro splines.
  3. Copyright (C) 2007 Raph Levien
  4. This program is free software; you can redistribute it and/or
  5. modify it under the terms of the GNU General Public License
  6. as published by the Free Software Foundation; either version 2
  7. of the License, or (at your option) any later version.
  8. This program is distributed in the hope that it will be useful,
  9. but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. GNU General Public License for more details.
  12. You should have received a copy of the GNU General Public License
  13. along with this program; if not, write to the Free Software
  14. Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
  15. 02110-1301, USA.
  16. */
  17. #include "x3.h"
  18. #include <string.h>
  19. #include <stdio.h>
  20. #include <math.h>
  21. #include "zmisc.h"
  22. #include "bezctx.h"
  23. #include "bezctx_x3.h"
  24. #include "bezctx_ps.h"
  25. #include "cornu.h"
  26. #include "spiro.h"
  27. #include "plate.h"
  28. #include "image.h"
  29. int n_iter = 10;
  30. typedef struct {
  31. const char *description;
  32. plate *p;
  33. } undo_record;
  34. typedef struct {
  35. x3widget *view;
  36. const char *description;
  37. plate *p;
  38. int undo_n;
  39. int undo_i;
  40. undo_record undo_buf[16];
  41. int undo_xn_state;
  42. x3widget *undo_me;
  43. x3widget *redo_me;
  44. x3widget *show_knots_me;
  45. x3widget *show_bg_me;
  46. int show_knots;
  47. int show_bg;
  48. image *bg_image;
  49. } plate_edit;
  50. #define C1 0.55228
  51. static void
  52. draw_dot(x3dc *dc,
  53. double x, double y, double r, guint32 rgba)
  54. {
  55. x3setrgba(dc, rgba);
  56. x3moveto(dc, x + r, y);
  57. x3curveto(dc, x + r, y - C1 * r, x + C1 * r, y - r, x, y - r);
  58. x3curveto(dc, x - C1 * r, y - r, x - r, y - C1 * r, x - r, y);
  59. x3curveto(dc, x - r, y + C1 * r, x - C1 * r, y + r, x, y + r);
  60. x3curveto(dc, x + C1 * r, y + r, x + r, y + C1 * r, x + r, y);
  61. x3fill(dc);
  62. }
  63. static void
  64. draw_raw_rect(x3dc *dc,
  65. double rx0, double ry0, double rx1, double ry1, guint32 rgba)
  66. {
  67. x3setrgba(dc, rgba);
  68. x3rectangle(dc, rx0, ry0, rx1 - rx0, ry1 - ry0);
  69. x3fill(dc);
  70. }
  71. static void
  72. draw_rect(x3dc *dc,
  73. double x, double y, double r, guint32 rgba)
  74. {
  75. draw_raw_rect(dc,
  76. x - r, y - r, x + r, y + r, rgba);
  77. }
  78. static void
  79. draw_half(x3dc *dc,
  80. double x, double y, double r, double th, guint32 rgba)
  81. {
  82. double c = cos(th);
  83. double s = sin(th);
  84. x3setrgba(dc, rgba);
  85. x3moveto(dc, x + c * r, y + s * r);
  86. x3curveto(dc,
  87. x + c * r + C1 * s * r,
  88. y + s * r - C1 * c * r,
  89. x + s * r + C1 * c * r,
  90. y - c * r + C1 * s * r,
  91. x + s * r,
  92. y - c * r);
  93. x3curveto(dc,
  94. x + s * r - C1 * c * r,
  95. y - c * r - C1 * s * r,
  96. x - c * r + C1 * s * r,
  97. y - s * r - C1 * c * r,
  98. x - c * r,
  99. y - s * r);
  100. x3closepath(dc);
  101. x3fill(dc);
  102. }
  103. static void
  104. draw_plate(x3dc *dc,
  105. plate_edit *pe)
  106. {
  107. plate *p = pe->p;
  108. int i, j;
  109. /* find an existing point to select, if any */
  110. for (i = 0; i < p->n_sp; i++) {
  111. bezctx *bc = new_bezctx_x3(dc);
  112. subpath *sp = &p->sp[i];
  113. spiro_seg *s = draw_subpath(sp, bc);
  114. bezctx_x3_finish(bc);
  115. x3setrgba(dc, 0x000000ff);
  116. x3setlinewidth(dc, 1.5);
  117. x3stroke(dc);
  118. for (j = 0; j < sp->n_kt; j++) {
  119. if (pe->show_knots) {
  120. knot *kt = &sp->kt[j];
  121. kt_flags kf = kt->flags;
  122. if ((kf & KT_SELECTED) && (kf & KT_OPEN)) {
  123. draw_dot(dc, kt->x, kt->y,
  124. 3, 0x000000ff);
  125. draw_dot(dc, kt->x, kt->y,
  126. 1.5, 0xffffffff);
  127. } else if ((kf & KT_SELECTED) && (kf & KT_CORNER)) {
  128. draw_rect(dc, kt->x, kt->y,
  129. 3, 0x000000ff);
  130. draw_rect(dc, kt->x, kt->y,
  131. 1.5, 0xffffffff);
  132. } else if (!(kf & KT_SELECTED) && (kf & KT_CORNER)) {
  133. draw_rect(dc, kt->x, kt->y,
  134. 2.5, 0x000080ff);
  135. } else if ((kf & KT_SELECTED) && (kf & KT_CORNU)) {
  136. draw_rect(dc, kt->x, kt->y,
  137. 3, 0xc000c0ff);
  138. draw_rect(dc, kt->x, kt->y,
  139. 1.5, 0xffffffff);
  140. } else if (!(kf & KT_SELECTED) && (kf & KT_CORNU)) {
  141. draw_rect(dc, kt->x, kt->y,
  142. 2.5, 0x800080ff);
  143. } else if ((kf & KT_LEFT) || (kf & KT_RIGHT)) {
  144. double th = 1.5708 + (s ? get_knot_th(s, j) : 0);
  145. if (kf & KT_LEFT)
  146. th += 3.1415926;
  147. if (kf & KT_SELECTED) {
  148. draw_half(dc, kt->x, kt->y,
  149. 4, th, 0x000000ff);
  150. draw_half(dc,
  151. kt->x + sin(th), kt->y - cos(th),
  152. 2, th, 0xffffffff);
  153. } else {
  154. draw_half(dc, kt->x, kt->y,
  155. 3, th, 0x000080ff);
  156. }
  157. } else {
  158. draw_dot(dc, kt->x, kt->y,
  159. 2, 0x000080ff);
  160. }
  161. }
  162. }
  163. free_spiro(s);
  164. }
  165. }
  166. static void
  167. draw_selection(x3dc *dc,
  168. plate_edit *pe)
  169. {
  170. plate *p = pe->p;
  171. if (p->motmode == MOTION_MODE_SELECT) {
  172. double rx0 = p->sel_x0;
  173. double ry0 = p->sel_y0;
  174. double rx1 = p->x0;
  175. double ry1 = p->y0;
  176. if (rx0 > rx1) {
  177. double tmp = rx1;
  178. rx1 = rx0;
  179. rx0 = tmp;
  180. }
  181. if (ry0 > ry1) {
  182. double tmp = ry1;
  183. ry1 = ry0;
  184. ry0 = tmp;
  185. }
  186. if (rx1 > rx0 && ry1 > ry0)
  187. draw_raw_rect(dc,
  188. rx0, ry0, rx1, ry1, 0x0000ff20);
  189. }
  190. }
  191. /* Make sure there's room for at least one more undo record. */
  192. static void
  193. makeroom_undo(plate_edit *pe)
  194. {
  195. const int undo_max = sizeof(pe->undo_buf) / sizeof(undo_record);
  196. if (pe->undo_n == undo_max) {
  197. free_plate(pe->undo_buf[0].p);
  198. memmove(pe->undo_buf, pe->undo_buf + 1, (undo_max - 1) * sizeof(undo_record));
  199. pe->undo_i--;
  200. pe->undo_n--;
  201. }
  202. }
  203. static void
  204. set_undo_menuitem(x3widget *me, const char *name, const char *desc)
  205. {
  206. char str[256];
  207. if (desc) {
  208. sprintf(str, "%s %s", name, desc);
  209. } else {
  210. strcpy(str, name);
  211. }
  212. #if 0
  213. gtk_container_foreach(GTK_CONTAINER(me),
  214. (GtkCallback)gtk_label_set_text,
  215. str);
  216. #endif
  217. x3setactive(me, desc != NULL);
  218. }
  219. static void
  220. set_undo_state(plate_edit *pe, const char *undo_desc, const char *redo_desc)
  221. {
  222. set_undo_menuitem(pe->undo_me, "Undo", undo_desc);
  223. set_undo_menuitem(pe->redo_me, "Redo", redo_desc);
  224. }
  225. static void
  226. begin_undo_xn(plate_edit *pe)
  227. {
  228. int i;
  229. if (pe->undo_xn_state != 1) {
  230. for (i = pe->undo_i; i < pe->undo_n; i++)
  231. free_plate(pe->undo_buf[i].p);
  232. pe->undo_n = pe->undo_i;
  233. makeroom_undo(pe);
  234. i = pe->undo_i;
  235. pe->undo_buf[i].description = pe->description;
  236. pe->undo_buf[i].p = copy_plate(pe->p);
  237. pe->undo_n = i + 1;
  238. pe->undo_xn_state = 1;
  239. }
  240. }
  241. static void
  242. dirty_undo_xn(plate_edit *pe, const char *description)
  243. {
  244. if (pe->undo_xn_state == 0) {
  245. g_warning("dirty_undo_xn: not in begin_undo_xn state");
  246. begin_undo_xn(pe);
  247. }
  248. if (description == NULL)
  249. description = pe->p->description;
  250. if (pe->undo_xn_state == 1) {
  251. pe->undo_i++;
  252. pe->undo_xn_state = 2;
  253. set_undo_state(pe, description, NULL);
  254. }
  255. pe->description = description;
  256. }
  257. static void
  258. begindirty_undo_xn(plate_edit *pe, const char *description)
  259. {
  260. begin_undo_xn(pe);
  261. dirty_undo_xn(pe, description);
  262. }
  263. static void
  264. end_undo_xn(plate_edit *pe)
  265. {
  266. if (pe->undo_xn_state == 0) {
  267. g_warning("end_undo_xn: not in undo xn");
  268. }
  269. pe->undo_xn_state = 0;
  270. }
  271. static int
  272. undo(plate_edit *pe)
  273. {
  274. if (pe->undo_i == 0)
  275. return 0;
  276. if (pe->undo_i == pe->undo_n) {
  277. makeroom_undo(pe);
  278. pe->undo_buf[pe->undo_i].description = pe->description;
  279. pe->undo_buf[pe->undo_i].p = pe->p;
  280. pe->undo_n++;
  281. } else {
  282. free_plate(pe->p);
  283. }
  284. pe->undo_i--;
  285. pe->description = pe->undo_buf[pe->undo_i].description;
  286. set_undo_state(pe,
  287. pe->undo_i > 0 ? pe->description : NULL,
  288. pe->undo_buf[pe->undo_i + 1].description);
  289. g_print("undo: %d of %d\n", pe->undo_i, pe->undo_n);
  290. pe->p = copy_plate(pe->undo_buf[pe->undo_i].p);
  291. return 1;
  292. }
  293. static int
  294. redo(plate_edit *pe)
  295. {
  296. if (pe->undo_i >= pe->undo_n - 1)
  297. return 0;
  298. free_plate(pe->p);
  299. pe->undo_i++;
  300. set_undo_state(pe,
  301. pe->undo_buf[pe->undo_i].description,
  302. pe->undo_i < pe->undo_n - 1 ?
  303. pe->undo_buf[pe->undo_i + 1].description : NULL);
  304. pe->description = pe->undo_buf[pe->undo_i].description;
  305. pe->p = copy_plate(pe->undo_buf[pe->undo_i].p);
  306. g_print("redo: %d of %d\n", pe->undo_i, pe->undo_n);
  307. return 1;
  308. }
  309. typedef struct {
  310. x3viewclient base;
  311. plate_edit *pe;
  312. } x3vc_ppe;
  313. static void
  314. ppedit_viewclient_draw(x3viewclient *self, x3dc *dc)
  315. {
  316. plate_edit *pe = ((x3vc_ppe *)self)->pe;
  317. #ifdef VERBOSE
  318. printf("ppedit draw\n");
  319. #endif
  320. #if 1
  321. x3setrgba(dc, 0xffffffff);
  322. #else
  323. x3setrgba(dc, rand() << 8 | 0xff);
  324. #endif
  325. x3rectangle(dc, dc->x, dc->y, dc->width, dc->height);
  326. x3fill(dc);
  327. draw_plate(dc, pe);
  328. draw_selection(dc, pe);
  329. }
  330. static void
  331. ppedit_viewclient_mouse(x3viewclient *self,
  332. int button, int mods,
  333. double x, double y)
  334. {
  335. x3vc_ppe *z = (x3vc_ppe *)self;
  336. plate_edit *pe = z->pe;
  337. plate *p = pe->p;
  338. #ifdef VERBOSE
  339. printf("ppedit mouse: %d %d %g %g\n", button, mods, x, y);
  340. #endif
  341. if (button == 1) {
  342. press_mod pm = 0;
  343. if (mods & GDK_SHIFT_MASK) pm |= PRESS_MOD_SHIFT;
  344. if (mods & GDK_CONTROL_MASK) pm |= PRESS_MOD_CTRL;
  345. if (mods == GDK_2BUTTON_PRESS) pm |= PRESS_MOD_DOUBLE;
  346. if (mods == GDK_3BUTTON_PRESS) pm |= PRESS_MOD_TRIPLE;
  347. begin_undo_xn(pe);
  348. p->description = NULL;
  349. plate_press(p, x, y, pm);
  350. if (p->description) dirty_undo_xn(pe, NULL);
  351. x3view_dirty(pe->view);
  352. } else if (button == -1) {
  353. int need_redraw = (pe->p->motmode == MOTION_MODE_SELECT);
  354. plate_unpress(pe->p);
  355. if (need_redraw) x3view_dirty(pe->view);
  356. } else if (button == 0 && (mods & GDK_BUTTON1_MASK)) {
  357. if (p->motmode == MOTION_MODE_MOVE) {
  358. plate_motion_move(p, x, y);
  359. dirty_undo_xn(pe, NULL);
  360. } else if (p->motmode == MOTION_MODE_SELECT) {
  361. plate_motion_select(p, x, y);
  362. }
  363. x3view_dirty(pe->view);
  364. }
  365. }
  366. static int
  367. ppedit_viewclient_key(x3viewclient *self, char *keyname, int mods, int key)
  368. {
  369. x3vc_ppe *z = (x3vc_ppe *)self;
  370. plate_edit *pe = z->pe;
  371. double dx = 0, dy = 0;
  372. int did_something = 0;
  373. if (!strcmp(keyname, "Left"))
  374. dx = -1;
  375. else if (!strcmp(keyname, "Right"))
  376. dx = 1;
  377. else if (!strcmp(keyname, "Up"))
  378. dy = -1;
  379. else if (!strcmp(keyname, "Down"))
  380. dy = 1;
  381. if (mods & X3_SHIFT_MASK) {
  382. dx *= 10;
  383. dy *= 10;
  384. } else if (mods & X3_CONTROL_MASK) {
  385. dx *= .1;
  386. dy *= .1;
  387. }
  388. if (dx != 0 || dy != 0) {
  389. begindirty_undo_xn(pe, "Keyboard move");
  390. plate_motion_move(pe->p, pe->p->x0 + dx, pe->p->y0 + dy);
  391. end_undo_xn(pe);
  392. did_something = TRUE;
  393. }
  394. if (did_something) {
  395. x3view_dirty(pe->view);
  396. return 1;
  397. } else
  398. return 0;
  399. }
  400. x3viewclient *ppedit_viewclient(plate_edit *pe)
  401. {
  402. x3vc_ppe *result = (x3vc_ppe *)malloc(sizeof(x3vc_ppe));
  403. x3viewclient_init(&result->base);
  404. result->base.draw = ppedit_viewclient_draw;
  405. result->base.mouse = ppedit_viewclient_mouse;
  406. result->base.key = ppedit_viewclient_key;
  407. result->pe = pe;
  408. return &result->base;
  409. }
  410. int
  411. print_func(plate_edit *pe)
  412. {
  413. plate *p = pe->p;
  414. int i;
  415. FILE *f = fopen("/tmp/foo.ps", "w");
  416. bezctx *bc = new_bezctx_ps(f);
  417. fputs(ps_prolog, f);
  418. for (i = 0; i < p->n_sp; i++) {
  419. subpath *sp = &p->sp[i];
  420. free_spiro(draw_subpath(sp, bc));
  421. }
  422. bezctx_ps_close(bc);
  423. fputs(ps_postlog, f);
  424. fclose(f);
  425. return TRUE;
  426. }
  427. int
  428. ppedit_callback(x3widget *w, void *data,
  429. char *cmd, char *what, char *arg, void *more)
  430. {
  431. plate_edit *pe = (plate_edit *)data;
  432. printf("my callback: cmd=\"%s\", what=\"%s\", arg=\"%s\"\n",
  433. cmd, what ? what : "(null)", arg ? arg : "(null)");
  434. if (!strcmp(cmd, "quit")) {
  435. gtk_main_quit();
  436. } else if (!strcmp(cmd, "save")) {
  437. file_write_plate("plate", pe->p);
  438. } else if (!strcmp(cmd, "khid")) {
  439. pe->show_knots = !pe->show_knots;
  440. x3view_dirty(pe->view);
  441. } else if (!strcmp(cmd, "bghd")) {
  442. pe->show_bg = !pe->show_bg;
  443. x3view_dirty(pe->view);
  444. } else if (!strcmp(cmd, "prin")) {
  445. return print_func(pe);
  446. } else if (!strcmp(cmd, "undo")) {
  447. undo(pe);
  448. x3view_dirty(pe->view);
  449. } else if (!strcmp(cmd, "redo")) {
  450. redo(pe);
  451. x3view_dirty(pe->view);
  452. } else if (!strcmp(cmd, "togc")) {
  453. begindirty_undo_xn(pe, "Toggle Corner");
  454. plate_toggle_corner(pe->p);
  455. end_undo_xn(pe);
  456. x3view_dirty(pe->view);
  457. } else if (!strcmp(cmd, "delp")) {
  458. begindirty_undo_xn(pe, "Delete Point");
  459. plate_delete_pt(pe->p);
  460. end_undo_xn(pe);
  461. x3view_dirty(pe->view);
  462. } else if (!strcmp(cmd, "mods")) {
  463. pe->p->mmode = MOUSE_MODE_SELECT;
  464. } else if (!strcmp(cmd, "modo")) {
  465. pe->p->mmode = MOUSE_MODE_ADD_CURVE;
  466. } else if (!strcmp(cmd, "modv")) {
  467. pe->p->mmode = MOUSE_MODE_ADD_CORNER;
  468. } else if (!strcmp(cmd, "modl")) {
  469. pe->p->mmode = MOUSE_MODE_ADD_LEFT;
  470. } else if (!strcmp(cmd, "modr")) {
  471. pe->p->mmode = MOUSE_MODE_ADD_RIGHT;
  472. } else if (!strcmp(cmd, "modc")) {
  473. pe->p->mmode = MOUSE_MODE_ADD_CORNU;
  474. }
  475. return 1;
  476. }
  477. static void
  478. create_mainwin(plate_edit *pe)
  479. {
  480. x3widget *mainwin;
  481. x3widget *view;
  482. x3widget *menu;
  483. void *data = pe;
  484. x3viewflags viewflags = x3view_click | x3view_hover | x3view_key |
  485. x3view_2d | x3view_scroll;
  486. mainwin = x3window(x3window_main, "ppedit", ppedit_callback, data);
  487. menu = x3menu(mainwin, "File");
  488. x3menuitem(menu, "Save", "save", "<cmd>s");
  489. x3menuitem(menu, "Print", "prin", "<cmd>p");
  490. x3menuitem(menu, "Quit", "quit", "<cmd>q");
  491. menu = x3menu(mainwin, "Edit");
  492. pe->undo_me = x3menuitem(menu, "Undo", "undo", "<cmd>z");
  493. pe->redo_me = x3menuitem(menu, "Redo", "redo", "<cmd>y");
  494. //set_undo_state(p, NULL, NULL);
  495. x3menuitem(menu, "Toggle Corner", "togc", "<cmd>t");
  496. x3menuitem(menu, "Delete Point", "delp", "<cmd>d");
  497. x3menuitem(menu, "Selection Mode", "mods", "1");
  498. x3menuitem(menu, "Add Curve Mode", "modo", "2");
  499. x3menuitem(menu, "Add Corner Mode", "modv", "3");
  500. x3menuitem(menu, "Add Left Mode", "modl", "4");
  501. x3menuitem(menu, "Add Right Mode", "modr", "5");
  502. x3menuitem(menu, "Add G2 point Mode", "modc", "6");
  503. menu = x3menu(mainwin, "View");
  504. pe->show_knots_me = x3menuitem(menu, "Hide Knots", "khid", "<cmd>k");
  505. pe->show_bg_me = x3menuitem(menu, "Hide BG", "bghd", "<cmd>b");
  506. pe->view = x3view(mainwin, viewflags, ppedit_viewclient(pe));
  507. #ifdef X3_GTK
  508. gtk_window_set_default_size(GTK_WINDOW(mainwin->widget), 512, 512);
  509. #endif
  510. x3_window_show(mainwin);
  511. }
  512. int main(int argc, char **argv)
  513. {
  514. plate_edit pe;
  515. plate *p = NULL;
  516. x3init(&argc, &argv);
  517. char *reason;
  518. if (argc > 1)
  519. p = file_read_plate(argv[1]);
  520. if (p == NULL)
  521. p = new_plate();
  522. pe.p = p;
  523. pe.undo_n = 0;
  524. pe.undo_i = 0;
  525. pe.undo_xn_state = 0;
  526. pe.show_knots = 1;
  527. pe.show_bg = 1;
  528. pe.bg_image = load_image_file("/tmp/foo.ppm", &reason);
  529. create_mainwin(&pe);
  530. x3main();
  531. return 0;
  532. }