File r36/cslbase/c_text.cpp artifact 8ae004cfe5 part of check-in 3af273af29


// c_text.cpp
//     Support for a window that can support regular, bold, italic and symbol
//     fonts each in large & small, plus the ability to select the size of
//     characters to be used.
//
//
//     See accompanying file "cwin.tex" for an explanation of the policy
//     for scrolling, text selection and treatment of buffer over-full
//     conditions.
//
//     Copyright (C)/©/¸ Codemist Ltd, 1995-99

/* Signature: 7f4001bb 07-Mar-2000 */


#include "cwin.hpp"

// There are times when this code wants to indicate to the user's
// application that it should terminate.  If such a request has been
// raised the macro exception_pending() given here will evaluate to true,
// but the way that this happens is specific the the CSL/CCL Lisp system
// and thus is not exactly generic.

extern "C" {
extern int C_nil;
extern FILE *spool_file;
extern void inject_randomness(int);
}

#define exception_pending() ((C_nil&1) != 0)

static BOOL hasFinished = FALSE;

CMainWindow::CMainWindow()
{
    hasFinished = FALSE;
    complete = FALSE;
    char *menuName = "MainMenu";
    HINSTANCE hInst = AfxFindResourceHandle(menuName, RT_MENU);
    HMENU hMenu = ::LoadMenu(hInst, menuName);
    this->CreateEx(WS_EX_ACCEPTFILES,
                   mainWindowClass, "",
                   WS_OVERLAPPEDWINDOW | WS_HSCROLL | WS_VSCROLL,
                   CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
                   NULL, hMenu);

// Now I will add the dynamic menu items that I need...
// I demand that the "help" be the rightmost one on the menu bar
    HMENU helpMenu = ::GetSubMenu(hMenu, ::GetMenuItemCount(hMenu)-1);
    int i;
    theApp.dynamicCount = 0;
// Insert extra help menu items here. I expect that the registry will
// contain a subkey called HelpItems which is an integer, and then
// a bunch of strings takked as T000/P000, T001/P001 etc where the Tiii are
// strings to place on the menu and the Piii are paths to relevant help
// files that can be opened.
    int helpCount = theApp.GetProfileInt("HelpItems", "HowMany", 0);
#ifdef MAYBE
    int helpCount = theApp.GetProfileInt("HelpItems", "HowMany", -1);
    if (helpCount < 0)
    {   theApp.dynamic[0] = "&Contents"
        theApp.dynamic_files[0] = "???";
        theApp.dynamic[1] = "&Search for Help"
        theApp.dynamic_files[1] = "???";
        helpCount = 2;
        WriteProfileInt("HelpItems", "HowMany", 2);
        WriteProfileString("HelpItems", "T000", theApp.dynamic[0]);
        WriteProfileString("HelpItems", "P000", theApp.dynamic_files[0]);
        WriteProfileString("HelpItems", "T001", theApp.dynamic[1]);
        WriteProfileString("HelpItems", "P001", theApp.dynamic_files[1]);
    }
    else
#endif
    {   if (helpCount >= IDM_LAST_DYNAMIC-IDM_DYNAMIC_ITEMS)
            helpCount = IDM_LAST_DYNAMIC-IDM_DYNAMIC_ITEMS-1;
        for (i=0; i<helpCount; i++)
        {   char tag[8];
            sprintf(tag, "T%.3d", i);
            CString key(theApp.GetProfileString("HelpItems", tag, ""));
            sprintf(tag, "P%.3d", i);
            CString path(theApp.GetProfileString("HelpItems", tag, ""));
            if (key.GetLength() != 0 && path.GetLength() != 0)
            {   char *m;
                m = (char *)malloc(1+key.GetLength());
                strcpy(m, LPCTSTR(key));
                theApp.dynamic[theApp.dynamicCount] = m;
                m = (char *)malloc(1+path.GetLength());
                strcpy(m, LPCTSTR(path));
                theApp.dynamic_files[theApp.dynamicCount++] = m;
            }
        }
    }
// Now place them on the menu
    for (i=0; i<theApp.dynamicCount; i++)
        ::InsertMenu(helpMenu, IDM_HELP_ON_HELP, MF_BYCOMMAND,
                             IDM_DYNAMIC_ITEMS+i, theApp.dynamic[i]);

// typeAheadBuffer is a smallish buffer and when the user presses a key the
// resulting character is put there. No echoing is performed. Use of DEL
// can discard a previously-typed character and an attempt to over-fill the
// buffer before other parts of the code have emptied it will cause a BEEP.
//
    typeAheadP1 = typeAheadP2 = 0;              // pointers into typeAheadBuffer
// inputBuffer holds stuff that the user has accepted (by typing a newline)
// but that the program has not yet read. inputP shows where the next
// character should be read from it. The end of the buffer is marked with
// a newline. If inputP<0 the buffer is empty.
    inputP = -1;
// textBuffer is a large buffer just containing characters. textFirst
// points to the first active character and textLast to one after the last
// active character. Both pointers are kept masked with TEXT_MASK which
// wraps them around to give a circular buffer.
    textFirst = textLast = 0;           // main text buffer, now empty
    caretChar = 0;                      // char before which caret is shown
    caretLine = 0; caretX = 0;
    icaretChar = icaretLine = icaretX = 0;
    caretVisible = TRUE;
    caretFontWidths = windowFonts.HCourier.across;
    icaretFontWidths = windowFonts.HCourier.across;
    endFontWidths = windowFonts.HCourier.across;
    endX = 0;
    xOffset = 0;                        // Not horizontally scrolled.
    inputLineStart = -1;
    caretWidth = 2*GetSystemMetrics(SM_CXBORDER);

    clipboardInput = NULL;
    insertMode = TRUE;

    savedP1 = savedP2 = savedFirst = savedLast = 0;
    currentInputLine = -1;

    pageLine = 0;
    pageMode = pagePaused = FALSE;

    selFirstChar = selStartChar = selEndChar = 0;
    selFirstX = selStartX = selEndX = 0;
    selFirstLine = selStartLine = selEndLine = 0;
    trackingSelection = FALSE;
    selRootValid = FALSE;
    hasFocus = FALSE;

// The characters in textBuffer are organised into lines. The start of each
// line is recorded in lineBuffer, which is also a circular buffer so the
// pointers into it are kept reduced using LINE_MASK. lineFirst identified
// the first line stored.  lineLast identifies the last line. Note that
// the data making up a single line runs from where lineBuffer points either
// to where a termination character is found in the text (this will be
// a newline character) or until textLast. The latter case arises only for
// a final non-terminated line of text.  In general there will not be room
// on the screen to display all the lines of text that are present.
// lineVisible identifies the first line that can be displayed.
//
    lineFirst = lineVisible = 0;        // line buffer, contains one (empty)
    lineLast = 0;                       // line
    lineBuffer[0].position = 0;
    lineBuffer[0].up = 000;
    lineBuffer[0].height = 000;         // get filled in when fonts are created
    lineBuffer[0].width = 000;          // gets filled in when line painted.
    lineBuffer[0].address = textFirst;

    cLeft[0] = cRight[0] = 0;
    strncpy(cMid, programName, 31); cMid[31] = 0;
    GetSystemTime(&titleUpdateTime);
    inject_randomness((int)titleUpdateTime.wMilliseconds);
    cwin_display_date();

    strcpy(cwin_prompt_string, "> ");   // A default prompt string.

    windowColour = GetSysColor(COLOR_WINDOW);
    textColour = GetSysColor(COLOR_WINDOWTEXT);
    highlightColour = GetSysColor(COLOR_HIGHLIGHT);
    highlightTextColour = GetSysColor(COLOR_HIGHLIGHTTEXT);

#ifdef GRAPHICS_WINDOW
    graphicsShown = FALSE;
    graphicsWindow = new CGraphicsWindow;
    graphicsWindow->ShowWindow(SW_HIDE);
#endif
    helpShown = FALSE;
    helpWindow = new CHelpWindow;

    CClientDC dc(helpWindow);
    dc.SelectObject(&helpWindow->helpFont);
    TEXTMETRIC mm;
    dc.GetTextMetrics(&mm);
    helpWindow->height = mm.tmExternalLeading + mm.tmAscent + mm.tmDescent;
    int widths[4];
    dc.GetCharWidth('X', 'X', widths);
    helpWindow->width = widths[0];

    WINDOWPLACEMENT wp;
    helpWindow->GetWindowPlacement(&wp);
    int screenWidth = GetSystemMetrics(SM_CXSCREEN);
    RECT *wr = &wp.rcNormalPosition;
    int left = wr->left;
    int cwidth = 80*helpWindow->width +
                 3*GetSystemMetrics(SM_CXBORDER) +
                 2*GetSystemMetrics(SM_CXFRAME) + 5;
// Try to get the whole window onto the screen.
    if (left + cwidth > screenWidth)
    {   left = screenWidth - cwidth;
        if (left < 0) left = 0;
    }
    int screenHeight = GetSystemMetrics(SM_CYSCREEN);
    int top = wr->top;
    int cheight = 25*helpWindow->height +
                   GetSystemMetrics(SM_CYCAPTION) +
                   GetSystemMetrics(SM_CYMENU) +
                   3*GetSystemMetrics(SM_CYBORDER) +
                   2*GetSystemMetrics(SM_CYFRAME) + 5;
    if (top + cheight > screenHeight)
    {   top = screenHeight - cheight;
        if (top < 0) top = 0;
    } 
    helpWindow->SetWindowPos(NULL, 
       top, left,
       cwidth, cheight,
       SWP_NOACTIVATE | SWP_NOREDRAW | SWP_NOZORDER);

    helpWindow->ShowWindow(SW_HIDE);
    myTimer = SetTimer(1, 50, NULL);   // 20 ticks per second -> message queue
    cwin_interrupt_pending = 0;
    complete = TRUE;
}

CMainWindow::~CMainWindow()
{
}

void CMainWindow::OnDestroy()
{
#ifdef GRAPHICS_WINDOW
    graphicsWindow->viewpointWindow.DestroyWindow();
    graphicsWindow->DestroyWindow();
//  delete graphicsWindow;
#endif
    if (myTimer!=0) KillTimer(myTimer);
    windowFonts.DeleteFonts();
    hasFinished = TRUE;
}

BEGIN_MESSAGE_MAP(CMainWindow, CFrameWnd)

    ON_WM_DESTROY()
    ON_WM_CHAR()
    ON_WM_HSCROLL()
    ON_WM_KEYDOWN()
    ON_WM_KILLFOCUS()
    ON_WM_LBUTTONDBLCLK()
    ON_WM_LBUTTONDOWN()
    ON_WM_LBUTTONUP()
    ON_WM_MBUTTONDBLCLK()
    ON_WM_MBUTTONDOWN()
    ON_WM_MBUTTONUP()
    ON_WM_MOUSEMOVE()
//  ON_WM_NCLBUTTONDOWN()
//  ON_WM_NCMBUTTONDOWN()
//  ON_WM_NCRBUTTONDOWN()
    ON_WM_PAINT()
    ON_WM_TIMER()
    ON_WM_RBUTTONDBLCLK()
    ON_WM_RBUTTONDOWN()
    ON_WM_RBUTTONUP()
    ON_WM_SETFOCUS()
    ON_WM_SIZE()
    ON_WM_MOVE()
    ON_WM_VSCROLL()


// Now things on the menus...

    ON_COMMAND(IDM_READ,           OnRead)
    ON_COMMAND(IDM_SAVEAS,         OnSaveAs)
    ON_COMMAND(IDM_SAVESEL,        OnSaveSel)
    ON_COMMAND(IDM_PRINT,          OnPrint)
    ON_COMMAND(IDM_PRINTSEL,       OnPrintSel)
    ON_COMMAND(IDM_TOFILE,         OnToFile)
    ON_COMMAND(IDM_EXIT,           OnExit)

    ON_COMMAND(IDM_CUT,            OnCut)
    ON_COMMAND(IDM_COPY,           OnCopy)
    ON_COMMAND(IDM_PASTE,          OnPaste)
    ON_COMMAND(IDM_REINPUT,        OnReInput)
    ON_COMMAND(IDM_SELECTALL,      OnSelectAll)
    ON_COMMAND(IDM_CLEAR,          OnClear)
//  ON_COMMAND(IDM_UNDO,           OnUndo)
    ON_COMMAND(IDM_REDRAW,         OnRedraw)
    ON_COMMAND(IDM_HOME,           OnHome)
    ON_COMMAND(IDM_END,            OnEnd)
    ON_COMMAND(IDM_FONT,           OnFont)
    ON_COMMAND(IDM_RESET_FONT,     OnResetFont)
    ON_COMMAND(IDM_RESET_WINDOW,   OnResetWindow)

    ON_COMMAND(IDM_INTERRUPT,      OnInterrupt)
    ON_COMMAND(IDM_BACKTRACE,      OnBacktrace)
    ON_COMMAND(IDM_PAGEMODE,       OnPageMode)

#ifdef GRAPHICS_WINDOW
    ON_COMMAND(IDM_GRAPHICS,       OnGraphics)
    ON_MESSAGE(WM_USER,            OnGraphics1)
#endif

#ifndef COMMON
    ON_COMMAND_RANGE(IDM_FIRSTLOAD, IDM_FIRSTLOAD+199, OnLoadLibrary)
    ON_COMMAND_RANGE(IDS_FIRSTSWITCH, IDS_FIRSTSWITCH+199, OnSwitch)
#endif

END_MESSAGE_MAP()


// Message handlers...


#define caretY  (lineBuffer[caretLine].position - \
                 lineBuffer[lineVisible].position)
#define caretDY (lineBuffer[caretLine].height)
#define caretY1 (caretY+caretDY)

void CMainWindow::OnPaint()
{
    RECT update;
    inject_randomness((int)clock());
    if (!GetUpdateRect(&update, TRUE)) return;
    CPaintDC dc(this);
    dc.SetTextAlign(TA_BASELINE);
    dc.SetTextColor(RGB(0,0,0));          currentColour = CH_BLACK;
    dc.SetBkColor(windowColour);
    dc.SelectObject(windowFonts.Courier); currentFont = CH_COURIER;
    currentWidths = windowFonts.HCourier.across;
    int yBase = lineBuffer[lineVisible].position, line;
    HideCaret();
    for (line=lineVisible;; line=(line+1)&LINE_MASK)
    {   int y = lineBuffer[line].position - yBase;
        int y1 = y+lineBuffer[line].up;
        int y2 = y+lineBuffer[line].height;
        if (y2 >= update.top)
            lineBuffer[line].width =
                PaintTextLine(&dc, -xOffset, y, y1, y2,
                              lineBuffer[line].address,
                              clientWidth, &windowFonts, 0);
        if (y > update.bottom+lineBuffer[line].up) break;
        if (line==lineLast) break;
    }
    ShowCaret();
    dc.SelectStockObject(SYSTEM_FONT);
// Now I will worry about positioning the horizontal scroll thumb.
    int longestLine = 0;
    for (line=lineVisible;; line=(line+1)&LINE_MASK)
    {   int y = lineBuffer[line].position - yBase;
        if (y > clientHeight) break;
        int w = lineBuffer[line].width;
        if (w > longestLine) longestLine = w;
        if (line==lineLast) break;
    }
    if (longestLine <= clientWidth)
    {   if (xOffset != 0)
        {
// Mess here - my response is to force the X offset to zero
// and scroll the window to match - that will generally force a fresh
// painting operation.  This sort of thing can happen when there was a
// long line in the buffer and the window had been scrolled right to view it,
// and then by a sequence of vertical scrolling operations that line is
// no longer on the screen and all the lines that are visible are short enough
// to be shown in their entirity.  It can also happen when somebody
// increases the size of the window.
            ScrollWindow(xOffset, 0, NULL, NULL);
            xOffset = 0;
            SetScrollPos(SB_HORZ, 0, TRUE);
        }
    }
    else
    {   int HScrollPos = (100*xOffset)/(longestLine-clientWidth/2);
// If (for instance) the font had just cahnged or the windows size had been
// enlarged you might have too little left visible on the screen - in such
// cases I will forcibly scroll to correct things.
        if (HScrollPos > 100)
        {   ScrollWindow(xOffset-longestLine+clientWidth/2, 0, NULL, NULL);
            xOffset = longestLine-clientWidth/2;
            HScrollPos = 100;
        }
        SetScrollPos(SB_HORZ, HScrollPos, TRUE);
    }
    CPoint caretPos(caretX-xOffset, caretY);
    SetCaretPos(caretPos);
}

// a, b and c are pointers into the circular text buffer, with a<=c
// (logically). Test is a<=b<c.  The mess here is because of wrap-
// around with TEXT_MASK.

BOOL betweenChar(int a, int b, int c)
{
    if (a<=c) return (a<=b && b<c);
    else return (a<=b || b<c);
}

// Ditto but relative to the line buffer. Observe that the code I have is
// identical, but I still like to keep the abstractions separate.

BOOL betweenLine(int a, int b, int c)
{
    if (a<=c) return (a<=b && b<c);
    else return (a<=b || b<c);
}

int CMainWindow::PaintTextLine(CDC *dc, int x,
                               int topY, int y, int bottomY, int textChar,
                               int width, FontArray *ff, int context)
{
// context = 0     painting to screen
//         = 1     printing whole of buffer
//         = 2     just printing selected region
    int nCount = 0;
    int needFont = (currentFont != CH_COURIER);
    int needColour = (currentColour != CH_BLACK);
// I will buffer up to 80 characters of fixed-pitch output and display them
// using a single call to TextOut. This is intended to reduce the cost of
// calling the operating system for output.
    char buff[80];
    int bufp = 0, bufx = 0, bufy = 0;
    for (;;textChar=(textChar+1)&TEXT_MASK)
    {
// I reset the X coordinate relating to the root, start and end of
// any selection. This is necessary (for instance) after a font change,
// and also means I do not have to re-calculate the X offsets when I am
// inserting characters into the middle of the buffer.
        if (context == 0)
        {   if (textChar == selFirstChar) selFirstX = x+xOffset;
            if (textChar == selStartChar) selStartX = x+xOffset;
            if (textChar == selEndChar) selEndX = x+xOffset;
// Just after I have deleted a character (or two) I can have repositioned
// the character but I will have lost track of the font active at its new
// position, and thus of the font metrics, and the X coordinate that the
// caret will be at. So although when inserting characters into the buffer
// I can keep track of font & position I reset them here so that a
// Paint operation following a delection puts things back in a coherent
// state.
            if (textChar==caretChar)
            {   caretX = x+xOffset;
                caretFontWidths = currentWidths;
            }
            if (textChar==icaretChar)
            {   icaretX = x+xOffset;
                icaretFontWidths = currentWidths;
            }
// Ditto for the end of the text buffer.
            if (textChar==textLast)
            {   endX = x+xOffset;
                endFontWidths = currentWidths;
                break;
            }
        }
        else if (textChar==textLast) break;
// I will stop if I either find a newline character or I reach the end
// of the buffer. In each case I will have sorted out how long the line
// I was displaying was.
        int c = textBuffer[textChar];
// I permit ESC characters in the buffer, but I will display them as $
// following an ancient tradition. Other control characters are not really
// permitted and could lead to mangled display.
        if (c == 0x1b) c = '$';
        int w = currentWidths[c & 0xff];
        BOOL inSelection = betweenChar(selStartChar, textChar, selEndChar);
        if (context==0 && inSelection &&
            (currentColour==CH_BLACK || currentColour==CH_HIGHLIGHT))
        {   needColour = FALSE;
            currentColour = CH_HIGHLIGHT;
            if (bufp != 0) dc->TextOut(bufx, bufy, buff, bufp), bufp=0;
            dc->SetTextColor(highlightTextColour);
            dc->SetBkColor(highlightColour);
        }
        else if (!inSelection)
        {
// If I am outside the selected region and context==2 (printing selection)
// then I map characters onto blanks so thet they will not appear in the
// output.
            if (context == 2) c = ' ';
            if (currentColour==CH_HIGHLIGHT)
            {   needColour = FALSE;
                currentColour = CH_BLACK;
                if (bufp != 0) dc->TextOut(bufx, bufy, buff, bufp), bufp=0;
                dc->SetTextColor(RGB(0,0,0));
                dc->SetBkColor(windowColour);
            }
        }
        switch (c & 0xff)
        {
    case '\n':
            break;
    case CH_RED:
            needColour = FALSE;
            if (currentColour == c) continue;
            currentColour = c;
            if (bufp != 0) dc->TextOut(bufx, bufy, buff, bufp), bufp=0;
            dc->SetTextColor(RGB(255,0,0));
            dc->SetBkColor(windowColour);
            continue;
    case CH_BLUE:
            needColour = FALSE;
            if (currentColour == c) continue;
            currentColour = c;
            if (bufp != 0) dc->TextOut(bufx, bufy, buff, bufp), bufp=0;
            dc->SetTextColor(RGB(0,0,255));
            dc->SetBkColor(windowColour);
            continue;
    case CH_BLACK:
            if (inSelection && context==0)
            {   c = CH_HIGHLIGHT;
                needColour = FALSE;
                if (currentColour == c) continue;
                currentColour = c;
                if (bufp != 0) dc->TextOut(bufx, bufy, buff, bufp), bufp=0;
                dc->SetTextColor(highlightTextColour);
                dc->SetBkColor(highlightColour);
            }
            else
            {   needColour = FALSE;
                if (currentColour == c) continue;
                currentColour = c;
                if (bufp != 0) dc->TextOut(bufx, bufy, buff, bufp), bufp=0;
                dc->SetTextColor(RGB(0,0,0));
                dc->SetBkColor(windowColour);
            }
            continue;
    case CH_GRAY:
            needColour = FALSE;
            if (currentColour == c) continue;
            currentColour = c;
            if (bufp != 0) dc->TextOut(bufx, bufy, buff, bufp), bufp=0;
            dc->SetTextColor(RGB(128,128,128));
            dc->SetBkColor(windowColour);
            continue;
    case CH_COURIER:
            needFont = FALSE;
            if (currentFont == c) continue;
            currentFont = c;
            currentWidths = ff->HCourier.across;
            dc->SelectObject(ff->Courier);
            continue;
    case CH_ROMAN:
            needFont = FALSE;
            if (currentFont == c) continue;
            currentFont = c;
            currentWidths = ff->HRoman.across;
            if (bufp != 0) dc->TextOut(bufx, bufy, buff, bufp), bufp=0;
            dc->SelectObject(ff->Roman);
            continue;
    case CH_BOLD:
            needFont = FALSE;
            if (currentFont == c) continue;
            currentFont = c;
            currentWidths = ff->HBold.across;
            if (bufp != 0) dc->TextOut(bufx, bufy, buff, bufp), bufp=0;
            dc->SelectObject(ff->Bold);
            continue;
    case CH_ITALIC:
            needFont = FALSE;
            if (currentFont == c) continue;
            currentFont = c;
            currentWidths = ff->HItalic.across;
            if (bufp != 0) dc->TextOut(bufx, bufy, buff, bufp), bufp=0;
            dc->SelectObject(ff->Italic);
            continue;
    case CH_SYMBOL:
            needFont = FALSE;
            if (currentFont == c) continue;
            currentFont = c;
            currentWidths = ff->HSymbol.across;
            if (bufp != 0) dc->TextOut(bufx, bufy, buff, bufp), bufp=0;
            dc->SelectObject(ff->Symbol);
            continue;
    case CH_Roman:
            needFont = FALSE;
            if (currentFont == c) continue;
            currentFont = c;
            currentWidths = ff->Hroman.across;
            if (bufp != 0) dc->TextOut(bufx, bufy, buff, bufp), bufp=0;
            dc->SelectObject(ff->roman);
            continue;
    case CH_Bold:
            needFont = FALSE;
            if (currentFont == c) continue;
            currentFont = c;
            currentWidths = ff->Hbold.across;
            if (bufp != 0) dc->TextOut(bufx, bufy, buff, bufp), bufp=0;
            dc->SelectObject(ff->bold);
            continue;
    case CH_Italic:
            needFont = FALSE;
            if (currentFont == c) continue;
            currentFont = c;
            currentWidths = ff->Hitalic.across;
            if (bufp != 0) dc->TextOut(bufx, bufy, buff, bufp), bufp=0;
            dc->SelectObject(ff->italic);
            continue;
    case CH_Symbol:
            needFont = FALSE;
            if (currentFont == c) continue;
            currentFont = c;
            currentWidths = ff->Hsymbol.across;
            if (bufp != 0) dc->TextOut(bufx, bufy, buff, bufp), bufp=0;
            dc->SelectObject(ff->symbol);
            continue;
    default:
// The start of every line should be in regular-sized Courier and
// in black. But rather than calling Windows to force that selection
// on every line (I hypothesize that selecting a fond and a colour
// into my device context may be costly) I only do so when I find I have
// a character to display and then only in cases where what was left over
// from whatever was last printed was somehow different.
            if (context==0 && needColour && inSelection)
            {   if (bufp != 0) dc->TextOut(bufx, bufy, buff, bufp), bufp=0;
                dc->SetTextColor(highlightTextColour);
                dc->SetBkColor(highlightColour);
                currentColour = CH_BLUE;
                needColour = FALSE;
            }
            else if (needColour)
            {   if (bufp != 0) dc->TextOut(bufx, bufy, buff, bufp), bufp=0;
                dc->SetTextColor(RGB(0,0,0));
                dc->SetBkColor(windowColour);
                currentColour = CH_BLACK;
                needColour = FALSE;
            }
            if (needFont)
            {   dc->SelectObject(ff->Courier);
                currentFont = CH_COURIER;
                currentWidths = ff->HCourier.across;
                needFont = FALSE;
            }
            if (c == '\t')
            {   c = ' ';
                w = currentWidths[c]*(8-(nCount&7));
                nCount = 7;
            }
            else if (c < 32)
            {   w = currentWidths['^'];
// There is a curiosity here, that for the moment I am going to leave alone.
// If one has a control character in the buffer it is displayed as something
// like ^I (with the '^' as a separate character and the 'I' a normal letter).
// If this is within a selection the 'I' is shown in inverse video as usual
// but the '^' is not. This could perhaps be seen as a way of making it
// possible to distinguish between the control character and a regular pair
// of characters spelt the same.
                if (x+w>=0 && x<width)
                {   if (bufp>=sizeof(buff))
                        dc->TextOut(bufx, bufy, buff, bufp), bufp=0;
                    if (bufp == 0) bufx = x, bufy = y;
                    buff[bufp++] = '^';
                }
                x += currentWidths['^'];
                c = c | 0x40;
                w = currentWidths[c & 0xff];
                nCount++;
            }
            if (inSelection && context==0)
            {   CBrush b(highlightColour);
                CBrush *oldbrush = dc->SelectObject(&b);
                if (bufp != 0) dc->TextOut(bufx, bufy, buff, bufp), bufp=0;
                dc->SelectStockObject(NULL_PEN);
                dc->Rectangle(x, topY, x+w+1, bottomY+1);
                dc->SelectObject(oldbrush);
            }
            if (x+w>=0 && x<width)
            {   if (currentFont == CH_COURIER)
                {   if (bufp>=sizeof(buff))
                        dc->TextOut(bufx, bufy, buff, bufp), bufp = 0;
                    if (bufp == 0) bufx = x, bufy = y;
                    buff[bufp++] = c;
                }
                else
                {   if (bufp != 0) dc->TextOut(bufx, bufy, buff, bufp), bufp=0;
                    buff[0] = c;
                    dc->TextOut(x, y, buff, 1);
                }
            }
            x += w;
            nCount++;
            continue;
        }
        break;
    }
    if (bufp != 0) dc->TextOut(bufx, bufy, buff, bufp);
// I am NOT happy about the next few lines -- they fill an area in background
// colour out across to the end of the line. I do this because otherwise
// despite lots of calls to HideCaret I seem to observe remains of the caret
// displayed after I have made a line shorter. I do not understand why this
// happens but hope that what follows will work around the problem.
    if (context==0)
    {   CBrush b(windowColour);
        CBrush *oldbrush = dc->SelectObject(&b);
        dc->SelectStockObject(NULL_PEN);
        dc->Rectangle(x, topY,
                      width, bottomY+1);
        dc->SelectObject(oldbrush);
    }
    return x + xOffset;
}


int cwin_linelength = 80;

void CMainWindow::OnSize(UINT nType, int cx, int cy)
{
    WINDOWPLACEMENT wp;
    GetWindowPlacement(&wp);
    RECT *wr = &wp.rcNormalPosition;
    clientWidth = cx;
    clientHeight = cy;
    if (nType != SIZE_MINIMIZED)
        cwin_linelength = (clientWidth-5) / windowFonts.HCourier.across['X'];
    theApp.WriteProfileInt("MainWindow", "ScreenWidth",  wr->right-wr->left);
    theApp.WriteProfileInt("MainWindow", "ScreenHeight", wr->bottom-wr->top);
    theApp.WriteProfileInt("MainWindow", "LineLength",   cwin_linelength);
    if (caretVisible)
    {   OnVScroll(SB_FOR_CARET, 0, NULL);
        OnHScroll(SB_FOR_CARET, 0, NULL);
    }
    OnHScroll(SB_REFRESH_THUMB, 0, NULL);
    OnVScroll(SB_REFRESH_THUMB, 0, NULL);
}

void CMainWindow::OnMove(int x, int y)
{
    WINDOWPLACEMENT wp;
    GetWindowPlacement(&wp);
    RECT *wr = &wp.rcNormalPosition;
    theApp.WriteProfileInt("MainWindow", "ScreenLeft", wr->left);
    theApp.WriteProfileInt("MainWindow", "ScreenTop", wr->top);
}

void CMainWindow::OnResetWindow()
{
    int screenWidth = GetSystemMetrics(SM_CXSCREEN);
    WINDOWPLACEMENT wp;
    GetWindowPlacement(&wp);
    RECT *wr = &wp.rcNormalPosition;
    int left = wr->left;
    int cwidth = 80*windowFonts.HCourier.across['X'] +
                 3*GetSystemMetrics(SM_CXBORDER) +
                 2*GetSystemMetrics(SM_CXFRAME) +
                 GetSystemMetrics(SM_CXVSCROLL) + 5;
// Try to get the whole window onto the screen.
    if (left + cwidth > screenWidth)
    {   left = screenWidth - cwidth;
        if (left < 0) left = 0;
    }
    SetWindowPos(NULL, 
        left, wr->top,
        cwidth, (wr->bottom - wr->top),
        SWP_SHOWWINDOW | SWP_NOZORDER);
    UpdateWindow();
    cwin_linelength = 80;
    theApp.WriteProfileInt("MainWindow", "ScreenLeft",   left);
    theApp.WriteProfileInt("MainWindow", "ScreenWidth",  cwidth);
    theApp.WriteProfileInt("MainWindow", "ScreenTop",    wr->top);
    theApp.WriteProfileInt("MainWindow", "ScreenHeight", wr->bottom-wr->top);
    theApp.WriteProfileInt("MainWindow", "LineLength",   cwin_linelength);
} 

void CMainWindow::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar *pNctl)
{
// I will start by computing the total height of all the stuff I have in
// my text buffer. At the very worst I only save a couple of thousand lines,
// so adding up their heights again will not be TOO painful, I hope.
    int VScrollPos, movedUp=0, i, w;
    int aboveTop = -LineY(lineFirst);
    int totalHeight = aboveTop + LineY(lineLast) + LineDY(lineLast);

    inject_randomness(totalHeight);
// If I can make everything visible I will do that regardless of anything
// the user tries to do.
    if (totalHeight <= clientHeight)
    {   lineVisible = lineFirst;
        movedUp = aboveTop;
        VScrollPos = 0;
    }
    else
    {    switch (nSBCode)
        {
    case SB_ENDSCROLL:
            return;
    case SB_REFRESH_THUMB:
// In case the window has just been enlarged I check to see if I can scroll
// up a bit until one more scroll up would make the last line of my text
// drop off the (new) bottom of the window.
            while (lineVisible!=lineFirst)
            {   int lv = (lineVisible-1)&LINE_MASK;
                int nAbove = lineBuffer[lv].position -
                             lineBuffer[lineFirst].position;
                if (totalHeight > nAbove+clientHeight) break;
                lineVisible = lv;
                w = LineDY(lv);
                movedUp += w;
                aboveTop -= w;
            }
            break;
    case SB_THUMBPOSITION:
    case SB_THUMBTRACK:
            i = (nPos*(totalHeight-clientHeight))/100;
            if (i > aboveTop)
            {   while (totalHeight > aboveTop+clientHeight &&
                       i > aboveTop)
                {   w = LineDY(lineVisible);
                    aboveTop += w;
                    movedUp -= w;
                    lineVisible = (lineVisible+1) & LINE_MASK;
                }
            }
            else
            {   i += LineDY(lineVisible);
                while (lineVisible != lineFirst &&
                       i <= aboveTop)
                {   lineVisible = (lineVisible-1) & LINE_MASK;
                    w = LineDY(lineVisible);
                    aboveTop -= w;
                    movedUp += w;
                }
            }
            break;
    case SB_FOR_CARET:
            if (LineY(caretLine)+LineDY(caretLine) > clientHeight)
            {   while (totalHeight > aboveTop+clientHeight &&
                       LineY(caretLine)+LineDY(caretLine) > clientHeight)
                {   w = LineDY(lineVisible);
                    aboveTop += w;
                    movedUp -= w;
                    lineVisible = (lineVisible+1) & LINE_MASK;
                }
            }
            else
            {   while (lineVisible != lineFirst &&
                       LineY(caretLine) < 0)
                {   lineVisible = (lineVisible-1) & LINE_MASK;
                    w = LineDY(lineVisible);
                    aboveTop -= w;
                    movedUp += w;
                }
            }
            break;
    case SB_LINEDOWN:
// If the bottom line of text is already visible I will not scroll down
// any further.
            if (totalHeight > aboveTop+clientHeight)
            {   lineVisible = (lineVisible+1) & LINE_MASK;
                w = lineBuffer[lineVisible].height;
                aboveTop += w;
                movedUp = -w;
            }
            break;
    case SB_LINEUP:
            if (lineVisible != lineFirst)
            {   w = lineBuffer[lineVisible].height;
                aboveTop -= w;
                movedUp = w;
                lineVisible = (lineVisible-1) & LINE_MASK;
            }
            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:
            while (totalHeight > aboveTop+clientHeight &&
                   -10*movedUp < 7*clientHeight)
            {   lineVisible = (lineVisible+1) & LINE_MASK;
                w = lineBuffer[lineVisible].height;
                aboveTop += w;
                movedUp -= w;
            }
            break;
    case SB_PAGEUP:
            while (lineVisible != lineFirst &&
                   10*movedUp < 7*clientHeight)
            {   w = lineBuffer[lineVisible].height;
                aboveTop -= w;
                movedUp += w;
                lineVisible = (lineVisible-1) & LINE_MASK;
            }
            break;
        }
        if (lineVisible == lineFirst) VScrollPos = 0;
        else if (totalHeight <= aboveTop+clientHeight) VScrollPos = 100;
        else VScrollPos = (100*aboveTop)/(totalHeight-clientHeight);
    }
// If the user had just dragged the scroll bar I will tend to leave the
// scroll bar thumb where the user put it. However I will make an exception
// if the user's drag caused me to go to one or other extreme position, since
// I think it would be confusing to have the window totally fully scrolled
// and the thumb not extremal in its bar. But at all intermediate positions
// it is kindest to let it settle where the user put it!
    if (nSBCode == SB_THUMBPOSITION ||
        nSBCode == SB_THUMBTRACK)
    {   if (VScrollPos == 0 || VScrollPos == 100)
            nPos = VScrollPos;
        SetScrollPos(SB_VERT, nPos, TRUE);
    }
    else SetScrollPos(SB_VERT, VScrollPos, TRUE);
    if (movedUp != 0)
    {   HideCaret();
        ScrollWindow(0, movedUp, NULL, NULL);
        ShowCaret();
    }
// If the window is minimized then I will not change the status of apparent
// visibility of the caret. This is important because if the caret becomes
// marked as invisible because (for instance) the window is of zero size then
// it will remain invisible and the window will not be scrolled ever, leaving
// the text buffer to over-fill and hang things up.
    if (IsIconic()) return;
    else if (caretY >= 0 && caretY1 <= clientHeight &&
        caretX-xOffset >= 0 && caretX-xOffset < clientWidth)
        caretVisible = TRUE;
    else caretVisible = FALSE;
}


void CMainWindow::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar *PCntl)
{
// Each time I hit the horizontal scroll-bar I will check to find how
// long the longest (partially) visible line is.
    int yBase = lineBuffer[lineVisible].position;
    int line, longestLine = 0;
    for (line=lineVisible;; line=(line+1)&LINE_MASK)
    {   int y = lineBuffer[line].position - yBase;
        if (y > clientHeight) break;
        int w = lineBuffer[line].width;
        if (w > longestLine) longestLine = w;
        if (line==lineLast) break;
    }
// If everything fits I will just make sure I am scrolled fully left. Well as
// a matter of caution I do not force things that way if the request was
// to make the caret visible, since maybe if the screen is not quite up to
// date it may seem that I could afford to scroll fully left but that would
// in fact hide the caret.
    if (longestLine < clientWidth && nSBCode != SB_FOR_CARET)
    {   if (xOffset != 0)
        {   HideCaret();
            ScrollWindow(xOffset, 0, NULL, NULL);
            ShowCaret();
        }
        xOffset = 0;
        SetScrollPos(SB_HORZ, 0, TRUE);
        return;
    }
    int moveLeft = 0, w;
    switch (nSBCode)
    {
case SB_ENDSCROLL:
        return;
case SB_REFRESH_THUMB:
        break;
case SB_THUMBPOSITION:
case SB_THUMBTRACK:
        w = (nPos*(longestLine-clientWidth/2))/100;
        moveLeft = xOffset - w;
        xOffset = w;
        break;
case SB_FOR_CARET:
        w = windowFonts.HCourier.across['X'];
        if (caretX-xOffset < 5*w)
            moveLeft = w*((clientWidth/2 - (caretX-xOffset))/w);
        else if (caretX-xOffset >= clientWidth)
            moveLeft = -w*((caretX - xOffset - clientWidth)/w + 4);
        xOffset -= moveLeft;
        break;
// Small jumps are by the width of the letter X in the biggest fixed pitch
// font that I have selected. Thus if you select a big font you will get
// coarser contol over horizontal scrolling.
case SB_LINERIGHT:
        w = windowFonts.HCourier.across['X'];
        xOffset += w;
        moveLeft = -w;
        break;
case SB_LINELEFT:
        w = windowFonts.HCourier.across['X'];
        xOffset -= w;
        moveLeft = w;
        break;
// Big jumps are by roughly (2/3) the width of the main Window
case SB_PAGERIGHT:
        w = (2*clientWidth)/3;
        xOffset += w;
        moveLeft = -w;
        break;
case SB_PAGELEFT:
        w = (2*clientWidth)/3;
        xOffset -= w;
        moveLeft = w;
        break;
    }
    if (xOffset < 0)
    {   moveLeft += xOffset;
        xOffset = 0;
    }
// I will permit scrolls to the right to a stage where the longest line
// is (1/2) across the screen. But no further and especially I will not
// permit scrolls right that cause the text on the screen to vanish
// totally.  The limit I use here is somewhat of an arbitrary choice. If a
// "scroll page right" request looked as if it would move beyond this I will
// clip the scroll.
    w = longestLine - clientWidth/2;
    if (w <= 0)
    {   moveLeft += xOffset;
        xOffset = 0;
        w = 1;
    }
    else if (xOffset>w)
    {   moveLeft += xOffset - w;
        xOffset = w;
    }
    int HScrollPos = (100*xOffset)/w;
    if (HScrollPos > 100) HScrollPos = 100;
    if ((nSBCode != SB_THUMBPOSITION &&
         nSBCode != SB_THUMBTRACK) ||
        HScrollPos == 0 ||
        HScrollPos == 100) SetScrollPos(SB_HORZ, HScrollPos, TRUE);
    else SetScrollPos(SB_HORZ, nPos, TRUE);
    if (moveLeft != 0)
    {   HideCaret();
        ScrollWindow(moveLeft, 0, NULL, NULL);
        ShowCaret();
    }
}


void CMainWindow::OnSetFocus(CWnd *pOldWnd)
{
    CreateSolidCaret(caretWidth, windowFonts.HCourier.height);
    ShowCaret();
    SetWindowPos(&wndTop, 0, 0, 0, 0, SWP_NOMOVE|SWP_NOSIZE|SWP_SHOWWINDOW);
    selectScrollSpeed = 0;
    hasFocus = TRUE;   /* True just after focus has returned to this window */
}


void CMainWindow::OnKillFocus(CWnd *pNewWnd)
{
    ::DestroyCaret();
    hasFocus = FALSE;
}


// Sometime I find reading documentation quite hard. I had really thought that
// when one selected an application (eg using ALT+TAB) that would give the
// input focus to that application's main window. But it appears
// (experimentally) that this is not always true, and I found myself with
// a window with hard blue title bar but no input focus. It seems (?) that
// the application has been "activated", so here I ensure that when that
// happens it grabs the focus. Elsewhere you will find that whenever I see a
// mouse button depressed I grab the focus too (just to be on the safe side).

void CMainWindow::OnActivate(UINT state, CWnd *pWnd, BOOL minim)
{
    if (state != WA_INACTIVE) SetFocus();
}

// When I put a character into the text buffer I need to worry about purging
// old characters when the buffer wraps around. That may have an effect on
// what is visible on the screen, or on a selected region. If I insert a
// character and I am at the foot of a page I may need to scroll up. All
// in all the funny cases are quite messy here!

void CMainWindow::cwin_putchar(int ch)
{
    if (hasFinished) return;
    BOOL caretAtEnd = (caretChar == textLast);
    if (ch == '\n')
    {
// If the caret is at the end of the text and if "page mode" is enabled
// then putting a newline into the text buffer will sometimes lead to
// a pause....
        BOOL usualTitle = TRUE;
// The test here says pause if
//  (a) I am in page mode
//  (b) lineVisible==pageLine. Well lineFirst is the line that is now in
//      danger of being about to scroll off the top of the screen, and so
//      pageLine will identify the current line to be "protected" from that
//      fate.
//  (c) caretLine vs clientHeight: This tries to test if scrolling would be
//      called for if the caret moved down a line (hence the 2*LineDY()).
//  In the pause case I just loop polling the window manager until something
//  magic happens. In general I can do the correct magic by making a suitable
//  selection of events do "pageLine = caretLine;"
//
// Actually it is nastier than that, because lineVisible may not be up to
// date - so I have to work out what lineVisible WOULD be in the relevant
// situation by having code rather line that in OnVScroll/SB_FOR_CARET.
        while (pageMode)
        {   int lv = lineVisible, w;
            int aboveTop = -LineY(lineFirst);
            int totalHeight = aboveTop + LineY(lineLast) + LineDY(lineLast);
            if (totalHeight <= clientHeight) lv = lineFirst;
            else while (totalHeight > aboveTop+clientHeight &&
                   LineY(caretLine)+LineDY(caretLine) > clientHeight)
            {   w = LineDY(lv);
                aboveTop += w;
                lv = (lv+1) & LINE_MASK;
            }
            if (lv==pageLine &&
                (LineY(caretLine)+2*LineDY(caretLine) > clientHeight))
            {   if (usualTitle)
                {   usualTitle = FALSE;
                    pagePaused = TRUE;
//                  BeginWaitCursor();
// For reasons that I do not fully understand a "wait" cursor established here
// gets turned back into the usual arrow one before I want it to. I suspect
// maybe that whenever the system re-draws the cursor it does so using the
// version that is set as default for the application? Anyway the effect is
// that I get an apparantly inconsistent behaviour, and at present I think
// that keeping the cursor consistently an arrow is better than letting it
// flash incoherently between that and an hourglass.
                    SetWindowText("(page-mode) Type any key to continue");
                }
// Here I will force the caret to be visible and poll the window manager
                if (!hasFinished)
                {   OnVScroll(SB_FOR_CARET, 0, NULL);
                    OnHScroll(SB_FOR_CARET, 0, NULL);
                    cwin_poll_window_manager();
                }
                if (hasFinished) return;
                continue;
            }
            else break;
        }
        if (!usualTitle)
        {   SetWindowText(mainTitle);
//          EndWaitCursor();
        }
        pagePaused = FALSE;
        textBuffer[textLast] = '\n';
        int n = (textLast+1) & TEXT_MASK;
        if (n == textFirst) WrapTextBuffer(TRUE);
        textLast = n;
// When I see a newline I may need to wrap the line buffer, discarding the
// oldest saved line.
        int nextPos = lineBuffer[lineLast].position +
                      lineBuffer[lineLast].height;
        n = (lineLast+1) & LINE_MASK;
        if (n == lineFirst) WrapTextBuffer(TRUE);
        lineLast = n;
        lineBuffer[lineLast].position = nextPos;
        lineBuffer[lineLast].up = windowFonts.HCourier.up;
        lineBuffer[lineLast].height = windowFonts.HCourier.up +
                                      windowFonts.HCourier.down;
        lineBuffer[lineLast].width = 0;
        lineBuffer[lineLast].address = textLast;
        endFontWidths = windowFonts.HCourier.across;
        endX = 0;
        if (caretAtEnd)
        {   caretChar = icaretChar = textLast;
            caretLine = icaretLine = lineLast;
            caretX = icaretX = 0;
            caretFontWidths = icaretFontWidths = windowFonts.HCourier.across;
        }
    }
    else
    {   textBuffer[textLast] = ch;
        int n = (textLast+1) & TEXT_MASK;
        if (n == textFirst) WrapTextBuffer(TRUE);
// I invalidate all the way to the right of the line just to be on the
// safe side. This is probably excessive... but equally the painting in of
// the solid blank to the right of any real text will not be too expensive.
        CRect cr(lineBuffer[lineLast].width-xOffset, LineY(lineLast),
                 clientWidth, LineY(lineLast)+LineDY(lineLast));
        InvalidateRect(&cr);  // Should I hide the caret here?
        textLast = n;
        endX += endFontWidths[ch];
        lineBuffer[lineLast].width += endFontWidths[ch];
        if (caretAtEnd)
        {   caretChar = icaretChar = textLast;
            caretX += caretFontWidths[ch];
            icaretX = caretX;
        }
    }
}

// This code inserts characters wherever the caret happens to be, typically
// in the middle of the text buffer. This returns TRUE if it succeeds. The
// case when it might fail is if the text buffer is full and the caret is on
// the first line. Then to make room for the next stuff the first line would
// need to be discarded, but I can not do that without loss of the caret.
//
// UnTypeAhead() is used to push characters into the type-ahead buffer if
// a newline is inserted into the current line of input (ie between
// inputLineStart and textLast). It returns FALSE if pushing characters
// flushed out previously typed stuff.

BOOL CMainWindow::UnTypeAhead(int ch)
{
    int w = (typeAheadP1-1)&typeAheadBufferMask;
    typeAheadBuffer[w] = ch;
    typeAheadP1 = w;
    if (typeAheadP1 == typeAheadP2)
    {   typeAheadP2 = (typeAheadP2-1)&typeAheadBufferMask;
        return FALSE;  // Inserting pushes out something else
    }
    else return TRUE;
}

BOOL CMainWindow::InsertAtCaret(char *s, int n)
{
// I will do large inserts in 60-character chunks. That is so I can have
// a limited size circular buffer (64 chars here) for use when copying
// stuff down the buffer. Huge inserts would benefit from a larger chunk-size
// here, at the cost of using more memory for the copy buffer.
    while (n > 60)
    {   if (!InsertAtCaret(s, 60)) return FALSE;
        s += 60;
        n -= 60;
    }
    if (n == 0) return TRUE;
    int oldCaretLine = caretLine, oldCaretChar = caretChar;
    BOOL startEnd = (inputLineStart == textLast);
    int i, p = textLast;
// Firstly I will make sure that there is room for the inserted text by
// stepping on n characters from the current end of the text buffer and
// wraping to free up the space I walk over.  If this were to collide with
// the caret position I would be in a MESS so in such cases I will not
// perform the insert & will return a failure flag.
    for (i=0; i<n; i++)
    {   p = (p+1)&TEXT_MASK;
        if (p == textFirst)
        {   if (caretLine == lineFirst) return FALSE; // fail
            else WrapTextBuffer(FALSE);
        }
    }
// Also I need to ensure that there is room for the number of new lines
// that I will insert. It will be rare that this is a problem, I suspect.
// Also beware here if the caret is positioned on the top line of the
// buffer.
    int k = 0;
    for (i=0; i<n; i++) if (s[i] == '\n') k++;
    p = lineLast;
    for (i=0; i<k; i++)
    {   p = (p+1)&LINE_MASK;
        if (p == lineFirst)
        {   if (caretLine == lineFirst) return FALSE; // fail
            else WrapTextBuffer(FALSE);
        }
    }
// Now I know that there is room for the insertion, I will "just" need to
// copy characters up through the text buffer.
    char circle[64], circleFlags[64];
    memcpy(circle, s, n);
    memset(circleFlags, 0, n);
    int inP = n, outP = 0;
// (line,p) is where I read characters from the buffer, while (line1,p1)
// is where I put them back. Certainly at the start, and quite often all
// the way through these values will remain in step. They may drift apart if
// previous deletions within lines lave left gaps in the buffer between the
// end of one line and the start of the next.
    int line = caretLine;
    p = caretChar;
    int line1 = line, p1 = p;
    while (p != textLast)
    {   int c = textBuffer[p];
        circle[inP] = c;
        circleFlags[inP] = 0;
        if (p == caretChar) circleFlags[inP] |= 1;
// I will leave inputLineStart where it is if it lies just before the
// caret position at the start of the insert operation.
        if (p == inputLineStart && p != oldCaretChar) circleFlags[inP] |= 2;
        if (p == icaretChar) circleFlags[inP] |= 4;
        if (c == '\n')
        {   line = (line+1)&LINE_MASK;
            p = lineBuffer[line].address;
        }
        else p = (p+1)&TEXT_MASK;
        inP = (inP+1)&63;
        int c1 = circle[outP];
        textBuffer[p1] = c1;
        if (circleFlags[outP] & 1) caretChar = p1, caretLine = line1;
        if (circleFlags[outP] & 2) inputLineStart = p1;
        if (circleFlags[outP] & 4) icaretChar = p1, icaretLine = line1;
        p1 = (p1+1)&TEXT_MASK;
        outP = (outP+1)&63;
// Now if I had just inserted a newline I need to fix up the pointers
// from the line buffer into the text buffer.
        if (c1 == '\n')
        {   if (line == line1) // Here I need to shuffle lines up
            {   int a = p1, line2=line;
                lineLast = (lineLast+1)&LINE_MASK;
                for (;;)
                {   line2 = (line2+1)&LINE_MASK;
                    int w = lineBuffer[line2].address;
                    lineBuffer[line2].address = a;
                    if (line2==lineLast) break;
                    a = w;
                }
                line = (line+1)&LINE_MASK;
            }
            line1 = (line1+1)&LINE_MASK;
            lineBuffer[line1].address = p1;
        }
    }
    while (inP != outP)
    {   int c1 = circle[outP];
        textBuffer[p1] = c1;
        if (circleFlags[outP] & 1) caretChar = p1, caretLine = line1;
        if (circleFlags[outP] & 2) inputLineStart = p1;
        if (circleFlags[outP] & 4) icaretChar = p1, icaretLine = line1;
        if (c1 == '\n')
        {   line1 = (line1+1)&LINE_MASK;
            lineBuffer[line1].address = p1;
        }
        p1 = (p1+1)&TEXT_MASK;
        outP = (outP+1)&63;
    }
    textLast = p1;
    lineLast = line1;
// Here it may be that a newline had been inserted within the line that
// was being prepared for input. If so, stuff after that newline must be
// pushed back into the type-ahead buffer. When this happens it will always
// be the case that the text in the buffer after inputLineStart will form
// a compact block so I can just copy chars backwards
    BOOL success = TRUE;
    if (startEnd) inputLineStart = textLast;
    else if (inputLineStart>=0 && k!=0)
    {   int lastNewline = inputLineStart;
        while (lastNewline!=textLast && textBuffer[lastNewline]!='\n')
            lastNewline = (lastNewline+1)&TEXT_MASK;
        while (textLast!=lastNewline)
        {   int nn = (textLast-1)&TEXT_MASK;
            if (textLast==caretChar) caretChar = nn;
            if (textLast==icaretChar) icaretChar = nn;
            if (textLast==selStartChar ||
                textLast==selFirstChar ||
                textLast==selEndChar) CancelSelection();
            textLast = nn;
            int c = textBuffer[textLast];
            if (!UnTypeAhead(c)) success = FALSE;
            if (c=='\n') lineLast = (lineLast-1)&LINE_MASK;
        }
    }
    LineSizes();
    CRect cr(0, LineY(oldCaretLine), clientWidth, clientHeight);
    if (k==0)                      // No newlines inserted
    {   cr.left = caretX;
        cr.bottom = LineY(caretLine)+LineDY(caretLine);
    }
    InvalidateRect(&cr);
    UpdateWindow();
    cwin_poll_window_manager();
    return success;
}

void CMainWindow::cwin_caret_putchar(int ch)
{
    if (hasFinished) return;
// If the caret happens to be at the end maybe I ought not to have got here
// anyway, but I will chain to the code that inserts things at the end of the
// text.
    if (caretChar == textLast)
    {   cwin_putchar(ch);
        return;
    }
    char buff[4];
    buff[0] = ch;
// I beep if the insertion was impossible.
    if (!InsertAtCaret(buff, 1)) ::MessageBeep(0xffffffff);
}

void CMainWindow::cwin_caret_replacechar(int ch)
{
    if (hasFinished) return;
// If the caret happens to be at the end maybe I ought not to have got here
// anyway, but I will chain to the code that inserts things at the end of the
// text.
    if (caretChar == textLast || ch == '\n' || textBuffer[caretChar] == '\n')
    {   cwin_putchar(ch);
        return;
    }
// Gosh this OUGHT to be easy!
    textBuffer[caretChar] = ch;
    if (caretChar == icaretChar)
    {   icaretX += caretFontWidths[ch];
        icaretChar++;
    }
    CRect cr(caretX, LineY(caretLine),
             clientWidth, LineY(caretLine)+LineDY(caretLine));
    InvalidateRect(&cr);
    caretX += caretFontWidths[ch];
    caretChar++;
    UpdateWindow();
    cwin_ensure_screen(FALSE);
}

// When I have put enough characters on the screen my text buffer may become
// full. In which case I will discard the earliest stored line in it. Because
// the line will always be stored with at least a newline character present
// this guarantees to free up at least one byte in the buffer. I can also
// call this routine to discard the top line of the buffer when I need to
// recycle space in the record of lines (as distinct from characters).
//
// If I attempt to cancel part of the buffer by coming here but either the
// top line of the buffer is visible on the screen or part of the top line
// is included in a selection then I will pause until the user scrolls the
// window or does something to the selection that will mean that throwing
// away information will not discard anything still being looked at.
//
// Note that when I say here "Part of the first line is selected" I will
// mean that a non-empty part of the line is selected. Not having a
// selection in force is indicated by selStartChar==selEndChar, but the
// selection pointers can otherwise point anywhere at all in the buffer
// or indeed outside it.

void CMainWindow::WrapTextBuffer(BOOL waitForSelection)
{
    selRootValid = FALSE;
    BOOL usualTitle = TRUE;
    if (waitForSelection)
    {   while ((lineVisible == lineFirst && caretChar != textLast &&
                LineY(lineLast)+LineDY(lineLast)>=clientHeight) ||
               (selStartLine == lineFirst &&
                selStartChar != selEndChar))
        {   cwin_ensure_screen(FALSE);
            if (usualTitle)
                usualTitle = FALSE,
                SetWindowText(
                    "Output pending: cancel selection/scroll down please");
            cwin_poll_window_manager();
        }
    }
    if (hasFinished) return;
    if (!usualTitle) SetWindowText(mainTitle);
    if (lineFirst==lineLast) OnClear(); // Drastic!
    else
    {   lineFirst = (lineFirst+1)&LINE_MASK;
        textFirst = lineBuffer[lineFirst].address;
    }
}

void cwin_putchar(int c)
{
    theApp.mainWindow->cwin_putchar(c);
    inject_randomness(c);
    inject_randomness((int)clock());
}

void CMainWindow::cwin_puts(const char *s)
{
    if (hasFinished) return;
    int ch;
// Here I should be careful and do just ONE call to Invalidate for all the
// changes that I make. But to start with it is MUCH easier to call putchar
// lots of times, so that is what I will do.
    while ((ch=*s++)!=0) cwin_putchar(ch);
}

void cwin_puts(const char *s)
{
    theApp.mainWindow->cwin_puts(s);
}

#ifndef MS_CDECL
#ifdef _MSC_VER
#define MS_CDECL __cdecl
#else
#define MS_CDECL
#endif
#endif

void MS_CDECL CMainWindow::cwin_printf(const char *s, ...)
{
    if (hasFinished) return;
    va_list a;
    va_start(a, s);
    cwin_vfprintf(s, a);
    va_end(a);
}

void MS_CDECL cwin_printf(const char *s, ...)
{
    if (hasFinished) return;
    va_list a;
    va_start(a, s);
    theApp.mainWindow->cwin_vfprintf(s, a);
    va_end(a);
}

// The versions of printf() that work with the window system use an internal
// buffer and print characters into that before sending them to the screen.
// It appears to be hard to check for overflow of this buffer, so the user
// will have to take care. A call to cwin_printf that generates more than
// MAX_PRINTF_OUTPUT characters can crash the system. Sorry.  Maybe I
// should use the Watcom-specific _vbprintf() function here...

#define MAX_PRINTF_OUTPUT 256

void CMainWindow::cwin_vfprintf(const char *s, va_list a)
{
    if (hasFinished) return;
    char temp[MAX_PRINTF_OUTPUT];
#ifdef __WATCOMC__
    _vbprintf(temp, MAX_PRINTF_OUTPUT, s, a);
// I put a zero byte at the end of the buffer to ensure that the string
// left there is properly terminated even if _vbprintf() wrote proper
// characters right up to the end.
    temp[MAX_PRINTF_OUTPUT-1] = 0;
#else
    vsprintf(temp, s, a);
#endif
    cwin_puts(temp);
}

void cwin_vfprintf(const char *s, va_list a)
{
    theApp.mainWindow->cwin_vfprintf(s, a);
}

void CMainWindow::cwin_ensure_screen(BOOL poll)
{
    if (hasFinished) return;
// Here I need to do Invalidate operations, re-position the caret
// and adjust the position of scroll-bar thumbs.
    if (caretVisible)
    {   OnVScroll(SB_FOR_CARET, 0, NULL);
        OnHScroll(SB_FOR_CARET, 0, NULL);
    }
//  OnVScroll(SB_REFRESH_THUMB, 0, NULL);
    HideCaret();
    CPoint caretPos(caretX-xOffset, caretY);
    SetCaretPos(caretPos);
    ShowCaret();
    if (poll) cwin_poll_window_manager();
}

void cwin_ensure_screen()
{
    theApp.mainWindow->cwin_ensure_screen(TRUE);
}

// This will remove a character from the buffer at a location defined
// by the caret, which will in general not be at the end of the text.


void CMainWindow::cwin_caret_unputchar()
{
// When I try to remove a character from the screen I know that there is
// no selection active, since a DELETE when there was a selection deleted it
// as a block, and so did not reach this bit of code. So perhaps I can
// make this code work by creating a selected region containing the
// character (or prompt string) that I to be discarded and then do a
// DeleteSelection.
    if (hasFinished) return;
// If it happens that I am at the end of the text I will call the other
// bit of character deletion code, so that in the bulk of this function I
// can know that I am in the general case (which tends to be an expensive one)
    if (caretChar==textLast)
    {   cwin_unputchar();
        return;
    }
    selRootValid = FALSE;
// If the caret is right at the start of the buffer I do not have
// anything to delete. This will, of course, include the case where the
// buffer is totally empty.
    if (caretChar==textFirst) return;
// Set up the start of the selection at the position of the caret.
    selFirstChar = selStartChar = selEndChar = caretChar;
    selFirstX = selStartX = selEndX = caretX;
    selFirstLine = selStartLine = selEndLine = caretLine;
// Now I need to step back a "character", where that could be a newline
// or a prompt string rather than any single simple character. I also
// ought to worry here about deletion of items in the text buffer that
// control formatting. All in all this is pretty dodgy, so I will cope with
// just the simple cases to start with.
    int n;
    if (lineBuffer[caretLine].address == caretChar)  // deleting a newline?
    {   return;
// For now I am going to prevent deletion of or through a prompt or newline
        n = (caretLine-1)&LINE_MASK;
        caretLine=n;
        caretX = lineBuffer[n].width;
        n = lineBuffer[n].address;
        while (textBuffer[n]!='\n') n = (n+1)&TEXT_MASK;
    }
    else
    {   n = (caretChar-1) & TEXT_MASK;
// I produce just an APPROXIMATION to the desired caretX here, but want that
// so that I can limit the amount of re-painting that happens.
        int w = windowFonts.HCourier.across['X'];
        if ((textBuffer[n]&0xff)==CH_ENDPROMPT)   // delete whole of a prompt
        {
            return;                        // do not delete prompt
            do
            {   n = (n-1)&TEXT_MASK;
                caretX -= w;
            } while (n!=textFirst && (textBuffer[n]&0xff)!=CH_PROMPT);
        }
        else caretX -= w;
    }
    caretChar = n;
    if (caretLine == lineLast)
    {   icaretChar = caretChar;
        icaretLine = caretLine;
        icaretX = caretX;
    }
    selStartChar = n;
    selStartLine = caretLine;
    selStartX = caretX;
    DeleteSelection();
}

// Now a version of unputchar that deletes characters from the end of the
// text buffer (and the caret may or may not be there).

void CMainWindow::cwin_unputchar()
{
    if (hasFinished) return;
    selRootValid = FALSE;
    int n;
    if (textFirst == textLast) return;             // utterly empty buffer
    if (lineBuffer[lineLast].address == textLast)  // deleting a newline?
    {   return;
        n = (lineLast-1)&LINE_MASK;
        endX = lineBuffer[n].width;
        if (caretLine==lineLast) caretLine=n, caretX=endX;
        if (icaretLine==lineLast) icaretLine=n, icaretX=endX;
        lineLast=n;
        n = lineBuffer[n].address;
        while (textBuffer[n]!='\n') n = (n+1)&TEXT_MASK;
        if (textLast == caretChar) caretChar = n;
        if (textLast == icaretChar) icaretChar = n;
        if (textLast == inputLineStart) inputLineStart = n;
        textLast = n;
    }
    else
    {   n = (textLast-1) & TEXT_MASK;
        int w = windowFonts.HCourier.across['X'];
        if ((textBuffer[n]&0xff)==CH_ENDPROMPT)   // delete whole of a prompt
        {   return;
            do
            {   n = (n-1)&TEXT_MASK;
                endX -= w;
            } while (n!=textFirst && (textBuffer[n]&0xff)!=CH_PROMPT);
        }
        else endX -= w;
        if (textLast == caretChar) caretChar = n;
        if (textLast == icaretChar) icaretChar = n;
        if (textLast == inputLineStart) inputLineStart = n;
        textLast = n;
// The amount of invalidation done here runs across to the end of the
// last line, and that ought to get rid of any residues of the old
// caret. I use an APPROXIMATION to the new value of endX, but hope that
// that gets corrected during the re-painting.
        CRect cr(endX-xOffset, LineY(lineLast),
                 clientWidth, LineY(lineLast)+LineDY(lineLast));
        InvalidateRect(&cr);  // Should I hide the caret here?
    }
    UpdateWindow();
    HideCaret();
    CPoint caretPos(caretX-xOffset, caretY);
    SetCaretPos(caretPos);
    ShowCaret();
}

int CMainWindow::cwin_getchar()
{
    if (hasFinished) return EOF;
    int ch;
    if (inputP >= 0)
    {   ch = inputBuffer[inputP++];
        if (ch == '\n') inputP = -1; // This buffer now all used up.
        return ch;
    }
    cwin_putchar(CH_PROMPT);
    cwin_puts(cwin_prompt_string);
    if (spool_file != NULL) fprintf(spool_file, "%s", cwin_prompt_string);
    cwin_putchar(CH_ENDPROMPT);
    if (hasFinished) return EOF;
    inputLineStart = textLast;          // just beyond the prompt
// Next I move characters across from the type-ahead buffer into the main
// text buffer, echoing them as I go (and handling DELETE chars too). I
// stop when I find a newline.  Perhaps some people would like if if some
// other characters would terminate an input line, but for the moment I am
// sticking to just newline.... except that any keyboard or menu activity
// that raises an "exception" will stop input for me rather abruptly.
    for (;;)
    {   cwin_ensure_screen(FALSE);
        while (typeAheadP1 == typeAheadP2 &&
               clipboardInput == NULL)
        {   cwin_poll_window_manager();
            if (hasFinished || exception_pending()) return EOF;
        }
        if (clipboardInput != NULL)
        {   ch = *clipboardInputP++;
// When input is coming from the clipboard to the very end of the input
// text I will ignore copied prompt strings. Note that this is different
// from the situation with PASTE inserts into the middle of the text.
// A curious side-effect here is that if a regular TEXT clipboard file is
// pasted in and it happens to contain these funny character codes...
            while ((ch&0xff) == CH_PROMPT)
            {   while ((ch = *clipboardInputP++) != 0 &&
                       (ch&0xff) != CH_ENDPROMPT);
                if (ch == 0) break;
                ch = *clipboardInputP++;
            }
            if (ch == '\r') continue;
            if (ch == 0)
            {   free(clipboardInput);
                clipboardInput = NULL;
                continue;
            }
        }
        else
        {   ch = typeAheadBuffer[typeAheadP1];
            typeAheadP1 = (typeAheadP1 + 1) & typeAheadBufferMask;
        }
        if (ch == 0x7f) cwin_unputchar();
        else cwin_putchar(ch);
        if (ch == '\n' || hasFinished) break;
    }
    if (hasFinished) return EOF;
// By the time I exit the above loop there must be at least one character
// in the line that has just been read, even if it is only the terminating
// '\n'.
    inputP = 0;
// When I copy stuff from the screen into the input buffer I discard any
// prompts. They might be there if the line of input was created in part by
// pasting material into the middle of a partly-typed line.
    BOOL inPrompt = FALSE;
    for (;;)
    {   ch = textBuffer[inputLineStart];
        inputLineStart = (inputLineStart+1)&TEXT_MASK;
// Here I quietly truncate very very long input lines. For my first attempt
// I have inputBufferSize set at 2048.
        if ((ch&0xff) == CH_PROMPT) inPrompt = TRUE;
        if (inputP<inputBufferSize && !inPrompt) inputBuffer[inputP++] = ch;
        if ((ch&0xff) == CH_ENDPROMPT) inPrompt = FALSE;
        else if (ch == '\n') break;
    }
    inputBuffer[inputBufferSize-1] = '\n';
// Now I have the next line of user input in inputBuffer, running from 0
// up to the first '\n' (and I know there is a '\n' in there somewhere).
// I want to copy it away into a "saved input lines" buffer, so that they
// can be retrieved later by a DOSKEY-like protocol. Note that provided
// I keep the size of the save buffer larger than that of the input buffer
// the copying operation here will not cause embarassing overflow!
    currentInputLine = -1;
    savedLines[savedP2++] = savedLast;
    if (savedP2 == MAX_SAVED_LINES) savedP2 = 0;
    if (savedP2 == savedP1)
    {   savedP1++;
        if (savedP1 == MAX_SAVED_LINES) savedP1 = 0;
        savedFirst = savedLines[savedP1];
    }
    inputP = 0;
    for (;;)
    {   ch = inputBuffer[inputP++];
        savedChars[savedLast++] = ch;
        if (savedLast == MAX_SAVED_CHARS) savedLast = 0;
        if (savedLast == savedFirst)
        {   savedP1++;
            if (savedP1 == MAX_SAVED_LINES) savedP1 = 0;
            savedFirst = savedLines[savedP1];
        }
        if (ch == '\n') break;
    }
    inputP = 0;
    ch = inputBuffer[inputP++];
    if (ch == '\n') inputP = -1; // This buffer now all used up.
    cwin_ensure_screen(FALSE);
    return ch;
}

int cwin_getchar()
{
    return theApp.mainWindow->cwin_getchar();
}

int CMainWindow::cwin_getchar_nowait()
{
    if (hasFinished) return EOF;
    int ch;
    if (inputP >= 0)
    {   ch = inputBuffer[inputP++];
        if (ch == '\n') inputP = -1; // This buffer now all used up.
        return ch;
    }
    cwin_putchar(CH_PROMPT);
    cwin_puts(cwin_prompt_string);
    if (spool_file != NULL) fprintf(spool_file, "%s", cwin_prompt_string);
    cwin_putchar(CH_ENDPROMPT);
    if (hasFinished) return EOF;
    inputLineStart = textLast;          // just beyond the prompt
// Next I move characters across from the type-ahead buffer into the main
// text buffer, echoing them as I go (and handling DELETE chars too). I
// stop when I find a newline.
    for (;;)
    {   cwin_ensure_screen(FALSE);
        if (typeAheadP1 == typeAheadP2 &&
            clipboardInput == NULL) return EOF;
        if (clipboardInput != NULL)
        {   ch = *clipboardInputP++;
            while ((ch&0xff) == CH_PROMPT)
            {   while ((ch = *clipboardInputP++) != 0 &&
                       (ch&0xff) != CH_ENDPROMPT);
                if (ch == 0) break;
                ch = *clipboardInputP++;
            }
            if (ch == '\r') continue;
            if (ch == 0)
            {   free(clipboardInput);
                clipboardInput = NULL;
                continue;
            }
        }
        else
        {   ch = typeAheadBuffer[typeAheadP1];
            typeAheadP1 = (typeAheadP1 + 1) & typeAheadBufferMask;
        }
        if (ch == 0x7f) cwin_unputchar();
        else cwin_putchar(ch);
        if (ch == '\n' || hasFinished) break;
    }
    if (hasFinished) return EOF;
// By the time I exit the above loop there must be at least one character
// in the line that has just been read, even if it is only the terminating
// '\n'.
    inputP = 0;
    BOOL inPrompt = FALSE;
    for (;;)
    {   ch = textBuffer[inputLineStart];
        inputLineStart = (inputLineStart+1)&TEXT_MASK;
// Here I quietly truncate very very long input lines. For my first attempt
// I have inputBufferSize set at 2048.
        if ((ch&0xff) == CH_PROMPT) inPrompt = TRUE;
        if (inputP<inputBufferSize && !inPrompt) inputBuffer[inputP++] = ch;
        if ((ch&0xff) == CH_ENDPROMPT) inPrompt = FALSE;
        else if (ch == '\n') break;
    }
    inputBuffer[inputBufferSize-1] = '\n';
    inputP = 0;
    ch = inputBuffer[inputP++];
    if (ch == '\n') inputP = -1; // This buffer now all used up.
    return ch;
}

int cwin_getchar_nowait()
{
    return theApp.mainWindow->cwin_getchar_nowait();
}

void CMainWindow::cwin_discard_input()
{
    typeAheadP1 = typeAheadP2 = 0;
    if (clipboardInput) free(clipboardInput);
    clipboardInput = 0;
}

void cwin_discard_input()
{
    theApp.mainWindow->cwin_discard_input();
}

void CMainWindow::cwin_set_prompt(const char *s)
{
    strncpy(cwin_prompt_string, s, 31);
    cwin_prompt_string[31] = 0;
}

void cwin_set_prompt(const char *s)
{
    theApp.mainWindow->cwin_set_prompt(s);
}


// The READ command on the menu will work by forcing the caret to the
// end of the input buffer and simulating some typed in text which
// contains a file-name.  I will make a start at pretending to make it
// configurable here.  But there seem to be LOTS of things that need to be
// changed to match the system that is using the window manager code...
// I hope that eventually I will provide configuration calls so that the
// grim detail here gets set from "user" code. The basic idea is that
// readFileCommand holds a string to be generated, with a marker (%) somewhere
// where the file-name should go.  I need options to control processing
// of the file-name to get around reader escape conventions etc. I also need
// to tell the file selection dialog what sorts of files to look for...

#define VERBATIM_FILENAME    0
#define DOUBLE_BACKSLASH     1
#define DOUBLE_DOUBLEQUOTE   2

#ifdef COMMON
static char *readFileCommand  = "(load \"%\")";
static int fileOptions = DOUBLE_BACKSLASH;
static char *defaultExtension = "lsp";
static char *defaultFile      = "*.lsp";
static char *fileNameFilter   =
   "Lisp Files (*.lsp)|*.lsp|All Files (*.*)|*.*||\0";
#else
static char *readFileCommand  = "in \"%\";";
static int fileOptions = DOUBLE_DOUBLEQUOTE;
static char *defaultExtension = "red";
static char *defaultFile      = "*.RED;*.TST";
static char *fileNameFilter   =
   "Reduce (*.red/*.tst)|*.RED;*.TST|Lisp Files (*.lsp)|*.LSP|All Files (*.*)|*.*||\0";
#endif

void CMainWindow::OnRead()
{
    OnEnd();
    pageLine = caretLine;
    CFileDialog FDialog(TRUE, defaultExtension,
        defaultFile, OFN_FILEMUSTEXIST | OFN_HIDEREADONLY,
        fileNameFilter, NULL);
    FDialog.m_ofn.lpstrTitle = "Read File";
    if (FDialog.DoModal() != IDOK)
    {   cwin_ensure_screen(FALSE);  // needed to re-position caret, it seems.
        return;
    }
    CString p = FDialog.GetPathName();
    int nameLength = p.GetLength();
    char *q = readFileCommand;
    int i = strlen(q);
    while (i-- != 0)
    {   int ch = q[i];
        if (ch == '%')
        {   int j = nameLength;
            while (j-- != 0)
            {   int ch1 = p[j];
                switch (fileOptions)
                {
            case DOUBLE_BACKSLASH:
                    if (ch1 == '\\') UnTypeAhead(ch1);
                    break;
            case DOUBLE_DOUBLEQUOTE:
                    if (ch1 == '"') UnTypeAhead(ch1);
                    break;
            default:break;
                }
                UnTypeAhead(ch1);
            }
        }
        else UnTypeAhead(ch);
    }
    cwin_ensure_screen(FALSE);
}

#ifndef COMMON

static char **library_name_p = NULL;
static int library_name_n = 0;

static char *library_name[] =
{
    "algint",
    "applysym",
    "arnum",
    "assist",
    "avector",
    "boolean",
    "cali",
    "camal",
    "changevr",
    "compact",
    "complex",
    "crack",
    "cvit",
    "decompos",
    "defint",
    "desir",
    "dfpart",
    "dummy",
    "elem",
    "excalc",
    "fide",
    "fps",
    "gentran",
    "gnuplot",
    "groebner",
    "ideals",
    "ineq",
    "invbase",
    "laplace",
    "lie",
    "linalg",
    "modsr",
    "ncpoly",
    "normform",
    "numeric",
    "odesolve",
    "orthovec",
    "physop",
    "pmrules",
    "randpoly",
    "reacteqn",
    "residue",
    "rlfi",
    "rsolve",
    "scope",
    "sets",
    "spde",
    "specfn",
    "symmetry",
    "taylor",
    "tps",
    "tri",
    "trigsimp",
    "wu",
    "xcolor",
    "xideal",
    "zeilberg",
    "ztrans"
};

void CMainWindow::OnLoadLibrary(UINT a)
{
    OnEnd();
    pageLine = caretLine;
    char q[100];
    int i;
    if (library_name_p != NULL)
    {   if (a-IDM_FIRSTLOAD >= library_name_n) return;
        sprintf(q, "load_package %s;\n", 
                   library_name_p[a - IDM_FIRSTLOAD]);
    }
    else
    {   sprintf(q, "load_package %s;\n", library_name[a - IDM_FIRSTLOAD]);
    }
    i = strlen(q);
    while (i-- != 0)
    {   int ch = q[i];
        UnTypeAhead(ch);
    }
    cwin_ensure_screen(FALSE);
}

static char **switch_name_p = NULL;
static int switch_name_n = 0;

static struct { char *name; int status; } switch_name[] =
{
    {"algint",             0},
    {"adjprec",            0},
    {"allbranch",          0},
    {"allfac",             1},
    {"arbvars",            0},
    {"asterisk",           1},
    {"backtrace",          0},
    {"balanced_mod",       0},
    {"bfspace",            0},
    {"combineexpt",        0},
    {"combinelogs",        0},
    {"comp",               0},
    {"complex",            0},
    {"compxroots",         0},
    {"cramer",             0},
    {"cref",               0},
    {"defn",               0},
    {"demo",               0},
    {"dfprint",            0},
    {"div",                0},
    {"echo",               0},
    {"errcont",            0},
    {"evallhseqp",         0},
    {"exp",                1},
    {"expandexpt",         1},
    {"expandlogs",         0},
    {"ezgcd",              0},
    {"factor",             0},
    {"fastfor",            0},
    {"force",              0},
    {"fort",               0},
    {"fortupper",          0},
    {"fullprec",           0},
    {"fullprecision",      0},
    {"fullroots",          0},
    {"gc",                 0},
    {"gcd",                0},
    {"heugcd",             0},
    {"horner",             0},
    {"ifactor",            0},
    {"int",                0},
    {"intstr",             0},
    {"lcm",                1},
    {"lessspace",          0},
    {"limitedfactors",     0},
    {"list",               0},
    {"listargs",           0},
    {"lower",              1},
    {"mcd",                1},
    {"modular",            0},
    {"msg",                1},
    {"multiplicities",     0},
    {"nat",                1},
    {"nero",               0},
    {"noarg",              1},
    {"noconvert",          0},
    {"nonlnr",             0},
    {"nosplit",            1},
    {"numval",             1},
    {"output",             1},
    {"period",             1},
    {"pgwd",               0},
    {"plap",               0},
    {"precise",            1},
    {"pret",               0},
    {"pri",                1},
    {"pwrds",              1},
    {"quotenewnam",        1},
    {"raise",              0},
    {"rat",                0},
    {"ratarg",             0},
    {"rational",           0},
    {"rationalize",        0},
    {"ratpri",             1},
    {"reduced",            0},
    {"revpri",             0},
    {"rlisp88",            0},
    {"rootmsg",            0},
    {"roundall",           1},
    {"roundbf",            0},
    {"rounded",            0},
    {"savestructr",        0},
    {"solvesingular",      0},
    {"time",               0},
    {"trallfac",           0},
    {"trfac",              0},
    {"trint",              0},
    {"trroot",             0}
};

void CMainWindow::OnSwitch(UINT a)
{
    OnEnd();
    pageLine = caretLine;
    char q[100];
    int i;
    if (switch_name_p != NULL)
    {   int n = a - IDS_FIRSTSWITCH;
        if (n >= switch_name_n) return;
        i = switch_name_p[n][0] == 'y';
        switch_name_p[n][0] ^= ('y' ^ 'n');
// Note well - if the user types "on xxx" or "off xxx" directly rather than
// by using this mechanism then the checked or otherwise status of the menu
// items will get out of step. Tough luck. If you use the menu to set or clear
// a switch the switch will certainly be brought into line with what the
// menu information indicates.
        GetMenu()->CheckMenuItem(a, MF_BYCOMMAND |
                                    (i ? MF_UNCHECKED : MF_CHECKED));
        DrawMenuBar();
        sprintf(q, "%s %s;\n",
                i ? "off" : "on",
                1+switch_name_p[n]);
    }
    else
    {   i = switch_name[a - IDS_FIRSTSWITCH].status;
        switch_name[a - IDS_FIRSTSWITCH].status = !i;
// Note well - if the user types "on xxx" or "off xxx" directly rather than
// by using this mechanism then the checked or otherwise status of the menu
// items will get out of step. Tough luck. If you use the menu to set or clear
// a switch the switch will certainly be brought into line with what the
// menu information indicates.
        GetMenu()->CheckMenuItem(a, MF_BYCOMMAND |
                                    (i ? MF_UNCHECKED : MF_CHECKED));
        DrawMenuBar();
        sprintf(q, "%s %s;\n",
                i ? "off" : "on",
                switch_name[a - IDS_FIRSTSWITCH].name);
    }
    i = strlen(q);
    while (i-- != 0)
    {   int ch = q[i];
        UnTypeAhead(ch);
    }
    cwin_ensure_screen(FALSE);
}

#endif

void CMainWindow::OnExit()
{
    pageLine = caretLine;
    DestroyWindow();
}

int cwin_interrupt_pending = 0;

void CMainWindow::OnInterrupt()
{
    pageLine = caretLine;
    cwin_interrupt_pending = 1;
    cwin_ensure_screen(FALSE);
}

void CMainWindow::OnBacktrace()
{
    pageLine = caretLine;
    cwin_interrupt_pending = 3;
    cwin_ensure_screen(FALSE);
}

void CMainWindow::OnPageMode()
{
    pageLine = caretLine;
    pageMode = !pageMode;
    GetMenu()->CheckMenuItem(IDM_PAGEMODE,
        MF_BYCOMMAND | (pageMode ? MF_CHECKED : MF_UNCHECKED));
    DrawMenuBar();
    cwin_ensure_screen(FALSE);
}

#ifdef GRAPHICS_WINDOW
void CMainWindow::OnGraphics()
{
    graphicsWindow->ShowWindow(graphicsShown ? SW_HIDE : SW_SHOW);
    if (graphicsShown) graphicsWindow->viewpointWindow.ShowWindow(SW_HIDE);
    graphicsShown = !graphicsShown;
    if (graphicsShown) graphicsWindow->Invalidate();
    GetMenu()->CheckMenuItem(IDM_GRAPHICS,
        MF_BYCOMMAND | (graphicsShown ? MF_CHECKED : MF_UNCHECKED));
    DrawMenuBar();
    cwin_ensure_screen(FALSE);
}

void CMainWindow::OnGraphics1(UINT a, LONG b)
{
    OnGraphics();
}
#endif

void CMainWindow::ReWriteTitleText()
{
    if (hasFinished) return;
    CWindowDC dc(this);
// 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.
    CRect clientArea;
    GetClientRect(&clientArea);
    int wTitle = 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;
        }
#ifdef DISCARD_LEFT_IF_TITLE_OVERFULL
        if (padLeft <= 0 && padRight <= 0)
        {   strcpy(mainTitle, cMid);    // Abandon both right & left items
            break;
        }
#else
// Here even though there is no room to display the whole text I wanted
// on the title bar (maybe the window had been iconized?) I will display
// the left & mid parts with a single blank between.
        if (padLeft <= 0 && padRight <= 0)
        {   sprintf(mainTitle, "%s%c%s", cLeft, PADDING_CHAR, cMid);
            break;
        }
#endif
        else
        {
#ifdef DISCARD_LEFT_IF_TITLE_OVERFULL
            if (padLeft <= 0 && wLeft != 0)
            {   l = "";                       // Abandon left item & re-try
                wLeft = 0;
                continue;
            }
#endif
            if (padRight <= 0 && wRight != 0) // Abandon right item & re-try
            {   r = "";
                wRight = 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;
        }
    }
    SetWindowText(mainTitle);
}

void CMainWindow::cwin_report_left(const char *msg)
{
// I will take this opportunity to make the "To-File" menu item track
// whether there really is a spool file active. This only effects how
// the menu gets displayed, not what it does...
    if (complete)
    {   GetMenu()->ModifyMenu(IDM_TOFILE+MF_STRING, MF_BYCOMMAND, IDM_TOFILE,
                              spool_file==NULL ? "&To File ..." :
                              "&Terminate log");
        DrawMenuBar();
    }
    if (msg == NULL)
    {   leftSetByUser = FALSE;
        return;                         // Date and time of day will appear
    }
    strncpy(cLeft, msg, 31); cLeft[31] = 0;
    ReWriteTitleText();
    leftSetByUser = TRUE;
}

void cwin_report_left(const char *s)
{
    theApp.mainWindow->cwin_report_left(s);
}

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

void cwin_report_mid(const char *s)
{
    theApp.mainWindow->cwin_report_mid(s);
}

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

void cwin_report_right(const char *s)
{
    theApp.mainWindow->cwin_report_right(s);
}

void CMainWindow::cwin_display_date()
{
    char dateBuffer[20];
    time_t t0 = time(NULL);
    char *m = ctime(&t0);
#ifdef OLD_AND_USES_UNIVERSAL_TIME_WHICH_IS_SILLY
    sprintf(dateBuffer, "%02d-%.3s-%02d, %02d:%02d:%02d",
       titleUpdateTime.wDay,
       "xxxJanFebMarAprMayJunJulAugSepOctNovDec"+3*titleUpdateTime.wMonth,
       titleUpdateTime.wYear%100,
       titleUpdateTime.wHour, titleUpdateTime.wMinute,
       titleUpdateTime.wSecond);
#else
    sprintf(dateBuffer, "%.2s-%.3s-%.2s, %.8s",
                        m+8, m+4, m+22, m+11);
#endif
    cwin_report_left(dateBuffer);
    leftSetByUser = FALSE;
}

void cwin_menus(char **packages, char **switches)
{
    theApp.mainWindow->cwin_menus(packages, switches);
}

void CMainWindow::cwin_menus(char **packages, char **switches)
{
    CMenu *main = GetMenu();  // main menu bar for this window
    if (packages != NULL && *packages != NULL)
    {   library_name_p = packages;
        main->DeleteMenu(4, MF_BYPOSITION);
        int n = IDM_FIRSTLOAD;
        CMenu mload;
        mload.CreatePopupMenu();
        int firstletter = 'a';
        int lastletter = 'a', nextletter;
        int count = 0, nextcount;
        char **p = packages;
        library_name_n = 0;
        while (*p++ != NULL) library_name_n++;
        p = packages;
        while (*p && **p == lastletter) count++, p++;
        char **p1 = p;
        while (*packages)
        {   for (;;)
            {   nextcount = 0;
                nextletter = lastletter + 1;
                while (*p && **p == nextletter) nextcount++, p++;
                if (count + nextcount > 20) break;
                lastletter = nextletter;
                count += nextcount;
                p1 = p;
                if (lastletter == 'z') break;
            }
            char subname[8];
            if (firstletter == lastletter) sprintf(subname, "%c", firstletter);
            else sprintf(subname, "%c-%c", firstletter, lastletter);
            CMenu sub1;
            sub1.CreatePopupMenu();
            while (packages != p1)
                sub1.AppendMenu(MF_STRING, n++, *packages++);
            mload.AppendMenu(MF_STRING | MF_POPUP, (UINT)sub1.Detach(), subname);
            firstletter = lastletter = nextletter;
            count = nextcount;
            p1 = p;
        }
        main->InsertMenu(4, MF_BYPOSITION | MF_POPUP | MF_STRING, 
                         (UINT)mload.Detach(), "&Load");
    }
// Now do roughly the same with switches
    if (switches != NULL && *switches != NULL)
    {   switch_name_p = switches;
        main->DeleteMenu(5, MF_BYPOSITION);
        int n = IDS_FIRSTSWITCH;
        CMenu mload;
        mload.CreatePopupMenu();
        int firstletter = 'a';
        int lastletter = 'a', nextletter;
        int count = 0, nextcount;
        char **p = switches;
        switch_name_n = 0;
        while (*p++ != NULL) switch_name_n++;
        p = switches;
        while (*p && (*p)[1] == lastletter) count++, p++;
        char **p1 = p;
        while (*switches)
        {   for (;;)
            {   nextcount = 0;
                nextletter = lastletter + 1;
                while (*p && (*p)[1] == nextletter) nextcount++, p++;
                if (count + nextcount > 20) break;
                lastletter = nextletter;
                count += nextcount;
                p1 = p;
                if (lastletter == 'z') break;
            }
            char subname[8];
            if (firstletter == lastletter) sprintf(subname, "%c", firstletter);
            else sprintf(subname, "%c-%c", firstletter, lastletter);
            CMenu sub1;
            sub1.CreatePopupMenu();
            while (switches != p1)
            {   sub1.AppendMenu(MF_STRING, n, 1+*switches);
                sub1.CheckMenuItem(n, MF_BYCOMMAND |
                   (**switches=='y' ? MF_CHECKED : MF_UNCHECKED));
                n++;
                switches++;
            }
            mload.AppendMenu(MF_STRING | MF_POPUP, (UINT)sub1.Detach(), subname);
            firstletter = lastletter = nextletter;
            count = nextcount;
            p1 = p;
        }
        main->InsertMenu(5, MF_BYPOSITION | MF_POPUP | MF_STRING, 
                         (UINT)mload.Detach(), "&Switch");
    }
 
    DrawMenuBar();
}

// When the user types in a key all that happens (to start with) is that
// I store it in a buffer.  If the buffer gets to be full I beep and
// ignore any further characters.

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

void CMainWindow::OnChar(UINT ch, UINT nRepCnt, UINT nFlags)
{
// I turn the ENTER key into a newline character (and consequently ^M will
// also go that way). I also discard 0x7f (for the delete/erase key) and
// anything in the range 0x80 to 0xa0 (which I reserve for my own private
// use in the text buffer).  I ignore the repetition count.
// I also have to process some accelerators.
    pageLine = caretLine;
// The following line is in effect following a request from F J Wright. He
// pointed out that the character that gets pressed when one is in page mode
// to un-pause the screen should be allowed to do just that and should not
// also appear as an input character. So here if I find a character being
// typed when things are paused I just lose it (as well as clearing the
// flags that pause things.
    if (pagePaused) 
    {   pagePaused = FALSE;
        return;
    }
    inject_randomness(ch);
    inject_randomness((int)time(NULL));
    switch (ch)
    {
case Ctrl('C'):
        ToIcaret();
        caretVisible = TRUE;
        cwin_ensure_screen(FALSE);  // may jump screen to caret location
        OnInterrupt();
        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'):
        ToIcaret();
        caretVisible = TRUE;
        cwin_ensure_screen(FALSE);  // may jump screen to caret location
        OnBacktrace();
        return;
case Ctrl('I'):          // TAB  (I need to worry about the display of tabs!)
        break;
case Ctrl('J'):          // LINEFEED
        ch = '\n';
        break;
case Ctrl('L'):
        OnRedraw();
        return;
case Ctrl('M'):          // CARRIAGE RETURN
        ch = '\n';
case Ctrl('N'):
        OnEnd();         // so that CR accepts the line always
        break;
case Ctrl('O'):
        OnCopy();
        return;
case Ctrl('Q'):
        if (pageMode) OnPageMode();
        return;
case Ctrl('R'):
        OnReInput();
        return;
case Ctrl('S'):
        if (!pageMode) OnPageMode();
        return;
case Ctrl('V'):
        OnPaste();
        return;
case Ctrl('X'):
        OnCut();
        return;
case Ctrl('Z'):         // Some PC users may be used to Ctrl+Z as EOF?
        OnExit();
        return;
case 0x1b:              // ESC goes through to the user
        break;
default:
        if ((ch & 0x7f) < 0x20) return;  // Discard control chars.
        break;
    }
    CancelSelection();
    ToIcaret();
    caretVisible = TRUE;
    cwin_ensure_screen(FALSE);
// If the caret is NOT at the end of the text then the insert operation I do
// here goes directly into the text buffer. If the caret IS at the end of the
// text I push the character into a type-ahead buffer, and beep if there
// was no room for it there.
    if (caretChar != textLast)
    {   if (insertMode) cwin_caret_putchar(ch);
        else cwin_caret_replacechar(ch);
    }
    else
    {   int p2 = (typeAheadP2 + 1) & typeAheadBufferMask;
        if (p2 == typeAheadP1)
        {   ::MessageBeep(0xffffffff);
            return;
        }
        typeAheadBuffer[typeAheadP2] = ch;
        typeAheadP2 = p2;
    }
}

void CMainWindow::ReplaceLastLine(unsigned char *s)
{
    typeAheadP1 = typeAheadP2 = 0;  // cancel type-ahead
    selRootValid = FALSE;
    OnEnd();
    caretVisible = TRUE;
// The following loop deletes characters from the last line of the input,
// until what is left is (a) a totally empty buffer, (b) a buffer where the
// last line is empty or (c) the final thing in the buffer is a prompt
// item. While doing the deletion I will not update the screen at all.
    while (textFirst != textLast &&
           lineBuffer[lineLast].address != textLast)
    {   int n = (textLast-1) & TEXT_MASK;
        if ((textBuffer[n]&0xff)==CH_ENDPROMPT) break;
        if (textLast == caretChar) caretChar = n;
        if (textLast == icaretChar) icaretChar = n;
        if (textLast == inputLineStart) inputLineStart = n;
        textLast = n;
    }
    int i=savedLines[currentInputLine];
    for (;;)
    {   int ch = savedChars[i++];
        if (ch == '\n') break;
        if (i == MAX_SAVED_CHARS) i = 0;
        cwin_putchar(ch);
    }
    CRect cr(0, LineY(lineLast),
             clientWidth, LineY(lineLast)+LineDY(lineLast));
    InvalidateRect(&cr);
    LineSizes();
    UpdateWindow();
    cwin_ensure_screen(FALSE);
}

void CMainWindow::OnKeyDown(UINT ch, UINT nRepCnt, UINT nFlags)
{
    CPoint cp;
    int saveX, saveY;
    pageLine = caretLine;
    switch (ch)
    {
// The INS key flips the status with respect to insert mode. I make this
// TRUE at the start of a run, and any time anybody does a PASTE operation.
case VK_INSERT:
        insertMode = !insertMode;
        return;

// I make the DEL key delete forwards by first simulating a right-arrow
// cursor movement effect and then performing the sort of deletion that
// I consider ordinary.
case VK_DELETE:
        ToIcaret();
        caretVisible = TRUE;
        cwin_ensure_screen(FALSE);
        if (selStartChar != selEndChar)
        {   DeleteSelection();
            return;
        }
        if (caretChar == textLast) return;
        OnKeyDown(VK_RIGHT, 1, nFlags);
        if (caretChar != textLast)
        {   cwin_caret_unputchar();
            return;
        }
        if (typeAheadP1 == typeAheadP2)
        {   typeAheadBuffer[typeAheadP2] = 0x7f;
            typeAheadP2 = (typeAheadP2 + 1) & typeAheadBufferMask;
            return;
        }
        typeAheadP2 = (typeAheadP2 - 1) & typeAheadBufferMask;
        return;

case VK_BACK:
        ToIcaret();
        caretVisible = TRUE;
        cwin_ensure_screen(FALSE);  // may jump screen to caret location
// If there is a selection valid then DELETE will delete it, without any
// regard for where the caret is (although the caret will generally be
// at one or other end of the selected region).
        if (selStartChar != selEndChar)
        {   DeleteSelection();
            return;
        }
// If the caret is NOT at the end of the text then DELETE has to work
// inside the buffer. This will typically make it a fairly expensive
// operation.
        if (caretChar != textLast)
        {   cwin_caret_unputchar();
            return;
        }
// If there are no characters typed ahead I will put 0x7f into the
// input buffer. This case can arise in two different circumstances. The
// first is if a user types DELETE as the first character on a line. Then
// I will want to discard the junk characters later on. Otherwise it may be
// that the current line is partly displayed and is subject to interactive
// editing and then the 0x7f put in the buffer will be found again very
// soon and used to cause deletion of some prior character.  Because of this
// latter case I will not beep on initial DELETE characters on a line.
        if (typeAheadP1 == typeAheadP2)
        {   typeAheadBuffer[typeAheadP2] = 0x7f;
            typeAheadP2 = (typeAheadP2 + 1) & typeAheadBufferMask;
            return;
        }
// If there are typed-ahead characters I can just discard the most recent. At
// this stage the character has not been displayed (eg a user typed XXX DEL
// before the application was ready to look at it) so the removal is really
// very easy.
        typeAheadP2 = (typeAheadP2 - 1) & typeAheadBufferMask;
        return;
// The various keys like "PAGE UP" etc do just what the scroll bar can do.
case VK_PRIOR:
        OnVScroll(SB_PAGEUP, 0, NULL);
        return;
case VK_NEXT:
        OnVScroll(SB_PAGEDOWN, 0, NULL);
        return;
case VK_HOME:
        OnHome();
        return;
case VK_END:
        OnEnd();
        return;
// The cursor arrow keys are funny here, with behaviour inspired by the
// DOSKEY history program.  Right and left movement moves the caret, but
// up and down will bring back one of a number of stored input line.
case VK_UP:
// If my caret is on the last line I will do something DOSKEY-ish...
        if (caretLine == lineLast)
        {   if (currentInputLine == savedP1) return;
            if (currentInputLine == -1) currentInputLine = savedP2;
            if (currentInputLine == 0) currentInputLine = MAX_SAVED_LINES;
            currentInputLine--;
            ReplaceLastLine(&savedChars[savedLines[currentInputLine]]);
            return;
        }
        if (caretLine == lineFirst) return;
        if (caretLine == lineVisible) OnVScroll(SB_LINEUP, 0, NULL);
        cp.x = caretX-xOffset;
        cp.y = caretY - 1;
        FindMouseChar(cp);
        caretVisible = TRUE;
        cwin_ensure_screen(FALSE);
        return;
case VK_DOWN:
// If my caret is on the last line I will do something DOSKEY-ish...
        if (caretLine == lineLast)
        {   int n = currentInputLine + 1;
            if (n == MAX_SAVED_LINES) n = 0;
            if (currentInputLine == -1 || n == savedP2) return;
            currentInputLine = n;
            ReplaceLastLine(&savedChars[savedLines[currentInputLine]]);
            return;
        }
        cp.x = caretX-xOffset;
        saveY = caretY, saveX = caretX;
        cp.y = caretY + LineDY(caretLine) + 1;
// The case when I am at the bottom of the screen and hit the "DOWN" key
// is a bit of a mess. I call FindMouse Char as usual to try to re-position
// the caret. If that would be outside the window I get a flag set to tell me
// so. In that case I re-position the caret where it just came from, then
// scroll the window, and finally repeat my attempt to re-position the
// caret.
        FindMouseChar(cp);
        if (mouseOutside & 8)
        {   cp.x = saveX-xOffset;
            cp.y = saveY + 1;
            FindMouseChar(cp);
            OnVScroll(SB_LINEDOWN, 0, NULL);
            cp.x = caretX-xOffset;
            cp.y = caretY + LineDY(caretLine) + 1;
            FindMouseChar(cp);
        }
        caretVisible = TRUE;
        cwin_ensure_screen(FALSE);
        return;
case VK_LEFT:
        caretVisible = TRUE;
        if (textFirst == caretChar) return;             // at start of buffer?
        if (lineBuffer[caretLine].address == caretChar) // back over a newline?
        {   int n = (caretLine-1)&LINE_MASK;
            caretX = lineBuffer[n].width;
            caretLine=n;
            n = lineBuffer[n].address;
            while (textBuffer[n]!='\n') n = (n+1)&TEXT_MASK;
            caretChar = n;
        }
        else
        {   int n = (caretChar-1) & TEXT_MASK;
            int w = windowFonts.HCourier.across['X'];
            if ((textBuffer[n]&0xff)==CH_ENDPROMPT)   // move over whole of a prompt
            {   do
                {   n = (n-1)&TEXT_MASK;
                    caretX -= w;
                } while (n!=textFirst && (textBuffer[n]&0xff)!=CH_PROMPT);
                caretChar = n;
                if (lineBuffer[caretLine].address == caretChar) // back over a newline?
                {   n = (caretLine-1)&LINE_MASK;
                    caretX = lineBuffer[n].width;
                    caretLine=n;
                    n = lineBuffer[n].address;
                    while (textBuffer[n]!='\n') n = (n+1)&TEXT_MASK;
                    caretChar = n;
                }
            }
            else caretX -= w;
            if (caretLine == lineLast) icaretChar = n, icaretLine = lineLast;
            caretChar = n;
// The amount of invalidation done here runs across to the end of the
// last line, and that ought to get rid of any residues of the old
// caret. I use an APPROXIMATION to the new value of caretX, but hope that
// that gets corrected during the re-painting.
            CRect cr(caretX-xOffset, LineY(caretLine),
                     clientWidth, LineY(caretLine)+LineDY(caretLine));
            InvalidateRect(&cr);  // Should I hide the caret here?
        }
        UpdateWindow();
        cwin_ensure_screen(FALSE);
        return;
case VK_RIGHT:
        caretVisible = TRUE;
        if (textLast == caretChar) return;              // at end of buffer?
        if (textBuffer[caretChar] == '\n')              // at a newline?
        {   caretX = 0;
            int w = windowFonts.HCourier.across['X'];
            caretLine = (caretLine+1)&LINE_MASK;
            caretChar = lineBuffer[caretLine].address;
            if (caretChar!=textLast &&
                (textBuffer[caretChar]&0xff)==CH_PROMPT)   // move over whole of a prompt
            {   while (caretChar!=textLast && 
                       (textBuffer[caretChar]&0xff)!=CH_ENDPROMPT)
                {   caretChar = (caretChar+1)&TEXT_MASK;
                    caretX += w;
                }
                caretX -= w;
                if (caretChar!=textLast) caretChar = (caretChar+1)&TEXT_MASK;
            }
            CPoint caretPos(caretX-xOffset, caretY);
            SetCaretPos(caretPos);
            if (caretLine == lineLast)
            {   icaretLine = caretLine;
                icaretChar = caretChar;
                icaretX = caretX;
            }
        }
        else
        {   int n = caretChar;
            int oldCaretX = caretX;
            int w = windowFonts.HCourier.across['X'];
            n = (n+1)&TEXT_MASK;
            caretX += w;
            if (caretLine == lineLast) icaretChar = n, icaretLine = caretLine;
            caretChar = n;
            CRect cr(oldCaretX-xOffset, LineY(caretLine),
                     caretX-xOffset+w, LineY(caretLine)+LineDY(caretLine));
            InvalidateRect(&cr);  // Should I hide the caret here?
        }
        UpdateWindow();
        cwin_ensure_screen(FALSE);
        return;
    }
}


// The next function takes a mouse position and identifies the character in
// the text buffer that it identifies.  It must be kept well in step with
// the corresponding code in PaintTextLine().

int CMainWindow::FindMouseChar(CPoint point)
{
// I may have captured the mouse, in which case the coordinates returned may
// be outside my client area. Clip them to it before doing anything else.
    int x = point.x, y = point.y;
    int nCount = 0;
    mouseOutside = 0;
    if (x<0) x=0, mouseOutside |= 1;
    else if (x>clientWidth) x=clientWidth, mouseOutside |= 2;
    if (y<0) y=0, x=0, mouseOutside |= 4;
    else if (y>clientHeight) y=clientHeight, x=clientWidth, mouseOutside |= 8;
    if (mouseOutside == 0) selectScrollSpeed = 0;
    int yOffset = lineBuffer[lineVisible].position;
    int line;
    for (line=lineVisible;;line=(line+1)&LINE_MASK)
    {   int y1 = lineBuffer[line].position - yOffset;
        int y2 = y1 + lineBuffer[line].height;
        if (y>=y1 && y<=y2) break;
        if (line==lineLast) break;
    }
// Now I have identified the line that the mouse cursor is on.
    int textChar = lineBuffer[line].address;
    int activeFont = CH_COURIER;
    unsigned char *activeWidths = windowFonts.HCourier.across;
    int xc = -xOffset;
    int inPrompt = -1, inPromptX;
    for (;;textChar=(textChar+1)&TEXT_MASK)
    {   if (textChar==textLast) break;
        int c = textBuffer[textChar];
        int w = activeWidths[c & 0xff];
        switch (c & 0xff)
        {
    case '\n':
            break;
    case CH_PROMPT:
            inPrompt = textChar, inPromptX = xc;
            continue;
    case CH_ENDPROMPT:
            inPrompt = -1;
            continue;
    case CH_RED:
//    case CH_BLUE:
//    case CH_BLACK:
    case CH_GRAY:
            continue;
    case CH_COURIER:
            activeFont = c;
            activeWidths = windowFonts.HCourier.across;
            continue;
    case CH_ROMAN:
            activeFont = c;
            activeWidths = windowFonts.HRoman.across;
            continue;
    case CH_BOLD:
            activeFont = c;
            activeWidths = windowFonts.HBold.across;
            continue;
    case CH_ITALIC:
            activeFont = c;
            activeWidths = windowFonts.HItalic.across;
            continue;
    case CH_SYMBOL:
            activeFont = c;
            activeWidths = windowFonts.HSymbol.across;
            continue;
    case CH_Roman:
            activeFont = c;
            activeWidths = windowFonts.Hroman.across;
            continue;
    case CH_Bold:
            activeFont = c;
            activeWidths = windowFonts.Hbold.across;
            continue;
    case CH_Italic:
            activeFont = c;
            activeWidths = windowFonts.Hitalic.across;
            continue;
    case CH_Symbol:
            activeFont = c;
            activeWidths = windowFonts.Hsymbol.across;
            continue;
    default:
            if (c == '\t')
            {   c = ' ';
                w = currentWidths[' ']*(8-(nCount&7));
                nCount = 7;
            }
            else if (c < 32) w = activeWidths['^'] + activeWidths[c & 0xff],
                             nCount++;
            if (x <= xc+w/2)
            {
// I will not allow the user to select a position within a prompt, since
// if I did it would make it possible to CUT or DELete part of the prompt
// string, including either the control character that marked its start or
// its end. The result would be that the status of the text as "prompt" would
// be lost with possible consequent confusion.
                if (inPrompt>=0) textChar=inPrompt, xc=inPromptX;
                break;   // Bingo! Found it.
            }
            xc += w;
            nCount++;
            continue;
        }
        break;
    }
// If the mouse click moves me to the last line then I place the
// input caret there. Otherwise if a previous caret had been on the last line
// I leave the input caret where the previous caret had been.
    if (line == lineLast)
    {   icaretChar = textChar;
        icaretX = xc+xOffset;
        icaretFontWidths = activeWidths;
        icaretLine = line;
    }
    else if (caretLine == lineLast)
    {   icaretChar = caretChar;
        icaretX = caretX;
        icaretFontWidths = caretFontWidths;
        icaretLine = caretLine;
    }
    caretChar = textChar;
    caretX = xc+xOffset;
    caretFontWidths = activeWidths;
    caretLine = line;
    inject_randomness(caretX);
    inject_randomness(caretLine);
// In the case that mouse activity re-positions the caret I will move the
// caret on the screen directly to where it needs to be shown. No benefit
// would come from delaying until a future re-paint operation.
    HideCaret();
    CPoint caretPos(caretX-xOffset, caretY);
    SetCaretPos(caretPos);
    ShowCaret();
    return caretChar;
}

// StartSelection is slightly curious in that it sets the selection
// to be empty but rooted at a specific point. The effect will be that
// a subsequent MouseMove or shift-click can extend the selection to
// be non-trivial.  The ugly feature of this is that a selection that has
// been "started" in this way could suffer if the contents of the text buffer
// moved. To prevent trouble I will keep a flag that indicates when I have
// a selection starting-point that I will count as valid. I will set this
// flag here and clear it anywhere where I move or destroy bits of the
// buffer.

void CMainWindow::StartSelection()
{
    CancelSelection();
    selFirstChar = selStartChar = selEndChar = caretChar;
    selFirstX = selStartX = selEndX = caretX;
    selFirstLine = selStartLine = selEndLine = caretLine;
    selRootValid = TRUE;
}

void CMainWindow::InvalidateSelection(int l1, int x1, int l2, int x2)
{
    int w;
    if (l1 > l2)
    {   w=l1, l1=l2, l2=w, w=x1, x1=x2, x2=w;
        if (x2 == 0) l2=(l2-1)&LINE_MASK, x2=clientWidth+xOffset;
    }
    HideCaret();
    if (l1==l2)
    {   if (x1>x2) w=x1, x1=x2, x2=w;
        CRect r(x1-xOffset, LineY(l1),
                x2+caretWidth-xOffset, LineY(l1)+LineDY(l1));
        if (x1 != x2) InvalidateRect(&r);
    }
    else
    {   CRect r(0, LineY(l1), clientWidth, LineY(l2)+LineDY(l2));
        InvalidateRect(&r);
    }
    ShowCaret();
}

void CMainWindow::ExtendSelection()
{
    int oldStart = selStartChar, oldEnd = selEndChar;
    if (selFirstChar == selStartChar)
        InvalidateSelection(selEndLine, selEndX, caretLine, caretX);
    else InvalidateSelection(selStartLine, selStartX, caretLine, caretX);
    if (betweenChar(textFirst, caretChar, selFirstChar))
    {   selStartChar = caretChar;     selStartX = caretX;  selStartLine = caretLine;
        selEndChar = selFirstChar; selEndX = selFirstX; selEndLine = selFirstLine;
    }
    else
    {   selStartChar = selFirstChar; selStartX = selFirstX; selStartLine = selFirstLine;
        selEndChar = caretChar;         selEndX = caretX;      selEndLine = caretLine;
    }
    if (selStartChar==oldStart && selEndChar==oldEnd) return;
}

void CMainWindow::CancelSelection()
{
    InvalidateSelection(selStartLine, selStartX, selEndLine, selEndX);
    selFirstChar = selStartChar = selEndChar = caretChar;
    selFirstX = selStartX = selEndX = caretX;
    selFirstLine = selStartLine = selEndLine = caretLine;
}

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


void CMainWindow::OnLButtonDown(UINT nFlags, CPoint point)
{
/*
 * The idea here is that a first click within a window should NOT move
 * the caret or start a new selection, since it was probably (?) the
 * mouse event that just caused the window to become active again.
 */
    if (pagePaused || hasFocus) return;
    FindMouseChar(point);
    trackingSelection = TRUE;
    SetCapture();
    selectScrollSpeed = 0;
    if (nFlags & MK_SHIFT && selRootValid) ExtendSelection();
    else StartSelection();
}


void CMainWindow::OnLButtonUp(UINT nFlags, CPoint point)
{
    if (hasFocus)
    {   hasFocus = FALSE;
        return;
    }
    if (pagePaused) pageLine = caretLine;
    else
    {   FindMouseChar(point);
        ::ReleaseCapture();
        if (trackingSelection) ExtendSelection();
        trackingSelection = FALSE;
        selectScrollSpeed = 0;
    }
}


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


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


void CMainWindow::OnMButtonUp(UINT nFlags, CPoint point)
{
    if (hasFocus)
    {   hasFocus = FALSE;
        return;
    }
    pageLine = caretLine;
}


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


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


void CMainWindow::OnRButtonUp(UINT nFlags, CPoint point)
{
    if (hasFocus)
    {   hasFocus = FALSE;
        return;
    }
    pageLine = caretLine;
}


void CMainWindow::OnMouseMove(UINT nFlags, CPoint point)
{
    inject_randomness((int)clock());
    if (trackingSelection && !pagePaused)
    {   FindMouseChar(point);
        if (mouseOutside != 0)
        {   if (selectScrollSpeed == 0)
                selectScrollSpeed = 7,
                selectScrollCount = 1;
            selectScrollDirection = mouseOutside;
            selectScrollPoint = point;
            OnTimer(1);
        }
        else ExtendSelection();
    }
}


void CMainWindow::OnTimer(UINT timerId)
{
    if (selectScrollSpeed == 0) return;
    selectScrollCount--;
    if (selectScrollCount > 0) return;
    if (selectScrollDirection == 0) return;
    if (selectScrollDirection & 8) OnVScroll(SB_LINEDOWN, 0, NULL);
    else if (selectScrollDirection & 4) OnVScroll(SB_LINEUP, 0, NULL);
    else if (selectScrollDirection & 1 && xOffset!=0)
        OnHScroll(SB_LINELEFT, 0, NULL);
    else if (selectScrollDirection & 2 &&
             caretX < lineBuffer[caretLine].width)
        OnHScroll(SB_LINERIGHT, 0, NULL);
    selectScrollCount = selectScrollSpeed;
    if (selectScrollSpeed > 1) selectScrollSpeed--;
    FindMouseChar(selectScrollPoint);
    ExtendSelection();
}

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


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


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



//
// Now the things provoked from the menus...
//

// SAVEAS will dump everything that is in the text buffer into a
// log file. The status of prompts and any other funny stuff will not
// concern me here - I will just dump out bytes as they appear in the
// text buffer. But at least I will arrange that I do not dump any characters
// that have a code >= 0x80 into the saved file.

void CMainWindow::OnSaveAs()
{
    CFileDialog FDialog(FALSE, "LOG", "savefile.log",
        OFN_HIDEREADONLY,
        "Log Files (*.LOG)|*.LOG|All Files (*.*)|*.*||", NULL);
    FDialog.m_ofn.lpstrTitle = "Save As";
    if (FDialog.DoModal() != IDOK) return;
    FILE *ofile = fopen(FDialog.GetPathName(), "w");
    if (ofile == NULL)
    {   DisplayMsg("Could not write to file");
        cwin_ensure_screen(FALSE);
        return;
    }
    for (int i=lineFirst; i!=lineLast; i=(i+1)&LINE_MASK)
    {   int p = lineBuffer[i].address, c;
        while ((c = textBuffer[p]) != '\n')
        {   if ((c & 0x80) == 0) putc(c, ofile);
            p = (p+1) & TEXT_MASK;
        }
        putc('\n', ofile);
    }
    int p = lineBuffer[lineLast].address;
    while (p != textLast)
    {   int c = textBuffer[p];
        if ((c & 0x80) == 0) putc(c, ofile);
        p = (p+1) & TEXT_MASK;
    }
    putc('\n', ofile);  // extra newline at end to be tidy.
    fclose(ofile);
    cwin_ensure_screen(FALSE);
}

void CMainWindow::OnSaveSel()
{
    CFileDialog FDialog(FALSE, "LOG", "savesel.log",
        OFN_HIDEREADONLY,
        "Log Files (*.LOG)|*.LOG|All Files (*.*)|*.*||", NULL);
    FDialog.m_ofn.lpstrTitle = "Save Selection";
    if (FDialog.DoModal() != IDOK) return;
    FILE *ofile = fopen(FDialog.GetPathName(), "w");
    if (ofile == NULL)
    {   DisplayMsg("Could not write to file");
        cwin_ensure_screen(FALSE);
        return;
    }
// print selected region...
    int l1 = selStartLine;
    int cp = selStartChar;
    for (;;)
    {   while (cp!=selEndChar && textBuffer[cp]!='\n')
        {   int c = textBuffer[cp];
            if ((c & 0x80) == 0) putc(c, ofile);
            cp = (cp+1)&TEXT_MASK;
        }
        if (cp==selEndChar) break;
        putc('\n', ofile);
        l1 = (l1+1)&LINE_MASK;
        cp = lineBuffer[l1].address;
    }
    fclose(ofile);
    cwin_ensure_screen(FALSE);
}

void CMainWindow::OnToFile()
{
// 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.
//
// NOTE that the "spool_file" mentioned here belongs somewhere in CSL,
// and is not in the "cwin" code in any more direct way.
    if (spool_file != NULL)
    {   fprintf(spool_file, "\n+++ End of transcript +++\n");
        fclose(spool_file);
        spool_file = NULL;
        GetMenu()->ModifyMenu(IDM_TOFILE+MF_STRING, MF_BYCOMMAND,
                              IDM_TOFILE, "&To File...");
        DrawMenuBar();
        cwin_ensure_screen(FALSE);
        return;
    }
    CFileDialog FDialog(FALSE, "LOG", "logfile.log",
        OFN_HIDEREADONLY,
        "Log Files (*.LOG)|*.LOG|All Files (*.*)|*.*||", NULL);
    FDialog.m_ofn.lpstrTitle = "Transcript File";
    if (FDialog.DoModal() != IDOK) return;
    FILE *ofile = fopen(FDialog.GetPathName(), "w");
    if (ofile == NULL)
    {   DisplayMsg("Could not write to file");
        cwin_ensure_screen(FALSE);
        return;
    }
    spool_file = ofile;
    time_t t0 = time(NULL);
    fprintf(spool_file, "+++ Transcript started at %.24s +++\n", ctime(&t0));
    GetMenu()->ModifyMenu(IDM_TOFILE+MF_STRING, MF_BYCOMMAND,
                          IDM_TOFILE, "&Terminate log");
    DrawMenuBar();
    cwin_ensure_screen(FALSE);
}

static CFont *MakeNewFont(CDC *dc, FontHeights *fh, DWORD charSet,
                          const char *fontName, int weight,
                          DWORD italic, int height)
{
    CFont *newFont = new CFont;
    newFont->CreateFont(
        height, 0,        // height, width
        0, 0,             // angle of escapement, base line orientation angle
        weight, italic,   // weight, italic-flag
        0, 0, charSet,    // underline, strike-out, character-set
        OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS,
        DEFAULT_QUALITY, DEFAULT_PITCH+FF_DONTCARE,
        fontName);
    dc->SelectObject(newFont);
    TEXTMETRIC mm;
    dc->GetTextMetrics(&mm);
    fh->up = mm.tmExternalLeading + mm.tmAscent;
    fh->down = mm.tmDescent;
    fh->height = mm.tmHeight;
    int widths[256], i;
    dc->GetCharWidth(0, 255, widths);
    for (i=0x80; i<0xa0; i++) widths[i] = 0; // my control characters
// The width of a character in any sensible sized font will be less than
// 256 pixels, so I save some space by stashing the information in a byte
// array rather than a word array.  If I really knew I was only ever going to
// use TrueType fonts maybe I ought to use ABCWidths rather than just
// widths. At present I think that would count as overkill.
    for (i=0; i<256; i++) fh->across[i] = (unsigned char)widths[i];
    return newFont;
}

void CMainWindow::OnPrint()
{
    CPrintDialog pd(FALSE);
    if (pd.DoModal() != IDOK)
    {   cwin_ensure_screen(FALSE);
        return;
    }
    CDC PrintDC;
    if (PrintDC.CreateDC(pd.GetDriverName(), pd.GetDeviceName(),
                         pd.GetPortName(), NULL))
    {   int printWidth = PrintDC.GetDeviceCaps(HORZRES),
            printHeight = PrintDC.GetDeviceCaps(VERTRES);
// Also get the size of the print area in mm.
        int sizeX = PrintDC.GetDeviceCaps(HORZRES),  // measured in pixels
            sizeY = PrintDC.GetDeviceCaps(VERTRES);
        int mmX = PrintDC.GetDeviceCaps(HORZSIZE),   // measured in mm
            mmY = PrintDC.GetDeviceCaps(VERTSIZE);
// I will try to allow a 10mm margin to right & left and a 15mm at top
//and bottom of the printed page.
        int offsetX = (10*sizeX)/mmX;
        int offsetY = (15*sizeY)/mmY;
        sizeX -= 2*offsetX;
        sizeY -= 2*offsetY;

// Create printer fonts, using the same type face as is present on the
// screen.  Scale it so that around 80 columns will fit across the page.
// I scale for 81 not 80 to leave a margin for rounding etc etc.
        FontHeights fh;
        CFont *f = MakeNewFont(&PrintDC, &fh, ANSI_CHARSET,
                               (char *)windowFonts.baseFace,
                               windowFonts.boldFlag, 0, 100);
        PrintDC.SelectStockObject(SYSTEM_FONT);
        delete f;
        int X = fh.across['X'];
        int newH = (fh.height * sizeX)/(81 * X);
        printerFonts.ChangeFont(&PrintDC, newH,
                                windowFonts.boldFlag, windowFonts.baseFace);

        DOCINFO DocInfo;
        DocInfo.cbSize = sizeof(DOCINFO);
        DocInfo.lpszDocName = "Screen Dump";
        DocInfo.lpszOutput = NULL;
        if (PrintDC.StartDoc(&DocInfo) != -1)
        {
            PrintDC.StartPage();
            PrintDC.SetTextAlign(TA_BASELINE);
            PrintDC.SetTextColor(RGB(0,0,0));          currentColour = CH_BLACK;
            PrintDC.SetBkColor(windowColour);
            PrintDC.SelectObject(printerFonts.Courier);currentFont = CH_COURIER;
            currentWidths = printerFonts.HCourier.across;

            int y = offsetY, line;
            for (line=lineFirst;; line=(line+1)&LINE_MASK)
            {   int up, down;
                MeasureLine(lineBuffer[line].address,
                            &up, &down, &printerFonts);
                int y1 = y+up;
                int y2 = y+up+down;
                if (y2 > sizeY)
                {    PrintDC.EndPage();
                     PrintDC.StartPage();
                     PrintDC.SetTextAlign(TA_BASELINE);
                     PrintDC.SetTextColor(RGB(0,0,0));          currentColour = CH_BLACK;
                     PrintDC.SetBkColor(windowColour);
                     PrintDC.SelectObject(printerFonts.Courier);currentFont = CH_COURIER;
                     currentWidths = printerFonts.HCourier.across;
                     y = offsetY;
                     y1 = y+up;
                     y2 = y+up+down;
                }
                PaintTextLine(&PrintDC, offsetX, y, y1, y2,
                              lineBuffer[line].address,
                              sizeX, &printerFonts, 1);
                y = y2;
                if (line==lineLast) break;
            }
            PrintDC.SelectStockObject(BLACK_PEN);
            PrintDC.EndPage();
            PrintDC.EndDoc();
        }
        printerFonts.DeleteFonts();
    }
    GlobalFree(pd.m_pd.hDevMode);
    GlobalFree(pd.m_pd.hDevNames);
    cwin_ensure_screen(FALSE);
}

void CMainWindow::OnPrintSel()
{
    if (selStartChar == selEndChar) return; // nothing selected!
    CPrintDialog pd(FALSE);
    if (pd.DoModal() != IDOK)
    {   cwin_ensure_screen(FALSE);
        return;
    }
    CDC PrintDC;
    if (PrintDC.CreateDC(pd.GetDriverName(), pd.GetDeviceName(),
                         pd.GetPortName(), NULL))
    {   int printWidth = PrintDC.GetDeviceCaps(HORZRES),
            printHeight = PrintDC.GetDeviceCaps(VERTRES);
// Also get the size of the print area in mm.
        int sizeX = PrintDC.GetDeviceCaps(HORZRES),  // measured in pixels
            sizeY = PrintDC.GetDeviceCaps(VERTRES);
        int mmX = PrintDC.GetDeviceCaps(HORZSIZE),   // measured in mm
            mmY = PrintDC.GetDeviceCaps(VERTSIZE);
// I will try to allow a 10mm margin to right & left and a 15mm at top
//and bottom of the printed page.
        int offsetX = (10*sizeX)/mmX;
        int offsetY = (15*sizeY)/mmY;
        sizeX -= 2*offsetX;
        sizeY -= 2*offsetY;

// Create printer fonts, using the same type face as is present on the
// screen.  Scale it so that around 80 columns will fit across the page.
// I scale for 81 not 80 to leave a margin for rounding etc etc.
        FontHeights fh;
        CFont *f = MakeNewFont(&PrintDC, &fh, ANSI_CHARSET,
                               (char *)windowFonts.baseFace,
                               windowFonts.boldFlag, 0, 100);
        PrintDC.SelectStockObject(SYSTEM_FONT);
        delete f;
        int X = fh.across['X'];
        int newH = (fh.height * sizeX)/(81 * X);
        printerFonts.ChangeFont(&PrintDC, newH,
                                windowFonts.boldFlag, windowFonts.baseFace);

        DOCINFO DocInfo;
        DocInfo.cbSize = sizeof(DOCINFO);
        DocInfo.lpszDocName = "Selection Dump";
        DocInfo.lpszOutput = NULL;
        if (PrintDC.StartDoc(&DocInfo) != -1)
        {
            PrintDC.StartPage();
            PrintDC.SetTextAlign(TA_BASELINE);
            PrintDC.SetTextColor(RGB(0,0,0));          currentColour = CH_BLACK;
            PrintDC.SetBkColor(windowColour);
            PrintDC.SelectObject(printerFonts.Courier);currentFont = CH_COURIER;
            currentWidths = printerFonts.HCourier.across;

            int y = offsetY, line;
            for (line=selStartLine;; line=(line+1)&LINE_MASK)
            {   int up, down;
                MeasureLine(lineBuffer[line].address,
                            &up, &down, &printerFonts);
                int y1 = y+up;
                int y2 = y+up+down;
                if (y2 > sizeY)
                {    PrintDC.EndPage();
                     PrintDC.StartPage();
                     PrintDC.SetTextAlign(TA_BASELINE);
                     PrintDC.SetTextColor(RGB(0,0,0));          currentColour = CH_BLACK;
                     PrintDC.SetBkColor(windowColour);
                     PrintDC.SelectObject(printerFonts.Courier);currentFont = CH_COURIER;
                     currentWidths = printerFonts.HCourier.across;
                     y = offsetY;
                     y1 = y+up;
                     y2 = y+up+down;
                }
                PaintTextLine(&PrintDC, offsetX, y, y1, y2,
                              lineBuffer[line].address,
                              sizeX, &printerFonts, 2);
                y = y2;
                if (line==selEndLine) break;
            }
            PrintDC.SelectStockObject(BLACK_PEN);
            PrintDC.EndPage();
            PrintDC.EndDoc();
        }
        printerFonts.DeleteFonts();
    }
    GlobalFree(pd.m_pd.hDevMode);
    GlobalFree(pd.m_pd.hDevNames);
    cwin_ensure_screen(FALSE);
}

void CMainWindow::OnCut()
{
    OnCopy();
    DeleteSelection();
    cwin_ensure_screen(FALSE);
}

void CMainWindow::DeleteSelection()
{
    int p = selStartChar, q = selEndChar;
    if (betweenChar(p, caretChar, q))
    {   caretChar = p;
        caretLine = selStartLine;
// In general after a delete operation caretX will not be valid, but the
// paint operation puts that right soon enough.
//--    caretX = selStartX;
    }
    if (betweenChar(p, icaretChar, q))
    {   icaretChar = p;
        icaretLine = selStartLine;
// In general after a delete operation icaretX will not be valid, but the
// paint operation puts that right soon enough.
//--    icaretX = selStartX;
    }
    if (selStartLine==selEndLine)
    {   while (q!=textLast)
        {   int c = textBuffer[q];
            textBuffer[p] = c;
            if (caretChar==q) caretChar = p;
            if (icaretChar==q) icaretChar = p;
            if (inputLineStart==q) inputLineStart=p;
            if (c == '\n') break;
            p = (p+1)&TEXT_MASK;
            q = (q+1)&TEXT_MASK;
        }
        if (textLast==q) textLast = p;
        if (inputLineStart==q) inputLineStart=p;
        if (caretChar==q) caretChar=p;
        if (icaretChar==q) icaretChar=p;
// The caret will have its position re-calculated during the re-draw, and also
// the line-length record will be adjusted then.
        InvalidateSelection(selStartLine, selStartX,
                            selStartLine, clientWidth+xOffset);
    }
    else
    {   int l = selStartLine, l1 = selEndLine;
        lineLast = l;
        for (;;)
        {   while (q!=textLast)
            {   int c = textBuffer[q];
                textBuffer[p] = c;
                if (caretChar==q) caretChar=q, caretLine = l;
                if (inputLineStart==q) inputLineStart=p;
                if (icaretChar==q) icaretChar=p, icaretLine = l;
                p = (p+1)&TEXT_MASK;
                q = (q+1)&TEXT_MASK;
                if (c=='\n') break;
            }
            if (q==textLast) break;
            l1 = (l1+1)&LINE_MASK;
            q = lineBuffer[l1].address;
            l = (l+1)&LINE_MASK;
            lineBuffer[l].address = p;
            lineLast = l;
        }
        if (inputLineStart==q) inputLineStart=p;
        if (caretChar==q) caretChar=p;
        if (icaretChar==q) caretChar=p;
        textLast = p;
        LineSizes();
// I invalidate down to the bottom of the screen. In some cases maybe I
// count do a ScrollWindow to good effect, and the plain invalidation
// here may cause the screen to flicker more than would be ideal.
        CRect cr(0, LineY(selStartLine), clientWidth, clientHeight);
        InvalidateRect(&cr);
    }
    selFirstChar = selEndChar = selStartChar;
    selFirstX = selEndX = selStartX;
    selFirstLine = selEndLine = selStartLine;
// The following sequence of operations is intended to scroll the window
// (if necessary) to make the caret visible, to get scroll bar thumbs
// up to date (eg after a multi-line delete) etc.
    UpdateWindow();
    OnSize(SIZE_RESTORED, clientWidth, clientHeight);
    HideCaret();
    CPoint caretPos(caretX-xOffset, caretY);
    SetCaretPos(caretPos);
    ShowCaret();
}

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.  If the text buffer contains funny
// characters that are used to indicate font changes etc I will just leave
// them in there for the moment.
    if (!OpenClipboard()) return;
    if (!::EmptyClipboard()) return;  // Take control of clipboard

    int l1 = selStartLine;
    int cp = selStartChar, size=1;
    for (;;)
    {   while (cp!=selEndChar && textBuffer[cp]!='\n')
        {   cp = (cp+1)&TEXT_MASK;
            size++;
        }
        if (cp==selEndChar) break;
        size=size+2;
        l1 = (l1+1)&LINE_MASK;
        cp = lineBuffer[l1].address;
    }
    HGLOBAL h = ::GlobalAlloc(GMEM_MOVEABLE+GMEM_DDESHARE, size);
    HGLOBAL h1 = 0;
    if (clipboardformat != 0)
        h1 = ::GlobalAlloc(GMEM_MOVEABLE+GMEM_DDESHARE, size);
    char *p = NULL, *p1 = NULL;
    if (h != NULL) p = (char *)::GlobalLock(h);
    if (h1 != NULL) p1 = (char *)::GlobalLock(h1);
    if (p != NULL)
    {   l1 = selStartLine;
        cp = selStartChar;
        for (;;)
        {   while (cp!=selEndChar && textBuffer[cp]!='\n')
            {   int c = textBuffer[cp];
                if ((c&0xff) != CH_PROMPT && (c&0xff) != CH_ENDPROMPT) *p++ = c;
                if (p1 != NULL) *p1++ = c;
                cp = (cp+1)&TEXT_MASK;
                size++;
            }
            if (cp==selEndChar) break;
            *p++ = '\r'; *p++ = '\n';   // CR/LF combination at end of line
            if (p1 != NULL)
            {   *p1++ = '\r';
                *p1++ = '\n';
            }
            l1 = (l1+1)&LINE_MASK;
            cp = lineBuffer[l1].address;
        }
        *p = 0;
        ::GlobalUnlock(h);
        if (p1 != NULL)
        {   *p1 = 0;
            ::GlobalUnlock(h1);
            ::SetClipboardData(clipboardformat, h1);
        }
        ::SetClipboardData(CF_TEXT, h);
        
    }
    ::CloseClipboard();
    cwin_ensure_screen(FALSE);
}

void CMainWindow::OnPaste()
{
    HGLOBAL clipboardInputHandle;
    char *w;
    ToIcaret();
    caretVisible = TRUE;
    cwin_ensure_screen(FALSE);  // may jump screen to caret location
    pageLine = caretLine;
    insertMode = TRUE;
    if (!OpenClipboard()) return;
// If I get a new PASTE operation before an earlier one has quite finished
// I will just abandon the end of the old one without further fuss.
    if (clipboardInput != NULL) free(clipboardInput);
// If I had managed to register my own format I will use data in that format
// in preference to text. Otherwise I will just grab text.
    if (clipboardformat != 0)
        clipboardInputHandle = ::GetClipboardData(clipboardformat);
    else clipboardInputHandle = 0;
    if (clipboardInputHandle == 0)
        clipboardInputHandle = ::GetClipboardData(CF_TEXT);
    clipboardInput = NULL;
    if (clipboardInputHandle != 0)
    {   w = (char *)::GlobalLock(clipboardInputHandle);
        if (w != NULL)
        {   int n = strlen(w); // amount of data found
            clipboardInput = clipboardInputP = (char *)malloc(n+1);
            if (clipboardInput != NULL) strcpy(clipboardInput, w);
            ::GlobalUnlock(clipboardInputHandle);
        }
    }
    ::CloseClipboard();
    if (caretChar != textLast)
    {   char oneLine[128];
        for (;;)
        {   int i=0, c;
            while (i<120 && clipboardInput!=NULL && (c=*clipboardInputP++)!=0)
            {   if (c == '\r') continue;
                oneLine[i++] = c;
                if (c == '\n') break;  // Do pastes one line at a time
break; // do it one CHARACTER at a time...
            }
            InsertAtCaret(oneLine, i);
            if (c == 0) break;
        }
        if (clipboardInput != NULL) free(clipboardInput);
        clipboardInput = NULL;
    }
    cwin_ensure_screen(FALSE);
}

// The REINPUT request just combines a COPY and PASTE. It seems clumsy to
// have to do both as separate actions.

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

void CMainWindow::OnSelectAll()
{
    selFirstChar = selStartChar = textFirst;
    selEndChar = textLast;
    selFirstX = selStartX = 0;
    selEndX = lineBuffer[lineLast].width;
    selFirstLine = selStartLine = lineFirst;
    selEndLine = lineLast;
    HideCaret();
    Invalidate();
    ShowCaret();
}

void CMainWindow::OnClear()
{
    CancelSelection();
    caretChar = icaretChar = textFirst = textLast;
    caretLine = icaretLine = lineFirst = lineLast;
    caretX = icaretX = endX = 0;
    xOffset = 0;
    caretVisible = TRUE;
    caretFontWidths = windowFonts.HCourier.across;
    endFontWidths = windowFonts.HCourier.across;
    if (inputLineStart>=0) inputLineStart = textLast;
    pageLine = caretLine;
    cwin_ensure_screen(FALSE);
}

// void CMainWindow::OnUndo()
// {
//     DisplayMsg("OnUndo Text");
// }

void CMainWindow::OnRedraw()
{
    HideCaret();
    Invalidate();
    ShowCaret();
}

// I make END reset the caret to the end of the text buffer, since that
// is an important state to be in, both with regard to input and to output.
// HOME just scrolls the window to make the top of the text visible, but does
// not re-position the caret. That is done because I do not generally expect
// people to want to insert characters at the very top of the buffer.

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

void CMainWindow::OnEnd()
{
    icaretLine = caretLine = lineLast;
    icaretChar = caretChar = textLast;
    icaretX = caretX = endX;
    caretFontWidths = endFontWidths;
    selRootValid = TRUE;

    OnVScroll(SB_FOR_CARET, 0, NULL);
    OnHScroll(SB_FOR_CARET, 0, NULL);
    caretVisible = TRUE;
    cwin_ensure_screen(FALSE);  // makes sure caret is properly on screen
}

void CMainWindow::ToIcaret()
{
    caretLine = icaretLine;
    caretChar = icaretChar;
    caretX = icaretX;
    caretFontWidths = icaretFontWidths;

    OnVScroll(SB_FOR_CARET, 0, NULL);
    OnHScroll(SB_FOR_CARET, 0, NULL);
    caretVisible = TRUE;
    cwin_ensure_screen(FALSE);  // makes sure caret is properly on screen
}

//
// The font arrangements are as follows - at each stage I will have a
// master fixed-pitch font (typically "Courier New"). Whenever I change
// that font I locate a collection of slave fonts - Roman, Roman Italic
// and Symbol each in two sizes (one about the same as the master font and
// another about (10/12)^2 of that size, for use in sub- and super-scripts).
//

// When I change fonts I will only change the ones used for display in
// my window. The ones used when printing will be left as their original size.
// I only permit font selection to alter the font (name) for the simple
// fixed-pitch font that I use. But I still need to reconstruct printer
// fonts to allow for changes there.

void CMainWindow::OnResetFont()
{
    HideCaret();
    CClientDC dc(this);
// I need to find a suitable size for my default font, so I create one
// asking for "size=0" and see what windows hands back to me.
    FontHeights fh;
    CFont *f = MakeNewFont(&dc, &fh, ANSI_CHARSET, "Courier New",
                           FW_BOLD, 0, 0);
    dc.SelectStockObject(SYSTEM_FONT);
    delete f;
// If there is a font already set up somebody must discard it...
    windowFonts.DeleteFonts();
    windowFonts.ChangeFont(&dc, fh.height, FW_BOLD, "Courier New");
    theApp.WriteProfileInt("MainWindow", "FontSize", fh.height);
    theApp.WriteProfileString("MainWindow", "FontName", "Courier New");
    theApp.WriteProfileInt("MainWindow", "FontWeight", 0);
    // scroll activity may be needed here just as it is
    // after some changes of window size.
    OnSize(SIZE_RESTORED, clientWidth, clientHeight);
    Invalidate();    // I think a total re-draw after a font change
                     // makes sense, and will not worry evan if the new
                     // font was identical to the old one, since after all
                     // the user prodded the OK not the CANCEL button.
    ShowCaret();
}

void CMainWindow::OnFont()
{
    HideCaret();
    CClientDC dc(this);
    LOGFONT logFont;
    windowFonts.Courier->GetObject(sizeof(logFont), &logFont);
    CFontDialog fontDialog(&logFont,
                           CF_SCREENFONTS | CF_FIXEDPITCHONLY |
                           CF_TTONLY |
                           CF_ANSIONLY | CF_INITTOLOGFONTSTRUCT);
// 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;
// If there is a font already set up somebody must discard it...
        windowFonts.DeleteFonts();
// Whatever the user does I do not want either very very small fonts or
// ones that would be so huge that their metrics could exceed 256. I apply
// rather arbitrary cut-offs here.
        if (lf->lfHeight < -150) lf->lfHeight = -150;
        if (lf->lfHeight <= -6);
        else if (lf->lfHeight < 6) lf->lfHeight = -6;
        else if (lf->lfHeight > 150) lf->lfHeight = -150;
        windowFonts.ChangeFont(&dc, lf->lfHeight, lf->lfWeight, lf->lfFaceName);
        theApp.WriteProfileInt("MainWindow", "FontSize", lf->lfHeight);
        theApp.WriteProfileString("MainWindow", "FontName", lf->lfFaceName);
        theApp.WriteProfileInt("MainWindow", "FontWeight",
                               lf->lfWeight==FW_BOLD ? 0 : 1);
        break;
    }
    // scroll activity may be needed here just as it is
    // after some changes of window size.
    OnSize(SIZE_RESTORED, clientWidth, clientHeight);
    Invalidate();    // I think a total re-draw after a font change
                     // makes sense, and will not worry evan if the new
                     // font was identical to the old one, since after all
                     // the user prodded the OK not the CANCEL button.
    ShowCaret();
}

void FontArray::InitFont(CDC *dc, const char *name, int weight, int size)
{
// Create and install a suitable initial font - I use "Courier New"
// and ask for a default size the very first time, and this is
// achieved by passing down "Courier New" and "0".
// Because Courier tends to look rather light on
// the screen I will use a bold weight by default.
// I really hope that the default size will be such that 80 columns will fit
// neatly across the screen...
    FontHeights fh;
    weight = weight ? FW_NORMAL : FW_BOLD;
    CFont *f = MakeNewFont(dc, &fh, ANSI_CHARSET, name,
                           weight, 0, size);
    dc->SelectStockObject(SYSTEM_FONT);
    delete f;
    ChangeFont(dc, size == 0 ? fh.height : size, weight, name);
}

void FontArray::DeleteFonts()
{
    delete Courier;    Courier = NULL;
    delete Roman;      Roman = NULL;
    delete Bold;       Bold = NULL;
    delete Italic;     Italic = NULL;
    delete Symbol;     Symbol = NULL;
    delete roman;      roman = NULL;
    delete bold;       bold = NULL;
    delete italic;     italic = NULL;
    delete symbol;     symbol = NULL;
}

// The next function is a place-holder, I guess.

void CMainWindow::MeasureLine(int address, int *up, int *down, FontArray *fa)
{
    *up = fa->HCourier.up;
    *down = fa->HCourier.down;
}

void FontArray::ChangeFont(CDC *dc, int size, int weight, const char *facename)
{
    strcpy(baseFace, facename);
    boldFlag = weight;
    Courier = MakeNewFont(dc, &HCourier,    ANSI_CHARSET,
                          facename,         weight, 0, size);
// I choose the scale factor 1.2^2 between my main and my script font mainly
// because that is what TeX seems to have adopted - Windows will generally
// round the size I ask for somewhat.... I also reload the size of my
// basic fixed-pitch font in case an inconvenient value had been handed in
// to start with and creating the font led to rounding.
    size = HCourier.height;
    int small = (100*size+72)/144;
// These fonts have my predefined font-names and weights. They are not in fact
// really (?) used at present.
    Roman  = MakeNewFont(dc, &HRoman,       ANSI_CHARSET,
                         "Times New Roman", FW_NORMAL, 0, size);
    Bold   = MakeNewFont(dc, &HBold,        ANSI_CHARSET,
                         "Times New Roman", FW_BOLD,   0, size);
    Italic = MakeNewFont(dc, &HItalic,      ANSI_CHARSET,
                         "Times New Roman", FW_NORMAL, 1, size);
    Symbol = MakeNewFont(dc, &HSymbol,      SYMBOL_CHARSET,
                         "Symbol",          FW_NORMAL, 0, size);
    roman  = MakeNewFont(dc, &Hroman,       ANSI_CHARSET,
                         "Times New Roman", FW_NORMAL, 0, small);
    bold   = MakeNewFont(dc, &Hbold,        ANSI_CHARSET,
                         "Times New Roman", FW_BOLD,   0, small);
    italic = MakeNewFont(dc, &Hitalic,      ANSI_CHARSET,
                         "Times New Roman", FW_NORMAL, 1, small);
    symbol = MakeNewFont(dc, &Hsymbol,      SYMBOL_CHARSET,
                         "Symbol",          FW_NORMAL, 0, small);
    theApp.mainWindow->LineSizes();
    ::DestroyCaret();
    theApp.mainWindow->CreateSolidCaret(
        2*GetSystemMetrics(SM_CXBORDER), HCourier.height);
}

void CMainWindow::LineSizes()
{
    int up, down, pos;
    int lineFirst = theApp.mainWindow->lineFirst;
    int lineLast = theApp.mainWindow->lineLast;
    TextLine *lineBuffer = &theApp.mainWindow->lineBuffer[0];
    MeasureLine(lineBuffer[lineFirst].address, &up, &down, &windowFonts);
    lineBuffer[lineFirst].position = 0;
    lineBuffer[lineFirst].up = up;
    lineBuffer[lineFirst].height = up+down;
    lineBuffer[lineFirst].width = 000;
    pos = up + down;
    for (int i=(lineFirst+1)&LINE_MASK;;i=(i+1)&LINE_MASK)
    {   MeasureLine(lineBuffer[i].address, &up, &down, &windowFonts);
        lineBuffer[i].position = pos;
        lineBuffer[i].up = up;
        lineBuffer[i].height = up+down;
        lineBuffer[i].width = 000;
        pos += up + down;
        if (i==lineLast) break;
    }
}

// Now some support for a really crude and simple text-based help window.
// The interface I provide here is intended to be such that I can have
// alternative implementations (eg using Unix) that use a "curses" style
// screen manager.

// For Windows I will only support an 80 by 25 window. I guess it
// would be easy enough to permit other sizes, except that I do not have
// an easy answer to what should happen if the user re-sizes the window
// while other things are going on. Hence my conservative caution - at
// least for now!

extern "C" {

int LINES = 25, COLS = 80;

// initscr() must be called once at the start of a run
extern void initscr();

// initkb() and resetkb() delimit regions in the code where keyboard
// input is treated as requests to the curses window but is accepted
// with no delay and no echo.  Also mouse events can be posted during
// this time.

extern void initkb();
extern void resetkb();

extern int mouse_button;  // set non-zero when user presses a button
extern int mouse_cx;      // 0 <= mouse_cx < COLS
extern int mouse_cy;      // 0 <= mouse_cy < LINES

// refresh() is called to force the screen to be up to date
extern void refresh();

// endwin() hides the curses window, restoring simple text handling
extern void endwin();

// Move text insertion point. Origin (0,0) is top left of screen
extern void move(int y, int x);

// standout() and standend() delimit inverse video (or whatever) text
extern void standout();
extern void standend();

// erase() clears the whole screen
extern void erase();

//
// addch() and addstr() add text to the screen, advancing the cursor. I
// view it as illegal to write beyond either right or bottom margin of the
// screen.
//
extern void addch(int ch);
extern void addstr(char *s);

//
// getch() reads a character from the keyboard. It does not wait for
// a newline, and does not echo anything. Because the name getch() may be
// in use in some C libraries in a way that could conflict I use some

// re-naming here.  If there has been a mouse-click recently then getch()
// should return a value (0x100 + bits) where the odd bits may indicate which
// button was pressed. In that case (mouse_cx,mouse_cy) will be the
// character-position coordinates at which the hit was taken. Systems
// that can not support a mouse do not have to worry about this and can always
// return a value in the range 0..255, or EOF. On some systems getch() will
// return 0 with no delay if there is no character available (so that
// the application will busy-wait). On others it is entitled to wait until
// the user presses a key. But (once again) it should not do line editing or
// wait for an ENTER.
//
extern int my_getch();
#undef getch
#define getch() my_getch()

}

static BOOL helpShown = FALSE;
static int helpChar = -1;

void initscr()
{
    helpShown = FALSE;
    helpChar = -1;
}

void initkb()
{
    if (!helpShown)
    {   erase();
        theApp.mainWindow->helpWindow->ShowWindow(SW_SHOW);
        helpShown = TRUE;
    }
    helpChar = -1;
}

void resetkb()
{
    if (helpShown)
    {   theApp.mainWindow->helpWindow->ShowWindow(SW_HIDE);
        helpShown = FALSE;
    }
}

void endwin()
{
    resetkb();
}

static int helpY, helpX;

void move(int y, int x)
{
    helpY = y;
    helpX = x;
}

void erase()
{
    helpX = helpY = 0;
    for (int y=0; y<LINES; y++)
        for (int x=0; x<COLS; x++)
            theApp.mainWindow->helpWindow->contents[y][x] = ' ';
    theApp.mainWindow->helpWindow->highline =
        theApp.mainWindow->helpWindow->highstart =
        theApp.mainWindow->helpWindow->highend = -1;
}

void standout()
{
    theApp.mainWindow->helpWindow->highline = helpY;
    theApp.mainWindow->helpWindow->highstart =
        theApp.mainWindow->helpWindow->highend = helpX;
}

void standend()
{
    if (theApp.mainWindow->helpWindow->highline != helpY)
    {   theApp.mainWindow->helpWindow->highline = helpY;
        theApp.mainWindow->helpWindow->highstart = 0;
    }
    theApp.mainWindow->helpWindow->highend = helpX;
}

void addch(int ch)
{
    if (helpX < COLS && helpY < LINES)
    {   theApp.mainWindow->helpWindow->contents[helpY][helpX] = ch;
        helpX++;
    }
}

void addstr(char *s)
{
    while (*s != 0) addch(*s++);
}

int mouse_button = 0, mouse_cx = 0, mouse_cy = 0;

void refresh()
{
    if (!helpShown) initkb();
    theApp.mainWindow->helpWindow->Invalidate();
}

int my_getch()
{
    while (helpChar < 0) cwin_poll_window_manager();
    int ch = helpChar;
    helpChar = -1;
    return ch;    
}

CHelpWindow::CHelpWindow()
{
    char *menuName = "HelpMenu";
    HINSTANCE hInst = AfxFindResourceHandle(menuName, RT_MENU);
    HMENU hMenu = ::LoadMenu(hInst, menuName);
    this->CreateEx(0, mainWindowClass, "Help Window",
                   WS_OVERLAPPED | WS_CAPTION,
                   CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
                   NULL, hMenu);
    helpFont.CreateFont(
        0, 0,           // height, width
        0, 0,           // angle of escapement, base line orientation angle
        FW_BOLD, 0,     // weight, italic-flag
        0, 0, ANSI_CHARSET,    // underline, strike-out, character-set
        OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS,
        DEFAULT_QUALITY, DEFAULT_PITCH+FF_DONTCARE,
        "Courier New");
    for (int y=0;y<25;y++)
        for (int x=0;x<80;x++) contents[y][x] = ' ';
    highline = 5;
    highstart = 7;
    highend = 11;
}


BEGIN_MESSAGE_MAP(CHelpWindow, CFrameWnd)

    ON_WM_PAINT()
    ON_WM_CHAR()
    ON_WM_KEYDOWN()
    ON_WM_LBUTTONDOWN()
    ON_COMMAND(IDM_REDRAW,         OnRedraw)
//  ON_COMMAND(IDM_COPY,           OnCopy)
    ON_COMMAND(IDM_CLOSE,          OnClose)

END_MESSAGE_MAP()


void CHelpWindow::OnPaint()
{
    CPaintDC dc(this);
    dc.SelectObject(&helpFont);
    for (int i = 0; i<25; i++)
    {   if (i == highline)
        {   dc.TextOut(0, i*height, contents[i], highstart);
            dc.SetTextColor(theApp.mainWindow->highlightTextColour);
            dc.SetBkColor(theApp.mainWindow->highlightColour);
            dc.TextOut(highstart*width, i*height, &contents[i][highstart],
                       highend-highstart);
            dc.SetTextColor(theApp.mainWindow->textColour);
            dc.SetBkColor(theApp.mainWindow->windowColour);
            dc.TextOut(highend*width, i*height, &contents[i][highend],
                       80-highend);
        }
        else dc.TextOut(0, i*height, contents[i], 80);
    }
    dc.SelectStockObject(SYSTEM_FONT);
}

void CHelpWindow::OnChar(UINT ch, UINT nRepCnt, UINT nFlags)
{
    switch (ch)
    {
//case ('O' & 0x1f):
//        OnCopy();
//        return;
case ('L' & 0x1f):
        OnRedraw();
        return;
default:
        helpChar = ch;
        return;
    }
}

void CHelpWindow::OnKeyDown(UINT ch, UINT nRepCnt, UINT nFlags)
{
}


void CHelpWindow::OnLButtonDown(UINT nFlags, CPoint point)
{
}

//void CHelpWindow::OnCopy()
//{
//    HGLOBAL hpbmi = 0;   // @@@@@@@@@
//    if (this->OpenClipboard())
//    {   ::EmptyClipboard();
//        ::SetClipboardData(CF_DIB, hpbmi);
//        ::CloseClipboard();
//    }
//}

void CHelpWindow::OnRedraw()
{
    Invalidate();
}

void CHelpWindow::OnClose()
{
    helpChar = 'q';
}


// end of c_text.cpp



REDUCE Historical
REDUCE Sourceforge Project | Historical SVN Repository | GitHub Mirror | SourceHut Mirror | NotABug Mirror | Chisel Mirror | Chisel RSS ]