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= ⁁
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