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/>.
27 #define DIGIT_LIST "0123456789"
30 static XmString digits[10];
32 #define XC_FLAG_STATIC 1u
33 #define XC_FLAG_CHANGED 2u
35 #define MIN(a, b) ((a) < (b) ? (a) : (b))
36 #define MAX(a, b) ((a) > (b) ? (a) : (b))
42 uint_least8_t num_static, num_segments, total_segments;
44 /* Prior dimensions for resize handling. */
45 Dimension old_w, old_h;
47 struct xcounter_segment {
53 XmString static_strings[FLEXIBLE_ARRAY_MEMBER];
56 static void init_digits(void)
63 for (i = 0; i < 10; i++) {
64 char s[2] = {'0' + i};
65 digits[i] = XmStringCreateLocalized(s);
69 static void alloc_segments(struct xcounter *xc, int num_digits)
71 int count = num_digits + xc->num_static;
73 assert(num_digits >= 0 && count <= (uint_least8_t)-1);
74 xc->segments = (void *)XtRealloc((void *)xc->segments,
75 count * sizeof xc->segments[0]);
77 while (xc->total_segments < count) {
78 struct xcounter_segment new_seg = {0};
80 new_seg.flags = XC_FLAG_CHANGED;
81 xc->segments[xc->total_segments++] = new_seg;
83 assert(xc->total_segments == count);
86 /* Add as many entries to the segments array as there are digits in s. */
87 static void realloc_segments(struct xcounter *xc, const char *s)
92 for (; s[0]; s += strcspn(s, DIGIT_LIST)) {
93 l = strspn(s, DIGIT_LIST);
98 alloc_segments(xc, xc->total_segments - xc->num_static + new_digits);
101 struct xcounter *xcounter_init(Widget w, char *template)
103 int l, num_static = 0, num_digits = 0;
105 XmString *static_tab;
112 /* Count static segments and digits from template */
113 for (s = template; s[0];) {
114 if ((l = strcspn(s, DIGIT_LIST))) {
119 if ((l = strspn(s, DIGIT_LIST))) {
125 assert(num_static >= 0 && num_static < (uint_least8_t)-1);
126 xc = (void *)XtMalloc(sizeof *xc
127 + num_static * sizeof xc->static_strings[0]);
128 xc->num_static = num_static;
129 xc->gc = XtAllocateGC(w, 0, 0, 0, 0, 0);
130 xc->num_segments = xc->total_segments = 0;
131 xc->old_w = xc->old_h = 0;
134 /* Use a dummy label widget's render table */
135 label = XmCreateLabelGadget(w, "text", NULL, 0);
136 XtVaGetValues(label, XmNrenderTable, &rt, (char *)NULL);
137 xc->rt = XmRenderTableCopy(rt, NULL, 0);
138 XtDestroyWidget(label);
140 /* Initialize static string table */
141 static_tab = xc->static_strings;
142 for (s = template; s[0]; s += strspn(s, DIGIT_LIST)) {
143 if ((l = strcspn(s, DIGIT_LIST))) {
148 *static_tab++ = XmStringCreateLocalized(s);
154 alloc_segments(xc, num_digits);
158 static void update_static(struct xcounter *xc, int seg_index,
159 int str_index, unsigned *new_xpos)
161 struct xcounter_segment *seg = &xc->segments[seg_index];
163 assert(str_index < xc->num_static);
165 if (seg->str != xc->static_strings[str_index]) {
166 seg->flags |= XC_FLAG_CHANGED;
167 seg->flags |= XC_FLAG_STATIC;
169 seg->str = xc->static_strings[str_index];
170 seg->width = XmStringWidth(xc->rt, seg->str);
172 *new_xpos += seg->width;
175 static void update_digit(struct xcounter *xc, int seg_index,
176 unsigned digit, unsigned *new_xpos)
178 struct xcounter_segment *seg = &xc->segments[seg_index];
180 assert(digit >= '0' && digit <= '9');
183 if (seg->str != digits[digit]) {
184 seg->flags |= XC_FLAG_CHANGED;
185 seg->flags &= ~XC_FLAG_STATIC;
187 seg->str = digits[digit];
188 seg->width = XmStringWidth(xc->rt, seg->str);
190 *new_xpos += seg->width;
193 static void check_position(struct xcounter *xc, int i, unsigned *prev_xpos,
196 struct xcounter_segment *seg = &xc->segments[i];
198 if (i >= xc->num_segments) {
199 seg->flags |= XC_FLAG_CHANGED;
203 if (*prev_xpos != new_xpos)
204 seg->flags |= XC_FLAG_CHANGED;
205 *prev_xpos += seg->width;
208 static void resize(Widget w, struct xcounter *xc, Dimension width)
210 Dimension line_height;
212 line_height = XmStringHeight(xc->rt, digits[0]);
213 XtVaSetValues(w, XmNwidth, width+4,
214 XmNheight, line_height+4,
218 static int in_rect(XRectangle *r, int x, int y, int width, int height)
220 int i_x1, i_y1, i_x2, i_y2;
227 i_x2 = MIN(r->x + r->width, x + width);
228 i_y2 = MIN(r->y + r->height, y + height);
230 return i_x2 > i_x1 && i_y2 > i_y1;
234 * Redraw the changed portion of the text. There are two types of changes:
236 * - The text is explicitly updated via xcounter_update, or
237 * - A region needs to be redrawn due to expose events.
239 static void redraw(Widget w, struct xcounter *xc, XRectangle *expose)
241 Display *display = XtDisplay(w);
242 Window window = XtWindow(w);
244 Dimension line_height, width, xpos = MARGIN, ypos = MARGIN;
247 XtVaGetValues(w, XmNwidth, &width, (char *)NULL);
248 line_height = XmStringHeight(xc->rt, digits[0]);
250 for (i = 0; i < xc->num_segments && xpos < width; i++) {
251 struct xcounter_segment *seg = &xc->segments[i];
252 int exposed, changed;
254 exposed = in_rect(expose, xpos, ypos, seg->width, line_height);
255 changed = seg->flags & XC_FLAG_CHANGED;
258 XClearArea(display, window, xpos, ypos, seg->width, line_height, 0);
259 if (changed || exposed) {
260 XmStringDraw(display, window, xc->rt, seg->str, xc->gc, xpos,
261 ypos, seg->width, XmALIGNMENT_BEGINNING,
262 XmSTRING_DIRECTION_L_TO_R, expose);
263 seg->flags &= ~XC_FLAG_CHANGED;
269 XClearArea(display, window, MIN(xpos, width-MARGIN), MARGIN,
273 void xcounter_update(Widget w, struct xcounter *xc, const char *str)
275 int i, l, num_static = 0;
276 unsigned prev_xpos = 0, new_xpos = 0;
279 for (i = 0, s = str; s[0];) {
280 if ((l = strcspn(s, DIGIT_LIST))) {
281 if (i >= xc->total_segments) {
282 assert(s > str && s[-1] >= '0' && s[-1] <= '9');
283 realloc_segments(xc, s-1);
285 check_position(xc, i, &prev_xpos, new_xpos);
286 update_static(xc, i++, num_static++, &new_xpos);
290 for (l = strspn(s, DIGIT_LIST); l; l--) {
291 if (i >= xc->total_segments)
292 realloc_segments(xc, s);
293 check_position(xc, i, &prev_xpos, new_xpos);
294 update_digit(xc, i++, *s, &new_xpos);
298 xc->num_segments = i;
300 resize(w, xc, new_xpos);
305 void xcounter_expose(Widget w, struct xcounter *xc, XExposeEvent *e)
307 XRectangle rect = { e->x, e->y, e->width, e->height };
309 XClearArea(XtDisplay(w), XtWindow(w),
310 e->x, e->y, e->width, e->height, 0);
311 redraw(w, xc, &rect);
314 void xcounter_resize(Widget w, struct xcounter *xc,
315 Dimension width, Dimension height)
317 if (XtIsRealized(w)) {
318 if (width > xc->old_w) {
319 XClearArea(XtDisplay(w), XtWindow(w),
320 xc->old_w - MARGIN, 0, -1, height, 1);
323 if (height > xc->old_h) {
324 XClearArea(XtDisplay(w), XtWindow(w),
325 0, xc->old_h, xc->old_w, -1, 1);
329 xc->old_w = MAX(width, MARGIN)-MARGIN;
330 xc->old_h = MAX(height, MARGIN)-MARGIN;
333 void xcounter_resize_cb(Widget w, void *data, void *cb_data)
335 Dimension width, height;
337 XtVaGetValues(w, XmNwidth, &width, XmNheight, &height, (char *)NULL);
338 xcounter_resize(w, data, width, height);
341 void xcounter_expose_cb(Widget w, void *data, void *cb_data)
343 XmDrawingAreaCallbackStruct *cbs = cb_data;
345 if (cbs->reason == XmCR_EXPOSE)
346 xcounter_expose(w, data, &cbs->event->xexpose);