chiark / gitweb /
test-ui => gui-displayer, 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 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
295     if (!strcmp(arg,"--sizes")) {
296       printf("%d\n%d\n", ui_plan_data.xsz, ui_plan_data.ysz);
297       if (ferror(stdout) || fflush(stdout)) diee("print stdout");
298       exit(0);
299     }
300
301     if (arg[0]=='-') die("invalid option(s)");
302
303     errno=0; w= strtoul(arg,&ep,0);
304     if (errno || ep==arg || *ep) die("bad windowid");
305   } else {
306     w= XCreateSimpleWindow(d, DefaultRootWindow(d),
307                            0,0, ui_plan_data.xsz, ui_plan_data.ysz,
308                            0,0, 0);
309     if (w == None) diex("XCreateSimpleWindow", "initial");
310   }
311
312   XCALL( XGetWindowAttributes, 0, (d,w,&wattribs) );
313
314   XPMCALL( XpmCreatePixmapFromData, "background",
315            (d,w, (char**)ui_plan_data.background, &bg_pixmap,0,0) );
316
317   XCALL( XSetWindowBackgroundPixmap, 0, (d,w,bg_pixmap) );
318   XCALL( XFreePixmap, "background", (d,bg_pixmap) );
319
320   XCALL( XAllocNamedColor, "background",
321          (d, wattribs.colormap, ui_plan_colour_off,
322           &background_colour, &background_colour) );
323
324   state= mmalloc(sizeof(*state) * ui_plan_data.n_segments);
325   for (segment_ix= 0, segment_d= ui_plan_data.segments;
326        segment_ix < ui_plan_data.n_segments;
327        segment_ix++, segment_d++) {
328     state[segment_ix]= fs=
329       mmalloc(sizeof(**state) * segment_d->n_movfeats);
330     for (movfeat_ix= 0, movfeat_d= segment_d->movfeats;
331          movfeat_ix < segment_d->n_movfeats;
332          movfeat_ix++, movfeat_d++, fs++) {
333       fs->next= states_head;  states_head= fs;
334       fs->invert= -1;
335       fs->det= fs->posn= 0;
336       fs->x= movfeat_d->x;
337       fs->y= movfeat_d->y;
338       fs->redraw_needed= 0;
339       mattribs.valuemask= XpmDepth | XpmColorSymbols;
340       mattribs.depth= 1;
341       mattribs.colorsymbols= coloursymbols;
342       mattribs.numsymbols= sizeof(coloursymbols) / sizeof(*coloursymbols);
343       XPMCALL( XpmCreatePixmapFromData, "mask",
344                (d,w, (char**)movfeat_d->off, &mask,0, &mattribs) );
345       fs->width= mattribs.width;
346       fs->height= mattribs.height;
347
348       gcv.clip_x_origin= fs->x;
349       gcv.clip_y_origin= fs->y;
350       gcv.clip_mask= mask;
351       gcv.foreground= background_colour.pixel;
352       fs->gc= XCreateGC(d,w,
353                         GCClipXOrigin | GCClipYOrigin
354                         | GCClipMask | GCForeground,
355                         &gcv);
356       XCALL( XFreePixmap, "mask", (d,mask) );
357
358       fs->posns= mmalloc(sizeof(*fs->posns)*movfeat_d->n_posns);
359       for (posn= 0; posn < movfeat_d->n_posns; posn++)
360         for (invert=0; invert<2; invert++)
361           for (det=0; det<2; det++) {
362             XPMCALL( XpmCreatePixmapFromData, "main",
363                      (d,w,
364                       (char**)(movfeat_d->posns[posn].a[invert][det]),
365                       &fs->posns[posn][invert][det],
366                       0,0) );
367           }
368     }
369   }
370   
371   sys_events= oop_sys_new();  if (!sys_events) diee("oop_sys_new");
372   events= oop_sys_source(sys_events);  assert(events);
373
374   rd= oop_rd_new_fd(events, 0, 0,0);
375   if (!rd) diee("oop_rd_new_fd");
376   oor= oop_rd_read(rd, OOP_RD_STYLE_GETLINE, 1024,
377                    stdin_ifok, 0,
378                    stdin_iferr, 0);
379   if (oor) diee("oop_rd_read");
380
381   events->on_fd(events, 0,                   OOP_EXCEPTION, some_exception, 0);
382   events->on_fd(events, ConnectionNumber(d), OOP_READ,      xlib_readable,  0);
383   events->on_fd(events, ConnectionNumber(d), OOP_EXCEPTION, some_exception, 0);
384
385   XCALL( XSelectInput, 0, (d,w, ExposureMask) );
386
387   if (arg) {
388     XCALL( XClearArea, "initial", (d,w, 0,0,0,0, True) );
389   } else {
390     XCALL( XMapWindow, 0, (d,w) );
391   }
392
393   xlib_process();
394
395   oop_sys_run(sys_events);
396   abort();
397 }