1 /*
2  * $Id: xbasic.c,v 1.4 2010-01-10 05:02:23 dhmunro Exp $
3  * Implement the basic X windows engine for GIST.
4  */
5 /* Copyright (c) 2005, The Regents of the University of California.
6  * All rights reserved.
7  * This file is part of yorick (http://yorick.sourceforge.net).
8  * Read the accompanying LICENSE file for details.
9  */
10 
11 #include "xbasic.h"
12 #include "gtext.h"
13 #include "pstdlib.h"
14 
15 #include <string.h>
16 
17 static void g_on_expose(void *c, int *xy);
18 static void g_on_destroy(void *c);
19 static void g_on_resize(void *c,int w,int h);
20 static void g_on_focus(void *c,int in);
21 static void g_on_key(void *c,int k,int md);
22 static void g_on_click(void *c,int b,int md,int x,int y, unsigned long ms);
23 static void g_on_motion(void *c,int md,int x,int y);
24 static void g_on_deselect(void *c);
25 static void g_on_panic(p_scr *screen);
26 
27 static g_callbacks g_x_on = {
28   "gist XEngine", g_on_expose, g_on_destroy, g_on_resize, g_on_focus,
29   g_on_key, g_on_click, g_on_motion, g_on_deselect };
30 
31 static int ChangePalette(Engine *engine);
32 static GpReal TextWidth(const char *text, int nc, const GpTextAttribs *t);
33 static void Kill(Engine *engine);
34 static void ShutDown(XEngine *xEngine);
35 static int Clear(Engine *engine, int always);
36 static int Flush(Engine *engine);
37 static void GetXRectangle(GpXYMap *map, GpBox *box,
38                           int *x0, int *y0, int *x1, int *y1);
39 static void ClearArea(Engine *engine, GpBox *box);
40 static void SetXTransform(GpTransform *trans, int landscape, int dpi);
41 static void gx_translate(GpBox *trans_window, int x, int y);
42 static GpBox *DamageClip(GpBox *damage);
43 static void ChangeMap(Engine *engine);
44 static void chk_clipping(XEngine *xeng);
45 static int SetupLine(XEngine *xeng, GpLineAttribs *gistAl, int join);
46 static int DrawLines(Engine *engine, long n, const GpReal *px,
47                      const GpReal *py, int closed, int smooth);
48 static int DrawMarkers(Engine *engine, long n, const GpReal *px,
49                        const GpReal *py);
50 static int DrwText(Engine *engine, GpReal x0, GpReal y0, const char *text);
51 static int DrawFill(Engine *engine, long n, const GpReal *px,
52                     const GpReal *py);
53 static int GetCells(GpMap *map, GpReal xmin, GpReal xmax,
54                     GpReal px, GpReal qx, long width,
55                     int *i0, int *di, int *ncols, int *x0, int *x1);
56 static int DrawCells(Engine *engine, GpReal px, GpReal py, GpReal qx,
57                      GpReal qy, long width, long height, long nColumns,
58                      const GpColor *colors);
59 static int DrawDisjoint(Engine *engine, long n, const GpReal *px,
60                         const GpReal *py, const GpReal *qx, const GpReal *qy);
61 static void GetVisibleNDC(XEngine *xeng,
62                           GpReal *xn, GpReal *xx, GpReal *yn, GpReal *yx);
63 
64 /* Hook for hlevel.c error handling.  */
65 extern void (*HLevelHook)(Engine *engine);
66 
67 
68 extern int GxJustifyText(GpXYMap *map, GpReal x0, GpReal y0, const char *text,
69                          int *ix, int *iy, int xbox[], int ybox[]);
70 extern int GxJustifyNext(const char **text, int *ix, int *iy);
71 
72 static int gxErrorFlag= 0;
73 static void GxErrorHandler(void);
74 
75 /* Engine which currently has mouse focus (set to NULL when the
76    "current" engine get destroyed or when mouse leaves the "current"
77    engine window, set to engine address on mouse motion). */
78 Engine *gxCurrentEngine = NULL;
79 
80 /* ------------------------------------------------------------------------ */
81 
82 static int
ChangePalette(Engine * engine)83 ChangePalette(Engine *engine)
84 {
85   XEngine *xeng= (XEngine *)engine;
86   p_scr *s = xeng->s;
87   p_win *win = xeng->win;
88   GpColorCell *palette= engine->palette;  /* requested palette */
89   int nColors= engine->nColors;
90   int width, height, depth;
91 
92   if (!s) return 0;
93   depth = p_sshape(s, &width, &height);
94   if (depth > 8) depth = 8;
95   if (nColors > 256) nColors= 256;
96 
97   p_palette(win, palette, nColors);
98 
99   xeng->e.colorChange= 0;
100 
101   return (1<<depth);
102 }
103 
104 /* ------------------------------------------------------------------------ */
105 
106 static int nChars, lineHeight, textHeight, prevWidth, maxWidth, alignH;
107 static int textAscent;
108 static int firstTextLine= 0;
109 
110 static p_scr *current_scr;
111 static p_win *current_win;
112 static int current_state, current_fsym, current_fsize;
113 static int chunkWidth, nChunk, supersub, dy_super, dy_sub, x_chunks;
114 
115 /* ARGSUSED */
116 static GpReal
TextWidth(const char * text,int nc,const GpTextAttribs * t)117 TextWidth(const char *text, int nc, const GpTextAttribs *t)
118 {
119   int width= 0;
120   if (firstTextLine) nChars= nc;
121   if (!gtDoEscapes) {
122     width = p_txwidth(current_scr, text, nc, gistA.t.font, current_fsize);
123     if (firstTextLine) {
124       /* record width of first line */
125       prevWidth= chunkWidth= width;
126       nChunk= nc;
127       firstTextLine= 0;
128     }
129   } else if (nc>0) {
130     int firstChunk= firstTextLine;
131     const char *txt= text;
132     char c;
133     for ( ; nc-- ; text++) {
134       c= text[0];
135       if ((nc && c=='!') || c=='^' || c=='_') {
136         if (txt<text)
137           width += p_txwidth(current_scr, txt, (int)(text-txt),
138                              gistA.t.font, current_fsize);
139         if (firstChunk) {
140           nChunk= (int)(text-txt);
141           chunkWidth= width;
142           firstChunk= 0;
143         }
144         txt= text+1;
145         if (c=='!') {
146           /* process single character escapes immediately */
147           text= txt;
148           nc--;
149           c= txt[0];
150           if (c!='!' && c!='^' && c!='_') {
151             if (c==']') c= '^';
152             width += p_txwidth(current_scr, &c, 1,
153                                current_fsym, current_fsize);
154             txt++;
155           }
156         } else if (c=='^') {
157           supersub|= 1;
158         } else {
159           supersub|= 2;
160         }
161       }
162     }
163     if (txt<text)
164       width += p_txwidth(current_scr, txt, (int)(text-txt),
165                          gistA.t.font, current_fsize);
166     if (firstChunk) {
167       nChunk= (int)(text-txt);
168       chunkWidth= width;
169     }
170     if (firstTextLine) {
171       /* record width of first line */
172       prevWidth= width;  /* this is whole line */
173       firstTextLine= 0;
174     }
175   }
176   return (GpReal)width;
177 }
178 
179 int
GxJustifyText(GpXYMap * map,GpReal x0,GpReal y0,const char * text,int * ix,int * iy,int xbox[],int ybox[])180 GxJustifyText(GpXYMap *map, GpReal x0, GpReal y0, const char *text,
181               int *ix, int *iy, int xbox[], int ybox[])
182 {
183   int nLines, alignV, dx, dy, xmin, xmax, ymin, ymax, ix0, iy0;
184   GpReal rwidest;
185 
186   /* abort if string screen position is ridiculous */
187   x0 = map->x.scale*x0 + map->x.offset;
188   y0 = map->y.scale*y0 + map->y.offset;
189   if (x0<-16000. || x0>16000. || y0<-16000. || y0>16000.) return -1;
190   current_state = 0;
191 
192   /* call p_font before any calls to TextWidth
193    * - may improve efficiency of p_txwidth, p_txheight */
194   p_font(current_win, gistA.t.font, current_fsize, gistA.t.orient);
195 
196   /* Set nLines, maxWidth, nChars, prevWidth */
197   firstTextLine = 1;
198   nChars = prevWidth = chunkWidth = nChunk = supersub = 0;
199   nLines = GtTextShape(text, &gistA.t, &TextWidth, &rwidest);
200   maxWidth = (int)rwidest;
201 
202   /* state bits:
203      1 - this chunk is superscript
204      2 - this chunk is subscript (1 and 2 together meaningless)
205      4 - this chunk is single symbol char
206    */
207   x_chunks= 0;
208 
209   /* Compute height of one line */
210   textHeight = p_txheight(current_scr, gistA.t.font, current_fsize,
211                           &textAscent);
212   dy_super = (supersub&1)? textAscent/3 : 0;
213   dy_sub = (supersub&2)? textAscent/3 : 0;
214   lineHeight = textHeight+dy_sub+dy_super;
215 
216   /* Compute displacement and bounding box of entire string,
217      relative to specified location */
218   GtGetAlignment(&gistA.t, &alignH, &alignV);
219   /* Note: xmax= xmin+maxWidth  */
220   if (alignH==TH_LEFT) {
221     dx= xmin= 0;
222     xmax= maxWidth;
223   } else if (alignH==TH_CENTER) {
224     xmax= maxWidth/2;
225     xmin= -xmax;
226     dx= -prevWidth/2;
227   } else {
228     xmax= 0;
229     xmin= -maxWidth;
230     dx= -prevWidth;
231   }
232 
233   /* Note: ymax= ymin+nLines*lineHeight  and  ymin= dy-textAscent  */
234   if (alignV<=TV_CAP) {
235     dy= textAscent + dy_super;
236     ymin= 0;
237     ymax= nLines*lineHeight;
238   } else if (alignV==TV_HALF) {
239     ymin= textAscent/2;
240     ymax= nLines*lineHeight;
241     dy= ymin-(ymax-lineHeight)/2;
242     ymin= dy-textAscent-dy_super;
243     ymax+= ymin;
244   } else if (alignV==TV_BASE) {
245     dy= -(nLines-1)*lineHeight;
246     ymin= dy-textAscent-dy_super;
247     ymax= textHeight-textAscent + dy_sub;
248   } else {
249     ymin= dy_sub-nLines*lineHeight;
250     dy= ymin+textAscent+dy_super;
251     ymax= dy_sub;
252   }
253 
254   /* handle orientation (path) of text */
255   if (gistA.t.orient==TX_LEFT) {         /* upside down */
256     int tmp;
257     dx= -dx;  dy= -dy;
258     tmp= xmin;  xmin= -xmax;  xmax= -tmp;
259     tmp= ymin;  ymin= -ymax;  ymax= -tmp;
260   } else if (gistA.t.orient==TX_UP) {    /* reading upwards */
261     int tmp;
262     tmp= dx;  dx= dy;  dy= -tmp;
263     tmp= xmin;  xmin= ymin;  ymin= -xmax;
264                 xmax= ymax;  ymax= -tmp;
265   } else if (gistA.t.orient==TX_DOWN) {  /* reading downwards */
266     int tmp;
267     tmp= dx;  dx= -dy;  dy= tmp;
268     tmp= xmin;  xmin= -ymax;  ymax= xmax;
269                 xmax= -ymin;  ymin= tmp;
270   }
271 
272   /* get bounding box and adjusted reference point */
273   ix0 = (int)x0;
274   iy0 = (int)y0;
275 
276   xbox[0] = ix0+xmin;   ybox[0] = iy0+ymin;
277   xbox[1] = ix0+xmax;   ybox[1] = iy0+ymax;
278 
279   *ix= dx + ix0;
280   *iy= dy + iy0;
281 
282   if (nChunk) p_font(current_win,
283                      gistA.t.font, current_fsize, gistA.t.orient);
284   return nChunk;
285 }
286 
287 int
GxJustifyNext(const char ** text,int * ix,int * iy)288 GxJustifyNext(const char **text, int *ix, int *iy)
289 {
290   const char *txt= *text+nChunk;
291   int xadj= 0, yadj= 0;
292   char c;
293 
294   nChars-= nChunk;
295   if (!nChars) {
296     /* last chunk was the last one on a line */
297     txt= GtNextLine(txt, &nChars, 0);
298     if (!txt) return -1;
299 
300     *text= txt;
301 
302     /* scan for end of first chunk */
303     if (gtDoEscapes) {
304       for (nChunk=0 ; nChunk<nChars ; nChunk++) {
305         c= txt[nChunk];
306         if ((nChunk+1<nChars && c=='!') || c=='^' || c=='_') break;
307       }
308     } else {
309       nChunk= nChars;
310     }
311 
312     /* compute width of this chunk if necessary, compute width
313        of whole line if necessary for justification */
314     if (alignH!=TH_LEFT || gistA.t.orient!=TX_RIGHT) {
315       /* need to compute width of entire line */
316       int width= prevWidth;
317       firstTextLine= 1;
318       prevWidth= (int)TextWidth(txt, nChars, &gistA.t);
319       if (alignH==TH_CENTER) xadj= (width-prevWidth)/2;
320       else if (alignH==TH_RIGHT) xadj= width-prevWidth;
321       /* TextWidth sets chunkWidth */
322     } else if (nChunk<nChars) {
323       /* just need width of this chunk */
324       if (nChunk) chunkWidth = p_txwidth(current_scr, txt, nChunk,
325                                          gistA.t.font, current_fsize);
326       else chunkWidth= 0;  /* unused */
327     }
328 
329     /* reset state, adjusting (possibly rotated) x and y as well */
330     xadj-= x_chunks;
331     yadj= lineHeight;
332     if (current_state&1) yadj+= dy_super;
333     else if (current_state&2) yadj-= dy_sub;
334     if (nChunk && (current_state&4))
335       p_font(current_win, gistA.t.font, current_fsize, gistA.t.orient);
336     current_state= 0;
337 
338   } else {
339     /* previous chunk ended with an escape character, or was single
340        escaped symbol character -- can't get here unles gtDoEscapes */
341     char c1= '\0';
342     xadj= chunkWidth;      /* width of previous chunk */
343     x_chunks+= chunkWidth; /* accumulate all chunks except last */
344     yadj= 0;
345     if (!(current_state&4)) {
346       c1= *txt++;
347       nChars--;
348     }
349     *text= txt;
350 
351     if (c1=='!') {
352       /* this chunk begins with escaped character */
353       nChunk= 1;
354       c= txt[0];
355       if (c=='!' || c=='^' || c=='_') {
356         /* chunk is just ordinary text */
357         for ( ; nChunk<nChars ; nChunk++) {
358           c= txt[nChunk];
359           if ((nChunk+1<nChars && c=='!') || c=='^' || c=='_') break;
360         }
361         p_font(current_win, gistA.t.font, current_fsize, gistA.t.orient);
362         current_state&= 3;
363       } else {
364         /* chunk is single symbol char */
365         p_font(current_win, current_fsym, current_fsize, gistA.t.orient);
366         current_state|= 4;
367       }
368 
369     } else {
370       for (nChunk=0 ; nChunk<nChars ; nChunk++) {
371         c= txt[nChunk];
372         if ((nChunk+1<nChars && c=='!') || c=='^' || c=='_') break;
373       }
374       if (nChunk)
375         p_font(current_win, gistA.t.font, current_fsize, gistA.t.orient);
376       if (c1=='^') {
377         if (current_state&1) {
378           yadj+= dy_super;  /* return from super to normal */
379           current_state= 0;
380         } else {
381           if (current_state&2) yadj-= dy_sub;
382           yadj-= dy_super;  /* move to superscript */
383           current_state= 1;
384         }
385       } else if (c1=='_') {
386         if (current_state&2) {
387           yadj-= dy_sub;  /* return from sub to normal */
388           current_state= 0;
389         } else {
390           if (current_state&1) yadj+= dy_super;
391           yadj+= dy_sub;  /* move to subscript */
392           current_state= 2;
393         }
394       } else {
395         /* just finished a symbol char */
396         current_state&= 3;
397       }
398     }
399 
400     if (nChunk &&
401         (nChunk<nChars || alignH!=TH_LEFT || gistA.t.orient!=TX_RIGHT)) {
402       char caret= '^';
403       if (nChunk==1 && (current_state&4) && txt[0]==']') txt= &caret;
404       chunkWidth = p_txwidth(current_scr, txt, nChunk,
405                              (current_state&4)? current_fsym : gistA.t.font,
406                              current_fsize);
407     } else {
408       chunkWidth= 0;  /* unused */
409     }
410   }
411 
412   if (gistA.t.orient==TX_RIGHT) {
413     *iy+= yadj;
414     *ix+= xadj;
415   } else if (gistA.t.orient==TX_LEFT) {
416     *iy-= yadj;
417     *ix-= xadj;
418   } else if (gistA.t.orient==TX_UP) {
419     *ix+= yadj;
420     *iy-= xadj;
421   } else {
422     *ix-= yadj;
423     *iy+= xadj;
424   }
425 
426   return nChunk;
427 }
428 
429 /* ------------------------------------------------------------------------ */
430 
431 /* notes for Kill() and g_on_destroy
432  * (1) Kill() is program-driven (e.g.- winkill)
433  *     g_on_destroy() is event-driven (e.g.- mouse click)
434  * (2) however, the p_destroy() function MIGHT or MIGHT NOT
435  *     result in a call to g_on_destroy()
436  *     under Windows, g_on_destroy() is naturally called
437  *       by the call p_destroy() must make to close the window
438  *     under UNIX/X, the play event handler calls p_destroy()
439  *       after g_on_destroy() returns
440  * (3) therefore g_on_destroy() must not call p_destroy(), since
441  *       this would cause infinite recursion on Windows, and a
442  *       double call on X.
443  * (4) worse, if this is the final window on an X display, gist
444  *       should disconnect from the display, but that can only
445  *       be done after the final p_destroy(), which is after
446  *       the g_on_destroy() in the event-driven case
447  *       -- thus, the p_disconnect must take place on the next
448  *          event, which is during the GhBeforeWait hlevel.c function
449  */
450 /* hack to disconnect if last engine destroyed (see GhBeforeWait) */
451 static void g_do_disconnect(void);
452 extern void (*g_pending_task)(void);
453 void (*g_pending_task)(void) = 0;
454 
455 static void
Kill(Engine * engine)456 Kill(Engine *engine)
457 {
458   XEngine *xeng= (XEngine *)engine;
459   p_win *w = xeng->win;
460   ShutDown(xeng);
461   if (w) p_destroy(w);
462   /* for program-driven Kill(), can take care of p_disconnect immediately */
463   g_do_disconnect();
464 }
465 
466 static int
Clear(Engine * engine,int always)467 Clear(Engine *engine, int always)
468 {
469   XEngine *xeng = (XEngine *)engine;
470   if (!xeng->w) return 1;
471   if ((always || xeng->e.marked) && xeng->w==xeng->win) {
472     int tm = xeng->topMargin;
473     int lm = xeng->leftMargin;
474     if (tm || lm) {
475       int xmax = (int)xeng->swapped.window.xmax;
476       int ymax = (int)xeng->swapped.window.ymin;
477       if (xmax > lm+xeng->wtop) xmax = lm+xeng->wtop;
478       if (ymax > tm+xeng->htop) ymax = tm+xeng->htop;
479       if (xeng->clipping) {
480         p_clip(xeng->w, 0,0,0,0);
481         xeng->clipping = 0;
482       }
483       p_color(xeng->w, P_BG);
484       p_rect(xeng->w, lm, tm, xmax, ymax, 0);
485     } else {
486       p_clear(xeng->w);
487     }
488   }
489   if (xeng->e.colorChange) ChangePalette(engine);
490   xeng->e.marked = 0;
491   return 0;
492 }
493 
494 static int
Flush(Engine * engine)495 Flush(Engine *engine)
496 {
497   XEngine *xeng = (XEngine *)engine;
498   if (!xeng->w) return 1;
499   p_flush(xeng->w);
500   /* test whether an X error has been reported */
501   if (gxErrorFlag) GxErrorHandler();
502   return 0;
503 }
504 
505 static void
GetXRectangle(GpXYMap * map,GpBox * box,int * x0,int * y0,int * x1,int * y1)506 GetXRectangle(GpXYMap *map, GpBox *box,
507               int *x0, int *y0, int *x1, int *y1)
508 {
509   /* get corners of clip rectangle in pixel coordinates */
510   int wmin= (int)(map->x.scale*box->xmin+map->x.offset);
511   int wmax= (int)(map->x.scale*box->xmax+map->x.offset);
512   if (wmax>=wmin) {
513     *x0 = wmin;
514     *x1 = wmax+1;
515   } else {
516     *x0 = wmax;
517     *x1 = wmin+1;
518   }
519   wmin= (int)(map->y.scale*box->ymin+map->y.offset);
520   wmax= (int)(map->y.scale*box->ymax+map->y.offset);
521   if (wmax>=wmin) {
522     *y0 = wmin;
523     *y1 = wmax+1;
524   } else {
525     *y0 = wmax;
526     *y1 = wmin+1;
527   }
528 }
529 
530 static void
ClearArea(Engine * engine,GpBox * box)531 ClearArea(Engine *engine, GpBox *box)
532 {
533   XEngine *xeng= (XEngine *)engine;
534   p_win *w = xeng->w;
535   int x0, y0, x1, y1;
536   if (!w) return;
537   /* if this is animation mode, do not try to clear window */
538   if (w==xeng->win) {
539     int lm = xeng->leftMargin;
540     int tm = xeng->topMargin;
541     GetXRectangle(&engine->devMap, box, &x0, &y0, &x1, &y1);
542     if (x0 < lm) x0 = lm;
543     if (x1 > lm+xeng->wtop) x1 = lm+xeng->wtop;
544     if (y0 < tm) y0 = tm;
545     if (y1 > tm+xeng->htop) y1 = tm+xeng->htop;
546     p_color(w, P_BG);
547     p_rect(w, x0, y0, x1, y1, 0);
548   }
549 }
550 
551 static void
SetXTransform(GpTransform * trans,int landscape,int dpi)552 SetXTransform(GpTransform *trans, int landscape, int dpi)
553 {
554   trans->viewport= landscape? gLandscape : gPortrait;
555   trans->window.xmin= 0.0;
556   trans->window.xmax= PixelsPerNDC(dpi)*trans->viewport.xmax;
557   trans->window.ymin= PixelsPerNDC(dpi)*trans->viewport.ymax;
558   trans->window.ymax= 0.0;
559 }
560 
561 static void
gx_translate(GpBox * trans_window,int x,int y)562 gx_translate(GpBox *trans_window, int x, int y)
563 {
564   trans_window->xmax += x - trans_window->xmin;
565   trans_window->xmin = x;
566   trans_window->ymin += y - trans_window->ymax;
567   trans_window->ymax = y;
568 }
569 
570 static GpBox cPort;
571 
572 static GpBox *
DamageClip(GpBox * damage)573 DamageClip(GpBox *damage)
574 {
575   cPort= gistT.viewport;
576   if (cPort.xmin>cPort.xmax)
577     { GpReal tmp= cPort.xmin; cPort.xmin= cPort.xmax; cPort.xmax= tmp; }
578   if (cPort.ymin>cPort.ymax)
579     { GpReal tmp= cPort.ymin; cPort.ymin= cPort.ymax; cPort.ymax= tmp; }
580   /* (assume damage box is properly ordered) */
581   if (damage->xmin>cPort.xmin) cPort.xmin= damage->xmin;
582   if (damage->xmax<cPort.xmax) cPort.xmax= damage->xmax;
583   if (damage->ymin>cPort.ymin) cPort.ymin= damage->ymin;
584   if (damage->ymax<cPort.ymax) cPort.ymax= damage->ymax;
585   if (cPort.xmin>cPort.xmax || cPort.ymin>cPort.ymax) return 0;
586   else return &cPort;
587 }
588 
589 static void
ChangeMap(Engine * engine)590 ChangeMap(Engine *engine)
591 {
592   XEngine *xeng = (XEngine *)engine;
593   p_win *w = xeng->w;
594   int landscape = xeng->width > xeng->height;
595   int x0, y0, x1, y1;
596   GpBox *clipport;
597   if (!w) return;
598 
599   /* check to be sure that landscape/portrait mode hasn't changed */
600   if (landscape!=xeng->e.landscape) {
601     /* this is probably insane if in animation mode... */
602     SetXTransform(&xeng->e.transform, xeng->e.landscape, xeng->dpi);
603     xeng->width = (int)xeng->e.transform.window.xmax;
604     xeng->height = (int)xeng->e.transform.window.ymin;
605     xeng->swapped = xeng->e.transform;
606     /* make adjustments to allow for SetXTransform, then recenter */
607     if (xeng->w != xeng->win) {
608       xeng->a_x += xeng->x+1;
609       xeng->a_y += xeng->y+1;
610     }
611     xeng->x = xeng->y = -1;
612     GxRecenter(xeng, xeng->wtop+xeng->leftMargin, xeng->htop+xeng->topMargin);
613   }
614 
615   /* do generic change map */
616   GpComposeMap(engine);
617 
618   /* get current clip window */
619   if (xeng->e.damaged) clipport = DamageClip(&xeng->e.damage);
620   else clipport = &gistT.viewport;
621   if (clipport) {
622     /* set clipping rectangle for this XEngine */
623     GetXRectangle(&engine->devMap, clipport, &x0, &y0, &x1, &y1);
624     if (xeng->w == xeng->win) {
625       /* additional restriction for vignetting by window borders */
626       int lm = xeng->leftMargin;
627       int tm = xeng->topMargin;
628       if (x0 < lm) x0 = lm;
629       if (x1 > lm+xeng->wtop) x1 = lm+xeng->wtop;
630       if (y0 < tm) y0 = tm;
631       if (y1 > tm+xeng->htop) y1 = tm+xeng->htop;
632       xeng->clipping = 1;
633     } else {
634       if (x0 < 0) x0 = 0;
635       if (x1 > xeng->a_width) x1 = xeng->a_width;
636       if (y0 < 0) y0 = 0;
637       if (y1 > xeng->a_height) y1 = xeng->a_height;
638       if (x0==0 && x1==xeng->a_width && y0==0 && y1==xeng->a_height)
639         x0 = x1 = y0 = y1 = 0;
640     }
641     if (x0 || x1 || y0 || y1) {
642       if (x1<=x0) x1 = x0+1;
643       if (y1<=y0) y1 = y0+1;
644     }
645     p_clip(xeng->w, x0, y0, x1, y1);
646   }
647 }
648 
649 static void
chk_clipping(XEngine * xeng)650 chk_clipping(XEngine *xeng)
651 {
652   p_win *w = xeng->win;
653   if (!xeng->clipping) {
654     int x0, y0, x1, y1;
655     int lm = xeng->leftMargin;
656     int tm = xeng->topMargin;
657     if (xeng->e.damaged) {
658       GpBox *box = DamageClip(&xeng->e.damage);
659       GpXYMap map;
660       if (xeng->w != w)
661         GpSetMap(&xeng->swapped.viewport, &xeng->swapped.window, &map);
662       else
663         map = xeng->e.devMap;
664       GetXRectangle(&map, box, &x0, &y0, &x1, &y1);
665       /* additional restriction for vignetting by window borders */
666       if (x0 < lm) x0 = lm;
667       if (x1 > lm+xeng->wtop) x1 = lm+xeng->wtop;
668       if (y0 < tm) y0 = tm;
669       if (y1 > tm+xeng->htop) y1 = tm+xeng->htop;
670     } else {
671       x0 = lm;
672       x1 = lm+xeng->wtop;
673       y0 = tm;
674       y1 = tm+xeng->htop;
675     }
676     xeng->clipping = 1;
677     if (x1<=x0) x1 = x0+1;
678     if (y1<=y0) y1 = y0+1;
679     p_clip(w, x0, y0, x1, y1);
680   }
681 }
682 
683 /* ------------------------------------------------------------------------ */
684 
685 static int
SetupLine(XEngine * xeng,GpLineAttribs * gistAl,int join)686 SetupLine(XEngine *xeng, GpLineAttribs *gistAl, int join)
687 {
688   GpXYMap *map= &xeng->e.map;
689   double xt[2], yt[2];
690   xt[0] = map->x.scale;
691   xt[1] = map->x.offset;
692   yt[0] = map->y.scale;
693   yt[1] = map->y.offset;
694   p_d_map(xeng->w, xt, yt, 1);
695   chk_clipping(xeng);
696   if (gistAl->type != L_NONE) {
697     int type = gistAl->type-1;
698     int width = (unsigned int)(DEFAULT_LINE_INCHES*xeng->dpi*gistAl->width);
699     if (join) type |= P_SQUARE;
700     p_pen(xeng->w, width, type);
701     p_color(xeng->w, gistAl->color);
702     return 0;
703   } else {
704     return 1;
705   }
706 }
707 
708 static int
DrawLines(Engine * engine,long n,const GpReal * px,const GpReal * py,int closed,int smooth)709 DrawLines(Engine *engine, long n, const GpReal *px,
710           const GpReal *py, int closed, int smooth)
711 {
712   XEngine *xeng = (XEngine *)engine;
713   p_win *w = xeng->w;
714   long i, imax;
715   int npts;
716 
717   if (!w) return 1;
718   if (n<=0 || SetupLine(xeng, &gistA.l, 0)) return 0;
719 
720   closed = (closed && n>1 && (px[0]!=px[n-1] || py[0]!=py[n-1]));
721   for (i=0 ; i<n ; i=imax) {
722     imax = i+2047;
723     npts = (imax<=n)? 2047 : (int)(n-i);
724     p_d_pnts(w, px+i, py+i, npts);
725     if (closed && imax>=n)
726       p_d_pnts(w, px, py, -1);
727     p_lines(w);
728   }
729 
730   xeng->e.marked = 1;
731   return 0;
732 }
733 
734 /* ------------------------------------------------------------------------ */
735 
736 /* play has no polymarker primitive, use GpPseudoMark-- unless
737    user requested tiny points, in which case we use p_dots */
738 static int
DrawMarkers(Engine * engine,long n,const GpReal * px,const GpReal * py)739 DrawMarkers(Engine *engine, long n, const GpReal *px, const GpReal *py)
740 {
741   XEngine *xeng = (XEngine *)engine;
742   p_win *w = xeng->w;
743   if (!w || !xeng->mapped) return 1;
744 
745   xeng->e.marked = 1;
746   if (gistA.m.type!=M_POINT || gistA.m.size>1.5) {
747     return GpPseudoMark(engine, n, px, py);
748 
749   } else {
750     long i, imax;
751     int npts;
752     GpXYMap *map= &xeng->e.map;
753     double xt[2], yt[2];
754     xt[0] = map->x.scale;
755     xt[1] = map->x.offset;
756     yt[0] = map->y.scale;
757     yt[1] = map->y.offset;
758     p_d_map(w, xt, yt, 1);
759     chk_clipping(xeng);
760     p_color(w, gistA.m.color);
761 
762     for (i=0 ; i<n ; i=imax) {
763       imax = i+2048;
764       npts = (imax<=n)? 2048 : (int)(n-i);
765       p_d_pnts(w, px+i, py+i, npts);
766       p_dots(w);
767     }
768 
769     return 0;
770   }
771 }
772 
773 /* ------------------------------------------------------------------------ */
774 
775 static int
DrwText(Engine * engine,GpReal x0,GpReal y0,const char * text)776 DrwText(Engine *engine, GpReal x0, GpReal y0, const char *text)
777 {
778   XEngine *xeng = (XEngine *)engine;
779   p_win *w = xeng->w;
780   GpXYMap *map = &xeng->e.map;
781   int ix, iy, len, xbox[2], ybox[2], xn, xx, yn, yx;
782   const char *txt;
783   char *caret= "^";
784 
785   if (!w || !xeng->mapped) return 1;
786   chk_clipping(xeng);
787 
788   current_fsize = (int)((xeng->dpi/ONE_INCH)*gistA.t.height);
789   if (current_fsize<4) current_fsize = 4;     /* totally illegible */
790   if (current_fsize>180) current_fsize = 180; /* way too big */
791   current_fsym = T_SYMBOL | (gistA.t.font&3);
792   current_scr = xeng->s;
793   current_win = w;
794 
795   /* get current window */
796   xn = (int)(gistT.window.xmin*map->x.scale + map->x.offset);
797   xx = (int)(gistT.window.xmax*map->x.scale + map->x.offset);
798   yn = (int)(gistT.window.ymin*map->y.scale + map->y.offset);
799   yx = (int)(gistT.window.ymax*map->y.scale + map->y.offset);
800   if (yn > yx) { int tmp=yn ; yn=yx; yx=tmp ; }
801 
802   /* handle multi-line strings */
803   len = GxJustifyText(map, x0, y0, text, &ix, &iy, xbox, ybox);
804   if (len < 0) return 0;
805 
806   /* consider whether string is completely clipped */
807   if (ybox[0]>yx || ybox[1]<yn || xbox[0]>xx || xbox[1]<xn) return 0;
808 
809   /* erase background if string is opaque */
810   if (gistA.t.opaque) {
811     p_color(w, P_BG);
812     p_rect(w, xbox[0], ybox[0], xbox[1], ybox[1], 0);
813   }
814   p_color(w, gistA.t.color);
815 
816   do {
817     if (len>0) {
818       if (len==1 && (current_state&4) && text[0]==']') txt = caret;
819       else txt = text;
820       p_text(w, ix, iy, txt, len);
821     }
822     len = GxJustifyNext(&text, &ix, &iy);
823   } while (len>=0);
824 
825   xeng->e.marked = 1;
826   return 0;
827 }
828 
829 /* ------------------------------------------------------------------------ */
830 
831 static int
DrawFill(Engine * engine,long n,const GpReal * px,const GpReal * py)832 DrawFill(Engine *engine, long n, const GpReal *px,
833          const GpReal *py)
834 {
835   XEngine *xeng = (XEngine *)engine;
836   p_win *w = xeng->w;
837   long i, imax;
838   int npts, has_edge;
839 
840   if (!w || !xeng->mapped) return 1;
841   has_edge = !SetupLine(xeng, &gistA.e, 0); /* but shouldn't set color */
842   p_color(w, gistA.f.color);
843 
844   /* This gives incorrect results if more than one pass through the loop,
845    * but there is no way to give the correct result, so may as well...  */
846   for (i=0 ; i<n ; i=imax) {
847     imax = i+2048;
848     npts = (imax<=n)? 2048 : (int)(n-i);
849     p_d_pnts(w, px+i, py+i, npts);
850     /* Can Nonconvex or Convex be detected? */
851     p_fill(w, 0);
852   }
853 
854   xeng->e.marked = 1;
855   if (has_edge) {
856     p_color(w, gistA.e.color);
857     for (i=0 ; i<n ; i=imax) {
858       imax = i+2047;
859       npts = (imax<=n)? 2047 : (int)(n-i);
860       p_d_pnts(w, px+i, py+i, npts);
861       if (imax>=n)
862         p_d_pnts(w, px, py, -1);
863       p_lines(w);
864     }
865   }
866 
867   return 0;
868 }
869 
870 /* ------------------------------------------------------------------------ */
871 
872 static int
GetCells(GpMap * map,GpReal xmin,GpReal xmax,GpReal px,GpReal qx,long width,int * i0,int * di,int * ncols,int * x0,int * x1)873 GetCells(GpMap *map, GpReal xmin, GpReal xmax,
874          GpReal px, GpReal qx, long width,
875          int *i0, int *di, int *ncols, int *x0, int *x1)
876 {
877   GpReal scale = map->scale;
878   GpReal offset = map->offset;
879   GpReal x, dx = (qx-px)/width;
880   int imin, imax;
881 
882   if (xmin>xmax) x=xmin, xmin=xmax, xmax=x;
883 
884   if (dx>=0.0) {
885     if (qx<xmin || px>xmax) return 0;
886     if (dx > 0.0) {
887       x = (xmin - px)/dx;
888       if (x < 1.) imin = 0;
889       else imin=(int)x, px+=imin*dx;
890       x = (qx - xmax)/dx;
891       if (x < 1.) imax = 0;
892       else imax=(int)x, qx-=imax*dx;
893       width -= imin + imax;
894       if (width < 3) { /* see comment below */
895         if (qx <= xmax) {
896           if (imin) px = xmin;
897         } else if (px >= xmin) {
898           if (imax) qx = xmax;
899         } else if (width < 2) {
900           px = xmin;
901           qx = xmax;
902         } else if (qx-xmax <= xmin-px) {
903           px += qx - xmax;
904           qx = xmax;
905         } else {
906           qx += qx - xmin;
907           px = xmin;
908         }
909       }
910     } else {
911       imin = width/2;
912       imax = width - imin - 1;
913       width = 1;
914     }
915   } else {
916     if (px<xmin || qx>xmax) return 0;
917     dx = -dx;
918     x = (px - xmax)/dx;
919     if (x < 1.) imin = 0;
920     else imin=(int)x, px-=imin*dx;
921     x = (xmin - qx)/dx;
922     if (x < 1.) imax = 0;
923     else imax=(int)x, qx+=imax*dx;
924     width -= imin + imax;
925     if (width < 3) { /* see comment below */
926       if (px <= xmax) {
927         if (imax) qx = xmin;
928       } else if (qx >= xmin) {
929         if (imin) px = xmax;
930       } else if (width < 2) {
931         qx = xmin;
932         px = xmax;
933       } else if (px-xmax <= xmin-qx) {
934         qx += px - xmax;
935         px = xmax;
936       } else {
937         px += qx - xmin;
938         qx = xmin;
939       }
940     }
941   }
942   /* width<3 logic above guarantees these will not overflow as int */
943   px = px*scale + offset;
944   qx = qx*scale + offset;
945   if (qx >= px) {
946     *i0 = imin;
947     *di = 1;
948     *ncols = width;
949     *x0 = (int)px;
950     *x1 = (int)qx;
951   } else {
952     *i0 = imin + width - 1;
953     *di = -1;
954     *ncols = width;
955     *x0 = (int)qx;
956     *x1 = (int)px;
957   }
958   /* cell array always at least 1 pixel */
959   if (*x1 == *x0) *x1 += 1;
960 
961   return 1;
962 }
963 
964 static int
DrawCells(Engine * engine,GpReal px,GpReal py,GpReal qx,GpReal qy,long width,long height,long nColumns,const GpColor * colors)965 DrawCells(Engine *engine, GpReal px, GpReal py, GpReal qx,
966           GpReal qy, long width, long height, long nColumns,
967           const GpColor *colors)
968 {
969   XEngine *xeng = (XEngine *)engine;
970   p_win *w = xeng->w;
971   GpXYMap *map= &xeng->e.map;
972   int i0, j0, di, dj, ncols, nrows, x0, y0, x1, y1;
973 
974   if (!w || !xeng->mapped) return 1;
975   chk_clipping(xeng);
976 
977   if (GetCells(&map->x, gistT.window.xmin, gistT.window.xmax,
978                px, qx, width, &i0, &di, &ncols, &x0, &x1) &&
979       GetCells(&map->y, gistT.window.ymin, gistT.window.ymax,
980                py, qy, height, &j0, &dj, &nrows, &y0, &y1)) {
981     unsigned char *ndxs = (unsigned char *)colors;
982     if (di<0 || dj<0 || ncols!=width || nrows!=height || nColumns!=width) {
983       int r, c, nr, nc, i, j;
984       ndxs = p_malloc(gistA.rgb?3*ncols*nrows:ncols*nrows);
985       j0 *= nColumns;
986       dj *= nColumns;
987       if (gistA.rgb) {
988         for (j=j0,c=0,nr=nrows ; nr-- ; j+=dj,c+=ncols) {
989           nc = ncols;
990           for (i=i0,r=0 ; nc-- ; i+=di,r++) {
991             ndxs[3*(c+r)] = colors[3*(j+i)];
992             ndxs[3*(c+r)+1] = colors[3*(j+i)+1];
993             ndxs[3*(c+r)+2] = colors[3*(j+i)+2];
994           }
995         }
996       } else {
997         for (j=j0,c=0,nr=nrows ; nr-- ; j+=dj,c+=ncols) {
998           nc = ncols;
999           for (i=i0,r=0 ; nc-- ; i+=di,r++)
1000             ndxs[c+r] = colors[j+i];
1001         }
1002       }
1003     }
1004     if (ncols && nrows) {
1005       if (gistA.rgb)
1006         p_rgb_cell(w, ndxs, ncols, nrows, x0, y0, x1, y1);
1007       else
1008         p_ndx_cell(w, ndxs, ncols, nrows, x0, y0, x1, y1);
1009     }
1010     if (ndxs!=colors) p_free(ndxs);
1011   }
1012 
1013   xeng->e.marked = 1;
1014   return 0;
1015 }
1016 
1017 /* ------------------------------------------------------------------------ */
1018 
1019 static int
DrawDisjoint(Engine * engine,long n,const GpReal * px,const GpReal * py,const GpReal * qx,const GpReal * qy)1020 DrawDisjoint(Engine *engine, long n, const GpReal *px,
1021              const GpReal *py, const GpReal *qx, const GpReal *qy)
1022 {
1023   XEngine *xeng = (XEngine *)engine;
1024   p_win *w = xeng->w;
1025   long i, imax;
1026   int nseg;
1027 
1028   if (!w || !xeng->mapped) return 1;
1029   if (SetupLine(xeng, &gistA.l, 1)) return 0;
1030 
1031   p_d_pnts(w, px, py, 0);
1032   for (i=0 ; i<n ;) {
1033     imax = i+1024;
1034     nseg = (imax<=n)? 1024 : (int)(n-i);
1035     while (nseg--) {
1036       p_d_pnts(w, px+i, py+i, -1);
1037       p_d_pnts(w, qx+i, qy+i, -1);
1038       i++;
1039     }
1040     p_segments(w);
1041   }
1042 
1043   xeng->e.marked = 1;
1044   return 0;
1045 }
1046 
1047 /* ------------------------------------------------------------------------ */
1048 
1049 static Engine *waiting_for = 0;
1050 static void (*wait_callback)(void) = 0;
1051 
1052 int
gist_expose_wait(Engine * eng,void (* e_callback)(void))1053 gist_expose_wait(Engine *eng, void (*e_callback)(void))
1054 {
1055   if (waiting_for) {
1056     waiting_for = 0;
1057     wait_callback = 0;
1058     return 1;
1059   } else {
1060     XEngine *xeng = GisXEngine(eng);
1061     if (!xeng || !xeng->w) return 1;
1062     if (xeng->mapped) return 2;
1063     waiting_for = eng;
1064     wait_callback = e_callback;
1065     return 0;
1066   }
1067 }
1068 
1069 static void
g_on_expose(void * c,int * xy)1070 g_on_expose(void *c, int *xy)
1071 {
1072   XEngine *xeng = c;
1073   if (xeng->e.on==&g_x_on) {
1074     if (c && c == waiting_for) {
1075       waiting_for = 0;
1076       if (wait_callback) wait_callback();
1077       wait_callback = 0;
1078     }
1079     if (!xeng->w) return;
1080     xeng->mapped = 1;
1081     if (xeng->HandleExpose)
1082       /* the alternate handler should probably call GxExpose */
1083       xeng->HandleExpose(&xeng->e, xeng->e.drawing, xy);
1084     else
1085       GxExpose(&xeng->e, xeng->e.drawing, xy);
1086   } else if (xeng->e.on && xeng->e.on->expose) {
1087     xeng->e.on->expose(c, xy);
1088   }
1089 }
1090 
1091 static void
g_on_click(void * c,int b,int md,int x,int y,unsigned long ms)1092 g_on_click(void *c,int b,int md,int x,int y, unsigned long ms)
1093 {
1094   XEngine *xeng = c;
1095   if (xeng->e.on==&g_x_on) {
1096     if (!xeng->w) return;
1097     if (xeng->HandleClick)
1098       xeng->HandleClick(&xeng->e, b, md, x, y, ms);
1099   } else if (xeng->e.on && xeng->e.on->click) {
1100     xeng->e.on->click(c, b, md, x, y, ms);
1101   }
1102 }
1103 
1104 static void
g_on_motion(void * c,int md,int x,int y)1105 g_on_motion(void *c,int md,int x,int y)
1106 {
1107   XEngine *xeng = c;
1108   gxCurrentEngine = (Engine *)c;
1109   if (xeng->e.on==&g_x_on) {
1110     if (!xeng->w) return;
1111     if (xeng->HandleMotion)
1112       xeng->HandleMotion(&xeng->e, md, x, y);
1113   } else if (xeng->e.on && xeng->e.on->motion) {
1114     xeng->e.on->motion(c, md, x, y);
1115   }
1116 }
1117 
1118 static void
g_on_destroy(void * c)1119 g_on_destroy(void *c)
1120 {
1121   XEngine *xeng = c;
1122   if (xeng->e.on==&g_x_on) {
1123     /* if xeng->win==0, assume ShutDown already called */
1124     if (xeng->win) ShutDown(xeng);
1125   } else if (xeng->e.on && xeng->e.on->destroy) {
1126     xeng->e.on->destroy(c);
1127   }
1128 }
1129 
1130 static void
g_on_resize(void * c,int w,int h)1131 g_on_resize(void *c,int w,int h)
1132 {
1133   XEngine *xeng = c;
1134   if (xeng->e.on==&g_x_on) {
1135     if (xeng->w) GxRecenter(xeng, w, h);
1136   } else if (xeng->e.on && xeng->e.on->resize) {
1137     xeng->e.on->resize(c, w, h);
1138   }
1139 }
1140 
1141 static void
g_on_focus(void * c,int in)1142 g_on_focus(void *c,int in)
1143 {
1144   XEngine *xeng = c;
1145   if (in == 2) gxCurrentEngine = NULL; /* current window has lost mouse focus */
1146   if (xeng->e.on==&g_x_on) {
1147     if (xeng->w && xeng->HandleMotion && in==2)
1148       xeng->HandleMotion(&xeng->e, 0, -1, -1);
1149   } else if (xeng->e.on && xeng->e.on->focus) {
1150     xeng->e.on->focus(c, in);
1151   }
1152 }
1153 
1154 static void
g_on_key(void * c,int k,int md)1155 g_on_key(void *c,int k,int md)
1156 {
1157   XEngine *xeng = c;
1158   if (xeng->e.on==&g_x_on) {
1159     if (xeng->w && xeng->HandleKey)
1160       xeng->HandleKey(&xeng->e, k, md);
1161   } else if (xeng->e.on && xeng->e.on->key) {
1162     xeng->e.on->key(c, k , md);
1163   }
1164 }
1165 
g_on_deselect(void * c)1166 static void g_on_deselect(void *c)
1167 {
1168   XEngine *xeng = c;
1169   if (xeng->e.on==&g_x_on) {
1170   } else if (xeng->e.on && xeng->e.on->deselect) {
1171     xeng->e.on->deselect(c);
1172   }
1173 }
1174 
1175 void
GxExpose(Engine * engine,Drauing * drawing,int * xy)1176 GxExpose(Engine *engine, Drauing *drawing, int *xy)
1177 {
1178   XEngine *xeng = (XEngine *)engine;
1179   GpBox damage;
1180   if (!drawing || !xeng->w) return;
1181   /* xy=0 to redraw all, otherwise x0,y0,x1,y1 */
1182   if (xy) {
1183     GpXYMap *map = &engine->devMap;
1184     damage.xmin= (xy[0]-map->x.offset)/map->x.scale;
1185     damage.xmax= (xy[2]-map->x.offset)/map->x.scale;
1186     damage.ymax= (xy[1]-map->y.offset)/map->y.scale;
1187     damage.ymin= (xy[3]-map->y.offset)/map->y.scale;
1188   } else {
1189     damage.xmin = xeng->swapped.viewport.xmin;
1190     damage.xmax = xeng->swapped.viewport.xmax;
1191     damage.ymin = xeng->swapped.viewport.ymin;
1192     damage.ymax = xeng->swapped.viewport.ymax;
1193   }
1194   if (engine->damaged) {
1195     GpSwallow(&engine->damage, &damage);
1196   } else {
1197     engine->damage = damage;
1198     engine->damaged = 1;
1199   }
1200   GdSetDrawing(drawing);
1201   GpPreempt(engine);
1202   GdDraw(1);
1203   GpPreempt(0);        /* not correct if damaged during a preempt... */
1204   GdSetDrawing(0);
1205 }
1206 
1207 void
GxRecenter(XEngine * xeng,int width,int height)1208 GxRecenter(XEngine *xeng, int width, int height)
1209 {
1210   int x, y;
1211   int eWidth = xeng->width;
1212   int eHeight = xeng->height;
1213   width -= xeng->leftMargin;
1214   height -= xeng->topMargin;
1215   xeng->wtop = width;
1216   xeng->htop = height;
1217   x = (eWidth-width)/2;
1218   /* put center of page at center of landscape window */
1219   if (eWidth>eHeight) y = (eHeight-height)/2;
1220   /* put center of upper square of page at center of portrait window */
1221   else y = (eWidth-height)/2;
1222   /* once either dimension is big enough for whole picture, stop moving it */
1223   if (y<0) y = 0;
1224   if (x<0) x = 0;
1225   if (x!=xeng->x || y!=xeng->y) {
1226     int tmargin = xeng->topMargin;
1227     int lmargin = xeng->leftMargin;
1228     gx_translate(&xeng->swapped.window, -x+lmargin, -y+tmargin);
1229     if (xeng->w == xeng->win) {
1230       gx_translate(&xeng->e.transform.window, -x+lmargin, -y+tmargin);
1231       GpDeviceMap(&xeng->e);
1232     } else {
1233       xeng->a_x -= x - xeng->x;
1234       xeng->a_y -= y - xeng->y;
1235       lmargin = tmargin = 0;
1236     }
1237     xeng->x = x;
1238     xeng->y = y;
1239     if (xeng->wtop>0) x = xeng->wtop+lmargin;
1240     else x = lmargin+1;
1241     if (xeng->htop>0) y = xeng->htop+tmargin;
1242     else y = tmargin+1;
1243     xeng->clipping = 1;
1244     p_clip(xeng->win, lmargin, tmargin, x, y);
1245   }
1246 }
1247 
1248 /* ------------------------------------------------------------------------ */
1249 
1250 typedef struct g_scr g_scr;
1251 struct g_scr {
1252   char *name;
1253   int number;
1254   p_scr *s;
1255 };
1256 static g_scr *g_screens = 0;
1257 static int n_screens = 0;
1258 
1259 /* ARGSUSED */
1260 void
g_initializer(int * pargc,char * argv[])1261 g_initializer(int *pargc, char *argv[])
1262 {
1263   extern char *g_argv0;
1264   g_argv0 = argv? argv[0] : 0;
1265   p_gui(&g_on_expose, &g_on_destroy, &g_on_resize, &g_on_focus,
1266         &g_on_key, &g_on_click, &g_on_motion, &g_on_deselect,
1267         &g_on_panic);
1268 }
1269 
1270 p_scr *
g_connect(char * displayName)1271 g_connect(char *displayName)
1272 {
1273   p_scr *s = 0;
1274   int i, j, i0=-1, len=0, number=0;
1275 
1276   /* split display into base name and screen number (separated by dot) */
1277   if (displayName) while (displayName[len]) len++;
1278   if (len) {
1279     for (i=len-1 ; i>=0 ; i--) if (displayName[i]=='.') break;
1280     if (i>=0) {
1281       int i0 = i;
1282       for (i++ ; i<len && displayName[i]<='9' && displayName[i]>='0' ; i++)
1283         number = 10*number + (displayName[i]-'0');
1284       if (i == len) len = i0;
1285       else number = 0;
1286     }
1287   }
1288   if (!len) displayName = 0;
1289   if (g_screens) {
1290     for (i=0 ; i<n_screens ; i++) {
1291       j = 0;
1292       if (g_screens[i].name) {
1293         for ( ; j<len ; j++)
1294           if (g_screens[i].s && g_screens[i].name[j]!=displayName[j]) break;
1295       }
1296       if (j==len && (len? (!g_screens[i].name[j]) : !g_screens[i].name)) {
1297         if (number == g_screens[i].number) break;
1298         else if (i0<0) i0 = i;
1299       }
1300     }
1301     if (i<n_screens) s = g_screens[i].s;
1302   }
1303   if (!s) {
1304     if (i0<0) s = p_connect(displayName);
1305     else s = p_multihead(g_screens[i0].s, number);
1306     if (!s) return s;
1307     for (i=0 ; i<n_screens ; i++) if (!g_screens[i].s) break;
1308     if (i==n_screens && !(i & (i-1))) {
1309       int n = i? 2*i : 1;
1310       g_screens = p_realloc(g_screens, sizeof(g_scr)*n);
1311     }
1312     g_screens[i].number = number;
1313     g_screens[i].name = displayName? p_strncat(0, displayName, len) : 0;
1314     g_screens[i].s = s;
1315     if (i==n_screens) n_screens++;
1316   }
1317 
1318   return s;
1319 }
1320 
1321 void
g_disconnect(p_scr * s)1322 g_disconnect(p_scr *s)
1323 {
1324   if (s) {
1325     int i;
1326     char *name;
1327     for (i=0 ; i<n_screens ; i++) {
1328       if (g_screens[i].s == s) {
1329         name = g_screens[i].name;
1330         g_screens[i].name = 0;
1331         g_screens[i].s = 0;
1332         p_free(name);
1333       }
1334     }
1335     p_disconnect(s);
1336   } else {
1337     g_do_disconnect();
1338   }
1339 }
1340 
1341 XEngine *
GxEngine(p_scr * s,char * name,GpTransform * toPixels,int x,int y,int topMargin,int leftMargin,long engineSize)1342 GxEngine(p_scr *s, char *name, GpTransform *toPixels,
1343          int x, int y, int topMargin, int leftMargin, long engineSize)
1344 {
1345   XEngine *xEngine;
1346   unsigned int width, height;
1347   GpReal pixels_per_page;
1348   int dpi;
1349 
1350   if (!s) return 0;
1351 
1352   /* Graphics window will have dimensions of toPixels transform window */
1353   if (toPixels->window.xmin<toPixels->window.xmax)
1354     width = (unsigned int)(toPixels->window.xmax - toPixels->window.xmin);
1355   else
1356     width = (unsigned int)(toPixels->window.xmin - toPixels->window.xmax);
1357   if (toPixels->window.ymin<toPixels->window.ymax)
1358     height = (unsigned int)(toPixels->window.ymax - toPixels->window.ymin);
1359   else
1360     height = (unsigned int)(toPixels->window.ymin - toPixels->window.ymax);
1361 
1362   /* Reconstruct dpi (dots per inch) from toPixels transform */
1363   pixels_per_page = toPixels->window.ymin;
1364   if (pixels_per_page < toPixels->window.xmax)
1365     pixels_per_page = toPixels->window.xmax;
1366   dpi = (int)(0.01 + pixels_per_page*ONE_INCH/gPortrait.ymax);
1367 
1368   /* adjust VDC window so GpDeviceMap in GpNewEngine sets proper
1369    * transform, which will have xmin=x<0, ymax=y<0 */
1370   gx_translate(&toPixels->window, x+leftMargin, y+topMargin);
1371 
1372   xEngine =
1373     (XEngine *)GpNewEngine(engineSize, name, &g_x_on, toPixels, width>height,
1374                            &Kill, &Clear, &Flush, &ChangeMap,
1375                            &ChangePalette, &DrawLines, &DrawMarkers,
1376                            &DrwText, &DrawFill, &DrawCells,
1377                            &DrawDisjoint);
1378   if (!xEngine) {
1379     strcpy(gistError, "memory manager failed in GxEngine");
1380     return 0;
1381   }
1382 
1383   /* XEngines can repair damage */
1384   xEngine->e.ClearArea = &ClearArea;
1385 
1386   /* Fill in Engine properties specific to XEngine */
1387   xEngine->s = s;
1388   xEngine->win = 0;
1389   xEngine->width = width;
1390   xEngine->height = height;
1391   xEngine->topMargin = topMargin;
1392   xEngine->leftMargin = leftMargin;
1393   xEngine->x = -x;
1394   xEngine->y = -y;
1395   xEngine->mapped = xEngine->clipping = 0;
1396   xEngine->dpi = dpi;
1397 
1398   xEngine->e.colorMode = 0;
1399 
1400   xEngine->w = 0;
1401   xEngine->a_width = xEngine->a_height= 0;
1402   xEngine->a_x = xEngine->a_y= 0;
1403   xEngine->swapped = xEngine->e.transform;
1404 
1405   xEngine->HandleExpose = 0;
1406   xEngine->HandleClick = 0;
1407   xEngine->HandleMotion = 0;
1408   xEngine->HandleKey = 0;
1409 
1410   return xEngine;
1411 }
1412 
1413 /* default top window represents 6 inch square */
1414 int gx75width = 450;
1415 int gx100width = 600;
1416 int gx75height = 450;
1417 int gx100height = 600;
1418 
1419 unsigned long gx_parent = 0;
1420 int gx_xloc=0, gx_yloc=0;
1421 
1422 int gist_private_map = 0;
1423 int gist_input_hint = 0;
1424 int gist_rgb_hint = 0;
1425 
1426 Engine *
GpBXEngine(char * name,int landscape,int dpi,char * displayName)1427 GpBXEngine(char *name, int landscape, int dpi, char *displayName)
1428 {
1429   p_scr *s = g_connect(displayName);
1430   int topWidth = DefaultTopWidth(dpi);
1431   int topHeight = DefaultTopHeight(dpi);
1432   GpTransform toPixels;
1433   int x, y, width, height, hints;
1434   XEngine *xeng;
1435 
1436   if (!s) return 0;
1437 
1438   SetXTransform(&toPixels, landscape, dpi);
1439   width = (int)toPixels.window.xmax;
1440   height = (int)toPixels.window.ymin;
1441   x = (width-topWidth)/2;
1442   if (landscape) y = (height-topHeight)/2;
1443   else y = (width-topHeight)/2;
1444   if (y<0) y = 0;
1445   if (x<0) x = 0;
1446   xeng = GxEngine(s, name, &toPixels, -x,-y,0,0, sizeof(XEngine));
1447 
1448   xeng->wtop = topWidth;
1449   xeng->htop = topHeight;
1450   /* possibly want optional P_RGBMODEL as well */
1451   hints = (gist_private_map?P_PRIVMAP:0) | (gist_input_hint?0:P_NOKEY) |
1452     (gist_rgb_hint?P_RGBMODEL:0);
1453   xeng->win = xeng->w = gx_parent?
1454     p_subwindow(s, topWidth, topHeight,
1455                 gx_parent, gx_xloc, gx_yloc, P_BG, hints, xeng) :
1456     p_window(s, topWidth, topHeight, name, P_BG, hints, xeng);
1457   gx_parent = 0;
1458   if (!xeng->win) {
1459     GpDelEngine(&xeng->e);
1460     return 0;
1461   }
1462 
1463   return &xeng->e;
1464 }
1465 
1466 int
GxInput(Engine * engine,void (* HandleExpose)(Engine *,Drauing *,int *),void (* HandleClick)(Engine *,int,int,int,int,unsigned long),void (* HandleMotion)(Engine *,int,int,int),void (* HandleKey)(Engine *,int,int))1467 GxInput(Engine *engine,
1468         void (*HandleExpose)(Engine *, Drauing *, int *),
1469         void (*HandleClick)(Engine *,int,int,int,int,unsigned long),
1470         void (*HandleMotion)(Engine *,int,int,int),
1471         void (*HandleKey)(Engine *,int,int))
1472 {
1473   XEngine *xeng = GisXEngine(engine);
1474   if (!xeng) return 1;
1475   xeng->HandleExpose = HandleExpose;
1476   xeng->HandleClick = HandleClick;
1477   xeng->HandleMotion = HandleMotion;
1478   xeng->HandleKey = HandleKey;
1479   return 0;
1480 }
1481 
1482 XEngine *
GisXEngine(Engine * engine)1483 GisXEngine(Engine *engine)
1484 {
1485   return (engine && engine->on==&g_x_on)? (XEngine *)engine : 0;
1486 }
1487 
1488 /* ------------------------------------------------------------------------ */
1489 
1490 int
GxAnimate(Engine * engine,GpBox * viewport)1491 GxAnimate(Engine *engine, GpBox *viewport)
1492 {
1493   XEngine *xeng = GisXEngine(engine);
1494   int x, y, x1, y1;
1495   GpBox *v, *w;
1496   GpReal xmin, xmax, ymin, ymax;
1497   GpReal scalx, offx, scaly, offy;
1498 
1499   if (!xeng || !xeng->w) return 1;
1500   if (xeng->w!=xeng->win) GxDirect(engine);
1501 
1502   v = &xeng->e.transform.viewport;  /* NDC */
1503   w = &xeng->e.transform.window;    /* pixels */
1504 
1505   /* get NDC-->pixel mapping coefficients */
1506   scalx = xeng->e.devMap.x.scale;
1507   offx = xeng->e.devMap.x.offset;
1508   scaly = xeng->e.devMap.y.scale;
1509   offy = xeng->e.devMap.y.offset;
1510 
1511   /* clip given viewport to portion of NDC space which is actually
1512    * visible now -- note that v is either gLandscape or gPortrait,
1513    * so that min<max for v; must also be true for input viewport */
1514   GetVisibleNDC(xeng, &xmin, &xmax, &ymin, &ymax);
1515   if (viewport->xmin>xmin) xmin = viewport->xmin;
1516   if (viewport->xmax<xmax) xmax = viewport->xmax;
1517   if (viewport->ymin>ymin) ymin = viewport->ymin;
1518   if (viewport->ymax<ymax) ymax = viewport->ymax;
1519 
1520   /* install NDC-->pixel transform for animation pixmap */
1521   v->xmin = xmin;
1522   v->xmax = xmax;
1523   v->ymin = ymin;
1524   v->ymax = ymax;
1525 
1526   /* set the engine transform to map the specified viewport into
1527    * offscreen pixels, and get (x,y) offset from full window pixels
1528    * to offscreen pixels */
1529   w->xmin = scalx*xmin+offx;
1530   w->xmax = scalx*xmax+offx;
1531   if (w->xmax > w->xmin) {
1532     x = (int)w->xmin;
1533     w->xmax -= w->xmin;
1534     w->xmin = 0.0;
1535   } else {
1536     x = (int)w->xmax;
1537     w->xmin -= w->xmax;
1538     w->xmax = 0.0;
1539   }
1540   w->ymin = scaly*ymin+offy;
1541   w->ymax = scaly*ymax+offy;
1542   if (w->ymax > w->ymin) {
1543     y = (int)w->ymin;
1544     w->ymax -= w->ymin;
1545     w->ymin = 0.0;
1546   } else {
1547     y = (int)w->ymax;
1548     w->ymin -= w->ymax;
1549     w->ymax = 0.0;
1550   }
1551   GpDeviceMap((Engine *)xeng);
1552   /* GetXRectangle(&xeng->e.devMap, v, &x0, &y0, &x1, &y1);
1553      x1 -= x0;
1554      y1 -= y0;
1555   */
1556   x1 = xeng->wtop;
1557   y1 = xeng->htop;
1558 
1559   /* create the offscreen pixmap */
1560   xeng->w = p_offscreen(xeng->win, x1, y1);
1561   if (!xeng->w) {
1562     xeng->w = xeng->win;
1563     xeng->e.transform = xeng->swapped;
1564     GpDeviceMap((Engine *)xeng);
1565     return 2;
1566   }
1567   xeng->a_width = x1;
1568   xeng->a_height = y1;
1569   xeng->a_x = x;
1570   xeng->a_y = y;
1571 
1572   /* set coordinate mapping for offscreen */
1573   ChangeMap((Engine *)xeng);
1574 
1575   /* reset mapping clip to whole visible window */
1576   if (xeng->wtop>0) x1 = xeng->wtop+xeng->leftMargin;
1577   else x1 = xeng->leftMargin+1;
1578   if (xeng->htop>0) y1 = xeng->htop+xeng->topMargin;
1579   else y1 = xeng->topMargin+1;
1580   xeng->clipping = 1;
1581   p_clip(xeng->win, xeng->leftMargin, xeng->topMargin, x1, y1);
1582 
1583   p_clear(xeng->w);
1584   return 0;
1585 }
1586 
1587 static void
GetVisibleNDC(XEngine * xeng,GpReal * xn,GpReal * xx,GpReal * yn,GpReal * yx)1588 GetVisibleNDC(XEngine *xeng,
1589               GpReal *xn, GpReal *xx, GpReal *yn, GpReal *yx)
1590 {
1591   GpReal scalx = xeng->e.devMap.x.scale;
1592   GpReal offx = xeng->e.devMap.x.offset;
1593   GpReal scaly = xeng->e.devMap.y.scale;
1594   GpReal offy = xeng->e.devMap.y.offset;
1595   int xmin, xmax, ymin, ymax;
1596 
1597   xmin = xeng->leftMargin;
1598   xmax = xmin+xeng->wtop;
1599   ymax = xeng->topMargin;
1600   ymin = ymax+xeng->htop;
1601 
1602   /* Convert pixels to NDC coordinates */
1603   *xn = (xmin-offx)/scalx;
1604   *xx = (xmax-offx)/scalx;
1605   *yn = (ymin-offy)/scaly;
1606   *yx = (ymax-offy)/scaly;
1607 }
1608 
1609 int
GxStrobe(Engine * engine,int clear)1610 GxStrobe(Engine *engine, int clear)
1611 {
1612   XEngine *xeng = GisXEngine(engine);
1613 
1614   if (!xeng || !xeng->w || xeng->w==xeng->win) return 1;
1615 
1616   p_bitblt(xeng->win, xeng->a_x, xeng->a_y, xeng->w,
1617            0, 0, xeng->a_width, xeng->a_height);
1618   if (clear) p_clear(xeng->w);
1619 
1620   return 0;
1621 }
1622 
1623 int
GxDirect(Engine * engine)1624 GxDirect(Engine *engine)
1625 {
1626   XEngine *xeng = GisXEngine(engine);
1627 
1628   if (!xeng || !xeng->w || xeng->w==xeng->win) return 1;
1629 
1630   p_destroy(xeng->w);
1631   xeng->w = xeng->win;
1632 
1633   /* set coordinate map and clipping to values for graphics window */
1634   xeng->e.transform = xeng->swapped;
1635   GpDeviceMap((Engine *)xeng);
1636   ChangeMap((Engine *)xeng);
1637 
1638   return 0;
1639 }
1640 
1641 /* ------------------------------------------------------------------------ */
1642 
1643 void (*HLevelHook)(Engine *engine)= 0;
1644 
1645 static void
g_do_disconnect(void)1646 g_do_disconnect(void)
1647 {
1648   if (g_screens) {
1649     p_scr *s;
1650     int i;
1651     for (i=n_screens-1 ; i>=0 ; i--) {
1652       s = g_screens[i].s;
1653       if (s && !p_wincount(s)) g_disconnect(s);
1654     }
1655   }
1656   g_pending_task = 0;
1657 }
1658 
1659 static void
ShutDown(XEngine * xeng)1660 ShutDown(XEngine *xeng)
1661 {
1662   p_scr *s = xeng->s;
1663   p_win *w = xeng->w;
1664   p_win *win = xeng->win;
1665   if ((Engine *)xeng == gxCurrentEngine) gxCurrentEngine = NULL;
1666   xeng->mapped= 0;
1667   /* turn off all event callbacks (probably unnecessary) */
1668   xeng->e.on = 0;
1669   /* destroy any hlevel references without further ado */
1670   if (HLevelHook) HLevelHook((Engine *)xeng);
1671   xeng->w = xeng->win = 0;
1672   xeng->s = 0;
1673   /* note that p_destroy and GpDelEngine call on_destroy in Windows */
1674   if (w && w!=win) p_destroy(w);
1675   GpDelEngine(&xeng->e);
1676   if (s) {
1677     if (!p_wincount(s))
1678       g_pending_task = g_do_disconnect;
1679   }
1680 }
1681 
1682 static void (*XErrHandler)(char *errMsg)= 0;
1683 
1684 static void
g_on_panic(p_scr * screen)1685 g_on_panic(p_scr *screen)
1686 {
1687   Engine *eng = 0;
1688   XEngine *xeng = 0;
1689   do {
1690     for (eng=GpNextEngine(eng) ; eng ; eng=GpNextEngine(eng)) {
1691       xeng= GisXEngine(eng);
1692       if (xeng && xeng->s==screen) break;
1693     }
1694     if (eng) {
1695       xeng->s = 0;  /* be sure not to call p_disconnect */
1696       Kill(eng);
1697     }
1698   } while (eng);
1699   XErrHandler("play on_panic called (screen graphics engines killed)");
1700 }
1701 
1702 /* this routine actually calls the XErrHandler, which may not return
1703    and/or which may trigger additional X protocol requests */
GxErrorHandler(void)1704 static void GxErrorHandler(void)
1705 {
1706   char msg[80];
1707   gxErrorFlag= 0;
1708   XErrHandler(msg);
1709 }
1710 
GpSetXHandler(void (* ErrHandler)(char * errMsg))1711 int GpSetXHandler(void (*ErrHandler)(char *errMsg))
1712 {
1713   /* install X error handlers which don't call exit */
1714   XErrHandler= ErrHandler;
1715   return 0;
1716 }
1717 
1718 /* ------------------------------------------------------------------------ */
1719 
1720 int
g_rgb_read(Engine * eng,GpColor * rgb,long * nx,long * ny)1721 g_rgb_read(Engine *eng, GpColor *rgb, long *nx, long *ny)
1722 {
1723   XEngine *xeng = GisXEngine(eng);
1724   if (!xeng || !xeng->w || !xeng->win) return 1;
1725   GpPreempt(eng);
1726   GdDraw(1);       /* make sure screen updated */
1727   GpPreempt(0);
1728   if (xeng->w == xeng->win) {
1729     /* not in animate mode */
1730     if (!rgb) {
1731       *nx = xeng->wtop;
1732       *ny = xeng->htop;
1733     } else {
1734       p_rgb_read(xeng->win, rgb, xeng->leftMargin, xeng->topMargin,
1735                  xeng->leftMargin+xeng->wtop, xeng->topMargin+xeng->htop);
1736     }
1737   } else {
1738     /* in animate mode, read offscreen pixmap */
1739     if (!rgb) {
1740       *nx = xeng->a_width;
1741       *ny = xeng->a_height;
1742     } else {
1743       p_rgb_read(xeng->w, rgb, 0, 0, xeng->a_width, xeng->a_height);
1744     }
1745   }
1746   return 0;
1747 }
1748 
1749 /* ------------------------------------------------------------------------ */
1750