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