1
0
mirror of git://projects.qi-hardware.com/eda-tools.git synced 2024-11-22 15:10:38 +02:00
eda-tools/eeshow/gui/gui.c

490 lines
11 KiB
C

/*
* 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 <gtk/gtk.h>
#include "version.h"
#include "misc/util.h"
#include "misc/diag.h"
#include "file/git-hist.h"
#include "kicad/ext.h"
#include "kicad/pl.h"
#include "kicad/lib.h"
#include "kicad/sch.h"
#include "kicad/pro.h"
#include "kicad/delta.h"
#include "gui/aoi.h"
#include "gui/input.h"
#include "gui/common.h"
#include "gui/icons.h"
#include "gui/gui.h"
/* ----- Helper functions -------------------------------------------------- */
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;
}
/* ----- AoIs -------------------------------------------------------------- */
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;
if (!ctx->old_hist || ctx->diff_mode != diff_old) {
for (sheet = ctx->new_hist->sheets; sheet; sheet = sheet->next)
if (sheet->sch == obj->u.sheet.sheet) {
go_to_sheet(ctx, sheet);
return;
}
abort();
}
for (sheet = ctx->old_hist->sheets; sheet; sheet = sheet->next)
if (sheet->sch == obj->u.sheet.sheet)
goto found;
abort();
found:
sheet = find_corresponding_sheet(ctx->new_hist->sheets,
ctx->old_hist->sheets, sheet);
go_to_sheet(ctx, sheet);
}
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);
}
/* ----- Load revisions ---------------------------------------------------- */
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, struct gui_hist *hist,
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->hist = hist;
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,
const struct file_names *fn, bool recurse, struct gui_hist *prev)
{
char *rev = NULL;
struct file pro_file, sch_file;
struct file pl_file;
const struct file *leader = NULL;
unsigned 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);
if (fn->pro) {
if (file_open_revision(&pro_file, rev, fn->pro, NULL)) {
free(rev);
rev = NULL; /* thus sch_file opens as with file_open */
fn = pro_parse_file(&pro_file, fn);
if (!fn)
return NULL;
leader = &pro_file;
} else {
/*
* If we happen to have a top sheet name, we may as
* well try to use it.
*/
if (!fn->sch) {
free(rev);
return NULL;
}
}
}
sch_init(&hist->sch_ctx, recurse);
ok = file_open_revision(&sch_file, rev, fn->sch, leader);
free(rev);
if (!ok) {
sch_free(&hist->sch_ctx);
return NULL;
}
if (!leader)
leader = &sch_file;
struct file lib_files[fn->n_libs];
lib_init(&hist->lib);
for (libs_open = 0; libs_open != fn->n_libs; libs_open++)
if (!file_open(lib_files + libs_open, fn->libs[libs_open],
leader))
goto fail;
if (fn->pl) {
if (!file_open(&pl_file, fn->pl, leader))
goto fail;
hist->pl = pl_parse(&pl_file);
file_close(&pl_file);
/*
* We treat failing to parse the page layout as a "minor"
* failure and don't reject the revision just because of it.
*/
}
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);
// @@@ close pro_file
if (prev && prev->sheets &&
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);
// @@@ close pro_file
return NULL;
}
struct add_hist_ctx {
struct gui_ctx *ctx;
const struct file_names *fn;
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->libs_open = 0;
hist->identical = 0;
hist->pl = NULL;
sch = parse_files(hist, ahc->fn, ahc->recurse, prev);
hist->sheets = sch ? get_sheets(ctx, hist, sch) : NULL;
hist->age = age;
hist->next = NULL;
*anchor = hist;
if (ctx->hist_size)
progress_update(ctx);
}
static void get_revisions(struct gui_ctx *ctx, const struct file_names *fn,
bool recurse, int limit)
{
struct add_hist_ctx add_hist_ctx = {
.ctx = ctx,
.fn = fn,
.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);
}
/* ----- Retrieve and count history ---------------------------------------- */
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);
}
/* ----- Initialization ---------------------------------------------------- */
int gui(const struct file_names *fn, bool recurse, int limit)
{
GtkWidget *window;
char *title;
struct gui_ctx ctx = {
.scale = 1 / 16.0,
.hist = NULL,
.vcs_hist = NULL,
.showing_history= 0,
.sheet_overlays = NULL,
.hist_overlays = NULL,
.pop_overlays = NULL,
.pop_underlays = NULL,
.pop_origin = NULL,
.glabel = NULL,
.aois = NULL,
.diff_mode = diff_delta,
.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);
if (asprintf(&title, "eeshow (rev %s)", version)) {};
gtk_window_set_title(GTK_WINDOW(window), title);
gtk_widget_set_events(ctx.da,
GDK_EXPOSE | GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK);
input_setup(ctx.da);
gtk_widget_show_all(window);
get_history(&ctx, fn->pro ? fn->pro : fn->sch, limit);
if (ctx.hist_size)
setup_progress_bar(&ctx, window);
get_revisions(&ctx, fn, 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(window, "destroy",
G_CALLBACK(gtk_main_quit), NULL);
icons_init();
sheet_setup(&ctx);
render_setup(&ctx);
// 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;
}