Root/eeshow/gui/gui.c

Source at commit 1b250bd46737db7133fe9314be6baa3edab3d5c7 created 7 years 6 months ago.
By Werner Almesberger, eeshow/: change file name handling from positional to be extension-driven
1/*
2 * gui/gui.c - GUI for eeshow
3 *
4 * Written 2016 by Werner Almesberger
5 * Copyright 2016 by Werner Almesberger
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 */
12
13/*
14 * Resources:
15 *
16 * http://zetcode.com/gfx/cairo/cairobackends/
17 * https://developer.gnome.org/gtk3/stable/gtk-migrating-2-to-3.html
18 */
19
20#define _GNU_SOURCE /* for asprintf */
21#include <stddef.h>
22#include <stdbool.h>
23#include <stdlib.h>
24#include <stdio.h>
25#include <string.h>
26
27#include <gtk/gtk.h>
28
29#include "version.h"
30#include "misc/util.h"
31#include "misc/diag.h"
32#include "file/git-hist.h"
33#include "kicad/ext.h"
34#include "kicad/pl.h"
35#include "kicad/lib.h"
36#include "kicad/sch.h"
37#include "kicad/delta.h"
38#include "gui/aoi.h"
39#include "gui/input.h"
40#include "gui/common.h"
41#include "gui/icons.h"
42#include "gui/gui.h"
43
44
45/* ----- Helper functions -------------------------------------------------- */
46
47
48struct gui_sheet *find_corresponding_sheet(struct gui_sheet *pick_from,
49     struct gui_sheet *ref_in, const struct gui_sheet *ref)
50{
51    struct gui_sheet *sheet, *plan_b;
52    const char *title = ref->sch->title;
53
54    /* plan A: try to find sheet with same name */
55
56    if (title)
57        for (sheet = pick_from; sheet; sheet = sheet->next)
58            if (sheet->sch->title &&
59                !strcmp(title, sheet->sch->title))
60                return sheet;
61
62    /* plan B: use sheet in same position in sheet sequence */
63
64    plan_b = ref_in;
65    for (sheet = pick_from; sheet; sheet = sheet->next) {
66        if (plan_b == ref)
67            return sheet;
68        plan_b = plan_b->next;
69    }
70
71    /* plan C: just go to the top */
72    return pick_from;
73}
74
75
76/* ----- AoIs -------------------------------------------------------------- */
77
78
79struct sheet_aoi_ctx {
80    struct gui_ctx *gui_ctx;
81    const struct sch_obj *obj;
82};
83
84
85static void select_subsheet(void *user)
86{
87    const struct sheet_aoi_ctx *aoi_ctx = user;
88    struct gui_ctx *ctx = aoi_ctx->gui_ctx;
89    const struct sch_obj *obj = aoi_ctx->obj;
90    struct gui_sheet *sheet;
91
92    if (!obj->u.sheet.sheet)
93        return;
94
95    if (!ctx->old_hist || ctx->diff_mode != diff_old) {
96        for (sheet = ctx->new_hist->sheets; sheet; sheet = sheet->next)
97            if (sheet->sch == obj->u.sheet.sheet) {
98                go_to_sheet(ctx, sheet);
99                return;
100            }
101        abort();
102    }
103
104    for (sheet = ctx->old_hist->sheets; sheet; sheet = sheet->next)
105        if (sheet->sch == obj->u.sheet.sheet)
106            goto found;
107    abort();
108
109found:
110    sheet = find_corresponding_sheet(ctx->new_hist->sheets,
111                    ctx->old_hist->sheets, sheet);
112    go_to_sheet(ctx, sheet);
113}
114
115
116static void add_sheet_aoi(struct gui_ctx *ctx, struct gui_sheet *parent,
117    const struct sch_obj *obj)
118{
119    struct sheet_aoi_ctx *aoi_ctx = alloc_type(struct sheet_aoi_ctx);
120
121    aoi_ctx->gui_ctx = ctx;
122    aoi_ctx->obj = obj;
123
124    struct aoi aoi = {
125        .x = obj->x,
126        .y = obj->y,
127        .w = obj->u.sheet.w,
128        .h = obj->u.sheet.h,
129        .click = select_subsheet,
130        .user = aoi_ctx,
131    };
132
133    aoi_add(&parent->aois, &aoi);
134}
135
136
137/* ----- Load revisions ---------------------------------------------------- */
138
139
140void mark_aois(struct gui_ctx *ctx, struct gui_sheet *sheet)
141{
142    const struct sch_obj *obj;
143
144    sheet->aois = NULL;
145    for (obj = sheet->sch->objs; obj; obj = obj->next)
146        switch (obj->type) {
147        case sch_obj_sheet:
148            add_sheet_aoi(ctx, sheet, obj);
149            break;
150        case sch_obj_glabel:
151            add_glabel_aoi(sheet, obj);
152        default:
153            break;
154        }
155}
156
157
158static struct gui_sheet *get_sheets(struct gui_ctx *ctx, struct gui_hist *hist,
159    const struct sheet *sheets)
160{
161    const struct sheet *sheet;
162    struct gui_sheet *gui_sheets = NULL;
163    struct gui_sheet **next = &gui_sheets;
164    struct gui_sheet *new;
165
166    for (sheet = sheets; sheet; sheet = sheet->next) {
167        new = alloc_type(struct gui_sheet);
168        new->sch = sheet;
169        new->ctx = ctx;
170        new->hist = hist;
171        new->rendered = 0;
172
173        *next = new;
174        next = &new->next;
175    }
176    *next = NULL;
177    return gui_sheets;
178}
179
180
181/*
182 * Library caching:
183 *
184 * We reuse previous components if all libraries are identical
185 *
186 * Future optimizations:
187 * - don't parse into single list of components, so that we can share
188 * libraries that are the same, even if there are others that have changed.
189 * - maybe put components into tree, so that they can be replaced individually
190 * (this would also help to identify sheets that don't need parsing)
191 *
192 * Sheet caching:
193 *
194 * We reuse previous sheets if
195 * - all libraries are identical (whether a given sheet uses them or not),
196 * - they have no sub-sheets, and
197 * - the objects IDs (hashes) are identical.
198 *
199 * Note that we only compare with the immediately preceding (newer) revision,
200 * so branches and merges can disrupt caching.
201 *
202 * Possible optimizations:
203 * - if we record which child sheets a sheet has, we could also clone it,
204 * without having to parse it. However, this is somewhat complex and may
205 * not save all that much time.
206 * - we could record what libraries a sheet uses, and parse only if one of
207 * these has changed (benefits scenarios with many library files),
208 * - we could record what components a sheet uses, and parse only if one of
209 * these has changed (benefits scenarios with few big libraries),
210 * - we could postpone library lookups to render time.
211 * - we could record IDs globally, which would help to avoid tripping over
212 * branches and merges.
213 */
214
215static const struct sheet *parse_files(struct gui_hist *hist,
216    const struct file_names *fn, bool recurse, struct gui_hist *prev)
217{
218    char *rev = NULL;
219    struct file sch_file;
220    struct file lib_files[fn->n_libs];
221    struct file pl_file;
222    unsigned libs_open, i;
223    bool libs_cached = 0;
224    bool ok;
225
226    if (hist->vcs_hist && hist->vcs_hist->commit)
227        rev = vcs_git_get_rev(hist->vcs_hist);
228
229    sch_init(&hist->sch_ctx, recurse);
230    ok = file_open_revision(&sch_file, rev, fn->sch, NULL);
231
232    if (rev)
233        free(rev);
234    if (!ok) {
235        sch_free(&hist->sch_ctx);
236        return NULL;
237    }
238
239    lib_init(&hist->lib);
240    for (libs_open = 0; libs_open != fn->n_libs; libs_open++)
241        if (!file_open(lib_files + libs_open, fn->libs[libs_open],
242            &sch_file))
243            goto fail;
244
245    if (fn->pl) {
246        if (!file_open(&pl_file, fn->pl, &sch_file))
247            goto fail;
248         hist->pl = pl_parse(&pl_file);
249        file_close(&pl_file);
250        /*
251         * We treat failing to parse the page layout as a "minor"
252         * failure and don't reject the revision just because of it.
253         */
254    }
255
256    if (hist->vcs_hist) {
257        hist->oids = alloc_type_n(void *, libs_open);
258        hist->libs_open = libs_open;
259        for (i = 0; i != libs_open; i++)
260            hist->oids[i] = file_oid(lib_files + i);
261        if (prev && prev->vcs_hist && prev->libs_open == libs_open) {
262            for (i = 0; i != libs_open; i++)
263                if (!file_oid_eq(hist->oids[i], prev->oids[i]))
264                    break;
265            if (i == libs_open) {
266                hist->lib.comps = prev->lib.comps;
267                libs_cached = 1;
268            }
269        }
270    }
271
272    if (!libs_cached)
273        for (i = 0; i != libs_open; i++)
274            if (!lib_parse_file(&hist->lib, lib_files +i))
275                goto fail;
276
277    if (!sch_parse(&hist->sch_ctx, &sch_file, &hist->lib,
278        libs_cached ? &prev->sch_ctx : NULL))
279        goto fail;
280
281    for (i = 0; i != libs_open; i++)
282        file_close(lib_files + i);
283    file_close(&sch_file);
284
285    if (prev && sheet_eq(prev->sch_ctx.sheets, hist->sch_ctx.sheets))
286        prev->identical = 1;
287
288    /*
289     * @@@ we have a major memory leak for the component library.
290     * We should record parsed schematics and libraries separately, so
291     * that we can clean them up, without having to rely on the history,
292     * with - when sharing unchanged item - possibly many duplicate
293     * pointers.
294     */
295    return hist->sch_ctx.sheets;
296
297fail:
298    while (libs_open--)
299        file_close(lib_files + libs_open);
300    sch_free(&hist->sch_ctx);
301    lib_free(&hist->lib);
302    file_close(&sch_file);
303    return NULL;
304}
305
306
307struct add_hist_ctx {
308    struct gui_ctx *ctx;
309    const struct file_names *fn;
310    bool recurse;
311    unsigned limit;
312};
313
314
315static void add_hist(void *user, struct hist *h)
316{
317    struct add_hist_ctx *ahc = user;
318    struct gui_ctx *ctx = ahc->ctx;
319    struct gui_hist **anchor, *hist, *prev;
320    const struct sheet *sch;
321    unsigned age = 0;
322
323    if (!ahc->limit)
324        return;
325    if (ahc->limit > 0)
326        ahc->limit--;
327
328    prev = NULL;
329    for (anchor = &ctx->hist; *anchor; anchor = &(*anchor)->next) {
330        prev = *anchor;
331        age++;
332    }
333
334    hist = alloc_type(struct gui_hist);
335    hist->ctx = ctx;
336    hist->vcs_hist = h;
337    hist->libs_open = 0;
338    hist->identical = 0;
339    hist->pl = NULL;
340    sch = parse_files(hist, ahc->fn, ahc->recurse, prev);
341    hist->sheets = sch ? get_sheets(ctx, hist, sch) : NULL;
342    hist->age = age;
343
344    hist->next = NULL;
345    *anchor = hist;
346
347    if (ctx->hist_size)
348        progress_update(ctx);
349}
350
351
352static void get_revisions(struct gui_ctx *ctx, const struct file_names *fn,
353    bool recurse, int limit)
354{
355    struct add_hist_ctx add_hist_ctx = {
356        .ctx = ctx,
357        .fn = fn,
358        .recurse = recurse,
359        .limit = limit ? limit < 0 ? -limit : limit : -1,
360    };
361
362    if (ctx->vcs_hist)
363        hist_iterate(ctx->vcs_hist, add_hist, &add_hist_ctx);
364    else
365        add_hist(&add_hist_ctx, NULL);
366}
367
368
369/* ----- Retrieve and count history ---------------------------------------- */
370
371
372static void count_history(void *user, struct hist *h)
373{
374    struct gui_ctx *ctx = user;
375
376    ctx->hist_size++;
377}
378
379
380static void get_history(struct gui_ctx *ctx, const char *sch_name, int limit)
381{
382    if (!vcs_git_try(sch_name)) {
383        ctx->vcs_hist = NULL;
384        return;
385    }
386    
387    ctx->vcs_hist = vcs_git_hist(sch_name);
388    if (limit)
389        ctx->hist_size = limit > 0 ? limit : -limit;
390    else
391        hist_iterate(ctx->vcs_hist, count_history, ctx);
392}
393
394
395/* ----- Initialization ---------------------------------------------------- */
396
397
398int gui(const struct file_names *fn, bool recurse, int limit)
399{
400    GtkWidget *window;
401    char *title;
402    struct gui_ctx ctx = {
403        .scale = 1 / 16.0,
404        .hist = NULL,
405        .vcs_hist = NULL,
406        .showing_history= 0,
407        .sheet_overlays = NULL,
408        .hist_overlays = NULL,
409        .pop_overlays = NULL,
410        .pop_underlays = NULL,
411        .pop_origin = NULL,
412        .glabel = NULL,
413        .aois = NULL,
414        .diff_mode = diff_delta,
415        .old_hist = NULL,
416        .hist_y_offset = 0,
417        .hist_size = 0,
418    };
419
420    window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
421    ctx.da = gtk_drawing_area_new();
422    gtk_container_add(GTK_CONTAINER(window), ctx.da);
423
424    gtk_window_set_default_size(GTK_WINDOW(window), 640, 480);
425    if (asprintf(&title, "eeshow (rev %s)", version)) {};
426    gtk_window_set_title(GTK_WINDOW(window), title);
427
428    gtk_widget_set_events(ctx.da,
429        GDK_EXPOSE | GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK);
430
431    input_setup(ctx.da);
432
433    gtk_widget_show_all(window);
434
435    get_history(&ctx, fn->sch, limit);
436    if (ctx.hist_size)
437        setup_progress_bar(&ctx, window);
438
439    get_revisions(&ctx, fn, recurse, limit);
440    for (ctx.new_hist = ctx.hist; ctx.new_hist && !ctx.new_hist->sheets;
441        ctx.new_hist = ctx.new_hist->next);
442    if (!ctx.new_hist)
443        fatal("no valid sheets\n");
444
445    g_signal_connect(window, "destroy",
446        G_CALLBACK(gtk_main_quit), NULL);
447
448    icons_init();
449    sheet_setup(&ctx);
450    render_setup(&ctx);
451
452// gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
453
454    go_to_sheet(&ctx, ctx.new_hist->sheets);
455    gtk_widget_show_all(window);
456
457    /* for performance testing, use -N-depth */
458    if (limit >= 0)
459        gtk_main();
460
461    return 0;
462}
463

Archive Download this file

Branches:
master



interactive