aboutsummaryrefslogtreecommitdiff
path: root/colitur-mu.c
diff options
context:
space:
mode:
Diffstat (limited to 'colitur-mu.c')
-rw-r--r--colitur-mu.c227
1 files changed, 227 insertions, 0 deletions
diff --git a/colitur-mu.c b/colitur-mu.c
new file mode 100644
index 0000000..d76152d
--- /dev/null
+++ b/colitur-mu.c
@@ -0,0 +1,227 @@
+/* colitur-mu -- an editable microui front-end for colitur.
+ *
+ * Unlike colitur-x, which only renders, this one lets you CHANGE the
+ * settings colitur was invoked with -- language, book-name form, numbering
+ * tradition, year -- and re-runs it. It never reimplements any calendar
+ * logic: every value on screen came out of `colitur emit --format csv`.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+#define _POSIX_C_SOURCE 200809L
+#include <X11/Xlib.h>
+#include <X11/keysym.h>
+#include <X11/Xutil.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#include "vendor/microui.h"
+#include "mu_x11.h"
+#include "config.h"
+
+#define MAXDAYS 400
+#define F 16
+
+typedef struct { char *raw; char *f[F]; } Row;
+
+static Row rows[MAXDAYS];
+static int nrows, sel;
+static char status[256] = "ready";
+
+/* --- settings the UI edits ---------------------------------------------- */
+static int year = 2027;
+static const char *langs[] = { "la", "en" };
+static const char *books[] = { "abbr", "full" };
+static const char *traditions[] = { "vulgate", "modern" };
+static int lang_i, book_i, trad_i;
+
+static int split(char *line, char **out)
+{
+ int n = 0; char *p = line;
+ while (n < F) {
+ if (*p == '"') {
+ out[n++] = ++p;
+ while (*p && *p != '"') p++;
+ if (*p) *p++ = 0;
+ if (*p == ',') p++;
+ } else {
+ out[n++] = p;
+ while (*p && *p != ',') p++;
+ if (*p) *p++ = 0; else break;
+ }
+ }
+ return n;
+}
+
+/* Shell out to colitur with the current settings. This is the whole point:
+ the front-end owns no calendar logic, only the invocation. */
+static void reload(void)
+{
+ for (int i = 0; i < nrows; i++) free(rows[i].raw);
+ nrows = 0; sel = 0;
+
+ char cmd[512];
+ snprintf(cmd, sizeof cmd,
+ "colitur emit --format csv --from %d --to %d "
+ "--lang %s --sigla-book %s --sigla-tradition %s 2>&1",
+ year, year, langs[lang_i], books[book_i], traditions[trad_i]);
+
+ FILE *p = popen(cmd, "r");
+ if (!p) { snprintf(status, sizeof status, "cannot run colitur"); return; }
+
+ char buf[4096];
+ if (!fgets(buf, sizeof buf, p)) { pclose(p); snprintf(status, sizeof status, "no output"); return; }
+ if (strncmp(buf, "date,", 5)) { /* colitur printed an error */
+ buf[strcspn(buf, "\n")] = 0;
+ snprintf(status, sizeof status, "%.200s", buf);
+ pclose(p); return;
+ }
+ while (nrows < MAXDAYS && fgets(buf, sizeof buf, p)) {
+ buf[strcspn(buf, "\n")] = 0;
+ char *d = strdup(buf);
+ if (!d) break;
+ rows[nrows].raw = d;
+ if (split(d, rows[nrows].f) >= 12) nrows++; else free(d);
+ }
+ pclose(p);
+ snprintf(status, sizeof status, "%d days ยท %s", nrows, cmd + 8);
+}
+
+/* --- one frame ---------------------------------------------------------- */
+/* One frame. NOTE the explicit row heights: mu_layout_row's height
+ argument is stored verbatim as the row size, so passing 0 lays every
+ control out zero pixels tall -- they are "drawn", invisibly, and the
+ result looks like a layout bug rather than a height bug. */
+static void frame(mu_Context *ctx, int W, int H)
+{
+ enum { RH = 24 };
+ if (!mu_begin_window_ex(ctx, "colitur", mu_rect(0, 0, W, H),
+ MU_OPT_NOTITLE | MU_OPT_NORESIZE | MU_OPT_NOCLOSE))
+ return;
+
+ int wy[] = { 120, 40, 40, 90, -1 };
+ mu_layout_row(ctx, 5, wy, RH);
+ mu_label(ctx, "Year");
+ if (mu_button(ctx, "-")) { year--; reload(); }
+ if (mu_button(ctx, "+")) { year++; reload(); }
+ char y[16]; snprintf(y, sizeof y, "%d", year);
+ mu_label(ctx, y);
+ mu_label(ctx, "");
+
+ int w4[] = { 120, 90, 90, -1 };
+ struct { const char *cap; const char **opts; int *idx; } sets[] = {
+ { "Language", langs, &lang_i },
+ { "Book names", books, &book_i },
+ { "Numbering", traditions, &trad_i },
+ };
+ for (size_t r = 0; r < sizeof sets / sizeof *sets; r++) {
+ mu_layout_row(ctx, 4, w4, RH);
+ mu_label(ctx, sets[r].cap);
+ for (int i = 0; i < 2; i++) {
+ char lbl[48];
+ snprintf(lbl, sizeof lbl, "%s%s",
+ *sets[r].idx == i ? "\xe2\x97\x8f " : " ", sets[r].opts[i]);
+ if (mu_button_ex(ctx, lbl, 0, MU_OPT_ALIGNCENTER)) {
+ *sets[r].idx = i; reload();
+ }
+ }
+ mu_label(ctx, "");
+ }
+
+ int w1[] = { -1 };
+ mu_layout_row(ctx, 1, w1, RH);
+ mu_label(ctx, status);
+
+ int listh = H - 7 * RH - 90;
+ if (listh < 100) listh = 100;
+ mu_layout_row(ctx, 1, w1, listh);
+ mu_begin_panel(ctx, "days");
+ int wrow[] = { 110, 100, 330, -1 };
+ for (int i = 0; i < nrows; i++) {
+ mu_layout_row(ctx, 4, wrow, 22);
+ if (mu_button(ctx, rows[i].f[0])) sel = i;
+ mu_label(ctx, rows[i].f[9]);
+ mu_label(ctx, rows[i].f[6]);
+ char cit[160];
+ snprintf(cit, sizeof cit, "%s %s", rows[i].f[13], rows[i].f[14]);
+ mu_label(ctx, cit);
+ }
+ mu_end_panel(ctx);
+
+ if (nrows) {
+ char d[600];
+ mu_layout_row(ctx, 1, w1, RH);
+ snprintf(d, sizeof d, "%s %s", rows[sel].f[0], rows[sel].f[6]);
+ mu_label(ctx, d);
+ mu_layout_row(ctx, 1, w1, RH);
+ snprintf(d, sizeof d, "%s %s %s Epistola %s Evangelium %s",
+ rows[sel].f[3], rows[sel].f[9], rows[sel].f[11],
+ rows[sel].f[13], rows[sel].f[14]);
+ mu_label(ctx, d);
+ }
+ mu_end_window(ctx);
+}
+
+int main(void)
+{
+ Display *dpy = XOpenDisplay(NULL);
+ if (!dpy) { fputs("cannot open display\n", stderr); return 1; }
+ int scr = DefaultScreen(dpy);
+ int W = 1000, H = 700;
+ Window win = XCreateSimpleWindow(dpy, RootWindow(dpy, scr), 0, 0, W, H, 0,
+ BlackPixel(dpy, scr), BlackPixel(dpy, scr));
+ XStoreName(dpy, win, "colitur-mu");
+ XSelectInput(dpy, win, ExposureMask | KeyPressMask | KeyReleaseMask |
+ ButtonPressMask | ButtonReleaseMask | PointerMotionMask |
+ StructureNotifyMask);
+ XMapWindow(dpy, win);
+ mux_init(dpy, win, font);
+
+ mu_Context *ctx = malloc(sizeof *ctx);
+ mu_init(ctx);
+ ctx->text_width = mux_text_width;
+ ctx->text_height = mux_text_height;
+
+ reload();
+
+ /* An immediate-mode UI is a RENDER LOOP, not an event handler. Drawing
+ only in response to an X event is wrong twice over: microui's first
+ frame merely creates its containers and the second is what lays them
+ out, and a container's scroll state settles a frame late. Drain the
+ queue, then always draw. */
+ for (int running = 1; running;) {
+ while (XPending(dpy)) {
+ XEvent ev;
+ XNextEvent(dpy, &ev);
+ switch (ev.type) {
+ case ConfigureNotify: W = ev.xconfigure.width; H = ev.xconfigure.height; break;
+ case MotionNotify: mu_input_mousemove(ctx, ev.xmotion.x, ev.xmotion.y); break;
+ case ButtonPress:
+ if (ev.xbutton.button == 4) mu_input_scroll(ctx, 0, -30);
+ else if (ev.xbutton.button == 5) mu_input_scroll(ctx, 0, 30);
+ else mu_input_mousedown(ctx, ev.xbutton.x, ev.xbutton.y, MU_MOUSE_LEFT);
+ break;
+ case ButtonRelease:
+ if (ev.xbutton.button < 4)
+ mu_input_mouseup(ctx, ev.xbutton.x, ev.xbutton.y, MU_MOUSE_LEFT);
+ break;
+ case KeyPress: {
+ KeySym k = XLookupKeysym(&ev.xkey, 0);
+ if (k == XK_q) running = 0;
+ if (k == XK_Down && sel < nrows - 1) sel++;
+ if (k == XK_Up && sel > 0) sel--;
+ break;
+ }
+ }
+ }
+ mu_begin(ctx);
+ frame(ctx, W, H);
+ mu_end(ctx);
+ mux_present(ctx, W, H, mu_color(20, 20, 20, 255));
+ XFlush(dpy);
+ nanosleep(&(struct timespec){ 0, 16000000 }, NULL); /* ~60 Hz */
+ }
+ XCloseDisplay(dpy);
+ return 0;
+}