/*
 * Modifications Copyright 1993, 1994, 1995, 1996, 1999, 2000 by Paul Mattes.
 * Original X11 Port Copyright 1990 by Jeff Sparkes.
 *  Permission to use, copy, modify, and distribute this software and its
 *  documentation for any purpose and without fee is hereby granted,
 *  provided that the above copyright notice appear in all copies and that
 *  both that copyright notice and this permission notice appear in
 *  supporting documentation.
 *
 * Copyright 1989 by Georgia Tech Research Corporation, Atlanta, GA 30332.
 *  All Rights Reserved.  GTRC hereby grants public use of this software.
 *  Derivative works based on this software must incorporate this copyright
 *  notice.
 */

/*
 *	screen.c
 *		This module handles the X display.  It has been extensively
 *		optimized to minimize X drawing operations.
 */

#include "globals.h"
#include <X11/IntrinsicP.h>
#include <X11/StringDefs.h>
#include <X11/Xatom.h>
#include <X11/Shell.h>
#include <X11/Composite.h>
#include <X11/Xaw/Dialog.h>
#include <X11/Xaw/Scrollbar.h>
#include "Husk.h"
#include <X11/cursorfont.h>
#include <X11/keysym.h>
#include <errno.h>
#include "3270ds.h"
#include "appres.h"
#include "screen.h"
#include "ctlr.h"
#include "cg.h"
#include "resources.h"

#include "actionsc.h"
#include "ansic.h"
#include "charsetc.h"
#include "ctlrc.h"
#include "hostc.h"
#include "keymapc.h"
#include "keypadc.h"
#include "kybdc.h"
#include "menubarc.h"
#include "popupsc.h"
#include "savec.h"
#include "screenc.h"
#include "scrollc.h"
#include "statusc.h"
#include "tablesc.h"
#include "trace_dsc.h"
#include "utilc.h"
#include "xioc.h"

#if defined(HAVE_SYS_SELECT_H) /*[*/
#include <sys/select.h>		/* fd_set declaration */
#endif /*]*/

#include "x3270.bm"
#include "wait.bm"

#define SCROLLBAR_WIDTH	15
/* #define _ST */

/* Externals: main.c */
extern int      default_screen;

/* Externals: ctlr.c */
extern Boolean  screen_changed;
extern int      first_changed;
extern int      last_changed;

/* Globals */
unsigned char  *selected;	/* selection bitmap */
Dimension       main_width;
#include <X11/bitmaps/gray>
Boolean         scrollbar_changed = False;
Boolean         model_changed = False;
Boolean		efont_changed = False;
Boolean		oversize_changed = False;
Boolean		scheme_changed = False;
Pixel           keypadbg_pixel;
Boolean         flipped = False;
Pixmap          icon;
Boolean		shifted = False;
struct font_list *font_list = (struct font_list *) NULL;
int             font_count = 0;
char	       *efontname;

/* Statics */
static Dimension main_height;
static union sp *temp_image;	/* temporary for X display */
static Pixel	colorbg_pixel;
static Boolean  cursor_displayed = False;
static Boolean  cursor_enabled = True;
static Boolean  cursor_blink_pending = False;
static XtIntervalId cursor_blink_id;
static int	field_colors[4];
static Boolean  in_focus = False;
static Boolean  line_changed = False;
static Boolean  cursor_changed = False;
static Boolean  iconic = False;
static Widget   container;
static Widget   scrollbar;
static Dimension menubar_height;
#if defined(X3270_KEYPAD) /*[*/
static Dimension keypad_height;
static Dimension keypad_xwidth;
#endif /*]*/
static Dimension container_width;
static Dimension cwidth_nkp;
static Dimension container_height;
static Dimension scrollbar_width;
static char    *aicon_text = CN;
static XFontStruct *ailabel_font;
static Dimension aicon_label_height = 0;
static GC       ailabel_gc;
static Pixel    cpx[16];
static Boolean  cpx_done[16];
static Pixel    normal_pixel;
static Pixel    select_pixel;
static Pixel    bold_pixel;
static Pixel    selbg_pixel;
static Pixel    cursor_pixel;
static Boolean  text_blinking_on = True;
static Boolean  text_blinkers_exist = False;
static Boolean  text_blink_scheduled = False;
static XtIntervalId text_blink_id;
static XtTranslations screen_t00 = NULL;
static XtTranslations screen_t0 = NULL;
static XtTranslations container_t00 = NULL;
static XtTranslations container_t0 = NULL;
static unsigned char    *rt_buf8 = (unsigned char *) NULL;
static XChar2b *rt_buf16 = (XChar2b *) NULL;
static char    *color_name[16] = {
    (char *)NULL, (char *)NULL, (char *)NULL, (char *)NULL,
    (char *)NULL, (char *)NULL, (char *)NULL, (char *)NULL,
    (char *)NULL, (char *)NULL, (char *)NULL, (char *)NULL,
    (char *)NULL, (char *)NULL, (char *)NULL, (char *)NULL
};
static Boolean	configure_ticking = False;
static XtIntervalId configure_id;

static Pixmap   inv_icon;
static Pixmap   wait_icon;
static Pixmap   inv_wait_icon;
static Boolean  icon_inverted = False;
static Widget	icon_shell;

static struct font_list *font_last = (struct font_list *) NULL;

/* Globals for undoing reconfigurations. */
static enum {
    REDO_NONE,
    REDO_FONT,
#if defined(X3270_MENUS) /*[*/
    REDO_MODEL,
#endif /*]*/
#if defined(X3270_KEYPAD) /*[*/
    REDO_KEYPAD,
#endif /*]*/
    REDO_SCROLLBAR,
    REDO_RESIZE
} screen_redo = REDO_NONE;
static char *redo_old_font = CN;
#if defined(X3270_MENUS) /*[*/
static int redo_old_model;
static int redo_old_ov_cols;
static int redo_old_ov_rows;
#endif /*]*/

static unsigned char blank_map[32];
#define BKM_SET(n)	blank_map[(n)/8] |= 1 << ((n)%8)
#define BKM_ISSET(n)	((blank_map[(n)/8] & (1 << ((n)%8))) != 0)

enum fallback_color { FB_WHITE, FB_BLACK };
static enum fallback_color ibm_fb = FB_WHITE;

/*
 * The screen state structure.  This structure is swapped whenever we switch
 * between normal and active-iconic states.
 */
#define NGCS	16
struct sstate {
	Widget          widget;	/* the widget */
	Window          window;	/* the window */
	union sp       *image;	/* what's on the X display */
	int             cursor_daddr;	/* displayed cursor address */
	Boolean         exposed_yet;	/* have we been exposed yet? */
	Boolean         overstrike;	/* are we overstriking? */
	Dimension       screen_width;	/* screen dimensions in pixels */
	Dimension       screen_height;
	GC              gc[NGCS * 2],	/* standard, inverted GCs */
	                selgc[NGCS],	/* color selected text GCs */
	                mcgc,	/* monochrome block cursor GC */
	                ucgc,	/* unique-cursor-color cursor GC */
	                invucgc;/* inverse ucgc */
	int             char_height;
	int             char_width;
	Font		fid;
	int		ascent;
	int		descent;
	Boolean         standard_font;
	Boolean		extended_3270font;
	Boolean         latin1_font;
	Boolean         debugging_font;
	Boolean         obscured;
	Boolean         copied;
	unsigned char  *xfmap;	/* standard font CG-to-ASCII map */
};
static struct sstate nss;
static struct sstate iss;
static struct sstate *ss = &nss;

/* Globals based on nss, used mostly by status and select routines. */
Widget         *screen = &nss.widget;
Window         *screen_window = &nss.window;
int            *char_width = &nss.char_width;
int            *char_height = &nss.char_height;
int            *ascent = &nss.ascent;
int            *descent = &nss.descent;
Boolean        *standard_font = &nss.standard_font;
Boolean        *latin1_font = &nss.latin1_font;
Boolean        *debugging_font = &nss.debugging_font;
Boolean        *extended_3270font = &nss.extended_3270font;

/* Mouse-cursor state */
enum mcursor_state { LOCKED, NORMAL, WAIT };
static enum mcursor_state mcursor_state = LOCKED;
static enum mcursor_state icon_cstate = NORMAL;

static void aicon_init(void);
static void aicon_reinit(unsigned cmask);
static void screen_focus(Boolean in);
static void make_gc_set(struct sstate *s, int i, Pixel fg, Pixel bg);
static void make_gcs(struct sstate *s);
static void put_cursor(int baddr, Boolean on);
static void resync_display(union sp *buffer, int first, int last);
static void draw_fields(union sp *buffer, int first, int last);
static void render_text(union sp *buffer, int baddr, int len,
    Boolean block_cursor, union sp *attrs);
static void cursor_pos(void);
static void cursor_on(void);
static void schedule_cursor_blink(void);
static void schedule_text_blink(void);
static void inflate_screen(void);
static int fa_color(unsigned char fa);
static Boolean cursor_off(void);
static void draw_aicon_label(void);
static void set_mcursor(void);
static void scrollbar_init(Boolean is_reset);
static void init_rsfonts(void);
static void allocate_pixels(void);
static int fl_baddr(int baddr);
static GC get_gc(struct sstate *s, int color);
static GC get_selgc(struct sstate *s, int color);
static void default_color_scheme(void);
static Boolean xfer_color_scheme(char *cs, Boolean do_popup);
static void set_font_globals(XFontStruct *f, const char *ef, Font ff);
static void screen_connect(Boolean ignored);
static void configure_stable(XtPointer closure, XtIntervalId *id);
static void cancel_blink(void);
static void render_blanks(int baddr, int height, union sp *buffer);
static void resync_text(int baddr, int len, union sp *buffer);
static void screen_reinit(unsigned cmask);
static void aicon_font_init(void);
static void aicon_size(Dimension *iw, Dimension *ih);
static void invert_icon(Boolean inverted);
static const char *load_fixed_font(const char *name);
static void lock_icon(enum mcursor_state state);

/* Resize font list. */
struct rsfont {
	struct rsfont *next;
	char *name;
	int width;
	int height;
	int total_width;	/* transient */
	int total_height;	/* transient */
	int area;		/* transient */
};
static struct rsfont *rsfonts;

#define BASE_MASK		0x0f	/* mask for 16 actual colors */
#define INVERT_MASK		0x10	/* toggle for inverted colors */
#define GC_NONDEFAULT		0x20	/* distinguishes "color 0" from zeroed
					    memory */

#define COLOR_MASK		(GC_NONDEFAULT | BASE_MASK)
#define INVERT_COLOR(c)		((c) ^ INVERT_MASK)
#define NO_INVERT(c)		((c) & ~INVERT_MASK)

#define DEFAULT_PIXEL		(appres.m3279 ? COLOR_BLUE : FA_INT_NORM_NSEL)
#define PIXEL_INDEX(c)		((c) & BASE_MASK)


/*
 * Save 00 event translations.
 */
void
save_00translations(Widget w, XtTranslations *t00)
{
	*t00 = w->core.tm.translations;
}

/* 
 * Define our event translations
 */
void
set_translations(Widget w, XtTranslations *t00, XtTranslations *t0)
{
	struct trans_list *t;

	if (t00 != (XtTranslations *)NULL)
		XtOverrideTranslations(w, *t00);

	for (t = trans_list; t != NULL; t = t->next)
		XtOverrideTranslations(w, lookup_tt(t->name, CN));

	*t0 = w->core.tm.translations;
}

/*
 * Add or clear a temporary keymap.
 */
void
screen_set_temp_keymap(XtTranslations trans)
{
	if (trans != (XtTranslations)NULL) {
		XtOverrideTranslations(nss.widget, trans);
		XtOverrideTranslations(container, trans);
	} else {
		XtUninstallTranslations(nss.widget);
		XtOverrideTranslations(nss.widget, screen_t0);
		XtUninstallTranslations(container);
		XtOverrideTranslations(container, container_t0);
	}
}

/*
 * Change the baselevel keymap.
 */
void
screen_set_keymap(void)
{
	XtUninstallTranslations(nss.widget);
	set_translations(nss.widget, &screen_t00, &screen_t0);
	XtUninstallTranslations(container);
	set_translations(container, &container_t00, &container_t0);
}


/*
 * Initialize the screen.
 */
void
screen_init(void)
{
	register int i;

	/* Initialize ss. */
	nss.cursor_daddr = 0;
	nss.exposed_yet = False;

	/* Initialize "gray" bitmap. */
	if (appres.mono)
		gray = XCreatePixmapFromBitmapData(display,
		    root_window, (char *)gray_bits,
		    gray_width, gray_height,
		    appres.foreground, appres.background, screen_depth);

	/* Initialize the resize list. */
	init_rsfonts();

	/* Initialize the blank map. */
	(void) memset((char *)blank_map, '\0', sizeof(blank_map));
	BKM_SET(CG_null);
	BKM_SET(CG_nobreakspace);
	BKM_SET(CG_ff);
	BKM_SET(CG_cr);
	BKM_SET(CG_nl);
	BKM_SET(CG_em);
	BKM_SET(CG_space);
	for (i = 0; i <= 0xf; i++) {
		BKM_SET(0xc0 + i);
		BKM_SET(0xe0 + i);
	}

	/* Register state change callbacks. */
	register_schange(ST_HALF_CONNECT, screen_connect);
	register_schange(ST_CONNECT, screen_connect);
	register_schange(ST_3270_MODE, screen_connect);

	/* Initialize the emulated 3270 controller hardware. */
	ctlr_init(ALL_CHANGE);

	/* Initialize the actve icon. */
	aicon_init();

	/* Initialize the status line. */
	status_init();

	/* Initialize the placement of the pop-up keypad. */
	keypad_placement_init();

	/* Now call the "reinitialize" function to set everything else up. */
	screen_reinit(ALL_CHANGE);
}

/*
 * Re-initialize the screen.
 */
static void
screen_reinit(unsigned cmask)
{
	Dimension cwidth_curr;
#if defined(X3270_KEYPAD) /*[*/
	Dimension mkw;
#endif /*]*/

	/* Allocate colors. */
	if (cmask & COLOR_CHANGE) {
		if (appres.m3279) {
			default_color_scheme();
			(void) xfer_color_scheme(appres.color_scheme, False);
		}
		allocate_pixels();
	}

	/* Define graphics contexts. */
	if (cmask & (FONT_CHANGE | COLOR_CHANGE))
		make_gcs(&nss);

	/* Reinitialize the controller. */
	ctlr_reinit(cmask);

	/* Allocate buffers. */
	if (cmask & MODEL_CHANGE) {
		/* Selection bitmap */
		if (selected)
			XtFree((char *)selected);
		selected = (unsigned char *)XtCalloc(sizeof(unsigned char),
				(maxROWS * maxCOLS + 7) / 8);

		/* X display image */
		if (nss.image)
			XtFree((char *)nss.image);
		nss.image = (union sp *)XtCalloc(sizeof(union sp),
				maxROWS * maxCOLS);
		if (temp_image)
			XtFree((char *)temp_image);
		temp_image = (union sp *)XtCalloc(sizeof(union sp),
				maxROWS*maxCOLS);

		/* render_text buffers */
		if (rt_buf8 != (unsigned char *)CN)
			XtFree((char *)rt_buf8);
		rt_buf8 = (unsigned char *)XtMalloc(maxCOLS);
		if (rt_buf16 != (XChar2b *)NULL)
			XtFree((char *)rt_buf16);
		rt_buf16 = (XChar2b *)XtMalloc(maxCOLS * sizeof(XChar2b));
	} else
		(void) memset((char *) nss.image, 0,
		              sizeof(union sp) * maxROWS * maxCOLS);

	/* Set up a container for the menubar, screen and keypad */

	if (cmask & (FONT_CHANGE | MODEL_CHANGE)) {
		nss.screen_width  = SCREEN_WIDTH(ss->char_width);
		nss.screen_height = SCREEN_HEIGHT(ss->char_height);
	}

	if (toggled(SCROLL_BAR))
		scrollbar_width = SCROLLBAR_WIDTH;
	else
		scrollbar_width = 0;
	container_width = cwidth_nkp = nss.screen_width+2 + scrollbar_width;
#if defined(X3270_KEYPAD) /*[*/
	mkw = min_keypad_width();
	if (kp_placement == kp_integral && container_width < mkw) {
		keypad_xwidth = mkw - container_width;
		container_width = mkw;
	} else
		keypad_xwidth = 0;
#endif /*]*/

	if (container == (Widget)NULL) {
		container = XtVaCreateManagedWidget(
		    "container", huskWidgetClass, toplevel,
		    XtNborderWidth, 0,
		    XtNwidth, container_width,
		    XtNheight, 10,
			/* XXX -- a temporary lie to make Xt happy */
		    NULL);
		save_00translations(container, &container_t00);
		set_translations(container, (XtTranslations *)NULL,
		    &container_t0);
		if (appres.mono)
			XtVaSetValues(container, XtNbackgroundPixmap, gray,
			    NULL);
		else
			XtVaSetValues(container, XtNbackground, keypadbg_pixel,
			    NULL);
	}

	/* Initialize the menu bar and integral keypad */

#if defined(X3270_KEYPAD) /*[*/
	cwidth_curr = appres.keypad_on ? container_width : cwidth_nkp;
#else /*][*/
	cwidth_curr = container_width;
#endif /*]*/
	menubar_height = menubar_qheight(cwidth_curr);
	menubar_init(container, container_width, cwidth_curr);

	container_height = menubar_height + nss.screen_height+2;
#if defined(X3270_KEYPAD) /*[*/
	if (kp_placement == kp_integral) {
		(void) keypad_init(container, container_height,
		    container_width, False, False);
		keypad_height = keypad_qheight();
	} else
		keypad_height = 0;
	container_height += keypad_height;
#endif /*]*/

	/* Create screen and set container dimensions */
	inflate_screen();

	/* Create scrollbar */
	scrollbar_init((cmask & MODEL_CHANGE) != 0);

	XtRealizeWidget(toplevel);
	nss.window = XtWindow(nss.widget);
	set_mcursor();

	/* Reinitialize the active icon. */
	aicon_reinit(cmask);

	/* Reinitialize the status line. */
	status_reinit(cmask);

	cursor_changed = True;

	line_changed = True;

	/* Redraw the screen. */
	action_internal(PA_Expose_action, IA_REDRAW, CN, CN);
}


static void
set_toplevel_sizes(void)
{
	Dimension tw, th;

#if defined(X3270_KEYPAD) /*[*/
	tw = container_width - (appres.keypad_on ? 0 : keypad_xwidth);
	th = container_height - (appres.keypad_on ? 0 : keypad_height);
#else /*][*/
	tw = container_width;
	th = container_height;
#endif /*]*/
	XtVaSetValues(toplevel,
	    XtNwidth, tw,
	    XtNheight, th,
	    NULL);
	if (!appres.allow_resize)
		XtVaSetValues(toplevel,
		    XtNbaseWidth, tw,
		    XtNbaseHeight, th,
		    XtNminWidth, tw,
		    XtNminHeight, th,
		    XtNmaxWidth, tw,
		    XtNmaxHeight, th,
		    NULL);
	XtVaSetValues(container,
	    XtNwidth, container_width,
	    XtNheight, container_height,
	    NULL);
	main_width = tw;
	main_height = th;

	/*
	 * Start a timer ticking, in case the window manager doesn't approve
	 * of the change.
	 */
	if (configure_ticking)
		XtRemoveTimeOut(configure_id);
	configure_id = XtAppAddTimeOut(appcontext, 500, configure_stable, 0);
	configure_ticking = True;

	keypad_move();
}

static void
inflate_screen(void)
{
	/* Create the screen window */
	if (nss.widget == NULL) {
		nss.widget = XtVaCreateManagedWidget(
		    "screen", widgetClass, container,
		    XtNwidth, nss.screen_width,
		    XtNheight, nss.screen_height,
		    XtNx,
#if defined(X3270_KEYPAD) /*[*/
			appres.keypad_on ? (keypad_xwidth / 2) : 0,
#else /*][*/
			0,
#endif /*]*/
		    XtNy, menubar_height,
		    XtNbackground,
			appres.mono ? appres.background : colorbg_pixel,
		    NULL);
		save_00translations(nss.widget, &screen_t00);
		set_translations(nss.widget, (XtTranslations *)NULL,
		    &screen_t0);
	} else {
		XtVaSetValues(nss.widget,
		    XtNwidth, nss.screen_width,
		    XtNheight, nss.screen_height,
		    XtNx,
#if defined(X3270_KEYPAD) /*[*/
			appres.keypad_on ? (keypad_xwidth / 2) : 0,
#else /*][*/
			0,
#endif /*]*/
		    XtNy, menubar_height,
		    XtNbackground,
			appres.mono ? appres.background : colorbg_pixel,
		    NULL);
	}

	/* Set the container and toplevel dimensions */
	XtVaSetValues(container,
	    XtNwidth, container_width,
	    XtNheight, container_height,
	    NULL);

	set_toplevel_sizes();
}

/* Scrollbar support. */
void
screen_set_thumb(float top, float shown)
{
	if (toggled(SCROLL_BAR))
		XawScrollbarSetThumb(scrollbar, top, shown);
}

static void
screen_scroll_proc(Widget w unused, XtPointer client_data unused,
    XtPointer position)
{
	scroll_proc((int)position, (int)nss.screen_height);
}

static void
screen_jump_proc(Widget w unused, XtPointer client_data unused,
    XtPointer percent_ptr)
{
	jump_proc(*(float *)percent_ptr);
}

/* Create, move, or reset the scrollbar. */
static void
scrollbar_init(Boolean is_reset)
{
	if (!scrollbar_width) {
		if (scrollbar != (Widget)NULL)
			XtUnmapWidget(scrollbar);
	} else {
		if (scrollbar == (Widget)NULL) {
			scrollbar = XtVaCreateManagedWidget(
			    "scrollbar", scrollbarWidgetClass,
			    container,
			    XtNx, nss.screen_width+1
#if defined(X3270_KEYPAD) /*[*/
				    + (appres.keypad_on ? (keypad_xwidth / 2) : 0)
#endif /*]*/
				    ,
			    XtNy, menubar_height,
			    XtNwidth, scrollbar_width-1,
			    XtNheight, nss.screen_height,
			    XtNbackground, appres.mono ?
				appres.background : keypadbg_pixel,
			    NULL);
			XtAddCallback(scrollbar, XtNscrollProc,
			    screen_scroll_proc, NULL);
			XtAddCallback(scrollbar, XtNjumpProc,
			    screen_jump_proc, NULL);
		} else {
			XtVaSetValues(scrollbar,
			    XtNx, nss.screen_width+1
#if defined(X3270_KEYPAD) /*[*/
				    + (appres.keypad_on ? (keypad_xwidth / 2) : 0)
#endif /*]*/
				    ,
			    XtNy, menubar_height,
			    XtNwidth, scrollbar_width-1,
			    XtNheight, nss.screen_height,
			    XtNbackground, appres.mono ?
				appres.background : keypadbg_pixel,
			    NULL);
			XtMapWidget(scrollbar);
		}
		XawScrollbarSetThumb(scrollbar, 0.0, 1.0);

	}

	/*
	 * If the screen dimensions have changed, reallocate the scroll
	 * save area.
	 */
	if (is_reset || !scroll_initted)
		scroll_init();
	else
		rethumb();
}

/* Turn the scrollbar on or off */
void
toggle_scrollBar(struct toggle *t unused, enum toggle_type tt unused)
{
	scrollbar_changed = True;

	if (toggled(SCROLL_BAR)) {
		scrollbar_width = SCROLLBAR_WIDTH;
		screen_redo = REDO_SCROLLBAR;
	} else {
		scroll_to_bottom();
		scrollbar_width = 0;
	}

	screen_reinit(SCROLL_CHANGE);
	if (toggled(SCROLL_BAR))
		rethumb();
}

/*
 * Called when a host connects, disconnects or changes ANSI/3270 mode.
 */
static void
screen_connect(Boolean ignored unused)
{
	if (!screen_buf)
		return;		/* too soon */

	if (CONNECTED) {
		ctlr_erase(True);
		if (IN_3270)
			scroll_round();
		cursor_on();
		schedule_cursor_blink();
	} else {
		if (appres.disconnect_clear)
			ctlr_erase(True);
		(void) cursor_off();
	}

	mcursor_normal();
}

/*
 * Mouse cursor changes
 */

static void
set_mcursor(void)
{
	switch (mcursor_state) {
	    case LOCKED:
		XDefineCursor(display, nss.window, appres.locked_mcursor);
		break;
	    case NORMAL:
		XDefineCursor(display, nss.window, appres.normal_mcursor);
		break;
	    case WAIT:
		XDefineCursor(display, nss.window, appres.wait_mcursor);
		break;
	}
	lock_icon(mcursor_state);
}

void
mcursor_normal(void)
{
	if (CONNECTED)
		mcursor_state = NORMAL;
	else if (HALF_CONNECTED)
		mcursor_state = WAIT;
	else
		mcursor_state = LOCKED;
	set_mcursor();
}

void
mcursor_waiting(void)
{
	mcursor_state = WAIT;
	set_mcursor();
}

void
mcursor_locked(void)
{
	mcursor_state = LOCKED;
	set_mcursor();
}

#if defined(X3270_KEYPAD) /*[*/
/*
 * Called from the keypad button to expose or hide the integral keypad.
 */
void
screen_showikeypad(Boolean on)
{
	if (on)
		screen_redo = REDO_KEYPAD;

	inflate_screen();
	if (keypad_xwidth > 0) {
		if (scrollbar != (Widget) NULL)
			scrollbar_init(False);
		menubar_resize(on ? container_width : cwidth_nkp);
	}
}
#endif /*]*/


/*
 * The host just wrote a blinking character; make sure it blinks
 */
void
blink_start(void)
{
	text_blinkers_exist = True;
	if (!text_blink_scheduled) {
		/* Start in "on" state and start first iteration */
		text_blinking_on = True;
		schedule_text_blink();
	}
}

/*
 * Restore blanked blinking text
 */
static void
text_blink_it(XtPointer closure unused, XtIntervalId *id unused)
{
	/* Flip the state. */
	text_blinking_on = !text_blinking_on;

	/* Force a screen redraw. */
	ctlr_changed(0, ROWS*COLS);

	/* If there is still blinking text, schedule the next iteration */
	if (text_blinkers_exist)
		schedule_text_blink();
	else
		text_blink_scheduled = False;
}

/*
 * Schedule an event to restore blanked blinking text
 */
static void
schedule_text_blink(void)
{
	text_blink_scheduled = True;
	text_blink_id = XtAppAddTimeOut(appcontext, 500, text_blink_it, 0);
}


/*
 * Make the (displayed) cursor disappear.  Returns a Boolean indiciating if
 * the cursor was on before the call.
 */
static Boolean
cursor_off(void)
{
	if (cursor_displayed) {
		cursor_displayed = False;
		put_cursor(ss->cursor_daddr, False);
		return True;
	} else
		return False;
}



/*
 * Blink the cursor
 */
static void
cursor_blink_it(XtPointer closure unused, XtIntervalId *id unused)
{
	cursor_blink_pending = False;
	if (!CONNECTED || !toggled(CURSOR_BLINK))
		return;
	if (cursor_displayed) {
		if (in_focus)
			(void) cursor_off();
	} else
		cursor_on();
	schedule_cursor_blink();
}

/*
 * Schedule a cursor blink
 */
static void
schedule_cursor_blink(void)
{
	if (!toggled(CURSOR_BLINK) || cursor_blink_pending)
		return;
	cursor_blink_pending = True;
	cursor_blink_id = XtAppAddTimeOut(appcontext, 500, cursor_blink_it, 0);
}

/*
 * Cancel a cursor blink
 */
static void
cancel_blink(void)
{
	if (cursor_blink_pending) {
		XtRemoveTimeOut(cursor_blink_id);
		cursor_blink_pending = False;
	}
}

/*
 * Toggle cursor blinking (called from menu)
 */
void
toggle_cursorBlink(struct toggle *t unused, enum toggle_type tt unused)
{
	if (!CONNECTED)
		return;

	if (toggled(CURSOR_BLINK))
		schedule_cursor_blink();
	else
		cursor_on();
}

/*
 * Make the cursor visible at its (possibly new) location.
 */
static void
cursor_on(void)
{
	if (cursor_enabled && !cursor_displayed) {
		cursor_displayed = True;
		put_cursor(cursor_addr, True);
		ss->cursor_daddr = cursor_addr;
		cursor_changed = False;
	}
}


/*
 * Toggle the cursor (block/underline).
 */
void
toggle_altCursor(struct toggle *t, enum toggle_type tt unused)
{
	Boolean was_on;

	/* do_toggle already changed the value; temporarily change it back */
	toggle_toggle(t);

	was_on = cursor_off();

	/* Now change it back again */
	toggle_toggle(t);

	if (was_on)
		cursor_on();
}


/*
 * Move the cursor to the specified buffer address.
 */
void
cursor_move(int baddr)
{
	cursor_addr = baddr;
	cursor_pos();
}

/*
 * Display the cursor position on the status line
 */
static void
cursor_pos(void)
{
	if (!toggled(CURSOR_POS) || !CONNECTED)
		return;
	status_cursor_pos(cursor_addr);
}

/*
 * Toggle the display of the cursor position
 */
void
toggle_cursorPos(struct toggle *t unused, enum toggle_type tt unused)
{
	if (toggled(CURSOR_POS))
		cursor_pos();
	else
		status_uncursor_pos();
}

/*
 * Enable or disable cursor display (used by scroll logic)
 */
void
enable_cursor(Boolean on)
{
	if ((cursor_enabled = on) && CONNECTED) {
		cursor_on();
		cursor_changed = True;
	} else
		(void) cursor_off();
}


/*
 * Redraw the screen.
 */
static void
do_redraw(Widget w, XEvent *event, String *params unused,
    Cardinal *num_params unused)
{
	int	x, y, width, height;
	int	startcol, ncols;
	int	startrow, endrow, row;
	register int i;
	int     c0;

	if (w == nss.widget) {
		keypad_first_up();
		if (appres.active_icon && iconic) {
			ss = &nss;
			iconic = False;
		}
	} else if (appres.active_icon && w == iss.widget) {
		if (appres.active_icon && !iconic) {
			ss = &iss;
			iconic = True;
		}
	} else if (event)
		return;

	/* Only redraw as necessary for an expose event */
	if (event && event->type == Expose) {
		ss->exposed_yet = True;
		x = event->xexpose.x;
		y = event->xexpose.y;
		width = event->xexpose.width;
		height = event->xexpose.height;
		startrow = ssY_TO_ROW(y);
		if (startrow < 0)
			startrow = 0;
		if (startrow > 0)
			startrow--;
		endrow = ssY_TO_ROW(y+height);
		endrow = endrow >= maxROWS ? maxROWS : endrow + 1;
		startcol = ssX_TO_COL(x);
		if (startcol < 0)
			startcol = 0;
		if (startcol > 0)
			startcol--;
		ncols = ssX_TO_COL(width+ss->char_width) + 2;
		while ((ROWCOL_TO_BA(startrow, startcol) % maxCOLS) + ncols > maxCOLS)
			ncols--;
		for (row = startrow; row < endrow; row++) {
			(void) memset((char *) &ss->image[ROWCOL_TO_BA(row, startcol)],
			      0, ncols * sizeof(union sp));
			if (ss->debugging_font) {
				c0 = ROWCOL_TO_BA(row, startcol);

				for (i = 0; i < ncols; i++)
					ss->image[c0 + i].bits.cg = CG_space;
			}
		}
					    
	} else {
		XFillRectangle(display, ss->window,
		    get_gc(ss, INVERT_COLOR(0)),
		    0, 0, ss->screen_width, ss->screen_height);
		(void) memset((char *) ss->image, 0,
		              (maxROWS*maxCOLS) * sizeof(union sp));
		if (ss->debugging_font)
			for (i = 0; i < maxROWS*maxCOLS; i++)
				ss->image[i].bits.cg = CG_space;
		ss->copied = False;
	}
	ctlr_changed(0, ROWS*COLS);
	cursor_changed = True;
	if (!appres.active_icon || !iconic) {
		line_changed = True;
		status_touch();
	}
}

/*
 * Explicitly redraw the screen (invoked from the keyboard).
 */
void
Redraw_action(Widget w, XEvent *event, String *params, Cardinal *num_params)
{
	action_debug(Redraw_action, event, params, num_params);
	do_redraw(w, event, params, num_params);
}

/*
 * Implicitly redraw the screen (triggered by Expose events).
 */
void
PA_Expose_action(Widget w, XEvent *event, String *params, Cardinal *num_params)
{
#if defined(INTERNAL_ACTION_DEBUG) /*[*/
	action_debug(PA_Expose_action, event, params, num_params);
#endif /*]*/
	do_redraw(w, event, params, num_params);
}


/*
 * Redraw the changed parts of the screen.
 */

void
screen_disp(void)
{

	/* No point in doing anything if we aren't visible yet. */
	if (!ss->exposed_yet)
		return;

	/*
	 * We don't set "cursor_changed" when the host moves the cursor,
	 * 'cause he might just move it back later.  Set it here if the cursor
	 * has moved since the last call to screen_disp.
	 */
	if (cursor_addr != ss->cursor_daddr)
		cursor_changed = True;

	/*
	 * If only the cursor has changed (and not the screen image), draw it.
	 */
	if (cursor_changed && !screen_changed) {
		if (cursor_off())
			cursor_on();
	}

	/*
	 * Redraw the parts of the screen that need refreshing, and redraw the
	 * cursor if necessary.
	 */
	if (screen_changed) {
		Boolean	was_on = False;

		/* Draw the new screen image into "temp_image" */
		if (screen_changed)
			draw_fields(temp_image, first_changed, last_changed);

		/* Set "cursor_changed" if the text under it has changed. */
		if (ss->image[fl_baddr(cursor_addr)].word !=
		    temp_image[fl_baddr(cursor_addr)].word)
			cursor_changed = True;

		/* Undraw the cursor, if necessary. */
		if (cursor_changed)
			was_on = cursor_off();

		/* Intelligently update the X display with the new text. */
		resync_display(temp_image, first_changed, last_changed);

		/* Redraw the cursor. */
		if (was_on)
			cursor_on();

		screen_changed = False;
		first_changed = -1;
		last_changed = -1;
	}

	if (!appres.active_icon || !iconic) {
		/* Refresh the status line. */
		status_disp();

		/* Refresh the line across the bottom of the screen. */
		if (line_changed) {
			XDrawLine(display, ss->window,
			    get_gc(ss, GC_NONDEFAULT | DEFAULT_PIXEL),
			    0,
			    ssROW_TO_Y(maxROWS-1)+SGAP-1,
			    ssCOL_TO_X(maxCOLS)+HHALO,
			    ssROW_TO_Y(maxROWS-1)+SGAP-1);
			line_changed = False;
		}
	}
	draw_aicon_label();
}


/*
 * Render a blank rectangle on the X display.
 */
static void
render_blanks(int baddr, int height, union sp *buffer)
{
	int x, y;

#if defined(_ST) /*[*/
	(void) printf("render_blanks(baddr=%s, height=%d)\n", rcba(baddr),
	    height);
#endif /*]*/

	x = ssCOL_TO_X(BA_TO_COL(baddr));
	y = ssROW_TO_Y(BA_TO_ROW(baddr));

	XFillRectangle(display, ss->window,
	    get_gc(ss, INVERT_COLOR(0)),
	    x, y - ss->ascent,
	    (ss->char_width * COLS) + 1, (ss->char_height * height));

	(void) memmove(&ss->image[baddr], &buffer[baddr],
	                   COLS * height *sizeof(union sp));
}

/*
 * Check if a region of the screen is effectively empty: all blanks or nulls
 * with no extended highlighting attributes and not selected.
 *
 * Works _only_ with non-debug fonts.
 */
static Boolean
empty_space(register union sp *buffer, int len)
{
	register int i;

	for (i = 0; i < len; i++, buffer++) {
		if (buffer->bits.gr ||
		    buffer->bits.sel ||
		    (buffer->bits.fg & INVERT_MASK) ||
		    !BKM_ISSET(buffer->bits.cg))
			return False;
	}
	return True;
}


/*
 * Reconcile the differences between a region of 'buffer' (what we want on
 * the X display) and ss->image[] (what is on the X display now).  The region
 * must not span lines.
 */
static void
resync_text(int baddr, int len, union sp *buffer)
{
	static Boolean ever = False;
	static unsigned long cmask = 0L;
	static unsigned long gmask = 0L;

#if defined(_ST) /*[*/
	(void) printf("resync_text(baddr=%s, len=%d)\n", rcba(baddr), len);
#endif /*]*/
	if (!ever) {
		union sp b;

		/* Create masks for the "important" fields in an sp. */
		b.word = 0L;
		b.bits.fg = COLOR_MASK | INVERT_MASK;
		b.bits.sel = 1;
		b.bits.gr = GR_UNDERLINE | GR_INTENSIFY;
		cmask = b.word;

		b.word = 0L;
		b.bits.fg = INVERT_MASK;
		b.bits.sel = 1;
		b.bits.gr = 0xf;
		gmask = b.word;

		ever = True;
	}

	if (!ss->debugging_font &&
	    len > 1 &&
	    empty_space(&buffer[baddr], len)) {
		int x, y;

		x = ssCOL_TO_X(BA_TO_COL(baddr));
		y = ssROW_TO_Y(BA_TO_ROW(baddr));

		/* All empty, fill a rectangle */
#if defined(_ST) /*[*/
		(void) printf("FillRectangle(baddr=%s, len=%d)\n", rcba(baddr),
		    len);
#endif /*]*/
		XFillRectangle(display, ss->window,
		    get_gc(ss, INVERT_COLOR(0)),
		    x, y - ss->ascent,
		    (ss->char_width * len) + 1, ss->char_height);
	} else {
		unsigned long attrs, attrs2;
		Boolean has_gr, has_gr2;
		Boolean empty, empty2;
		union sp ra;
		int i;
		int i0 = 0;

		ra = buffer[baddr];

		/* Note the characteristics of the beginning of the region. */
		attrs = buffer[baddr].word & cmask;
		has_gr = (buffer[baddr].word & gmask) != 0;
		empty = !has_gr && BKM_ISSET(buffer[baddr].bits.cg);

		for (i = 0; i < len; i++) {
			/* Note the characteristics of this character. */
			attrs2 = buffer[baddr+i].word & cmask;
			has_gr2 = (buffer[baddr+i].word & gmask) != 0;
			empty2 = !has_gr2 && BKM_ISSET(buffer[baddr+i].bits.cg);

			/* If this character has exactly the same attributes
			   as the current region, simply add it, noting that
			   the region might now not be empty. */
			if (attrs2 == attrs) {
				if (!empty2)
					empty = 0;
				continue;
			}

			/* If this character is empty, and the current region
			   has no GR attributes, pretend it matches. */
			if (empty2 && !has_gr)
				continue;

			/* If the current region is empty, this character
			   isn't empty, and this character has no GR
			   attributes, change the current region's attributes
			   to this character's attributes and add it. */
			if (empty && !empty2 && !has_gr2) {
				attrs = attrs2;
				has_gr = has_gr2;
				empty = empty2;
				ra = buffer[baddr+i];
				continue;
			}

			/* Dump the region and start a new one with this
			   character. */
			render_text(&buffer[baddr+i0], baddr+i0, i - i0, False,
			    &ra);
			attrs = attrs2;
			has_gr = has_gr2;
			empty = empty2;
			i0 = i;
			ra = buffer[baddr+i];
		}

		/* Dump the remainder of the region. */
		render_text(&buffer[baddr+i0], baddr+i0, len - i0, False, &ra);
	}

	/* The X display is now correct; update ss->image[]. */
	(void) memmove(&ss->image[baddr], &buffer[baddr], len*sizeof(union sp));
}

/*
 * Render text onto the X display.  The region must not span lines.
 */
static void
render_text(union sp *buffer, int baddr, int len, Boolean block_cursor,
    union sp *attrs)
{
	int color;
	int x, y;
	GC dgc;
	int sel = attrs->bits.sel;
	register int i;

#if defined(_ST) /*[*/
	(void) printf("render_text(baddr=%s, len=%d)\n", rcba(baddr), len);
#endif /*]*/

	if (ss->standard_font) {		/* Standard X font */
		register unsigned char *xfmap = ss->xfmap;

		if (toggled(MONOCASE)) {
			for (i = 0; i < len; i++) {
				switch (buffer[i].bits.cs) {
				    case 0:
					rt_buf8[i] =
					    asc2uc[xfmap[buffer[i].bits.cg]];
					break;
				    case 1:
					rt_buf8[i] = ge2asc[buffer[i].bits.cg];
					break;
				    case 2:
					rt_buf8[i] = buffer[i].bits.cg;
					break;
				}
			}
		} else for (i = 0; i < len; i++) {
			switch (buffer[i].bits.cs) {
			    case 0:
				rt_buf8[i] = xfmap[buffer[i].bits.cg];
				break;
			    case 1:
				rt_buf8[i] = ge2asc[buffer[i].bits.cg];
				break;
			    case 2:
				rt_buf8[i] = buffer[i].bits.cg;
				break;
			}
		}
	} else if (ss->extended_3270font) {	/* 16-bit (new) x3270 font */
		if (toggled(MONOCASE)) {
			for (i = 0; i < len; i++) {
				rt_buf16[i].byte1 = buffer[i].bits.cs;
				switch (buffer[i].bits.cs) {
				    case 0:
					rt_buf16[i].byte2 =
					    cg2uc[buffer[i].bits.cg];
					break;
				    case 1:
				    case 2:
					rt_buf16[i].byte2 = buffer[i].bits.cg;
					break;
				}
			}
		} else
			for (i = 0; i < len; i++) {
				rt_buf16[i].byte1 = buffer[i].bits.cs;
				rt_buf16[i].byte2 = buffer[i].bits.cg;
			}
	} else {				/* 8-bit (old) x3270 font */
		if (toggled(MONOCASE)) {
			for (i = 0; i < len; i++) {
				switch (buffer[i].bits.cs) {
				    case 0:
					rt_buf8[i] =
					    cg2uc[buffer[i].bits.cg];
					break;
				    case 1:
					rt_buf8[i] =
					    ge2cg8[buffer[i].bits.cg];
					break;
				    case 2:
					rt_buf8[i] = CG_boxsolid;
					break;
				}
			}
		} else
			for (i = 0; i < len; i++) {
				switch (buffer[i].bits.cs) {
				    case 0:
					rt_buf8[i] = buffer[i].bits.cg;
					break;
				    case 1:
					rt_buf8[i] =
					    ge2cg8[buffer[i].bits.cg];
					break;
				    case 2:
					rt_buf8[i] = CG_boxsolid;
					break;
				}
			}
	}

	x = ssCOL_TO_X(BA_TO_COL(baddr));
	y = ssROW_TO_Y(BA_TO_ROW(baddr));
	color = attrs->bits.fg;

	/* Draw. */
	if (sel && !block_cursor) {
		if (!appres.mono)
			dgc = get_selgc(ss, color);
		else
			dgc = get_gc(ss, INVERT_COLOR(color));
	} else if (block_cursor && !(appres.mono && sel)) {
		if (appres.use_cursor_color)
			dgc = ss->invucgc;
		else
			dgc = get_gc(ss, INVERT_COLOR(color));
	} else
		dgc = get_gc(ss, color);

	/* Draw the text */
	if (ss->extended_3270font)
		XDrawImageString16(display, ss->window, dgc, x, y, rt_buf16,
		    len);
	else
		XDrawImageString(display, ss->window, dgc, x, y,
		    (char *)rt_buf8, len);
	if (ss->overstrike &&
	    ((attrs->bits.gr & GR_INTENSIFY) ||
	     ((appres.mono || (!appres.m3279 && appres.highlight_bold)) &&
	      ((color & BASE_MASK) == FA_INT_HIGH_SEL)))) {
		if (ss->extended_3270font)
			XDrawString16(display, ss->window, dgc, x+1, y,
			    rt_buf16, len);
		else
			XDrawString(display, ss->window, dgc, x+1, y,
			    (char *)rt_buf8, len);
	}
	if (attrs->bits.gr & GR_UNDERLINE)
		XDrawLine(display,
		    ss->window,
		    dgc,
		    x,
		    y - ss->ascent + ss->char_height - 1,
		    x + ss->char_width * len,
		    y - ss->ascent + ss->char_height - 1);
}


#if defined(X3270_ANSI) /*[*/
Boolean
screen_obscured(void)
{
	return ss->obscured;
}

/*
 * Scroll the screen image one row.
 *
 * This is the optimized path from ctlr_scroll(); it assumes that
 * screen_buf[] and ea_buf[] have already been modified and that the screen
 * can be brought into sync by hammering ss->image and the bitmap.
 */
void
screen_scroll(void)
{
	Boolean was_on;

	if (!ss->exposed_yet)
		return;

	was_on = cursor_off();
	(void) memmove(&ss->image[0], &ss->image[COLS],
	                   (ROWS - 1) * COLS * sizeof(union sp));
	(void) memmove(&temp_image[0], &temp_image[COLS],
	                   (ROWS - 1) * COLS * sizeof(union sp));
	(void) memset((char *)&ss->image[(ROWS - 1) * COLS], 0,
	              COLS * sizeof(union sp));
	(void) memset((char *)&temp_image[(ROWS - 1) * COLS], 0,
	              COLS * sizeof(union sp));
	XCopyArea(display, ss->window, ss->window, get_gc(ss, 0),
	    ssCOL_TO_X(0),
	    ssROW_TO_Y(1) - ss->ascent,
	    ss->char_width * COLS,
	    ss->char_height * (ROWS - 1),
	    ssCOL_TO_X(0),
	    ssROW_TO_Y(0) - ss->ascent);
	ss->copied = True;
	XFillRectangle(display, ss->window, get_gc(ss, INVERT_COLOR(0)),
	    ssCOL_TO_X(0),
	    ssROW_TO_Y(ROWS - 1) - ss->ascent,
	    (ss->char_width * COLS) + 1,
	    ss->char_height);
	if (was_on)
		cursor_on();
}
#endif /*]*/


/*
 * Toggle mono-/dual-case mode.
 */
void
toggle_monocase(struct toggle *t unused, enum toggle_type tt unused)
{
	(void) memset((char *) ss->image, 0,
		      (ROWS*COLS) * sizeof(union sp));
	ctlr_changed(0, ROWS*COLS);
}

/*
 * Toggle screen flip
 */
void
screen_flip(void)
{
	flipped = !flipped;

	action_internal(PA_Expose_action, IA_REDRAW, CN, CN);
}

/*
 * "Draw" screen_buf into a buffer
 */
static void
draw_fields(union sp *buffer, int first, int last)
{
	int	baddr = 0;
	unsigned char	*fap;
	unsigned char	fa;
	struct ea       *field_ea;
	struct ea	*char_ea = ea_buf;
	unsigned char	*sbp = screen_buf;
	int	field_color;
	int	zero;
	Boolean	any_blink = False;

	/* If there is any blinking text, override the suggested boundaries. */
	if (text_blinkers_exist) {
		first = -1;
		last = -1;
	}

	/* Adjust pointers to start of region. */
	if (first > 0) {
		baddr += first;
		char_ea += first;
		sbp += first;
		buffer += first;
	}
	fap = get_field_attribute(baddr);
	fa = *fap;
	field_ea = fa2ea(fap);

	/* Adjust end of region. */
	if (last == -1 || last >= ROWS*COLS)
		last = 0;

	zero = FA_IS_ZERO(fa);
	if (field_ea->fg && (!appres.modified_sel || !FA_IS_MODIFIED(fa)))
		field_color = field_ea->fg & COLOR_MASK;
	else
		field_color = fa_color(fa);

	do {
		unsigned char	c = *sbp;
		union sp	b;

		b.word = 0;	/* clear out all fields */

		if (IS_FA(c)) {
			fa = c;
			field_ea = char_ea;
			zero = FA_IS_ZERO(fa);
			if (field_ea->fg && (!appres.modified_sel || !FA_IS_MODIFIED(fa)))
				field_color = field_ea->fg & COLOR_MASK;
			else
				field_color = fa_color(fa);
			if (ss->debugging_font) {
				b.bits.cg = c;
				if (appres.m3279 || !zero)
					b.bits.fg = field_color;
			} /* otherwise, CG_null in color 0 */
		} else {
			unsigned short gr;
			int e_color;

			/* Find the right graphic rendition. */
			gr = char_ea->gr;
			if (!gr)
				gr = field_ea->gr;
			if (gr & GR_BLINK)
				any_blink = True;
			if (appres.highlight_bold && FA_IS_HIGH(fa))
				gr |= GR_INTENSIFY;

			/* Find the right color. */
			if (char_ea->fg)
				e_color = char_ea->fg & COLOR_MASK;
			else if (appres.mono && (gr & GR_INTENSIFY))
				e_color = fa_color(FA_INT_HIGH_SEL);
			else
				e_color = field_color;
			if (gr & GR_REVERSE)
				e_color = INVERT_COLOR(e_color);
			if (!appres.mono)
				b.bits.fg = e_color;

			/* Find the right character and character set. */
			if (zero) {
				if (ss->debugging_font)
					b.bits.cg = CG_space;
			} else if ((c != CG_null && c != CG_space) ||
			           (gr & (GR_REVERSE | GR_UNDERLINE)) ||
				   ss->debugging_font) {

				b.bits.fg = e_color;

				/*
				 * Replace blanked-out blinking text with
				 * spaces.
				 */
				if (!text_blinking_on && (gr & GR_BLINK))
					b.bits.cg = CG_space;
				else {
					b.bits.cg = c;
					if (char_ea->cs)
						b.bits.cs = char_ea->cs;
					else
						b.bits.cs = field_ea->cs;
					if (b.bits.cs & CS_GE)
						b.bits.cs = 1;
					else
						b.bits.cs &= CS_MASK;
				}

			} /* otherwise, CG_null */

			b.bits.gr = gr & (GR_UNDERLINE | GR_INTENSIFY);
		}
		if (SELECTED(baddr))
			b.bits.sel = 1;
		if (!flipped)
			*buffer++ = b;
		else
			*(buffer + fl_baddr(baddr)) = b;
		char_ea++;
		sbp++;
		INC_BA(baddr);
	} while (baddr != last);

	/* Cancel blink timeouts if none were seen this pass. */
	if (!any_blink)
		text_blinkers_exist = False;
}


/*
 * Resync the X display with the contents of 'buffer'
 */
static void
resync_display(union sp *buffer, int first, int last)
{
	register int	i, j;
	int		b = 0;
	int		i0 = -1;
	Boolean		ccheck;
	int		fca = fl_baddr(cursor_addr);
	int		first_row, last_row;
#	define SPREAD	10

	if (first < 0) {
		first_row = 0;
		last_row = ROWS;
	} else {
		first_row = first / COLS;
		b = first_row * COLS;
		last_row = (last + (COLS-1)) / COLS;
	}

	for (i = first_row; i < last_row; b += COLS, i++) {
		int d0 = -1;
		int s0 = -1;

		/* Has the line changed? */
		if (!memcmp((char *) &ss->image[b], (char *) &buffer[b],
		    COLS*sizeof(union sp))) {
			if (i0 >= 0) {
				render_blanks(i0 * COLS, i - i0, buffer);
				i0 = -1;
			}
			continue;
		}

		/* Is the new value empty? */
		if (!ss->debugging_font &&
		    !(fca >= b && fca < (b+COLS)) &&
		    empty_space(&buffer[b], COLS)) {
			if (i0 < 0)
				i0 = i;
			continue;
		}

		/* Yes, it changed, and it isn't blank.
		   Dump any pending blank lines. */
		if (i0 >= 0) {
			render_blanks(i0 * COLS, i - i0, buffer);
			i0 = -1;
		}

		/* New text.  Scan it. */
		ccheck = cursor_displayed &&
			 fca >= b &&
			 fca < (b+COLS);
		for (j = 0; j < COLS; j++) {
			if (ccheck && b+j == fca) {
				/* Don't repaint over the cursor. */

				/* Dump any pending "different" characters. */
				if (d0 >= 0)
					resync_text(b+d0, j-d0, buffer);

				/* Start over. */
				d0 = -1;
				s0 = -1;
				continue;
			}
			if (ss->image[b+j].word == buffer[b+j].word) {

				/* Character is the same. */

				if (d0 >= 0) {
					/* Something is pending... */
					if (s0 < 0) {
						/* Start of "same" area */
						s0 = j;
					} else {
						/* nth matching character */
						if (j - s0 > SPREAD) {
							/* too many */
							resync_text(b+d0,
							    s0-d0, buffer);
							d0 = -1;
							s0 = -1;
						}
					}
				}
			} else {

				/* Character is different. */

				/* Forget intermediate matches. */
				s0 = -1;

				if (d0 < 0)
					/* Mark the start. */
					d0 = j;
			}
		}

		/* Dump any pending "different" characters. */
		if (d0 >= 0)
			resync_text(b+d0, COLS-d0, buffer);
	}
	if (i0 >= 0)
		render_blanks(i0 * COLS, last_row - i0, buffer);
}


/*
 * Support code for cursor redraw.
 */

/*
 * Calculate a flipped baddr.
 */
static int
fl_baddr(int baddr)
{
	if (!flipped)
		return baddr;
	return ((baddr / COLS) * COLS) + (COLS - (baddr % COLS) - 1);
}

/*
 * Return the proper foreground color for a character position.
 */

static int
char_color(int baddr)
{
	unsigned char *fa;
	int color;

	fa = get_field_attribute(baddr);

	/*
	 * Find the color of the character or the field.
	 */
	if (ea_buf[baddr].fg)
		color = ea_buf[baddr].fg & COLOR_MASK;
	else if (fa2ea(fa)->fg && (!appres.modified_sel || !FA_IS_MODIFIED(*fa)))
		color = fa2ea(fa)->fg & COLOR_MASK;
	else
		color = fa_color(*fa);

	/*
	 * Now apply reverse video.
	 *
	 * One bit of strangeness:
	 *  If the buffer is a field attribute and we aren't using the
	 *  debug font, it's displayed as a blank; don't invert.
	 */
	if (!((IS_FA(screen_buf[baddr]) && !ss->debugging_font)) &&
	    ((ea_buf[baddr].gr & GR_REVERSE) || (fa2ea(fa)->gr & GR_REVERSE)))
		color = INVERT_COLOR(color);

	/*
	 * In monochrome, apply selection status as well.
	 */
	if (appres.mono && SELECTED(baddr))
		color = INVERT_COLOR(color);

	return color;
}


/*
 * Select a GC for drawing a hollow or underscore cursor.
 */
static GC
cursor_gc(int baddr)
{
	/*
	 * If they say use a particular color, use it.
	 */
	if (appres.use_cursor_color)
		return ss->ucgc;
	else
		return get_gc(ss, char_color(baddr));
}

/*
 * Redraw one character.
 * If 'invert' is true, invert the foreground and background colors.
 */
static void
redraw_char(int baddr, Boolean invert)
{
	union sp buffer;
	unsigned char *fa;
	int gr;
	int blank_it = 0;

	if (!invert) {
		int flb = fl_baddr(baddr);

		/* Simply put back what belongs there. */
		render_text(&ss->image[flb], flb, 1, False, &ss->image[flb]);
		return;
	}

	/*
	 * Fabricate the right thing.
	 * ss->image isn't going to help, because it may contain shortcuts
	 *  for faster display, so we have to construct a buffer to use.
	 */
	buffer.word = 0L;
	buffer.bits.cg = screen_buf[baddr];
	buffer.bits.cs = ea_buf[baddr].cs;
	if (buffer.bits.cs & CS_GE)
		buffer.bits.cs = 1;
	else
		buffer.bits.cs &= CS_MASK;
	fa = get_field_attribute(baddr);
	gr = ea_buf[baddr].gr;
	if (!gr)
		gr = fa2ea(fa)->gr;
	if (IS_FA(screen_buf[baddr])) {
		if (!ss->debugging_font)
			blank_it = 1;
	} else if (FA_IS_ZERO(*fa)) {
		blank_it = 1;
	} else if (text_blinkers_exist && !text_blinking_on) {
		if (gr & GR_BLINK)
			blank_it = 1;
	}
	if (blank_it) {
		buffer.bits.cg = CG_space;
		buffer.bits.cs = 0;
	}
	buffer.bits.fg = char_color(baddr);
	buffer.bits.gr |= (gr & GR_INTENSIFY);
	render_text(&buffer, fl_baddr(baddr), 1, True, &buffer);
}

/*
 * Draw a hollow cursor.
 */
static void
hollow_cursor(int baddr)
{
	XDrawRectangle(display,
	    ss->window,
	    cursor_gc(baddr),
	    ssCOL_TO_X(BA_TO_COL(fl_baddr(baddr))),
	    ssROW_TO_Y(BA_TO_ROW(baddr)) - ss->ascent +
		(appres.mono ? 1 : 0),
	    ss->char_width - 1,
	    ss->char_height - (appres.mono ? 2 : 1));
}

/*
 * Draw an underscore cursor.
 */
static void
underscore_cursor(int baddr)
{
	XDrawRectangle(display,
	    ss->window,
	    cursor_gc(baddr),
	    ssCOL_TO_X(BA_TO_COL(fl_baddr(baddr))),
	    ssROW_TO_Y(BA_TO_ROW(baddr)) - ss->ascent +
		ss->char_height - 2,
	    ss->char_width - 1,
	    1);
}

/*
 * Invert a square over a character.
 */
static void
small_inv_cursor(int baddr)
{
	XFillRectangle(display,
	    ss->window,
	    ss->mcgc,
	    ssCOL_TO_X(BA_TO_COL(fl_baddr(baddr))),
	    ssROW_TO_Y(BA_TO_ROW(baddr)) - ss->ascent + 1,
	    ss->char_width,
	    (ss->char_height > 2) ? (ss->char_height - 2) : 1);
}

/*
 * Draw or remove the cursor.
 */
static void
put_cursor(int baddr, Boolean on)
{
	/*
	 * If the cursor is being turned off, simply redraw the text under it.
	 */
	if (!on) {
		redraw_char(baddr, False);
		return;
	}

	/*
	 * If underscore cursor, redraw the character and draw the underscore.
	 */
	if (toggled(ALT_CURSOR)) {
		redraw_char(baddr, False);
		underscore_cursor(baddr);
		return;
	}

	/*
	 * On, and not an underscore.
	 *
	 * If out of focus, either draw an empty box in its place (if block
	 * cursor) or redraw the underscore (if underscore).
	 */
	if (!in_focus) {
		hollow_cursor(baddr);
		return;
	}

	/*
	 * If monochrome, invert a small square over the characters.
	 */
	if (appres.mono) {
		small_inv_cursor(baddr);
		return;
	}

	/*
	 * Color: redraw the character in reverse video.
	 */
	redraw_char(baddr, True);
}


/* Allocate a named color. */
static Boolean
alloc_color(char *name, enum fallback_color fb_color, Pixel *pixel)
{
	XColor cell, db;
	Screen *s;

	s = XtScreen(toplevel);

	if (XAllocNamedColor(display, XDefaultColormapOfScreen(s), name,
	    &cell, &db) != 0) {
		*pixel = db.pixel;
		return True;
	}
	switch (fb_color) {
	    case FB_WHITE:
		*pixel = XWhitePixelOfScreen(s);
		break;
	    case FB_BLACK:
		*pixel = XBlackPixelOfScreen(s);
		break;
	}
	return False;
}

/* Spell out a fallback color. */
static const char *
fb_name(enum fallback_color fb_color)
{
	switch (fb_color) {
	    case FB_WHITE:
		return "white";
	    case FB_BLACK:
		return "black";
	}
	return "chartreuse";	/* to keep Gcc -Wall happy */
}

/* Allocate color pixels. */
static void
allocate_pixels(void)
{

	if (appres.mono)
		return;

	/* Allocate constant elements. */
	if (!alloc_color(appres.colorbg_name, FB_BLACK, &colorbg_pixel))
		xs_warning("Cannot allocate colormap \"%s\" for screen background, using \"black\"",
		    appres.colorbg_name);
	if (!alloc_color(appres.selbg_name, FB_BLACK, &selbg_pixel))
		xs_warning("Cannot allocate colormap \"%s\" for select background, using \"black\"",
		    appres.selbg_name);
	if (!alloc_color(appres.keypadbg_name, FB_WHITE, &keypadbg_pixel))
		xs_warning("Cannot allocate colormap \"%s\" for keypad background, using \"white\"",
		    appres.keypadbg_name);
	if (appres.use_cursor_color) {
		if (!alloc_color(appres.cursor_color_name, FB_WHITE, &cursor_pixel))
			xs_warning("Cannot allocate colormap \"%s\" for cursor color, using \"white\"",
			    appres.cursor_color_name);
	}

	/* Allocate pseudocolors. */
	if (!appres.m3279) {
		if (!alloc_color(appres.normal_name, FB_WHITE, &normal_pixel))
			xs_warning("Cannot allocate colormap \"%s\" for text, using \"white\"",
			    appres.normal_name);
		if (!alloc_color(appres.select_name, FB_WHITE, &select_pixel))
			xs_warning("Cannot allocate colormap \"%s\" for selectable text, using \"white\"",
			    appres.select_name);
		if (!alloc_color(appres.bold_name, FB_WHITE, &bold_pixel))
			xs_warning("Cannot allocate colormap \"%s\" for bold text, using \"white\"",
			    appres.bold_name);
		return;
	}
}

#if defined(X3270_MENUS) /*[*/
/* Deallocate pixels. */
static void
destroy_pixels(void)
{
	int i;

	/*
	 * It would make sense to deallocate many of the pixels here, but
	 * the only available call (XFreeColors) would deallocate cells
	 * that may be in use by other Xt widgets.  Occh.
	 */

	for (i = 0; i < 16; i++)
		cpx_done[i] = False;
}
#endif /*]*/

/*
 * Create graphics contexts.
 */
static void
make_gcs(struct sstate *s)
{
	XGCValues xgcv;

	if (appres.m3279) {
		int i;

		for (i = 0; i < NGCS; i++) {
			if (s->gc[i] != (GC)None) {
				XtReleaseGC(toplevel, s->gc[i]);
				s->gc[i] = (GC)None;
			}
			if (s->gc[i + NGCS] != (GC)None) {
				XtReleaseGC(toplevel, s->gc[i + NGCS]);
				s->gc[i + NGCS] = (GC)None;
			}
			if (s->selgc[i] != (GC)None) {
				XtReleaseGC(toplevel, s->selgc[i]);
				s->selgc[i] = (GC)None;
			}
		}
	} else {
		if (!appres.mono) {
			make_gc_set(s, FA_INT_NORM_NSEL, normal_pixel,
			    colorbg_pixel);
			make_gc_set(s, FA_INT_NORM_SEL,  select_pixel,
			    colorbg_pixel);
			make_gc_set(s, FA_INT_HIGH_SEL,  bold_pixel,
			    colorbg_pixel);
		} else {
			make_gc_set(s, FA_INT_NORM_NSEL, appres.foreground,
			    appres.background);
			make_gc_set(s, FA_INT_NORM_SEL,  appres.foreground,
			    appres.background);
			make_gc_set(s, FA_INT_HIGH_SEL,  appres.foreground,
			    appres.background);
		}
	}

	/* Create monochrome block cursor GC. */
	if (appres.mono && s->mcgc == (GC)None) {
		if (screen_depth > 1)
			xgcv.function = GXinvert;
		else
			xgcv.function = GXxor;
		xgcv.foreground = 1L;
		s->mcgc = XtGetGC(toplevel,
		    GCForeground|GCFunction,
		    &xgcv);
	}

	/* Create explicit cursor color cursor GCs. */
	if (appres.use_cursor_color) {
		if (s->ucgc != (GC)None) {
			XtReleaseGC(toplevel, s->ucgc);
			s->ucgc = (GC)None;
		}
		xgcv.foreground = cursor_pixel;
		s->ucgc = XtGetGC(toplevel, GCForeground, &xgcv);

		if (s->invucgc != (GC)None) {
			XtReleaseGC(toplevel, s->invucgc);
			s->invucgc = (GC)None;
		}
		xgcv.foreground = colorbg_pixel;
		xgcv.background = cursor_pixel;
		xgcv.font = s->fid;
		s->invucgc = XtGetGC(toplevel,
		    GCForeground|GCBackground|GCFont, &xgcv);
	}

	/* Set the flag for overstriking bold. */
	s->overstrike = (s->char_width > 1);
}

/* Set up a default color scheme. */
static void
default_color_scheme(void)
{
	static int default_attrib_colors[4] = {
	    GC_NONDEFAULT | COLOR_GREEN,/* default */
	    GC_NONDEFAULT | COLOR_RED,	/* intensified */
	    GC_NONDEFAULT | COLOR_BLUE,	/* protected */
	    GC_NONDEFAULT | COLOR_WHITE	/* protected, intensified */
	};
	int i;

	ibm_fb = FB_WHITE;
	for (i = 0; i < 16; i++) {
		if (color_name[i] != (char *)NULL)
			XtFree(color_name[i]);
		color_name[i] = XtNewString("white");
	}
	for (i = 0; i < 4; i++)
		field_colors[i] = default_attrib_colors[i];
}

/* Transfer the colorScheme resource into arrays. */
static Boolean
xfer_color_scheme(char *cs, Boolean do_popup)
{
	int i;
	char *scheme_name = CN;
	char *s0 = CN, *scheme = CN;
	char *tk;

	char *tmp_color_name[16];
	enum fallback_color tmp_ibm_fb = FB_WHITE;
	char *tmp_colorbg_name = CN;
	char *tmp_selbg_name = CN;
	int tmp_field_colors[4];

	if (cs == CN)
		goto failure;
	scheme_name = xs_buffer("%s.%s", ResColorScheme, cs);
	s0 = get_resource(scheme_name);
	if (s0 == CN) {
		if (do_popup)
			popup_an_error("Can't find resource %s", scheme_name);
		else
			xs_warning("Can't find resource %s", scheme_name);
		goto failure;
	}
	scheme = s0 = XtNewString(s0);
	for (i = 0; (tk = strtok(scheme, " \t\n")) != CN; i++) {
		scheme = CN;
		if (i > 22) {
			xs_warning("Ignoring excess data in %s resource",
			    scheme_name);
			break;
		}
		switch (i) {
		    case  0: case  1: case  2: case  3:
		    case  4: case  5: case  6: case  7:
		    case  8: case  9: case 10: case 11:
		    case 12: case 13: case 14: case 15:	/* IBM color name */
			tmp_color_name[i] = tk;
			break;
		    case 16:	/* default for IBM colors */
			if (!strcmp(tk, "white"))
				tmp_ibm_fb = FB_WHITE;
			else if (!strcmp(tk, "black"))
				tmp_ibm_fb = FB_BLACK;
			else {
				if (do_popup)
					popup_an_error("Invalid default color");
				else
					xs_warning("Invalid default color");
				goto failure;
			}
			break;
		    case 17:	/* screen background */
			tmp_colorbg_name = tk;
			break;
		    case 18:	/* select background */
			tmp_selbg_name = tk;
			break;
		    case 19: case 20: case 21: case 22:	/* attribute colors */
			tmp_field_colors[i-19] = atoi(tk);
			if (tmp_field_colors[i-19] < 0 ||
			    tmp_field_colors[i-19] > 0x0f) {
				if (do_popup)
					popup_an_error("Invalid %s resource, ignoring",
					    scheme_name);
				else
					xs_warning("Invalid %s resource, ignoring",
					    scheme_name);
				goto failure;
			}
			tmp_field_colors[i-19] |= GC_NONDEFAULT;
		}
	}
	if (i < 23) {
		if (do_popup)
			popup_an_error("Insufficient data in %s resource",
			    scheme_name);
		else
			xs_warning("Insufficient data in %s resource",
			    scheme_name);
		goto failure;
	}

	/* Success: transfer to live variables. */
	for (i = 0; i < 16; i++) {
		if (color_name[i] != (char *)NULL)
			XtFree(color_name[i]);
		color_name[i] = XtNewString(tmp_color_name[i]);
	}
	ibm_fb = tmp_ibm_fb;
	appres.colorbg_name = XtNewString(tmp_colorbg_name);
	appres.selbg_name = XtNewString(tmp_selbg_name);
	for (i = 0; i < 4; i++)
		field_colors[i] = tmp_field_colors[i];

	/* Clean up and exit. */
	if (scheme_name != CN)
		XtFree(scheme_name);
	if (s0 != CN)
		XtFree(s0);
	return True;

    failure:
	if (scheme_name != CN)
		XtFree(scheme_name);
	if (s0 != CN)
		XtFree(s0);
	return False;
}

/* Look up a GC, allocating it if necessary. */
static GC
get_gc(struct sstate *s, int color)
{
	int pixel_index;
	XGCValues xgcv;
	GC r;

	if (color & GC_NONDEFAULT)
		color &= ~GC_NONDEFAULT;
	else
		color = (color & INVERT_MASK) | DEFAULT_PIXEL;

	if ((r = s->gc[color]) != (GC)None)
		return r;

	/* Allocate the pixel. */
	pixel_index = PIXEL_INDEX(color);
	if (!cpx_done[pixel_index]) {
		if (!alloc_color(color_name[pixel_index], ibm_fb,
				 &cpx[pixel_index])) {
			static char nbuf[16];

			(void) sprintf(nbuf, "%d", pixel_index);
			xs_warning("Cannot allocate colormap \"%s\" for 3279 color %s (%s), using \"%s\"",
			    color_name[pixel_index], nbuf,
			    see_color((unsigned char)(pixel_index + 0xf0)),
			    fb_name(ibm_fb));
		}
		cpx_done[pixel_index] = True;
	}

	/* Allocate the GC. */
	xgcv.font = s->fid;
	if (!(color & INVERT_MASK)) {
		xgcv.foreground = cpx[pixel_index];
		xgcv.background = colorbg_pixel;
	} else {
		xgcv.foreground = colorbg_pixel;
		xgcv.background = cpx[pixel_index];
	}
	if (s == &nss && pixel_index == DEFAULT_PIXEL) {
		xgcv.graphics_exposures = True;
		r = XtGetGC(toplevel,
		    GCForeground|GCBackground|GCFont|GCGraphicsExposures,
		    &xgcv);
	} else
		r = XtGetGC(toplevel,
		    GCForeground|GCBackground|GCFont,
		    &xgcv);
	return s->gc[color] = r;
}

/* Look up a selection GC, allocating it if necessary. */
static GC
get_selgc(struct sstate *s, int color)
{
	XGCValues xgcv;
	GC r;

	if (color & GC_NONDEFAULT)
		color = PIXEL_INDEX(color);
	else
		color = DEFAULT_PIXEL;

	if ((r = s->selgc[color]) != (GC)None)
		return r;

	/* Allocate the pixel. */
	if (!cpx_done[color]) {
		if (!alloc_color(color_name[color], FB_WHITE, &cpx[color])) {
			static char nbuf[16];

			(void) sprintf(nbuf, "%d", color);
			xs_warning("Cannot allocate colormap \"%s\" for 3279 color %s (%s), using \"white\"",
			    color_name[color], nbuf,
			    see_color((unsigned char)(color + 0xf0)));
		}
		cpx_done[color] = True;
	}

	/* Allocate the GC. */
	xgcv.font = s->fid;
	xgcv.foreground = cpx[color];
	xgcv.background = selbg_pixel;
	return s->selgc[color] =
	    XtGetGC(toplevel, GCForeground|GCBackground|GCFont, &xgcv);
}

/* External entry points for GC allocation. */

GC
screen_gc(int color)
{
	return get_gc(ss, color | GC_NONDEFAULT);
}

GC
screen_invgc(int color)
{
	return get_gc(ss, INVERT_COLOR(color | GC_NONDEFAULT));
}
 
/*
 * Preallocate a set of graphics contexts for a given color.
 *
 * This logic is used only in pseudo-color mode.  In full color mode,
 * GCs are allocated dynamically by get_gc().
 */
static void
make_gc_set(struct sstate *s, int i, Pixel fg, Pixel bg)
{
	XGCValues xgcv;

	if (s->gc[i] != (GC)None)
		XtReleaseGC(toplevel, s->gc[i]);
	xgcv.foreground = fg;
	xgcv.background = bg;
	xgcv.graphics_exposures = True;
	xgcv.font = s->fid;
	if (s == &nss && !i)
		s->gc[i] = XtGetGC(toplevel,
		    GCForeground|GCBackground|GCFont|GCGraphicsExposures,
		    &xgcv);
	else
		s->gc[i] = XtGetGC(toplevel,
		    GCForeground|GCBackground|GCFont, &xgcv);
	if (s->gc[NGCS + i] != (GC)None)
		XtReleaseGC(toplevel, s->gc[NGCS + i]);
	xgcv.foreground = bg;
	xgcv.background = fg;
	s->gc[NGCS + i] = XtGetGC(toplevel, GCForeground|GCBackground|GCFont,
	    &xgcv);
	if (!appres.mono) {
		if (s->selgc[i] != (GC)None)
			XtReleaseGC(toplevel, s->selgc[i]);
		xgcv.foreground = fg;
		xgcv.background = selbg_pixel;
		s->selgc[i] = XtGetGC(toplevel,
		    GCForeground|GCBackground|GCFont, &xgcv);
	}
}


/*
 * Convert an attribute to a color index.
 */
static int
fa_color(unsigned char fa)
{
	if (appres.m3279) {
		/*
		 * Color indices are the low-order 4 bits of a 3279 color
		 * identifier (0 through 15)
		 */
		if (appres.modified_sel && FA_IS_MODIFIED(fa))
			return GC_NONDEFAULT |
				(appres.modified_sel_color & 0xf);
		else if (appres.visual_select && FA_IS_SEL(fa))
			return GC_NONDEFAULT |
				(appres.visual_select_color & 0xf);
		else
			return field_colors[(fa >> 1) & 0x03];
	} else {
		/*
		 * Color indices are the intensity bits (0 through 2)
		 */
		if (FA_IS_ZERO(fa) ||
		    (appres.modified_sel && FA_IS_MODIFIED(fa)))
			return GC_NONDEFAULT | FA_INT_NORM_SEL;
		else
			return GC_NONDEFAULT | (fa & FA_INTENSITY);
	}
}



/*
 * Event handlers for toplevel FocusIn, FocusOut, KeymapNotify and
 * PropertyChanged events.
 */

static Boolean toplevel_focused = False;
static Boolean keypad_entered = False;

void
PA_Focus_action(Widget w unused, XEvent *event, String *params unused,
    Cardinal *num_params unused)
{
	XFocusChangeEvent *fe = (XFocusChangeEvent *)event;

#if defined(INTERNAL_ACTION_DEBUG) /*[*/
	action_debug(PA_Focus_action, event, params, num_params);
#endif /*]*/
	switch (fe->type) {
	    case FocusIn:
		if (fe->detail != NotifyPointer) {
			toplevel_focused = True;
			screen_focus(True);
		}
		break;
	    case FocusOut:
		toplevel_focused = False;
		if (!toplevel_focused && !keypad_entered)
			screen_focus(False);
		break;
	}
}

void
PA_EnterLeave_action(Widget w unused, XEvent *event unused,
    String *params unused, Cardinal *num_params unused)
{
	XCrossingEvent *ce = (XCrossingEvent *)event;

#if defined(INTERNAL_ACTION_DEBUG) /*[*/
	action_debug(PA_EnterLeave_action, event, params, num_params);
#endif /*]*/
	switch (ce->type) {
	    case EnterNotify:
		keypad_entered = True;
		screen_focus(True);
		break;
	    case LeaveNotify:
		keypad_entered = False;
		if (!toplevel_focused && !keypad_entered)
			screen_focus(False);
		break;
	}
}

void
PA_KeymapNotify_action(Widget w unused, XEvent *event, String *params unused,
    Cardinal *num_params unused)
{
	XKeymapEvent *k = (XKeymapEvent *)event;

#if defined(INTERNAL_ACTION_DEBUG) /*[*/
	action_debug(PA_KeymapNotify_action, event, params, num_params);
#endif /*]*/
	shift_event(state_from_keymap(k->key_vector));
}

static void
query_window_state(void)
{
	Atom actual_type;
	int actual_format;
	unsigned long nitems;
	unsigned long leftover;
	unsigned char *data = NULL;

	/* If using an active icon, the expose events are more reliable. */
	if (appres.active_icon)
		return;

	if (XGetWindowProperty(display, XtWindow(toplevel), a_state, 0L,
	    (long)BUFSIZ, False, a_state, &actual_type, &actual_format,
	    &nitems, &leftover, &data) != Success)
		return;
	if (actual_type == a_state && actual_format == 32) {
		if (*(unsigned long *)data == IconicState)
			iconic = True;
		else {
			iconic = False;
			invert_icon(False);
			keypad_first_up();
		}
	}
}

void
PA_StateChanged_action(Widget w unused, XEvent *event, String *params,
    Cardinal *num_params)
{
#if defined(INTERNAL_ACTION_DEBUG) /*[*/
	action_debug(PA_StateChanged_action, event, params, num_params);
#endif /*]*/
	query_window_state();
}

/*
 * Handle Shift events (KeyPress and KeyRelease events, or KeymapNotify events
 * that occur when the mouse enters the window).
 */

void
shift_event(int event_state)
{
	static int old_state;
	Boolean shifted_now =
	    (event_state & (ShiftKeyDown | MetaKeyDown | AltKeyDown)) != 0;

	if (event_state != old_state) {
		old_state = event_state;
		status_shift_mode(event_state);
		if (shifted != shifted_now) {
			shifted = shifted_now;
			keypad_shift();
		}
	}
}

/*
 * Handle the mouse entering and leaving the window.
 */
static void
screen_focus(Boolean in)
{
	/*
	 * Cancel any pending cursor blink.  If we just came into focus and
	 * have a blinking cursor, we will start a fresh blink cycle below, so
	 * the filled-in cursor is visible for a full turn.
	 */
	cancel_blink();

	/*
	 * If the cursor is disabled, simply change internal state.
	 */
	if (!CONNECTED) {
		in_focus = in;
		return;
	}

	/*
	 * Change the appearance of the cursor.  Make it hollow out or fill in
	 * instantly, even if it was blinked off originally.
	 */
	(void) cursor_off();
	in_focus = in;
	cursor_on();

	/*
	 * If we just came into focus and we're supposed to have a blinking
	 * cursor, schedule a blink.
	 */
	if (in_focus && toggled(CURSOR_BLINK))
		schedule_cursor_blink();
}

/*
 * Change fonts.
 */
void
SetFont_action(Widget w unused, XEvent *event, String *params,
    Cardinal *num_params)
{
	action_debug(SetFont_action, event, params, num_params);
	if (check_usage(SetFont_action, *num_params, 1, 1) < 0)
		return;
	screen_newfont(params[0], True);
}

void
screen_newfont(char *fontname, Boolean do_popup)
{
	const char *emsg;
	char *old_font;

	/* Can't change fonts in APL mode. */
	if (appres.apl_mode)
		return;

	if (efontname && !strcmp(fontname, efontname))
		return;

	/* Save the old font before trying the new one. */
	old_font = XtNewString(efontname);

	/* Try the new one. */
	if ((emsg = load_fixed_font(fontname))) {
		if (do_popup)
			popup_an_error("Font \"%s\"\n%s", fontname, emsg);
		XtFree(old_font);
		return;
	}

	/* Store the old name away, in case we have to go back to it. */
	if (redo_old_font != CN)
		XtFree(redo_old_font);
	redo_old_font = old_font;
	screen_redo = REDO_FONT;

	screen_reinit(FONT_CHANGE);
	efont_changed = True;
}

/*
 * Load and query a font.
 * Returns NULL (okay) or an error message.
 */
static const char *
load_fixed_font(const char *name)
{
	XFontStruct *f;
	Font ff;
	char **matches;
	int count;

	if (*name == '!')	/* backwards compatibility */
		name++;

	matches = XListFontsWithInfo(display, name, 1, &count, &f);
	if (matches == (char **)NULL)
		return "does not exist";
	ff = XLoadFont(display, matches[0]);
	set_font_globals(f, name, ff);
	XFreeFontInfo(matches, f, count);
	return CN;
}

/*
 * Set globals based on font name and info
 */
static void
set_font_globals(XFontStruct *f, const char *ef, Font ff)
{
	unsigned long svalue, svalue2;

	if (efontname)
		XtFree(efontname);
	efontname = XtNewString(ef);
	nss.char_width  = fCHAR_WIDTH(f);
	nss.char_height = fCHAR_HEIGHT(f);
	nss.fid = ff;
	nss.ascent = f->ascent;
	nss.descent = f->descent;
	if (XGetFontProperty(f, XA_FAMILY_NAME, &svalue))
		nss.standard_font = (Atom) svalue != a_3270;
	else if (!strncmp(efontname, "3270", 4))
		nss.standard_font = False;
	else
		nss.standard_font = True;
	if (nss.standard_font) {
		nss.extended_3270font = False;
		nss.latin1_font =
		    f->max_char_or_byte2 > 127 &&
		    XGetFontProperty(f, a_registry, &svalue) &&
		    (svalue == a_iso8859 || svalue == a_ISO8859) &&
		    XGetFontProperty(f, a_encoding, &svalue2) &&
		    svalue2 == a_1;
		nss.xfmap = nss.latin1_font ? cg2asc : cg2asc7;
		nss.debugging_font = False;
	} else {
#if defined(BROKEN_MACH32)
		nss.extended_3270font = False;
#else
		nss.extended_3270font = f->max_byte1 > 0 ||
			f->max_char_or_byte2 > 255;
#endif
		nss.latin1_font = False;
		nss.xfmap = (unsigned char *)NULL;
		nss.debugging_font = !strcmp(efontname, "3270d");
	}
}

/*
 * Font initialization.
 */
void
font_init(void)
{
	char *s, *label, *font;
	struct font_list *f;
	const char *ef, *emsg;

	/* Parse the fontMenuList resource. */
	if (!appres.font_list)
		xs_error("No %s resource", ResFontList);
	s = XtNewString(appres.font_list);
	while (split_dresource(&s, &label, &font) == 1) {
		f = (struct font_list *)XtMalloc(sizeof(*f));
		f->label = label;
		f->font = font;
		f->next = (struct font_list *)NULL;
		if (font_list)
			font_last->next = f;
		else
			font_list = f;
		font_last = f;
		font_count++;
	}
	if (!font_count)
		xs_error("Invalid %s resource", ResFontList);

	/* Now figure out which emulator font to load and load it. */
	if (appres.apl_mode) {
		/*
		 * APL mode overrides any explicit font selection, but if there
		 * isn't an APL font defined, APL mode is ignored.
		 */
		if ((appres.efontname = appres.afontname) == NULL) {
			xs_warning("No %s resource, ignoring APL mode",
			    ResAplFont);
			appres.apl_mode = False;
		}
	}

	/*
	 * If there's no explicit emulator font, take the first one off the
	 * menu list.
	 */
	if (!appres.efontname)
		appres.efontname = font_list->font;

	/*
	 * Try the user's selection, then the first off the menu list, then
	 * "fixed", then give up altogether.
	 */
	ef = appres.efontname;
	if ((emsg = load_fixed_font(ef)) != CN) {
		xs_warning("%s \"%s\" %s", ResEmulatorFont, ef, emsg);
		if (appres.apl_mode) {
			XtWarning("Ignoring APL mode");
			appres.apl_mode = False;
		}
		if (strcmp(ef, font_list->font)) {
			ef = font_list->font;
			emsg = load_fixed_font(ef);
			if (emsg != CN)
				xs_warning("%s \"%s\" %s", ResEmulatorFont, ef,
				    emsg);
		}
		if (emsg != CN) {
			ef = "fixed";
			if ((emsg = load_fixed_font(ef)) != CN)
				xs_error("%s \"%s\" %s", ResEmulatorFont, ef,
				    emsg);
		}
		xs_warning("Using font \"%s\"", ef);
	}
}

#if defined(X3270_MENUS) /*[*/
/*
 * Change models.
 */
void
screen_change_model(int mn, int ovc, int ovr)
{
	if (CONNECTED ||
	    (model_num == mn && ovc == ov_cols && ovr == ov_rows))
		return;

	redo_old_model = model_num;
	redo_old_ov_cols = ov_cols;
	redo_old_ov_rows = ov_rows;
	screen_redo = REDO_MODEL;

	model_changed = True;
	if (ov_cols != ovc || ov_rows != ovr)
		oversize_changed = True;
	set_rows_cols(mn, ovc, ovr);
	st_changed(ST_REMODEL, True);
	screen_reinit(MODEL_CHANGE);
}

/*
 * Change emulation modes.
 */
void
screen_extended(Boolean extended unused)
{
	set_rows_cols(model_num, ov_cols, ov_rows);
	model_changed = True;
}

void
screen_m3279(Boolean m3279 unused)
{
	destroy_pixels();
	screen_reinit(COLOR_CHANGE);
	set_rows_cols(model_num, ov_cols, ov_rows);
	model_changed = True;
}

/*
 * Change color schemes.  Alas, this is destructive if it fails.
 */
void
screen_newscheme(char *s)
{
	Boolean xferred;

	if (!appres.m3279)
		return;

	destroy_pixels();
	xferred = xfer_color_scheme(s, True);
	if (xferred)
		appres.color_scheme = s;
	screen_reinit(COLOR_CHANGE);
	scheme_changed = True;
}

/*
 * Change character sets.
 */
void
screen_newcharset(char *csname)
{
	if (!charset_init(csname))
		popup_an_error("Cannot find charset \"%s\"", csname);
	else {
		screen_reinit(CHARSET_CHANGE);
		charset_changed = True;
		popup_an_info("The new character set will only be reflected\n"
			"in new data from the host");
	}
}
#endif /*]*/

/*
 * Visual or not-so-visual bell
 */
void
ring_bell(void)
{
	static XGCValues xgcv;
	static GC bgc;
	static int initted;
	struct timeval tv;

	/* Ring the real display's bell. */
	if (!appres.visual_bell)
		XBell(display, appres.bell_volume);

	/* If we're iconic, invert the icon and return. */
	if (!appres.active_icon) {
		query_window_state();
		if (iconic) {
			invert_icon(True);
			return;
		}
	}

	if (!appres.visual_bell || !ss->exposed_yet)
		return;

	/* Do a screen flash. */

	if (!initted) {
		xgcv.function = GXinvert;
		bgc = XtGetGC(toplevel, GCFunction, &xgcv);
		initted = 1;
	}
	screen_disp();
	XFillRectangle(display, ss->window, bgc,
	    0, 0, ss->screen_width, ss->screen_height);
	XSync(display, 0);
	tv.tv_sec = 0;
	tv.tv_usec = 125000;
	(void) select(0, NULL, NULL, NULL, &tv);
	XFillRectangle(display, ss->window, bgc,
	    0, 0, ss->screen_width, ss->screen_height);
	XSync(display, 0);
}

/*
 * Window deletion
 */
void
PA_WMProtocols_action(Widget w, XEvent *event, String *params,
    Cardinal *num_params)
{
	XClientMessageEvent *cme = (XClientMessageEvent *)event;

#if defined(INTERNAL_ACTION_DEBUG) /*[*/
	action_debug(PA_WMProtocols_action, event, params, num_params);
#endif /*]*/
	if ((Atom)cme->data.l[0] == a_delete_me) {
		if (w == toplevel)
			x3270_exit(0);
		else
			XtPopdown(w);
	} else if ((Atom)cme->data.l[0] == a_save_yourself && w == toplevel) {
		save_yourself();
	}
}


/* Initialize the icon. */
void
icon_init(void)
{
	icon = XCreateBitmapFromData(display, root_window,
	    (char *) x3270_bits, x3270_width, x3270_height);

	if (appres.active_icon) {
		Dimension iw, ih;

		aicon_font_init();
		aicon_size(&iw, &ih);
		icon_shell =  XtVaAppCreateShell(
		    "x3270icon",
		    "X3270",
		    overrideShellWidgetClass,
		    display,
		    XtNwidth, iw,
		    XtNheight, ih,
		    XtNmappedWhenManaged, False,
		    NULL);
		XtRealizeWidget(icon_shell);
		XtVaSetValues(toplevel,
		    XtNiconWindow, XtWindow(icon_shell),
		    NULL);
		if (appres.active_icon) {
			XtVaSetValues(icon_shell,
			    XtNbackground, appres.mono ? appres.background
						       : colorbg_pixel,
			    NULL);
		}
	} else {
		unsigned i;

		for (i = 0; i < sizeof(x3270_bits); i++)
			x3270_bits[i] = ~x3270_bits[i];
		inv_icon = XCreateBitmapFromData(display, root_window,
		    (char *) x3270_bits, x3270_width, x3270_height);
		wait_icon = XCreateBitmapFromData(display, root_window,
		    (char *) wait_bits, wait_width, wait_height);
		for (i = 0; i < sizeof(wait_bits); i++)
			wait_bits[i] = ~wait_bits[i];
		inv_wait_icon = XCreateBitmapFromData(display, root_window,
		    (char *) wait_bits, wait_width, wait_height);
		XtVaSetValues(toplevel,
		    XtNiconPixmap, icon,
		    XtNiconMask, icon,
		    NULL);
	}
}

/*
 * Initialize the active icon font information.
 */
static void
aicon_font_init(void)
{
	XFontStruct *f;
	Font ff;
	char **matches;
	int count;

	if (!appres.active_icon) {
		appres.label_icon = False;
		return;
	}

	matches = XListFontsWithInfo(display, appres.icon_font, 1, &count, &f);
	if (matches == (char **)NULL) {
		xs_warning("No font %s \"%s\"; activeIcon will not work",
		    ResIconFont, appres.icon_font);
		appres.active_icon = False;
		return;
	}
	ff = XLoadFont(display, matches[0]);
	iss.char_width = fCHAR_WIDTH(f);
	iss.char_height = fCHAR_HEIGHT(f);
	iss.fid = ff;
	iss.ascent = f->ascent;
	XFreeFontInfo(matches, f, count);
	iss.overstrike = False;
	iss.standard_font = True;
	iss.extended_3270font = False;
	iss.latin1_font = False;
	iss.debugging_font = False;
	iss.obscured = True;
	iss.xfmap = cg2asc7;
	if (appres.label_icon) {
		matches = XListFontsWithInfo(display, appres.icon_label_font,
		    1, &count, &ailabel_font);
		if (matches == (char **)NULL) {
			xs_warning("Cannot load %s \"%s\" font; labelIcon will not work",
			    ResIconLabelFont, appres.icon_label_font);
			appres.label_icon = False;
			return;
		}
		ailabel_font->fid = XLoadFont(display, matches[0]);
		aicon_label_height = fCHAR_HEIGHT(ailabel_font) + 2;
	}
}

/*
 * Determine the current size of the active icon.
 */
static void
aicon_size(Dimension *iw, Dimension *ih)
{
	XIconSize *is;
	int count;

	*iw = maxCOLS*iss.char_width + 2*VHALO;
	*ih = maxROWS*iss.char_height + 2*HHALO + aicon_label_height;
	if (XGetIconSizes(display, root_window, &is, &count)) {
		if (*iw > (unsigned) is[0].max_width)
			*iw = is[0].max_width;
		if (*ih > (unsigned) is[0].max_height)
			*ih = is[0].max_height;
	}
}

/*
 * Initialize the active icon.  Assumes that aicon_font_init has already been
 * called.
 */
static void
aicon_init(void)
{
	if (!appres.active_icon)
		return;

	iss.widget = icon_shell;
	iss.window = XtWindow(iss.widget);
	iss.cursor_daddr = 0;
	iss.exposed_yet = False;
	if (appres.label_icon) {
		XGCValues xgcv;

		xgcv.font = ailabel_font->fid;
		xgcv.foreground = appres.foreground;
		xgcv.background = appres.background;
		ailabel_gc = XtGetGC(toplevel,
		    GCFont|GCForeground|GCBackground,
		    &xgcv);
	}
}

/*
 * Reinitialize the active icon.
 */
static void
aicon_reinit(unsigned cmask)
{
	if (!appres.active_icon)
		return;

	if (cmask & (FONT_CHANGE | COLOR_CHANGE))
		make_gcs(&iss);

	if (cmask & MODEL_CHANGE) {
		aicon_size(&iss.screen_width, &iss.screen_height);
		if (iss.image)
			XtFree((char *) iss.image);
		iss.image = (union sp *)
		    XtMalloc(sizeof(union sp) * maxROWS * maxCOLS);
		XtVaSetValues(iss.widget,
		    XtNwidth, iss.screen_width,
		    XtNheight, iss.screen_height,
		    NULL);
	}
	if (cmask & (MODEL_CHANGE | FONT_CHANGE | COLOR_CHANGE))
		(void) memset((char *)iss.image, 0,
			      sizeof(union sp) * maxROWS * maxCOLS);
}

/* Draw the aicon label */
static void
draw_aicon_label(void)
{
	int len;
	Position x;

	if (!appres.label_icon || !iconic)
		return;

	XFillRectangle(display, iss.window,
	    get_gc(&iss, INVERT_COLOR(0)),
	    0, iss.screen_height - aicon_label_height,
	    iss.screen_width, aicon_label_height);
	len = strlen(aicon_text);
	x = ((int)iss.screen_width - XTextWidth(ailabel_font, aicon_text, len))
	     / 2;
	if (x < 0)
		x = 2;
	XDrawImageString(display, iss.window, ailabel_gc,
	    x,
	    iss.screen_height - aicon_label_height + ailabel_font->ascent,
	    aicon_text, len);
}

/* Set the aicon label */
void
set_aicon_label(char *l)
{
	if (aicon_text)
		XtFree(aicon_text);
	aicon_text = XtNewString(l);
	draw_aicon_label();
}

/* Change the bitmap icon. */
static void
flip_icon(Boolean inverted, enum mcursor_state mstate)
{
	Pixmap p = icon;
	
	if (mstate == LOCKED)
		mstate = NORMAL;
	if (appres.active_icon
	    || (inverted == icon_inverted && mstate == icon_cstate))
		return;
	switch (mstate) {
	    case WAIT:
		if (inverted)
			p = inv_wait_icon;
		else
			p = wait_icon;
		break;
	    case LOCKED:
	    case NORMAL:
		if (inverted)
			p = inv_icon;
		else
			p = icon;
		break;
	}
	XtVaSetValues(toplevel,
	    XtNiconPixmap, p,
	    XtNiconMask, p,
	    NULL);
	icon_inverted = inverted;
	icon_cstate = mstate;
}

/*
 * Invert the icon.
 */
static void
invert_icon(Boolean inverted)
{
	flip_icon(inverted, icon_cstate);
}

/*
 * Change to the lock icon.
 */
static void
lock_icon(enum mcursor_state state)
{
	flip_icon(icon_inverted, state);
}


/*
 * Resize font list parser.
 */
static void
init_rsfonts(void)
{
	char *s;
	char *name;
	struct rsfont *r;

	/* Can't resize in APL mode. */
	if (appres.apl_mode)
		return;

	if ((s = get_resource(ResResizeFontList)) == CN)
		return;

	s = XtNewString(s);
	while (split_lresource(&s, &name) == 1) {
		XFontStruct *f;
		char **matches;
		int count;

		matches = XListFontsWithInfo(display, name, 1, &count, &f);
		if (matches == (char **)NULL)
			continue;
		r = (struct rsfont *)XtMalloc(sizeof(*r));
		r->name = name;
		r->width = fCHAR_WIDTH(f);
		r->height = fCHAR_HEIGHT(f);
		XFreeFontInfo(matches, f, count);
		r->next = rsfonts;
		rsfonts = r;
	}
}

/*
 * Handle ConfigureNotify events.
 */
static struct {
	Boolean ticking;
	Dimension width, height;
	Position x, y;
	XtIntervalId id;
} cn = {
	False, 0, 0, 0, 0, 0
};
static Position main_x = 0, main_y = 0;

/*
 * Timeout routine called 0.5 sec after x3270 sets new screen dimensions.
 * We assume that if this happens, the window manager is happy with our new
 * size.
 */
static void
configure_stable(XtPointer closure unused, XtIntervalId *id unused)
{
	trace_event("Reconfigure timer expired\n");
	configure_ticking = False;
	if (!cn.ticking)
		screen_redo = REDO_NONE;
}

/* Perform a resize operation. */
static void
do_resize(void)
{
	struct rsfont *r;
	struct rsfont *best = (struct rsfont *) NULL;

	/* What we're doing now is irreversible. */
	screen_redo = REDO_RESIZE;

	if (rsfonts == (struct rsfont *)NULL || !appres.allow_resize) {
		/* Illegal or impossible. */
		if (rsfonts == (struct rsfont *)NULL)
			trace_event("  no fonts available for resize\n"
			    "    reasserting previous size\n");
		else
			trace_event("  resize prohibited by resource\n"
			    "    reasserting previous size\n");
		set_toplevel_sizes();
		return;
	}

	/*
	 * Recompute the resulting screen area for each font, based on the
	 * current keypad, model, and scrollbar settings.
	 */
	for (r = rsfonts; r != (struct rsfont *) NULL; r = r->next) {
		Dimension cw, ch;	/* container_width, container_height */

		cw = SCREEN_WIDTH(r->width)+2 + scrollbar_width;
#if defined(X3270_KEYPAD) /*[*/
		{
			Dimension mkw;

			mkw = min_keypad_width();
			if (kp_placement == kp_integral
			    && appres.keypad_on
			    && cw < mkw)
				cw = mkw;
		}

#endif /*]*/
		ch = SCREEN_HEIGHT(r->height)+2 + menubar_qheight(cw);
#if defined(X3270_KEYPAD) /*[*/
		if (kp_placement == kp_integral && appres.keypad_on)
			ch += keypad_qheight();
#endif /*]*/
		r->total_width = cw;
		r->total_height = ch;
		r->area = cw * ch;
	}

	/*
	 * Find the the best match for requested dimensions.
	 *
	 * In past, the "best" was the closest area that was larger or
	 * smaller than the current area, whichever was requested.
	 *
	 * Now, if they try to shrink the screen, "they" might be the
	 * window manager enforcing a size restriction, so the "best"
	 * match is the largest window that fits within the requested
	 * dimensions.
	 *
	 * If they try to grow the screen, then the "best" is the
	 * smallest font that lies between the current and requested
	 * length in the requested dimension(s).
	 *
	 * An ambiguous request (one dimension larger and the other smaller)
	 * is taken to be a "larger" request.
	 */

	if ((cn.width <= main_width && cn.height <= main_height) ||
	    (cn.width > main_width && cn.height > main_height)) {
		/*
		 * Shrink or two-dimensional grow: Find the largest font which
		 * fits within the new boundaries.
		 *
		 * Note that a shrink in one dimension, with the other
		 * dimension matching, is considered a two-dimensional
		 * shrink.
		 */
		for (r = rsfonts; r != (struct rsfont *) NULL; r = r->next) {
			if (r->total_width <= cn.width &&
			    r->total_height <= cn.height) {
				if (best == NULL || r->area > best->area)
					best = r;
			}
		}
		/*
		 * If no font is small enough, see if there is a font smaller
		 * than the current font along the requested dimension(s),
		 * which is better than doing nothing.
		 */
		if (best == NULL) {
			for (r = rsfonts;
			     r != (struct rsfont *) NULL;
			     r = r->next) {
				if (cn.width < main_width &&
				    r->total_width > main_width)
					continue;
				if (cn.height < main_height &&
				    r->total_height > main_height)
					continue;
				if (best == NULL ||
				    r->area < best->area) {
					best = r;
				}
			}
		}
	} else {
		/*
		 * One-dimensional grow: Find the largest font which fits
		 * within the lengthened boundary, and don't constrain the
		 * other dimension.
		 *
		 * Note than an ambiguous change (grow in one dimensional and
		 * shrink in the other) is considered a one-dimensional grow.
		 *
		 * In either case, the "other" dimension is considered
		 * unconstrained.
		 */
		if (cn.width > main_width) {
			/* Wider. */
			for (r = rsfonts;
			     r != (struct rsfont *) NULL;
			     r = r->next) {
				if (r->total_width <= cn.width &&
				    (best == NULL ||
				     r->total_width > best->total_width)) {
					best = r;
				}
			}
		} else {
			/* Taller. */
			for (r = rsfonts;
			     r != (struct rsfont *) NULL;
			     r = r->next) {
				if (r->total_height <= cn.height &&
				    (best == NULL ||
				     r->total_height > best->total_height)) {
					best = r;
				}
			}
		}
	}

	/* Change fonts. */
	if (!best || (efontname && !strcmp(best->name, efontname))) {
		if (cn.width > main_width || cn.height > main_height)
			trace_event("  no larger font available\n"
			    "    reasserting previous size\n");
		else
			trace_event("  no smaller font available\n"
			    "    reasserting previous size\n");
		set_toplevel_sizes();
	} else {
		trace_event("    switching to font '%s', new size %dx%d\n",
		    best->name, best->total_width, best->total_height);
		screen_newfont(best->name, False);

		/* screen_newfont() sets screen_redo to REDO_FONT. */
		screen_redo = REDO_RESIZE;
	}
}

/*
 * Timeout routine called 0.5 sec after x3270 receives the last ConfigureNotify
 * message.  This is for window managers that use 'continuous' move or resize
 * actions.
 */

static void
stream_end(XtPointer closure unused, XtIntervalId *id unused)
{
	Boolean needs_moving = False;
	const char *revert = CN;
	char want_size[64];

	trace_event("Stream timer expired %hux%hu+%hd+%hd\n",
		cn.width, cn.height, cn.x, cn.y);

	/* Not ticking any more. */
	cn.ticking = False;

	/* Save the new coordinates in globals for next time. */
	if (cn.x != main_x || cn.y != main_y) {
		main_x = cn.x;
		main_y = cn.y;
		needs_moving = True;
	}

	/*
	 * If the dimensions are correct, do nothing, forget about any
	 * reconfig we may need to revert, and get out.
	 */
	if (cn.width == main_width && cn.height == main_height) {
		trace_event("  width and height match\n    doing nothing\n");
		screen_redo = REDO_NONE;
		goto done;
	}

	/* They're not correct. */
	(void) sprintf(want_size, "%ux%u", main_width, main_height);
	trace_event("  size mismatch, want %s\n", want_size);

	/* If there's a reconfiguration pending, try to undo it. */
	switch (screen_redo) {
	    case REDO_FONT:
		revert = "font";
		screen_newfont(redo_old_font, False);
		break;
#if defined(X3270_MENUS) /*[*/
	    case REDO_MODEL:
		revert = "model number";
		screen_change_model(redo_old_model,
		    redo_old_ov_cols, redo_old_ov_rows);
		break;
#endif /*]*/
#if defined(X3270_KEYPAD) /*[*/
	    case REDO_KEYPAD:
		revert = "keypad configuration";
		screen_showikeypad(appres.keypad_on = False);
		break;
#endif /*]*/
	    case REDO_SCROLLBAR:
		revert = "scrollbar configuration";
		if (toggled(SCROLL_BAR)) {
			toggle_toggle(&appres.toggle[SCROLL_BAR]);
			toggle_scrollBar(&appres.toggle[SCROLL_BAR],
			    TT_TOGGLE);
		}
		break;
	    case REDO_RESIZE:
		/* Changed fonts in response to a previous user resize. */
		trace_event("  size reassertion failed, window truncated\n"
		    "    doing nothing\n");
		screen_redo = REDO_NONE;
		goto done;
	    case REDO_NONE:
	        /* Initial configuration, or user-generated resize. */
		do_resize();
		goto done;
	    default:
		break;
	}

	/* Tell the user what we're doing. */
	if (revert != CN) {
		trace_event("    reverting to previous %s\n", revert);
		popup_an_error("Main window does not fit on the "
		    "X display\n"
		    "Reverting to previous %s", revert);
	}

	screen_redo = REDO_NONE;

    done:
	if (needs_moving && !iconic)
		keypad_move();
}

void
PA_ConfigureNotify_action(Widget w unused, XEvent *event, String *params unused,
    Cardinal *num_params unused)
{
	XConfigureEvent *re = (XConfigureEvent *) event;
	Position xx, yy;

#if defined(INTERNAL_ACTION_DEBUG) /*[*/
	action_debug(PA_ConfigureNotify_action, event, params, num_params);
#endif /*]*/

	/*
	 * Get the new window coordinates.  If the configure event reports it
	 * as (0,0), ask for it explicitly.
	 */
	if (re->x || re->y) {
		xx = re->x;
		yy = re->y;
	} else {
		XtVaGetValues(toplevel, XtNx, &xx, XtNy, &yy, NULL);
	}
	trace_event("ConfigureNotify %hux%hu+%hd+%hd\n",
	    re->width, re->height, xx, yy);
	
	/* Save the latest values. */
	cn.x = xx;
	cn.y = yy;
	cn.width = re->width;
	cn.height = re->height;

	/* Set the stream timer for 0.5 sec from now. */
	if (cn.ticking)
		XtRemoveTimeOut(cn.id);
	cn.id = XtAppAddTimeOut(appcontext, 500, stream_end, 0);
	cn.ticking = True;
}

/*
 * Process a VisibilityNotify event, setting the 'visibile' flag in nss.
 * This will switch the behavior of screen scrolling.
 */
void
PA_VisibilityNotify_action(Widget w unused, XEvent *event unused,
    String *params unused, Cardinal *num_params unused)
{
	XVisibilityEvent *e;

#if defined(INTERNAL_ACTION_DEBUG) /*[*/
	action_debug(PA_VisibilityNotify_action, event, params, num_params);
#endif /*]*/
	e = (XVisibilityEvent *)event;
	nss.obscured = (e->state != VisibilityUnobscured);
}

/*
 * Process a GraphicsExpose event, refreshing the screen if we have done
 * one or more failed XCopyArea calls.
 */
void
PA_GraphicsExpose_action(Widget w unused, XEvent *event unused,
    String *params unused, Cardinal *num_params unused)
{
	int i;

#if defined(INTERNAL_ACTION_DEBUG) /*[*/
	action_debug(PA_GraphicsExpose_action, event, params, num_params);
#endif /*]*/

	if (nss.copied) {
		/*
		 * Force a screen redraw.
		 */
		(void) memset((char *) ss->image, 0,
		              (maxROWS*maxCOLS) * sizeof(union sp));
		if (ss->debugging_font)
			for (i = 0; i < maxROWS*maxCOLS; i++)
				ss->image[i].bits.cg = CG_space;
		ctlr_changed(0, ROWS*COLS);
		cursor_changed = True;

		nss.copied = False;
	}
}

/* Display size functions. */
unsigned
display_width(void)
{
	return XDisplayWidth(display, default_screen);
}

unsigned
display_widthMM(void)
{
	return XDisplayWidthMM(display, default_screen);
}

unsigned
display_height(void)
{
	return XDisplayHeight(display, default_screen);
}

unsigned
display_heightMM(void)
{
	return XDisplayHeightMM(display, default_screen);
}
