From 547e7059ae2fd8279f05f296fcc2c9e4592fc261 Mon Sep 17 00:00:00 2001 From: Werner Almesberger Date: Thu, 18 Aug 2016 01:45:24 -0300 Subject: [PATCH] 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. --- eeshow/Makefile | 2 +- eeshow/README | 7 +- eeshow/gui/gui.c | 237 ++++++++++++------------------------- eeshow/gui/input.c | 288 +++++++++++++++++++++++++++++++++++++++++++++ eeshow/gui/input.h | 52 ++++++++ 5 files changed, 422 insertions(+), 164 deletions(-) create mode 100644 eeshow/gui/input.c create mode 100644 eeshow/gui/input.h diff --git a/eeshow/Makefile b/eeshow/Makefile index ce7b7ed..dcba627 100644 --- a/eeshow/Makefile +++ b/eeshow/Makefile @@ -14,7 +14,7 @@ NAME = eeshow OBJS = main.o \ kicad/sch-parse.o kicad/sch-render.o kicad/lib-parse.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 \ gfx/style.o gfx/fig.o gfx/record.o gfx/cro.o gfx/diff.o gfx/gfx.o \ gfx/text.o gfx/misc.o \ diff --git a/eeshow/README b/eeshow/README index e9bb49d..e916333 100644 --- a/eeshow/README +++ b/eeshow/README @@ -153,12 +153,13 @@ eeshow `sed -n '/pcbnew/q;/^LibName[0-9]*=/{s///;s/$/.lib/p;};d' anelok.pro` \ Mouse function in GUI mode -------------------------- +Eeshow uses only the left mouse button and the scroll wheel. + Hover show additional details on revisions, sheets, global 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 -Left-click and drag scroll history -Middle-click and drag pan sheet +Click and drag pan sheet, scroll history Scroll wheel zoom in or out diff --git a/eeshow/gui/gui.c b/eeshow/gui/gui.c index ccbd7ba..4ba0e0c 100644 --- a/eeshow/gui/gui.c +++ b/eeshow/gui/gui.c @@ -43,6 +43,7 @@ #include "gui/aoi.h" #include "gui/style.h" #include "gui/over.h" +#include "gui/input.h" #include "gui/gui.h" @@ -84,15 +85,9 @@ struct gui_hist { struct gui_ctx { GtkWidget *da; - int curr_x; /* last on-screen mouse position */ - int curr_y; - unsigned zoom; /* scale by 1.0 / (1 << zoom) */ int x, y; /* center, in eeschema coordinates */ - bool panning; - int pan_x, pan_y; - struct gui_hist *hist; /* revision history; 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 -------------------------------------------------------------- */ @@ -813,114 +774,89 @@ static bool go_next_sheet(struct gui_ctx *ctx) } -/* ----- Event handlers ---------------------------------------------------- */ +/* ----- Input: sheet ------------------------------------------------------ */ -static bool botton_1_down = 0; - - -static gboolean motion_notify_event(GtkWidget *widget, GdkEventMotion *event, - gpointer data) +static bool sheet_click(void *user, int x, int y) { - struct gui_ctx *ctx = data; + struct gui_ctx *ctx = user; const struct gui_sheet *curr_sheet = ctx->curr_sheet; - int x, y; + int ex, ey; - ctx->curr_x = event->x; - ctx->curr_y = event->y; + canvas_coord(ctx, x, y, &ex, &ey); - canvas_coord(ctx, event->x, event->y, &x, &y); + if (aoi_down(ctx->aois, x, y)) + return aoi_up(ctx->aois, x, y); + if (aoi_down(curr_sheet->aois, + ex + curr_sheet->xmin, ey + curr_sheet->ymin)) + return aoi_up(curr_sheet->aois, + ex + curr_sheet->xmin, ey + curr_sheet->ymin); - 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; + if (ctx->showing_history) + hide_history(ctx); + overlay_remove_all(&ctx->pop_overlays); + redraw(ctx); + return 1; } -static gboolean button_press_event(GtkWidget *widget, GdkEventButton *event, - gpointer data) +static bool sheet_hover_update(void *user, int x, int y) { - struct gui_ctx *ctx = data; + struct gui_ctx *ctx = user; 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) { - 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, - x + curr_sheet->xmin, y + curr_sheet->ymin)) - break; - if (ctx->showing_history) - hide_history(ctx); - overlay_remove_all(&ctx->pop_overlays); - redraw(ctx); - break; - case 2: - pan_begin(ctx, event->x, event->y); - break; - case 3: - break; - } - return TRUE; + if (aoi_move(ctx->aois, x, y)) + return 1; + if (aoi_move(curr_sheet->aois, + ex + curr_sheet->xmin, ey + curr_sheet->ymin)) + return 1; + if (aoi_hover(ctx->aois, x, y)) + return 1; + return aoi_hover(curr_sheet->aois, + ex + curr_sheet->xmin, ey + curr_sheet->ymin); } -static gboolean button_release_event(GtkWidget *widget, GdkEventButton *event, - gpointer data) +static void sheet_hover_end(void *user) { - 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: - botton_1_down = 0; - - if (aoi_up(ctx->aois, event->x, event->y)) - break; - if (aoi_up(curr_sheet->aois, - x + curr_sheet->xmin, y + curr_sheet->ymin)) - break; - 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, - gpointer data) +static void sheet_drag_move(void *user, int dx, int dy) { - struct gui_ctx *ctx = data; + 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; - 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 '=': zoom_in(ctx, x, y); @@ -958,29 +894,23 @@ static gboolean key_press_event(GtkWidget *widget, GdkEventKey *event, case GDK_KEY_q: gtk_main_quit(); } - return TRUE; } -static gboolean scroll_event(GtkWidget *widget, GdkEventScroll *event, - gpointer data) -{ - struct gui_ctx *ctx = data; - int x, y; +static const struct input_ops input_sheet_ops = { + .click = sheet_click, + .hover_begin = sheet_hover_update, + .hover_update = sheet_hover_update, + .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) { - case GDK_SCROLL_UP: - zoom_in(ctx, x, y); - break; - case GDK_SCROLL_DOWN: - zoom_out(ctx, x, y); - break; - default: - /* ignore */; - } - return TRUE; -} + +/* ----- Event handlers ---------------------------------------------------- */ 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; struct gui_ctx ctx = { .zoom = 4, /* scale by 1 / 16 */ - .panning = 0, .hist = NULL, .vcs_hist = NULL, .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_title(GTK_WINDOW(window), "eeshow"); - gtk_widget_set_can_focus(ctx.da, TRUE); - gtk_widget_set_events(ctx.da, - GDK_EXPOSE | GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK | - GDK_KEY_PRESS_MASK | - GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | - GDK_SCROLL_MASK | - GDK_POINTER_MOTION_MASK); + GDK_EXPOSE | GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK); + + input_setup(ctx.da); 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_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_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); gtk_widget_show_all(window); + input_push(&input_sheet_ops, &ctx); + /* for performance testing, use -N-depth */ if (limit >= 0) gtk_main(); diff --git a/eeshow/gui/input.c b/eeshow/gui/input.c new file mode 100644 index 0000000..3b430c3 --- /dev/null +++ b/eeshow/gui/input.c @@ -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 +#include +#include +#include + +#include + +#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); +} diff --git a/eeshow/gui/input.h b/eeshow/gui/input.h new file mode 100644 index 0000000..7e32df6 --- /dev/null +++ b/eeshow/gui/input.h @@ -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 + +#include + + +/* + * 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 */