
/*
 * GRAPHICS routines (public functions).
 * --------------------------------------
 */

void
GtActivate (GtermWidget w)
{
    w->gterm.interactive = 0;
    w->gterm.save_x = w->gterm.save_y = 0;
}

void
GtDeactivate (GtermWidget w)
{
    Display *display = w->gterm.display;
    Window window = w->gterm.window;

    if (w->gterm.interactive) {
	if (w->gterm.save_x > 0 && w->gterm.save_y > 0) {
	    if (w->gterm.warpCursor) {
		/* Workaround X server bug. */
		if (w->gterm.root != w->gterm.save_root)
		    XWarpPointer (display,None,w->gterm.root, 0,0,0,0,
			WidthOfScreen(w->gterm.screen) - 1,
			HeightOfScreen(w->gterm.screen) - 1);

		/* Move pointer to saved position. */
		XWarpPointer (display, None, w->gterm.save_root,
		    0,0,0,0, w->gterm.save_x, w->gterm.save_y);
	    }
	    w->gterm.save_x = 0;
	    w->gterm.save_y = 0;
	}
	w->gterm.interactive = 0;
    }
}

int
GtReady (GtermWidget w)
{
    return (w->gterm.delay == 0);
}

void
GtReset (GtermWidget w)
{
    invalidate_draw_context (w);
    set_default_color_index (w);
    XSetFunction (w->gterm.display, w->gterm.drawGC, GXcopy);
    XSetLineAttributes (w->gterm.display,
	w->gterm.drawGC, 1, LineSolid, CapButt, JoinMiter);

    /* Set defaults. */
    w->gterm.line_width = 1;
    w->gterm.data_level = GtSet;
    w->gterm.line_style = GtSolid;
    w->gterm.fill_type  = GtSolid;
    w->gterm.raster     = 0;
}

void
GtTimerInhibit (GtermWidget w, Boolean state)
{
    /* This is a kludge to allow a client (xgterm) to disable use of timers
     * if they don't work in a given implementation.
     */
    w->gterm.useTimers = !state;
}

void
GtAugmentTranslations (GtermWidget w, char *translations)
{
    int i;

    if ((i = w->gterm.nauxTrans) < MAX_AUXTRANS) {
	w->gterm.auxTrans[i] =
	    XtParseTranslationTable (translations);
	w->gterm.auxTType[i] = T_augment;
	w->gterm.nauxTrans++;
	XtAugmentTranslations ((Widget)w, w->gterm.auxTrans[i]);
    }
}

void
GtOverrideTranslations (GtermWidget w, char *translations)
{
    int i;

    if ((i = w->gterm.nauxTrans) < MAX_AUXTRANS) {
	w->gterm.auxTrans[i] =
	    XtParseTranslationTable (translations);
	w->gterm.auxTType[i] = T_override;
	w->gterm.nauxTrans++;
	XtOverrideTranslations ((Widget)w, w->gterm.auxTrans[i]);
    }
}

void
GtFlush (GtermWidget w)
{
    XFlush (w->gterm.display);
}

void
GtSetLogRes (GtermWidget w, int width, int height)
{
    w->gterm.xres = width;
    w->gterm.yres = height;
}

void
GtGetLogRes (GtermWidget w, int *width, int *height)
{
    *width = w->gterm.xres;
    *height = w->gterm.yres;
}

void
GtGetPhysRes (GtermWidget w, int raster, int *width, int *height)
{
    if (raster) {
	Raster rp = &w->gterm.rasters[raster];
	*width = rp->width;
	*height = rp->height;
    } else {
	*width = w->core.width;
	*height = w->core.height;
    }
}

void
GtSetPhysRes (GtermWidget w, int raster, int width, int height)
{
    GtCreateRaster (w, raster, GtServer, width, height, RasterDepth);
}

void
GtSetRaster (GtermWidget w, int raster)
{
    if (raster >= 0 && raster < w->gterm.maxRasters) {
	w->gterm.raster = raster;
	invalidate_draw_context (w);
    }
}

int
GtGetRaster (GtermWidget w)
{
    return (w->gterm.raster);
}

/* ARGSUSED */
void
GtSetTextRes (GtermWidget w, int optrows, int optcols)
{
    w->gterm.optrows = optrows;
    w->gterm.optcols = optcols;
}

/* ARGSUSED */
void
GtSetCharSize (GtermWidget w, int ival)
{
}

void
GtSetDataLevel (GtermWidget w, int ival)
{
    invalidate_draw_context (w);

    switch (ival) {
    case GtSet:
	XSetFunction (w->gterm.display, w->gterm.drawGC, GXcopy);
	if (w->gterm.useGlobalCmap)
	    XSetForeground (w->gterm.display, w->gterm.drawGC,
	        static_colors[w->gterm.color_index].value);
	else
	    XSetForeground (w->gterm.display, w->gterm.drawGC,
	        w->gterm.cmap[w->gterm.color_index]);
	w->gterm.data_level = ival;
	break;

    case GtClear:
	XSetFunction (w->gterm.display, w->gterm.drawGC, GXcopy);
	if (w->gterm.useGlobalCmap)
	    XSetForeground (w->gterm.display, w->gterm.drawGC,
	        static_colors[0].value);
	else
	    XSetForeground (w->gterm.display, w->gterm.drawGC, w->gterm.color0);
	w->gterm.data_level = ival;
	break;

    case GtInvert:
	/* This probably won't work correctly but leave it for now... */
	XSetFunction (w->gterm.display, w->gterm.drawGC, GXxor);
	w->gterm.data_level = ival;
	break;
    }
}


void
GtSetLineWidth (GtermWidget w, int ival)
{
    w->gterm.line_width = ival;
    GtSetLineStyle (w, w->gterm.line_style);
}

#define	Dashed		"\010\003"
#define	Dotted		"\002\003"
#define	DashDot		"\016\003\001\003"
#define	Dash3Dot	"\024\003\001\003\001\003\001\003"

void
GtSetLineStyle (GtermWidget w, int ival)
{
    int line_width = w->gterm.line_width;
    int line_style = LineSolid;
    int cap_style = CapButt;
    int join_style = JoinMiter;
    int dash_offset = 0;
    char *dash_list = NULL;
    int dash_list_length = 0;

    switch (ival) {
    case GtSolid:
	w->gterm.line_style = ival;
	break;
    case GtDashed:
	line_style = LineOnOffDash;
	dash_list = (char *)Dashed;
	dash_list_length = strlen(Dashed);
	w->gterm.line_style = ival;
	break;
    case GtDotted:
	line_style = LineOnOffDash;
	dash_list = (char *)Dotted;
	dash_list_length = strlen(Dotted);
	w->gterm.line_style = ival;
	break;
    case GtDashDot:
	line_style = LineOnOffDash;
	dash_list = (char *)DashDot;
	dash_list_length = strlen(DashDot);
	w->gterm.line_style = ival;
	break;
    case GtDash3Dot:
	line_style = LineOnOffDash;
	dash_list = (char *)Dash3Dot;
	dash_list_length = strlen(Dash3Dot);
	w->gterm.line_style = ival;
	break;
    }

    if (dash_list_length)
	XSetDashes (w->gterm.display, w->gterm.drawGC, dash_offset, dash_list,
	    dash_list_length);

    XSetLineAttributes (w->gterm.display,
	w->gterm.drawGC, line_width, line_style, cap_style, join_style);

    invalidate_draw_context (w);
}

void
GtSetColorIndex (GtermWidget w, int ival)
{
    int color = w->gterm.iomap[ival];

    if (color >= 0 && color < w->gterm.ncolors) {
	if (w->gterm.useGlobalCmap) {
	    int i, found = 0;

	    for (i=0; i < num_static_colors; i++) {
		if (ival == static_colors[i].index) {
	    	    XSetForeground (w->gterm.display, w->gterm.drawGC,
	        	static_colors[i].value);
		    found++;
		}
	    }

	    /* If no match found, set the foreground color as the index.
	    */
	    if (!found) {
	        XSetForeground (w->gterm.display, w->gterm.drawGC,
	            static_colors[1].value);
	    }
	} else
	    XSetForeground (w->gterm.display, w->gterm.drawGC,
	        w->gterm.cmap[color]);
	w->gterm.color_index = color;
	invalidate_draw_context (w);
    }
}

void
GtSetFillType (GtermWidget w, int ival)
{
    switch (ival) {
    case GtSolid:
    case GtOutline:
	w->gterm.fill_type = ival;
	break;
    }
}

void
GtClearScreen (GtermWidget w)
{
    Mapping mp;


    if (!w || !XtIsRealized ((Widget)w))
	return;

    if (w->gterm.pixmap)
	XFillRectangle (w->gterm.display, w->gterm.pixmap,
	    w->gterm.clearGC, 0, 0, w->core.width, w->core.height);
    XClearWindow (w->gterm.display, w->gterm.window);

    set_default_color_index (w);
    XSetFunction (w->gterm.display, w->gterm.drawGC, GXcopy);
    if (w->gterm.useGlobalCmap)
	XSetForeground (w->gterm.display, w->gterm.drawGC,
	    static_colors[1].value);
    else
        XSetForeground (w->gterm.display, w->gterm.drawGC, w->gterm.color1);
    XSetLineAttributes (w->gterm.display,
	w->gterm.drawGC, 1, LineSolid, CapRound, JoinRound);

    w->gterm.line_width     = 1;
    w->gterm.line_style     = GtSolid;
    w->gterm.fill_type      = GtSolid;
    w->gterm.data_level     = GtSet;
    w->gterm.preserve_valid = 0;
    w->gterm.gm_redisplay   = 1;
    w->gterm.d_saved        = 0;

    /* Mark any screen mappings to be unconditionally refreshed. */
    for (mp = w->gterm.mp_head;  mp;  mp = mp->next)
	if (mp->enabled && mp->dst == 0)
	    mp->refresh++;

    invalidate_draw_context (w);
    update_transients (w, NULL);

    /* Reinitialize the display pixmap shadow.
    */
    if (w->gterm.rasters)
        initialize_shadow_pixmap (w, 0);
}

void
GtDrawPolyline (GtermWidget w, XPoint *pv, int npts)
{
    XPoint *points, o_pv[MAX_POINTS];
    DrawContext dx = get_draw_context (w);
    MappingContext mx;
    int i;

    for (i=0;  i < dx->nmappings;  i++) {
	mx = &dx->mapContext[i];
	points = mx->scale ? mapVector(mx,pv,o_pv,npts) : pv;

	/* Add code to support max display request size. */
	if (mx->use_backing_store)
	    XDrawLines (w->gterm.display, w->gterm.pixmap,
		mx->drawGC, points, npts, CoordModeOrigin);
	XDrawLines (w->gterm.display, mx->pixmap,
	    mx->drawGC, points, npts, CoordModeOrigin);
    }

    update_transients (w, (Region)NULL);
}

void
GtDrawPolymarker (GtermWidget w, XPoint *pv, int npts)
{
    XPoint *points, o_pv[MAX_POINTS];
    DrawContext dx = get_draw_context (w);
    MappingContext mx;
    int i;

    for (i=0;  i < dx->nmappings;  i++) {
	mx = &dx->mapContext[i];
	points = mx->scale ? mapVector(mx,pv,o_pv,npts) : pv;

	/* Add code to support max display request size. */
	if (mx->use_backing_store)
	    XDrawPoints (w->gterm.display, w->gterm.pixmap,
		mx->drawGC, points, npts, CoordModeOrigin);
	XDrawPoints (w->gterm.display, mx->pixmap,
	    mx->drawGC, points, npts, CoordModeOrigin);
    }

    update_transients (w, (Region)NULL);
}

void
GtDrawPolygon (GtermWidget w, XPoint *pv, int npts)
{
    XPoint *points, o_pv[MAX_POINTS];
    DrawContext dx = get_draw_context (w);
    MappingContext mx;
    int i;

    for (i=0;  i < dx->nmappings;  i++) {
	mx = &dx->mapContext[i];
	points = mx->scale ? mapVector(mx,pv,o_pv,npts) : pv;

	if (w->gterm.fill_type == GtOutline) {
	    /* Draw outline of region.
	     */
	    int first = 0;
	    int last = npts - 1;

	    if (mx->use_backing_store)
		XDrawLines (w->gterm.display, w->gterm.pixmap, mx->drawGC,
		    points, npts, CoordModeOrigin);
	    XDrawLines (w->gterm.display, mx->pixmap, mx->drawGC,
		points, npts, CoordModeOrigin);

	    if (points[last].x != points[first].x ||
		points[last].y != points[first].y) {

		if (mx->use_backing_store)
		    XDrawLine (w->gterm.display, w->gterm.pixmap, mx->drawGC,
			points[last].x, points[last].y,
			points[first].x, points[first].y);
		XDrawLine (w->gterm.display, mx->pixmap, mx->drawGC,
		    points[last].x, points[last].y,
		    points[first].x, points[first].y);
	    }
	} else {
	    /* Fill the outlined area.
	     */
	    if (mx->use_backing_store) {
		XFillPolygon (w->gterm.display, w->gterm.pixmap, mx->drawGC,
		    points, npts, Nonconvex, CoordModeOrigin);
		XDrawLines (w->gterm.display, w->gterm.pixmap, mx->drawGC,
		    points, npts, CoordModeOrigin);
	    }
	    XFillPolygon (w->gterm.display, mx->pixmap, mx->drawGC,
		points, npts, Nonconvex, CoordModeOrigin);
	    XDrawLines (w->gterm.display, mx->pixmap, mx->drawGC,
		points, npts, CoordModeOrigin);
	}
    }

    update_transients (w, (Region)NULL);
}

void
GtDrawMarker (GtermWidget w, int x, int y, int xsize, int ysize, int type)
{
}

void
GtBell (GtermWidget w)
{
    XBell (w->gterm.display, 0);
}


/* GtSetCursorPos -- Set the cursor position to the given coordinates X,Y.
 * Coordinates are specified in the current graphics coordinate system,
 * defined by the current raster and logical resolution.
 *
 * This routine is a little more complex than one might think due to the
 * complication of mappings.  Screen coordinates are required to set the
 * cursor, but the graphics drawing context may be defined relative to
 * any raster.  In the general case a graphics pipeline defines the series
 * of coordinate transformations required to transform from graphics
 * coordinates to screen coordinates.  Things are further complicated since
 * the pipeline or desired position may not map to the screen, or there
 * may be multiple mappings to the screen.  The first case (no mapping to
 * the screen) is dealt with by ignoring the request to warp the cursor.
 * The second case (one-to-many mapping) is dealt with by a heuristic:
 * the most recent screen coordinates are unmapped back to the raster we
 * are "drawing" into, defining a unique path through the mappings which
 * we can use to map back to the screen.
 *
 * The simplest case occurs when we are drawing directly into the screen.
 * In this case (raster=0) there may still be a logical to physical
 * coordinate transformation, but there are no mappings to complicate things.
 */
void
GtSetCursorPos (GtermWidget w, int x, int y)
{
    MappingContext mx;
    DrawContext dx;
    Mapping mp;

    Window window = w->gterm.window;
    int sv_raster = w->gterm.raster;
    int sv_xres = w->gterm.xres, sv_yres = w->gterm.yres;
    int rasters[256], mappings[256], nmap=0, ntrans=0;
    int rx, ry, src, dst, map, i, npts = 1;
    int raster = w->gterm.raster;
    XPoint pv1[1], pv2[2];
    XPoint *points, pv[1];
    Raster rp;

    if (!w || !XtIsRealized ((Widget)w))
	return;

    /* Follow the current cursor position back to the source raster if
     * possible.  This gives us a default pipeline to follow in the reverse
     * direction to map raster coordinates to screen coordinates, and is
     * necessary to find the right mappings when multiple mappings are
     * defined on a single source.
     */
    rx = w->gterm.last_x;
    ry = w->gterm.last_y;
    src = 0;
    do {
	src = GtSelectRaster (w, dst=src, GtPixel,rx,ry, GtPixel,&rx,&ry,&map);
	if (src != dst) {
	    rasters[nmap] = src;
	    mappings[nmap++] = map;
	}
    } while (src != dst && src != raster);

    /* Ray trace the point through all of the mappings to the screen.
     * This isn't fully general, but gives us the capability to follow
     * most graphics pipelines to a point on the screen.
     */
    do {
	GtSetRaster (w, raster);
	if (ntrans++ || raster == 0) {				/* MF041 */
	    /* After the first transformation we have raster coordinates,
	     * so set the logical resolution to the raster dimensions.
	     */
	    rp = &w->gterm.rasters[raster];
	    GtSetLogRes (w, rp->width, rp->height);
	}

	dx = get_draw_context (w);
	if (!dx->nmappings)
	    return;

	/* Try to find the next mapping. */
	if (nmap && rasters[nmap-1] == raster)
	    for (i=0;  i < dx->nmappings;  i++) {
		mx = &dx->mapContext[i];
		if (mx->mapping == mappings[nmap-1]) {
		    mp = mx->mp;
		    nmap--;
		    goto havemap;
		}
	    }
	for (i=0;  i < dx->nmappings;  i++) {
	    mx = &dx->mapContext[i];
	    mp = mx->mp;
	    if (mp && mp->dst == 0)
		break;
	}
	if (i >= dx->nmappings) {
	    mx = &dx->mapContext[0];
	    mp = mx->mp;
	}

havemap:
	rp = &w->gterm.rasters[mp ? mp->dst : raster];

	/* Compute the coordinates points[0].{x,y} of the point x,y in the
	 * destination raster.
	 */
	if (mx->scale) {
	    /* Scaling is in effect.  The following subterfuge is used to
	     * compute the coordinates of the center of the raster pixel (x,y)
	     * when the image is zoomed.  We want to set the cursor to the
	     * center of the selected pixel, not the edge.
	     */
	    pv[0].x = x;  pv[0].y = y;
	    mapVector (mx, pv, pv1, npts);
	    pv[0].x = x + 1;  pv[0].y = y + 1;
	    mapVector (mx, pv, pv2, npts);

	    pv[0].x = (pv1[0].x + pv2[0].x) / 2.0;
	    pv[0].y = (pv1[0].y + pv2[0].y) / 2.0;
	    points = pv;

	} else {
	    /* No scaling. */
	    pv[0].x = x;  pv[0].y = y;
	    points = pv;
	}

	/* Clip to the bounds of the destination raster and generate the
	 * new x,y.
	 */
	x = max(0, min(rp->width-1,  points[0].x));
	y = max(0, min(rp->height-1, points[0].y));

    } while (mp && (raster = mp->dst));

    XWarpPointer (w->gterm.display, window, window, 0,0,0,0, x,y);

    w->gterm.last_x = w->gterm.cur_x = x;
    w->gterm.last_y = w->gterm.cur_y = y;

    GtSetRaster (w, sv_raster);
    GtSetLogRes (w, sv_xres, sv_yres);
}

void
GtGetCursorPos (GtermWidget w, int *x, int *y)
{
    *x = w->gterm.last_x;
    *y = w->gterm.last_y;
}

void
GtSetCursorType (GtermWidget w, int type)
{
    static XtIntervalId id = (XtIntervalId) NULL;
    Display *display = w->gterm.display;
    Cursor cursor;
    int interval;
    Widget pw;

    if (!w || !XtIsRealized ((Widget)w))
	return;
    if (w->gterm.cursor_type == type)
	return;

    switch (w->gterm.cursor_type = type) {
    case GtNoCursor:
    case GtIdleCursor:
	erase_crosshair (w);
	cursor = w->gterm.idle_cursor;
	break;

    case GtGinmodeCursor:
	/* Begin graphics cursor mode.
	 */

	/* If a screen clear or drawing operation has caused the redisplay
	 * flag to be set, redisplay any marker overlays.
	 */
	if (w->gterm.gm_redisplay) {
	    GmRedisplay (w, (Region)NULL);
	    w->gterm.gm_redisplay = False;
	}

	/* Make sure the window is visible.
	 */
	if (w->gterm.raiseWindow || w->gterm.deiconifyWindow)
	    for (pw = (Widget)w;  pw;  pw = XtParent(pw))
		if (XtIsShell(pw)) {
		    if (w->gterm.deiconifyWindow)
			XMapWindow (display, XtWindow(pw));
		    if (w->gterm.raiseWindow)
			XRaiseWindow (display, XtWindow(pw));
		    XSync (display, False);
		}

	/* The first time this is done after a GtActivate causes the cursor
	 * to be warped into the graphics window.  The interactive flag is set
	 * to cause GtDeactivate to restore the cursor to its original position
	 * after the graphics interaction finishes.
	 */
	if (w->gterm.warpCursor) {
	    int root_x, root_y, win_x, win_y;
	    int xoff, yoff, width, height, x, y;
	    Window gtermwin, root, child;
	    unsigned int keys;
	    int in_window = 0;

	    width = w->core.width;
	    height = w->core.height;
	    gtermwin = w->gterm.window;
	    XTranslateCoordinates (display, gtermwin, w->gterm.root,
		w->core.x, w->core.y, &xoff, &yoff, &child);

	    if (XQueryPointer (display, w->gterm.root, &root, &child,
		&root_x, &root_y, &win_x, &win_y, &keys)) {

		/* Already in gterm window? */
		if ((root_x >= xoff && root_x < xoff+width) &&
		    (root_y >= yoff && root_y < yoff+height)) {

		    if (!w->gterm.interactive) {
			w->gterm.save_x = 0;
			w->gterm.save_y = 0;
			w->gterm.interactive++;
		    }
		    x = root_x - xoff;   y = root_y - yoff;
		    in_window++;

		} else {
		    if (!w->gterm.interactive) {
			w->gterm.save_x = root_x;
			w->gterm.save_y = root_y;
			w->gterm.save_root = root;
			w->gterm.interactive++;
		    }
		    x = w->gterm.cur_x;  y = w->gterm.cur_y;
		}
	    } else {
		/* Pointer not on the current screen.
		 */
		if (!w->gterm.interactive) {
		    w->gterm.save_x = root_x;
		    w->gterm.save_y = root_y;
		    w->gterm.save_root = root;
		    w->gterm.interactive++;
		}
		x = w->gterm.cur_x;  y = w->gterm.cur_y;
	    }

	    if ((x < 5 || x > width-5) || (y < 5 || y > height-5)) {
		x = width / 2;
		y = height / 2;
	    }

	    if (!in_window) {
		erase_crosshair (w);
		if (w->gterm.warpCursor) {
		    XWindowAttributes wa;
		    if (XGetWindowAttributes (display, gtermwin, &wa) &&
			    wa.map_state == IsViewable) {

			/* The following should not be necessary but is needed
			 * to workaround an X server bug.  When warping to a
			 * different screen the pointer is not erased on the
			 * old screen.  It is hard to erase it, but we can
			 * at least move it to the corner of the screen.
			 */
			if (root != w->gterm.root) {
			    Screen *screen = w->gterm.screen;
			    if (XGetWindowAttributes (display, root, &wa))
				screen = wa.screen;
			    XWarpPointer (display,None,root, 0,0,0,0,
				WidthOfScreen(screen) - 1,
				HeightOfScreen(screen) - 1);
			}

			/* Now warp into the gterm window. */
			XWarpPointer (display, None, gtermwin, 0,0,0,0, x,y);
		    }
		    if (w->gterm.full_crosshair)
			draw_crosshair (w, x, y);
		}

	    } else if (w->gterm.full_crosshair) {
		erase_crosshair (w);
		draw_crosshair (w, x, y);
	    }
	} else
	    update_cursor (w);

	cursor = w->gterm.ginmode_cursor;
	if ((interval = w->gterm.ginmodeBlinkInterval)) {
	    XtAppContext appcon = XtWidgetToApplicationContext ((Widget) w);
	    id = XtAppAddTimeOut (appcon,
		interval, (XtTimerCallbackProc)blink_cursor, (XtPointer)w);
	} else
	    id = (XtIntervalId) NULL;
	break;

    case GtBusyCursor:
	/* Exit graphics cursor mode.
	 */
	erase_crosshair (w);
	cursor = w->gterm.busy_cursor;
	break;
    }

    if (w->core.visible)
	XDefineCursor (w->gterm.display, w->gterm.window,
	    w->gterm.cursor = cursor);
    if (id && w->gterm.cursor_type != GtGinmodeCursor) {
	XtRemoveTimeOut (id);
	id = (XtIntervalId) NULL;
    }
}

static void
blink_cursor (GtermWidget w, XtIntervalId *id)
{
    XtAppContext app_context;
    XColor bg, fg;
    int interval;

    if (w->gterm.cursor_type != GtGinmodeCursor) 		/* MF032 */
	return;

    bg = w->gterm.ginmodeColors[1];
    fg = w->gterm.ginmodeColors[0];

    app_context = XtWidgetToApplicationContext ((Widget) w);
    XRecolorCursor (w->gterm.display, w->gterm.ginmode_cursor, &fg, &bg);
    XFlush (w->gterm.display);

    w->gterm.ginmodeColors[0] = bg;
    w->gterm.ginmodeColors[1] = fg;

    if ((interval = w->gterm.ginmodeBlinkInterval))
	XtAppAddTimeOut (app_context,
	    interval, (XtTimerCallbackProc) blink_cursor, (XtPointer)w);
}

void
GtPostInputProc (GtermWidget w, GtCallbackProc userfcn, XtPointer client_data)
{
    GtCallback *cb, *new;

    new = (GtCallback *) XtMalloc (sizeof (GtCallback));
    new->proc = userfcn;
    new->client_data = client_data;
    new->next = NULL;

    for (cb = w->gterm.inputCallback;  cb && cb->next;  cb = cb->next)
	;
    if (cb)
	cb->next = new;
    else
	w->gterm.inputCallback = new;
}

void
GtDeleteInputProc (GtermWidget w, GtCallbackProc userfcn, XtPointer client_data)
{
    GtCallback *cb, *prev;

    for (prev=NULL, cb = w->gterm.inputCallback;  cb;  cb = cb->next)
	if (cb->proc == userfcn && cb->client_data == client_data) {
	    if (prev)
		prev->next = cb->next;
	    else
		w->gterm.inputCallback = cb->next;
	    XtFree ((char *)cb);
	    break;
	} else
	    prev = cb;
}

void
GtPostResetProc (GtermWidget w, GtCallbackProc userfcn, XtPointer client_data)
{
    GtCallback *cb, *new;

    new = (GtCallback *) XtMalloc (sizeof (GtCallback));
    new->proc = userfcn;
    new->client_data = client_data;
    new->next = NULL;

    for (cb = w->gterm.resetCallback;  cb && cb->next;  cb = cb->next)
	;
    if (cb)
	cb->next = new;
    else
	w->gterm.resetCallback = new;
}

void
GtDeleteResetProc (GtermWidget w, GtCallbackProc userfcn, XtPointer client_data)
{
    GtCallback *cb, *prev;

    for (prev=NULL, cb = w->gterm.resetCallback;  cb;  cb = cb->next)
	if (cb->proc == userfcn && cb->client_data == client_data) {
	    if (prev)
		prev->next = cb->next;
	    else
		w->gterm.resetCallback = cb->next;
	    XtFree ((char *)cb);
	    break;
	} else
	    prev = cb;
}

void
GtPostResizeProc (GtermWidget w, GtCallbackProc userfcn, XtPointer client_data)
{
    GtCallback *cb, *new;

    new = (GtCallback *) XtMalloc (sizeof (GtCallback));
    new->proc = userfcn;
    new->client_data = client_data;
    new->next = NULL;

    for (cb = w->gterm.resizeCallback;  cb && cb->next;  cb = cb->next)
	;
    if (cb)
	cb->next = new;
    else
	w->gterm.resizeCallback = new;
}

void
GtDeleteResizeProc (GtermWidget w, GtCallbackProc userfcn, XtPointer client_data)
{
    GtCallback *cb, *prev;

    for (prev=NULL, cb = w->gterm.resizeCallback;  cb;  cb = cb->next)
	if (cb->proc == userfcn && cb->client_data == client_data) {
	    if (prev)
		prev->next = cb->next;
	    else
		w->gterm.resizeCallback = cb->next;
	    XtFree ((char *)cb);
	    break;
	} else
	    prev = cb;
}

void
GtDrawAlphaText (GtermWidget w, int x, int y, char *text)
{
    XPoint *points, pv[1], o_pv[1];
    DrawContext dx = get_draw_context (w);
    MappingContext mx;
    int npts, i;

    pv[0].x = x;
    pv[0].y = y;
    npts = 1;

    for (i=0;  i < dx->nmappings;  i++) {
	mx = &dx->mapContext[i];
	points = mx->scale ? mapVector(mx,pv,o_pv,npts) : pv;
	x = points[0].x;  y = points[0].y;

	if (mx->use_backing_store)
	    XDrawString (w->gterm.display, w->gterm.pixmap,
		mx->drawGC, x, y, text, strlen(text));
	XDrawString (w->gterm.display, mx->pixmap,
	    mx->drawGC, x, y, text, strlen(text));
    }

    update_transients (w, (Region)NULL);
}

void
GtGetAlphaTextSize (GtermWidget w, char *string, int *width, int *height, int *base)
{
    XFontStruct *fp;

    fp = w->gterm.alpha_fonts[w->gterm.alpha_font];
    if (string)
	*width = XTextWidth (fp, string, strlen(string));
    else
	*width = fp->max_bounds.width;

    *height = fp->max_bounds.ascent + fp->max_bounds.descent;
    *base = fp->max_bounds.ascent;
}

void
GtWriteAlphaCursor (GtermWidget w, int x, int y)
{
}

void
GtEraseAlphaCursor (GtermWidget w)
{
}

void
GtStartDialog (GtermWidget w)
{
    if (DBG_TRACE)
	fprintf (stderr, "GtStartDialog:  ENTER  d_pixmap=0x%lx d_saved=%d\n",
	    w->gterm.d_pixmap, w->gterm.d_saved);

    if (w->gterm.d_pixmap) {
	if (w->gterm.d_saved) {
	    GtEraseDialog (w);
	} else {
	    XSetClipMask (w->gterm.display, w->gterm.exposeGC, None);
	    XCopyArea (w->gterm.display,
		w->gterm.pixmap ? w->gterm.pixmap : w->gterm.window,
		w->gterm.d_pixmap, w->gterm.exposeGC,
		0, w->gterm.d_yoff, w->core.width, w->gterm.d_height, 0, 0);
	    w->gterm.d_saved = 1;
	}
    }

    if (DBG_TRACE)
	fprintf (stderr, "GtStartDialog:  LEAVING\n");
}

void
GtEndDialog (GtermWidget w)
{
    GtEraseDialog (w);
    w->gterm.d_saved = 0;
}

void
GtEraseDialog (GtermWidget w)
{
    if (DBG_TRACE)
	fprintf (stderr, "GtEraseDialog:  ENTER  d_pixmap=0x%lx d_saved=%d\n",
	    w->gterm.d_pixmap, w->gterm.d_saved);

    if (w->gterm.d_pixmap && w->gterm.d_saved) {
	XSetClipMask (w->gterm.display, w->gterm.exposeGC, None);
	XCopyArea (w->gterm.display,
	    w->gterm.d_pixmap, w->gterm.window, w->gterm.exposeGC,
	    0, 0, w->core.width, w->gterm.d_height, 0, w->gterm.d_yoff);
	if (w->gterm.pixmap)
	    XCopyArea (w->gterm.display,
		w->gterm.d_pixmap, w->gterm.pixmap, w->gterm.exposeGC,
		0, 0, w->core.width, w->gterm.d_height, 0, w->gterm.d_yoff);
	update_transients (w, (Region)NULL);
    }

    if (DBG_TRACE)
	fprintf (stderr, "GtEraseDialog:  LEAVING\n");
}

void
GtDrawDialogText (GtermWidget w, int x, int y, char *text)
{
    int xpos = w->gterm.d_xoff + x;
    int ypos = w->gterm.d_yoff + y;

    if (w->gterm.pixmap)
	XDrawImageString (w->gterm.display, w->gterm.pixmap,
	    w->gterm.dialogGC, xpos, ypos, text, strlen(text));
    XDrawImageString (w->gterm.display, w->gterm.window,
	w->gterm.dialogGC, xpos, ypos, text, strlen(text));
}

void
GtGetDialogTextSize (GtermWidget w, char *string, int *width, int *height, int *base)
{
    XFontStruct *fp;

    fp = w->gterm.dialog_fonts[w->gterm.dialog_font];
    if (string)
	*width = XTextWidth (fp, string, strlen(string));
    else
	*width = fp->max_bounds.width;

    *height = fp->max_bounds.ascent + fp->max_bounds.descent;
    *base = fp->max_bounds.ascent;
}


/*
 * Internal functions for above code.
 * ----------------------------------------
 */

static void
set_default_color_index (GtermWidget w)
{
    /* The default color index is 1, corresponding to the foreground
     * drawing color color1.  Index zero is the background drawing color
     * color0.  The remaining NColors color table entries are the optional
     * drawing colors corresponding to resources "color2" through "colorN".
     * These are used only if explicitly selected by the client application.
     */
    if (w->gterm.useGlobalCmap)
        XSetForeground (w->gterm.display, w->gterm.drawGC,
	    static_colors[1].value);
    else
        XSetForeground (w->gterm.display, w->gterm.drawGC, w->gterm.cmap[1]);
    w->gterm.color_index = 1;
    invalidate_draw_context (w);
}


static void
draw_crosshair (GtermWidget w, int x, int y)
{
    if (!w || !XtIsRealized ((Widget)w))
	return;

    if (w->gterm.pixmap) {
	/* The preserve_screen flag is set if we need to preserve the
	 * exact display window contents, rather than merely refresh from
	 * the backing store pixmap.
	 */
	if (w->gterm.preserve_screen) {
	    if (!w->gterm.preserve_valid || y != w->gterm.cur_y)
		XCopyArea (w->gterm.display,
		    w->gterm.window, w->gterm.pixmap, w->gterm.exposeGC,
		    0, y, w->core.width, 1, 0, w->core.height);
	    if (!w->gterm.preserve_valid || x != w->gterm.cur_x)
		XCopyArea (w->gterm.display,
		    w->gterm.window, w->gterm.pixmap, w->gterm.exposeGC,
		    x, 0, 1, w->core.height, w->core.width, 0);
	    w->gterm.preserve_valid = 1;
	}

	XDrawLine (w->gterm.display, w->gterm.window, w->gterm.cursorGC,
	    0, y, w->core.width, y);
	XDrawLine (w->gterm.display, w->gterm.window, w->gterm.cursorGC,
	    x, 0, x, w->core.height);

	XFlush (w->gterm.display);
	w->gterm.cursor_drawn++;
    }

    w->gterm.cur_x = x;
    w->gterm.cur_y = y;
}


static void
erase_crosshair (GtermWidget w)
{
    if (!w || !XtIsRealized ((Widget)w))
	return;

    if (w->gterm.cursor_drawn) {
	int x = w->gterm.cur_x;
	int y = w->gterm.cur_y;

	XSetClipMask (w->gterm.display, w->gterm.exposeGC, None);
	if (w->gterm.pixmap) {
	    if (w->gterm.preserve_screen && w->gterm.preserve_valid) {
		XCopyArea (w->gterm.display,
		    w->gterm.pixmap, w->gterm.window, w->gterm.exposeGC,
		    0, w->core.height, w->core.width, 1, 0, y);
		XCopyArea (w->gterm.display,
		    w->gterm.pixmap, w->gterm.window, w->gterm.exposeGC,
		    w->core.width, 0, 1, w->core.height, x, 0);
	    } else {
		XCopyArea (w->gterm.display,
		    w->gterm.pixmap, w->gterm.window, w->gterm.exposeGC,
		    0, y, w->core.width, 1, 0, y);
		XCopyArea (w->gterm.display,
		    w->gterm.pixmap, w->gterm.window, w->gterm.exposeGC,
		    x, 0, 1, w->core.height, x, 0);
	    }
	}

	w->gterm.cursor_drawn = 0;
	w->gterm.preserve_valid = 0;
    }
}


static void
update_transients (GtermWidget w, Region region)
{
    /* If an explicit region is given redisplay any markers in it immediately,
     * otherwise set the redisplay flag to cause a full screen redisplay when
     * drawing finishes and the widget is ready for input.
     */
    if ((char *)region)
	GmRedisplay (w, region);
    else
	w->gterm.gm_redisplay = True;

    /* Update the crosshair cursor if GIN mode is in effect. */
    update_cursor (w);
}


static void
update_cursor (GtermWidget w)
{
    if (w->gterm.cursor_type == GtGinmodeCursor && w->gterm.full_crosshair) {
	int x = w->gterm.cur_x;
	int y = w->gterm.cur_y;

	if (x || y)
	    draw_crosshair (w, x, y);
    }
}

static Cursor
get_cursor (GtermWidget w, String cursor_name)
{
    XrmValue from, to;
    Cursor cursor;

    from.size = strlen (cursor_name) + 1;
    from.addr = cursor_name;

    to.addr = (void *) &cursor;
    to.size = sizeof(cursor);

    if (!XtConvertAndStore ((Widget)w, XtRString, &from, XtRCursor, &to))
	cursor = XCreateFontCursor (w->gterm.display, XC_crosshair);

    return (cursor);
}


static DrawContext
get_draw_context (GtermWidget w)
{
    DrawContext dx = &w->gterm.draw;

    if (!dx->valid) {
	int raster = w->gterm.raster;
	Raster rp = &w->gterm.rasters[raster];
	MappingContext mx = &dx->mapContext[0];
	Region clip_region, mask_region;
	struct mapping *map, *mp, *np, p_mp;
	int xres = w->gterm.xres;
	int yres = w->gterm.yres;
	float xscale, yscale;
	XRectangle r;
	int i, j;

	dx->raster = w->gterm.raster;
	dx->rp = rp;

	if (raster == 0) {
	    dx->nmappings = 1;
	    mx->mapping = 0;
	    mx->mp = NULL;
	    mx->use_backing_store = (w->gterm.pixmap != (Pixmap)NULL);
	    mx->pixmap = w->gterm.window;
	    mx->drawGC = w->gterm.drawGC;
	    mx->GC_private = 0;

/* (7/16/97) MJF - we don't scale raster 0 since it's already in screen coords.
   Otherwise the cursor movement keystrokes scale incorrectly and quickly move
   to (0,0).
	    mx->xoffset = mx->yoffset = mx->scale = 0;
   [DCT] This doesn't look entirely right as it disables logical coords for
   the screen.  Leave as is until this can be studied more carefully.
 */

	    mx->xoffset = mx->yoffset = 0;
	    if (xres == rp->width && yres == rp->height)
		mx->scale = 0;
	    else {
		mx->xscale = (float)rp->width / (float)xres;
		mx->yscale = (float)rp->height / (float)yres;
		mx->scale = 1;
	    }

	} else {
	    dx->nmappings = 0;
	    for (mp = w->gterm.mp_head;  mp;  mp = mp->next) {
		if (!mp->enabled || mp->src != raster ||
			w->gterm.rasters[mp->dst].type != GtServer)
		    continue;
		if (!valid_mapping (w, mp))
		    continue;

		mx->mp = mp;
		mx->mapping = mp->mapping;
		mx->pixmap = w->gterm.rasters[mp->dst].r.pixmap;
		mx->use_backing_store = (mp->dst == 0 &&
		    w->gterm.pixmap && !(mp->rop & R_Transient));

		/* Determine if any scaling is necessary. */
		get_pixel_mapping (w, mp, &p_mp, 0);
		map = &p_mp;

		/* Compute logical-to-raster scaling. */
		mx->xoffset = mx->yoffset = 0;
		if (xres == rp->width && yres == rp->height) {
		    mx->xscale = mx->yscale = 1.0;
		    mx->scale = 0;
		} else {
		    mx->xscale = (float)rp->width / (float)xres;
		    mx->yscale = (float)rp->height / (float)yres;
		    mx->scale = 1;
		}

		/* Compute overall scale factors by combining logical-to-
		 * raster and raster-to-screen mappings.
		 */
		if (map->snx != map->dnx || map->sny != map->dny ||
		    map->sx  != map->dx  || map->sy  != map->dy) {

		    xscale = (float)map->dnx / (float)map->snx;
		    mx->xscale *= xscale;
		    if (xscale < 0)
			mx->xoffset = map->dx + abs(map->dnx) - 1;
		    else
			mx->xoffset = map->dx;
		    mx->xoffset -= (map->sx * xscale);

		    yscale = (float)map->dny / (float)map->sny;
		    mx->yscale *= yscale;
		    if (yscale < 0)
			mx->yoffset = map->dy + abs(map->dny) - 1;
		    else
			mx->yoffset = map->dy;
		    mx->yoffset -= (map->sy * yscale);

		    mx->scale = 1;
		}

		/* Compute the clip mask which will clip graphics to the
		 * destination rect of the mapping, minus any regions of
		 * this rect covered by other mappings.
		 */
		clip_region = XCreateRegion();
		r.x = map->dx;  r.y = map->dy;
		r.width = abs(map->dnx);
		r.height = abs(map->dny);
		XUnionRectWithRegion (&r, clip_region, clip_region);

		for (np = mp->next;  np;  np = np->next) {
		    struct mapping p_np;

		    if (!np->enabled || np->dst != mp->dst)
			continue;
		    get_pixel_mapping (w, np, &p_np, 0);

		    mask_region = XCreateRegion();
		    r.x = p_np.dx;  r.y = p_np.dy;
		    r.width = abs(p_np.dnx);
		    r.height = abs(p_np.dny);
		    XUnionRectWithRegion (&r, mask_region, mask_region);

		    XSubtractRegion (clip_region, mask_region, clip_region);
		    XDestroyRegion (mask_region);
		}

		/* Create a drawing GC which is a copy of the global drawGC
		 * but using the clip mask computed above.
		 */
		mx->drawGC = XCreateGC (w->gterm.display, w->gterm.root,
		    0, NULL);

                /* Deep Frame */
                mx->drawGC = XCreateGC (w->gterm.display, w->gterm.window,
                    0, NULL);
                /* Deep Frame */

		XCopyGC (w->gterm.display, w->gterm.drawGC, ~0, mx->drawGC);
		XSetRegion (w->gterm.display, mx->drawGC, clip_region);
		XDestroyRegion (clip_region);
		mx->GC_private = 1;

		if (++dx->nmappings >= MAX_DRAW)
		    break;
		else
		    mx++;
	    }
	}

	dx->valid = 1;
    }

    return (dx);
}


static void
invalidate_draw_context (GtermWidget w)
{
    DrawContext dx = &w->gterm.draw;
    MappingContext mx;
    int i;

    if (dx->valid) {
	for (i=0;  i < dx->nmappings;  i++) {
	    mx = &dx->mapContext[i];
	    if (mx->GC_private)
		XFreeGC (w->gterm.display, mx->drawGC);
	}
	dx->valid = 0;
    }
}

static XPoint *
mapVector (MappingContext mx, XPoint *pv1, XPoint *pv2, int npts)
{
    XPoint *ip = pv1;
    XPoint *op = pv2;
    int n;

    for (n=npts;  --n >= 0;  ip++, op++) {
	op->x = ip->x * mx->xscale + mx->xoffset;
	op->y = ip->y * mx->yscale + mx->yoffset;
    }

    return (pv2);
}


static void
savepos (GtermWidget w, XEvent *event)
{
    if (event == NULL)
	return;

    switch (event->type) {
    case KeyPress:
    case KeyRelease:
	w->gterm.last_x = event->xkey.x;
	w->gterm.last_y = event->xkey.y;
	break;
    case ButtonPress:
    case ButtonRelease:
	w->gterm.last_x = event->xbutton.x;
	w->gterm.last_y = event->xbutton.y;
	break;
    case MotionNotify:
	w->gterm.last_x = event->xmotion.x;
	w->gterm.last_y = event->xmotion.y;
	break;
    }
}
