mirror of
git://projects.qi-hardware.com/fped.git
synced 2024-11-28 17:00:18 +02:00
589 lines
12 KiB
C
589 lines
12 KiB
C
/*
|
|
* 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;
|
|
}
|