mirror of
git://projects.qi-hardware.com/fped.git
synced 2024-11-18 11:22:27 +02:00
86c082f5a9
Until now, everything used the bounding box of the root frame which contains all the packages, visible or not. We now also record what ends up in which package, allowing inst_get_bbox to return the bounding box of a specific package. This is mainly useful for scaling Postscript output where only one package is printed per sheet and there is not much point in reserving space for any other packages that may be generated from the same footprint definition.
590 lines
12 KiB
C
590 lines
12 KiB
C
/*
|
|
* gui_canvas.c - GUI, canvas
|
|
*
|
|
* Written 2009, 2010, 2012 by Werner Almesberger
|
|
* Copyright 2009, 2010, 2012 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_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 '/':
|
|
{
|
|
/* @@@ find a better place for this */
|
|
extern int show_vars;
|
|
show_vars = !show_vars;
|
|
change_world();
|
|
}
|
|
}
|
|
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;
|
|
}
|