/*
 * $Id: wvart.cpp 1.11 1997/04/05 00:26:27 dumoulin Exp $
 */

/*---  wvart.cpp ------------------------------------------- */
/*  This file contains the window procedure for the Article Viewing window
 *  for WinVn.  (In RCS this files was formally wvart.c v 1.52)
 */

#pragma warning( disable : 4786 ) // Turn off map template length warning
#define FontCharSet (DEFAULT_CHARSET)

#include <windows.h>
#include <windowsx.h>
#include <sys/stat.h>
#include "wvglob.h"
#include "winvn.h"
#include "sha1.h"
#include "sha256.h"
#include "wvnzb.h"

#pragma hdrstop
#include <stdlib.h>
#include <ctype.h>
#include "wvclass.h"
#include <commctrl.h>     /* for tooltips */
#include <string.h>
#if 1 //S.Yoshida
#ifdef __GNUC__
#ifdef USE_3D_CONTROLS
#include "ctl3d.h"
#endif
#else
#include <shellapi.h>
#include <ddeml.h>
#endif
#else
#include <shellapi.h>
#include <ddeml.h>
#endif


/********************* find_article_by_subject *********************
 *
 *   Amended to ignore Re: in either heading line as part of match
 *   criteria
 *
 *  Note the duplication of Knowledge of the rules of subject header
 *  format with wvheader : GetArticleSubject
 *
 *  Ideally some object containing all knowledge of rules of format
 *  would be a more elegant implementation
 *  Also note heading changes with each find next of same heading
 *  request. Ideally want to maintain first heading in series
 *  allowing a more liberal interpreation of 'same' in same heading
 *
 *                       Matthew Bretherton 2nd March 1993
 *
 ******************************************************************/

long 
find_article_by_subject (header_p headers,
                         long artindex,
                         long num_headers)
{
  const char far *nextsub;
  const char far *thissub;

  thissub = (header_elt (headers, artindex++))->pHD->sSubject;
  if (_strnicmp (thissub, "Re:", 3) == 0)
    thissub = thissub + 4;

  if (artindex >= num_headers)
    return (-1);

  do {
    nextsub = (header_elt (headers, artindex))->pHD->sSubject;
    if (_strnicmp (nextsub, "Re:", 3) == 0)
      nextsub = nextsub + 4;
    if (lstrcmpi (nextsub, thissub) == 0)
      return (artindex);        /* return the index */
  } while (artindex++ < num_headers);

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

//  WinVN to WWW Interface
//             
//                  Mitsuo Shimomai
//                  Kagoshima University, Japan
//                 
//    Mods to support User Interface  JD 7/24/95   
//    mods to support DDE Shimomai 97/1/10
BOOL
 DoShell (char far *client, char far *argument)
 {
   char execute[MAXFILENAME + MAXFILENAME];
   unsigned int err;

 #ifdef _WIN32
   STARTUPINFO sInfo;
   PROCESS_INFORMATION pInfo;
   char modulename[MAXFILENAME + MAXFILENAME];
   char *p1, *p2;
 #endif
   
    if (argument)
       _snprintf (execute, MAXFILENAME + MAXFILENAME, "%s %s", client, argument);
    else
       _snprintf (execute, MAXFILENAME + MAXFILENAME, "%s", client);

 #ifdef _WIN32
   p1 = execute;
   p2 = modulename;
   if (execute[0] == '"') 
   {
     p1 += 1;
     while ((*p1 != '\0') && (*p1 != '"')) 
		*p2++=*p1++;
   } 
   else 
   {
     while ((*p1 != '\0') && (*p1 != ' ')) 
		*p2++=*p1++;
   }
   *p2 = '\0';

   //if (ShellExecute( NULL, NULL, modulename, execute+strlen(modulename)+1, NULL, SW_SHOWNORMAL ) >= (HINSTANCE) 32)
   


   memset (&sInfo, 0, sizeof (STARTUPINFO));
   sInfo.dwFlags = STARTF_USESHOWWINDOW | STARTF_FORCEONFEEDBACK;
   sInfo.cb = sizeof(sInfo);
   sInfo.wShowWindow = SW_SHOWNORMAL;
   err = CreateProcess (NULL, execute, NULL, NULL, FALSE,
                       DETACHED_PROCESS | NORMAL_PRIORITY_CLASS, NULL, NULL,
                       &sInfo, &pInfo);
   if (err == TRUE) 
   {           // successful

    CloseHandle (pInfo.hThread);    // let it run detached 
    CloseHandle (pInfo.hProcess);

   }
   else 
   {
    sprintf(str,"%u", err); 
    WVArg2SetStatbarText( CommDoc ? CommDoc->hWndFrame : NetDoc.hWndFrame, IDS_STBAR_EXECUTE,&NetDoc, TRUE, TRUE, execute, str);
    return FALSE;
   }
#else
   if ((err = WinExec (execute, SW_SHOWNORMAL)) < 32) 
   {
		sprintf(str,"%u", err); 
		WVArg2SetStatbarText(CommDoc->hWndFrame, IDS_STBAR_EXECUTE,&NetDoc, TRUE, TRUE, execute, str);
    return FALSE;
   }
#endif
 return TRUE;
 }
 
#include <ddeml.h>
#ifdef __GNUC__

struct dlllist_t {
  struct dlllist_t *next;
  HINSTANCE         hinst;
  const char       *module;
} ALIGNED;
struct dlllist_t *dynload = NULL;

extern "C" void DynUnloadLibs( void );

void DynUnloadLibs( void )
{
  while( dynload != NULL ) {
    struct dlllist_t *next = dynload->next;
    FreeLibrary( dynload->hinst );
    free( dynload );
    dynload = next;
  }
}

HINSTANCE DynLoadLib( const char *module )
{
  struct dlllist_t *mod = dynload;
  HINSTANCE hInst;
  while( mod != NULL ) {
    if( !stricmp( mod->module, module ) ) {
      return mod->hinst;
    }
    mod = mod->next;
  }
  hInst = GetModuleHandle( module );
  if( hInst ) {
    return hInst;
  }
  hInst = LoadLibrary( module );
  if( hInst ) {
    mod = (struct dlllist_t*) malloc( sizeof( struct dlllist_t ) );
    mod->module = module;
    mod->hinst = hInst;
    mod->next = dynload;
    dynload = mod;
  }
  return hInst;
}

#define SETUP_DYNLIB(mod)                              \
    static HINSTANCE g_hInst##mod##Lib = NULL;

#define LOAD_DYNLIB(mod)                               \
    ( g_hInst##mod##Lib != NULL                        \
  || (g_hInst##mod##Lib = DynLoadLib(#mod ".dll")) != NULL )

#define LOAD_DYNFUNC(mod,val,ecode,name,format,param)  \
    typedef val (WINAPI * ft##name) format ;           \
    static ft##name lp##name = NULL ;                  \
    val name format {                                  \
      if( !LOAD_DYNLIB(mod) ) return ecode;            \
      if( !(lp##name = (ft##name)GetProcAddress(g_hInst##mod##Lib, #name)) ) \
        return ecode;                                  \
      return lp##name param;                           \
    }
#define LOAD_DYNFUNCVOID(mod,val,ecode,name,format,param)  \
    typedef val (WINAPI * ft##name) format ;           \
    static ft##name lp##name = NULL ;                  \
    val name format {                                  \
      if( !LOAD_DYNLIB(mod) ) return;            \
      if( !(lp##name = (ft##name)GetProcAddress(g_hInst##mod##Lib, #name)) ) \
        return;                                  \
      return lp##name param;                           \
    }

SETUP_DYNLIB(ctl3d32)

LOAD_DYNFUNC(ctl3d32,BOOL,0,Ctl3dRegister,(HANDLE hndl),(hndl))
LOAD_DYNFUNC(ctl3d32,BOOL,0,Ctl3dUnregister,(HANDLE hndl),(hndl))
LOAD_DYNFUNC(ctl3d32,BOOL,0,Ctl3dAutoSubclass,(HANDLE hndl),(hndl))
LOAD_DYNFUNC(ctl3d32,HBRUSH,0,Ctl3dCtlColorEx,(UINT m,WPARAM w,LPARAM l),(m,w,l))
LOAD_DYNFUNC(ctl3d32,BOOL,0,Ctl3dColorChange,(VOID),())
LOAD_DYNFUNC(ctl3d32,BOOL,0,Ctl3dSubclassCtl,(HWND hCtl),(hCtl))

SETUP_DYNLIB(comctl32)

LOAD_DYNFUNCVOID(comctl32,VOID,0,InitCommonControls,(VOID),())
LOAD_DYNFUNC(comctl32,HWND,0,CreateToolbarEx,
  (HWND hwnd, DWORD ws, UINT wID, int nBitmaps, HINSTANCE hBMInst,
   UINT wBMID, LPCTBBUTTON lpButtons, int iNumButtons, int dxButton,
   int dyButton, int dxBitmap, int dyBitmap, UINT uStructSize),
  (hwnd, ws, wID, nBitmaps, hBMInst, wBMID, lpButtons,
   iNumButtons, dxButton, dyButton, dxBitmap, dyBitmap, uStructSize))


SETUP_DYNLIB(user32)

LOAD_DYNFUNC(user32,HDDEDATA,0,DdeClientTransaction,
  (LPBYTE pData, DWORD cbData, HCONV hConv, HSZ hszItem, UINT wFmt,
   UINT wType, DWORD dwTimeout, LPDWORD pdwResult),
  (pData, cbData, hConv, hszItem, wFmt, wType, dwTimeout, pdwResult))
LOAD_DYNFUNC(user32,BOOL,0,DdeFreeStringHandle,(DWORD idInst, HSZ hsz), (idInst, hsz))

SETUP_DYNLIB(mpr)

LOAD_DYNFUNC(mpr,DWORD,ERROR_NO_NETWORK,WNetGetConnectionA,(LPCTSTR lpLocalName, LPTSTR lpRemoteName, LPDWORD lpnLength),(lpLocalName,lpRemoteName,lpnLength))


#endif // __GNUC__

HDDEDATA CALLBACK DdeCallback(
        UINT uType,       /* transaction type                 */
        UINT uFmt,        /* clipboard data format            */
        HCONV hconv,      /* handle of conversation           */
        HSZ hsz1,         /* handle of string                 */
        HSZ hsz2,         /* handle of string                 */
        HDDEDATA hdata,   /* handle of global memory object   */
        DWORD dwData1,    /* transaction-specific data        */
        DWORD dwData2    /* transaction-specific data        */
        )
{
        return (HDDEDATA) NULL;
}

BOOL
DdeRequest(DWORD idInst, char *szServName, char *szSysTopic, char *szItem)
{
        HCONV hConv;
        HSZ hszServName, hszSysTopic, hszItem;
        BOOL result = FALSE;
        HDDEDATA hData = NULL;

        hszServName = szServName ?
                                DdeCreateStringHandle(idInst, szServName, CP_WINANSI) : (HSZ) NULL;
        hszSysTopic = DdeCreateStringHandle(idInst, szSysTopic, CP_WINANSI);

        hConv = DdeConnect(
        idInst,               /* instance identifier        */
        hszServName,          /* service-name string handle */
        hszSysTopic,          /* System-topic string handle */
        (PCONVCONTEXT) NULL); /* use default context        */

        if (hConv) {
                hszItem = DdeCreateStringHandle(idInst, szItem, CP_WINANSI);
                hData = DdeClientTransaction(
                        (unsigned char *)szItem,
                        (DWORD) strlen(szItem),
                        hConv,
                        hszItem,
                        CF_TEXT,
                        XTYP_REQUEST,
                        1000,
                        NULL);
                DdeDisconnect(hConv);
                DdeFreeStringHandle(idInst, hszItem);
                if (hData != NULL) result = TRUE;
        }

        DdeFreeStringHandle(idInst, hszServName);
        DdeFreeStringHandle(idInst, hszSysTopic);
        return result;
}

BOOL OpenURL(char *url)
{
        DWORD idInst = 0L;
//      char *szServName = NULL;
        char szServName[MAXFILENAME];
        char szArg[MAXINTERNALLINE];
        BOOL result;

        szServName[0] = NULL;
        if (DdeInitialize(&idInst,         /* receives instance identifier */
                (PFNCALLBACK) DdeCallback,     /* address of callback function */
                APPCMD_CLIENTONLY,
                0)) 
		{
                /* error handler */
                return FALSE;
        }
        if (DdeServID == DDECLIENT_USR_SPECIFIED)
                strcpy(szServName, DdeServName);
        else if (DdeServID == DDECLIENT_NETSCAPE)
                strcpy(szServName, "NETSCAPE");
        else if (DdeServID == DDECLIENT_IEXPLORE)
                strcpy(szServName,"IExplore");
        else if (DdeServID == DDECLIENT_FIREFOX)	  
                strcpy(szServName,"Firefox");

        sprintf(szArg,"\"%s\",,-1,0,,,,",url);
        result = DdeRequest(idInst, szServName, "WWW_OpenURL", szArg);
        if (!result && DdeServName && DdeServID == DDECLIENT_NO_DIRECTIVE) 
		{
            strcpy(szServName,DdeServName);
			result = DdeRequest(idInst, szServName, "WWW_OpenURL", szArg);
        }
        if (!result && DdeServID == DDECLIENT_NO_DIRECTIVE) 
		{
                strcpy(szServName,"NETSCAPE");
                result = DdeRequest(idInst, szServName, "WWW_OpenURL", szArg);
        }
        if (!result && DdeServID == DDECLIENT_NO_DIRECTIVE) 
		{
                strcpy(szServName,"IExplore");
                result = DdeRequest(idInst, szServName, "WWW_OpenURL", szArg);
        }
        if (!result && DdeServID == DDECLIENT_NO_DIRECTIVE) 
		{
                strcpy(szServName,"Firefox");
                result = DdeRequest(idInst, szServName, "WWW_OpenURL", szArg);
        }
        if (result) {
                DdeRequest(idInst, szServName, "WWW_Activate", "-1,0");
        }
        DdeUninitialize(idInst);
        return result;
}
 
//  WinVN to WWW Interface
//             
//                  Mitsuo Shimomai
//                  Kagoshima University, Japan
//                 
//    Modified by JD 7/24/95 to support User Interface,
//    use of environment variables and user definable
//    location of command line.
   
int
 URLCheckExecute(char far *str)
 {
   char far *Client, *CmdLine, *CmdLine1, *EnvVar, *p;
   unsigned int i,j,k,len;
   char envvar[MAXENVVAR];
   char cmdstring[MAXFILENAME + MAXFILENAME];
   char pstring[10];
   char hstring[MAXINTERNALLINE];
   char fstring[MAXINTERNALLINE];
   char ccstring[MAXINTERNALLINE];

   while (*str && isspace((unsigned char)*str))
	str++;
   
   p = str + strlen(str) -1;
   if (*str=='<' && *p=='>' && strchr(str,'@') && (stristr(str,"http://") == NULL && stristr(str,"https://") == NULL && stristr(str, "www.") == NULL ))
    return (FALSE);     // Maybe, Message-ID

   if (*str == '<' && *p == '>' && (stristr(str,"http://") != NULL || stristr(str,"https://") != NULL))
   {
	  str++;
	  *p = '\0';
	  p--;
   }

   Client = NULL;
   hstring[0] = '\0';
   pstring[0] = '\0';
   fstring[0] = '\0';

   if( CmdLine = strstr(str,"http://") ) {
    Client = HttpClient;
    strcpy(pstring,"http");
    if (CmdLine1 = strstr(CmdLine+7,"/")) 
	{
      strncpy(hstring,CmdLine+7,(int)((char far *)CmdLine1 - (char far *)(CmdLine+7)));
	  hstring[(int)((char far *)CmdLine1 - (char far *)(CmdLine+7))] = '\0';
      strcpy(fstring,CmdLine1);
    }
   }
   else if( CmdLine = strstr(str,"https://") ) {
    Client = HttpClient;
    strcpy(pstring,"https");
    if (CmdLine1 = strstr(CmdLine+8,"/")) 
	{
      strncpy(hstring,CmdLine+8,(int)((char far *)CmdLine1 - (char far *)(CmdLine+8)));
	  hstring[(int)((char far *)CmdLine1 - (char far *)(CmdLine+8))] = '\0';
      strcpy(fstring,CmdLine1);
    }
   }
   else if( (CmdLine = strstr(str,"ftp://")) || 
    (CmdLine = strstr(str,"file://")) ) {
     Client = FtpClient;
     strcpy(pstring,"ftp");
     if (CmdLine1 = strstr(CmdLine+6,"/")) {
      len = (int)((char far *)CmdLine1 - (char far *)(CmdLine+6));
      strncpy(hstring,CmdLine+6,len);
      hstring[len]='\0';
      strcpy(fstring,CmdLine1);
    }
     if( Client == NULL ) Client = HttpClient;
   }
   else if(CmdLine = strstr(str,"gopher://")) {
     Client = GopherClient;
     strcpy(pstring,"gopher");
     if (CmdLine1 = strstr(CmdLine+9,"/")) {
      len = (int)((char far *)CmdLine1 - (char far *)(CmdLine+9));
      strncpy(hstring,CmdLine+9,len);
      hstring[len]='\0';
      strcpy(fstring,CmdLine1);
    }
     if( Client == NULL ) Client = HttpClient;
   }
   else if(CmdLine = strstr(str,"wais://")) {
     Client = WaisClient;
     strcpy(pstring,"wais");
     if (CmdLine1 = strstr(CmdLine+7,"/")) {
      len = (int)((char far *)CmdLine1 - (char far *)(CmdLine+7));
      strncpy(hstring,CmdLine+7,len);
      hstring[len]='\0';
      strcpy(fstring,CmdLine1);
    }
     if( Client == NULL ) Client = HttpClient;
   }
   else if(p = strstr(str,"://")) {
     Client = HttpClient;
     CmdLine1 = p;
     for (--p ; p >= str && ((*p >= 'a' && *p <= 'z') || (*p >= 'A' && *p <= 'Z')) ; p--);
       CmdLine = ++p;
     len = (int)((char far *)CmdLine1 - (char far *)CmdLine);
     strncpy(pstring, CmdLine, len);
     pstring[len]='\0';
     if (p = strstr(CmdLine1+3,"/")) {
       len = (int)((char far *)p - (char far *)(CmdLine1+3));
       strncpy(hstring,CmdLine1+3,len);
       hstring[len]='\0';
       strcpy(fstring,p);
     }
   }
   else if( strstr(str,":/") ) {
     Client = FtpClient;
     strcpy(pstring,"ftp");
     CmdLine = str;
     strcpy(fstring,CmdLine);
     if(*CmdLine == '<' || *CmdLine == ' ') ++CmdLine;
   }
   else if (CmdLine = strstr(str, "www.") ) { //also allow URL w/o prefix
    Client = HttpClient;
    strcpy(pstring,"http");
	strcpy(ccstring, "http://");
	strcat(ccstring, str);
	CmdLine = str = ccstring;
    
	if (CmdLine1 = strstr(CmdLine+7,"/")) 
	{
      strncpy(hstring,CmdLine+7,(int)((char far *)CmdLine1 - (char far *)(CmdLine+7)));
	  hstring[(int)((char far *)CmdLine1 - (char far *)(CmdLine+7))] = '\0';
      strcpy(fstring,CmdLine1);
    }
 
   }

   if (Client) 
   {
   
      for (p = CmdLine; *p && !strchr(" <>\"\'{}()",*p); p++);
      *p = '\0';

      // DDE Control, if enable.
      if (Client == HttpClient && UsingDDE && OpenURL(CmdLine))
                return TRUE;

      j = 0;

      for (i=0; Client[i]; i++) 
	  {
        cmdstring[j] = Client[i];
        
        // check for special case of %u to find URL string
        if ((Client[i] == '%') &&  
            ((Client[i+2] == '\0') || strchr(" :<>()\"\'[]{}",Client[i+2]))) {
           switch (Client[i+1]){
              case 'u': 
                 i++;
                 for (p = CmdLine; *p ; p++) {
                    cmdstring[j] = *p;
                    j++;
                 }
                 break;
                 
               case 'p':
                  i++;
                  for (p = pstring; *p ; p++) {
                     cmdstring[j] = *p;
                     j++;
                  }
                  break;

               case 'h':
                  i++;
                  for (p = hstring; *p ; p++) {
                     cmdstring[j] = *p;
                     j++;
                  }
                  break;

               case 'f':
                  i++;
                  for (p = fstring; *p && !strchr(" <>()\"\'[]{}",*p) ; p++) {
                     cmdstring[j] = *p;
                     j++;
                  }
                  break;
           }
        }

        // check to see if we have an environment variable
        // if so, look it up and use that instead
        else if (Client[i] == '%') 
		{
                
            // check to see if this is a special case of %%
            // if so, reduce it to a single %
            if (Client[i+1] == '%')
			{
              i++;
            } 
            else 
			{
              for (k = 0; Client[i+k+1] && !strchr(" %<>()\"\'[]{}", Client[i+k+1]); k++)
			  {
                 envvar[k] = Client[i+k+1];
              }
              envvar[k] = '\0';
              EnvVar = getenv(envvar);
              i=i+k;
              if (EnvVar)
			  {
                 i++;   // strip off trailing %
                 for (p = EnvVar; *p; p++) 
				 {
                    cmdstring[j] = *p;
                    j++;
                 }
               }
              else
                 for (p = envvar; *p; p++) 
				 {
                    cmdstring[j] = *p;
                    j++;
                 }
            }
        }
       else j++;
    }
    cmdstring[j]='\0';
    if (j == 0) strcpy(cmdstring, CmdLine);
	if (!strcmp(str, cmdstring) || j == 0)
	{
		ShellExecute(NULL, "open", cmdstring, NULL, NULL, SW_SHOW);
		return TRUE;
	}
 
 // DoShell(Client, CmdLine);
    DoShell(cmdstring, '\0');
 // DoShell(cmdstring);
    return (TRUE);
   }
   return(FALSE);
 }


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

    FUNCTION: WinVnArtWndProc(HWND, unsigned, WORD, LONG)

    PURPOSE:  Processes messages

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

LRESULT CALLBACK  WinVnArtWndProc (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
  PAINTSTRUCT ps;

  HDC hDC;                  /* handle to display context */
  RECT myRect;              /* selection rectangle      */
  TypDoc *ThisDoc = NULL;
  TypGroup far *GroupDoc;
  int ih, j;
  unsigned int i;
  int found;
  BOOL endless;
  long charnum;
  int X, Y; //MDS
  int CtrlState;
  TypLine far *LinePtr;
  TypBlock far *BlockPtr;
  char mybuf[MAXINTERNALLINE];
  char far *textptr;
  int textlen;
  TypLineID MyLineID;
  HMENU hMenu, hSubMenu;
    DWORD dwTime = 0;




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

  if (!found) 
  {
    ThisDoc = CommDoc;
  }

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

  switch (message) 
  {

  case WM_SIZE:
    GetClientRect (hWnd, &myRect);
    ThisDoc->ScXWidth = myRect.right;
    ThisDoc->ScYHeight = myRect.bottom;

	ThisDoc->ScYLines = (RectHeight (myRect) - ArtTopSpace) / ArtLineHeight; //what about scrollbar?
	ThisDoc->ScXChars = (RectWidth (myRect) - ArtSideSpace) / ArtCharWidth;

	
    break;

  case WM_HELP:
	DoHelp (hWnd, HELP_CONTEXT, (DWORD)415);
	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.
     */

    if (wParam == VK_F6) {
      NextWindow (ThisDoc->hWndFrame, ThisDoc->DocType);
    }
    else if (wParam == VK_F5) {
      if ( !IsFlagSet(ThisDoc, EndOfArtViewed))//(ThisDoc->ActiveLines) > (ThisDoc->ScYLines + ThisDoc->TopLineOrd)) 
	  {
        SendMessage (hWnd, (UINT) WM_KEYDOWN, (WPARAM) VK_NEXT, (LPARAM) 0L);
        break;
      }
      else {
        if (!(CommBusy && ThisDoc == CommDoc))
          SendMessage (ThisDoc->hWndFrame, (UINT) WM_COMMAND, (WPARAM) IDM_FIND_NEXT_SELECTED, (LPARAM) 0L);
        break;
      }
    }
    else if (wParam == VK_SPACE) {
//shimomai        if (ThisDoc->ActiveLines > (ThisDoc->ScYLines + ThisDoc->TopLineOrd)) 
      if ((ThisDoc->TotalLines > 0) &&
               ((ThisDoc->NextTopLine > 0L && (ThisDoc->NextTopLine < ThisDoc->TotalLines))
               || ThisDoc->ActiveLines > (ThisDoc->ScYLines + ThisDoc->TopLineOrd))) {
        SendMessage (hWnd, (UINT) WM_KEYDOWN, (WPARAM) VK_NEXT, (LPARAM) 0L);
        break;
      }
      else {
        if (!(CommBusy && ThisDoc == CommDoc))
          SendMessage (ThisDoc->hWndFrame, (UINT) WM_COMMAND, (WPARAM) IDM_FIND_NEXT_UNSEEN, (LPARAM) 0L);
        break;
      }
    }
    else if (wParam == VK_BACK) {  //toggle back to group wnd  MDS 20004011
      if (ThisDoc->ParentDoc)
      {
        if (IsMinimized(ThisDoc->ParentDoc->hWndFrame))
          ShowWindow(ThisDoc->ParentDoc->hWndFrame, SW_RESTORE);
        SetFocus(ThisDoc->ParentDoc->hWndFrame);
      }

    break;  

    }
#ifdef OFFLINE  // TANAKA Goh
    else if (wParam == VK_DELETE) {
          SendMessage(ThisDoc->hWndFrame, WM_COMMAND, IDM_DELETE_NEXT_ARTICLE, 0);
          break;
    }
    else if (wParam == VK_INSERT) {
          SendMessage(ThisDoc->hWndFrame, WM_COMMAND, IDM_SENDME_ARTICLE, 0);
          break;
    }
#endif    
    else {
      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_RBUTTONDOWN:
    if ((ThisDoc->TextSelected) && (ThisDoc->TotalLines > 0)) {
      CopyTextToClipBoard (ThisDoc);
      SendMessage (ThisDoc->hWndFrame, (UINT) WM_COMMAND, (WPARAM) IDM_DESELECT_ALL, (LPARAM) 0L);  //Un-mark selection
    }
    break;


  case WM_MBUTTONDOWN:
    MiddleButton_Is_Down = TRUE;
    if (AScrollMode == WHEELSCROLL_CLICK) {
        endless = FALSE; /* first loop assuemd as save */
        do {
                APosScrollByWheel++;
                if ( APosScrollByWheel >= MAX_WHEEL_ENTRIES ) {
                        APosScrollByWheel = 0;
                        if (endless) break; /* prevent a endless loop in case this occures twice */
                        endless = TRUE; /* remember first loop just encountered, now it could be endless */
                }
        } while (AScrollByWheel[APosScrollByWheel] == 0);
    }
    break;
    
  case WM_LBUTTONDOWN:
    {
      long TempX, TempY;

      if ((CommBusy && CommDoc == ThisDoc) || (ThisDoc->TotalLines == 0)) {
        break;
      }
      else 
      {
        DragMouseAction = DRAG_SELECT;
        ThisDoc->TextSelected = FALSE;
        hMenu = GetMenu (ThisDoc->hWndFrame);
        hSubMenu = GetSubMenu (hMenu, 1);
        EnableMenuItem (hSubMenu, IDM_COPY, DISABLE_MENU);
        SendMessage (ThisDoc->hWndTB, TB_ENABLEBUTTON, IDM_COPY, FALSE);
        EnableMenuItem (hSubMenu, IDM_DESELECT_ALL, DISABLE_MENU);

        X = GET_X_LPARAM (lParam);
        Y = GET_Y_LPARAM (lParam);

        if ((int)X < (int)ArtSideSpace) 
		{
          TempX = (long) ArtSideSpace;
        }
        else 
		{
          TempX = (long) min ((int)X, (int)ThisDoc->ScXWidth);
        }

        if ((int)Y < (int)ArtTopSpace) 
		{
          TempY = ArtTopSpace;
        }
        else 
		{
          TempY = min ((long) Y, (long) (ArtTopSpace + (ThisDoc->ScYLines - 1) * ArtLineHeight));
          if (TempY > (long) (ArtTopSpace + (ThisDoc->TotalLines - ThisDoc->TopLineOrd - 1) * ArtLineHeight)) 
		  {
            TempY = (long) ArtTopSpace + (ThisDoc->TotalLines - ThisDoc->TopLineOrd - 1) * ArtLineHeight;
          }
        }
#ifdef _DEBUG        
		HideCaret(hWnd);
#endif		

		POINT pt;
        ThisDoc->BeginSelect.LineNum = (int) ((TempY - ArtTopSpace) / ArtLineHeight + ThisDoc->TopLineOrd);
        ThisDoc->BeginSelect.CharNum = (int) cursor_to_char_number (TempX, TempY, ThisDoc, &BlockPtr, &LinePtr, &pt);
        ThisDoc->EndSelect.LineNum = -1;
        SetRect (&myRect, 0, 0, ThisDoc->ScXWidth, ThisDoc->ScYHeight);
        InvalidateRect (ThisDoc->hDocWnd, &myRect, FALSE);
        hSaveCursor = SetCursor (LoadCursor (NULL, IDC_IBEAM));
        SetCapture (hWnd);
#ifdef _DEBUG 		
		if (pt.x >= 0 || pt.y >= 0)
		{
			SetCaretPos(pt.x, pt.y);
			ShowCaret(hWnd);
		}
#endif		
      }
    }
    break;

  case WM_MOUSEMOVE:
    {
      long TempX, TempY;
      TextSelect Previous = {0};
      int ScMin, ScMax;
      POINT CursorLocation;
      MSG Message;
      BOOL ScrollText = FALSE;
      void CheckForUpdate ();

      if (DragMouseAction == DRAG_SELECT) {
        ThisDoc->TextSelected = TRUE;

        hMenu = GetMenu (ThisDoc->hWndFrame);
        hSubMenu = GetSubMenu (hMenu, 1);
        EnableMenuItem (hSubMenu, IDM_COPY, ENABLE_MENU);
        SendMessage (ThisDoc->hWndTB, TB_ENABLEBUTTON, IDM_COPY, TRUE);
        EnableMenuItem (hSubMenu, IDM_DESELECT_ALL, ENABLE_MENU);

        X = GET_X_LPARAM (lParam);
        Y = GET_Y_LPARAM (lParam);

        // Check to see if text selection needs to scroll left.
        if ((X < 0) && (ThisDoc->ScXOffset > 0)) {
          ScrollText = TRUE;

          do                    // keep scrolling until the mouse moves.
           {
            if  (GetTickCount () > dwTime)
            {


                TempX = ArtSideSpace;
                if ((int)Y < (int)ArtTopSpace) {
                    TempY = ArtTopSpace;
                }
                else {
                    TempY = min ((long) Y, (long)(ArtTopSpace + (ThisDoc->ScYLines - 1) * ArtLineHeight));
                    if (TempY > (long) (ArtTopSpace + (ThisDoc->TotalLines - ThisDoc->TopLineOrd - 1) * ArtLineHeight)) {
                        TempY = (long) (ArtTopSpace + (ThisDoc->TotalLines - ThisDoc->TopLineOrd - 1) * ArtLineHeight);
                    }
                }

                Previous.LineNum = ThisDoc->EndSelect.LineNum;
                Previous.CharNum = ThisDoc->EndSelect.CharNum;
                SendMessage (ThisDoc->hDocWnd, WM_HSCROLL, SB_LINELEFT, 0);
                ThisDoc->EndSelect.LineNum = (int) ((TempY -  ArtTopSpace) / ArtLineHeight + ThisDoc->TopLineOrd);
                ThisDoc->EndSelect.CharNum = (int) cursor_to_char_number (TempX, TempY, ThisDoc, &BlockPtr, &LinePtr);
                UpdateHighlightedText (ThisDoc, &Previous);
                SendMessage (ThisDoc->hDocWnd, WM_PAINT, 0, 0);
                dwTime = GetTickCount() + 20;
            }
            if (PeekMessage (&Message, ThisDoc->hDocWnd, WM_LBUTTONUP, WM_LBUTTONUP, PM_NOREMOVE))
              break;
            GetCursorPos (&CursorLocation);
            ScreenToClient (ThisDoc->hDocWnd, &CursorLocation);
          }
          while ((X == CursorLocation.x) && (Y ==  CursorLocation.y) &&
                 (ThisDoc->ScXOffset > 0));
        }

        GetScrollRange (ThisDoc->hDocWnd, SB_HORZ, &ScMin, &ScMax);
        // Check to see if text selection needs to scroll right.
        if (((int) X > (int)ThisDoc->ScXWidth) && (ThisDoc->ScXOffset < (unsigned int) ScMax)) {
          ScrollText = TRUE;

          do                    // keep scrolling until the mouse moves.
           {
            if  (GetTickCount () > dwTime)
            {


                TempX = ThisDoc->ScXWidth;
                if ((int)Y < (int)ArtTopSpace) {
                    TempY = ArtTopSpace;
                }
                else {
                    TempY = min ((long) Y, (long) (ArtTopSpace + (ThisDoc->ScYLines - 1) * ArtLineHeight));
                    if (TempY > (long) (ArtTopSpace + ((ThisDoc->TotalLines - ThisDoc->TopLineOrd - 1) * ArtLineHeight)))
                    {
                        TempY =  (long) (ArtTopSpace + ((ThisDoc->TotalLines - ThisDoc->TopLineOrd - 1) * ArtLineHeight));
                    }
                }

                Previous.LineNum = ThisDoc->EndSelect.LineNum;
                Previous.CharNum = ThisDoc->EndSelect.CharNum;
                SendMessage (ThisDoc->hDocWnd, WM_HSCROLL, SB_LINERIGHT, 0);
                ThisDoc->EndSelect.LineNum = (int) ((TempY - ArtTopSpace) / ArtLineHeight + ThisDoc->TopLineOrd);
                ThisDoc->EndSelect.CharNum = (int) cursor_to_char_number (TempX, TempY, ThisDoc, &BlockPtr, &LinePtr);

                UpdateHighlightedText (ThisDoc, &Previous);
                SendMessage (ThisDoc->hDocWnd, WM_PAINT, 0, 0);
                dwTime = GetTickCount() + 20;
            }
            if (PeekMessage (&Message, ThisDoc->hDocWnd, WM_LBUTTONUP, WM_LBUTTONUP, PM_NOREMOVE))
              break;
            GetCursorPos (&CursorLocation);
            ScreenToClient (ThisDoc->hDocWnd, &CursorLocation);
          }
          while ((X == CursorLocation.x) && (Y == CursorLocation.y)
                 && ((int) ThisDoc->ScXOffset < ScMax));
        }

        // Check to see if text selection needs to scroll up.
        if (((int)Y < 0) && (ThisDoc->TopLineOrd > 0)) {
          ScrollText = TRUE;

          do                    // keep scrolling until the mouse moves.
           {
            if  (GetTickCount () > dwTime)
            {


                TempY = ArtTopSpace;
                if ((int)X < (int)ArtSideSpace) {
                    TempX = ArtSideSpace;
                }
                else {
                    TempX = min ((int)X, (int)ThisDoc->ScXWidth);
                }

                Previous.LineNum = ThisDoc->EndSelect.LineNum;
                Previous.CharNum = ThisDoc->EndSelect.CharNum;
                SendMessage (ThisDoc->hDocWnd, WM_VSCROLL, SB_LINEUP, 0);
                ThisDoc->EndSelect.LineNum = (int) ((TempY - ArtTopSpace) / ArtLineHeight + ThisDoc->TopLineOrd);
                ThisDoc->EndSelect.CharNum = (int) cursor_to_char_number (TempX, TempY, ThisDoc, &BlockPtr, &LinePtr);
                UpdateHighlightedText (ThisDoc, &Previous);
                SendMessage (ThisDoc->hDocWnd, WM_PAINT, 0, 0);
                dwTime = GetTickCount() + 50;
            }

            if (PeekMessage (&Message, ThisDoc->hDocWnd, WM_LBUTTONUP, WM_LBUTTONUP, PM_NOREMOVE))
                break;
            GetCursorPos (&CursorLocation);
            ScreenToClient (ThisDoc->hDocWnd, &CursorLocation);
          }
          while ((X == CursorLocation.x) && (Y == CursorLocation.y)
                 && (ThisDoc->TopLineOrd > 0));
        }

        GetScrollRange (ThisDoc->hDocWnd, SB_VERT, &ScMin, &ScMax);
        // Check to see if text selection needs to scroll down.
        if (((int)Y > (int)ThisDoc->ScYHeight) && (ThisDoc->TopLineOrd < (unsigned int) ScMax)) {
          ScrollText = TRUE;

          do                    // keep scrolling until the mouse moves.
           {
            if  (GetTickCount () > dwTime)
            {

	                TempY = (long) (ArtTopSpace + (ThisDoc->ScYLines - 1) * ArtLineHeight);
	                if (TempY >  (long)(ArtTopSpace + (ThisDoc->TotalLines - ThisDoc->TopLineOrd - 1) * ArtLineHeight)) {
	                    TempY = (long) (ArtTopSpace + (ThisDoc->TotalLines - ThisDoc->TopLineOrd - 1) * ArtLineHeight);
	                }
	
	                if ((int)X < (int)ArtSideSpace) {
	                    TempX = ArtSideSpace;
	                }
	                else {
	                    TempX = min ((int)X, (int)ThisDoc->ScXWidth);
	                }
	
	                Previous.LineNum = ThisDoc->EndSelect.LineNum;
	                Previous.CharNum = ThisDoc->EndSelect.CharNum;
	                SendMessage (ThisDoc->hDocWnd, WM_VSCROLL, SB_LINEDOWN, 0);
	                ThisDoc->EndSelect.LineNum = (int) (TempY - ArtTopSpace) / ArtLineHeight + ThisDoc->TopLineOrd;
	                ThisDoc->EndSelect.CharNum = (int) cursor_to_char_number (TempX, TempY, ThisDoc, &BlockPtr, &LinePtr);
	                UpdateHighlightedText (ThisDoc, &Previous);
	                SendMessage (ThisDoc->hDocWnd, WM_PAINT, 0, 0);
	                dwTime = GetTickCount() + 50;
	            }
	
	            if (PeekMessage (&Message, ThisDoc->hDocWnd, WM_LBUTTONUP, WM_LBUTTONUP, PM_NOREMOVE))
	                break;

            GetCursorPos (&CursorLocation);
            ScreenToClient (ThisDoc->hDocWnd, &CursorLocation);
          }
          while ((X == CursorLocation.x) && (Y == CursorLocation.y)
                 && (ThisDoc->TopLineOrd < (unsigned int) ScMax));
        }

        if (!ScrollText) {
          if ((int)X < (int)ArtSideSpace) {
            TempX = ArtSideSpace;
          }
          else {
            TempX = min ((int)X, (int)ThisDoc->ScXWidth);
          }
          if ((int)Y < (int)ArtTopSpace) {
            TempY = ArtTopSpace;
          }
          else {
            TempY = min ((long) Y, (long) (ArtTopSpace + (ThisDoc->ScYLines - 1) * ArtLineHeight));
            if (TempY >  (long) (ArtTopSpace + (ThisDoc->TotalLines - ThisDoc->TopLineOrd - 1) * ArtLineHeight)) {
              TempY =  (long) (ArtTopSpace + (ThisDoc->TotalLines - ThisDoc->TopLineOrd - 1) * ArtLineHeight);
            }
          }

          Previous.LineNum = ThisDoc->EndSelect.LineNum;
          Previous.CharNum = ThisDoc->EndSelect.CharNum;
          ThisDoc->EndSelect.LineNum = (int) (TempY - ArtTopSpace) / ArtLineHeight + ThisDoc->TopLineOrd;
          ThisDoc->EndSelect.CharNum = (int) cursor_to_char_number (TempX, TempY, ThisDoc, &BlockPtr, &LinePtr);
          UpdateHighlightedText (ThisDoc, &Previous);

        }

        if ((ThisDoc->EndSelect.LineNum > ThisDoc->BeginSelect.LineNum) ||
            ((ThisDoc->EndSelect.LineNum == ThisDoc->BeginSelect.LineNum) &&
             (ThisDoc->EndSelect.CharNum >= ThisDoc->BeginSelect.CharNum))) {
          if (((Previous.LineNum < ThisDoc->BeginSelect.LineNum) ||
               ((Previous.LineNum == ThisDoc->BeginSelect.LineNum) &&
                (Previous.CharNum <= ThisDoc->BeginSelect.CharNum)))) {
            InvalidateRect (hWnd, NULL, FALSE);
          }
        }
        else {
          if (((Previous.LineNum > ThisDoc->BeginSelect.LineNum) ||
               ((Previous.LineNum == ThisDoc->BeginSelect.LineNum) &&
                (Previous.CharNum >= ThisDoc->BeginSelect.CharNum)))) {
            InvalidateRect (hWnd, NULL, FALSE);
          }
        }
      }
    }
    break;

  case WM_MBUTTONUP:
    MiddleButton_Is_Down = FALSE;
    break;

  case WM_LBUTTONUP:
    {
      long TempX, TempY;

      if (DragMouseAction == DRAG_SELECT) 
      {
        DragMouseAction = DRAG_NONE;
        if (ThisDoc->TextSelected == TRUE) 
        {
          X = GET_X_LPARAM (lParam);
          Y = GET_Y_LPARAM (lParam);

          if ((int)X < (int)ArtSideSpace) 
          {
            TempX = ArtSideSpace;
          }
          else 
          {
            TempX = min ((int)X, (int)ThisDoc->ScXWidth);
          }
          if ((int)Y < (int)ArtTopSpace) 
          {
            TempY = ArtTopSpace;
          }
          else 
          {
            TempY = min ((long) Y, (long) (ArtTopSpace + (ThisDoc->ScYLines - 1) * ArtLineHeight));
          }

          ThisDoc->EndSelect.LineNum = (int) (TempY - ArtTopSpace) / ArtLineHeight + ThisDoc->TopLineOrd;
          ThisDoc->EndSelect.CharNum = (int) cursor_to_char_number (TempX, TempY, ThisDoc, &BlockPtr, &LinePtr);
          InvalidateRect (ThisDoc->hDocWnd, NULL, FALSE);
        }
        SetCursor (hSaveCursor);
        ReleaseCapture ();
      }
#ifdef _DEBUG 
	  ShowCaret(hWnd);
#endif 
    }
    break;

  case WM_LBUTTONDBLCLK:
    if (ThisDoc->TotalLines == 0)
        break;

    X = GET_X_LPARAM (lParam);
    Y = GET_Y_LPARAM (lParam);

    charnum = cursor_to_char_number (X, Y, ThisDoc, &BlockPtr, &LinePtr);

    if (charnum >= 0) 
    {
      int nLines = 1;
	  textlen = GetTextLen(LinePtr);
      textptr = GetTextPtr(LinePtr);

      if (textlen) {            /* find a message-id or URL */
        char far *start, far * end;

        for (start = textptr + charnum; start > textptr; start--)
        {
            if (*start == '<' || *start == ' ' || ((unsigned char) *start) == 0xa0 )
            {
			   break;
			}
		}

        for (end = textptr + charnum; end < (textptr + textlen-1); end++)
        {

			if (*end == '>' || *end == ' ' || *end >= 0x7f)
				break;
		}

#if defined(__OS2__) || defined(__WATCOMC__)
          snprintf (str,(int) (((long) end - (long) start) + 2),"%s",start);
#else
          //sprintf (str, "%.*Fs", (int) ((long) end - (long) start + 1), start);
          int leng = min( (end-start)+2, MAXHEADERLINE);
          strncpy(str, start, leng );
		  str[leng-1]=0;
#endif
          
        //check for wrapped URL
        //this could be expanded to handle any length, but for now only 2 wrapped lines. 
        if ( WrapIncomingArticleText && 
             end == (textptr + textlen-1) )
        {

            BOOL bOK = TRUE;

			while (bOK && nLines < 5 && NextLine (&BlockPtr, &LinePtr))
            {
                
			    nLines++;
                textlen = GetTextLen(LinePtr);
                textptr = GetTextPtr(LinePtr);
                if ( !strnicmp(textptr, "http://", 7) )
				{
					bOK = FALSE; // no chars, don't wrap
					break;
				}
                //break on 1st non-URL like char
                for (end = textptr; end < (textptr + textlen-1); end++)
                    if (*end == '>' || isspace((unsigned char)*end) || *end >= 0x7f)
                        break;

				if (end > textptr && isspace((unsigned char)*end))
					end--;                    
                
                if (end == textptr)
                    bOK = FALSE; // no chars, don't wrap
                
                if ( !isalnum(*end) && *end != 0x2f && *end != 0x3d && *end != '>')
                    bOK = FALSE; // trailing punctuation? -not a url.
                                 // except when '/' or '='
                if (bOK)
                {
#if defined(__OS2__) || defined(__WATCOMC__)
                    snprintf (str+strlen(str),(int) (((long) end - (long) textptr) + 2),"%s",textptr);
#else
                    strncat(str, textptr, min((end-textptr)+1, MAXHEADERLINE-strlen(str)));
                    //sprintf (str+strlen(str), "%.*Fs", (int) ((long) end - (long) textptr+1), textptr);
#endif
                    }
                }

            }
            else if ( end == (textptr + textlen-1) &&  textlen > 72 )
            {
                BOOL bOK = TRUE;
				while (bOK && nLines < 5 && NextLine (&BlockPtr, &LinePtr))
                {
                    
					nLines++;

                    textlen = GetTextLen(LinePtr);
                    textptr = GetTextPtr(LinePtr);
                
                    //break on 1st non-URL like char
                    for (end = textptr; end < (textptr + textlen-1); end++)
                        if (*end == '>' || isspace((unsigned char)*end) || *end >= 0x7f)
                            break;
                
                    if (end == textptr)
                        bOK = FALSE; // no chars, don't wrap
                
                    if ( !isalnum(*end) && *end != 0x2f && *end != 0x3d && *end != '>')
                        bOK = FALSE; // trailing punctuation? -not a url.
                                     // except when '/' or '='
                    if (bOK)
                    {
						strncat(str, textptr, min((end-textptr)+1, MAXHEADERLINE-strlen(str)));

                        //sprintf (str+strlen(str), "%.*Fs", (int) ((long) end - (long) textptr+1), textptr);
                    }
                }
                
            }

            end = str + strlen(str) - 1;
            if (*end <= ' ' || *end >= 0x7f || *end == '.') 
                *end = '\0';

          if (!URLCheckExecute(str))
          {
             if (*str == '<' && *(str+strlen(str)-1) == '>')
             if (!TestArgCommBusy(hWnd, IDS_TEST_AVAILABLE, str) && (CommState==ST_IN_GROUP) 
                && (ThisDoc->ParentDoc)) 
			 { //MDS NULL if artlist closed!
                IsArticleAvailable = FALSE;
                AsynMsg.hwnd = hWnd;
                AsynMsg.uMsg = ID_STAT_CHECK_COMPLETE;
                AsynMsg.wParam = (WPARAM) 0;
                AsynMsg.lParam = (LPARAM) 0L;
                strncpy(TestArticleId,str,MAXHEADERLINE);
                sprintf(mybuf,"STAT %s",TestArticleId); /* Execute STAT msg-id command */
                PutCommLine(mybuf);
                CommBusy = TRUE;
                CommState = ST_IS_ARTICLE_ON_SERVER;
                ExecAsynMessage = TRUE;
             }
          /***** the following is done later indirect over the ID_STAT_CHECK_COMPLETE Msg ***
          LockLine (ThisDoc->ParentDoc->hParentBlock,
                    ThisDoc->ParentDoc->ParentOffset,
                    ThisDoc->ParentDoc->ParentLineID,
                    &BlockPtr, &LinePtr);
          GroupDoc = GetGroup(LinePtr);
          SetArticleMenus (ThisDoc, DISABLE);
          ViewArticle (ThisDoc->ParentDoc, ThisDoc->LastSeenLineID, REUSE, SHOW, str);
          *******************************************************************************/
        }
      }
    }

    break;

  case ID_STAT_CHECK_COMPLETE:
     if ((CommBusy) || (CommState == ST_IS_ARTICLE_ON_SERVER)) 
	 {
        ExecAsynMessage = TRUE;
        break;
     }
     if (IsArticleAvailable) 
	 {
        LockLine (ThisDoc->ParentDoc->hParentBlock,
                ThisDoc->ParentDoc->ParentOffset,
                ThisDoc->ParentDoc->ParentLineID,
                &BlockPtr, &LinePtr);
        GroupDoc = GetGroup(LinePtr);
        SetArticleMenus (ThisDoc, DISABLE);
        ViewArticle (ThisDoc->ParentDoc, ThisDoc->LastSeenLineID, REUSE, SHOW, TestArticleId);
     }
     else
     {
		SetStatbarText (ThisDoc->hWndFrame, "Article not found", ThisDoc, TRUE, FALSE);
     }
     break;

  /*******************************************************/
  /*** This code handles the Microsoft Intellimouse    ***/
  /*** wheel rotation.                                 ***/
  /*******************************************************/
  case WM_MYMOUSEWHEEL:
  case WM_MOUSEWHEEL:
    {
      int   zDelta;
      if (ThisDoc->ActiveLines == 0) //MDS avoid endless loop/GPF if scroll before 1st
        break;                                           //article line has arrived
      if (message == WM_MOUSEWHEEL) {
                zDelta = (short) HIWORD(wParam);
      } else {
           zDelta = (int) wParam;
      }

      ATotalDelta += zDelta;
      if ((AScrollMode != WHEELSCROLL_DONT)  && (abs(ATotalDelta) >= WHEEL_DELTA)) {
                ATotalDelta = 0;
        if ( zDelta > 0 )
        {
            if ((AScrollMode == WHEELSCROLL_SCROLL) && (MiddleButton_Is_Down)) {
                if ((APosScrollByWheel > 0) && (AScrollByWheel[APosScrollByWheel-1]))
                   APosScrollByWheel--;
            } 
			else 
			{
                if (AScrollByWheel[APosScrollByWheel] == (UINT) -1)
				{
					ScrollIt (ThisDoc, (WPARAM) SB_PAGEUP, lParam);
				}
				else
				{
					for (i = 0 ; i < AScrollByWheel[APosScrollByWheel] ; i++)
						ScrollIt (ThisDoc, (WPARAM) SB_LINEUP, lParam);
				}
            }
        }
        else
        {
            if ((AScrollMode == WHEELSCROLL_SCROLL) && (MiddleButton_Is_Down)) {
                if ((APosScrollByWheel < (MAX_WHEEL_ENTRIES-1)) && (AScrollByWheel[APosScrollByWheel+1]))
                   APosScrollByWheel++;
            } 
			else 
			{
                if (AScrollByWheel[APosScrollByWheel] == (UINT) -1)
				{
					ScrollIt (ThisDoc, (WPARAM) SB_PAGEDOWN, lParam);
				}
				else
				{
			 
					for (i = 0 ; i < AScrollByWheel[APosScrollByWheel] ; i++ )
						ScrollIt (ThisDoc, (WPARAM) SB_LINEDOWN, lParam);
				}
            }
        }
      }
    }
    break;
    
  case WM_VSCROLL:
    ScrollIt (ThisDoc, wParam, lParam);
    break;

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

        {
			HDC hDC = (HDC) wParam;
			if (g_hPalette && hArticleBackgroundBrush)
			{
				
				if (g_hPalette)
				{
					SelectPalette (hDC, g_hPalette, TRUE) ;
					RealizePalette (hDC) ;				
				}
				
				HBRUSH hOldBrush = (HBRUSH) SelectObject(hDC, hArticleBackgroundBrush);
		
        
				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:
    {
      
      HANDLE hBlock;
      unsigned int Offset, MyLen;
      long VertLines;
      long HorzChars;
      long RangeHigh, CurPos;
      long lineNum;
      char far *textptr;
      TypBlock far *BlockPtr;
      TypLine far *LinePtr;
      BOOL ROT13Mode = GetArticleRot13Mode (ThisDoc->hWndFrame);
      RECT aRect;
      TextSelect FAR *Start = NULL, *End = NULL;
      SIZE extent;
      HBRUSH hSolidBrush, hOldBrush;
      HANDLE hOldFont;
      BOOL PageFeed = FALSE;        //shimomai
#ifdef _WIN32
      SCROLLINFO  scrollInfo;  //MDS proportional scroll
#endif
      
      if (!GetUpdateRect(hWnd, &aRect, FALSE))
        break;
#ifdef _DEBUG       
      HideCaret(hWnd);
#endif
	  hDC = BeginPaint (hWnd, &ps);
	  if (g_hPalette)
	  {
		SelectPalette(hDC, g_hPalette, FALSE);
		RealizePalette(hDC);
	  }
	   

      SetBkColor (hDC, ArticleBackgroundColor);
      SetTextColor (hDC, ArticleTextColor);

      GetClientRect (hWnd, &myRect);
      VertLines = ThisDoc->ScYLines;
      HorzChars = ThisDoc->ScXChars;

      // Prepare to highlight text if it has been selected
      if (ThisDoc->TextSelected) 
	  {
        // check to see if selection is in a forward direction 
        // (i.e. top-to-bottom or left-to-right
        if ((ThisDoc->EndSelect.LineNum > ThisDoc->BeginSelect.LineNum) ||
            ((ThisDoc->EndSelect.LineNum == ThisDoc->BeginSelect.LineNum) &&
             (ThisDoc->EndSelect.CharNum >= ThisDoc->BeginSelect.CharNum))) 
		{
          Start = &ThisDoc->BeginSelect;
          End = &ThisDoc->EndSelect;
        }
        else 
		{
          Start = &ThisDoc->EndSelect;
          End = &ThisDoc->BeginSelect;
        }
      }

      /* Update the scroll bar thumb position. */

      RangeHigh = ThisDoc->TotalLines - VertLines;
       if (RangeHigh < 0 && ThisDoc->NextTopLine == 0) 
	   {
        RangeHigh = 0;
        ThisDoc->TopLineOrd = 0;
        ThisDoc->TopScLineID = 0;
        ThisDoc->hCurTopScBlock = ThisDoc->hFirstBlock;
        ThisDoc->TopScOffset = sizeof (TypBlock);
        LockLine (ThisDoc->hFirstBlock, ThisDoc->TopScOffset, ThisDoc->TopScLineID, 
                &BlockPtr, &LinePtr);
      } 
	  else 
	  {
          LockLine (ThisDoc->hCurTopScBlock, ThisDoc->TopScOffset, ThisDoc->TopScLineID,
                    &BlockPtr, &LinePtr);
      }
      CurPos = lineNum = ThisDoc->TopLineOrd;
      if (CurPos < 0)
        CurPos = 0;
#ifdef _WIN32 //MDS proportional scroll
	  if (RangeHigh < 0 && ThisDoc->NextTopLine > 0)
	  {
		RangeHigh = ThisDoc->TotalLines ;	
	  }
      scrollInfo.cbSize = sizeof(SCROLLINFO);
      scrollInfo.fMask = SIF_RANGE | SIF_PAGE;
      scrollInfo.nMin = 0;
      scrollInfo.nMax = RangeHigh + VertLines -1;
      scrollInfo.nPage = ThisDoc->ScYLines; 
      if (!IsFlagSet(ThisDoc, ThumbTracking))
      {
        scrollInfo.fMask |= SIF_POS;
        scrollInfo.nPos = CurPos;
      }
      SetScrollInfo( hWnd, SB_VERT, &scrollInfo, TRUE);
#else

		  SetScrollRange (hWnd, SB_VERT, 0, OrdToScroll(RangeHigh,RangeHigh), FALSE);
		 //IsFlagSet(ThisDoc, ThumbTracking) ? FALSE : TRUE);
		
	      /* thumb pos is updated at end of thumb track */
	      if (!IsFlagSet(ThisDoc, ThumbTracking)) 
	      {
	      	if (GetScrollPos(hWnd, SB_VERT) != OrdToScroll(RangeHigh,CurPos) )
	          SetScrollPos (hWnd, SB_VERT, OrdToScroll(RangeHigh,CurPos), TRUE);
	      }
  
#endif

      RangeHigh = ThisDoc->LongestLine - ThisDoc->ScXChars;
      if (RangeHigh < 0) 
	  {
        RangeHigh = 0;
        ThisDoc->ScXOffset = 0;
      }
#ifdef _WIN32 //MDS proportional scroll

      scrollInfo.cbSize = sizeof(SCROLLINFO);
      scrollInfo.fMask = SIF_RANGE | SIF_PAGE | SIF_POS;
      scrollInfo.nMin = 0;
      scrollInfo.nMax = RangeHigh + HorzChars -1;
      scrollInfo.nPage = HorzChars;
      scrollInfo.nPos =  ThisDoc->ScXOffset;
      SetScrollInfo( hWnd, SB_HORZ, &scrollInfo, TRUE);
#else

      SetScrollRange (hWnd, SB_HORZ, 0, OrdToScroll(RangeHigh,RangeHigh), FALSE);
      SetScrollPos (hWnd, SB_HORZ, OrdToScroll(RangeHigh,ThisDoc->ScXOffset), TRUE);

#endif
      /* Now paint this stuff on the screen for debugging. */
      X = ArtSideSpace - ThisDoc->ScXOffset * (ArtCharWidth + 1);
      Y = ArtTopSpace;

      if (LinePtr->length != END_OF_BLOCK)
        do 
		{
          MyLen = GetTextLen(LinePtr);
          textptr = GetTextPtr(LinePtr);
          if (*textptr == SpoilChar) PageFeed = TRUE;   //shimomai
          
          /*  within body of article take copy of string to avoid changing original data */
          if (ROT13Mode && ((unsigned) lineNum > ThisDoc->HeaderLines)) 
		  {   
            strcpy (mybuf, textptr);
            textptr = mybuf;
            strROT13 (textptr);
          }
           
          /* prepare to print a quotation Line */
          if (ItalicizeQuotes && isLineQuotation (textptr)) 
		  {   
            hOldFont = SelectObject (hDC,  hFontArtQuote );
          }
          else 
		  {  /* prepare to print a normal line */
            hOldFont = SelectObject (hDC, hFontArtNormal);
          }

          /* Now write out the line. */
          /* Check to see the line is to be highlighted */
          if ((ThisDoc->TextSelected) && (lineNum >= Start->LineNum) &&
              (lineNum <= End->LineNum)) 
		  {
            if (MyLen != 0) 
			{
              if (Start->LineNum == End->LineNum) 
			  {     /* Only one line contains highlighted text */
                GetTextExtentPoint (hDC, (LPSTR) textptr, Start->CharNum, &extent);
                SetRect (&aRect, X, Y, X + extent.cx, Y + ArtLineHeight);
                ExtTextOut (hDC, X, Y, ETO_OPAQUE, &aRect, textptr, Start->CharNum, (LPINT) NULL);
                SetBkColor (hDC, ArticleTextColor);
                SetTextColor (hDC, ArticleBackgroundColor);
                aRect.left = aRect.right;
                GetTextExtentPoint (hDC, (LPSTR) textptr, End->CharNum + 1, &extent);
                aRect.right = X + extent.cx;
                ExtTextOut (hDC, aRect.left, Y, ETO_OPAQUE, &aRect, textptr + Start->CharNum,
                            End->CharNum - Start->CharNum + 1, (LPINT) NULL);
                aRect.left = aRect.right;
                GetTextExtentPoint (hDC, (LPSTR) textptr, MyLen, &extent);
                aRect.right = X + extent.cx;
                SetBkColor (hDC, ArticleBackgroundColor);
                SetTextColor (hDC, ArticleTextColor);
                ExtTextOut (hDC, aRect.left, Y, ETO_OPAQUE, &aRect,textptr + End->CharNum + 1, 
						MyLen - End->CharNum - 1, (LPINT) NULL);
                Y += ArtLineHeight;
                lineNum++;
              }
              else 
			  {
                if (lineNum == Start->LineNum) 
				{    /* First line of more than one line to highlight */
                  GetTextExtentPoint (hDC, (LPSTR) textptr, Start->CharNum, &extent);
                  SetRect (&aRect, X, Y, X + extent.cx, Y + ArtLineHeight);
                  ExtTextOut (hDC, X, Y, ETO_OPAQUE, &aRect, textptr, Start->CharNum, (LPINT) NULL);
                  SetBkColor (hDC, ArticleTextColor);
                  SetTextColor (hDC, ArticleBackgroundColor);
                  aRect.left = aRect.right;
                  GetTextExtentPoint (hDC, (LPSTR) textptr, MyLen, &extent);
                  aRect.right = X + extent.cx;
                  ExtTextOut (hDC, aRect.left, Y, ETO_OPAQUE, &aRect, textptr + Start->CharNum, 
						MyLen - Start->CharNum, (LPINT) NULL);
                  SetBkColor (hDC, ArticleBackgroundColor);
                  SetTextColor (hDC, ArticleTextColor);
                }
                if ((lineNum > Start->LineNum) && (lineNum < End->LineNum)) 
				{   /* Whole lines to be highlighted */
                  GetTextExtentPoint (hDC, (LPSTR) textptr, MyLen, &extent);
                  SetRect (&aRect, X, Y, X + extent.cx, Y + ArtLineHeight);
                  SetBkColor (hDC, ArticleTextColor);
                  SetTextColor (hDC, ArticleBackgroundColor);
                  ExtTextOut (hDC, X, Y, ETO_OPAQUE, &aRect, textptr, MyLen, (LPINT) NULL);
                  SetBkColor (hDC, ArticleBackgroundColor);
                  SetTextColor (hDC, ArticleTextColor);
                }
                if (lineNum == End->LineNum) 
				{  /* Last line to be highlighted */
                  SetBkColor (hDC, ArticleTextColor);
                  SetTextColor (hDC, ArticleBackgroundColor);
                  GetTextExtentPoint (hDC, (LPSTR) textptr, End->CharNum + 1, &extent);
                  SetRect (&aRect, X, Y, X + extent.cx, Y + ArtLineHeight);
                  ExtTextOut (hDC, X, Y, ETO_OPAQUE, &aRect, textptr, End->CharNum + 1, (LPINT) NULL);
                  aRect.left = aRect.right;
                  GetTextExtentPoint (hDC, (LPSTR) textptr, MyLen, &extent);
                  aRect.right = X + extent.cx;
                  SetBkColor (hDC, ArticleBackgroundColor);
                  SetTextColor (hDC, ArticleTextColor);
                  ExtTextOut (hDC, aRect.left, Y, ETO_OPAQUE, &aRect, textptr + End->CharNum + 1, 
						MyLen - End->CharNum - 1, (LPINT) NULL);
                }

                Y += ArtLineHeight;
                lineNum++;

              }
            }
            else 
			{              /* Blank Line -- Rectangle for CR/LF */
              SetRect (&aRect, X, Y, X + 4, Y + ArtLineHeight);
              hSolidBrush = CreateSolidBrush (ArticleTextColor);
              FillRect (hDC, &aRect, hSolidBrush);
              DeleteObject ((HGDIOBJ) hSolidBrush);
              Y += ArtLineHeight;
              lineNum++;
            }
          }
          else 
		  {                /* Line is not highlighted */
            GetTextExtentPoint (hDC, (LPSTR) textptr, MyLen, &extent);
			SetRect (&aRect, X, Y, X + extent.cx, Y + ArtLineHeight);

            ExtTextOut (hDC, X, Y, ETO_OPAQUE, &aRect, textptr,
                        MyLen, (LPINT) NULL);
            aRect.left = aRect.right;
            Y += ArtLineHeight;
            lineNum++;
          }

          SelectObject (hDC, hOldFont);

          /* Fill in the space to the right of the end of the line *
           * with the article background color                     */
          aRect.left = aRect.right;
          aRect.right = ThisDoc->ScXWidth+1;
          hSolidBrush = CreateSolidBrush (ArticleBackgroundColor);
          FillRect (hDC, &aRect, hSolidBrush);
          DeleteObject ((HGDIOBJ) hSolidBrush);

        }
//shimomai while (--VertLines > 0 && NextLine (&BlockPtr, &LinePtr));
        while (--VertLines > 0 && NextLine (&BlockPtr, &LinePtr)
                && (PageFeed == FALSE));

	     if (ThisDoc->TotalLines  && ThisDoc->TopLineOrd + (ThisDoc->ScYLines-VertLines) >= ThisDoc->TotalLines  )
		 {
			if (!IsFlagSet(ThisDoc, EndOfArtViewed))
			{
				TRACE("END OF ART VIEWED");
				SetFlags(ThisDoc, EndOfArtViewed);
			}
		  }

//shimomai
        if (PageFeed == TRUE)
		{ 
			ThisDoc->NextTopLine = lineNum;
      
			//spoiler makes larger scroll range
			scrollInfo.cbSize = sizeof(SCROLLINFO);
			scrollInfo.fMask = SIF_RANGE;
			scrollInfo.nMin = 0;
			scrollInfo.nMax = ThisDoc->TotalLines + ThisDoc->ScYLines -1 + (lineNum-ThisDoc->TopLineOrd);
			scrollInfo.nPage = ThisDoc->ScYLines; 
			SetScrollInfo( hWnd, SB_VERT, &scrollInfo, TRUE);
			SetStatbarText (ThisDoc->hWndFrame, "** SPOILER ** (PgDn)", ThisDoc, TRUE, FALSE);

		}
        else if (ThisDoc->NextTopLine)
		{
          ThisDoc->NextTopLine = ThisDoc->TotalLines + 1;
		  SetStatbarText (ThisDoc->hWndFrame, "", ThisDoc, TRUE, FALSE);
		}
        else 
			ThisDoc->NextTopLine = 0;

      /* 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 and blank top space.          */
      hOldBrush = (HBRUSH) SelectObject (hDC, hArticleBackgroundBrush);
      PatBlt (hDC, 0, Y, myRect.right, myRect.bottom - Y, PATCOPY);
      PatBlt (hDC, 0, 0, myRect.right, ArtTopSpace, PATCOPY);
      if (ThisDoc->ScXOffset == 0) 
	  {
        PatBlt (hDC, 0, 0, ArtSideSpace, ThisDoc->ScYHeight, PATCOPY);
      }

      UnlockLine (BlockPtr, LinePtr, &hBlock, &Offset, &MyLineID);
      SelectObject (hDC, hOldBrush);
      EndPaint (hWnd, &ps);
#ifdef _DEBUG       
	  ShowCaret(hWnd);
#endif	  
      break;
    }
  default:
    return (DefWindowProc (hWnd, message, wParam, lParam));
  }
  return (0);
}

/*------------ CloseArtWnd ------------------------------
 *
 *  Make sure this Wnd is not the active Comm window, then destroy it
 */
BOOL CloseArtWnd (HWND hWnd, TypDoc * ThisDoc)
{
  //allow close art if we are just keep-aliving
  if ( (CommBusy && CommState != ST_TIMEOUT_WAKEUP) && ThisDoc == CommDoc) 
  {
    WVMessageBox(hWnd, IDS_WVART_WAIT, IDS_WVART_WAIT_T, MB_OK | MB_ICONSTOP);
    return 0;
  }

  SetHandleBkBrush (ThisDoc->hDocWnd, (HBRUSH) GetStockObject (WHITE_BRUSH));
  DestroyWindow (hWnd);
  return 1;
}

/* Ray Dunn:99/10:*/
/*------------ CloseArtWndSelectAnother ------------------------------
 *
 *  Make sure this Wnd is not the active Comm window, then destroy it
 *  Select another to avoid user having to hunt and pick
 */
void CloseArtWndSelectAnother (HWND hWnd, TypDoc * ThisDoc)
{ int i;
  TypDoc *adoc, *pdoc;

  pdoc = ThisDoc->ParentDoc;

  if (!CloseArtWnd(hWnd, ThisDoc))
    return;

  for (i = MAXARTICLEWNDS; i >= 0; i--)
  {
    adoc = &ArticleDocs[i];
    if (adoc->InUse && !IsMinimized(adoc->hWndFrame))
    {
      SetFocus(adoc->hWndFrame);
      return;
    }
  }
  
  if (pdoc && pdoc->hWndFrame)
  {
    if (IsMinimized(pdoc->hWndFrame))
      ShowWindow(pdoc->hWndFrame, SW_RESTORE);
    SetFocus(pdoc->hWndFrame);
  }
  else if (NetDoc.hWndFrame)
  {
    if (IsMinimized(NetDoc.hWndFrame))
      ShowWindow(NetDoc.hWndFrame, SW_RESTORE);
    SetFocus(NetDoc.hWndFrame);
  }
}
/* Ray Dunn:99/10:End */

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

  hMenu = GetMenu (DocPtr->hWndFrame);
  hSubMenu = GetSubMenu (hMenu, 4);     // respond menu

  EnableMenuItem (hSubMenu, IDM_MAIL, MailCtrl.enableMail);
  EnableMenuItem (hSubMenu, IDM_FORWARD, MailCtrl.enableForward);

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

// dis/enable menu items which depend on article being completely retrieved
void
SetArticleMenus (TypDoc FAR * DocPtr, int enable)
{
  HMENU hMenu, hSubMenu;
  UINT mode;
  UINT modeexit;

  modeexit = (enable == ENABLE) ? ENABLE_MENU : DISABLE_MENU;
  /* a Doc which is empty is one internaly faked by WinVN itself to avoid aborting */
  /* if aricle is canceled after retriving groulist but before opened itself */
  mode = (DocPtr->TotalLines == 0 ) ? DISABLE_MENU : modeexit ;

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

  EnableMenuItem (hSubMenu, IDM_SAVE, mode);
  EnableMenuItem (hSubMenu, IDM_SAVEAS, mode);
  EnableMenuItem (hSubMenu, IDM_PRINT, mode);
  EnableMenuItem (hSubMenu, IDM_PRINT_SETUP, mode);
  EnableMenuItem (hSubMenu, IDV_EXIT, modeexit);
  hSubMenu = GetSubMenu (hMenu, 1);     // edit menu
  EnableMenuItem (hSubMenu, IDM_SELECT_ALL, mode);
  hSubMenu = GetSubMenu (hMenu, 2);     // search menu
  EnableMenuItem (hSubMenu, IDM_SEARCH, mode);
  EnableMenuItem (hSubMenu, IDM_SEARCH_NEXT, mode);
  EnableMenuItem (hSubMenu, IDM_FIND_PREV_SAME, mode);
  hSubMenu = GetSubMenu (hMenu, 4);     // respond menu
  EnableMenuItem (hSubMenu, IDM_POST, mode);

  if (mode == ENABLE_MENU)  {
    // activate based on transport availability
    SetArticleMailMenu (DocPtr);
  }
  else {
    // deactivate regardless
    EnableMenuItem (hSubMenu, IDM_MAIL, mode);
    EnableMenuItem (hSubMenu, IDM_FORWARD, mode);
    SendMessage (DocPtr->hWndTB, TB_ENABLEBUTTON, IDM_FORWARD, FALSE);
    SendMessage (DocPtr->hWndTB, TB_ENABLEBUTTON, IDM_MAIL, FALSE);
  }

  // set toolbar buttons
  modeexit = (enable == ENABLE) ? TRUE : FALSE;
  mode = (DocPtr->TotalLines == 0) ? FALSE : modeexit;

  SendMessage (DocPtr->hWndTB, TB_ENABLEBUTTON, IDM_POST, mode);
  SendMessage (DocPtr->hWndTB, TB_ENABLEBUTTON, IDM_SAVEAS, mode);
  SendMessage (DocPtr->hWndTB, TB_ENABLEBUTTON, IDM_PRINT, mode);
  SendMessage (DocPtr->hWndTB, TB_ENABLEBUTTON, IDM_SEARCH, mode);
  SendMessage (DocPtr->hWndTB, TB_ENABLEBUTTON, IDM_SEARCH_NEXT, mode);
  SendMessage (DocPtr->hWndTB, TB_ENABLEBUTTON, IDV_EXIT, modeexit);

  // initmenu handles next/prev article buttons, as well as decode, and send
  SendMessage (DocPtr->hWndFrame, WM_MYINITMENU, (WPARAM) 0, (LPARAM) 0);

}

/*------GetArticleRot13Mode-------------------------------
 *
 *  Routine to get the this article window Rot mode.
 *  Note :
 *     interogation of windows menu state to determine
 *     ROT13 drawing mode
 *    
 *-----------------------------------------------------*/

BOOL
GetArticleRot13Mode (HWND hWnd)
{
  return (GetMenuState (GetMenu (hWnd), IDM_ROT13, 0) == MF_CHECKED);
}

/*------SetArticleRot13Mode-------------------------------
 *
 *  Routine to set the this article window into (or out of
 *  Rot mode).
 *  Note :
 *     interogation of windows menu state to determine
 *     ROT13 drawing mode
 *     need to invalidate window to force repaint to display
 *     in new mode
 *-----------------------------------------------------*/

void
SetArticleRot13Mode (HWND hWnd, BOOL RotMode)
{
  UINT action;
  HWND hParentWnd;

  if (RotMode == TRUE)
    action = MF_CHECKED;
  else
    action = MF_UNCHECKED;

  if ((hParentWnd = GetParent (hWnd)) == NULL)
    CheckMenuItem (GetMenu (hWnd), IDM_ROT13, action);
  else
    CheckMenuItem (GetMenu (hParentWnd), IDM_ROT13, action);

}

/*            strROT13
 * change the input string by ROT'ing each character
 *
 */
void
strROT13 (char *cstring)
{
  char *cptr = cstring;

  while (*cptr) {
    (*cptr++) = chROT13 (*cptr);

  }
}

/*        chROT13
 * return a new character that is the ROT(ation of) 13 characters
 * of the input character
 *
 */
char 
chROT13 (char achar)
{
  char newchar;

  if (isalpha (achar)) {
    if ((achar & 31) <= 13) {
      newchar = achar + 13;
    }
    else {
      newchar = achar - 13;
    }
  }
  else
    newchar = achar;

  return (newchar);
}
/*            strnROT13
 * change the input string by ROT'ating each character
 * for len or end of string (whichever is less)
 *
 */
void 
strnROT13 (char *cstring, int cstringlen)
{
  char *cptr = cstring;
  int clen = cstringlen;

  while (*cptr && clen) {
    (*cptr) = chROT13 (*cptr);
    cptr++;
    clen--;
  }

}

/* UpdateHighlightedText

 * This function updates hightlighted text from the previous
 * position to the current position.
 *
 */

void UpdateHighlightedText (TypDoc far * DocPtr, TextSelect far * PreviousPos)
{
  SIZE extent;
  char far *textptr;
  int textlen;
  RECT Rect;
  long CurrentLineNum;
  //BOOL ChangedSelectionDirection = FALSE;
  HBRUSH hSolidBrush;
  HDC hDC;
  TypBlock far *BlockPtr;
  TypLine far *LinePtr;
  TextSelect far *Start, *End, *Temp;
  HANDLE hOldFont;
  TEXTMETRIC tmFontInfo;
  int charWidth;

  hDC = GetDC (DocPtr->hDocWnd);

  // check to see if selection is in a forward direction 
  // (i.e. top-to-bottom or left-to-right
  if ((DocPtr->EndSelect.LineNum > DocPtr->BeginSelect.LineNum) ||
      ((DocPtr->EndSelect.LineNum == DocPtr->BeginSelect.LineNum) &&
       (DocPtr->EndSelect.CharNum >= DocPtr->BeginSelect.CharNum))) {
    if ((PreviousPos->LineNum == -1)) {
      Start = &DocPtr->BeginSelect;
      //if (ChangedSelectionDirection) { // unreachable code
      //  InvalidateRect (DocPtr->hDocWnd, NULL, FALSE);
      //}
    }
    else {
      Start = PreviousPos;
    }
    End = &DocPtr->EndSelect;

    // check to see if highlighting or unhighlighting
    if ((End->LineNum > Start->LineNum) ||
        ((End->LineNum == Start->LineNum) &&
         (End->CharNum >= Start->CharNum))) {
      SetBkColor (hDC, ArticleTextColor);
      SetTextColor (hDC, ArticleBackgroundColor);
    }
    else {
      SetBkColor (hDC, ArticleBackgroundColor);
      SetTextColor (hDC, ArticleTextColor);
      Temp = Start;
      Start = End;
      End = Temp;
    }
  }
  else {

    Start = &DocPtr->EndSelect;
    if ((PreviousPos->LineNum == -1)) {
      End = &DocPtr->BeginSelect;
      //if (ChangedSelectionDirection) { // unreachable code
      //  InvalidateRect (DocPtr->hDocWnd, NULL, FALSE);
      //}
    }
    else {
      End = PreviousPos;
    }

    // check to see if highlighting or unhighlighting
    if ((End->LineNum > Start->LineNum) ||
        ((End->LineNum == Start->LineNum) &&
         (End->CharNum >= Start->CharNum))) {
      SetBkColor (hDC, ArticleTextColor);
      SetTextColor (hDC, ArticleBackgroundColor);
    }
    else {
      SetBkColor (hDC, ArticleBackgroundColor);
      SetTextColor (hDC, ArticleTextColor);
      Temp = Start;
      Start = End;
      End = Temp;
    }
  }
  
  if ((End->LineNum >= 0) && (Start->LineNum >= 0) && 
      (End->CharNum >= 0) && (Start->CharNum >= 0))
   {
    FindLineOrd (DocPtr, (unsigned) Start->LineNum, &BlockPtr, &LinePtr);
    textlen = GetTextLen(LinePtr);
    textptr = GetTextPtr(LinePtr);
    
    /* prepare to print a quotation Line */
    if (ItalicizeQuotes && isLineQuotation (textptr)) { 
        hOldFont = SelectObject (hDC,  hFontArtQuote);
    }
    else {  /* prepare to print a normal line */
        hOldFont = SelectObject (hDC,  hFontArtNormal);
    }
    GetTextMetrics (hDC, (LPTEXTMETRIC) & tmFontInfo);
    charWidth = tmFontInfo.tmAveCharWidth;

    GetTextExtentPoint (hDC, (LPSTR) textptr, Start->CharNum, &extent);
    Rect.top = (int) (ArtTopSpace + ((Start->LineNum - DocPtr->TopLineOrd) * ArtLineHeight));
    Rect.left = extent.cx - ((int) DocPtr->ScXOffset * (charWidth + 1) - ArtSideSpace);

    if (Start->LineNum == End->LineNum) {
        GetTextExtentPoint (hDC, (LPSTR) textptr, End->CharNum + 1, &extent);
        Rect.bottom = Rect.top + ArtLineHeight;
        Rect.right = extent.cx - ((int) DocPtr->ScXOffset * (charWidth + 1) - ArtSideSpace);

        if (textlen != 0) 
		{
          ExtTextOut (hDC, Rect.left, Rect.top, ETO_OPAQUE, &Rect,
                      textptr + Start->CharNum, End->CharNum - Start->CharNum + 1, (LPINT) NULL);
        }
        else 
		{
          Rect.right = Rect.left + 4;
          hSolidBrush = CreateSolidBrush (GetBkColor (hDC));
          FillRect (hDC, &Rect, hSolidBrush);   /* Rectangle used to represent CR/LF */
          DeleteObject ((HGDIOBJ) hSolidBrush);
        }
    }
    else {
        GetTextExtentPoint (hDC, (LPSTR) textptr, textlen, &extent);
        Rect.bottom = Rect.top + ArtLineHeight;
        Rect.right = extent.cx - ((int) DocPtr->ScXOffset * (charWidth + 1) - ArtSideSpace);

        if (textlen != 0) 
		{
			ExtTextOut (hDC, Rect.left, Rect.top, ETO_OPAQUE, &Rect,
              textptr + Start->CharNum, textlen - Start->CharNum, (LPINT) NULL);
        }
        else 
		{
            Rect.right = Rect.left + 4;
            hSolidBrush = CreateSolidBrush (GetBkColor (hDC));
            FillRect (hDC, &Rect, hSolidBrush); /* Rectangle used to represent CR/LF */
            DeleteObject ((HGDIOBJ) hSolidBrush);
        }

        SelectObject (hDC, hOldFont);
        CurrentLineNum = Start->LineNum + 1;
         
        if (CurrentLineNum >= 0)
        {
        while (CurrentLineNum < End->LineNum) {
         NextLine (&BlockPtr, &LinePtr);
         textlen = GetTextLen(LinePtr);
         textptr = GetTextPtr(LinePtr);
        
        /* prepare to print a quotation Line */ 
        if (ItalicizeQuotes && isLineQuotation (textptr)) { 
            hOldFont = SelectObject (hDC,  hFontArtQuote);
        }
        else {  /* prepare to print a normal line */
            hOldFont = SelectObject (hDC, hFontArtNormal);
        }
        GetTextMetrics (hDC, (LPTEXTMETRIC) & tmFontInfo);
        charWidth = tmFontInfo.tmAveCharWidth;

        Rect.top = (int) (ArtTopSpace + ((CurrentLineNum - DocPtr->TopLineOrd) * ArtLineHeight));
        Rect.left = ArtSideSpace - (int) DocPtr->ScXOffset * (charWidth + 1);
        GetTextExtentPoint (hDC, (LPSTR) textptr, textlen, &extent);
        Rect.bottom = Rect.top + ArtLineHeight;
        Rect.right = extent.cx - ((int) DocPtr->ScXOffset * (charWidth + 1) - ArtSideSpace);

        if (textlen != 0) 
		{
            ExtTextOut (hDC, Rect.left, Rect.top, ETO_OPAQUE, &Rect,
                        textptr, textlen, (LPINT) NULL);
        }
        else 
		{
            Rect.right = Rect.left + 4;
            hSolidBrush = CreateSolidBrush (GetBkColor (hDC));
            FillRect (hDC, &Rect, hSolidBrush);     /* Rectangle used to represent CR/LF */
            DeleteObject ((HGDIOBJ) hSolidBrush);
          }

        CurrentLineNum++;
        SelectObject (hDC, hOldFont);
        }

        NextLine (&BlockPtr, &LinePtr);
        textlen = GetTextLen(LinePtr);
        textptr = GetTextPtr(LinePtr);
        
        /* prepare to print a quotation Line */
        if (ItalicizeQuotes && isLineQuotation (textptr)) { 
            hOldFont = SelectObject (hDC,  hFontArtQuote);
        }
        else {  /* prepare to print a normal line */
        hOldFont = SelectObject (hDC,  hFontArtNormal);
        }
        GetTextMetrics (hDC, (LPTEXTMETRIC) & tmFontInfo);
        charWidth = tmFontInfo.tmAveCharWidth;

        Rect.top = (int) (ArtTopSpace + ((CurrentLineNum - DocPtr->TopLineOrd) *  ArtLineHeight));
        Rect.left = ArtSideSpace - (int) DocPtr->ScXOffset * (charWidth + 1);
        GetTextExtentPoint (hDC, (LPSTR) textptr, End->CharNum + 1, &extent);
        Rect.bottom = Rect.top + ArtLineHeight;
        Rect.right = extent.cx - ((int) DocPtr->ScXOffset * (charWidth + 1) - ArtSideSpace);

        if (textlen != 0) 
		{
            ExtTextOut (hDC, Rect.left, Rect.top, ETO_OPAQUE, &Rect,
                      textptr, End->CharNum + 1, (LPINT) NULL);
        }
        else 
		{
			Rect.right = Rect.left + 4;
			hSolidBrush = CreateSolidBrush (GetBkColor (hDC));
			FillRect (hDC, &Rect, hSolidBrush); /* Rectangle used to represent CR/LF */
			DeleteObject ((HGDIOBJ) hSolidBrush);
        }

        SelectObject (hDC, hOldFont);
     }
    }
   }
  ReleaseDC (DocPtr->hDocWnd, hDC);
}

void CopyTextToClipBoard (TypDoc FAR * DocPtr)
{
  TextSelect *Start, *End;
  TypBlock FAR *BlockPtr;
  TypLine FAR *LinePtr;
  long CurrentLineNum;
  int j;
  int textlen;
  char huge *textptr;  //huge reqired to copy > 64K on Win16
  BOOL ROT13Mode = GetArticleRot13Mode (DocPtr->hWndFrame);

  HGLOBAL hGlobalMemory;
  char huge *GlobalMemoryPtr;
  DWORD SelectedTextLength = 0;

  /* Check to see if selection is in a forward direction.           *
   * Start should always point to first selected position           *
   * when the document is seen from top-to-bottom and               *
   * left-to-right.                                                 */
  if ((DocPtr->EndSelect.LineNum > DocPtr->BeginSelect.LineNum) ||
      ((DocPtr->EndSelect.LineNum == DocPtr->BeginSelect.LineNum) &&
       (DocPtr->EndSelect.CharNum >= DocPtr->BeginSelect.CharNum))) {
    Start = &DocPtr->BeginSelect;
    End = &DocPtr->EndSelect;
  }
  else {
    Start = &DocPtr->EndSelect;
    End = &DocPtr->BeginSelect;
  }

  /*Calculate the number of bytes to be moved to the clipboard.     */
  CurrentLineNum = Start->LineNum;
  FindLineOrd (DocPtr, (unsigned) CurrentLineNum, &BlockPtr, &LinePtr);
  textlen = GetTextLen(LinePtr);

  if (Start->LineNum == End->LineNum) {
    /* Text is selected on only one line.                           */
    SelectedTextLength = End->CharNum - Start->CharNum + 1;
  }
  else {
    /* Length of the first line. The 2 is for CR/LF.*/
    SelectedTextLength = textlen - Start->CharNum + 2;

    NextLine (&BlockPtr, &LinePtr);
    CurrentLineNum++;

    /* Add in the lengths of all whole lines selected */
    while (CurrentLineNum < End->LineNum) {
      textlen = GetTextLen(LinePtr);
      SelectedTextLength += textlen + 2;  /* The 2 is for CR/LF.*/
      NextLine (&BlockPtr, &LinePtr);
      CurrentLineNum++;
    }

    /* Add in the length of the last line selected.*/
    textlen = GetTextLen(LinePtr);
    if (textlen != 0) {
      SelectedTextLength += End->CharNum + 1;
    }
    else {
      SelectedTextLength += 2;  /* The 2 is for CR/LF.*/
    }
  }

  /* Allocate memory and move selected text to clipboard.     *
   * The 1 is for NULL string termination.                    */
  hGlobalMemory = GlobalAlloc (GHND, SelectedTextLength + 1);
  if (hGlobalMemory) {
    GlobalMemoryPtr = (char huge *) GlobalLock (hGlobalMemory);
    CurrentLineNum = Start->LineNum;
    FindLineOrd (DocPtr, (unsigned) CurrentLineNum, &BlockPtr, &LinePtr);
    textlen = GetTextLen(LinePtr);
    textptr = GetTextPtr(LinePtr);
    /* Move textptr to the first character selected.                 */
    textptr += Start->CharNum;

    if (Start->LineNum == End->LineNum) {
      /* Copy the text from the only line selected.                   */
      for (j = 0; j < End->CharNum - Start->CharNum + 1; j++) {
        *GlobalMemoryPtr++ = (ROT13Mode && ((unsigned) CurrentLineNum > DocPtr->HeaderLines)) 
                            ? chROT13(*textptr++) : *textptr++;
      }
    }
    else {
      /* Copy the first line and add CR/LF at the end of the line.    */
      for (j = 0; j < textlen - Start->CharNum; j++) {
        *GlobalMemoryPtr++ = (ROT13Mode && ((unsigned) CurrentLineNum > DocPtr->HeaderLines))
                            ? chROT13(*textptr++) : *textptr++;
      }
      *GlobalMemoryPtr++ = '\r';
      *GlobalMemoryPtr++ = '\n';
      NextLine (&BlockPtr, &LinePtr);
      CurrentLineNum++;
      
      /* Copy all whole lines selected add add CR/LF at the end of each line. */
      while (CurrentLineNum < End->LineNum) {
        textlen = GetTextLen(LinePtr);
        textptr = GetTextPtr(LinePtr);
        for (j = 0; j < textlen; j++) {
          *GlobalMemoryPtr++ = (ROT13Mode && ((unsigned) CurrentLineNum > DocPtr->HeaderLines)) 
                              ? chROT13(*textptr++) : *textptr++;
        }
        *GlobalMemoryPtr++ = '\r';
        *GlobalMemoryPtr++ = '\n';
        NextLine (&BlockPtr, &LinePtr);
        CurrentLineNum++;
      }
      /* Copy the last line selected */
      textlen = GetTextLen(LinePtr);
      textptr = GetTextPtr(LinePtr);
      if (textlen != 0) {
        for (j = 0; j < End->CharNum + 1; j++) {
            *GlobalMemoryPtr++ = (ROT13Mode && ((unsigned) CurrentLineNum > DocPtr->HeaderLines))  
                                ? chROT13(*textptr++) : *textptr++;
        }
      }
      else {
        *GlobalMemoryPtr++ = '\r';
        *GlobalMemoryPtr++ = '\n';
      }
    }

    GlobalUnlock (hGlobalMemory);
    OpenClipboard (DocPtr->hDocWnd);
    EmptyClipboard ();
    SetClipboardData (CF_TEXT, hGlobalMemory);
    CloseClipboard ();
  }
  else {
    WVMessageBox(DocPtr->hDocWnd, IDS_WVART_NOT_ENOUGH_MEM, IDS_WVART_OUT_MEMORY, MB_OK | MB_ICONSTOP);
  }

}

#ifdef OFFLINE  // TANAKA Goh
void delete_article(TypDoc far *Doc)
{
	if (Doc->ParentDoc) 
	{
		TypBlock far    *BlockPtr;
		TypLine far             *LinePtr;
		TypGroup                *GroupDoc;
		HANDLE                  header_handle;
		HANDLE                  thread_handle;
		header_p                headers;
		HANDLE                  hBlock;
		unsigned int    Offset;
		TypLineID               MyLineID;

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

		if (GroupDoc) 
		{
			TypLineID               ActiveLineID;
			header_p                header;
			unsigned long   number;
			char far                *lpszGroupName;
			char                    mybuf[MAXINTERNALLINE];
			long int                total_headers;
			int                             i, j;

			lpszGroupName = (char far *)LinePtr + sizeof(TypLine) + sizeof(TypGroup);

			if (lstrcmp(CurrentGroup, lpszGroupName)) 
			{
				CommState = ST_GROUP_REJOIN;
				sprintf(mybuf, "GROUP %s", lpszGroupName);
				PutCommLine(mybuf);
				strntcpy(CurrentGroup, lpszGroupName, MAXGROUPNAME);
				strntcpy(CurrentDescription, GroupDoc->Description, MAXDESCRIPTION);
			}

			ActiveLineID = Doc->ParentDoc->ActiveLineID;
			header = header_elt(headers, Doc->LastSeenLineID);
			number = header->number;

			set_index_to_identity(GroupDoc->header_handle, GroupDoc->thread_handle, GroupDoc->total_headers);

			total_headers = GroupDoc->total_headers;

			for (i = 0, j = 0 ; i < max_headers ; i++) 
			{
				header = header_elt(headers, i);

			if (header->number == number) 
			{
				CommState = ST_DELETE;
				sprintf(mybuf, "DELETE %I64d", header->number);
				PutCommLine(mybuf);
				Doc->ParentDoc->ActiveLines--;
				Doc->ParentDoc->TotalLines--;
				GroupDoc->ServerEstNum--;
				GroupDoc->total_headers--;
				GroupDoc->max_headers--;

				if (ActiveLineID > i) 
				{
					Doc->ParentDoc->ActiveLineID--;
				}
				} 
				else 
				{
					header_p        header_new = header_elt(headers, j++);

					memcpy(header_new, header, sizeof(TypHeader));
				}
			}

			if (Doc->ParentDoc->TotalLines == 0) 
			{
				UpdateSeenArts(Doc->ParentDoc);
			}

			if (Doc->ParentDoc->ActiveLineID >= Doc->ParentDoc->ActiveLines) 
			{
				Doc->ParentDoc->ActiveLineID = Doc->ParentDoc->ActiveLines - 1;
			}

			SendMessage(Doc->ParentDoc->hDocWnd, WM_COMMAND, iSortOption, 0);
			AdjustTopScByDoc(Doc->ParentDoc, Doc->ParentDoc->ActiveLineID);
			sprintf(mybuf, "%u", Doc->ParentDoc->TotalLines);
			WVArgXSetWindowText(Doc->ParentDoc->hWndFrame, IDS_WVART_GROUP, CurrentGroup, mybuf, CurrentDescription);
			InvalidateRect(Doc->ParentDoc->hDocWnd, NULL, FALSE);
		}

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

	Doc->TextSelected = FALSE;
}

void sendme_article(TypDoc far *Doc)
{
	if (Doc->ParentDoc) 
	{
		TypBlock far    *BlockPtr;
		TypLine far             *LinePtr;
		TypGroup                *GroupDoc;
		HANDLE                  header_handle;
		HANDLE                  thread_handle;
		header_p                headers;
		HANDLE                  hBlock;
		unsigned int    Offset;
		TypLineID               MyLineID;

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

		if (GroupDoc) 
		{
			header_p                header;
			char far                *lpszGroupName;
			char                    mybuf[MAXINTERNALLINE];

			lpszGroupName = (char far *)LinePtr + sizeof(TypLine) + sizeof(TypGroup);

			if (lstrcmp(CurrentGroup, lpszGroupName)) 
			{
				CommState = ST_GROUP_REJOIN;
				sprintf(mybuf, "GROUP %s", lpszGroupName);
				PutCommLine(mybuf);
				strntcpy(CurrentGroup, lpszGroupName, MAXGROUPNAME);
				strntcpy(CurrentDescription, GroupDoc->Description, MAXDESCRIPTION);
			}

			header = header_elt(headers, Doc->LastSeenLineID);
			TypSendmeList * SMEptr = new TypSendmeList;
			if (SMEptr) 
			{
				SMEptr->next = GroupDoc->pSME;
				SMEptr->article = header->number;
				GroupDoc->pSME = SMEptr;
			}
			sprintf(mybuf, "SENDME %I64d", header->number);
			PutCommLine(mybuf);

		}

		unlock_headers(header_handle, thread_handle);
		UnlockLine(BlockPtr, LinePtr, &hBlock, &Offset, &MyLineID);
	}
}
#endif

#define NUMARTBUTTONS 21

LRESULT CALLBACK  WinVnArtFrameWndProc (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{

	RECT myRect;                  /* selection rectangle      */
	TypGroup far *GroupDoc;
	TypDoc *ThisDoc = NULL;
	int ih, i, j;
	BOOL found;
	BOOL bReverse = FALSE;
	TBBUTTON tbButton[NUMARTBUTTONS];
	TypLine far *LinePtr;
	TypBlock far *BlockPtr;
	HANDLE hBlock;
	char mybuf[MAXINTERNALLINE];
	char mybuf1[MAXINTERNALLINE];
	char headerLine[MAXHEADERLINE];
	char *ptr,*ptr1;
	unsigned int Offset;
	int textlen;
	BOOL continueFind;
	BOOL AllowCancel;
	TypLineID MyLineID, artindex;
	HANDLE header_handle;
	HANDLE thread_handle;
	header_p headers;
	header_p header;
	long header_num;
	HMENU hMenu, hSubMenu;
	PAINTSTRUCT ps;               /* paint structure          */
	HDC hDC;                      /* handle to display context */
	UINT ToolbarID = IDB_ARTTOOLBAR;
	int ToolbarHeight = TOOLBARHEIGHT;
	int ToolbarSize = 16;
	BOOL bSkipInit = FALSE;
	BOOL bSupersede = FALSE;

#ifdef OFFLINE  // TANAKA Goh
	char* pGenSockDLL;
#endif

	if (message == uMSH_MOUSEWHEEL) /* set by registerwindowsmessage in winvn.c but only used un NT 351 and Win95 */
		message = WM_MYMOUSEWHEEL;    /* this is the NT4.0 and Win98 default */

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

	if (!found)
		ThisDoc = CommDoc;

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

	int nCurrent = nToolbarArt;
	if ( (message == WM_SIZE || message == WM_RETHINKTOOLBAR ||message == WM_CREATE) && nCurrent == 3)
	{
		//determine dpi to pick size
		int size = xGetWindowDPI(hWnd);
		if (size <= 96)
			nCurrent = 0;
		else if (size <= 120)
			nCurrent = 1;
		else
			nCurrent = 2;
	}

    switch (nCurrent)
    {
        case 1:
            ToolbarID = IDB_ARTTOOLBARMED;
            ToolbarHeight = TOOLBARHEIGHTMED;
            ToolbarSize = 24;
            break;
        case 2:
            ToolbarID = IDB_ARTTOOLBARBIG;
            ToolbarHeight = TOOLBARHEIGHTBIG;
            ToolbarSize = 32;
            break;
        case 0:
        default:
            ToolbarID = IDB_ARTTOOLBAR;
            ToolbarHeight = TOOLBARHEIGHT;
            ToolbarSize = 16;
            break;
    }

  switch (message) 
  {
  case WM_KILLFOCUS:
#ifdef _DEBUG   
	DestroyCaret();
#endif	
	return (DefWindowProc (hWnd, message, wParam, lParam));

  case WM_SETFOCUS:
    StatBarArtPopups (ThisDoc);
    SetCapsLockText(hWnd);
    SetNumLockText(hWnd);
#ifdef _DEBUG     
	CreateCaret(ThisDoc->hDocWnd, (HBITMAP) NULL, 2, ArtLineHeight);//MulDiv (ArticleFontSize, ScreenYPixels, 72)); 
#endif
    /* fall through */



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

  case WM_PARENTNOTIFY:
        return 0L;
  case WM_DPICHANGED:
  {
	int g_dpi = HIWORD(wParam);
	//UpdateDpiDependentFontsAndResources();
	if (lParam && wParam)
	{
		int nDPI = xGetWindowDPI(ThisDoc->hDocWnd);
		return 0L;
	}
	break;
  }	
   
  case WM_WINDOWPOSCHANGED:
      
    if (ThisDoc->hWndXFace)
    {
        POINT ptTest;
        RECT faceRect, rect;

        GetWindowRect(ThisDoc->hWndXFace, &faceRect);
        ptTest.x = faceRect.left;
        ptTest.y = faceRect.top;

        if (!IsIconic( ThisDoc->hWndFrame))
		{
			GetWindowRect(ThisDoc->hWndFrame, &rect);
			ptTest.x -= rect.left;
			ptTest.y -= rect.top;
			if (ptTest.x != ptXFaceCoord.x ||
				ptTest.y != ptXFaceCoord.y )
			{

				rect.top += ptXFaceCoord.y;
				rect.left += ptXFaceCoord.x;
				rect.right = rect.left + faceRect.right - faceRect.left;
				rect.bottom = rect.top+ faceRect.bottom - faceRect.top;

				MakeRectVisible(rect);

        
				SetWindowPos( ThisDoc->hWndXFace, NULL, rect.left, rect.top, 0,0, SWP_NOSIZE | SWP_NOZORDER );

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

  break;

    case WM_ACTIVATEAPP:
	return 1;
	break;

  
  case WM_ACTIVATE:
    if((wParam == WA_ACTIVE) || (wParam == WA_CLICKACTIVE))
	{
       ActiveArticleDoc = ThisDoc;
		InvalidateRect(hWnd, NULL, FALSE);
	}
    /* fall throughto update toolbars */
    
  case WM_MYINITMENU:
  case WM_INITMENU:
    hMenu = GetMenu (hWnd);
    hSubMenu = GetSubMenu (hMenu, 0);   /* File menu */
    i = ((!CommBusy || ThisDoc != CommDoc) &&
         (CodingState == INACTIVE) && (ThisDoc->TotalLines > 0));
    EnableMenuItem (hSubMenu, IDM_DECODE_ARTICLE, i ? ENABLE_MENU : DISABLE_MENU);
#ifdef PREVIEW
    EnableMenuItem (hSubMenu, IDM_PREVIEW, i ? ENABLE_MENU : DISABLE_MENU);
#endif	
    SendMessage (ThisDoc->hWndTB, TB_ENABLEBUTTON, IDM_DECODE_ARTICLE, i);
#ifdef PREVIEW
    SendMessage (ThisDoc->hWndTB, TB_ENABLEBUTTON, IDM_PREVIEW, i);
#endif

#ifdef OFFLINE  // TANAKA Goh
	if ((pGenSockDLL = strrchr(GenSockDLL, '\\')) == NULL) 
	{
		pGenSockDLL = GenSockDLL;
	} 
	else 
	{
		pGenSockDLL++;
	}

	if (!stricmp(pGenSockDLL, "gwssoup.dll") || !stricmp(pGenSockDLL, "gsoup32.dll")) 
	{
		i = CommBusy || (ThisDoc->ParentDoc == (TypDoc *)NULL);
		EnableMenuItem (hSubMenu, IDM_DELETE_ARTICLE, i ? DISABLE_MENU : ENABLE_MENU);
		// ThisDoc->CountedLines is set by the XOVER command. If all these lines are headers
		// SENDME is useful, but since SENDME over the group-window always works
		// the user have some problems with this. Heirich 19970614
		// i = CommBusy || (ThisDoc->ParentDoc == (TypDoc *)NULL) || (ThisDoc->HeaderLines != ThisDoc->CountedLines);
		EnableMenuItem (hSubMenu, IDM_SENDME_ARTICLE, i ? DISABLE_MENU : ENABLE_MENU);
	} 
	else if (!stricmp(pGenSockDLL, "ggnspool.dll") || !stricmp(pGenSockDLL, "gspool32.dll")) 
	{
		i = CommBusy || (ThisDoc->ParentDoc == (TypDoc *)NULL);
		EnableMenuItem (hSubMenu, IDM_DELETE_ARTICLE, i ? DISABLE_MENU : ENABLE_MENU);
		EnableMenuItem (hSubMenu, IDM_SENDME_ARTICLE, DISABLE_MENU);
	} 
	else 
	{
		//EnableMenuItem (hSubMenu, IDM_DELETE_ARTICLE, DISABLE_MENU);
		//EnableMenuItem (hSubMenu, IDM_SENDME_ARTICLE, DISABLE_MENU);
		DeleteMenu (hSubMenu, IDM_DELETE_ARTICLE, MF_BYCOMMAND);
		DeleteMenu (hSubMenu, IDM_SENDME_ARTICLE, MF_BYCOMMAND);
	}
#endif

	    hSubMenu = GetSubMenu (hMenu, 3);   /* view menu */
	    i = CommBusy || (ThisDoc->ParentDoc == (TypDoc *)NULL);
	    EnableMenuItem (hSubMenu, IDM_NEXT_ARTICLE, i ? DISABLE_MENU : ENABLE_MENU);
	    EnableMenuItem (hSubMenu, IDM_FIND_NEXT_UNSEEN, i ? DISABLE_MENU : ENABLE_MENU);
	    EnableMenuItem (hSubMenu, IDM_NEXT_SAME_SUBJ, i ? DISABLE_MENU : ENABLE_MENU);
	    EnableMenuItem (hSubMenu, IDM_FIND_NEXT_SELECTED, i ? DISABLE_MENU : ENABLE_MENU);
	    EnableMenuItem (hSubMenu, IDM_PREV_ARTICLE, i ? DISABLE_MENU : ENABLE_MENU);
	
	    SendMessage (ThisDoc->hWndTB, TB_ENABLEBUTTON, IDM_NEXT_ARTICLE, i ? FALSE : TRUE);
	    SendMessage (ThisDoc->hWndTB, TB_ENABLEBUTTON, IDM_FIND_NEXT_UNSEEN, i ? FALSE : TRUE);
	    SendMessage (ThisDoc->hWndTB, TB_ENABLEBUTTON, IDM_NEXT_SAME_SUBJ, i ? FALSE : TRUE);
	    SendMessage (ThisDoc->hWndTB, TB_ENABLEBUTTON, IDM_FIND_NEXT_SELECTED, i ? FALSE : TRUE);
	    SendMessage (ThisDoc->hWndTB, TB_ENABLEBUTTON, IDM_PREV_ARTICLE, i ? FALSE : TRUE);

#ifdef OFFLINE  // TANAKA Goh
        if (!stricmp(pGenSockDLL, "gwssoup.dll") || !stricmp(pGenSockDLL, "gsoup32.dll") || !stricmp(pGenSockDLL, "ggnspool.dll") || !stricmp(pGenSockDLL, "gspool32.dll")) {
          i = CommBusy || (ThisDoc->ParentDoc == (TypDoc *)NULL);
          EnableMenuItem (hSubMenu, IDM_DELETE_NEXT_ARTICLE, i ? DISABLE_MENU : ENABLE_MENU);
          EnableMenuItem (hSubMenu, IDM_DELETE_FIND_NEXT_UNSEEN, i ? DISABLE_MENU : ENABLE_MENU);
        } else {
          //EnableMenuItem (hSubMenu, IDM_DELETE_NEXT_ARTICLE, DISABLE_MENU);
          //EnableMenuItem (hSubMenu, IDM_DELETE_FIND_NEXT_UNSEEN, DISABLE_MENU);
          DeleteMenu (hSubMenu, IDM_DELETE_NEXT_ARTICLE, MF_BYCOMMAND);
          DeleteMenu (hSubMenu, IDM_DELETE_FIND_NEXT_UNSEEN, MF_BYCOMMAND);
        }
#endif


  // The return causes a GPF in the Watcom runtime system AFTER the program has
  // ended. It modifies values in a  __OpenStreams memory array. These variables
  // are used by Watcom to close open streams.   (Peter Heirich)
  // On 08-Mar-1997 Peter said this is no longer necessary 

//    #if defined(__WATCOMC__) && !defined(__OS2__)
//    #else   
    if (message == WM_ACTIVATE) // kludgeville
      return (DefWindowProc (hWnd, message, wParam, lParam));
//    #endif
    break;

  case WM_QUERYNEWPALETTE:
	{
		if (g_hPalette)
		{
			HDC hDC = GetDC(hWnd);
			if (hDC)
			{
				SelectPalette(hDC, g_hPalette, FALSE);
				RealizePalette(hDC);
				ReleaseDC(hWnd, hDC);
				return TRUE;
			}
			return 0;

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

	}
    case WM_PALETTECHANGED:
          if (!g_hPalette || (HWND) wParam == hWnd)
               break ;

          {
			HDC hdc = GetDC (hWnd) ;
			  if (hdc)
			  {
				SelectPalette (hdc, g_hPalette, FALSE) ;
				RealizePalette (hdc) ;
				UpdateColors (hdc) ;
  				//TODO send to decendents
				ReleaseDC (hWnd, hdc) ;
			}
		  }
          break ;
    case WM_ERASEBKGND:

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

		}

	break;

  case WM_PAINT:
    /* paint status bar  */
    hDC = BeginPaint (hWnd, &ps);
    PaintStatbar (hWnd, hDC, ThisDoc);
    EndPaint (hWnd, &ps);
    if (ThisDoc->hWndXFace)
    {
                POINT ptTest;
        RECT faceRect, rect;

        GetWindowRect(ThisDoc->hWndXFace, &faceRect);
        ptTest.x = faceRect.left;
        ptTest.y = faceRect.top;

        GetWindowRect(ThisDoc->hWndFrame, &rect);
        ptTest.x -= rect.left;
        ptTest.y -= rect.top;
        if (ptTest.x != ptXFaceCoord.x ||
            ptTest.y != ptXFaceCoord.y )
        {

            rect.top += ptXFaceCoord.y;
            rect.left += ptXFaceCoord.x;
            rect.right = rect.left + faceRect.right - faceRect.left;
            rect.bottom = rect.top+ faceRect.bottom - faceRect.top;

            MakeRectVisible(rect);

        
            SetWindowPos( ThisDoc->hWndXFace, NULL, rect.left, rect.top, 0,0, SWP_NOSIZE | SWP_NOZORDER );
        }

    }

    return (DefWindowProc (hWnd, message, wParam, lParam));
    break;

	case WM_NCHITTEST:
	{
		
		LRESULT ht = DefWindowProc(hWnd, message, wParam, lParam); 
		if( ht == HTCLIENT ) 
		{ 
			POINT pt;
			RECT rc;
			pt.x = GET_X_LPARAM(lParam);  // horizontal position of cursor 
			pt.y = GET_Y_LPARAM(lParam);  // vertical position of cursor

			GetWindowRect(hWnd, &rc ); 
			rc.left = rc.right - GetSystemMetrics(SM_CXHSCROLL); 
			rc.top = rc.bottom - GetSystemMetrics(SM_CYVSCROLL); 
			if( PtInRect( &rc, pt ) ) 
				ht = HTBOTTOMRIGHT; 
		}
		return ht; 
		
	}
	break;


  case WM_SIZE:
    /* Store the new size of the window.                     */
    GetClientRect (hWnd, &myRect);
    MoveWindow (ThisDoc->hDocWnd, 0, ToolbarHeight,
                myRect.right - myRect.left,
    (myRect.bottom - myRect.top) - ToolbarHeight - StatbarPntData.dyStatbar,
                TRUE);
    MoveWindow (ThisDoc->hWndTB, 0, 0,
                myRect.right - myRect.left,
                ToolbarHeight,
                TRUE);
	if (wParam != SIZE_MINIMIZED)
	{
		if (ThisDoc->hWndXFace)
			ShowWindow(ThisDoc->hWndXFace, SW_SHOWNA);

	}
    break;

  case WM_CLOSE:
    ThisDoc->TextSelected = FALSE;
/* Ray Dunn:99/10:  Here and other in this module - automatically select an
   appropriate window when user closes article
*/
    CloseArtWndSelectAnother (hWnd, ThisDoc);
/* Ray Dunn:99/10:End*/

    return (0);
    break;

  case WM_DESTROY:
    NumArticleWnds--;
    ThisDoc->TextSelected = FALSE;
    ThisDoc->InUse = FALSE;
    ThisDoc->LongestLine = 0;
    ThisDoc->ScXOffset = 0;
	if (ThisDoc->hWndXFace)
	{
		DestroyWindow(ThisDoc->hWndXFace);
		ThisDoc->hWndXFace = NULL;
	}
    if (ThisDoc == CommDoc) 
	{
      CommBusy = FALSE;
      CommDoc = (TypDoc *) NULL;
    }
    
    /* Clean up pointers and Clear document */
    SeverArticleParent(ThisDoc);
    FreeDoc (ThisDoc);

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

    ActiveArticleDoc = (TypDoc *)NULL;
    for (j = MAXARTICLEWNDS - 1; j >= 0; j--) 
	{
      if (ArticleDocs[j].InUse) 
	  {
        ActiveArticleDoc = &(ArticleDocs[j]);
        break;
      }
    }
    return (0);
    break;

  case WM_RETHINKTOOLBAR:
      if (ThisDoc->hWndTB)
        {
            DestroyWindow(ThisDoc->hWndTB);
            ThisDoc->hWndTB = NULL;
        }
        bSkipInit = TRUE;
        PostMessage(hWnd, WM_SIZE, 0 ,0);
      //fall through

  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 = i;    // copy

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    tbButton[++j].iBitmap = ++i;    // Next Unseen

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

    tbButton[++j].iBitmap = ++i;    // Next Same Subject

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

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

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

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

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

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

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

#ifdef COMMCTRL
    tbButton[j].dwData = 0;
    tbButton[j].iString = 0;
#endif

#ifdef PREVIEW

    tbButton[++j].iBitmap = ++i;    /* preview */
    tbButton[j].idCommand = IDM_PREVIEW ;    
    tbButton[j].fsState = TBSTATE_ENABLED;
    tbButton[j].fsStyle = TBSTYLE_BUTTON;
#endif

#ifdef COMMCTRL
    tbButton[j].dwData = 0;
    tbButton[j].iString = 0;
    ThisDoc->hWndTB = CreateToolbarEx(hWnd,
                                      CCS_TOP | WS_VISIBLE | TBSTYLE_TOOLTIPS,
                                      ToolbarID,
                                      i + 1,
                                      hInst,
                                      ToolbarID,
                                      tbButton,
                                      j + 1,
                                      ToolbarSize,
                                      ToolbarSize,
                                      ToolbarSize,
                                      ToolbarSize,
                                      sizeof(TBBUTTON));
#else

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

#ifndef OFFLINE
	//remove unused menu items
	hMenu = GetMenu (hWnd);
	if (hMenu)
	{
		hSubMenu = GetSubMenu (hMenu, 0);     //file menu
		if (hSubMenu)
		{
			DeleteMenu(hSubMenu, IDM_SENDME_ARTICLE, MF_BYCOMMAND);
			DeleteMenu(hSubMenu, IDM_DELETE_ARTICLE, MF_BYCOMMAND);
		}

		hSubMenu = GetSubMenu (hMenu, 3);     //view menu
		if (hSubMenu)
		{
			DeleteMenu(hSubMenu, IDM_DELETE_NEXT_ARTICLE, MF_BYCOMMAND);
			DeleteMenu(hSubMenu, IDM_DELETE_FIND_NEXT_UNSEEN, MF_BYCOMMAND);
		}
	}      
#endif

    ThisDoc->hWndFrame = hWnd;
#ifdef COMMCTRL
    ThisDoc->hDocWnd = CreateWindowEx (WS_EX_CLIENTEDGE,
                                      "WinVnArt",
                                      NULL,
                                      WS_CHILD | WS_VSCROLL | WS_HSCROLL,
                                      0,                             /* Initial X position */
                                      ToolbarHeight,                 /* Initial Y position */
                                      myRect.right - myRect.left,    /* Initial X width */
     (myRect.bottom - myRect.top) - ToolbarHeight - StatbarPntData.dyStatbar,    /* Initial Y height */
                                      hWnd,
                                      NULL,
                                      hInst,
                                      NULL);
#else
    ThisDoc->hDocWnd = CreateWindow ("WinVnArt",
                                     NULL,
                                     WS_CHILD | WS_VSCROLL | WS_HSCROLL,
                                     0,                             /* Initial X position */
                                     ToolbarHeight,                 /* Initial Y position */
                                     myRect.right - myRect.left,    /* Initial X width */
    (myRect.bottom - myRect.top) - ToolbarHeight - StatbarPntData.dyStatbar,    /* Initial Y height */
                                     hWnd,
                                     NULL,
                                     hInst,
                                     NULL);
#endif

    SetHandleBkBrush (ThisDoc->hDocWnd, hArticleBackgroundBrush);
    ShowWindow (ThisDoc->hDocWnd, SW_SHOWNORMAL);

    NumArticleWnds++;

    SetArticleMenus (ThisDoc, DISABLE);
    // copy button should be disabled to begin with
    SendMessage (ThisDoc->hWndTB, TB_ENABLEBUTTON, IDM_COPY, FALSE);
//	SetFocus(hWnd);

    break;

  case WM_MYMOUSEWHEEL:
  case WM_MOUSEWHEEL:
  case WM_KEYDOWN:
    SendMessage (ThisDoc->hDocWnd, message, wParam, lParam);
    break;

  case WM_COMMAND:
    {
    WORD wFunc = LOWORD(wParam);
    switch (wFunc) 
	{
    case IDV_EXIT:
      ThisDoc->TextSelected = FALSE;
/* Ray Dunn:99/10:*/
      CloseArtWndSelectAnother (hWnd, ThisDoc);
/* Ray Dunn:99/10:End */

      return (0);
      break;

    case IDM_CONFIG_ARTICLE:
      DialogBox (hInst, MAKEINTRESOURCE(IDD_WINVNARTICLEPREFS), hWnd, lpfnWinVnArticleDlg);
      break;

    case IDM_SAVE:
      if (strcmp (SaveArtFileName, "")) 
	  {
        
		DWORD dwError =	MRRWriteDocument (ActiveArticleDoc, sizeof (TypText), SaveArtFileName, TRUE);
        if ( dwError != NO_ERROR)
		{
			//TODO report error
		}
        break;
      }
      else 
	  {
        goto saveas;
      }
    case IDM_SAVE_SELECTED: //F8 key mapping
    {
      if (CommBusy && CommDoc == ThisDoc) 
	  {
        WVMessageBox(hWnd, IDS_WVART_WAIT,IDS_WVART_PLEASEWAIT,MB_OK | MB_ICONINFORMATION); 
        break;
      }
    }

    case IDM_SAVEAS:
    saveas:;

      if (DialogBox (hInst, MAKEINTRESOURCE(IDD_WINVNSAVEART), hWnd, lpfnWinVnSaveArtDlg)) {
        InvalidateRect (ThisDoc->hDocWnd, NULL, TRUE);
      }
      break;

    case IDM_PRINT:
      PrintArticle (hWnd, ThisDoc);
      break;

    case IDM_PRINT_SETUP:
      PrinterSetup (hWnd, PD_PRINTSETUP);
      break;

    case IDM_SUPERSEDE:
		bSupersede = TRUE;
    case IDM_CANCELART:                         /* JD 4/10/95 */
      
        AllowCancel = FALSE;
        // Extra checks to make sure we really are allowed to Cancel this Article
		if (GetSameHeaderLines(ThisDoc, "Cancel-Lock:", headerLine, MAXHEADERLINE))
		{
			
			char lock[MAXHEADERLINE];
			strcpy(lock, headerLine);
			bool b256 = false;
			char * cur = lock, *end; 
			char * found = cur;
			while (!AllowCancel && (found = stristr(cur, "sha1:")) != NULL || (found = stristr(cur, "sha256:")) != NULL )
			{
				if ( !_strnicmp(found, "sha1:", 5) )
				{
					found +=5;
					b256 = false;
				}
				else if ( !_strnicmp(found, "sha256:", 7))
				{
					found +=7;
					b256 = true;	
				}
				cur = found;
				end = cur;
				while (*end && !isspace((unsigned char)*end))
					end++;

				GetHeaderLine (ThisDoc, "Message-ID:", headerLine, MAXHEADERLINE);
				if (headerLine && strlen(headerLine) > 11)
				{
					char * key;
					if (b256)
						key = sha_key(CancelKey, strlen(CancelKey), headerLine+12, strlen(headerLine+12) );
					else	
						key = sha_key1(CancelKey, strlen(CancelKey), headerLine+12, strlen(headerLine+12) );


					if (key)
					{
						BOOL bNext = *end != 0;

						*end = '\0';
						if (b256)
							AllowCancel = !sha_verify(key+7, cur);
						else
							AllowCancel = !sha_verify1(key+5, cur);

						free(key);
						if (bNext)
							cur = end+1;	
					} 
				} 


			}
				 
			if (!AllowCancel)
			{
				WVMessageBox(hWnd, IDS_WVART_NEEDKEY, IDS_WVART_NO_CANCEL, MB_OK | MB_ICONSTOP);
				break;
			}			

		}
		else
		{

			GetHeaderLine (ThisDoc, "From:", headerLine, MAXHEADERLINE);
			if (headerLine && strlen(headerLine) > 6) {
			  GetFromAddress (mybuf, MAXHEADERLINE, ThisDoc);
			  if (string_compare_insensitive(mybuf,headerLine+6))    /* 6 = size of "From: " */
				  AllowCancel = TRUE;
			  }
            
			if (AllowCancel == TRUE) 
			{
			  GetHeaderLine (ThisDoc, "Organization:", headerLine, MAXHEADERLINE);
			  if (headerLine && strlen(headerLine) > 14)
			  {
				GetOrganization (mybuf, MAXHEADERLINE, ThisDoc);
				if (_stricmp(mybuf,headerLine+14) == 0)    /* 14 = size of "Organization " */
				  AllowCancel = TRUE;
				else
				  AllowCancel = FALSE;
			  }
			}
		}
        
        if (AllowCancel == TRUE)
             CreateComposeWnd (hWnd, ThisDoc,  bSupersede ? DOCTYPE_SUPERSEDE : DOCTYPE_CANCEL);
        else
             WVMessageBox(hWnd, IDS_WVART_ORIGINAL_AUTHOR, IDS_WVART_NO_CANCEL, MB_OK | MB_ICONSTOP);            

      break;


	case IDM_PREVIEW:
		if (TestCodingBusy (hWnd, IDS_TEST_DECODE_ONE_ARTICLE))
		break;
		if (CommBusy && CommDoc == ThisDoc) 
		{
			WVMessageBox(hWnd, IDS_WVART_WAIT_DECODE,IDS_WVART_PLEASEWAIT,MB_OK); 
			break;
		}
		{
			WVString last = DecodePathName;
			GetTempPath(MAXFILENAME, DecodePathName);

		

			BOOL bSet = FALSE;
			char SubjectLine[MAXHEADERLINE];

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

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

				if (GroupDoc) 
				{
					header = NULL;
					TypLineID thisIndex = 0, foundIndex = -1;
					//TypLineID thisIndex = ThisDoc->LastSeenLineID;
					//header = header_elt(headers, thisIndex);
					//cant use because user may have changed index
					header_num = -1;

					while ((thisIndex < GroupDoc->max_headers) && header_num < 0) 
					{
						header = header_elt(headers, thisIndex);
						if (header->ArtDoc == ThisDoc)
						{
							foundIndex = thisIndex;

						}
						thisIndex++;
					}
					//if we've clicked on a reference we may not have an index for this doc					
					if (foundIndex < 0)
					{
						if (!GetHeaderLine (ThisDoc, "Subject:", SubjectLine, MAXHEADERLINE))
							break;
						header = NULL;
					}
					else
					{
					
						header = header_elt(headers, foundIndex);
						strncpy(SubjectLine, header->pHD->sSubject, MAXHEADERLINE-1);
					}
					

					{
						//create a new Entry
						HEADERENTRY* pEntry = WVFileSortHeaders::ProcessSubject(SubjectLine, header);
						if (pEntry)
						{

							if (pEntry->nTotal > 0 &&  pEntry->nPart > 0 && pEntry->nTotal != 1 )
							{

								WVFileSortHeaders* pFileList = NULL; 
								if (!GroupDoc->pFile_sort)
								{
									HCURSOR hCursorOld = SetCursor (LoadCursor (NULL, IDC_WAIT));
									SetStatbarText (hWnd, "Building grouped file list...", ThisDoc, TRUE, FALSE);
									GroupDoc->pFile_sort = pFileList = new WVFileSortHeaders();
									pFileList->SortDoc(ThisDoc->ParentDoc, 0 );
									SetStatbarText (hWnd, "", ThisDoc, TRUE, FALSE);
									SetCursor(hCursorOld);
								}
								else
								{
									pFileList = (WVFileSortHeaders*) GroupDoc->pFile_sort;
								}
								
								int nTotal = -1;
								int nAffected = pFileList->SelectArticlesBySubject( pEntry, nTotal, ART_PREVIEW, TRUE);

								if(	nAffected > 0 || nTotal > 1 )
								{
									
									bSet = TRUE;
									if (nTotal > 1)
									{
										InvalidateRect (ThisDoc->ParentDoc->hDocWnd, NULL, TRUE);
										UpdateWindow( ThisDoc->ParentDoc->hDocWnd);
										BOOL bNeverMind = FALSE;
										if (nAffected < nTotal)
										{
											if (WVArgXMessageBox(hWnd, IDS_WVART_INCOMPLETE,IDS_WVART_INCOMPLETE_TITLE,MB_YESNO | MB_ICONQUESTION, nAffected, nTotal) != IDYES)
											{
												bNeverMind = TRUE;
											}

										} 
										
										
										
										if (!bNeverMind && nAffected > 7)
										{
											if (WVArgXMessageBox(hWnd, IDS_WVART_PREVIEW_MANY,IDS_WVART_PREVIEW_TITLE,MB_YESNO |MB_ICONQUESTION, nAffected) != IDYES)
											{
												bNeverMind = TRUE;
											}
										}

											
										if (!bNeverMind)
										{
											if (Initializing == INIT_NOT_CONNECTED && Autoconnect)
												Reconnecting = 1;
											if (TestCommBusy (hWnd, IDS_TEST_PREVIEW))
											{
												bNeverMind = TRUE;
											}
										}

										
										if (bNeverMind)
										{
											
											pFileList->SelectArticlesBySubject( pEntry, nTotal, -ART_PREVIEW, TRUE);
											
											InvalidateRect (ThisDoc->ParentDoc->hDocWnd, NULL, TRUE);

										}
										else
										{
											AbortDownload = 0;
											DecodeInit (FALSE);
											if (DecodeDoc (hWnd, ThisDoc, TRUE) >= 0)
											{
												SetFlags(ThisDoc->ParentDoc, PreviewDecode);
												SendMessage( ThisDoc->ParentDoc->hWndFrame,WM_COMMAND, IDM_PREVIEW,0);
											}
			
										}
									}
								}

								//


							}

							free( pEntry);
							pEntry = NULL;
							
						}

					}

				}

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

			}


			
			if (!bSet)
			{
				DecodeInit (FALSE);
				DecodeDoc (hWnd, ThisDoc, TRUE);
				DecodeDone (TRUE);
			}

			strcpy(DecodePathName, last); 

	  }
	  break;
    case IDM_DECODE_ARTICLE:
      if (TestCodingBusy (hWnd, IDS_TEST_DECODE_ONE_ARTICLE))
        break;
      if (CommBusy && CommDoc == ThisDoc) 
	  {
        WVMessageBox(hWnd, IDS_WVART_WAIT_DECODE,IDS_WVART_PLEASEWAIT,MB_OK | MB_ICONINFORMATION); 
        break;
      }

      if (!DialogBoxParam (hInst,MAKEINTRESOURCE(IDD_WINVNDECODEARTS), hWnd, lpfnWinVnDecodeArtsDlg, 0)) {
        InvalidateRect (ThisDoc->hDocWnd, NULL, TRUE);
      }
      else 
	  {
        DecodeInit (FALSE);
        if (AlsoDecodeOpenArticles) 
		{
          DecodeOpenArticles (hWnd);
        }
        else 
		{
          DecodeDoc (hWnd, ThisDoc);
        }
        DecodeDone ();
      }
      break;

    case IDM_DECODE_SELECTED: //F10 accel, no dlg, just decode

      if (TestCodingBusy (hWnd, IDS_TEST_DECODE_ONE_ARTICLE))
        break;
      if (CommBusy && CommDoc == ThisDoc) 
	  {
        WVMessageBox(hWnd, IDS_WVART_WAIT_DECODE,IDS_WVART_PLEASEWAIT,MB_OK | MB_ICONINFORMATION); 
        break;
      }
      if (!*DecodePathName && !DialogBoxParam (hInst, MAKEINTRESOURCE(IDD_WINVNDECODEARTS), hWnd, lpfnWinVnDecodeArtsDlg, 0)) {
        InvalidateRect (ThisDoc->hDocWnd, NULL, TRUE);
        return 0;
      }
      
      DecodeInit (FALSE);
      if (AlsoDecodeOpenArticles) 
	  {
        DecodeOpenArticles (hWnd);
      }
      else 
	  {
        DecodeDoc (hWnd, ThisDoc);
      }
      DecodeDone ();
      break;


#ifdef OFFLINE  // TANAKA Goh
	case IDM_DELETE_ARTICLE:
		if ((pGenSockDLL = strrchr(GenSockDLL, '\\')) == NULL) 
		{
			pGenSockDLL = GenSockDLL;
		} 
		else 
		{
			pGenSockDLL++;
		}

		if (!stricmp(pGenSockDLL, "gwssoup.dll") || !stricmp(pGenSockDLL, "gsoup32.dll") ||
			!stricmp(pGenSockDLL, "ggnspool.dll") || !stricmp(pGenSockDLL, "gspool32.dll")) 
		{
			if (ThisDoc->ParentDoc) 
			{
				if (ConfirmDeleteArticle) 
				{
					if (WVMessageBox(hWnd, IDS_WVART_VERIFY_EXIT, IDS_WVART_PLEASE_CONFIRM, MB_YESNO | MB_ICONQUESTION) == IDYES) 
					{
						delete_article(ThisDoc);
						/* Ray Dunn:99/10:*/
						CloseArtWndSelectAnother (hWnd, ThisDoc);
						/* Ray Dunn:99/10:End */

					}
				} 
				else 
				{
				delete_article(ThisDoc);
				/* Ray Dunn:99/10:*/
				CloseArtWndSelectAnother (hWnd, ThisDoc);
				/* Ray Dunn:99/10:End */

				}
			}
		}

		return 0;

	case IDM_DELETE_NEXT_ARTICLE:
	if ((pGenSockDLL = strrchr(GenSockDLL, '\\')) == NULL) 
	{
		pGenSockDLL = GenSockDLL;
	} 
	else 
	{
		pGenSockDLL++;
	}

	if (!stricmp(pGenSockDLL, "gwssoup.dll") || !stricmp(pGenSockDLL, "gsoup32.dll") || 
		!stricmp(pGenSockDLL, "ggnspool.dll") || !stricmp(pGenSockDLL, "gspool32.dll")) 
	{
		if (ThisDoc->ParentDoc) 
		{
			if (ConfirmDeleteArticle) 
			{
				if (WVMessageBox(hWnd, IDS_WVART_VERIFY_NEXT, IDS_WVART_PLEASE_CONFIRM,MB_YESNO | MB_ICONQUESTION) == IDYES) 
				{
					delete_article(ThisDoc);

					if (ThisDoc->ParentDoc->ActiveLines == 0) 
					{
						WVMessageBox(hWnd,IDS_WVART_NO_MORE, IDS_WVART_THATS_ALL,MB_OK| MB_ICONINFORMATION); 
						/* Ray Dunn:99/10:*/
						CloseArtWndSelectAnother (hWnd, ThisDoc);
						/* Ray Dunn:99/10:End */

					} 
					else if (ThisDoc->ParentDoc->ActiveLines == ThisDoc->LastSeenLineID) 
					{
						WVMessageBox(hWnd,IDS_WVART_NO_MORE, IDS_WVART_THATS_ALL,MB_OK| MB_ICONINFORMATION);
						SendMessage(hWnd, WM_COMMAND, IDM_PREV_ARTICLE, 0);
					} 
					else 
					{
						ThisDoc->LastSeenLineID--;
						SendMessage(hWnd, WM_COMMAND, IDM_NEXT_ARTICLE, 0);
					}
				}
			} 
			else 
			{
				delete_article(ThisDoc);

				if (ThisDoc->ParentDoc->ActiveLines == 0) 
				{
					WVMessageBox(hWnd,IDS_WVART_NO_MORE, IDS_WVART_THATS_ALL,MB_OK| MB_ICONINFORMATION);
					/* Ray Dunn:99/10:*/
					CloseArtWndSelectAnother (hWnd, ThisDoc);
					/* Ray Dunn:99/10:End */
				} 
				else if (ThisDoc->ParentDoc->ActiveLines == ThisDoc->LastSeenLineID) 
				{
					WVMessageBox(hWnd,IDS_WVART_NO_MORE, IDS_WVART_THATS_ALL,MB_OK| MB_ICONINFORMATION);
					SendMessage(hWnd, WM_COMMAND, IDM_PREV_ARTICLE, 0);
				} 
				else 
				{
					ThisDoc->LastSeenLineID--;
					SendMessage(hWnd, WM_COMMAND, IDM_NEXT_ARTICLE, 0);
				}
			}
		}
	}

	return 0;

	case IDM_DEL_LINE:
	case IDM_DELETE_FIND_NEXT_UNSEEN:
		if ((pGenSockDLL = strrchr(GenSockDLL, '\\')) == NULL) 
		{
			pGenSockDLL = GenSockDLL;
		} 
		else 
		{
			pGenSockDLL++;
		}

		if (!stricmp(pGenSockDLL, "gwssoup.dll") || !stricmp(pGenSockDLL, "gsoup32.dll") || 
			!stricmp(pGenSockDLL, "ggnspool.dll") || !stricmp(pGenSockDLL, "gspool32.dll")) 
		{
			if (ThisDoc->ParentDoc) 
			{
				if (ConfirmDeleteArticle) 
				{
					if (WVMessageBox(hWnd, IDS_WVART_VERIFY_UNSEEN, IDS_WVART_PLEASE_CONFIRM, MB_YESNO | MB_ICONQUESTION) == IDYES) 
					{
						delete_article(ThisDoc);

						if (ThisDoc->ParentDoc->ActiveLines == 0) 
						{
							WVMessageBox(hWnd, IDS_WVART_NO_MORE_GROUP, IDS_WVART_THATS_ALL, MB_OK| MB_ICONINFORMATION);
							/* Ray Dunn:99/10:*/
							CloseArtWndSelectAnother (hWnd, ThisDoc);
							/* Ray Dunn:99/10:End */
						} 
						else if (ThisDoc->ParentDoc->ActiveLines == ThisDoc->LastSeenLineID) 
						{
							WVMessageBox(hWnd, IDS_WVART_NO_MORE_GROUP, IDS_WVART_THATS_ALL, MB_OK| MB_ICONINFORMATION);
							SendMessage(hWnd, WM_COMMAND, IDM_PREV_ARTICLE, 0);
						} 
						else 
						{
							ThisDoc->LastSeenLineID--;

							LockLine(ThisDoc->ParentDoc->hParentBlock, ThisDoc->ParentDoc->ParentOffset, ThisDoc->ParentDoc->ParentLineID, &BlockPtr, &LinePtr);
							GroupDoc = (TypGroup far *)((char far *)LinePtr + sizeof(TypLine));

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

								artindex = ThisDoc->LastSeenLineID;
								header_num = -1;

								while ((++artindex < GroupDoc->max_headers) && header_num < 0) 
								{
									header = header_elt(headers, artindex);
									if (ART_UNSEEN == (ART_SEENORKILLED & header->SeenSelected))
										header_num = artindex;
								}

								if (header_num >= 0) 
								{
									SendMessage(hWnd, WM_COMMAND, IDM_FIND_NEXT_UNSEEN, 0);
								} 
								else 
								{
									WVMessageBox(hWnd, IDS_WVART_NO_MORE_GROUP, IDS_WVART_THATS_ALL, MB_OK| MB_ICONINFORMATION);
									SendMessage(hWnd, WM_COMMAND, IDM_NEXT_ARTICLE, 0);
								}

								unlock_headers(header_handle, thread_handle);
							}

							UnlockLine(BlockPtr, LinePtr, &hBlock, &Offset, &MyLineID);
						}
				}
			} 
			else 
			{
				delete_article(ThisDoc);

				if (ThisDoc->ParentDoc->ActiveLines == 0) 
				{
					WVMessageBox(hWnd, IDS_WVART_NO_MORE_GROUP, IDS_WVART_THATS_ALL, MB_OK| MB_ICONINFORMATION);
					/* Ray Dunn:99/10:*/
					CloseArtWndSelectAnother (hWnd, ThisDoc);
					/* Ray Dunn:99/10:End */

				} 
				else if (ThisDoc->ParentDoc->ActiveLines == ThisDoc->LastSeenLineID) 
				{
					WVMessageBox(hWnd, IDS_WVART_NO_MORE_GROUP, IDS_WVART_THATS_ALL, MB_OK| MB_ICONINFORMATION);
					SendMessage(hWnd, WM_COMMAND, IDM_PREV_ARTICLE, 0);
				} 
				else 
				{
					ThisDoc->LastSeenLineID--;

					LockLine(ThisDoc->ParentDoc->hParentBlock, ThisDoc->ParentDoc->ParentOffset, ThisDoc->ParentDoc->ParentLineID, &BlockPtr, &LinePtr);
					GroupDoc = (TypGroup far *)((char far *)LinePtr + sizeof(TypLine));

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

						artindex = ThisDoc->LastSeenLineID;
						header_num = -1;

						while ((++artindex < GroupDoc->total_headers) && header_num < 0) 
						{
							header = header_elt(headers, artindex);
							if (ART_UNSEEN == (ART_SEENORKILLED & header->SeenSelected))
								header_num = artindex;
						}

						if (header_num >= 0) 
						{
							SendMessage(hWnd, WM_COMMAND, IDM_FIND_NEXT_UNSEEN, 0);
						} 
						else 
						{
							WVMessageBox(hWnd, IDS_WVART_NO_MORE_GROUP, IDS_WVART_THATS_ALL, MB_OK| MB_ICONINFORMATION);
							SendMessage(hWnd, WM_COMMAND, IDM_NEXT_ARTICLE, 0);
						}

						unlock_headers(header_handle, thread_handle);
						}

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

		return 0;

	case IDM_SENDME_ARTICLE:
	if ((pGenSockDLL = strrchr(GenSockDLL, '\\')) == NULL) 
	{
		pGenSockDLL = GenSockDLL;
	} 
	else 
	{
		pGenSockDLL++;
	}

	if (!stricmp(pGenSockDLL, "gwssoup.dll") || !stricmp(pGenSockDLL, "gsoup32.dll")) 
	{
		if (ThisDoc->ParentDoc) 
		{
			sendme_article(ThisDoc);
		}
	}

	return 0;
#endif

    case IDM_SELECT_ALL:
      hMenu = GetMenu (hWnd);
      hSubMenu = GetSubMenu (hMenu, 1);     /* Edit menu */

      EnableMenuItem (hSubMenu, IDM_COPY, ENABLE_MENU);
      SendMessage (ThisDoc->hWndTB, TB_ENABLEBUTTON, IDM_COPY, TRUE);
      EnableMenuItem (hSubMenu, IDM_DESELECT_ALL, ENABLE_MENU);

      ThisDoc->TextSelected = TRUE;
      ThisDoc->BeginSelect.LineNum = 0;
      ThisDoc->BeginSelect.CharNum = 0;
      ThisDoc->EndSelect.LineNum = ThisDoc->TotalLines - 1;
      FindLineOrd (ThisDoc, (unsigned) ThisDoc->EndSelect.LineNum, &BlockPtr, &LinePtr);
      textlen = GetTextLen(LinePtr);
      ThisDoc->EndSelect.CharNum = textlen - 1;
      GetClientRect (hWnd, &myRect);
      InvalidateRect (ThisDoc->hDocWnd, &myRect, FALSE);
      break;

    case IDM_DESELECT_ALL:
      hMenu = GetMenu (hWnd);
      hSubMenu = GetSubMenu (hMenu, 1);
      EnableMenuItem (hSubMenu, IDM_COPY, DISABLE_MENU);
      SendMessage (ThisDoc->hWndTB, TB_ENABLEBUTTON, IDM_COPY, FALSE);
      EnableMenuItem (hSubMenu, IDM_DESELECT_ALL, DISABLE_MENU);

      ThisDoc->TextSelected = FALSE;
      GetClientRect (hWnd, &myRect);
      InvalidateRect (ThisDoc->hDocWnd, &myRect, FALSE);
      break;

    case IDM_COPY:
      if (ThisDoc->TextSelected) {
        CopyTextToClipBoard (ThisDoc);
        SendMessage (ThisDoc->hWndFrame, (UINT) WM_COMMAND, (WPARAM) IDM_DESELECT_ALL, (LPARAM) 0L);    //Un-mark selection
      }
      break;

    case IDM_NEXT_SAME_SUBJ:
    case IDM_NEXT_ARTICLE:
    case IDM_PREV_ARTICLE:
    case IDM_FIND_NEXT_UNSEEN:
    case IDM_FIND_NEXT_SAME:
    case IDM_FIND_NEXT_SELECTED:

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

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

        if (GroupDoc) 
		{
          if (wFunc == IDM_FIND_NEXT_UNSEEN) 
		  {
           artindex = ThisDoc->LastSeenLineID;
            header_num = -1;

            while ((++artindex < GroupDoc->total_headers) && header_num < 0) 
			{
              header = header_elt (headers, artindex);
              if (ART_UNSEEN == (ART_SEENORKILLED & header->SeenSelected))
                header_num = artindex;
            }

            if (header_num >= 0) 
			{
              if (ArtListMultiSelect) 
			  {
                ThisDoc->ParentDoc->AnchorLineID = header_num;
                SetGroupSelections (ThisDoc->ParentDoc, headers, header_num);
              }
              SetArticleMenus (ThisDoc, DISABLE);
              ViewArticle (ThisDoc->ParentDoc, header_num, REUSE, SHOW, NO_ID);
              AdjustTopScByDoc (ThisDoc->ParentDoc, header_num);

            }
            else 
			{
              if (!ConfigJumpGroup
               || !RetrieveNextGroup( BlockPtr, LinePtr, IDS_WVART_THATS_ALL, FALSE))
                WVMessageBox(hWnd, IDS_WVART_NO_MORE_GROUP, IDS_WVART_THATS_ALL, MB_OK| MB_ICONINFORMATION);
            }
          }
                    
      else if (wFunc == IDM_FIND_NEXT_SELECTED)
      {
        artindex = -1;
        header_num = -1;

        while ((++artindex < GroupDoc->total_headers) && header_num < 0)
        {
          header = header_elt (headers, artindex);
          if(ART_SELECTED & header->SeenSelected)
          {
            if(artindex == ThisDoc->LastSeenLineID)
            {
              header->SeenSelected &= ~ART_SELECTED;
            }
            else
            {
              header_num = artindex;
            }
          }
        }

        if (header_num >= 0)
        {
          SetArticleMenus (ThisDoc, DISABLE);
          ViewArticle (ThisDoc->ParentDoc, header_num, REUSE, SHOW, NO_ID);
          article_operation(ThisDoc->ParentDoc, header_num, selected_false);
          AdjustTopScByDoc (ThisDoc->ParentDoc, header_num);
        }
        else
    WVMessageBox(hWnd, IDS_WVART_NO_MORE_SELECT, IDS_WVART_THATS_ALL, MB_OK| MB_ICONINFORMATION);
      }
          else if (wFunc == IDM_NEXT_SAME_SUBJ || wFunc == IDM_FIND_NEXT_SAME) 
		  {
            header_num =
              find_article_by_subject (headers,
                                       ThisDoc->LastSeenLineID,
                                       GroupDoc->total_headers - 1);

            if (header_num >= 0) {
              if (ArtListMultiSelect) {
                ThisDoc->ParentDoc->AnchorLineID = header_num;
                SetGroupSelections (ThisDoc->ParentDoc, headers, header_num);
              }
              SetArticleMenus (ThisDoc, DISABLE);
              ViewArticle (ThisDoc->ParentDoc, header_num, REUSE, SHOW, NO_ID);
              AdjustTopScByDoc (ThisDoc->ParentDoc, header_num);
            }
            else
        WVMessageBox(hWnd,IDS_WVART_NO_MORE, IDS_WVART_THATS_ALL,MB_OK| MB_ICONINFORMATION);
          }

          else if ((wFunc == IDM_NEXT_ARTICLE)
             && (ThisDoc->LastSeenLineID < (GroupDoc->total_headers - 1))) 
		  {
            if (ArtListMultiSelect) 
			{
              ThisDoc->ParentDoc->AnchorLineID = ThisDoc->LastSeenLineID;
              SetGroupSelections (ThisDoc->ParentDoc, headers, ThisDoc->LastSeenLineID + 1);
            }
            SetArticleMenus (ThisDoc, DISABLE);
            ViewArticle (ThisDoc->ParentDoc, ThisDoc->LastSeenLineID + 1, REUSE, SHOW, NO_ID);
            AdjustTopScByDoc (ThisDoc->ParentDoc, ThisDoc->LastSeenLineID + 1);
          }
          else if ((wFunc == IDM_PREV_ARTICLE)
                   && (ThisDoc->LastSeenLineID > 0)) {
            if (ArtListMultiSelect) {
              ThisDoc->ParentDoc->AnchorLineID = ThisDoc->LastSeenLineID;
              SetGroupSelections (ThisDoc->ParentDoc, headers, ThisDoc->LastSeenLineID - 1);
            }
            SetArticleMenus (ThisDoc, DISABLE);
            ViewArticle (ThisDoc->ParentDoc, ThisDoc->LastSeenLineID - 1, REUSE, SHOW, NO_ID);
            AdjustTopScByDoc (ThisDoc->ParentDoc, ThisDoc->LastSeenLineID - 1);
          }
          else
      WVMessageBox(hWnd,IDS_WVART_NO_MORE, IDS_WVART_THATS_ALL,MB_OK| MB_ICONINFORMATION);
        }

        else
    WVMessageBox(hWnd, IDS_WVART_NO_PREV, IDS_WVART_NO_PREV_T,  MB_OK| MB_ICONINFORMATION);

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

      else
    WVMessageBox(hWnd, IDS_WVART_NO_PREV, IDS_WVART_NO_PREV_T,  MB_OK| MB_ICONINFORMATION);

      break;

    case IDM_POST:
    if (FollowupToPoster(ThisDoc) == TRUE)
      CreateComposeWnd (hWnd, ThisDoc, DOCTYPE_MAIL);
    else
      CreateComposeWnd (hWnd, ThisDoc, DOCTYPE_POSTING);
      break;

    case IDM_MAIL:
      (MailCtrl.fnMlWinCreate) (hWnd, ThisDoc, DOCTYPE_MAIL);
      break;

    case IDM_FORWARD:
      (MailCtrl.fnMlWinCreate) (hWnd, ThisDoc, DOCTYPE_FORWARD);
      break;

    case IDM_HELP:
      DoHelp (ThisDoc->hDocWnd,  HELP_INDEX, 0L);
      break;

    case IDM_FIND:
    case IDM_SEARCH:
      FindDoc = ThisDoc;
      if (!(FindDoc->SearchStr[0]) && LastArticleTextFind[0])
        strcpy (FindDoc->SearchStr, LastArticleTextFind);

      if (DialogBox (hInst, MAKEINTRESOURCE(IDD_WINVNFIND), hWnd, lpfnWinVnFindDlg)) 
	  {
        found = DoFind (TRUE, FALSE, FALSE);
        if (!found) 
		{
          WVArgMessageBox(hWnd, IDS_WVART_ARGNOTFOUND, IDS_WVART_NOT_FOUND, MB_OK| MB_ICONINFORMATION, FindDoc->SearchStr);
        }
        else
          strcpy (LastArticleTextFind, FindDoc->SearchStr);
      }
      break;

    case IDM_FIND_PREV_SAME:
      bReverse = TRUE;
      //fall through
    case IDM_SEARCH_NEXT:
    
      FindDoc = ThisDoc;
      continueFind = TRUE;
	  ThisDoc->TextSelected = FALSE;
      if (!FindDoc->SearchStr[0]) {
        if (!(FindDoc->SearchStr[0]) && LastArticleTextFind[0])
          strcpy (FindDoc->SearchStr, LastArticleTextFind);
        continueFind = DialogBox (hInst, MAKEINTRESOURCE(IDD_WINVNFIND), hWnd, lpfnWinVnFindDlg);
      }

      if (continueFind && FindDoc->SearchStr[0]) {
        found = DoFind (!FindDoc->hFindBlock && !FindDoc->FindLineID, FALSE, bReverse);
        if (!found) 
		{
			InvalidateRect (hWnd, NULL, FALSE);
			WVArgMessageBox(hWnd, IDS_WVART_ARGNOTFOUND, IDS_WVART_NOMOREFOUND, MB_OK| MB_ICONINFORMATION, FindDoc->SearchStr);
        }
      }
      break;


    case IDM_ROT13:
      if (ThisDoc->TotalLines > 0) 
	  {
         SetArticleRot13Mode (ThisDoc->hWndFrame,
                              GetArticleRot13Mode (ThisDoc->hWndFrame) != TRUE);
         InvalidateRect (ThisDoc->hDocWnd, NULL, TRUE);
      }
      break;
    case IDM_THREADREAD:
    
		break;

    case ID_ARTICLE_ERROR:
    case ID_ARTICLE_RETRIEVE_COMPLETE:
		SetArticleMenus (ThisDoc, ENABLE);
		/* let my group know that I'm done receiving */
		/* use AsynMsg, because SendMessage is recursive and uses a lot of stack */
		/* there are problems with crosspostings too */
		if (ThisDoc->ParentDoc) 
		{
			AsynMsg.hwnd = ThisDoc->ParentDoc->hWndFrame;
			AsynMsg.uMsg = message;
			AsynMsg.wParam = wParam;
			AsynMsg.lParam = lParam;
			ExecAsynMessage = TRUE;
		}
		SetStatbarText (ThisDoc->hWndFrame, "", ThisDoc, TRUE, TRUE);
		InvalidateRect (ThisDoc->hWndFrame, NULL, TRUE);

		/* find out if this article is crossposted to other groups  JD 5/4/95*/
		if (((TrackSubscribedCrossposts == TRUE) || 
			(TrackUnsubscribedCrossposts == TRUE)) &&  
			(GetXref(headerLine,MAXHEADERLINE,ThisDoc))) 
		{
			ptr = headerLine;
			while (*ptr != 0) 
			{
				ptr1 = ptr;
				while((*ptr1 != 0) && (*ptr1 != ':'))
					(ptr1)++;  /* HB 1996/03/23 */
				strntcpy(mybuf,ptr,ptr1-ptr);                       /* save groupname */
				NextToken(&ptr);                                    /* skip over artindex */
				ptr1++;
				strntcpy(mybuf1,ptr1,ptr-ptr1);                     /* save artindex */
				if (_stricmp(mybuf,CurrentGroup) != 0)               /* mark if not our group */ 
					mark_article_seen(&NetDoc, mybuf,atoBIGINT(mybuf1));
			}
		}
		break;
	}
  }
  default:
    return (DefWindowProc (hWnd, message, wParam, lParam));

  }
  //SetFocus (ThisDoc->hDocWnd); //NO! is very very annoying when downloading multiple small articles! MDS
  return (0);
}

//Remember to unlock line if group IS found
BOOL FindGroup(char * groupname, TypDoc  *DocPtr, TypBlock **BlockPtr, TypLine **LinePtr)
{

	unsigned int Offset,TextOffset;
	int TargLen = 0;
	int SourceLen;
	char *Target;
	char sourceline[MAXGROUPNAME*2]; //*2 paranoia
	char targline[MAXGROUPNAME*2];
	char *sourceptr, far * orglineptr;
	char *targptr;

	TypLineID MyLineID;  
	register char ch;
	int CharPos = -1;


	if (!DocPtr || !groupname || !*groupname || !BlockPtr || !LinePtr)
		return NULL;

	//  First locate the Group Doc structure
	if(LockLine (DocPtr->hFirstBlock, sizeof(TypBlock), 0L, BlockPtr, LinePtr))
	{
		TextOffset = DocPtr->OffsetToText;
		Target = groupname;
		for (targptr = targline; ch = *Target, (*(targptr++) = tolower (ch)) && (TargLen < MAXGROUPNAME*2); TargLen++)
			Target++;

		if(((*LinePtr)->length != END_OF_BLOCK) && ((*LinePtr)->length > 0))
		{
			do
			{
				if((*LinePtr)->active)
				{
					orglineptr = (char far *) (*LinePtr) + TextOffset;
					sourceptr = sourceline;
					for(SourceLen = 0;
						ch = *(orglineptr), ((*(sourceptr++) = tolower (ch)) && SourceLen < 2*MAXGROUPNAME);
						SourceLen++)
					orglineptr++;

					if (SourceLen == TargLen)  // know these so may as well use them
						CharPos = strcmp(sourceline, targline);

				}
			} while(CharPos != 0 && NextLine (BlockPtr, LinePtr));
		}
		
		if (CharPos)
			UnlockLine (*BlockPtr, *LinePtr, &(*BlockPtr)->hCurBlock,&Offset,&MyLineID);

	
	} //if lockline

	return CharPos == 0;



}

//
// Mark an article as "seen".    (JD 5/4/95)
//
//  Doc       = pointer to the main Newsgroups Doc structure
//  groupname = string containing name of newsgroup
//  artindex  = long containing the article index number on the server
//

void mark_article_seen(TypDoc FAR * Doc, char * groupname, BIGINT artnum)
{
	TypBlock far *BlockPtr;
	TypLine far *LinePtr;
	HANDLE hBlock;
	unsigned int Offset;
	TypGroup far *MyGroup;
	TypLineID MyLineID;  
	long index;




#if 0
	int TargLen = 0;
	int CharPos = -1;
	int SourceLen;
	char *Target;
	char sourceline[MAXHEADERLINE];
	char targline[MAXFINDSTRING];
	char *sourceptr, far * orglineptr;
	char *targptr;
	register char ch;
	unsigned int TextOffset;

	//  First locate the Group Doc structure
	if(LockLine (Doc->hFirstBlock, sizeof(TypBlock), 0L, &BlockPtr, &LinePtr))
	{
		TextOffset = Doc->OffsetToText;
		Target = groupname;
		for (targptr = targline; ch = *Target, *(targptr++) = tolower (ch);TargLen++)
			Target++;

		if((LinePtr->length != END_OF_BLOCK) && (LinePtr->length > 0))
		{
			do
			{
				if(LinePtr->active)
				{
					orglineptr = (char far *) LinePtr + TextOffset;
					sourceptr = sourceline;
					for(SourceLen = 0;
						ch = *(orglineptr), ((*(sourceptr++) = tolower (ch)) && SourceLen < MAXHEADERLINE);
						SourceLen++)
					orglineptr++;
					
					/* Ray Dunn:99/12: Track seen crosspost fix
					There's a major problem here:

					CharPos = SearchLine (sourceline, SourceLen, targline, TargLen);

					SearchLine finds targline as *any substring* of sourceline, so, for example,
					"sci.astro" will be found in "sci.astro.seti",
					"comp.lang.c" will be found in "comp.lang.c++",
					"efg.h" will be found in abcd.efg.h".

					So tracking crosspostings has always been buggy, and corrupting the newsrc, because it
					will mark articles as seen in the wrong newsgroups!!

					Unless I misunderstand the logic here, a simple strcmp should be used, because we
					are just comparing two groupnames, and we've tolower'ed them both - in fact stricmp
					could be used directly, but let's not worry about that.

					I wonder if there is an endemic problem with the use of SearchLine - will check
					when I have time!
					*/
					if (SourceLen == TargLen)  // know these so may as well use them
						CharPos = strcmp(sourceline, targline);

				}
			} while(CharPos != 0 && NextLine (&BlockPtr, &LinePtr));
		}


		//    if(CharPos >= 0)
		if(CharPos == 0)
#endif
		if (FindGroup(groupname, Doc, &BlockPtr, &LinePtr))
		{
			//  Found Group, now get pointers to Group structure
			MyGroup = GetGroup(LinePtr);

			if(MyGroup->Subscribed ? TrackSubscribedCrossposts : TrackUnsubscribedCrossposts)
			{

				if (artnum > MyGroup->ServerFirst) //MDs don't track bogus Xref low numbers
				{


					if (!MyGroup->Determined)
					{
						InlineGroupPtr = MyGroup;
						if ( InlineGroup( groupname) )
						{
							if (!MyGroup->pSeendb) //never seen this group?  This could use large amts of memory
								MyGroup->pSeendb = new WVSeenDB(MyGroup->ServerFirst, MyGroup->ServerLast, NULL);
							else
							{
								//make a slightly bigger seenDB if necessary
								if (  artnum > MyGroup->pSeendb->GetHighRange()) //artnums lower than lorange are automatically seen
								{

									UBIGINT newTop = artnum + 5000;

									if (newTop > MyGroup->ServerLast)
										newTop = MyGroup->ServerLast;									
									
									WVSeenDB*pSDB;
									pSDB = MyGroup->pSeendb;
									TRACE("made a new SeenDB\r\n");

									MyGroup->pSeendb = new WVSeenDB(MyGroup->pSeendb->GetLowRange(), newTop, pSDB);
									if(MyGroup->pSeendb)
										delete pSDB;
									else
										MyGroup->pSeendb = pSDB;
								}
							
							}

							MyGroup->pSeendb->MarkSeen(artnum);
						}
						else
						{
							//inline failed, just track seen
							MyGroup->pSeendb->MarkSeen(artnum);
						}

					}
					else
					{
						//make a slightly bigger seenDB if necessary
						if (artnum > MyGroup->pSeendb->GetHighRange())
						{
							UBIGINT newTop = artnum + 5000;

							if (newTop > MyGroup->ServerLast)
								newTop = MyGroup->ServerLast;

							WVSeenDB* pSDB;
							pSDB = MyGroup->pSeendb;
							TRACE("made a new SeenDB\r\n");

							MyGroup->pSeendb = new WVSeenDB(MyGroup->pSeendb->GetLowRange(), newTop, pSDB);
							if(MyGroup->pSeendb)
								delete pSDB;
							else
								MyGroup->pSeendb = pSDB;

						}
						
						MyGroup->pSeendb->MarkSeen(artnum);
					}
					

					//MDS mark active visible group article as seen
					if((MyGroup->total_headers > 0) && (MyGroup->header_handle) && (MyGroup->SubjDoc))
					{
						header_p xheaders;

						xheaders = lock_headers (MyGroup->header_handle, MyGroup->thread_handle);
						for (index = 0; index < MyGroup->total_headers; index++) 
						{
							if ((long) xheaders[index].number == artnum)
							{
								NewsrcDirty = TRUE;
								xheaders[index].SeenSelected |= ART_SEEN;
								xheaders[index].SeenSelected &= ~ART_SELECTED;
								if(MyGroup->SubjDoc->hDocWnd)
									InvalidateRect(MyGroup->SubjDoc->hDocWnd, NULL, FALSE);
								break;
							}
						}

						unlock_headers (MyGroup->header_handle, MyGroup->thread_handle);

					}
				} //artindex > serverfirst
			}
		UnlockLine (BlockPtr,LinePtr,&hBlock,&Offset,&MyLineID);
		} 
		
#if 0	
	} //if lockline
#endif
}

/*
 support for xface 

*/

#define IS_WIN30_DIB(lpbi)  ((*(LPDWORD)(lpbi)) == sizeof(BITMAPINFOHEADER))

/*************************************************************************
 *
 * DIBNumColors()
 *
 * Parameter:
 *
 * LPSTR lpDIB      - pointer to packed-DIB memory block
 *
 * Return Value:
 *
 * WORD             - number of colors in the color table
 *
 * Description:
 *
 * This function calculates the number of colors in the DIB's color table
 * by finding the bits per pixel for the DIB (whether Win3.0 or OS/2-style
 * DIB). If bits per pixel is 1: colors=2, if 4: colors=16, if 8: colors=256,
 * if 24, no colors in color table.
 *
 ************************************************************************/

WORD DIBNumColors(LPBYTE lpDIB)
{
    WORD wBitCount;  // DIB bit count

    // If this is a Windows-style DIB, the number of colors in the
    // color table can be less than the number of bits per pixel
    // allows for (i.e. lpbi->biClrUsed can be set to some value).
    // If this is the case, return the appropriate value.
    

    if (IS_WIN30_DIB(lpDIB))
    {
        DWORD dwClrUsed;

        dwClrUsed = ((LPBITMAPINFOHEADER)lpDIB)->biClrUsed;
        if (dwClrUsed)

        return (WORD)dwClrUsed;
    }

    // Calculate the number of colors in the color table based on
    // the number of bits per pixel for the DIB.
    
    if (IS_WIN30_DIB(lpDIB))
        wBitCount = ((LPBITMAPINFOHEADER)lpDIB)->biBitCount;
    else
        wBitCount = ((LPBITMAPCOREHEADER)lpDIB)->bcBitCount;

    // return number of colors based on bits per pixel

    switch (wBitCount)
    {
        case 1:
            return 2;

        case 4:
            return 16;

        case 8:
            return 256;

        default:
            return 0;
    }
}
/*************************************************************************
 *
 * PaletteSize()
 *
 * Parameter:
 *
 * LPSTR lpDIB      - pointer to packed-DIB memory block
 *
 * Return Value:
 *
 * WORD             - size of the color palette of the DIB
 *
 * Description:
 *
 * This function gets the size required to store the DIB's palette by
 * multiplying the number of colors by the size of an RGBQUAD (for a
 * Windows 3.0-style DIB) or by the size of an RGBTRIPLE (for an OS/2-
 * style DIB).
 *
 ************************************************************************/

WORD PaletteSize(LPBYTE lpDIB)
{
    // calculate the size required by the palette
    if (IS_WIN30_DIB (lpDIB))
        return (DIBNumColors(lpDIB) * sizeof(RGBQUAD));
    else
        return (DIBNumColors(lpDIB) * sizeof(RGBTRIPLE));
}


/*************************************************************************
 *
 * FindDIBBits()
 *
 * Parameter:
 *
 * LPSTR lpDIB      - pointer to packed-DIB memory block
 *
 * Return Value:
 *
 * LPSTR            - pointer to the DIB bits
 *
 * Description:
 *
 * This function calculates the address of the DIB's bits and returns a
 * pointer to the DIB bits.
 *
 ************************************************************************/

LPBYTE FindDIBBits(LPBYTE lpDIB)
{
   return (lpDIB + *(LPDWORD)lpDIB + PaletteSize(lpDIB));
}


BOOL    bCapture = FALSE;
POINT   ptDown;
LRESULT CALLBACK  WinVnArtFaceWndProc( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{

    BOOL bHandled = FALSE;
    BOOL found;
    TypDoc *ThisDoc = NULL;
    int ih;

    for (ih = 0, found = FALSE; !found && ih < MAXARTICLEWNDS; ih++) 
    {
        if (ArticleDocs[ih].hWndXFace == hWnd) 
        {
            found = TRUE;
            ThisDoc = &(ArticleDocs[ih]);
        }
    }

    if (!found) 
    {
        ThisDoc = CommDoc;
    }
     

    switch (message) 
    {
        
	case WM_HELP:
		DoHelp (hWnd, HELP_CONTEXT, (DWORD)417);
		break;
       
	    case WM_COMMAND:
            {
                WORD wFunc = LOWORD(wParam);
                switch (wFunc)
                {
                    case IDM_COPY:
                        {
                            if (OpenClipboard(hWnd))
                            {
                                EmptyClipboard();
                                SetClipboardData(CF_BITMAP, ThisDoc->hbmFace);
                                CloseClipboard();
                            }
                        }
                        break;

                }
            }
            break;
        case WM_UPDATEFACE:
			{
				//ThisDoc->hbmFace
			   HGLOBAL hDib = (HGLOBAL) wParam;
			   HBITMAP hbmNew = NULL;

                // get a DC 
                HDC hDC = ::GetDC(NULL);
                if (!hDC)
                {
                    // clean up and return NULL

                    return -1;
                }

                //Face bitmap has default dc palette (black & white)
                //no need to mess with palettes

                // get a pointer to the DIB bits
                LPBYTE lpDIBBits = FindDIBBits( (LPBYTE) hDib);

                // create bitmap from DIB info. and bits
                hbmNew = ::CreateDIBitmap(hDC, (LPBITMAPINFOHEADER)hDib, CBM_INIT,
                        lpDIBBits, (LPBITMAPINFO)hDib, DIB_RGB_COLORS);

                if (hbmNew)
				{
					if (ThisDoc->hbmFace)
					{
						DeleteObject(ThisDoc->hbmFace);
						ThisDoc->hbmFace = NULL;

					}
					ThisDoc->hbmFace = hbmNew;
					InvalidateRect(hWnd, NULL, TRUE);
				}
                // clean up
                ::ReleaseDC(NULL, hDC);

				return 0L;
			}
			break;
        case WM_CREATE:
            if (lParam)
            {

                LPCREATESTRUCT  pCS = ( LPCREATESTRUCT) lParam;
                HGLOBAL hDib = (HGLOBAL) pCS->lpCreateParams;
                BITMAPINFOHEADER * pBM = (BITMAPINFOHEADER *) GlobalLock(hDib);


                // get a DC 
                HDC hDC = ::GetDC(NULL);
                if (!hDC)
                {
                    // clean up and return NULL
					GlobalUnlock(hDib);
                    return -1;
                }

                //Face bitmap has default dc palette (black & white)
                //no need to mess with palettes

                // get a pointer to the DIB bits
                LPBYTE lpDIBBits = FindDIBBits( (LPBYTE) pBM);
				LPBITMAPINFOHEADER pbmih = (LPBITMAPINFOHEADER) pBM;

                // create bitmap from DIB info. and bits
                ThisDoc->hbmFace = ::CreateDIBitmap(hDC, pbmih, CBM_INIT,
                        lpDIBBits, (LPBITMAPINFO)pBM, DIB_RGB_COLORS);

                
                // clean up
                ::ReleaseDC(NULL, hDC);
				GlobalUnlock(hDib);
                
            }
            
            break;
        

        case WM_CLOSE:
        

            break;

        case WM_DESTROY:
            {
                RECT rect;


                if (ThisDoc && ThisDoc->hWndFrame && !IsIconic(ThisDoc->hWndFrame))
                {
                    GetWindowRect(hWnd, &rect);
                    ptXFaceCoord.x = rect.left;
                    ptXFaceCoord.y = rect.top;

                    GetWindowRect(ThisDoc->hWndFrame, &rect);
                    ptXFaceCoord.x -= rect.left;
                    ptXFaceCoord.y -= rect.top;
                }
                if (ThisDoc->hbmFace)
                {
                    DeleteObject(ThisDoc->hbmFace);
                    ThisDoc->hbmFace = NULL;
                }

                bHandled = FALSE;
            }
            break;

        case WM_PAINT:
            {
                HDC hDC, hDCMem;
                BITMAP       bm;
                PAINTSTRUCT ps; 
                if (!ThisDoc->hbmFace)
                    break; 

                hDC = BeginPaint(hWnd, &ps);
                if (hDC)
                {


                    hDCMem = CreateCompatibleDC(hDC);

                    GetObject(ThisDoc->hbmFace, sizeof(BITMAP), &bm);

                    HBITMAP bmOld = (HBITMAP) ::SelectObject(hDCMem, ThisDoc->hbmFace );
                    BitBlt( hDC, 0, 0, bm.bmWidth, bm.bmHeight, hDCMem, 0, 0, SRCCOPY );
                    if (bmOld)
                        ::SelectObject(hDCMem, bmOld);
                        
                    DeleteDC(hDCMem);
                    
                    EndPaint(hWnd, &ps); 
                    bHandled = TRUE;
                }
            }
            
            
            break;

        case WM_LBUTTONDOWN:
            SetCapture(hWnd);
            bCapture = TRUE;
            GetCursorPos( &ptDown);
            break;

        case WM_ACTIVATE:
            if (wParam != WA_INACTIVE)
                break;
            //fall through   
        case WM_LBUTTONUP:
            if (bCapture)
                ReleaseCapture();
            bCapture = FALSE;
            break;

        case WM_RBUTTONDOWN:
            {
                HMENU hMenu = CreatePopupMenu();
                if (hMenu)
                {
                    POINT ptNow;
                    GetCursorPos( &ptNow);

                    AppendMenu(hMenu, MF_STRING | MF_ENABLED, IDM_COPY, "&Copy to clipboard");
                    TrackPopupMenu(hMenu, TPM_LEFTALIGN | TPM_RIGHTBUTTON,ptNow.x, ptNow.y, 0, hWnd, NULL);

                    DestroyMenu(hMenu);
                }
            }
            break;

        case WM_MOUSEMOVE:
            if (bCapture)
            {
                RECT rect;
                POINT ptNow;

                GetCursorPos( &ptNow);
                int x = ptDown.x - ptNow.x;
                int y = ptDown.y - ptNow.y;

                GetWindowRect(hWnd, &rect);

                rect.top -= y;
                rect.left -= x;

                ptDown = ptNow;

                SetWindowPos( hWnd, NULL, rect.left, rect.top, 0,0, SWP_NOSIZE | SWP_NOZORDER );
                if (ThisDoc && ThisDoc->hWndFrame)
                {
                    //GetWindowRect(hWnd, &rect);
                    ptXFaceCoord.x = rect.left;
                    ptXFaceCoord.y = rect.top;

                    GetWindowRect(ThisDoc->hWndFrame, &rect);
                    ptXFaceCoord.x -= rect.left;
                    ptXFaceCoord.y -= rect.top;
                }

            }
            break;
        default:
            break;
    }

    if (!bHandled)
        return (DefWindowProc (hWnd, message, wParam, lParam));
    
    
    return 0;
}

