/*
 * $Id: wvutil.c 1.69 1994/09/18 22:35:49 jcooper 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 <commdlg.h>    // for GetOpenFileName dialog (JSC)
#include <ctype.h>
#include <stdlib.h>
#include <time.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);

// 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 (ptr, num)
     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 (cstring)
     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 */

  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);
      prevPercent = newPercent;
    }
  }
}

int
check_server_code (int retcode)
{
  int class = retcode / 100;
  switch (class) {
  case 5:
    MessageBox (hWndConf, "News Server Error", "WinVN", MB_OK | MB_ICONHAND);
    CommBusy = FALSE;
    CommState = ST_NONE;
    return (1);
    break;
  case 4:
    MessageBox (hWndConf, CommLineIn, "WinVN", MB_OK | MB_ICONHAND);
    switch (class) {
    case 400:
      /* service discontinued */
      MRRCloseComm ();
      PostQuitMessage (0);
      break;
    default:
      break;
    }
    CommBusy = FALSE;
    CommState = ST_NONE;
    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 *bracket1, *bracket2;
  char *end = refer + strlen (refer) - 1; /* points to NULL */
  int bracket_len;

  bracket1 = strrchr (refer, '<');
  if (bracket1) {
    bracket_len = (int) (end - bracket1) + 2;
    if ((bracket_len < 30) && (!strrchr (bracket1, '>'))) {
      *bracket1 = (char) NULL;
      bracket1 = strrchr (refer, '<');
      if (!bracket1)
   bracket_len = 0;
      else {
   bracket2 = strrchr (refer, '>');
   if (bracket2)
     bracket_len = (int) (bracket2 - bracket1) + 1;
      }
    }
  }
  else
    bracket_len = 0;

  if (!bracket_len)
    return (NULL);
  else if (bracket_len > 29)
    bracket_len = 29;

  if ((bracket1 + bracket_len) < end)
    *(bracket1 + bracket_len) = (char) NULL;

  return (bracket1);
}

/*-- 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 ()
{
  static BOOL dialog_active = FALSE;
  TypLine far *LinePtr;
  TypBlock far *BlockPtr;
  HANDLE hBlock;
  unsigned int Offset;
  TypLineID MyLineID;
  int retcode;
  int found;
  unsigned long estnum;
  long int first, last, syncnum;
  unsigned long artnum;
  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;

  /* 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 || PostEdit) {

    switch (CommState) {
    case ST_NONE:
      break;

    case ST_ESTABLISH_COMM:
      if (!sscanf (CommLineIn, "%u", &retcode))
   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", hWndConf, (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 {
   MessageBox (hWndConf, CommLineIn, "Access Problem", MB_OK);
   /*      MRRCloseComm (); */
   /*        PostQuitMessage (0); */
   Initializing = INIT_NOT_CONNECTED;
   InvalidateRect (hWndConf, NULL, TRUE);
      }

      break;

    case ST_CHECK_AUTHINFO_USERNAME:
      retcode = 0;
      sscanf (CommLineIn, "%u", &retcode);
      if (!retcode)
   break;
      if (retcode >= 500) {
   MessageBox (hWndConf, "Error authorizing your username with the News Server.", "WinVN", MB_OK | MB_ICONHAND);
   goto End_Authinfo;
      }
      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:
      if (dialog_active)
   break;
      retcode = 0;
      if (sscanf (CommLineIn, "%u", &retcode) <= 0)
   break;
      if (retcode < 200 || retcode > 299) {
   dialog_active = TRUE;
   sprintf (mybuf, "Error authorizing your password with the News Server:\n%s.", CommLineIn);
   MessageBox (hWndConf, mybuf, "WinVN", MB_OK | MB_ICONHAND);
   dialog_active = FALSE;
      } else {                                               
         /* Authentication was successful.  Store this fact, and the name under
          * which the user was authenticated.
          */
         Authenticated = TRUE;
         strncpy(AuthenticatedName,NNTPUserName,MAXNNTPSIZE);
      }
      goto End_Authinfo;


    case ST_END_AUTHINFO:
    End_Authinfo:;
      /* 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 (hWndConf, "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;

      if (dolist) {
   StartList ();
   did_list = 1;
      }
      else {
   did_list = 0;
   CommState = ST_NONE;
   CommBusy = FALSE;
   Initializing = INIT_READY;
      }
      InvalidateRect (hWndConf, 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;
   InvalidateRect (hWndConf, NULL, FALSE);

   ProcEndList ();
      }
      else {
   ProcListLine ((unsigned char *) CommLineIn);
      }
      break;

    case ST_GROUP_RESP:
      retcode = 0;
      sscanf (CommLineIn, "%u", &retcode);
      switch (retcode) {
      case 411:
   MessageBox (hWndConf, "No Such Newsgroup", "Error", MB_OK | MB_ICONHAND);
   /* abort the fledgling group window */
   DestroyWindow (CommDoc->hDocWnd);
   CommBusy = FALSE;
   CommState = ST_NONE;
   return;
   break;
      case 502:
   MessageBox (hWndConf, "Restricted Access", "WinVN", MB_OK | MB_ICONHAND);
   /* abort the fledgling group window */
   DestroyWindow (CommDoc->hDocWnd);
   CommBusy = FALSE;
   CommState = ST_NONE;
   return;
   break;
      default:
   if (check_server_code (retcode))
     return;
   break;
      }

      sscanf (CommLineIn, "%u %lu %ld %ld %s", &retcode, &estnum, &first, &last, group);

      LockLine (CommDoc->hParentBlock, CommDoc->ParentOffset,
      CommDoc->ParentLineID, &BlockPtr, &LinePtr);
      GroupDoc = ((TypGroup far *) ((char far *) LinePtr + sizeof (TypLine)));
      RangePtr = (TypRange far *) ((char far *) GroupDoc + RangeOffset (GroupDoc->NameLen));
      GroupDoc->Determined = TRUE;

      /* we don't want to grab *that* many! */
      if (estnum >= article_threshold) {
   if (!ShowUnreadOnly)
     { 
     arts_to_retrieve = (int) estnum;
     DlgStatus = DialogBox (hInst, (LPCSTR) "THRESHOLD", CommDoc->hDocWnd, (DLGPROC) lpfnWinVnThresholdDlg);
          if (DlgStatus == FALSE)
             {             
              DestroyWindow (CommDoc->hDocWnd);
         CommBusy = FALSE;
         CommState = ST_NONE;
         GroupDoc->ServerFirst = GroupDoc->ServerLast;
         GroupDoc->ServerEstNum = estnum;
              return;
              }
          }
   if ((arts_to_retrieve > 0)
       && ((last - arts_to_retrieve) > first)) {
     first = (last - arts_to_retrieve) + 1;
   }
   else if (arts_to_retrieve == -1) /* they clicked 'all of them' */
     arts_to_retrieve = (int) estnum;
   /* added by jlg */
   else if ((arts_to_retrieve == -2)   /* they clicked 'unread' */
       ||(ShowUnreadOnly)) {
     if (GroupDoc->nRanges) {
       first = RangePtr[0].Last + 1;
       arts_to_retrieve = (int) ((last - first) + 1);
       if (arts_to_retrieve < 50) {
         arts_to_retrieve = 50;
         first = last - 49;
       }
     }
     else
       arts_to_retrieve = (int) estnum;
   }
      }
      else {
   if (estnum > 0)
     arts_to_retrieve = (int) estnum;
   else {
     MessageBox (hWndConf, "Empty Newsgroup", "WinVN", MB_OK | MB_ICONHAND);
     /* abort the fledgling group window */
     DestroyWindow (CommDoc->hDocWnd);
     InvalidateRect(hWndConf, NULL, FALSE);
     CommBusy = FALSE;
     CommState = ST_NONE;
     GroupDoc->ServerFirst = GroupDoc->ServerLast;
     GroupDoc->ServerEstNum = 0;
     return;
   }
      }

      CommDoc->TotalLines = arts_to_retrieve;

      if (arts_to_retrieve > 0) {
   header_handle =
     GlobalAlloc (GMEM_MOVEABLE, (long)
             ((sizeof (TypHeader)) *
            (long) (arts_to_retrieve)) + sizeof (thread_array *));

   /* allocate space for the header_array index table */
   thread_handle =
     GlobalAlloc (GMEM_MOVEABLE,
             (long) ((sizeof (long)) * (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 = (TypGroup far *) ((char far *) LinePtr + sizeof (TypLine));
      GroupDoc->ServerEstNum = estnum;
      GroupDoc->ServerFirst = first;
      GroupDoc->ServerLast = last;

      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;

      /* 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_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 (CommDoc);
   InvalidateRect (CommDoc->hDocWnd, NULL, FALSE);
      }
      else {
   char *this_hop, *next_hop;
   char *reference;

   LockLine (CommDoc->hParentBlock, CommDoc->ParentOffset,
        CommDoc->ParentLineID, &BlockPtr, &LinePtr);
   GroupDoc = ((TypGroup far *) ((char far *) LinePtr + sizeof (TypLine)));
   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;

   mylstrncpy (header->message_id, this_hop, HEADER_MESSAGE_ID_LENGTH);

   /* references */
   this_hop = next_hop;
   next_hop = strchr (this_hop, '\t');
   *(next_hop++) = (char) NULL;

   reference = get_best_reference (this_hop);
   if (reference)
     mylstrncpy (header->references, reference, HEADER_REFERENCES_LENGTH);

   /* 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->hDocWnd, 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 = ((TypGroup far *) ((char far *) LinePtr + sizeof (TypLine)));
   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 = ((TypGroup far *) ((char far *) LinePtr + sizeof (TypLine)));
   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->hDocWnd, 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 = ((TypGroup far *) ((char far *) LinePtr + sizeof (TypLine)));
   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 = ((TypGroup far *) ((char far *) LinePtr + sizeof (TypLine)));
   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->hDocWnd, 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 = ((TypGroup far *) ((char far *) LinePtr + sizeof (TypLine)));
   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 = ((TypGroup far *) ((char far *) LinePtr + sizeof (TypLine)));

   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->hDocWnd, 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 = (TypGroup far *) ((char far *) LinePtr + sizeof (TypLine));
   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 = ((TypGroup far *) ((char far *) LinePtr + sizeof (TypLine)));

   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)
       mylstrncpy ((header_elt (headers, syncnum))->references,
         refer, HEADER_REFERENCES_LENGTH);
   }

   unlock_headers (header_handle, thread_handle);

   CommDoc->ActiveLines++;
   update_window_title (CommDoc->hDocWnd, 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 = (TypGroup far *) ((char far *) LinePtr + sizeof (TypLine));

   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 = (TypGroup far *) ((char far *) LinePtr + sizeof (TypLine));

   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)
     mylstrncpy ((header_elt (headers, syncnum))->message_id,
            (char far *) (get_xhdr_line (CommLineIn)),
            HEADER_MESSAGE_ID_LENGTH); /* bad, hardcoded. */

   unlock_headers (header_handle, thread_handle);

   CommDoc->ActiveLines++;
   update_window_title (CommDoc->hDocWnd, 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 (CommDoc);
   InvalidateRect (CommDoc->hDocWnd, NULL, FALSE);
      }
      else {

   artnum = 0;
   sscanf (CommLineIn, "%ld", &artnum);
   if (artnum) {
     LockLine (CommDoc->hParentBlock, CommDoc->ParentOffset,
          CommDoc->ParentLineID, &BlockPtr, &LinePtr);

     GroupDoc = ((TypGroup far *) ((char far *) LinePtr + sizeof (TypLine)));
     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, (TypGroup far *) (((char far *) LinePtr) +
                    sizeof (TypLine)));

     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->hDocWnd, 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->hDocWnd);
   break;
      }
      CommState = ST_REC_ARTICLE_HEADER;
      /*      Bossanova = FALSE; */
      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;
      AddCommLineToDoc (CommLineIn);
      break;

    case ST_REC_ARTICLE:
      if (strcmp (CommLineIn, ".") == 0) {   /* article receive complete */

   CommState = ST_IN_GROUP;
   CommBusy = FALSE;

   if (CommDecoding) {
     SendMessage (currentCoded->hParentWnd, IDM_ARTICLE_RETRIEVE_COMPLETE, 0, 0);
     break;
   }
   else
   {
          SendMessage (CommDoc->ParentDoc->hDocWnd, IDM_ARTICLE_RETRIEVE_COMPLETE, 0, 0);
          SendMessage(CommDoc->hDocWnd, IDM_ARTICLE_RETRIEVE_COMPLETE, 0, 0);
        }

   LockLine (CommDoc->ParentDoc->hParentBlock,
        CommDoc->ParentDoc->ParentOffset,
        CommDoc->ParentDoc->ParentLineID,
        &BlockPtr, &LinePtr);

   GroupDoc = (TypGroup far *) ((char far *) LinePtr + sizeof (TypLine));
   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);

   mylstrncpy (group, lpsz, MAXGROUPNAME);
   sprintf (mybuf, "%s (%u lines)", group, CommDoc->TotalLines);
   SetWindowText (CommDoc->hDocWnd, 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);
   found = FALSE;
   do {
     lpsz = ((char far *) LinePtr + sizeof (TypLine) + sizeof (TypText));
     if (IsLineBlank (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);
      }
      else        /* not finished, continue article receive */
   AddCommLineToDoc (CommLineIn);

      break;

    case ST_POST_WAIT_PERMISSION:

      /*      WndPost = getWndEdit(WndPosts,CommWnd,MAXPOSTWNDS) ; */
      /*      found = (WndPost != NULL) ; */

      /* lock out changes */
      SendMessage (PostEdit->hWndEdit, EM_SETREADONLY, TRUE, 0L);

      retcode = 0;
      sscanf (CommLineIn, "%u", &retcode);

      if (retcode == 340) {
   if (Attaching && !ReviewAttach)
     ProcessAttach (CONTINUE);
   else
     PostText (PostEdit);
      }
      else {
   check_server_code (retcode);
   MessageBox (PostEdit->hWnd, CommLineIn + 4, "Cannot Post Article",
          MB_OK | MB_ICONEXCLAMATION);
   CommBusy = FALSE;
   CommState = ST_NONE;

   if (Attaching && !ReviewAttach)
     ProcessAttach (ABORT);   /* cancel attachment */

   else
     /* unlock to allow user modification */
     SendMessage (PostEdit->hWndEdit, EM_SETREADONLY, FALSE, 0L);
      }
      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 == 441 || retcode == 440) {
   CompletePost(PostEdit, FAIL);
   done = TRUE;
   nextBatchIndex = 0;  /* cancel any batch */

   if (Attaching && !ReviewAttach)
     ProcessAttach (ABORT);/* cancel attachment */

      }
      else if (retcode == 240) {
   CompletePost(PostEdit, SUCCESS);
   done = TRUE;
      }
      else if (check_server_code (retcode))
   break;

      if (done) {
   CommBusy = FALSE;
   CommState = ST_NONE;
   PostEdit = (WndEdit *) NULL;

   if (nextBatchIndex)  /* if we're sending a batch, send the next */
     BatchSend (DOCTYPE_POSTING);
      }
      if (Attaching && !ReviewAttach)
   ProcessAttach (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 = memchr (textptr, QuoteLineInd, 2);
  if (!loc)
    loc = memchr (textptr, '|', 2);
  if (!loc)
    loc = memchr (textptr, ':', 2);
  return (loc != NULL);
}


/*-- function AddCommLineToDoc ---------------------------------------
 *
 *  Adds the given line to the comm doc
 */
void
AddCommLineToDoc (char *line)
{
  TypLine far *LinePtr;
  TypBlock far *BlockPtr;
  char *cptr, *cdest;
  int col, mylen;
  char artline[MAXINTERNALLINE];

  /* special case for lines starting with '..' */
  if (strncmp (CommLineIn, "..", 2))
    cptr = CommLineIn;
  else
    cptr = CommLineIn + 1;

  if (CommDecoding) {
    DecodeLine (currentCoded, cptr);
    return;
  }
  /* Copy this line into an image of a textblock line,
   * expanding tabs.
   */
  cdest = artline + sizeof (TypLine) + sizeof (TypText);
  for (col = 0;
  *cptr && col < (MAXINTERNALLINE - 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) mylen);
  CommDoc->ActiveLines++;
  UnlockLine (BlockPtr, LinePtr, &(CommDoc->hCurAddBlock),
         &(CommDoc->AddOffset), &(CommDoc->AddLineID));

  if ((CommDoc->TotalLines % UPDATE_ART_FREQ) == 0)
    InvalidateRect (CommDoc->hDocWnd, NULL, FALSE);
}

/*-- 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 (ArtNum, GroupPtr)
     unsigned long ArtNum;
     TypGroup far *GroupPtr;
{
  TypRange far *RangePtr = (TypRange far *) ((char far *)
            GroupPtr + RangeOffset (GroupPtr->NameLen));
  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 mylstrncmp -----------------------------------------------
 *
 *   Just like strncmp, except takes long pointers.
 */
int
mylstrncmp (ptr1, ptr2, len)
     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 (ptr1, ptr2, len)
     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 (ptr1, ptr2)
     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 (str1, str2)
     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;
/*  HANDLE hBlock; */
  char far *lpsz;
  char group[MAXGROUPNAME];
  char mybuf[MAXINTERNALLINE];
  header_p headers;

  /* 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 = ((TypGroup far *) ((char far *) LinePtr + sizeof (TypLine)));
  header_handle = GroupDoc->header_handle;
  thread_handle = GroupDoc->thread_handle;
  lock_headers (header_handle, thread_handle);

  if (threadp) {
    SetWindowText (CommDoc->hDocWnd, "sorting headers...");
    sort_by_threads (header_handle, thread_handle, CommDoc->TotalLines);
  }

  unlock_headers (header_handle, thread_handle);

  GroupDoc->total_headers = CommDoc->TotalLines;

  lpsz = (char far *) (((char far *) LinePtr) +
             sizeof (TypLine) + sizeof (TypGroup));

  mylstrncpy (group, lpsz, MAXGROUPNAME);
  sprintf (mybuf, "%s (%u articles)", group, CommDoc->TotalLines);
  SetWindowText (CommDoc->hDocWnd, 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 = ((TypGroup far *) ((char far *) LinePtr + sizeof (TypLine)));
    header_handle = GroupDoc->header_handle;
    thread_handle = GroupDoc->thread_handle;
    headers = lock_headers (header_handle, thread_handle);

    if (CommDoc->TotalLines > 0)
      for (i = CommDoc->TotalLines - 1;
      ((i > 0) && ((header_elt (headers, i))->Seen == FALSE));
      i--);
    CommDoc->TopLineOrd = (i > 5) ? i - 4 : 0;

    unlock_headers (header_handle, thread_handle);
  }

  SendMessage (CommDoc->hDocWnd, IDM_RETRIEVE_COMPLETE, 0, 0);
  InvalidateRect (CommDoc->hDocWnd, NULL, FALSE);
}

/*
 * 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;
}



/* ------------------------------------------------------------------------
 * 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);
}

/* ------------------------------------------------------------------------
 *    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 (hWndConf, 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;

  /* SendMessage (hWndEdit, EM_FMTLINES, TRUE, 0); */

  size = (unsigned int) SendMessage (hWndEdit, WM_GETTEXTLENGTH, 0, 0L) + 1;

  if ((newText = (char *) GlobalAllocPtr (GMEM_MOVEABLE, size * sizeof (char))) == NULL) {
    MessageBox (hWndEdit, "Memory allocation failure", "Edit Text", MB_OK);
    return (NULL);
  }
  if (SendMessage (hWndEdit, WM_GETTEXT, size, (LPARAM) ((LPCSTR) newText)) != (long) (size - 1)) {
    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));
}

/* ------------------------------------------------------------------------
 * To maximize amount of data allowable in posting, allocate a new
 * data segment for this posting from the Global heap, and set the
 * window hInstance to this segment
 * Don't forget you can't use EM_GET/SETHANDLE on an edit buf with
 * a global mem segment
 */
BOOL
CreateEditWnd (WndEdit * NewWnd)
{
#ifndef _WIN32
  GLOBALHANDLE editDS;
  LPVOID lpPtr;
  if ((editDS = GlobalAlloc (GMEM_DDESHARE | GMEM_MOVEABLE | GMEM_ZEROINIT, 1024L)) == NULL) {
    MessageBox (NewWnd->hWnd, "Memory allocation failure", "Edit Buffer", MB_OK);
    editDS = hInst;     /* use local heap instead */

  }
  else {
    lpPtr = GlobalLock (editDS);
    LocalInit (HIWORD ((LONG) lpPtr), 0, (WORD) (GlobalSize (editDS) - 16));
    UnlockSegment (HIWORD ((LONG) lpPtr));   /* we still have a global lock */

  }
  NewWnd->hWndEdit = CreateWindow ((LPCSTR) "edit", (LPCSTR) NULL,
          WS_CHILD | WS_VISIBLE | WS_HSCROLL | WS_VSCROLL | WS_BORDER |
         ES_LEFT | ES_MULTILINE | ES_AUTOHSCROLL | ES_AUTOVSCROLL,
               0, 0, 0, 0,
          NewWnd->hWnd, (HMENU) EDITID, (HINSTANCE) HIWORD ((LONG) lpPtr), NULL);
#else
  NewWnd->hWndEdit = CreateWindow ("edit", (char *) NULL,
          WS_CHILD | WS_VISIBLE | WS_HSCROLL | WS_VSCROLL | WS_BORDER |
         ES_LEFT | ES_MULTILINE | ES_AUTOHSCROLL | ES_AUTOVSCROLL,
               0, 0, 0, 0,
               NewWnd->hWnd, (HMENU) EDITID, hInst, NULL);  /* No problems with Win32 */
#endif
  if (!NewWnd->hWndEdit) {
    MessageBox (NewWnd->hWnd, "Window creation failure", "Edit Buffer", MB_OK);
    return (FAIL);
  }

  SendMessage (NewWnd->hWndEdit, EM_LIMITTEXT, 0, 0L);
  SetHandleBkBrush (NewWnd->hWndEdit, hArticleBackgroundBrush);
  NewWnd->dirty = DT_CLEAN;

  return (SUCCESS);
}
/* ------------------------------------------------------------------------
 *    Write an integer to the private profile
 */
BOOL
WritePrivateProfileInt (lpAppName, lpKeyName, intval, lpProFile)
     char far *lpAppName;
     char far *lpKeyName;
     char far *lpProFile;
     int intval;
{
  char msg[20];

  itoa (intval, msg, 10);
  return (WritePrivateProfileString (lpAppName, lpKeyName, msg, lpProFile));
}

/* ------------------------------------------------------------------------
 *    Write an unsigned integer to the private profile
 *      (JSC 1/8/94)
 */
BOOL
WritePrivateProfileUInt (lpAppName, lpKeyName, intval, lpProFile)
     char far *lpAppName;
     char far *lpKeyName;
     char far *lpProFile;
     unsigned int intval;
{
  char msg[20];

  uitoa (intval, msg, 10);
  return (WritePrivateProfileString (lpAppName, lpKeyName, msg, lpProFile));
}

/* ------------------------------------------------------------------------
 *    Get an unsigned integer to the private profile
 *      (JSC 1/8/94)
 */
unsigned int
GetPrivateProfileUInt (lpAppName, lpKeyName, intval, lpProFile)
     char far *lpAppName;
     char far *lpKeyName;
     char far *lpProFile;
     unsigned int intval;
{
  char buf[20];

  GetPrivateProfileString (lpAppName, lpKeyName, "", buf, 20, lpProFile);

  if (buf[0] == '\0')
    return (intval);
  else
    return (atoui (buf));
}
/* ------------------------------------------------------------------------
 *    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].hDocWnd, WM_SIZE, 0, 0L);
      InvalidateRect (GroupDocs[i].hDocWnd, NULL, TRUE);
    }
}

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].hDocWnd, WM_SIZE, 0, 0L);
      InvalidateRect (ArticleDocs[i].hDocWnd, NULL, TRUE);
    }

  for (i = 0; i < MAXPOSTWNDS; i++)
    if (WndPosts[i].hWnd) {
      SendMessage (WndPosts[i].hWndEdit, WM_SETFONT, (WPARAM) hFontArtNormal, TRUE);
      SetHandleBkBrush (WndPosts[i].hWndEdit, hArticleBackgroundBrush);
      InvalidateRect (WndPosts[i].hWndEdit, NULL, TRUE);
    }

  for (i = 0; i < MAXMAILWNDS; i++)
    if (WndMails[i].hWnd) {
      SendMessage (WndMails[i].hWndEdit, WM_SETFONT, (WPARAM) hFontArtNormal, TRUE);
      SetHandleBkBrush (WndMails[i].hWndEdit, hArticleBackgroundBrush);
      InvalidateRect (WndMails[i].hWndEdit, NULL, TRUE);
    }

}
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) {
    SetHandleBkBrush (hCodedBlockWnd, hStatusBackgroundBrush);
    SendMessage (hCodedBlockWnd, WM_SIZE, 0, 0L);
    InvalidateRect (hCodedBlockWnd, NULL, TRUE);
  }

}
/* ------------------------------------------------------------------------
 *    Close Window functions
 *      Batch operation, close all windows of a certain type
 *      (group/article/status)
 *      (JSC 1/18/94)
 */
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].hDocWnd, 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].hDocWnd, 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
CascadeWindows()
{
  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(hWndConf, (HWND)NULL, 1, width, height, maxX, maxY);
  nthWnd = 2;
  for (i = 0; i < MAXGROUPWNDS; i++)
    if (GroupDocs[i].InUse && GroupDocs[i].hDocWnd && !IsMinimized(GroupDocs[i].hDocWnd)) {
		prevWnd = CascadeWnd(GroupDocs[i].hDocWnd, prevWnd, nthWnd, width, height, maxX, maxY);
        nthWnd++;
	}
    
  for (i = 0; i < MAXARTICLEWNDS; i++)
    if (ArticleDocs[i].InUse && ArticleDocs[i].hDocWnd && !IsMinimized(ArticleDocs[i].hDocWnd)) {
		prevWnd = CascadeWnd(ArticleDocs[i].hDocWnd, 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)
    SetWindowPos(hCodedBlockWnd, (HWND)NULL, (xScreen-STATUSWIDTH)>>1, 1, STATUSWIDTH, STATUSHEIGHT, SWP_DRAWFRAME);
  
}
/* ------------------------------------------------------------------------
 *    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 */

  if (type == DOCTYPE_POSTING)
    StartPost (&WndEdits[thisSend]);
  else
    StartMail (&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, I am already busy communicating with the server.\n"
      "Try again in a little while.", msg,
      MB_OK | MB_ICONASTERISK);
    return (TRUE);
  }
  else
    return (FALSE);
}

BOOL
TestDecodeBusy (HWND hParentWnd, char *msg)
{
  if (Decoding || CommDecoding) {
    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(hWndConf);
    for (i = 0; i < MAXARTICLEWNDS; i++)
	    if (ArticleDocs[i].hDocWnd) 
	    	SetArticleMailMenu(ArticleDocs[i].hDocWnd);

	for (i = 0; i < MAXGROUPWNDS; i++)
		if (GroupDocs[i].hDocWnd)
	    	SetGroupMailMenu(GroupDocs[i].hDocWnd);
}   	
