// "cwin.cpp"                            Copyright (C) A C Norman, 1994

//
// Main window manager code to provide general support for simple
// applications.   The interface that this provides is documented
// in "cwin.h".
//

// This uses MFC, as documented online help files and in
//       "Microsoft Foundation Class Primer" by Jim Conger
//       Waite Group Press, 1993.
// It is compiled using MS VC++ (32-bit edition), and runs under
// Win32 or Win32s.


// The following line is used by the Codemist "filesign" utility to keep
// a checksum and date-stamp in the file.

/* Signature: 5a6978da 14-Nov-2000 */


#include <string.h>
#include <ctype.h>
#include <stdarg.h>
#include <time.h>
#include <math.h>
#include <setjmp.h>

#define WINVER  0x030a                  // I rely on at least Windows 3.1

#include <afxdlgs.h>
#include <afxwin.h>                     // Main MFC header file

#include "cwin.h"                       // defines external interface

#ifdef BUILDING_DLL
#define DLLexport __declspec(dllexport)
#else
#define DLLexport
#endif

// DisplayMsg is used a bit like fprintf(stderr, ...) but ONLY for debugging.
// It pops up a modal dialog box each time it is called.  This is easy to
// code, but a bit clumsy in the way it distrubs the screen.

static void DisplayMsg(char *msg, ...)
{
    char buffer[256];
    va_list a;
    va_start(a, msg);
    _vsnprintf(buffer, 250, msg, a);
    va_end(a);
    AfxMessageBox(buffer);
}

class CTheApp : public CWinApp          // application class
{
public:
   BOOL InitInstance();                 // override default InitInstance()
   int Run();                           // override top level loop (!)
};

extern MSG *msgPtr;                     // needed by cwin_poll_window_manager()

// The amount of text that can be stored in a main window is fixed here -
// I allow for up to 16K chars and 512 lines (an average of 32 chars per
// line, and I expect these limits to be roughly comparable).

const int LINE_MASK       = 0x1ff;      // keep up to 512 lines
const int TEXT_SIZE       = 0x4000;     // keep up to 16K chars
const int MAX_LINE_LENGTH = 504;        // < 512, multiple of 8.

// I pack a line/column value into one word.  By restricting lines to be
// at worst 500 characters long I can afford to use a 9-bit field for the
// column. So provided I have at worst 4000000 lines (2^22) the packed
// value I have will remain a positive 32-bit value.  I do not check for
// line-count overflow here.

#define MakePos(line, col) (((line) << 9) | (col))
#define PosLine(x) ((x) >> 9)
#define PosChar(x) ((x) & 0x1ff)

// The main issue is that the following two values must not collide
// with SB_xxx (eg SB_LINEDOWN)

#define PRIVATE_SET_LEFT_MARGIN   0x10000
#define PRIVATE_SET_VISIBLE_LINE  0x10001

class CMainWindow : public CFrameWnd    // main window class
{
public:
    CMainWindow();                      // declare constructor
        CRect clientArea;
    void InitialCleanup();

    void InsertChar(int ch);            // insert one character & display
    void InsertLine(char *s, int n);    // insert whole line of text
    void InsertSeveralLines(char **lines, int *lengths, int nlines);
    void RemoveChar();                  // remove a single character
#ifdef FOR_ML
        void ExpandGreek();
#endif
    void AwaitInput();                  // polls until data is available
    void AbandonClipboard();            // closes partially done PASTE op.

private:
    CMenu Menu;
    void OnPaint();
        void PaintText(CPaintDC *dc, BOOL highlighting,
                       int x, int y, char *s, int n);
        DWORD windowColor, textColor, highlightColor, highlightTextColor;

    void OnSize(UINT nType, int cx, int cy);
    void AfterSizeChange();
        CRect clipArea;
        int nRows, nCols;               // rows & cols of characters

    void OnVScroll(UINT nSBCode, UINT nPos, CScrollBar *PCntl);
    void OnHScroll(UINT nSBCode, UINT nPos, CScrollBar *PCntl);
        int VScrollPos;                 // always 0 <= thumb <= 100
        int HScrollPos;                 // always 0 <= thumb <= 100
        int leftMargin;                 // measured in character widths

    void OnSetFocus(CWnd *pOldWnd);
    void OnKillFocus(CWnd *pNewWnd);

    void OnChar(UINT ch, UINT nRepCnt, UINT nFlags);
    void OnKeyDown(UINT ch, UINT nRepCnt, UINT nFlags);

    void OnLButtonDblClk(UINT nFlags, CPoint point);
    void OnLButtonDown(UINT nFlags, CPoint point);
    void OnLButtonUp(UINT nFlags, CPoint point);
    void OnMButtonDblClk(UINT nFlags, CPoint point);
    void OnMButtonDown(UINT nFlags, CPoint point);
    void OnMButtonUp(UINT nFlags, CPoint point);
    void OnRButtonDblClk(UINT nFlags, CPoint point);
    void OnRButtonDown(UINT nFlags, CPoint point);
    void OnRButtonUp(UINT nFlags, CPoint point);
    void OnMouseMove(UINT nFlags, CPoint point);
        int mousePos;
        BOOL trackingSelection;
        void CancelSelection();         // loses any selection
        void FindMouseChar(int x, int y);

    void OnNcLButtonDown(UINT nFlags, CPoint point);
    void OnNcMButtonDown(UINT nFlags, CPoint point);
    void OnNcRButtonDown(UINT nFlags, CPoint point);

    void OnRead();                      // handle messages from all menu items
    void OnSaveas();
    void OnSaveSel();
    void OnTofile();
    void OnPrint();
    void OnPrintSetup();
    void OnExit();

#ifdef CUT_SUPPORTED
    void OnCut();
#endif
    void OnCopy();
    void OnPaste();
    void OnMove();
        BOOL GrabLineFromClipboard();
        HGLOBAL clipboardInputHandle;
        BOOL inputspec;
        char *clipboardInput;

    void OnSelectAll();
    void OnClear();
    void OnRedraw();
    void OnUndo();
    void OnTop();
    void OnBottom();
#ifdef FOR_ML
    void OnGreek();
#endif

    void OnFont();
        BOOL SetNewFont(CFont *newFont);
        CFont *mainFont;
        int charWidth;                  // I use a fixed-pitch font, so 
        int charHeight;                 // only one character size applies
        int mainOffset;                 // vertical offset when drawing
        CFont *symbolFont;              // may be 0 if no Greek available
        int symbolWidth[256];           // character width information
        int symbolOffset;               // vertical offset needed for symbols


    void OnGShow();
    void OnGHide();
    void OnGClear();

    void OnInterrupt();
    void OnBacktrace();

    void OnHelpContents();
    void OnHelp();
    void OnHelpUsing();
    void OnAbout();

    void RemoveFirstLine();             // used when buffer gets full

    char txtBuffer[TEXT_SIZE];          // buffer for text
    int firstChar, currentChar;         // character buffer ptrs

    int txtLine[LINE_MASK+1];           // where each line starts
    int firstLine, firstVisibleLine, currentLine;
    int txtLineLen[LINE_MASK+1];        // length of each line
    int currentLineLength;

    void StartSelection();
    void ExtendSelection();
        int selFirstPos;                // where user clicked to begin sel
        int selStartPos;                // start posn of selection
        int selEndPos;                  // end posn of selection

    DECLARE_MESSAGE_MAP()
};

#define IDM_READ           10    /* Codes in the main menu */
#define IDM_SAVEAS         11
#define IDM_SAVESEL        12
#define IDM_TOFILE         13
#define IDM_PRINT          14
#define IDM_PRINTSETUP     15
#define IDM_EXIT           16

#define IDM_CUT            17    /* Codes in the EDIT menu */
#define IDM_COPY           18
#define IDM_PASTE          19
#define IDM_MOVE           20
#define IDM_SELECTALL      21
#define IDM_CLEAR          22
#define IDM_UNDO           23
#define IDM_REDRAW         24
#define IDM_TOP            25
#define IDM_BOTTOM         26
#ifdef FOR_ML
#define IDM_GREEK          27
#endif
#define IDM_FONT           28

#define IDM_INTERRUPT      29    /* Codes in the BREAK menu */
#define IDM_BACKTRACE      30

#define IDM_GSHOW          31    /* Codes in the GRAPHICS menu */
#define IDM_GHIDE          32
#define IDM_GCLEAR         33

#define IDM_ABOUT          34


BEGIN_MESSAGE_MAP(CMainWindow, CFrameWnd)
    ON_WM_SETFOCUS()
    ON_WM_KILLFOCUS()
    ON_WM_SIZE()
    ON_WM_VSCROLL()
    ON_WM_HSCROLL()
    ON_WM_PAINT()

    ON_WM_CHAR()
    ON_WM_KEYDOWN()

    ON_WM_LBUTTONDBLCLK()
    ON_WM_LBUTTONDOWN()
    ON_WM_LBUTTONUP()
    ON_WM_MBUTTONDBLCLK()
    ON_WM_MBUTTONDOWN()
    ON_WM_MBUTTONUP()
    ON_WM_RBUTTONDBLCLK()
    ON_WM_RBUTTONDOWN()
    ON_WM_RBUTTONUP()
    ON_WM_MOUSEMOVE()

    ON_WM_NCLBUTTONDOWN()
    ON_WM_NCMBUTTONDOWN()
    ON_WM_NCRBUTTONDOWN()

    ON_COMMAND(IDM_READ, OnRead)
    ON_COMMAND(IDM_SAVEAS, OnSaveas)
    ON_COMMAND(IDM_SAVESEL, OnSaveSel)
    ON_COMMAND(IDM_TOFILE, OnTofile)
    ON_COMMAND(IDM_PRINT, OnPrint)
    ON_COMMAND(IDM_PRINTSETUP, OnPrintSetup)
    ON_COMMAND(IDM_EXIT, OnExit)
#ifdef CUT_SUPPORTED
    ON_COMMAND(IDM_CUT, OnCut)
#endif
    ON_COMMAND(IDM_COPY, OnCopy)
    ON_COMMAND(IDM_PASTE, OnPaste)
    ON_COMMAND(IDM_MOVE, OnMove)
    ON_COMMAND(IDM_SELECTALL, OnSelectAll)
    ON_COMMAND(IDM_CLEAR, OnClear)
    ON_COMMAND(IDM_UNDO, OnUndo)
    ON_COMMAND(IDM_REDRAW, OnRedraw)
    ON_COMMAND(IDM_TOP, OnTop)
    ON_COMMAND(IDM_BOTTOM, OnBottom)
#ifdef FOR_ML
    ON_COMMAND(IDM_GREEK, OnGreek)
#endif
    ON_COMMAND(IDM_FONT, OnFont)
    ON_COMMAND(IDM_INTERRUPT, OnInterrupt)
    ON_COMMAND(IDM_BACKTRACE, OnBacktrace)
    ON_COMMAND(IDM_GSHOW, OnGShow)
    ON_COMMAND(IDM_GHIDE, OnGHide)
    ON_COMMAND(IDM_GCLEAR, OnGClear)
    ON_COMMAND(ID_HELP_INDEX, OnHelpContents)
    ON_COMMAND(ID_HELP, OnHelp)
    ON_COMMAND(ID_HELP_USING, OnHelpUsing)
    ON_COMMAND(IDM_ABOUT, OnAbout)
END_MESSAGE_MAP()

#define PICTURE_SIZE 2000

typedef struct picElement
{
    unsigned colour;
    double size, c00, c01, c02, c10, c11, c12;
} picElement;

class CGraphicsWindow : public CWnd     // graphics window class
{
public:
    CGraphicsWindow();                  // declare constructor

    void Clear();
    void Point(unsigned colour, transform c);
    void Line(unsigned colour, double size, transform c);
    void Circle(unsigned colour, double size, transform c);
    void Square(unsigned colour, double size, transform c);
    BOOL isOnTop;

private:
    CRect clientArea;
    double halfx, halfy, scalex, scaley;

    picElement objects[PICTURE_SIZE];
    int nObjects;

    void OnPaint();
    void OnSetFocus(CWnd *pOldWnd);
    void OnChar(UINT ch, UINT nRepCnt, UINT nFlags);
    void OnSysChar(UINT ch, UINT nRepCnt, UINT nFlags);
    void OnKeyDown(UINT ch, UINT nRepCnt, UINT nFlags);
    void OnSysKeyDown(UINT ch, UINT nRepCnt, UINT nFlags);

    DECLARE_MESSAGE_MAP()
};

BEGIN_MESSAGE_MAP(CGraphicsWindow, CWnd)
    ON_WM_PAINT()
    ON_WM_SETFOCUS()
    ON_WM_CHAR()
    ON_WM_SYSCHAR()
    ON_WM_KEYDOWN()
    ON_WM_SYSKEYDOWN()
END_MESSAGE_MAP()

void CGraphicsWindow::Point(unsigned colour, transform c)
{
    if (nObjects >= PICTURE_SIZE) return;
    objects[nObjects].colour = colour;
    objects[nObjects].size   = 0.0;
    objects[nObjects].c00    = c[0][0];
    objects[nObjects].c01    = c[0][1];
    objects[nObjects].c02    = c[0][2];
    objects[nObjects].c10    = c[1][0];
    objects[nObjects].c11    = c[1][1];
    objects[nObjects].c12    = c[1][2];
    nObjects++;
    Invalidate(TRUE);
}

void CGraphicsWindow::Line(unsigned colour, double size, transform c)
{
    if (nObjects >= PICTURE_SIZE) return;
    objects[nObjects].colour = colour | 0x01000000;
    objects[nObjects].size   = size;
    objects[nObjects].c00    = c[0][0];
    objects[nObjects].c01    = c[0][1];
    objects[nObjects].c02    = c[0][2];
    objects[nObjects].c10    = c[1][0];
    objects[nObjects].c11    = c[1][1];
    objects[nObjects].c12    = c[1][2];
    nObjects++;
    Invalidate(TRUE);
}

void CGraphicsWindow::Circle(unsigned colour, double size, transform c)
{
    if (nObjects >= PICTURE_SIZE) return;
    objects[nObjects].colour = colour | 0x02000000;
    objects[nObjects].size   = size;
    objects[nObjects].c00    = c[0][0];
    objects[nObjects].c01    = c[0][1];
    objects[nObjects].c02    = c[0][2];
    objects[nObjects].c10    = c[1][0];
    objects[nObjects].c11    = c[1][1];
    objects[nObjects].c12    = c[1][2];
    nObjects++;
    Invalidate(TRUE);
}

void CGraphicsWindow::Square(unsigned colour, double size, transform c)
{
    if (nObjects >= PICTURE_SIZE) return;
    objects[nObjects].colour = colour | 0x03000000;
    objects[nObjects].size   = size;
    objects[nObjects].c00    = c[0][0];
    objects[nObjects].c01    = c[0][1];
    objects[nObjects].c02    = c[0][2];
    objects[nObjects].c10    = c[1][0];
    objects[nObjects].c11    = c[1][1];
    objects[nObjects].c12    = c[1][2];
    nObjects++;
    Invalidate(TRUE);
}

void CGraphicsWindow::Clear()
{
    nObjects = 0;
    Invalidate(TRUE);
}

#define _pi 3.141592653589793238

void CGraphicsWindow::OnPaint()
{
    CPaintDC dc(this);
    for (int i=0; i<nObjects; i++)
    {   CPen pen(PS_SOLID, 1, objects[i].colour & 0x00ffffff);
        double r, x, y, x0, y0;
        int j;
        dc.SelectObject(&pen);
        switch (objects[i].colour & 0xff000000)
        {
    default:
    case 0x00000000:  /* point */
            x = objects[i].c02;
            y = objects[i].c12;
            dc.SetPixel((int)(halfx + scalex*x0),
                        (int)(halfy + scaley*y0),
                        objects[i].colour & 0x00ffffff);
            break;
    case 0x01000000:  /* line */
            r = 0.5*objects[i].size;
            x0 = -r*objects[i].c00 + objects[i].c02;
            y0 = -r*objects[i].c10 + objects[i].c12;
            dc.MoveTo((int)(halfx + scalex*x0), (int)(halfy + scaley*y0));
            x = r*objects[i].c00 + objects[i].c02;
            y = r*objects[i].c10 + objects[i].c12;
            dc.LineTo((int)(halfx + scalex*x), (int)(halfy + scaley*y));
            break;
    case 0x02000000:    /* circle */
            r = objects[i].size;
            x = -r*objects[i].c00 + objects[i].c02;
            y = -r*objects[i].c10 + objects[i].c12;
            dc.MoveTo((int)(halfx + scalex*x), (int)(halfy + scaley*y));
            for (j=0; j<=40; j++)
            {   double th = _pi*(double)j/20.0;
                double xx = -r*cos(th), yy = r*sin(th);
                x = xx*objects[i].c00 + yy*objects[i].c01 + objects[i].c02;
                y = xx*objects[i].c10 + yy*objects[i].c11 + objects[i].c12;
                dc.LineTo((int)(halfx + scalex*x), (int)(halfy + scaley*y));
            }
            break;
    case 0x03000000:  /* square */
            r = 0.5*objects[i].size;
            x0 = -r*objects[i].c00 - r*objects[i].c01 + objects[i].c02;
            y0 = -r*objects[i].c10 - r*objects[i].c11 + objects[i].c12;
            dc.MoveTo((int)(halfx + scalex*x0), (int)(halfy + scaley*y0));
            x = r*objects[i].c00 - r*objects[i].c01 + objects[i].c02;
            y = r*objects[i].c10 - r*objects[i].c11 + objects[i].c12;
            dc.LineTo((int)(halfx + scalex*x), (int)(halfy + scaley*y));
            x = r*objects[i].c00 + r*objects[i].c01 + objects[i].c02;
            y = r*objects[i].c10 + r*objects[i].c11 + objects[i].c12;
            dc.LineTo((int)(halfx + scalex*x), (int)(halfy + scaley*y));
            x = -r*objects[i].c00 + r*objects[i].c01 + objects[i].c02;
            y = -r*objects[i].c10 + r*objects[i].c11 + objects[i].c12;
            dc.LineTo((int)(halfx + scalex*x), (int)(halfy + scaley*y));
            dc.LineTo((int)(halfx + scalex*x0), (int)(halfy + scaley*y0));
            break;
        }
    }
}


// There is a sort-of wonderful delicacy here - the variable 'theApp'
// defined here will get initialised during program start-up, and pretty
// well all of the interesting activity in this whole program happens
// as part of this initialisation!  Wow.

CTheApp theApp;
CMainWindow *theWindow;
CGraphicsWindow *thePicture = NULL;
FILE *log_file = NULL;

DLLexport void __cdecl cwin_point(unsigned colour, transform c)
{
    if (thePicture == NULL) return;
    thePicture->Point(colour, c);
}

DLLexport void __cdecl cwin_line(unsigned colour, double size, transform c)
{
    if (thePicture == NULL) return;
    thePicture->Line(colour, size, c);
}

DLLexport void __cdecl cwin_circle(unsigned colour, double size, transform c)
{
    if (thePicture == NULL) return;
    thePicture->Circle(colour, size, c);
}

DLLexport void __cdecl cwin_square(unsigned colour, double size, transform c)
{
    if (thePicture == NULL) return;
    thePicture->Square(colour, size, c);
}

DLLexport void __cdecl cwin_clear()
{
    if (thePicture == NULL) return;
    thePicture->Clear();
}

DLLexport void __cdecl cwin_show()
{
    if (thePicture == NULL) return;
    thePicture->SetWindowPos(&thePicture->wndTopMost, 0, 0, 0, 0,
        SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW | SWP_NOACTIVATE);
    thePicture->isOnTop = TRUE;
    theWindow->SetFocus();
}

// Well, see.  When I create a graphics window I force it to be topmost
// so that it is unconditionally visible. I do this so that it will not
// vanish at the time that my main window is given the input focus. However
// if the user actually clicks the mouse anywhere in the main window I
// will cancel this special status and the graphics display will usually
// drop down and get at least partially obscured.

static void cwin_unTop()
{
    if (thePicture==NULL || !thePicture->isOnTop) return;
    thePicture->SetWindowPos(&thePicture->wndNoTopMost, 0, 0, 0, 0,
        SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW | SWP_NOACTIVATE);
    thePicture->isOnTop = FALSE;
    theWindow->BringWindowToTop();
}

static CString mainWindowClass;

CGraphicsWindow::CGraphicsWindow()
{
    nObjects = 0;
    int height = GetSystemMetrics(SM_CYSCREEN),
        width  = GetSystemMetrics(SM_CXSCREEN),
        capy   = GetSystemMetrics(SM_CYCAPTION);
// Here I make some attempt to choose a sensible size for the
// graphics window based on the total size of my display. I also cause it
// to pop up somewhere roughly in the top right hand corner of the
// screen.
    for (int w = 1024; w+capy > height || w > width;) w = (3*w)/4;
    w = (3*w)/4;
    if (CreateEx(WS_EX_NOPARENTNOTIFY, mainWindowClass, "CWIN Graphics", 
           WS_OVERLAPPED | WS_POPUP | WS_CAPTION,
           width-w-capy, capy, w, w+capy, NULL, 0, 0) == 0)
    {   DisplayMsg("Unable to create graphics window");
        ::PostQuitMessage(0);
        return;
    }
    GetClientRect(&clientArea);
    halfx = 0.5*(double)clientArea.right;
    halfy = 0.5*(double)clientArea.bottom;
    scalex = 0.5*halfx;
    scaley = 0.5*halfy;
    isOnTop = FALSE;
}

// Here I arrange that grabbing the focus to the graphics window moves it
// to the top. I really want to set the focus back again to the
// main window, but if I do that in the obvious way here I end up
// not being able to re-position the graphics window, which is an
// undesirable situation.

void CGraphicsWindow::OnSetFocus(CWnd *pOldWnd)
{
    SetWindowPos(&wndTopMost, 0, 0, 0, 0,
        SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW | SWP_NOACTIVATE);
    isOnTop = TRUE;
}

// I grab keyboard events and re-send them to the main window, altering
// the focus as I do...

void CGraphicsWindow::OnChar(UINT ch, UINT nRepCnt, UINT nFlags)
{
    theWindow->SetFocus();
    theWindow->SendMessage(WM_CHAR, ch, 1 + ((nFlags & 0x1ff)<<16));
}

void CGraphicsWindow::OnSysChar(UINT ch, UINT nRepCnt, UINT nFlags)
{
    theWindow->SetFocus();
    theWindow->SendMessage(WM_SYSCHAR, ch, 1 + ((nFlags & 0x1ff)<<16));
}

void CGraphicsWindow::OnKeyDown(UINT ch, UINT nRepCnt, UINT nFlags)
{
    theWindow->SetFocus();
    theWindow->SendMessage(WM_KEYDOWN, ch, 1 + ((nFlags & 0x1ff)<<16));
}

void CGraphicsWindow::OnSysKeyDown(UINT ch, UINT nRepCnt, UINT nFlags)
{
    theWindow->SetFocus();
    theWindow->SendMessage(WM_SYSKEYDOWN, ch, 1 + ((nFlags & 0x1ff)<<16));
}

#define OUTPUT_BUFFER_SIZE    2048
#define OUTPUT_BUFFER_LINES     32
// The output buffer here is circular. First points to the first active
// character, Last points one beyond the last, and CurLine to where
// data for the current incomplete line (length CurLength) starts.
static char outputBuffer[OUTPUT_BUFFER_SIZE];
static char *outputFirst, *outputLast, *outputCurLine;
static int outputCurLength;
// outputLines and Lengths give an index into completed lines in
// the buffer.
static char *outputLines[OUTPUT_BUFFER_LINES];
static int outputLengths[OUTPUT_BUFFER_LINES];
static int nOutputLines;
DLLexport int cwin_has_greek = 0;

DLLexport void __cdecl cwin_putchar(int c)
{
    if (c == '\n')
    {   outputLines[nOutputLines] = outputCurLine;
        outputLengths[nOutputLines++] = outputCurLength;
        outputCurLine = outputLast;
        outputCurLength = 0;
        if (nOutputLines == OUTPUT_BUFFER_LINES)
        {   theWindow->InsertSeveralLines(outputLines,
                                          outputLengths, nOutputLines);
            nOutputLines = 0;
            outputFirst = outputLast = outputCurLine = outputBuffer;
        }
        return;
    }
// When the buffer wraps round I will copy the current incomplete line
// round to the bottom of the buffer so that every line is stored as
// a single uninterrupted chunk.
    if (outputLast == &outputBuffer[OUTPUT_BUFFER_SIZE-1])
    {   outputLast = outputBuffer;
        int n = outputCurLength;
        outputCurLength = 0;
        char *p = outputCurLine;
        outputCurLine = outputBuffer;
        for (int i=0; i<n; i++) cwin_putchar(p[i]);
    }
    *outputLast++ = c;
    outputCurLength++;
    if (outputLast == outputFirst)
    {
        theWindow->InsertSeveralLines(outputLines,
                                      outputLengths, nOutputLines);
        nOutputLines = 0;
        outputFirst = outputCurLine;
    }
}

DLLexport void __cdecl cwin_puts(char *s)
{
    int c;
    while ((c = *s++) != 0) cwin_putchar(c);
}

DLLexport void __cdecl cwin_printf(char *fmt, ...)
{
    va_list a;
    va_start(a, fmt);
    cwin_vfprintf(fmt, a);
}

DLLexport void __cdecl cwin_vfprintf(char *fmt, va_list a)
{
    char tmpbuf[MAX_LINE_LENGTH];
    int len = _vsnprintf(tmpbuf, MAX_LINE_LENGTH-1, fmt, a);
    cwin_puts(tmpbuf);
}

static SYSTEMTIME titleUpdateTime, lastFlushTime;

DLLexport void __cdecl cwin_almost_ensure_screen()
{
    if (nOutputLines != 0)
    {
        theWindow->InsertSeveralLines(outputLines,
                                      outputLengths, nOutputLines);
        nOutputLines = 0;
        outputFirst = outputCurLine;
        cwin_poll_window_manager();
        GetSystemTime(&lastFlushTime);
        return;
    }
}

DLLexport void __cdecl cwin_ensure_screen()
{
// The first call to cwin_almost_ensure_screen() flushes out all but
// a possible partial last line (this guarantees that it leaves the
// screen scrolled fully left).  There may be some characters left over
// so I then display them one at a time.
    if (nOutputLines != 0) cwin_almost_ensure_screen();
    if (outputCurLength != 0)
    {   for (int i=0; i<outputCurLength; i++)
            theWindow->InsertChar(outputCurLine[i]);
        outputFirst = outputLast = outputCurLine = outputBuffer;
        outputCurLength = 0;
        cwin_poll_window_manager();
    }
    GetSystemTime(&lastFlushTime);
}

// ttyIbuf is a temporary buffer that can hold just one line, used
// while that line is being typed in and subject to line-editing. After
// the user types an ENTER the data is moved elsewhere

static char ttyIbuf[MAX_LINE_LENGTH+4];
static int ttyIbufn;

// inputBuffer is a longer-term place for characters to reside. Once there
// they can not be changed (but cwin_discard_input() can abandon them)

#define INPUT_BUFFER_BITS    10
#define INPUT_BUFFER_MASK    ((1<<INPUT_BUFFER_BITS)-1)
static int inputBufferInsert = 0, inputBufferRemove = 0;
static char inputBuffer[INPUT_BUFFER_MASK+1];

DLLexport void __cdecl cwin_discard_input()
{
    theWindow->AbandonClipboard();
    inputBufferInsert = inputBufferRemove = 0;
    while (ttyIbufn != 0) theWindow->RemoveChar(), ttyIbufn--;
}

void CMainWindow::AwaitInput()
{
    BOOL bottomed = FALSE;
    while (inputBufferRemove == inputBufferInsert &&
           ((clipboardInputHandle==0 && !inputspec) ||
            GrabLineFromClipboard()))
    {
// If there is clipboard input being processed I will not move to the
// bottom of the screen.  Otherwise I will do so ONCE here, so that
// if the user hits scroll bars then the window can stay scrolled... but
// otherwise the caret tends to stay visible.
        if (clipboardInputHandle == 0 && !inputspec && !bottomed)
        {   OnBottom();
            bottomed = TRUE;
        }
        cwin_ensure_screen();
        cwin_poll_window_manager();
    }
}

DLLexport int __cdecl cwin_getchar()
{
    theWindow->AwaitInput();
    return inputBuffer[inputBufferRemove++ & INPUT_BUFFER_MASK];
}


static char MainTitle[84];
static char cLeft[32], cMid[32], cRight[32];

static void ReWriteTitleText()
{
    CWindowDC dc(theWindow);
// It is not (at present) clear to me how to decide how wide to make
// the title.  What follows is some sort of a guess.
    int wTitle = theWindow->clientArea.Width() -
                 3*::GetSystemMetrics(SM_CXVSCROLL);
    int wLeft  = dc.GetTextExtent(cLeft, strlen(cLeft)).cx;
    int wMid   = dc.GetTextExtent(cMid, strlen(cMid)).cx;
    int wRight = dc.GetTextExtent(cRight, strlen(cRight)).cx;
// For the calculations about padding that I use here to work the font used
// in the title bar had better not do any kerning and overhang-effects must
// not interfere.  I find I have all sorts of horrid problems if I try to
// use regular blank characters for padding, but '\xa0' displays as blank
// space and is better behaved.  It also seems (???) that at least under
// Windows 3.1 there is no great joy in trying to use caption strings that
// are longer than 78 characters...
#define PADDING_CHAR   '\xa0'
    char strSp[4]; strSp[0] = PADDING_CHAR;
    int wSp    = dc.GetTextExtent(strSp, 1).cx;
    int cw = wTitle / wSp;
// I first measure things on the supposition that I would allow up to
// 90 characters in the title.  After balancing it a bit I cut that
// down to the 78 that Windows 3.1 seems to be prepared to tolerate.
    if (cw > 90) wTitle = 90*wSp;
    int pad = (wTitle - wMid)/2;
    char *l = cLeft, *r = cRight;
    for (;;)
    {   int padLeft  = (pad - wLeft) / wSp;
        int padRight = (pad - wRight) / wSp;
        int excess = strlen(cLeft) + padLeft + strlen(cMid) +
                     padRight + strlen(cRight) - 78;
        if (excess > 0)
        {   if (excess & 1)
            {   if (padLeft > padRight) padLeft--; else padRight--;
                excess--;
            }
            excess /= 2;
            padLeft -= excess;
            padRight -= excess;
        }
        if (padLeft <= 0 && padRight <= 0)
        {   strcpy(MainTitle, cMid);    // Abandon both right & left items
            break;
        }
        else
        {   if (padLeft <= 0)
            {   l = "";                 // Abandon left item & re-try
                wLeft = 0;
                continue;
            }
            if (padRight <= 0)          // Abandon right item & re-try
            {   r = "";
                padRight = 0;
                continue;
            }
            char *p = MainTitle;
            while (*l != 0) *p++ = *l++;
            for (int i=0; i<padLeft; i++) *p++ = PADDING_CHAR;
            l = cMid;
            while (*l != 0) *p++ = *l++;
            for (i=0; i<padRight; i++) *p++ = PADDING_CHAR;
            while (*r != 0) *p++ = *r++;
            *p = 0;
            break;
        }
    }
    theWindow->SetWindowText(MainTitle);
}

static BOOL leftSetByUser = FALSE;

DLLexport void __cdecl cwin_report_left(char *msg)
{
    if (msg == NULL)
    {   leftSetByUser = FALSE;
        return;                         // Date and time of day will appear
    }
    strncpy(cLeft, msg, 31); cLeft[31] = 0;
    ReWriteTitleText();
    leftSetByUser = TRUE;
}

static char programName[16];

DLLexport void __cdecl cwin_report_mid(char *msg)
{
    if (msg == NULL) msg = programName;
    strncpy(cMid, msg, 31); cLeft[31] = 0;
    ReWriteTitleText();
}

DLLexport void __cdecl cwin_report_right(char *msg)
{
    if (msg == NULL) msg = "";
    strncpy(cRight, msg, 31); cRight[31] = 0;
    ReWriteTitleText();
}

static void display_date()
{
    char dateBuffer[20];
    sprintf(dateBuffer, "%02d-%.3s-%02d.%02d:%02d:%02d",
       titleUpdateTime.wDay,
       "xxxJanFebMarAprMayJunJulAugSepOctNovDec"+3*titleUpdateTime.wMonth,
       titleUpdateTime.wYear%100,
       titleUpdateTime.wHour, titleUpdateTime.wMinute,
       titleUpdateTime.wSecond);
    cwin_report_left(dateBuffer);
    leftSetByUser = FALSE;
}

//
// Windows enters my code at WinMain, but with MFC I arrive via all sorts
// of jolly initialisation code.  To support old code I read the command
// line that invoked me, and parse it into words which I store in argv[],
// much as per the regular C startup process.  When I have a window
// ready, I might call the normal C entrypoint to my program (ie. "main()").
//
 
DLLexport char *cwin_full_program_name;
static int argc;
static char **argv;

static void set_up_argv()
// This sets up argc and argv[] as expected for a regular C application.
// It arranges that argv[0] is an unqualified name, forced into upper case.
// Ie argv[0] does not have a path-name on the front of it or any ".EXE"
// suffix after it.
{
    int i = 0, c, len = 0;
    char *w = GetCommandLine();
// The string I obtained there may be in UniCode, but I will suppose that it
// does not contain any funny characters.  In particular I will demand
// that this module is compiled with Unicode mapped onto ordinary 8-bit
// chars.
    char *w1 = w, *argbuf;
// I scan the command line once to assess its length. Note that I do not
// take any special account of quote marks etc, so this parsing MAY upset
// some people
    do
    {   while ((c = *w++) == ' ');            // Blank at start of an item
        i++;                                  // Count of words
        while (c != 0 && c != ' ') c = *w++, len++;
    } while (c != 0);
// Now I can allocate space for the argument vector and a copy of the data
    argv = (char **)malloc(i*sizeof(char *));
    argbuf = (char *)malloc(i+len);
    argc = 0;
    if (argv==NULL || argbuf==NULL) return;
// Re-scan the command line copying characters into buffers
    w = w1;
    do
    {   while ((c = *w++) == ' ');
        argv[argc++] = argbuf;
        while (c != 0 && c != ' ')
        {   *argbuf++ = c;
            c = *w++;
        }
        *argbuf++ = 0;
    } while (c != 0);
// Put a NULL pointer at the end of argv[], just to be safe
    argv[argc] = NULL;
// Now I want to trim argv[0] so that even if it started with a full
// path or with an extension (eg. "\bin\csl.exe") it is passed on trimmed
// down to just its root (eg. "csl" in the above case).  This string will
// be left in programName too.
    w = w1 = argv[0];
    while ((c = *w++) != 0) if (c == '\\') w1 = w;
    if (*w1 == 0) w1 = "CWIN";  // Final char of argv[0] was '\': use default
    w = programName;
    while ((c = *w1++) != 0 && c != '.') *w++ = toupper(c);
    *w = 0;
    argv[0] = programName;
// Now I would like to get a full path to the program name... The
// SearchPath function looks first in the directory from which the
// application was fetched, and provided that the ".exe" extension
// I specify here is correct the file really ought to be located!
    int nameLength = SearchPath(NULL, programName, ".exe", 0, argbuf, &w);
// There is one critically important case where "SearchPath" will fail here,
// and that is when the program has been started from a debugger and the
// real name of the program is just not available.  In that case tough
// luck, you will have to make resources available by some means NOT
// dependent on the program name or the directory it lives in.
    cwin_full_program_name = (char *)malloc(nameLength+1);
    if (cwin_full_program_name == NULL) cwin_full_program_name = "cwin.exe";
    else
    {   if (SearchPath(NULL, programName, ".exe",
                   nameLength+1, cwin_full_program_name, &w) == 0)
            cwin_full_program_name = "cwin.exe";
    }
    strcpy(cMid, programName);
    cLeft[0] = cRight[0] = 0;
    strcpy(MainTitle, cMid);
}

BOOL CTheApp::InitInstance()
{
    set_up_argv();
    log_file = NULL;
// I register a new window class so that I can associate the icon named
// "CWIN" with it when it is minimised.  This icon is the only thing that is
// needed in the resource file to be associated with a CWIN application, so
// file file "xxx.rc" can contain just
//
//                   CWIN ICON xxx.ico
// (ie only one line!)
// and "rc -r xxx.rc" will make "xxx.res" that can be linked in with the
// application.  The icon "xxx.ico" needs to be created by some suitable
// image editor.
    CBrush *brush = new CBrush(GetSysColor(COLOR_WINDOW));
    mainWindowClass = ::AfxRegisterWndClass(CS_DBLCLKS,
                          LoadStandardCursor(IDC_ARROW),
                          (HBRUSH)brush->GetSafeHandle(),
                          LoadIcon("CWIN"));
    m_pMainWnd = theWindow = new CMainWindow(); // create a main window
    if (theWindow == NULL) return FALSE;
    theWindow->ShowWindow(m_nCmdShow);          // make it visible
    theWindow->UpdateWindow();

    thePicture = new CGraphicsWindow();
    if (thePicture == NULL) return FALSE;
    thePicture->ShowWindow(SW_HIDE);

    theWindow->InitialCleanup();   // ensure main window has focus, etc.

    return TRUE;
}

static MSG *msgPtr;

// At various times I will want to go back and poll the window manager
// to ensure that mouse activity is responded to, the screen is re-drawn and
// other applications get a share of the CPU. To do that I will arrange that
// 'cwin_poll_window_manager()' is called from time to time in the middle of
// whatever else I am doing.  This grabs a message from the window manager
// and dispatches it to whatever handler is relevant.  Note that
// cwin_poll_window_manager() longjumps out when it is time to finish. This is
// maybe a bit drastic, and is only really proper if there are NO "C++"
// procedures activated between main() and it [the relevant destructors
// will not get called properly]

static jmp_buf endApplication;

typedef void __cdecl ExitFunction(void);

#define MAX_EXIT_FUNCTIONS 32

static ExitFunction *exitFunction[MAX_EXIT_FUNCTIONS];
static int exitFunctionCount;
static int returnCode;

DLLexport int __cdecl cwin_atexit(ExitFunction *func)
{
    if (exitFunctionCount < MAX_EXIT_FUNCTIONS)
    {   exitFunction[exitFunctionCount++] = func;
        return 0;
    }
    else return 1;
}

static void DoFinishBox();
DLLexport int cwin_pause_at_end;

DLLexport void __cdecl cwin_exit(int return_code)
{
// Note: I do not put up the "OK" box if the user exits by selecting EXIT or
//       CLOSE from a menu, in that in such cases there has already been
//       some chance to see what the screen says.
    if (cwin_pause_at_end)
    {   cwin_unTop();
        cwin_ensure_screen();
        DoFinishBox();                  // just says OK to give a pause
    }
    while (exitFunctionCount > 0) (*exitFunction[--exitFunctionCount])();
    returnCode = return_code;
    longjmp(endApplication, 1);
}

DLLexport void __cdecl cwin_poll_window_manager()
{
// If the application has registered an idle-time handler then that gets
// invoked until it has finished or until a real message arrives whenever
// I call cwin_poll_window_manager().  I also process ALL the window messages
// that have stacked up and only go back to the user when otherwise idle.
    BOOL msg;
    do
    {   SYSTEMTIME t1;
        GetSystemTime(&t1);
        if (t1.wHour != lastFlushTime.wHour ||
            (t1.wMinute - lastFlushTime.wMinute)*60 +
            (t1.wSecond - lastFlushTime.wSecond) > 3)
            cwin_almost_ensure_screen();
        if (!leftSetByUser)
        {
// Here I arrange to update the title-bar clock about once every 5 secs. It
// seems that every second it too frequent, especially since it often flashes
// the title-bar while re-drawing it.  But 10 seconds is too long and lets
// the user feel things may be stuck.
// If the user explicitly sets anv value in the left part of the title bar
// then this action is disabled.
            if (titleUpdateTime.wHour != t1.wHour ||
                titleUpdateTime.wMinute != t1.wMinute ||
                titleUpdateTime.wSecond/5 != t1.wSecond/5)
            {   titleUpdateTime = t1;
                display_date();
            }
        }
// Now I do any "idle tasks" that have been registered.
        LONG Idle = 0;
        while (!(msg=::PeekMessage(msgPtr, NULL, NULL, NULL, PM_NOREMOVE)) &&
               theApp.OnIdle(Idle++)) {}
// If the user selects CLOSE from the system menu it causes PumpMessage to
// return FALSE, so in that case I close things down.
        if (msg && !theApp.PumpMessage())
        {   while (exitFunctionCount > 0)
                (*exitFunction[--exitFunctionCount])();
            returnCode = 0;
            longjmp(endApplication, 1);
        }
    } while (msg);
}

int CTheApp::Run()       // Main running routine until application exits
{
// If I had not managed to open a main window then I should give up.
    if (m_pMainWnd == NULL) ::PostQuitMessage(0);
// The message handler needs to access m_msgCur which is a private member
// of the class, so I can not use just (theApp.m_msgCur) to get at it. To
// work around the problem I just dump a reference to it in the variable
// msgPtr.
    msgPtr = &m_msgCur;
// Now the real fun!  I call main() which fires up my application code
// Note that if main() is written in C rather than C++ the use of setjmp
// is reasonably valid.  Remember that main() should either return to me
// or do a cwin_exit() and it should NOT call exit().
    exitFunctionCount = 0;
    cwin_pause_at_end = FALSE;
    cwin_poll_window_manager();       // Permit window to appear
    if (setjmp(endApplication) == 0) cwin_exit(main(argc, argv));
    int returnCode1 = ExitInstance();
    if (returnCode1 > returnCode) returnCode = returnCode1;
    return returnCode;
}


struct MENUSTRUCT
{   char *name;
    int id;
    int flags;
};


static MENUSTRUCT fileMenu[] =
{
#ifdef FOR_ML
    {"R&ead",           IDM_READ,        MF_ENABLED},
    {"Save &As...",     IDM_SAVEAS,      MF_ENABLED},
    {"&Save Selection", IDM_SAVESEL,     MF_ENABLED},
    {"&To File...",     IDM_TOFILE,      MF_ENABLED},
#else
    {"R&ead",           IDM_READ,        MF_GRAYED},
    {"Save &As...",     IDM_SAVEAS,      MF_GRAYED},
    {"&Save Selection", IDM_SAVESEL,     MF_GRAYED},
    {"&To File...",     IDM_TOFILE,      MF_GRAYED},
#endif
    {NULL,              0,               MF_SEPARATOR},
    {"&Print...",       IDM_PRINT,       MF_GRAYED},
    {"P&rint Setup...", IDM_PRINTSETUP,  MF_GRAYED},
    {NULL,              0,               MF_SEPARATOR},
    {"E&xit\tCtrl+D",   IDM_EXIT,        MF_ENABLED},
    {NULL,              -1,              -1}
};


static MENUSTRUCT editMenu[] =
{
#ifdef CUT_SUPPORTED
    {"Cu&t\tCtrl+X",    IDM_CUT,         MF_ENABLED},
#endif
    {"&Copy\tCtrl+C",   IDM_COPY,        MF_ENABLED},
    {"&Paste\tCtrl+V",  IDM_PASTE,       MF_ENABLED},
    {"&Move\tCtrl+O",   IDM_MOVE,        MF_ENABLED},
    {"Select &All",     IDM_SELECTALL,   MF_ENABLED},
    {"C&lear All",      IDM_CLEAR,       MF_ENABLED},
    {"&Undo",           IDM_UNDO,        MF_GRAYED},
    {NULL,              0,               MF_SEPARATOR},
    {"&Redraw\tCtrl+L", IDM_REDRAW,      MF_ENABLED},
    {"&Home",           IDM_TOP,         MF_ENABLED},
    {"&End",            IDM_BOTTOM,      MF_ENABLED},
    {NULL,              0,               MF_SEPARATOR},
#ifdef FOR_ML
    {"&Greek",          IDM_GREEK,       MF_ENABLED},
#endif
    {"&Font",           IDM_FONT,        MF_ENABLED},
    {NULL,              -1,              -1}
};

static MENUSTRUCT breakMenu[] =
{   {"&Interrupt\tCtrl+G", IDM_INTERRUPT,   MF_ENABLED},
    {"&Backtrace",         IDM_BACKTRACE,   MF_GRAYED},
    {NULL,                 -1,              -1}
};

static MENUSTRUCT graphicsMenu[] =
{   {"&Show",              IDM_GSHOW,    MF_ENABLED},
    {"&Hide",              IDM_GHIDE,    MF_ENABLED},
    {"&Clear",             IDM_GCLEAR,   MF_ENABLED},
    {NULL,                 -1,           -1}
};

static MENUSTRUCT helpMenu[] =
{   {"&Contents",                ID_HELP_INDEX,   MF_ENABLED},
    {"&Search for Help On...",   ID_HELP,         MF_ENABLED},
    {"&How to Use Help",         ID_HELP_USING,   MF_ENABLED},
    {NULL,                       0,               MF_SEPARATOR},
#ifdef FOR_ML
    {"&About CML",               IDM_ABOUT,       MF_ENABLED},
#else
    {"&About this program",      IDM_ABOUT,       MF_ENABLED},
#endif
    {NULL,                       -1,              -1}
};

static HMENU MakeMenu(MENUSTRUCT *p)
{
    CMenu *m = new CMenu();
    if (m == NULL) DisplayMsg("Failed to create sub-menu");
    if (m->CreatePopupMenu() == 0) DisplayMsg("Failed to create pop-up menu");
    while (p->id != -1)
    {   if (m->AppendMenu(p->flags, p->id, p->name) == 0)
            DisplayMsg("Failed to AppendMenu");
        p++;
    }
    return m->m_hMenu;
}

//  
//  CSL ACCELERATORS
//  {
//      VK_F1, ID_HELP_INDEX, VIRTKEY
//  }
//  
//  

class CenteredDialogBox : public CDialog
{
public:
    void CreateAndDisplay(HGLOBAL h);
};

void CenteredDialogBox::CreateAndDisplay(HGLOBAL h)
{
    InitModalIndirect(h);
    DoModal();
}

// In-store dialog-box templates need some of their strings in 16-bit
// Unicode form.  This code stretches out a simple string. It also round
// the length of the data written to a multiple of 8 bytes, which seems to
// be an unpublished (?) requirement for the dialog box template structures.

LPWORD WidenString(LPWORD p, char *q)
{
    int n = 0;
    while ((*p++ = *q++) != 0) n++;
    if (n & 1) *p++ = 0;
    return p;
}

// The following function fills in details about one control within a
// dialog box template.

LPWORD PlantDlgItem(LPWORD p3, int x, int y, int cx, int cy,
                    int id, DWORD style, int type, char *text)
{
    LPDLGITEMTEMPLATE p2 = (LPDLGITEMTEMPLATE)p3;
    p2->x = x; p2->y = y; p2->cx = cx, p2->cy = cy;
    p2->id = id;
    p2->style = style;
    p3 = (LPWORD)(p2 + 1);
    *p3++ = 0xffff;
    *p3++ = type;
    int n = 1;
    while ((*p3++ = *text++) != 0) n++;
    if (n & 1) *p3++ = 0;
    *p3++ = 0;
    return p3;
}

// When I want to pop up a box that says "Press OK to exit" I make the
// structure that defines the dialog box here in memory rather than putting
// it into my resource file.  The reason for taking this step is that it
// allows to to keep the resource file as spartan and simple as possible.
// It also provides a place for me to ensure that the dialog box is central
// in the area that my window occupies.

static void DoFinishBox()
{
    HGLOBAL h = GlobalAlloc(GMEM_ZEROINIT, 1024);
    if (!h) return;
    LPDLGTEMPLATE p1 = (LPDLGTEMPLATE)GlobalLock(h);
    WORD *p0 = (WORD *)p1; //DEBUG
    p1->style = WS_POPUP | WS_CAPTION | WS_SYSMENU | DS_MODALFRAME;
    p1->cdit = 2;
    p1->cx = 95; p1->cy = 52;
// I want the box to appear in the centre of where my window is. This
// causes extra fun because of the special coordinate system used with
// dialog boxes - I have to convert units.
    LONG units = ::GetDialogBaseUnits();
    int dlgx = units & 0xffff, dlgy = (units >> 16) & 0xffff;
    p1->x = ((4*theWindow->clientArea.Width())/dlgx - p1->cx)/2;
    p1->y = ((8*theWindow->clientArea.Height())/dlgy - p1->cy)/2;
    LPWORD p2 = (LPWORD)(p1 + 1);
    *p2++ = 0;       // no menu
    *p2++ = 0;       // a predefined box class
    *p2++ = 0;       // no title
    p2 = PlantDlgItem(p2, 1, 10, 94, 12, -1,
        WS_CHILD | WS_VISIBLE | SS_CENTER, 0x0082, "Press OK to exit");
    p2 = PlantDlgItem(p2, 28, 23, 40, 14, IDOK,
        WS_CHILD | WS_VISIBLE | BS_DEFPUSHBUTTON, 0x0080, "OK");
    GlobalUnlock(h);
    CenteredDialogBox dlg;
    dlg.CreateAndDisplay(h);
    GlobalFree(h);
}


// Similarly I make the "ABOUT" dialog box from an in-memory template, and
// this makes it possible to make the text that is included depend on
// strings that the user can potentially reconfigure.  The strings put here
// are somewhat generic.   Note also that if a user hits the HELP menu
// during system start-up before the regular user code at main() has been
// entered than the messages shown here will appear, even though later on
// the user's properly selected messages will be the ones that show up. I
// think that on balance I almost count that to be a positive advantage! It
// means that the "CWIN" information and credits are at least just about
// available to all users!

char about_box_title[32]       = "About CWIN";
char about_box_description[32] = "The CWIN window driver";
char about_box_rights_1[32]    = "Copyright Codemist Ltd.";
char about_box_rights_2[32]    = "A C Norman         1994";

static void DoAboutBox()
{
    HGLOBAL h = GlobalAlloc(GMEM_ZEROINIT, 1024);
    if (!h) return;
    LPDLGTEMPLATE p1 = (LPDLGTEMPLATE)GlobalLock(h);
    WORD *p0 = (WORD *)p1;
    p1->style = WS_POPUP | WS_CAPTION | WS_SYSMENU | DS_MODALFRAME;
    p1->cdit = 5;
    p1->cx = 167; p1->cy = 86;
    LONG units = ::GetDialogBaseUnits();
    int dlgx = units & 0xffff, dlgy = (units >> 16) & 0xffff;
    p1->x = ((4*theWindow->clientArea.Width())/dlgx - p1->cx)/2;
    p1->y = ((8*theWindow->clientArea.Height())/dlgy - p1->cy)/2;
    LPWORD p2 = (LPWORD)(p1 + 1);
    *p2++ = 0;       // no menu
    *p2++ = 0;       // a predefined box class
    p2 = WidenString(p2, about_box_title);
    p2 = PlantDlgItem(p2, 0, 4, 167, 8, -1,
        WS_CHILD | WS_VISIBLE | SS_CENTER, 0x0082, about_box_description);
    p2 = PlantDlgItem(p2, 0, 45, 167, 8, -1,
        WS_CHILD | WS_VISIBLE | SS_CENTER, 0x0082, about_box_rights_1);
    p2 = PlantDlgItem(p2, 0, 53, 167, 8, -1,
        WS_CHILD | WS_VISIBLE | SS_CENTER, 0x0082, about_box_rights_2);
    p2 = PlantDlgItem(p2, 74, 22, 0, 0, -1,
        WS_CHILD | WS_VISIBLE | SS_ICON, 0x0082, "CWIN");
    p2 = PlantDlgItem(p2, 66, 65, 32, 14, IDOK,
        WS_CHILD | WS_VISIBLE | BS_DEFPUSHBUTTON, 0x0080, "OK");
    GlobalUnlock(h);
    CenteredDialogBox dlg;
    dlg.CreateAndDisplay(h);
    GlobalFree(h);
}

#ifdef FOR_ML
// The following table maps code 'a' to 'z' onto lower case Greek
// letters.  A few variant styles of letters are tagged on the end
// to make things up to 26, and I artificially exclude lambda
// since in functional languages etc it tends to be wanted for other
// things.  It will be pretty unusual for more than the first four or
// five characters to get used...

static char greekOf[] = "abgdezhqikmnxoprstufcywjvV";
char latinOf[26];
#endif

CMainWindow::CMainWindow()              // constructor fn for the window
{
    GetSystemTime(&titleUpdateTime);
    lastFlushTime = titleUpdateTime;
    titleUpdateTime.wHour = -1;        // so that update happens soon
    charHeight = charWidth = 0;        // mark it all as uninitialised
    clientArea.top = clientArea.bottom =
        clientArea.left = clientArea.right = 0;
    clipArea.top = clipArea.bottom =
        clipArea.left = clipArea.right = 0;
// Now make the main parts of the window
    if (Create(mainWindowClass, MainTitle, 
           WS_OVERLAPPEDWINDOW | WS_VSCROLL | WS_HSCROLL) == 0)
    {   DisplayMsg("Unable to create main window");
        ::PostQuitMessage(0);
        return;
    }
    m_bAutoMenuEnable = FALSE;
    if (Menu.CreateMenu() == 0) DisplayMsg("Failed to make main menu");
    if (Menu.AppendMenu(MF_POPUP, (UINT)MakeMenu(fileMenu), "&File") == 0)
        DisplayMsg("Failed appendmenu");
    if (Menu.AppendMenu(MF_POPUP, (UINT)MakeMenu(editMenu), "&Edit") == 0)
        DisplayMsg("Failed appendmenu");
    if (Menu.AppendMenu(MF_POPUP, (UINT)MakeMenu(breakMenu), "&Break") == 0)
        DisplayMsg("Failed appendmenu");
    if (Menu.AppendMenu(MF_POPUP, (UINT)MakeMenu(graphicsMenu), "&Graphics") == 0)
        DisplayMsg("Failed appendmenu");
    if (Menu.AppendMenu(MF_POPUP, (UINT)MakeMenu(helpMenu),  "&Help") == 0)
        DisplayMsg("Failed appendmenu");;
    if (SetMenu(&Menu) == 0) DisplayMsg("Failed to Setmenu()");
    windowColor = GetSysColor(COLOR_WINDOW);
    textColor = GetSysColor(COLOR_WINDOWTEXT);
    highlightColor = GetSysColor(COLOR_HIGHLIGHT);
    highlightTextColor = GetSysColor(COLOR_HIGHLIGHTTEXT);
// Discover how large the client rectangle will be
    GetClientRect(&clientArea);
    VScrollPos = HScrollPos = 0;    // scroll thumbs start at zero
    leftMargin = 0;
    firstChar = currentChar = 0;    // empty text buffer
    firstLine = firstVisibleLine = currentLine = txtLine[0] = 0;
    currentLineLength = 0;
    selFirstPos = selStartPos = selEndPos = 0;
    trackingSelection = FALSE;
    outputFirst = outputLast = outputCurLine = outputBuffer;
    outputCurLength = nOutputLines = 0;
// Create and install a suitable initial font - I use "Courier New" at around
// 12 points in bold, which looks OK on my screen.  The "-16" seems to ask
// for a font that is around 16 pixels high... but the font selection dialog
// boxes call this "12 pt".  I make the symbol font slightly smaller so that
// characters fit neatly...
    mainFont = symbolFont = NULL;
    CFont *newFont = new CFont();
// The call to SetNewFont sets charWidth, charHeight, nRows and nCols
// as well as the font itself.
    if (newFont == NULL ||
        !newFont->CreateFont(-16, 0, 0, 0, FW_NORMAL, 0, 0, 0,
           ANSI_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS,
           DEFAULT_QUALITY, FIXED_PITCH+FF_DONTCARE, "Courier New") ||
        !SetNewFont(newFont))
    {   DisplayMsg("Failed to find standard font");
        ::PostQuitMessage(0);
        return;
    }
    clipboardInputHandle = 0;
    clipboardInput = NULL;
    inputspec = FALSE;
#ifdef FOR_ML
    for (int k=0; k<sizeof(greekOf)-1; k++)
        latinOf[greekOf[k] - 'a'] = k + 'a';
#endif
}

void CMainWindow::InitialCleanup()
{
    CancelSelection();
    SetFocus();
}


// The re-painting I do here does not pay any attention to the rectangle
// that indicates what parts of the window need re-painting. This is in the
// hope that lower level parts of Windows will perform whatever clipping
// is necessary and not too much performance will be lost.

// In my text buffer I hold regular ASCII characters with codes in the range 0
// to 127 (as usual).  Rather than taking advantage of the extra characters
// with codes in the range 128:255 that the standard Windows character set
// supports I map those characters onto selected parts of the symbol
// character set.  I print symbols one character at a time and make some
// attempt to center them in the fixed-pitch cells set aside for my
// main font.  This will cause some pain with wide symbols that can overflow
// the cell.
// Well actually if the C compiler treats the "char" data type as signed
// than by "128:255" I suppose I mean "-128:-1".
// Furthermore I reserve characters in the range 0x80 to 0x9f for things
// in the regular character set but displayed in colour -- this is for
// prompts.



void CMainWindow::PaintText(CPaintDC *dc, BOOL highlighting,
                            int x, int y, char *s, int n)
{
// When I want to highlight text I will paint the background as a
// rectangle and then draw my text using a drawing mode that does not
// disturb the background further.  This is because the basic character
// cells for the things I display are not all of uniform size.
    if (highlighting)
    {   CBrush b(highlightColor);
        CBrush *oldBrush = dc->SelectObject(&b);
        dc->BitBlt(x, y, n*charWidth, charHeight, NULL, 0, 0, PATCOPY);
        dc->SelectObject(oldBrush);
        dc->SetBkMode(TRANSPARENT);
        dc->SetTextColor(highlightTextColor);
    }
    else dc->SetTextColor(textColor);
    int i, c;
    BOOL anySymbols = FALSE;
    char oneCharacter[4];
    for (i=0; i<n; i++)
    {   c = s[i] & 0xff;
        if (c < 0xa0) continue;
        if (!anySymbols)
        {   if (symbolFont != 0) dc->SelectObject(symbolFont);
            anySymbols = TRUE;
        }
// At present I just map all symbol codes onto the lower half of the
// code map.  This will give me access to greek letters, but in due course
// I may find that I want something more elaborate so that I can
// make use of other symbols.
        c = c & 0x7f;
        oneCharacter[0] = c;
        dc->TextOut(x + i*charWidth + (charWidth - symbolWidth[c])/2,
                    y + symbolOffset,
                    oneCharacter, 1);
    }
    if (anySymbols) dc->SelectObject(mainFont);
    for (i=0; i<n; i++)
    {   c = s[i] & 0xff;
        switch (c)
        {
    case 0x81: c = '-'; break;
    case 0x82: c = '='; break;
    case 0x80: anySymbols = TRUE;   // Blank - do not print anything!
    default:   continue;
        }
        anySymbols = TRUE;
        oneCharacter[0] = c;
        COLORREF old = dc->SetTextColor(RGB(255, 0 , 0));
        dc->TextOut(x + i*charWidth,
                    y + mainOffset,
                    oneCharacter, 1);
        dc->SetTextColor(old);
    }
    if (anySymbols)
    {   int f = 0;
        for (i=0; i<n; i++)
        {   c = s[i] & 0xff;
            if (c < 0x80) continue;
            if (f != i) dc->TextOut(x + f*charWidth, y+mainOffset, s+f, i-f);
            f = i+1;
        }
        if (f != i) dc->TextOut(x + f*charWidth, y+mainOffset, s+f, i-f);
    }
    else dc->TextOut(x, y+mainOffset, s, n);
}

void CMainWindow::OnPaint()
{
    int y=0, line=firstVisibleLine, p, n, n1;
    CPaintDC dc(this);
    CRect r = dc.m_ps.rcPaint;    // The rectangle that needs re-painting
    dc.SelectObject(mainFont);
    txtLineLen[currentLine & LINE_MASK] = currentLineLength;
    int selStartLine = PosLine(selStartPos),
        selStartChar = PosChar(selStartPos),
        selEndLine   = PosLine(selEndPos),
        selEndChar   = PosChar(selEndPos);
// I always keep the caret at the very end of my text, where new characters
// can be added.  This is so even when I am dragging the mouse to select
// some earlier stuff.
// A real problem about this is that it means that it is typically not
// possible to see if a selection includes a newline character at on or
// other end. For the moment I will ignore this limitation.
    CPoint cp;
    cp.x = charWidth*(currentLineLength - leftMargin);
    cp.y = charHeight*(currentLine - firstVisibleLine);
    SetCaretPos(cp);
// This is somewhat sordid mainly because it needs to paint a selected region
// of text in inverse video. I deal with this by first painting any lines
// that appear above the selection.
    int highlighting = (line > selStartLine && line <= selEndLine);
    for (;
         y < nRows && line <= currentLine && line < selStartLine;
         y++, line++)
    {   p = txtLine[line & LINE_MASK],
        n = txtLineLen[line & LINE_MASK] - leftMargin;
        if (n > nCols) n = nCols;    // clip to whole number of chars
        if (n > 0) PaintText(&dc, highlighting,
                             0, charHeight*y,
                             &txtBuffer[p + leftMargin],
                             n);
    }
    if (y >= nRows || line > currentLine) return;
// Next deal with the line on which a selection starts. This is more
// complicated because the selection can start part way across the line,
// but what is worse, the selection can end later on the same line. In
// that case I paint in three segments here.
    if (line == selStartLine)
    {   p = txtLine[line & LINE_MASK],
        n = selStartChar - leftMargin;
        if (n > nCols) n = nCols;
        if (n > 0) PaintText(&dc, FALSE,
                             0, charHeight*y,
                             &txtBuffer[p + leftMargin],
                             n);
        if (selStartLine == selEndLine)
        {   n1 = selEndChar - leftMargin;
            if (n1 > nCols) n1 = nCols;
            if (n1 > n) PaintText(&dc, TRUE,
                                  charWidth*n, charHeight*y,
                                  &txtBuffer[p + leftMargin + n],
                                  n1 - n);
            n = n1;
            n1 = txtLineLen[line & LINE_MASK] - leftMargin;
            if (n1 > nCols) n1 = nCols;
            if (n1 > n) PaintText(&dc, FALSE,
                                  charWidth*n, charHeight*y,
                                  &txtBuffer[p + leftMargin + n],
                                  n1 - n);
        }
        else
        {   n1 = txtLineLen[line & LINE_MASK] - leftMargin;
            if (n1 > nCols) n1 = nCols;
            if (n1 > n) PaintText(&dc, TRUE,
                                  charWidth*n, charHeight*y,
                                  &txtBuffer[p + leftMargin + n],
                                  n1 - n);
        }
        y++, line++;
        if (y >= nRows || line > currentLine) return;
    }
// Now if there was a multi-line selection I have a straightforward bunch
// of lines all to be painted in inverse video.
    for (;
         y < nRows && line <= currentLine && line < selEndLine;
         y++, line++)
    {   p = txtLine[line & LINE_MASK],
        n = txtLineLen[line & LINE_MASK] - leftMargin;
        if (n > nCols) n = nCols;
        if (n > 0) PaintText(&dc, TRUE,
                             0, charHeight*y,
                             &txtBuffer[p + leftMargin],
                             n);
    }
    if (y >= nRows || line > currentLine) return;

    if (line == selEndLine)
    {   p = txtLine[line & LINE_MASK],
        n = selEndChar - leftMargin;
        if (n > nCols) n = nCols;
        if (n > 0) PaintText(&dc, TRUE,
                             0, charHeight*y,
                             &txtBuffer[p + leftMargin],
                             n);
        n1 = txtLineLen[line & LINE_MASK] - leftMargin;
        if (n1 > nCols) n1 = nCols;
        if (n1 > n) PaintText(&dc, FALSE,
                              charWidth*n, charHeight*y,
                              &txtBuffer[p + leftMargin + n],
                              n1 - n);
        y++, line++;
        if (y >= nRows || line > currentLine) return;
    }
// Finally I have any unselected lines at the end of the buffer
    for (;
         y < nRows && line <= currentLine;
         y++, line++)
    {   p = txtLine[line & LINE_MASK],
        n = txtLineLen[line & LINE_MASK] - leftMargin;
        if (n > nCols) n = nCols;
        if (n > 0) PaintText(&dc, FALSE,
                             0, charHeight*y,
                             &txtBuffer[p + leftMargin],
                             n);
    }
// Note that as this procedure exits the CPaintDC goes out of scope so
// its destructor is invoked - and the caret gets re-displayed.
}

// This is the basic structure for the representation of text:
//
//  txtBuffer[] holds the characters.  Viewing it as a circular buffer
//         firstChar is the index of the first character stored, and
//         currentChar is one beyond the last.  Within this buffer data
//         is stored in "lines" and no line is ever split across the
//         wrap-around.  Characters for one line follow on immediately
//         after the previous (no terminator, no '\n' between).
//  txtLine[] holds the offsets in txtBuffer[] of the first character of
//         each line.
//  txtLineLen[] records the length of each line.
//         The index for txtLine[] and txtLineLen is taken to be an actual
//         line number masked with LINE_MASK.
//  firstLine, currentLine are the true line numbers of the first and last
//         lines stored (so txtBuffer[txtLine[firstLine]] is the first
//         character stored).
//         txtLineLen[currentLine & LINE_MASK] is not filled in until the line
//         concerned is complete, so
//  currentLineLength stores the length of the current (active) line until
//         then.
//
//  Additional variables are concerned with selected regions, scrolling,
// and the fact that in general only part of the text will fit on the
// screen.

#ifdef FOR_ML

static BOOL separator(int ch)
{
    return (ch & 0x80) ||
           !(isalpha(ch) || isdigit(ch) || ch == '_');
}

#endif

void CMainWindow::InsertChar(int ch)
// This inserts one character into the text buffer, removing stale ones
// if space is getting tight, scrolling the window (horizontally and/or
// vertically) as necessary and either painting to the screen or marking
// things invalid as it sees fit.  The cases of '\n' (newlines) and regular
// characters are totally different but it still seems nicer to provide just
// one interface to both forms of insertion.
{
    if (log_file != NULL) 
    {   switch (ch)
        {
    case 0x80: putc(' ', log_file); break;
    case 0x81: putc('-', log_file); break;
    case 0x82: putc('=', log_file); break;
    default:   putc(ch,  log_file); break;
        }
    }
#ifdef FOR_ML
// If I see a pair of characters <'x> for some lower case letter <x>
// surrounded by punctuation, I render the pair as a single Greek
// letter.  Thus <'a> comes out as <alpha>, <'b> as <beta> and so on --
// but (for curious reasons) I make <fn> map to <lambda>.
    if (cwin_has_greek && separator(ch) && ch != '\'' &&
        (currentLineLength == 2 ||
         (currentLineLength > 2 && separator(txtBuffer[currentChar-3]))))
    {   int c1 = txtBuffer[currentChar-2],
            c2 = txtBuffer[currentChar-1],
            greek = 0;
        if (c1 == 'f' && c2 == 'n') greek = 'l' | 0x80;
        else if (c1 == '\'' && 'a' <= c2 && c2 <= 'z')
            greek = greekOf[c2 - 'a'] | 0x80;
        if (greek != 0)
        {   currentChar--;
            txtBuffer[currentChar-1] = greek;
            currentLineLength--;
            RECT r;
            r.top = charHeight*(currentLine - firstVisibleLine);
            r.bottom = r.top + charHeight;
            r.left = charWidth*(currentLineLength - 1 - leftMargin);
            r.right = r.left + charWidth;
            InvalidateRect(&r, TRUE);
        }
    }
#endif
    if (ch == '\n')
    {   txtLineLen[LINE_MASK & currentLine++] = currentLineLength;
        while (currentLine == firstLine + LINE_MASK + 1) RemoveFirstLine();
        txtLine[currentLine & LINE_MASK] = currentChar;
        currentLineLength = 0;
        if (leftMargin != 0) OnHScroll(PRIVATE_SET_LEFT_MARGIN, 0, NULL);
        if (currentLine - firstVisibleLine == nRows)
            OnVScroll(PRIVATE_SET_VISIBLE_LINE, firstVisibleLine+1, NULL);
// Since inserting a newline may not provoke any re-painting my normal
// mechanism for keeping the caret in place fails - hence I re-position
// it by hand here.
        CPoint cp;
        cp.x = charWidth*(currentLineLength - leftMargin);
        cp.y = charHeight*(currentLine - firstVisibleLine);
        SetCaretPos(cp);
    }
// I have an upper limit on the length of output line I will consider.  The
// main reason for this is that with this limit I can be certain that my
// text buffer has room in it for several worst-length lines, and this
// avoids awkward degenerate cases when the main buffer becomes full.  I just
// silently discard characters that are beyond the limit.  A further useful
// effect of the line-length limit is that it allows me to pack logical
// positions in the text into one word.
    else if (currentLineLength < MAX_LINE_LENGTH)
    {   txtBuffer[currentChar++] = ch;
        currentLineLength++;
        if (currentChar == TEXT_SIZE)              // need to wrap around
        {   while (firstChar <= currentLineLength) RemoveFirstLine();
            memcpy(txtBuffer, &txtBuffer[txtLine[currentLine & LINE_MASK]],
                              currentLineLength);
            txtLine[currentLine & LINE_MASK] = 0;
            currentChar = currentLineLength;
        }
        while (currentChar == firstChar) RemoveFirstLine();
        if (currentLineLength - leftMargin >= nCols)
        {   int excess = currentLineLength - leftMargin - nCols + 1;
            if (nCols > 15 && excess < 10) excess = 10;
            OnHScroll(PRIVATE_SET_LEFT_MARGIN, leftMargin+excess, NULL);
        }
        RECT r;
        r.top = charHeight*(currentLine - firstVisibleLine);
        r.bottom = r.top + charHeight;
        r.right = charWidth*(currentLineLength - leftMargin);
        r.left = r.right - charWidth;
        InvalidateRect(&r, TRUE);
    }
}

void CMainWindow::InsertLine(char *s, int n)
// This inserts several characters into the buffer, with the last of these
// being a newline (thus at the end this always leaves the insertion point
// at the left margin, and it always moves down just one line).  This
// ought to be more efficent than inserting the characters one at a time,
// especially if the line created would have been long enough to provoke
// temporary horizontal scrolling of the window.
{
    int iLineLength = currentLineLength;
// If the line looks as if it will be longer than the longest line that I
// permit I will silently truncate it. I do this even if the line would
// turn out to contain many character pairs that would collapse into single
// Greek letters...
    if (iLineLength + n >= MAX_LINE_LENGTH)
        n = MAX_LINE_LENGTH - iLineLength - 1;
#ifdef FOR_ML
    int c1 = ' ', c2, c3, greek;
    switch (currentLineLength)
    {
case 0:
case 1:  c2 = c3 = ' ';
         break;
default: c1 = txtBuffer[currentChar-3];
case 2:  c2 = txtBuffer[currentChar-2];
         c3 = txtBuffer[currentChar-1];
         break;
    }
    if (separator(c1) && separator(*s) && *s != '\'') iLineLength--;
#endif
    for (int i=0; i<n; i++)
    {   int ch = *s++;
#ifdef FOR_ML
        if (cwin_has_greek && separator(c1) && separator(ch) && ch != '\'')
        {   greek = 0;
            if (c2 == 'f' && c3 == 'n') greek = 'l' | 0x80;
            else if (c2 == '\'' && 'a' <= c3 && c3 <= 'z')
                greek = greekOf[c3 - 'a'] | 0x80;
            if (greek != 0)
            {   txtBuffer[currentChar-2] = c3 = greek;
                txtBuffer[currentChar-1] = ch;
                continue;
            }
        }
#endif
        txtBuffer[currentChar++] = ch;
        currentLineLength++;
#ifdef FOR_ML
        c1 = c2; c2 = c3; c3 = ch;
#endif
        if (currentChar == TEXT_SIZE)              // need to wrap around
        {   while (firstChar <= currentLineLength) RemoveFirstLine();
            memcpy(txtBuffer, &txtBuffer[txtLine[currentLine & LINE_MASK]],
                              currentLineLength);
            txtLine[currentLine & LINE_MASK] = 0;
            currentChar = currentLineLength;
        }
        while (currentChar == firstChar) RemoveFirstLine();
    }
#ifdef FOR_ML
// An ugly extra complication here to cope with the final character on
// a line being in Greek.
    if (cwin_has_greek && separator(c1))
    {   greek = 0;
        if (c2 == 'f' && c3 == 'n') greek = 'l' | 0x80;
        else if (c2 == '\'' && 'a' <= c3 && c3 <= 'z')
            greek = greekOf[c3 - 'a'] | 0x80;
        if (greek != 0)
        {   currentChar--;
            currentLineLength--;
            txtBuffer[currentChar-1] = greek;
        }
    }
#endif
// The amount that I invalidate here does not take account of reduction
// to Greek -- but that only means I re-paint a few extra character positions
// and so it seems unimportant.
    if (n > 0)
    {   RECT r;
        r.top    = charHeight*(currentLine - firstVisibleLine);
        r.bottom = r.top + charHeight;
        r.right  = charWidth*(currentLineLength - leftMargin);
        r.left   = charWidth*(iLineLength - leftMargin);
        InvalidateRect(&r, TRUE);
    }
    txtLineLen[LINE_MASK & currentLine++] = currentLineLength;
    while (currentLine == firstLine + LINE_MASK + 1) RemoveFirstLine();
    txtLine[currentLine & LINE_MASK] = currentChar;
    currentLineLength = 0;
    if (leftMargin != 0) OnHScroll(PRIVATE_SET_LEFT_MARGIN, 0, NULL);
    if (currentLine - firstVisibleLine == nRows)
        OnVScroll(PRIVATE_SET_VISIBLE_LINE, firstVisibleLine+1, NULL);
    if (n <= 0)
    {   CPoint cp;
        cp.x = charWidth*(currentLineLength - leftMargin);
        cp.y = charHeight*(currentLine - firstVisibleLine);
        SetCaretPos(cp);
    }
}

void CMainWindow::InsertSeveralLines(char **lines, int *lengths, int nlines)
// This inserts several lines of characters into the buffer.  The arrays
// lines contains pointers to each string to write, while the corresponding
// entries in lengths says how long each string is,
{
    for (int k=0; k<nlines; k++)
    {   char *s = lines[k];
        int n = lengths[k];
#ifdef FOR_ML
        int c1 = ' ', c2 = ' ', c3 = ' ', greek;
#endif
        int iLineLength = currentLineLength;
        if (iLineLength + n >= MAX_LINE_LENGTH)
            n = MAX_LINE_LENGTH - iLineLength - 1;
        for (int i=0; i<n; i++)
        {   int ch = *s++;
#ifdef FOR_ML
            if (cwin_has_greek && separator(c1) &&
                separator(ch) && ch != '\'')
            {   greek = 0;
                if (c2 == 'f' && c3 == 'n') greek = 'l' | 0x80;
                else if (c2 == '\'' && 'a' <= c3 && c3 <= 'z')
                    greek = greekOf[c3 - 'a'] | 0x80;
                if (greek != 0) 
                {   txtBuffer[currentChar-2] = greek;
                    txtBuffer[currentChar-1] = ch;
                    ch = greek;
                }
                else
                {   txtBuffer[currentChar++] = ch;
                    currentLineLength++;
                }
            }
            else
            {   txtBuffer[currentChar++] = ch;
                currentLineLength++;
            }
            c1 = c2; c2 = c3; c3 = ch;
#else
            txtBuffer[currentChar++] = ch;
            currentLineLength++;
#endif
            if (currentChar == TEXT_SIZE)
            {   while (firstChar <= currentLineLength) RemoveFirstLine();
                memcpy(txtBuffer, &txtBuffer[txtLine[currentLine & LINE_MASK]],
                                  currentLineLength);
                txtLine[currentLine & LINE_MASK] = 0;
                currentChar = currentLineLength;
            }
            while (currentChar == firstChar) RemoveFirstLine();
        }
#ifdef FOR_ML
        if (cwin_has_greek && separator(c1))
        {   greek = 0;
            if (c2 == 'f' && c3 == 'n') greek = 'l' | 0x80;
            else if (c2 == '\'' && 'a' <= c3 && c3 <= 'z')
                greek = greekOf[c3 - 'a'] | 0x80;
            if (greek != 0)
            {   currentChar--;
                currentLineLength--;
                txtBuffer[currentChar-1] = greek;
            }
        }
#endif
        if (n > 0)
        {   RECT r;
            r.top    = charHeight*(currentLine - firstVisibleLine);
            r.bottom = r.top + charHeight;
            r.right  = charWidth*(currentLineLength - leftMargin);
            r.left   = charWidth*(iLineLength - leftMargin);
            InvalidateRect(&r, TRUE);
        }
        txtLineLen[LINE_MASK & currentLine++] = currentLineLength;
        while (currentLine == firstLine + LINE_MASK + 1) RemoveFirstLine();
        txtLine[currentLine & LINE_MASK] = currentChar;
        currentLineLength = 0;
    }
    if (leftMargin != 0) OnHScroll(PRIVATE_SET_LEFT_MARGIN, 0, NULL);
    if (currentLine - firstVisibleLine == nRows)
        OnVScroll(PRIVATE_SET_VISIBLE_LINE, firstVisibleLine+1, NULL);
    CPoint cp;
    cp.x = charWidth*(currentLineLength - leftMargin);
    cp.y = charHeight*(currentLine - firstVisibleLine);
    SetCaretPos(cp);
}

// When the text buffer gets full I make space in it by discarding the
// oldest stored line.  The function does that.  In the worst case this can
// lead to a need to scroll the window.

void CMainWindow::RemoveFirstLine()
{
    if (PosLine(selStartPos) == firstLine)
    {   selStartPos = MakePos(firstLine+1, 0);
        if (selStartPos > selEndPos) selEndPos = selStartPos;
        if (selStartPos > selFirstPos) selFirstPos = selStartPos;
    }
    firstLine++;
    firstChar = txtLine[firstLine & LINE_MASK];
    while (firstLine > firstVisibleLine)
        OnVScroll(PRIVATE_SET_VISIBLE_LINE, firstVisibleLine+1, NULL);
}

#ifdef FOR_ML

void CMainWindow::ExpandGreek()
{
    if (currentLineLength != 0)
    {   int ch = txtBuffer[currentChar - 1] & 0xff;
        if (ch >= 0xa0) 
        {   switch (ch)
            {
        case 'l' | 0x80:
                txtBuffer[currentChar-1] = 'f';
                ch = 'n';
                break;
        case 'V' | 0x80:
                txtBuffer[currentChar-1] = '\'';
                ch = 'z';
                break;
        default:
                txtBuffer[currentChar-1] = '\'';
                ch = latinOf[(ch & 0x7f) - 'a'];
                break;
            }
            currentLineLength++;
            txtBuffer[currentChar++] = ch;
            RECT r;
            r.top = charHeight*(currentLine - firstVisibleLine);
            r.bottom = r.top + charHeight;
            r.left = charWidth*(currentLineLength - 2 - leftMargin);
            r.right = r.left + 2*charWidth;
            InvalidateRect(&r, TRUE);
        }
    }
}

#endif

void CMainWindow::RemoveChar()
// This removes one character from the text buffer. If the buffer is
// empty it does nothing.
{
    if (currentLineLength == 0)
    {   if (currentLine == firstLine) return;  // buffer is empty.
        currentLineLength = txtLineLen[LINE_MASK & --currentLine];
        currentChar = txtLine[currentLine & LINE_MASK] + currentLineLength;
#ifdef FOR_ML
        ExpandGreek();
#endif
        if (PosLine(selEndPos) > currentLine)
        {   selEndPos = MakePos(currentLine, currentLineLength);
            if (selFirstPos > selEndPos) selFirstPos = selEndPos;
            if (selStartPos > selEndPos) selStartPos = selEndPos;
        }
        if (currentLineLength - leftMargin >= nCols)
        {   OnHScroll(PRIVATE_SET_LEFT_MARGIN,
                      currentLineLength - nCols + 1, NULL);
        }
        if (currentLine < firstVisibleLine)
        {   int deficit = firstVisibleLine - currentLine;
            int half = nRows/2;
            if (deficit < half) deficit = half;
            if (firstVisibleLine - firstLine < deficit)
                deficit = firstVisibleLine - firstLine;
            OnVScroll(PRIVATE_SET_VISIBLE_LINE,
                      firstVisibleLine-deficit, NULL);
        }
        CPoint cp;
        cp.x = charWidth*(currentLineLength - leftMargin);
        cp.y = charHeight*(currentLine - firstVisibleLine);
        SetCaretPos(cp);
    }
    else
    {   currentChar--;
        currentLineLength--;
#ifdef FOR_ML
        ExpandGreek();
#endif
        if (PosLine(selEndPos) == currentLine &&
            PosChar(selEndPos) > currentLineLength)
        {   selEndPos = MakePos(currentLine, currentLineLength);
            if (selFirstPos > selEndPos) selFirstPos = selEndPos;
            if (selStartPos > selEndPos) selStartPos = selEndPos;
        }
        if (currentLineLength <= leftMargin && leftMargin != 0)
        {   int deficit = leftMargin - currentLineLength;
            if (nCols > 15 && deficit < 10) deficit = 10;
            if (deficit > leftMargin) deficit = leftMargin;
            OnHScroll(PRIVATE_SET_LEFT_MARGIN,
                      leftMargin - deficit, NULL);
        }
        RECT r;
        r.top = charHeight*(currentLine - firstVisibleLine);
        r.bottom = r.top + charHeight;
        r.left = charWidth*(currentLineLength - leftMargin);
        r.right = r.left + charWidth;
        InvalidateRect(&r, TRUE);
    }
}

#define Ctrl(x) ((x) & 0x1f)

void CMainWindow::OnChar(UINT ch, UINT nRepCnt, UINT nFlags)
{
    ch &= 0xff;
    switch (ch)
    {
case Ctrl('C'):
        OnCopy();
        return;
// I make Ctrl+D and Ctrl+Z exit, but note very well that they both
// exit from this system super-promptly when the key is typed, and
// they do NOT wait until the program gets around to reading them.
case Ctrl('D'):          // Some Unix users may be used to Ctrl+D as EOF?
        OnExit();
        return;
case Ctrl('G'):
        OnInterrupt();
        return;
case Ctrl('L'):
        OnRedraw();
        return;
case Ctrl('O'):
        OnMove();
        return;
case Ctrl('V'):
        OnPaste();
        return;
#ifdef CUT_SUPPORTED
case Ctrl('X'):
        OnCut();
        return;
#endif
case Ctrl('Z'):         // Some PC users may be used to Ctrl+Z as EOF?
        OnExit();
        return;
case 0x0a:
case 0x0d:
        ch = '\n';
        break;
case '\t':
        break;
default:
        if ((ch & 0x7f) < 0x20) return;  // Discard control chars.
        break;
    }
    OnBottom();            // make insertion point visible
    if (ch == '\n')
    {   ttyIbuf[ttyIbufn++] = ch;
        if (inputBufferInsert + ttyIbufn - inputBufferRemove >=
            INPUT_BUFFER_MASK)
        {   ::Beep(1000, 100);          // Input buffer full - BEEP
            return;
        }
        for (int i=0; i<ttyIbufn; i++)
            inputBuffer[inputBufferInsert++ & INPUT_BUFFER_MASK] =
                ttyIbuf[i];
        ttyIbufn = 0;
        InsertChar('\n');
        return;
    }
    if (ttyIbufn >= MAX_LINE_LENGTH)
    {   ::Beep(1000 ,100);
        return;
    }
// I treat tabs as things to convert to the correct number of spaces
// to align input to a multiple of 8 columns. MAX_LINE_LENGTH ought to
// be a multiple of 8 for the overflow check here to have been valid.
// Note that currentLineLength should be at least as great as ttyIbufn:
// it can be strictly larger if there was text on the current line (eg
// a prompt) before the user started to type anything in.
    if (ch == '\t')
    {   do
        {   ttyIbuf[ttyIbufn++] = ' ';
            InsertChar(' ');
        } while ((currentLineLength & 7) != 0);
    }
    else
    {   ttyIbuf[ttyIbufn++] = ch;
        InsertChar(ch);
    }
}

void CMainWindow::OnKeyDown(UINT ch, UINT nRepCnt, UINT nFlags)
{
    switch (ch)
    {
case VK_BACK:                  // I make "backspace" and "delete"
case VK_DELETE:                // synonyms here to reduce mixups.
        if (ttyIbufn > 0) ttyIbufn--, RemoveChar();
        OnBottom();            // make insertion point visible
        return;
// The various keys like "PAGE UP" etc do just what the scroll bar can do.
case VK_UP:
        OnVScroll(SB_LINEUP, 0, NULL);
        return;
case VK_PRIOR:
        OnVScroll(SB_PAGEUP, 0, NULL);
        return;
case VK_NEXT:
        OnVScroll(SB_PAGEDOWN, 0, NULL);
        return;
case VK_DOWN:
        OnVScroll(SB_LINEDOWN, 0, NULL);
        return;
case VK_HOME:
        OnTop();
        return;
case VK_END:
        OnBottom();
        return;
    }
}

#ifdef CUT_SUPPORTED

// The CUT operation seems to be unexpectedly unpleasant to implement
// with the data structures that I have used (partly bacause they are
// all tuned for text insertion and delation only at the END of the
// buffer).  The comments in the code that follows give some indications
// of what I find nasty. Until I resolve all these problems I just disable
// the facility.

void CMainWindow::OnCut()
{
    OnCopy();
// Now I "just" have to delete the current selection.  If the selection
// is all on one line I will do a reasonably efficient job and only
// get that line re-painted: if the selection goes across line boundaries
// I will apply a load of brute force, which may sometimes be a bit slow.
//
// Warning - if the text that is CUT out includes some of the currectly
// active input line I will NOT at present remove that part from the
// input buffer.  The reason I do not do so is that the input buffer and
// screen buffers are separate structures and keeping them in step
// may cause me difficulty.
    if (PosLine(selStartPos) == PosLine(selEndPos))
    {   int l = PosLine(selStartPos);
        int c = txtLine[l & LINE_MASK];
        int c1 = PosChar(selStartPos), c2 = PosChar(selEndPos);
// Use of CUT can lead to (slight) anomolies here.  If you start off
// with the text   ... '####a ...  and then use CUT to remove the #### then
// the string <'a> is left, but I will not redisplay this as <alpha>.
// Also if you start with <alpha> followed by some non-letter and CUT
// out the non-letter than you can arrive with text that still displays the
// Greek letter but which could not have been entered directly.  I propose
// to ignore these bad features for the moment.
       memmove(&txtBuffer[c+c1], &txtBuffer[c+c2], c2 - c1);
        txtLineLen[l & LINE_MASK] -= (c2 - c1);
        CRect r;
        r.left = 0;
        r.right = charWidth*txtLineLen[l & LINE_MASK];
        r.top = charHeight*(l - firstVisibleLine);
        r.bottom = r.top + charHeight;
        InvalidateRect(&r, TRUE);
        return;
    }
    return;      // This is so messy (especially if the cut out
                 // section wraps around my circular buffer and the
                 // process of cutting introduces a very long line)
                 // that I make CUT behave as COPY for now.
    {    int l = PosLine(selStartPos);
         txtLine[l & LINE_MASK] += PosChar(selStartPos);
// At present I delete text from the middle of the buffer by just
// adjusting the buffer index so that the discarded lines will be
// ignored.  In the long run this is a potential horrible mess, since
// I could have a buffer apparently full but in fact just full of wasted
// space.  I suspect that the place to address this issue is in
// RemoveFirstLine() which is called only if the buffer is pretty full.
         for (int k=PosLine(selEndPos); k<=currentLine; k++)
         {   l++;
             txtLine[l] = txtLine[k];
             txtLineLen[l] = txtLineLen[k];
         }
         currentLine -= PosLine(selEndPos) - PosLine(selStartPos);
         Invalidate(TRUE);              // repaint entire window.
    }
    currentLineLength = txtLineLen[currentLine & LINE_MASK];
// Further worry arises if the selection that was just deleted included
// the free end of the text, since then I must reset the insertion point
// and caret.  Well the caret needs to be repositioned whenever part of
// the deletion was on the current line.
    selStartPos = selEndPos = selFirstPos = 0;
}

#endif

static char *xmemcopy(char *p, char *q, int n)
{
    while (n-- > 0)
    {   int c = *q++ & 0xff;
#ifdef FOR_ML
// Here, when copying things to the clipboard, I map Greek letters
// back onto the dull pairs of characters used for them in plain text files.
        switch (c)
        {
    case 0x80:
    case 0x81:
    case 0x82:
            continue;   // Do not pick up the prompt!!!!
    case 'l' | 0x80:
            *p++ = 'f', c = 'n'; break;
    case 'V' | 0x80:
            *p++ = '\'', c = 'z'; break;
    default:
            if (c & 0x80) *p++ = '\'', c = latinOf[(c & 0x7f) - 'a'];
        }
#endif
        *p++ = c;
    }
    return p;
}

void CMainWindow::OnCopy()
{
// Here I have to allocate some global memory and copy text out of my buffer
// into it, making sure I insert CR/LF combinations between lines and a
// zero byte on the end as a terminator.
    int i, size;
    txtLineLen[currentLine] = currentLineLength;
    if (!OpenClipboard()) return;
    if (!::EmptyClipboard()) return;
    int l1 = PosLine(selStartPos), l2 = PosLine(selEndPos);
    int c1 = PosChar(selStartPos), c2 = PosChar(selEndPos);
// I make the buffer for COPYing into rather larger than may be
// necessary to be certain that I have left enough space in case I need to
// expand <alpha> into <'a> etc.  For a one-line selection I just allocate
// twice as many characters as are visible - for multi-line ones I am
// more careful.
    if (l1 == l2) size = 2*(c2 - c1);
    else
    {   size = 2*(txtLineLen[l1 & LINE_MASK] - c1);  // tail of first line
        for (i=l1+1; i<l2; i++)
        {   int len = txtLineLen[i & LINE_MASK];
#ifdef FOR_ML
            char *bb = &txtBuffer[txtLine[i & LINE_MASK]];
            for (int k=0; k<len; k++)
                if (bb[k] & 0x80) size++;   // may slightly over-estimate
#endif
            size += len + 2;
        }
        size += 2*c2;
    }
    HGLOBAL h = ::GlobalAlloc(GMEM_MOVEABLE+GMEM_DDESHARE, size+1);
    char *p = NULL;
    if (h != NULL) p = (char *)::GlobalLock(h);
    if (p != NULL)
    {   if (l1 == l2)
        {   p = xmemcopy(p, &txtBuffer[txtLine[LINE_MASK & l1]+c1], c2-c1);
            *p = 0;
        }
        else
        {   int n = txtLineLen[l1 & LINE_MASK] - c1;
            p = xmemcopy(p, &txtBuffer[txtLine[l1 & LINE_MASK]+c1], n);
            *p++ = '\r'; *p++ = '\n';
            for (i=l1+1; i<l2; i++)
            {   n = txtLineLen[i & LINE_MASK];
                p = xmemcopy(p, &txtBuffer[txtLine[i & LINE_MASK]], n);
                *p++ = '\r'; *p++ = '\n';
            }
            p = xmemcopy(p, &txtBuffer[txtLine[l2 & LINE_MASK]], c2);
            *p = 0;
        }
        ::GlobalUnlock(h);
        ::SetClipboardData(CF_TEXT, h);
    }
    ::CloseClipboard();
}

BOOL CMainWindow::GrabLineFromClipboard()
{
    int c;
/*
 * When FOR_ML is set I could imagine that I would want to turn the
 * sequence "fn" from the clipboard into a lambda.  But I will not, since
 * it feels like more effort than it is worth.
 */
    while ((c = *clipboardInput++) != 0)
    {   if (c == '\r') continue;
        c &= 0xff;
        if (c == 0x0a || c == 0x0d)   // Accept CR or LF here.
        {   ttyIbuf[ttyIbufn++] = '\n';
            for (int i=0; i<ttyIbufn; i++)
                inputBuffer[inputBufferInsert++ & INPUT_BUFFER_MASK] =
                    ttyIbuf[i];
            ttyIbufn = 0;
            cwin_putchar('\n');
            break;
        }
// I just truncate overlong lines that come from the clipboard
        if (ttyIbufn >= MAX_LINE_LENGTH) continue;
        if (c == '\t')
        {   do
            {   ttyIbuf[ttyIbufn++] = ' ';
                cwin_putchar(' ');
            } while ((ttyIbufn & 7) != 0);
        }
        else if ((c & 0x7f) >= 0x20)   // Ignore control chars
        {   ttyIbuf[ttyIbufn++] = c;
            cwin_putchar(c);
        }
    }
    if (c == 0)
    {   if (inputspec) inputspec = FALSE;
        else
        {   ::GlobalUnlock(clipboardInputHandle);
            clipboardInputHandle = 0;
            OnBottom();
        }
    }
    return TRUE;
}

void CMainWindow::AbandonClipboard()
{
    if (clipboardInputHandle)
    {   ::GlobalUnlock(clipboardInputHandle);
        clipboardInputHandle = 0;
    }
    inputspec = FALSE;
}

void CMainWindow::OnPaste()
{
    if (!OpenClipboard()) return;
    inputspec = FALSE;
    if (clipboardInputHandle) ::GlobalUnlock(clipboardInputHandle);
    clipboardInputHandle = ::GetClipboardData(CF_TEXT);
    if (clipboardInputHandle != 0)
    {   clipboardInput = (char *)::GlobalLock(clipboardInputHandle);
        if (clipboardInput == NULL) clipboardInputHandle = 0;
    }
    ::CloseClipboard();
// I transfer the first line of stuff from the clipboard at once.
// If the clipboard contained multiple lines of text than subsequent
// ones are only accessed when the user's code tries to read them.
    if (clipboardInputHandle)
    {   int c;
        while ((c = *clipboardInput++) != 0)
        {   if (c == '\r') continue;
            OnChar(c, 1, 0);
            if (c == '\n') break;
        }
        if (c == 0)
        {   if (clipboardInputHandle) ::GlobalUnlock(clipboardInputHandle);
            clipboardInputHandle = 0;
            OnBottom();
        }
    }
}

void CMainWindow::OnMove()
{
    OnCopy();
    OnPaste();
}

// Clear All just throws away the entire contents of the text buffer and
// leaves us ready to start again from scratch.

void CMainWindow::OnClear()
{
    VScrollPos = HScrollPos = 0;    // scroll thumbs start at zero
    leftMargin = 0;
    firstChar = currentChar = 0;    // empty text buffer
    firstLine = firstVisibleLine = currentLine = txtLine[0] = 0;
    currentLineLength = 0;
    selFirstPos = selStartPos = selEndPos = 0;
    trackingSelection = FALSE;
    ::ReleaseCapture();
    SetScrollPos(SB_HORZ, 0, TRUE);
    SetScrollPos(SB_VERT, 0, TRUE);
    Invalidate(TRUE);
}

void CMainWindow::FindMouseChar(int x, int y)
{
// Maybe the mouse could be outside my client area, since sometimes I
// capture the mouse.  Clip coordinates here to be on the safe side.
    if (y < 0) y = 0;
    else if (y > clientArea.Height()) y = clientArea.Height();
    y = y/charHeight + firstVisibleLine;
    if (y > currentLine) y = currentLine;
// Experiments with regular Windows "edit controls" seems to suggest
// that the rounding indicated here is what is expected.
    if (x < 0) x = 0;
    x = (x + charWidth/2)/charWidth + leftMargin;
    int len = txtLineLen[y & LINE_MASK];
    if (x > len) x = len;
    mousePos = MakePos(y, x);
}

void CMainWindow::OnSelectAll()
{
    selFirstPos = selStartPos = MakePos(firstLine, 0);
    selEndPos = MakePos(currentLine, currentLineLength);
// Even if everything was already selected I will invalidate the entire
// screen, since selecting everything seems uncommon and drastic.
    Invalidate(TRUE);
}

void CMainWindow::StartSelection()
{
    CancelSelection();
    selFirstPos = selStartPos = selEndPos = mousePos;
}

void CMainWindow::ExtendSelection()
{
    int oldStartPos = selStartPos;
    int oldEndPos = selEndPos;
    if (mousePos > selFirstPos)
    {   selStartPos = selFirstPos;
        selEndPos = mousePos;
    }
    else
    {   selStartPos = mousePos;
        selEndPos = selFirstPos;
    }
    if (oldStartPos == selStartPos &&
        oldEndPos == selEndPos) return;
// Working out what part of the screen needs to be invalidated here
// involves many more cases than I might have hoped...
    int first, last;
    if (oldEndPos == selStartPos)
    {   first = oldStartPos;
        last = selEndPos;
    }
    else if (selEndPos == oldStartPos)
    {   first = selStartPos;
        last = oldEndPos;
    }
    else if (oldEndPos == selEndPos)
    {   if (oldStartPos < selStartPos)
        {   first = oldStartPos;
            last = selStartPos;
        }
        else
        {   first = selStartPos;
            last = oldStartPos;
        }
    }
    else
    {   if (oldEndPos < selEndPos)
        {   first = oldEndPos;
            last = selEndPos;
        }
        else
        {   first = selEndPos;
            last = oldEndPos;
        }
    }
    RECT r;
    txtLineLen[currentLine & LINE_MASK] = currentLineLength;
    if (PosLine(first) == PosLine(last))
    {   r.left   = charWidth*(PosChar(first) - leftMargin);
        r.right  = charWidth*(PosChar(last) - leftMargin);
        r.top    = charHeight*(PosLine(first) - firstVisibleLine);
        r.bottom = r.top + charHeight;
        InvalidateRect(&r, TRUE);
    }
// When I have a bunch of short lines I go to the trouble to invalidate
// just the parts of them that contain real character data, and not the
// blank space at the end.
    else for (int i=PosLine(first); i<=PosLine(last); i++)
    {   r.left   = 0;
        r.right  = charWidth*txtLineLen[i & LINE_MASK];
        r.top    = charHeight*(i - firstVisibleLine);
        r.bottom = r.top + charHeight;
        InvalidateRect(&r, TRUE);
    }
}

void CMainWindow::CancelSelection()
{
    if (PosLine(selStartPos) != PosLine(selEndPos))
    {   RECT r;
        r.left = 0;
        r.right = charWidth*nCols;
        r.top = charHeight*(PosLine(selStartPos) - firstVisibleLine);
        r.bottom = charHeight*(PosLine(selEndPos) + 1 - firstVisibleLine);
        InvalidateRect(&r, TRUE);
    }
    else if (selStartPos != selEndPos)
    {   RECT r;
        r.left = charWidth*(PosChar(selStartPos) - leftMargin);
        r.right = charWidth*(PosChar(selEndPos) - leftMargin);
        r.top = charHeight*(PosLine(selStartPos) - firstVisibleLine);
        r.bottom = r.top + charHeight;
        InvalidateRect(&r, TRUE);
    }
    selFirstPos = selStartPos = selEndPos = 0;
}

void CMainWindow::OnLButtonDblClk(UINT nFlags, CPoint point)
{
// This should probably select a word
    OnLButtonDown(nFlags, point);
}

void CMainWindow::OnLButtonDown(UINT nFlags, CPoint point)
{
    cwin_unTop();
    FindMouseChar(point.x, point.y);
    trackingSelection = TRUE;
    SetCapture();
    if (nFlags & MK_SHIFT) ExtendSelection();
    else StartSelection();
}

void CMainWindow::OnLButtonUp(UINT nFlags, CPoint point)
{
    FindMouseChar(point.x, point.y);
    ::ReleaseCapture();
    if (trackingSelection) ExtendSelection();
    trackingSelection = FALSE;
}

//
// I make a click on the middle mouse button do "COPY" then "PASTE" which
// is a bit like the behaviour I have seen with X windows.  Well that is
// what this is intended to do, but the machine I have at home seems to ignore
// the mouse middle button, and GetSystemMetrics(SB_CMOUSEBUTTONS) returns 0.
// Anyway, CTRL+O does what the middle mouse button does, so no worry.
//

void CMainWindow::OnMButtonDblClk(UINT nFlags, CPoint point)
{
    OnMButtonDown(nFlags, point);
}

void CMainWindow::OnMButtonDown(UINT nFlags, CPoint point)
{
    cwin_unTop();
    OnMove();
}

void CMainWindow::OnMButtonUp(UINT nFlags, CPoint point)
{
}

void CMainWindow::OnRButtonDblClk(UINT nFlags, CPoint point)
{
    cwin_unTop();
}

// Dragging with the right mouse position always extends a selection,
// and it tried to be a tiny bit clever about which end of an existing
// selection it leaves anchored.

void CMainWindow::OnRButtonDown(UINT nFlags, CPoint point)
{
    cwin_unTop();
    FindMouseChar(point.x, point.y);
    int newFirst = selFirstPos;
    if (mousePos > selEndPos) newFirst = selStartPos;
    else if (mousePos < selStartPos) newFirst = selEndPos;
    trackingSelection = TRUE;
    SetCapture();
    if (selFirstPos != newFirst)
    {   CancelSelection();
         selStartPos = selEndPos = selFirstPos = newFirst;
    }
    ExtendSelection();
}

void CMainWindow::OnRButtonUp(UINT nFlags, CPoint point)
{
    FindMouseChar(point.x, point.y);
    ::ReleaseCapture();
    if (trackingSelection) ExtendSelection();
    trackingSelection = FALSE;
}

void CMainWindow::OnMouseMove(UINT nFlags, CPoint point)
{
    if (trackingSelection)
    {   FindMouseChar(point.x, point.y);
        ExtendSelection();
    }
}

// The next three functions are intended to make non-client hits on
// the main window demote the graphics window before they do their normal
// actions.

void CMainWindow::OnNcLButtonDown(UINT nFlags, CPoint point)
{
    cwin_unTop();
    CFrameWnd::OnNcLButtonDown(nFlags, point);
}

void CMainWindow::OnNcMButtonDown(UINT nFlags, CPoint point)
{
    cwin_unTop();
    CFrameWnd::OnNcMButtonDown(nFlags, point);
}

void CMainWindow::OnNcRButtonDown(UINT nFlags, CPoint point)
{
    cwin_unTop();
    CFrameWnd::OnNcRButtonDown(nFlags, point);
}

void CMainWindow::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar *pNctl)
{
    int w, oldFirst = firstVisibleLine;
// If all the text in the buffer is currently visible then I will ignore
// any attempts to scroll.
    if (currentLine - firstLine < nRows &&
        firstLine == firstVisibleLine) return;
// If I can make everything visible I will do that regardless of anything
// the user tries to do.
    if (currentLine - firstLine < nRows)
    {   firstVisibleLine = firstLine;
        VScrollPos = 0;
    }
    else
    {    switch (nSBCode)
        {
    case SB_THUMBPOSITION:
            w = currentLine - firstLine + 1 - nRows;
            if (nPos != 100) w = (nPos * w)/100;
            if (w == firstVisibleLine) return;   // no change!
            if (w < firstLine) w = firstLine;
            else if (w > currentLine - nRows + 1) w = currentLine - nRows + 1;
            firstVisibleLine = w;
            break;
    case PRIVATE_SET_VISIBLE_LINE:
            firstVisibleLine = nPos;
            break;
    case SB_LINEDOWN:
            if (currentLine - firstVisibleLine < nRows) return;
            firstVisibleLine++;
            break;
    case SB_LINEUP:
            if (firstLine == firstVisibleLine) return;
            firstVisibleLine--;
            break;
// When asked to scroll be a "page" I in fact jump by around 0.7 times
// the number of lines visible on a page.
    case SB_PAGEDOWN:
            if (currentLine - firstVisibleLine < nRows) return;
            w = firstVisibleLine + (7*nRows)/10;
            if (w > currentLine - nRows + 1) w = currentLine - nRows + 1;
            firstVisibleLine = w;
            break;
    case SB_PAGEUP:
            if (firstLine == firstVisibleLine) return;
            w = firstVisibleLine - (7*nRows)/10;
            if (w < firstLine) w = firstLine;
            firstVisibleLine = w;
            break;
        }
        VScrollPos = (firstVisibleLine * 100) /
                     (currentLine - firstLine + 1 - nRows);
        if (VScrollPos < 0) VScrollPos = 0;
        if (VScrollPos > 100) VScrollPos = 100;
    }
    SetScrollPos(SB_VERT, VScrollPos, TRUE);
    ScrollWindow(0, charHeight*(oldFirst - firstVisibleLine), NULL, clipArea);
}

void CMainWindow::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar *pNctl)
{
// Each time I hit the horizontal scroll-bar I will check to find how
// long the longest (partially) visible line is.
    int longest = 0, w, oldLeft = leftMargin;
    txtLineLen[currentLine & LINE_MASK] = currentLineLength;
    for (int i = firstVisibleLine;
         i<=currentLine & i < firstVisibleLine+nRows;
         i++)
        if (txtLineLen[i] > longest) longest = txtLineLen[i];
// If everything fits and I can see it all do nothing.
    if (longest <= nCols && leftMargin == 0) return;
// If I can make everything fit just scroll back and do that.
    if (longest <= nCols)
    {   leftMargin = 0;
        HScrollPos = 0;
    }
    else
    {   switch (nSBCode)
        {
    case SB_THUMBPOSITION:
            w = (nPos * (longest - nCols)) / 100;
            if (w == leftMargin) return;
            else if (w < 0) w = 0;
            else if (w > longest - nCols) w = longest - nCols;
            leftMargin = w;
            break;
    case PRIVATE_SET_LEFT_MARGIN:
            leftMargin = nPos;
            break;
    case SB_LINERIGHT:
            if (leftMargin >= longest - nCols) return;
            leftMargin++;
            break;
    case SB_LINELEFT:
            if (leftMargin == 0) return;
            leftMargin--;
            break;
// Big jumps are by roughly half the number of visible columns.
    case SB_PAGERIGHT:
            w = leftMargin + nCols/2;
            if (w > longest - nCols) w = longest - nCols;
            leftMargin = w;
            break;
    case SB_PAGELEFT:
            w = leftMargin - nCols/2;
            if (w < 0) w = 0;
            leftMargin = w;
            break;
        }
        HScrollPos = (leftMargin * 100) / (longest - nCols);
        if (HScrollPos < 0) HScrollPos = 0;
        if (HScrollPos > 100) HScrollPos = 100;
    }
    SetScrollPos(SB_HORZ, HScrollPos, TRUE);
    ScrollWindow(charWidth*(oldLeft - leftMargin), 0, NULL, clipArea);
}

// After the window has been re-sized OR after the font used has been
// changed I need to reconsider how much of the text is visible and
// where the scroll-bar thumbs are.  When I do this I am about to redraw
// the entire window anyway, so I do not need to re-state that fact.

void CMainWindow::AfterSizeChange()
{
// The clip area is my client area cut down to be a whole number of
// character cells high and wide.  It is used when scrolling so that I
// never put characters half-way off the edge of the client area.
    CRect newArea;
    newArea.top  = 0; newArea.bottom = nRows * charHeight;
    newArea.left = 0; newArea.right  = nCols * charWidth;
    CRect r;
    if (newArea.right > clipArea.right)
    {   r.top = newArea.top; r.bottom = newArea.bottom;
        r.left = clipArea.right; r.right = newArea.right;
        InvalidateRect(&r, TRUE);
    }
    if (newArea.bottom > clipArea.bottom)
    {   r.top = clipArea.bottom; r.bottom = newArea.bottom;
        r.left = newArea.left; r.right = newArea.right;
        InvalidateRect(&r, TRUE);
    }
    clipArea = newArea;
// I need to invalidate the margin between my clip area and the full client
// area, since otherwise that can be left filled with left-over parts of
// characters when I re-size my window.
    r.top = clientArea.top; r.bottom = clientArea.bottom;
    r.left = clipArea.right; r.right = clientArea.right;
    InvalidateRect(&r, TRUE);
    r.top = clipArea.bottom; r.bottom = clientArea.bottom;
    r.left = clientArea.left; r.right = clipArea.right;
    InvalidateRect(&r, TRUE);
// Maybe I have now invalidated enough?
    if (currentLine - firstLine < nRows)
    {   firstVisibleLine = firstLine;
        VScrollPos = 0;
    }
    else VScrollPos = (firstVisibleLine * 100) /
                      (currentLine - firstLine + 1 - nRows);
    if (VScrollPos < 0) VScrollPos = 0;
    if (VScrollPos > 100) VScrollPos = 100;
    SetScrollPos(SB_VERT, VScrollPos, TRUE);
    int longest = 0;
    txtLineLen[currentLine & LINE_MASK] = currentLineLength;
    for (int i = firstVisibleLine;
         i<=currentLine & i < firstVisibleLine+nRows;
         i++)
        if (txtLineLen[i] > longest) longest = txtLineLen[i];
    if (longest <= nCols)        // move to start of line if all will fit
    {   leftMargin = 0;
        HScrollPos = 0;
    }
    else
    {   int w = longest - nCols; // justify longest line to right of screen?
        if (w < leftMargin) leftMargin = w;
        HScrollPos = (leftMargin * 100) / w;
    }
    if (HScrollPos < 0) HScrollPos = 0;
    if (HScrollPos > 100) HScrollPos = 100;
    SetScrollPos(SB_HORZ, HScrollPos, TRUE);
}

static char file_select_buffer[256];

void CMainWindow::OnRead()
{
#ifdef FOR_ML
    CFileDialog FDialog(TRUE, "ML", "*.ML;*.SML",
        OFN_FILEMUSTEXIST | OFN_HIDEREADONLY,
        "ML Files (*.ML/*.SML)|*.ML;*.SML|All Files (*.*)|*.*||", NULL);
    if (FDialog.DoModal() != IDOK) return;
    AbandonClipboard();
    sprintf(file_select_buffer, "use \"");
    char *p = file_select_buffer+5;
    const char *p1 = FDialog.GetPathName();
    int ch, i=0;
    while ((ch = *p1++) != 0 && i < 252) // I truncate overlong filenames
    {   *p++ = ch, i++;                  // and I need to double '\\' chars,
        if (ch == '\\') *p++ = ch, i++;  // since otherwise ML gets unhappy.
    }
    *p++ = '"';
    *p++ = ';';
    *p = 0;
    clipboardInput = file_select_buffer;
    inputspec = TRUE;    
#endif
}

static void xputc(int c, FILE *f)
{
    c = c & 0xff;
#ifdef FOR_ML
    switch (c)
    {
case 0x80:
        c = ' '; break;      // prompt characters
case 0x81:
        c = '-'; break;
case 0x82:
        c = '='; break;
case 'l' | 0x80:             // "lambda" goes back to "fn"
        putc('f', f);
        c = 'n';
        break;
case 'V' | 0x80:             // special case on transcription of 'z
        putc('\'', f);
        c = 'z';
        break;
default:if (c & 0x80)        // Other Greek letters
        {   putc('\'', f);
            c = latinOf[(c & 0x7f) - 'a'];
        }
        break;
    }
#endif
    putc(c, f);
}

void CMainWindow::OnSaveas()
{
    CFileDialog FDialog(FALSE, "LOG", "SAVEFILE.LOG",
        OFN_HIDEREADONLY,
        "Log Files (*.LOG)|*.LOG|All Files (*.*)|*.*||", NULL);
    if (FDialog.DoModal() != IDOK) return;
    const char *p1 = FDialog.GetPathName();
    FILE *ofile = fopen(p1, "w");
    if (ofile == NULL)
    {   DisplayMsg("Could not write to file");
        return;
    }
    txtLineLen[currentLine & LINE_MASK] = currentLineLength;
    for (int i=firstLine; i<=currentLine; i++)
    {   int n = txtLineLen[i & LINE_MASK];
        char *l = &txtBuffer[txtLine[i & LINE_MASK]];
        for (int j=0; j<n; j++) xputc(*l++, ofile);
        putc('\n', ofile);
    }
    fclose(ofile);
}

void CMainWindow::OnSaveSel()
{
    CFileDialog FDialog(FALSE, "LOG", "SAVESEL.LOG",
        OFN_HIDEREADONLY,
        "Log Files (*.LOG)|*.LOG|All Files (*.*)|*.*||", NULL);
    if (FDialog.DoModal() != IDOK) return;
    const char *p1 = FDialog.GetPathName();
    FILE *ofile = fopen(p1, "w");
    if (ofile == NULL)
    {   DisplayMsg("Could not write to file");
        return;
    }
    txtLineLen[currentLine & LINE_MASK] = currentLineLength;
    int i = PosLine(selStartPos), j, n;
    char *l;
    if (PosLine(selEndPos) == i)
    {   n = PosChar(selEndPos);
        l = &txtBuffer[txtLine[i & LINE_MASK]];
        for (j=PosChar(selStartPos); j<n; j++) xputc(*l++, ofile);
        putc('\n', ofile);
    }
    else
    {   n = txtLineLen[i & LINE_MASK];
        l = &txtBuffer[txtLine[i & LINE_MASK]];
        for (j=PosChar(selStartPos); j<n; j++) xputc(*l++, ofile);
        putc('\n', ofile);
        i++;
        for (; i<PosLine(selEndPos); i++)
        {   n = txtLineLen[i & LINE_MASK];
            l = &txtBuffer[txtLine[i & LINE_MASK]];
            for (j=0; j<n; j++) xputc(*l++, ofile);
            putc('\n', ofile);
        }
        n = PosChar(selEndPos);
        l = &txtBuffer[txtLine[i & LINE_MASK]];
        for (j=0; j<n; j++) xputc(*l++, ofile);
        putc('\n', ofile);
    }
    fclose(ofile);
}

void CMainWindow::OnTofile()
{
#ifdef FOR_ML
// When I have a log file active I will make the menu entry into
// one that closes the transcript, while if I do not have a file in
// use the File/&T menu entry will start one off.
    if (log_file != NULL) 
    {   fclose(log_file);
        log_file = NULL;
        Menu.ModifyMenu(IDM_TOFILE+MF_STRING, MF_BYCOMMAND,
                        IDM_TOFILE, "&To File...");
        DrawMenuBar();
        return;
    }
    CFileDialog FDialog(FALSE, "LOG", "LOGFILE.LOG",
        OFN_HIDEREADONLY,
        "Log Files (*.LOG)|*.LOG|All Files (*.*)|*.*||", NULL);
    if (FDialog.DoModal() != IDOK) return;
    const char *p1 = FDialog.GetPathName();
    FILE *ofile = fopen(p1, "w");
    if (ofile == NULL)
    {   DisplayMsg("Could not write to file");
        return;
    }
    log_file = ofile; 
    Menu.ModifyMenu(IDM_TOFILE+MF_STRING, MF_BYCOMMAND,
                    IDM_TOFILE, "&Terminate log");
    DrawMenuBar();
#endif
}

void CMainWindow::OnPrint()
{
    SetWindowText("OnPrint");
}

void CMainWindow::OnPrintSetup()
{
    SetWindowText("OnPrintSetup");
}

void CMainWindow::OnExit()
{
// Since "EXIT" from the menu is a fairly simple way to get out I will
// obey all the user's registered exit functions.
    while (exitFunctionCount > 0) (*exitFunction[--exitFunctionCount])();
    returnCode = 0;
    DestroyWindow();
}

// "Undo" is provided on the menu partly as provocation.  Until and unless I
// understand what operations OUGHT to be undo-able I can not easily
// implement anything useful.

void CMainWindow::OnUndo()
{
}

// "Redraw" is provided so that if the screen gets scrambled for some
// reason (typically a bug in this code!) the user can go
//    ALT/E-R
// or select REDRAW from the EDIT menu and maybe get everything put
// back in a tidy state.

void CMainWindow::OnRedraw()
{
    Invalidate(TRUE);
    return;
}

// The TOP and BOTTOM items in the edit menu behave like the HOME and END
// keys on the keyboard, and just scroll the window to one or other extreme
// position.

void CMainWindow::OnTop()
{
    OnVScroll(SB_THUMBPOSITION, 0, NULL);
    OnHScroll(SB_THUMBPOSITION, 0, NULL);
    return;
}

void CMainWindow::OnBottom()
{
    if (currentLine - firstVisibleLine < nRows &&
        currentLine - firstVisibleLine >= nRows/2 &&
        currentLineLength - leftMargin < nCols) return;
    OnVScroll(PRIVATE_SET_VISIBLE_LINE, currentLine - nRows + 1, NULL);
    int left = currentLineLength - nCols + 1;
    OnHScroll(PRIVATE_SET_LEFT_MARGIN, left < 0 ? 0 : left, NULL);
    return;
}

BOOL CMainWindow::SetNewFont(CFont *newFont)
{
    CClientDC dc(this);
    dc.SelectObject(newFont);    // Be very certain that old one is not in use
// Since I have a fixed-pitch font it ought not to matter what character I
// measure here. I use "M" to stress that I want a maximum width & height.
// One could worry about fine distinctions between the several different
// measurement mechanisms that Windows provides, and in particular whether
// the size of a font is certain to be a neat whole number. I believe that
// for TrueType fonts that are fixed pitch the following MAY suffice.
    CSize charSize = dc.GetTextExtent("M", 1);
    TEXTMETRIC mainSize, symbolSize;
    if (!dc.GetTextMetrics(&mainSize)) return FALSE;
    CFont *newSymbolFont = new CFont();
    if (newSymbolFont == NULL) return FALSE;
    if (!newSymbolFont->CreateFont(
        mainSize.tmHeight, 0,
        0, 0, 
        mainSize.tmWeight, mainSize.tmItalic,
        0, 0, SYMBOL_CHARSET,
        OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS,
        DEFAULT_QUALITY, DEFAULT_PITCH+FF_DONTCARE, 
        "Symbol"))
    {   delete newSymbolFont;
        return FALSE;
    }
    LOGFONT logFont;
    newSymbolFont->GetObject(sizeof(logFont), &logFont);
    if (logFont.lfCharSet != SYMBOL_CHARSET)
    {   delete newSymbolFont;
        newSymbolFont = NULL;
    }
    else
    {   dc.SelectObject(newSymbolFont);
        if (!dc.GetTextMetrics(&symbolSize))
        {   delete newSymbolFont;
            return FALSE;
        }
        if (!dc.GetCharWidth(0, 255, symbolWidth))
        {   delete newSymbolFont;
            return FALSE;
        }
    }
    if (mainFont != NULL) delete mainFont;
    if (symbolFont != NULL) delete symbolFont;
    mainFont = newFont;
    symbolFont = newSymbolFont;
    charWidth  = charSize.cx;
    charHeight = charSize.cy;
    mainOffset = 0;
    if (symbolFont == 0) symbolOffset = 0;
    else symbolOffset = (mainSize.tmAscent - symbolSize.tmAscent);
// I adjust my "character height" to allow sufficient space for symbols
    int mainh = mainSize.tmAscent +
                mainSize.tmInternalLeading + mainSize.tmExternalLeading;
    int symh =  symbolFont == 0 ? mainh : symbolSize.tmAscent +
                symbolSize.tmInternalLeading + symbolSize.tmExternalLeading;
    int d = symh - mainh;
    if (d > 0)
    {   charHeight += d;
        mainOffset += d;
        symbolOffset += d;
    }
    if (symbolFont != 0)
    {   d = symbolSize.tmDescent - mainSize.tmDescent;
        if (d > 0) charHeight += d;
    }
    nRows = clientArea.Height() / charHeight;
    nCols = clientArea.Width()  / charWidth;
    AfterSizeChange();
    OnKillFocus(NULL); OnSetFocus(NULL);        // re-establish the caret
// If the user changes font I will re-draw the entire window.  This would
// be overkill if the new font was in fact identical to the old one, but
// I am not going to worry about that!
    Invalidate(TRUE);
    cwin_has_greek = symbolFont != 0;
#ifdef FOR_ML
    Menu.CheckMenuItem(IDM_GREEK, MF_BYCOMMAND |
       (cwin_has_greek ? MF_CHECKED : MF_UNCHECKED));
    Menu.EnableMenuItem(IDM_GREEK, MF_BYCOMMAND |
       (cwin_has_greek ? MF_ENABLED : MF_GRAYED));
#endif
    return TRUE;
}

#ifdef FOR_ML

void CMainWindow::OnGreek()
{
    if (symbolFont == 0) return;
    cwin_has_greek = !cwin_has_greek;
    Menu.CheckMenuItem(IDM_GREEK, MF_BYCOMMAND |
       (cwin_has_greek ? MF_CHECKED : MF_UNCHECKED));
}

#endif

void CMainWindow::OnFont()
{
    CFontDialog fontDialog;
    LOGFONT logFont;
    mainFont->GetObject(sizeof(logFont), &logFont);
    fontDialog.m_cf.Flags = CF_SCREENFONTS | CF_FIXEDPITCHONLY |
                            CF_ANSIONLY | CF_INITTOLOGFONTSTRUCT;
    fontDialog.m_cf.lpLogFont = &logFont;       // default for selection
// I will keep trying to obtain fonts from the user until I find one that
// can be properly installed.  Maybe it is unkind, but if a font as selected
// from the dialog box fails to be created I just restart the dialog without
// further explanation.
    for (;;)
    {   int w = fontDialog.DoModal();
        if (w == IDCANCEL) break;
        else if (w != IDOK) continue;
        LOGFONT *lf = fontDialog.m_cf.lpLogFont;
        CFont *newFont = new CFont();
        if (newFont != NULL &&
            lf != NULL &&
            newFont->CreateFont(
                lf->lfHeight, 0,
                0, 0, 
                lf->lfWeight, lf->lfItalic,
                0, 0, ANSI_CHARSET,
                OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS,
                DEFAULT_QUALITY, FIXED_PITCH+FF_DONTCARE, 
                lf->lfFaceName) != 0 &&
            SetNewFont(newFont)) break;
    }
}

DLLexport int cwin_interrupt_pending = 0;

void CMainWindow::OnInterrupt()
{
    cwin_interrupt_pending = 1;
    SetWindowText("OnInterrupt");
}

void CMainWindow::OnBacktrace()
{
    cwin_interrupt_pending = 3;
    SetWindowText("OnBacktrace");
}

void CMainWindow::OnGShow()
{
    if (thePicture == NULL) return;
    thePicture->ShowWindow(SW_SHOW);
    cwin_show();
    thePicture->UpdateWindow();
}

void CMainWindow::OnGHide()
{
    if (thePicture == NULL) return;
    thePicture->ShowWindow(SW_HIDE);
    thePicture->isOnTop = FALSE;
}

void CMainWindow::OnGClear()
{
    if (thePicture == NULL) return;
    thePicture->Clear();
}

void myhelp(HWND m_hWnd, UINT type, DWORD data)
// This invoked the Microsoft Help Engine.  It indicates that the
// help file to be scanned is xxx.hlp where xxx.exe was the executable
// image for this application (including its full path name). If the
// full path name seems to be damaged in some what I cop out and use the
// fixed file name "cwin.hlp" which will probably not do the user much
// good. But the Help Engine will report that it is not available and provides
// some opportunity for browsing to find an alternative.
{
    int len = strlen(cwin_full_program_name);
    char *helpfile = (char *)malloc(len+1);
    if (helpfile == NULL) ::WinHelp(m_hWnd, "cwin.hlp", type, data);
    else
    {   strcpy(helpfile, cwin_full_program_name);
        len -= 4;
        if (len > 0 &&
            helpfile[len] == '.' &&
            toupper(helpfile[len+1]) == 'E' &&
            toupper(helpfile[len+2]) == 'X' &&
            toupper(helpfile[len+3]) == 'E') strcpy(&helpfile[len], ".HLP");
        else strcpy(helpfile, "cwin.hlp");
        ::WinHelp(m_hWnd, helpfile, type, data);
        free(helpfile);
    }
}

void CMainWindow::OnHelpContents()               // Start on contents page.
{
    myhelp(m_hWnd, HELP_CONTENTS, 0);
}

void CMainWindow::OnHelp()
{
    myhelp(m_hWnd, HELP_PARTIALKEY, (DWORD)"");  // Search through keywords.
}

void CMainWindow::OnHelpUsing()                  // "Help About Help".
{
    myhelp(m_hWnd, HELP_HELPONHELP, 0);
}

void CMainWindow::OnAbout()
{
    DoAboutBox();                       // I create the box in memory so I
                                        // do not need amuch of a resoure file
}

void CMainWindow::OnSetFocus(CWnd *pOldWnd)
{
    CreateSolidCaret(2*GetSystemMetrics(SM_CXBORDER), charHeight);
    CPoint cp;
    cp.x = charWidth*(currentLineLength - leftMargin);
    cp.y = charHeight*(currentLine - firstVisibleLine);
    SetCaretPos(cp);
    ShowCaret();
}

void CMainWindow::OnKillFocus(CWnd *pOldWnd)
{
    ::DestroyCaret();
}

void CMainWindow::OnSize(UINT nType, int cx, int cy)
{
    if (charWidth == 0) return;      // This is caution in case OnSize gets
    CRect newArea;                   // called before a font has been created.
    GetClientRect(&newArea);
    nRows = newArea.Height() / charHeight;
    nCols = newArea.Width() / charWidth;
    CRect r;
    if (newArea.right > clientArea.right)
    {   r.top = newArea.top; r.bottom = newArea.bottom;
        r.left = clientArea.right; r.right = newArea.right;
        InvalidateRect(&r, TRUE);
    }
    if (newArea.bottom > clientArea.bottom)
    {   r.top = clientArea.bottom; r.bottom = newArea.bottom;
        r.left = newArea.left; r.right = newArea.right;
        InvalidateRect(&r, TRUE);
    }
    clientArea = newArea;
    AfterSizeChange();
    ReWriteTitleText();
    OnBottom();
    if (nType == SIZE_MINIMIZED) OnGHide(); // Hide graphics
}

// end of "cwin.cpp"
