2 * Helpers for implementing a rapid-update counter display in Motif.
3 * Copyright © 2022-2023 Nick Bowler
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.
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.
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/>.
25 /* Define to 1 to add highlights and delays for visually debugging redraws */
32 #define MAX_STRINGS 20
33 #define DIGIT_LIST "0123456789"
36 #define MIN(a, b) ((a) < (b) ? (a) : (b))
37 #define MAX(a, b) ((a) > (b) ? (a) : (b))
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)
50 uint_least32_t render_mask;
52 /* Cached width of the widget */
53 uint_least16_t render_width;
55 unsigned char seq[23], num_strings;
58 * xpos[n] is the x position of the nth string image within the
59 * pixmap, plus the width of that image.
61 uint_least16_t xpos[FLEXIBLE_ARRAY_MEMBER];
63 #define XC_SIZE(n) offsetof(struct xcounter, xpos[n])
65 static struct xcounter *
66 xc_alloc(Widget w, XmRenderTable rt, XmString *strings, int nstr)
68 Display *display = XtDisplay(w);
69 Screen *screen = XtScreen(w);
72 Drawable root = RootWindowOfScreen(screen);
73 unsigned depth = DefaultDepthOfScreen(screen);
74 unsigned wsum = 0, height = 0;
78 assert(nstr >= 0 && nstr < MAX_STRINGS);
79 xc = (void *)XtMalloc(XC_SIZE(nstr));
80 xc->gc = XtAllocateGC(w, 0, 0, 0, GCForeground, GCBackground);
81 memset(xc->seq, -1, sizeof xc->seq);
82 xc->num_strings = nstr;
86 for (i = 0; i < nstr; i++) {
89 if ((h = XmStringHeight(rt, strings[i])) > height)
90 height = h + 2*MARGIN;
92 wsum += XmStringWidth(rt, strings[i]);
96 XtVaSetValues(w, XmNheight, height + 2*MARGIN, (char *)NULL);
97 XtVaGetValues(w, XmNforeground, &fg, XmNbackground, &bg, (char *)NULL);
99 xc->pixmap = XCreatePixmap(display, root, wsum, height, depth);
100 XSetForeground(display, xc->gc, bg);
101 XFillRectangle(display, xc->pixmap, xc->gc, 0, 0, wsum, height);
103 XSetForeground(display, xc->gc, fg);
104 for (i = 0; i < nstr; i++) {
105 unsigned xpos = i ? xc->xpos[i-1] : 0;
106 unsigned w = xc->xpos[i] - xpos;
108 XmStringDraw(display, xc->pixmap, rt, strings[i], xc->gc,
109 xpos, 0, w, XmALIGNMENT_BEGINNING,
110 XmSTRING_DIRECTION_L_TO_R, NULL);
116 static struct xcounter *xc_configure(Widget w, XmRenderTable rt, char *tpl)
118 XmString strings[MAX_STRINGS];
123 for (i = 0; i < 10; i++) {
124 char s[2] = { '0' + i };
126 strings[i] = XmStringCreateLocalized(s);
129 for (s = tpl; s[0];) {
130 if ((l = strspn(s, DIGIT_LIST))) {
134 if ((l = strcspn(s, DIGIT_LIST))) {
138 strings[i++] = XmStringCreateLocalized(s);
143 xc = xc_alloc(w, rt, strings, i);
145 for (i--; i >= 0; i--) {
146 XmStringFree(strings[i]);
152 struct xcounter *xcounter_init(Widget w, char *template)
158 /* Use a dummy label widget's render table */
159 dummy = XmCreateLabelGadget(w, "text", NULL, 0);
160 XtVaGetValues(dummy, XmNrenderTable, &rt, (char *)NULL);
162 xc = xc_configure(w, rt, template);
164 XtDestroyWidget(dummy);
170 * Internal helper to imlpement the xc_foreach_seq macro.
172 static int xc_seq_iter(struct xcounter *xc, int i, unsigned *src_x,
173 unsigned *w, uint_fast32_t *mask)
177 if ((seq_i = xc->seq[i]) == (unsigned char)-1)
180 *src_x = seq_i ? xc->xpos[seq_i-1] : 0;
181 *w = xc->xpos[seq_i] - *src_x;
187 * xc_foreach_seq(xc, int, unsigned, unsigned, unsigned, uint_fast32_t)
189 * Helper macro to iterate through the display sequence. The first argument is
190 * a pointer to the state structure. The remaining 5 arguments are lvalues of
191 * the given type designating objects that are updated on each iteration.
193 * i - index into the sequence
194 * out_x - x position for drawing of this element
195 * src_x - x position in the pixmap of this element
196 * w - width of this element
197 * mask - shifted left by 1 each iteration
199 #define xc_foreach_seq(xc, i, out_x, src_x, w, mask) for \
200 ( (out_x) = MARGIN, (i) = 0 \
201 ; (i) < sizeof (xc)->seq \
202 && xc_seq_iter(xc, i, &(src_x), &(w), &(mask)) \
203 ; (i)++, (out_x) += (w) )
206 * Redraw the changed portion of the text. The mask bitmap indicates which
207 * elements of the sequence to redraw, with bit 0 corresponding to the last
208 * possible element, bit 1 the second last, and so on.
210 static void xc_redraw(struct xcounter *xc, uint_fast32_t mask)
212 Display *display = XtDisplay(xc->widget);
213 Drawable d = XtWindow(xc->widget);
215 unsigned out_x = MARGIN, src_x, w;
219 max_w = MAX(MARGIN, xc->render_width) - MARGIN;
220 xc_foreach_seq(xc, i, out_x, src_x, w, mask) {
221 if (out_x + w > max_w) {
225 if (mask & (1ul << sizeof xc->seq)) {
227 XRectangle r = { out_x, MARGIN, w, -1 };
229 XSetForeground(display, xc->gc, 0xff0000);
230 XFillRectangles(display, d, xc->gc, &r, 1);
235 * NB: we want to copy the entire vertical contents
236 * of the pixmap; using any large height value will
237 * work but not too large otherwise there are visual
238 * problems on Intel DDX (<=821722853 seems OK).
240 XCopyArea(display, xc->pixmap, d, xc->gc,
246 assert(out_x <= max_w);
247 XClearArea(display, d, out_x, 0, -1, -1, 0);
250 static Boolean xc_do_render(void *data)
252 struct xcounter *xc = data;
254 xc_redraw(xc, xc->render_mask);
259 static void xc_start_render(void *data, XtIntervalId *id)
261 struct xcounter *xc = data;
264 if (xc->render_mask & XC_PROC_INSTALLED)
267 xc->render_mask |= XC_PROC_INSTALLED;
268 app = XtWidgetToApplicationContext(xc->widget);
269 XtAppAddWorkProc(app, xc_do_render, xc);
272 static void xc_queue_render(struct xcounter *xc, uint_fast32_t mask)
274 uint_fast32_t changed = xc->render_mask |= mask;
277 if (changed & XC_TICK_INSTALLED || !changed)
280 xc->render_mask |= XC_TICK_INSTALLED;
281 app = XtWidgetToApplicationContext(xc->widget);
282 XtAppAddTimeOut(app, 3, xc_start_render, xc);
285 static uint_fast32_t xc_add_seq(struct xcounter *xc, int i, unsigned char val)
289 ret = xc->seq[i] != val;
295 void xcounter_update(struct xcounter *xc, const char *str)
297 int static_seq = 10, i = 0, l;
298 uint_fast32_t mask = 0;
301 for (s = str; s[0];) {
302 if ((l = strcspn(s, DIGIT_LIST))) {
305 mask |= xc_add_seq(xc, i++, static_seq++);
309 while (s[0] >= '0' && s[0] <= '9') {
311 mask |= xc_add_seq(xc, i++, s[0] - '0');
316 assert(static_seq <= MAX_STRINGS);
317 assert(i <= sizeof xc->seq);
319 mask <<= sizeof xc->seq - i;
320 memset(xc->seq+i, -1, sizeof xc->seq - i);
322 if (XtIsRealized(xc->widget))
323 xc_queue_render(xc, mask);
326 void xcounter_expose(struct xcounter *xc, XExposeEvent *e)
328 unsigned left = e->x, right = left + e->width;
329 unsigned out_x = MARGIN, src_x, w;
330 uint_fast32_t mask = 0;
333 xc_foreach_seq(xc, i, out_x, src_x, w, mask) {
334 mask |= out_x + w >= left && out_x < right;
336 mask <<= sizeof xc->seq - i;
338 xc_queue_render(xc, mask);
341 void xcounter_resize_cb(Widget w, void *data, void *cb_data)
343 struct xcounter *xc = data;
346 XtVaGetValues(xc->widget, XmNwidth, &width, (char *)NULL);
347 xc->render_width = width;
349 xc_queue_render(xc, XC_RESIZING);
352 void xcounter_expose_cb(Widget w, void *data, void *cb_data)
354 XmDrawingAreaCallbackStruct *cbs = cb_data;
356 if (cbs->reason == XmCR_EXPOSE)
357 xcounter_expose(data, &cbs->event->xexpose);