// -*- C++ -*-
/*
 * $Id: wvutil.cpp 1.99 1996/08/31 07:03:27 dumoulin Exp $
 *
 */

/*-- WVUTIL.C -- File containing utility routines.
 */

#include <windows.h>
#include <windowsx.h>			// for GlobalFreePtr (JSC)
extern "C"
{
#include "wvglob.h"
#include "winvn.h"
}
#pragma hdrstop
#include "WVClass.h"
#include <commdlg.h>			// for GetOpenFileName dialog (JSC)
#include <ctype.h>
#include <stdlib.h>
#include <time.h>
#ifndef WIN32
#include <dos.h>				// for _dos_getdiskfree
#endif
#include <io.h>					// for _open etc
#include <fcntl.h>

char far *mylstrcpy (char_p ptr1, char far * ptr2);
char *get_xhdr_line (char *line);
time_t parse_usenet_date (char *date);
void finish_header_retrieval ();
void GenerateFileFilters (HWND hParentWnd, char *filters);

BOOL TrimHeader (char *header);
void AddCommLineToDoc (char *line);
void WrapAddCommLineToDoc (char *line);

// please update this if you modify XHDR retrieval
// This will now be either 5 or 7, depending on whether threading
// is enabled via 'threadp'.
unsigned int total_xhdrs = 5;

/*--- function GetNum --------------------------------------------
 *
 *  Cracks off a positive integer number from a string.
 *
 *  Entry    *ptr  is the character position to start scanning
 *                 for an integer
 *
 *  Exit     *ptr  is the character position at which we stopped
 *                 scanning (because of a non-digit).
 *           *num  is the cracked off number.
 *           Returns TRUE iff we got a number.
 */
BOOL
GetNum (char **ptr, long int *num)
{
  BOOL gotit = FALSE;

  /* Skip initial spaces                                            */

  while ((**ptr) && **ptr == ' ')
	(*ptr)++;

  *num = 0;
  while (**ptr && isdigit (**ptr)) {
	*num = 10 * (*num) + (**ptr - '0');
	gotit = TRUE;
	(*ptr)++;
  }
  return (gotit);
}

char *
get_xhdr_line (char *line)
{
  char *cptr;
/* skip past the art # and space */
  for (cptr = line; isdigit (*cptr); cptr++);
  for (; *cptr == ' '; cptr++);
  return (cptr);
}

#if 0
MRB already did this
void
make_neat_from (char far * in, char far * out)
{
  char far *left, far * right;

  /* this is controlled from .ini */
  if (FullNameFrom) {
	left = strchr (in, '(');
	right = strrchr (in, ')');

	if ((left && right) && (left < right)) {
	  strncpy (out, left + 1, (size_t) (right - left - 1));
	  out[(right - left - 1)] = (char) 0;
	}
	else						/* No name in parens */
	  strcpy (out, in);
  }
  else							/* !FullNameFrom */
	strcpy (out, in);
}
#endif

/*-- function StrToRGB -------------------------------------------------
 *
 *  Takes an ASCII string of the form "r,g,b" where r, g, and b are
 *  decimal ASCII numbers, and converts it to an RGB color number.
 */
COLORREF
StrToRGB (char *cstring)
{
  BYTE red, green, blue;
  long int lred, lgreen, lblue;

  GetNum (&cstring, &lred);
  cstring++;
  GetNum (&cstring, &lgreen);
  cstring++;
  GetNum (&cstring, &lblue);
  red = (BYTE) lred;
  green = (BYTE) lgreen;
  blue = (BYTE) lblue;

  return (RGB (red, green, blue));
}

/*-- function RGBToStr -------------------------------------------------
 *
 *  Takes an RGB color ref and converts to a string of the form "r,g,b"
 *  result is placed in buf
 *  (JSC)
 */
char *
RGBToStr (char *buf, DWORD rgbVal)
{
  sprintf (buf, "%u,%u,%u", GetRValue (rgbVal),
		   GetGValue (rgbVal),
		   GetBValue (rgbVal));
  return (buf);
}

/* String to Timezone */
time_t
dTimeZone(char *s)
{
#define SIZEOF(array)	((int)(sizeof array / sizeof array[0]))
#define ENDOF(array)	(&array[SIZEOF(array)])
#define HOUR(x)		(x * 60)

typedef struct _TABLE {
  char   *name;
  time_t value;
} TABLE;

/* Timezone table. */
static TABLE	TimezoneTable[] = {
    { "gmt",    HOUR( 0) },	/* Greenwich Mean */
    { "ut",     HOUR( 0) },	/* Universal */
    { "utc",    HOUR( 0) },	/* Universal Coordinated */
    { "cut",    HOUR( 0) },	/* Coordinated Universal */
    { "z",      HOUR( 0) },	/* Greenwich Mean */
    { "wet",    HOUR( 0) },	/* Western European */
    { "bst",    HOUR( 0)-60 },	/* British Summer */
    { "nst",	HOUR( 3)+30 }, /* Newfoundland Standard */
    { "ndt",	HOUR( 3)+30-60 }, /* Newfoundland Daylight */
    { "ast",	HOUR( 4) },	/* Atlantic Standard */
    { "adt",	HOUR( 4)-60 },	/* Atlantic Daylight */
    { "est",	HOUR( 5) },	/* Eastern Standard */
    { "edt",	HOUR( 5)-60 },	/* Eastern Daylight */
    { "cst",	HOUR( 6) },	/* Central Standard */
    { "cdt",	HOUR( 6)-60 },	/* Central Daylight */
    { "mst",	HOUR( 7) },	/* Mountain Standard */
    { "mdt",	HOUR( 7)-60 },	/* Mountain Daylight */
    { "pst",	HOUR( 8) },	/* Pacific Standard */
    { "pdt",	HOUR( 8)-60 },	/* Pacific Daylight */
    { "yst",	HOUR( 9) },	/* Yukon Standard */
    { "ydt",	HOUR( 9)-60 },	/* Yukon Daylight */
    { "akst",	HOUR( 9) },	/* Alaska Standard */
    { "akdt",	HOUR( 9)-60 },	/* Alaska Daylight */
    { "hst",	HOUR(10) },	/* Hawaii Standard */
    { "hast",	HOUR(10) },	/* Hawaii-Aleutian Standard */
    { "hadt",	HOUR(10)-60 },	/* Hawaii-Aleutian Daylight */
    { "ces",	-HOUR(1)-60 },	/* Central European Summer */
    { "cest",	-HOUR(1)-60 },	/* Central European Summer */
    { "mez",	-HOUR(1) },	/* Middle European */
    { "mezt",	-HOUR(1)-60 },	/* Middle European Summer */
    { "cet",	-HOUR(1) },	/* Central European */
    { "met",	-HOUR(1) },	/* Middle European */
    { "eet",	-HOUR(2) },	/* Eastern Europe */
    { "msk",	-HOUR(3) },	/* Moscow Winter */
    { "msd",	-HOUR(3)-60 },	/* Moscow Summer */
    { "wast",	-HOUR(8) },	/* West Australian Standard */
    { "wadt",	-HOUR(8)-60 },	/* West Australian Daylight */
    { "hkt",	-HOUR(8) },	/* Hong Kong */
    { "cct",	-HOUR(8) },	/* China Coast */
    { "jst",	-HOUR(9) },	/* Japan Standard */
    { "kst",	-HOUR(9) },	/* Korean Standard */
    { "kdt",	-HOUR(9)-60 },	/* Korean Daylight */
    { "cast",	-(HOUR(9)+30) }, /* Central Australian Standard */
    { "cadt",	-(HOUR(9)+30)-60 }, /* Central Australian Daylight */
    { "east",	-HOUR(10) },	/* Eastern Australian Standard */
    { "eadt",	-HOUR(10)-60 },	/* Eastern Australian Daylight */
    { "nzst",	-HOUR(12) },	/* New Zealand Standard */
    { "nzdt",	-HOUR(12)-60 },	/* New Zealand Daylight */
	{ "", 0 }
};
  char *p;
  int tz;
  
  for (p = s; isalpha(*p); p++)
	*p = tolower (*p);

  if (isalpha(*s)) {
	char cbak;
	TABLE *tp;

	cbak = *p;
	*p = '\0';
    /* Try for a timezone. */
    for (tp = TimezoneTable; tp < ENDOF(TimezoneTable); tp++)	{
	  if (s[0] == tp->name[0] && s[1] == tp->name[1] && strcmp(s, tp->name) == 0)
	    return (tp->value * (-60));
	}
	*p = cbak;
	s = p;
  }

  tz = atoi(s);
  if (tz >= 12 || tz <= -12) {
	int hour, min;
	hour = tz / 100;
	min = tz % 100;
	return ((size_t)(hour * 3600 + min * 60));
  }
  return ((size_t)tz * 3600);
}

/* This was lifted from ANU news. */

time_t
parse_usenet_date (char *s)
{
  struct tm datetime;
  char *cp, mon[80], tzone[80];
  int dom = 0, yr = 0, hr = 0, mn = 0, sc = 0, mth = 0;
  static char fmtMonthTable[37] = "janfebmaraprmayjunjulaugsepoctnovdec";

  if (!s || !*s)
	return (0);
  if (cp = strchr (s, ','))
	s = ++cp;
  while (isspace (*s))
	s++;
  *mon = '\0';
  if (isdigit (*s)) {
	sscanf (s, "%d %s %d %d:%d:%d %s", &dom, mon, &yr, &hr, &mn, &sc, tzone);
	if (yr < 100)
	  yr += 1900;
  }
  else
	sscanf (s, "%*s %d %d:%d:%d %d", mon, &dom, &hr, &mn, &sc, &yr);

  if (!dom || !yr || !*(cp = mon))
	return (0);
  if ((dom <= 0) || (dom >= 32))
	return (0);
  if ((yr < 1980) || (yr > 2020))
	return (0);
  if (strlen (mon) > 10)
	return (0);
  if ((hr < 0) || (hr > 23))
	return (0);
  if ((mn < 0) || (mn > 59))
	return (0);
  if ((sc < 0) || (sc > 59))
	return (0);

  for (cp = mon; *cp; cp++)
	*cp = tolower (*cp);

  if (cp = strstr (fmtMonthTable, mon))
	mth = (cp - fmtMonthTable) / 3;

/*  Setup a Posix time structure and calculate time in absolute
   time (seconds since midnight, Jan 1, 1970    JD 06/25/93 */

  memset (&datetime, 0, sizeof (struct tm));
  datetime.tm_year = yr - 1900;
  datetime.tm_mon = mth;
  datetime.tm_mday = dom;
  datetime.tm_hour = hr;
  datetime.tm_min = mn;
  datetime.tm_sec = sc;

  return (mktime (&datetime) - dTimeZone(tzone) - _timezone);
}

/*-- function StringDate ----------------*/
char *
StringDate (char *s, time_t time)
{
  struct tm *datetime;
  if (time != 0) {
	datetime = localtime (&time);

	if (fmtDaysB4Mth) {
          sprintf (s, "%02d%s%02d %02d:%02d",
                       datetime->tm_mday, fmtDateDelim, datetime->tm_mon + 1,
                       datetime->tm_hour, datetime->tm_min);
	}
	else {
          sprintf (s, "%02d%s%02d %02d:%02d",
                       datetime->tm_mon + 1, fmtDateDelim, datetime->tm_mday,
                       datetime->tm_hour, datetime->tm_min);
	}
	return (s);
  }
  else
	return ("-----");
}

/*-- function DoCommInput ---------------------------------------
 *
 *
 */
void
DoCommInput ()
{
  int ch;

  while ((CommState != ST_CLOSED_COMM) && ((ch = MRRReadComm ()) >= 0)) {
	if (ch == IgnoreCommCh) {
	}
	else if (ch == EOLCommCh) {
	  *CommLinePtr = '\0';
	  DoCommState ();
	  CommLinePtr = CommLineIn;
	}
	else {
	  *(CommLinePtr++) = (char) ch;
	  if (CommLinePtr == CommLineLWAp1)
		CommLinePtr--;
	}
  }
}

void
update_window_title (HWND hwnd,
					 char *group_name,
					 unsigned long line_number,
					 unsigned long total_lines)
{
  char title[200];
  static int prevPercent, newPercent;
  // to avoid flicker, update percent only if it has changed more than 1%

  line_number *= 100;
  if (newPercent < prevPercent)
	prevPercent = 0;

  if ((line_number % UPDATE_TITLE_FREQ) == 0) {
	  newPercent = (total_lines > 0) ? (int) (line_number / total_lines) : 0;
	if (newPercent != prevPercent && newPercent - prevPercent > 1) {
	  sprintf (title, "Retrieving headers for '%s' : %d%%", group_name, newPercent);
	  SetWindowText (hwnd, title);
     SetStatbarPercent(hwnd, newPercent, CommDoc, TRUE);
	  prevPercent = newPercent;
	}
  }
}

int
check_server_code (int retcode)
{
  HWND hAlertWnd;
  int iClass = retcode / 100;

  if (ComposeWnd) {
	hAlertWnd = ComposeWnd->hWnd;
  }
  else if (CommDoc) {
	hAlertWnd = CommDoc->hWndFrame;
  }
  else {
	hAlertWnd = NetDoc.hWndFrame;
  }

  switch (iClass) {
  case 5:
	CommBusy = FALSE;
	CommState = ST_NONE;
	MessageBox (hAlertWnd, "Error On News Server", "WinVN", MB_OK | MB_ICONHAND);
	return (1);
	break;
  case 4:
	CommBusy = FALSE;
	CommState = ST_NONE;
	MessageBox (hAlertWnd, CommLineIn, "Message From News Server", MB_OK | MB_ICONHAND);
	switch (iClass) {
	case 400:
	  /* service discontinued */
//      MRRCloseComm ();
	  //      PostQuitMessage (0);
	  Disconnect ();
	  break;
	default:
	  break;
	}
	return (1);
	break;
  }
  return (0);
}

/*  Function sync_artnum

   Normally XREF returns lists of the same length for each header type
   but some servers have errors that could cause these lists to get
   out of sync. This function tries to find the proper location in the
   headers array and returns that location.  If the article number isn't
   found, it returns -1.  JD 6/19/93 */

long
sync_artnum (long artnum,
			 long activenum,
			 header_p headers, TypGroup far * GroupDoc)
{
  long headerloc = CommDoc->ActiveLines;
  if (artnum == activenum)
	return (headerloc);
  else if (artnum < activenum) {
	while ((artnum != activenum) && (headerloc > 0)) {
	  headerloc--;
	  if ((header_elt (headers, headerloc))->number == artnum)
		return (headerloc);
	}
	return (-1);
  }
  else {
	while ((artnum != activenum) && (headerloc < GroupDoc->total_headers - 1)) {
	  headerloc++;
	  if ((header_elt (headers, headerloc))->number == artnum)
		return (headerloc);
	}
	return (-1);
  }
}

char * get_best_reference (char * refer)
{
  char * start, * end;
  start = strrchr (refer, '<');
  if (!start) {
	// no valid ref here without a '<'
	return NULL;
  } else {
	start++;
	end = strrchr (start, '>');
	if (!end) {
	  // the last ref is not complete, recurse
	  *(start-1) = (char)0;
	  return (get_best_reference (refer));
	} else {
	  *end = 0; // was: *(end+1) = (char)0;  HAB 19951229
	  return start;
	}
  }
}


// Some Usenet voting software is generating obscenely long message-id's
// with non-unique front-parts 30 chars or greater... this was throwing
// off winvn in an interesting way.
// We solve this by replacing the last 4 characters of
// mid's longer than HEADER_MESSAGE_ID_LENGTH with a 4-char hex hash value
// string of the entire mid.

// This must be done to all references _and_ mid's in order for threading
// to be able to match 'em.

// slightly modified version of 'HashGroup' from wvlist.c

void
copy_message_id_with_frob (char * dest, const char * source)
{
  int pos;

  if(source && ('<' == *source)) // chop leading '<'
    source++;
  strncpy(dest, source, HEADER_MESSAGE_ID_LENGTH-1);
  pos = strcspn(dest, "> \t" );  // chop trailing whitespace and '>'
  if(pos)
  {
    dest[pos] = 0;
  }

  // only frob if we need to
  if (strlen(dest) >= HEADER_MESSAGE_ID_LENGTH - 2) {
	unsigned long sum = 0;
	const char * p;
	for (p = source; *p; p++) {
	  sum = (sum << 1) + *p;
	}
	sprintf (dest+HEADER_MESSAGE_ID_LENGTH-9, "%08x", sum);
  }
}


// clear out the latest header info
void ClearLatestInfo()
{
  memset(&Latest, 0, sizeof(Latest));
//  AddressString[0] = 0;
//  NameString[0] = 0;
}


// store detailed info on the latest header read
void StoreHeaderInfo(const char* line)
{
  const char* sTmp = line;

  if(!sTmp) return;
  while(*sTmp && !isspace(*sTmp)) // find first whitespace
    sTmp++;
  while(*sTmp && isspace(*sTmp)) // find first char following whitespace
    sTmp++;

  if(!_strnicmp ("subject:", line, 8))
  {
//    if(!_strnicmp("Re: ", sTmp, 4))
//      sTmp += 4;
    mylstrncpy(Latest.subject, sTmp, sizeof(Latest.subject));
  }
  else if(!_strnicmp ("from:", line, 5))
  {
	  ParseAddress(sTmp,
	               Latest.email, sizeof(Latest.email),
	               Latest.from, sizeof(Latest.from));
  }
  else if(!_strnicmp ("lines:", line, 6))
  {
    Latest.lines = atoi(sTmp);
  }

  else if(!_strnicmp ("newsgroups:", line, 11))
  {
    mylstrncpy(Latest.newsgroups, sTmp, sizeof(Latest.newsgroups));
  }

  else if(!_strnicmp ("message-id:", line, 11))
  {
    copy_message_id_with_frob(Latest.message_id, sTmp);
  }
}


//   Build a list of frobbed references
void build_ref_list(char*dest, const char*src)
{
  char sRetval[HEADER_FROBREFLIST_LENGTH + HEADER_MESSAGE_ID_LENGTH + 2];
  char sTmpMsgId[HEADER_MESSAGE_ID_LENGTH];
  char*start;

  dest[0] = 0;
  if(src[0])
  {
    sRetval[0] = 0;
    start = strchr(src, '<');
    while(start && strlen(sRetval) < HEADER_FROBREFLIST_LENGTH)
    {
      copy_message_id_with_frob(sTmpMsgId, start);
      strcat(sRetval, sTmpMsgId);
      strcat(sRetval, " ");
      start = strchr(++start, '<');
    }
    mylstrncpy(dest, sRetval, HEADER_FROBREFLIST_LENGTH);
  }
}

/*-- function DoCommState ----------------------------------------------
 *
 *  Function to implement an FSA to process incoming lines from
 *  the server.
 *  This function is called once for each line from the server.
 *
 *    Entry    CommLineIn  is a zero-terminated line received from
 *                         the server.
 *             CommState   is the current state of the FSA.
 */
void
DoCommState ()
{
  TypLine far *LinePtr;
  TypBlock far *BlockPtr;
  HANDLE hBlock;
  unsigned int Offset;
  TypLineID MyLineID;
  int retcode;
  int found;
  static long first, last;
  long estnum, artnum;
  long syncnum;
  int mylen;
  BOOL done = FALSE;
  BOOL DlgStatus = FALSE;
  BOOL dolist, do_authinfo;
  static char group[MAXINTERNALLINE];
  char mybuf[MAXINTERNALLINE];
  char mybuf2[MAXINTERNALLINE];
  char far *lpsz;
  HANDLE header_handle;
  HANDLE thread_handle;
  header_p headers;
  header_p header;
  TypGroup far *GroupDoc;
  TypRange *RangePtr;
  static int PrevState = ST_NONE;
  char szStatBarText[MAXSTATBARTEXT];
  TypDoc *statDoc;
  WVArticleAction aa;

  /* CommDoc is !NULL if retrieving group list, article headers or articles */
  /* CommDecoding is true if retrieving article in decode mode (not to a doc) */
  /* PostEdit !NULL if we are posting (this is from an edit, no doc involved) */
  if (CommDoc || CommDecoding || SendingPost) {
	if (CommState != PrevState) {
	  PrevState = CommState;
	  statDoc = CommDoc ? CommDoc : &NetDoc;
	  if (LoadString (hInst, CommState, szStatBarText, MAXSTATBARTEXT) != 0)
		SetStatbarText (statDoc->hWndFrame, szStatBarText, statDoc, TRUE, TRUE);
	}

	switch (CommState) {
	case ST_NONE:
	  break;

	case ST_ESTABLISH_COMM:
	  if (!sscanf (CommLineIn, "%u", &retcode))
		break;
	  /* check for innd, send 'mode reader' command */
	  /* this is only necessary in unusual cases... */
	  /* ... at least until someone ports INN to NT 8^) */
	  if (strstr (CommLineIn, "InterNetNews") &&
	      !strstr (CommLineIn, "NNRP")) {
	    PutCommLine ("mode reader");
	    break;
	  }
	  
	  if (retcode == 200 || retcode == 201) {	/* was 500 from induced error */
		CommBusy = TRUE;
		do_authinfo = FALSE;
		Authenticated = FALSE;
		if (strlen (NNTPUserName)) {
		  /* We have the AUTHINFO username.  Do we have the password? */
		  if (!strlen (NNTPPasswordEncrypted)) {
			/* Get the news server user password from the user */
			if (DialogBox (hInst, (LPCSTR) "WinVnComm", NetDoc.hDocWnd, (DLGPROC) lpfnWinVnCommDlg)
				&& strlen (NNTPPasswordEncrypted)) {
			  do_authinfo = TRUE;
			}
		  }
		  else {
			do_authinfo = TRUE;
		  }
		}
		if (do_authinfo) {
		  sprintf (mybuf, "AUTHINFO user %s", NNTPUserName);
		  CommState = ST_CHECK_AUTHINFO_USERNAME;
		  PutCommLine (mybuf);
		}
		else {
		  goto End_Authinfo;
		}

	  }
	  else {
		Disconnect ();
		InvalidateRect (NetDoc.hDocWnd, NULL, TRUE);
		MessageBox (NetDoc.hDocWnd, CommLineIn, "Access Problem", MB_OK);
	  }
	  break;

	case ST_CHECK_AUTHINFO_USERNAME:
	  retcode = 0;
	  sscanf (CommLineIn, "%u", &retcode);
	  if (!retcode)
		break;
	  if (retcode >= 500) {
		Disconnect ();
		InvalidateRect (NetDoc.hDocWnd, NULL, TRUE);
		MessageBox (NetDoc.hDocWnd,
		  "Error authorizing your username with the News Server.\n"
		  "\n"
		  "Try an anonymous connection by removing your username/password from the\n"
		  "\"Config->Communications...\" dialog box",
		  "WinVN", MB_OK | MB_ICONHAND);
		break;
	  }
	  MRRDecrypt (NNTPPasswordEncrypted, (unsigned char *) mybuf2, MAXINTERNALLINE);
	  sprintf (mybuf, "AUTHINFO pass %s", mybuf2);
	  CommState = ST_CHECK_AUTHINFO_PASSWORD;
	  PutCommLine (mybuf);
	  break;

	case ST_CHECK_AUTHINFO_PASSWORD:
	  retcode = 0;
	  if (sscanf (CommLineIn, "%u", &retcode) <= 0)
		break;
	  if (retcode < 200 || retcode > 299) {
		Disconnect ();
		InvalidateRect (NetDoc.hDocWnd, NULL, TRUE);
		sprintf (mybuf,
		  "Error authorizing your password with the News Server:\n"
          "\n"
		  "  %s.\n"
          "\n"
		  "Try an anonymous connection by removing your username/password from the\n"
		  "\"Config->Communications...\" dialog box.", CommLineIn);
		MessageBox (NetDoc.hDocWnd, mybuf, "WinVN", MB_OK | MB_ICONHAND);
		break;
	  }
	  else {
		/* Authentication was successful.  Store this fact, and the name under
		 * which the user was authenticated.
		 */
		Authenticated = TRUE;
		strntcpy (AuthenticatedName, NNTPUserName, MAXNNTPSIZE - 1);
	  }
	  goto End_Authinfo;


	case ST_END_AUTHINFO:
	End_Authinfo:;
	  /* allow exit now... */
	  SendMessage (NetDoc.hWndFrame, WM_MYINITMENU, (WPARAM) 0, (LPARAM) 0);

	  /* now check for the XOVER command */
	  CommState = ST_XOVER_CHECK;
	  PutCommLine ("XOVER");
	  break;

	case ST_XOVER_CHECK:
	  retcode = 0;
	  sscanf (CommLineIn, "%u", &retcode);

	  /* 412 == 'not in a newsgroup' */
	  if (retcode == 412 && (force_xhdr == 0 || force_xhdr == HDR_XOVER))    
		xoverp = 1;
	  else						/* 500 == 'command not understood' */
		xoverp = 0;

	  dolist = DoList;
	  if (dolist == ID_DOLIST_ASK - ID_DOLIST_BASE)
		if (MessageBox (NetDoc.hDocWnd, "Request the latest group list from server?\n(This can be time consuming)",
						"Request LIST from server?", MB_ICONQUESTION | MB_YESNO | MB_DEFBUTTON2) == IDNO)
		  dolist = 0;

	  /* may have lost connection while dialog box up... */
	  if (Initializing == INIT_NOT_CONNECTED) {
	  	break;
	  }
	  if (dolist) {
		StartList ();
		did_list = 1;
	  }
	  else {
		did_list = 0;
		CommState = ST_NONE;
		CommBusy = FALSE;
		Initializing = INIT_READY;
		SetStatbarText (CommDoc->hWndFrame, "", CommDoc, TRUE, TRUE);
		InvalidateRect (NetDoc.hDocWnd, NULL, FALSE);
	  }
	  InvalidateRect (NetDoc.hDocWnd, NULL, FALSE);
	  break;

	case ST_LIST_RESP:
	  retcode = 0;
	  sscanf (CommLineIn, "%u", &retcode);
	  if (retcode != 215) {
		check_server_code (retcode);
		break;
	  }

	  CommState = ST_LIST_GROUPLINE;
	  RcvLineCount = 0;
	  break;

	case ST_LIST_GROUPLINE:
	  if (strcmp (CommLineIn, ".") == 0) {
		CommState = ST_NONE;
		CommBusy = FALSE;
		Initializing = INIT_READY;
		SetStatbarText (CommDoc->hWndFrame, "", CommDoc, TRUE, TRUE);
		InvalidateRect (NetDoc.hDocWnd, NULL, FALSE);

		ProcEndList ();
	  }
	  else if (!EnableGroupFilter || MatchFilter (CommLineIn, GroupFilter)) {
		ProcListLine ((unsigned char *) CommLineIn);
	  }
	  break;

	case ST_GROUP_RESP:
	  retcode = 0;
	  sscanf (CommLineIn, "%u", &retcode);
	  switch (retcode) {
	  case 411:
		/* abort the fledgling group window */
		DestroyWindow (CommDoc->hWndFrame);
		CommBusy = FALSE;
		CommState = ST_NONE;
		MessageBox (NetDoc.hDocWnd, "No Such Newsgroup", "Error", MB_OK | MB_ICONHAND);
		return;
		break;
	  case 502:
		/* abort the fledgling group window */
		DestroyWindow (CommDoc->hWndFrame);
		CommBusy = FALSE;
		CommState = ST_NONE;
		MessageBox (NetDoc.hDocWnd, "Restricted Access", "WinVN", MB_OK | MB_ICONHAND);
		return;
		break;
	  default:
		if (check_server_code (retcode))
		  return;
		break;
	  }

	  sscanf (CommLineIn, "%u %lu %lu %lu %s", &retcode, &estnum, &first, &last, group);

	  LockLine (CommDoc->hParentBlock, CommDoc->ParentOffset,
				CommDoc->ParentLineID, &BlockPtr, &LinePtr);
	  GroupDoc = GetGroup(LinePtr);
	  RangePtr = GetRangePtr(GroupDoc);
	  GroupDoc->Determined = TRUE;
#if 0	// TANAKA Goh (newsrc patch)
	  GroupDoc->ServerLast = last;
	  GroupDoc->ServerFirst = first;
#else	// TANAKA Goh (newsrc patch)
	  if (estnum) {
		GroupDoc->ServerLast = last;
		GroupDoc->ServerFirst = first;
	  }
#endif	// TANAKA Goh (newsrc patch)
	  GroupDoc->ServerEstNum = estnum;

	  /* we don't want to grab *that* many! */
	  if (estnum >= article_threshold) {
		if (!ShowUnreadOnly) {
		  arts_to_retrieve = estnum;
		  if (GroupDoc->nRanges)
		    est_num_unread = CalcNumUnread(GroupDoc);
		  else
			est_num_unread = arts_to_retrieve;

		  DlgStatus = DialogBox (hInst, (LPCSTR) "THRESHOLD", CommDoc->hDocWnd, (DLGPROC) lpfnWinVnThresholdDlg);
		  if (CommDoc && (DlgStatus == FALSE)) {
			DestroyWindow (CommDoc->hWndFrame);
			CommBusy = FALSE;
			CommState = ST_NONE;
			GroupDoc->ServerFirst = GroupDoc->ServerLast;
			GroupDoc->ServerEstNum = estnum;
			return;
		  }
		}
		else
		  arts_to_retrieve = ID_THRESHOLD_UNREAD;

		if ((DlgStatus == TRUE) &&
		    (last >= arts_to_retrieve) &&
		    ((last - arts_to_retrieve) > first)) {
		  first = (last - arts_to_retrieve) + 1;
		}
		else if (DlgStatus == ID_THRESHOLD_ALL)		/* they clicked 'all of them' */
		  arts_to_retrieve = estnum;
		/* added by jlg */
		else if ((DlgStatus == ID_THRESHOLD_UNREAD)		/* they clicked 'unseen' */
				 ||(ShowUnreadOnly)) {
		  if (GroupDoc->nRanges) {

      if (RangePtr[GroupDoc->nRanges - 1].Last <= last)
        first = max (first, (long) RangePtr[0].Last + 1);

			arts_to_retrieve = (last - first) + 1;
			if (arts_to_retrieve < min_to_retrieve) {
			  arts_to_retrieve = min_to_retrieve;
			  if (last < min_to_retrieve)
			     first = 1;
			  else
			     first = last - (min_to_retrieve - 1);
			}
			if (arts_to_retrieve == 0) {
			  /* abort the fledgling group window */
			  DestroyWindow (CommDoc->hWndFrame);
			  CommBusy = FALSE;
			  CommState = ST_NONE;
			  MessageBox (NetDoc.hDocWnd, "No Articles to Retrieve", "WinVN", MB_OK | MB_ICONHAND);
			  return;
			}
		  }
		  else
			arts_to_retrieve = estnum;
		}
	  }
	  else {
		if (estnum > 0)
		  arts_to_retrieve = estnum;
		else {
		  /* abort the fledgling group window */
		  DestroyWindow (CommDoc->hWndFrame);
		  CommBusy = FALSE;
		  CommState = ST_NONE;
		  if (GroupDoc->ServerLast) {
			GroupDoc->ServerFirst = GroupDoc->ServerLast;
		  }
		  GroupDoc->ServerEstNum = 0;
		  MessageBox (NetDoc.hDocWnd, "Empty Newsgroup", "WinVN", MB_OK | MB_ICONHAND);
		  return;
		}
	  }

	  CommDoc->TotalLines = (unsigned int) arts_to_retrieve;

	  if (arts_to_retrieve > 0) {
		header_handle =
		  GlobalAlloc (GMEM_MOVEABLE, (long)
					   ((sizeof (TypHeader)) *
						arts_to_retrieve) + sizeof (thread_array *));

		/* allocate space for the header_array index table */
		thread_handle =
		  GlobalAlloc (GMEM_MOVEABLE,
					   (long) ((sizeof (long)) * arts_to_retrieve));

		GroupDoc->header_handle = header_handle;
		GroupDoc->thread_handle = thread_handle;

	  }

	  /* stick nulls and 0's, etc.. in case display code get mis-threaded */
	  initialize_header_array (header_handle, thread_handle, arts_to_retrieve);

	  GroupDoc = GetGroup(LinePtr);
	  GroupDoc->ServerEstNum = estnum;
	  GroupDoc->ServerFirst = first;
	  GroupDoc->Threaded = xoverp || threadp;
	  GlobalUnlock (BlockPtr->hCurBlock);

	  if (xoverp) {
		mylen = sprintf (mybuf, "XOVER %ld-%ld", first, last);
		CommState = ST_XOVER_START;
		PutCommLine (mybuf);
	  }
	  else {
		mylen = sprintf (mybuf, "XHDR from %ld-%ld", first, last);
		CommState = ST_XHDR_FROM_START;
		PutCommLine (mybuf);
	  }

	  break;

	  /* use XOVER if its available */
	case ST_XOVER_START:
	  retcode = 0;
	  ClearLatestInfo();
		
	  sscanf (CommLineIn, "%d", &retcode);
	  if (retcode == 224) {
			CommState = ST_XOVER_DATA;
			CommDoc->ActiveLines = 0;
			if (bEnableArticleAction) {
				aa.ReadActions(CurrentGroup);      /* Get article action list */
				if (aa.NeedNgroupHeader()) {       /* Find out if we need newsgroups */
					total_xhdrs = 2;
		        }
				else {
					total_xhdrs = 1;
				}
	        }
	  }
	  else {
			mylen = sprintf (mybuf, "XHDR from %ld-%ld", first, last);
			CommState = ST_XHDR_FROM_START;
			PutCommLine (mybuf);
	  }
	  break;


	case ST_XOVER_DATA:
    if((strcmp(CommLineIn, ".") == 0) &&
       (LockLine(CommDoc->hParentBlock, CommDoc->ParentOffset,
                 CommDoc->ParentLineID, &BlockPtr, &LinePtr)))
    {
      GroupDoc = GetGroup(LinePtr);
      GroupDoc->total_headers = CommDoc->ActiveLines;
      first = GroupDoc->ServerFirst;      
      last = GroupDoc->ServerLast;
      GlobalUnlock (BlockPtr->hCurBlock);
      

     if (bEnableArticleAction){
         aa.ReadActions(CurrentGroup);            /* Get article action list */
         if (aa.NeedNgroupHeader()) {             /* Find out if we need newsgroups */
			/* Now ask for the newsgroups */
		    CommDoc->ActiveLines = 0;
			mylen = sprintf (mybuf, "XHDR newsgroups %ld-%ld", first, last);
			CommState = ST_XHDR_NEWSGROUPS_START;
			PutCommLine (mybuf);
    	    }
		 else
			{
			  CommState = ST_IN_GROUP;
			  CommBusy = FALSE;
			  finish_header_retrieval();
		      SetStatbarText (CommDoc->hWndFrame,  "", CommDoc, TRUE, TRUE);
			}
		}
	 else {
		 CommState = ST_IN_GROUP;
		 CommBusy = FALSE;
		 finish_header_retrieval();
		 SetStatbarText (CommDoc->hWndFrame,  "", CommDoc, TRUE, TRUE);
	     }
    }
	else if (*CommLineIn) {	/* avoid blank XOVER lines (peterk@borland.com) */
		char *this_hop, *next_hop;
		char *best_reference;

		LockLine (CommDoc->hParentBlock, CommDoc->ParentOffset,
				  CommDoc->ParentLineID, &BlockPtr, &LinePtr);
		GroupDoc = GetGroup(LinePtr);
		header_handle = GroupDoc->header_handle;
		thread_handle = GroupDoc->thread_handle;
		GlobalUnlock (BlockPtr->hCurBlock);

		/* Lock the header data */
		headers = lock_headers (header_handle, thread_handle);
		header = header_elt (headers, CommDoc->ActiveLines);
		this_hop = CommLineIn;

		/* article number */
		next_hop = strchr (this_hop, '\t');
		*(next_hop++) = (char) NULL;
		header->number = atol (this_hop);

		/* subject */
		this_hop = next_hop;
		next_hop = strchr (this_hop, '\t');
		*(next_hop++) = (char) NULL;

		mylstrncpy (header->subject, this_hop, HEADER_SUBJECT_LENGTH);
//		CommDoc->LongestLine = max (CommDoc->LongestLine,
//									ARTICLE_SUBJECT_OFFSET +
//									(unsigned) lstrlen (header->subject));
		CommDoc->LongestLine = max (CommDoc->LongestLine,
									ArtListSeparator3 / CharWidth +
									(unsigned) lstrlen (header->subject));

		/* author */
		this_hop = next_hop;
		next_hop = strchr (this_hop, '\t');
		*(next_hop++) = (char) NULL;

		ParseAddress (this_hop,
					  AddressString, MAXDIALOGSTRING,
					  NameString, MAXDIALOGSTRING);

		if (FullNameFrom)
		  mylstrncpy (header->from, NameString, HEADER_FROM_LENGTH);
		else
		  mylstrncpy (header->from, this_hop, HEADER_FROM_LENGTH);

        mylstrncpy(header->email, AddressString, HEADER_EMAIL_LENGTH);

		/* date */
		this_hop = next_hop;
		next_hop = strchr (this_hop, '\t');
		*(next_hop++) = (char) NULL;

		header->date = parse_usenet_date (this_hop);

		/* message-id */
		this_hop = next_hop;
		next_hop = strchr (this_hop, '\t');
		*(next_hop++) = (char) NULL;

		copy_message_id_with_frob (header->message_id, this_hop+1);

		/* references */
		this_hop = next_hop;
		next_hop = strchr (this_hop, '\t');
		*(next_hop++) = (char) NULL;

		build_ref_list(header->frob_ref_list, this_hop);
		best_reference = get_best_reference (this_hop);
		if (best_reference) {
		  copy_message_id_with_frob (header->best_ref, best_reference);
		}

		/* bytes (ignored) */
		this_hop = next_hop;
		next_hop = strchr (this_hop, '\t');
		*(next_hop++) = (char) NULL;

		/* lines (last one doesn't have to have the tab */
		this_hop = next_hop;
		header->lines = atoi (this_hop);

		/* set other header fields */
		header->Selected = FALSE;
		header->ArtDoc = (TypDoc *) NULL;
		header->Seen = WasArtSeen (header->number, GroupDoc);

		unlock_headers (header_handle, thread_handle);

		CommDoc->ActiveLines++;

		update_window_title (CommDoc->hWndFrame, group,
							 RcvLineCount++,
							 CommDoc->TotalLines * total_xhdrs);
	  }

	  break;


	  /* The next few cases handle retrieval of XHDR information for display */
	  /* in the group window.  If you change the number of XHDR's retrieved */
	  /* (such as adding 'XHDR References' back into the state machine), you */
	  /* need to reflect that change in the variable total_xhdrs. */

	  /* the current flow is FROM -> DATE -> LINES -> SUBJECT -> NEWSGROUPS */
	  /* (threadp) FROM -> DATE -> LINES -> REF -> ID -> SUBJECT -> NEWSGROUPS */
	  /* The NEWSGROUPS state is skipped if there are no NEWSGROUPS kill filters

	  /* this will now be done dynamically, depending on the state of */
	  /* the 'threadp' variable */

	case ST_XHDR_FROM_START:
	  retcode = 0;
	  sscanf (CommLineIn, "%d", &retcode);
	  /* we do this here to allow mid-session change-of-mind */
       total_xhdrs = threadp ? 6 : 4;
	   if (bEnableArticleAction) {
		   aa.ReadActions(CurrentGroup);            /* Get article action list */
           if (aa.NeedNgroupHeader()) {             /* Find out if we need newsgroups */
	          total_xhdrs++;
		   }
	   }

	  if (retcode < 100)
		break;
	  CommState = ST_XHDR_FROM_DATA;
	  CommDoc->ActiveLines = 0;
	  break;

	case ST_XHDR_FROM_DATA:
	  if (strcmp (CommLineIn, ".") == 0) {
		LockLine (CommDoc->hParentBlock, CommDoc->ParentOffset,
				  CommDoc->ParentLineID, &BlockPtr, &LinePtr);

		GroupDoc = GetGroup(LinePtr);
		GroupDoc->total_headers = CommDoc->ActiveLines;

		first = GroupDoc->ServerFirst;
		last = GroupDoc->ServerLast;

		GlobalUnlock (BlockPtr->hCurBlock);
		CommDoc->ActiveLines = 0;
		ClearLatestInfo();

		/* Now ask for the date lines */
		mylen = sprintf (mybuf, "XHDR date %ld-%ld", first, last);
		CommState = ST_XHDR_DATE_START;
		PutCommLine (mybuf);
	  }
	  else {
		/*      char neat_from [80]; */
		/* Access the Group struct, get HANDLE for header data */
		LockLine (CommDoc->hParentBlock, CommDoc->ParentOffset,
				  CommDoc->ParentLineID, &BlockPtr, &LinePtr);

		GroupDoc = GetGroup(LinePtr);
		header_handle = GroupDoc->header_handle;
		thread_handle = GroupDoc->thread_handle;
		GlobalUnlock (BlockPtr->hCurBlock);

		/* Lock the header data */
		headers = lock_headers (header_handle, thread_handle);
		sscanf (CommLineIn, "%ld", &artnum);
		header = header_elt (headers, CommDoc->ActiveLines);
		header->number = artnum;
		header->Selected=FALSE;
		header->ArtDoc = (TypDoc *) NULL;
		header->Seen = WasArtSeen (artnum, GetGroup(LinePtr));

		/* now use some of our nice formatting of email addresses */
		ParseAddress (get_xhdr_line (CommLineIn),
					  AddressString, MAXDIALOGSTRING,
					  NameString, MAXDIALOGSTRING);

		/* copy that into headers[].from */
		if (FullNameFrom)
		  mylstrncpy (header->from, NameString, HEADER_FROM_LENGTH);
		else
		  mylstrncpy (header->from, AddressString, HEADER_FROM_LENGTH);

        mylstrncpy(header->email, AddressString, HEADER_FROM_LENGTH);

		unlock_headers (header_handle, thread_handle);
		CommDoc->ActiveLines++;
		update_window_title (CommDoc->hWndFrame, group,
							 RcvLineCount++,
							 CommDoc->TotalLines * total_xhdrs);
	  }

	  break;

	case ST_XHDR_DATE_START:
	  retcode = 0;
	  sscanf (CommLineIn, "%d", &retcode);
	  if (check_server_code (retcode))
		break;
	  CommState = ST_XHDR_DATE_DATA;
	  CommDoc->ActiveLines = 0;
	  break;

	case ST_XHDR_DATE_DATA:
	  if ((strcmp (CommLineIn, ".") == 0) &&
		  LockLine (CommDoc->hParentBlock, CommDoc->ParentOffset,
				    CommDoc->ParentLineID, &BlockPtr, &LinePtr))	 {

		GroupDoc = GetGroup(LinePtr);
		GroupDoc->total_headers = CommDoc->ActiveLines;
			
		first = GroupDoc->ServerFirst;
		last = GroupDoc->ServerLast;
		GlobalUnlock (BlockPtr->hCurBlock);
		CommDoc->ActiveLines = 0;

			/* Now ask for the #of lines */
		mylen = sprintf (mybuf, "XHDR lines %ld-%ld", first, last);
		CommState = ST_XHDR_LINES_START;
		PutCommLine (mybuf);
	  }
	  else {

		/* Access the Group struct, get HANDLE for header data */
		if (LockLine (CommDoc->hParentBlock, CommDoc->ParentOffset,
				  	  CommDoc->ParentLineID, &BlockPtr, &LinePtr)) {
		  GroupDoc = GetGroup(LinePtr);
		  header_handle = GroupDoc->header_handle;
		  thread_handle = GroupDoc->thread_handle;
		  GlobalUnlock (BlockPtr->hCurBlock);

		  /* Lock the header data */
		  headers = lock_headers (header_handle, thread_handle);
	  	  syncnum = sync_artnum (atol (CommLineIn),
		  			     (header_elt (headers, CommDoc->ActiveLines))->number,
							     headers,
							     GroupDoc);
		  if (syncnum >= 0)
		  {
		   (header_elt (headers, syncnum))->date
			  = parse_usenet_date (get_xhdr_line (CommLineIn));          
		  }
		  CommDoc->ActiveLines++;
		  unlock_headers (header_handle, thread_handle);
		  
	  	  update_window_title (CommDoc->hWndFrame, group,
							   RcvLineCount++,
							   CommDoc->TotalLines * total_xhdrs);
		}
	  }

	  break;

	case ST_XHDR_LINES_START:
	  retcode = 0;
	  sscanf (CommLineIn, "%d", &retcode);
	  if (check_server_code (retcode))
		break;
	  CommState = ST_XHDR_LINES_DATA;
	  CommDoc->ActiveLines = 0;
	  break;

	case ST_XHDR_LINES_DATA:
	  if ((strcmp (CommLineIn, ".") == 0) &&
		  LockLine (CommDoc->hParentBlock, CommDoc->ParentOffset,
				    CommDoc->ParentLineID, &BlockPtr, &LinePtr))  {

		GroupDoc = GetGroup(LinePtr);
		GroupDoc->total_headers = CommDoc->ActiveLines;

		first = GroupDoc->ServerFirst;
		last = GroupDoc->ServerLast;

		GlobalUnlock (BlockPtr->hCurBlock);
		CommDoc->ActiveLines = 0;

		/* Check for threading option, if enabled, go to REF & ID */
		/* states first */

		if (threadp) {
		  CommState = ST_XHDR_REF_START;
		  mylen = sprintf (mybuf, "XHDR references %ld-%ld", first, last);
		  PutCommLine (mybuf);
		}
		else {
		  CommState = ST_XHDR_SUBJECT_START;
		  mylen = sprintf (mybuf, "XHDR subject %ld-%ld", first, last);
		  PutCommLine (mybuf);
		}
	  }

	  else {

		/* Access the Group struct, get HANDLE for header data */
		if (LockLine (CommDoc->hParentBlock, CommDoc->ParentOffset,
				  	  CommDoc->ParentLineID, &BlockPtr, &LinePtr)) {
		  GroupDoc = GetGroup(LinePtr);
		  header_handle = GroupDoc->header_handle;
		  thread_handle = GroupDoc->thread_handle;
	  	  GlobalUnlock (BlockPtr->hCurBlock);

		  /* Lock the header data */
		  headers = lock_headers (header_handle, thread_handle);

		  syncnum = sync_artnum (atol (CommLineIn),
					     (header_elt (headers, CommDoc->ActiveLines))->number,
							     headers,
							     GroupDoc);
		  if (syncnum >= 0)
		    sscanf (CommLineIn, "%ld %Fd", &artnum, &((header_elt (headers, syncnum))->lines));

		  unlock_headers (header_handle, thread_handle);
		  CommDoc->ActiveLines++;
		  update_window_title (CommDoc->hWndFrame, group,
							   RcvLineCount++,
							   CommDoc->TotalLines * total_xhdrs);
		}
	  }

	  break;

	case ST_XHDR_REF_START:
	  retcode = 0;
	  sscanf (CommLineIn, "%d", &retcode);
	  if (check_server_code (retcode))
		break;
	  CommState = ST_XHDR_REF_DATA;
	  CommDoc->ActiveLines = 0;
	  break;

	case ST_XHDR_REF_DATA:
	  if ((strcmp (CommLineIn, ".") == 0) &&
		  (LockLine (CommDoc->hParentBlock, CommDoc->ParentOffset,
				     CommDoc->ParentLineID, &BlockPtr, &LinePtr))) {

		GroupDoc = GetGroup(LinePtr);
		GroupDoc->total_headers = CommDoc->ActiveLines;
		first = GroupDoc->ServerFirst;
		last = GroupDoc->ServerLast;
		GlobalUnlock (BlockPtr->hCurBlock);
		CommDoc->ActiveLines = 0;

		/* Now ask for the message-id lines */
		mylen = sprintf (mybuf, "XHDR message-id %ld-%ld", first, last);
		CommState = ST_XHDR_MID_START;
		PutCommLine (mybuf);
	  }
	  else {
		header_p header;
		char temp_frob_ref_list[HEADER_FROBREFLIST_LENGTH];
		char far *refer;

		/* Access the Group struct, get HANDLE for header data */
		if (LockLine (CommDoc->hParentBlock, CommDoc->ParentOffset,
				      CommDoc->ParentLineID, &BlockPtr, &LinePtr)){
			GroupDoc = GetGroup(LinePtr);
			header_handle = GroupDoc->header_handle;
			thread_handle = GroupDoc->thread_handle;
			GlobalUnlock (BlockPtr->hCurBlock);

			/* Lock the header data */
			headers = lock_headers (header_handle, thread_handle);
			header = header_elt (headers, CommDoc->ActiveLines);

			/* for now, we only pay attention to first (whole) referral */
			refer = get_xhdr_line (CommLineIn);
			build_ref_list(temp_frob_ref_list, refer);
			refer = get_best_reference (refer);

			if (refer) {
		  	/* Patch to check for bad info from server JD 6/19/93 */
		  	syncnum = sync_artnum (atol (CommLineIn),
									 (header_elt (headers, CommDoc->ActiveLines))->number,
								 				headers, GroupDoc);
		  	if (syncnum >= 0) {
        header = header_elt (headers, syncnum);
        strcpy(header->frob_ref_list, temp_frob_ref_list);
				copy_message_id_with_frob (header->best_ref, refer);
		  	}
			}

			unlock_headers (header_handle, thread_handle);

			CommDoc->ActiveLines++;
			update_window_title (CommDoc->hWndFrame, group,
							 	RcvLineCount++,
							 	CommDoc->TotalLines * total_xhdrs);
			}

	  }

	  break;


	case ST_XHDR_MID_START:
	  retcode = 0;
	  sscanf (CommLineIn, "%d", &retcode);
	  if (check_server_code (retcode))
		break;
	  CommState = ST_XHDR_MID_DATA;
	  CommDoc->ActiveLines = 0;
	  break;

	case ST_XHDR_MID_DATA:
	  if ((strcmp (CommLineIn, ".") == 0) &&
		  (LockLine (CommDoc->hParentBlock, CommDoc->ParentOffset,
		             CommDoc->ParentLineID, &BlockPtr, &LinePtr))) {

		GroupDoc = GetGroup(LinePtr);
		GroupDoc->total_headers = CommDoc->ActiveLines;
		first = GroupDoc->ServerFirst;
		last = GroupDoc->ServerLast;
		GlobalUnlock (BlockPtr->hCurBlock);

		/* Now ask for the subject lines */
		CommDoc->ActiveLines = 0;
		mylen = sprintf (mybuf, "XHDR subject %ld-%ld", first, last);
		CommState = ST_XHDR_SUBJECT_START;
		PutCommLine (mybuf);
	  }
	  else {
		/* Access the Group struct, get HANDLE for header data */
		if (LockLine (CommDoc->hParentBlock, CommDoc->ParentOffset,
				      CommDoc->ParentLineID, &BlockPtr, &LinePtr)) {

			GroupDoc = GetGroup(LinePtr);
			header_handle = GroupDoc->header_handle;
			thread_handle = GroupDoc->thread_handle;
			GlobalUnlock (BlockPtr->hCurBlock);

		/* Lock the header data */
			headers = lock_headers (header_handle, thread_handle);
			syncnum = sync_artnum (atol (CommLineIn),
					   	(header_elt (headers, CommDoc->ActiveLines))->number,
							   	headers,
							   	GroupDoc);
			if (syncnum >= 0) {
		 	 copy_message_id_with_frob ((header_elt (headers, syncnum))->message_id,
										 (char far *) (get_xhdr_line (CommLineIn)+1));
			}
			unlock_headers (header_handle, thread_handle);

			CommDoc->ActiveLines++;
			update_window_title (CommDoc->hWndFrame, group,
								 RcvLineCount++,
								 CommDoc->TotalLines * total_xhdrs);
		  }

	  	}

	  break;


	case ST_XHDR_SUBJECT_START:
	  retcode = 0;
	  sscanf (CommLineIn, "%d", &retcode);
	  if (check_server_code (retcode))
		break;
	  CommState = ST_XHDR_SUBJECT_DATA;
	  break;

	case ST_XHDR_SUBJECT_DATA:
    if((strcmp(CommLineIn, ".") == 0) &&
       (LockLine(CommDoc->hParentBlock, CommDoc->ParentOffset,
                 CommDoc->ParentLineID, &BlockPtr, &LinePtr)))
    {
      GroupDoc = GetGroup(LinePtr);
      GroupDoc->total_headers = CommDoc->ActiveLines;
      first = GroupDoc->ServerFirst;
      last = GroupDoc->ServerLast;
      GlobalUnlock (BlockPtr->hCurBlock);  

	  if (bEnableArticleAction){
         aa.ReadActions(CurrentGroup);            /* Get article action list */
         if (aa.NeedNgroupHeader()) {             /* Find out if we need newsgroups */
			/* Now ask for the newsgroups */
			CommDoc->ActiveLines = 0;
			mylen = sprintf (mybuf, "XHDR newsgroups %ld-%ld", first, last);
			CommState = ST_XHDR_NEWSGROUPS_START;
			PutCommLine (mybuf);
		    }
		     else {
				CommState = ST_IN_GROUP;
				CommBusy = FALSE;
				finish_header_retrieval();
				SetStatbarText (CommDoc->hWndFrame, "", CommDoc, TRUE, TRUE);
 		    }
      }
      else {
			CommState = ST_IN_GROUP;
			CommBusy = FALSE;
			finish_header_retrieval();
			SetStatbarText (CommDoc->hWndFrame, "", CommDoc, TRUE, TRUE);
	  }
    }
	  else {

		artnum = 0;
		sscanf (CommLineIn, "%ld", &artnum);
		if (artnum &&
		    (LockLine (CommDoc->hParentBlock, CommDoc->ParentOffset,
					   CommDoc->ParentLineID, &BlockPtr, &LinePtr))) {

		  GroupDoc = GetGroup(LinePtr);
		  header_handle = GroupDoc->header_handle;
		  thread_handle = GroupDoc->thread_handle;
			GlobalUnlock (BlockPtr->hCurBlock);

		/* Lock the header data */
		  headers = lock_headers (header_handle, thread_handle);

		  /* update the seen thing. */
		  syncnum = sync_artnum (atol (CommLineIn),
					   (header_elt (headers, CommDoc->ActiveLines))->number,
								 headers,
								 GroupDoc);
      if(syncnum >= 0)
      {
        header = header_elt (headers, syncnum);
        mylstrncpy (header->subject,
                    get_xhdr_line (CommLineIn), HEADER_SUBJECT_LENGTH);
//        CommDoc->LongestLine = max (CommDoc->LongestLine,
//                                    ARTICLE_SUBJECT_OFFSET +
//                                    (unsigned) lstrlen (header->subject));
   		    CommDoc->LongestLine = max (CommDoc->LongestLine,
                    									ArtListSeparator3 / CharWidth +
                  									  (unsigned) lstrlen (header->subject));
        CommDoc->ActiveLines++;
      }
		  UnlockLine (BlockPtr, LinePtr, &(CommDoc->hParentBlock),
					  &(CommDoc->ParentOffset), &(CommDoc->ParentLineID));

		  unlock_headers (header_handle, thread_handle);
		  update_window_title (CommDoc->hWndFrame, group,
							   RcvLineCount++,
							   CommDoc->TotalLines * total_xhdrs);

		}
	  }

	  break;

	case ST_XHDR_NEWSGROUPS_START:
    retcode = 0;
    sscanf(CommLineIn, "%d", &retcode);
    if(check_server_code(retcode))
      break;
    CommState = ST_XHDR_NEWSGROUPS_DATA;
    CommDoc->ActiveLines = 0;
    break;

	case ST_XHDR_NEWSGROUPS_DATA:
    if (strcmp (CommLineIn, ".") == 0)
    {
      CommState = ST_IN_GROUP;
      CommBusy = FALSE;
      finish_header_retrieval();
      SetStatbarText (CommDoc->hWndFrame, "", CommDoc, TRUE, TRUE);
    }
    else
    {
      /* Access the Group struct, get HANDLE for header data */
      if(LockLine(CommDoc->hParentBlock, CommDoc->ParentOffset,
         CommDoc->ParentLineID, &BlockPtr, &LinePtr))
      {
        GroupDoc = GetGroup(LinePtr);
        header_handle = GroupDoc->header_handle;
        thread_handle = GroupDoc->thread_handle;
        GlobalUnlock(BlockPtr->hCurBlock);

        /* Lock the header data */
        headers = lock_headers(header_handle, thread_handle);
        syncnum = sync_artnum(atol(CommLineIn),
                              (header_elt(headers, CommDoc->ActiveLines))->number,
                              headers, GroupDoc);
        if(syncnum >= 0)
        {
          header = header_elt (headers, syncnum);
          mylstrncpy(header->newsgroups,
                     get_xhdr_line(CommLineIn), HEADER_NEWSGROUPS_LENGTH);
          CommDoc->ActiveLines++;
        }
        unlock_headers(header_handle, thread_handle);

        update_window_title(CommDoc->hWndFrame, group,
                            RcvLineCount++, CommDoc->TotalLines * total_xhdrs);
      }
    }
    break;

	case ST_IN_GROUP:
	  break;

	case ST_ARTICLE_RESP:
	  retcode = 0;
	  sscanf (CommLineIn, "%d", &retcode);
	  if (check_server_code (retcode)) {
		if (CommDoc->hDocWnd)
		  DestroyWindow (CommDoc->hWndFrame);
		break;
	  }
	  ClearLatestInfo();
	  CommState = ST_REC_ARTICLE_HEADER;
	  UsingMIME = FALSE;		/* new article, init to no MIME */
	  break;

	case ST_REC_ARTICLE_HEADER:
	  if (IsBlankStr (CommLineIn)) {	/* headers end in blank line */
		CommState = ST_REC_ARTICLE;
		CommDoc->HeaderLines = CommDoc->ActiveLines;  //Heirich 19960728
	  }
	  if (!TrimHeader (CommLineIn))
	  {
  		WrapAddCommLineToDoc (CommLineIn);
        StoreHeaderInfo(CommLineIn);
	  }
	  break;

	case ST_REC_ARTICLE:
	  if (strcmp (CommLineIn, ".") != 0) {
		WrapAddCommLineToDoc (CommLineIn);
	  }
	  else {
	    if (CommDoc) {			/* Handle aborted condition JD 10/26/95 */
			/* article receive complete */
			CommState = ST_IN_GROUP;
			CommBusy = FALSE;

			if (CommDecoding) {
		 	 SendMessage (currentCoded->hParentWnd, (UINT) WM_COMMAND,
						   (WPARAM) ID_ARTICLE_RETRIEVE_COMPLETE, 0L);
		 	 break;
			}
			else {
		  	SendMessage (CommDoc->hWndFrame, (UINT) WM_COMMAND,
						   (WPARAM) ID_ARTICLE_RETRIEVE_COMPLETE, 0L);
			}

			if ((CommDoc->ParentDoc) &&
				(LockLine (CommDoc->ParentDoc->hParentBlock,
						  CommDoc->ParentDoc->ParentOffset,
						  CommDoc->ParentDoc->ParentLineID,
						  &BlockPtr, &LinePtr))) {

				GroupDoc = GetGroup(LinePtr);
				header_handle = GroupDoc->header_handle;
				thread_handle = GroupDoc->thread_handle;
				headers = lock_headers (header_handle, thread_handle);
				lpsz = (char far *) ((header_elt (headers, CommDoc->LastSeenLineID))->subject);
				unlock_headers (header_handle, thread_handle);

				SetStatbarText (CommDoc->hWndFrame, "", CommDoc, TRUE, TRUE);
				mylstrncpy (group, lpsz, MAXGROUPNAME);
				sprintf (mybuf, "%s (%u lines)", group, CommDoc->TotalLines);
				SetWindowText (CommDoc->hWndFrame, mybuf);
				InvalidateRect (CommDoc->hDocWnd, NULL, FALSE);
				GlobalUnlock (BlockPtr->hCurBlock);

			/* Skip to the first line of the text of the article
			 * and make sure it's visible on the screen.  This is
			 * so that the user doesn't have to have the first
			 * screen filled with a lengthy, worthless header.
			 *
			 * and save number of header lines (on display)
			 * for later (Bretherton)
			 */
				if (TopOfDoc (CommDoc, &BlockPtr, &LinePtr) &&
				    ScrollPastHeaders) {
		 		  found = FALSE;
		 		  do {
					lpsz = ((char far *) LinePtr + sizeof (TypLine) + sizeof (TypText));
					if (IsBlankStr (lpsz)) {
				 	 found = TRUE;
				  	CommDoc->HeaderLines = WhatLine (BlockPtr, LinePtr);
				 	 break;
					}
					if (!NextLine (&BlockPtr, &LinePtr))
			  		break;
			  	}
			  	while (!found);
			 	 NextLine (&BlockPtr, &LinePtr);

		 	 /* If the line is in the last screen's worth of lines, back
		 	  * up the pointer so it points to the first line of the last
		 	  * screen.
		  	 */
			  	if (found && CommDoc->TotalLines > CommDoc->ScYLines &&
				 	 !CommDoc->TopScLineID)
					AdjustTopSc (BlockPtr, LinePtr);

			  	UnlockLine (BlockPtr, LinePtr, &hBlock, &Offset, &MyLineID);
				}
	  		}
		  }
	  	}
	  break;

	case ST_POST_WAIT_PERMISSION:

	  /*      WndPost = getWndEdit(WndPosts,CommWnd,MAXPOSTWNDS) ; */
	  /*      found = (WndPost != NULL) ; */

	  retcode = 0;
	  sscanf (CommLineIn, "%u", &retcode);

	  SetStatbarText (NetDoc.hWndFrame, "", &NetDoc, TRUE, TRUE);
	  InvalidateRect (NetDoc.hDocWnd, NULL, TRUE);
	  if (retcode == 340) {
		DoSend (CONTINUE);
	  }
	  else {
		check_server_code (retcode);
		AbortSendPost (ComposeWnd->hWnd);
		DoSend (CONTINUE);		/* allow any sendmail to continue */
	  }
	  break;

	case ST_POST_WAIT_END:

	  /*      WndPost = getWndEdit(WndPosts,CommWnd,MAXPOSTWNDS) ; */
	  /*      found = (WndPost != NULL) ; */

	  /* no check for failure to find posting documents */
	  retcode = 0;
	  sscanf (CommLineIn, "%d", &retcode);
	  if (retcode == 240) {
		DoSend (CONTINUE);
	  }
	  else if (check_server_code (retcode)) {
		/* cut down on winvn mailing list torture */
		if (strcmp (CommLineIn,
					"441 Article not posted -- more included text than new text") == 0) {
		  MessageBox (NetDoc.hDocWnd,
					  "Your news server has rejected\n"
					  "the article because it contains\n"
					  "more quoted lines than new.\n"
					  "You can either:\n"
					  "1) Rephrase your article with fewer\n"
					  "       quotations\n"
					  "2) Use a character other than '>' to\n"
					  "       indicate quoted passages, or\n"
					  "3) send email to 'usenet' on your news\n"
					  "       server and ask them to rebuild\n"
					  "       INN with CHECK_INCLUDED_TEXT\n"
					  "       set to DONT.",
					  "WinVN", MB_OK | MB_ICONHAND);
		}
		
		/*
		 * if we have an error, and did not lose the connection, abort send.
		 * (if connection was lost the send was already aborted)
		 */

		if (Initializing != INIT_NOT_CONNECTED) {
		  AbortSendPost (ComposeWnd->hWnd);
		  DoSend (CONTINUE);	/* allow any sendmail to continue */
		}
	  }
	  break;

	  /* the following code is for an MRR-hacked nntp server */

	case ST_GROUP_REJOIN:
	  CommState = ST_ARTICLE_RESP;
	  break;
	}
  }
}

BOOL
isLineQuotation (char *textptr)
{
  char *loc;
  loc = (char*) memchr (textptr, QuoteLineInd, 2);
  if (!loc)
	loc = (char*) memchr (textptr, '|', 2);
  if (!loc)
	loc = (char*) memchr (textptr, ':', 2);
  return (loc != NULL);
}


/*-- function AddCommLineToDoc ---------------------------------------
 *  Adds the given line to the comm doc, wrapping if necessary
 *  uses AddCommLineToDocHelp
 */
void
AddCommLineToDoc (char *line)
{
  TypLine far *LinePtr;
  TypBlock far *BlockPtr;
  char *cptr, *cdest;
  int mylen;
  unsigned int col, maxlen;
  char artline[MAXHEADERLINE];
  int PercentDone;

  if (CommState == ST_REC_ARTICLE_HEADER) {
  	maxlen = MAXHEADERLINE;
  } else {
  	maxlen = MAXINTERNALLINE;
  }

  /* Copy this line into an image of a textblock line,
   * expanding tabs.
   */
  cptr = line;
  cdest = artline + sizeof (TypLine) + sizeof (TypText);
  for (col = 0;
  *cptr && col < (maxlen - 3 * sizeof (TypLine) - sizeof (TypText));
	   cptr++) {
	if (*cptr == '\t') {
	  do {
		*(cdest++) = ' ';
	  }
	  while (++col & 7);
	}
	else {
	  *(cdest++) = *cptr;
	  col++;
	}
  }
  *(cdest++) = '\0';

  ((TypLine *) artline)->LineID = NextLineID++;
  if (LockLine (CommDoc->hCurAddBlock, CommDoc->AddOffset, 
                CommDoc->AddLineID, &BlockPtr, &LinePtr)) {
  	mylen = (cdest - artline) + sizeof (int);

  // changed by Holger.Liebig@mch.sni.de to have correct
  // allignment on MIPS processors.
  // was:  mylen += mylen % 2;
  	if (mylen % sizeof(int)){
    	 mylen += sizeof(int) - (mylen % sizeof(int));
  	}

  	((TypText *) (artline + sizeof (TypLine)))->NameLen =
		(cdest - 1) - (artline + sizeof (TypLine) + sizeof (TypText));
  	((TypLine *) artline)->length = mylen;
  	((TypLine *) artline)->active = TRUE;
  	*((int *) (artline + mylen - sizeof (int))) = mylen;
  	AddLine ((TypLine *) artline, &BlockPtr, &LinePtr);
  	CommDoc->LongestLine = max (CommDoc->LongestLine, (unsigned int) col);
  	CommDoc->ActiveLines++;
  	UnlockLine (BlockPtr, LinePtr, &(CommDoc->hCurAddBlock),
			  	&(CommDoc->AddOffset), &(CommDoc->AddLineID));
	}

  if (CommDoc->CountedLines > 0) {
    PercentDone = MulDiv(CommDoc->ActiveLines, 100, CommDoc->CountedLines);
    SetStatbarPercent(CommDoc->hWndFrame, (int) PercentDone, CommDoc, TRUE);
  }

  if ((CommDoc->TotalLines % UPDATE_ART_FREQ) == 0)
	InvalidateRect (CommDoc->hDocWnd, NULL, FALSE);
}

void
WrapAddCommLineToDoc (char *line)
{
  char *start, *end;
  int thisLen, lenToGo;
  char saveChar;

  /* special case for lines starting with '..' */
  if (strncmp (CommLineIn, "..", 2)) {
	start = CommLineIn;
  }
  else {
	start = CommLineIn + 1;
  }
  if (CommDecoding) {
	DecodeLine (currentCoded, start);
  }
  else if (!WrapIncomingArticleText || CommState == ST_REC_ARTICLE_HEADER || 
  			*start == '\0') {
	AddCommLineToDoc (start);
  }
  else {
	lenToGo = strlen (start);
	while (*start) {
	  thisLen = min (lenToGo, WrapIncomingArticleTextLength);
	  lenToGo -= thisLen;

	  end = start + thisLen;	/* save char at end of this line */
	  saveChar = *end;
	  *end = '\0';

	  AddCommLineToDoc (start);

	  *end = saveChar;			/* restore char at end of this line */
	  start = end;
	}
  }
}

/*-- function TrimHeader ---------------------------------------------
 *
 * If TrimHeaders is deactivated, or if we are decoding, then 
 * always return false (don't trim header).
 * else, if this header line should be skipped, return TRUE
 *
 * jsc 9/24/94
 */
BOOL
TrimHeader (char *line)
{
  if (TrimHeaders && !CommDecoding) {
	if (!IsBlankStr (line) &&
		_strnicmp ("to:", line, 3) &&
		_strnicmp ("subject:", line, 8) &&
		_strnicmp ("date:", line, 5) &&
		_strnicmp ("from:", line, 5) &&
		_strnicmp ("reply-to:", line, 9) &&
		_strnicmp ("newsgroups:", line, 11) &&
		_strnicmp ("references:", line, 11) &&
		_strnicmp ("summary:", line, 8) &&
		_strnicmp ("distribution:", line, 13) &&
		_strnicmp ("message-id:", line, 11) &&	/* need this for article replies */
		_strnicmp ("followup-to:", line, 12) &&
		_strnicmp ("keywords:", line, 9)) {
	  return TRUE;				/* matches none of above, so trim it */
	}
  }
  return FALSE;					/* don't trim it */
}

/*-- function WasArtSeen ---------------------------------------------
 *
 *  Determines whether (according to the information in a TypGroup entry)
 *  a given article number was seen.
 *
 *  Returns ART_SEEN iff the article has been seen.
 */
char
WasArtSeen(unsigned long ArtNum, TypGroup far *GroupPtr)
{
  TypRange far *RangePtr = GetRangePtr(GroupPtr);
  unsigned int nr;

  for (nr = 0; nr < GroupPtr->nRanges; nr++) {
	if (ArtNum >= (unsigned long) RangePtr->First &&
		ArtNum <= (unsigned long) RangePtr->Last) {
	  return (ART_SEEN);
	}
	else {
	  RangePtr++;
	}
  }
  return (ART_UNSEEN);
}

/*-- function CalcNumUnread ---------------------------------------------
 *
 *  Determines number of unseen articles in a group
 *
 *  jsc 11/12/94
 */
unsigned long 
CalcNumUnread(TypGroup far *GroupPtr)
{
	TypRange far *RangePtr = (TypRange far *) ((char far *)
						GroupPtr + RangeOffset (GroupPtr->NameLen));
	unsigned int i;
	unsigned long numUnseen, prev;
	
	if ((GroupPtr->nRanges == 1 && RangePtr->First == 0 && RangePtr->Last == 0) ||
		(long)(RangePtr[GroupPtr->nRanges - 1].Last) < GroupPtr->ServerFirst) {
		return min(GroupPtr->ServerEstNum, (GroupPtr->ServerLast - GroupPtr->ServerFirst + 1));
	}
#if 0	// TANAKA Goh (newsrc patch)
	if (RangePtr->First != 1) {	/* this should never happen */
		RangePtr->First = 1;
	}
#endif	// TANAKA Goh (newsrc patch)

	prev = GroupPtr->ServerFirst;
	numUnseen = 0L;
	for (i = 0; i < GroupPtr->nRanges; i++) {
    	if ((unsigned long)RangePtr->First > prev) {
    		numUnseen += ((unsigned long)RangePtr->First - prev - 1);
    	}
    	if ((unsigned long)RangePtr->Last > prev) {
    		prev = (unsigned long)RangePtr->Last;
		}
    	if (i + 1 < GroupPtr->nRanges) {
    		RangePtr++;
    	}
	}

	if (GroupPtr->ServerLast > (long)RangePtr->Last) {
		numUnseen += (GroupPtr->ServerLast - (long)RangePtr->Last);
	} 
	
	return numUnseen;
}

/*--- function mylstrncmp -----------------------------------------------
 *
 *   Just like strncmp, except takes long pointers.
 */
int mylstrncmp(char far *ptr1, char far *ptr2, int len)
{
  for (; len--; ptr1++, ptr2++) {
	if (*ptr1 > *ptr2) {
	  return (1);
	}
	else if (*ptr1 < *ptr2) {
	  return (-1);
	}
  }
  return (0);
}

/*--- function mylstrncpy -----------------------------------------------
 *
 *   Just like strncpy, except takes long pointers.
 */
char far * mylstrncpy(char far *ptr1, const char far *ptr2, int len)
{
  char far *targ = ptr1;

  for (; --len && *ptr2; ptr1++, ptr2++) {
	*ptr1 = *ptr2;
  }
  *ptr1 = '\0';
  return (targ);
}

/* this is a temporary test... */
char far * mylstrcpy(char_p ptr1, char far *ptr2)
{
  char far *targ = ptr1;
  for (; *ptr2; ptr1++, ptr2++) {
	*ptr1 = *ptr2;
  }
  *ptr1 = '\0';
  return (targ);
}

#if 0
/*--- function lstrcmpnoblank ------------------------------------------
 *
 *   Like strcmp, except takes long pointers and also stops at
 *   the first blank.
 */
int
lstrcmpnoblank(char far **str1, char far **str2)
{
  register char far *s1 = *str1, far * s2 = *str2;

  for (; *s1 && *s2 && *s1 != ' ' && *s2 != ' '; s1++, s2++) {
	if (*s1 > *s2) {
	  return (1);
	}
	else if (*s1 < *s2) {
	  return (-1);
	}
  }
  if (*s1 == *s2) {
	return (0);
  }
  else if (*s1) {
	return (1);
  }
  else {
	return (-1);
  }
}
#endif

void
finish_header_retrieval ()
{
  char *sSortDef;
  TypLine far *LinePtr;
  TypBlock far *BlockPtr;
  TypGroup far *GroupDoc;
  HANDLE header_handle, thread_handle;
  thread_array thread_index;
  char mybuf[MAXINTERNALLINE];
  header_p headers, hp;
  uint32 i;

  /* release the mouse that is captured to the usenet window */
  ReleaseCapture ();

  CommDoc->TotalLines = CommDoc->ActiveLines;
  /* Disabled by MRR so that ActiveLines is the number of lines
   * we should display in the Group window.  Eventually, will
   * change it so that ActiveLines will count only unseen articles
   * if the user desires.
   */
  /* CommDoc->ActiveLines = 0; */
  /* Fetch this group's line in NetDoc so we can get the
   * group's name for the window's title bar.
   */
  if (LockLine (CommDoc->hParentBlock, CommDoc->ParentOffset,
			    CommDoc->ParentLineID, &BlockPtr, &LinePtr)) {

  	GroupDoc = GetGroup(LinePtr);
  	header_handle = GroupDoc->header_handle;
  	thread_handle = GroupDoc->thread_handle;
  	headers = lock_headers (header_handle, thread_handle);
  	thread_index = *((thread_array_p) ((char_p) headers - sizeof (char_p)));
    
    GroupDoc->total_headers = CommDoc->TotalLines;  // needed in ActOnArticles()

    if(bEnableArticleAction)
    {
      WVArticleAction aa;
      aa.ReadActions(CurrentGroup);  /* Get article action list */
      aa.ActOnArticles(GroupDoc, headers);  /* Perform article actions */
    }

    if (CommDoc->TotalLines > 0)
    {
      CommDoc->SelectedLines = 0;
      for(i = 0; i < CommDoc->TotalLines; i++)
      {
        hp = header_elt(headers, i);
        if(ART_SELECTED & hp->Selected)
          CommDoc->SelectedLines++;
      }
    }

//  	iSortOption = IDM_SORT_ARTNUM;
    sSortDef = STR_SORT_ARTNUM;
  	if (threadp) {
		SetWindowText (CommDoc->hWndFrame, "sorting headers...");
//    	iSortOption = IDM_SORT_THREADSUB; // we're testing this as the default.
      sSortDef = STR_SORT_THREADSUB;
  	}

//  	lstrcpy(szTmp,"G: ");
//  	lstrcat(szTmp, CurrentGroup);
//  	iSortOption = GetPrivateProfileInt(szTmp, "SortOption", iSortOption, szAppProFile);
    iSortOption = ReadSortOption(sSortDef, CurrentGroup);

/*  TRACE2("WVUtil: Newsgroup <%s>, iSortOption: %d\n", CurrentGroup, iSortOption); */
  	sort_by_option(headers, thread_index, threadp, CommDoc->TotalLines,
        	header_handle, thread_handle);

  	unlock_headers (header_handle, thread_handle);
  }

  sprintf (mybuf, "%s (%u articles)", CurrentGroup, CommDoc->TotalLines);
  SetWindowText (CommDoc->hWndFrame, mybuf);

  /* If we have information from NEWSRC on the highest-
   * numbered article previously seen, position the window
   * so the new articles can be seen without scrolling.
   */
  {
	unsigned int i;

	if (LockLine (CommDoc->hParentBlock, CommDoc->ParentOffset,
			      CommDoc->ParentLineID, &BlockPtr, &LinePtr)) {

	  /* inside the lock, can access the GroupStruct */
	  GroupDoc = GetGroup(LinePtr);
	  header_handle = GroupDoc->header_handle;
	  thread_handle = GroupDoc->thread_handle;
	  headers = lock_headers (header_handle, thread_handle);

  /* skip to FIRST unseen article (jsc) */
	  if (CommDoc->TotalLines > 0) {
	    for (i = 0;
		     (i < CommDoc->TotalLines && (header_elt (headers, i))->Seen);
		     i++);
	  }

	  CommDoc->TopLineOrd =
	    (CommDoc->TotalLines > i && CommDoc->ScYLines > 0 &&
	     CommDoc->TotalLines - i < CommDoc->ScYLines - 1) ? 
		   (CommDoc->TotalLines - CommDoc->ScYLines) + 1 : 
		     (i > 5 && CommDoc->TotalLines > CommDoc->ScYLines) ? i - 4 : 0; 
	
	  CommDoc->ActiveLineID = i;

	  CommDoc->ThumbTracking = FALSE;	/* if thumb tracking, release it */
	
	  unlock_headers (header_handle, thread_handle);
	  }
    }

  SendMessage (CommDoc->hWndFrame, (UINT) WM_COMMAND, (WPARAM) ID_RETRIEVE_COMPLETE, 0L);
  InvalidateRect (CommDoc->hDocWnd, NULL, TRUE);
  UpdateWindow (CommDoc->hDocWnd);
}

/*
 * Look through the MAIL or Post edits and return the edit with
 * matching window handle Consider - centralising initial window
 * location in wvmail and wndpost using a single array (save passing
 * structure and size into this module)
 *
 */

WndEdit *
getWndEdit (WndEdit * WndEdits, HWND hWnd, int numEntries)
{
  int ih;

  for (ih = 0; ih < numEntries; ih++) {
	if (WndEdits[ih].hWnd == hWnd) {
	  return &WndEdits[ih];
	}
  }

  /*MessageBox(0,"getWndEditFound Nothing","mrb debug", MB_OK | MB_ICONHAND); */

  return (WndEdit *) NULL;
}

WndEdit *
GetComposeWnd (HWND hWnd)
{
  WndEdit *compWnd;

  compWnd = getWndEdit (WndPosts, hWnd, MAXPOSTWNDS);
  if (!compWnd) {
	compWnd = getWndEdit (WndMails, hWnd, MAXMAILWNDS);
  }
  return compWnd;
}
/* ------------------------------------------------------------------------
 * Replace any white space at end of string with NULL's
 * JSC 11/1/93
 */
void
RemoveTrailingWhiteSpace (char *str)
{
  register int i;

  for (i = strlen (str) - 1; i > 0 && isspace (str[i]); i--)
	str[i] = '\0';
}

/*------------------------------------------------------------------------------
 * IsBlankStr
 * Returns true if the string is entirely whitespace, else false
 * JSC 12/6/93
 */
BOOL
IsBlankStr (char *temp)
{
  register char *ptr;
  for (ptr = temp; *ptr; ptr++)
	if (!isspace (*ptr))
	  return (FALSE);
  return (TRUE);
}

/*------------------------------------------------------------------------------
 * isnumber
 * Returns true if the string is a all digits
 * JSC 12/6/93
 */
BOOL
isnumber (char *str)
{
  char *ptr;

  for (ptr = str; *ptr != '\0'; ptr++)
	if (!isdigit (*ptr))
	  return (FALSE);
  return (TRUE);
}

/* ------------------------------------------------------------------------
 * strntcpy is strncpy, but also terminates the dest str
 * jsc 9/28/94
 */
char *
strntcpy (char *dest, char *src, int len)
{
  register char *d = dest;
  register char *s = src;
  register int l = 0;

  while (l < len && (*d++ = *s++)) {
	l++;
  }
  if (l == len) {
	*d = '\0';
  }
  return dest;
}


/* ------------------------------------------------------------------------
 *    Open the common font dialog
 *      Place resulting selection name and size in face,style and size
 *      Note: to select a printer font, send style as "Printer"
 *      printer font selection ignores any chosen style
 *      (JSC 1/9/94)
 */
BOOL
AskForFont (HWND hParentWnd, char *face, int *size, char *style)
{
  LOGFONT lf;
  CHOOSEFONT cf;
  HDC hDC;

  memset (&lf, 0, sizeof (LOGFONT));
  strcpy (lf.lfFaceName, face);
  /* convert points to logical units (1 pt = 1/72 inch) */
  /* For printer fonts, use ScreenYPixels here anyway - the choosefont */
  /* dialog appears to require the lfHeight to be in screen units */
  /* we will convert point size to PrinterUnits in InitPrinterFonts() */
  lf.lfHeight = -MulDiv (*size, ScreenYPixels, 72);

  memset (&cf, 0, sizeof (CHOOSEFONT));
  cf.lStructSize = sizeof (CHOOSEFONT);
  cf.hwndOwner = hParentWnd;
  cf.lpLogFont = &lf;
  if (!stricmp (style, "Printer")) {
	cf.nFontType = PRINTER_FONTTYPE;
	hDC = GetPrinterDC (hParentWnd);
	cf.hDC = hDC;
	cf.Flags = CF_PRINTERFONTS | CF_INITTOLOGFONTSTRUCT | CF_FORCEFONTEXIST;
  }
  else {
	cf.nFontType = SCREEN_FONTTYPE;
	cf.Flags = CF_SCREENFONTS | CF_INITTOLOGFONTSTRUCT | CF_USESTYLE | CF_FORCEFONTEXIST;
	cf.lpszStyle = style;
  }
  if (!ChooseFont (&cf))
	return (FAIL);

/*      if (!stricmp (style, "Printer"))      // commented out JD 6/17/94 */
  /*         ReleaseDC (NetDoc.hDocWnd, hDC);  */

/*      if (!stricmp (style, "Printer")) */
  /*         DeletePrinterDC (hDC); */

  *size = cf.iPointSize / 10;	/* iPointSize is in tenths of a point */

  strcpy (face, lf.lfFaceName);
  return (SUCCESS);
}

/* ------------------------------------------------------------------------
 *    Open the common color dialog
 *      (JSC 1/9/94)
 */
BOOL
AskForColor (HWND hParentWnd, COLORREF * color)
{
  CHOOSECOLOR cc;
  COLORREF nearC;
  HDC hDC;

  memset (&cc, 0, sizeof (CHOOSECOLOR));
  cc.lStructSize = sizeof (CHOOSECOLOR);
  cc.hwndOwner = hParentWnd;
  cc.rgbResult = *color;
  cc.lpCustColors = CustomColors;
  cc.Flags = CC_RGBINIT;

  if (!ChooseColor (&cc))
	return (FAIL);

  /* until we figure out how to deal with dithered colors, force */
  /* the color to the nearest physical color */
  hDC = GetDC (hParentWnd);
  nearC = GetNearestColor (hDC, cc.rgbResult);
  if (cc.rgbResult != nearC)
	MessageBox (hParentWnd, "WinVN does not currently support dithered (non-solid) colors.\nThe nearest physical solid color has been selected.",
				"Sorry", MB_OK | MB_ICONINFORMATION);
  *color = nearC;
  ReleaseDC (hParentWnd, hDC);
  return (SUCCESS);
}

/* ------------------------------------------------------------------------
 * This should be used instead of EM_GETHANDLE on global edit buf
 * Returns a string containing the contents of an edit wnd
 * It is the reponsibility of the caller to GlobalFreePtr the string
 * (JSC)
 */
char *
GetEditText (HWND hWndEdit)
{
  unsigned int size;
  char *newText;

#define EDIT_PAD 2

  SendMessage (hWndEdit, EM_FMTLINES, (WPARAM) WordWrap, 0L);

  size = (unsigned int) SendMessage (hWndEdit, WM_GETTEXTLENGTH, 0, 0L) + EDIT_PAD;

  if ((newText = (char *) GlobalAllocPtr (GMEM_MOVEABLE, size * sizeof (char))) == NULL) {
	MessageBox (hWndEdit, "Memory allocation failure", "Edit Text", MB_OK);
	return (NULL);
  }
  *newText = '\0';

  if (SendMessage (hWndEdit, WM_GETTEXT, size, (LPARAM) (LPCSTR) newText) != (long) (size - EDIT_PAD)) {
	MessageBox (hWndEdit, "Failed to get text", "Edit Text", MB_OK);
	return (NULL);
  }

  return (newText);
}
LRESULT
SetEditText (HWND hWndEdit, char *editMem)
{
  return (SendMessage (hWndEdit, WM_SETTEXT, 0, (LPARAM) (LPCSTR) editMem));
}

/* ------------------------------------------------------------------------
 *    Write an integer to the private profile
 */
BOOL
WritePrivateProfileInt (char far * lpAppName, char far * lpKeyName, int val, char far * lpProFile)
{
  char buf[20];

  itoa (val, buf, 10);
  return (WritePrivateProfileString (lpAppName, lpKeyName, buf, lpProFile));
}

/* ------------------------------------------------------------------------
 *    Get/Write an unsigned integers and longs to the private profile
 *      (JSC 1/8/94)
 */
BOOL
WritePrivateProfileUInt (char far * lpAppName, char far * lpKeyName, unsigned int val, char far * lpProFile)
{
  char buf[20];

  uitoa (val, buf, 10);
  return (WritePrivateProfileString (lpAppName, lpKeyName, buf, lpProFile));
}

unsigned int
GetPrivateProfileUInt (char far * lpAppName, char far * lpKeyName, unsigned int val, char far * lpProFile)
{
  char buf[20];

  GetPrivateProfileString (lpAppName, lpKeyName, "", buf, 20, lpProFile);

  if (*buf)
	return (atoui (buf));
  else
	return (val);
}

BOOL
WritePrivateProfileLong (char far * lpAppName, char far * lpKeyName, long val, char far * lpProFile)
{
  char buf[20];

  ltoa (val, buf, 10);
  return (WritePrivateProfileString (lpAppName, lpKeyName, buf, lpProFile));
}

long
GetPrivateProfileLong (char far * lpAppName, char far * lpKeyName, long val, char far * lpProFile)
{
  char buf[20];

  GetPrivateProfileString (lpAppName, lpKeyName, "", buf, 20, lpProFile);

  if (*buf)
	return (atol (buf));
  else
	return (val);
}
/* ------------------------------------------------------------------------
 *    Refresh Window functions
 *      Called after a font/color selection has changed to affect all
 *      windows of a certain type (group/article/status)
 *      (JSC 1/9/94)
 */
void
RefreshGroupWnds ()
{
  register int i;
  for (i = 0; i < MAXGROUPWNDS; i++)
	if (GroupDocs[i].InUse && GroupDocs[i].hDocWnd) {
	  SetHandleBkBrush (GroupDocs[i].hDocWnd, hListBackgroundBrush);
	  SendMessage (GroupDocs[i].hWndFrame, WM_SIZE, 0, 0L);
	  InvalidateRect (GroupDocs[i].hWndFrame, NULL, TRUE);
	  InvalidateRect (GroupDocs[i].hDocWnd, NULL, TRUE);
	  UpdateWindow (GroupDocs[i].hWndFrame);
	}
}

void
RefreshArticleWnds ()
{
  register int i;

  for (i = 0; i < MAXARTICLEWNDS; i++)
	if (ArticleDocs[i].InUse && ArticleDocs[i].hDocWnd) {
	  SetHandleBkBrush (ArticleDocs[i].hDocWnd, hArticleBackgroundBrush);
	  SendMessage (ArticleDocs[i].hWndFrame, WM_SIZE, 0, 0L);
	  InvalidateRect (ArticleDocs[i].hWndFrame, NULL, TRUE);
	  InvalidateRect (ArticleDocs[i].hDocWnd, NULL, TRUE);
	  UpdateWindow (ArticleDocs[i].hWndFrame);
	}
}

void
RefreshComposeWnds ()
{
  register int i;

  for (i = 0; i < MAXPOSTWNDS; i++)
	if (WndPosts[i].hWnd) {
	  ResizeComposeControls (&WndPosts[i], 0, 0);
	}

  for (i = 0; i < MAXMAILWNDS; i++)
	if (WndMails[i].hWnd) {
	  ResizeComposeControls (&WndMails[i], 0, 0);
	}
}

void
RefreshStatusWnds ()
{
  register int i;

  for (i = 0; i < NumStatusTexts; i++)
	if (CodingStatusText[i]->hTextWnd) {
	  SetHandleBkBrush (CodingStatusText[i]->hTextWnd, hStatusBackgroundBrush);
	  SendMessage (CodingStatusText[i]->hTextWnd, WM_SIZE, 0, 0L);
	  InvalidateRect (CodingStatusText[i]->hTextWnd, NULL, TRUE);
	}
  if (hCodedBlockWnd) {
	RefreshCodedBlockWnd ();
  }

}
/* ------------------------------------------------------------------------
 *    Close Window functions
 *      Batch operation, close all windows of a certain type
 *      (group/article/status)
 *      (JSC 1/18/94)
 */
void
CloseWindows ()
{
  CloseArticleWnds ();
  CloseGroupWnds ();
  CloseComposeWnds ();
  CloseStatusWnds ();
}

void
CloseGroupWnds ()
{
  register int i;
  for (i = 0; i < MAXGROUPWNDS; i++)
	if (GroupDocs[i].InUse && GroupDocs[i].hDocWnd && (!CommBusy || &GroupDocs[i] != CommDoc))
	  SendMessage (GroupDocs[i].hWndFrame, WM_CLOSE, 0, 0L);
}

void
CloseArticleWnds ()
{
  register int i;

  for (i = 0; i < MAXARTICLEWNDS; i++)
	if (ArticleDocs[i].InUse && ArticleDocs[i].hDocWnd && (!CommBusy || &ArticleDocs[i] != CommDoc))
	  SendMessage (ArticleDocs[i].hWndFrame, WM_CLOSE, 0, 0L);
}

void
CloseComposeWnds ()
{
  register int i;

  for (i = 0; i < MAXPOSTWNDS; i++)
	if (WndPosts[i].hWnd && !WndPosts[i].busy)
	  SendMessage (WndPosts[i].hWnd, WM_CLOSE, 0, 0L);

  for (i = 0; i < MAXMAILWNDS; i++)
	if (WndMails[i].hWnd && !WndMails[i].busy)
	  SendMessage (WndMails[i].hWnd, WM_CLOSE, 0, 0L);
}

void
CloseStatusWnds ()
{
  /* destroying a coding status text is like popping from a stack */
  /* so we just loop while the top of the stack still exists */
  int numSkipped = 0;
  while (numSkipped < NumStatusTexts && CodingStatusText[numSkipped]->hTextWnd)
	if (!CodingStatusText[numSkipped]->IsBusy)
	  SendMessage (CodingStatusText[numSkipped]->hTextWnd, WM_CLOSE, 0, 0L);
	else
	  numSkipped++;
#if 0
  if (CodingState) {
	MessageBox (CodingStatusText[0]->hTextWnd,
				"Please wait until en/decoding is complete",
				"Cannot close status window", MB_OK | MB_ICONSTOP);
	break;
  }
  else
	SendMessage (CodingStatusText[0]->hTextWnd, WM_CLOSE, 0, 0L);
#endif
}

/* ------------------------------------------------------------------------
 *    CascadeWindows (and helper CascadeWnd)
 *      cascade em
 *      jsc 9/18/94
 */
HWND
CascadeWnd (HWND hWnd, HWND prevWnd, int nthWnd, int width, int height, int maxX, int maxY)
{
  short x, y;

//  if (IsMaximized(hWnd))
  //     ShowWindow(hWnd, SW_SHOWNORMAL);

  x = (nthWnd * 12) % maxX;
  y = (nthWnd * (CaptionHeight + 2)) % maxY;
  SetWindowPos (hWnd, prevWnd, x, y, width, height, SWP_DRAWFRAME);

  return hWnd;
}

void
WinVNCascadeWindows ()
{
  register int i;
  int nthWnd, width, height, maxX, maxY;
  HWND prevWnd;

  width = (int) (xScreen >> 1);
  height = (int) (yScreen >> 1);
  maxX = 3 * (width >> 1);		/* 3/4 screen width  */
  maxY = 3 * (height >> 1);		/* 3/4 screen height */

  prevWnd = CascadeWnd (NetDoc.hWndFrame, (HWND) NULL, 1, width, height, maxX, maxY);
  nthWnd = 2;
  for (i = 0; i < MAXGROUPWNDS; i++)
	if (GroupDocs[i].InUse && GroupDocs[i].hWndFrame && !IsMinimized (GroupDocs[i].hWndFrame)) {
	  prevWnd = CascadeWnd (GroupDocs[i].hWndFrame, prevWnd, nthWnd, width, height, maxX, maxY);
	  nthWnd++;
	}

  for (i = 0; i < MAXARTICLEWNDS; i++)
	if (ArticleDocs[i].InUse && ArticleDocs[i].hWndFrame && !IsMinimized (ArticleDocs[i].hWndFrame)) {
	  prevWnd = CascadeWnd (ArticleDocs[i].hWndFrame, prevWnd, nthWnd, width, height, maxX, maxY);
	  nthWnd++;
	}

  for (i = 0; i < MAXPOSTWNDS; i++)
	if (WndPosts[i].hWnd && !IsMinimized (WndPosts[i].hWnd)) {
	  prevWnd = CascadeWnd (WndPosts[i].hWnd, prevWnd, nthWnd, width, height, maxX, maxY);
	  nthWnd++;
	}

  for (i = 0; i < MAXMAILWNDS; i++)
	if (WndMails[i].hWnd && !IsMinimized (WndMails[i].hWnd)) {
	  prevWnd = CascadeWnd (WndMails[i].hWnd, prevWnd, nthWnd, width, height, maxX, maxY);
	  nthWnd++;
	}

  for (i = 0; i < NumStatusTexts; i++)
	if (CodingStatusText[i]->hTextWnd && !IsMinimized (CodingStatusText[i]->hTextWnd)) {
	  prevWnd = CascadeWnd (CodingStatusText[i]->hTextWnd, prevWnd, nthWnd, width, height, maxX, maxY);
	  nthWnd++;
	}

  /* move coded block status window to top center */
  if (hCodedBlockWnd && !IsMinimized (hCodedBlockWnd)) {
	SetWindowPos (hCodedBlockWnd, (HWND) NULL, (xScreen - STATUSWIDTH) >> 1, 1, STATUSWIDTH, STATUSHEIGHT, SWP_DRAWFRAME);
  }
}

/* ------------------------------------------------------------------------
 *    MinimizeWindows
 *      jsc 10/18/94
 */
void
MinimizeWindows ()
{
  ShowWindow (NetDoc.hWndFrame, SW_MINIMIZE);
  MinimizeGroupWnds ();
  MinimizeArticleWnds ();
  MinimizeComposeWnds ();
  MinimizeStatusWnds ();
}

void
MinimizeGroupWnds ()
{
  register int i;

  for (i = 0; i < MAXGROUPWNDS; i++)
	if (GroupDocs[i].InUse && GroupDocs[i].hDocWnd && !IsMinimized (GroupDocs[i].hWndFrame)) {
	  ShowWindow (GroupDocs[i].hWndFrame, SW_MINIMIZE);
	}
}

void
MinimizeArticleWnds ()
{
  register int i;

  for (i = 0; i < MAXARTICLEWNDS; i++)
	if (ArticleDocs[i].InUse && ArticleDocs[i].hDocWnd && !IsMinimized (ArticleDocs[i].hWndFrame)) {
	  ShowWindow (ArticleDocs[i].hWndFrame, SW_MINIMIZE);
	}
}

void
MinimizeComposeWnds ()
{
  register int i;

  for (i = 0; i < MAXPOSTWNDS; i++)
	if (WndPosts[i].hWnd && !IsMinimized (WndPosts[i].hWnd)) {
	  ShowWindow (WndPosts[i].hWnd, SW_MINIMIZE);
	}

  for (i = 0; i < MAXMAILWNDS; i++)
	if (WndMails[i].hWnd && !IsMinimized (WndMails[i].hWnd)) {
	  ShowWindow (WndMails[i].hWnd, SW_MINIMIZE);
	}
}

void
MinimizeStatusWnds ()
{
  register int i;

  for (i = 0; i < NumStatusTexts; i++)
	if (CodingStatusText[i]->hTextWnd && !IsMinimized (CodingStatusText[i]->hTextWnd)) {
	  ShowWindow (CodingStatusText[i]->hTextWnd, SW_MINIMIZE);
	}

  if (hCodedBlockWnd) {
	ShowWindow (hCodedBlockWnd, SW_MINIMIZE);
  }
}

/* ------------------------------------------------------------------------
 *    RestoreWindows from minimized state
 *      jsc 1/18/95
 */

void
RestoreWindows ()
{
  register int i;

  if (IsMinimized(NetDoc.hWndFrame)) {
  	ShowWindow (NetDoc.hWndFrame, SW_RESTORE);
  }

  for (i = 0; i < MAXGROUPWNDS; i++)
	if (GroupDocs[i].InUse && GroupDocs[i].hDocWnd && IsMinimized (GroupDocs[i].hWndFrame)) {
	  ShowWindow (GroupDocs[i].hWndFrame, SW_RESTORE);
	}
  for (i = 0; i < MAXARTICLEWNDS; i++)
	if (ArticleDocs[i].InUse && ArticleDocs[i].hDocWnd && IsMinimized (ArticleDocs[i].hWndFrame)) {
	  ShowWindow (ArticleDocs[i].hWndFrame, SW_RESTORE);
	}
  for (i = 0; i < MAXPOSTWNDS; i++)
	if (WndPosts[i].hWnd && IsMinimized (WndPosts[i].hWnd)) {
	  ShowWindow (WndPosts[i].hWnd, SW_RESTORE);
	}

  for (i = 0; i < MAXMAILWNDS; i++)
	if (WndMails[i].hWnd && IsMinimized (WndMails[i].hWnd)) {
	  ShowWindow (WndMails[i].hWnd, SW_RESTORE);
	}
  for (i = 0; i < NumStatusTexts; i++)
	if (CodingStatusText[i]->hTextWnd && IsMinimized (CodingStatusText[i]->hTextWnd)) {
	  ShowWindow (CodingStatusText[i]->hTextWnd, SW_RESTORE);
	}

  if (hCodedBlockWnd) {
	ShowWindow (hCodedBlockWnd, SW_RESTORE);
  }
}

/* ------------------------------------------------------------------------
 *    BatchSend
 *      type is DOCTYPE_MAIL or _POST
 *      Increments nextBatchIndex and initiates mail/post
 *      Note: on entry nextBatchIndex is the index we will use for this send
 *      on exit, nextBatchIndex is either 0 (no more to send) or the index
 *      of the next mail/post to send
 *      (JSC 1/18/94)
 */
void
BatchSend (int type)
{
  int thisSend;
  int maxWnds;
  WndEdit *WndEdits;

  if (type == DOCTYPE_POSTING) {
	WndEdits = WndPosts;
	maxWnds = MAXPOSTWNDS;
  }
  else {
	WndEdits = WndMails;
	maxWnds = MAXMAILWNDS;
  }

//  Variable being used without initialization JD 11/10/95?
//  thisSend = WndEdits[thisSend].nextBatchIndex;
  thisSend = WndEdits[0].nextBatchIndex;
  if (thisSend == 0) {			/* find first in batch (if any) */
	while (thisSend < maxWnds)
	  if (WndEdits[thisSend].hWnd)
		break;
	  else
		thisSend++;

	if (thisSend == maxWnds)
	  return;					/* no open windows.  cancel */

	WndEdits[thisSend].nextBatchIndex = thisSend;
  }

  /* find next in batch (if any) */
  while (++WndEdits[thisSend].nextBatchIndex < maxWnds)
	if (WndEdits[WndEdits[thisSend].nextBatchIndex].hWnd)
	  break;

  if (WndEdits[thisSend].nextBatchIndex == maxWnds)
	WndEdits[thisSend].nextBatchIndex = 0;			/* no more */

  SendComposition (&WndEdits[thisSend]);
}

/* ------------------------------------------------------------------------
 *    Test busy functions
 *      Called to test if a comm or decoding is busy
 *      Returns true if busy, false if not busy
 *      (JSC 1/9/94)
 */
BOOL
TestCommBusy (HWND hParentWnd, char *msg)
{
  if (CommBusy) {
	MessageBox (hParentWnd,
				"Sorry, WinVN is busy communicating with the news server.\n"
				"Try again in a little while.", msg,
				MB_OK | MB_ICONASTERISK);
	return (TRUE);
  }
  else
	return (FALSE);
}

BOOL
TestCodingBusy (HWND hParentWnd, char *msg)
{
  if (CodingState != INACTIVE) {
	MessageBox (hParentWnd,
			 "Sorry, I can only handle one en/decoding session at a time.\n"
				"Try again in a little while.", msg,
				MB_OK | MB_ICONASTERISK);
	return (TRUE);
  }
  else
	return (FALSE);
}

/* ------------------------------------------------------------------------
 *        Update the mail menus -- called on mail transport change
 *      jsc 9/9/94
 */
void
UpdateAllMailMenus ()
{
  register int i;

  SetMainMailMenu (&NetDoc);
  for (i = 0; i < MAXARTICLEWNDS; i++)
	if (ArticleDocs[i].hDocWnd &&
		(!CommBusy || CommDoc != &ArticleDocs[i]))
	  SetArticleMailMenu (&ArticleDocs[i]);

  for (i = 0; i < MAXGROUPWNDS; i++)
	if (GroupDocs[i].hDocWnd)
	  SetGroupMailMenu (&GroupDocs[i]);
}

/* ------------------------------------------------------------------------
 *        GetFreeDiskSpace in bytes, given a drive name (i.e. "c:\\")
 *      jsc 9/29/94
 */
unsigned long 
GetFreeDiskSpace (char *drive)
{
#ifdef WIN32
  DWORD sectorsPerCluster, bytesPerSector, freeClusters, totalClusters;
  if (GetDiskFreeSpace (drive, &sectorsPerCluster, &bytesPerSector,
						&freeClusters, &totalClusters) == FALSE) {
	return 0L;
  }
  return (unsigned long) (sectorsPerCluster * bytesPerSector * freeClusters);
#else
  struct _diskfree_t d;
  if (_dos_getdiskfree (tolower (*drive) - 'a', &d) != 0) {
	return 0L;
  }
  return (unsigned long) d.avail_clusters *
	(unsigned long) d.sectors_per_cluster *
	(unsigned long) d.bytes_per_sector;
#endif
}

/* ------------------------------------------------------------------------
 *        GetFileLength in bytes  given a file name
 *      jsc 9/29/94
 */
unsigned long 
GetFileLength (char *fileName)
{
  int fh;
  long len;

  if ((fh = _open (fileName, _O_RDONLY)) < 0) {
	return 0L;
  }
  if ((len = _filelength (fh)) < 0) {
	len = 0L;
  }
  _close (fh);
  return (unsigned long) len;
}

void
DrawDragingCursor(HWND hWnd, long oldPosition, long newPosition, BYTE flag)
{
  HDC hDc;
  HPEN hOrigPen;
  int nOrigDrawMode;
  RECT clientRect;

  hDc = GetDC(hWnd);
  hOrigPen = (HPEN)SelectObject(hDc, CreatePen(PS_SOLID, 1, ListBackgroundColor));
  nOrigDrawMode = GetROP2(hDc);
  SetROP2(hDc, R2_XORPEN);
  GetClientRect(hWnd, &clientRect);
  if ((flag == 1) || (flag == 2)) {
	MoveToEx(hDc, oldPosition, 0, NULL);
	LineTo(hDc, oldPosition, clientRect.bottom);
  }
  if (flag == 2) {
	MoveToEx(hDc, newPosition, 0, NULL);
	LineTo(hDc, newPosition, clientRect.bottom);
  }
  SetROP2(hDc, nOrigDrawMode);
  DeleteObject(SelectObject(hDc,hOrigPen));
  ReleaseDC(hWnd, hDc);
}

//
// Local variables:
// tab-width: 2
// end:
//
