1
0
mirror of git://projects.qi-hardware.com/eda-tools.git synced 2024-11-17 21:46:15 +02:00

eeshow/gui/gui.c: begin moving input (mouse, keyboard) handling to input.c (WIP)

We had input state scattered all over the place. This cleans things up.
We also merge the functions of left and (previously) middle button.

This breaks history panning.
This commit is contained in:
Werner Almesberger 2016-08-18 01:45:24 -03:00
parent c4811c8dd6
commit 547e7059ae
5 changed files with 422 additions and 164 deletions

View File

@ -14,7 +14,7 @@ NAME = eeshow
OBJS = main.o \ OBJS = main.o \
kicad/sch-parse.o kicad/sch-render.o kicad/lib-parse.o \ kicad/sch-parse.o kicad/sch-render.o kicad/lib-parse.o \
kicad/lib-render.o kicad/dwg.o kicad/delta.o \ kicad/lib-render.o kicad/dwg.o kicad/delta.o \
gui/gui.o gui/over.o gui/style.o gui/aoi.o gui/fmt-pango.o \ gui/gui.o gui/over.o gui/style.o gui/aoi.o gui/fmt-pango.o gui/input.o \
file/file.o file/git-util.o file/git-file.o file/git-hist.o \ file/file.o file/git-util.o file/git-file.o file/git-hist.o \
gfx/style.o gfx/fig.o gfx/record.o gfx/cro.o gfx/diff.o gfx/gfx.o \ gfx/style.o gfx/fig.o gfx/record.o gfx/cro.o gfx/diff.o gfx/gfx.o \
gfx/text.o gfx/misc.o \ gfx/text.o gfx/misc.o \

View File

@ -153,12 +153,13 @@ eeshow `sed -n '/pcbnew/q;/^LibName[0-9]*=/{s///;s/$/.lib/p;};d' anelok.pro` \
Mouse function in GUI mode Mouse function in GUI mode
-------------------------- --------------------------
Eeshow uses only the left mouse button and the scroll wheel.
Hover show additional details on revisions, sheets, global Hover show additional details on revisions, sheets, global
labels labels
Left-click jump to sheet (on sub-sheet, sheet stack, global Click jump to sheet (on sub-sheet, sheet stack, global
label hover box), open history, select revision label hover box), open history, select revision
Left-click and drag scroll history Click and drag pan sheet, scroll history
Middle-click and drag pan sheet
Scroll wheel zoom in or out Scroll wheel zoom in or out

View File

@ -43,6 +43,7 @@
#include "gui/aoi.h" #include "gui/aoi.h"
#include "gui/style.h" #include "gui/style.h"
#include "gui/over.h" #include "gui/over.h"
#include "gui/input.h"
#include "gui/gui.h" #include "gui/gui.h"
@ -84,15 +85,9 @@ struct gui_hist {
struct gui_ctx { struct gui_ctx {
GtkWidget *da; GtkWidget *da;
int curr_x; /* last on-screen mouse position */
int curr_y;
unsigned zoom; /* scale by 1.0 / (1 << zoom) */ unsigned zoom; /* scale by 1.0 / (1 << zoom) */
int x, y; /* center, in eeschema coordinates */ int x, y; /* center, in eeschema coordinates */
bool panning;
int pan_x, pan_y;
struct gui_hist *hist; /* revision history; NULL if none */ struct gui_hist *hist; /* revision history; NULL if none */
struct hist *vcs_hist; /* underlying VCS data; NULL if none */ struct hist *vcs_hist; /* underlying VCS data; NULL if none */
@ -307,40 +302,6 @@ static void eeschema_coord(const struct gui_ctx *ctx,
} }
/* ----- Panning ----------------------------------------------------------- */
static void pan_begin(struct gui_ctx *ctx, int x, int y)
{
if (ctx->panning)
return;
ctx->panning = 1;
ctx->pan_x = x;
ctx->pan_y = y;
}
static void pan_update(struct gui_ctx *ctx, int x, int y)
{
if (!ctx->panning)
return;
ctx->x -= (x - ctx->pan_x) << ctx->zoom;
ctx->y -= (y - ctx->pan_y) << ctx->zoom;
ctx->pan_x = x;
ctx->pan_y = y;
redraw(ctx);
}
static void pan_end(struct gui_ctx *ctx, int x, int y)
{
pan_update(ctx, x, y);
ctx->panning = 0;
}
/* ----- Zoom -------------------------------------------------------------- */ /* ----- Zoom -------------------------------------------------------------- */
@ -813,114 +774,89 @@ static bool go_next_sheet(struct gui_ctx *ctx)
} }
/* ----- Event handlers ---------------------------------------------------- */ /* ----- Input: sheet ------------------------------------------------------ */
static bool botton_1_down = 0; static bool sheet_click(void *user, int x, int y)
static gboolean motion_notify_event(GtkWidget *widget, GdkEventMotion *event,
gpointer data)
{ {
struct gui_ctx *ctx = data; struct gui_ctx *ctx = user;
const struct gui_sheet *curr_sheet = ctx->curr_sheet; const struct gui_sheet *curr_sheet = ctx->curr_sheet;
int x, y; int ex, ey;
ctx->curr_x = event->x; canvas_coord(ctx, x, y, &ex, &ey);
ctx->curr_y = event->y;
canvas_coord(ctx, event->x, event->y, &x, &y); if (aoi_down(ctx->aois, x, y))
return aoi_up(ctx->aois, x, y);
aoi_move(ctx->aois, event->x, event->y) ||
aoi_move(curr_sheet->aois,
x + curr_sheet->xmin, y + curr_sheet->ymin) ||
aoi_hover(ctx->aois, event->x, event->y) ||
aoi_hover(curr_sheet->aois,
x + curr_sheet->xmin, y + curr_sheet->ymin);
pan_update(ctx, event->x, event->y);
return TRUE;
}
static gboolean button_press_event(GtkWidget *widget, GdkEventButton *event,
gpointer data)
{
struct gui_ctx *ctx = data;
const struct gui_sheet *curr_sheet = ctx->curr_sheet;
int x, y;
canvas_coord(ctx, event->x, event->y, &x, &y);
switch (event->button) {
case 1:
/*
* Double-click is sent as down-down-up, confusing the AoI
* logic that assumes each "down" to have a matching "up".
*/
if (botton_1_down)
return TRUE;
botton_1_down = 1;
if (aoi_down(ctx->aois, event->x, event->y))
break;
if (aoi_down(curr_sheet->aois, if (aoi_down(curr_sheet->aois,
x + curr_sheet->xmin, y + curr_sheet->ymin)) ex + curr_sheet->xmin, ey + curr_sheet->ymin))
break; return aoi_up(curr_sheet->aois,
ex + curr_sheet->xmin, ey + curr_sheet->ymin);
if (ctx->showing_history) if (ctx->showing_history)
hide_history(ctx); hide_history(ctx);
overlay_remove_all(&ctx->pop_overlays); overlay_remove_all(&ctx->pop_overlays);
redraw(ctx); redraw(ctx);
break; return 1;
case 2:
pan_begin(ctx, event->x, event->y);
break;
case 3:
break;
}
return TRUE;
} }
static gboolean button_release_event(GtkWidget *widget, GdkEventButton *event, static bool sheet_hover_update(void *user, int x, int y)
gpointer data)
{ {
struct gui_ctx *ctx = data; struct gui_ctx *ctx = user;
const struct gui_sheet *curr_sheet = ctx->curr_sheet; const struct gui_sheet *curr_sheet = ctx->curr_sheet;
int x, y; int ex, ey;
canvas_coord(ctx, event->x, event->y, &x, &y); canvas_coord(ctx, x, y, &ex, &ey);
switch (event->button) { if (aoi_move(ctx->aois, x, y))
case 1: return 1;
botton_1_down = 0; if (aoi_move(curr_sheet->aois,
ex + curr_sheet->xmin, ey + curr_sheet->ymin))
if (aoi_up(ctx->aois, event->x, event->y)) return 1;
break; if (aoi_hover(ctx->aois, x, y))
if (aoi_up(curr_sheet->aois, return 1;
x + curr_sheet->xmin, y + curr_sheet->ymin)) return aoi_hover(curr_sheet->aois,
break; ex + curr_sheet->xmin, ey + curr_sheet->ymin);
break;
case 2:
pan_end(ctx, event->x, event->y);
break;
case 3:
break;
}
return TRUE;
} }
static gboolean key_press_event(GtkWidget *widget, GdkEventKey *event, static void sheet_hover_end(void *user)
gpointer data)
{ {
struct gui_ctx *ctx = data; }
static void sheet_drag_move(void *user, int dx, int dy)
{
struct gui_ctx *ctx = user;
ctx->x -= dx << ctx->zoom;
ctx->y -= dy << ctx->zoom;
redraw(ctx);
}
static void sheet_scroll(void *user, int x, int y, int dy)
{
struct gui_ctx *ctx = user;
int ex, ey;
canvas_coord(ctx, x, y, &ex, &ey);
if (dy < 0)
zoom_in(ctx, ex, ey);
else
zoom_out(ctx, ex, ey);
}
static void sheet_key(void *user, int x, int y, int keyval)
{
struct gui_ctx *ctx = user;
struct gui_sheet *sheet = ctx->curr_sheet; struct gui_sheet *sheet = ctx->curr_sheet;
int x, y; int ex, ey;
canvas_coord(ctx, ctx->curr_x, ctx->curr_y, &x, &y); canvas_coord(ctx, x, y, &ex, &ey);
switch (event->keyval) { switch (keyval) {
case '+': case '+':
case '=': case '=':
zoom_in(ctx, x, y); zoom_in(ctx, x, y);
@ -958,29 +894,23 @@ static gboolean key_press_event(GtkWidget *widget, GdkEventKey *event,
case GDK_KEY_q: case GDK_KEY_q:
gtk_main_quit(); gtk_main_quit();
} }
return TRUE;
} }
static gboolean scroll_event(GtkWidget *widget, GdkEventScroll *event, static const struct input_ops input_sheet_ops = {
gpointer data) .click = sheet_click,
{ .hover_begin = sheet_hover_update,
struct gui_ctx *ctx = data; .hover_update = sheet_hover_update,
int x, y; .hover_click = sheet_click,
.hover_end = sheet_hover_end,
.scroll = sheet_scroll,
.drag_begin = input_accept,
.drag_move = sheet_drag_move,
.key = sheet_key,
};
canvas_coord(ctx, event->x, event->y, &x, &y);
switch (event->direction) { /* ----- Event handlers ---------------------------------------------------- */
case GDK_SCROLL_UP:
zoom_in(ctx, x, y);
break;
case GDK_SCROLL_DOWN:
zoom_out(ctx, x, y);
break;
default:
/* ignore */;
}
return TRUE;
}
static void size_allocate_event(GtkWidget *widget, GdkRectangle *allocation, static void size_allocate_event(GtkWidget *widget, GdkRectangle *allocation,
@ -1472,7 +1402,6 @@ int gui(unsigned n_args, char **args, bool recurse, int limit)
GtkWidget *window; GtkWidget *window;
struct gui_ctx ctx = { struct gui_ctx ctx = {
.zoom = 4, /* scale by 1 / 16 */ .zoom = 4, /* scale by 1 / 16 */
.panning = 0,
.hist = NULL, .hist = NULL,
.vcs_hist = NULL, .vcs_hist = NULL,
.showing_history= 0, .showing_history= 0,
@ -1492,14 +1421,10 @@ int gui(unsigned n_args, char **args, bool recurse, int limit)
gtk_window_set_default_size(GTK_WINDOW(window), 640, 480); gtk_window_set_default_size(GTK_WINDOW(window), 640, 480);
gtk_window_set_title(GTK_WINDOW(window), "eeshow"); gtk_window_set_title(GTK_WINDOW(window), "eeshow");
gtk_widget_set_can_focus(ctx.da, TRUE);
gtk_widget_set_events(ctx.da, gtk_widget_set_events(ctx.da,
GDK_EXPOSE | GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK | GDK_EXPOSE | GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK);
GDK_KEY_PRESS_MASK |
GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | input_setup(ctx.da);
GDK_SCROLL_MASK |
GDK_POINTER_MOTION_MASK);
gtk_widget_show_all(window); gtk_widget_show_all(window);
@ -1515,16 +1440,6 @@ int gui(unsigned n_args, char **args, bool recurse, int limit)
g_signal_connect(G_OBJECT(ctx.da), "draw", g_signal_connect(G_OBJECT(ctx.da), "draw",
G_CALLBACK(on_draw_event), &ctx); G_CALLBACK(on_draw_event), &ctx);
g_signal_connect(G_OBJECT(ctx.da), "motion_notify_event",
G_CALLBACK(motion_notify_event), &ctx);
g_signal_connect(G_OBJECT(ctx.da), "button_press_event",
G_CALLBACK(button_press_event), &ctx);
g_signal_connect(G_OBJECT(ctx.da), "button_release_event",
G_CALLBACK(button_release_event), &ctx);
g_signal_connect(G_OBJECT(ctx.da), "scroll_event",
G_CALLBACK(scroll_event), &ctx);
g_signal_connect(G_OBJECT(ctx.da), "key_press_event",
G_CALLBACK(key_press_event), &ctx);
g_signal_connect(G_OBJECT(ctx.da), "size_allocate", g_signal_connect(G_OBJECT(ctx.da), "size_allocate",
G_CALLBACK(size_allocate_event), &ctx); G_CALLBACK(size_allocate_event), &ctx);
@ -1536,6 +1451,8 @@ int gui(unsigned n_args, char **args, bool recurse, int limit)
go_to_sheet(&ctx, ctx.new_hist->sheets); go_to_sheet(&ctx, ctx.new_hist->sheets);
gtk_widget_show_all(window); gtk_widget_show_all(window);
input_push(&input_sheet_ops, &ctx);
/* for performance testing, use -N-depth */ /* for performance testing, use -N-depth */
if (limit >= 0) if (limit >= 0)
gtk_main(); gtk_main();

288
eeshow/gui/input.c Normal file
View File

@ -0,0 +1,288 @@
/*
* gui/input.c - Input operations
*
* Written 2016 by Werner Almesberger
* Copyright 2016 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 <stddef.h>
#include <stdbool.h>
#include <math.h>
#include <assert.h>
#include <gtk/gtk.h>
#include "misc/util.h"
#include "gui/input.h"
#define DRAG_RADIUS 5
static struct input {
const struct input_ops *ops;
void *user;
enum state {
input_normal,
input_clicking,
input_ignoring, /* click rejected by moving the cursor */
input_hovering,
input_dragging,
} state;
struct input *next;
} *sp = NULL;
static int curr_x, curr_y; /* last mouse position */
static int clicked_x, clicked_y; /* button down position */
/* ----- Mouse button ------------------------------------------------------ */
static gboolean motion_notify_event(GtkWidget *widget, GdkEventMotion *event,
gpointer data)
{
const struct input *old_sp = sp;
curr_x = event->x;
curr_y = event->y;
if (!sp)
return TRUE;
switch (sp->state) {
case input_normal:
if (sp->ops->hover_begin &&
sp->ops->hover_begin(sp->user, event->x, event->y))
sp->state = input_hovering;
assert(sp == old_sp);
break;
case input_clicking:
if (hypot(event->x - clicked_x, event->y - clicked_y) <
DRAG_RADIUS)
break;
if (sp->ops->drag_begin &&
sp->ops->drag_begin(sp->user, clicked_x, clicked_y))
sp->state = input_dragging;
else
sp->state = input_ignoring;
assert(sp == old_sp);
break;
case input_ignoring:
break;
case input_hovering:
if (!sp->ops->hover_update)
break;
/* Caution: hover_update may switch input layers */
if (sp->ops->hover_update(sp->user, event->x, event->y) &&
sp == old_sp) {
sp->state = input_normal;
if (sp->ops->hover_end)
sp->ops->hover_end(sp->user);
}
break;
case input_dragging:
if (sp->ops->drag_move)
sp->ops->drag_move(sp->user,
event->x - clicked_x, event->y - clicked_y);
clicked_x = event->x;
clicked_y = event->y;
break;
default:
abort();
}
return TRUE;
}
static gboolean button_press_event(GtkWidget *widget, GdkEventButton *event,
gpointer data)
{
const struct input *old_sp = sp;
if (event->button != 1)
return TRUE;
switch (sp->state) {
case input_normal:
sp->state = input_clicking;
clicked_x = event->x;
clicked_y = event->y;
break;
case input_clicking:
case input_ignoring:
case input_dragging:
/* ignore double-click */
break;
case input_hovering:
if (sp->ops->hover_click &&
sp->ops->hover_click(sp->user, event->x, event->y) &&
sp == old_sp) {
sp->state = input_ignoring;
if (sp->ops->hover_end)
sp->ops->hover_end(sp->user);
}
break;
default:
abort();
}
return TRUE;
}
static gboolean button_release_event(GtkWidget *widget, GdkEventButton *event,
gpointer data)
{
if (event->button != 1)
return TRUE;
switch (sp->state) {
case input_normal:
abort();
case input_clicking:
sp->state = input_normal;
if (sp->ops->click)
sp->ops->click(sp->user, clicked_x, clicked_y);
break;
case input_ignoring:
sp->state = input_normal;
break;
case input_dragging:
sp->state = input_normal;
if (sp->ops->drag_end)
sp->ops->drag_end(sp->user);
break;
case input_hovering:
break;
default:
abort();
}
return TRUE;
}
/* ----- Scroll wheel ------------------------------------------------------ */
static gboolean scroll_event(GtkWidget *widget, GdkEventScroll *event,
gpointer data)
{
if (!sp || !sp->ops->scroll)
return TRUE;
switch (event->direction) {
case GDK_SCROLL_UP:
sp->ops->scroll(sp->user, event->x, event->y, -1);
break;
case GDK_SCROLL_DOWN:
sp->ops->scroll(sp->user, event->x, event->y, 1);
break;
default:
/* ignore */;
}
return TRUE;
}
/* ----- Keys -------------------------------------------------------------- */
static gboolean key_press_event(GtkWidget *widget, GdkEventKey *event,
gpointer data)
{
if (sp && sp->ops->key)
sp->ops->key(sp->user, curr_x, curr_y, event->keyval);
return TRUE;
}
/* ----- Covenience function for hover_begin and drag_begin ---------------- */
bool input_accept(void *user, int x, int y)
{
return 1;
}
/* ----- Adding/removing interaction layers -------------------------------- */
static void cleanup(void)
{
if (!sp)
return;
switch (sp->state) {
case input_hovering:
if (sp->ops->hover_end)
sp->ops->hover_end(sp->user);
break;
case input_dragging:
if (sp->ops->drag_end)
sp->ops->drag_end(sp->user);
break;
default:
;
}
}
void input_push(const struct input_ops *ops, void *user)
{
struct input *new;
cleanup();
new = alloc_type(struct input);
new->ops = ops;
new->user = user;
new->state = input_normal;
new->next = sp;
sp = new;
}
void input_pop(void)
{
struct input *next = sp->next;
cleanup();
free(sp);
sp = next;
}
/* ----- Initialization ---------------------------------------------------- */
void input_setup(GtkWidget *da)
{
gtk_widget_set_can_focus(da, TRUE);
gtk_widget_add_events(da,
GDK_KEY_PRESS_MASK |
GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
GDK_SCROLL_MASK |
GDK_POINTER_MOTION_MASK);
g_signal_connect(G_OBJECT(da), "key_press_event",
G_CALLBACK(key_press_event), NULL);
g_signal_connect(G_OBJECT(da), "motion_notify_event",
G_CALLBACK(motion_notify_event), NULL);
g_signal_connect(G_OBJECT(da), "button_press_event",
G_CALLBACK(button_press_event), NULL);
g_signal_connect(G_OBJECT(da), "button_release_event",
G_CALLBACK(button_release_event), NULL);
g_signal_connect(G_OBJECT(da), "scroll_event",
G_CALLBACK(scroll_event), NULL);
}

52
eeshow/gui/input.h Normal file
View File

@ -0,0 +1,52 @@
/*
* gui/input.h - Input operations
*
* Written 2016 by Werner Almesberger
* Copyright 2016 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.
*/
#ifndef GUI_INPUT_H
#define GUI_NPUT_H
#include <stdbool.h>
#include <gtk/gtk.h>
/*
* All members of input_ops are optional, i.e., can be NULL.
*
* hover_begin and drag_begin must not call input_push or input_pop.
*/
struct input_ops {
bool (*click)(void *user, int x, int y);
bool (*hover_begin)(void *user, int x, int y);
bool (*hover_update)(void *user, int x, int y);
bool (*hover_click)(void *user, int x, int y);
void (*hover_end)(void *user);
bool (*drag_begin)(void *user, int x, int y);
void (*drag_move)(void *user, int dx, int dy);
void (*drag_end)(void *user);
void (*scroll)(void *user, int x, int y, int dy);
/* down = 1, up = -1 */
void (*key)(void *user, int x, int y, int keyval);
};
bool input_accept(void *user, int x, int y);
void input_push(const struct input_ops *ops, void *user);
void input_pop(void);
void input_setup(GtkWidget *da);
#endif /* !GUI_INPUT_H */