/*
* $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 <string.h>
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<<depth);
}
/* ------------------------------------------------------------------------ */
static int nChars, lineHeight, textHeight, prevWidth, maxWidth, alignH;
static int textAscent;
static int firstTextLine= 0;
static p_scr *current_scr;
static p_win *current_win;
static int current_state, current_fsym, current_fsize;
static int chunkWidth, nChunk, supersub, dy_super, dy_sub, x_chunks;
/* ARGSUSED */
static GpReal
TextWidth(const char *text, int nc, const GpTextAttribs *t)
{
int width= 0;
if (firstTextLine) nChars= nc;
if (!gtDoEscapes) {
width = p_txwidth(current_scr, text, nc, gistA.t.font, current_fsize);
if (firstTextLine) {
/* record width of first line */
prevWidth= chunkWidth= width;
nChunk= nc;
firstTextLine= 0;
}
} else if (nc>0) {
int firstChunk= firstTextLine;
const char *txt= text;
char c;
for ( ; nc-- ; text++) {
c= text[0];
if ((nc && c=='!') || c=='^' || c=='_') {
if (txt<text)
width += p_txwidth(current_scr, txt, (int)(text-txt),
gistA.t.font, current_fsize);
if (firstChunk) {
nChunk= (int)(text-txt);
chunkWidth= width;
firstChunk= 0;
}
txt= text+1;
if (c=='!') {
/* process single character escapes immediately */
text= txt;
nc--;
c= txt[0];
if (c!='!' && c!='^' && c!='_') {
if (c==']') c= '^';
width += p_txwidth(current_scr, &c, 1,
current_fsym, current_fsize);
txt++;
}
} else if (c=='^') {
supersub|= 1;
} else {
supersub|= 2;
}
}
}
if (txt<text)
width += p_txwidth(current_scr, txt, (int)(text-txt),
gistA.t.font, current_fsize);
if (firstChunk) {
nChunk= (int)(text-txt);
chunkWidth= width;
}
if (firstTextLine) {
/* record width of first line */
prevWidth= width; /* this is whole line */
firstTextLine= 0;
}
}
return (GpReal)width;
}
int
GxJustifyText(GpXYMap *map, GpReal x0, GpReal y0, const char *text,
int *ix, int *iy, int xbox[], int ybox[])
{
int nLines, alignV, dx, dy, xmin, xmax, ymin, ymax, ix0, iy0;
GpReal rwidest;
/* abort if string screen position is ridiculous */
x0 = map->x.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 ; nChunk<nChars ; nChunk++) {
c= txt[nChunk];
if ((nChunk+1<nChars && c=='!') || c=='^' || c=='_') break;
}
} else {
nChunk= nChars;
}
/* compute width of this chunk if necessary, compute width
of whole line if necessary for justification */
if (alignH!=TH_LEFT || gistA.t.orient!=TX_RIGHT) {
/* need to compute width of entire line */
int width= prevWidth;
firstTextLine= 1;
prevWidth= (int)TextWidth(txt, nChars, &gistA.t);
if (alignH==TH_CENTER) xadj= (width-prevWidth)/2;
else if (alignH==TH_RIGHT) xadj= width-prevWidth;
/* TextWidth sets chunkWidth */
} else if (nChunk<nChars) {
/* just need width of this chunk */
if (nChunk) chunkWidth = p_txwidth(current_scr, txt, nChunk,
gistA.t.font, current_fsize);
else chunkWidth= 0; /* unused */
}
/* reset state, adjusting (possibly rotated) x and y as well */
xadj-= x_chunks;
yadj= lineHeight;
if (current_state&1) yadj+= dy_super;
else if (current_state&2) yadj-= dy_sub;
if (nChunk && (current_state&4))
p_font(current_win, gistA.t.font, current_fsize, gistA.t.orient);
current_state= 0;
} else {
/* previous chunk ended with an escape character, or was single
escaped symbol character -- can't get here unles gtDoEscapes */
char c1= '\0';
xadj= chunkWidth; /* width of previous chunk */
x_chunks+= chunkWidth; /* accumulate all chunks except last */
yadj= 0;
if (!(current_state&4)) {
c1= *txt++;
nChars--;
}
*text= txt;
if (c1=='!') {
/* this chunk begins with escaped character */
nChunk= 1;
c= txt[0];
if (c=='!' || c=='^' || c=='_') {
/* chunk is just ordinary text */
for ( ; nChunk<nChars ; nChunk++) {
c= txt[nChunk];
if ((nChunk+1<nChars && c=='!') || c=='^' || c=='_') break;
}
p_font(current_win, gistA.t.font, current_fsize, gistA.t.orient);
current_state&= 3;
} else {
/* chunk is single symbol char */
p_font(current_win, current_fsym, current_fsize, gistA.t.orient);
current_state|= 4;
}
} else {
for (nChunk=0 ; nChunk<nChars ; nChunk++) {
c= txt[nChunk];
if ((nChunk+1<nChars && c=='!') || c=='^' || c=='_') break;
}
if (nChunk)
p_font(current_win, gistA.t.font, current_fsize, gistA.t.orient);
if (c1=='^') {
if (current_state&1) {
yadj+= dy_super; /* return from super to normal */
current_state= 0;
} else {
if (current_state&2) yadj-= dy_sub;
yadj-= dy_super; /* move to superscript */
current_state= 1;
}
} else if (c1=='_') {
if (current_state&2) {
yadj-= dy_sub; /* return from sub to normal */
current_state= 0;
} else {
if (current_state&1) yadj+= dy_super;
yadj+= dy_sub; /* move to subscript */
current_state= 2;
}
} else {
/* just finished a symbol char */
current_state&= 3;
}
}
if (nChunk &&
(nChunk<nChars || alignH!=TH_LEFT || gistA.t.orient!=TX_RIGHT)) {
char caret= '^';
if (nChunk==1 && (current_state&4) && txt[0]==']') txt= ⁁
chunkWidth = p_txwidth(current_scr, txt, nChunk,
(current_state&4)? current_fsym : gistA.t.font,
current_fsize);
} else {
chunkWidth= 0; /* unused */
}
}
if (gistA.t.orient==TX_RIGHT) {
*iy+= yadj;
*ix+= xadj;
} else if (gistA.t.orient==TX_LEFT) {
*iy-= yadj;
*ix-= xadj;
} else if (gistA.t.orient==TX_UP) {
*ix+= yadj;
*iy-= xadj;
} else {
*ix-= yadj;
*iy+= xadj;
}
return nChunk;
}
/* ------------------------------------------------------------------------ */
/* notes for Kill() and g_on_destroy
* (1) Kill() is program-driven (e.g.- winkill)
* g_on_destroy() is event-driven (e.g.- mouse click)
* (2) however, the p_destroy() function MIGHT or MIGHT NOT
* result in a call to g_on_destroy()
* under Windows, g_on_destroy() is naturally called
* by the call p_destroy() must make to close the window
* under UNIX/X, the play event handler calls p_destroy()
* after g_on_destroy() returns
* (3) therefore g_on_destroy() must not call p_destroy(), since
* this would cause infinite recursion on Windows, and a
* double call on X.
* (4) worse, if this is the final window on an X display, gist
* should disconnect from the display, but that can only
* be done after the final p_destroy(), which is after
* the g_on_destroy() in the event-driven case
* -- thus, the p_disconnect must take place on the next
* event, which is during the GhBeforeWait hlevel.c function
*/
/* hack to disconnect if last engine destroyed (see GhBeforeWait) */
static void g_do_disconnect(void);
extern void (*g_pending_task)(void);
void (*g_pending_task)(void) = 0;
static void
Kill(Engine *engine)
{
XEngine *xeng= (XEngine *)engine;
p_win *w = xeng->win;
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->xmax<cPort.xmax) cPort.xmax= damage->xmax;
if (damage->ymin>cPort.ymin) cPort.ymin= damage->ymin;
if (damage->ymax<cPort.ymax) cPort.ymax= damage->ymax;
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 ; i=imax) {
imax = i+2047;
npts = (imax<=n)? 2047 : (int)(n-i);
p_d_pnts(w, px+i, py+i, npts);
if (closed && imax>=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 ; i<n ; i=imax) {
imax = i+2048;
npts = (imax<=n)? 2048 : (int)(n-i);
p_d_pnts(w, px+i, py+i, npts);
p_dots(w);
}
return 0;
}
}
/* ------------------------------------------------------------------------ */
static int
DrwText(Engine *engine, GpReal x0, GpReal y0, const char *text)
{
XEngine *xeng = (XEngine *)engine;
p_win *w = xeng->w;
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]<yn || xbox[0]>xx || xbox[1]<xn) return 0;
/* erase background if string is opaque */
if (gistA.t.opaque) {
p_color(w, P_BG);
p_rect(w, xbox[0], ybox[0], xbox[1], ybox[1], 0);
}
p_color(w, gistA.t.color);
do {
if (len>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 ; i<n ; i=imax) {
imax = i+2048;
npts = (imax<=n)? 2048 : (int)(n-i);
p_d_pnts(w, px+i, py+i, npts);
/* Can Nonconvex or Convex be detected? */
p_fill(w, 0);
}
xeng->e.marked = 1;
if (has_edge) {
p_color(w, gistA.e.color);
for (i=0 ; i<n ; i=imax) {
imax = i+2047;
npts = (imax<=n)? 2047 : (int)(n-i);
p_d_pnts(w, px+i, py+i, npts);
if (imax>=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 (qx<xmin || px>xmax) 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 (px<xmin || qx>xmax) 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 ; i<n ;) {
imax = i+1024;
nseg = (imax<=n)? 1024 : (int)(n-i);
while (nseg--) {
p_d_pnts(w, px+i, py+i, -1);
p_d_pnts(w, qx+i, qy+i, -1);
i++;
}
p_segments(w);
}
xeng->e.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<len && displayName[i]<='9' && displayName[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 ; i<n_screens ; i++) {
j = 0;
if (g_screens[i].name) {
for ( ; j<len ; j++)
if (g_screens[i].s && g_screens[i].name[j]!=displayName[j]) break;
}
if (j==len && (len? (!g_screens[i].name[j]) : !g_screens[i].name)) {
if (number == g_screens[i].number) break;
else if (i0<0) i0 = i;
}
}
if (i<n_screens) s = g_screens[i].s;
}
if (!s) {
if (i0<0) s = p_connect(displayName);
else s = p_multihead(g_screens[i0].s, number);
if (!s) return s;
for (i=0 ; i<n_screens ; i++) if (!g_screens[i].s) break;
if (i==n_screens && !(i & (i-1))) {
int n = i? 2*i : 1;
g_screens = p_realloc(g_screens, sizeof(g_scr)*n);
}
g_screens[i].number = number;
g_screens[i].name = displayName? p_strncat(0, displayName, len) : 0;
g_screens[i].s = s;
if (i==n_screens) n_screens++;
}
return s;
}
void
g_disconnect(p_scr *s)
{
if (s) {
int i;
char *name;
for (i=0 ; i<n_screens ; i++) {
if (g_screens[i].s == s) {
name = g_screens[i].name;
g_screens[i].name = 0;
g_screens[i].s = 0;
p_free(name);
}
}
p_disconnect(s);
} else {
g_do_disconnect();
}
}
XEngine *
GxEngine(p_scr *s, char *name, GpTransform *toPixels,
int x, int y, int topMargin, int leftMargin, long engineSize)
{
XEngine *xEngine;
unsigned int width, height;
GpReal pixels_per_page;
int dpi;
if (!s) return 0;
/* Graphics window will have dimensions of toPixels transform window */
if (toPixels->window.xmin<toPixels->window.xmax)
width = (unsigned int)(toPixels->window.xmax - toPixels->window.xmin);
else
width = (unsigned int)(toPixels->window.xmin - toPixels->window.xmax);
if (toPixels->window.ymin<toPixels->window.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 min<max for v; must also be true for input viewport */
GetVisibleNDC(xeng, &xmin, &xmax, &ymin, &ymax);
if (viewport->xmin>xmin) xmin = viewport->xmin;
if (viewport->xmax<xmax) xmax = viewport->xmax;
if (viewport->ymin>ymin) ymin = viewport->ymin;
if (viewport->ymax<ymax) ymax = viewport->ymax;
/* 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;
}
/* ------------------------------------------------------------------------ */
syntax highlighted by Code2HTML, v. 0.9.1