/*---------------------------------------------------------------------------
 *
 *  Module: mswin.c
 *
 * Thomas Unger
 * Networks and Distributed Computing
 * Computing and Communications
 * University of Washington
 * Administration Builiding, AG-44
 * Seattle, Washington, 98195, USA
 * Internet: tunger@cac.washington.edu
 *
 * Copyright 1991-1994  University of Washington
 *
 *  Permission to use, copy, modify, and distribute this software and its
 * documentation for any purpose and without fee to the University of
 * Washington is hereby granted, provided that the above copyright notice
 * appears in all copies and that both the above copyright notice and this
 * permission notice appear in supporting documentation, and that the name
 * of the University of Washington not be used in advertising or publicity
 * pertaining to distribution of the software without specific, written
 * prior permission.  This software is made available "as is", and
 * THE UNIVERSITY OF WASHINGTON DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED,
 * WITH REGARD TO THIS SOFTWARE, INCLUDING WITHOUT LIMITATION ALL IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, AND IN
 * NO EVENT SHALL THE UNIVERSITY OF WASHINGTON BE LIABLE FOR ANY SPECIAL,
 * INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
 * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, TORT
 * (INCLUDING NEGLIGENCE) OR STRICT LIABILITY, ARISING OUT OF OR IN CONNECTION
 * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 *
 * Pine and Pico are trademarks of the University of Washington.
 * No commercial use of these trademarks may be made without prior
 * written permission of the University of Washington.
 *
 *
 *--------------------------------------------------------------------------*/

#define WIN31 
#define STRICT

#include <windows.h>
#include <commdlg.h>
#include <print.h>
#include <cderr.h>
#include <toolhelp.h>

#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <errno.h>
#include <signal.h>
#include <setjmp.h>
/*#include <conio.h>*/
#include <time.h>
/*#include <signal.h>*/
#include <fcntl.h>

#define	termdef	1			/* don't define "term" external */

#include "osdep.h"
#include "pico.h"
#include "estruct.h"
#include "efunc.h"
#include "edef.h"



/* Windows only version and resource defines. */
#include "resource.h"




/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 *
 *			Defines
 *
 *++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/





/* For debugging, export locals so debugger can see them. */
#ifdef DEBUG
#define LOCAL
#else
#define LOCAL		static
#endif

/* Define which debugging is deisred.  Generally only FDEBUG. */
#define FDEBUG		/* Standard file debugging. */

#undef SDEBUG		/* Verbose debugging of startup and windows handling*/
#undef CDEBUG		/* Verbose debugging of character input timeing. */

#undef  MEM_DEBUG	/* Memory debugging. */
#define MEM_DEBUG_LEVEL		9

/* Define if we want to write to our own debug file, not pine's. */
#undef OWN_DEBUG_FILE



#define GWL_PTTYINFO		0	/* Offset in Window extra storage. */

#define ABOUTDLG_USEBITMAP	1



/* Max size permitted for the screen.  Larger than ever expec, but small
 * enough to prevent errors.  And small enough that the size of the
 * screen strucure is less than 64K. */
#define MAXNROW			180
#define MAXNCOLUMN		256

#define MINNROW			10	/* Minimum screen size */
#define MINNCOLUMN		32

#define MARGINE_LEFT		3
#define MARGINE_TOP		1

#define WIN_MIN_X_SIZE		190	/* Minimum window size. */
#define WIN_MIN_Y_SIZE		180

#define WIN_X_BORDER_SIZE	8	/* Space taked by window frame. */
#define WIN_Y_BORDER_SIZE	65

#define FONT_MIN_SIZE		5
#define FONT_MAX_SIZE		21

#define WIN_POS_STR_MAX_LEN	20	/* Max length for window-position
					 * string. */



/* Max size for temp storage. */
#define MAXLEN_TEMPSTR		256



/* Length of keyboard input queue. */
#define CHARACTER_QUEUE_LENGTH	32
#define MOUSE_QUEUE_LENGTH	32


/* Number of resize callback functions we can keep track of. */
#define RESIZE_CALLBACK_ARRAY_SIZE	3


/* Number of bytes held in the write accumulator. */
#define WRITE_ACCUM_SIZE		200



/* Max size of a directory path. */
#define PATH_MAX		128



/* Cursor states. */
#define CS_SHOW         0x01		/* Cursor is not hidden. */
#define CS_FOCUSED	0x02		/* Window is focused. */
#define CS_VISIBLE	0x03		/* When above two bits set, cursor is
					 * visible. */

/* Auto Wrap States. */
#define WRAP_OFF	0		/* Never wrap to next line. */
#define WRAP_ON		1		/* Wrap to next line. */
#define WRAP_NO_SCROLL	2		/* Wrap to next line but DON'T scroll
					   screen to do it. */

/* Speicial keys in the Character Queue. */
#define CQ_FLAG_DOWN		0x01
#define CQ_FLAG_EXTENDED	0x02
#define CQ_FLAG_ALT		0x04



/* Special ASCII characters. */
#define ASCII_BEL       0x07
#define ASCII_BS        0x08
#define ASCII_LF        0x0A
#define ASCII_CR        0x0D
#define ASCII_XON       0x11
#define ASCII_XOFF      0x13




/* Character Attributes. */
#define CHAR_ATTR_NORM	0x00		/* Normal. */
#define CHAR_ATTR_REV	0x01		/* Reverse Video. */
#define CHAR_ATTR_SEL	0x02		/* Selected text. */



/* My Timer Message */
#define MY_TIMER_ID	33
#define MY_TIMER_PERIOD (UINT)60000	/* timeout period in miliseconds. */
#define MY_TIMER_SHORT_PERIOD (UINT)5000  /* shorter period for when more
					     frequent checks are desired. */

#define TIMER_FAIL_MESSAGE "Failed to get all necessary Windows resoruce (timers).  Pine will run, but may not be able to keep the connection to the server alive.  Quiting other applications and restarting Pine may solve the problem."


/* Max time that may pass between calls to GetMessage.  See mswin_charavail()
 */
#define GM_MAX_TIME	3000		/* In milliseconds.*/




/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 *
 *			Typedefs
 *
 *++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/

/* Type that the screen array is made up of. */
typedef unsigned char		CHAR;

/* Type that the attribute array is made up of. */
typedef BYTE			CharAttrib;
typedef int			(*ResizeCallBackProc)();

/* NOTE:  There is currently code that assumes that CHAR and CharAttrib
 *	are one byte in size.  All this cold is flaged with a preceeding
 *	assert () */


/* General info. */
typedef struct tagTTYINFO {
    CHAR	*pScreen;	/* Screen. */
    BYTE	*pAttrib;	/* Attributes. */
    BOOL	screenDirty;	/* TRUE if screen needs update. */
    BOOL	eraseScreen;	/* TRUE if need to erase whole screen */
#ifdef OWNRECT
    RECT	rPaintRect;	/* Rectangle that needs to be repainted. */
#endif
    CHAR	writeAccum[WRITE_ACCUM_SIZE];
    int		writeAccumCount;
    WORD	wCursorState;	/* Is cursor displayed? */
    HFONT	hTTYFont;
    LOGFONT	lfTTYFont;
    DWORD	rgbFGColor;	/* Normal forground color. */
    DWORD	rgbBGColor;	/* Normal background color. */
    DWORD	rgbRFGColor;	/* Reverse forground color. */
    DWORD	rgbRBGColor;	/* Reverse background color */
    BOOL	fMinimized;	/* True when window is minimized. */
    BOOL	fFocused;	/* True when we have focus. */
    BOOL	fNewLine;	/* Auto LF on CR. */
    BOOL	fMassiveUpdate;	/* True when in Massive screen update. */
    ResizeCallBackProc  resizer[RESIZE_CALLBACK_ARRAY_SIZE];
    int		autoWrap;	/* Auto wrap to next line. */
    CharAttrib	curAttrib;	/* Current character attributes. */
    int		actNRow, actNColumn;	/* Actual number of rows and comumns
					 * displayed. */
    int		xSize, ySize;		/* Size of screen in pixels */
    int		xScroll, yScroll;	/* ?? */
    int		xOffset, yOffset;	/* Amount of scroll offset 
				         * (always zero) in pixels */
    int		nColumn, nRow;		/* Current position of cursor in 
				         * cells. */
    int		xChar, yChar;		/* Width of a char in pixels. */
    int		fDesiredSize;		/* TRUE when there is a specific size
					 * the window should be expanded to
					 * after being minimized. */
    int		xDesPos, yDesPos;	/* Desired position. */
    int		xDesSize, yDesSize;	/* Desired window position. */
} TTYINFO, *PTTYINFO ;



typedef struct MSWINColor {
    char		*colorName;
    COLORREF		colorRef;
} MSWINColor;


/*
 * Entry in the OnTask list.  This is a list of actions to perform
 * when a task exits.  Currently, the only thing we do is delete
 * files.  if that changes this structure will get more complex.
 */
typedef struct ontask {
    struct ontask	*next;
    HTASK		hTask;
    char		path[PATH_MAX+1];
} OnTaskItem;


/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 *
 *			Forward function declarations.
 *
 *++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/



#define GETHINST( hWnd )  ((HINSTANCE) GetWindowWord( hWnd, GWW_HINSTANCE ))

#define MIN(x,y)	((x) < (y) ? (x) : (y))
#define MAX(x,y)	((x) > (y) ? (x) : (y))


// function prototypes (private)

LOCAL BOOL	InitApplication (HANDLE);
LOCAL HWND	InitInstance (HANDLE, int);
LOCAL void	MakeArgv (HINSTANCE hInstance, LPSTR cmdLine, int *pargc, 
				char ***pargv);
LOCAL LRESULT NEAR	CreateTTYInfo (HWND hWnd);
LOCAL BOOL NEAR	DestroyTTYInfo (HWND hWnd);
LOCAL int	ResizeTTYScreen (HWND hWnd, PTTYINFO pTTYInfo, 
					int newNRow, int newNColumn);
LOCAL BOOL	ResetTTYFont (HWND, PTTYINFO, LOGFONT *);
#ifdef OWNRECT
void		MyInvalidateRect (PTTYINFO pTTYInfo, HWND hWnd, RECT *r);
#endif
LOCAL BOOL	PaintTTY (HWND);
LOCAL BOOL	GetMinMaxInfoTTY (HWND hWnd, MINMAXINFO __far *lpmmi);
LOCAL BOOL	AboutToSizeTTY (HWND hWnd, WINDOWPOS *winPos);
LOCAL BOOL	SizeTTY (HWND, int, WORD, WORD);
LOCAL BOOL	MoveTTY (HWND hWnd, int xPos, int yPos);
LOCAL BOOL	SetTTYFocus (HWND);
LOCAL BOOL	KillTTYFocus (HWND);
LOCAL BOOL	MoveTTYCursor (HWND);
LOCAL BOOL	ProcessTTYKeyDown (HWND hWnd, WORD bOut, DWORD keyData);
LOCAL BOOL	ProcessTTYKeyUp (HWND hWnd, WORD key, DWORD keyData);
LOCAL BOOL	ProcessTTYCharacter (HWND hWnd, WORD bOut, DWORD keyData);
LOCAL BOOL	ProcessTTYMouse (int mevent, int button, int xPos, 
					int yPos, WORD keys);
LOCAL void	ScrollTTY (HWND hWnd, int scroll, int maxRows);
LOCAL BOOL	WriteTTYBlock (HWND, LPSTR, int);
LOCAL BOOL	WriteTTYText (HWND, LPSTR, int);
LOCAL BOOL	WriteTTYChar (HWND, char);
LOCAL VOID	GoModalDialogBoxParam (HINSTANCE, LPCSTR, HWND, 
					DLGPROC, LPARAM);
LOCAL BOOL	SelectTTYFont (HWND);
LOCAL void	SetColorAttribute (COLORREF *cf, char *colorName);
LOCAL BOOL	ConvertRGBString (char *colorName, COLORREF *cf);
LOCAL BOOL	ScanInt (char *str, int min, int max, int *val);

LOCAL void	SelStart (int nRow, int nColumn);
LOCAL void	SelFinish (int nRow, int nColumn);
LOCAL void	SelClear (void);
LOCAL void	SelTrackXYMouse (int xPos, int yPos);
LOCAL void	SelTrackMouse (int nRow, int nColumn);
LOCAL BOOL	SelAvailable (void);
LOCAL void	SelDoCopy (HANDLE hCB, DWORD lenCB);

LOCAL void	FlushWriteAccum (void);

		/* ... interface routines ... */

LOCAL void	FileQuit (void);

LOCAL void	UpdateMenu (void);
LOCAL void	EditCut (void);
LOCAL void	EditCopy (void);
LOCAL void	EditCopyAppend (void);
LOCAL void	EditDoCopyData (HANDLE hCB, DWORD lenCB);
LOCAL void	EditPaste (void);
LOCAL void	EditCancelPaste (void);
LOCAL WORD	EditPasteGet (void);
LOCAL BOOL	EditPasteAvailable (void);

LOCAL void	ShowHelp (void);

LOCAL void	ProcessOnTask (void);

LOCAL void	CQInit (void);
LOCAL BOOL	CQAvailable (void);
LOCAL BOOL	CQAdd (WORD c, DWORD keyData);
LOCAL WORD	CQGet ();

LOCAL void	MQInit (void);
LOCAL BOOL	MQAvailable (void);
LOCAL BOOL	MQAdd (int mevent, int button, int nRow, int nColumn, 
				unsigned int keys);
LOCAL BOOL	MQGet (MEvent * pmouse);

LOCAL int	MapVKtoMS (WORD c, WORD flags);



/* Functions exported to MS Windows. */

LRESULT FAR PASCAL __export PWndProc (HWND, UINT, WPARAM, LPARAM);
BOOL FAR PASCAL __export AboutDlgProc (HWND, UINT, WPARAM, LPARAM);







/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 *
 *			Exported Globals
 *
 *++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/



/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 *
 *			Imported Globals
 *
 *++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/

/* From line.c */
extern char    *kbufp;               /* Kill buffer data             */
extern unsigned kused;               /* # of bytes used in KB        */




/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 *
 *			Module globals.
 *
 *++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/


LOCAL PTTYINFO		gpTTYInfo;
LOCAL HWND		ghTTYWnd;
LOCAL char		TempBuf [MAXLEN_TEMPSTR];

LOCAL char		gszTTYClass[] = "TTYWndClass";
LOCAL char		gszAppName[45];

LOCAL HANDLE		ghAccel;

LOCAL BOOL		gfInitPhase;

/* Used for Pasting text. */
LOCAL HANDLE		ghPaste = NULL;		/* Handle to Paste data. */
LOCAL char		*gpPasteNext = NULL;	/* Pointer to current char. */
LOCAL size_t		gPasteBytesRemain = 0;	/* Count of bytes left. */
LOCAL BOOL		gPasteWasCR = FALSE;	/* Previous char was CR. */
LOCAL int		gPasteEnabled = MSWIN_PASTE_DISABLE;
LOCAL getc_t		gCopyCutFunction = NULL;
LOCAL BOOL		gAllowCopy = FALSE;
LOCAL BOOL		gAllowCut = FALSE;
LOCAL BOOL		gAllowExit = MSWIN_EXIT_DISABLE;
LOCAL char	        *gExitDenyReason;
LOCAL int		gExitSendChar;

LOCAL BOOL		gKeyControlDown = FALSE;	/* Keep track of the control
						 * key position. */

LOCAL char		*gpHelpText;		/* Pointer to help text. */
LOCAL BOOL		gfHelpMenu = FALSE;	/* TRUE when help menu 
						 * installed. */
LOCAL char		*gpCloseText;

LOCAL FILE		*DebugFile = NULL;
LOCAL int		Debug = 0;

LOCAL DWORD		gGMLastCall = 0;	/* Last time I called
						 * GetMessage. */
LOCAL OnTaskItem	*gOnTaskList = NULL;	/* Things to dowhen a task
						 * exits. */


LOCAL MSWINColor  MSWINColorTable[] =  {
	"black",	RGB(0,0,0),
	"blue",		RGB(0,0,255),
	"green",	RGB(0,255,0),
	"cyan",		RGB(0,255,255),
	"red",		RGB(255,0,0),
	"magenta",	RGB(255,0,255),
	"yellow",	RGB(255,255,0),
	"white",	RGB(255,255,255),
	"ltgray",	RGB(192,192,192),
	"gray",		RGB(128,128,128),
	"dkgray",	RGB(64,64,64),
	NULL,		0,
};







/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 *
 *			Windows Functions.
 *
 *++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/

/*---------------------------------------------------------------------------
 *  int PASCAL WinMain( HANDLE hInstance, HANDLE hPrevInstance,
 *                      LPSTR lpszCmdLine, int nCmdShow )
 *
 *  Description:
 *     This is the main window loop!
 *
 *  Parameters:
 *     As documented for all WinMain() functions.
 *
 *--------------------------------------------------------------------------*/

int PASCAL
WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,
                    LPSTR lpszCmdLine, int nCmdShow )
{
    char		**argv;
    int			argc;

    gfInitPhase = TRUE;
    if (!hPrevInstance)
	if (!InitApplication( hInstance ))
	    return ( FALSE ) ;
    
#ifdef OWN_DEBUG_FILE	/* Want to write to seperate memdebug.txt file. */
    DebugFile = fopen ("memdebug.txt", "w");
    fprintf (DebugFile, "Beginning of mswin debug log\n");
    if (DebugFile != NULL) {
	Debug = 4;
	MemDebug (FALSE, DebugFile);
	fprintf (DebugFile, "Show window as:  %d\n", nCmdShow);
	fflush (DebugFile);
    }
#endif

    if (NULL == (ghTTYWnd = InitInstance (hInstance, nCmdShow)))
	return (FALSE);


    MakeArgv (hInstance, lpszCmdLine, &argc, &argv);
    
    gfInitPhase = FALSE;
    app_main (argc, argv);

#ifdef OWN_DEBUG_FILE
    fclose (DebugFile);
#endif
    /* Take one last look at the task list. */
    ProcessOnTask ();

    return (TRUE);
}


/*---------------------------------------------------------------------------
 *  BOOL  InitApplication( HANDLE hInstance )
 *
 *  Description:
 *     First time initialization stuff.  This registers information
 *     such as window classes.
 *
 *  Parameters:
 *     HANDLE hInstance
 *        Handle to this instance of the application.
 *
 *--------------------------------------------------------------------------*/

LOCAL BOOL  
InitApplication (HANDLE hInstance)
{
    WNDCLASS  wndclass;

    /* 
     * Register tty window class.
     */

    wndclass.style =         CS_NOCLOSE;
    wndclass.lpfnWndProc =   PWndProc;
    wndclass.cbClsExtra =    0;
    wndclass.cbWndExtra =    sizeof (LONG);
    wndclass.hInstance =     hInstance ;
    wndclass.hIcon =         LoadIcon (hInstance, MAKEINTRESOURCE( PINEICON));
    wndclass.hCursor =       LoadCursor (NULL, IDC_ARROW);
    wndclass.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1);
    wndclass.lpszMenuName =  MAKEINTRESOURCE (PINEMENU);
    wndclass.lpszClassName = gszTTYClass ;

    return (RegisterClass (&wndclass));
}

/*---------------------------------------------------------------------------
 *  HWND  InitInstance( HANDLE hInstance, int nCmdShow )
 *
 *  Description:
 *     Initializes instance specific information.
 *
 *  Parameters:
 *     HANDLE hInstance
 *        Handle to instance
 *
 *     int nCmdShow
 *        How do we show the window?
 *
/*--------------------------------------------------------------------------*/

LOCAL HWND  
InitInstance (HANDLE hInstance, int nCmdShow)
{
    HWND  hTTYWnd;

#ifdef SDEBUG
    if (Debug >= 5) 
	fprintf (DebugFile, "InitInstance:::  entered, nCmdShow %d\n",
		nCmdShow);
#endif

    /* load accelerators */
    ghAccel = LoadAccelerators (hInstance, MAKEINTRESOURCE (PINEACCELL));

    LoadString (hInstance, IDS_APPNAME, gszAppName, sizeof (gszAppName));

    /* create the TTY window */
    hTTYWnd = CreateWindow (gszTTYClass, gszAppName,
		       WS_OVERLAPPEDWINDOW,
		       CW_USEDEFAULT, CW_USEDEFAULT,
		       CW_USEDEFAULT, CW_USEDEFAULT,
		       NULL, NULL, hInstance, NULL);


    if (NULL == hTTYWnd) 
	    return (NULL);

    ghTTYWnd = hTTYWnd;

    ShowWindow (hTTYWnd, nCmdShow);
    UpdateWindow (hTTYWnd);

    CQInit ();
    MQInit ();

    return (hTTYWnd);
}


/*---------------------------------------------------------------------------
 *  void MakeArgv ()
 *
 *  Description:
 *	Build a standard C argc, argv pointers into the command line string.
 *
 *
 *  Parameters:
 *	cmdLine		- Command line.
 *	*argc		- Count of words.
 *	***argc		- Pointer to Pointer to array of pointers to 
 *			  characters.
 *
 *--------------------------------------------------------------------------*/
LOCAL void
MakeArgv (HINSTANCE hInstance, LPSTR cmdLine, int *pargc, char ***pargv)
{
    int			argc;
    char		**argv;
    LPSTR		c;
    BOOL		inWord;
    int			wordCount;
#define CMD_PATH_LEN    128
    char		*modPath;
    char		mpLen;
    

    /* Count words in cmdLine. */
    wordCount = 0;
    inWord = FALSE;
    for (c = cmdLine; *c != '\0'; ++c) {
	if (inWord) {
	    if (*c == ' ' || *c == '\t') 
		inWord = FALSE;
	}
	else {
	    if (*c != ' ' && *c != '\t') {
		inWord = TRUE;
		++wordCount;
	    }
        }
    }
    
    ++wordCount;				/* One for program name. */
    argv = (char **) MemAlloc (sizeof (char _far *) * wordCount);
    *pargv = argv;
    *pargc = wordCount;

    modPath = (char *) MemAlloc (CMD_PATH_LEN);
    mpLen = GetModuleFileName (hInstance, modPath, CMD_PATH_LEN);
    if (mpLen > 0) {
	*(modPath + mpLen) = '\0';
        *(argv++) = modPath;
    }
    else {
	MemFree (modPath);
	*(argv++) = "Pine/Pico";
    }
    
    /* Now break up command line. */
    inWord = FALSE;
    for (c = cmdLine; *c != '\0'; ++c) {
	if (inWord) {
	    if (*c == ' ' || *c == '\t') {
		inWord = FALSE;
		*c = '\0';
	    }
	}
	else {
	    if (*c != ' ' && *c != '\t') {
		inWord = TRUE;
		*(argv++) = c;
	    }
        }
    }
}




/*---------------------------------------------------------------------------
 *  LRESULT FAR PASCAL __export TTYWndProc( HWND hWnd, UINT uMsg,
 *                                 WPARAM wParam, LPARAM lParam )
 *
 *  Description:
 *     This is the TTY Window Proc.  This handles ALL messages
 *     to the tty window.
 *
 *  Parameters:
 *     As documented for Window procedures.
 *
/*--------------------------------------------------------------------------*/

LRESULT FAR PASCAL __export 
PWndProc (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
#ifdef CDEBUG
    if (Debug > 12) {
        fprintf (DebugFile, "PWndProc::  uMsg = 0x%x\n", uMsg);
	fflush (DebugFile);
    }
#endif
    switch (uMsg)
    {
    case WM_CREATE:
	 if (SetTimer (hWnd, MY_TIMER_ID, MY_TIMER_PERIOD, NULL) == 0) 
	     MessageBox (hWnd, TIMER_FAIL_MESSAGE, NULL, 
			     MB_OK | MB_ICONINFORMATION);
         return (CreateTTYInfo (hWnd));

    case WM_COMMAND: {
	switch ((WORD) wParam) {
		
	case IDM_FILE_EXIT:
	    FileQuit ();
	    break;
	    
        case IDM_SETFONT:
	    SelectTTYFont (hWnd);
	    break ;

        case IDM_ABOUT:
            GoModalDialogBoxParam ( GETHINST( hWnd ),
                                       MAKEINTRESOURCE( ABOUTDLGBOX ),
                                       hWnd,
                                       AboutDlgProc, NULL ) ;
            break;
	
	case IDM_EDIT_CUT:
	    EditCut ();
	    break;
	    
	case IDM_EDIT_COPY:
	    EditCopy ();
	    break;
	    
	case IDM_EDIT_COPY_APPEND:
	    EditCopyAppend ();
	    break;
	    
	case IDM_EDIT_PASTE:
	    EditPaste ();
	    break;
	    
	case IDM_EDIT_CANCEL_PASTE:
	    EditCancelPaste ();
	    break;
	    
	case IDM_HELP:
	    ShowHelp ();
	    break;
	    
	    
#if 0	/* No exit menu item. */
        case IDM_EXIT:
            PostMessage( hWnd, WM_CLOSE, NULL, 0L ) ;
            break;
#endif
        }
        }
	break ;

    case WM_PAINT:
	PaintTTY( hWnd ) ;
        break ;
	 
    case WM_GETMINMAXINFO:
	GetMinMaxInfoTTY (hWnd, (MINMAXINFO __far *)lParam);
	break;

    case WM_SIZE:
	SizeTTY (hWnd, wParam, HIWORD(lParam), LOWORD(lParam));
        break ;
	
    case WM_MOVE:
	MoveTTY (hWnd, (int) LOWORD(lParam), (int) HIWORD(lParam));
	break;
	
    case WM_WINDOWPOSCHANGING:
	/* Allows us to adjust new size of window. */
	AboutToSizeTTY (hWnd, (WINDOWPOS FAR *) lParam);
	break;
		 

    /*
     * WM_KEYDOWN is sent for every "key press" and reports on they
     * keyboard key, with out processing shift and control keys.
     * WM_CHAR is a synthetic event, created from KEYDOWN and KEYUP
     * events.  It includes processing or control and shift characters.
     * But does not get generated for extended keys suchs as arrow
     * keys. 
     * I'm going to try to use KEYDOWN for processing just extended keys
     * and let CHAR handle the the rest.
     *
     * The only key combo that is special is ^-space.  For that, I'll use
     * WM_KEYDOWN and WM_KEYUP to track the state of the control key.
     */
    case WM_CHAR:
        ProcessTTYCharacter (hWnd, LOBYTE (wParam), (DWORD)lParam);
        break ;
	 
    case WM_KEYDOWN:
	if (ProcessTTYKeyDown (hWnd, LOBYTE (wParam), (DWORD)lParam))
	    return (0);
        return( DefWindowProc( hWnd, uMsg, wParam, lParam ) ) ;

    case WM_KEYUP:
	if (ProcessTTYKeyUp (hWnd, LOBYTE (wParam), (DWORD)lParam))
	    return (0);
        return( DefWindowProc( hWnd, uMsg, wParam, lParam ) ) ;
	
	
    case WM_SYSCHAR:
	if (LOBYTE (wParam) == VK_F10) {
	    ProcessTTYCharacter (hWnd, LOBYTE (wParam), (DWORD)lParam);
	    return (0);
        }
        return( DefWindowProc( hWnd, uMsg, wParam, lParam ) ) ;
	    
    case WM_SYSKEYDOWN:
	if (LOBYTE (wParam) == VK_F10) 
	    if (ProcessTTYKeyDown (hWnd, LOBYTE (wParam), (DWORD)lParam))
		return (0);
        return( DefWindowProc( hWnd, uMsg, wParam, lParam ) ) ;

    case WM_SYSKEYUP:
	if (LOBYTE (wParam) == VK_F10) 
	    if (ProcessTTYKeyUp (hWnd, LOBYTE (wParam), (DWORD)lParam))
		return (0);
        return( DefWindowProc( hWnd, uMsg, wParam, lParam ) ) ;
	

    case WM_LBUTTONDOWN:
	ProcessTTYMouse (MEVENT_MOUSEDOWN, 1, LOWORD (lParam), 
			 HIWORD (lParam), wParam);
	break;

    case WM_LBUTTONUP:
	ProcessTTYMouse (MEVENT_MOUSEUP, 1, LOWORD (lParam), 
			 HIWORD (lParam), wParam);
	break;

    case WM_MBUTTONDOWN:
	ProcessTTYMouse (MEVENT_MOUSEDOWN, 2, LOWORD (lParam), 
		 HIWORD (lParam), wParam);
	break;

    case WM_MBUTTONUP:
	ProcessTTYMouse (MEVENT_MOUSEUP, 2, LOWORD (lParam), 
			 HIWORD (lParam), wParam);
	break;

    case WM_RBUTTONDOWN:
	ProcessTTYMouse (MEVENT_MOUSEDOWN, 3, LOWORD (lParam), 
			 HIWORD (lParam), wParam);
	break;

    case WM_RBUTTONUP:
	ProcessTTYMouse (MEVENT_MOUSEUP, 3, LOWORD (lParam), 
			 HIWORD (lParam), wParam);
	break;
	
    case WM_MOUSEMOVE:
	SelTrackXYMouse (LOWORD (lParam), HIWORD (lParam));
	break;


    case WM_SETFOCUS:
        SetTTYFocus (hWnd);
        break;

    case WM_KILLFOCUS:
        KillTTYFocus (hWnd);
        break;

    case WM_INITMENU:
	UpdateMenu ();
	break;
	
    case WM_TIMER:
	/* Really just used so that we continue to receive messages even while
	 * in background.  Causes mswin_getc() to process message and return
	 * to caller so that it can get some periodic processing in. */
	/* Latter:  as long as we are receiving timers, lets use it to check
	 * OnTask list. */
	if (gOnTaskList != NULL)
	    ProcessOnTask ();
	break;

    case WM_DESTROY:
	KillTimer (hWnd, MY_TIMER_ID);
        DestroyTTYInfo (hWnd);
        PostQuitMessage (0);
        break;

    case WM_CLOSE:
        MessageBox (hWnd, gpCloseText != NULL ? gpCloseText : 
	"You must quit the program by typeing the proper command.", 
		gszAppName, MB_OK | MB_ICONINFORMATION);
        break;

         // fall through

    default:
        return( DefWindowProc( hWnd, uMsg, wParam, lParam ) ) ;
    }
    return 0L ;

} // end of TTYWndProc()




/*---------------------------------------------------------------------------
 *  LRESULT NEAR CreateTTYInfo( HWND hWnd )
 *
 *  Description:
 *     Creates the tty information structure and sets
 *     menu option availability.  Returns -1 if unsuccessful.
 *
 *  Parameters:
 *     HWND  hWnd
 *        Handle to main window.
 *
 *-------------------------------------------------------------------------*/

LOCAL LRESULT NEAR 
CreateTTYInfo (HWND hWnd)
{
    HMENU		hMenu;
    PTTYINFO		pTTYInfo;
    LOGFONT		newFont;
    int			i;
    
#ifdef SDEBUG
    if (Debug >= 5) 
	fprintf (DebugFile, "CreateTTYInfo:::  entered\n");
#endif

    pTTYInfo = (PTTYINFO) MemAlloc (sizeof (TTYINFO));
    if (pTTYInfo == NULL)
	return ((LRESULT) - 1);
    gpTTYInfo = pTTYInfo;

    /* initialize TTY info structure */
    memset (pTTYInfo, 0, sizeof (TTYINFO));
    pTTYInfo->wCursorState		= CS_SHOW;/* Shown but not focused. */
    pTTYInfo->fMinimized		= FALSE;
    pTTYInfo->fFocused			= FALSE;
    pTTYInfo->fNewLine			= FALSE;
    pTTYInfo->fMassiveUpdate		= FALSE;
    pTTYInfo->autoWrap			= WRAP_NO_SCROLL;
    pTTYInfo->writeAccumCount		= 0;
    pTTYInfo->actNRow			= 0;
    pTTYInfo->actNColumn		= 0;
    pTTYInfo->xSize			= 0;
    pTTYInfo->ySize			= 0;
    pTTYInfo->xScroll			= 0;
    pTTYInfo->yScroll			= 0;
    pTTYInfo->xOffset			= MARGINE_LEFT;
    pTTYInfo->yOffset			= MARGINE_TOP;
    pTTYInfo->nColumn			= 0;
    pTTYInfo->nRow			= 0;
    pTTYInfo->xChar			= 0;
    pTTYInfo->yChar			= 0;
    pTTYInfo->fDesiredSize		= FALSE;
    pTTYInfo->hTTYFont			= NULL;
    pTTYInfo->rgbFGColor		= GetSysColor (COLOR_WINDOWTEXT);
    pTTYInfo->rgbBGColor		= GetSysColor (COLOR_WINDOW);
#if 1
    pTTYInfo->rgbRFGColor		= GetSysColor (COLOR_HIGHLIGHTTEXT);
    pTTYInfo->rgbRBGColor		= GetSysColor (COLOR_HIGHLIGHT);
#else
    pTTYInfo->rgbRFGColor		= pTTYInfo->rgbBGColor;
    pTTYInfo->rgbRBGColor		= pTTYInfo->rgbFGColor;
#endif

    /* Clear resize callback procs. */
    for (i = 0; i < RESIZE_CALLBACK_ARRAY_SIZE; ++i) 
	pTTYInfo->resizer[i] = NULL;

#ifdef OWNRECT
    /* set empty paint rect. */
    SetRectEmpty (&pTTYInfo->rPaintRect);
#endif
	    

    /* clear screen space */
    pTTYInfo->pScreen = NULL;
    pTTYInfo->pAttrib = NULL;

    /* setup default font information */

    newFont.lfHeight =         12;
    newFont.lfWidth =          0;
    newFont.lfEscapement =     0;
    newFont.lfOrientation =    0;
    newFont.lfWeight =         0;
    newFont.lfItalic =         0;
    newFont.lfUnderline =      0;
    newFont.lfStrikeOut =      0;
    newFont.lfCharSet =        OEM_CHARSET;
    newFont.lfOutPrecision =   OUT_DEFAULT_PRECIS;
    newFont.lfClipPrecision =  CLIP_DEFAULT_PRECIS;
    newFont.lfQuality =        DEFAULT_QUALITY;
    newFont.lfPitchAndFamily = FIXED_PITCH | FF_MODERN;
    newFont.lfFaceName[0] =    NULL;

    /* set TTYInfo handle before any further message processing. */

    SetWindowLong (hWnd, GWL_PTTYINFO, (LPARAM) pTTYInfo);

    /* reset the character information, etc. */

    ResetTTYFont (hWnd, pTTYInfo, &newFont);

    hMenu = GetMenu (hWnd);
    EnableMenuItem (hMenu, IDM_EDIT_CUT, MF_BYCOMMAND | MF_GRAYED);
    EnableMenuItem (hMenu, IDM_EDIT_COPY, MF_BYCOMMAND | MF_GRAYED);
    EnableMenuItem (hMenu, IDM_EDIT_COPY_APPEND, MF_BYCOMMAND | MF_GRAYED);
    EnableMenuItem (hMenu, IDM_EDIT_PASTE, MF_BYCOMMAND | MF_GRAYED);
    return ((LRESULT) TRUE);
}



/*---------------------------------------------------------------------------
 *  BOOL NEAR DestroyTTYInfo( HWND hWnd )
 *
 *  Description:
 *     Destroys block associated with TTY window handle.
 *
 *  Parameters:
 *     HWND hWnd
 *        handle to TTY window
 *
 *-------------------------------------------------------------------------*/

LOCAL BOOL NEAR 
DestroyTTYInfo (HWND hWnd)
{
	PTTYINFO			pTTYInfo;

	pTTYInfo = (PTTYINFO) GetWindowLong (hWnd, GWL_PTTYINFO);
	if (pTTYInfo == NULL)
		return (FALSE);


	DeleteObject (pTTYInfo->hTTYFont);

	MemFree (pTTYInfo);
	return (TRUE);
}



/*---------------------------------------------------------------------------
 *  void  ResizeTTYScreen( HWND hWnd, PTTYINFO pTTYInfo, 
 *					int newNrow, int newNColumn);
 *
 *  Description:
 *		Resize the screen to new size, copying data.
 *
 *  Parameters:
 *     PTTYINFO  pTTYInfo
 *        pointer to TTY info structure
 *		newNCo.umn, newNRow
 *			new size of screen.
 *
/*--------------------------------------------------------------------------*/

LOCAL BOOL
ResizeTTYScreen (HWND hWnd, PTTYINFO pTTYInfo, int newNRow, int newNColumn)
{
    BYTE		*	pNewAttrib;
    CHAR		*	pNewScreen;
    void		*	source;
    void		*	dest;
    size_t			len;
    int				cells;
    int				r;
    extern TERM			term;

    
    if (newNColumn < MINNCOLUMN)
	    newNColumn = MINNCOLUMN;
    if (newNRow < MINNROW)
	    newNRow = MINNROW;

#ifdef SDEBUG
    if (Debug >= 5) 
	fprintf (DebugFile, "ResizeTTYScreen:::  entered, new row %d, col %d\n",
		newNRow, newNColumn);
#endif
    
	
    SelClear ();
    cells = newNColumn * newNRow;
    pNewScreen = (CHAR *)MemAlloc (cells * sizeof (CHAR));
    if (pNewScreen == NULL)
	return (FALSE);
    pNewAttrib = (CharAttrib *)MemAlloc (cells * sizeof (CharAttrib));
    if (pNewAttrib == NULL) {
	MemFree ((void *)pNewScreen);
	return (FALSE);
    }


    /* 
     * Clear new screen. 
     */

    assert (sizeof (CHAR) == 1);
    assert (sizeof (CharAttrib) == 1);
    _fmemset ((void *)pNewScreen, ' ', cells);
    _fmemset ((void *)pNewAttrib, 0, cells);


    /* 
     * Copy old screen onto new screen. 
     */
    if (pTTYInfo->pScreen != NULL) {

	for (r = 1; r <= newNRow && r <= pTTYInfo->actNRow; ++r) {

		
	    source = pTTYInfo->pScreen + ((pTTYInfo->actNRow - r) * 
						    pTTYInfo->actNColumn);
	    dest = pNewScreen + ((newNRow - r) * newNColumn);
	    len = MIN (newNColumn, pTTYInfo->actNColumn) * sizeof (CHAR);
	    _fmemcpy (dest, source, len);

	    
	    source = pTTYInfo->pAttrib + ((pTTYInfo->actNRow - r) * 
						    pTTYInfo->actNColumn);
	    dest = pNewAttrib + ((newNRow - r) * newNColumn);
	    len = MIN (newNColumn, pTTYInfo->actNColumn) * sizeof(CharAttrib);
	    _fmemcpy (dest, source, len);

	}
	pTTYInfo->nColumn = MIN (pTTYInfo->nColumn, newNColumn);
	pTTYInfo->nRow = MAX (0, 
			pTTYInfo->nRow + (newNRow - pTTYInfo->actNRow));
	MemFree (pTTYInfo->pScreen);
	MemFree (pTTYInfo->pAttrib);
    }
    else {
	pTTYInfo->nColumn = MIN (pTTYInfo->nColumn, newNColumn);
	pTTYInfo->nRow = MIN (pTTYInfo->nRow, newNRow);
    }
    pTTYInfo->pScreen = pNewScreen;
    pTTYInfo->pAttrib = pNewAttrib;
    pTTYInfo->actNColumn = newNColumn;
    pTTYInfo->actNRow = newNRow;
    
    
    /* Repaint whole screen. */
    pTTYInfo->screenDirty = TRUE;
    pTTYInfo->eraseScreen = TRUE;
#ifdef OWNRECT
    MyInvalidateRect (pTTYInfo, hWnd, NULL);
#else
    InvalidateRect (hWnd, NULL, FALSE);
#endif

    
    
    /* xxx pico specific. */
    if (term.t_nrow == 0) {
	term.t_nrow = newNRow - 1;
	term.t_ncol = newNColumn;
    }
    

    return (TRUE);
}
		
		




/*---------------------------------------------------------------------------
 *  BOOL  ResetTTYFont( HWND hWnd, PTTYINFO pTTYInfo, LOGFONT *newFont)
 *
 *  Description:
 *     Resets the TTY character information and causes the
 *     screen to resize to update the scroll information.
 *
 *  Parameters:
 *     PTTYINFO  pTTYInfo
 *        pointer to TTY info structure
 *
/*--------------------------------------------------------------------------*/

LOCAL BOOL  
ResetTTYFont (HWND hWnd, PTTYINFO pTTYInfo, LOGFONT *newFont)
{
    HDC			hDC;
    HFONT		hFont;
    TEXTMETRIC		tm;
    int			newNRow;
    int			newNColumn;
    BOOL		newsize;
    int			i;
/*	RECT		rcWindow;*/


#ifdef SDEBUG
    if (Debug >= 5) 
	fprintf (DebugFile, "ResetTTYFont:::  entered, curent window size X %d, Y %d\n",
		pTTYInfo->xSize, pTTYInfo->ySize);
#endif


    if (NULL == pTTYInfo)
	    return (FALSE);
    
    SelClear ();

    /*
     * Create new font.
     */
    hFont = CreateFontIndirect (newFont);
    if (hFont == NULL)
	    return (FALSE);
    hDC = GetDC (hWnd);
    SelectObject (hDC, hFont);
    GetTextMetrics (hDC, &tm);
    ReleaseDC (hWnd, hDC);

    
    /*
     * Replace old font.
     */
    if (NULL != pTTYInfo->hTTYFont)
	    DeleteObject (pTTYInfo->hTTYFont);
    pTTYInfo->hTTYFont = hFont;
    _fmemcpy (&pTTYInfo->lfTTYFont, newFont, sizeof (LOGFONT));

    
    /* Update the char cell size. */
    pTTYInfo->xChar = tm.tmAveCharWidth;
    pTTYInfo->yChar = tm.tmHeight + tm.tmExternalLeading;

    /* Update the current number of rows and cols.  Don't allow
     * either to be less than zero. */
    newNRow = MAX (MINNROW, 
	    MIN (MAXNROW, (pTTYInfo->ySize - (2 * pTTYInfo->yOffset)) / 
				    pTTYInfo->yChar));
    newNColumn = MAX (MINNCOLUMN, 
	    MIN (MAXNCOLUMN, (pTTYInfo->xSize - (2 * pTTYInfo->xOffset))/
				    pTTYInfo->xChar));

    newsize = newNRow != pTTYInfo->actNRow || 
		    newNColumn != pTTYInfo->actNColumn;
    if (newsize)
	    ResizeTTYScreen (hWnd, pTTYInfo, newNRow, newNColumn);

    /* Resize the carrot as well. */
    if (pTTYInfo->wCursorState == CS_VISIBLE) {
	    HideCaret (hWnd);
	    DestroyCaret();
	    CreateCaret (hWnd, NULL, pTTYInfo->xChar, pTTYInfo->yChar);
	    SetCaretPos ((pTTYInfo->nColumn * pTTYInfo->xChar) + pTTYInfo->xOffset,
		    (pTTYInfo->nRow * pTTYInfo->yChar) + pTTYInfo->yOffset);
	    ShowCaret (hWnd);
    }

    /* Redraw screen and, if the "size" changed, tell the upper layers. */
    pTTYInfo->screenDirty = TRUE;
    pTTYInfo->eraseScreen = TRUE;
#ifdef OWNRECT
    MyInvalidateRect (pTTYInfo, hWnd, NULL);
#else
    InvalidateRect (hWnd, NULL, FALSE);
#endif

    /* Always call the resize functions - even if the screen size
     * has not changed, the font style may have. */
    for (i = 0; i < RESIZE_CALLBACK_ARRAY_SIZE; ++i) {
	if (pTTYInfo->resizer[i] != NULL) 
	    pTTYInfo->resizer[i](pTTYInfo->actNRow, pTTYInfo->actNColumn);
    }

    return (TRUE);
}



#ifdef OWNRECT
void
MyInvalidateRect (PTTYINFO pTTYInfo, HWND hWnd, RECT *r)
{
    RECT	u;
    
    if (r == NULL) {
	SetRect (&pTTYInfo->rPaintRect, 0, 0, pTTYInfo->xSize, pTTYInfo->ySize);
    }
    else {
	UnionRect (&u, &pTTYInfo->rPaintRect, r);
	CopyRect (&pTTYInfo->rPaintRect, &u);
    }
}
#endif





/*---------------------------------------------------------------------------
 *  BOOL  PaintTTY( HWND hWnd )
 *
 *  Description:
 *     Paints the rectangle determined by the paint struct of
 *     the DC.
 *
 *  Parameters:
 *     HWND hWnd
 *        handle to TTY window (as always)
 *
/*--------------------------------------------------------------------------*/

LOCAL BOOL
PaintTTY (HWND hWnd) 
{
    int          nRow, nCol;		/* Top right corner of update. */
    int		 nEndRow, nEndCol;	/* lower right corner of update. */
    int		 nCount;		/* Number of columns in each row. */
    int		 nHorzPos, nVertPos;	/* Position of each text write. */
    int		 col;			/* start col of run of similar attr */
    int		 count;			/* count of run of similar attrib. */
    int		 endCount;		/* How far to count. */
    CharAttrib	*pAttrib;
    HDC          hDC ;
    HFONT        hOldFont;
    PTTYINFO    pTTYInfo;
    PAINTSTRUCT  ps;
    RECT         rect;
    RECT	 erect;
    HBRUSH	 hBrush;
    long	 offset;		/* Offset into screen and attrib */
    CharAttrib	 lastAttrib;		/* Attributes of last text write. */
    CharAttrib	 newAttrib;		/* Attributes of this text write. */


#ifdef CDEBUG
    if (Debug >= 9) 
	fprintf (DebugFile, "PaintTTY:::  entered\n");
#endif
    
    
    pTTYInfo = (PTTYINFO) GetWindowLong (hWnd, GWL_PTTYINFO);
    if (pTTYInfo == NULL)
	return (FALSE);

    hDC = BeginPaint (hWnd, &ps);
    hOldFont = SelectObject (hDC, pTTYInfo->hTTYFont);
    SetTextColor (hDC, pTTYInfo->rgbFGColor);
    SetBkColor (hDC, pTTYInfo->rgbBGColor);
    SetBkMode (hDC, OPAQUE);
    rect = ps.rcPaint;

    nRow = min (pTTYInfo->actNRow - 1,
	   max (0, (rect.top - pTTYInfo->yOffset) / pTTYInfo->yChar));
    nEndRow = min (pTTYInfo->actNRow - 1,
	    ((rect.bottom - pTTYInfo->yOffset - 1) / pTTYInfo->yChar));
    nCol = min (pTTYInfo->actNColumn - 1,
	    max (0, (rect.left - pTTYInfo->xOffset) / pTTYInfo->xChar));
    nEndCol = min (pTTYInfo->actNColumn - 1,
	    ((rect.right - pTTYInfo->xOffset - 1) / pTTYInfo->xChar));

    nCount = nEndCol - nCol + 1;
    lastAttrib = CHAR_ATTR_NORM;
    
    
    /* Erase screen if necessary. */
    if (pTTYInfo->eraseScreen) {
	erect.top = 0;
	erect.left = 0;
	erect.bottom = pTTYInfo->ySize;
	erect.right = pTTYInfo->xSize;
	hBrush = CreateSolidBrush (pTTYInfo->rgbBGColor);
	if (hBrush != NULL) {
	    FillRect (hDC, &erect, hBrush);
	    DeleteObject (hBrush);
	}
	pTTYInfo->eraseScreen = FALSE;
    }
    else {
	/* If not erasing entire screen, erase just the rect to be painted. */
	hBrush = CreateSolidBrush (pTTYInfo->rgbBGColor);
	if (hBrush != NULL) {
	    FillRect (hDC, &rect, hBrush);
	    DeleteObject (hBrush);
	}
	pTTYInfo->eraseScreen = FALSE;
    }
    
    
    /* Paint rows of text. */
    for (; nRow <= nEndRow; nRow++)    {
	nVertPos = (nRow * pTTYInfo->yChar) + pTTYInfo->yOffset;
	rect.top = nVertPos;
	rect.bottom = nVertPos + pTTYInfo->yChar;
	
	/* Paint runs of similar attributes. */
	col = nCol;				/* Start at left. */
	while (col <= nEndCol) {		/* While not past right. */

	    /* Starting with Character at nRow, col, what is its attribute? */
	    offset = (nRow * pTTYInfo->actNColumn) + col;
	    newAttrib = *(pTTYInfo->pAttrib + offset);
	    
	    if (newAttrib != lastAttrib) {
		/* Set new attributes.  If Reverse or Selected, then
		 * show in reverse colors.  But if neither, or both, then
		 * normal colors. */
		if (newAttrib > 0 && newAttrib 
			< (CHAR_ATTR_REV | CHAR_ATTR_SEL)) {
		    SetTextColor (hDC, pTTYInfo->rgbRFGColor);
		    SetBkColor (hDC, pTTYInfo->rgbRBGColor);
		}
		else {
		    SetTextColor (hDC, pTTYInfo->rgbFGColor);
		    SetBkColor (hDC, pTTYInfo->rgbBGColor);
		}
	    }
	    
	    /* Find run of similar attributes. */
	    count = 1;
	    pAttrib = pTTYInfo->pAttrib + offset + 1;
	    endCount = nEndCol - col;
	    while (count <= endCount && *pAttrib++ == newAttrib) 
		++count;
	
	    /* Paint run of characters from nRow, col to nRow, col + count 
	     * rect.top and rect.bottom have already been calculated. */
	    nHorzPos = (col * pTTYInfo->xChar) + pTTYInfo->xOffset;
	    rect.left = nHorzPos;
	    rect.right = nHorzPos + pTTYInfo->xChar * count;
	    ExtTextOut (hDC, nHorzPos, nVertPos, ETO_OPAQUE | ETO_CLIPPED, &rect,
		(LPSTR)(pTTYInfo->pScreen + offset),
		count, NULL);
	
	    /* Move pointer to end of this span of characters. */
	    col += count;
	    lastAttrib = newAttrib;
        }
    }

    SelectObject (hDC, hOldFont);
    EndPaint (hWnd, &ps);
    MoveTTYCursor (hWnd);
    pTTYInfo->screenDirty = FALSE;
#ifdef OWNRECT
    SetRectEmpty (&pTTYInfo->rPaintRect);
#endif
    return (TRUE);
}

/*---------------------------------------------------------------------------
 *  BOOL  GetMinMaxInfoTTY (HWND hWnd, (MINMAXINFO __far *)lParam)
 *
 *  Description:
 *     Return the min and max size that the window can be.
 *
 *  Parameters:
 *     HWND hWnd
 *        handle to TTY window
 *
 *     MINMAXINFO
 *	  Info structure that Windows would like us to fill.
 *
/*--------------------------------------------------------------------------*/

LOCAL BOOL  
GetMinMaxInfoTTY (HWND hWnd, MINMAXINFO __far *lpmmi)
{
    PTTYINFO		pTTYInfo;
    
    
#ifdef SDEBUG
    if (Debug >= 5) 
	fprintf (DebugFile, "GetMinMaxInfoTTY:::  entered\n");
#endif
    
    
    pTTYInfo = (PTTYINFO) GetWindowLong (hWnd, GWL_PTTYINFO);
    if (pTTYInfo == NULL)
	return (FALSE);

    lpmmi->ptMaxTrackSize.x = lpmmi->ptMaxSize.x = MIN (lpmmi->ptMaxSize.x,
			    pTTYInfo->xChar * MAXNCOLUMN + WIN_X_BORDER_SIZE);
    lpmmi->ptMaxTrackSize.y = lpmmi->ptMaxSize.y = MIN (lpmmi->ptMaxSize.y, 
			    pTTYInfo->yChar * MAXNROW + WIN_Y_BORDER_SIZE);

    lpmmi->ptMinTrackSize.x = MAX (WIN_MIN_X_SIZE, 
		    pTTYInfo->xChar * MINNCOLUMN + WIN_X_BORDER_SIZE);
    lpmmi->ptMinTrackSize.y = MAX (WIN_MIN_Y_SIZE,
		    pTTYInfo->yChar * MINNROW + WIN_Y_BORDER_SIZE);
    return (TRUE);
}



/*---------------------------------------------------------------------------
 *  BOOL  AboutToSizeTTY (HWND hWnd, WINDOWPOS *winPos)
 *
 *  Description:
 *      Called just before Windows resizes our window.  We can change the
 *	values in 'winPos' to change the new size of the window.
 *	Do this is mswin_setwindow has been called while the window
 *	was minimized.
 *
 *  Parameters:
 *     HWND hWnd
 *        handle to TTY window
 *
 *     WORD wVertSize
 *        new vertical size
 *
 *     WORD wHorzSize
 *        new horizontal size
 *
/*--------------------------------------------------------------------------*/
LOCAL BOOL  
AboutToSizeTTY (HWND hWnd, WINDOWPOS *winPos)
{
    PTTYINFO	pTTYInfo;

    
    pTTYInfo = (PTTYINFO) GetWindowLong (hWnd, GWL_PTTYINFO);
    if (pTTYInfo == NULL)
	    return ( FALSE );

#ifdef SDEBUG
	if (Debug >= 5) 
	    fprintf (DebugFile, "AboutToSizeTTY:::  After x%lx, pos %d, %d, size %d, %d, flags x%x\n", 
		    winPos->hwndInsertAfter, winPos->x, winPos->y, winPos->cx,
		    winPos->cy, winPos->flags);

#endif

    /*
     * Was the window minimized AND is there a desired new size for it?
     * AND is this a call that specifies a new size and position.
     */
    if (pTTYInfo->fMinimized && pTTYInfo->fDesiredSize &&
	    (winPos->flags & (SWP_NOSIZE | SWP_NOMOVE)) == 0) {
#ifdef SDEBUG
	if (Debug >= 5) 
	    fprintf (DebugFile, "AboutToSizeTTY:::  substitue pos (%d, %d), size (%d, %d)\n",
		    pTTYInfo->xDesPos, pTTYInfo->yDesPos,
		    pTTYInfo->xDesSize, pTTYInfo->yDesSize);
#endif
	pTTYInfo->fDesiredSize = FALSE;
	winPos->x = pTTYInfo->xDesPos;
	winPos->y = pTTYInfo->yDesPos;
	winPos->cx = pTTYInfo->xDesSize;
	winPos->cy = pTTYInfo->yDesSize;
    }
    return (TRUE);
}


/*---------------------------------------------------------------------------
 *  BOOL  SizeTTY( HWND hWnd, int fwSizeType, WORD wVertSize, WORD wHorzSize )
 *
 *  Description:
 *     Sizes TTY and sets up scrolling regions.
 *
 *  Parameters:
 *     HWND hWnd
 *        handle to TTY window
 *
 *     WORD wVertSize
 *        new vertical size
 *
 *     WORD wHorzSize
 *        new horizontal size
 *
/*--------------------------------------------------------------------------*/

LOCAL BOOL  
SizeTTY (HWND hWnd, int fwSizeType, WORD wVertSize, WORD wHorzSize)
{
/*  int		nScrollAmt ;*/
    PTTYINFO	pTTYInfo;
    int		newNColumn;
    int		newNRow;
    int		i;


#ifdef SDEBUG
    if (Debug >= 5) 
	fprintf (DebugFile, "SizeTTY:::  entered, sizeType %d, New screen size %d, %d pixels\n",
		fwSizeType, wHorzSize, wVertSize);
#endif

    pTTYInfo = (PTTYINFO) GetWindowLong (hWnd, GWL_PTTYINFO);
    if (pTTYInfo == NULL)
	    return ( FALSE );

    
    /*
     * Is the window being minimized?
     */
    if (fwSizeType == SIZE_MINIMIZED) {
	pTTYInfo->fMinimized = TRUE;
	return (TRUE);
    }
    
    pTTYInfo->fMinimized = FALSE;
	    
	    
    pTTYInfo->ySize = (int) wVertSize;
    newNRow = max (MINNROW, min (MAXNROW, (pTTYInfo->ySize - (2 * MARGINE_TOP)) / 
				    pTTYInfo->yChar));
    pTTYInfo->yOffset = MARGINE_TOP;

    pTTYInfo->xSize = (int) wHorzSize;
    newNColumn = max (MINNCOLUMN, 
		    min (MAXNCOLUMN, (pTTYInfo->xSize - (2 * MARGINE_LEFT)) / 
		    pTTYInfo->xChar));
    pTTYInfo->xOffset = MARGINE_LEFT;


    ResizeTTYScreen (hWnd, pTTYInfo, newNRow, newNColumn);
    pTTYInfo->screenDirty = TRUE;
    pTTYInfo->eraseScreen = TRUE;
#ifdef OWNRECT
    MyInvalidateRect (pTTYInfo, hWnd, NULL);
#else
    InvalidateRect (hWnd, NULL, FALSE);
#endif

    for (i = 0; i < RESIZE_CALLBACK_ARRAY_SIZE; ++i) {
	if (pTTYInfo->resizer[i] != NULL) 
		pTTYInfo->resizer[i](pTTYInfo->actNRow, pTTYInfo->actNColumn);
    }

    return (TRUE);
}



/*---------------------------------------------------------------------------
 *  BOOL  MoveTTY (HWND hWnd, int xPos, int yPos)
 *
 *  Description:
 *     Notes the fact that the window has moved. 
 *     Only real purpose is so we can tell pine which can the write the
 *     new window position to the 'pinerc' file.
 *
 *  Parameters:
 *     HWND hWnd
 *        handle to TTY window
 *
 *     int xPos, yPos
 *	  New position of the top left corner. 
 *
 *
/*--------------------------------------------------------------------------*/

LOCAL BOOL  
MoveTTY (HWND hWnd, int xPos, int yPos)
{
    int		i;
    
#ifdef SDEBUG
    if (Debug >= 5) 
	fprintf (DebugFile, "MoveTTY:::  entered\n");
#endif

    for (i = 0; i < RESIZE_CALLBACK_ARRAY_SIZE; ++i) {
	if (gpTTYInfo->resizer[i] != NULL) 
		gpTTYInfo->resizer[i](gpTTYInfo->actNRow, 
					gpTTYInfo->actNColumn);
    }
    return (TRUE);
}



/*---------------------------------------------------------------------------
 *  BOOL  SetTTYFocus( HWND hWnd )
 *
 *  Description:
 *     Sets the focus to the TTY window also creates caret.
 *
 *  Parameters:
 *     HWND hWnd
 *        handle to TTY window
 *
/*--------------------------------------------------------------------------*/

LOCAL BOOL  
SetTTYFocus (HWND hWnd)
{
    PTTYINFO  pTTYInfo;

#ifdef SDEBUG
    if (Debug >= 5) 
	fprintf (DebugFile, "SetTTYFocus:::  entered\n");
#endif

    pTTYInfo = (PTTYINFO) GetWindowLong (hWnd, GWL_PTTYINFO);
    if (pTTYInfo == NULL)
	    return (FALSE);

    pTTYInfo->fFocused = TRUE;
    pTTYInfo->wCursorState |= CS_FOCUSED;
    gKeyControlDown = FALSE;

    if (pTTYInfo->wCursorState == CS_VISIBLE) {
	    CreateCaret (hWnd, NULL, pTTYInfo->xChar, pTTYInfo->yChar);
	    ShowCaret (hWnd);
    }

    MoveTTYCursor (hWnd);
    return (TRUE);
}




/*---------------------------------------------------------------------------
 *  BOOL  KillTTYFocus( HWND hWnd )
 *
 *  Description:
 *     Kills TTY focus and destroys the caret.
 *
 *  Parameters:
 *     HWND hWnd
 *        handle to TTY window
 *
/*--------------------------------------------------------------------------*/

LOCAL BOOL  
KillTTYFocus (HWND hWnd)
{
    PTTYINFO  pTTYInfo;

#ifdef SDEBUG
    if (Debug >= 5) 
	fprintf (DebugFile, "KillTTYFocus:::  entered\n");
#endif
    pTTYInfo = (PTTYINFO) GetWindowLong (hWnd, GWL_PTTYINFO);
    if (pTTYInfo == NULL)
	    return (FALSE);

    if (pTTYInfo->wCursorState == CS_VISIBLE) {
	    HideCaret (hWnd);
	    DestroyCaret();
    }

    pTTYInfo->wCursorState &= ~CS_FOCUSED;
    pTTYInfo->fFocused = FALSE;
    gKeyControlDown = FALSE;

    return (TRUE);
}




/*---------------------------------------------------------------------------
 *  BOOL  MoveTTYCursor( HWND hWnd )
 *
 *  Description:
 *     Moves caret to current position.
 *
 *  Parameters:
 *     HWND hWnd
 *        handle to TTY window
 *
/*--------------------------------------------------------------------------*/

LOCAL BOOL  
MoveTTYCursor (HWND hWnd)
{
    PTTYINFO  pTTYInfo;

    pTTYInfo = (PTTYINFO) GetWindowLong (hWnd, GWL_PTTYINFO);
    if (pTTYInfo == NULL)
	    return (FALSE);

    if (pTTYInfo->wCursorState == CS_VISIBLE && 
	    !pTTYInfo->fMassiveUpdate) {
	HideCaret (hWnd);
	SetCaretPos ((pTTYInfo->nColumn * pTTYInfo->xChar) + pTTYInfo->xOffset,
		(pTTYInfo->nRow * pTTYInfo->yChar) + pTTYInfo->yOffset);
	ShowCaret (hWnd);
    }

    return (TRUE);
}




/*---------------------------------------------------------------------------
 *  BOOL  ProcessTTYKeyDown ( HWND hWnd, WORD bOut, DWORD keyData )
 *
 *  Description:
 *	Called to process MW_KEYDOWN message.  We are only interested in 
 *	virtual keys that pico/pine use.  All others get passed on to 
 *	the default message handler.  Regular key presses will return 
 *	latter as a WM_CHAR message, with SHIFT and CONTROL processing
 *	already done.
 *
 *  Parameters:
 *     HWND hWnd
 *        handle to TTY window
 *
 *     BYTE key
 *	  Virtual key code.
 *
 *     DWORD keyData
 *	  Additional flags passed in lParam for WM_KEYDOWN
 *
/*--------------------------------------------------------------------------*/

LOCAL BOOL  
ProcessTTYKeyDown (HWND hWnd, WORD key, DWORD keyData)
{
    WORD		myKey;
    
    
    /* Special keys. */
    if (keyData & 0X20000000)
	return (FALSE);			/* Message NOT handled. */

    switch (key) {
	case VK_UP:		myKey = MSWIN_KEY_UP;		break;
	case VK_DOWN:		myKey = MSWIN_KEY_DOWN;		break;
	case VK_RIGHT:		myKey = MSWIN_KEY_RIGHT;	break;
	case VK_LEFT:		myKey = MSWIN_KEY_LEFT;		break;
	case VK_PRIOR:		myKey = MSWIN_KEY_PREVPAGE;	break;
	case VK_NEXT:		myKey = MSWIN_KEY_NEXTPAGE;	break;
	case VK_HOME:		myKey = MSWIN_KEY_HOME;		break;
	case VK_END:		myKey = MSWIN_KEY_END;		break;
	case VK_DELETE:		myKey = MSWIN_KEY_DELETE;	break;
	case VK_F1:		myKey = MSWIN_KEY_F1;		break;
	case VK_F2:		myKey = MSWIN_KEY_F2;		break;
	case VK_F3:		myKey = MSWIN_KEY_F3;		break;
	case VK_F4:		myKey = MSWIN_KEY_F4;		break;
	case VK_F5:		myKey = MSWIN_KEY_F5;		break;
	case VK_F6:		myKey = MSWIN_KEY_F6;		break;
	case VK_F7:		myKey = MSWIN_KEY_F7;		break;
	case VK_F8:		myKey = MSWIN_KEY_F8;		break;
	case VK_F9:		myKey = MSWIN_KEY_F9;		break;
	case VK_F10:		myKey = MSWIN_KEY_F10;		break;
	case VK_F11:		myKey = MSWIN_KEY_F11;		break;
	case VK_F12:		myKey = MSWIN_KEY_F12;		break;
	
	/* Control is special - I keep track, but do not claim to handle. */
	case VK_CONTROL:	gKeyControlDown = TRUE;
				return (FALSE);
				
	default:		return (FALSE);	/* Message NOT handled.*/
    }

    CQAdd (myKey, 0);
    return (TRUE);			/* Message handled .*/
}



/*---------------------------------------------------------------------------
 *  BOOL  ProcessTTYKeyUp ( HWND hWnd, WORD bOut, DWORD keyData )
 *
 *  Description:
 *	Called to process MW_KEYDOWN message. 
 *	Used only to detect when the control key goes up.
 *
 *  Parameters:
 *     HWND hWnd
 *        handle to TTY window
 *
 *     BYTE key
 *	  Virtual key code.
 *
 *     DWORD keyData
 *	  Additional flags passed in lParam for WM_KEYDOWN
 *
/*--------------------------------------------------------------------------*/

LOCAL BOOL  
ProcessTTYKeyUp (HWND hWnd, WORD key, DWORD keyData)
{
    WORD		myKey;
    
    
    /* Special keys. */
    if (keyData & 0X20000000)
	return (FALSE);			/* Message NOT handled. */

    if (key == VK_CONTROL) 
	gKeyControlDown = FALSE;
				
    return (FALSE);	/* Message NOT handled.*/
}




/*---------------------------------------------------------------------------
 *  BOOL  ProcessTTYCharacter( HWND hWnd, WORD bOut, DWORD keyData )
 *
 *  Description:
 *		Place the character into a queue.
 *
 *  Parameters:
 *     HWND hWnd
 *        handle to TTY window
 *
 *     BYTE bOut
 *        byte from keyboard
 *
/*--------------------------------------------------------------------------*/

LOCAL BOOL  
ProcessTTYCharacter (HWND hWnd, WORD bOut, DWORD keyData)
{
    if (bOut == ' ' && gKeyControlDown)
	    bOut = '\0';
    CQAdd (bOut, keyData);
    return (TRUE);		/* Message handled. */
}





/*---------------------------------------------------------------------------
 *  BOOL  ProcessTTYMouse(int mevent, int button, int xPos, int yPos, 
 *				WORD keys)
 *
 *  Description:
 *		Place the Mouse into a queue.
 *
 *  Parameters:
 *     HWND hWnd
 *        handle to TTY window
 *
 *     BYTE bOut
 *        byte from keyboard
 *
/*--------------------------------------------------------------------------*/

LOCAL BOOL  
ProcessTTYMouse (int mevent, int button, int xPos, int yPos, WORD keys)
{
    int		nRow;
    int		nColumn;
    

    nColumn = (xPos - gpTTYInfo->xOffset) / gpTTYInfo->xChar;
    nRow = (yPos - gpTTYInfo->yOffset) / gpTTYInfo->yChar;

    MQAdd (mevent, button, nRow, nColumn, keys);


    /*
     * 
     */
    if (button == 1) {
	if (mevent == MEVENT_MOUSEDOWN) {
	    SetCapture (ghTTYWnd);
	    SelStart (nRow, nColumn);
	}
	else {
	    ReleaseCapture ();
	    SelFinish (nRow, nColumn);
        }
    }
    return (0);		/* Message handled. */
}





#if 0
/* Old function used when screen was fixed array. */

/*---------------------------------------------------------------------------
 *  void ScrollTTY (HWND hWnd, int scroll)
 *
 *  Description:
 *		Scroll the screen up (negative) or down (positive) by 'scroll' rows.
 *
 *  Parameters:
 *     HWND hWnd
 *        handle to TTY window
 *
 *     int scroll
 *        number of rows to scroll
 *
/*--------------------------------------------------------------------------*/

LOCAL void
ScrollTTY (HWND hWnd, int scroll, int maxRows)

{
	PTTYINFO	pTTYInfo;
	int			ascroll;
	LPSTR		source, dest;
	size_t		len;
	
	if (scroll == 0) 
		return;
	if (scroll >= MAXROWS)
		return;

	pTTYInfo = (PTTYINFO) GetWindowLong (hWnd, GWL_PTTYINFO);
	if (pTTYInfo == NULL)
		return;
	
	ascroll = 0;
	if (scroll < 0) {
		ascroll = -scroll;
		source = (LPSTR) (pTTYInfo->abScreen + (MAXCOLS * ascroll));
		dest = (LPSTR)pTTYInfo->abScreen;
		len = MAXCOLS * (maxRows - ascroll);
		_fmemmove (dest, source, len);
		_fmemset ((LPSTR) (pTTYInfo->abScreen + (MAXCOLS * (maxRows - ascroll))),
			' ', MAXCOLS * ascroll);
		pTTYInfo->nRow = max (0, pTTYInfo->nRow - ascroll);
	}
	else {
		source = (LPSTR)pTTYInfo->abScreen;
		dest = (LPSTR) (pTTYInfo->abScreen + (MAXCOLS * scroll));
		len = MAXCOLS * (maxRows - scroll);
		_fmemmove (dest, source, len);
		_fmemset ((LPSTR)pTTYInfo->abScreen, ' ', MAXCOLS * scroll);
		pTTYInfo->nRow = min (maxRows - 1, pTTYInfo->nRow + scroll);
	}

	MoveTTYCursor (hWnd);
	pTTYInfo->screenDirty = TRUE;
	pTTYInfo->eraseScreen = TRUE;
#ifdef OWNRECT
	MyInvalidateRect (pTTYInfo, hWnd, NULL);
#else
	InvalidateRect (hWnd, NULL, FALSE);
#endif
}
#endif /* 0 */






/*---------------------------------------------------------------------------
 *  BOOL  WriteTTYBlock( HWND hWnd, LPSTR lpBlock, int nLength )
 *
 *  Description:
 *     Writes block to TTY screen.  Nothing fancy - just
 *     straight TTY.
 *
 *  Parameters:
 *     HWND hWnd
 *        handle to TTY window
 *
 *     LPSTR lpBlock
 *        far pointer to block of data
 *
 *     int nLength
 *        length of block
 *
/*--------------------------------------------------------------------------*/

LOCAL BOOL  
WriteTTYBlock (HWND hWnd, LPSTR lpBlock, int nLength)
{
    int				i;
    PTTYINFO			pTTYInfo;
    RECT			rect;
    BOOL			fNewLine;
    long			offset;

    pTTYInfo = (PTTYINFO) GetWindowLong (hWnd, GWL_PTTYINFO);
    if (pTTYInfo == NULL)
	    return (FALSE);

    for (i = 0 ; i < nLength; i++) {
	switch (lpBlock[i]) {
	case ASCII_BEL:
	    // Bell
	    MessageBeep (0) ;
	    break ;

	case ASCII_BS:
	    // Backspace
	    if (pTTYInfo->nColumn > 0)
		    --pTTYInfo->nColumn;
	    MoveTTYCursor (hWnd);
	    break;

	case ASCII_CR:
	    // Carriage return
	    pTTYInfo->nColumn = 0 ;
	    MoveTTYCursor (hWnd);
	    if (!pTTYInfo->fNewLine)
		    break;

	    // fall through

	case ASCII_LF:
	    // Line feed
	    if (++pTTYInfo->nRow == pTTYInfo->actNRow) {
		/* Scroll the Screen. */
		_fmemmove ((LPSTR)pTTYInfo->pScreen,
			(LPSTR) (pTTYInfo->pScreen + pTTYInfo->actNColumn),
			((pTTYInfo->actNRow - 1) * pTTYInfo->actNColumn) *
				sizeof (CHAR));
		assert (sizeof (CHAR) == 1);
		_fmemset ((LPSTR) (pTTYInfo->pScreen + 
			    (pTTYInfo->actNRow - 1) * pTTYInfo->actNColumn),
			' ', pTTYInfo->actNColumn);

		/* Scroll the Attributes. */
		_fmemmove ((LPSTR)pTTYInfo->pAttrib,
			(LPSTR) (pTTYInfo->pAttrib + pTTYInfo->actNColumn),
			((pTTYInfo->actNRow - 1) * pTTYInfo->actNColumn) *
				sizeof (CharAttrib));
		assert (sizeof (CharAttrib) == 1);
		_fmemset ((LPSTR) (pTTYInfo->pScreen + 
			    (pTTYInfo->actNRow - 1) * pTTYInfo->actNColumn),
			0, pTTYInfo->actNColumn);


		pTTYInfo->screenDirty = TRUE;
		pTTYInfo->eraseScreen = TRUE;
#ifdef OWNRECT
		MyInvalidateRect (pTTYInfo, hWnd, NULL);
#else
		InvalidateRect (hWnd, NULL, FALSE);
#endif
		--pTTYInfo->nRow;
	    }
	    MoveTTYCursor (hWnd);
	    break;



	default:
	    offset = (pTTYInfo->nRow * pTTYInfo->actNColumn) +
		    pTTYInfo->nColumn;
	    *(pTTYInfo->pScreen + offset) = lpBlock[i];
	    *(pTTYInfo->pAttrib + offset) = pTTYInfo->curAttrib;
	    rect.left = (pTTYInfo->nColumn * pTTYInfo->xChar) +
		    pTTYInfo->xOffset;
	    rect.right = rect.left + pTTYInfo->xChar;
	    rect.top = (pTTYInfo->nRow * pTTYInfo->yChar) +
		    pTTYInfo->yOffset;
	    rect.bottom = rect.top + pTTYInfo->yChar;
	    pTTYInfo->screenDirty = TRUE;
#ifdef OWNRECT
	    MyInvalidateRect (pTTYInfo, hWnd, &rect);
#else
	    InvalidateRect (hWnd, &rect, FALSE);
#endif

	    /* Line Wrap. */
	    if (pTTYInfo->nColumn < pTTYInfo->actNColumn - 1)
		    pTTYInfo->nColumn++ ;
	    else if (pTTYInfo->autoWrap == WRAP_ON || 
		    (pTTYInfo->autoWrap == WRAP_NO_SCROLL && 
			    pTTYInfo->nRow < pTTYInfo->actNRow - 1)) {
		    fNewLine = pTTYInfo->fNewLine;
		    pTTYInfo->fNewLine = FALSE;
		    WriteTTYBlock (hWnd, "\r\n", 2);
		    pTTYInfo->fNewLine = fNewLine;
	    }
	    break;
	}
    }
    return (TRUE);
}




/*---------------------------------------------------------------------------
 *  BOOL  WriteTTYText ( HWND hWnd, LPSTR lpBlock, int nLength )
 *
 *  Description:
 *	Like WriteTTYBlock but optimized for strings that are text only,
 *	no carrage control characters. 
 *
 *  Parameters:
 *     HWND hWnd
 *        handle to TTY window
 *
 *     LPSTR lpBlock
 *        far pointer to block of data
 *
 *     int nLength
 *        length of block
 *
/*--------------------------------------------------------------------------*/

LOCAL BOOL  
WriteTTYText (HWND hWnd, LPSTR lpText, int nLength)
{
    int				i;
    PTTYINFO			pTTYInfo;
    RECT			rect;
    BOOL			fNewLine;
    long			offset;
    long			colEnd;
    long			screenEnd;
    BOOL			wraper;

    
    pTTYInfo = (PTTYINFO) GetWindowLong (hWnd, GWL_PTTYINFO);
    if (pTTYInfo == NULL)
	    return ( FALSE );

    
    /* Calculate offset of cursor, end of current column, and end of screen */
    offset = (pTTYInfo->nRow * pTTYInfo->actNColumn) + pTTYInfo->nColumn;
    colEnd = (pTTYInfo->nRow + 1) * pTTYInfo->actNColumn;
    screenEnd = pTTYInfo->actNRow * pTTYInfo->actNColumn;
    
    
    /* Text is allowed to wrap around to subsequent lines, but not past end
     * of screen */
    if (offset + nLength > screenEnd)
	nLength = screenEnd - offset;


    /* Calculate bounding rectangle. */
    if (offset + nLength <= colEnd) {
	/* Single line. */
	rect.left = (pTTYInfo->nColumn * pTTYInfo->xChar) + pTTYInfo->xOffset;
	rect.right = rect.left + (pTTYInfo->xChar * nLength);
	rect.top = (pTTYInfo->nRow * pTTYInfo->yChar) + pTTYInfo->yOffset;
	rect.bottom = rect.top + pTTYInfo->yChar;
    }
    else {
	/* Wraps across multiple lines.  Calculate one rect to cover all 
	 * lines. */
	rect.left = 0;
	rect.right = pTTYInfo->ySize;
	rect.top = (pTTYInfo->nRow * pTTYInfo->yChar) + pTTYInfo->yOffset;
	rect.bottom = ((((offset + nLength) / pTTYInfo->actNColumn) + 1) * 
			pTTYInfo->yChar) + pTTYInfo->yOffset;
    }
    
    
    /* Apply text and attributes to screen in one smooth motion. */
    _fmemcpy (pTTYInfo->pScreen + offset, lpText, nLength);
    _fmemset (pTTYInfo->pAttrib + offset, pTTYInfo->curAttrib, nLength);
    
    
    /* New cursor position. */
    offset += nLength;
    if (offset == screenEnd) {
	pTTYInfo->nRow = pTTYInfo->actNRow - 1;
	pTTYInfo->nColumn = pTTYInfo->actNColumn - 1;
    }
    else {
	pTTYInfo->nRow = offset / pTTYInfo->actNColumn;
	pTTYInfo->nColumn = offset % pTTYInfo->actNColumn;
    }
    
 
    /* Invalidate rectangle */
    pTTYInfo->screenDirty = TRUE;
#ifdef OWNRECT
    MyInvalidateRect (pTTYInfo, hWnd, &rect);
#else
    InvalidateRect (hWnd, &rect, FALSE);
#endif
    return (TRUE);
}


/*---------------------------------------------------------------------------
 *  BOOL  WriteTTYChar (HWND hWnd, char ch)
 *
 *  Description:
 *	Write a single character to the cursor position and advance the
 *	cursor.  Does not handle carage control.
 *
 *  Parameters:
 *     HWND hWnd
 *        handle to TTY window
 *
 *     char ch
 *	  character being written.
 *
/*--------------------------------------------------------------------------*/

LOCAL BOOL  
WriteTTYChar (HWND hWnd, char ch)
{
    int				i;
    PTTYINFO			pTTYInfo;
    RECT			rect;
    BOOL			fNewLine;
    long			offset;
    long			colEnd;
    long			screenEnd;
    BOOL			wraper;

    pTTYInfo = (PTTYINFO) GetWindowLong (hWnd, GWL_PTTYINFO);
    if (pTTYInfo == NULL)
	return (FALSE);

#if 0
   /* xxxx for debugging only to provide a bread point for updates to status
    * xxxx line. */
    if (pTTYInfo->nRow == pTTYInfo->actNRow - 3)
	wraper = TRUE;
#endif

    offset = (pTTYInfo->nRow * pTTYInfo->actNColumn) +
	    pTTYInfo->nColumn;
    
    *(pTTYInfo->pScreen + offset) = ch;
    *(pTTYInfo->pAttrib + offset) = pTTYInfo->curAttrib;

    rect.left = (pTTYInfo->nColumn * pTTYInfo->xChar) + pTTYInfo->xOffset;
    rect.right = rect.left + pTTYInfo->xChar;
    rect.top = (pTTYInfo->nRow * pTTYInfo->yChar) + pTTYInfo->yOffset;
    rect.bottom = rect.top + pTTYInfo->yChar;
    pTTYInfo->screenDirty = TRUE;
#ifdef OWNRECT
    MyInvalidateRect (pTTYInfo, hWnd, &rect);
#else
    InvalidateRect (hWnd, &rect, FALSE);
#endif
    
    

    /* Line Wrap. */
    if (pTTYInfo->nColumn < pTTYInfo->actNColumn - 1)
	pTTYInfo->nColumn++ ;
    else if ((pTTYInfo->autoWrap == WRAP_ON || 
	      pTTYInfo->autoWrap == WRAP_NO_SCROLL) && 
		    pTTYInfo->nRow < pTTYInfo->actNRow - 1) {
       pTTYInfo->nRow++;
       pTTYInfo->nColumn = 0;
    }
    return (TRUE);
}

/*---------------------------------------------------------------------------
 *  VOID  GoModalDialogBoxParam( HINSTANCE hInstance,
 *                                   LPCSTR lpszTemplate, HWND hWnd,
 *                                   DLGPROC lpDlgProc, LPARAM lParam )
 *
 *  Description:
 *     It is a simple utility function that simply performs the
 *     MPI and invokes the dialog box with a DWORD paramter.
 *
 *  Parameters:
 *     similar to that of DialogBoxParam() with the exception
 *     that the lpDlgProc is not a procedure instance
 *
/*--------------------------------------------------------------------------*/

LOCAL VOID  
GoModalDialogBoxParam( HINSTANCE hInstance, LPCSTR lpszTemplate,
                                 HWND hWnd, DLGPROC lpDlgProc, LPARAM lParam )
{
   DLGPROC  lpProcInstance ;

   lpProcInstance = (DLGPROC) MakeProcInstance( (FARPROC) lpDlgProc,
                                                hInstance ) ;
   DialogBoxParam( hInstance, lpszTemplate, hWnd, lpProcInstance, lParam ) ;
   FreeProcInstance( (FARPROC) lpProcInstance ) ;
}





/*---------------------------------------------------------------------------
 *  BOOL FAR PASCAL __export AboutDlgProc( HWND hDlg, UINT uMsg,
 *                                WPARAM wParam, LPARAM lParam )
 *
 *  Description:
 *     Simulates the Windows System Dialog Box.
 *
 *  Parameters:
 *     Same as standard dialog procedures.
 *
/*--------------------------------------------------------------------------*/

BOOL FAR PASCAL __export 
AboutDlgProc (HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
   switch (uMsg) {
      case WM_INITDIALOG:
      {
         int          idModeString ;
         char         szTemp [81];
         DWORD        dwFreeMemory, dwWinFlags ;
         WORD         wFreeResources, wRevision, wVersion ;

#ifdef ABOUTDLG_USEBITMAP
         // if we are using the bitmap, hide the icon

         ShowWindow( GetDlgItem( hDlg, IDD_ABOUTICON ), SW_HIDE ) ;
#endif
         // sets up the version number for Windows

         wVersion = LOWORD (GetVersion());
         switch (HIBYTE (wVersion)) {
            case 10:
               wRevision = 1;
               break;

            default:
               wRevision = 0;
               break;
         }
         wVersion &= 0xFF;

	 
         /* sets up version number for PINE */

         GetDlgItemText (hDlg, IDD_VERSION, szTemp, sizeof (szTemp));
         wsprintf (TempBuf, szTemp, mswin_majorver(), mswin_minorver(),
		   mswin_compilation_date());
         SetDlgItemText (hDlg, IDD_VERSION, (LPSTR) TempBuf);

         // get by-line

         LoadString (GETHINST (hDlg), IDS_BYLINE, TempBuf,
                     sizeof (TempBuf));
         SetDlgItemText (hDlg, IDD_BYLINE, TempBuf);

      }
      return ( TRUE ) ;

#ifdef ABOUTDLG_USEBITMAP
      // used to paint the bitmap

      case WM_PAINT:
      {
         HBITMAP      hBitMap ;
         HDC          hDC, hMemDC ;
         PAINTSTRUCT  ps ;

         // load bitmap and display it

         hDC = BeginPaint( hDlg, &ps ) ;
         if (NULL != (hMemDC = CreateCompatibleDC( hDC )))
         {
            hBitMap = LoadBitmap( GETHINST( hDlg ),
                                  MAKEINTRESOURCE( PINEBITMAP ) ) ;
            hBitMap = SelectObject( hMemDC, hBitMap ) ;
            BitBlt( hDC, 10, 10, 64, 64, hMemDC, 0, 0, SRCCOPY ) ;
            DeleteObject( SelectObject( hMemDC, hBitMap ) ) ;
            DeleteDC( hMemDC ) ;
         }
         EndPaint( hDlg, &ps ) ;
      }
      break ;
#endif

      case WM_COMMAND:
         if ((WORD) wParam == IDD_OK)
         {
            EndDialog( hDlg, TRUE ) ;
            return ( TRUE ) ;
         }
         break;
   }
   return ( FALSE ) ;

} // end of AboutDlgProc()









/*---------------------------------------------------------------------------
 *  BOOL  SelectTTYFont( HWND hDlg )
 *
 *  Description:
 *     Selects the current font for the TTY screen.
 *     Uses the Common Dialog ChooseFont() API.
 *
 *  Parameters:
 *     HWND hDlg
 *        handle to settings dialog
 *
/*--------------------------------------------------------------------------*/

BOOL  
LOCAL SelectTTYFont (HWND hWnd)
{
    CHOOSEFONT		cfTTYFont;
    LOGFONT		newFont;
    PTTYINFO		pTTYInfo;

    pTTYInfo = (PTTYINFO) GetWindowLong (hWnd, GWL_PTTYINFO);
    if (pTTYInfo == NULL)
	return (FALSE);

    _fmemcpy (&newFont, &gpTTYInfo->lfTTYFont, sizeof (LOGFONT));

    cfTTYFont.lStructSize    = sizeof (CHOOSEFONT);
    cfTTYFont.hwndOwner      = hWnd ;
    cfTTYFont.hDC            = NULL ;
    cfTTYFont.rgbColors      = pTTYInfo->rgbFGColor;
    cfTTYFont.lpLogFont      = &newFont;
    cfTTYFont.Flags          = CF_SCREENFONTS | CF_FIXEDPITCHONLY |
	    CF_EFFECTS | CF_INITTOLOGFONTSTRUCT | CF_ANSIONLY | 
	    CF_FORCEFONTEXIST | CF_LIMITSIZE;
    cfTTYFont.nSizeMin	     = FONT_MIN_SIZE;
    cfTTYFont.nSizeMax	     = FONT_MAX_SIZE;
    cfTTYFont.lCustData      = NULL ;
    cfTTYFont.lpfnHook       = NULL ;
    cfTTYFont.lpTemplateName = NULL ;
    cfTTYFont.hInstance      = GETHINST (hWnd);



    if (ChooseFont (&cfTTYFont)) {
	pTTYInfo->rgbFGColor = cfTTYFont.rgbColors;
	ResetTTYFont (hWnd, pTTYInfo, &newFont);
    }

    return (TRUE);
}



/*
 * Set a specific color (forground, background, reverse, normal) to
 * the color specified by name. 
 */
LOCAL void
SetColorAttribute (COLORREF *cf, char *colorName)
{
    MSWINColor		*ct;
    int			i;
    
    for (ct = MSWINColorTable; ct->colorName != NULL; ++ct) {
	if (strcmpi (colorName, ct->colorName) == 0) goto FoundColor;
    }
    
    /* color name not in table.  Try converting RGB string. */
    ConvertRGBString (colorName, cf);
    return;
    
FoundColor:
    *cf = ct->colorRef;

    /* Redraw screen. */
    gpTTYInfo->screenDirty = TRUE;
    gpTTYInfo->eraseScreen = TRUE;
#ifdef OWNRECT
    MyInvalidateRect (gpTTYInfo, hWnd, NULL);
#else
    InvalidateRect (ghTTYWnd, NULL, FALSE);
#endif
}



/*
 * Convert a string to an integer.
 */
LOCAL BOOL
ScanInt (char *str, int min, int max, int *val)
{
    BOOL	numberString;
    char	*c;
    int		v;
    
    
    if (str == NULL) return (FALSE);
    if (*str == '\0' || strlen (str) > 9) return (FALSE);
    numberString = TRUE;
    for (c = str; *c != '\0'; ++c) {
	if (!isdigit(*c))
	    return (FALSE);
    }
    v = atoi (str);
    if (v < min || v > max) 
	return (FALSE);
    *val = v;
    return (TRUE);
}



/*
 * Convert a RGB string to a color ref.  The string should look like:
 *    rrr,ggg,bbb
 * where rrr, ggg, and bbb are numbers between 0 and 255 that represent
 * red, gree, and blue values.  Must be comma seperated.
 * Returns:
 *	TRUE	- Successfully converted string.
 *	FALSE	- Bad format, 'cf' unchanged.
 */

LOCAL BOOL
ConvertRGBString (char *colorName, COLORREF *cf)
{
    int		cv;
    char	*c;
    char	*s;
    char	*p;
    char	cpy[16];
    int		rgb[3];
   
    /* Some basic tests. */
    if (colorName == NULL) return (FALSE);		/* Not Null? */
    if (strlen (colorName) > 11) return (FALSE);	/* Not too long? */
    for (s = colorName; *s != '\0'; ++s) {
	if (!isdigit (*s) && *s != ',') return (FALSE); /* Valid characters?*/
    }
    
    /* Work with a copy of string. */
    strcpy (cpy, colorName);
    s = cpy;				/* Start at beginning. */
    for (cv = 0; cv < 3; ++cv) {	/* Get three digits. */
	if (cv < 2) {			/* Expect only two commas. */
	    c = strchr (s, ',');		/* Find next comma. */
	    if (c == NULL) return (FALSE);
	    *c = '\0';
	}
	if (*s == ',' || *s == '\0') return (FALSE);
	if (strlen (s) > 3) return (FALSE);
	rgb[cv] = atoi (s);
	if (rgb[cv] < 0 || rgb[cv] > 255) return (FALSE);
	s = c + 1;
    }

    *cf = RGB (rgb[0], rgb[1], rgb[2]);
    printf ("%d, %d, %d  ", rgb[0], rgb[1], rgb[2]);
    return (TRUE);
}


/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 *
 *                  Mouse Selection routeins.
 *
 *++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/

LOCAL BOOL	SelSelected = FALSE;
LOCAL BOOL	SelTracking = FALSE;
LOCAL int	SelAnchorRow;
LOCAL int	SelAnchorCol;
LOCAL int	SelPointerRow;
LOCAL int	SelPointerCol;
typedef struct {
    CHAR	*pRow;
    int		len;
} CopyRow;

LOCAL void
SelRSet (int oStart, int oEnd)
{
    CharAttrib	*pca;
    
    for (pca = gpTTYInfo->pAttrib + oStart; oStart < oEnd; ++pca, ++oStart)
	*pca |= CHAR_ATTR_SEL;
}


LOCAL void
SelRClear (int oStart, int oEnd)
{
    CharAttrib	*pca;
    
    for (pca = gpTTYInfo->pAttrib + oStart; oStart < oEnd; ++pca, ++oStart)
	*pca &= ~CHAR_ATTR_SEL;
}


LOCAL void
SelRInvalidate (int oStart, int oEnd)
{
    RECT	rect;
    int		sRow, sCol;
    int		eRow, eCol;
    
    sRow = oStart / gpTTYInfo->actNColumn;
    sCol = oStart % gpTTYInfo->actNColumn;
    eRow = oEnd   / gpTTYInfo->actNColumn;
    eCol = oEnd   % gpTTYInfo->actNColumn;
    
    rect.top = (sRow * gpTTYInfo->yChar) + gpTTYInfo->yOffset;
    rect.bottom = ((eRow+1) * gpTTYInfo->yChar) + gpTTYInfo->yOffset;
    if (sRow == eRow) {
	rect.left = (sCol * gpTTYInfo->xChar) + gpTTYInfo->xOffset;
	rect.right = ((eCol+1) * gpTTYInfo->xChar) + gpTTYInfo->xOffset;
    } else {
	rect.left = gpTTYInfo->xOffset;
	rect.right = (gpTTYInfo->actNColumn * gpTTYInfo->xChar) + 
			gpTTYInfo->xOffset;
    }
    InvalidateRect (ghTTYWnd, &rect, FALSE);
}



/*
 * Start a mouse selection.
 */
LOCAL void
SelStart (int nRow, int nColumn)
{
    SelClear ();
    SelTracking = TRUE;
    SelSelected = TRUE;
    SelPointerRow = SelAnchorRow = nRow;
    SelPointerCol = SelAnchorCol = nColumn;
    return;
}


/*
 * Finish a mouse selection.
 */
LOCAL void
SelFinish (int nRow, int nColumn)
{
    if (nRow == SelAnchorRow && nColumn == SelAnchorCol) {
	/* Mouse up in same place it went down - no selection. */
	SelClear ();
    }
    else {
	/* Update screen selection and set final position of mouse
	 * then turn of mouse tracking.  Selection remains in effect
	 * until SelClear is called. */
	SelTrackMouse (nRow, nColumn);
	SelTracking = FALSE;
    }
}

LOCAL void
SelClear (void)
{
    int		a, p;
    int		s, e;
    CharAttrib	*pca;

    
    if (!SelSelected) 
	return;


    /* Convert the anchor and point coordinates to offsets then
     * order the offsets. */
    a = (SelAnchorRow * gpTTYInfo->actNColumn) + SelAnchorCol;
    p = (SelPointerRow * gpTTYInfo->actNColumn) + SelPointerCol;
    if (a < p) {
	s = a;
	e = p;
    } else {
	s = p;
	e = a;
    }

    /* Clear selected attribute of those cells in range. */
    SelRClear (s, e);
    SelRInvalidate (s, e);
    SelSelected = FALSE;
    SelTracking = FALSE;
}


/*
 * Update the position of the mouse point.
 */
LOCAL void
SelTrackXYMouse (int xPos, int yPos)
{
    int		nRow;
    int		nColumn;
    
    nColumn = (xPos - gpTTYInfo->xOffset) / gpTTYInfo->xChar;
    nRow = (yPos - gpTTYInfo->yOffset) / gpTTYInfo->yChar;

    SelTrackMouse (nRow, nColumn);
}


/*
 * Update the position of the mouse point.
 */
LOCAL void
SelTrackMouse (int nRow, int nColumn)
{
    int		a, p, n;
    int		s, e;
    
    if (!SelTracking)
	return;

    /* Constrain the cel position to be on the screen.  But allow
     * for the Column to be one past the right edge of the screen so
     * the user can select the right most cel of a row. */
    nColumn = max (0, nColumn);
    nColumn = min (gpTTYInfo->actNColumn, nColumn);
    nRow = max (0, nRow);
    nRow = min (gpTTYInfo->actNRow-1, nRow);


    /* Convert the anchor, previous mouse position, and new mouse
     * position to offsets. */
    a = (SelAnchorRow * gpTTYInfo->actNColumn) + SelAnchorCol;
    p = (SelPointerRow * gpTTYInfo->actNColumn) + SelPointerCol;
    n = (nRow * gpTTYInfo->actNColumn) + nColumn;
    
    /* If previous position same as current position, do nothing. */
    if (p == n)
	return;

    /* there are six possible orderings of the points, each with
     * a different action:
     *	order		clear		set		redraw
     *	n p a				n - p		n - p
     *	p n a		p - n				p - n
     *	p a n		p - a		a - n		p - n
     *	a p n				p - n		p - n
     *  a n p		n - p				n - p
     *  n a p		a - p		n - a		n - p
     */
    if (p < a) {
	if (n < a) {
	    if (n < p) {
		SelRSet (n, p);
		SelRInvalidate (n, p);
	    } else {
		SelRClear (p, n);
		SelRInvalidate (p, n);
	    }
	} else {
	    SelRClear (p, a);
	    SelRSet (a, n);
	    SelRInvalidate (p, n);
        }
    } else {
	if (n > a) {
	    if (n > p) {
		SelRSet (p, n);
		SelRInvalidate (p, n);
	    } else {
		SelRClear (n, p);
		SelRInvalidate (n, p);
	    }
	} else {
	    SelRClear (a, p);
	    SelRSet (n, a);
	    SelRInvalidate (n, p);
        }
    }

    /* Set new pointer. */
    SelPointerRow = nRow;
    SelPointerCol = nColumn;
}



LOCAL BOOL
SelAvailable (void)
{
    return (SelSelected);
}

/*
 * Copy screen data to clipboard.  Actually appends data from screen to 
 * existing handle so as we can implement a "Copy Append" to append to
 * existing clipboard data.  
 *
 * The screen does not have real line terminators.  We decide where the 
 * actual screen data ends by scanning the line (row) from end backwards
 * to find the first non-space.
 * 
 * I don't know how many bytes of data I'll be appending to the clipboard.
 * So I implemented in two passes.  The first finds all the row starts
 * and length while the second copies the data.
 */

LOCAL void
SelDoCopy (HANDLE hCB, DWORD lenCB)
{
    HANDLE		newCB;		/* Used in reallocation. */
    CHAR		*pCB;		/* Points to CB data. */	
    CHAR		*p2;		/* Temp pointer to screen data. */
    int			sRow, eRow;	/* Start and End Rows. */
    int			sCol, eCol;	/* Start and End columns. */
    int			row, c1, c2;	/* temp row and column indexes. */
    int			totalLen;	/* total len of new data. */
    CopyRow		*rowTable, *rp;	/* pointers to table of rows. */
    BOOL		noLastCRLF;
    

    if (OpenClipboard (ghTTYWnd)) {		/* ...and we get the CB. */
      if (EmptyClipboard ()) {		/* ...and clear previous CB.*/


	/* Find the start and end row and column. */
	if ( (SelAnchorRow * gpTTYInfo->actNColumn) + SelAnchorCol < 
	     (SelPointerRow * gpTTYInfo->actNColumn) + SelPointerCol) {
	    sRow = SelAnchorRow;
	    sCol = SelAnchorCol;
	    eRow = SelPointerRow;
	    eCol = SelPointerCol;
	}
	else {
	    sRow = SelPointerRow;
	    sCol = SelPointerCol;
	    eRow = SelAnchorRow;
	    eCol = SelAnchorCol;
	}

	/* Allocate a table in which we store info on rows. */
	rowTable = (CopyRow *) MemAlloc (sizeof (CopyRow) * (eRow-sRow+1));
	if (rowTable == NULL)
	    goto Fail1;

	/* Find the start and length of each row. */
	totalLen = 0;
	for (row = sRow, rp = rowTable; row <= eRow; ++row, ++rp) {
	    /* Find beginning and end columns, which depends on if
	     * this is the first or last row in the selection. */
	    c1 = (row == sRow ? sCol : 0);
	    c2 = (row == eRow ? eCol : gpTTYInfo->actNColumn);

	    /* Calculate pointer to beginning of this line. */
	    rp->pRow = gpTTYInfo->pScreen + 
		    ((row * gpTTYInfo->actNColumn) + c1);

	    /* Back down from end column to find first non space. 
	     * noLastCRLF indicates if it looks like the selection
	     * should include a CRLF on the end of the line.  It
	     * gets set for each line, but only the setting for the
	     * last line in the selection is remembered (which is all
	     * we're interested in). */
	    p2 = gpTTYInfo->pScreen + 
		    ((row * gpTTYInfo->actNColumn) + c2);
	    noLastCRLF = TRUE;
	    while (c2 > c1) {
		if (*(p2-1) != ' ')
		    break;
		noLastCRLF = FALSE;
		--c2;
		--p2;
	    }

	    /* Calculate size of line, then increment totalLen plus 2 for
	     * the CRLF which will terminate each line. */
	    rp->len = c2 - c1;
	    totalLen += rp->len + 2;
	}

	/* Reallocate the memory block.  Add one byte for null terminator. */
	newCB = GlobalReAlloc (hCB, lenCB + totalLen + 1, 0);
	if (newCB == NULL)
	    goto Fail2;
	hCB = newCB;

	pCB = GlobalLock (hCB);
	if (pCB == NULL)
	    goto Fail2;

	/* Append each of the rows, deliminated by a CRLF. */
	pCB += lenCB;
	for (row = sRow, rp = rowTable; row <= eRow; ++row, ++rp) {
	    if (rp->len > 0) {
		_fmemcpy (pCB, rp->pRow, rp->len);
		pCB += rp->len;
	    }
	    if (row < eRow || !noLastCRLF) {
		*pCB++ = ASCII_CR;
		*pCB++ = ASCII_LF;
	    }
	}
	*pCB = '\0';			/* Null terminator. */
	MemFree (rowTable);
	GlobalUnlock (hCB);

	/* Attempt to pass the data to the clipboard, then release clipboard
	 * and exit function. */
	if (SetClipboardData (CF_TEXT, hCB) == NULL)
		      /* Failed!  Free the data. */
		      GlobalFree (hCB);
	CloseClipboard ();
      }
    }
    return;
    
    
	/* Error exit. */
Fail2:	MemFree (rowTable);
Fail1:	GlobalFree (hCB);
	CloseClipboard ();
	return;
}




    

/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 *
 *                  Upper Layer Screen routines.
 *
 *++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/

/*
 * Flush the write accumulator buffer.
 */
LOCAL void
FlushWriteAccum (void)
{
    if (gpTTYInfo->writeAccumCount > 0) {
	WriteTTYText (ghTTYWnd, gpTTYInfo->writeAccum, 
					gpTTYInfo->writeAccumCount);
	gpTTYInfo->writeAccumCount = 0;
    }
}



/*
 *	Called to get mouse event.
 */
int
WinGetMEvent (MEvent * pMouse)
{
	return (MQGet (pMouse));
}


/*
 *	Called in idle loops.   Dispatch messages but do not yeild control
 */
void
WinIdle (void)
{
    MSG		msg;
    DWORD	start;
    
    start = GetTickCount ();
#ifdef CDEBUG
    if (Debug > 16) 
	fprintf (DebugFile, "WinIdle:: Entered\n");
#endif
    if (PeekMessage (&msg, NULL, 0, 0, PM_NOYIELD | PM_REMOVE)) {
	if (ghAccel == NULL || 
		!TranslateAccelerator (ghTTYWnd, ghAccel, &msg)) {
	    TranslateMessage (&msg);
	    DispatchMessage (&msg);
	}
    }
#ifdef CDEBUG
    if (Debug > 16) 
	fprintf (DebugFile, "WinIdle::  Delay %ld msec\n",
		GetTickCount () - start);
#endif
}


/*
 * Set up debugging stuff.
 */
mswin_setdebug (int debug, FILE *debugfile)
{
    /* Accept new file only if we don't already have a file. */
    if (DebugFile == 0) {
	Debug = debug;
	DebugFile = debugfile;
	if (debug >= MEM_DEBUG_LEVEL) 
	    MemDebug (TRUE, DebugFile);
    }
}



/*
 * Set a callback for function 'ch'
 */
int
mswin_setresizecallback (int (*cb)())
{
    int		i;
    int		e;
    
    
    /*
     * Look through whole array for this call back function.  Don't
     * insert duplicate.  Also look for empty slot.
     */
    e = -1;
    for (i = 0; i < RESIZE_CALLBACK_ARRAY_SIZE; ++i) {
	if (gpTTYInfo->resizer[i] == cb) 
	    return (0);
        if (e == -1 && gpTTYInfo->resizer[i] == NULL) 
	    e = i;
    }
    
    /*
     * Insert in empty slot or return an error.
     */
    if (e != -1) {
	gpTTYInfo->resizer[e] = cb;
	return (0);
    }
    return (-1);
}

/*
 * Clear all instances of the callback function 'cb'
 */
int
mswin_clearresizecallback (int (*cb)())
{
    int		i;
    int		status;
    
    status = -1;
    for (i = 0; i < RESIZE_CALLBACK_ARRAY_SIZE; ++i) {
	if (gpTTYInfo->resizer[i] == cb) {
	    gpTTYInfo->resizer[i] = NULL;
	    status = 0;
	}
    }
    return (status);
}


void
mswin_beginupdate (void)
{
    gpTTYInfo->fMassiveUpdate = TRUE;
}



void
mswin_endupdate (void)
{
    gpTTYInfo->fMassiveUpdate = FALSE;
    MoveTTYCursor (ghTTYWnd);
}



int
mswin_setwindow (char *fontName, char *fontSize, char *fontStyle, 
	char *windowPosition)
{
    LOGFONT		newFont;
    int			newHeight;
    HDC			hDC;
    int			ppi;
    RECT		wndRect, cliRect;
    char		*c;
    char		*n;
    BOOL		numberString;
    int			wColumns, wRows;
    int			wXPos, wYPos;
    int			wXBorder, wYBorder;
    int			wXSize, wYSize;
    char		wp[WIN_POS_STR_MAX_LEN + 1];
    
    
#ifdef SDEBUG
    if (Debug >= 5) 
	fprintf (DebugFile, "mswin_setwindow:::  entered, minimized:  %d\n",
		gpTTYInfo->fMinimized);
#endif

    /* Require a font name to set font info. */
    if (fontName != NULL && *fontName != '\0' && 
	    strlen (fontName) <= LF_FACESIZE - 1) {
	    
	hDC = GetDC (ghTTYWnd);
	ppi = GetDeviceCaps (hDC, LOGPIXELSY);
	ReleaseDC (ghTTYWnd, hDC);

	/* Default height, then examin the requested fontSize. */
	newFont.lfHeight =  -MulDiv (12, ppi, 72);
	if (ScanInt (fontSize, FONT_MIN_SIZE, FONT_MAX_SIZE, &newHeight)){
	    newFont.lfHeight = -MulDiv (newHeight, ppi, 72);
        }
	
	/* Default font style, then read requested style. */
	newFont.lfWeight =         0;
	newFont.lfItalic =         0;
	if (fontStyle != NULL) {
	    _strlwr (fontStyle);
	    if (strstr (fontStyle, "bold"))
		newFont.lfWeight = FW_BOLD;
	    if (strstr (fontStyle, "italic"))
		newFont.lfItalic = 1;
	}
	
	/* Everything else takes the default. */
	newFont.lfWidth =          0;
	newFont.lfEscapement =     0;
	newFont.lfOrientation =    0;
	newFont.lfUnderline =      0;
	newFont.lfStrikeOut =      0;
	newFont.lfCharSet =        ANSI_CHARSET;
	newFont.lfOutPrecision =   OUT_DEFAULT_PRECIS;
	newFont.lfClipPrecision =  CLIP_DEFAULT_PRECIS;
	newFont.lfQuality =        DEFAULT_QUALITY;
	newFont.lfPitchAndFamily = FIXED_PITCH;
	_fstrcpy (newFont.lfFaceName, fontName);
	ResetTTYFont (ghTTYWnd, gpTTYInfo, &newFont);
    }
    
    /*
     * Set window position.  String format is:  CxR+X+Y
     * Where C is the number of columns, R is the number of rows,
     * and X and Y specify the top left corner of the window.
     */
    if (windowPosition != NULL && *windowPosition != '\0') {
	if (strlen(windowPosition) > WIN_POS_STR_MAX_LEN) return (0);
	_fstrcpy (wp, windowPosition);
	
	/* Look for Columns, deliminated by 'x'. */
	c = _fstrchr (wp, 'x');
	if (c == NULL) return (0);
	*c = '\0';
	if (!ScanInt (wp, 0, 9999, &wColumns))	return (0);

	/* Look for Rows, deliminated by '+'. */
	n = c + 1;
	c = _fstrchr (n, '+');
	if (c == NULL) return (0);
	*c = '\0';
	if (!ScanInt (n, 0, 9999, &wRows))	return (0);
	
	/* Look for X position, deliminated by '+'. */
	n = c + 1;
	c = _fstrchr (n, '+');
	if (c == NULL) return (0);
	*c = '\0';
	if (!ScanInt (n, 0, 9999, &wXPos))	return (0);
	
	/* And get Y position, deliminated by end of string. */
	n = c + 1;
	if (!ScanInt (n, 0, 9999, &wYPos))	return (0);
	
	
	/* Get the current window rect and client area. */
	GetWindowRect (ghTTYWnd, &wndRect);
	GetClientRect (ghTTYWnd, &cliRect);
	
	
	/* Calculate boarder sizes. */
	wXBorder = wndRect.right - wndRect.left - cliRect.right;
	wYBorder = wndRect.bottom - wndRect.top - cliRect.bottom;
	
	
	/* Calculate new window pos and size. */
	wXSize = wXBorder + (wColumns * gpTTYInfo->xChar) + 
						(2 * gpTTYInfo->xOffset);
	wYSize = wYBorder + (wRows * gpTTYInfo->yChar) + 
						(2 * gpTTYInfo->yOffset);
	
	
	/* If it fits on screen, move window. */
	GetWindowRect (GetDesktopWindow(), &wndRect);
	if (wXPos + wXSize > wndRect.right) return (0);
	if (wYPos + wYSize > wndRect.bottom) return (0);
	if (!gpTTYInfo->fMinimized) 
	    MoveWindow (ghTTYWnd, wXPos, wYPos, wXSize, wYSize, TRUE);
	else {
	    gpTTYInfo->fDesiredSize = TRUE;
	    gpTTYInfo->xDesPos = wXPos;
	    gpTTYInfo->yDesPos = wYPos;
	    gpTTYInfo->xDesSize = wXSize;
	    gpTTYInfo->yDesSize = wYSize;
	}
    }
    return (0);
}



/*
 * Retreive the current font name, font size, and window position
 * These get stored in the pinerc file and will be passed to 
 * mswin_setwindow() when pine starts up.  See pinerc for comments
 * on the format.
 */
int
mswin_getwindow (char *fontName, char *fontSize, char *fontStyle, 
	char *windowPosition)
{
    HDC			hDC;
    int			ppi;
    RECT		wndRect;
    
    if (fontName != NULL) {
	_fstrcpy (fontName, gpTTYInfo->lfTTYFont.lfFaceName);
    }

    if (fontSize != NULL) {
	hDC = GetDC (ghTTYWnd);
	ppi = GetDeviceCaps (hDC, LOGPIXELSY);
	ReleaseDC (ghTTYWnd, hDC);
	sprintf (fontSize, "%d", MulDiv (-gpTTYInfo->lfTTYFont.lfHeight,
					72, ppi));
    }
    if (fontStyle != NULL) {
	char		*sep[] = {"", ", "};
	int		iSep = 0;
	
	*fontStyle = '\0';
	if (gpTTYInfo->lfTTYFont.lfWeight >= FW_BOLD) {
	    strcat (fontStyle, "bold");
	    iSep = 1;
        }
	if (gpTTYInfo->lfTTYFont.lfItalic) {
	    strcat (fontStyle, sep[iSep]);
	    strcat (fontStyle, "italic");
        }
    }


    if (windowPosition != NULL) {
	GetWindowRect (ghTTYWnd, &wndRect);
	sprintf (windowPosition, "%dx%d+%d+%d", gpTTYInfo->actNColumn, 
		gpTTYInfo->actNRow, wndRect.left, wndRect.top);
    }
    return (0);
}



/*
 * Set the window help text.  Add or delete the Help menu item as needed.
 */
int
mswin_sethelptext (char *pHelpText)
{
    HMENU		hMenu;
    BOOL		brc;

    
    gpHelpText = pHelpText;

    hMenu = GetMenu (ghTTYWnd);
    if (gpHelpText == NULL) {
	if (gfHelpMenu) {
	    brc = DeleteMenu (hMenu, IDM_HELP, MF_BYCOMMAND);
	    DrawMenuBar (ghTTYWnd);
	}
	gfHelpMenu = FALSE;
    }
    else {
	if (!gfHelpMenu) {
	    brc = AppendMenu (hMenu, MF_STRING | MF_ENABLED, IDM_HELP, 
		    "&Help");
	    DrawMenuBar (ghTTYWnd);
	}
	gfHelpMenu = TRUE;
    }
    
}


/*
 * Set the text displayed when someone tries to close the application 
 * the wrong way.
 */
int
mswin_setclosetext (char *pCloseText)
{
    gpCloseText = pCloseText;
}


/*
 *	Called to see if there is a character available.
 */
int
mswin_charavail (void)
{
    MSG		msg;
    BOOL	ca, pa, ma;
    DWORD	start;
    
    /*
     * If there are no windows messages waiting for this app, GetMessage
     * can take a long time.  So before calling GetMessage check if
     * there is anything I should be doing.  If there is, then only
     * call PeekMessage.
     * BUT!  Don't let too much time elapse between calls to GetMessage
     * or we'll shut out other windows.
     */
    ca = CQAvailable ();
    pa = EditPasteAvailable ();
#ifdef CDEBUG
    start = GetTickCount ();
    if (Debug > 16) 
	fprintf (DebugFile, "mswin_charavail::  Entered, ca %d, pa %d\n",ca,pa);
#endif
    if ((ca || pa) && GetTickCount () < gGMLastCall + GM_MAX_TIME) 
        ma = PeekMessage (&msg, NULL, 0, 0, PM_NOYIELD | PM_REMOVE);
    else {
        ma = GetMessage (&msg, NULL, 0, 0);
	gGMLastCall = GetTickCount ();
    }
    if (ma) {
	if (ghAccel == NULL || 
		!TranslateAccelerator (ghTTYWnd, ghAccel, &msg)) {
	    TranslateMessage (&msg);
	    DispatchMessage (&msg);
	}
    }
#ifdef CDEBUG
    if (Debug > 16) 
	fprintf (DebugFile, "mswin_charavail::  Delay %ld msec\n",
		GetTickCount () - start);
#endif

    return (pa || ca || CQAvailable ());
}



	

/*
 *	Call to get next character.  Dispatch one message.
 */
int
mswin_getc (void)
{
    BOOL	ca, pa, ma;
    MSG		msg;
    DWORD	start;
    
    mswin_flush();
    
    

    /*
     * If there are no windows messages waiting for this app, GetMessage
     * can take a long time.  So before calling GetMessage check if
     * there is anything I should be doing.  If there is, then only
     * call PeekMessage.
     */
    ca = CQAvailable ();
    pa = EditPasteAvailable ();
#ifdef CDEBUG
    if (Debug > 16) {
	start = GetTickCount ();
	fprintf (DebugFile, "mswin_getc::  Entered, ca %d pa %d\n", ca, pa);
    }
#endif
    if ((ca || pa) && GetTickCount () < gGMLastCall + GM_MAX_TIME) 
        ma = PeekMessage (&msg, NULL, 0, 0, PM_NOYIELD | PM_REMOVE);
    else {
        ma = GetMessage (&msg, NULL, 0, 0);
	gGMLastCall = GetTickCount ();
    }
    if (ma) {
	if (ghAccel == NULL || 
		!TranslateAccelerator (ghTTYWnd, ghAccel, &msg)) {
	    TranslateMessage (&msg);
	    DispatchMessage (&msg);
	}
    }
#ifdef CDEBUG
    if (Debug > 16) 
	fprintf (DebugFile, "mswin_getc::  Delay %ld msec\n",
		GetTickCount () - start);
#endif


    if (pa) {
	SelClear ();
	return (EditPasteGet ());
    }
    if (ca || CQAvailable ()) {
	SelClear();
	return (CQGet ());
    }
    return (MSWIN_KEY_NODATA);
}




/*
 *	Like mswin_getc, but don't yeild control.
 */
int
mswin_getc_fast (void)
{
    MSG		msg;
    
    if (EditPasteAvailable ()) {
	SelClear ();
	return (EditPasteGet ());
    }
    if (CQAvailable ()) {
	SelClear ();
	return (CQGet ());
    }
    return (MSWIN_KEY_NODATA);
}







/*
 * ibmopen - open termnal
 */
int
mswin_open (void)
{
    /* This is a no-op.  All of this should have been set up already. */
    revexist = TRUE;
    return (0);
}


/*
 * ibmclose - Close terminal
 */
int
mswin_close (void)
{
    /* Another no-op. */
    return (0);
}




/*
 * ibmmove - Move cursor to...
 */
int
mswin_move (int row, int column)

{
    FlushWriteAccum ();
    if (row < 0 || row >= gpTTYInfo->actNRow)
	return (-1);
    if (column < 0 || column >= gpTTYInfo->actNColumn)
	return (-1);
    gpTTYInfo->nRow = row;
    gpTTYInfo->nColumn = column;
    MoveTTYCursor (ghTTYWnd);
    return (0);
}



int
mswin_getpos (int *row, int *column)
{
    FlushWriteAccum ();
    if (row == NULL || column == NULL)
	return (-1);
    *row = gpTTYInfo->nRow;
    *column = gpTTYInfo->nColumn;
    return (0);
}



int
mswin_getscreensize (int *row, int *column)
{
    if (row == NULL || column == NULL)
	return (-1);
    *row = gpTTYInfo->actNRow;
    *column = gpTTYInfo->actNColumn;
#ifdef SDEBUG
    if (Debug >= 5) 
	fprintf (DebugFile, "mswin_getscreensize::: reported size %d, %d\n",
		*row, *column);
#endif
    return (0);
}




int
mswin_showcursor (int show)
{
    int		wasShow;
    
    
    wasShow = (gpTTYInfo->wCursorState & CS_SHOW) != 0;
	
    if (show) {
	
	/* if the cursor was not already show, show it now. */
	if (!wasShow) {
	    gpTTYInfo->wCursorState |= CS_SHOW;

	    if (gpTTYInfo->wCursorState == CS_VISIBLE) {
		    CreateCaret (ghTTYWnd, NULL, gpTTYInfo->xChar, 
			    gpTTYInfo->yChar);
		    ShowCaret (ghTTYWnd);
	    }
	    MoveTTYCursor (ghTTYWnd);
        }
    }
    else {
	
	/* If the cursor is shown, hide it. */
	if (wasShow) {
	    if (gpTTYInfo->wCursorState == CS_VISIBLE) {
		HideCaret (ghTTYWnd);
		DestroyCaret();
	    }

	    gpTTYInfo->wCursorState &= ~CS_SHOW;
	}
    }

    return (wasShow);
}



int
mswin_puts (char *str)
{
    int			strLen;

    FlushWriteAccum ();
    if (str == NULL)
	return (-1);
    strLen = strlen (str);
    if (strLen > 0)
	WriteTTYText (ghTTYWnd, str, strLen);
    return (0);
}



int
mswin_putblock (char *str, int strLen)
{
    FlushWriteAccum ();
    if (str == NULL)
	return (-1);
    if (strLen > 0)
	WriteTTYText (ghTTYWnd, str, strLen);
    return (0);
}



/*
 * mswin_putc - put a character at the current position in the
 *	     current colors
 */

int
mswin_putc (int c)
{
    BYTE		cc;

    cc = (char)(c & 0xff);

    if (cc >= ' ') {
	/* Not carrage control. */
	gpTTYInfo->writeAccum[gpTTYInfo->writeAccumCount++] = cc;
	if (gpTTYInfo->writeAccumCount == WRITE_ACCUM_SIZE)
		FlushWriteAccum ();
    }
    else {
	/* Carrage control.  Need to flush write accumulator and
	 * write this char. */
	FlushWriteAccum ();
	WriteTTYBlock (ghTTYWnd, &cc, 1);
    }
    return (0);
}



/* 
 * ibmoutc - output a single character with the right attributes, but
 *           don't advance the cursor
 */
int
mswin_outc (char c)
{
    RECT	rect;
    long	offset;
    
    FlushWriteAccum ();
    
    switch (c) {
    case ASCII_BEL:
	MessageBeep (0);
	break;
	
    case ASCII_BS:
    case ASCII_CR:
    case ASCII_LF:
	/* Do nothing for these screen motion characters. */
	break;
	
	
    default:
	/* Paint character to screen. */
	offset = (gpTTYInfo->nRow * gpTTYInfo->actNColumn) + gpTTYInfo->nColumn;
	*(gpTTYInfo->pScreen + offset) = c;
	*(gpTTYInfo->pAttrib + offset) = gpTTYInfo->curAttrib;
	
	/* Invalidate rectange covering singel character. */
	rect.left = (gpTTYInfo->nColumn * gpTTYInfo->xChar) +
		gpTTYInfo->xOffset;
	rect.right = rect.left + gpTTYInfo->xChar;
	rect.top = (gpTTYInfo->nRow * gpTTYInfo->yChar) + 
		gpTTYInfo->yOffset;
	rect.bottom = rect.top + gpTTYInfo->yChar;
	gpTTYInfo->screenDirty = TRUE;
#ifdef OWNRECT
	MyInvalidateRect (gpTTYInfo, ghTTYWnd, &rect);
#else
	InvalidateRect (ghTTYWnd, &rect, FALSE);
#endif
	break;
    }
    return (0);
}



/*
 * mswin_delchar - delete character at cursor.  Shift end of line left.
 *	Cursor stays in position.
 */
int
mswin_delchar (void)
{
    CHAR	*cStart;
    CharAttrib	*aStart;
    long	length;
    RECT	rect;
    
    FlushWriteAccum ();
    
    /* From position right of cursor to end of line. */
    length = gpTTYInfo->actNColumn - (gpTTYInfo->nColumn + 1);

    /* Shift end of line. */
    if (length > 0) {
        cStart = gpTTYInfo->pScreen + (gpTTYInfo->nRow *gpTTYInfo->actNColumn)
				+ gpTTYInfo->nColumn;
	_fmemmove (cStart, cStart+1, (size_t) length);
        aStart = gpTTYInfo->pAttrib + (gpTTYInfo->nRow *gpTTYInfo->actNColumn)
			+ gpTTYInfo->nColumn;
        _fmemmove (aStart, aStart + 1, (size_t) length);
    }
    
    /* Clear last char in line. */
    *(cStart + length) = ' ';
    *(aStart + length) = CHAR_ATTR_NORM;

    /* Invalidate from cursor to end of line. */
    rect.left = (gpTTYInfo->nColumn * gpTTYInfo->xChar) + gpTTYInfo->xOffset;
    rect.right = gpTTYInfo->xSize;
    rect.top = (gpTTYInfo->nRow * gpTTYInfo->yChar) + gpTTYInfo->yOffset;
    rect.bottom = rect.top + gpTTYInfo->yChar;
    gpTTYInfo->screenDirty = TRUE;
#ifdef OWNRECT
    MyInvalidateRect (gpTTYInfo, ghTTYWnd, &rect);
#else
    InvalidateRect (ghTTYWnd, &rect, FALSE);
#endif

    return (0);
}



/*
 * mswin_inschar - insert character in current position.  Shift line
 *	to right and drop last char on line. 
 *	Cursor advances one 
 */
int
mswin_inschar (int c)
{
    CHAR	*cStart;
    CharAttrib	*aStart;
    long	length;
    RECT	rect;
    
    FlushWriteAccum ();
    
    /* From cursor to end of line - 1
    length = (gpTTYInfo->actNColumn - gpTTYInfo->nColumn) - 1;

    /* Shift end of line. */
    if (length > 0) {
        cStart = gpTTYInfo->pScreen + (gpTTYInfo->nRow *gpTTYInfo->actNColumn)
				+ gpTTYInfo->nColumn;
	_fmemmove (cStart+1, cStart, (size_t) length);
        aStart = gpTTYInfo->pAttrib + (gpTTYInfo->nRow *gpTTYInfo->actNColumn)
			+ gpTTYInfo->nColumn;
        _fmemmove (aStart+1, aStart, (size_t) length);
    }
    
    /* Insert new char. */
    *(cStart + length) = (char)(c & 0x00ff);
    *(aStart + length) = gpTTYInfo->curAttrib;

    /* Invalidate from cursor to end of line. */
    rect.left = (gpTTYInfo->nColumn * gpTTYInfo->xChar) + gpTTYInfo->xOffset;
    rect.right = gpTTYInfo->xSize;
    rect.top = (gpTTYInfo->nRow * gpTTYInfo->yChar) + gpTTYInfo->yOffset;
    rect.bottom = rect.top + gpTTYInfo->yChar;
    gpTTYInfo->screenDirty = TRUE;
#ifdef OWNRECT
    MyInvalidateRect (gpTTYInfo, ghTTYWnd, &rect);
#else
    InvalidateRect (ghTTYWnd, &rect, FALSE);
#endif
    
    /* move cursor forward one space. */
    if (gpTTYInfo->nColumn < gpTTYInfo->actNColumn - 1) {
	++gpTTYInfo->nColumn;
	MoveTTYCursor (ghTTYWnd);
    }

    return (0);
}



/*
 * ibmrev - change reverse video state
 */
int
mswin_rev (int state)
{
    int		curState;
    
    curState = (gpTTYInfo->curAttrib & CHAR_ATTR_REV);
    if ((state && !curState) || (!state && curState)) {
	FlushWriteAccum ();
	if (state) 
	    gpTTYInfo->curAttrib |= CHAR_ATTR_REV;
	else
	    gpTTYInfo->curAttrib &= ~CHAR_ATTR_REV;
    }
    return (0);
}
	


/*
 * Get current reverse video state. 
 */
int
mswin_getrevstate (void)
{
    return ((gpTTYInfo->curAttrib & CHAR_ATTR_REV) != 0);
}



/*
 * ibmeeol - erase to the end of the line
 */
int
mswin_eeol (void)
{
    CHAR	*cStart;
    CharAttrib	*aStart;
    long	length;
    RECT	rect;
    
    FlushWriteAccum ();
    
    /* From current position to end of line. */
    length = gpTTYInfo->actNColumn - gpTTYInfo->nColumn;		

    cStart = gpTTYInfo->pScreen + (gpTTYInfo->nRow * gpTTYInfo->actNColumn)
			+ gpTTYInfo->nColumn;
    _fmemset (cStart, ' ', (size_t) length);
    
    aStart = gpTTYInfo->pAttrib + (gpTTYInfo->nRow * gpTTYInfo->actNColumn)
			+ gpTTYInfo->nColumn;
    _fmemset (aStart, CHAR_ATTR_NORM, (size_t) length);

    rect.left = (gpTTYInfo->nColumn * gpTTYInfo->xChar) + 
	    gpTTYInfo->xOffset;
    rect.right = gpTTYInfo->xSize;
    rect.top = (gpTTYInfo->nRow * gpTTYInfo->yChar) + 
	    gpTTYInfo->yOffset;
    rect.bottom = rect.top + gpTTYInfo->yChar;
    gpTTYInfo->screenDirty = TRUE;
#ifdef OWNRECT
    MyInvalidateRect (gpTTYInfo, ghTTYWnd, &rect);
#else
    InvalidateRect (ghTTYWnd, &rect, FALSE);
#endif

    return (0);
}



/*
 * ibmeeop - clear from cursor to end of page
 */
int
mswin_eeop (void)
{
    CHAR	*cStart;
    CharAttrib	*aStart;
    long	length;
    RECT	rect;
    
    FlushWriteAccum ();
    /* From current position to end of screen. */

    cStart = gpTTYInfo->pScreen + (gpTTYInfo->nRow * gpTTYInfo->actNColumn)
			+ gpTTYInfo->nColumn;
    length = (gpTTYInfo->pScreen + (gpTTYInfo->actNColumn * gpTTYInfo->actNRow))
		    - cStart;
    _fmemset (cStart, ' ', (size_t) length);
    
    aStart = gpTTYInfo->pAttrib + (gpTTYInfo->nRow * gpTTYInfo->actNColumn)
			+ gpTTYInfo->nColumn;
    _fmemset (aStart, CHAR_ATTR_NORM, (size_t) length);

    
    /* Invalidate a rectangle that includes all of the current line down
     * to the bottom of the window. */
    rect.left = 0;
    rect.right = gpTTYInfo->xSize;
    rect.top = (gpTTYInfo->nRow * gpTTYInfo->yChar) + 
	    gpTTYInfo->yOffset;
    rect.bottom = gpTTYInfo->ySize;
    gpTTYInfo->screenDirty = TRUE;
#ifdef OWNRECT
    MyInvalidateRect (gpTTYInfo, ghTTYWnd, &rect);
#else
    InvalidateRect (ghTTYWnd, &rect, FALSE);
#endif

    return (0);
}



/*
 * ibmbeep - system beep...
 */
int
mswin_beep (void)
{
    MessageBeep (MB_OK);
    return (0);
}


/*
 * pause - wait in function for specified number of seconds.
 */
int
mswin_pause (int seconds)
{
    DWORD	stoptime;
    
    stoptime = GetTickCount () + (DWORD) seconds * 1000;
    while (stoptime > GetTickCount ()) 
	WinIdle ();
}
	


/*
 * ibmflush - Flush output to screen.
 *
 *	NOTE:  This function does not really wait for the changes to
 *	       be flushed to the screen because:
 *		1) If the part of the window in which the change was
 *		   made is off the screen we may never, ever receive
 *		   an update and will hang up in this function.
 *		2) It does not seem to matter that this function does
 *		   not really flush to the screen.
 *
 */
int
mswin_flush (void)
{
    FlushWriteAccum ();
    return (0);
}



/*
 * ibmflush - really flush output to screen.
 *
 *	But be careful:  if the changes made were off screen, 
 *	this function may never exit.
 */
int
mswin_really_flush (void)
{
    FlushWriteAccum ();
#ifdef OWNRECT
    if (gpTTYInfo->screenDirty) 
	InvalidateRect (ghTTYWnd, &gpTTYInfo->rPaintRect, FALSE);
#endif

    /* 
     * While the screen is dirty, Yeild to windows.
     * BUT, don't to this if the screen is minimized.
     */
    if (!gpTTYInfo->fMinimized) {
	while (gpTTYInfo->screenDirty) 
	    WinIdle ();
    }
    return (0);
}


/*
 * A replacement for fflush
 * relies on #define fflush mswin_fflush
 */
#undef fflush
int
mswin_fflush (FILE *f)
{
    if (f == stdout) {
	mswin_flush();
    }
    else
	fflush (f);
}



/*
 * Signals whether or not Paste should be turned on in the
 * menu bar.
 */
int
mswin_allowpaste (int on)
{
    HMENU		hMenu;
    
    hMenu = GetMenu (ghTTYWnd);
    gPasteEnabled = on;
    if(!IsClipboardFormatAvailable (CF_TEXT) || on == MSWIN_PASTE_DISABLE)
      EnableMenuItem (hMenu, IDM_EDIT_PASTE, (MF_BYCOMMAND | MF_GRAYED));
    else
      EnableMenuItem (hMenu, IDM_EDIT_PASTE, (MF_BYCOMMAND | MF_ENABLED));

}



/*
 * Signals whether or not Copy/Cut should be turned on in the
 * menu bar.
 */
int
mswin_allowcopy (getc_t copyfunc)
{
    HMENU		hMenu;

    hMenu = GetMenu (ghTTYWnd);
    gCopyCutFunction = copyfunc;
    gAllowCopy = (copyfunc != NULL);
}



/*
 * Signals whether or not Copy/Cut should be turned on in the
 * menu bar.
 */
int
mswin_allowcopycut (getc_t copyfunc)
{
    HMENU		hMenu;
    
    hMenu = GetMenu (ghTTYWnd);
    gCopyCutFunction = copyfunc;
    gAllowCopy = gAllowCut = (copyfunc != NULL);
}



int
mswin_allowexit (int allow, char *denyReason, int quitCmd)
{
    HMENU		hMenu;
    
    
    hMenu = GetMenu (ghTTYWnd);
    switch (allow) {
    case MSWIN_EXIT_DISABLE:
	gAllowExit = allow;
	break;
	
    case MSWIN_EXIT_DENY:
	gAllowExit = allow;
	gExitDenyReason = denyReason;
	break;

    case MSWIN_EXIT_SENDCHAR:
	gAllowExit = allow;
	gExitDenyReason = NULL;
	gExitSendChar = quitCmd;
	break;
    }
    return (0);
}




/*---------------------------------------------------------------------------
 *
 * Printing stuff
 *
 *-------------------------------------------------------------------------*/

/*
 * Printing globals
 */
LOCAL HDC	P_PrintDC;	/* Printer device context. */
LOCAL int	P_PageRows;	/* Number of rows we put on a page. */
LOCAL int	P_PageColumns;	/* Number of columns we put on a page. */
LOCAL int	P_RowHeight;	/* Hight of a row in printer pixels. */
LOCAL int	P_CurRow;	/* Current row, starting at zero */
LOCAL int	P_CurCol;	/* Current col, starting at zero. */
LOCAL int	P_TopOffset;	/* Top Margin offset, in pixels. */
LOCAL int	P_LeftOffset;	/* Top Margin offset, in pixels. */
LOCAL HFONT	P_hFont;	/* Handle to printing font. */
char		*P_LineText;	/* Pointer to line buffer. */




/*
 * Define the margin as number of lines at top and bottom of page. 
 * (might be better to define as a percent of verticle page size)
 */
#define VERTICLE_MARGIN		3	/* lines at top and bottom of page. */
#define HORIZONTAL_MARGIN	1	/* margine at left & right in chars */

/*
 * Several errors that can be reported. 
 */
#define PE_DIALOG_FAILED	1
#define PE_USER_CANCEL		2
#define PE_CANT_START_DOC	3
#define PE_OUT_OF_MEMORY	4
#define PE_GENERAL_ERROR	5
#define PE_OUT_OF_DISK		6
#define PE_PRINTER_NOT_FOUND	7
#define PE_PINE_INTERNAL	8
#define PE_FONT_FAILED		9


LOCAL struct pe_error_message {
	int		error_code;
	char		*error_message;
 } P_ErrorMessages[] = {
	{ PE_DIALOG_FAILED, "Print Dialog Failed"},
        { PE_USER_CANCEL, "User canceled" },
        { PE_CANT_START_DOC,	"Can't start document" },
	{ PE_OUT_OF_MEMORY,	"Out of memory" },
	{ PE_OUT_OF_DISK,	"Out of disk space" },
	{ PE_PRINTER_NOT_FOUND,	"Printer not found" },
	{ PE_PINE_INTERNAL,	"Pine internal error" },
	{ PE_FONT_FAILED,	"Failed to create font" },
	{ 0, NULL }};



/*
 * Send text in the line buffer to the printer.  
 * Advance to next page if necessary.
 */
LOCAL int
_print_send_line (void)
{
    int		status;
    
    status = 0;
    if (P_CurCol > 0) 
	TextOut (P_PrintDC, P_LeftOffset, 
				P_TopOffset + (P_CurRow * P_RowHeight), 
		P_LineText, P_CurCol);
    P_CurCol = 0;
    if (++P_CurRow >= P_PageRows) 
	status = _print_send_page ();
	
    return (status);
}

    

/*
 * Advance one page. 
 */
int
_print_send_page ()
{
    int		status;
    
    
    status = EndPage (P_PrintDC);
    if (status < 0)
	goto PrintError;
    P_CurRow = 0;
    StartPage (P_PrintDC);
    SelectObject (P_PrintDC, P_hFont);
    return (0);
    
    
PrintError:
    switch (status) {
    case SP_USERABORT:	return (PE_USER_CANCEL);
    case SP_OUTOFDISK:  return (PE_OUT_OF_DISK);
    case SP_OUTOFMEMORY:  return (PE_OUT_OF_MEMORY);
    default:
    case SP_ERROR:	return (PE_GENERAL_ERROR);
    }
}



/*
 * Map errors reported to my own error set.
 */
int
_print_map_dlg_error (DWORD error)
{
    switch (error) {
    case 0:			    return (PE_USER_CANCEL);
    case CDERR_MEMALLOCFAILURE:
    case CDERR_MEMLOCKFAILURE:
				    return (PE_OUT_OF_MEMORY);
    case PDERR_PRINTERNOTFOUND:
    case PDERR_NODEVICES:
				    return (PE_PRINTER_NOT_FOUND);
    case CDERR_STRUCTSIZE:
				    return (PE_PINE_INTERNAL);
    default:
				    return (PE_GENERAL_ERROR);
    }
}
	


/*
 * Get the printer ready.  Returns ZERO for success, or an error code that
 * can be passed to mswin_print_error() to get a text message.
 */
int
mswin_print_ready (char *docDesc)
{
    PRINTDLG		pd;
    DOCINFO		di;
    TEXTMETRIC		tm;
    HDC			hDC;
    int			fontSize;	/* Size in Points. */
    int			ppi;		/* Pixels per inch in device. */
    int			xChar;
    int			status;
    HFONT		oldFont;
    LOGFONT		newFont;
    
    
    status = 0;
    P_PrintDC = NULL;

    
    /*
     * Show print dialog.
     */
    pd.lStructSize = sizeof (pd);
    pd.hwndOwner = ghTTYWnd;
    pd.hDevMode = NULL;
    pd.hDevNames = NULL;
    pd.Flags = PD_ALLPAGES | PD_NOSELECTION | PD_NOPAGENUMS | 
	    PD_HIDEPRINTTOFILE | PD_RETURNDC;
    pd.nCopies = 1;
    if (PrintDlg (&pd) == 0) 
	return (_print_map_dlg_error (CommDlgExtendedError()));

    
    /*
     * Returns the device name which we could use to remember what printer 
     * they selected.  But instead, we just toss them.
     */
    if (pd.hDevNames)
	GlobalFree (pd.hDevNames);
    if (pd.hDevMode)
	GlobalFree (pd.hDevMode);

    /*
     * Get the device drawing context.
     * (does PringDlg() ever return success but fail to return a DC?)
     */
    if (pd.hDC != NULL) 
	P_PrintDC = pd.hDC;
    else {
        status = PE_DIALOG_FAILED;
	goto Done;
    }
    
    

    
    /*
     * Start Document
     */
    di.cbSize = sizeof (DOCINFO);
    di.lpszDocName = docDesc;		/* This appears in the print manager*/
    di.lpszOutput = NULL;		/* Could suply a file name to print
					   to. */
    if (StartDoc (P_PrintDC, &di) < 0) {
	DeleteDC (P_PrintDC);
	P_PrintDC = NULL;
	status = PE_CANT_START_DOC;
	goto Done;
    }
    
    /*
     * Get the current font size in points, then create a new font
     * of same size for printer.  Do the calculation using the actual
     * screen resolution instead of the logical resolution so that
     * we get pretty close to the same font size on the printer
     * as we see on the screen.
     */
    hDC = GetDC (ghTTYWnd);			/* Temp screen DC. */
    ppi = (int) ((float)GetDeviceCaps (hDC, VERTRES) / 
		((float) GetDeviceCaps (hDC, VERTSIZE) / 25.3636));
#ifdef FDEBUG
    if (Debug >= 8) {
	fprintf (DebugFile, "mswin_print_ready:  Screen res %d ppi, font height %d pixels\n",
	    ppi, -gpTTYInfo->lfTTYFont.lfHeight);
        fprintf (DebugFile, "                    Screen height %d pixel, %d mm\n",
		GetDeviceCaps (hDC, VERTRES), GetDeviceCaps (hDC, VERTSIZE));
    }
#endif
    ReleaseDC (ghTTYWnd, hDC);
    
    /* Convert from screen pixels to points. */
    fontSize = MulDiv (-gpTTYInfo->lfTTYFont.lfHeight, 72, ppi);
    ++fontSize;		/* Fudge a little. */
    
    
    /* Get printer resolution and convert form points to printer pixels. */
    ppi = GetDeviceCaps (P_PrintDC, LOGPIXELSY);
    newFont.lfHeight =  -MulDiv (fontSize, ppi, 72);

#ifdef FDEBUG
    if (Debug >= 8) {
        fprintf (DebugFile, "                    font Size %d points\n",
		fontSize);
	fprintf (DebugFile, "                    printer res %d ppi, font height %d pixels\n",
	    ppi, -newFont.lfHeight);
        fprintf (DebugFile, "                    paper height %d pixel, %d mm\n",
		GetDeviceCaps (P_PrintDC, VERTRES), 
		GetDeviceCaps (P_PrintDC, VERTSIZE));
    }
#endif
    
    /* Fill out rest of font description and request font. */
    newFont.lfWidth =          0;
    newFont.lfEscapement =     0;
    newFont.lfOrientation =    0;
    newFont.lfWeight =         0;
    newFont.lfItalic =         0;
    newFont.lfUnderline =      0;
    newFont.lfStrikeOut =      0;
    newFont.lfCharSet =        ANSI_CHARSET;
    newFont.lfOutPrecision =   OUT_DEFAULT_PRECIS;
    newFont.lfClipPrecision =  CLIP_DEFAULT_PRECIS;
    newFont.lfQuality =        DEFAULT_QUALITY;
    newFont.lfPitchAndFamily = FIXED_PITCH;
    _fstrcpy (newFont.lfFaceName, gpTTYInfo->lfTTYFont.lfFaceName);
    P_hFont = CreateFontIndirect (&newFont);
    if (P_hFont == NULL) {
	status = PE_FONT_FAILED;
	DeleteDC (P_PrintDC);
	goto Done;
    }
    
    
    /*
     * Start page.  
     * Must select font for each page or it returns to default.
     * Windows seems good about maping selected font to a font that 
     * will actually print on the printer.
     */
    StartPage (P_PrintDC);
    oldFont = SelectObject (P_PrintDC, P_hFont);
    
    
    /*
     * Find out about the font we got and set up page size and margins.
     * This assumes all pages are the same size - which seems reasonable.
     */
    GetTextMetrics (P_PrintDC, &tm);
    xChar = tm.tmAveCharWidth;			
    P_RowHeight = tm.tmHeight + tm.tmExternalLeading;
    
    /* HORZRES and VERTRES report size of page in printer pixels. */
    P_PageColumns = GetDeviceCaps (P_PrintDC, HORZRES) / xChar;
    P_PageRows = GetDeviceCaps (P_PrintDC, VERTRES) / P_RowHeight;
    
    /* We allow a margin at top and bottom measured in text rows. */
    P_PageRows -= VERTICLE_MARGIN * 2;
    P_TopOffset = VERTICLE_MARGIN * P_RowHeight;
    
    /* And allow for a left and right margine measured in characters. */
    P_PageColumns -= HORIZONTAL_MARGIN * 2;
    P_LeftOffset = HORIZONTAL_MARGIN * xChar;
    
    P_CurRow = 0;			/* Start counting at row 0. */
    P_CurCol = 0;			/* At character 0. */
    P_LineText = MemAlloc (P_PageColumns + 1);
    if (P_LineText == NULL) {
	EndDoc (P_PrintDC);
	DeleteObject (P_hFont);
	P_hFont = NULL;
	DeleteDC (P_PrintDC);
	P_PrintDC = NULL;
	status = PE_OUT_OF_MEMORY;
	goto Done;
    }
    
    
Done:
    return (status);
}



/* 
 * Called when printing is done.
 * xxx what happens if there is an error?  Will this get called?
 */
int
mswin_print_done (void)
{
    if (P_PrintDC != NULL) {
	if (P_LineText != NULL)
		MemFree (P_LineText);
	EndPage (P_PrintDC);
	EndDoc (P_PrintDC);
	DeleteObject (P_hFont);
	P_hFont = NULL;
	DeleteDC (P_PrintDC);
	P_PrintDC = NULL;
    }
    return (0);
}



/*
 * Return ponter to a text string that describes the erorr.
 */
char *
mswin_print_error (int error_code)
{
    int		i;
    
    for (i = 0; P_ErrorMessages[i].error_message != NULL; ++i) {
	if (P_ErrorMessages[i].error_code == error_code) 
	    return (P_ErrorMessages[i].error_message);
    }
    return ("(Unknow error)");
}




/*
 * Add a single character to the current line.  
 * Only handles CRLF carrage control.
 */
int
mswin_print_char (int c)
{
    int		status;
    
    status = 0;
    switch (c) {
    case ASCII_CR:
	break;

    case ASCII_LF:
	status = _print_send_line ();
	break;

    default:
	if (P_CurCol == P_PageColumns)
	    _print_send_line ();
        *(P_LineText + P_CurCol++) = (char) c;
	break;
    }
    return (status);
}
    



/*
 * Send a string to the printer.
 */
int
mswin_print_text (char *text)
{
    if (text != NULL) {
	while (*text) 
	    mswin_print_char (*(text++));
    }
}



#if 0
/*
 * Send a whole line to the printer.
 * Assume no carrage control in the line.
 */
int
mswin_print_line (char *line)
{
	
    int		status;
    int		linePos;
    int		lineLen;
    int		count;
    
    status = 0;
    lineLen = strlen (line);
    linePos = 0;
    
    /* Any text already in P_LineText is kept and appended to. */
    if (lineLen == 0) {
	/* If the line is empty, send that. */
	_print_send_line ();
    }
    else {
	/* Send the line to the printer.  Appending to anything that
	 * was already there, and wrapping to subsequent lines if
	 * there is more that will fit in one line. */
	while (lineLen > 0) {
	    count = min (lineLen - linePos, P_PageColumns - P_CurCol);
	    memcpy (P_LineText + P_CurCol, line + linePos, count);
	    P_CurCol += count;
	    linePos += count;
	    lineLen -= count;
	    _print_send_line ();
	}
    }
    return (0);
}
#endif








/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 *
 *        File dialog boxes.
 *
 *++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
/*
 * Display a save file dialog box.
 *
 *	dir	>	directory to start search in
 *		<	directory finished in.
 *	fName	<	Name of file selectec
 *	nMaxFName	length of dir and fName.
 */
int
mswin_savefile (char *dir, char *fName, int nMaxFName)
{
    OPENFILENAME	ofn;
    char		filters[128];
    DWORD		rc;
    char		*p;
    size_t		l;


    /* Set filters array.  (pairs of null terminated strings, terminated
     * by a double null). */
    _fstrcpy (filters, "Text Files (*.txt)#*.txt#All Files (*.*)#*.*#");
    for (p = filters; *p != '\0'; ++p) {
	if (*p == '#')
	    *p = '\0';
    }



    /* Set up the BIG STRUCTURE. */
    _fmemset (&ofn, 0, sizeof(ofn));
    ofn.lStructSize = sizeof (OPENFILENAME);
    ofn.hwndOwner = ghTTYWnd;
    ofn.lpstrFilter = filters;
    ofn.lpstrCustomFilter = NULL;
    ofn.nFilterIndex = 1;
    ofn.lpstrFile = fName;
    ofn.nMaxFile = nMaxFName;
    ofn.lpstrFileTitle = NULL;
    ofn.nMaxFileTitle = 0;
    ofn.lpstrInitialDir = dir;
    ofn.lpstrTitle = "Save To File";
    ofn.Flags = OFN_NOREADONLYRETURN | 
		    OFN_OVERWRITEPROMPT | OFN_PATHMUSTEXIST;
    ofn.lpstrDefExt = "txt";

    if (GetSaveFileName (&ofn)) {
	    
	/* Copy directory name to dir. */
	strcpy (dir, fName);
	if (dir[ofn.nFileOffset-1] == '\\')
	    dir[ofn.nFileOffset-1] = '\0';
        else
	    dir[ofn.nFileOffset] = '\0';
	
	/* Move file name portion to beginning of fName buffer. */
	p = fName + ofn.nFileOffset;
	l = strlen (p) + 1;
	memmove (fName, p, l);
	return (0);
    }
    else {
	rc = CommDlgExtendedError ();
	return (-1);
    }
}




/*
 * Display an open file dialog box.
 *
 *	dir	>	directory to start search in
 *		<	directory finished in.
 *	fName	<	Name of file selectec
 *	nMaxFName	length of dir and fName.
 */
int
mswin_openfile (char *dir, char *fName, int nMaxFName)
{
    OPENFILENAME	ofn;
    char		filters[128];
    DWORD		rc;
    char		*p;
    size_t		l;


    /* Set filters array.  (pairs of null terminated strings, terminated
     * by a double null). */
    _fstrcpy (filters, "Text Files (*.txt)#*.txt#All Files (*.*)#*.*#");
    for (p = filters; *p != '\0'; ++p) {
	if (*p == '#')
	    *p = '\0';
    }
    
    

    /* Set up the BIG STRUCTURE. */
    _fmemset (&ofn, 0, sizeof(ofn));
    ofn.lStructSize = sizeof (OPENFILENAME);
    ofn.hwndOwner = ghTTYWnd;
    ofn.lpstrFilter = filters;
    ofn.lpstrCustomFilter = NULL;
    ofn.nFilterIndex = 1;
    ofn.lpstrFile = fName;
    ofn.nMaxFile = nMaxFName;
    ofn.lpstrFileTitle = NULL;
    ofn.nMaxFileTitle = 0;
    ofn.lpstrInitialDir = dir;
    ofn.lpstrTitle = "Select File";
    ofn.Flags = OFN_HIDEREADONLY | OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST;
    ofn.lpstrDefExt = "txt";

    if (GetOpenFileName (&ofn)) {
	    
	/* Copy directory name to dir. */
	strcpy (dir, fName);
	if (dir[ofn.nFileOffset-1] == '\\')
	    dir[ofn.nFileOffset-1] = '\0';
        else
	    dir[ofn.nFileOffset] = '\0';
	
	/* Move file name portion to beginning of fName buffer. */
	p = fName + ofn.nFileOffset;
	l = strlen (p) + 1;
	memmove (fName, p, l);
	return (0);
    }
    else {
	rc = CommDlgExtendedError ();
	return (-1);
    }
}
	
	




/*---------------------------------------------------------------------------
 */

/*
 * pico_XXcolor() - each function sets a particular attribute
 */
pico_nfcolor(s)
char *s;
{
    SetColorAttribute (&gpTTYInfo->rgbFGColor, s);
}

pico_nbcolor(s)
char *s;
{
    SetColorAttribute (&gpTTYInfo->rgbBGColor, s);
}

pico_rfcolor(s)
char *s;
{
    SetColorAttribute (&gpTTYInfo->rgbRFGColor, s);
}

pico_rbcolor(s)
char *s;
{
    SetColorAttribute (&gpTTYInfo->rgbRBGColor, s);
}





/*
 * Signal 
 */
void (__cdecl * __cdecl signal (int sig,void (__cdecl *hndlr)(int)))(int)

{
    return (SIG_IGN);
}



int
printf (const char *fmt, ...)
{
    va_list	marker;
    int		strLen;
    
    va_start (marker, fmt);
    _vsnprintf (TempBuf, MAXLEN_TEMPSTR, fmt, marker);
    
    FlushWriteAccum ();
    strLen = strlen (TempBuf);
    if (strLen > 0) {
        if (TempBuf[strLen-1] == ASCII_LF) {
	    TempBuf[strLen-1] == ASCII_CR;
	    TempBuf[strLen] == ASCII_LF;
	    TempBuf[++strLen] = '\0';
        }
	WriteTTYBlock (ghTTYWnd, TempBuf, strLen);
    }
    return (1);
}



/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 *
 *        File quit menu.
 *
 *++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
LOCAL void
FileQuit (void)
{
    char		titleBuf[512];
    HMENU		hMenu;
    
    switch (gAllowExit) {
    case MSWIN_EXIT_DENY:
	if (gExitDenyReason != NULL) {
	    sprintf (titleBuf, "Quit %s", gszAppName);
	    MessageBox (ghTTYWnd, gExitDenyReason, titleBuf, 
		    MB_APPLMODAL | MB_OK | MB_ICONSTOP);
        }
	break;
	
    case MSWIN_EXIT_SENDCHAR:
	CQAdd (gExitSendChar, 0);
	break;
	
    default:
	/* Should not have been enabled, disable now. */
	hMenu = GetMenu (ghTTYWnd);
	EnableMenuItem (hMenu, IDM_FILE_EXIT, MF_BYCOMMAND | MF_GRAYED);
	break;
    }
}
	
	
    
/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 *
 *        Cut, Copy, and Paste operations
 *
 *++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/


/*
 * Gets called right before the menu is displayed so we can make
 * any last minute adjustments.
 */
LOCAL void
UpdateMenu (void)
{
    HMENU		hMenu;
    BOOL		brc;
    
    hMenu = GetMenu (ghTTYWnd);
    if (ghPaste == NULL) {
	/* 
	 * Not pasting.  If text is available on clipboard and we are
	 * at a place where we can paste, enable past menu option.
	 * Also check if we currently have a selection that can be copied
	 * or cut.  If so, we don't want to paste into a selection - it
	 * works but it is slow and probably not what the user intended.
	 */
	if (IsClipboardFormatAvailable (CF_TEXT) && gPasteEnabled && 
		gCopyCutFunction == NULL)
	    EnableMenuItem (hMenu, IDM_EDIT_PASTE, MF_BYCOMMAND | MF_ENABLED);
	else
	    EnableMenuItem (hMenu, IDM_EDIT_PASTE, MF_BYCOMMAND | MF_GRAYED);
	EnableMenuItem (hMenu, IDM_EDIT_CANCEL_PASTE, MF_BYCOMMAND | MF_GRAYED);
    }
    else {
	/* Currently pasting so disable paste and enable cancel paste. */
	EnableMenuItem (hMenu, IDM_EDIT_PASTE, MF_BYCOMMAND | MF_GRAYED);
	EnableMenuItem (hMenu, IDM_EDIT_CANCEL_PASTE, MF_BYCOMMAND | MF_ENABLED);
    }
    
    if (SelAvailable ()) {
	EnableMenuItem (hMenu, IDM_EDIT_CUT, MF_BYCOMMAND | MF_GRAYED);
	EnableMenuItem (hMenu, IDM_EDIT_COPY, MF_BYCOMMAND | MF_ENABLED);
	EnableMenuItem (hMenu, IDM_EDIT_COPY_APPEND, 
				MF_BYCOMMAND | MF_ENABLED);
    } else {
	if (gAllowCut)
	    EnableMenuItem (hMenu, IDM_EDIT_CUT, MF_BYCOMMAND | MF_ENABLED);
        else
	    EnableMenuItem (hMenu, IDM_EDIT_CUT, MF_BYCOMMAND | MF_GRAYED);

        if (gAllowCopy) {
	    EnableMenuItem (hMenu, IDM_EDIT_COPY, MF_BYCOMMAND | MF_ENABLED);
	    EnableMenuItem (hMenu, IDM_EDIT_COPY_APPEND, 
				MF_BYCOMMAND | MF_ENABLED);	
	}
        else {
	    EnableMenuItem (hMenu, IDM_EDIT_COPY, MF_BYCOMMAND | MF_GRAYED);
	    EnableMenuItem (hMenu, IDM_EDIT_COPY_APPEND, 
				MF_BYCOMMAND | MF_GRAYED);	
	}
    }
    
    
    if (gAllowExit == MSWIN_EXIT_SENDCHAR || 
	 (gAllowExit == MSWIN_EXIT_DENY && gExitDenyReason != NULL))
	EnableMenuItem (hMenu, IDM_FILE_EXIT, MF_BYCOMMAND | MF_ENABLED);
    else
	EnableMenuItem (hMenu, IDM_FILE_EXIT, MF_BYCOMMAND | MF_GRAYED);
    
}



/*
 * Cut region to kill buffer.
 */
LOCAL void
EditCut (void)
{
    HANDLE		hCB;
    
    if(gCopyCutFunction == kremove){
	hCB = GlobalAlloc (GMEM_MOVEABLE, 0);
	if (hCB != NULL) {
	    copyregion (1, 0);
	    EditDoCopyData (hCB, 0);
	    killregion (1, 0);	/* Kill Region. */
	    update ();		/* And update the screen */
        }
    }
}


/*
 * Copy region to kill buffer. 
 */
LOCAL void
EditCopy (void)
{
    HANDLE		hCB;
    
    if (SelAvailable()) {
	/* This is a copy of the windows selection. */
	hCB = GlobalAlloc (GMEM_MOVEABLE, 0);
	if (hCB != NULL) 
	    SelDoCopy (hCB, 0);
    } 
    else {
	    
	/* Otherwise, it's a Pico/Pine copy. */
	if(gCopyCutFunction == kremove){
	    copyregion (1, 0);
	}

	hCB = GlobalAlloc (GMEM_MOVEABLE, 0);
	if (hCB != NULL) 
	    EditDoCopyData (hCB, 0);
    }
}



/*
 * Called in responce to "Copy Append" menu command, when there is an active
 * Windows selection on the screen.
 */
LOCAL void
EditCopyAppend (void)
{
    HANDLE	hCB;
    HANDLE	hMyCopy;
    char	*pCB;
    char	*pMyCopy;
    size_t	cbSize;

    /* Attempt to copy clipboard data to my own handle. */
    hMyCopy = NULL;
    if (OpenClipboard (ghTTYWnd)) {		/* And can get clipboard. */
	hCB = GetClipboardData (CF_TEXT);
	if (hCB != NULL) {			/* And can get data. */
	    pCB = GlobalLock (hCB);
	    cbSize = strlen (pCB);		/* It's a null term string. */
	    hMyCopy = GlobalAlloc (GMEM_MOVEABLE, cbSize);
	    if (hMyCopy != NULL) {		/* And can get memory. */
		pMyCopy = GlobalLock (hMyCopy);
		if (pMyCopy != NULL) {
		    _fmemcpy (pMyCopy, pCB, cbSize);  /* Copy data. */
		    GlobalUnlock (hMyCopy);
		}
		else {
		    GlobalFree (hMyCopy);
		    hMyCopy = NULL;
		}
	    }
	    GlobalUnlock (hCB);
	}					/* GetClipboardData. */
	CloseClipboard ();
    }					/* OpenClipboard. */
    


    /* Now, if I got a copy, append current selection to that
     * and stuff it back into the clipboard. */
    if (hMyCopy != NULL) {
	if (SelAvailable ()) {
	    SelDoCopy (hMyCopy, cbSize);
	}
	else {
	    if(gCopyCutFunction == kremove) {
		copyregion (1, 0);
	    }
	    EditDoCopyData (hMyCopy, cbSize);
	}
    }
}









/*
 * Copy data from the kill buffer to the clipboard.  Handle LF->CRLF
 * translation if necessary.
 */
LOCAL void
EditDoCopyData (HANDLE hCB, DWORD lenCB)
{
    char		*pCB;
    char		*p;
    char		c;
    char		lastc = '\0';
    DWORD		cbSize;			/* Allocated size of hCB. */
    DWORD		i;
#define	BUF_INC	4096

    if (gCopyCutFunction != NULL) {		/* If there really is data. */
	if (OpenClipboard (ghTTYWnd)) {		/* ...and we get the CB. */
	    if (EmptyClipboard ()) {		/* ...and clear previous CB.*/
		pCB = GlobalLock (hCB);
		p = pCB + lenCB;
		cbSize = lenCB;
		/* Copy it. (BUG: change int arg) */
		for(i = 0L; (c = (*gCopyCutFunction)((int)i)) != -1; i++){
		    /*
		     * Rather than fix every function that might
		     * get called for character retrieval to supply
		     * CRLF EOLs, let's just fix it here.  The downside
		     * is a much slower copy for large buffers, but
		     * hey, what do they want?
		     */
		    if(lenCB + 2L >= cbSize){
			cbSize += BUF_INC;
			GlobalUnlock (hCB);
			hCB = GlobalReAlloc (hCB, cbSize, GMEM_MOVEABLE);
			if (hCB == NULL)
			  return;

			pCB = GlobalLock (hCB);
			p = pCB + lenCB;
		    }

		    if(c == ASCII_LF && lastc != ASCII_CR) {
		      *p++ = ASCII_CR;	/* insert CR before LF */
		      lenCB++;
		    }

		    *p++ = lastc = c;
		    lenCB++;
		}

		*p = '\0';
		GlobalUnlock (hCB);

		if (SetClipboardData (CF_TEXT, hCB) == NULL)
		  /* Failed!  Free the data. */
		  GlobalFree (hCB);
	    }
	    CloseClipboard (); 
        }
    }
}



/*
 * Get a handle to the current (text) clipboard and make my own copy.
 * Keep my copy locked because I'll be using it to read bytes from.
 */
LOCAL void
EditPaste (void)
{
    HANDLE	hCB;
    char	*pCB;
    char	*pPaste;
    size_t	cbSize;
   
    if (ghPaste == NULL) {		/* If we are not already pasting. */
	if (OpenClipboard (ghTTYWnd)) {		/* And can get clipboard. */
	    hCB = GetClipboardData (CF_TEXT);
	    if (hCB != NULL) {			/* And can get data. */
		pCB = GlobalLock (hCB);
		cbSize = strlen (pCB);		/* It's a null term string. */
		ghPaste = GlobalAlloc (GMEM_MOVEABLE, cbSize+1);
		if (ghPaste != NULL) {		/* And can get memory. */
		    gpPasteNext = GlobalLock (ghPaste);
		    _fmemcpy (gpPasteNext, pCB, cbSize+1);  /* Copy data. */
		    /* Keep ghPaste locked. */

		    /*
		     * If we're paste is enabled but limited to the first
		     * line of the clipboard, prune the paste buffer...
		     */
		    if(gPasteEnabled == MSWIN_PASTE_LINE
		       && (pPaste = strchr(gpPasteNext, ASCII_CR))){
			*pPaste = '\0';
			cbSize  = strlen(gpPasteNext);
		    }

		    gPasteBytesRemain = cbSize;
		    gPasteWasCR = FALSE;
#ifdef FDEBUG
		    if (Debug > 8) 
			fprintf (DebugFile, "EditPaste::  Paste %d bytes\n",
				    gPasteBytesRemain);
#endif
		}
		GlobalUnlock (hCB);
	    }
	    CloseClipboard ();
        }
    }
}






/*
 * Cancel an active paste operation.
 */
LOCAL void
EditCancelPaste (void)
{
    HANDLE	hCB;
    char	*pCB;
    char	*pPaste;
    size_t	cbSize;
   
    if (ghPaste != NULL) {	/* Must be pasting. */
	GlobalUnlock (ghPaste);	/* Then Unlock... */
	GlobalFree (ghPaste);	/* ...and free the paste buffer. */
	ghPaste = NULL;		/* Indicates no paste data. */
	gpPasteNext = NULL;		/* Just being tidy. */
	gPasteBytesRemain = 0;	/* ditto. */
#ifdef FDEBUG
	if (Debug > 8) 
	    fprintf (DebugFile, "EditCancelPaste::  Free Paste Data\n");
#endif
    }
}


/*
 * Get the next byte from the paste buffer.  If all bytes have been
 * retreived, free the paste buffer.
 * Map all CRLF sequence to a single CR.
 */
LOCAL WORD
EditPasteGet (void)
{
    int		b;
    
    b = MSWIN_KEY_NODATA;
    if (ghPaste != NULL) {		/* ghPaste tells if we are pasting. */
        if (gPasteBytesRemain > 0) {	/* Just in case... */
	    b = *gpPasteNext++;		/* Get one byte and move pointer. */
	    --gPasteBytesRemain;	/*    one less. */
	    if (gPasteWasCR && b == ASCII_LF) {
		if (gPasteBytesRemain) {
		    b = *gpPasteNext++;	/* Skip of LF. */
		    --gPasteBytesRemain;
	        }
		else 
		    b = MSWIN_KEY_NODATA;  /* Ignore last LF. */
	    }
	    gPasteWasCR = (b == ASCII_CR);
#ifdef FDEBUG
	    if (Debug > 8) 
		fprintf (DebugFile, "EditPasteGet::  char %c, gPasteWasCR %d, gPasteBytesRemain %d\n",
			b, gPasteWasCR, gPasteBytesRemain);
#endif
        }
	if (gPasteBytesRemain <= 0) {	/* All Done? */
	    GlobalUnlock (ghPaste);	/* Then Unlock... */
	    GlobalFree (ghPaste);	/* ...and free the paste buffer. */
	    ghPaste = NULL;		/* Indicates no paste data. */
	    gpPasteNext = NULL;		/* Just being tidy. */
	    gPasteBytesRemain = 0;	/* ditto. */
#ifdef FDEBUG
	    if (Debug > 8) 
		fprintf (DebugFile, "EditPasteGet::  Free Paste Data\n");
#endif
        }
    }
    return (b);
}


/*
 * Return true if Paste data is available.  If gpPaste != NULL then there
 * is paste data.
 */
LOCAL BOOL
EditPasteAvailable (void)
{
    return (ghPaste != NULL);
}





LOCAL void
ShowHelp (void)
{
    char	wndTitle[128];
    
    if (gpHelpText != NULL) {
	strcpy (wndTitle, gszAppName);
	strcat (wndTitle, " Help");
	MessageBox (ghTTYWnd, gpHelpText, "PC-Pine Help", 
		MB_OK | MB_ICONINFORMATION);
    }
}





/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 *
 *                  On Task processing
 *
 *++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/


WINHAND
mswin_inst2task (WINHAND hModule)
{
    TASKENTRY		te;
    MODULEENTRY		me;
    BOOL		brc;
    

    memset (&te, 0, sizeof (te));
    te.dwSize = sizeof (te);
    
#ifdef FDEBUG
    if (Debug >= 8) 
	fprintf (DebugFile, "mswin_inst2task:  look for x%x\n", hModule);
#endif

    brc = TaskFirst (&te);
    while (brc) {
#ifdef FDEBUG
	if (Debug >= 8)
	    fprintf (DebugFile, "  task:  '%s', hTask x%x, module x%x, inst x%x\n", 
		    te.szModule, (UINT) te.hTask, (UINT) te.hModule, 
		    (UINT) te.hInst);
#endif
	if (te.hModule == (HMODULE)hModule)
	    return ((WINHAND)te.hTask);
	if (te.hInst == (HINSTANCE)hModule)
	    return ((WINHAND)te.hTask);
	brc = TaskNext (&te);
    }
    
    return (0);
}
    
    


/*
 * Register a file deletion operation when hTask goes away.
 */
int		
mswin_ontask_del (WINHAND hTask, char *path)
{
    OnTaskItem		*ot;
    BOOL		wasEmpty;
    
    ot = MemAlloc (sizeof (OnTaskItem));
    if (ot == NULL)
	return (-1);

    wasEmpty = (gOnTaskList == NULL);
    ot->hTask = (HTASK) hTask;
    strncpy (ot->path, path, PATH_MAX);
    ot->path[PATH_MAX] = '\0';
    ot->next = gOnTaskList;
    gOnTaskList = ot;
    if (wasEmpty)
	SetTimer (ghTTYWnd, MY_TIMER_ID, MY_TIMER_SHORT_PERIOD, NULL);
	
    return (0);
}



/*
 * Execute command and wait for the 
 */
int
mswin_exec_and_wait (char *whatsit, char *command)
{
    HTASK	hTask;
    MEvent	mouse;
    char	waitingFor[256];
    BOOL	brc;
    UINT	urc;
    int		rc;
    
    
    mswin_flush ();
    
    urc = WinExec (command, SW_SHOW);
#ifdef FDEBUG
    if (Debug >= 8) {
	fprintf (DebugFile, "mswin_exec_and_wait:  command '%s'\n", command);
	fprintf (DebugFile, "                      WinExec returns module x%x\n",
		urc);
    }
#endif

    if (urc < 32) 
	return ((int)urc);
    hTask = (HTASK) mswin_inst2task (urc);

#ifdef FDEBUG
    if (Debug >= 8) 
	fprintf (DebugFile, "mswin_exec_and_wait:  module maps to task x%x, wait for it to finish\n",
		(UINT) hTask);
#endif

    if (hTask == NULL)
	return (-1);
    
    sprintf (waitingFor, "%s is currently waiting for the %s (%s) to complete.  Click \"Cancel\" to stop waiting, or \"OK\" to continue waiting.",
	    gszAppName, whatsit, command);
    

    /*
     * Go into holding pattern until the other application terminates
     * or we are told to stop waiting.
     */
    while (IsTask (hTask)) {
	rc = mswin_getc();
	brc = WinGetMEvent (&mouse);

	if (rc != MSWIN_KEY_NODATA || 
		(brc && mouse.event == MEVENT_MOUSEDOWN)) {
	
	    rc = MessageBox (ghTTYWnd, waitingFor, whatsit, 
		    MB_ICONSTOP | MB_OKCANCEL);
	    SelClear ();
	    if (rc == IDCANCEL)
		 return (-2);
	}
    }
    if (gpTTYInfo->fMinimized) 
	ShowWindow (ghTTYWnd, SW_SHOWNORMAL);
    BringWindowToTop (ghTTYWnd);
    return (0);
}

    
	



LOCAL void
ProcessOnTask (void)
{
    OnTaskItem		*ot, *pot, *not;
    BOOL		wasEmpty;
    
    if (gOnTaskList == NULL) 
	return;

    /* Scann through list. */
    wasEmpty = (gOnTaskList == NULL);
    ot = gOnTaskList;
    while (ot != NULL) {
	not = ot->next;
	
	/* Task gone? */
	if (!IsTask (ot->hTask)) {

	    /* Perform action.*/
	    unlink (ot->path);
	    
	    /* Remove item. */
	    if (ot == gOnTaskList) {
		/* Remve from head of list. */
		gOnTaskList = not;
	    } else {
		/* Remove from middle of list - find previous. */
		for (pot = gOnTaskList; pot->next == ot; pot = pot->next);
		pot->next = not;
	    }
	    MemFree (ot);
        }
		
	ot = not;
    }
    if (!wasEmpty && gOnTaskList == NULL)
	SetTimer (ghTTYWnd, MY_TIMER_ID, MY_TIMER_PERIOD, NULL);
	
}
	     




/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 *
 *                  Character Queue
 *
 *++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/


typedef struct {
	WORD	flags;
	WORD	c;
} CQEntry;

LOCAL CQEntry CQBuffer [CHARACTER_QUEUE_LENGTH];
LOCAL int CQHead;
LOCAL int CQTail;
LOCAL int CQCount;


/*---------------------------------------------------------------------------
 *  BOOL  CQInit ()
 *
 *  Description:
 *		Initialize the Character queue.
 *
 *  Parameters:
 *
 *
/*--------------------------------------------------------------------------*/
LOCAL void
CQInit (void)
{
	CQHead = 0;
	CQTail = 0;
	CQCount = 0;
}
 
 
/*---------------------------------------------------------------------------
 *  BOOL  CQAvailable (void)
 *
 *  Description:
 *		Return TRUE if there are characters in the queue.
 *
 *  Parameters:
 *
 *
/*--------------------------------------------------------------------------*/

LOCAL BOOL
CQAvailable (void)
{
	return (CQCount > 0);
}
 

 
/*---------------------------------------------------------------------------
 *  BOOL  CQAdd (WORD c, DWORC keyData)
 *
 *  Description:
 *		Add 'c' to the end of the character queue.
 *
 *  Parameters:
 *		return true if successfull.
 *
/*--------------------------------------------------------------------------*/

LOCAL BOOL
CQAdd (WORD c, DWORD keyData)
{
	if (CQCount == CHARACTER_QUEUE_LENGTH)
		return (FALSE);
	
	
	CQBuffer[CQTail].flags = 0;
	if ((keyData & 0x80000000) == 0)
	    CQBuffer[CQTail].flags |= CQ_FLAG_DOWN;
	if (keyData & 0x01000000)
	    CQBuffer[CQTail].flags |= CQ_FLAG_EXTENDED;
	if (keyData & 0x20000000)
	    CQBuffer[CQTail].flags |= CQ_FLAG_ALT;
	CQBuffer[CQTail].c = c;
	CQTail = (CQTail + 1) % CHARACTER_QUEUE_LENGTH;
	++CQCount;
	return (TRUE);
}
 
 
/*---------------------------------------------------------------------------
 *  int  CQGet ()
 *
 *  Description:
 *		Return the next byte from the head of the queue.  If there is
 *		no byte available, returns 0, which is indistinquishable from 
 *		'\0'.  So it is a good idea to call CQAvailable first.
 *
 *  Parameters:
 *		none.
 *
/*--------------------------------------------------------------------------*/

LOCAL WORD
CQGet ()
{
    WORD	c;

    if (CQCount == 0)
	return (0);

    c = CQBuffer[CQHead].c;
    CQHead = (CQHead + 1) % CHARACTER_QUEUE_LENGTH;
    --CQCount;
    return (c);
}


#if 0
/*---------------------------------------------------------------------------
 *
 *  LOCAL int MapVKtoMS (WORD c, WORD flags);
 *
 * Description:
 *	Map key received in WM_CHAR message to intermediate character code.
 *      which latter gets maped to a pico or pine character code.
 */
LOCAL int
MapVKtoMS (WORD c, WORD flags)
{
    /* Special keys. */
    if (flags & CQ_FLAG_ALT)
	return (MSWIN_KEY_NODATA);

    if (flags & CQ_FLAG_EXTENDED) {
	switch (c) {
	    case VK_UP:			return (MSWIN_KEY_UP);
	    case VK_DOWN:		return (MSWIN_KEY_DOWN);
	    case VK_RIGHT:		return (MSWIN_KEY_RIGHT);
	    case VK_LEFT:		return (MSWIN_KEY_LEFT);
	    case VK_PRIOR:		return (MSWIN_KEY_PREVPAGE);
	    case VK_NEXT:		return (MSWIN_KEY_NEXTPAGE);
	    case VK_HOME:		return (MSWIN_KEY_HOME);
	    case VK_END:		return (MSWIN_KEY_END);
	    case VK_DELETE:		return (MSWIN_KEY_DELETE);
	    case VK_F1:			return (MSWIN_KEY_F1);
	    case VK_F2:			return (MSWIN_KEY_F2);
	    case VK_F3:			return (MSWIN_KEY_F3);
	    case VK_F4:			return (MSWIN_KEY_F4);
	    case VK_F5:			return (MSWIN_KEY_F5);
	    case VK_F6:			return (MSWIN_KEY_F6);
	    case VK_F7:			return (MSWIN_KEY_F7);
	    case VK_F8:			return (MSWIN_KEY_F8);
	    case VK_F9:			return (MSWIN_KEY_F9);
	    case VK_F10:		return (MSWIN_KEY_F10);
	    case VK_F11:		return (MSWIN_KEY_F11);
	    case VK_F12:		return (MSWIN_KEY_F12);
	    default:			return (MSWIN_KEY_NODATA);
        }
    }
    
    /* Normal keys. */
    return (c);
}
#endif




/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 *
 *                  Mouse Event Queue
 *
 *++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/



LOCAL MEvent MQBuffer [MOUSE_QUEUE_LENGTH];
LOCAL int MQHead;
LOCAL int MQTail;
LOCAL int MQCount;


/*---------------------------------------------------------------------------
 *  BOOL  MQInit ()
 *
 *  Description:
 *		Initialize the Character queue.
 *
 *  Parameters:
 *
 *
/*--------------------------------------------------------------------------*/
LOCAL void
MQInit (void)
{
	MQHead = 0;
	MQTail = 0;
	MQCount = 0;
}
 
 
/*---------------------------------------------------------------------------
 *  BOOL  MQAvailable (void)
 *
 *  Description:
 *		Return TRUE if there are characters in the queue.
 *
 *  Parameters:
 *
 *
/*--------------------------------------------------------------------------*/

LOCAL BOOL
MQAvailable (void)
{
	return (MQCount > 0);
}
 

 
/*---------------------------------------------------------------------------
 *  BOOL  MQAdd ()
 *
 *  Description:
 *		Add 'c' to the end of the character queue.
 *
 *  Parameters:
 *		return true if successfull.
 *
/*--------------------------------------------------------------------------*/

LOCAL BOOL
MQAdd (int mevent, int button, int nRow, int nColumn, unsigned int keys)
{
    if (MQCount == MOUSE_QUEUE_LENGTH)
	    return (FALSE);

    /* Constrain the cel to be on the screen. */
    nColumn = max (0, nColumn);
    nColumn = min (gpTTYInfo->actNColumn-1, nColumn);
    nRow = max (0, nRow);
    nRow = min (gpTTYInfo->actNRow-1, nRow);
    MQBuffer[MQTail].event = mevent;
    MQBuffer[MQTail].button = button;
    MQBuffer[MQTail].nRow = nRow;
    MQBuffer[MQTail].nColumn = nColumn;
    MQBuffer[MQTail].flags = (unsigned long) keys;
    MQTail = (MQTail + 1) % MOUSE_QUEUE_LENGTH;
    ++MQCount;
    return (TRUE);
}
 
 
/*---------------------------------------------------------------------------
 *  BOOL  MQGet ()
 *
 *  Description:
 *		Return the next byte from the head of the queue.  If there is
 *		no byte available, returns 0, which is indistinquishable from 
 *		'\0'.  So it is a good idea to call MQAvailable first.
 *
 *  Parameters:
 *		none.
 *
/*--------------------------------------------------------------------------*/

LOCAL BOOL
MQGet (MEvent * pMouse)
{

    if (MQCount == 0)
	return (FALSE);

    *pMouse = MQBuffer[MQHead];
    pMouse->flags = MQBuffer[MQHead].flags;
    MQHead = (MQHead + 1) % MOUSE_QUEUE_LENGTH;
    --MQCount;
    return (TRUE);
}





/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 *
 *                  Memory allocation routines.
 *
 *++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/

/*
 *	The plan is to allocate small blocks in the local heap and
 *	large blocks in the global heap.  The intention is to keep
 *	the number of global allocations to a minimum.
 *
 *	The boundry between small memory and large memory is
 *	controld by the constant SMALL_MEM_BOUNDRY.  Blocks smaller
 *	than SMALL_MEM_BOUNDRY go into the local heap.  This should
 *	be set large enough to capture the majority of small memory
 *	blocks in the local heap.  But if it is too large, the local 
 *	heap will fill up and we will end up allocating a lot of small
 *	blocks in the global heap.
 *
 *	Unfortunatly, pine seems to need a large stack.  With the
 *	stack, some global data, and the heap all cramed in to a 64K
 *	segment we end up with a heap that is smaller than ideal.
 *	This could be improved by reducing the size of the stack, or
 *	by moving the heap to a seperate memory block.  I did a little
 *	work on moving the heap, but was not successful.  My attepts
 *	can be seen in the function MemATest().
 *
 *	Currently (7/8/94) I've set the stack to 32K (in pine/makefile.win),
 *	the heap to 12K, and the small memory boundry to 32 bytes.
 *	Statistics on pine memory allocation suggest that it would be better
 *	to have a 25K local heap and a 95 byte small memory boundry.
 *
 *	Statistics on memory use can be gathered by logging memory debugging
 *	to a file, then running the following awk script:
 *
   # mem.awk
   #
   #	Looks through log and find blocks that were allocated but not
   #	freed uses block id numbers, rather than addresses, since this is
   #	more accurate (and since this is what block ids exist!)
   #
   #	awk may run out of memory if the logfile gets too big.  If this
   #	happens, then another strategy will be needed...
   #

   BEGIN {
	   FS = "[ ()]+";

	   b[0] = 16; b[1] = 32; b[2] = 64; b[3] = 96; b[4] = 128; 
	   b[5] = 256; b[6] = 512; b[7] = 1024; b[8] = 2048; 
	   b[9] = 4096; b[10] = 9600; b[11] = 20000; b[12] = 40000; 
	   b[13] = 80000; b[14] = 200000000; b[15] = 0; 
	   bcount = 15;
	   for (i = 0; i < bcount; ++i)
		   c[i] = 0;

	   maxmem = 0;		# Memory high water mark.
	   maxsmallmem = 0;	# Small block memory high water mark.

	   allocs = 0;		# Count of all allocations.
	   globalallocs = 0;	# count of allocations in the global heap.
   }

   {
	   if( $1 == "MemAlloc:" ) {
		   m[$5] = $0;		# remember the line, by ID
		   ++allocs;		# Count one allocations
		   if ($9 == 1) ++globalallocs; # Count one in the global heap.

		   # Find the bucket for byte size.
		   for (i = 0; i < bcount; ++i) {
			   if (b[i] > $7) {
				   ++c[i];
				   break;
			   }
		   }
	   }
	   else if( $1 == "MemFree:" ) {	# Freed block, forget the line.
		   delete m[$5];
	   }
	   else if ($1 == "Memory") {	# Tells us what memory use is up to.
		   if ($6 > maxmem) maxmem = $6;
	   }
	   else if ($1 == "Small") {	# What small memory use is up to.
		   if ($7 > maxsmallmem) maxsmallmem = $7;
	   }
   }


   END {
	   # Print line for every block allocated by not freed.
	   for( i in m ) {
		   print m[i]
	   }

	   # Print histogram of block size. 
	   cumulative = 0;
	   for (i = 0; i < bcount; ++i) {
		   cumulative += c[i];
		   printf "%9d : %5d  (%5d)\n", b[i], c[i], cumulative;
	   }

	   print;
	   print "Max memory use:        ", maxmem;
	   print "Max small memory use:  ", maxsmallmem;
	   print;
	   print "Local allocations   ", allocs - globalallocs;
	   print "Global allocations  ", globalallocs;
	   print "Total allocations   ", allocs;
   }

 *
 *	Each memory block is assigned a unique id.  This is only used
 *	to match allocations with frees in the debug log.
 */


#define GET_SEGMENT(x)		(0xffff & ((DWORD)(x) >> 16))
#define GET_OFFSET(x)		(0xffff & (DWORD)(x))

#ifdef MEM_DEBUG
static BOOL		MemDoDebug = FALSE;	/* Doing debugging. */
static FILE		*MemDebugFile = NULL;	/* File to write to. */
#endif

#undef malloc
#undef realloc
#undef free

void		MemATest (void);


/*
 * Define some standard functions and map to our functions.
 */
void *
malloc (size_t size)
{
	return (MemAlloc (size));
}


void __far *
_fmalloc (size_t size)
{
	return (MemAlloc (size));
}


void __far *
realloc (void *memblock, size_t size)
{
	return (MemRealloc (memblock, size));
}


void __far *
_frealloc (void *memblock, size_t size)
{
	return (MemRealloc (memblock, size));
}


void 
free (void *memblock)
{
	MemFree (memblock);
}

void 
_ffree (void *memblock)
{
	MemFree (memblock);
}


/*
 * Turn on memory debugging and specify file to write to.
 */
void
MemDebug (int debug, FILE *debugFile)
{
#ifdef MEM_DEBUG
    if (debugFile == NULL) {
	MemDoDebug = FALSE;
	MemDebugFile = NULL;
    }
    else {
	MemDoDebug = debug;
	MemDebugFile = debugFile;
	fprintf (MemDebugFile, "MemDebuging set on\n");
    }
#endif /* MEM_DEBUG */
}


#define SEGHEAP
#ifndef SEGHEAP
#define GUARD_LOW0		0xbbbb
#define GUARD_LOW		0x9999
#define GUARD_HIGH		0xaaaaaaaa

#define SMALL_MEM_BOUNDRY	32

#define HEAP_SIZE		32000


/* Memory block header.  Placed at beginning of each allocated block. */
typedef struct {			/*size  len */
    WORD		guard0;		/* 00 - 02 */
    HGLOBAL		handle;		/* 02 - 02 */
    short		globalBlk;	/* 04 - 02 */
    MemSize		size;		/* 06 - 04 */
    WORD		id;		/* 0A - 02 */
    WORD		guard1;		/* 0C - 02 */
} MemBlk;				/* Total size:  0E */

typedef MemBlk __far *	MemBlkPtr;


/* Memory high guard tailer.  Placed at end of each allocated block. */
typedef struct {
    unsigned long	guard1;
} MemHighGuard;

typedef MemHighGuard __far *MemHighGuardPtr;


/*
 * Memory allocation globals. 
 */
LOCAL WORD		MemID = 0;		/* Keep track of ID. */
LOCAL unsigned long	MemLocalFails = 0;
LOCAL BOOL		MemLocalFull = FALSE;	/* True when local heap full*/
#ifdef MEM_DEBUG
LOCAL unsigned long	MemInUse = 0;		/* Total bytes in use. */
LOCAL unsigned long	MemInUseMax = 0;	/* max in use at one time. */
LOCAL unsigned long	SmallMemInUse = 0;
LOCAL unsigned long	SmallMemInUseMax = 0;
#endif /* MEM_DEBUG */



/*
 * Allocate a memory block.
 * The file and line indicate where we are called from (for debugging)
 * but in pine these mostly point to a bottel neck routine and are
 * useless.
 */
MemPtr
_MemAlloc (MemSize size, char __far * file, int line)
{
    MemBlkPtr			pBlk;
    MemHighGuardPtr		pHG;
    HGLOBAL			hBlk;
    HLOCAL			hLBlk;
    UINT			totalSize;
    BYTE		__far *	pb;

    assert (size <= MEM_BLOCK_SIZE_MAX);
    

    /*
     * Calculate total size we need to allocate.
     */
    totalSize = (UINT)size + sizeof (MemBlk) + sizeof (MemHighGuard);
    
    
    pBlk = NULL;
    
    /*
     * If it's a small block and the local heap is not full, try 
     * allocating from the local heap. 
     */
    if (size <= SMALL_MEM_BOUNDRY && !MemLocalFull) {
	    
	/* Allocate block from local storage. */
	hLBlk = LocalAlloc (LMEM_MOVEABLE, totalSize);
	if (hLBlk != NULL) {

	    /* Lock block and dereference. */
	    pBlk = (MemBlkPtr) LocalLock (hLBlk);
	    if (pBlk != NULL) {
		pBlk->handle = hLBlk;
		pBlk->globalBlk = FALSE;
	    }
	    else 
	        LocalFree (hLBlk);
	}
	else {
	    ++MemLocalFails;
	    MemLocalFull = TRUE;
#ifdef MEM_DEBUG
	    if (MemDoDebug)
		fprintf (MemDebugFile, "Local Memory alloc failed, %lu fails, %lu bytes in use\n",
			MemLocalFails, SmallMemInUse);
#endif
	}
    }

    
    /* 
     * If it is a large block, or local alloc failed, we allocate from
     * global space. 
     */
    if (pBlk == NULL) {
	    
	/* Allocate block from global storage. */
	hBlk = GlobalAlloc (GMEM_MOVEABLE, totalSize);
	if (hBlk == NULL) 
	    return (NULL);


	/* Lock block and dereference. */
	pBlk = (MemBlkPtr) GlobalLock (hBlk);
	if (pBlk == NULL) {
	    GlobalFree (hBlk);
	    return (NULL);
	}
	pBlk->handle = hBlk;
	pBlk->globalBlk = TRUE;
    }


    
    /* Fill rest of header. */
    pBlk->guard0 = GUARD_LOW0;
    pBlk->size = size;
    pBlk->id = ++MemID;
    pBlk->guard1 = GUARD_LOW;

    
    /* Find address that will be returned to caller. */
    pb = (BYTE __far *) (pBlk + 1);

    
    /* Find high guard and fill. */
    pHG = (MemHighGuardPtr) (pb + size);
    pHG->guard1 = GUARD_HIGH;
    

    /* Debugging info... */
#ifdef MEM_DEBUG
    if (MemDoDebug) {
	if( !file ) file = "??";
	fprintf (MemDebugFile, "MemAlloc: addr(%lx) id(%u) size(%ld) global(%d)\n",
		pb, pBlk->id, (long)size, pBlk->globalBlk);
	fflush (MemDebugFile);
    }
    MemInUse += totalSize;
    if (MemInUse > MemInUseMax)
	MemInUseMax = MemInUse;
    if (size <= SMALL_MEM_BOUNDRY) {
	SmallMemInUse += totalSize;
	if (SmallMemInUse > SmallMemInUseMax)
	    SmallMemInUseMax = SmallMemInUse;
    }
#endif /* MEM_DEBUG */
	

    return ((MemPtr) pb);
}




/*
 * Free a block.
 */
int
_MemFree (MemPtr block, char __far *file, int line)
{
    MemBlkPtr		pBlk;
    MemHighGuardPtr	pHG;
    HGLOBAL		hBlk;
    HLOCAL		hLBlk;
    BOOL		brc;
    UINT		totalSize;
    
    if (block == NULL)
        return (0);


    /* Find header and high guard and check them. */
    pBlk = ((MemBlkPtr)block) - 1;
    pHG = (MemHighGuardPtr) ((char __far *)block + pBlk->size);
    
    totalSize = pBlk->size + sizeof (MemBlk) + sizeof (MemHighGuard);

    /* If these changed them someone wrote where the should not have. */
    assert (pBlk->guard0 == GUARD_LOW0);
    assert (pBlk->guard1 == GUARD_LOW);
    assert (pHG->guard1 == GUARD_HIGH);


    
#ifdef MEM_DEBUG    
    /* Deubgging info... */
    if (MemDoDebug) {
	if (pBlk->size <= SMALL_MEM_BOUNDRY && 
		SmallMemInUse == SmallMemInUseMax) 
	   fprintf (MemDebugFile, "Small memory usage is up to %lu\n", SmallMemInUseMax);
	if (MemInUse == MemInUseMax) 
	   fprintf (MemDebugFile, "Memory usage is up to %lu\n", MemInUseMax);
    }
    MemInUse -= totalSize;
    if (pBlk->size <= SMALL_MEM_BOUNDRY)
        SmallMemInUse -= totalSize;
    if (MemDoDebug) {
	fprintf (MemDebugFile, "MemFree: addr(%lx) id(%u)\n", 
		block, pBlk->id);
	fflush (MemDebugFile);
    }
#endif /* MEM_DEBUG */



    /*
     * Header indicates which block it came from
     */
    if (!pBlk->globalBlk) {
	/* Unlock block */
	hLBlk = pBlk->handle;
	brc = LocalUnlock (hLBlk);
	assert (!brc);

	/* And free block. */
	hLBlk = LocalFree (hLBlk);
	assert (hLBlk == NULL);
	MemLocalFull = FALSE;
    }
    else {
	/* Unlock block */
	hBlk = pBlk->handle;
	brc = GlobalUnlock (hBlk);
	assert (!brc);

	/* And free block. */
	hBlk = GlobalFree (hBlk);
	assert (hBlk == NULL);
    }
    return (0);
}




/*
 * Reallocate a memory block.  Simplistic approach.
 */
MemPtr
_MemRealloc (MemPtr block, MemSize size, char __far * file, int line)
{
    MemPtr	newBlock;
    
    
    newBlock = MemAlloc (size);
    if (newBlock == NULL)
	return (NULL);

    if (block != NULL) {
	_fmemcpy (newBlock, block , (size_t)MIN (size, MemBlkSize (block)));
	MemFree (block);
    }
	
    return (newBlock);
}
	
	

/*
 * Return the size of a memory block
 */
MemSize
MemBlkSize (MemPtr block)
{
    MemBlkPtr			pBlk;

    if (block == NULL) return (0);
    pBlk = ((MemBlkPtr)block) - 1;
    assert (pBlk->guard1 == GUARD_LOW);

    return (pBlk->size);
}


#ifdef MEM_DEBUG
struct testblock {
    struct testblock	__far * prev;
    HLOCAL			h;
};



void
MemATest (void)
{
    void    __near		*n;
    struct testblock __far	*p;
    struct testblock __far	*pnew;
    HLOCAL			hl;
    int				bcount;
    UINT			segment, start, end;
    void    __far		*f;
    HGLOBAL			hg;
    DWORD			dw;
    LOCALINFO			li;
    UINT			DataSeg;
    
#if 0  
    hg = GlobalAlloc (GMEM_FIXED, HEAP_SIZE);	/* Allocate global block */
    if (hg == NULL)
	return;
    f = GlobalLock (hg);			/* Lock and get pointer. */
    if (f == NULL) 
	goto Fail1;
    segment = (UINT) GET_SEGMENT (f);		/* Get segment and offsets. */
    start = (UINT) GET_OFFSET (f);
    end = start + HEAP_SIZE - 1;
    start += 16;
    if (!LocalInit (segment, start, end))		/* Init it as the local heap*/
	goto Fail2;
#endif
#if 0
    __asm MOV DataSeg,DS;			/* Get current DS. */
    __asm MOV DS,segment;			/* Set DS to new heap. */
    hl = LocalAlloc (0, SMALL_MEM_BOUNDRY);	/* Now allocate something. */
    __asm MOV DS,DataSeg;			/* Restore DS. */
    if (hl == NULL) 
	    return;
    n = LocalLock (hl);				/* Find where it is. */
    f = (void __far *)n;
    segment = GET_SEGMENT(f);			/* What Segment. */
    dw = GlobalHandle (segment);
    hg = (HGLOBAL) (dw & 0xffff);
    if (hg == NULL)
	    return;
    
    li.dwSize = sizeof (li);			/* What size. */
    if (!LocalInfo (&li, hg))
	    return;
    
    dw = GlobalSize (hg);
    f = GlobalLock (hg);
    GlobalUnlock (hg);
    
    LocalUnlock (hl);
    LocalFree (hl);

    
#endif
    
    
	
	
    p = NULL;
    pnew = NULL;
    bcount = 0;
	
    do {
	hl = LocalAlloc (0, SMALL_MEM_BOUNDRY);
	if (hl != NULL) {
	    ++bcount;
	    n = LocalLock (hl);
	    pnew = (struct testblock __far*) n;
	    pnew->h = hl;
	    pnew->prev = p;
	    p = pnew;
        }
    } while (hl != NULL);
    
    
    if (MemDebugFile != NULL)
	fprintf (MemDebugFile, "Allocated %d blocks of size %d\n",
	    bcount, SMALL_MEM_BOUNDRY);
			
    while (p != NULL) {
	pnew = p->prev;
	hl = p->h;
	LocalUnlock (hl);
	LocalFree (hl);
	p = pnew;
    }
    fflush (MemDebugFile);
#if 0 
Fail2:	GlobalUnlock (hg);
Fail1:	GlobalFree (hg);
#endif
	return;
}
#endif /* MEM_DEBUG */

#else /* SEGHEAP */

/* Copyright (C) Stephen Chung, 1991-1992.  All rights reserved. */


#define MAGIC           0x42022667
#define MAGIC2		0x56743296

typedef struct MemoryStruct {
    long int		magic;
    void far		*page;
    WORD		id;
    MemSize		size;
    BOOL		allocated;
    struct MemoryStruct far *next, far *prev;
    long int		magic2;
} MEMHEADER;

typedef struct PageHeaderStruct {
    long int		magic;
    HANDLE		handle;
    WORD		id;
    MemSize		size;
    MemSize		used;
    MemSize		overhead;
    MEMHEADER far	*data, far *empty;
    struct PageHeaderStruct far *next, far *prev;
    long int		magic2;
} MEMPAGEHEADER;

typedef struct {
    MEMPAGEHEADER far *pages;
    int nr_pages;
} MAINMEMHEADER;

#define PAGESIZE        (6 * 1024)
#define USEABLESIZE     (PAGESIZE - sizeof(MEMPAGEHEADER) - sizeof(MEMHEADER))


LOCAL MAINMEMHEADER	MemHeader = { NULL, 0 };
LOCAL WORD		MemID = 0;		/* Keep track of ID. */
LOCAL WORD		PageID = 0;
#ifdef MEM_DEBUG
LOCAL unsigned long	MemInUse = 0;		/* Total bytes in use. */
LOCAL unsigned long	MemInUseMax = 0;	/* max in use at one time. */
LOCAL unsigned long     PageMemInUse = 0;
LOCAL unsigned long	PageMemInUseMax = 0;
#endif /* MEM_DEBUG */



static MEMPAGEHEADER far *
AddPage(MemSize n)
{
    void far *cp;
    MEMHEADER far *mp;
    MEMPAGEHEADER far *p;
    HANDLE handle = NULL;

    handle = GlobalAlloc(GHND, n);
    if (handle == NULL) {
#ifdef MEM_DEBUG
	if (MemDoDebug >= 1) 
	    fprintf (MemDebugFile, "Out of memory: allocating %d bytes", n);
#endif
        return (NULL);
    }

    if (MemHeader.pages == NULL || MemHeader.nr_pages <= 0) {
        p = MemHeader.pages = (MEMPAGEHEADER far *) GlobalLock(handle);
        p->prev = NULL;
    } else {
        for (p = MemHeader.pages; p->next != NULL; p = p->next);
        p->next = (MEMPAGEHEADER far *) GlobalLock(handle);
        p->next->prev = p;
        p = p->next;
    }

    p->magic = MAGIC;
    p->handle = handle;
    p->next = NULL;
    p->id = PageID++;
    p->size = n;
    p->used = 0;
    p->overhead = sizeof(MEMPAGEHEADER) + sizeof(MEMHEADER);
    p->magic2 = MAGIC2;

    cp = ((char far *) p) + sizeof(MEMPAGEHEADER);
    mp = (MEMHEADER far *) cp;

    p->data = p->empty = mp;

    mp->magic = 0L;
    mp->magic2 = 0L;
    mp->allocated = FALSE;
    mp->page = p;
    mp->size = p->size - p->overhead;
    mp->next = mp->prev = NULL;

    MemHeader.nr_pages++;

#ifdef MEM_DEBUG
    if (MemDoDebug) {
	fprintf (MemDebugFile, "PageAlloc: addr(%lx) id(%u) size(%ld) global(%d)\n",
		p, p->id, (long)n, 1);
	fflush (MemDebugFile);
    }
    PageMemInUse += n;
    if (PageMemInUse > PageMemInUseMax)
	PageMemInUseMax = PageMemInUse;
#endif /* MEM_DEBUG */
    

    return (p);
}



static void 
DeletePage (MEMPAGEHEADER far *p)
{
#ifdef MEM_DEBUG    
    /* Deubgging info... */
    if (MemDoDebug) {
	if (PageMemInUse == PageMemInUseMax) 
	   fprintf (MemDebugFile, "Page usage is up to %lu\n", PageMemInUseMax);
    }
    PageMemInUse -= p->size;
    if (MemDoDebug) {
	fprintf (MemDebugFile, "PageFree: addr(%lx) id(%u)\n", 
		p, p->id);
	fflush (MemDebugFile);
    }
#endif /* MEM_DEBUG */


    if (p->next == NULL && p->prev == NULL) {
        MemHeader.pages = NULL;
        MemHeader.nr_pages = 0;
	GlobalUnlock (p->handle);
        GlobalFree (p->handle);
    } else {
        if (p == MemHeader.pages) MemHeader.pages = p->next;
        MemHeader.nr_pages--;

        if (p->prev != NULL) p->prev->next = p->next;
        if (p->next != NULL) p->next->prev = p->prev;

	GlobalUnlock (p->handle);
        GlobalFree (p->handle);
    }
}


/* 
 * Segmented heap memory allocation. 
 */

MemPtr
_MemAlloc (MemSize n, char __far * file, int line)
{
    MEMPAGEHEADER far *p;
    MEMHEADER far *mp;
    char far *cp;

    if (n >= 65535) {
	assert (n < 65535);
        return (NULL);
    }

    /* Larger than page size? */

    if (n > USEABLESIZE) {
	p = AddPage(n + sizeof(MEMPAGEHEADER) + sizeof(MEMHEADER));
	if (p == NULL)
	    return (NULL);

        mp = p->data;
        mp->magic = MAGIC;
	mp->magic2 = MAGIC2;
	mp->id = MemID++;
        mp->allocated = TRUE;
	

        p->used = n;
        p->empty = NULL;

        cp = ((char far *) mp) + sizeof(MEMHEADER);
#ifdef MEM_DEBUG
	if (MemDoDebug) {
	    fprintf (MemDebugFile, "MemAlloc: addr(%lx) id(%u) size(%ld) global(%d)\n",
		    cp, mp->id, (long)n, 0);
	    fflush (MemDebugFile);
	}
	MemInUse += n;
	if (MemInUse > MemInUseMax)
	    MemInUseMax = MemInUse;
#endif /* MEM_DEBUG */
        return ((MemPtr) cp);
    }


    /* Search for the hole */

    for (p = MemHeader.pages; p != NULL; p = p->next) {
        /* Scan the chains */
        if (p->size - p->used - p->overhead <= 0) continue;
		if (p->empty == NULL) continue;

        for (mp = p->empty; mp != NULL; mp = mp->next) {
            if (!mp->allocated && mp->size >= n) break;
        }

        if (mp != NULL) break;
    }

    /* New page needed? */

    if (p == NULL) {
        p = AddPage(PAGESIZE);
	if (p == NULL)
	    return (NULL);
	mp = p->data;
    }

    /* Do we need to break it up? */

    if (mp->size - n > sizeof(MEMHEADER)) {
        MEMHEADER far *mp2;

        cp = ((char far *) mp) + n + sizeof(MEMHEADER);
        mp2 = (MEMHEADER far *) cp;

        mp2->magic = 0L;
	mp2->magic2 = 0L;
        mp2->allocated = FALSE;
        mp2->page = p;
        mp2->size = mp->size - n - sizeof(MEMHEADER);

        mp2->next = mp->next;
        mp2->prev = mp;
        if (mp->next != NULL) mp->next->prev = mp2;
        mp->next = mp2;


        p->overhead += sizeof(MEMHEADER);

        mp->size = n;
    }

    mp->magic = MAGIC;
    mp->magic2 = MAGIC2;
    mp->allocated = TRUE;
    mp->id = MemID++;

    p->used += n;
    cp = ((char far *) mp) + sizeof(MEMHEADER);


    /* Debugging info... */
#ifdef MEM_DEBUG
    if (MemDoDebug) {
	fprintf (MemDebugFile, "MemAlloc: addr(%lx) id(%u) size(%ld) global(%d)\n",
		cp, mp->id, (long)n, 0);
	fflush (MemDebugFile);
    }
    MemInUse += n;
    if (MemInUse > MemInUseMax)
	MemInUseMax = MemInUse;
#endif /* MEM_DEBUG */
    
    
    
    
    /* Search for the next empty hole */

    for (; mp != NULL; mp = mp->next) {
        if (!mp->allocated && mp->size > 0) break;
    }

    p->empty = mp;

    return ((MemPtr) cp);
}



/*
 * Segmented heap memory free.
 */
int
_MemFree (MemPtr vp, char __far *file, int line)
{
    MEMPAGEHEADER far *p;
    MEMHEADER far *mp, far *mp2;
    char far *cp;
    
    if (vp == NULL)
        return (0);
    

    cp = ((char far *) vp) - sizeof(MEMHEADER);
    mp = (MEMHEADER far *) cp;

    if (mp->magic != MAGIC || mp->magic2 != MAGIC2|| !mp->allocated) {
	assert (mp->magic == MAGIC);
	assert (mp->magic2 == MAGIC2);
	assert (mp->allocated);
	return (-1);
    }
    
#ifdef MEM_DEBUG    
    /* Deubgging info... */
    if (MemDoDebug) {
	if (MemInUse == MemInUseMax) 
	   fprintf (MemDebugFile, "Memory usage is up to %lu\n", MemInUseMax);
    }
    MemInUse -= mp->size;
    if (MemDoDebug) {
	fprintf (MemDebugFile, "MemFree: addr(%lx) id(%u)\n", vp, mp->id);
	fflush (MemDebugFile);
    }
#endif /* MEM_DEBUG */
    

    p = (MEMPAGEHEADER far *) mp->page;
    p->used -= mp->size;

    mp->magic = 0L;
    mp->magic2 = 0L;
    mp->allocated = FALSE;

    /* Merge? */

    mp2 = mp->prev;

    if (mp2 != NULL && !mp2->allocated) {
        mp2->next = mp->next;
        if (mp->next != NULL) mp->next->prev = mp2;
        mp2->size += mp->size + sizeof(MEMHEADER);

        p->overhead -= sizeof(MEMHEADER);

        mp = mp2;
    }

    mp2 = mp->next;

    if (mp2 != NULL && !mp2->allocated) {
        mp->next = mp2->next;
        if (mp2->next != NULL) 
	    mp2->next->prev = mp;

        mp->size += mp2->size + sizeof(MEMHEADER);

	p->overhead -= sizeof(MEMHEADER);
    }

    if (mp->prev == NULL && mp->next == NULL) {
        DeletePage(p);
    } else {
        if (p->empty == NULL || mp < p->empty) p->empty = mp;
    }
    return (0);
}



MemPtr
_MemRealloc (MemPtr p, MemSize n, char __far *file, int line)
{
    MEMHEADER far *mp;
    char far *cp;

    if (p != NULL) {
	/* Block already large enough? */
	cp = ((char far *) p) - sizeof(MEMHEADER);
	mp = (MEMHEADER far *) cp;

	if (mp->magic != MAGIC || mp->magic2 != MAGIC2) {
	    assert (mp->magic == MAGIC);
	    assert (mp->magic2 == MAGIC2);
	    return (p);
	}

	if (mp->size >= n) return (p);      /* No need to do anything */
    }
    /* Else swap to another block */

    cp = MemAlloc (n);
    if (cp == NULL)
	return (NULL);

    if (p != NULL) {
	_fmemcpy(cp, p, (size_t)((mp->size >= n) ? n : mp->size));
	MemFree (p);
    }

    return ((void far *) cp);
}



MemSize
MemBlkSize (MemPtr p)
{
    MEMHEADER far *mp;
    char far *cp;

    if (p == NULL) return (0);
    cp = ((char far *) p) - sizeof(MEMHEADER);

    mp = (MEMHEADER far *) cp;

    if (mp->magic != MAGIC || mp->magic2 != MAGIC2) {
	assert (mp->magic == MAGIC);
	assert (mp->magic2 == MAGIC2);
	return (0);
    }

    return (mp->size);
}


#if 0
MemPtr
MemDup (void far *p)
{
    unsigned int len;
    void far *p1;

    len = MgetBlkSize (p);
    p1 = MemAlloc (len);
    if (p1 != NULL)
	_fmemcpy(p1, p, len);

    return (p1);
}


void 
MemoryStatistics (long int *allocated, long int *used, long int *overhead)
{
    MEMPAGEHEADER far *p;

    *allocated = *used = *overhead = 0L;

    for (p = MemHeader.pages; p != NULL; p = p->next) {
        *allocated += p->size;
        *used += p->used;
        *overhead += p->overhead;
    }
}
#endif


void 
MemFreeAll (void)
{
    MEMPAGEHEADER far *p, far *p1;

    for (p = MemHeader.pages; p != NULL; ) {
        p1 = p->next;
	GlobalUnlock (p->handle);
        GlobalFree (p->handle);
        p = p1;
    }

    MemHeader.pages = NULL;
    MemHeader.nr_pages = 0;
}


#ifdef MEM_DEBUG

/* For debugging purposes...  not very pretty */

void PrintMemoryChains(void)
{
    MEMPAGEHEADER far *p;
	MEMHEADER far *mp;
	char far *cp;
    char buffer[100];

	/* Block already large enough? */


    for (p = MemHeader.pages; p != NULL; p = p->next) {
        for (mp = p->data; mp != NULL; mp = mp->next) {
            fprintf (MemDebugFile, "%Fp | %u | %s", mp, mp->size, mp->allocated ? "Alloc" : "Free");
        }
    }
}

#endif /* DEBUG */

#endif /* ifdef SEGHEAP */



void 
AssertFail	(char *str, char *file, int line, int msgbox)
{
    /* static to avoid risk of stack overflow */
    static int	inAssert	= 0;
    static int	doBreak		= 1;
    char	*	filename;
    int			ret;



    /* for now, strip off path from filename */
    filename = _fstrrchr(file, '\\');
    if (filename)
	    filename++;
    else
	    filename = file;

    wsprintf(TempBuf, "Assert(%s) failed!  %s:%d\n", 
			    str, filename, line);

    inAssert++;


retry:

    /*	Unfortunately, there are certain times when attempting to
     *	display a message box will cause the system to crash (e.g.,
     *	when handling certain messages in a WndProc).
     *
     *	Message box was previously TaskModal, but this didn't work
     *	well because the client AIM could keep sending requests.
     *	SystemModal shuts the whole system down but at least it
     *	guarantees some degree of consistency, and makes debugging
     *	a bit easier.
     */
    ret = MessageBox (NULL, TempBuf, NULL, 
				    MB_ABORTRETRYIGNORE | MB_SYSTEMMODAL );


    if (ret == IDABORT) {
	    ret = MessageBox(NULL, 
			    "AbortIng may not properly free resources.  Are you sure?",
			    "Assertion Abort",
			    MB_YESNO | MB_TASKMODAL | MB_ICONSTOP);

	    if (ret == IDYES) {
		    /*
		     *	Cause a GPF in case DrWatson is running
		     */
		    char *p = NULL;
		    *p = 1;
	    }
	    else 
		    goto retry;
    }

    /* retry is not (and will never be) hooked up, but it's
     * easier to use the standard dialog box than create
     * our own.  if retry is selected, just report the
     * error again.
     */
    if (ret == IDRETRY)
	    goto retry;
    inAssert--;
}
