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