// -*- 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.78 1996/08/13 04:45:48 tanaka Exp $
 *
 */

#include <windows.h>
#include <windowsx.h>           // for GlobalFreePtr (JSC)
//extern "C"
//{
#include "wvglob.h"
#include "winvn.h"
//}
#pragma hdrstop
extern "C"
{
#include "wvtb\wvtb.h"          /* for toolbar */
}
#include "WVClass.h"
#include <stdlib.h>
#include <assert.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)) {
		    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;
      int VertLines, HorzChars;
      int EndofDoc = FALSE;
      int RangeHigh, CurPos;
      char indicator;
      TypLineID artindex;
      long int artnum;
      TypBlock far *NetBlockPtr;
      TypLine far *NetLinePtr;
      TypGroup far *group;
      header_p headers;
      header_p header;
      HANDLE header_handle;
      HANDLE thread_handle;
      char scratch_line[MAXINTERNALLINE];
      long OldHighestSeen;
      COLORREF MyColors[5], MyBack[5];
      RECT aRect;
      int MyColorMask = 0;
      int PrevColorMask = MyColorMask;
      HBRUSH hOldBrush;
      HANDLE hOldFont;
			char AuthorName[32]; //shimomai
			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 - VertLines;
      RangeHigh = ThisDoc->TotalLines - ThisDoc->ScYLines;
      if (RangeHigh < 0) {
        RangeHigh = 0;
        ThisDoc->TopLineOrd = 0;
        VertLines = ThisDoc->TotalLines;
      }
      CurPos = ThisDoc->TopLineOrd;
      if (CurPos < 0)
        CurPos = 0;

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

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

	  X = SideSpace - ThisDoc->ScXOffset * (CharWidth + 1); //shimomai
//      GetTextExtentPoint (hDC, "s 99999 ", 8, &sz);
//      indicatorwidth = sz.cx;

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

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

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

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

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

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

          ptr = scratch_line;

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


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

          /* Now write out the line. */
/* out article number */
		  SetRect (&aRect, 0, Y, X+ArtListSeparator1+CharWidth, Y + LineHeight);
		  SetTextAlign (hDC, TA_TOP | TA_RIGHT);
                  _ltoa(artnum, ptr, 10);
      ExtTextOut (hDC, X+ArtListSeparator1, Y, ETO_OPAQUE | ETO_CLIPPED, &aRect,
                      ptr, strlen(ptr), (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 (ptr, header->date),
		  ExtTextOut (hDC, X+ArtListSeparator1+CharWidth, Y, ETO_OPAQUE | ETO_CLIPPED, &aRect,
                                          ptr, strlen(ptr), (LPINT) NULL);

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

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

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

//          ExtTextOut (hDC, X, Y, ETO_OPAQUE | ETO_CLIPPED, &aRect,
//                      scratch_line, MyLen, (LPINT) NULL);
          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)
{
//  int32 retval = -1;
  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 = 0;
    artindex = -1;

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


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

  return value;
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

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

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

    header_handle = GroupDoc->header_handle;
    thread_handle = GroupDoc->thread_handle;
    headers = lock_headers (header_handle, thread_handle);
    header = header_elt (headers, artindex);

    if ((articleId == NO_ID) &&
        (header->ArtDoc != NULL) &&
        (header->ArtDoc->CountedLines == header->lines)){

        /* We already have a document containing the article */
        /* so just activate it.                */

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

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

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

    newdoc = FALSE;

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

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

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

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

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

  if (newdoc) {
    char poschars[MAXINTERNALLINE];

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

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

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

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

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

  InitDoc (CommDoc, hWndArt, Doc, DOCTYPE_ARTICLE);

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

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

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

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

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

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


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

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

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

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

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

  if (!IsMinimized(CommDoc->hWndFrame)) {
    SendMessage (CommDoc->hWndFrame, WM_MYINITMENU, 0, 0L);
  }

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

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

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

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


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

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

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

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

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

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

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

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

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

  if (group->total_headers > 0) {

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

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

#if 0	// TANAKA Goh (newsrc patch)
	group->nRanges = 0;
	group->nSpareRanges	= SpareRanges;

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

	RangePtr = GetRangePtr(group);
	MyRange.First = MyRange.Last = 1;
#else	// TANAKA Goh (newsrc patch)
	if (group->ServerEstNum == group->total_headers) {
    group->nRanges = 0;
    group->nSpareRanges = SpareRanges;

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

    RangePtr = GetRangePtr(group);
    MyRange.First = MyRange.Last = 1;
	} else {
	  TypRange far *PrevRangePtr = (TypRange far *)((char far *)group + RangeOffset(group->NameLen));
	  unsigned int	nPrevRanges = group->nRanges;

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

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

	  RangePtr = GetRangePtr(group);

	  for (unsigned int i = 0 ; i < nPrevRanges ; i++) {
		if (PrevRangePtr[i].First <= PrevRangePtr[i].Last) {
		  if (PrevRangePtr[i].First < group->ServerFirst) {
			if (PrevRangePtr[i].Last < group->ServerFirst - 1) {
			  InSeen = FALSE;
			  *(RangePtr++) = PrevRangePtr[i];
			  (group->nRanges)++;
			} else {
			  InSeen = TRUE;
			  MyRange.First = PrevRangePtr[i].First;
			  MyRange.Last = group->ServerFirst - 1;
			  break;
			}
		  } else {
			InSeen = FALSE;
			break;
		  }
		}
	  }

	  if (InSeen == FALSE) {
		InSeen = TRUE;
		MyRange.First = group->ServerFirst;
	  }
	}
#endif	// TANAKA Goh (newsrc patch)

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

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

    artindex = 0;
    if (!Doc->TotalLines) {
#if 0	// TANAKA Goh (newsrc patch)
      MyRange.Last = 1;
#else	// TANAKA Goh (newsrc patch)
	  MyRange.Last = group->ServerLast;

	  if (MyRange.First <= MyRange.Last) {
		*(RangePtr++) = MyRange;
		(group->nRanges)++;
	  }
#endif	// TANAKA Goh (newsrc patch)
    }
    else {
      header = (header_elt (headers, artindex));
      MyRange.Last = header->number - 1;

      /* Loop to scan through the document, fabricating article ranges.*/
      do {
#if 1	// TANAKA Goh (newsrc patch)
		header_p	prev_header = header;
#endif	// TANAKA Goh (newsrc patch)

        header = (header_elt (headers, artindex));
#if 1	// TANAKA Goh (newsrc patch)
		if (header->number - prev_header->number > 1) {
		  if (InSeen) {
			/* Continuing a sequence of seen articles.            */
			MyRange.Last = header->number - 1;
		  }
		  else {
			/* Starting a new sequence of seen articles.          */
			MyRange.First = prev_header->number + 1;
			MyRange.Last = header->number - 1;
			InSeen = TRUE;
		  }
		}
#endif	// TANAKA Goh (newsrc patch)
        if (header->Seen) {
          if (InSeen) {
            /* Continuing a sequence of seen articles.*/
            MyRange.Last = header->number;
          }
          else {
            /* Starting a new sequence of seen articles.*/
            MyRange.First = header->number;
            MyRange.Last = header->number;
            InSeen = TRUE;
          }
        }
        else {
          if (InSeen) {
            /* Ending a sequence of seen articles.*/
            InSeen = FALSE;
            if (MyRange.First <= MyRange.Last) {
              *(RangePtr++) = MyRange;
              (group->nRanges)++;
            }
          }
          else {
            /* Continuing a sequence of unseen articles.*/
          }
        }
      }
      while ((group->nRanges < maxRanges) &&
             ((++artindex < Doc->TotalLines)));

#if 0	// TANAKA Goh (newsrc patch)
      if (InSeen) {
        *(RangePtr++) = MyRange;
        (group->nRanges)++;
      }
#else	// TANAKA Goh (newsrc patch)
	  if (InSeen) {
		MyRange.Last = group->ServerLast;

		if (MyRange.First <= MyRange.Last) {
		  *(RangePtr++) = MyRange;
		  (group->nRanges)++;
		}
	  } else {
		MyRange.First = header->number + 1;
		MyRange.Last = group->ServerLast;

		if (MyRange.First <= MyRange.Last) {
		  *(RangePtr++) = MyRange;
		  (group->nRanges)++;
		}
	  }
#endif	// TANAKA Goh (newsrc patch)
    }

  }
  group->NumUnread = CalcNumUnread(group);
  unlock_headers (header_handle, thread_handle);

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

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

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

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

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

  char_p temp;

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

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

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

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

  return (NULL);
}


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

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

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

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

    return TRUE;
    break;
  }
  return FALSE;
}

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

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

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

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

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

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

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

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

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


  MyDoc->ActiveLineID = newActive;

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

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

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

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

  if (!found)
    ThisDoc = CommDoc;

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

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

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

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

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

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

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

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

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

      /*CN  if (group->total_headers != 0)  */
      if (CN_Total_Lines != 0) {    /* CN */
        UpdateSeenArts (ThisDoc);
        UnlinkArtsInGroup (ThisDoc);
        if (group->header_handle) {
#if 0
          /* Is this necessary? SMR 941221 */
          unlock_headers (group->header_handle, group->thread_handle);
#endif
          free_headers (group->header_handle, group->thread_handle);
        }
        group->header_handle = (HANDLE) NULL;
        group->thread_handle = (HANDLE) NULL;
        group->total_headers = 0;
      }
    }

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    break;

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

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

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

        /* this dependency on [thread_array_p][head0][head1][head2]... */
        /* should be moved into headarry.c */
        headers = lock_headers (header_handle, thread_handle);
        thread_index = *((thread_array_p) ((char_p) headers - sizeof (char_p)));

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

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

        sort_by_option(headers, thread_index, threadOk, ThisDoc->TotalLines,
           header_handle, thread_handle);
        unlock_headers (header_handle, thread_handle);
        InvalidateRect (ThisDoc->hDocWnd, NULL, FALSE);
    }
    break;

  case WM_AA_ENABLE_DISABLE:
    hMenu = GetMenu (hWnd);
    hSubMenu = GetSubMenu (hMenu, 1);   /* sorting menu */
    EnableMenuItem (hSubMenu, IDM_VIEW_ACTION, bEnableArticleAction ? ENABLE_MENU : DISABLE_MENU);
    break;

  case WM_COMMAND:
    switch (LOWORD (wParam)) {
    case IDV_EXIT:
      CloseGroupWnd (hWnd, ThisDoc);
      return (0);
      break;

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

    case IDV_NEXT:
      break;

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

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

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

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

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

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

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

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

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

        headers = lock_headers (header_handle, thread_handle);

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

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

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

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


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


    case IDM_FIND:
    case IDM_FIND_NEXT_SAME:
      FindDoc = ThisDoc;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            SetGroupMenus (ThisDoc, DISABLE);

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

      /* the following msgs are sent by wvutil engine on completion
       * retrieval
       */
    case ID_RETRIEVE_COMPLETE:
      SetGroupMenus (ThisDoc, ENABLE);
      SetStatbarText (ThisDoc->hWndFrame, "", ThisDoc, TRUE, TRUE);
	  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 &&
                (header_elt (headers, ThisDoc->savingArtIndex))->Selected == FALSE) {
            ThisDoc->savingArtIndex = (ThisDoc->savingArtIndex + 1) % (int) (group->total_headers);
            if (ThisDoc->savingArtIndex == startIndex)
                break;
            }

            if (ThisDoc->savingArtIndex == startIndex &&        /* this shouldn't happen */
                (header_elt (headers, ThisDoc->savingArtIndex))->Selected == FALSE) {
            ThisDoc->SelectedLines = 0;
            }

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

                if (CodingState != INACTIVE) DecodeDone ();

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

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

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

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