File r38/lisp/csl/cslbase/FXTerminal.cpp artifact b7a566b2f9 part of check-in 9992369dd3


//
// "FXTerminal.cpp"                         Copyright A C Norman 2003-2007
//
//
// Window interface for old-fashioned C applications. Intended to
// be better than just running them within rxvt/xterm, but some people will
// always believe that running them under emacs is best!
//

/******************************************************************************
* Copyright (C) 2003-4 by Arthur Norman, Codemist Ltd.   All Rights Reserved. *
*******************************************************************************
* This library is free software; you can redistribute it and/or               *
* modify it under the terms of the GNU Lesser General Public                  *
* License as published by the Free Software Foundation;                       *
* version 2.1 of the License.                                                 *
*                                                                             *
* This library is distributed in the hope that it will be useful,             *
* but WITHOUT ANY WARRANTY; without even the implied warranty of              *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU           *
* Lesser General Public License for more details.                             *
*                                                                             *
* You should have received a copy of the GNU Lesser General Public            *
* License along with this library; if not, write to the Free Software         *
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA.  *
*                                                                             *
* See also the FOX Toolkit addendum to the LGPL, which also applies to this   *
* code. This addedum gives, in addition to the rights granted by the LGPL,    *
* permission to distribute this code statically linked against other code     *
* without any need for that other code to have its source released.           *
******************************************************************************/


/* Signature: 53b9d2e0 04-Jan-2008 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#else
#error HAVE_CONFIG_H expected
#endif

#ifndef HAVE_LIBFOX

char FXTerminal_msg[] = "No FOX so no FXTerminal";

#else

#include "fwin.h"
#include <fx.h>

#ifdef WIN32
#include <windows.h>
#else
#include <pthread.h>
#endif

#if FOX_MAJOR==1 && FOX_MINOR==0
#  define FXSEL(a,b) MKUINT(a,b)
#  define minimize() iconify()
#  define restore()  deiconify()
#endif

#include <fxkeys.h>          // not included by <fx.h>

#include "FXShowMath.h"

#include "FXTerminal.h"      // my own header file.

#include "termed.h"

#include <string.h>
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <stdarg.h>

#ifdef CSL

#include <sys/stat.h>

#ifndef S_IXUSR
#ifdef __S_IXUSR
#define S_IXUSR __S_IXUSR
#endif
#endif

#endif

// The next is for pipes and threads
#ifdef WIN32

#include <windows.h>
HANDLE pipedes;
int event_code = -1;

#else

#include <unistd.h>
int pipedes[2];

#endif /* WIN32 */

static FXPrinter printer;

// I need an event table of things that the user interface must respond to.
//
// I have to implement local editing and history stuff for the FOX-based
// version of the code here, and a parallel implementation is in the
// file "termed.c" to cope with cursor-addressible terminals (rather than
// real windows). It is perhaps unfortunate to have two parallel versions,
// but in various detailed ways it does not make sense for the treatment of
// keystrokes to match exactly in windowed and non-windowed mode (I thing!)
// and the code here has to be event driven, while the terminal version
// pulls characters from the terminal driver. At least by having both
// versions of the code my own and under my own control I can keep some sort
// of a handle on compatibility.
// Two examples of marginal oddities: the windowed version will very clearly
// have menu short-cut keys and they are processed by FOX rather directly.
// The terminal one does not need a menu to change its font, and any menu
// short-cut keys have to be handled directly by me.
// On the other hand while a terminal is in "cooked" mode various characters
// such as ^S, ^Q, ^C and ^Z are liable to be handled for me automatically,
// while in the FOX/Window version I need to intercept and deal with those
// for myself.


FXDEFMAP(FXTerminal) FXTerminalMap[] =
{
// User types some character. I override the FXMathText behaviour here
    FXMAPFUNC(SEL_KEYPRESS,     0, FXTerminal::onKeyPress),

// User types a newline. This overrides the behaviour that FXMathText had
    FXMAPFUNC(SEL_COMMAND,      FXMathText::ID_INSERT_NEWLINE,
                                FXTerminal::onCmdInsertNewline),

// Several events that can be generated by the worker thread.
    FXMAPFUNC(SEL_IO_READ,      FXTerminal::ID_IPC, FXTerminal::onIPC),

// Regular timer ticks from a timer thread.
    FXMAPFUNC(SEL_TIMEOUT,      FXTerminal::ID_TIMEOUT, FXTerminal::onTimeout),

// ... and now all the menu items that the user can provoke. The three cases
// above are given first as a (minor) efficiency issue.

#ifdef CSL
    FXMAPFUNC(SEL_COMMAND,      FXTerminal::ID_LOAD_MODULE, FXTerminal::onCmdLoadModule),
    FXMAPFUNC(SEL_COMMAND,      FXTerminal::ID_FLIP_SWITCH, FXTerminal::onCmdFlipSwitch),
    FXMAPFUNC(SEL_COMMAND,      FXTerminal::ID_REDUCE_UPDATE, FXTerminal::onCmdReduceUpdate),
#endif
    FXMAPFUNC(SEL_COMMAND,      FXTerminal::ID_READ, FXTerminal::onCmdRead),
    FXMAPFUNC(SEL_COMMAND,      FXTerminal::ID_SAVE, FXTerminal::onCmdSave),
    FXMAPFUNC(SEL_COMMAND,      FXTerminal::ID_SAVE_SELECTION, FXTerminal::onCmdSaveSelection),
    FXMAPFUNC(SEL_COMMAND,      FXTerminal::ID_TO_FILE, FXTerminal::onCmdToFile),
    FXMAPFUNC(SEL_COMMAND,      FXTerminal::ID_PRINT, FXTerminal::onCmdPrint),
    FXMAPFUNC(SEL_COMMAND,      FXTerminal::ID_PRINT_SELECTION, FXTerminal::onCmdPrintSelection),
    FXMAPFUNC(SEL_COMMAND,      FXTerminal::ID_CUT_SEL_X, FXTerminal::onCmdCutSel),
    FXMAPFUNC(SEL_COMMAND,      FXMathText::ID_CUT_SEL, FXTerminal::onCmdCutSel),
    FXMAPFUNC(SEL_COMMAND,      FXTerminal::ID_PASTE_SEL_X, FXTerminal::onCmdPasteSel),
    FXMAPFUNC(SEL_COMMAND,      FXMathText::ID_PASTE_SEL, FXTerminal::onCmdPasteSel),
    FXMAPFUNC(SEL_COMMAND,      FXMathText::ID_PASTE_MIDDLE, FXTerminal::onCmdPasteMiddle),
    FXMAPFUNC(SEL_COMMAND,      FXTerminal::ID_COPY_SEL_X, FXTerminal::onCmdCopySel),
    FXMAPFUNC(SEL_COMMAND,      FXMathText::ID_COPY_SEL, FXTerminal::onCmdCopySel),
    FXMAPFUNC(SEL_COMMAND,      FXTerminal::ID_REINPUT, FXTerminal::onCmdReinput),
    FXMAPFUNC(SEL_COMMAND,      FXTerminal::ID_CLEAR, FXTerminal::onCmdClear),
    FXMAPFUNC(SEL_COMMAND,      FXTerminal::ID_REDRAW, FXTerminal::onCmdRedraw),
    FXMAPFUNC(SEL_COMMAND,      FXTerminal::ID_HOME, FXTerminal::onCmdHome),
    FXMAPFUNC(SEL_COMMAND,      FXTerminal::ID_END, FXTerminal::onCmdEnd),
    FXMAPFUNC(SEL_COMMAND,      FXTerminal::ID_FONT, FXTerminal::onCmdFont),
    FXMAPFUNC(SEL_COMMAND,      FXTerminal::ID_RESET_FONT, FXTerminal::onCmdResetFont),
    FXMAPFUNC(SEL_COMMAND,      FXTerminal::ID_RESET_WINDOW, FXTerminal::onCmdResetWindow),
    FXMAPFUNC(SEL_COMMAND,      FXTerminal::ID_BREAK, FXTerminal::onCmdBreak),
    FXMAPFUNC(SEL_COMMAND,      FXTerminal::ID_BACKTRACE, FXTerminal::onCmdBacktrace),
    FXMAPFUNC(SEL_COMMAND,      FXTerminal::ID_PAUSE, FXTerminal::onCmdPause),
    FXMAPFUNC(SEL_COMMAND,      FXTerminal::ID_RESUME, FXTerminal::onCmdResume),
    FXMAPFUNC(SEL_COMMAND,      FXTerminal::ID_STOP, FXTerminal::onCmdStop),
    FXMAPFUNC(SEL_COMMAND,      FXTerminal::ID_DISCARD, FXTerminal::onCmdDiscard),
#ifndef WIN32
#if !defined MACINTOSH || !defined MAC_FRAMEWORK
    FXMAPFUNC(SEL_COMMAND,      FXTerminal::ID_BROWSER, FXTerminal::onCmdSelectBrowser),
#endif
#endif
    FXMAPFUNC(SEL_COMMAND,      FXTerminal::ID_HELP, FXTerminal::onCmdHelp),
    FXMAPFUNC(SEL_COMMAND,      FXTerminal::ID_ABOUT, FXTerminal::onCmdAbout)

};

#define TYPEAHEAD_SIZE 200

static int type_in = 0, type_out = 0;
static char ahead_buffer[TYPEAHEAD_SIZE];

static char *paste_buffer;
static int paste_flags, paste_n, paste_p, paste_is_html;

FXIMPLEMENT(FXTerminal, FXMathText, FXTerminalMap, ARRAYNUMBER(FXTerminalMap))

FXTerminal::FXTerminal(FXComposite *p,FXObject* tgt,FXSelector sel,
                       FXuint opts,
                       FXint x,FXint y,FXint w,FXint h) :
    FXMathText(p, tgt, sel, opts, x, y, w, h)
{
    lineSpacing = 1;
    setWrapColumns(80);
    delay_callback = NULL;

    FXPrintDialog dummyPrintDialog(this, "");
    dummyPrintDialog.getPrinter(printer);  // do not show - just get defaults.

// It is of course well within the bounds of imagination that users would
// like to be able to specify these colours, and that of output text,
// for themselves.
    promptColor = FXRGB(0, 64, 200);
    inputColor = FXRGB(200, 64, 128);

    fwin_in = fwin_out = 0;
    inputBufferLen = inputBufferP = 0;
    logfile = NULL;

    type_in = type_out = 0;
    paste_buffer = NULL;
    paste_flags = paste_n = paste_p = paste_is_html = 0;

    input_history_init();
    historyFirst = 0;
    historyLast = -1; // flag to say history is empty.
    pauseFlags = keyFlags = historyNumber = searchFlags = 0;
    promptEnd = length;
    InitMutex(pauseMutex);

    InitMutex(mutex1);
    InitMutex(mutex2);
    InitMutex(mutex3);
    InitMutex(mutex4);

    LockMutex(mutex3);
    LockMutex(mutex4);
    sync_even = 1;

#ifdef WIN32
    pipedes = CreateEvent(NULL, FALSE, FALSE, NULL);
    if (pipedes == 0)
    {   fprintf(stderr,
            "Failed to create an event object for internal communication\n");
        application_object->exit(1);
        exit(1);
    }
    application_object->addInput(pipedes,
                                 INPUT_READ, this, ID_IPC);
#else
    if (pipe(pipedes) != 0)
    {   fprintf(stderr,
            "Failed to create a pipe for internal communication\n");
        application_object->exit(1);
        exit(1);
    }
    application_object->addInput(pipedes[PIPE_READ_PORT],
                                 INPUT_READ, this, ID_IPC);
#endif

    setFocus();    // select this window for input

//- #if FOX_MAJOR==1 && FOX_MINOR==0
//-     timer = application_object->addTimeout(1000, this, ID_TIMEOUT);
//- #else
//- #if FOX_MAJOR==1 && (FOX_MINOR==1 || FOX_MINOR==2)
//-     timer = application_object->addTimeout(this, ID_TIMEOUT, 1000, NULL);
//- #else
//-     application_object->addTimeout(this, ID_TIMEOUT, 1000, NULL);
//- #endif
//- #endif
    matchtime = 800; // causes parens to flash as they match (800 milliseconds)
}

FXTerminal::FXTerminal()
{
    fprintf(stderr,
        "I hope this never happens: report \"@FXT@\" to Codemist please\n");
    fflush(stderr);
}

FXTerminal::~FXTerminal()
{
    input_history_end();
#if FOX_MAJOR==1 && FOX_MINOR<=2
    application_object->removeTimeout(timer); // cancel ticks
#else
    application_object->removeTimeout(this,
                            (FXSelector)ID_TIMEOUT); // cancel ticks
#endif
#ifdef WIN32
    application_object->removeInput(pipedes, ID_IPC);
    CloseHandle(pipedes);
#else
    application_object->removeInput(pipedes[PIPE_READ_PORT], ID_IPC);
    close(pipedes[0]);
    close(pipedes[1]);
#endif
    DestroyMutex(mutex1);
    DestroyMutex(mutex2);
    DestroyMutex(mutex3);
    DestroyMutex(mutex4);
}

void FXTerminal::create()
{
    FXMathText::create();
    setFocus();    // select this window for input

}

extern "C"
{
int showmathInitialised = 0;
}

void FXTerminal::setupShowMath()
{
// Note that the terminal must have been created before I set up the
// FXShowMath stuff since at least on X I need to access the window
// identifier that it uses.
//
// I can give the second arg to setupShowMath, which controls font size, in
// two manners:
// (a) A positive value denotes a font size in decipoints. Well it is actually
//     a bit more ugly than that since on some platforms the size gets
//     adjusted to allow for (notional) screen pixels per inch and sometimes
//     not, and so I do not find this able to give me a totally consistent
//     control;
// (b) A negative value is the width of my root window, and I select my
//     font so that 80 "m" characters in the "subscript" size fit across
//     it. This provides some attempt at an automatic way to let the font
//     scale with window size.
//
// I set showmathInitialised if I set things up successfully
//
// I believe that this is the only place in the code where I link down to
// a module that uses Xft. That matters to me because at one stage I had a
// platform that failed when I tried to use Xft...
    showmathInitialised =
        ::setupShowMath(application_object, -getDefaultWidth());
    return;
}

void FXTerminal::setEditable(FXbool fg)
{
    FXMathText::setEditable(fg);
}


void FXTerminal::setVisibleRows(FXint rows)
{
#if FOX_MAJOR==1 && FOX_MINOR==0
    FXMathText::setVisRows(rows);
#else
    FXMathText::setVisibleRows(rows);
#endif
}

void FXTerminal::setVisibleColumns(FXint cols)
{
#if FOX_MAJOR==1 && FOX_MINOR==0
    FXMathText::setVisCols(cols);
#else
    FXMathText::setVisibleColumns(cols);
#endif
}

long FXTerminal::onCmdPause(FXObject *c, FXSelector sel, void *ptr)
{
    keyFlags &= ~ESC_PENDING;
    if ((pauseFlags & PAUSE_PAUSE) == 0)
    {   LockMutex(pauseMutex);
        main_window->setTitle("Paused: Type ^Q to resume");
    }
    pauseFlags |= PAUSE_PAUSE;
    setFocus();   // I am uncertain, but without this I lose focus...
    return 1;
}

static char window_full_title[90] = "";

long FXTerminal::onCmdResume(FXObject *c, FXSelector sel, void *ptr)
{
    keyFlags &= ~ESC_PENDING;
    if (pauseFlags & PAUSE_PAUSE)
    {   pauseFlags &= ~(PAUSE_PAUSE | PAUSE_STOP);
        if (pauseFlags & PAUSE_DISCARD)
            main_window->setTitle("Discarding output...");
        else main_window->setTitle(window_full_title);
        UnlockMutex(pauseMutex);
    }
    setFocus();   // I am uncertain, but without this I lose focus...
    return 1;
}

long FXTerminal::onCmdStop(FXObject *c, FXSelector sel, void *ptr)
{
// At present this is implemented just so it flips the state of the
// pause mutex and flag that ^S and ^Q use. Well I want it to do a bit more!
// I want it to force the worker thread to go into a suspended state. I think
// that for now I am going to allow ^S to halt output when some was due
// anyway, ^Z to pause the worker task soon even if it was not trying to
// generate output, and then when things are suspended either ^Q or another
// ^Z will release them.
    keyFlags &= ~ESC_PENDING;
    if (pauseFlags & PAUSE_PAUSE)
    {   pauseFlags &= ~(PAUSE_PAUSE | PAUSE_STOP);
        if (pauseFlags & PAUSE_DISCARD)
            main_window->setTitle("Discarding output...");
        else main_window->setTitle(window_full_title);
        UnlockMutex(pauseMutex);
    }
    else
    {   LockMutex(pauseMutex);
        main_window->setTitle("Stopped: press ^Z to resume");
        pauseFlags |= (PAUSE_PAUSE | PAUSE_STOP);
// Now to ensure that we hang up SOON I will take steps to provoke a soft
// interrupt. I want this to cause the worker to lock and then instantly
// unlock the pauseMutex...
        if (interrupt_callback != NULL)
        {   int r = (*interrupt_callback)(QUERY_INTERRUPT);
            if (r == 0) (*interrupt_callback)(TICK_INTERRUPT);
        }
    }
    setFocus();   // I am uncertain, but without this I lose focus...
    return 1;
}

long FXTerminal::onCmdDiscard(FXObject *c, FXSelector sel, void *ptr)
{
    keyFlags &= ~ESC_PENDING;
    pauseFlags |= PAUSE_DISCARD;
    main_window->setTitle("Discarding output...");
// I might hit ^O when the last line on the screen is not a complete
// one. I think it is neater to force in a newline here. The "..." is to
// remind the user I have chucked something away.
    FXMathText::appendText("\n...\n", 5);
    setFocus();   // I am uncertain, but without this I lose focus...
    return 1;
}

void FXTerminal::appendText(const FXchar *text, FXint n, FXbool notify)
{
    FXMathText::appendText(text, n, notify);
}

void FXTerminal::appendStyledText(const FXchar *text, FXint n, FXint style, FXbool notify)
{
    FXMathText::appendStyledText(text, n, style, notify);
}

void FXTerminal::setStyled(FXbool st)
{
    FXMathText::setStyled(st);
}

// Responses to menu items (and corresponding keyboard shortcuts)

void FXTerminal::type_ahead(int ch)
{
    ahead_buffer[type_in] = ch;
    int p1 = type_in + 1;
    if (p1 == TYPEAHEAD_SIZE) p1 = 0;
    if (p1 == type_out) getApp()->beep();
    else type_in = p1;
}

void FXTerminal::string_ahead(const char *s)
{
    while (*s != 0) type_ahead(*s++);
}

#ifndef LONGEST_LEGAL_FILENAME
#define LONGEST_LEGAL_FILENAME 1024
#endif

static char most_recent_read_file[LONGEST_LEGAL_FILENAME] = ".";

long FXTerminal::onCmdRead(FXObject *c, FXSelector sel, void *ptr)
{
    keyFlags &= ~ESC_PENDING;
    FXFileDialog opendialog(this, "Read File");
    opendialog.setSelectMode(SELECTFILE_EXISTING);
    opendialog.setFilename(most_recent_read_file);
    opendialog.setPatternList("Reduce Files (*.red,*.tst)\nAll Files (*)");
    const char *s = NULL;
    FXString filename;
    if (opendialog.execute())
    {   filename = opendialog.getFilename();
#if !(FOX_MINOR<=4)
/*
 * For versions before 1.6 I did not have the FXStat package to use. I could
 * re-code this to call ::stat directly but all it does is to filter cases
 * where the user exists the "Read File" dialog without a proper file name
 * in selected.
 */
        if (FXStat::isFile(filename)) 
#endif
            s = filename.text();
        strcpy(most_recent_read_file, s);
    }
    if (s != NULL && *s!=0)
    {   if (isEditable())
        {   killSelection();
            setInputText("", 0);
            appendText("in \"", 4, FALSE);
            appendText(s, strlen(s), FALSE);
            appendText("\";", 2, FALSE);
//
// Here I insert a command in to the input buffer, with a ";" on the end
// of it. I will then wait for the user to type ENTER to accept that, or
// maybe to delete the ";" and replace it with a "$" for silent reading.
//
//          onCmdInsertNewline(c, sel, ptr);
        }
        else
        {   string_ahead("in \"");
            string_ahead(s);
            string_ahead("\";");
        }
    }
    setFocus();   // I am uncertain, but without this I lose focus...
    return 1;
}

static char most_recent_save_file[LONGEST_LEGAL_FILENAME] = ".";

long FXTerminal::onCmdSave(FXObject *c, FXSelector s, void *ptr)
{
    keyFlags &= ~ESC_PENDING;
// Use FXFileDialog::getSaveFilename() here ???
    FXFileDialog d(this, "Save", DECOR_BORDER|DECOR_TITLE);
    d.setFilename(most_recent_save_file);
    d.setPatternList(
        "Log File (*.log)\nAll Files (*)");
    if (d.execute())
    {   FXString ss = d.getFilename();
        const char *s = ss.text();
// It seems plausible here that if I had not specified an explicit extension
// in my file-name that I should tag on ".log"
#define SAVE_BUFFER_SIZE 1024
        char buff[SAVE_BUFFER_SIZE], style[SAVE_BUFFER_SIZE];
        int i = strlen(s) - 1;
        while (i > 0 && s[i]!='.' && s[i]!='/' && s[i]!='\\') i--;
        if (s[i] == '.') strcpy(buff, s);
        else sprintf(buff, "%s.log", s);
        FILE *f = fopen(buff, "w");
        if (f == NULL)
        {   FXMessageBox::error(this, MBOX_OK, "Error",
                "Unable to write to \"%s\"", buff);
            setFocus();
            return 1;
        }
        else
        {   int i = 0;
            strcpy(most_recent_save_file, buff);
            while (i < length)
            {   int n = SAVE_BUFFER_SIZE;
                if (i + n > length) n = length - i;
                extractText(buff, i, n);
// Do I want to do something special with prompt strings? As it is
// I put the extractStyle call here so that I could identify them, but
// I just dump characters regardless.
                extractStyle(style, i, n);
                int n1 = fwrite(buff, 1, n, f);
// expect n1 == n here, unless there was an IO failure
                if (n != n1)
                {   FXMessageBox::error(this, MBOX_OK, "Error",
                        "Writing the file seems to have failed");
                    break;
                }
                i += n;
            }
            fclose(f);  // returns 0 if all is well
        }
    }
    setFocus();   // I am uncertain, but without this I lose focus...
    return 1;
}

long FXTerminal::onCmdSaveSelection(FXObject *c, FXSelector s, void *ptr)
{
    keyFlags &= ~ESC_PENDING;
    FXFileDialog d(this, "Save Selection", DECOR_BORDER|DECOR_TITLE);
    d.setFilename(most_recent_save_file);
    d.setPatternList(
        "Log File (*.log)\nAll Files (*)");
    if (d.execute())
    {   FXString ss = d.getFilename();
        const char *s = ss.text();
// It seems plausible here that if I had not specified an explicit extension
// in my file-name that I should tag on ".log"
        char buff[SAVE_BUFFER_SIZE], style[SAVE_BUFFER_SIZE];
        int i = strlen(s) - 1;
        while (i > 0 && s[i]!='.' && s[i]!='/' && s[i]!='\\') i--;
        if (s[i] == '.') strcpy(buff, s);
        else sprintf(buff, "%s.log", s);
        FILE *f = fopen(buff, "w");
        if (f == NULL)
        {   FXMessageBox::error(this, MBOX_OK, "Error",
                "Unable to write to \"%s\"", buff);
            setFocus();
            return 1;
        }
        else
        {   int i = getSelStartPos();
            int len = getSelEndPos();
            strcpy(most_recent_save_file, buff);
            if (len <= i) return 1; // no selection
            while (i < len)
            {   int n = SAVE_BUFFER_SIZE;
                if (i + n > len) n = len - i;
                extractText(buff, i, n);
// Do I want to do something special with prompt strings? As it is
// I put the extractStyle call here so that I could identify them, but
// I just dump characters regardless.
                extractStyle(style, i, n);
                int n1 = fwrite(buff, 1, n, f);
// expect n1 == n here, unless there was an IO failure
                if (n != n1)
                {   FXMessageBox::error(this, MBOX_OK, "Error",
                        "Writing the file seems to have failed");
                    break;
                }
                i += n;
            }
            fclose(f);  // returns 0 if all is well
        }
    }
    setFocus();   // I am uncertain, but without this I lose focus...
    return 1;
}

static char most_recent_log_file[LONGEST_LEGAL_FILENAME] = ".";

long FXTerminal::onCmdToFile(FXObject *c, FXSelector s, void *ptr)
{
    keyFlags &= ~ESC_PENDING;
    FILE *oldLogfile = logfile;
// There is a synchronisation issue here. My worker thread tends to
// go
//   FILE *f = logfile;
//   if (f != NULL) <write to it>
// so setting logfile to NULL here will switch logging off, however there
// could be one final write operation to be performed. So what if I do
// a close(logfile) too quickly. Well I think that the write will just fail
// an no desparate harm will ensue. Also I delay the close(logfile) until
// a dialog-box has run for teh user and that will with very very high
// probability leave me totally tidy.
    logfile = NULL;
    FXFileDialog d(this, "Log to File", DECOR_BORDER|DECOR_TITLE);
    d.setFilename(most_recent_log_file);
    d.setPatternList(
        "Log File (*.log)\nAll Files (*)");
    if (d.execute())
    {   fclose(oldLogfile);
        FXString ss = d.getFilename();
        const char *s = ss.text();
// It seems plausible here that if I had not specified an explicit extension
// in my file-name that I should tag on ".log"
        char buff[SAVE_BUFFER_SIZE];
        int i = strlen(s) - 1;
        while (i > 0 && s[i]!='.' && s[i]!='/' && s[i]!='\\') i--;
        if (s[i] == '.') strcpy(buff, s);
        else sprintf(buff, "%s.log", s);
        FILE *f = fopen(buff, "w");
        if (f == NULL)
        {   FXMessageBox::error(this, MBOX_OK, "Error",
                "Unable to write to \"%s\"", buff);
            setFocus();
            return 1;
        }
        strcpy(most_recent_log_file, buff);
        logfile = f;
    }
    else fclose(oldLogfile);
    setFocus();   // I am uncertain, but without this I lose focus...
    return 1;
}

// I make my own somewhat arbitrary choice of page margins here.



#define leftmargin_inches    0.5
#define rightmargin_inches   0.5
#define topmargin_inches     0.75
#define bottommargin_inches  1.0

// This prints a section of a row of text, where all the section uses
// the same style. The styles supported here are
//    SELECTED )
//    HILITE   ) these result in colour-effects for the display
//    PROMPT   )
//    INPUT    )
//    CONTROL    this lets control characters print as ^x (and at present
//               it does not behave well wrt line-wrapping, so I hope
//               it never gets used!)

int FXTerminal::printBufferText(FXDCNativePrinter &dc, FXint x, FXint y,
                                 char *str, FXint n, FXuint style)
{
    FXuint index=(style&STYLE_MASK);
    FXColor color;
    color=0;
    if (hilitestyles && index) // Get colors from style table
    {   if (style&STYLE_SELECTED)
            color=hilitestyles[index-1].selectForeColor;
        else if (style&STYLE_HILITE)
            color=hilitestyles[index-1].hiliteForeColor;
        if (color==0)          // Fall back on normal foreground color
            color=hilitestyles[index-1].normalForeColor;
    }
    if (color==0)              // Fall back to default style
    {   if (style&STYLE_SELECTED) color=seltextColor;
        else if (style&STYLE_HILITE) color=hilitetextColor;
        if (color==0) color=textColor;   // Fall back to normal text color
    }
    if (style&FXTerminal::STYLE_PROMPT)
    {   color=promptColor;     // ACN special
    }
    else if (style&FXTerminal::STYLE_INPUT)
    {   color=inputColor;     // ACN special
    }
    dc.setForeground(color);
    if (style&STYLE_CONTROL)
    {   y += dc.fntGetFontAscent();
        FXchar str2[2];
        str2[0]='^';
        while (n!=0)
        {   str2[1]=*str++ | 0x40;
            dc.drawText(x, y, str2, 2);
            x += dc.fntGetTextWidth(str2, 2);
            n--;
        }
    }
    else
    {   y += dc.fntGetFontAscent();
        dc.drawText(x, y, str, n);
        x += dc.fntGetTextWidth(str, n);
    }
    return x;
}

// Here I print one line of text. I let it terminate either at the
// end of a line, or after 80 characters (where a line-wrap is called for)
// or at the end of the buffer. I hand back the index of the start of
// the next line to print after this one. To cope with "styles" this
// scans the buffer spotting runs of characters that agree in their
// style, and send such runs in blocks to printBufferText.


static int charPointer;

static int staticCharForShowMath();

int FXTerminal::printTextRow(FXDCNativePrinter &dc,
                             int p, int y, int left, int right)
{
    int firstThis = p < length ? getChar(p) : 'x';
    int lineend;
    int line = 0;
    if (firstThis == 0x02)
    {   lineend=lineEnd(p);  // I want the true end of the LINE not the end
                             // of the ROW here...
        int realbeg=lineStart(p);
// Now a bit of a messy issue. I may be drawing something that was passed as
// the second or third row of a single formula, but I want to display the
// whole thing. This can arise eg when a window has been scrolled so that
// the top of a formula will not be visible. I will therefore step
// back to the start of the line and adjust my y position accordingly.
        line-=(p-realbeg);
        charPointer = p+1;
// now I may be at something other than the final row of a formula, so I will
// need to skip over any extra 0x02 chars that there might be.
        while (charPointer<length && getChar(charPointer)==0x02) charPointer++;
        int extraLines=charPointer-realbeg-1;
        int h=dc.fntGetFontHeight();
        int extra=extraLines*h;
        int x=right;
        int edge=left;
// Recover the scale that is to be used.
        int scale = getChar(charPointer) & 0xff;
        setMathsFontScale(scale & 0x07);
// Get pointer to box structure for the formula, or NULL if it has been
// discarded because of space limitations.
        Box *b = getBoxAddress(charPointer+1);
        if (b == NULL)
        {   int p = charPointer;
            charPointer += 4;
// Parse again to re-create a box that had gone away. This time it happens
// that my variables are set up so (p+1) is the location for the reference to
// the box, ie the "owner" info.
            b = parseTeX(staticCharForShowMath, p+1);
            if (b == NULL) b = makeTextBox("malformed expression", 20, 0);
            else text->recordBoxAddress(p+1, b);
        }
        measureBox(b);
// I paint the background for math output in a different (a sort of pale
// green) colour to help it starnd out.
        dc.setForeground(FXRGB(230,255,242));
        dc.fillRectangle(edge,y,right-edge,h+extra);
        dc.setForeground(FXRGB(0,0,0));  // render maths in BLACK for now
// Try to centre the formula across the line and within its space
// (well if it was a multi-line formula I try to centre the longest line
// at least roughly, and align the left of all others with that)
        int fh=b->text.height, fd=b->text.depth;
        int delta = (h+extra+fh-fd)/2;
// the next bit is worrying wrt pixels vs print units.
        int xoff = (x - b->text.width)/2;  // This would centre it.
        if (scale >= 0x28)                 // Multi-line formula fun.
        {   scale = (scale - 0x28)/8;      // Space on line in units of
            scale *= mathWidth;            // mathWidth, and now in pixels
            scale /= 2;                    // Now I have indent to centre it.
// Because the recorded "spare" info is not quite reliable I will try to
// adjust it to avoid spilling over edges even in truly dire cases.
            if (scale+b->text.width >= x) scale = x-b->text.width-1;
            if (scale < 0) scale = 0;
            xoff = scale;
        }
// Now actually display the formula!
        paintBox(&dc, b,  xoff, y+delta);
        b->top.measuredSize = -1; // force re-measure when printing finished.
// Whew! Done.
        p = charPointer;
        for (;;)
        {   if (p == length) return p;           // end of buffer
            if (getChar(p) == '\n') return p+1;  // end of line
            p++;
        }
    }
    int column = 0;
    FXuint style = getStyle(p), st = 0;
    int ch = ' ', x = left;
    for (;;)               // collect one line of output, which may end up
    {   char buff[84];     // expressed as multiple segments
        int bp = 0;
        for (;;)           // accumulate a segment
        {   if (p == length) break;       // stop at end of text buffer
            ch = getChar(p);
            if (ch == '\n') break;        // stop at end of this line
            if (column >= 80) break;      // need to wrap the line
            st = getStyle(p);
            if (ch == '\t') break;        // stop before tab
            if (st != style || (st & STYLE_CONTROL)!=0) break;
                                          // stop on style change
            buff[bp++] = ch;
            column++;
            p++;
        }
        if (bp!=0)
        {   x = printBufferText(dc, x, y, buff, bp, style);
        }
        if (p == length) return p;        // end of buffer
        if (ch == '\n') return p+1;       // end of (ordinary) line
        if (column >= 80) return p;       // end of wrapped line
        if (ch == '\t')                   // I ignore styles on tabs!
        {   int blanks = 8 - (column%8);
            x += dc.fntGetTextWidth("        ", blanks);
// Note that since I put tab-stops every 8 positions and my line length
// is 80, a tab can bring me up to the position where a line is about to
// wrap, but it could not cause a wrap in any case where a simple blank
// would not. The long and short of this is that I do not have to do anything
// at all special about line-wrapping here.
            column += blanks;
        }
        else if ((st & STYLE_CONTROL) != 0)
        {   buff[0] = '^';
            buff[1] = ch | 0x40;
// Here I do have a worry about line-wrapping. If I have 79 chars on the line
// already and then I issue a STYLE_CONTROL character it will want to
// print as "^X" for some "X". The "^" can go on the current line but the
// "X" needs to wrap to the next.
//
// I will IGNORE this issue now (except that I have left room in my buffer
// for slightly overlong lines, and made my wrap-test as ">=80" rather
// then "==80"). Thus in such cases (which my programs will never exercise!)
// the printed output can have a few lines 81 chars long rather than 80.
            x = printBufferText(dc, x, y, buff, 2, st & (~STYLE_CONTROL));
            column += 2;
        }
        else style = st;
        bp = 0;
    }
}


// The next function prints from character startc to endc in the print
// buffer. This may use several pages, depending on the number of lines
// to be printed and the page size. Lines will be wrapped at 80 columns.

void FXTerminal::printContents(FXDCNativePrinter &dc,
    int startc, int endc,
    int left, int right, int top, int bottom)
{
// the size of paper to print on is measured in points, taken here to
// run at 72 points per inch.
    int p = lineStart(startc);
    int hh=dc.fntGetFontSpacing();
    for (int pageNo=1;;pageNo++)
    {   dc.beginPage(pageNo);
        FXint yy = top + hh;
        int inMath = 'S';        // see corresponding screen drawing code
                                 // for an explanation of the logic here.
        while (yy < bottom)
        {
            int c1 = p<length ? 
                     (getStyle(p) & STYLE_MATH ? getChar(p) : 'x') :
                     'x';
            int c2 = p+1<length ? getChar(p+1) : 'x';
            if (inMath == 'S') 
            {   if (c1 == 0x02 && c2 == 0x02)
                {   inMath = 'T';
// This is about to print the top row of a bit of maths. Check how
// many rows in all will be used and whether there is room for them, and if
// not try to insert a page break.
                    int p1 = p+2;
                    int yyy = yy;
                    while (p1 < length && getChar(p1) == 0x02)
                    {   yyy += hh;
                        p1++;
                    }
                    if (yyy >= bottom && yy != top+hh)
                    {   break;   // force this formula to a new page
                    }
                    p = printTextRow(dc, p, yy, left, right);
                }
                else p = printTextRow(dc, p, yy, left, right);
            }
            else
            {   if (c1 != 0x02) p = printTextRow(dc, p, yy, left, right);
                if (c1 != 0x02 || c2 != 0x02) inMath = 'S';
            }
            if (p >= endc) break;
            yy += hh;
        }
        dc.endPage();
        if (p >= endc) break;
    }
    dc.endPrint();
}

// I want to create a font that will be fixed pitch and such that 80
// columns of text go neatly across the width of my paper. This selects
// a plausible choice by first creating a font of almost arbitrary size,
// then measuring the width that it delivers, and on that basis choosing a
// larger or smaller size to use. I use an initial point size of 10
// since that is about the size I expect to end up with.

// Note that the implementation I have here is suitable for a case
// where I have only one font associated with printing.

static void setPrinterFont(FXDCNativePrinter &dc, int pageWidth, const char *font_name)
{
#if (FOX_MINOR<=4)
    FXFont *f = dc.fntGenerateFont(font_name, 10, FONTWEIGHT_BOLD);
#else
    FXFont *f = dc.fntGenerateFont(font_name, 10, FXFont::Bold);
#endif
    f->create();
#if FOR_MAJOR==1 && FOX_MINOR==0
    dc.setTextFont(f);
#else
    dc.setFont(f);
#endif
// I will get the width of a string of 10 "M" characters to assess the
// width of my font. On a really clever system if count be other than a
// whole number of points.
    int w = dc.fntGetTextWidth("MMMMMMMMMM", 10);
// The font I have just measured might not have been exactly the size
// font I originally asked for, so I will check what size it was and
// base calculations on that. Note that (ugh) getSize returns the size
// in deci-points not points, so I have a factor of 10 to fudge in
// somewhere. I use the length of my string of "M" characters...
    double bestSize = dc.fntDoubleSize()*(double)pageWidth/(80.0*w);
// Now I think I know the size of font that would suit me best. I
// rather expect it to be 8pt or 9pt, but if the font I was using was
// more expanded or condensed it could stray somewhat from that range.
//
// For a NativePrinter I can specify a font size as a double, so I do
// that here to get as good a fit as I can.
    delete f;
#if (FOX_MINOR<=4)
    f = dc.fntDoubleGenerateFont(font_name, bestSize, FONTWEIGHT_BOLD);
#else
    f = dc.fntDoubleGenerateFont(font_name, bestSize, FXFont::Bold);
#endif
    f->create();
#if FOX_MAJOR==1 && FOX_MINOR==0
    dc.setTextFont(f);
#else
    dc.setFont(f);
#endif
}

long FXTerminal::doPrinting(int startp, int endp)
{
    FXPrintDialog d(this, "Print");
    d.setPrinter(printer);    // carry forward state from previous usage
    if (d.execute())
    {   d.getPrinter(printer);
        FXDCNativePrinter dc(getApp());
        if (!dc.beginPrint(printer))
        {   FXMessageBox::error(this, MBOX_OK, "Printer Error",
                "Unable to print to %s", printer.name.text());
            setFocus();
            return 1;
        }
#define PER_INCH 3600
// For measuring and moving around the page (while I print) I will
// work in terms of units of 1/3600in. This number has been selected so that
// a point (1/72in) is a whole number of units, and so that typical
// real printer resolutions like 300, 600, 720dpi let me work in whole
// numbers of units per pixel. In terms of this resolution an A4 page of
// paper is around 40000 units high - I believe I am a long way from
// running into integer overflow issues. Note that even though I measure
// and specify X and Y coordinates in these units, when I select font
// sized I still need to work in points.
        dc.setHorzUnitsInch(PER_INCH);
        dc.setVertUnitsInch(PER_INCH);
        int pw = dc.getPageWidth();
        int ph = dc.getPageHeight();
#define leftmargin   ((int)(leftmargin_inches*(double)PER_INCH))
#define rightmargin  ((int)(rightmargin_inches*(double)PER_INCH))
#define topmargin    ((int)(topmargin_inches*(double)PER_INCH))
#define bottommargin ((int)(bottommargin_inches*(double)PER_INCH))

// Another delicacy here. On Windows I make my default font "Courier New"
// and if I am doing a windows-print I can and should use that. However
// if I print to file I must use one of the simple Adobe fonts, since
// otherwise the Postscript I generate would need to map font names
// and/or embed detailed font information, and I do not support that!
//
// Well actually setPrinterFont will probably map "Courier New" onto
// "courier" in the relevant case, but it still makes sense that at this
// point I alert the gentle reader to the fact that screen and printer
// fonts may differ.
//
// A yet further qualification to the above commentary is that if I have
// any mathematics displayed via SHOWMATH then I will need to Computer
// Modern fonts to print it (just as I do to display it) and so I WILL embed
// them. For a first version (at least) of this I will embed the whole of
// the three fonts (cmr10, cmmi10, cmsy10 and cmex10) that I use by just
// sending the "*.pfa" files to the printer at the start of the print job.
// If I wanted to be seriously more messy here I could work out just what
// subset of characters I was using and send only those. That seems like way
// too much work for now!
        int widthToUse = pw-leftmargin-rightmargin;
        if (printer.flags&PRINT_DEST_FILE)
            setPrinterFont(dc, widthToUse, "courier");
        else setPrinterFont(dc, widthToUse, DEFAULT_FONT_NAME);
        cmrFontsEmbedded = 0;             // embed at first use: not done yet.
// If I can not select the correct maths font for the printer I will
// suppress printing!
//
// There us a real "jolly" here in that I want the font sizes to be such
// that things fit across the printed page in a way that is roughly the same
// as the way I fit things across the screen. The Fox-level code here
// (and selecting the main printer font) works in scaled abstract units,
// but until and unless I adjust things if I use Xft I would work in
// pixels - whatever they are in the printer case! I think that my best
// resolution is to arrange that I do NOT use Xft when dealing with printer
// fonts. The effect will then be that I use the more ordinary Fox procedures
// to measure everything, and that should lead to some degree of consistency
// without me needing to retrofit Fox-style scaled coordinate systems to
// other stuff I do with FXShowMath.
        void *fontSave[36];
        for (int i=0; i<36; i++)
        {   fontSave[i] = masterFont[i];
            masterFont[i] = NULL;  // NECESSARY!
        }
        if (changeMathFontSize(application_object, -widthToUse))
            printContents(dc, startp, endp,   // which bit needs printing?
                          leftmargin, pw-rightmargin,
                          topmargin, ph-bottommargin);
#if FOX_MAJOR==1 && FOX_MINOR==0
        delete dc.getTextFont();
#else
        delete dc.getFont();
#endif
        for (int i=0; i<36; i++)
        {   masterFont[i] = fontSave[i];
        }
    }
    setFocus();   // I am uncertain, but without this I lose focus...
    return 1;
}

long FXTerminal::onCmdPrint(FXObject *c, FXSelector s, void *ptr)
{
    keyFlags &= ~ESC_PENDING;
    return doPrinting(0, length);
}

long FXTerminal::onCmdPrintSelection(FXObject *c, FXSelector s, void *ptr)
{
    keyFlags &= ~ESC_PENDING;
// This actually prints the lines containing the whole selection...
    return doPrinting(getSelStartPos(), getSelEndPos());
}


// Cut
long FXTerminal::onCmdCutSel(FXObject *, FXSelector, void *)
{
// I will not permit a CUT from anywhere other than part of the current
// input line. I delegate the messy but to COPY.
    if (selstartpos < selendpos &&
        promptEnd <= selstartpos &&
        (options & TEXT_READONLY) == 0)
    {   onCmdCopySel(this, 0, NULL);
// I will only delete the stuff if I managed to copy it to the clipboard,
// which I can tell here by virtue of clipbuffer being reasonable.
        if (clipbuffer)
        {   removeText(selstartpos, selendpos-selstartpos, TRUE);
            killSelection(TRUE);
            setCursorPos(cursorpos, TRUE);
            makePositionVisible(cursorpos);
            flags |= FLAG_CHANGED;
            modified = TRUE;
        }
    }
    else getApp()->beep();
    return 1;
}


// Copy
long FXTerminal::onCmdCopySel(FXObject *, FXSelector, void *)
{
    FXDragType types[2];
    if (selstartpos < selendpos)
    {   types[0]=stringType;
        types[1]=textType;
// I am going to put text onto the clipboard in HTML format - right now
// I am not quite certain how to make an Atom that declares that they is
// the mime type I will use...
        if (acquireClipboard(types, 2))
        {
// Now I am going to want to convert from what I find in the text buffer
// into what I want to place on the clipboard. What I will want to generate
// will be along the lines of
//  <   ->   &lt;    : -> 4
//  >   ->   &gt;    : -> 4
//  &   ->   &amp;   : -> 5
//  \n  ->   <br>    : -> 4+CRLF
//
// POSSIBLY   ' ' -> &nbsp;
//            '"' -> &quo;      but I do not do those transformations yet.
//  
//  <html><body><style>tt.prompt{color:rgb(0,64,128)}</style><tt>   : 61+CRLF
//  Line 1<br>                                                      : +4+CRLF
//  <tt class="prompt">PROMPT:</tt>Line 2<br>                       : +19+5+4
//  Line 3<br>
//  </tt></body></html>                                             : +19
            const char *clipStart   = "<html><body><style>tt.prompt"
                                      "{color:rgb(0,64,128)}"
                                      "</style><tt>\r\n";
            const char *clipEnd     = "</tt></body></html>\r\n";
            const char *prStart     = "<tt class=\"prompt\">";
            const char *prEnd       = "</tt>";
            int style = 0, i;
            cliplength = strlen(clipStart);
            for (i=selstartpos; i<selendpos; i++)
            {   char ch = getChar(i);
                int st = getStyle(i) & STYLE_PROMPT;
                if (st != style)
                {   if (st) cliplength += strlen(prStart);
                    else cliplength += strlen(prEnd);
                    style = st;
                }
                switch (ch)
                {
            case '<':
            case '>': cliplength += 4; break;
            case '&': cliplength += 5; break;
            case '\n':cliplength += 6; break;
            default:  cliplength++;    break;
                }
            }
            if (style) cliplength += strlen(prEnd);
            cliplength += strlen(clipEnd);
            FXFREE(&clipbuffer);
            FXCALLOC(&clipbuffer, FXchar, cliplength+1);
            if (!clipbuffer)
            {   fxwarning("%s::onCmdCopySel: out of memory\n",getClassName());
                cliplength=0;
            }
            else
            {   char *p = clipbuffer;
                strcpy(p, clipStart);
                style = 0;
                p += strlen(clipStart);
// Now I have to copy the selected region mapping it onto the HTML that I
// want it to be. Slightly messy!
                for (i=selstartpos; i<selendpos; i++)
                {   char ch = getChar(i);
                    int st = getStyle(i) & STYLE_PROMPT;
                    if (st != style)
                    {   if (st)
                        {   strcpy(p, prStart);
                            p += strlen(prStart);
                        }
                        else
                        {   strcpy(p, prEnd);
                            p += strlen(prEnd);
                        }
                        style = st;
                    }
                    switch (ch)
                    {
                case '<': strcpy(p, "&lt;");     p += 4; break;
                case '>': strcpy(p, "&gt;");     p += 4; break;
                case '&': strcpy(p, "&amp;");    p += 5; break;
                case '\n':strcpy(p, "<br>\r\n"); p += 6; break;
                default:  *p++ = ch;                     break;
                    }
                }
                if (style)
                {   strcpy(p, prEnd);
                    p += strlen(prEnd);
                }
                strcpy(p, clipEnd);
            }
        }
    }
    return 1;
}


// Paste clipboard

long FXTerminal::onCmdPasteSel(FXObject *, FXSelector, void *)
{
    if (!isEditable() || paste_buffer)
    {   getApp()->beep();
        return 1;
    }
    if (isPosSelected(cursorpos))
    {   removeText(selstartpos, selendpos-selstartpos, TRUE);
        killSelection(TRUE);
        setCursorPos(cursorpos, TRUE);
        makePositionVisible(cursorpos);
        flags |= FLAG_CHANGED;
        modified = TRUE;
    }
    FXchar *string;
    FXint len;
    if (getDNDData(FROM_CLIPBOARD, stringType,
                   (FXuchar*&)string, (FXuint&)len))
        performPaste(string, len);
    return 1;
}


// Paste selection (used for middle mouse button)

long FXTerminal::onCmdPasteMiddle(FXObject *, FXSelector, void *)
{
    if (!isEditable() || paste_buffer)
    {   getApp()->beep();
        return 1;
    }
    FXchar *string; FXint len;
    if (selstartpos==selendpos ||
        cursorpos<=selstartpos ||
        selendpos<=cursorpos)
    {   // Avoid paste inside selection
        if (getDNDData(FROM_SELECTION, stringType,
                       (FXuchar*&)string, (FXuint&)len))
            performPaste(string, len);
    }
    return 1;
}

void FXTerminal::performPaste(FXchar *string, FXint len)
{
    paste_buffer = string;
    paste_n = len;
    paste_p = 0;
    paste_flags = 0;
// Now decide if I think I have an HTML paste. First skip simple whitespace
    while (*string == ' ' || *string == '\r' || *string == '\n')
    {   paste_p++;
        string++;
    }
    if (string[0] == '<' &&
        tolower(string[1]) == 'h' &&
        tolower(string[2]) == 't' &&
        tolower(string[3]) == 'm' &&
        tolower(string[4]) == 'l' &&
        string[5] == '>')
    {   paste_is_html = 1;
        paste_p += 6;
// OK, so in the HTML case I now point at the body of the stuff. I will need
// to ignore HTML tags (both opening and closing) while I transfer stuff, and
// I will want to ignore prompts, which are marked as
//   <tt style="prompt"> ... </tt>
// and I will also want to ignore style declaractions as in
//   <style> ... </style>
// In each case I will suppose that I do not have other HTML blocks nested
// inside.
    }
    else
    {   paste_is_html = 0;
        paste_p = 0;
    }
    if (insertFromPaste()) onCmdInsertNewline(this, 0, NULL);
}

int FXTerminal::isStartPrompt(const char *s)
{
// This is crummy code! It looks for 'tt class="prompt"'
// and allows arbitrary case within "tt" and "class" and whitespace
// there too.
    while (*s!=0 && isspace(*s)) s++;
    if (tolower(*s) != 't') return 0;
    s++;
    if (tolower(*s) != 't') return 0;
    s++;
    while (*s!=0 && isspace(*s)) s++;
    if (tolower(*s) != 'c') return 0;
    s++;
    if (tolower(*s) != 'l') return 0;
    s++;
    if (tolower(*s) != 'a') return 0;
    s++;
    if (tolower(*s) != 's') return 0;
    s++;
    if (tolower(*s) != 's') return 0;
    s++;
    while (*s!=0 && isspace(*s)) s++;
    if (tolower(*s) != '=') return 0;
    s++;
    if (strncmp(s, "\"prompt\"", 8) != 0) return 0;
    return 1;
}

int FXTerminal::isStyle(const char *s)
{
    const char *s0 = s;
    while (*s!=0 && isspace(*s)) s++;
    if (tolower(*s) != 's') return 0;
    s++;
    if (tolower(*s) != 't') return 0;
    s++;
    if (tolower(*s) != 'y') return 0;
    s++;
    if (tolower(*s) != 'l') return 0;
    s++;
    if (tolower(*s) != 'e') return 0;
    s++;
    return (s - s0);
}

int FXTerminal::insertFromPaste()
{
// I will deal with the easy case of plain text pastes first
    if (!paste_is_html)
    {   for (;;)
        {   int ch;
            if (paste_p == paste_n || (ch = paste_buffer[paste_p++]) == 0)
            {   FXFREE(&paste_buffer);
                paste_n = paste_p = paste_is_html = 0;
                return 0;  // all done and finished.
            }
            if (ch == '\r') continue;
            else if (ch == '\n') return 1;
            else insertText(cursorpos, &paste_buffer[paste_p-1], 1, 0);
        } 
    }
// Inserting from HTML is really rather similar to plain inserting, except
// that I want to process items such as "&lt;", skip HTML tags such
// as </tt> and detect as a special case '<tt type="prompt">'.

    for (;;)
    {   int ch;
        if (paste_p >= paste_n || (ch = paste_buffer[paste_p++]) == 0)
        {   FXFREE(&paste_buffer);
            paste_n = paste_p = paste_is_html = 0;
            return 0;  // all done and finished.
        }
        if (ch == '\r' || ch == '\n') continue;
// Since I only worry about three "&" items here I will write out tests
// in-line. If I had more I ought to set up something table-driven. If I
// really want to handle HTML that comes from other applications I ought
// to think about a LOT more cases... but while my concentration is on
// cut-and-paste to my own code I can remain happy just like this.
        if (ch == '&' && !paste_flags)
        {   if (strncmp(&paste_buffer[paste_p], "lt;", 3)==0)
            {   paste_p += 3;
                if (paste_p > paste_n) continue; // ran over the end
                insertText(cursorpos, "<", 1, 0);
                continue;
            }
            if (strncmp(&paste_buffer[paste_p], "gt;", 3)==0)
            {   paste_p += 3;
                if (paste_p > paste_n) continue; // ran over the end
                insertText(cursorpos, ">", 1, 0);
                continue;
            }
            if (strncmp(&paste_buffer[paste_p], "amp;", 4)==0)
            {   paste_p += 4;
                if (paste_p > paste_n) continue; // ran over the end
                insertText(cursorpos, "&", 1, 0);
                continue;
            }
        }
// In handling HTML tags I will permit lower or upper case, however I
// will not allow extra whitespace. Thus "< br>" or "<br >" will not do!
        if (ch == '<')
        {   if (tolower(paste_buffer[paste_p]) == 'b' &&
                tolower(paste_buffer[paste_p+1]) == 'r' &&
                paste_buffer[paste_p+2] == '>')
            {   paste_p += 3;     // <br> encodes a newline
                paste_flags = 0;
                if (paste_p > paste_n) continue; // ran over the end
                return 1;
            }
            if (isStartPrompt(&paste_buffer[paste_p]) ||
                isStyle(&paste_buffer[paste_p])) paste_flags = 1;
            else paste_flags = 0;
            while (paste_p < paste_n &&
                   paste_buffer[paste_p] != '>') paste_p++;
            paste_p++;  // past the ">"
            continue;
        }
        if (!paste_flags) insertText(cursorpos, &paste_buffer[paste_p-1], 1, 0);
    } 
}


long FXTerminal::onCmdReinput(FXObject *c, FXSelector s, void *ptr)
{
    keyFlags &= ~ESC_PENDING;
// "ReInput" acts as a copy, followed by cursor movement to the end of
// the input line, a cancelling of the selection and finally a paste.
    onCmdCopySel(c, s, ptr);
    killSelection(TRUE);  // "deselect all"
    onCmdEnd(c, s, ptr);
    long r = onCmdPasteSel(c, s, ptr);
    setFocus();
    return r;
}

long FXTerminal::onCmdClear(FXObject *c, FXSelector s, void *ptr)
{
    keyFlags &= ~ESC_PENDING;
// Discard absolutely everything! Including any prompt or currently part-
// completed input-line.
    setText("", 0);
    recalc();
    update();
    setFocus();   // I am uncertain, but without this I lose focus...
    return 1;
}

long FXTerminal::onCmdRedraw(FXObject *c, FXSelector s, void *ptr)
{
    keyFlags &= ~ESC_PENDING;
// This is intended for use when a bug or something has left the screen
// corrupted. I hope that what I do here will be enough to force a
// reasonably complete re-draw.
    recalc();
    update();
    setFocus();   // I am uncertain, but without this I lose focus...
    return 1;
}

long FXTerminal::onCmdHome(FXObject *c, FXSelector s, void *ptr)
{
    keyFlags &= ~ESC_PENDING;
    makePositionVisible(0);
    setCursorPos(0);
    setFocus();   // I am uncertain, but without this I lose focus...
    return 1;
}

long FXTerminal::onCmdEnd(FXObject *c, FXSelector s, void *ptr)
{
    keyFlags &= ~ESC_PENDING;
    int n = rowStart(length);
    makePositionVisible(n);
// The above two encourage the system to do any horizontal scrolls
// that it can to try to make the start of the final line visible
// as well as its end.  Well maybe I will be trying to prevent horizontal
// scrolling anyway, but I will leave this code here since it is
// fairly harmless.
    makePositionVisible(length);
    setCursorPos(length);
    setFocus();   // I am uncertain, but without this I lose focus...
    return 1;
}

long FXTerminal::onCmdFont(FXObject *c, FXSelector s, void *ptr)
{
    keyFlags &= ~ESC_PENDING;
    FXFontDialog d(this, "Font", DECOR_BORDER|DECOR_TITLE);
    FXFontDesc description;
    font->getFontDesc(description);    // infomation about the current font ..
#if (FOX_MINOR<=4)
    description.flags =
        (description.flags & ~FONTPITCH_VARIABLE) | FONTPITCH_FIXED;
#else
    description.flags =
        (description.flags & ~FXFont::Variable) | FXFont::Fixed;
#endif
    d.setFontSelection(description);   // .. and make that default choice!
// I really want to adjust the font-selector dialog so that it only
// shows and accepts fixed-pitch fonts. I am not quite sure how to do
// this yet.
    if (d.execute())
    {   FXFont *o = font;
        d.getFontSelection(description);
        FXFont *newFont = new FXFont(application_object, description);
        newFont->create();
        if (!newFont->isFontMono())
        {   delete newFont;
            FXMessageBox::error(this, MBOX_OK, "Error",
                "You must select a fixed-pitch font");
            return 1;
        }
        setFont(newFont);
        delete o;                     // I must delete the old font.
    }
    setFocus();   // I am uncertain, but without this I lose focus...
    return 1;
}

long FXTerminal::onCmdResetFont(FXObject *c, FXSelector s, void *ptr)
{
    keyFlags &= ~ESC_PENDING;
// Resets the font (and thus window-width) to my default. This is to give
// some level of safety in case I feel messed up.
    FXFont *o = font;
#if (FOX_MINOR<=4)
    FXFont *f = selectFont(DEFAULT_FONT_NAME, 0,  // 0 means "choose for me"
        FONTWEIGHT_BOLD, FONTSLANT_REGULAR, FONTENCODING_DEFAULT,
        FONTSETWIDTH_DONTCARE, FONTPITCH_FIXED|FONTHINT_MODERN);
#else
    FXFont *f = selectFont(DEFAULT_FONT_NAME, 0,  // 0 means "choose for me"
        FXFont::Bold, FXFont::Straight, FONTENCODING_DEFAULT,
        0, FXFont::Fixed|FXFont::Modern);
#endif
    setFont(f);
    delete o;
    setFocus();   // I am uncertain, but without this I lose focus...
    return 1;
}

long FXTerminal::onCmdResetWindow(FXObject *c, FXSelector s, void *ptr)
{
    keyFlags &= ~ESC_PENDING;
// Put the whole window back in a tidy-ish state
    setVisibleRows(24);
    onCmdResetFont(c, s, ptr);
    int dx = getDefaultWidth()+FXScrollArea::vertical->getDefaultWidth();
    int dy = main_window->getDefaultHeight();
    main_window->getShell()->resize(dx, dy);
    update();     // major changes and so I should refresh everything
    setFocus();   // I am uncertain, but without this I lose focus...
    return 1;
}

long FXTerminal::onCmdBreak(FXObject *c, FXSelector s, void *ptr)
{
    keyFlags &= ~ESC_PENDING;
// I always call the interrupt callback procedure. If the user task was
// suspended waiting for input then I release it causing the fwin_getchar()
// call to return a control-C.
    if (pauseFlags & PAUSE_DISCARD) main_window->setTitle(window_full_title);
    pauseFlags &= ~PAUSE_DISCARD;
    if (interrupt_callback != NULL) (*interrupt_callback)(QUIET_INTERRUPT);
    if (isEditable()) // at present we are waiting for keyboard input.
    {   inputBuffer[0] = 0x1f & 'C';
        inputBuffer[1] = 0;
        inputBufferLen = 1;
        inputBufferP = 0;
        if (sync_even)
        {   sync_even = 0;
            UnlockMutex(mutex3);
            LockMutex(mutex2);
            UnlockMutex(mutex4);
        }
        else
        {   sync_even = 1;
            UnlockMutex(mutex1);
            LockMutex(mutex4);
            UnlockMutex(mutex2);
        }
        recently_flushed = 0;
        FXMathText::appendText("^C", 2);
        long r = FXMathText::onCmdInsertNewline(c, s, ptr);
        setEditable(FALSE);
        setFocus();   // I am uncertain, but without this I lose focus...
        return r;
    }
    type_in = type_out = 0;
    setFocus();   // I am uncertain, but without this I lose focus...
    return 1;
}

long FXTerminal::onCmdBacktrace(FXObject *c, FXSelector s, void *ptr)
{
#ifdef CSL
// The concept of a "noisy" interrupt is not obviously relevant to everybody
// so the code here is maybe CSL-specific...
#endif
    keyFlags &= ~ESC_PENDING;
    if (pauseFlags & PAUSE_DISCARD) main_window->setTitle(window_full_title);
    pauseFlags &= ~PAUSE_DISCARD;
    if (interrupt_callback != NULL) (*interrupt_callback)(NOISY_INTERRUPT);
    if (isEditable()) // at present we are waiting for keyboard input.
    {   inputBuffer[0] = 0x1f & 'G';
        inputBuffer[1] = 0;
        inputBufferLen = 1;
        inputBufferP = 0;
        if (sync_even)
        {   sync_even = 0;
            UnlockMutex(mutex3);
            LockMutex(mutex2);
            UnlockMutex(mutex4);
        }
        else
        {   sync_even = 1;
            UnlockMutex(mutex1);
            LockMutex(mutex4);
            UnlockMutex(mutex2);
        }
        recently_flushed = 0;
        FXMathText::appendText("^G", 2);
        long r = FXMathText::onCmdInsertNewline(c, s, ptr);
        setEditable(FALSE);
        setFocus();   // I am uncertain, but without this I lose focus...
        return r;
    }
    type_in = type_out = 0;
    setFocus();   // I am uncertain, but without this I lose focus...
    return 1;
}


#ifdef CSL

char **modules_list, **switches_list;

long FXTerminal::onCmdLoadModule(FXObject *c, FXSelector s, void *ptr)
{
    keyFlags &= ~ESC_PENDING;
    FXString ss = ((FXMenuCommand *)c)->getText();
    const char *mtext = ss.text();
    if (isEditable())
    {   killSelection();
        setInputText("", 0);
        appendText("load_package \"", 14, FALSE);
        appendText(mtext, strlen(mtext), FALSE);
        appendText("\";", 2, FALSE);
        onCmdInsertNewline(c, s, ptr);
    }
    else
    {   string_ahead("load_package \"");
        string_ahead(mtext);
        string_ahead("\";\n");
    }
    setFocus();   // I am uncertain, but without this I lose focus...
    return 1;
}

long FXTerminal::onCmdReduceUpdate(FXObject *c, FXSelector s, void *ptr)
{
    keyFlags &= ~ESC_PENDING;
    if (isEditable())
    {   killSelection();
        setInputText("", 0);
        appendText("update_reduce();", 16, FALSE);
        onCmdInsertNewline(c, s, ptr);
    }
    else
    {   string_ahead("update_reduce();\n");
    }
    setFocus();   // I am uncertain, but without this I lose focus...
    return 1;
}

long FXTerminal::onCmdFlipSwitch(FXObject *c, FXSelector s, void *ptr)
{
    keyFlags &= ~ESC_PENDING;
    FXMenuCheck *m = (FXMenuCheck *)c;
    FXString ss = m->getText();
    const char *mtext = ss.text();
    int state = m->getCheck();
    const char *cmd;
// The very act of selecting the menu item flipped its state, and so now
// I need to force the underlying system to reflect that by issuing a
// suitable command.
    if (state == TRUE) cmd = "on ";
    else cmd = "off ";
    if (isEditable())
    {   killSelection();
        setInputText("", 0);
        appendText(cmd, strlen(cmd), FALSE);
        appendText(mtext, strlen(mtext), FALSE);
        appendText(";", 1, FALSE);
        onCmdInsertNewline(c, s, ptr);
    }
    else
    {   string_ahead(cmd);
        string_ahead(mtext);
        string_ahead(";\n");
    }
// I also want to record in my table of switches the new state of this one.
    for (char **switches = switches_list; *switches!=NULL; switches++)
    {   char *p = *switches;
        if (strcmp(p+1, mtext) == 0)
        {   if (state == TRUE) *p = 'y';
            else *p = 'n';
            break;
        }
    }
    setFocus();   // I am uncertain, but without this I lose focus...
    return 1;
}

#ifndef WIN32
#if !defined MACINTOSH || !defined MAC_FRAMEWORK

// On Windows I can browse an HTML file using the user's default browser by
// invoking the "ShellExececute" function. On other systems I need to know
// what browser to use, and there is probably no global concept of the
// "preferred" one. So the first time a user tries to access help, or if they
// use a menu entry on the HELP menu, a dialog box for selecting between a
// number of browsers (plus an option for the user to type in a custom name)
// will appear, amd the information so gained gets recorded for future uses.
//

#define NBROWSERS 10

class FXAPI BrowserBox : public FXDialogBox
{
    FXDECLARE(BrowserBox)
public:
    BrowserBox(FXApp *, const char *p);
    BrowserBox();
    void addbutton(FXVerticalFrame *v, const char *name, const char *d);
    long onButton(FXObject *,FXSelector,void *);
    long onText(FXObject *,FXSelector,void *);
    int nbr;
    FXRadioButton *choices[NBROWSERS];
    FXTextField *text;
    char data[40];
    const char *path;
};

FXDEFMAP(BrowserBox) BrowserBoxMap[] =
{
    FXMAPFUNC(SEL_COMMAND,      FXDialogBox::ID_LAST, BrowserBox::onButton),
    FXMAPFUNC(SEL_COMMAND,      FXDialogBox::ID_LAST+1, BrowserBox::onText),
};

FXIMPLEMENT(BrowserBox, FXDialogBox, BrowserBoxMap, ARRAYNUMBER(BrowserBoxMap))

static int file_is_executable(char *filename)
{
    struct stat buf;
    if (*filename == 0) return 0;
    if (stat(filename, &buf) == -1) return 0;
#ifndef S_IXUSR
    return 1;
#else
    return (buf.st_mode & S_IXUSR);
#endif
}


void BrowserBox::addbutton(FXVerticalFrame *v, const char *name, const char *d)
{
    char menuname[256];
// I will start by seeing if I can find the named browser in my PATH
    const char *p = path;
    int found = 0;
    while (*p != 0)
    {   int j = 0;
        while (*p!=0 && *p!=':')
        {   if (j < 240) menuname[j++] = *p;
            p++;
        }
        menuname[j++] = '/';
        strcpy(&menuname[j], name);
        if (file_is_executable(menuname))
        {   found = 1;
            break;
        }
        if (*p!=0) p++;
    }
    if (!found) return;
    menuname[0] = '&';
    strcpy(menuname+1, name);
    menuname[1] = toupper(menuname[1]);
    choices[nbr & 0xff] =
        new FXRadioButton(v, menuname, this, FXDialogBox::ID_LAST);
    if (strcmp(name, d)==0) 
    {   choices[nbr & 0xff]->setCheck();
        nbr += 0x100;  /* flag to say that a default has been set */
    }
    nbr++;
}

BrowserBox::BrowserBox(FXApp *a, const char *p) :
            FXDialogBox(a, "Choose your browser")
{
    strcpy(data, p);
    FXVerticalFrame *v =
        new FXVerticalFrame(this, LAYOUT_FILL_X|LAYOUT_FILL_Y);
// Elsewhere in parts of my code I conditionalise getenv() to force the
// name to upper case when under Windows and to allow for some (ancient?)
// variants on Unix where it takes two arguments. I will not worry about
// either of those issues here.
    path = getenv("PATH");
    int i;
    for (i=0; i<NBROWSERS; i++)
        choices[i] = NULL;
    nbr = 0;
    addbutton(v, "firefox", p);
    addbutton(v, "galeon", p);
    addbutton(v, "konqueror", p);
    addbutton(v, "mozilla", p);
    addbutton(v, "netscape", p);
    addbutton(v, "opera", p);
    choices[nbr & 0xff] = new FXRadioButton(v, "&User:", this, FXDialogBox::ID_LAST);
    FXHorizontalFrame *h0 =
        new FXHorizontalFrame(v,
            LAYOUT_TOP|LAYOUT_LEFT|LAYOUT_FILL_X|PACK_UNIFORM_WIDTH);
    new FXLabel(h0, "User command to launch browser:");
    text = new FXTextField(h0, 32, this, FXDialogBox::ID_LAST+1);
    if (nbr <= 0xff)
    {   choices[nbr]->setCheck();
        text->setText(p);
        strcpy(data, p);
    }
    nbr &= 0xff;
    FXHorizontalFrame *h1 =
        new FXHorizontalFrame(v,
            LAYOUT_TOP|LAYOUT_LEFT|LAYOUT_FILL_X|PACK_UNIFORM_WIDTH);
    new FXButton(h1, "&OK", NULL, this, FXDialogBox::ID_ACCEPT,
        BUTTON_INITIAL|BUTTON_DEFAULT|FRAME_RAISED|FRAME_THICK|
        LAYOUT_TOP|LAYOUT_LEFT|LAYOUT_CENTER_X);
    new FXButton(h1, "&Cancel", NULL, this, FXDialogBox::ID_CANCEL,
        BUTTON_INITIAL|BUTTON_DEFAULT|FRAME_RAISED|FRAME_THICK|
        LAYOUT_TOP|LAYOUT_LEFT|LAYOUT_CENTER_X);
}

BrowserBox::BrowserBox() : FXDialogBox()
{
}

long BrowserBox::onButton(FXObject *a, FXSelector s, void *p)
{
    for (int i=0; i<NBROWSERS; i++)
    {   if (choices[i] == NULL) continue;
        if (a != choices[i]) choices[i]->setCheck(FALSE);
        else
        {   if (i == nbr)
            {   FXString s = text->getText();
                strcpy(data, s.text());
            }
            else
            {   FXString s = choices[i]->getText();
                strcpy(data, s.text());
                data[0] = tolower(data[0]);
            }
        }
    }
    return 1;
}

long BrowserBox::onText(FXObject *a, FXSelector s, void *p)
{
    for (int i=0; i<NBROWSERS; i++)
    {   if (choices[i] != NULL)
            choices[i]->setCheck(FALSE);
    }
    choices[nbr]->setCheck();
    FXString ss = text->getText();
    strcpy(data, ss.text());
    return 1;
}

static char preferred_browser[40];

static const char *selectBrowser(FXRegistry *reg, const char *preferred)
{
    BrowserBox box(application_object,  preferred);
    int rc = box.execute(PLACEMENT_OWNER);
    if (rc == 1)
    {   strncpy(preferred_browser, box.data, 40);
        preferred_browser[39] = 0;
        preferred = preferred_browser;
        reg->writeStringEntry("browser", "preferred", preferred);
    }
    return preferred;
}

long FXTerminal::onCmdSelectBrowser(FXObject *c, FXSelector s, void *ptr)
{
    FXRegistry *reg = &application_object->reg();
    const char *preferred = reg->readStringEntry("browser", "preferred");
    if (preferred == NULL || *preferred == 0) preferred = "mozilla";
    selectBrowser(reg, preferred);
    setFocus();
    return 1;
}

#endif
#endif

#if defined MACINTOSH && defined MAC_FRAMEWORK

// The code included here comes from an Apple library, and it has its
// redistribution rights listed in comments at its top.

#include "FinderLaunch.c"

#endif

long FXTerminal::onCmdHelp(FXObject *c, FXSelector s, void *ptr)
{
// I expect to have a directory called r38.doc in the same place that the
// r38 executable and the file r38.img live (the same style applies if the
// program is called something other than "r38"). The directory r38.doc
// should contain a file index.html and a help request will launch a browser
// to inspect that. The user's preferred browser will be recorded in the
// registry in the Unix case, but is dealt with by ShellExecute in the
// Windows case.
#if defined MACINTOSH && defined MAC_FRAMEWORK
    char helpFile[256];
    sprintf(helpFile, "%s.doc/index.html", fwin_full_program_name);
    if (CGDisplayIsActive(CGMainDisplayID()) != 1)
    {   FXMessageBox::error(this,
                MBOX_OK, "Manual Browser Launch Needed",
                "Please browser the file %s", helpFile);

    }
    else if (fork() == 0)
    {
// Attempting to launch a browser in this way seems to cause big trouble
// if you are connected to the Mac via a remote X session. As a really
// heavy-handed way to arrange that this trouble does not spill over and
// disrupt anything else that I am doing I will run the "delicate" procedure
// in a separate fork where ANYTHING that happens should be well isolated.
// The earlier check in CGDisplayIsActive() is supposed to have filtered
// trouble away, but I am going to try to be super careful!
        int n = FinderLaunch(helpFile);
        if (n != noErr)
        {   FXMessageBox::error(this,
                MBOX_OK, "Error",
                "Sorry - help file not available (%s:%d)", helpFile,n);
        }
        exit(0);
    }
#else
#ifdef WIN32
    char helpFile[256];
    GetModuleFileName(NULL, helpFile, 250);
    strcpy(helpFile+strlen(helpFile)-3, "doc");
    HINSTANCE n = ShellExecute(NULL,            // parent window for popup
                              "open",           // verb
                              "index.html",     // file to open
                              NULL,             // parameters to pass
                              helpFile,         // directory to run in
                              SW_SHOWNORMAL);
    if (n <= (HINSTANCE)32)
        FXMessageBox::error(this, MBOX_OK, "Error",
                            "Sorry - help file not available (%p)", n);

#else
    FXRegistry *reg = &application_object->reg();
    const char *preferred = reg->readStringEntry("browser", "preferred");
    if (preferred == NULL || *preferred == 0)
        preferred = selectBrowser(reg, "mozilla");
    char helpFile[256];
    sprintf(helpFile, "file://%s/%s.doc/index.html", programDir, programName);
// For non-windows the browsers I might imagine include
//      netscape, mozilla, opera, firebird, konqueror, galeon, ...
// I will try these in turn. It is probably a politically delicate issue
// which one I try first! If I do not find any then just nothing will
// happen.
    if (fork() == 0)
    {   const char *browsers[] = {
            NULL,
            "opera",
            "mozilla",
            "konqueror",
            "galeon",
            "netscape",
            NULL};
// I put the user's preferred browser as the first to try, but if that
// does not work I try a further bunch.
        browsers[0] = preferred;
        const char **b = browsers;
// As soon as one of these calls to execlp succeeds in launching a browser
// I lose all control, and in particular there is no risk of me ever launching
// two browsers.
        while (*b != NULL)
        {   execlp(*b, *b, helpFile, NULL);
            b++;
        }
// If NONE of the browsers manage to launch I get here. But note that I am in
// a fork, so if I just exit the attempt to browse help will terminate fairly
// cleanly. I might quite like to pop up a dialog box reporting failure but
// to feel save on that I feel I ought to agree with the main fork. Too much
// hassle!!
        fflush(stdout);
        exit(1);
    }
#endif
#endif
    setFocus();
    return 1;
}

#endif // CSL

long FXTerminal::onCmdAbout(FXObject *c, FXSelector s, void *ptr)
{
// each line of about_box information is limited to 32 chars.
    char msg[4*32+4];
    sprintf(msg, 
        "%s\n%s\n%s\n%s",
        about_box_description,
        about_box_rights_1,
        about_box_rights_2,
        about_box_rights_3);
    FXMessageBox about(this, 
        about_box_title,
        msg,
        main_window->getIcon(),
        MBOX_OK|DECOR_TITLE|DECOR_BORDER);
    about.execute(PLACEMENT_OWNER);
    setFocus();
    return 1;
}

int FXTerminal::forceWidth()
{
    int dx = getDefaultWidth()+FXScrollArea::vertical->getDefaultWidth();
    if (dx != main_window->getWidth())
    {   int dy = main_window->getHeight();
        main_window->getShell()->resize(dx, dy);
    }
    return dx;
}

void FXTerminal::setFont(FXFont *font0)
{
    FXMathText::setFont(font0);
    lineSpacing = font0->getFontSpacing();
    setVisibleColumns(80);  // but do not change rows..
    recalc();
    int dx = getDefaultWidth()+FXScrollArea::vertical->getDefaultWidth();
    int dy = main_window->getHeight();
    main_window->getShell()->resize(dx, dy);
// I will force at least the top left of my window to be visible, and if I can
// I will make it all visible.
    int x = main_window->getX(), y = main_window->getY();
    if (x < 0) dx = -x;       // need to move right
    else if (x+dx>rootWidth)  // need to move left
    {   dx = rootWidth - x - dx;
        if (x + dx < 0) dx = -x; // but try to leave left edge visible still
    }
    else dx = 0;
// The next line used to say (y<0) but so that if the window started strictly
// above the screen it got moved down. I now ensure that after the reset the
// window is at least 4 pixels down from the top of the screen. I do this
// because on Linux at least the "main_window" here does not include the
// title bar and other decorations and I need at least a small amount of
// title bar visible if I am to be able to drag the window to re-position it.
// But I am happy to guarantee just 4 pixels not the whole lot...
#define TOPGAP 5
    if (y < TOPGAP) dy = TOPGAP - y;
    else if (y+dy>rootHeight)
    {   dy = rootHeight - y - dy;
        if (y + dy < 0) dy = -y;
    }
    else dy = 0;
    if (dx != 0 || dy != 0) main_window->move(x+dx, y+dy);
// Since the window width has probably changed I should adjust the size of my
// maths font so that thing still fit neatly. However it happens that I
// will call setFont while creating an FXTerminal before the showMath module
// has been initialised, and so I must not try to meddle with it too
// early.
    if (showmathInitialised)
        changeMathFontSize(application_object, -getDefaultWidth());
// The above line has some depths! I insist that if the new set of
// fonts that I want can not be opened that the old lot remain available,
// because otherwise attempts to update the display would crash horribly,
// and I do not have an easy recipe for switching off reliance on fancy
// fonts part way through a run! So failure to open a font when the main
// font size changes will be BAD but its badness will be limited to
// having formulae remain the same size, and future attempts to change font
// size will re-try.
    update();       // major changes and so I should refresh everything
    makePositionVisible(cursorpos);

// Now I update the registry so that when I next start this application
// the same font and screen configuration will apply
    FXRegistry *reg = &(application_object->reg());
    reg->writeStringEntry("screen", "fontname",  font0->getName().text());
    reg->writeIntEntry("screen", "fontsize",     font0->getSize());
    reg->writeIntEntry("screen", "fontweight",   font0->getWeight());
    reg->writeIntEntry("screen", "fontslant",    font0->getSlant());
    reg->writeIntEntry("screen", "fontencoding", font0->getEncoding());
    reg->writeIntEntry("screen", "fontsetwidth", font0->getSetWidth());
    reg->writeIntEntry("screen", "fonthints",    font0->getHints());
}

// Now handlers for the things that get signalled from my worker thread

long FXTerminal::onIPC(FXObject *c, FXSelector s, void *ptr)
{
    char pipe_data[1];
#ifdef WIN32
    pipe_data[0] = event_code;
#else
    if (read(pipedes[PIPE_READ_PORT], pipe_data, 1) != 1)
    {   fprintf(stderr, "Fatal error attempting to read from pipe\n");
        application_object->exit(1);
        exit(1);
    }
#endif
    switch (pipe_data[0])
    {
default:
        fprintf(stderr, "Fatal error: unknown IPC code %d\n", pipe_data[0]);
        application_object->exit(1);
        exit(1);
case WORKER_EXITING:
        return requestWorkerExiting();
case FLUSH_BUFFER:
        return requestFlushBuffer();
case REQUEST_INPUT:
        return requestRequestInput();
case SET_PROMPT:
        return requestSetPrompt();
case REFRESH_TITLE:
        return requestRefreshTitle();
case SHOW_MATH:
        return requestShowMath();
#ifdef CSL
case SET_MENUS:
        return requestSetMenus();
case REFRESH_SWITCHES:
        return requestRefreshSwitches();
#endif
case MINIMIZE_MAIN:
        main_window->minimize();
        return 1;
case RESTORE_MAIN:
        main_window->restore();
        return 1;
    }
}

long FXTerminal::requestWorkerExiting()
{
    if (fwin_pause_at_end)
        FXMessageBox::information(this, MBOX_OK, "Pause at End",
            "Application is exiting");
#ifdef WIN32
    DWORD retval;
    switch (WaitForSingleObject(thread1, 10000))
    {
case WAIT_OBJECT_0:
        if (!GetExitCodeThread(thread1, &retval)) retval = 1;
        CloseHandle(thread1);
        break;
default:
        retval = 1;
    }
#else
    void *p;
    int retval;
    if (!pthread_join(thread1, &p)) retval = *(int *)p;
    else retval = 1 /* joining failed - default to return code of 1 */;
#endif
// I am now ready to stop. By calling FXApp::exit I should get FOX closed
// down tidily, with the registry written back. There is some slight
// uncertainty as to whether FXApp::exit does or should actually quit
// the whole application or just the FOX activity, so I will arrange that
// if it does return then I will stop yet more enthusiastically. And
// to keep compilers from moaning I still make this procedure look as if
// it can return 1 as its result!
    application_object->exit(retval);
    exit(retval);
    return 1;
}

// Since I want to keep things consistent I think I might like to document
// what I expect key-strokes to do:

//============================================================================
//                         KEYBOARD HANDLING
//
//
// Key-bindings that I hope to make work in both terminal and windowed mode,
// on both Unix/Linux, Microsoft Windows and the Macintosh.
// 
// Note that ALT can be achieved either by holding the ALT key at the
// same time as the listed key, or by pressing ESC before the key.
// 
// ALT takes priority over SHIFT, and Control takes priority over ALT so
// that a character is only treated as having one attribute. If it has none
// it just inserts itself.
// 
// Where I put a "-" in this table it means that I do not define the meaning
// of the keystroke. In the short term at least that will either cause the
// keystroke to be ignored, inserted, or treated the same way as the
// corresponding character without Control or ALT. In the longer term I may
// assign behaviours to some of those keys. I also want to reserve the
// possibility of making keys with both Control and ALT have yet different
// effects.
//
//Key   Control              ALT
//
// @    Set Mark             -                    (note this key is not
//                                                 always detected!)
// A    To beginning         [Package load menu]  (also Home key)
// B    Back char            Back word            (also left arrow key)
// C    ^C interrupt         Capitalise word
// D    Delete forward       Delete word          (also the Delete key)
//      Also ^D before any other input on a line sends EOF
// E    To end               [Edit menu]          (also End key)
// F    Forward char         Foward word          (also right arrow key)
// G    ^G interrupt/cancel input         -       <<also escape search mode>>
//
// H    Delete back          Del word back
// I    TAB                  [File menu]          (also TAB key)
// J    Newline              -
// K    Kill line            -
// L    Clear screen         Lowercase word
// M    Newline              -
// N    Next history         Search history next  (also down arrow key)
// O    Discard output       [Font menu]
//
// P    Previous history     Search history prev  (also up arrow key)
// Q    Resume output        -
// R    Redisplay            [Break menu]
// S    Pause output         [Switch menu]
// T    Transpose chars      -
// U    <undo?/escape srch>  Uppercase word
// V    Quoted insert        -
// W    Del Word back        Copy region
//
// X    eXtended command     Obey command
// Y    Yank (=Paste)        -
// Z    Stop execution       -
// [    =ESC: Meta prefix    -
// \    Quit                 -
// ]    -                    -
// _    -                    Copy previous word
// ^    Reinput              -
//
//
// Arrow etc keys...
//
// ->   forward char/word
// <-   backwards char/word
// ^    history prev/search history prev
// v    history next/search history next
// home start line/start buffer
// end  end line/end buffer
//
//
// The items shown as menus behave as follows:
//
// ALT-E C cut
//       O copy
//       P paste
//       R reinput
//       A select all
//       L clear
//       D redraw
//       H home
//       E end
// ALT-I R read
//       S save
//       L save selected text
//       P print
//       N print selected text
//       X exit
// ALT-M   &Module menu shortcut - load a REDUCE module
// ALT-O F select new font
//       R reset to default font
//       W reset font and window to default
// Alt-R C as ^C, interrupt current computation
//       D as ^O, discard pending output
//       G as ^G, interrupt & backtrace current computation
//       P as ^S, pause output
//       R as ^Q, resume output
//       X as ^X, stop current computation
// ALT-S   &Switch menu shortcut - flip a REDUCE switch
//
//
//============================================================================


long FXTerminal::onKeyPress(FXObject *c, FXSelector s, void *ptr)
{
    int ch;
    FXEvent *event = (FXEvent *)ptr;
    const char *history_string = "";
    if (!isEnabled()) return 0;
    switch (event->code)
    {
// Here are some keys that  just want to ignore..
case KEY_Shift_L:
case KEY_Shift_R:
case KEY_Control_L:
case KEY_Control_R:
case KEY_Caps_Lock:
case KEY_Shift_Lock:
case KEY_Meta_L:
case KEY_Meta_R:
case KEY_Alt_L:
case KEY_Alt_R:
case KEY_Super_L:
case KEY_Super_R:
case KEY_Hyper_L:
case KEY_Hyper_R:
case KEY_VoidSymbol:  // used when just ALT (say) is pressed and a
                      // key-repetition event is generated.
        return 1;
    }
// If a previous keystroke had been ESC then I act as if this one
// had ALT combined with it. I will cancel the pending ESC on various
// menu things as well as here. Note that this conversion copes with
// local editing combinations such as ALT-D, but ESC-I does not activate
// a menu the way that ALT-I would have.
    if (keyFlags & ESC_PENDING)
    {   event->state |= ALTMASK;
        keyFlags &= ~ESC_PENDING;
    }
// I will let the Search Pending code drop through in cases where the
// keystroke should be treated as a return to "ordinary" processing. Also
// note that I only expect to find myself in search mode in cases where the
// system is waiting for input.
    if (searchFlags != 0)
    {   int save, r, ls;
        switch (event->code)
        {
    case KEY_h:
            if (!(event->state & CONTROLMASK)) goto defaultlabelsearch;
            if (event->state & ALTMASK) goto defaultlabelsearch;
            // drop through to BackSpace
    case KEY_BackSpace:
// When I delete a character from a search string I will pop the active
// history line back to the first one found when the remaining string
// was searched for. If I delete back to nothing I will leave the input
// line blank.
            if (SEARCH_LENGTH == 0)
            {   getApp()->beep();
                searchFlags = 0;   // cancel searching before it started!
                killSelection(TRUE);
                return 1;
            }
            searchFlags--;
            if (SEARCH_LENGTH == 0)
            {   searchFlags = 0;   // delete the one char in the search string
                killSelection(TRUE);
                setInputText("", 0);
                return 1;
            }
            historyNumber = searchStack[SEARCH_LENGTH];
// The "trySearch" here really really ought to succeed since I have reverted
// to a history line where it succeeded before. I do it again here so I can
// find out where, on that line, the match was so I can establish it as a
// selection.
            startMatch = trySearch();
            history_string = input_history_get(historyNumber);
// ought not to return NULL here!
            ls = setInputText(history_string, strlen(history_string));
// To give a visual indication of what I have found I will select the match,
// which will leave it highlighted on the display. I must remember to kill
// my selection every time I exit search mode!
            killSelection(TRUE);
            setAnchorPos(ls+startMatch);
            extendSelection(ls+startMatch+SEARCH_LENGTH, SELECT_CHARS, TRUE);
            setCursorPos(ls+startMatch+SEARCH_LENGTH, TRUE);
            return 1;
    case KEY_p:
            if (!(event->state & ALTMASK)) goto defaultlabelsearch;
    case KEY_Up:
            searchFlags &= ~SEARCH_FORWARD;
            searchFlags |= SEARCH_BACKWARD;
            if (historyNumber <= historyFirst)
            {   getApp()->beep();  // already on last possible place
                return 1;
            }
            save = historyNumber;
            historyNumber--;
            r = trySearch();
            if (r == -1)
            {   historyNumber = save;
                getApp()->beep();
                return 1;
            }
            startMatch = r;
            history_string = input_history_get(historyNumber);
            ls = setInputText(history_string, strlen(history_string));
// To give a visual indication of what I have found I will select the match,
// which will leave it highlighted on the display. I must remember to kill
// my selection every time I exit search mode!
            killSelection(TRUE);
            setAnchorPos(ls+startMatch);
            extendSelection(ls+startMatch+SEARCH_LENGTH, SELECT_CHARS, TRUE);
            setCursorPos(ls+startMatch+SEARCH_LENGTH, TRUE);
            return 1;
    case KEY_n:
            if (!(event->state & ALTMASK)) goto defaultlabelsearch;
    case KEY_Down:
            searchFlags |= SEARCH_FORWARD;
            searchFlags &= ~SEARCH_BACKWARD;
            if (historyNumber >= historyLast)
            {   getApp()->beep();
                return 1;
            }
            save = historyNumber;
            historyNumber++;
            r = trySearch();
            if (r == -1)
            {   historyNumber = save;
                getApp()->beep();
                return 1;
            }
            startMatch = r;
            history_string = input_history_get(historyNumber);
            ls = setInputText(history_string, strlen(history_string));
// To give a visual indication of what I have found I will select the match,
// which will leave it highlighted on the display. I must remember to kill
// my selection every time I exit search mode!
            killSelection(TRUE);
            setAnchorPos(ls+startMatch);
            extendSelection(ls+startMatch+SEARCH_LENGTH, SELECT_CHARS, TRUE);
            setCursorPos(ls+startMatch+SEARCH_LENGTH, TRUE);
            return 1;
// I detect ^U here and cause it to exit search mode.
    case KEY_u:
            if (!(event->state & CONTROLMASK)) goto defaultlabelsearch;
            searchFlags = 0;
            killSelection(TRUE);
            return 1;
    case KEY_bracketleft:
            if (!(event->state & CONTROLMASK)) goto defaultlabelsearch;
            if (event->state & ALTMASK) goto defaultlabelsearch;
            // drop through to Escape
    case KEY_Escape:       // ctl-[
            keyFlags ^= ESC_PENDING;
            return 1;
    default:
    defaultlabelsearch:
// I suggest "^@" as a sensible character to type to exit from searching.
// Acting on it just "sets the mark" which is typically harmless.
            if ((event->code & ~0xff) != 0 ||
                event->text[1] != 0 ||
                event->state & (CONTROLMASK | ALTMASK))
            {   searchFlags = 0;
                killSelection(TRUE);
                break;
            }
// here I should have a single simple character
            ch = event->text[0];
// and I will filter out control characters... except tab!
            if ((ch & 0xff) < 0x20 && (ch & 0xff) != '\t')
            {   searchFlags = 0;
                killSelection(TRUE);
                break;
            }
// Here I have a further printable character to add to the search
// pattern. If ignore it if the search string has become ridiculously long
// to avoid a buffer overflow.
            if (SEARCH_LENGTH > 250)
            {   getApp()->beep();
                return 1;
            }
            searchString[SEARCH_LENGTH] = ch;
            searchStack[SEARCH_LENGTH] = historyNumber;
            searchFlags++;
            save = historyNumber;
            r = trySearch();
            if (r == -1)
            {   historyNumber = save;
                getApp()->beep();
                searchFlags--;
                return 1;
            }
            startMatch = r;
            history_string = input_history_get(historyNumber);
            ls = setInputText(history_string, strlen(history_string));
// To give a visual indication of what I have found I will select the match,
// which will leave it highlighted on the display. I must remember to kill
// my selection every time I exit search mode!
            killSelection(TRUE);
            setAnchorPos(ls+startMatch);
            extendSelection(ls+startMatch+SEARCH_LENGTH, SELECT_CHARS, TRUE);
            setCursorPos(ls+startMatch+SEARCH_LENGTH, TRUE);
            return 1;
        }
    }
// If the very first character I see is a "^D" it is to be taken as EOF
// rather than as "delete next character".
    if (event->code == KEY_d &&
        event->state & CONTROLMASK &&
        !(keyFlags & ANY_KEYS))
    {   setCursorPos(length);
// I force a Control-D character into the buffer and then pretend that
// a newline had also been typed.
        FXMathText::appendText("\004", 1);
        return onCmdInsertNewline(this, 0, NULL);
    }
// If the very first key I see is "^G" I will raise an exception
// for the user.
    if (event->code == KEY_g &&
        event->state & CONTROLMASK &&
        !(keyFlags & ANY_KEYS)) return onCmdBacktrace(this, 0, NULL);
    keyFlags |= ANY_KEYS;
    ch = 0x00;
    switch (event->code)
    {
case KEY_BackSpace:
        if (event->state & (CONTROLMASK|ALTMASK))
            return editDeleteBackwardWord();
        else return editDeleteBackward();
case KEY_End:
case KEY_KP_End:
// Hmmm - still should I extend a selection if an anchor is set?
// END should probably go to the end of the current line, with ALT-END
// going to the end of the last line.
        if (event->state & (ALTMASK|CONTROLMASK)) return onCmdEnd(c, s, ptr);
        else return editMoveLineEnd();
case KEY_Home:
case KEY_KP_Home:
// OME should probably go to the start of the current active line, with
// ALT-HOME being the thing that goes to top of the screen-buffer.
        if (event->state & (ALTMASK|CONTROLMASK)) return onCmdHome(c, s, ptr);
        else return editMoveLineStart();
case KEY_Left:
        if (event->state & (CONTROLMASK|ALTMASK)) return editPrevWord();
        else return editPrevChar();
case KEY_Right:
        if (event->state & (CONTROLMASK|ALTMASK)) return editNextWord();
        else return editNextChar();
case KEY_Up:
        if (event->state & CONTROLMASK || (options & TEXT_READONLY))
            return onCmdCursorUp(this, 0, NULL);
        else if (event->state & ALTMASK)
            return editSearchHistoryPrev();
        else return editHistoryPrev();
case KEY_Down:
// If you are not waiting for input then cursor up and down just move you up
// and down! If you are waiting for input then Control can be used to break
// you out from the input-line...
        if (event->state & CONTROLMASK || (options & TEXT_READONLY))
            return onCmdCursorDown(this, 0, NULL);
        else if (event->state & ALTMASK)
            return editSearchHistoryNext();
        else return editHistoryNext();
case KEY_Return:
case KEY_Linefeed:
// I always act as if newlines were typed at the very end of the input.
        setCursorPos(length);
        ch = '\n';
        break;
case KEY_Tab:
case KEY_KP_Tab:
        ch = '\t';
        break;
case KEY_at:
// As a default sort of behaviour if my chart of actions shows a key doing
// something interesting with CONTROL but does not specify what happens
// with ALT, I think I will tend to make ALT-x behave like ^x.
        if (event->state & (CONTROLMASK|ALTMASK)) return editSetMark();
        else goto defaultlabel;
case KEY_a:
        if (event->state & (CONTROLMASK|ALTMASK)) return editMoveLineStart();
        else goto defaultlabel;
case KEY_b:
        if (event->state & CONTROLMASK) return editPrevChar();
        else if (event->state & ALTMASK) return editPrevWord();
        else goto defaultlabel;
case KEY_c:
        if (event->state & CONTROLMASK) return editBreak();
        else if (event->state & ALTMASK) return editCapitalize();
        else goto defaultlabel;
case KEY_Delete:
        if (event->state & (CONTROLMASK | ALTMASK)) return editDeleteForwardWord();
        break;
case KEY_d:
// Here I may want to arrange that if the current input-buffer is empty
// then ^D causes and EOF to be returned. Well yes, I have arranged that
// elsewhere so I only get here if the user has typed some chars already.
        if (event->state & CONTROLMASK) return editDeleteForward();
        else if (event->state & ALTMASK) return editDeleteForwardWord();
        else goto defaultlabel;
case KEY_e:
        if (event->state & CONTROLMASK) return editMoveLineEnd();
// ALT-e enters the EDIT menu: this is handled by having the menu
// registered elsewhere.
        else goto defaultlabel;
case KEY_f:
        if (event->state & CONTROLMASK) return editNextChar();
        else if (event->state & ALTMASK) return editNextWord();
        else goto defaultlabel;
case KEY_g:
        if (event->state & (CONTROLMASK | ALTMASK)) return editBacktrace();
        else goto defaultlabel;
case KEY_h:
        if (event->state & CONTROLMASK) return editDeleteBackward();
        else if (event->state & ALTMASK) return editDeleteBackwardWord();
        else goto defaultlabel;
case KEY_i:
// ^I is a TAB
// ALT-i enters the FILE menu
        if (event->state & CONTROLMASK) ch = '\t';
        else goto defaultlabel;
case KEY_j:
        if (event->state & (CONTROLMASK | ALTMASK)) return editNewline();
        else goto defaultlabel;
case KEY_k:
        if (event->state & (CONTROLMASK | ALTMASK)) return editCutLine();
        else goto defaultlabel;
case KEY_l:
// ^L will be CLEAR SCREEN
        if (event->state & ALTMASK) return editLowercase();
        else goto defaultlabel;
case KEY_m:
        if (event->state & CONTROLMASK) return editNewline();
// ALT-m enters the MODULE menu
        else goto defaultlabel;
case KEY_n:
        if (options & TEXT_READONLY)
        {   if (event->state & CONTROLMASK)
                return onCmdCursorDown(this, 0, NULL);
        }
        else
        {   if (event->state & CONTROLMASK) return editHistoryNext();
            else if (event->state & ALTMASK) return editSearchHistoryNext();
        }
        goto defaultlabel;
case KEY_o:
// ^O will be purge output.
//    I hope that by making ^O, ^S and ^Q menu shortcuts they will get
//    acted upon whether I am waiting for input or not.
// ALT-O enters the FONT menu
        goto defaultlabel;
case KEY_p:
        if (options & TEXT_READONLY)
        {   if (event->state & CONTROLMASK)
                return onCmdCursorUp(this, 0, NULL);
        }
        else
        {   if (event->state & CONTROLMASK) return editHistoryPrev();
            else if (event->state & ALTMASK) return editSearchHistoryPrev();
        }
        goto defaultlabel;
case KEY_q:
// ^Q will be RESUME OUTPUT
        if (event->state & ALTMASK) return 1; // Ignore ALT-Q
        goto defaultlabel;
case KEY_r:
        if (event->state & CONTROLMASK) return editRedisplay();
// ALT-r will be the B&reak menu
        goto defaultlabel;
case KEY_s:
// ^S should pause output
// ALT-s enters the SWITCH menu
        goto defaultlabel;
case KEY_t:
        if (event->state & (CONTROLMASK | ALTMASK)) return editTranspose();
        else goto defaultlabel;
case KEY_u:
        if (event->state & CONTROLMASK) return editUndo();
        else if (event->state & ALTMASK) return editUppercase();
        else goto defaultlabel;
case KEY_v:
// ^V will be PASTE and is handled as a shortcut
        goto defaultlabel;
case KEY_w:
// ^W behaviour is just like ALT-H
        if (event->state & CONTROLMASK) return editDeleteBackwardWord();
        else if (event->state & ALTMASK) return editCopyRegion();
        else goto defaultlabel;
case KEY_x:
// Just what these have to do is a mystery to me at present!
// Well that is an overstatement - what I mean is that I am not yet
// implementing anything!
        if (event->state & CONTROLMASK) return editExtendedCommand();
        else if (event->state & ALTMASK) return editObeyCommand();
        else goto defaultlabel;
case KEY_y:
// ^Y is short for YANK, otherwise known as PASTE
        if (event->state & CONTROLMASK) return editPaste();
        else if (event->state & ALTMASK) return editRotateClipboard();
        else goto defaultlabel;
case KEY_z:
// ^Z is short for SUSPEND
        goto defaultlabel;
case KEY_bracketleft:
        if (event->state & CONTROLMASK) return editEscape();
        else goto defaultlabel;
case KEY_Escape:       // ctl-[
// ESC must have the effect of simulating the ALT property for the following
// character.
        return editEscape();
case KEY_backslash:
// ^\ exits the application
        goto defaultlabel;
case KEY_bracketright:
        goto defaultlabel;
case KEY_asciicircum:
        if (event->state & CONTROLMASK) return editReinput();
        goto defaultlabel;
case KEY_underscore:
        if (event->state & ALTMASK) return editCopyPreviousWord();
        goto defaultlabel;

default:
defaultlabel:
        if ((event->code & ~0xff) != 0 ||
            event->text[1] != 0)
            return FXMathText::onKeyPress(c, s, ptr);
// here I should have a single simple character
        ch = event->text[0];
// and I will filter out control characters...
        if ((ch & 0xff) < 0x20) return FXMathText::onKeyPress(c, s, ptr);
        break;
    }
// Now I am left with printable characters plus TAB and NEWLINE. If the
// terminal is waiting for input or if CTRL or ALT was associated with
// the key I delegate.
// @@@ I should really try to check so that when I insert a ")", "]" or
// "}" I look for the corresponding opening bracket and flash it. FXMathText
// has some support for that!
    if (isEditable() ||
        (event->state & (CONTROLMASK|ALTMASK)))
    {   long rr = FXMathText::onKeyPress(c, s, ptr);
// I want the input line to be in a special colour.
        changeStyle(promptEnd, length-promptEnd, STYLE_INPUT);
        return rr;
    }
// I have now delegated everything except simple printable characters
// plus tab, backspace and newline without CTRL or ALT.
// I will interpret backspace as deleting the most recent character
// (if there is one, and not if we get back to a newline). Otherwise
// I just fill a (circular) buffer.
    flags&=~FLAG_TIP;
    if (ch == '\b')  // delete previous character in buffer if there is one
    {   int n = type_in;
        if (--n < 0) n = TYPEAHEAD_SIZE-1;
// I can not delete a character if there is not one there. I will not delete
// it if the previous character was a newline. In such cases I just beep.
        if (type_in == type_out ||
            ahead_buffer[n] == '\n')
        {   getApp()->beep();
            return 1;
        }
        type_in = n;
    }
    else type_ahead(ch);
    return 1;
}

//
// Here I have the procedures that implement each editing action. In
// quite a lot of cases they simply delegate to actions already supported
// by FXMathText, but I have a method for each here because I think it is
// slightly clearer to have all the entrypoints visible in one place.
//

//
// Something I have NOT fitted quite carefully enough to all this is
// arrangements that I ignore things if not waiting for input and force
// the cursor to the final line in relevant cases.
//

// ^@   set mark, ie start a selection

int FXTerminal::editSetMark()
{
// This is in fact just an operation that FXMathText already supports.
    return onCmdMark(this, 0, NULL);
}

// ^A   move to start of current line (after any prompt text!)

int FXTerminal::editMoveLineStart()
{
    int n = lineStart(cursorpos);
    makePositionVisible(n);
    while (n < length && (getStyle(n) & STYLE_PROMPT)) n++;
    makePositionVisible(n);
    setCursorPos(n);
// If the mark is set maybe I should extend the selection...
    return 1;
}

// ^B  move back a character

int FXTerminal::editPrevChar()
{
// If the mark is set maybe I should extend the selection...?
// If I am accepting input I will not let the user move backwards into the
// prompt string.
    if ((options && TEXT_READONLY) == 0 &&
        cursorpos == promptEnd)
    {   getApp()->beep();
        return 1;
    }
    return onCmdCursorLeft(this, 0, NULL);
}

// ALT-B move back a word

int FXTerminal::editPrevWord()
{
// If the mark is set maybe I should extend the selection...?
// If I am accepting input I prevent the user from moving back past where the
// prompt string ends. I beep if I make no move at all.
    int w = cursorpos;
    if ((options & TEXT_READONLY) == 0 && w == promptEnd)
    {   getApp()->beep();
        return 1;
    }
    onCmdCursorWordLeft(this, 0, NULL);
    if ((options && TEXT_READONLY) == 0 &&
        w > promptEnd &&
        cursorpos < promptEnd) setCursorPos(promptEnd);
    return 1;
}

// ^C  abandon input, returning an exception to user

int FXTerminal::editBreak()
{
// Note that ^C generates a break action whether I am waiting for input or not.
    return onCmdBreak(this, 0, NULL);;
}

// ALT-c  capitalize a word

int FXTerminal::editCapitalize()
{
// I arbitrarily limit the length of a word that I casefix to 63
// chars.
    if (!isEditable())
    {   getApp()->beep();
        return 1;
    }
    char wordbuffer[64];
    int cp = cursorpos;
    int ws = wordStart(cp);
    int we = wordEnd(cp);
    if (ws < promptEnd) ws = promptEnd;
    if (we > ws + 63) we = ws + 63;
    extractText(wordbuffer, ws, we-ws);
    int i;
    wordbuffer[0] = toupper(wordbuffer[0]);
    for (i=1; i<we-ws; i++)
        wordbuffer[i] = tolower(wordbuffer[i]);
    replaceText(ws, we-ws, wordbuffer, we-ws);
    setCursorPos(cp);
    makePositionVisible(cp);
    return 1;
}

// ^D  delete character under cursor (fowards)

int FXTerminal::editDeleteForward()
{
    if (!isEditable())     // side effect is to move to last line if necessary
    {   getApp()->beep();
        return 1;
    }
    return onCmdDelete(this, 0, NULL);
}

// Should this do special things (a) if there is a selection or (b)
// if there is a selection and the cursor is within it?

// ALT-d  delete word forwards

int FXTerminal::editDeleteForwardWord()
{
    if (!isEditable())     // side effect is to move to last line if necessary
    {   getApp()->beep();
        return 1;
    }
    return onCmdDeleteWord(this, 0, NULL);
}

// ^E  move to end of current line

int FXTerminal::editMoveLineEnd()
{
// extend selection?
    return onCmdCursorEnd(this, 0, NULL);
}

// ^F  forward one character

int FXTerminal::editNextChar()
{
// If the mark is set maybe I should extend the selection...
    return onCmdCursorRight(this, 0, NULL);
}

// ALT-F  forward one word

int FXTerminal::editNextWord()
{
// If the mark is set maybe I should extend the selection...
    return onCmdCursorWordRight(this, 0, NULL);
}

// ^G   If it was the very very first character typed or if I am not
//      waiting for input, ^G raises an interrupt. If I am waiting for
//      input and have not typed anything much then it clears the current
//      input line leaving me back with a fresh start. I will make that so
//      fresh that ^G^G guarantees an interrupt!

int FXTerminal::editBacktrace()
{
    if (!isEditable()) return onCmdBacktrace(this, 0, NULL);
    killSelection();
    setInputText("", 0);
    historyNumber = historyLast + 1;
    keyFlags &= ~ANY_KEYS;
    return 1;
}

// ^H  (backspace) delete char before cursor if that is reasonable.

int FXTerminal::editDeleteBackward()
{
    switch (isEditableForBackspace())
    {
default:                // within the area for active editing.
        return FXMathText::onCmdBackspace(this, 0, NULL);
case -1:                // current input line is empty.
case 0:                 // input is not active
        getApp()->beep();
        return 1;
    }
}

// ALT-h  delete previous word

int FXTerminal::editDeleteBackwardWord()
{
    int pos;
    switch (isEditableForBackspace())
    {
default:                // within the area for active editing.
// I want to be confident that whatever prompt string has been set the
// following will never delete part of the prompt...
        pos = leftWord(cursorpos);
        if (pos < promptEnd) pos = promptEnd;
        removeText(pos, cursorpos-pos, TRUE);
        setCursorPos(cursorpos, TRUE);
        makePositionVisible(cursorpos);
        flags |= FLAG_CHANGED;
        modified = TRUE;
        return 1;
case -1:                // current input line is empty.
case 0:                 // input is not active
        getApp()->beep();
        return 1;
    }
}

// ^I was just a TAB and has been handled elsewhere

// ^J (linefeed) accepts the current line of text

int FXTerminal::editNewline()
{
    setCursorPos(length);
    return onCmdInsertNewline(this, 0, NULL);
}

// ^K  kill current line
// Note that ^G and ^U are somewhat related, and that I do not
// do anything by way of putting cut text into a kill-buffer, or allowing the
// user to make selections using the keyboard...

int FXTerminal::editCutLine()
{
    killSelection();
    setInputText("", 0);
    return 1;
}

// ^L    clear screen (handled as menu shortcut)

// ALT-L convert to lower case

int FXTerminal::editLowercase()
{
// I arbitrarily limit the length of a word that I casefix to 63
// chars.
    if (!isEditable())
    {   getApp()->beep();
        return 1;
    }
    char wordbuffer[64];
    int cp = cursorpos;
    int ws = wordStart(cp);
    if (ws < promptEnd) ws = promptEnd;
    int we = wordEnd(cp);
    if (we > ws + 63) we = ws + 63;
    extractText(wordbuffer, ws, we-ws);
    int i;
    for (i=0; i<we-ws; i++)
        wordbuffer[i] = tolower(wordbuffer[i]);
    replaceText(ws, we-ws, wordbuffer, we-ws);
    setCursorPos(cp);
    makePositionVisible(cp);
    return 1;
}

// ^M  as ENTER, ^J

// ALT-M  a &Module menu

// ^N  history next if we are at present on the bottom line
//     otherwise move down a line
// (also down-arrow key)

// To replace the input line I can can use this... It returns the
// index of the first character of the inserted line.

int FXTerminal::setInputText(const FXchar *text, int n)
{
    int n2 = length;
    int n1 = lineStart(n2);
    while (n1 < n2 && (getStyle(n1) & STYLE_PROMPT)) n1++;
    replaceText(n1, n2-n1, text, n);
    changeStyle(n1, length-n1, STYLE_INPUT);  // paint it the right colour
    setCursorPos(length);
    makePositionVisible(length);
    return n1;
}


// The history routines here are never invoked unless we are awaiting input

int FXTerminal::editHistoryNext()
{
    const char *history_string;
    if (historyLast == -1) // no history lines at all to retrieve!
    {   getApp()->beep();
        return 1;
    }
    if (historyNumber < historyLast) historyNumber++;
    if ((history_string = input_history_get(historyNumber)) == NULL)
    {   getApp()->beep();
        return 1;
    }
    setInputText(history_string, strlen(history_string));
    return 1;
}

// Commentary on the search mechanism:
//   If not at present engaged in a search the search key
//   enters search mode with an empty search string and a given
//   direction, and the empty string will match against the current
//   (usually empty) input line so nothing much visible will happen.
//
//   A further use of the search key will move one line in the given
//   direction and search again until the pattern matches. If the alternate
//   direction search key is pressed the line is moved one line in the
//   new direction before scanning that way.
//
//   ENTER or an arrow key, or DEL or ESC (in general most things that
//   and not printing characters and not otherwise listed here) exits
//   search mode with the new current line.
//
//   BACKSPACE (^H) removes a character from the search pattern. If there
//   that leaves none it exits search mode. It pops back to the line you
//   had before the character it removed was inserted.
//
//   typical printing characters add that character to the pattern. If the
//   pattern is not a valid Regular Expression at the time concerned it is
//   treated as if completed in the most generous manner possible? Or maybe
//   the match fails so you get a beep and no movement?
//   [Gosh what do I mean by that? Do I *REALLY* want regexp matches here?]
//   Searching continues in the most recently selected direction. If no match
//   is found the line does not move and the system beeps.
//

// ALT-n  forward search

int FXTerminal::editSearchHistoryNext()
{
    if (historyLast == -1) // no history to search
    {   getApp()->beep();
        return 1;
    }
// If I am not in a search at present then set the flag for a search
// with an empty search string and a mark that the direction is forwards.
// Well if I not only am not in a search but I had not previously scrolled
// back in the history so I have nowhere to search then I might as well
// beep and give up.
    if (historyNumber > historyLast)
    {   getApp()->beep();
        return 1;
    }
    searchFlags = SEARCH_FORWARD;
    return 1;
}

int FXTerminal::trySearch()
{
    int r = -1;
    const char *history_string = input_history_get(historyNumber);
    if (history_string == NULL) return -1;
    while ((r = matchString(searchString, SEARCH_LENGTH, history_string)) < 0)
    {   if (searchFlags & SEARCH_FORWARD)
        {   if (historyNumber == historyLast) return -1;
            historyNumber++;
        }
        else
        {   if (historyNumber == historyFirst) return -1;
            historyNumber--;
        }
        history_string = input_history_get(historyNumber);
        if (history_string == NULL) return -1;
    }
    return r;
}

int FXTerminal::matchString(const char *pat, int n, const char *text)
{
// This is a crude and not especially efficient pattern match. I think
// it should be good enough for use here! I make it return the offset where
// a match first occurred (if one does) in case that will be useful to me
// later. I could put the cursor there, perhaps?
    int offset;
    for (offset=0;*(text+offset)!=0;offset++)
    {   const char *p = pat, *q = text+offset;
        int i;
        for (i=0; i<n; i++)
        {   if (p[i] != q[i]) break;
        }
        if (i == n) return offset;
    }
    return -1;
}


// ^O  abandon pending output. Menu shortcut

// ^P  history previous if we are on bottom line
//     [else cursor up????]
// (also uparrow key)

int FXTerminal::editHistoryPrev()
{
    const char *history_string;
    if (historyLast == -1) // no previous lines to retrieve
    {   getApp()->beep();
        return 1;
    }
// If I have not moved the history pointer at all yet move it into the
// range of valid history entries.
    if (historyNumber > historyFirst) historyNumber--;
    history_string = input_history_get(historyNumber);
    if (history_string == NULL)
    {   getApp()->beep();
        return 1;
    }
    setInputText(history_string, strlen(history_string));
    return 1;
}

// ALT-P  reverse search

int FXTerminal::editSearchHistoryPrev()
{
    if (historyLast == -1) // no history to search
    {   getApp()->beep();
        return 1;
    }
    if (historyNumber == historyLast + 1) historyNumber--;
    searchFlags = SEARCH_BACKWARD;
    return 1;
}

// ^Q  unpause output (see ^Z, ^S) treated as menu shortcut


// ^R  Redisplay

int FXTerminal::editRedisplay()
{
    return onCmdRedraw(this, 0, NULL);
}

// ^S as pause output is handled as a shortcut so that it can be
// accepted whether or not I am awaiting input.

// ^T  transpose

int FXTerminal::editTranspose()
{
    if (!isEditable())
    {   getApp()->beep();
        return 1;
    }
    char buff[2];
    int cp = cursorpos;
    if (cp > length-2)
    {   getApp()->beep();
        return 1;
    }
    extractText(buff, cp, 2);
    int ch;
    ch = buff[0];
    buff[0] = buff[1];
    buff[1] = ch;
    replaceText(cp, 2, buff, 2);
    setCursorPos(cp);
    makePositionVisible(cp);
    return 1;
}

// ^U  reserved for UNDO, and also exits search mode.

int FXTerminal::editUndo()
{
// @@@@@
    return 1;
}

// ALT-U convert to upper case

int FXTerminal::editUppercase()
{
// I arbitrarily limit the length of a word that I casefix to 63
// chars.
    if (!isEditable())
    {   getApp()->beep();
        return 1;
    }
    char wordbuffer[64];
    int cp = cursorpos;
    int ws = wordStart(cp);
    if (ws < promptEnd) ws = promptEnd;
    int we = wordEnd(cp);
    if (we > ws + 63) we = ws + 63;
    extractText(wordbuffer, ws, we-ws);
    int i;
    for (i=0; i<we-ws; i++)
        wordbuffer[i] = toupper(wordbuffer[i]);
    replaceText(ws, we-ws, wordbuffer, we-ws);
    setCursorPos(cp);
    makePositionVisible(cp);
    return 1;
}

// ^V  shortcut for PASTE

// ^W  delete previous word just as ALT-H

// ALT-W  Copy region

int FXTerminal::editCopyRegion()
{
// @@@@@
    return 1;
}

// ^X  extended command

int FXTerminal::editExtendedCommand()
{
// @@@@@
    return 1;
}

// ALT-X  obey command

int FXTerminal::editObeyCommand()
{
// @@@@@
    return 1;
}


// ^Y  paste

int FXTerminal::editPaste()
{
    if (!isEditable())
    {   getApp()->beep();
        return 1;
    }
    return onCmdPasteSel(this, 0, NULL);
}

// ALT-y rotate killbuffer/clipboard

int FXTerminal::editRotateClipboard()
{
// @@@@@
    return 1;
}

// ^Z is a keyboard shortcut to pause execution

// ALT-[, ESCAPE

int FXTerminal::editEscape()
{
    keyFlags ^= ESC_PENDING; // so that ESC ESC cancels the effect.
    return 1;
}

// ^\   exit the application (menu shortcut)
// ^]   unused

// ^^   re-input (= COPY/PASTE)

int FXTerminal::editReinput()
{
    if (!isEditable())
    {   getApp()->beep();
        return 1;
    }
    return onCmdReinput(this, 0, NULL);
}

// ALT-_  copy previous word

int FXTerminal::editCopyPreviousWord()
{
// @@@@@
    return 1;
}


// Return true if editable, which here is used as
// a mark of whether the user has requested input.

FXbool FXTerminal::isEditable()
{
    if ((options&TEXT_READONLY)!=0) return FALSE;
// If we are asking if the FXTerminal is editable that is because we
// are trying to insert something. Here it is editable, so the user is
// waiting for input. I will make the very query force the final line
// to be visible and ensure that the cursor is within it. This should prevent
// anybody from every clobbering anything other than the active input line.
// Note that key-presses while the program is NOT ready to accept them
// will not cause cursor movement until the program requests input.
    int n = lineStart(length);
    makePositionVisible(n);
    while (n < length && (getStyle(n) & STYLE_PROMPT)) n++;
    makePositionVisible(length);
    if (cursorpos < n) setCursorPos(length);
// Furthermore if I am about to change thing I will ensure that any
// selection lies within the active line.
    if (selstartpos < n) selstartpos = n;
    if (selendpos < selstartpos) selendpos = selstartpos;
    return TRUE;
}

// Return true if editable, to be used when the next operation would
// be a BACKSPACE (delete-previous). It must thus shift the cursor to
// avoid deleting the final character of the prompt string.

int FXTerminal::isEditableForBackspace()
{
    if ((options&TEXT_READONLY)!=0) return 0;  // must buffer the action
    int n = lineStart(length);
    makePositionVisible(n);
    while (n < length && (getStyle(n) & STYLE_PROMPT)) n++;
    makePositionVisible(length);
// The next line has "<=" where the previous function has just "<"
    if (cursorpos <= n) setCursorPos(length);
// Furthermore if I am about to change thing I will ensure that any
// selection lies within the active line.
    if (selstartpos < n) selstartpos = n;
    if (selendpos < selstartpos) selendpos = selstartpos;
    if (n == length) return -1; // nothing that I am allowed to delete
    return 1;
}

// #define INPUT_BUFFER_LENGTH 256   (in header file)
// int inputBufferLen = 0;
// int inputBufferP = 0;
// char inputBuffer[INPUT_BUFFER_LENGTH];

int recently_flushed = 0;

long FXTerminal::onCmdInsertNewline(FXObject *c, FXSelector s, void *ptr)
{
// Note that the 3 args to this procedure are never used!
    FXint p = length;
// I find the first "real" character of the input line by scanning back
// to (a) the start of the buffer (b) the end of a previous line or (c) the
// end of a prompt string.
    while (p>0 && getChar(p-1)!='\n' && (getStyle(p-1)&STYLE_PROMPT)==0) p--;
    FXint n = length-p;
    if (n > (int)sizeof(inputBuffer)-5) n = sizeof(inputBuffer)-5;
    extractText(inputBuffer, p, n);
// I enter the line that has just been collected into the history
// record.
    inputBuffer[n] = 0;
    input_history_add(inputBuffer);
// Adding an entry could cause an old one to be discarded. So I now ensure
// that I know what the first and last recorded numbers are.
    historyLast = input_history_next - 1;
    historyFirst = input_history_next - INPUT_HISTORY_SIZE;
    if (historyFirst < 0) historyFirst = 0;
    historyNumber = historyLast + 1; // so that ALT-P moves to first entry
// Now I add a newline to the text, since the user will expect to see that.
    inputBuffer[n] = '\n';
    inputBuffer[n+1] = 0;
    inputBufferLen = n+1;
    inputBufferP = 0;
// Stick a newline into the text buffer, and make the screen non-updatable.
    FXMathText::onCmdInsertNewline(c, s, ptr);
    setEditable(FALSE);
    recently_flushed = 0;
// stuff user typed is now in buffer... I should never have got here unless
// the user thread was waiting, so here I unlock it, to tell it that
// the input buffer is ready.
    if (sync_even)
    {   sync_even = 0;
        UnlockMutex(mutex3);
        LockMutex(mutex2);
        UnlockMutex(mutex4);
    }
    else
    {   sync_even = 1;
        UnlockMutex(mutex1);
        LockMutex(mutex4);
        UnlockMutex(mutex2);
    }
    return 1;
}

long FXTerminal::requestFlushBuffer()
{
    recently_flushed = 0;
// here the worker thread is locked waiting for mutex2, so I can afford to
// adjust fwin_in and fwin_out.
    if (sync_even)
    {   LockMutex(mutex1);
        if (fwin_in != fwin_out && (pauseFlags & PAUSE_DISCARD) == 0)
        {   if (fwin_in > fwin_out)
                FXMathText::appendText(&fwin_buffer[fwin_out], fwin_in-fwin_out);
            else
            {   FXMathText::appendText(&fwin_buffer[fwin_out], FWIN_BUFFER_SIZE-fwin_out);
                FXMathText::appendText(&fwin_buffer[0], fwin_in);
            }
            makePositionVisible(rowStart(length));
        }
// After this call fwin_in and fwin_out are always both zero.
        fwin_out = fwin_in = 0;
        sync_even = 0;
        UnlockMutex(mutex3);
        LockMutex(mutex2);
        UnlockMutex(mutex4);
    }
    else
    {   LockMutex(mutex3);
        if (fwin_in != fwin_out && (pauseFlags & PAUSE_DISCARD) == 0)
        {   if (fwin_in > fwin_out)
                FXMathText::appendText(&fwin_buffer[fwin_out], fwin_in-fwin_out);
            else
            {   FXMathText::appendText(&fwin_buffer[fwin_out], FWIN_BUFFER_SIZE-fwin_out);
                FXMathText::appendText(&fwin_buffer[0], fwin_in);
            }
            makePositionVisible(rowStart(length));
        }
        fwin_out = fwin_in = 0;
        sync_even = 1;
        UnlockMutex(mutex1);
        LockMutex(mutex4);
        UnlockMutex(mutex2);
    }
    return 1;
}

static int staticCharForShowMath()
{
    return text->charForShowMath();
}

int FXTerminal::charForShowMath()
{
    if (charPointer >= length) return 0;
    int c = getChar(charPointer);
    if (c == '\n') return 0;
    charPointer++;
    return c;
}

void FXTerminal::insertMathsLines()
{
    const char *p = fwin_maths;
    int start = length;
    int linecount = 0;
    while (*p != 0)
    {   while (*p!=0 && *p!='\n') p++;
        if (*p=='\n') p++;
// 0x02  is a lead-in to introduce the maths data, and it exists because
//       at a higher level I want to map one line of maths onto several
//       rows. I will end up (later on) with one 0x02 for each row to be used.
//       HOWEVER for reasons that convince at least me I will start by
//       inserting 0x21 where (0x20+n) will be used to indicate a line of
//       maths that needs n rows...
// 0x24  is a scale marker. I use 0x20 to 0x24 for the 5 different scales at
//       which I can render mathematics.
//       In fact I will use the buttom 3 bits for a scale, and the
//       remaining 5 bits to help me position multi-line formulae neatly.
//       0x28 to 0xf8 can give an indent hint value in that case.
// 0,0,0 is a 3-byte gap that will be used to hold a handle to the box-
//       structure representing the given line of the mathematical formula.
//       The handle will use 7-bits per byte so I have 21-bits here.
// Note that when I parse a bit of TeX I may run out of memory in the
// area reserved for box structures, and in that case I will discard some
// old box. When I do so I want to be able to identify the reference to it
// so I can mark it as stale. That is achieved by having the maths display
// parsing code hold a reference back into my text buffer so it can clobber
// the reference. For this to work it is VITAL that the text buffer should not
// change under the feet of the box-management package. This is an ugly
// constraint and probably shows that the two chunks of code need a tighter
// interface...
        FXMathText::appendStyledText("\x21\x84\x00\x00\x00", 5, STYLE_MATH);
        FXMathText::appendStyledText(fwin_maths, p-fwin_maths, STYLE_MATH);
        linecount++;
        fwin_maths = p;
    }
    int scale = 4;
    int p1 = start;
    while (p1<length)
    {   charPointer = p1+5;
// First parse the line of stuff to get a box-structure. The parsed box gets
// a reference back to position p1+2 in the text buffer since that is where
// the reference will be put.
        Box *b = parseTeX(staticCharForShowMath, p1+2);
// Style 0 in makeTextBox gives Roman typeface at normal size.
// Note that the blank in this message will be displayed as a sort of "`".
// There is no blank in the Roman font that I use here!!!!!
        if (b == NULL) b = makeTextBox("malformed expression", 20, 0);
// Remember where the box is. Note that if it gets discarded as I parse
// another box later on this reference will be replaced with a zero.
        text->recordBoxAddress(p1+2, b);
// Measure it at the current scale
        setMathsFontScale(scale);
        measureBox(b);
// If it is too wide then I will try to scale it down until it looks as if it
// will fit. When I do that I will measure all subsequent lines at the new
// reduced scale. But previously-measured boxes may need revision in a later
// pass over the data.
        while (b->text.width > text->getDefaultWidth() && scale>0)
        {   setMathsFontScale(--scale);
            measureBox(b);
        }
// Move on to the next line. Note that I arrange that any special info I
// put in the buffer will NEVER include a newline character!
        while (p1<length && getChar(p1)!='\n') p1++;
        if (p1 < length) p1++;
    }
// Now I take a second pass, and in this second pass I will sort out just how
// many rows each line will need, and insert markers to record this. I can
// also find the greatest width present in a multi-line display.
    int maxWidth = 0;
    p1 = start;
    while (p1<length)
    {
// Recover the box so I can re-measure it.
        Box *b = getBoxAddress(p1+2);
// If it has been discarded then the recovered pointer will be NULL
// so I re-parse the LaTeX.
        if (b == NULL)
        {   charPointer = p1+5;
// Again I should tell the box that its "owner" is the place in the
// text buffer where the pointer to it lives. (p1+2) here.
            b = parseTeX(staticCharForShowMath, p1+2);
            if (b == NULL) b = makeTextBox("malformed expression", 20, 0);
        }
// Measure it (again) at the current scale. In simple cases the effect here
// is that the measureBox() call gets done twice in an unnecessary way. I
// will let that happen and count simplicity in this code more important that
// efficiency!
        setMathsFontScale(scale);
        measureBox(b);
// By now the line is expected to fit (horizontally). I arranged that on
// my first pass. However I will want to do a bit more magic on multi-line
// formulae.
// Record the scale that is to be used.
        replaceStyledText(p1+1, 1, "\x20\x21\x22\x23\x24"+scale,
            1, STYLE_MATH);
        if (b->text.width > maxWidth) maxWidth = b->text.width;
        int h = b->text.height + b->text.depth;  // height
        FXint hh=font->getFontHeight();         // row height
// Work out how many rows are needed to let this line of maths fit in. I will
// insist that I have at least 0.33 or the row height spare (I will distribute
// that evenly above and below the formula in the display).
        int nrows = (h+hh+hh/3)/hh;
// However as a SPECIAL CASE if the data forms part of a multi-line display
// and this line is just a single item consisting of a string of digits
// then I will force it to use up just one row. This is to try to make the
// display of very big numbers look more sensible.  The macro BoxText is
// defined in FXShowMath.cpp and is mostly private to there...
#define BoxText 0
        if (linecount!=1 && b->top.sub->text.type == BoxText)
        {   char *ss = b->top.sub->text.text;
            int nn = b->top.sub->text.n;
            while (--nn >= 0) if (!isdigit(ss[nn])) break;
            if (nn < 0) nrows = 1;
        }
// Right now I hold a row-count in a byte such that the largest count that
// can be stored is 223. I guess that a tall array could use more than this!
// But for a first stab at this code I will not treat that case too seriously 
// and I will arbitrarily limit row counts. I guess it would be easy to
// use 2 bytes here and end up with a limit at around 50000 that should deal
// with all even half sane cases. But that is for later on.
        if (nrows > 0xdf) nrows = 0xdf;
// Now record how many rows are needed. Note that I do this without
// disturbing the layout of the text buffer.
        char heightString[1];
        heightString[0] = 0x20 + nrows;
        replaceStyledText(p1, 1, heightString, 1, STYLE_MATH);
        while (p1<length && getChar(p1)!='\n') p1++;
        if (p1 < length) p1++;
    }
// A third pass turns the lead-in bytes that are at present in the form
// 0x20+rowCount into a sequence of 0x02 chars. The reason I need to
// encode the number of rows used by a formula in unary this way is that
// to fit in with the rest of FXMathText I need to view the display as
// composed of rows, and I need locations within the buffer that can
// stand for the start of each row.
// When I insert the extra characters here the result will be that
// back-pointers from boxes into the text buffer will become incorrect, so
// I need to correct them all. I do not do ANY operations that could
// involve allocating new boxes during this phase and so I will never follow
// a back-pointer while it is broken...
    int spare = (text->getDefaultWidth() - maxWidth)/mathWidth;
    if (spare < 0) spare = 0; // should never happen!
    spare = 8*spare + 0x28;
    if (spare > 0xf8) spare = 0xf8;
    if (linecount == 1) spare = 0x20;
// The above has set up spare to be a messy byte that is there to help with
// multi-line formulae. The bottom 3 bits are a scale to render at. The top
// 5 bits show 0x20 for a 1-line formula (which I will centre), or
// a value bigger than that for any multi-line formulae, and the value then
// gives info about how much spare space there should be on the longest
// line in the entire formula.
    char spareBytes[2];
    spareBytes[0] = 0x02;
    spareBytes[1] = spare;
    p1 = start;
    while (p1<length)
    {   int heightCode = (getChar(p1) & 0xff) - 0x20;
        spareBytes[1] = spare + (getChar(p1+1) & 0x07);
        replaceStyledText(p1, 2, spareBytes, 2, STYLE_MATH);
        while (heightCode > 1)
        {   insertStyledText(p1, "\x02", 1, STYLE_MATH);
            heightCode--;
            p1++;
        }
        Box *b = getBoxAddress(p1+2);
// If it has been discarded then the recovered pointer will be NULL
// and so there will be no need to reset a back pointer.
        if (b != NULL) updateOwner(b, p1+2);
        while (p1<length && getChar(p1)!='\n') p1++;
        if (p1 < length) p1++;
    }
// Now I think everything is in a consistent state ready for display!
}

Box *FXTerminal::getBoxAddress(int p) const
{
    int c1 = getChar(p),
        c2 = getChar(p+1),
        c3 = getChar(p+2);
    if (c1 == 0) return NULL;
    int n = (c1&0x7f) + ((c2&0x7f) << 7) + ((c3&0x7f) << 14);
    Box *b=(Box *)poolPointerFromHandle(n);
    return b;
}

void FXTerminal::recordBoxAddress(int p, Box *b)
{
    char s[3];
    int c1=0, c2=0, c3=0;
    if (b != NULL)
    {   int n = handleFromPoolPointer(b);
        c1 = (n & 0x7f) + 0x80,
        c2 = ((n>>7) & 0x7f) + 0x80;
        c3 = ((n>>14) & 0x7f) + 0x80;
    }
    s[0] = c1;
    s[1] = c2;
    s[2] = c3;
    replaceStyledText(p, 3, s, 3, STYLE_MATH);
}

// This curious function is a call-back from FXShowMath and is invoked
// when a box-structure gets destroyed (they get destroyed on a cyclic basis
// when memory starts to get full). It zeros out the record here of where the
// box structure is, and as a result any future attempt to re-pain that
// bit ofthe display will provoke a re-parse and thus a re-creation of
// the data (which will presumably displace some other boxes...). Also the
// call-back wants to be a simple C function but to update my buffer I need to
// regain class access...

void reportDestroy(int p)
{
    text->reportDestroy(p);
}

void FXTerminal::reportDestroy(int p)
{
    replaceStyledText(p, 3, "\x00\x00\x00", 3, STYLE_MATH);
}

long FXTerminal::requestShowMath()
{
    recently_flushed = 0;
    if (length != 0 && getChar(length-1) != '\n')
        FXMathText::appendText("\n", 1); // terminate any pending line
    if (sync_even)
    {   LockMutex(mutex1);
        insertMathsLines();
        makePositionVisible(rowStart(length));
        sync_even = 0;
        UnlockMutex(mutex3);
        LockMutex(mutex2);
        UnlockMutex(mutex4);
    }
    else
    {   LockMutex(mutex3);
        insertMathsLines();
        makePositionVisible(rowStart(length));
        sync_even = 1;
        UnlockMutex(mutex1);
        LockMutex(mutex4);
        UnlockMutex(mutex2);
    }
    return 1;
}


static char promptString[MAX_PROMPT_LENGTH] = "> ";
static int promptLength = 2;

long FXTerminal::requestSetPrompt()
{
    strncpy(promptString, fwin_prompt_string, MAX_PROMPT_LENGTH);
    promptString[MAX_PROMPT_LENGTH-1] = 0;
    promptLength = strlen(promptString);
    if (sync_even)
    {   LockMutex(mutex1);
        sync_even = 0;
        UnlockMutex(mutex3);
        LockMutex(mutex2);
        UnlockMutex(mutex4);
    }
    else
    {   LockMutex(mutex3);
        sync_even = 1;
        UnlockMutex(mutex1);
        LockMutex(mutex4);
        UnlockMutex(mutex2);
    }
    return 1;
}

long FXTerminal::requestRefreshTitle()
{
    strcpy(window_full_title, full_title);
// I ought to make all actions on the window stuff happen in this thread.
    if (pauseFlags == 0) main_window->setTitle(window_full_title);
// Having done all that I can re-sync with the worker thread.
    if (sync_even)
    {   LockMutex(mutex1);
        sync_even = 0;
        UnlockMutex(mutex3);
        LockMutex(mutex2);
        UnlockMutex(mutex4);
    }
    else
    {   LockMutex(mutex3);
        sync_even = 1;
        UnlockMutex(mutex1);
        LockMutex(mutex4);
        UnlockMutex(mutex2);
    }
    return 1;
}

#ifdef CSL

long FXTerminal::requestSetMenus()
{
    char **modules = modules_list,
         **switches =  switches_list;
    FXMenuPane *loadMenu, *switchMenu, *tempMenu;
    int some = 0;
    if (modules != NULL && *modules!=NULL)
    {   loadMenu = new FXMenuPane(main_window);
// There is an amazing bit of messing about here! I accept a raw list of
// names, but if I just put them all as menu items directly that could lead
// to an objectionably long menu. So I bunch items alphabetically keeeping
// each block either starting with a single letter or no longer than 20
// items. These bunches then form sub-menus.
        int firstletter = 'a';
        int lastletter = 'a', nextletter;
        int count = 0, nextcount;
        char **p = modules;
        while (*p && (*p)[1] == lastletter) count++, p+=2;
        char **p1 = p;
        while (*modules)
        {   for (;;)
            {   nextcount = 0;
                nextletter = lastletter + 1;
                if (lastletter == 'z') break;
                while (*p && (*p)[1] == nextletter) nextcount++, p+=2;
                if (count + nextcount > 20) break;
                lastletter = nextletter;
                count += nextcount;
                p1 = p;
            }
            char subname[8];
            if (firstletter == lastletter) sprintf(subname, "%c", firstletter);
            else sprintf(subname, "%c-%c", firstletter, lastletter);
            tempMenu = new FXMenuPane(main_window);
            while (modules != p1)
            {   FXMenuCommand *m =
                    new FXMenuCommand(tempMenu, 1+*modules++, NULL,
                        (FXObject *)text, FXTerminal::ID_LOAD_MODULE);
                *modules++ = (char *)m;
            }
            new FXMenuCascade(loadMenu, subname, NULL, tempMenu);
            firstletter = lastletter = nextletter;
            count = nextcount;
            p1 = p;
        }
        FXMenuTitle *tt =
            new FXMenuTitle(main_menu_bar, "Load P&ackage", NULL, loadMenu);
        tt->create();
        some = 1;
    }
// Now do roughly the same with switches
    if (switches != NULL && *switches != NULL)
    {   switchMenu = new FXMenuPane(main_window);
        int firstletter = 'a';
        int lastletter = 'a', nextletter;
        int count = 0, nextcount;
        char **p = switches;
        p = switches;
        while (*p && (*p)[1] == lastletter) count++, p+=2;
        char **p1 = p;
        while (*switches)
        {   for (;;)
            {   nextcount = 0;
                nextletter = lastletter + 1;
                if (lastletter == 'z') break;
                while (*p && (*p)[1] == nextletter) nextcount++, p+=2;
                if (count + nextcount > 20) break;
                lastletter = nextletter;
                count += nextcount;
                p1 = p;
            }
            char subname[8];
            if (firstletter == lastletter) sprintf(subname, "%c", firstletter);
            else sprintf(subname, "%c-%c", firstletter, lastletter);
            if (count > 24)
            {   int chunks = count/18;
                if (chunks == 1) chunks = 2;
                int step = (count+chunks-1)/chunks;
                tempMenu = new FXMenuPane(main_window);
                for (int i=0; i<chunks; i++)
                {   FXMenuPane *sub = new FXMenuPane(main_window);
                    char partname[10];
                    sprintf(partname, "Part %d", i+1);
                    for (int j=0; j<step; j++)
                    {   if (*switches==NULL || switches==p1) break;
                        const char *name = *switches++;
                        FXMenuCheck *cc = new FXMenuCheck(sub, 1+name,
                            (FXObject *)text, FXTerminal::ID_FLIP_SWITCH);
                        *switches++ = (char *)cc;
                        cc->setCheck(*name=='y' ? TRUE : FALSE);
                        if (*name=='x') cc->disable();
                        else cc->enable();
                    }
                    new FXMenuCascade(tempMenu, partname, NULL, sub);
                }
            }
            else
            {   tempMenu = new FXMenuPane(main_window);
                while (*switches && switches != p1)
                {   const char *name = *switches++;
                    FXMenuCheck *cc = new FXMenuCheck(tempMenu, 1+name,
                        (FXObject *)text, FXTerminal::ID_FLIP_SWITCH);
                    *switches++ = (char *)cc;
                    cc->setCheck(*name=='y' ? TRUE : FALSE);
                    if (*name=='x') cc->disable();
                    else cc->enable();
                }
            }
            new FXMenuCascade(switchMenu, subname, NULL, tempMenu);

            firstletter = lastletter = nextletter;
            count = nextcount;
            p1 = p;
        }
        FXMenuTitle *tt =
            new FXMenuTitle(main_menu_bar, "&Switch", NULL, switchMenu);
        tt->create();
        some = 1;
    }
// Only add the "update reduce" menu if there were modules or switches shown
    if (some != 0)
    {   (new FXMenuCommand(main_menu_bar, "REDUCE Update", NULL,
                       (FXObject *)text, FXTerminal::ID_REDUCE_UPDATE,
                       LAYOUT_RIGHT))->create();
    }
    main_menu_bar->recalc();
    main_menu_bar->update();
// Having done all that I can re-sync with the worker thread.
    if (sync_even)
    {   LockMutex(mutex1);
        sync_even = 0;
        UnlockMutex(mutex3);
        LockMutex(mutex2);
        UnlockMutex(mutex4);
    }
    else
    {   LockMutex(mutex3);
        sync_even = 1;
        UnlockMutex(mutex1);
        LockMutex(mutex4);
        UnlockMutex(mutex2);
    }
    return 1;
}

long FXTerminal::requestRefreshSwitches()
{
    char **switches =  switches_list;
    char **modules = modules_list;
    while (switches != NULL && *switches != NULL)
    {   char *sw = *switches++;
        FXMenuCheck *m = (FXMenuCheck *)(*switches++);
        switch (*sw)
        {
    default:break;
    case 'X':
            m->setCheck(FALSE);
            m->disable();
            *sw = 'x';
            break;
    case 'Y':
            m->enable();
    case 0x3f&'Y':
            m->setCheck(TRUE);
            *sw = 'y';
            break;
    case 'N':
            m->enable();
    case 0x3f&'N':
            m->setCheck(FALSE);
            *sw = 'n';
            break;
        }
    }
    while (modules != NULL && *modules != NULL)
    {   char *sw = *modules++;
        FXMenuCommand *m = (FXMenuCommand *)(*modules++);
        switch (*sw)
        {
    default:break;          // a blank says "currently enabled"
    case 'X':               // the "X" said "disable now"
            m->disable();
            *sw = 'y';      // the "y" says "done that"
            break;
        }
    }
// Having done all that I can re-sync with the worker thread.
    if (sync_even)
    {   LockMutex(mutex1);
        sync_even = 0;
        UnlockMutex(mutex3);
        LockMutex(mutex2);
        UnlockMutex(mutex4);
    }
    else
    {   LockMutex(mutex3);
        sync_even = 1;
        UnlockMutex(mutex1);
        LockMutex(mutex4);
        UnlockMutex(mutex2);
    }
    return 1;
}

#endif

long FXTerminal::requestRequestInput()
{
// The sequence needs to be
//   worker requests another line of input.
//       GUI flushes all pending output to screen
//       GUI displays a prompt and enabled the keyboard
//   worker must remain suspended while GUI does its stuff
//       GUI eventually sees a CR from the user. Transfers data
//       to the worker and releases it to run.
    if (sync_even) LockMutex(mutex1);
    else LockMutex(mutex3);
    int x;
// When I get here I have just interlocked with the worker task. If an
// interrupt has been posted but not yet accepted I will return at once
// with a "^C" or "^G" as relevant, and hope that the worker then picks up
// the interrupt promptly.
    if (interrupt_callback != NULL &&
        (x = (*interrupt_callback)(QUERY_INTERRUPT)) != 0 &&
        x != TICK_INTERRUPT)
    {   inputBuffer[0] = x == QUIET_INTERRUPT ? 0x1f & 'C' : 0x1f & 'G';
        inputBuffer[1] = 0;
        inputBufferLen = 1;
        inputBufferP = 0;
        if (sync_even)
        {   sync_even = 0;
            UnlockMutex(mutex3);
            LockMutex(mutex2);
            UnlockMutex(mutex4);
        }
        else
        {   sync_even = 1;
            UnlockMutex(mutex1);
            LockMutex(mutex4);
            UnlockMutex(mutex2);
        }
        recently_flushed = 0;
        if (pauseFlags & PAUSE_DISCARD)
            main_window->setTitle(window_full_title);
        pauseFlags &= ~PAUSE_DISCARD;
        FXMathText::appendText(x == QUIET_INTERRUPT ? "^C" : "^G", 2);
        long r = FXMathText::onCmdInsertNewline(this, 0, NULL);
        setEditable(FALSE);
        setFocus();
        return r;
    }
    if (fwin_in != fwin_out && (pauseFlags & PAUSE_DISCARD) == 0)
    {   if (fwin_in > fwin_out)
            FXMathText::appendText(&fwin_buffer[fwin_out], fwin_in-fwin_out);
        else
        {   FXMathText::appendText(&fwin_buffer[fwin_out], FWIN_BUFFER_SIZE-fwin_out);
            FXMathText::appendText(&fwin_buffer[0], fwin_in);
        }
        fwin_out = fwin_in;
    }
    if (pauseFlags & PAUSE_DISCARD) main_window->setTitle(window_full_title);
    pauseFlags &= ~PAUSE_DISCARD;
    FXMathText::appendStyledText(promptString, promptLength, STYLE_PROMPT);
    promptEnd = length; // start of final line, list after the prompt
    makePositionVisible(rowStart(length));
    makePositionVisible(length);
    setCursorPos(length);
// Now having displayed the prompt, I leave the worker thread locked
// until the user types ENTER, at which stage I will complete the
// handshake. At this stage I "unlock the keyboard" by making the
// object editable.
    setEditable(TRUE);
// At this stage I will stact tracking whether keys have been pressed.
    keyFlags &= ~ANY_KEYS;
// Hah - before I return from this procedure and hence before allowing anything
// else to happen in this thread I will check the type-ahead buffer and move
// across characters from it as relevant. And I make any pending paste process
// take prioity even over typed-ahead stuff. insertFromPaste returns true if
// it inserts a segment that should end with a carriage return.
    if (paste_buffer && insertFromPaste())
    {   // I want the input line to be in a special colour
        changeStyle(promptEnd, length-promptEnd, STYLE_INPUT);
        return onCmdInsertNewline(this, 0, NULL);
    }
    while (type_out != type_in)
    {   int ch[4];
        keyFlags |= ANY_KEYS;
        ch[0] = ahead_buffer[type_out++];
        ch[1] = 0;
        if (type_out == TYPEAHEAD_SIZE) type_out = 0;
// The actions might be compared with what FXMathText does when a character
// is to be inserted. But here the type-ahead nature of things means that
// we can not possibly have a selection spanning the insert point. Also I
// do not support overstrike mode for type-ahead. So it ends up very simple!
        killSelection(TRUE);
        switch (ch[0])
        {
    case '\n':  // I want the input line to be in a special colour
            changeStyle(promptEnd, length-promptEnd, STYLE_INPUT);
            return onCmdInsertNewline(this, 0, NULL);
    case '\t':
            onCmdInsertTab(this, 0, NULL);
            break;
    default:
            onCmdInsertString(this, 0, (void *)ch);
            changeStyle(promptEnd, length-promptEnd, STYLE_INPUT);
            break; // out of the switch but not out of the while loop.
        }
    }
    return 1;
}

long FXTerminal::onTimeout(FXObject *c, FXSelector s, void *p)
{
// This is called (about) one per second. If within the last couple of
// second the worker thread flushed output buffers then nothing happens. If
// however the screen has not been updated for a couple of second and
// there is buffered output then the buffer is flushed. The idea is that
// I can do tolerably enthusiastic buffering of output so that I avoid
// as much synchronisation and GUI overhead, but still be assured that the
// screen remains silent for at worst a second or two.
//
// I will also want to update information on the title-bar here I suspect,
// but that is not implemented yet.
//
// Restart the timer so I get a continuing stream of ticks.
#if FOX_MAJOR==1 && FOX_MINOR==0
    timer = application_object->addTimeout(1000, this, ID_TIMEOUT);
#else
#if FOX_MAJOR==1 && (FOX_MINOR==1 || FOX_MINOR==2)
    timer = application_object->addTimeout(this, ID_TIMEOUT, 1000, NULL);
#else
    application_object->addTimeout(this, ID_TIMEOUT, 1000, NULL);
#endif
#endif
// I signal the user process with a "tick" around once per second. This can
// be used eg to cause it to update the time display at the top of the
// screen, or whatever... Note that if this happens just after somebody had
// tried to post a more genuine interrupt this might risk overriding that, so
// I only post a timer interrupt if no other one is pending.
    if (interrupt_callback != NULL)
    {   int r = (*interrupt_callback)(QUERY_INTERRUPT);
        if (r == 0) (*interrupt_callback)(TICK_INTERRUPT);
    }
//
    if (++recently_flushed < 2) return 0;
// When this handler is triggered it is in the interface thread and so
// no other interface code is running. This it may update fwin_out. However
// it is not interlocked with the worker thread so it MUST NOT allter fwin_in.
    if (fwin_in != fwin_out && (pauseFlags & PAUSE_DISCARD) == 0)
    {   if (fwin_in > fwin_out)
            FXMathText::appendText(&fwin_buffer[fwin_out], fwin_in-fwin_out);
        else
        {   FXMathText::appendText(&fwin_buffer[fwin_out], FWIN_BUFFER_SIZE-fwin_out);
            FXMathText::appendText(&fwin_buffer[0], fwin_in);
        }
        makePositionVisible(rowStart(length));
    }
    fwin_out = fwin_in;
    recently_flushed = 0;
    return 1;
}

// Repaint lines of text. Note that visrows MUST be arranged to
// reflect displayed maths so that one display expression is one "row".

void FXTerminal::drawContents(FXDCWindow& dc,FXint x,FXint y,FXint w,FXint h) const {
  register FXint hh=font->getFontHeight();
  register FXint yy=pos_y+margintop+toprow*hh;
  register FXint tl=(y-yy)/hh;
  register FXint bl=(y+h-yy)/hh;
  register FXint ln;
  if(tl<0) tl=0;
  if(bl>=nvisrows) bl=nvisrows-1;
// Now if I have any mathematical expression that is to be displayed I want to
// call drawTextRow exactly once. I will arrange drawTextRow so that it draws
// the whole formula whichever of the rows that make it up get passed (and I
// want that so I can cope with cases where the formula is only partly on the
// screen).
// To cope with all this I consider 3 sorts of rows
//   (A)  0x02 0x02 ...     maths but another part of same line is to come
//   (B)  0x02 <else>       final row of a maths line
//   (C)  <else>            non-maths
// and I can pretend that just before the top line to draw there had
// been a C. Here is a regular grammar to show what I do, with actions
// in parentheses and comments in brackets:
//            S -> A (drawmath) T [first sight of a maths row sequence]
//            S -> B (drawmath) S [maths formula on one row]
//            S -> C (draw) S     [ordinary line]
//            T -> A T            [follow on rows in one maths line]
//            T -> B S            [final row of a formula]
//            T -> C (draw) S     [can never arise]
  int inMath = 'S';
  for(ln=tl; ln<=bl; ln++){
    int linebeg=visrows[ln];
// Maths data has "0x02" bytes to introduce it, but it has to be in STYLE_MATH
// as well.
    int c1 = linebeg<length ? 
             (getStyle(linebeg) & STYLE_MATH ? getChar(linebeg) : 'x') :
             'x';
    int c2 = linebeg+1<length ? getChar(linebeg+1) : 'x';
    if (inMath == 'S') 
    {   if (c1 == 0x02 && c2 == 0x02)
        {   inMath = 'T';
            drawTextRow(dc,ln,x,x+w);
        }
        else drawTextRow(dc,ln,x,x+w);
    }
    else
    {   if (c1 != 0x02) drawTextRow(dc,ln,x,x+w);
        if (c1 != 0x02 || c2 != 0x02) inMath = 'S';
    }
  }
}


// Draw partial text line with correct style. The purpose of this
// over-ride ofthe FXMathText version is to support FXShowMath stuff, which
// is triggered by having a special marker character at the start and
// and of a line.

void FXTerminal::drawTextRow(FXDCWindow& dc,FXint line,FXint left,FXint right) const {
  register FXint x,y,w,h,linebeg,lineend,truelineend,cw,sp,ep,row,edge;
  register FXuint curstyle,newstyle;
  linebeg=visrows[line];
  lineend=truelineend=visrows[line+1];
  if(linebeg<lineend && isspace(getChar(lineend-1))) lineend--;         // Back off last space
  int firstThis = linebeg < length ? getChar(linebeg) : 'x';
  if (firstThis == 0x02)
  { lineend=lineEnd(linebeg);  // I want the true end of the LINE not the end
                                // of the ROW here...
    int realbeg=lineStart(linebeg);
// Now a bit of a messy issue. I may be drawing something that was passed as
// the second or third row of a single formula, but I want to display the
// whole thing. This can arise eg when a window has been scrolled so that
// the top of a formula will not be visible. I will therefore step
// back to the start of the line and adjust my y position accordingly.
    line-=(linebeg-realbeg);
    charPointer = linebeg+1;
// now I may be at something other than the final row of a formula, so I will
// need to skip over any extra 0x02 chars that there might be.
    while (charPointer<length && getChar(charPointer)==0x02) charPointer++;
    int extraLines=charPointer-realbeg-1;
    h=font->getFontHeight();
    int extra=extraLines*h;
// Oh how HATEFUL C++ is at times! This method is flagged as "const" and I can
// not change that because of the inheritance rules. But getDefaultWidth is
// not (even though it does not actually change anything!). However "text"
// is a reference to the FXTerminal (ie to "this") so I can go via that!
    x=text->getDefaultWidth();
    y=pos_y+margintop+(toprow+line)*h;
    edge=pos_x+marginleft+barwidth;
// Recover the scale that is to be used.
    int scale = getChar(charPointer) & 0xff;
    setMathsFontScale(scale & 0x07);
// Get pointer to box structure for the formula, or NULL if it has been
// discarded because of space limitations.
    Box *b = getBoxAddress(charPointer+1);
    if (b == NULL)
    {   int p = charPointer;
        charPointer += 4;
// Parse again to re-create a box that had gone away. This time it happens
// that my variables are set up so (p+1) is the location for the reference to
// the box, ie the "owner" info.
        b = parseTeX(staticCharForShowMath, p+1);
        if (b == NULL) b = makeTextBox("malformed expression", 20, 0);
        else text->recordBoxAddress(p+1, b);
//****************************************************************************
//** The above line has a side effect of marking the text buffer as "updated".
//** This is MESSY since it is liable to cause the screen to be redrawn
//** AGAIN. This double redraw happens when memory cycling causes a box to
//** need to be re-parsed. If I get very twitchy I will re-implement
//** recordBoxAddress so it does not flag the display as dirty, but for now
//** I will accept the slight performance hit in somewhat unusual cases.
//****************************************************************************
// If created again it needs measuring again.
        measureBox(b);
// If the box has been stored from before then it can have its measurements
// refreshed by measureBox1(). This leaves it alone if the font size has not
// changed since it was last measured, but otherwise re-assesses things.
    }
    else measureBox1(b);
// preserve font & colour across the drawing code.
    FXFont *ff = dc.getFont();
    FXColor fc = dc.getForeground();
// I paint the background for math output in a different (a sort of pale
// green) colour to help it starnd out.
    dc.setForeground(FXRGB(230,255,242));
    dc.fillRectangle(edge,y,right-edge,h+extra);
    dc.setForeground(FXRGB(0,0,0));  // render maths in BLACK for now
// Try to centre the formula across the line and within its space
// (well if it was a multi-line formula I try to centre the longest line
// at least roughly, and align the left of all others with that)
    int fh=b->text.height, fd=b->text.depth;
    int delta = (h+extra+fh-fd)/2;
    int xoff = (x - b->text.width)/2;  // This would centre it.
    if (scale >= 0x28)                 // Multi-line formula fun.
    {   scale = (scale - 0x28)/8;      // Space on line in units of
        scale *= mathWidth;            // mathWidth, and now in pixels
        scale /= 2;                    // Now I have indent to centre it.
// Because the recorded "spare" info is not quite reliable I will try to
// adjust it to avoid spilling over edges even in truly dire cases.
        if (scale+b->text.width >= x) scale = x-b->text.width-1;
        if (scale < 0) scale = 0;
        xoff = scale;
    }
// Now actually display the formula!
    paintBox(&dc, b,  xoff, y+delta);
// restore font and colour.
    dc.setForeground(fc);
    dc.setFont(ff);
// Whew! Done.
    return;
  }
  x=0;
  w=0;
  h=font->getFontHeight();
  y=pos_y+margintop+(toprow+line)*h;
  edge=pos_x+marginleft+barwidth;
  row=toprow+line;

  // Scan ahead till until we hit the end or the left edge
  for(sp=linebeg; sp<lineend; sp++){
    cw=charWidth(getChar(sp),x);
    if(x+edge+cw>=left) break;
    x+=cw;
    }

  // First style to display
  curstyle=style(row,linebeg,lineend,sp);

  // Draw until we hit the end or the right edge
  for(ep=sp; ep<lineend; ep++){
    newstyle=style(row,linebeg,truelineend,ep);
    if(newstyle!=curstyle){
      fillBufferRect(dc,edge+x,y,w,h,curstyle);
      if(curstyle&STYLE_TEXT) drawBufferText(dc,edge+x,y,w,h,sp,ep-sp,curstyle);
      curstyle=newstyle;
      sp=ep;
      x+=w;
      w=0;
      }
    cw=charWidth(getChar(ep),x+w);
    if(x+edge+w>=right) break;
    w+=cw;
    }

  // Draw unfinished fragment
  fillBufferRect(dc,edge+x,y,w,h,curstyle);
  if(curstyle&STYLE_TEXT) drawBufferText(dc,edge+x,y,w,h,sp,ep-sp,curstyle);
  x+=w;

  // Fill any left-overs outside of text
  if(x+edge<right){
    curstyle=style(row,linebeg,truelineend,ep);
    fillBufferRect(dc,edge+x,y,right-edge-x,h,curstyle);
    }
  }



// Draw fragment of text in given style
// This overrides the version in FXMathText.cpp adding around 1 extra line of code
// to handle the "PROMPT" style and put prompt & input text in different
// colours.

void FXTerminal::drawBufferText(FXDCWindow& dc,FXint x,FXint y,FXint,FXint,FXint pos,FXint n,FXuint style) const {
  register FXuint index=(style&STYLE_MASK);
  register FXColor color;
  FXchar str[2];
  color=0;
  if(hilitestyles && index){                                                    // Get colors from style table
    if(style&STYLE_SELECTED) color=hilitestyles[index-1].selectForeColor;
    else if(style&STYLE_HILITE) color=hilitestyles[index-1].hiliteForeColor;
    if(color==0) color=hilitestyles[index-1].normalForeColor;                   // Fall back on normal foreground color
    }
  if(color==0){                                                                 // Fall back to default style
    if(style&STYLE_SELECTED) color=seltextColor;
    else if(style&STYLE_HILITE) color=hilitetextColor;
    if(color==0) color=textColor;                                               // Fall back to normal text color
    }
  if (style&FXTerminal::STYLE_PROMPT)
  { color=promptColor; // ACN special
  }
  else if (style&FXTerminal::STYLE_INPUT)
  { color=inputColor; // ACN special
  }
  dc.setForeground(color);
  if(style&STYLE_CONTROL){
    y+=font->getFontAscent();
    str[0]='^';
    while(pos<gapstart && 0<n){
      str[1]=buffer[pos]|0x40;
      dc.drawText(x,y,str,2);
      x+=font->getTextWidth(str,2);
      pos++;
      n--;
      }
    while(0<n){
      str[1]=buffer[pos-gapstart+gapend]|0x40;
      dc.drawText(x,y,str,2);
      x+=font->getTextWidth(str,2);
      pos++;
      n--;
      }
    }
  else{
    y+=font->getFontAscent();
    if(pos+n<=gapstart){
      dc.drawText(x,y,&buffer[pos],n);
      }
    else if(pos>=gapstart){
      dc.drawText(x,y,&buffer[pos-gapstart+gapend],n);
      }
    else{
      dc.drawText(x,y,&buffer[pos],gapstart-pos);
      x+=font->getTextWidth(&buffer[pos],gapstart-pos);
      dc.drawText(x,y,&buffer[gapend],pos+n-gapstart);
      }
    }
  }

#endif /* HAVE_LIBFOX */

// End of FXTerminal.cpp


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