gui_canvas.c 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589
  1. /*
  2. * gui_canvas.c - GUI, canvas
  3. *
  4. * Written 2009, 2010, 2012, 2015 by Werner Almesberger
  5. * Copyright 2009, 2010, 2012, 2015 by Werner Almesberger
  6. *
  7. * This program is free software; you can redistribute it and/or modify
  8. * it under the terms of the GNU General Public License as published by
  9. * the Free Software Foundation; either version 2 of the License, or
  10. * (at your option) any later version.
  11. */
  12. #include <math.h>
  13. #include <gtk/gtk.h>
  14. #include <gdk/gdkkeysyms.h>
  15. #include "obj.h"
  16. #include "delete.h"
  17. #include "inst.h"
  18. #include "gui_util.h"
  19. #include "gui_inst.h"
  20. #include "gui_style.h"
  21. #include "gui_status.h"
  22. #include "gui_tool.h"
  23. #include "gui.h"
  24. #include "gui_frame_drag.h"
  25. #include "gui_frame.h"
  26. #include "gui_canvas.h"
  27. #if 0
  28. #define DPRINTF(fmt, ...) fprintf(stderr, fmt "\n", ##__VA_ARGS__)
  29. #else
  30. #define DPRINTF(fmt, ...)
  31. #endif
  32. void (*highlight)(void) = NULL;
  33. static struct coord curr_pos; /* canvas coordinates ! */
  34. static struct coord user_origin = { 0, 0 };
  35. static int dragging = 0;
  36. static int drag_escaped = 0; /* 1 once we've made it out of the drag radius */
  37. static struct coord drag_start;
  38. static struct inst *selected_before_drag;
  39. /* instance selected before dragging. we use it to do the click-to-select
  40. routine in case we later find out the drag was really just a click. */
  41. /* ----- status display ---------------------------------------------------- */
  42. static void update_zoom(void)
  43. {
  44. status_set_zoom("Zoom factor", "x%d", draw_ctx.scale);
  45. }
  46. static void update_pos(struct coord pos)
  47. {
  48. struct coord user;
  49. unit_type diag;
  50. set_with_units(status_set_sys_x, "X ", pos.x, "Absolute X position");
  51. set_with_units(status_set_sys_y, "Y ", pos.y, "Absolute Y position");
  52. user.x = pos.x-user_origin.x;
  53. user.y = pos.y-user_origin.y;
  54. set_with_units(status_set_user_x, "x ", user.x,
  55. "User X position. Press SPACE to zero.");
  56. set_with_units(status_set_user_y, "y ", user.y,
  57. "User Y position. Press SPACE to zero.");
  58. if (!selected_inst) {
  59. diag = hypot(user.x, user.y);
  60. set_with_units(status_set_r, "r = ", diag,
  61. "Distance from user origin");
  62. status_set_angle_xy("Angle from user origin", user);
  63. }
  64. }
  65. void refresh_pos(void)
  66. {
  67. update_pos(canvas_to_coord(curr_pos.x, curr_pos.y));
  68. }
  69. /* ----- coordinate system ------------------------------------------------- */
  70. static void center(const struct bbox *this_bbox)
  71. {
  72. struct bbox bbox;
  73. bbox = this_bbox ? *this_bbox : inst_get_bbox(NULL);
  74. draw_ctx.center.x = (bbox.min.x+bbox.max.x)/2;
  75. draw_ctx.center.y = (bbox.min.y+bbox.max.y)/2;
  76. }
  77. static void auto_scale(const struct bbox *this_bbox)
  78. {
  79. struct bbox bbox;
  80. unit_type h, w;
  81. int sx, sy;
  82. float aw, ah;
  83. bbox = this_bbox ? *this_bbox : inst_get_bbox(NULL);
  84. aw = draw_ctx.widget->allocation.width;
  85. ah = draw_ctx.widget->allocation.height;
  86. h = bbox.max.x-bbox.min.x;
  87. w = bbox.max.y-bbox.min.y;
  88. aw -= 2*CANVAS_CLEARANCE;
  89. ah -= 2*CANVAS_CLEARANCE;
  90. if (aw < 1)
  91. aw = 1;
  92. if (ah < 1)
  93. ah = 1;
  94. sx = ceil(h/aw);
  95. sy = ceil(w/ah);
  96. draw_ctx.scale = sx > sy ? sx : sy > 0 ? sy : 1;
  97. update_zoom();
  98. }
  99. /* ----- drawing ----------------------------------------------------------- */
  100. void redraw(void)
  101. {
  102. float aw, ah;
  103. aw = draw_ctx.widget->allocation.width;
  104. ah = draw_ctx.widget->allocation.height;
  105. gdk_draw_rectangle(draw_ctx.widget->window,
  106. instantiation_error ? gc_bg_error : gc_bg, TRUE, 0, 0, aw, ah);
  107. DPRINTF("--- redraw: inst_draw ---");
  108. inst_draw();
  109. if (highlight)
  110. highlight();
  111. DPRINTF("--- redraw: tool_redraw ---");
  112. tool_redraw();
  113. DPRINTF("--- redraw: done ---");
  114. }
  115. /* ----- drag -------------------------------------------------------------- */
  116. static void drag_left(struct coord pos)
  117. {
  118. if (!dragging)
  119. return;
  120. if (!drag_escaped &&
  121. hypot(pos.x-drag_start.x, pos.y-drag_start.y)/draw_ctx.scale <
  122. DRAG_MIN_R)
  123. return;
  124. drag_escaped = 1;
  125. tool_drag(pos);
  126. }
  127. static void drag_middle(struct coord pos)
  128. {
  129. }
  130. static gboolean motion_notify_event(GtkWidget *widget, GdkEventMotion *event,
  131. gpointer data)
  132. {
  133. struct coord pos = canvas_to_coord(event->x, event->y);
  134. DPRINTF("--- motion ---");
  135. curr_pos.x = event->x;
  136. curr_pos.y = event->y;
  137. tool_hover(pos);
  138. if (event->state & GDK_BUTTON1_MASK)
  139. drag_left(pos);
  140. if (event->state & GDK_BUTTON2_MASK)
  141. drag_middle(pos);
  142. update_pos(pos);
  143. return FALSE;
  144. }
  145. /* ----- drag and drop (frame to canvas) ----------------------------------- */
  146. void canvas_frame_begin(struct frame *frame)
  147. {
  148. inst_deselect(); /* don't drag away bits of the selected object */
  149. redraw();
  150. tool_push_frame(frame);
  151. }
  152. int canvas_frame_motion(struct frame *frame, int x, int y)
  153. {
  154. struct coord pos = canvas_to_coord(x, y);
  155. return tool_hover(pos);
  156. }
  157. void canvas_frame_end(void)
  158. {
  159. tool_dehover();
  160. tool_pop_frame();
  161. }
  162. int canvas_frame_drop(struct frame *frame, int x, int y)
  163. {
  164. struct coord pos = canvas_to_coord(x, y);
  165. if (!tool_place_frame(frame, pos))
  166. return FALSE;
  167. change_world();
  168. return TRUE;
  169. }
  170. /* ----- button press and release ------------------------------------------ */
  171. static void click_to_select(struct coord pos)
  172. {
  173. const struct inst *prev;
  174. tool_reset();
  175. prev = selected_inst;
  176. inst_select(pos);
  177. if (prev != selected_inst)
  178. redraw();
  179. }
  180. static gboolean button_press_event(GtkWidget *widget, GdkEventButton *event,
  181. gpointer data)
  182. {
  183. struct coord pos = canvas_to_coord(event->x, event->y);
  184. int res;
  185. DPRINTF("--- button press ---");
  186. gtk_widget_grab_focus(widget);
  187. switch (event->button) {
  188. case 1:
  189. if (dragging) {
  190. fprintf(stderr, "HUH ?!?\n");
  191. tool_cancel_drag();
  192. dragging = 0;
  193. }
  194. res = tool_consider_drag(pos);
  195. /* tool doesn't do drag */
  196. if (res < 0) {
  197. change_world();
  198. inst_deselect();
  199. break;
  200. }
  201. if (res) {
  202. selected_before_drag = selected_inst;
  203. inst_deselect();
  204. redraw();
  205. dragging = 1;
  206. drag_escaped = 0;
  207. drag_start = pos;
  208. break;
  209. }
  210. click_to_select(pos);
  211. break;
  212. case 2:
  213. tool_dehover();
  214. draw_ctx.center = pos;
  215. redraw();
  216. tool_hover(canvas_to_coord(event->x, event->y));
  217. break;
  218. }
  219. return TRUE;
  220. }
  221. static gboolean button_release_event(GtkWidget *widget, GdkEventButton *event,
  222. gpointer data)
  223. {
  224. struct coord pos = canvas_to_coord(event->x, event->y);
  225. DPRINTF("--- button release ---");
  226. switch (event->button) {
  227. case 1:
  228. if (is_dragging_anything())
  229. return FALSE;
  230. if (!dragging)
  231. break;
  232. drag_left(pos);
  233. dragging = 0;
  234. if (!drag_escaped) {
  235. tool_cancel_drag();
  236. selected_inst = selected_before_drag;
  237. click_to_select(pos);
  238. break;
  239. }
  240. if (tool_end_drag(pos))
  241. change_world();
  242. break;
  243. }
  244. return TRUE;
  245. }
  246. /* ----- zoom control ------------------------------------------------------ */
  247. static void zoom_in(struct coord pos)
  248. {
  249. if (draw_ctx.scale < 2)
  250. return;
  251. tool_dehover();
  252. draw_ctx.scale /= 2;
  253. draw_ctx.center.x = (draw_ctx.center.x+pos.x)/2;
  254. draw_ctx.center.y = (draw_ctx.center.y+pos.y)/2;
  255. update_zoom();
  256. redraw();
  257. tool_hover(pos);
  258. }
  259. static void zoom_out(struct coord pos)
  260. {
  261. struct bbox bbox;
  262. bbox = inst_get_bbox(NULL);
  263. bbox.min = translate(bbox.min);
  264. bbox.max = translate(bbox.max);
  265. if (bbox.min.x >= ZOOM_STOP_BORDER &&
  266. bbox.max.y >= ZOOM_STOP_BORDER &&
  267. bbox.max.x < draw_ctx.widget->allocation.width-ZOOM_STOP_BORDER &&
  268. bbox.min.y < draw_ctx.widget->allocation.height-ZOOM_STOP_BORDER)
  269. return;
  270. tool_dehover();
  271. draw_ctx.scale *= 2;
  272. draw_ctx.center.x = 2*draw_ctx.center.x-pos.x;
  273. draw_ctx.center.y = 2*draw_ctx.center.y-pos.y;
  274. update_zoom();
  275. redraw();
  276. tool_hover(pos);
  277. }
  278. void zoom_in_center(void)
  279. {
  280. zoom_in(draw_ctx.center);
  281. }
  282. void zoom_out_center(void)
  283. {
  284. zoom_out(draw_ctx.center);
  285. }
  286. void zoom_to_frame(void)
  287. {
  288. tool_dehover();
  289. center(&active_frame_bbox);
  290. auto_scale(&active_frame_bbox);
  291. redraw();
  292. tool_hover(canvas_to_coord(curr_pos.x, curr_pos.y));
  293. }
  294. void zoom_to_extents(void)
  295. {
  296. tool_dehover();
  297. center(NULL);
  298. auto_scale(NULL);
  299. redraw();
  300. tool_hover(canvas_to_coord(curr_pos.x, curr_pos.y));
  301. }
  302. static gboolean scroll_event(GtkWidget *widget, GdkEventScroll *event,
  303. gpointer data)
  304. {
  305. struct coord pos = canvas_to_coord(event->x, event->y);
  306. gtk_widget_grab_focus(widget);
  307. switch (event->direction) {
  308. case GDK_SCROLL_UP:
  309. zoom_in(pos);
  310. break;
  311. case GDK_SCROLL_DOWN:
  312. zoom_out(pos);
  313. break;
  314. default:
  315. /* ignore */;
  316. }
  317. return TRUE;
  318. }
  319. /* ----- keys -------------------------------------------------------------- */
  320. static gboolean key_press_event(GtkWidget *widget, GdkEventKey *event,
  321. gpointer data)
  322. {
  323. struct coord pos = canvas_to_coord(curr_pos.x, curr_pos.y);
  324. DPRINTF("--- key press ---");
  325. switch (event->keyval) {
  326. case ' ':
  327. user_origin = pos;
  328. update_pos(pos);
  329. break;
  330. case '+':
  331. case '=':
  332. zoom_in(pos);
  333. break;
  334. case '-':
  335. zoom_out(pos);
  336. break;
  337. case '*':
  338. zoom_to_extents();
  339. break;
  340. case '#':
  341. zoom_to_frame();
  342. break;
  343. case '.':
  344. tool_dehover();
  345. draw_ctx.center = pos;
  346. redraw();
  347. tool_hover(canvas_to_coord(curr_pos.x, curr_pos.y));
  348. break;
  349. case GDK_BackSpace:
  350. case GDK_Delete:
  351. #if 0
  352. case GDK_KP_Delete:
  353. if (selected_inst) {
  354. inst_delete(selected_inst);
  355. change_world();
  356. }
  357. break;
  358. #endif
  359. case 'u':
  360. if (undelete())
  361. change_world();
  362. break;
  363. case '/':
  364. sidebar = sidebar == sidebar_last ? 0 : sidebar + 1;
  365. update_menu_bar();
  366. change_world();
  367. break;
  368. }
  369. return TRUE;
  370. }
  371. /* ----- expose event ------------------------------------------------------ */
  372. static gboolean expose_event(GtkWidget *widget, GdkEventExpose *event,
  373. gpointer data)
  374. {
  375. static int first = 1;
  376. DPRINTF("--- expose ---");
  377. if (first) {
  378. init_canvas();
  379. first = 0;
  380. }
  381. tool_dehover();
  382. redraw();
  383. return TRUE;
  384. }
  385. /* ----- enter/leave ------------------------------------------------------- */
  386. static gboolean enter_notify_event(GtkWidget *widget, GdkEventCrossing *event,
  387. gpointer data)
  388. {
  389. DPRINTF("--- enter ---");
  390. gtk_widget_grab_focus(widget);
  391. return FALSE;
  392. }
  393. static gboolean leave_notify_event(GtkWidget *widget, GdkEventCrossing *event,
  394. gpointer data)
  395. {
  396. DPRINTF("--- leave ---");
  397. if (dragging)
  398. tool_cancel_drag();
  399. tool_dehover();
  400. dragging = 0;
  401. return FALSE;
  402. }
  403. /* ----- tooltip ----------------------------------------------------------- */
  404. static gboolean canvas_tooltip(GtkWidget *widget, gint x, gint y,
  405. gboolean keyboard_mode, GtkTooltip *tooltip, gpointer user_data)
  406. {
  407. struct coord pos = canvas_to_coord(x, y);
  408. const char *res;
  409. res = tool_tip(pos);
  410. if (!res)
  411. return FALSE;
  412. gtk_tooltip_set_markup(tooltip, res);
  413. return TRUE;
  414. }
  415. /* ----- canvas setup ------------------------------------------------------ */
  416. /*
  417. * Note that we call init_canvas twice: first to make sure we'll make it safely
  418. * through select_frame, and the second time to set the geometry for the actual
  419. * screen.
  420. */
  421. void init_canvas(void)
  422. {
  423. center(NULL);
  424. auto_scale(NULL);
  425. }
  426. GtkWidget *make_canvas(void)
  427. {
  428. GtkWidget *canvas;
  429. GdkColor black = { 0, 0, 0, 0 };
  430. /* Canvas */
  431. canvas = gtk_drawing_area_new();
  432. gtk_widget_modify_bg(canvas, GTK_STATE_NORMAL, &black);
  433. g_signal_connect(G_OBJECT(canvas), "motion_notify_event",
  434. G_CALLBACK(motion_notify_event), NULL);
  435. g_signal_connect(G_OBJECT(canvas), "button_press_event",
  436. G_CALLBACK(button_press_event), NULL);
  437. g_signal_connect(G_OBJECT(canvas), "button_release_event",
  438. G_CALLBACK(button_release_event), NULL);
  439. g_signal_connect(G_OBJECT(canvas), "scroll_event",
  440. G_CALLBACK(scroll_event), NULL);
  441. GTK_WIDGET_SET_FLAGS(canvas, GTK_CAN_FOCUS);
  442. g_signal_connect(G_OBJECT(canvas), "key_press_event",
  443. G_CALLBACK(key_press_event), NULL);
  444. g_signal_connect(G_OBJECT(canvas), "expose_event",
  445. G_CALLBACK(expose_event), NULL);
  446. g_signal_connect(G_OBJECT(canvas), "enter_notify_event",
  447. G_CALLBACK(enter_notify_event), NULL);
  448. g_signal_connect(G_OBJECT(canvas), "leave_notify_event",
  449. G_CALLBACK(leave_notify_event), NULL);
  450. gtk_widget_set(canvas, "has-tooltip", TRUE, NULL);
  451. g_signal_connect(G_OBJECT(canvas), "query_tooltip",
  452. G_CALLBACK(canvas_tooltip), NULL);
  453. gtk_widget_set_events(canvas,
  454. GDK_EXPOSE | GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK |
  455. GDK_KEY_PRESS_MASK |
  456. GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
  457. GDK_SCROLL |
  458. GDK_POINTER_MOTION_MASK);
  459. gtk_widget_set_double_buffered(canvas, FALSE);
  460. setup_canvas_drag(canvas);
  461. draw_ctx.widget = canvas;
  462. return canvas;
  463. }