Root/eeshow/gui/gui.c

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

Archive Download this file

Branches:
master



interactive