Artifact b7a566b2f986d97ac50943b7768760dd55d643811fedceecf41bb9ce2d739876:
- Executable file
r38/lisp/csl/cslbase/FXTerminal.cpp
— part of check-in
[f2fda60abd]
at
2011-09-02 18:13:33
on branch master
— Some historical releases purely for archival purposes
git-svn-id: https://svn.code.sf.net/p/reduce-algebra/code/trunk/historical@1375 2bfe0521-f11c-4a00-b80e-6202646ff360 (user: arthurcnorman@users.sourceforge.net, size: 159250) [annotate] [blame] [check-ins using] [more...]
// // "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 // < -> < : -> 4 // > -> > : -> 4 // & -> & : -> 5 // \n -> <br> : -> 4+CRLF // // POSSIBLY ' ' -> // '"' -> &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, "<"); p += 4; break; case '>': strcpy(p, ">"); p += 4; break; case '&': strcpy(p, "&"); 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 "<", 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