]> git.draconx.ca Git - rrace.git/blob - src/xcounter.c
e8e5d17baa2ad76c532e0436dd6c8dc78990f320
[rrace.git] / src / xcounter.c
1 /*
2  * Helpers for implementing a rapid-update counter display in Motif.
3  * Copyright © 2022 Nick Bowler
4  *
5  * This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
17  */
18
19 #include <config.h>
20 #include <string.h>
21 #include <inttypes.h>
22 #include <assert.h>
23 #include <Xm/XmAll.h>
24
25 /* Define to 1 to add highlights and delays for visually debugging redraws */
26 #if X11_RENDER_DEBUG
27 #  include <unistd.h>
28 #endif
29
30 #include "xcounter.h"
31
32 #define MAX_STRINGS 20
33 #define DIGIT_LIST "0123456789"
34 #define MARGIN 2
35
36 #define MIN(a, b) ((a) < (b) ? (a) : (b))
37 #define MAX(a, b) ((a) > (b) ? (a) : (b))
38
39 /* render flags in the upper bits are ORed into render_mask */
40 #define XC_TICK_INSTALLED (1ul << 31)
41 #define XC_PROC_INSTALLED (1ul << 30)
42 #define XC_RESIZING       (1ul << 29)
43
44 struct xcounter {
45         Widget widget;
46         GC gc;
47
48         Pixmap pixmap;
49
50         uint_least32_t render_mask;
51         unsigned char seq[23], num_strings;
52
53         /*
54          * xpos[n] is the x position of the nth string image within the
55          * pixmap, plus the width of that image.
56          */
57         uint_least16_t xpos[FLEXIBLE_ARRAY_MEMBER];
58 };
59 #define XC_SIZE(n) offsetof(struct xcounter, xpos[n])
60
61 static struct xcounter *
62 xc_alloc(Widget w, XmRenderTable rt, XmString *strings, int nstr)
63 {
64         Display *display = XtDisplay(w);
65         Screen *screen = XtScreen(w);
66         unsigned long fg, bg;
67
68         Drawable root = RootWindowOfScreen(screen);
69         unsigned depth = DefaultDepthOfScreen(screen);
70         unsigned wsum = 0, height = 0;
71         struct xcounter *xc;
72         int i;
73
74         assert(nstr >= 0 && nstr < MAX_STRINGS);
75         xc = (void *)XtMalloc(XC_SIZE(nstr));
76         xc->gc = XtAllocateGC(w, 0, 0, 0, GCForeground, GCBackground);
77         memset(xc->seq, -1, sizeof xc->seq);
78         xc->num_strings = nstr;
79         xc->render_mask = 0;
80         xc->widget = w;
81
82         for (i = 0; i < nstr; i++) {
83                 unsigned w, h;
84
85                 if ((h = XmStringHeight(rt, strings[i])) > height)
86                         height = h + 2*MARGIN;
87
88                 wsum += XmStringWidth(rt, strings[i]);
89                 xc->xpos[i] = wsum;
90         }
91
92         XtVaSetValues(w, XmNheight, height + 2*MARGIN, (char *)NULL);
93         XtVaGetValues(w, XmNforeground, &fg, XmNbackground, &bg, (char *)NULL);
94
95         xc->pixmap = XCreatePixmap(display, root, wsum, height, depth);
96         XSetForeground(display, xc->gc, bg);
97         XFillRectangle(display, xc->pixmap, xc->gc, 0, 0, wsum, height);
98
99         XSetForeground(display, xc->gc, fg);
100         for (i = 0; i < nstr; i++) {
101                 unsigned xpos = i ? xc->xpos[i-1] : 0;
102                 unsigned w = xc->xpos[i] - xpos;
103
104                 XmStringDraw(display, xc->pixmap, rt, strings[i], xc->gc,
105                              xpos, 0, w, XmALIGNMENT_BEGINNING,
106                              XmSTRING_DIRECTION_L_TO_R, NULL);
107         }
108
109         return xc;
110 }
111
112 static struct xcounter *xc_configure(Widget w, XmRenderTable rt, char *tpl)
113 {
114         XmString strings[MAX_STRINGS];
115         struct xcounter *xc;
116         int i, l;
117         char *s;
118
119         for (i = 0; i < 10; i++) {
120                 char s[2] = { '0' + i };
121
122                 strings[i] = XmStringCreateLocalized(s);
123         }
124
125         for (s = tpl; s[0];) {
126                 if ((l = strspn(s, DIGIT_LIST))) {
127                         s += l;
128                 }
129
130                 if ((l = strcspn(s, DIGIT_LIST))) {
131                         char tmp = s[l];
132
133                         s[l] = 0;
134                         strings[i++] = XmStringCreateLocalized(s);
135                         *(s += l) = tmp;
136                 }
137         }
138
139         xc = xc_alloc(w, rt, strings, i);
140
141         for (i--; i >= 0; i--) {
142                 XmStringFree(strings[i]);
143         }
144
145         return xc;
146 }
147
148 struct xcounter *xcounter_init(Widget w, char *template)
149 {
150         struct xcounter *xc;
151         XmRenderTable rt;
152         Widget dummy;
153
154         /* Use a dummy label widget's render table */
155         dummy = XmCreateLabelGadget(w, "text", NULL, 0);
156         XtVaGetValues(dummy, XmNrenderTable, &rt, (char *)NULL);
157
158         xc = xc_configure(w, rt, template);
159
160         XtDestroyWidget(dummy);
161
162         return xc;
163 }
164
165 /*
166  * Internal helper to imlpement the xc_foreach_seq macro.
167  */
168 static int xc_seq_iter(struct xcounter *xc, int i, unsigned *src_x,
169                                             unsigned *w, uint_fast32_t *mask)
170 {
171         int seq_i;
172
173         if ((seq_i = xc->seq[i]) == (unsigned char)-1)
174                 return 0;
175
176         *src_x = seq_i ? xc->xpos[seq_i-1] : 0;
177         *w = xc->xpos[seq_i] - *src_x;
178         *mask <<= 1;
179         return 1;
180 }
181
182 /*
183  * xc_foreach_seq(xc, int, unsigned, unsigned, unsigned, uint_fast32_t)
184  *
185  * Helper macro to iterate through the display sequence.  The first argument is
186  * a pointer to the state structure.  The remaining 5 arguments are lvalues of
187  * the given type designating objects that are updated on each iteration.
188  *
189  *   i     - index into the sequence
190  *   out_x - x position for drawing of this element
191  *   src_x - x position in the pixmap of this element
192  *   w     - width of this element
193  *   mask  - shifted left by 1 each iteration
194  */
195 #define xc_foreach_seq(xc, i, out_x, src_x, w, mask) for \
196         ( (out_x) = MARGIN, (i) = 0 \
197         ; (i) < sizeof (xc)->seq \
198           && xc_seq_iter(xc, i, &(src_x), &(w), &(mask)) \
199         ; (i)++, (out_x) += (w) )
200
201 /*
202  * Redraw the changed portion of the text.  The mask bitmap indicates which
203  * elements of the sequence to redraw, with bit 0 corresponding to the last
204  * possible element, bit 1 the second last, and so on.
205  */
206 static void xc_redraw(struct xcounter *xc, uint_fast32_t mask)
207 {
208         Display *display = XtDisplay(xc->widget);
209         Drawable d = XtWindow(xc->widget);
210
211         unsigned out_x = MARGIN, src_x, w;
212         Dimension max_w, h;
213         int i;
214
215         XtVaGetValues(xc->widget, XmNwidth, &max_w,
216                                   (char *)NULL);
217         max_w = MAX(MARGIN, max_w) - MARGIN;
218
219         xc_foreach_seq(xc, i, out_x, src_x, w, mask) {
220                 if (out_x + w > max_w) {
221                         w = max_w - out_x;
222                 }
223
224                 if (mask & (1ul << sizeof xc->seq)) {
225 #if X11_RENDER_DEBUG
226                         XRectangle r = { out_x, MARGIN, w, -1 };
227
228                         XSetForeground(display, xc->gc, 0xff0000);
229                         XFillRectangles(display, d, xc->gc, &r, 1);
230                         XFlush(display);
231                         usleep(18000);
232 #endif
233                         /*
234                          * NB: we want to copy the entire vertical contents
235                          * of the pixmap; using any large height value will
236                          * work but not too large otherwise there are visual
237                          * problems on Intel DDX (<=821722853 seems OK).
238                          */
239                         XCopyArea(display, xc->pixmap, d, xc->gc,
240                                   src_x, 0, w, 4096,
241                                   out_x, MARGIN);
242                 }
243         }
244
245         assert(out_x <= max_w);
246         XClearArea(display, d, out_x, 0, -1, -1, 0);
247 }
248
249 static Boolean xc_do_render(void *data)
250 {
251         struct xcounter *xc = data;
252
253         xc_redraw(xc, xc->render_mask);
254         xc->render_mask = 0;
255         return True;
256 }
257
258 static void xc_start_render(void *data, XtIntervalId *id)
259 {
260         struct xcounter *xc = data;
261         XtAppContext app;
262
263         if (xc->render_mask & XC_PROC_INSTALLED)
264                 return;
265
266         xc->render_mask |= XC_PROC_INSTALLED;
267         app = XtWidgetToApplicationContext(xc->widget);
268         XtAppAddWorkProc(app, xc_do_render, xc);
269 }
270
271 static void xc_queue_render(struct xcounter *xc, uint_fast32_t mask)
272 {
273         uint_fast32_t changed = xc->render_mask |= mask;
274         XtAppContext app;
275
276         if (changed & XC_TICK_INSTALLED || !changed)
277                 return;
278
279         xc->render_mask |= XC_TICK_INSTALLED;
280         app = XtWidgetToApplicationContext(xc->widget);
281         XtAppAddTimeOut(app, 3, xc_start_render, xc);
282 }
283
284 static uint_fast32_t xc_add_seq(struct xcounter *xc, int i, unsigned char val)
285 {
286         uint_fast32_t ret;
287
288         ret = xc->seq[i] != val;
289         xc->seq[i] = val;
290
291         return ret;
292 }
293
294 void xcounter_update(struct xcounter *xc, const char *str)
295 {
296         int static_seq = 10, i = 0, l;
297         uint_fast32_t mask = 0;
298         const char *s;
299
300         for (s = str; s[0];) {
301                 if ((l = strcspn(s, DIGIT_LIST))) {
302
303                         mask <<= 1;
304                         mask |= xc_add_seq(xc, i++, static_seq++);
305                         s += l;
306                 }
307
308                 while (s[0] >= '0' && s[0] <= '9') {
309                         mask <<= 1;
310                         mask |= xc_add_seq(xc, i++, s[0] - '0');
311                         s++;
312                 }
313         }
314
315         assert(static_seq <= MAX_STRINGS);
316         assert(i <= sizeof xc->seq);
317
318         mask <<= sizeof xc->seq - i;
319         memset(xc->seq+i, -1, sizeof xc->seq - i);
320
321         if (XtIsRealized(xc->widget))
322                 xc_queue_render(xc, mask);
323 }
324
325 void xcounter_expose(struct xcounter *xc, XExposeEvent *e)
326 {
327         unsigned left = e->x, right = left + e->width;
328         unsigned out_x = MARGIN, src_x, w;
329         uint_fast32_t mask = 0;
330         int i;
331
332         xc_foreach_seq(xc, i, out_x, src_x, w, mask) {
333                 mask |= out_x + w >= left && out_x < right;
334         }
335         mask <<= sizeof xc->seq - i;
336
337         xc_queue_render(xc, mask);
338 }
339
340 void xcounter_resize_cb(Widget w, void *data, void *cb_data)
341 {
342         xc_queue_render(data, XC_RESIZING);
343 }
344
345 void xcounter_expose_cb(Widget w, void *data, void *cb_data)
346 {
347         XmDrawingAreaCallbackStruct *cbs = cb_data;
348
349         if (cbs->reason == XmCR_EXPOSE)
350                 xcounter_expose(data, &cbs->event->xexpose);
351 }