head	1.106;
access;
symbols
	V0-99-8:1.99
	V0-99-7:1.97
	V0-99-6:1.92
	V0-99-5:1.90
	V0-99-4:1.89
	V0-99-2:1.88
	V0-99-1:1.88
	V0-93-14:1.87
	V0-93-13:1.86
	V0-93-12:1.83
	V0-93-11:1.80
	V0-93-10:1.77
	V0-93-9:1.76
	V0-93-8:1.75
	V0-93-7:1.75
	V0-93-5:1.73
	V80:1.17
	V76d:1.3;
locks; strict;
comment	@ * @;


1.106
date	97.04.05.00.39.18;	author dumoulin;	state Exp;
branches;
next	1.105;

1.105
date	97.03.28.19.43.54;	author dumoulin;	state Exp;
branches;
next	1.104;

1.104
date	97.03.07.02.21.14;	author dumoulin;	state Exp;
branches;
next	1.103;

1.103
date	97.02.28.19.27.23;	author dumoulin;	state Exp;
branches;
next	1.102;

1.102
date	97.02.14.20.12.31;	author dumoulin;	state Exp;
branches;
next	1.101;

1.101
date	97.02.08.11.02.25;	author dumoulin;	state Exp;
branches;
next	1.100;

1.100
date	97.02.06.21.18.08;	author dumoulin;	state Exp;
branches;
next	1.99;

1.99
date	96.08.31.07.03.27;	author dumoulin;	state Exp;
branches;
next	1.98;

1.98
date	96.08.13.05.07.03;	author dumoulin;	state Exp;
branches;
next	1.97;

1.97
date	95.11.10.12.07.49;	author dumoulin;	state Exp;
branches;
next	1.96;

1.96
date	95.11.10.07.05.38;	author dumoulin;	state Exp;
branches;
next	1.95;

1.95
date	95.11.09.00.24.11;	author dumoulin;	state Exp;
branches;
next	1.94;

1.94
date	95.11.07.22.38.19;	author dumoulin;	state Exp;
branches;
next	1.93;

1.93
date	95.09.27.21.54.58;	author brydon;	state Exp;
branches;
next	1.92;

1.92
date	95.08.16.22.48.13;	author goh;	state Exp;
branches;
next	1.91;

1.91
date	95.07.21.16.11.20;	author jcooper;	state Exp;
branches;
next	1.90;

1.90
date	95.06.06.06.00.52;	author dumoulin;	state Exp;
branches;
next	1.89;

1.89
date	95.05.19.22.18.13;	author dumoulin;	state Exp;
branches;
next	1.88;

1.88
date	95.03.01.19.18.14;	author rushing;	state Exp;
branches;
next	1.87;

1.87
date	95.02.13.19.30.41;	author rushing;	state Exp;
branches;
next	1.86;

1.86
date	95.02.07.23.02.16;	author jcooper;	state Exp;
branches;
next	1.85;

1.85
date	95.02.02.23.16.57;	author rushing;	state Exp;
branches;
next	1.84;

1.84
date	95.02.02.21.25.23;	author rushing;	state Exp;
branches;
next	1.83;

1.83
date	95.01.25.01.27.51;	author dumoulin;	state Exp;
branches;
next	1.82;

1.82
date	95.01.19.02.47.25;	author jglasser;	state Exp;
branches;
next	1.81;

1.81
date	95.01.03.20.39.59;	author brydon;	state Exp;
branches;
next	1.80;

1.80
date	94.12.22.18.35.51;	author rushing;	state Exp;
branches;
next	1.79;

1.79
date	94.12.19.17.06.02;	author rushing;	state Exp;
branches;
next	1.78;

1.78
date	94.12.13.21.27.41;	author brydon;	state Exp;
branches;
next	1.77;

1.77
date	94.12.13.12.56.13;	author jcooper;	state Exp;
branches;
next	1.76;

1.76
date	94.11.30.22.22.08;	author jcooper;	state Exp;
branches;
next	1.75;

1.75
date	94.11.22.02.53.26;	author jcooper;	state Exp;
branches;
next	1.74;

1.74
date	94.11.16.21.33.26;	author rushing;	state Exp;
branches;
next	1.73;

1.73
date	94.11.10.23.00.19;	author jcooper;	state Exp;
branches;
next	1.72;

1.72
date	94.11.10.02.47.39;	author rushing;	state Exp;
branches;
next	;


desc
@winvn version 0.76 placed into RCS
@


1.106
log
@removed check for a trailing '>' when checking references
@
text
@// -*- C++ -*-
/*
 * $Id: wvutil.cpp 1.105 1997/03/28 19:43:54 dumoulin Exp $
 *
 */

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

#include <windows.h>
#include <windowsx.h>           // for GlobalFreePtr (JSC)
#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, unsigned 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

 
// Simple check to see if Username is legal.  This needs
// to be more complicated to handle all possible illegal
// names, but for now, just handle the main ones.
// Return TRUE if valid.   (JD 3/26/97)
//        
BOOL ValidEmailAddress(char far * name)
{
 char far *atsign;

 if (name) {
	 atsign = strchr (name, '@@');
	 if (atsign &&                          // needs an @@ sign
		   (atsign > name) &&                 // nonblank to left of @@
			 (strstr (atsign, "@@.") == NULL) && // simple typo check
			 (strchr (atsign, '.') != NULL) &&  // at least one '.' rt of @@
			 (strchr (name, '|') == NULL))      // don't allow pipes in name
   {
		 return TRUE;
	 }
 }
return FALSE;
}

/*-- 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;
  unsigned 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) {
//
//    Fix Year 2000 problem waiting to happen (JD 2/7/97)
//      yr += 1900;
//
      yr += (yr < 50) ? 2000 : 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 (unsigned long artnum,
             unsigned 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);
  }
}


//
// Side effect:
//  The last '>' replaced with '\0'
//
char * get_best_reference (char * refer)
{
  char * start, * end;
  start = strrchr (refer, '<');
  if (!start) {
    // no valid ref here without a '<'
    return NULL;
  } else {
    start++;
	if (!start) return NULL;          // JD 2/27/97
    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

WVString message_id_with_frob(const char * source)
{
  int pos;
  unsigned long sum;
  const char * p;
  static char sRetval[HEADER_MESSAGE_ID_LENGTH + 1];
  
  sRetval[0] = '\0';
  if(source)
  {
 //  if('<' == *source) // skip any leading '<'
 //    source++;
 // fix a bug where a bad references line causes a GPF
 // JD 2/10/97
 //
	while (source && (('<' == *source) || 
		              (' ' == *source) ||
					  ('>' == *source)))
       source++;
	if(source)
	{
	  strncpy(sRetval, source, HEADER_MESSAGE_ID_LENGTH);
  	  sRetval[HEADER_MESSAGE_ID_LENGTH] = '\0';
      if(pos = strcspn(sRetval, "> \t")) // chop trailing whitespace and '>'
        sRetval[pos] = 0;
    
      // only frob if we need to
      if(strlen(sRetval) >= HEADER_MESSAGE_ID_LENGTH - 1)
      {
        sum = 0;
        for (p = source; *p; p++)
        {
          sum = (sum << 1) + *p;
        }
        sprintf(sRetval+HEADER_MESSAGE_ID_LENGTH-8, "%08x", sum);
      }
	}
  }

  // if there is no valid reference, somehow string objects
  // release hugh chunks of memory when you allocate an object
  // of zero length.  This is debug code so we can set a breakpoint.
  // (JD 2/14/97)
  //
  // if(source == 0)
  //	return 0;
  // else
  //   return WVString(sRetval);

    return WVString(sRetval);
}

// clear out the latest header info
void ClearLatestInfo()
{
  Latest.lines = 0;
  Latest.iGroups = 0;
  Latest.sFrom       = "";
  Latest.sEmail      = "";
  Latest.sSubject    = "";
  Latest.sMessage_id = "";
  Latest.sNewsgroups = "";
}


/*--- function ParseAddress --------------------------
 *   String version of the function defined in WVHeader
 */
void
ParseAddress(const char *headerline, WVString& sAddr, WVString& sName)
{
  char addr[MAXHEADERLINE];
  char name[MAXHEADERLINE];
  ParseAddress(headerline, addr, sizeof(addr), name, sizeof(name));
  sAddr = addr;
  sName = name;
}


// 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))
  {
    Latest.sSubject = sTmp;
  }
  else if(!_strnicmp ("from:", line, 5))
  {
      ParseAddress(sTmp, Latest.sEmail, Latest.sFrom);
  }
  else if(!_strnicmp ("lines:", line, 6))
  {
    Latest.lines = atoi(sTmp);
  }

  else if(!_strnicmp ("newsgroups:", line, 11))
  {
    Latest.sNewsgroups = sTmp;
    Latest.iGroups = 1;
    while(sTmp = strchr(sTmp, ','))
    {
      Latest.iGroups++;
      sTmp++;
    }
  }

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


//   Build a list of frobbed references
//
//  Side effects:
//    Source string gets clobbered with nulls
//
void build_ref_list(WVString& sDest, const char*src)
{
  char*sTmp;
  char*start;
  
  if(src[0])
  {
    sTmp = new char[2 + strlen(src)];
    if(sTmp)
    {
      sTmp[0] = 0;
      start = strchr(src, '<');
      while(start)
  //  while(start && !strchr(start+1,'>'))
      {
        strcat(sTmp, message_id_with_frob(start));
        strcat(sTmp, " ");
 //   This can blow up (JD 2/27/97)
 //       start = strchr(start++, '>');
 //       if(start)
 //         start = strchr(start, '<');
		start++;
		if (start)
		   start = strchr(start,'>');
		if (start)
		   start = strchr(start,'<');
      }
      sDest = sTmp;
      delete [] sTmp;
    }
  }
  else
    sDest = "";
}


/*-- 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 unsigned long first, last;
  unsigned long /* estnum, */ artnum;
  static uint32 uLastArtNum = 0;
  long syncnum;
  int mylen;
  BOOL done = FALSE;
  BOOL DlgStatus = FALSE;
  BOOL dolist, do_authinfo;
  static char group[MAXINTERNALLINE];
  char mybuf[MAXINTERNALLINE];
  char mybuf2[MAXINTERNALLINE];
  const char far *lpsz;
  HANDLE header_handle;
  HANDLE thread_handle;
  header_p headers;
  header_p header;
  TypGroup far *GroupDoc;
  WVSeenDB*pSDB;
  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;
      }

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

      sscanf (CommLineIn, "%u %lu %lu %lu %s", &retcode, &GroupDoc->ServerEstNum,
        &GroupDoc->ServerFirst, &GroupDoc->ServerLast, group);

      GroupDoc->uLoadFirst = GroupDoc->ServerFirst;
      GroupDoc->uLoadLast = max(GroupDoc->ServerFirst, GroupDoc->ServerLast); // ServerLast can be zero
      GroupDoc->Determined = TRUE;

      if(!(GroupDoc->pAA))
      {
        GroupDoc->pAA = new WVArticleAction(CurrentGroup);
        GroupDoc->pAA->ReadActions();
      }
      pSDB = GroupDoc->pSeendb;
      GroupDoc->pSeendb = new WVSeenDB(GroupDoc->uLoadFirst, GroupDoc->uLoadLast, pSDB);
      if(GroupDoc->pSeendb)
        delete pSDB;
      else
        GroupDoc->pSeendb = pSDB;

      if(ShowUnreadOnly)
//        GroupDoc->uLoadFirst = GroupDoc->pSeendb->LowestUnseen();
        GroupDoc->uLoadFirst = GroupDoc->pSeendb->NextUnseen();
      arts_to_retrieve = GroupDoc->ServerEstNum;

      /* we don't want to grab *that* many! */
      if(arts_to_retrieve >= article_threshold)
      {

          est_num_unread = CalcNumUnread(GroupDoc);
          est_num_unread = min(est_num_unread, arts_to_retrieve);

        if (!ShowUnreadOnly) {
          DlgStatus = DialogBox (hInst, (LPCSTR) "THRESHOLD", CommDoc->hDocWnd, (DLGPROC) lpfnWinVnThresholdDlg);
          // (arts_to_retrieve and DlgStatus set)
          if (CommDoc && (DlgStatus == FALSE)) {
            DestroyWindow (CommDoc->hWndFrame);
            CommBusy = FALSE;
            CommState = ST_NONE;
            GroupDoc->ServerFirst = GroupDoc->uLoadLast;
            //GroupDoc->ServerEstNum = estnum;
            return;
          }
        }
        else
        {
//          arts_to_retrieve = article_threshold;
          arts_to_retrieve = est_num_unread;           /* VEV 2/3/97 */
          DlgStatus = ID_THRESHOLD_UNREAD;
        }

        if(DlgStatus == ID_THRESHOLD_ALL)  /* 'all of them' */
        {
          GroupDoc->uLoadFirst = GroupDoc->ServerFirst;
          GroupDoc->uLoadLast = GroupDoc->ServerLast;
          arts_to_retrieve = GroupDoc->ServerEstNum;
        }
        else
        {

					//   Change the way we retrieve selected number of articles.   If user picks
					//   100 articles, grab the latest 100 articles instead of the 1st 100 articles
					//                                           (JD 12/31/96)

					//  GroupDoc->uLoadLast = min(GroupDoc->ServerLast, GroupDoc->uLoadFirst + arts_to_retrieve - 1);
			GroupDoc->uLoadLast = GroupDoc->ServerLast;
			GroupDoc->uLoadFirst = min(GroupDoc->ServerLast, GroupDoc->uLoadLast - arts_to_retrieve + 1);
        }
        if((DlgStatus == ID_THRESHOLD_UNREAD)     /* they clicked 'unseen' */
                 ||(ShowUnreadOnly)) {
//          GroupDoc->uLoadFirst = GroupDoc->pSeendb->LowestUnseen();
          GroupDoc->uLoadFirst = GroupDoc->pSeendb->NextUnseen();
          if(arts_to_retrieve < min_to_retrieve)
          {
            arts_to_retrieve = min_to_retrieve;
            if (GroupDoc->uLoadLast < min_to_retrieve)
              GroupDoc->uLoadFirst = 1;
            else
              GroupDoc->uLoadFirst = 1 + GroupDoc->uLoadLast - min_to_retrieve;
          }
          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 {
        if (GroupDoc->ServerEstNum <= 0)
        {
          /* abort the fledgling group window */
          DestroyWindow (CommDoc->hWndFrame);
          CommBusy = FALSE;
          CommState = ST_NONE;
          GroupDoc->ServerFirst = GroupDoc->uLoadLast;
          //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 | GMEM_ZEROINIT, (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);     // (didn't we already do this?)
      //GroupDoc->ServerEstNum = estnum;
      //GroupDoc->ServerFirst = GroupDoc->uLoadFirst;
	  
      GroupDoc->Threaded = xoverp || threadp;
      ClearLatestInfo();

      if (xoverp) {
        mylen = sprintf (mybuf, "XOVER %ld-%ld", GroupDoc->uLoadFirst, GroupDoc->uLoadLast);
        CommState = ST_XOVER_START;
      }
      else {
        mylen = sprintf (mybuf, "XHDR from %ld-%ld", GroupDoc->uLoadFirst, GroupDoc->uLoadLast);
        CommState = ST_XHDR_FROM_START;
      }
      PutCommLine (mybuf);
      GlobalUnlock (BlockPtr->hCurBlock);

      break;

      /* use XOVER if its available */
    case ST_XOVER_START:
      retcode = 0;
        
      sscanf (CommLineIn, "%d", &retcode);
      if (retcode == 224) {
            CommState = ST_XOVER_DATA;
            CommDoc->ActiveLines = 0;
            if (bEnableArticleAction) {
              if(LockLine(CommDoc->hParentBlock, CommDoc->ParentOffset,
                CommDoc->ParentLineID, &BlockPtr, &LinePtr))
              {
                GroupDoc = GetGroup(LinePtr);
                //aa.ReadActions(CurrentGroup);      /* Get article action list */
                uLastArtNum = GroupDoc->uLoadFirst;
                if (g_pAction->NeedNgroupHeader() || GroupDoc->pAA->NeedNgroupHeader())
                {       /* Find out if we need newsgroups */
                    total_xhdrs = 2;
                }
                else {
                    total_xhdrs = 1;
                }
                GlobalUnlock (BlockPtr->hCurBlock);
              }
            }
      }
      else {
        LockLine(CommDoc->hParentBlock, CommDoc->ParentOffset,
          CommDoc->ParentLineID, &BlockPtr, &LinePtr);
        GroupDoc = GetGroup(LinePtr);
        mylen = sprintf (mybuf, "XHDR from %ld-%ld", GroupDoc->uLoadFirst, GroupDoc->uLoadLast);
        CommState = ST_XHDR_FROM_START;
        PutCommLine (mybuf);
        GlobalUnlock (BlockPtr->hCurBlock);
      }
      break;

    case ST_XOVER_DATA:
		char * refer;

    if(strcmp(CommLineIn, ".") == 0)
    {
      if(LockLine(CommDoc->hParentBlock, CommDoc->ParentOffset,
                 CommDoc->ParentLineID, &BlockPtr, &LinePtr))
    {
      GroupDoc = GetGroup(LinePtr);
      GroupDoc->total_headers = CommDoc->ActiveLines;
      //first = GroupDoc->ServerFirst;      
      //last = GroupDoc->ServerLast;
      
      if(uLastArtNum < GroupDoc->uLoadLast)
      {
        GroupDoc->pSeendb->MarkSeen(uLastArtNum + 1, GroupDoc->uLoadLast);
      }
      if(bEnableArticleAction &&  /* Find out if we need newsgroups */
        (g_pAction->NeedNgroupHeader() || GroupDoc->pAA->NeedNgroupHeader()))
      {
        /* Now ask for the newsgroups */
        CommDoc->ActiveLines = 0;
        mylen = sprintf (mybuf, "XHDR newsgroups %ld-%ld", GroupDoc->uLoadFirst, GroupDoc->uLoadLast);
        CommState = ST_XHDR_NEWSGROUPS_START;
        PutCommLine (mybuf);
      }
      else
      {
        CommState = ST_IN_GROUP;
        CommBusy = FALSE;
        finish_header_retrieval();
        SetStatbarText (CommDoc->hWndFrame,  "", CommDoc, TRUE, TRUE);
      }
      GlobalUnlock (BlockPtr->hCurBlock);
      }
    }
      else if (*CommLineIn) {   /* avoid blank XOVER lines (peterk@@borland.com) */
        char *this_hop, *next_hop;

        if(LockLine (CommDoc->hParentBlock, CommDoc->ParentOffset,
          CommDoc->ParentLineID, &BlockPtr, &LinePtr))
        {
          GroupDoc = GetGroup(LinePtr);

          if(CommDoc->ActiveLines > CommDoc->TotalLines)
          {
            GroupDoc->uLoadLast = min(GroupDoc->uLoadLast, uLastArtNum - 1);
          }
        else
        {
        header_handle = GroupDoc->header_handle;
        thread_handle = GroupDoc->thread_handle;
        //header->sNewsgroups = "";

        /* 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);
        if(uLastArtNum < header->number - 1)
          GroupDoc->pSeendb->MarkSeen(uLastArtNum + 1, header->number - 1);
        uLastArtNum = header->number;

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

        header->pHD->sSubject = this_hop;
        CommDoc->LongestLine = max (CommDoc->LongestLine,
                                    ArtListSeparator3 / CharWidth +
                                    (unsigned) lstrlen (header->pHD->sSubject));

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

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

        if (FullNameFrom)
          header->pHD->sFrom = NameString;
        else
          header->pHD->sFrom = this_hop;
        header->pHD->sEmail = AddressString;

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

        header->pHD->date = parse_usenet_date (this_hop);

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

		if(this_hop+1)
			header->pHD->sMessage_id = message_id_with_frob(this_hop+1);
		else
			header->pHD->sMessage_id = "";

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

		// stop GPF's when reference line is messed up  (JD 2/17/97) 
		// header->pHD->sBest_ref = message_id_with_frob(get_best_reference(this_hop));
		refer = get_best_reference(this_hop);
		if (refer) {
          header->pHD->sBest_ref = message_id_with_frob(refer);
          build_ref_list(header->pHD->sFrob_ref_list, this_hop);
		}

        /* 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->ArtDoc = (TypDoc *) NULL;
        header->SeenSelected = WasArtSeen (header->number, GroupDoc) ? ART_SEEN : ART_UNSEEN_UNSELECTED;

        unlock_headers (header_handle, thread_handle);

        CommDoc->ActiveLines++;

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

      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);

      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);
        /* we do this here to allow mid-session change-of-mind */
        total_xhdrs = threadp ? 6 : 4;
        if (bEnableArticleAction &&  /* Find out if we need newsgroups */
          (g_pAction->NeedNgroupHeader() || GroupDoc->pAA->NeedNgroupHeader()))
        {
          total_xhdrs++;
        }
        if(uLastArtNum < GroupDoc->uLoadLast)
        {
          GroupDoc->pSeendb->MarkSeen(uLastArtNum + 1, GroupDoc->uLoadLast);
        }
        GroupDoc->total_headers = CommDoc->ActiveLines;

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

        CommDoc->ActiveLines = 0;

        /* Now ask for the date lines */
        mylen = sprintf (mybuf, "XHDR date %ld-%ld", GroupDoc->uLoadFirst, GroupDoc->uLoadLast);
        CommState = ST_XHDR_DATE_START;
        PutCommLine (mybuf);
        GlobalUnlock (BlockPtr->hCurBlock);
      }
      else {

        /* Access the Group struct, get HANDLE for header data */
        LockLine (CommDoc->hParentBlock, CommDoc->ParentOffset,
                  CommDoc->ParentLineID, &BlockPtr, &LinePtr);

        GroupDoc = GetGroup(LinePtr);

        if(CommDoc->ActiveLines > CommDoc->TotalLines)
        {
          GroupDoc->uLoadLast = min(GroupDoc->uLoadLast, uLastArtNum - 1);
        }
        else
        {

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

        /* Lock the header data */
        headers = lock_headers (header_handle, thread_handle);
        sscanf (CommLineIn, "%ld", &artnum);
        header = header_elt (headers, CommDoc->ActiveLines);
        header->number = artnum;
        if(uLastArtNum < header->number - 1)
          GroupDoc->pSeendb->MarkSeen(uLastArtNum + 1, header->number - 1);
        uLastArtNum = header->number;
        
        //header->sSubject = header->sFrom = header->sEmail = header->sMessage_id =
        // header->sBest_ref = header->sFrob_ref_list = header->sNewsgroups = "";
        header->ArtDoc = (TypDoc *) NULL;
        header->SeenSelected = WasArtSeen (artnum, GetGroup(LinePtr)) ? ART_SEEN : ART_UNSEEN;
        
        /* now use some of our nice formatting of email addresses */
        ParseAddress (get_xhdr_line (CommLineIn), header->pHD->sEmail, header->pHD->sFrom);
        
        if(!FullNameFrom)
          header->pHD->sFrom = header->pHD->sEmail;
        
        unlock_headers (header_handle, thread_handle);
        CommDoc->ActiveLines++;
        update_window_title (CommDoc->hWndFrame, group,
          RcvLineCount++,
          CommDoc->TotalLines * total_xhdrs);
        GlobalUnlock (BlockPtr->hCurBlock);
        }
      }

      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;
        CommDoc->ActiveLines = 0;

            /* Now ask for the #of lines */
        mylen = sprintf (mybuf, "XHDR lines %ld-%ld", GroupDoc->uLoadFirst, GroupDoc->uLoadLast);
        CommState = ST_XHDR_LINES_START;
        PutCommLine (mybuf);
        GlobalUnlock (BlockPtr->hCurBlock);
      }
      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))->pHD->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;

        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", GroupDoc->uLoadFirst, GroupDoc->uLoadLast);
          PutCommLine (mybuf);
        }
        else {
          CommState = ST_XHDR_SUBJECT_START;
          mylen = sprintf (mybuf, "XHDR subject %ld-%ld", GroupDoc->uLoadFirst, GroupDoc->uLoadLast);
          PutCommLine (mybuf);
        }
        GlobalUnlock (BlockPtr->hCurBlock);
      }

      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 %Fhd", &artnum, &((header_elt (headers, syncnum))->lines));
        CommDoc->ActiveLines++;
      }
          unlock_headers (header_handle, thread_handle);
          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", GroupDoc->uLoadFirst, GroupDoc->uLoadLast);
        CommState = ST_XHDR_MID_START;
        PutCommLine (mybuf);
      }
      else {
        header_p header;
        char far *sLine;
		char * 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);
          
          /* for now, we only pay attention to first (whole) referral */
          sLine = get_xhdr_line (CommLineIn);
          
          /* 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);
	     // stop GPF's when reference line is messed up  (JD 2/17/97) 
		 // header->pHD->sBest_ref = message_id_with_frob(get_best_reference(sLine));
			refer = get_best_reference(sLine);
			if (refer) {

			  header->pHD->sBest_ref = message_id_with_frob(refer);
			  build_ref_list(header->pHD->sFrob_ref_list, sLine);
            }
            CommDoc->ActiveLines++;
          }
          
          unlock_headers (header_handle, thread_handle);
          
          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;
        CommDoc->ActiveLines = 0;

        /* Now ask for the subject lines */
        mylen = sprintf (mybuf, "XHDR subject %ld-%ld", GroupDoc->uLoadFirst, GroupDoc->uLoadLast);
        CommState = ST_XHDR_SUBJECT_START;
        PutCommLine (mybuf);
        GlobalUnlock (BlockPtr->hCurBlock);
      }
      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))->pHD->sMessage_id =
          message_id_with_frob((char far *) (get_xhdr_line (CommLineIn)+1));
        CommDoc->ActiveLines++;
            }
            unlock_headers (header_handle, thread_handle);

            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)
    {
    if(LockLine(CommDoc->hParentBlock, CommDoc->ParentOffset,
                 CommDoc->ParentLineID, &BlockPtr, &LinePtr))
    {
      GroupDoc = GetGroup(LinePtr);

      GroupDoc->total_headers = CommDoc->ActiveLines;
      //first = GroupDoc->ServerFirst;
      //last = GroupDoc->ServerLast;

      if (bEnableArticleAction &&
         (g_pAction->NeedNgroupHeader() || GroupDoc->pAA->NeedNgroupHeader())) {             /* Find out if we need newsgroups */
            /* Now ask for the newsgroups */
          CommDoc->ActiveLines = 0;
          mylen = sprintf (mybuf, "XHDR newsgroups %ld-%ld", GroupDoc->uLoadFirst, GroupDoc->uLoadLast);
          CommState = ST_XHDR_NEWSGROUPS_START;
          PutCommLine (mybuf);
      }
      else {
            CommState = ST_IN_GROUP;
            CommBusy = FALSE;
            finish_header_retrieval();
            SetStatbarText (CommDoc->hWndFrame, "", CommDoc, TRUE, TRUE);
      }
      GlobalUnlock (BlockPtr->hCurBlock);  
      }
    }
      else {

    if(LockLine(CommDoc->hParentBlock, CommDoc->ParentOffset,
                 CommDoc->ParentLineID, &BlockPtr, &LinePtr))
    {
      GroupDoc = GetGroup(LinePtr);

        artnum = 0;
        sscanf (CommLineIn, "%ld", &artnum);
        if (artnum) {
          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);
        header->pHD->sSubject = get_xhdr_line (CommLineIn);
        //CommDoc->LongestLine = max (CommDoc->LongestLine,
        //                            ARTICLE_SUBJECT_OFFSET +
        //                            (unsigned) lstrlen (header->subject));
            CommDoc->LongestLine = max (CommDoc->LongestLine,
                                                        ArtListSeparator3 / CharWidth +
                                                      (unsigned) lstrlen (header->pHD->sSubject));
        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)
        {
          const char* sTmp;
          header = header_elt (headers, syncnum);
          header->pHD->sNewsgroups = sTmp = get_xhdr_line(CommLineIn);
          header->pHD->iGroups = 1;
          while(sTmp = strchr(sTmp, ','))
          {
            header->pHD->iGroups++;
            sTmp++;
          }
          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 (strcmp (CommLineIn, ".") == 0) {
//      ;                       /* error: empty article (end in middle of 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 *) (const char far*)((header_elt (headers, CommDoc->LastSeenLineID))->pHD->sSubject);
                unlock_headers (header_handle, thread_handle);

                SetStatbarText (CommDoc->hWndFrame, "", CommDoc, TRUE, TRUE);
                mylstrncpy (group, lpsz, MAXGROUPNAME);
                sprintf (mybuf, "%s (%lu 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);
    PercentDone = (int)((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)
{
  return GroupPtr->pSeendb->WasSeen(ArtNum) ? ART_SEEN : ART_UNSEEN;
}

/*-- function CalcNumUnread ---------------------------------------------
 *
 *  Determines number of unseen articles in a group
 *
 *  jsc 11/12/94
 */
unsigned long 
CalcNumUnread(TypGroup far *GroupPtr)
{
  return GroupPtr->pSeendb->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 */
      g_pAction->ActOnArticles(GroupDoc, headers);
      //GroupDoc->pAA->ReadActions();  /* Get article action list */
      GroupDoc->pAA->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->SeenSelected)
          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;
    }

    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 (%lu 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 long 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) */
      i = 0;
      if (CommDoc->TotalLines > 0) {
        for (i = 0;
             (i < CommDoc->TotalLines && (ART_SEENORKILLED & (header_elt (headers, i))->SeenSelected));
             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';
}
/* ------------------------------------------------------------------------
 * Subsitute all occurences of character "oldchar" in string with
 * character "newchar" in string  (JD 3/27/97
 */
void
SubsituteCharInString(char *str, char oldchar, char newchar)
{
  register int i;

  for (i = strlen (str) - 1; i > 0; i--)
  {
    if (str[i] == oldchar) 
	    str[i] = newchar;
  }
}

/*------------------------------------------------------------------------------
 * IsBlankStr
 * Returns true if the string is entirely whitespace, else false
 * JSC 12/6/93
 */
BOOL
IsBlankStr (const char *temp)
{
  register const 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
 */
extern "C" BOOL
WritePrivateProfileInt (const char far * lpAppName, const char far * lpKeyName, int val, const 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 (const char far * lpAppName, const char far * lpKeyName, unsigned int val, const 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, (int) oldPosition, 0, NULL);
    LineTo(hDc, (int) oldPosition, clientRect.bottom);
  }
  if (flag == 2) {
    MoveToEx(hDc, (int) newPosition, 0, NULL);
    LineTo(hDc, (int) newPosition, clientRect.bottom);
  }
  SetROP2(hDc, nOrigDrawMode);
  DeleteObject(SelectObject(hDc,hOrigPen));
  ReleaseDC(hWnd, hDc);
}

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


1.105
log
@added a new routine to subsutite all chars in a string with some
other character.
@
text
@d3 1
a3 1
 * $Id: wvutil.cpp 1.104 1997/03/07 02:21:14 dumoulin Exp $
d662 2
a663 2
   //   while(start)
	  while(start && !strchr(start+1,'>'))
@


1.104
log
@initialized some string classes with "" instead of a null char
@
text
@d3 1
a3 1
 * $Id: wvutil.cpp 1.103 1997/02/28 19:27:23 dumoulin Exp $
d107 24
d2383 15
@


1.103
log
@changed a number of longs to uint32's
@
text
@d3 1
a3 1
 * $Id: wvutil.cpp 1.102 1997/02/14 20:12:31 dumoulin Exp $
a11 2
//extern "C"
//{
a13 1
//}
d15 2
a16 1
#include "WVClass.h"
d503 1
a503 1

d616 1
a616 1
	  Latest.sMessage_id = 0;
d1207 1
a1207 1
			header->pHD->sMessage_id = 0;
d1510 1
a1510 1
        
a1530 1
	
d1817 1
a1817 1
                sprintf (mybuf, "%s (%u lines)", group, CommDoc->TotalLines);
d2266 1
a2266 1
  sprintf (mybuf, "%s (%u articles)", CurrentGroup, CommDoc->TotalLines);
@


1.102
log
@Add patches for WinVN .99-9 Beta 2
@
text
@d3 1
a3 1
 * $Id: wvutil.cpp 1.101 1997/02/08 11:02:25 dumoulin Exp $
d473 1
d499 1
a499 2
WVString
message_id_with_frob(const char * source)
d505 1
a505 1
  
d538 9
a546 8
  // if there is no valid reference, don't return a string object
  // just return null.  This fixes problems with the string class
  // releasing hugh chunks of memory when you allocate an object
  // of zero length.  (JD 2/14/97)

  if(source == 0) {
	return 0;
    }
d548 1
a548 1
return WVString(sRetval);
d615 4
a618 1
    Latest.sMessage_id = message_id_with_frob(sTmp);
d635 1
a635 1
    sTmp = new char[1 + strlen(src)];
d640 2
a641 1
      while(start)
d645 9
a653 3
        start = strchr(++start, '>');
        if(start)
          start = strchr(start, '<');
d1103 2
d1206 4
a1209 1
        header->pHD->sMessage_id = message_id_with_frob(this_hop+1);
d1216 7
a1222 2
        header->pHD->sBest_ref = message_id_with_frob(get_best_reference(this_hop));
        build_ref_list(header->pHD->sFrob_ref_list, this_hop);
d1511 1
d1533 9
a1541 2
            header->pHD->sBest_ref = message_id_with_frob(get_best_reference(sLine));
            build_ref_list(header->pHD->sFrob_ref_list, sLine);            
@


1.101
log
@fix datecode for the year 2000
@
text
@d3 1
a3 1
 * $Id: wvutil.cpp 1.100 1997/02/06 21:18:08 dumoulin Exp dumoulin $
d506 1
d509 15
a523 6
    if('<' == *source) // skip any leading '<'
      source++;
    strncpy(sRetval, source, HEADER_MESSAGE_ID_LENGTH);
    sRetval[HEADER_MESSAGE_ID_LENGTH] = '\0';
    if(pos = strcspn(sRetval, "> \t")) // chop trailing whitespace and '>'
      sRetval[pos] = 0;
d525 2
a526 5
    // only frob if we need to
    if(strlen(sRetval) >= HEADER_MESSAGE_ID_LENGTH - 1)
    {
      sum = 0;
      for (p = source; *p; p++)
d528 6
a533 1
        sum = (sum << 1) + *p;
d535 10
a544 1
      sprintf(sRetval+HEADER_MESSAGE_ID_LENGTH-8, "%08x", sum);
d546 2
a547 6
  }
  else
  {
    sRetval[0] = '\0';
  }
  return WVString(sRetval);
a549 1

d935 1
a935 1
        if (!ShowUnreadOnly) {
d939 1
d953 2
a954 1
          arts_to_retrieve = article_threshold;
@


1.100
log
@change many ints to longs
@
text
@d3 1
a3 1
 * $Id: wvutil.cpp 1.99 1996/08/31 07:03:27 dumoulin Exp $
d262 7
a268 2
    if (yr < 100)
      yr += 1900;
d912 2
a913 1
        GroupDoc->uLoadFirst = GroupDoc->pSeendb->LowestUnseen();
d949 3
a951 3
		//   Change the way we retrieve selected number of articles.   If user picks
	    //   100 articles, grab the latest 100 articles instead of the 1st 100 articles
		//                                           (JD 12/31/96)
d953 1
a953 1
        //  GroupDoc->uLoadLast = min(GroupDoc->ServerLast, GroupDoc->uLoadFirst + arts_to_retrieve - 1);
d959 2
a960 1
          GroupDoc->uLoadFirst = GroupDoc->pSeendb->LowestUnseen();
@


1.99
log
@fixed bug with parse_date.
@
text
@d3 1
a3 1
 * $Id: wvutil.cpp 1.98 1996/08/13 05:07:03 dumoulin Exp $
d11 3
a13 3
#include <windowsx.h>			// for GlobalFreePtr (JSC)
extern "C"
{
d16 1
a16 1
}
d19 1
a19 1
#include <commdlg.h>			// for GetOpenFileName dialog (JSC)
d24 1
a24 1
#include <dos.h>				// for _dos_getdiskfree
d26 1
a26 1
#include <io.h>					// for _open etc
d57 1
a57 1
GetNum (char **ptr, long int *num)
d64 1
a64 1
	(*ptr)++;
d68 3
a70 3
	*num = 10 * (*num) + (**ptr - '0');
	gotit = TRUE;
	(*ptr)++;
d94 2
a95 2
	left = strchr (in, '(');
	right = strrchr (in, ')');
d97 6
a102 6
	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);
d104 2
a105 2
  else							/* !FullNameFrom */
	strcpy (out, in);
d118 1
a118 1
  long int lred, lgreen, lblue;
d142 2
a143 2
		   GetGValue (rgbVal),
		   GetBValue (rgbVal));
d151 3
a153 3
#define SIZEOF(array)	((int)(sizeof array / sizeof array[0]))
#define ENDOF(array)	(&array[SIZEOF(array)])
#define HOUR(x)		(x * 60)
d161 50
a210 50
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 }
d216 1
a216 1
	*p = tolower (*p);
d219 2
a220 2
	char cbak;
	TABLE *tp;
d222 2
a223 2
	cbak = *p;
	*p = '\0';
d225 6
a230 6
    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;
d235 4
a238 4
	int hour, min;
	hour = tz / 100;
	min = tz % 100;
	return ((size_t)(hour * 3600 + min * 60));
d254 1
a254 1
	return (0);
d256 1
a256 1
	s = ++cp;
d258 1
a258 1
	s++;
d261 3
a263 3
	sscanf (s, "%d %s %d %d:%d:%d %s", &dom, mon, &yr, &hr, &mn, &sc, tzone);
	if (yr < 100)
	  yr += 1900;
d266 1
a266 1
	sscanf (s, "%*s %d %d:%d:%d %d", mon, &dom, &hr, &mn, &sc, &yr);
d269 1
a269 1
	return (0);
d271 1
a271 1
	return (0);
d273 1
a273 1
	return (0);
d275 1
a275 1
	return (0);
d277 1
a277 1
	return (0);
d279 1
a279 1
	return (0);
d281 1
a281 1
	return (0);
d284 1
a284 1
	*cp = tolower (*cp);
d287 1
a287 1
	mth = (cp - fmtMonthTable) / 3;
d309 1
a309 1
	datetime = localtime (&time);
d311 1
a311 1
	if (fmtDaysB4Mth) {
d315 2
a316 2
	}
	else {
d320 2
a321 2
	}
	return (s);
d324 1
a324 1
	return ("-----");
d337 12
a348 12
	if (ch == IgnoreCommCh) {
	}
	else if (ch == EOLCommCh) {
	  *CommLinePtr = '\0';
	  DoCommState ();
	  CommLinePtr = CommLineIn;
	}
	else {
	  *(CommLinePtr++) = (char) ch;
	  if (CommLinePtr == CommLineLWAp1)
		CommLinePtr--;
	}
d354 3
a356 3
					 char *group_name,
					 unsigned long line_number,
					 unsigned long total_lines)
d364 1
a364 1
	prevPercent = 0;
d367 4
a370 4
	  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);
d372 2
a373 2
	  prevPercent = newPercent;
	}
d384 1
a384 1
	hAlertWnd = ComposeWnd->hWnd;
d387 1
a387 1
	hAlertWnd = CommDoc->hWndFrame;
d390 1
a390 1
	hAlertWnd = NetDoc.hWndFrame;
d395 5
a399 5
	CommBusy = FALSE;
	CommState = ST_NONE;
	MessageBox (hAlertWnd, "Error On News Server", "WinVN", MB_OK | MB_ICONHAND);
	return (1);
	break;
d401 6
a406 6
	CommBusy = FALSE;
	CommState = ST_NONE;
	MessageBox (hAlertWnd, CommLineIn, "Message From News Server", MB_OK | MB_ICONHAND);
	switch (iClass) {
	case 400:
	  /* service discontinued */
d408 8
a415 8
	  //      PostQuitMessage (0);
	  Disconnect ();
	  break;
	default:
	  break;
	}
	return (1);
	break;
d429 3
a431 3
sync_artnum (long artnum,
			 long activenum,
			 header_p headers, TypGroup far * GroupDoc)
d435 1
a435 1
	return (headerloc);
d437 6
a442 6
	while ((artnum != activenum) && (headerloc > 0)) {
	  headerloc--;
	  if ((header_elt (headers, headerloc))->number == artnum)
		return (headerloc);
	}
	return (-1);
d445 6
a450 6
	while ((artnum != activenum) && (headerloc < GroupDoc->total_headers - 1)) {
	  headerloc++;
	  if ((header_elt (headers, headerloc))->number == artnum)
		return (headerloc);
	}
	return (-1);
d454 5
d464 2
a465 2
	// no valid ref here without a '<'
	return NULL;
d467 10
a476 10
	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;
	}
d493 2
a494 2
void
copy_message_id_with_frob (char * dest, const char * source)
d497 5
a501 6

  if(source && ('<' == *source)) // chop leading '<'
    source++;
  strncpy(dest, source, HEADER_MESSAGE_ID_LENGTH-1);
  pos = strcspn(dest, "> \t" );  // chop trailing whitespace and '>'
  if(pos)
d503 17
a519 1
    dest[pos] = 0;
d521 3
a523 9

  // 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);
d525 1
d532 21
a552 3
  memset(&Latest, 0, sizeof(Latest));
//  AddressString[0] = 0;
//  NameString[0] = 0;
d569 1
a569 3
//    if(!_strnicmp("Re: ", sTmp, 4))
//      sTmp += 4;
    mylstrncpy(Latest.subject, sTmp, sizeof(Latest.subject));
d573 1
a573 3
	  ParseAddress(sTmp,
	               Latest.email, sizeof(Latest.email),
	               Latest.from, sizeof(Latest.from));
d582 7
a588 1
    mylstrncpy(Latest.newsgroups, sTmp, sizeof(Latest.newsgroups));
d593 1
a593 1
    copy_message_id_with_frob(Latest.message_id, sTmp);
d599 5
a603 1
void build_ref_list(char*dest, const char*src)
d605 1
a605 2
  char sRetval[HEADER_FROBREFLIST_LENGTH + HEADER_MESSAGE_ID_LENGTH + 2];
  char sTmpMsgId[HEADER_MESSAGE_ID_LENGTH];
d607 1
a607 2

  dest[0] = 0;
d610 2
a611 3
    sRetval[0] = 0;
    start = strchr(src, '<');
    while(start && strlen(sRetval) < HEADER_FROBREFLIST_LENGTH)
d613 12
a624 4
      copy_message_id_with_frob(sTmpMsgId, start);
      strcat(sRetval, sTmpMsgId);
      strcat(sRetval, " ");
      start = strchr(++start, '<');
a625 1
    mylstrncpy(dest, sRetval, HEADER_FROBREFLIST_LENGTH);
d627 2
d631 1
d652 3
a654 2
  static long first, last;
  long estnum, artnum;
d663 1
a663 1
  char far *lpsz;
d669 1
a669 1
  TypRange *RangePtr;
d673 1
a673 1
  WVArticleAction aa;
d679 88
a766 88
	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"
d768 1
a768 1
		  "  %s.\n"
d770 301
a1070 277
		  "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)))
d1074 23
a1096 2
      first = GroupDoc->ServerFirst;      
      last = GroupDoc->ServerLast;
d1098 401
d1500 60
d1561 14
a1574 488
     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)))
d1577 1
d1579 17
a1595 2
      first = GroupDoc->ServerFirst;
      last = GroupDoc->ServerLast;
a1596 16

	  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);
 		    }
d1598 1
d1600 21
a1620 27
			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);
d1624 7
a1630 8
        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));
d1633 2
a1634 2
		  UnlockLine (BlockPtr, LinePtr, &(CommDoc->hParentBlock),
					  &(CommDoc->ParentOffset), &(CommDoc->ParentLineID));
d1636 4
a1639 4
		  unlock_headers (header_handle, thread_handle);
		  update_window_title (CommDoc->hWndFrame, group,
							   RcvLineCount++,
							   CommDoc->TotalLines * total_xhdrs);
d1641 4
a1644 4
		}
	  }

	  break;
d1646 1
a1646 1
	case ST_XHDR_NEWSGROUPS_START:
d1655 1
a1655 1
	case ST_XHDR_NEWSGROUPS_DATA:
d1681 1
d1683 7
a1689 2
          mylstrncpy(header->newsgroups,
                     get_xhdr_line(CommLineIn), HEADER_NEWSGROUPS_LENGTH);
d1700 111
a1810 2
	case ST_IN_GROUP:
	  break;
d1812 67
a1878 173
	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;
	}
d1888 1
a1888 1
	loc = (char*) memchr (textptr, '|', 2);
d1890 1
a1890 1
	loc = (char*) memchr (textptr, ':', 2);
d1911 1
a1911 1
  	maxlen = MAXHEADERLINE;
d1913 1
a1913 1
  	maxlen = MAXINTERNALLINE;
d1923 11
a1933 11
	   cptr++) {
	if (*cptr == '\t') {
	  do {
		*(cdest++) = ' ';
	  }
	  while (++col & 7);
	}
	else {
	  *(cdest++) = *cptr;
	  col++;
	}
d1940 1
a1940 1
  	mylen = (cdest - artline) + sizeof (int);
d1945 3
a1947 15
  	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));
	}
d1949 12
d1962 2
a1963 1
    PercentDone = MulDiv(CommDoc->ActiveLines, 100, CommDoc->CountedLines);
d1968 1
a1968 1
	InvalidateRect (CommDoc->hDocWnd, NULL, FALSE);
d1980 1
a1980 1
	start = CommLineIn;
d1983 1
a1983 1
	start = CommLineIn + 1;
d1986 1
a1986 1
	DecodeLine (currentCoded, start);
d1989 2
a1990 2
  			*start == '\0') {
	AddCommLineToDoc (start);
d1993 14
a2006 14
	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;
	}
d2022 15
a2036 15
	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 */
	}
d2038 1
a2038 1
  return FALSE;					/* don't trim it */
d2051 1
a2051 13
  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);
d2063 1
a2063 34
	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;
d2073 6
a2078 6
	if (*ptr1 > *ptr2) {
	  return (1);
	}
	else if (*ptr1 < *ptr2) {
	  return (-1);
	}
d2092 1
a2092 1
	*ptr1 = *ptr2;
d2103 1
a2103 1
	*ptr1 = *ptr2;
d2121 6
a2126 6
	if (*s1 > *s2) {
	  return (1);
	}
	else if (*s1 < *s2) {
	  return (-1);
	}
d2129 1
a2129 1
	return (0);
d2132 1
a2132 1
	return (1);
d2135 1
a2135 1
	return (-1);
d2167 1
a2167 1
			    CommDoc->ParentLineID, &BlockPtr, &LinePtr)) {
d2169 5
a2173 5
  	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)));
d2179 6
a2184 3
      WVArticleAction aa;
      aa.ReadActions(CurrentGroup);  /* Get article action list */
      aa.ActOnArticles(GroupDoc, headers);  /* Perform article actions */
d2193 1
a2193 1
        if(ART_SELECTED & hp->Selected)
d2198 1
a2198 1
//  	iSortOption = IDM_SORT_ARTNUM;
d2200 3
a2202 3
  	if (threadp) {
		SetWindowText (CommDoc->hWndFrame, "sorting headers...");
//    	iSortOption = IDM_SORT_THREADSUB; // we're testing this as the default.
d2204 1
a2204 1
  	}
a2205 3
//  	lstrcpy(szTmp,"G: ");
//  	lstrcat(szTmp, CurrentGroup);
//  	iSortOption = GetPrivateProfileInt(szTmp, "SortOption", iSortOption, szAppProFile);
d2209 2
a2210 2
  	sort_by_option(headers, thread_index, threadp, CommDoc->TotalLines,
        	header_handle, thread_handle);
d2212 1
a2212 1
  	unlock_headers (header_handle, thread_handle);
d2223 1
a2223 1
	unsigned int i;
d2225 2
a2226 2
	if (LockLine (CommDoc->hParentBlock, CommDoc->ParentOffset,
			      CommDoc->ParentLineID, &BlockPtr, &LinePtr)) {
d2228 5
a2232 5
	  /* 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);
d2235 19
a2253 18
	  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);
	  }
d2275 3
a2277 3
	if (WndEdits[ih].hWnd == hWnd) {
	  return &WndEdits[ih];
	}
d2292 1
a2292 1
	compWnd = getWndEdit (WndMails, hWnd, MAXMAILWNDS);
d2306 1
a2306 1
	str[i] = '\0';
d2315 1
a2315 1
IsBlankStr (char *temp)
d2317 1
a2317 1
  register char *ptr;
d2319 2
a2320 2
	if (!isspace (*ptr))
	  return (FALSE);
d2335 2
a2336 2
	if (!isdigit (*ptr))
	  return (FALSE);
d2352 1
a2352 1
	l++;
d2355 1
a2355 1
	*d = '\0';
d2388 4
a2391 4
	cf.nFontType = PRINTER_FONTTYPE;
	hDC = GetPrinterDC (hParentWnd);
	cf.hDC = hDC;
	cf.Flags = CF_PRINTERFONTS | CF_INITTOLOGFONTSTRUCT | CF_FORCEFONTEXIST;
d2394 3
a2396 3
	cf.nFontType = SCREEN_FONTTYPE;
	cf.Flags = CF_SCREENFONTS | CF_INITTOLOGFONTSTRUCT | CF_USESTYLE | CF_FORCEFONTEXIST;
	cf.lpszStyle = style;
d2399 1
a2399 1
	return (FAIL);
d2407 1
a2407 1
  *size = cf.iPointSize / 10;	/* iPointSize is in tenths of a point */
d2432 1
a2432 1
	return (FAIL);
d2439 2
a2440 2
	MessageBox (hParentWnd, "WinVN does not currently support dithered (non-solid) colors.\nThe nearest physical solid color has been selected.",
				"Sorry", MB_OK | MB_ICONINFORMATION);
d2465 2
a2466 2
	MessageBox (hWndEdit, "Memory allocation failure", "Edit Text", MB_OK);
	return (NULL);
d2471 2
a2472 2
	MessageBox (hWndEdit, "Failed to get text", "Edit Text", MB_OK);
	return (NULL);
d2486 2
a2487 2
BOOL
WritePrivateProfileInt (char far * lpAppName, char far * lpKeyName, int val, char far * lpProFile)
d2500 1
a2500 1
WritePrivateProfileUInt (char far * lpAppName, char far * lpKeyName, unsigned int val, char far * lpProFile)
d2516 1
a2516 1
	return (atoui (buf));
d2518 1
a2518 1
	return (val);
d2538 1
a2538 1
	return (atol (buf));
d2540 1
a2540 1
	return (val);
d2553 7
a2559 7
	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);
	}
d2568 7
a2574 7
	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);
	}
d2583 3
a2585 3
	if (WndPosts[i].hWnd) {
	  ResizeComposeControls (&WndPosts[i], 0, 0);
	}
d2588 3
a2590 3
	if (WndMails[i].hWnd) {
	  ResizeComposeControls (&WndMails[i], 0, 0);
	}
d2599 5
a2603 5
	if (CodingStatusText[i]->hTextWnd) {
	  SetHandleBkBrush (CodingStatusText[i]->hTextWnd, hStatusBackgroundBrush);
	  SendMessage (CodingStatusText[i]->hTextWnd, WM_SIZE, 0, 0L);
	  InvalidateRect (CodingStatusText[i]->hTextWnd, NULL, TRUE);
	}
d2605 1
a2605 1
	RefreshCodedBlockWnd ();
d2629 2
a2630 2
	if (GroupDocs[i].InUse && GroupDocs[i].hDocWnd && (!CommBusy || &GroupDocs[i] != CommDoc))
	  SendMessage (GroupDocs[i].hWndFrame, WM_CLOSE, 0, 0L);
d2639 2
a2640 2
	if (ArticleDocs[i].InUse && ArticleDocs[i].hDocWnd && (!CommBusy || &ArticleDocs[i] != CommDoc))
	  SendMessage (ArticleDocs[i].hWndFrame, WM_CLOSE, 0, 0L);
d2649 2
a2650 2
	if (WndPosts[i].hWnd && !WndPosts[i].busy)
	  SendMessage (WndPosts[i].hWnd, WM_CLOSE, 0, 0L);
d2653 2
a2654 2
	if (WndMails[i].hWnd && !WndMails[i].busy)
	  SendMessage (WndMails[i].hWnd, WM_CLOSE, 0, 0L);
d2664 4
a2667 4
	if (!CodingStatusText[numSkipped]->IsBusy)
	  SendMessage (CodingStatusText[numSkipped]->hTextWnd, WM_CLOSE, 0, 0L);
	else
	  numSkipped++;
d2670 4
a2673 4
	MessageBox (CodingStatusText[0]->hTextWnd,
				"Please wait until en/decoding is complete",
				"Cannot close status window", MB_OK | MB_ICONSTOP);
	break;
d2676 1
a2676 1
	SendMessage (CodingStatusText[0]->hTextWnd, WM_CLOSE, 0, 0L);
d2709 2
a2710 2
  maxX = 3 * (width >> 1);		/* 3/4 screen width  */
  maxY = 3 * (height >> 1);		/* 3/4 screen height */
d2715 4
a2718 4
	if (GroupDocs[i].InUse && GroupDocs[i].hWndFrame && !IsMinimized (GroupDocs[i].hWndFrame)) {
	  prevWnd = CascadeWnd (GroupDocs[i].hWndFrame, prevWnd, nthWnd, width, height, maxX, maxY);
	  nthWnd++;
	}
d2721 4
a2724 4
	if (ArticleDocs[i].InUse && ArticleDocs[i].hWndFrame && !IsMinimized (ArticleDocs[i].hWndFrame)) {
	  prevWnd = CascadeWnd (ArticleDocs[i].hWndFrame, prevWnd, nthWnd, width, height, maxX, maxY);
	  nthWnd++;
	}
d2727 4
a2730 4
	if (WndPosts[i].hWnd && !IsMinimized (WndPosts[i].hWnd)) {
	  prevWnd = CascadeWnd (WndPosts[i].hWnd, prevWnd, nthWnd, width, height, maxX, maxY);
	  nthWnd++;
	}
d2733 4
a2736 4
	if (WndMails[i].hWnd && !IsMinimized (WndMails[i].hWnd)) {
	  prevWnd = CascadeWnd (WndMails[i].hWnd, prevWnd, nthWnd, width, height, maxX, maxY);
	  nthWnd++;
	}
d2739 4
a2742 4
	if (CodingStatusText[i]->hTextWnd && !IsMinimized (CodingStatusText[i]->hTextWnd)) {
	  prevWnd = CascadeWnd (CodingStatusText[i]->hTextWnd, prevWnd, nthWnd, width, height, maxX, maxY);
	  nthWnd++;
	}
d2746 1
a2746 1
	SetWindowPos (hCodedBlockWnd, (HWND) NULL, (xScreen - STATUSWIDTH) >> 1, 1, STATUSWIDTH, STATUSHEIGHT, SWP_DRAWFRAME);
d2770 3
a2772 3
	if (GroupDocs[i].InUse && GroupDocs[i].hDocWnd && !IsMinimized (GroupDocs[i].hWndFrame)) {
	  ShowWindow (GroupDocs[i].hWndFrame, SW_MINIMIZE);
	}
d2781 3
a2783 3
	if (ArticleDocs[i].InUse && ArticleDocs[i].hDocWnd && !IsMinimized (ArticleDocs[i].hWndFrame)) {
	  ShowWindow (ArticleDocs[i].hWndFrame, SW_MINIMIZE);
	}
d2792 3
a2794 3
	if (WndPosts[i].hWnd && !IsMinimized (WndPosts[i].hWnd)) {
	  ShowWindow (WndPosts[i].hWnd, SW_MINIMIZE);
	}
d2797 3
a2799 3
	if (WndMails[i].hWnd && !IsMinimized (WndMails[i].hWnd)) {
	  ShowWindow (WndMails[i].hWnd, SW_MINIMIZE);
	}
d2808 3
a2810 3
	if (CodingStatusText[i]->hTextWnd && !IsMinimized (CodingStatusText[i]->hTextWnd)) {
	  ShowWindow (CodingStatusText[i]->hTextWnd, SW_MINIMIZE);
	}
d2813 1
a2813 1
	ShowWindow (hCodedBlockWnd, SW_MINIMIZE);
d2828 1
a2828 1
  	ShowWindow (NetDoc.hWndFrame, SW_RESTORE);
d2832 3
a2834 3
	if (GroupDocs[i].InUse && GroupDocs[i].hDocWnd && IsMinimized (GroupDocs[i].hWndFrame)) {
	  ShowWindow (GroupDocs[i].hWndFrame, SW_RESTORE);
	}
d2836 3
a2838 3
	if (ArticleDocs[i].InUse && ArticleDocs[i].hDocWnd && IsMinimized (ArticleDocs[i].hWndFrame)) {
	  ShowWindow (ArticleDocs[i].hWndFrame, SW_RESTORE);
	}
d2840 3
a2842 3
	if (WndPosts[i].hWnd && IsMinimized (WndPosts[i].hWnd)) {
	  ShowWindow (WndPosts[i].hWnd, SW_RESTORE);
	}
d2845 3
a2847 3
	if (WndMails[i].hWnd && IsMinimized (WndMails[i].hWnd)) {
	  ShowWindow (WndMails[i].hWnd, SW_RESTORE);
	}
d2849 3
a2851 3
	if (CodingStatusText[i]->hTextWnd && IsMinimized (CodingStatusText[i]->hTextWnd)) {
	  ShowWindow (CodingStatusText[i]->hTextWnd, SW_RESTORE);
	}
d2854 1
a2854 1
	ShowWindow (hCodedBlockWnd, SW_RESTORE);
d2875 2
a2876 2
	WndEdits = WndPosts;
	maxWnds = MAXPOSTWNDS;
d2879 2
a2880 2
	WndEdits = WndMails;
	maxWnds = MAXMAILWNDS;
d2886 6
a2891 6
  if (thisSend == 0) {			/* find first in batch (if any) */
	while (thisSend < maxWnds)
	  if (WndEdits[thisSend].hWnd)
		break;
	  else
		thisSend++;
d2893 2
a2894 2
	if (thisSend == maxWnds)
	  return;					/* no open windows.  cancel */
d2896 1
a2896 1
	WndEdits[thisSend].nextBatchIndex = thisSend;
d2901 2
a2902 2
	if (WndEdits[WndEdits[thisSend].nextBatchIndex].hWnd)
	  break;
d2905 1
a2905 1
	WndEdits[thisSend].nextBatchIndex = 0;			/* no more */
d2920 5
a2924 5
	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);
d2927 1
a2927 1
	return (FALSE);
d2934 5
a2938 5
	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);
d2941 1
a2941 1
	return (FALSE);
d2955 3
a2957 3
	if (ArticleDocs[i].hDocWnd &&
		(!CommBusy || CommDoc != &ArticleDocs[i]))
	  SetArticleMailMenu (&ArticleDocs[i]);
d2960 2
a2961 2
	if (GroupDocs[i].hDocWnd)
	  SetGroupMailMenu (&GroupDocs[i]);
d2974 2
a2975 2
						&freeClusters, &totalClusters) == FALSE) {
	return 0L;
d2981 1
a2981 1
	return 0L;
d2984 2
a2985 2
	(unsigned long) d.sectors_per_cluster *
	(unsigned long) d.bytes_per_sector;
d3000 1
a3000 1
	return 0L;
d3003 1
a3003 1
	len = 0L;
d3023 2
a3024 2
	MoveToEx(hDc, oldPosition, 0, NULL);
	LineTo(hDc, oldPosition, clientRect.bottom);
d3027 2
a3028 2
	MoveToEx(hDc, newPosition, 0, NULL);
	LineTo(hDc, newPosition, clientRect.bottom);
@


1.98
log
@mods to winvn state machine to support newsgroup XHDR fetching
@
text
@d3 1
a3 1
 * $Id: wvutil.cpp 1.97 1995/11/10 12:07:49 dumoulin Exp $
d266 1
a266 1
	sscanf (s, "%*s %s %d %d:%d:%d %d", mon, &dom, &hr, &mn, &sc, &yr);
d751 3
a753 1
	  if (retcode == 412 && !force_xhdr)	/* 412 == 'not in a newsgroup' */
d981 6
a986 3
				total_xhdrs = 2;
		    }
	   }
@


1.97
log
@formatting changes
@
text
@d3 1
a3 1
 * $Id: wvutil.cpp 1.96 1995/11/10 07:05:38 dumoulin Exp $
d40 1
a40 1
// This will now be either 4 or 6, depending on whether threading
d42 1
a42 1
unsigned int total_xhdrs = 4;
d147 96
d249 1
a249 1
  char *cp, mon[80];
d261 1
a261 1
	sscanf (s, "%d %s %d %d:%d:%d", &dom, mon, &yr, &hr, &mn, &sc);
d300 1
a300 1
  return (mktime (&datetime));
d312 3
a314 1
	  sprintf (s, "%02d%s%02d", datetime->tm_mday, fmtDateDelim, datetime->tm_mon + 1);
d317 3
a319 1
	  sprintf (s, "%02d%s%02d", datetime->tm_mon + 1, fmtDateDelim, datetime->tm_mday);
d367 1
a367 1
	newPercent = (int) (line_number / total_lines);
d429 2
a430 2
sync_artnum (unsigned long artnum,
			 unsigned long activenum,
d469 1
a469 1
	  *(end+1) = (char)0;
d475 1
d480 1
a480 1
// mid's longer than HEADER_REFERENCES_LENGTH with a 4-char hex hash value
d489 1
a489 1
copy_message_id_with_frob (char * dest, char * source)
d491 10
a500 1
  strncpy (dest, source, HEADER_REFERENCES_LENGTH);
d503 1
a503 1
  if (strlen(source) >= HEADER_REFERENCES_LENGTH - 1) {
d505 1
a505 1
	char * p;
d509 74
a582 1
	sprintf (dest+HEADER_REFERENCES_LENGTH-9, "%08x", sum);
d606 2
a607 2
  static unsigned long first, last;
  unsigned long estnum, artnum;
d626 1
a682 2
		/*      MRRCloseComm (); */
		/*        PostQuitMessage (0); */
d841 1
d844 6
d882 1
a882 1
		else if ((DlgStatus == ID_THRESHOLD_UNREAD)		/* they clicked 'unread' */
d885 4
a888 1
			first = max (first, (unsigned long) RangePtr[0].Last + 1);
d918 3
a920 1
		  GroupDoc->ServerFirst = GroupDoc->ServerLast;
d970 2
d974 8
a981 2
		CommState = ST_XOVER_DATA;
		CommDoc->ActiveLines = 0;
d984 3
a986 3
		mylen = sprintf (mybuf, "XHDR from %ld-%ld", first, last);
		CommState = ST_XHDR_FROM_START;
		PutCommLine (mybuf);
d990 1
d992 36
a1027 8
	  if (strcmp (CommLineIn, ".") == 0) {
		/* this is a yuck way to do this */
		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) */
d1029 1
a1029 1
		char *reference;
d1054 3
d1058 1
a1058 1
									ARTICLE_SUBJECT_OFFSET +
d1060 1
d1075 2
d1096 4
a1099 3
		reference = get_best_reference (this_hop);
		if (reference) {
		  copy_message_id_with_frob (header->references, reference);
d1122 1
a1122 1
							 CommDoc->TotalLines);
d1133 3
a1135 2
	  /* the current flow is FROM -> DATE -> LINES -> SUBJECT */
	  /* (threadp) FROM -> DATE -> LINES -> REF -> ID -> SUBJECT */
d1143 9
a1151 2
	  total_xhdrs = threadp ? 6 : 4;	/* we do this here to allow */
	  /* mid-session change-of-mind  */
d1171 1
d1194 3
d1209 2
d1264 1
d1266 3
a1268 2
			  = parse_usenet_date (get_xhdr_line (CommLineIn));

d1270 1
a1270 2

		  CommDoc->ActiveLines++;
d1374 3
a1376 2
		char far *refer;		/* , far * end,far * bracket1,far *bracket2; */
		/*      int bracket_len; */
d1388 1
d1392 1
a1392 1

d1398 1
a1398 2
									 (header_elt (headers,
											  	CommDoc->ActiveLines))->number,
d1401 3
a1403 1
				copy_message_id_with_frob ((header_elt (headers, syncnum))->references, refer);
a1438 1
		CommDoc->ActiveLines = 0;
d1441 1
d1488 31
a1518 6

	  if (strcmp (CommLineIn, ".") == 0) {
		CommState = ST_IN_GROUP;
		CommBusy = FALSE;
		finish_header_retrieval();
		SetStatbarText (CommDoc->hWndFrame, "", CommDoc, TRUE, TRUE);
d1520 1
d1532 3
d1542 13
a1554 8
		  if (syncnum >= 0)
			header = header_elt (headers, syncnum);
		  else
			header = header_elt (headers, CommDoc->ActiveLines);

		  header->Selected = FALSE;
		  header->ArtDoc = (TypDoc *) NULL;
		  header->Seen = WasArtSeen (artnum, GetGroup(LinePtr));
a1556 5
		  mylstrncpy (header->subject,
					  get_xhdr_line (CommLineIn), HEADER_SUBJECT_LENGTH);
		  CommDoc->LongestLine = max (CommDoc->LongestLine,
									  ARTICLE_SUBJECT_OFFSET +
									  (unsigned) lstrlen (header->subject));
a1558 1
		  CommDoc->ActiveLines++;
d1568 48
d1627 1
a1632 3
	  if (strcmp (CommLineIn, ".") == 0) {
		;						/* error: empty article (end in middle of header) */
	  }
d1635 1
d1637 4
a1640 2
	  if (!TrimHeader (CommLineIn)) {
		WrapAddCommLineToDoc (CommLineIn);
d1944 2
a1945 1
		_strnicmp ("message-id", line, 10) &&	/* need this for article replies */
d1958 1
a1958 1
 *  Returns TRUE iff the article has been seen.
d1960 1
a1960 1
BOOL
d1969 1
a1969 1
	  return (TRUE);
d1975 1
a1975 1
  return (FALSE);
d1980 1
a1980 1
 *  Determines number of unread articles in a group
d1993 1
a1993 1
		(unsigned long)(RangePtr[GroupPtr->nRanges - 1].Last) < GroupPtr->ServerFirst) {
d1996 1
d2000 1
d2016 2
a2017 2
	if (GroupPtr->ServerLast > (unsigned long)RangePtr->Last) {
		numUnseen += (GroupPtr->ServerLast - (unsigned long)RangePtr->Last);
d2044 1
a2044 1
char far * mylstrncpy(char far *ptr1, char far *ptr2, int len)
d2100 1
d2107 2
a2108 2
  header_p headers;
  char szTmp[MAXGROUPNAME+4];
d2116 1
a2116 1
   * change it so that ActiveLines will count only unread articles
d2131 9
d2141 10
a2150 3
  	/* clear thread_depth info */
	/*  for (i = 0; i < group->total_headers; i++)
   	 headers[i].thread_depth = 0; */
d2152 2
a2153 1
  	iSortOption = IDM_SORT_ARTNUM;
d2156 2
a2157 2
    	iSortOption = IDM_SORT_THREADSUB; // we're testing this as the default.
	/*	sort_by_threads (header_handle, thread_handle, CommDoc->TotalLines); */
d2160 4
a2163 5
/*  iSortOption = threadp ? IDM_SORT_THREADS : IDM_SORT_ARTNUM; */

  	lstrcpy(szTmp,"G: ");
  	lstrcat(szTmp, CurrentGroup);
  	iSortOption = GetPrivateProfileInt(szTmp, "SortOption", iSortOption, szAppProFile);
a2171 1
  GroupDoc->total_headers = CommDoc->TotalLines;
a2190 22
  #if 0
      /* Get article action list */
      if(GroupDoc->pAction == NULL)
     {
          WVArticleAction *aa = new WVArticleAction;
          aa->ReadActions(CurrentGroup);
          GroupDoc->pAction = (void*) aa;
      }
      else
        DEBUG_BREAK;  // pointer unexpectedly non-NULL

      /* Perform article actions */
      ActOnArticles(GroupDoc, headers);
 #endif

      /* Get article action list */
      WVArticleAction aa;
      aa.ReadActions(CurrentGroup);

      /* Perform article actions */
      aa.ActOnArticles(GroupDoc, headers);

d2965 26
d2993 1
a2993 1
// tab-width: 4
@


1.96
log
@variable in  batch send being used without being initialized.
initialized variable to zero
@
text
@d1 2639
a2639 2639
// -*- C++ -*-
/*
 * $Id: wvutil.cpp 1.95 1995/11/09 00:24:11 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 4 or 6, depending on whether threading
// is enabled via 'threadp'.
unsigned int total_xhdrs = 4;

/*--- 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);
}

/* This was lifted from ANU news. */

time_t
parse_usenet_date (char *s)
{
  struct tm datetime;
  char *cp, mon[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", &dom, mon, &yr, &hr, &mn, &sc);
	if (yr < 100)
	  yr += 1900;
  }
  else
	sscanf (s, "%*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));
}

/*-- 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", datetime->tm_mday, fmtDateDelim, datetime->tm_mon + 1);
	}
	else {
	  sprintf (s, "%02d%s%02d", datetime->tm_mon + 1, fmtDateDelim, datetime->tm_mday);
	}
	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 = (int) (line_number / total_lines);
	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 (unsigned long artnum,
			 unsigned 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+1) = (char)0;
	  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_REFERENCES_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, char * source)
{
  strncpy (dest, source, HEADER_REFERENCES_LENGTH);

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

/*-- 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 unsigned long first, last;
  unsigned 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;

  /* 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 {
		/*      MRRCloseComm (); */
		/*        PostQuitMessage (0); */
		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);
	  if (retcode == 412 && !force_xhdr)	/* 412 == 'not in a newsgroup' */
		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;
	  GroupDoc->ServerLast = last;
	  GroupDoc->ServerFirst = first;
	  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 'unread' */
				 ||(ShowUnreadOnly)) {
		  if (GroupDoc->nRanges) {
			first = max (first, (unsigned 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;
		  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;
	  sscanf (CommLineIn, "%d", &retcode);
	  if (retcode == 224) {
		CommState = ST_XOVER_DATA;
		CommDoc->ActiveLines = 0;
	  }
	  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) {
		/* this is a yuck way to do this */
		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 *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));
		/* 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);

		/* 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;

		reference = get_best_reference (this_hop);
		if (reference) {
		  copy_message_id_with_frob (header->references, 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);
	  }

	  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 */
	  /* (threadp) FROM -> DATE -> LINES -> REF -> ID -> SUBJECT */

	  /* 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);
	  total_xhdrs = threadp ? 6 : 4;	/* we do this here to allow */
	  /* mid-session change-of-mind  */
	  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;

		/* 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;

		/* 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);

		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));

		  unlock_headers (header_handle, thread_handle);

		  CommDoc->ActiveLines++;
	  	  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 {
		char far *refer;		/* , far * end,far * bracket1,far *bracket2; */
		/*      int bracket_len; */

		/* 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);

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

			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) {
				copy_message_id_with_frob ((header_elt (headers, syncnum))->references, 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);
		CommDoc->ActiveLines = 0;

		/* Now ask for the subject lines */
		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) {
		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;
		  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);
		  else
			header = header_elt (headers, CommDoc->ActiveLines);

		  header->Selected = FALSE;
		  header->ArtDoc = (TypDoc *) NULL;
		  header->Seen = WasArtSeen (artnum, GetGroup(LinePtr));
		  UnlockLine (BlockPtr, LinePtr, &(CommDoc->hParentBlock),
					  &(CommDoc->ParentOffset), &(CommDoc->ParentLineID));
		  mylstrncpy (header->subject,
					  get_xhdr_line (CommLineIn), HEADER_SUBJECT_LENGTH);
		  CommDoc->LongestLine = max (CommDoc->LongestLine,
									  ARTICLE_SUBJECT_OFFSET +
									  (unsigned) lstrlen (header->subject));

		  unlock_headers (header_handle, thread_handle);
		  CommDoc->ActiveLines++;
		  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;
	  }
	  CommState = ST_REC_ARTICLE_HEADER;
	  UsingMIME = FALSE;		/* new article, init to no MIME */
	  break;

	case ST_REC_ARTICLE_HEADER:
	  if (strcmp (CommLineIn, ".") == 0) {
		;						/* error: empty article (end in middle of header) */
	  }
	  if (IsBlankStr (CommLineIn)) {	/* headers end in blank line */
		CommState = ST_REC_ARTICLE;
	  }
	  if (!TrimHeader (CommLineIn)) {
		WrapAddCommLineToDoc (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, 10) &&	/* need this for article replies */
		_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 TRUE iff the article has been seen.
 */
BOOL
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 (TRUE);
	}
	else {
	  RangePtr++;
	}
  }
  return (FALSE);
}

/*-- function CalcNumUnread ---------------------------------------------
 *
 *  Determines number of unread 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) ||
		(unsigned long)(RangePtr[GroupPtr->nRanges - 1].Last) < GroupPtr->ServerFirst) {
		return min(GroupPtr->ServerEstNum, (GroupPtr->ServerLast - GroupPtr->ServerFirst + 1));
	}
	if (RangePtr->First != 1) {	/* this should never happen */
		RangePtr->First = 1;
	}

	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 > (unsigned long)RangePtr->Last) {
		numUnseen += (GroupPtr->ServerLast - (unsigned 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, 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 ()
{
  TypLine far *LinePtr;
  TypBlock far *BlockPtr;
  TypGroup far *GroupDoc;
  HANDLE header_handle, thread_handle;
  thread_array thread_index;
  char mybuf[MAXINTERNALLINE];
  header_p headers;
  char szTmp[MAXGROUPNAME+4];

  /* 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 unread 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)));

  	/* clear thread_depth info */
	/*  for (i = 0; i < group->total_headers; i++)
   	 headers[i].thread_depth = 0; */

  	iSortOption = IDM_SORT_ARTNUM;
  	if (threadp) {
		SetWindowText (CommDoc->hWndFrame, "sorting headers...");
    	iSortOption = IDM_SORT_THREADSUB; // we're testing this as the default.
	/*	sort_by_threads (header_handle, thread_handle, CommDoc->TotalLines); */
  	}

/*  iSortOption = threadp ? IDM_SORT_THREADS : IDM_SORT_ARTNUM; */

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

/*  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);
  }

  GroupDoc->total_headers = CommDoc->TotalLines;
  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);

  #if 0
      /* Get article action list */
      if(GroupDoc->pAction == NULL)
     {
          WVArticleAction *aa = new WVArticleAction;
          aa->ReadActions(CurrentGroup);
          GroupDoc->pAction = (void*) aa;
      }
      else
        DEBUG_BREAK;  // pointer unexpectedly non-NULL

      /* Perform article actions */
      ActOnArticles(GroupDoc, headers);
 #endif

      /* Get article action list */
      WVArticleAction aa;
      aa.ReadActions(CurrentGroup);

      /* Perform article actions */
      aa.ActOnArticles(GroupDoc, headers);

  /* 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;
}

//
// Local variables:
// tab-width: 4
// end:
//
@


1.95
log
@format changes to make RCS happy
@
text
@d1 2639
a2639 2637
// -*- C++ -*-
/*
 * $Id: wvutil.cpp 1.94 1995/11/07 22:38:19 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 4 or 6, depending on whether threading
// is enabled via 'threadp'.
unsigned int total_xhdrs = 4;

/*--- 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);
}

/* This was lifted from ANU news. */

time_t
parse_usenet_date (char *s)
{
  struct tm datetime;
  char *cp, mon[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", &dom, mon, &yr, &hr, &mn, &sc);
	if (yr < 100)
	  yr += 1900;
  }
  else
	sscanf (s, "%*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));
}

/*-- 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", datetime->tm_mday, fmtDateDelim, datetime->tm_mon + 1);
	}
	else {
	  sprintf (s, "%02d%s%02d", datetime->tm_mon + 1, fmtDateDelim, datetime->tm_mday);
	}
	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 = (int) (line_number / total_lines);
	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 (unsigned long artnum,
			 unsigned 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+1) = (char)0;
	  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_REFERENCES_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, char * source)
{
  strncpy (dest, source, HEADER_REFERENCES_LENGTH);

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

/*-- 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 unsigned long first, last;
  unsigned 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;

  /* 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 {
		/*      MRRCloseComm (); */
		/*        PostQuitMessage (0); */
		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);
	  if (retcode == 412 && !force_xhdr)	/* 412 == 'not in a newsgroup' */
		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;
	  GroupDoc->ServerLast = last;
	  GroupDoc->ServerFirst = first;
	  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 'unread' */
				 ||(ShowUnreadOnly)) {
		  if (GroupDoc->nRanges) {
			first = max (first, (unsigned 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;
		  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;
	  sscanf (CommLineIn, "%d", &retcode);
	  if (retcode == 224) {
		CommState = ST_XOVER_DATA;
		CommDoc->ActiveLines = 0;
	  }
	  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) {
		/* this is a yuck way to do this */
		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 *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));
		/* 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);

		/* 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;

		reference = get_best_reference (this_hop);
		if (reference) {
		  copy_message_id_with_frob (header->references, 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);
	  }

	  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 */
	  /* (threadp) FROM -> DATE -> LINES -> REF -> ID -> SUBJECT */

	  /* 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);
	  total_xhdrs = threadp ? 6 : 4;	/* we do this here to allow */
	  /* mid-session change-of-mind  */
	  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;

		/* 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;

		/* 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);

		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));

		  unlock_headers (header_handle, thread_handle);

		  CommDoc->ActiveLines++;
	  	  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 {
		char far *refer;		/* , far * end,far * bracket1,far *bracket2; */
		/*      int bracket_len; */

		/* 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);

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

			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) {
				copy_message_id_with_frob ((header_elt (headers, syncnum))->references, 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);
		CommDoc->ActiveLines = 0;

		/* Now ask for the subject lines */
		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) {
		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;
		  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);
		  else
			header = header_elt (headers, CommDoc->ActiveLines);

		  header->Selected = FALSE;
		  header->ArtDoc = (TypDoc *) NULL;
		  header->Seen = WasArtSeen (artnum, GetGroup(LinePtr));
		  UnlockLine (BlockPtr, LinePtr, &(CommDoc->hParentBlock),
					  &(CommDoc->ParentOffset), &(CommDoc->ParentLineID));
		  mylstrncpy (header->subject,
					  get_xhdr_line (CommLineIn), HEADER_SUBJECT_LENGTH);
		  CommDoc->LongestLine = max (CommDoc->LongestLine,
									  ARTICLE_SUBJECT_OFFSET +
									  (unsigned) lstrlen (header->subject));

		  unlock_headers (header_handle, thread_handle);
		  CommDoc->ActiveLines++;
		  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;
	  }
	  CommState = ST_REC_ARTICLE_HEADER;
	  UsingMIME = FALSE;		/* new article, init to no MIME */
	  break;

	case ST_REC_ARTICLE_HEADER:
	  if (strcmp (CommLineIn, ".") == 0) {
		;						/* error: empty article (end in middle of header) */
	  }
	  if (IsBlankStr (CommLineIn)) {	/* headers end in blank line */
		CommState = ST_REC_ARTICLE;
	  }
	  if (!TrimHeader (CommLineIn)) {
		WrapAddCommLineToDoc (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, 10) &&	/* need this for article replies */
		_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 TRUE iff the article has been seen.
 */
BOOL
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 (TRUE);
	}
	else {
	  RangePtr++;
	}
  }
  return (FALSE);
}

/*-- function CalcNumUnread ---------------------------------------------
 *
 *  Determines number of unread 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) ||
		(unsigned long)(RangePtr[GroupPtr->nRanges - 1].Last) < GroupPtr->ServerFirst) {
		return min(GroupPtr->ServerEstNum, (GroupPtr->ServerLast - GroupPtr->ServerFirst + 1));
	}
	if (RangePtr->First != 1) {	/* this should never happen */
		RangePtr->First = 1;
	}

	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 > (unsigned long)RangePtr->Last) {
		numUnseen += (GroupPtr->ServerLast - (unsigned 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, 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 ()
{
  TypLine far *LinePtr;
  TypBlock far *BlockPtr;
  TypGroup far *GroupDoc;
  HANDLE header_handle, thread_handle;
  thread_array thread_index;
  char mybuf[MAXINTERNALLINE];
  header_p headers;
  char szTmp[MAXGROUPNAME+4];

  /* 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 unread 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)));

  	/* clear thread_depth info */
	/*  for (i = 0; i < group->total_headers; i++)
   	 headers[i].thread_depth = 0; */

  	iSortOption = IDM_SORT_ARTNUM;
  	if (threadp) {
		SetWindowText (CommDoc->hWndFrame, "sorting headers...");
    	iSortOption = IDM_SORT_THREADSUB; // we're testing this as the default.
	/*	sort_by_threads (header_handle, thread_handle, CommDoc->TotalLines); */
  	}

/*  iSortOption = threadp ? IDM_SORT_THREADS : IDM_SORT_ARTNUM; */

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

/*  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);
  }

  GroupDoc->total_headers = CommDoc->TotalLines;
  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);

  #if 0
      /* Get article action list */
      if(GroupDoc->pAction == NULL)
     {
          WVArticleAction *aa = new WVArticleAction;
          aa->ReadActions(CurrentGroup);
          GroupDoc->pAction = (void*) aa;
      }
      else
        DEBUG_BREAK;  // pointer unexpectedly non-NULL

      /* Perform article actions */
      ActOnArticles(GroupDoc, headers);
 #endif

      /* Get article action list */
      WVArticleAction aa;
      aa.ReadActions(CurrentGroup);

      /* Perform article actions */
      aa.ActOnArticles(GroupDoc, headers);

  /* 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;
  }

  thisSend = WndEdits[thisSend].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;
}

//
// Local variables:
// tab-width: 4
// end:
//
@


1.94
log
@fixed areas where LockLine was being called but success for
lock was never checked before using the data areas.
@
text
@d1 2637
a2637 2637
// -*- C++ -*-
/*
 * $Id: wvutil.cpp 1.93 1995/09/27 21:54:58 brydon 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 4 or 6, depending on whether threading
// is enabled via 'threadp'.
unsigned int total_xhdrs = 4;

/*--- 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);
}

/* This was lifted from ANU news. */

time_t
parse_usenet_date (char *s)
{
  struct tm datetime;
  char *cp, mon[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", &dom, mon, &yr, &hr, &mn, &sc);
	if (yr < 100)
	  yr += 1900;
  }
  else
	sscanf (s, "%*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));
}

/*-- 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", datetime->tm_mday, fmtDateDelim, datetime->tm_mon + 1);
	}
	else {
	  sprintf (s, "%02d%s%02d", datetime->tm_mon + 1, fmtDateDelim, datetime->tm_mday);
	}
	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 = (int) (line_number / total_lines);
	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 (unsigned long artnum,
			 unsigned 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+1) = (char)0;
	  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_REFERENCES_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, char * source)
{
  strncpy (dest, source, HEADER_REFERENCES_LENGTH);

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

/*-- 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 unsigned long first, last;
  unsigned 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;

  /* 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 {
		/*      MRRCloseComm (); */
		/*        PostQuitMessage (0); */
		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);
	  if (retcode == 412 && !force_xhdr)	/* 412 == 'not in a newsgroup' */
		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;
	  GroupDoc->ServerLast = last;
	  GroupDoc->ServerFirst = first;
	  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 'unread' */
				 ||(ShowUnreadOnly)) {
		  if (GroupDoc->nRanges) {
			first = max (first, (unsigned 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;
		  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;
	  sscanf (CommLineIn, "%d", &retcode);
	  if (retcode == 224) {
		CommState = ST_XOVER_DATA;
		CommDoc->ActiveLines = 0;
	  }
	  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) {
		/* this is a yuck way to do this */
		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 *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));
		/* 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);

		/* 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;

		reference = get_best_reference (this_hop);
		if (reference) {
		  copy_message_id_with_frob (header->references, 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);
	  }

	  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 */
	  /* (threadp) FROM -> DATE -> LINES -> REF -> ID -> SUBJECT */

	  /* 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);
	  total_xhdrs = threadp ? 6 : 4;	/* we do this here to allow */
	  /* mid-session change-of-mind  */
	  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;

		/* 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;

		/* 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);

		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));

		  unlock_headers (header_handle, thread_handle);

		  CommDoc->ActiveLines++;
	  	  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 {
		char far *refer;		/* , far * end,far * bracket1,far *bracket2; */
		/*      int bracket_len; */

		/* 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);

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

			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) {
				copy_message_id_with_frob ((header_elt (headers, syncnum))->references, 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);
		CommDoc->ActiveLines = 0;

		/* Now ask for the subject lines */
		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) {
		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;
		  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);
		  else
			header = header_elt (headers, CommDoc->ActiveLines);

		  header->Selected = FALSE;
		  header->ArtDoc = (TypDoc *) NULL;
		  header->Seen = WasArtSeen (artnum, GetGroup(LinePtr));
		  UnlockLine (BlockPtr, LinePtr, &(CommDoc->hParentBlock),
					  &(CommDoc->ParentOffset), &(CommDoc->ParentLineID));
		  mylstrncpy (header->subject,
					  get_xhdr_line (CommLineIn), HEADER_SUBJECT_LENGTH);
		  CommDoc->LongestLine = max (CommDoc->LongestLine,
									  ARTICLE_SUBJECT_OFFSET +
									  (unsigned) lstrlen (header->subject));

		  unlock_headers (header_handle, thread_handle);
		  CommDoc->ActiveLines++;
		  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;
	  }
	  CommState = ST_REC_ARTICLE_HEADER;
	  UsingMIME = FALSE;		/* new article, init to no MIME */
	  break;

	case ST_REC_ARTICLE_HEADER:
	  if (strcmp (CommLineIn, ".") == 0) {
		;						/* error: empty article (end in middle of header) */
	  }
	  if (IsBlankStr (CommLineIn)) {	/* headers end in blank line */
		CommState = ST_REC_ARTICLE;
	  }
	  if (!TrimHeader (CommLineIn)) {
		WrapAddCommLineToDoc (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, 10) &&	/* need this for article replies */
		_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 TRUE iff the article has been seen.
 */
BOOL
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 (TRUE);
	}
	else {
	  RangePtr++;
	}
  }
  return (FALSE);
}

/*-- function CalcNumUnread ---------------------------------------------
 *
 *  Determines number of unread 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) ||
		(unsigned long)(RangePtr[GroupPtr->nRanges - 1].Last) < GroupPtr->ServerFirst) {
		return min(GroupPtr->ServerEstNum, (GroupPtr->ServerLast - GroupPtr->ServerFirst + 1));
	}
	if (RangePtr->First != 1) {	/* this should never happen */
		RangePtr->First = 1;
	}

	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 > (unsigned long)RangePtr->Last) {
		numUnseen += (GroupPtr->ServerLast - (unsigned 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, 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 ()
{
  TypLine far *LinePtr;
  TypBlock far *BlockPtr;
  TypGroup far *GroupDoc;
  HANDLE header_handle, thread_handle;
  thread_array thread_index;
  char mybuf[MAXINTERNALLINE];
  header_p headers;
  char szTmp[MAXGROUPNAME+4];

  /* 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 unread 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)));

  	/* clear thread_depth info */
	/*  for (i = 0; i < group->total_headers; i++)
   	 headers[i].thread_depth = 0; */

  	iSortOption = IDM_SORT_ARTNUM;
  	if (threadp) {
		SetWindowText (CommDoc->hWndFrame, "sorting headers...");
    	iSortOption = IDM_SORT_THREADSUB; // we're testing this as the default.
	/*	sort_by_threads (header_handle, thread_handle, CommDoc->TotalLines); */
  	}

/*  iSortOption = threadp ? IDM_SORT_THREADS : IDM_SORT_ARTNUM; */

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

/*  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);
  }

  GroupDoc->total_headers = CommDoc->TotalLines;
  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);

  #if 0
      /* Get article action list */
      if(GroupDoc->pAction == NULL)
     {
          WVArticleAction *aa = new WVArticleAction;
          aa->ReadActions(CurrentGroup);
          GroupDoc->pAction = (void*) aa;
      }
      else
        DEBUG_BREAK;  // pointer unexpectedly non-NULL

      /* Perform article actions */
      ActOnArticles(GroupDoc, headers);
 #endif

      /* Get article action list */
      WVArticleAction aa;
      aa.ReadActions(CurrentGroup);

      /* Perform article actions */
      aa.ActOnArticles(GroupDoc, headers);

  /* 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;
  }

  thisSend = WndEdits[thisSend].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;
}

//
// Local variables:
// tab-width: 4
// end:
//
@


1.93
log
@modifications to support killfiles
@
text
@d3 1
a3 1
 * $Id: wvutil.cpp 1.92 1995/08/16 22:48:13 goh Exp $
d978 3
a980 3
	  if (strcmp (CommLineIn, ".") == 0) {
		LockLine (CommDoc->hParentBlock, CommDoc->ParentOffset,
				  CommDoc->ParentLineID, &BlockPtr, &LinePtr);
d984 1
a984 1

d990 1
a990 1
		/* Now ask for the #of lines */
d998 6
a1003 6
		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);
d1005 9
a1013 9
		/* 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));
d1015 1
a1015 1
		unlock_headers (header_handle, thread_handle);
d1017 5
a1021 4
		CommDoc->ActiveLines++;
		update_window_title (CommDoc->hWndFrame, group,
							 RcvLineCount++,
							 CommDoc->TotalLines * total_xhdrs);
d1036 3
a1038 3
	  if (strcmp (CommLineIn, ".") == 0) {
		LockLine (CommDoc->hParentBlock, CommDoc->ParentOffset,
				  CommDoc->ParentLineID, &BlockPtr, &LinePtr);
d1067 6
a1072 6
		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);
d1074 2
a1075 2
		/* Lock the header data */
		headers = lock_headers (header_handle, thread_handle);
d1077 6
a1082 6
		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));
d1084 6
a1089 5
		unlock_headers (header_handle, thread_handle);
		CommDoc->ActiveLines++;
		update_window_title (CommDoc->hWndFrame, group,
							 RcvLineCount++,
							 CommDoc->TotalLines * total_xhdrs);
d1104 3
a1106 3
	  if (strcmp (CommLineIn, ".") == 0) {
		LockLine (CommDoc->hParentBlock, CommDoc->ParentOffset,
				  CommDoc->ParentLineID, &BlockPtr, &LinePtr);
d1125 25
a1149 6
		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);
d1151 1
a1151 2
		/* Lock the header data */
		headers = lock_headers (header_handle, thread_handle);
d1153 5
a1157 2
		/* for now, we only pay attention to first (whole) referral */
		refer = get_xhdr_line (CommLineIn);
a1158 20
		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) {
			copy_message_id_with_frob ((header_elt (headers, syncnum))->references, refer);
		  }
		}

		unlock_headers (header_handle, thread_handle);

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

d1174 3
a1176 3
	  if (strcmp (CommLineIn, ".") == 0) {
		LockLine (CommDoc->hParentBlock, CommDoc->ParentOffset,
				  CommDoc->ParentLineID, &BlockPtr, &LinePtr);
d1192 2
a1193 2
		LockLine (CommDoc->hParentBlock, CommDoc->ParentOffset,
				  CommDoc->ParentLineID, &BlockPtr, &LinePtr);
d1195 4
a1198 4
		GroupDoc = GetGroup(LinePtr);
		header_handle = GroupDoc->header_handle;
		thread_handle = GroupDoc->thread_handle;
		GlobalUnlock (BlockPtr->hCurBlock);
d1201 10
a1210 10
		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);
d1212 5
a1216 4
		CommDoc->ActiveLines++;
		update_window_title (CommDoc->hWndFrame, group,
							 RcvLineCount++,
							 CommDoc->TotalLines * total_xhdrs);
d1218 1
a1218 1
	  }
d1243 3
a1245 3
		if (artnum) {
		  LockLine (CommDoc->hParentBlock, CommDoc->ParentOffset,
					CommDoc->ParentLineID, &BlockPtr, &LinePtr);
d1316 4
a1319 3
		/* article receive complete */
		CommState = ST_IN_GROUP;
		CommBusy = FALSE;
d1321 9
a1329 28
		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);
		}

		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);
d1331 55
a1385 20
		/* 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)
		 */
		TopOfDoc (CommDoc, &BlockPtr, &LinePtr);
		if (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;
d1387 1
a1387 14
		  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);
		}
	  }
d1518 22
a1539 13
  LockLine (CommDoc->hCurAddBlock, CommDoc->AddOffset, CommDoc->AddLineID, &BlockPtr, &LinePtr);
  mylen = (cdest - artline) + sizeof (int);
  mylen += mylen % 2;
  ((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));
d1786 2
a1787 2
  LockLine (CommDoc->hParentBlock, CommDoc->ParentOffset,
			CommDoc->ParentLineID, &BlockPtr, &LinePtr);
d1789 16
a1804 16
  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)));

  /* clear thread_depth info */
/*  for (i = 0; i < group->total_headers; i++)
    headers[i].thread_depth = 0; */

  iSortOption = IDM_SORT_ARTNUM;
  if (threadp) {
	SetWindowText (CommDoc->hWndFrame, "sorting headers...");
    iSortOption = IDM_SORT_THREADSUB; // we're testing this as the default.
/*	sort_by_threads (header_handle, thread_handle, CommDoc->TotalLines); */
  }
d1808 3
a1810 3
  lstrcpy(szTmp,"G: ");
  lstrcat(szTmp, CurrentGroup);
  iSortOption = GetPrivateProfileInt(szTmp, "SortOption", iSortOption, szAppProFile);
d1813 2
a1814 2
  sort_by_option(headers, thread_index, threadp, CommDoc->TotalLines,
        header_handle, thread_handle);
d1816 3
a1818 1
  unlock_headers (header_handle, thread_handle);
d1830 2
a1831 2
	LockLine (CommDoc->hParentBlock, CommDoc->ParentOffset,
			  CommDoc->ParentLineID, &BlockPtr, &LinePtr);
d1833 40
a1872 40
	/* 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);

#if 0
    /* Get article action list */
    if(GroupDoc->pAction == NULL)
    {
        WVArticleAction *aa = new WVArticleAction;
        aa->ReadActions(CurrentGroup);
        GroupDoc->pAction = (void*) aa;
    }
    else
      DEBUG_BREAK;  // pointer unexpectedly non-NULL

    /* Perform article actions */
    ActOnArticles(GroupDoc, headers);
#endif

    /* Get article action list */
    WVArticleAction aa;
    aa.ReadActions(CurrentGroup);

    /* Perform article actions */
    aa.ActOnArticles(GroupDoc, headers);

/* 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; 
d1874 1
a1874 1
	CommDoc->ActiveLineID = i;
d1876 1
a1876 1
	CommDoc->ThumbTracking = FALSE;	/* if thumb tracking, release it */
d1878 3
a1880 2
	unlock_headers (header_handle, thread_handle);
  }
d2509 1
a2509 1
  thisSend = nextBatchIndex;
d2520 1
a2520 1
	nextBatchIndex = thisSend;
d2524 2
a2525 2
  while (++nextBatchIndex < maxWnds)
	if (WndEdits[nextBatchIndex].hWnd)
d2528 2
a2529 2
  if (nextBatchIndex == maxWnds)
	nextBatchIndex = 0;			/* no more */
@


1.92
log
@fixed bugs in sync_artnum that could cause gpfs
@
text
@d1 2617
a2617 2597
// -*- C++ -*-
/*
 * $Id: wvutil.cpp 1.90 1995/06/06 06:00:52 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 <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 4 or 6, depending on whether threading
// is enabled via 'threadp'.
unsigned int total_xhdrs = 4;

/*--- 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);
}

/* This was lifted from ANU news. */

time_t
parse_usenet_date (char *s)
{
  struct tm datetime;
  char *cp, mon[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", &dom, mon, &yr, &hr, &mn, &sc);
	if (yr < 100)
	  yr += 1900;
  }
  else
	sscanf (s, "%*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));
}

/*-- 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", datetime->tm_mday, fmtDateDelim, datetime->tm_mon + 1);
	}
	else {
	  sprintf (s, "%02d%s%02d", datetime->tm_mon + 1, fmtDateDelim, datetime->tm_mday);
	}
	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 = (int) (line_number / total_lines);
	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 (unsigned long artnum,
			 unsigned 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+1) = (char)0;
	  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_REFERENCES_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, char * source)
{
  strncpy (dest, source, HEADER_REFERENCES_LENGTH);

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

/*-- 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 unsigned long first, last;
  unsigned 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;

  /* 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 {
		/*      MRRCloseComm (); */
		/*        PostQuitMessage (0); */
		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);
	  if (retcode == 412 && !force_xhdr)	/* 412 == 'not in a newsgroup' */
		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;
	  GroupDoc->ServerLast = last;
	  GroupDoc->ServerFirst = first;
	  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 'unread' */
				 ||(ShowUnreadOnly)) {
		  if (GroupDoc->nRanges) {
			first = max (first, (unsigned 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;
		  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;
	  sscanf (CommLineIn, "%d", &retcode);
	  if (retcode == 224) {
		CommState = ST_XOVER_DATA;
		CommDoc->ActiveLines = 0;
	  }
	  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) {
		/* this is a yuck way to do this */
		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 *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));
		/* 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);

		/* 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;

		reference = get_best_reference (this_hop);
		if (reference) {
		  copy_message_id_with_frob (header->references, 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);
	  }

	  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 */
	  /* (threadp) FROM -> DATE -> LINES -> REF -> ID -> SUBJECT */

	  /* 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);
	  total_xhdrs = threadp ? 6 : 4;	/* we do this here to allow */
	  /* mid-session change-of-mind  */
	  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;

		/* 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;

		/* 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);

		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 */
		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));

		unlock_headers (header_handle, thread_handle);

		CommDoc->ActiveLines++;
		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 */
		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 {
		char far *refer;		/* , far * end,far * bracket1,far *bracket2; */
		/*      int bracket_len; */

		/* 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);

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

		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) {
			copy_message_id_with_frob ((header_elt (headers, syncnum))->references, 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);
		CommDoc->ActiveLines = 0;

		/* Now ask for the subject lines */
		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 */
		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) {
		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;
		  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);
		  else
			header = header_elt (headers, CommDoc->ActiveLines);

		  header->Selected = FALSE;
		  header->ArtDoc = (TypDoc *) NULL;
		  header->Seen = WasArtSeen (artnum, GetGroup(LinePtr));
		  UnlockLine (BlockPtr, LinePtr, &(CommDoc->hParentBlock),
					  &(CommDoc->ParentOffset), &(CommDoc->ParentLineID));
		  mylstrncpy (header->subject,
					  get_xhdr_line (CommLineIn), HEADER_SUBJECT_LENGTH);
		  CommDoc->LongestLine = max (CommDoc->LongestLine,
									  ARTICLE_SUBJECT_OFFSET +
									  (unsigned) lstrlen (header->subject));

		  unlock_headers (header_handle, thread_handle);
		  CommDoc->ActiveLines++;
		  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;
	  }
	  CommState = ST_REC_ARTICLE_HEADER;
	  UsingMIME = FALSE;		/* new article, init to no MIME */
	  break;

	case ST_REC_ARTICLE_HEADER:
	  if (strcmp (CommLineIn, ".") == 0) {
		;						/* error: empty article (end in middle of header) */
	  }
	  if (IsBlankStr (CommLineIn)) {	/* headers end in blank line */
		CommState = ST_REC_ARTICLE;
	  }
	  if (!TrimHeader (CommLineIn)) {
		WrapAddCommLineToDoc (CommLineIn);
	  }
	  break;

	case ST_REC_ARTICLE:
	  if (strcmp (CommLineIn, ".") != 0) {
		WrapAddCommLineToDoc (CommLineIn);
	  }
	  else {
		/* 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);
		}

		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)
		 */
		TopOfDoc (CommDoc, &BlockPtr, &LinePtr);
		if (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++;
  LockLine (CommDoc->hCurAddBlock, CommDoc->AddOffset, CommDoc->AddLineID, &BlockPtr, &LinePtr);
  mylen = (cdest - artline) + sizeof (int);
  mylen += mylen % 2;
  ((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, 10) &&	/* need this for article replies */
		_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 TRUE iff the article has been seen.
 */
BOOL
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 (TRUE);
	}
	else {
	  RangePtr++;
	}
  }
  return (FALSE);
}

/*-- function CalcNumUnread ---------------------------------------------
 *
 *  Determines number of unread 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) ||
		(unsigned long)(RangePtr[GroupPtr->nRanges - 1].Last) < GroupPtr->ServerFirst) {
		return min(GroupPtr->ServerEstNum, (GroupPtr->ServerLast - GroupPtr->ServerFirst + 1));
	}
	if (RangePtr->First != 1) {	/* this should never happen */
		RangePtr->First = 1;
	}

	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 > (unsigned long)RangePtr->Last) {
		numUnseen += (GroupPtr->ServerLast - (unsigned 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, 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 ()
{
  TypLine far *LinePtr;
  TypBlock far *BlockPtr;
  TypGroup far *GroupDoc;
  HANDLE header_handle, thread_handle;
  thread_array thread_index;
/*  HANDLE hBlock; */
/*  char far *lpsz;
  char group[MAXGROUPNAME]; */
  char mybuf[MAXINTERNALLINE];
  header_p headers;
  char szTmp[MAXGROUPNAME+4];

  /* 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 unread 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.
   */
  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)));

  /* clear thread_depth info */
/*  for (i = 0; i < group->total_headers; i++)
    headers[i].thread_depth = 0; */

  iSortOption = IDM_SORT_ARTNUM;
  if (threadp) {
	SetWindowText (CommDoc->hWndFrame, "sorting headers...");
    iSortOption = IDM_SORT_THREADSUB; // we're testing this as the default.
/*	sort_by_threads (header_handle, thread_handle, CommDoc->TotalLines); */
  }

/*  iSortOption = threadp ? IDM_SORT_THREADS : IDM_SORT_ARTNUM; */

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

/*  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);
  GroupDoc->total_headers = CommDoc->TotalLines;
  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;

	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;
  }

  thisSend = 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 */

	nextBatchIndex = thisSend;
  }

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

  if (nextBatchIndex == maxWnds)
	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;
}

//
// Local variables:
// tab-width: 4
// end:
//
@


1.91
log
@fixes stop GPF in XHDR code and sync_artnum.  Also fixes to
fetching proper number of articles in "latest unread" feature.
@
text
@d1 2597
a2597 2597
// -*- C++ -*-
/*
 * $Id: wvutil.cpp 1.90 1995/06/06 06:00:52 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 <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 4 or 6, depending on whether threading
// is enabled via 'threadp'.
unsigned int total_xhdrs = 4;

/*--- 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);
}

/* This was lifted from ANU news. */

time_t
parse_usenet_date (char *s)
{
  struct tm datetime;
  char *cp, mon[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", &dom, mon, &yr, &hr, &mn, &sc);
	if (yr < 100)
	  yr += 1900;
  }
  else
	sscanf (s, "%*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));
}

/*-- 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", datetime->tm_mday, fmtDateDelim, datetime->tm_mon + 1);
	}
	else {
	  sprintf (s, "%02d%s%02d", datetime->tm_mon + 1, fmtDateDelim, datetime->tm_mday);
	}
	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 = (int) (line_number / total_lines);
	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 (unsigned long artnum,
			 unsigned 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+1) = (char)0;
	  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_REFERENCES_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, char * source)
{
  strncpy (dest, source, HEADER_REFERENCES_LENGTH);

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

/*-- 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 unsigned long first, last;
  unsigned 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;

  /* 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 {
		/*      MRRCloseComm (); */
		/*        PostQuitMessage (0); */
		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);
	  if (retcode == 412 && !force_xhdr)	/* 412 == 'not in a newsgroup' */
		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;
	  GroupDoc->ServerLast = last;
	  GroupDoc->ServerFirst = first;
	  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 'unread' */
				 ||(ShowUnreadOnly)) {
		  if (GroupDoc->nRanges) {
			first = max (first, (unsigned 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;
		  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;
	  sscanf (CommLineIn, "%d", &retcode);
	  if (retcode == 224) {
		CommState = ST_XOVER_DATA;
		CommDoc->ActiveLines = 0;
	  }
	  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) {
		/* this is a yuck way to do this */
		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 *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));
		/* 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);

		/* 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;

		reference = get_best_reference (this_hop);
		if (reference) {
		  copy_message_id_with_frob (header->references, 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);
	  }

	  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 */
	  /* (threadp) FROM -> DATE -> LINES -> REF -> ID -> SUBJECT */

	  /* 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);
	  total_xhdrs = threadp ? 6 : 4;	/* we do this here to allow */
	  /* mid-session change-of-mind  */
	  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;

		/* 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;

		/* 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);

		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 */
		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));

		unlock_headers (header_handle, thread_handle);

		CommDoc->ActiveLines++;
		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 */
		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 {
		char far *refer;		/* , far * end,far * bracket1,far *bracket2; */
		/*      int bracket_len; */

		/* 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);

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

		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) {
			copy_message_id_with_frob ((header_elt (headers, syncnum))->references, 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);
		CommDoc->ActiveLines = 0;

		/* Now ask for the subject lines */
		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 */
		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) {
		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;
		  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);
		  else
			header = header_elt (headers, CommDoc->ActiveLines);

		  header->Selected = FALSE;
		  header->ArtDoc = (TypDoc *) NULL;
		  header->Seen = WasArtSeen (artnum, GetGroup(LinePtr));
		  UnlockLine (BlockPtr, LinePtr, &(CommDoc->hParentBlock),
					  &(CommDoc->ParentOffset), &(CommDoc->ParentLineID));
		  mylstrncpy (header->subject,
					  get_xhdr_line (CommLineIn), HEADER_SUBJECT_LENGTH);
		  CommDoc->LongestLine = max (CommDoc->LongestLine,
									  ARTICLE_SUBJECT_OFFSET +
									  (unsigned) lstrlen (header->subject));

		  unlock_headers (header_handle, thread_handle);
		  CommDoc->ActiveLines++;
		  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;
	  }
	  CommState = ST_REC_ARTICLE_HEADER;
	  UsingMIME = FALSE;		/* new article, init to no MIME */
	  break;

	case ST_REC_ARTICLE_HEADER:
	  if (strcmp (CommLineIn, ".") == 0) {
		;						/* error: empty article (end in middle of header) */
	  }
	  if (IsBlankStr (CommLineIn)) {	/* headers end in blank line */
		CommState = ST_REC_ARTICLE;
	  }
	  if (!TrimHeader (CommLineIn)) {
		WrapAddCommLineToDoc (CommLineIn);
	  }
	  break;

	case ST_REC_ARTICLE:
	  if (strcmp (CommLineIn, ".") != 0) {
		WrapAddCommLineToDoc (CommLineIn);
	  }
	  else {
		/* 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);
		}

		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)
		 */
		TopOfDoc (CommDoc, &BlockPtr, &LinePtr);
		if (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++;
  LockLine (CommDoc->hCurAddBlock, CommDoc->AddOffset, CommDoc->AddLineID, &BlockPtr, &LinePtr);
  mylen = (cdest - artline) + sizeof (int);
  mylen += mylen % 2;
  ((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, 10) &&	/* need this for article replies */
		_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 TRUE iff the article has been seen.
 */
BOOL
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 (TRUE);
	}
	else {
	  RangePtr++;
	}
  }
  return (FALSE);
}

/*-- function CalcNumUnread ---------------------------------------------
 *
 *  Determines number of unread 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) ||
		(unsigned long)(RangePtr[GroupPtr->nRanges - 1].Last) < GroupPtr->ServerFirst) {
		return min(GroupPtr->ServerEstNum, (GroupPtr->ServerLast - GroupPtr->ServerFirst + 1));
	}
	if (RangePtr->First != 1) {	/* this should never happen */
		RangePtr->First = 1;
	}

	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 > (unsigned long)RangePtr->Last) {
		numUnseen += (GroupPtr->ServerLast - (unsigned 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, 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 ()
{
  TypLine far *LinePtr;
  TypBlock far *BlockPtr;
  TypGroup far *GroupDoc;
  HANDLE header_handle, thread_handle;
  thread_array thread_index;
/*  HANDLE hBlock; */
/*  char far *lpsz;
  char group[MAXGROUPNAME]; */
  char mybuf[MAXINTERNALLINE];
  header_p headers;
  char szTmp[MAXGROUPNAME+4];

  /* 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 unread 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.
   */
  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)));

  /* clear thread_depth info */
/*  for (i = 0; i < group->total_headers; i++)
    headers[i].thread_depth = 0; */

  iSortOption = IDM_SORT_ARTNUM;
  if (threadp) {
	SetWindowText (CommDoc->hWndFrame, "sorting headers...");
    iSortOption = IDM_SORT_THREADSUB; // we're testing this as the default.
/*	sort_by_threads (header_handle, thread_handle, CommDoc->TotalLines); */
  }

/*  iSortOption = threadp ? IDM_SORT_THREADS : IDM_SORT_ARTNUM; */

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

/*  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);
  GroupDoc->total_headers = CommDoc->TotalLines;
  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;

	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;
  }

  thisSend = 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 */

	nextBatchIndex = thisSend;
  }

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

  if (nextBatchIndex == maxWnds)
	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;
}

//
// Local variables:
// tab-width: 4
// end:
//
@


1.90
log
@changed name of CascadeWindows to remove conflict with Windows95
@
text
@d1 2597
a2597 2593
// -*- C++ -*-
/*
 * $Id: wvutil.cpp 1.89 1995/05/19 22:18:13 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 <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 4 or 6, depending on whether threading
// is enabled via 'threadp'.
unsigned int total_xhdrs = 4;

/*--- 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);
}

/* This was lifted from ANU news. */

time_t
parse_usenet_date (char *s)
{
  struct tm datetime;
  char *cp, mon[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", &dom, mon, &yr, &hr, &mn, &sc);
	if (yr < 100)
	  yr += 1900;
  }
  else
	sscanf (s, "%*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));
}

/*-- 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", datetime->tm_mday, fmtDateDelim, datetime->tm_mon + 1);
	}
	else {
	  sprintf (s, "%02d%s%02d", datetime->tm_mon + 1, fmtDateDelim, datetime->tm_mday);
	}
	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 = (int) (line_number / total_lines);
	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 (unsigned long artnum,
			 unsigned 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)) {
	  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+1) = (char)0;
	  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_REFERENCES_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, char * source)
{
  strncpy (dest, source, HEADER_REFERENCES_LENGTH);

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

/*-- 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;
  unsigned long estnum, first, last, 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;

  /* 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 {
		/*      MRRCloseComm (); */
		/*        PostQuitMessage (0); */
		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);
	  if (retcode == 412 && !force_xhdr)	/* 412 == 'not in a newsgroup' */
		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;
	  GroupDoc->ServerLast = last;
	  GroupDoc->ServerFirst = first;
	  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 'unread' */
				 ||(ShowUnreadOnly)) {
		  if (GroupDoc->nRanges) {
			first = max (first, (unsigned long) RangePtr[0].Last + 1);
			arts_to_retrieve = (last - first) + 1;
			if (arts_to_retrieve < min_to_retrieve) {
			  arts_to_retrieve = min_to_retrieve;
			  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;
		  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;
	  sscanf (CommLineIn, "%d", &retcode);
	  if (retcode == 224) {
		CommState = ST_XOVER_DATA;
		CommDoc->ActiveLines = 0;
	  }
	  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) {
		/* this is a yuck way to do this */
		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 *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));
		/* 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);

		/* 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;

		reference = get_best_reference (this_hop);
		if (reference) {
		  copy_message_id_with_frob (header->references, 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);
	  }

	  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 */
	  /* (threadp) FROM -> DATE -> LINES -> REF -> ID -> SUBJECT */

	  /* 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);
	  total_xhdrs = threadp ? 6 : 4;	/* we do this here to allow */
	  /* mid-session change-of-mind  */
	  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;

		/* 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;

		/* 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);

		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 */
		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));

		unlock_headers (header_handle, thread_handle);

		CommDoc->ActiveLines++;
		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 */
		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 {
		char far *refer;		/* , far * end,far * bracket1,far *bracket2; */
		/*      int bracket_len; */

		/* 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);

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

		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) {
			copy_message_id_with_frob ((header_elt (headers, syncnum))->references, 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);
		CommDoc->ActiveLines = 0;

		/* Now ask for the subject lines */
		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 */
		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) {
		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;
		  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);
		  else
			header = header_elt (headers, CommDoc->ActiveLines);

		  header->Selected = FALSE;
		  header->ArtDoc = (TypDoc *) NULL;
		  header->Seen = WasArtSeen (artnum, GetGroup(LinePtr));
		  UnlockLine (BlockPtr, LinePtr, &(CommDoc->hParentBlock),
					  &(CommDoc->ParentOffset), &(CommDoc->ParentLineID));
		  mylstrncpy (header->subject,
					  get_xhdr_line (CommLineIn), HEADER_SUBJECT_LENGTH);
		  CommDoc->LongestLine = max (CommDoc->LongestLine,
									  ARTICLE_SUBJECT_OFFSET +
									  (unsigned) lstrlen (header->subject));

		  unlock_headers (header_handle, thread_handle);
		  CommDoc->ActiveLines++;
		  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;
	  }
	  CommState = ST_REC_ARTICLE_HEADER;
	  UsingMIME = FALSE;		/* new article, init to no MIME */
	  break;

	case ST_REC_ARTICLE_HEADER:
	  if (strcmp (CommLineIn, ".") == 0) {
		;						/* error: empty article (end in middle of header) */
	  }
	  if (IsBlankStr (CommLineIn)) {	/* headers end in blank line */
		CommState = ST_REC_ARTICLE;
	  }
	  if (!TrimHeader (CommLineIn)) {
		WrapAddCommLineToDoc (CommLineIn);
	  }
	  break;

	case ST_REC_ARTICLE:
	  if (strcmp (CommLineIn, ".") != 0) {
		WrapAddCommLineToDoc (CommLineIn);
	  }
	  else {
		/* 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);
		}

		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)
		 */
		TopOfDoc (CommDoc, &BlockPtr, &LinePtr);
		if (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++;
  LockLine (CommDoc->hCurAddBlock, CommDoc->AddOffset, CommDoc->AddLineID, &BlockPtr, &LinePtr);
  mylen = (cdest - artline) + sizeof (int);
  mylen += mylen % 2;
  ((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, 10) &&	/* need this for article replies */
		_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 TRUE iff the article has been seen.
 */
BOOL
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 (TRUE);
	}
	else {
	  RangePtr++;
	}
  }
  return (FALSE);
}

/*-- function CalcNumUnread ---------------------------------------------
 *
 *  Determines number of unread 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) ||
		(unsigned long)(RangePtr[GroupPtr->nRanges - 1].Last) < GroupPtr->ServerFirst) {
		return min(GroupPtr->ServerEstNum, (GroupPtr->ServerLast - GroupPtr->ServerFirst + 1));
	}
	if (RangePtr->First != 1) {	/* this should never happen */
		RangePtr->First = 1;
	}

	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 > (unsigned long)RangePtr->Last) {
		numUnseen += (GroupPtr->ServerLast - (unsigned 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, 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 ()
{
  TypLine far *LinePtr;
  TypBlock far *BlockPtr;
  TypGroup far *GroupDoc;
  HANDLE header_handle, thread_handle;
  thread_array thread_index;
/*  HANDLE hBlock; */
/*  char far *lpsz;
  char group[MAXGROUPNAME]; */
  char mybuf[MAXINTERNALLINE];
  header_p headers;
  char szTmp[MAXGROUPNAME+4];

  /* 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 unread 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.
   */
  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)));

  /* clear thread_depth info */
/*  for (i = 0; i < group->total_headers; i++)
    headers[i].thread_depth = 0; */

  iSortOption = IDM_SORT_ARTNUM;
  if (threadp) {
	SetWindowText (CommDoc->hWndFrame, "sorting headers...");
    iSortOption = IDM_SORT_THREADSUB; // we're testing this as the default.
/*	sort_by_threads (header_handle, thread_handle, CommDoc->TotalLines); */
  }

/*  iSortOption = threadp ? IDM_SORT_THREADS : IDM_SORT_ARTNUM; */

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

/*  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);
  GroupDoc->total_headers = CommDoc->TotalLines;
  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;

	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;
  }

  thisSend = 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 */

	nextBatchIndex = thisSend;
  }

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

  if (nextBatchIndex == maxWnds)
	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;
}

//
// Local variables:
// tab-width: 4
// end:
//
@


1.89
log
@changed all pointer calculations into macro calls.
@
text
@d3 1
a3 1
 * $Id: wvutil.cpp 1.88 1995/03/01 19:18:14 rushing Exp $
d671 1
a671 1
		  if (DlgStatus == FALSE) {
d2283 1
a2283 1
CascadeWindows ()
@


1.88
log
@message-id hashing for better threading
@
text
@d3 1
a3 1
 * $Id: wvutil.cpp 1.87 1995/02/13 19:30:41 rushing Exp $
d654 2
a655 2
	  GroupDoc = ((TypGroup far *) ((char far *) LinePtr + sizeof (TypLine)));
	  RangePtr = (TypRange far *) ((char far *) GroupDoc + RangeOffset (GroupDoc->NameLen));
a666 2
//			est_num_unread = min (arts_to_retrieve,
//								  (last - max (first, (unsigned long) (RangePtr[0].Last + 1))) + 1);
d683 3
a685 1
		if (DlgStatus == TRUE && ((last - arts_to_retrieve) > first)) {
d749 1
a749 1
	  GroupDoc = (TypGroup far *) ((char far *) LinePtr + sizeof (TypLine));
a751 1

a752 1

d797 1
a797 1
		GroupDoc = ((TypGroup far *) ((char far *) LinePtr + sizeof (TypLine)));
a799 1

a804 1

a809 1

d912 1
a912 1
		GroupDoc = ((TypGroup far *) ((char far *) LinePtr + sizeof (TypLine)));
d932 1
a932 1
		GroupDoc = ((TypGroup far *) ((char far *) LinePtr + sizeof (TypLine)));
a934 1

a938 1

a954 1

a955 1

d977 1
a977 1
		GroupDoc = ((TypGroup far *) ((char far *) LinePtr + sizeof (TypLine)));
a981 1

d995 1
a995 1
		GroupDoc = ((TypGroup far *) ((char far *) LinePtr + sizeof (TypLine)));
a997 1

d1034 1
a1034 1
		GroupDoc = ((TypGroup far *) ((char far *) LinePtr + sizeof (TypLine)));
d1063 1
a1063 2
		GroupDoc = ((TypGroup far *) ((char far *) LinePtr + sizeof (TypLine)));

a1065 1

d1101 1
a1101 1
		GroupDoc = (TypGroup far *) ((char far *) LinePtr + sizeof (TypLine));
a1102 1

a1104 1

d1120 1
a1120 2
		GroupDoc = ((TypGroup far *) ((char far *) LinePtr + sizeof (TypLine)));

a1122 1

d1170 1
a1170 2
		GroupDoc = (TypGroup far *) ((char far *) LinePtr + sizeof (TypLine));

a1171 1

a1173 1

d1187 1
a1187 2
		GroupDoc = (TypGroup far *) ((char far *) LinePtr + sizeof (TypLine));

a1189 1

a1193 1

d1238 1
a1238 1
		  GroupDoc = ((TypGroup far *) ((char far *) LinePtr + sizeof (TypLine)));
d1255 1
a1255 4
		  header->Seen =
			WasArtSeen (artnum, (TypGroup far *) (((char far *) LinePtr) +
												  sizeof (TypLine)));

a1257 1

a1259 1

d1326 1
a1326 1
		GroupDoc = (TypGroup far *) ((char far *) LinePtr + sizeof (TypLine));
a1328 1

d1607 1
a1607 2
  TypRange far *RangePtr = (TypRange far *) ((char far *)
								GroupPtr + RangeOffset (GroupPtr->NameLen));
d1770 1
a1770 1
  GroupDoc = ((TypGroup far *) ((char far *) LinePtr + sizeof (TypLine)));
a1797 1

a1798 7

/*  lpsz = (char far *) (((char far *) LinePtr) +
					   sizeof (TypLine) + sizeof (TypGroup));

  mylstrncpy (group, lpsz, MAXGROUPNAME);
  sprintf (mybuf, "%s (%u articles)", group, CommDoc->TotalLines); */

d1813 1
a1813 1
	GroupDoc = ((TypGroup far *) ((char far *) LinePtr + sizeof (TypLine)));
a1817 6
/*
   if (CommDoc->TotalLines > 0)
   for (i = CommDoc->TotalLines - 1;
   ((i > 0) && ((header_elt (headers, i))->Seen == FALSE));
   i--);
 */
@


1.87
log
@made a new hash function, rather than trying to reuse HashGroup
@
text
@d3 1
a3 1
 * $Id: wvutil.cpp 1.86 1995/02/07 23:02:16 jcooper Exp $
d353 1
a353 2
char *
get_best_reference (char *refer)
d355 15
a369 19
  char *bracket1, *bracket2;
  char *end = refer + strlen (refer) - 1;	/* points to NULL */
  int bracket_len;

  bracket1 = strrchr (refer, '<');
  if (bracket1) {
    bracket1++; // skip '<' character
	bracket_len = (int) (end - bracket1) + 2;
	if ((bracket_len < HEADER_REFERENCES_LENGTH) && (!strrchr (bracket1, '>'))) {
	  *bracket1 = (char) NULL;
	  bracket1 = strrchr (refer, '<');
	  if (!bracket1)
		bracket_len = 0;
	  else {
	    bracket1++; // skip '<' character
		bracket2 = strrchr (refer, '>');
		if (bracket2)
		  bracket_len = (int) (bracket2 - bracket1) + 1;
	  }
a371 12
  else
	bracket_len = 0;
  
  if (!bracket_len)
	return (NULL);
  else if (bracket_len > (HEADER_REFERENCES_LENGTH - 1))
	bracket_len = (HEADER_REFERENCES_LENGTH - 1);
  
  if ((bracket1 + bracket_len) < end)
	*(bracket1 + bracket_len) = (char) NULL;

  return (bracket1);
d386 2
a387 2
unsigned int
hash_mid (char * gname)
d389 1
a389 1
  long unsigned int sum = 0;
d391 8
a398 2
  for (; *gname; gname++) {
	sum = (sum << 1) + *gname;
a399 1
  return ((unsigned int)sum);
a401 11
void
frob_message_id (char * mid)
{
  unsigned int hash;
  
  if (strlen(mid) == HEADER_REFERENCES_LENGTH-1) {
    hash = hash_mid (mid);
    sprintf (mid+HEADER_REFERENCES_LENGTH-5, "%04x", hash);
  }
}

d852 1
a852 2
		mylstrncpy (header->message_id, this_hop+1, HEADER_MESSAGE_ID_LENGTH);
		frob_message_id (header->message_id);
d861 1
a861 2
		  mylstrncpy (header->references, reference, HEADER_REFERENCES_LENGTH);
		  frob_message_id (header->references);
d1157 2
a1158 4
			mylstrncpy ((header_elt (headers, syncnum))->references,
						refer, HEADER_REFERENCES_LENGTH);
			frob_message_id ((header_elt (headers, syncnum))->references);
			}
d1221 4
a1224 6
		if (syncnum >= 0)
		  mylstrncpy ((header_elt (headers, syncnum))->message_id,
					  (char far *) (get_xhdr_line (CommLineIn)+1),
					  HEADER_MESSAGE_ID_LENGTH);
        
        frob_message_id ((header_elt (headers, syncnum))->message_id);
@


1.86
log
@About box shows IDD_VERSION_NUMBER in static text
Added Restore All command to Window menu and to main window Sys menu
@
text
@d3 1
a3 1
 * $Id: wvutil.cpp 1.85 1995/02/02 23:16:57 rushing Exp $
d401 1
a401 1
extern unsigned int HashGroup (unsigned char huge *);
d403 11
d420 1
a420 1
    hash = HashGroup (mid);
@


1.85
log
@skip '<' on message-id's in header struct.
@
text
@d3 1
a3 1
 * $Id: wvutil.cpp 1.84 1995/02/02 21:25:23 rushing Exp rushing $
d2414 1
a2414 1
	if (GroupDocs[i].InUse && GroupDocs[i].hDocWnd && !IsMinimized (GroupDocs[i].hDocWnd)) {
d2425 1
a2425 1
	if (ArticleDocs[i].InUse && ArticleDocs[i].hDocWnd && !IsMinimized (ArticleDocs[i].hDocWnd)) {
a2455 1
  /* move coded block status window to top center */
d2462 41
@


1.84
log
@skip over '<' character in mids & refs.
make mids & refs > 30 chars unique with a 4-char hash at the end.
@
text
@d3 1
a3 1
 * $Id: wvutil.cpp 1.83 1995/01/25 01:27:51 dumoulin Exp $
d864 1
a864 1
		mylstrncpy (header->message_id, this_hop, HEADER_MESSAGE_ID_LENGTH);
d1239 2
a1240 2
					  (char far *) (get_xhdr_line (CommLineIn)),
					  HEADER_MESSAGE_ID_LENGTH);	/* bad, hardcoded. */
@


1.83
log
@setting default thread sort to THREADSUB for testing
@
text
@d3 1
a3 1
 * $Id: wvutil.cpp 1.82 1995/01/19 02:47:25 jglasser Exp dumoulin $
d362 1
d364 1
a364 1
	if ((bracket_len < 30) && (!strrchr (bracket1, '>'))) {
d370 1
d379 1
a379 1

d382 3
a384 3
  else if (bracket_len > 29)
	bracket_len = 29;

d391 22
d865 1
d873 1
a873 1
		if (reference)
d875 2
d1170 1
a1170 1
		  if (syncnum >= 0)
d1173 2
d1241 2
a1242 1

@


1.82
log
@Jody Glasser's changes of 950113
@
text
@d3 1
a3 1
 * $Id: wvutil.cpp 1.81 1995/01/03 20:39:59 brydon Exp $
d1801 1
a1801 1
    iSortOption = IDM_SORT_THREADS;
@


1.81
log
@"Sort" menu: checkmark on sorting option (ie. CheckMenuItem) selected by
user.
"Sort" menu: add Thread/Subject sort option.
Record and remember user's sort option for each newsgroup.
Made ANSI, several declarations and calls fixed.
@
text
@d3 1
a3 1
 * $Id: wvutil.cpp 1.80 1994/12/22 18:35:51 rushing Exp $
d270 1
d1492 1
d1536 5
@


1.80
log
@explain the 'more included text' rejection notice to users.
@
text
@d1 1
d3 1
a3 1
 * $Id: wvutil.c 1.79 1994/12/19 17:06:02 rushing Exp rushing $
d12 2
d16 1
d56 1
a56 3
GetNum (ptr, num)
	 char **ptr;
	 long int *num;
d114 1
a114 2
StrToRGB (cstring)
	 char *cstring;
d279 1
a279 1
  int class = retcode / 100;
d291 1
a291 1
  switch (class) {
d302 1
a302 1
	switch (class) {
d777 1
a777 1
		finish_header_retrieval (CommDoc);
d1237 1
a1237 1
		finish_header_retrieval (CommDoc);
d1469 1
a1469 1
  loc = memchr (textptr, QuoteLineInd, 2);
d1471 1
a1471 1
	loc = memchr (textptr, '|', 2);
d1473 1
a1473 1
	loc = memchr (textptr, ':', 2);
d1615 1
a1615 3
WasArtSeen (ArtNum, GroupPtr)
	 unsigned long ArtNum;
	 TypGroup far *GroupPtr;
d1680 1
a1680 5
int
mylstrncmp (ptr1, ptr2, len)
	 char far *ptr1;
	 char far *ptr2;
	 int len;
d1697 1
a1697 5
char far *
mylstrncpy (ptr1, ptr2, len)
	 char far *ptr1;
	 char far *ptr2;
	 int len;
d1709 1
a1709 4
char far *
mylstrcpy (ptr1, ptr2)
	 char_p ptr1;
	 char far *ptr2;
d1726 1
a1726 3
lstrcmpnoblank (str1, str2)
	 char far **str1;
	 char far **str2;
d1757 1
d1759 2
a1760 2
  char far *lpsz;
  char group[MAXGROUPNAME];
d1763 1
d1784 6
a1789 1
  lock_headers (header_handle, thread_handle);
d1791 1
d1794 2
a1795 1
	sort_by_threads (header_handle, thread_handle, CommDoc->TotalLines);
d1798 10
d1812 1
a1812 1
  lpsz = (char far *) (((char far *) LinePtr) +
d1816 3
a1818 1
  sprintf (mybuf, "%s (%u articles)", group, CommDoc->TotalLines);
d2574 5
a2578 6
/*
 * Local variables:
 * tab-width: 4
 * end:
 */

@


1.79
log
@.no comment (jcooper)
@
text
@d2 1
a2 1
 * $Id: wvutil.c 1.78 1994/12/13 21:27:41 brydon Exp rushing $
d1424 21
a1444 1
		/* if we have an error, and did not lose the connection, abort send.
d1447 1
@


1.78
log
@informative message when AUTHINFO fails
@
text
@d2 1
a2 1
 * $Id: wvutil.c 1.77 1994/12/13 12:56:13 jcooper Exp $
d1823 5
a1827 4
		CommDoc->TotalLines - i < CommDoc->ScYLines - 1 ?
			(CommDoc->TotalLines - CommDoc->ScYLines) + 1:
			(i > 5 && CommDoc->TotalLines > CommDoc->ScYLines) ? i - 4 : 0;

@


1.77
log
@93.10 update (take two)
@
text
@d2 1
a2 1
 * $Id: wvutil.c 1.76 1994/11/30 22:22:08 jcooper Exp $
d387 1
d499 6
a504 1
		MessageBox (NetDoc.hDocWnd, "Error authorizing your username with the News Server.", "WinVN", MB_OK | MB_ICONHAND);
d520 7
a526 1
		sprintf (mybuf, "Error authorizing your password with the News Server:\n%s.", CommLineIn);
d2550 1
@


1.76
log
@93.9 changes
@
text
@d2 1
a2 1
 * $Id: wvutil.c 1.75 1994/11/22 02:53:26 jcooper Exp $
d294 1
a294 1
	MessageBox (hAlertWnd, "Error On News Server Error", "WinVN", MB_OK | MB_ICONHAND);
a487 1

d530 3
d1454 9
a1462 2
  int col, mylen;
  char artline[MAXINTERNALLINE];
d1470 1
a1470 1
  *cptr && col < (MAXINTERNALLINE - 3 * sizeof (TypLine) - sizeof (TypText));
d1521 2
a1522 1
  else if (!WrapIncomingArticleText || *start == '\0') {
d1811 5
a1815 1
	  (i > 5 && CommDoc->TotalLines > CommDoc->ScYLines) ? i - 4 : 0;
a1818 4
	/* make the 'n' in the group-list go away */
	GroupDoc->HighestPrevSeen = GroupDoc->ServerLast;
	InvalidateRect (NetDoc.hDocWnd, NULL, TRUE);

@


1.75
log
@93.7 update
@
text
@d2 1
a2 1
 * $Id: wvutil.c 1.74 1994/11/16 21:33:26 rushing Exp $
d1617 1
a1617 1
    		numUnseen += ((unsigned long)RangePtr->First - prev);
d1803 2
@


1.74
log
@check for connection to INND, if so, send 'mode reader'
@
text
@d2 1
a2 1
 * $Id: wvutil.c 1.73 1994/11/10 23:00:19 jcooper Exp rushing $
a291 1
	MessageBox (hAlertWnd, "Error On News Server Error", "WinVN", MB_OK | MB_ICONHAND);
d294 1
d298 2
a310 2
	CommBusy = FALSE;
	CommState = ST_NONE;
d550 4
a600 1
		MessageBox (NetDoc.hDocWnd, "No Such Newsgroup", "Error", MB_OK | MB_ICONHAND);
a603 1

d605 1
a608 1
		MessageBox (NetDoc.hDocWnd, "Restricted Access", "WinVN", MB_OK | MB_ICONHAND);
d613 1
d629 3
d638 3
a640 2
			est_num_unread = min (arts_to_retrieve,
								  (last - max (first, (unsigned long) (RangePtr[0].Last + 1))) + 1);
a672 1
			  MessageBox (NetDoc.hDocWnd, "No Articles to Retrieve", "WinVN", MB_OK | MB_ICONHAND);
d677 1
a688 1
		  MessageBox (NetDoc.hDocWnd, "Empty Newsgroup", "WinVN", MB_OK | MB_ICONHAND);
a691 1

d695 1
a723 1
	  GroupDoc->ServerLast = last;
d1486 1
a1486 1
  CommDoc->LongestLine = max (CommDoc->LongestLine, (unsigned) mylen);
d1591 42
@


1.73
log
@93.5 update
@
text
@d2 1
a2 1
 * $Id: wvutil.c 1.72 1994/11/10 02:47:39 rushing Exp $
d445 9
d2473 6
@


1.72
log
@restart
@
text
@a399 1
  static BOOL dialog_active = FALSE;
d488 2
d491 1
a491 1
		goto End_Authinfo;
a499 2
	  if (dialog_active)
		break;
d504 2
a505 1
		dialog_active = TRUE;
d508 1
a508 1
		dialog_active = FALSE;
@
