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 */