/* * $Id: xbasic.c,v 1.1.1.1 2005/09/18 22:04:21 dhmunro Exp $ * Implement the basic X windows engine for GIST. */ /* Copyright (c) 2005, The Regents of the University of California. * All rights reserved. * This file is part of yorick (http://yorick.sourceforge.net). * Read the accompanying LICENSE file for details. */ #include "xbasic.h" #include "gtext.h" #include "pstdlib.h" #include static void g_on_expose(void *c, int *xy); static void g_on_destroy(void *c); static void g_on_resize(void *c,int w,int h); static void g_on_focus(void *c,int in); static void g_on_key(void *c,int k,int md); static void g_on_click(void *c,int b,int md,int x,int y, unsigned long ms); static void g_on_motion(void *c,int md,int x,int y); static void g_on_deselect(void *c); static void g_on_panic(p_scr *screen); static g_callbacks g_x_on = { "gist XEngine", g_on_expose, g_on_destroy, g_on_resize, g_on_focus, g_on_key, g_on_click, g_on_motion, g_on_deselect }; static int ChangePalette(Engine *engine); static GpReal TextWidth(const char *text, int nc, const GpTextAttribs *t); static void Kill(Engine *engine); static void ShutDown(XEngine *xEngine); static int Clear(Engine *engine, int always); static int Flush(Engine *engine); static void GetXRectangle(GpXYMap *map, GpBox *box, int *x0, int *y0, int *x1, int *y1); static void ClearArea(Engine *engine, GpBox *box); static void SetXTransform(GpTransform *trans, int landscape, int dpi); static void gx_translate(GpBox *trans_window, int x, int y); static GpBox *DamageClip(GpBox *damage); static void ChangeMap(Engine *engine); static void chk_clipping(XEngine *xeng); static int SetupLine(XEngine *xeng, GpLineAttribs *gistAl, int join); static int DrawLines(Engine *engine, long n, const GpReal *px, const GpReal *py, int closed, int smooth); static int DrawMarkers(Engine *engine, long n, const GpReal *px, const GpReal *py); static int DrwText(Engine *engine, GpReal x0, GpReal y0, const char *text); static int DrawFill(Engine *engine, long n, const GpReal *px, const GpReal *py); 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); static int DrawCells(Engine *engine, GpReal px, GpReal py, GpReal qx, GpReal qy, long width, long height, long nColumns, const GpColor *colors); static int DrawDisjoint(Engine *engine, long n, const GpReal *px, const GpReal *py, const GpReal *qx, const GpReal *qy); static void GetVisibleNDC(XEngine *xeng, GpReal *xn, GpReal *xx, GpReal *yn, GpReal *yx); /* Hook for hlevel.c error handling. */ extern void (*HLevelHook)(Engine *engine); extern int GxJustifyText(GpXYMap *map, GpReal x0, GpReal y0, const char *text, int *ix, int *iy, int xbox[], int ybox[]); extern int GxJustifyNext(const char **text, int *ix, int *iy); static int gxErrorFlag= 0; static void GxErrorHandler(void); /* ------------------------------------------------------------------------ */ static int ChangePalette(Engine *engine) { XEngine *xeng= (XEngine *)engine; p_scr *s = xeng->s; p_win *win = xeng->win; GpColorCell *palette= engine->palette; /* requested palette */ int nColors= engine->nColors; int width, height, depth; if (!s) return 0; depth = p_sshape(s, &width, &height); if (depth > 8) depth = 8; if (nColors > 256) nColors= 256; p_palette(win, palette, nColors); xeng->e.colorChange= 0; return (1<0) { int firstChunk= firstTextLine; const char *txt= text; char c; for ( ; nc-- ; text++) { c= text[0]; if ((nc && c=='!') || c=='^' || c=='_') { if (txtx.scale*x0 + map->x.offset; y0 = map->y.scale*y0 + map->y.offset; if (x0<-16000. || x0>16000. || y0<-16000. || y0>16000.) return -1; current_state = 0; /* call p_font before any calls to TextWidth * - may improve efficiency of p_txwidth, p_txheight */ p_font(current_win, gistA.t.font, current_fsize, gistA.t.orient); /* Set nLines, maxWidth, nChars, prevWidth */ firstTextLine = 1; nChars = prevWidth = chunkWidth = nChunk = supersub = 0; nLines = GtTextShape(text, &gistA.t, &TextWidth, &rwidest); maxWidth = (int)rwidest; /* state bits: 1 - this chunk is superscript 2 - this chunk is subscript (1 and 2 together meaningless) 4 - this chunk is single symbol char */ x_chunks= 0; /* Compute height of one line */ textHeight = p_txheight(current_scr, gistA.t.font, current_fsize, &textAscent); dy_super = (supersub&1)? textAscent/3 : 0; dy_sub = (supersub&2)? textAscent/3 : 0; lineHeight = textHeight+dy_sub+dy_super; /* Compute displacement and bounding box of entire string, relative to specified location */ GtGetAlignment(&gistA.t, &alignH, &alignV); /* Note: xmax= xmin+maxWidth */ if (alignH==TH_LEFT) { dx= xmin= 0; xmax= maxWidth; } else if (alignH==TH_CENTER) { xmax= maxWidth/2; xmin= -xmax; dx= -prevWidth/2; } else { xmax= 0; xmin= -maxWidth; dx= -prevWidth; } /* Note: ymax= ymin+nLines*lineHeight and ymin= dy-textAscent */ if (alignV<=TV_CAP) { dy= textAscent + dy_super; ymin= 0; ymax= nLines*lineHeight; } else if (alignV==TV_HALF) { ymin= textAscent/2; ymax= nLines*lineHeight; dy= ymin-(ymax-lineHeight)/2; ymin= dy-textAscent-dy_super; ymax+= ymin; } else if (alignV==TV_BASE) { dy= -(nLines-1)*lineHeight; ymin= dy-textAscent-dy_super; ymax= textHeight-textAscent + dy_sub; } else { ymin= dy_sub-nLines*lineHeight; dy= ymin+textAscent+dy_super; ymax= dy_sub; } /* handle orientation (path) of text */ if (gistA.t.orient==TX_LEFT) { /* upside down */ int tmp; dx= -dx; dy= -dy; tmp= xmin; xmin= -xmax; xmax= -tmp; tmp= ymin; ymin= -ymax; ymax= -tmp; } else if (gistA.t.orient==TX_UP) { /* reading upwards */ int tmp; tmp= dx; dx= dy; dy= -tmp; tmp= xmin; xmin= ymin; ymin= -xmax; xmax= ymax; ymax= -tmp; } else if (gistA.t.orient==TX_DOWN) { /* reading downwards */ int tmp; tmp= dx; dx= -dy; dy= tmp; tmp= xmin; xmin= -ymax; ymax= xmax; xmax= -ymin; ymin= tmp; } /* get bounding box and adjusted reference point */ ix0 = (int)x0; iy0 = (int)y0; xbox[0] = ix0+xmin; ybox[0] = iy0+ymin; xbox[1] = ix0+xmax; ybox[1] = iy0+ymax; *ix= dx + ix0; *iy= dy + iy0; if (nChunk) p_font(current_win, gistA.t.font, current_fsize, gistA.t.orient); return nChunk; } int GxJustifyNext(const char **text, int *ix, int *iy) { const char *txt= *text+nChunk; int xadj= 0, yadj= 0; char c; nChars-= nChunk; if (!nChars) { /* last chunk was the last one on a line */ txt= GtNextLine(txt, &nChars, 0); if (!txt) return -1; *text= txt; /* scan for end of first chunk */ if (gtDoEscapes) { for (nChunk=0 ; nChunkwin; ShutDown(xeng); if (w) p_destroy(w); /* for program-driven Kill(), can take care of p_disconnect immediately */ g_do_disconnect(); } static int Clear(Engine *engine, int always) { XEngine *xeng = (XEngine *)engine; if (!xeng->w) return 1; if ((always || xeng->e.marked) && xeng->w==xeng->win) { int tm = xeng->topMargin; int lm = xeng->leftMargin; if (tm || lm) { int xmax = (int)xeng->swapped.window.xmax; int ymax = (int)xeng->swapped.window.ymin; if (xmax > lm+xeng->wtop) xmax = lm+xeng->wtop; if (ymax > tm+xeng->htop) ymax = tm+xeng->htop; if (xeng->clipping) { p_clip(xeng->w, 0,0,0,0); xeng->clipping = 0; } p_color(xeng->w, P_BG); p_rect(xeng->w, lm, tm, xmax, ymax, 0); } else { p_clear(xeng->w); } } if (xeng->e.colorChange) ChangePalette(engine); xeng->e.marked = 0; return 0; } static int Flush(Engine *engine) { XEngine *xeng = (XEngine *)engine; if (!xeng->w) return 1; p_flush(xeng->w); /* test whether an X error has been reported */ if (gxErrorFlag) GxErrorHandler(); return 0; } static void GetXRectangle(GpXYMap *map, GpBox *box, int *x0, int *y0, int *x1, int *y1) { /* get corners of clip rectangle in pixel coordinates */ int wmin= (int)(map->x.scale*box->xmin+map->x.offset); int wmax= (int)(map->x.scale*box->xmax+map->x.offset); if (wmax>=wmin) { *x0 = wmin; *x1 = wmax+1; } else { *x0 = wmax; *x1 = wmin+1; } wmin= (int)(map->y.scale*box->ymin+map->y.offset); wmax= (int)(map->y.scale*box->ymax+map->y.offset); if (wmax>=wmin) { *y0 = wmin; *y1 = wmax+1; } else { *y0 = wmax; *y1 = wmin+1; } } static void ClearArea(Engine *engine, GpBox *box) { XEngine *xeng= (XEngine *)engine; p_win *w = xeng->w; int x0, y0, x1, y1; if (!w) return; /* if this is animation mode, do not try to clear window */ if (w==xeng->win) { int lm = xeng->leftMargin; int tm = xeng->topMargin; GetXRectangle(&engine->devMap, box, &x0, &y0, &x1, &y1); if (x0 < lm) x0 = lm; if (x1 > lm+xeng->wtop) x1 = lm+xeng->wtop; if (y0 < tm) y0 = tm; if (y1 > tm+xeng->htop) y1 = tm+xeng->htop; p_color(w, P_BG); p_rect(w, x0, y0, x1, y1, 0); } } static void SetXTransform(GpTransform *trans, int landscape, int dpi) { trans->viewport= landscape? gLandscape : gPortrait; trans->window.xmin= 0.0; trans->window.xmax= PixelsPerNDC(dpi)*trans->viewport.xmax; trans->window.ymin= PixelsPerNDC(dpi)*trans->viewport.ymax; trans->window.ymax= 0.0; } static void gx_translate(GpBox *trans_window, int x, int y) { trans_window->xmax += x - trans_window->xmin; trans_window->xmin = x; trans_window->ymin += y - trans_window->ymax; trans_window->ymax = y; } static GpBox cPort; static GpBox * DamageClip(GpBox *damage) { cPort= gistT.viewport; if (cPort.xmin>cPort.xmax) { GpReal tmp= cPort.xmin; cPort.xmin= cPort.xmax; cPort.xmax= tmp; } if (cPort.ymin>cPort.ymax) { GpReal tmp= cPort.ymin; cPort.ymin= cPort.ymax; cPort.ymax= tmp; } /* (assume damage box is properly ordered) */ if (damage->xmin>cPort.xmin) cPort.xmin= damage->xmin; if (damage->xmaxxmax; if (damage->ymin>cPort.ymin) cPort.ymin= damage->ymin; if (damage->ymaxymax; if (cPort.xmin>cPort.xmax || cPort.ymin>cPort.ymax) return 0; else return &cPort; } static void ChangeMap(Engine *engine) { XEngine *xeng = (XEngine *)engine; p_win *w = xeng->w; int landscape = xeng->width > xeng->height; int x0, y0, x1, y1; GpBox *clipport; if (!w) return; /* check to be sure that landscape/portrait mode hasn't changed */ if (landscape!=xeng->e.landscape) { /* this is probably insane if in animation mode... */ SetXTransform(&xeng->e.transform, xeng->e.landscape, xeng->dpi); xeng->width = (int)xeng->e.transform.window.xmax; xeng->height = (int)xeng->e.transform.window.ymin; xeng->swapped = xeng->e.transform; /* make adjustments to allow for SetXTransform, then recenter */ if (xeng->w != xeng->win) { xeng->a_x += xeng->x+1; xeng->a_y += xeng->y+1; } xeng->x = xeng->y = -1; GxRecenter(xeng, xeng->wtop+xeng->leftMargin, xeng->htop+xeng->topMargin); } /* do generic change map */ GpComposeMap(engine); /* get current clip window */ if (xeng->e.damaged) clipport = DamageClip(&xeng->e.damage); else clipport = &gistT.viewport; if (clipport) { /* set clipping rectangle for this XEngine */ GetXRectangle(&engine->devMap, clipport, &x0, &y0, &x1, &y1); if (xeng->w == xeng->win) { /* additional restriction for vignetting by window borders */ int lm = xeng->leftMargin; int tm = xeng->topMargin; if (x0 < lm) x0 = lm; if (x1 > lm+xeng->wtop) x1 = lm+xeng->wtop; if (y0 < tm) y0 = tm; if (y1 > tm+xeng->htop) y1 = tm+xeng->htop; xeng->clipping = 1; } if (x1<=x0) x1 = x0+1; if (y1<=y0) y1 = y0+1; p_clip(xeng->w, x0, y0, x1, y1); } } static void chk_clipping(XEngine *xeng) { p_win *w = xeng->win; if (!xeng->clipping) { int x0, y0, x1, y1; int lm = xeng->leftMargin; int tm = xeng->topMargin; if (xeng->e.damaged) { GpBox *box = DamageClip(&xeng->e.damage); GpXYMap map; if (xeng->w != w) GpSetMap(&xeng->swapped.viewport, &xeng->swapped.window, &map); else map = xeng->e.devMap; GetXRectangle(&map, box, &x0, &y0, &x1, &y1); /* additional restriction for vignetting by window borders */ if (x0 < lm) x0 = lm; if (x1 > lm+xeng->wtop) x1 = lm+xeng->wtop; if (y0 < tm) y0 = tm; if (y1 > tm+xeng->htop) y1 = tm+xeng->htop; } else { x0 = lm; x1 = lm+xeng->wtop; y0 = tm; y1 = tm+xeng->htop; } xeng->clipping = 1; if (x1<=x0) x1 = x0+1; if (y1<=y0) y1 = y0+1; p_clip(xeng->w, x0, y0, x1, y1); } } /* ------------------------------------------------------------------------ */ static int SetupLine(XEngine *xeng, GpLineAttribs *gistAl, int join) { GpXYMap *map= &xeng->e.map; double xt[2], yt[2]; xt[0] = map->x.scale; xt[1] = map->x.offset; yt[0] = map->y.scale; yt[1] = map->y.offset; p_d_map(xeng->w, xt, yt, 1); chk_clipping(xeng); if (gistAl->type != L_NONE) { int type = gistAl->type-1; int width = (unsigned int)(DEFAULT_LINE_INCHES*xeng->dpi*gistAl->width); if (join) type |= P_SQUARE; p_pen(xeng->w, width, type); p_color(xeng->w, gistAl->color); return 0; } else { return 1; } } static int DrawLines(Engine *engine, long n, const GpReal *px, const GpReal *py, int closed, int smooth) { XEngine *xeng = (XEngine *)engine; p_win *w = xeng->w; long i, imax; int npts; if (!w) return 1; if (n<=0 || SetupLine(xeng, &gistA.l, 0)) return 0; closed = (closed && n>1 && (px[0]!=px[n-1] || py[0]!=py[n-1])); for (i=0 ; i=n) p_d_pnts(w, px, py, -1); p_lines(w); } xeng->e.marked = 1; return 0; } /* ------------------------------------------------------------------------ */ /* play has no polymarker primitive, use GpPseudoMark-- unless user requested tiny points, in which case we use p_dots */ static int DrawMarkers(Engine *engine, long n, const GpReal *px, const GpReal *py) { XEngine *xeng = (XEngine *)engine; p_win *w = xeng->w; if (!w || !xeng->mapped) return 1; xeng->e.marked = 1; if (gistA.m.type!=M_POINT || gistA.m.size>1.5) { return GpPseudoMark(engine, n, px, py); } else { long i, imax; int npts; GpXYMap *map= &xeng->e.map; double xt[2], yt[2]; xt[0] = map->x.scale; xt[1] = map->x.offset; yt[0] = map->y.scale; yt[1] = map->y.offset; p_d_map(w, xt, yt, 1); chk_clipping(xeng); p_color(w, gistA.m.color); for (i=0 ; iw; GpXYMap *map = &xeng->e.map; int ix, iy, len, xbox[2], ybox[2], xn, xx, yn, yx; const char *txt; char *caret= "^"; if (!w || !xeng->mapped) return 1; chk_clipping(xeng); current_fsize = (int)((xeng->dpi/ONE_INCH)*gistA.t.height); if (current_fsize<4) current_fsize = 4; /* totally illegible */ if (current_fsize>180) current_fsize = 180; /* way too big */ current_fsym = T_SYMBOL | (gistA.t.font&3); current_scr = xeng->s; current_win = w; /* get current window */ xn = (int)(gistT.window.xmin*map->x.scale + map->x.offset); xx = (int)(gistT.window.xmax*map->x.scale + map->x.offset); yn = (int)(gistT.window.ymin*map->y.scale + map->y.offset); yx = (int)(gistT.window.ymax*map->y.scale + map->y.offset); if (yn > yx) { int tmp=yn ; yn=yx; yx=tmp ; } /* handle multi-line strings */ len = GxJustifyText(map, x0, y0, text, &ix, &iy, xbox, ybox); if (len < 0) return 0; /* consider whether string is completely clipped */ if (ybox[0]>yx || ybox[1]xx || xbox[1]0) { if (len==1 && (current_state&4) && text[0]==']') txt = caret; else txt = text; p_text(w, ix, iy, txt, len); } len = GxJustifyNext(&text, &ix, &iy); } while (len>=0); xeng->e.marked = 1; return 0; } /* ------------------------------------------------------------------------ */ static int DrawFill(Engine *engine, long n, const GpReal *px, const GpReal *py) { XEngine *xeng = (XEngine *)engine; p_win *w = xeng->w; long i, imax; int npts, has_edge; if (!w || !xeng->mapped) return 1; has_edge = !SetupLine(xeng, &gistA.e, 0); /* but shouldn't set color */ p_color(w, gistA.f.color); /* This gives incorrect results if more than one pass through the loop, * but there is no way to give the correct result, so may as well... */ for (i=0 ; ie.marked = 1; if (has_edge) { p_color(w, gistA.e.color); for (i=0 ; i=n) p_d_pnts(w, px, py, -1); p_lines(w); } } return 0; } /* ------------------------------------------------------------------------ */ 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) { GpReal scale = map->scale; GpReal offset = map->offset; GpReal x, dx = (qx-px)/width; int imin, imax; if (xmin>xmax) x=xmin, xmin=xmax, xmax=x; if (dx>=0.0) { if (qxxmax) return 0; if (dx > 0.0) { x = (xmin - px)/dx; if (x < 1.) imin = 0; else imin=(int)x, px+=imin*dx; x = (qx - xmax)/dx; if (x < 1.) imax = 0; else imax=(int)x, qx-=imax*dx; width -= imin + imax; if (width < 3) { /* see comment below */ if (qx <= xmax) { if (imin) px = xmin; } else if (px >= xmin) { if (imax) qx = xmax; } else if (width < 2) { px = xmin; qx = xmax; } else if (qx-xmax <= xmin-px) { px += qx - xmax; qx = xmax; } else { qx += qx - xmin; px = xmin; } } } else { imin = width/2; imax = width - imin - 1; width = 1; } } else { if (pxxmax) return 0; dx = -dx; x = (px - xmax)/dx; if (x < 1.) imin = 0; else imin=(int)x, px-=imin*dx; x = (xmin - qx)/dx; if (x < 1.) imax = 0; else imax=(int)x, qx+=imax*dx; width -= imin + imax; if (width < 3) { /* see comment below */ if (px <= xmax) { if (imax) qx = xmin; } else if (qx >= xmin) { if (imin) px = xmax; } else if (width < 2) { qx = xmin; px = xmax; } else if (px-xmax <= xmin-qx) { qx += px - xmax; px = xmax; } else { px += qx - xmin; qx = xmin; } } } /* width<3 logic above guarantees these will not overflow as int */ px = px*scale + offset; qx = qx*scale + offset; if (qx >= px) { *i0 = imin; *di = 1; *ncols = width; *x0 = (int)px; *x1 = (int)qx; } else { *i0 = imin + width - 1; *di = -1; *ncols = width; *x0 = (int)qx; *x1 = (int)px; } /* cell array always at least 1 pixel */ if (*x1 == *x0) *x1 += 1; return 1; } static int DrawCells(Engine *engine, GpReal px, GpReal py, GpReal qx, GpReal qy, long width, long height, long nColumns, const GpColor *colors) { XEngine *xeng = (XEngine *)engine; p_win *w = xeng->w; GpXYMap *map= &xeng->e.map; int i0, j0, di, dj, ncols, nrows, x0, y0, x1, y1; if (!w || !xeng->mapped) return 1; chk_clipping(xeng); if (GetCells(&map->x, gistT.window.xmin, gistT.window.xmax, px, qx, width, &i0, &di, &ncols, &x0, &x1) && GetCells(&map->y, gistT.window.ymin, gistT.window.ymax, py, qy, height, &j0, &dj, &nrows, &y0, &y1)) { unsigned char *ndxs = (unsigned char *)colors; if (di<0 || dj<0 || ncols!=width || nrows!=height || nColumns!=width) { int r, c, nr, nc, i, j; ndxs = p_malloc(gistA.rgb?3*ncols*nrows:ncols*nrows); j0 *= nColumns; dj *= nColumns; if (gistA.rgb) { for (j=j0,c=0,nr=nrows ; nr-- ; j+=dj,c+=ncols) { nc = ncols; for (i=i0,r=0 ; nc-- ; i+=di,r++) { ndxs[3*(c+r)] = colors[3*(j+i)]; ndxs[3*(c+r)+1] = colors[3*(j+i)+1]; ndxs[3*(c+r)+2] = colors[3*(j+i)+2]; } } } else { for (j=j0,c=0,nr=nrows ; nr-- ; j+=dj,c+=ncols) { nc = ncols; for (i=i0,r=0 ; nc-- ; i+=di,r++) ndxs[c+r] = colors[j+i]; } } } if (ncols && nrows) { if (gistA.rgb) p_rgb_cell(w, ndxs, ncols, nrows, x0, y0, x1, y1); else p_ndx_cell(w, ndxs, ncols, nrows, x0, y0, x1, y1); } if (ndxs!=colors) p_free(ndxs); } xeng->e.marked = 1; return 0; } /* ------------------------------------------------------------------------ */ static int DrawDisjoint(Engine *engine, long n, const GpReal *px, const GpReal *py, const GpReal *qx, const GpReal *qy) { XEngine *xeng = (XEngine *)engine; p_win *w = xeng->w; long i, imax; int nseg; if (!w || !xeng->mapped) return 1; if (SetupLine(xeng, &gistA.l, 1)) return 0; p_d_pnts(w, px, py, 0); for (i=0 ; ie.marked = 1; return 0; } /* ------------------------------------------------------------------------ */ static Engine *waiting_for = 0; static void (*wait_callback)(void) = 0; int gist_expose_wait(Engine *eng, void (*e_callback)(void)) { if (waiting_for) { waiting_for = 0; wait_callback = 0; return 1; } else { XEngine *xeng = GisXEngine(eng); if (!xeng || !xeng->w) return 1; if (xeng->mapped) return 2; waiting_for = eng; wait_callback = e_callback; return 0; } } static void g_on_expose(void *c, int *xy) { XEngine *xeng = c; if (xeng->e.on==&g_x_on) { if (c && c == waiting_for) { waiting_for = 0; if (wait_callback) wait_callback(); wait_callback = 0; } if (!xeng->w) return; xeng->mapped = 1; if (xeng->HandleExpose) /* the alternate handler should probably call GxExpose */ xeng->HandleExpose(&xeng->e, xeng->e.drawing, xy); else GxExpose(&xeng->e, xeng->e.drawing, xy); } else if (xeng->e.on && xeng->e.on->expose) { xeng->e.on->expose(c, xy); } } static void g_on_click(void *c,int b,int md,int x,int y, unsigned long ms) { XEngine *xeng = c; if (xeng->e.on==&g_x_on) { if (!xeng->w) return; if (xeng->HandleClick) xeng->HandleClick(&xeng->e, b, md, x, y, ms); } else if (xeng->e.on && xeng->e.on->click) { xeng->e.on->click(c, b, md, x, y, ms); } } static void g_on_motion(void *c,int md,int x,int y) { XEngine *xeng = c; if (xeng->e.on==&g_x_on) { if (!xeng->w) return; if (xeng->HandleMotion) xeng->HandleMotion(&xeng->e, md, x, y); } else if (xeng->e.on && xeng->e.on->motion) { xeng->e.on->motion(c, md, x, y); } } static void g_on_destroy(void *c) { XEngine *xeng = c; if (xeng->e.on==&g_x_on) { /* if xeng->win==0, assume ShutDown already called */ if (xeng->win) ShutDown(xeng); } else if (xeng->e.on && xeng->e.on->destroy) { xeng->e.on->destroy(c); } } static void g_on_resize(void *c,int w,int h) { XEngine *xeng = c; if (xeng->e.on==&g_x_on) { if (xeng->w) GxRecenter(xeng, w, h); } else if (xeng->e.on && xeng->e.on->resize) { xeng->e.on->resize(c, w, h); } } static void g_on_focus(void *c,int in) { XEngine *xeng = c; if (xeng->e.on==&g_x_on) { if (xeng->w && xeng->HandleMotion && in==2) xeng->HandleMotion(&xeng->e, 0, -1, -1); } else if (xeng->e.on && xeng->e.on->focus) { xeng->e.on->focus(c, in); } } static void g_on_key(void *c,int k,int md) { XEngine *xeng = c; if (xeng->e.on==&g_x_on) { if (xeng->w && xeng->HandleKey) xeng->HandleKey(&xeng->e, k, md); } else if (xeng->e.on && xeng->e.on->key) { xeng->e.on->key(c, k , md); } } static void g_on_deselect(void *c) { XEngine *xeng = c; if (xeng->e.on==&g_x_on) { } else if (xeng->e.on && xeng->e.on->deselect) { xeng->e.on->deselect(c); } } void GxExpose(Engine *engine, Drauing *drawing, int *xy) { XEngine *xeng = (XEngine *)engine; GpBox damage; if (!drawing || !xeng->w) return; /* xy=0 to redraw all, otherwise x0,y0,x1,y1 */ if (xy) { GpXYMap *map = &engine->devMap; damage.xmin= (xy[0]-map->x.offset)/map->x.scale; damage.xmax= (xy[2]-map->x.offset)/map->x.scale; damage.ymax= (xy[1]-map->y.offset)/map->y.scale; damage.ymin= (xy[3]-map->y.offset)/map->y.scale; } else { damage.xmin = xeng->swapped.viewport.xmin; damage.xmax = xeng->swapped.viewport.xmax; damage.ymin = xeng->swapped.viewport.ymin; damage.ymax = xeng->swapped.viewport.ymax; } if (engine->damaged) { GpSwallow(&engine->damage, &damage); } else { engine->damage = damage; engine->damaged = 1; } GdSetDrawing(drawing); GpPreempt(engine); GdDraw(1); GpPreempt(0); /* not correct if damaged during a preempt... */ GdSetDrawing(0); } void GxRecenter(XEngine *xeng, int width, int height) { int x, y; int eWidth = xeng->width; int eHeight = xeng->height; width -= xeng->leftMargin; height -= xeng->topMargin; xeng->wtop = width; xeng->htop = height; x = (eWidth-width)/2; /* put center of page at center of landscape window */ if (eWidth>eHeight) y = (eHeight-height)/2; /* put center of upper square of page at center of portrait window */ else y = (eWidth-height)/2; /* once either dimension is big enough for whole picture, stop moving it */ if (y<0) y = 0; if (x<0) x = 0; if (x!=xeng->x || y!=xeng->y) { int tmargin = xeng->topMargin; int lmargin = xeng->leftMargin; gx_translate(&xeng->swapped.window, -x+lmargin, -y+tmargin); if (xeng->w == xeng->win) { gx_translate(&xeng->e.transform.window, -x+lmargin, -y+tmargin); GpDeviceMap(&xeng->e); } else { xeng->a_x -= x - xeng->x; xeng->a_y -= y - xeng->y; lmargin = tmargin = 0; } xeng->x = x; xeng->y = y; if (xeng->wtop>0) x = xeng->wtop+lmargin; else x = lmargin+1; if (xeng->htop>0) y = xeng->htop+tmargin; else y = tmargin+1; xeng->clipping = 1; p_clip(xeng->win, lmargin, tmargin, x, y); } } /* ------------------------------------------------------------------------ */ typedef struct g_scr g_scr; struct g_scr { char *name; int number; p_scr *s; }; static g_scr *g_screens = 0; static int n_screens = 0; /* ARGSUSED */ void g_initializer(int *pargc, char *argv[]) { extern char *g_argv0; g_argv0 = argv? argv[0] : 0; p_gui(&g_on_expose, &g_on_destroy, &g_on_resize, &g_on_focus, &g_on_key, &g_on_click, &g_on_motion, &g_on_deselect, &g_on_panic); } p_scr * g_connect(char *displayName) { p_scr *s = 0; int i, j, i0=-1, len=0, number=0; /* split display into base name and screen number (separated by dot) */ if (displayName) while (displayName[len]) len++; if (len) { for (i=len-1 ; i>=0 ; i--) if (displayName[i]=='.') break; if (i>=0) { int i0 = i; for (i++ ; i='0' ; i++) number = 10*number + (displayName[i]-'0'); if (i == len) len = i0; else number = 0; } } if (!len) displayName = 0; if (g_screens) { for (i=0 ; iwindow.xminwindow.xmax) width = (unsigned int)(toPixels->window.xmax - toPixels->window.xmin); else width = (unsigned int)(toPixels->window.xmin - toPixels->window.xmax); if (toPixels->window.yminwindow.ymax) height = (unsigned int)(toPixels->window.ymax - toPixels->window.ymin); else height = (unsigned int)(toPixels->window.ymin - toPixels->window.ymax); /* Reconstruct dpi (dots per inch) from toPixels transform */ pixels_per_page = toPixels->window.ymin; if (pixels_per_page < toPixels->window.xmax) pixels_per_page = toPixels->window.xmax; dpi = (int)(0.01 + pixels_per_page*ONE_INCH/gPortrait.ymax); /* adjust VDC window so GpDeviceMap in GpNewEngine sets proper * transform, which will have xmin=x<0, ymax=y<0 */ gx_translate(&toPixels->window, x+leftMargin, y+topMargin); xEngine = (XEngine *)GpNewEngine(engineSize, name, &g_x_on, toPixels, width>height, &Kill, &Clear, &Flush, &ChangeMap, &ChangePalette, &DrawLines, &DrawMarkers, &DrwText, &DrawFill, &DrawCells, &DrawDisjoint); if (!xEngine) { strcpy(gistError, "memory manager failed in GxEngine"); return 0; } /* XEngines can repair damage */ xEngine->e.ClearArea = &ClearArea; /* Fill in Engine properties specific to XEngine */ xEngine->s = s; xEngine->win = 0; xEngine->width = width; xEngine->height = height; xEngine->topMargin = topMargin; xEngine->leftMargin = leftMargin; xEngine->x = -x; xEngine->y = -y; xEngine->mapped = xEngine->clipping = 0; xEngine->dpi = dpi; xEngine->e.colorMode = 0; xEngine->w = 0; xEngine->a_width = xEngine->a_height= 0; xEngine->a_x = xEngine->a_y= 0; xEngine->swapped = xEngine->e.transform; xEngine->HandleExpose = 0; xEngine->HandleClick = 0; xEngine->HandleMotion = 0; xEngine->HandleKey = 0; return xEngine; } /* default top window represents 6 inch square */ int gx75width = 450; int gx100width = 600; int gx75height = 450; int gx100height = 600; int gist_private_map = 0; int gist_input_hint = 0; int gist_rgb_hint = 0; Engine * GpBXEngine(char *name, int landscape, int dpi, char *displayName) { p_scr *s = g_connect(displayName); int topWidth = DefaultTopWidth(dpi); int topHeight = DefaultTopHeight(dpi); GpTransform toPixels; int x, y, width, height, hints; XEngine *xeng; if (!s) return 0; SetXTransform(&toPixels, landscape, dpi); width = (int)toPixels.window.xmax; height = (int)toPixels.window.ymin; x = (width-topWidth)/2; if (landscape) y = (height-topHeight)/2; else y = (width-topHeight)/2; if (y<0) y = 0; if (x<0) x = 0; xeng = GxEngine(s, name, &toPixels, -x,-y,0,0, sizeof(XEngine)); xeng->wtop = topWidth; xeng->htop = topHeight; /* possibly want optional P_RGBMODEL as well */ hints = (gist_private_map?P_PRIVMAP:0) | (gist_input_hint?0:P_NOKEY) | (gist_rgb_hint?P_RGBMODEL:0); xeng->win = xeng->w = p_window(s, topWidth, topHeight, name, P_BG, hints, xeng); if (!xeng->win) { GpDelEngine(&xeng->e); return 0; } return &xeng->e; } 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)) { XEngine *xeng = GisXEngine(engine); if (!xeng) return 1; xeng->HandleExpose = HandleExpose; xeng->HandleClick = HandleClick; xeng->HandleMotion = HandleMotion; xeng->HandleKey = HandleKey; return 0; } XEngine * GisXEngine(Engine *engine) { return (engine && engine->on==&g_x_on)? (XEngine *)engine : 0; } /* ------------------------------------------------------------------------ */ int GxAnimate(Engine *engine, GpBox *viewport) { XEngine *xeng = GisXEngine(engine); int x, y, x1, y1; GpBox *v, *w; GpReal xmin, xmax, ymin, ymax; GpReal scalx, offx, scaly, offy; if (!xeng || !xeng->w) return 1; if (xeng->w!=xeng->win) GxDirect(engine); v = &xeng->e.transform.viewport; /* NDC */ w = &xeng->e.transform.window; /* pixels */ /* get NDC-->pixel mapping coefficients */ scalx = xeng->e.devMap.x.scale; offx = xeng->e.devMap.x.offset; scaly = xeng->e.devMap.y.scale; offy = xeng->e.devMap.y.offset; /* clip given viewport to portion of NDC space which is actually * visible now -- note that v is either gLandscape or gPortrait, * so that minxmin>xmin) xmin = viewport->xmin; if (viewport->xmaxxmax; if (viewport->ymin>ymin) ymin = viewport->ymin; if (viewport->ymaxymax; /* install NDC-->pixel transform for animation pixmap */ v->xmin = xmin; v->xmax = xmax; v->ymin = ymin; v->ymax = ymax; /* set the engine transform to map the specified viewport into * offscreen pixels, and get (x,y) offset from full window pixels * to offscreen pixels */ w->xmin = scalx*xmin+offx; w->xmax = scalx*xmax+offx; if (w->xmax > w->xmin) { x = (int)w->xmin; w->xmax -= w->xmin; w->xmin = 0.0; } else { x = (int)w->xmax; w->xmin -= w->xmax; w->xmax = 0.0; } w->ymin = scaly*ymin+offy; w->ymax = scaly*ymax+offy; if (w->ymax > w->ymin) { y = (int)w->ymin; w->ymax -= w->ymin; w->ymin = 0.0; } else { y = (int)w->ymax; w->ymin -= w->ymax; w->ymax = 0.0; } GpDeviceMap((Engine *)xeng); /* GetXRectangle(&xeng->e.devMap, v, &x0, &y0, &x1, &y1); x1 -= x0; y1 -= y0; */ x1 = xeng->wtop; y1 = xeng->htop; /* create the offscreen pixmap */ xeng->w = p_offscreen(xeng->win, x1, y1); if (!xeng->w) { xeng->w = xeng->win; xeng->e.transform = xeng->swapped; GpDeviceMap((Engine *)xeng); return 2; } xeng->a_width = x1; xeng->a_height = y1; xeng->a_x = x; xeng->a_y = y; /* set coordinate mapping for offscreen */ ChangeMap((Engine *)xeng); /* reset mapping clip to whole visible window */ if (xeng->wtop>0) x1 = xeng->wtop+xeng->leftMargin; else x1 = xeng->leftMargin+1; if (xeng->htop>0) y1 = xeng->htop+xeng->topMargin; else y1 = xeng->topMargin+1; xeng->clipping = 1; p_clip(xeng->win, xeng->leftMargin, xeng->topMargin, x1, y1); p_clear(xeng->w); return 0; } static void GetVisibleNDC(XEngine *xeng, GpReal *xn, GpReal *xx, GpReal *yn, GpReal *yx) { GpReal scalx = xeng->e.devMap.x.scale; GpReal offx = xeng->e.devMap.x.offset; GpReal scaly = xeng->e.devMap.y.scale; GpReal offy = xeng->e.devMap.y.offset; int xmin, xmax, ymin, ymax; xmin = xeng->leftMargin; xmax = xmin+xeng->wtop; ymax = xeng->topMargin; ymin = ymax+xeng->htop; /* Convert pixels to NDC coordinates */ *xn = (xmin-offx)/scalx; *xx = (xmax-offx)/scalx; *yn = (ymin-offy)/scaly; *yx = (ymax-offy)/scaly; } int GxStrobe(Engine *engine, int clear) { XEngine *xeng = GisXEngine(engine); if (!xeng || !xeng->w || xeng->w==xeng->win) return 1; p_bitblt(xeng->win, xeng->a_x, xeng->a_y, xeng->w, 0, 0, xeng->a_width, xeng->a_height); if (clear) p_clear(xeng->w); return 0; } int GxDirect(Engine *engine) { XEngine *xeng = GisXEngine(engine); if (!xeng || !xeng->w || xeng->w==xeng->win) return 1; p_destroy(xeng->w); xeng->w = xeng->win; /* set coordinate map and clipping to values for graphics window */ xeng->e.transform = xeng->swapped; GpDeviceMap((Engine *)xeng); ChangeMap((Engine *)xeng); return 0; } /* ------------------------------------------------------------------------ */ void (*HLevelHook)(Engine *engine)= 0; static void g_do_disconnect(void) { if (g_screens) { p_scr *s; int i; for (i=n_screens-1 ; i>=0 ; i--) { s = g_screens[i].s; if (s && !p_wincount(s)) g_disconnect(s); } } g_pending_task = 0; } static void ShutDown(XEngine *xeng) { p_scr *s = xeng->s; p_win *w = xeng->w; p_win *win = xeng->win; xeng->mapped= 0; /* turn off all event callbacks (probably unnecessary) */ xeng->e.on = 0; /* destroy any hlevel references without further ado */ if (HLevelHook) HLevelHook((Engine *)xeng); xeng->w = xeng->win = 0; xeng->s = 0; /* note that p_destroy and GpDelEngine call on_destroy in Windows */ if (w && w!=win) p_destroy(w); GpDelEngine(&xeng->e); if (s) { if (!p_wincount(s)) g_pending_task = g_do_disconnect; } } static void (*XErrHandler)(char *errMsg)= 0; static void g_on_panic(p_scr *screen) { Engine *eng = 0; XEngine *xeng = 0; do { for (eng=GpNextEngine(eng) ; eng ; eng=GpNextEngine(eng)) { xeng= GisXEngine(eng); if (xeng && xeng->s==screen) break; } if (eng) { xeng->s = 0; /* be sure not to call p_disconnect */ Kill(eng); } } while (eng); XErrHandler("play on_panic called (screen graphics engines killed)"); } /* this routine actually calls the XErrHandler, which may not return and/or which may trigger additional X protocol requests */ static void GxErrorHandler(void) { char msg[80]; gxErrorFlag= 0; XErrHandler(msg); } int GpSetXHandler(void (*ErrHandler)(char *errMsg)) { /* install X error handlers which don't call exit */ XErrHandler= ErrHandler; return 0; } /* ------------------------------------------------------------------------ */ int g_rgb_read(Engine *eng, GpColor *rgb, long *nx, long *ny) { XEngine *xeng = GisXEngine(eng); if (!xeng || !xeng->w || !xeng->win) return 1; GpPreempt(eng); GdDraw(1); /* make sure screen updated */ GpPreempt(0); if (xeng->w == xeng->win) { /* not in animate mode */ if (!rgb) { *nx = xeng->wtop; *ny = xeng->htop; } else { p_rgb_read(xeng->win, rgb, xeng->leftMargin, xeng->topMargin, xeng->leftMargin+xeng->wtop, xeng->topMargin+xeng->htop); } } else { /* in animate mode, read offscreen pixmap */ if (!rgb) { *nx = xeng->a_width; *ny = xeng->a_height; } else { p_rgb_read(xeng->w, rgb, 0, 0, xeng->a_width, xeng->a_height); } } return 0; } /* ------------------------------------------------------------------------ */