summaryrefslogtreecommitdiff
path: root/gui
diff options
context:
space:
mode:
authorWerner Almesberger <werner@almesberger.net>2016-08-17 20:54:25 -0300
committerWerner Almesberger <werner@almesberger.net>2016-08-17 20:54:25 -0300
commit5014b5de97b9c3fa1600fd8c4d3460520c4ba081 (patch)
treecafd98b7d86d6619c6b9cf13bf42945fca391f57 /gui
parent34ea81f8d4018fc3ecf91663d741ae5afcbc26d7 (diff)
downloadeeshow-5014b5de97b9c3fa1600fd8c4d3460520c4ba081.tar.gz
eeshow-5014b5de97b9c3fa1600fd8c4d3460520c4ba081.tar.bz2
eeshow-5014b5de97b9c3fa1600fd8c4d3460520c4ba081.zip
eeshow/: move gui* into subdirectory gui/
Diffstat (limited to 'gui')
-rw-r--r--gui/aoi.c207
-rw-r--r--gui/aoi.h43
-rw-r--r--gui/gui.c1542
-rw-r--r--gui/gui.h25
-rw-r--r--gui/over.c305
-rw-r--r--gui/over.h58
-rw-r--r--gui/style.c78
-rw-r--r--gui/style.h35
8 files changed, 2293 insertions, 0 deletions
diff --git a/gui/aoi.c b/gui/aoi.c
new file mode 100644
index 0000000..2e0e6ee
--- /dev/null
+++ b/gui/aoi.c
@@ -0,0 +1,207 @@
+/*
+ * gui/aoi.c - GUI: areas of interest
+ *
+ * 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.
+ */
+
+/*
+ * Resources:
+ *
+ * http://zetcode.com/gfx/cairo/cairobackends/
+ * https://developer.gnome.org/gtk3/stable/gtk-migrating-2-to-3.html
+ */
+
+#include <stddef.h>
+#include <math.h>
+#include <assert.h>
+
+#include "util.h"
+#include "gui/aoi.h"
+
+
+#define DRAG_RADIUS 5
+
+
+static const struct aoi *hovering = NULL;
+static const struct aoi *clicked = NULL;
+static const struct aoi *dragging = NULL;
+static int clicked_x, clicked_y;
+
+
+struct aoi *aoi_add(struct aoi **aois, const struct aoi *cfg)
+{
+ struct aoi *new;
+
+ new = alloc_type(struct aoi);
+ *new = *cfg;
+ new->next = *aois;
+ *aois = new;
+
+ return new;
+}
+
+
+void aoi_update(struct aoi *aoi, const struct aoi *cfg)
+{
+ struct aoi *next = aoi->next;
+
+ *aoi = *cfg;
+ aoi->next = next;
+}
+
+
+static const struct aoi *find_aoi(const struct aoi *aois, int x, int y)
+{
+ const struct aoi *aoi;
+
+ for (aoi = aois; aoi; aoi = aoi->next)
+ if (x >= aoi->x && x < aoi->x + aoi->w &&
+ y >= aoi->y && y < aoi->y + aoi->h)
+ break;
+ return aoi;
+}
+
+
+static bool aoi_on_list(const struct aoi *aois, const struct aoi *ref)
+{
+ const struct aoi *aoi;
+
+ for (aoi = aois; aoi; aoi = aoi->next)
+ if (aoi == ref)
+ return 1;
+ return 0;
+}
+
+
+bool aoi_hover(const struct aoi *aois, int x, int y)
+{
+ const struct aoi *aoi;
+
+ if (dragging)
+ return 0;
+ if (hovering) {
+ if (x >= hovering->x && x < hovering->x + hovering->w &&
+ y >= hovering->y && y < hovering->y + hovering->h)
+ return 1;
+ hovering->hover(hovering->user, 0);
+ hovering = NULL;
+ }
+
+ aoi = find_aoi(aois, x, y);
+ if (aoi && aoi->hover && aoi->hover(aoi->user, 1)) {
+ hovering = aoi;
+ return 1;
+ }
+ return 0;
+}
+
+
+bool aoi_move(const struct aoi *aois, int x, int y)
+{
+ if (dragging) {
+ if (aoi_on_list(aois, dragging)) {
+ dragging->drag(dragging->user,
+ x - clicked_x, y - clicked_y);
+ clicked_x = x;
+ clicked_y = y;
+ }
+ return 1;
+ }
+ if (!clicked)
+ return 0;
+
+ /*
+ * Ensure we're on the right list and are using the same coordinate
+ * system.
+ */
+ if (!aoi_on_list(aois, clicked))
+ return 0;
+
+ if (hypot(x - clicked_x, y - clicked_y) > DRAG_RADIUS) {
+ if (clicked && clicked->drag) {
+ dragging = clicked;
+ dragging->drag(dragging->user,
+ x - clicked_x, y - clicked_y);
+ clicked_x = x;
+ clicked_y = y;
+ }
+ clicked = NULL;
+ }
+ return 1;
+}
+
+
+bool aoi_down(const struct aoi *aois, int x, int y)
+{
+ assert(!clicked);
+
+ aoi_dehover();
+
+ clicked = find_aoi(aois, x, y);
+ if (!clicked)
+ return 0;
+ if (!clicked->click) {
+ clicked = NULL;
+ return 0;
+ }
+
+ clicked_x = x;
+ clicked_y = y;
+
+ return 1;
+}
+
+
+bool aoi_up(const struct aoi *aois, int x, int y)
+{
+ const struct aoi *aoi;
+
+ if (!aoi_move(aois, x, y))
+ return 0;
+
+ /*
+ * Ensure we're on the right list and are using the same coordinate
+ * system.
+ */
+ for (aoi = aois; aoi; aoi = aoi->next)
+ if (aoi == clicked || aoi == dragging)
+ break;
+ if (!aoi)
+ return 0;
+ if (dragging) {
+ dragging = NULL;
+ return 1;
+ }
+
+ clicked->click(clicked->user);
+ clicked = NULL;
+ return 1;
+}
+
+
+void aoi_remove(struct aoi **aois, const struct aoi *aoi)
+{
+ if (hovering == aoi) {
+ aoi->hover(aoi->user, 0);
+ hovering = NULL;
+ }
+ while (*aois != aoi)
+ aois = &(*aois)->next;
+ *aois = aoi->next;
+ free((void *) aoi);
+}
+
+
+void aoi_dehover(void)
+{
+ if (hovering)
+ hovering->hover(hovering->user, 0);
+ hovering = NULL;
+}
+
diff --git a/gui/aoi.h b/gui/aoi.h
new file mode 100644
index 0000000..b804b1b
--- /dev/null
+++ b/gui/aoi.h
@@ -0,0 +1,43 @@
+/*
+ * gui/aoi.h - GUI: areas of interest
+ *
+ * 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_AOI_H
+#define GUI_AOI_H
+
+#include <stdbool.h>
+
+
+struct aoi {
+ int x, y, w, h; /* activation box, eeschema coordinates */
+ /* points to hovered aoi, or NULL */
+
+ bool (*hover)(void *user, bool on);
+ void (*click)(void *user);
+ void (*drag)(void *user, int dx, int dy);
+ void *user;
+
+ struct aoi *next;
+};
+
+
+struct aoi *aoi_add(struct aoi **aois, const struct aoi *cfg);
+void aoi_update(struct aoi *aoi, const struct aoi *cfg);
+bool aoi_hover(const struct aoi *aois, int x, int y);
+
+bool aoi_move(const struct aoi *aois, int x, int y);
+bool aoi_down(const struct aoi *aois, int x, int y);
+bool aoi_up(const struct aoi *aois, int x, int y);
+
+void aoi_remove(struct aoi **aois, const struct aoi *aoi);
+void aoi_dehover(void);
+
+#endif /* !GUI_AOI_H */
diff --git a/gui/gui.c b/gui/gui.c
new file mode 100644
index 0000000..5ed3e82
--- /dev/null
+++ b/gui/gui.c
@@ -0,0 +1,1542 @@
+/*
+ * gui/gui.c - GUI for eeshow
+ *
+ * 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.
+ */
+
+/*
+ * Resources:
+ *
+ * http://zetcode.com/gfx/cairo/cairobackends/
+ * https://developer.gnome.org/gtk3/stable/gtk-migrating-2-to-3.html
+ */
+
+#define _GNU_SOURCE /* for asprintf */
+#include <stddef.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <math.h>
+
+#include <cairo/cairo.h>
+#include <gtk/gtk.h>
+
+#include "util.h"
+#include "diag.h"
+#include "style.h"
+#include "cro.h"
+#include "gfx.h"
+#include "git-hist.h"
+#include "sch.h"
+#include "delta.h"
+#include "diff.h"
+#include "dwg.h"
+#include "gui/aoi.h"
+#include "gui/style.h"
+#include "gui/over.h"
+#include "gui/gui.h"
+
+
+struct gui_ctx;
+
+struct gui_sheet {
+ const struct sheet *sch;
+ struct gui_ctx *ctx; /* back link */
+ struct cro_ctx *gfx_ctx;
+
+ int w, h; /* in eeschema coordinates */
+ int xmin, ymin;
+
+ bool rendered; /* 0 if still have to render it */
+
+ struct overlay *over; /* current overlay */
+ struct aoi *aois; /* areas of interest; in schematics coord */
+
+ struct gui_sheet *next;
+};
+
+struct gui_hist {
+ struct gui_ctx *ctx; /* back link */
+ struct hist *vcs_hist; /* NULL if not from repo */
+ struct overlay *over; /* current overlay */
+ struct gui_sheet *sheets; /* NULL if failed */
+ unsigned age; /* 0-based; uncommitted or HEAD = 0 */
+
+ /* caching support */
+ void **oids; /* file object IDs */
+ int libs_open;
+ struct sch_ctx sch_ctx;
+ struct lib lib; /* combined library */
+ bool identical; /* identical with previous entry */
+
+ struct gui_hist *next;
+};
+
+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 */
+
+ bool showing_history;
+ enum selecting {
+ sel_only, /* select the only revision we show */
+ sel_new, /* select the new revision */
+ sel_old, /* select the old revision */
+ } selecting;
+
+ struct overlay *sheet_overlays;
+ struct overlay *hist_overlays;
+ struct overlay *pop_overlays; /* pop-up dialogs */
+ int pop_x, pop_y;
+ struct aoi *aois; /* areas of interest; in canvas coord */
+
+ struct gui_sheet delta_a;
+ struct gui_sheet delta_b;
+ struct gui_sheet delta_ab;
+
+ struct gui_sheet *curr_sheet;
+ /* current sheet, always on new_hist */
+ struct gui_hist *new_hist;
+ struct gui_hist *old_hist; /* NULL if not comparing */
+
+ int hist_y_offset; /* history list y offset */
+
+ /* progress bar */
+ int hist_size; /* total number of revisions */
+ unsigned progress; /* progress counter */
+ unsigned progress_scale;/* right-shift by this value */
+};
+
+
+/* ----- Helper functions -------------------------------------------------- */
+
+
+static void redraw(const struct gui_ctx *ctx)
+{
+ gtk_widget_queue_draw(ctx->da);
+}
+
+
+static struct gui_sheet *find_corresponding_sheet(struct gui_sheet *pick_from,
+ struct gui_sheet *ref_in, const struct gui_sheet *ref)
+{
+ struct gui_sheet *sheet, *plan_b;
+ const char *title = ref->sch->title;
+
+ /* plan A: try to find sheet with same name */
+
+ if (title)
+ for (sheet = pick_from; sheet; sheet = sheet->next)
+ if (sheet->sch->title &&
+ !strcmp(title, sheet->sch->title))
+ return sheet;
+
+ /* plan B: use sheet in same position in sheet sequence */
+
+ plan_b = ref_in;
+ for (sheet = pick_from; sheet; sheet = sheet->next) {
+ if (plan_b == ref)
+ return sheet;
+ plan_b = plan_b->next;
+ }
+
+ /* plan C: just go to the top */
+ return pick_from;
+}
+
+
+/* ----- Rendering --------------------------------------------------------- */
+
+
+#define VCS_OVERLAYS_X 5
+#define VCS_OVERLAYS_Y 5
+
+#define SHEET_OVERLAYS_X -10
+#define SHEET_OVERLAYS_Y 10
+
+
+static void hack(const struct gui_ctx *ctx, cairo_t *cr)
+{
+ const struct gui_sheet *new = ctx->curr_sheet;
+ const struct gui_sheet *old = find_corresponding_sheet(
+ ctx->old_hist->sheets, ctx->new_hist->sheets, ctx->curr_sheet);
+
+ diff_to_canvas(cr, ctx->x, ctx->y, 1.0 / (1 << ctx->zoom),
+ old->gfx_ctx, new->gfx_ctx);
+}
+
+
+static gboolean on_draw_event(GtkWidget *widget, cairo_t *cr,
+ gpointer user_data)
+{
+ const struct gui_ctx *ctx = user_data;
+ const struct gui_sheet *sheet = ctx->curr_sheet;
+ GtkAllocation alloc;
+ float f = 1.0 / (1 << ctx->zoom);
+ int x, y;
+
+ gtk_widget_get_allocation(ctx->da, &alloc);
+ x = -(sheet->xmin + ctx->x) * f + alloc.width / 2;
+ y = -(sheet->ymin + ctx->y) * f + alloc.height / 2;
+
+ cro_canvas_prepare(cr);
+ if (!ctx->old_hist) {
+ cro_canvas_draw(sheet->gfx_ctx, cr, x, y, f);
+ } else {
+#if 0
+ /* @@@ fix geometry later */
+ cro_canvas_draw(ctx->delta_ab.gfx_ctx, cr, x, y, f);
+ cro_canvas_draw(ctx->delta_a.gfx_ctx, cr, x, y, f);
+ cro_canvas_draw(ctx->delta_b.gfx_ctx, cr, x, y, f);
+#endif
+ hack(ctx, cr);
+ }
+
+ overlay_draw_all(ctx->sheet_overlays, cr,
+ SHEET_OVERLAYS_X, SHEET_OVERLAYS_Y);
+ overlay_draw_all_d(ctx->hist_overlays, cr,
+ VCS_OVERLAYS_X,
+ VCS_OVERLAYS_Y + (ctx->showing_history ? ctx->hist_y_offset : 0),
+ 0, 1);
+ overlay_draw_all(ctx->pop_overlays, cr, ctx->pop_x, ctx->pop_y);
+
+ return FALSE;
+}
+
+
+static void render_sheet(struct gui_sheet *sheet)
+{
+ char *argv[] = { "gui", NULL };
+
+ gfx_init(&cro_canvas_ops, 1, argv);
+ sch_render(sheet->sch);
+ cro_canvas_end(gfx_ctx,
+ &sheet->w, &sheet->h, &sheet->xmin, &sheet->ymin);
+ sheet->gfx_ctx = gfx_ctx;
+ sheet->rendered = 1;
+ // gfx_end();
+}
+
+
+/* @@@ not nice to have this so far out */
+static void mark_aois(struct gui_ctx *ctx, struct gui_sheet *sheet);
+
+
+static void render_delta(struct gui_ctx *ctx)
+{
+#if 0
+ /* @@@ needs updating for curr/last vs. new/old */
+ struct sheet *sch_a, *sch_b, *sch_ab;
+ const struct gui_sheet *a = ctx->curr_sheet;
+ const struct gui_sheet *b = find_corresponding_sheet(
+ ctx->last_hist->sheets, ctx->curr_hist->sheets, ctx->curr_sheet);
+
+ sch_a = alloc_type(struct sheet);
+ sch_b = alloc_type(struct sheet);
+ sch_ab = alloc_type(struct sheet);
+
+ delta(a->sch, b->sch, sch_a, sch_b, sch_ab);
+ ctx->delta_a.sch = sch_a,
+ ctx->delta_b.sch = sch_b,
+ ctx->delta_ab.sch = sch_ab,
+
+ render_sheet(&ctx->delta_a);
+ render_sheet(&ctx->delta_b);
+ render_sheet(&ctx->delta_ab);
+
+ cro_color_override(ctx->delta_ab.gfx_ctx, COLOR_LIGHT_GREY);
+ cro_color_override(ctx->delta_a.gfx_ctx, COLOR_RED);
+ cro_color_override(ctx->delta_b.gfx_ctx, COLOR_GREEN2);
+
+ // @@@ clean up when leaving sheet
+#endif
+ struct gui_sheet *b = find_corresponding_sheet(
+ ctx->old_hist->sheets, ctx->new_hist->sheets, ctx->curr_sheet);
+
+ if (!b->rendered) {
+ render_sheet(b);
+ mark_aois(ctx, b);
+ }
+}
+
+
+/* ----- Tools ------------------------------------------------------------- */
+
+
+static void canvas_coord(const struct gui_ctx *ctx,
+ int ex, int ey, int *x, int *y)
+{
+ GtkAllocation alloc;
+ int sx, sy;
+
+ gtk_widget_get_allocation(ctx->da, &alloc);
+ sx = ex - alloc.width / 2;
+ sy = ey - alloc.height / 2;
+ *x = (sx << ctx->zoom) + ctx->x;
+ *y = (sy << ctx->zoom) + ctx->y;
+}
+
+
+static void eeschema_coord(const struct gui_ctx *ctx,
+ int x, int y, int *rx, int *ry)
+{
+ GtkAllocation alloc;
+
+ gtk_widget_get_allocation(ctx->da, &alloc);
+ *rx = ((x - ctx->x) >> ctx->zoom) + alloc.width / 2;
+ *ry = ((y - ctx->y) >> ctx->zoom) + alloc.height / 2;
+}
+
+
+/* ----- 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 -------------------------------------------------------------- */
+
+
+
+static void zoom_in(struct gui_ctx *ctx, int x, int y)
+{
+ if (ctx->zoom == 0)
+ return;
+ ctx->zoom--;
+ ctx->x = (ctx->x + x) / 2;
+ ctx->y = (ctx->y + y) / 2;
+ redraw(ctx);
+}
+
+
+static void zoom_out(struct gui_ctx *ctx, int x, int y)
+{
+ if (ctx->curr_sheet->w >> ctx->zoom <= 16)
+ return;
+ ctx->zoom++;
+ ctx->x = 2 * ctx->x - x;
+ ctx->y = 2 * ctx->y - y;
+ redraw(ctx);
+}
+
+
+static void curr_sheet_size(struct gui_ctx *ctx, int *w, int *h)
+{
+ const struct gui_sheet *sheet = ctx->curr_sheet;
+ int ax1, ay1, bx1, by1;
+
+ if (!ctx->old_hist) {
+ *w = sheet->w;
+ *h = sheet->h;
+ } else {
+ const struct gui_sheet *old =
+ find_corresponding_sheet(ctx->old_hist->sheets,
+ ctx->new_hist->sheets, sheet);
+
+ /*
+ * We're only interested in differences here, so no need for
+ * the usual "-1" in x1 = x0 + w - 1
+ */
+ ax1 = sheet->xmin + sheet->w;
+ ay1 = sheet->ymin + sheet->h;
+ bx1 = old->xmin + old->w;
+ by1 = old->ymin + old->h;
+ *w = (ax1 > bx1 ? ax1 : bx1) -
+ (sheet->xmin < old->xmin ? sheet->xmin : old->xmin);
+ *h = (ay1 > by1 ? ay1 : by1) -
+ (sheet->ymin < old->ymin ? sheet->ymin : old->ymin);
+ }
+}
+
+
+static void zoom_to_extents(struct gui_ctx *ctx)
+{
+ GtkAllocation alloc;
+ int w, h;
+
+ curr_sheet_size(ctx, &w, &h);
+ ctx->x = w / 2;
+ ctx->y = h / 2;
+
+ gtk_widget_get_allocation(ctx->da, &alloc);
+ ctx->zoom = 0;
+ while (w >> ctx->zoom > alloc.width || h >> ctx->zoom > alloc.height)
+ ctx->zoom++;
+
+ redraw(ctx);
+}
+
+
+/* ----- Need this for jumping around -------------------------------------- */
+
+
+static void go_to_sheet(struct gui_ctx *ctx, struct gui_sheet *sheet);
+static bool go_up_sheet(struct gui_ctx *ctx);
+
+
+/* ----- Revision history -------------------------------------------------- */
+
+
+static void do_revision_overlays(struct gui_ctx *ctx);
+
+
+static void hide_history(struct gui_ctx *ctx)
+{
+ ctx->showing_history = 0;
+ do_revision_overlays(ctx);
+ redraw(ctx);
+}
+
+
+#define RGBA(r, g, b, a) ((struct color) { (r), (g), (b), (a) })
+#define COLOR(color) RGBA(color)
+
+
+static void set_history_style(struct gui_hist *h, bool current)
+{
+ struct gui_ctx *ctx = h->ctx;
+ struct overlay_style style = overlay_style_dense;
+ const struct gui_hist *new = ctx->new_hist;
+ const struct gui_hist *old = ctx->old_hist;
+
+ /* this is in addition to showing detailed content */
+ if (current)
+ style.width++;
+
+ switch (ctx->selecting) {
+ case sel_only:
+ style.frame = COLOR(FRAME_SEL_ONLY);
+ break;
+ case sel_old:
+ style.frame = COLOR(FRAME_SEL_OLD);
+ break;
+ case sel_new:
+ style.frame = COLOR(FRAME_SEL_NEW);
+ break;
+ default:
+ abort();
+ }
+
+ if (ctx->new_hist == h || ctx->old_hist == h) {
+ style.width++;
+ style.font = BOLD_FONT;
+ }
+ if (ctx->old_hist) {
+ if (h == new)
+ style.bg = COLOR(BG_NEW);
+ if (h == old)
+ style.bg = COLOR(BG_OLD);
+ }
+
+ if (h->identical)
+ style.fg = RGBA(0.5, 0.5, 0.5, 1);
+ if (!h->sheets)
+ style.fg = RGBA(0.7, 0.0, 0.0, 1);
+
+ overlay_style(h->over, &style);
+}
+
+
+static bool hover_history(void *user, bool on)
+{
+ struct gui_hist *h = user;
+ struct gui_ctx *ctx = h->ctx;
+ char *s;
+
+ if (on) {
+ s = vcs_git_long_for_pango(h->vcs_hist);
+ overlay_text_raw(h->over, s);
+ free(s);
+ } else {
+ overlay_text(h->over, "<small>%s</small>",
+ vcs_git_summary(h->vcs_hist));
+ }
+ set_history_style(h, on);
+ redraw(ctx);
+ return 1;
+}
+
+
+static void click_history(void *user)
+{
+ struct gui_hist *h = user;
+ struct gui_ctx *ctx = h->ctx;
+ struct gui_sheet *sheet;
+
+ hide_history(ctx);
+
+ if (!h->sheets)
+ return;
+
+ sheet = find_corresponding_sheet(h->sheets,
+ ctx->new_hist->sheets, ctx->curr_sheet);
+
+ switch (ctx->selecting) {
+ case sel_only:
+ ctx->old_hist = ctx->new_hist;
+ ctx->new_hist = h;
+ break;
+ case sel_new:
+ ctx->new_hist = h;
+ break;
+ case sel_old:
+ ctx->old_hist = h;
+ break;
+ default:
+ abort();
+ }
+
+ if (ctx->new_hist->age > ctx->old_hist->age) {
+ swap(ctx->new_hist, ctx->old_hist);
+ if (ctx->selecting == sel_old)
+ go_to_sheet(ctx, sheet);
+ else
+ render_delta(ctx);
+ } else {
+ if (ctx->selecting != sel_old)
+ go_to_sheet(ctx, sheet);
+ else
+ render_delta(ctx);
+ }
+
+ if (ctx->old_hist == ctx->new_hist)
+ ctx->old_hist = NULL;
+
+ do_revision_overlays(ctx);
+ redraw(ctx);
+}
+
+
+static void drag_overlay(void *user, int dx, int dy)
+{
+ const struct gui_hist *h = user;
+ struct gui_ctx *ctx = h->ctx;
+
+ ctx->hist_y_offset += dy;
+ redraw(ctx);
+}
+
+
+static void ignore_click(void *user)
+{
+}
+
+
+static struct gui_hist *skip_history(struct gui_ctx *ctx, struct gui_hist *h)
+{
+ struct overlay_style style = overlay_style_dense;
+ unsigned n;
+
+ /* don't skip the first entry */
+ if (h == ctx->hist)
+ return h;
+
+ /* need at least two entries */
+ if (!h->identical || !h->next || !h->next->identical)
+ return h;
+
+ /* don't skip the last entry */
+ for (n = 0; h->next && h->identical; h = h->next)
+ n++;
+
+ h->over = overlay_add(&ctx->hist_overlays, &ctx->aois,
+ NULL, ignore_click, h);
+ overlay_draggable(h->over, drag_overlay);
+ overlay_text(h->over, "<small>%u commits without changes</small>", n);
+
+ style.width = 0;
+ style.pad = 0;
+ style.bg = RGBA(1.0, 1.0, 1.0, 0.8);
+ overlay_style(h->over, &style);
+
+ return h;
+}
+
+
+static void show_history(struct gui_ctx *ctx, enum selecting sel)
+{
+ struct gui_hist *h = ctx->hist;
+
+ ctx->showing_history = 1;
+ ctx->hist_y_offset = 0;
+ ctx->selecting = sel;
+ overlay_remove_all(&ctx->hist_overlays);
+ for (h = ctx->hist; h; h = h->next) {
+ h = skip_history(ctx, h);
+ h->over = overlay_add(&ctx->hist_overlays, &ctx->aois,
+ hover_history, click_history, h);
+ overlay_draggable(h->over, drag_overlay);
+ hover_history(h, 0);
+ set_history_style(h, 0);
+ }
+ redraw(ctx);
+}
+
+
+static void show_history_cb(void *user)
+{
+ struct gui_hist *h = user;
+ struct gui_ctx *ctx = h->ctx;
+ enum selecting sel = sel_only;
+
+ if (ctx->old_hist)
+ sel = h == ctx->new_hist ? sel_new : sel_old;
+ show_history(ctx, sel);
+}
+
+
+/* ----- Navigate sheets --------------------------------------------------- */
+
+
+/* @@@ find a better place for this forward declaration */
+static void mark_aois(struct gui_ctx *ctx, struct gui_sheet *sheet);
+
+
+static void close_subsheet(void *user)
+{
+ struct gui_sheet *sheet = user;
+ struct gui_ctx *ctx = sheet->ctx;
+
+ go_to_sheet(ctx, sheet);
+}
+
+
+static bool hover_sheet(void *user, bool on)
+{
+ struct gui_sheet *sheet = user;
+ struct gui_ctx *ctx = sheet->ctx;
+ const char *title = sheet->sch->title;
+
+ if (!title)
+ title = "(unnamed)";
+ if (on) {
+ const struct gui_sheet *s;
+ int n = 0, this = -1;
+
+ for (s = ctx->new_hist->sheets; s; s = s->next) {
+ n++;
+ if (s == sheet)
+ this = n;
+ }
+ overlay_text(sheet->over, "<b>%s</b>\n<big>%d / %d</big>",
+ title, this, n);
+ } else {
+ overlay_text(sheet->over, "<b>%s</b>", title);
+ }
+ redraw(ctx);
+ return 1;
+}
+
+
+static bool show_history_details(void *user, bool on)
+{
+ struct gui_hist *h = user;
+ struct gui_ctx *ctx = h->ctx;
+ char *s;
+
+ if (on) {
+ s = vcs_git_long_for_pango(h->vcs_hist);
+ overlay_text_raw(h->over, s);
+ free(s);
+ } else {
+ overlay_text(h->over, "%.40s", vcs_git_summary(h->vcs_hist));
+ }
+ redraw(ctx);
+ return 1;
+}
+
+
+static void revision_overlays_diff(struct gui_ctx *ctx)
+{
+ struct gui_hist *new = ctx->new_hist;
+ struct gui_hist *old = ctx->old_hist;
+
+ new->over = overlay_add(&ctx->hist_overlays, &ctx->aois,
+ show_history_details, show_history_cb, new);
+ overlay_style(new->over, &overlay_style_diff_new);
+ show_history_details(new, 0);
+
+ old->over = overlay_add(&ctx->hist_overlays, &ctx->aois,
+ show_history_details, show_history_cb, old);
+ overlay_style(old->over, &overlay_style_diff_old);
+ show_history_details(old, 0);
+}
+
+
+static void do_revision_overlays(struct gui_ctx *ctx)
+{
+ overlay_remove_all(&ctx->hist_overlays);
+
+ if (ctx->old_hist) {
+ revision_overlays_diff(ctx);
+ } else {
+ ctx->new_hist->over = overlay_add(&ctx->hist_overlays,
+ &ctx->aois, show_history_details, show_history_cb,
+ ctx->new_hist);
+ overlay_style(ctx->new_hist->over, &overlay_style_default);
+ show_history_details(ctx->new_hist, 0);
+ }
+}
+
+
+static struct gui_sheet *find_parent_sheet(struct gui_sheet *sheets,
+ const struct gui_sheet *ref)
+{
+ struct gui_sheet *parent;
+ const struct sch_obj *obj;
+
+ for (parent = sheets; parent; parent = parent->next)
+ for (obj = parent->sch->objs; obj; obj = obj->next)
+ if (obj->type == sch_obj_sheet &&
+ obj->u.sheet.sheet == ref->sch)
+ return parent;
+ return NULL;
+}
+
+
+static void sheet_selector_recurse(struct gui_ctx *ctx, struct gui_sheet *sheet)
+{
+ struct gui_sheet *parent;
+
+ parent = find_parent_sheet(ctx->new_hist->sheets, sheet);
+ if (parent)
+ sheet_selector_recurse(ctx, parent);
+ sheet->over = overlay_add(&ctx->sheet_overlays, &ctx->aois,
+ hover_sheet, close_subsheet, sheet);
+ hover_sheet(sheet, 0);
+}
+
+
+static void do_sheet_overlays(struct gui_ctx *ctx)
+{
+ overlay_remove_all(&ctx->sheet_overlays);
+ sheet_selector_recurse(ctx, ctx->curr_sheet);
+}
+
+
+static void go_to_sheet(struct gui_ctx *ctx, struct gui_sheet *sheet)
+{
+ aoi_dehover();
+ overlay_remove_all(&ctx->pop_overlays);
+ if (!sheet->rendered) {
+ render_sheet(sheet);
+ mark_aois(ctx, sheet);
+ }
+ ctx->curr_sheet = sheet;
+ if (ctx->old_hist)
+ render_delta(ctx);
+ if (ctx->vcs_hist)
+ do_revision_overlays(ctx);
+ do_sheet_overlays(ctx);
+ zoom_to_extents(ctx);
+}
+
+
+static bool go_up_sheet(struct gui_ctx *ctx)
+{
+ struct gui_sheet *parent;
+
+ parent = find_parent_sheet(ctx->new_hist->sheets, ctx->curr_sheet);
+ if (!parent)
+ return 0;
+ go_to_sheet(ctx, parent);
+ return 1;
+}
+
+
+static bool go_prev_sheet(struct gui_ctx *ctx)
+{
+ struct gui_sheet *sheet;
+
+ for (sheet = ctx->new_hist->sheets; sheet; sheet = sheet->next)
+ if (sheet->next && sheet->next == ctx->curr_sheet) {
+ go_to_sheet(ctx, sheet);
+ return 1;
+ }
+ return 0;
+}
+
+
+static bool go_next_sheet(struct gui_ctx *ctx)
+{
+ if (!ctx->curr_sheet->next)
+ return 0;
+ go_to_sheet(ctx, ctx->curr_sheet->next);
+ return 1;
+}
+
+
+/* ----- Event handlers ---------------------------------------------------- */
+
+
+static bool botton_1_down = 0;
+
+
+static gboolean motion_notify_event(GtkWidget *widget, GdkEventMotion *event,
+ gpointer data)
+{
+ struct gui_ctx *ctx = data;
+ const struct gui_sheet *curr_sheet = ctx->curr_sheet;
+ int x, y;
+
+ ctx->curr_x = event->x;
+ ctx->curr_y = event->y;
+
+ canvas_coord(ctx, event->x, event->y, &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,
+ 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 gboolean button_release_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:
+ 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)
+{
+ struct gui_ctx *ctx = data;
+ struct gui_sheet *sheet = ctx->curr_sheet;
+ int x, y;
+
+ canvas_coord(ctx, ctx->curr_x, ctx->curr_y, &x, &y);
+
+ switch (event->keyval) {
+ case '+':
+ case '=':
+ zoom_in(ctx, x, y);
+ break;
+ case '-':
+ zoom_out(ctx, x, y);
+ break;
+ case '*':
+ zoom_to_extents(ctx);
+ break;
+ case GDK_KEY_Home:
+ if (sheet != ctx->new_hist->sheets)
+ go_to_sheet(ctx, ctx->new_hist->sheets);
+ break;
+ case GDK_KEY_BackSpace:
+ case GDK_KEY_Delete:
+ go_up_sheet(ctx);
+ break;
+ case GDK_KEY_Page_Up:
+ case GDK_KEY_KP_Page_Up:
+ go_prev_sheet(ctx);
+ break;
+ case GDK_KEY_Page_Down:
+ case GDK_KEY_KP_Page_Down:
+ go_next_sheet(ctx);
+ break;
+ case GDK_KEY_Up:
+ case GDK_KEY_KP_Up:
+ show_history(ctx, sel_new);
+ break;
+ case GDK_KEY_Down:
+ case GDK_KEY_KP_Down:
+ show_history(ctx, sel_old);
+ break;
+ 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;
+
+ 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;
+}
+
+
+static void size_allocate_event(GtkWidget *widget, GdkRectangle *allocation,
+ gpointer data)
+{
+ struct gui_ctx *ctx = data;
+
+ zoom_to_extents(ctx);
+}
+
+
+/* ----- AoI callbacks ----------------------------------------------------- */
+
+
+struct sheet_aoi_ctx {
+ struct gui_ctx *gui_ctx;
+ const struct sch_obj *obj;
+};
+
+
+static void select_subsheet(void *user)
+{
+ const struct sheet_aoi_ctx *aoi_ctx = user;
+ struct gui_ctx *ctx = aoi_ctx->gui_ctx;
+ const struct sch_obj *obj = aoi_ctx->obj;
+ struct gui_sheet *sheet;
+
+ if (!obj->u.sheet.sheet)
+ return;
+ for (sheet = ctx->new_hist->sheets; sheet; sheet = sheet->next)
+ if (sheet->sch == obj->u.sheet.sheet) {
+ go_to_sheet(ctx, sheet);
+ return;
+ }
+ abort();
+}
+
+
+struct glabel_aoi_ctx {
+ const struct gui_sheet *sheet;
+ const struct sch_obj *obj;
+ struct dwg_bbox bbox;
+ struct overlay *over;
+};
+
+
+/* small offset to hide rounding errors */
+#define CHEAT 1
+
+
+static void glabel_dest_click(void *user)
+{
+ struct gui_sheet *sheet = user;
+
+ go_to_sheet(sheet->ctx, sheet);
+}
+
+
+static bool hover_glabel(void *user, bool on)
+{
+ struct glabel_aoi_ctx *aoi_ctx = user;
+ struct gui_ctx *ctx = aoi_ctx->sheet->ctx;
+ const struct gui_sheet *curr_sheet = ctx->curr_sheet;
+ const struct dwg_bbox *bbox = &aoi_ctx->bbox;
+
+ if (!on) {
+ overlay_remove(&ctx->pop_overlays, aoi_ctx->over);
+ redraw(ctx);
+ return 1;
+ }
+
+ GtkAllocation alloc;
+ struct overlay_style style = {
+ .font = BOLD_FONT,
+ .wmin = 100,
+ .wmax = 100,
+ .radius = 0,
+ .pad = 4,
+ .skip = -4,
+ .fg = { 0.0, 0.0, 0.0, 1.0 },
+ .bg = { 1.0, 0.8, 0.4, 0.8 },
+ .frame = { 1.0, 1.0, 1.0, 1.0 }, /* debugging */
+ .width = 0,
+ };
+ int sx, sy, ex, ey, mx, my;
+ unsigned n = 0;
+ struct gui_sheet *sheet;
+ const struct sch_obj *obj;
+ struct overlay *over;
+
+ aoi_dehover();
+ overlay_remove_all(&ctx->pop_overlays);
+ for (sheet = ctx->new_hist->sheets; sheet; sheet = sheet->next) {
+ n++;
+ if (sheet == curr_sheet)
+ continue;
+ for (obj = sheet->sch->objs; obj; obj = obj->next) {
+ if (obj->type != sch_obj_glabel)
+ continue;
+ if (strcmp(obj->u.text.s, aoi_ctx->obj->u.text.s))
+ continue;
+ over = overlay_add(&ctx->pop_overlays,
+ &ctx->aois, NULL, glabel_dest_click, sheet);
+ overlay_text(over, "%d %s", n, sheet->sch->title);
+ overlay_style(over, &style);
+ break;
+ }
+ }
+
+ eeschema_coord(ctx,
+ bbox->x - curr_sheet->xmin, bbox->y - curr_sheet->ymin,
+ &sx, &sy);
+ eeschema_coord(ctx, bbox->x + bbox->w - curr_sheet->xmin,
+ bbox->y + bbox->h - curr_sheet->ymin, &ex, &ey);
+
+ gtk_widget_get_allocation(ctx->da, &alloc);
+ mx = (sx + ex) / 2;
+ my = (sy + ey) / 2;
+ ctx->pop_x = mx < alloc.width / 2 ?
+ sx - CHEAT : -(alloc.width - ex) + CHEAT;
+ ctx->pop_y = my < alloc.height / 2 ?
+ sy - CHEAT : -(alloc.height - ey) + CHEAT;
+
+ redraw(ctx);
+ return 0;
+}
+
+
+/* ----- Progress bar ------------------------------------------------------ */
+
+
+#define PROGRESS_BAR_HEIGHT 10
+
+
+static void progress_draw_event(GtkWidget *widget, cairo_t *cr,
+ gpointer user_data)
+{
+ GtkAllocation alloc;
+ struct gui_ctx *ctx = user_data;
+ unsigned w, x;
+
+ x = ctx->progress >> ctx->progress_scale;
+ if (!x) {
+ /* @@@ needed ? Gtk seems to always clear the the surface. */
+ cairo_set_source_rgb(cr, 1, 1, 1);
+ cairo_paint(cr);
+ }
+
+ gtk_widget_get_allocation(ctx->da, &alloc);
+ w = ctx->hist_size >> ctx->progress_scale;
+
+ cairo_save(cr);
+ cairo_translate(cr,
+ (alloc.width - w) / 2, (alloc.height - PROGRESS_BAR_HEIGHT) / 2);
+
+ cairo_set_source_rgb(cr, 0, 0.7, 0);
+ cairo_set_line_width(cr, 0);
+ cairo_rectangle(cr, 0, 0, x, PROGRESS_BAR_HEIGHT);
+ cairo_fill(cr);
+
+ cairo_set_source_rgb(cr, 0, 0, 0);
+ cairo_set_line_width(cr, 2);
+ cairo_rectangle(cr, 0, 0, w, PROGRESS_BAR_HEIGHT);
+ cairo_stroke(cr);
+
+ cairo_restore(cr);
+}
+
+
+static void setup_progress_bar(struct gui_ctx *ctx, GtkWidget *window)
+{
+ GtkAllocation alloc;
+
+ gtk_widget_get_allocation(ctx->da, &alloc);
+
+ ctx->progress_scale = 0;
+ while ((ctx->hist_size >> ctx->progress_scale) > alloc.width)
+ ctx->progress_scale++;
+ ctx->progress = 0;
+
+ g_signal_connect(G_OBJECT(ctx->da), "draw",
+ G_CALLBACK(progress_draw_event), ctx);
+
+ redraw(ctx);
+ gtk_main_iteration_do(0);
+}
+
+
+static void progress_update(struct gui_ctx *ctx)
+{
+ unsigned mask = (1 << ctx->progress_scale) - 1;
+
+ ctx->progress++;
+ if ((ctx->progress & mask) != mask)
+ return;
+
+ redraw(ctx);
+ gtk_main_iteration_do(0);
+}
+
+
+/* ----- Initialization ---------------------------------------------------- */
+
+
+static void add_sheet_aoi(struct gui_ctx *ctx, struct gui_sheet *parent,
+ const struct sch_obj *obj)
+{
+ struct sheet_aoi_ctx *aoi_ctx = alloc_type(struct sheet_aoi_ctx);
+
+ aoi_ctx->gui_ctx = ctx;
+ aoi_ctx->obj = obj;
+
+ struct aoi aoi = {
+ .x = obj->x,
+ .y = obj->y,
+ .w = obj->u.sheet.w,
+ .h = obj->u.sheet.h,
+ .click = select_subsheet,
+ .user = aoi_ctx,
+ };
+
+ aoi_add(&parent->aois, &aoi);
+}
+
+
+static void add_glabel_aoi(struct gui_sheet *sheet, const struct sch_obj *obj)
+{
+ const struct dwg_bbox *bbox = &obj->u.text.bbox;
+ struct glabel_aoi_ctx *aoi_ctx = alloc_type(struct glabel_aoi_ctx);
+
+ struct aoi cfg = {
+ .x = bbox->x,
+ .y = bbox->y,
+ .w = bbox->w,
+ .h = bbox->h,
+ .hover = hover_glabel,
+ .user = aoi_ctx,
+ };
+
+ aoi_ctx->sheet = sheet;
+ aoi_ctx->obj = obj;
+ aoi_ctx->bbox = *bbox;
+
+ aoi_add(&sheet->aois, &cfg);
+}
+
+
+static void mark_aois(struct gui_ctx *ctx, struct gui_sheet *sheet)
+{
+ const struct sch_obj *obj;
+
+ sheet->aois = NULL;
+ for (obj = sheet->sch->objs; obj; obj = obj->next)
+ switch (obj->type) {
+ case sch_obj_sheet:
+ add_sheet_aoi(ctx, sheet, obj);
+ break;
+ case sch_obj_glabel:
+ add_glabel_aoi(sheet, obj);
+ default:
+ break;
+ }
+}
+
+
+static struct gui_sheet *get_sheets(struct gui_ctx *ctx,
+ const struct sheet *sheets)
+{
+ const struct sheet *sheet;
+ struct gui_sheet *gui_sheets = NULL;
+ struct gui_sheet **next = &gui_sheets;
+ struct gui_sheet *new;
+
+ for (sheet = sheets; sheet; sheet = sheet->next) {
+ new = alloc_type(struct gui_sheet);
+ new->sch = sheet;
+ new->ctx = ctx;
+ new->rendered = 0;
+
+ *next = new;
+ next = &new->next;
+ }
+ *next = NULL;
+ return gui_sheets;
+}
+
+
+/*
+ * Library caching:
+ *
+ * We reuse previous components if all libraries are identical
+ *
+ * Future optimizations:
+ * - don't parse into single list of components, so that we can share
+ * libraries that are the same, even if there are others that have changed.
+ * - maybe put components into tree, so that they can be replaced individually
+ * (this would also help to identify sheets that don't need parsing)
+ *
+ * Sheet caching:
+ *
+ * We reuse previous sheets if
+ * - all libraries are identical (whether a given sheet uses them or not),
+ * - they have no sub-sheets, and
+ * - the objects IDs (hashes) are identical.
+ *
+ * Note that we only compare with the immediately preceding (newer) revision,
+ * so branches and merges can disrupt caching.
+ *
+ * Possible optimizations:
+ * - if we record which child sheets a sheet has, we could also clone it,
+ * without having to parse it. However, this is somewhat complex and may
+ * not save all that much time.
+ * - we could record what libraries a sheet uses, and parse only if one of
+ * these has changed (benefits scenarios with many library files),
+ * - we could record what components a sheet uses, and parse only if one of
+ * these has changed (benefits scenarios with few big libraries),
+ * - we could postpone library lookups to render time.
+ * - we could record IDs globally, which would help to avoid tripping over
+ * branches and merges.
+ */
+
+static const struct sheet *parse_files(struct gui_hist *hist,
+ int n_args, char **args, bool recurse, struct gui_hist *prev)
+{
+ char *rev = NULL;
+ struct file sch_file;
+ struct file lib_files[n_args - 1];
+ int libs_open, i;
+ bool libs_cached = 0;
+ bool ok;
+
+ if (hist->vcs_hist && hist->vcs_hist->commit)
+ rev = vcs_git_get_rev(hist->vcs_hist);
+
+ sch_init(&hist->sch_ctx, recurse);
+ ok = file_open_revision(&sch_file, rev, args[n_args - 1], NULL);
+
+ if (rev)
+ free(rev);
+ if (!ok) {
+ sch_free(&hist->sch_ctx);
+ return NULL;
+ }
+
+ lib_init(&hist->lib);
+ for (libs_open = 0; libs_open != n_args - 1; libs_open++)
+ if (!file_open(lib_files + libs_open, args[libs_open],
+ &sch_file))
+ goto fail;
+
+ if (hist->vcs_hist) {
+ hist->oids = alloc_type_n(void *, libs_open);
+ hist->libs_open = libs_open;
+ for (i = 0; i != libs_open; i++)
+ hist->oids[i] = file_oid(lib_files + i);
+ if (prev && prev->vcs_hist && prev->libs_open == libs_open) {
+ for (i = 0; i != libs_open; i++)
+ if (!file_oid_eq(hist->oids[i], prev->oids[i]))
+ break;
+ if (i == libs_open) {
+ hist->lib.comps = prev->lib.comps;
+ libs_cached = 1;
+ }
+ }
+ }
+
+ if (!libs_cached)
+ for (i = 0; i != libs_open; i++)
+ if (!lib_parse_file(&hist->lib, lib_files +i))
+ goto fail;
+
+ if (!sch_parse(&hist->sch_ctx, &sch_file, &hist->lib,
+ libs_cached ? &prev->sch_ctx : NULL))
+ goto fail;
+
+ for (i = 0; i != libs_open; i++)
+ file_close(lib_files + i);
+ file_close(&sch_file);
+
+ if (prev && sheet_eq(prev->sch_ctx.sheets, hist->sch_ctx.sheets))
+ prev->identical = 1;
+
+ /*
+ * @@@ we have a major memory leak for the component library.
+ * We should record parsed schematics and libraries separately, so
+ * that we can clean them up, without having to rely on the history,
+ * with - when sharing unchanged item - possibly many duplicate
+ * pointers.
+ */
+ return hist->sch_ctx.sheets;
+
+fail:
+ while (libs_open--)
+ file_close(lib_files + libs_open);
+ sch_free(&hist->sch_ctx);
+ lib_free(&hist->lib);
+ file_close(&sch_file);
+ return NULL;
+}
+
+
+struct add_hist_ctx {
+ struct gui_ctx *ctx;
+ int n_args;
+ char **args;
+ bool recurse;
+ unsigned limit;
+};
+
+
+static void add_hist(void *user, struct hist *h)
+{
+ struct add_hist_ctx *ahc = user;
+ struct gui_ctx *ctx = ahc->ctx;
+ struct gui_hist **anchor, *hist, *prev;
+ const struct sheet *sch;
+ unsigned age = 0;
+
+ if (!ahc->limit)
+ return;
+ if (ahc->limit > 0)
+ ahc->limit--;
+
+ prev = NULL;
+ for (anchor = &ctx->hist; *anchor; anchor = &(*anchor)->next) {
+ prev = *anchor;
+ age++;
+ }
+
+ hist = alloc_type(struct gui_hist);
+ hist->ctx = ctx;
+ hist->vcs_hist = h;
+ hist->identical = 0;
+ sch = parse_files(hist, ahc->n_args, ahc->args, ahc->recurse, prev);
+ hist->sheets = sch ? get_sheets(ctx, sch) : NULL;
+ hist->age = age;
+
+ hist->next = NULL;
+ *anchor = hist;
+
+ if (ctx->hist_size)
+ progress_update(ctx);
+}
+
+
+static void count_history(void *user, struct hist *h)
+{
+ struct gui_ctx *ctx = user;
+
+ ctx->hist_size++;
+}
+
+
+static void get_history(struct gui_ctx *ctx, const char *sch_name, int limit)
+{
+ if (!vcs_git_try(sch_name)) {
+ ctx->vcs_hist = NULL;
+ return;
+ }
+
+ ctx->vcs_hist = vcs_git_hist(sch_name);
+ if (limit)
+ ctx->hist_size = limit > 0 ? limit : -limit;
+ else
+ hist_iterate(ctx->vcs_hist, count_history, ctx);
+}
+
+
+static void get_revisions(struct gui_ctx *ctx,
+ int n_args, char **args, bool recurse, int limit)
+{
+ struct add_hist_ctx add_hist_ctx = {
+ .ctx = ctx,
+ .n_args = n_args,
+ .args = args,
+ .recurse = recurse,
+ .limit = limit ? limit < 0 ? -limit : limit : -1,
+ };
+
+ if (ctx->vcs_hist)
+ hist_iterate(ctx->vcs_hist, add_hist, &add_hist_ctx);
+ else
+ add_hist(&add_hist_ctx, NULL);
+}
+
+
+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,
+ .sheet_overlays = NULL,
+ .hist_overlays = NULL,
+ .pop_overlays = NULL,
+ .aois = NULL,
+ .old_hist = NULL,
+ .hist_y_offset = 0,
+ .hist_size = 0,
+ };
+
+ window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+ ctx.da = gtk_drawing_area_new();
+ gtk_container_add(GTK_CONTAINER(window), ctx.da);
+
+ 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);
+
+ gtk_widget_show_all(window);
+
+ get_history(&ctx, args[n_args - 1], limit);
+ if (ctx.hist_size)
+ setup_progress_bar(&ctx, window);
+
+ get_revisions(&ctx, n_args, args, recurse, limit);
+ for (ctx.new_hist = ctx.hist; ctx.new_hist && !ctx.new_hist->sheets;
+ ctx.new_hist = ctx.new_hist->next);
+ if (!ctx.new_hist)
+ fatal("no valid sheets\n");
+
+ 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);
+
+ g_signal_connect(window, "destroy",
+ G_CALLBACK(gtk_main_quit), NULL);
+
+// gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
+
+ go_to_sheet(&ctx, ctx.new_hist->sheets);
+ gtk_widget_show_all(window);
+
+ /* for performance testing, use -N-depth */
+ if (limit >= 0)
+ gtk_main();
+
+ return 0;
+}
diff --git a/gui/gui.h b/gui/gui.h
new file mode 100644
index 0000000..04bc1dd
--- /dev/null
+++ b/gui/gui.h
@@ -0,0 +1,25 @@
+/*
+ * gui/gui.h - GUI for eeshow
+ *
+ * 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_GUI_H
+#define GUI_GUI_H
+
+#include <stdbool.h>
+
+/*
+ * Note: this isn't (argc, argv) ! args stars right with the first file name
+ * and there is no NULL at the end.
+ */
+
+int gui(unsigned n_args, char **args, bool recurse, int limit);
+
+#endif /* !GUI_GUI_H */
diff --git a/gui/over.c b/gui/over.c
new file mode 100644
index 0000000..7ee7a91
--- /dev/null
+++ b/gui/over.c
@@ -0,0 +1,305 @@
+/*
+ * gui/over.c - GUI: overlays
+ *
+ * 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.
+ */
+
+/*
+ * Resources:
+ *
+ * http://zetcode.com/gfx/cairo/cairobackends/
+ * https://developer.gnome.org/gtk3/stable/gtk-migrating-2-to-3.html
+ * https://www.cairographics.org/samples/rounded_rectangle/
+ *
+ * Section "Description" in
+ * https://developer.gnome.org/pango/stable/pango-Cairo-Rendering.html
+ */
+
+#include <stddef.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <math.h>
+
+#include <cairo/cairo.h>
+#include <pango/pangocairo.h>
+
+#include "util.h"
+#include "fmt-pango.h"
+#include "gui/aoi.h"
+#include "gui/style.h"
+#include "gui/over.h"
+
+
+struct overlay {
+ const char *s;
+ struct overlay_style style;
+
+ struct aoi **aois;
+ bool (*hover)(void *user, bool on);
+ void (*click)(void *user);
+ void (*drag)(void *user, int dx, int dy);
+ void *user;
+
+ struct aoi *aoi;
+
+ struct overlay *next, *prev;
+};
+
+
+static void rrect(cairo_t *cr, int x, int y, int w, int h, int r)
+{
+ const double deg = M_PI / 180.0;
+
+ cairo_new_path(cr);
+ cairo_arc(cr, x + w - r, y + r, r, -90 * deg, 0);
+ cairo_arc(cr, x + w - r, y + h - r, r, 0, 90 * deg);
+ cairo_arc(cr, x + r, y + h - r, r, 90 * deg, 180 * deg);
+ cairo_arc(cr, x + r, y + r, r, 180 * deg, 270 * deg);
+ cairo_close_path(cr);
+}
+
+
+static unsigned overlay_draw(struct overlay *over, cairo_t *cr,
+ unsigned x, unsigned y, int dx, int dy)
+{
+ const struct overlay_style *style = &over->style;
+ const struct color *fg = &style->fg;
+ const struct color *bg = &style->bg;
+ const struct color *frame = &style->frame;
+ unsigned ink_w, ink_h; /* effectively used text area size */
+ unsigned w, h; /* box size */
+ int tx, ty; /* text start position */
+
+ PangoLayout *layout;
+ PangoFontDescription *desc;
+ PangoRectangle ink_rect;
+
+ desc = pango_font_description_from_string(style->font);
+ layout = pango_cairo_create_layout(cr);
+ pango_layout_set_font_description(layout, desc);
+ pango_layout_set_markup(layout, over->s, -1);
+ pango_font_description_free(desc);
+
+ pango_layout_get_extents(layout, &ink_rect, NULL);
+#if 0
+fprintf(stderr, "%d + %d %d + %d\n",
+ ink_rect.x / PANGO_SCALE, ink_rect.width / PANGO_SCALE,
+ ink_rect.y / PANGO_SCALE, ink_rect.height / PANGO_SCALE);
+#endif
+ ink_w = ink_rect.width / PANGO_SCALE;
+ ink_h = ink_rect.height / PANGO_SCALE;
+
+ ink_w = ink_w > style->wmin ? ink_w : style->wmin;
+ ink_w = !style->wmax || ink_w < style->wmax ? ink_w : style->wmax;
+ w = ink_w + 2 * style->pad;
+ h = ink_h + 2 * style->pad;
+
+ if (dx < 0)
+ x -= w;
+ if (dy < 0)
+ y -= h;
+
+ tx = x - ink_rect.x / PANGO_SCALE + style->pad;
+ ty = y - ink_rect.y / PANGO_SCALE + style->pad;
+
+ rrect(cr, x, y, w, h, style->radius);
+
+ cairo_set_source_rgba(cr, bg->r, bg->g, bg->b, bg->alpha);
+ cairo_fill_preserve(cr);
+ cairo_set_source_rgba(cr, frame->r, frame->g, frame->b, frame->alpha);
+ cairo_set_line_width(cr, style->width);
+ cairo_stroke(cr);
+
+ if (style->wmax) {
+ cairo_new_path(cr);
+#if 0
+fprintf(stderr, "%u(%d) %u %.60s\n", ty, ink_rect.y / PANGO_SCALE, ink_h, over->s);
+#endif
+/*
+ * @@@ for some mysterious reason, we get
+ * ink_h = ink_rect.height / PANGO_SCALE = 5
+ * instead of 2 if using overlay_style_dense_selected. Strangely, changing
+ * overlay_style_dense_selected such that it becomes more like
+ * overlay_style_dense has no effect.
+ *
+ * This causes the text to be cut vertically, roughly in the middle. We hack
+ * around this problem by growind the clipping area vertically. This works,
+ * since we're currently only concerned about horizontal clipping anyway.
+ */
+
+ cairo_rectangle(cr, tx, ty, ink_w, ink_h + 20);
+ cairo_clip(cr);
+ }
+
+ cairo_set_source_rgba(cr, fg->r, fg->g, fg->b, fg->alpha);
+ cairo_move_to(cr, tx, ty);
+
+ pango_cairo_update_layout(cr, layout);
+ pango_cairo_show_layout(cr, layout);
+ cairo_reset_clip(cr);
+ g_object_unref(layout);
+
+ if (over->hover || over->click || over->drag) {
+ struct aoi aoi_cfg = {
+ .x = x,
+ .y = y,
+ .w = w,
+ .h = h,
+ .hover = over->hover,
+ .click = over->click,
+ .drag = over->drag,
+ .user = over->user,
+ };
+
+ if (over->aoi)
+ aoi_update(over->aoi, &aoi_cfg);
+ else
+ over->aoi = aoi_add(over->aois, &aoi_cfg);
+ }
+
+ return h;
+}
+
+
+void overlay_draw_all_d(struct overlay *overlays, cairo_t *cr,
+ unsigned x, unsigned y, int dx, int dy)
+{
+ struct overlay *over = overlays;
+ unsigned h;
+
+ if (dy < 0)
+ while (over && over->next)
+ over = over->next;
+ while (over) {
+ h = overlay_draw(over, cr, x, y, dx, dy);
+ y += dy * (h + over->style.skip);
+ if (dy >= 0)
+ over = over->next;
+ else
+ over = over->prev;
+
+ }
+}
+
+
+void overlay_draw_all(struct overlay *overlays, cairo_t *cr, int x, int y)
+{
+ int dx = 1;
+ int dy = 1;
+
+ if (x < 0 || y < 0) {
+ double x1, y1, x2, y2;
+ int sw, sh;
+
+ cairo_clip_extents(cr, &x1, &y1, &x2, &y2);
+ sw = x2 - x1;
+ sh = y2 - y1;
+ if (x < 0) {
+ x = sw + x;
+ dx = -1;
+ }
+ if (y < 0) {
+ y = sh + y;
+ dy = -1;
+ }
+ }
+
+ overlay_draw_all_d(overlays, cr, x, y, dx, dy);
+}
+
+
+struct overlay *overlay_add(struct overlay **overlays, struct aoi **aois,
+ bool (*hover)(void *user, bool on), void (*click)(void *user), void *user)
+{
+ struct overlay *over, *prev;
+ struct overlay **anchor;
+
+ over = alloc_type(struct overlay);
+ over->s = NULL;
+ over->style = overlay_style_default;
+
+ over->aois = aois;
+ over->hover = hover;
+ over->click = click;
+ over->user = user;
+ over->aoi = NULL;
+
+ prev = NULL;
+ for (anchor = overlays; *anchor; anchor = &(*anchor)->next)
+ prev = *anchor;
+ over->next = NULL;
+ over->prev = prev;
+ *anchor = over;
+
+ return over;
+}
+
+
+void overlay_style(struct overlay *over, const struct overlay_style *style)
+{
+ over->style = *style;
+}
+
+
+void overlay_draggable(struct overlay *over,
+ void (*drag)(void *user, int dx, int dy))
+{
+ over->drag = drag;
+}
+
+
+void overlay_text_raw(struct overlay *over, const char *s)
+{
+ free((char *) over->s);
+ over->s = stralloc(s);
+}
+
+
+void overlay_text(struct overlay *over, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ overlay_text_raw(over, vfmt_pango(fmt, ap));
+ va_end(ap);
+}
+
+
+static void overlay_free(struct overlay *over)
+{
+ if (over->aoi)
+ aoi_remove(over->aois, over->aoi);
+ free((void *) over->s);
+ free(over);
+}
+
+
+void overlay_remove(struct overlay **overlays, struct overlay *over)
+{
+ if (over->next)
+ over->next->prev = over->prev;
+ if (over->prev)
+ over->prev->next = over->next;
+ else
+ *overlays = over->next;
+ overlay_free(over);
+}
+
+
+void overlay_remove_all(struct overlay **overlays)
+{
+ struct overlay *next;
+
+ while (*overlays) {
+ next = (*overlays)->next;
+ overlay_free(*overlays);
+ *overlays = next;
+ }
+}
diff --git a/gui/over.h b/gui/over.h
new file mode 100644
index 0000000..f340282
--- /dev/null
+++ b/gui/over.h
@@ -0,0 +1,58 @@
+/*
+ * gui/over.h - GUI: overlays
+ *
+ * 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_OVER_H
+#define GUI_OVER_H
+
+#include <stdbool.h>
+#include <stdint.h>
+
+#include <cairo/cairo.h>
+
+#include "gui/aoi.h"
+
+
+struct color {
+ double r, g, b, alpha;
+};
+
+
+struct overlay_style {
+ const char *font;
+ unsigned wmin, wmax;
+ unsigned radius;
+ unsigned pad; /* in x and y direction; adjust for radius ! */
+ unsigned skip; /* should be list-specific */
+ struct color fg;
+ struct color bg;
+ struct color frame;
+ double width;
+};
+
+struct overlay;
+
+
+void overlay_draw_all_d(struct overlay *overlays, cairo_t *cr,
+ unsigned x, unsigned y, int dx, int dy);
+void overlay_draw_all(struct overlay *overlays, cairo_t *cr, int x, int y);
+
+struct overlay *overlay_add(struct overlay **overlays, struct aoi **aois,
+ bool (*hover)(void *user, bool on), void (*click)(void *user), void *user);
+void overlay_text_raw(struct overlay *over, const char *s);
+void overlay_text(struct overlay *over, const char *fmt, ...);
+void overlay_style(struct overlay *over, const struct overlay_style *style);
+void overlay_draggable(struct overlay *over,
+ void (*drag)(void *user, int dx, int dy));
+void overlay_remove(struct overlay **overlays, struct overlay *over);
+void overlay_remove_all(struct overlay **overlays);
+
+#endif /* !GUI_OVER_H */
diff --git a/gui/style.c b/gui/style.c
new file mode 100644
index 0000000..db40ca3
--- /dev/null
+++ b/gui/style.c
@@ -0,0 +1,78 @@
+/*
+ * gui/style.c - GUI: overlay styles
+ *
+ * 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 "gui/style.h"
+
+
+#define OVER_BORDER 8
+#define OVER_RADIUS 6
+#define OVER_SEP 8
+
+#define NORMAL_PAD 8
+#define NORMAL_RADIUS 6
+#define NORMAL_SKIP 8
+#define NORMAL_WIDTH 2
+
+#define DENSE_PAD 4
+#define DENSE_RADIUS 3
+#define DENSE_SKIP 5
+#define DENSE_WIDTH 1
+
+#define BG_STANDARD { 0.8, 0.9, 1.0, 0.8 }
+#define FG_STANDARD { 0.0, 0.0, 0.0, 1.0 }
+#define FRAME_STANDARD { 0.5, 0.5, 1.0, 0.7 }
+
+#define BG_DIFF_NEW BG_STANDARD
+#define FG_DIFF_NEW { 0.0, 0.6, 0.0, 1.0 }
+#define FRAME_DIFF_NEW FRAME_STANDARD
+
+#define BG_DIFF_OLD BG_STANDARD
+#define FG_DIFF_OLD { 0.8, 0.0, 0.0, 1.0 }
+#define FRAME_DIFF_OLD FRAME_STANDARD
+
+
+#define BOX_ATTRS(style) \
+ .pad = style##_PAD, \
+ .radius = style##_RADIUS, \
+ .skip = style##_SKIP, \
+ .width = style##_WIDTH
+
+#define NORMAL BOX_ATTRS(NORMAL)
+#define DENSE BOX_ATTRS(DENSE)
+
+#define COLOR_ATTRS(style) \
+ .bg = BG_##style, \
+ .fg = FG_##style, \
+ .frame = FRAME_##style
+
+#define STANDARD COLOR_ATTRS(STANDARD)
+#define DIFF_NEW COLOR_ATTRS(DIFF_NEW)
+#define DIFF_OLD COLOR_ATTRS(DIFF_OLD)
+
+
+struct overlay_style overlay_style_default = {
+ .font = NORMAL_FONT,
+ NORMAL,
+ STANDARD,
+}, overlay_style_dense = {
+ .font = NORMAL_FONT,
+ DENSE,
+ STANDARD,
+}, overlay_style_diff_new = {
+ .font = NORMAL_FONT,
+ NORMAL,
+ DIFF_NEW,
+}, overlay_style_diff_old = {
+ .font = NORMAL_FONT,
+ NORMAL,
+ DIFF_OLD,
+};
diff --git a/gui/style.h b/gui/style.h
new file mode 100644
index 0000000..a5e6d4d
--- /dev/null
+++ b/gui/style.h
@@ -0,0 +1,35 @@
+/*
+ * gui/style.h - GUI: overlay styles
+ *
+ * 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_STYLE_H
+#define GUI_STYLE_H
+
+#include "gui/over.h"
+
+
+#define NORMAL_FONT "Helvetica 10"
+#define BOLD_FONT "Helvetica Bold 10"
+
+#define FRAME_SEL_ONLY 0.0, 0.0, 0.0, 0.9
+#define FRAME_SEL_OLD 0.8, 0.2, 0.2, 0.9
+#define FRAME_SEL_NEW 0.0, 0.6, 0.0, 0.9
+
+#define BG_NEW 0.6, 1.0, 0.6, 0.8
+#define BG_OLD 1.0, 0.8, 0.8, 0.8
+
+
+extern struct overlay_style overlay_style_default;
+extern struct overlay_style overlay_style_dense;
+extern struct overlay_style overlay_style_diff_new;
+extern struct overlay_style overlay_style_diff_old;
+
+#endif /* !GUI_STYLE_H */