Root/eeshow/gui/history.c

1/*
2 * gui/history.c - Revision history (navigation)
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#include <stddef.h>
14#include <stdbool.h>
15#include <stdlib.h>
16
17#include <gtk/gtk.h>
18#include <pango/pangocairo.h>
19
20#include "misc/util.h"
21#include "file/git-hist.h"
22#include "gui/fmt-pango.h"
23#include "gui/style.h"
24#include "gui/aoi.h"
25#include "gui/over.h"
26#include "gui/input.h"
27#include "gui/common.h"
28
29
30static void hide_history(struct gui_ctx *ctx)
31{
32    input_pop();
33
34    ctx->showing_history = 0;
35    do_revision_overlays(ctx);
36    redraw(ctx);
37}
38
39
40static void set_history_style(struct gui_hist *h, bool current)
41{
42    struct gui_ctx *ctx = h->ctx;
43    struct overlay_style style = overlay_style_dense;
44    const struct gui_hist *new = ctx->new_hist;
45    const struct gui_hist *old = ctx->old_hist;
46
47    /* this is in addition to showing detailed content */
48    if (current)
49        style.width++;
50
51    switch (ctx->selecting) {
52    case sel_only:
53    case sel_split:
54        style.frame = COLOR(FRAME_SEL_ONLY);
55        break;
56    case sel_old:
57        style.frame = COLOR(FRAME_SEL_OLD);
58        break;
59    case sel_new:
60        style.frame = COLOR(FRAME_SEL_NEW);
61        break;
62    default:
63        abort();
64    }
65
66    if (ctx->new_hist == h || ctx->old_hist == h) {
67        style.width++;
68        style.font = BOLD_FONT;
69    }
70    if (ctx->old_hist) {
71        if (h == new)
72            style.bg = COLOR(BG_NEW);
73        if (h == old)
74            style.bg = COLOR(BG_OLD);
75    }
76
77    if (h->identical)
78        style.fg = RGBA(0.5, 0.5, 0.5, 1);
79    if (!h->sheets)
80        style.fg = RGBA(0.7, 0.0, 0.0, 1);
81
82    overlay_style(h->over, &style);
83}
84
85
86/*
87 * One difficulty with resizing (enlarging, in this case) list items on hover
88 * is that, if we only change the size but not the position, hovering towards
89 * the next item will cause the previous item to shrink and thus move the next
90 * item up - possibly even above the mouse pointer. This can be confusing.
91 *
92 * We could adjust the mouse pointer, but manipulating the pointer position is
93 * not universally popular.
94 *
95 * Instead, we move the list such that the bottom edge of the item we're
96 * leaving remains stationary. Thus the list moves down when mousing over items
97 * from the top towards the bottom.
98 *
99 * To prevent this movement from being overly pronounced, we try to predict the
100 * direction in which an item will be left (i.e., in the same direction from
101 * which it was entered), and compensate for the likely list movement on
102 * departure on entry.
103 *
104 * These heuristics can still sometimes fail, but by and large, they produce
105 * the desired result without introducing too much list movement.
106 */
107
108static bool hover_history(void *user, bool on, int dx, int dy)
109{
110    struct gui_hist *h = user;
111    struct gui_ctx *ctx = h->ctx;
112    char *s;
113    int before, after;
114
115    if (dy)
116        overlay_size(h->over, gtk_widget_get_pango_context(ctx->da),
117            NULL, &before);
118    if (on) {
119        s = vcs_git_long_for_pango(h->vcs_hist, fmt_pango);
120        overlay_text_raw(h->over, s);
121        free(s);
122    } else {
123        overlay_text(h->over, "<small>%s</small>",
124            vcs_git_summary(h->vcs_hist));
125    }
126    set_history_style(h, on);
127    if (dy)
128        overlay_size(h->over, gtk_widget_get_pango_context(ctx->da),
129            NULL, &after);
130
131    if (dy < 0 && on)
132        ctx->hist_y_offset -= after - before;
133    if (dy > 0 && !on)
134        ctx->hist_y_offset -= after - before;
135
136    redraw(ctx);
137    return 1;
138}
139
140
141static void click_history(void *user)
142{
143    struct gui_hist *h = user;
144    struct gui_ctx *ctx = h->ctx;
145    struct gui_sheet *sheet, *old_sheet;
146
147    hide_history(ctx);
148
149    if (!h->sheets)
150        return;
151
152    sheet = find_corresponding_sheet(h->sheets,
153        ctx->new_hist->sheets, ctx->curr_sheet);
154    old_sheet = find_corresponding_sheet(
155        ctx->old_hist ? ctx->old_hist->sheets : ctx->new_hist->sheets,
156        ctx->new_hist->sheets, ctx->curr_sheet);
157
158    switch (ctx->selecting) {
159    case sel_only:
160        ctx->new_hist = h;
161        break;
162    case sel_split:
163        ctx->old_hist = ctx->new_hist;
164        ctx->new_hist = h;
165        break;
166    case sel_new:
167        ctx->new_hist = h;
168        break;
169    case sel_old:
170        ctx->old_hist = h;
171        break;
172    default:
173        abort();
174    }
175
176    ctx->diff_mode = diff_delta;
177
178    if (ctx->old_hist) {
179        if (ctx->new_hist->age > ctx->old_hist->age) {
180            swap(ctx->new_hist, ctx->old_hist);
181            if (ctx->selecting == sel_old) {
182                go_to_sheet(ctx, sheet);
183            } else {
184                go_to_sheet(ctx, old_sheet);
185                render_delta(ctx);
186            }
187        } else {
188            if (ctx->selecting != sel_old)
189                go_to_sheet(ctx, sheet);
190            else
191                render_delta(ctx);
192        }
193    } else {
194        go_to_sheet(ctx, sheet);
195    }
196
197    if (ctx->old_hist == ctx->new_hist)
198        ctx->old_hist = NULL;
199
200    do_revision_overlays(ctx);
201    redraw(ctx);
202}
203
204
205static void ignore_click(void *user)
206{
207}
208
209
210static struct gui_hist *skip_history(struct gui_ctx *ctx, struct gui_hist *h)
211{
212    struct overlay_style style = overlay_style_dense;
213    unsigned n;
214
215    /* don't skip the first entry */
216    if (h == ctx->hist)
217        return h;
218
219    /* need at least two entries */
220    if (!h->identical || !h->next || !h->next->identical)
221        return h;
222
223    /* don't skip the last entry */
224    for (n = 0; h->next && h->identical; h = h->next)
225        n++;
226
227    h->over = overlay_add(&ctx->hist_overlays, &ctx->aois,
228        NULL, ignore_click, h);
229    overlay_text(h->over, "<small>%u commits without changes</small>", n);
230
231    style.width = 0;
232    style.pad = 0;
233    style.bg = RGBA(1.0, 1.0, 1.0, 0.8);
234    overlay_style(h->over, &style);
235
236    return h;
237}
238
239
240/* ----- Input ------------------------------------------------------------- */
241
242
243static bool history_click(void *user, int x, int y)
244{
245    struct gui_ctx *ctx = user;
246
247    if (aoi_click(&ctx->aois, x, y))
248        return 1;
249    hide_history(ctx);
250    return 1;
251}
252
253
254static bool history_hover_update(void *user, int x, int y)
255{
256    struct gui_ctx *ctx = user;
257
258    return aoi_hover(&ctx->aois, x, y);
259}
260
261
262static void history_drag_move(void *user, int dx, int dy)
263{
264    struct gui_ctx *ctx = user;
265
266    ctx->hist_y_offset += dy;
267    redraw(ctx);
268}
269
270
271static void history_key(void *user, int x, int y, int keyval)
272{
273    struct gui_ctx *ctx = user;
274
275    switch (keyval) {
276    case GDK_KEY_Escape:
277        hide_history(ctx);
278        break;
279    case GDK_KEY_q:
280        gtk_main_quit();
281    }
282}
283
284
285static const struct input_ops history_input_ops = {
286    .click = history_click,
287    .hover_begin = history_hover_update,
288    .hover_update = history_hover_update,
289    .hover_click = history_click,
290    .drag_begin = input_accept,
291    .drag_move = history_drag_move,
292    .key = history_key,
293};
294
295
296/* ----- Invocation -------------------------------------------------------- */
297
298
299void show_history(struct gui_ctx *ctx, enum selecting sel)
300{
301    struct gui_hist *h = ctx->hist;
302
303    input_push(&history_input_ops, ctx);
304
305    ctx->showing_history = 1;
306    ctx->hist_y_offset = 0;
307    ctx->selecting = sel;
308    overlay_remove_all(&ctx->hist_overlays);
309    for (h = ctx->hist; h; h = h->next) {
310        h = skip_history(ctx, h);
311        h->over = overlay_add(&ctx->hist_overlays, &ctx->aois,
312            hover_history, click_history, h);
313        set_history_style(h, 0);
314        hover_history(h, 0, 0, 0);
315    }
316    redraw(ctx);
317}
318

Archive Download this file

Branches:
master



interactive