/*======================================================================*\

Copyright (c) 1990-2016  Paul Vojta and others

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to
deal in the Software without restriction, including without limitation the
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
sell copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
PAUL VOJTA OR ANY OTHER AUTHOR OF THIS SOFTWARE BE LIABLE FOR ANY CLAIM,
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

NOTE:
xdvi is based on prior work, as noted in the modification history
in xdvi.c.

\*========================================================================*/

#include "xdvi-config.h"

#ifdef STDC_HEADERS
# include <unistd.h>
# include <fcntl.h>
#endif
#include <signal.h>
#include <sys/file.h>	/* this defines FASYNC */
#ifdef HAVE_SYS_FCNTL_H
# include <sys/fcntl.h>  /* Or this might define FASYNC */
#endif
#include <sys/ioctl.h>	/* this defines SIOCSPGRP and FIOASYNC */
#include <sys/wait.h>	/* ignore HAVE_SYS_WAIT_H -- we always need WNOHANG */

#include "xdvi.h" /* this includes Xlib and Xutil are already included */
#include "xdvi-debug.h"


#ifndef MOTIF
#include <X11/Xaw/Form.h> /* for XtNresizable */
#endif

#include <setjmp.h>
#include <sys/time.h>	/* for gettimeofday */

#include <X11/IntrinsicP.h>

#include <X11/Xatom.h>
#include <X11/StringDefs.h>
#include <X11/Shell.h>	/* needed for def. of XtNiconX(??) */

#include "pagesel.h"
#include "filehist.h"
#include "special.h"
#include "psgs.h"


#include <errno.h>

#include <ctype.h>

#include "util.h"
#include "x_util.h"
#include "string-utils.h"
#include "print-dialog.h"
#include "search-dialog.h"
#include "sfSelFile.h"
#include "mag.h"
#include "help-window.h"
#include "message-window.h"
#include "dvi-draw.h"
#include "statusline.h"
#include "hypertex.h"
#include "dvi-init.h"
#include "exit-handlers.h"
#include "Tip.h"
#include "browser.h"
#include "search-internal.h"
#include "my-snprintf.h"

#include "events.h"
#include "selection.h"
#include "encodings.h"
#include "pagehist.h"
#include "xm_colorsel.h"
#include "xm_toolbar.h"
#include "xaw_menu.h"
#include "xm_menu.h"
#include "xm_prefs.h"

#include "xm_prefs_appearance.h" /* for update_preferences_expert() */
#include "xm_prefs_fonts.h"	 /* for update_preferences_color() */
#include "xm_prefs_page.h"	 /* for update_preferences_shrink() */

#ifdef	X_NOT_STDC_ENV
extern int errno;
#endif /* X_NOT_STDC_ENV */

#if HAVE_X11_INTRINSICI_H
# include <X11/IntrinsicI.h>
#else

/* Taken from <X11/TranslateI.h> in libXt-1.1.3 (June 2012) */
typedef struct _LateBindings {
    unsigned int knot:1;
    unsigned int pair:1;
    unsigned short ref_count;	/* garbage collection */
    KeySym keysym;
} LateBindings, *LateBindingsPtr;

extern Boolean _XtComputeLateBindings(
    Display*		/* dpy */,
    LateBindingsPtr	/* lateModifiers */,
    Modifiers*		/* computed */,
    Modifiers*		/* computedMask */
);

#endif /* not HAVE_X11_INTRINSICI_H */

#if HAVE_XKB_BELL_EXT
# include <X11/XKBlib.h>
# define XdviBell(display, window, percent)	\
	 XkbBell(display, window, percent, (Atom) None)
#else
# define XdviBell(display, window, percent)	XBell(display, percent)
#endif

/* Linux prefers O_ASYNC over FASYNC; SGI IRIX does the opposite.  */
#if !defined(FASYNC) && defined(O_ASYNC)
# define FASYNC	O_ASYNC
#endif

#if !defined(FLAKY_SIGPOLL) && !HAVE_STREAMS && !defined(FASYNC)
# if !defined(SIOCSPGRP) || !defined(FIOASYNC)
#  define FLAKY_SIGPOLL	1
# endif
#endif

#ifndef FLAKY_SIGPOLL

# ifndef SIGPOLL
#  define SIGPOLL	SIGIO
# endif

# ifndef SA_RESTART
#  define SA_RESTART 0
# endif

# if HAVE_STREAMS
#  include <stropts.h>

#  ifndef S_RDNORM
#   define S_RDNORM S_INPUT
#  endif

#  ifndef S_RDBAND
#   define S_RDBAND  0
#  endif

#  ifndef S_HANGUP
#   define S_HANGUP  0
#  endif

#  ifndef S_WRNORM
#   define S_WRNORM  S_OUTPUT
#  endif
# endif /* HAVE_STREAMS */

#endif /* not FLAKY_SIGPOLL */

#if HAVE_SIGACTION && !defined SA_RESETHAND
# ifdef SA_ONESHOT
#  define SA_RESETHAND SA_ONESHOT
# else
#  undef HAVE_SIGACTION         /* Needed for Mac OS X < 10.2 (9/2002) */
# endif
#endif

#if HAVE_POLL
# include <poll.h>
#else
# if HAVE_SYS_SELECT_H
#  include <sys/select.h>
# else
#  if HAVE_SELECT_H
#   include <select.h>
#  endif
# endif
# define XIO_IN 1
# define XIO_OUT 2
#endif /* HAVE_POLL */

/* cannot be const since Strings in Action routines aren't either */
static char *Act_true_retval = "true";
static char *Act_false_retval = "false";


#if HAVE_XI21

int		xi2_opcode;
Boolean		xi2_active	= False;
struct xi2_master *xi2_masters;		/* linked list of master devs */
struct xi2_master *xi2_current;		/* current master device */
struct xi2_slave *xi2_slaves;		/* linked list of slave devs */

struct xi2_slave xi2_no_slave;		/* if no slave assigned yet */

/*
 *	XInput 2.1 creates spurious enter/leave events around the time it
 *	creates fake wheel button events.  So, we keep track of the last
 *	such fake button event, and if an enter/leave occurs too soon after
 *	that event, then we ignore the enter event.
 *	I've managed to get rid of this (by making sure that the relevant
 *	events come from the drawing window if possible), but I left the code
 *	here just in case.
 */

static	Time	xi2_ign_time;	/* time of last XI 2.1 wheel button event */

#endif
    

static sigset_t all_signals;

/*
 *	Interrupt system for receiving events.  The program sets a flag
 *	whenever an event comes in, so that at the proper time (i.e., when
 *	reading a new dvi item), we can check incoming events to see if we
 *	still want to go on printing this page.  This way, one can stop
 *	displaying a page if it is about to be erased anyway.  We try to read
 *	as many events as possible before doing anything and base the next
 *	action on all events read.
 *	Note that the Xlib and Xt routines are not reentrant, so the most we
 *	can do is set a flag in the interrupt routine and check it later.
 *	Also, sometimes the interrupts are not generated (some systems only
 *	guarantee that SIGIO is generated for terminal files, and on the system
 *	I use, the interrupts are not generated if I use "(xdvi foo &)" instead
 *	of "xdvi foo").  Therefore, there is also a mechanism to check the
 *	event queue every 70 drawing operations or so.  This mechanism is
 *	disabled if it turns out that the interrupts do work.
 *	For a fuller discussion of some of the above, see xlife in
 *	comp.sources.x.
 */

/*
 *	Signal flags
 */

/* This could be static volatile, but we want to avoid any optimizer bugs.  */
VOLATILE unsigned int sig_flags	= 0;

#define	SF_USR	1
#define	SF_ALRM	2
#define	SF_POLL	4
#define	SF_CHLD	8
#define	SF_TERM	16
#define	SF_SEGV	32

static void do_sigusr(void);
static void do_sigalrm(void);
static void do_sigpoll(void);
static void do_sigchld(void);
static void do_sigterm(void);
static void do_sigsegv(void);

/* these must be in the same order as SF_*.
   The higher flags have higher priority, since these are
   checked first when resolving flags_to_sigproc[sig_flags].

   Example: flag := SF_TERM | SF_CHLD = 24
   
   flags_to_sigproc[24] =>
   do_sigterm, which sets flag to 24 & ~SF_TERM == 8
   then, in next check:       
   flags_to_sigproc[8] =>
   do_sigchld, which sets flag 8 & ~SF_CHLD == 0.
*/
#define	SP0	do_sigusr
#define	SP1	do_sigalrm
#define	SP2	do_sigpoll
#define	SP3	do_sigchld
#define	SP4	do_sigterm
#define	SP5	do_sigsegv

typedef	void	(*signalproc)(void);

static const signalproc flags_to_sigproc[64] = {
    NULL,
    /* 1 */
    SP0,
    /* 2 - 3 */
    SP1, SP1,
    /* 4 - 7 */
    SP2, SP2, SP2, SP2,
    /* 8 - 15 */
    SP3, SP3, SP3, SP3, SP3, SP3, SP3, SP3,
    /* 16 - 31 */
    SP4, SP4, SP4, SP4, SP4, SP4, SP4, SP4,
    SP4, SP4, SP4, SP4, SP4, SP4, SP4, SP4,
    /* 32 - 63 */
    SP5, SP5, SP5, SP5, SP5, SP5, SP5, SP5,
    SP5, SP5, SP5, SP5, SP5, SP5, SP5, SP5,
    SP5, SP5, SP5, SP5, SP5, SP5, SP5, SP5,
    SP5, SP5, SP5, SP5, SP5, SP5, SP5, SP5
};

#undef	SP0
#undef	SP2
#undef	SP3
#undef	SP4
#undef	SP5

/* file-static variables for prefix argument mechanism */
static Boolean m_have_arg = False; /* flag whether we have a possible prefix arg */
static int m_number = 0;        /* prefix arg value, without the sign */
static int m_sign = 1;          /* is prefix arg negative? */


/* to remember the scrollbar positions so that we can restore them
   when `keep position' is active and window is resized to full size
   (removing scrollbars) and then back (#810501) */
static int m_x_scroll = 0, m_y_scroll = 0;

static int source_reverse_x, source_reverse_y;
static int source_show_all;

/* globals for color stuff */
Pixel plane_masks[4];
XColor color_data[2];
struct pagecolor_info page_colors = { 0, NULL };

struct rgb *color_bottom;
unsigned int color_bot_size;		/* number of entries */
struct colorframe *rcs_top;
struct rgb fg_initial;			/* Initial fg (from command line) */
struct rgb bg_initial;			/* Initial bg */
struct bgrec *bg_head = NULL;		/* head of list */
struct bgrec *bg_current = NULL;	/* current bg value */
struct fgrec *fg_current;		/* current fg value */
struct fgrec *fg_active = NULL;		/* where the GCs are */
Pixel *color_list;			/* list of colors */
unsigned int color_list_len = 0;	/* current len of list*/
unsigned int color_list_max = 0;	/* allocated size */
Boolean color_warned = False;

void null_mouse(XEvent *event)
{
    UNUSED(event);
}

mouse_proc mouse_motion = null_mouse;
mouse_proc mouse_release = null_mouse;

static void Act_digit(Widget, XEvent *, String *, Cardinal *);
static void Act_find(Widget, XEvent *, String *, Cardinal *);
static void Act_incremental_find(Widget, XEvent *, String *, Cardinal *);
static void Act_find_next(Widget, XEvent *, String *, Cardinal *);
static void Act_minus(Widget, XEvent *, String *, Cardinal *);
static void Act_quit(Widget, XEvent *, String *, Cardinal *);
static void Act_quit_confirm(Widget, XEvent *, String *, Cardinal *);
static void Act_print(Widget, XEvent *, String *, Cardinal *);
static void Act_save(Widget, XEvent *, String *, Cardinal *);
static void Act_help(Widget, XEvent *, String *, Cardinal *);
static void Act_goto_page(Widget, XEvent *, String *, Cardinal *);
static void Act_declare_page_number(Widget, XEvent *, String *, Cardinal *);
static void Act_toggle_mark(Widget, XEvent *, String *, Cardinal *);
static void Act_home(Widget, XEvent *, String *, Cardinal *);
static void Act_home_or_top(Widget, XEvent *, String *, Cardinal *);
static void Act_end_or_bottom(Widget, XEvent *, String *, Cardinal *);
static void Act_center(Widget, XEvent *, String *, Cardinal *);
static void Act_left(Widget, XEvent *, String *, Cardinal *);
static void Act_right(Widget, XEvent *, String *, Cardinal *);
static void Act_up(Widget, XEvent *, String *, Cardinal *);
static void Act_down(Widget, XEvent *, String *, Cardinal *);
static void Act_up_or_previous(Widget, XEvent *, String *, Cardinal *);
static void Act_down_or_next(Widget, XEvent *, String *, Cardinal *);
static void Act_set_margins(Widget, XEvent *, String *, Cardinal *);
static void Act_show_display_attributes(Widget, XEvent *, String *, Cardinal *);
static void Act_set_density(Widget, XEvent *, String *, Cardinal *);
static void Act_change_density(Widget, XEvent *, String *, Cardinal *);
static void Act_fullscreen(Widget, XEvent *, String *, Cardinal *);
#ifdef GREY
static void Act_set_greyscaling(Widget, XEvent *, String *, Cardinal *);
#endif
#if COLOR
static void Act_set_color(Widget, XEvent *, String *, Cardinal *);
#endif

static void Act_htex_anchorinfo(Widget, XEvent *, String *, Cardinal *);

static void Act_reread_dvi_file(Widget, XEvent *, String *, Cardinal *);
static void Act_select_dvi_file(Widget, XEvent *, String *, Cardinal *);
static void Act_discard_number(Widget, XEvent *, String *, Cardinal *);
static void Act_drag(Widget, XEvent *, String *, Cardinal *);
static void Act_wheel(Widget, XEvent *, String *, Cardinal *);
static void Act_hwheel(Widget, XEvent *, String *, Cardinal *);
static void Act_press(Widget, XEvent *, String *, Cardinal *);
static void Act_motion(Widget, XEvent *, String *, Cardinal *);
static void Act_release(Widget, XEvent *, String *, Cardinal *);
static void Act_toggle_grid_mode(Widget, XEvent *, String *, Cardinal *);
static void Act_source_special(Widget, XEvent *, String *, Cardinal *);
static void Act_show_source_specials(Widget, XEvent *, String *, Cardinal *);
static void Act_source_what_special(Widget, XEvent *, String *, Cardinal *);
static void Act_unpause_or_next(Widget, XEvent *, String *, Cardinal *);
static void Act_user_exec(Widget, XEvent *, String *, Cardinal *);
static void Act_ruler_snap_origin(Widget w, XEvent *event, String *params, Cardinal *num_params);
static void Act_load_url(Widget w, XEvent *event, String *params, Cardinal *num_params);
#ifdef MOTIF
static void Act_prefs_dialog(Widget w, XEvent *event, String *params, Cardinal *num_params);
#endif

static XtActionsRec m_actions[] = {
    {"digit", Act_digit},
    {"mouse-modes", Act_mouse_modes},
    {"switch-mode", Act_switch_mode},
    {"minus", Act_minus},
    {"recent-files", Act_recent_files},
    {"quit", Act_quit},
    {"quit-confirm", Act_quit_confirm},
    {"print", Act_print},
    {"save", Act_save},
    {"find", Act_find},
    {"incremental-find", Act_incremental_find},
    {"find-next", Act_find_next},
    {"help", Act_help},
    {"goto-page", Act_goto_page},
    {"use-tex-pages", Act_use_tex_pages},
    {"forward-page", Act_forward_page},
    {"back-page", Act_back_page},
    {"toggle-mark", Act_toggle_mark},
    {"declare-page-number", Act_declare_page_number},
    {"home", Act_home},
    {"home-or-top", Act_home_or_top},
    {"end-or-bottom", Act_end_or_bottom},
    {"center", Act_center},
    {"set-keep-flag", Act_set_keep_flag},
    {"left", Act_left},
    {"right", Act_right},
    {"up", Act_up},
    {"down", Act_down},
    {"up-or-previous", Act_up_or_previous},
    {"down-or-next", Act_down_or_next},
    {"set-margins", Act_set_margins},
    {"show-display-attributes", Act_show_display_attributes},
    {"set-shrink-factor", Act_set_shrink_factor},
    {"shrink-to-dpi", Act_shrink_to_dpi},
    {"set-density", Act_set_density},
    {"change-density", Act_change_density},
    {"fullscreen", Act_fullscreen},
#ifdef GREY
    {"set-greyscaling", Act_set_greyscaling},
#endif
#if COLOR
    {"set-color", Act_set_color},
#endif
#ifdef PS
    {"set-ps", Act_set_ps},
#endif
    {"htex-back", Act_htex_back},
    {"htex-forward", Act_htex_forward},
    {"htex-anchorinfo", Act_htex_anchorinfo},
#ifdef PS_GS
    {"set-gs-alpha", Act_set_gs_alpha},
#endif
    {"set-expert-mode", Act_set_expert_mode},
    {"reread-dvi-file", Act_reread_dvi_file},
    {"select-dvi-file", Act_select_dvi_file},
    {"discard-number", Act_discard_number},
    {"drag", Act_drag},
    {"wheel", Act_wheel},
    {"hwheel", Act_hwheel},
    {"press", Act_press},
    {"motion", Act_motion},
    {"release", Act_release},
    {"toggle-grid-mode", Act_toggle_grid_mode},
    {"source-special", Act_source_special},
    {"show-source-specials", Act_show_source_specials},
    {"source-what-special", Act_source_what_special},
    {"unpause-or-next", Act_unpause_or_next},
    {"user-exec", Act_user_exec},
#if 0 /* not implemented yet */
    {"set-papersize", Act_set_papersize},
    {"set-paper-landscape", Act_set_paper_landscape},
#endif
    {"load-url", Act_load_url},
    {"scroll-list-up", Act_scroll_list_up},
    {"scroll-list-down", Act_scroll_list_down},
    {"pagehistory-clear", Act_pagehistory_clear},
    {"pagehistory-back", Act_pagehistory_back},
    {"pagehistory-forward", Act_pagehistory_forward},
    {"pagehistory-delete-backward", Act_pagehistory_delete_backward},
    {"pagehistory-delete-forward", Act_pagehistory_delete_forward},    
#ifdef MOTIF
    {"prefs-dialog", Act_prefs_dialog},    
#endif
    {"magnifier", Act_magnifier},
    {"ruler", Act_ruler},
    {"ruler-snap-origin", Act_ruler_snap_origin},
    {"text-selection", Act_text_selection},
    {"do-href", Act_href},
    {"do-href-newwindow", Act_href_newwindow},
    {"switch-magnifier-units", Act_switch_magnifier_units},
};


Boolean
compile_action(const char *str, struct xdvi_action **app)
{
    const char *p, *p1, *p2, *end_cmd;
    XtActionsRec *actp;
    struct xdvi_action *ap;
    String *params;
    Cardinal num_params;

    for (;;) {

	while (*str == ' ' || *str == '\t')
	    ++str;

	if (*str == '\0' || *str == '\n')
	    break;

	p = str;

	/* find end of command name */
	while (isalnum((int)*p) || *p == '-' || *p == '_')
	    ++p;
	
	end_cmd = p;
	
	for (actp = m_actions; ; ++actp) {
	    if (actp >= m_actions + XtNumber(m_actions)) {
		const char *tmp = strchr(str, '\0');
		if (tmp == NULL) {
		    tmp = p;
		}
		XDVI_WARNING((stderr, "Cannot compile action \"%.*s\".",
			     (int) (tmp - str), str));
		*app = NULL;
		return False;
	    }
	    if (memcmp(str, actp->string, p - str) == 0
	      && actp->string[p - str] == '\0')
		break;
	}

	while (*p == ' ' || *p == '\t')
	    ++p;
	if (*p != '(') {
	    while (*p != '\0' && *p != '\n')
		++p;
	    XDVI_WARNING((stderr, "Syntax error in action %.*s.",
			 (int) (p - str), str));
	    *app = NULL;
	    return False;
	}

	do {++p;}
	while (*p == ' ' || *p == '\t');

	num_params = 0;
	if (*p == ')')
	    params = NULL;
	else {
	    Cardinal max_params = 4;
	    
	    params = xmalloc(max_params * sizeof (String));

	    for (;;) {	/* loop over params */
		if (*p == '"') {
		    ++p;
		    p1 = strchr(p, '"');
		    if (p1 == NULL) {
			p1 = strchr(p, '\n');
			if (p1 == NULL)
			    p1 = p + strlen(p);
			XDVI_WARNING((stderr, "Syntax error in action %.*s.",
				     (int) (p1 - str), str));
			while (num_params > 0) {
			    --num_params;
			    free(params[num_params]);
			}
			free(params);
			*app = NULL;
			return False;
		    }
		    params[num_params++] = xstrndup(p, p1 - p);

		    do {++p1;}
		    while (*p1 == ' ' || *p1 == '\t');

		    if (*p1 != ')' && *p1 != ',') {
			p2 = strchr(p1, '\n');
			if (p2 == NULL)
			    p2 = p1 + strlen(p1);
			XDVI_WARNING((stderr, "Syntax error in action %.*s.",
				     (int) (p2 - str), str));
			while (num_params > 0) {
			    --num_params;
			    free(params[num_params]);
			}
			free(params);
			*app = NULL;
			return False;
		    }
		}
		else {	/* param is not quoted */
		    for (p1 = p;; ++p1) {
			if (*p1 == '\0' || *p1 == '\n') {
			    XDVI_WARNING((stderr,
					 "Syntax error in action %.*s.",
					 (int) (p1 - str), str));
			    while (num_params > 0) {
				--num_params;
				free(params[num_params]);
			    }
			    free(params);
			    *app = NULL;
			    return False;
			}
			if (*p1 == ')' || *p1 == ',')
			    break;
		    }
		    p2 = p1;
		    while (p2 > p && (p2[-1] == ' ' || p2[-1] == '\t'))
			--p2;
		    params[num_params++] = xstrndup(p, p2 - p);
		}

		p = p1;
		if (*p == ')')
		    break;

		do {++p;}
		while (*p == ' ' || *p == '\t');

		if (num_params >= max_params) {
		    max_params *= 2;
		    params = xrealloc(params, max_params * sizeof (String));
		}
	    }
	} /* end if */

	ap = xmalloc(sizeof *ap);
	ap->proc = actp->proc;
	ap->command = xstrndup(str, end_cmd - str);
	ap->params = params;
	ap->num_params = num_params;

	*app = ap;
	app = &ap->next;

	str = p + 1;
    }

    *app = NULL;
    return True;
}

/*
 *	cached_compile_action does the same as compile_action, but retains
 *	the compiled actions.  It is used only in Act_mouse_modes(), which
 *	otherwise would recompile the same actions over and over again.
 */

struct avl_cached_action {
	AVL_COMMON;
	struct xdvi_action *ap;
};


static void
cached_compile_action(const char *str, struct xdvi_action **app)
{
	static struct avl_cached_action *avl_ca_head = NULL;
	struct avl_cached_action *avl_ca;

	avl_ca = (struct avl_cached_action *)
	  avladd(str, strlen(str), (struct avl **) &avl_ca_head,
	  sizeof (struct avl_cached_action));

	if (avl_ca->key == str) {	/* if new record */
	    avl_ca->key = xstrdup(str);	/* copy string to new storage */
	    compile_action(str, &avl_ca->ap);
	}
	*app = avl_ca->ap;
}


/*
 * Access to m_actions
 */
int get_num_actions(void)
{
    return XtNumber(m_actions);
}

XtActionsRec *get_actions(void)
{
    return m_actions;
}

/*
 *	Data for buffered events.
 */

#ifndef FLAKY_SIGPOLL
static VOLATILE int event_freq = 70;
#else
#define	event_freq	70
#endif

static void can_exposures(struct WindowRec *windowrec);


/*
 *	Set the flag so that termination occurs, via the above routine.
 *	This should be used in place of xdvi_exit() when there may be a
 *	non-killable process running (e.g., anytime within read_events()).
 */

static void
xdvi_normal_exit(void)
{
    sig_flags |= SF_TERM;
}

static void
xdvi_normal_exit_cb(XtPointer arg)
{
    UNUSED(arg);
    sig_flags |= SF_TERM;
}

void
xdvi_exit_callback(Widget w, XtPointer client_data, XtPointer call_data)
{
    UNUSED(w);
    UNUSED(client_data);
    UNUSED(call_data);

    sig_flags |= SF_TERM;
}

/*
 * Event-handling routines.
 */


void
expose(struct WindowRec *windowrec,
       int x, int y,
       unsigned int w, unsigned int h)
{
    if (windowrec->min_x > x)
	windowrec->min_x = x;
    if (windowrec->max_x < (int)(x + w))
	windowrec->max_x = x + w;
    if (windowrec->min_y > y)
	windowrec->min_y = y;
    if (windowrec->max_y < (int)(y + h))
	windowrec->max_y = y + h;

    globals.ev.flags |= EV_EXPOSE;
}

void
clearexpose(struct WindowRec *windowrec,
	    int x, int y,
	    unsigned w, unsigned h)
{
    XClearArea(DISP, windowrec->win, x, y, w, h, False);
    expose(windowrec, x, y, w, h);
}

/*
 *	Routines for X11 toolkit.
 */

static Position m_window_x, m_window_y;
static Arg arg_xy[] = {
    {XtNx, (XtArgVal) &m_window_x},
    {XtNy, (XtArgVal) &m_window_y},
};

#define	get_xy() XtGetValues(globals.widgets.draw_widget, arg_xy, XtNumber(arg_xy))


static void
warn_num_params(const char *act_name, String *params, int num_params, int max_params)
{
    if (num_params > max_params) {
	XDVI_WARNING((stderr, "Too many parameters (%d) for action \"%s\", ignoring all after \"%s\"",
		      num_params, act_name, params[max_params - 1]));
    }
}


void
handle_command(Widget widget, XtPointer client_data, XtPointer call_data)
{
    struct xdvi_action *actp;

    UNUSED(call_data);

    /* call all actions registered for this event */
    for (actp = (struct xdvi_action *)client_data; actp != NULL; actp = actp->next) {
	if (globals.debug & DBG_EVENT)
	    fprintf(stderr, "calling action with param: %s\n",
		    actp->num_params ? actp->params[0] : "(null)");
	(actp->proc)(widget, NULL, actp->params, &actp->num_params);
    }
}

#ifdef MOTIF
int
set_bar_value(Widget bar, int value, int max)
{
    XmScrollBarCallbackStruct call_data;

#ifdef TEST_SCROLLING
    fprintf(stderr, "set_bar_value: val %d, max %d\n", value, max);
#endif
    if (value > max)
	value = max;
    if (value < 0)
	value = 0;
    call_data.value = value;
    XtVaSetValues(bar, XmNvalue, value, NULL);
    XtCallCallbacks(bar, XmNvalueChangedCallback, &call_data);
    return value;
}
#endif

void
home(wide_bool scrl)
{
    if (!scrl)
	XUnmapWindow(DISP, mane.win);
# ifdef MOTIF
    {
	int value;

	if (!resource.keep_flag) {
	    value = (globals.page.w - mane.width) / 2;
	    if (value > resource.sidemargin_int / mane.shrinkfactor)
		value = resource.sidemargin_int / mane.shrinkfactor;
	    (void)set_bar_value(globals.widgets.x_bar, value, (int)(globals.page.w - mane.width));
	}

	value = (globals.page.h - mane.height) / 2;
	if (value > resource.topmargin_int / mane.shrinkfactor)
	    value = resource.topmargin_int / mane.shrinkfactor;
	(void)set_bar_value(globals.widgets.y_bar, value, (int)(globals.page.h - mane.height));
    }
# else
    get_xy();
    if (!resource.keep_flag && globals.widgets.x_bar != NULL) {
	int coord = (globals.page.w - mane.width) / 2;

	if (coord > resource.sidemargin_int / mane.shrinkfactor)
	    coord = resource.sidemargin_int / mane.shrinkfactor;
	XtCallCallbacks(globals.widgets.x_bar, XtNscrollProc, cast_int_to_XtPointer(m_window_x + coord));
    }
    if (globals.widgets.y_bar != NULL) {
	int coord = (globals.page.h - mane.height) / 2;

	if (coord > resource.topmargin_int / mane.shrinkfactor)
	    coord = resource.topmargin_int / mane.shrinkfactor;
	XtCallCallbacks(globals.widgets.y_bar, XtNscrollProc, cast_int_to_XtPointer(m_window_y + coord));
    }
# endif /* MOTIF */
    if (!scrl) {
	XMapWindow(DISP, mane.win);
	/* Wait for the server to catch up---this eliminates flicker. */
	XSync(DISP, False);
    }
#ifdef USE_PANNER
    handle_x_scroll(NULL, NULL, NULL, NULL);
    handle_y_scroll(NULL, NULL, NULL, NULL);
#endif
}

/*
 *	Same as home(), except move to the bottom of the page.
 */

static void
home_bottom(wide_bool scrl)
{
    UNUSED(scrl);
    XUnmapWindow(DISP, mane.win);
#ifdef MOTIF
    {
	int value;

	if (!resource.keep_flag) {
	    value = (globals.page.w - mane.width) / 2;
	    if (value > resource.sidemargin_int / mane.shrinkfactor)
		value = resource.sidemargin_int / mane.shrinkfactor;
	    (void)set_bar_value(globals.widgets.x_bar, value, (int)(globals.page.w - mane.width));
	}

	(void)set_bar_value(globals.widgets.y_bar, (int)(globals.page.h - mane.height), (int)(globals.page.h - mane.height));
    }
#else /* MOTIF */
    get_xy();
    if (!resource.keep_flag && globals.widgets.x_bar != NULL) {
	int coord = (globals.page.w - mane.width) / 2;

	if (coord > resource.sidemargin_int / mane.shrinkfactor)
	    coord = resource.sidemargin_int / mane.shrinkfactor;
	XtCallCallbacks(globals.widgets.x_bar, XtNscrollProc, cast_int_to_XtPointer(m_window_x + coord));
    }
    if (globals.widgets.y_bar != NULL)
	XtCallCallbacks(globals.widgets.y_bar, XtNscrollProc, cast_int_to_XtPointer(m_window_y + (globals.page.h - mane.height)));
#endif /* MOTIF */
    XMapWindow(DISP, mane.win);
    /* Wait for the server to catch up---this eliminates flicker. */
    XSync(DISP, False);

#ifdef USE_PANNER
    handle_x_scroll(NULL, NULL, NULL, NULL);
    handle_y_scroll(NULL, NULL, NULL, NULL);
#endif
}


#ifndef MOTIF
static void
handle_destroy_bar(Widget w, XtPointer client_data, XtPointer call_data)
{
    UNUSED(w);
    UNUSED(call_data);
    *(Widget *) client_data = NULL;
}
#endif

static Boolean resized = False;

static void
get_geom(void)
{
    static Dimension new_clip_w, new_clip_h;
    
    static Arg arg_wh_clip[] = {
	{XtNwidth, (XtArgVal) &new_clip_w},
	{XtNheight, (XtArgVal) &new_clip_h},
    };

    static Dimension window_w, window_h;

    static Arg arg_wh[] = {
	{XtNwidth, (XtArgVal) &window_w},
	{XtNheight, (XtArgVal) &window_h},
    };

    
    int old_clip_w;

#ifdef MOTIF
    /* event handlers for Motif scrollbars have already been added
       in create_initialize_widgets(), xdvi.c */
    XtGetValues(globals.widgets.main_window, arg_wh, XtNumber(arg_wh));
#else
    XtGetValues(globals.widgets.vport_widget, arg_wh, XtNumber(arg_wh));
    /* Note:  widgets may be destroyed but not forgotten */
    if (globals.widgets.x_bar == NULL) {
	globals.widgets.x_bar = XtNameToWidget(globals.widgets.vport_widget, "horizontal");
	if (globals.widgets.x_bar != NULL) {
	    XtAddCallback(globals.widgets.x_bar, XtNdestroyCallback, handle_destroy_bar,
			  (XtPointer)&globals.widgets.x_bar);
#ifdef USE_PANNER
	    XtAddEventHandler(globals.widgets.x_bar, ButtonMotionMask | ButtonPressMask | ButtonReleaseMask,
			      False, handle_x_scroll, NULL);
#endif
	}
    }
    if (globals.widgets.y_bar == NULL) {
	globals.widgets.y_bar = XtNameToWidget(globals.widgets.vport_widget, "vertical");
	if (globals.widgets.y_bar != NULL) {
	    XtAddCallback(globals.widgets.y_bar, XtNdestroyCallback, handle_destroy_bar,
			  (XtPointer)&globals.widgets.y_bar);
#ifdef USE_PANNER
	    XtAddEventHandler(globals.widgets.y_bar, ButtonMotionMask | ButtonPressMask | ButtonReleaseMask,
			      False, handle_y_scroll, NULL);
#endif
	}
    }
#endif
    XtGetValues(globals.widgets.clip_widget, arg_wh_clip, XtNumber(arg_wh_clip));

    old_clip_w = mane.width;

    /* we need to do this because 
       sizeof(Dimension) != sizeof(int)
    */
    mane.width = new_clip_w;
    mane.height = new_clip_h;
    if (old_clip_w == 0) {
	globals.ev.flags |= EV_NEWPAGE;
    }

    if (resource.keep_flag) {
#ifndef MOTIF
	Dimension d;
	int curr_scroll;
	if ((globals.widgets.x_bar != NULL && m_x_scroll != 0) || (globals.widgets.y_bar != NULL && m_y_scroll != 0)) {
	    get_xy();
	}
#endif
	if (globals.widgets.x_bar != NULL && m_x_scroll != 0) {
#ifdef MOTIF
	    if (m_x_scroll > 0)
		(void)set_bar_value(globals.widgets.x_bar, m_x_scroll, (int)(globals.page.w - mane.width));
#else
	    XtVaGetValues(globals.widgets.clip_widget, XtNy, &d, NULL);
	    curr_scroll = d - m_window_x;
	    if (m_x_scroll > curr_scroll) {
		TRACE_GUI((stderr, "======== diff: %d", m_x_scroll - curr_scroll));
		XtCallCallbacks(globals.widgets.x_bar, XtNscrollProc, cast_int_to_XtPointer(m_x_scroll - curr_scroll));
	    }
#endif
	}
	if (globals.widgets.y_bar != NULL && m_y_scroll != 0) {
#ifdef MOTIF
	    if (m_y_scroll > 0)
		(void)set_bar_value(globals.widgets.y_bar, m_y_scroll, (int)(globals.page.h - mane.height));
#else
	    XtVaGetValues(globals.widgets.clip_widget, XtNy, &d, NULL);
	    curr_scroll = d - m_window_y;
	    if (m_y_scroll > curr_scroll) {
		TRACE_GUI((stderr, "======== diff: %d", m_y_scroll - curr_scroll));
		XtCallCallbacks(globals.widgets.y_bar, XtNscrollProc, cast_int_to_XtPointer(m_y_scroll - curr_scroll));
	    }
#endif
	}
    }
    /*     home(False); */
    resized = False;
}

/*
 * Callback routines
 */

void
handle_resize(Widget widget, XtPointer junk, XEvent *event, Boolean *cont)
{
    UNUSED(widget);
    UNUSED(junk);
    UNUSED(event);
    UNUSED(cont);
    
    resized = True;
#ifndef MOTIF
    handle_statusline_resize();
    handle_pagelist_resize();
#endif
}

void
reconfig(void)
{
    /*      Dimension x, y; */

    if (globals.dvi_file.bak_fp == NULL)
	return;
    
#ifndef MOTIF
    XtVaSetValues(globals.widgets.vport_widget, XtNresizable, (XtArgVal)False, NULL);
#endif
    TRACE_GUI((stderr, "globals.widgets.draw_widget: w %d, h %d", globals.page.w, globals.page.h));
    XtVaSetValues(globals.widgets.draw_widget, XtNwidth, (XtArgVal)globals.page.w, XtNheight, (XtArgVal)globals.page.h, NULL);
    
#ifdef TEST_SCROLLING
    /*     XtVaSetValues(globals.widgets.draw_background, XtNwidth, (XtArgVal)globals.page.w, XtNheight, (XtArgVal)globals.page.h, NULL); */
#endif
    
#ifndef MOTIF
    handle_statusline_resize(); /* without this, statusline will disappear */
    /* following not needed? */
    /*     handle_pagelist_resize(); */
#endif
    
    get_geom();

    /*     set_windowsize(&x, &y */
    /* #ifndef MOTIF */
    /* 		   , get_panel_width() */
    /* #endif */
    /* 		   ); */
    /*     XResizeWindow(DISP, XtWindow(globals.widgets.top_level), x, y); */
    /*      reconfigure_window(False, x, y, True); */
    /*      reconfig_window(); */
}


int
check_goto_page(int pageno, Boolean insert_into_pagehist)
{
    int retval;
    if (pageno < 0) {
	xdvi_bell();
/* 	statusline_info(STATUS_SHORT, "Can't go to page %d, going to first page instead", pageno + 1); */
	retval = 0;
    }
    else if (pageno >= total_pages) {
	xdvi_bell();
/* 	statusline_info(STATUS_SHORT, */
/* 			 "Can't go to page %d, going to last page (%d) instead", */
/* 			 pageno + 1, total_pages); */
	retval = total_pages - 1;
    }
    else
	retval = pageno;

    if (insert_into_pagehist) {
	page_history_insert(retval);
    }
    return retval;
}


static int
check_goto_tex_page(int pageno)
{
    /*
      Translate from TeX page number to `real' page number if
      needed. Note that pageno is a C-style 0-based number, hence we
      add 1 for the argument of pageinfo_get_index_of_number().
    */
    int retval;
    if (resource.use_tex_pages) {
	int res = pageinfo_get_index_of_number(pageno + 1);
	if (res >= 0)
	    retval = res;
	else {
	    xdvi_bell();
	    if (pageno < 1) {
/* 		statusline_info(STATUS_SHORT, "Can't go to page %d, going to first page instead", pageno + 1); */
		retval = 0;
	    }
	    else {
		/* there is no quick way to determine the last page number in the TeX page index */
/* 		statusline_info(STATUS_SHORT, */
/* 				 "Can't go to page %d, going to last page instead", */
/* 				 pageno + 1); */
		retval = total_pages - 1;
	    }
	}
	page_history_insert(retval);
    }
    else {
	retval = check_goto_page(pageno, True);
    }
    return retval;
}

/* |||
 *	Currently the event handler does not coordinate XCopyArea requests
 *	with GraphicsExpose events.  This can lead to problems if the window
 *	is partially obscured and one, for example, drags a scrollbar.
 */

/*
 *	Actions for the translation mechanism.
 */

/* if there are global prefixes, return them in res and reset them to defaults */
static Boolean
get_prefix_arg(int *res)
{
    Boolean ret;

    *res = m_sign * m_number;
    ret = m_have_arg;
    /* reset global flags */
    m_have_arg = False;
    m_number = 0;
    m_sign = 1;
    return ret;
}

Boolean
get_int_arg(String *param, Cardinal *num_params, int *res)
{
    if (*num_params > 0) {
	*res = atoi(*param);
	return True;
    }
    else {
	if (get_prefix_arg(res)) { /* prefix argument? */
	    return True;
	}
    }
    return False;
}


Boolean
toggle_arg(int arg, String *param, Cardinal *num_params)
{
    if (*num_params > 0) {
	if (**param != 't' && (atoi(*param) != 0) == arg)
	    return False;
    }
    else {
	if (m_have_arg) {
	    int	tmparg = m_number;

	    m_have_arg = False;
	    m_number = 0;
	    m_sign = 1;

	    if ((tmparg != 0) == arg)
		return False;
	}
    }
    return True;
}

Boolean
check_resource_expert(void *val, const char *param)
{
    int j = strtol(param, (char **)NULL, 10);
    /* check if the j-1th bit is set: */
    return (*(int *)val >> (j - 1)) & 1;
}

Boolean
check_papersize(void *val, const char *param)
{
    UNUSED(val);
    UNUSED(param);
    return False; /* TODO */
}

Boolean
check_paper_landscape(void *val, const char *param)
{
    UNUSED(val);
    UNUSED(param);
    return False; /* TODO */
}

/* comparison functions for the menu setting code */
Boolean
check_toggle(void *val, const char *param)
{
    Boolean *on = val;
    if (strcmp(param, "toggle") == 0) {
	return *on;
    }
    else {
	fprintf(stderr, "TODO: check_toggle: arg |%s|, curr: %d\n", param, *(int *)val);
	return *on;
    }
}

Boolean
check_int(void *val, const char *param)
{
    int i = strtol(param, (char **)NULL, 10);
    return i == *(int *)val;
}

void
Act_ruler(Widget w, XEvent *event,
	  String *params, Cardinal *num_params)
{
    UNUSED(w);
    UNUSED(params);
    UNUSED(num_params);

    MYTRACE((stderr, "ruler!\n"));
    
    if (dvi_file_changed()) {
	globals.ev.flags |= EV_RELOAD;
	return;
    }

#ifdef MOTIF
    /* see xm_menu.c for an explanation of this */
    if (event && pulldown_menu_active(event->xany.serial)) {
	return;
    }
#endif
    
    if (bg_current == NULL) {
	/*
	  HACK ALERT: we can arrive here after loading a new file via the file selector
	  for which not all fonts have been generated. In that case, dereferencing
	  bg_current would bomb. Try to recover by simply returning here.
	*/
	return;
    }

    show_ruler(event);
    globals.curr_mode = RULER_MODE_ACTIVE;
    globals.ev.flags |= EV_CURSOR;
    XFlush(DISP);
    show_distance_from_ruler(event, False);
}

void
Act_text_selection(Widget w, XEvent *event,
		   String *params, Cardinal *num_params)
{
    UNUSED(w);
    UNUSED(params);
    UNUSED(num_params);

    MYTRACE((stderr, "text selection!\n"));
    
    if (dvi_file_changed()) {
	globals.ev.flags |= EV_RELOAD;
	return;
    }

#ifdef MOTIF
    /* see xm_menu.c for an explanation of this */
    if (pulldown_menu_active(event->xany.serial)) {
	return;
    }
#endif
    
    if (bg_current == NULL) {
	/*
	  HACK ALERT: we can arrive here after loading a new file via the file selector
	  for which not all fonts have been generated. In that case, dereferencing
	  bg_current would bomb. Try to recover by simply returning here.
	*/
	return;
    }

    globals.curr_mode = TEXT_MODE_ACTIVE;
    globals.ev.flags |= EV_CURSOR;
    XFlush(DISP);
    text_selection_start(event);
    text_motion(event);
}

/****************************************************************************
 * Actions specific to the handling of the magnifier
 */

void
Act_magnifier(Widget w, XEvent *event,
	      String *params, Cardinal *num_params)
{
    UNUSED(w);

    MYTRACE((stderr, "magnifier!\n"));
    
    if (dvi_file_changed()) {
	globals.ev.flags |= EV_RELOAD;
	return;
    }

#ifdef MOTIF
    /* see xm_menu.c for an explanation of this */
    if (pulldown_menu_active(event->xany.serial)) {
	return;
    }
#endif
    
    if (bg_current == NULL) {
	/*
	  HACK ALERT: we can arrive here after loading a new file via the file selector
	  for which not all fonts have been generated. In that case, dereferencing
	  bg_current would bomb. Try to recover by simply returning here.
	*/
	return;
    }

    if (event->type != ButtonPress || mouse_release != null_mouse
	|| MAGNIFIER_ACTIVE || mane.shrinkfactor == 1 || *num_params != 1) {
	XdviBell(DISP, event->xany.window, 0);
	if (mane.shrinkfactor == 1) {
	    statusline_info(STATUS_SHORT,
			     "No magnification available at shrink factor 1");
	}
	return;
    }
    
    magnifier_move(*params, event);
}

void
Act_switch_magnifier_units(Widget w, XEvent *event,
			   String *params, Cardinal *num_params)
{
    size_t k = 0;
    static char *TeX_units[] = {
	"mm", "pt", "in", "sp", "bp", "cc", "dd", "pc", "px",
    };

    UNUSED(w);
    UNUSED(event);
    UNUSED(params);
    UNUSED(num_params);
    
    for (k = 0; k < XtNumber(TeX_units); ++k)
	if (strcmp(resource.tick_units, TeX_units[k]) == 0)
	    break;
    k++;
    if (k >= XtNumber(TeX_units))
	k = 0;
    resource.tick_units = TeX_units[k];
    if (globals.curr_mode == RULER_MODE_ACTIVE) {
	show_distance_from_ruler(event, False);
    }
    else {
	statusline_info(STATUS_SHORT, "Ruler units: %s\n", resource.tick_units);
    }
}

void
Act_href(Widget w, XEvent *event,
	 String *params, Cardinal *num_params)
{
    int x, y;
    Window dummy;

    UNUSED(w);
    UNUSED(num_params);

    (void)XTranslateCoordinates(DISP, event->xkey.window, mane.win,
				event->xkey.x, event->xkey.y, &x, &y, &dummy);

    if (params) {
	if (htex_handleref(x, y, False))
	    *params = Act_true_retval;
	else
	    *params = Act_false_retval;
    }
}

void
Act_href_newwindow(Widget w, XEvent *event,
		   String *params, Cardinal *num_params)
{
    int x, y;
    Window dummy;
    
    UNUSED(w);
    UNUSED(num_params);
    
    (void)XTranslateCoordinates(DISP, event->xkey.window, mane.win,
				event->xkey.x, event->xkey.y, &x, &y, &dummy);

    if (params) {
	if (htex_handleref(x, y, True))
	    *params = Act_true_retval;
	else
	    *params = Act_false_retval;
    }
}

static void
Act_digit(Widget w, XEvent *event,
	  String *params, Cardinal *num_params)
{
    int digit;
    /* for overflow checks */
    static const int MAXINT_QUOT = INT_MAX / 10;
    static const int MAXINT_MOD = INT_MAX % 10;


    UNUSED(w);
    UNUSED(event);

    if (*num_params != 1 || (digit = **params - '0') > 9) {
	xdvi_bell();
	return;
    }
    m_have_arg = True;

    /* don't increment m_number if it would overflow */
    if (m_number < MAXINT_QUOT || (m_number == MAXINT_QUOT && digit <= MAXINT_MOD)) {
	m_number = m_number * 10 + digit;
	if (resource.expert_mode & XPRT_SHOW_STATUSLINE) /* too distracting for stdout */
	    statusline_info(STATUS_SHORT, "numerical prefix: %s%d", m_sign < 0 ? "-" : "", m_number);
    }
    else {
	xdvi_bell();
	statusline_info(STATUS_SHORT, "numerical prefix: %s%d: no larger value possible", m_sign < 0 ? "-" : "", m_number);
    }
}

static void
Act_minus(Widget w, XEvent *event,
	  String *params, Cardinal *num_params)
{
    UNUSED(w);
    UNUSED(event);
    UNUSED(params);
    UNUSED(num_params);
    
    m_have_arg = True;
    m_sign = -m_sign;
    if (m_number > 0) {
	if (resource.expert_mode & XPRT_SHOW_STATUSLINE) /* too distracting for stdout */
	    statusline_info(STATUS_SHORT, "numerical prefix: %s%d", m_sign < 0 ? "-" : "", m_number);
    }
    else {
	if (resource.expert_mode & XPRT_SHOW_STATUSLINE) /* too distracting for stdout */
	    statusline_info(STATUS_SHORT, "numerical prefix: %s", m_sign < 0 ? "-" : "");
    }
}

static void
Act_quit(Widget w, XEvent *event,
	 String *params, Cardinal *num_params)
{
    UNUSED(w);
    UNUSED(event);
    UNUSED(params);
    UNUSED(num_params);
    
#ifndef FLAKY_SIGPOLL
    if (globals.debug & DBG_EVENT)
	puts(event_freq < 0
	     ? "SIGPOLL is working"
	     : "no SIGPOLL signals received");
#endif
    xdvi_normal_exit();
}

static void
Act_quit_confirm(Widget w, XEvent *event,
		 String *params, Cardinal *num_params)
{
    static Widget dialog = 0;
    
    UNUSED(w);
    UNUSED(event);
    UNUSED(params);
    UNUSED(num_params);

#ifndef FLAKY_SIGPOLL
    if (globals.debug & DBG_EVENT)
	puts(event_freq < 0
	     ? "SIGPOLL is working"
	     : "no SIGPOLL signals received");
#endif

    /* already a quit dialog open? */
    if (dialog != 0) {
	/* HACK ALERT: use brute force, since tests for XtIsRealized()
	   or XtIsMapped() don't work?? Grabbing the server apparently
	   has problems with Xaw, and it's not a nice solution anyway ... */
	if (kill_message_window(dialog))
	    xdvi_bell();
    }

    dialog = choice_dialog_sized(globals.widgets.top_level,
				 MSG_QUESTION,
				 SIZE_SMALL,
				 NULL,
#ifndef MOTIF
				 "quit",
#endif
				 NULL, NULL, /* no pre_callbacks */
				 "OK", xdvi_normal_exit_cb, NULL,
				 "Cancel", NULL, NULL,
				 "Really quit xdvi?");
}

static void
Act_load_url(Widget w, XEvent *event, String *params, Cardinal *num_params)
{
    UNUSED(w);
    UNUSED(event);
    UNUSED(num_params);
    launch_browser(*params);
}

static void
Act_print(Widget w, XEvent *event, String *params, Cardinal *num_params)
{
    /* static so that we can pass them around to callbacks etc. */
    static struct save_or_print_info info;
    static struct select_pages_info pinfo = {
	0, 0, check_marked, { 0, NULL }, NO_ERROR
    };
    static struct file_info finfo = {
	NULL, NULL,
	NULL, NULL,
	NULL
    };
    static Boolean first_time = True;

    UNUSED(w);
    UNUSED(event);
    UNUSED(params);
    UNUSED(num_params);
    
    if (first_time) {
	info.shell = info.printlog = NULL;
	info.act = FILE_PRINT;
	info.pinfo = &pinfo;
	info.finfo = &finfo;
	info.callbacks = NULL;
	first_time = False;
    }
    save_or_print_callback(&info);
}

static void
Act_save(Widget w, XEvent *event, String *params, Cardinal *num_params)
{
    /* static so that we can pass them around to callbacks etc. */
    static struct save_or_print_info info;
    static struct select_pages_info pinfo = {
	0, 0, check_marked, { 0, NULL }, NO_ERROR
    };
    static struct file_info finfo = {
	NULL, NULL,
	NULL, NULL,
	NULL
    };
    static Boolean first_time = True;

    UNUSED(w);
    UNUSED(event);
    UNUSED(params);
    UNUSED(num_params);
    
    if (first_time) {
	info.shell = info.printlog = NULL;
	info.act = FILE_SAVE;
	info.pinfo = &pinfo;
	info.finfo = &finfo;
	info.callbacks = NULL;
	first_time = False;
    }
    save_or_print_callback(&info);
}

static void
Act_find(Widget w, XEvent *event, String *params, Cardinal *num_params)
{
    UNUSED(w);
    UNUSED(event);
    UNUSED(params);
    UNUSED(num_params);
    dvi_find_string(NULL, False);
}

static void
Act_incremental_find(Widget w, XEvent *event, String *params, Cardinal *num_params)
{
    UNUSED(w);
    UNUSED(event);
    UNUSED(params);
    UNUSED(num_params);
    isearch_start();
}

static void
Act_find_next(Widget w, XEvent *event, String *params, Cardinal *num_params)
{
    UNUSED(w);
    UNUSED(event);
    UNUSED(params);
    UNUSED(num_params);
    dvi_find_string(NULL, True);
}

static void
Act_help(Widget w, XEvent *event,
	 String *params, Cardinal *num_params)
{
    char *arg = NULL;

    UNUSED(w);
    UNUSED(event);
    
    if (*num_params > 0)
	arg = *params;
    
    show_help(globals.widgets.top_level, arg);
}

static home_proc home_action = NULL;

void
goto_page(int new_page, home_proc proc, Boolean force)
{
    /* SU: added clearing the window here, otherwise old window content
       will survive switching pages as long as globals.pausing.flag is active
       (i.e. when accidentally changing page instead of unpausing)
    */
    if (globals.pausing.flag) {
	XClearWindow(DISP, mane.win);
    }
	    
    if (globals.dvi_file.bak_fp == NULL) {
	current_page = new_page; /* so that xdvi gets the page change */
	return;
    }
    
    xdvi_assert(XDVI_VERSION_INFO, __FILE__, __LINE__,
		new_page >= 0 && new_page < total_pages,
		"%d >= 0 && %d < %d", new_page, new_page, total_pages);
    
    if (current_page != new_page || force) {
	globals.cursor.flags &= ~CURSOR_LINK; /* disable link cursor if needed */

	current_page = new_page;
	home_action = proc;
	globals.warn_spec_now = resource.warn_spec;
	/* this seems unneccessary */
	/* 	if (!resource.keep_flag) */
	/* 	    home(False); */
#if defined(MOTIF) && HAVE_XPM
	tb_check_navigation_sensitivity(current_page);
	/*  	page_history_update_toolbar_navigation(); */
#endif
	maybe_scroll_pagelist(current_page, False);

	if (globals.pausing.num_save)
	    globals.pausing.num = globals.pausing.num_save[new_page];
	else
	    globals.pausing.num = 0;
	/* Control-L (and changing the page) clears this box */
	globals.src.fwd_box_page = -1;

	globals.ev.flags |= EV_NEWPAGE;
	XFlush(DISP);       
    }
}


static void
Act_goto_page(Widget w, XEvent *event,
	      String *params, Cardinal *num_params)
{
    int arg;
    Boolean clear_statusline = False;

    UNUSED(w);
    UNUSED(event);

    warn_num_params("goto-page()", params, *num_params, 1);
    
    if (*num_params > 0) {
	if (**params == 'e') {
	    arg = total_pages - 1;
	}
	else
	    arg = atoi(*params) - globals.pageno_correct;
    }
    else {
	if (get_prefix_arg(&arg)) {
	    clear_statusline = True;
	    arg -= globals.pageno_correct;
	}
	else {
	    arg = total_pages - 1;
	}
    }
    if (arg == total_pages - 1) {
	/* with TeX numbers, total_pages - 1 might not be found in the page list,
	   so don't use check_goto_tex_page() in this case */
	goto_page(check_goto_page(arg, True), resource.keep_flag ? NULL : home, False);
    }
    else {
	goto_page(check_goto_tex_page(arg), resource.keep_flag ? NULL : home, False);
    }
    search_signal_page_changed();
    if (clear_statusline)
	statusline_clear();
    statusline_erase("Page history:");
}

void
Act_recent_files(Widget w, XEvent *event,
		 String *params, Cardinal *num_params)
{
    UNUSED(w);
    UNUSED(event);
    UNUSED(params);
    UNUSED(num_params);
}

void
Act_use_tex_pages(Widget w, XEvent *event,
		  String *params, Cardinal *num_params)
{
    UNUSED(w);
    UNUSED(event);

    
    if (*num_params == 0) {
	if (m_have_arg) {
	    resource.use_tex_pages = (m_number != 0);
	    m_have_arg = False;
	    m_number = 0;
	    m_sign = 1;
	}
	else {
	    resource.use_tex_pages = !resource.use_tex_pages;
	}
    }
    else
	resource.use_tex_pages = (**params == 't' ? !resource.use_tex_pages : atoi(*params));
    
    if (resource.use_tex_pages) {
	statusline_info(STATUS_SHORT, "Using TeX page numbers for \"g\", goto-page()");
    }
    else {
	statusline_info(STATUS_SHORT, "Using physical page numbers for \"g\", goto-page()");
    }

    store_preference(NULL, "useTeXPages", "%s", resource.use_tex_pages ? "True" : "False");
    
    set_menu(&resource.use_tex_pages, Act_use_tex_pages, check_toggle);

    refresh_pagelist(total_pages, current_page);
}

void
Act_forward_page(Widget w, XEvent *event,
		 String *params, Cardinal *num_params)
{
    int arg;
    Boolean clear_statusline = False;

    UNUSED(w);
    UNUSED(event);

    if (!get_int_arg(params, num_params, &arg))
	arg = 1;
    else
	clear_statusline = True;
    
    arg += current_page;

    if (arg == current_page) { /* zero argument -> redraw page */
	globals.src.fwd_box_page = -1;
	search_reset_info();
	globals.ev.flags |= EV_NEWPAGE;
	XFlush(DISP);
	statusline_info(STATUS_SHORT, "Page redrawn.");
	return;
    }
    else if (current_page >= total_pages - 1) {
	xdvi_bell();
/* 	statusline_info(STATUS_SHORT, "Last page of DVI file"); */
	return;
    }
    
    goto_page(check_goto_page(arg, True), resource.keep_flag ? NULL : home, False);
    if (clear_statusline)
	statusline_clear();
    statusline_erase("Page history:");
    search_signal_page_changed();
}

void
Act_back_page(Widget w, XEvent *event,
	      String *params, Cardinal *num_params)
{
    int arg;
    Boolean clear_statusline = False;
    
    UNUSED(w);
    UNUSED(event);

    if (!get_int_arg(params, num_params, &arg))
	arg = 1;
    else
	clear_statusline = True;
    
    arg = current_page - arg;

    if (current_page == 0) {
	xdvi_bell();
/* 	statusline_info(STATUS_SHORT, "First page of DVI file"); */
	return;
    }

    goto_page(check_goto_page(arg, True), resource.keep_flag ? NULL : home, False);
    if (clear_statusline)
	statusline_clear();
    statusline_erase("Page history:");
    search_signal_page_changed();
}

static void
Act_declare_page_number(Widget w, XEvent *event,
			String *params, Cardinal *num_params)
{
    int arg;

    UNUSED(w);
    UNUSED(event);
    
    if (globals.curr_mode == RULER_MODE_ACTIVE) {
	show_distance_from_ruler(event, True);
	return;
    }
    
    if (!get_int_arg(params, num_params, &arg)) {
	arg = 0;
    }
    globals.pageno_correct = arg - current_page;
    statusline_info(STATUS_SHORT, "Current page number set to %d", arg);
}

static void
Act_toggle_mark(Widget w, XEvent *event,
		String *params, Cardinal *num_params)
{
    int arg;

    UNUSED(w);
    UNUSED(event);

    if (globals.dvi_file.bak_fp == NULL)
	return;
    
    if (
#ifdef MOTIF
	(resource.expert_mode & XPRT_SHOW_PAGELIST) == 0
#else
	(resource.expert_mode & XPRT_SHOW_BUTTONS) == 0
#endif
	) {
	xdvi_bell();
	statusline_info(STATUS_SHORT,
			 "Expert mode: Page list not available.");
	return;
    }
    
    if (*num_params > 0) {
	arg = atoi(*params);
	if (arg < -1 || arg > 2) {
	    xdvi_bell();
	    statusline_error(STATUS_SHORT,
			     "Possible arguments: none (toggle current), "
			     "-1 (mark all), 0 (unmark all), 1 (toggle odd), 2 (toggle even)");
	}
	list_toggle_marks(arg);
    }
    else if (get_prefix_arg(&arg)) {
	list_toggle_marks(arg);
	statusline_clear();
    }
    else {
	arg = current_page;
	list_toggle_current(arg);
    }
}

static void
Act_home(Widget w, XEvent *event,
	 String *params, Cardinal *num_params)
{
    UNUSED(w);
    UNUSED(event);
    UNUSED(params);
    UNUSED(num_params);

    home(True);
}

static void
Act_home_or_top(Widget w, XEvent *event,
		String *params, Cardinal *num_params)
{
    UNUSED(w);
    UNUSED(event);
    UNUSED(params);
    UNUSED(num_params);
    
    if (resource.keep_flag) {
	String args[1];
	args[0] = "10"; /* should be large enough ... */
	XtCallActionProc(globals.widgets.top_level, "up", NULL, args, 1);
    }
    else {
	home(True);
    }
}

static void
Act_end_or_bottom(Widget w, XEvent *event,
		  String *params, Cardinal *num_params)
{
    String args[1];

    UNUSED(w);
    UNUSED(event);
    UNUSED(params);
    UNUSED(num_params);

    args[0] = "10"; /* should be large enough ... */
    XtCallActionProc(globals.widgets.top_level, "down", NULL, args, 1);
    
    if (!resource.keep_flag
#ifndef MOTIF
	&& globals.widgets.x_bar != NULL
#endif
	) {
	XtCallActionProc(globals.widgets.top_level, "right", NULL, args, 1);
    }
}

static void
Act_center(Widget w, XEvent *event,
	   String *params, Cardinal *num_params)
{
    int x, y;

    UNUSED(w);
    UNUSED(params);
    UNUSED(num_params);

#ifdef MOTIF
    /* The clip widget gives a more exact value. */
    x = event->xkey.x - mane.width / 2;
    y = event->xkey.y - mane.height / 2;

    x = set_bar_value(globals.widgets.x_bar, x, (int)(globals.page.w - mane.width));
    y = set_bar_value(globals.widgets.y_bar, y, (int)(globals.page.h - mane.height));
    get_xy();
    XWarpPointer(DISP, None, None, 0, 0, 0, 0, -x - m_window_x, -y - m_window_y);
#else

    if (event == NULL)
	return;	/* button actions do not provide events */

    x = event->xkey.x - mane.width / 2;
    y = event->xkey.y - mane.height / 2;
    /* The clip widget gives a more exact value. */
    if (globals.widgets.x_bar != NULL)
	XtCallCallbacks(globals.widgets.x_bar, XtNscrollProc, cast_int_to_XtPointer(x));
    if (globals.widgets.y_bar != NULL)
	XtCallCallbacks(globals.widgets.y_bar, XtNscrollProc, cast_int_to_XtPointer(y));
    XWarpPointer(DISP, None, None, 0, 0, 0, 0, -x, -y);
#endif
#ifdef USE_PANNER
    handle_x_scroll(NULL, NULL, NULL, NULL);
    handle_y_scroll(NULL, NULL, NULL, NULL);
#endif
}

void
Act_ruler_snap_origin(Widget w, XEvent *event,
		      String *params, Cardinal *num_params)
{
    UNUSED(w);
    UNUSED(params);
    UNUSED(num_params);

    if (globals.curr_mode == RULER_MODE_ACTIVE)
	ruler_snap_origin(event);
}

#if 0
void
Act_set_papersize(Widget w, XEvent *event,
		  String *params, Cardinal *num_params)
{
    UNUSED(w);
    UNUSED(event);
    UNUSED(num_params);

    fprintf(stderr, "set_paperformat: %s\n", *params);
    resource.paper = *params;
    set_menu((void *)resource.paper, Act_set_papersize, check_papersize);
}

void
Act_set_paper_landscape(Widget w, XEvent *event,
			String *params, Cardinal *num_params)
{
    UNUSED(w);
    UNUSED(event);
    UNUSED(num_params);

    resource.paper_landscape = !resource.paper_landscape;
    fprintf(stderr, "set_paper_landscape: %s\n", *params);

    set_menu((char *)resource.paper,  Act_set_paper_landscape, check_paper_landscape);
}
#endif /* 0 */


void
Act_set_keep_flag(Widget w, XEvent *event,
		  String *params, Cardinal *num_params)
{
    UNUSED(event);
    UNUSED(w);
    
    if (*num_params == 0) {
	if (m_have_arg) {
	    resource.keep_flag = (m_number != 0);
	    m_have_arg = False;
	    m_number = 0;
	    m_sign = 1;
	}
	else {
	    resource.keep_flag = !resource.keep_flag;
	}
    }
    else
	resource.keep_flag = (**params == 't'
			      ? !resource.keep_flag : atoi(*params));
    if (resource.keep_flag) {
	statusline_info(STATUS_SHORT, "Keeping position when switching pages");
    }
    else {
	statusline_info(STATUS_SHORT,
			 "Not keeping position when switching pages");
    }

    store_preference(NULL, "keepPosition", "%s", resource.keep_flag ? "True" : "False");
    
    set_menu(&resource.keep_flag, Act_set_keep_flag, check_toggle);
}

static void
Act_left(Widget w, XEvent *event,
	 String *params, Cardinal *num_params)
{
    UNUSED(w);
    UNUSED(event);

    do_autoscroll = False;
    
    warn_num_params("left()", params, *num_params, 1);

#ifdef MOTIF
    get_xy();
#ifdef TEST_SCROLLING
    fprintf(stderr, "left for %p\n", (void *)w);
    fprintf(stderr, "window x: %d, y: %d\n", m_window_x, m_window_y);
#endif

    (void)set_bar_value(globals.widgets.x_bar, (*num_params == 0 ? (-2 * (int)mane.width / 3)
						: (int)(-my_atof(*params) * mane.width)) - m_window_x,
			(int)(globals.page.w - mane.width));
#else
    if (globals.widgets.x_bar != NULL)
	XtCallCallbacks(globals.widgets.x_bar, XtNscrollProc,
			cast_int_to_XtPointer(*num_params == 0 ? (-2 * (int)mane.width / 3)
					      : (int)(-my_atof(*params) * mane.width)));
    else {
	xdvi_bell();
/* 	statusline_info(STATUS_SHORT, "Horizontal scrolling not possible"); */
    }
#endif
#ifdef USE_PANNER
    handle_x_scroll(NULL, NULL, NULL, NULL);
#endif
}

static void
Act_right(Widget w, XEvent *event,
	  String *params, Cardinal *num_params)
{
    UNUSED(w);
    UNUSED(event);

    do_autoscroll = False;
    
    warn_num_params("right()", params, *num_params, 1);

#ifdef MOTIF
    get_xy();
#ifdef TEST_SCROLLING
    fprintf(stderr, "right for %p\n", (void *)w);
    fprintf(stderr, "window x: %d, y: %d\n", m_window_x, m_window_y);
#endif

    (void)set_bar_value(globals.widgets.x_bar, (*num_params == 0 ? (2 * (int)mane.width / 3)
						: (int)(my_atof(*params) * mane.width)) - m_window_x,
			(int)(globals.page.w - mane.width));
#else
    if (globals.widgets.x_bar != NULL)
	XtCallCallbacks(globals.widgets.x_bar, XtNscrollProc,
			cast_int_to_XtPointer(*num_params == 0 ? (2 * (int)mane.width / 3)
					      : (int)(my_atof(*params) * mane.width)));
    else {
	xdvi_bell();
/* 	statusline_info(STATUS_SHORT, "Horizontal scrolling not possible"); */
    }
#endif
#ifdef USE_PANNER
    handle_x_scroll(NULL, NULL, NULL, NULL);
#endif
}

static void
Act_up(Widget w, XEvent *event,
       String *params, Cardinal *num_params)
{
    UNUSED(w);
    UNUSED(event);

    do_autoscroll = False;
    
    warn_num_params("up()", params, *num_params, 1);
    
#ifdef MOTIF
#ifdef TEST_SCROLLING
    fprintf(stderr, "up for %p\n", (void *)w);
#endif
    get_xy();
    (void)set_bar_value(globals.widgets.y_bar, (*num_params == 0 ? (-2 * (int)mane.height / 3)
						: (int)(-my_atof(*params) * mane.height)) - m_window_y,
			(int)(globals.page.h - mane.height));
#else
    if (globals.widgets.y_bar != NULL)
	XtCallCallbacks(globals.widgets.y_bar, XtNscrollProc,
			cast_int_to_XtPointer(*num_params == 0 ? (-2 * (int)mane.height / 3)
					      : (int)(-my_atof(*params) * mane.height)));
    else {
	xdvi_bell();
/* 	statusline_info(STATUS_SHORT, "Vertical scrolling not possible"); */
    }
#endif
#ifdef USE_PANNER
    handle_y_scroll(NULL, NULL, NULL, NULL);
#endif
}

static void
Act_down(Widget w, XEvent *event,
	 String *params, Cardinal *num_params)
{
    UNUSED(w);
    UNUSED(event);

    do_autoscroll = False;

    warn_num_params("down()", params, *num_params, 1);

#ifdef MOTIF
#ifdef TEST_SCROLLING
    fprintf(stderr, "down for %p\n", (void *)w);
#endif
    get_xy();
    (void)set_bar_value(globals.widgets.y_bar, (*num_params == 0 ? (2 * (int)mane.height / 3)
						: (int)(my_atof(*params) * mane.height)) - m_window_y,
			(int)(globals.page.h - mane.height));
#else
    if (globals.widgets.y_bar != NULL)
	XtCallCallbacks(globals.widgets.y_bar, XtNscrollProc,
			cast_int_to_XtPointer(*num_params == 0 ? (2 * (int)mane.height / 3)
					      : (int)(my_atof(*params) * mane.height)));
    else {
	xdvi_bell();
/* 	statusline_info(STATUS_SHORT, "Vertical scrolling not possible"); */
    }
#endif
#ifdef USE_PANNER
    handle_y_scroll(NULL, NULL, NULL, NULL);
#endif
}

static void
Act_down_or_next(Widget w, XEvent *event,
		 String *params, Cardinal *num_params)
{
    UNUSED(w);
    UNUSED(event);

    do_autoscroll = False;

    warn_num_params("down-or-next()", params, *num_params, 1);

#ifdef TEST_SCROLLING
    fprintf(stderr, "down-or-next for %p\n", (void *)w);
#endif

#ifdef MOTIF
    get_xy();
    if (m_window_y > (int)mane.height - (int)globals.page.h) {
	(void)set_bar_value(globals.widgets.y_bar, (*num_params == 0 ? (2 * (int)mane.height / 3)
						    : (int)(my_atof(*params) * mane.height)) -
			    m_window_y, (int)(globals.page.h - mane.height));
	return;
    }
#else
    if (globals.widgets.y_bar != NULL) {
	get_xy();
	if (m_window_y > (int)mane.height - (int)globals.page.h) {
	    XtCallCallbacks(globals.widgets.y_bar, XtNscrollProc,
			    cast_int_to_XtPointer(*num_params ==
						  0 ? (2 * (int)mane.height / 3)
						  : (int)(my_atof(*params) * mane.height)));
	    return;
	}
    }
#endif

    if (current_page < total_pages - 1) {
	goto_page(current_page + 1, home, False);
	search_signal_page_changed();
    }
    else {
	xdvi_bell();
/* 	statusline_info(STATUS_SHORT, "At bottom of last page"); */
    }
#ifdef USE_PANNER
    handle_y_scroll(NULL, NULL, NULL, NULL);
#endif
    statusline_erase("Page history:");
}


static void
Act_up_or_previous(Widget w, XEvent *event,
		   String *params, Cardinal *num_params)
{
    UNUSED(w);
    UNUSED(event);

    do_autoscroll = False;

    warn_num_params("up-or-previous()", params, *num_params, 1);

#ifdef TEST_SCROLLING
    fprintf(stderr, "up-or-previous for %p\n", (void *)w);
#endif
#ifdef MOTIF
    get_xy();
    if (m_window_y < 0) {
	(void)set_bar_value(globals.widgets.y_bar,
			    (*num_params == 0 ? (-2 * (int)mane.height / 3)
			     : (int)(-my_atof(*params) * mane.height)) - m_window_y,
			    (int)(globals.page.h - mane.height));
	return;
    }
#else
    if (globals.widgets.y_bar != NULL) {
	get_xy();
	if (m_window_y < 0) {
	    XtCallCallbacks(globals.widgets.y_bar, XtNscrollProc,
			    cast_int_to_XtPointer(*num_params ==
						  0 ? (-2 * (int)mane.height / 3)
						  : (int)(-my_atof(*params) * mane.height)));
	    return;
	}
    }
#endif

    if (current_page > 0) {
	goto_page(current_page - 1, home_bottom, False);
	search_signal_page_changed();
    }
    else {
	xdvi_bell();
/* 	statusline_info(STATUS_SHORT, "At top of first page"); */
    }
    statusline_erase("Page history:");
#ifdef USE_PANNER
    handle_y_scroll(NULL, NULL, NULL, NULL);
#endif
}


static void
Act_set_margins(Widget w, XEvent *event,
		String *params, Cardinal *num_params)
{
    Window dummy;

    UNUSED(w);
    UNUSED(params);
    UNUSED(num_params);

#ifndef MOTIF
    if (event == NULL)
	return;	/* button actions do not provide events */
#endif

    (void)XTranslateCoordinates(DISP, event->xkey.window, mane.win,
				event->xkey.x, event->xkey.y, &resource.sidemargin_int, &resource.topmargin_int, &dummy);	/* throw away last argument */
    statusline_info(STATUS_SHORT, "Margins set to cursor position (%d, %d)", resource.sidemargin_int, resource.topmargin_int);
    resource.sidemargin_int *= mane.shrinkfactor;
    resource.topmargin_int *= mane.shrinkfactor;
}

static void
Act_show_display_attributes(Widget w, XEvent *event,
			    String *params, Cardinal *num_params)
{
    UNUSED(w);
    UNUSED(event);
    UNUSED(params);
    UNUSED(num_params);

    statusline_error(STATUS_SHORT, "Unit = %d, bitord = %d, byteord = %d",
		     BitmapUnit(DISP), BitmapBitOrder(DISP),
		     ImageByteOrder(DISP));
}

int
shrink_to_fit(void)
{
    int value1;
    int value2;
    static Dimension window_w;
#ifndef MOTIF
    static Dimension window_h;
#endif
    
    static Arg arg_wh[] = {
	{XtNwidth, (XtArgVal) &window_w}
#ifndef MOTIF
	, {XtNheight, (XtArgVal) &window_h}
#endif
    };

    
#ifdef MOTIF
    XtGetValues(globals.widgets.main_window, arg_wh, XtNumber(arg_wh));
#else
    XtGetValues(globals.widgets.vport_widget, arg_wh, XtNumber(arg_wh));
#endif
    
    value1 = ROUNDUP(globals.page.unshrunk_w, window_w - 2);

#ifdef  MOTIF
    {	/* account for menubar */
	static Dimension new_h;

	/* get rid of scrollbar */
	XtVaSetValues(globals.widgets.draw_widget, XtNwidth, (XtArgVal)1, XtNheight, (XtArgVal)1, NULL);
	XtVaGetValues(globals.widgets.clip_widget, XtNheight, &new_h, NULL);
	value2 = ROUNDUP(globals.page.unshrunk_h, new_h - 2);
    }
#else
    /* FIXME: value seems too small here! */
    value2 = ROUNDUP(globals.page.unshrunk_h, window_h - 2);
#endif

    return value1 > value2 ? value1 : value2;
}

void
do_set_shrinkfactor(int arg, Boolean set_resource)
{
    static int shrink_bak = -1;
    
    /* We don't store this as preference since it may affect initial window geometry. */
    /*  store_preference(NULL, "shrinkFactor", "%d", arg); */
    
    if (globals.curr_mode == TEXT_MODE_ACTIVE) {
	text_change_region(TEXT_SEL_CLEAR, NULL);
    }
    
    mane.shrinkfactor = arg;
    if (set_resource)
	resource.shrinkfactor = mane.shrinkfactor;
    
    set_menu(&arg, Act_set_shrink_factor, check_int);
#if HAVE_XMP
    tb_set_zoom_sensitivity(arg != 1);
#endif

    if (arg != 1 && arg != shrink_bak) {
	shrink_bak = arg;
#if GREY
#if COLOR
	if (resource.use_grey)
	    fg_active = NULL;
#else 
	if (resource.use_grey)
	    init_pix();
#endif
#endif /* GREY */
	reset_fonts();
    }
    init_page();
    reconfig();

    htex_resize_page();

    /* this seems unneccessary */
    /*     if (!resource.keep_flag) */
    /* 	home(False); */

    globals.ev.flags |= EV_NEWPAGE;
    XFlush(DISP);
}


/* A 'meta' action that is bound to mouse buttons and invokes actions depending on current mouse mode */
void
Act_mouse_modes(Widget w, XEvent *event,
		String *params, Cardinal *num_params)
{
    int i;
    size_t mode_idx = 0; /* default: one action for all modes */
    struct xdvi_action *my_action = NULL, *ap = NULL;

    if (*num_params > 1) { /* different actions */
	if (resource.mouse_mode >= *num_params) {
	    size_t k;
	    XDVI_WARNING((stderr, "Only %d parameters in X resource 'mouse-modes', should be %d",
			  *num_params, resource.mouse_mode + 1));
	    for (k = 0; k < *num_params; k++) {
		XDVI_WARNING((stderr, "Param %d: `%s'", (int)(k + 1), params[k]));
	    }
	    return;
	}
	mode_idx = resource.mouse_mode;
    }

    cached_compile_action(params[mode_idx], &my_action);
    for (i = 0, ap = my_action; ap; ap = ap->next, i++) {
	String *args;
	String retval;

	TRACE_EVENTS((stderr, "Action %d for mode %lu: '%s', %d args |%s| maps to proc |%p|",
		      i, (unsigned long) mode_idx, ap->command, ap->num_params,
		      ap->num_params > 0 ? ap->params[0] : "(none)",
		      ap->proc));

	/* copy args because the action proc may modify them (see below) */
	if (ap->num_params) {
	    args = xmalloc(ap->num_params * sizeof (String));
	    memcpy(args, ap->params, ap->num_params * sizeof (String));
	}
	else args = xmalloc(sizeof (String));

	/* now call the action proc directly */
	XtCallActionProc(w, ap->command, event, args, ap->num_params);

	retval = args[0];
	free(args);
	if (retval == Act_true_retval) { /* we may compare the pointers here */
	    /* Special case: Only invoke first action, not subsequent ones;
	     * e.g. if mouse is over a link and action is Act_href or
	     * Act_href_newwindow (currently the only cases where this is used).
	     */
	    break;
	}
    }
}


void
Act_set_shrink_factor(Widget w, XEvent *event,
		      String *params, Cardinal *num_params)
{
    int arg;
    /* Set a limit to the shrink factors allowed; otherwise xdvi may
       consume a lot of memory (even run out of memory in malloc) in
       the allocation for the subpixel lookup table pixeltbl,
       dvi-draw.c. The `correct' solution would be to recognize that at a
       certain shrink, the only pixel for a character will be always
       set to `on' anyway, so that we could do without the lookup table for
       very large shrink factors ... */
    static const int SHRINK_MAX = 999;
     
    UNUSED(w);
    UNUSED(event);

    warn_num_params("set-shrink-factor()", params, *num_params, 1);

    if (*num_params > 0) {
	if (**params == 'a')
	    arg = shrink_to_fit();
	else if (**params == '-')
	    arg = mane.shrinkfactor + 1;
	else if (**params == '+')
	    arg = mane.shrinkfactor - 1;
	else
	    arg = atoi(*params);
    }
    else if (!get_prefix_arg(&arg)) {
	arg = shrink_to_fit();
    }

    if (arg <= 0) {
	xdvi_bell();
/* 	statusline_info(STATUS_SHORT, */
/* 			 "No more enlarging possible"); */
	return;
    }
    
    if (arg > SHRINK_MAX) {
	xdvi_bell();
	statusline_info(STATUS_SHORT,
			 "Shrink factor %d too large (maximum: %d)", arg, SHRINK_MAX);
	return;
    }
    
    /* SU: added clearing the window here, else old window content
       will survive changing shrink
    */
    if (globals.pausing.flag) {
	XClearWindow(DISP, mane.win);
    }
    
    statusline_info(STATUS_SHORT, "shrink factor: %d", arg);
#if 0
    /* Uncommented this, otherwise selecting `shrink to fit' with same value as
       current mane.shrinkfactor will clear the canvas (only fixed by changing shrink
       factor again). That bug is also present in xdvik-22.48-alpha3. */
    if (arg == mane.shrinkfactor) {
	return;
    }
#endif

    do_set_shrinkfactor(arg, True);

#if MOTIF    
    /* note: mustn't do that inside do_set_shrinkfactor(),
       or we get into an endless loop! */
    update_preferences_shrink();
#endif
}

void
Act_shrink_to_dpi(Widget w, XEvent *event,
		  String *params, Cardinal *num_params)
{
    int arg;
    
    UNUSED(w);
    UNUSED(event);

    warn_num_params("shrink-to-dpi()", params, *num_params, 1);
    
    if (!get_int_arg(params, num_params, &arg))
	arg = 0;
    
    if (arg > 0)
	arg = (double)resource.pixels_per_inch / arg + 0.5;

    if (arg <= 0) {
	xdvi_bell();
	statusline_info(STATUS_SHORT,
			 "shrink-to-dpi requires a positive argument");
	return;
    }

    /* like elsewhere */
    if (globals.pausing.flag) {
	XClearWindow(DISP, mane.win);
    }
#if 0
    /* Uncommented this, otherwise selecting `shrink to fit' with same value as
       current mane.shrinkfactor will clear the canvas (only fixed by changing shrink
       factor again). That bug is also present in xdvik-22.48-alpha3. */
    if (arg == mane.shrinkfactor)
	return;
#endif
    do_set_shrinkfactor(arg, True);

#if MOTIF    
    /* note: mustn't do that inside do_set_shrinkfactor(),
       or we get into an endless loop! */
    update_preferences_shrink();
#endif
}

void
do_set_density(double newgamma, Boolean force, Boolean update_resource)
{
    
    /* like elsewhere */
    if (globals.pausing.flag) {
	XClearWindow(DISP, mane.win);
    }

#ifdef GREY
    if (resource.use_grey) {
	if (newgamma == resource.gamma && !force) {
	    statusline_info(STATUS_SHORT, "density value: %.3f", newgamma);
	    return;
	}
	resource.gamma = newgamma;
	if (update_resource)
	    globals.curr_gamma = resource.gamma;
	
#if COLOR
	fg_active = NULL;
	reset_colors();
#else
	init_pix();
	if (G_visual->class != TrueColor) {
	    return;
	}
	reset_fonts();
#endif /* COLOR */
	statusline_info(STATUS_SHORT, "density value: %.3f", newgamma);
    } else
#endif /* GREY */
    {
	reset_fonts();
	if (mane.shrinkfactor == 1) {
	    statusline_info(STATUS_SHORT,
			     "set-density ignored at magnification 1");
	    return;
	}
	statusline_info(STATUS_SHORT, "density value: %.3f", newgamma);
    }

    store_preference(NULL, "gamma", "%f", resource.gamma);

    globals.ev.flags |= EV_NEWPAGE;
    XFlush(DISP);
}

static void
Act_set_density(Widget w, XEvent *event,
		String *params, Cardinal *num_params)
{
    int arg;

    UNUSED(w);
    UNUSED(event);

    warn_num_params("set-density()", params, *num_params, 1);
    
    if (!get_int_arg(params, num_params, &arg)) {
	xdvi_bell();
	return;
    }
    /*     if (arg < 0) { */
    /* 	XBell(DISP, 0); */
    /* 	statusline_info(STATUS_SHORT, */
    /* 			 "set-density requires a positive value"); */
    /* 	return; */
    /*     } */
    do_set_density(arg != 0 ? arg / 100.0 : 1.0, False, True);
#if MOTIF
    update_preferences_darkness();
#else
    store_preference(NULL, "gamma", "%f", resource.gamma);
#endif
}


static void
Act_change_density(Widget w, XEvent *event,
		   String *params, Cardinal *num_params)
{
    int arg;
    double diff;
    double oldgamma = resource.gamma;
    UNUSED(w);
    UNUSED(event);

    warn_num_params("change-density()", params, *num_params, 1);
    
    if (!get_int_arg(params, num_params, &arg)) {
	xdvi_bell();
	return;
    }
    diff = oldgamma / arg;
    if (oldgamma + diff <= 0.0)
	oldgamma = 0.0;
    else
	oldgamma += diff;
    do_set_density(oldgamma, False, True);
#if MOTIF
    update_preferences_darkness();
#endif
}

static void h_restore_scroll_position(int orig_pos,
#ifndef MOTIF
				      Dimension *get_pos,
#endif
				      Widget scrollbar)
{
    if (resource.keep_flag && orig_pos != 0 && scrollbar != NULL) {
#ifdef MOTIF
	if (!resource.fullscreen) {
	    /* only scroll back in non-fullscreen mode */
	    (void)set_bar_value(scrollbar, orig_pos, INT_MAX);
	}
#else
	int curr_pos;
	get_xy();
	XtVaGetValues(globals.widgets.clip_widget, XtNx, get_pos, NULL);
	curr_pos = -((*get_pos) - m_window_x);
	if (curr_pos - orig_pos > 0) {
	    XtCallCallbacks(scrollbar, XtNscrollProc, cast_int_to_XtPointer(curr_pos - orig_pos));
	}
#endif /* MOTIF */
    }
}

static void
Act_fullscreen(Widget w, XEvent *event,
	       String *params, Cardinal *num_params)
{
    Dimension main_win_w, main_win_h;
    static int orig_x = 0, orig_y = 0;
#ifndef MOTIF
    static Dimension get_x, get_y;
#endif
    static Dimension old_w = 0, old_h = 0;
    Dimension panel_width = 0;
#if 0
    static Dimension w_old = 0, h_old = 0;
#endif

    UNUSED(w);
    UNUSED(event);
    
    if (!toggle_arg(resource.fullscreen, params, num_params)) {
	return;
    }
    
    resource.fullscreen = !resource.fullscreen;
    
#ifndef MOTIF
    panel_width = get_panel_width();
#endif
    
    if (resource.fullscreen) {
	/* when toggling to fullscreen, save current scrollbar values
	   and window geometry */
	XtVaGetValues(globals.widgets.top_level, XtNwidth, &old_w, XtNheight, &old_h, NULL);
	/*  	fprintf(stderr, "saved geometry: %dx%d\n", old_w, old_h); */
#ifdef MOTIF
	if (globals.widgets.x_bar != NULL)
	    XtVaGetValues(globals.widgets.x_bar, XmNvalue, &orig_x, NULL);
	if (globals.widgets.y_bar != NULL)
	    XtVaGetValues(globals.widgets.y_bar, XmNvalue, &orig_y, NULL);
#else
	get_xy();
	if (globals.widgets.x_bar != NULL)
	    XtVaGetValues(globals.widgets.clip_widget, XtNx, &get_x, NULL);
	if (globals.widgets.y_bar != NULL)
	    XtVaGetValues(globals.widgets.clip_widget, XtNy, &get_y, NULL);
	orig_x = -(get_x - m_window_x);
	orig_y = -(get_y - m_window_y);
#endif /* MOTIF */
	/*  	fprintf(stderr, "Current offsets: %d, %d, %d, %d\n", orig_x, m_window_x, orig_y, m_window_y); */
    }
    if (resource.fullscreen) {
	set_windowsize(&main_win_w, &main_win_h, panel_width, 0, False);
    }
    else { /* re-use old window sizes, or initialize them if started in fullscreen mode */
	if (old_w == 0 || old_h == 0) /* started in fullscreen mode */
	    set_windowsize(&old_w, &old_h, panel_width, 0, False);
	else
	    set_windowsize(&old_w, &old_h, panel_width, 0, True);
    }

    /*      fprintf(stderr, "reconfigure window!\n"); */
    if (resource.fullscreen)
	reconfigure_window(resource.fullscreen, main_win_w, main_win_h, True);
    else {
	reconfigure_window(resource.fullscreen, old_w, old_h, True);
    }

    /* restore horizontal/vertical scroll positions */
    h_restore_scroll_position(orig_x,
#ifndef MOTIF
			      &get_x,
#endif
			      globals.widgets.x_bar);
    h_restore_scroll_position(orig_y,
#ifndef MOTIF			      
			      &get_y,
#endif
			      globals.widgets.y_bar);
    
#ifdef USE_PANNER
    handle_x_scroll(NULL, NULL, NULL, NULL);
    handle_y_scroll(NULL, NULL, NULL, NULL);
#endif
}

#ifdef GREY

static void
Act_set_greyscaling(Widget w, XEvent *event,
		    String *params, Cardinal *num_params)
{
    int arg;

    UNUSED(w);
    UNUSED(event);

    warn_num_params("set-greyscaling()", params, *num_params, 1);
    
    if (!get_int_arg(params, num_params, &arg)) { /* no arg, toggle */
	if (!toggle_arg(resource.use_grey, params, num_params)) {
	    return;
	}
	resource.use_grey = !resource.use_grey;
	if (resource.use_grey) {
	    statusline_info(STATUS_SHORT, "greyscaling on");
	}
	else {
	    statusline_info(STATUS_SHORT, "greyscaling off");
	}
	globals.ev.flags |= EV_NEWPAGE;
	XFlush(DISP);
	return;
    }

    switch (arg) {
    case 0:
	resource.use_grey = False;
	statusline_info(STATUS_SHORT, "greyscaling off");
	break;
    case 1:
	resource.use_grey = True;
	statusline_info(STATUS_SHORT, "greyscaling on");
	break;
    default:
	{
	    float newgamma = arg != 0 ? arg / 100.0 : 1.0;
	    resource.use_grey = newgamma;
	    statusline_info(STATUS_SHORT, "greyscale value: %.1f", newgamma);
	}
    }

    /* like elsewhere */
    if (globals.pausing.flag) {
	XClearWindow(DISP, mane.win);
    }
    
    if (resource.use_grey) {
	if (G_visual->class != TrueColor)
	    init_plane_masks();
#if COLOR
	fg_active = NULL;
#else
	init_pix();
#endif
    }
    reset_fonts();
    globals.ev.flags |= EV_NEWPAGE;
    XFlush(DISP);
}

#endif

#if COLOR

void
do_toggle_color(Boolean update_resource)
{
    if (resource.use_color) {
	resource.use_color = False;
	full_reset_colors();
	scanned_page_color = total_pages;
#if PS
	if (ignore_papersize_specials || scanned_page_ps <= total_pages) {
	    scanned_page = scanned_page_ps;
	}
#endif
	statusline_info(STATUS_SHORT, "color specials off");
    }
    else {
	resource.use_color = True;
	scanned_page = scanned_page_color = scanned_page_reset;
	statusline_info(STATUS_SHORT, "color specials on");
    }
    if (update_resource)
	globals.curr_use_color = resource.use_color;
    
#ifdef MOTIF
    update_preferences_color();
#endif
    globals.ev.flags |= EV_NEWPAGE;
}

static void
Act_set_color(Widget w, XEvent *event,
	      String *params, Cardinal *num_params)
{
    UNUSED(w);
    UNUSED(event);

    if (!toggle_arg(resource.use_color, params, num_params)) {
	return;
    };
	
    /* like elsewhere */
    if (globals.pausing.flag) {
	XClearWindow(DISP, mane.win);
    }

    do_toggle_color(True);
}

#endif /* COLOR */

#if PS

void
Act_set_ps(Widget w, XEvent *event,
	   String *params, Cardinal *num_params)
{
    int arg;

    UNUSED(event);
    UNUSED(w);
    
    if (!get_int_arg(params, num_params, &arg))
	resource.postscript++;
    else
	resource.postscript = arg;

    if (resource.postscript > 2)
	resource.postscript = 0;

#ifdef PS_GS
    if (!resource.useGS) {
	if (resource.postscript > 0) {
	    popup_message(globals.widgets.top_level,
			  MSG_WARN,
			  "This version of xdvi depends on ghostscript for rendering Postscript images. "
			  "Postscript rendering cannot be activated if the option ``-noghostscript'' is used "
			  "or if the resource ``Ghostscript'' is set to false.",
			  /* message */
			  "Option ``-noghostscript'' is active; "
			  "cannot enable Postscript rendering without ghostscript.");
	}
	resource.postscript = 0;
    }
#endif
    if (resource.postscript > 0) {
	scanned_page_ps = scanned_page_ps_bak;
	if (scanned_page > scanned_page_ps)
	    scanned_page = scanned_page_ps;
	if (resource.postscript == 1) {
	    statusline_info(STATUS_SHORT, "Postscript rendering on");
	}
	else {
	    statusline_info(STATUS_SHORT, "Postscript rendering on (with bounding box)");
	}
    }
    else {
	scanned_page_ps_bak = scanned_page_ps;
	scanned_page_ps = total_pages;
#if COLOR
	if (ignore_papersize_specials || scanned_page_color <= total_pages) {
	    scanned_page = scanned_page_color;
	}
#endif
	statusline_info(STATUS_SHORT, "Postscript rendering off; displaying bounding box instead");
    }

    store_preference(NULL, "postscript", "%d", resource.postscript);
    
    psp.toggle(resource.postscript);

    set_menu(&resource.postscript, Act_set_ps, check_int);

    /* like elsewhere */
    if (globals.pausing.flag) {
	XClearWindow(DISP, mane.win);
    }
    
    globals.ev.flags |= EV_PS_TOGGLE;
    XFlush(DISP);
}

#endif /* PS */

void
Act_htex_back(Widget w, XEvent *event,
	      String *params, Cardinal *num_params)
{
    UNUSED(w);
    UNUSED(event);
    UNUSED(params);
    UNUSED(num_params);

    htex_back();
}

void
Act_htex_forward(Widget w, XEvent *event,
		 String *params, Cardinal *num_params)
{
    UNUSED(w);
    UNUSED(event);
    UNUSED(params);
    UNUSED(num_params);

    htex_forward();
}

static void
Act_htex_anchorinfo(Widget w, XEvent *event,
		    String *params, Cardinal *num_params)
{
    int x, y;
    Window dummy;

    UNUSED(w);
    UNUSED(params);
    UNUSED(num_params);

    (void)XTranslateCoordinates(DISP, event->xkey.window, mane.win,
				event->xkey.x, event->xkey.y, &x, &y, &dummy);
    htex_displayanchor(x, y);
}

#ifdef PS_GS
void
Act_set_gs_alpha(Widget w, XEvent *event,
		 String *params, Cardinal *num_params)
{
    UNUSED(w);
    UNUSED(event);

    if (!toggle_arg(resource.gs_alpha, params, num_params)) {
	return;
    }
    resource.gs_alpha = !resource.gs_alpha;

    if (resource.gs_alpha) {
	statusline_info(STATUS_SHORT, "ghostscript alpha active");
    }
    else {
	statusline_info(STATUS_SHORT, "ghostscript alpha inactive");
    }

    store_preference(NULL, "gsAlpha", "%s", resource.gs_alpha ? "True" : "False");
    
    set_menu(&resource.gs_alpha, Act_set_gs_alpha, check_toggle);

#if GS_PIXMAP_CLEARING_HACK
    had_ps_specials = False; /* else infinite loop and/or crash in redraw! */
#endif
    /* like elsewhere */
    if (globals.pausing.flag) {
	XClearWindow(DISP, mane.win);
    }
    
    globals.ev.flags |= EV_PS_TOGGLE;
    XFlush(DISP);
}
#endif

static void
Act_reread_dvi_file(Widget w, XEvent *event,
		    String *params, Cardinal *num_params)
{
    UNUSED(w);
    UNUSED(event);
    UNUSED(params);
    UNUSED(num_params);

    /*      fprintf(stderr, "reread file!\n"); */
    globals.ev.flags |= EV_RELOAD;
}

static void
Act_discard_number(Widget w, XEvent *event,
		   String *params, Cardinal *num_params)
{
    UNUSED(w);
    UNUSED(event);
    UNUSED(params);
    UNUSED(num_params);

    m_have_arg = False;
    m_number = 0;
    m_sign = 1;

    statusline_info(STATUS_SHORT, "numerical prefix discarded");
}

/*
 * Actions to support dragging the image.
 */

static int drag_last_x, drag_last_y;	/* last position of cursor */

static int text_last_x = -1;
static int text_last_y = -1;
static int text_last_page = -1;

static void drag_motion(XEvent *);
static void drag_release(XEvent *);

static void
text_release(XEvent * event)
{
    int ulx, uly, w, h;
    static char *text;
    int text_len = 0;
    
    UNUSED(event);

    MYTRACE((stderr, "text_release!\n"));
    
    if (mouse_motion == null_mouse)
	return;
    mouse_motion = mouse_release = null_mouse;

    w = text_last_x - drag_last_x;
    h = text_last_y - drag_last_y;

    if (w == 0 || h == 0) {
	text_last_x = text_last_y = -1;
	drag_last_x = drag_last_y = -1;
	text_last_page = -1;
	return;
    }

    if (w < 0) {
	ulx = drag_last_x + w;
	w = -w;
    }
    else
	ulx = drag_last_x;

    if (h < 0) {
	uly = drag_last_y + h;
	h = -h;
    }
    else
	uly = drag_last_y;

    free(text);
    /* this allocates text */
    text = get_text_selection(&text_len,
			      ulx * currwin.shrinkfactor,
			      uly * currwin.shrinkfactor,
			      (ulx + w) * currwin.shrinkfactor,
			      (uly + h) * currwin.shrinkfactor);
    if (text[0] == '\0') {
	/* fprintf(stdout, "Text selection empty.\n"); */
	return;
    }

    TRACE_GUI((stderr, "Selected `%s'", text));

    if (text_len > 4 * XMaxRequestSize(DISP) - 32) {
	xdvi_bell();
	statusline_error(STATUS_MEDIUM, "Selection too large (%d bytes, maximum %d bytes)",
			 text_len, 4 * XMaxRequestSize(DISP) - 32);
	return;
    }
    
    if (!set_selection(text, globals.widgets.top_level)) {
	xdvi_bell();
	statusline_error(STATUS_MEDIUM, "Could not set primary selection!");
	text_change_region(TEXT_SEL_CLEAR, NULL);
    }
}

void
text_motion(XEvent *event)
{
    MYTRACE((stderr, "motion!\n"));
    text_change_region(TEXT_SEL_MOVE, event);
}

static void
get_rectangle(XRectangle *rect, int x, int y, int last_x, int last_y)
{
    rect->width = ABS(x - last_x);
    rect->height = ABS(y - last_y);
    rect->x = (x < last_x) ? x : last_x;
    rect->y = (y < last_y) ? y : last_y;
}

static void
crop_to_window(int *x, int *y)
{
    if (*x < -currwin.base_x + 1)
	*x = -currwin.base_x + 1;
    else if (*x > -currwin.base_x + (int)ROUNDUP(pageinfo_get_page_width(current_page), currwin.shrinkfactor) + 1)
	*x = -currwin.base_x + ROUNDUP(pageinfo_get_page_width(current_page), currwin.shrinkfactor) + 1;
    
    if (*y < -currwin.base_y + 1)
	*y = -currwin.base_y + 1;
    else if (*y > -currwin.base_y + (int)ROUNDUP(pageinfo_get_page_height(current_page), currwin.shrinkfactor) + 1)
	*y = -currwin.base_y + (int)ROUNDUP(pageinfo_get_page_height(current_page), currwin.shrinkfactor) + 1;
}

void
text_change_region(textSelectionT mode, XEvent *event)
{
    static GC bboxGC = 0;
    static GC redrawGC = 0; /* needed since bboxGC is clipped to diff of old and new region */

    if (bboxGC == 0) {
	XGCValues values;
	unsigned long valuemask;

	values.function = GXinvert; /* as in search code */
	if (values.function == GXinvert) {
	    valuemask = GCFunction;
	}
	else {
	    values.foreground = WhitePixelOfScreen(SCRN) ^ BlackPixelOfScreen(SCRN);
	    /* 		fprintf(stderr, "foreground: 0x%lx, white pixel: 0x%lx, black pixel: 0x%lx\n", */
	    /* 			values.foreground, WhitePixelOfScreen(SCRN), BlackPixelOfScreen(SCRN)); */
	    valuemask = GCFunction | GCForeground;
	}
	values.line_width = 1;
	valuemask |= GCLineWidth;
	bboxGC = XCreateGC(DISP, XtWindow(globals.widgets.top_level), valuemask, &values);
	redrawGC = XCreateGC(DISP, XtWindow(globals.widgets.top_level), valuemask, &values);
    }

    MYTRACE((stderr, "mode: %d\n", mode));
    switch (mode) {
    case TEXT_SEL_MOVE:
	{
	    int x, y;
	    
	    Window dummy;

	    XRectangle redraw = { -1, -1, 0, 0 };

	    ASSERT(event != NULL, "event in text_change_region() musn't be NULL for TEXT_SEL_MOVE");

	    (void)XTranslateCoordinates(DISP, event->xkey.window, mane.win,
					event->xkey.x, event->xkey.y,
					&x, &y,
					&dummy);

	    crop_to_window(&x, &y);

	    get_rectangle(&redraw, x, y, drag_last_x, drag_last_y);

	    /*
	     * If we have an old region, we want to clip the GC to the area:
	     *
	     * (clip \cup redraw) - (clip \cap redraw)
	     *
	     * and redraw both rectangle areas; otherwise, just draw the redraw area.
	     */

	    if (text_last_x != -1 && text_last_y != -1) {

		XRectangle clip = { -1, -1, 0, 0 };
		
		Region clip_region = XCreateRegion();
		Region redraw_region = XCreateRegion();
		Region union_region = XCreateRegion();
		Region intersect_region = XCreateRegion();

		get_rectangle(&clip, text_last_x, text_last_y, drag_last_x, drag_last_y);

		XUnionRectWithRegion(&clip, clip_region, clip_region);
		XUnionRectWithRegion(&redraw, redraw_region, redraw_region);

		XUnionRegion(clip_region, redraw_region, union_region);
		XIntersectRegion(clip_region, redraw_region, intersect_region);
		
		XSubtractRegion(union_region, intersect_region, redraw_region);

		XSetRegion(DISP, bboxGC, redraw_region);

		XDestroyRegion(clip_region);
		XDestroyRegion(redraw_region); 
		XDestroyRegion(union_region); 
		XDestroyRegion(intersect_region); 
		
		XFillRectangle(DISP, mane.win, bboxGC, clip.x, clip.y, clip.width, clip.height);
	    }
	    
	    XFillRectangle(DISP, mane.win, bboxGC, redraw.x, redraw.y, redraw.width, redraw.height);

	    text_last_x = x;
	    text_last_y = y;
	    text_last_page = current_page;
	}
	break;
    case TEXT_SEL_CLEAR:
	unset_selection(globals.widgets.top_level);
    case TEXT_SEL_ERASE:
    case TEXT_SEL_REDRAW:
	if (text_last_page == current_page
	    && text_last_x != -1 && text_last_y != -1
	    && drag_last_x != -1 && drag_last_y != -1) {

	    XRectangle clear = { -1, -1, 0, 0 };

	    get_rectangle(&clear, text_last_x, text_last_y, drag_last_x, drag_last_y);
	    
	    if (mode == TEXT_SEL_CLEAR) {
		text_last_x = text_last_y = drag_last_x = drag_last_y = -1;
		text_last_page = -1;
		/* Note ZLB: the region is erased instead of inverted to avoid
		 * multiple inverting problem. An exposure is generated to
		 * make the region redrawn */
 		clearexpose(&mane, clear.x, clear.y, clear.width, clear.height);
	    }
	    else if (clip_region_to_rect(&clear)) {
		if (mode == TEXT_SEL_ERASE) {
		    /* If width or height are 0, XClearArea will clear entire window
		     * from coordinates x, y, so check for that: */
		    if (clear.width > 0 && clear.height > 0)
		    	XClearArea(DISP, mane.win, clear.x, clear.y, clear.width, clear.height, False);
		}
		else
		    XFillRectangle(DISP, mane.win, redrawGC, clear.x, clear.y, clear.width, clear.height);
	    }
	}
	break;
    }
}

void
text_selection_start(XEvent *event)
{
    int x, y;
    Window dummy;

    /* erase existing region */
    text_change_region(TEXT_SEL_CLEAR, NULL);

    (void)XTranslateCoordinates(DISP, event->xkey.window, mane.win,
				event->xkey.x, event->xkey.y,
				&x, &y,
				&dummy);	/* throw away last argument */

    crop_to_window(&x, &y);
    
    drag_last_x = x;
    drag_last_y = y;

    text_last_x = text_last_y = -1;

    MYTRACE((stderr, "selection start; mouse_release: %p; null: %p!\n", mouse_release, null_mouse));
    if (mouse_release == null_mouse) {
	MYTRACE((stderr, "init text_motion!\n"));
	mouse_motion = text_motion;
	mouse_release = text_release;
    }
/*     resource.mouse_mode_text_selection_active = True; */
}

void
Act_switch_mode(Widget w, XEvent *event,
		String *params, Cardinal *num_params)
{
/*     mouseModeT prev_mode = resource.mouse_mode; */
    int arg = -1;
/*     char *ptr; */
    const char *mode_name = NULL;
    const char *mode_description = NULL;
/*     XEvent ev; */

    /* if called the first time from main(), this is only used
     * to initialize the menu according to the mode.
     */
    Boolean initialization = (w == globals.widgets.top_level);
    
    UNUSED(event);

    if (get_int_arg(params, num_params, &arg)) {
	if (arg < MOUSE_MODE1 || arg >= MOUSE_MODE_MAX) {
	    statusline_info(STATUS_SHORT, "Argument for Act_switch_mode outside of range from %d to %d",
			     MOUSE_MODE1, MOUSE_MODE_MAX - 1);
	    resource.mouse_mode++;
	}
	else
	    resource.mouse_mode = (mouseModeT)arg;
    }
    else
	resource.mouse_mode++;

    /* check if wrapped */
    if (resource.mouse_mode >= MOUSE_MODE_MAX)
	resource.mouse_mode = MOUSE_MODE1;

    if (globals.curr_mode == RULER_MODE_ACTIVE) {
	clear_ruler();
	mouse_motion = mouse_release = null_mouse;
    }
    else if (globals.curr_mode == TEXT_MODE_ACTIVE) {
	text_change_region(TEXT_SEL_CLEAR, NULL);
	mouse_motion = mouse_release = null_mouse;
    }
    globals.curr_mode = NO_MODE_ACTIVE;
    
    switch(resource.mouse_mode) {
    case MOUSE_MODE1:
	mode_name = resource.mouse_mode1_name;
	mode_description = resource.mouse_mode1_description;
	break;
    case MOUSE_MODE2:
	mode_name = resource.mouse_mode2_name;
	mode_description = resource.mouse_mode2_description;
/* 	globals.cursor.flags |= CURSOR_TEXT; /\*XXX FIXME: don't hardcode flag here; instead, have Act_text_selection set it *\/ */
	break;
    case MOUSE_MODE3:
	mode_name = resource.mouse_mode3_name;
	mode_description = resource.mouse_mode3_description;
/* 	globals.cursor.flags |= CURSOR_RULER; */
	break;
    default: /* can't happen */
	XDVI_WARNING((stderr, "resource.mouse_mode larger than %d!", resource.mouse_mode));
	break;
    }

    globals.ev.flags |= EV_CURSOR;
    XFlush(DISP);

    if (!initialization) {
	statusline_info(STATUS_SHORT, "%s mode; %s", mode_name, mode_description);
	store_preference(NULL, "mouseMode", "%d", resource.mouse_mode);
    }
    set_menu(&resource.mouse_mode, Act_switch_mode, check_int);

    
    /*XXX FIXME: Activate mode by synthesizing a mouse event??? */
/*     synthesize_event(&ev, globals.widgets.draw_widget); */
/*     Act_mouse_modes(w, event, params, num_params); */
}

static void
Act_drag(Widget w, XEvent *event,
	 String *params, Cardinal *num_params)
{
    UNUSED(w);

    if (mouse_release != null_mouse && mouse_release != drag_release)
	return;

    if ((globals.curr_mode == TEXT_MODE_ACTIVE) && text_last_page != -1
	/* this was:
	   && (text_last_x != drag_last_x || text_last_y != drag_last_y)
	   but that isn't restrictive enough */
	) { /* dragging would mess up region drawing */
	xdvi_bell();
	statusline_info(STATUS_SHORT, "Cannot drag page when selection is active");
	return;
    }
    
    if (*num_params != 1) {
	XDVI_WARNING((stderr, "drag() requires 1 argument (got %d)", *num_params));
	return;
    }
    switch (**params) {
    case '|':
	globals.cursor.flags |= CURSOR_DRAG_V;
	break;
    case '-':
	globals.cursor.flags |= CURSOR_DRAG_H;
	break;
    case '+':
	globals.cursor.flags |= CURSOR_DRAG_A;
	break;
    default:
	XDVI_WARNING((stderr, "drag(): Valid arguments are `+', `|' or `-'"));
    }
    
    globals.ev.flags |= EV_CURSOR;
    
    if (mouse_release == null_mouse) {
	mouse_motion = drag_motion;
	mouse_release = drag_release;
	drag_last_x = event->xbutton.x_root;
	drag_last_y = event->xbutton.y_root;
    }
    else
	drag_motion(event);

    XFlush(DISP);
}


static void
drag_motion(XEvent * event)
{
#ifdef MOTIF
    get_xy();
#endif

    if (globals.cursor.flags & (CURSOR_DRAG_H | CURSOR_DRAG_A)) { /* horizontal motion */
#ifdef MOTIF
	(void)set_bar_value(globals.widgets.x_bar,
			    drag_last_x - event->xbutton.x_root - m_window_x,
			    (int)(globals.page.w - mane.width));
#else
	if (globals.widgets.x_bar != NULL) {
	    XtCallCallbacks(globals.widgets.x_bar, XtNscrollProc,
			    cast_int_to_XtPointer(drag_last_x - event->xbutton.x_root));
	}
#endif
	drag_last_x = event->xbutton.x_root;
    }

    if (globals.cursor.flags & (CURSOR_DRAG_V | CURSOR_DRAG_A)) { /* vertical motion */
#ifdef MOTIF
	(void)set_bar_value(globals.widgets.y_bar,
			    drag_last_y - event->xbutton.y_root - m_window_y,
			    (int)(globals.page.h - mane.height));
#else
	if (globals.widgets.y_bar != NULL) {
	    XtCallCallbacks(globals.widgets.y_bar, XtNscrollProc,
			    cast_int_to_XtPointer(drag_last_y - event->xbutton.y_root));
	}
#endif
	drag_last_y = event->xbutton.y_root;
    }
#ifdef USE_PANNER
    handle_x_scroll(NULL, NULL, NULL, NULL);
    handle_y_scroll(NULL, NULL, NULL, NULL);
#endif
}


static void
drag_release(XEvent * event)
{
    drag_motion(event);
    mouse_motion = mouse_release = null_mouse;

    globals.cursor.flags &= ~(CURSOR_DRAG_H | CURSOR_DRAG_V | CURSOR_DRAG_A);
    globals.ev.flags |= EV_CURSOR;
    XFlush(DISP);
}



/* Wheel mouse support.  */

static int wheel_button = -1;

static void
Act_wheel(Widget w, XEvent *event,
	  String *params, Cardinal *num_params)
{
    int dist;

    UNUSED(w);

    if (*num_params != 1) {
	XDVI_WARNING((stderr, "wheel() requires 1 argument (got %d)", *num_params));
	return;
    }
    dist = (strchr(*params, '.') == NULL) ? atoi(*params)
	: (int)(my_atof(*params) * resource.wheel_unit);
#ifdef MOTIF
    get_xy();
    set_bar_value(globals.widgets.y_bar, dist - m_window_y, (int)(globals.page.h - mane.height));
#else
    if (globals.widgets.y_bar != NULL)
	XtCallCallbacks(globals.widgets.y_bar, XtNscrollProc, cast_int_to_XtPointer(dist));
#endif

    if (event != NULL)
	wheel_button = event->xbutton.button;

#ifdef USE_PANNER
    handle_y_scroll(NULL, NULL, NULL, NULL);
#endif
}

static int wheel_h_button = -1;

static void
Act_hwheel(Widget w, XEvent *event,
	  String *params, Cardinal *num_params)
{
    int dist;

    UNUSED(w);

    if (*num_params != 1) {
	XDVI_WARNING((stderr, "wheel() requires 1 argument (got %d)", *num_params));
	return;
    }
    dist = (strchr(*params, '.') == NULL) ? atoi(*params)
	: (int)(my_atof(*params) * resource.wheel_unit);
#ifdef MOTIF
    get_xy();
    set_bar_value(globals.widgets.x_bar, dist - m_window_x,
		  (int) (globals.page.w - mane.width));
#else
    if (globals.widgets.x_bar != NULL)
	XtCallCallbacks(globals.widgets.x_bar, XtNscrollProc,
			cast_int_to_XtPointer(dist));
#endif

    if (event != NULL)
	wheel_h_button = event->xbutton.button;

#ifdef USE_PANNER
    handle_x_scroll(NULL, NULL, NULL, NULL);
#endif
}


/* Internal mouse actions.  */

/*
 * Here xdvi does its own parsing and processing of translations for mouse
 * button presses.  This is necessary because the X Toolkit does not recognize
 * <Btn6Down> or <Btn7Down> event types.
 */

#if HAVE_X11_INTRINSICI_H
# include <X11/IntrinsicI.h>
#else

/* From <X11/TranslateI.h> */
extern Boolean _XtComputeLateBindings(
    Display*		/* dpy */,
    struct _LateBindings* /* lateModifiers */,
    Modifiers*		/* computed */,
    Modifiers*		/* computedMask */
);

#endif /* HAVE_X11_INTRINSICI_H */


static void
Act_press(Widget w, XEvent *event,
	  String *params, Cardinal *num_params)
{
    struct mouse_acts *mactp;
    struct xdvi_action *actp;

#if HAVE_XI21
    if (xi2_active
      && (xi2_current->slave->btn_mask & (1 << event->xbutton.button))) {
	TRACE_EVENTS((stderr, "Ignoring button %d press (XI 2.1 active)",
	  event->xbutton.button));
	if (event->xbutton.button <= 5)
	    wheel_button = event->xbutton.button;
	else
	    wheel_h_button = event->xbutton.button;
	xi2_ign_time = event->xbutton.time;
	return;
    }
#endif

    for (mactp = mouse_actions; mactp != NULL; mactp = mactp->next) {
	if (event->xbutton.button == mactp->button || mactp->button == 0) {
	    Modifiers mask = 0;
	    Modifiers value = 0;

	    if (mactp->late_bindings == NULL
	      || _XtComputeLateBindings(DISP, mactp->late_bindings,
	      &value, &mask)) {
		mask |= mactp->mask;
		value |= mactp->value;
		if (((value ^ event->xbutton.state) & mask) == 0) {
		    for (actp = mactp->action; actp != NULL;
		      actp = actp->next)
			(actp->proc)(w, event, actp->params, &actp->num_params);
		    return;
		}
	    }
	}
    }
}


static void
Act_motion(Widget w, XEvent *event,
	   String *params, Cardinal *num_params)
{
    /* remember last position, to do this only when pointer is actually moved */
    static int old_x = -1, old_y = -1;
    int x, y;

    UNUSED(w);
    UNUSED(params);
    UNUSED(num_params);

    MYTRACE((stderr, "act_motion!\n"));

    if (globals.curr_mode == RULER_MODE_ACTIVE) {
	show_distance_from_ruler(event, False);
    }
    /* This used to be:
       (abs(x - old_x) > x_threshold || abs(y - old_y) > y_threshold))
       but that didn't work too well either. Just change it whenever user
       moves the mouse. */
    if (!MAGNIFIER_ACTIVE && !(globals.curr_mode == RULER_MODE_ACTIVE)
	&& pointerlocate(&x, &y) && (x != old_x || y != old_y)) {
	htex_displayanchor(x, y);
	old_x = x;
	old_y = y;
    }

    if ((int)(event->xbutton.button) != wheel_button
	&& ((int) (event->xbutton.button) != wheel_h_button)) {
	MYTRACE((stderr, "mouse_motion!\n"));
	mouse_motion(event);
    }
}


static void
Act_release(Widget w, XEvent *event,
	    String *params, Cardinal *num_params)
{
    UNUSED(w);
    UNUSED(params);
    UNUSED(num_params);

    if ((int)(event->xbutton.button) == wheel_button) {
	wheel_button = -1;
	return;
    }

    if ((int)(event->xbutton.button) == wheel_h_button) {
	wheel_h_button = -1;
	return;
    }

    mouse_release(event);
}

static void
Act_toggle_grid_mode(Widget w, XEvent *event,
		     String *params, Cardinal *num_params)
{
    int arg;

    UNUSED(w);
    UNUSED(event);

    if (!get_int_arg(params, num_params, &arg)) {
	arg = -1;
    }
    switch (arg) {
    case -1:
	resource.grid_mode = !resource.grid_mode;
	if (resource.grid_mode) {
	    statusline_info(STATUS_SHORT, "Grid mode on");
	}
	else {
	    statusline_info(STATUS_SHORT, "Grid mode off");
	}
	break;
    case 1: /* fall through */
    case 2: /* fall through */
    case 3:
	resource.grid_mode = arg;
	statusline_info(STATUS_SHORT, "Grid mode %d", arg);
	break;
    default:
	xdvi_bell();
	statusline_info(STATUS_SHORT,
			 "Valid arguments for grid mode are: none (toggles), 1, 2, 3");
	return;
    }
    init_page();
    reconfig();
    globals.ev.flags |= EV_NEWPAGE;
    XFlush(DISP);
}


/* Actions for source specials.  */

void
Act_source_special(Widget w, XEvent *event,
		   String *params, Cardinal *num_params)
{
    Window dummy;

    UNUSED(w);
    UNUSED(params);
    UNUSED(num_params);

    if ((event->type == ButtonPress && mouse_release != null_mouse)
	|| MAGNIFIER_ACTIVE) {
	xdvi_bell();
	return;
    }

    source_reverse_x = event->xbutton.x;
    source_reverse_y = event->xbutton.y;
    if (event->xbutton.window != mane.win)
	(void)XTranslateCoordinates(DISP,
				    RootWindowOfScreen(SCRN), mane.win,
				    event->xbutton.x_root,
				    event->xbutton.y_root,
				    &source_reverse_x,
				    &source_reverse_y,
				    &dummy);	/* throw away last argument */

    source_reverse_x = (source_reverse_x + mane_base_x) * mane.shrinkfactor;
    source_reverse_y = (source_reverse_y + mane_base_y) * mane.shrinkfactor;

    globals.ev.flags |= EV_SRC;
}

void
Act_show_source_specials(Widget w, XEvent *event,
			 String *params, Cardinal *num_params)
{
    int arg;
    Boolean clear_statusline = False;

    UNUSED(w);

    if (!get_int_arg(params, num_params, &arg))
	arg = -1;
    else
	clear_statusline = True;

    if ((event->type == ButtonPress && mouse_release != null_mouse)
	|| MAGNIFIER_ACTIVE) {
	xdvi_bell();
	return;
    }

    if (!(globals.ev.flags & EV_SRC)) {
	source_reverse_x = -1;
	source_show_all = (arg == 1 ? True : False);

	globals.ev.flags |= EV_SRC;
    }
    if (clear_statusline)
	statusline_clear();
}

void
Act_source_what_special(Widget w, XEvent *event,
			String *params, Cardinal *num_params)
{
    int my_x, my_y;
    Window dummy;

    UNUSED(w);
    UNUSED(params);
    UNUSED(num_params);

    (void)XTranslateCoordinates(DISP, event->xkey.window, mane.win,
				event->xkey.x, event->xkey.y, &my_x, &my_y, &dummy);	/* throw away last argument */
    my_x = (my_x + mane_base_x) * mane.shrinkfactor;
    my_y = (my_y + mane_base_y) * mane.shrinkfactor;
    source_reverse_search(my_x, my_y, False);
}

static const char *user_exec_help =
"The user-exec() action lets you run a child process. "
"It takes a single string, which is tokenized on whitespace.";

void
Act_user_exec(Widget w, XEvent *event,
	      String *params, Cardinal *num_params)
{
    char **argv;
    int i;
    char *errmsg = NULL;

    if (*num_params == 0)
	errmsg = "No arguments supplied to the user-exec() action.";
    else if (*num_params > 1)
	errmsg = "Too many arguments supplied to the user-exec() action.";
    else if (setenv("XDVI_FILE", globals.dvi_name, 1) == -1)
        errmsg = strerror(errno);

    if (errmsg) {
	popup_message(globals.widgets.top_level,
		      MSG_ERR,
		      /* helptext */
		      user_exec_help,
		      /* popup */
		      errmsg);
	return;
    }

    argv = get_separated_list(params[0], " \t", True);
    fork_process(argv[0], False, NULL, NULL, NULL, 0, argv);
    for (i = 0; argv[i] != NULL; i++)
	free(argv[i]);	
    free(argv);
}

static void
select_cb(const char *filename, void *data)
{
    UNUSED(data);
    if (filename != NULL) {
	TRACE_FILES((stderr, "new filename: |%s|", filename));
		
	if (resource.filesel_open_new_window) {
	    /*  	    char *fname = expand_filename_append_dvi(filename); */
	    launch_xdvi(filename, NULL);
	}
	else {
	    /* the filename should be complete already, so don't append .dvi */
	    set_dvi_name(expand_filename(filename, USE_CWD_PATH));
	    current_page = 0; /* switch to first page */
	    close_old_filep();
	    globals.ev.flags |= EV_NEWDOC;
	    globals.ev.flags |= EV_PAGEHIST_INSERT;
	}
    }
}

static void
Act_select_dvi_file(Widget w, XEvent *event,
		    String *params, Cardinal *num_params)
{
    /* static so that we can pass its address */
    static struct filesel_callback cb = {
	NULL, NULL, "xdvik: Open File", "Open file:", "OK", "Cancel",
	NULL, "*.dvi", True, False, NULL, NULL
    };
    int arg = -1;

    UNUSED(w);
    UNUSED(event);

    if (get_int_arg(params, num_params, &arg)) { /* try item from file history */
	char *fname;
	int dummy_page;

	if (arg < 1) {
	    xdvi_bell();
	    statusline_info(STATUS_MEDIUM, "Error: File history number must be >= 1");
	}
	else if ((fname = file_history_get_elem(arg - 1, &dummy_page)) == NULL) {
	    statusline_info(STATUS_MEDIUM, "No file number %d in history (history size: %lu)",
			     arg, (unsigned long)file_history_size());
	}
	else {
	    file_history_open(fname);
	}
	return;
    }

    file_history_set_page(current_page);
    
    cb.func_ptr = select_cb;
    cb.init_path = globals.dvi_file.dirname;
    if (cb.shell == NULL)
	cb.shell = XsraSelFile(globals.widgets.top_level, &cb);
    XsraSelFilePopup(&cb);
}

/*
 * If all GUI elements have been turned on/off, make this synonymous
 * with expert mode off/on, so that next `x' keystroke does something
 * reasonable.
 */
void
update_expert_mode(void)
{
    if ((resource.expert_mode & (XPRT_SHOW_STATUSLINE | XPRT_SHOW_SCROLLBARS
#ifdef MOTIF
				 | XPRT_SHOW_PAGELIST | XPRT_SHOW_TOOLBAR | XPRT_SHOW_MENUBAR
#else
				 | XPRT_SHOW_BUTTONS
#endif
				 )) == XPRT_SHOW_ALL) {
	resource.expert = False;
    }
    else if ((resource.expert_mode & (XPRT_SHOW_STATUSLINE
#ifdef MOTIF
				      | XPRT_SHOW_SCROLLBARS | XPRT_SHOW_PAGELIST
				      | XPRT_SHOW_TOOLBAR | XPRT_SHOW_MENUBAR
#else
				      | BROKEN_RECONFIG ? 0 : XPRT_SHOW_SCROLLBARS | XPRT_SHOW_BUTTONS
#endif
				      )) == XPRT_SHOW_NONE) {
	resource.expert = True;
    }
}

void
Act_set_expert_mode(Widget w, XEvent *event,
		    String *params, Cardinal *num_params)
{
    int arg;
    Boolean clear_statusline = False;

    UNUSED(w);
    UNUSED(event);

    if (!get_int_arg(params, num_params, &arg))
	arg = -1;
    else
	clear_statusline = True;
    
    switch(arg) {
    case 1:
	resource.expert_mode ^= XPRT_SHOW_STATUSLINE;
	toggle_statusline();
	update_expert_mode();
	break;
    case 2:
#ifndef MOTIF
	/* show this warning only when not toggling global expert mode
	   (that's why it can't be inside toggle_scrollbars) */
	if (BROKEN_RECONFIG) {
	    popup_message(globals.widgets.top_level,
			  MSG_WARN,
			  NULL,
			  "Sorry - cannot toggle scrollbars with this X Version.\n"
			  "This version of XFree has a broken implementation of the viewportWidget, "
			  "which would break the layout if the scrollbars are toggled. "
			  "Versions that are known to work have a VendorRelease version below 4000 or above 4002. "
			  "You will need to update your XFree server to fix this.");
	    return;
	}
#endif
	resource.expert_mode ^= XPRT_SHOW_SCROLLBARS;
	toggle_scrollbars();
	update_expert_mode();
	break;
	
#ifdef MOTIF
    case 3:
	resource.expert_mode ^= XPRT_SHOW_PAGELIST;
	toggle_pagelist();
	update_expert_mode();
	break;
	
    case 4: /* toolbar */
	resource.expert_mode ^= XPRT_SHOW_TOOLBAR;
	toggle_toolbar();
	update_expert_mode();
	break;
	
    case 5:
	resource.expert_mode ^= XPRT_SHOW_MENUBAR;
	toggle_menubar();
	update_expert_mode();
	break;
#else
    case 3:
	/*  	fprintf(stderr, "sidebar\n"); */
	resource.expert_mode ^= XPRT_SHOW_BUTTONS;
	toggle_buttons();
	update_expert_mode();
	break;
#endif
    default:
	/* warn 'em */
	if (
#ifdef MOTIF
	    arg > 5
#else
	    arg > 3
#endif
	    ) {
	    statusline_info(STATUS_SHORT, "Number %d too large for `set-expert-mode', using 0 (= toggle) instead.",
			     arg);
	}
	/* toggle all items */
	resource.expert = !resource.expert;
	if (resource.expert)
	    resource.expert_mode = XPRT_SHOW_NONE;
	else
	    resource.expert_mode = XPRT_SHOW_ALL;
	
	toggle_statusline();
#ifndef MOTIF
	if (!BROKEN_RECONFIG)
	    toggle_scrollbars();
#else
	toggle_scrollbars();
#endif
	
#ifdef MOTIF
	toggle_pagelist();
	toggle_toolbar();
	toggle_menubar();
#else
	toggle_buttons();
#endif
    }

#ifdef MOTIF
    update_preferences_expert();
#endif

    store_preference(NULL, "expertMode", "%d", resource.expert_mode);
    
    if (clear_statusline)
	statusline_clear();
}

Boolean have_src_specials = False;
static Boolean do_update_property = False;

#ifdef USE_PANNER
void
handle_x_scroll(Widget w, XtPointer closure, XEvent *ev, Boolean *cont)
{
#if !defined(MOTIF)
    Dimension get_x = 0;
#endif

    UNUSED(w);
    UNUSED(closure);
    UNUSED(ev);
    UNUSED(cont);

    if (/* !resource.keep_flag || */ globals.widgets.x_bar == NULL)
	return;

#ifdef MOTIF
    XtVaGetValues(globals.widgets.x_bar, XmNvalue, &m_x_scroll, NULL);
#else
    get_xy();
    XtVaGetValues(globals.widgets.clip_widget, XtNx, &get_x, NULL);
    m_x_scroll = get_x - m_window_x;
    scroll_x_panner(m_x_scroll);
#endif /* MOTIF */
}

void
handle_y_scroll(Widget w, XtPointer closure, XEvent *ev, Boolean *cont)
{
#if !defined(MOTIF)
    Dimension get_y = 0;
#endif
    
    UNUSED(w);
    UNUSED(closure);
    UNUSED(ev);
    UNUSED(cont);
    
    if (/* !resource.keep_flag || */ globals.widgets.y_bar == NULL)
	return;
    
#ifdef MOTIF
    XtVaGetValues(globals.widgets.y_bar, XmNvalue, &m_y_scroll, NULL);
#else
    get_xy();
    XtVaGetValues(globals.widgets.clip_widget, XtNy, &get_y, NULL);
    m_y_scroll = get_y - m_window_y;
    scroll_y_panner(m_y_scroll);
#endif /* MOTIF */
}
#endif /* USE_PANNER */

void
handle_expose(Widget w, XtPointer closure, XEvent *ev, Boolean *cont)
{
    struct WindowRec *windowrec = (struct WindowRec *)closure;

    UNUSED(w);
    UNUSED(cont);

    if (windowrec == &magnifier) {
	if (magnifier_stat < 0) { /* destroy upon exposure */
	    magnifier_stat = 0;
	    mag_release(ev);
	    return;
	}
	else
	    magnifier_stat = 0;
    }

    do_update_property = False;

    if (have_src_specials && !MAGNIFIER_ACTIVE) {
	do_update_property = True;
    }

    expose(windowrec, (&(ev->xexpose))->x, (&(ev->xexpose))->y,
	   (unsigned int)(&(ev->xexpose))->width, (unsigned int)(&(ev->xexpose))->height);
}


void
handle_property_change(Widget w, XtPointer junk,
		       XEvent *ev, Boolean *cont)
{
    char *prop_ret;
    size_t prop_len;

    UNUSED(w);
    UNUSED(junk);
    UNUSED(cont);

    if ((&(ev->xproperty))->window != XtWindow(globals.widgets.top_level)) /* if spurious event */
	return;
    
    if ((&(ev->xproperty))->atom == atom_src_goto()) {
	/* forward search requested */
	if ((prop_len = property_get_data(XtWindow(globals.widgets.top_level), atom_src_goto(),
					  &prop_ret,
					  XGetWindowProperty)) == 0) {
	    TRACE_CLIENT((stderr, "property_get_data() failed for atom_src_goto()!"));
	    return;
	}
	TRACE_CLIENT((stderr, "got back atom_src_goto: |%s|", prop_ret));
	globals.src.fwd_string = prop_ret;
	globals.ev.flags |= EV_SRC;
    }
    else if ((&(ev->xproperty))->atom == atom_find_string()) {
	/* string search requested */
	if ((prop_len = property_get_data(XtWindow(globals.widgets.top_level), atom_find_string(),
					  &prop_ret,
					  XGetWindowProperty)) == 0) {
	    TRACE_CLIENT((stderr, "property_get_data() failed for atom_find_string()!"));
	    return;
	}
	TRACE_FIND((stderr, "got back atom_find_string: |%s|", prop_ret));
	resource.find_string = prop_ret;
	globals.ev.flags |= EV_FIND;
    }
    else if ((&(ev->xproperty))->atom == atom_reload()) {
	/* like do_sigusr(); there's no data in this case. */
	TRACE_CLIENT((stderr, "atom_reload()"));
	globals.ev.flags |= EV_RELOAD;
    }
    else if ((&(ev->xproperty))->atom == atom_newdoc()) {
	/* loading a new file */
	FILE *new_fp;
	if ((prop_len = property_get_data(XtWindow(globals.widgets.top_level), atom_newdoc(),
					  &prop_ret,
					  XGetWindowProperty)) == 0) {
	    TRACE_CLIENT((stderr, "property_get_data() returned zero length for atom_newdoc()"));
	    /* just raise it in this case */
	    XMapRaised(XtDisplay(globals.widgets.top_level), XtWindow(globals.widgets.top_level));
	    raise_message_windows();
	    return;
	}
	TRACE_CLIENT((stderr, "got back atom_newdoc: |%s|", prop_ret));
	if ((new_fp = XFOPEN(prop_ret, "r")) == NULL) {
	    popup_message(globals.widgets.top_level,
			  MSG_ERR, NULL, "Loading %s failed: %s",
			  prop_ret, strerror(errno));
	    return;
	}
	set_dvi_name_expand(prop_ret);

	globals.ev.flags |= EV_NEWDOC;
    }
    else if ((&(ev->xproperty))->atom == atom_newpage()) {
	/* jumping to a different page */
	int newpage;
	char *testptr;
	if ((prop_len = property_get_data(XtWindow(globals.widgets.top_level), atom_newpage(),
					  &prop_ret,
					  XGetWindowProperty)) == 0) {
	    TRACE_CLIENT((stderr, "property_get_data() failed for atom_newpage(): |%s|", prop_ret));
	    return;
	}
	TRACE_CLIENT((stderr, "got back atom_newpage: |%s|", prop_ret));
	if (strcmp(prop_ret, "+") == 0) { /* special case: treat `+' as last page */
	    newpage = total_pages - 1;
	}
	else {
	    newpage = strtol(prop_ret, &testptr, 10) - 1;
	    if (*testptr != '\0') {
		XDVI_FATAL((stderr, "Invalid page number: `%s'.", prop_ret));
	    }
	}

	if (newpage == total_pages - 1) { /* as in Act_goto_page() */
	    goto_page(check_goto_page(newpage, True), resource.keep_flag ? NULL : home, False);
	    search_signal_page_changed();
	}
	else {
	    goto_page(check_goto_tex_page(newpage), resource.keep_flag ? NULL : home, False);
	    search_signal_page_changed();
	}
    }
    else if ((&(ev->xproperty))->atom == atom_raise()) {
	XMapRaised(XtDisplay(globals.widgets.top_level), XtWindow(globals.widgets.top_level));
	raise_message_windows();
    }
    else if ((&(ev->xproperty))->atom == atom_reread_prefs()) {
	read_user_preferences(globals.widgets.top_level, ".xdvirc.tmp");
    }
}


/*
 *	Signal routines.  At the signal level, all we do is set flags.
 */

#ifndef FLAKY_SIGPOLL
static RETSIGTYPE
handle_sigpoll(int signo)
{
    UNUSED(signo);
    
    globals.ev.ctr = 1;
    event_freq = -1;	/* forget Plan B */
    sig_flags |= SF_POLL;
# ifndef HAVE_SIGACTION
    (void) signal(SIGPOLL, handle_sigpoll);	/* reset the signal */
# endif
}
#endif

static RETSIGTYPE
handle_sigterm(int signo)
{
    UNUSED(signo);
    
    sig_flags |= SF_TERM;
}

static RETSIGTYPE
handle_sigchld(int signo)
{
    UNUSED(signo);

    sig_flags |= SF_CHLD;
}

static RETSIGTYPE
handle_sigalrm(int signo)
{
    UNUSED(signo);

    sig_flags |= SF_ALRM;
}

static RETSIGTYPE
ignore_sigusr(int signo)
{
    UNUSED(signo);
    fprintf(stderr, "IGNORE SIGUSR1!\n");
}

static RETSIGTYPE
handle_sigusr(int signo)
{
    UNUSED(signo);
    
    globals.ev.ctr = 1;
    sig_flags |= SF_USR;
}

static RETSIGTYPE
handle_sigsegv(int signo)
{
    UNUSED(signo);
    
    XDVI_ABORT((stderr, "Segmentation fault - trying to clean up and aborting ..."));
}

static Boolean sigalarm_initialized = False;

void
setup_sigalarm(void)
{
#if HAVE_SIGACTION
    struct sigaction a;

    a.sa_handler = handle_sigalrm;
    (void) sigemptyset(&a.sa_mask);
    (void) sigaddset(&a.sa_mask, SIGALRM);
    a.sa_flags = 0;
    sigaction(SIGALRM, &a, NULL);
#else /* not HAVE_SIGACTION */
    (void) signal(SIGALRM, handle_sigalrm);
#endif /* not HAVE_SIGACTION */

    sigalarm_initialized = True;
}

/*
 * Called from main to set up the signal handlers.
 */
void
setup_signal_handlers(Boolean early)
{
#ifndef FLAKY_SIGPOLL
    int	sock_fd	= 0;
#endif
#if HAVE_SIGACTION
    struct sigaction a;
#endif

    if (early) {
#if HAVE_SIGACTION
	a.sa_handler = ignore_sigusr;
	(void) sigemptyset(&a.sa_mask);
	(void) sigaddset(&a.sa_mask, SIGUSR1);
	a.sa_flags = 0;
	sigaction(SIGUSR1, &a, NULL);
#else /* not HAVE_SIGACTION */
	(void) signal(SIGUSR1, ignore_sigusr);
#endif /* not HAVE_SIGACTION */

	(void)sigemptyset(&all_signals);
	(void)sigaddset(&all_signals, SIGUSR1);
	return;
    }

    
#if HAVE_SIGACTION
    a.sa_handler = handle_sigusr;
    (void) sigemptyset(&a.sa_mask);
    (void) sigaddset(&a.sa_mask, SIGUSR1);
    a.sa_flags = 0;
    sigaction(SIGUSR1, &a, NULL);
#else /* not HAVE_SIGACTION */
    (void) signal(SIGUSR1, handle_sigusr);
#endif /* not HAVE_SIGACTION */

#ifndef FLAKY_SIGPOLL
#if HAVE_SIGACTION
    /* Subprocess handling, e.g., MakeTeXPK, fails on the Alpha without
       this, because SIGPOLL interrupts the call of system(3), since OSF/1
       doesn't retry interrupted wait calls by default.  From code by
       maj@cl.cam.ac.uk.  */
    a.sa_handler = handle_sigpoll;
    (void) sigemptyset(&a.sa_mask);
    (void) sigaddset(&a.sa_mask, SIGPOLL);
    a.sa_flags = SA_RESTART;
    sigaction(SIGPOLL, &a, NULL);
#else /* not HAVE_SIGACTION */
    (void) signal(SIGPOLL, handle_sigpoll);
#endif /* not HAVE_SIGACTION */

    sock_fd = ConnectionNumber(DISP);
    prep_fd(sock_fd, False);
#endif	/* not FLAKY_SIGPOLL */

#if HAVE_SIGACTION
    a.sa_handler = handle_sigterm;
    (void) sigemptyset(&a.sa_mask);
    (void) sigaddset(&a.sa_mask, SIGINT);
    (void) sigaddset(&a.sa_mask, SIGQUIT);
    (void) sigaddset(&a.sa_mask, SIGTERM);
    (void) sigaddset(&a.sa_mask, SIGHUP);
    a.sa_flags = SA_RESETHAND;
    sigaction(SIGINT, &a, NULL);
    sigaction(SIGQUIT, &a, NULL);
    sigaction(SIGTERM, &a, NULL);
    sigaction(SIGHUP, &a, NULL);
    a.sa_handler = handle_sigsegv;
    (void)sigemptyset(&a.sa_mask);
    (void)sigaddset(&a.sa_mask, SIGSEGV);
    a.sa_flags = 0;
    sigaction(SIGSEGV, &a, NULL);
#else /* not HAVE_SIGACTION */
    (void) signal(SIGINT, handle_sigterm);
    (void) signal(SIGQUIT, handle_sigterm);
    (void) signal(SIGTERM, handle_sigterm);
    (void) signal(SIGHUP, handle_sigterm);
    (void)signal(SIGSEGV, handle_sigsegv);
#endif /* not HAVE_SIGACTION */

#if HAVE_SIGACTION
    a.sa_handler = handle_sigchld;
    (void) sigemptyset(&a.sa_mask);
    (void) sigaddset(&a.sa_mask, SIGCHLD);
    a.sa_flags = 0;
    sigaction(SIGCHLD, &a, NULL);
#else /* not HAVE_SIGACTION */
    (void) signal(SIGCHLD, handle_sigchld);
#endif /* not HAVE_SIGACTION */

    (void)sigemptyset(&all_signals);
    (void)sigaddset(&all_signals, SIGPOLL);
    (void)sigaddset(&all_signals, SIGINT);
    (void)sigaddset(&all_signals, SIGQUIT);
    (void)sigaddset(&all_signals, SIGTERM);
    (void)sigaddset(&all_signals, SIGHUP);
    (void)sigaddset(&all_signals, SIGCHLD);
    (void)sigaddset(&all_signals, SIGALRM);
    (void)sigaddset(&all_signals, SIGUSR1);
    (void)sigaddset(&all_signals, SIGSEGV);

}


/*
 *	Mid-level signal handlers.  These are called from within read_events(),
 *	and do the actual work appropriate for the given signal.
 */

/*
 *	Process-related routines.  Call set_chld(xchild *) to indicate
 *	that a given child process should be watched for when it
 *	terminates.  Call clear_chld() to remove the process from the
 *	list.  When the child terminates, the record is removed from
 *	the list and xchild->proc is called.
 *	The caller can set the `io' member in xchild to a pointer to
 *	an xio structure for reading from the file descriptor; see
 *	psgs.c and util.c for examples.
 */

static struct xchild *child_recs = NULL;	/* head of child process list */

void
set_chld(struct xchild *cp)
{
    cp->next = child_recs;
    child_recs = cp;
}

void
clear_chld(struct xchild *cp)
{
    struct xchild	**cpp;
    struct xchild	*cp2;

    if (child_recs == NULL) {
	if (globals.debug & DBG_EVENT)
	    fprintf(stderr, "child_recs: %p\n", (void *)child_recs);
	return;
    }
    for (cpp = &child_recs;;) {
	cp2 = *cpp;
	if (cp2 == cp)
	    break;
	cpp = &cp2->next;
    }
    *cpp = cp->next;
}

static void
do_sigchld(void)
{
    pid_t pid;
    int	status;

    sig_flags &= ~SF_CHLD;

#if ! HAVE_SIGACTION
    (void) signal(SIGCHLD, handle_sigchld);	/* reset the signal */
#endif
    for (;;) {
#if HAVE_WAITPID
	pid = waitpid(-1, &status, WNOHANG);
#else
	pid = wait3(&status, WNOHANG, NULL);
#endif
	if (pid == 0) break;

	if (pid != -1) {
	    struct xchild	**cpp;
	    struct xchild	*cp;

	    for (cpp = &child_recs;;) {
		cp = *cpp;
		if (cp == NULL)
		    break;
		if (cp->pid == pid) {
		    *cpp = cp->next;	/* unlink it */
		    /* call exit reporting procedure for this child */
		    (cp->proc)(status, cp);
		    break;
		}
		cpp = &cp->next;
	    }
	    break;
	}

	if (errno == EINTR) continue;
	if (errno == ECHILD) break;
#if HAVE_WAITPID
	perror("xdvi: waitpid");
#else
	perror("xdvi: wait3");
#endif
	break;
    }
}


/*
 *	File-related routines.  Call set_io() to indicate that a given fd
 *	should be watched for ability to input or output data.  Call clear_io()
 *	to remove it from the list.  When poll()/select() indicates that the fd
 *	is available for the indicated type of i/o, the corresponding routine
 *	is called.  Call clear_io() to remove an fd from the list.
 *	Both set_io() and clear_io() can be called from within read_proc or
 *	write_proc (although turning an io descriptor on or off is better
 *	accomplished by setting the events flag in the xio structure, and
 *	in the corresponding pollfd structure if the pfd pointer is not NULL
 *	(it is always non-NULL when read_proc and write_proc are called)).
 *	We allocate space for one additional record in the pollfd array, to
 *	accommodate the fd for the X connection; this is done by initializing
 *	num_fds to 1 instead of zero.
 */

static	struct xio	*iorecs	= NULL;	/* head of xio list */

#if HAVE_POLL
static	struct pollfd	*fds	= NULL;
static	int		num_fds	= 1;	/* current number of fds */
static	int		max_fds	= 0;	/* max allocated number of fds */
static	Boolean		io_dirty= True;	/* need to recompute fds[] array */
#else
static	int		numfds	= 0;
static	fd_set		readfds;
static	fd_set		writefds;
#endif

void
set_io(struct xio *ip)
{
    ip->next = iorecs;
    iorecs = ip;

#if HAVE_POLL
    ++num_fds;
    if (!io_dirty && num_fds <= max_fds) {
	fds[num_fds - 1].fd = ip->fd;
	fds[num_fds - 1].events = ip->xio_events;
	ip->pfd = &fds[num_fds - 1];
    }
    else {
	ip->pfd = NULL;
	io_dirty = True;
    }
#else
    if (numfds <= ip->fd) numfds = ip->fd + 1;
#endif
}

void
clear_io(struct xio *ip)
{
    struct xio	**ipp;

    for (ipp = &iorecs;;) {
	struct xio *ip2;

	ip2 = *ipp;
	if (ip2 == ip) break;
	ipp = &ip2->next;
    }
    *ipp = ip->next;

#if HAVE_POLL
    --num_fds;
    io_dirty = True;
#else
# if FLAKY_SIGPOLL
    numfds = ConnectionNumber(DISP);
# else
    numfds = (event_freq < 0 ? -1 : ConnectionNumber(DISP));
# endif
    for (ip = iorecs; ip != NULL; ip = ip->next)
	if (ip->fd > numfds)
	    numfds = ip->fd;
    ++numfds;
#endif /* !HAVE_POLL */
}

static void
do_sigpoll(void)
{
#if FLAKY_SIGPOLL
    sig_flags &= ~SF_POLL;
#else
    struct xio	*ip;

    sig_flags &= ~SF_POLL;

# if HAVE_POLL

    if (io_dirty) {
	struct pollfd *fp;

	if (num_fds > max_fds) {
	    if (fds != NULL) free(fds);
	    fds = xmalloc(num_fds * sizeof *fds);
	    memset(fds, 0, num_fds * sizeof *fds);
	    max_fds = num_fds;
	    fds->fd = ConnectionNumber(DISP);
	    fds->events = POLLIN;
	}
	fp = fds + 1;
	for (ip = iorecs; ip != NULL; ip = ip->next) {
	    fp->fd = ip->fd;
	    fp->events = ip->xio_events;
	    ip->pfd = fp;
	    ++fp;
	}
	io_dirty = False;
    }

    for (;;) {
	if (poll(fds + 1, num_fds - 1, 0) >= 0)
	    break;

	if (errno != EAGAIN && errno != EINTR) {
	    perror("xdvi: poll");
	    return;
	}
    }

    for (ip = iorecs; ip != NULL; ip = ip->next) {
	int revents = ip->pfd->revents;
	if (revents & POLLIN && ip->read_proc != NULL)
	    (void)(ip->read_proc)(ip->fd, ip->data);
	if (revents & POLLOUT && ip->write_proc != NULL)
	    (ip->write_proc)(ip->fd, ip->data);
    }

# else

    FD_ZERO(&readfds);
    FD_ZERO(&writefds);
    for (ip = iorecs; ip != NULL; ip = ip->next) {
	if (ip->xio_events & XIO_IN)
	    FD_SET(ip->fd, &readfds);
	if (ip->xio_events & XIO_OUT)
	    FD_SET(ip->fd, &writefds);
    }

    for (;;) {
	struct timeval tv;

	tv.tv_sec = tv.tv_usec = 0;
	if (select(numfds, &readfds, &writefds, (fd_set *) NULL, &tv) >= 0)
	    break;

	if (errno != EAGAIN && errno != EINTR) {
	    perror("select (xdvi read_events)");
	    return;
	}
    }

    for (ip = iorecs; ip != NULL; ip = ip->next) {
	if (FD_ISSET(ip->fd, &readfds) && ip->read_proc != NULL)
	    (void)(ip->read_proc)(ip->fd, ip->data);
	if (FD_ISSET(ip->fd, &writefds) && ip->write_proc != NULL)
	    (ip->write_proc)(ip->fd, ip->data);
    }

# endif
#endif /* not FLAKY_SIGPOLL */
}


/*
 *	Timer-related routines.  Call set_timer() to set a timer a given number
 *	of milliseconds in the future.  At that time, the timer will be cleared
 *	and the given procedure will be called with argument set to the struct
 *	passed to set_timer().  The timer routine may call set_timer() or
 *	cancel_timer().
 */


static struct xtimer *timers = NULL;	/* head of timer list */

static struct itimerval itv = {{0, 0}, {0, 0}};

#ifndef	timercmp
#define	timercmp(a, b, OP)	(((a)->tv_sec OP (b)->tv_sec || \
	((a)->tv_sec == (b)->tv_sec && (a)->tv_usec OP (b)->tv_usec)))
#endif	/* timercmp */


static void
show_timers(const char *what)
{
    struct xtimer *tp;
    fprintf(stderr, "=======%s; timers:\n", what);
    for (tp = timers; tp != NULL; tp = tp->next) {
	fprintf(stderr, "timer %p: %lu\n", (void *)tp, (unsigned long)tp->when.tv_sec);
    }
    fprintf(stderr, "=======\n");
}

void
set_timer(struct xtimer *tp, int ms)
{
    struct xtimer	**tpp;
    struct xtimer	*tp2;

    if (globals.debug & DBG_EVENT)
	fprintf(stderr, "%s:%d: set_timer\n", __FILE__, __LINE__);

    gettimeofday(&tp->when, NULL);
    itv.it_value.tv_sec = ms / 1000;
    itv.it_value.tv_usec = (ms % 1000) * 1000;
    tp->when.tv_sec += itv.it_value.tv_sec;
    tp->when.tv_usec += itv.it_value.tv_usec;
    if (tp->when.tv_usec >= 1000000) {
	tp->when.tv_usec -= 1000000;
	++tp->when.tv_sec;
    }

    for (tpp = &timers;;) {		/* add timer to list */
	tp2 = *tpp;
	if (tp2 == NULL || timercmp(&tp->when, &tp2->when, <))
	    break;
	tpp = &tp2->next;
    }
    tp->next = tp2;
    *tpp = tp;

    if (tpp == &timers) {
	setitimer(ITIMER_REAL, &itv, NULL);
	if (ms == 0)
	    sig_flags |= SF_ALRM;
    }
    if (globals.debug & DBG_EVENT)
	show_timers("after set_timer");
}

void
cancel_timer(struct xtimer *tp)
{
    struct xtimer **tpp;

    if (globals.debug & DBG_EVENT)
	show_timers("beginning of cancel_timer");
    
    if (timers == NULL) {
	fprintf(stderr, "%s:%d: BUG? timers == NULL!\n", __FILE__, __LINE__);
	return;
    }

    if (globals.debug & DBG_EVENT)
	fprintf(stderr, "%s:%d: cancel_timer %p from %p\n", __FILE__, __LINE__, (void *)&timers, (void *)tp);

    ASSERT(timers != NULL, "timers in cancel_timer() mustn't be NULL");
    for (tpp = &timers; ; ) {		/* remove from list */
	if (*tpp == tp)
	    break;
	tpp = &(*tpp)->next;
    }

    *tpp = (*tpp)->next;	/* unlink it */

    if (timers == NULL) {	/* cancel SIGALRM */
	itv.it_value.tv_sec = itv.it_value.tv_usec = 0;
	setitimer(ITIMER_REAL, &itv, NULL);
    }
}

#if XDVI_XT_TIMER_HACK
/*
 * Original comment by Paul:
 *	Newer versions of the Motif toolkit use the timer facility
 *	(XtAppAddTimeOut(), etc.) in the X Toolkit.  Proper functioning of
 *	this mechanism, however, requires that the X Toolkit be in charge of
 *	blocking.  Since xdvi does its own blocking, this means that we need
 *	to provide working alternatives to these X Toolkit routines (by
 *	redefining them ...).
 *	One symptom of the above-mentioned bug is that the printlog window
 *	eventually stops showing dvips progress until you move the mouse.
 *
 * Comment SU:
 *	Xdvik also uses XtAppAddTimeOut() in other places (hyperref/statusline/...),
 *	so we also need these redefinitions also in the Xaw version.
 *
 */

static void xt_alarm(struct xtimer *this, void *data);

static struct xtimer *xt_free_timers = NULL;

XtIntervalId
XtAddTimeOut(unsigned long interval, XtTimerCallbackProc proc, XtPointer closure)
{
    return XtAppAddTimeOut(NULL, interval, proc, closure);
}

XtIntervalId
XtAppAddTimeOut(XtAppContext app, unsigned long interval, XtTimerCallbackProc proc, XtPointer closure)
{
    struct xtimer *tp;
    
    UNUSED(app);

    /* FIXME: better way of checking this instead of static boolean sigalarm_initialized?
       The following doesn't work, even after
       sigaddset(&all_signals, SIGALRM);
       
       static sigset_t sig_set;
       (void)sigprocmask(0, NULL, &sig_set);
       if (sigismember(&sig_set, SIGALRM))
       ... OK ...
       else
       ... NOT OK ...
    */
    ASSERT(sigalarm_initialized, "Shouldn't invoke XtAppAddTimeOut() before setup_sigalarm()");
    
    if (globals.debug & DBG_EVENT)
	fprintf(stderr, "XtAppAddTimeOut: %lu msecs\n", interval);
    
    if (xt_free_timers == NULL)
	tp = xmalloc(sizeof *tp);
    else {
	tp = xt_free_timers;
	xt_free_timers = xt_free_timers->next;
    }

    tp->proc = xt_alarm;
    tp->data = closure;
    tp->xt_proc = proc;
    tp->closure = closure;
    
    set_timer(tp, interval);

    if (globals.debug & DBG_EVENT)
	show_timers("XtAppAddTimeOut");

    return (XtIntervalId)tp;
}

void
XtRemoveTimeOut(XtIntervalId id)
{
    struct xtimer *tp;

    ASSERT(id != 0, "XtIntervalId argument in XtRemoveTimeOut() mustn't be NULL");
    tp = (struct xtimer *)id;

    /* Motif (2.1 on Solaris 9, 2003) sometimes calls XtRemoveTimeOut() after
       the timer event has occurred, so we need to be sure not to remove
       the timer record twice.  */
    if (tp->proc == NULL)
	return;
    
    cancel_timer(tp);
    
    tp->next = xt_free_timers;
    xt_free_timers = tp;

    if (globals.debug & DBG_EVENT)
	show_timers("XtRemoveTimeOut");
}

void
xt_alarm(struct xtimer *tp, void *data)
{
    XtIntervalId id;
    UNUSED(data);

    tp->proc = NULL;	/* flag timer as used-up */
    id = (XtIntervalId)tp;
    (tp->xt_proc)(tp->closure, &id);
    
    tp->next = xt_free_timers;
    xt_free_timers = tp;

    if (globals.debug & DBG_EVENT)
	show_timers("xt_alarm");
}
#endif /* XDVI_XT_TIMER_HACK */

static	void
do_sigalrm(void)
{
    struct timeval now;

    sig_flags &= ~SF_ALRM;
#ifndef HAVE_SIGACTION
    (void) signal(SIGALRM, handle_sigalrm);	/* reset the signal */
#endif

    gettimeofday(&now, NULL);

    while (timers != NULL && timercmp(&timers->when, &now, <=)) {
	struct xtimer *tp = timers;
	timers = timers->next;	/* unlink it _first_ */
	(tp->proc)(tp, tp->data);
    }

    if (timers != NULL) {		/* set next timer */
	int i;
	itv.it_value.tv_sec = timers->when.tv_sec - now.tv_sec;
	i = timers->when.tv_usec - now.tv_usec;
	if (i < 0) {
	    --itv.it_value.tv_sec;
	    i += 1000000;
	}
	itv.it_value.tv_usec = i;

	setitimer(ITIMER_REAL, &itv, NULL);
    }
}



/*
 *	Handle SIGUSR1 signal.  Pretty straightforward.
 */

static void
do_sigusr(void)
{
    sig_flags &= ~SF_USR;
#ifndef HAVE_SIGACTION
    (void) signal(SIGUSR1, handle_sigusr);	/* reset the signal */
#endif
    globals.ev.flags |= EV_RELOAD;
}


static void
do_sigsegv(void)
{
    sig_flags &= ~SF_SEGV;
#ifndef HAVE_SIGACTION
    (void) signal(SIGSEGV, handle_sigsegv);	/* reset the signal */
#endif
    handle_sigsegv(SIGSEGV);
}


/*
 *	Handle termination signals.  Just call xdvi_exit, which does all the
 *	work, since that is the common exit point.
 */

static void
do_sigterm(void)
{
    sig_flags &= ~SF_TERM;

    xdvi_exit(EXIT_SUCCESS);
}

/*
 * This routine should be used for all exits. (SU: This is different
 * from non-k xdvi, where it's only used for `non-early' exits; all
 * routines called here should be aware of their own state and either
 * perform cleanup or just return, unless xdvi_exit() itself checks for
 * the status).
 */

void
xdvi_exit(int status)
{
    struct xchild   *cp;

    /* do the following only if the window has been opened: */
    if (globals.widgets.top_level != 0 && XtIsRealized(globals.widgets.top_level)) {
	char *filehist;
	file_history_set_page(current_page);
	filehist = file_history_get_list();
	store_preference(NULL, "fileHistory", "%s", filehist);
	free(filehist);

#if MOTIF
	if (preferences_changed()) {
	    return;
	}
	/*      else { */
	/*  	fprintf(stderr, "Preferences not changed.\n"); */
	/*      } */
#endif
	/* try to save user preferences, unless we're exiting with an error */
	if (status == EXIT_SUCCESS && !save_user_preferences(True))
	    return;
    
	/* Clean up the "xdvi windows" property in the root window.  */
	update_window_property(XtWindow(globals.widgets.top_level), False);
    }

#if PS
    ps_destroy_nofree();
#endif

    /* loop through child processes */
    for (cp = child_recs; cp != NULL; cp = cp->next) {
	if (cp->killsig > 0)
	    kill(cp->pid, cp->killsig);
	else if (cp->killsig < 0)
	    kill(-cp->pid, -cp->killsig);
    }

    call_exit_handlers();

    exit(status);
}


#if HAVE_XI21

/*
 *	XInput 2.1 event processing.
 *
 *	The XInput system has (virtual) "master" devices, including a master
 *	pointer and master keyboard (and maybe others), and "slave" devices
 *	that can be assigned to a master.  Each master can have only one slave
 *	at a time.  If you have two mice plugged in, for example, each is a
 *	separate slave device, and the one assigned to the master pointer device
 *	is whichever one was moved or clicked most recently.  Moving or clicking
 *	the other mouse would then cause an XInput SlaveSwitch event, which
 *	notifies the X client that a different pointer is now active (and the
 *	previously active mouse would then become inactive).
 *
 *	The code here looks not just at events from the master pointer, it also
 *	tracks events from the slave pointer devices.  This is because the
 *	valuators for each slave pointer may have different scales.
 *
 *	The XI_Motion events contain the master and slave ids of the device
 *	sending the event, so in principle it wouldn't be necessary to track
 *	SlaveSwitch events.  However, we do track these events, because we need
 *	to know the valuator values at the time of the switch (otherwise, e.g.,
 *	the first click of a wheel mouse would be lost).
 *
 *	It is also possible that a valuator may change while the pointer is
 *	outside of the drawing window.  So, we need to reset the valuator
 *	values upon XI_Enter events.  For some strange reason, we get extra
 *	XI_Enter events when using a traditional wheel mouse, and so the code
 *	takes care to ignore those.  This only happens with the Xaw toolkit.
 *
 *	Slave devices have events that notify clients when they are (i) added
 *	or removed, (ii) attached or detached, and (iii) enabled or disabled.
 *	We only worry about (iii), and for disabled devices we don't bother to
 *	delete the data structure.
 *
 */

/*
 *	xi2_find_master:  set xi2_current to record for given device id
 *	Return True if found, False if not.
 */

static Boolean
xi2_find_master(int id)
{
	struct xi2_master *mp;

	for (mp = xi2_masters; mp != NULL; mp = mp->next)
	    if (mp->id == id) {
		xi2_current = mp;
		return True;
	    }
	return False;
}

/*
 *	xi2_find_slave:  set xi2_current->slave to record for given slave device
 *	Return True if found, False if not.
 */

static Boolean
xi2_find_slave(int id)
{
	struct xi2_slave *sp;

	for (sp = xi2_slaves; sp != NULL; sp = sp->next)
	    if (sp->id == id) {
		xi2_current->slave = sp;
		return True;
	    }
	return False;
}

/*
 *	Handle XI2 Enter event.  We need to look at these events because if the
 *	user scrolls in some other window, then the valuator will be changed
 *	by a large amount, and that scrolling should not occur also in the
 *	main xdvi window when the user resumes scrolling here.
 */

static void
xi2_ev_enter(XIEnterEvent *xi_event)
{
	XIDeviceInfo	*info;
	int		ndevices;
	int		i;

	if (xi_event->time < xi2_ign_time + 5) {
	    TRACE_EVENTS((stderr, "Ignoring XI_Enter event as spurious."));
	    return;
	}

	if (xi2_current->id != xi_event->deviceid
	  && !xi2_find_master(xi_event->deviceid)) {
	    TRACE_EVENTS((stderr,
		"Ignoring XI_Enter event: master device %d not found",
		  xi_event->deviceid));
	    return;
	}

	if (xi_event->sourceid == xi2_current->id) {
	    TRACE_EVENTS((stderr,
	      "Ignoring XI_Enter event for master device."));
	    return;
	}
	else if (xi_event->sourceid != xi2_current->slave->id) {
	    struct xi2_slave *prev_slave = xi2_current->slave;

	    if (!xi2_find_slave(xi_event->sourceid)) {
		TRACE_EVENTS((stderr,
		  "Ignoring XI_Enter event: device %d->%d not found.",
		  xi_event->deviceid, xi_event->sourceid));
		return;
	    }
	    TRACE_EVENTS((stderr,
	      "%s %d->%d",
	      prev_slave == &xi2_no_slave
		? "Implicit switch in XI_Enter to"
		: "Received out-of-turn XI_Enter event for",
	      xi_event->deviceid, xi_event->sourceid));
	    if (!xi2_current->slave->enabled)
		TRACE_EVENTS((stderr, "Caution: slave is not enabled."));
	}
	else {
	    TRACE_EVENTS((stderr, "Received XI_Enter event"));
	}

	info = XIQueryDevice(DISP, xi_event->sourceid, &ndevices);
	if (info == NULL || ndevices != 1) {
	    TRACE_EVENTS((stderr, "xi2_ev_enter:  XIQueryDevice failed for %d",
	      xi_event->sourceid));
	    return;
	}

	for (i = 0; i < info->num_classes; ++i)
	    if (info->classes[i]->type == XIValuatorClass) {
		XIValuatorClassInfo *valuator;

		valuator = (XIValuatorClassInfo *) info->classes[i];
		if (valuator->number == xi2_current->slave->vert.number) {
		    xi2_current->slave->vert.lastexact =
		    xi2_current->slave->vert.lastval = valuator->value;
		    xi2_current->slave->vert.serial =
		      LastKnownRequestProcessed(DISP);
		}
		else if (valuator->number == xi2_current->slave->horiz.number) {
		    xi2_current->slave->horiz.lastexact =
		    xi2_current->slave->horiz.lastval = valuator->value;
		    xi2_current->slave->horiz.serial =
		      LastKnownRequestProcessed(DISP);
		}
	    }
	XIFreeDeviceInfo(info);
}

static void
xi2_emulate_action(struct xdvi_action *actp, struct xi2_valinfo *valinfo,
	double value)
{
	double factor;
	int dist;

	for (; actp != NULL; actp = actp->next) {
	    if (actp->proc == Act_wheel || actp->proc == Act_hwheel) {
		if (actp->num_params == 0)
		    factor = resource.wheel_unit;
		else if (index(actp->params[0], '.') == NULL)
		    factor = atoi(actp->params[0]);
		else
		    factor = atof(actp->params[0]) * resource.wheel_unit;
		factor /= valinfo->increment;

		if (valinfo->factor != fabs(factor)) {
		    valinfo->lastval = valinfo->lastexact;
		    valinfo->factor = fabs(factor);
		}

		/*
		 * In extreme cases, the server may reset the valuator to zero
		 * to avoid large values.  The next few lines avoid the extreme
		 * scrolling event that would otherwise occur in this event.
		 */
		if (fabs(value - valinfo->lastval) > INT_MAX / 2) {
		    valinfo->lastval +=
		      value > valinfo->lastval ? INT_MAX : -INT_MAX;
		    TRACE_EVENTS((stderr,
		      "Adjusted for pointer overflow; lastval = %g, new = %g\n",
		      valinfo->lastval, value));
		}

		dist = (value - valinfo->lastval) * factor;
		if (dist == 0) {
		    valinfo->lastexact = value;
		    continue;
		}

		if (actp->proc == Act_wheel) {
#  if !MOTIF
		    if (globals.widgets.y_bar != NULL)
			XtCallCallbacks(globals.widgets.y_bar, XtNscrollProc,
			  cast_int_to_XtPointer(dist));
#  else /* MOTIF */
		    get_xy();
		    set_bar_value(globals.widgets.y_bar,
		      dist - m_window_y, (int) (globals.page.h - mane.height));
#  endif /* MOTIF */
		}
		else {	/* Act_hwheel */
#  if !MOTIF
		    if (globals.widgets.x_bar != NULL)
			XtCallCallbacks(globals.widgets.x_bar, XtNscrollProc,
			  cast_int_to_XtPointer(dist));
#  else /* MOTIF */
		    get_xy();
		    set_bar_value(globals.widgets.x_bar,
		      dist - m_window_x, (int) (globals.page.w - mane.width));
#  endif /* MOTIF */
		}
		/* The next line puts back any rounding error */
		valinfo->lastval += dist / factor;
		valinfo->lastexact = value;
	    }
	    else if (actp->proc == Act_mouse_modes) {
		size_t mode_idx = 0; /* default: one action for all modes */
		struct xdvi_action *my_action = NULL;

		if (actp->num_params > 1) { /* if different actions */
		    if (resource.mouse_mode >= actp->num_params) {
			size_t k;

			XDVI_WARNING((stderr, "X action 'mouse-modes' called "
			    "with only %d parameters, should be %d",
			  actp->num_params, resource.mouse_mode + 1));
			for (k = 0; k < actp->num_params; k++) {
			    XDVI_WARNING((stderr, "  Param %d: `%s'",
			      (int)(k + 1), actp->params[k]));
			}
			return;
		    }
		    mode_idx = resource.mouse_mode;
		}

		cached_compile_action(actp->params[mode_idx], &my_action);
		xi2_emulate_action(my_action, valinfo, value);
	    }
	    else
		TRACE_EVENTS((stderr, "Mouse_translations action is neither "
		    "wheel() nor hwheel(); ignoring."));
	}
}

static void
xi2_do_valuator(XIDeviceEvent *xi_event, struct xi2_valinfo *valinfo,
	int button, double value)
{
	struct mouse_acts *mactp;
	Modifiers state;

	state = xi_event->buttons.mask[0];
	if (xi_event->buttons.mask_len >= 2) {
	    state |= xi_event->buttons.mask[1] << 8;
	    if (xi_event->buttons.mask_len >= 3) {
		state |= xi_event->buttons.mask[2] << 16;
		if (xi_event->buttons.mask_len >= 4)
		    state |= xi_event->buttons.mask[3] << 24;
	    }
	}

	for (mactp = mouse_actions; mactp != NULL; mactp = mactp->next)
	    if (mactp->button == button || mactp->button == 0) {
		Modifiers mask = 0;
		Modifiers mods_value = 0;

		if (mactp->late_bindings == NULL
		  || _XtComputeLateBindings(DISP, mactp->late_bindings,
		  &mods_value, &mask)) {
		    mask |= mactp->mask;
		    mods_value |= mactp->value;
		    if (((mods_value ^ state) & mask) == 0) {
			xi2_emulate_action(mactp->action, valinfo, value);
			return;
		    }
		}
	    }
}

static void
xi2_ev_motion(XIDeviceEvent *xi_event)
{
	double *val;
	int i;
	XEvent event;

	if (xi2_current->id != xi_event->deviceid
	  && !xi2_find_master(xi_event->deviceid)) {
	    TRACE_EVENTS((stderr,
	      "Ignoring XI_Motion event: master device %d not found",
	      xi_event->deviceid));
	    return;
	}

	if (xi2_current->slave->id != xi_event->sourceid) {
	    struct xi2_slave *prev_slave = xi2_current->slave;

	    if (!xi2_find_slave(xi_event->sourceid)) {
		TRACE_EVENTS((stderr,
		  "Ignoring XI_Motion event: device %d->%d not found.",
		  xi_event->deviceid, xi_event->sourceid));
		return;
	    }
	    TRACE_EVENTS((stderr, "%s %d->%d\n",
	      prev_slave == &xi2_no_slave
		? "Implicit switch in XI_Motion to"
		: "Received out-of-turn XI_Motion event for",
	      xi_event->deviceid, xi_event->sourceid));
	    if (!xi2_current->slave->enabled)
		TRACE_EVENTS((stderr, "Caution: slave is not enabled."));
	}

	val = xi_event->valuators.values;
	for (i = 0; i < xi_event->valuators.mask_len * 8; ++i)
	    if (XIMaskIsSet(xi_event->valuators.mask, i)) {
		if (i == xi2_current->slave->vert.number) {
		    if ((long) (xi_event->serial
		      - xi2_current->slave->vert.serial) < 0) {
			TRACE_EVENTS((stderr,
			  "Vertical valuator in EnterEvent was outdated "
			    "(%lu < %lu)",
			  xi_event->serial, xi2_current->slave->vert.serial));
			xi2_current->slave->vert.serial = xi_event->serial;
			xi2_current->slave->vert.lastexact =
			xi2_current->slave->vert.lastval = *val;
		    }
		    else
			xi2_do_valuator(xi_event, &xi2_current->slave->vert, 5,
			  *val);
		}
		else if (i == xi2_current->slave->horiz.number) {
		    if ((long) (xi_event->serial
		      - xi2_current->slave->horiz.serial) < 0) {
			TRACE_EVENTS((stderr,
			  "Horizontal valuator in EnterEvent was outdated"));
			xi2_current->slave->horiz.serial = xi_event->serial;
			xi2_current->slave->horiz.lastexact =
			xi2_current->slave->horiz.lastval = *val;
		    }
		    else
			xi2_do_valuator(xi_event, &xi2_current->slave->horiz, 7,
			  *val);
		}
		++val;
	    }

	/* For some reason the X server doesn't want to send a core motion
	 * event if an XInput motion event is already being sent.
	 * So we need to fake it.  The routines called from Act_motion()
	 * should be sure to use only the fields provided here.  */
	event.xmotion.window = xi_event->event;
	event.xmotion.x = xi_event->event_x;
	event.xmotion.y = xi_event->event_y;
	event.xmotion.x_root = xi_event->root_x;
	event.xmotion.y_root = xi_event->root_y;
	Act_motion(NULL, &event, NULL, NULL);
}

static void
xi2_ev_devchange(XIDeviceChangedEvent *xi_event)
{
	struct xi2_slave *sp;

	if (xi_event->reason == XISlaveSwitch) {
	    struct xi2_master *mp;

	    TRACE_EVENTS((stderr,
	      "Received XI_DeviceChanged event, XISlaveSwitch, device = %d->%d",
	      xi_event->deviceid, xi_event->sourceid));

	    for (sp = xi2_slaves;; sp = sp->next) {
		if (sp == NULL) {
		    TRACE_EVENTS((stderr, "ignoring (device not found)"));
		    return;
		}
		if (sp->id == xi_event->sourceid)
		    break;
	    }

	    for (mp = xi2_masters;; mp = mp->next) {
		if (mp == NULL) {
		    TRACE_EVENTS((stderr,
			"Cannot switch slave (master device not found)"));
		    return;
		}
		if (mp->id == xi_event->deviceid)
		    break;
	    }
	    mp->slave = sp;

	    /* A valuator may change when a slave is switched off */
	    xi2_init_valuators(sp, xi_event->classes, xi_event->num_classes);

	    if (!sp->enabled)
		TRACE_EVENTS((stderr, "Caution: slave device is not enabled."));
	}
	else if (xi_event->reason == XIDeviceChange) {
	    /* In principle, this will add any device, but there's no "use" */
	    /* field, so no easy way to check if it's a suitable slave device.*/
	    TRACE_EVENTS((stderr,
	     "Received XI_DeviceChanged event, XIDeviceChange, device = %d->%d",
	      xi_event->deviceid, xi_event->sourceid));

	    for (sp = xi2_slaves;; sp = sp->next) {
		if (sp == NULL) {
		    TRACE_EVENTS((stderr, " (new device)"));
		    sp = xmalloc(sizeof (struct xi2_slave));
		    sp->id = xi_event->sourceid;
		    sp->enabled = 0;
		    sp->next = xi2_slaves;
		    xi2_slaves = sp;
		    break;
		}
		if (sp->id == xi_event->sourceid)
		    break;
	    }

	    xi2_init_valuators(sp, xi_event->classes, xi_event->num_classes);

	    if (!xi2_active && sp->btn_mask) {
		TRACE_EVENTS((stderr, "Activating XI2 now."));
		xi2_activate();
	    }
	}
	else {
	    TRACE_EVENTS((stderr,
	      "Ignoring XI_DeviceChanged event with unknown reason %x, "
		"device = %d->%d",
	      xi_event->reason, xi_event->deviceid, xi_event->sourceid));
	}
}


static void
xi2_ev_hierchange(XIHierarchyEvent *xi_event)
{
	XIHierarchyInfo	*infp, *inf_end;

	if (globals.debug & DBG_EVENT) {
	    fprintf(stderr, "%s:%d: XI_HierarchyChanged event: ",
	       __FILE__, __LINE__);
	    if (xi_event->flags & XIMasterAdded)
		fputs(" XIMasterAdded", stderr);
	    if (xi_event->flags & XIMasterRemoved)
		fputs(" XIMasterRemoved", stderr);
	    if (xi_event->flags & XISlaveAdded)
		fputs(" XISlaveAdded", stderr);
	    if (xi_event->flags & XISlaveRemoved)
		fputs(" XISlaveRemoved", stderr);
	    if (xi_event->flags & XISlaveAttached)
		fputs(" XISlaveAttached", stderr);
	    if (xi_event->flags & XISlaveDetached)
		fputs(" XISlaveDetached", stderr);
	    if (xi_event->flags & XIDeviceEnabled)
		fputs(" XIDeviceEnabled", stderr);
	    if (xi_event->flags & XIDeviceDisabled)
		fputs(" XIDeviceDisabled", stderr);
	    if (!xi_event->flags)
		fputs("no flags(?)", stderr);
	    fputc('\n', stderr);
	}

	if (!(xi_event->flags & (XIDeviceEnabled | XIDeviceDisabled)))
	    return;

	inf_end = xi_event->info + (xi_event->num_info - 1);
	for (infp = xi_event->info; infp <= inf_end; ++infp) {
	    struct xi2_slave *sp;

	    if (infp->use != XISlavePointer && infp->use != XIFloatingSlave)
		continue;

	    for (sp = xi2_slaves;; sp = sp->next) {
		if (sp == NULL) {
		    if (infp->enabled)
			TRACE_EVENTS((stderr,
			  "  Ignoring enable request for %d->%d (not found)",
			  infp->attachment, infp->deviceid));
		    break;
		}
		if (sp->id == infp->deviceid) {
		    if (infp->enabled != sp->enabled) {
			TRACE_EVENTS((stderr, "  %s %d->%d",
			  infp->enabled ? "Enabling" : "Disabling",
			  infp->attachment, infp->deviceid));
			sp->enabled = infp->enabled;
			if (!sp->enabled && xi2_current->slave == sp) {
			    TRACE_EVENTS((stderr,
				"  Removing from master device"));
			    xi2_current->slave = &xi2_no_slave;
			}
		    }
		    break;
		}
	    }
	}
}

#endif /* HAVE_XI21 */


/*
 *	Since redrawing the screen is (potentially) a slow task, xdvi checks
 *	for incoming events while this is occurring.  It does not register
 *	a work proc that draws and returns every so often, as the toolkit
 *	documentation suggests.  Instead, it checks for events periodically
 *	(or not, if SIGPOLL can be used instead) and processes them in
 *	a subroutine called by the page drawing routine.  This routine (below)
 *	checks to see if anything has happened and processes those events and
 *	signals.  (Or, if it is called when there is no redrawing that needs
 *	to be done, it blocks until something happens.)
 *
 *	Ultimately, the goal is to have this be the only place in xdvi where
 *	blocking occurs.
 *
 *	The argument to this function should be a mask of event types (EV_*)
 *	indicating which event types should cause read_events to return instead
 *	of waiting for more events.  This function will always process all
 *	pending events and signals before returning.
 *	The return value is the value of globals.ev.flags.
 */

unsigned int
read_events(unsigned int ret_mask)
{
    XEvent event;

#if !HAVE_POLL
    if (numfds == 0)
	numfds = ConnectionNumber(DISP) + 1;
#endif

    if (globals.debug & DBG_EVENT)
	fprintf(stderr, "%s:%d: read_events %u\n", __FILE__, __LINE__, ret_mask);
    for (;;) {
	globals.ev.ctr = event_freq;
	/*
	 * The above line clears the flag indicating that an event is
	 * pending.  So if an event comes in right now, the flag will be
	 * set again needlessly, but we just end up making an extra call.
	 * Also, be careful about destroying the magnifying glass while
	 * drawing on it.
	 */

#if !FLAKY_SIGPOLL

	if (event_freq < 0) {	/* if SIGPOLL works */
	    if (!XtPending()) {
		sigset_t oldsig;

		(void) sigprocmask(SIG_BLOCK, &all_signals, &oldsig);
		for (;;) {
#ifdef SHOW_SIG_FLAGS
		    /* this gives HUGE output ... */
		    if (globals.debug & DBG_EVENT)
			fprintf(stderr, "%s:%d: sig_flags = %d\n",
				__FILE__, __LINE__, sig_flags);
#endif
		    while (sig_flags) {
			flags_to_sigproc[sig_flags]();
		    }

		    if (XtPending())
			break;

		    if (globals.ev.flags & ret_mask) {
			(void) sigprocmask(SIG_SETMASK, &oldsig, (sigset_t *) NULL);
			return globals.ev.flags;
		    }
		    (void) sigsuspend(&oldsig);
		}
		(void) sigprocmask(SIG_SETMASK, &oldsig, (sigset_t *) NULL);
	    }
	}
	else

#endif /* not FLAKY_SIGPOLL */

	{
	    for (;;) {
		struct xio	*ip;

		if (globals.debug & DBG_EVENT)
		    fprintf(stderr, "%s:%d: (flaky) sig_flags = %d\n",
			    __FILE__, __LINE__, sig_flags);
		while (sig_flags) {
		    sigset_t oldsig;

		    (void) sigprocmask(SIG_BLOCK, &all_signals, &oldsig);

		    while (sig_flags) {
			flags_to_sigproc[sig_flags]();
		    }

		    (void) sigprocmask(SIG_SETMASK, &oldsig,
				       (sigset_t *) NULL);
		}

		if (XtPending())
		    break;

		if (globals.ev.flags & ret_mask)
		    return globals.ev.flags;

		/* If a SIGUSR1 signal comes right now, then it will wait
		   until an X event or another SIGUSR1 signal arrives. */

#if HAVE_POLL
		if (globals.debug & DBG_EVENT)
		    fprintf(stderr, "%s:%d: have_poll!\n",
			    __FILE__, __LINE__);
		if (io_dirty) {
		    struct pollfd *fp;

		    if (num_fds > max_fds) {
			if (fds != NULL) free(fds);
			fds = xmalloc(num_fds * sizeof *fds);
			max_fds = num_fds;
			fds->fd = ConnectionNumber(DISP);
			fds->events = POLLIN;
		    }
		    fp = fds + 1;
		    for (ip = iorecs; ip != NULL; ip = ip->next) {
			fp->fd = ip->fd;
			fp->events = ip->xio_events;
			ip->pfd = fp;
			++fp;
		    }
		    io_dirty = False;
		}

		for (;;) {
		    if (poll(fds, num_fds, -1) >= 0) {
			for (ip = iorecs; ip != NULL; ip = ip->next) {
			    int revents = ip->pfd->revents;

			    if (revents & POLLIN && ip->read_proc != NULL)
				(ip->read_proc)(ip->fd, ip->data);
			    if (revents & POLLOUT && ip->write_proc != NULL)
				(ip->write_proc)(ip->fd, ip->data);
			}
			break;
		    }

		    if (errno == EINTR)
			break;

		    if (errno != EAGAIN) {
			perror("xdvi: poll");
			break;
		    }
		}
#else /* HAVE_POLL */
		if (globals.debug & DBG_EVENT)
		    fprintf(stderr, "%s:%d: NOT have_poll!\n",
			    __FILE__, __LINE__);
		FD_ZERO(&readfds);
		FD_ZERO(&writefds);
		FD_SET(ConnectionNumber(DISP), &readfds);
		for (ip = iorecs; ip != NULL; ip = ip->next) {
		    if (ip->xio_events & XIO_IN)
			FD_SET(ip->fd, &readfds);
		    if (ip->xio_events & XIO_OUT)
			FD_SET(ip->fd, &writefds);
		}

		for (;;) {
		    if (select(numfds, &readfds, &writefds, (fd_set *) NULL,
			       (struct timeval *) NULL) >= 0) {
			for (ip = iorecs; ip != NULL; ip = ip->next) {
			    if (FD_ISSET(ip->fd, &readfds) && ip->read_proc != NULL) {
				if (globals.debug & DBG_EVENT)
				    fprintf(stderr, "%s:%d: reading from %d\n",
					    __FILE__, __LINE__, ip->fd);
				(ip->read_proc)(ip->fd, ip->data);
			    }
			    if (FD_ISSET(ip->fd, &writefds) && ip->write_proc != NULL) {
				if (globals.debug & DBG_EVENT)
				    fprintf(stderr, "%s:%d: writing to %d\n",
					    __FILE__, __LINE__, ip->fd);
				(ip->write_proc)(ip->fd, ip->data);
			    }
			}
			break;
		    }

		    if (errno == EINTR)
			break;

		    if (errno != EAGAIN) {
			perror("xdvi: select");
			break;
		    }
		}
#endif /* HAVE_POLL */
	    }
	}

	XtAppNextEvent(globals.app, &event);

#ifdef MOTIF
	if ((resource.expert_mode & XPRT_SHOW_TOOLBAR) != 0)
	    TipAppHandle(globals.app, &event);
#endif

#if HAVE_XI21
	if (event.xany.type == GenericEvent
	  && event.xcookie.extension == xi2_opcode) {
	    if (!XGetEventData(DISP, &event.xcookie)) {
		TRACE_EVENTS((stderr,
		  "Received XI2 event, of type %d, with no cookie",
		  event.xcookie.evtype));
	    }
	    else {
		switch (event.xcookie.evtype) {

		    case XI_HierarchyChanged:
			xi2_ev_hierchange(event.xcookie.data);
			break;

		    case XI_DeviceChanged:
			xi2_ev_devchange(event.xcookie.data);
			break;

		    case XI_Enter:
			xi2_ev_enter(event.xcookie.data);
			break;

		    case XI_Motion:
			/* This needs to be filled in by the client */
			((XIDeviceEvent *) event.xcookie.data)->serial
			  = event.xany.serial;
			xi2_ev_motion(event.xcookie.data);
			break;

		    default:
			TRACE_EVENTS((stderr,
			  "Received XI2 event of unknown type %d",
			  event.xcookie.evtype));

		}
		XFreeEventData(DISP, &event.xcookie);
	    }
	    continue;
	}
#endif /* HAVE_XI21 */

	if (resized)
	    get_geom();

	if (event.xany.window == magnifier.win && event.type == Expose) {
	    handle_expose((Widget) NULL, (XtPointer)&magnifier, &event,
			  (Boolean *) NULL);
	    continue;
	}
	else if (globals.broken_motif_event_handling &&
		 (globals.cursor.flags & (CURSOR_RULER | CURSOR_TEXT))) {
	    /* In this case, Act_motion() and Act_release() are not called properly
	     * for updating the ruler/text selection (it works with the magnifier though),
	     * so we need to invoke them ourselves here: */
	    if (event.type == MotionNotify)
		Act_motion(NULL, &event, NULL, NULL);
	    else if (event.type == ButtonRelease)
		Act_release(NULL, &event, NULL, NULL);
	}

#ifdef MOTIF
	if (XtIsRealized(globals.widgets.top_level)
	    && event.xany.window == XtWindow(globals.widgets.clip_widget)
	    && event.type == KeyPress) { /* workaround for #610206 */
	    motif_translations_hack();
	}
#else
	if (resource.expert_mode & XPRT_SHOW_BUTTONS)
	    SubMenuHandleEvent(globals.app, &event);
#endif
	XtDispatchEvent(&event);
    }
}


/*
 * Higher-level routines for managing events.
 */

static void
can_exposures(struct WindowRec *windowrec)
{
    windowrec->min_x = windowrec->min_y = MAXDIM;
    windowrec->max_x = windowrec->max_y = 0;
}

void
redraw(struct WindowRec *windowrec)
{
    currwin = *windowrec;
    globals.win_expose.min_x = currwin.min_x + currwin.base_x;
    globals.win_expose.min_y = currwin.min_y + currwin.base_y;
    globals.win_expose.max_x = currwin.max_x + currwin.base_x;
    globals.win_expose.max_y = currwin.max_y + currwin.base_y;
    can_exposures(windowrec);

    /* fix for bug #619070 - the complicated flags (and do_update_property)
       are needed to avoid too many updates at exposures, especially for
       a window of another xdvi instance when the magnifier intersects with
       that window.
    */
    if (have_src_specials && do_update_property
	&& globals.win_expose.min_x != 1
	&& globals.win_expose.max_y - globals.win_expose.min_y != 1
	&& currwin.base_x == 0 && currwin.base_y == 0) {
	update_window_property(XtWindow(globals.widgets.top_level), True);
    }

    TRACE_EVENTS((stderr, "Redraw %d x %d at (%d, %d) (base=%d,%d)",
		  globals.win_expose.max_x - globals.win_expose.min_x,
		  globals.win_expose.max_y - globals.win_expose.min_y,
		  globals.win_expose.min_x, globals.win_expose.min_y,
		  currwin.base_x, currwin.base_y));

    /* can't use ev_cursor here, since the event loop might not see this change quick enough */
    if (!(globals.ev.flags & EV_CURSOR)) {
	TRACE_EVENTS((stderr, "Cursor: %ld", globals.cursor.flags));
	if (!(globals.cursor.flags & (CURSOR_MAG | CURSOR_DRAG_H | CURSOR_DRAG_V | CURSOR_DRAG_A))) {
	    if (resource.mouse_mode == MOUSE_MODE3)
		XDefineCursor(DISP, CURSORWIN, globals.cursor.mode3);
	    else
		XDefineCursor(DISP, CURSORWIN, globals.cursor.wait);
	    XFlush(DISP);
	}
	globals.ev.flags |= EV_CURSOR;
    }

    /* No longer needed since windows are correctly transient now */
    /*      raise_message_windows(); */
    raise_file_selector();
    draw_page();
    globals.warn_spec_now = False;
}


void
redraw_page(void)
{
#if COLOR
    const struct rgb *rgbp;
#endif
    TRACE_FILES((stderr, "Redraw page on %p", (void *)globals.dvi_file.bak_fp));

    if (globals.dvi_file.bak_fp == NULL)
	return;

    if (scanned_page < current_page) {
	TRACE_FILES((stderr, "redraw_page: scanned_page = %d, current_page = %d, prescanning %p\n",
		     scanned_page, current_page, (void *)globals.dvi_file.bak_fp));

	prescan(globals.dvi_file.bak_fp);

	if (globals.ev.flags & EV_GE_NEWPAGE) {	/* if we need to re-prescan */
	    return;
	}
    }

    TRACE_FILES((stderr, "redraw_page: current_page = %d", current_page));
    if (pageinfo_get_window_width(current_page) != globals.page.unshrunk_w
	|| pageinfo_get_window_height(current_page) != globals.page.unshrunk_h) {
	TRACE_FILES((stderr, "NEW SIZE: %dx%d",
		     pageinfo_get_window_width(current_page), pageinfo_get_window_height(current_page)));
	init_page();
	reconfig();
    }
    
    /* We can't call home() without proper unshrunk_page_*, which requires
     * prescan(), which can't be done from within read_events() */

    /* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!! BUG ALERT !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
       There's some complicated interaction with Postscript specials
       here: if home_action check comes before the gs stuff, psp.drawfile
       might not get initialized correctly, resulting in empty PS figures
       (bounding box instead of figure). This is different in xdvi, due to
       different handling of the home_action stuff, but at the moment I can't
       remember the reason for this ...
       !!!!!!!!!!!!!!!!!!!!!!!!!!!!!! BUG ALERT !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
    */
#if PS_GS
    if (gs_postpone_prescan) {
	if (!setjmp(globals.ev.canit)) {
	    gs_resume_prescan();
	}
	else
	    return;
    }
#endif
    if (home_action != NULL) {
	home_action(False);
	home_action = NULL;
	/* This discards the expose event generated by home()
	   (1 for each page) */
	if (read_events(EV_NOWAIT) & EV_GE_NEWPAGE) {
	    return;
	}

	can_exposures(&mane);
    }

#if COLOR
    rgbp = &bg_initial;
    if (page_colors.stack != NULL) {
	ASSERT(current_page < (int)page_colors.size, "page_colors.size too small");
	rgbp = &page_colors.stack[current_page].bg;
    }

    /* Set background color */
    if (bg_current == NULL
	|| rgbp->r != bg_current->color.r
	|| rgbp->g != bg_current->color.g
	|| rgbp->b != bg_current->color.b) {
	struct bgrec **bgpp;

	for (bgpp = &bg_head;;) {
	    bg_current = *bgpp;
	    if (bg_current == NULL) {	/* if bg is not in list */
		bg_current = *bgpp = xmalloc(sizeof *bg_current);
		bg_current->next = NULL;
		bg_current->color = *rgbp;
		bg_current->fg_head = NULL;
		bg_current->pixel_good = False;
		break;
	    }
	    if (bg_current->color.r == rgbp->r
		&& bg_current->color.g == rgbp->g
		&& bg_current->color.b == rgbp->b)
		break;
	    bgpp = &bg_current->next;
	}
	fg_current = NULL;	/* force change of foreground color */
	/* globals.gc.high is only used in XDrawRectangle, so its background color
	   doesn't need to be changed.  */
	if (globals.debug & DBG_DVI)
	    printf("Changing background color to %5d %5d %5d\n",
		   bg_current->color.r, bg_current->color.g,
		   bg_current->color.b);

	if (!bg_current->pixel_good) {
	    bg_current->pixel = alloc_color(&bg_current->color,
					    color_data[1].pixel);
	    bg_current->pixel_good = True;
	}
	XSetWindowBackground(DISP, mane.win, bg_current->pixel);
#if MOTIF && !FIXED_FLUSHING_PAGING
	XtVaSetValues(XtParent(globals.widgets.draw_widget), XtNbackground, bg_current->pixel, NULL);
#endif
	/* 	XSetWindowBackground(DISP, mane.win, bg_current->pixel); */
	/*  	XClearWindow(DISP, mane.win); */
#if 0 /* don't recolor the cursor - gives too low contrast on color backgrounds,
	 and bad appearance when part of the background is white and cursor mask
	 is colored */
	{
	    XColor	bg_Color;
	    bg_Color.pixel = bg_current->pixel;
	    XQueryColor(DISP, G_colormap, &bg_Color);
	    XRecolorCursor(DISP, globals.cursor.ready, &globals.cr_color, &bg_Color);
	    XRecolorCursor(DISP, globals.cursor.wait, &globals.cr_color, &bg_Color);
	}
#endif
    }
#endif /* COLOR */
    
    if (!globals.pausing.flag) {
	XClearWindow(DISP, mane.win);
    }

    if (G_backing_store != NotUseful) {
	mane.min_x = mane.min_y = 0;
	mane.max_x = globals.page.w;
	mane.max_y = globals.page.h;
    }
    else {
	get_xy();
	mane.min_x = -m_window_x;
	mane.max_x = -m_window_x + mane.width;
	mane.min_y = -m_window_y;
	mane.max_y = -m_window_y + mane.height;
    }

    /*      update_TOC(); */
    redraw(&mane);
}

void
do_pages(void)
{
    if (globals.debug & DBG_BATCH) {
	
	(void)read_events(EV_GT_IDLE);
	for (current_page = 0; current_page < total_pages; ++current_page) {
	    if (resource.keep_flag) {
		home_action = NULL;
	    }
	    else {
		home_action = home;
	    }
	    globals.warn_spec_now = resource.warn_spec;
#if PS_GS
	    for (;;) {
		redraw_page();
		(void) read_events(EV_NOWAIT);
		if (!(globals.ev.flags & (EV_NEWPAGE | EV_NEWDOC | EV_RELOAD)))
		    break;
		globals.ev.flags = EV_IDLE;
	    }
#else
	    redraw_page();
#endif
	}
	xdvi_exit(EXIT_SUCCESS);
    }
    else {
	for (;;) {	/* normal operation */
	    (void) read_events(EV_GT_IDLE);
 	    TRACE_EVENTS((stderr, "globals.ev.flags: %d; ev_newpage: %d, ev_newdoc: %d, ev_reload: %d\n",
			  globals.ev.flags, EV_NEWPAGE, EV_NEWDOC, EV_RELOAD));
	    /* NOTE: reloading must be checked first! */
	    if (globals.ev.flags & (EV_NEWPAGE | EV_NEWDOC | EV_RELOAD | EV_PS_TOGGLE)) {
		TRACE_EVENTS((stderr, "EV_NEWPAGE | ..."));
		globals.ev.flags &= ~(EV_NEWPAGE | EV_EXPOSE | EV_PS_TOGGLE);
		if (globals.ev.flags & EV_RELOAD) {
		    dviErrFlagT errflag;

		    globals.ev.flags &= ~EV_RELOAD;
		    if (load_dvi_file(
#if !DELAYED_MKTEXPK
				      True,
#endif
				      &errflag)) {
#if PS_GS
			if (resource.gs_alpha) {
			    /* restart gs so that user has a method for fixing GS artifacts with gs_alpha
			       by using `reload' (see also GS_PIXMAP_CLEARING_HACK) */
			    ps_destroy();
			}
#endif
			statusline_info(STATUS_SHORT, "File reloaded.");
		    }
		    /* 		    else { */
		    /* 			statusline_info(STATUS_MEDIUM, "File corrupted, not reloading."); */
		    /* 		    } */
		}
		if (globals.ev.flags & EV_NEWDOC) {
		    dviErrFlagT errflag;
		    TRACE_EVENTS((stderr, "EV_NEWDOC!"));
		    /*  		    fprintf(stderr, "newdoc!\n"); */
		    TRACE_FILES((stderr, "current page: %d", current_page));
		    /*  		    file_history_set_page(current_page); */
		    globals.ev.flags &= ~EV_NEWDOC;
		    if (load_dvi_file(
#if !DELAYED_MKTEXPK
				      True,
#endif
				      &errflag)) {
			statusline_append(STATUS_SHORT, "Opened ", "Opened \"%s\"", globals.dvi_name);
			/* 			statusline_info(STATUS_SHORT, "Opened \"%s\"", globals.dvi_name); */
			TRACE_FILES((stderr, "Adding to history: |%s|\n", globals.dvi_name));
			if (file_history_push(globals.dvi_name)) { /* it's a new file, add to history */
			    TRACE_FILES((stderr, "New entry!"));
			    filehist_menu_add_entry(globals.dvi_name);
			}
			else { /* only need to move existing elements to new positions */
			    TRACE_FILES((stderr, "Existing entry!\n"));
			    filehist_menu_refresh();
			}
		    }
		}

		can_exposures(&mane);
		can_exposures(&magnifier);

#if PS && PS_GS && GS_PIXMAP_CLEARING_HACK
		if (had_ps_specials && !MAGNIFIER_ACTIVE) {
		    erasepage_gs();
		    had_ps_specials = False;
		}
#endif /* PS && PS_GS && GS_PIXMAP_CLEARING_HACK */

		if (globals.dvi_file.bak_fp != NULL) {
		    TRACE_EVENTS((stderr, "redraw_page()"));
		    redraw_page();
		}
		else {
		    TRACE_EVENTS((stderr, "dvi_file_changed()"));
		    (void)dvi_file_changed();
		}
	    }
	    else if (globals.ev.flags & EV_PAGEHIST_GOTO_PAGE) {
		int pageno;
		globals.ev.flags &= ~EV_PAGEHIST_GOTO_PAGE;
		pageno = check_goto_page(page_history_get_page(), False);
		goto_page(pageno, resource.keep_flag ? NULL : home, False);
		TRACE_FILES((stderr, "got page: %d", pageno));
	    }
	    else if (globals.ev.flags & EV_FILEHIST_GOTO_PAGE) {
		int pageno;
		globals.ev.flags &= ~EV_FILEHIST_GOTO_PAGE;
		pageno = check_goto_page(file_history_get_page(), True);
		goto_page(pageno, resource.keep_flag ? NULL : home, False);
		TRACE_FILES((stderr, "got page: %d", pageno));
	    }
	    else if (globals.ev.flags & EV_PAGEHIST_INSERT) {
		globals.ev.flags &= ~EV_PAGEHIST_INSERT;
		page_history_insert(current_page);
	    }
	    else if (globals.ev.flags & EV_FIND_CANCEL) {
		/* NOTE: This must be done before checking for expose() */
		globals.ev.flags &= ~EV_FIND_CANCEL;
	    }
	    else if (globals.ev.flags & EV_ANCHOR) {
		/*
		 * Similar to forward search: search for a htex anchor.
		 * This needs to come before the next case which does the redraw_page(),
		 * otherwise anchors for the current page might not be drawn at all:
		 * anchor_search() sets the info later used by htex_draw_anchormarkers(),
		 * which is invoked by redraw_page().
		 */
		
		/* switch off the link cursor */
		globals.cursor.flags &= ~CURSOR_LINK;

		if (dvi_file_changed())
		    continue;
		
		anchor_search(g_anchor_pos);
		
		/* added, otherwise anchors on same page may not be drawn */
		/* NOTE: This caused a crash when clicking on link "Langer" on p3 of diss.dvi
		   since the color stack wasn't allocated for the target pages. Removed for the
		   time being, since I can't reproduce the problem for which it was introduced.
		   CVS commit message was:
		   "fix anchor drawing for other window"
		*/
		/* redraw(&mane);  */
		
		globals.ev.flags &= ~EV_ANCHOR;
	    }
	    else if (globals.ev.flags & EV_SRC) {
		/*
		 * Source special operations are deferred to here because
		 * they call geom_scan(), which may call define_font(),
		 * which may call makefont(), which may call read_events()
		 * recursively.
		 */
		if (globals.src.fwd_string != NULL) {
		    const char *s = globals.src.fwd_string;

		    if (dvi_file_changed())
			continue;
		    
		    source_forward_search(s);
		    globals.ev.flags &= ~EV_SRC;
		    globals.src.fwd_string = NULL;

		    /* de-iconify window if needed, and raise it */
		    XMapRaised(XtDisplay(globals.widgets.top_level), XtWindow(globals.widgets.top_level));
		    raise_message_windows();
		}
		else if (source_reverse_x != -1) {
		    if (dvi_file_changed())
			continue;
		    
		    source_reverse_search(source_reverse_x, source_reverse_y, True);
		    globals.ev.flags &= ~EV_SRC;
		}
		else {
		    source_special_show(source_show_all);
		    globals.ev.flags &= ~EV_SRC;
		}
	    }
	    /* support for `-findstring' */
	    else if (globals.ev.flags & EV_FIND) {
		if (dvi_file_changed())
		    continue;

		if (resource.find_string != NULL) { /* not first call */
		    dvi_find_string(resource.find_string, False);
		    resource.find_string = NULL;
		}
		else { /* actually should never arrive here?? */
		    dvi_find_string(NULL, True);
		}
		globals.ev.flags &= ~EV_FIND;
	    }
	    else if (globals.ev.flags & EV_MAG_MOVE) {
		MYTRACE((stderr, "moving mag!\n"));
		move_magnifier();
	    }
	    else if (globals.ev.flags & EV_EXPOSE) {
		if (magnifier.min_x < MAXDIM) {
		    /*  		    fprintf(stderr, "magnifier < maxdim!\n"); */
		    if (mane.min_x >= MAXDIM) {
			/*  			fprintf(stderr, "mane >= maxdim!\n"); */
			globals.ev.flags &= ~EV_EXPOSE;
		    }
		    redraw(&magnifier);
		}
		else {
		    /* see comment in mag.c */
		    globals.ev.flags &= ~EV_EXPOSE;
		    if (mane.min_x < MAXDIM) {
			redraw(&mane);
		    }
		}
	    }
	    else if (globals.ev.flags & EV_CURSOR) {
		/*
		 * This code eliminates unnecessary calls to XDefineCursor,
		 * since this is a slow operation on some hardware (e.g., S3
		 * chips).
		 */
		XSync(DISP, False);
		if (!XtPending()) {
		    Cursor curr;
			
		    if (globals.cursor.flags & CURSOR_DRAG_V)
			curr = globals.cursor.drag_v;
		    else if (globals.cursor.flags & CURSOR_DRAG_H)
			curr = globals.cursor.drag_h;
		    else if (globals.cursor.flags & CURSOR_DRAG_A)
			curr = globals.cursor.drag_a;
		    else if (resource.mouse_mode == MOUSE_MODE3)
			curr = globals.cursor.mode3;
		    else if (resource.mouse_mode == MOUSE_MODE2)
			curr = globals.cursor.mode2;
		    else if (globals.cursor.flags & CURSOR_LINK)
			curr = globals.cursor.link;
		    else if (globals.cursor.flags & CURSOR_MAG)
			curr = globals.cursor.empty;
		    else if (globals.cursor.flags & CURSOR_CORRUPTED)
			curr = globals.cursor.corrupted;
		    else if (globals.pausing.flag)
			curr = globals.cursor.pause;
		    else
			curr = globals.cursor.mode1;
		    XDefineCursor(DISP, CURSORWIN, curr);
		    globals.ev.flags &= ~EV_CURSOR;
		}
	    }
	    XFlush(DISP);
	}
    }
}

static void
Act_unpause_or_next(Widget w, XEvent *event,
		    String *params, Cardinal *num_params)
{
    if (globals.pausing.flag) {
        globals.pausing.num++;
	if (globals.pausing.num_save)
	    globals.pausing.num_save[current_page] = globals.pausing.num; 
	redraw_page();
    }
    else {
	Act_down_or_next(w, event, params, num_params);
    }
}

/*
 * timer callback for watching the DVI file.
 */
void
watch_file_cb(XtPointer client_data, XtIntervalId *id)
{
    static XtIntervalId timer = 0;
    
    UNUSED(client_data);
    UNUSED(id);

    if (resource.watch_file > 0.0) {
	unsigned long watch_time_ms;

	(void)dvi_file_changed();
    
	if (timer) {
	    XtRemoveTimeOut(timer);
	    timer = (XtIntervalId)(XtIntervalId)0;
	}

	watch_time_ms = (unsigned long)(resource.watch_file * 1000);
	timer = XtAppAddTimeOut(globals.app, watch_time_ms, watch_file_cb, (XtPointer)NULL);
    }
}

void
Act_scroll_list_up(Widget w, XEvent *event, String *params,
	Cardinal *num_params)
{
    UNUSED(w);
    UNUSED(event);
    UNUSED(params);
    UNUSED(num_params);

    if (current_page == 0) {
	xdvi_bell();
	/* statusline_info(STATUS_SHORT, "First page of DVI file"); */
	return;
    }
    goto_page(check_goto_page(current_page - 1, True),
      resource.keep_flag ? NULL : home, False);
    search_signal_page_changed();
    statusline_erase("Page history:");
}

void
Act_scroll_list_down(Widget w, XEvent *event, String *params,
	Cardinal *num_params)
{
    UNUSED(w);
    UNUSED(event);
    UNUSED(params);
    UNUSED(num_params);

    if (current_page >= total_pages - 1) {
	xdvi_bell();
	/* statusline_info(STATUS_SHORT, "Last page of DVI file"); */
	return;
    }
    goto_page(check_goto_page(current_page + 1, True),
      resource.keep_flag ? NULL : home, False);
    search_signal_page_changed();
    statusline_erase("Page history:");
}

void Act_pagehistory_back(Widget w, XEvent *event, String *params, Cardinal *num_params)
{
    int arg;

    UNUSED(w);
    UNUSED(event);

    if (!get_int_arg(params, num_params, &arg)) {
	arg = 1;
    }
    page_history_move(-arg);
}

void Act_pagehistory_forward(Widget w, XEvent *event, String *params, Cardinal *num_params)
{
    int arg;

    UNUSED(w);
    UNUSED(event);

    if (!get_int_arg(params, num_params, &arg)) {
	arg = 1;
    }
    page_history_move(arg);
}

void Act_pagehistory_clear(Widget w, XEvent *event, String *params, Cardinal *num_params)
{
    UNUSED(w);
    UNUSED(event);
    UNUSED(params);
    UNUSED(num_params);

    page_history_clear();
}

void Act_pagehistory_delete_backward(Widget w, XEvent *event, String *params, Cardinal *num_params)
{
    int arg;

    UNUSED(w);
    UNUSED(event);

    if (!get_int_arg(params, num_params, &arg)) {
	arg = 1;
    }
    page_history_delete(-arg);
}

void Act_pagehistory_delete_forward(Widget w, XEvent *event, String *params, Cardinal *num_params)
{
    int arg;

    UNUSED(w);
    UNUSED(event);

    if (!get_int_arg(params, num_params, &arg)) {
	arg = 1;
    }
    page_history_delete(arg);
}

#ifdef MOTIF
void Act_prefs_dialog(Widget w, XEvent *event, String *params, Cardinal *num_params)
{
    int arg;
    
    UNUSED(w);
    UNUSED(event);

    if (!get_int_arg(params, num_params, &arg)) {
	arg = -1;
    }
    
    popup_preferences_dialog(globals.widgets.top_level, arg);
}
#endif /* MOTIF */

