summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile2
-rw-r--r--README7
-rw-r--r--gui/gui.c229
-rw-r--r--gui/input.c288
-rw-r--r--gui/input.h52
5 files changed, 418 insertions, 160 deletions
diff --git a/Makefile b/Makefile
index ce7b7ed..dcba627 100644
--- a/Makefile
+++ b/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/README b/README
index e9bb49d..e916333 100644
--- a/README
+++ b/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/gui/gui.c b/gui/gui.c
index ccbd7ba..4ba0e0c 100644
--- a/gui/gui.c
+++ b/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;
-
- ctx->curr_x = event->x;
- ctx->curr_y = event->y;
+ int ex, ey;
- canvas_coord(ctx, event->x, event->y, &x, &y);
+ canvas_coord(ctx, x, y, &ex, &ey);
- 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);
+ 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);
- 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_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);
+}
- 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;
+
+static void sheet_hover_end(void *user)
+{
}
-static gboolean button_release_event(GtkWidget *widget, GdkEventButton *event,
- gpointer data)
+static void sheet_drag_move(void *user, int dx, int dy)
{
- struct gui_ctx *ctx = data;
- const struct gui_sheet *curr_sheet = ctx->curr_sheet;
- int x, y;
+ struct gui_ctx *ctx = user;
- canvas_coord(ctx, event->x, event->y, &x, &y);
+ ctx->x -= dx << ctx->zoom;
+ ctx->y -= dy << ctx->zoom;
+ redraw(ctx);
+}
- 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 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 gboolean key_press_event(GtkWidget *widget, GdkEventKey *event,
- gpointer data)
+static void sheet_key(void *user, int x, int y, int keyval)
{
- struct gui_ctx *ctx = data;
+ 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/gui/input.c b/gui/input.c
new file mode 100644
index 0000000..3b430c3
--- /dev/null
+++ b/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 <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);
+}
diff --git a/gui/input.h b/gui/input.h
new file mode 100644
index 0000000..7e32df6
--- /dev/null
+++ b/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 <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 */