123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589 |
- /*
- * gui_canvas.c - GUI, canvas
- *
- * Written 2009, 2010, 2012, 2015 by Werner Almesberger
- * Copyright 2009, 2010, 2012, 2015 by Werner Almesberger
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- */
- #include <math.h>
- #include <gtk/gtk.h>
- #include <gdk/gdkkeysyms.h>
- #include "obj.h"
- #include "delete.h"
- #include "inst.h"
- #include "gui_util.h"
- #include "gui_inst.h"
- #include "gui_style.h"
- #include "gui_status.h"
- #include "gui_tool.h"
- #include "gui.h"
- #include "gui_frame_drag.h"
- #include "gui_frame.h"
- #include "gui_canvas.h"
- #if 0
- #define DPRINTF(fmt, ...) fprintf(stderr, fmt "\n", ##__VA_ARGS__)
- #else
- #define DPRINTF(fmt, ...)
- #endif
- void (*highlight)(void) = NULL;
- static struct coord curr_pos; /* canvas coordinates ! */
- static struct coord user_origin = { 0, 0 };
- static int dragging = 0;
- static int drag_escaped = 0; /* 1 once we've made it out of the drag radius */
- static struct coord drag_start;
- static struct inst *selected_before_drag;
- /* instance selected before dragging. we use it to do the click-to-select
- routine in case we later find out the drag was really just a click. */
- /* ----- status display ---------------------------------------------------- */
- static void update_zoom(void)
- {
- status_set_zoom("Zoom factor", "x%d", draw_ctx.scale);
- }
- static void update_pos(struct coord pos)
- {
- struct coord user;
- unit_type diag;
- set_with_units(status_set_sys_x, "X ", pos.x, "Absolute X position");
- set_with_units(status_set_sys_y, "Y ", pos.y, "Absolute Y position");
- user.x = pos.x-user_origin.x;
- user.y = pos.y-user_origin.y;
- set_with_units(status_set_user_x, "x ", user.x,
- "User X position. Press SPACE to zero.");
- set_with_units(status_set_user_y, "y ", user.y,
- "User Y position. Press SPACE to zero.");
- if (!selected_inst) {
- diag = hypot(user.x, user.y);
- set_with_units(status_set_r, "r = ", diag,
- "Distance from user origin");
- status_set_angle_xy("Angle from user origin", user);
- }
- }
- void refresh_pos(void)
- {
- update_pos(canvas_to_coord(curr_pos.x, curr_pos.y));
- }
- /* ----- coordinate system ------------------------------------------------- */
- static void center(const struct bbox *this_bbox)
- {
- struct bbox bbox;
- bbox = this_bbox ? *this_bbox : inst_get_bbox(NULL);
- draw_ctx.center.x = (bbox.min.x+bbox.max.x)/2;
- draw_ctx.center.y = (bbox.min.y+bbox.max.y)/2;
- }
- static void auto_scale(const struct bbox *this_bbox)
- {
- struct bbox bbox;
- unit_type h, w;
- int sx, sy;
- float aw, ah;
- bbox = this_bbox ? *this_bbox : inst_get_bbox(NULL);
- aw = draw_ctx.widget->allocation.width;
- ah = draw_ctx.widget->allocation.height;
- h = bbox.max.x-bbox.min.x;
- w = bbox.max.y-bbox.min.y;
- aw -= 2*CANVAS_CLEARANCE;
- ah -= 2*CANVAS_CLEARANCE;
- if (aw < 1)
- aw = 1;
- if (ah < 1)
- ah = 1;
- sx = ceil(h/aw);
- sy = ceil(w/ah);
- draw_ctx.scale = sx > sy ? sx : sy > 0 ? sy : 1;
- update_zoom();
- }
- /* ----- drawing ----------------------------------------------------------- */
- void redraw(void)
- {
- float aw, ah;
- aw = draw_ctx.widget->allocation.width;
- ah = draw_ctx.widget->allocation.height;
- gdk_draw_rectangle(draw_ctx.widget->window,
- instantiation_error ? gc_bg_error : gc_bg, TRUE, 0, 0, aw, ah);
- DPRINTF("--- redraw: inst_draw ---");
- inst_draw();
- if (highlight)
- highlight();
- DPRINTF("--- redraw: tool_redraw ---");
- tool_redraw();
- DPRINTF("--- redraw: done ---");
- }
- /* ----- drag -------------------------------------------------------------- */
- static void drag_left(struct coord pos)
- {
- if (!dragging)
- return;
- if (!drag_escaped &&
- hypot(pos.x-drag_start.x, pos.y-drag_start.y)/draw_ctx.scale <
- DRAG_MIN_R)
- return;
- drag_escaped = 1;
- tool_drag(pos);
- }
- static void drag_middle(struct coord pos)
- {
- }
- static gboolean motion_notify_event(GtkWidget *widget, GdkEventMotion *event,
- gpointer data)
- {
- struct coord pos = canvas_to_coord(event->x, event->y);
- DPRINTF("--- motion ---");
- curr_pos.x = event->x;
- curr_pos.y = event->y;
- tool_hover(pos);
- if (event->state & GDK_BUTTON1_MASK)
- drag_left(pos);
- if (event->state & GDK_BUTTON2_MASK)
- drag_middle(pos);
- update_pos(pos);
- return FALSE;
- }
- /* ----- drag and drop (frame to canvas) ----------------------------------- */
- void canvas_frame_begin(struct frame *frame)
- {
- inst_deselect(); /* don't drag away bits of the selected object */
- redraw();
- tool_push_frame(frame);
- }
- int canvas_frame_motion(struct frame *frame, int x, int y)
- {
- struct coord pos = canvas_to_coord(x, y);
- return tool_hover(pos);
- }
- void canvas_frame_end(void)
- {
- tool_dehover();
- tool_pop_frame();
- }
- int canvas_frame_drop(struct frame *frame, int x, int y)
- {
- struct coord pos = canvas_to_coord(x, y);
- if (!tool_place_frame(frame, pos))
- return FALSE;
- change_world();
- return TRUE;
- }
- /* ----- button press and release ------------------------------------------ */
- static void click_to_select(struct coord pos)
- {
- const struct inst *prev;
- tool_reset();
- prev = selected_inst;
- inst_select(pos);
- if (prev != selected_inst)
- redraw();
- }
- static gboolean button_press_event(GtkWidget *widget, GdkEventButton *event,
- gpointer data)
- {
- struct coord pos = canvas_to_coord(event->x, event->y);
- int res;
- DPRINTF("--- button press ---");
- gtk_widget_grab_focus(widget);
- switch (event->button) {
- case 1:
- if (dragging) {
- fprintf(stderr, "HUH ?!?\n");
- tool_cancel_drag();
- dragging = 0;
- }
- res = tool_consider_drag(pos);
- /* tool doesn't do drag */
- if (res < 0) {
- change_world();
- inst_deselect();
- break;
- }
- if (res) {
- selected_before_drag = selected_inst;
- inst_deselect();
- redraw();
- dragging = 1;
- drag_escaped = 0;
- drag_start = pos;
- break;
- }
- click_to_select(pos);
- break;
- case 2:
- tool_dehover();
- draw_ctx.center = pos;
- redraw();
- tool_hover(canvas_to_coord(event->x, event->y));
- break;
- }
- return TRUE;
- }
- static gboolean button_release_event(GtkWidget *widget, GdkEventButton *event,
- gpointer data)
- {
- struct coord pos = canvas_to_coord(event->x, event->y);
- DPRINTF("--- button release ---");
- switch (event->button) {
- case 1:
- if (is_dragging_anything())
- return FALSE;
- if (!dragging)
- break;
- drag_left(pos);
- dragging = 0;
- if (!drag_escaped) {
- tool_cancel_drag();
- selected_inst = selected_before_drag;
- click_to_select(pos);
- break;
- }
- if (tool_end_drag(pos))
- change_world();
- break;
- }
- return TRUE;
- }
- /* ----- zoom control ------------------------------------------------------ */
- static void zoom_in(struct coord pos)
- {
- if (draw_ctx.scale < 2)
- return;
- tool_dehover();
- draw_ctx.scale /= 2;
- draw_ctx.center.x = (draw_ctx.center.x+pos.x)/2;
- draw_ctx.center.y = (draw_ctx.center.y+pos.y)/2;
- update_zoom();
- redraw();
- tool_hover(pos);
- }
- static void zoom_out(struct coord pos)
- {
- struct bbox bbox;
- bbox = inst_get_bbox(NULL);
- bbox.min = translate(bbox.min);
- bbox.max = translate(bbox.max);
- if (bbox.min.x >= ZOOM_STOP_BORDER &&
- bbox.max.y >= ZOOM_STOP_BORDER &&
- bbox.max.x < draw_ctx.widget->allocation.width-ZOOM_STOP_BORDER &&
- bbox.min.y < draw_ctx.widget->allocation.height-ZOOM_STOP_BORDER)
- return;
- tool_dehover();
- draw_ctx.scale *= 2;
- draw_ctx.center.x = 2*draw_ctx.center.x-pos.x;
- draw_ctx.center.y = 2*draw_ctx.center.y-pos.y;
- update_zoom();
- redraw();
- tool_hover(pos);
- }
- void zoom_in_center(void)
- {
- zoom_in(draw_ctx.center);
- }
- void zoom_out_center(void)
- {
- zoom_out(draw_ctx.center);
- }
- void zoom_to_frame(void)
- {
- tool_dehover();
- center(&active_frame_bbox);
- auto_scale(&active_frame_bbox);
- redraw();
- tool_hover(canvas_to_coord(curr_pos.x, curr_pos.y));
- }
- void zoom_to_extents(void)
- {
- tool_dehover();
- center(NULL);
- auto_scale(NULL);
- redraw();
- tool_hover(canvas_to_coord(curr_pos.x, curr_pos.y));
- }
- static gboolean scroll_event(GtkWidget *widget, GdkEventScroll *event,
- gpointer data)
- {
- struct coord pos = canvas_to_coord(event->x, event->y);
- gtk_widget_grab_focus(widget);
- switch (event->direction) {
- case GDK_SCROLL_UP:
- zoom_in(pos);
- break;
- case GDK_SCROLL_DOWN:
- zoom_out(pos);
- break;
- default:
- /* ignore */;
- }
- return TRUE;
- }
- /* ----- keys -------------------------------------------------------------- */
- static gboolean key_press_event(GtkWidget *widget, GdkEventKey *event,
- gpointer data)
- {
- struct coord pos = canvas_to_coord(curr_pos.x, curr_pos.y);
- DPRINTF("--- key press ---");
- switch (event->keyval) {
- case ' ':
- user_origin = pos;
- update_pos(pos);
- break;
- case '+':
- case '=':
- zoom_in(pos);
- break;
- case '-':
- zoom_out(pos);
- break;
- case '*':
- zoom_to_extents();
- break;
- case '#':
- zoom_to_frame();
- break;
- case '.':
- tool_dehover();
- draw_ctx.center = pos;
- redraw();
- tool_hover(canvas_to_coord(curr_pos.x, curr_pos.y));
- break;
- case GDK_BackSpace:
- case GDK_Delete:
- #if 0
- case GDK_KP_Delete:
- if (selected_inst) {
- inst_delete(selected_inst);
- change_world();
- }
- break;
- #endif
- case 'u':
- if (undelete())
- change_world();
- break;
- case '/':
- sidebar = sidebar == sidebar_last ? 0 : sidebar + 1;
- update_menu_bar();
- change_world();
- break;
- }
- return TRUE;
- }
- /* ----- expose event ------------------------------------------------------ */
- static gboolean expose_event(GtkWidget *widget, GdkEventExpose *event,
- gpointer data)
- {
- static int first = 1;
- DPRINTF("--- expose ---");
- if (first) {
- init_canvas();
- first = 0;
- }
- tool_dehover();
- redraw();
- return TRUE;
- }
- /* ----- enter/leave ------------------------------------------------------- */
- static gboolean enter_notify_event(GtkWidget *widget, GdkEventCrossing *event,
- gpointer data)
- {
- DPRINTF("--- enter ---");
- gtk_widget_grab_focus(widget);
- return FALSE;
- }
- static gboolean leave_notify_event(GtkWidget *widget, GdkEventCrossing *event,
- gpointer data)
- {
- DPRINTF("--- leave ---");
- if (dragging)
- tool_cancel_drag();
- tool_dehover();
- dragging = 0;
- return FALSE;
- }
- /* ----- tooltip ----------------------------------------------------------- */
- static gboolean canvas_tooltip(GtkWidget *widget, gint x, gint y,
- gboolean keyboard_mode, GtkTooltip *tooltip, gpointer user_data)
- {
- struct coord pos = canvas_to_coord(x, y);
- const char *res;
- res = tool_tip(pos);
- if (!res)
- return FALSE;
- gtk_tooltip_set_markup(tooltip, res);
- return TRUE;
- }
- /* ----- canvas setup ------------------------------------------------------ */
- /*
- * Note that we call init_canvas twice: first to make sure we'll make it safely
- * through select_frame, and the second time to set the geometry for the actual
- * screen.
- */
- void init_canvas(void)
- {
- center(NULL);
- auto_scale(NULL);
- }
- GtkWidget *make_canvas(void)
- {
- GtkWidget *canvas;
- GdkColor black = { 0, 0, 0, 0 };
- /* Canvas */
- canvas = gtk_drawing_area_new();
- gtk_widget_modify_bg(canvas, GTK_STATE_NORMAL, &black);
- g_signal_connect(G_OBJECT(canvas), "motion_notify_event",
- G_CALLBACK(motion_notify_event), NULL);
- g_signal_connect(G_OBJECT(canvas), "button_press_event",
- G_CALLBACK(button_press_event), NULL);
- g_signal_connect(G_OBJECT(canvas), "button_release_event",
- G_CALLBACK(button_release_event), NULL);
- g_signal_connect(G_OBJECT(canvas), "scroll_event",
- G_CALLBACK(scroll_event), NULL);
- GTK_WIDGET_SET_FLAGS(canvas, GTK_CAN_FOCUS);
- g_signal_connect(G_OBJECT(canvas), "key_press_event",
- G_CALLBACK(key_press_event), NULL);
- g_signal_connect(G_OBJECT(canvas), "expose_event",
- G_CALLBACK(expose_event), NULL);
- g_signal_connect(G_OBJECT(canvas), "enter_notify_event",
- G_CALLBACK(enter_notify_event), NULL);
- g_signal_connect(G_OBJECT(canvas), "leave_notify_event",
- G_CALLBACK(leave_notify_event), NULL);
- gtk_widget_set(canvas, "has-tooltip", TRUE, NULL);
- g_signal_connect(G_OBJECT(canvas), "query_tooltip",
- G_CALLBACK(canvas_tooltip), NULL);
- gtk_widget_set_events(canvas,
- GDK_EXPOSE | GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK |
- GDK_KEY_PRESS_MASK |
- GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
- GDK_SCROLL |
- GDK_POINTER_MOTION_MASK);
- gtk_widget_set_double_buffered(canvas, FALSE);
- setup_canvas_drag(canvas);
- draw_ctx.widget = canvas;
- return canvas;
- }
|