Message-Id: <2.2.32.19960527231053.002e4a10@tulsa.dowell.slb.com>
X-Sender: brydon@tulsa.dowell.slb.com
X-Mailer: Windows Eudora Pro Version 2.2 (32)
Mime-Version: 1.0
Content-Type: multipart/mixed; boundary="=====================_833256653==_"
Date: Mon, 27 May 1996 18:10:53 -0500
To: DUMOULIN@TITAN.KSC.NASA.GOV
From: Harvey Brydon <brydon@tulsa.dowell.slb.com>
Subject: WinVN submission
Cc: brydon@tulsa.dowell.slb.com,vev@michvhf.com
X-Attachments: D:\share\winvn\HB998B2\WVUSENET.cpp;
  D:\share\winvn\HB998B2\INI_NEWS.cpp;
  D:\share\winvn\HB998B2\WVGROUP.cpp;

--=====================_833256653==_
Content-Type: text/plain; charset="us-ascii"

A lot of work for me, but not much code to show for it... :-(

Reasons for submission:
- Provide visual cue (FrameRect) in article list for article currently being
read.
- Find first article in article list if it was selected (incorrect index
value used in loop).
- Provide global article sort default (undocumented via WinVN.ini setting).

[Tested under Win32 only.]

Besides the things above, I also tried to look at:
(1) The recently reported printing problem.  I didn't see anything wrong,
and was not able to duplicate the problems.  From the messages I looked at,
I don't know if the situation is unique to the 16 or 32 bit version or in
both.  I have read from an unauthoritative source that for multi-page
documents, you are supposed to reset the font and pagesize info at every
newpage.  Some print drivers don't require this but some do... I see you
don't do this here but I didn't diddle with your code.
(2) The recently reported problem with email addresses being either
truncated or getting "bonus" characters.  I wasn't able to duplicate...  I
just saw Vince's message again that describes the problem more accurately.
I'll investigate later this week.
(3) Recently reported problem encoding.  Unable to duplicate on NT with 32
bit version...





--=====================_833256653==_
Content-Type: text/plain; charset="us-ascii"
Content-Disposition: attachment; filename="WVUSENET.cpp"

/********************************************************************
 *                                                                  *
 *  MODULE    :  WVUSENET.CPP                                       *
 *                                                                  *
 *  PURPOSE   : This file contains the window procedure and support *
 *              routines for WinVn's main window                    *
 *                                                                  *
 ********************************************************************/

/*
 * $Id: wvusenet.cpp 1.8 1995/11/10 06:45:49 dumoulin Exp $
 *
 */
#include <windows.h>
#include <windowsx.h>
#include "wvglob.h"
#include "winvn.h"
#include <errno.h>

#pragma hdrstop
#include "wvclass.h"
#include <stdlib.h>
#include <assert.h>
extern "C"
{
#include "wvtb\wvtb.h"          /* for toolbar  */
}
#include "WVClass.h"
#ifdef USE_3D_CONTROLS
#include "ctl3d.h"
#endif

/* This is for the special case where the user invoked the */
/* program with dolist=0, then turned it on.  Without checking */
/* this condition, the newsrc will be truncated to only those */
/* groups read.  SMR 930224 */

int started_with_no_dolist;
void show_version_strings (HWND);
void SetGroupActiveLines (void);
BOOL WarnColors (COLORREF c1, COLORREF c2, char *warnMsg);
BOOL SelectGroup (TypDoc far * Doc, TypGroup far * group, BOOL value);

static char groupbuf[MAXHEADERLINE];

/*--- FUNCTION: WinVnConfWndProc ---------------------------------------
 *
 *    Window procedure for the "Net" window.
 *    This window displays the names of the newsgroups, one per line.
 *
 *    Entry    The usual parameters for any window function.
 *
 *    Note:    We don't do anything until "Initialized" is TRUE.
 *             This way, the user won't try to perform functions
 *             while the communications are still being set up.
 */

long WINAPI
WinVnConfWndProc(HWND hWnd, unsigned message, WPARAM wParam, LPARAM lParam)
{
  PAINTSTRUCT ps;               /* paint structure          */
//  POINT ptCursor;

  HDC hDC;                      /* handle to display context */
  HWND hWndView;
  RECT myRect;                  /* selection rectangle                */

  int j;
  unsigned int i;
  int found;
  char far *lpsz;
  TypLine far *LinePtr, far * GroupLinePtr;
  TypBlock far *BlockPtr, far * GroupBlockPtr;
  HANDLE hBlock;
  TypDoc *MyDoc;
  unsigned int Offset;
  BOOL looping = TRUE;
  TypGroup far *MyGroup;

  int X, Y;
  int docnum;
  int newdoc;
  char mybuf[MAXINTERNALLINE];
  int OldSel = FALSE;
  int CtrlState;
  TypLineID MyLineID;

  if (StatusBarProc (hWnd, message, wParam, lParam, &NetDoc))
    return(0);

  switch (message) {

/*
   case WM_DESTROY:
   SetHandleBkBrush (hWnd, GetStockObject (WHITE_BRUSH));
   break;
 */

  case WM_CREATE:
    MailInit (hWnd);

    /* Get handle to the hourglass cursor */
    hHourGlass = LoadCursor (NULL, IDC_WAIT);

    /* Fill in the Net document's window handle that we
     * were unable to fill in in the call to InitDoc. */  
    NetDoc.hDocWnd = hWnd;
    InitBitmaps ();
    break;

  case WM_CHAR:
    /* Hitting CR on a group name is the same as double-clicking
     * on the name. */
    if (!Initializing) {
      if (wParam == '\r') {

//       if (GroupListMultiSelect) {
          if (LineIDtoLinePtr (NetDoc.ActiveLineID, &NetDoc, &GroupBlockPtr, &GroupLinePtr))
            goto getgroup1;
 //        else
 //          break;
 //
 //       }
        else {
          GetCursorPos (&ptCursor);
          ScreenToClient (hWnd, &ptCursor);
          X = ptCursor.x;
          Y = ptCursor.y;
          goto getgroup;
        }
      }
    }
    break;

  case WM_KEYDOWN:
    /* See if this key should be mapped to a scrolling event
     * for which we have programmed the mouse.  If so,
     * construct the appropriate mouse call and call the mouse code.
     * Also, F6 means switch to next window.
     * (This latter should also be table-driven.)
     */
    if (wParam == VK_F6) {
      NextWindow (NetDoc.hWndFrame, NetDoc.DocType);
    }                               
    else if (!Initializing) {
      switch (wParam) {
      case VK_DOWN:                      /* move one line down from Active line */
          if (LineIDtoLinePtr (NetDoc.ActiveLineID, &NetDoc, &BlockPtr, &LinePtr)) {
            if (NextActiveLine (&BlockPtr, &LinePtr)) {
              SetSelections (&NetDoc, LinePtr);
              AdjustMidSc (BlockPtr, LinePtr);
            }
          }
        break;

      case VK_UP:                       /* move one line up from Active line */
          if (LineIDtoLinePtr (NetDoc.ActiveLineID, &NetDoc, &BlockPtr, &LinePtr)) {
            if (PrevActiveLine (&BlockPtr, &LinePtr)) {
              SetSelections (&NetDoc, LinePtr);
              AdjustMidSc (BlockPtr, LinePtr);
            }
          }
          break;

      case VK_HOME:
          if (TopOfDoc (&NetDoc, &BlockPtr, &LinePtr)) {
          	SetSelections (&NetDoc, LinePtr);
          	AdjustMidSc (BlockPtr, LinePtr);
		  }
          break;

      case VK_END:
          if (BottomOfDoc (&NetDoc, &BlockPtr, &LinePtr)) {
          	SetSelections (&NetDoc, LinePtr);
          	AdjustMidSc (BlockPtr, LinePtr);
		  }
          break;

      case VK_NEXT:
          if (LineIDtoLinePtr (NetDoc.ActiveLineID, &NetDoc, &BlockPtr, &LinePtr)) {
            for (i = 0; i < NetDoc.ScYLines; i++) {
              if (!NextActiveLine (&BlockPtr, &LinePtr))
                break;
            }
            SetSelections (&NetDoc, LinePtr);
            AdjustMidSc (BlockPtr, LinePtr);
          }
        break;

      case VK_PRIOR:
          if (LineIDtoLinePtr (NetDoc.ActiveLineID, &NetDoc, &BlockPtr, &LinePtr)) {
            for (i = 0; i < NetDoc.ScYLines; i++) {
              if (!PrevActiveLine (&BlockPtr, &LinePtr))
                break;
            }
            SetSelections (&NetDoc, LinePtr);
            AdjustMidSc (BlockPtr, LinePtr);
          }
          break;

      default:
        /* Based on the state of the Control key and the value
         * of the key just pressed, look up in the array
         * key2scroll to see if we should process this keystroke
         * as if it were a mouse event.  If so, simulate the
         * mouse event by sending the appropriate message.
         */

        CtrlState = GetKeyState (VK_CONTROL) < 0;
        for (j = 0; j < NUMKEYS; j++) {
          if (wParam == key2scroll[j].wVirtKey &&
              CtrlState == key2scroll[j].CtlState) {
            SendMessage (hWnd, key2scroll[j].iMessage,
                         key2scroll[j].wRequest, 0L);
            break;
          }
        }

      }
    }
    break;

  case WM_LBUTTONDOWN:
    /*  Clicking the left button on a group name toggles the
     *  selected/not selected status of that group.
     *  Currently selected groups are displayed in reverse video.
     */

    DragMouseAction = DRAG_NONE;
    if (!Initializing) {
      BOOL status;
      TypGroup *aGroup;

      X = LOWORD (lParam);
      Y = HIWORD (lParam);

      /* make this behave more like a multi-select listbox (jlg) */
      /* if click,       make the line the only one selected */
      /* if ctrl-click,  add the line to the selection */
      /* if shift-click, select from previously selected to current */
      if (GroupListMultiSelect) {
        if (!(wParam & MK_CONTROL) && !(wParam & MK_SHIFT)) {
          if (NetDoc.SelectedLines == 1) {
            LineIDtoLinePtr (NetDoc.ActiveLineID, &NetDoc, &BlockPtr, &LinePtr);
            SelectGroup (&NetDoc, GetGroup(LinePtr), FALSE);
          }
          else {
            ForAllLines (&NetDoc, SetLineFlag, 0, FALSE);
          }
        }
      }
      if (CursorToTextLine (X, Y, &NetDoc, &BlockPtr, &LinePtr)) {
        aGroup = GetGroup(LinePtr);
        status = SelectGroup (&NetDoc, aGroup, aGroup->Selected ? FALSE : TRUE);
        if (GroupListMultiSelect) {
          NetDoc.ActiveLineID = LinePtr->LineID;
          if (wParam & MK_SHIFT) {
            ForAllLines (&NetDoc, SetExLineFlag, LINE_FLAG_SET, FALSE);
          }
          else
            NetDoc.AnchorLineID = LinePtr->LineID;

          if (!(wParam & MK_CONTROL) && !(wParam & MK_SHIFT))
            DragMouseAction = status ? DRAG_SELECT : DRAG_DESELECT;
          else
            DragMouseAction = DRAG_NONE;
        }
        else
          DragMouseAction = status ? DRAG_SELECT : DRAG_DESELECT;

        GlobalUnlock (BlockPtr->hCurBlock);
        SetCapture (hWnd);      /* release capture on button up */
      }

      InvalidateRect (hWnd, NULL, FALSE);
    }
    break;

  case WM_LBUTTONUP:
    /*  Letting up on the left button on a group name 
     *  gets us out of Dragging mode   */
    ReleaseCapture ();
    DragMouseAction = DRAG_NONE;
    break;

  case WM_MOUSEMOVE:
    /*  Code to drag the mouse and change the select/not selected
     *  status of that group.*/
    if ((!Initializing) && (DragMouseAction != DRAG_NONE)) {
      X = LOWORD (lParam);
      Y = HIWORD (lParam);

      GetClientRect (hWnd, &myRect);
      if (CursorToTextLine (X, Y, &NetDoc, &BlockPtr, &LinePtr)) {
        if (GroupListMultiSelect) {
          NetDoc.ActiveLineID = LinePtr->LineID;
          ForAllLinesOnScreen (&NetDoc, SetExLineFlag, LINE_FLAG_SET, FALSE);
          GlobalUnlock (BlockPtr->hCurBlock);
        }
        else {
          SelectGroup (&NetDoc, GetGroup(LinePtr),(DragMouseAction == DRAG_SELECT) ? TRUE : FALSE);
          GlobalUnlock (BlockPtr->hCurBlock);
        }
      }
      InvalidateRect (hWnd, NULL, FALSE);
    }
    break;

  case WM_LBUTTONDBLCLK:
    /*  Double-clicking on a group name creates a "Group"
     *  window, whose purpose is to display the subjects
     *  of the articles in the group. */
    if (!Initializing) {
      X = LOWORD (lParam);
      Y = HIWORD (lParam);

      getgroup:;
      if (CursorToTextLine (X, Y, &NetDoc, &GroupBlockPtr, &GroupLinePtr)) {

      getgroup1:;

        if (MyDoc = (GetGroup(GroupLinePtr))->SubjDoc) {

          /* We already have a document containing the subjects */
          /* of this group, so just activate it.                */

          ShowWindow (MyDoc->hWndFrame, SW_SHOWNORMAL);
          SetActiveWindow (MyDoc->hWndFrame);
          SetFocus (MyDoc->hDocWnd);
          GlobalUnlock (GroupBlockPtr->hCurBlock);
          break;
        }
        if (TestCommBusy (hWnd, "Can't Retrieve Group")) {
          GlobalUnlock (GroupBlockPtr->hCurBlock);
          break;
        }

        /* At this point, we've determined that the subjects for
         * this newsgroup are not in memory, and that it's OK
         * to fetch them from the server.  (Comm line not busy.)
         * Figure out whether a new window/document should be
         * created for this group & set "newdoc" accordingly.
         */
        newdoc = FALSE;
        if (ViewNew || !ActiveGroupDoc || !(ActiveGroupDoc->InUse)) {
          found = FALSE;
          for (docnum = 0; docnum < MAXGROUPWNDS; docnum++) {
            if (!GroupDocs[docnum].InUse) {
              found = TRUE;
              newdoc = TRUE;
              CommDoc = &(GroupDocs[docnum]);
              CommDoc->LongestLine = 0;
              CommDoc->ScXOffset = 0;
              break;
            }
          }
          if (!found) {
            MessageBox (hWnd, "You have too many group windows active.\n"
                              "Close one or deselect 'New window for each group'.", 
                              "Can't open new window", MB_OK | MB_ICONASTERISK);
            UnlockLine (GroupBlockPtr, GroupLinePtr, &hBlock, &Offset, &MyLineID);
            break;
          }
        }
        else {
          /* Must reuse old window for this group.           */
          ActiveGroupDoc->LongestLine = 0;
          ActiveGroupDoc->ScXOffset = 0;
          CommDoc = ActiveGroupDoc;

          LockLine (CommDoc->hParentBlock, CommDoc->ParentOffset, CommDoc->ParentLineID, &BlockPtr, &LinePtr);
          MyGroup = GetGroup(LinePtr);
          MyGroup->SubjDoc = (TypDoc *) NULL;

          /* make the 'n' in the group-list go away */
          MyGroup->HighestPrevSeen = MyGroup->ServerLast;
          InvalidateRect (NetDoc.hDocWnd, NULL, TRUE);

          UnlockLine (BlockPtr, LinePtr, &hBlock, &Offset, &MyLineID);
          
          /* Update range info for Newsrc and clear out contents of document for reuse */
          UpdateSeenArts (CommDoc);
          UnlinkArtsInGroup (CommDoc);
          FreeDoc (CommDoc);
        }

        /* Create window title indicating we are retrieving text. */
        CommDoc->InUse = TRUE;
        lpsz = GetGroupName(GroupLinePtr);
        strcpy (mybuf, "Retrieving ");
        lstrcat (mybuf, lpsz);

        /* If necessary, create a new window.              */
        if (newdoc) {
          int x, y, width, height;
          char poschars[MAXINTERNALLINE];

          /* Compute default screen position. */
          x = 1;
          y = 0;
          width = (int) xScreen;
          height = (int) (yScreen * 1 / 2);

          /* If the screen position has been saved, use that instead. */
          GetPrivateProfileString (ARTLIST, "GroupWindowPos", "!",
                                   poschars, MAXINTERNALLINE, szAppProFile);
          if (poschars[0] != '!') {
            sscanf (poschars, "%d,%d,%d,%d", &x, &y, &width, &height);
          }
          hWndView = CreateWindow ("WinVnViewFrame",
                                   mybuf,
                                   WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN,
                                   x + (int) docnum * CharWidth,    /* Initial X pos */
                                   y + (int) docnum * LineHeight,
                                   width,   /* Initial X Width */
                                   height,
                                   NULL,
                                   NULL,
                                   hInst,
                                   NULL);

          if (!hWndView)
            return (0);         /* ??? */

/*    SetHandleBkBrush (hWndView, hListBackgroundBrush); */
          ShowWindow (hWndView, SW_SHOWNORMAL);
        }
        else {
          hWndView = CommDoc->hWndFrame;
          SetWindowText (hWndView, mybuf);
          SetGroupMenus (CommDoc, DISABLE);
        }
        RcvLineCount = 0;

        /* Do some housekeeping:  Set group as selected,
         * initialize empty document to hold subject lines,
         * set focus to this document, set pointers linking
         * this document and the subject line.
         */
        SelectGroup (&NetDoc, GetGroup(GroupLinePtr), TRUE);
        InitDoc (CommDoc, hWndView, &NetDoc, DOCTYPE_GROUP);
        PtrToOffset (GroupBlockPtr, GroupLinePtr, &(CommDoc->hParentBlock),
                     &(CommDoc->ParentOffset), &(CommDoc->ParentLineID));
        SetActiveWindow (CommDoc->hWndFrame);
        SetFocus (CommDoc->hDocWnd);
        (GetGroup(GroupLinePtr))->SubjDoc = CommDoc;
        GlobalUnlock (GroupBlockPtr->hCurBlock);
        InvalidateRect (CommDoc->hDocWnd, NULL, FALSE);
        UpdateWindow (CommDoc->hDocWnd);
        InvalidateRect (NetDoc.hDocWnd, NULL, FALSE);

        /* Deal with Comm-related stuff:  set FSA variables,
         * send the GROUP command to NNTP.*/
        CommLinePtr = CommLineIn;
        CommBusy = TRUE;
        CommState = ST_GROUP_RESP;
        hSaveCursor = SetCursor (hHourGlass);
        ShowCursor (TRUE);
        strcpy (mybuf, "GROUP ");
        lpsz = GetGroupName(GroupLinePtr);
        lstrcat (mybuf, lpsz);
        mylstrncpy (CurrentGroup, lpsz, MAXFINDSTRING);
        PutCommLine (mybuf);
      }
    }
    break;

/* If we've lost the connection for some reason, kill the timer */
    /* that's banging on the socket. */
  case WM_TIMER:
    if ((CommState == ST_CLOSED_COMM) && (!Initializing)) {
      KillTimer (hWnd, ID_TIMER);
    }
    else {
      if (CommState != ST_CLOSED_COMM)
        DoCommInput ();
    };
    break;

  case WM_SIZE:
    GetClientRect (hWnd, &myRect);
    NetDoc.ScXWidth = myRect.right;
    NetDoc.ScYHeight = myRect.bottom;
    NetDoc.ScYLines = (RectHeight (myRect) - TopSpace) / LineHeight;
    NetDoc.ScXChars = (RectWidth (myRect) - SideSpace) / CharWidth;
    break;

  case WM_VSCROLL:
    if (!Initializing) {
      ScrollIt (&NetDoc, wParam, lParam);
    }
    break;

  case WM_HSCROLL:
    if (!Initializing) {
      HScrollIt (&NetDoc, wParam, lParam);
    }
    break;

  case WM_PAINT:
    {
      HANDLE hBlock;
      SIZE sz;
      unsigned int Offset, MyLen, indicatorwidth;
      int VertLines, HorzChars;
      int EndofDoc = FALSE;
      int RangeHigh, CurPos;
      int Xtext;
      char far *textptr;
      char *cptr, *cptr2;
      char indicator;
      char indcptr[128];
      char group_name[MAXINTERNALLINE];
      char init_msg[60];
      TypGroup far *MyGroup;
      COLORREF MyColors[4], MyBack[4];
      RECT myRect, aRect;
      int MyColorMask = 1, PrevColorMask = MyColorMask;
      HBRUSH hOldBrush;
      HANDLE hOldFont;
	  BOOL GotLock = FALSE;

      hDC = BeginPaint (hWnd, &ps);
      GetClientRect (hWnd, &myRect);

      /* If still initializing comm link, put out a message    */
      /* to that effect.                                       */
      switch (Initializing) {
      case INIT_NOT_CONNECTED:
        cptr = "Not connected";
        cptr2 = "to news server";
        goto display_msg;

      case INIT_ESTAB_CONN:
        cptr = "Establishing link";
        cptr2 = "to news server...";
        goto display_msg;

      case INIT_READING_NEWSRC:
        cptr = "Reading your log";
        cptr2 = "(\"newsrc\")...";
        goto display_msg;

      case INIT_SCANNING_NETDOC:
        cptr = "Creating hash table";
        cptr2 = "from current groups...";
        goto display_msg;

      case INIT_GETTING_LIST:
        sprintf (mybuf, "Receiving %uth newsgroup", RcvLineCount);
        cptr = mybuf;
        cptr2 = "name from server...";

      display_msg:;
        hOldFont = SelectObject (hDC, hWinVnFont);
        SetBkColor (hDC, ListBackgroundColor);
        if (IsDark(ListBackgroundColor))
          SetTextColor (hDC, RGB (200, 200, 200));  /* if background is black, make text white */
        else
          SetTextColor (hDC, RGB (0, 0, 0));    /* otherwise text is black */

        X = SideSpace + CharWidth;
        Y = LineHeight;
        strcpy (init_msg, cptr);
        strcat (init_msg, "    ");
        j = strlen (init_msg);
        TextOut (hDC, X, Y, init_msg, j);
        strcpy (init_msg, cptr2);
        strcat (init_msg, "    ");
        j = strlen (init_msg);
        TextOut (hDC, X, Y + WinVnLineHeight, init_msg, j);
        SelectObject (hDC, hOldFont);
        break;

      case INIT_READY:
      
        /* this state happens when all connection prep is finished 
         * and we are ready to begin normal WinVn user operation */
        SetUserMenus (&NetDoc, ENABLE);
        Initializing = INIT_DONE;
        /* fall into INIT_DONE */

      case INIT_DONE:
        VertLines = NetDoc.ScYLines;
        HorzChars = NetDoc.ScXChars;

        MyColors[0] = NetUnSubscribedColor;     /* unsubscribed/unselected     */

        MyBack[0] = ListBackgroundColor;
        MyColors[1] = NetSubscribedColor;		/* subscribed/unselected */

        MyBack[1] = ListBackgroundColor;

        if (UseInverseSelections) {
          MyColors[2] = ListBackgroundColor;    /* unsubscribed/selected */

          MyBack[2] = NetUnSubscribedColor;
          MyColors[3] = ListBackgroundColor;    /* subscribed/selected */

          MyBack[3] = NetSubscribedColor;
          MyColors[2] = ListBackgroundColor;    /* unsubscribed/selected */

          MyBack[2] = NetUnSubscribedColor;
          MyColors[3] = ListBackgroundColor;    /* subscribed/selected */

          MyBack[3] = NetSubscribedColor;
        }
        else {
          if (NetUnSubscribedColor == RGB (0, 0, 0) ||  /* unsub/selected */
               ListBackgroundColor == RGB (0, 0, 0) && IsBright (NetUnSubscribedColor))
            MyColors[2] = ListBackgroundColor;
          else
            MyColors[2] = NetUnSubscribedColor;

          if (NetSubscribedColor == RGB (0, 0, 0) ||    /* sub/selected */
               ListBackgroundColor == RGB (0, 0, 0) && IsBright (NetSubscribedColor))
            MyColors[3] = ListBackgroundColor;
          else
            MyColors[3] = NetSubscribedColor;

          if (ListBackgroundColor == RGB (0, 0, 0))
            MyBack[2] = RGB (200, 200, 200);
          else
            MyBack[2] = RGB (0, 0, 0);
          MyBack[3] = MyBack[2];
        }

        SetTextColor (hDC, MyColors[MyColorMask]);
        SetBkColor (hDC, MyBack[MyColorMask]);

        hOldFont = SelectObject (hDC, hListFont);

        GetTextExtentPoint (hDC, "n", 2, &sz);  /* was a '*', now will be 'u' or 'n' */
        indicatorwidth = sz.cx * 7 * 7;

        /* Update the scroll bar thumb position.                 */
        RangeHigh = NetDoc.ActiveLines - VertLines;
        if (RangeHigh < 0) {
          RangeHigh = 0;
          NetDoc.TopLineOrd = 0;
          NetDoc.TopScLineID = 0;
          NetDoc.hCurTopScBlock = NetDoc.hFirstBlock;
          NetDoc.TopScOffset = sizeof (TypBlock);
          GotLock = LockLine (NetDoc.hFirstBlock, NetDoc.TopScOffset, NetDoc.TopScLineID,
                              &BlockPtr, &LinePtr);
		  if (GotLock) {
             AdvanceToActive (&BlockPtr, &LinePtr);
             VertLines = NetDoc.ActiveLines;
			 }
         }
         else {
          GotLock = LockLine (NetDoc.hCurTopScBlock, NetDoc.TopScOffset, NetDoc.TopScLineID,
                              &BlockPtr, &LinePtr);
        }

		if (GotLock == FALSE) return(0);  /* JD 6/6/95 */

        CurPos = NetDoc.TopLineOrd;
        if (CurPos < 0)
          CurPos = 0;

        SetScrollRange (hWnd, SB_VERT, 0, RangeHigh,
                        NetDoc.ThumbTracking ? FALSE : TRUE);
        /* thumb pos is updated at end of thumb track */
        if (!NetDoc.ThumbTracking) {
            SetScrollPos (hWnd, SB_VERT, CurPos, TRUE);
        }
        
        RangeHigh = NetDoc.LongestLine - NetDoc.ScXChars;
        if (RangeHigh < 0) {
          RangeHigh = 0;
          NetDoc.ScXOffset = 0;
        }
        SetScrollRange (hWnd, SB_HORZ, 0, RangeHigh, FALSE);
        SetScrollPos (hWnd, SB_HORZ, NetDoc.ScXOffset, TRUE);

        /* Loop through the lines, painting them on the screen. */
        X = SideSpace - NetDoc.ScXOffset * (CharWidth + 1);
        Xtext = X + indicatorwidth;
        Y = StartPen;
        if (LinePtr->length != END_OF_BLOCK)
          do {
            MyGroup = GetGroup(LinePtr);
            MyLen = MyGroup->NameLen;
            textptr = GetGroupName(LinePtr);

            /* Display this line only if it is active. */
            if (LinePtr->active) {

              /* Figure out the color of this line. */
              if (MyGroup->Subscribed) {
                if (MyGroup->Selected)
                  MyColorMask = 3;
                else
                  MyColorMask = 1;
              }
              else {
                if (MyGroup->Selected)
                  MyColorMask = 2;
                else
                  MyColorMask = 0;
              }

              if (MyColorMask != PrevColorMask) {
                SetTextColor (hDC, MyColors[MyColorMask]);
                SetBkColor (hDC, MyBack[MyColorMask]);
                PrevColorMask = MyColorMask;
              }

              /* Figure out what indicator character to use. */
              indicator = ' ';
              if (NetDoc.FindLineID == LinePtr->LineID) {
                indicator = '>';
              }
              else if (MyGroup->HighestPrevSeen) {
                if (MyGroup->ServerLast > MyGroup->HighestPrevSeen) {
                  indicator = 'n';  /* was a '*' */
                }
                else {
                  /* check if there are unseen articles */
                  if (MyGroup->NumUnread && MyGroup->ServerEstNum > 0) {
                      indicator = 'u';
                  }
                }
              }

              mylstrncpy (group_name, textptr, MyGroup->NameLen + 1);

              if (MyGroup->Determined == FALSE)
                strcpy (str, "-");
              else
                sprintf (str, "%5lu", MyGroup->ServerEstNum);

              sprintf (indcptr, "%c %5s %s ", indicator, str, group_name);

              /* Now write out the line.                            */
              MyLen = strlen (indcptr);
              SetRect (&aRect, 0, Y, myRect.right, Y + LineHeight);
              ExtTextOut (hDC, X, Y, ETO_OPAQUE | ETO_CLIPPED, &aRect,
                          indcptr, MyLen, (LPINT) NULL);

              if (NetDoc.ActiveLineID == -1L)
                NetDoc.ActiveLineID = LinePtr->LineID;
              if (NetDoc.ActiveLineID == LinePtr->LineID && GroupListMultiSelect)
                DrawFocusRect (hDC, &aRect);

              Y += LineHeight;
              --VertLines;
            }                   /* end if LinePtr->active */
          }
          while (VertLines > 0 && NextLine (&BlockPtr, &LinePtr));

        SelectObject (hDC, hOldFont);

        /* Blank out bottom and top of screen */
        hOldBrush = (HBRUSH) SelectObject (hDC, hListBackgroundBrush);
        PatBlt (hDC, 0, Y, myRect.right - 1, myRect.bottom - Y, PATCOPY);
        PatBlt (hDC, 0, 0, myRect.right - 1, TopSpace, PATCOPY);
        SelectObject (hDC, hOldBrush);
        UnlockLine (BlockPtr, LinePtr, &hBlock, &Offset, &MyLineID);
      }
      EndPaint (hWnd, &ps);
      break;
    }

  case WM_ENDSESSION:

    WinHelp (NetDoc.hDocWnd, LFN_HELP, HELP_QUIT, 0L);
    /*CloseComm (CommDevice); */
    break;

  case WM_COMMAND:
    return (SendMessage (NetDoc.hWndFrame, message, wParam, lParam));

  default:
    return (DefWindowProc (hWnd, message, wParam, lParam));
  }
  return (0);
}

void
SetSelections (TypDoc * MyDoc, TypLine far * LinePtr)
{
  TypLine far *deselPtr;
  TypBlock far *BlockPtr;
  TypLineID prevActive;

  if (GetKeyState (VK_SHIFT) >= 0) {    /* shift is NOT pressed  */
    prevActive = MyDoc->ActiveLineID;
    MyDoc->ActiveLineID = LinePtr->LineID;
    /* clear select status of all lines */
    if (MyDoc->SelectedLines == 1) {
      LineIDtoLinePtr (prevActive, MyDoc, &BlockPtr, &deselPtr);
      SelectGroup (MyDoc, GetGroup(deselPtr), FALSE);
    }
    else {
      ForAllLines (MyDoc, SetLineFlag, LINE_FLAG_SET, FALSE);
    }
    /* set only current line selected and make it the anchor */
    SelectGroup (MyDoc, GetGroup(LinePtr), TRUE);
    MyDoc->AnchorLineID = LinePtr->LineID;
  }
  else
    /* shift is pressed  */
  {
    /* select all lines between ActiveLineID and AnchorLineID inclusive */
    MyDoc->ActiveLineID = LinePtr->LineID;
    ForAllLines (MyDoc, SetExLineFlag, LINE_FLAG_SET, FALSE);
  }
}

/* if enable is true then enable connect, disable disconnect */
/* disconnect is managed in INTITMENU */
void
SetConnectMenuItems (TypDoc FAR * DocPtr, int enable)
{
  HMENU hMenu, hSubMenu;
  hMenu = GetMenu (DocPtr->hWndFrame);
  hSubMenu = GetSubMenu (hMenu, 0);     /* network menu */

  EnableMenuItem (hSubMenu, IDM_CONNECT,  enable ? ENABLE_MENU : DISABLE_MENU);
  EnableMenuItem (hSubMenu, IDM_RESET, ENABLE_MENU);
  SendMessage (DocPtr->hWndTB, TB_ENABLEBUTTON, IDB_TOGGLE_CONNECT, TRUE);
  SendMessage (DocPtr->hWndTB, TB_CHECKBUTTON, IDB_TOGGLE_CONNECT, enable ? FALSE : TRUE);

  /* updates the exit menus/button */
  SendMessage (NetDoc.hWndFrame, WM_MYINITMENU, (WPARAM) 0, (LPARAM) 0);
}

void
SetMainMailMenu (TypDoc FAR * DocPtr)
{
  HMENU hMenu, hSubMenu;

  hMenu = GetMenu (DocPtr->hWndFrame);
  hSubMenu = GetSubMenu (hMenu, 0);     /* network menu */
  EnableMenuItem (hSubMenu, IDM_LOGOUT, MailCtrl.enableLogout);
  hSubMenu = GetSubMenu (hMenu, 2);     /* utilities menu */
  EnableMenuItem (hSubMenu, IDM_MAIL, MailCtrl.enableMail);
  SendMessage (DocPtr->hWndTB, TB_ENABLEBUTTON, IDM_MAIL,
               (MailCtrl.enableMail == MF_ENABLED));
}

void
SetUserMenus (TypDoc FAR * DocPtr, int enable)
{
  HMENU hMenu, hSubMenu;
  UINT mode;

  assert (DocPtr->hWndFrame != 0);
  mode = (enable == ENABLE) ? ENABLE_MENU : DISABLE_MENU;
  hMenu = GetMenu (DocPtr->hWndFrame);
  hSubMenu = GetSubMenu (hMenu, 1);     /* group menu */
  EnableMenuItem (hSubMenu, IDM_FIND, mode);
  EnableMenuItem (hSubMenu, IDM_FIND_NEXT_SAME, mode);
  hSubMenu = GetSubMenu (hMenu, 2);     /* utilities menu */
  EnableMenuItem (hSubMenu, IDM_POST, mode);
  hSubMenu = GetSubMenu (hMenu, 0);     /* network menu */
  EnableMenuItem (hSubMenu, IDM_RESET, mode);

  /* connect item is enabled when all else is disabled and v.v. */
  SetConnectMenuItems (DocPtr, !enable);
  SetMainMailMenu (DocPtr);

  /* set toolbar buttons */
  mode = (enable == ENABLE) ? TRUE : FALSE;
  SendMessage (DocPtr->hWndTB, TB_ENABLEBUTTON, IDM_RESET, mode);
  SendMessage (DocPtr->hWndTB, TB_ENABLEBUTTON, IDM_FIND, mode);
  SendMessage (DocPtr->hWndTB, TB_ENABLEBUTTON, IDM_FIND_NEXT_SAME, mode);
  SendMessage (DocPtr->hWndTB, TB_ENABLEBUTTON, IDM_POST, mode);
}

/*---  FUNCTION: About ---------------------------------------------------
 *
 *  Process messages for "About" dialog box
 */

BOOL WINAPI
About (HWND hDlg, unsigned message, WORD wParam, LONG lParam)
{
  switch (message) {
  case WM_INITDIALOG:
    SetDlgItemText (hDlg, IDD_VERSION_NUMBER, (LPSTR)WINVN_VERSION);
    return (TRUE);

  case WM_COMMAND:
    if (wParam == IDOK) {
      EndDialog (hDlg, TRUE);
      return (TRUE);
    }
    break;
  }
  return (FALSE);
}

/* ----------------------------------------------------
 * Warn user if colors overlap
 * jsc 9/30/94
 */
BOOL
WarnColors (COLORREF c1, COLORREF c2, char *warnMsg)
{
  if (c1 == c2) {
    sprintf (str, "This color is the same as the %s color.\n"
                  "Some displays may become unreadable.", warnMsg);
    MessageBox (NetDoc.hDocWnd, str, "Duplicate Color", MB_OK | MB_ICONINFORMATION);
    return TRUE;
  }
  return FALSE;
}

BOOL
SelectGroup (TypDoc far * Doc, TypGroup far * group, BOOL value)
{
  if (group->Selected != value) {
    group->Selected = value;
    if (value)
      (Doc->SelectedLines)++;
    else
      (Doc->SelectedLines)--;
  }
  return value;
}

/* --- function CrackGroupLine --------------------------------------

 *  Given a line from a .newsrc file, create a text line containing
 *  a structure of type TypGroup containing the same information.
 *  The line is zero-terminated upon input.
 *
 *    Entry    buf      points to a zero-terminated line from .newsrc.
 *             lineptr  points to a place to put the converted textline.
 *
 *    Exit     The line pointed to by "lineptr" now is a TypLine textline
 *             containing a structure of type TypGroup, containing the
 *             information in the input line from .newsrc.
 *
 *  Returns TRUE iff group was subscribed to.
 */
BOOL
CrackGroupLine (char *buf, TypLine *lineptr, void*pEndLine)
{
  char *grname = GetGroupName(lineptr);
  TypGroup *group = GetGroup(lineptr);
  TypRange *RangePtr;
  BOOL MoreNums;
  BOOL bMoreRanges;
  unsigned int MyLength;

  group->Subscribed = FALSE;
  group->Selected = FALSE;
  group->Determined = FALSE;
/* group->Threaded skipped */
  group->NameLen = 0;
  group->SubjDoc = (TypDoc *) NULL;
  group->ServerEstNum = 0;
  group->ServerFirst = 0;
  group->ServerLast = 0;
  group->HighestPrevSeen = 0;
  group->header_handle = (HANDLE) NULL;
/* group->thread_handle skipped */
/* group->total_headers skipped */
  group->NumUnread = 0;
  group->nRanges = 0;
  group->nSpareRanges = 0;

  /* Copy group name to output line. */
  while (*buf && *buf != ':' && *buf != '!') {
    *(grname++) = *(buf++);
    (group->NameLen)++;
  }
  *(grname++) = '\0';

  if (!(*buf)) {
    /* Ran off end of line without seeing ':' or '!'.  Error. */
//    DEBUG_BREAK; // new group on server
  }
  else {
    if (*buf == ':') {
      group->Subscribed = TRUE;
	  group->nSpareRanges = SpareRanges;
    }
    buf++;
  }

  /* if we're allowed to mark crossposted articles, create some room
     in the Group Data structure for the Range Array to grow.  This
     will keep memory from getting too fragmented while reading articles
     and updating the ranges of other articles on the fly   (JD 5/12/95)  */
  if ((TrackSubscribedCrossposts && (group->Subscribed == TRUE)) ||
      (TrackUnsubscribedCrossposts && (group->Subscribed = FALSE))) {
    group->nSpareRanges = SpareRanges;
  }  

  /* Look for the highest article number previously available on the server,
   * in an entry of form "s" followed by a number. */
  while (*buf && *buf == ' ' && *buf != '\n')
    buf++;
  if (*buf == 's') {
    buf++;
    GetNum (&buf, &(group->HighestPrevSeen));
  }

  /* Convert the article number ranges to the internal numeric
   * form we use in WinVN.*/
  RangePtr = GetRangePtr(group);
  RangePtr->Last = RangePtr->First = 0;
  bMoreRanges = TRUE;

  /* allow lines like this, with no range data: */
  /* news.group:  */
  MoreNums = ((*buf) == '\n' ? FALSE : TRUE);
  while (MoreNums) {
    while (*buf && (*buf == ' ' || *buf == ','))
      buf++;
    if(RangePtr > (TypRange *)(char*)pEndLine - sizeof(TypRange))
    {
      bMoreRanges = FALSE;
    }
    if (GetNum (&buf, &(RangePtr->First))) {
      /* We have the first number in a range.                     */
      if(bMoreRanges)
        (group->nRanges)++;
      RangePtr->Last = RangePtr->First;
      if (*buf == '-') {
        buf++;
        if (GetNum (&buf, &(RangePtr->Last))) {
          if(bMoreRanges)
            RangePtr++;
          /* at this point, we are positioned just after a range */
        }
        else {
          RangePtr->Last = RangePtr->First;
          MoreNums = FALSE;
        }
      }
      else if (*buf == ',') {
        /* We have a single number "n"; interpret as the range "n-n".
         */
        if(bMoreRanges)
          RangePtr++;
      }
      else {
        /* That must have been the last number.*/
        MoreNums = FALSE;
      }
    }
    else {
      MoreNums = FALSE;
    }
  }
  if (group->nRanges == 0) {
    (group->nRanges)++;
	RangePtr->Last = RangePtr->First = 1;
	 }

  MyLength = CalcGroupLen(group);
  lineptr->length = MyLength;
  lineptr->LineID = NextLineID++;
  GroupLenPtr(lineptr) = MyLength;

  return (group->Subscribed);
}

/*-- function CursorToTextLine ----------------------------------------
 *
 *   Routine to locate a text line in a document, based on the
 *   cursor position.  Used to figure out which line is being selected
 *   when a user clicks a mouse button.
 *
 *   Entry    X, Y    are the position of the cursor.
 *            DocPtr  points to the current document.
 *
 *   Exit     *LinePtr points to the current line, if one was found.
 *            *BlockPtr points to the current block, if found.
 *            Function returns TRUE iff a line was found that corresponds
 *              to the cursor position.
 */
BOOL
CursorToTextLine (int X, int Y, TypDoc *DocPtr, TypBlock far **BlockPtr, TypLine far **LinePtr)
{
  int found;
  int SelLine;

  if (Y < TopSpace || (unsigned) Y > TopSpace + DocPtr->ScYLines * LineHeight ||
      X < SideSpace) {
    /* Cursor is in no-man's-land at edge of window.               */
    found = FALSE;
  }
  else {
    found = TRUE;
    SelLine = (Y - TopSpace) / LineHeight;
    if ((unsigned int) SelLine >= DocPtr->ScYLines)     /* double-check */
      return (FALSE);

    LockLine (DocPtr->hCurTopScBlock, DocPtr->TopScOffset, DocPtr->TopScLineID,
              BlockPtr, LinePtr);
    AdvanceToActive (BlockPtr, LinePtr);

    for (found = TRUE, il = 0; il < SelLine;) {
      if (!NextLine (BlockPtr, LinePtr)) {
        found = FALSE;          /* ran off end of document */
        break;
      }
      else if ((*LinePtr)->active) {
        il++;
      }
    }
  }
  return (found);
}

extern char * GetFileExtension (char *ext, char *fileName);

/*-- function ReadNewsrc ----------------------------------------------
 *
 *    Reads NEWSRC into the Net document.
 *    This routine opens NEWSRC, reads & parses the lines into the NetDoc
 *    document, and closes the file.  One call does it all.
 *
 *    Entry    The NetDoc document is assumed to be initialized.
 *
 *    Exit     Returns TRUE if all went well, else zero.
 */
int
ReadNewsrc ()
{
  TypBlock far *BlockPtr;
  TypLine *LinePtr, far * GroupPtr;
  HANDLE hBlock;
  HFILE hRetCode;
  unsigned int Offset;
  TypLineID MyLineID;
  char mybuf[TEMPBUFSIZE];
  TypMRRFile *MRRFile;
  int returned;
  int iRet = 0;

  LockLine (NetDoc.hCurAddBlock, NetDoc.AddOffset, NetDoc.AddLineID, &BlockPtr, &GroupPtr);
  if ((LinePtr = (TypLine *) GlobalAllocPtr (GMEM_MOVEABLE, TEMPBUFSIZE)) == NULL) {
    return 0;
  }
 
  hRetCode = MRROpenFile (szNewsSrc, OF_READ, &MRRFile);
  if ((int) hRetCode <= 0) {
    iRet = 0;
  }
  else {
    /* Loop to read lines, convert them to internal format, and
     * insert them into the NetDoc document.
     */

    LinesInRC = 0;
    while ((returned = MRRReadLine (MRRFile, mybuf, TEMPBUFSIZE)) > (-1)) {
      mybuf[returned] = '\0';
      if(returned >= TEMPBUFSIZE)
      {
        DEBUG_BREAK;
      }
      mybuf[TEMPBUFSIZE-1] = '\0';
      if (CrackGroupLine (mybuf, LinePtr, (char far*)LinePtr + (TEMPBUFSIZE-1))) {
        NetDoc.CountedLines++;
      }
       AddLine (LinePtr, &BlockPtr, &GroupPtr);
      LinesInRC++;
    }
    MRRCloseFile (MRRFile);

  /* Change the title of the Net document.  I suppose that,
   * strictly speaking, this oughtn't be done in this routine.
   */
  SetNetDocTitle ();
  UnlockLine (BlockPtr, GroupPtr, &hBlock, &Offset, &MyLineID);

  NetDoc.hCurTopScBlock = NetDoc.hFirstBlock;
  NetDoc.TopScOffset = sizeof (TypBlock);
  NetDoc.TopScLineID = 0L;
  NetDoc.LongestLine = 0;
  /* Mark lines active or inactive according to ShowUnsubscribed. */
  SetGroupActiveLines ();
  /* we'll check this during WriteNewsrc(). SMR 930224 */
  started_with_no_dolist = !DoList;
  iRet = 1;
  }
    GlobalFreePtr (LinePtr); 
  NewsrcDirty = FALSE;

  return iRet;
}

char *ltoa ();

#define WNEWSRC_INIT       5
#define WNEWSRC_OPEN       9
#define WNEWSRC_BEGINSAVE 10
#define WNEWSRC_ENDSAVE   90
#define WNEWSRC_CLOSE     92
#define WNEWSRC_WAIT      94
#define WNEWSRC_RENAME    96
#define WNEWSRC_FINISH    98

/*--- function WriteNewsrc ---------------------------------------------
 *
 *  Write out a NEWSRC file, based on the information in the
 *  NetDoc document.  Use the standard Unix "rn" format for .newsrc.
 *
 *    Entry    no parameters
 *             NetDoc   has the group information.
 *
 *    Exit     The NEWSRC file has been written.
 */
void
WriteNewsrc()
{
  TypBlock far *BlockPtr;
  TypLine far *LinePtr;
  HANDLE hBlock;
  unsigned int Offset;
  TypLineID MyLineID;
  char far *fromptr;
  char *toptr;
  char *NewsLine;
  HFILE hRetCode;
  TypMRRFile *MRRFile;
  int nranges,percent,opercent,errcode;
  TypGroup far *Group;
  TypRange far *RangePtr;
  BOOL firstrange;
  char newsrc_temp[MAXFILENAME];
  char newsrc_extension[MAXFILENAME];
  char szString[MAXFILENAME+20];
  int extension_length, filename_length;
  uint32 loopctr = 0;
  uint32 loopend = max(1,LinesInRC);

  /* conditions in which we do not want to write it out */
  if (Initializing == INIT_READING_NEWSRC ||
      Initializing == INIT_SCANNING_NETDOC ||
      Initializing == INIT_GETTING_LIST) {
	MessageBox (NetDoc.hDocWnd, "Newsrc was busy initializing.  Newsrc not saved.", "Save Newsrc Failure", MB_OK | MB_ICONSTOP);  
    return;
  }
  
  sprintf(szString,"Saving NewsRC File %s",szNewsSrc); 
  SetStatbarText(NetDoc.hWndFrame,szString,&NetDoc, TRUE, TRUE);
  SetStatbarPercent(NetDoc.hWndFrame,(percent = 0),&NetDoc,TRUE);
  LockLine (NetDoc.hFirstBlock, sizeof (TypBlock), 0L, &BlockPtr, &LinePtr);

  if ((NewsLine = (char *) GlobalAllocPtr (GMEM_MOVEABLE, BLOCK_SIZE)) == NULL) {
    MessageBox (NetDoc.hDocWnd, "Memory allocation failure.  Newsrc not saved.", "Save Newsrc Failure", MB_OK | MB_ICONSTOP);
    return;
  }

  strcpy (newsrc_temp, szNewsSrc);
  GetFileExtension (newsrc_extension, szNewsSrc);
  extension_length = strlen (newsrc_extension);
  filename_length = strlen (newsrc_temp);

  if ((extension_length != 0) || (newsrc_temp[filename_length] == '.')) {
    strcpy (newsrc_temp + (filename_length - (extension_length+1)), ".tmp");
   } 
  else {
    strcpy (newsrc_temp + filename_length, ".tmp");
  }

  sprintf(szString,"Opening NewsRC File %s",szNewsSrc); 
  SetStatbarText(NetDoc.hWndFrame,szString,&NetDoc, TRUE, TRUE);  
  hRetCode = MRROpenFile (newsrc_temp, OF_WRITE, &MRRFile);
  sprintf(szString,"Writing NewsRC File %s",szNewsSrc); 
  SetStatbarText(NetDoc.hWndFrame,szString,&NetDoc, TRUE, TRUE);
  opercent = percent;
  
  if ((int) hRetCode <= 0) {
    MessageBox (NetDoc.hDocWnd, "Could not write to newsrc file", "Save Newsrc Failure", MB_OK | MB_ICONSTOP);
  }
  else {
    do {
     percent = (int) (((float) loopctr++ / (float) loopend) * 100);
     if (percent > opercent + 5) {  
         SetStatbarPercent(NetDoc.hWndFrame,percent,&NetDoc,TRUE);
         opercent = percent;
     } 
     toptr = NewsLine;
     Group = GetGroup(LinePtr);

      /* Remove groups only if we performed a LIST command and */
      /* the server did not have them.  Otherwise if !did_list, the */
      /* newsrc will be practically empty! */

      if ((Group->ServerFirst || Group->ServerEstNum) || !did_list) {
        /* Copy group name                                          */
        fromptr = GetGroupName(LinePtr);
        while (*(toptr++) = *(fromptr++));
        toptr--;

        /* Affix : or ! depending upon whether subscribed.          */
        *(toptr++) = (char) (Group->Subscribed ? ':' : '!');
        *(toptr++) = ' ';

        /* If we know the highest article number on the server,
         * output it preceded by an "s".*/
        if (Group->ServerLast || Group->HighestPrevSeen) {
          *(toptr++) = 's';
          if (Group->ServerLast)
            ltoa ((unsigned long) Group->ServerLast, toptr, 10);
          else
            ltoa ((unsigned long) Group->HighestPrevSeen, toptr, 10);
          while (*toptr)
            toptr++;
          *(toptr++) = ' ';
        }

        /* Affix ranges of articles read.*/
        firstrange = TRUE;
        nranges = Group->nRanges;
        RangePtr = GetRangePtr(Group);

        while ((nranges--) > 0) {
          /* Write out ',' if not first range of articles. */
          if (!firstrange) {
            *(toptr++) = ',';
          }
          else {
            firstrange = FALSE;
          }
          /* Write out first article in a range.*/
          ltoa ((unsigned long) RangePtr->First, toptr, 10);
          while (*toptr)
            toptr++;

          /* If the range is of form "n-n", just put out "n"  */
          if (RangePtr->Last > RangePtr->First) {
            /* Put out the hyphen in middle of range. */
            *(toptr++) = '-';
            /* Put out the last article in a range. */
            ltoa ((unsigned long) RangePtr->Last, toptr, 10);
            while (*toptr)
              toptr++;
          }
          RangePtr++;
        }
        MRRWriteLine (MRRFile, NewsLine, toptr - NewsLine);
      }
    }
    while (NextLine (&BlockPtr, &LinePtr));
    UnlockLine (BlockPtr, LinePtr, &hBlock, &Offset, &MyLineID); 
    sprintf(szString,"Closing NewsRC File %s",szNewsSrc); 
    SetStatbarText(NetDoc.hWndFrame,szString,&NetDoc, TRUE, TRUE);
    SetStatbarPercent(NetDoc.hWndFrame,100,&NetDoc,TRUE);
    
    MRRCloseFile (MRRFile);
    unlink (szNewsSrc);
    SetStatbarPercent(NetDoc.hWndFrame,0,&NetDoc,TRUE);
    SetStatbarText(NetDoc.hWndFrame,"",&NetDoc, TRUE, TRUE);
    
  //  errcode = rename (newsrc_temp,szNewsSrc);
  	errcode = EXDEV;  /* debug */
    if (errcode != 0) {
      char error_string[256];

      switch (errcode) {
      case EACCES:
      	sprintf (error_string, "Could not rename temporary newsrc file '%s'.\n File specified by '%s' still exists after being deleted or could not be created", newsrc_temp,szNewsSrc);
      	MessageBox (NetDoc.hDocWnd, error_string, "Save Newsrc Failure", MB_OK | MB_ICONSTOP);
      	break;
      case ENOENT:
      	sprintf (error_string, "Could not rename temporary newsrc file '%s'. \n File Not Found", newsrc_temp);
      	MessageBox (NetDoc.hDocWnd, error_string, "Save Newsrc Failure", MB_OK | MB_ICONSTOP);
        break;
      case EXDEV:
        /* Failed because Rename doesn't work across some networks or file systems
           try a slower block by block copy instead   JD 4/16/96  */
        sprintf(szString,"Making temporary NewsRC File %s permanent",szNewsSrc); 
        SetStatbarText(NetDoc.hWndFrame,szString,&NetDoc, TRUE, TRUE);
        errcode = CopyFile(newsrc_temp,szNewsSrc,FALSE);
        if (errcode = 0) {
        	sprintf (error_string, "Could not rename temporary newsrc file '%s'.\n Error Code = '%n'", newsrc_temp,errcode);
      		MessageBox (NetDoc.hDocWnd, error_string, "Save Newsrc Failure", MB_OK | MB_ICONSTOP);
      		}
      	else
      		unlink (newsrc_temp);
      		SetStatbarText(NetDoc.hWndFrame,"",&NetDoc, TRUE, TRUE);
      	break;
      }
    }
  }
  
  NewsrcDirty = FALSE;
  GlobalFreePtr (NewsLine);
}

/*--- function SetNetDocTitle -------------------------------------------
 *
 */
void
SetNetDocTitle ()
{
  char mybuf[120];

  sprintf (mybuf, "WinVN:  %u groups; %u subscribed", NetDoc.TotalLines,
           NetDoc.CountedLines);
  SetWindowText (NetDoc.hWndFrame, mybuf);

}

/*--- function SetLineFlag --------------------------------------------
 *
 *  Set some flag in a line in a document.
 *
 *  Entry   Doc      points to the document.
 *          LinePtr  points to th line.
 *
 *  Exit    lFlag    says what to do.
 */
void
SetLineFlag (TypDoc * Doc, TypBlock far ** BlockPtr, TypLine far ** LinePtr, int wFlag, int wValue)
{
  switch (wFlag) {
  case LINE_FLAG_SET:
    SelectGroup (Doc, GetGroup(*LinePtr), wValue);
    break;
  }
}

void
SetExLineFlag (TypDoc * Doc, TypBlock far ** BlockPtr, TypLine far ** LinePtr, int wFlag, int wValue)
{
  static BOOL extending = FALSE;

  switch (wFlag) {
  case LINE_FLAG_SET:
    if ((*LinePtr)->LineID == Doc->ActiveLineID ||
        (*LinePtr)->LineID == Doc->AnchorLineID) {
      if (extending == TRUE) {
        SelectGroup (Doc, GetGroup(*LinePtr), TRUE);
        extending = FALSE;
      }
      else {
        SelectGroup (Doc, GetGroup(*LinePtr), TRUE);
        if (Doc->ActiveLineID == Doc->AnchorLineID)
          extending = FALSE;
        else
          extending = TRUE;
      }
    }
    else
      SelectGroup (Doc, GetGroup(*LinePtr), extending);

    break;
  }
}

/*--- function GroupAction --------------------------------------------
 *
 *  Perform some action on a group that is specified by a pointer
 *  to a line in the Net document.
 *  Typically called for each line in the Net document by
 *  ForAllLines.
 *
 *  Entry   Doc      points to NetDoc
 *          LinePtr  points to a line in that document.
 *          lFlag    indicates what to do with that line.
 */
void
GroupAction (TypDoc * Doc, TypBlock far ** BlockPtr, TypLine far ** LinePtr, int wFlag, int wValue)
{
  static char mybuf[MAXINTERNALLINE];

  switch (wFlag) {
  case GROUP_ACTION_SUBSCRIBE:
  case GROUP_ACTION_UNSUBSCRIBE:	 
     if ((GetGroup(*LinePtr))->Selected) {
	  (GetGroup(*LinePtr))->Subscribed = wValue;
      AddGroupToTable ((char far *) *LinePtr);
      DeleteLine (BlockPtr, LinePtr);
    }
    break;

  case GROUP_ACTION_SELECTED:	 
     if ((GetGroup(*LinePtr))->Selected) {
      AddGroupToTable ((char far *) *LinePtr);
  //    DeleteLine (BlockPtr, LinePtr);
    }
    break;

  case GROUP_ACTION_CHECK_ACTIVE:
    if ((GetGroup(*LinePtr))->Subscribed || ShowUnsubscribed) {
      (*LinePtr)->active = TRUE;
      (*BlockPtr)->NumActiveLines++;
      NetDoc.ActiveLines++;
      NetDoc.LongestLine = max (NetDoc.LongestLine, 
                               (unsigned)(GetGroup(*LinePtr))->NameLen + GROUP_NAME_OFFSET);
      if ((GetGroup(*LinePtr))->Subscribed)
        NetDoc.CountedLines++;
    }
    else {
      (*LinePtr)->active = FALSE;
    }
    break;

  case GROUP_ACTION_GET_GROUPNAMES:
    if ((GetGroup(*LinePtr))->Selected) {
      ExtractTextLine (&NetDoc, *LinePtr, mybuf, sizeof(mybuf));
      if (strlen(groupbuf) + strlen(mybuf) + 2 < sizeof(groupbuf)) {
        if (*groupbuf)
          strcat(groupbuf, ",");
        strcat(groupbuf, mybuf);
      }
    }
    break;
  }
}

/****************************************************************************

   FUNCTION:   MakeHelpPathName

   PURPOSE:    HelpEx assumes that the .HLP help file is in the same
          directory as the HelpEx executable.This function derives
          the full path name of the help file from the path of the
          executable.

   Taken from HELPEX.C, from the MS Windows SDK.

****************************************************************************/

void
MakeHelpPathName (char *szFileName, int maxchars)
{
  char *pcFileName;
  int nFileNameLen;

  nFileNameLen = GetModuleFileName (hInst, szFileName, maxchars);
  pcFileName = szFileName + nFileNameLen;

  while (pcFileName > szFileName) {
    if (*pcFileName == '\\' || *pcFileName == ':') {
      *(++pcFileName) = '\0';
      break;
    }
    nFileNameLen--;
    pcFileName--;
  }

  if ((nFileNameLen + 13) < maxchars) {
    lstrcat (szFileName, LFN_HELP);
  }

  else {
    lstrcat (szFileName, "?");
  }
  return;
}

long
cursor_to_char_number (long X, long Y, TypDoc *DocPtr, TypBlock far **BlockPtr, TypLine far **LinePtr)
{
  int SelLine;
  long charnum = -1;
  SIZE extent;

  char far *textptr;
  int textlen;
  HDC display_context;
  int iTopSpace, iSideSpace, iLineHeight, iCharWidth;
  HANDLE hOldFont;

  if (DocPtr->DocType == DOCTYPE_ARTICLE) {
    iTopSpace = ArtTopSpace;
    iSideSpace = ArtSideSpace;
    iLineHeight = ArtLineHeight;
    iCharWidth = ArtCharWidth;
  }
  else {
    iTopSpace = TopSpace;
    iSideSpace = SideSpace;
    iLineHeight = LineHeight;
    iCharWidth = CharWidth;
  }

  if (Y < iTopSpace || (unsigned) Y > iTopSpace + DocPtr->ScYLines * iLineHeight ||
      X < iSideSpace) {
    return (-1);
  }
  else {
    SelLine = (int) (Y - iTopSpace) / iLineHeight;

    LockLine (DocPtr->hCurTopScBlock, DocPtr->TopScOffset, DocPtr->TopScLineID,
              BlockPtr, LinePtr);

    for (il = 0; il < SelLine; il++) {
      if (!NextLine (BlockPtr, LinePtr)) {
        return (-1);
        break;
      }
    }
  }

  /* find the right character on the text line */
  textlen = GetTextLen(*LinePtr);
  
  if (textlen) {
    textptr = GetTextPtr(*LinePtr);
    display_context = GetDC (DocPtr->hDocWnd);

    if (isLineQuotation (textptr)) {    /* prepare to print a quotation Line */
      hOldFont = SelectObject (display_context, hFontArtQuote);
    }
    else {                      /* prepare to print a normal line */
      hOldFont = SelectObject (display_context, hFontArtNormal);
    }

    for (charnum = 1; charnum < textlen; charnum++) {
      GetTextExtentPoint (display_context, (LPSTR) textptr, (int) charnum, &extent);
      if (extent.cx > (X + (int) DocPtr->ScXOffset * (iCharWidth + 1) - iSideSpace))
        break;
    }
    SelectObject (display_context, hOldFont);
    ReleaseDC (DocPtr->hDocWnd, display_context);
    return (charnum - 1);
  }
  return(0);
}

/*--- function SetGroupActiveLines --------------------------------------
 *
 *  Go through all the lines in the Net document, marking each
 *  as active or inactive according to whether the corresponding
 *  group is subscribed and whether we are displaying unsubscribed
 *  groups.
 *
 *  Entry:  NetDoc and ShowUnsubscribed are set properly.
 *
 *  Exit:   Each of the lines in NetDoc has had its "active" field
 *          set properly.
 */
void
SetGroupActiveLines ()
{
  NetDoc.ActiveLines = 0;
  NetDoc.CountedLines = 0;
  ForAllBlocks (&NetDoc, SetForBlock, BLOCK_ACTION_SET_ACTIVE, 0);
  ForAllLines (&NetDoc, GroupAction, GROUP_ACTION_CHECK_ACTIVE, 0);
}


/* stuff for showing version numbers of the files */

#include "version.h"
#include "version.c"

/*-- function VerListDlgProc ---------------------------------------
 *
 *  Dialog function to handle selection of Version List
 *                                  JD 8/3/94
 */

LRESULT CALLBACK
VerListDlgProc (HWND hDlg, UINT iMessage, WPARAM wParam, LPARAM lParam)
{
  int j;
  char far *cptr;
  char szVersion[64];

  switch (iMessage) {
  case WM_INITDIALOG:
    sprintf (szVersion, "Version %s", (LPSTR) WINVN_VERSION);
    SetDlgItemText ((HWND) hDlg, IDD_VERSION_NUMBER, (LPSTR) szVersion);
    hVerDlgList = GetDlgItem (hDlg, IDD_VERSION_LISTBOX);
    SendMessage (hVerDlgList, WM_SETREDRAW, FALSE, 0L);

    cptr = 0;
    for (j = 0; j < (sizeof (version_string) / sizeof version_string[0]); j++) {
      cptr = version_string[j];
      SendMessage (hVerDlgList, LB_ADDSTRING, 0, (LONG) cptr);
    }
    SendMessage (hVerDlgList, WM_SETREDRAW, TRUE, 0L);
    return TRUE;
    break;

  case WM_COMMAND:
    switch (wParam) {
    case IDOK:
      EndDialog (hDlg, TRUE);
      break;

    case IDCANCEL:
      EndDialog (hDlg, FALSE);
      break;

    default:
      return FALSE;
    }
    break;

  case WM_DESTROY:
    break;

  default:
    return FALSE;
    break;
  }
  return TRUE;
}

/*  Display Dialog Box to show version info   JD 8/4/94 */
void
show_version_strings (HWND hWnd)
{
  lpfnWinVnVersionListDlg = (DLGPROC) MakeProcInstance ((FARPROC) VerListDlgProc, (HINSTANCE) hInst);
  DialogBox (hInst, "WINVNVERSIONLIST", hWnd, lpfnWinVnVersionListDlg);
  FreeProcInstance ((FARPROC) lpfnWinVnVersionListDlg);

}


long WINAPI
WinVnConfFrameWndProc (HWND hWnd, unsigned message, WPARAM wParam, LPARAM lParam)
{
  DLGPROC lpProcAbout;
  RECT myRect;                  /* selection rectangle  */
  static HANDLE ToolBarLib;
  char mybuf[MAXINTERNALLINE];
  int found;
  PAINTSTRUCT ps;               /* paint structure          */
  HDC hDC;                      /* handle to display context */
  int i, j, numComposeWnds;
  TBBUTTON tbButton[MAXTBBUTTONS];
  HMENU hMenu, hSubMenu;
  BOOL continueFind;
  TypLine far *LinePtr;
  TypBlock far *BlockPtr;

  if (StatusBarProc (hWnd, message, wParam, lParam, &NetDoc))
    return 0;

  switch (message) {
#ifdef USE_3D_CONTROLS
  case WM_SYSCOLORCHANGE:
    Ctl3dColorChange ();
    break;
#endif

  case WM_SETFOCUS:
   SetCapsLockText(hWnd);
   SetNumLockText(hWnd);
   return (DefWindowProc (hWnd, message, wParam, lParam));
    break;

  case WM_SYSCOMMAND:
    switch (wParam) {
        case ID_ABOUT:
            lpProcAbout = (DLGPROC) MakeProcInstance ((FARPROC) About, hInst);
            DialogBox (hInst, "AboutBox", hWnd, lpProcAbout);
            FreeProcInstance ((FARPROC) lpProcAbout);
            break;

        case IDM_RESTORE_ALL:
            RestoreWindows();
            break;

        default:
            return (DefWindowProc (hWnd, message, wParam, lParam));
    }
    break;

  case WM_CREATE:
    GetClientRect (hWnd, &myRect);

/* add common controls for toolbar (jlg) */
    i = j = 0;
    tbButton[j].iBitmap = 0;    /* connect */

    tbButton[j].idCommand = IDB_TOGGLE_CONNECT;
    tbButton[j].fsState = TBSTATE_ENABLED;
    tbButton[j].fsStyle = TBSTYLE_BUTTON;
    tbButton[++j].iBitmap = ++i;    /* reset */

    tbButton[j].idCommand = IDM_RESET;
    tbButton[j].fsState = TBSTATE_ENABLED;
    tbButton[j].fsStyle = TBSTYLE_BUTTON;

    tbButton[++j].iBitmap = 8;  /* separator */

    tbButton[j].fsState = TBSTATE_ENABLED;
    tbButton[j].fsStyle = TBSTYLE_SEP;

    tbButton[++j].iBitmap = ++i;    /* find */

    tbButton[j].idCommand = IDM_FIND;
    tbButton[j].fsState = TBSTATE_ENABLED;
    tbButton[j].fsStyle = TBSTYLE_BUTTON;
    tbButton[++j].iBitmap = ++i;    /* find next */

    tbButton[j].idCommand = IDM_FIND_NEXT_SAME;
    tbButton[j].fsState = TBSTATE_ENABLED;
    tbButton[j].fsStyle = TBSTYLE_BUTTON;

    tbButton[++j].iBitmap = 8;  /* separator */

    tbButton[j].fsState = TBSTATE_ENABLED;
    tbButton[j].fsStyle = TBSTYLE_SEP;

    tbButton[++j].iBitmap = ++i;    /* New Article */

    tbButton[j].idCommand = IDM_POST;
    tbButton[j].fsState = TBSTATE_ENABLED;
    tbButton[j].fsStyle = TBSTYLE_BUTTON;
    tbButton[++j].iBitmap = ++i;    /* New Mail */

    tbButton[j].idCommand = IDM_MAIL;
    tbButton[j].fsState = TBSTATE_ENABLED;
    tbButton[j].fsStyle = TBSTYLE_BUTTON;

    tbButton[++j].iBitmap = 8;  /* separator */

    tbButton[j].fsState = TBSTATE_ENABLED;
    tbButton[j].fsStyle = TBSTYLE_SEP;

    tbButton[++j].iBitmap = ++i;    /* Save Configuration */

    tbButton[j].idCommand = IDM_SAVE_CONFIG;
    tbButton[j].fsState = TBSTATE_ENABLED;
    tbButton[j].fsStyle = TBSTYLE_BUTTON;
    tbButton[++j].iBitmap = ++i;    /* Toggle View Unsubscribed */

    tbButton[j].idCommand = IDB_TOGGLE_VIEW_UNSUB;
    if (ShowUnsubscribed == TRUE)
      tbButton[j].fsState = TBSTATE_ENABLED | TBSTATE_CHECKED;
    else
      tbButton[j].fsState = TBSTATE_ENABLED;
    tbButton[j].fsStyle = TBSTYLE_BUTTON;

	tbButton[++j].iBitmap = 8;  /* separator */
    tbButton[j].fsState = TBSTATE_ENABLED;
    tbButton[j].fsStyle = TBSTYLE_SEP;

    tbButton[++j].iBitmap = ++i;    /* Exit */

    tbButton[j].idCommand = IDB_FASTEXIT;
    tbButton[j].fsState = TBSTATE_ENABLED;
    tbButton[j].fsStyle = TBSTYLE_BUTTON;

    tbButton[++j].iBitmap = 8;  /* separator */

    tbButton[j].fsState = TBSTATE_ENABLED;
    tbButton[j].fsStyle = TBSTYLE_SEP;

    tbButton[++j].iBitmap = ++i;    /* About */

  //  tbButton[j].idCommand = ID_ABOUT;
  //  Help is more useful than About    (JD 5/16/95)

    tbButton[j].idCommand = IDM_HELP; 	 
    tbButton[j].fsState = TBSTATE_ENABLED;
    tbButton[j].fsStyle = TBSTYLE_BUTTON;

    NetDoc.hWndTB = CreateToolbar (hWnd,
                                   WS_BORDER | WS_VISIBLE,
                                   (WORD) GetMenu (hWnd),
                                   i + 1,
                                   hInst,
                                   IDB_TOOLBAR,
                                   tbButton,
                                   j + 1);

    assert (NetDoc.hWndTB != NULL);

    NetDoc.hDocWnd = CreateWindow ("WinVn",
                                   "WinVN -- Usenet News Reader",
                                   WS_CHILD | WS_VSCROLL | WS_HSCROLL,
                                   0,                                          /* Initial X position */
                                   TOOLBARHEIGHT,                              /* Initial Y position */
                                   myRect.right - myRect.left,                 /* Initial X width */
    (myRect.bottom - myRect.top) - TOOLBARHEIGHT - StatbarPntData.dyStatbar,   /* Initial Y height */
                                   hWnd,
                                   NULL,
                                   hInst,
                                   NULL);

    assert (NetDoc.hDocWnd != NULL);

    SetHandleBkBrush (NetDoc.hDocWnd, hListBackgroundBrush);

    /* Add the "About" and "Restore All" options to the system menu. */
    hMenu = GetSystemMenu (hWnd, FALSE);
    AppendMenu (hMenu, MF_SEPARATOR, 0, (LPCSTR) NULL);
    AppendMenu (hMenu, MF_STRING, IDM_RESTORE_ALL, (LPCSTR) "Restore &All");
    AppendMenu (hMenu, MF_STRING, ID_ABOUT, (LPCSTR) "A&bout WinVn...");

    /* Get the global Action items */
    g_action.ReadActions(NULL);

#ifdef USE_SPLASH
    /* Add the Splash option */
    hMenu = GetMenu(hWnd);
    hSubMenu = GetSubMenu (hMenu, 3);   /* Config menu */
    InsertMenu (hMenu, IDM_SAVE_CONFIG, MF_STRING|MF_BYCOMMAND, 
                    IDM_USE_SPLASH, (LPCSTR) "Show Splash Screen");
    InsertMenu (hMenu, IDM_SAVE_CONFIG, MF_SEPARATOR|MF_BYCOMMAND, 0, (LPCSTR) NULL);
#endif
    break;

  case WM_MYINITMENU:
  case WM_INITMENU:
    hMenu = GetMenu (hWnd);

    hSubMenu = GetSubMenu (hMenu, 0);   /* network menu */
    
    /* don't allow disconnect until connection is established */
    i = (Initializing == INIT_ESTAB_CONN || Initializing == INIT_NOT_CONNECTED);
    EnableMenuItem (hSubMenu, IDM_RECONNECT,  i ? DISABLE_MENU : ENABLE_MENU);
    EnableMenuItem (hSubMenu, IDM_DISCONNECT, i ? DISABLE_MENU : ENABLE_MENU);

    i = (Initializing != INIT_ESTAB_CONN);
    EnableMenuItem (hSubMenu, IDM_EXIT,  i ? ENABLE_MENU : DISABLE_MENU);
    EnableMenuItem (hSubMenu, IDM_QUIT,  i ? ENABLE_MENU : DISABLE_MENU);
    SendMessage (NetDoc.hWndTB, TB_ENABLEBUTTON, IDB_FASTEXIT, i ? TRUE : FALSE);

    hSubMenu = GetSubMenu (hMenu, 1);   /* group menu */
    EnableMenuItem (hSubMenu, IDM_SUBSCRIBE, NetDoc.SelectedLines ? ENABLE_MENU : DISABLE_MENU);
    EnableMenuItem (hSubMenu, IDM_UNSUBSCRIBE, NetDoc.SelectedLines ? ENABLE_MENU : DISABLE_MENU);
   	EnableMenuItem (hSubMenu, IDM_SORT_SELECTED, NetDoc.SelectedLines ? ENABLE_MENU : DISABLE_MENU);
    EnableMenuItem (hSubMenu, IDM_GROUP_TOP, NetDoc.SelectedLines ? ENABLE_MENU : DISABLE_MENU);
    EnableMenuItem (hSubMenu, IDM_UNSEL_ALL, NetDoc.SelectedLines ? ENABLE_MENU : DISABLE_MENU);

    hSubMenu = GetSubMenu (hMenu, 2);   /* Utilities menu */
    EnableMenuItem (hMenu, IDM_DECODE_FILE,
                    (CodingState == INACTIVE) ? ENABLE_MENU : DISABLE_MENU);
    EnableMenuItem (hMenu, IDM_ENCODE_FILE,
                    (CodingState == INACTIVE) ? ENABLE_MENU : DISABLE_MENU);
    EnableMenuItem (GetSubMenu (hSubMenu, 3), IDM_SEND_ALL_POST,
                    NumPostWnds ? ENABLE_MENU : DISABLE_MENU);
    EnableMenuItem (GetSubMenu (hSubMenu, 3), IDM_SEND_ALL_MAIL,
                    NumMailWnds ? ENABLE_MENU : DISABLE_MENU);

    hSubMenu = GetSubMenu (hMenu, 3);   /* Config menu */
    CheckMenuItem (GetSubMenu (hSubMenu, 15), IDM_TRUE_INVERSE_SELECTIONS,
         MF_BYCOMMAND | (UseInverseSelections ? MF_CHECKED : MF_UNCHECKED));
#ifdef USE_SPLASH
    CheckMenuItem (hSubMenu, IDM_USE_SPLASH,
         MF_BYCOMMAND | (ShowSplashScreen ? MF_CHECKED : MF_UNCHECKED));
#endif

    hSubMenu = GetSubMenu (hMenu, 4);   /* Window menu */
    numComposeWnds = NumPostWnds + NumMailWnds;
    /* close submenu */
    EnableMenuItem (GetSubMenu (hSubMenu, 1), IDM_CLOSE_ALL_ARTICLE,
                    NumArticleWnds ? MF_ENABLED : MF_DISABLED | MF_GRAYED);
    EnableMenuItem (GetSubMenu (hSubMenu, 1), IDM_CLOSE_ALL_GROUP,
                    NumGroupWnds ? MF_ENABLED : MF_DISABLED | MF_GRAYED);
    EnableMenuItem (GetSubMenu (hSubMenu, 1), IDM_CLOSE_ALL_COMPOSE,
                    numComposeWnds ? MF_ENABLED : MF_DISABLED | MF_GRAYED);
    EnableMenuItem (GetSubMenu (hSubMenu, 1), IDM_CLOSE_ALL_STATUS,
                    NumStatusTexts ? MF_ENABLED : MF_DISABLED | MF_GRAYED);
    EnableMenuItem (GetSubMenu (hSubMenu, 1), IDM_CLOSE_ALL,
                    (NumArticleWnds || NumGroupWnds || NumStatusTexts || numComposeWnds) ? MF_ENABLED : MF_GRAYED);
    /* minimize submenu */
    EnableMenuItem (GetSubMenu (hSubMenu, 2), IDM_MINIMIZE_ALL_ARTICLE,
                    NumArticleWnds ? MF_ENABLED : MF_DISABLED | MF_GRAYED);
    EnableMenuItem (GetSubMenu (hSubMenu, 2), IDM_MINIMIZE_ALL_GROUP,
                    NumGroupWnds ? MF_ENABLED : MF_DISABLED | MF_GRAYED);
    EnableMenuItem (GetSubMenu (hSubMenu, 2), IDM_MINIMIZE_ALL_COMPOSE,
                    numComposeWnds ? MF_ENABLED : MF_DISABLED | MF_GRAYED);
    EnableMenuItem (GetSubMenu (hSubMenu, 2), IDM_MINIMIZE_ALL_STATUS,
                    NumStatusTexts ? MF_ENABLED : MF_DISABLED | MF_GRAYED);
    /* can always minimize all - at least the main window! */
    EnableMenuItem (GetSubMenu (hSubMenu, 2), IDM_MINIMIZE_ALL, MF_ENABLED);
    break;

  case WM_SIZE:
    GetClientRect (hWnd, &myRect);
    ShowWindow (NetDoc.hDocWnd, SW_SHOWNORMAL);
    MoveWindow (NetDoc.hDocWnd, 0, TOOLBARHEIGHT,
                myRect.right - myRect.left,
    (myRect.bottom - myRect.top) - TOOLBARHEIGHT - StatbarPntData.dyStatbar,
                TRUE);
    MoveWindow (NetDoc.hWndTB, 0, 0,
                myRect.right - myRect.left,
                TOOLBARHEIGHT,
                TRUE);
    break;

  case WM_CLOSE:
    if (Initializing == INIT_ESTAB_CONN) {
        break;      /* can't quit if in process of connecting (will GPF) */
    }
    if (!ConfirmSaveOnExit) {
      SaveNewsrc = SaveConfig = TRUE;
    }
    else {
      /* WinVnExitDlg sets SaveNewsrc and SaveConfig */
      if (!DialogBox (hInst, "WinVnExit", hWnd, lpfnWinVnExitDlg)) {
	     if (NetDoc.hDocWnd){
            InvalidateRect (NetDoc.hDocWnd, NULL, TRUE);
		 }
        break;
      }
    }
    SetHandleBkBrush (NetDoc.hDocWnd, (HBRUSH) GetStockObject (WHITE_BRUSH));    
    MailClose (hWnd);

    if (SaveConfig) WriteWinvnProfile ();     
    if (SaveNewsrc && NewsrcDirty) WriteNewsrc ();
      
    DestroyWindow (hWnd);
    return (0);
    break;

  case WM_DESTROY:
    if (Initializing == INIT_ESTAB_CONN) {
        break;      /* can't quit if in process of connecting (will GPF) */
    }
    if (Initializing != INIT_NOT_CONNECTED && Initializing != INIT_ESTAB_CONN) {
      PutCommData ("QUIT\r\n", 6);
    }
   
    Disconnect ();
       
    /* Remove resources before exiting program */
    FreeTextBlock (Signature);
    if (MailList)
      FreeTextBlock (MailList);

    DestroyFonts ();
    DestroyBitmaps ();

    if (hListBackgroundBrush)
      DeleteObject (hListBackgroundBrush);
    if (hArticleBackgroundBrush)
      DeleteObject (hArticleBackgroundBrush);
    if (hStatusBackgroundBrush)
      DeleteObject (hStatusBackgroundBrush);
//  if (StatbarPntData.hFontStatbar)
//    DeleteObject (StatbarPntData.hFontStatbar);
    PostQuitMessage (0);
    return (0);
    break;

  case WM_VSCROLL:
  case WM_HSCROLL:
    break;
  case WM_PAINT:
/*  if (Initializing == INIT_READY || Initializing == INIT_DONE) */
/*    SetUserMenus (&NetDoc, ENABLE); */

    /* paint status bar  */
    hDC = BeginPaint (hWnd, &ps);
    PaintStatbar (hWnd, hDC, &NetDoc);
    EndPaint (hWnd, &ps);
    if (GetFocus () == hWnd)
      SetFocus (NetDoc.hDocWnd);
    return (DefWindowProc (hWnd, message, wParam, lParam));
    break;

  case WM_COMMAND:
    switch (LOWORD (wParam)) {
    case IDM_QUIT:
      if (Initializing == INIT_ESTAB_CONN) {
        break;      /* can't quit if in process of connecting (will GPF) */
      }  
      SaveNewsrc = SaveConfig = FALSE;
      SetHandleBkBrush (NetDoc.hDocWnd, (HBRUSH) GetStockObject (WHITE_BRUSH));
      DestroyWindow (hWnd);
      return (0);
      break;
    case IDM_EXIT:
      if (Initializing == INIT_ESTAB_CONN) {
        break;      /* can't quit if in process of connecting (will GPF) */
      }  
      SendMessage (hWnd, WM_CLOSE, 0, 0L);
      break;

    case IDM_CONNECT:
      Connect ();               /* menus are enabled in WM_PAINT once connection is established */
      break;
    
    case IDM_SAVE_NEWSRC:
      WriteNewsrc ();
      break;
      
    case IDB_TOGGLE_VIEW_UNSUB:
      if (ShowUnsubscribed == FALSE)
        ShowUnsubscribed = TRUE;
      else
        ShowUnsubscribed = FALSE;
      SendMessage (NetDoc.hWndTB, TB_CHECKBUTTON, IDB_TOGGLE_VIEW_UNSUB, ShowUnsubscribed);
      NetDoc.LongestLine = 0;
      SetGroupActiveLines ();
      ScreenToTop (&NetDoc);
      InvalidateRect (NetDoc.hDocWnd, NULL, FALSE);
      break;

    case IDB_FASTEXIT:
      if (Initializing == INIT_ESTAB_CONN) {
        break;      /* can't quit if in process of connecting (will GPF) */
      }
      if (NewsrcDirty) WriteNewsrc ();
      WriteWinvnProfile ();
      SaveNewsrc = SaveConfig = FALSE;  /* we've already done them */
      SetHandleBkBrush (NetDoc.hDocWnd, (HBRUSH) GetStockObject (WHITE_BRUSH));
      DestroyWindow (hWnd);
      return (0);
      break;

    case IDB_TOGGLE_CONNECT:
      if (Initializing == INIT_NOT_CONNECTED) {
        SendMessage (NetDoc.hWndTB, TB_ENABLEBUTTON, IDB_TOGGLE_CONNECT, FALSE);
        Connect ();
        break;
      }
      /* else fall into disconnect */
      SendMessage (NetDoc.hWndTB, TB_ENABLEBUTTON, IDB_TOGGLE_CONNECT, FALSE);
      
    case IDM_DISCONNECT:
      /* If the NNTP server disconnected, then Initializing == INIT_NOT_CONNECTED */
      if ((Initializing != INIT_NOT_CONNECTED && ConfirmDisconnect &&
           (MessageBox (NetDoc.hDocWnd,
                        "Are you sure you wish to disconnect from the server?\n"
                        "All active windows will be closed.",
                        "Please Confirm", MB_YESNO | MB_ICONQUESTION) == IDNO))) {
        SendMessage (NetDoc.hWndTB, TB_ENABLEBUTTON, IDB_TOGGLE_CONNECT, TRUE);
        SendMessage (NetDoc.hWndTB, TB_CHECKBUTTON, IDB_TOGGLE_CONNECT, TRUE);
        break;
      }

      if (Initializing != INIT_NOT_CONNECTED && Initializing != INIT_ESTAB_CONN) {
        PutCommData ("QUIT\r\n", 6);
      }
      Disconnect ();
      break;


    case IDM_VIEW_SEL_GROUP:
      break;

    case IDM_SHOW_SUBSCR:
    case IDM_SHOW_ALL_GROUP:
    case IDM_SEL_SUBSCR:   
      MessageBox (hWnd, "Command not implemented","Sorry", MB_OK);
      break;

	case IDM_SELECT_ALL:
	  ForAllLines (&NetDoc, SetLineFlag, 0, TRUE);
      InvalidateRect (NetDoc.hDocWnd, NULL, FALSE);
      break;

    case IDM_UNSEL_ALL:
      ForAllLines (&NetDoc, SetLineFlag, 0, FALSE);
      InvalidateRect (NetDoc.hDocWnd, NULL, FALSE);
      break;

    case IDM_SUBSCRIBE:
      InitGroupTable ();
      ForAllLines (&NetDoc, GroupAction, GROUP_ACTION_SUBSCRIBE, TRUE);
      BuildPtrList ();
      MergeGroups (ADD_SUBSCRIBED_END_OF_SUB);
      CleanUpGroupTable ();
      ForAllLines (&NetDoc, SetLineFlag, 0, FALSE);
      NetDoc.LongestLine = 0;
      SetGroupActiveLines ();
      ScreenToTop (&NetDoc);
      SetNetDocTitle ();
      InvalidateRect (NetDoc.hDocWnd, NULL, FALSE);
	  NewsrcDirty = TRUE;
      break;

    case IDM_UNSUBSCRIBE:
      InitGroupTable ();
      ForAllLines (&NetDoc, GroupAction, GROUP_ACTION_UNSUBSCRIBE, FALSE);
      BuildPtrList ();
      ShellSort (NewGroupTable,
                 (size_t) nNewGroups,
                 (size_t) sizeof (void far *),
                 (int (*)(const void huge *,const void huge *)) GroupCompare);
      MergeGroups (ADD_SUBSCRIBED_END_OF_SUB);
      CleanUpGroupTable ();
      ForAllLines (&NetDoc, SetLineFlag, 0, FALSE);
      NetDoc.LongestLine = 0;
      SetGroupActiveLines ();
      ScreenToTop (&NetDoc);
      SetNetDocTitle ();
      InvalidateRect (NetDoc.hDocWnd, NULL, FALSE);
	  NewsrcDirty = TRUE;
      break;

 case IDM_SORT_SELECTED:
      InitGroupTable ();
      ForAllLines (&NetDoc, GroupAction, GROUP_ACTION_SELECTED, TRUE);
      BuildPtrList ();
      ShellSort (NewGroupTable,
                 (size_t) nNewGroups,
                 (size_t) sizeof (void far *),
                 (int (*)(const void huge *,const void huge *)) GroupCompare);
      MergeGroups (ADD_FIRST_SELECTED);
      CleanUpGroupTable ();
      ForAllLines (&NetDoc, SetLineFlag, 0, FALSE);
      NetDoc.LongestLine = 0;
      SetGroupActiveLines ();
      ScreenToTop (&NetDoc);
      SetNetDocTitle ();
      InvalidateRect (NetDoc.hDocWnd, NULL, FALSE);
	  NewsrcDirty = TRUE;
      break;

    case IDM_GROUP_TOP:
      InitGroupTable ();
      ForAllLines (&NetDoc, GroupAction, GROUP_ACTION_SUBSCRIBE, TRUE);
      BuildPtrList ();
      MergeGroups (ADD_SUBSCRIBED_TOP_OF_DOC);
      CleanUpGroupTable ();
      ForAllLines (&NetDoc, SetLineFlag, 0, FALSE);
      ScreenToTop (&NetDoc);
      InvalidateRect (NetDoc.hDocWnd, NULL, FALSE);
	  NewsrcDirty = TRUE;
      break;


    case IDM_COMMOPTIONS:
      DialogBox (hInst, "WinVnComm", hWnd, lpfnWinVnCommDlg);
      break;

    case IDM_CONFIG_PERSONAL:
      DialogBox (hInst, "WinVnPersonal", hWnd, lpfnWinVnPersonalInfoDlg);
      break;
    
    case IDM_CONFIG_EXECUTE:
      DialogBox (hInst, "WinVnExecute", hWnd, lpfnWinVnExecuteInfoDlg);
      break;
        
    case IDM_CONFIG_CONFIRMATIONS:
      DialogBox (hInst, "WinVnConfirmations", hWnd, lpfnWinVnConfirmationDlg);
      break;

    case IDM_CONFIG_LOG:
      DialogBox (hInst, "WinVnLogOpts", hWnd, lpfnWinVnLogOptDlg);
      break;

    case IDM_CONFIG_SMART_FILER:
      DialogBox (hInst, "WinvnSmartFiler", NetDoc.hDocWnd, lpfnWinVnSmartFilerDlg);
      break;

    case IDM_CONFIG_ARTLIST:
      DialogBox (hInst, "WinVnArticleListPrefs", hWnd, lpfnWinVnArtListDlg);
      break;

    case IDM_CONFIG_ARTICLE:
      DialogBox (hInst, "WinVnArticlePrefs", hWnd, lpfnWinVnArticleDlg);
      break;

    case IDM_CONFIG_GROUPLIST:
      DialogBox (hInst, "WinVnGroupListPrefs", hWnd, lpfnWinVnGroupListDlg);
      break;

    case IDM_COMPOSE_PREFS:
      DialogBox (hInst, "WinVnComposePrefs", hWnd, lpfnWinVnComposePrefsDlg);
      break;

    case IDM_ATTACH_PREFS:
      DialogBox (hInst, "WinVnAttachPrefs", hWnd, lpfnWinVnAttachPrefsDlg);
      break;

    case IDM_CONFIG_CODING:
      DialogBox (hInst, "WinVnCodingPrefs", hWnd, lpfnWinVnCodingPrefsDlg);
      break;

    case IDM_FONT_WINVNSYSTEM:
      if (AskForFont (NetDoc.hDocWnd, WinVnFontFace, &WinVnFontSize, WinVnFontStyle))
        break;
      InitWinVnFonts ();
      InitStatBar (&StatbarPntData);
      SendMessage (NetDoc.hWndFrame, WM_SIZE, 0, 0L);
      InvalidateRect (NetDoc.hWndFrame, NULL, TRUE);
      InvalidateRect (NetDoc.hDocWnd, NULL, TRUE);
      RefreshArticleWnds ();
      RefreshGroupWnds ();
      RefreshComposeWnds ();
      break;

    case IDM_FONT_COMPOSITION:
      if (AskForFont (NetDoc.hDocWnd, CompositionFontFace, &CompositionFontSize, CompositionFontStyle))
        break;
      InitCompositionFonts ();
      RefreshComposeWnds ();
      break;

    case IDM_FONT_GROUPLIST:
      if (AskForFont (NetDoc.hDocWnd, ListFontFace, &ListFontSize, ListFontStyle))
        break;
      InitListFonts ();
      SendMessage (NetDoc.hWndFrame, WM_SIZE, 0, 0L);
      InvalidateRect (NetDoc.hDocWnd, NULL, TRUE);
      RefreshGroupWnds ();
      break;

    case IDM_FONT_ARTICLE_TEXT:
      if (AskForFont (NetDoc.hDocWnd, ArticleFontFace, &ArticleFontSize, ArticleFontStyle))
        break;
      InitArticleFonts ();
      RefreshArticleWnds ();
      RefreshComposeWnds ();
      break;

    case IDM_FONT_STATUS_TEXT:
    askStatusFont:;
      if (AskForFont (NetDoc.hDocWnd, StatusFontFace, &StatusFontSize, StatusFontStyle))
        break;
      InitStatusFonts ();
      if (STATUSWIDTH > xScreen) {
        MessageBox (NetDoc.hDocWnd, "The status window will not fit on your screen with this font.\nPlease select a smaller font",
                    "Font too big", MB_OK | MB_ICONSTOP);
        goto askStatusFont;
      }
      RefreshStatusWnds ();
      break;

    case IDM_FONT_PRINT_TEXT:
      if (AskForFont (NetDoc.hDocWnd, PrintFontFace, &PrintFontSize, "Printer"))
        break;
      InitPrintFonts ();
      break;

    case IDM_TRUE_INVERSE_SELECTIONS:
      UseInverseSelections = !UseInverseSelections;
      InvalidateRect (NetDoc.hDocWnd, NULL, TRUE);
      RefreshGroupWnds ();
      break;

#ifdef USE_SPLASH
    case IDM_USE_SPLASH:
      ShowSplashScreen = !ShowSplashScreen;
      InvalidateRect (NetDoc.hDocWnd, NULL, TRUE);
      break;
#endif

    case IDM_COLOR_SUBSCRIBED:
      if (AskForColor (NetDoc.hDocWnd, &NetSubscribedColor))
        break;
      WarnColors (NetSubscribedColor, ListBackgroundColor, "List Background");
      InvalidateRect (NetDoc.hDocWnd, NULL, TRUE);
      break;

    case IDM_COLOR_UNSUBSCRIBED:
      if (AskForColor (NetDoc.hDocWnd, &NetUnSubscribedColor))
        break;
      WarnColors (NetUnSubscribedColor, ListBackgroundColor, "List Background");
      InvalidateRect (NetDoc.hDocWnd, NULL, TRUE);
      break;

    case IDM_COLOR_SEEN:
      if (AskForColor (NetDoc.hDocWnd, &ArticleSeenColor))
        break;
      WarnColors (ArticleSeenColor, ListBackgroundColor, "List Background");
      RefreshGroupWnds ();
      break;

    case IDM_COLOR_UNSEEN:
      if (AskForColor (NetDoc.hDocWnd, &ArticleUnSeenColor))
        break;
      WarnColors (ArticleUnSeenColor, ListBackgroundColor, "List Background");
      RefreshGroupWnds ();
      break;

    case IDM_COLOR_KILLED:
      if (AskForColor (NetDoc.hDocWnd, &ArticleKilledColor))
        break;
      WarnColors (ArticleKilledColor, ListBackgroundColor, "List Background");
      RefreshGroupWnds ();
      break;

    case IDM_COLOR_ARTICLE_TEXT:
      if (AskForColor (NetDoc.hDocWnd, &ArticleTextColor))
        break;
      WarnColors (ArticleTextColor, ArticleBackgroundColor, "Article Background");
      RefreshArticleWnds ();
      break;

    case IDM_COLOR_STATUS_TEXT:
      if (AskForColor (NetDoc.hDocWnd, &StatusTextColor))
        break;
      WarnColors (StatusTextColor, StatusBackgroundColor, "Status Background");
      RefreshStatusWnds ();
      break;

    case IDM_COLOR_LIST_BACKGROUND:
      if (AskForColor (NetDoc.hDocWnd, &ListBackgroundColor))
        break;
      DeleteObject (hListBackgroundBrush);
      hListBackgroundBrush = CreateSolidBrush (ListBackgroundColor);
      SetHandleBkBrush (hWnd, hListBackgroundBrush);

      WarnColors (ArticleSeenColor, ListBackgroundColor, "Seen Article") ||
        WarnColors (ArticleUnSeenColor, ListBackgroundColor, "Unseen Article") ||
        WarnColors (ArticleKilledColor, ListBackgroundColor, "Killed Article") ||
        WarnColors (NetSubscribedColor, ListBackgroundColor, "Subscribed Group") ||
        WarnColors (NetUnSubscribedColor, ListBackgroundColor, "Unsubscribed Group");

      RefreshGroupWnds ();
      InvalidateRect (NetDoc.hDocWnd, NULL, TRUE);
      break;

    case IDM_COLOR_ARTICLE_BACKGROUND:
      if (AskForColor (NetDoc.hDocWnd, &ArticleBackgroundColor))
        break;
      DeleteObject (hArticleBackgroundBrush);
      hArticleBackgroundBrush = CreateSolidBrush (ArticleBackgroundColor);

      WarnColors (ArticleTextColor, ArticleBackgroundColor, "Article Text");
      RefreshArticleWnds ();
      break;

    case IDM_COLOR_STATUS_BACKGROUND:
      if (AskForColor (NetDoc.hDocWnd, &StatusBackgroundColor))
        break;
      DeleteObject (hStatusBackgroundBrush);
      hStatusBackgroundBrush = CreateSolidBrush (StatusBackgroundColor);
      RefreshStatusWnds ();

      WarnColors (StatusTextColor, StatusBackgroundColor, "Status Text");
      break;

    case IDM_WINDOW_CASCADE:
      WinVNCascadeWindows ();
      break;

    case IDM_CLOSE_ALL:
      if (ConfirmBatchOps)
        if (MessageBox (NetDoc.hDocWnd, "Are you sure you wish to close all open windows?",
                      "Please Confirm", MB_YESNO | MB_ICONQUESTION) == IDNO)
          break;
      CloseWindows ();
      break;

    case IDM_CLOSE_ALL_ARTICLE:
      if (ConfirmBatchOps)
        if (MessageBox (NetDoc.hDocWnd, "Are you sure you wish to close all open article windows?",
                      "Please Confirm", MB_YESNO | MB_ICONQUESTION) == IDNO)
          break;
      CloseArticleWnds ();
      break;

    case IDM_CLOSE_ALL_GROUP:
      if (ConfirmBatchOps)
        if (MessageBox (NetDoc.hDocWnd, "Are you sure you wish to close all open group windows?",
                      "Please Confirm", MB_YESNO | MB_ICONQUESTION) == IDNO)
          break;
      CloseGroupWnds ();
      break;

    case IDM_CLOSE_ALL_COMPOSE:
      if (ConfirmBatchOps)
        if (MessageBox (NetDoc.hDocWnd, "Are you sure you wish to close all open composition windows?",
                      "Please Confirm", MB_YESNO | MB_ICONQUESTION) == IDNO)
          break;
      CloseComposeWnds ();
      break;

    case IDM_CLOSE_ALL_STATUS:
      if (ConfirmBatchOps)
        if (MessageBox (NetDoc.hDocWnd, "Are you sure you wish to close all open status windows?",
                      "Please Confirm", MB_YESNO | MB_ICONQUESTION) == IDNO)
          break;
      CloseStatusWnds ();
      break;

    case IDM_MINIMIZE_ALL:
      MinimizeWindows ();
      break;

    case IDM_MINIMIZE_ALL_ARTICLE:
      MinimizeArticleWnds ();
      break;

    case IDM_MINIMIZE_ALL_GROUP:
      MinimizeGroupWnds ();
      break;

    case IDM_MINIMIZE_ALL_COMPOSE:
      MinimizeComposeWnds ();
      break;

    case IDM_MINIMIZE_ALL_STATUS:
      MinimizeStatusWnds ();
      break;

    case IDM_RESTORE_ALL:
      RestoreWindows ();
      break;

    case IDM_SEND_ALL_POST:
      if (ConfirmBatchOps)
        if (MessageBox (NetDoc.hDocWnd, "Are you sure you wish to send all open post windows?",
                      "Please Confirm", MB_YESNO | MB_ICONQUESTION) == IDNO)
          break;
      BatchSend (DOCTYPE_POSTING);
      break;

    case IDM_SEND_ALL_MAIL:
      if (ConfirmBatchOps)
        if (MessageBox (NetDoc.hDocWnd, "Are you sure you wish to send all open mail windows?",
                      "Please Confirm", MB_YESNO | MB_ICONQUESTION) == IDNO)
          break;
      BatchSend (DOCTYPE_MAIL);
      break;

    case IDM_DECODE_FILE:
      if (TestCodingBusy (NetDoc.hDocWnd, "Can't decode file"))
        break;

      if (!DialogBoxParam (hInst, "WinVnDecodeArts", NetDoc.hDocWnd, lpfnWinVnDecodeArtsDlg, 0))
        InvalidateRect (NetDoc.hDocWnd, NULL, TRUE);
      else
        DecodeFile (hWnd);
      break;

    case IDM_ENCODE_FILE:
      {
        TypAttachment attach;
        if (TestCodingBusy (NetDoc.hDocWnd, "Can't encode file"))
          break;
        if (!DialogBoxParam (hInst, "WinVnEncode", NetDoc.hDocWnd, lpfnWinVnEncodeDlg, (LPARAM) & attach))
          InvalidateRect (NetDoc.hDocWnd, NULL, TRUE);
        else
          EncodeToFile (NetDoc.hDocWnd, &attach);
      }
      break;

    case IDM_RESET:
      if (!CommBusy || SendingMail || SendingPost) {
          if (MessageBox (NetDoc.hDocWnd,
                  "Are you sure you wish to reset the server protocol?\n"
                   "Any active communications and coding sessions will be aborted.",
                   "Please Confirm", MB_YESNO | MB_ICONQUESTION) == IDYES) {
            AbortAllComm ();
            hMenu = GetMenu (hWnd);
            hSubMenu = GetSubMenu (hMenu, 1);
            EnableMenuItem (hSubMenu, IDM_CONNECT, ENABLE_MENU);
            
          }
      } else {  
          if (MessageBox (NetDoc.hDocWnd,
                "It is not usually safe to reset server protocol while receiving \n"
                "information from your server information.  This can leave WinVN \n"
                "in an unstable state.  Use only as a last resort.  Are you \n"
                "sure you wish to continue. \n",
                "Please Confirm", MB_YESNO | MB_ICONQUESTION) == IDYES) {
                
              CommDoc = (TypDoc *) NULL;
              CommBusy = FALSE;
              CommState = ST_NONE;
              hMenu = GetMenu (hWnd);
              hSubMenu = GetSubMenu (hMenu, 1);
              EnableMenuItem (hSubMenu, IDM_CONNECT, ENABLE_MENU);
    //        Reconnect();
          }
      }
      break;

    case IDM_RECONNECT:
      if ((Initializing != INIT_NOT_CONNECTED && Initializing != INIT_ESTAB_CONN) &&
          (!ConfirmDisconnect ||
          (MessageBox (NetDoc.hDocWnd,
                    "Are you sure you wish to disconnect and reconnect to the server?\n"
                       "All active windows will be closed.",
                   "Please Confirm", MB_YESNO | MB_ICONQUESTION) == IDYES))) {
            Reconnect();
        }
        break;
        
    case IDM_SAVE_CONFIG:
      WriteWinvnProfile ();
      MessageBox (NetDoc.hDocWnd, "WinVN Configuration Saved", "WinVN Configuration", MB_OK);
      break;

    case IDM_SAVE_WINDOW:
      SaveWindowPositions ();
	  WriteWinvnProfile ();
      break;

    case IDM_FIND:
      FindDoc = &NetDoc;
      if (!(FindDoc->SearchStr[0]) && LastGroupNameFind[0])
        strcpy (FindDoc->SearchStr, LastGroupNameFind);

      if (DialogBox (hInst, "WinVnFind", hWnd, lpfnWinVnFindDlg)) {
        found = DoFind (TRUE, GroupListMultiSelect);
        if (!found) {
          strcpy (mybuf, "\"");
          strcat (mybuf, NetDoc.SearchStr);
          strcat (mybuf, "\" not found.");
          MessageBox (hWnd, mybuf, "Not found", MB_OK);
        }
        else {
          strcpy (LastGroupNameFind, FindDoc->SearchStr);
          if (LineIDtoLinePtr (FindDoc->FindLineID, FindDoc, &BlockPtr, &LinePtr)) {
            SetSelections (FindDoc, LinePtr);
            if (GroupListMultiSelect) {
              /* set the active and anchor line IDs = FindLineID */
              FindDoc->AnchorLineID = FindDoc->FindLineID;
            }
          }
        }
      }
      break;

    case IDM_FIND_NEXT_SAME:
      FindDoc = &NetDoc;
      continueFind = TRUE;
      if (!FindDoc->SearchStr[0]) {
        if (!(FindDoc->SearchStr[0]) && LastGroupNameFind[0])
          strcpy (FindDoc->SearchStr, LastGroupNameFind);
        continueFind = DialogBox (hInst, "WinVnFind", hWnd, lpfnWinVnFindDlg);
      }

      if (continueFind && FindDoc->SearchStr[0]) {
        found = DoFind (!FindDoc->hFindBlock && !FindDoc->FindLineID, GroupListMultiSelect);
        if (!found) {
          strcpy (mybuf, "\"");
          strcat (mybuf, NetDoc.SearchStr);
          strcat (mybuf, "\" not found.");
          MessageBox (hWnd, mybuf, "No more occurrences", MB_OK);
        }
        else {
          if (LineIDtoLinePtr (FindDoc->FindLineID, FindDoc, &BlockPtr, &LinePtr)) {
            SetSelections (FindDoc, LinePtr);
            if (GroupListMultiSelect) {
              /* set the active and anchor line IDs = FindLineID */
              FindDoc->AnchorLineID = FindDoc->FindLineID;
            }
          }
        }
      }
      break;

    case ID_ABOUT:
      lpProcAbout = (DLGPROC) MakeProcInstance ((FARPROC) About, hInst);
      DialogBox (hInst, "AboutBox", hWnd, lpProcAbout);
      FreeProcInstance ((FARPROC) lpProcAbout);
      break;

    case IDM_HELP:
      MakeHelpPathName (mybuf, MAXINTERNALLINE);
      WinHelp (NetDoc.hDocWnd, mybuf, HELP_INDEX, 0L);
      break;

    case IDM_MAIL:
      (MailCtrl.fnMlWinCreate) (hWnd, (TypDoc *) NULL, DOCTYPE_MAIL);
      break;

    case IDM_LOGOUT:
      (MailCtrl.fnMlLogout) (hWnd);
      break;

    case IDM_SHOW_VERSION:
      show_version_strings (hWnd);
      break;

    case IDM_POST:
      /* NewsgroupsPtr = (char *) NULL; */
      *groupbuf = '\0';
      ForAllLines (&NetDoc, GroupAction, GROUP_ACTION_GET_GROUPNAMES, TRUE);
      NewsgroupsPtr = &groupbuf[0];
      CreateComposeWnd (hWnd, (TypDoc *) NULL, DOCTYPE_POSTING);
      break;
    }

  default:
    if (GetFocus () == hWnd)
      SetFocus (NetDoc.hDocWnd);
    return (DefWindowProc (hWnd, message, wParam, lParam));
    break;
  }

  if (GetFocus () == hWnd)
    SetFocus (NetDoc.hDocWnd);
  return (0);
}

/*
 * Local Variables:
 * tab-width: 2
 * end:
 */

/* Last line of WVUSENET.CPP */

--=====================_833256653==_
Content-Type: text/plain; charset="us-ascii"
Content-Disposition: attachment; filename="INI_NEWS.cpp"

/* -*- C++ -*-
/*
 * $Id: ini_news.cpp 1.14 1995/03/29 00:36:04 dumoulin Exp $
 */

/******************************************************************** */
/* Filename: Ini-News.c */

#include <windows.h>
#include <windowsx.h>

//extern "C"
//{
#include "wvglob.h"
#include "winvn.h"
//}

#pragma hdrstop

#include <stdlib.h>
#include <direct.h>
#include <time.h>
#include <commdlg.h>

/*************************************************************************
  This function looks for the .Ini file and the NewsSrc file.  Each file
  is searched for separately and they need not be in the same directory.
  If neither file can be found, the user is asked to locate the files with
  an OPENFILENAME dialog box.  Once the location of the NewSrc file is
  known, it is saved in the WinVN.ini file.

  The search order is:
    Command Line (one or two entries)
    Environment variable "WINVN" or "winvn" (case sensitive in Win16)
    Current Working directory (set by "Properties" dialog)
    Application Directory
    Windows Directory (eg. C:\WINDOWS)
    Windows System Directory (eg. C:\WINDOWS\SYSTEM, \System32)
    the path
    result of an "Open File" dialog box (WinVN.ini only)
    the .INI file (NewSrc only)
    result of an "Open File" dialog box (NewSrc only)


  If the Function is sucessful, the szAppProFile variable contains the path and
  filename of the .Ini file and the szNewsSrc variable contains the path and
  filename of the NewSrc file.  If either file does not exist, the "Open
  File" dialog creates each file.  In all cases, the name of the NewSrc
  file is written to the .INI file for future accesses.

  RETURNS:  zero if sucessful
            -1 if one or both files could not be found

  NOTE: The command line allows for the use of unique names for the standard
        WinVn.ini and NewSrc files. The entry order is .Ini and then NewSrc.

 Command line examples are as follows:

  1.  c:\WinVn\My.Ini<sp>c:\Tom\NewsSrc.Tom  => both unique names

  2.  c:\Tom<sp>c:\Tom\NewsSrc.Tom        => standard .Ini, unique NewSrc
  2a. c:\Tom\<sp>c:\Tom\NewsSrc.Tom          (same, less ambiguous)

  3.  c:\Tom\TomVn.ini                    => unique .Ini, Standard NewSrc

  4.  c:\Tom                              => standard .Ini, Standard NewSrc
  4a. c:\Tom\                                (same, less ambiguous)

  In the first example, if both files exist, szAppProFile = "c:\WinVn\My.Ini",
  szNewsSrc = "c:\Tom\NewsSrc.Tom" and the function returns Zero.

 For all specification types other than the Command line:
 1. Only one entry may be made.
 2. The "Working directory" spec doesn't allow trailing "\".
 3. Syntax and algorithm are otherwise the same as for Command Line.

**************************************************************************/

static struct ValStrTable SortOptionTable[] =
{
  IDM_SORT_DATE,      STR_SORT_DATE,
  IDM_SORT_SUBJECT,   STR_SORT_SUBJECT,
  IDM_SORT_LINES,     STR_SORT_LINES,
  IDM_SORT_THREADS,   STR_SORT_THREADS,
  IDM_SORT_THREADSUB, STR_SORT_THREADSUB,
  IDM_SORT_ARTNUM,    STR_SORT_ARTNUM,
  IDM_SORT_FROM,      STR_SORT_FROM
};

#define SORTOPTIONTABLESIZE (sizeof(SortOptionTable) / sizeof(SortOptionTable[0]))

#define CHECK_DIR 1
#define CHECK_FILE 2

/*  Routine to test existence of filename on path choice */
BOOL 
ProbeFilePath (char *szFileSrc, const char *szPath, const char *szFname, int iChk)
{
  DWORD stat;
  OFSTRUCT ofb;

  /* Check for path in the form of "x:\dir\...\dir[\]" */
  if (iChk & CHECK_DIR) {
	int i;
	lstrcpy (szFileSrc, szPath);
	i = lstrlen (szFileSrc);
	if (i) {
	  if (szFileSrc[i - 1] == '\\') {	/* Trailing '\', it must be a directory */
		lstrcat (szFileSrc, szFname);
		if (OpenFile (szFileSrc, &ofb, OF_EXIST) != HFILE_ERROR) {
		  lstrcpy (szFileSrc, ofb.szPathName);
		  return (TRUE);
		}
		else
		  return (FALSE);
	  }
	}
#ifdef GetFileAttributes		/*  32 bit  */
	stat = GetFileAttributes (szFileSrc);
	if ((stat != 0xFFFFFFFF) && (stat & FILE_ATTRIBUTE_DIRECTORY))
#else /*  16 bit  */
	stat = 0;					/* avoid 'unreferenced' warning */
	if (TRUE)
#endif
	{
	  lstrcat (szFileSrc, "\\");
	  lstrcat (szFileSrc, szFname);
	  if (OpenFile (szFileSrc, &ofb, OF_EXIST) != HFILE_ERROR) {
		lstrcpy (szFileSrc, ofb.szPathName);
		return TRUE;
	  }
	}
  }

  /* Check for non-directory file in the form of "x:\dir\...\dir\file.ext" */
  if (iChk & CHECK_FILE) {
	lstrcpy (szFileSrc, szPath);

#ifdef GetFileAttributes
	if (GetFileAttributes (szFileSrc) & FILE_ATTRIBUTE_DIRECTORY)
	  return (FALSE);
#endif

	if (OpenFile (szFileSrc, &ofb, OF_EXIST) != HFILE_ERROR) {
	  lstrcpy (szFileSrc, ofb.szPathName);
	  return TRUE;
	}
  }
  return FALSE;
}



/*  See if we can find the WinVN.ini and NewSrc files... */
int 
LocIniSrc (HINSTANCE hInstance, LPSTR lpCmdLine)
{
  char szWPath[256];

  const char szIniDef[] = "WinVN.ini";	/* ' .ini' default filename */
  const char szNewsDefault[] = "newsrc";	/* 'newsrc' default filename  */

  BOOL fProfileFound = FALSE;
  BOOL fNewsFound = FALSE;

  int iRvalue;

  LPSTR lpEnvStr;
  OFSTRUCT ofOpenBuffer;

  ofOpenBuffer.cBytes = sizeof (OFSTRUCT);


  if (*lpCmdLine != '\0') {		/* Look at the Command Line - path | path and file */
	lstrcpy (szWPath, lpCmdLine);
//	TRACE1 ("Checking Command Line: %s\n", szWPath);
	iRvalue = lstrlen (szWPath);

	while (iRvalue--) {			/* check for two arguments */
	  if (szWPath[iRvalue] == ' ') {
		szWPath[iRvalue] = 0;
		break;
	  }
	}

	if (iRvalue > 0) {			/* two entries on command line - check for path and/or path\file */
	  fProfileFound = ProbeFilePath (szAppProFile, szWPath,
									 szIniDef, CHECK_DIR | CHECK_FILE);
	  fNewsFound = ProbeFilePath (szNewsSrc, &szWPath[iRvalue + 1],
								  szNewsDefault, CHECK_FILE);
	}

	else {						/* single command line entry - path and/or path\file */
	  fProfileFound = ProbeFilePath (szAppProFile, szWPath,
									 szIniDef, CHECK_DIR | CHECK_FILE);
	  fNewsFound = ProbeFilePath (szNewsSrc, szWPath,
								  szNewsDefault, CHECK_DIR);
	}
	if (fProfileFound && fNewsFound)
	  return 0;
  }



  /* Look in the Environment for a path */
  if ((lpEnvStr = getenv ("winvn")) == NULL) {
	lpEnvStr = getenv ("WINVN");
  }

  if (lpEnvStr && (iRvalue = lstrlen (lpEnvStr))) {
	/* found environment varible, check it out */
	lstrcpy (szWPath, lpEnvStr);

//	TRACE1 ("Checking environment var: %s\n", szWPath);
	if (!fProfileFound)
	  fProfileFound = ProbeFilePath (szAppProFile, szWPath,
									 szIniDef, CHECK_DIR | CHECK_FILE);

//	TRACE1 ("Checking for newsrc in %s\n", szWPath);
	if (!fNewsFound)
	  fNewsFound = ProbeFilePath (szNewsSrc, szWPath,
								  szNewsDefault, CHECK_DIR);
	if (fProfileFound && fNewsFound)
	  return 0;
  }



  /* Look in the current working Directory */
  if (_getcwd (szWPath, sizeof (szWPath))) {
//	TRACE1 ("Checking current working directory: %s\n", szWPath);
	if (!fProfileFound)
	  fProfileFound = ProbeFilePath (szAppProFile, szWPath,
									 szIniDef, CHECK_DIR);

	if (!fNewsFound)
	  fNewsFound = ProbeFilePath (szNewsSrc, szWPath,
								  szNewsDefault, CHECK_DIR);

	if (fProfileFound && fNewsFound)
	  return 0;
  }



  /* Look in WinVn's Directory */
  if (iRvalue = GetModuleFileName (hInstance, szWPath, sizeof (szWPath))) {
	while (iRvalue--) {			/* remove the module name */
	  if (szWPath[iRvalue] == '\\') {
		szWPath[++iRvalue] = 0;
		break;
	  }
	}

//	TRACE1 ("Checking WinVN directory: %s\n", szWPath);
	if (!fProfileFound)
	  fProfileFound = ProbeFilePath (szAppProFile, szWPath,
									 szIniDef, CHECK_DIR);

	if (!fNewsFound)
	  fNewsFound = ProbeFilePath (szNewsSrc, szWPath,
								  szNewsDefault, CHECK_DIR);

	if (fProfileFound && fNewsFound)
	  return 0;
  }



  /* Look in the Windows Directory */
  if (GetWindowsDirectory (szWPath, sizeof (szWPath))) {
//	TRACE1 ("Checking Windows directory: %s\n", szWPath);
	if (!fProfileFound)
	  fProfileFound = ProbeFilePath (szAppProFile, szWPath,
									 szIniDef, CHECK_DIR);

	if (!fNewsFound)
	  fNewsFound = ProbeFilePath (szNewsSrc, szWPath,
								  szNewsDefault, CHECK_DIR);

	if (fProfileFound && fNewsFound)
	  return 0;
  }



  /* Look in the Windows System Directory */
  if (GetSystemDirectory (szWPath, sizeof (szWPath))) {
//	TRACE1 ("Checking Windows System directory: %s\n", szWPath);
	if (!fProfileFound)
	  fProfileFound = ProbeFilePath (szAppProFile, szWPath,
									 szIniDef, CHECK_DIR);

	if (!fNewsFound)
	  fNewsFound = ProbeFilePath (szNewsSrc, szWPath,
								  szNewsDefault, CHECK_DIR);

	if (fProfileFound && fNewsFound)
	  return 0;
  }



  /* Try the current path */
  szWPath[0] = '\0';
//  TRACE0("Checking path\n");
  if (!fProfileFound)
	fProfileFound = ProbeFilePath (szAppProFile, szWPath,
								   szIniDef, CHECK_DIR);

  if (!fNewsFound)
	fNewsFound = ProbeFilePath (szNewsSrc, szWPath,
								szNewsDefault, CHECK_DIR);

  if (fProfileFound && fNewsFound)
	return 0;
  else {						/* Ask the user if he wants to create the files */
	static OPENFILENAME ofn;

	ofn.lStructSize = sizeof (OPENFILENAME);
	ofn.hwndOwner = NULL;
	ofn.hInstance = NULL;
	/* ofn.lpstrFilter       = (Filled in below)  */
	ofn.lpstrCustomFilter = NULL;
	ofn.nMaxCustFilter = 0;
	ofn.nFilterIndex = 0;
	/* ofn.lpstrFile         = (Filled in below)  */
	/* ofn.nMaxFile          = (Filled in below)  */
	ofn.lpstrFileTitle = NULL;
	ofn.nMaxFileTitle = 0;
	ofn.lpstrInitialDir = szWPath;
	ofn.lpstrTitle = "Please identify existing or new filename";
	ofn.Flags =
	  OFN_CREATEPROMPT | OFN_HIDEREADONLY
#ifdef OFN_NONETWORKBUTTON
	  | OFN_NONETWORKBUTTON
#endif
	  | OFN_NOREADONLYRETURN;
	ofn.nFileOffset = 0;
	ofn.nFileExtension = 0;
	/* ofn.lpstrDefExt       = (Filled in below)  */
	ofn.lCustData = 0L;
	ofn.lpfnHook = NULL;
	ofn.lpTemplateName = NULL;

	if (!fProfileFound) {
	  char szCreated[32];

	  ofn.lpstrFilter =
		"Init Files (*.INI)\0*.ini\0All Files (*.*)\0*.*\0";
	  lstrcpy (szAppProFile, szIniDef);
	  ofn.lpstrFile = szAppProFile;
	  ofn.nMaxFile = sizeof (szAppProFile);
	  ofn.lpstrDefExt = "INI";

//	  TRACE0("Checking user-specified filename\n");
	  fProfileFound = GetOpenFileName (&ofn);
	  if (fProfileFound) {
		char dbuffer[9];
		char tbuffer[9];
		OFSTRUCT ofs;

		/* ofn.lpstrInitialDir = ...  */

		if (OpenFile (szAppProFile, &ofs, OF_EXIST) == HFILE_ERROR) {
		  _strdate (dbuffer);
		  _strtime (tbuffer);

//		  TRACE1 ("Writing new WinVN.INI file to %s\n", szAppProFile);
		  lstrcpy (szCreated, dbuffer);
		  lstrcat (szCreated, " ");
		  lstrcat (szCreated, tbuffer);

		  WritePrivateProfileString (ADMIN, "Created",
									 szCreated, szAppProFile);
		}
	  }
	}


	/* Look for NewSrc spec in WinVN.INI */
	if (fProfileFound && !fNewsFound) {
//	  TRACE1 ("Checking %s for NewSrc file", szAppProFile);
	  if (GetPrivateProfileString (ADMIN, "Newsrc", "",
							 szNewsSrc, sizeof (szNewsSrc), szAppProFile)) {
		OFSTRUCT ofs;
		fNewsFound = (OpenFile (szNewsSrc, &ofs, OF_EXIST) != HFILE_ERROR);
	  }
	}


	/* Give up?  Ask the user where the file is... */
	if (fProfileFound && !fNewsFound) {
	  ofn.lpstrFilter =
		"No-ext Files (*.)\0*.\0All Files (*.*)\0*.*\0";
	  ofn.nFilterIndex = 0;
	  lstrcpy (szNewsSrc, szNewsDefault);
	  ofn.lpstrFile = szNewsSrc;
	  ofn.nMaxFile = sizeof (szNewsSrc);
	  ofn.lpstrDefExt = "";

	  fNewsFound = GetOpenFileName (&ofn);
	  if (fNewsFound) {
		HFILE hf;
		OFSTRUCT ofs;
		if (OpenFile (szNewsSrc, &ofs, OF_EXIST) == HFILE_ERROR) {
		  hf = OpenFile (szNewsSrc, &ofs, OF_CREATE);
		  /* write some default newsgroups... */
		  _lwrite (hf, "news.announce.newusers: s0\r\n", 28);
		  _lwrite (hf, "news.newusers.questions: s0\r\n", 29);
		  _lclose (hf);
		}
	  }
	}
  }

  if (fProfileFound && fNewsFound)
	return 0;
  return (-1);
}


/*  Locate the WinVN.ini and NewSrc files... */
int 
fnLocateFiles (HINSTANCE hInstance, LPSTR lpCmdLine)
{
  int result;
  result = LocIniSrc (hInstance, lpCmdLine);
  if (!result) {
//	TRACE1 ("WinVN.INI file located: %s\n", szAppProFile);
//	TRACE1 ("NewSrc file located:    %s\n", szNewsSrc);
	WritePrivateProfileString (ADMIN, "Newsrc",
							   szNewsSrc, szAppProFile);
  }
  return (result);
}


// Read the group sort option from the .ini file
int ReadSortOption(const char* sDefSort, const char* sGrp)
{
  int retval;
  int i;
  char szTmp[MAXGROUPNAME+4];
  char mybuf[32];
  static char sGlobDefSort[32];

  if(!sGlobDefSort[0])
  {
    lstrcpy(szTmp, "N: ");
    lstrcat(szTmp, NNTPHost);
    GetPrivateProfileString(szTmp, "DefaultArtSort", sDefSort, sGlobDefSort, sizeof(sGlobDefSort), szAppProFile);
  }

  lstrcpy(szTmp,"G: ");
  lstrcat(szTmp, sGrp);
  GetPrivateProfileString(szTmp, "SortOption", sGlobDefSort, mybuf, sizeof(mybuf), szAppProFile);
  retval = atoi(mybuf);
  if(!retval)
  {
    for(i = 0; i < SORTOPTIONTABLESIZE; i++)
    {
      if(!strcmp(mybuf, SortOptionTable[i].text))
      {
        return SortOptionTable[i].value;
      }
    }
    retval = IDM_SORT_ARTNUM; // gotta return something valid...
  }
  return retval;
}


// Write the group sort option to the .ini file
void WriteSortOption(int iOpt, const char* sGrp)
{
  char szTmp[MAXGROUPNAME+4];
  lstrcpy(szTmp,"G: ");
  lstrcat(szTmp, sGrp);
  for(int i = 0; i < SORTOPTIONTABLESIZE; i++)
  {
    if(iOpt == SortOptionTable[i].value)
    {
      WritePrivateProfileString(szTmp, "SortOption", SortOptionTable[i].text, szAppProFile);
      return;
    }
  }
  WritePrivateProfileString(szTmp, "SortOption", STR_SORT_ARTNUM, szAppProFile); // write something valid
}


/*
 * Local Variables:
 * tab-width: 4
 * end:
 */

--=====================_833256653==_
Content-Type: text/plain; charset="us-ascii"
Content-Disposition: attachment; filename="WVGROUP.cpp"

// -*- C++ -*-
/********************************************************************
 *                                                                  *
 *  MODULE    :  WVGROUP.CPP                                        *
 *                                                                  *
 *  PURPOSE   : This file contains the window procedure for the     *
 *              Group viewing window for WinVN.                     *
 *                                                                  *
 *  FUNCTIONS :                                                     *
 *              WinVnViewWndProc()    -  Window Procedure for any   *
 *                                       of WinVN's group windows   *
 *                                       (now a child window window)*
 *                                                                  *
 *              WinVnViewFrameWndProc()- Window Procedure for any   *
 *                                       WinVN group frame window   *
 *                                                                  *
 *              FileLength()          -  Find the size in bytes of  *
 *                                       a file                     *
 *                                                                  *
 *              ViewArticle()         -  Requests and views an      *
 *                                       article from the server    * 
 *                                                                  *
 *              setArticleFocus()     -  Makes an article active    *
 *                                                                  *
 *              UnlinkArtsInGroup()   -  Prepares article window    *
 *                                       for deletion               *
 *                                                                  *
 *              UpdateSeenArts()      -  Save seen info in document *
 *                                                                  *
 *              CursorToTextLine()    -  Locates text in a document *
 *                                       based on cursor position   *
 *                                                                  *
 *              search_headers()      -  Search all headers for a   *
 *                                       given substring            *
 *                                                                  *
 *    string_compare_insensitive()    -  Compare two strings while  *
 *                                       ignoring case sensitivity  *
 *                                                                  *
 *               AffectSelected()     -  Set selected flag based on *
 *                                       search criteria            *
 *                                                                  *
 ********************************************************************/


/*
 * $Id: wvgroup.cpp 1.77 1995/11/10 06:45:49 dumoulin Exp $
 *
 */

#include <windows.h>
#include <windowsx.h>           // for GlobalFreePtr (JSC)
//extern "C"
//{
#include "wvglob.h"
#include "winvn.h"
//}
#pragma hdrstop
extern "C"
{
#include "wvtb\wvtb.h"          /* for toolbar */
}
#include "WVClass.h"
#include <assert.h>

#define WM_DISPLAY_SORTED    WM_USER + 3570
#define WM_AA_ENABLE_DISABLE WM_USER + 3571

char_p string_compare_insensitive (char_p a, LPSTR b);
long find_first_selected_art(TypDoc far * Doc);

/*--- FUNCTION: WinVnViewWndProc --------------------------------------------
 *
 *    Window procedure for a Group window, which contains the subjects
 *    of the various articles in a newsgroup.
 *    Note that there may be several different Group windows active;
 *    this routine gets called any time anything happens to any of them.
 */

long FAR PASCAL
WinVnViewWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{

  PAINTSTRUCT ps;               /* paint structure          */

  HDC hDC;                      /* handle to display context */
  RECT clientRect;              /* selection rectangle      */
  TypDoc far *ThisDoc;
  int ih, j;
  long stop;
  long artindex, il;
  BOOL found;
  int CtrlState;
  TypLineID MyLineID;
  _int16 X, Y;
  int OldSel = FALSE;
  TypLine far *LinePtr;
  TypBlock far *BlockPtr;
  TypGroup far *group;
  header_p headers;
  HANDLE header_handle;
  HANDLE thread_handle;

  /* We know what *window* is being acted on, but we must find
   * out which *document* is being acted on.  There's a one-to-one
   * relationship between the two, and we find out which document
   * corresponds to this window by scanning the GroupDocs array.
   */

  for (ih = 0, found = FALSE; !found && ih < MAXGROUPWNDS; ih++) {
    if (GroupDocs[ih].hDocWnd == hWnd) {
      found = TRUE;
      ThisDoc = &(GroupDocs[ih]);
    }
  }

  if (!found) {
    ThisDoc = CommDoc;
  }

  if (StatusBarProc (hWnd, message, wParam, lParam, ThisDoc))
    return(0);

  switch (message) {
  case WM_DESTROY:
    break;
  case WM_SIZE:
    GetClientRect (hWnd, &clientRect);
    ThisDoc->ScXWidth = clientRect.right;
    ThisDoc->ScYHeight = clientRect.bottom;
    ThisDoc->ScYLines = (RectHeight (clientRect) - TopSpace) / LineHeight;
    ThisDoc->ScXChars = (RectWidth (clientRect) - SideSpace) / CharWidth;
    break;

  case WM_COMMAND:
    return (SendMessage (ThisDoc->hWndFrame, message, wParam, lParam));

  case WM_KEYDOWN:
    /* See if this key should be mapped to a scrolling event
     * for which we have programmed the mouse.  If so,
     * construct the appropriate mouse call and call the mouse code.
     */
    LockLine (ThisDoc->hParentBlock, ThisDoc->ParentOffset,
              ThisDoc->ParentLineID, &BlockPtr, &LinePtr);
    group = GetGroup(LinePtr);

    if (group->header_handle && group->total_headers > 0) {
      header_handle = group->header_handle;
      thread_handle = group->thread_handle;
      headers = lock_headers (header_handle, thread_handle);
    }
    else
      headers = NULL;

    switch (wParam) {
    case VK_F5: // This never gets touched if F5 accelerator is defined
        artindex = find_first_selected_art(ThisDoc);
        ThisDoc->ActiveLineID = artindex;
        AdjustTopScByDoc (ThisDoc, ThisDoc->ActiveLineID);
        goto getarticle1;

    case VK_F6:
      NextWindow (ThisDoc->hDocWnd, ThisDoc->DocType);
      break;

    case VK_DOWN:
      if (headers != NULL) {
        /* move one line down from Active line */
        if (ThisDoc->ActiveLineID >= 0 &&
            ThisDoc->ActiveLineID < group->total_headers - 1) {
          SetGroupSelections (ThisDoc, headers, ThisDoc->ActiveLineID + 1);
          AdjustTopScByDoc (ThisDoc, ThisDoc->ActiveLineID);
        }
      }
      break;

    case VK_UP:
      if (headers != NULL) {
        /* move one line up from Active line */
        if (ThisDoc->ActiveLineID > 0) {
          SetGroupSelections (ThisDoc, headers, ThisDoc->ActiveLineID - 1);
          AdjustTopScByDoc (ThisDoc, ThisDoc->ActiveLineID);
        }
      }
      break;

    case VK_HOME:
      if (headers != NULL) {
        SetGroupSelections (ThisDoc, headers, 0);
        AdjustTopScByDoc (ThisDoc, ThisDoc->ActiveLineID);
      }
      break;

    case VK_END:
      if (headers != NULL) {
        SetGroupSelections (ThisDoc, headers, group->total_headers - 1);
        AdjustTopScByDoc (ThisDoc, ThisDoc->ActiveLineID);
      }
      break;

    case VK_NEXT: 
      if (headers != NULL) {
        if (ThisDoc->ActiveLineID >= 0 &&
            ThisDoc->ActiveLineID < group->total_headers - 1) {
          stop = min (ThisDoc->ActiveLineID + (long) ThisDoc->ScYLines,
                      group->total_headers - 1);
          for (il = ThisDoc->ActiveLineID + 1; il < stop; il++);
          SetGroupSelections (ThisDoc, headers, il);
          AdjustTopScByDoc (ThisDoc, ThisDoc->ActiveLineID);
        }
      }
      break;

    case VK_PRIOR:
      if (headers != NULL) {
        if (ThisDoc->ActiveLineID > 0) {
          stop = max (0, ThisDoc->ActiveLineID - (long) ThisDoc->ScYLines);
          for (il = ThisDoc->ActiveLineID - 1; il > stop; il--);
          SetGroupSelections (ThisDoc, headers, il);
          AdjustTopScByDoc (ThisDoc, ThisDoc->ActiveLineID);
        }
      }
      break;


    default:
      CtrlState = GetKeyState (VK_CONTROL) < 0;
      for (j = 0; j < NUMKEYS; j++) {
        if (wParam == key2scroll[j].wVirtKey &&
            CtrlState == key2scroll[j].CtlState) {
          SendMessage (hWnd, key2scroll[j].iMessage,
                       key2scroll[j].wRequest, 0L);
          break;
        }
      }

    }

    break;


  case WM_CHAR:
  {
//  int X, Y;
    /* If any articles are selected, find the first one and view it.  If none selected,
     * Carriage Return means the same as double-clicking
     * on where the cursor is currently pointing.
     */
    if (wParam == '\r') {
//    if (ArtListMultiSelect) {
      artindex = find_first_selected_art(ThisDoc);
      if(artindex == -1)
      {
        artindex = ThisDoc->ActiveLineID;
      }
      goto getarticle1;
//    else {
//      GetCursorPos (&ptCursor);
//      ScreenToClient (hWnd, &ptCursor);
//      X = ptCursor.x;
//      Y = ptCursor.y;
//      goto getarticle;
//
//    }
    }
    else {
    }
  }
    break;

  case WM_RBUTTONDBLCLK:
    /* double right click will mark all up to here as read */

    X = LOWORD (lParam);
    Y = HIWORD (lParam);

    artindex = NewCursorToTextLine (X, Y, ThisDoc);
    if (artindex > -1) {
      article_operation (ThisDoc, artindex, mark_read_to_here);
      InvalidateRect (hWnd, NULL, FALSE);
    }

    break;

  case WM_MBUTTONDOWN:
  case WM_RBUTTONDOWN:
    /* Single middle click or right click will toggle the seen/unseen status 
       of the article */
    DragMouseAction = DRAG_NONE;
    if (!CommBusy || (CommDoc != ThisDoc || CommDecoding)) {
      BOOL status;
      X = LOWORD (lParam);
      Y = HIWORD (lParam);

      artindex = NewCursorToTextLine (X, Y, ThisDoc);
      if (artindex > -1) {
        status = article_operation (ThisDoc, artindex, toggle_read_unread);
        DragMouseAction = status ? SEEN_SELECT : SEEN_DESELECT;
        InvalidateRect (hWnd, NULL, FALSE);
        SetCapture (hWnd);      // release capture on button up

      }
    }
    break;

  case WM_LBUTTONDOWN:
    /*  Clicking the left button on an article name toggles the
     *  selected/not selected status of that group.
     *  Currently selected groups are displayed in reverse video.
     */

    DragMouseAction = DRAG_NONE;
    if (!CommBusy || (CommDoc != ThisDoc || CommDecoding)) {
      BOOL status;
      X = LOWORD (lParam);
      Y = HIWORD (lParam);

      // make this behave more like a multi-select listbox (jlg)
      // if click,       make the line the only one selected
      // if ctrl-click,  add the line to the selection
      // if shift-click, select from previously selected to current
      if (ArtListMultiSelect) {
        if (!(wParam & MK_CONTROL) && !(wParam & MK_SHIFT)) {
          if (ThisDoc->SelectedLines == 1) {
            article_operation (ThisDoc, ThisDoc->ActiveLineID, selected_false);
          }
          else {
            AffectSelected (ThisDoc, DESELECT, NO_COMPARE);
          }
        }
      }

      artindex = NewCursorToTextLine (X, Y, ThisDoc);
      if (artindex > -1) {
        ThisDoc->ActiveLineID = artindex;
        if (ArtListMultiSelect) {
          status = article_operation (ThisDoc, artindex, toggle_selected);

          if (!(wParam & MK_CONTROL) && !(wParam & MK_SHIFT))
            DragMouseAction = status ? DRAG_SELECT : DRAG_DESELECT;
          else
            DragMouseAction = DRAG_NONE;

          if (wParam & MK_SHIFT)
            AffectSelected (ThisDoc, DESELECT, EXTEND);
          else
            ThisDoc->AnchorLineID = artindex;
        }
        else {
          if (MK_SHIFT & wParam) {
            status = article_operation (ThisDoc, artindex, toggle_read_unread);
            DragMouseAction = status ? SEEN_SELECT : SEEN_DESELECT;
          }
          else {
            status = article_operation (ThisDoc, artindex, toggle_selected);
            DragMouseAction = status ? DRAG_SELECT : DRAG_DESELECT;
          }
        }
        SetCapture (hWnd);      // release capture on button up
        InvalidateRect (hWnd, NULL, FALSE);
      }
    }
    else {                      /* let user cheat while retrieving to see headers so far */
      InvalidateRect (hWnd, NULL, FALSE);
    }
    break;

  case WM_LBUTTONUP:
    /*  Letting up on the left button on an article name 
     *  gets us out of Dragging mode   */
/*
    ReleaseCapture ();
    DragMouseAction = DRAG_NONE;
    break;
*/

  case WM_MBUTTONUP:
  case WM_RBUTTONUP:
    /*  Letting up on the middle button on an article name
     *  gets us out of Dragging mode   */
    ReleaseCapture ();
    DragMouseAction = DRAG_NONE;
    break;

  case WM_LBUTTONDBLCLK:
    /*  Double-clicking on an article subject creates an "Article"
     *  window, whose purpose is to display the article.
     */
    if (TestCommBusy (hWnd, "Can't Retrieve Article"))
      break;

    X = LOWORD (lParam);
    Y = HIWORD (lParam);
// getarticle:;

    artindex = NewCursorToTextLine (X, Y, ThisDoc);

  getarticle1:;

    if (artindex > -1) {
      if (!ArtListMultiSelect)
        article_operation (ThisDoc, artindex, selected_false);
      ViewArticle (ThisDoc, artindex, NO_REUSE, SHOW, NO_ID);
    }

    break;

  case WM_MOUSEMOVE:
    /*  Code to drag the mouse and change the select/not selected
     *  status of that group.
     */

    if ((DragMouseAction != DRAG_NONE) &&
        (!CommBusy || CommDoc != ThisDoc || CommDecoding)) {
      X = LOWORD (lParam);
      Y = HIWORD (lParam);

      artindex = NewCursorToTextLine (X, Y, ThisDoc);
      if (artindex > -1) {
        if (ArtListMultiSelect && 
            DragMouseAction != SEEN_SELECT && DragMouseAction != SEEN_DESELECT) {
          ThisDoc->ActiveLineID = artindex;
          AffectSelectedOnScreen (ThisDoc, DESELECT, EXTEND);
        }
        else {
          switch (DragMouseAction) {
          case DRAG_SELECT:
            article_operation (ThisDoc, artindex, selected_true);
            InvalidateRect (hWnd, NULL, FALSE);
            break;

          case DRAG_DESELECT:
            article_operation (ThisDoc, artindex, selected_false);
            InvalidateRect (hWnd, NULL, FALSE);
            break;

          case SEEN_SELECT:
            article_operation (ThisDoc, artindex, seen_true);
            InvalidateRect (hWnd, NULL, FALSE);
            break;

          case SEEN_DESELECT:
            article_operation (ThisDoc, artindex, seen_false);
            InvalidateRect (hWnd, NULL, FALSE);
            break;
          }

        }
      }
    }

    break;

  case WM_VSCROLL:
    NewScrollIt (ThisDoc, wParam, lParam);
    break;

  case WM_HSCROLL:
    HScrollIt (ThisDoc, wParam, lParam);
    break;

  case WM_PAINT:
    {
      HANDLE hBlock;
      SIZE sz;
      int X, Y, MyLen;
      unsigned int Offset;
      int VertLines, HorzChars;
      int EndofDoc = FALSE;
      int RangeHigh, CurPos;
      int indicatorwidth, Xtext;
      char indicator;
      TypLineID artindex;
      long int artnum;
      TypBlock far *NetBlockPtr;
      TypLine far *NetLinePtr;
      TypGroup far *group;
      header_p headers;
      header_p header;
      HANDLE header_handle;
      HANDLE thread_handle;
      char scratch_line[MAXINTERNALLINE];
      char date_string[80];
      unsigned long OldHighestSeen;
      COLORREF MyColors[5], MyBack[5];
      RECT aRect;
      int MyColorMask = 0;
      int PrevColorMask = MyColorMask;
      HBRUSH hOldBrush;
      HANDLE hOldFont;

      /* MyColors and MyBack are arrays of colors used to display text
       * foreground and background.
       * The ColorMask variables are indices into these arrays.
       * We set and clear bits in these indices depending upon
       * whether the article has been selected, seen or killed.
       */

      hDC = BeginPaint (hWnd, &ps);
      GetClientRect (hWnd, &clientRect);
      hOldFont = SelectObject (hDC, hListFont);

      VertLines = (ThisDoc->ScYLines > (ThisDoc->TotalLines - ThisDoc->TopLineOrd))
        ? (ThisDoc->TotalLines - ThisDoc->TopLineOrd) : ThisDoc->ScYLines;

      HorzChars = ThisDoc->ScXChars;

      MyColors[0] = ArticleUnSeenColor;     // unseen/unselected
      MyBack[0] = ListBackgroundColor;

      MyColors[1] = ArticleSeenColor;       // seen/unselected
      MyBack[1] = ListBackgroundColor;

      MyColors[4] = ArticleKilledColor; // marked seen/unselected
      MyBack[4] = ListBackgroundColor;

      if (UseInverseSelections) {
        MyColors[2] = ListBackgroundColor;  // unseen/selected

        MyBack[2] = ArticleUnSeenColor;
        MyColors[3] = ListBackgroundColor;  // [marked] seen/selected

        MyBack[3] = ArticleSeenColor;
      }
      else {
        if (ArticleUnSeenColor == RGB (0, 0, 0) ||
            ListBackgroundColor == RGB (0, 0, 0) && IsBright (ArticleUnSeenColor))
          MyColors[2] = ListBackgroundColor;    // unseen/selected

        else
          MyColors[2] = ArticleUnSeenColor;

        if (ArticleSeenColor == RGB (0, 0, 0) ||
        ListBackgroundColor == RGB (0, 0, 0) && IsBright (ArticleSeenColor))
          MyColors[3] = ListBackgroundColor;    // seen/selected

        else
          MyColors[3] = ArticleSeenColor;

        if (ListBackgroundColor == RGB (0, 0, 0))
          MyBack[2] = RGB (200, 200, 200);
        else
          MyBack[2] = RGB (0, 0, 0);
        MyBack[3] = MyBack[2];
      }

      SetTextColor (hDC, MyColors[MyColorMask]);
      SetBkColor (hDC, MyBack[MyColorMask]);

      /* Update the scroll bar thumb position.                 */

      //RangeHigh = ThisDoc->TotalLines - VertLines;
      RangeHigh = ThisDoc->TotalLines - ThisDoc->ScYLines;
      if (RangeHigh < 0) {
        RangeHigh = 0;
        ThisDoc->TopLineOrd = 0;
        VertLines = ThisDoc->TotalLines;
      }
      CurPos = ThisDoc->TopLineOrd;
      if (CurPos < 0)
        CurPos = 0;

      SetScrollRange (hWnd, SB_VERT, 0, RangeHigh,
         ThisDoc->ThumbTracking ? FALSE : TRUE);
      /* thumb pos is updated at end of thumb track */
      if (!ThisDoc->ThumbTracking) {
        SetScrollPos (hWnd, SB_VERT, CurPos, TRUE);
      }

      RangeHigh = ThisDoc->LongestLine - ThisDoc->ScXChars;
      if (RangeHigh < 0) {
        RangeHigh = 0;
        ThisDoc->ScXOffset = 0;
      }
      SetScrollRange (hWnd, SB_HORZ, 0, RangeHigh, FALSE);
      SetScrollPos (hWnd, SB_HORZ, ThisDoc->ScXOffset, TRUE);

      GetTextExtentPoint (hDC, "s 99999 ", 8, &sz);
      indicatorwidth = sz.cx;

      X = SideSpace - ThisDoc->ScXOffset * (CharWidth + 2);
      Xtext = X + indicatorwidth;
      Y = StartPen;

      /* Now paint this stuff on the screen.  */
      artindex = ThisDoc->TopLineOrd;
      if (ThisDoc->ActiveLines) {
        LockLine (ThisDoc->hParentBlock, ThisDoc->ParentOffset, ThisDoc->ParentLineID,
                  &NetBlockPtr, &NetLinePtr);
        group = GetGroup(NetLinePtr);
        header_handle = group->header_handle;
        thread_handle = group->thread_handle;

        if (header_handle)
          headers = lock_headers (header_handle, thread_handle);
        else
          break;                /* this can happen during Update */

        OldHighestSeen = group->HighestPrevSeen;
        UnlockLine (NetBlockPtr, NetLinePtr, &hBlock, &Offset, &MyLineID);
        do {
          header = header_elt (headers, artindex);
          artnum = header->number;
          indicator = ' ';
      if(ART_UNSEEN == header->Seen)
      {
            if (OldHighestSeen && (header->number > OldHighestSeen))
          indicator = 'n';
      }
      else if(ART_SEEN & header->Seen)
      {
        indicator = 's';
      }
          else if(ART_KILLED & header->Seen)
          {
        indicator = 'k';
          }

          if (ThisDoc->FindOffset == (unsigned) artindex)
            indicator = '>';

          _snprintf (scratch_line, MAXINTERNALLINE, "%c%5Flu %-5.5Fs %-18.18Fs %4Fd %-*s%-Fs ",
                   indicator,
                   header->number,
                   StringDate (date_string, header->date),
                   header->from,
                   header->lines,
                   header->thread_depth * 2,
                   "",
                   (ThreadFullSubject || !(header->thread_depth))
                   ? header->subject
                   : ThreadDepthIndicator
            );

          MyLen = lstrlen (scratch_line);

          /* Figure out the color of this line. */
          if (ART_SEENORKILLED & header->Seen) {
            if (header->Selected)
              MyColorMask = 3;
            else
              MyColorMask = (ART_KILLED == header->Seen) ? 4 : 1;
          }
          else {
            if (header->Selected)
              MyColorMask = 2;
            else
              MyColorMask = 0;
          }


          if (MyColorMask != PrevColorMask) {
            SetTextColor (hDC, MyColors[MyColorMask]);
            SetBkColor (hDC, MyBack[MyColorMask]);
            PrevColorMask = MyColorMask;
          }

          /* Now write out the line. */
          SetRect (&aRect, 0, Y, clientRect.right, Y + LineHeight);

          ExtTextOut (hDC, X, Y, ETO_OPAQUE | ETO_CLIPPED, &aRect,
                      scratch_line, MyLen, (LPINT) NULL);
//          if(ART_DRAWFRAME & header->Seen)
//          {
//            FrameRect(hDC, &aRect, (HBRUSH) GetStockObject(GRAY_BRUSH));
//          }
          if (ThisDoc->ActiveLineID == -1L)
            ThisDoc->ActiveLineID = artindex;
          if (ThisDoc->ActiveLineID == artindex && ArtListMultiSelect) {
            DrawFocusRect (hDC, &aRect);
          }

          Y += LineHeight;
          artindex++;

        }
        while (--VertLines > 0);

        if (header_handle)
          unlock_headers (header_handle, thread_handle);
      }
      SelectObject (hDC, hOldFont);

      /* We've reached the end of the data to be displayed     */
      /* on this window.  If there's more screen real estate   */
      /* left, just blank it out.                              */

      hOldBrush = (HBRUSH) SelectObject (hDC, hListBackgroundBrush);
      PatBlt (hDC, 0, Y, clientRect.right - 1, clientRect.bottom - Y, PATCOPY);
      PatBlt (hDC, 0, 0, clientRect.right - 1, StartPen, PATCOPY);
      SelectObject (hDC, hOldBrush);
      EndPaint (hWnd, &ps);
      break;
    }

  default:
    return (DefWindowProc (hWnd, message, wParam, lParam));
  }
  return (0);
}

/*------------ CloseGroupWnd ------------------------------
 *
 *  Make sure this Wnd is not the active Comm window, then destroy it
 *  also make sure no child articles are active comm... jsc 11/1/94 
 */
void 
CloseGroupWnd (HWND hWnd, TypDoc far * ThisDoc)
{
  register int i;
  BOOL stop = FALSE;

  if (CommBusy) {
    stop = (ThisDoc == CommDoc);
    for (i = 0; !stop && i < MAXARTICLEWNDS; i++) {
      if (ArticleDocs[i].InUse && CommDoc == &ArticleDocs[i] &&
          ArticleDocs[i].ParentDoc == ThisDoc) {
        stop = TRUE;
      }
    }
  }

  if (stop)
    MessageBox (hWnd,
                "Please wait until group activity is complete",
                "Cannot close group window", MB_OK | MB_ICONSTOP);
  else {
    SetHandleBkBrush (ThisDoc->hDocWnd, (HBRUSH) GetStockObject (WHITE_BRUSH));
    if (SaveNewsrcOnClose && NewsrcDirty) WriteNewsrc ();
    DestroyWindow (hWnd);
    /* invalidate main window, so '*' will be updated, if appropriate */
    InvalidateRect (NetDoc.hDocWnd, NULL, FALSE);
  }
}

void 
SetGroupMailMenu (TypDoc far * DocPtr)
{
  HMENU hMenu, hSubMenu;

  hMenu = GetMenu (DocPtr->hWndFrame);
  hSubMenu = GetSubMenu (hMenu, 0);     // Articles menu

  EnableMenuItem (hSubMenu, IDM_MAIL, MailCtrl.enableMail);
  SendMessage (DocPtr->hWndTB, TB_ENABLEBUTTON, IDM_MAIL,
               (MailCtrl.enableMail == MF_ENABLED));
}

/*------------ SetGroupMenus ------------------------------
 * dis/enable menu items which depend on group list being completely 
 * retrieved
 */
void
SetGroupMenus (TypDoc far * DocPtr, int enable)
{
  HMENU hMenu, hSubMenu;
  UINT mode;

  mode = (enable == ENABLE) ? ENABLE_MENU : DISABLE_MENU;

  hMenu = GetMenu (DocPtr->hWndFrame);
  hSubMenu = GetSubMenu (hMenu, 0);     // Articles menu

  EnableMenuItem (hSubMenu, IDM_SELECT_ALL, mode);
  EnableMenuItem (hSubMenu, IDM_SELECT_MATCH, mode);
  EnableMenuItem (hSubMenu, IDM_DESELECT_MATCH, mode);
  EnableMenuItem (hSubMenu, IDM_MARK_ALL, mode);
  EnableMenuItem (hSubMenu, IDV_EXIT, mode);

  // mail menu ignores this enable flag - it depends solely
  // on transport availability
  SetGroupMailMenu (DocPtr);

  hSubMenu = GetSubMenu (hMenu, 1);     // Sort menu

  EnableMenuItem (hSubMenu, IDM_SORT_DATE, mode);
  EnableMenuItem (hSubMenu, IDM_SORT_SUBJECT, mode);
  EnableMenuItem (hSubMenu, IDM_SORT_LINES, mode);
//  EnableMenuItem (hSubMenu, IDM_SORT_THREADS, mode);  // handled in INITMENU
  EnableMenuItem (hSubMenu, IDM_SORT_ARTNUM, mode);
  EnableMenuItem (hSubMenu, IDM_SORT_FROM, mode);

  hSubMenu = GetSubMenu (hMenu, 2);     // Search menu

  EnableMenuItem (hSubMenu, IDM_FIND, mode);
  EnableMenuItem (hSubMenu, IDM_FIND_NEXT_SAME, mode);

  // set toolbar buttons
  mode = (enable == ENABLE) ? TRUE : FALSE;
  SendMessage (DocPtr->hWndTB, TB_ENABLEBUTTON, IDM_FIND, mode);
  SendMessage (DocPtr->hWndTB, TB_ENABLEBUTTON, IDM_FIND_NEXT_SAME, mode);
  SendMessage (DocPtr->hWndTB, TB_ENABLEBUTTON, IDM_POST, mode);
  SendMessage (DocPtr->hWndTB, TB_ENABLEBUTTON, IDM_MARK_ALL, mode);
  SendMessage (DocPtr->hWndTB, TB_ENABLEBUTTON, IDV_EXIT, mode);

  // initmenu handles save/decode selected, as well as deselect all
  SendMessage (DocPtr->hWndFrame, WM_MYINITMENU, (WPARAM) 0, (LPARAM) 0);
}

/*------------ SetMenusForMultiArticleOperation ----------
 *  (jsc) killer function name, huh?!
 * During multi-article op (i.e. Save selected, Decode selected)
 * we don't want the user to do any of these things
 */
void
SetMenusForMultiArticleOperation (TypDoc far * DocPtr, int enable)
{
  HMENU hMenu, hSubMenu;
  UINT mode;

  mode = (enable == ENABLE) ? ENABLE_MENU : DISABLE_MENU;

  hMenu = GetMenu (DocPtr->hWndFrame);
  hSubMenu = GetSubMenu (hMenu, 0);     // Articles menu

  EnableMenuItem (hSubMenu, IDM_SAVE_SELECTED, mode);
  EnableMenuItem (hSubMenu, IDM_DECODE_SELECTED, mode);
//  EnableMenuItem (hSubMenu, IDM_READ_SELECTED, mode);
  EnableMenuItem (hSubMenu, IDV_EXIT, mode);
  EnableMenuItem (hSubMenu, IDM_MARK_ALL, mode);

  hSubMenu = GetSubMenu (hMenu, 1);     // Sort menu

  EnableMenuItem (hSubMenu, IDM_SORT_DATE, mode);
  EnableMenuItem (hSubMenu, IDM_SORT_SUBJECT, mode);
  EnableMenuItem (hSubMenu, IDM_SORT_LINES, mode);
  EnableMenuItem (hSubMenu, IDM_SORT_THREADS, mode);
  EnableMenuItem (hSubMenu, IDM_SORT_THREADSUB, mode);
  EnableMenuItem (hSubMenu, IDM_SORT_ARTNUM, mode);
  EnableMenuItem (hSubMenu, IDM_SORT_FROM, mode);
  // set toolbar buttons
  if (enable == ENABLE)
    mode = TRUE;
  else
    mode = FALSE;
  SendMessage (DocPtr->hWndTB, TB_ENABLEBUTTON, IDM_SAVE_SELECTED, mode);
  SendMessage (DocPtr->hWndTB, TB_ENABLEBUTTON, IDM_DECODE_SELECTED, mode);
  SendMessage (DocPtr->hWndTB, TB_ENABLEBUTTON, IDV_EXIT, mode);
  SendMessage (DocPtr->hWndTB, TB_ENABLEBUTTON, IDM_MARK_ALL, mode);
}

long find_first_selected_art(TypDoc far * Doc)
{
  long retval = -1;
  HANDLE hBlock;
  unsigned int Offset;
  TypLineID MyLineID;
  TypBlock far *BlockPtr;
  TypLine far *LinePtr;
  TypGroup *GroupDoc;
  HANDLE header_handle;
  HANDLE thread_handle;
  header_p headers;
  header_p header;
  TypLineID artindex;

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

  if (header_handle)
  {
    thread_handle = GroupDoc->thread_handle;
    headers = lock_headers (header_handle, thread_handle);

    artindex = -1;

    while((++artindex < GroupDoc->total_headers) && retval < 0)
    {
        header = header_elt (headers, artindex);
        if(header->Selected)
          retval = artindex;
    }
    unlock_headers (header_handle, thread_handle);
    UnlockLine (BlockPtr, LinePtr, &hBlock, &Offset, &MyLineID);
  }
  return (retval);
}


/*----------------------------------------------------------------------------
 * article header manipulation routines
 */
BOOL
SelectHeader (TypDoc far * Doc, header_p header, BOOL value)
{
  int was = Doc->SelectedLines;
  if (header->Selected != value) {
    header->Selected = value;
    if (value)
      (Doc->SelectedLines)++;
    else
      (Doc->SelectedLines)--;
  }
  if ((was == 0 && value) || (was != 0 && Doc->SelectedLines == 0)) {
    /* this is a kludge to update the toolbar */
    SendMessage (Doc->hWndFrame, WM_MYINITMENU, (WPARAM) 0, (LPARAM) 0);
  }

  return value;
}

extern "C" BOOL
article_operation (TypDoc far * Doc, long artindex,
                   BOOL (*art_fun) (TypDoc far * Doc, header_p headers,
                                    TypGroup * group, long artindex))
{
  HANDLE hBlock;
  unsigned int Offset;
  TypLineID MyLineID;
  TypBlock far *BlockPtr;
  TypLine far *LinePtr;
  TypGroup *GroupDoc;
  HANDLE header_handle;
  HANDLE thread_handle;
  BOOL result = FALSE;
  header_p headers;

  LockLine (Doc->hParentBlock, Doc->ParentOffset,
            Doc->ParentLineID, &BlockPtr, &LinePtr);
  GroupDoc = GetGroup(LinePtr);
  header_handle = GroupDoc->header_handle;
  NewsrcDirty = TRUE;

  if (header_handle) {
    thread_handle = GroupDoc->thread_handle;
    headers = lock_headers (header_handle, thread_handle);
    result = art_fun (Doc, headers, GroupDoc, artindex);

    unlock_headers (header_handle, thread_handle);
    UnlockLine (BlockPtr, LinePtr, &hBlock, &Offset, &MyLineID);
  }
  return (result);
}

BOOL
toggle_read_unread (TypDoc far * Doc, header_p headers,
                    TypGroup * GroupDoc,
                    long artindex)
{

  /* only try if in range.  All this work for these two lines 8^) */
  if ((artindex > -1) && (artindex < GroupDoc->total_headers)) {
    header_p header = header_elt (headers, artindex);
    header->Seen = (header->Seen ? ART_UNSEEN : ART_SEEN);
    return (header->Seen);
  }
  return (FALSE);
}

BOOL
mark_read_to_here (TypDoc far * Doc, header_p headers,
                   TypGroup * GroupDoc,
                   long artindex)
{
  header_p header;

  /* only try if in range.  All this work for these two lines 8^) */
  if ((artindex > -1) && (artindex < GroupDoc->total_headers)) {
    int i;
    for (i = 0; i <= artindex; i++) {
      header = header_elt (headers, i);
      header->Seen = ART_SEEN;
      // added by jlg to get rid of erroneous '*' in group window
      if (header->number > GroupDoc->HighestPrevSeen)
        GroupDoc->HighestPrevSeen = header->number;
    }
    return (TRUE);
  }
  return (FALSE);
}

BOOL
mark_read_all (TypDoc far * Doc, header_p headers,
               TypGroup * GroupDoc,
               long artindex)
{
  header_p header;

  int i;
  for (i = 0; i < GroupDoc->total_headers; i++) {
    header = header_elt (headers, i);
    header->Seen = ART_SEEN;
  }
  GroupDoc->HighestPrevSeen = GroupDoc->ServerLast;
  return (TRUE);
}

BOOL
mark_read_selected (TypDoc far * Doc, header_p headers,
               TypGroup * GroupDoc,
               long artindex)
{
  header_p header;

  int i;
  for (i = 0; i < GroupDoc->total_headers; i++) {
    header = header_elt (headers, i);
    if (header->Selected) {
        header->Seen = ART_SEEN;
    }
  }
  return (TRUE);
}

BOOL
mark_selected_read_articles (TypDoc far * Doc, header_p headers,
               TypGroup * GroupDoc,
               long artindex)
{
  header_p header;

  int i;
  for (i = 0; i < GroupDoc->total_headers; i++) {
    header = header_elt (headers, i);
    if (header->Seen) { 
        SelectHeader (Doc, header, TRUE);
    }
  }
  return (TRUE);
}

BOOL
mark_selected_unread_articles (TypDoc far * Doc, header_p headers,
               TypGroup * GroupDoc,
               long artindex)
{
  header_p header;

  int i;
  for (i = 0; i < GroupDoc->total_headers; i++) {
    header = header_elt (headers, i);
    if (header->Seen == ART_UNSEEN) {
        SelectHeader (Doc, header, TRUE);
    }
  }
  return (TRUE);
}

BOOL
mark_deselected_unread_articles (TypDoc far * Doc, header_p headers,
               TypGroup * GroupDoc,
               long artindex)
{
  header_p header;

  int i;
  for (i = 0; i < GroupDoc->total_headers; i++) {
    header = header_elt (headers, i);
    if (header->Seen == ART_UNSEEN) {
            SelectHeader (Doc, header, FALSE);
    }
  }
  return (TRUE);
}

BOOL
mark_deselected_read_articles (TypDoc far * Doc, header_p headers,
               TypGroup * GroupDoc,
               long artindex)
{
  header_p header;

  int i;
  for (i = 0; i < GroupDoc->total_headers; i++) {
    header = header_elt (headers, i);
    if (header->Seen) {
        SelectHeader (Doc, header, FALSE);
    }
  }
  return (TRUE);
}

BOOL
mark_unread_selected (TypDoc far * Doc, header_p headers,
               TypGroup * GroupDoc,
               long artindex)
{
  header_p header;

  int i;
  for (i = 0; i < GroupDoc->total_headers; i++) {
    header = header_elt (headers, i);
    if (header->Selected) {
        header->Seen = ART_UNSEEN;
    }
  }
  return (TRUE);
}

BOOL
toggle_selected (TypDoc far * Doc, header_p headers,
                 TypGroup * GroupDoc,
                 long artindex)
{

  /* only try if in range.  All this work for these two lines 8^) */
  if ((artindex > -1) && (artindex < GroupDoc->total_headers)) {
    header_p header = header_elt (headers, artindex);
    SelectHeader (Doc, header, header->Selected ? FALSE : TRUE);
    return (header->Selected);
  }
  return (FALSE);
}

BOOL
seen_false (TypDoc far * Doc, header_p headers,
            TypGroup * GroupDoc,
            long artindex)
{

  /* only try if in range. */
  if ((artindex > -1) && (artindex < GroupDoc->total_headers)) {
    header_p header = header_elt (headers, artindex);
    header->Seen = ART_UNSEEN;
    return (TRUE);
  }
  return (FALSE);
}

BOOL
seen_true (TypDoc far * Doc, header_p headers,
           TypGroup * GroupDoc,
           long artindex)
{

  /* only try if in range. */
  if ((artindex > -1) && (artindex < GroupDoc->total_headers)) {
    header_p header = header_elt (headers, artindex);
    header->Seen = ART_SEEN;
    return (TRUE);
  }
  return (FALSE);
}

BOOL
selected_true (TypDoc far * Doc, header_p headers,
               TypGroup * GroupDoc,
               long artindex)
{

  /* only try if in range. */
  if ((artindex > -1) && (artindex < GroupDoc->total_headers)) {
    header_p header = header_elt (headers, artindex);
    SelectHeader (Doc, header, TRUE);
    return (TRUE);
  }
  return (FALSE);
}

extern "C" BOOL
selected_false (TypDoc far * Doc, header_p headers,
                TypGroup * GroupDoc,
                long artindex)
{

  /* only try if in range. */
  if ((artindex > -1) && (artindex < GroupDoc->total_headers)) {
    header_p header = header_elt (headers, artindex);
    SelectHeader (Doc, header, FALSE);
    return (TRUE);
  }
  return (FALSE);
}


/*-- function ViewArticle -------------------------------------------------
 *
 *  View a given article.   Either create a new window for it or
 *  recycle an existing window.
 *  This function requests an article from the server, so there
 *  must not already be a transaction in progress.
 *  Can retrieve an article by artindex from the group header list,
 *  or any arbitrary article ID.
 *
 *    Entry    Doc            points to the document for this group.
 *             artindex       index into header array for this group.
 *             reuse          is TRUE if we ought to reuse the
 *                            currently active article window (if any).
 *             showArt        is TRUE to retrieve maximized (shown)
 *                            is FALSE to retrieve minimized
 *             articleId      is NO_ID if wish to retrieve artindex->number
 *                            is some art ID if wish to retrieve article by ID
 */
void
ViewArticle (TypDoc far *Doc, long artindex, BOOL reuse, BOOL showArt, char far *articleId)
{
  TypGroup far *GroupDoc;
  BOOL newdoc;
  BOOL found;
  int docnum;
  TypBlock far *BlockPtr;
  TypLine far *LinePtr;
  HWND hWndArt;
  int x, y, width, height, num;
  char mybuf[MAXINTERNALLINE];
  char far *lpsz;
  HWND hWndGroup = Doc->hDocWnd;
  HANDLE header_handle;
  HANDLE thread_handle;
  header_p headers;
  header_p header;
  HMENU hMenu, hSubMenu;

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

    Doc->ActiveLineID = artindex;
    GroupDoc = GetGroup(LinePtr);

    if (articleId == NO_ID)     /* if retrieving artindex, only try if in range */
        if ((artindex < 0) || (artindex >= GroupDoc->total_headers)){
        UnlockLine (BlockPtr, LinePtr, &Doc->hParentBlock,
                       &Doc->ParentOffset, &Doc->ParentLineID);
        return;
        }

    header_handle = GroupDoc->header_handle;
    thread_handle = GroupDoc->thread_handle;
    headers = lock_headers (header_handle, thread_handle);
    header = header_elt (headers, artindex); 
             
    if ((articleId == NO_ID) &&
        (header->ArtDoc != NULL) &&
        (header->ArtDoc->CountedLines == header->lines)){ 
      
        /* We already have a document containing the article */
        /* so just activate it.                */

        if (showArt) {
        setArticleFocus (header->ArtDoc->hWndFrame);
        ShowWindow (header->ArtDoc->hWndFrame, SW_SHOWNORMAL);
        }
        else
        ShowWindow (header->ArtDoc->hWndFrame, SW_SHOWMINNOACTIVE);

        InvalidateRect (header->ArtDoc->hWndFrame, NULL, FALSE);
        InvalidateRect (header->ArtDoc->hDocWnd, NULL, FALSE);
        SendMessage (hWndGroup, (UINT) WM_COMMAND, (WPARAM) ID_ARTICLE_RETRIEVE_COMPLETE, 0L);
        goto endit;
    } 

    if (TestCommBusy (hWndGroup, "Can't Retrieve Article"))
        goto endit;

    newdoc = FALSE;

    if ((NewArticleWindow && !reuse) || !ActiveArticleDoc || 
                !(ActiveArticleDoc->InUse)) {
        found = FALSE;
        for (docnum = 0; docnum < MAXARTICLEWNDS; docnum++) {
        if (!ArticleDocs[docnum].InUse) {
            found = TRUE;
            newdoc = TRUE;
            CommDoc = &(ArticleDocs[docnum]);
            break;
        }
        }
        if (!found) {
        MessageBox (hWndGroup,
                      "You have too many article windows active;\n"
                      "Close one or uncheck the option"
                     "'New Window for each Article'.",
                     "Can't open new window", MB_OK | MB_ICONASTERISK);
        goto endit;
        }
    }
    else {
        /* Must reuse old window for this article.         */
        ActiveArticleDoc->LongestLine = 0;
        ActiveArticleDoc->ScXOffset = 0;
        ActiveArticleDoc->TextSelected = FALSE;
        SetArticleMenus (ActiveArticleDoc, DISABLE);
        CommDoc = ActiveArticleDoc;
        /* sever the article/artindex connection */
        if (CommDoc->ParentDoc == Doc)
            (header_elt (headers, CommDoc->ParentOffset))->ArtDoc = (TypDoc far *) NULL;
        else {
            SeverArticleParent(CommDoc);
        }

    /* clear out old doc and clear its window*/
        FreeDoc (CommDoc);
        InvalidateRect(CommDoc->hDocWnd, NULL, FALSE);
    }

    header->Seen |= ART_SEEN;
    header->Selected &= ~ART_SELECTED;
    NewsrcDirty = TRUE;
    InvalidateRect (hWndGroup, NULL, FALSE);

    if (articleId == NO_ID)
        lpsz = (char far *) header->subject;
    else
        lpsz = articleId;

    strcpy (mybuf, "Retrieving \"");
    lstrcat (mybuf, lpsz);
    lstrcat (mybuf, "\"");

  if (newdoc) {
    char poschars[MAXINTERNALLINE];

    /* Compute default screen position. */
    num = (WrapIncomingArticleText) ? WrapIncomingArticleTextLength : 88;
    num = min (max (15, WrapIncomingArticleTextLength), 88);
    if (xScreen > num * ArtCharWidth) {
      width = num * ArtCharWidth;
    }
    else {
      width = xScreen - 1 * ArtCharWidth;
    }
    x = xScreen - width;
    y = (int) (yScreen * 3 / 8);
    height = (int) (yScreen * 5 / 8) - (1 * ArtLineHeight);

    /* If the screen position has been saved, use that instead. */
    GetPrivateProfileString (ARTICLE, "ArticleWindowPos", "!",
                             poschars, MAXINTERNALLINE, szAppProFile);
    if (poschars[0] != '!') {
      sscanf (poschars, "%d,%d,%d,%d", &x, &y, &width, &height);
    }

    hWndArt = CreateWindow ("WinVnArtFrame",
                            mybuf,
                            WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN,
                            x + (docnum * ArtCharWidth),
                            y + (docnum * ArtLineHeight),
                            width,
                            height,
                            NULL,
                            NULL,
                            hInst,
                            NULL);

    if (!hWndArt)
      return;                   /* ??? */
  }
  else {
    hWndArt = CommDoc->hWndFrame;
    SetWindowText (CommDoc->hWndFrame, mybuf);
  }

  /*  Now that we have created the window, create the corresponding
   *  document, and make the new window active.
   */

  InitDoc (CommDoc, hWndArt, Doc, DOCTYPE_ARTICLE);

  CommDoc->InUse = TRUE;
  CommDoc->LastSeenLineID = artindex;   /* Keep an index with the article */

  if (showArt) {
    setArticleFocus (hWndArt);
    ShowWindow (hWndArt, newdoc?SW_SHOWNORMAL:SW_SHOW);
  }
  else
    ShowWindow (hWndArt, SW_SHOWMINNOACTIVE);

  if (articleId == NO_ID) {
    header->ArtDoc = CommDoc;
  } else {
    header->ArtDoc = (TypDoc *)NULL;
  }
  CommDoc->ParentOffset = (int) artindex;

  /* stash Lines: header in CountedLines */
  CommDoc->CountedLines = header->lines; 
  InvalidateRect (hWndArt, NULL, FALSE);
  UpdateWindow (hWndArt);

  if (articleId == NO_ID) {
//  if (header->number > GroupDoc->HighestPrevSeen) {
//    GroupDoc->HighestPrevSeen = header->number;
//  }
    sprintf (mybuf, "%ld", header->number);
    InitiateReceiveArticle (Doc, mybuf);
  }
  else
    InitiateReceiveArticle (Doc, articleId);

/* decide if we are the author and allowed to cancel this post */
  hMenu = GetMenu (hWndArt); 
  hSubMenu = GetSubMenu(hMenu,0);
  if ((stricmp (header->from, UserName) == 0) ||
      (stricmp (header->from, MailAddress) == 0) ||
      (stricmp (header->from, ReplyTo) == 0)) 
     EnableMenuItem(hSubMenu,IDM_CANCELART,ENABLE_MENU);
  else
     EnableMenuItem(hSubMenu,IDM_CANCELART,DISABLE_MENU);


endit:
  UnlockLine (BlockPtr, LinePtr, &Doc->hParentBlock, &Doc->ParentOffset, &Doc->ParentLineID);
  unlock_headers (header_handle, thread_handle);
 }
}

void
setArticleFocus (HWND hWndArt)
{
  SetArticleRot13Mode (hWndArt, FALSE);
  SetActiveWindow (hWndArt);
  SetFocus (hWndArt);
}

void
InitiateReceiveArticle (TypDoc far * Doc, char far * articleId)
{
  TypBlock far *BlockPtr;
  TypLine far *LinePtr;
  char far *lpszGroupName;
  char mybuf[MAXINTERNALLINE];
  HANDLE hBlock;
  unsigned int Offset;
  TypLineID MyLineID;

  CommLinePtr = CommLineIn;
  CommBusy = TRUE;
  CommState = ST_ARTICLE_RESP;

  // kludge to update the toolbar (deactivate the comm stuff)
  if (!IsMinimized(Doc->hWndFrame)) {
    SendMessage (Doc->hWndFrame, WM_MYINITMENU, 0, 0L);
  }
  
  if (!IsMinimized(CommDoc->hWndFrame)) {
    SendMessage (CommDoc->hWndFrame, WM_MYINITMENU, 0, 0L);
  }

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

  /* If we're not already in this group on the server,
   * send out a GROUP command for this window so we get back
   * into the right Group.
   */
    lpszGroupName = GetGroupName(LinePtr);
    if (lstrcmp (CurrentGroup, lpszGroupName)) {
      CommState = ST_GROUP_REJOIN;
      strcpy (mybuf, "GROUP ");
      lstrcat (mybuf, lpszGroupName);
      mylstrncpy (CurrentGroup, lpszGroupName, MAXGROUPNAME);
      PutCommLine (mybuf);
    }

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

    sprintf (mybuf, "ARTICLE %s", articleId);
    PutCommLine (mybuf);
  }
}


/* Clear the pointer in the line for this article in the   */
/* group  document.  This pointer currently points       */
/* to the current document, which we are wiping out      */
/* with the destruction of this window.                  */
void SeverArticleParent(TypDoc *doc)
{
  TypGroup far *GroupDoc;
  TypBlock far *BlockPtr;
  TypLine far *LinePtr;
  HANDLE header_handle;
  HANDLE thread_handle;
  HANDLE hBlock;
  header_p headers;
  TypLineID MyLineID;
  unsigned int Offset;

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

      GroupDoc = GetGroup(LinePtr);
      header_handle = GroupDoc->header_handle;
      thread_handle = GroupDoc->thread_handle;
      headers = lock_headers (header_handle, thread_handle);

      (header_elt (headers, doc->LastSeenLineID))->ArtDoc = (TypDoc *) NULL;
      UnlockLine (BlockPtr, LinePtr, &hBlock, &Offset, &MyLineID);
      unlock_headers (header_handle, thread_handle);
    }
}

/*-- Function UnlinkArtsInGroup ---------------------------------------
 *
 *  Modify all the article documents and all the article windows currently
 *  associated with a group so that none of them points to that group.
 *  Used when the group window is going away or is being recycled.
 *
 *    Entry    GroupDoc    points to the document to which references
 *                         should be eliminated.
 */
void
UnlinkArtsInGroup (TypDoc far *GroupDoc)
{
  int iart;

  for (iart = 0; iart < MAXARTICLEWNDS; iart++) {
    if (ArticleDocs[iart].InUse && ArticleDocs[iart].ParentDoc == GroupDoc) {
      ArticleDocs[iart].ParentDoc = (TypDoc far *) NULL;
      ArticleDocs[iart].hParentBlock = 0;
    }
  }
}

/*--- function UpdateSeenArts -------------------------------------------
 *
 *  Given a Group document, update the TypGroup line for
 *  that document in the Net document with respect to which
 *  articles have been seen.
 *  This routine would typically be called just before a Group document
 *  is going to be destroyed or erased.  That would be the time to
 *  take the information in the TypArticle structures of each line
 *  in the document and transfer it to the line in the NetDoc document
 *  corresponding to this group.
 *
 *  This routine has to take information of the form:
 *    123:Unseen;  124:Seen; 125:Unseen; 126:Unseen; 127:Seen; 128:Seen; 129:Seen
 *  found in the TypArticle structures in consecutive lines in the document
 *  and transform it to the general form used by .newsrc files:
 *    124,127-129
 *  (though we are using our internal representation & not ASCII characters).
 *
 *    Entry    Doc      points to the document for this group.
 *
 *    Exit     The line in the Net document corresponding to this
 *              group has been updated.
 */
void
UpdateSeenArts (TypDoc far *Doc)
{
  TypRange MyRange, *RangePtr;
  TypGroup *group;
  TypLine far *ParentLine;
  TypBlock far *ParentBlock;
  HANDLE header_handle, thread_handle;
  TypLine *LinePtr;
  header_p headers;
  header_p header;
  BOOL InSeen = TRUE;
  BOOL GotLock = FALSE;
  unsigned int MyLength;
  unsigned int maxRanges;
  unsigned long artindex;

  /*  Get the line in the Net document that corresponds to this
   *  group.  Make a local copy of it and set RangePtr to point to
   *  the first range in that line.  We will ignore the old line's
   *  "seen" data and create the information afresh from what we
   *  have in this document.
   */
  GotLock = LockLine (Doc->hParentBlock, Doc->ParentOffset, Doc->ParentLineID,
                 &ParentBlock, &ParentLine);

  if ((GotLock == FALSE) ||
      (LinePtr = (TypLine *) GlobalAllocPtr (GMEM_MOVEABLE, BLOCK_SIZE)) == NULL) {
    return;
  }
  group = GetGroup(LinePtr);
  MoveBytes (ParentLine, LinePtr, ParentLine->length);

  if (group->total_headers > 0) {

    header_handle = group->header_handle;
    thread_handle = group->thread_handle;
    /* this can happen if user selects "Update from server" then cancel. */
    if (!header_handle)
      return;

    set_index_to_identity (header_handle, thread_handle, group->total_headers);
    headers = lock_headers (header_handle, thread_handle);

    group->nRanges = 0;
    group->nSpareRanges = SpareRanges;

    maxRanges = ((Doc->BlockSize - Doc->SplitSize) - ParentLine->length +
                 group->nRanges * sizeof (TypRange) + 
                 group->nSpareRanges * sizeof (TypRange)) / sizeof (TypRange) - 1;

    RangePtr = GetRangePtr(group);
    MyRange.First = MyRange.Last = 1;

    /* Get the first line in this document.
     * If it cannot be found, just set Last=First and skip the
     * proceeding processing.  Otherwise, assume we've seen everything
     * up to but not including the first article in the document.
     */

    /*  LockLine (Doc->hFirstBlock, sizeof (TypBlock), 0L, &BlockPtr, &LinePtr); */

    artindex = 0;
    if (!Doc->TotalLines) {
      MyRange.Last = 1;
    }
    else {
      header = (header_elt (headers, artindex));
      MyRange.Last = header->number - 1;

      /* Loop to scan through the document, fabricating article ranges.*/
      do {
        header = (header_elt (headers, artindex));
        if (header->Seen) {
          if (InSeen) {
            /* Continuing a sequence of seen articles.*/
            MyRange.Last = header->number;
          }
          else {
            /* Starting a new sequence of seen articles.*/
            MyRange.First = header->number;
            MyRange.Last = header->number;
            InSeen = TRUE;
          }
        }
        else {
          if (InSeen) {
            /* Ending a sequence of seen articles.*/
            InSeen = FALSE;
            *(RangePtr++) = MyRange;
            (group->nRanges)++;
          }
          else {
            /* Continuing a sequence of unseen articles.*/
          }
        }
      }
      while ((group->nRanges < maxRanges) &&
             ((++artindex < Doc->TotalLines)));

      if (InSeen) {
        *(RangePtr++) = MyRange;
        (group->nRanges)++;
      }
    }
    
  }
  group->NumUnread = CalcNumUnread(group);
  unlock_headers (header_handle, thread_handle);

  MyLength = CalcGroupLen(group);
  LinePtr->length = MyLength;
  GroupLenPtr(LinePtr) = MyLength;
  ReplaceLine (LinePtr, &ParentBlock, &ParentLine);
  GlobalUnlock (ParentBlock->hCurBlock);
  GlobalFreePtr (LinePtr);
  NewsrcDirty = TRUE;
}

/*-- function CursorToTextLine ----------------------------------------
 *
 *   Routine to locate a text line in a document, based on the
 *   cursor position.  Used to figure out which line is being selected
 *   when a user clicks a mouse button.
 *
 *   Entry    X, Y    are the position of the cursor.
 *            DocPtr  points to the current document.
 *
 *   Exit     *LinePtr points to the current line, if one was found.
 *            *BlockPtr points to the current block, if found.
 *            Function returns TRUE iff a line was found that corresponds
 *              to the cursor position.
 */
long
NewCursorToTextLine (int X, int Y, TypDoc far *DocPtr)
{
  int SelLine;
  long result;

  if (Y < TopSpace || (unsigned) Y > TopSpace + DocPtr->ScYLines * LineHeight ||
      X < SideSpace) {
    /* Cursor is in no-man's-land at edge of window.               */
    return (-1);
  }
  else {
    SelLine = (Y - TopSpace) / LineHeight;
    if ((unsigned int) SelLine >= DocPtr->ScYLines)     /* double-check */
      return (-1);
    else {
      result = (long) DocPtr->TopLineOrd + (long) SelLine;
      if (result < (long) DocPtr->TotalLines)
        return (result);
      else
//         return ((long)DocPtr->TotalLines-1); 
        return (-1);
    }
  }
}

/* this has been changed to use header_elt (SMR) */

long
search_headers (TypDoc far * HeaderDoc, header_p headers,
                long artindex, long num_headers)
{

  char_p temp;

  do {
    temp = (header_elt (headers, artindex))->subject;
    if (string_compare_insensitive (temp, HeaderDoc->SearchStr))
      return (artindex);        /* return the index */
  } while (artindex++ < (num_headers - 1));

  return (-1);                  /* not found */
}

char_p
string_compare_insensitive (char_p a, char far * b)
{
  int lena = lstrlen (a);
  int lenb = lstrlen (b);
  int count;

  for (count = lena - lenb + 1; count > 0; count--, a++)
    if (strnicmp (a, b, lenb) == 0)
      return a;

  return (NULL);
}


BOOL
AffectOneSelected (TypDoc far * Doc, header_p ThisHeader, long index, BOOL value, BOOL compare)
{
  char_p temp;
  static BOOL extending = FALSE;

  switch (compare) {
  case COMPARE:
    temp = ThisHeader->subject;
    if (string_compare_insensitive (temp, Doc->SearchStr)) {
      SelectHeader (Doc, ThisHeader, value);
      return TRUE;
    }
    break;

  case NO_COMPARE:
    SelectHeader (Doc, ThisHeader, value);
    return TRUE;
    break;

  case EXTEND:
    if (index == Doc->ActiveLineID || index == Doc->AnchorLineID) {
      if (extending == TRUE) {
        SelectHeader (Doc, ThisHeader, TRUE);
        extending = FALSE;
      }
      else {
        SelectHeader (Doc, ThisHeader, TRUE);
        if (Doc->ActiveLineID == Doc->AnchorLineID)
          extending = FALSE;
        else
          extending = TRUE;
      }
    }
    else
      SelectHeader (Doc, ThisHeader, extending);

    return TRUE;
    break;
  }
  return FALSE;
}

long
AffectSelected (TypDoc far * Doc, BOOL value, BOOL compare)
{
  TypGroup far *group;
  HANDLE header_handle, thread_handle;
  header_p headers, ThisHeader;
  TypBlock far *BlockPtr;
  TypLine far *LinePtr;
  long index, num_affected;

  LockLine (Doc->hParentBlock, Doc->ParentOffset,
            Doc->ParentLineID, &BlockPtr, &LinePtr);
  group = GetGroup(LinePtr);
  header_handle = group->header_handle;
  thread_handle = group->thread_handle;
  headers = lock_headers (header_handle, thread_handle);

  for (index = 0, num_affected = 0; index < group->total_headers; index++) {
    ThisHeader = header_elt (headers, index);
    if (AffectOneSelected (Doc, ThisHeader, index, value, compare)) {
      num_affected++;
    }
  }

  InvalidateRect (Doc->hDocWnd, NULL, FALSE);
  return (num_affected);
}

long
AffectSelectedOnScreen (TypDoc far * Doc, BOOL value, BOOL compare)
{
  TypGroup far *group;
  HANDLE header_handle, thread_handle;
  header_p headers, ThisHeader;
  TypBlock far *BlockPtr;
  TypLine far *LinePtr;
  long index, num_affected;
  static BOOL extending = FALSE;
  int VertLines;

  LockLine (Doc->hParentBlock, Doc->ParentOffset,
            Doc->ParentLineID, &BlockPtr, &LinePtr);
  group = GetGroup(LinePtr);
  header_handle = group->header_handle;
  thread_handle = group->thread_handle;
  headers = lock_headers (header_handle, thread_handle);

  VertLines = (Doc->ScYLines > (Doc->TotalLines - Doc->TopLineOrd))
    ? (Doc->TotalLines - Doc->TopLineOrd) : Doc->ScYLines;

  for (index = Doc->TopLineOrd, num_affected = 0; VertLines > 0; index++, VertLines--) {
    ThisHeader = header_elt (headers, index);
    if (AffectOneSelected (Doc, ThisHeader, index, value, compare)) {
      num_affected++;
    }
  }
  InvalidateRect (Doc->hDocWnd, NULL, FALSE);
  return (num_affected);
}

void 
SetGroupSelections (TypDoc * MyDoc, header_p headers, long newActive)
{
  long prevActive = MyDoc->ActiveLineID;
  header_p ThisHeader = header_elt (headers, newActive);


  MyDoc->ActiveLineID = newActive;

  if (GetKeyState (VK_SHIFT) >= 0) {    /* shift is NOT pressed  */
    /* clear select status of all lines */
    if (MyDoc->SelectedLines == 1) {
      article_operation (MyDoc, prevActive, selected_false);
    }
    else {
      AffectSelected (MyDoc, DESELECT, NO_COMPARE);
    }
    /* set only current line selected */
    SelectHeader (MyDoc, ThisHeader, TRUE);
    /* set the current line to be the anchor */
    MyDoc->AnchorLineID = MyDoc->ActiveLineID;
  }
  else {                        /* shift is pressed  */
    /* select all lines between ActiveLineID and AnchorLineID inclusive */
    AffectSelected (MyDoc, DESELECT, EXTEND);
  }
  InvalidateRect (MyDoc->hDocWnd, NULL, FALSE);
}

long FAR PASCAL
WinVnViewFrameWndProc (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
  TypDoc far *ThisDoc;
  int ih, i, j;
  BOOL found;
  TBBUTTON tbButton[20];
  RECT MyRect;
  TypGroup *GroupDoc;
  TypBlock far *BlockPtr;
  HANDLE hBlock;
  TypLine far *LinePtr;
  unsigned int Offset;
  TypLineID MyLineID;
  long found_artindex;
  BOOL continueFind, threadOk;
  char mybuf[MAXINTERNALLINE];
  HMENU hMenu, hSubMenu;
  PAINTSTRUCT ps;               /* paint structure          */
  HDC hDC;                      /* handle to display context */

  /* We know what *window* is being acted on, but we must find
     * out which *document* is being acted on.  There's a one-to-one
     * relationship between the two, and we find out which document
     * corresponds to this window by scanning the GroupDocs array.
   */

  for (ih = 0, found = FALSE; !found && ih < MAXGROUPWNDS; ih++) {
    if (GroupDocs[ih].hWndFrame == hWnd) {
      found = TRUE;
      ThisDoc = &(GroupDocs[ih]);
    }
  }

  if (!found)
    ThisDoc = CommDoc;

  if (StatusBarProc (hWnd, message, wParam, lParam, ThisDoc))
    return 0;

  switch (message) {
  case WM_SYSCOMMAND:
    return (DefWindowProc (hWnd, message, wParam, lParam));

  case WM_CLOSE:
    CloseGroupWnd (hWnd, ThisDoc);
    return (0);
    break;

  case WM_DESTROY:
    /* Unlink all the article windows that belong to this group */
    NumGroupWnds--;

    LockLine (ThisDoc->hParentBlock, ThisDoc->ParentOffset,
              ThisDoc->ParentLineID, &BlockPtr, &LinePtr);

    {
      /* Clear the pointer in the line for this group in the   */
      /* NetDoc document.  This pointer currently points       */
      /* to the current document, which we are wiping out      */
      /* with the destruction of this window.                  */
      TypGroup far *group = GetGroup(LinePtr);
      long int CN_Total_Lines;

      group->SubjDoc = (TypDoc far *) NULL;

      ThisDoc->InUse = FALSE;
      if (ThisDoc == CommDoc) {
        CN_Total_Lines = CommDoc->TotalLines;
        CommBusy = FALSE;
        CommDoc = (TypDoc far *) NULL;
      }
      /* SMR 940420 cause of mysterious F3 bug */
      else
        CN_Total_Lines = group->total_headers;

      /* make the 'n' in the group-list go away */
      group->HighestPrevSeen = group->ServerLast;
      InvalidateRect (NetDoc.hDocWnd, NULL, TRUE);
      
      /*CN  if (group->total_headers != 0)  */
      if (CN_Total_Lines != 0) {    /* CN */
        UpdateSeenArts (ThisDoc);
        UnlinkArtsInGroup (ThisDoc);
        if (group->header_handle) {
#if 0
          /* Is this necessary? SMR 941221 */
          unlock_headers (group->header_handle, group->thread_handle);
#endif
          free_headers (group->header_handle, group->thread_handle);
        }
        group->header_handle = (HANDLE) NULL;
        group->thread_handle = (HANDLE) NULL;
        group->total_headers = 0;
      }
    }

    UnlockLine (BlockPtr, LinePtr, &hBlock, &Offset, &MyLineID);
    /* Clear document                                        */
    FreeDoc (ThisDoc);

    /* If there's another group window, make it the active   */
    /* group window so we don't create a new one if the      */
    /* New Group flag is FALSE.                              */

    for (j = MAXGROUPWNDS - 1; j >= 0; j--) {
      if (GroupDocs[j].InUse) {
        ActiveGroupDoc = &(GroupDocs[j]);
        break;
      }
    }
    return (0);
    break;

  case WM_CREATE:
    /* create the child window and the toolbar  */
    GetClientRect (hWnd, &MyRect);

// add common controls for toolbar (jlg)
    i = j = 0;
    tbButton[j].iBitmap = 0;    // find

    tbButton[j].idCommand = IDM_FIND;
    tbButton[j].fsState = TBSTATE_ENABLED;
    tbButton[j].fsStyle = TBSTYLE_BUTTON;
    tbButton[++j].iBitmap = ++i;    // find next

    tbButton[j].idCommand = IDM_FIND_NEXT_SAME;
    tbButton[j].fsState = TBSTATE_ENABLED;
    tbButton[j].fsStyle = TBSTYLE_BUTTON;

    tbButton[++j].iBitmap = 8;  // separator

    tbButton[j].fsState = TBSTATE_ENABLED;
    tbButton[j].fsStyle = TBSTYLE_SEP;

    tbButton[++j].iBitmap = ++i;    // New Article

    tbButton[j].idCommand = IDM_POST;
    tbButton[j].fsState = TBSTATE_ENABLED;
    tbButton[j].fsStyle = TBSTYLE_BUTTON;
    tbButton[++j].iBitmap = ++i;    // New Mail

    tbButton[j].idCommand = IDM_MAIL;
    tbButton[j].fsState = TBSTATE_ENABLED;
    tbButton[j].fsStyle = TBSTYLE_BUTTON;

    tbButton[++j].iBitmap = 8;  // separator

    tbButton[j].fsState = TBSTATE_ENABLED;
    tbButton[j].fsStyle = TBSTYLE_SEP;

    tbButton[++j].iBitmap = ++i;    // Save selected

    tbButton[j].idCommand = IDM_SAVE_SELECTED;
    tbButton[j].fsState = TBSTATE_ENABLED;
    tbButton[j].fsStyle = TBSTYLE_BUTTON;

    tbButton[++j].iBitmap = ++i;    // Decode selected

    tbButton[j].idCommand = IDM_DECODE_SELECTED;
    tbButton[j].fsState = TBSTATE_ENABLED;
    tbButton[j].fsStyle = TBSTYLE_BUTTON;

    tbButton[++j].iBitmap = 8;  // separator

    tbButton[j].fsState = TBSTATE_ENABLED;
    tbButton[j].fsStyle = TBSTYLE_SEP;

    tbButton[++j].iBitmap = ++i;    // Catch Up and Exit

    tbButton[j].idCommand = IDM_MARK_ALL;
    tbButton[j].fsState = TBSTATE_ENABLED;
    tbButton[j].fsStyle = TBSTYLE_BUTTON;
    tbButton[++j].iBitmap = ++i;    // Exit

    tbButton[j].idCommand = IDV_EXIT;
    tbButton[j].fsState = TBSTATE_ENABLED;
    tbButton[j].fsStyle = TBSTYLE_BUTTON;

    tbButton[++j].iBitmap = 8;  // separator
    tbButton[j].fsState = TBSTATE_ENABLED;
    tbButton[j].fsStyle = TBSTYLE_SEP;

    tbButton[++j].iBitmap = ++i;    /* Help */
    tbButton[j].idCommand = IDM_HELP;    
    tbButton[j].fsState = TBSTATE_ENABLED;
    tbButton[j].fsStyle = TBSTYLE_BUTTON;

    ThisDoc->hWndTB = CreateToolbar (hWnd,
                                     WS_BORDER | WS_VISIBLE,
                                     (WORD) GetMenu (hWnd),
                                     i + 1,
                                     hInst,
                                     IDB_VIEWTOOLBAR,
                                     tbButton,
                                     j + 1);

    ThisDoc->hWndFrame = hWnd;
    ThisDoc->hDocWnd = CreateWindow ("WinVnView",
                                     NULL,
                                     WS_CHILD | WS_VSCROLL | WS_HSCROLL,
                                     0,     /* Initial X position */
                                     TOOLBARHEIGHT,     /* Initial Y position */
                                     RectWidth (MyRect),
             RectHeight (MyRect) - TOOLBARHEIGHT - StatbarPntData.dyStatbar,
                                     hWnd,
                                     NULL,
                                     hInst,
                                     NULL);
    SetHandleBkBrush (ThisDoc->hDocWnd, hListBackgroundBrush);
    ShowWindow (ThisDoc->hDocWnd, SW_SHOWNORMAL);

    SetGroupMenus (ThisDoc, DISABLE);
    NumGroupWnds++;
    break;

  case WM_ACTIVATE:
    if((wParam == WA_ACTIVE) || (wParam == WA_CLICKACTIVE))
      ActiveGroupDoc = ThisDoc;

    /* fall through to update toolbars */
  case WM_MYINITMENU:
  case WM_INITMENU:
    hMenu = GetMenu (hWnd);
    hSubMenu = GetSubMenu (hMenu, 0);
    i = (!CommBusy && ThisDoc->SelectedLines);

    EnableMenuItem (hSubMenu, IDM_SAVE_SELECTED, i ? ENABLE_MENU : DISABLE_MENU);
    EnableMenuItem (hSubMenu, IDM_DECODE_SELECTED, (i && CodingState == INACTIVE) ? ENABLE_MENU : DISABLE_MENU);
//  EnableMenuItem (hSubMenu, IDM_READ_SELECTED, (i && CodingState == INACTIVE) ? ENABLE_MENU : DISABLE_MENU);
    EnableMenuItem (hSubMenu, IDM_DESELECT_ALL, ThisDoc->SelectedLines ? ENABLE_MENU : DISABLE_MENU);
    EnableMenuItem (hSubMenu, IDM_MARK_SELECTED, ThisDoc->SelectedLines ? ENABLE_MENU : DISABLE_MENU);
    EnableMenuItem (hSubMenu, IDM_UNMARK_SELECTED, ThisDoc->SelectedLines ? ENABLE_MENU : DISABLE_MENU);
    EnableMenuItem (hSubMenu, IDM_UNMARK_READ_ARTICLES, ThisDoc->SelectedLines ? ENABLE_MENU : DISABLE_MENU);
    EnableMenuItem (hSubMenu, IDM_UNMARK_UNREAD_ARTICLES, ThisDoc->SelectedLines ? ENABLE_MENU : DISABLE_MENU);
    EnableMenuItem (hSubMenu, IDM_DESELECT_MATCH, ThisDoc->SelectedLines ? ENABLE_MENU : DISABLE_MENU); 
    EnableMenuItem (hSubMenu, IDM_UPDATE, CommBusy ? DISABLE_MENU : ENABLE_MENU);

    SendMessage (ThisDoc->hWndTB, TB_ENABLEBUTTON, IDM_SAVE_SELECTED, i);
    SendMessage (ThisDoc->hWndTB, TB_ENABLEBUTTON, IDM_DECODE_SELECTED, (i && CodingState == INACTIVE));

    /* only allow sort by threads if the group has threads.  it's not enough
     * to check threadp - since the user may have changed the threadp
     * option after retrieving this group article-list 
     */
    threadOk = FALSE;
    /*
     * we check hParentBlock because this code can be called before the Document
     * has been initialized via InitDoc
     */
    if (ThisDoc->hParentBlock && (!CommBusy || CommDoc != ThisDoc)) {
      LockLine (ThisDoc->hParentBlock, ThisDoc->ParentOffset,
                ThisDoc->ParentLineID, &BlockPtr, &LinePtr);
      if (LinePtr) {
        GroupDoc = GetGroup(LinePtr);
        threadOk = GroupDoc->Threaded;
        UnlockLine (BlockPtr, LinePtr, &hBlock, &Offset, &MyLineID);
      }
    }
    SendMessage(hWnd, WM_AA_ENABLE_DISABLE, 0, 0); // conditionally enable Article Actions
    hSubMenu = GetSubMenu (hMenu, 1);   /* sorting menu */
    EnableMenuItem (hSubMenu, IDM_SORT_THREADS, threadOk ? ENABLE_MENU : DISABLE_MENU);
    EnableMenuItem (hSubMenu, IDM_SORT_THREADSUB, threadOk ? ENABLE_MENU : DISABLE_MENU);

    if(message == WM_INITMENU)
    {
      CheckMenuItem(hMenu, IDM_SORT_DATE,      MF_BYCOMMAND|MF_UNCHECKED);
      CheckMenuItem(hMenu, IDM_SORT_SUBJECT,   MF_BYCOMMAND|MF_UNCHECKED);
      CheckMenuItem(hMenu, IDM_SORT_LINES,     MF_BYCOMMAND|MF_UNCHECKED);
      CheckMenuItem(hMenu, IDM_SORT_THREADS,   MF_BYCOMMAND|MF_UNCHECKED);
      CheckMenuItem(hMenu, IDM_SORT_THREADSUB, MF_BYCOMMAND|MF_UNCHECKED);
      CheckMenuItem(hMenu, IDM_SORT_FROM,      MF_BYCOMMAND|MF_UNCHECKED);
      CheckMenuItem(hMenu, IDM_SORT_ARTNUM,    MF_BYCOMMAND|MF_UNCHECKED);

      CheckMenuItem(hMenu, iSortOption, MF_BYCOMMAND|MF_CHECKED);
/*      TRACE2("WVGroup/WM_INITMENU: Newsgroup <%s> iSortOption: %d\n", CurrentGroup, iSortOption); */
    }

    if (message == WM_ACTIVATE) // kludgeville
      return (DefWindowProc (hWnd, message, wParam, lParam));
    break;

  case WM_SETFOCUS:
    StatBarGroupPopups (ThisDoc);
    SetCapsLockText(hWnd);
    SetNumLockText(hWnd);
    break;

  case WM_PAINT:
    /* paint status bar  */
    hDC = BeginPaint (hWnd, &ps);
    PaintStatbar (hWnd, hDC, ThisDoc);
    EndPaint (hWnd, &ps);
    return (DefWindowProc (hWnd, message, wParam, lParam));
    break;

  case WM_SIZE:
    /* Store the new size of the window.                     */
    GetClientRect (hWnd, &MyRect);
    MoveWindow (ThisDoc->hDocWnd, 0, TOOLBARHEIGHT,
                RectWidth (MyRect),
             RectHeight (MyRect) - TOOLBARHEIGHT - StatbarPntData.dyStatbar,
                TRUE);
    MoveWindow (ThisDoc->hWndTB, 0, 0,
                RectWidth (MyRect),
                TOOLBARHEIGHT,
                TRUE);

    break;

  case WM_KEYDOWN:
    SendMessage (ThisDoc->hDocWnd, (UINT) WM_KEYDOWN, wParam, lParam);
    break;

  case WM_DISPLAY_SORTED:
    {
        TypGroup far *group;
        HANDLE header_handle, thread_handle;
        header_p headers, hp;
        thread_array thread_index;
        BOOL threadOk;
        long i;
        uint32 i1;

        LockLine (ThisDoc->hParentBlock, ThisDoc->ParentOffset,
                  ThisDoc->ParentLineID, &BlockPtr, &LinePtr);
        group = GetGroup(LinePtr);
        header_handle = group->header_handle;
        thread_handle = group->thread_handle;
        threadOk = group->Threaded;

        /* this dependency on [thread_array_p][head0][head1][head2]... */
        /* should be moved into headarry.c */
        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;

        ThisDoc->SelectedLines = 0; // (this recount may be redundant...)
        if(ThisDoc->TotalLines > 0)
        {
          for(i1 = 0; i1 < ThisDoc->TotalLines; i1++)
          {
            hp = header_elt(headers, i1);
            if(ART_SELECTED & hp->Selected)
              ThisDoc->SelectedLines++;
          }
        }
        UnlockLine (BlockPtr, LinePtr, &hBlock, &Offset, &MyLineID);

        sort_by_option(headers, thread_index, threadOk, ThisDoc->TotalLines,
           header_handle, thread_handle);
        unlock_headers (header_handle, thread_handle);
        InvalidateRect (ThisDoc->hDocWnd, NULL, FALSE);
    }
    break;
  
  case WM_AA_ENABLE_DISABLE:
    hMenu = GetMenu (hWnd);
    hSubMenu = GetSubMenu (hMenu, 1);   /* sorting menu */
    EnableMenuItem (hSubMenu, IDM_VIEW_ACTION, bEnableArticleAction ? ENABLE_MENU : DISABLE_MENU);
    break;
  
  case WM_COMMAND:
    switch (LOWORD (wParam)) {
    case IDV_EXIT:
      CloseGroupWnd (hWnd, ThisDoc);
      return (0);
      break;

    case IDM_CONFIG_ARTLIST:
      DialogBox (hInst, "WinVnArticleListPrefs", hWnd, lpfnWinVnArtListDlg);
      SendMessage(hWnd, WM_AA_ENABLE_DISABLE, 0, 0); // conditionally enable Article Actions
      break;

    case IDV_NEXT:
      break;

    case IDM_MARK_ALL:
      article_operation (ThisDoc, 0, mark_read_all);
      CloseGroupWnd (hWnd, ThisDoc);
      return (0);
      break;

    case IDM_MARK_SELECTED:
      article_operation (ThisDoc, 0, mark_read_selected);
      InvalidateRect (ThisDoc->hDocWnd, NULL, FALSE);
      break;

    case IDM_UNMARK_SELECTED:
      article_operation (ThisDoc, 0, mark_unread_selected);
      InvalidateRect (ThisDoc->hDocWnd, NULL, FALSE);
      break;
    
    case IDM_MARK_READ_ARTICLES:
      article_operation (ThisDoc, 0, mark_selected_read_articles);
      InvalidateRect (ThisDoc->hDocWnd, NULL, FALSE);
      break;
      
    case IDM_UNMARK_READ_ARTICLES:
      article_operation (ThisDoc, 0, mark_deselected_read_articles);
      InvalidateRect (ThisDoc->hDocWnd, NULL, FALSE);
      break;
    
    case IDM_MARK_UNREAD_ARTICLES:
      article_operation (ThisDoc, 0, mark_selected_unread_articles);
      InvalidateRect (ThisDoc->hDocWnd, NULL, FALSE);
      break;
      
    case IDM_UNMARK_UNREAD_ARTICLES:
      article_operation (ThisDoc, 0, mark_deselected_unread_articles);
      InvalidateRect (ThisDoc->hDocWnd, NULL, FALSE);
      break;  

    case IDM_VIEW_ACTION:
      {
        TypGroup far *group;
        HANDLE header_handle, thread_handle;
        header_p headers;
        WVArticleAction aa;

        LockLine (ThisDoc->hParentBlock, ThisDoc->ParentOffset,
                  ThisDoc->ParentLineID, &BlockPtr, &LinePtr);
        group = GetGroup(LinePtr);
        header_handle = group->header_handle;
        thread_handle = group->thread_handle;
        threadOk = group->Threaded;

        headers = lock_headers (header_handle, thread_handle);

        if(DialogBox(hInst, "ActionDlg", hWnd, lpfnWinVnArticleActionDlg))
        {
            g_action.ActOnArticles(group, headers);
            aa.ReadActions(CurrentGroup);
            aa.ActOnArticles(group, headers);
        }
        unlock_headers (header_handle, thread_handle);
        SendMessage(hWnd, WM_DISPLAY_SORTED, 0, 0);
      }
      break;

    case IDM_SORT_DATE:
    case IDM_SORT_SUBJECT:
    case IDM_SORT_LINES:
    case IDM_SORT_THREADSUB:
    case IDM_SORT_THREADS:
    case IDM_SORT_ARTNUM:
    case IDM_SORT_FROM:
      iSortOption = LOWORD(wParam);
      WriteSortOption(iSortOption, CurrentGroup);

      hMenu = GetMenu(hWnd);
      CheckMenuItem(hMenu, IDM_SORT_DATE,      MF_BYCOMMAND|MF_UNCHECKED);
      CheckMenuItem(hMenu, IDM_SORT_SUBJECT,   MF_BYCOMMAND|MF_UNCHECKED);
      CheckMenuItem(hMenu, IDM_SORT_LINES,     MF_BYCOMMAND|MF_UNCHECKED);
      CheckMenuItem(hMenu, IDM_SORT_THREADS,   MF_BYCOMMAND|MF_UNCHECKED);
      CheckMenuItem(hMenu, IDM_SORT_THREADSUB, MF_BYCOMMAND|MF_UNCHECKED);
      CheckMenuItem(hMenu, IDM_SORT_FROM,      MF_BYCOMMAND|MF_UNCHECKED);
      CheckMenuItem(hMenu, IDM_SORT_ARTNUM,    MF_BYCOMMAND|MF_UNCHECKED);

      CheckMenuItem(hMenu, iSortOption, MF_BYCOMMAND|MF_CHECKED);   
      SendMessage(hWnd, WM_DISPLAY_SORTED, 0, 0);
      break;


    case IDM_HELP:
      MakeHelpPathName (mybuf, MAXINTERNALLINE);
      WinHelp (ThisDoc->hDocWnd, mybuf, HELP_INDEX, 0L);
      break;


    case IDM_FIND:
    case IDM_FIND_NEXT_SAME:
      FindDoc = ThisDoc;

      continueFind = TRUE;
      if (!FindDoc->SearchStr[0] || LOWORD (wParam) == IDM_FIND) {
        if (!(FindDoc->SearchStr[0]) && LastArticleHeaderFind[0])
          strcpy (FindDoc->SearchStr, LastArticleHeaderFind);
        continueFind = DialogBox (hInst, "WinVnFind", hWnd, lpfnWinVnFindDlg);
      }
      if (continueFind && FindDoc->SearchStr[0]) {
        TypGroup far *group;
        header_p headers;
        HANDLE header_handle;
        HANDLE thread_handle;
        TypLineID starting_at;

        LockLine (ThisDoc->hParentBlock, ThisDoc->ParentOffset, ThisDoc->ParentLineID,
                  &BlockPtr, &LinePtr);
        group = GetGroup(LinePtr);
        header_handle = group->header_handle;
        thread_handle = group->thread_handle;
        headers = lock_headers (header_handle, thread_handle);
        UnlockLine (BlockPtr, LinePtr, &hBlock, &Offset, &MyLineID);

        /* 'Find Next' will start one line after */
        if (ArtListMultiSelect)
          starting_at = ThisDoc->ActiveLineID + ((LOWORD (wParam) == IDM_FIND) ? 0 : 1);
        else
          starting_at = ThisDoc->TopLineOrd + ((LOWORD (wParam) == IDM_FIND) ? 0 : 1);

        /* back up one if we're at the end */
        if (starting_at >= group->total_headers)
          starting_at--;

        found_artindex = search_headers (ThisDoc, headers, starting_at, group->total_headers);
        if (found_artindex == -1) {
          strcpy (mybuf, "\"");
          strcat (mybuf, ThisDoc->SearchStr);
          strcat (mybuf, "\" not found.");
          MessageBox (hWnd, mybuf, "Not found", MB_OK);
        }
        else {
          if (ArtListMultiSelect) {
            ThisDoc->FindOffset = (int) found_artindex;
            ThisDoc->AnchorLineID = (int) found_artindex;
            strcpy (LastArticleHeaderFind, FindDoc->SearchStr);
            SetGroupSelections (ThisDoc, headers, (int) found_artindex);
            AdjustTopScByDoc (ThisDoc, ThisDoc->ActiveLineID);
          }
          else {
            ThisDoc->TopLineOrd = (int) found_artindex;
            ThisDoc->FindOffset = (int) found_artindex;
            strcpy (LastArticleHeaderFind, FindDoc->SearchStr);
            InvalidateRect (ThisDoc->hDocWnd, NULL, FALSE);
          }
        }
        unlock_headers (header_handle, thread_handle);
      }
      break;

    case IDM_POST:
      /* We are creating the skeleton text of a new posting.
         * Most of the work is done by CreatePostingWnd and
         * CreatePostingText.  Here we have to identify
         * the newsgroup for those routines.
         * Get the newsgroup from the line in NetDoc that
         * points to this document.
       */
      if (LockLine (ThisDoc->hParentBlock, ThisDoc->ParentOffset,
                    ThisDoc->ParentLineID, &BlockPtr, &LinePtr)) {
        ExtractTextLine (ThisDoc->ParentDoc, LinePtr,
                       mybuf, MAXINTERNALLINE);
        UnlockLine (BlockPtr, LinePtr, &hBlock, &Offset, &MyLineID);
        NewsgroupsPtr = mybuf;
        CreateComposeWnd (hWnd, (TypDoc far *) NULL, DOCTYPE_POSTING);
      }
      break;

    case IDM_SAVE_SELECTED:
      /* Query user for file name */
      if (!DialogBox (hInst, "WinVnSaveArts", hWnd, lpfnWinVnSaveArtsDlg)) {
        InvalidateRect (ThisDoc->hDocWnd, NULL, TRUE);
      }
      else {
        ThisDoc->numArtsSaved = 0;
        ThisDoc->savingArtIndex = 0;
        SetMenusForMultiArticleOperation (ThisDoc, DISABLE);
        goto doretrieve;
      }
      break;

    case IDM_FIND_NEXT_SELECTED:
    case IDM_READ_SELECTED:
      SendMessage(ThisDoc->hDocWnd, (UINT) WM_KEYDOWN, (WPARAM) VK_F5, 0);
      break;

    case IDM_DECODE_SELECTED:
      /* Query user for file name */
      if (!DialogBoxParam (hInst, "WinVnDecodeArts", hWnd, lpfnWinVnDecodeArtsDlg, 1)) {
        InvalidateRect (ThisDoc->hDocWnd, NULL, TRUE);
      }
      else {
        if (TestCommBusy (hWnd, "Can't Decode Articles") ||
            TestCodingBusy (hWnd, "Can't Decode Articles"))
          break;

        DecodeInit ();
        CommDoc = ThisDoc;
        CommDecoding = TRUE;
        ThisDoc->numArtsSaved = 0;
        ThisDoc->savingArtIndex = 0;
        SetMenusForMultiArticleOperation (ThisDoc, DISABLE);
        if (AlsoDecodeOpenArticles) {
          DecodeOpenArticles (hWnd);
        }
        /* now start the retrieving the selected arts */
        goto doretrieve;
      }
      break;

    case IDM_SELECT_ALL:
      /* select all articles */
      AffectSelected (ThisDoc, SELECT, NO_COMPARE);
      break;

    case IDM_DESELECT_ALL:
      /* deselect all articles */
      AffectSelected (ThisDoc, DESELECT, NO_COMPARE);
      break;

    case IDM_SELECT_MATCH:
      /* select all articles containing a string */
      FindDoc = ThisDoc;
      DialogBox (hInst, "WinVnFind", hWnd, lpfnWinVnFindDlg);
      if (strcmp (FindDoc->SearchStr, "")) {
        if (AffectSelected (FindDoc, SELECT, COMPARE) == 0) {
          strcpy (mybuf, "\"");
          strcat (mybuf, FindDoc->SearchStr);
          strcat (mybuf, "\" not found.");
          MessageBox (hWnd, mybuf, "Not found", MB_OK);
        }
      }
      break;
      
    case IDM_DESELECT_MATCH:
      /* deselect all articles containing a string */
      FindDoc = ThisDoc;
      DialogBox (hInst, "WinVnFind", hWnd, lpfnWinVnFindDlg);
      if (strcmp (FindDoc->SearchStr, "")) {
        if (AffectSelected (FindDoc, DESELECT, COMPARE) == 0) {
          strcpy (mybuf, "\"");
          strcat (mybuf, FindDoc->SearchStr);
          strcat (mybuf, "\" not found.");
          MessageBox (hWnd, mybuf, "Not found", MB_OK);
        }
      }
      break;

    case IDM_MAIL:
      (MailCtrl.fnMlWinCreate) (hWnd, (TypDoc far *) NULL, DOCTYPE_MAIL);
      break;

    case IDM_UPDATE:
      if (!CommBusy) {
        TypGroup far *group;
        char far *group_name;

        /* update the newsrc data */
        UpdateSeenArts (ThisDoc);
        UnlinkArtsInGroup (ThisDoc);
        if (LockLine (ThisDoc->hParentBlock, ThisDoc->ParentOffset,
                      ThisDoc->ParentLineID, &BlockPtr, &LinePtr)) {
            group = GetGroup(LinePtr);
            group_name = GetGroupName(LinePtr);

            // Free whatever headers are there.
            if (group->header_handle) {
#if 0
            /* is this necessary ? SMR 941221 */
            unlock_headers (group->header_handle, group->thread_handle);
#endif
            free_headers (group->header_handle, group->thread_handle);
            }
            group->header_handle = (HANDLE) NULL;
            group->thread_handle = (HANDLE) NULL;
            group->total_headers = 0;

            ThisDoc->hLastSeenBlock = 0;
            ThisDoc->TopLineOrd = 0;
            ThisDoc->FindLineID = 0L;
            ThisDoc->TopScLineID = 0L;
            ThisDoc->hFindBlock = 0;
            ThisDoc->ActiveLineID = -1L;
            ThisDoc->AddLineID = 0L;
            ThisDoc->TopScLineID = 0L;
            ThisDoc->LastSeenLineID = 0L;
            ThisDoc->LongestLine = 0;
            ThisDoc->ScXOffset = 0;

            SetGroupMenus (ThisDoc, DISABLE);

            RcvLineCount = 0;
            CommDoc = ThisDoc;
            CommState = ST_GROUP_RESP;
            CommBusy = TRUE;
            strcpy (mybuf, "GROUP ");
            lstrcat (mybuf, group_name);
            PutCommLine (mybuf);
            NewsrcDirty = TRUE;
        }
      }
      break;

      /* the following msgs are sent by wvutil engine on completion 
       * retrieval
       */
    case ID_RETRIEVE_COMPLETE:
      SetGroupMenus (ThisDoc, ENABLE);
      SetStatbarText (ThisDoc->hWndFrame, "", ThisDoc, TRUE, TRUE);
      InvalidateRect (ThisDoc->hWndFrame, NULL, TRUE);
      break;

    case ID_ARTICLE_RETRIEVE_COMPLETE:
      {
        TypGroup far *group;

        header_p headers;
        HANDLE header_handle;
        HANDLE thread_handle;
        int justSavedIndex, startIndex;

        if (ThisDoc->savingArtIndex == -1)  /* this retrieve wasn't part of a save operation */
          break;

        if (CodingState == INACTIVE) {
          /* If this is the 1st article saved, then value of Append depends
             * on what the user selected in dialog.  If > 1st, always append 
           */
          if (!MRRWriteDocument (ActiveArticleDoc, sizeof (TypText),
                                 SaveArtFileName,
                              (ThisDoc->numArtsSaved == 0) ? SaveArtAppend : TRUE)) {
            *SaveArtFileName = '\0';
            MessageBox (hWnd, "Could not write to file.  Operation canceled", "Problems saving file", MB_OK | MB_ICONEXCLAMATION);
            SetMenusForMultiArticleOperation (ThisDoc, ENABLE);
            ThisDoc->savingArtIndex = -1;
            break;
          }
        }
        else {
          if (CompleteThisDecode () == FAIL) {
            DecodeDone ();
            MessageBox (hWnd, "Aborted decode", "Problems during decode", MB_OK | MB_ICONEXCLAMATION);
            SetMenusForMultiArticleOperation (ThisDoc, ENABLE);
            ThisDoc->savingArtIndex = -1;
            break;
          }
        }
        justSavedIndex = ThisDoc->savingArtIndex;
        ThisDoc->numArtsSaved++;

      doretrieve:;
        if (LockLine (ThisDoc->hParentBlock, ThisDoc->ParentOffset, ThisDoc->ParentLineID,
                      &BlockPtr, &LinePtr)) {
            group = GetGroup(LinePtr);
            header_handle = group->header_handle;
            thread_handle = group->thread_handle;
            headers = lock_headers (header_handle, thread_handle);
            UnlockLine (BlockPtr, LinePtr, &hBlock, &Offset, &MyLineID);
            if (ThisDoc->numArtsSaved > 0)  // deselect the article just finished

            SelectHeader (ThisDoc, header_elt (headers, justSavedIndex), FALSE);

        /* skip to next selected article */
        /* i'm paranoid of infinite loop in case Doc->SelectedLines is wrong */

            if (ThisDoc->savingArtIndex >= group->total_headers) {
            ThisDoc->savingArtIndex = 0;
            }
            startIndex = ThisDoc->savingArtIndex;
            while (ThisDoc->SelectedLines &&
                (header_elt (headers, ThisDoc->savingArtIndex))->Selected == FALSE) {
            ThisDoc->savingArtIndex = (ThisDoc->savingArtIndex + 1) % (int) (group->total_headers);
            if (ThisDoc->savingArtIndex == startIndex)
                break;
            }

            if (ThisDoc->savingArtIndex == startIndex &&        /* this shouldn't happen */
                (header_elt (headers, ThisDoc->savingArtIndex))->Selected == FALSE) {
            ThisDoc->SelectedLines = 0;
            }
            
            if (ThisDoc->SelectedLines == 0) {  /* done */
               SetStatbarText (ThisDoc->hWndFrame, "", ThisDoc, TRUE, TRUE);
               if (ThisDoc->numArtsSaved > 0) {
                  if (CodingState == INACTIVE) {
                     SetStatbarPercent(ThisDoc->hWndFrame,0,ThisDoc,TRUE);
                     sprintf (mybuf, "Done! Saved %d article%c to file %s",ThisDoc->numArtsSaved, 
                              (ThisDoc->numArtsSaved == 1) ? ' ' : 's', SaveArtFileName);
                     SetStatbarText (ThisDoc->hWndFrame, mybuf, ThisDoc, TRUE, TRUE);
                   }
                }
                else
                    SetStatbarText (ThisDoc->hWndFrame, "No articles selected", ThisDoc, TRUE, TRUE);

                if (CodingState != INACTIVE) DecodeDone ();

                SetMenusForMultiArticleOperation (ThisDoc, ENABLE);
                InvalidateRect (ThisDoc->hWndFrame, NULL, TRUE);
                unlock_headers (header_handle, thread_handle);
                ThisDoc->savingArtIndex = -1;   /* reset to non-saving state */
            }
            else {
                sprintf (mybuf, "Saved %d article%c to file %s",ThisDoc->numArtsSaved, 
                        (ThisDoc->numArtsSaved == 1) ? ' ' : 's',SaveArtFileName);
                SetStatbarText (ThisDoc->hWndFrame, mybuf, ThisDoc, TRUE, TRUE);
                SetStatbarPercent(ThisDoc->hWndFrame,
                                 ((int)(ThisDoc->numArtsSaved / (ThisDoc->numArtsSaved + ThisDoc->SelectedLines)) * 100),ThisDoc,TRUE);

                if (KeepArticleHeaderVisible)
                    AdjustTopScByDoc (ThisDoc, ThisDoc->savingArtIndex);
                if (hCodedBlockWnd) {
                    sprintf (str, "Decoding Status: %d to go", ThisDoc->SelectedLines);
                    SetWindowText (hCodedBlockWnd, (LPCSTR) str);
                }
                sprintf (mybuf, "%ld", (header_elt (headers, ThisDoc->savingArtIndex))->number);
                (header_elt (headers, ThisDoc->savingArtIndex))->Seen = ART_SEEN;
                unlock_headers (header_handle, thread_handle);

                if (CodingState != INACTIVE) {
                    if ((currentCoded = InitCoded (hWnd)) == NULL) {
                        MessageBox (hWnd, "Unable to continue due to memory constraints.  Aborted", "Init Coded Object Error", MB_OK);
                        DecodeDone ();
                        SetMenusForMultiArticleOperation (ThisDoc, ENABLE);
                        ThisDoc->savingArtIndex = -1;
                        break;
                    }
                    InitiateReceiveArticle (ThisDoc, mybuf);
                    UpdateBlockStatus ();   // display some initial status
                }
                else
                    ViewArticle (ThisDoc, ThisDoc->savingArtIndex, REUSE, NO_SHOW, NO_ID);
                }
                InvalidateRect (ThisDoc->hDocWnd, NULL, FALSE);
                return (0);
         }
      }
      break;
    }
  default:
    return (DefWindowProc (hWnd, message, wParam, lParam));
  }
  if (GetFocus () == hWnd)
    SetFocus (ThisDoc->hDocWnd);
  return (0);
}

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

--=====================_833256653==_
Content-Type: text/plain; charset="us-ascii"

-- 
Harvey Brydon         Internet: hab@slb.com or brydon@tulsa.dowell.slb.com
Schlumberger Dowell   Phone:    (918)250-4312
O-

--=====================_833256653==_--

