/********************************************************************
 *                                                                  *
 *  MODULE    :  WVCODEWN.C                                         *
 *                                                                  *
 *  PURPOSE   : This file contains the window procs for coding      *
 *              status windows                                      *                                 
 *                                                                  *
 * Author: John S. Cooper (jcooper@netcom.com)                      *
 *   Date: Sept 30, 1993                                            *
 ********************************************************************/

/* 
 * $Id: wvcodewn.c 1.12 1996/08/13 04:37:28 dumoulin Exp $
 */

#include <windows.h>
#include <windowsx.h>
#include "wvglob.h"
#include "winvn.h"
#pragma hdrstop
#include <STDARG.H>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>

/* ------------------------------------------------------------------------
 *    Window Proc for block coding status window
 *  Simple fixed text display 
 *  Associated with TypCoded  (always currentCoded)
 *  Fixed size, only ever one on screen, destroyed on block coding complete
 *  To avoid redrawing the entire status window on each update,
 *  wParam in association with WM_PAINT decides what to draw this time:
 */
LRESULT CALLBACK  WinVnCodedBlockWndProc (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	HDC hDC;
	HMENU hMenu;
	HFONT hOldFont;
	PAINTSTRUCT ps;
	HWND hButton;
	void DrawStatusFrame (HDC hDC);
	void DrawStatusName (HDC hDC);
	void DrawStatusLines (HDC hDC);
	void DrawStatusBytes (HDC hDC);
	void DrawStatusSeq (HDC hDC);
	void DrawStatusActivity (HDC hDC);

	switch (message) 
	{
	case WM_CREATE:
		hMenu = GetSystemMenu (hWnd, FALSE);
		AppendMenu (hMenu, MF_SEPARATOR, 0, (LPSTR) NULL);
		AppendMenu (hMenu, MF_STRING, IDM_ALWAYSONTOP, "Always On Top");
		SetWindowLongPtr(hWnd, GWLP_USERDATA, 0);


		if (BlockCodingStatusAlwaysOnTop) 
		{
			SetWindowPos (hWnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
			CheckMenuItem (hMenu, IDM_ALWAYSONTOP, MF_BYCOMMAND | MF_CHECKED);
		}
		else 
		{
			SetWindowPos (hWnd, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
			CheckMenuItem (hMenu, IDM_ALWAYSONTOP, MF_BYCOMMAND | MF_UNCHECKED);
		}
		break;
	case WM_ABORTBUTTON:
		hButton = (HWND) GetDlgItem(hWnd, IDABORT);
		if (wParam)
		{
			RECT client;
			int Width = STATUSWIDTH/10;
			int Height = STATUSHEIGHT/6; 
			SetWindowLongPtr(hWnd, GWLP_USERDATA, lParam);
			if (IsMinimized(hWnd))
				ShowWindow(hWnd, SW_SHOWNORMAL);
			
			if (hButton)
			{
				EnableWindow(hButton, TRUE);
				break;
			}
			GetClientRect (hWnd, &client);
			InflateRect(&client, 2*-GetSystemMetrics( SM_CXDLGFRAME), 2*-GetSystemMetrics(SM_CYDLGFRAME));

			hButton = CreateWindow( "BUTTON", "Abort", 
                         WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, 
                         client.right - Width, client.top, Width, 
                         Height, hWnd, 
                         (HMENU) IDABORT, 
                         (HINSTANCE) GetWindowLongPtr(hWnd, GWLP_HINSTANCE), NULL );
			
			 
		}
		else
		{
			if ( hButton && !GetWindowLongPtr(hWnd, GWLP_USERDATA ) )
			{
				EnableWindow(hButton, FALSE);
			}
		}
		break;

	case WM_COMMAND:
		if (HIWORD(wParam) == BN_CLICKED) 
			switch(LOWORD(wParam)) 
			{

				case IDABORT:
				{
					HWND hCancel = (HWND) GetWindowLongPtr(hWnd, GWLP_USERDATA );
					if (hCancel)
					{
						TRACE("Abort button pressed");
						PostMessage(hCancel, WM_COMMAND, IDM_CANCEL_DOWNLOAD, 0);
					}
					else
					{
					
						if (CodingState == ATTACH_WAITING)
							DoSend(UNKNOWN);
						else
							DoSend(ABORT);
					}
				}
				break;
				default:
					TRACE("what?");
				break;
			}
			break;


	case WM_SYSCOMMAND:
		if (wParam == IDM_ALWAYSONTOP) 
		{
			hMenu = GetSystemMenu (hWnd, FALSE);
			BlockCodingStatusAlwaysOnTop = !(GetMenuState (hMenu, IDM_ALWAYSONTOP, MF_BYCOMMAND) & MF_CHECKED);
			RefreshCodedBlockWnd ();
		}
		else 
		{
			return (DefWindowProc (hWnd, message, wParam, lParam));
		}
		break;

	case WM_SIZE:
	{
		RECT client;
		if (wParam != SIZE_MINIMIZED) 
		{
			GetWindowRect (hWnd, &client);
			MoveWindow (hWnd, client.left, client.top, STATUSWIDTH, STATUSHEIGHT, TRUE);
		}
		break;
	}
  case WM_ERASEBKGND:

        {
			HDC hDC = (HDC) wParam;
			if (g_hPalette && hStatusBackgroundBrush)
			{
				
				if (g_hPalette)
				{
					SelectPalette (hDC, g_hPalette, TRUE) ;
					RealizePalette (hDC) ;				
				}
				
				HBRUSH hOldBrush = (HBRUSH) SelectObject(hDC, hStatusBackgroundBrush);
		
        
				RECT rect;
				GetClientRect(hWnd, &rect );
				PatBlt( hDC,  rect.left, rect.top, 
						 RectWidth(rect), RectHeight(rect),PATCOPY);
				SelectObject(hDC, hOldBrush);
				return 1;
			}
		}
		return (DefWindowProc (hWnd, message, wParam, lParam));

	break;


	case WM_PAINT:
		hDC = BeginPaint (hWnd, &ps);
		if (g_hPalette)
		{
			SelectPalette(hDC, g_hPalette, FALSE);
			RealizePalette(hDC);
		}

		SetBkColor (hDC, StatusBackgroundColor);
		SetTextColor (hDC, StatusTextColor);
		hOldFont = (HFONT)SelectObject (hDC, hStatusFont);
		if (currentCoded == NULL)
			DrawStatusFrame (hDC);
		else 
		{
			DrawStatusFrame (hDC);
			DrawStatusName (hDC);
			DrawStatusLines (hDC);
			DrawStatusBytes (hDC);
			DrawStatusSeq (hDC);
			DrawStatusActivity (hDC);
		}
		SelectObject (hDC, hOldFont);
		EndPaint (hWnd, &ps);
		break;

	case WM_CLOSE:
		if (CodingState)
			WVMessageBox (hWnd, IDS_WVCODEWN_WAIT, IDS_WVCODEWN_WAIT_T, MB_OK | MB_ICONSTOP);
		else
			DestroyWindow (hWnd);

		break;
	case WM_DESTROY:
	{
		hButton = (HWND) GetDlgItem(hWnd, IDABORT );
		if (hButton)
		{
			DestroyWindow(hButton);
			SetWindowLongPtr(hWnd, GWLP_USERDATA, (LONG_PTR) 0);
		}
		break;
	}
	
	default:
		return (DefWindowProc (hWnd, message, wParam, lParam));
	}

	return (0);
}

void WVArgMessageLog(const char *format, ...)
{
  char buf[4096];
  va_list ar;

  va_start(ar, format);
  _vsnprintf(buf, sizeof(buf), format, ar);
  va_end(ar);
  WVMessageLog(buf);
}

void WVMessageLog(const char *text)
{
  char mybuf[MAXINTERNALLINE];
  int width, height, maxX, maxY, x, y;

  if (!DebugText) {
    DebugText = InitTextBlock(NULL);
    if (DebugText == NULL) {
      return;
    }
    width = xScreen >> 1;
    height = (yScreen >> 1) - CaptionHeight;
    maxX = 3 * (width >> 1);   /* 3/4 screen width */
    maxY = 3 * (height >> 1);  /* 3/4 screen height */
    x = 1;
    y = yScreen >> 2;

    GetPrivateProfileString (CODING, "DecodeStatusWindowPos", "!",
                                      mybuf, MAXINTERNALLINE, szAppProFile);
    if (*mybuf != '!') {
      sscanf (mybuf, "%d,%d,%d,%d", &x, &y, &width, &height);
    }
    DebugText->hTextWnd = NULL;
    DebugText->hTextWnd =
        CreateWindow ("WinVnCoding", "log window",
                  WS_OVERLAPPEDWINDOW | WS_VSCROLL | WS_HSCROLL,
                  (x + (NumStatusTexts * StatusCharWidth)) % maxX,
                  (y + (NumStatusTexts * StatusLineHeight)) % maxY, 
                  width, height,
                  (HWND) NULL, (HMENU) NULL, hInst, (void far *) NULL);
    if (DebugText->hTextWnd == (HWND) NULL) {
      FreeTextBlock (DebugText);
      DebugText = NULL;
      return;
    }
    SetHandleBkBrush (DebugText->hTextWnd, hStatusBackgroundBrush);
    ShowWindow (DebugText->hTextWnd, SW_SHOWNORMAL);
    UpdateWindow (DebugText->hTextWnd);
  } else {
    if (DebugText->numBytes >= 2 * 1024L * 1024L) {
      return;
    }
  }

  AddLineToTextBlock (DebugText, text);
  InvalidateRect (DebugText->hTextWnd, NULL, FALSE);
}

void RefreshCodedBlockWnd ()
{
  HMENU hMenu;

  if (hCodedBlockWnd) {
	hMenu = GetSystemMenu (hCodedBlockWnd, FALSE);

	if (BlockCodingStatusAlwaysOnTop) {
	  SetWindowPos (hCodedBlockWnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
	  CheckMenuItem (hMenu, IDM_ALWAYSONTOP, MF_BYCOMMAND | MF_CHECKED);
	}
	else {
	  SetWindowPos (hCodedBlockWnd, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
	  CheckMenuItem (hMenu, IDM_ALWAYSONTOP, MF_BYCOMMAND | MF_UNCHECKED);
	}
	SetHandleBkBrush (hCodedBlockWnd, hStatusBackgroundBrush);
	if (!IsMinimized (hCodedBlockWnd)) {
	  SendMessage (hCodedBlockWnd, WM_SIZE, 0, 0L);		/* force a good resize */
	}
	InvalidateRect (hCodedBlockWnd, NULL, TRUE);
  }
}
/* ------------------------------------------------------------------------
 *   Routines for drawing coding block status window
 *   2 columns, 3 lines
 *
 *   HTITLE1       HTEXT1        HTITLE2  HTEXT2
 *   !             !             !        !
 *   Retrieving    ___________                          <- YOFFSET
 *   Lines Read    __________    Activity ___________   <- YOFFSET+YSPACE
 *   Bytes Decoded __________    Sequence ___________   <- YOFFSET+2*YSPACE
 */
#define HTITLE1 (3*StatusCharWidth)
#define HTITLE2 (45*StatusCharWidth)
#define HTEXT1  (22*StatusCharWidth)
#define HTEXT2  (57*StatusCharWidth)
#define YOFFSET (StatusLineHeight)
#define YSPACE  (int)(StatusLineHeight*1.5)

void
DrawStatusFrame (HDC hDC)
{
  if (CodingState >= ATTACH_PROCESSING) {
	TextOut (hDC, HTITLE1, YOFFSET, "Processing", 10);
	TextOut (hDC, HTITLE1, YOFFSET + YSPACE, "Lines sent", 10);
	TextOut (hDC, HTITLE1, YOFFSET + 2 * YSPACE, "Bytes sent", 10);
	TextOut (hDC, HTITLE2, YOFFSET+2*YSPACE, "Sequence", 8);
  }
  else {
	TextOut (hDC, HTITLE1, YOFFSET, "Retrieving", 10);
	TextOut (hDC, HTITLE1, YOFFSET + YSPACE, "Lines read", 10);
	TextOut (hDC, HTITLE1, YOFFSET + 2 * YSPACE, "Bytes decoded", 13);
	TextOut (hDC, HTITLE2, YOFFSET+2*YSPACE, "Sequence", 8);
  }
  TextOut (hDC, HTITLE2, YOFFSET + YSPACE, "Activity", 8);
}

void
DrawStatusName (HDC hDC)
{
  char temp[MAXINTERNALLINE];
  RECT aRect;

  if (currentCoded->name[0] == '\0' && currentCoded->ident[0] == '\0') {
	if (CodingState >= ATTACH_PROCESSING)
	  strcpy (temp, "Article text");
	else
	  strcpy (temp, "Name unknown");
  }
  else {
	if (currentCoded->name[0] != '\0')	// len must be <= 20    
	 {
		if (strchr(currentCoded->ident, '\\') )
			NameWithoutPath (temp, currentCoded->ident);	//cut off any path
		else
			strncpy(temp, currentCoded->ident, MAXINTERNALLINE-1);

	}
	else 
	{
		if (strchr(currentCoded->ident, '\\') )
			NameWithoutPath (temp, currentCoded->ident);	//cut off any path
		else
			strncpy(temp, currentCoded->ident, MAXINTERNALLINE-1);

	}
  }

  // be sure to erase old name 
  SetRect (&aRect, HTEXT1, YOFFSET, STATUSWIDTH - 1, YOFFSET + YSPACE - 1);
  ExtTextOut (hDC, HTEXT1, YOFFSET, ETO_OPAQUE | ETO_CLIPPED, &aRect,
			  temp, lstrlen (temp), (LPINT) NULL);
}
void
DrawStatusLines (HDC hDC)
{
  int len;
  RECT aRect;

  len = sprintf (str, "%lu", currentCoded->numLines);
  SetRect (&aRect, HTEXT1, YOFFSET + YSPACE, HTITLE2 - 1, YOFFSET + 2 * YSPACE - 1);
  ExtTextOut (hDC, HTEXT1, YOFFSET + YSPACE, ETO_OPAQUE | ETO_CLIPPED,
			  &aRect, str, len, (LPINT) NULL);
}

void
DrawStatusBytes (HDC hDC)
{
  int len;
  RECT aRect;

  len = sprintf (str, "%lu", currentCoded->numBytes);
  TextOut (hDC, HTEXT1, YOFFSET + 2 * YSPACE, str, len);
  SetRect (&aRect, HTEXT1, YOFFSET + 2 * YSPACE, HTITLE2 - 1, YOFFSET + 3 * YSPACE - 1);
  ExtTextOut (hDC, HTEXT1, YOFFSET + 2 * YSPACE, ETO_OPAQUE | ETO_CLIPPED,
			  &aRect, str, len, (LPINT) NULL);
}

void
DrawStatusSeq (HDC hDC)
{
  int len;
  RECT aRect;

  if (currentCoded->sequence == -1)
	len = sprintf (str, "%s", "Unknown");
  else
	len = sprintf (str, "%d", currentCoded->sequence);

  SetRect (&aRect, HTEXT2, YOFFSET+2*YSPACE, STATUSWIDTH - 1, YOFFSET+3*YSPACE  - 1);
  ExtTextOut (hDC, HTEXT2, YOFFSET+2*YSPACE, ETO_OPAQUE | ETO_CLIPPED,
			  &aRect, str, len, (LPINT) NULL);
}

void
DrawStatusActivity (HDC hDC)
{
  COLORREF backColor, textColor;
  int len;
  SIZE size;
  RECT aRect;

  switch (CodingState) {
  case DECODE_SKIPPING:
	backColor = RGB (100, 0, 0);	// red

	textColor = RGB (255, 255, 255);
	strcpy (str, "Skipping");
	break;

  case DECODE_GET_TABLE:
	backColor = RGB (0, 128, 128);	// purple?

	textColor = RGB (255, 255, 255);
	strcpy (str, "Getting table");
	break;

  case DECODE_PROCESSING:
  case DECODE_YENC_HEADER:
	backColor = RGB (0, 128, 0);	// green

	textColor = RGB (0, 0, 0);
	strcpy (str, "Decoding");
	break;

  case ATTACH_PROCESSING:
	backColor = RGB (0, 128, 0);	// green

	textColor = RGB (0, 0, 0);
	strcpy (str, "Encoding");
	break;

  case ATTACH_SENDING:
	backColor = RGB (128, 128, 0);	// yellow

	textColor = RGB (0, 0, 0);
	strcpy (str, "Sending");
	break;

  case ATTACH_READFILE:
	backColor = RGB (0, 128, 0);	// green

	textColor = RGB (0, 0, 0);
	strcpy (str, "Reading file");
	break;

  case ATTACH_WAITING:
	backColor = RGB (100, 0, 0);	// red

	textColor = RGB (255, 255, 255);
	strcpy (str, "Waiting");
	break;
  }

  len = lstrlen (str);
  // Create colored rectangle behind text
  SetRect (&aRect, HTEXT2, YOFFSET + YSPACE, HTEXT2 + 20 * StatusCharWidth,
		   YOFFSET + YSPACE + StatusLineHeight + 1);

  SetTextColor (hDC, textColor);
  SetBkColor (hDC, backColor);

  // center the text in the rectangle
  GetTextExtentPoint (hDC, str, len, &size);
  ExtTextOut (hDC, HTEXT2 + (20 * StatusCharWidth - size.cx) / 2,
			  YOFFSET + YSPACE, ETO_OPAQUE | ETO_CLIPPED, &aRect,
			  str, len, (LPINT) NULL);

  SetTextColor (hDC, StatusTextColor);
  SetBkColor (hDC, StatusBackgroundColor);

//  prevState = CodingState;
}

/* ------------------------------------------------------------------------
 *    Window Proc for Coding status window
 *  Simple text display window w/ scroll bars
 *  Associated with TypDecodeThread
 *  One per decode thread, variable size, user must delete it
 *
 *  Mostly copied from Petzold
 */
LRESULT CALLBACK 
WinVnCodingWndProc (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
  static int cxClient, cyClient, nVscrollPos, nVscrollMax, nHscrollPos, nHscrollMax;
  int rangeTemp;
  RECT client;
  HDC hDC;
  int i, x, y, nPaintBeg, nPaintEnd, nVscrollInc, nHscrollInc;
  PAINTSTRUCT ps;
  TypTextBlock *ThisText;
  int ThisTextNum = 0;
  int CtrlState;
  HFONT hOldFont;

  if ((DebugText != NULL && DebugText->hTextWnd == NULL)
   || (DebugText != NULL && DebugText->hTextWnd == hWnd)) {
    ThisText = DebugText;
  } else {
    for (ThisTextNum = 0; ThisTextNum < NumStatusTexts; ThisTextNum++)
      if (CodingStatusText[ThisTextNum]->hTextWnd == hWnd)
	      break;

    if ((NumStatusTexts != 0) &&
	    (ThisTextNum == NumStatusTexts))
	  ThisTextNum = NumStatusTexts - 1;	// new status window

    ThisText = CodingStatusText[ThisTextNum];
  }
  switch (message) {
  case WM_CREATE:
//      nVscrollPos = nHscrollPos = 0;
	break;

  case WM_SIZE:
	GetClientRect (hWnd, &client);
	cxClient = RectWidth (client);;
	cyClient = RectHeight (client);
	// nVscrollMax is # lines that won't fit in client y area
	rangeTemp = ((int) (ThisText->numLines + 8) * (int) StatusLineHeight - cyClient) / (int) StatusLineHeight;
	nVscrollMax = max (0, rangeTemp);
	nVscrollPos = min (nVscrollPos, nVscrollMax);

	SetScrollRange (hWnd, SB_VERT, 0, nVscrollMax, FALSE);
	SetScrollPos (hWnd, SB_VERT, nVscrollPos, TRUE);

	// nHscrollMax is # chars that won't fit in client x area
	rangeTemp = ((int) (ThisText->maxLineLen + 8) * (int) StatusCharWidth - cxClient) / (int) StatusCharWidth;
	nHscrollMax = max (0, rangeTemp);
	nHscrollPos = min (nHscrollPos, nHscrollMax);

	SetScrollRange (hWnd, SB_HORZ, 0, nHscrollMax, FALSE);
	SetScrollPos (hWnd, SB_HORZ, nHscrollPos, TRUE);
	break;

  case WM_VSCROLL:
#ifdef _WIN32
	switch (LOWORD(wParam)) {
#else
	switch (wParam) {
#endif

	case SB_TOP:
	  nVscrollInc = -nVscrollPos;
	  break;

	case SB_BOTTOM:
	  nVscrollInc = nVscrollMax - nVscrollPos;
	  break;

	case SB_LINEUP:
	  nVscrollInc = -1;
	  break;

	case SB_LINEDOWN:
	  nVscrollInc = 1;
	  break;

	case SB_PAGEUP:
	  nVscrollInc = min (-1, -cyClient / (int) StatusLineHeight);
	  break;

	case SB_PAGEDOWN:
	  nVscrollInc = max (1, cyClient / (int) StatusLineHeight);
	  break;

	case SB_THUMBTRACK:
	case SB_THUMBPOSITION:
#ifdef _WIN32
	  nVscrollInc = HIWORD(wParam) - nVscrollPos;
#else
	  nVscrollInc = LOWORD (lParam) - nVscrollPos;
#endif
	  break;

	default:
	  nVscrollInc = 0;
	}
	nVscrollInc = max (-nVscrollPos,
					   min (nVscrollInc, nVscrollMax - nVscrollPos));

	if (nVscrollInc != 0) {
	  nVscrollPos += nVscrollInc;
	  ScrollWindow (hWnd, 0, -1 * (int) StatusLineHeight * nVscrollInc, NULL, NULL);
	  SetScrollPos (hWnd, SB_VERT, nVscrollPos, TRUE);

	  UpdateWindow (hWnd);
	}
	break;

  case WM_HSCROLL:
#ifdef _WIN32
	switch (LOWORD(wParam)) {
#else
	switch (wParam) {
#endif
	case SB_LINEUP:
	  nHscrollInc = -1;
	  break;

	case SB_LINEDOWN:
	  nHscrollInc = 1;
	  break;

	case SB_PAGEUP:
	  nHscrollInc = -8;
	  break;

	case SB_PAGEDOWN:
	  nHscrollInc = 8;
	  break;

	case SB_THUMBPOSITION:
	case SB_THUMBTRACK: 

#ifdef _WIN32
	  nHscrollInc = HIWORD(wParam) - nHscrollPos;
#else
	  nHscrollInc = LOWORD (lParam) - nHscrollPos;
#endif
	  break;

	default:
	  nHscrollInc = 0;
	}
	nHscrollInc = max (-nHscrollPos,
					   min (nHscrollInc, nHscrollMax - nHscrollPos));

	if (nHscrollInc != 0) {
	  nHscrollPos += nHscrollInc;
	  ScrollWindow (hWnd, -1 * (int) StatusCharWidth * nHscrollInc, 0, NULL, NULL);
	  SetScrollPos (hWnd, SB_HORZ, nHscrollPos, TRUE);

	}
	break;
  case WM_ERASEBKGND:

        {
			HDC hDC = (HDC) wParam;
			if (g_hPalette && hStatusBackgroundBrush)
			{
				
				if (g_hPalette)
				{
					SelectPalette (hDC, g_hPalette, TRUE) ;
					RealizePalette (hDC) ;				
				}
				
				HBRUSH hOldBrush = (HBRUSH) SelectObject(hDC, hStatusBackgroundBrush);
		
        
				RECT rect;
				GetClientRect(hWnd, &rect );
				PatBlt( hDC,  rect.left, rect.top, 
						 RectWidth(rect), RectHeight(rect),PATCOPY);
				SelectObject(hDC, hOldBrush);
				return 1;
			}
		}
		return (DefWindowProc (hWnd, message, wParam, lParam));

	break;


  case WM_PAINT:
	if (ThisText->numLines == 0)
	  break;

	hDC = BeginPaint (hWnd, &ps);
	if (g_hPalette)
	{
		SelectPalette(hDC, g_hPalette, TRUE);
		RealizePalette(hDC);
	}

	SetBkColor (hDC, StatusBackgroundColor);
	SetTextColor (hDC, StatusTextColor);
	hOldFont = (HFONT) SelectObject (hDC, hStatusFont);

	nPaintBeg = max (0, (nVscrollPos + ps.rcPaint.top / (int) StatusLineHeight - 1));
	nPaintEnd = min ((int) ThisText->numLines,
				(nVscrollPos + ps.rcPaint.bottom / (int) StatusLineHeight));

	SetTextAlign (hDC, TA_LEFT | TA_TOP);
	SIZE sMax, sExtent;
	sMax.cx = 0; sMax.cy = 0;
	for (i = nPaintBeg; i < nPaintEnd; i++) {
	  x = (int) StatusCharWidth *(1 - nHscrollPos);
	  y = (int) StatusLineHeight *(1 - nVscrollPos + i);
  
	  TextOut (hDC, x, y, TextBlockLine (ThisText, i),
			   lstrlen (TextBlockLine (ThisText, i)));
	}

/*      GetClientRect (hWnd, &client);
   cyClient = client.bottom - client.top;
   cxClient = client.right - client.left;
 */
	// nVscrollMax is # lines that won't fit in client y area
	rangeTemp = ((int) (ThisText->numLines + 8) * (int) StatusLineHeight - cyClient) / (int) StatusLineHeight;
	nVscrollMax = max (0, rangeTemp);
	SetScrollRange (hWnd, SB_VERT, 0, nVscrollMax, TRUE);
	// nHscrollMax is # chars that won't fit in client x area
	rangeTemp = ((int) (ThisText->maxLineLen + 8) * (int) StatusCharWidth - cxClient) / (int) StatusCharWidth;
	nHscrollMax = max (0, rangeTemp);
	SetScrollRange (hWnd, SB_HORZ, 0, nHscrollMax, TRUE);

	SelectObject (hDC, hOldFont);
	EndPaint (hWnd, &ps);
	break;

  case WM_KEYDOWN:
	/* See if this key should be mapped to a scrolling event
	   * for which we have programmed the mouse.  If so,
	   * construct the appropriate mouse call and call the mouse code.
	 */

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

  case WM_CLOSE:
	if (CodingState && ThisText->IsBusy)
	  WVMessageBox (hWnd, IDS_WVCODEWN_WAIT_CODE, IDS_WVCODEWN_WAIT_T, MB_OK | MB_ICONSTOP);
	else
	  DestroyWindow (hWnd);
	break;

  case WM_DESTROY:
	for (i = ThisTextNum; i < NumStatusTexts; i++)
	  CodingStatusText[i] = CodingStatusText[i + 1];
	FreeTextBlock (ThisText);
	NumStatusTexts--;
	break;

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

  return (0);
}
