chiark / gitweb /
gui-plan seems to work, yay
[trains.git] / hostside / gui-plan.c
1 /*
2  * usage: .../gui-plan [<windowid>]
3  * protocol on stdin:
4  *  series of lines
5  *   off <segname>[/[<movfeat>]
6  *   [i]on <segname>[[/<movfeat>] <movpos>]
7  *   [i]det <segname>[[/<movfeat>] <movpos>]
8  */
9
10 #include <stdlib.h>
11 #include <assert.h>
12 #include <string.h>
13 #include <errno.h>
14
15 #include <X11/Xlib.h>
16 #include <X11/xpm.h>
17
18 #include "hostside.h"
19 #include "../layout/plan-data-format.h"
20
21 #include <oop.h>
22 #include <oop-read.h>
23
24 typedef struct SegmovfeatState SegmovfeatState;
25 struct SegmovfeatState {
26   SegmovfeatState *next;
27   int invert, det, posn, x, y, width, height, redraw_needed;
28   GC gc;
29   Pixmap (*posns)[2/*i*/][2/*det*/];
30 };
31
32 oop_source *events;
33
34 static SegmovfeatState **state, *states_head;
35 static Display *d;
36 static oop_source_sys *sys_events;
37 static Window w;
38 static int redraw_needed_count, expose_count;
39
40 static void diex(const char *fn, const char *w) __attribute__((noreturn));
41 static void diex(const char *fn, const char *w) {
42   die("Xlib call failed: %s%s%s%s", fn,
43       (w)?" (":"", (w), (w)?")":"");
44 }
45
46 #define XCALL(f,w,al) do{                       \
47     if (!( (f) al ))                            \
48       diex(#f, (w));                            \
49   }while(0)
50
51 static void diexpm(const char *fn, const char *w, int xpmst)
52      __attribute__((noreturn));
53 static void diexpm(const char *fn, const char *w, int xpmst) {
54   die("Xpm call failed: %s%s%s%s: %s", fn,
55       (w)?" (":"", (w), (w)?")":"", XpmGetErrorString(xpmst));
56 }
57
58 #define XPMCALL(f,w,al) do{                     \
59     int xpmcall_xpmst;                          \
60     xpmcall_xpmst= ((f) al);                    \
61     if (xpmcall_xpmst != XpmSuccess)            \
62       diexpm(#f, (w), xpmcall_xpmst);           \
63   }while(0)
64
65 void vbadcmd(ParseState *ps, const char *fmt, va_list al) {
66   fprintf(stderr,"gui-plan: incorrect input: ");
67   vfprintf(stderr,fmt,al);
68   putc('\n',stderr);
69   exit(8);
70 }
71
72 static void *stdin_iferr(oop_source *evts, oop_read *stdin_read,
73                           oop_rd_event evt, const char *errmsg, int errnoval,
74                           const char *data, size_t recsz, void *cl_v) {
75   die("read stdin: %s", oop_rd_errmsg(stdin_read, evt,
76                                       errnoval, OOP_RD_STYLE_GETLINE));
77   return OOP_CONTINUE;
78 }
79
80 static int lstrpdbsearch(const char *str, int l,
81                          const char *what,
82                          const void *items, int n_items,
83                          int itemsz) {
84   int min, maxe, try, cmp;
85   const void *try_item;
86   const char *try_name;
87   
88   min= 0;
89   maxe= n_items;
90   for (;;) {
91     if (min >= maxe) badcmd(0,"unknown %s",what);
92     try= min + (maxe - min) / 2;
93     try_item= (const char*)items + itemsz * try;
94     try_name= *(const char *const *)try_item;
95     cmp= lstrstrcmp(str, l, try_name ? try_name : "");
96     if (!cmp) return try;
97     if (cmp < 0) maxe= try;
98     else min= try + 1;
99   }
100 }
101
102 static void *some_exception(oop_source *evts, int fd,
103                             oop_event evt, void *cl_v) {
104   die("poll exception on fd %d",fd);
105 }
106
107 static int range_overlap(int x1, int width1, int x2, int width2) {
108   /* works for y's and heights too, obviously. */
109   int rhs1, rhs2;
110   rhs1= x1 + width1;
111   rhs2= x2 + width2;
112   if (rhs1 <= x2 || rhs2 <= x1) return 0;
113   return 1;
114 }
115
116 static void xlib_expose(XExposeEvent *ev) {
117   SegmovfeatState *fs;
118   
119   expose_count= ev->count;
120   if (!ev->width || !ev->height) return;
121
122   for (fs= states_head;
123        fs;
124        fs= fs->next) {
125     if (fs->redraw_needed)
126       continue;
127     if (!range_overlap(fs->x, fs->width,
128                        ev->x, ev->width)) continue;
129     if (!range_overlap(fs->y, fs->height,
130                        ev->y, ev->height)) continue;
131     fs->redraw_needed= 1;
132     redraw_needed_count++;
133   }
134 }
135
136 static void redraw(SegmovfeatState *fs) {
137   Pixmap src;
138   
139   if (fs->redraw_needed) {
140     fs->redraw_needed= 0;
141     redraw_needed_count--;
142   }
143   if (fs->invert < 0) {
144     XCALL( XFillRectangle, "redraw",
145            (d,w, fs->gc,
146             fs->x, fs->y,
147             fs->width, fs->height) );
148   } else {
149     src= fs->posns[fs->posn][fs->invert][fs->det];
150     XCALL( XCopyArea, "redraw",
151            (d, src, w, fs->gc,
152             0,0, fs->width, fs->height,
153             fs->x, fs->y) );
154   }
155 }
156
157 static void redraw_as_needed(void) {
158   SegmovfeatState *fs;
159
160   for (fs= states_head;
161        fs;
162        fs= fs->next)
163     if (fs->redraw_needed)
164       redraw(fs);
165   assert(!redraw_needed_count);
166 }
167   
168 static Bool evpredicate_always(Display *d, XEvent *ev, XPointer a) {
169   return True;
170 }
171
172 static void xlib_process(void) {
173   XEvent ev;
174   Status xst;
175   
176   for (;;) {
177     xst= XCheckIfEvent(d,&ev,evpredicate_always,0);
178     if (!xst) {
179       if (!redraw_needed_count || expose_count)
180         return;
181       redraw_as_needed();
182       continue;
183     }
184
185     switch (ev.type) {
186     case Expose: xlib_expose(&ev.xexpose); break;
187     case NoExpose: break;
188     default: die("unrequested event type %d\n",ev.type);
189     }
190   }
191 }
192
193 static void *xlib_readable(oop_source *evts, int fd,
194                            oop_event evt, void *cl_v) {
195   xlib_process();
196   return OOP_CONTINUE;
197 }
198                            
199 static void *stdin_ifok(oop_source *evts, oop_read *cl_read,
200                         oop_rd_event evt, const char *errmsg, int errnoval,
201                         const char *data, size_t recsz, void *cl_v) {
202   const char *slash, *movfeatname;
203   ParseState ps;
204   int invert, det, segment_ix, movfeat_ix, lmovfeatname;
205   long posn;
206   const PlanSegmentData *segment_d;
207   const PlanSegmovfeatData *movfeat_d;
208   SegmovfeatState *fs;
209
210   if (evt == OOP_RD_EOF)
211     exit(0);
212   
213   if (evt != OOP_RD_OK)
214     return stdin_iferr(evts,cl_read,evt,errmsg,errnoval,data,recsz,cl_v);
215
216   ps.cl= 0;
217   ps.remain= data;
218   ps_needword(&ps);
219
220   if (!thiswordstrcmp(&ps,"off")) {
221     invert= -1;
222     det= 0;
223   } else {
224     invert= (ps.thisword[0]=='i');
225     if (invert) { ps.thisword++; ps.lthisword--; }
226     det= (!thiswordstrcmp(&ps,"on") ? 0 :
227           !thiswordstrcmp(&ps,"det") ? 1 :
228           (badcmd(&ps,"unknown command"),-1));
229   }
230   ps_needword(&ps);
231   slash= memchr(ps.thisword, '/', ps.lthisword);
232   if (slash) {
233     movfeatname= slash + 1;
234     lmovfeatname= (ps.thisword + ps.lthisword) - movfeatname;
235     ps.lthisword= slash - ps.thisword;
236   } else {
237     movfeatname= 0;
238     lmovfeatname= 0;
239   }
240   segment_ix= lstrpdbsearch(ps.thisword, ps.lthisword,
241                             "segment", ui_plan_data.segments,
242                             ui_plan_data.n_segments, sizeof(*segment_d));
243   segment_d= &ui_plan_data.segments[segment_ix];
244   
245   movfeat_ix= lstrpdbsearch(movfeatname, lmovfeatname,
246                             "movfeat", segment_d->movfeats,
247                             segment_d->n_movfeats, sizeof(*movfeat_d));
248   movfeat_d= &segment_d->movfeats[movfeat_ix];
249
250   if (ps.remain) {
251     if (invert<0) badcmd(0,"off may not take movfeatpos");
252     ps_neednumber(&ps, &posn, 0, movfeat_d->n_posns-1, "movfeatpos");
253   } else {
254     if (invert>=0 && movfeat_d->n_posns > 1) badcmd(0,"movfeatpos needed");
255     posn= 0;
256   }
257
258   ps_neednoargs(&ps);
259
260   fs= &state[segment_ix][movfeat_ix];
261   fs->invert= invert;
262   fs->det= det;
263   fs->posn= posn;
264
265   redraw(fs);
266   xlib_process();
267
268   return OOP_CONTINUE;
269 }
270
271 int main(int argc, const char *const *argv) {
272   static XpmColorSymbol coloursymbols[2]= {
273     { (char*)"space", 0, 0 },
274     { (char*)"mark",  0, 1 }
275   };
276   
277   oop_read *rd;
278   const char *arg;
279   Pixmap bg_pixmap;
280   Pixmap mask;
281   XpmAttributes mattribs;
282   XWindowAttributes wattribs;
283   XColor background_colour;
284   int segment_ix, movfeat_ix, posn, invert, det, oor;
285   SegmovfeatState *fs;
286   const PlanSegmentData *segment_d;
287   const PlanSegmovfeatData *movfeat_d;
288   XGCValues gcv;
289
290   d= XOpenDisplay(0);  if (!d) die("XOpenDisplay failed");
291
292   if ((arg= *++argv)) {
293     char *ep;
294     if (arg[0]=='-') die("no options understood");
295
296     errno=0; w= strtoul(arg,&ep,0);
297     if (errno || ep==arg || *ep) die("bad windowid");
298   } else {
299     w= XCreateSimpleWindow(d, DefaultRootWindow(d),
300                            0,0, ui_plan_data.xsz, ui_plan_data.ysz,
301                            0,0, 0);
302     if (w == None) diex("XCreateSimpleWindow", "initial");
303   }
304
305   XCALL( XGetWindowAttributes, 0, (d,w,&wattribs) );
306
307   XPMCALL( XpmCreatePixmapFromData, "background",
308            (d,w, (char**)ui_plan_data.background, &bg_pixmap,0,0) );
309
310   XCALL( XSetWindowBackgroundPixmap, 0, (d,w,bg_pixmap) );
311   XCALL( XFreePixmap, "background", (d,bg_pixmap) );
312
313   XCALL( XAllocNamedColor, "background",
314          (d, wattribs.colormap, ui_plan_colour_off,
315           &background_colour, &background_colour) );
316
317   state= mmalloc(sizeof(*state) * ui_plan_data.n_segments);
318   for (segment_ix= 0, segment_d= ui_plan_data.segments;
319        segment_ix < ui_plan_data.n_segments;
320        segment_ix++, segment_d++) {
321     state[segment_ix]= fs=
322       mmalloc(sizeof(**state) * segment_d->n_movfeats);
323     for (movfeat_ix= 0, movfeat_d= segment_d->movfeats;
324          movfeat_ix < segment_d->n_movfeats;
325          movfeat_ix++, movfeat_d++, fs++) {
326       fs->next= states_head;  states_head= fs;
327       fs->invert= -1;
328       fs->det= fs->posn= 0;
329       fs->x= movfeat_d->x;
330       fs->y= movfeat_d->y;
331       fs->redraw_needed= 0;
332       mattribs.valuemask= XpmDepth | XpmColorSymbols;
333       mattribs.depth= 1;
334       mattribs.colorsymbols= coloursymbols;
335       mattribs.numsymbols= sizeof(coloursymbols) / sizeof(*coloursymbols);
336       XPMCALL( XpmCreatePixmapFromData, "mask",
337                (d,w, (char**)movfeat_d->off, &mask,0, &mattribs) );
338       fs->width= mattribs.width;
339       fs->height= mattribs.height;
340
341       gcv.clip_x_origin= fs->x;
342       gcv.clip_y_origin= fs->y;
343       gcv.clip_mask= mask;
344       gcv.foreground= background_colour.pixel;
345       fs->gc= XCreateGC(d,w,
346                         GCClipXOrigin | GCClipYOrigin
347                         | GCClipMask | GCForeground,
348                         &gcv);
349       XCALL( XFreePixmap, "mask", (d,mask) );
350
351       fs->posns= mmalloc(sizeof(*fs->posns)*movfeat_d->n_posns);
352       for (posn= 0; posn < movfeat_d->n_posns; posn++)
353         for (invert=0; invert<2; invert++)
354           for (det=0; det<2; det++) {
355             XPMCALL( XpmCreatePixmapFromData, "main",
356                      (d,w,
357                       (char**)(movfeat_d->posns[posn].a[invert][det]),
358                       &fs->posns[posn][invert][det],
359                       0,0) );
360           }
361     }
362   }
363   
364   sys_events= oop_sys_new();  if (!sys_events) diee("oop_sys_new");
365   events= oop_sys_source(sys_events);  assert(events);
366
367   rd= oop_rd_new_fd(events, 0, 0,0);
368   if (!rd) diee("oop_rd_new_fd");
369   oor= oop_rd_read(rd, OOP_RD_STYLE_GETLINE, 1024,
370                    stdin_ifok, 0,
371                    stdin_iferr, 0);
372   if (oor) diee("oop_rd_read");
373
374   events->on_fd(events, 0,                   OOP_EXCEPTION, some_exception, 0);
375   events->on_fd(events, ConnectionNumber(d), OOP_READ,      xlib_readable,  0);
376   events->on_fd(events, ConnectionNumber(d), OOP_EXCEPTION, some_exception, 0);
377
378   XCALL( XSelectInput, 0, (d,w, ExposureMask) );
379
380   if (arg) {
381     XCALL( XClearArea, "initial", (d,w, 0,0,0,0, True) );
382   } else {
383     XCALL( XMapWindow, 0, (d,w) );
384   }
385
386   xlib_process();
387
388   oop_sys_run(sys_events);
389   abort();
390 }