2 * Helpers for implementing a rapid-update counter display in Motif.
3 * Copyright © 2022 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;
51 unsigned char seq[23], num_strings;
54 * xpos[n] is the x position of the nth string image within the
55 * pixmap, plus the width of that image.
57 uint_least16_t xpos[FLEXIBLE_ARRAY_MEMBER];
59 #define XC_SIZE(n) offsetof(struct xcounter, xpos[n])
61 static struct xcounter *
62 xc_alloc(Widget w, XmRenderTable rt, XmString *strings, int nstr)
64 Display *display = XtDisplay(w);
65 Screen *screen = XtScreen(w);
68 Drawable root = RootWindowOfScreen(screen);
69 unsigned depth = DefaultDepthOfScreen(screen);
70 unsigned wsum = 0, height = 0;
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;
82 for (i = 0; i < nstr; i++) {
85 if ((h = XmStringHeight(rt, strings[i])) > height)
86 height = h + 2*MARGIN;
88 wsum += XmStringWidth(rt, strings[i]);
92 XtVaSetValues(w, XmNheight, height + 2*MARGIN, (char *)NULL);
93 XtVaGetValues(w, XmNforeground, &fg, XmNbackground, &bg, (char *)NULL);
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);
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;
104 XmStringDraw(display, xc->pixmap, rt, strings[i], xc->gc,
105 xpos, 0, w, XmALIGNMENT_BEGINNING,
106 XmSTRING_DIRECTION_L_TO_R, NULL);
112 static struct xcounter *xc_configure(Widget w, XmRenderTable rt, char *tpl)
114 XmString strings[MAX_STRINGS];
119 for (i = 0; i < 10; i++) {
120 char s[2] = { '0' + i };
122 strings[i] = XmStringCreateLocalized(s);
125 for (s = tpl; s[0];) {
126 if ((l = strspn(s, DIGIT_LIST))) {
130 if ((l = strcspn(s, DIGIT_LIST))) {
134 strings[i++] = XmStringCreateLocalized(s);
139 xc = xc_alloc(w, rt, strings, i);
141 for (i--; i >= 0; i--) {
142 XmStringFree(strings[i]);
148 struct xcounter *xcounter_init(Widget w, char *template)
154 /* Use a dummy label widget's render table */
155 dummy = XmCreateLabelGadget(w, "text", NULL, 0);
156 XtVaGetValues(dummy, XmNrenderTable, &rt, (char *)NULL);
158 xc = xc_configure(w, rt, template);
160 XtDestroyWidget(dummy);
166 * Internal helper to imlpement the xc_foreach_seq macro.
168 static int xc_seq_iter(struct xcounter *xc, int i, unsigned *src_x,
169 unsigned *w, uint_fast32_t *mask)
173 if ((seq_i = xc->seq[i]) == (unsigned char)-1)
176 *src_x = seq_i ? xc->xpos[seq_i-1] : 0;
177 *w = xc->xpos[seq_i] - *src_x;
183 * xc_foreach_seq(xc, int, unsigned, unsigned, unsigned, uint_fast32_t)
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.
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
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) )
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.
206 static void xc_redraw(struct xcounter *xc, uint_fast32_t mask)
208 Display *display = XtDisplay(xc->widget);
209 Drawable d = XtWindow(xc->widget);
211 unsigned out_x = MARGIN, src_x, w;
215 XtVaGetValues(xc->widget, XmNwidth, &max_w,
217 max_w = MAX(MARGIN, max_w) - MARGIN;
219 xc_foreach_seq(xc, i, out_x, src_x, w, mask) {
220 if (out_x + w > max_w) {
224 if (mask & (1ul << sizeof xc->seq)) {
226 XRectangle r = { out_x, MARGIN, w, -1 };
228 XSetForeground(display, xc->gc, 0xff0000);
229 XFillRectangles(display, d, xc->gc, &r, 1);
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).
239 XCopyArea(display, xc->pixmap, d, xc->gc,
245 assert(out_x <= max_w);
246 XClearArea(display, d, out_x, 0, -1, -1, 0);
249 static Boolean xc_do_render(void *data)
251 struct xcounter *xc = data;
253 xc_redraw(xc, xc->render_mask);
258 static void xc_start_render(void *data, XtIntervalId *id)
260 struct xcounter *xc = data;
263 if (xc->render_mask & XC_PROC_INSTALLED)
266 xc->render_mask |= XC_PROC_INSTALLED;
267 app = XtWidgetToApplicationContext(xc->widget);
268 XtAppAddWorkProc(app, xc_do_render, xc);
271 static void xc_queue_render(struct xcounter *xc, uint_fast32_t mask)
273 uint_fast32_t changed = xc->render_mask |= mask;
276 if (changed & XC_TICK_INSTALLED || !changed)
279 xc->render_mask |= XC_TICK_INSTALLED;
280 app = XtWidgetToApplicationContext(xc->widget);
281 XtAppAddTimeOut(app, 3, xc_start_render, xc);
284 static uint_fast32_t xc_add_seq(struct xcounter *xc, int i, unsigned char val)
288 ret = xc->seq[i] != val;
294 void xcounter_update(struct xcounter *xc, const char *str)
296 int static_seq = 10, i = 0, l;
297 uint_fast32_t mask = 0;
300 for (s = str; s[0];) {
301 if ((l = strcspn(s, DIGIT_LIST))) {
304 mask |= xc_add_seq(xc, i++, static_seq++);
308 while (s[0] >= '0' && s[0] <= '9') {
310 mask |= xc_add_seq(xc, i++, s[0] - '0');
315 assert(static_seq <= MAX_STRINGS);
316 assert(i <= sizeof xc->seq);
318 mask <<= sizeof xc->seq - i;
319 memset(xc->seq+i, -1, sizeof xc->seq - i);
321 if (XtIsRealized(xc->widget))
322 xc_queue_render(xc, mask);
325 void xcounter_expose(struct xcounter *xc, XExposeEvent *e)
327 unsigned left = e->x, right = left + e->width;
328 unsigned out_x = MARGIN, src_x, w;
329 uint_fast32_t mask = 0;
332 xc_foreach_seq(xc, i, out_x, src_x, w, mask) {
333 mask |= out_x + w >= left && out_x < right;
335 mask <<= sizeof xc->seq - i;
337 xc_queue_render(xc, mask);
340 void xcounter_resize_cb(Widget w, void *data, void *cb_data)
342 xc_queue_render(data, XC_RESIZING);
345 void xcounter_expose_cb(Widget w, void *data, void *cb_data)
347 XmDrawingAreaCallbackStruct *cbs = cb_data;
349 if (cbs->reason == XmCR_EXPOSE)
350 xcounter_expose(data, &cbs->event->xexpose);