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