// -*- 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.82 1997/02/14 20:12:31 dumoulin Exp $
 *
 */

#include <windows.h>
#include <windowsx.h>           // for GlobalFreePtr (JSC)
#include "wvglob.h"
#include "winvn.h"

#pragma hdrstop
#ifdef COMMCTRL
#include <commctrl.h>
#else
extern "C"
{
#include "wvtb\wvtb.h"          /* for toolbar */
}
#endif
#include "WVClass.h"
#include <stdlib.h>
#include <assert.h>
#include <limits.h>

/* added by shimomai */
extern "C" {
DEF HCURSOR hArrowCursor;
DEF HCURSOR hSizeArrow;
}
static BOOL WidthCursor = FALSE;
static int orgPos;
static int *Separator;
#define DRAG_ON     5

#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);
void DrawDragingCursor(HWND hWnd, long oldPosition, long newPosition, BYTE flag); //ishido

/*--- 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;
  static long DragCursorPosition = 0;

  /* 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_CREATE:
    WidthCursor = FALSE;
    /* Article List Separator */
    if (ArtListSeparator1 <= 0) {
      ArtListSeparator1 = CharWidth * 8;
      ArtListSeparator2 = CharWidth * 15;
      ArtListSeparator3 = CharWidth * 40;
    }
    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;

    case VK_ESCAPE: //ishido
      if (DragMouseAction == DRAG_ON) {
        DrawDragingCursor(hWnd, DragCursorPosition, 0, 1);
        ReleaseCapture ();
        DragMouseAction = DRAG_NONE;
        WidthCursor = FALSE;
        SetCursor (hArrowCursor);
        InvalidateRect(hWnd, NULL, FALSE);
      }
      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;
//shimomai & ishido :-)
    if (WidthCursor) {
      orgPos = LOWORD (lParam);
      DragCursorPosition = orgPos;
      DrawDragingCursor(hWnd, DragCursorPosition, 0, 1);
        SetCapture (hWnd);      // release capture on button up
      DragMouseAction = DRAG_ON;
    }
    else 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   */
//shimomai & ishido :-)
    if (DragMouseAction == DRAG_ON) {
      DrawDragingCursor(hWnd, DragCursorPosition, 0, 1);
      ReleaseCapture ();
      *Separator += LOWORD (lParam) - orgPos;
      if (ArtListSeparator1 < CharWidth ||
        ArtListSeparator1 > ArtListSeparator2 ||
        ArtListSeparator2 > ArtListSeparator3) {
        ArtListSeparator1 = CharWidth * 8;
        ArtListSeparator2 = CharWidth * 15;
        ArtListSeparator3 = CharWidth * 40;
      }
      InvalidateRect (hWnd, NULL, FALSE);
    }
/*
    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_SETCURSOR:    //shimomai
    if ((!CommBusy || CommDoc != ThisDoc || CommDecoding)) {
      if (WidthCursor)
        SetCursor (hSizeArrow);
      else
        SetCursor (hArrowCursor);
    }
    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)) {
// edited by shimomai
    if ((!CommBusy || CommDoc != ThisDoc || CommDecoding)) {
      X = LOWORD (lParam);
      Y = HIWORD (lParam);

      switch (DragMouseAction) {
      case DRAG_NONE:
        WidthCursor = FALSE;
        X -= SideSpace - ThisDoc->ScXOffset * (CharWidth + 1);
        if (Y < LineHeight) {
            WidthCursor = TRUE;
            if (X >= ArtListSeparator1 && X < ArtListSeparator1 + CharWidth) {
                Separator = &ArtListSeparator1;
            }
            else if (X >= ArtListSeparator2 && X < ArtListSeparator2 + CharWidth) {
                Separator = &ArtListSeparator2;
            }
            else if (X >= ArtListSeparator3 && X < ArtListSeparator3 + CharWidth) {
                Separator = &ArtListSeparator3;
            }
            else {
                WidthCursor = FALSE;
            }
        }
        break;

      case DRAG_ON:
        {
          if (!(wParam & MK_LBUTTON)) {    // changed from && JD 12/31/96
            DragMouseAction = DRAG_NONE;
            DrawDragingCursor(hWnd, DragCursorPosition, 0, 1);
        ReleaseCapture ();
          } else {
            DrawDragingCursor(hWnd, DragCursorPosition, X, 2);
            DragCursorPosition = X;
          }
          break;
        }

      default:

      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;
      int X, Y;
      unsigned int Offset;
      long VertLines, RangeHigh;
      long HorzChars;
      int EndofDoc = FALSE;
      long CurPos;
      char indicator;
      TypLineID artindex;
      long 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];
      unsigned long OldHighestSeen;
      COLORREF MyColors[5], MyBack[5];
      RECT aRect;
      int MyColorMask = 0;
      int PrevColorMask = MyColorMask;
      HBRUSH hOldBrush;
      HANDLE hOldFont;
      char AuthorName[MAXINTERNALLINE];
      const char *ptr;     //shimomai

      /* 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 - 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, OrdToScroll(RangeHigh,RangeHigh),
         ThisDoc->ThumbTracking ? FALSE : TRUE);

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

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

      X = SideSpace - ThisDoc->ScXOffset * (CharWidth + 1); //shimomai
      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(!(header->SeenSelected & ART_SEENORKILLED))
      {
            if (OldHighestSeen && (header->number > OldHighestSeen))
          indicator = 'n';
      }
      else if(ART_SEEN & header->SeenSelected)
      {
        indicator = 's';
      }
          else if(ART_KILLED & header->SeenSelected)
          {
        indicator = 'k';
          }

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

          { //ishido DBCS aware author name trimming
            const char *base_p = header->pHD->sFrom;
            const char *p = base_p;
            while ((*p != '\0') && (p - base_p < sizeof(AuthorName)-1))
              p = AnsiNext(p);
            strncpy(AuthorName, (const char*)header->pHD->sFrom, (p - base_p) + 1);
            AuthorName[p - base_p] = '\0';
          }

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


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

          /* Now write out the line. */
/* out article number */
          SetRect (&aRect, 0, Y, X+ArtListSeparator1+CharWidth, Y + LineHeight);
          SetTextAlign (hDC, TA_TOP | TA_RIGHT);
                  _ltoa(artnum, scratch_line, 10);
      ExtTextOut (hDC, X+ArtListSeparator1, Y, ETO_OPAQUE | ETO_CLIPPED, &aRect,
                      scratch_line, strlen(scratch_line), (LPINT) NULL);

/* out indicator */
          SetTextAlign (hDC, TA_TOP | TA_LEFT);
                  TextOut (hDC, X, Y, &indicator, 1);

/* out date */
          SetRect (&aRect, X+ArtListSeparator1+CharWidth, Y, X+ArtListSeparator2, Y + LineHeight);
                  StringDate (scratch_line, header->pHD->date),
          ExtTextOut (hDC, X+ArtListSeparator1+CharWidth, Y, ETO_OPAQUE | ETO_CLIPPED, &aRect,
                                          scratch_line, strlen(scratch_line), (LPINT) NULL);

/* out author name */
          SetRect (&aRect, X+ArtListSeparator2, Y, X+ArtListSeparator3, Y + LineHeight);
          ExtTextOut (hDC, X+ArtListSeparator2, Y, ETO_OPAQUE | ETO_CLIPPED, &aRect,
                      AuthorName, strlen(AuthorName), (LPINT) NULL);

/* out lines */
          SetTextAlign (hDC, TA_TOP | TA_RIGHT);
                 scratch_line[0] = ' ';
                  _ltoa(header->lines, &(scratch_line[1]), 10);
          TextOut (hDC, X+ArtListSeparator3, Y, scratch_line, strlen(scratch_line));

/* out subject */
          SetRect (&aRect, X+ArtListSeparator3, Y, clientRect.right, Y + LineHeight);
          SetTextAlign (hDC, TA_TOP | TA_LEFT);
          ptr = (ThreadFullSubject || !(header->thread_depth))
            ? (const char*)header->pHD->sSubject : ThreadDepthIndicator;
          ExtTextOut (hDC, X+ArtListSeparator3+ CharWidth * (1 + header->thread_depth * 2), Y,
                    ETO_OPAQUE | ETO_CLIPPED, &aRect,
                    (const char*)ptr, strlen(ptr), (LPINT) NULL);
          SetRect (&aRect, 0, Y, clientRect.right, Y + LineHeight);

          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(ART_SELECTED & header->SeenSelected)
          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 ((ART_SELECTED & header->SeenSelected) != (value ? ART_SELECTED: ART_UNSELECTED)) {
    if (value)
    {
      header->SeenSelected |= ART_SELECTED;
      (Doc->SelectedLines)++;
    }
    else
    {
      header->SeenSelected &= ~ART_SELECTED;
      (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->SeenSelected = (ART_SELECTED & header->SeenSelected) |
      ((ART_SEENORKILLED & header->SeenSelected) ? ART_UNSEEN : ART_SEEN);
    return (ART_SEEN & header->SeenSelected);
  }
  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->SeenSelected |= 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->SeenSelected |= ART_SEEN;
  }
  //GroupDoc->pSeendb->MarkSeen(1, GroupDoc->uLoadLast); // HB - catch expired articles
  GroupDoc->HighestPrevSeen = GroupDoc->uLoadLast;
  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 (ART_SELECTED & header->SeenSelected) {
        header->SeenSelected |= 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 (ART_SEENORKILLED & header->SeenSelected) { 
        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 ((ART_SEENORKILLED & header->SeenSelected) == 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 ((ART_SEENORKILLED & header->SeenSelected) == 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 (ART_SEENORKILLED & header->SeenSelected) {
        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 (ART_SELECTED & header->SeenSelected) {
        header->SeenSelected &= ~ART_SEENORKILLED;
    }
  }
  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, ART_SELECTED & ~header->SeenSelected);
    return (ART_SELECTED & header->SeenSelected);
  }
  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->SeenSelected &= ~ART_SEENORKILLED;
    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->SeenSelected |= 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[32];
  WVString sTitle;
  const char far *lpsz;
  HWND hWndGroup = Doc->hDocWnd;
  HANDLE header_handle;
  HANDLE thread_handle;
  header_p headers;
  header_p header;
  HMENU hMenu, hSubMenu;
  char	FromAddress[MAXHEADERLINE];

  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->SeenSelected |= ART_SEEN;
    header->SeenSelected &= ~ART_SELECTED;
    NewsrcDirty = TRUE;
    InvalidateRect (hWndGroup, NULL, FALSE);

    if (articleId == NO_ID)
        lpsz = header->pHD->sSubject;
    else
        lpsz = articleId;

    sTitle = "Retrieving \"";
    sTitle += header->pHD->sSubject;
    sTitle += "\"";

  if (newdoc) {
    char poschars[MAXINTERNALLINE];

    /* Compute default screen position. */
    num = (WrapIncomingArticleText) ? WrapIncomingArticleTextLength : 88;
    num = min (max (15, WrapIncomingArticleTextLength), 88);
    if (xScreen > num * (int) 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",
                            (const char*) sTitle,
                            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, (const char*) sTitle);
  }

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

// allow cancel article even FullNameFrom is not selected (TANAKA Goh	1996/09/20)
// Adopted to new TypHeader struct (Masaki Ishido 1997/01/15)

  GetFromAddress(FromAddress, MAXHEADERLINE, NULL);
  if (FullNameFrom) {
 	 ParseAddress(FromAddress, AddressString, MAXDIALOGSTRING, NameString, MAXDIALOGSTRING);

//  if ((stricmp (header->pHD->sFrom, UserName) == 0) ||
//      (stricmp (header->pHD->sFrom, MailAddress) == 0) ||
//      (stricmp (header->pHD->sFrom, ReplyTo) == 0)) 
//     EnableMenuItem(hSubMenu,IDM_CANCELART,ENABLE_MENU);
//  else
//     EnableMenuItem(hSubMenu,IDM_CANCELART,DISABLE_MENU);

	  if (stricmp(header->pHD->sFrom, NameString) == 0)
	    EnableMenuItem(hSubMenu, IDM_CANCELART, ENABLE_MENU);
    else
	    EnableMenuItem(hSubMenu, IDM_CANCELART, DISABLE_MENU);
   }
  else {
	  if (stricmp(header->pHD->sFrom, FromAddress) == 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)
{
  TypGroup *group;
  TypLine far *ParentLine;
  TypBlock far *ParentBlock;
  HANDLE header_handle, thread_handle;
  header_p headers;
  header_p header;
  BOOL GotLock;
  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)
    return;
  
  group = GetGroup(ParentLine);
  
  if (group->total_headers > 0)
  {
    header_handle = group->header_handle;
    thread_handle = group->thread_handle;
    
    if (!header_handle) // this can happen if user selects "Update from server" then cancel.
      return;
    
    set_index_to_identity (header_handle, thread_handle, group->total_headers);
    headers = lock_headers (header_handle, thread_handle);
    
    artindex = 0;
    header = (header_elt (headers, artindex));
    
    do
    {
      header_p prev_header = header;
      
      header = (header_elt (headers, artindex));
      if(ART_SEENORKILLED & header->SeenSelected)
      {
        group->pSeendb->MarkSeen(header->number);
      }
      else
      {
        group->pSeendb->MarkUnseen(header->number);
      }
    } while(++artindex < Doc->TotalLines);
    unlock_headers (header_handle, thread_handle);
  }
  group->NumUnread = CalcNumUnread(group);
  
  GlobalUnlock (ParentBlock->hCurBlock);
  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);
    }
  }
}

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

  char_p temp;
  
  do {
    temp = (char*)(const char*)(header_elt (headers, artindex))->pHD->sSubject;
    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 = (char*)(const char*)ThisHeader->pHD->sSubject;
    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;
  long 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--;

    if(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->uLoadLast;
      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) {
          long i;
          header_p headers;
          
          headers = lock_headers (group->header_handle, group->thread_handle);
          for(i = 0; i < group->total_headers; i++)
          {
            delete headers[i].pHD;
          }
          unlock_headers (group->header_handle, group->thread_handle);
          free_headers (group->header_handle, group->thread_handle);
        }
        group->header_handle = (HANDLE) NULL;
        group->thread_handle = (HANDLE) NULL;
        group->total_headers = 0;
        delete group->pAA;
        group->pAA = NULL;
      }
    }

    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;
#ifdef COMMCTRL
    tbButton[j].dwData = 0;
    tbButton[j].iString = 0;
#endif

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

    tbButton[j].idCommand = IDM_FIND_NEXT_SAME;
    tbButton[j].fsState = TBSTATE_ENABLED;
    tbButton[j].fsStyle = TBSTYLE_BUTTON;
#ifdef COMMCTRL
    tbButton[j].dwData = 0;
    tbButton[j].iString = 0;
#endif

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

    tbButton[j].fsState = TBSTATE_ENABLED;
    tbButton[j].fsStyle = TBSTYLE_SEP;
#ifdef COMMCTRL
    tbButton[j].dwData = 0;
    tbButton[j].iString = 0;
#endif

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

    tbButton[j].idCommand = IDM_POST;
    tbButton[j].fsState = TBSTATE_ENABLED;
    tbButton[j].fsStyle = TBSTYLE_BUTTON;
#ifdef COMMCTRL
    tbButton[j].dwData = 0;
    tbButton[j].iString = 0;
#endif

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

    tbButton[j].idCommand = IDM_MAIL;
    tbButton[j].fsState = TBSTATE_ENABLED;
    tbButton[j].fsStyle = TBSTYLE_BUTTON;
#ifdef COMMCTRL
    tbButton[j].dwData = 0;
    tbButton[j].iString = 0;
#endif

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

    tbButton[j].fsState = TBSTATE_ENABLED;
    tbButton[j].fsStyle = TBSTYLE_SEP;
#ifdef COMMCTRL
    tbButton[j].dwData = 0;
    tbButton[j].iString = 0;
#endif

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

    tbButton[j].idCommand = IDM_SAVE_SELECTED;
    tbButton[j].fsState = TBSTATE_ENABLED;
    tbButton[j].fsStyle = TBSTYLE_BUTTON;
#ifdef COMMCTRL
    tbButton[j].dwData = 0;
    tbButton[j].iString = 0;
#endif

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

    tbButton[j].idCommand = IDM_DECODE_SELECTED;
    tbButton[j].fsState = TBSTATE_ENABLED;
    tbButton[j].fsStyle = TBSTYLE_BUTTON;
#ifdef COMMCTRL
    tbButton[j].dwData = 0;
    tbButton[j].iString = 0;
#endif

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

    tbButton[j].fsState = TBSTATE_ENABLED;
    tbButton[j].fsStyle = TBSTYLE_SEP;
#ifdef COMMCTRL
    tbButton[j].dwData = 0;
    tbButton[j].iString = 0;
#endif

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

    tbButton[j].idCommand = IDM_MARK_ALL;
    tbButton[j].fsState = TBSTATE_ENABLED;
    tbButton[j].fsStyle = TBSTYLE_BUTTON;
#ifdef COMMCTRL
    tbButton[j].dwData = 0;
    tbButton[j].iString = 0;
#endif

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

    tbButton[j].idCommand = IDV_EXIT;
    tbButton[j].fsState = TBSTATE_ENABLED;
    tbButton[j].fsStyle = TBSTYLE_BUTTON;
#ifdef COMMCTRL
    tbButton[j].dwData = 0;
    tbButton[j].iString = 0;
#endif

    tbButton[++j].iBitmap = 8;  // separator
    tbButton[j].fsState = TBSTATE_ENABLED;
    tbButton[j].fsStyle = TBSTYLE_SEP;
#ifdef COMMCTRL
    tbButton[j].dwData = 0;
    tbButton[j].iString = 0;
#endif

    tbButton[++j].iBitmap = ++i;    /* Help */
    tbButton[j].idCommand = IDM_HELP;    
    tbButton[j].fsState = TBSTATE_ENABLED;
    tbButton[j].fsStyle = TBSTYLE_BUTTON;
#ifdef COMMCTRL
    tbButton[j].dwData = 0;
    tbButton[j].iString = 0;

    ThisDoc->hWndTB = CreateToolbarEx(hWnd,	
                                      CCS_TOP | WS_VISIBLE | TBSTYLE_TOOLTIPS,
                                      IDB_VIEWTOOLBAR,
                                      i + 1,
                                      hInst,
                                      IDB_VIEWTOOLBAR,
                                      tbButton,
                                      j + 1,
                                      16,
                                      16,
                                      16,
                                      16,
                                      sizeof(TBBUTTON));
#else

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

    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->SeenSelected)
              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: // actually "Catch up and exit this group"
      article_operation (ThisDoc, 0, mark_read_all);
      NewsrcDirty = TRUE;
      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;

        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(DialogBoxParam(hInst, "ActionDlg", hWnd, lpfnWinVnArticleActionDlg,
          (LPARAM)group->pAA))
        {
            g_pAction->ActOnArticles(group, headers);
            group->pAA->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) {
              long i;
              header_p headers;
              
              headers = lock_headers (group->header_handle, group->thread_handle);
              for(i = 0; i < group->total_headers; i++)
              {
                delete headers[i].pHD;
              }
              unlock_headers (group->header_handle, group->thread_handle);
            free_headers (group->header_handle, group->thread_handle);
            }
            group->header_handle = (HANDLE) NULL;
            group->thread_handle = (HANDLE) NULL;
            group->total_headers = 0;
            delete group->pAA;
            group->pAA = NULL;

            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);
      SetCursor (hArrowCursor); //shimomai
      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 &&
                !(ART_SELECTED & (header_elt (headers, ThisDoc->savingArtIndex))->SeenSelected)) {
            ThisDoc->savingArtIndex = (ThisDoc->savingArtIndex + 1) % (int) (group->total_headers);
            if (ThisDoc->savingArtIndex == startIndex)
                break;
            }

            if (ThisDoc->savingArtIndex == startIndex &&        /* this shouldn't happen */
                !(ART_SELECTED & (header_elt (headers, ThisDoc->savingArtIndex))->SeenSelected)) {
            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))->SeenSelected |= 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);
}


// WVSeendb member functions

WMemPool WVSeenDB::m_ClassPool(sizeof(WVSeenDB), POOLBLOCKSIZE / sizeof(WVSeenDB));


// Create bitmap of articles in the range uLo to uHi.
// Init all, either taking possibly overlapping historical information
// from pre-existing info, otherwise unseen
WVSeenDB::WVSeenDB(uint32 uLo, uint32 uHi, const WVSeenDB*pSDB)
: m_uLowVal(min(uLo, uHi)), m_uHiVal(max(uLo, uHi)),
  m_uLowBase((m_uLowVal/BMSIZE) * BMSIZE), m_uHiBase((m_uHiVal/BMSIZE) * BMSIZE)
{
  uint32 i;
  uint32 idx1, idx2;
  uint32 uBase1, uBase2;
  uint32 uNumInts = 1 + (m_uHiBase - m_uLowBase)/BMSIZE;
  
//#ifdef _DEBUG
//  TRACE4("WVSeenDB(): SeenDB created from %ld (%ld)-%ld (%ld)\n",
//    m_uLowBase, m_uLowVal, m_uHiBase + BMSIZE-1, m_uHiVal);
//  if(pSDB)
//  {
//    TRACE4(" Old range was %ld (%ld)-%ld (%ld)\n",
//      pSDB->m_uLowBase, pSDB->m_uLowVal, pSDB->m_uHiBase + BMSIZE-1, pSDB->m_uHiVal);
//  }
//#endif
  
// m_pBitmap = new unsigned int[uNumInts]; 

// The new operator will return a data structure
// that is uNumInts * 4 in length. If this is
// greater than 64K then we need to deal with
// huge pointers.

  if (uNumInts < (INT_MAX / 4)) { 
    m_pBitmap = new unsigned long[uNumInts];
  }
  else {
#ifdef _WIN32
	m_pBitmap = new __far unsigned long[4,uNumInts];  
#else 
    m_pBitmap = new __huge unsigned long[4,(unsigned long) uNumInts];
  	
#endif
  }
  
  if(m_pBitmap)
  {
    if(pSDB)
    {
      uBase1 = this->m_uLowBase;
      uBase2 = pSDB->m_uLowBase;
      idx2 = 0;
      while(uBase2 < this->m_uLowBase)
      {
        idx2++;
        uBase2 += BMSIZE;
      }
      for(idx1 = 0; idx1 < uNumInts; idx1++)
      {
        if(uBase1 < uBase2)
          m_pBitmap[idx1] = (unsigned int) (int) -1;
        else if((uBase1 == uBase2) && uBase2 <= pSDB->m_uHiBase)
        {
          this->m_pBitmap[idx1] = pSDB->m_pBitmap[idx2++];
          uBase2 += BMSIZE;
        }
        else
          m_pBitmap[idx1] = 0;
        uBase1 += BMSIZE;
      }
    }
    else // pSDB is NULL, init all bits to zero
    {
      for(i = 0; i < uNumInts; i++)
        m_pBitmap[i] = 0;
    }
  }
#ifdef _DEBUG
  else
    DEBUG_BREAK; // Unable to allocate bitmap
#endif
}

  
// Mark an article as 'seen'
void WVSeenDB::MarkSeen(uint32 uArtIdx)
{
  uint32 uArrIdx;
  
  if(this && m_pBitmap)
  {
    if((uArtIdx >= m_uLowVal) && (uArtIdx <= m_uHiVal))
    {
      uArrIdx = (uArtIdx - m_uLowBase) / BMSIZE;
      m_pBitmap[uArrIdx] |= 1 << (uArtIdx % BMSIZE);
    }
  }
}


// Mark a range of articles as 'seen'
void WVSeenDB::MarkSeen(uint32 uArtIdx1, uint32 uArtIdx2)
{
  uint32 uBitIdx1, uBitIdx2;
  uint32 uArrIdx1, uArrIdx2;
  
  if(this && m_pBitmap)
  {
    uArtIdx1 = max(uArtIdx1, m_uLowBase);
    uArtIdx1 = min(uArtIdx1, m_uHiVal);
    uArtIdx2 = max(uArtIdx1, uArtIdx2);
    uArtIdx2 = min(uArtIdx2, m_uHiVal);
    uArrIdx1 = (uArtIdx1 - m_uLowBase) / BMSIZE;
    uArrIdx2 = (uArtIdx2 - m_uLowBase) / BMSIZE;
    uBitIdx2 = (uArrIdx1 == uArrIdx2) ? uArtIdx2 % BMSIZE : BMSIZE - 1;
    
    for(uBitIdx1 = uArtIdx1 % BMSIZE; uBitIdx1 <= uBitIdx2; uBitIdx1++)
    {
      m_pBitmap[uArrIdx1] |= 1 << uBitIdx1;
    }
    
    if(uArrIdx1 < uArrIdx2)
    {
      for(uArrIdx1++; uArrIdx1 < uArrIdx2; uArrIdx1++)
        m_pBitmap[uArrIdx1] = (unsigned int) (int) -1;
      
      uBitIdx2 = uArtIdx2 % BMSIZE;
      for(uBitIdx1 = 0; uBitIdx1 <= uBitIdx2; uBitIdx1++)
        m_pBitmap[uArrIdx2] |= 1 << uBitIdx1;
    }
  }
}


// Mark an article as 'unseen'
void WVSeenDB::MarkUnseen(uint32 uArtIdx)
{
  uint32 uArrIdx;
  
  if(this && m_pBitmap)
  {
    if((uArtIdx >= m_uLowVal) && (uArtIdx <= m_uHiVal))
    {
      uArrIdx = (uArtIdx - m_uLowBase) / BMSIZE;
      m_pBitmap[uArrIdx] &= ~(1 << (uArtIdx % BMSIZE));
    }
  }
}


// Has a particular article been 'seen'?
BOOL WVSeenDB::WasSeen(uint32 uArtIdx)
{
  uint32 uArrIdx;
  
  if(this && m_pBitmap && (uArtIdx <= m_uHiVal))
  {
    if(uArtIdx < m_uLowVal && uArtIdx)
      return TRUE;
    uArrIdx = (uArtIdx - m_uLowBase)/ BMSIZE;
    if(m_pBitmap[uArrIdx] & (1 << (uArtIdx % BMSIZE)))
      return TRUE;
  }
  return FALSE;
}


// Count number of unseen articles
uint32 WVSeenDB::NumUnseen()
{
  uint32 uBitIdx1, uBitIdx2;
  uint32 uArrInd;
  uint32 uMaxInd;
  uint32 uRetval = 0;
  
  if(this && m_pBitmap)
  {
    uMaxInd = (m_uHiBase - m_uLowBase)/BMSIZE;
    
    uBitIdx2 = uMaxInd ? BMSIZE - 1 : m_uHiVal % BMSIZE;
    for(uBitIdx1 = m_uLowVal % BMSIZE; uBitIdx1 <= uBitIdx2; uBitIdx1++)
    {
      if(!(m_pBitmap[0] & (1 << uBitIdx1)))
        uRetval++;
    }
    if(uMaxInd) // more than one array member
    {
      uBitIdx2 = m_uHiVal % BMSIZE;
      for(uBitIdx1 = 0; uBitIdx1 <= uBitIdx2; uBitIdx1++)
      {
        if(!(m_pBitmap[uMaxInd] & (1 << uBitIdx1)))
          uRetval++;
      }
      for(uArrInd = 1; uArrInd < uMaxInd; uArrInd++)
      {
        if(m_pBitmap[uArrInd] != (unsigned int) (int) -1) // all seen
        {
          if(m_pBitmap[uArrInd] == 0) // all unseen
            uRetval += BMSIZE;
          else  // count 'em
          {
            for(uBitIdx1 = 0; uBitIdx1 < BMSIZE; uBitIdx1++)
            {
              if(!(m_pBitmap[uArrInd] & (1 << uBitIdx1)))
                uRetval++;
            }
          }
        }
      }
    }
  }
  return uRetval;
}

// Find the highest seen article
// Return:
//   Highest seen article (if found)
//   Lowest article number if all seen
uint32 WVSeenDB::HighestSeen()
{
  uint32 uRetval;

  for(uRetval = m_uHiVal; uRetval >= m_uLowVal; uRetval--)
  {
    if(WasSeen(uRetval))
      return uRetval;
  }
  return m_uLowVal ? m_uLowVal - 1 : 0;
}


// Find the next unseen article, starting with 'uNext'
// Return:
//   Next unseen article number (if found)
//   Highest article number + 1 if all seen
//   0 if invalid, no bitmap available, etc.
uint32 WVSeenDB::NextUnseen(uint32 uNext)
{
  uint32 uBitIdx1;
  uint32 uBitIdx2;
  uint32 uArrIdx;
  uint32 uMaxInd;
  uint32 uRetval = 0;

  if(this && m_pBitmap)
  {
    //if(uNext > m_uHiVal)
    //  return m_uHiVal + 1;
    uRetval = max(uNext, m_uLowVal);
    uMaxInd = (m_uHiBase - m_uLowBase)/BMSIZE;

    for(uArrIdx = (uRetval - m_uLowBase)/ BMSIZE; uArrIdx <= uMaxInd; uArrIdx++)
    {
      uBitIdx1 = uRetval % BMSIZE;
      if((!uBitIdx1) && m_pBitmap[uArrIdx] == (unsigned int) (int) -1) // all seen
        uRetval += BMSIZE;
      else
      {
        uBitIdx2 = (uArrIdx == uMaxInd) ? m_uHiVal % BMSIZE : BMSIZE - 1;
        while(uBitIdx1 <= uBitIdx2)
        {
          if(!(m_pBitmap[uArrIdx] & (1 << uBitIdx1)))
            return uRetval;
          uBitIdx1++;
          uRetval++;
        }
      }
    }
    if(uRetval > m_uHiVal)
    {
      if(uRetval != m_uHiVal + 1)
        DEBUG_BREAK;
      uRetval = m_uHiVal + 1;
    }
  }
  return uRetval;
}


// Find the next seen article, starting with 'uNext'
// Return:
//   Next seen article number (if found)
//   0 if all seen, invalid, no bitmap available, etc.
uint32 WVSeenDB::NextSeen(uint32 uNext)
{
  uint32 uBitIdx1;
  uint32 uBitIdx2;
  uint32 uArrIdx;
  uint32 uMaxInd;
  uint32 uRetval = 0;

  if(this && m_pBitmap && uNext <= m_uHiVal)
  {
    if(uNext < m_uLowVal)
      return uNext;
    uMaxInd = (m_uHiBase - m_uLowBase)/BMSIZE;
    uRetval = uNext;

    for(uArrIdx = (uRetval - m_uLowBase)/ BMSIZE; uArrIdx <= uMaxInd; uArrIdx++)
    {
      uBitIdx1 = uRetval % BMSIZE;
      if((!uBitIdx1) && m_pBitmap[uArrIdx] == (unsigned int) (int) 0) // all unseen
        uRetval += BMSIZE;
      else
      {
        uBitIdx2 = (uArrIdx == uMaxInd) ? m_uHiVal % BMSIZE : BMSIZE - 1;
        while(uBitIdx1 <= uBitIdx2)
        {
          if(m_pBitmap[uArrIdx] & (1 << uBitIdx1))
            return uRetval;
          uBitIdx1++;
          uRetval++;
        }
      }
    }
    if(uRetval > m_uHiVal)
      uRetval = 0;
  }
  return uRetval;
}


// Get the next range.  Return {0,0} at end of range or invalid
void WVSeenDB::GetRange(TypRange& r)
{
  if(this && m_pBitmap)
  {
    r.First = NextSeen(r.Last + 1);
    if(r.First)
    {
      r.Last = NextUnseen(r.First + 1) - 1;
      if(r.Last <= m_uHiVal)
        return;
    }
  }
  
  r.First = r.Last = 0;
  return;
}

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


