
#if !defined(lint) && !defined(SABER)
static char XRNrcsid[] = "$Header: /wrld/mnt11/ricks/src/X/xrn/RCS/buttons.c,v 1.33 91/12/04 11:32:34 ricks Exp $";
#endif

/*
 * xrn - an X-based NNTP news reader
 *
 * Copyright (c) 1988, 1989, 1990, 1991, Ellen M. Sentovich and Rick L. Spickelmier.
 *
 * Permission to use, copy, modify, and distribute this software and its
 * documentation for any purpose and without fee is hereby granted, provided
 * that the above copyright notice appear in all copies and that both that
 * copyright notice and this permission notice appear in supporting
 * documentation, and that the name of the University of California not
 * be used in advertising or publicity pertaining to distribution of 
 * the software without specific, written prior permission.  The University
 * of California makes no representations about the suitability of this
 * software for any purpose.  It is provided "as is" without express or
 * implied warranty.
 *
 * THE UNIVERSITY OF CALIFORNIA DISCLAIMS ALL WARRANTIES WITH REGARD TO 
 * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND 
 * FITNESS, IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE FOR
 * ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
 * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
 * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN 
 * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

/*
 * buttons.c: create and handle the buttons
 *
 */

#include "copyright.h"
#include <X11/Xos.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "config.h"
#include "utils.h"
#include <X11/Intrinsic.h>
#include <X11/StringDefs.h>

#ifndef MOTIF
#include <X11/Xaw/Paned.h>
#include <X11/Xaw/Label.h>
#include <X11/Xaw/Command.h>
#include <X11/Xaw/Box.h>
#include <X11/Xaw/AsciiText.h>
#include <X11/Xaw/Dialog.h>
#define XawStringSourceDestroy XtDestroyWidget
#else
#include <string.h>
#include <Xm/PanedW.h>
#include <Xm/Label.h>
#include <Xm/PushB.h>
#include <Xm/PushBG.h>
#include <Xm/RowColumn.h>
#include <Xm/Text.h>
#include <Xm/MessageB.h>
#include <Xm/List.h>
#endif

#include "compose.h"
#include "cursor.h"
#include "mesg.h"
#include "dialogs.h"
#include "modes.h"
#include "resources.h"
#include "internals.h"
#include "save.h"
#include "xmisc.h"
#include "error_hnds.h"
#include "xthelper.h"
#include "xrn.h"
#include "cancel.h"
#include "news.h"
#include "buttons.h"

static off_t ArticleTextFilesize;		/* size of the file */

#ifdef MOTIF
/**********************************************************************
Befin hacked XawText-type routines to interface with XmList.  See
MotifXawHack.h for a short description.
**********************************************************************/

#include "MotifXawHack.h"
#include "resources.h"

static char *TextMotifString = 0;		/* text for top list */
static char *ArticleTextMotifString = 0;	/* text for bottom list */
static Boolean isTextSelection = False;		/* did the user highlight? */

/**********************************************************************
Convert an XmString to a C string
**********************************************************************/

char *
decodeCS(string)
XmString string;
{
    XmStringContext ctx;
    char *text = 0;
    XmStringCharSet charset;
    XmStringDirection dirRtoL;
    Boolean separator;

    if(!XmStringInitContext(&ctx, string))
	printf("init failed\n");
    else if(!XmStringGetNextSegment(ctx, &text, &charset, &dirRtoL,
				    &separator))
	printf("decode failed\n");
    return text;
}

/**********************************************************************
For the article text / all groups area there is actually both a text
widget and a list widget.  Only one of these is full-sized at a given
time, and this routine switches them.  When text is desired, we shrink
the list and vice-versa.  There is a hidden paned window containing
the two widgets.
**********************************************************************/

Widget ChooseText(b)
Boolean b;
{
  Widget other, parent;
  Arg args[3];
  Dimension height;

  if ((ArticleText == ArticleTextText && b) ||
      (ArticleText != ArticleTextText && !b)) {
    return ArticleText;
  } else {
    if (ArticleText != ArticleTextText) {
      ArticleText = ArticleTextText;
      parent = ArticleTextText;
      other = XtParent(ArticleTextList);
    } else {
      ArticleText = ArticleTextList;
      parent = XtParent(ArticleTextList);
      other = ArticleTextText;
    }
    XtSetArg(args[0], XmNrefigureMode, False);
    XtSetValues(ArticleContainer, args, 1);
    XtSetArg(args[0], XmNheight, &height);
    XtGetValues(other, args, 1);
    XtSetArg(args[0], XmNpaneMinimum, 1);
    XtSetArg(args[1], XmNpaneMaximum, 1);
    XtSetValues(other, args, 2);
    XtSetArg(args[0], XmNpaneMinimum, height);
    XtSetArg(args[1], XmNpaneMaximum, 1000);
    XtSetValues(parent, args, 2);
    XtSetArg(args[0], XmNrefigureMode, True);
    XtSetValues(ArticleContainer, args, 1);
#ifndef MOTIF_BUG	/* Motif paned widget bug */
    XtUnmanageChild(BottomButtonBox);
    XtManageChild(BottomButtonBox);
#endif
    XtSetArg(args[0], XmNpaneMinimum, 1);
    XtSetValues(parent, args, 1);
    return ArticleText;
  }
}

/**********************************************************************
The stupid Motif paned widget grows the first "malleable" widget to
fill up space.  Therefore, it grows the top button box all the way and
gives the text widget one pixel.  In order to get around this, I
manually compute how high the top button box wants to be (I couldn't
get this value without the parent paned window mucking with its size
first).
**********************************************************************/

int DesiredBoxHeight(w, wlist, wnum)
Widget w;
Widget *wlist;
int wnum;
{
  Arg args[10];
  int ct, height, pos, each;
  Dimension width, marginHeight, marginWidth, ht, wid;
  short spacing;

  ct = 0;
  XtSetArg(args[ct], XmNwidth, &width);  ct++;
  XtSetArg(args[ct], XmNmarginWidth, &marginWidth);  ct++;
  XtGetValues(Frame, args, ct);
  width -= marginWidth*2;
  
  ct = 0;
  XtSetArg(args[ct], XmNmarginHeight, &marginHeight);  ct++;
  XtSetArg(args[ct], XmNmarginWidth, &marginWidth);  ct++;
  XtSetArg(args[ct], XmNspacing, &spacing);  ct++;
  XtGetValues(w, args, ct);

  ct = 0;
  XtSetArg(args[ct], XmNheight, &ht);  ct++;
  XtGetValues(wlist[0], args, ct);

  height = marginHeight-spacing;
  pos = width;
  for (each=0; each<wnum; each++) {
    ct = 0;
    XtSetArg(args[ct], XmNwidth, &wid);  ct++;
    XtGetValues(wlist[each], args, ct);
    if (pos+wid+spacing >= width) {
      height += ht+spacing;
      pos = marginWidth-spacing;
    }
    pos += wid+spacing;
  }
  return height+marginHeight;
}

static void doTheRightThing(); 

/**********************************************************************
When the user makes a list selection (i.e. when this is called and
there is a corresponding X event, remember that the user did so.  This
distinguishes between a "selection" vs. "current position," both of
which are indicated by a list item highlight.
**********************************************************************/

void TextListSelection(w, data, lc)
Widget w;
caddr_t data;
XmListCallbackStruct *lc;
{
  if (lc->event) {
    if (w != Text) {
      fprintf(stderr, "Unexpected widget in TextListSelection\n");
    }
    isTextSelection = True;
    if (lc->reason == XmCR_DEFAULT_ACTION) {
      doTheRightThing(w, lc->event, "", 0);
    }
  }
}

/**********************************************************************
These routines provide XawText-type manipulation of the list widget
**********************************************************************/

void XawNothing(w)
Widget w;
{
  ;
}

/**********************************************************************
Convert a string index into the corresponding Motif list item position
**********************************************************************/

int XawTextToMotifIndex(str, pos)
char *str;
int pos;
{
  char *p;
  int result;

  result = 1;
  p = str;
  while (p && p-str < pos && (p = strchr(++p, '\n'))) {
    result++;
    if (p-str >= pos) {
      result--;
    }
  }
  return result;
}

/**********************************************************************
Convert a Motif list item position into its corresponding string index
**********************************************************************/

int XawMotifIndexToText(w, pos)
Widget w;
int pos;
{
  char *str, *p, len;
  int each;

  if (w == Text) {
    str = TextMotifString;
  } else if (w == ArticleText) {
    str = ArticleTextMotifString;
  } else {
    fprintf(stderr, "Unknown list widget in XawMotifIndexToText\n");
    exit(1);
  }
  p = str;
  for (each=1; each<pos && p; each++) {
    p = strchr(p, '\n');
    if (p) {
      p++;
    }
  };
  if (!p) {
/*
    fprintf(stderr, "Unexpected end of string in XawMotifIndexToText\n");
    exit(1);
*/
    return 0;
  }
  return p-str;
}

/**********************************************************************
Redisplay the list items corresponding to the string indices from and
to, getting the new source from the associated string of the list
**********************************************************************/

void XawTextInvalidate(w, from, to)
Widget w;
XawTextPosition from, to;
{
  int each, num, *pos_list, pos_count;
  char *p, *q, *r;
  Arg args[3];
  XmString *xs, *items;

  if (!XmListGetSelectedPos(w, &pos_list, &pos_count)) {
    pos_count = 0;
  }
  if (w == Text) {
    p = TextMotifString;
  } else if (w == ArticleText) {
    p = ArticleTextMotifString;
  } else {
    fprintf(stderr, "Unknown list widget in XawTextInvalidate\n");
    exit(1);
  }
  if (!p || !strlen(p)) {
    XtSetArg(args[0], XmNitemCount, 0);
    XtSetArg(args[1], XmNitems, NULL);
    XtSetArg(args[2], XmNvalue, "");
    XtSetValues(w, args, 3);
  } else {
    if (!from && !to) {
      XawTextInvalidateAll(w, p);
      return;
    }
    if (!from) {
      from = 1;
    } else {
      from = XawTextToMotifIndex(p, from);
    }
    if (!to) {
      to = XawTextToMotifIndex(p, strlen(p)-1);
    } else {
      to = XawTextToMotifIndex(p, to);
    }
    XtSetArg(args[0], XmNitemCount, &num);
    XtSetArg(args[1], XmNitems, &items);
    XtGetValues(w, args, 2);
    if (from > num) {
      from = num+1;
    }
    xs = (XmString *) calloc(to-from+1, sizeof(XmString));
    for (each=1; each<from; each++) {
      p = strchr(p, '\n');
      if (p) {
	p++;
      }
    }
    for (each=from; each<=to; each++) {
      if (p) {
	q = strchr(p, '\n');
	if (q) {
	  *q = '\0';
	}
	r = strchr(p, '\t');
	if (r) {
	  *r = ' ';
	}
	xs[each-from] = XmStringCreate(p, XmSTRING_DEFAULT_CHARSET);
	if (q) {
	  *q = '\n';
	}
	if (r) {
	  *r = '\t';
	}
      }
      if (!p) {
	fprintf(stderr, "Unexpected NULL pointer in XawTextInvalidate\n");
	exit(1);
      } else {
	p = strchr(p, '\n');
	if (p) {
	  p++;
	}
      }
    }
    if (to <= num) {
      XmListReplaceItemsPos(w, xs, to-from+1, from);
    } else {
      XmListReplaceItemsPos(w, xs, num-from+1, from);
      XmListAddItems(w, xs+num+1-from, to-num, num+1);
    }
    for (each=from; each<=to; each++) {
      XmStringFree(xs[each-from]);
    }
    free(xs);
    if (pos_count) {
      XmListSelectPos(w, pos_list[0], False);
      XtFree((char *) pos_list);
    }
  }
}

/**********************************************************************
Replace the entire list contents with the contents of the string
**********************************************************************/

void XawTextInvalidateAll(w, p)
Widget w;
char *p;
{
  XawTextPosition from, to;
  int each, num, len;
  char *q, *r;
  Arg args[3];
  XmString *xs, *items;

  from = 1;
  to = XawTextToMotifIndex(p, strlen(p)-1);
  len = to;
  xs = (XmString *) calloc(len, sizeof(XmString));
  for (each=0; each<len; each++) {
    xs[each] = (XmString) 0;
  }
  for (each=from; each<=to; each++) {
    if (p) {
      q = strchr(p, '\n');
      if (q) {
	*q = '\0';
      }
      if (xs[each-1]) {
	XmStringFree(xs[each-1]);
      }
      r = strchr(p, '\t');
      if (r) {
	*r = ' ';
      }
      xs[each-1] = XmStringCreate(p, XmSTRING_DEFAULT_CHARSET);
      if (q) {
	*q = '\n';
      }
      if (r) {
	*r = '\t';
      }
    }
    if (!p) {
      fprintf(stderr, "Unexpected NULL pointer in XawTextInvalidateAll\n");
      exit(1);
    } else {
      p = strchr(p, '\n');
      if (p) {
	p++;
      }
    }
  }
  XtSetArg(args[0], XmNitemCount, len);
  XtSetArg(args[1], XmNitems, xs);
  XtSetValues(w, args, 2);
  for (each=0; each<len; each++) {
    XmStringFree(xs[each]);
  }
  free(xs);
}

/**********************************************************************
Remember the string associated with this list, since it gives the new
source when portions of the list are invalidated
**********************************************************************/

void XawTextSetMotifString(w, source)
Widget w;
char *source;
{
  if (w == Text) {
    TextMotifString = source;
  } else if (w == ArticleText) {
    ArticleTextMotifString = source;
    if (source) {
      w = ChooseText(False);
    }
  } else {
    fprintf(stderr, "Unknown widget in XawTextSetMotifString\n");
    exit(1);
  }
  XawTextInvalidate(w, 0, 0);
  XawTextUnsetSelection(w);
}

/**********************************************************************
Highlight the list item associated with the string index
**********************************************************************/

void XawTextSetInsertionPoint(w, pos)
Widget w;
XawTextPosition pos;
{
  int topItemPosition, visibleItemCount;
  Arg args[2];

  if (w == Text) {
    pos = XawTextToMotifIndex(TextMotifString, pos);
  } else if (w == ArticleText) {
    pos = XawTextToMotifIndex(ArticleTextMotifString, pos);
  } else {
    fprintf(stderr, "Unknown widget in XawTextSetInsertionPoint\n");
    exit(1);
  }
  XtSetArg(args[0], XmNtopItemPosition, &topItemPosition);
  XtSetArg(args[1], XmNvisibleItemCount, &visibleItemCount);
  XtGetValues(w, args, 2);
  XmListDeselectAllItems(w);
  XmListSelectPos(w, pos, False);
  if (pos < topItemPosition ||
      pos >= topItemPosition+visibleItemCount) {
    XmListSetBottomPos(w, pos);
  }
}

/**********************************************************************
Return the string index corresponding to the highlighted list item
**********************************************************************/

XmTextPosition XawTextGetInsertionPoint(w)
Widget w;
{
  int *pos_list, pos_count, pos;
  
  if (!XmListGetSelectedPos(w, &pos_list, &pos_count)) {
    return 0;
  }
  if (!pos_count) {
    return 0;
  } else {
    if (pos_count > 1) {
      fprintf(stderr,
	      "Unexpected multiple selection in XawTextGetInsertionPoint\n");
    }
    pos = pos_list[0];
    XtFree((char *) pos_list);
    return XawMotifIndexToText(w, pos);
  }
}

/**********************************************************************
Erase memory of the users selection.  This has no visible effect
because we still want the current list position highlighted.  However,
if we're asked for the selection later, we say there is none.
**********************************************************************/

void XawTextUnsetSelection(w)
Widget w;
{
  if (w == Text) {
    isTextSelection = False;
  }
}

/**********************************************************************
Return the string range corresponding to the list items selected, if
any.  Note that even though an item might be highlighted, it might not
be a selection because the user didn't highlight it directly.
**********************************************************************/

void XawTextGetSelectionPos(w, left, right)
Widget w;
XawTextPosition *left, *right;
{
  XmString *selected;
  int count;
  Arg args[2];
  char *item, *p;

  XtSetArg(args[0], XmNselectedItemCount, &count);
  XtSetArg(args[1], XmNselectedItems, &selected);
  XtGetValues(w, args, 2);
  if (!count || (w == Text && !isTextSelection)) {
    *left = *right = 0;
  } else {
    *left = XawMotifIndexToText(w, XmListItemPos(w, selected[0]));
    *right = XawMotifIndexToText(w, XmListItemPos(w, selected[count-1])+1);
  }
}

/**********************************************************************
Return the string index corresponding to the top item in the list
**********************************************************************/

int XawTextTopPosition(w)
Widget w;
{
  Arg args[1];
  int topItemPosition;

  XtSetArg(args[0], XmNtopItemPosition, &topItemPosition);
  XtGetValues(w, args, 1);
  return topItemPosition;
}

#endif

#define GROUP_NAME_SIZE 128

#ifdef VMS
#define getArticle getArticleFile
#include <descrip.h>
#endif

static Widget *AddButtons = NIL(Widget);
static Widget *NgButtons = NIL(Widget);
static Widget *AllButtons = NIL(Widget);
static Widget *ArtButtons = NIL(Widget);
static Widget *ArtSpecButtons = NIL(Widget);

static char *AddGroupsString = NIL(char);  /* new newsgroups list ...           */
static char *NewsGroupsString = NIL(char); /* newsgroups list that is displayed */
static char *AllGroupsString = NIL(char); /* list of all groups so the user */
				          /* can subscribe/unsubscribe to them */
static char *SubjectString = NIL(char); /* list of article numbers and subjects */

static XawTextPosition GroupPosition = (XawTextPosition) 0; /* cursor position */
				/* newsgroup window */
static XawTextPosition ArtPosition = (XawTextPosition) 0;	/* cursor position in */
				/* article subject window */
static XawTextPosition NewsgroupTop = (XawTextPosition) 0; /* top position in */
				/* newsgroup window */
static XawTextPosition ArticleTop = (XawTextPosition) 0; /* top position in */
				/* article subject window */

static char LastGroup[GROUP_NAME_SIZE];	/* last newsgroup accessed; used to */
				/* determine whether or not to move the */
				/* cursor in the newsgroup window */
static char *LastArticle;	/* the article currently displayed */
				/* in the article window */
static int CurrentArticle;	/* the number of the article currently */
				/* displayed, used for marking an article */
				/* as saved */

static long PrevArticle;	/* the number of the article displayed */
				/* before the current one */

static int ArtStatus = 0;	/* keeps track of what kind of article to */
				/* to search for: next, previous, or next */
				/* unread */
static int AllStatus = 1;	/* keeps track of which order to put the */
				/* groups in in all groups mode */
static char *LastRegexp;	/* last regular expression searched for */
static int LastSearch;		/* the direction of the last regular */
				/* expression search */

static XawTextPosition First;	/* keeps the beginning of the selected text */
				/* for the command move groups */
static XawTextPosition Last;	/* keeps the end of the selected text for */
				/* the command move groups */

static int Action;		/* action to take when a confirmation box */
				/* is clicked in */

static int NewsgroupDisplayMode = 0;	/* 0 for unread groups, 1 for all sub */
#define XRN_JUMP 0
#define XRN_GOTO 1
static int NewsgroupEntryMode = XRN_GOTO;

static char *SaveString = 0;	/* last input to save box */
static char *GotoNewsgroupString = 0;
static int ArtEntry = 1;

#ifndef MOTIF
static XawTextSelectType lineSelectArray[] = {XawselectLine, XawselectNull};

static Arg lineSelArgs[] = {
    {XtNselectTypes, (XtArgVal) lineSelectArray},
};    

static XawTextSelectType allSelectArray[] =
    {XawselectPosition, XawselectChar, XawselectWord, XawselectLine,
     XawselectParagraph, XawselectAll, XawselectNull};
#endif

#ifndef MOTIF
static Widget AllSource = 0;
static Widget SubjectSource = 0;
static Widget ArtSource = 0;
static Widget DummySource = 0;
#endif

/* article mode "modes" ... determines what to do: jump out of article */
/* mode, change the subject string, or do nothing */
#define art_DONE 0
#define art_CHANGE 1
#define art_NOCHANGE 2

/* keeps track of which type of article to search for: Next, Unread, or */
/* previous */
#define art_NEXT 0
#define art_UNREAD 1
#define art_PREV 2
#define art_NEXTWRAP 3


static int Mode = NO_MODE;            /* current mode                       */
static int PreviousMode = NO_MODE;    /* previous mode, what buttons to */
				       /* remove */

#define XRN_NO 0
#define XRN_YES 1

/* the user is in a command - eat type ahead */
int inCommand = 0;

static struct _translations {
    Widget widget;
    XtTranslations tables[MAX_MODE];
    char *unparsed[MAX_MODE];
} Translations[6];

/* handle autorescan timeouts */
static XtIntervalId TimeOut = 0;

void addTimeOut()
{
    void autoRescan();

    if (Mode != NEWSGROUP_MODE) {
	return;
    }

    if (app_resources.rescanTime <= 0) {
	return;
    }

    /* do not allow recursive timeouts */
    if (TimeOut) {
	return;
    }
    /* handle race conditions??? */
    TimeOut = 1;

    TimeOut = XtAddTimeOut(app_resources.rescanTime * 1000, autoRescan, 0);
    return;
}


void removeTimeOut()
{
    XtIntervalId temp;

    if (Mode != NEWSGROUP_MODE) {
	return;
    }

    /* handle race conditions??? */
    temp = TimeOut;
    TimeOut = 0;

    /* do not allow recursive timeouts */
    if (temp) {
	XtRemoveTimeOut(temp);
    }
    return;
}


/*
 * definition of the buttons
 */

#undef lint
#ifdef lint
#define BUTTON(nm,lbl)
#else
#if defined(__STDC__) && !defined(UNIXCPP)
#define BUTTON(nm,lbl)				\
static void nm##Core(widget, event, string, count) \
Widget widget;					\
XEvent *event;					\
String *string;					\
Cardinal count;					\
{						\
    static void nm##Function();			\
    if (inCommand) {				\
	return;					\
    }						\
    inCommand = 1;				\
    removeTimeOut();				\
    busyCursor();				\
    nm##Function(widget, event, string, count);	\
    unbusyCursor();				\
    addTimeOut();				\
    inCommand = 0;				\
    return;					\
}						\
/*ARGSUSED*/                                    \
static void nm##Action(widget, event, string, count) \
Widget widget;					\
XEvent *event;					\
String *string;					\
Cardinal count;					\
{						\
    nm##Core(widget, event, string, count);	\
    return;					\
}						\
/*ARGSUSED*/                                    \
static void nm##Button(widget, client_data, call_data)	\
Widget widget;					\
caddr_t client_data;				\
caddr_t call_data;				\
{						\
    nm##Core(widget, NULL, 0, 0);		\
    return;					\
}						\
static XtCallbackRec nm##Callbacks[] = {	\
    {nm##Button, NULL},				\
    {NULL, NULL}				\
};						\
static Arg nm##Args[] = {			\
    {XtNname, (XtArgVal) #nm},			\
/*    {XtNlabel, (XtArgVal) #lbl}, */		\
    {MyNcallback, (XtArgVal) nm##Callbacks}	\
};
#else
#define BUTTON(nm,lbl)				\
static void nm/**/Core(widget, event, string, count) \
Widget widget;					\
XEvent *event;					\
String *string;					\
Cardinal count;					\
{						\
    static void nm/**/Function();		\
    if (inCommand) {				\
	return;					\
    }						\
    inCommand = 1;				\
    removeTimeOut();				\
    busyCursor();				\
    nm/**/Function(widget, event, string, count); \
    unbusyCursor();				\
    addTimeOut();				\
    inCommand = 0;				\
    return;					\
}						\
/*ARGSUSED*/					\
static void nm/**/Action(widget, event, string, count) \
Widget widget;					\
XEvent *event;					\
String *string;					\
Cardinal count;					\
{						\
    nm/**/Core(widget, event, string, count);	\
    return;					\
}						\
/*ARGSUSED*/					\
static void nm/**/Button(widget, client_data, call_data)	\
Widget widget;					\
caddr_t client_data;				\
caddr_t call_data;				\
{						\
    nm/**/Core(widget, NULL, 0, 0);		\
    return;					\
}						\
static XtCallbackRec nm/**/Callbacks[] = {	\
    {nm/**/Button, NULL},			\
    {NULL, NULL}				\
};						\
static Arg nm/**/Args[] = {			\
    {XtNname, (XtArgVal) "nm"},			\
/*    {XtNlabel, (XtArgVal) "lbl"}, */		\
    {MyNcallback, (XtArgVal) nm/**/Callbacks}	\
};
#endif
#endif

BUTTON(addQuit,quit)
BUTTON(addFirst,add first)
BUTTON(addLast,add last)
BUTTON(addAfter,add after group)
BUTTON(addUnsub,add unsubscribed)

BUTTON(ngExit,exit)
BUTTON(ngQuit,quit)
BUTTON(ngRead,read group)
BUTTON(ngNext,next)
BUTTON(ngPrev,prev)
BUTTON(ngCatchUp,catch up)
BUTTON(ngSubscribe,subscribe)
BUTTON(ngUnsub,unsubscribe)
BUTTON(ngGoto,goto newsgroup)
BUTTON(ngListOld,sub groups)
BUTTON(ngAllGroups,all groups)
BUTTON(ngRescan,rescan)
BUTTON(ngPrevGroup,prev group)
BUTTON(ngSelect,select groups)
BUTTON(ngMove,move)
BUTTON(ngCheckPoint,checkpoint)
BUTTON(ngPost,post)
BUTTON(ngGripe,gripe)

BUTTON(allQuit,quit)
BUTTON(allSub,subscribe)
BUTTON(allFirst,subscribe first)
BUTTON(allLast,subscribe last)
BUTTON(allAfter,subscribe after group)
BUTTON(allUnsub,unsubscribe)
BUTTON(allGoto,goto group)
BUTTON(allSelect,select groups)
BUTTON(allMove,move)
BUTTON(allToggle,toggle order)
BUTTON(allScroll,scroll forward)
BUTTON(allScrollBack,scroll backward)

BUTTON(artQuit,quit)
BUTTON(artNext,next)
BUTTON(artNextUnread,next unread)
BUTTON(artPrev,prev)
BUTTON(artLast,last)
BUTTON(artNextGroup,next newsgroup)
BUTTON(artGotoArticle,goto article)
BUTTON(artCatchUp,catch up)
BUTTON(artFedUp,fed up)
BUTTON(artMarkRead,mark read)
BUTTON(artMarkUnread,mark unread)
BUTTON(artUnsub,unsubscribe)
BUTTON(artScroll,scroll forward)
BUTTON(artScrollBack,scroll backward)
BUTTON(artScrollLine,scroll line forward)
BUTTON(artScrollBackLine,scroll line backward)
BUTTON(artScrollEnd,scroll to end)
BUTTON(artScrollBeginning,scroll to beginning)
BUTTON(artSubNext,subject next)
BUTTON(artSubPrev,subject prev)
BUTTON(artKillSession,session kill)
BUTTON(artKillLocal,local kill)
BUTTON(artKillGlobal,global kill)
BUTTON(artKillAuthor,author kill)
BUTTON(artSubSearch,subject search)
BUTTON(artContinue,continue)
BUTTON(artPost,post)
BUTTON(artExit,exit)
BUTTON(artCheckPoint,checkpoint)
BUTTON(artGripe,gripe)
BUTTON(artListOld,list old)

BUTTON(artSave,save)
BUTTON(artReply,reply)
BUTTON(artForward,forward)
BUTTON(artFollowup,followup)
BUTTON(artCancel,cancel)
BUTTON(artRot13,rot-13)
BUTTON(artHeader,toggle header)
BUTTON(artPrint,print)

static void doTheRightThing(); 
static XtActionsRec TopActions[] = {
    {"doTheRightThing",	doTheRightThing},
};

static XtActionsRec AddActions[] = {
    {"addQuit",		addQuitAction},
    {"addFirst",	addFirstAction},
    {"addLast",		addLastAction},
    {"addAfter",	addAfterAction},
    {"addUnsub",	addUnsubAction},
};

static XtActionsRec NgActions[] = {
    {"ngQuit",		ngQuitAction},
    {"ngRead",		ngReadAction},
    {"ngNext",		ngNextAction},
    {"ngPrev",		ngPrevAction},
    {"ngCatchUp",	ngCatchUpAction},
    {"ngSubscribe",	ngSubscribeAction},
    {"ngUnsub",		ngUnsubAction},
    {"ngGoto",		ngGotoAction},
    {"ngRescan",	ngRescanAction},
    {"ngAllGroups",	ngAllGroupsAction},
    {"ngToggleGroups",	ngListOldAction},
    {"ngPrevGroup",	ngPrevGroupAction},
    {"ngSelect",	ngSelectAction},
    {"ngMove",		ngMoveAction},
    {"ngExit",		ngExitAction},
    {"ngGripe",		ngGripeAction},
    {"ngPost",		ngPostAction},
    {"ngCheckPoint",	ngCheckPointAction},
};    


static XtActionsRec ArtActions[] = {
    {"artQuit",		artQuitAction},
    {"artNextUnread",	artNextUnreadAction},
    {"artScroll",	artScrollAction},
    {"artScrollBack",	artScrollBackAction},
    {"artScrollLine",	artScrollLineAction},
    {"artScrollBackLine",	artScrollBackLineAction},
    {"artScrollEnd",	artScrollEndAction},
    {"artScrollBeginning",	artScrollBeginningAction},
    {"artNext",		artNextAction},
    {"artPrev",		artPrevAction},
    {"artLast",		artLastAction},
    {"artNextGroup",	artNextGroupAction},
    {"artCatchUp",	artCatchUpAction},
    {"artFedUp",	artFedUpAction},
    {"artGotoArticle",	artGotoArticleAction},
    {"artMarkRead",	artMarkReadAction},
    {"artMarkUnread",	artMarkUnreadAction},
    {"artUnsub",	artUnsubAction},
    {"artSubNext",	artSubNextAction},
    {"artSubPrev",	artSubPrevAction},
    {"artKillSession",	artKillSessionAction},
    {"artKillLocal",	artKillLocalAction},
    {"artKillGlobal",	artKillGlobalAction},
    {"artKillAuthor",	artKillAuthorAction},
    {"artSubSearch",	artSubSearchAction},
    {"artContinue",	artContinueAction},
    {"artPost",		artPostAction},
    {"artExit",		artExitAction},
    {"artCheckPoint",	artCheckPointAction},
    {"artSave",		artSaveAction},
    {"artReply",	artReplyAction},
    {"artForward",	artForwardAction},
    {"artFollowup",	artFollowupAction},
    {"artCancel",	artCancelAction},
    {"artRot13",	artRot13Action},
    {"artHeader",	artHeaderAction},
    {"artPrint",	artPrintAction},
    {"artGripe",	artGripeAction},
    {"artListOld",	artListOldAction},
};

static XtActionsRec AllActions[] = {
    {"allQuit",		allQuitAction},
    {"allSub",		allSubAction},
    {"allFirst",	allFirstAction},
    {"allLast",		allLastAction},
    {"allAfter",	allAfterAction},
    {"allUnsub",	allUnsubAction},
    {"allGoto",		allGotoAction},
    {"allSelect",	allSelectAction},
    {"allMove",		allMoveAction},
    {"allToggle",	allToggleAction},
    {"allScroll",	allScrollAction},
    {"allScrollBack",	allScrollBackAction},
};

typedef struct buttonList {
    Arg *buttonArgs;
    unsigned int size;
    char *message;
} ButtonList;

static ButtonList AddButtonList[] = {
#ifdef lint
    {NULL, NULL}
#else
    {addQuitArgs, XtNumber(addQuitArgs),
   "Quit add mode, unsubscribe all remaining groups"},
    {addFirstArgs, XtNumber(addFirstArgs),
   "Add the selected group(s) to the beginning of the .newsrc file"},
    {addLastArgs, XtNumber(addLastArgs),
   "Add the selected group(s) to the end of the .newsrc file"},
    {addAfterArgs, XtNumber(addAfterArgs),
   "Add the selected group(s) after a particular group in the .newsrc file"},
    {addUnsubArgs, XtNumber(addUnsubArgs),
   "Add the selected group(s) as unsubscribed"},
#endif /* lint */
};

static int AddButtonListCount = XtNumber(AddButtonList);

static ButtonList NgButtonList[] = {
#ifdef lint
    {NULL, NULL}
#else
    {ngQuitArgs, XtNumber(ngQuitArgs),
   "Quit XRN"},
    {ngReadArgs, XtNumber(ngReadArgs),
   "Read the articles in the current group"},
    {ngNextArgs, XtNumber(ngNextArgs),
   "Move the cursor to the next group"},
    {ngPrevArgs, XtNumber(ngPrevArgs),
   "Move the cursor to the previous group"},
    {ngCatchUpArgs, XtNumber(ngCatchUpArgs),
   "Mark all articles in the current group as read"},
    {ngSubscribeArgs, XtNumber(ngSubscribeArgs),
   "Subscribe to a group"},
    {ngUnsubArgs, XtNumber(ngUnsubArgs),
   "Unsubscribe to the current group"},
    {ngGotoArgs, XtNumber(ngGotoArgs),
   "Go to a newsgroup"},
    {ngAllGroupsArgs, XtNumber(ngAllGroupsArgs),
   "View all available groups, with option to subscribe"},
    {ngListOldArgs, XtNumber(ngListOldArgs),
   "Show groups with no new articles (toggle)"},
    {ngRescanArgs, XtNumber(ngRescanArgs),
   "Query the news server for new articles and groups"},
    {ngPrevGroupArgs, XtNumber(ngPrevGroupArgs),
   "Return to the group just visited"},
    {ngSelectArgs, XtNumber(ngSelectArgs),
   "Mark current selection for subsequent move operations"},
    {ngMoveArgs, XtNumber(ngMoveArgs),
   "Move previously selected groups in front of the current selection"},
    {ngExitArgs, XtNumber(ngExitArgs),
   "Quit XRN, leaving the .newsrc file unchanged since the last rescan"},
    {ngCheckPointArgs, XtNumber(ngCheckPointArgs),
   "Update the .newsrc file"},
    {ngGripeArgs, XtNumber(ngGripeArgs),
   "Mail a gripe to the XRN maintainers"},
    {ngPostArgs, XtNumber(ngPostArgs),
   "Post an article to a newsgroup"},
#endif /* lint */
};

static int NgButtonListCount = XtNumber(NgButtonList);

static ButtonList AllButtonList[] = {
#ifdef lint
    {NULL, NULL}
#else
    {allQuitArgs, XtNumber(allQuitArgs),
   "Return to newsgroup mode, saving changes"},
    {allSubArgs, XtNumber(allSubArgs),
   "Subscribe to current group, leaving .newsrc position unchanged"},
    {allFirstArgs, XtNumber(allFirstArgs),
   "Subscribe to the selected group(s); put at the beginning of the .newsrc file"},
    {allLastArgs, XtNumber(allLastArgs),
   "Subscribe to the selected group(s); put at the end of the .newsrc file"},
    {allAfterArgs, XtNumber(allAfterArgs),
   "Subscribe to the selected group(s); put after a specified group in the .newsrc file"},
    {allUnsubArgs, XtNumber(allUnsubArgs),
   "Unsubscribe to the selected group(s)"},
    {allGotoArgs, XtNumber(allGotoArgs),
   "Go to the current newsgroup"},
    {allSelectArgs, XtNumber(allSelectArgs),
   "Mark current selection for subsequent move operations"},
    {allMoveArgs, XtNumber(allMoveArgs),
   "Move previously selected groups to the current position"},
    {allToggleArgs, XtNumber(allToggleArgs),
   "Change order of groups: alphabetical/.newsrc order"},
    {allScrollArgs, XtNumber(allScrollArgs),
   "Scroll the ALL groups screen forward"},
    {allScrollBackArgs, XtNumber(allScrollBackArgs),
   "Scroll the ALL groups screen backward"},
#endif /* lint */
};

static int AllButtonListCount = XtNumber(AllButtonList);

static ButtonList ArtButtonList[] = {
#ifdef lint
    {NULL, NULL}
#else
    {artQuitArgs, XtNumber(artQuitArgs),
   "Return to newsgroup mode"},
    {artNextUnreadArgs, XtNumber(artNextUnreadArgs),
   "Read the next unread article"},
    {artNextArgs, XtNumber(artNextArgs),
   "Read the next article"},
    {artScrollArgs, XtNumber(artScrollArgs),
   "Scroll current article forward"},
    {artScrollBackArgs, XtNumber(artScrollBackArgs),
   "Scroll current article backward"},
    {artScrollLineArgs, XtNumber(artScrollLineArgs),
   "Scroll current article one line forward"},
    {artScrollBackLineArgs, XtNumber(artScrollBackLineArgs),
   "Scroll current article one line backward"},
    {artScrollEndArgs, XtNumber(artScrollEndArgs),
   "Scroll to end of current article"},
    {artScrollBeginningArgs, XtNumber(artScrollBeginningArgs),
   "Scroll to beginning of current article"},
    {artPrevArgs, XtNumber(artPrevArgs),
   "Read the previous article"},
    {artLastArgs, XtNumber(artLastArgs),
   "Go back to the last article displayed"},
    {artNextGroupArgs, XtNumber(artNextGroupArgs),
   "Go to the next newsgroup, skipping newsgroup mode"},
    {artCatchUpArgs, XtNumber(artCatchUpArgs),
   "Mark all articles (up to the selected article) in the current group as read"},
    {artFedUpArgs, XtNumber(artFedUpArgs),
   "Mark all articles in the current group as read and go to the next newsgroup "},
    {artGotoArticleArgs, XtNumber(artGotoArticleArgs),
   "Go to the specified article number in the current group"},
    {artMarkReadArgs, XtNumber(artMarkReadArgs),
   "Mark selected article(s) as read"},
    {artMarkUnreadArgs, XtNumber(artMarkUnreadArgs),
   "Mark selected article(s) as unread"},
    {artUnsubArgs, XtNumber(artUnsubArgs),
   "Unsubscribe to the current group"},
    {artSubNextArgs, XtNumber(artSubNextArgs),
   "Search for the next article with the selected subject"},
    {artSubPrevArgs, XtNumber(artSubPrevArgs),
   "Search for the previous article with the selected subject"},
    {artKillSessionArgs, XtNumber(artKillSessionArgs),
   "Mark all articles with this subject as read, this session only"},
    {artKillLocalArgs, XtNumber(artKillLocalArgs),
   "Mark all articles with this subject as read in this group for this and all future sessions"},
    {artKillGlobalArgs, XtNumber(artKillGlobalArgs),
   "Mark all articles with this subject as read for all groups for this and all future sessions"},
    {artKillAuthorArgs, XtNumber(artKillAuthorArgs),
   "Mark all articles with this author as read, this session only"},
    {artSubSearchArgs, XtNumber(artSubSearchArgs),
   "Search the subject lines for a regular expression"},
    {artContinueArgs, XtNumber(artContinueArgs),
   "Continue the regular expression subject search"},
    {artPostArgs, XtNumber(artPostArgs),
   "Post an article to this newsgroup"},
    {artExitArgs, XtNumber(artExitArgs),
   "Return to newsgroup mode, marking all articles as unread"},
    {artCheckPointArgs, XtNumber(artCheckPointArgs),
   "Update the .newsrc file"},
    {artGripeArgs, XtNumber(artGripeArgs),
   "Mail a gripe to the XRN maintainers"},
    {artListOldArgs, XtNumber(artListOldArgs),
   "List all articles in the current group (may be slow)"},
#endif /* lint */
};

static int ArtButtonListCount = XtNumber(ArtButtonList);

static ButtonList ArtSpecButtonList[] = {
#ifdef lint
    {NULL, NULL}
#else
    {artSaveArgs, XtNumber(artSaveArgs),
   "Save the current article in a file"},
    {artReplyArgs, XtNumber(artReplyArgs),
   "Mail a reply to the author of the current article"},
    {artForwardArgs, XtNumber(artForwardArgs),
   "Forward an article to a user(s)"},
    {artFollowupArgs, XtNumber(artFollowupArgs),
   "Post a followup to the current article"},
    {artCancelArgs, XtNumber(artCancelArgs),
   "Cancel the current article"},
    {artRot13Args, XtNumber(artRot13Args),
   "Decrypt an encrypted joke"},
    {artHeaderArgs, XtNumber(artHeaderArgs),
   "Display the complete/stripped header"},
    {artPrintArgs, XtNumber(artPrintArgs),
   "Print the article"},
#endif /* lint */
};

static int ArtSpecButtonListCount = XtNumber(ArtSpecButtonList);

static char TopNonButtonInfo[LABEL_SIZE];
static char BottomNonButtonInfo[LABEL_SIZE];


/*ARGSUSED*/
void
topInfoHandler(widget, client_data, event)
Widget widget;
caddr_t client_data;
XEvent *event;
/*
 * handle the Enter and Leave events for the buttons
 *
 * upon entering a button, get it's info string and put in the Question label
 * upon leaving a button, restore the old info string
 *
 */
{
    Arg infoLineArg[1];
#ifndef MOTIF

    if (event->type == LeaveNotify) {
	XtSetArg(infoLineArg[0], XtNlabel, TopNonButtonInfo);
    } else if (event->type == EnterNotify) {
	XtSetArg(infoLineArg[0], XtNlabel, client_data);
    }
    XtSetValues(TopInfoLine, infoLineArg, XtNumber(infoLineArg));
#else
    XmString xs;

    if (event->type == LeaveNotify) {
      xs = XmStringCreate(TopNonButtonInfo, XmSTRING_DEFAULT_CHARSET);
      XtSetArg(infoLineArg[0], XmNlabelString, xs);
    } else if (event->type == EnterNotify) {
      xs = XmStringCreate(client_data, XmSTRING_DEFAULT_CHARSET);
      XtSetArg(infoLineArg[0], XmNlabelString, xs);
    }
    XtSetValues(TopInfoLine, infoLineArg, 1);
    XmStringFree(xs);
#endif
    return;
}


/*ARGSUSED*/
void
bottomInfoHandler(widget, client_data, event)
Widget widget;
caddr_t client_data;
XEvent *event;
/*
 * handle the Enter and Leave events for the buttons
 *
 * upon entering a button, get it's info string and put in the Question label
 * upon leaving a button, restore the old info string
 *
 */
{
    Arg infoLineArg[1];

#ifndef MOTIF
    if (event->type == LeaveNotify) {
	XtSetArg(infoLineArg[0], XtNlabel, BottomNonButtonInfo);
    } else if (event->type == EnterNotify) {
	XtSetArg(infoLineArg[0], XtNlabel, client_data);
    }
    XtSetValues(BottomInfoLine, infoLineArg, XtNumber(infoLineArg));
#else
    XmString xs;

    if (event->type == LeaveNotify) {
      xs = XmStringCreate(BottomNonButtonInfo, XmSTRING_DEFAULT_CHARSET);
      XtSetArg(infoLineArg[0], XmNlabelString, xs);
    } else if (event->type == EnterNotify) {
      xs = XmStringCreate(client_data, XmSTRING_DEFAULT_CHARSET);
      XtSetArg(infoLineArg[0], XmNlabelString, xs);
    }
    XtSetValues(BottomInfoLine, infoLineArg, 1);
    XmStringFree(xs);
#endif
    return;
}


static void
resetSelection()
/*
 * Reset First and Last to zero, so the user doesn't accidentally
 * move groups
 */
{
    First = 0;
    Last = 0;
    
    return;
}


static void
setTopInfoLineHandler(widget, message)
Widget widget;
char *message;
{
    XtAddEventHandler(widget,
		      (EventMask) (EnterWindowMask|LeaveWindowMask),
		      False,
		      topInfoHandler,
		      (caddr_t) message);
    return;
}


static void
setBottomInfoLineHandler(widget, message)
Widget widget;
char *message;
{
    XtAddEventHandler(widget,
		      (EventMask) (EnterWindowMask|LeaveWindowMask),
		      False,
		      bottomInfoHandler,
		      (caddr_t) message);
    return;
}


static void
setTopInfoLine(message)  
char *message;
{
    Arg infoLineArg[1];

#ifndef MOTIF
    XtSetArg(infoLineArg[0], XtNlabel, message);
    (void) strcpy(TopNonButtonInfo, (char *) infoLineArg[0].value);
    XtSetValues(TopInfoLine, infoLineArg, XtNumber(infoLineArg));
#else
    XmString xs;

    (void) strcpy(TopNonButtonInfo, message);
    xs = XmStringCreate(message, XmSTRING_DEFAULT_CHARSET);
    XtSetArg(infoLineArg[0], XmNlabelString, xs);
    XtSetValues(TopInfoLine, infoLineArg, 1);
    XmStringFree(xs);
#endif
    return;
}


static void
setBottomInfoLine(message)  
char *message;
{
    Arg infoLineArg[1];

#ifndef MOTIF
    XtSetArg(infoLineArg[0], XtNlabel, message);
    (void) strcpy(BottomNonButtonInfo, (char *) infoLineArg[0].value);
    XtSetValues(BottomInfoLine, infoLineArg, XtNumber(infoLineArg));
#else
    XmString xs;

    (void) strcpy(BottomNonButtonInfo, message);
    xs = XmStringCreate(message, XmSTRING_DEFAULT_CHARSET);
    XtSetArg(infoLineArg[0], XmNlabelString, xs);
    XtSetValues(BottomInfoLine, infoLineArg, 1);
    XmStringFree(xs);
#endif
    return;
}

#define TOP	0
#define BOTTOM	1

static void
doButtons(resource, box, buttons, buttonList, size, infoLine)
char *resource;
Widget box;
Widget *buttons;
ButtonList *buttonList;
int *size;
int infoLine;
{
    char *ptr, *token;
    int j, i = 0;

    if (resource) {
	ptr = resource;

	while ((token = strtok(ptr, ", \t\n")) != NIL(char)) {
	    /* find name */
	    for (j = 0; j < *size; j++) {
		if (STREQ(token, (char *) buttonList[j].buttonArgs[0].value)) {
#ifndef MOTIF
		    buttons[i] = XtCreateWidget((char *) buttonList[j].buttonArgs[0].value,
						  commandWidgetClass,
						  box,
						  buttonList[j].buttonArgs,
						  buttonList[j].size);
#else
		    if (app_resources.useGadgets) {
		      buttons[i] = XmCreatePushButtonGadget(box,
						      (char *) buttonList[j].buttonArgs[0].value,
						      buttonList[j].buttonArgs,
						      buttonList[j].size);
		    } else {
		      buttons[i] = XmCreatePushButton(box,
						      (char *) buttonList[j].buttonArgs[0].value,
						      buttonList[j].buttonArgs,
						      buttonList[j].size);
		    }
		  if (!app_resources.useGadgets)
#endif
		    if (infoLine == TOP) {
			setTopInfoLineHandler(buttons[i], buttonList[j].message);
		    } else {
			setBottomInfoLineHandler(buttons[i], buttonList[j].message);
		    }
		    i++;
		    break;
		}
	    }
	    if (j == *size) {
		mesgPane(XRN_SERIOUS, "XRN error: bad button name: %s", token);
	    }
	    ptr = NIL(char);
	}
	*size = i;
	
    } else {
	for (i = 0; i < *size; i++) {
#ifndef MOTIF	    
	    buttons[i] = XtCreateWidget((char *) buttonList[i].buttonArgs[0].value,
					  commandWidgetClass,
					  box,
					  buttonList[i].buttonArgs,
					  buttonList[i].size);
#else
	    if (app_resources.useGadgets) {
	      buttons[i] = XmCreatePushButtonGadget(box,
					      (char *) buttonList[i].buttonArgs[0].value,
					      buttonList[i].buttonArgs,
					      buttonList[i].size);
	    } else {
	      buttons[i] = XmCreatePushButton(box,
					      (char *) buttonList[i].buttonArgs[0].value,
					      buttonList[i].buttonArgs,
					      buttonList[i].size);
	    }
	  if (!app_resources.useGadgets)
#endif
	    if (infoLine == TOP) {
		setTopInfoLineHandler(buttons[i], buttonList[i].message);
	    } else {
		setBottomInfoLineHandler(buttons[i], buttonList[i].message);
	    }
	}
    }
    return;
}


static void
createButtons()  
{
#define SETTRANSLATIONS(w, index, mode, bind) \
    Translations[index].widget = w; \
    Translations[index].unparsed[mode] = bind;

    SETTRANSLATIONS(TopButtonBox, 0, ADD_MODE, app_resources.addBindings);
    SETTRANSLATIONS(BottomButtonBox, 1, ADD_MODE, app_resources.addBindings);
    SETTRANSLATIONS(TopInfoLine, 2, ADD_MODE, app_resources.addBindings);
    SETTRANSLATIONS(BottomInfoLine, 3, ADD_MODE, app_resources.addBindings);
    SETTRANSLATIONS(Text, 4, ADD_MODE, app_resources.addBindings);
    SETTRANSLATIONS(ArticleText, 5, ADD_MODE, app_resources.addBindings);

    SETTRANSLATIONS(TopButtonBox, 0, ALL_MODE, app_resources.allBindings);
    SETTRANSLATIONS(BottomButtonBox, 1, ALL_MODE, app_resources.allBindings);
    SETTRANSLATIONS(TopInfoLine, 2, ALL_MODE, app_resources.allBindings);
    SETTRANSLATIONS(BottomInfoLine, 3, ALL_MODE, app_resources.allBindings);
    SETTRANSLATIONS(Text, 4, ALL_MODE, app_resources.allBindings);
    SETTRANSLATIONS(ArticleText, 5, ALL_MODE, app_resources.allBindings);

    SETTRANSLATIONS(TopButtonBox, 0, NEWSGROUP_MODE, app_resources.ngBindings);
    SETTRANSLATIONS(BottomButtonBox, 1, NEWSGROUP_MODE, app_resources.ngBindings);
    SETTRANSLATIONS(TopInfoLine, 2, NEWSGROUP_MODE, app_resources.ngBindings);
    SETTRANSLATIONS(BottomInfoLine, 3, NEWSGROUP_MODE, app_resources.ngBindings);
    SETTRANSLATIONS(Text, 4, NEWSGROUP_MODE, app_resources.ngBindings);
    SETTRANSLATIONS(ArticleText, 5, NEWSGROUP_MODE, app_resources.ngBindings);

    SETTRANSLATIONS(TopButtonBox, 0, ARTICLE_MODE, app_resources.artBindings);
    SETTRANSLATIONS(BottomButtonBox, 1, ARTICLE_MODE, app_resources.artBindings);
    SETTRANSLATIONS(TopInfoLine, 2, ARTICLE_MODE, app_resources.artBindings);
    SETTRANSLATIONS(BottomInfoLine, 3, ARTICLE_MODE, app_resources.artBindings);
    SETTRANSLATIONS(Text, 4, ARTICLE_MODE, app_resources.artBindings);
    SETTRANSLATIONS(ArticleText, 5, ARTICLE_MODE, app_resources.artBindings);

    XtAddActions(TopActions, XtNumber(TopActions));
    
    AddButtons = ARRAYALLOC(Widget, XtNumber(AddButtonList));
    XtAddActions(AddActions, XtNumber(AddActions));

    doButtons(app_resources.addButtonList, TopButtonBox, AddButtons, AddButtonList, &AddButtonListCount, TOP);

    NgButtons = ARRAYALLOC(Widget, XtNumber(NgButtonList));
    XtAddActions(NgActions, XtNumber(NgActions));
    
    doButtons(app_resources.ngButtonList, TopButtonBox, NgButtons, NgButtonList, &NgButtonListCount, TOP);

    AllButtons = ARRAYALLOC(Widget, XtNumber(AllButtonList));
    XtAddActions(AllActions, XtNumber(AllActions));
    
    doButtons(app_resources.allButtonList, BottomButtonBox, AllButtons, AllButtonList, &AllButtonListCount, BOTTOM);
    
    ArtButtons = ARRAYALLOC(Widget, XtNumber(ArtButtonList));
    XtAddActions(ArtActions, XtNumber(ArtActions));
    
    doButtons(app_resources.artButtonList, TopButtonBox, ArtButtons, ArtButtonList, &ArtButtonListCount, TOP);
    
    ArtSpecButtons = ARRAYALLOC(Widget, XtNumber(ArtSpecButtonList));
    
    doButtons(app_resources.artSpecButtonList, BottomButtonBox, ArtSpecButtons, ArtSpecButtonList, &ArtSpecButtonListCount, BOTTOM);

    return;
}


static void
setTranslations(mode)
int mode;
{
    Arg args[1];
    static int init[MAX_MODE] = {0,0,0,0};
    int i;

    if (!init[mode]) {
	/*
	 * first time:
	 *   parse table
	 *   override
	 *   XXX gone - get table back and store
	 */
	for (i = 0; i < sizeof(Translations) / sizeof(struct _translations); i++) {
	    XtTranslations table;

	    if (Translations[i].unparsed[mode] == NIL(char)) {
		table = 0;
	    } else {
		table = XtParseTranslationTable(Translations[i].unparsed[mode]);
	    }
#ifdef TRANSLATIONBUG
	    XtSetArg(args[0], XtNtranslations, 0);
	    XtGetValues(Translations[i].widget, args, XtNumber(args));
	    if ((XtTranslations) args[0].value == (XtTranslations) 0) {
		if (table) {
		    XtSetArg(args[0], XtNtranslations, table);
		    XtSetValues(Translations[i].widget, args, XtNumber(args));
		}
	    }
#endif
	    if (table) {
		XtOverrideTranslations(Translations[i].widget, table);
	    }
	    XtSetArg(args[0], XtNtranslations, 0);
	    XtGetValues(Translations[i].widget, args, XtNumber(args));
	    Translations[i].tables[mode] = (XtTranslations) args[0].value;
	    /* instead of the previous two lines:
	     * Translations[i].tables[mode] = table;
	     * however, this seems to lose bindings...
	     */
	}
	init[mode] = 1;
    } else {
	/*
	 * second and future times:
	 *   install translations
	 */
	for (i = 0; i < sizeof(Translations) / sizeof(struct _translations); i++) {
	    if (Translations[i].tables[mode]) {
#ifdef TRANSLATIONS_NOT_FREED
		XtSetArg(args[0], XtNtranslations, Translations[i].tables[mode]);
		XtSetValues(Translations[i].widget, args, XtNumber(args));
#else
		XtOverrideTranslations(Translations[i].widget, 
				       Translations[i].tables[mode]);
#endif
	    }
	}
    }
    return;
}


static void
swapMode()
/*
 * change the buttons displayed in the TopButtonBox (switch modes)
 */
{
    if (PreviousMode == Mode) {
	return;
    }

    XawTextDisableRedisplay(ArticleText);
#ifndef MOTIF
    XawPanedSetRefigureMode(Frame, False);
#else
    {
      Arg args[1];

      XtSetArg(args[0], XmNrefigureMode, False);
      XtSetValues(Frame, args, 1);
    }
#endif
    
    /*
     * NONE -> ADD
     *    manage add in top box
     *    manage art in bottom box
     *    desensitize bottom box
     *    install add actions in top box
     */
    if ((PreviousMode == NO_MODE) && (Mode == ADD_MODE)) {
	XtManageChildren(AddButtons, AddButtonListCount);
	XtManageChildren(ArtSpecButtons, ArtSpecButtonListCount);
	XtSetSensitive(BottomButtonBox, False);
	setTranslations(ADD_MODE);
    /*    
     * NONE -> NG
     *    manage ng in top box
     *    manage art in bottom box
     *    desensitize bottom box
     *    install ng actions in top box
     */
    } else if ((PreviousMode == NO_MODE) && (Mode == NEWSGROUP_MODE)) {
	XtManageChildren(NgButtons, NgButtonListCount);
	XtManageChildren(ArtSpecButtons, ArtSpecButtonListCount);
	XtSetSensitive(BottomButtonBox, False);
	setTranslations(NEWSGROUP_MODE);
    /*
     * ADD -> NG
     *    unmanage add in top box
     *    manage ng in top box
     *    install ng actions in top box
     */
    } else if ((PreviousMode == ADD_MODE) && (Mode == NEWSGROUP_MODE)) {
	XtUnmanageChildren(AddButtons, AddButtonListCount);
	XtManageChildren(NgButtons, NgButtonListCount);
	setTranslations(NEWSGROUP_MODE);
    /*
     * NG -> ART
     *    unmanage ng in top box
     *    manage art in top box
     *    sensitize bottom box
     *    install art actions in top box
     *    install art actions in bottom box
     */
    } else if ((PreviousMode == NEWSGROUP_MODE) && (Mode == ARTICLE_MODE)) {
	XtUnmanageChildren(NgButtons, NgButtonListCount);
	XtManageChildren(ArtButtons, ArtButtonListCount);
	XtSetSensitive(BottomButtonBox, True);
	setTranslations(ARTICLE_MODE);
    /*
     * NG -> ADD
     *    unmanage ng in top box
     *    manage add in top box
     *    install add actions in top box
     */
    } else if ((PreviousMode == NEWSGROUP_MODE) && (Mode == ADD_MODE)) {
	XtUnmanageChildren(NgButtons, NgButtonListCount);
	XtManageChildren(AddButtons, AddButtonListCount);
	setTranslations(ADD_MODE);
    /*
     * NG -> ALL
     *    desensitize top box
     *    unmanage art in bottom box
     *    manage all in bottom box
     *    sensitize bottom box
     *    install all actions in bottom box
     */
    } else if ((PreviousMode == NEWSGROUP_MODE) && (Mode == ALL_MODE)) {
	XtSetSensitive(TopButtonBox, False);
	XtUnmanageChildren(ArtSpecButtons, ArtSpecButtonListCount);
	XtManageChildren(AllButtons, AllButtonListCount);
	XtSetSensitive(BottomButtonBox, True);
	setTranslations(ALL_MODE);
    /*     
     * ART -> NG
     *    desensitize bottom box
     *    unmanage art in top box
     *    manage ng in top box
     *    install ng actions in top box
     */
    } else if ((PreviousMode == ARTICLE_MODE) && (Mode == NEWSGROUP_MODE)) {
	XtSetSensitive(BottomButtonBox, False);
	XtUnmanageChildren(ArtButtons, ArtButtonListCount);
	XtManageChildren(NgButtons, NgButtonListCount);
	setTranslations(NEWSGROUP_MODE);
    /*
     * ALL -> NG
     *    sensitize top box
     *    unmanage all in bottom box
     *    manage art in bottom box
     *    desensitize bottom box
     */
    } else if ((PreviousMode == ALL_MODE) && (Mode == NEWSGROUP_MODE)) {
	XtSetSensitive(TopButtonBox, True);
	XtUnmanageChildren(AllButtons, AllButtonListCount);
	XtManageChildren(ArtSpecButtons, ArtSpecButtonListCount);
	XtSetSensitive(BottomButtonBox, False);
	setTranslations(NEWSGROUP_MODE);
    /*
     * ART -> ALL (going back to previous ALL_MODE)
     *    unmanage art in bottom box
     *    unmanage art in top box
     *    manage all in bottom box
     *    manage ng in top box
     *    desensitize top box
     *    install all actions in bottom box
     */
    } else if ((PreviousMode == ARTICLE_MODE) && (Mode == ALL_MODE)) {
	XtUnmanageChildren(ArtSpecButtons, ArtSpecButtonListCount);
	XtUnmanageChildren(ArtButtons, ArtButtonListCount);
	XtManageChildren(AllButtons, AllButtonListCount);
	XtManageChildren(NgButtons, NgButtonListCount);
	XtSetSensitive(TopButtonBox, False);
	setTranslations(ALL_MODE);
    /*	
     * ALL -> ART
     *    unmanage ng in top box
     *    sensitize top box
     *    manage art in top box
     *    unmanage all in bottom box
     *    manage art in bottom box
     *    install art actions in bottom box
     */
    } else if ((PreviousMode == ALL_MODE) && (Mode == ARTICLE_MODE)) {
	XtSetSensitive(TopButtonBox, True);
	XtUnmanageChildren(NgButtons, NgButtonListCount);
	XtManageChildren(ArtButtons, ArtButtonListCount);
	XtUnmanageChildren(AllButtons, AllButtonListCount);
	XtManageChildren(ArtSpecButtons, ArtSpecButtonListCount);
	setTranslations(ARTICLE_MODE);
    } else {
	(void) sprintf(error_buffer, "unsupported transition: %d to %d\n",
			       PreviousMode, Mode);
	ehErrorExitXRN(error_buffer);
    }
#ifndef MOTIF
    XawPanedSetRefigureMode(Frame, True);
#else
    {
      Arg args[2];
      Dimension height;
      short columns;

/* Lock size of button boxes to their desired height */
      height = 0;
      if (XtIsManaged(AddButtons[0])) {
	height = DesiredBoxHeight(TopButtonBox, AddButtons,
				  AddButtonListCount);
      } else if (XtIsManaged(NgButtons[0])) {
	height = DesiredBoxHeight(TopButtonBox, NgButtons,
				  NgButtonListCount);
      } else if (XtIsManaged(ArtButtons[0])) {
	height = DesiredBoxHeight(TopButtonBox, ArtButtons,
				  ArtButtonListCount);
      }
      if (height) {
	XtSetArg(args[0], XmNpaneMinimum, height);
	XtSetArg(args[1], XmNpaneMaximum, height);
	XtSetValues(TopButtonBox, args, 2);
      }

      height = 0;
      if (XtIsManaged(AllButtons[0])) {
	height = DesiredBoxHeight(BottomButtonBox, AllButtons,
				  AllButtonListCount);
      } else if (XtIsManaged(ArtSpecButtons[0])) {
	height = DesiredBoxHeight(BottomButtonBox, ArtSpecButtons,
				  ArtSpecButtonListCount);
      }
      if (height) {
	XtSetArg(args[0], XmNpaneMinimum, height);
	XtSetArg(args[1], XmNpaneMaximum, height);
	XtSetValues(BottomButtonBox, args, 2);
      }

      XtSetArg(args[0], XmNrefigureMode, True);
      XtSetValues(Frame, args, 1);
#ifndef MOTIF_BUG	/* Motif paned widget bug */
      XtUnmanageChild(BottomButtonBox);
      XtManageChild(BottomButtonBox);
#endif
    }
#endif
    XawTextEnableRedisplay(ArticleText);
    return;
}


static int XRNAbort = 0;

int
abortP()
{
    xthHandleAllPendingEvents();
    return XRNAbort;
}

void
abortSet()
{
    XRNAbort = 1;
    return;
}

void
abortClear()
{
    XRNAbort = 0;
    return;
}


#ifndef MOTIF
int
lastPage(w)
Widget w;
{
    XawTextPosition top;

    top = XawTextTopPosition(w);

    if (top >= ArticleTextFilesize) {
	return 1;
    }
    return 0;
}
#else

/**********************************************************************
In Motif, scrolling the text when their is no more text to scroll
doesn't result in a detectable state like the Xaw text widget does.
Therefore, this is a different routine that should be used before
trying to scroll, and indicates whether scrolling is possible.  We do
this by examining the vertical scrollbar state of the scrolled window
which is the parent of the text widget.
**********************************************************************/

Boolean
onLastPage(w)
Widget w;
{
#ifdef OLD_VERSION
    Widget vs;
    int max, value, size, ignore;
    Arg args[2];

    XtSetArg(args[0], XmNverticalScrollBar, &vs);
    XtGetValues(XtParent(w), args, 1);
    XtSetArg(args[0], XmNmaximum, &max);
    XtGetValues(vs, args, 1);
    XmScrollBarGetValues(vs, &value, &size, &ignore, &ignore);
    return (value+size >= max);
#else
    Position ignore;
    return (XmTextPosToXY(w, XmTextGetLastPosition(w), &ignore, &ignore));
#endif
}
#endif


/*ARGSUSED*/
static void
doTheRightThing(widget, event, string, count)
Widget widget;
XEvent *event;
String *string;
Cardinal *count;
{
    static void ngReadFunction(), artScrollFunction(), artNextFunction();
    static void artNextUnreadFunction(), artSubNextFunction();
    static void allScrollFunction();

    if (inCommand) {
	return;
    }
    inCommand = 1;
    busyCursor();
    switch (Mode) {
	case ALL_MODE:
	allScrollFunction((Widget) 0);
	break;

	case NEWSGROUP_MODE:
	if (count && *count == 1 && strcmp(string[0], "jump") == 0) {
	    NewsgroupEntryMode = XRN_JUMP;
	}
	ngReadFunction((Widget) 0);
	break;

	case ARTICLE_MODE:
	if (event &&
	    (event->type == ButtonPress || event->type == ButtonRelease)) {
	    artNextFunction((Widget) 0);
	    break;
	}
	if (!app_resources.pageArticles) {
	    if (app_resources.subjectRead == False) {
		artNextUnreadFunction((Widget) 0);
	    } else {
		artSubNextFunction((Widget) 0);
	    }
	} else {
/* For Motif, check if scrolling is possible before doing it */
#ifndef MOTIF
	    artScrollFunction((Widget) 0);
	    if (lastPage(ArticleText)) {
#else
	    if (onLastPage(ArticleText)) {
#endif
		if (app_resources.subjectRead == False) {
		    artNextUnreadFunction((Widget) 0);
		} else {
		    artSubNextFunction((Widget) 0);
		}
#ifdef MOTIF
	    } else {
	      artScrollFunction((Widget) 0);
#endif
	    }
	}
	break;
    }
    unbusyCursor();
    inCommand = 0;
    return;
}


void
updateSubjectWidget(left, right)
XawTextPosition left, right;
/*
 *  Redraw the text between left and right in the subject window;
 *  Adjust the window so the cursor is between min and max lines.
 */
{
    XawTextPosition currentPos;
    Arg arg[1];
    Arg sargs[5];
    int numLines, count;

#ifndef MOTIF
    if (SubjectSource != 0) {
#else
    if (TextMotifString != 0) {
#endif
	currentPos = ArticleTop = XawTextTopPosition(Text);
	XawTextInvalidate(Text, left - 1, right + 1);
	if ((app_resources.minLines >= 0) && (app_resources.maxLines >= 0)) {
	    if (currentPos <= ArtPosition) {
		for (numLines = 1; currentPos < ArtPosition; numLines++) {
		    if (!moveCursor(FORWARD, SubjectString, &currentPos)) {
			break;
		    }
		}
	    } else {
		numLines = -1;
		currentPos = ArtPosition;
	    }
	    if (numLines > app_resources.maxLines
			|| numLines < app_resources.minLines) {
		for (count = 1; count < app_resources.defaultLines; count++) {
		    if (!moveCursor(BACK, SubjectString, &currentPos)) {
			break;
		    }
		}
#ifndef MOTIF
		XtSetArg(arg[0], XtNdisplayPosition, currentPos);
		XtSetValues(Text, arg, XtNumber(arg));
#else
		XmListSetPos(Text,
			     XawTextToMotifIndex(TextMotifString, currentPos));
#endif
	    }
#ifdef notdef
	      else if (numLines < app_resources.minLines) {
		for (count = 1; count < app_resources.maxLines; count++) {
		    if (!moveCursor(BACK, SubjectString, &currentPos)) {
			break;
		    }
		}
#ifndef MOTIF
		XtSetArg(arg[0], XtNdisplayPosition, currentPos);
		XtSetValues(Text, arg, XtNumber(arg));
#else
		XmListSetPos(Text,
			     XawTextToMotifIndex(TextMotifString, currentPos));
#endif
	    }
#endif /* notdef */
	}
    } else {
#ifndef MOTIF
	ArticleTop = (XawTextPosition) 0;
	XtSetArg(sargs[0], XtNstring, SubjectString);
	XtSetArg(sargs[1], XtNlength, utStrlen(SubjectString) + 1);
	XtSetArg(sargs[2], XtNeditType, XawtextRead);
	XtSetArg(sargs[3], XtNuseStringInPlace, True);
	XtSetArg(sargs[4], XtNtype, XawAsciiString);
	SubjectSource = XtCreateWidget("subjectTextSource",
				       asciiSrcObjectClass,
				       Text, sargs, XtNumber(sargs));
	XawTextSetSource(Text, SubjectSource, (XawTextPosition) ArticleTop);
#else
	XawTextSetMotifString(Text, SubjectString);
#endif
    }
    XawTextSetInsertionPoint(Text, ArtPosition);
    return;
}


static int
getNearbyArticle(status, filename, question, artNum)
int status;
char **filename, **question;
long *artNum;
/*
 * Get the nearest article to the cursor.  If there is no article on the
 * current line, search forward or backwards for a valid article, depending
 * on the value of status.  Return the filename and question of the
 * article obtained.
 */
{
    XawTextPosition beginning;
    
    if (status == art_PREV) {
	if (SubjectString[ArtPosition] == '\0') {
	    if (ArtPosition == 0) {
		/* no articles remain, jump out of article mode */
		return art_DONE;
	    }
	    if (!moveCursor(BACK, SubjectString, &ArtPosition)) {
		return art_DONE;
	    }
	}
	*artNum = atol(&SubjectString[ArtPosition+2]);
	gotoArticle(*artNum);
	while (getArticle(filename, question) != XRN_OKAY) {
#ifndef MOTIF
	    removeLine(SubjectString, Text, &SubjectSource, ArtPosition,
		       &ArticleTop);
#else
	    removeLine(SubjectString, Text, &TextMotifString, ArtPosition,
		       &ArticleTop);
#endif
	    if (!moveCursor(BACK, SubjectString, &ArtPosition)) {
		return art_DONE;
	    }
	    *artNum = atol(&SubjectString[ArtPosition+2]);
	    gotoArticle(*artNum);
	}
	return art_CHANGE;
    }

    if (status == art_NEXT) {
	if (SubjectString[ArtPosition] == '\0') {
	    return art_DONE;
	}
	*artNum = atol(&SubjectString[ArtPosition+2]);
	gotoArticle(*artNum);
	while (getArticle(filename, question) != XRN_OKAY) {
#ifndef MOTIF
	    removeLine(SubjectString, Text, &SubjectSource, ArtPosition,
		       &ArticleTop);
#else
	    removeLine(SubjectString, Text, &TextMotifString, ArtPosition,
		       &ArticleTop);
#endif
	    if (SubjectString[ArtPosition] == '\0') {
		return art_DONE;
	    }
	    *artNum = atol(&SubjectString[ArtPosition+2]);
	    gotoArticle(*artNum); 
	}
	return art_CHANGE;
    }
    if (status == art_NEXTWRAP) {
	if (SubjectString[ArtPosition] == '\0') {
	    if (ArtPosition == 0) {
		return art_DONE;
	    }
	    ArtPosition = 0;
	}
	*artNum = atol(&SubjectString[ArtPosition+2]);
	gotoArticle(*artNum);
	while (getArticle(filename, question) != XRN_OKAY) {
#ifndef MOTIF
	    removeLine(SubjectString, Text, &SubjectSource, ArtPosition,
		       &ArticleTop);
#else
	    removeLine(SubjectString, Text, &TextMotifString, ArtPosition,
		       &ArticleTop);
#endif
	    if (SubjectString[ArtPosition] == '\0') {
		if (ArtPosition == 0) {
		    return art_DONE;
		}
		ArtPosition = 0;
	    }
	    *artNum = atol(&SubjectString[ArtPosition+2]);
	    gotoArticle(*artNum); 
	}
	return art_CHANGE;
    }
    if (status == art_UNREAD) {
	if (SubjectString[ArtPosition] == '\0') {
	    if (ArtPosition == 0) {
		return art_DONE;
	    }
	    ArtPosition = 0;
	}
	beginning = ArtPosition;
	if (SubjectString[ArtPosition] != ' ') {
	    (void) moveUpWrap(SubjectString, &ArtPosition);
	    while ((SubjectString[ArtPosition] != ' ') &&
		   (ArtPosition != beginning)) {
		if (!moveUpWrap(SubjectString, &ArtPosition)) {
		    return art_DONE;
		}
	    }
	    if (ArtPosition == beginning) {
		return art_DONE;
	    }
	}
	/* we are at an unread article */
	*artNum = atol(&SubjectString[ArtPosition+2]);
	gotoArticle(*artNum);
	while (getArticle(filename, question) != XRN_OKAY) {
#ifndef MOTIF
	    removeLine(SubjectString, Text, &SubjectSource, ArtPosition,
		       &ArticleTop);
#else
	    removeLine(SubjectString, Text, &TextMotifString, ArtPosition,
		       &ArticleTop);
#endif
	    if (SubjectString[ArtPosition] == '\0') {
		if (ArtPosition == 0) {
		    return art_DONE;
		}
		ArtPosition = 0;
	    }
	    while ((SubjectString[ArtPosition] != ' ') &&
		   (ArtPosition != beginning)) {
		if (!moveUpWrap(SubjectString, &ArtPosition)) {
		    return art_DONE;
		}
	    }
	    if (ArtPosition == beginning) {
		return art_DONE;
	    }
	    *artNum = atol(&SubjectString[ArtPosition+2]);
	    gotoArticle(*artNum); 
	}
	return art_CHANGE;
    }
    return art_CHANGE;
}


#define CHANGE 0		/* subject window has changed */
#define NOCHANGE 1		/* subject window has not changed */
#define DONE 2			/* no new article was found */
				/* EXIT is already defined, it implies */
				/* there are no articles left at all */

static int
isPrevSubject(subject, filename, question, artNum)
char *subject;
char **filename, **question;
long *artNum;
/*
 *
 */
{
    char *newsubject;
    char *newLine;
    char *newSubjectString;
    char *newString;
    char *oldString;
    XawTextPosition save;
    int count = 0;

    oldString = NIL(char);
    save = ArtPosition;
    startSearch();
    abortClear();
    
    for (;;) {
	count++;

	if (count == app_resources.cancelCount) {
	    cancelButton();
	}

	if (abortP()) {
	    failedSearch();
	    ArtPosition = save;
	    cancelDestroy();
	    return ABORT;
	}
	if (SubjectString[ArtPosition] == '\0') {
	    cancelDestroy();
	    return EXIT;
	}
	if (ArtPosition != 0) {
	    (void) moveCursor(BACK, SubjectString, &ArtPosition);
	    *artNum = atol(&SubjectString[ArtPosition+2]);
	    newsubject = getSubject(*artNum);
	    if (utSubjectCompare(newsubject, subject) == 0) {
		gotoArticle(*artNum);
		if (getArticle(filename, question) != XRN_OKAY) {
#ifndef MOTIF
		    removeLine(SubjectString, Text, &SubjectSource, ArtPosition,
			       &ArticleTop);
#else
		    removeLine(SubjectString, Text, &TextMotifString, ArtPosition,
			       &ArticleTop);
#endif
		    continue;
		}
		if (SubjectString[ArtPosition] == 'u') {
		    markArticleAsUnread(*artNum);
		}
		cancelDestroy();
		return NOCHANGE;
	    }
	    continue;
	} else {
	    if ((newLine = getPrevSubject()) == NIL(char)) {
		failedSearch();
		ArtPosition = save;
		cancelDestroy();
		return DONE;
	    }
	    newLine[0] = '+';
	    *artNum = atol(&newLine[2]);
	    newsubject = getSubject(*artNum);
	    if (oldString != NIL(char)) {
		newString = ARRAYALLOC(char, (utStrlen(oldString) + utStrlen(newLine) + 2));
		(void) strcpy(newString, newLine);
		(void) strcat(newString, "\n");
		(void) strcat(newString, oldString);
		FREE(oldString);
	    } else {
		newString = ARRAYALLOC(char, (utStrlen(newLine) + 2));
		(void) strcpy(newString, newLine);
		(void) strcat(newString, "\n");
	    }
	    if (utSubjectCompare(newsubject, subject) == 0) {
		/* found a match, go with it */

		newSubjectString = ARRAYALLOC(char, (utStrlen(newString) + utStrlen(SubjectString) + 1));
		(void) strcpy(newSubjectString, newString);
		(void) strcat(newSubjectString, SubjectString);
		FREE(SubjectString);
		SubjectString = newSubjectString;

		gotoArticle(*artNum);
		(void) getArticle(filename, question);
		if (SubjectString[ArtPosition] == 'u') {
		    markArticleAsUnread(*artNum);
		}
		cancelDestroy();
		return CHANGE;
	    }
	    oldString = newString;
	    continue;
	}
    }
}


static int
isNextSubject(subject, filename, question, artNum)
char *subject;
char **filename, **question;
long *artNum;
/*
 *
 */
{
    char *newsubject;
    XawTextPosition save = ArtPosition;
    int count = 0;

    abortClear();
    
    for (;;) {
	count++;

	if (count == app_resources.cancelCount) {
	    cancelButton();
	}

	if (count >= app_resources.cancelCount && count % 10 == 0 && abortP()) {
	    failedSearch();
	    ArtPosition = save;
	    cancelDestroy();
	    return ABORT;
	}
	if (SubjectString[ArtPosition] == '\0') {
	    cancelDestroy();
	    if (ArtPosition == 0) {
		return EXIT;
	    }
	    ArtPosition = save;
	    return DONE;
	}
	*artNum = atol(&SubjectString[ArtPosition+2]);
	newsubject = getSubject(*artNum);
	if (utSubjectCompare(newsubject, subject) == 0) {
	    gotoArticle(*artNum);
	    if (getArticle(filename, question) != XRN_OKAY) {
#ifndef MOTIF
		removeLine(SubjectString, Text, &SubjectSource, ArtPosition,
			   &ArticleTop);
#else
		removeLine(SubjectString, Text, &TextMotifString, ArtPosition,
			   &ArticleTop);
#endif
		continue;
	    }
	    cancelDestroy();
	    return NOCHANGE;
	} else {
	    if (!moveCursor(FORWARD, SubjectString, &ArtPosition)) {
		cancelDestroy();
		return EXIT;
	    }
	}
    }
}


static int
getPrevious(artNum)
XawTextPosition *artNum;
/*
 *
 */
{
    char *newLine;
    char *newString;
    Arg sargs[5];

    if ((newLine = getPrevSubject()) != NIL(char)) {
	newString = ARRAYALLOC(char, (utStrlen(SubjectString) + utStrlen(newLine) + 2));
	(void) strcpy(newString, newLine);
	(void) strcat(newString, "\n");
	(void) strcat(newString, SubjectString);
	FREE(SubjectString);
	SubjectString = newString;
	ArtPosition = ArticleTop = 0;
#ifndef MOTIF
	if (SubjectSource != 0) {
	    XawStringSourceDestroy(SubjectSource);
	}

	XtSetArg(sargs[0], XtNstring, SubjectString);
	XtSetArg(sargs[1], XtNlength, utStrlen(SubjectString) + 1);
	XtSetArg(sargs[2], XtNeditType, XawtextRead);
	XtSetArg(sargs[3], XtNuseStringInPlace, True);
	XtSetArg(sargs[4], XtNtype, XawAsciiString);
	SubjectSource = XtCreateWidget("subjectTextSource",
				       asciiSrcObjectClass,
				       Text, sargs, XtNumber(sargs));
	XawTextSetSource(Text, SubjectSource, ArticleTop);
#else
	XawTextSetMotifString(Text, SubjectString);
#endif
	XawTextSetInsertionPoint(Text, ArtPosition);
	*artNum = atol(&SubjectString[ArtPosition+2]);
	return TRUE;
    }
    
    return FALSE;
}

    
static void
selectedArticle(status)
int status;
/*
 */
{
    XawTextPosition left, right;
    
    XawTextGetSelectionPos(Text, &left, &right);
    XawTextUnsetSelection(Text);
    
    /* nothing was selected if left == right, so get article on NEXT or */
    /* PREVIOUS line */

    if (left == right) {
	ArtPosition = XawTextGetInsertionPoint(Text);

	if (ArtEntry == 1) {
	    return;
	}

	/* get article on NEXT line */

	if ((status == art_NEXT) || (status == art_UNREAD)) {
	    (void) moveCursor(FORWARD, SubjectString, &ArtPosition);
	} else if (status == art_PREV) {
	    (void) moveCursor(BACK, SubjectString, &ArtPosition);
	}
	return;
    }

    /* something was selected */

    /* make sure selection includes only whole groups */
    moveBeginning(SubjectString, &left);
    ArtPosition = left;
    return;
}


static void
adjustNewsgroupWidget()
/*
 * Adjust the top position in the newsgroup window such
 * that the cursor stays between min and max lines.
 */
{
    int numLines, count;
    XawTextPosition currentPos;

    moveBeginning(NewsGroupsString, &GroupPosition);
    currentPos = NewsgroupTop;
#ifndef MOTIF
    if ((app_resources.minLines >= 0) && (app_resources.maxLines >= 0)) {
#else
/* Make sure that the number of list items is greater than the min and max
   constraints before adjusting the list.  Otherwise, we get really weird
   scrolling effects.  This is actually a potential bug with Xaw, as well. */
      {
	Arg args[1];
	int v;

	XtSetArg(args[0], XmNvisibleItemCount, &v);
	XtGetValues(Text, args, 1);
	count = v;
      }
    if (app_resources.minLines >= 0 && app_resources.minLines < count &&
	app_resources.maxLines >= 0 && app_resources.maxLines < count) {
#endif
	if (currentPos <= GroupPosition) {
	    for (numLines = 1; currentPos < GroupPosition; numLines++) {
		if (!moveCursor(FORWARD, NewsGroupsString, &currentPos)) {
		    break;
		}
	    }
	} else {
	    numLines = -1;
	    currentPos = GroupPosition;
	}
	if (numLines > app_resources.maxLines
		|| numLines < app_resources.minLines) {
	    for (count = 1; count < app_resources.defaultLines; count++) {
		if (!moveCursor(BACK, NewsGroupsString, &currentPos)) {
		    break;
		}
	    }
	    NewsgroupTop = currentPos;
	}
    }
    return;
}


static void
redrawAllWidget(position)
XawTextPosition position;
/*
 * Redraw the all groups window, assuming it has changed
 */
{
    Arg sargs[5];
    
    if (Mode != ALL_MODE) {
	return;
    }

#ifndef MOTIF
    /* free source */
    if (AllSource != 0) {
	XawStringSourceDestroy(AllSource);
	AllSource = 0;
    }
#else
    ArticleTextMotifString = 0;
#endif
    
    /* free string */
    if (AllGroupsString != NIL(char)) {
	FREE(AllGroupsString);
	AllGroupsString = NIL(char);
    }
    AllGroupsString = getStatusString(AllStatus);

	
#ifndef MOTIF
    XtSetArg(sargs[0], XtNstring, AllGroupsString);
    XtSetArg(sargs[1], XtNlength, utStrlen(AllGroupsString) + 1);
    XtSetArg(sargs[2], XtNeditType, XawtextRead);
    XtSetArg(sargs[3], XtNuseStringInPlace, True);
    XtSetArg(sargs[4], XtNtype, XawAsciiString);
    AllSource = XtCreateWidget("allTextSource",
				   asciiSrcObjectClass,
				   ArticleText, sargs, XtNumber(sargs));
    XawTextSetSource(ArticleText, AllSource, position);
    XtSetValues(ArticleText, lineSelArgs, XtNumber(lineSelArgs));
#else
    XawTextSetMotifString(ArticleText, AllGroupsString);
#endif
    
    return;
}

#ifdef WATCH
static int watchcount = -1;
static char **GroupList;
static char *GroupListString = 0;

Boolean   watchingGroup(str)
char * str;
{

    char *p;
    int i;

    /* if resource not defined or empty */
    if(! app_resources.watchList || (watchcount == -1 && GroupListString))
	return True;
    if (str == 0)
	return False;
    if( watchcount == -1){
	char *q,*p;
	char * strpbrk();
	int maxcount;

	GroupListString = XtNewString(app_resources.watchList);
	q = GroupListString;
	watchcount = 0;
	while(1){
	    /* skip white space */
	    q = q + strspn(q," \t");
	    if( *q == '\0')
		break;
	    ++watchcount;
	    /* find end of token */
	    if((q = strpbrk(q," \t")) == 0)
		break;
	}
	GroupList = ARRAYALLOC(char *, watchcount);
	q = GroupListString;
	watchcount = -1;
	while(1){
	    /* skip white space */
	    q = q + strspn(q," \t");
	    if( *q == '\0')
		break;
	    GroupList[++watchcount] = q;
	    /* find end of token */
	    if((q = strpbrk(q," \t")) == 0)
		break;
	    *q++ = '\0';
	}

    }	
    for(i = 0; i <= watchcount; i++)
	{
	    char *q;
	    char *strstr();

	    if(p = strstr(str,GroupList[i]))
		if( p == str || (*--p) == ' '){
		    q = p + strlen(GroupList[i])+1;
		    if( *q == '\0' || *q == ' ' || *q == '\t')
			return True;
		}
	}

    return False;
}
#endif

static void
redrawNewsgroupTextWidget()
/*
 * Rebuild the newsgroup text window.
 * Find out what groups have articles to read and build up the string.
 * Create a string source and display it.
 */
{
    static Widget NgSource = 0;
    Arg sargs[5];
    char name[GROUP_NAME_SIZE];

    if (Mode != NEWSGROUP_MODE) {
	return;
    }

    if (NewsGroupsString != NIL(char)) {
	FREE(NewsGroupsString);
    }
    
    NewsGroupsString = unreadGroups(NewsgroupDisplayMode);

    /* update the info line */
    if (utStrlen(NewsGroupsString) == 0) {
	setTopInfoLine("No more unread articles in the subscribed to newsgroups");
        if (XtIsRealized(TopLevel))
	    XSetIconName(XtDisplay(TopLevel), XtWindow(TopLevel),
			 app_resources.iconName);
    } else {
        if (XtIsRealized(TopLevel)) {
#ifdef WATCH
	    if(watchingGroup(NewsGroupsString))
#endif
		XSetIconName(XtDisplay(TopLevel), XtWindow(TopLevel),
			 app_resources.unreadIconName);
#ifdef WATCH
	    else
		XSetIconName(XtDisplay(TopLevel), XtWindow(TopLevel),
			 app_resources.iconName);
#endif
	}
    }
#ifndef MOTIF
    if (NgSource != 0) {
	XawStringSourceDestroy(NgSource);
    }
#endif
    if (NewsGroupsString[GroupPosition] == '\0') {
	GroupPosition = 0;
    }
    currentGroup(Mode, NewsGroupsString, name, GroupPosition);
    if (STREQ(name, LastGroup)) {
	(void) moveUpWrap(NewsGroupsString, &GroupPosition);
    }
    
    adjustNewsgroupWidget();
    
#ifndef MOTIF
    XtSetArg(sargs[0], XtNstring, NewsGroupsString);
    XtSetArg(sargs[1], XtNlength, utStrlen(NewsGroupsString) + 1);
    XtSetArg(sargs[2], XtNeditType, XawtextRead);
    XtSetArg(sargs[3], XtNuseStringInPlace, True);
    XtSetArg(sargs[4], XtNtype, XawAsciiString);
    NgSource = XtCreateWidget("allTextSource",
				   asciiSrcObjectClass,
				   Text, sargs, XtNumber(sargs));
    XawTextSetSource(Text, NgSource, (XawTextPosition) NewsgroupTop);
#else
    if (NewsGroupsString[0] == '\0' && !XtIsRealized(TopLevel)) {
	XawTextSetMotifString(Text, " ");
    }
    XawTextSetMotifString(Text, NewsGroupsString);
#endif
    
    return;
}


static void
updateNewsgroupMode()
/*
 * update the info line and update the newsgroup text window
 */
{
    if (PreviousMode != NEWSGROUP_MODE) {
	setTopInfoLine("Questions apply to current selection or cursor position");
    }
    redrawNewsgroupTextWidget();
    XawTextSetInsertionPoint(Text, GroupPosition);
    return;
}


static void
switchToNewsgroupMode()
/*
 * install the newsgroup mode buttons (and the delete the previous mode buttons)
 * and then go to newsgroup mode
 */
{
    PreviousMode = Mode;
    Mode = NEWSGROUP_MODE;
    LastRegexp = NIL(char);

    resetSelection();

    /* switch buttons */
    swapMode();
    
    /* update the newsgroup mode windows */
    updateNewsgroupMode();
    
    return;
}


static int
switchToArticleMode()
/*
 * install the article mode buttons (and delete the previous mode buttons),
 * build the subject line screen, and call ARTICLE_MODE function 'next unread'
 */
{
    static void artNextUnreadFunction();
    int oldMode;
    char *NewSubjectString;
    
    NewSubjectString = getSubjects(UNREAD);

    if ((NewSubjectString == NIL(char)) || (*NewSubjectString == '\0')) {
	bogusNewsgroup();
	/*
	 * the sources and strings have been destroyed at this point
	 * have to recreate them - the redraw routines check the mode
	 * so we can call all of them and only the one that is for the
	 * current mode will do something
	 */
	redrawAllWidget((XawTextPosition) 0);
	redrawNewsgroupTextWidget();
	return BAD_GROUP;
    }

#ifndef MOTIF
    /* change the text window */
    /* XawTextSetLastPos(Text, (XawTextPosition) 0); */

    if (SubjectSource != 0) {
	XawStringSourceDestroy(SubjectSource);
	SubjectSource = 0;
    }
#else
    TextMotifString = 0;
#endif

    if (SubjectString != NIL(char)) {
	FREE(SubjectString);
    }
    SubjectString = NewSubjectString;
    
    /* get rid of previous groups save file string */
    if (SaveString && app_resources.resetSave) {
	if (SaveString != app_resources.saveString) {
		XtFree(SaveString);
	}
	SaveString = XtNewString(app_resources.saveString);
    }	

    oldMode = PreviousMode;
    
    PreviousMode = Mode;
    Mode = ARTICLE_MODE;

    /* switch buttons */
    swapMode();

    /*
     * "What's the purpose of this?"  you're saying.  "It looks wrong."
     * Well, try taking out any mention of oldMode in this function,
     * then recompile and do the following:
     * 1. Enter article mode
     * 2. Clock "Next Newsgroup".
     * 3. Click "Quit".
     * You will be returned to all mode instead of article mode.
     * Therefore, we've got to keep track in this function of whether
     * we're switching from article mode to article mode, and if so,
     * set PreviousMode truly in order for swapMode to work, but once
     * that's done, we can put it back to what it was before).
     */
    if (PreviousMode == ARTICLE_MODE) {
	PreviousMode = oldMode;
    }
    /* get and display the article */
    ArtPosition = 0;
    updateSubjectWidget(0, 0);
    XawTextSetInsertionPoint(Text, 0);	/* source isn't around... */
#ifndef MOTIF
    XawTextSetSelectionArray(ArticleText, allSelectArray);
#endif /* MOTIF */
    ArtEntry = 1;
    artNextUnreadFunction((Widget) 0);

    return GOOD_GROUP;
}


static void
updateAllWidget(left, right)
XawTextPosition left, right;
/*
 *
 */
{
    XawTextPosition current;
    Arg sargs[5];
    
#ifndef MOTIF
    if (AllSource != 0) {
#else
    if (ArticleTextMotifString != 0) {
#endif
	XawTextInvalidate(ArticleText, left - 1, right + 1);
	current = right+1;
	(void) setCursorCurrent(AllGroupsString, &current);
	XawTextSetInsertionPoint(ArticleText, current);
    } else {
#ifndef MOTIF
	XtSetArg(sargs[0], XtNstring, AllGroupsString);
	XtSetArg(sargs[1], XtNlength, utStrlen(AllGroupsString) + 1);
	XtSetArg(sargs[2], XtNeditType, XawtextRead);
	XtSetArg(sargs[3], XtNuseStringInPlace, True);
	XtSetArg(sargs[4], XtNtype, XawAsciiString);
	AllSource = XtCreateWidget("allTextSource",
				   asciiSrcObjectClass,
				   ArticleText, sargs, XtNumber(sargs));
	XawTextSetSource(ArticleText, AllSource, 0);
#else
	XawTextSetMotifString(ArticleText, AllGroupsString);
#endif
    }
    
    return;
}


static void
redrawArticleWidget(filename, question)
char *filename, *question;
/*
 * If the article to be displayed has changed, update the article
 * window and redraw the mode line
 */
/*
 * In order to avoid bug(?) in AsciiSrc (It will only notice if the *value*
 * of XtNstring (i.e. the address) has changed when XtNtype = XawAsciiFile),
 * we will do it "the Motif way" and read in the file ourselves...
 */
{
    Arg args[5];
    int fildes;
    struct stat buf;
    char *olddata;
    char *data = 0;

    if (filename != LastArticle) {
	LastArticle = filename;
	if ((fildes = open(filename, O_RDONLY)) != -1) {
	    fstat(fildes, &buf);
	    data = XtMalloc((size_t) buf.st_size+1);
	    read(fildes, data, (unsigned) buf.st_size);
	    data[buf.st_size] = '\0';
	    close(fildes);

#ifndef MOTIF
	    XtSetArg(args[0], XtNtype, XawAsciiString);
	    XtSetArg(args[1], XtNstring, data);
	    XtSetArg(args[2], XtNlength, buf.st_size+1);
	    XtSetArg(args[3], XtNuseStringInPlace, True);
	    XtSetArg(args[4], XtNeditType, XawtextRead);

#ifdef TEXT_WIDGET_WORKS_CORRECTLY
	    if (ArtSource == 0) {
		ArtSource = XtCreateWidget("artTextSource",
					   asciiSrcObjectClass,
					   ArticleText, args, XtNumber(args));
		XawTextSetSource(ArticleText, ArtSource, (XawTextPosition) 0);
		XawTextSetSelectionArray(ArticleText, allSelectArray);
	    } else {
		XtVaGetValues(ArtSource,
			      XtNstring, &olddata,
			      NULL);
		XtFree(olddata);
		XtSetValues(ArtSource, args, XtNumber(args));
	    }
#else
	    /* destroy the old text file window */
	    if (ArtSource != 0) {
		XtVaGetValues(ArtSource,
			      XtNstring, &olddata,
			      NULL);
		XtFree(olddata);
		XtDestroyWidget(ArtSource);
	    }

	    ArtSource = XtCreateWidget("artTextSource",
				       asciiSrcObjectClass,
				       ArticleText, args, XtNumber(args));
	    XawTextSetSource(ArticleText, ArtSource, (XawTextPosition) 0);
	    XawTextSetSelectionArray(ArticleText, allSelectArray);
#endif
#else
	    ChooseText(True);
	    XmTextSetString(ArticleText, data);
	    XtFree(data);
#endif
	    ArticleTextFilesize = buf.st_size;
	}

	setBottomInfoLine(question);
#ifdef XRN_PREFETCH
	/* force the screen to update before prefetching */
	xthHandlePendingExposeEvents();
	
	prefetchNextArticle();
	
#endif /* XRN_PREFETCH */
    }
    return;
}


static void
exitArticleMode()
/*
 * release the storage associated with article mode, unlink the article files,
 * and go to newsgroup mode
 */
{
    char *data;

    LastArticle = NIL(char);
    PrevArticle = CurrentArticle = 0;
    
#ifndef MOTIF
    /* release storage and unlink files */
    if (SubjectSource != 0) {
	XawStringSourceDestroy(SubjectSource);
	SubjectSource = 0;
    }

    /* dummy source - a placeholder until the new sources are installed */
    if (DummySource == 0) {
	static Arg sargs[] = {
	    {XtNstring, (XtArgVal) ""},
	    {XtNlength, (XtArgVal) 2},
	    {XtNeditType, (XtArgVal) XawtextRead},
	    {XtNuseStringInPlace, (XtArgVal) True},
	    {XtNtype, (XtArgVal) XawAsciiString},
	};
	DummySource = XtCreateWidget("dummyTextSource",
				   asciiSrcObjectClass,
				   ArticleText, sargs, XtNumber(sargs));
    }
    XawTextSetSource(Text, DummySource, (XawTextPosition) 0);
#ifdef TEXT_WIDGET_WORKS_CORRECTLY
    {
	Arg args[5];
	XtVaGetValues(ArtSource,
		      XtNstring, &data,
		      NULL);
	XtFree(data);
	data = XtNewString("");
	XtSetArg(args[0], XtNtype, XawAsciiString);
	XtSetArg(args[1], XtNstring, data);
	XtSetArg(args[2], XtNlength, 1);
	XtSetArg(args[3], XtNuseStringInPlace, True);
	XtSetArg(args[4], XtNeditType, XawtextRead);
	XtSetValues(ArtSource, args, XtNumber(args));
    }
#else
    /* clear the article window */
    if (ArtSource != 0) {
	XtVaGetValues(ArtSource,
		      XtNstring, &data,
		      NULL);
	XtFree(data);
	XtDestroyWidget(ArtSource);
	ArtSource = 0;
    }

    XawTextSetSource(ArticleText, DummySource, (XawTextPosition) 0);
#endif /* TEXT_WIDGET_WORKS_CORRECTLY */
#else /* MOTIF */
    XawTextSetMotifString(Text, NULL);
    XawTextSetMotifString(ArticleText, NULL);
#endif
    setBottomInfoLine("");
    if (SubjectString != NIL(char)) {
	FREE(SubjectString);
	SubjectString = NIL(char);
    }
    
    releaseNewsgroupResources();
    if (app_resources.updateNewsrc == TRUE) {
	while (!updatenewsrc())
	    ehErrorRetryXRN("Cannot update the newsrc file", True);
    }

    if (PreviousMode == NEWSGROUP_MODE) {
	switchToNewsgroupMode();
    } else {
	static void switchToAllMode();
	switchToAllMode();
    }
    
    return;
}


#ifndef MOTIF
/* source for the add mode text window */
static Widget AddSource = 0;
#endif


static void
exitAddMode()
/*
 * release storage associated with add mode and go to newsgroup mode
 */
{
#ifndef MOTIF
    if (AddSource != 0) {
	XawStringSourceDestroy(AddSource);
	AddSource = 0;
    }
#else
    TextMotifString = 0;
#endif

    if (AddGroupsString != NIL(char)) {
	FREE(AddGroupsString);
	AddGroupsString = NIL(char);
    }

    switchToNewsgroupMode();
    
    return;
}


static void
redrawAddTextWidget(insertPoint)
XawTextPosition insertPoint;
/*
 * update the add mode text window to correspond to the new set of groups
 */
{
    Arg sargs[5];
    
#ifndef MOTIF
    if (AddSource != 0) {
	XawStringSourceDestroy(AddSource);
    }
#endif
    (void) setCursorCurrent(AddGroupsString, &insertPoint);

#ifndef MOTIF
    XtSetArg(sargs[0], XtNstring, AddGroupsString);
    XtSetArg(sargs[1], XtNlength, utStrlen(AddGroupsString) + 1);
    XtSetArg(sargs[2], XtNeditType, XawtextRead);
    XtSetArg(sargs[3], XtNuseStringInPlace, True);
    XtSetArg(sargs[4], XtNtype, XawAsciiString);
    AddSource = XtCreateWidget("allTextSource",
				   asciiSrcObjectClass,
				   Text, sargs, XtNumber(sargs));
    
    XawTextSetSource(Text, AddSource, (XawTextPosition) insertPoint);
#else
    XawTextSetMotifString(Text, AddGroupsString);
#endif
    XawTextSetInsertionPoint(Text, insertPoint);

    if (XtIsRealized(TopLevel)) {
#ifdef WATCH
	    if(watchingGroup(NewsGroupsString))
#endif
		XSetIconName(XtDisplay(TopLevel), XtWindow(TopLevel),
				app_resources.unreadIconName);
#ifdef WATCH
	    else
		XSetIconName(XtDisplay(TopLevel), XtWindow(TopLevel),
			 app_resources.iconName);
#endif
    }
    
    return;
}


void
foundArticle(file, ques, artNum)
char *file, *ques;
long artNum;
/*
 * Display new article, mark as read.
 */
{
    PrevArticle = CurrentArticle;
    if (SubjectString[ArtPosition] == 'u') {
	markArticleAsUnread(artNum);
	CurrentArticle = artNum;
    } else {
	CurrentArticle = markStringRead(SubjectString, ArtPosition);
    }
    updateSubjectWidget(ArtPosition, ArtPosition);
    XawTextSetInsertionPoint(Text, ArtPosition);

    redrawArticleWidget(file, ques);
    
    return;
}


void
catchUpNG()
/*
 * used when the user has elected to catch
 * up newsgroups in newsgroup mode
 */
{
    char name[GROUP_NAME_SIZE];
    XawTextPosition left, right;
    
    if (getSelection(Text, NewsGroupsString, &left, &right)) {
	GroupPosition = left;
	while (left <= right) {
	    currentGroup(Mode, NewsGroupsString, name, left);
/*	    (void) strcpy(LastGroup, name);*/
	    if (gotoNewsgroup(name) == GOOD_GROUP) {
		catchUp();
	    }
	    if (!moveCursor(FORWARD, NewsGroupsString, &left)) {
		break;
	    }
	}
	updateNewsgroupMode();
	return;
    }
    (void) moveCursor(BACK, NewsGroupsString, &left);
    GroupPosition = left;
    XawTextSetInsertionPoint(Text, GroupPosition);
    
    return;
}


void
unsubscribeNG()
/*
 * Unsubscribe user from selected group(s)
 */
{
    char name[GROUP_NAME_SIZE];
    XawTextPosition left, right;
    
    if (getSelection(Text, NewsGroupsString, &left, &right)) {
	GroupPosition = left;
	while (left <= right) {
	    currentGroup(Mode, NewsGroupsString, name, left);
/*	    (void) strcpy(LastGroup, name);*/
	    if (gotoNewsgroup(name) == GOOD_GROUP) {
		unsubscribe();
	    }
	    if (!moveCursor(FORWARD, NewsGroupsString, &left)) {
		break;
	    }
	}
	updateNewsgroupMode();
	return;
    }
    (void) moveCursor(BACK, NewsGroupsString, &left);
    GroupPosition = left;
    XawTextSetInsertionPoint(Text, GroupPosition);
    
    return;
}


void
catchUpART()
/*
 * Catch up group, and exit article mode
 */
{
    catchUp();
    exitArticleMode();
    return;   
}


void
catchUpPartART()
/*
 * Mark articles not marked as unread between 0 and ArtPosition as read.
 * Get the next unread article and display it, quit
 * if there are no more unread articles.
 */
{
    char *filename, *question;
    long artNum;
    XawTextPosition left = 0;

    while (left < ArtPosition) {
	if (SubjectString[left] != 'u') {
	    SubjectString[left] = '+';
	    markArticleAsRead(atol(&SubjectString[left+2]));
	}
	(void) moveCursor(FORWARD, SubjectString, &left);
    }
    (void) moveCursor(BACK, SubjectString, &left);
    updateSubjectWidget((XawTextPosition) 0, ArtPosition);
    if (getNearbyArticle(art_UNREAD, &filename, &question, &artNum) == 
	art_DONE) {
	/* exitArticleMode(); */
	return;
    }
    foundArticle(filename, question, artNum);
    
    return;
}


void
fedUpART()
{
    static void artQuitFunction(), artNextGroupFunction();

    catchUp();
    if (NewsgroupDisplayMode) {
        artQuitFunction((Widget) 0);
    } else {
        artNextGroupFunction((Widget) 0);
    }
}


void
unsubscribeART()
/*
 * Unsubscribe user from the current group;
 * exit article mode
 */
{
    unsubscribe();
    exitArticleMode();
    
    return;
}


static Widget ExitConfirmBox = (Widget) 0;
static Widget CatchUpConfirmBox = (Widget) 0;
static Widget PartCatchUpConfirmBox = (Widget) 0;
static Widget UnSubConfirmBox = (Widget) 0;
static Widget FedUpConfirmBox = (Widget) 0;

/*ARGSUSED*/
static void
generalHandler(widget, client_data, call_data)
Widget widget;
caddr_t client_data;
caddr_t call_data;
{
    if (inCommand) {
	return;
    }
    inCommand = 1;
    busyCursor();
    switch(Action) {
	case NG_EXIT:
	if ((int) client_data == XRN_YES) {
	    ehNoUpdateExitXRN();
	}
	PopDownDialog(ExitConfirmBox);
	ExitConfirmBox = 0;
	break;

	case NG_QUIT:
	if ((int) client_data == XRN_YES) {
	    ehCleanExitXRN();
	}
	PopDownDialog(ExitConfirmBox);
	ExitConfirmBox = 0;
	break;

	case NG_CATCHUP:
	if ((int) client_data == XRN_YES) {
	    catchUpNG();
	}
	PopDownDialog(CatchUpConfirmBox);
	CatchUpConfirmBox = 0;
    	break;
	    
	case NG_UNSUBSCRIBE:
	if ((int) client_data == XRN_YES) {
	    unsubscribeNG();
	}
	PopDownDialog(UnSubConfirmBox);
	UnSubConfirmBox = 0;
	break;
	    
	case ART_CATCHUP:
	if ((int) client_data == XRN_YES) {
	    catchUpART();
	}	    
	PopDownDialog(CatchUpConfirmBox);
	CatchUpConfirmBox = 0;
	break;
	    
	case ART_PART_CATCHUP:
	if ((int) client_data == XRN_YES) {
	    catchUpPartART();
	}	    
	PopDownDialog(PartCatchUpConfirmBox);
	PartCatchUpConfirmBox = 0;
	break;
	    
	case ART_UNSUBSCRIBE:
	if ((int) client_data == XRN_YES) {
	    unsubscribeART();
	}	    
	PopDownDialog(UnSubConfirmBox);
	UnSubConfirmBox = 0;
	break;
	    
	case ART_FEDUP:
	if ((int) client_data == XRN_YES) {
	    fedUpART();
	}	    
	PopDownDialog(FedUpConfirmBox);
	FedUpConfirmBox = 0;
	break;
    }
    unbusyCursor();
    inCommand = 0;
    return;
}


/*ARGSUSED*/
static void
ngQuitFunction(widget)
Widget widget;
/*
 * called when the user wants to quit xrn
 *
 *  full update the newsrc file
 *  exit
 */
{
    static struct DialogArg args[] = {
        {"no",               generalHandler, (caddr_t) XRN_NO},
	{"yes",              generalHandler, (caddr_t) XRN_YES},
    };

    if (Mode != NEWSGROUP_MODE) {
	return;
    }
    if (app_resources.confirmMode & NG_QUIT) {
	Action = NG_QUIT;
	if (ExitConfirmBox == (Widget) 0) {
	    ExitConfirmBox = CreateDialog(TopLevel, "Are you sure?",
					   DIALOG_NOTEXT, args, XtNumber(args));
	}
	PopUpDialog(ExitConfirmBox);
	return;
    }
    ehCleanExitXRN();
}


/*ARGSUSED*/
static void
ngReadFunction(widget)
Widget widget;
/*
 * called when the user wants to read a new newsgroup
 *
 * get the selected group, set the internal pointers, and go to article mode
 *
 */
{
    char name[GROUP_NAME_SIZE];
    XawTextPosition left, right;
    int status;
      
    if (Mode != NEWSGROUP_MODE) {
	return;
    }

    resetSelection();
    if (getSelection(Text, NewsGroupsString, &left, &right)) {
	currentGroup(Mode, NewsGroupsString, name, left);
	if (NewsgroupEntryMode == XRN_GOTO) {
	    status = gotoNewsgroupForRead(name);
	} else {
	    status = jumpToNewsgroup(name);
	    NewsgroupEntryMode = XRN_GOTO;
	}
	if (status == GOOD_GROUP) {
	    if (getNewsgroup() == XRN_NOMORE) {
		mesgPane(XRN_SERIOUS, "Cannot get the group, it may have been deleted");
		XawTextUnsetSelection(Text);
		return;
	    }
	    (void) strcpy(LastGroup, name);
	    GroupPosition = left;
	    switchToArticleMode();
	}
    } else {
	(void) moveCursor(BACK, NewsGroupsString, &left);
	GroupPosition = left;
	XawTextSetInsertionPoint(Text, GroupPosition);
    }
    XawTextUnsetSelection(Text);
    
    return;
}


/*ARGSUSED*/
static void
ngNextFunction(widget)
Widget widget;
/*
 * called when the user does not want to read a newsgroup
 *
 * if selected group, set internal group
 * call updateNewsgroupMode
 */
{
    XawTextPosition left, right;
    Arg arg[1];

    if (Mode != NEWSGROUP_MODE) {
	return;
    }
    resetSelection();

    (void) getSelection(Text, NewsGroupsString, &left, &right);
    (void) moveUpWrap(NewsGroupsString, &left);
    GroupPosition = left;
    adjustNewsgroupWidget();
#ifndef MOTIF
    XtSetArg(arg[0], XtNdisplayPosition, NewsgroupTop);
    XtSetValues(Text, arg, XtNumber(arg));
#else
    XmListSetPos(Text,
		 XawTextToMotifIndex(TextMotifString, NewsgroupTop));
#endif
    XawTextSetInsertionPoint(Text, GroupPosition);

    return;
}


/*ARGSUSED*/
static void
ngPrevFunction(widget)
Widget widget;
/*
 * called when the user wants to move the cursor up in
 * the newsgroup window
 *
 * if selected group, set internal group
 * call updateNewsgroupMode
 */
{
    XawTextPosition left, right;
    Arg arg[1];

    if (Mode != NEWSGROUP_MODE) {
	return;
    }
    resetSelection();
    
    (void) getSelection(Text, NewsGroupsString, &left, &right);
    (void) moveCursor(BACK, NewsGroupsString, &left);
    GroupPosition = left;
    adjustNewsgroupWidget();
#ifndef MOTIF
    XtSetArg(arg[0], XtNdisplayPosition, NewsgroupTop);
    XtSetValues(Text, arg, XtNumber(arg));
#else
    XmListSetPos(Text,
		 XawTextToMotifIndex(TextMotifString, NewsgroupTop));
#endif
    XawTextSetInsertionPoint(Text, GroupPosition);

    return;
}


/*ARGSUSED*/
static void
ngCatchUpFunction(widget)
Widget widget;
/*
 * called to catch up on all unread articles in this newsgroup
 * use a confirmation box if the user has requested it
 * if selected group, set internal group
 */
{
    static struct DialogArg args[] = {
        {"no",               generalHandler, (caddr_t) XRN_NO},
	{"yes",              generalHandler, (caddr_t) XRN_YES},
    };

    if (Mode != NEWSGROUP_MODE) {
	return;
    }
    resetSelection();
    
    if (app_resources.confirmMode & NG_CATCHUP) {
	Action = NG_CATCHUP;
	if (CatchUpConfirmBox == (Widget) 0) {
	    CatchUpConfirmBox = CreateDialog(TopLevel, "OK to catch up?",
					     DIALOG_NOTEXT, args, XtNumber(args));
	}
	PopUpDialog(CatchUpConfirmBox);

	return;
    }
    catchUpNG();

    return;
}


/*ARGSUSED*/
static void
ngUnsubFunction(widget)
Widget widget;
/*
 * called to unsubscribe to a newsgroup
 *
 * if selected group, set internal group
 * do internals
 * call updateNewsgroupMode
 */
{
    static struct DialogArg args[] = {
        {"no",               generalHandler, (caddr_t) XRN_NO},
	{"yes",              generalHandler, (caddr_t) XRN_YES},
    };    

    if (Mode != NEWSGROUP_MODE) {
	return;
    }
    resetSelection();
    
    if (app_resources.confirmMode & NG_UNSUBSCRIBE) {
	Action = NG_UNSUBSCRIBE;
	if (UnSubConfirmBox == (Widget) 0) {
	    UnSubConfirmBox = CreateDialog(TopLevel, "OK to unsubscribe?",
					   DIALOG_NOTEXT, args, XtNumber(args));
	}
	PopUpDialog(UnSubConfirmBox);
	return;
    }
    unsubscribeNG();

    return;
}


#define XRNsub_ABORT 0
#define XRNsub_LASTGROUP 1
#define XRNsub_FIRST 2
#define XRNsub_LAST 3
#define XRNsub_CURRENT 4

static Widget SubscribeBox = (Widget) 0;

/*ARGSUSED*/
static void
subscribeHandler(widget, client_data, call_data)
Widget widget;
caddr_t client_data;
caddr_t call_data;
{
    int status = SUBSCRIBE;
    char name[GROUP_NAME_SIZE];
    XawTextPosition left, right;

    if (inCommand) {
	return;
    }
    inCommand = 1;
    busyCursor();
    switch ((int) client_data) {
	case XRNsub_LASTGROUP:
	if (LastGroup[0] != '\0') {
	    if (gotoNewsgroup(LastGroup) != GOOD_GROUP) {
		mesgPane(XRN_SERIOUS, "Bad Group");
		PopDownDialog(SubscribeBox);
		SubscribeBox = 0;
		unbusyCursor();
		inCommand = 0;
		return;
	    }
	    subscribe();
	    updateNewsgroupMode();
	}
	break;

	case XRNsub_FIRST:
	if (addToNewsrcBeginning(GetDialogValue(SubscribeBox),
				 status) == GOOD_GROUP) {
	    GroupPosition = 0;
	    updateNewsgroupMode();
	}
	break;

	case XRNsub_LAST:
	if (addToNewsrcEnd(GetDialogValue(SubscribeBox),
			   status) == GOOD_GROUP) {
	    updateNewsgroupMode();
	    endInsertionPoint(NewsGroupsString, &GroupPosition);
	    XawTextSetInsertionPoint(Text, GroupPosition);
	}
	break;

	case XRNsub_CURRENT:
	if (NewsGroupsString[XawTextGetInsertionPoint(Text)] == '\0') {
	    if (addToNewsrcEnd(GetDialogValue(SubscribeBox),
			       status) == GOOD_GROUP) {
		updateNewsgroupMode();
		endInsertionPoint(NewsGroupsString, &GroupPosition);
		XawTextSetInsertionPoint(Text, GroupPosition);
	    }
	} else {

	    /* don't need to check for the null group here, it would have */
	    /* been already handled above */
	    (void) getSelection(Text, NewsGroupsString, &left, &right);
	    GroupPosition = left;
	    if (GroupPosition == 0) {
	        if (addToNewsrcBeginning(GetDialogValue(
		    SubscribeBox),status) == GOOD_GROUP) {
		    updateNewsgroupMode();
		}
	    } else {
		(void) moveCursor(BACK, NewsGroupsString, &GroupPosition);
		currentGroup(Mode, NewsGroupsString, name, GroupPosition);
	        if (addToNewsrcAfterGroup(GetDialogValue(
		    SubscribeBox), name, status) == GOOD_GROUP) {
		    (void) moveUpWrap(NewsGroupsString, &GroupPosition);
		    updateNewsgroupMode();
		}
	    }
    	}
	break;
    }
    
    PopDownDialog(SubscribeBox);
    SubscribeBox = 0;
    XawTextUnsetSelection(Text);
    unbusyCursor();
    inCommand = 0;
    return;
}


/*ARGSUSED*/
static void
ngSubscribeFunction(widget)
Widget widget;
/*
 * Subscribe to a group currently unsubscribed to
 */
{
    static struct DialogArg args[] = {
	{"abort",            subscribeHandler, (caddr_t) XRNsub_ABORT},
	{"last group",       subscribeHandler, (caddr_t) XRNsub_LASTGROUP},
	{"first",            subscribeHandler, (caddr_t) XRNsub_FIRST},
	{"last",             subscribeHandler, (caddr_t) XRNsub_LAST},
	{"cursor position",  subscribeHandler, (caddr_t) XRNsub_CURRENT},
    };

    if (Mode != NEWSGROUP_MODE) {
	return;
    }
    resetSelection();
    
    if (SubscribeBox == (Widget) 0) {
	SubscribeBox = CreateDialog(TopLevel, "Group to subscribe to:",
				    DIALOG_TEXT, args, XtNumber(args));
    }
    PopUpDialog(SubscribeBox);

    return;
}


#define XRNgoto_ABORT 0
#define XRNgoto_GOTO 1

static Widget GotoNewsgroupBox = (Widget) 0;

/*ARGSUSED*/
static void
gotoHandler(widget, client_data, call_data)
Widget widget;
caddr_t client_data;
caddr_t call_data;
{
    char *name;

    if (inCommand) {
	return;
    }
    inCommand = 1;
    busyCursor();
    XawTextUnsetSelection(Text);
    if ((int) client_data == XRNgoto_GOTO) {
	name = GetDialogValue(GotoNewsgroupBox);
	if (name[0] == '\0') {
	    mesgPane(XRN_INFO, "No Newsgroup name specified");
	} else if (jumpToNewsgroup(name) == GOOD_GROUP) {
	    /* jumpToNewsgroup may have found a match for regex name */
	    name = Newsrc[CurrentGroupNumber]->name;
	    (void) strcpy(LastGroup, name);
	    subscribe();
	    switchToArticleMode();
	}
	XtFree(GotoNewsgroupString);
	GotoNewsgroupString = XtNewString(GetDialogValue(GotoNewsgroupBox));
    }
    PopDownDialog(GotoNewsgroupBox);
    GotoNewsgroupBox = 0;
    unbusyCursor();
    inCommand = 0;
    return;
}


/*ARGSUSED*/
static void
ngGotoFunction(widget)
Widget widget;
/*
 * Jump to a newsgroup not displayed in newsgroup mode (either because
 * it's not subscribed to, or because all the articles have been read)
 *
 */
{
    static struct DialogArg args[] = {
	{"abort",           gotoHandler, (caddr_t) XRNgoto_ABORT},
	{"go to newsgroup", gotoHandler, (caddr_t) XRNgoto_GOTO},
    };

    if (Mode != NEWSGROUP_MODE) {
	return;
    }
    resetSelection();
    if (GotoNewsgroupBox == (Widget) 0) {
	GotoNewsgroupBox = CreateDialog(TopLevel, "Group to go to:",
				    GotoNewsgroupString == NULL ?
				    DIALOG_TEXT : GotoNewsgroupString,
				    args, XtNumber(args));
    }
    PopUpDialog(GotoNewsgroupBox);
    return;
}


static void
switchToAllMode()
{  
    Arg sargs[5];
    
    PreviousMode = Mode;
    Mode = ALL_MODE;

    /* switch buttons */
    swapMode();
    
    setBottomInfoLine("View all available groups, with option to subscribe");
    /* create the screen */
    AllGroupsString = getStatusString(AllStatus);

#ifndef MOTIF
    XtSetArg(sargs[0], XtNstring, AllGroupsString);
    XtSetArg(sargs[1], XtNlength, utStrlen(AllGroupsString) + 1);
    XtSetArg(sargs[2], XtNeditType, XawtextRead);
    XtSetArg(sargs[3], XtNuseStringInPlace, True);
    XtSetArg(sargs[4], XtNtype, XawAsciiString);
    AllSource = XtCreateWidget("allTextSource",
				   asciiSrcObjectClass,
				   ArticleText, sargs, XtNumber(sargs));
    XawTextSetSource(ArticleText, AllSource, (XawTextPosition) 0);
    XtSetValues(ArticleText, lineSelArgs, XtNumber(lineSelArgs));
#else
    XawTextSetMotifString(ArticleText, AllGroupsString);
#endif
    
    return;
}


/*ARGSUSED*/
static void
ngListOldFunction(widget)
Widget widget;
{
    if (Mode != NEWSGROUP_MODE) {
	return;
    }
    NewsgroupDisplayMode = (NewsgroupDisplayMode == 0) ? 1 : 0;
    redrawNewsgroupTextWidget();
    return;
}


/*ARGSUSED*/
static void
ngAllGroupsFunction(widget)
Widget widget;
/*
 * Enter "all" mode.  Display all available groups to allow user to
 * subscribe/unsubscribe to them.
 */
{
    
    if (Mode != NEWSGROUP_MODE) {
	return;
    }
    resetSelection();
    switchToAllMode();
}


/*ARGSUSED*/
static void
ngRescanFunction(widget)
Widget widget;
/*
 * query the server to see if there are any new articles and groups
 */
{
    if (Mode != NEWSGROUP_MODE) {
	return;
    }
    resetSelection();
    rescanServer();
    determineMode();
    
    return;
}


/*ARGSUSED*/
void autoRescan(data, id)
caddr_t data;
XtIntervalId *id;
{
    if (Mode != NEWSGROUP_MODE) {
	TimeOut = 0;
	return;
    }
    if (TimeOut != *id) {
	fprintf(stderr, "bad time out, id is %d, expected %d\n", *id, TimeOut);
	TimeOut = 0;
	return;
    }
    TimeOut = 0;
    busyCursor();
    infoNow("automatic rescan in progress...");
    ngRescanFunction((Widget) 0);
    infoNow("");
    unbusyCursor();
    addTimeOut();

    return;
}


/*ARGSUSED*/
static void
ngPrevGroupFunction(widget)
Widget widget;
/*
 * put the user in the previous newsgroup accessed
 */
{
    if (Mode != NEWSGROUP_MODE) {
	return;
    }
    resetSelection();
    if (LastGroup[0] != '\0') {
	if (jumpToNewsgroup(LastGroup) == GOOD_GROUP) {
	    switchToArticleMode();
	}
    }

    return;
}


/*ARGSUSED*/
static void
ngSelectFunction(widget)
Widget widget;
/* 
 * save the user's selection of groups to be moved with the move
 * command
 */
{
    if (Mode != NEWSGROUP_MODE) {
	return;
    }
    (void) getSelection(Text, NewsGroupsString, &First, &Last);
    
    return;
}


/*ARGSUSED*/
static void
ngMoveFunction(widget)
Widget widget;
/*
 * Move the previously selected groups to the position before the
 * current selection
 */
{
    char newGroup[GROUP_NAME_SIZE];
    char oldGroup[GROUP_NAME_SIZE];
    int status = SUBSCRIBE;
    char *newString;
    XawTextPosition left, right;
    XawTextPosition stringPoint;
    XawTextPosition cursorSpot;
    int direction = 0;
    int numGroups = 0;

    if (Mode != NEWSGROUP_MODE) {
	return;
    }
    if (First == Last) {
	mesgPane(XRN_INFO, "No groups were selected");
	return;	
    }
    buildString(&newString, First, Last, NewsGroupsString);
    stringPoint = 0;
    (void) getSelection(Text, NewsGroupsString, &left, &right);
    if ((left >= First) && (left <= Last+1)) {
	mesgPane(XRN_SERIOUS, "Selected groups have not been moved");
	resetSelection();
	return;
    }
    GroupPosition = cursorSpot= left;
    if (left > First) {
	direction = 1;
    }
    currentGroup(Mode, newString, newGroup, stringPoint);
    if (!moveCursor(BACK, NewsGroupsString, &left)) {
	(void) addToNewsrcBeginning(newGroup, status);
	(void) strcpy(oldGroup, newGroup);
	(void) moveCursor(FORWARD, newString, &stringPoint);
	numGroups++;
    } else {
	currentGroup(Mode, NewsGroupsString, oldGroup, left);
	(void) addToNewsrcAfterGroup(newGroup, oldGroup, status);
	(void) strcpy(oldGroup, newGroup);
	(void) moveCursor(FORWARD, newString, &stringPoint);
	numGroups++;
    }
    while (newString[stringPoint] != '\0') {
	numGroups++;
	currentGroup(Mode, newString, newGroup, stringPoint);
	(void) addToNewsrcAfterGroup(newGroup, oldGroup, status);
	(void) strcpy(oldGroup, newGroup);
	if (!moveCursor(FORWARD, newString, &stringPoint)) {
	    break;
	}
    }
    redrawNewsgroupTextWidget();
    if (direction) {
	GroupPosition = cursorSpot;
	while (numGroups > 0) {
	    (void) moveCursor(BACK, NewsGroupsString, &GroupPosition);
	    numGroups--;
	}
	adjustNewsgroupWidget();
    }
    XawTextSetInsertionPoint(Text, GroupPosition);
    resetSelection();
    
    return;
}


/*ARGSUSED*/
static void
ngExitFunction(widget)
Widget widget;
/*
 * Quit xrn, leaving the newsrc in the state it was in at
 * the last invokation of rescan.
 */
{
    static struct DialogArg args[] = {
        {"no",               generalHandler, (caddr_t) XRN_NO},
	{"yes",              generalHandler, (caddr_t) XRN_YES},
    };

    if (Mode != NEWSGROUP_MODE) {
	return;
    }

    if (app_resources.confirmMode & NG_EXIT) {
	Action = NG_EXIT;
	if (ExitConfirmBox == (Widget) 0) {
	    ExitConfirmBox = CreateDialog(TopLevel, "Are you sure?",
					   DIALOG_NOTEXT, args, XtNumber(args));
	}
	PopUpDialog(ExitConfirmBox);
	return;
    }

    ehNoUpdateExitXRN();
}


/*ARGSUSED*/
static void
ngCheckPointFunction(widget)
Widget widget;
/*
 * update the .newsrc file
 */
{
    if (Mode != NEWSGROUP_MODE) {
	return;
    }

    while (!updatenewsrc())
	ehErrorRetryXRN("Cannot update the newsrc file", True);

    return;
}


/*ARGSUSED*/
static void
ngGripeFunction(widget)
Widget widget;
/*
 * Allow user to gripe
 */
{
    if (Mode != NEWSGROUP_MODE) {
	return;
    }
    gripe();
    return;
}


/*ARGSUSED*/
static void
ngPostFunction(widget)
Widget widget;
/*
 * allow user to post an article
 */
{
    if (Mode != NEWSGROUP_MODE) {
	return;
    }
    post(0);
    
    return;
}


/*ARGSUSED*/
static void
artQuitFunction(widget)
Widget widget;
/*
 * called when the user wants to quit the current newsgroup and go to
 * the next one
 */
{
    if (Mode != ARTICLE_MODE) {
	return;
    }
    exitArticleMode();
    
    return;
}

/*ARGSUSED*/
static void
artNextFunction(widget)
Widget widget;
/*
 * called when the user wants to read the next article
 */
{
    char *filename;		/* name of the article file */
    char *question;		/* question to put in the question box */
    long artNum;

    if (Mode != ARTICLE_MODE) {
	return;
    }
    selectedArticle(ArtStatus);
    if (getNearbyArticle(ArtStatus, &filename, &question,
			 &artNum) == art_DONE) {
	exitArticleMode();
	return;
    }
    /* update the text window */
    foundArticle(filename, question, artNum);

    ArtStatus = art_NEXT;
    ArtEntry = 0;
    
    return;
}


/*ARGSUSED*/
static void
artScrollFunction(widget)
Widget widget;
/*
 * called when the user wants to scroll the current article
 */
{
    if (Mode != ARTICLE_MODE) {
	return;
    }
    XtCallActionProc(ArticleText, "next-page", 0, 0, 0);
    return;
}


/*ARGSUSED*/
static void
artScrollBackFunction(widget)
Widget widget;
/*
 * called when the user wants to scroll the current article
 */
{
    if (Mode != ARTICLE_MODE) {
	return;
    }
    XtCallActionProc(ArticleText, "previous-page", 0, 0, 0);
    return;
}


/*ARGSUSED*/
static void
artScrollLineFunction(widget)
Widget widget;
/*
 * called when the user wants to scroll the current article
 */
{
    if (Mode != ARTICLE_MODE) {
	return;
    }
    XtCallActionProc(ArticleText, "scroll-one-line-up", 0, 0, 0);
    return;
}


/*ARGSUSED*/
static void
artScrollBackLineFunction(widget)
Widget widget;
/*
 * called when the user wants to scroll the current article
 */
{
    if (Mode != ARTICLE_MODE) {
	return;
    }
    XtCallActionProc(ArticleText, "scroll-one-line-down", 0, 0, 0);
    return;
}


/*ARGSUSED*/
static void
artScrollEndFunction(widget)
Widget widget;
/*
 * called when the user wants to scroll the current article
 */
{
    if (Mode != ARTICLE_MODE) {
	return;
    }
#ifndef MOTIF
    /* Workaround for TextWidget bug: Only scrolls if insertion point moved */
    XawTextSetInsertionPoint(ArticleText, XawTextTopPosition(ArticleText));
    XtCallActionProc(ArticleText, "end-of-file", 0, 0, 0);
#else
    {
      char *data;

      data = XmTextGetString(ArticleText);
      XmTextShowPosition(ArticleText, strlen(data)-1);
      XtCallActionProc(ArticleText, "scroll-one-line-up", 0, 0, 0);
      XtFree(data);
    }
#endif
    return;
}


/*ARGSUSED*/
static void
artScrollBeginningFunction(widget)
Widget widget;
/*
 * called when the user wants to scroll the current article
 */
{
    if (Mode != ARTICLE_MODE) {
	return;
    }
#ifndef MOTIF
    /* Workaround for TextWidget bug: Only scrolls if insertion point moved */
    XawTextSetInsertionPoint(ArticleText, XawTextTopPosition(ArticleText));
    XtCallActionProc(ArticleText, "beginning-of-file", 0, 0, 0);
#else
    XmTextShowPosition(ArticleText, 0);
#endif
    return;
}


static void
artNextUnreadFunction(widget)
Widget widget;
/*
 * called when the user wants to go to the next unread news
 * article in the current newsgroup
 * 
 */
{
    static void artNextFunction();
    
    if (Mode != ARTICLE_MODE) {
	return;
    }
    ArtStatus = art_UNREAD;
    artNextFunction(widget);
    
    return;
}


/*ARGSUSED*/
static void
artPrevFunction(widget)
Widget widget;
/*
 * called when the user wants to read the previous article
 */
{
    XawTextPosition left, right;
    long artNum;
    char *filename, *question;

    if (Mode != ARTICLE_MODE) {
	return;
    }
    ArtStatus = art_PREV;
    XawTextGetSelectionPos(Text, &left, &right);
    XawTextUnsetSelection(Text);
    ArtPosition = left;
    if (left == right) {
	ArtPosition = XawTextGetInsertionPoint(Text);
	if (ArtPosition == 0) {
	    if (getPrevious(&artNum)) {
		gotoArticle(artNum);
		(void) getArticle(&filename, &question);
		foundArticle(filename, question, artNum);
		ArtStatus = art_NEXT;
		return;
	    }
	    return;
	}
	(void) moveCursor(BACK, SubjectString, &ArtPosition);
    } else {
	moveBeginning(SubjectString, &ArtPosition);
    }
    if (getNearbyArticle(ArtStatus, &filename, &question, &artNum) == art_DONE) {
	exitArticleMode();
	return;
    }
    foundArticle(filename, question, artNum);
    ArtStatus = art_NEXT;
    
    return;
}


/*ARGSUSED*/
static void
artNextGroupFunction(widget)
Widget widget;
{
    static void artQuitFunction();
    char name[GROUP_NAME_SIZE];
    int append = 0;
    
    LastArticle = NIL(char);
    PrevArticle = CurrentArticle = 0;
    
    while (1) {
	 /* 
	  * XXX if the newsgroup is fully read, then when the new newsgroup
	  * string is regenerated (upon entry to newsgroup mode), the string
	  * for this group will not be there and thus the Group Position will
	  * be too far forward (by one group)
	  */

	if (NewsGroupsString) {
	    FREE(NewsGroupsString);
	    NewsGroupsString = NIL(char);
	}

        NewsGroupsString = unreadGroups(NewsgroupDisplayMode);
	currentGroup(Mode, NewsGroupsString, name, GroupPosition);
        if (STREQ(name, LastGroup)) {
	    /* last group not fully read */
	    if (moveCursor(FORWARD, NewsGroupsString, &GroupPosition) == FALSE) {
		 artQuitFunction(widget);
		 return;
	    }
	    currentGroup(NEWSGROUP_MODE, NewsGroupsString, name, GroupPosition);
        }

#ifdef notdef
	 if (moveCursor(FORWARD, NewsGroupsString, &GroupPosition) == FALSE) {
	      artQuitFunction(widget);
	      return;
	 }
	 currentGroup(NEWSGROUP_MODE, NewsGroupsString, name, GroupPosition);
	 moveBeginning(NewsGroupsString, &GroupPosition);
#endif

	 if ((name == NIL(char)) || (*name == '\0')) {
	      artQuitFunction(widget);
	      return;
	 }

	 if (gotoNewsgroup(name) != GOOD_GROUP) {
	      mesgPane(XRN_SERIOUS | append,
		       "Bad group `%s', skipping to next group", name);
	      append = XRN_APPEND;
	      continue;
	 }
	 
	 if (getNewsgroup() == XRN_NOMORE) {
	      mesgPane(XRN_INFO | append,
		       "No unread articles in `%s', skipping to next group",
		       name);
	      append = XRN_APPEND;
	      continue;
	 }

	 if (switchToArticleMode() == GOOD_GROUP) {
	      setNewsgroup(LastGroup);
	      releaseNewsgroupResources();
	      setNewsgroup(name);
	      (void) strcpy(LastGroup, name);
	      if (app_resources.updateNewsrc == TRUE) {
		   while (!updatenewsrc())
		       ehErrorRetryXRN("Cannot update the newsrc file",
				       True);
	      }
	      return;
	 }
	 /*
	  * Normally, I'd put a call to mesgPane in here to tell the
	  * user that the switchToArticleMode failed, but it isn't
	  * necessary because switchToArticleMode calls bogusNewsgroup
	  * if it fails, and bogusNewsgroup calls mesgPane with an
	  * appropriate message.
	  */
    }
}


/*ARGSUSED*/
static void
artFedUpFunction(widget)
Widget widget;
{
    static struct DialogArg args[] = {
        {"no",               generalHandler, (caddr_t) XRN_NO},
	{"yes",              generalHandler, (caddr_t) XRN_YES},
    };

    if (app_resources.confirmMode & ART_FEDUP) {
	Action = ART_FEDUP;
	if (FedUpConfirmBox == (Widget) 0) {
	    FedUpConfirmBox = CreateDialog(TopLevel, "Are you sure?",
					   DIALOG_NOTEXT, args, XtNumber(args));
	}
	PopUpDialog(FedUpConfirmBox);
	return;
    }

    fedUpART();
    return;
}


/*ARGSUSED*/
static void
artCatchUpFunction(widget)
Widget widget;
/*
 * called when the user wants to mark all articles in the current group as read
 */
{
    XawTextPosition left, right;

    static struct DialogArg args[] = {
        {"no",               generalHandler, (caddr_t) XRN_NO},
	{"yes",              generalHandler, (caddr_t) XRN_YES},
    };

    if (Mode != ARTICLE_MODE) {
	return;
    }
    XawTextGetSelectionPos(Text, &left, &right);
    if (left != right) {
	(void) getSelection(Text, SubjectString, &left, &right);
    }
    ArtPosition = right;
    if (left == right) {
	if (app_resources.confirmMode & ART_CATCHUP) {
	    Action = ART_CATCHUP;
	    if (CatchUpConfirmBox == (Widget) 0) {
		CatchUpConfirmBox = CreateDialog(TopLevel, "OK to catch up?",
						 DIALOG_NOTEXT, args, XtNumber(args));
	    }
	    PopUpDialog(CatchUpConfirmBox);

	    return;
	}
	catchUpART();
	return;
    }
    if (moveCursor(FORWARD, SubjectString, &ArtPosition)) {
	if (app_resources.confirmMode & ART_CATCHUP) {
	    Action = ART_PART_CATCHUP;
	    if (PartCatchUpConfirmBox == (Widget) 0) {
		PartCatchUpConfirmBox = CreateDialog(TopLevel,
						     "OK to catch up to current position?",
						     DIALOG_NOTEXT, args, XtNumber(args));
	    }
	    PopUpDialog(PartCatchUpConfirmBox);
	} else {
	    catchUpPartART();
	}
    }
    return;
}


/*ARGSUSED*/
static void
artUnsubFunction(widget)
Widget widget;
/*
 * called when the user wants to unsubscribe to the current group
 */
{
    static struct DialogArg args[] = {
        {"no",               generalHandler, (caddr_t) XRN_NO},
	{"yes",              generalHandler, (caddr_t) XRN_YES},
    };

    if (Mode != ARTICLE_MODE) {
	return;
    }
    if (app_resources.confirmMode & ART_UNSUBSCRIBE) {
	Action = ART_UNSUBSCRIBE;
	if (UnSubConfirmBox == (Widget) 0) {
	    UnSubConfirmBox = CreateDialog(TopLevel, "OK to unsubscribe?",
					   DIALOG_NOTEXT, args, XtNumber(args));
	}
	PopUpDialog(UnSubConfirmBox);
	return;
    }
    unsubscribeART();
    return;
}


static void
markFunction(marker)
char marker;
/*
 * Get selection region, mark articles, redisplay subject window.
 */
{
    XawTextPosition left, right;
    
    (void) getSelection(Text, SubjectString, &left, &right);
    markArticles(SubjectString, left, right, marker);
    updateSubjectWidget(left, right);
    
    return;
}


/*ARGSUSED*/
static void
artMarkReadFunction(widget)
Widget widget;
/*
 * Mark selected article(s) as read
 */
{
    char marker = '+';
    XawTextPosition save;

    if (Mode != ARTICLE_MODE) {
	return;
    }
    save = ArtPosition;
    markFunction(marker);
    ArtPosition = save;
    return;
}


/*ARGSUSED*/
static void
artMarkUnreadFunction(widget)
Widget widget;
/*
 * Mark selected article(s) as unread
 */
{
    char marker = 'u';
    XawTextPosition save;

    if (Mode != ARTICLE_MODE) {
	return;
    }
    save = ArtPosition;
    markFunction(marker);
    ArtPosition = save;
    return;
}


/*ARGSUSED*/
static void
artPostFunction(widget)
Widget widget;
/*
 * allow user to post to the newsgroup currently being read
 */
{
    if (Mode != ARTICLE_MODE) {
	return;
    }
    post(1);
    
    return;
}


/*ARGSUSED*/
static void
artSubNextFunction(widget)
Widget widget;
/*
 *
 */
{
    XawTextPosition left, right;
    char *filename, *question;
    char *subject;
    long artNum;
    int status;
    
    if (Mode != ARTICLE_MODE) {
	return;
    }
    XawTextGetSelectionPos(Text, &left, &right);
    XawTextUnsetSelection(Text);
    ArtPosition = left;
    if (left == right) {
	ArtPosition = XawTextGetInsertionPoint(Text);
	if (SubjectString[ArtPosition] == '\0') {
	    return;
	}
	artNum = atol(&SubjectString[ArtPosition+2]);
	subject = XtNewString(getSubject(artNum));
	(void) moveCursor(FORWARD, SubjectString, &ArtPosition);
	status = isNextSubject(subject, &filename, &question, &artNum);
	switch (status) {
	  case ABORT:
	    FREE(subject);
	    infoNow("search has been aborted");
	    return;

	  case NOCHANGE:
	    (void) sprintf(error_buffer, "Subject search: %s", subject);
	    info(error_buffer);
	    FREE(subject);
	    foundArticle(filename, question, artNum);
	    return;

	  case DONE:
	    FREE(subject);
	    ArtPosition = 0;
	    infoNow("Subject has been exhausted, returning to first unread article");
	    if (getNearbyArticle(art_UNREAD,&filename,&question,&artNum) == art_DONE) {
		exitArticleMode();
		return;
	    }
	    foundArticle(filename, question, artNum);
	    return;
	  case EXIT:
	    FREE(subject);
	    exitArticleMode();
	    return;
	}
    }
    if (getNearbyArticle(art_NEXT, &filename, &question, &artNum) == art_DONE) {
	exitArticleMode();
	return;
    }
    foundArticle(filename, question, artNum);
    
    return;
}
	

/*ARGSUSED*/
static void
artSubPrevFunction(widget)
Widget widget;
/*
 *
 */
{
    XawTextPosition left, right;
    char *subject;
    long artNum;
    char *filename, *question;
    int status;
    Arg sargs[5];
    
    if (Mode != ARTICLE_MODE) {
	return;
    }
    XawTextGetSelectionPos(Text, &left, &right);
    XawTextUnsetSelection(Text);
    ArtPosition = left;
    if (left == right) {
	/* nothing selected, we should be on a valid article */
	ArtPosition = XawTextGetInsertionPoint(Text);
	if (SubjectString[ArtPosition] == '\0') {
	    return;
	}
	artNum = atol(&SubjectString[ArtPosition+2]);
	subject = XtNewString(getSubject(artNum));
	(void) sprintf(error_buffer, "Subject search: %s", subject);
	status = isPrevSubject(subject, &filename, &question, &artNum);
	FREE(subject);
	switch(status) {
	  case ABORT:
	    infoNow("search has been aborted");
	    return;
	  case NOCHANGE:
	    info(error_buffer);
	    foundArticle(filename, question, artNum);
	    return;
	  case CHANGE:
#ifndef MOTIF
	    if (SubjectSource != 0) {
#else
	    if (TextMotifString != 0) {
#endif
		ArticleTop = XawTextTopPosition(Text);
#ifndef MOTIF
		XawStringSourceDestroy(SubjectSource);
#endif
	    }

#ifndef MOTIF
	    XtSetArg(sargs[0], XtNstring, SubjectString);
	    XtSetArg(sargs[1], XtNlength, utStrlen(SubjectString) + 1);
	    XtSetArg(sargs[2], XtNeditType, XawtextRead);
	    XtSetArg(sargs[3], XtNuseStringInPlace, True);
	    XtSetArg(sargs[4], XtNtype, XawAsciiString);
	    SubjectSource = XtCreateWidget("subjectTextSource",
				       asciiSrcObjectClass,
				       Text, sargs, XtNumber(sargs));
	    XawTextSetSource(Text, SubjectSource, ArticleTop);
#else
	    XawTextSetMotifString(Text, SubjectString);
#endif
	    info(error_buffer);
	    foundArticle(filename, question, artNum);
	    return;
	  case DONE:
	    infoNow("Subject has been exhausted");
	    return;
	  case EXIT:
	    exitArticleMode();
	    return;
	}
    }
    moveBeginning(SubjectString, &left);
    ArtPosition = left;
    artNum = atol(&SubjectString[ArtPosition+2]);
    gotoArticle(artNum);
    if (getArticle(&filename, &question) != XRN_OKAY) {
#ifndef MOTIF
	removeLine(SubjectString, Text, &SubjectSource, ArtPosition,
		   &ArticleTop);
#else
	removeLine(SubjectString, Text, &TextMotifString, ArtPosition,
		   &ArticleTop);
#endif
	if (getNearbyArticle(art_NEXT, &filename, &question, &artNum) == art_DONE) {
	    exitArticleMode();
	    return;
	}
	infoNow(error_buffer);
	foundArticle(filename, question, artNum);
	return;
    }
    infoNow(error_buffer);
    foundArticle(filename, question, artNum);
    
    return;
}


char *SubjectKilled;

/*ARGSUSED*/
static void
_artKillSession(widget)
Widget widget;
/*
 * Allow user to mark all articles with the current subject as read
 *
 * XXX get subject, kill using data structures, rebuild SubjectString
 */
{
    XawTextPosition left, right, save;
    char *subject;
    char *cursubject;
    char *filename, *question;
    long artNum;
    Arg sargs[5];
    
    if (Mode != ARTICLE_MODE) {
	return;
    }
    if (!getSelection(Text, SubjectString, &left, &right)) {
	return;
    }
    ArtPosition = left;
    save = ArtPosition;
#ifdef ellen
    XawTextGetSelectionPos(Text, &left, &right);
    XawTextUnsetSelection(Text);
    if (left == right) {
	ArtPosition = XawTextGetInsertionPoint(Text);
	if (SubjectString[ArtPosition] == '\0') {
	    return;
	}
	left = ArtPosition;
    } else {
	ArtPosition = left;
    }
    moveBeginning(SubjectString, &ArtPosition);
#endif
    artNum = atol(&SubjectString[ArtPosition+2]);
    subject = XtNewString(getSubject(artNum));
    SubjectKilled = XtNewString(subject);
    ArtPosition = 0;
    while (SubjectString[ArtPosition] != '\0') {
	artNum = atol(&SubjectString[ArtPosition+2]);
	cursubject = getSubject(artNum);
	/* only kill those that have not been marked as unread */
	if ((STREQ(subject, cursubject)) &&
	    (SubjectString[ArtPosition] != 'u')) {
	    markArticleAsRead(artNum);
	    (void) markStringRead(SubjectString, ArtPosition);
	}
	if (!moveCursor(FORWARD, SubjectString, &ArtPosition)) {
	    break;
	}
    }
    /* set the cursor back to the beginning of the subject screen */
    ArtPosition = save;
    FREE(subject);
    infoNow("Subject has been killed, returning to first unread article");
    if (getNearbyArticle(art_UNREAD, &filename, &question, &artNum)
	== art_DONE) {
	exitArticleMode();
	return;
    }
#ifndef MOTIF
    if (SubjectSource != 0) {
#else
    if (TextMotifString != 0) {
#endif
	ArticleTop = XawTextTopPosition(Text);
#ifndef MOTIF
	XawStringSourceDestroy(SubjectSource);
#endif
    }

#ifndef MOTIF
    XtSetArg(sargs[0], XtNstring, SubjectString);
    XtSetArg(sargs[1], XtNlength, utStrlen(SubjectString) + 1);
    XtSetArg(sargs[2], XtNeditType, XawtextRead);
    XtSetArg(sargs[3], XtNuseStringInPlace, True);
    XtSetArg(sargs[4], XtNtype, XawAsciiString);
    SubjectSource = XtCreateWidget("subjectTextSource",
				   asciiSrcObjectClass,
				   Text, sargs, XtNumber(sargs));
    XawTextSetSource(Text, SubjectSource, ArticleTop);
#else
    XawTextSetMotifString(Text, SubjectString);
#endif

    foundArticle(filename, question, artNum);
    
    return;
}

    
/*ARGSUSED*/
static void
artKillSessionFunction(widget)
Widget widget;
{
     _artKillSession(widget);
     FREE(SubjectKilled);
     return;
}


/*ARGSUSED*/
static void
artKillAuthorFunction(widget)
Widget widget;
/*
 * Allow user to mark all articles with the current author as read
 *
 * XXX get author, kill using data structures, rebuild SubjectString
 * XXX merge this with artKillSession
 */
{
    XawTextPosition left, right;
    char *author;
    char *curauthor;
    char *filename, *question;
    long artNum;
    Arg sargs[5];
    
    if (Mode != ARTICLE_MODE) {
	return;
    }
    if (!getSelection(Text, SubjectString, &left, &right)) {
	return;
    }
    ArtPosition = left;
#ifdef ellen
    XawTextGetSelectionPos(Text, &left, &right);
    XawTextUnsetSelection(Text);
    if (left == right) {
	ArtPosition = XawTextGetInsertionPoint(Text);
	if (SubjectString[ArtPosition] == '\0') {
	    return;
	}
	left = ArtPosition;
    } else {
	ArtPosition = left;
    }
    moveBeginning(SubjectString, &ArtPosition);
#endif
    artNum = atol(&SubjectString[ArtPosition+2]);
    author = XtNewString(getAuthor(artNum));
    ArtPosition = 0;
    while (SubjectString[ArtPosition] != '\0') {
	artNum = atol(&SubjectString[ArtPosition+2]);
	curauthor = getAuthor(artNum);
	/* only kill those that have not been marked as unread */
	if ((STREQ(author, curauthor)) &&
	    (SubjectString[ArtPosition] != 'u')) {
	    markArticleAsRead(artNum);
	    (void) markStringRead(SubjectString, ArtPosition);
	}
	if (!moveCursor(FORWARD, SubjectString, &ArtPosition)) {
	    break;
	}
    }
    /* set the cursor back to the beginning of the subject screen */
    ArtPosition = 0;
    FREE(author);
    infoNow("Author has been killed, returning to first unread article");
    if (getNearbyArticle(art_UNREAD, &filename, &question, &artNum)
	== art_DONE) {
	exitArticleMode();
	return;
    }
#ifndef MOTIF
    if (SubjectSource != 0) {
#else
    if (TextMotifString != 0) {
#endif
	ArticleTop = XawTextTopPosition(Text);
#ifndef MOTIF
	XawStringSourceDestroy(SubjectSource);
#endif
    }

#ifndef MOTIF
    XtSetArg(sargs[0], XtNstring, SubjectString);
    XtSetArg(sargs[1], XtNlength, utStrlen(SubjectString) + 1);
    XtSetArg(sargs[2], XtNeditType, XawtextRead);
    XtSetArg(sargs[3], XtNuseStringInPlace, True);
    XtSetArg(sargs[4], XtNtype, XawAsciiString);
    SubjectSource = XtCreateWidget("subjectTextSource",
				   asciiSrcObjectClass,
				   Text, sargs, XtNumber(sargs));
    XawTextSetSource(Text, SubjectSource, ArticleTop);
#else
    XawTextSetMotifString(Text, SubjectString);
#endif

    foundArticle(filename, question, artNum);
    
    return;
}


/*ARGSUSED*/
static void
artKillLocalFunction(widget)
Widget widget;
{
    if (Mode != ARTICLE_MODE) {
	return;
    }
    _artKillSession(widget);
    killItem(SubjectKilled, KILL_LOCAL);
    FREE(SubjectKilled);
    return;
}


/*ARGSUSED*/
static void
artKillGlobalFunction(widget)
Widget widget;
{
    if (Mode != ARTICLE_MODE) {
	return;
    }
    _artKillSession(widget);
    killItem(SubjectKilled, KILL_GLOBAL);
    FREE(SubjectKilled);
    return;
}


#define XRNgotoArticle_ABORT	0
#define XRNgotoArticle_DOIT	1

static Widget GotoArticleBox = (Widget) 0;

/*ARGSUSED*/
static void
artListOldFunction(widget)
Widget widget;
{
    char *filename, *question;
    int SavePosition;
    int status;
    long artNum;
    Arg sargs[5];
    struct newsgroup *newsgroup = Newsrc[CurrentGroupNumber];
    struct article *articles = GETARTICLES(newsgroup);

    busyCursor();
    XawTextUnsetSelection(Text);
    SavePosition = ArtPosition;

    artNum = newsgroup->first;
    while (artNum<=newsgroup->last && IS_UNAVAIL(articles[INDEX(artNum)]))
        {artNum++;}
    
    status = moveToArticle(artNum, &filename, &question);

    switch (status) {

      case NOMATCH:
	mesgPane(XRN_SERIOUS,
	    "First article (number %d) not available", artNum);

      case ERROR:
	ArtPosition = SavePosition;
	break;

      case MATCH:
#ifndef MOTIF
        if (SubjectSource != 0) {
	    XawStringSourceDestroy(SubjectSource);
	}
#endif

	if (SubjectString != NIL(char)) {
	    FREE(SubjectString);
	}

	SubjectString = getSubjects(ALL);

#ifndef MOTIF
	XtSetArg(sargs[0], XtNstring, SubjectString);
	XtSetArg(sargs[1], XtNlength, utStrlen(SubjectString) + 1);
	XtSetArg(sargs[2], XtNeditType, XawtextRead);
	XtSetArg(sargs[3], XtNuseStringInPlace, True);
	XtSetArg(sargs[4], XtNtype, XawAsciiString);
	SubjectSource = XtCreateWidget("subjectTextSource",
				       asciiSrcObjectClass,
				       Text, sargs, XtNumber(sargs));
	XawTextSetSource(Text, SubjectSource, (XawTextPosition) 0);
#else
        XawTextSetMotifString(Text, SubjectString);
#endif

	ArtPosition = 0;
	foundArticle(filename, question, artNum);
	break;
    }
    unbusyCursor();
    return;
}


/*ARGSUSED*/
static void
artCheckPointFunction(widget)
Widget widget;
/*
 * update the .newsrc file
 */
{
    if (Mode != ARTICLE_MODE) {
	return;
    }

    while (!updatenewsrc())
	ehErrorRetryXRN("Cannot update the newsrc file", True);

    return;
}


/*ARGSUSED*/
static void 
gotoArticleHandler(widget, client_data, call_data)
Widget widget;
caddr_t client_data;
caddr_t call_data;
{
    char *numberstr;
    char *filename, *question;
    int SavePosition;
    int status;
    long artNum;
    Arg sargs[5];

    if (inCommand) {
	return;
    }
    inCommand = 1;
    busyCursor();
    XawTextUnsetSelection(Text);
    if ((int) client_data == XRNgotoArticle_ABORT) {
	PopDownDialog(GotoArticleBox);
	GotoArticleBox = 0;
	unbusyCursor();
	inCommand = 0;
	return;
    }
    SavePosition = ArtPosition;
    numberstr = GetDialogValue(GotoArticleBox);
    if (numberstr == NIL(char)) {
	mesgPane(XRN_INFO, "Bad article number");
	PopDownDialog(GotoArticleBox);
	GotoArticleBox = 0;
	unbusyCursor();
	inCommand = 0;
	return;
    }

    artNum = atol(numberstr);
    if (artNum == 0) {
	mesgPane(XRN_SERIOUS, "Bad article number");
	PopDownDialog(GotoArticleBox);
	GotoArticleBox = 0;
	unbusyCursor();
	inCommand = 0;
	return;
    }
    
    status = moveToArticle(artNum, &filename, &question);

    switch (status) {

      case NOMATCH:
	mesgPane(XRN_SERIOUS, "Article number %d not available", artNum);

      case ERROR:
	ArtPosition = SavePosition;
	break;

      case MATCH:
#ifndef MOTIF
	if (SubjectSource != 0) {
	    XawStringSourceDestroy(SubjectSource);
	}
#endif

	if (SubjectString != NIL(char)) {
	    FREE(SubjectString);
	}

	SubjectString = getSubjects(ALL);

#ifndef MOTIF
	XtSetArg(sargs[0], XtNstring, SubjectString);
	XtSetArg(sargs[1], XtNlength, utStrlen(SubjectString) + 1);
	XtSetArg(sargs[2], XtNeditType, XawtextRead);
	XtSetArg(sargs[3], XtNuseStringInPlace, True);
	XtSetArg(sargs[4], XtNtype, XawAsciiString);
	/* XXX not right...  only right if going back... */
	SubjectSource = XtCreateWidget("subjectTextSource",
				       asciiSrcObjectClass,
				       Text, sargs, XtNumber(sargs));
	XawTextSetSource(Text, SubjectSource, (XawTextPosition) 0);
#else
	XawTextSetMotifString(Text, SubjectString);
#endif

	ArtPosition = 0;
	foundArticle(filename, question, artNum);
	break;
    }

    PopDownDialog(GotoArticleBox);
    GotoArticleBox = 0;
    unbusyCursor();
    inCommand = 0;
    return;
}


/*ARGSUSED*/
static void
artGotoArticleFunction(widget)
Widget widget;
{
    static struct DialogArg args[] = {
	{"abort",   gotoArticleHandler, (caddr_t) XRNgotoArticle_ABORT},
	{"doit", gotoArticleHandler, (caddr_t) XRNgotoArticle_DOIT},
    };
    
    if (Mode != ARTICLE_MODE) {
	return;
    }
    if (GotoArticleBox == (Widget) 0) {
	GotoArticleBox = CreateDialog(TopLevel, "Article Number:",
				  DIALOG_TEXT, args, XtNumber(args));
    }
    PopUpDialog(GotoArticleBox);
    return;
}


#define XRNsubSearch_ABORT 0
#define XRNsubSearch_FORWARD 1
#define XRNsubSearch_BACK 2

static Widget SubSearchBox = (Widget) 0;

#define CLEANUP \
	if (SubSearchBox) PopDownDialog(SubSearchBox); \
	SubSearchBox = 0; \
	inCommand = 0; \
	unbusyCursor();

/*ARGSUSED*/
static void 
subSearchHandler(widget, client_data, call_data)
Widget widget;
caddr_t client_data;
caddr_t call_data;
{
    char *regexpr;
    char *filename, *question;
    int SavePosition;
    int status;
    int direction;
    long artNum;
    Arg sargs[5];

    if (inCommand) {
	return;
    }
    inCommand = 1;

    busyCursor();
    XawTextUnsetSelection(Text);
    if ((int) client_data == XRNsubSearch_ABORT) {
	CLEANUP;
	return;
    }
    SavePosition = ArtPosition;
    regexpr = GetDialogValue(SubSearchBox);
    if (*regexpr == 0) {
	if (LastRegexp == NIL(char)) {
	    mesgPane(XRN_INFO, "No previous regular expression");
	    CLEANUP;
	    return;	   
	}
	regexpr = LastRegexp;
    } else {
	if (LastRegexp != NIL(char)) {
	    FREE(LastRegexp);
	}
	LastRegexp = XtNewString(regexpr);
    }

    /* XXX */
    if (SubSearchBox) PopDownDialog(SubSearchBox);
    SubSearchBox = 0;
    
    direction = ((int) client_data == XRNsubSearch_FORWARD) ? FORWARD : BACK;
    LastSearch = direction;
#ifndef MOTIF
    status = subjectSearch(direction, &SubjectString, SubjectSource, 
			   &ArtPosition, ArticleTop, LastRegexp,
			   &filename, &question, &artNum);
#else
    status = subjectSearch(direction, &SubjectString, TextMotifString, 
			   &ArtPosition, ArticleTop, LastRegexp,
			   &filename, &question, &artNum);
#endif
    switch (status) {
      case ABORT:
	infoNow("search aborted");
	ArtPosition = SavePosition;
	break;

      case NOMATCH:
	(void) sprintf(error_buffer, "Search for expression %s: no match was found",
		       LastRegexp);
	infoNow(error_buffer);
      case ERROR:
	ArtPosition = SavePosition;
	break;

      case MATCH:
	(void) sprintf(error_buffer, "Search for expression %s", LastRegexp);
	infoNow(error_buffer);
	foundArticle(filename, question, artNum);
	break;

      case WINDOWCHANGE:
	(void) sprintf(error_buffer, "Search for expression %s", LastRegexp);
	infoNow(error_buffer);
#ifndef MOTIF
	if (SubjectSource != 0) {
#else
	if (TextMotifString != 0) {
#endif
	    ArticleTop = XawTextTopPosition(Text);
#ifndef MOTIF
	    XawStringSourceDestroy(SubjectSource);
#endif
	}
#ifndef MOTIF
	XtSetArg(sargs[0], XtNstring, SubjectString);
	XtSetArg(sargs[1], XtNlength, utStrlen(SubjectString) + 1);
	XtSetArg(sargs[2], XtNeditType, XawtextRead);
	XtSetArg(sargs[3], XtNuseStringInPlace, True);
	XtSetArg(sargs[4], XtNtype, XawAsciiString);
	SubjectSource = XtCreateWidget("subjectTextSource",
				       asciiSrcObjectClass,
				       Text, sargs, XtNumber(sargs));
	XawTextSetSource(Text, SubjectSource, ArticleTop);
#else
	XawTextSetMotifString(Text, SubjectString);
#endif
	foundArticle(filename, question, artNum);
	break;

      case EXIT:
	exitArticleMode();
	break;
    }

    CLEANUP;
    return;
}


/*ARGSUSED*/
static void
artSubSearchFunction(widget)
Widget widget;
{
    static struct DialogArg args[] = {
	{"abort",   subSearchHandler, (caddr_t) XRNsubSearch_ABORT},
	{"forward", subSearchHandler, (caddr_t) XRNsubSearch_FORWARD},
	{"back",    subSearchHandler, (caddr_t) XRNsubSearch_BACK},
    };
    
    if (Mode != ARTICLE_MODE) {
	return;
    }
    if (SubSearchBox == (Widget) 0) {
	SubSearchBox = CreateDialog(TopLevel, "Regular Expression:",
				  DIALOG_TEXT, args, XtNumber(args));
    }
    PopUpDialog(SubSearchBox);
    return;
}


/*ARGSUSED*/
static void
artContinueFunction(widget)
Widget widget;
/*
 * Continue a previously started regular expression
 * search of the subject lines.  Search for same
 * regular expression, in same direction.
 */
{
    char *filename, *question;
    XawTextPosition SavePosition;
    int status;
    long artNum;
    Arg sargs[5];

    if (!LastRegexp) {
	mesgPane(XRN_INFO, "No previous regular expression");
	return;
    }
    (void) sprintf(error_buffer, "Searching for expression %s", LastRegexp);
    info(error_buffer);
    XawTextUnsetSelection(Text);
    SavePosition = ArtPosition;
#ifndef MOTIF
    status = subjectSearch(LastSearch, &SubjectString, SubjectSource,
			   &ArtPosition, ArticleTop, NIL(char),
			   &filename, &question, &artNum);
#else
    status = subjectSearch(LastSearch, &SubjectString, TextMotifString,
			   &ArtPosition, ArticleTop, NIL(char),
			   &filename, &question, &artNum);
#endif
    switch (status) {
      case ABORT:
	infoNow("search aborted");
	ArtPosition = SavePosition;
	return;
      case NOMATCH:
	(void) sprintf(error_buffer, "Search for expression %s: no match was found", LastRegexp);
	infoNow(error_buffer);
      case ERROR:
	ArtPosition = SavePosition;
	return;
      case MATCH:
	(void) sprintf(error_buffer, "Search for expression %s", LastRegexp);
	infoNow(error_buffer);    
	foundArticle(filename, question, artNum);
	return;
      case WINDOWCHANGE:
	(void) sprintf(error_buffer, "Search for expression %s", LastRegexp);
	infoNow(error_buffer);
#ifndef MOTIF	
	if (SubjectSource != 0) {
#else
	if (TextMotifString != 0) {
#endif
	    ArticleTop = XawTextTopPosition(Text);
#ifndef MOTIF
	    XawStringSourceDestroy(SubjectSource);
#endif
	}

#ifndef MOTIF
	XtSetArg(sargs[0], XtNstring, SubjectString);
	XtSetArg(sargs[1], XtNlength, utStrlen(SubjectString) + 1);
	XtSetArg(sargs[2], XtNeditType, XawtextRead);
	XtSetArg(sargs[3], XtNuseStringInPlace, True);
	XtSetArg(sargs[4], XtNtype, XawAsciiString);
	SubjectSource = XtCreateWidget("subjectTextSource",
				       asciiSrcObjectClass,
				       Text, sargs, XtNumber(sargs));
	XawTextSetSource(Text, SubjectSource, ArticleTop);
#else
	XawTextSetMotifString(Text, SubjectString);
#endif
	foundArticle(filename, question, artNum);
	return;
      case EXIT:
	exitArticleMode();
	return;
    }
}
	
/*ARGSUSED*/
static void
artLastFunction(widget)
Widget widget;
/*
 * Display the article accessed before the current one
 */
{
    char *filename, *question;

    if (Mode != ARTICLE_MODE) {
	return;
    }
    if (PrevArticle == 0) {
	mesgPane(XRN_INFO, "No previous article");
	return;
    }
    ArtPosition = 0;
    findArticle(SubjectString, PrevArticle, &ArtPosition);
    gotoArticle(PrevArticle);
    if (getArticle(&filename, &question) != XRN_OKAY) {
#ifndef MOTIF
	removeLine(SubjectString, Text, &SubjectSource, ArtPosition, &ArticleTop);
#else
	removeLine(SubjectString, Text, &TextMotifString, ArtPosition, &ArticleTop);
#endif
    } else {
	foundArticle(filename, question, PrevArticle);
    }
    
    return;
}


/*ARGSUSED*/
static void
artExitFunction(widget)
Widget widget;
/*
 * Exit from the current newsgroup, marking all articles as
 * unread
 */
{
    XawTextPosition beg, end;

    if (Mode != ARTICLE_MODE) {
	return;
    }
    beg = (XawTextPosition) 0;
    end = (XawTextPosition) 0;
    endInsertionPoint(SubjectString, &end);
    moveEnd(SubjectString, &end);
    markArticles(SubjectString, beg, end, ' ');
    exitArticleMode();
    
    return;
}


/*ARGSUSED*/
static void
addQuitFunction(widget)
Widget widget;
/*
 * unsubscribe to the remaining groups and exit add mode
 */
{
    char newGroup[GROUP_NAME_SIZE];
    int left, right, nbytes;
    int status = UNSUBSCRIBE;

    if (Mode != ADD_MODE) {
	return;
    }
    left = 0;

    /*
     * go through the remaining groups, add them
     * to the end of the newsrc and unsubscribe them
     */
    while (AddGroupsString[left] != '\0') {
	for (right = left; AddGroupsString[right] != '\n'; right++);
	nbytes = right - left;
	(void) strncpy(newGroup, &AddGroupsString[left], nbytes);
	newGroup[nbytes] = '\0';
	(void) addToNewsrcEnd(newGroup, status);
	left = right+1;
    }

    exitAddMode();
    return;
}


/*ARGSUSED*/
static void
addFirstFunction(widget)
Widget widget;
/*
 * Find selected group(s) and add them to the .newsrc in the first position.
 * Move the cursor to the next group.
 * Update the AddGroupsString, going into newsgroup mode if it
 * is NULL.  Update the text window, update the insertion point.
 *
 */
{
    char newGroup[GROUP_NAME_SIZE];
    char oldGroup[GROUP_NAME_SIZE];
    int status = SUBSCRIBE;
    XawTextPosition gbeg, left, right;

    if (Mode != ADD_MODE) {
	return;
    }
    if (getSelection(Text, AddGroupsString, &left, &right)) {
	gbeg = left;
	currentGroup(Mode, AddGroupsString, newGroup, gbeg);
	(void) addToNewsrcBeginning(newGroup, status);
	(void) strcpy(oldGroup, newGroup);
	(void) moveCursor(FORWARD, AddGroupsString, &gbeg);
	while (gbeg <= right) {
	    currentGroup(Mode, AddGroupsString, newGroup, gbeg);
	    (void) addToNewsrcAfterGroup(newGroup, oldGroup, status);
	    (void) strcpy(oldGroup, newGroup);
	    if (!moveCursor(FORWARD, AddGroupsString, &gbeg)) {
		break;
	    }
	}
	(void) strcpy(&AddGroupsString[left], &AddGroupsString[right+1]);
	if (setCursorCurrent(AddGroupsString, &left)) {
	    /* update the text window */
	    redrawAddTextWidget(left);
	} else {
	    exitAddMode();
	}
    } else {
	(void) moveUpWrap(AddGroupsString, &left);
	XawTextSetInsertionPoint(Text, left);
    }
    
    return;
}


/*ARGSUSED*/
static void
addLastFunction(widget)
Widget widget;
/*
 * add the currently selected group(s) to the end of the .newsrc file
 * and subscribe to them.
 */
{
    char newGroup[GROUP_NAME_SIZE];
    int status = SUBSCRIBE;
    XawTextPosition gbeg, left, right;

    if (Mode != ADD_MODE) {
	return;
    }
    if (getSelection(Text, AddGroupsString, &left, &right)) {
	gbeg = left;
	while (gbeg <= right) {
	    currentGroup(Mode, AddGroupsString, newGroup, gbeg);
	    (void) addToNewsrcEnd(newGroup, status);
	    if (!moveCursor(FORWARD, AddGroupsString, &gbeg)) {
		break;
	    }
	}
	(void) strcpy(&AddGroupsString[left], &AddGroupsString[right+1]);
	if (setCursorCurrent(AddGroupsString, &left)) {
	    redrawAddTextWidget(left);
	} else {
	    exitAddMode();
	}
    } else {
	(void) moveUpWrap(AddGroupsString, &left);
	XawTextSetInsertionPoint(Text, left);
    }
    
    return;
}


/* entering the name of a newsgroup to add after */

#define XRNadd_ADD 1
#define XRNadd_ABORT 0

static Widget AddBox = (Widget) 0;

/*ARGSUSED*/
static void
addHandler(widget, client_data, call_data)
Widget widget;
caddr_t client_data;
caddr_t call_data;
/*
 * get the newsgroup to add a new newsgroup after in the .newsrc file
 */
{
    char newGroup[GROUP_NAME_SIZE];
    char oldGroup[GROUP_NAME_SIZE];
    int status = SUBSCRIBE;
    XawTextPosition gbeg, left, right;

    if (inCommand) {
	return;
    }
    inCommand = 1;
    busyCursor();
    if (getSelection(Text, AddGroupsString, &left, &right)) {
	if ((int) client_data == XRNadd_ADD) {
	    gbeg = left;
	    currentGroup(Mode, AddGroupsString, newGroup, gbeg);
	    if (addToNewsrcAfterGroup(newGroup,
				      GetDialogValue(AddBox),
				      status) == GOOD_GROUP) {
		(void) moveCursor(FORWARD, AddGroupsString, &gbeg);
		while (gbeg <= right) {
		    (void) strcpy(oldGroup, newGroup);
		    currentGroup(Mode, AddGroupsString, newGroup, gbeg);
		    (void) addToNewsrcAfterGroup(newGroup, oldGroup, status);
		    if (!moveCursor(FORWARD,AddGroupsString, &gbeg)) {
			break;
		    }
		}
		(void) strcpy(&AddGroupsString[left],
			      &AddGroupsString[right+1]);
	    }
	}
	if (setCursorCurrent(AddGroupsString, &left)) {
	    redrawAddTextWidget(left);
	} else {
	    exitAddMode();
	}
    } else {
	XawTextSetInsertionPoint(Text, (XawTextPosition) 0);
    }
    PopDownDialog(AddBox);
    AddBox = 0;
    unbusyCursor();
    inCommand = 0;
    return;
}


/*ARGSUSED*/
static void
addAfterFunction(widget)
Widget widget;
/*
 * subscribe to a new newsgroup, adding after a particular group in the
 * .newsrc file
 */
{
    static struct DialogArg args[] = {
	{"abort", addHandler, (caddr_t) XRNadd_ABORT},
	{"add",   addHandler, (caddr_t) XRNadd_ADD},
    };

    if (Mode != ADD_MODE) {
	return;
    }
    if (AddBox == (Widget) 0) {
	AddBox = CreateDialog(TopLevel, "After which newsgroup?",
				  DIALOG_TEXT, args, XtNumber(args));
    }
    PopUpDialog(AddBox);
    return;
}


/*ARGSUSED*/
static void
addUnsubFunction(widget)
Widget widget;
/*
 * add a group to the end of the .newsrc file as unsubscribed
 */
{
    char newGroup[GROUP_NAME_SIZE];
    int status = UNSUBSCRIBE;
    XawTextPosition gbeg, left, right;

    if (Mode != ADD_MODE) {
	return;
    }
    if (getSelection(Text, AddGroupsString, &left, &right)) {
	gbeg = left;
	while (gbeg <= right) {
	    currentGroup(Mode, AddGroupsString, newGroup, gbeg);
	    (void) addToNewsrcEnd(newGroup, status);
	    if (!moveCursor(FORWARD, AddGroupsString, &gbeg)) {
		break;
	    }
	}
	(void) strcpy(&AddGroupsString[left], &AddGroupsString[right+1]);
	if (setCursorCurrent(AddGroupsString, &left)) {
	    redrawAddTextWidget(left);
	} else {
	    exitAddMode();
	}
    } else {
	(void) moveUpWrap(AddGroupsString, &left);
	XawTextSetInsertionPoint(Text, left);
    }
    return;
}


#define XRNsave_ABORT          0
#define XRNsave_SAVE           1

static Widget SaveBox = (Widget) 0;  /* box for typing in the name of a file */

/*ARGSUSED*/
static void
saveHandler(widget, client_data, call_data)
Widget widget;
caddr_t client_data;
caddr_t call_data;
/*
 * handler for the save box
 */
{
    if (inCommand) {
	return;
    }
    inCommand = 1;
    busyCursor();
    if ((int) client_data != XRNsave_ABORT) {
	XawTextPosition left, right;

	(void) getSelection(Text, SubjectString, &left, &right);
	if (left == right) {
	    if (saveCurrentArticle(GetDialogValue(SaveBox))) {
		SubjectString[ArtPosition+1] = 'S';
		XawTextInvalidate(Text, ArtPosition, ArtPosition + 2);
	    }
	} else {
	    long article;
	    char *template = GetDialogValue(SaveBox);
	    char buffer[1024];

	    while (left < right) {
		article = atoi(&SubjectString[left + 2]);
		(void) sprintf(buffer, template, article);
		if (saveArticleByNumber(buffer, article)) {
		    SubjectString[left + 1] = 'S';
		    XawTextInvalidate(Text, left, left + 2);
		}
		(void) moveCursor(FORWARD, SubjectString, &left);
	    }
	    (void) moveCursor(BACK, SubjectString, &left);
	}
	if (!SaveString && SaveString != app_resources.saveString) {
		XtFree(SaveString);
	}
	SaveString = XtNewString(GetDialogValue(SaveBox));
    }
    PopDownDialog(SaveBox);
    SaveBox = 0;
    unbusyCursor();
    inCommand = 0;
    return;
}    

/*ARGSUSED*/
static void
artSaveFunction(widget, ev, params, num_params)
Widget widget;
XEvent *ev;
String *params;
Cardinal *num_params;
/*
 * query the user about saving an article
 *
 *    brings up a dialog box
 *
 *    returns: void
 *
 */
{
    static struct DialogArg args[] = {
	{"abort", saveHandler, (caddr_t) XRNsave_ABORT},
	{"save",  saveHandler, (caddr_t) XRNsave_SAVE},
    };

    if (Mode != ARTICLE_MODE) {
	return;
    }
    if (num_params && *num_params == 1) {
	XawTextPosition left, right;
	char *ssstring;
	ssstring = SaveString;
	SaveString = params[0];

	busyCursor();

	(void) getSelection(Text, SubjectString, &left, &right);
	if (left == right) {
	    if (saveCurrentArticle(SaveString)) {
	        SubjectString[ArtPosition + 1] = 'S';
	        XawTextInvalidate(Text, ArtPosition, ArtPosition + 2);
	    }
	} else {
	    long article;
	    char buffer[1024];

	    while (left < right) {
	    	article = atoi(&SubjectString[left + 2]);
	    	(void) sprintf(buffer, SaveString, article);
	    	if (saveArticleByNumber(buffer, article)) {
	    	    SubjectString[left + 1] = 'S';
	    	    XawTextInvalidate(Text, left, left + 2);
	    	}
	    	(void) moveCursor(FORWARD, SubjectString, &left);
	    }
	    (void) moveCursor(BACK, SubjectString, &left);
	}
	SaveBox = 0;
	unbusyCursor();
	inCommand = 0;
	SaveString = ssstring;
	return;
    }
    if (SaveBox == (Widget) 0) {
	if (!SaveString && app_resources.saveString) {
		SaveString = XtNewString(app_resources.saveString);
	}
	SaveBox = CreateDialog(TopLevel, "    File or +Folder Name?    ",
				  SaveString == NULL ? DIALOG_TEXT
				   : SaveString, args, XtNumber(args));
    }
    PopUpDialog(SaveBox);
    return;
}

/*ARGSUSED*/
static void
artPrintFunction(widget)
Widget widget;
{
    char buffer[1024];
    struct newsgroup *newsgroup = Newsrc[CurrentGroupNumber];
    art_num art = newsgroup->current;
#ifdef VMS
    int status;
    short msglen;
    struct dsc$descriptor_s buf_desc = { sizeof(buffer)-1,
	DSC$K_DTYPE_T, DSC$K_CLASS_S, buffer };

    (void) sprintf(buffer, "%sARTICLE-%u.LIS", app_resources.tmpDir,
	CurrentArticle);
    if (saveCurrentArticle(buffer))
    {
	(void) sprintf(buffer, "%s %sARTICLE-%u.LIS",
	    app_resources.printCommand, app_resources.tmpDir,
	    CurrentArticle);
	status = system(buffer);
	if (status & 1) {
	    info("Article sucessfully queued");
	    SubjectString[ArtPosition+1] = 'P';
	    XawTextInvalidate(Text, ArtPosition, ArtPosition + 2);
	    SET_PRINTED(newsgroup->articles[INDEX(art)]);
	} else {
	    status = SYS$GETMSG(status, &msglen, &buf_desc, 0, 0);
	    buffer[msglen] = NULL;
	    info(buffer);
	}
    }

#else /* Not VMS */
    (void) sprintf(buffer, "| %s", app_resources.printCommand);
    if(saveCurrentArticle(buffer)) 
    {
	SubjectString[ArtPosition+1] = 'P';
	XawTextInvalidate(Text, ArtPosition, ArtPosition + 2);
	SET_PRINTED(newsgroup->articles[INDEX(art)]);
    }
#endif /* VMS */

    return;
}


/*ARGSUSED*/
static void
artReplyFunction(widget)
Widget widget;
/*
 * Allow user to post a reply to the currently posted article
 */
{
    if (Mode != ARTICLE_MODE) {
	return;
    }
    reply();
    return;
}



/*ARGSUSED*/
static void
artForwardFunction(widget)
Widget widget;
/*
 * Allow user to forward an article to a user(s)
 */
{
    if (Mode != ARTICLE_MODE) {
	return;
    }
    forward();
    return;
}


/*ARGSUSED*/
static void
artGripeFunction(widget)
Widget widget;
/*
 * Allow user to gripe
 */
{
    if (Mode != ARTICLE_MODE) {
	return;
    }
    gripe();
    return;
}


/*ARGSUSED*/
static void
artFollowupFunction(widget)
Widget widget;
/*
 * Allow user to post a followup to the currently displayed article
 */
{
    if (Mode != ARTICLE_MODE) {
	return;
    }
    followup();
    return;
}

/*ARGSUSED*/
static void
artCancelFunction(widget)
Widget widget;
/*
 * Allow user to post a followup to the currently displayed article
 */
{
    if (Mode != ARTICLE_MODE) {
	return;
    }
    cancelArticle();
    return;
}


/*ARGSUSED*/
static void
artRot13Function(widget)
Widget widget;
/*
 * decrypt a joke
 */
{
    char *filename, *question;

    if (Mode != ARTICLE_MODE) {
	return;
    }
    if (toggleRotation(&filename, &question) == XRN_OKAY) {
	LastArticle = NIL(char);
	redrawArticleWidget(filename, question);
    }
    return;
}


/*ARGSUSED*/
static void
artHeaderFunction(widget)
Widget widget;
{
    char *filename, *question;

    if (Mode != ARTICLE_MODE) {
	return;
    }
    if (toggleHeaders(&filename, &question) == XRN_OKAY) {
	LastArticle = NIL(char);
	redrawArticleWidget(filename, question);
    }
    return;
}


#define SUB_STRING   "subscribed  "
#define UNSUB_STRING "unsubscribed"

/*ARGSUSED*/
static void
allQuitFunction(widget)
Widget widget;
/*
 * Quit all groups mode.
 */
{
    if (Mode != ALL_MODE) {
	return;
    }
    resetSelection();
    /* destory string and source and stuff in the dummy source */
    
#ifndef MOTIF
    if (AllSource != 0) {
	XawStringSourceDestroy(AllSource);
	AllSource = 0;
    }
#else
    ArticleTextMotifString = 0;
#endif

    if (AllGroupsString != NIL(char)) {
	FREE(AllGroupsString);
	AllGroupsString = NIL(char);
    }

#ifndef MOTIF
    if (DummySource == 0) {
	static Arg sargs[] = {
	    {XtNstring, (XtArgVal) ""},
	    {XtNlength, (XtArgVal) 2},
	    {XtNeditType, (XtArgVal) XawtextRead},
	    {XtNuseStringInPlace, (XtArgVal) True},
	    {XtNtype, (XtArgVal) XawAsciiString},
	};
	DummySource = XtCreateWidget("dummyTextSource",
				   asciiSrcObjectClass,
				   ArticleText, sargs, XtNumber(sargs));
    }
    XawTextSetSource(ArticleText, DummySource, (XawTextPosition) 0);
#else
    XawTextSetMotifString(ArticleText, NULL);
    ChooseText(True);
#endif
    switchToNewsgroupMode();
    return;
}


/*ARGSUSED*/
static void
allSubFunction(widget)
Widget widget;
/*
 * Make the selected group(s) subscribed to, and leave them in
 * their current position in the newsrc file.
 */
{
    char newGroup[GROUP_NAME_SIZE];
    XawTextPosition gbeg, left, right;

    if (Mode != ALL_MODE) {
	return;
    }
    resetSelection();
    
    if (getSelection(ArticleText, AllGroupsString, &left, &right)) {
	gbeg = left;
	while (gbeg <= right) {
	    currentGroup(Mode, AllGroupsString, newGroup, gbeg);
	    if (gotoNewsgroup(newGroup) != GOOD_GROUP) {
		mesgPane(XRN_SERIOUS, "Bad group: %s", newGroup);
		return;
	    }
	    subscribe();
	    if (!moveCursor(FORWARD, AllGroupsString, &gbeg)) {
		break;
	    }
	}
	markAllString(AllGroupsString, left, right, SUB_STRING);
	updateAllWidget(left, right);
    } else {
	(void) moveUpWrap(AllGroupsString, &left);
	XawTextSetInsertionPoint(ArticleText, left);
    }
    return;
}


/*ARGSUSED*/
static void
allFirstFunction(widget)
Widget widget;
/*
 * Mark the selected group(s) as subscribed to, and move them to the
 * beginning of the newsrc file.
 */
{
    char newGroup[GROUP_NAME_SIZE];
    char oldGroup[GROUP_NAME_SIZE];
    int status = SUBSCRIBE;
    XawTextPosition gbeg, left, right;

    if (Mode != ALL_MODE) {
	return;
    }
    resetSelection();
    
    if (getSelection(ArticleText, AllGroupsString, &left, &right)) {
	gbeg = left;
	currentGroup(Mode, AllGroupsString, newGroup, gbeg);
	(void) addToNewsrcBeginning(newGroup, SUBSCRIBE);
	(void) strcpy(oldGroup, newGroup);
	(void) moveCursor(FORWARD, AllGroupsString, &gbeg);
	while (gbeg <= right) {
	    currentGroup(Mode, AllGroupsString, newGroup, gbeg);
	    (void) addToNewsrcAfterGroup(newGroup, oldGroup, status);
	    (void) strcpy(oldGroup, newGroup);
	    if (!moveCursor(FORWARD, AllGroupsString, &gbeg)) {
		break;
	    }
	}
	markAllString(AllGroupsString, left, right, SUB_STRING);
	if (AllStatus == 0) {
	    redrawAllWidget((XawTextPosition) 0);
	} else {
	    updateAllWidget(left, right);
	}
    } else {
	(void) moveUpWrap(AllGroupsString, &left);
	XawTextSetInsertionPoint(ArticleText, left);
	return;
    }
    return;
}


/*ARGSUSED*/
static void
allLastFunction(widget)
Widget widget;
/*
 * Mark the selected group(s) as subscribed to, and move them
 * to the end of the newsrc file.
 */
{
    char newGroup[GROUP_NAME_SIZE];
    int status = SUBSCRIBE;
    XawTextPosition gbeg, left, right;

    if (Mode != ALL_MODE) {
	return;
    }
    resetSelection();
    
    if (getSelection(ArticleText, AllGroupsString, &left, &right)) {
	gbeg = left;
	while (gbeg <= right) {
	    currentGroup(Mode, AllGroupsString, newGroup, gbeg);
	    (void) addToNewsrcEnd(newGroup, status);
	    if (!moveCursor(FORWARD, AllGroupsString, &gbeg)) {
		break;
	    }
	}
	markAllString(AllGroupsString, left, right, SUB_STRING);
	if (AllStatus == 0) {
	    redrawAllWidget(left);
	} else {
	    updateAllWidget(left, right);
	}
    } else {
	(void) moveUpWrap(AllGroupsString, &left);
	XawTextSetInsertionPoint(ArticleText, left);
	return;
    }
    return;
}


static Widget AllBox = (Widget) 0;

/*ARGSUSED*/
static void
allHandler(widget, client_data, call_data)
Widget widget;
caddr_t client_data;
caddr_t call_data;
/*
 * Mark the selected group(s) as subscribed to, and place them
 * after the group name (entered in the dialog box) in the newsrc file.
 */
{
    char newGroup[GROUP_NAME_SIZE];
    char oldGroup[GROUP_NAME_SIZE];
    XawTextPosition gbeg, left, right;
    int all = 0;

    if (inCommand) {
	return;
    }
    inCommand = 1;
    busyCursor();
    if (getSelection(ArticleText, AllGroupsString, &left, &right)) {
	if ((int) client_data == XRNadd_ADD) {
	    gbeg = left;
	    currentGroup(Mode, AllGroupsString, newGroup, gbeg);
	    if (addToNewsrcAfterGroup(newGroup,
				      GetDialogValue(AllBox),
				      SUBSCRIBE) == GOOD_GROUP) {
		(void) moveCursor(FORWARD, AllGroupsString, &gbeg);
		while (gbeg <= right) {
		    (void) strcpy(oldGroup, newGroup);
		    currentGroup(Mode, AllGroupsString, newGroup, gbeg);
		    (void) addToNewsrcAfterGroup(newGroup, oldGroup, SUBSCRIBE);
		    if (!moveCursor(FORWARD, AllGroupsString, &gbeg)) {
			break;
		    }
		}
		markAllString(AllGroupsString, left, right, SUB_STRING);
		if (AllStatus == 0) {
		    redrawAllWidget(left);
		} else {
		    updateAllWidget(left, right);
		}
		all = 1;
	    }
	}
    } else {
	(void) moveUpWrap(AllGroupsString, &left);
    }
    if (!all) {
	XawTextSetInsertionPoint(ArticleText, left);
    }
    PopDownDialog(AllBox);
    AllBox = 0;
    unbusyCursor();
    inCommand = 0;
    return;
}


/*ARGSUSED*/
static void
allAfterFunction(widget)
Widget widget;
/*
 * Put up a dialog box for the user to enter a group name after which
 * the selected articles should be placed.
 */
{
    static struct DialogArg args[] = {
	{"abort",     allHandler, (caddr_t) XRNadd_ABORT},
	{"subscribe", allHandler, (caddr_t) XRNadd_ADD},
    };

    if (Mode != ALL_MODE) {
	return;
    }
    resetSelection();
    
    if (AllBox == (Widget) 0) {
	AllBox = CreateDialog(TopLevel, "After which newsgroup?",
				  DIALOG_TEXT, args, XtNumber(args));
    }
    PopUpDialog(AllBox);
    return;
}


/*ARGSUSED*/
static void
allUnsubFunction(widget)
Widget widget;
/*
 * Mark the selected group(s) as unsubscribed, leaving their position
 * in the newsrc file unchanged.
 */
{
    char newGroup[GROUP_NAME_SIZE];
    XawTextPosition gbeg, left, right;

    if (Mode != ALL_MODE) {
	return;
    }
    resetSelection();
    
    if (getSelection(ArticleText, AllGroupsString, &left, &right)) {
	gbeg = left;
	while (gbeg <= right) {
	    currentGroup(Mode, AllGroupsString, newGroup, gbeg);
	    if (gotoNewsgroup(newGroup) != GOOD_GROUP) {
		mesgPane(XRN_SERIOUS, "Bad group: %s", newGroup);
		return;
	    }
	    unsubscribe();
	    if (!moveCursor(FORWARD, AllGroupsString, &gbeg)) {
		return;
	    }
	}
	markAllString(AllGroupsString, left, right, UNSUB_STRING);
	updateAllWidget(left, right);
    } else {
	(void) moveCursor(BACK, AllGroupsString, &left);
	XawTextSetInsertionPoint(ArticleText, left);
    }
    return;
}


/*ARGSUSED*/
static void
allScrollFunction(widget)
Widget widget;
/*
 * called when the user wants to scroll the all groups window
 */
{
    if (Mode != ALL_MODE) {
	return;
    }
#ifndef MOTIF
    XtCallActionProc(ArticleText, "next-page", 0, 0, 0);
#else
    {
      int topItemPosition, itemCount, visibleItemCount;
      Arg args[3];
      
      XtSetArg(args[0], XmNtopItemPosition, &topItemPosition);
      XtSetArg(args[1], XmNitemCount, &itemCount);
      XtSetArg(args[2], XmNvisibleItemCount, &visibleItemCount);
      XtGetValues(ArticleText, args, 3);
      if (topItemPosition+visibleItemCount-1 < itemCount) {
	XmListSetPos(ArticleText, topItemPosition+visibleItemCount-1);
      } else {
	XmListSetPos(ArticleText, itemCount);
      }
    }
#endif
    return;
}


/*ARGSUSED*/
static void
allScrollBackFunction(widget)
Widget widget;
/*
 * called when the user wants to scroll the all groups window
 */
{
    if (Mode != ALL_MODE) {
	return;
    }
#ifndef MOTIF
    XtCallActionProc(ArticleText, "previous-page", 0, 0, 0);
#else
    {
      int topItemPosition;
      Arg args[1];
      
      XtSetArg(args[0], XmNtopItemPosition, &topItemPosition);
      XtGetValues(ArticleText, args, 1);
      XmListSetBottomPos(ArticleText, topItemPosition);
    }
#endif
    return;
}


/*ARGSUSED*/
static void
allGotoFunction(widget)
Widget widget;
/*
 * Go to the current newsgroup.  The current
 * group is either the first group of a selection,
 * or, if there is no selection, the group the cursor
 * is currently on (if any).
 */
{
    char newGroup[GROUP_NAME_SIZE];
    XawTextPosition left, right;

    if (Mode != ALL_MODE) {
	return;
    }
    resetSelection();
    
    /* get the current group name */

    if (getSelection(ArticleText, AllGroupsString, &left, &right)) {
	currentGroup(Mode, AllGroupsString, newGroup, left);
    } else {
	/* if at the end of the string, move to the beginning and quit */
	(void) moveUpWrap(AllGroupsString, &left);
	XawTextSetInsertionPoint(ArticleText, left);
	return;
    }

    /* jump to the newsgroup */

    if (jumpToNewsgroup(newGroup) == GOOD_GROUP) {
	(void) strcpy(LastGroup, newGroup);
	
	/* free source */

#ifndef MOTIF
	if (AllSource != 0) {
	    XawStringSourceDestroy(AllSource);
	    AllSource = 0;
	}
#else
	ArticleTextMotifString = 0;
#endif

	/* free string */

	if (AllGroupsString != NIL(char)) {
	    FREE(AllGroupsString);
	    AllGroupsString = NIL(char);
	}
	switchToArticleMode();
	return;
    }
    return;
}


/*ARGSUSED*/
static void
allSelectFunction(widget)
Widget widget;
/*
 * Make note of the groups that were selected
 * to be moved.
 */
{
    if (Mode != ALL_MODE) {
	return;
    }
    resetSelection();
    
    (void) getSelection(ArticleText, AllGroupsString, &First, &Last);
    return;
}


/*ARGSUSED*/
static void
allMoveFunction(widget)
Widget widget;
/*
 * Move the groups in the last selection to
 * the current cursor position (before the
 * current selection.
 */
{
    char newGroup[GROUP_NAME_SIZE];
    char oldGroup[GROUP_NAME_SIZE];
    int status = SUBSCRIBE;
    int dummy;
    char *newString;
    XawTextPosition left, right;
    XawTextPosition stringPoint;
    XawTextPosition cursorSpot;
    XawTextPosition ngGroupPosition;
    int direction = 0;
    int numGroups = 0;

    if (Mode != ALL_MODE) {
	return;
    }
    if (First == Last) {
	mesgPane(XRN_INFO, "No groups were selected");
	return;
    }
    
    buildString(&newString, First, Last, AllGroupsString);
    stringPoint = 0;
    (void) getSelection(ArticleText, AllGroupsString, &left, &right);
    if ((left >= First) &&(left <= Last+1)) {
	mesgPane(XRN_SERIOUS, "Selected groups have not been moved");
	resetSelection();
	return;
    }
    ngGroupPosition = GroupPosition;
    GroupPosition = cursorSpot = left;
    if (left > First) {
	direction = 1;
    }
    currentMode(newString, newGroup, &status, stringPoint);
    if (!moveCursor(BACK, AllGroupsString, &left)) {
	(void) addToNewsrcBeginning(newGroup, status);
	(void) strcpy(oldGroup, newGroup);
	(void) moveCursor(FORWARD, newString, &stringPoint);
	numGroups++;
    } else {
	currentMode(AllGroupsString, oldGroup, &dummy, left);
	(void) addToNewsrcAfterGroup(newGroup, oldGroup, status);
	(void) strcpy(oldGroup, newGroup);
	(void) moveCursor(FORWARD, newString, &stringPoint);
	numGroups++;
    }
    while (newString[stringPoint] != '\0') {
	numGroups++;
	currentMode(newString, newGroup, &status, stringPoint);
	(void) addToNewsrcAfterGroup(newGroup, oldGroup, status);
	(void) strcpy(oldGroup, newGroup);
	if (!moveCursor(FORWARD, newString, &stringPoint)) {
	    break;
	}
    }
    redrawAllWidget(left);
    if (direction) {
	GroupPosition = cursorSpot;
	while (numGroups > 0) {
	    (void) moveCursor(BACK, AllGroupsString, &GroupPosition);
	    numGroups--;
	}
    }
    GroupPosition = ngGroupPosition;
    resetSelection();
    return;
}


/*ARGSUSED*/
static void
allToggleFunction(widget)
Widget widget;
/* 
 * Change the order the groups appear on the screen.
 */
{
    Arg sargs[5];
    
    if (Mode != ALL_MODE) {
	return;
    }
    resetSelection();

#ifndef MOTIF
    if (AllSource != 0) {
	XawStringSourceDestroy(AllSource);
	AllSource = 0;
    }
#else
    ArticleTextMotifString = 0;
#endif

    if (AllGroupsString != NIL(char)) {
	FREE(AllGroupsString);
	AllGroupsString = NIL(char);
    }

    AllStatus = (AllStatus == 0) ? 1 : 0;

    /* make the new string and source */

    AllGroupsString = getStatusString(AllStatus);

#ifndef MOTIF
    XtSetArg(sargs[0], XtNstring, AllGroupsString);
    XtSetArg(sargs[1], XtNlength, utStrlen(AllGroupsString) + 1);
    XtSetArg(sargs[2], XtNeditType, XawtextRead);
    XtSetArg(sargs[3], XtNuseStringInPlace, True);
    XtSetArg(sargs[4], XtNtype, XawAsciiString);
    AllSource = XtCreateWidget("allTextSource",
				   asciiSrcObjectClass,
				   ArticleText, sargs, XtNumber(sargs));
    XawTextSetSource(ArticleText, AllSource, (XawTextPosition) 0);
    XtSetValues(ArticleText, lineSelArgs, XtNumber(lineSelArgs));
#else
    XawTextSetMotifString(ArticleText, AllGroupsString);
#endif
    return;
}


void
determineMode()
/*
 * determine the initial mode and set up Text, TopButtonBox, and Question
 */
{
    /* set mode, handle text and question */
    PreviousMode = Mode;
    
    if ((AddGroupsString = newGroups()) != NIL(char)) {
	Mode = ADD_MODE;
	setTopInfoLine("Select groups to `add', `quit' unsubscribes remaining groups");
	redrawAddTextWidget((XawTextPosition) 0);
    } else {
	Mode = NEWSGROUP_MODE;

#ifndef MOTIF
	redrawNewsgroupTextWidget();
#else
/* If I don't do this instead of redrawNewsgroupTextWidget, the first unread
   group isn't highlighted (e.g. the current position isn't set to the
   beginning) when xrn first starts up.  Don't know why it works with Xaw.
   Maybe the text widget automatically puts the insertion cursor at the
   beginning. */
	updateNewsgroupMode();
#endif

	/* update the question */
	if (utStrlen(NewsGroupsString) == 0) {
	    setTopInfoLine("No more unread articles in the subscribed to newsgroups");
	} else {
	    setTopInfoLine("Operations apply to current selection or cursor position");
	}
    }
    setBottomInfoLine("");
    /*
     * article specific buttons, only create them if this is the first
     * call to 'determineMode'
     */
    if (PreviousMode == NO_MODE) {
	createButtons();
	XtManageChildren(ArtSpecButtons, ArtSpecButtonListCount);
    }
    
    /* switch buttons */
    swapMode();
    
    return;
}
       
