// -*- C++ -*-
/*
 * $Id: wvutil.cpp 1.106 1997/04/05 00:39:18 dumoulin Exp $
 *
 */

/*-- WVUTIL.C -- File containing utility routines.
 */
#pragma warning( disable : 4786 ) // Turn off map template length warning
#include <windows.h>
#include <windowsx.h>           // for GlobalFreePtr (JSC)
#include "wvglob.h"
#include "winvn.h"
#include "xface.h"
#include "wvimage.h"

#ifdef _WIN32
#define COMPILE_MULTIMON_STUBS
#include <multimon.h>
#undef COMPILE_MULTIMON_STUBS 
#endif

#pragma hdrstop

#ifndef _WIN32
#include <dos.h>                // for _dos_getdiskfree  
#include <ver.h>

#else

#include <winver.h>

#endif

#include "wvclass.h"
#include <commdlg.h>            // for GetOpenFileName dialog (JSC)
#include <ctype.h>
#include <stdlib.h>
#include <time.h>
#include <io.h>                 // for _open etc
#include <fcntl.h>
#include <shellapi.h>           // for Registry access
#include <stdarg.h>
#define FIX_df__20000427        /* Dirk Fieldhouse XOVER server lies fixes */

char *get_xhdr_line (char *line);
/* Ray Dunn:99/10: added datetime arg for date sorting */
time_t parse_usenet_date (struct tm *datetime, char *date);
void finish_header_retrieval ();
void GenerateFileFilters (HWND hParentWnd, char *filters);

BOOL TrimHeader (char *header);
void AddCommLineToDoc (char *line);
void WrapAddCommLineToDoc (char *line);
void MakeXFaceWnd(const char * szFace);
void MakeFaceWnd(const char * szFace);

typedef struct tagCOLORITEM
{
	UINT MenuID;
	UINT TextID;
	COLORREF* pcrOriginal;
	COLORREF crCurrent;
} COLORITEM, *LPCOLORITEM;


// please update this if you modify XHDR retrieval
// This will now be either 5 or 7, depending on whether threading
// is enabled via 'threadp'.
unsigned int total_xhdrs = 5;

/*--- function GetNum --------------------------------------------
 *
 *  Cracks off a positive integer number from a string.
 *
 *  Entry    *ptr  is the character position to start scanning
 *                 for an integer
 *
 *  Exit     *ptr  is the character position at which we stopped
 *                 scanning (because of a non-digit).
 *           *num  is the cracked off number.
 *           Returns TRUE iff we got a number.
 */
BOOL GetNum (char **ptr, UBIGINT *num)
{
	BOOL gotit = FALSE;

	/* Skip initial spaces                                            */

	while ((**ptr) && **ptr == ' ')
		(*ptr)++;

	*num = 0;
	while (**ptr && isdigit (**ptr)) 
	{
		*num = 10 * (*num) + (**ptr - '0');
		gotit = TRUE;
		(*ptr)++;
	}
	return (gotit);
}
BOOL GetNum32 (char **ptr, unsigned long *num)
{
	BOOL gotit = FALSE;

	/* Skip initial spaces                                            */

	while ((**ptr) && **ptr == ' ')
		(*ptr)++;

	*num = 0;
	while (**ptr && isdigit (**ptr)) 
	{
		*num = 10 * (*num) + (**ptr - '0');
		gotit = TRUE;
		(*ptr)++;
	}
	return (gotit);
}


char * get_xhdr_line (char *line)
{
	char *cptr;
	/* skip past the art # and space */
	for (cptr = line; isdigit (*cptr); cptr++);
	for (; *cptr == ' '; cptr++);
	return (cptr);
}

#if 0
//MRB already did this
void make_neat_from (char far * in, char far * out)
{
	char far *left, far * right;

	/* this is controlled from .ini */
	if (FullNameFrom) 
	{
		left = strchr (in, '(');
		right = strrchr (in, ')');

		if ((left && right) && (left < right)) 
		{
			strntcpy (out, left + 1, (size_t) (right - left - 1));
			out[(right - left - 1)] = (char) 0;
		}
		else                        /* No name in parens */
			strcpy (out, in);
	}
	else                          /* !FullNameFrom */
		strcpy (out, in);
}
#endif

 
// Simple check to see if Username is legal.  This needs
// to be more complicated to handle all possible illegal
// names, but for now, just handle the main ones.
// Return TRUE if valid.   (JD 3/26/97)
//        
BOOL ValidEmailAddress(char far * name)
{
	char far *atsign;

	if (name) 
	{
		atsign = strchr (name, '@');
		if (atsign &&                          // needs an @ sign
			(atsign > name) &&                 // nonblank to left of @
			(strstr (atsign, "@.") == NULL) && // simple typo check
			(strchr (atsign, '.') != NULL) &&  // at least one '.' rt of @
			(strchr (name, '|') == NULL))      // don't allow pipes in name
		{
			return TRUE;
		}
	}
	return FALSE;
}

/*-- function StrToRGB -------------------------------------------------
 *
 *  Takes an ASCII string of the form "r,g,b" where r, g, and b are
 *  decimal ASCII numbers, and converts it to an RGB color number.
 */
COLORREF StrToRGB (char *cstring)
{
	BYTE red, green, blue;
	unsigned long int lred, lgreen, lblue;

	GetNum32 (&cstring, &lred);
	cstring++;
	GetNum32 (&cstring, &lgreen);
	cstring++;
	GetNum32 (&cstring, &lblue);
	red = (BYTE) lred;
	green = (BYTE) lgreen;
	blue = (BYTE) lblue;

	return (PALETTERGB (red, green, blue));
}

/*-- function RGBToStr -------------------------------------------------
 *
 *  Takes an RGB color ref and converts to a string of the form "r,g,b"
 *  result is placed in buf
 *  (JSC)
 */

char * RGBToStr (char *buf, COLORREF rgbVal)
{
	_sprintf(buf, "%u,%u,%u", GetRValue (rgbVal),
		   GetGValue (rgbVal),
		   GetBValue (rgbVal));
	return (buf);
}


/* String to Timezone */
time_t dTimeZone(char *s)
{
#define SIZEOF(array)   ((int)(sizeof array / sizeof array[0]))
#define ENDOF(array)    (&array[SIZEOF(array)])
#define HOUR(x)     (x * 60)

typedef struct _TABLE 
{
	char   *name;
	time_t value;
} ALIGNED TABLE;

/* Timezone table. */
static TABLE    TimezoneTable[] = {
    { "gmt",    HOUR( 0) }, /* Greenwich Mean */
    { "ut",     HOUR( 0) }, /* Universal */
    { "utc",    HOUR( 0) }, /* Universal Coordinated */
    { "cut",    HOUR( 0) }, /* Coordinated Universal */
    { "z",      HOUR( 0) }, /* Greenwich Mean */
    { "wet",    HOUR( 0) }, /* Western European */
    { "bst",    HOUR( 0)-60 },  /* British Summer */
    { "nst",    HOUR( 3)+30 }, /* Newfoundland Standard */
    { "ndt",    HOUR( 3)+30-60 }, /* Newfoundland Daylight */
    { "ast",    HOUR( 4) }, /* Atlantic Standard */
    { "adt",    HOUR( 4)-60 },  /* Atlantic Daylight */
    { "est",    HOUR( 5) }, /* Eastern Standard */
    { "edt",    HOUR( 5)-60 },  /* Eastern Daylight */
    { "cst",    HOUR( 6) }, /* Central Standard */
    { "cdt",    HOUR( 6)-60 },  /* Central Daylight */
    { "mst",    HOUR( 7) }, /* Mountain Standard */
    { "mdt",    HOUR( 7)-60 },  /* Mountain Daylight */
    { "pst",    HOUR( 8) }, /* Pacific Standard */
    { "pdt",    HOUR( 8)-60 },  /* Pacific Daylight */
    { "yst",    HOUR( 9) }, /* Yukon Standard */
    { "ydt",    HOUR( 9)-60 },  /* Yukon Daylight */
    { "akst",   HOUR( 9) }, /* Alaska Standard */
    { "akdt",   HOUR( 9)-60 },  /* Alaska Daylight */
    { "hst",    HOUR(10) }, /* Hawaii Standard */
    { "hast",   HOUR(10) }, /* Hawaii-Aleutian Standard */
    { "hadt",   HOUR(10)-60 },  /* Hawaii-Aleutian Daylight */
    { "ces",    -HOUR(1)-60 },  /* Central European Summer */
    { "cest",   -HOUR(1)-60 },  /* Central European Summer */
    { "mez",    -HOUR(1) }, /* Middle European */
    { "mezt",   -HOUR(1)-60 },  /* Middle European Summer */
    { "cet",    -HOUR(1) }, /* Central European */
    { "met",    -HOUR(1) }, /* Middle European */
    { "eet",    -HOUR(2) }, /* Eastern Europe */
    { "msk",    -HOUR(3) }, /* Moscow Winter */
    { "msd",    -HOUR(3)-60 },  /* Moscow Summer */
    { "wast",   -HOUR(8) }, /* West Australian Standard */
    { "wadt",   -HOUR(8)-60 },  /* West Australian Daylight */
    { "hkt",    -HOUR(8) }, /* Hong Kong */
    { "cct",    -HOUR(8) }, /* China Coast */
    { "jst",    -HOUR(9) }, /* Japan Standard */
    { "kst",    -HOUR(9) }, /* Korean Standard */
    { "kdt",    -HOUR(9)-60 },  /* Korean Daylight */
    { "cast",   -(HOUR(9)+30) }, /* Central Australian Standard */
    { "cadt",   -(HOUR(9)+30)-60 }, /* Central Australian Daylight */
    { "east",   -HOUR(10) },    /* Eastern Australian Standard */
    { "eadt",   -HOUR(10)-60 }, /* Eastern Australian Daylight */
    { "nzst",   -HOUR(12) },    /* New Zealand Standard */
    { "nzdt",   -HOUR(12)-60 }, /* New Zealand Daylight */
    { "", 0 }
};
	char *p;
	int tz;

	for (p = s; isalpha((BYTE)(*p)); p++)
		*p = tolower ((BYTE)*p);

	if (isalpha((BYTE)*s)) 
	{
		char cbak;
		TABLE *tp;

		cbak = *p;
		*p = '\0';
		/* Try for a timezone. */
		for (tp = TimezoneTable; tp < ENDOF(TimezoneTable); tp++)   
		{
			if (s[0] == tp->name[0] && s[1] == tp->name[1] && strcmp(s, tp->name) == 0)
				return (tp->value * (-60));
		}
		*p = cbak;
		s = p;
	}

	//handle +-200 tz format
	p = s;
	if (*p == '+')
		p++;

	tz = atoi(p);
	if (tz >= 12 || tz <= -12) 
	{
		int hour, min;
		hour = tz / 100;
		min = tz % 100;
		return ((time_t)(hour * 3600 + min * 60));
	}
	return ((time_t)tz * 3600);
}

/* This was lifted from ANU news. */

time_t parse_usenet_date (struct tm *ptime, char *s)
{
	char *cp, mon[80], tzone[80];
	int dom = 0, yr = 0, hr = 0, mn = 0, sc = 0, mth = 0;
	static char fmtMonthTable[37] = "janfebmaraprmayjunjulaugsepoctnovdec";
	time_t time;
	struct tm datetime = {0};
	TIME_ZONE_INFORMATION  tz;
	long gmtoff = 0;
	//TRACE("Time src %s\r\n",s);
	if (ptime)
		memset (ptime, 0, sizeof (struct tm));

	if (!s || !*s)
		return (0);
	if (cp = strchr (s, ','))
	s = ++cp;
	while (isspace ((unsigned char)*s))
		s++;
	*mon = '\0';
	if (isdigit (*s)) 
	{
		sscanf (s, "%d %s %d %d:%d:%d %s", &dom, mon, &yr, &hr, &mn, &sc, tzone);
		if (yr < 100) 
		{
			//
			//    Fix Year 2000 problem waiting to happen (JD 2/7/97)
			//      yr += 1900;
			//
			yr += (yr < 70) ? 2000 : 1900;
		}
	}
	else
		sscanf (s, "%s %d %d:%d:%d %d", mon, &dom, &hr, &mn, &sc, &yr);

	if (!dom || !yr || !*(cp = mon))
		return (0);
	if ((dom <= 0) || (dom >= 32))
		return (0);
	if ((yr < 1980) || (yr > 2050))
		return (0);
	if (strlen (mon) > 10)
		return (0);
	if ((hr < 0) || (hr > 23))
		return (0);
	if ((mn < 0) || (mn > 59))
		return (0);
	if ((sc < 0) || (sc > 59))
		return (0);

	for (cp = mon; *cp; cp++)
		*cp = tolower (*cp);

	if (cp = strstr (fmtMonthTable, mon))
		mth = (cp - fmtMonthTable) / 3;




	/*  Setup a Posix time structure and calculate time in absolute
	time (seconds since midnight, Jan 1, 1970    JD 06/25/93 */

	datetime.tm_year = yr - 1900;
	datetime.tm_mon = mth;
	datetime.tm_mday = dom;
	datetime.tm_hour = hr;
	datetime.tm_min = mn;
	datetime.tm_sec = sc;
	datetime.tm_isdst = 0;
	//UTC = local time + bias 
	 DWORD rc = GetTimeZoneInformation(&tz);
	 switch (rc) 
	 {
	 case TIME_ZONE_ID_UNKNOWN:
		 datetime.tm_isdst = 0;
		 /* Bias = UTC - local time in minutes
		  * gmtoff is seconds east of UTC
		  */
		 gmtoff = tz.Bias * -60;
		 break;
	case TIME_ZONE_ID_STANDARD:
		datetime.tm_isdst = 0;
		gmtoff = (tz.Bias + tz.StandardBias) * -60;
		break;
	case TIME_ZONE_ID_DAYLIGHT:
		 datetime.tm_isdst = 1;

		gmtoff = (tz.Bias + tz.DaylightBias) * -60;
		 break;
	 default:
		 datetime.tm_isdst = 0;
		 break;
	}

	time = mktime (&datetime);
	
	//TRACE("before TZ: %s, ", asctime( &datetime ));
	 
	time -=dTimeZone(tzone); //to gmt



	memcpy(&datetime, localtime(&time), sizeof (struct tm ) );   //store datetime
	//TRACE("Time out %s GMT\r\n", asctime( &datetime ) );

	time += (gmtoff);   //to local
	

	if (ptime)
	{
		memcpy(ptime, localtime(&time), sizeof (struct tm ) );   //store datetime
		//TRACE("Time out %s local\r\n", asctime( ptime ) );
	}

	return time;
}

/*-- function StringDate ----------------*/
char * StringDate (char *s, time_t time)
{
	struct tm *datetime = NULL;
	if (time != 0)
		datetime = localtime (&time);

/*
GetLocalTime(<);
GetDateFormat(LOCALE_USER_DEFAULT, 0, <, "dd'-'MMM'-'yyyy", (LPTSTR) szText, sizeof(szText));	
	
	// Set CRT library locale to user and system
	// defaults to get correct date/time formatting
	::GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SENGLANGUAGE, szBuf, STR_SZ);
	_tcscpy(szLocale, szBuf);
	::GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SENGCOUNTRY, szBuf, STR_SZ);
	if (_tcsclen(szBuf) != 0)
	{
		_tcscat(szLocale, _T("_"));
		_tcscat(szLocale, szBuf);
	}
	::GetLocaleInfo(LOCALE_SYSTEM_DEFAULT, 
		 LOCALE_IDEFAULTANSICODEPAGE, szBuf, STR_SZ);
	if (_tcsclen(szBuf) != 0)
	{
		_tcscat(szLocale, _T("."));
		_tcscat(szLocale, szBuf);
	}

	_tsetlocale(LC_ALL, szLocale);



    TCHAR strDate[STR_SZ];
    TCHAR strTime[STR_SZ];

    time_t lt;
    time (&lt);
    tm* timeptr = localtime (&lt); // get current time

    _tcsftime(strDate, STR_SZ, _T("%x"), timeptr); // format date
    _tcsftime(strTime, STR_SZ, _T("%X"), timeptr); // format time

*/


	if (time && datetime) 
	{
		if (fmtDaysB4Mth) 
		{
			_sprintf (s, "%02d%s%02d%s%02d %02d:%02d",
			datetime->tm_mday, fmtDateDelim, datetime->tm_mon + 1,
			fmtDateDelim,datetime->tm_year % 100,
			datetime->tm_hour, datetime->tm_min);
		}
		else 
		{
			_sprintf (s, "%02d%s%02d%s%02d %02d:%02d",
			datetime->tm_mon + 1, fmtDateDelim, datetime->tm_mday,
			fmtDateDelim,datetime->tm_year % 100,
			datetime->tm_hour, datetime->tm_min);
		}
		return (s);
	}
	else 
	{
		strcpy(s, "-----");
		return (s);
	}
}

/* not used
#define MSGF_SLEEPMSG 0x5300

BOOL SleepMsg(DWORD dwTimeout)
{
	DWORD dwStart = GetTickCount();
	DWORD dwElapsed;

#ifndef MWFMO_WAITANY
#define MWFMO_WAITANY 0
#endif

	while ((dwElapsed = GetTickCount() - dwStart) < dwTimeout) 
	{
		//MsgWaitForMultipleObjectsEx not available on Win95
		DWORD dwStatus = MsgWaitForMultipleObjectsEx(0, NULL,
							dwTimeout - dwElapsed, QS_ALLINPUT,
							MWMO_INPUTAVAILABLE);
		if (dwStatus == WAIT_OBJECT_0) 
		{
			MSG msg;
			while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) 
			{
				if (msg.message == WM_QUIT) 
				{
					PostQuitMessage((int)msg.wParam);
					return FALSE; // abandoned due to WM_QUIT
				}
				if (!CallMsgFilter(&msg, MSGF_SLEEPMSG)) 
				{
					TranslateMessage(&msg);
					DispatchMessage(&msg);
				}
			}
		}
	}
	return TRUE; // timed out
}
*/
void LoadGroup()
 {
	BOOL dolist;

	SetStatbarText (NetDoc.hWndFrame, "Load Group List?", &NetDoc, TRUE, TRUE);

	dolist = DoList;
	if (dolist == ID_DOLIST_ASK - ID_DOLIST_BASE)
	{
		if (WVMessageBox (NetDoc.hDocWnd, IDS_WVUTIL_REQUEST_LATEST, IDS_WVUTIL_REQUEST_LIST,
					MB_ICONQUESTION | MB_YESNO | MB_DEFBUTTON2) == IDNO)
			dolist = 0;
	}

	/* may have lost connection while dialog box up... */
	//We don't care now if autoreconnecting
	//if (Initializing == INIT_NOT_CONNECTED) 
	//{
	//
	//break;
	// }
	if (dolist) 
	{
		StartList ();
		did_list = 1;
	}
	else 
	{
		did_list = 0;
		CommState = ST_NONE;
		CommBusy = FALSE;
		Initializing = INIT_READY;
		if (CommDoc)
			SetStatbarText (CommDoc->hWndFrame, "", CommDoc, TRUE, TRUE);
		InvalidateRect (NetDoc.hDocWnd, NULL, FALSE);
	}
}

/*-- function DoCommInput ---------------------------------------
 *
 *
 */
void DoCommInput ()
{
    int ch;

    if ((CommBusy == TRUE) && (StallTimeout)) 
    {
        
		if (CommDecoding || (CommDoc && CommDoc->ParentDoc && CommDoc->ParentDoc->savingArtIndex != (UINT)-1) )		
		{
			if ((--InternalTimeoutCounter) == 0) 
			{

				TRACE("Download Stalled!\r\n");
				InternalTimeoutCounter = StallTimeout * 1000 / COMM_TIMER_INTERVAL;	 //seconds/250
				
				MRRCloseComm(); //CommState = ST_CLOSED_COMM;

				PostMessage (NetDoc.hDocWnd, (UINT) WM_COMMAND,
								(WPARAM) IDM_DISCONNECTED, (LPARAM) 0);

				return;


			}
		}
	}
	//ST_NONE no group
	if ((CommState == ST_IN_GROUP) && (CommBusy == FALSE) && (CommTimeout)) 
	{
		if ((--TimeoutCounter) == 0) 
		{
			
			char mybuf[	MAXGROUPNAME + 20];
			TypDoc *statDoc;

			CommBusy = TRUE; // this causes not to execute upon next WM_TIMER 
			CommState = ST_TIMEOUT_WAKEUP;

			statDoc = CommDoc ? CommDoc : &NetDoc;
			WVSetStatbarText (statDoc->hWndFrame, CommState, statDoc, TRUE, TRUE);

			_sprintf(mybuf,"GROUP %s\r\n",CurrentGroup);
			PutCommData(mybuf, strlen(mybuf));
			TimeoutCounter = INTERNAL_RESPONSE_TIMEOUT;  // this is 12 currently 
			return; // check response during next WM_TIMER tick, end now, enough done 
			
		}
	}
	else if ((CommState == ST_TIMEOUT_WAKEUP) && (CommBusy) && (CommTimeout)) 
	{
		if ((--TimeoutCounter) == 0) 
		{
			//MDS abort ignored command otherwise we're stuck waiting forever
			TypDoc *statDoc;
			TimeoutCounter = CommTimeout * 1000 / COMM_TIMER_INTERVAL; 
			CommState = ST_IN_GROUP;
			CommBusy = FALSE;
			statDoc = CommDoc ? CommDoc : &NetDoc;
			SetStatbarText (statDoc->hWndFrame, "", statDoc, TRUE, TRUE);

			return;
		}
	}
	else if ( (CommTimeout) && (CommState != ST_IN_GROUP) && (CommState != ST_TIMEOUT_WAKEUP) && (CommBusy)) 
	{
		//reset timer there has been activity

		// time = seconds * 1000 / refreshtime in ms
		TimeoutCounter = CommTimeout * 1000 / COMM_TIMER_INTERVAL; 
	}

	#define TIMELIMIT 3000
	
	DWORD dwStartTime = GetTickCount();
    
	while ((CommState != ST_CLOSED_COMM) && ((ch = MRRReadComm ()) >= 0)) 
    {
        
		if (ch == IgnoreCommCh) 
        {
        }
        else if (ch == EOLCommCh) 
        {
            *CommLinePtr = '\0';
            DoCommState ();
            CommLinePtr = CommLineIn;
			InternalTimeoutCounter = StallTimeout * 1000 / COMM_TIMER_INTERVAL;
			TimeoutCounter = CommTimeout * 1000 / COMM_TIMER_INTERVAL;
			if( GetTickCount() - dwStartTime >= TIMELIMIT )
				break;	 //give up some time 

        }
        else 
        {
            *(CommLinePtr++) = (char) ch;
            if (CommLinePtr == CommLineLWAp1)
				CommLinePtr--;
			InternalTimeoutCounter = StallTimeout * 1000 / COMM_TIMER_INTERVAL;
			TimeoutCounter = CommTimeout * 1000 / COMM_TIMER_INTERVAL;
        }
    }
}

void update_window_title (HWND hwnd,
                     char *group_name,
                     unsigned long line_number,
                     unsigned long total_lines)
{
	char tPercent[20];
	static int prevPercent, newPercent;
	// to avoid flicker, update percent only if it has changed more than 1%

	line_number *= 100;
	if (newPercent < prevPercent)
		prevPercent = 0;

	if ((line_number % UPDATE_TITLE_FREQ) == 0) 
	{
		newPercent = (total_lines > 0) ? (int) (line_number / total_lines) : 0;
		if (newPercent != prevPercent && newPercent - prevPercent > 1) 
		{
			_sprintf (tPercent, "%d", newPercent);
			WVArgXSetWindowText (hwnd, IDS_GROUP_TITLE, CurrentGroup, tPercent, CurrentDescription);
			SetStatbarPercent(hwnd, newPercent, CommDoc, TRUE);
			prevPercent = newPercent;
		}
	}
}


int check_server_code (int retcode)
{
	HWND hAlertWnd;
	int iClass = retcode / 100;

	if (ComposeWnd) 
	{
		hAlertWnd = ComposeWnd->hWnd;
	}
	else if (CommDoc) 
	{
		if ( (CommDecoding || ( CommDoc->ParentDoc && CommDoc->ParentDoc->savingArtIndex != (UINT)-1 )) && 
			(retcode == 423 || retcode == 430) ) //bad message #, no such article
		{
			//If we're saving multiple messages, don't bother showing bad article msg MDS 
			return (-1); 
		}
		hAlertWnd = CommDoc->hWndFrame;

	}
	else 
	{
		hAlertWnd = NetDoc.hWndFrame;
	}

	switch (iClass) 
	{
		case 5:
		CommBusy = FALSE;
		CommState = ST_NONE;
#ifdef _WIN32
		//WVMessageBox (hAlertWnd, IDS_WVUTIL_ERR_SERVER, IDS_WVUTIL_WINVN1, MB_OK | MB_ICONHAND | MB_SETFOREGROUND);
		WVArgMessageBox (hAlertWnd, IDS_WVUTIL_STRING1, IDS_WVUTIL_ERR_SERVER,
						MB_OK | MB_ICONHAND | MB_SETFOREGROUND, CommLineIn );

#else
		WVMessageBox (hAlertWnd, IDS_WVUTIL_ERR_SERVER, IDS_WVUTIL_WINVN1, MB_OK | MB_ICONHAND );

#endif
		return (1);
		break;
	case 4:
		CommBusy = FALSE;
		CommState = ST_NONE;
#ifdef _WIN32
		WVArgMessageBox (hAlertWnd, IDS_WVUTIL_STRING1, IDS_WVUTIL_MESSAGE_FROM,
						MB_OK | MB_ICONHAND | MB_SETFOREGROUND, CommLineIn );
#else
		WVArgMessageBox (hAlertWnd, IDS_WVUTIL_STRING1, IDS_WVUTIL_MESSAGE_FROM, MB_OK | MB_ICONHAND, CommLineIn  );
#endif
		if (retcode == 400)
		{
			Disconnect();
			return (1);
			break;
		}
		return (1);
		break;
	}
	return (0);
}

/*  Function sync_artnum

   Normally XREF returns lists of the same length for each header type
   but some servers have errors that could cause these lists to get
   out of sync. This function tries to find the proper location in the
   headers array and returns that location.  If the article number isn't
   found, it returns -1.  JD 6/19/93 */

long sync_artnum (BIGINT artnum,
             BIGINT activenum,
             header_p headers, TypGroup far * GroupDoc)
{
	long headerloc = CommDoc->ActiveLines;
	if (artnum == activenum)
		return (headerloc);
	else if (artnum < activenum) 
	{
		while ((artnum != activenum) && (headerloc > 0)) 
		{
			headerloc--;
			if ((header_elt (headers, headerloc))->number == artnum)
				return (headerloc);
		}
		return (-1);
	}
	else 
	{
		while ((artnum != activenum) && (headerloc < GroupDoc->total_headers - 1)) 
		{
			headerloc++;
			if ((header_elt (headers, headerloc))->number == artnum)
				return (headerloc);
		}
		return (-1);
	}
}

// Some Usenet voting software is generating obscenely long message-id's
// with non-unique front-parts 30 chars or greater... this was throwing
// off winvn in an interesting way.
// We solve this by replacing the last 4 characters of
// mid's longer than HEADER_MESSAGE_ID_LENGTH with a 4-char hex hash value
// string of the entire mid.

// This must be done to all references _and_ mid's in order for threading
// to be able to match 'em.

// slightly modified version of 'HashGroup' from wvlist.c

const char * message_id_with_frob(const char * source)
{
	size_t pos;
	unsigned long sum;
	const char * p;
	static char sRetval[HEADER_MESSAGE_ID_LENGTH + 1];

	sRetval[0] = '\0';
	if(source)
	{
		//  if('<' == *source) // skip any leading '<'
		//      source++;
		// fix a bug where a bad references line causes a GPF
		// JD 2/10/97
		//
		while (source && (('<' == *source) || 
						  (' ' == *source) ||
						  ('>' == *source)))
			source++;
		if(source)
		{
			strntcpy(sRetval, source, HEADER_MESSAGE_ID_LENGTH);
			sRetval[HEADER_MESSAGE_ID_LENGTH] = '\0';
			
			if(pos = strcspn(source, "> \t")) // chop trailing whitespace and '>'
			{
				if (pos < HEADER_MESSAGE_ID_LENGTH)
					sRetval[pos] = 0;
			}
			// only frob if we need to
			if(pos >= HEADER_MESSAGE_ID_LENGTH)
			{
				sum = 0;
				for (p = source; *p; p++)
				{
					sum = (sum << 1) + *p;
				}
				_sprintf(sRetval+HEADER_MESSAGE_ID_LENGTH-8, "%08x", sum);
			}
		}
	}

	// if there is no valid reference, somehow string objects
	// release hugh chunks of memory when you allocate an object
	// of zero length.  This is debug code so we can set a breakpoint.
	// (JD 2/14/97)
	//
	// if(source == 0)
	//    return 0;
	// else
	//   return WVString(sRetval);

	return (sRetval);
}


//
// Side effect:
//  The last '>' replaced with '\0'
//
char * get_best_reference (char * refer,WVString& message_id)
{
	char * start, * end;
	if (!refer)
		return NULL;
	start = strrchr (refer, '<');
	if (!start) 
	{
		// no valid ref here without a '<'
		return NULL;
	} 
	else 
	{
		start++;
		if (!start) 
			return NULL;          // JD 2/27/97
		end = strrchr (start, '>');
		if (!end) 
		{
			// the last ref is not complete, recurse
			*(start-1) = '\0';
			return (get_best_reference (refer, message_id));
		} 
		else 
		{
			if (strcmp(message_id_with_frob(start),message_id)==0) 
			{
				/* some broken software append the own message_id on the references, recurse */
				*(start-1) = '\0';
				return (get_best_reference(refer, message_id));
			} 
			else 
			{
				*end = 0; // was: *(end+1) = (char)0;  HAB 19951229
				return start;
			}
		}
	}
}



// clear out the latest header info
void ClearLatestInfo()
{
	Latest.lines = 0;
	Latest.iGroups = 0;
	Latest.sFrom       = "";
	Latest.sEmail      = "";
	Latest.sSubject    = "";
	Latest.sMessage_id = "";
	Latest.sNewsgroups = "";
	Latest.sNNTPPostHost= "";
	Latest.sXFace = "";
	Latest.sFace = "";
	Latest.cAddWrap = 0;
}

int CountTabs(char *pStr, int nMax)
{
	int ntab = 0;
	char *p = pStr;
	while (p && *p && p - pStr < nMax)
		if (*p++ == 0x09)
			ntab++;
	return ntab;
}

BOOL StripNewline( char* pStr, int nMax)
{
	char *pSrc = pStr;
	char *pDest = pStr;
	if (!pStr)
		return FALSE;

	while(*pSrc && pSrc - pStr < nMax) 
	{
		if (*pSrc == 0x0D || *pSrc == 0x0A) 
		{
			pSrc++;
			continue;
		}

		*pDest++ = *pSrc++;
	}
	if (pDest - pStr < nMax)
		*pDest = '\0';

	return (pDest != pSrc);
}

// return pointer to text of specified field.
// fields are separated by tabs
char * GetField(char * pStr, int nMax, int field)
{
	int ntab = 0;
	char *p = pStr;
	while (p && *p && p - pStr < nMax && ntab != field)
		if (*(p++) == 0x09)
			ntab++;

	if (p - pStr >= nMax)
		return NULL;
	return p;
}

//determine if the field contains only numbers
BOOL IsNumberField(char * p)
{
	BOOL bNum = FALSE;
	while (p && *p && *p > 0x1F) 
	{
		if (!isdigit(*p) && !isspace((unsigned char)*p)) 
		{
			bNum = FALSE;
			break;
		}
		bNum = TRUE;
		p++; 
	}
	return bNum;
}

// XOVER should contain the following fields
//  0              1         2       3     4            5+        6       7     8
// articlenumber, subject, author, date, message-id, references, bytes, lines, Xref:
// Some servers break that sequence w/ multiple references before the last two fields
// Look for:  number<tab>*<tab>*<tab>*<tab>*<tab>*<tab>number<tab>number*
// OR number<tab>(multiple fields)<tab>number<tab>number*
// OR number<tab>*<tab>*<tab>*<tab>*<tab>*<tab><tab>   (empty lines, and/or bytes)
//
// return: 0 - Invalid
//         1 - Standard  (7 standard fields)
//         2 - Extra fields, line & byte fields at end
//         3 - extra references
int IsValidXOVER( char * pStr, int nMax)
{
	char * p;
	BOOL bMoreTabsOK = TRUE;
	BOOL bBorkedRefs = FALSE;
	int nTabCount = CountTabs(pStr, nMax);

	if ( nTabCount < 7)
		return 0;

	if (!IsNumberField(pStr)) //article #
		return 0;

	if (nTabCount > 7) 
	{
		p = GetField(pStr, nMax, nTabCount -1);
		if ( p == NULL || ! IsNumberField(p))
			bMoreTabsOK = FALSE;

		if (bMoreTabsOK) 
		{
			p = GetField(pStr, nMax, nTabCount );
			if ( p == NULL || ! IsNumberField(p))
				bMoreTabsOK = FALSE;      
		}

		if (bMoreTabsOK) //there are extra fields, but it ends as expected
			return 2;
		
	}

	p = GetField(pStr, nMax, 6);  //allow for empty fields (bytes)
	if ( p == NULL || (!IsNumberField(p) && *p != 0x09))
		bBorkedRefs = TRUE;
	
	p = GetField(pStr, nMax, 7);  //allow for empty fields (lines)
	if ( p == NULL || (!IsNumberField(p) && *p != 0x09 && *p != 0x00))
		bBorkedRefs = TRUE;

	if (bBorkedRefs)
	{
		p = GetField(pStr, nMax, nTabCount );
		if ( !strnicmp(p,"xref: ", 6))
		{
			//last tab is xref? and previous 2 are digits (or empty)? then there are extra references
			char * b= GetField(pStr, nMax, nTabCount-2 );
			char * l= GetField(pStr, nMax, nTabCount-1 );
			if ( (b==NULL || IsNumberField(b)) && (l==NULL || IsNumberField(l)) )
			{
				return 3;
			}
		}
	}

	return 1;
}
/*--- function ParseAddress --------------------------
 *   String version of the function defined in WVHeader
 */
void
ParseAddress(const char *headerline, WVString& sAddr, WVString& sName)
{
	char addr[MAXHEADERLINE];
	char name[MAXHEADERLINE];
	ParseAddress(headerline, addr, sizeof(addr), name, sizeof(name));
	sAddr = addr;
	sName = name;
}


// store detailed info on the latest header read
void StoreHeaderInfo(const char* line)
{
	const char* sTmp = line;

	if (Latest.cAddWrap)
	{
		//wrapped header lines start with space
		if (!sTmp || !isspace( (const unsigned char) *sTmp))
			Latest.cAddWrap = 0;
	}   

	if(!sTmp) 
		return;
	
	while(*sTmp && !isspace((const unsigned char)*sTmp)) // find first whitespace
		sTmp++;
	while(*sTmp && isspace((const unsigned char)*sTmp)) // find first char following whitespace
		sTmp++;

	if(!_strnicmp ("subject:", line, 8))
	{
		Latest.sSubject = sTmp;
		Latest.cAddWrap = 3;
	}
	else if(!_strnicmp ("from:", line, 5))
	{
		ParseAddress(sTmp, Latest.sEmail, Latest.sFrom);
	}
	else if(!_strnicmp ("lines:", line, 6))
	{
		Latest.lines = atoi(sTmp);
	}

	else if(!_strnicmp ("newsgroups:", line, 11))
	{
		Latest.sNewsgroups = sTmp;
		Latest.iGroups = 1;
		while(sTmp = strchr(sTmp, ','))
		{
			Latest.iGroups++;
			sTmp++;
		}
	}

	else if(!_strnicmp ("message-id:", line, 11))
	{
		if(sTmp)
			Latest.sMessage_id = message_id_with_frob(sTmp);
		else
			Latest.sMessage_id = "";
	}
	else if (!_strnicmp ("NNTP-Posting-Host:", line, 18))
	{
		if (sTmp)
			Latest.sNNTPPostHost = sTmp;
	} 
	else if ( !_strnicmp ("x-face: ", line, 8))
	{
		Latest.cAddWrap = 1;
		if (sTmp)
		{
			Latest.sXFace = sTmp;
			MakeXFaceWnd( Latest.sXFace );
		}
	}
	else if ( !_strnicmp ("Face: ", line, 6))
	{
		Latest.cAddWrap = 2;
		if (sTmp)
		{
			Latest.sFace = sTmp;
		}

	}
	else if (Latest.cAddWrap)
	{
		if (sTmp)
		{
			if (Latest.cAddWrap == 1)
			{
				Latest.sXFace += sTmp;
				MakeXFaceWnd( Latest.sXFace );
			}
			else if (Latest.cAddWrap == 2)
			{
				Latest.sFace += sTmp;
			}
			else if (Latest.cAddWrap == 3)
			{
				Latest.sSubject += sTmp;
			}

		}

	}
}


//   Build a list of frobbed references
//
//  Side effects:
//    Source string gets clobbered with nulls
//
void build_ref_list(WVString& sDest, const char*src,WVString& message_id)
{
	char * sTmp;
	WVString mid;
	size_t midlen;
#if 1 // S.Yoshida
	const char * start;
#else
	char * start;
#endif
	char * chk;
	BOOL found;

	if(src[0])
	{
		sTmp = new char[2 + strlen(src)];
		if(sTmp)
		{
			sTmp[0] = 0;
			start = strchr(src, '<');
			while(start)
			{
				/* some broken software inserts < > or <> which causes to add a reference twice, so jump over */
				while (start && (('<' == *start) || (' ' == *start) || ('>' == *start))) start++;
				mid = message_id_with_frob(start); /* some broken software puts itself into the references */
				if (strcmp(mid,message_id)!=0) 
				{
					/* i'm shure that it isn't the mid of the current article */
					mid += " "; /* add a blank to separate entries */
					midlen = strlen(mid);
					chk = sTmp;
					found = FALSE;
					while (chk) 
					{
						while ((chk) && (*chk == ' ')) 
							chk++;
						if (strncmp(chk,mid,midlen) == 0) 
						{
							/* this message-id is already part of the reflist */
							/* don't refer it twice */
							found = TRUE;
						}
						chk = strchr(chk,' ');

					}
					if (!found) 
					{
						/* not twice, add it to the ref_list */
						strcat(sTmp, mid);
					}
				}

				if (start)
					start = strchr(start,'>');
				if (start)
					start = strchr(start,'<');
			}
			sDest = sTmp;
			delete [] sTmp;
		}
	}
	else
		sDest = "";
}


/*-- function DoCommState ----------------------------------------------
 *
 *  Function to implement an FSA to process incoming lines from
 *  the server.
 *  This function is called once for each line from the server.
 *
 *    Entry    CommLineIn  is a zero-terminated line received from
 *                         the server.
 *             CommState   is the current state of the FSA.
 */
char group[MAXINTERNALLINE];
char LastCommLine[MAXCOMMLINE];

void DoCommState ()
{
	TypLine far *LinePtr;
	TypBlock far *BlockPtr;
	HANDLE hBlock;
	unsigned int Offset;
	TypLineID MyLineID;
	int retcode;
	int found;
	static UBIGINT uLastArtNum = 0;
	long syncnum;
	int mylen;
	BOOL DlgStatus = FALSE;
	BOOL do_authinfo;

	char mybuf[MAXHEADERLINE];
	char mybuf2[MAXHEADERLINE];
	char * ptr;
	char * ptr1;
	const char far *lpsz;
	HANDLE header_handle;
	HANDLE thread_handle;
	header_p headers;
	header_p header;
	TypGroup far *GroupDoc;
	WVSeenDB*pSDB;
	static int PrevState = ST_NONE;

	TypDoc *statDoc;


	/* CommDoc is !NULL if retrieving group list, article headers or articles */
	/* CommDecoding is true if retrieving article in decode mode (not to a doc) */
	/* PostEdit !NULL if we are posting (this is from an edit, no doc involved) */
	if (CommDoc || CommDecoding || SendingPost || CommState == ST_TIMEOUT_WAKEUP) 
	{
		if (CommState != PrevState) 
		{
			PrevState = CommState;
			statDoc = CommDoc ? CommDoc : &NetDoc;
			WVSetStatbarText (statDoc->hWndFrame, CommState, statDoc, TRUE, TRUE);
		}

	switch (CommState) 
	{
	case ST_NONE:
#ifdef _DEBUG
		MessageBeep( (UINT) -1);
		OutputDebugString(CommLineIn);
		OutputDebugString("\r\n");	
#endif
	break;

	case ST_ESTABLISH_COMM:
		if (!sscanf (CommLineIn, "%u", &retcode))
			break;
		/* check for innd, send 'mode reader' command */
		/* this is only necessary in unusual cases... */
		/* ... at least until someone ports INN to NT 8^) */
		SendModeReader = FALSE;
		if ( (strstr (CommLineIn, "InterNetNews") &&
			!strstr (CommLineIn, "NNRP")) || strstr (CommLineIn, "(no reading)")) 
		{
			//may have to authenticate first
			SendModeReader = TRUE;

		}
		sprintf(mybuf, "N: %s", NNTPHost);
		g_bUseXHDR = GetPrivateProfileInt (mybuf, "ActionUseXHDR", 1, szAppProFile);
  
		if (retcode == 200 || retcode == 201)    /* was 500 from induced error */
		{
			CommBusy = TRUE;
			do_authinfo = FALSE;
			Authenticated = FALSE;
			if (strlen (NNTPUserName)) 
			{
				/* We have the AUTHINFO username.  Do we have the password? */
				if (!strlen (NNTPPasswordEncrypted)) 
				{
					/* Get the news server user password from the user */
					if (DialogBoxParam (hInst, MAKEINTRESOURCE(IDD_WINVNCOMM), NetDoc.hDocWnd, (DLGPROC) lpfnWinVnCommDlg, 1L)
							&& strlen (NNTPPasswordEncrypted)) 
					{
						do_authinfo = TRUE;
					}
					//has connection been closed while dlg up?
					if (CommState == ST_CLOSED_COMM)
					{
						Authenticated = FALSE;
						NNTPPasswordEncrypted[0] = '\0';
						CommBusy = FALSE;
						WVSetStatbarText (NetDoc.hWndFrame, CommState, &NetDoc, TRUE, TRUE);
						break;
					}
				}
				else 
				{
					do_authinfo = TRUE;
				}
			}
			if (do_authinfo) 
			{
				_sprintf (mybuf, "AUTHINFO user %s", NNTPUserName);
				CommState = ST_CHECK_AUTHINFO_USERNAME;
				WVSetStatbarText (NetDoc.hWndFrame, CommState, &NetDoc, TRUE, TRUE);
				PutCommLine (mybuf);
			}
			else 
			{
				goto End_Authinfo;
			}

		}
		else 
		{
			Disconnect ();
			InvalidateRect (NetDoc.hDocWnd, NULL, TRUE);
			WVArgMessageBox (NetDoc.hDocWnd, IDS_WVUTIL_STRING2, IDS_WVUTIL_ACCESS_PROB, MB_OK, CommLineIn);
		}
		break;

	case ST_CHECK_AUTHINFO_USERNAME:
		retcode = 0;
		sscanf (CommLineIn, "%u", &retcode);
		if (!retcode)
			break;
		if (retcode >= 500) 
		{
			Disconnect ();
			InvalidateRect (NetDoc.hDocWnd, NULL, TRUE);
			WVMessageBox (NetDoc.hDocWnd, IDS_WVUTIL_AUTHORITY, IDS_WVUTIL_WINVN2, MB_OK | MB_ICONHAND);
			break;
		}
		// retcode should be 381
		MRRDecrypt (NNTPPasswordEncrypted, (unsigned char *) mybuf2, MAXINTERNALLINE);
		_sprintf (mybuf, "AUTHINFO pass %s", mybuf2);
		CommState = ST_CHECK_AUTHINFO_PASSWORD;
		WVSetStatbarText (NetDoc.hWndFrame, CommState, &NetDoc, TRUE, TRUE);
		PutCommLine (mybuf);
		break;

		case ST_CHECK_AUTHINFO_PASSWORD:
		retcode = 0;
		if (sscanf (CommLineIn, "%u", &retcode) <= 0)
			break;
		//retcode should be 281
		if (retcode < 200 || retcode > 299) 
		{
			Disconnect ();
			InvalidateRect (NetDoc.hDocWnd, NULL, TRUE);
			WVArgMessageBox (NetDoc.hDocWnd, IDS_WVUTIL_ARG_AUTHORITY, IDS_WVUTIL_WINVN3, MB_OK | MB_ICONHAND, CommLineIn );
			break;
		}
		else 
		{
			/* Authentication was successful.  Store this fact, and the name under
			* which the user was authenticated.
			*/
			Authenticated = TRUE;
			strntcpy (AuthenticatedName, NNTPUserName, MAXNNTPSIZE - 1);
		}
		goto End_Authinfo;


    case ST_END_AUTHINFO:
End_Authinfo:;

		if (SendModeReader)
		{
			CommState = ST_END_AUTHINFO;
			SendModeReader=FALSE;
			PutCommLine ("mode reader");
			break;

		}

		if (use_zhdr == 1)
		{
			CommState = ST_XFEATURES_CHECK;
			PutCommLine("XFEATURES");
			break;
		}
		else if (use_zhdr == 2)
		{
			CommState = ST_XFEATURES_CHECK;
			PutCommLine("XZVER");
			break;

		}


EndFeatures:;

		/* allow exit now... */
		SendMessage (NetDoc.hWndFrame, WM_MYINITMENU, (WPARAM) 0, (LPARAM) 0);

		if (Reconnecting)
		{
			Reconnecting = 0;

			CommBusy = FALSE;

			WVSetStatbarText (NetDoc.hWndFrame, CommState, &NetDoc, TRUE, TRUE);
			HideShowWindows(SW_SHOW);
			Initializing = INIT_READY;
			SetStatbarText (CommDoc->hWndFrame, "", CommDoc, TRUE, TRUE);
			InvalidateRect (NetDoc.hDocWnd, NULL, FALSE);

			if (*CurrentGroup)
			{
				CommState = ST_GROUP_RETURN;
				CommBusy = TRUE;
				_sprintf(mybuf,"GROUP %s\r\n",CurrentGroup);
				PutCommData(mybuf, strlen(mybuf));

			}
			else
			{
				CommState = ST_RECONNECTED;
			}
			//in case main wnd is hidden, don't rely on WM_PAINT
			SetUserMenus (&NetDoc, ENABLE);
			Initializing = INIT_DONE;
			break;
		}

		/* now check for the XOVER command */
		CommState = ST_XOVER_CHECK;
		PutCommLine ("XOVER");

		break;

	case ST_XFEATURES_CHECK:
		retcode = 0;
		sscanf (CommLineIn, "%u", &retcode);

		if ( retcode == 390 && use_zhdr == 1) 
		{
			//send features
			//like: COMPRESS GZIP

			CommState = ST_XFEATURES_RESPONSE;
			PutCommLine("COMPRESS GZIP\r\n.");
			break;
		}

		if ((retcode/100) == 4) 
		{
			
			if ( retcode == 412 &&  use_zhdr == 2)	//not in newsgroup
			{
				goto EndFeatures;
				break;
			}
			if (retcode == 480)  //auth required
			{
				Disconnect ();
				InvalidateRect (NetDoc.hDocWnd, NULL, TRUE);
				WVArgMessageBox (NetDoc.hDocWnd, IDS_WVUTIL_UNEXPECTED, IDS_WVUTIL_WINVN3, MB_OK | MB_ICONHAND, CommLineIn );
				break;
			}

		}
		//any other 400/500 level reponse
		if (use_zhdr == 1)
		{
			use_zhdr = 2;
			CommState = ST_END_AUTHINFO; //try again with XZVER
			DoCommState();
			break;

		}
		else if (use_zhdr == 2)
		{
			if (retcode == 500 || retcode == 400)
			{
				use_zhdr  = 0;
			}


		}
		
		goto EndFeatures;	
		

		break;
	case ST_XFEATURES_RESPONSE:
		//server is telling us the order of the XOVER fields, we just look for the one that makes
		//crossreferences & ngroup filters easy
		retcode = 0;
		sscanf (CommLineIn, "%u", &retcode);
		if ( retcode == 290 ) 
		{
			//290 features updated
			//We don't actually need to do anything
		}

		//end of features

		goto EndFeatures;


		break;



	case ST_XOVER_CHECK:
		retcode = 0;
		sscanf (CommLineIn, "%u", &retcode);

		//Charter is returning 224 these days... prevent multiple list request dlgs
		if (strcmp (CommLineIn, ".") == 0 || (retcode == 224 && strchr(CommLineIn, '.') != 0)) 
			break;

		/* 412 == 'not in a newsgroup', others return 411 no such newsgroup*/
		if ( ((retcode/100) == 4 || retcode == 224) && (force_xhdr == 0 || force_xhdr == HDR_XOVER))    
			xoverp = 1;
		else if ((retcode/100) == 5)      /* 500 == 'command not understood' */
		{
			//if we have FORCE, it shouldn't Auto Disable!!
			if (force_xhdr != HDR_XHDR && retcode == 502) //INN 2.5.0 (20081026 snapshot) is returning 502 Read access denied
				xoverp = 1;
			else 
				xoverp = 0;
		}
			


		if (retcode == 480)  //auth required
		{
			Disconnect ();
			InvalidateRect (NetDoc.hDocWnd, NULL, TRUE);
			WVArgMessageBox (NetDoc.hDocWnd, IDS_WVUTIL_UNEXPECTED, IDS_WVUTIL_WINVN3, MB_OK | MB_ICONHAND, CommLineIn );
			break;
		}
		InvalidateRect (NetDoc.hDocWnd, NULL, FALSE);

		/* now check for the OVERVIEW.FMT command */
		CommState = ST_OVERVIEW_FMT_CHECK;
		PutCommLine ("LIST OVERVIEW.FMT");

		break;
        
	case ST_OVERVIEW_FMT_CHECK:
		//look for xref: full so we don't have to load crossref & posting groups
		retcode = 0;
		sscanf (CommLineIn, "%u", &retcode);

		if (retcode/100 == 2)
		{
			CommState = ST_OVERVIEW_FMT;
			if (stristr(CommLineIn, "[COMPRESS=GZIP]"))
			{
				TRACE("COMPRESSED STREAM\r\n");
				CommState = ST_OVERVIEW_FMTCMP;
				MRRInitZStream(0);
			}
		}
		else
		{
			CommState = ST_NONE; //no more msgs
			*CommLineIn = 0;
			LoadGroup();	
		}
		break;
	case ST_OVERVIEW_FMT:
	case ST_OVERVIEW_FMTCMP:
		//server is telling us the order of the XOVER fields, we just look for the one that makes
		//crossreferences & ngroup filters easy
		if (strcmp (CommLineIn, ".") == 0) 
		{
			LoadGroup();

		}
		else
		{

			if (!_stricmp(CommLineIn, "Xref:full") )
			{
				TRACE("Hey Xref:full is supported\r\n");
				g_bHaveGroupsHeader = TRUE;
			}
		}

		break;

    case ST_LIST_RESP:
		retcode = 0;
		sscanf (CommLineIn, "%u", &retcode);
		if (retcode != 215) 
		{
			check_server_code (retcode);
			CommBusy = FALSE;
			CommState = ST_NONE;
			//MDS Reset back to initial state to kill hourglass
			Initializing = INIT_READY;
			SetStatbarText (CommDoc->hWndFrame, "", CommDoc, TRUE, TRUE);
			InvalidateRect (NetDoc.hDocWnd, NULL, FALSE);

			break;
		}
		if (stristr(CommLineIn, "[COMPRESS=GZIP]"))
		{
			TRACE("COMPRESSED STREAM\r\n");
			CommState = ST_LIST_GROUPLINECMP;
			MRRInitZStream(0);
		}
		else
		CommState = ST_LIST_GROUPLINE;
		RcvLineCount = 0;
      break;
	case ST_LIST_GROUPLINECMP:
    case ST_LIST_GROUPLINE:
	  if (strcmp (CommLineIn, ".") == 0) 
	  {
        CommState = ST_NONE;
        CommBusy = FALSE;
        Initializing = INIT_READY;
        SetStatbarText (CommDoc->hWndFrame, "", CommDoc, TRUE, TRUE);
        InvalidateRect (NetDoc.hDocWnd, NULL, FALSE);

        ProcEndList ();
      }
      else if (!EnableGroupFilter || MatchFilter (CommLineIn, GroupFilter)) 
	  {
        ProcListLine ((unsigned char *) CommLineIn);
      }
      break;

    case ST_GROUP_DESCRIPTION_RESP:
      retcode = 0;
      sscanf (CommLineIn, "%u", &retcode);
      if (retcode != 215) {
        CommState = ST_GROUP_RESP;
        mylen = _sprintf(mybuf,"GROUP %s",CurrentGroup);
        PutCommLine(mybuf);
        break;
      }
      CommState = ST_GROUP_DESCRIPTION_LINE;
      break;

    case ST_GROUP_DESCRIPTION_LINE:
		if (strcmp(CommLineIn,".") == 0) 
		{
			CommState = ST_GROUP_RESP;
			mylen = _sprintf(mybuf,"GROUP %s",CurrentGroup);
			PutCommLine(mybuf);
			break;
		}
		ptr = CommLineIn;
		while ((*ptr) && (*ptr != ' ') && (*ptr != '\t'))
			ptr++;
		//MDS make sure it is the group we want.
		if ( _strnicmp(CommLineIn, CurrentGroup, max((int)strlen(CurrentGroup), (int)(ptr-CommLineIn))) != 0) 
		{
			//maybe we should automatically turn off ShowDescription here? since it has caused an unexpected group list
			//ShowDescription = 0;
			break;
		}
		while ((*ptr == ' ') || (*ptr == '\t'))
			ptr++;
		strntcpy(CurrentDescription, ptr, MAXDESCRIPTION);
      break;

    case ST_INLINEGROUP_RESP:
      retcode = 0;
      sscanf (CommLineIn, "%u", &retcode);
	  //one way or another, right now we are done receiving messages so set CommState to NONE
	  //so we don't keep popping up threshold dialogs
	  CommState = ST_NONE;
      switch (retcode) 
	  {
		  case 411:	//no group
		  case 502: //restricted
			break;

		  default:
			if (check_server_code (retcode)) 
			{
			  CommBusy = FALSE;
			  FlagJumpGroup = 0;
			  return;
			}
			break;
      }
	  {

			TypGroup far *TempGroupDoc = InlineGroupPtr;
			if (!InlineGroupPtr)
				break;

			sscanf (CommLineIn, "%u %I64u %I64u %I64u %s", &retcode, &TempGroupDoc->ServerEstNum,
				&TempGroupDoc->ServerFirst, &TempGroupDoc->ServerLast, mybuf2);
			TempGroupDoc->Determined = TRUE;
			TempGroupDoc->NumUnread = CalcNumUnread(TempGroupDoc);

			

			if (TempGroupDoc->HighestPrevSeen > TempGroupDoc->ServerLast)
			{

				// best we can do is mark all as unseen and to reset HighestPrevSeen
				if (TempGroupDoc->pSeendb)
				{ 
					
					if (TempGroupDoc->ServerLast < TempGroupDoc->pSeendb->GetLowRange() &&
					TempGroupDoc->HighestPrevSeen - TempGroupDoc->ServerLast > MaxDBSize)
					{
						//something is seriously wrong, we could end up 
						//reallocating billions of art entries.
						//So start over

						pSDB = TempGroupDoc->pSeendb;
						TempGroupDoc->pSeendb = new WVSeenDB(TempGroupDoc->ServerFirst, max(TempGroupDoc->ServerFirst, TempGroupDoc->ServerLast) );
						if(TempGroupDoc->pSeendb)
						{
							TempGroupDoc->HighestPrevSeen = TempGroupDoc->ServerFirst-1;
							delete pSDB;
						}
						else
							TempGroupDoc->pSeendb = pSDB; //failed? what now?


					}
					else
					{	 //art ranges incorrect, toss old db

						pSDB = TempGroupDoc->pSeendb;
						TempGroupDoc->pSeendb = new WVSeenDB(TempGroupDoc->ServerFirst, max(TempGroupDoc->ServerFirst, TempGroupDoc->ServerLast) );
						if(TempGroupDoc->pSeendb)
						{
							TempGroupDoc->HighestPrevSeen = TempGroupDoc->ServerFirst-1;
							delete pSDB;
						}
						else
							TempGroupDoc->pSeendb = pSDB; //failed? what now?


						TempGroupDoc->pSeendb->MarkUnseen(1, TempGroupDoc->HighestPrevSeen);

						
					}
				}

				TempGroupDoc->HighestPrevSeen = TempGroupDoc->ServerFirst-1;
				NewsrcDirty = TRUE;
			}

			TempGroupDoc->uLoadFirst = TempGroupDoc->ServerFirst;
			TempGroupDoc->uLoadLast = max(TempGroupDoc->ServerFirst, TempGroupDoc->ServerLast); // ServerLast can be zero
			TempGroupDoc->Determined = TRUE;

			if ( TempGroupDoc->ServerEstNum && (TempGroupDoc->uLoadLast - TempGroupDoc->uLoadFirst) > (TempGroupDoc->ServerEstNum*2) && 
				(TempGroupDoc->uLoadLast - TempGroupDoc->uLoadFirst) > MaxDBSize)
			{
				//this is an extremely strange range, and it's probably wrong, and we'll use HUGE amounts of memory creating
				//a DB to hold the seen list of say 8,million arts.
				//So punt.

				TempGroupDoc->uLoadFirst = TempGroupDoc->uLoadLast  - min(MaxDBSize, (TempGroupDoc->ServerEstNum*2)); 
			}
			

	  }
	  break;
	case ST_GROUP_RESP:
      retcode = 0;
      sscanf (CommLineIn, "%u", &retcode);
	  //one way or another, right now we are done receiving messages so set CommState to NONE
	  //so we don't keep popping up threshold dialogs
	  CommState = ST_NONE;
      switch (retcode) 
	  {
      case 411:
        /* abort the fledgling group window */
        DestroyWindow (CommDoc->hWndFrame);
        CommBusy = FALSE;
        FlagJumpGroup = 0;
        WVMessageBox (NetDoc.hDocWnd, IDS_WVUTIL_NO_SUCH_GROUP, IDS_WVUTIL_ERROR1, MB_OK | MB_ICONHAND);
        return;
        break;
      case 502:
        /* abort the fledgling group window */
        DestroyWindow (CommDoc->hWndFrame);
        CommBusy = FALSE;
        FlagJumpGroup = 0;
        WVMessageBox (NetDoc.hDocWnd, IDS_WVUTIL_RESTRICTED, IDS_WVUTIL_WINVN4, MB_OK | MB_ICONHAND);
        return;
        break;

      default:
        if (check_server_code (retcode)) 
        {
          CommBusy = FALSE;
          FlagJumpGroup = 0;
          return;
        }
        break;
      }

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

      sscanf (CommLineIn, "%u %I64u %I64u %I64u %s", &retcode, &GroupDoc->ServerEstNum,
        &GroupDoc->ServerFirst, &GroupDoc->ServerLast, group);

/* Ray Dunn:99/12: Lets do some consistency checking of newsrc. */
      if (GroupDoc->HighestPrevSeen > GroupDoc->ServerLast + 100/*allow for fudge*/)
      {
        
        // best we can do is mark all as unseen and to reset HighestPrevSeen
        if (GroupDoc->pSeendb)
        { 
			 //art ranges incorrect, toss old db
			MessageBeep((UINT) -1);
            pSDB = GroupDoc->pSeendb;
            UBIGINT LowVal = min(GroupDoc->ServerFirst, GroupDoc->ServerLast);
            UBIGINT HiVal = max(GroupDoc->ServerFirst, GroupDoc->ServerLast);
            GroupDoc->ServerFirst = LowVal;
            GroupDoc->ServerLast = HiVal;
            //retain low val if its legal
            if (pSDB->NextUnseen() >= LowVal && pSDB->NextUnseen()-1 <= HiVal)
				LowVal = pSDB->NextUnseen()-1;
			else
				//you know what?  just mark 'em all as seen in this group, why get ALL old arts?
				LowVal = HiVal;
            GroupDoc->pSeendb = new WVSeenDB(LowVal, HiVal );
            if(GroupDoc->pSeendb)
            {
                if (LowVal > 0)
					GroupDoc->HighestPrevSeen = LowVal-1;
				else
					GroupDoc->HighestPrevSeen = 0;
                delete pSDB;
            }
            else
                GroupDoc->pSeendb = pSDB; //failed? what now?
            
			GroupDoc->pSeendb->MarkUnseen(1,GroupDoc->HighestPrevSeen);

            
        }

        NewsrcDirty = TRUE;
      }
/* Ray Dunn:99/12:End */

      GroupDoc->uLoadFirst = GroupDoc->ServerFirst;
      GroupDoc->uLoadLast = max(GroupDoc->ServerFirst, GroupDoc->ServerLast); // ServerLast can be zero
      GroupDoc->Determined = TRUE;

      if ( GroupDoc->ServerEstNum && (GroupDoc->uLoadLast - GroupDoc->uLoadFirst) > (GroupDoc->ServerEstNum*2) && 
         (GroupDoc->uLoadLast - GroupDoc->uLoadFirst) > MaxDBSize)
      {
        //this is an extremely strange range, and it's probably wrong, and we'll use HUGE amounts of memory creating
        //a DB to hold the seen list of say 8,billion arts.
        //So punt.
        
         GroupDoc->uLoadFirst = GroupDoc->uLoadLast  - min(MaxDBSize, (GroupDoc->ServerEstNum*2)); 
      }
      
      if(!(GroupDoc->pAA))
      {
        GroupDoc->pAA = new WVArticleAction(CurrentGroup);
        GroupDoc->pAA->ReadActions();
      }
#if 0 //MDS 2010-03-20
      pSDB = GroupDoc->pSeendb;
      //why are we making a seenDB as large as what the server has online?
      //perhaps to have a place to hold any server ranges... boy are they getting big!
      //why not wait until we know what the user wants to retrieve>
      GroupDoc->pSeendb = new WVSeenDB(GroupDoc->uLoadFirst, GroupDoc->uLoadLast, pSDB);
      if(GroupDoc->pSeendb)
        delete pSDB;
      else
        GroupDoc->pSeendb = pSDB;
#endif

      if(!(GroupDoc->pAddHdr))
      {
          GroupDoc->pAddHdr = new WVAddHdr(CurrentGroup);
          GroupDoc->pAddHdr->ReadAddHdr();
      }

      if(!(GroupDoc->Description))
      {
          GroupDoc->Description = (char *) GlobalAllocPtr (GMEM_MOVEABLE, MAXDESCRIPTION * sizeof (char));
          strntcpy(GroupDoc->Description, CurrentDescription, MAXDESCRIPTION);
      }


      //arts_to_retrieve = GroupDoc->ServerEstNum;
      server_first = GroupDoc->ServerFirst;
      server_last = GroupDoc->ServerLast;
	  arts_to_retrieve = GroupDoc->ServerEstNum;
     
      est_num_unread = min(CalcNumUnread(GroupDoc), arts_to_retrieve);

/* Ray Dunn:99/10: calculate the number of articles in the list from the first unseen */
/*                 Note: cancelled articles screws this up somewhat! */

      art_num = GroupDoc->pSeendb->NextUnseen();
      arts_reqd_for_first_unseen = 1 + GroupDoc->uLoadLast - art_num;


      if (arts_reqd_for_first_unseen > arts_to_retrieve)
        arts_reqd_for_first_unseen = arts_to_retrieve;
      
	  if (arts_reqd_for_first_unseen < est_num_unread)
        arts_reqd_for_first_unseen = est_num_unread;

   	  
      /* we don't want to grab *that* many! */ //shouldn't this be est_num_uread? MDS 20100326
      if( (arts_reqd_for_first_unseen == 0 && min_to_retrieve == 0) || (arts_reqd_for_first_unseen >= article_threshold) || (IsFlagSet(CommDoc, InstantUpdate)) )
      {

		TypDoc * SaveDoc = CommDoc;
        if (!IsFlagSet(CommDoc, InstantUpdate) && ShowUnreadOnly) 
        {
          DlgStatus = ID_THRESHOLD_UNREAD; /* don't ask set it for the user */
        } 
        else 
        {
          DlgStatus = DialogBox (hInst, MAKEINTRESOURCE(IDD_THRESHOLD), CommDoc->hDocWnd, (DLGPROC) lpfnWinVnThresholdDlg);
          // (arts_to_retrieve and DlgStatus set)
        }
		

		if ( Initializing == INIT_NOT_CONNECTED && Autoconnect && Reconnecting && DlgStatus != FALSE)
		{
			
			if (!InlineReconnect(NetDoc.hDocWnd))
			{
				ClearFlags(CommDoc, InstantUpdate);

				return;
			}

			CommDoc = SaveDoc;
		}

        if (!CommDoc) //server disconnected while dlg was up?
		{
            DestroyWindow (SaveDoc->hWndFrame);
			return;
		}

          
        if (CommDoc && (DlgStatus == FALSE)) 
        { /* user has aborted, return to main-window */


          CommBusy = FALSE;
          CommState = ST_NONE;
		  ClearFlags(CommDoc, InstantUpdate);

		  SendMessage(CommDoc->hWndFrame, WM_CLOSE, 12345L,0);

          //MDS 2006-06-16 WHY?
		  //GroupDoc->ServerFirst = GroupDoc->uLoadLast;
          
		  //GroupDoc->ServerEstNum = estnum;
          FlagJumpGroup = 0;
          return;
        }
      } 
      else 
      {
        DlgStatus = ID_THRESHOLD_UNREAD;
		/*
		if (ShowUnreadOnly) 
        {
          DlgStatus = ID_THRESHOLD_UNREAD;
        } 
        else 
        {
          DlgStatus = ID_THRESHOLD_ALL;
        }*/  
      }
      switch (DlgStatus) 
      {
        case IDC_RB_EARLIEST:
        case IDC_RB_RANGE:
            GroupDoc->uLoadFirst = server_first;
            GroupDoc->uLoadLast = server_first + arts_to_retrieve;
            if (GroupDoc->uLoadLast > GroupDoc->ServerLast)
                GroupDoc->uLoadLast = GroupDoc->ServerLast;
            break;
		case IDC_RB_NEXTUNSEEN:
            GroupDoc->uLoadFirst = GroupDoc->pSeendb->NextUnseen();
            GroupDoc->uLoadLast = GroupDoc->uLoadFirst + arts_to_retrieve;
			if (GroupDoc->uLoadLast > GroupDoc->ServerLast)
				GroupDoc->uLoadLast = GroupDoc->ServerLast;
			break;

        case ID_THRESHOLD_ALL:
          /* all of them! note this is different from retrieving GroupDoc->ServerEstNum
             headers because sometimes canceled article are NOT counted. Example
             ServerEstNum = 592 ServerFirst = 1 ServerLast = 595 because 3 articles
             are canceled on the server */
        
          GroupDoc->uLoadFirst = GroupDoc->ServerFirst;
          arts_to_retrieve = GroupDoc->ServerEstNum;
          break;
        case ID_THRESHOLD_UNREAD:
          /* retrieve from the first unread article to the end of the server */
                 
          arts_to_retrieve = 1 + GroupDoc->uLoadLast - GroupDoc->pSeendb->NextUnseen();

                //*********** to do **********
          // make min_to_retrieve depending host and newsgroup like sortoption
          // because min_to_retrieve is depending on the traffic in the group.
          // a good value should be to go back about 3 days for every group
#ifndef FIX_df__20000427
          if ((arts_to_retrieve < min_to_retrieve) && (arts_to_retrieve > 0))
#else
         /*df 2000-04-27 - fix logic to ensure uLoadFirst <= uLoadlast */
         if (arts_to_retrieve < min_to_retrieve && !IsFlagSet(CommDoc, InstantUpdate))
#endif
                 
          {
             arts_to_retrieve = min_to_retrieve;
          }
          /* calculate which article we should load first */
          GroupDoc->uLoadFirst = 1 + GroupDoc->uLoadLast - arts_to_retrieve;

          
          //MDS no no, Server Est number can exclude cancelled arts
          //so a ServerEstNum < art_to_retrieve is OK, but we dont want to get
          //previously seen arts so only set arts_to_retrieve, don't set
          //uLoadFirst.
          if (GroupDoc->ServerEstNum < arts_to_retrieve) 
          {
            arts_to_retrieve = GroupDoc->ServerEstNum;
            if (    GroupDoc->uLoadFirst < GroupDoc->ServerFirst)   //MDS added this. Shouldn't get called
              GroupDoc->uLoadFirst = GroupDoc->ServerFirst;
          }
                    

            break;
        default:
          /* if user response is a number of articles ats_to retrive is already set */
                    /* but could also be 0 which is simply pressing the OK button without entering a value */
			if (arts_to_retrieve == 0) 
			{
				arts_to_retrieve = 1 + GroupDoc->uLoadLast - GroupDoc->pSeendb->NextUnseen();
				if ((arts_to_retrieve < min_to_retrieve)) 
				{
					arts_to_retrieve = min_to_retrieve;
				}
			}
			/* calculate which article we should load first */
			GroupDoc->uLoadFirst = 1 + GroupDoc->uLoadLast - arts_to_retrieve;

			//MDS no no, Server Est number can exclude cancelled arts
			//so a ServerEstNum < art_to_retrieve is OK, but we dont want to get
			//previously seen arts so only set arts_to_retrieve, don't change
			//uLoadFirst.
			if (GroupDoc->ServerEstNum < arts_to_retrieve) 
			{
				arts_to_retrieve = GroupDoc->ServerEstNum;
				if (    GroupDoc->uLoadFirst < GroupDoc->ServerFirst)   //MDS added this. Shouldn't get called
				  GroupDoc->uLoadFirst = GroupDoc->ServerFirst;
			}

      } /* switch */
#if 1 //MDS 2010-03-20
      pSDB = GroupDoc->pSeendb;
      //why are we making a seenDB as large as what the server has online?
      //perhaps to have a place to hold any server ranges... boy are they getting big!
      //why not wait until we know what the user wants to retrieve>
	 {
		UBIGINT uLo = GroupDoc->uLoadFirst;
		  UBIGINT uHi = GroupDoc->uLoadLast;
		  if (GroupDoc->pSeendb)
		  {
			  //don't make DB smaller than what we started with
			  uLo = min(GroupDoc->uLoadFirst, GroupDoc->pSeendb->GetLowRange());
			  uHi = max(GroupDoc->uLoadLast, GroupDoc->pSeendb->GetHighRange());
			  if (uHi > GroupDoc->ServerLast)
				uHi = GroupDoc->ServerLast;
			  if (uLo < GroupDoc->ServerFirst)
				uLo = GroupDoc->ServerFirst;
		  }
		  
		  GroupDoc->pSeendb = new WVSeenDB(uLo , uHi, pSDB);
		  if(GroupDoc->pSeendb)
			delete pSDB;
		  else
			GroupDoc->pSeendb = pSDB;
	  }
#endif

	  ClearFlags(CommDoc, InstantUpdate);
#if 0
    if (arts_to_retrieve == 0) 
    {
       /* abort the fledgling group window */
       DestroyWindow (CommDoc->hWndFrame);
       CommBusy = FALSE;
       CommState = ST_NONE;
       MessageBox (NetDoc.hDocWnd, "No Articles to Retrieve", "WinVN", MB_OK | MB_ICONHAND);
       return;
    }
     if (GroupDoc->ServerEstNum <= 0) 
     {
       /* abort the fledgling group window */
       DestroyWindow (CommDoc->hWndFrame);
       CommBusy = FALSE;
       CommState = ST_NONE;
       GroupDoc->ServerFirst = GroupDoc->uLoadLast;
       //if (GroupDoc->ServerLast) {
       //  GroupDoc->ServerFirst = GroupDoc->ServerLast;
       //}
       GroupDoc->ServerEstNum = 0;
       MessageBox (NetDoc.hDocWnd, "Empty Newsgroup", "WinVN", MB_OK | MB_ICONHAND);
       return;
    }
#endif

      
      CommDoc->TotalLines = (unsigned long) arts_to_retrieve; //MDS may be > 64K! but not 4GB

      if (arts_to_retrieve > 0) 
      {
   
        /*df 2000-04-26 - Handle server that lies or has received new articles,
         *                by allocating (5) extra article slots.
         *                5 may be changed to some larger value if desired.
         *                Additionally, it would be good to count the _actual_
         *                number of articles retrieved and use that in place
         *                of the server estimate for a subsequent article
         *                retrieval in the same group - to do.
         */
         unsigned long my_arts_to_retrieve = (unsigned long) arts_to_retrieve + 5;

        /*df - these GlobalAllocs ought to be encapsulated in headarry.cpp -
         * or is it just me?
         */
		DWORD dwSize = ((sizeof (TypHeader)) *
                          my_arts_to_retrieve) + sizeof (thread_array *);
        header_handle =
            GlobalAlloc (GMEM_MOVEABLE | GMEM_ZEROINIT, (unsigned long)
                         ((sizeof (TypHeader)) *
                          my_arts_to_retrieve) + sizeof (thread_array *)); 

		dwSize = ((sizeof (long)) * my_arts_to_retrieve);
        
		/* allocate space for the header_array index table */
        thread_handle =
          GlobalAlloc (GMEM_MOVEABLE,
                       (unsigned long) ((sizeof (long)) * my_arts_to_retrieve));


        
		if (!header_handle || !thread_handle)
		{
			if (header_handle)
				GlobalFree(header_handle);
			if (thread_handle)
				GlobalFree(thread_handle);

			/* abort the fledgling group window */
			DestroyWindow (CommDoc->hWndFrame);
			CommBusy = FALSE;
			CommState = ST_NONE;
			MessageBox (NetDoc.hDocWnd, "Unable to allocate memory for that many articles", "WinVN", MB_OK | MB_ICONHAND);
			return;
		}
		
		GroupDoc->header_handle = header_handle;
        GroupDoc->thread_handle = thread_handle;
        GroupDoc->alloc_headers = (unsigned long) arts_to_retrieve;
        /* stick nulls and 0's, etc.. in case display code get mis-threaded */

#ifdef FIX_df__20000427
        /*df 2000-04-26 - Avoid losing the last 5 articles */
        /*df - Having allocated extra headers, ensure they are usable! */
        initialize_header_array (header_handle, thread_handle, my_arts_to_retrieve);
        CommDoc->TotalLines = my_arts_to_retrieve;
#else
        initialize_header_array (header_handle, thread_handle, arts_to_retrieve);
#endif

      }
      
      //GroupDoc = GetGroup(LinePtr);     // (didn't we already do this?)
      //GroupDoc->ServerEstNum = estnum;
      //GroupDoc->ServerFirst = GroupDoc->uLoadFirst;
          
      GroupDoc->Threaded = ((xoverp || threadp) && (GroupDoc->thread_handle));
      ClearLatestInfo();
#ifdef FIX_df__20000427
     /*df 2000-04-26 - Initialise to _not saving_ state.
      *                This makes return codes 4xx (except 400) recoverable
      *                in response to XOVER, by fall-back to XHDR. Arguably
      *                412 and 420 should not be recoverable, but they "can't
      *                happen" by design with WinVN (oh yeah?)
      */
     GroupDoc->SubjDoc->savingArtIndex = -1;
#endif


      if (xoverp) 
      {
        if (use_zhdr < 2)
			mylen = sprintf (mybuf, "XOVER %I64d-%I64d", GroupDoc->uLoadFirst, GroupDoc->uLoadLast);
		else
			mylen = sprintf (mybuf, "XZVER %I64d-%I64d", GroupDoc->uLoadFirst, GroupDoc->uLoadLast);
        CommState = ST_XOVER_START;
      }
      else 
      {
        if (use_zhdr == 2)
			mylen = sprintf (mybuf, "XZHDR from %I64d-%I64d", GroupDoc->uLoadFirst, GroupDoc->uLoadLast);
		else
			mylen = sprintf (mybuf, "XHDR from %I64d-%I64d", GroupDoc->uLoadFirst, GroupDoc->uLoadLast);
        CommState = ST_XHDR_FROM_START;
      }
      PutCommLine (mybuf);
      GlobalUnlock (BlockPtr->hCurBlock);

      break;

      /* use XOVER if its available */
    case ST_XOVER_START:
      retcode = 0;
      *LastCommLine = '\0';
	  
      sscanf (CommLineIn, "%d", &retcode);
      if (retcode == 224) 
      {
			CommState = ST_XOVER_DATA;

			if (stristr(CommLineIn, "[COMPRESS=GZIP]"))
			{
				TRACE("COMPRESSED STREAM\r\n");
				MRRInitZStream(0);
				CommState = ST_XOVER_DATACMP;
			}
			else if (use_zhdr == 2 || stristr(CommLineIn, "compressed"))
			{
				//yEnc of zlib data
				TRACE("YENC COMPRESSED STREAM\r\n");
				MRRInitZStream(1);
				CommState = ST_XOVER_DATACMP;
			}
			
            CommDoc->ActiveLines = 0;
            total_xhdrs = 1; //MDS set here in case killfile not enabled
            if (bEnableArticleAction) 
            {
              if(LockLine(CommDoc->hParentBlock, CommDoc->ParentOffset,
					CommDoc->ParentLineID, &BlockPtr, &LinePtr))
              {
                GroupDoc = GetGroup(LinePtr);
                //aa.ReadActions(CurrentGroup);      /* Get article action list */
                uLastArtNum = GroupDoc->uLoadFirst;
                total_xhdrs = 1;
                if ((g_pAction->NeedNgroupHeader() || GroupDoc->pAA->NeedNgroupHeader()) && !g_bHaveGroupsHeader)
                {       /* Find out if we need newsgroups */
                    total_xhdrs++;
                }
                if (g_pAction->NeedNNTPHost() || GroupDoc->pAA->NeedNNTPHost() )
                {
                    total_xhdrs++; //we need NNTP-Posting-Host too
                }
                if (!g_bUseXHDR)
                    total_xhdrs = 1;

                GlobalUnlock (BlockPtr->hCurBlock);
              }
            }
      }
      else 
      {
        if (check_server_code (retcode)) 
        {
            //abort group window
            if (CommDoc)
				SendMessage (CommDoc->hWndFrame,  WM_CLOSE, 0, 0L);

          break;
        }

        LockLine(CommDoc->hParentBlock, CommDoc->ParentOffset,
          CommDoc->ParentLineID, &BlockPtr, &LinePtr);
        GroupDoc = GetGroup(LinePtr);
		if (use_zhdr == 2)
        mylen = _sprintf (mybuf, "XZHDR from %I64d-%I64d", GroupDoc->uLoadFirst, GroupDoc->uLoadLast);
		else
        mylen = _sprintf (mybuf, "XHDR from %I64d-%I64d", GroupDoc->uLoadFirst, GroupDoc->uLoadLast);
        CommState = ST_XHDR_FROM_START;
        PutCommLine (mybuf);
        GlobalUnlock (BlockPtr->hCurBlock);
      }
      break;

    case ST_XOVER_DATACMP:
	case ST_XOVER_DATA:
      char * refer;

    if(strcmp(CommLineIn, ".") == 0)
    {
      if(LockLine(CommDoc->hParentBlock, CommDoc->ParentOffset,
                 CommDoc->ParentLineID, &BlockPtr, &LinePtr))
      {
        GroupDoc = GetGroup(LinePtr);
        GroupDoc->total_headers = CommDoc->ActiveLines;
                
        //first = GroupDoc->ServerFirst;      
        //last = GroupDoc->ServerLast;
      
        if(uLastArtNum < GroupDoc->uLoadLast)
        {
          GroupDoc->pSeendb->MarkSeen(uLastArtNum + (GroupDoc->total_headers ? 1 : 0), GroupDoc->uLoadLast);
        }
        if(bEnableArticleAction && CommDoc->ActiveLines)/* Find out if we need newsgroups */
        { //was anything there to load?
            if ((g_pAction->NeedNgroupHeader() || GroupDoc->pAA->NeedNgroupHeader()) && !g_bHaveGroupsHeader)
            {
              /* Now ask for the newsgroups */
              if (  g_bUseXHDR)
              {
                  if (use_zhdr == 2)
				  mylen = _sprintf (mybuf, "XZHDR newsgroups %I64d-%I64d", GroupDoc->uLoadFirst, GroupDoc->uLoadLast);
				  else
				  mylen = _sprintf (mybuf, "XHDR newsgroups %I64d-%I64d", GroupDoc->uLoadFirst, GroupDoc->uLoadLast);
                  CommState = ST_XHDR_NEWSGROUPS_START;
              }
              else
              {
                 art_num = GroupDoc->uLoadFirst;
                 mylen = _sprintf (mybuf, "HEAD %I64d", art_num);
                 CommState = ST_HEAD_NEWSGROUPS_START;
                 CommDoc->ActiveLines = 0;
              }
              PutCommLine (mybuf);
            }
            else if (g_pAction->NeedNNTPHost() || GroupDoc->pAA->NeedNNTPHost())
            {
              if (  g_bUseXHDR)
              {

                if (use_zhdr == 2)
				mylen = _sprintf (mybuf, "XZHDR NNTP-Posting-Host %I64d-%I64d", GroupDoc->uLoadFirst, GroupDoc->uLoadLast);
				else                
				mylen = _sprintf (mybuf, "XHDR NNTP-Posting-Host %I64d-%I64d", GroupDoc->uLoadFirst, GroupDoc->uLoadLast);
                CommState = ST_XHDR_NNTP_HOST_START;
              }
              else
              {
                 art_num = GroupDoc->uLoadFirst;
                 mylen = _sprintf (mybuf, "HEAD %I64d", art_num);
                 CommState = ST_HEAD_NEWSGROUPS_START;
                 CommDoc->ActiveLines = 0;

              }
              PutCommLine (mybuf);

            }
        }


        if (CommState == ST_XOVER_DATA || CommState == ST_XOVER_DATACMP)
        {
          CommState = ST_IN_GROUP;
          CommBusy = FALSE;
          finish_header_retrieval();
          SetStatbarText (CommDoc->hWndFrame,  "", CommDoc, TRUE, TRUE);
        }
        GlobalUnlock (BlockPtr->hCurBlock);
      }
    }
    else 
    {
      char *this_hop, *next_hop;
      int   tabCount;
      int   nXoverType;

      //MDS 19991211
      //added support for bizzare multiline XOVER data from server at clio.lyris.net
      //group lyris-discuss which splits long lines, and adds extra tabs between fields.
      //we accumulate partial lines until we get a complete valid line or a part which
      //completes this XOVER line.
      //According to IETF nntp docs, XOVER doesn't preclude embedded newlines.
      //However changing field order is not considered normal. Since this doesn't break
      //old servers, it shouldn't hurt to have this support in here.
      if ( *LastCommLine != '\0')
      {
        //acummulating responses
        if (IsValidXOVER(CommLineIn, MAXCOMMLINE)) 
		{
          //skip old data
          TRACE1("XOVER Dropping %.200s\r\n", LastCommLine);
          *LastCommLine = '\0';
        }
        else {
          //XOVER data is probably wrapped
          strncat(LastCommLine, CommLineIn, MAXCOMMLINE - strlen( LastCommLine) - 1);
          strntcpy(CommLineIn, LastCommLine, MAXCOMMLINE -1);
          StripNewline(CommLineIn, MAXCOMMLINE-1);
          *LastCommLine = '\0';

        }
      }

      /* the XOVER-line should contain the following fields: */
      /* articlenumber, subject, author, date, message-id, references, bytes, lines */
      /* this means there must be at least 7 tabs */
      /* this also avoids empty XOVER-lines */
      /* formerly code from (peterk@borland.com) only checks for real empty lines */
      
      tabCount = CountTabs(CommLineIn, MAXCOMMLINE);
      nXoverType = IsValidXOVER(CommLineIn, MAXCOMMLINE);
      if (nXoverType) 
	  {
        if(LockLine (CommDoc->hParentBlock, CommDoc->ParentOffset,
          CommDoc->ParentLineID, &BlockPtr, &LinePtr))
		{
          GroupDoc = GetGroup(LinePtr);

          if(CommDoc->ActiveLines >= CommDoc->TotalLines)
          {
            GroupDoc->uLoadLast = min(GroupDoc->uLoadLast, uLastArtNum - 1);
          }
          else
          {
          header_handle = GroupDoc->header_handle;
          thread_handle = GroupDoc->thread_handle;
          //header->sNewsgroups = "";

          /* Lock the header data */
          headers = lock_headers (header_handle, thread_handle);
          header = header_elt (headers, CommDoc->ActiveLines);
        
          this_hop = CommLineIn;

          /* article number */
          next_hop = strchr (this_hop, '\t');
          *(next_hop++) = (char) NULL;
          header->number = atoBIGINT (this_hop);
		  //mark gaps of articles as seen
          if(header->number && uLastArtNum < header->number - 1)
            GroupDoc->pSeendb->MarkSeen(uLastArtNum + 1, header->number - 1);
          uLastArtNum = header->number;

          /* subject */
          this_hop = next_hop;
          next_hop = strchr (this_hop, '\t');
          *(next_hop++) = (char) NULL;

          header->pHD->sSubject = this_hop;
#ifdef RFC1522
          decodeRFC1522(header->pHD->sSubject);
#endif         

          CommDoc->LongestLine = max (CommDoc->LongestLine,
                                      ArtListSeparator3 / CharWidth +
                                      (unsigned) lstrlen (header->pHD->sSubject));

          /* author */
          this_hop = next_hop;
          next_hop = strchr (this_hop, '\t');
          *(next_hop++) = (char) NULL;

          header->pHD->sFrom = this_hop;
#ifdef RFC1522
            decodeRFC1522(header->pHD->sFrom);
#endif

		  ParseAddress (header->pHD->sFrom,
                        AddressString, MAXDIALOGSTRING,
                        NameString, MAXDIALOGSTRING);

          if (FullNameFrom) 
		  {
            header->pHD->sFrom = NameString;

          }

          header->pHD->sEmail = AddressString;

          /* date */
          this_hop = next_hop;
          next_hop = strchr (this_hop, '\t');
          *(next_hop++) = (char) NULL;

          /* Ray Dunn:99/10: added datetime arg for date only (day/month) sorting */
          header->pHD->date = parse_usenet_date (&header->pHD->datetime, this_hop);

          /* message-id */
          this_hop = next_hop;
          next_hop = strchr (this_hop, '\t');
          *(next_hop++) = (char) NULL;

          if(*this_hop)
            header->pHD->sMessage_id = message_id_with_frob(this_hop);
          else
            header->pHD->sMessage_id = "";

          /* references */
          this_hop = next_hop;
          if (tabCount > 7 && nXoverType != 1)
          {
            if (nXoverType == 3)
			{	//last field is xref, previous 2 are bytes & line (extra refs fields)
			    //for some reason some posts have extra tabs embedded in references field
				char *skipto;
				next_hop = strchr (this_hop, '\t');
				TRACE("I would have tossed this art due to weird xover %I64d\r\n", header->number);
				
				skipto =  GetField(this_hop, (CommLineIn +MAXCOMMLINE) - this_hop, tabCount-7 ); //five fields already read
				if (skipto)
				{
				  *(skipto -1) = '\0';
				  next_hop = skipto;
				  //remove embedded '\t' in references
				  for (skipto = this_hop; skipto < next_hop; skipto++)
				  {
				     if (*skipto == '\t')
						*skipto = ' ';
				  }
				}

			}
			else
			{
				//more fields than expected, expected last two fields are bytes & lines
				next_hop =  GetField(this_hop, (CommLineIn +MAXCOMMLINE) - this_hop, tabCount-6 ); //five fields already read
				if (next_hop)
				  *(next_hop -1) = '\0';
				else
				  next_hop = strchr (this_hop, '\t'); //should never happen
			} 
          }
          else
          {
            next_hop = strchr (this_hop, '\t');
            *(next_hop++) = (char) NULL;
          }
          // stop GPF's when reference line is messed up  (JD 2/17/97) 
          // header->pHD->sBest_ref = message_id_with_frob(get_best_reference(this_hop));
          refer = get_best_reference(this_hop,header->pHD->sMessage_id);
          if (refer) 
		  {
            header->pHD->sBest_ref = message_id_with_frob(refer);
            build_ref_list(header->pHD->sFrob_ref_list, this_hop,header->pHD->sMessage_id);
          }

          /* bytes (ignored mostly) */
          this_hop = next_hop;
          next_hop = strchr (this_hop, '\t');
          *(next_hop++) = (char) NULL;
		  header->bytes	= (int) atol(this_hop);

          /* lines (last one doesn't have to have the tab) */
          this_hop = next_hop;
		  next_hop = strchr (this_hop, '\t');
		  if (next_hop)
			*(next_hop++) = (char) NULL;
          
          //MDS some microsoft posts now set total number of lines for a multipart
          //message in each sub-part, thus overflowing the #lines conversion.
          //instead of changing the TypDoc struct, we'll trim the linecount, since 
          //the line count is wrong anyway.   20000401
#ifdef _WIN32
          header->lines =   (int) atol(this_hop);
#else
          long nLines = atol(this_hop);
          if (nLines > 0xFFFF)    
            nLines = 0xFFFF;
          header->lines = (unsigned short) nLines;
#endif
          /* optional Xref: line*/
		  //Xref: server-name group-name:<number> group-name:<number> (repeats)
		  this_hop = next_hop;
		  if (this_hop && _strnicmp("xref: ", this_hop, 6) == 0 )
		  {

				char *mybuf = new char[strlen(this_hop)];
				if (!mybuf)
				{
abortgroup:
					/* abort the group window */
					unlock_headers (header_handle, thread_handle);
					GlobalUnlock(BlockPtr->hCurBlock);
					DestroyWindow (CommDoc->hWndFrame);
					CommBusy = FALSE;
					CommState = ST_NONE;
					MessageBox (NetDoc.hDocWnd, "Out of memory! You need to exit WinVN now to save your newsrc.", "WinVN", MB_OK | MB_ICONHAND);
					Initializing = INIT_DONE;
					MRRCloseComm();
					break;

				}
				char *p = mybuf, *ptr, *ptr1;
				*p = '\0';

				ptr = this_hop+6;
				while((*ptr != 0) && (*ptr != ' ')) (ptr)++;	 //skip server name
				if (*ptr)
					ptr++;
                while (*ptr != 0) 
                {
                    ptr1 = ptr;
                    while((*ptr1 != 0) && (*ptr1 != ':')) (ptr1)++;  /* HB 1996/03/23 */
                    if (*mybuf)
						strcat(mybuf,",");
					strncat(mybuf,ptr,ptr1-ptr);                     /* save groupname */
                    
                    NextToken(&ptr);                                  /* skip over artindex */
                    ptr1++;
                    //strntcpy(mybuf2,ptr1,ptr-ptr1);                   /* save artindex */
                    //if (_stricmp(mybuf,CurrentGroup) != 0)         /* mark if not our group */ 
                    //    mark_article_seen(&NetDoc, mybuf,atol(mybuf2));
                }
				
			  if (*mybuf)
			  {
				  
				  g_bHaveGroupsHeader = TRUE;
				  header->pHD->sNewsgroups = mybuf;
				  header->pHD->iGroups = 1;
				  while(p = strchr(p, ','))
				  {
					header->pHD->iGroups++;
					p++;
				  }
			  }
			  delete [] mybuf;					
				
			/*	
				
				syncnum = sync_artnum(atol(CommLineIn),
									  (header_elt(headers, CommDoc->ActiveLines))->number,
									  headers, GroupDoc);
				if(syncnum >= 0)
				{
				  const char* sTmp;
				  header = header_elt (headers, syncnum);
				  sTmp = get_xhdr_line(CommLineIn);

				  if (strcmp(sTmp, "(none)") )
				  {
					  header->pHD->sNewsgroups = sTmp;
					  header->pHD->iGroups = 1;
					  while(sTmp = strchr(sTmp, ','))
					  {
						header->pHD->iGroups++;
						sTmp++;
					  }

					  CommDoc->ActiveLines++;

				  }



				}
				unlock_headers(header_handle, thread_handle);

				update_window_title(CommDoc->hWndFrame, group,
									RcvLineCount++, CommDoc->TotalLines * total_xhdrs);
			  */
		}
			  
		  /* set other header fields */
          header->ArtDoc = (TypDoc *) NULL;
          header->SeenSelected = WasArtSeen (header->number, GroupDoc) ? ART_SEEN : ART_UNSEEN_UNSELECTED;

          unlock_headers (header_handle, thread_handle);

          CommDoc->ActiveLines++;

          update_window_title (CommDoc->hWndFrame, group,
                               RcvLineCount++,
                               CommDoc->TotalLines * total_xhdrs);
          }
          GlobalUnlock (BlockPtr->hCurBlock);
        }
        LastCommLine[0] = '\0';
     }
     else
     {
        if (*LastCommLine) 
		{
          TRACE1("Ignoring data %s\n", LastCommLine);
          *LastCommLine = '\0';
        }
        else 
		{
          strntcpy(LastCommLine, CommLineIn, MAXCOMMLINE-1);
        }
     }
    }
    break;


      /* The next few cases handle retrieval of XHDR information for display */
      /* in the group window.  If you change the number of XHDR's retrieved */
      /* (such as adding 'XHDR References' back into the state machine), you */
      /* need to reflect that change in the variable total_xhdrs. */

      /* the current flow is FROM -> DATE -> LINES -> SUBJECT -> NEWSGROUPS */
      /* (threadp) FROM -> DATE -> LINES -> ID -> REF -> SUBJECT -> NEWSGROUPS */
      /* The NEWSGROUPS state is skipped if there are no NEWSGROUPS kill filters */

      /* this will now be done dynamically, depending on the state of */
      /* the 'threadp' variable */

    case ST_XHDR_FROM_START:
      retcode = 0;
      sscanf (CommLineIn, "%d", &retcode);

      if (retcode < 100)
        break;
      if (check_server_code (retcode)) 
	  {
        //abort group window
        SendMessage (CommDoc->hWndFrame,  WM_CLOSE, 0, 0L);
        break;
      }
		if (stristr(CommLineIn, "[COMPRESS=GZIP]"))
		{
			TRACE("COMPRESSED STREAM\r\n");	
			MRRInitZStream(0);
		}
		else if (use_zhdr == 2 || stristr(CommLineIn, "compressed"))
		{
			//yEnc of zlib data
			TRACE("YENC COMPRESSED STREAM\r\n");
			MRRInitZStream(1);
		}



      CommState = ST_XHDR_FROM_DATA;
      CommDoc->ActiveLines = 0;
      break;

    case ST_XHDR_FROM_DATA:
      if (strcmp (CommLineIn, ".") == 0) 
	  {
        LockLine (CommDoc->hParentBlock, CommDoc->ParentOffset,
                  CommDoc->ParentLineID, &BlockPtr, &LinePtr);

        GroupDoc = GetGroup(LinePtr);
        /* we do this here to allow mid-session change-of-mind */
        total_xhdrs = threadp ? 6 : 4;
        if (bEnableArticleAction &&  /* Find out if we need newsgroups */
          (g_pAction->NeedNgroupHeader() || GroupDoc->pAA->NeedNgroupHeader()))
        {
          total_xhdrs++;
        }
        if(uLastArtNum < GroupDoc->uLoadLast)
        {
          GroupDoc->pSeendb->MarkSeen(uLastArtNum + 1, GroupDoc->uLoadLast);
        }
        GroupDoc->total_headers = CommDoc->ActiveLines;

        //first = GroupDoc->ServerFirst;
        //last = GroupDoc->ServerLast;

        CommDoc->ActiveLines = 0;

        /* Now ask for the date lines */
		if (use_zhdr == 2)
        mylen = _sprintf (mybuf, "XZHDR date %I64d-%I64d", GroupDoc->uLoadFirst, GroupDoc->uLoadLast);
		else
        mylen = _sprintf (mybuf, "XHDR date %I64d-%I64d", GroupDoc->uLoadFirst, GroupDoc->uLoadLast);
        CommState = ST_XHDR_DATE_START;
        PutCommLine (mybuf);
        GlobalUnlock (BlockPtr->hCurBlock);
      }
      else 
	  {

        /* Access the Group struct, get HANDLE for header data */
        LockLine (CommDoc->hParentBlock, CommDoc->ParentOffset,
                  CommDoc->ParentLineID, &BlockPtr, &LinePtr);

        GroupDoc = GetGroup(LinePtr);

        if(CommDoc->ActiveLines > CommDoc->TotalLines)
        {
          GroupDoc->uLoadLast = min(GroupDoc->uLoadLast, uLastArtNum - 1);
        }
        else
        {

        header_handle = GroupDoc->header_handle;
        thread_handle = GroupDoc->thread_handle;

        /* Lock the header data */
        headers = lock_headers (header_handle, thread_handle);
        sscanf (CommLineIn, "%I64d", &art_num);
        header = header_elt (headers, CommDoc->ActiveLines);
        header->number = art_num;
        if(uLastArtNum < header->number - 1)
          GroupDoc->pSeendb->MarkSeen(uLastArtNum + 1, header->number - 1);
        uLastArtNum = header->number;
        
        //header->sSubject = header->sFrom = header->sEmail = header->sMessage_id =
        // header->sBest_ref = header->sFrob_ref_list = header->sNewsgroups = "";
        header->ArtDoc = (TypDoc *) NULL;
        header->SeenSelected = WasArtSeen (art_num, GetGroup(LinePtr)) ? ART_SEEN : ART_UNSEEN;
        
        /* now use some of our nice formatting of email addresses */
        ParseAddress (get_xhdr_line (CommLineIn), header->pHD->sEmail, header->pHD->sFrom);
        
        if(!FullNameFrom)
          header->pHD->sFrom = header->pHD->sEmail;
#ifdef RFC1522
        else
          decodeRFC1522(header->pHD->sFrom);
#endif                   
        
        unlock_headers (header_handle, thread_handle);
        CommDoc->ActiveLines++;
        update_window_title (CommDoc->hWndFrame, group,
          RcvLineCount++,
          CommDoc->TotalLines * total_xhdrs);
        GlobalUnlock (BlockPtr->hCurBlock);
        }
      }

      break;

    case ST_XHDR_DATE_START:
      retcode = 0;
      sscanf (CommLineIn, "%d", &retcode);
      if (check_server_code (retcode)) {
        CommBusy = FALSE;
        CommState = ST_IN_GROUP;
        break;
      }
		if (stristr(CommLineIn, "[COMPRESS=GZIP]"))
		{
			TRACE("COMPRESSED STREAM\r\n");	
			MRRInitZStream(0);
		}
		else if (use_zhdr == 2 || stristr(CommLineIn, "compressed"))
		{
			//yEnc of zlib data
			TRACE("YENC COMPRESSED STREAM\r\n");
			MRRInitZStream(1);
		}


      CommState = ST_XHDR_DATE_DATA;
      CommDoc->ActiveLines = 0;
      break;

    case ST_XHDR_DATE_DATA:
      if ((strcmp (CommLineIn, ".") == 0) &&
          LockLine (CommDoc->hParentBlock, CommDoc->ParentOffset,
                    CommDoc->ParentLineID, &BlockPtr, &LinePtr))     {

        GroupDoc = GetGroup(LinePtr);
        GroupDoc->total_headers = CommDoc->ActiveLines;
            
        //first = GroupDoc->ServerFirst;
        //last = GroupDoc->ServerLast;
        CommDoc->ActiveLines = 0;

            /* Now ask for the #of lines */
		if (use_zhdr == 2)
        mylen = _sprintf (mybuf, "XZHDR lines %I64d-%I64d", GroupDoc->uLoadFirst, GroupDoc->uLoadLast);
		else
        mylen = _sprintf (mybuf, "XHDR lines %I64d-%I64d", GroupDoc->uLoadFirst, GroupDoc->uLoadLast);
        CommState = ST_XHDR_LINES_START;
        PutCommLine (mybuf);
        GlobalUnlock (BlockPtr->hCurBlock);
      }
      else {

        /* Access the Group struct, get HANDLE for header data */
        if (LockLine (CommDoc->hParentBlock, CommDoc->ParentOffset,
                      CommDoc->ParentLineID, &BlockPtr, &LinePtr)) {
          GroupDoc = GetGroup(LinePtr);
          header_handle = GroupDoc->header_handle;
          thread_handle = GroupDoc->thread_handle;
          GlobalUnlock (BlockPtr->hCurBlock);

          /* Lock the header data */
          headers = lock_headers (header_handle, thread_handle);
          syncnum = sync_artnum (atoBIGINT (CommLineIn),
                         (header_elt (headers, CommDoc->ActiveLines))->number,
                                 headers,
                                 GroupDoc);
          if (syncnum >= 0)
          {
/* Ray Dunn:99/10: added datetime arg for date sorting */
           (header_elt (headers, syncnum))->pHD->date
              = parse_usenet_date (&(header_elt (headers, syncnum))->pHD->datetime, get_xhdr_line (CommLineIn));          
            CommDoc->ActiveLines++;
          }

          unlock_headers (header_handle, thread_handle);

          update_window_title (CommDoc->hWndFrame, group,
                               RcvLineCount++,
                               CommDoc->TotalLines * total_xhdrs);
        }
      }

      break;

    case ST_XHDR_LINES_START:
      retcode = 0;
      sscanf (CommLineIn, "%d", &retcode);
      if (check_server_code (retcode)) {
        CommBusy = FALSE;
        CommState = ST_IN_GROUP;
        break;
      }
		if (stristr(CommLineIn, "[COMPRESS=GZIP]"))
		{
			TRACE("COMPRESSED STREAM\r\n");	
			MRRInitZStream(0);
		}
		else if (use_zhdr == 2 || stristr(CommLineIn, "compressed"))
		{
			//yEnc of zlib data
			TRACE("YENC COMPRESSED STREAM\r\n");
			MRRInitZStream(1);
		}


      CommState = ST_XHDR_LINES_DATA;
      CommDoc->ActiveLines = 0;
      break;

    case ST_XHDR_LINES_DATA:
      if ((strcmp (CommLineIn, ".") == 0) &&
          LockLine (CommDoc->hParentBlock, CommDoc->ParentOffset,
                    CommDoc->ParentLineID, &BlockPtr, &LinePtr))  {

        GroupDoc = GetGroup(LinePtr);
        GroupDoc->total_headers = CommDoc->ActiveLines;

        //first = GroupDoc->ServerFirst;
        //last = GroupDoc->ServerLast;

        CommDoc->ActiveLines = 0;

        /* Check for threading option, if enabled, go to ID & REF */
        /* states first */

        if (threadp) {
          /* Now ask for the message-id lines */
		  if (use_zhdr == 2)
          mylen = _sprintf (mybuf, "XZHDR message-id %I64d-%I64d", GroupDoc->uLoadFirst, GroupDoc->uLoadLast);
          else
		  mylen = _sprintf (mybuf, "XHDR message-id %I64d-%I64d", GroupDoc->uLoadFirst, GroupDoc->uLoadLast);
          CommState = ST_XHDR_MID_START;
          PutCommLine (mybuf);
        }
        else {
          CommState = ST_XHDR_SUBJECT_START;
          if (use_zhdr == 2)
		  mylen = _sprintf (mybuf, "XZHDR subject %I64d-%I64d", GroupDoc->uLoadFirst, GroupDoc->uLoadLast);
		  else
          mylen = _sprintf (mybuf, "XHDR subject %I64d-%I64d", GroupDoc->uLoadFirst, GroupDoc->uLoadLast);
          PutCommLine (mybuf);
        }
        GlobalUnlock (BlockPtr->hCurBlock);
      }

      else {

        /* Access the Group struct, get HANDLE for header data */
        if (LockLine (CommDoc->hParentBlock, CommDoc->ParentOffset,
                      CommDoc->ParentLineID, &BlockPtr, &LinePtr)) {
          GroupDoc = GetGroup(LinePtr);
          header_handle = GroupDoc->header_handle;
          thread_handle = GroupDoc->thread_handle;
          GlobalUnlock (BlockPtr->hCurBlock);

          /* Lock the header data */
          headers = lock_headers (header_handle, thread_handle);

          syncnum = sync_artnum (atoBIGINT (CommLineIn),
                         (header_elt (headers, CommDoc->ActiveLines))->number,
                                 headers,
                                 GroupDoc);
      if (syncnum >= 0)
      {
        //MDS some microsoft posts now set total number of lines for a multipart
        //message in each sub-part, thus overflowing the #lines conversion.
        //instead of changing the TypDoc struct, we'll trim the linecount, since 
        //the line count is wrong anyway.   20000401

        long nLines = 0;
        sscanf (CommLineIn, "%I64d %ld", &art_num, &nLines);
#ifdef _WIN32
        (header_elt (headers, syncnum))->lines = (int) nLines;
#else
        if (nLines > 0xFFFF)
          nLines = 0xFFFF;
        (header_elt (headers, syncnum))->lines = (short) nLines;
#endif
        CommDoc->ActiveLines++;
      }
          unlock_headers (header_handle, thread_handle);
          update_window_title (CommDoc->hWndFrame, group,
                               RcvLineCount++,
                               CommDoc->TotalLines * total_xhdrs);
        }
      }

      break;


    case ST_XHDR_MID_START:
      retcode = 0;
      sscanf (CommLineIn, "%d", &retcode);
      if (check_server_code (retcode)) {
        CommBusy = FALSE;
        CommState = ST_IN_GROUP;
        break;
      }
		if (stristr(CommLineIn, "[COMPRESS=GZIP]"))
		{
			TRACE("COMPRESSED STREAM\r\n");	
			MRRInitZStream(0);
		}
		else if (use_zhdr == 2 || stristr(CommLineIn, "compressed"))
		{
			//yEnc of zlib data
			TRACE("YENC COMPRESSED STREAM\r\n");
			MRRInitZStream(1);
		}


      CommState = ST_XHDR_MID_DATA;
      CommDoc->ActiveLines = 0;
      break;

    case ST_XHDR_MID_DATA:
      if ((strcmp (CommLineIn, ".") == 0) &&
          (LockLine (CommDoc->hParentBlock, CommDoc->ParentOffset,
                     CommDoc->ParentLineID, &BlockPtr, &LinePtr))) {

        GroupDoc = GetGroup(LinePtr);
        GroupDoc->total_headers = CommDoc->ActiveLines;
        //first = GroupDoc->ServerFirst;
        //last = GroupDoc->ServerLast;
        CommDoc->ActiveLines = 0;

        /* Now ask for the references lines */
        CommState = ST_XHDR_REF_START;
        if (use_zhdr == 2)
		mylen = _sprintf (mybuf, "XZHDR references %I64d-%I64d", GroupDoc->uLoadFirst, GroupDoc->uLoadLast);
		else
		mylen = _sprintf (mybuf, "XHDR references %I64d-%I64d", GroupDoc->uLoadFirst, GroupDoc->uLoadLast);
        PutCommLine (mybuf);
        GlobalUnlock (BlockPtr->hCurBlock);
      }
      else {
        /* Access the Group struct, get HANDLE for header data */
        if (LockLine (CommDoc->hParentBlock, CommDoc->ParentOffset,
                      CommDoc->ParentLineID, &BlockPtr, &LinePtr)) {

            GroupDoc = GetGroup(LinePtr);
            header_handle = GroupDoc->header_handle;
            thread_handle = GroupDoc->thread_handle;
            GlobalUnlock (BlockPtr->hCurBlock);

        /* Lock the header data */
            headers = lock_headers (header_handle, thread_handle);
            syncnum = sync_artnum (atoBIGINT (CommLineIn),
                        (header_elt (headers, CommDoc->ActiveLines))->number,
                                headers,
                                GroupDoc);
            if (syncnum >= 0) {
        (header_elt (headers, syncnum))->pHD->sMessage_id =
          message_id_with_frob((char far *) (get_xhdr_line (CommLineIn)+1));
        CommDoc->ActiveLines++;
            }
            unlock_headers (header_handle, thread_handle);

            update_window_title (CommDoc->hWndFrame, group,
                                 RcvLineCount++,
                                 CommDoc->TotalLines * total_xhdrs);
          }

        }

      break;


    case ST_XHDR_REF_START:
      retcode = 0;
      sscanf (CommLineIn, "%d", &retcode);
      if (check_server_code (retcode)) {
        CommBusy = FALSE;
        CommState = ST_IN_GROUP;
        break;
      }
		if (stristr(CommLineIn, "[COMPRESS=GZIP]"))
		{
			TRACE("COMPRESSED STREAM\r\n");	
			MRRInitZStream(0);
		}
		else if (use_zhdr == 2 || stristr(CommLineIn, "compressed"))
		{
			//yEnc of zlib data
			TRACE("YENC COMPRESSED STREAM\r\n");
			MRRInitZStream(1);
		}


      CommState = ST_XHDR_REF_DATA;
      CommDoc->ActiveLines = 0;
      break;

    case ST_XHDR_REF_DATA:
      if ((strcmp (CommLineIn, ".") == 0) &&
          (LockLine (CommDoc->hParentBlock, CommDoc->ParentOffset,
                     CommDoc->ParentLineID, &BlockPtr, &LinePtr))) {

        GroupDoc = GetGroup(LinePtr);
        GroupDoc->total_headers = CommDoc->ActiveLines;
        //first = GroupDoc->ServerFirst;
        //last = GroupDoc->ServerLast;
        GlobalUnlock (BlockPtr->hCurBlock);
        CommDoc->ActiveLines = 0;

       
        /* Now ask for the subject lines */
		if (use_zhdr == 2)
        mylen = _sprintf (mybuf, "XZHDR subject %I64d-%I64d", GroupDoc->uLoadFirst, GroupDoc->uLoadLast);
        else
		mylen = _sprintf (mybuf, "XHDR subject %I64d-%I64d", GroupDoc->uLoadFirst, GroupDoc->uLoadLast);
        CommState = ST_XHDR_SUBJECT_START;
        PutCommLine (mybuf);
      }
      else {
        header_p header;
        char far *sLine;
        char * refer;
        
        /* Access the Group struct, get HANDLE for header data */
        if (LockLine (CommDoc->hParentBlock, CommDoc->ParentOffset,
                      CommDoc->ParentLineID, &BlockPtr, &LinePtr)){
          GroupDoc = GetGroup(LinePtr);
          header_handle = GroupDoc->header_handle;
          thread_handle = GroupDoc->thread_handle;
          GlobalUnlock (BlockPtr->hCurBlock);
          
          /* Lock the header data */
          headers = lock_headers (header_handle, thread_handle);
          
          /* for now, we only pay attention to first (whole) referral */
          sLine = get_xhdr_line (CommLineIn);
          
          /* Patch to check for bad info from server JD 6/19/93 */
          syncnum = sync_artnum (atol (CommLineIn),
            (header_elt (headers, CommDoc->ActiveLines))->number,
            headers, GroupDoc);
          if (syncnum >= 0) {
            header = header_elt (headers, syncnum);
            refer = get_best_reference(sLine,header->pHD->sMessage_id);
            if (refer) {
                 header->pHD->sBest_ref = message_id_with_frob(refer);
                 build_ref_list(header->pHD->sFrob_ref_list, sLine,header->pHD->sMessage_id);
            }
            CommDoc->ActiveLines++;
          }
          
          unlock_headers (header_handle, thread_handle);
          
          update_window_title (CommDoc->hWndFrame, group,
            RcvLineCount++,
            CommDoc->TotalLines * total_xhdrs);
        }
        
      }
      
      break;



    case ST_XHDR_SUBJECT_START:
      retcode = 0;
      sscanf (CommLineIn, "%d", &retcode);
      if (check_server_code (retcode)) 
      {
        CommBusy = FALSE;
        CommState = ST_IN_GROUP;
        break;
      }
		if (stristr(CommLineIn, "[COMPRESS=GZIP]"))
		{
			TRACE("COMPRESSED STREAM\r\n");	
			MRRInitZStream(0);
		}
		else if (use_zhdr == 2 || stristr(CommLineIn, "compressed"))
		{
			//yEnc of zlib data
			TRACE("YENC COMPRESSED STREAM\r\n");
			MRRInitZStream(1);
		}


      CommState = ST_XHDR_SUBJECT_DATA;
      break;

    case ST_XHDR_SUBJECT_DATA:
      if(strcmp(CommLineIn, ".") == 0)
      {
        if(LockLine(CommDoc->hParentBlock, CommDoc->ParentOffset,
                     CommDoc->ParentLineID, &BlockPtr, &LinePtr))
        {
          GroupDoc = GetGroup(LinePtr);
          GroupDoc->total_headers = CommDoc->ActiveLines;

          if (bEnableArticleAction &&
             (g_pAction->NeedNgroupHeader() || GroupDoc->pAA->NeedNgroupHeader())) 
          {             /* Find out if we need newsgroups */
              /* Now ask for the newsgroups */
            // CommDoc->ActiveLines = 0; unneeded set in ST_XHDR_NEWSGROUPS_START
            if (use_zhdr == 2)
			mylen = _sprintf (mybuf, "XZHDR newsgroups %I64d-%I64d", GroupDoc->uLoadFirst, GroupDoc->uLoadLast);
			else
			mylen = _sprintf (mybuf, "XHDR newsgroups %I64d-%I64d", GroupDoc->uLoadFirst, GroupDoc->uLoadLast);

            CommState = ST_XHDR_NEWSGROUPS_START;
            PutCommLine (mybuf);
          }
          else if (bEnableArticleAction && 
                  (g_pAction->NeedNNTPHost() || GroupDoc->pAA->NeedNNTPHost()) )
          {
            mylen = _sprintf (mybuf, "XHDR NNTP-Posting-Host %I64d-%I64d", GroupDoc->uLoadFirst, GroupDoc->uLoadLast);
            CommState = ST_XHDR_NNTP_HOST_START;
            PutCommLine (mybuf);

          }
          else 
          {
            CommState = ST_IN_GROUP;
            CommBusy = FALSE;
            finish_header_retrieval();
            SetStatbarText (CommDoc->hWndFrame, "", CommDoc, TRUE, TRUE);
          }
          GlobalUnlock (BlockPtr->hCurBlock);  
        }
      }
      else 
      {

        if(LockLine(CommDoc->hParentBlock, CommDoc->ParentOffset,
                     CommDoc->ParentLineID, &BlockPtr, &LinePtr))
        {
          GroupDoc = GetGroup(LinePtr);

          art_num = 0;
          sscanf (CommLineIn, "%I64d", &art_num);
          if (art_num) 
          {
            header_handle = GroupDoc->header_handle;
            thread_handle = GroupDoc->thread_handle;
            GlobalUnlock (BlockPtr->hCurBlock);

            /* Lock the header data */
            headers = lock_headers (header_handle, thread_handle);

            /* update the seen thing. */
            syncnum = sync_artnum (atol (CommLineIn),
                      (header_elt (headers, CommDoc->ActiveLines))->number,
                                   headers, GroupDoc);
            if(syncnum >= 0)
            {
              header = header_elt (headers, syncnum);
              header->pHD->sSubject = get_xhdr_line (CommLineIn);
#ifdef RFC1522
              decodeRFC1522(header->pHD->sSubject);
#endif      
              //CommDoc->LongestLine = max (CommDoc->LongestLine,
              //                            ARTICLE_SUBJECT_OFFSET +
              //                            (unsigned) lstrlen (header->subject));
              CommDoc->LongestLine = max (CommDoc->LongestLine,
                                      ArtListSeparator3 / CharWidth +
                                      (unsigned) lstrlen (header->pHD->sSubject));
              CommDoc->ActiveLines++;
            }
            UnlockLine (BlockPtr, LinePtr, &(CommDoc->hParentBlock),
                      &(CommDoc->ParentOffset), &(CommDoc->ParentLineID));

            unlock_headers (header_handle, thread_handle);
            update_window_title (CommDoc->hWndFrame, group,
                               RcvLineCount++,
                               CommDoc->TotalLines * total_xhdrs);

          }
        }
      }
      break;

#ifdef OFFLINE
   case ST_HEAD2_NEWSGROUPS_START:
#endif

   case ST_HEAD_NEWSGROUPS_START:
      retcode = 0;
      sscanf (CommLineIn, "%d", &retcode);
#ifdef OFFLINE
      if (((retcode/100) == 5) && (CommState == ST_HEAD_NEWSGROUPS_START)) {
          sprintf (mybuf, "ARTICLE %I64d", art_num);
          CommState = ST_HEAD2_NEWSGROUPS_START;
          PutCommLine (mybuf);
          break;
      }
#endif
      if ((retcode/100) != 4) 
      {
         /* class 4 return-codes are handled by ST_HEAD_NEWSGROUPS_DATA */
         if(check_server_code(retcode)) 
         {
            CommBusy = FALSE;
            CommState = ST_IN_GROUP;
			finish_header_retrieval();
            SetStatbarText (CommDoc->hWndFrame, "", CommDoc, TRUE, TRUE);
            break;
         }

         CommState = ST_HEAD_NEWSGROUPS_DATA;
         break;
      }

   case ST_HEAD_NEWSGROUPS_DATA:
    if(LockLine(CommDoc->hParentBlock, CommDoc->ParentOffset,
            CommDoc->ParentLineID, &BlockPtr, &LinePtr))
    {
        GroupDoc = GetGroup(LinePtr);
	    retcode = 0;
	    sscanf (CommLineIn, "%d", &retcode);

        if ((strcmp (CommLineIn,".") == 0) || ((retcode/100) == 4)) 
        {
            if ((retcode/100) == 4) //MDS we add empty lines if ActiveLines is always incr
                CommDoc->TotalLines--;
            else
                CommDoc->ActiveLines++;
            if (CommDoc->ActiveLines >= CommDoc->TotalLines 
                   || (total_xhdrs == 2 && art_num >= GroupDoc->uLoadLast)) 
            {   //MDS don't get more than expected if AA got us here
               CommState = ST_IN_GROUP;
               CommBusy = FALSE;
               finish_header_retrieval();
               SetStatbarText (CommDoc->hWndFrame, "", CommDoc, TRUE, TRUE);
            } 
            else 
            {
               art_num++;
               _sprintf (mybuf, "HEAD %I64d", art_num);
               CommState = ST_HEAD_NEWSGROUPS_START;
               PutCommLine (mybuf);
            }
  
        } 
        else 
        {
           if (_strnicmp("newsgroups:",CommLineIn,11) == 0) 
           {
              header_handle = GroupDoc->header_handle;
              thread_handle = GroupDoc->thread_handle;

              /* Lock the header data */
              headers = lock_headers(header_handle, thread_handle);
              syncnum = sync_artnum(art_num,
                              (header_elt(headers, CommDoc->ActiveLines))->number,
                              headers, GroupDoc);
              if(syncnum >= 0)
              {
                const char* sTmp, * slTmp;
                header = header_elt (headers, syncnum);
                sTmp = CommLineIn + 11; /* points after "newsgroups:" */
                while (*sTmp == ' ')
                sTmp++;
                header->pHD->sNewsgroups = slTmp = sTmp;
                header->pHD->iGroups = 1;
                while(sTmp = strchr(sTmp, ','))
                {
                  if ((sTmp - slTmp) > 3)
                  {
                    //MDS skip bogus group names of i.e. '.'
                    header->pHD->iGroups++;
                  }
                  sTmp++;
                  slTmp = sTmp;
                }
                update_window_title (CommDoc->hWndFrame, group,
                CommDoc->ActiveLines,
                CommDoc->TotalLines);
              }
              unlock_headers(header_handle, thread_handle);
           }
           if (_strnicmp("nntp-posting-host:",CommLineIn,18) == 0)
           {
              header_handle = GroupDoc->header_handle;
              thread_handle = GroupDoc->thread_handle;

              /* Lock the header data */
              headers = lock_headers(header_handle, thread_handle);
              syncnum = sync_artnum(art_num,
                              (header_elt(headers, CommDoc->ActiveLines))->number,
                              headers, GroupDoc);
              if(syncnum >= 0)
              {
                
                
                const char* sTmp, * slTmp;
                header = header_elt (headers, syncnum);
                sTmp = CommLineIn + 18; /* points after "newsgroups:" */
                while (*sTmp == ' ')
                    sTmp++;
                header->pHD->sNNTPPostHost = slTmp = sTmp;

              }
              unlock_headers(header_handle, thread_handle);

           }

        }
        GlobalUnlock (BlockPtr->hCurBlock);  
    }
    break;
    case ST_XHDR_NNTP_HOST_START:
    // This is called to retrieve NNTP-Posting-Host if Article Action needs it
        retcode = 0;
        sscanf(CommLineIn, "%d", &retcode);
        if (xoverp && ((retcode/100) == 5))
        {
             if (GetNewsgroupsViaNNTP) 
             {
                if(LockLine(CommDoc->hParentBlock, CommDoc->ParentOffset,
                     CommDoc->ParentLineID, &BlockPtr, &LinePtr))
                   {
                     GroupDoc = GetGroup(LinePtr);
                     art_num = GroupDoc->uLoadFirst;

                     _sprintf (mybuf, "HEAD %I64d", art_num);
                     CommState = ST_HEAD_NEWSGROUPS_START;
                     CommDoc->ActiveLines = 0;
                     PutCommLine (mybuf);
                     GlobalUnlock (BlockPtr->hCurBlock);  
                   }
             } 
             else 
             {
                CommState = ST_IN_GROUP;
                CommBusy = FALSE;
                finish_header_retrieval();
                SetStatbarText (CommDoc->hWndFrame, "", CommDoc, TRUE, TRUE);
             }
             break;
        }
        if(check_server_code(retcode)) 
        {
          CommBusy = FALSE;
          CommState = ST_IN_GROUP;
          break;
        }
		if (stristr(CommLineIn, "[COMPRESS=GZIP]"))
		{
			TRACE("COMPRESSED STREAM\r\n");	
			MRRInitZStream(0);
		}
		else if (use_zhdr == 2 || stristr(CommLineIn, "compressed"))
		{
			//yEnc of zlib data
			TRACE("YENC COMPRESSED STREAM\r\n");
			MRRInitZStream(1);
		}


        CommState = ST_XHDR_NNTP_HOST_DATA;
        CommDoc->CountedLines = CommDoc->ActiveLines; //MDS save the old count in case HEAD command returns empty list
        CommDoc->ActiveLines = 0;
        break;

    case ST_XHDR_NNTP_HOST_DATA:
        if (strcmp (CommLineIn, ".") == 0)
        {
          if (CommDoc->ActiveLines == 0 &&  CommDoc->CountedLines != 0)
          {
            //Fix for needgroupheaders on NewsGuy which returns empty XHDR newsgroups list
            CommDoc->ActiveLines = CommDoc->CountedLines;
            CommDoc->CountedLines = 0;
            g_bUseXHDR = FALSE;

            if ( (GetNewsgroupsViaNNTP) ) 
            {
              //try again...
              if(LockLine(CommDoc->hParentBlock, CommDoc->ParentOffset,
                              CommDoc->ParentLineID, &BlockPtr, &LinePtr)) 
              {
                GroupDoc = GetGroup(LinePtr);
                art_num = GroupDoc->uLoadFirst;

                _sprintf (mybuf, "HEAD %I64d", art_num);
                CommState = ST_HEAD_NEWSGROUPS_START;
                CommDoc->ActiveLines = 0;
                PutCommLine (mybuf);
                GlobalUnlock (BlockPtr->hCurBlock);
                break;
              }
            }  

          }
          CommState = ST_IN_GROUP;
          CommBusy = FALSE;
          finish_header_retrieval();
          SetStatbarText (CommDoc->hWndFrame, "", CommDoc, TRUE, TRUE);
        }
        else
        {
          /* Access the Group struct, get HANDLE for header data */
          if(LockLine(CommDoc->hParentBlock, CommDoc->ParentOffset,
             CommDoc->ParentLineID, &BlockPtr, &LinePtr))
          {
            GroupDoc = GetGroup(LinePtr);
            header_handle = GroupDoc->header_handle;
            thread_handle = GroupDoc->thread_handle;
            GlobalUnlock(BlockPtr->hCurBlock);

            /* Lock the header data */
            headers = lock_headers(header_handle, thread_handle);
            syncnum = sync_artnum(atol(CommLineIn),
                                  (header_elt(headers, CommDoc->ActiveLines))->number,
                                  headers, GroupDoc);
            if(syncnum >= 0)
            {
              const char* sTmp;
              header = header_elt (headers, syncnum);
              sTmp = get_xhdr_line(CommLineIn);
              if (strcmp(sTmp, "(none)") )
              {
                header->pHD->sNNTPPostHost = sTmp;
                CommDoc->ActiveLines++;
              }
            }
            unlock_headers(header_handle, thread_handle);

            update_window_title(CommDoc->hWndFrame, group,
                                RcvLineCount++, CommDoc->TotalLines * total_xhdrs);
          }
        }
    break;

   case ST_XHDR_NEWSGROUPS_START:
        /* this is called also for retrieving newsgroups if Articleaction need this */
        retcode = 0;
        sscanf(CommLineIn, "%d", &retcode);
        if (((retcode/100) == 5))  //xhdr not supported can happen for non-XOVER too
        {
             if (!xoverp && GetNewsgroupsViaNNTP)
			 {
				if (WVMessageBox (CommDoc ? CommDoc->hDocWnd : NetDoc.hDocWnd, IDS_WVUTIL_NOXHDR, IDS_WVUTIL_WINVN2, MB_YESNO | MB_ICONHAND) == IDNO)
					GetNewsgroupsViaNNTP = FALSE;

			 }
			 if (GetNewsgroupsViaNNTP) 
             {
                if(LockLine(CommDoc->hParentBlock, CommDoc->ParentOffset,
                     CommDoc->ParentLineID, &BlockPtr, &LinePtr))
                   {
                     GroupDoc = GetGroup(LinePtr);
                     art_num = GroupDoc->uLoadFirst;

                     _sprintf (mybuf, "HEAD %I64d", art_num);
                     CommState = ST_HEAD_NEWSGROUPS_START;
                     CommDoc->ActiveLines = 0;
                     PutCommLine (mybuf);
                     GlobalUnlock (BlockPtr->hCurBlock);  
                   }
             } 
             else 
             {
                CommState = ST_IN_GROUP;
                CommBusy = FALSE;
                finish_header_retrieval();
                SetStatbarText (CommDoc->hWndFrame, "", CommDoc, TRUE, TRUE);
             }
             break;        
        }

        if(check_server_code(retcode)) 
        {
          CommBusy = FALSE;
          CommState = ST_IN_GROUP;
          finish_header_retrieval();
          SetStatbarText (CommDoc->hWndFrame, "", CommDoc, TRUE, TRUE);

          break;
        }
		if (stristr(CommLineIn, "[COMPRESS=GZIP]"))
		{
			TRACE("COMPRESSED STREAM\r\n");	
			MRRInitZStream(0);
		}
		else if (use_zhdr == 2 || stristr(CommLineIn, "compressed"))
		{
			//yEnc of zlib data
			TRACE("YENC COMPRESSED STREAM\r\n");
			MRRInitZStream(1);
		}


        CommState = ST_XHDR_NEWSGROUPS_DATA;
        CommDoc->CountedLines = CommDoc->ActiveLines; //MDS save the old count in case HEAD command returns empty list
        CommDoc->ActiveLines = 0;
    break;

    case ST_XHDR_NEWSGROUPS_DATA:
    if (strcmp (CommLineIn, ".") == 0)
    {
      if (CommDoc->ActiveLines == 0 &&  CommDoc->CountedLines != 0)
      {
        //Fix for needgroupheaders on NewsGuy which returns empty XHDR newsgroups list
        CommDoc->ActiveLines = CommDoc->CountedLines;
        CommDoc->CountedLines = 0;
        g_bUseXHDR = FALSE;

        if ( (GetNewsgroupsViaNNTP) ) 
        {
          //try again...
          if(LockLine(CommDoc->hParentBlock, CommDoc->ParentOffset,
                          CommDoc->ParentLineID, &BlockPtr, &LinePtr)) 
          {
            GroupDoc = GetGroup(LinePtr);
            art_num = GroupDoc->uLoadFirst;

            _sprintf (mybuf, "HEAD %I64d", art_num);
            CommState = ST_HEAD_NEWSGROUPS_START;
            CommDoc->ActiveLines = 0;
            PutCommLine (mybuf);
            GlobalUnlock (BlockPtr->hCurBlock);
            break;
          }
        }  

      }
      //Now see if we need NNTP HOST data
      if(LockLine(CommDoc->hParentBlock, CommDoc->ParentOffset,
                      CommDoc->ParentLineID, &BlockPtr, &LinePtr)) 
      {
        GroupDoc = GetGroup(LinePtr);
        if (bEnableArticleAction && 
            (g_pAction->NeedNNTPHost() || GroupDoc->pAA->NeedNNTPHost()) )
        {
          mylen = _sprintf (mybuf, "XHDR NNTP-Posting-Host %I64d-%I64d", GroupDoc->uLoadFirst, GroupDoc->uLoadLast);
          CommState = ST_XHDR_NNTP_HOST_START;
          PutCommLine (mybuf);

        }
        GlobalUnlock (BlockPtr->hCurBlock);
      }

      if (CommState == ST_XHDR_NEWSGROUPS_DATA)
      {
        //we're done
        CommState = ST_IN_GROUP;
        CommBusy = FALSE;
        finish_header_retrieval();
        SetStatbarText (CommDoc->hWndFrame, "", CommDoc, TRUE, TRUE);
      }
    }
    else
    {
      /* Access the Group struct, get HANDLE for header data */
      if(LockLine(CommDoc->hParentBlock, CommDoc->ParentOffset,
         CommDoc->ParentLineID, &BlockPtr, &LinePtr))
      {
        GroupDoc = GetGroup(LinePtr);
        header_handle = GroupDoc->header_handle;
        thread_handle = GroupDoc->thread_handle;
        GlobalUnlock(BlockPtr->hCurBlock);

        /* Lock the header data */
        headers = lock_headers(header_handle, thread_handle);
        syncnum = sync_artnum(atol(CommLineIn),
                              (header_elt(headers, CommDoc->ActiveLines))->number,
                              headers, GroupDoc);
        if(syncnum >= 0)
        {
          const char* sTmp;
          header = header_elt (headers, syncnum);
          sTmp = get_xhdr_line(CommLineIn);

          if (strcmp(sTmp, "(none)") )
          {
              header->pHD->sNewsgroups = sTmp;
              header->pHD->iGroups = 1;
              while(sTmp = strchr(sTmp, ','))
              {
                header->pHD->iGroups++;
                sTmp++;
              }

              CommDoc->ActiveLines++;

          }



        }
        unlock_headers(header_handle, thread_handle);

        update_window_title(CommDoc->hWndFrame, group,
                            RcvLineCount++, CommDoc->TotalLines * total_xhdrs);
      }
    }
    break;

    case ST_IN_GROUP:
#ifdef _DEBUG
	///hmmm not expecting messages are we?
	MessageBeep( (UINT) -1);
	OutputDebugString(CommLineIn);
	OutputDebugString("\r\n");	
#endif

        break;

    case ST_IS_ARTICLE_ON_SERVER:  // Heirich 19961015
        retcode = 0;
        sscanf(CommLineIn, "%d", &retcode);
        IsArticleAvailable =  (retcode != 430);
        CommState = ST_IN_GROUP; /* STAT Command completed */
        CommBusy = FALSE;
        break;

    case ST_ARTICLE_RESP:
        retcode = 0;
        sscanf (CommLineIn, "%d", &retcode);
		if (retcode == 412 && *CurrentGroup && CurrentArticleID[0]) //not in group?
		{
            
			//try to set group again, why isn't it?
			strcpy (mybuf, "GROUP ");
            lstrcat (mybuf, CurrentGroup);
            PutCommLine (mybuf);

			CommState = ST_GROUP_REJOIN;

			break;

		}

        if (check_server_code (retcode)) 
        {
            //TODO: MDS check for GPF when multiple bad message windows
            /* abort communication, because there doesn't come any data from server */
            CommState = ST_IN_GROUP;
            CommBusy = FALSE;
            /* tell the Doc retrieving is complete as a fake ! */
            if (CommDecoding && currentCoded) 
            {
                SendMessage (currentCoded->hParentWnd, (UINT) WM_COMMAND,
                            (WPARAM) ID_ARTICLE_ERROR, 0L);
            } 
            else 
            {
                if (CommDoc)
                SendMessage (CommDoc->hWndFrame, (UINT) WM_COMMAND,
                        (WPARAM) ID_ARTICLE_ERROR, 0L);
            }
            break;
        }


        ClearLatestInfo();
#if defined( JAPANESE ) && defined( RFC1522 )
        decodeMLStrRFC1522(MLTLN_INIT);
#endif
        CommState = ST_REC_ARTICLE_HEADER;
        UsingMIME = FALSE;        /* new article, init to no MIME */
        WithinSignature = FALSE; /* new article, init to no signature */
        ClearFlags(CommDoc, (yEncoded | BNewsEnc));
        break;

    case ST_REC_ARTICLE_HEADER:
#ifdef OFFLINE
        // S.Yoshida
        if (strcmp (CommLineIn, ".") == 0) 
        {
            goto nntpArticleERR;    /* error: empty article (end in middle of header) */
        }
#else
        //    if (strcmp (CommLineIn, ".") == 0) 
        //    {
        //      ; /* error: empty article (end in middle of header) */
        //    }
#endif
        if (IsBlankStr (CommLineIn)) 
        {    /* headers end in blank line */
            Latest.cAddWrap = 0;
#if defined( JAPANESE ) && defined( RFC1522 )
            strcpy (CommLineIn, ".");
            while (decodeMLStrRFC1522(CommLineIn)) 
            {
                WrapAddCommLineToDoc (CommLineIn);
                StoreHeaderInfo(CommLineIn);
                CommLineIn[0] = '\0';
            }
#endif // JAPANESE
            WrapAddCommLineToDoc (CommLineIn);
            CommState = ST_REC_ARTICLE;
            CommDoc->HeaderLines = CommDoc->ActiveLines;  //Heirich 19960728
        } 
        else 
        {
            if (CommDecoding) 
            {
                /* find out if this article is crossposted to other groups */
                if (((TrackSubscribedCrossposts == TRUE) || 
                        (TrackUnsubscribedCrossposts == TRUE)) &&  
                        (_strnicmp("xref:", CommLineIn, 5) == 0 )) 
                {
                    //header line looks like:
					//xref: myserver.somewhere agroup:1234 bgroup:2345
					ptr = CommLineIn+6;
					while (*ptr && *ptr != ' ') //MDS skip server name and space
						ptr++;
					while (*ptr && *ptr == ' ')
						ptr++;

                    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(mybuf2,ptr1,ptr-ptr1);                   /* save artindex */
                        if (_stricmp(mybuf,CurrentGroup) != 0)         /* mark if not our group */ 
                            mark_article_seen(&NetDoc, mybuf,atoBIGINT(mybuf2));
                    }
                }
            }
        }
        if (!TrimHeader (CommLineIn))
        {
#if defined( JAPANESE ) && defined( RFC1522 )
            while (decodeMLStrRFC1522(CommLineIn)) 
            {
                WrapAddCommLineToDoc (CommLineIn);
                StoreHeaderInfo(CommLineIn);
                CommLineIn[0] = '\0';
            }
#else
#ifdef RFC1522
            decodeStrRFC1522(CommLineIn);
#endif                            
            WrapAddCommLineToDoc (CommLineIn);
            StoreHeaderInfo(CommLineIn);
#endif  // JAPANESE
        }
        break;

    case ST_REC_ARTICLE:
        if (strcmp (CommLineIn, ".") != 0) 
        {
#ifdef JAPANESE
            decodeNetworkCoding (CommLineIn, WVCDS_ISO2022);
#endif
            WrapAddCommLineToDoc (CommLineIn);
        }
        else 
        {
nntpArticleERR:;
            if (CommDoc) 
            {          
                /* Handle aborted condition JD 10/26/95 */
                /* article receive complete */
                CommState = ST_IN_GROUP;
                CommBusy = FALSE;
#ifdef VISUAL1521                       
                Visual1521InputArticle(CommDoc); //Heirich 19960827
#endif          
                if (CommDecoding && currentCoded) 
                {
                    SendMessage (currentCoded->hParentWnd, (UINT) WM_COMMAND,
                                (WPARAM) ID_ARTICLE_RETRIEVE_COMPLETE, 0L);
                    break;
                }
                else 
                {
                    SendMessage (CommDoc->hWndFrame, (UINT) WM_COMMAND,
                                (WPARAM) ID_ARTICLE_RETRIEVE_COMPLETE, 0L);
                }

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

                    GroupDoc = GetGroup(LinePtr);
                    header_handle = GroupDoc->header_handle;
                    thread_handle = GroupDoc->thread_handle;

                    /* Ray Dunn:99/10 Allow optional Author name in title bar */
                    headers = lock_headers (header_handle, thread_handle);
                    lpsz = (char far *) (const char far*)((header_elt (headers, CommDoc->LastSeenLineID))->pHD->sSubject);
                    strntcpy (group, lpsz, MAXGROUPNAME);

                    lpsz = (char far *) (const char far*)((header_elt (headers, CommDoc->LastSeenLineID))->pHD->sFrom);
                    strntcpy (mybuf2, lpsz, MAXGROUPNAME);

                    unlock_headers (header_handle, thread_handle);

                    _sprintf(mybuf,"%lu", CommDoc->TotalLines);
                    SetStatbarText (CommDoc->hWndFrame, "", CommDoc, TRUE, TRUE);
                    // easy way out, if too big, don't do it
                    if (ShowAuthorName && strlen(group) + strlen(mybuf2) + 20 < sizeof(mybuf))
                        WVArgXSetWindowText(CommDoc->hWndFrame, IDS_ARTICLE_TITLE2 ,group, mybuf2, mybuf);
                    else
                        WVArgXSetWindowText(CommDoc->hWndFrame, IDS_ARTICLE_TITLE ,group, mybuf);

                    /* Ray Dunn:99/10:End */

                    //make xface here or...
                    
					if ( ((const char*)Latest.sFace)[0] != '\0')
                    {
                        
						MakeFaceWnd(Latest.sFace);
                        SetFocus(CommDoc->hDocWnd);
                    }
					

                    InvalidateRect (CommDoc->hDocWnd, NULL, FALSE);
                    GlobalUnlock (BlockPtr->hCurBlock);


                    /* Skip to the first line of the text of the article
                    * and make sure it's visible on the screen.  This is
                    * so that the user doesn't have to have the first
                    * screen filled with a lengthy, worthless header.
                    *
                    * and save number of header lines (on display)
                    * for later (Bretherton)
                    */
                    if (TopOfDoc (CommDoc, &BlockPtr, &LinePtr) && ScrollPastHeaders) 
                    {
                        found = FALSE;
                        do 
                        {
                            lpsz = ((char far *) LinePtr + sizeof (TypLine) + sizeof (TypText));
                            if (IsBlankStr (lpsz)) 
                            {
                                found = TRUE;
                                CommDoc->HeaderLines = WhatLine (BlockPtr, LinePtr);
                                break;
                            }
                            if (!NextLine (&BlockPtr, &LinePtr))
                                break;
                        } while (!found);

                        NextLine (&BlockPtr, &LinePtr);

                        /* If the line is in the last screen's worth of lines, back
                        * up the pointer so it points to the first line of the last
                        * screen.
                        */
                        if (found && CommDoc->TotalLines > CommDoc->ScYLines &&
                            !CommDoc->TopScLineID)
                            AdjustTopSc (BlockPtr, LinePtr);

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

    case ST_POST_WAIT_PERMISSION:

        /*      WndPost = getWndEdit(WndPosts,CommWnd,MAXPOSTWNDS) ; */
        /*      found = (WndPost != NULL) ; */
		{
			retcode = 0;
			sscanf (CommLineIn, "%u", &retcode);
			TypDoc *statDoc = CommDoc ? CommDoc : &NetDoc;
				SetStatbarText (statDoc->hWndFrame, "", statDoc, TRUE, TRUE);
			InvalidateRect (NetDoc.hDocWnd, NULL, TRUE);
			if (retcode == 340) 
			{
				//recommended ID <finq4h$635$1@registered.motzarella.org>
				DoSend (CONTINUE);
			}
			else 
			{
				check_server_code (retcode);
				AbortSendPost (ComposeWnd ? ComposeWnd->hWnd : NULL );
				DoSend (ComposeWnd ? CONTINUE : ABORT);      /* allow any sendmail to continue */
			}
        }
        break;

    case ST_POST_WAIT_END:

        /*      WndPost = getWndEdit(WndPosts,CommWnd,MAXPOSTWNDS) ; */
        /*      found = (WndPost != NULL) ; */

        /* no check for failure to find posting documents */
        retcode = 0;
        sscanf (CommLineIn, "%d", &retcode);
        if (retcode == 240) 
        {
            DoSend (CONTINUE);
        }
        else if (check_server_code (retcode)) 
        {
            /* cut down on winvn mailing list torture */
            if (strcmp (CommLineIn,
                    "441 Article not posted -- more included text than new text") == 0) 
            {
                //resource string > 250 chars, build error in MSCV1.5, thus split in two
                //WVMessageBox (NetDoc.hDocWnd, IDS_WVUTIL_REJECTED, IDS_WVUTIL_WINVN5, MB_OK | MB_ICONHAND);
                LoadString(hInst,IDS_WVUTIL_REJECTED2, mybuf2, MAXHEADERLINE);          
                WVArgMessageBox(NetDoc.hDocWnd, IDS_WVUTIL_REJECTED, IDS_WVUTIL_WINVN5, MB_OK | MB_ICONHAND, mybuf2);
            }
        
            /*
             * if we have an error, and did not lose the connection, abort send.
             * (if connection was lost the send was already aborted)
             */

            if (Initializing != INIT_NOT_CONNECTED) 
            {
                AbortSendPost (ComposeWnd->hWnd);
                DoSend (CONTINUE);    /* allow any sendmail to continue */
            }
        }
		else
		{
			//unknown response
			WVArgMessageBox (ComposeWnd->hWnd, IDS_WVUTIL_STRING1, IDS_WVUTIL_MESSAGE_FROM,
						MB_OK | MB_ICONHAND | MB_SETFOREGROUND, CommLineIn );
			 DoSend (CONTINUE);

			
		}

        break;
    
	case ST_TIMEOUT_WAKEUP:
		/* this is a answer upon a "GROUP" command to prevent Timeout of Newsserver */
		/* there isn't any need to check response from server */
		CommState = ST_IN_GROUP;
		CommBusy = FALSE;

		TimeoutCounter = CommTimeout * 1000 / COMM_TIMER_INTERVAL;

		statDoc = CommDoc ? CommDoc : &NetDoc;
		SetStatbarText (statDoc->hWndFrame, "", statDoc, TRUE, TRUE);

		break;
    
    case ST_GROUP_RETURN: //return after inline group query or reconnect
		CommState = ST_IN_GROUP;
        CommBusy = FALSE;

        statDoc = CommDoc ? CommDoc : &NetDoc;
        SetStatbarText (statDoc->hWndFrame, "", statDoc, TRUE, TRUE);

		break;

      /* the following code is for an MRR-hacked nntp server */

    case ST_GROUP_REJOIN:
	    if (CurrentArticleID[0])
		{
			

			sprintf (mybuf, "ARTICLE %s", CurrentArticleID);
			PutCommLine (mybuf);
			CurrentArticleID[0] = '\0';
		
		}
      CommState = ST_ARTICLE_RESP;
      break;
#ifdef OFFLINE  // TANAKA Goh
    case ST_DELETE:
      break;

    case ST_WRITE:
        if (strcmp(CommLineIn, ".") == 0) 
        {
            CommState = ST_IN_GROUP;
            CommBusy = FALSE;
            SetStatbarPercent(CommDoc->ParentDoc->hWndFrame, 100, CommDoc->ParentDoc, TRUE);
            //SetStatbarPercent(NetDoc.hWndFrame, 0, &NetDoc, TRUE);
            SetStatbarText(CommDoc->ParentDoc->hWndFrame, "", CommDoc->ParentDoc, TRUE, TRUE);
            //SetStatbarText(NetDoc.hWndFrame, "", &NetDoc, TRUE, TRUE);
            SetStatbarPercent(CommDoc->ParentDoc->hWndFrame, 0, CommDoc->ParentDoc, TRUE);
            //SetStatbarPercent(NetDoc.hWndFrame, 0, &NetDoc, TRUE);
            PostMessage(CommDoc->ParentDoc->hDocWnd, WM_TIMER, 0, 0);
        } 
        else 
        {
            static int  prevPercent = 100, newPercent = 0;
            unsigned long   number = atol(CommLineIn) * 100;

            if (newPercent < prevPercent) 
            {
                CommBusy = TRUE;
                prevPercent = 0;
                WVSetStatbarText(CommDoc->ParentDoc->hWndFrame, IDS_STBAR_SAVE_SOUP, CommDoc->ParentDoc, TRUE, TRUE);
                //SetStatbarText(NetDoc.hWndFrame, "Saving SOUP...", &NetDoc, TRUE, TRUE);
                SetStatbarPercent(CommDoc->ParentDoc->hWndFrame, 0, CommDoc->ParentDoc, TRUE);
                //SetStatbarPercent(NetDoc.hWndFrame, 0, &NetDoc, TRUE);
            }

            if ((number % UPDATE_TITLE_FREQ) == 0) 
            {
                newPercent = (int)(number / CommDoc->TotalLines);

                if (newPercent != prevPercent && newPercent - prevPercent > 1) 
                {
                    SetStatbarPercent(CommDoc->ParentDoc->hWndFrame, newPercent, CommDoc->ParentDoc, TRUE);
                    //SetStatbarPercent(NetDoc.hWndFrame, newPercent, &NetDoc, TRUE);
                    prevPercent = newPercent;
                }
            }
        }

        break;
#endif
    }
  }
}



BOOL isLineQuotation (char *textptr)
{
    char *loc;
    loc = (char*) memchr (textptr, QuoteLineInd, 2);
    if (!loc)
        loc = (char*) memchr (textptr, '|', 2);
    if (!loc)
        loc = (char*) memchr (textptr, ':', 2);
    return (loc != NULL);
}


BOOL isSignatureDelimiter(char *textptr)
{
    if (strcmp(textptr,SIG_DELIMITER) == 0)
        return TRUE;
    if ((TruncateSignatures > 1) && (strcmp(textptr,"- -- ") == 0)) /* PGP signed articles */
        return TRUE;
    if ((TruncateSignatures > 2) && (strncmp(textptr,SIG_DELIMITER,3)==0))
        return TRUE;
    if ((TruncateSignatures > 3) && (strcmp(textptr,"--")==0))
        return TRUE;
    if ((TruncateSignatures > 4) && (strncmp(textptr,"--",2)==0))
        return TRUE;
    return FALSE;
}

/*-- function AddCommLineToDoc ---------------------------------------
 *  Adds the given line to the comm doc, wrapping if necessary
 *  uses AddCommLineToDocHelp
 */
void AddCommLineToDoc (char *line)
{
    TypLine far *LinePtr;
    TypBlock far *BlockPtr;
    char *cptr, *cdest;
    int mylen;
    unsigned int col, maxlen;
    char artline[MAXHEADERLINE];
    int PercentDone;

    if (CommState == ST_REC_ARTICLE_HEADER) 
    {
        maxlen = MAXHEADERLINE;
    } 
    else 
    {
        maxlen = MAXINTERNALLINE;
    }

    /* Copy this line into an image of a textblock line,
    * expanding tabs.
    */
    cptr = line;
    cdest = artline + sizeof (TypLine) + sizeof (TypText);

    if (CommDoc && ( (CommDoc->ParentDoc && CommDoc->ParentDoc->savingArtIndex != (UINT)-1) || IsFlagSet(CommDoc, (yEncoded | BNewsEnc)) ) )
    {
        //saving art? always store long line
        maxlen = MAXHEADERLINE;

        for (col = 0; *cptr && col < (maxlen - 3 * sizeof (TypLine) - sizeof (TypText)); cptr++, col++) 
        {
            *(cdest++) = *cptr;
        }
    }
    else
    {
        for (col = 0;
            *cptr && col < (maxlen - 3 * sizeof (TypLine) - sizeof (TypText));
            cptr++) 
        {
            //converts TAB to spaces
			if (*cptr == '\t') 
            {
                do 
                {
                    *(cdest++) = ' ';
                } while (++col & 7);
            }
            else 
            {
                *(cdest++) = *cptr;
                col++;
            }
        }
    }
    *(cdest++) = '\0';

    ((TypLine *) artline)->LineID = NextLineID++;
    if (LockLine (CommDoc->hCurAddBlock, CommDoc->AddOffset, 
        CommDoc->AddLineID, &BlockPtr, &LinePtr)) 
    {
        mylen = (cdest - artline) + sizeof (int);

        // changed by Holger.Liebig@mch.sni.de to have correct
        // allignment on MIPS processors.
        // was:  mylen += mylen % 2;
        if (mylen % sizeof(int))
        {
            mylen += sizeof(int) - (mylen % sizeof(int));
        }

        ((TypText *) (artline + sizeof (TypLine)))->NameLen =
            (cdest - 1) - (artline + sizeof (TypLine) + sizeof (TypText));
        ((TypLine *) artline)->length = mylen;
        ((TypLine *) artline)->active = TRUE;
        *((int *) (artline + mylen - sizeof (int))) = mylen;
        AddLine ((TypLine *) artline, &BlockPtr, &LinePtr);
        CommDoc->LongestLine = max (CommDoc->LongestLine, (unsigned int) col);
        CommDoc->ActiveLines++;
        UnlockLine (BlockPtr, LinePtr, &(CommDoc->hCurAddBlock),
            &(CommDoc->AddOffset), &(CommDoc->AddLineID));
    }

    if (CommDoc->CountedLines > 0) 
    {
        //  PercentDone = MulDiv(CommDoc->ActiveLines, 100, CommDoc->CountedLines);
        PercentDone = (int)((CommDoc->ActiveLines * 100) / CommDoc->CountedLines);
        SetStatbarPercent(CommDoc->hWndFrame, (int) PercentDone, CommDoc, TRUE);
    }

    if ((CommDoc->TotalLines % UPDATE_ART_FREQ) == 0)
        InvalidateRect (CommDoc->hDocWnd, NULL, FALSE);

    if (*cptr)
    {
        //if line is too long, don't toss it, keep adding
        AddCommLineToDoc(cptr);
    }
}


#define MAXQUOTATIONLENGTH 30
void WrapAddCommLineToDoc (char *line)
{
    char *start, *end;
    int thisLen, lenToGo;
    char saveChar;
    char AppendLine[MAXHEADERLINE];
    char Quotations[MAXQUOTATIONLENGTH];
    unsigned int QuoteLength;
    BOOL FillInQuotations;
    char *p;
    int result;
    int SaveCommState;

    /* special case for lines starting with '..' */
    if (strncmp (CommLineIn, "..", 2)) 
    {
        start = CommLineIn;
    }
    else 
    {
        start = CommLineIn + 1;
    }
    if (CommDecoding) 
    {
        SaveCommState = CommState;
        CommState = ST_WAIT_COMM;
        /* Here we have to stop receiving, because recursive call of
        DecodeLine(), CompleteThisDecode() or InitCoded() causes a lot of problems.
        This happens if during this functions a Windowsfunction, like MessageBox()
        is called. During Windows wait for a Input to MessageBox() Windows continues
        processing WM_TIMER messages to WVUSENET which calls WVUTIL DoCommState()
        and this call WrapAddCommLineToDoc again. And so we have a recursion.
        As long ST_WAIT_COMM is set DoCommState isn't called in WVUSENET
        */
        result = DecodeLine (currentCoded, start);
        if (result == FAIL) 
        {
            MessageBox (CommDoc->hDocWnd, "Aborted decode", "Problems during decode", MB_OK | MB_ICONEXCLAMATION);
            //MDS
            if (CompleteThisDecode () == FAIL) 
            {
                MessageBox (CommDoc->hDocWnd, "Aborted decode", "Problems during decode", MB_OK | MB_ICONEXCLAMATION);
            }
            if ((currentCoded = InitCoded (CommDoc->hDocWnd)) == NULL) 
            {
                MessageBox (CommDoc->hDocWnd, "Unable to continue due to memory constraints.  Aborted", "Init Coded Object Error", MB_OK);
            }
        }
        if (result == END_BLOCK) 
        {
            if (CompleteThisDecode (IsFlagSet(CommDoc, PreviewDecode)) == FAIL) 
            {
                MessageBox (CommDoc->hDocWnd, "Aborted decode", "Problems during decode", MB_OK | MB_ICONEXCLAMATION);
            }
            if ((currentCoded = InitCoded (CommDoc->hDocWnd)) == NULL) 
            {
                MessageBox (CommDoc->hDocWnd, "Unable to continue due to memory constraints.  Aborted", "Init Coded Object Error", MB_OK);
            }
        }
        CommState = SaveCommState; /* Continue Receiving */
    } 
    else 
    {
		if ((CommState == ST_REC_ARTICLE) && (isSignatureDelimiter(start)))
            WithinSignature = TRUE;

        if (IsFlagSet(CommDoc,yEncoded) && strncmp( CommLineIn, "=yend ",6) == 0)
            ClearFlags(CommDoc,yEncoded);
        else if (strncmp( CommLineIn, "=ybegin ",8) == 0)
            SetFlags(CommDoc, yEncoded);
        else if ( !strncmp( CommLineIn, "<<BPST ", 7) && strstr(CommLineIn, "!BPST>>") )
            SetFlags(CommDoc, BNewsEnc);
		else if ( !strncmp( CommLineIn, "begin ",6) && isdigit(*(CommLineIn+6)) && isdigit( *(CommLineIn+7)) && isdigit(*(CommLineIn+8)) )
			SetFlags(CommDoc, UUCoded);
		else if (IsFlagSet(CommDoc,UUCoded) && strncmp( CommLineIn, "end ",3) == 0)
			ClearFlags(CommDoc,UUCoded);
        else if ( IsFlagSet(CommDoc,BNewsEnc) && (!*CommLineIn || !*(CommLineIn+1)) )
            ClearFlags(CommDoc,BNewsEnc);


        if ((!WithinSignature) || (!TruncateSignatures))
        {  

            if (!WrapIncomingArticleText || CommState == ST_REC_ARTICLE_HEADER || *start == '\0'
                    || (CommDoc && CommDoc->ParentDoc && CommDoc->ParentDoc->savingArtIndex != (UINT)-1 ) 
                    || (IsFlagSet(CommDoc, (yEncoded | BNewsEnc | UUCoded)) ) )
            {
                AddCommLineToDoc (start);
            }
            else 
            {
                lenToGo = strlen (start);
                Quotations[0] = '\0';
                FillInQuotations = TRUE;
                while (*start) 
                {
                    thisLen = min (lenToGo, WrapIncomingArticleTextLength);
                    end = start + thisLen;
                    while ((end >start) && (*end != '\0') && (*end != ' ') && (*end != '\t')) 
                    {
                        end--;
                        thisLen--;
                    }
                    if (end == start) 
                    {
                        /* there is no space or tab from start of line (or the remaining part of it) to the place where we want to insert a line break */
                        thisLen = min(lenToGo,WrapIncomingArticleTextLength);
                        end = start + thisLen;  /* prevent that we break in a =XY sequence of QP encoding */
                        if (*(end-1) == '=')
                            thisLen--;
                        else if  (*(end-2) == '=')
                            thisLen -= 2; 
                    }

                    lenToGo -= thisLen;

                    end = start + thisLen;        /* save char at end of this line */
                    saveChar = *end;
                    *end = '\0';

                    strcpy(AppendLine,Quotations);
                    strcat(AppendLine,start);
                    AddCommLineToDoc (AppendLine);

                    if ( FillInQuotations) 
                    {
                        p = Quotations;
                        QuoteLength = MAXQUOTATIONLENGTH;
                        while ((--QuoteLength) && ((isLineQuotation(start)) || (*start == ' ') || (*start == '\t'))) 
                        {
                            *p++ = *start++;
                        }
                        *p = '\0';
                        FillInQuotations = FALSE;
                    }

                    *end = saveChar;   /* restore char at end of this line */
                    if ((*end == ' ') || (*end == '\t')) 
                    {
                        end++; /* skip over a blank or tab, if we insert a linebreak instead */
                        lenToGo--; /* this blank or tab isn't counted anymore */
                    }
                    start = end;
                }
            }
        }
    }
}

/*-- function TrimHeader ---------------------------------------------
 *
 * If TrimHeaders is deactivated, or if we are decoding, then 
 * always return false (don't trim header).
 * else, if this header line should be skipped, return TRUE
 *
 * jsc 9/24/94
 */
BOOL TrimHeader (char *line)
{
	static BOOL bLastLineKept = FALSE;
    if (TrimHeaders && !CommDecoding) 
    {
        if (!IsBlankStr (line) &&
#ifdef VISUAL1521
            ( !UseVISUAL1521 || _strnicmp ("content-type:", line, 13) ) &&
            ( !UseVISUAL1521 || _strnicmp ("content-transfer-encoding:", line, 26) ) &&
			( !UseVISUAL1521 || _strnicmp ("MIME-Version:", line, 13) ) &&	 //trigger for decode processing
#endif  
            _strnicmp ("to:", line, 3) &&
            _strnicmp ("subject:", line, 8) &&
            _strnicmp ("date:", line, 5) &&
            _strnicmp ("from:", line, 5) &&
            _strnicmp ("reply-to:", line, 9) &&
            _strnicmp ("newsgroups:", line, 11) &&
            _strnicmp ("references:", line, 11) &&
            _strnicmp ("summary:", line, 8) &&
            _strnicmp ("distribution:", line, 13) &&
            _strnicmp ("message-id:", line, 11) &&  /* need this for article replies */
            _strnicmp ("followup-to:", line, 12) &&
            _strnicmp ("keywords:", line, 9) &&
            _strnicmp ("x-face:", line, 7) &&
			_strnicmp ("face: ", line, 6)) 
        {
			if (bLastLineKept && *line == ' ' && strlen(line) > 5)
				return FALSE;  //wrapped header, no trim

			bLastLineKept = FALSE;

			return TRUE;  /* matches none of above, so trim it */
        }

		bLastLineKept = TRUE;
		
    }
    return FALSE;         /* don't trim it */
}

/*-- function WasArtSeen ---------------------------------------------
 *
 *  Determines whether (according to the information in a TypGroup entry)
 *  a given article number was seen.
 *
 *  Returns ART_SEEN iff the article has been seen.
 */
char WasArtSeen(UBIGINT ArtNum, TypGroup far *GroupPtr)
{
    return GroupPtr->pSeendb->WasSeen(ArtNum) ? ART_SEEN : ART_UNSEEN;
}

/*-- function CalcNumUnread ---------------------------------------------
 *
 *  Determines number of unseen articles in a group
 *
 *  jsc 11/12/94
 */
UBIGINT CalcNumUnread(TypGroup far *GroupPtr)
{
    if (GroupPtr->pSeendb)
	{
		UBIGINT nVal = GroupPtr->pSeendb->NumUnseen();
		UBIGINT hival = GroupPtr->pSeendb->GetHighRange();
		if (hival < GroupPtr->ServerLast)
			nVal += GroupPtr->ServerLast-hival;
		
		return nVal;
	}
	else 
		return GroupPtr->ServerLast - GroupPtr->ServerFirst;
}

// find first occurance of s2 in s1 case insensitive   MDS20000410
char *stristr(const char far *s1, const char far *s2)
{
    int n, m, l, o, bfound;
    const char *c1 = NULL;
    
    n=m=l=bfound=0;
    
    while ( *(s1+n))
        n++;
    
    while ( *(s2+m))
        m++;
    
    if (m > n) return 0;
    
    m--;
    while ( l < n - m && !bfound)
    {
        if (tolower(*(s1+l)) == tolower(*(s2)))
        {
            c1 = (const char *) s1+l;
            for (o = 1; o <= m; o++)
            {
                if ( tolower( *(c1+o)) != tolower(*(s2+o)))
                {
                    break;
                }
                if ( o == m)
                    bfound = 1;
            }
        }
        if (!bfound)
            l++;    
    }
    if (bfound)
        return (char *) c1;
    else
        return 0;
     
}

#if 0
/*--- function lstrcmpnoblank ------------------------------------------
 *
 *   Like strcmp, except takes long pointers and also stops at
 *   the first blank.
 */
int lstrcmpnoblank(char far **str1, char far **str2)
{
    register char far *s1 = *str1, far * s2 = *str2;

    for (; *s1 && *s2 && *s1 != ' ' && *s2 != ' '; s1++, s2++) 
    {
        if (*s1 > *s2) 
        {
            return (1);
        }
        else if (*s1 < *s2) 
        {
            return (-1);
        }
    }
    if (*s1 == *s2) 
    {
        return (0);
    }
    else if (*s1) 
    {
        return (1);
    }
    else 
    {
        return (-1);
    }
}
#endif

void
finish_header_retrieval ()
{
  char *sSortDef;
  TypLine far *LinePtr;
  TypBlock far *BlockPtr;
  TypGroup far *GroupDoc;
  HANDLE header_handle, thread_handle;
  thread_array thread_index;
  char mybuf[MAXINTERNALLINE];
  header_p headers, hp;
  uint32 i;
  long active_headers;
  
  /* release the mouse that is captured to the usenet window */
  //ReleaseCapture ();



  CommDoc->TotalLines = CommDoc->ActiveLines;
  /* Disabled by MRR so that ActiveLines is the number of lines
   * we should display in the Group window.  Eventually, will
   * change it so that ActiveLines will count only unseen articles
   * if the user desires.
   */
  /* CommDoc->ActiveLines = 0; */
  /* Fetch this group's line in NetDoc so we can get the
   * group's name for the window's title bar.
   */
  if (LockLine (CommDoc->hParentBlock, CommDoc->ParentOffset,
                CommDoc->ParentLineID, &BlockPtr, &LinePtr)) 
  {

    SetStatbarPercent(CommDoc->hWndFrame, (int) 100, CommDoc, TRUE);
	
	GroupDoc = GetGroup(LinePtr);
    header_handle = GroupDoc->header_handle;
    thread_handle = GroupDoc->thread_handle;
    CommDoc->KilledLines = 0;
    CommDoc->SelectedLines = 0;
    
    if (header_handle) {
      headers = lock_headers (header_handle, thread_handle);
      thread_index = *((thread_array_p) ((char_p) headers - sizeof (char_p)));

      GroupDoc->max_headers = GroupDoc->total_headers;
      GroupDoc->total_headers = CommDoc->TotalLines;  // needed in ActOnArticles()
      CommDoc->SelectedLines = 0;

      if(bEnableArticleAction)
      {
		WVSetStatbarText (CommDoc->hWndFrame, IDS_GROUP_FILTERING, CommDoc, TRUE, FALSE);

        if ( !GroupDoc->pAA->IgnoreGlobalFilter() )
			g_pAction->ActOnArticles(GroupDoc, headers, TRUE);
        GroupDoc->pAA->ActOnArticles(GroupDoc, headers, FALSE);  /* Perform article actions */
        active_headers = move_killed_headers ( header_handle, thread_handle, GroupDoc->max_headers);

        CommDoc->TotalLines = active_headers;
        CommDoc->ActiveLines = active_headers;
        GroupDoc->total_headers = active_headers;
        CommDoc->KilledLines = GroupDoc->max_headers - active_headers;

      }

	  WVSetStatbarText (CommDoc->hWndFrame, IDS_GROUP_SORTING, CommDoc, TRUE, FALSE);

      if (CommDoc->TotalLines > 0)
      {
        for(i = 0; i < CommDoc->TotalLines; i++)
        {
          hp = header_elt(headers, i);
          if(ART_SELECTED & hp->SeenSelected)
            CommDoc->SelectedLines++;
        }
      }

//      iSortOption = IDM_SORT_ARTNUM;
      sSortDef = STR_SORT_ARTNUM;
      if (threadp) {
        WVSetWindowText (CommDoc->hWndFrame, IDS_GROUP_SORTING);
//      iSortOption = IDM_SORT_THREADSUB; // we're testing this as the default.
        sSortDef = STR_SORT_THREADSUB;
      }

/* Ray Dunn:99/11: No Sort Enhancement */
      if (InhibitSort)
      {
        iSortOption = IDM_DONT_SORT;
        InhibitSort = FALSE;
        WriteSortOption(iSortOption, CurrentGroup);
      }
      else
        iSortOption = ReadSortOption(sSortDef, CurrentGroup);


/*    TRACE2("WVUtil: Newsgroup <%s>, iSortOption: %d\n", CurrentGroup, iSortOption); */
      sort_by_option(headers, thread_index, threadp, CommDoc->TotalLines,
            header_handle, thread_handle);

      unlock_headers (header_handle, thread_handle);
    }
  }

  if (CommDoc->KilledLines) 
  {
	  char str2[32];
	  FormatNumber(CommDoc->TotalLines, str2);
	  FormatNumber(CommDoc->KilledLines, mybuf);
	  WVArgXSetWindowText (CommDoc->hWndFrame, IDS_GROUP_DESCRIPTIONKILLED, CurrentGroup, 
                         str2, mybuf, CurrentDescription);
  }
    else {
		FormatNumber(CommDoc->TotalLines, mybuf);
        WVArgXSetWindowText (CommDoc->hWndFrame, IDS_GROUP_DESCRIPTION, CurrentGroup, mybuf, CurrentDescription);
    }
  /* If we have information from NEWSRC on the highest-
   * numbered article previously seen, position the window
   * so the new articles can be seen without scrolling.
   */
  {
    unsigned long i;

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

      /* inside the lock, can access the GroupStruct */
      GroupDoc = GetGroup(LinePtr);
      header_handle = GroupDoc->header_handle;
      thread_handle = GroupDoc->thread_handle;
      if (header_handle) {
        headers = lock_headers (header_handle, thread_handle);

    /* skip to FIRST unseen article (jsc) */
        i = 0;
        if (CommDoc->TotalLines > 0) 
        {
          for (i = 0;
             (i < CommDoc->TotalLines && (ART_SEENORKILLED & (header_elt (headers, i))->SeenSelected));
             i++);
        }

                
        //MDS fixes so initial position line doesn't scroll off bottom of view 20000426
        if (i == CommDoc->TotalLines)
                    i--;
        CommDoc->TopLineOrd =
           (CommDoc->TotalLines > i && CommDoc->ScYLines > 0 && CommDoc->TotalLines - i < CommDoc->ScYLines - 1) ? 
           (CommDoc->TotalLines - CommDoc->ScYLines) : (i > CommDoc->ScYLines && CommDoc->TotalLines > CommDoc->ScYLines) ?
            i - CommDoc->ScYLines +1 : 0; 

        CommDoc->ActiveLineID = i;
        ClearFlags(CommDoc, ThumbTracking);
        //CommDoc->ThumbTracking = FALSE;   /* if thumb tracking, release it */
    
        unlock_headers (header_handle, thread_handle);
        if (ConfigJumpGroup && (FlagJumpGroup & JUMPFLAG_GROUP)) 
        {
          if (i >= CommDoc->TotalLines) 
          {
            RetrieveNextGroup( BlockPtr, LinePtr, IDS_WVART_THATS_ALL, FALSE );
          } 
          else 
          {
            FlagJumpGroup = JUMPFLAG_ARTICLE;
          }
        }

      }
    }
  }

  SendMessage (CommDoc->hWndFrame, (UINT) WM_COMMAND, (WPARAM) ID_RETRIEVE_COMPLETE, 0L);
  InvalidateRect (CommDoc->hDocWnd, NULL, TRUE);
  UpdateWindow (CommDoc->hDocWnd);
  if (FlagJumpGroup & JUMPFLAG_ARTICLE) 
  {
    PostMessage (CommDoc->hDocWnd, WM_CHAR, '\r', 0);
    FlagJumpGroup = 0;
  }

}

/*
 * Look through the MAIL or Post edits and return the edit with
 * matching window handle Consider - centralising initial window
 * location in wvmail and wndpost using a single array (save passing
 * structure and size into this module)
 *
 */

WndEdit * getWndEdit (WndEdit * WndEdits, HWND hWnd, int numEntries)
{
  int ih;

  for (ih = 0; ih < numEntries; ih++) 
  {
    if (WndEdits[ih].hWnd == hWnd) 
	{
      return &WndEdits[ih];
    }
  }

  //MessageBox(0,"getWndEditFound Nothing","mrb debug", MB_OK | MB_ICONHAND);

  return (WndEdit *) NULL;
}

WndEdit * GetComposeWnd (HWND hWnd)
{
  WndEdit *compWnd;

  compWnd = getWndEdit (WndPosts, hWnd, MAXPOSTWNDS);
  if (!compWnd) 
  {
    compWnd = getWndEdit (WndMails, hWnd, MAXMAILWNDS);
  }
  return compWnd;
}

/* ------------------------------------------------------------------------
 * Replace any white space at end of string with NULL's
 * JSC 11/1/93
 */
void RemoveTrailingWhiteSpace (char *str)
{
    register size_t i;

    for (i = strlen (str) - 1; i > 0 && isspace ((unsigned char)str[i]); i--)
        str[i] = '\0';
}
/* ------------------------------------------------------------------------
 * Subsitute all occurences of character "oldchar" in string with
 * character "newchar" in string  (JD 3/27/97
 */
void SubsituteCharInString(char *str, char oldchar, char newchar)
{
    register size_t i;

    for (i = strlen (str) - 1; i > 0; i--)
    {
        if (str[i] == oldchar) 
            str[i] = newchar;
    }
}

/*------------------------------------------------------------------------------
 * IsBlankStr
 * Returns true if the string is entirely whitespace, else false
 * JSC 12/6/93
 */
BOOL IsBlankStr (const char *temp)
{
    register const char *ptr;
    for (ptr = temp; *ptr; ptr++)
        if (!isspace ((unsigned char)*ptr))
            return (FALSE);
    return (TRUE);
}

/*------------------------------------------------------------------------------
 * isnumber
 * Returns true if the string is a all digits
 * JSC 12/6/93
 */
BOOL isnumber (char *str)
{
    char *ptr;

    for (ptr = str; *ptr != '\0'; ptr++)
        if (!isdigit (*ptr))
            return (FALSE);
    return (TRUE);
}

/* ------------------------------------------------------------------------
 * strntcpy is strntcpy, but also terminates the dest str
 * jsc 9/28/94
 */
char * strntcpy (char *dest, const char *src, int len)
{
    register char *d = dest;
    register const char *s = src;
    register int l = 0;

    while (l < len && (*d++ = *s++)) 
    {
        l++;
    }
    if (l == len) 
    {
        *d = '\0';
    }
    return dest;
}

/////////////////////////////////////////////////////////////////////////////
//get listview current select or -1
int GetListSelectedItem(HWND hList)
{
	if (!IsWindow(hList))
		return -1;

	UINT flags = MAKELPARAM(( UINT) LVNI_SELECTED, 0);
	int nSel = ListView_GetNextItem( hList, -1, flags );

	return nSel;

}
int SetListSelectedItem(HWND hList, int nSel, BOOL bSel /*= TRUE*/)
{
	if (!IsWindow(hList))
		return -1;

	for (int i = 0; i < ListView_GetItemCount(hList); i++)
	{
		LVITEM item = {0};
		item.iItem = i;
		item.state = (bSel && nSel == i) ? (LVIS_SELECTED | LVIS_FOCUSED) : 0;
		item.mask = LVIF_STATE;
		item.stateMask = LVIS_SELECTED | LVIS_FOCUSED;

		SendMessage(hList, LVM_SETITEMSTATE, i, (LPARAM) &item);
	}

	return 1;

}

/* ------------------------------------------------------------------------
 *    Open the common font dialog
 *      Place resulting selection name and size in face,style and size
 *      Note: to select a printer font, send style as "Printer"
 *      printer font selection ignores any chosen style
 *      (JSC 1/9/94)
 */

 //custom font picker callback
UINT_PTR CALLBACK FCHookProc( HWND hdlg, UINT uiMsg, WPARAM wParam, LPARAM lParam )
{
	#define MAXFONTSTR 32

	typedef struct tagFONTITEM
	{
		UINT	TextID;
		char	*Face;
		int		*Size;
		char	*Style;
		char	curFace[MAXFONTSTR+1];
		int		curSize;
		char	curStyle[MAXFONTSTR+1];
	}FONTITEM;

	//WinVn System
	//Composition Text
	//Group/Article List
	//Article Text
	//Status Text
	//Print Text
	//char PrinterStyle[MAXFONTSTR] = {"Printer"};
	static FONTITEM MyFontItems[] = {
		{IDS_FONT_SYSTEM, WinVnFontFace, &WinVnFontSize, WinVnFontStyle},
		{IDS_FONT_COMPOSITION, CompositionFontFace, &CompositionFontSize, CompositionFontStyle},
		{IDS_FONT_LIST, ListFontFace, &ListFontSize, ListFontStyle},
		{IDS_FONT_ART, ArticleFontFace, &ArticleFontSize, ArticleFontStyle},
		{IDS_FONT_STATUS, StatusFontFace, &StatusFontSize, StatusFontStyle},
		{IDS_FONT_PRINT, PrintFontFace, &PrintFontSize }

		};
		

	HWND hList = NULL;
	TCHAR buf[MAX_PATH];
	WORD msg_type;

    hList = GetDlgItem(hdlg,IDC_LIST_FONTS);
	static int nMax = sizeof(MyFontItems)/ sizeof(MyFontItems[0]);
	static int nLastSel = -1;

    switch (uiMsg) 
	{
		case WM_INITDIALOG:
		{
			nLastSel = -1; //being static, this is only init'd once by the compiler, we need it init'd each time

			//copy original fonts in
			for (int i = 0; i < nMax; i++)
			{
				strntcpy(MyFontItems[i].curFace, MyFontItems[i].Face, MAXFONTSTR);
				if (MyFontItems[i].Style)
					strntcpy(MyFontItems[i].curStyle, MyFontItems[i].Style, MAXFONTSTR);
				MyFontItems[i].curSize = *(MyFontItems[i].Size);
			}
			{
				CheckRadioButton (hdlg, IDC_RB_CTDEFAULT,
								IDC_RB_CTON, IDC_RB_CTDEFAULT + UseClearType );
			}

			if (hList)
			{
			
				LV_COLUMN lvColumn;
				RECT rCtrl;
				DWORD dwStype = ListView_GetExtendedListViewStyle(hList);
				dwStype |= LVS_EX_FULLROWSELECT;
				ListView_SetExtendedListViewStyle(hList, dwStype);

				ListView_DeleteAllItems (hList);

				GetWindowRect( hList, &rCtrl);

				// Initialize the columns in the list view.
				lvColumn.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT;
				lvColumn.fmt = LVCFMT_LEFT;
				lvColumn.cx = rCtrl.right-rCtrl.left - 8;
				lvColumn.pszText = "WinVN Item";

				ListView_InsertColumn (hList, 0, &lvColumn);

				ListView_SetItemCount(hList, nMax);
				
				for (int i = 0; i < nMax; i++)
				{
					LoadString(hInst, MyFontItems[i].TextID, buf, 32);
					
					LVITEM item = {0};
					item.iItem = i;
					item.mask = LVIF_TEXT | LVIF_PARAM;
					item.pszText = buf;
					item.cchTextMax = 32;
					item.lParam = (LPARAM) &MyFontItems[i];

					ListView_InsertItem(hList, &item);

				}

				SetListSelectedItem(hList, 0);		
			}


		}
		break;
		case WM_DESTROY:
		{
		/*
			if (hfont != NULL) 
			{
				DeleteObject(hfont);
				hfont = NULL;
			}
		*/

		}
		break;
		
		case WM_NOTIFY:
		{
			LPNMHDR pnmh = (LPNMHDR) lParam;
			if ( pnmh->code == LVN_ITEMCHANGED  && pnmh->idFrom == IDC_LIST_FONTS   )
			{
				
				NMLISTVIEW* pnmlv = (NMLISTVIEW*)lParam;	
				if ((pnmlv->uNewState & LVIS_SELECTED) && !(pnmlv->uOldState & LVIS_SELECTED))
				{
					int nSel = GetListSelectedItem(hList);

					if (nLastSel >= 0 && nLastSel < nMax)
					{
						//virtual apply
						if (hList)
						{
							LOGFONT lf;
							ZeroMemory(&lf, sizeof(LOGFONT));

							SendMessage(hdlg, WM_CHOOSEFONT_GETLOGFONT , 0, (LPARAM) &lf);
							HWND hCombo = GetDlgItem(hdlg, 1138);
							
							int nSize = labs( MulDiv(lf.lfHeight, 72, ScreenYPixels) );
							if (hCombo)
							{
								TCHAR szBuf[100];
								ComboBox_GetText(hCombo, szBuf, 100);
								nSize = _ttoi(szBuf);
							}
							
							strntcpy(MyFontItems[nLastSel].curFace, lf.lfFaceName, MAXFONTSTR);

							*buf = _T('\0');
							if (lf.lfWeight >= FW_BOLD)
								_tcscpy(buf, _T("Bold"));
									
							if (lf.lfItalic)
							{
								if (*buf)
									_tcscat(buf, _T(" "));
								_tcscat(buf, _T("Italic"));
							}
							if (!*buf)
								_tcscpy(buf, _T("Regular"));

							_tcsncpy(MyFontItems[nLastSel].curStyle, buf, MAXFONTSTR);

							MyFontItems[nLastSel].curSize = nSize;
						
						}

						

					}
					if ( nSel >= 0 && nSel < nMax )
					{
						SetFocus(hList);
						_itot(MyFontItems[nSel].curSize, buf, 10);

						//Apparently WM_CHOOSEFONT_SETLOGFONT doesn't work
						LOGFONT	lf;
						strntcpy(lf.lfFaceName, MyFontItems[nSel].curFace,  MAXFONTSTR);
						lf.lfHeight = -MulDiv (MyFontItems[nSel].curSize, ScreenYPixels, 72);
						lf.lfWeight = strstr(MyFontItems[nSel].curStyle, "Bold") ? FW_BOLD : FW_NORMAL;
						lf.lfItalic = strstr(MyFontItems[nSel].curStyle, "Italic") != NULL;
						SendMessage(GetParent(hdlg), WM_CHOOSEFONT_SETLOGFONT , 0, (LPARAM) &lf);						

						//we do this manually

						SendMessage(GetDlgItem(hdlg, 1136), CB_SELECTSTRING, (WPARAM)-1, (LPARAM)MyFontItems[nSel].curFace);
						SendMessage(hdlg, WM_COMMAND, MAKEWPARAM(1136, CBN_SELCHANGE), (LPARAM)GetDlgItem(hdlg, 1136));

						TRACE("MyFont[nSel].curStyle = %s\r\n", *MyFontItems[nSel].curStyle ? MyFontItems[nSel].curStyle : "NULL" );
						SendMessage(GetDlgItem(hdlg, 1137), CB_SELECTSTRING, (WPARAM)-1,(LPARAM) (*MyFontItems[nSel].curStyle ? MyFontItems[nSel].curStyle : "Regular") );
						SendMessage(hdlg, WM_COMMAND, MAKEWPARAM(1137, CBN_SELCHANGE), (LPARAM)GetDlgItem(hdlg, 1137));

						SendMessage(GetDlgItem(hdlg, 1138), CB_SELECTSTRING, (WPARAM)-1, (LPARAM)buf);
						SendMessage(hdlg, WM_COMMAND, MAKEWPARAM(1138, CBN_SELCHANGE), (LPARAM)GetDlgItem(hdlg, 1138));
						nLastSel = nSel;
					}
				} 

			}


		}
		break;
		case WM_MEASUREITEM:
		{
			if (wParam == IDC_LB_COLORITEMS)
			{
				LPMEASUREITEMSTRUCT lpMeasureItem = (LPMEASUREITEMSTRUCT) lParam;
				/*
				static TCHAR szBuff[256];
				//lpMeasureItem->CtlType = ODT_LISTVIEW

				//lpMeasureItem->CtlID; 
				//lpMeasureItem->itemID; 
				//lpMeasureItem->itemWidth; 
				//lpMeasureItem->itemHeight; 
				//ULONG_PTR lpMeasureItem->itemData;
				
				LVITEM lvi;
				LPCOLORITEM pColorItem;
				
				//GetClientRect(hList, &rcClient);
				
				lvi.mask=LVIF_TEXT | LVIF_STATE | LVIF_PARAM;
				lvi.iItem=0;
				lvi.iSubItem=0;
				lvi.pszText = (LPTSTR) szBuff;
				lvi.cchTextMax=sizeof(szBuff);
				lvi.stateMask=0xFFFF;		// get all state flags
				ListView_GetItem(hList, &lvi);
				*/
				SIZE  sz;
				HDC hDC = GetDC(hList);

				if (hDC)
				{
					//HFONT hOldFont = (HFONT) SelectObject(hDC, hfont);
					
					GetTextExtentPoint(hDC, "ABCDEFGHIJK", 11, &sz);

					//SelectObject(hDC, hOldFont);
					ReleaseDC(hList, hDC);
					lpMeasureItem->itemWidth = sz.cx;
					lpMeasureItem->itemHeight = sz.cy +1;
				}

				
				return 1;
			}

			
		}
		break;

		case WM_COMMAND:
			msg_type = LOWORD(wParam);
			switch (msg_type) 
			{
				case IDC_PB_RESET:
				{
					
					typedef struct tagDEFFONTITEM
					{
						const char *Face;
						int		 Size;
						const char	*Style;

					}DEFFONTITEM;					
					
					#include "defaults.h"
					//a place to store default values
					DEFFONTITEM OrigFontItems[] = {
						{DEF_WinVnFontFace, DEF_WinVnFontSize, DEF_WinVnFontStyle},
						{DEF_CompositionFontFace, DEF_CompositionFontSize, DEF_CompositionFontStyle},
						{DEF_ListFontFace, DEF_ListFontSize, DEF_ListFontStyle},
						{DEF_ArticleFontFace, DEF_ArticleFontSize, DEF_ArticleFontStyle},
						{DEF_StatusFontFace, DEF_StatusFontSize, DEF_StatusFontStyle},
						{DEF_PrintFontFace, DEF_PrintFontSize, DEF_StatusFontStyle}

						};
					//copy default settings	to current
					for (int i = 0; i < nMax; i++)
					{
						strntcpy(MyFontItems[i].curFace,  OrigFontItems[i].Face, MAXFONTSTR);
						strntcpy(MyFontItems[i].curStyle, OrigFontItems[i].Style,MAXFONTSTR);
						MyFontItems[i].curSize = OrigFontItems[i].Size;
					}
					nLastSel = -1; //don't save old values when selecting new
					SetListSelectedItem(hList, 0);



				}
				break;
				case IDOK:
				{		
					
					
					//Save current changes
					int nOldType = UseClearType;
					UseClearType =  IsDlgButtonChecked (hdlg, IDC_RB_CTDEFAULT) ? 0 :
                                    IsDlgButtonChecked (hdlg, IDC_RB_CTOFF) ? 1 :
                                    IsDlgButtonChecked (hdlg, IDC_RB_STANDARD) ? 2 : 3;
                                        
					BOOL bTypeChanged = (UseClearType != nOldType);  
					int nSel = GetListSelectedItem(hList);
					
					if (nSel >= 0 && nSel < nMax && hList)
					{

						LOGFONT lf;
						
						int nSel = GetListSelectedItem(hList);
						ZeroMemory(&lf, sizeof(LOGFONT));


						SendMessage(hdlg, WM_CHOOSEFONT_GETLOGFONT , 0, (LPARAM) &lf);
						HWND hCombo = GetDlgItem(hdlg, 1138);
						
						int nSize = labs( MulDiv(lf.lfHeight, 72, ScreenYPixels) );
						if (hCombo)
						{
							TCHAR szBuf[100];
							ComboBox_GetText(hCombo, szBuf, 100);
							nSize = _ttoi(szBuf);
						}
						
						strntcpy(MyFontItems[nSel].curFace, lf.lfFaceName, MAXFONTSTR);
						*buf = _T('\0');
						
						if (lf.lfWeight >= FW_BOLD)
							_tcscpy(buf, _T("Bold"));
								
						if (lf.lfItalic)
						{
							if (*buf)
								strcat(buf, _T(" "));
							_tcscat(buf, _T("Italic"));
						}
						if (!*buf)
							_tcscpy(buf, _T("Regular"));

						strntcpy(MyFontItems[nSel].curStyle, buf, MAXFONTSTR);

						MyFontItems[nSel].curSize = nSize;


					}

					DWORD dwMask = 1;
					WinVNFontChange = 0;
					//copy current settings	to system
					int i;
					for (i = 0; i < nMax; i++)
					{
						
						//cleartype change requires all fonts to be updated
						if (bTypeChanged && MyFontItems[i].TextID != IDS_FONT_PRINT)
							WinVNFontChange |= dwMask;

						if (_strnicmp(MyFontItems[i].Face, MyFontItems[i].curFace, MAXFONTSTR))
						{
							WinVNFontChange |= dwMask;
							strntcpy(MyFontItems[i].Face, MyFontItems[i].curFace, MAXFONTSTR);
						}

						if ( MyFontItems[i].Style && _strnicmp(MyFontItems[i].Style, MyFontItems[i].curStyle, MAXFONTSTR))
						{
							WinVNFontChange |= dwMask;
							strntcpy(MyFontItems[i].Style, MyFontItems[i].curStyle, MAXFONTSTR);
						}
						if (*(MyFontItems[i].Size) != MyFontItems[i].curSize)
						{
							WinVNFontChange |= dwMask;
							*(MyFontItems[i].Size) = MyFontItems[i].curSize;
						}

						dwMask = dwMask << 1;
					}

					//PostMessage(hdlg, WM_COMMAND, IDCANCEL, 0);
				}
				break;
				
				default:
				break;
			}

    }
    return 0;

}

BOOL AskForFont (HWND hParentWnd, char *face, int *size, char *style)
{
    LOGFONT lf;
    CHOOSEFONT cf;
    HDC hDC;

    memset (&lf, 0, sizeof (LOGFONT));

	if (face && style)
	{
		strcpy (lf.lfFaceName, face);
		/* convert points to logical units (1 pt = 1/72 inch) */
		/* For printer fonts, use ScreenYPixels here anyway - the choosefont */
		/* dialog appears to require the lfHeight to be in screen units */
		/* we will convert point size to PrinterUnits in InitPrinterFonts() */
		lf.lfHeight = -MulDiv (*size, ScreenYPixels, 72);
	}

    memset (&cf, 0, sizeof (CHOOSEFONT));
    cf.lStructSize = sizeof (CHOOSEFONT);
    cf.hwndOwner = hParentWnd;
    cf.lpLogFont = &lf;
	if (!face || !style)
	{
        cf.nFontType = SCREEN_FONTTYPE;
        cf.Flags = CF_BOTH  /*| CF_INITTOLOGFONTSTRUCT | CF_USESTYLE */| CF_FORCEFONTEXIST | CF_ENABLETEMPLATE | CF_ENABLEHOOK;
        cf.lpszStyle = style;
		cf.lpfnHook = &FCHookProc;
		cf.lpTemplateName = MAKEINTRESOURCE (IDD_FONTS);
		cf.hDC = GetPrinterDC (hParentWnd);

	}
    else if (!_stricmp (style, "Printer")) 
    {
        cf.nFontType = PRINTER_FONTTYPE;
        hDC = GetPrinterDC (hParentWnd);
        cf.hDC = hDC;
        cf.Flags = CF_PRINTERFONTS | CF_INITTOLOGFONTSTRUCT | CF_FORCEFONTEXIST;
    }
    else 
    {
        cf.nFontType = SCREEN_FONTTYPE;
        cf.Flags = CF_SCREENFONTS | CF_INITTOLOGFONTSTRUCT | CF_USESTYLE | CF_FORCEFONTEXIST;
        cf.lpszStyle = style;
    }
    try
    {
		if (!ChooseFont (&cf))
			return (FAIL);
	}
	catch (...)
	{
		return (FAIL);
	}
    /*      if (!_stricmp (style, "Printer"))      // commented out JD 6/17/94 */
    /*         ReleaseDC (NetDoc.hDocWnd, hDC);  */

    /*      if (!_stricmp (style, "Printer")) */
    /*         DeletePrinterDC (hDC); */

    if (size)
		*size = cf.iPointSize / 10;   /* iPointSize is in tenths of a point */

    if (face)
		strcpy (face, lf.lfFaceName);
    return (SUCCESS);
}

/* ------------------------------------------------------------------------
 *    Open the common color dialog
 *      (JSC 1/9/94)
 */

LRESULT DoListDraw( HWND, LPNMLVCUSTOMDRAW pLVCD )
{

    switch(pLVCD->nmcd.dwDrawStage) 
	{
		case CDDS_PREPAINT :
			return CDRF_NOTIFYITEMDRAW;

		case CDDS_ITEMPREPAINT:
		{	
			pLVCD->clrText = RGB(0,0,0);;

			LPCOLORITEM pcItem = (LPCOLORITEM) pLVCD->nmcd.lItemlParam;
			if (pcItem)
			{
				pLVCD->clrText = pcItem->crCurrent; 
				
				int nVal= (GetRValue(pcItem->crCurrent) + GetGValue(pcItem->crCurrent) + GetBValue(pcItem->crCurrent))/3;
				if (nVal < 127)
					pLVCD->clrTextBk  = RGB(255,255,255);
				else
					pLVCD->clrTextBk  = RGB(0,0,0);

			}

		}

		/* At this point, you can change the background colors for the item
		and any subitems and return CDRF_NEWFONT. If the list-view control
		is in report mode, you can simply return CDRF_NOTIFYSUBITEMDRAW
		to customize the item's subitems individually */

		return CDRF_NEWFONT;
		//  or return CDRF_NOTIFYSUBITEMDRAW;

		case CDDS_SUBITEM | CDDS_ITEMPREPAINT:

		/* This notification is received only if you are in report mode and
		returned CDRF_NOTIFYSUBITEMDRAW in the previous step. At
		this point, you can change the background colors for the
		subitem and return CDRF_NEWFONT.*/


		return CDRF_DODEFAULT;    
		}

	   return 0L;

}


// offsets for first and other columns
#define OFFSET_FIRST	2
#define OFFSET_OTHER	6

void DrawListItem(HWND hCtrl, LPDRAWITEMSTRUCT lpDrawItemStruct)
{

	HDC hDC = lpDrawItemStruct->hDC;
	RECT rcItem;
	static _TCHAR szBuff[MAX_PATH];

	int nItem = lpDrawItemStruct->itemID;
	BOOL bFocus = (GetFocus()==hCtrl);
	COLORREF clrTextSave = 0, clrBkSave = 0;
	LPCTSTR pszText;
	RECT rcAllLabels = {0};
	RECT rcLabel = {0};
	RECT rcClient = {0};
	LONG_PTR wndStyle;
	BOOL bSelected;
	LVITEM lvi;
	BOOL m_bClientWidthSel = TRUE;
	LPCOLORITEM pColorItem;
	
	GetClientRect(hCtrl, &rcClient);
	CopyRect(&rcItem, &lpDrawItemStruct->rcItem);

	int m_cxClient = RectWidth(rcClient) - 5;
	// get item data

	
	lvi.mask=LVIF_TEXT | LVIF_STATE | LVIF_PARAM;
	lvi.iItem=nItem;
	lvi.iSubItem=0;
	lvi.pszText=szBuff;
	lvi.cchTextMax=sizeof(szBuff);
	lvi.stateMask=0xFFFF;		// get all state flags
	ListView_GetItem(hCtrl, &lvi);

	wndStyle =  GetWindowLongPtr(hCtrl, GWL_STYLE);
	bSelected=(bFocus || (wndStyle & LVS_SHOWSELALWAYS)) && lvi.state & LVIS_SELECTED;
	bSelected=bSelected || (lvi.state & LVIS_DROPHILITED);

	pColorItem = (LPCOLORITEM) lvi.lParam;

	if (lpDrawItemStruct->itemState == ODS_DEFAULT)
	{
		//using this flag to create drag images, don't want focus or bkgnd
		bSelected = FALSE;
		bFocus = FALSE;
	}

	// set colors if item is selected



	ListView_GetItemRect(hCtrl, nItem, &rcAllLabels,LVIR_BOUNDS);
	ListView_GetItemRect(hCtrl,nItem, &rcLabel,LVIR_LABEL);
	
	rcAllLabels.left=rcLabel.left;
	
	if(m_bClientWidthSel && rcAllLabels.right<m_cxClient)
		rcAllLabels.right=m_cxClient;

	rcAllLabels.bottom++;

	if(bSelected)
	{
		clrTextSave = SetTextColor(hDC, GetSysColor(COLOR_HIGHLIGHTTEXT) );
		clrBkSave = SetBkColor( hDC, GetSysColor(COLOR_HIGHLIGHT));
		FillRect(hDC, &rcAllLabels,GetSysColorBrush( COLOR_HIGHLIGHT ));

	}
	else
		FillRect(hDC, &rcAllLabels,GetSysColorBrush(COLOR_WINDOW));


	ListView_GetItemRect(hCtrl, nItem, &rcItem,LVIR_LABEL);
    pszText = szBuff;

	//create a color well that shows current color
	if (pColorItem)
	{
		RECT rcWell;
		rcWell = rcItem;
		rcWell.right = rcWell.left + RectHeight(rcItem); 
		InflateRect( &rcWell, -1, -1);
		
		DrawFrameControl( hDC, &rcWell, DFC_BUTTON, DFCS_BUTTONPUSH | DFCS_PUSHED);
		
		InflateRect( &rcWell, -2, -2);	
		
		HBRUSH hbr = CreateSolidBrush( pColorItem->crCurrent);
		
		FillRect(hDC, &rcWell, hbr);
		DeleteObject(hbr);

		rcItem.left += RectWidth(rcWell) + 6;

	}


	rcLabel=rcItem;
	rcLabel.bottom--;

	DrawText(hDC, pszText,-1, &rcLabel, DT_LEFT | DT_SINGLELINE | DT_NOPREFIX | DT_NOCLIP | DT_VCENTER | DT_END_ELLIPSIS);

// draw labels for extra columns

	LV_COLUMN lvc;
	lvc.mask=LVCF_FMT | LVCF_WIDTH;

	// Add back in the state image offset
	// now that the first column is drawn
	
	//MDS this is wrong (see above)
	//rcItem.right+=m_cxStateImageOffset;

	for(int nColumn=1; ListView_GetColumn(hCtrl, nColumn,&lvc); nColumn++)
	{
		rcItem.left=rcItem.right;

		rcItem.right+=lvc.cx;

		LVITEM lvItem = {0};
		lvItem.iSubItem = nColumn;
		lvItem.mask = LVIF_TEXT;
		lvItem.pszText = szBuff;
		lvItem.cchTextMax = sizeof(szBuff);

		int nRetLen = (int) SendMessage(hCtrl, LVM_GETITEMTEXT, (WPARAM) nItem, (LPARAM) &lvItem);

		//int nRetLen = ListView_GetItemText((hCtrl, nItem,nColumn, szBuff, sizeof(szBuff));
		
		if (nRetLen==0) 
			continue;

        pszText = szBuff;

		UINT nJustify=DT_LEFT;

		if(pszText==szBuff)
		{
			switch(lvc.fmt & LVCFMT_JUSTIFYMASK)
			{
			case LVCFMT_RIGHT:
				nJustify=DT_RIGHT;
				break;
			case LVCFMT_CENTER:
				nJustify=DT_CENTER;
				break;
			default:
				break;
			}
		}

		rcLabel=rcItem;
		rcLabel.left+=OFFSET_OTHER;
		rcLabel.right-=OFFSET_OTHER;
		rcLabel.bottom--;

		DrawText(hDC, pszText,-1, &rcLabel,nJustify | DT_SINGLELINE | DT_NOPREFIX | DT_NOCLIP | DT_VCENTER | DT_END_ELLIPSIS);
	}

// draw focus rectangle if item has focus

	if(lvi.state & LVIS_FOCUSED && bFocus)
	{
		rcAllLabels.bottom--;
		DrawFocusRect(hDC, &rcAllLabels);
	}

// set original colors if item was selected

	if(bSelected)
	{
	    SetTextColor(hDC, clrTextSave);
		SetBkColor(hDC, clrBkSave);
	}

}



BOOL WarnColors (HWND hWnd, COLORREF c1, COLORREF c2, UINT nIDMesg)
{
	if (c1 == c2) 
	{
		TCHAR szBuf[80];

		LoadString(hInst, nIDMesg, szBuf, 79);
		
		WVArgMessageBox (hWnd, IDS_WINVN_SAME_COLOR, IDS_WINVN_COLOR, MB_OK | MB_ICONINFORMATION, szBuf);
		return TRUE;
	}
	return FALSE;
}
typedef enum eColors { COLOR_SUBSCRIBED = 0,
					   COLOR_UNSUBSCRIBED, 
					   COLOR_SEEN,
					   COLOR_UNSEEN,
					   COLOR_KILLED,
					   COLOR_ERROR,
					   COLOR_PREVIEW,
					   COLOR_LIST_BACKGROUND,
					   COLOR_ARTICLE_TEXT,
					   COLOR_ARTICLE_BACKGROUND,
					   COLOR_STATUS_TEXT,
					   COLOR_STATUS_BACKGROUND
					   };     
 //custom color callback
UINT_PTR CALLBACK CCHookProc( HWND hdlg, UINT uiMsg, WPARAM wParam, LPARAM lParam )
{
	static COLORITEM MyColorItems[] = { 
		{IDM_COLOR_SUBSCRIBED, IDS_COLOR_SUBSCRIBED, &NetSubscribedColor, NetSubscribedColor},
		{IDM_COLOR_UNSUBSCRIBED, IDS_COLOR_UNSUBSCRIBED, &NetUnSubscribedColor, NetUnSubscribedColor},
		{IDM_COLOR_SEEN, IDS_COLOR_SEEN, &ArticleSeenColor, ArticleSeenColor},
		{IDM_COLOR_UNSEEN, IDS_COLOR_UNSEEN, &ArticleUnSeenColor, ArticleUnSeenColor},
		{IDM_COLOR_KILLED, IDS_COLOR_KILLED, &ArticleKilledColor, ArticleKilledColor},
		{IDM_COLOR_ERROR, IDS_COLOR_ERROR, &ArticleErrorColor, ArticleErrorColor},
		{IDS_COLOR_PREVIEW, IDS_COLOR_PREVIEW, &ArticlePreviewColor, ArticlePreviewColor},
		{IDM_COLOR_LIST_BACKGROUND, IDS_COLOR_LIST_BACKGROUND,&ListBackgroundColor, ListBackgroundColor},
		{IDM_COLOR_ARTICLE_TEXT, IDS_COLOR_ARTICLE_TEXT, &ArticleTextColor, ArticleTextColor},
		{IDM_COLOR_ARTICLE_BACKGROUND, IDS_COLOR_ARTICLE_BACKGROUND, &ArticleBackgroundColor, ArticleBackgroundColor},
		{IDM_COLOR_STATUS_TEXT, IDS_COLOR_STATUS_TEXT, &StatusTextColor, StatusTextColor}, 
		{IDM_COLOR_STATUS_BACKGROUND, IDS_COLOR_STATUS_BACKGROUND, &StatusBackgroundColor, StatusBackgroundColor},
		};
	static UINT uSetID = RegisterWindowMessage(SETRGBSTRING);
	static UINT uColorOKID = RegisterWindowMessage(COLOROKSTRING); 
	static int nMax = sizeof(MyColorItems)/ sizeof(MyColorItems[0]);

	HWND hList = NULL;
	TCHAR buf[MAX_PATH];
	WORD msg_type;
	static BOOL bWantQuit = FALSE;

	//Microsoft struct found on internet
	#define COLORPROP  (LPCTSTR) 0xA000L 
	#define COLORBOXES           64 
	typedef struct { 
		UINT           ApiType; 
		LPCHOOSECOLOR  pCC; 
		HANDLE         hLocal; 
		HANDLE         hDialog; 
		HPALETTE       hPal; 
		DWORD          currentRGB; 
		WORD           currentHue; 
		WORD           currentSat; 
		WORD           currentLum; 
		WORD           nHueWidth; 
		WORD           nSatHeight; 
		WORD           nLumHeight; 
		WORD           nCurMix; 
		WORD           nCurDsp; 
		WORD           nCurBox; 
		WORD           nHuePos; 
		WORD           nSatPos; 
		WORD           nLumPos; 
		RECT           rOriginal; 
		RECT           rRainbow; 
		RECT           rLumScroll; 
		RECT           rLumPaint; 
		RECT           rCurrentColor; 
		RECT           rNearestPure; 
		RECT           rColorSamples; 
		BOOL           bFoldOut; 
		DWORD          rgbBoxColor[COLORBOXES]; 
	#ifdef UNICODE 
		LPCHOOSECOLORA pCCA; 
	#endif 
	} COLORINFO; 

    hList = GetDlgItem(hdlg,IDC_LB_COLORITEMS);
	if (uiMsg  == uColorOKID)
	{
	
		LPCHOOSECOLOR lpChooseColor = (LPCHOOSECOLOR) lParam;
		TRACE("Color is RGB(%d,%d,%d)\r\n", GetRValue(lpChooseColor->rgbResult), GetGValue(lpChooseColor->rgbResult), GetBValue(lpChooseColor->rgbResult));
	
		int nSel = GetListSelectedItem(hList);
		if (nSel >= 0 &&  nSel < nMax)
		{
			MyColorItems[nSel].crCurrent = lpChooseColor->rgbResult;
			InvalidateRect(	hList, NULL, FALSE);
		}
		//we want to set multiple colors, so IDOK always rejected	
		return 1; //reject
	}



    switch (uiMsg) 
	{
		case WM_INITDIALOG:
		{
			
			bWantQuit = FALSE;
			/*
			//create a fixed width font for the editbox
			hfont = CreateFont(-12,-7,0,0,400,0,0,0,0,0,0, DRAFT_QUALITY,
							   FIXED_PITCH|FF_MODERN,NULL);
			if (hfont) 
			{
				SendDlgItemMessage(hdlg,IDC_LB_COLORITEMS, WM_SETFONT,
					(WPARAM)hfont,MAKELPARAM(TRUE,0));
			}
			*/
			HDC hDC = GetDC(hdlg);
			if (hDC)
			{
				int nBits = GetDeviceCaps(hDC, BITSPIXEL);
				if (nBits == 8)
				{
					COLORINFO *pColor = (COLORINFO*) GetProp(hdlg, COLORPROP);
					if (pColor)
					{
						pColor->hPal = CreateHalftonePalette(hDC);
						SelectPalette (hDC, pColor->hPal, FALSE) ;
						RealizePalette (hDC) ;
					}
				}
				ReleaseDC(hdlg, hDC);
				hDC = NULL;
			}
			
			//copy original colors in
			for (int i = 0; i < nMax; i++)
			{
				MyColorItems[i].crCurrent = *MyColorItems[i].pcrOriginal;
			}
			if (hList)
			{
			
				DWORD dwStype = ListView_GetExtendedListViewStyle(hList);
				dwStype |= LVS_EX_FULLROWSELECT;
				ListView_SetExtendedListViewStyle(hList, dwStype);

				ListView_DeleteAllItems (hList);
				LV_COLUMN lvColumn;

				RECT rCtrl;
				GetWindowRect( hList, &rCtrl);

					// Initialize the columns in the list view.
				  lvColumn.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT;
				  lvColumn.fmt = LVCFMT_LEFT;
				  lvColumn.cx = rCtrl.right-rCtrl.left - 8;
				  lvColumn.pszText = "WinVN Item";

				ListView_InsertColumn (hList, 0, &lvColumn);


				int nCount = sizeof(MyColorItems)/ sizeof(MyColorItems[0]);
				ListView_SetItemCount(hList, nCount);
				
				for (int i = 0; i < nCount; i++)
				{
					LoadString(hInst, MyColorItems[i].TextID, buf, 32);
					
					LVITEM item = {0};
					item.iItem = i;
					item.mask = LVIF_TEXT | LVIF_PARAM;
					item.pszText = buf;
					item.cchTextMax = 32;
					item.lParam = (LPARAM) &MyColorItems[i];

					ListView_InsertItem(hList, &item);
					//ListBox_SetItemData(hCombo, nID, 5);
				}

				SetListSelectedItem(hList, 0);
				CheckDlgButton(hdlg, IDC_CB_INVERSE, UseInverseSelections ? 1 : 0);
			
			}


		}
		break;
		case WM_DESTROY:
		{
		/*
			if (hfont != NULL) 
			{
				DeleteObject(hfont);
				hfont = NULL;
			}
		*/

		}
		break;

		case WM_LBUTTONUP:
		{
			
			//#define COMDLG_ANSI 0x0. #define COMDLG_WIDE 0x1.
			COLORINFO *pColor = (COLORINFO*) GetProp(hdlg, COLORPROP);
			if (pColor && (pColor->ApiType == 0 || pColor->ApiType == 1))
			{
				POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)}; 

				RECT rect;
				HWND hRect1 = GetDlgItem(hdlg, COLOR_BOX1);
				HWND hRect2 = GetDlgItem(hdlg, COLOR_CUSTOM1);
				ClientToScreen(hdlg, &pt);

				if ( (hRect1 && GetWindowRect(hRect1, &rect) && PtInRect( &rect, pt)) ||
					 (hRect2 && GetWindowRect(hRect2, &rect) && PtInRect( &rect, pt)) )
				{

					int nSel = GetListSelectedItem(hList);

					if ( nSel >= 0 && nSel < nMax && MyColorItems[nSel].pcrOriginal)
					{
						MyColorItems[nSel].crCurrent  = (COLORREF) pColor->currentRGB;
						TRACE("Color is RGB(%d,%d,%d)\r\n", GetRValue(MyColorItems[nSel].crCurrent), GetGValue(MyColorItems[nSel].crCurrent), GetBValue(MyColorItems[nSel].crCurrent));
						InvalidateRect(	hList, NULL, FALSE);
					}
				}

			}

		}
		break;
		case WM_NOTIFY:
		{
			LPNMHDR pnmh = (LPNMHDR) lParam;
			if ( pnmh->code == LVN_ITEMCHANGED )
			{
				
				NMLISTVIEW* pnmlv = (NMLISTVIEW*)lParam;	
				if ((pnmlv->uNewState & LVIS_SELECTED) && !(pnmlv->uOldState & LVIS_SELECTED))
				{
					COLORREF cr = RGB(0,0,0);
					int nSel = GetListSelectedItem(hList);

					if ( nSel >= 0 && nSel < nMax && MyColorItems[nSel].pcrOriginal)
						cr = MyColorItems[nSel].crCurrent;

					SetFocus(hList);
					SendMessage(hdlg, uSetID, 0, (LPARAM) cr);
				} 

			}
			else if (pnmh->code == NM_CUSTOMDRAW)
			{
				LRESULT lRet = DoListDraw( hList, (LPNMLVCUSTOMDRAW) lParam );
				SetWindowLongPtr(hdlg, DWLP_MSGRESULT, (LONG_PTR ) lRet);
				return 1; 
			}


		}
		break;
		case WM_DRAWITEM:
		{
			LPDRAWITEMSTRUCT lpDrawItem = (LPDRAWITEMSTRUCT) lParam;

			DrawListItem(hList, lpDrawItem);

			return 1;
		}
		break;
		case WM_MEASUREITEM:
		{
			if (wParam == IDC_LB_COLORITEMS)
			{
				LPMEASUREITEMSTRUCT lpMeasureItem = (LPMEASUREITEMSTRUCT) lParam;

				SIZE  sz;
				HDC hDC = GetDC(hList);

				if (hDC)
				{
					//HFONT hOldFont = (HFONT) SelectObject(hDC, hfont);
					
					GetTextExtentPoint(hDC, "ABCDEFGHIJK", 11, &sz);

					//SelectObject(hDC, hOldFont);
					ReleaseDC(hList, hDC);
					lpMeasureItem->itemWidth = sz.cx;
					lpMeasureItem->itemHeight = sz.cy +1;
				}

				
				return 1;
			}

			
		}
		break;

		case WM_COMMAND:
			msg_type = LOWORD(wParam);
			switch (msg_type) 
			{
				case IDC_PB_DONE:
				{
				//TODO validate color selections
					
				  //yes the following code is set to use || so you only get warned ONCE even if
				  //multiple duplicates are detected
					if (WarnColors (hdlg, MyColorItems[COLOR_SEEN].crCurrent,		MyColorItems[COLOR_LIST_BACKGROUND].crCurrent, IDS_COLOR_SEEN) ||
						WarnColors (hdlg, MyColorItems[COLOR_UNSEEN].crCurrent,		MyColorItems[COLOR_LIST_BACKGROUND].crCurrent, IDS_COLOR_UNSEEN) ||
						WarnColors (hdlg, MyColorItems[COLOR_KILLED].crCurrent,		MyColorItems[COLOR_LIST_BACKGROUND].crCurrent, IDS_COLOR_KILLED) ||
						WarnColors (hdlg, MyColorItems[COLOR_SUBSCRIBED].crCurrent,	MyColorItems[COLOR_LIST_BACKGROUND].crCurrent, IDS_COLOR_SUBSCRIBED) ||
						WarnColors (hdlg, MyColorItems[COLOR_UNSUBSCRIBED].crCurrent,MyColorItems[COLOR_LIST_BACKGROUND].crCurrent, IDS_COLOR_UNSUBSCRIBED) || 
						WarnColors (hdlg, MyColorItems[COLOR_ERROR].crCurrent,		MyColorItems[COLOR_LIST_BACKGROUND].crCurrent, IDS_COLOR_ERROR) || 
						WarnColors (hdlg, MyColorItems[COLOR_PREVIEW].crCurrent,	MyColorItems[COLOR_LIST_BACKGROUND].crCurrent, IDS_COLOR_PREVIEW) || 
						WarnColors (hdlg, MyColorItems[COLOR_ARTICLE_TEXT].crCurrent,MyColorItems[COLOR_ARTICLE_BACKGROUND].crCurrent, IDS_COLOR_ARTICLE_TEXT) ||
						WarnColors (hdlg, MyColorItems[COLOR_STATUS_TEXT].crCurrent, MyColorItems[COLOR_STATUS_BACKGROUND].crCurrent, IDS_COLOR_STATUS_TEXT)
						)
						break;

					//copy current settings
					for (int i = 0; i < nMax; i++)
					{
						*MyColorItems[i].pcrOriginal = MyColorItems[i].crCurrent;
					}

					UseInverseSelections = IsDlgButtonChecked(hdlg, IDC_CB_INVERSE) ? 1 : 0;

					bWantQuit = TRUE;
					
					PostMessage(hdlg, WM_COMMAND, IDCANCEL, 0);
				}
					break;
				
				default:
				break;
			}

    }
    return 0;

}

BOOL AskForColor (HWND hParentWnd, COLORREF * pcolor)
{
    CHOOSECOLOR cc;
    COLORREF nearC;
    HDC hDC;

    memset (&cc, 0, sizeof (CHOOSECOLOR));
    cc.lStructSize = sizeof (CHOOSECOLOR);
    cc.hwndOwner = NULL;//hParentWnd;

	if (!pcolor)
	{
		//use our enhanced color chooser
		cc.lpfnHook =  &CCHookProc;
		cc.lpTemplateName = MAKEINTRESOURCE (IDD_COLORS);
		cc.Flags =  CC_RGBINIT | CC_ANYCOLOR | CC_ENABLETEMPLATE | CC_ENABLEHOOK;
	}
	else
	{
		cc.rgbResult = *pcolor;
		cc.Flags =  CC_RGBINIT | CC_ANYCOLOR;

	}
	cc.lpCustColors = CustomColors;
    
    if (!ChooseColor (&cc))
        return (FAIL);

    if (!pcolor)
		return (SUCCESS);
	
	/* until we figure out how to deal with dithered colors, force */
    /* the color to the nearest physical color */
    hDC = GetDC (hParentWnd);
	if (g_hPalette)
	{
		SelectPalette (hDC, g_hPalette, FALSE) ;
		RealizePalette (hDC) ;				
	}

    nearC = GetNearestColor (hDC, cc.rgbResult);
    if (cc.rgbResult != nearC)
        MessageBox (hParentWnd, "WinVN does not currently support dithered (non-solid) colors.\nThe nearest physical solid color has been selected.",
                    "Sorry", MB_OK | MB_ICONINFORMATION);
    *pcolor = nearC;
    ReleaseDC (hParentWnd, hDC);
    return (SUCCESS);
}

/* ------------------------------------------------------------------------
 * This should be used instead of EM_GETHANDLE on global edit buf
 * Returns a string containing the contents of an edit wnd
 * It is the reponsibility of the caller to GlobalFreePtr the string
 * (JSC)
 */
char * GetEditText (HWND hWndEdit)
{
  unsigned int size;
  char *newText;
  BOOL bRichEdit = FALSE;
  WndEdit *compWnd = GetComposeWnd (GetParent(hWndEdit));
  
  if (compWnd && compWnd->richedit)
    bRichEdit = TRUE;


#define EDIT_PAD 2
    if (!bRichEdit || !WordWrap)
    {
        SendMessage (hWndEdit, EM_FMTLINES, (WPARAM) WordWrap, 0L);

        size = (unsigned int) SendMessage (hWndEdit, WM_GETTEXTLENGTH, 0, 0L) + EDIT_PAD;

        if ((newText = (char *) GlobalAllocPtr (GMEM_MOVEABLE, size * sizeof (char))) == NULL) {
            MessageBox (hWndEdit, "Memory allocation failure", "Edit Text", MB_OK);
            return (NULL);
        }
        *newText = '\0';

        if (SendMessage (hWndEdit, WM_GETTEXT, size, (LPARAM) (LPCSTR) newText) != (long) (size - EDIT_PAD)) {
            MessageBox (hWndEdit, "Failed to get text", "Edit Text", MB_OK);
            return (NULL);
        }

    }
    else
    {
        //since EM_FMTLINES is not supported in rich edit controls
        //we get line wraps iteratively
        
        long nLines = 0;
        long nCount = 0;
        size_t nLen=0;
        char *p = NULL;

        nLines = SendMessage (hWndEdit, EM_GETLINECOUNT, 0, 0);
        size = (unsigned int) SendMessage (hWndEdit, WM_GETTEXTLENGTH, 0, 0L) + EDIT_PAD;
        size += (unsigned int)nLines * 2; //assume all softwrapped

        if ((newText = (char *) GlobalAllocPtr (GMEM_MOVEABLE, size * sizeof (char))) == NULL) {
            MessageBox (hWndEdit, "Memory allocation failure", "Edit Text", MB_OK);
            return (NULL);
        }
        *newText = '\0';
        
        p = newText;
        for (nCount = 0; nCount < nLines; nCount++)
        {
            //set buffer size
            WORD *pSize = (WORD *)p;
            if ( size - (p - newText) > 65535)
                *pSize = 65535;
            else 
                *pSize = (WORD) (size - (p - newText));

            nLen = SendMessage (hWndEdit, EM_GETLINE, (WPARAM)nCount, (LPARAM) p);
            p += nLen;
            if (nLen)
            {
                if (*(p-1) != '\n')
                {
                    *p++ = '\r';
                    *p++ = '\n';
                }
            }
        }
        *p = '\0';
    }
  return (newText);
}
LRESULT SetEditText (HWND hWndEdit, char *editMem)
{
    return (SendMessage (hWndEdit, WM_SETTEXT, 0, (LPARAM) (LPCSTR) editMem));
}

/* ------------------------------------------------------------------------
 *    Write an integer to the private profile
 */
BOOL WritePrivateProfileInt (const char far * lpAppName, const char far * lpKeyName, int val, const char far * lpProFile)
{
    char buf[20];

    _itoa (val, buf, 10);
    return (WritePrivateProfileString (lpAppName, lpKeyName, buf, lpProFile));
}

/* ------------------------------------------------------------------------
 *    Get/Write an unsigned integers and longs to the private profile
 *      (JSC 1/8/94)
 */
BOOL WritePrivateProfileUInt (const char far * lpAppName, const char far * lpKeyName, unsigned int val, const char far * lpProFile)
{
    char buf[20];
#ifdef __GNUC__
      //no ultoa in Mingw  GCC 2.95.
      _i64toa((unsigned __int64)val, buf, 10);
#else
    _ultoa ((unsigned long)val, buf, 10);
#endif
  return (WritePrivateProfileString (lpAppName, lpKeyName, buf, lpProFile));
}

unsigned int GetPrivateProfileUInt (char far * lpAppName, char far * lpKeyName, unsigned int val, char far * lpProFile)
{
    char buf[20];

    GetPrivateProfileString (lpAppName, lpKeyName, "", buf, 20, lpProFile);

    if (*buf)
        return (atoui (buf));
    else
        return (val);
}

BOOL WritePrivateProfileLong (char far * lpAppName, char far * lpKeyName, long val, char far * lpProFile)
{
    char buf[20];

    _ltoa (val, buf, 10);
    return (WritePrivateProfileString (lpAppName, lpKeyName, buf, lpProFile));
}

long GetPrivateProfileLong (char far * lpAppName, char far * lpKeyName, long val, char far * lpProFile)
{
    char buf[20];

    GetPrivateProfileString (lpAppName, lpKeyName, "", buf, 20, lpProFile);

    if (*buf)
        return (atol (buf));
    else
        return (val);
}

BOOL WritePrivateProfileLongLong (char far * lpAppName, char far * lpKeyName, LONGLONG val, char far * lpProFile)
{
    char buf[40];

    _i64toa (val, buf, 10);
    return (WritePrivateProfileString (lpAppName, lpKeyName, buf, lpProFile));
}


LONGLONG GetPrivateProfileLongLong(char far * lpAppName, char far * lpKeyName, LONGLONG val, char far * lpProFile)
{
    char buf[40];

    GetPrivateProfileString (lpAppName, lpKeyName, "", buf, 40, lpProFile);

    if (*buf)
        return (_atoi64 (buf));
    else
        return (val);
}

/* ------------------------------------------------------------------------
 *    Refresh Window functions
 *      Called after a font/color selection has changed to affect all
 *      windows of a certain type (group/article/status)
 *      (JSC 1/9/94)
 */
void RefreshGroupWnds (BOOL bReSort)
{
    register int i;
    for (i = 0; i < MAXGROUPWNDS; i++)
        if (GroupDocs[i].InUse && GroupDocs[i].hDocWnd) 
        {
/* Ray Dunn:99/10: Resort */
			if (bReSort)
				ResortGroup(GroupDocs[i].hWndFrame);
/* Ray Dunn:99/10:End */
            SetHandleBkBrush (GroupDocs[i].hDocWnd, hListBackgroundBrush);
            SendMessage (GroupDocs[i].hWndFrame, WM_SIZE, 0, 0L);
            InvalidateRect (GroupDocs[i].hWndFrame, NULL, TRUE);
            InvalidateRect (GroupDocs[i].hDocWnd, NULL, TRUE);
			SendMessage (GroupDocs[i].hDocWnd, WM_SIZE, 0, 0L); //refresh displayed lines, height etc.
            UpdateWindow (GroupDocs[i].hWndFrame);
        }
}

void
RefreshArticleWnds ()
{
    register int i;

    for (i = 0; i < MAXARTICLEWNDS; i++)
        if (ArticleDocs[i].InUse && ArticleDocs[i].hDocWnd) 
        {
            SetHandleBkBrush (ArticleDocs[i].hDocWnd, hArticleBackgroundBrush);
            SendMessage (ArticleDocs[i].hWndFrame, WM_SIZE, 0, 0L);
            InvalidateRect (ArticleDocs[i].hWndFrame, NULL, TRUE);
            InvalidateRect (ArticleDocs[i].hDocWnd, NULL, TRUE);
            UpdateWindow (ArticleDocs[i].hWndFrame);
        }
}

void RefreshComposeWnds ()
{
    register int i;

    for (i = 0; i < MAXPOSTWNDS; i++)
        if (WndPosts[i].hWnd) 
        {
            ResizeComposeControls (&WndPosts[i], 0, 0);
        }

    for (i = 0; i < MAXMAILWNDS; i++)
        if (WndMails[i].hWnd) 
        {
            ResizeComposeControls (&WndMails[i], 0, 0);
        }
}

BOOL AreComposeWndsDirty()
{
    register int i;

    for (i = 0; i < MAXPOSTWNDS; i++)
        if (WndPosts[i].hWnd) 
        {
            if (DoEditClose (WndPosts[i].hWnd, DT_DIRTY))
				return TRUE;
        }

    for (i = 0; i < MAXMAILWNDS; i++)
        if (WndMails[i].hWnd) 
        {
            if (DoEditClose (WndPosts[i].hWnd, DT_DIRTY))
				return TRUE;

        }
	return FALSE;
}

void RefreshStatusWnds ()
{
    register int i;

    if (DebugText != NULL) 
    {
        SetHandleBkBrush (DebugText->hTextWnd, hStatusBackgroundBrush);
        SendMessage (DebugText->hTextWnd, WM_SIZE, 0, 0L);
        InvalidateRect (DebugText->hTextWnd, NULL, TRUE);
    }

    for (i = 0; i < NumStatusTexts; i++)
        if (CodingStatusText[i]->hTextWnd) 
        {
          SetHandleBkBrush (CodingStatusText[i]->hTextWnd, hStatusBackgroundBrush);
          SendMessage (CodingStatusText[i]->hTextWnd, WM_SIZE, 0, 0L);
          InvalidateRect (CodingStatusText[i]->hTextWnd, NULL, TRUE);
        }
    if (hCodedBlockWnd) 
    {
        RefreshCodedBlockWnd ();
    }

}
/* ------------------------------------------------------------------------
 *    Close Window functions
 *      Batch operation, close all windows of a certain type
 *      (group/article/status)
 *      (JSC 1/18/94)
 */
void CloseWindows ()
{
    CloseArticleWnds ();
    CloseGroupWnds ();
    CloseComposeWnds ();
    CloseStatusWnds ();
}

void CloseGroupWnds ()
{
    register int i;
    for (i = 0; i < MAXGROUPWNDS; i++)
        if (GroupDocs[i].InUse && GroupDocs[i].hDocWnd && (!CommBusy || &GroupDocs[i] != CommDoc))
            SendMessage (GroupDocs[i].hWndFrame, WM_CLOSE, 12345, 0L);
}

void CloseArticleWnds ()
{
    register int i;

    for (i = 0; i < MAXARTICLEWNDS; i++)
        if (ArticleDocs[i].InUse && ArticleDocs[i].hDocWnd && (!CommBusy || &ArticleDocs[i] != CommDoc))
            SendMessage (ArticleDocs[i].hWndFrame, WM_CLOSE, 0, 0L);
}

void CloseComposeWnds ()
{
    register int i;

    for (i = 0; i < MAXPOSTWNDS; i++)
        if (WndPosts[i].hWnd && !WndPosts[i].busy)
            SendMessage (WndPosts[i].hWnd, WM_CLOSE, 0, 0L);

    for (i = 0; i < MAXMAILWNDS; i++)
        if (WndMails[i].hWnd && !WndMails[i].busy)
            SendMessage (WndMails[i].hWnd, WM_CLOSE, 0, 0L);
}

void CloseStatusWnds ()
{
    /* destroying a coding status text is like popping from a stack */
    /* so we just loop while the top of the stack still exists */
    int numSkipped = 0;
    while (numSkipped < NumStatusTexts && CodingStatusText[numSkipped]->hTextWnd)
        if (!CodingStatusText[numSkipped]->IsBusy)
            SendMessage (CodingStatusText[numSkipped]->hTextWnd, WM_CLOSE, 0, 0L);
        else
            numSkipped++;
#if 0
    if (CodingState) 
    {
        MessageBox (CodingStatusText[0]->hTextWnd,
            "Please wait until en/decoding is complete",
            "Cannot close status window", MB_OK | MB_ICONSTOP);
        break;
    }
    else
        SendMessage (CodingStatusText[0]->hTextWnd, WM_CLOSE, 0, 0L);
#endif
}

/* ------------------------------------------------------------------------
 *    CascadeWindows (and helper CascadeWnd)
 *      cascade em
 *      jsc 9/18/94
 */
HWND CascadeWnd (HWND hWnd, HWND prevWnd, int nthWnd, int width, int height, int maxX, int maxY)
{
    int x, y;

    //  if (IsMaximized(hWnd))
    //     ShowWindow(hWnd, SW_SHOWNORMAL);

    x = (nthWnd * 12) % maxX;
    y = (nthWnd * (CaptionHeight + 2)) % maxY;
    SetWindowPos (hWnd, prevWnd, x, y, width, height, SWP_DRAWFRAME);

    return hWnd;
}

void WinVNCascadeWindows ()
{
    register int i;
    int nthWnd, width, height, maxX, maxY;
    HWND prevWnd;

    width = (int) (xScreen >> 1);
    height = (int) (yScreen >> 1);
    maxX = 3 * (width >> 1);      /* 3/4 screen width  */
    maxY = 3 * (height >> 1);     /* 3/4 screen height */

    prevWnd = CascadeWnd (NetDoc.hWndFrame, (HWND) NULL, 1, width, height, maxX, maxY);
    nthWnd = 2;
    for (i = 0; i < MAXGROUPWNDS; i++)
        if (GroupDocs[i].InUse && GroupDocs[i].hWndFrame && !IsMinimized (GroupDocs[i].hWndFrame)) 
        {
            prevWnd = CascadeWnd (GroupDocs[i].hWndFrame, prevWnd, nthWnd, width, height, maxX, maxY);
            nthWnd++;
        }

    for (i = 0; i < MAXARTICLEWNDS; i++)
        if (ArticleDocs[i].InUse && ArticleDocs[i].hWndFrame && !IsMinimized (ArticleDocs[i].hWndFrame)) 
        {
            prevWnd = CascadeWnd (ArticleDocs[i].hWndFrame, prevWnd, nthWnd, width, height, maxX, maxY);
            nthWnd++;
        }

    for (i = 0; i < MAXPOSTWNDS; i++)
        if (WndPosts[i].hWnd && !IsMinimized (WndPosts[i].hWnd)) 
        {
            prevWnd = CascadeWnd (WndPosts[i].hWnd, prevWnd, nthWnd, width, height, maxX, maxY);
            nthWnd++;
        }

    for (i = 0; i < MAXMAILWNDS; i++)
        if (WndMails[i].hWnd && !IsMinimized (WndMails[i].hWnd)) 
        {
            prevWnd = CascadeWnd (WndMails[i].hWnd, prevWnd, nthWnd, width, height, maxX, maxY);
            nthWnd++;
        }

    for (i = 0; i < NumStatusTexts; i++)
        if (CodingStatusText[i]->hTextWnd && !IsMinimized (CodingStatusText[i]->hTextWnd)) 
        {
            prevWnd = CascadeWnd (CodingStatusText[i]->hTextWnd, prevWnd, nthWnd, width, height, maxX, maxY);
            nthWnd++;
        }

    /* move coded block status window to top center */
    if (hCodedBlockWnd && !IsMinimized (hCodedBlockWnd)) 
    {
        SetWindowPos (hCodedBlockWnd, (HWND) NULL, (xScreen - STATUSWIDTH) >> 1, 1, STATUSWIDTH, STATUSHEIGHT, SWP_DRAWFRAME);
    }
}

/* ------------------------------------------------------------------------
 *    MinimizeWindows
 *      jsc 10/18/94
 */
void MinimizeWindows ()
{
    ShowWindow (NetDoc.hWndFrame, SW_MINIMIZE);
    MinimizeGroupWnds ();
    MinimizeArticleWnds ();
    MinimizeComposeWnds ();
    MinimizeStatusWnds ();
}

void MinimizeGroupWnds ()
{
    register int i;

    for (i = 0; i < MAXGROUPWNDS; i++)
        if (GroupDocs[i].InUse && GroupDocs[i].hDocWnd && !IsMinimized (GroupDocs[i].hWndFrame)) 
        {
            ShowWindow (GroupDocs[i].hWndFrame, SW_MINIMIZE);
        }
}

void MinimizeArticleWnds ()
{
    register int i;

    for (i = 0; i < MAXARTICLEWNDS; i++)
        if (ArticleDocs[i].InUse && ArticleDocs[i].hDocWnd && !IsMinimized (ArticleDocs[i].hWndFrame)) 
        {
            ShowWindow (ArticleDocs[i].hWndFrame, SW_MINIMIZE);
        }
}

void MinimizeComposeWnds ()
{
    register int i;

    for (i = 0; i < MAXPOSTWNDS; i++)
        if (WndPosts[i].hWnd && !IsMinimized (WndPosts[i].hWnd)) 
        {
            ShowWindow (WndPosts[i].hWnd, SW_MINIMIZE);
        }

    for (i = 0; i < MAXMAILWNDS; i++)
        if (WndMails[i].hWnd && !IsMinimized (WndMails[i].hWnd)) 
        {
            ShowWindow (WndMails[i].hWnd, SW_MINIMIZE);
        }
}

void MinimizeStatusWnds ()
{
    register int i;

    for (i = 0; i < NumStatusTexts; i++)
        if (CodingStatusText[i]->hTextWnd && !IsMinimized (CodingStatusText[i]->hTextWnd)) 
        {
            ShowWindow (CodingStatusText[i]->hTextWnd, SW_MINIMIZE);
        }

    if (hCodedBlockWnd) 
    {
        ShowWindow (hCodedBlockWnd, SW_MINIMIZE);
    }
}

/* ------------------------------------------------------------------------
 *    RestoreWindows from minimized state
 *      jsc 1/18/95
 */

void RestoreWindows ()
{
    register int i;

    if (IsMinimized(NetDoc.hWndFrame)) 
    {
        ShowWindow (NetDoc.hWndFrame, SW_RESTORE);
    }

    for (i = 0; i < MAXGROUPWNDS; i++)
        if (GroupDocs[i].InUse && GroupDocs[i].hDocWnd && IsMinimized (GroupDocs[i].hWndFrame)) 
        {
            ShowWindow (GroupDocs[i].hWndFrame, SW_RESTORE);
        }
    for (i = 0; i < MAXARTICLEWNDS; i++)
        if (ArticleDocs[i].InUse && ArticleDocs[i].hDocWnd && IsMinimized (ArticleDocs[i].hWndFrame)) {
            ShowWindow (ArticleDocs[i].hWndFrame, SW_RESTORE);
        }
    for (i = 0; i < MAXPOSTWNDS; i++)
        if (WndPosts[i].hWnd && IsMinimized (WndPosts[i].hWnd)) 
        {
            ShowWindow (WndPosts[i].hWnd, SW_RESTORE);
        }

    for (i = 0; i < MAXMAILWNDS; i++)
        if (WndMails[i].hWnd && IsMinimized (WndMails[i].hWnd)) 
        {
            ShowWindow (WndMails[i].hWnd, SW_RESTORE);
        }
    for (i = 0; i < NumStatusTexts; i++)
        if (CodingStatusText[i]->hTextWnd && IsMinimized (CodingStatusText[i]->hTextWnd)) 
        {
            ShowWindow (CodingStatusText[i]->hTextWnd, SW_RESTORE);
        }

    if (hCodedBlockWnd) 
    {
        ShowWindow (hCodedBlockWnd, SW_RESTORE);
    }
}

/* ------------------------------------------------------------------------
 *    BatchSend
 *      type is DOCTYPE_MAIL or _POST
 *      Increments nextBatchIndex and initiates mail/post
 *      Note: on entry nextBatchIndex is the index we will use for this send
 *      on exit, nextBatchIndex is either 0 (no more to send) or the index
 *      of the next mail/post to send
 *      (JSC 1/18/94)
 */
void BatchSend (int type)
{
  int thisSend;
  int nextSend; // S.Yoshida
  int maxWnds;
  WndEdit *WndEdits;

#if 1
  if (CommBusy) 
  {
    if (type == DOCTYPE_MAIL) 
    {
      PostMessage (NetDoc.hWndFrame, WM_COMMAND, IDM_SEND_NEXT_MAIL, 0);
    } 
    else 
    {
      PostMessage (NetDoc.hWndFrame, WM_COMMAND, IDM_SEND_NEXT_POST, 0);
    }
    return;
  }
#endif

  if (type == DOCTYPE_POSTING) 
  {
    WndEdits = WndPosts;
    maxWnds = MAXPOSTWNDS;
  }
  else 
  {
    WndEdits = WndMails;
    maxWnds = MAXMAILWNDS;
  }

//  Variable being used without initialization JD 11/10/95?
//  thisSend = WndEdits[thisSend].nextBatchIndex;
  thisSend = WndEdits[0].nextBatchIndex;
  if (thisSend == 0 || WndEdits[thisSend].hWnd == NULL) {// S.Yoshida  if (thisSend == 0) {          /* find first in batch (if any) */
    thisSend = 0; //S.Yoshida
    while (thisSend < maxWnds)
      if (WndEdits[thisSend].hWnd)
        break;
      else
        thisSend++;

    if (thisSend == maxWnds)
      return;                   /* no open windows.  cancel */

    WndEdits[thisSend].nextBatchIndex = thisSend;
  }
  nextSend = thisSend;
  /* find next in batch (if any) */
//  while (++WndEdits[thisSend].nextBatchIndex < maxWnds)
  while (++nextSend < maxWnds)
    if (WndEdits[nextSend].hWnd)
      break;

//  if (WndEdits[thisSend].nextBatchIndex == maxWnds)
//    WndEdits[thisSend].nextBatchIndex = 0;          /* no more */
  if (nextSend == maxWnds)
    nextSend = 0;

  WndEdits[thisSend].nextBatchIndex = nextSend;
  if (thisSend)
    WndEdits[thisSend].nextBatchIndex = nextSend;

 // ------ the last chance to confirm send composition ----
   
 // -------------------------------------------------------

  SendComposition (&WndEdits[thisSend]);
}

BOOL InlineReconnect(HWND hParentWnd)
{
	static bool inReconnect = FALSE;
	BOOL bReturn = FALSE;
	MSG msg;

	if (inReconnect || Initializing != INIT_NOT_CONNECTED)
		return FALSE;

	inReconnect = TRUE;
	Reconnecting = 1;
	HCURSOR hSaveCursor = SetCursor (hHourGlass);

	Initializing = INIT_ESTAB_CONN;
	MRRCloseComm();
	if (MRRInitComm () != 0) 
	{    /* Set up communications */
		Initializing = INIT_NOT_CONNECTED;
		MRRCloseComm ();
		inReconnect = FALSE;
		Reconnecting = 0;
		*CurrentGroup = '\0';
		SetCursor(hSaveCursor);
		return FALSE;
	}
	CommDoc = &NetDoc;
	idTimer = SetTimer (NetDoc.hDocWnd, ID_TIMER, COMM_TIMER_INTERVAL, (TIMERPROC) NULL);

	while (1) //wait for comm timer
	{	
		WaitMessage();
		while(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) 
		{
	
			TranslateMessage(&msg);
			DispatchMessage(&msg);
			if (Initializing == INIT_NOT_CONNECTED || (CommState == ST_IN_GROUP || CommState == ST_RECONNECTED) )
				break;
		}

		if (Initializing == INIT_NOT_CONNECTED || (CommState == ST_IN_GROUP || CommState == ST_RECONNECTED))
				break;

	}
	inReconnect = FALSE;
	bReturn = CommState == ST_IN_GROUP || CommState == ST_RECONNECTED;
	if (CommState == ST_RECONNECTED)
		CommState = ST_NONE;
	SetCursor(hSaveCursor);

	return bReturn;
	
}


BOOL InlineGroup( char * szGroup)
{
	static bool inGroup = FALSE;

	char buf[MAXGROUPNAME + 20];
	int oldCommState = CommState;
	int oldInitalizing = Initializing;
	BOOL oldCommBusy = CommBusy;

	if (inGroup || CommBusy) 
		return FALSE;

	inGroup = TRUE;

    CommLinePtr = CommLineIn;
    CommBusy = TRUE;

	CommState = ST_INLINEGROUP_RESP;
	_sprintf(buf,"GROUP %s\r\n",szGroup);

    hSaveCursor = SetCursor (hHourGlass);
    ShowCursor (TRUE);
    PutCommLine (buf);


	//wait for comm timer
	do
	{	
		Sleep(50);	//good enough for now
		DoCommInput ();

	} while ( Initializing != INIT_NOT_CONNECTED && CommState == ST_INLINEGROUP_RESP );
	 
	//have we been disconnected?
	if (Initializing == INIT_NOT_CONNECTED)
	{
		SetCursor (hSaveCursor);
		return FALSE;
	}


	CommState = ST_GROUP_RETURN;
	_sprintf(buf,"GROUP %s\r\n",CurrentGroup);

    PutCommLine (buf);

	do
	{	
		Sleep(50); //good enough for now
		DoCommInput ();

	} while ( Initializing != INIT_NOT_CONNECTED && CommState == ST_GROUP_RETURN );

	if (Initializing != INIT_NOT_CONNECTED)
	{
		CommBusy = oldCommBusy;
		Initializing = oldInitalizing;
		CommState = oldCommState;
	}
	
	inGroup = FALSE;
	SetCursor (hSaveCursor);
	return TRUE;
	
}

/* ------------------------------------------------------------------------
 *    Test busy functions
 *      Called to test if a comm or decoding is busy
 *      Returns true if busy, false if not busy
 *      (JSC 1/9/94)
 */


BOOL TestCommBusy (HWND hParentWnd, UINT IDmsg)
{

	if ( Initializing == INIT_NOT_CONNECTED && Autoconnect && Reconnecting)
	{
		if (InlineReconnect(hParentWnd))
			return FALSE;
	}

	if (CommTimeout && CommBusy && CommState == ST_TIMEOUT_WAKEUP)
	{
		HCURSOR hSave = SetCursor (hHourGlass);

		while (1) //wait for keep-alive timer, shouldn't be long
		{	
			MSG msg;
			WaitMessage();
			BOOL bExit = FALSE;
			while(!bExit && PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) 
			{

				if (msg.message == WM_QUIT) 
				{
					PostQuitMessage((int)msg.wParam);
					return FALSE; // abandoned due to WM_QUIT
				}

		
				TranslateMessage(&msg);
				DispatchMessage(&msg);
				if (!CommBusy || CommState != ST_TIMEOUT_WAKEUP )
				{
					bExit = TRUE;
					break;
				}
			}
			if (bExit)
				break;

		}
		SetCursor (hSave);

		return TestCommBusy (hParentWnd, IDmsg);

		
	}

	/* Ray Dunn: 99/11 - warn if not connected (specifically useful when posting and
	connection has timed out) - used to hang winvn */
	if (Initializing != INIT_DONE)
	{
		WVMessageBox (hParentWnd, IDS_WVUTIL_NOTCONNECTED, IDmsg, MB_OK | MB_ICONASTERISK);
		return(TRUE);
	}
	else if (CommBusy)
	{
		WVMessageBox (hParentWnd, IDS_WVUTIL_TESTCOMMBUSY, IDmsg, MB_OK | MB_ICONASTERISK);
		return (TRUE);
	}
	else
		return (FALSE);
}

BOOL TestCodingBusy (HWND hParentWnd, UINT IDmsg)
{
  if (CodingState != INACTIVE) {
    WVMessageBox (hParentWnd, IDS_WVUTIL_TESTCODING, IDmsg, MB_OK | MB_ICONASTERISK);
    return (TRUE);
  }
  else
    return (FALSE);
}

/* ------------------------------------------------------------------------
 *        Update the mail menus -- called on mail transport change
 *      jsc 9/9/94
 */
void
UpdateAllMailMenus ()
{
  register int i;

  SetMainMailMenu (&NetDoc);
  for (i = 0; i < MAXARTICLEWNDS; i++)
    if (ArticleDocs[i].hDocWnd &&
        (!CommBusy || CommDoc != &ArticleDocs[i]))
      SetArticleMailMenu (&ArticleDocs[i]);

  for (i = 0; i < MAXGROUPWNDS; i++)
    if (GroupDocs[i].hDocWnd)
      SetGroupMailMenu (&GroupDocs[i]);
}

/* ------------------------------------------------------------------------
 *        GetFreeDiskSpace in bytes, given a drive name (i.e. "c:\\")
 *      jsc 9/29/94
 */
unsigned long 
GetFreeDiskSpace (char *drive)
{
#ifdef _WIN32
  DWORD sectorsPerCluster, bytesPerSector, freeClusters, totalClusters;
  if (GetDiskFreeSpace (drive, &sectorsPerCluster, &bytesPerSector,
                        &freeClusters, &totalClusters) == FALSE) {
    return 0L;
  }
  return (unsigned long) (sectorsPerCluster * bytesPerSector * freeClusters);
#else
  struct _diskfree_t d;
  if (_dos_getdiskfree (tolower (*drive) - 'a', &d) != 0) {
    return 0L;
  }
  return (unsigned long) d.avail_clusters *
    (unsigned long) d.sectors_per_cluster *
    (unsigned long) d.bytes_per_sector;
#endif
}

/* ------------------------------------------------------------------------
 *        GetFileLength in bytes  given a file name
 *      jsc 9/29/94
 */
unsigned long GetFileLength (const char *fileName)
{
	DWORD dwSize = 0;

	HANDLE hFile = CreateFile( fileName, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE,
		NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
	if (hFile != INVALID_HANDLE_VALUE)
	{
		dwSize = GetFileSize(hFile, NULL);
		if (dwSize == INVALID_FILE_SIZE )
			dwSize = 0;
			
		CloseHandle(hFile);	


	}
 

	return dwSize;
}

void
DrawDragingCursor(HWND hWnd, long oldPosition, long newPosition, BYTE flag)
{
  HDC hDc;
  HPEN hOrigPen;
  int nOrigDrawMode;
  RECT clientRect;

  hDc = GetDC(hWnd);
  hOrigPen = (HPEN)SelectObject(hDc, CreatePen(PS_SOLID, 1, ListBackgroundColor));
  nOrigDrawMode = GetROP2(hDc);
  SetROP2(hDc, R2_XORPEN);
  GetClientRect(hWnd, &clientRect);
  if ((flag == 1) || (flag == 2)) {
    MoveToEx(hDc, (int) oldPosition, 0, NULL);
    LineTo(hDc, (int) oldPosition, clientRect.bottom);
  }
  if (flag == 2) {
    MoveToEx(hDc, (int) newPosition, 0, NULL);
    LineTo(hDc, (int) newPosition, clientRect.bottom);
  }
  SetROP2(hDc, nOrigDrawMode);
  DeleteObject(SelectObject(hDc,hOrigPen));
  ReleaseDC(hWnd, hDc);
}

//Messagebox replacement that takes string IDs instead of strings
//resources save on DGROUP space for Win16

HHOOK hhk; //global hook to center our message boxes


//CBT hook to center our messageboxes on our application
LRESULT CALLBACK CBTProc(INT nCode, WPARAM wParam, LPARAM lParam)
{
	HWND  hParentWnd, hChildWnd;    // msgbox is "child"
	RECT  rParent, rChild, rDesktop;
	POINT pCenter, pStart;
	INT   nWidth, nHeight;

	// notification that a window is about to be activated
	// window handle is wParam
	if (nCode == HCBT_ACTIVATE)
	{
		
		// set window handles
		//hParentWnd = GetForegroundWindow();
		hChildWnd  = (HWND)wParam;
		hParentWnd = GetParent(hChildWnd);
		WINDOWPLACEMENT wndpl;
		ZeroMemory(&wndpl, sizeof(WINDOWPLACEMENT));
		wndpl.length = sizeof(WINDOWPLACEMENT);

		if((hParentWnd != 0) &&
			(hChildWnd != 0) &&
			(GetWindowRect(GetDesktopWindow(), &rDesktop) != 0) &&
			GetWindowPlacement(hParentWnd, &wndpl) != 0 &&
			//(GetWindowRect(hParentWnd, &rParent) != 0) &&
			(GetWindowRect(hChildWnd, &rChild) != 0))
		{
			
			

			if (wndpl.showCmd == SW_MINIMIZE)
			{
				CopyRect(&rParent, &wndpl.rcNormalPosition);	
			}
			else 
			{
				GetWindowRect(hParentWnd, &rParent);
			}


			
			
			// calculate message box dimensions
			nWidth  = (rChild.right - rChild.left);
			nHeight = (rChild.bottom - rChild.top);

			// calculate parent window center point
			pCenter.x = rParent.left+((rParent.right- rParent.left)/2);
			pCenter.y = rParent.top+((rParent.bottom- rParent.top)/2);

			// calculate message box starting point
			pStart.x = (pCenter.x - (nWidth/2));
			pStart.y = (pCenter.y - (nHeight/2));

			// adjust if message box is off desktop
			
			rDesktop.left = GetSystemMetrics(SM_XVIRTUALSCREEN);
			rDesktop.right = rDesktop.left + GetSystemMetrics(SM_CXVIRTUALSCREEN);
			
			rDesktop.top = GetSystemMetrics(SM_YVIRTUALSCREEN);
			rDesktop.bottom = rDesktop.top + GetSystemMetrics(SM_CYVIRTUALSCREEN);



			if(pStart.x < rDesktop.left) 
				pStart.x = rDesktop.left;
			if(pStart.y < rDesktop.top) 
				pStart.y = rDesktop.top;
			if(pStart.x + nWidth > rDesktop.right)
				pStart.x = rDesktop.right - nWidth;
			if(pStart.y + nHeight > rDesktop.bottom)
				pStart.y = rDesktop.bottom - nHeight;

			// move message box
			MoveWindow(hChildWnd, pStart.x, pStart.y,nWidth, nHeight,FALSE);
		}
		// exit CBT hook
		UnhookWindowsHookEx(hhk);
	}
	// otherwise, continue with any possible chained hooks
	else CallNextHookEx(hhk, nCode, wParam, lParam);
		return 0;
}

#define	TIMERID 1219
static HWND g_TimerHwnd;





void CALLBACK TimerProc(HWND hWnd, UINT uMsg, UINT_PTR idEvent, DWORD  dwTime)
{
	switch (uMsg)
	{
		case WM_TIMER:
		{
			if (idEvent == TIMERID)
			{

				TCHAR buf[1024];
				int nLen = GetDlgItemText(hWnd, 0xFFFF, buf, 1023);
				int nEnd, nStart;
				long lval = -1;
				nEnd = nStart = nLen;
				while (nStart)
				{
					
					if (isdigit(buf[nStart]))
					{

						nEnd = nStart;
						while (isdigit(buf[--nStart]))	{};
						lval = atol(buf + (++nStart));
						break;


					}
					nStart--;
				}
				
				if (lval > -1)
				{
					if (lval==0)
					{
						//end dialog
						HWND hButton = GetDlgItem(hWnd, 0x0001);
						RECT rect;
						GetWindowRect(hButton, &rect);
						POINT pt;
						pt.x = rect.left + (rect.right-rect.left)/2;
						pt.y = rect.top + (rect.bottom-rect.top)/2;

						ScreenToClient(hWnd, &pt);
						//PostMessage(hWnd, WM_LBUTTONDOWN, MK_LBUTTON, (LPARAM) POINTTOPOINTS(pt));
						//PostMessage(hWnd, WM_LBUTTONUP, MK_LBUTTON, (LPARAM) POINTTOPOINTS(pt));
						EndDialog(hWnd, 0x001);
						
					}
					else
					{
						--lval;
					}
					{
						char cval[10];
						int clen = 0;
						
						_ltoa(lval, cval, 10);
						clen = strlen(cval);
						memcpy(buf+nStart, cval, clen);

						
						if (clen < nEnd - nStart +1)
						{
							memcpy(buf +nStart+clen, buf+nEnd+1, nLen-nEnd); 
						}

					}
				}

				SetDlgItemText(hWnd, 0xFFFF, buf);
				
			}
		}

	}
	return;
}

LRESULT CALLBACK CBTTimerProc(INT nCode, WPARAM wParam, LPARAM lParam)
{
	// notification that a window is about to be activated
	// window handle is wParam
	if (nCode == HCBT_ACTIVATE)
	{
		HWND hWnd  = (HWND)wParam;
		HWND hParent = GetParent(hWnd);
		RECT rect;
		WINDOWPLACEMENT wndpl;
		ZeroMemory(&wndpl, sizeof(WINDOWPLACEMENT));
		wndpl.length = sizeof(WINDOWPLACEMENT);

		g_TimerHwnd = hWnd;

		if((hParent != 0) &&
			(hWnd != 0) &&
			(GetWindowRect(GetDesktopWindow(), &rect) != 0) &&
			GetWindowPlacement(hParent, &wndpl) != 0 &&
			wndpl.showCmd != SW_MINIMIZE)
		{
			CenterWindow(hWnd, hParent);
		}


		SetTimer( hWnd, TIMERID, 2000, &TimerProc);
		
		SetDlgItemText(hWnd, 0x0001, "Retry");
		
		
		// exit CBT hook
		UnhookWindowsHookEx(hhk);
	}
	// otherwise, continue with any possible chained hooks
	else CallNextHookEx(hhk, nCode, wParam, lParam);
		return 0;

}



#define MESSAGESIZE 2048
int WVMessageBox(HWND hwnd, LPSTR lpText, LPSTR lpCaption,UINT uType)
{
	hhk = SetWindowsHookEx(WH_CBT, &CBTProc, 0,GetCurrentThreadId());
	return MessageBox(hwnd, lpText, lpCaption, uType);
}

int WVTimedMessageBox(HWND hwnd, LPSTR lpText, LPSTR lpCaption,UINT uType)
{

	hhk = SetWindowsHookEx(WH_CBT, &CBTTimerProc, 0,GetCurrentThreadId());
	UINT uReturn = MessageBox(hwnd, lpText, lpCaption, uType);
	KillTimer(g_TimerHwnd,	 TIMERID);
	return uReturn;
}

int WVMessageBox(HWND hwndParent, UINT idText, UINT idTitle, UINT fuStyle)
{
	HGLOBAL hBuf;
	char far *pText = NULL;
	char far *pTitle = NULL;
	int nReturn = 0;

	hBuf = ::GlobalAlloc(GHND, (MESSAGESIZE) * 2 );
	if (hBuf) 
	{
		pText = (char far *) ::GlobalLock(hBuf);
		if (pText) 
		{

			pTitle = pText + MESSAGESIZE;
			LoadString(hInst,idText, pText, MESSAGESIZE);
			LoadString(hInst,idTitle, pTitle, MESSAGESIZE);

			hhk = SetWindowsHookEx(WH_CBT, &CBTProc, 0, GetCurrentThreadId());

			nReturn = MessageBox(hwndParent, pText, pTitle, fuStyle);

			::GlobalUnlock(hBuf);
			pText = pTitle = NULL;
		}
		::GlobalFree(hBuf);
		hBuf = NULL;             
	}
	return nReturn;
}

int WVArgMessageBox(HWND hwndParent, UINT idFormat, UINT idTitle, UINT fuStyle, const char far *psubst)
{

	HGLOBAL hBuf;
	char far *pFormat = NULL;
	char far *pTitle = NULL;
	char far *pMesg = NULL;
	int nReturn = 0;

	hBuf = ::GlobalAlloc(GHND, (MESSAGESIZE) * 3 );
	if (hBuf) 
	{
		pFormat = (char far *) ::GlobalLock(hBuf);
		if (pFormat) 
		{

			pTitle = pFormat + MESSAGESIZE;
			pMesg = pTitle + MESSAGESIZE;
			LoadString(hInst,idTitle, pTitle, MESSAGESIZE);
			if (LoadString(hInst,idFormat, pFormat, MESSAGESIZE)) 
			{

				_snprintf(pMesg, MESSAGESIZE, pFormat, psubst);
			}

			hhk = SetWindowsHookEx(WH_CBT, &CBTProc, 0, GetCurrentThreadId());
			nReturn = MessageBox(hwndParent, pMesg, pTitle, fuStyle);

			::GlobalUnlock(hBuf);
			pFormat = pMesg = pTitle = NULL;
		}
		::GlobalFree(hBuf);
		hBuf = NULL;             
	}
	return nReturn;
}


int WVArgXMessageBox(HWND hwndParent, UINT idFormat, UINT idTitle, UINT fuStyle, ...)
{
	va_list vargs;
	char far *pFormat = NULL;
	char far *pMesg = NULL;
	char far *pTitle = NULL;
	int  nStrLen = 0;
	int nReturn = 0;

	pMesg = (char far *)GlobalAllocPtr (GMEM_MOVEABLE, MESSAGESIZE * sizeof (char));
	pFormat = (char far *)GlobalAllocPtr (GMEM_MOVEABLE, MESSAGESIZE * sizeof (char) * 2);

	if (pFormat && pMesg) 
	{
		*pMesg = '\0';
		pTitle = pFormat + MESSAGESIZE;
		nStrLen = LoadString(hInst, idFormat, pFormat, MESSAGESIZE);
		nStrLen = LoadString(hInst, idTitle, pTitle, MESSAGESIZE);

		if (nStrLen && nStrLen < MESSAGESIZE) 
		{
		  va_start( vargs, fuStyle );
		  _vsnprintf( pMesg, MESSAGESIZE, pFormat, vargs );
		  va_end( vargs );        
		}
		hhk = SetWindowsHookEx(WH_CBT, &CBTProc, 0, GetCurrentThreadId());
		nReturn = MessageBox(hwndParent, pMesg, pTitle, fuStyle);
          
	}
	if (pFormat)
		GlobalFreePtr(pFormat);

	if (pMesg)
		GlobalFreePtr(pMesg);

	return nReturn;
}


BOOL WVSetWindowText(HWND hwndParent, UINT idText)
{
	HGLOBAL hBuf;
	char far *pText = NULL;
	BOOL bReturn = FALSE;

	hBuf = ::GlobalAlloc(GHND, (MESSAGESIZE));
	if (hBuf) 
	{
		pText = (char far *) ::GlobalLock(hBuf);
		if (pText) 
		{
			LoadString(hInst, idText, pText, MESSAGESIZE);

			bReturn = SetWindowText(hwndParent, pText);
    
			::GlobalUnlock(hBuf);
			pText= NULL;
		}
		::GlobalFree(hBuf);
		hBuf = NULL;             
	}
  return bReturn;
}


BOOL WVArgXSetWindowText(HWND hwndParent, TCHAR *szFormat, ...)
{
	va_list vargs;

	char far *pMesg = NULL;
	BOOL bReturn = FALSE;

	if (!szFormat)
	return FALSE;

	pMesg = (char far *)GlobalAllocPtr (GMEM_MOVEABLE, (_tcslen(szFormat) + MESSAGESIZE) * sizeof (TCHAR)  );


	if (pMesg) 
	{
		*pMesg = '\0';

		va_start( vargs, szFormat );
		_vsnprintf( pMesg, MESSAGESIZE * sizeof(TCHAR), szFormat, vargs );
		va_end( vargs );        

		bReturn = SetWindowText(hwndParent, pMesg);

		GlobalFreePtr(pMesg);             

	}

	return bReturn;

}


BOOL WVArgXSetWindowText(HWND hwndParent, UINT idFormat, ...)
{
	va_list vargs;
	char far *pFormat = NULL;
	char far *pMesg = NULL;
	BOOL bReturn = FALSE;
	int  nStrLen = 0;

	pMesg = (char far *)GlobalAllocPtr (GMEM_MOVEABLE, MESSAGESIZE * sizeof (char));
	pFormat = (char far *)GlobalAllocPtr (GMEM_MOVEABLE, MESSAGESIZE * sizeof (char));

	if (pFormat && pMesg) 
	{
		*pMesg = '\0';

		nStrLen = LoadString(hInst, idFormat, pFormat, MESSAGESIZE);

		if (nStrLen && nStrLen < MESSAGESIZE) 
		{
		  va_start( vargs, idFormat );
		  _vsnprintf( pMesg, MESSAGESIZE, pFormat, vargs );
		  va_end( vargs );        
		}

		bReturn = SetWindowText(hwndParent, pMesg);
                
	}
	if (pFormat)
		GlobalFreePtr(pFormat);

	if (pMesg)
		GlobalFreePtr(pMesg);

	return bReturn;
}



BOOL TestArgCommBusy (HWND hParentWnd, UINT IDmsg, const char far *psubst)
{
	HGLOBAL hBuf;
	char far *pFormat = NULL;
	char far *pMesg = NULL;
	char far *pTitle = NULL;
	BOOL bReturn = FALSE;

	hBuf = ::GlobalAlloc(GHND, (MESSAGESIZE) * 3 );
	if (hBuf) 
	{
		pFormat = (char far *) ::GlobalLock(hBuf);
		if (pFormat) 
		{
			pMesg = pFormat + MESSAGESIZE;
			pTitle = pMesg +  MESSAGESIZE;
			LoadString(hInst, IDS_WVUTIL_TESTCOMMBUSY, pMesg, MESSAGESIZE);
			if (LoadString(hInst, IDmsg, pFormat, MESSAGESIZE)) 
			{
				_snprintf(pTitle, MESSAGESIZE, pFormat, psubst);
			}

			if (CommBusy) 
			{
				MessageBox (hParentWnd, pMesg, pTitle, MB_OK | MB_ICONASTERISK);
				bReturn = TRUE;
			} 
			else
				bReturn = FALSE;

			::GlobalUnlock(hBuf);
			pFormat = pMesg = pTitle = NULL;
		}
		::GlobalFree(hBuf);
		hBuf = NULL;             
	}
	return bReturn;
}

BOOL RetrieveNextGroup( TypBlock far *BlockPtr, TypLine far *LinePtr, int nTitle, BOOL bConfirm )
{
	FlagJumpGroup = 0;
	while(1) 
	{
		if( !NextActiveLine( &BlockPtr, &LinePtr ) ) 
		{
			return FALSE;
		}
		if( !(GetGroup(LinePtr))->Determined ) 
		{ // Ignore NNTP
			break;
		}
		if( !(GetGroup(LinePtr))->ServerEstNum || !(GetGroup(LinePtr))->NumUnread ) 
		{
			continue;
		}
		break;
	}
	HWND hWnd = CommDoc ? CommDoc->hDocWnd : NetDoc.hDocWnd;
	if ( bConfirm || WVArgMessageBox(hWnd, IDS_WVUTIL_NEXTGROUP, nTitle, MB_YESNO, GetGroupName(LinePtr)) == IDYES) 
	{
		SetSelections( &NetDoc, LinePtr );
		AdjustTopSc( BlockPtr, LinePtr );
		if( GroupListMultiSelect ) 
		{
			/* set the active and anchor line IDs = LnkFind.LineID */
			NetDoc.AnchorLineID = LinePtr->LineID;
		}
		NetDoc.ActiveLineID = LinePtr->LineID;
		//if( ConfigJumpGroup & GJUMP_OPENGROUP ) {
		//ConfigJumpGroup |= GJUMP_OPN_GROUP;
		FlagJumpGroup = JUMPFLAG_GROUP;
		PostMessage( NetDoc.hDocWnd, WM_CHAR, '\r', 0 );
		//} else {
		//SetForegroundWindow( NetDoc.hWndFrame );
		//InvalidateRect(NetDoc.hWndFrame,NULL,FALSE);
		//InvalidateRect(NetDoc.hDocWnd,NULL,FALSE);
		//}
	}
	return TRUE;
}

// Search HKCL_Root for the specified key, return the default shell command
// MDS 20000410
BOOL GetRegDefaultCommand(LPCSTR szKey, LPSTR szCommandBuf, int nLen)
{
  
  HKEY hKey = 0, hsubKey = 0;
  DWORD dwSize = (DWORD) nLen;
  BOOL bFound = FALSE;
  char *endcmd = NULL;

  if (!szCommandBuf || !szKey || *szKey == '\0')
    return FALSE;

  *szCommandBuf = '\0';
  if (RegOpenKeyEx(HKEY_CLASSES_ROOT, szKey, 0, KEY_READ,  &hKey) == ERROR_SUCCESS)
  {
    if (RegOpenKeyEx(hKey, "shell\\open\\command", 0, KEY_READ,  &hsubKey) == ERROR_SUCCESS)
	{
		DWORD dwType;
		if (RegQueryValueEx( hsubKey, NULL, 0, &dwType, 
						(LPBYTE) szCommandBuf, &dwSize) == ERROR_SUCCESS )
		{

		  bFound = TRUE;

		  //remove quote wrapped path
		  if (*szCommandBuf == '"')
		  {

			memmove(szCommandBuf, szCommandBuf+1, strlen(szCommandBuf+1));
			endcmd = strchr(szCommandBuf, '"');
			if (endcmd)
			{
			  //toss args
			  *endcmd = '\0';
			}
        
			if (strchr(szCommandBuf, ' ') )
			{

			  char szBuf[_MAX_PATH];
			  DWORD dwReturn;
			  dwReturn = GetShortPathName(szCommandBuf, szBuf, _MAX_PATH);
			  if (dwReturn != 0 && dwReturn < _MAX_PATH)
			  {
				strntcpy(szCommandBuf, szBuf, nLen);
			  } 
			} 
		  }
		  else
		  {
			endcmd = strchr(szCommandBuf, ' ');
			if (endcmd)
			{
			  //toss args
			  *endcmd = '\0';;
			}
		  }

		}
		RegCloseKey(hsubKey);
	}
    
    RegCloseKey(hKey);

  } 

  return bFound;

}

typedef LONG NTSTATUS, *PNTSTATUS;
#define STATUS_SUCCESS (0x00000000)

typedef NTSTATUS (WINAPI* RtlGetVersionPtr)(PRTL_OSVERSIONINFOW);

RTL_OSVERSIONINFOW GetRealOSVersion() 
{
    HMODULE hMod = ::GetModuleHandleW(L"ntdll.dll");
    if (hMod) 
	{
        RtlGetVersionPtr fxPtr = (RtlGetVersionPtr)::GetProcAddress(hMod, "RtlGetVersion");
        if (fxPtr != NULL) 
        {
            RTL_OSVERSIONINFOW rovi = { 0 };
            rovi.dwOSVersionInfoSize = sizeof(rovi);
            if ( STATUS_SUCCESS == fxPtr(&rovi) ) 
            {
                return rovi;
            }
        }
    }
    RTL_OSVERSIONINFOW rovi = { 0 };
    return rovi;
}
/////////////////////////////////////////////////////////////////////////////
// DWORD GetFileVersion(LPTSTR lpszFilename, BOOL* pBIs64 /*=NULL*/)
//
// Retrieve the windows file version of the specified file
//
//  Input: a string containing the name of a path & file to search
//
//  Returns a DWORD containing the file's FileVersion. A file's version is 
//  stored as two DWORD values. This routine uses the low byte of each
//  part of the version number to create the dword. Thus the maximum
//  version number of each of the 4 parts is 255.
//  
//  Returns 0 if version information not found.
//
DWORD GetFileVersion(LPTSTR lpszFilename, LPTSTR lpszDescription /*=NULL*/, int nDescLen /*=-1*/, BOOL* pBIs64 /*=NULL*/)
{
    DWORD   dwVersion = (DWORD)-1; 
    DWORD   dwSize;         // Size of mem needed to hold version info
    DWORD   dwDummy;        // Pointer to variable to receive zero
    const TCHAR	*pBits = _T("");
	if (pBIs64)
	{
		*pBIs64 = FALSE;
		//1st determine 32 or 64
		BYTE * pBuf = new BYTE[4096];
		if (pBuf)
		{
			DWORD dwBytesRead;
			// Attempt to load file
			HANDLE hFile = CreateFile( lpszFilename,  GENERIC_READ , FILE_SHARE_READ, NULL,  OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN | FILE_ATTRIBUTE_READONLY, NULL );
			if( INVALID_HANDLE_VALUE != hFile )
			{
				
				// Read the 1st block
				if (ReadFile( hFile, pBuf, 4095, &dwBytesRead, NULL ))
				{
					if ( dwBytesRead > sizeof(IMAGE_DOS_HEADER) )
					{
						PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER) pBuf;
						if (pDosHeader->e_magic == 0x5A4D &&
							pDosHeader->e_lfanew + sizeof(IMAGE_NT_HEADERS) < dwBytesRead )
						{
							//if (pDosHeader->e_lfanew
							IMAGE_NT_HEADERS *pHeader = (IMAGE_NT_HEADERS*) (pBuf+	pDosHeader->e_lfanew) ;
							if (pHeader->Signature == 0x00004550 )
							{
								switch(pHeader->FileHeader.Machine)
								{
									case 0x8664: //AMD64
									case 0x0200: //IA64
										*pBIs64 = TRUE;
										break;
									default:
										break;

								}
								switch (pHeader->OptionalHeader.Magic)
								{
									case 0x10B:	//Win32 header
										break;
									case 0x20B: //Win64 header
										*pBIs64 = TRUE;
										break;
									case 0x107: //ROM
										break;
								}
							}
						}
					}
				}
			
				CloseHandle(hFile);
				if (*pBIs64)
					pBits = _T("64 bit");
				else
					pBits = _T("32 bit");				
			}
			// DWORD dwError = GetLastError();
			delete [] pBuf;
		}
	}
	
    // Get Version info size  
    dwSize = GetFileVersionInfoSize(lpszFilename, &dwDummy);
    if (dwSize != 0)
    {
		dwVersion = 0;
        // Allocate space to hold the info
        LPBYTE pInfoBuf = (LPBYTE) GlobalAllocPtr (GHND, dwSize);
        if (pInfoBuf != NULL)
        {
            TCHAR   SubBlock[256];
            // Get the info 
            if (GetFileVersionInfo(lpszFilename, 0, dwSize, pInfoBuf))
            {
                // Got version info, get pointer to VS_FIXEDFILEINFO & size
                // (non language dependant part)
                VS_FIXEDFILEINFO FAR *lpBuffer;     // Pointer to version results 
                unsigned int nBuffSize;             // Size of version results

                VerQueryValue(pInfoBuf, _T("\\"), (void far**)&lpBuffer, &nBuffSize);
                if (nBuffSize != 0)
                {
                    // File version stored as 64bit word (4 16 bit values)
                    // Convert this to 32bit word (4 8 bit values)
                    dwVersion =
                     ( (DWORD)(LOBYTE(HIWORD(lpBuffer->dwFileVersionMS))) << 24 
                    |  (DWORD)(LOBYTE(LOWORD(lpBuffer->dwFileVersionMS))) << 16
                    |  (DWORD)(LOBYTE(HIWORD(lpBuffer->dwFileVersionLS))) << 8
                    |  (DWORD)(LOBYTE(LOWORD(lpBuffer->dwFileVersionLS))) );
                } // if we got VS_FIXEDFILEINFO data
				
				//read description?
				if (lpszDescription && nDescLen > 0)
				{
					// Structure used to store enumerated languages and code pages.
					struct LANGANDCODEPAGE 
					{
						WORD wLanguage;
						WORD wCodePage;
					} *lpTranslate;
					UINT  cbTranslate = 0;

					// Read the list of languages and code pages.

					VerQueryValue(pInfoBuf, 
									TEXT("\\VarFileInfo\\Translation"),
									(LPVOID*)&lpTranslate,
									&cbTranslate);

					// Read the file description for each language and code page.

					for( unsigned int i=0; i < (cbTranslate/sizeof(struct LANGANDCODEPAGE)); i++ )
					{
						wsprintf( SubBlock, 
									TEXT("\\StringFileInfo\\%04x%04x\\FileDescription"),
									lpTranslate[i].wLanguage,
									lpTranslate[i].wCodePage);
									char *pbuf;
								
									// Retrieve file description for language and code page "i". 
									VerQueryValue(pInfoBuf, 
									SubBlock, 
									(LPVOID*)&pbuf, 
									&nBuffSize);

						if (!*lpszDescription || lpTranslate[i].wLanguage == GetSystemDefaultLangID())
						{
							strntcpy(lpszDescription, pbuf, min(nBuffSize, nDescLen)); 
							lpszDescription[min(nBuffSize, nDescLen)-1] = 0;
							
							if (strlen(lpszDescription) + 24 < nDescLen)
							
							wsprintf(lpszDescription + strlen(lpszDescription),"- %d.%d.%d.%d %s", 
								(int)(LOBYTE(HIWORD(lpBuffer->dwFileVersionMS))),
								(int)(LOBYTE(LOWORD(lpBuffer->dwFileVersionMS))),
								(int)(LOBYTE(HIWORD(lpBuffer->dwFileVersionLS))),
								(int)(LOBYTE(LOWORD(lpBuffer->dwFileVersionLS))), pBits ); 

						}

						if (lpTranslate[i].wLanguage == GetSystemDefaultLangID())
						{
							break;
						}
					}
				} //read description

            } // If we found version info

            // Let go of the memory
            GlobalFreePtr(pInfoBuf);

        } // If we allocated memory
    } // If file has version info
    else if (GetFileAttributes(lpszFilename) != (DWORD) -1)
    {
		dwVersion = 0;	
    }
    
    return dwVersion;
}  

DWORD GetDllVersion(LPCTSTR lpszDllName, BOOL *pbIs64 /*=NULL*/)
{

        
#ifdef _WIN32

    TCHAR szPath[_MAX_PATH+1];
    LPTSTR pFile;
    if (SearchPath( NULL, lpszDllName, NULL, _MAX_PATH, szPath, &pFile ))
    {
        return GetFileVersion(szPath, NULL, -1, pbIs64);
    }

#else

    HMODULE  hModule;                 // handle to a module
    MODULEENTRY myModuleEntry;        // module entry structure
    
    myModuleEntry.dwSize = sizeof(myModuleEntry);
    
    //The display driver in memory
    hModule = ModuleFindName( &myModuleEntry, lpszDllName); 
    if (hModule != NULL)
    {
        //Path to loaded display driver
        return GetFileVersion(myModuleEntry.szExePath);
    }    

#endif
    return 0L;    

}

void MakeXFaceWnd(const char * szFace)
{
#ifdef _WIN32
    XFaceOb Face;
    HGLOBAL hDib = NULL;
    
    if (!CommDoc || !szFace || !*szFace || !ShowXFace || CommDoc->DocType != DOCTYPE_ARTICLE)
		return;
	
	if (Face.Xface2BMP( szFace, &hDib) == 1 && hDib)
    {
        
        if (hDib && CommDoc->hWndXFace)
		{
			SendMessage(CommDoc->hWndXFace, WM_UPDATEFACE, (WPARAM) hDib, 0);
			GlobalFree(hDib);
			return;
		}
		
		
		RECT rect;
		WINDOWPLACEMENT wndpl;
		int nShow = SW_SHOWNOACTIVATE;

		ZeroMemory(&wndpl, sizeof(WINDOWPLACEMENT));
		wndpl.length = sizeof(WINDOWPLACEMENT);
		
		GetWindowRect(CommDoc->hWndFrame, &rect);

		if (GetWindowPlacement(CommDoc->hWndFrame, &wndpl) )
		{
			if (wndpl.showCmd == SW_MINIMIZE || IsIconic(CommDoc->hWndFrame))
			{
				CopyRect(&rect, &wndpl.rcNormalPosition);
				nShow = SW_HIDE;	
			}
			
		}

		rect.top += ptXFaceCoord.y;
		rect.left += ptXFaceCoord.x;
		rect.right = rect.left + 47 + 4* GetSystemMetrics(SM_CXEDGE);
		rect.bottom = rect.top + 47 + 4* GetSystemMetrics(SM_CYEDGE);
    
		MakeRectVisible(rect);
		if (CommDoc->hWndXFace)
		{
			DestroyWindow(CommDoc->hWndXFace);
		}
    
		CommDoc->hWndXFace = CreateWindow ("WinVnArtFace",
			"",
			 WS_DLGFRAME | WS_POPUP ,
			rect.left,
			rect.top,
			rect.right-rect.left,
			rect.bottom-rect.top,
			CommDoc->hWndFrame,
			NULL,
			hInst,
			(void *)hDib);
		if (CommDoc->hWndXFace)
		{
			//dont want xface to take focus away
			ShowWindow(CommDoc->hWndXFace, nShow);

		}


    }

    if (hDib)
    {
        GlobalFree(hDib);
        hDib = NULL;
    }

#endif
}

void MakeFaceWnd(const char * szFace)
{
#ifdef _WIN32

	HGLOBAL hDib = NULL;
    if (!CommDoc || !szFace || !*szFace || !ShowXFace || CommDoc->DocType != DOCTYPE_ARTICLE)
		return;

	{
		
		if (!g_GDIPlusImage.Init())
		{
			return;
		}
		

		 hDib = g_GDIPlusImage.DIBFromFace( szFace );
	}    

	
	if (hDib)
    {
        
        if (CommDoc->hWndXFace)
		{
			LPBYTE pData = (LPBYTE) GlobalLock(hDib);
			SendMessage(CommDoc->hWndXFace, WM_UPDATEFACE, (WPARAM) pData, 0);
			GlobalUnlock(hDib);
			GlobalFree(hDib);
			return;
		}
		
		
		RECT rect;
		WINDOWPLACEMENT wndpl;
		int nShow = SW_SHOWNOACTIVATE;

		ZeroMemory(&wndpl, sizeof(WINDOWPLACEMENT));
		wndpl.length = sizeof(WINDOWPLACEMENT);
		
		GetWindowRect(CommDoc->hWndFrame, &rect);

		if (GetWindowPlacement(CommDoc->hWndFrame, &wndpl) )
		{
			if (wndpl.showCmd == SW_MINIMIZE || IsIconic(CommDoc->hWndFrame))
			{
				CopyRect(&rect, &wndpl.rcNormalPosition);
				nShow = SW_HIDE;	
			}
			
		}

		rect.top += ptXFaceCoord.y;
		rect.left += ptXFaceCoord.x;
		rect.right = rect.left + 47 + 4* GetSystemMetrics(SM_CXEDGE);
		rect.bottom = rect.top + 47 + 4* GetSystemMetrics(SM_CYEDGE);
    
		MakeRectVisible(rect);
		if (CommDoc->hWndXFace)
		{
			DestroyWindow(CommDoc->hWndXFace);
		}
    
		CommDoc->hWndXFace = CreateWindow ("WinVnArtFace",
			"",
			 WS_DLGFRAME | WS_POPUP ,
			rect.left,
			rect.top,
			rect.right-rect.left,
			rect.bottom-rect.top,
			CommDoc->hWndFrame,
			NULL,
			hInst,
			(void *)hDib);
		if (CommDoc->hWndXFace)
		{
			//dont want xface to take focus away
			ShowWindow(CommDoc->hWndXFace, nShow);

		}



        GlobalFree(hDib);
        hDib = NULL;
    }

#endif
}



//returns the number of bytes copied
int figurePathFromPidl( LPITEMIDLIST pidl, TCHAR *szRetPath, int nPathLen)
{
	TCHAR szPath[_MAX_PATH] = {'\0'};

	if (! SHGetPathFromIDList(pidl, szPath))
		*szPath = '\0'; // just 2 b safe
	// Allocate a pointer to an IMalloc interface
	LPMALLOC pMalloc;

	// Get the address of our task allocator's IMalloc interface
	if (SUCCEEDED( SHGetMalloc(&pMalloc)))
	{	// Free the item ID list allocated by SHGetSpecialFolderLocation
		pMalloc->Free(pidl);

		// Free our task allocator
		pMalloc->Release();
	}
	if (szRetPath)
	{
		_tcsncpy(szRetPath, szPath, nPathLen);
	}
	return min((int) _tcslen(szPath), nPathLen);
}

int getSpecialFolder( UINT uCSIDL, TCHAR *szRetPath, int nPathLen)
{
	LPITEMIDLIST pidl;
	HRESULT hr = SHGetSpecialFolderLocation(NULL, uCSIDL, &pidl);

	if (SUCCEEDED( hr))
		return figurePathFromPidl( pidl, szRetPath, nPathLen);
	else
		return 0;

}

BOOL WVGetFileWriteTime(LPCTSTR szFile, FILETIME& writeTime)
{

	BOOL bOK = FALSE;
	HANDLE hFile = ::CreateFile(szFile, GENERIC_READ , FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, 
					 OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
	
	writeTime.dwLowDateTime = 0;
	writeTime.dwHighDateTime = 0;

	if (hFile != INVALID_HANDLE_VALUE)
	{
		bOK =  GetFileTime(hFile,NULL,NULL, &writeTime);
		CloseHandle(hFile);
	}

	return bOK;

}


/////////////////////////////////////////////////////////////////////////////
//   FUNCTION: CenterWindow (HWND, HWND)
//   PURPOSE:  Center one window over another
//   COMMENTS:
//      Dialog boxes take on the screen position that they were designed
//      at, which is not always appropriate. Centering the dialog over a
//      particular window usually results in a better position.
//
BOOL WINAPI CenterWindow (HWND hwndChild, HWND hwndParent)
{
	RECT    rChild, rParent= {0,0,0,0};
	int     wChild, hChild, wParent, hParent;
	int     wScreen, hScreen, xNew, yNew;
	HDC     hdc;
	WINDOWPLACEMENT wndpl;

	if (hwndParent == NULL)
		hwndParent = GetActiveWindow();
	if (hwndParent == NULL)
		hwndParent = GetDesktopWindow();

	wndpl.length = sizeof(WINDOWPLACEMENT);
	if (GetWindowPlacement( hwndParent, &wndpl ))
	{
		CopyRect(&rParent, &wndpl.rcNormalPosition);
	}
   
	// Get the Height and Width of the child window
	GetWindowRect (hwndChild, &rChild);
	wChild = rChild.right - rChild.left;
	hChild = rChild.bottom - rChild.top;

	// Get the Height and Width of the parent window
	//GetWindowRect (hwndParent, &rParent);
	wParent = rParent.right - rParent.left;
	hParent = rParent.bottom - rParent.top;

	// Get the display limits
	hdc = GetDC (hwndChild);
	wScreen = GetDeviceCaps (hdc, HORZRES);
	hScreen = GetDeviceCaps (hdc, VERTRES);
	ReleaseDC (hwndChild, hdc);

	// Calculate new X position, then adjust for screen
	xNew = rParent.left + ((wParent - wChild) /2);
	if (xNew < 0) 
	{
		xNew = 0;
	}
	else if ((xNew+wChild) > wScreen) 
	{
		xNew = wScreen - wChild;
	}

	// Calculate new Y position, then adjust for screen
	yNew = rParent.top  + ((hParent - hChild) /2);
	if (yNew < 0) 
	{
		yNew = 0;
	}
	else if ((yNew+hChild) > hScreen) 
	{
		yNew = hScreen - hChild;
	}

	// Set it, and return
	return SetWindowPos (hwndChild, NULL,
			xNew, yNew, 0, 0, SWP_NOSIZE | SWP_NOZORDER);
}

//Retrive the distance between two windows
BOOL WINAPI GetRelativeWindowPosition(HWND hWndParent, HWND hWndChild, POINT* pptOffset)
{

    RECT rChild, rParent;

    if (!GetWindowRect(hWndChild, &rChild))
		return FALSE;

    if (!IsIconic( hWndParent))
	{
		if (!GetWindowRect(hWndParent, &rParent))
			return FALSE;
	}
	else
	{
		WINDOWPLACEMENT wndpl;
		ZeroMemory(&wndpl, sizeof(WINDOWPLACEMENT));
		wndpl.length = sizeof(WINDOWPLACEMENT);


		if (GetWindowPlacement(hWndParent, &wndpl) )
		{
			CopyRect(&rParent, &wndpl.rcNormalPosition);
		}
		else
			return FALSE;

	}
	pptOffset->x = rChild.left - rParent.left;
	pptOffset->y = rChild.top - rParent.top;

    return TRUE;

}

//Move a window to a point relative to another window

BOOL WINAPI SetRelativeWindowPosition( HWND hWndParent, HWND hWndChild, POINT ptOffset)
{

    RECT rChild, rParent;

    if (!GetWindowRect(hWndChild, &rChild))
		return FALSE;

    if (!IsIconic( hWndParent))
	{
		if (!GetWindowRect(hWndParent, &rParent))
			return FALSE;
	}
	else
	{
		WINDOWPLACEMENT wndpl;
		ZeroMemory(&wndpl, sizeof(WINDOWPLACEMENT));
		wndpl.length = sizeof(WINDOWPLACEMENT);


		if (GetWindowPlacement(hWndParent, &wndpl) )
		{
			CopyRect(&rParent, &wndpl.rcNormalPosition);
		}
		else
			return FALSE;

	}
	ptOffset.x += rParent.left;
	ptOffset.y += rParent.top;

	OffsetRect(&rChild, -rChild.left, -rChild.top);
	OffsetRect(&rChild, ptOffset.x, ptOffset.y);

	MakeRectVisible(rChild);
    
	return SetWindowPos( hWndChild, NULL, rChild.left, rChild.top, 0,0, SWP_NOSIZE | SWP_NOZORDER );
}

/////////////////////////////////////////////////////////////////////////////
// BOOL MakeRectVisible(RECT& rect)
//
// Adjust coordiates of rect to fit on screen if possible
//
// Input: pointer to rect to be displayed
// Output: Adjusted Rect
// Returns:
//         BOOL could rect fit on screen?
//
/////////////////////////////////////////////////////////////////////////////
BOOL MakeRectVisible(RECT& rRect)
{
    RECT rSystem;
    int rWidth, rHeight, abit;
    if (rRect.bottom < rRect.top)
    {
		int nTemp = rRect.bottom;
		rRect.bottom = rRect.top;
		rRect.top = nTemp;
    }
    if (rRect.right < rRect.left)
    {
		int nTemp = rRect.right;
		rRect.right = rRect.left;
		rRect.left = nTemp;
    }

    
    //Get virtual desktop if multimonitor else fullscreen size
    rSystem.left = ::GetSystemMetrics(SM_XVIRTUALSCREEN);
    rSystem.top  = ::GetSystemMetrics(SM_YVIRTUALSCREEN);
    rSystem.right  = rSystem.left + ::GetSystemMetrics(SM_CXVIRTUALSCREEN);
    rSystem.bottom = rSystem.top  + ::GetSystemMetrics(SM_CYVIRTUALSCREEN);
    
    if (IsRectEmpty(&rSystem) )
    {
        //just to be sure
        rSystem.top = 0;
        rSystem.left = 0;
        rSystem.right = rSystem.left + ::GetSystemMetrics(SM_CXFULLSCREEN);
        rSystem.bottom = rSystem.top + ::GetSystemMetrics(SM_CYFULLSCREEN);
    }
    rWidth = RectWidth(rRect);
    rHeight = RectHeight(rRect);
    abit   = ::GetSystemMetrics(SM_CYMENU);

    //rRect->NormalizeRect();

    //Is some point of upper-left corner of window on virtual desktop?
    POINT tl = {rRect.left, rRect.top};
	POINT br = {rRect.right, rRect.bottom};
	POINT mid = { rRect.left + rWidth/2, rRect.top + rHeight/2};

    //is rect visible on any monitor?
	if ( MonitorFromPoint(tl, MONITOR_DEFAULTTONULL) && MonitorFromPoint(br, MONITOR_DEFAULTTONULL)  ) 
		return TRUE;

	//nope, move it to some monitor
	HMONITOR hMon = MonitorFromRect( &rRect, MONITOR_DEFAULTTONULL);
	if (!hMon)
		hMon = MonitorFromPoint( mid, MONITOR_DEFAULTTONEAREST);
	if (hMon)
	{
		//rect may be on virtual desktop but is not on screen
		MONITORINFO monInfo;

		ZeroMemory( &monInfo, sizeof(MONITORINFO));
		monInfo.cbSize = sizeof(MONITORINFO);
		if (GetMonitorInfo(hMon, &monInfo))
		{
			rSystem = monInfo.rcWork;
		}

	}
    

                
    if (rRect.right > rSystem.right)
    {
        //slide right edge onscreen
        rRect.left  = rSystem.right - rWidth;
        rRect.right = rSystem.right;
    }
    if (rRect.top > rSystem.bottom)
    {
        //slide top edge on screen plus a bit
        rRect.top = rSystem.bottom - abit - rHeight;
		rRect.bottom = rRect.top + rHeight;
    }

    if (rRect.top < rSystem.top)
    {
        rRect.bottom = rSystem.top + rHeight; 
        rRect.top = rSystem.top;
                
    }
    if (rRect.left < rSystem.left)
    {
        rRect.right = rSystem.left + rWidth;
        rRect.left = rSystem.left;
    }

    
    if ( rWidth > RectWidth(rSystem) || rHeight > RectHeight(rSystem))
        return FALSE;    //too big to fit on screen
    
    return TRUE;

}

void MoveControl(HWND hWnd, UINT nID, int x, int y)
{
    HWND hChild;
    RECT rect;

    if (!hWnd)
        return;
    
    hChild = GetDlgItem(hWnd, nID);
    
    if (!hChild)
        return;
    InvalidateRect(hChild, NULL, FALSE);
    GetWindowRect(hChild, &rect);
    ScreenToClient(hWnd, (LPPOINT) &rect);
    OffsetRect(&rect, x,y);

    SetWindowPos( hChild, NULL,  rect.left, rect.top, 0, 0, 
                SWP_NOZORDER|SWP_NOSIZE );

         
} // MoveControl

void GrowWindow(HWND hWnd, UINT nID, int x, int y)
{
    HWND hChild;
    RECT rect;
    
    if (!hWnd)
        return;

    hChild = GetDlgItem(hWnd, nID);  
    
    if (!hChild)
        return;

    InvalidateRect(hChild, NULL, FALSE);
    GetWindowRect(hChild, &rect);
    ScreenToClient(hWnd, (LPPOINT) &rect);

    ScreenToClient(hWnd, (LPPOINT) &rect.right);

    rect.right += x;
    rect.bottom += y;

    SetWindowPos(hChild, NULL,  0, 0, rect.right-rect.left, rect.bottom-rect.top, 
                SWP_NOZORDER|SWP_NOMOVE  );
}


//Add text to a list control, resizing columns to fit text extent
BOOL AddLCText(int nItem, int nCol, LPCSTR szText, HWND hList)
{
	BOOL bReturn = FALSE;
	if (!hList || !szText || !IsWindow(hList))
		return FALSE;

	int nMaxItemLength = ListView_GetColumnWidth(hList, nCol);
	//single column auto extent resize (scrollbar)
    //if (m_numCol == 1 )
    {
		HDC hDC = GetDC(hList);
		if (hDC)
		{
			
			HFONT  hFont = GetWindowFont(hList);
			HFONT hOldFont = NULL;

			if (hFont)
				hOldFont = (HFONT) SelectObject(hDC, hFont);

			SIZE extent;
			GetTextExtentPoint32(hDC, szText, _tcslen(szText),&extent);

			if (nCol == 0 && (ListView_GetExtendedListViewStyle(hList) & LVS_EX_CHECKBOXES) != 0)
			{

				LV_ITEM lvi;

				lvi.mask = LVIF_IMAGE | LVIF_STATE | LVIF_INDENT | LVIF_PARAM;
				lvi.iItem = nItem;
				lvi.iSubItem =0;
				lvi.pszText = 0;
				lvi.cchTextMax = 0;
				lvi.stateMask = 0xFFFF;
				
				ListView_GetItem(hList, &lvi); 

				
				//if checkbox style
				UINT nStateImageMask = lvi.state & LVIS_STATEIMAGEMASK;
				if (nStateImageMask)
				{
					//dont care which image, All images same size in imagelist
					//int nImage = (nStateImageMask>>12) - 1;
					HIMAGELIST hIL = (HIMAGELIST)SendMessage(hList, LVM_GETIMAGELIST, (WPARAM)LVSIL_STATE, 0);					

					if (hIL)
					{
						int cxIcon=0,cyIcon=0;
						ImageList_GetIconSize(hIL, &cxIcon, &cyIcon);
						extent.cx  += cxIcon;
					}
				} 


			}
			else
			{
				extent.cx += 12;

			}

			if ( nMaxItemLength < ListView_GetColumnWidth(hList, nCol))
                nMaxItemLength = ListView_GetColumnWidth(hList, nCol);

            if (extent.cx > nMaxItemLength)
			{
				
                nMaxItemLength = extent.cx;

				ListView_SetColumnWidth(hList, nCol, nMaxItemLength);

			}
			if (hOldFont)
				SelectObject(hDC, hOldFont);

			ReleaseDC(hList, hDC);
		}

		LVITEM lvi;
		lvi.iSubItem = nCol;
		lvi.pszText = (LPTSTR) szText;
		bReturn = (BOOL) SendMessage(hList, LVM_SETITEMTEXT, nItem, (LPARAM)&lvi);

    }
	return bReturn;
}

const char *WVGetMIMETypeForFile(char *szFile)
{

	TCHAR extn[_MAX_EXT];
	TCHAR szCmp[_MAX_EXT];

	GetFileExtension(extn, szFile);

	for (int i = 0; *extn && i < NUMFILETYPES; i++)
	{

		
		if (!_stricmp(extn, kFileTypes[i].szExt))
		{
			//matched
			return kFileTypes[i].szMIMEType;
		}
		else if (kFileTypes[i].szAlt)
		{
			
			TCHAR *p;
			int nLen;
			p = kFileTypes[i].szAlt;
			
			while (p && *p)
			{
				nLen = strcspn(p, " ,");
				if (nLen)
				{
					_tcsncpy(szCmp, p, nLen);
					szCmp[nLen] = 0;
					p += nLen +1;
				}
				else
				{
					_tcscpy(szCmp, p);
					p = NULL;
					
				}

			    if (_tcslen(extn) == _tcslen(szCmp) )
				{
					BOOL bMatch = TRUE;
					for (size_t j = 0; j < _tcslen(extn) && bMatch; j++)
					{
						if ( szCmp[j] != _T('#') && isdigit(extn[j]))
							bMatch = FALSE;
						else if ( tolower(szCmp[j] ) != tolower(extn[j]))
							bMatch = FALSE; 

					}
					if (bMatch)
					{
						return kFileTypes[i].szMIMEType;
					}
				
				}
			
			}


		}
	}

	return ContentTypes[APPOCT];

}

#define MAX_NUMERIC_LEN 32
LPCSTR FormatNumber( __int64 i, TCHAR * szBuf /*=NULL*/)
{
	static NUMBERFMT numFmt = {0};
	static TCHAR bufILZero[3] = {0};
	static TCHAR bufINegNum[3] = {0};
	static TCHAR bufSThousands[5] = {0};
	static TCHAR bufSDecimal[5] = {0};
	static BOOL bInit = FALSE;
	TCHAR szBufIn[MAX_NUMERIC_LEN] = {0};
	static TCHAR strOut[MAX_NUMERIC_LEN] = {0};
	_stprintf(szBufIn, _T("%I64d"), i);
	
	if (!bInit)
	{

		GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_ILZERO, bufILZero, 3);
		numFmt.LeadingZero = _ttoi(bufILZero);
		GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_INEGNUMBER, bufINegNum, 3);
		numFmt.NegativeOrder = _ttoi(bufINegNum);
		numFmt.Grouping = 3;
		GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_STHOUSAND, bufSThousands, 5);
		numFmt.lpThousandSep = bufSThousands;
		GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SDECIMAL, bufSDecimal, 5);
		numFmt.lpDecimalSep = bufSDecimal;
		numFmt.NumDigits = 0;
		bInit = TRUE;
	}
	GetNumberFormat(NULL, NULL, szBufIn, &numFmt, strOut, MAX_NUMERIC_LEN); 
	if (szBuf)
		_tcscpy(szBuf, strOut);
	return strOut;
}

int xGetWindowDPI(HWND hwnd)
{
	int nDPI = ScreenYPixels;
	
	if (!::IsWindow(hwnd))
		hwnd = NULL;
		
	UINT (WINAPI* pGetDpiForWindow)(HWND hwnd);
	pGetDpiForWindow = (UINT (WINAPI*)(HWND hwnd)) ::GetProcAddress(LoadLibrary("USER32"), "GetDpiForWindow");
	if (pGetDpiForWindow) 
	{
		return pGetDpiForWindow(hwnd);
	}
	HDC hdc = GetDC (hwnd);
	if (hdc)
	{
		nDPI = GetDeviceCaps (hdc, LOGPIXELSY);
		ReleaseDC(hwnd, hdc);
	}
	return nDPI;
}




