1 //
2 // "FXTerminal.cpp"                         Copyright A C Norman 2003-2020
3 //
4 //
5 // Window interface for old-fashioned C applications. Intended to
6 // be better than just running them within rxvt/xterm, but some people will
7 // always believe that running them under emacs is best!
8 //
9 
10 /******************************************************************************
11 * Copyright (C) 2003-20 by Arthur Norman, Codemist.  All Rights Reserved.     *
12 *******************************************************************************
13 * This library is free software; you can redistribute it and/or               *
14 * modify it under the terms of the GNU Lesser General Public                  *
15 * License as published by the Free Software Foundation;                       *
16 * version 2.1 of the License.                                                 *
17 *                                                                             *
18 * This library is distributed in the hope that it will be useful,             *
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of              *
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU           *
21 * Lesser General Public License for more details.                             *
22 *                                                                             *
23 * You should have received a copy of the GNU Lesser General Public            *
24 * License along with this library; if not, write to the Free Software         *
25 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA.  *
26 *                                                                             *
27 * I had intended to release this under the FOX addendum to its license that   *
28 * permits static linking, but the non-transitive nature of the terms there    *
29 * makes that infeasible hence this is just under LGPL.                        *
30 ******************************************************************************/
31 
32 // However as a special exception to LGPL 2.1 I grant permission for my code
33 // to be merged or linked with other code that is subject to LGPL version 3
34 // or GPL version 3. This provision does not represent permission to alter the
35 // license of my code to be that of LGPL 3 or GPL 3 - of itself and when
36 // removed from any LGPL 3 context it remains LGPL 2.1 and the freedom
37 // enshrined by that can not be reduced by adding in the additional
38 // constraints that LGPL 3 views as protections. However clearly the combined
39 // work that then includes my work would be subject to "3". But as per LGPL
40 // 2.1 (and the same would be true if I had used a BSD-style license here)
41 // notices explaining the license terms related to my code should not be
42 // removed. Anybody who changes or extends my code is permitted but not
43 // obliged to apply this exception, and perhaps by doing do they do not lock
44 // out (L)GPL 3 users but guarantee continued support for (L)GPL 2.1 in a way
45 // that the "or later" clause does not (since that permits anybody to
46 // unilaterally select just one version of the library to use, to the
47 // potential detriment of those whose choice differs).
48 
49 
50 /* $Id: FXTerminal.cpp 5246 2020-01-04 21:15:15Z arthurcnorman $ */
51 
52 // Apple no longer support the FinderLaunch sample code that they
53 // published and that explained to me how to open an HTML document
54 // programatically. So I will just ignore them an the capability that
55 // was once available is no more. If some Apple enthusiast wishes to
56 // review this and show how tro restore things that would be nice, but
57 // I do not believe me investigating would be a good use of my time.
58 //   ACN December 2015
59 
60 #if 0 && defined __APPLE__
61 #define MACINTOSH 1
62 #define MAC_FRAMEWORK 1
63 #else
64 #undef MACINTOSH
65 #undef MAC_FRAMEWORK
66 #endif
67 
68 #ifdef HAVE_CONFIG_H
69 #include "config.h"
70 #endif
71 
72 #include "fwin.h"
73 #include <fx.h>
74 
75 #ifdef WIN32
76 #include <windows.h>
77 #else
78 #include <pthread.h>
79 #endif
80 
81 #include <thread>
82 #include <chrono>
83 
84 #include <fxkeys.h>          // not included by <fx.h>
85 
86 #include "FXShowMath.h"
87 #include "FXTerminal.h"      // my own header file.
88 #include "termed.h"
89 #include "FXReduceDialog.h"
90 
91 #include <string.h>
92 #include <ctype.h>
93 #include <stdio.h>
94 #include <stdlib.h>
95 #include <time.h>
96 #include <stdarg.h>
97 #include <cwchar>
98 
99 #include <sys/stat.h>
100 
101 #ifndef S_IXUSR
102 #ifdef __S_IXUSR
103 #define S_IXUSR __S_IXUSR
104 #endif
105 #endif
106 
107 // The next is for pipes and threads
108 #ifdef WIN32
109 
110 #include <windows.h>
111 
112 
113 namespace FX {
114 
115 HANDLE pipedes;
116 int event_code = -1;
117 
118 #else
119 
120 #include <unistd.h>
121 
122 namespace FX {
123 
124 int pipedes[2];
125 
126 #endif /* WIN32 */
127 
128 static FXPrinter printer;
129 
130 // I need an event table of things that the user interface must respond to.
131 //
132 // I have to implement local editing and history stuff for the FOX-based
133 // version of the code here, and a parallel implementation is in the
134 // file "termed.c" to cope with cursor-addressible terminals (rather than
135 // real windows). It is perhaps unfortunate to have two parallel versions,
136 // but in various detailed ways it does not make sense for the treatment of
137 // keystrokes to match exactly in windowed and non-windowed mode (I thing!)
138 // and the code here has to be event driven, while the terminal version
139 // pulls characters from the terminal driver. At least by having both
140 // versions of the code my own and under my own control I can keep some sort
141 // of a handle on compatibility.
142 // Two examples of marginal oddities: the windowed version will very clearly
143 // have menu short-cut keys and they are processed by FOX rather directly.
144 // The terminal one does not need a menu to change its font, and any menu
145 // short-cut keys have to be handled directly by me.
146 // On the other hand while a terminal is in "cooked" mode various characters
147 // such as ^S, ^Q, ^C and ^Z are liable to be handled for me automatically,
148 // while in the FOX/Window version I need to intercept and deal with those
149 // for myself.
150 
151 
152 FXDEFMAP(FXTerminal) FXTerminalMap[] =
153 {
154 // User types some character. I override the FXText behaviour here
155     FXMAPFUNC(SEL_KEYPRESS,     0, FXTerminal::onKeyPress),
156 
157 // User types a newline. This overrides the behaviour that FXText had
158     FXMAPFUNC(SEL_COMMAND,      FXText::ID_INSERT_NEWLINE,
159                                 FXTerminal::onCmdInsertNewline),
160 
161 // Several events that can be generated by the worker thread.
162     FXMAPFUNC(SEL_IO_READ,      FXTerminal::ID_IPC, FXTerminal::onIPC),
163 
164 // Regular timer ticks from a timer thread.
165     FXMAPFUNC(SEL_TIMEOUT,      FXTerminal::ID_TIMEOUT, FXTerminal::onTimeout),
166 
167 // ... and now all the menu items that the user can provoke. The three cases
168 // above are given first as a (minor) efficiency issue.
169 
170 // The next 2 may not be especially useful to people other than me...
171     FXMAPFUNC(SEL_COMMAND,      FXTerminal::ID_LOAD_MODULE, FXTerminal::onCmdLoadModule),
172     FXMAPFUNC(SEL_COMMAND,      FXTerminal::ID_FLIP_SWITCH, FXTerminal::onCmdFlipSwitch),
173 
174     FXMAPFUNC(SEL_COMMAND,      FXTerminal::ID_READ, FXTerminal::onCmdRead),
175     FXMAPFUNC(SEL_COMMAND,      FXTerminal::ID_SAVE, FXTerminal::onCmdSave),
176     FXMAPFUNC(SEL_COMMAND,      FXTerminal::ID_SAVE_SELECTION, FXTerminal::onCmdSaveSelection),
177     FXMAPFUNC(SEL_COMMAND,      FXTerminal::ID_TO_FILE, FXTerminal::onCmdToFile),
178     FXMAPFUNC(SEL_COMMAND,      FXTerminal::ID_PRINT, FXTerminal::onCmdPrint),
179     FXMAPFUNC(SEL_COMMAND,      FXTerminal::ID_PRINT_SELECTION, FXTerminal::onCmdPrintSelection),
180     FXMAPFUNC(SEL_COMMAND,      FXTerminal::ID_CUT_SEL_X, FXTerminal::onCmdCutSel),
181     FXMAPFUNC(SEL_COMMAND,      FXText::ID_CUT_SEL, FXTerminal::onCmdCutSel),
182     FXMAPFUNC(SEL_COMMAND,      FXTerminal::ID_PASTE_SEL_X, FXTerminal::onCmdPasteSel),
183     FXMAPFUNC(SEL_COMMAND,      FXText::ID_PASTE_SEL, FXTerminal::onCmdPasteSel),
184     FXMAPFUNC(SEL_COMMAND,      FXText::ID_PASTE_MIDDLE, FXTerminal::onCmdPasteMiddle),
185     FXMAPFUNC(SEL_COMMAND,      FXTerminal::ID_COPY_SEL_X, FXTerminal::onCmdCopySel),
186     FXMAPFUNC(SEL_COMMAND,      FXText::ID_COPY_SEL, FXTerminal::onCmdCopySel),
187     FXMAPFUNC(SEL_COMMAND,      FXTerminal::ID_COPY_SEL_TEXT_X, FXTerminal::onCmdCopySelText),
188     FXMAPFUNC(SEL_COMMAND,      FXText::ID_COPY_SEL_TEXT, FXTerminal::onCmdCopySelText),
189     FXMAPFUNC(SEL_COMMAND,      FXTerminal::ID_REINPUT, FXTerminal::onCmdReinput),
190     FXMAPFUNC(SEL_COMMAND,      FXTerminal::ID_CLEAR, FXTerminal::onCmdClear),
191     FXMAPFUNC(SEL_COMMAND,      FXTerminal::ID_REDRAW, FXTerminal::onCmdRedraw),
192     FXMAPFUNC(SEL_COMMAND,      FXTerminal::ID_HOME, FXTerminal::onCmdHome),
193     FXMAPFUNC(SEL_COMMAND,      FXTerminal::ID_END, FXTerminal::onCmdEnd),
194     FXMAPFUNC(SEL_COMMAND,      FXTerminal::ID_FONT, FXTerminal::onCmdFont),
195     FXMAPFUNC(SEL_COMMAND,      FXTerminal::ID_RESET_FONT, FXTerminal::onCmdResetFont),
196     FXMAPFUNC(SEL_COMMAND,      FXTerminal::ID_RESET_WINDOW, FXTerminal::onCmdResetWindow),
197     FXMAPFUNC(SEL_COMMAND,      FXTerminal::ID_BREAK, FXTerminal::onCmdBreak),
198     FXMAPFUNC(SEL_COMMAND,      FXTerminal::ID_BACKTRACE, FXTerminal::onCmdBacktrace),
199     FXMAPFUNC(SEL_COMMAND,      FXTerminal::ID_BREAKLOOP, FXTerminal::onCmdBreakLoop),
200     FXMAPFUNC(SEL_COMMAND,      FXTerminal::ID_PAUSE, FXTerminal::onCmdPause),
201     FXMAPFUNC(SEL_COMMAND,      FXTerminal::ID_RESUME, FXTerminal::onCmdResume),
202     FXMAPFUNC(SEL_COMMAND,      FXTerminal::ID_STOP, FXTerminal::onCmdStop),
203     FXMAPFUNC(SEL_COMMAND,      FXTerminal::ID_DISCARD, FXTerminal::onCmdDiscard),
204 
205     FXMAPFUNC(SEL_COMMAND,      FXTerminal::ID_REDUCE, FXTerminal::onCmdReduce),
206 
207 #ifndef WIN32
208 #if !defined MACINTOSH || !defined MAC_FRAMEWORK
209     FXMAPFUNC(SEL_COMMAND,      FXTerminal::ID_BROWSER, FXTerminal::onCmdSelectBrowser),
210 #endif
211 #endif
212     FXMAPFUNC(SEL_COMMAND,      FXTerminal::ID_HELP, FXTerminal::onCmdHelp),
213     FXMAPFUNC(SEL_COMMAND,      FXTerminal::ID_ABOUT, FXTerminal::onCmdAbout)
214 
215 };
216 
217 #define TYPEAHEAD_SIZE 200
218 
219 static int type_in = 0, type_out = 0;
220 static int ahead_buffer[TYPEAHEAD_SIZE];
221 
222 static char *paste_buffer;
223 static int paste_flags, paste_n, paste_p, paste_is_html;
224 
225 static int longest_history_line;
226 
FXIMPLEMENT(FXTerminal,FXText,FXTerminalMap,ARRAYNUMBER (FXTerminalMap))227 FXIMPLEMENT(FXTerminal, FXText, FXTerminalMap, ARRAYNUMBER(FXTerminalMap))
228 
229 FXTerminal::FXTerminal(const char *argv0,
230                        FXComposite *p,FXObject* tgt,FXSelector sel,
231                        FXuint opts,
232                        FXint x,FXint y,FXint w,FXint h) :
233     FXText(p, tgt, sel, opts, x, y, w, h)
234 {
235     lineSpacing = 1;
236     setWrapColumns(80);
237     delay_callback = NULL;
238 
239     FXPrintDialog dummyPrintDialog(this, "");
240     dummyPrintDialog.getPrinter(printer);  // do not show - just get defaults.
241 
242 // It is of course well within the bounds of imagination that users would
243 // like to be able to specify these colours, and that of output text,
244 // for themselves.
245     promptColor = FXRGB(0, 64, 200);
246     inputColor = FXRGB(200, 64, 128);
247 
248     fwin_in = fwin_out = 0;
249     inputBufferLen = inputBufferP = 0;
250     logfile = NULL;
251 
252     type_in = type_out = 0;
253     paste_buffer = NULL;
254     paste_flags = paste_n = paste_p = paste_is_html = 0;
255 
256     historyFirst = 0;
257     historyLast = -1; // flag to say history is empty.
258     historyNumber = 0;
259     pauseFlags = keyFlags = searchFlags = 0;
260     promptEnd = length;
261     input_history_init(argv0, historyFirst, historyLast, historyNumber,
262                        input_history_next, longest_history_line);
263     InitMutex(pauseMutex);
264 
265     InitMutex(mutex1);
266     InitMutex(mutex2);
267     InitMutex(mutex3);
268     InitMutex(mutex4);
269 
270     LockMutex(mutex3);
271     LockMutex(mutex4);
272     sync_even = 1;
273 
274 #ifdef WIN32
275     pipedes = CreateEvent(NULL, FALSE, FALSE, NULL);
276     if (pipedes == 0)
277     {   fprintf(stderr,
278             "Failed to create an event object for internal communication\n");
279         application_object->exit(1);
280         exit(1);
281     }
282     application_object->addInput(pipedes,
283                                  INPUT_READ, this, ID_IPC);
284 #else
285     if (pipe(pipedes) != 0)
286     {   fprintf(stderr,
287             "Failed to create a pipe for internal communication\n");
288         application_object->exit(1);
289         exit(1);
290     }
291     application_object->addInput(pipedes[PIPE_READ_PORT],
292                                  INPUT_READ, this, ID_IPC);
293 #endif
294 
295 //    setFocus();    // select this window for input
296 
297     matchtime = 800; // causes parens to flash as they match (800 milliseconds)
298 }
299 
FXTerminal()300 FXTerminal::FXTerminal()
301 {
302     fprintf(stderr,
303         "I hope this never happens: report \"@FXT@\" to Codemist please\n");
304     fflush(stderr);
305 }
306 
~FXTerminal()307 FXTerminal::~FXTerminal()
308 {
309     input_history_end();
310     application_object->removeTimeout(this,
311                             (FXSelector)ID_TIMEOUT); // cancel ticks
312 #ifdef WIN32
313     application_object->removeInput(pipedes, ID_IPC);
314     CloseHandle(pipedes);
315 #else
316     application_object->removeInput(pipedes[PIPE_READ_PORT], ID_IPC);
317     close(pipedes[0]);
318     close(pipedes[1]);
319 #endif
320     DestroyMutex(mutex1);
321     DestroyMutex(mutex2);
322     DestroyMutex(mutex3);
323     DestroyMutex(mutex4);
324 }
325 
create()326 void FXTerminal::create()
327 {
328     FXText::create();
329     setFocus();    // select this window for input
330 
331 }
332 
setupShowMath()333 void FXTerminal::setupShowMath()
334 {
335 // Note that the terminal must have been created before I set up the
336 // FXShowMath stuff since at least on X I need to access the window
337 // identifier that it uses.
338 //
339 // I can give the second arg to setupShowMath, which controls font size, in
340 // two manners:
341 // (a) A positive value denotes a font size in decipoints. Well it is actually
342 //     a bit more ugly than that since on some platforms the size gets
343 //     adjusted to allow for (notional) screen pixels per inch and sometimes
344 //     not, and so I do not find this able to give me a totally consistent
345 //     control;
346 // (b) A negative value is the width of my root window, and I select my
347 //     font so that 80 "m" characters in the "subscript" size fit across
348 //     it. This provides some attempt at an automatic way to let the font
349 //     scale with window size.
350 //
351 // I set showmathInitialised if I set things up successfully
352 //
353 // I believe that this is the only place in the code where I link down to
354 // a module that uses Xft. That matters to me because at one stage I had a
355 // platform that failed when I tried to use Xft...
356     showmathInitialised =
357         ::setupShowMath(application_object, -getDefaultWidth(), this);
358     return;
359 }
360 
setEditable(FXbool fg)361 void FXTerminal::setEditable(FXbool fg)
362 {
363     FXText::setEditable(fg);
364 }
365 
366 
setVisibleRows(FXint rows)367 void FXTerminal::setVisibleRows(FXint rows)
368 {
369     FXText::setVisibleRows(rows);
370 }
371 
setVisibleColumns(FXint cols)372 void FXTerminal::setVisibleColumns(FXint cols)
373 {
374     FXText::setVisibleColumns(cols);
375 }
376 
377 #define UNUSED_ARG(x) ((x) = (x))
378 
onCmdPause(FXObject * c,FXSelector sel,void * ptr)379 long FXTerminal::onCmdPause(FXObject *c, FXSelector sel, void *ptr)
380 {
381     UNUSED_ARG(c); UNUSED_ARG(sel); UNUSED_ARG(ptr);
382     keyFlags &= ~ESC_PENDING;
383     if ((pauseFlags & PAUSE_PAUSE) == 0)
384     {   LockMutex(pauseMutex);
385         main_window->setTitle("Paused: Type ^Q to resume");
386     }
387     pauseFlags |= PAUSE_PAUSE;
388     setFocus();   // I am uncertain, but without this I lose focus...
389     return 1;
390 }
391 
392 static char window_full_title[90] = "";
393 
onCmdResume(FXObject * c,FXSelector sel,void * ptr)394 long FXTerminal::onCmdResume(FXObject *c, FXSelector sel, void *ptr)
395 {
396     UNUSED_ARG(c); UNUSED_ARG(sel); UNUSED_ARG(ptr);
397     keyFlags &= ~ESC_PENDING;
398     if (pauseFlags & PAUSE_PAUSE)
399     {   pauseFlags &= ~(PAUSE_PAUSE | PAUSE_STOP);
400         if (pauseFlags & PAUSE_DISCARD)
401             main_window->setTitle("Discarding output...");
402         else main_window->setTitle(window_full_title);
403         UnlockMutex(pauseMutex);
404     }
405     setFocus();   // I am uncertain, but without this I lose focus...
406     return 1;
407 }
408 
onCmdStop(FXObject * c,FXSelector sel,void * ptr)409 long FXTerminal::onCmdStop(FXObject *c, FXSelector sel, void *ptr)
410 {
411     UNUSED_ARG(c); UNUSED_ARG(sel); UNUSED_ARG(ptr);
412 // At present this is implemented just so it flips the state of the
413 // pause mutex and flag that ^S and ^Q use. Well I want it to do a bit more!
414 // I want it to force the worker thread to go into a suspended state. I think
415 // that for now I am going to allow ^S to halt output when some was due
416 // anyway, ^Z to pause the worker task soon even if it was not trying to
417 // generate output, and then when things are suspended either ^Q or another
418 // ^Z will release them.
419     keyFlags &= ~ESC_PENDING;
420     if (pauseFlags & PAUSE_PAUSE)
421     {   pauseFlags &= ~(PAUSE_PAUSE | PAUSE_STOP);
422         if (pauseFlags & PAUSE_DISCARD)
423             main_window->setTitle("Discarding output...");
424         else main_window->setTitle(window_full_title);
425         UnlockMutex(pauseMutex);
426     }
427     else
428     {   LockMutex(pauseMutex);
429         main_window->setTitle("Stopped: press ^Z to resume");
430         pauseFlags |= (PAUSE_PAUSE | PAUSE_STOP);
431     }
432     setFocus();   // I am uncertain, but without this I lose focus...
433     return 1;
434 }
435 
onCmdDiscard(FXObject * c,FXSelector sel,void * ptr)436 long FXTerminal::onCmdDiscard(FXObject *c, FXSelector sel, void *ptr)
437 {
438     UNUSED_ARG(c); UNUSED_ARG(sel); UNUSED_ARG(ptr);
439     keyFlags &= ~ESC_PENDING;
440     pauseFlags |= PAUSE_DISCARD;
441     main_window->setTitle("Discarding output...");
442 // I might hit ^O when the last line on the screen is not a complete
443 // one. I think it is neater to force in a newline here. The "..." is to
444 // remind the user I have chucked something away.
445     FXText::appendText("\n...\n", 5);
446     setFocus();   // I am uncertain, but without this I lose focus...
447     return 1;
448 }
449 
appendText(const FXchar * newtext,FXint n,FXbool notify)450 void FXTerminal::appendText(const FXchar *newtext, FXint n, FXbool notify)
451 {
452     FXText::appendText(newtext, n, notify);
453 }
454 
appendStyledText(const FXchar * newtext,FXint n,FXint style1,FXbool notify)455 void FXTerminal::appendStyledText(const FXchar *newtext, FXint n, FXint style1, FXbool notify)
456 {
457     FXText::appendStyledText(newtext, n, style1, notify);
458 }
459 
appendStyledText(const FXString & newtext,FXint style1,FXbool notify)460 void FXTerminal::appendStyledText(const FXString &newtext, FXint style1, FXbool notify)
461 {
462     FXText::appendStyledText(newtext, style1, notify);
463 }
464 
setStyled(FXbool st)465 void FXTerminal::setStyled(FXbool st)
466 {
467     FXText::setStyled(st);
468 }
469 
470 // Responses to menu items (and corresponding keyboard shortcuts)
471 
type_ahead(int ch)472 void FXTerminal::type_ahead(int ch)
473 {
474     ahead_buffer[type_in] = ch;
475     int p1 = type_in + 1;
476     if (p1 == TYPEAHEAD_SIZE) p1 = 0;
477     if (p1 == type_out) getApp()->beep();
478     else type_in = p1;
479 }
480 
string_ahead(const char * s)481 void FXTerminal::string_ahead(const char *s)
482 {
483     while (*s != 0) type_ahead(*s++ & 0xff);
484 }
485 
486 #ifndef LONGEST_LEGAL_FILENAME
487 #define LONGEST_LEGAL_FILENAME 1024
488 #endif
489 
490 static char most_recent_read_file[LONGEST_LEGAL_FILENAME] = ".";
491 
onCmdRead(FXObject * c,FXSelector sel,void * ptr)492 long FXTerminal::onCmdRead(FXObject *c, FXSelector sel, void *ptr)
493 {
494     UNUSED_ARG(c); UNUSED_ARG(sel); UNUSED_ARG(ptr);
495     keyFlags &= ~ESC_PENDING;
496     FXFileDialog opendialog(this, "Read File");
497     opendialog.setSelectMode(SELECTFILE_EXISTING);
498     opendialog.setFilename(most_recent_read_file);
499 // If this is not used for REDUCE the following line might well need adjusting
500 // in an application-sensitive manner.
501     opendialog.setPatternList("Reduce Files (*.red,*.tst)\nAll Files (*)");
502     const char *s = NULL;
503     FXString filename;
504     if (opendialog.execute())
505     {   filename = opendialog.getFilename();
506         if (FXStat::isFile(filename)) s = filename.text();
507         strcpy(most_recent_read_file, s);
508     }
509     if (s != NULL && *s!=0)
510     {   if (isEditable())
511         {   killSelection();
512             setInputText("", 0);
513             appendStyledText("in \"", 4, STYLE_INPUT);
514             appendStyledText(s, strlen(s), STYLE_INPUT);
515             appendStyledText("\";", 2, STYLE_INPUT);
516 //
517 // Here I insert a command in to the input buffer, with a ";" on the end
518 // of it. I will then wait for the user to type ENTER to accept that, or
519 // maybe to delete the ";" and replace it with a "$" for silent reading.
520 //
521 //          onCmdInsertNewline(c, sel, ptr);
522         }
523         else
524         {   string_ahead("in \"");
525             string_ahead(s);
526             string_ahead("\";");
527         }
528     }
529     setFocus();   // I am uncertain, but without this I lose focus...
530     return 1;
531 }
532 
533 static char most_recent_save_file[LONGEST_LEGAL_FILENAME] = ".";
534 
onCmdSave(FXObject * c,FXSelector sel,void * ptr)535 long FXTerminal::onCmdSave(FXObject *c, FXSelector sel, void *ptr)
536 {
537     UNUSED_ARG(c); UNUSED_ARG(sel); UNUSED_ARG(ptr);
538     keyFlags &= ~ESC_PENDING;
539 // Use FXFileDialog::getSaveFilename() here ?
540     FXFileDialog d(this, "Save", DECOR_BORDER|DECOR_TITLE);
541     d.setFilename(most_recent_save_file);
542     d.setPatternList(
543         "Log File (*.log)\nAll Files (*)");
544     if (d.execute())
545     {   FXString ss = d.getFilename();
546         const char *ss1 = ss.text();
547 // It seems plausible here that if I had not specified an explicit extension
548 // in my file-name that I should tag on ".log"
549 #define SAVE_BUFFER_SIZE 1024
550         char buff[SAVE_BUFFER_SIZE], style1[SAVE_BUFFER_SIZE];
551         int i = strlen(ss1) - 1;
552         while (i > 0 && ss1[i]!='.' && ss1[i]!='/' && ss1[i]!='\\') i--;
553         if (ss1[i] == '.') strcpy(buff, ss1);
554         else sprintf(buff, "%s.log", ss1);
555         FILE *f = fopen(buff, "w");
556         if (f == NULL)
557         {   FXMessageBox::error(this, MBOX_OK, "Error",
558                 "Unable to write to \"%s\"", buff);
559             setFocus();
560             return 1;
561         }
562         else
563         {   int ii = 0;
564             strcpy(most_recent_save_file, buff);
565             while (ii < length)
566             {   int n = SAVE_BUFFER_SIZE;
567                 if (ii + n > length) n = length - ii;
568                 extractText(buff, ii, n);
569 // Do I want to do something special with prompt strings? As it is
570 // I put the extractStyle call here so that I could identify them, but
571 // I just dump characters regardless.
572                 extractStyle(style1, ii, n);
573                 int n1 = fwrite(buff, 1, n, f);
574 // expect n1 == n here, unless there was an IO failure
575                 if (n != n1)
576                 {   FXMessageBox::error(this, MBOX_OK, "Error",
577                         "Writing the file seems to have failed");
578                     break;
579                 }
580                 ii += n;
581             }
582             fclose(f);  // returns 0 if all is well
583         }
584     }
585     setFocus();   // I am uncertain, but without this I lose focus...
586     return 1;
587 }
588 
onCmdSaveSelection(FXObject * c,FXSelector s,void * ptr)589 long FXTerminal::onCmdSaveSelection(FXObject *c, FXSelector s, void *ptr)
590 {
591     UNUSED_ARG(c); UNUSED_ARG(s); UNUSED_ARG(ptr);
592     keyFlags &= ~ESC_PENDING;
593     FXFileDialog d(this, "Save Selection", DECOR_BORDER|DECOR_TITLE);
594     d.setFilename(most_recent_save_file);
595     d.setPatternList(
596         "Log File (*.log)\nAll Files (*)");
597     if (d.execute())
598     {   FXString ss = d.getFilename();
599         const char *ss1 = ss.text();
600 // It seems plausible here that if I had not specified an explicit extension
601 // in my file-name that I should tag on ".log"
602         char buff[SAVE_BUFFER_SIZE], style1[SAVE_BUFFER_SIZE];
603         int i = strlen(ss1) - 1;
604         while (i > 0 && ss1[i]!='.' && ss1[i]!='/' && ss1[i]!='\\') i--;
605         if (ss1[i] == '.') strcpy(buff, ss1);
606         else sprintf(buff, "%s.log", ss1);
607         FILE *f = fopen(buff, "w");
608         if (f == NULL)
609         {   FXMessageBox::error(this, MBOX_OK, "Error",
610                 "Unable to write to \"%s\"", buff);
611             setFocus();
612             return 1;
613         }
614         else
615         {   int ii = getSelStartPos();
616             int len = getSelEndPos();
617             strcpy(most_recent_save_file, buff);
618             if (len <= ii) return 1; // no selection
619             while (ii < len)
620             {   int n = SAVE_BUFFER_SIZE;
621                 if (ii + n > len) n = len - ii;
622                 extractText(buff, ii, n);
623 // Do I want to do something special with prompt strings? As it is
624 // I put the extractStyle call here so that I could identify them, but
625 // I just dump characters regardless.
626                 extractStyle(style1, ii, n);
627                 int n1 = fwrite(buff, 1, n, f);
628 // expect n1 == n here, unless there was an IO failure
629                 if (n != n1)
630                 {   FXMessageBox::error(this, MBOX_OK, "Error",
631                         "Writing the file seems to have failed");
632                     break;
633                 }
634                 ii += n;
635             }
636             fclose(f);  // returns 0 if all is well
637         }
638     }
639     setFocus();   // I am uncertain, but without this I lose focus...
640     return 1;
641 }
642 
643 static char most_recent_log_file[LONGEST_LEGAL_FILENAME] = ".";
644 
onCmdToFile(FXObject * c,FXSelector s,void * ptr)645 long FXTerminal::onCmdToFile(FXObject *c, FXSelector s, void *ptr)
646 {
647     UNUSED_ARG(c); UNUSED_ARG(s); UNUSED_ARG(ptr);
648     keyFlags &= ~ESC_PENDING;
649     FILE *oldLogfile = logfile;
650 // There is a synchronisation issue here. My worker thread tends to
651 // go
652 //   FILE *f = logfile;
653 //   if (f != NULL) <write to it>
654 // so setting logfile to NULL here will switch logging off, however there
655 // could be one final write operation to be performed. So what if I do
656 // a close(logfile) too quickly. Well I think that the write will just fail
657 // an no desparate harm will ensue. Also I delay the close(logfile) until
658 // a dialog-box has run for teh user and that will with very very high
659 // probability leave me totally tidy.
660     logfile = NULL;
661     FXFileDialog d(this, "Log to File", DECOR_BORDER|DECOR_TITLE);
662     d.setFilename(most_recent_log_file);
663     d.setPatternList(
664         "Log File (*.log)\nAll Files (*)");
665     if (d.execute())
666     {   fclose(oldLogfile);
667         FXString ss = d.getFilename();
668         const char *ss1 = ss.text();
669 // It seems plausible here that if I had not specified an explicit extension
670 // in my file-name that I should tag on ".log"
671         char buff[SAVE_BUFFER_SIZE];
672         int i = strlen(ss1) - 1;
673         while (i > 0 && ss1[i]!='.' && ss1[i]!='/' && ss1[i]!='\\') i--;
674         if (ss1[i] == '.') strcpy(buff, ss1);
675         else sprintf(buff, "%s.log", ss1);
676         FILE *f = fopen(buff, "w");
677         if (f == NULL)
678         {   FXMessageBox::error(this, MBOX_OK, "Error",
679                 "Unable to write to \"%s\"", buff);
680             setFocus();
681             return 1;
682         }
683         strcpy(most_recent_log_file, buff);
684         logfile = f;
685     }
686     else fclose(oldLogfile);
687     setFocus();   // I am uncertain, but without this I lose focus...
688     return 1;
689 }
690 
691 // I make my own somewhat arbitrary choice of page margins here.
692 
693 
694 
695 #define leftmargin_inches    0.5
696 #define rightmargin_inches   0.5
697 #define topmargin_inches     0.75
698 #define bottommargin_inches  1.0
699 
700 // This prints a section of a row of text, where all the section uses
701 // the same style. The styles supported here are
702 //    SELECTED )
703 //    HILITE   ) these result in colour-effects for the display
704 //    PROMPT   )
705 //    INPUT    )
706 //    CONTROL    this lets control characters print as ^x (and at present
707 //               it does not behave well wrt line-wrapping, so I hope
708 //               it never gets used!)
709 
printBufferText(FXDCNativePrinter & dc,FXint x,FXint y,char * str,FXint n,FXuint style1)710 int FXTerminal::printBufferText(FXDCNativePrinter &dc, FXint x, FXint y,
711                                  char *str, FXint n, FXuint style1)
712 {
713     FXuint index=(style1&STYLE_MASK);
714     FXColor color;
715     color=0;
716     if (hilitestyles && index) // Get colors from style table
717     {   if (style1&STYLE_SELECTED)
718             color=hilitestyles[index-1].selectForeColor;
719         else if (style1&STYLE_HILITE)
720             color=hilitestyles[index-1].hiliteForeColor;
721         if (color==0)          // Fall back on normal foreground color
722             color=hilitestyles[index-1].normalForeColor;
723     }
724     if (color==0)              // Fall back to default style
725     {   if (style1&STYLE_SELECTED) color=seltextColor;
726         else if (style1&STYLE_HILITE) color=hilitetextColor;
727         if (color==0) color=textColor;   // Fall back to normal text color
728     }
729     if (style1&FXTerminal::STYLE_PROMPT)
730     {   color=promptColor;     // ACN special
731     }
732     else if (style1&FXTerminal::STYLE_INPUT)
733     {   color=inputColor;     // ACN special
734     }
735     dc.setForeground(color);
736     if (style1&STYLE_CONTROL)
737     {   y += dc.fntGetFontAscent();
738         FXchar str2[2];
739         str2[0]='^';
740         while (n!=0)
741         {   str2[1]=*str++ | 0x40;
742             dc.drawText(x, y, str2, 2);
743             x += dc.fntGetTextWidth(str2, 2);
744             n--;
745         }
746     }
747     else
748     {   y += dc.fntGetFontAscent();
749         dc.drawText(x, y, str, n);
750         x += dc.fntGetTextWidth(str, n);
751     }
752     return x;
753 }
754 
755 // Here I print one line of text. I let it terminate either at the
756 // end of a line, or after 80 characters (where a line-wrap is called for)
757 // or at the end of the buffer. I hand back the index of the start of
758 // the next line to print after this one. To cope with "styles" this
759 // scans the buffer spotting runs of characters that agree in their
760 // style, and send such runs in blocks to printBufferText.
761 
762 
763 static int charPointer;
764 
765 static int staticCharForShowMath();
766 
printTextRow(FXDCNativePrinter & dc,int p,int y,int left,int right)767 int FXTerminal::printTextRow(FXDCNativePrinter &dc,
768                              int p, int y, int left, int right)
769 {
770     int firstThis = p < length ? getByte(p) : 'x';
771     int line = 0;
772     if (firstThis == 0x02)
773     {   int realbeg=lineStart(p);
774 // Now a bit of a messy issue. I may be drawing something that was passed as
775 // the second or third row of a single formula, but I want to display the
776 // whole thing. This can arise eg when a window has been scrolled so that
777 // the top of a formula will not be visible. I will therefore step
778 // back to the start of the line and adjust my y position accordingly.
779         line-=(p-realbeg);
780         charPointer = p+1;
781 // now I may be at something other than the final row of a formula, so I will
782 // need to skip over any extra 0x02 chars that there might be.
783         while (charPointer<length && getChar(charPointer)==0x02) charPointer++;
784         int extraLines=charPointer-realbeg-1;
785         int h=dc.fntGetFontHeight();
786         int extra=extraLines*h;
787         int x=right;
788         int edge=left;
789 // Recover the scale that is to be used.
790         int scale = getByte(charPointer+1) & 0x07;
791         setMathsFontScale(scale);
792         int indent = (getByte(charPointer) - '0') & 0x3f;
793         indent += ((getByte(charPointer+1) - '0') & 0x38) << 3;
794 // Get pointer to box structure for the formula, or NULL if it has been
795 // discarded because of space limitations.
796         charPointer++;
797         Box *b = getBoxAddress(charPointer+1);
798         if (b == NULL)
799         {   int p1 = charPointer;
800             charPointer += 4;
801 // Parse again to re-create a box that had gone away. This time it happens
802 // that my variables are set up so (p1+1) is the location for the reference to
803 // the box, ie the "owner" info.
804             findTeXstart();
805             b = parseTeX(staticCharForShowMath, p1+1);
806             if (b == NULL) b = makeTopBox(makeTextBox("malformed-TeX-input", 19, 0));
807             text->recordBoxAddress(p1+1, b);
808         }
809         measureBox(b);
810 // I paint the background for math output in a different (a sort of pale
811 // green) colour to help it starnd out.
812         dc.setForeground(FXRGB(230,255,242));
813         dc.fillRectangle(edge,y,right-edge,h+extra);
814         dc.setForeground(FXRGB(0,0,0));  // render maths in BLACK for now
815 // Try to centre the formula across the line and within its space
816 // (well if it was a multi-line formula I try to centre the longest line
817 // at least roughly, and align the left of all others with that)
818         int fh=b->text.height, fd=b->text.depth;
819         int delta = (h+extra+fh-fd)/2;
820 // the next bit is worrying wrt pixels vs print units.
821         int xoff = (x - b->text.width)/2;  // This would centre it.
822         if (indent != 0)                   // Multi-line formula fun.
823         {   indent--;                      // Space on line in units of
824             indent *= mathWidth;           // mathWidth, and now in pixels
825             indent /= 2;                   // Now I have indent to centre it.
826 // Because the recorded "indent" info is not quite reliable I will try to
827 // adjust it to avoid spilling over edges even in truly dire cases.
828             if (indent+b->text.width >= x) indent = x-b->text.width-1;
829             if (indent < 0) indent = 0;
830             xoff = indent;
831         }
832 // Now actually display the formula!
833         paintBox(&dc, b,  xoff, y+delta);
834         b->top.measuredSize = -1; // force re-measure when printing finished.
835 // Whew! Done.
836         p = charPointer;
837         int c;
838         bool shifted=false;
839         for (;;)
840         {   if (p == length) return p;           // end of buffer
841             c=getByte(p);
842             if (c==0x0e) shifted=true;
843             else if (c==0x0f) shifted=false;
844             else if (!shifted && c=='\n') return p+1;  // end of line
845             p++;
846         }
847     }
848     int column = 0;
849     FXuint style1 = getStyle(p), st = 0;
850     int ch = ' ', x = left;
851     for (;;)               // collect one line of output, which may end up
852     {   char buff[84];     // expressed as multiple segments
853         int bp = 0;
854         for (;;)           // accumulate a segment
855         {   if (p == length) break;       // stop at end of text buffer
856             ch = getChar(p);
857             if (ch == '\n') break;        // stop at end of this line
858             if (column >= 80) break;      // need to wrap the line
859             st = getStyle(p);
860             if (ch == '\t') break;        // stop before tab
861             if (st != style1 || (st & STYLE_CONTROL)!=0) break;
862                                           // stop on style change
863             buff[bp++] = ch;
864             column++;
865             p++;
866         }
867         if (bp!=0)
868         {   buff[bp] = 0;     // Make sure the string is NUL-terminated
869             x = printBufferText(dc, x, y, buff, bp, style1);
870         }
871         if (p == length) return p;        // end of buffer
872         if (ch == '\n') return p+1;       // end of (ordinary) line
873         if (column >= 80) return p;       // end of wrapped line
874         if (ch == '\t')                   // I ignore styles on tabs!
875         {   int blanks = 8 - (column%8);
876             x += dc.fntGetTextWidth("        ", blanks);
877 // Note that since I put tab-stops every 8 positions and my line length
878 // is 80, a tab can bring me up to the position where a line is about to
879 // wrap, but it could not cause a wrap in any case where a simple blank
880 // would not. The long and short of this is that I do not have to do anything
881 // at all special about line-wrapping here.
882             column += blanks;
883         }
884         else if ((st & STYLE_CONTROL) != 0)
885         {   buff[0] = '^';
886             buff[1] = ch | 0x40;
887 // Here I do have a worry about line-wrapping. If I have 79 chars on the line
888 // already and then I issue a STYLE_CONTROL character it will want to
889 // print as "^X" for some "X". The "^" can go on the current line but the
890 // "X" needs to wrap to the next.
891 //
892 // I will IGNORE this issue now (except that I have left room in my buffer
893 // for slightly overlong lines, and made my wrap-test as ">=80" rather
894 // then "==80"). Thus in such cases (which my programs will never exercise!)
895 // the printed output can have a few lines 81 chars long rather than 80.
896             x = printBufferText(dc, x, y, buff, 2, st & (~STYLE_CONTROL));
897             column += 2;
898         }
899         else style1 = st;
900         bp = 0;
901     }
902 }
903 
904 
905 // The next function prints from character startc to endc in the print
906 // buffer. This may use several pages, depending on the number of lines
907 // to be printed and the page size. Lines will be wrapped at 80 columns.
908 
printContents(FXDCNativePrinter & dc,int startc,int endc,int left,int right,int top,int bottom)909 void FXTerminal::printContents(FXDCNativePrinter &dc,
910     int startc, int endc,
911     int left, int right, int top, int bottom)
912 {
913 // the size of paper to print on is measured in points, taken here to
914 // run at 72 points per inch.
915     int p = lineStart(startc);
916     int hh=dc.fntGetFontSpacing();
917     for (int pageNo=1;;pageNo++)
918     {   dc.beginPage(pageNo);
919         FXint yy = top + hh;
920         int inMath = 'S';        // see corresponding screen drawing code
921                                  // for an explanation of the logic here.
922         while (yy < bottom)
923         {
924             int c1 = p<length ?
925                      (getStyle(p) & STYLE_MATH ? getChar(p) : 'x') :
926                      'x';
927             int c2 = p+1<length ? getChar(p+1) : 'x';
928             if (inMath == 'S')
929             {   if (c1 == 0x02 && c2 == 0x02)
930                 {   inMath = 'T';
931 // This is about to print the top row of a bit of maths. Check how
932 // many rows in all will be used and whether there is room for them, and if
933 // not try to insert a page break.
934                     int p1 = p+2;
935                     int yyy = yy;
936                     while (p1 < length && getChar(p1) == 0x02)
937                     {   yyy += hh;
938                         p1++;
939                     }
940                     if (yyy >= bottom && yy != top+hh)
941                     {   break;   // force this formula to a new page
942                     }
943                     p = printTextRow(dc, p, yy, left, right);
944                 }
945                 else p = printTextRow(dc, p, yy, left, right);
946             }
947             else
948             {   if (c1 != 0x02) p = printTextRow(dc, p, yy, left, right);
949                 if (c1 != 0x02 || c2 != 0x02) inMath = 'S';
950             }
951             if (p >= endc) break;
952             yy += hh;
953         }
954         dc.endPage();
955         if (p >= endc) break;
956     }
957     dc.endPrint();
958 }
959 
960 // I want to create a font that will be fixed pitch and such that 80
961 // columns of text go neatly across the width of my paper. This selects
962 // a plausible choice by first creating a font of almost arbitrary size,
963 // then measuring the width that it delivers, and on that basis choosing a
964 // larger or smaller size to use. I use an initial point size of 10
965 // since that is about the size I expect to end up with.
966 
967 // Note that the implementation I have here is suitable for a case
968 // where I have only one font associated with printing.
969 
setPrinterFont(FXDCNativePrinter & dc,int pageWidth,const char * font_name)970 static void setPrinterFont(FXDCNativePrinter &dc, int pageWidth, const char *font_name)
971 {
972     FXFont *f = dc.fntGenerateFont(font_name, 10, FXFont::Bold);
973     f->create();
974     dc.setFont(f);
975 // I will get the width of a string of 10 "M" characters to assess the
976 // width of my font. On a really clever system if count be other than a
977 // whole number of points.
978     int w = dc.fntGetTextWidth("MMMMMMMMMM", 10);
979 // The font I have just measured might not have been exactly the size
980 // font I originally asked for, so I will check what size it was and
981 // base calculations on that. Note that (ugh) getSize returns the size
982 // in deci-points not points, so I have a factor of 10 to fudge in
983 // somewhere. I use the length of my string of "M" characters...
984     double bestSize = dc.fntDoubleSize()*(double)pageWidth/(80.0*w);
985 // Now I think I know the size of font that would suit me best. I
986 // rather expect it to be 8pt or 9pt, but if the font I was using was
987 // more expanded or condensed it could stray somewhat from that range.
988 //
989 // For a NativePrinter I can specify a font size as a double, so I do
990 // that here to get as good a fit as I can.
991     delete f;
992     f = dc.fntDoubleGenerateFont(font_name, bestSize, FXFont::Bold);
993     f->create();
994     dc.setFont(f);
995 }
996 
doPrinting(int startp,int endp)997 long FXTerminal::doPrinting(int startp, int endp)
998 {
999     FXPrintDialog d(this, "Print");
1000     d.setPrinter(printer);    // carry forward state from previous usage
1001     if (d.execute())
1002     {   d.getPrinter(printer);
1003         FXDCNativePrinter dc(getApp());
1004         if (!dc.beginPrint(printer))
1005         {   FXMessageBox::error(this, MBOX_OK, "Printer Error",
1006                 "Unable to print to %s", printer.name.text());
1007             setFocus();
1008             return 1;
1009         }
1010 #define PER_INCH 3600
1011 // For measuring and moving around the page (while I print) I will
1012 // work in terms of units of 1/3600in. This number has been selected so that
1013 // a point (1/72in) is a whole number of units, and so that typical
1014 // real printer resolutions like 300, 600, 720dpi let me work in whole
1015 // numbers of units per pixel. In terms of this resolution an A4 page of
1016 // paper is around 40000 units high - I believe I am a long way from
1017 // running into integer overflow issues. Note that even though I measure
1018 // and specify X and Y coordinates in these units, when I select font
1019 // sized I still need to work in points.
1020         dc.setHorzUnitsInch(PER_INCH);
1021         dc.setVertUnitsInch(PER_INCH);
1022         int pw = dc.getPageWidth();
1023         int ph = dc.getPageHeight();
1024 #define leftmargin   ((int)(leftmargin_inches*(double)PER_INCH))
1025 #define rightmargin  ((int)(rightmargin_inches*(double)PER_INCH))
1026 #define topmargin    ((int)(topmargin_inches*(double)PER_INCH))
1027 #define bottommargin ((int)(bottommargin_inches*(double)PER_INCH))
1028 
1029 // Another delicacy here. On Windows I make my default font "Courier New"
1030 // and if I am doing a windows-print I can and should use that. However
1031 // if I print to file I must use one of the simple Adobe fonts, since
1032 // otherwise the Postscript I generate would need to map font names
1033 // and/or embed detailed font information, and I do not support that!
1034 //
1035 // Well actually setPrinterFont will probably map "Courier New" onto
1036 // "courier" in the relevant case, but it still makes sense that at this
1037 // point I alert the gentle reader to the fact that screen and printer
1038 // fonts may differ.
1039 //
1040 // A yet further qualification to the above commentary is that if I have
1041 // any mathematics displayed via SHOWMATH then I will need to Computer
1042 // Modern fonts to print it (just as I do to display it) and so I WILL embed
1043 // them. For a first version (at least) of this I will embed the whole of
1044 // the three fonts (cmr10, cmmi10, cmsy10 and cmex10) that I use by just
1045 // sending the "*.pfa" files to the printer at the start of the print job.
1046 // If I wanted to be seriously more messy here I could work out just what
1047 // subset of characters I was using and send only those. That seems like way
1048 // too much work for now!
1049         int widthToUse = pw-leftmargin-rightmargin;
1050         if (printer.flags&PRINT_DEST_FILE)
1051             setPrinterFont(dc, widthToUse, "courier");
1052         else setPrinterFont(dc, widthToUse, DEFAULT_FONT_NAME);
1053         cmrFontsEmbedded = 0;             // embed at first use: not done yet.
1054 // If I can not select the correct maths font for the printer I will
1055 // suppress printing!
1056 //
1057 // There us a real "jolly" here in that I want the font sizes to be such
1058 // that things fit across the printed page in a way that is roughly the same
1059 // as the way I fit things across the screen. The Fox-level code here
1060 // (and selecting the main printer font) works in scaled abstract units,
1061 // but until and unless I adjust things if I use Xft I would work in
1062 // pixels - whatever they are in the printer case! I think that my best
1063 // resolution is to arrange that I do NOT use Xft when dealing with printer
1064 // fonts. The effect will then be that I use the more ordinary Fox procedures
1065 // to measure everything, and that should lead to some degree of consistency
1066 // without me needing to retrofit Fox-style scaled coordinate systems to
1067 // other stuff I do with FXShowMath.
1068         void *fontSave[36];
1069         for (int i=0; i<36; i++)
1070         {   fontSave[i] = masterFont[i];
1071             masterFont[i] = NULL;  // NECESSARY!
1072         }
1073         if (changeMathFontSize(application_object, -widthToUse))
1074             printContents(dc, startp, endp,   // which bit needs printing?
1075                           leftmargin, pw-rightmargin,
1076                           topmargin, ph-bottommargin);
1077         delete dc.getFont();
1078         for (int i=0; i<36; i++)
1079         {   masterFont[i] = fontSave[i];
1080         }
1081     }
1082     setFocus();   // I am uncertain, but without this I lose focus...
1083     return 1;
1084 }
1085 
onCmdPrint(FXObject * c,FXSelector s,void * ptr)1086 long FXTerminal::onCmdPrint(FXObject *c, FXSelector s, void *ptr)
1087 {
1088     UNUSED_ARG(c); UNUSED_ARG(s); UNUSED_ARG(ptr);
1089     keyFlags &= ~ESC_PENDING;
1090     return doPrinting(0, length);
1091 }
1092 
onCmdPrintSelection(FXObject * c,FXSelector s,void * ptr)1093 long FXTerminal::onCmdPrintSelection(FXObject *c, FXSelector s, void *ptr)
1094 {
1095     UNUSED_ARG(c); UNUSED_ARG(s); UNUSED_ARG(ptr);
1096     keyFlags &= ~ESC_PENDING;
1097 // This actually prints the lines containing the whole selection...
1098     return doPrinting(getSelStartPos(), getSelEndPos());
1099 }
1100 
1101 
1102 // Cut
onCmdCutSel(FXObject *,FXSelector,void *)1103 long FXTerminal::onCmdCutSel(FXObject *, FXSelector, void *)
1104 {
1105 #ifdef RECONSTRUCTED
1106 // I will not permit a CUT from anywhere other than part of the current
1107 // input line. I delegate the messy but to COPY.
1108     if (selstartpos < selendpos &&
1109         promptEnd <= selstartpos &&
1110         (options & TEXT_READONLY) == 0)
1111     {   onCmdCopySel(this, 0, NULL);
1112 // I will only delete the stuff if I managed to copy it to the clipboard,
1113 // which I can tell here by virtue of clipbuffer being reasonable.
1114         if (clipbuffer)
1115         {   removeText(selstartpos, selendpos-selstartpos, TRUE);
1116             killSelection(TRUE);
1117             setCursorPos(cursorpos, TRUE);
1118             makePositionVisible(cursorpos);
1119             flags |= FLAG_CHANGED;
1120             modified = TRUE;
1121         }
1122     }
1123     else getApp()->beep();
1124 #endif // RECONSTRUCTED
1125     return 1;
1126 }
1127 
1128 
1129 // Copy
onCmdCopySel(FXObject *,FXSelector,void *)1130 long FXTerminal::onCmdCopySel(FXObject *, FXSelector, void *)
1131 {
1132 #ifdef RECONSTRUCTED // I.e. I need to rework this!
1133     FXDragType types[2];
1134     if (selstartpos < selendpos)
1135     {   types[0]=stringType;
1136         types[1]=textType;
1137 // I am going to put text onto the clipboard in HTML format - right now
1138 // I am not quite certain how to make an Atom that declares that they is
1139 // the mime type I will use...
1140         if (acquireClipboard(types, 2))
1141         {
1142 // Now I am going to want to convert from what I find in the text buffer
1143 // into what I want to place on the clipboard. What I will want to generate
1144 // will be along the lines of
1145 //  <   ->   &lt;    : -> 4
1146 //  >   ->   &gt;    : -> 4
1147 //  &   ->   &amp;   : -> 5
1148 //  \n  ->   <br>    : -> 4+CRLF
1149 //
1150 // POSSIBLY   ' ' -> &nbsp;
1151 //            '"' -> &quo;      but I do not do those transformations yet.
1152 //
1153 //  <html><body><style>tt.prompt{color:rgb(0,64,128)}</style><tt>   : 61+CRLF
1154 //  Line 1<br>                                                      : +4+CRLF
1155 //  <tt class="prompt">PROMPT:</tt>Line 2<br>                       : +19+5+4
1156 //  Line 3<br>
1157 //  </tt></body></html>                                             : +19
1158             const char *clipStart   = "<html><body><style>tt.prompt"
1159                                       "{color:rgb(0,64,128)}"
1160                                       "</style><tt>\r\n";
1161             const char *clipEnd     = "</tt></body></html>\r\n";
1162             const char *prStart     = "<tt class=\"prompt\">";
1163             const char *prEnd       = "</tt>";
1164             int style = 0, i;
1165             cliplength = strlen(clipStart);
1166             for (i=selstartpos; i<selendpos; i++)
1167             {   char ch = getChar(i);
1168                 int st = getStyle(i) & STYLE_PROMPT;
1169                 if (st != style)
1170                 {   if (st) cliplength += strlen(prStart);
1171                     else cliplength += strlen(prEnd);
1172                     style = st;
1173                 }
1174                 switch (ch)
1175                 {
1176             case '<':
1177             case '>': cliplength += 4; break;
1178             case '&': cliplength += 5; break;
1179             case '\n':cliplength += 6; break;
1180             default:  cliplength++;    break;
1181                 }
1182             }
1183             if (style) cliplength += strlen(prEnd);
1184             cliplength += strlen(clipEnd);
1185             FXFREE(&clipbuffer);
1186             FXCALLOC(&clipbuffer, FXchar, cliplength+1);
1187             if (!clipbuffer)
1188             {   fxwarning("%s::onCmdCopySel: out of memory\n",getClassName());
1189                 cliplength=0;
1190             }
1191             else
1192             {   char *p = clipbuffer;
1193                 strcpy(p, clipStart);
1194                 style = 0;
1195                 p += strlen(clipStart);
1196 // Now I have to copy the selected region mapping it onto the HTML that I
1197 // want it to be. Slightly messy!
1198                 for (i=selstartpos; i<selendpos; i++)
1199                 {   char ch = getChar(i);
1200                     int st = getStyle(i) & STYLE_PROMPT;
1201                     if (st != style)
1202                     {   if (st)
1203                         {   strcpy(p, prStart);
1204                             p += strlen(prStart);
1205                         }
1206                         else
1207                         {   strcpy(p, prEnd);
1208                             p += strlen(prEnd);
1209                         }
1210                         style = st;
1211                     }
1212                     switch (ch)
1213                     {
1214                 case '<': strcpy(p, "&lt;");     p += 4; break;
1215                 case '>': strcpy(p, "&gt;");     p += 4; break;
1216                 case '&': strcpy(p, "&amp;");    p += 5; break;
1217                 case '\n':strcpy(p, "<br>\r\n"); p += 6; break;
1218                 default:  *p++ = ch;                     break;
1219                     }
1220                 }
1221                 if (style)
1222                 {   strcpy(p, prEnd);
1223                     p += strlen(prEnd);
1224                 }
1225                 strcpy(p, clipEnd);
1226             }
1227         }
1228     }
1229 #endif // RECONSTRUCTED
1230     return 1;
1231 }
1232 
1233 
1234 // Copy as Text
onCmdCopySelText(FXObject *,FXSelector,void *)1235 long FXTerminal::onCmdCopySelText(FXObject *, FXSelector, void *)
1236 {
1237 #ifdef RECONSTRUCTED
1238 // I will do minimal changes to the HTML-style COPY to get a plain version
1239     FXDragType types[2];
1240     if (selstartpos < selendpos)
1241     {   types[0]=stringType;
1242         types[1]=textType;
1243         if (acquireClipboard(types, 2))
1244         {   int i;
1245             cliplength = selendpos - selstartpos;
1246             FXFREE(&clipbuffer);
1247             FXCALLOC(&clipbuffer, FXchar, cliplength+1);
1248             if (!clipbuffer)
1249             {   fxwarning("%s::onCmdCopySelText: out of memory\n",getClassName());
1250                 cliplength=0;
1251             }
1252             else
1253             {   char *p = clipbuffer;
1254                 int ignore = 0;
1255                 for (i=selstartpos; i<selendpos; i++)
1256                 {   char ch = getChar(i);
1257                     if (ch == 0x03) continue;
1258                     if (ch == 0x02) { ignore = 6; continue; }
1259                     if (ignore > 0) { ignore--; continue; }
1260                     *p++ = ch;
1261                 }
1262             }
1263         }
1264     }
1265 #endif // RECONSTRUCTED
1266     return 1;
1267 }
1268 
1269 
1270 // Paste clipboard
1271 
onCmdPasteSel(FXObject *,FXSelector,void *)1272 long FXTerminal::onCmdPasteSel(FXObject *, FXSelector, void *)
1273 {
1274     if (!isEditable() || paste_buffer)
1275     {   getApp()->beep();
1276         return 1;
1277     }
1278     if (isPosSelected(cursorpos))
1279     {   removeText(selstartpos, selendpos-selstartpos, TRUE);
1280         killSelection(TRUE);
1281         setCursorPos(cursorpos, TRUE);
1282         makePositionVisible(cursorpos);
1283         flags |= FLAG_CHANGED;
1284         modified = TRUE;
1285     }
1286     FXchar *string;
1287     FXint len;
1288     if (getDNDData(FROM_CLIPBOARD, stringType,
1289                    (FXuchar*&)string, (FXuint&)len))
1290         performPaste(string, len);
1291     return 1;
1292 }
1293 
1294 
1295 // Paste selection (used for middle mouse button)
1296 
onCmdPasteMiddle(FXObject *,FXSelector,void *)1297 long FXTerminal::onCmdPasteMiddle(FXObject *, FXSelector, void *)
1298 {
1299     if (!isEditable() || paste_buffer)
1300     {   getApp()->beep();
1301         return 1;
1302     }
1303     FXchar *string; FXint len;
1304     if (selstartpos==selendpos ||
1305         cursorpos<=selstartpos ||
1306         selendpos<=cursorpos)
1307     {   // Avoid paste inside selection
1308         if (getDNDData(FROM_SELECTION, stringType,
1309                        (FXuchar*&)string, (FXuint&)len))
1310             performPaste(string, len);
1311     }
1312     return 1;
1313 }
1314 
performPaste(FXchar * string,FXint len)1315 void FXTerminal::performPaste(FXchar *string, FXint len)
1316 {
1317     paste_buffer = string;
1318     paste_n = len;
1319     paste_p = 0;
1320     paste_flags = 0;
1321 // Now decide if I think I have an HTML paste. First skip simple whitespace
1322     while (*string == ' ' || *string == '\r' || *string == '\n')
1323     {   paste_p++;
1324         string++;
1325     }
1326     if (string[0] == '<' &&
1327         tolower(string[1]) == 'h' &&
1328         tolower(string[2]) == 't' &&
1329         tolower(string[3]) == 'm' &&
1330         tolower(string[4]) == 'l' &&
1331         string[5] == '>')
1332     {   paste_is_html = 1;
1333         paste_p += 6;
1334 // OK, so in the HTML case I now point at the body of the stuff. I will need
1335 // to ignore HTML tags (both opening and closing) while I transfer stuff, and
1336 // I will want to ignore prompts, which are marked as
1337 //   <tt style="prompt"> ... </tt>
1338 // and I will also want to ignore style declaractions as in
1339 //   <style> ... </style>
1340 // In each case I will suppose that I do not have other HTML blocks nested
1341 // inside.
1342     }
1343     else
1344     {   paste_is_html = 0;
1345         paste_p = 0;
1346     }
1347     if (insertFromPaste()) onCmdInsertNewline(this, 0, NULL);
1348 }
1349 
isStartPrompt(const char * s)1350 int FXTerminal::isStartPrompt(const char *s)
1351 {
1352 // This is crummy code! It looks for 'tt class="prompt"'
1353 // and allows arbitrary case within "tt" and "class" and whitespace
1354 // there too.
1355     while (*s!=0 && isspace(*s)) s++;
1356     if (tolower(*s) != 't') return 0;
1357     s++;
1358     if (tolower(*s) != 't') return 0;
1359     s++;
1360     while (*s!=0 && isspace(*s)) s++;
1361     if (tolower(*s) != 'c') return 0;
1362     s++;
1363     if (tolower(*s) != 'l') return 0;
1364     s++;
1365     if (tolower(*s) != 'a') return 0;
1366     s++;
1367     if (tolower(*s) != 's') return 0;
1368     s++;
1369     if (tolower(*s) != 's') return 0;
1370     s++;
1371     while (*s!=0 && isspace(*s)) s++;
1372     if (tolower(*s) != '=') return 0;
1373     s++;
1374     if (strncmp(s, "\"prompt\"", 8) != 0) return 0;
1375     return 1;
1376 }
1377 
isStyle(const char * s)1378 int FXTerminal::isStyle(const char *s)
1379 {
1380     const char *s0 = s;
1381     while (*s!=0 && isspace(*s)) s++;
1382     if (tolower(*s) != 's') return 0;
1383     s++;
1384     if (tolower(*s) != 't') return 0;
1385     s++;
1386     if (tolower(*s) != 'y') return 0;
1387     s++;
1388     if (tolower(*s) != 'l') return 0;
1389     s++;
1390     if (tolower(*s) != 'e') return 0;
1391     s++;
1392     return (s - s0);
1393 }
1394 
insertFromPaste()1395 int FXTerminal::insertFromPaste()
1396 {
1397 // I will deal with the easy case of plain text pastes first
1398     if (!paste_is_html)
1399     {   for (;;)
1400         {   int ch;
1401             if (paste_p == paste_n || (ch = paste_buffer[paste_p++]) == 0)
1402             {   FXFREE(&paste_buffer);
1403                 paste_n = paste_p = paste_is_html = 0;
1404                 return 0;  // all done and finished.
1405             }
1406             if (ch == '\r') continue;
1407             else if (ch == '\n') return 1;
1408             else insertStyledText(cursorpos, &paste_buffer[paste_p-1], 1, STYLE_INPUT);
1409         }
1410     }
1411 // Inserting from HTML is really rather similar to plain inserting, except
1412 // that I want to process items such as "&lt;", skip HTML tags such
1413 // as </tt> and detect as a special case '<tt type="prompt">'.
1414 
1415     for (;;)
1416     {   int ch;
1417         if (paste_p >= paste_n || (ch = paste_buffer[paste_p++]) == 0)
1418         {   FXFREE(&paste_buffer);
1419             paste_n = paste_p = paste_is_html = 0;
1420             return 0;  // all done and finished.
1421         }
1422         if (ch == '\r' || ch == '\n') continue;
1423 // Since I only worry about three "&" items here I will write out tests
1424 // in-line. If I had more I ought to set up something table-driven. If I
1425 // really want to handle HTML that comes from other applications I ought
1426 // to think about a LOT more cases... but while my concentration is on
1427 // cut-and-paste to my own code I can remain happy just like this.
1428         if (ch == '&' && !paste_flags)
1429         {   if (strncmp(&paste_buffer[paste_p], "lt;", 3)==0)
1430             {   paste_p += 3;
1431                 if (paste_p > paste_n) continue; // ran over the end
1432                 insertStyledText(cursorpos, "<", 1, STYLE_INPUT);
1433                 continue;
1434             }
1435             if (strncmp(&paste_buffer[paste_p], "gt;", 3)==0)
1436             {   paste_p += 3;
1437                 if (paste_p > paste_n) continue; // ran over the end
1438                 insertStyledText(cursorpos, ">", 1, STYLE_INPUT);
1439                 continue;
1440             }
1441             if (strncmp(&paste_buffer[paste_p], "amp;", 4)==0)
1442             {   paste_p += 4;
1443                 if (paste_p > paste_n) continue; // ran over the end
1444                 insertStyledText(cursorpos, "&", 1, STYLE_INPUT);
1445                 continue;
1446             }
1447         }
1448 // In handling HTML tags I will permit lower or upper case, however I
1449 // will not allow extra whitespace. Thus "< br>" or "<br >" will not do!
1450         if (ch == '<')
1451         {   if (tolower(paste_buffer[paste_p]) == 'b' &&
1452                 tolower(paste_buffer[paste_p+1]) == 'r' &&
1453                 paste_buffer[paste_p+2] == '>')
1454             {   paste_p += 3;     // <br> encodes a newline
1455                 paste_flags = 0;
1456                 if (paste_p > paste_n) continue; // ran over the end
1457                 return 1;
1458             }
1459             if (isStartPrompt(&paste_buffer[paste_p]) ||
1460                 isStyle(&paste_buffer[paste_p])) paste_flags = 1;
1461             else paste_flags = 0;
1462             while (paste_p < paste_n &&
1463                    paste_buffer[paste_p] != '>') paste_p++;
1464             paste_p++;  // past the ">"
1465             continue;
1466         }
1467         if (!paste_flags) insertStyledText(cursorpos, &paste_buffer[paste_p-1], 1, STYLE_INPUT);
1468     }
1469 }
1470 
1471 
onCmdReinput(FXObject * c,FXSelector s,void * ptr)1472 long FXTerminal::onCmdReinput(FXObject *c, FXSelector s, void *ptr)
1473 {
1474     keyFlags &= ~ESC_PENDING;
1475 // "ReInput" acts as a copy, followed by cursor movement to the end of
1476 // the input line, a cancelling of the selection and finally a paste.
1477     onCmdCopySel(c, s, ptr);
1478     killSelection(TRUE);  // "deselect all"
1479     onCmdEnd(c, s, ptr);
1480     long r = onCmdPasteSel(c, s, ptr);
1481     setFocus();
1482     return r;
1483 }
1484 
onCmdClear(FXObject * c,FXSelector s,void * ptr)1485 long FXTerminal::onCmdClear(FXObject *c, FXSelector s, void *ptr)
1486 {
1487     UNUSED_ARG(c); UNUSED_ARG(s); UNUSED_ARG(ptr);
1488     keyFlags &= ~ESC_PENDING;
1489 // Discard absolutely everything! Including any prompt or currently part-
1490 // completed input-line.
1491     setText("", 0);
1492     promptEnd = 0;
1493     recalc();
1494     update();
1495     setFocus();   // I am uncertain, but without this I lose focus...
1496     return 1;
1497 }
1498 
onCmdRedraw(FXObject * c,FXSelector s,void * ptr)1499 long FXTerminal::onCmdRedraw(FXObject *c, FXSelector s, void *ptr)
1500 {
1501     UNUSED_ARG(c); UNUSED_ARG(s); UNUSED_ARG(ptr);
1502     keyFlags &= ~ESC_PENDING;
1503 // This is intended for use when a bug or something has left the screen
1504 // corrupted. I hope that what I do here will be enough to force a
1505 // reasonably complete re-draw.
1506     recalc();
1507     update();
1508     setFocus();   // I am uncertain, but without this I lose focus...
1509     return 1;
1510 }
1511 
onCmdHome(FXObject * c,FXSelector s,void * ptr)1512 long FXTerminal::onCmdHome(FXObject *c, FXSelector s, void *ptr)
1513 {
1514     UNUSED_ARG(c); UNUSED_ARG(s); UNUSED_ARG(ptr);
1515     keyFlags &= ~ESC_PENDING;
1516     makePositionVisible(0);
1517     setCursorPos(0);
1518     setFocus();   // I am uncertain, but without this I lose focus...
1519     return 1;
1520 }
1521 
onCmdEnd(FXObject * c,FXSelector s,void * ptr)1522 long FXTerminal::onCmdEnd(FXObject *c, FXSelector s, void *ptr)
1523 {
1524     UNUSED_ARG(c); UNUSED_ARG(s); UNUSED_ARG(ptr);
1525     keyFlags &= ~ESC_PENDING;
1526     int n = rowStart(length);
1527     makePositionVisible(n);
1528 // The above two encourage the system to do any horizontal scrolls
1529 // that it can to try to make the start of the final line visible
1530 // as well as its end.  Well maybe I will be trying to prevent horizontal
1531 // scrolling anyway, but I will leave this code here since it is
1532 // fairly harmless.
1533     makePositionVisible(length);
1534     setCursorPos(length);
1535     setFocus();   // I am uncertain, but without this I lose focus...
1536     return 1;
1537 }
1538 
onCmdFont(FXObject * c,FXSelector s,void * ptr)1539 long FXTerminal::onCmdFont(FXObject *c, FXSelector s, void *ptr)
1540 {
1541     UNUSED_ARG(c); UNUSED_ARG(s); UNUSED_ARG(ptr);
1542     keyFlags &= ~ESC_PENDING;
1543     FXFontDialog d(this, "Font", DECOR_BORDER|DECOR_TITLE);
1544     FXFontDesc description;
1545 //FILE *f = fopen("/tmp/font.log", "a");
1546 //fprintf(f, "onCmdFont\n"); fflush(f);
1547     font->getFontDesc(description);    // information about the current font ..
1548 //fprintf(f, "onCmdFont current face as requested <%s>\n", description.face); fflush(f);
1549     strcpy(description.face, font->getActualName().text());
1550 //fprintf(f, "onCmdFont actual face in use <%s>\n", description.face); fflush(f);
1551     description.flags =
1552         (description.flags & ~FXFont::Variable) | FXFont::Fixed;
1553     d.setFontSelection(description);   // .. and make that default choice!
1554 // I really want to adjust the font-selector dialog so that it only
1555 // shows and accepts fixed-pitch fonts. I am not quite sure how to do
1556 // this yet.
1557     if (d.execute())
1558     {   FXFont *o = font;
1559         d.getFontSelection(description);
1560 //fprintf(f, "new face <%s>\n", description.face); fflush(f);
1561         FXFont *newFont = new FXFont(application_object, description);
1562         newFont->create();
1563 //fprintf(f, "new actual name = %s\n", newFont->getActualName().text());
1564         if (!newFont->isFontMono())
1565         {   delete newFont;
1566             FXMessageBox::error(this, MBOX_OK, "Error",
1567                 "You must select a fixed-pitch font");
1568 //fclose(f);
1569             return 1;
1570         }
1571         setFont(newFont);
1572         delete o;                     // I must delete the old font.
1573     }
1574 //fclose(f);
1575     setFocus();   // I am uncertain, but without this I lose focus...
1576     return 1;
1577 }
1578 
onCmdResetFont(FXObject * c,FXSelector s,void * ptr)1579 long FXTerminal::onCmdResetFont(FXObject *c, FXSelector s, void *ptr)
1580 {
1581     UNUSED_ARG(c); UNUSED_ARG(s); UNUSED_ARG(ptr);
1582     keyFlags &= ~ESC_PENDING;
1583 // Resets the font (and thus window-width) to my default. This is to give
1584 // some level of safety in case I feel messed up.
1585     FXFont *o = font;
1586     FXFont *f = selectFont(DEFAULT_FONT_NAME, 0,  // 0 means "choose for me"
1587         FXFont::Bold, FXFont::Straight, FONTENCODING_DEFAULT,
1588         0, FXFont::Fixed|FXFont::Modern);
1589     setFont(f);
1590     delete o;
1591     setFocus();   // I am uncertain, but without this I lose focus...
1592     return 1;
1593 }
1594 
onCmdResetWindow(FXObject * c,FXSelector s,void * ptr)1595 long FXTerminal::onCmdResetWindow(FXObject *c, FXSelector s, void *ptr)
1596 {
1597     keyFlags &= ~ESC_PENDING;
1598 // Put the whole window back in a tidy-ish state
1599     setVisibleRows(24);
1600     onCmdResetFont(c, s, ptr);
1601     int dx = getDefaultWidth()+FXScrollArea::vertical->getDefaultWidth();
1602     int dy = main_window->getDefaultHeight();
1603     main_window->getShell()->resize(dx, dy);
1604     update();     // major changes and so I should refresh everything
1605     setFocus();   // I am uncertain, but without this I lose focus...
1606     return 1;
1607 }
1608 
onCmdBreak(FXObject * c,FXSelector s,void * ptr)1609 long FXTerminal::onCmdBreak(FXObject *c, FXSelector s, void *ptr)
1610 {
1611     keyFlags &= ~ESC_PENDING;
1612 // I always call the interrupt callback procedure. If the user task was
1613 // suspended waiting for input then I release it causing the fwin_getchar()
1614 // call to return a control-C.
1615     if (pauseFlags & PAUSE_DISCARD) main_window->setTitle(window_full_title);
1616     pauseFlags &= ~PAUSE_DISCARD;
1617     if (async_interrupt_callback != NULL) (*async_interrupt_callback)(QUIET_INTERRUPT);
1618     if (isEditable()) // at present we are waiting for keyboard input.
1619     {   inputBuffer[0] = '\n';
1620         inputBuffer[1] = 0;
1621         inputBufferLen = 1;
1622         inputBufferP = 0;
1623         if (sync_even)
1624         {   sync_even = 0;
1625             UnlockMutex(mutex3);
1626             LockMutex(mutex2);
1627             UnlockMutex(mutex4);
1628         }
1629         else
1630         {   sync_even = 1;
1631             UnlockMutex(mutex1);
1632             LockMutex(mutex4);
1633             UnlockMutex(mutex2);
1634         }
1635         recently_flushed = 0;
1636         long r = FXText::onCmdInsertNewline(c, s, ptr);
1637         setEditable(FALSE);
1638         setFocus();   // I am uncertain, but without this I lose focus...
1639         return r;
1640     }
1641     type_in = type_out = 0;
1642     setFocus();   // I am uncertain, but without this I lose focus...
1643     return 1;
1644 }
1645 
onCmdBacktrace(FXObject * c,FXSelector s,void * ptr)1646 long FXTerminal::onCmdBacktrace(FXObject *c, FXSelector s, void *ptr)
1647 {
1648     keyFlags &= ~ESC_PENDING;
1649     if (pauseFlags & PAUSE_DISCARD) main_window->setTitle(window_full_title);
1650     pauseFlags &= ~PAUSE_DISCARD;
1651     if (async_interrupt_callback != NULL) (*async_interrupt_callback)(NOISY_INTERRUPT);
1652     if (isEditable()) // at present we are waiting for keyboard input.
1653     {   inputBuffer[0] = '\n';
1654         inputBuffer[1] = 0;
1655         inputBufferLen = 1;
1656         inputBufferP = 0;
1657         if (sync_even)
1658         {   sync_even = 0;
1659             UnlockMutex(mutex3);
1660             LockMutex(mutex2);
1661             UnlockMutex(mutex4);
1662         }
1663         else
1664         {   sync_even = 1;
1665             UnlockMutex(mutex1);
1666             LockMutex(mutex4);
1667             UnlockMutex(mutex2);
1668         }
1669         recently_flushed = 0;
1670         FXText::appendText("^G", 2);
1671         long r = FXText::onCmdInsertNewline(c, s, ptr);
1672         setEditable(FALSE);
1673         setFocus();   // I am uncertain, but without this I lose focus...
1674         return r;
1675     }
1676     type_in = type_out = 0;
1677     setFocus();   // I am uncertain, but without this I lose focus...
1678     return 1;
1679 }
1680 
onCmdBreakLoop(FXObject * c,FXSelector s,void * ptr)1681 long FXTerminal::onCmdBreakLoop(FXObject *c, FXSelector s, void *ptr)
1682 {
1683     keyFlags &= ~ESC_PENDING;
1684 // I always call the interrupt callback procedure. If the user task was
1685 // suspended waiting for input then I release it causing the fwin_getchar()
1686 // call to return a control-C.
1687     if (pauseFlags & PAUSE_DISCARD) main_window->setTitle(window_full_title);
1688     pauseFlags &= ~PAUSE_DISCARD;
1689     if (async_interrupt_callback != NULL)
1690         (*async_interrupt_callback)(BREAK_LOOP);
1691     if (isEditable()) // at present we are waiting for keyboard input.
1692     {   inputBuffer[0] = '\n';
1693         inputBuffer[1] = 0;
1694         inputBufferLen = 1;
1695         inputBufferP = 0;
1696         if (sync_even)
1697         {   sync_even = 0;
1698             UnlockMutex(mutex3);
1699             LockMutex(mutex2);
1700             UnlockMutex(mutex4);
1701         }
1702         else
1703         {   sync_even = 1;
1704             UnlockMutex(mutex1);
1705             LockMutex(mutex4);
1706             UnlockMutex(mutex2);
1707         }
1708         recently_flushed = 0;
1709         long r = FXText::onCmdInsertNewline(c, s, ptr);
1710         setEditable(FALSE);
1711         setFocus();   // I am uncertain, but without this I lose focus...
1712         return r;
1713     }
1714     type_in = type_out = 0;
1715     setFocus();   // I am uncertain, but without this I lose focus...
1716     return 1;
1717 }
1718 
1719 // This should be called when the window is being closed...
1720 
setEOF()1721 void setEOF()
1722 {   if (text != NULL) text->setEOF();
1723 }
1724 
setEOF()1725 void FXTerminal::setEOF()
1726 {   if (mustQuit) return; // already done!
1727     mustQuit = true;
1728 // In CSL/Reduce is active doing computation it should detect the request
1729 // to quit that is set here...
1730     if (async_interrupt_callback != NULL)
1731         (*async_interrupt_callback)(QUIT_PROGRAM);
1732 // But maybe it was in fact hanging waiting for input. In which case I
1733 // can unlock some mutexes to allow it to move forward. Here I want to code
1734 // that tries to read characters to think it has some available. Because
1735 // I am not going to do anything much after this I can just unlock every
1736 // mutex that I think I might own and that the client might be waiting
1737 // on. It should then be able to move forward and detect the "give up"
1738 // flag that I have set!
1739     if (sync_even)
1740     {   UnlockMutex(mutex3);
1741         UnlockMutex(mutex4);
1742     }
1743     else
1744     {   UnlockMutex(mutex1);
1745         UnlockMutex(mutex2);
1746     }
1747 }
1748 
1749 
1750 // The following are concerned with a list of options and plugins that the
1751 // application may have.
1752 
1753 char **modules_list, **switches_list;
1754 
onCmdLoadModule(FXObject * c,FXSelector s,void * ptr)1755 long FXTerminal::onCmdLoadModule(FXObject *c, FXSelector s, void *ptr)
1756 {
1757     keyFlags &= ~ESC_PENDING;
1758     FXString ss = ((FXMenuCommand *)c)->getText();
1759     const char *mtext = ss.text();
1760     if (isEditable())
1761     {   killSelection();
1762         setInputText("", 0);
1763         appendStyledText("load_package \"", 14, STYLE_INPUT);
1764         appendStyledText(mtext, strlen(mtext), STYLE_INPUT);
1765         appendStyledText("\";", 2, STYLE_INPUT);
1766         onCmdInsertNewline(c, s, ptr);
1767     }
1768     else
1769     {   string_ahead("load_package \"");
1770         string_ahead(mtext);
1771         string_ahead("\";\n");
1772     }
1773     setFocus();   // I am uncertain, but without this I lose focus...
1774     return 1;
1775 }
1776 
onCmdFlipSwitch(FXObject * c,FXSelector s,void * ptr)1777 long FXTerminal::onCmdFlipSwitch(FXObject *c, FXSelector s, void *ptr)
1778 {
1779     keyFlags &= ~ESC_PENDING;
1780     FXMenuCheck *m = (FXMenuCheck *)c;
1781     FXString ss = m->getText();
1782     const char *mtext = ss.text();
1783     int state = m->getCheck();
1784     const char *cmd;
1785 // The very act of selecting the menu item flipped its state, and so now
1786 // I need to force the underlying system to reflect that by issuing a
1787 // suitable command.
1788     if (state == TRUE) cmd = "on ";
1789     else cmd = "off ";
1790     if (isEditable())
1791     {   killSelection();
1792         setInputText("", 0);
1793         appendStyledText(cmd, strlen(cmd), STYLE_INPUT);
1794         appendStyledText(mtext, strlen(mtext), STYLE_INPUT);
1795         appendStyledText(";", 1, STYLE_INPUT);
1796         onCmdInsertNewline(c, s, ptr);
1797     }
1798     else
1799     {   string_ahead(cmd);
1800         string_ahead(mtext);
1801         string_ahead(";\n");
1802     }
1803 // I also want to record in my table of switches the new state of this one.
1804     for (char **switches = switches_list; *switches!=NULL; switches++)
1805     {   char *p = *switches;
1806         if (strcmp(p+1, mtext) == 0)
1807         {   if (state == TRUE) *p = 'y';
1808             else *p = 'n';
1809             break;
1810         }
1811     }
1812     setFocus();   // I am uncertain, but without this I lose focus...
1813     return 1;
1814 }
1815 
onCmdReduce(FXObject * c,FXSelector s,void * ptr)1816 long FXTerminal::onCmdReduce(FXObject *c, FXSelector s, void *ptr)
1817 {
1818 // Here I have one of the Reduce-specific menu items that needs to create
1819 // a dialog box that will eventually return a string that is to be inserted
1820 // in the iput buffer. I search the list of menus to find just which one
1821 // was involved...
1822     keyFlags &= ~ESC_PENDING;
1823     FXString ss = ((FXMenuCommand *)c)->getText();
1824     const char *mtext = ss.text();
1825     int l = (int)strlen(mtext);
1826     const char **m = reduceMenus, *p, *p1;
1827     int found = 0;
1828     while (*m != NULL)
1829     {   p = *m++; // A particular menu string
1830         while (*p != '@') p++; // Skip top-level menu name
1831         p++;                   // past the "@"
1832         if (strncmp(mtext, p, l) == 0 && p[l] == '@')
1833         { found = 1;
1834           break;
1835         }
1836     }
1837     if (!found) return 1; // Not found - this is a BUG
1838     p = p+l+1;
1839 // Now p is a string that looks like:
1840 //    Dialog Box Title @ n @ f<1> @ f<2> @ .. @ f<n> @ template
1841 // and it should display a box that shows roughly
1842 //      ---------------------------
1843 //      | Dialog Box Title        |
1844 //      |-------------------------|
1845 //      | f1                      |
1846 //      |   ###################   |
1847 //      .           ...           .
1848 //      | fn                      |
1849 //      |   ###################   |
1850 //      |.........................|
1851 //      |    cancel         OK    |
1852 //      ---------------------------
1853 // where "###" denote fields that the user fills in, but that possibly have
1854 // some initial content to get them started.
1855 
1856 //@@    printf("Description string = <%s>\n", p);
1857     for (p1=p; *p1!='@'; p1++);
1858     FXString caption = FXString(p, p1-p);
1859     p1++; // past the "@"
1860     int n = *p1++ - '0';
1861     p1++; // past the "@"
1862 //@@    printf("Number of items in box = %d\n", n);
1863     FXString labels[9], initcontents[9];
1864     for (int i=0; i<9; i++) labels[i] = initcontents[i] = "";
1865     p = p1;
1866     for (int i=0; i<n; i++)
1867     { for (p1=p; *p1!='@' && *p1!=':'; p1++)
1868       {}
1869       labels[i] = FXString(p, p1-p);
1870       p = p1+1;
1871       while (*p1!='@') p1++;
1872       if (p1>p) initcontents[i] = FXString(p, p1-p);
1873       p = p1+1;
1874 //@@      printf("labels[%d] = <%s>, initcontents[%d] = <%s>\n",
1875 //@@        i, labels[i].text(), i, initcontents[i].text());
1876     }
1877 //@@    printf("Residual p = <%s>\n", p);
1878     FXReduceInputDialog query(this,
1879         caption,
1880         n,
1881         labels);
1882     for (int i=0; i<n; i++)
1883       query.setText(i, initcontents[i]);
1884     if (query.execute(PLACEMENT_OWNER))
1885     { setFocus();
1886 //@@ printf("Accepted\n");
1887 //@@ for (int i=0; i<n; i++)
1888 //@@   printf("Result %d = <%s>\n", i, query.getText(i).text());
1889       if (isEditable())
1890       { killSelection();
1891         setInputText("", 0);
1892         while (*p != 0)
1893         { while (*p != '$' && *p != 0)
1894           { appendStyledText(p, 1, STYLE_INPUT);
1895             p++;
1896           }
1897           if (*p == '$')
1898           { n = *++p - '0' - 1;
1899             p++;
1900             appendStyledText(query.getText(n), STYLE_INPUT);
1901           }
1902         }
1903         onCmdInsertNewline(c, s, ptr);
1904       }
1905       else
1906       { while (*p != 0)
1907         { while (*p != '$' && *p != 0)
1908           { char b[4];
1909             b[0] = *p++;
1910             b[1] = 0;
1911             string_ahead(b);
1912           }
1913           if (*p == '$')
1914           { n = *++p - '0' - 1;
1915             p++;
1916             string_ahead(query.getText(n).text());
1917           }
1918         }
1919         string_ahead("\";\n");
1920       }
1921     }
1922     setFocus();   // I am uncertain, but without this I lose focus...
1923     return 1;
1924 }
1925 
1926 #ifndef WIN32
1927 #if !defined MACINTOSH || !defined MAC_FRAMEWORK
1928 
1929 // On Windows I can browse an HTML file using the user's default browser by
1930 // invoking the "ShellExececute" function. On other systems I need to know
1931 // what browser to use, and there is probably no global concept of the
1932 // "preferred" one. So the first time a user tries to access help, or if they
1933 // use a menu entry on the HELP menu, a dialog box for selecting between a
1934 // number of browsers (plus an option for the user to type in a custom name)
1935 // will appear, amd the information so gained gets recorded for future uses.
1936 //
1937 
1938 #define NBROWSERS 10
1939 
1940 class FXAPI BrowserBox : public FXDialogBox
1941 {
1942     FXDECLARE(BrowserBox)
1943 public:
1944     BrowserBox(FXApp *, const char *p);
1945     BrowserBox();
1946     void addbutton(FXVerticalFrame *v, const char *name, const char *d);
1947     long onButton(FXObject *,FXSelector,void *);
1948     long onText(FXObject *,FXSelector,void *);
1949     int nbr;
1950     FXRadioButton *choices[NBROWSERS];
1951     FXTextField *text;
1952     char data[40];
1953     const char *path;
1954 };
1955 
1956 FXDEFMAP(BrowserBox) BrowserBoxMap[] =
1957 {
1958     FXMAPFUNC(SEL_COMMAND,      FXDialogBox::ID_LAST, BrowserBox::onButton),
1959     FXMAPFUNC(SEL_COMMAND,      FXDialogBox::ID_LAST+1, BrowserBox::onText),
1960 };
1961 
FXIMPLEMENT(BrowserBox,FXDialogBox,BrowserBoxMap,ARRAYNUMBER (BrowserBoxMap))1962 FXIMPLEMENT(BrowserBox, FXDialogBox, BrowserBoxMap, ARRAYNUMBER(BrowserBoxMap))
1963 
1964 static int file_is_executable(char *filename)
1965 {
1966     struct stat buf;
1967     if (*filename == 0) return 0;
1968     if (stat(filename, &buf) == -1) return 0;
1969 #ifndef S_IXUSR
1970     return 1;
1971 #else
1972     return (buf.st_mode & S_IXUSR);
1973 #endif
1974 }
1975 
1976 
addbutton(FXVerticalFrame * v,const char * name,const char * d)1977 void BrowserBox::addbutton(FXVerticalFrame *v, const char *name, const char *d)
1978 {
1979     char menuname[256];
1980 // I will start by seeing if I can find the named browser in my PATH
1981     const char *p = path;
1982     int found = 0;
1983     while (*p != 0)
1984     {   int j = 0;
1985         while (*p!=0 && *p!=':')
1986         {   if (j < 240) menuname[j++] = *p;
1987             p++;
1988         }
1989         menuname[j++] = '/';
1990         strcpy(&menuname[j], name);
1991         if (file_is_executable(menuname))
1992         {   found = 1;
1993             break;
1994         }
1995         if (*p!=0) p++;
1996     }
1997     if (!found) return;
1998     menuname[0] = '&';
1999     strcpy(menuname+1, name);
2000     menuname[1] = toupper(menuname[1]);
2001     choices[nbr & 0xff] =
2002         new FXRadioButton(v, menuname, this, FXDialogBox::ID_LAST);
2003     if (strcmp(name, d)==0)
2004     {   choices[nbr & 0xff]->setCheck();
2005         nbr += 0x100;  /* flag to say that a default has been set */
2006     }
2007     nbr++;
2008 }
2009 
BrowserBox(FXApp * a,const char * p)2010 BrowserBox::BrowserBox(FXApp *a, const char *p) :
2011             FXDialogBox(a, "Choose your browser")
2012 {
2013     strcpy(data, p);
2014     FXVerticalFrame *v =
2015         new FXVerticalFrame(this, LAYOUT_FILL_X|LAYOUT_FILL_Y);
2016 // Elsewhere in parts of my code I conditionalise getenv() to force the
2017 // name to upper case when under Windows and to allow for some (ancient?)
2018 // variants on Unix where it takes two arguments. I will not worry about
2019 // either of those issues here.
2020     path = getenv("PATH");
2021     int i;
2022     for (i=0; i<NBROWSERS; i++)
2023         choices[i] = NULL;
2024     nbr = 0;
2025     addbutton(v, "firefox", p);
2026     addbutton(v, "midori", p);
2027     addbutton(v, "iceweasel", p);
2028     addbutton(v, "safari", p);
2029     addbutton(v, "galeon", p);
2030     addbutton(v, "konqueror", p);
2031     addbutton(v, "mozilla", p);
2032     addbutton(v, "netscape", p);
2033     addbutton(v, "opera", p);
2034     choices[nbr & 0xff] = new FXRadioButton(v, "&User:", this, FXDialogBox::ID_LAST);
2035     FXHorizontalFrame *h0 =
2036         new FXHorizontalFrame(v,
2037             LAYOUT_TOP|LAYOUT_LEFT|LAYOUT_FILL_X|PACK_UNIFORM_WIDTH);
2038     new FXLabel(h0, "User command to launch browser:");
2039     text = new FXTextField(h0, 32, this, FXDialogBox::ID_LAST+1);
2040     if (nbr <= 0xff)
2041     {   choices[nbr]->setCheck();
2042         text->setText(p);
2043         strcpy(data, p);
2044     }
2045     nbr &= 0xff;
2046     FXHorizontalFrame *h1 =
2047         new FXHorizontalFrame(v,
2048             LAYOUT_TOP|LAYOUT_LEFT|LAYOUT_FILL_X|PACK_UNIFORM_WIDTH);
2049     new FXButton(h1, "&OK", NULL, this, FXDialogBox::ID_ACCEPT,
2050         BUTTON_INITIAL|BUTTON_DEFAULT|FRAME_RAISED|FRAME_THICK|
2051         LAYOUT_TOP|LAYOUT_LEFT|LAYOUT_CENTER_X);
2052     new FXButton(h1, "&Cancel", NULL, this, FXDialogBox::ID_CANCEL,
2053         BUTTON_INITIAL|BUTTON_DEFAULT|FRAME_RAISED|FRAME_THICK|
2054         LAYOUT_TOP|LAYOUT_LEFT|LAYOUT_CENTER_X);
2055 }
2056 
BrowserBox()2057 BrowserBox::BrowserBox() : FXDialogBox()
2058 {
2059 }
2060 
onButton(FXObject * a,FXSelector s,void * p)2061 long BrowserBox::onButton(FXObject *a, FXSelector s, void *p)
2062 {
2063     for (int i=0; i<NBROWSERS; i++)
2064     {   if (choices[i] == NULL) continue;
2065         if (a != choices[i]) choices[i]->setCheck(FALSE);
2066         else
2067         {   if (i == nbr)
2068             {   FXString ss = text->getText();
2069                 strcpy(data, ss.text());
2070             }
2071             else
2072             {   FXString ss = choices[i]->getText();
2073                 strcpy(data, ss.text());
2074                 data[0] = tolower(data[0]);
2075             }
2076         }
2077     }
2078     return 1;
2079 }
2080 
onText(FXObject * a,FXSelector s,void * p)2081 long BrowserBox::onText(FXObject *a, FXSelector s, void *p)
2082 {
2083     for (int i=0; i<NBROWSERS; i++)
2084     {   if (choices[i] != NULL)
2085             choices[i]->setCheck(FALSE);
2086     }
2087     choices[nbr]->setCheck();
2088     FXString ss = text->getText();
2089     strcpy(data, ss.text());
2090     return 1;
2091 }
2092 
2093 static char preferred_browser[40];
2094 
selectBrowser(FXRegistry * reg,const char * preferred)2095 static const char *selectBrowser(FXRegistry *reg, const char *preferred)
2096 {
2097     BrowserBox box(application_object,  preferred);
2098     int rc = box.execute(PLACEMENT_OWNER);
2099     if (rc == 1)
2100     {   strncpy(preferred_browser, box.data, 40);
2101         preferred_browser[39] = 0;
2102         preferred = preferred_browser;
2103         reg->writeStringEntry("browser", "preferred", preferred);
2104     }
2105     return preferred;
2106 }
2107 
onCmdSelectBrowser(FXObject * c,FXSelector s,void * ptr)2108 long FXTerminal::onCmdSelectBrowser(FXObject *c, FXSelector s, void *ptr)
2109 {
2110     FXRegistry *reg = &application_object->reg();
2111     const char *preferred = reg->readStringEntry("browser", "preferred");
2112     if (preferred == NULL || *preferred == 0) preferred = "firefox";
2113     selectBrowser(reg, preferred);
2114     setFocus();
2115     return 1;
2116 }
2117 
2118 #endif
2119 #endif
2120 
2121 #if defined MACINTOSH && defined MAC_FRAMEWORK && 0
2122 
2123 // The code FinderLaunch.c as provided by Apple worked a decade ago
2124 // but Apple have by now removed various system library data types and
2125 // entrypoints that it uses and it is not at all obvious whether they have
2126 // published a replacement bit of sample code. So I will just have to
2127 // disable this.
2128 
2129 // The code included here comes from an Apple library, and it has its
2130 // redistribution rights listed in comments at its top.
2131 
2132 #include "FinderLaunch.c"
2133 
2134 #endif
2135 
onCmdHelp(FXObject * c,FXSelector s,void * ptr)2136 long FXTerminal::onCmdHelp(FXObject *c, FXSelector s, void *ptr)
2137 {
2138     UNUSED_ARG(c); UNUSED_ARG(s); UNUSED_ARG(ptr);
2139 // I expect to have a directory called appname.doc in the same place that the
2140 // "appname" executable lives. The directory appname.doc
2141 // should contain a file index.html and a help request will launch a browser
2142 // to inspect that. The user's preferred browser will be recorded in the
2143 // registry in the Unix case, but is dealt with by ShellExecute in the
2144 // Windows case.
2145 #if defined MACINTOSH && defined MAC_FRAMEWORK
2146     char helpFile[256];
2147     sprintf(helpFile, "%s.doc/index.html", fwin_full_program_name);
2148     if (CGDisplayIsActive(CGMainDisplayID()) != 1)
2149     {   FXMessageBox::error(this,
2150                 MBOX_OK, "Manual Browser Launch Needed",
2151                 "Please browser the file %s", helpFile);
2152 
2153     }
2154     else if (fork() == 0)
2155     {
2156 // Attempting to launch a browser in this way seems to cause big trouble
2157 // if you are connected to the Mac via a remote X session. As a really
2158 // heavy-handed way to arrange that this trouble does not spill over and
2159 // disrupt anything else that I am doing I will run the "delicate" procedure
2160 // in a separate fork where ANYTHING that happens should be well isolated.
2161 // The earlier check in CGDisplayIsActive() is supposed to have filtered
2162 // trouble away, but I am going to try to be super careful!
2163         int n = FinderLaunch(helpFile);
2164         if (n != noErr)
2165         {   FXMessageBox::error(this,
2166                 MBOX_OK, "Error",
2167                 "Sorry - help file not available (%s:%d)", helpFile,n);
2168         }
2169         exit(0);
2170     }
2171 #else
2172 #ifdef WIN32
2173     char helpFile[LONGEST_LEGAL_FILENAME];
2174     char *q;
2175 //  GetModuleFileName(NULL, helpFile, 250);
2176 // Using GetModuleFileName causes confusion with the ACN scheme where a
2177 // multi-purpose stub loads the exact version of Reduce that you really
2178 // want...
2179     sprintf(helpFile, "%s/reduce.doc", programDir);
2180     for (q=helpFile; *q!=0; q++)
2181         if (*q == '/') *q = '\\';
2182 // printf("\n>>> %s <<<\n", helpFile); fflush(stdout);
2183     HINSTANCE n = ShellExecute(NULL,            // parent window for popup
2184                               "open",           // verb
2185                               "index.html",     // file to open
2186                               NULL,             // parameters to pass
2187                               helpFile,         // directory to run in
2188                               SW_SHOWNORMAL);
2189     if (n <= (HINSTANCE)32)
2190         FXMessageBox::error(this, MBOX_OK, "Error",
2191                             "Sorry - help file not available (%p)", n);
2192 
2193 #else
2194     FXRegistry *reg = &application_object->reg();
2195     const char *preferred = reg->readStringEntry("browser", "preferred");
2196     if (preferred == NULL || *preferred == 0)
2197         preferred = selectBrowser(reg, "firefox");
2198     char helpFile[256];
2199     sprintf(helpFile, "file://%s/%s.doc/index.html", programDir, programName);
2200 // For non-windows the browsers I might imagine include
2201 //      netscape, mozilla, opera, firebird, konqueror, galeon, ...
2202 // I will try these in turn. It is probably a politically delicate issue
2203 // which one I try first! If I do not find any then just nothing will
2204 // happen.
2205     if (fork() == 0)
2206     {   const char *browsers[] = {
2207             NULL,
2208             "opera",
2209             "firefox",
2210             "midori",
2211             "safari",
2212             "mozilla",
2213             "konqueror",
2214             "galeon",
2215             "netscape",
2216             NULL};
2217 // I put the user's preferred browser as the first to try, but if that
2218 // does not work I try a further bunch.
2219         browsers[0] = preferred;
2220         const char **b = browsers;
2221 // As soon as one of these calls to execlp succeeds in launching a browser
2222 // I lose all control, and in particular there is no risk of me ever launching
2223 // two browsers.
2224         while (*b != NULL)
2225         {   execlp(*b, *b, helpFile, NULL);
2226             b++;
2227         }
2228 // If NONE of the browsers manage to launch I get here. But note that I am in
2229 // a fork, so if I just exit the attempt to browse help will terminate fairly
2230 // cleanly. I might quite like to pop up a dialog box reporting failure but
2231 // to feel save on that I feel I ought to agree with the main fork. Too much
2232 // hassle!!
2233         fflush(stdout);
2234         exit(1);
2235     }
2236 #endif
2237 #endif
2238     setFocus();
2239     return 1;
2240 }
2241 
onCmdAbout(FXObject * c,FXSelector s,void * ptr)2242 long FXTerminal::onCmdAbout(FXObject *c, FXSelector s, void *ptr)
2243 {
2244 // each line of about_box information is limited to 40 chars.
2245     char msg[5*40+8];
2246     UNUSED_ARG(c); UNUSED_ARG(s); UNUSED_ARG(ptr);
2247     sprintf(msg,
2248         "%s\n%s\n%s\n%s\n%s",
2249         about_box_description,
2250         about_box_rights_1,
2251         about_box_rights_2,
2252         about_box_rights_3,
2253         about_box_rights_4);
2254     FXMessageBox about(this,
2255         about_box_title,
2256         msg,
2257         main_window->getIcon(),
2258         MBOX_OK|DECOR_TITLE|DECOR_BORDER);
2259     about.execute(PLACEMENT_OWNER);
2260     setFocus();
2261     return 1;
2262 }
2263 
forceWidth()2264 int FXTerminal::forceWidth()
2265 {
2266     int dx = getDefaultWidth()+FXScrollArea::vertical->getDefaultWidth();
2267     if (dx != main_window->getWidth())
2268     {   int dy = main_window->getHeight();
2269         main_window->getShell()->resize(dx, dy);
2270     }
2271     return dx;
2272 }
2273 
setFont(FXFont * font0)2274 void FXTerminal::setFont(FXFont *font0)
2275 {
2276     FXText::setFont(font0);
2277     lineSpacing = font0->getFontSpacing();
2278     setVisibleColumns(80);  // but do not change rows..
2279     recalc();
2280     int dx = getDefaultWidth()+FXScrollArea::vertical->getDefaultWidth();
2281     int dy = main_window->getHeight();
2282     main_window->getShell()->resize(dx, dy);
2283 // I will force at least the top left of my window to be visible, and if I can
2284 // I will make it all visible.
2285     int x = main_window->getX(), y = main_window->getY();
2286     if (x < 0) dx = -x;       // need to move right
2287     else if (x+dx>rootWidth)  // need to move left
2288     {   dx = rootWidth - x - dx;
2289         if (x + dx < 0) dx = -x; // but try to leave left edge visible still
2290     }
2291     else dx = 0;
2292 // The next line used to say (y<0) but so that if the window started strictly
2293 // above the screen it got moved down. I now ensure that after the reset the
2294 // window is at least 4 pixels down from the top of the screen. I do this
2295 // because on Linux at least the "main_window" here does not include the
2296 // title bar and other decorations and I need at least a small amount of
2297 // title bar visible if I am to be able to drag the window to re-position it.
2298 // But I am happy to guarantee just 4 pixels not the whole lot...
2299 #define TOPGAP 5
2300     if (y < TOPGAP) dy = TOPGAP - y;
2301     else if (y+dy>rootHeight)
2302     {   dy = rootHeight - y - dy;
2303         if (y + dy < 0) dy = -y;
2304     }
2305     else dy = 0;
2306     if (dx != 0 || dy != 0) main_window->move(x+dx, y+dy);
2307 // Since the window width has probably changed I should adjust the size of my
2308 // maths font so that thing still fit neatly. However it happens that I
2309 // will call setFont while creating an FXTerminal before the showMath module
2310 // has been initialised, and so I must not try to meddle with it too
2311 // early.
2312     if (showmathInitialised)
2313         changeMathFontSize(application_object, -getDefaultWidth());
2314 // The above line has some depths! I insist that if the new set of
2315 // fonts that I want can not be opened that the old lot remain available,
2316 // because otherwise attempts to update the display would crash horribly,
2317 // and I do not have an easy recipe for switching off reliance on fancy
2318 // fonts part way through a run! So failure to open a font when the main
2319 // font size changes will be BAD but its badness will be limited to
2320 // having formulae remain the same size, and future attempts to change font
2321 // size will re-try.
2322     update();       // major changes and so I should refresh everything
2323     makePositionVisible(cursorpos);
2324 
2325 // Now I update the registry so that when I next start this application
2326 // the same font and screen configuration will apply
2327     FXRegistry *reg = &(application_object->reg());
2328     reg->writeStringEntry("screen", "fontname",  font0->getName().text());
2329     int fs = font0->getSize();
2330     fs = 10*((fs + 5)/10); // Round to a multiple of 10
2331     if (fs < 80) fs = 80;
2332     else if (fs > 200) fs = 200;
2333     else if (fs > 120 &&
2334              ((fs/10) & 1) != 0) fs += 10;
2335     reg->writeIntEntry("screen", "fontsize",     fs);
2336     reg->writeIntEntry("screen", "fontweight",   font0->getWeight());
2337     reg->writeIntEntry("screen", "fontslant",    font0->getSlant());
2338     reg->writeIntEntry("screen", "fontencoding", font0->getEncoding());
2339     reg->writeIntEntry("screen", "fontsetwidth", font0->getSetWidth());
2340     reg->writeIntEntry("screen", "fonthints",    font0->getHints());
2341 }
2342 
2343 // Now handlers for the things that get signalled from my worker thread
2344 
onIPC(FXObject * c,FXSelector s,void * ptr)2345 long FXTerminal::onIPC(FXObject *c, FXSelector s, void *ptr)
2346 {
2347     char pipe_data[1];
2348     UNUSED_ARG(c); UNUSED_ARG(s); UNUSED_ARG(ptr);
2349 #ifdef WIN32
2350     pipe_data[0] = event_code;
2351 #else
2352     if (read(pipedes[PIPE_READ_PORT], pipe_data, 1) != 1)
2353     {   fprintf(stderr, "Fatal error attempting to read from pipe\n");
2354         application_object->exit(1);
2355         exit(1);
2356     }
2357 #endif
2358     switch (pipe_data[0])
2359     {
2360 default:
2361         fprintf(stderr, "Fatal error: unknown IPC code %d\n", pipe_data[0]);
2362         application_object->exit(1);
2363         exit(1);
2364 case WORKER_EXITING:
2365         return requestWorkerExiting();
2366 case FLUSH_BUFFER:
2367         return requestFlushBuffer();
2368 case REQUEST_INPUT:
2369         return requestRequestInput();
2370 case SET_PROMPT:
2371         return requestSetPrompt();
2372 case REFRESH_TITLE:
2373         return requestRefreshTitle();
2374 case SHOW_MATH:
2375         return requestShowMath();
2376 case SET_MENUS:
2377         return requestSetMenus();
2378 case REFRESH_SWITCHES:
2379         return requestRefreshSwitches();
2380 case MINIMIZE_MAIN:
2381         main_window->minimize();
2382         return 1;
2383 case RESTORE_MAIN:
2384         main_window->restore();
2385         return 1;
2386     }
2387 }
2388 
requestWorkerExiting()2389 long FXTerminal::requestWorkerExiting()
2390 {
2391 #ifdef WIN32
2392     DWORD retval;
2393     switch (WaitForSingleObject(thread1, 10000))
2394     {
2395 case WAIT_OBJECT_0:
2396         if (!GetExitCodeThread(thread1, &retval)) retval = 1;
2397         CloseHandle(thread1);
2398         break;
2399 default:
2400         retval = 1;
2401     }
2402 #else
2403     void *p;
2404     int retval;
2405     if (!pthread_join(thread1, &p)) retval = *(int *)p;
2406     else retval = 1 /* joining failed - default to return code of 1 */;
2407 #endif
2408 // I am now ready to stop. By calling FXApp::exit I should get FOX closed
2409 // down tidily, with the registry written back. There is some slight
2410 // uncertainty as to whether FXApp::exit does or should actually quit
2411 // the whole application or just the FOX activity, so I will arrange that
2412 // if it does return then I will stop yet more enthusiastically. And
2413 // to keep compilers from moaning I still make this procedure look as if
2414 // it can return 1 as its result!
2415     application_object->exit(retval);
2416     exit(retval);
2417     return 1;
2418 }
2419 
2420 // Since I want to keep things consistent I think I might like to document
2421 // what I expect key-strokes to do:
2422 
2423 //============================================================================
2424 //                         KEYBOARD HANDLING
2425 //
2426 //
2427 // Key-bindings that I hope to make work in both terminal and windowed mode,
2428 // on both Unix/Linux, Microsoft Windows and the Macintosh.
2429 //
2430 // Note that ALT can be achieved either by holding the ALT key at the
2431 // same time as the listed key, or by pressing ESC before the key.
2432 //
2433 // ALT takes priority over SHIFT, and Control takes priority over ALT so
2434 // that a character is only treated as having one attribute. If it has none
2435 // it just inserts itself.
2436 //
2437 // Where I put a "-" in this table it means that I do not define the meaning
2438 // of the keystroke. In the short term at least that will either cause the
2439 // keystroke to be ignored, inserted, or treated the same way as the
2440 // corresponding character without Control or ALT. In the longer term I may
2441 // assign behaviours to some of those keys. I also want to reserve the
2442 // possibility of making keys with both Control and ALT have yet different
2443 // effects.
2444 //
2445 //Key   Control              ALT
2446 //
2447 // @    Set Mark             -                    (note this key is not
2448 //                                                 always detected!)
2449 // A    To beginning         [Package load menu]  (also Home key)
2450 // B    Back char            Back word            (also left arrow key)
2451 // C    ^C interrupt         Capitalise word
2452 // D    Delete forward       Delete word          (also the Delete key)
2453 //      Also ^D before any other input on a line sends EOF
2454 // E    To end               [Edit menu]          (also End key)
2455 // F    Forward char         Foward word          (also right arrow key)
2456 // G    ^G backtrace         enter Break Loop     <<also escape search mode>>
2457 //
2458 // H    Delete back          Del word back
2459 // I    TAB                  [File menu]          (also TAB key)
2460 // J    Newline              -
2461 // K    Kill line            -
2462 // L    Clear screen         Lowercase word
2463 // M    Newline              -
2464 // N    Next history         Search history next  (also down arrow key)
2465 // O    Discard output       [Font menu]
2466 //
2467 // P    Previous history     Search history prev  (also up arrow key)
2468 // Q    Resume output        -
2469 // R    Redisplay            [Break menu]
2470 // S    Pause output         [Switch menu]
2471 // T    Transpose chars      -
2472 // U    <undo?/escape srch>  Uppercase word
2473 // V    Quoted insert        -
2474 // W    Del Word back        Copy region
2475 //
2476 // X    eXtended command     Obey command
2477 // Y    Yank (=Paste)        -
2478 // Z    Stop execution       -
2479 // [    =ESC: Meta prefix    -
2480 // \    Quit                 -
2481 // ]    -                    -
2482 // _    -                    Copy previous word
2483 // ^    Reinput              -
2484 //
2485 //
2486 // Arrow etc keys...
2487 //
2488 // ->   forward char/word
2489 // <-   backwards char/word
2490 // ^    history prev/search history prev
2491 // v    history next/search history next
2492 // home start line/start buffer
2493 // end  end line/end buffer
2494 //
2495 //
2496 // The items shown as menus behave as follows:
2497 //
2498 // ALT-E C cut
2499 //       O copy
2500 //       P paste
2501 //       R reinput
2502 //       A select all
2503 //       L clear
2504 //       D redraw
2505 //       H home
2506 //       E end
2507 // ALT-I R read
2508 //       S save
2509 //       L save selected text
2510 //       P print
2511 //       N print selected text
2512 //       X exit
2513 // ALT-M   &Module menu shortcut - load a REDUCE module
2514 // ALT-O F select new font
2515 //       R reset to default font
2516 //       W reset font and window to default
2517 // Alt-R C as ^C, interrupt current computation
2518 //       D as ^O, discard pending output
2519 //       G as ^G, interrupt & backtrace current computation
2520 //       P as ^S, pause output
2521 //       R as ^Q, resume output
2522 //       X as ^X, stop current computation
2523 // ALT-S   &Switch menu shortcut - flip a REDUCE switch
2524 //
2525 //
2526 //============================================================================
2527 
2528 
onKeyPress(FXObject * c,FXSelector s,void * ptr)2529 long FXTerminal::onKeyPress(FXObject *c, FXSelector s, void *ptr)
2530 {
2531     int ch;
2532     FXEvent *event = (FXEvent *)ptr;
2533     const wchar_t *history_string = L"";
2534     if (!isEnabled()) return 0;
2535     switch (event->code)
2536     {
2537 // Here are some keys that I just want to ignore..
2538 case KEY_Shift_L:
2539 case KEY_Shift_R:
2540 case KEY_Control_L:
2541 case KEY_Control_R:
2542 case KEY_Caps_Lock:
2543 case KEY_Shift_Lock:
2544 case KEY_Meta_L:
2545 case KEY_Meta_R:
2546 case KEY_Alt_L:
2547 case KEY_Alt_R:
2548 case KEY_Super_L:
2549 case KEY_Super_R:
2550 case KEY_Hyper_L:
2551 case KEY_Hyper_R:
2552 case KEY_VoidSymbol:  // used when just ALT (say) is pressed and a
2553                       // key-repetition event is generated.
2554         return 1;
2555     }
2556 // If a previous keystroke had been ESC then I act as if this one
2557 // had ALT combined with it. I will cancel the pending ESC on various
2558 // menu things as well as here. Note that this conversion copes with
2559 // local editing combinations such as ALT-D, but ESC-I does not activate
2560 // a menu the way that ALT-I would have.
2561     if (keyFlags & ESC_PENDING)
2562     {   event->state |= ALTMASK;
2563         keyFlags &= ~ESC_PENDING;
2564     }
2565 // I will let the Search Pending code drop through in cases where the
2566 // keystroke should be treated as a return to "ordinary" processing. Also
2567 // note that I only expect to find myself in search mode in cases where the
2568 // system is waiting for input.
2569     if (searchFlags != 0)
2570     {   int savehistorynum, r, ls;
2571         switch (event->code)
2572         {
2573     case KEY_h:
2574             if (!(event->state & CONTROLMASK)) goto defaultlabelsearch;
2575             if (event->state & ALTMASK) goto defaultlabelsearch;
2576             // drop through to BackSpace
2577     case KEY_BackSpace:
2578 // When I delete a character from a search string I will pop the active
2579 // history line back to the first one found when the remaining string
2580 // was searched for. If I delete back to nothing I will leave the input
2581 // line blank.
2582             if (SEARCH_LENGTH == 0)
2583             {   getApp()->beep();
2584                 searchFlags = 0;   // cancel searching before it started!
2585                 killSelection(TRUE);
2586                 return 1;
2587             }
2588             searchFlags--;
2589             if (SEARCH_LENGTH == 0)
2590             {   searchFlags = 0;   // delete the one char in the search string
2591                 killSelection(TRUE);
2592                 setInputText("", 0);
2593                 return 1;
2594             }
2595             historyNumber = searchStack[SEARCH_LENGTH];
2596 // The "trySearch" here really really ought to succeed since I have reverted
2597 // to a history line where it succeeded before. I do it again here so I can
2598 // find out where, on that line, the match was so I can establish it as a
2599 // selection.
2600             startMatch = trySearch();
2601             history_string = input_history_get(historyNumber);
2602 // ought not to return NULL here!
2603             ls = setInputText(history_string, std::wcslen(history_string));
2604 // To give a visual indication of what I have found I will select the match,
2605 // which will leave it highlighted on the display. I must remember to kill
2606 // my selection every time I exit search mode!
2607             killSelection(TRUE);
2608             setAnchorPos(ls+startMatch);
2609             extendSelection(ls+startMatch+SEARCH_LENGTH, SELECT_CHARS, TRUE);
2610             setCursorPos(ls+startMatch+SEARCH_LENGTH, TRUE);
2611             return 1;
2612     case KEY_p:
2613             if (!(event->state & ALTMASK)) goto defaultlabelsearch;
2614     case KEY_Up:
2615             searchFlags &= ~SEARCH_FORWARD;
2616             searchFlags |= SEARCH_BACKWARD;
2617             if (historyNumber <= historyFirst)
2618             {   getApp()->beep();  // already on last possible place
2619                 return 1;
2620             }
2621             savehistorynum = historyNumber;
2622             historyNumber--;
2623             r = trySearch();
2624             if (r == -1)
2625             {   historyNumber = savehistorynum;
2626                 getApp()->beep();
2627                 return 1;
2628             }
2629             startMatch = r;
2630             history_string = input_history_get(historyNumber);
2631             ls = setInputText(history_string, std::wcslen(history_string));
2632 // To give a visual indication of what I have found I will select the match,
2633 // which will leave it highlighted on the display. I must remember to kill
2634 // my selection every time I exit search mode!
2635             killSelection(TRUE);
2636             setAnchorPos(ls+startMatch);
2637             extendSelection(ls+startMatch+SEARCH_LENGTH, SELECT_CHARS, TRUE);
2638             setCursorPos(ls+startMatch+SEARCH_LENGTH, TRUE);
2639             return 1;
2640     case KEY_n:
2641             if (!(event->state & ALTMASK)) goto defaultlabelsearch;
2642     case KEY_Down:
2643             searchFlags |= SEARCH_FORWARD;
2644             searchFlags &= ~SEARCH_BACKWARD;
2645             if (historyNumber >= historyLast)
2646             {   getApp()->beep();
2647                 return 1;
2648             }
2649             savehistorynum = historyNumber;
2650             historyNumber++;
2651             r = trySearch();
2652             if (r == -1)
2653             {   historyNumber = savehistorynum;
2654                 getApp()->beep();
2655                 return 1;
2656             }
2657             startMatch = r;
2658             history_string = input_history_get(historyNumber);
2659             ls = setInputText(history_string, std::wcslen(history_string));
2660 // To give a visual indication of what I have found I will select the match,
2661 // which will leave it highlighted on the display. I must remember to kill
2662 // my selection every time I exit search mode!
2663             killSelection(TRUE);
2664             setAnchorPos(ls+startMatch);
2665             extendSelection(ls+startMatch+SEARCH_LENGTH, SELECT_CHARS, TRUE);
2666             setCursorPos(ls+startMatch+SEARCH_LENGTH, TRUE);
2667             return 1;
2668 // I detect ^U here and cause it to exit search mode.
2669     case KEY_u:
2670             if (!(event->state & CONTROLMASK)) goto defaultlabelsearch;
2671             searchFlags = 0;
2672             killSelection(TRUE);
2673             return 1;
2674     case KEY_bracketleft:
2675             if (!(event->state & CONTROLMASK)) goto defaultlabelsearch;
2676             if (event->state & ALTMASK) goto defaultlabelsearch;
2677             // drop through to Escape
2678     case KEY_Escape:       // ctl-[
2679             keyFlags ^= ESC_PENDING;
2680             return 1;
2681     default:
2682     defaultlabelsearch:
2683 // I suggest "^@" as a sensible character to type to exit from searching.
2684 // Acting on it just "sets the mark" which is typically harmless.
2685             if ((event->code & ~0xff) != 0 ||
2686                 event->text[1] != 0 ||
2687                 event->state & (CONTROLMASK | ALTMASK))
2688             {   searchFlags = 0;
2689                 killSelection(TRUE);
2690                 break;
2691             }
2692 // here I should have a single simple character
2693             ch = event->text[0];
2694 // and I will filter out control characters... except tab!
2695             if ((ch & 0xff) < 0x20 && (ch & 0xff) != '\t')
2696             {   searchFlags = 0;
2697                 killSelection(TRUE);
2698                 break;
2699             }
2700 // Here I have a further printable character to add to the search
2701 // pattern. If ignore it if the search string has become ridiculously long
2702 // to avoid a buffer overflow.
2703             if (SEARCH_LENGTH > 250)
2704             {   getApp()->beep();
2705                 return 1;
2706             }
2707             searchString[SEARCH_LENGTH] = ch;
2708             searchStack[SEARCH_LENGTH] = historyNumber;
2709             searchFlags++;
2710             savehistorynum = historyNumber;
2711             r = trySearch();
2712             if (r == -1)
2713             {   historyNumber = savehistorynum;
2714                 getApp()->beep();
2715                 searchFlags--;
2716                 return 1;
2717             }
2718             startMatch = r;
2719             history_string = input_history_get(historyNumber);
2720             ls = setInputText(history_string, std::wcslen(history_string));
2721 // To give a visual indication of what I have found I will select the match,
2722 // which will leave it highlighted on the display. I must remember to kill
2723 // my selection every time I exit search mode!
2724             killSelection(TRUE);
2725             setAnchorPos(ls+startMatch);
2726             extendSelection(ls+startMatch+SEARCH_LENGTH, SELECT_CHARS, TRUE);
2727             setCursorPos(ls+startMatch+SEARCH_LENGTH, TRUE);
2728             return 1;
2729         }
2730     }
2731 // If the very first character I see is a "^D" it is to be taken as EOF
2732 // rather than as "delete next character".
2733     if (event->code == KEY_d &&
2734         event->state & CONTROLMASK &&
2735         !(keyFlags & ANY_KEYS))
2736     {   setCursorPos(length);
2737 // I force a Control-D character into the buffer and then pretend that
2738 // a newline had also been typed.
2739         FXText::appendText("\004", 1);
2740         return onCmdInsertNewline(this, 0, NULL);
2741     }
2742 // If the very first key I see is "^G" I will raise an exception
2743 // for the user.
2744     if (event->code == KEY_g &&
2745         event->state & CONTROLMASK &&
2746         !(keyFlags & ANY_KEYS)) return onCmdBacktrace(this, 0, NULL);
2747     keyFlags |= ANY_KEYS;
2748     ch = 0x00;
2749     switch (event->code)
2750     {
2751 case KEY_BackSpace:
2752         if (event->state & (CONTROLMASK|ALTMASK))
2753             return editDeleteBackwardWord();
2754         else return editDeleteBackward();
2755 case KEY_End:
2756 case KEY_KP_End:
2757 // Hmmm - still should I extend a selection if an anchor is set?
2758 // END should probably go to the end of the current line, with ALT-END
2759 // going to the end of the last line.
2760         if (event->state & (ALTMASK|CONTROLMASK)) return onCmdEnd(c, s, ptr);
2761         else return editMoveLineEnd();
2762 case KEY_Home:
2763 case KEY_KP_Home:
2764 // OME should probably go to the start of the current active line, with
2765 // ALT-HOME being the thing that goes to top of the screen-buffer.
2766         if (event->state & (ALTMASK|CONTROLMASK)) return onCmdHome(c, s, ptr);
2767         else return editMoveLineStart();
2768 case KEY_Left:
2769         if (event->state & (CONTROLMASK|ALTMASK)) return editPrevWord();
2770         else return editPrevChar();
2771 case KEY_Right:
2772         if (event->state & (CONTROLMASK|ALTMASK)) return editNextWord();
2773         else return editNextChar();
2774 case KEY_Up:
2775         if (event->state & CONTROLMASK || (options & TEXT_READONLY))
2776             return onCmdCursorUp(this, 0, NULL);
2777         else if (event->state & ALTMASK)
2778             return editSearchHistoryPrev();
2779         else return editHistoryPrev();
2780 case KEY_Down:
2781 // If you are not waiting for input then cursor up and down just move you up
2782 // and down! If you are waiting for input then Control can be used to break
2783 // you out from the input-line...
2784         if (event->state & CONTROLMASK || (options & TEXT_READONLY))
2785             return onCmdCursorDown(this, 0, NULL);
2786         else if (event->state & ALTMASK)
2787             return editSearchHistoryNext();
2788         else return editHistoryNext();
2789 case KEY_Return:
2790 case KEY_Linefeed:
2791 // I always act as if newlines were typed at the very end of the input.
2792         setCursorPos(length);
2793         ch = '\n';
2794         break;
2795 case KEY_Tab:
2796 case KEY_KP_Tab:
2797         ch = '\t';
2798         break;
2799 case KEY_at:
2800 // As a default sort of behaviour if my chart of actions shows a key doing
2801 // something interesting with CONTROL but does not specify what happens
2802 // with ALT, I think I will tend to make ALT-x behave like ^x.
2803         if (event->state & (CONTROLMASK|ALTMASK)) return editSetMark();
2804         else goto defaultlabel;
2805 case KEY_a:
2806         if (event->state & (CONTROLMASK|ALTMASK)) return editMoveLineStart();
2807         else goto defaultlabel;
2808 case KEY_b:
2809         if (event->state & CONTROLMASK) return editPrevChar();
2810         else if (event->state & ALTMASK) return editPrevWord();
2811         else goto defaultlabel;
2812 case KEY_c:
2813         if ((event->state & ALTMASK) &&
2814             (event->state & CONTROLMASK)) exit(0);
2815         else if (event->state & CONTROLMASK) return editBreak();
2816         else if (event->state & ALTMASK) return editCapitalize();
2817         else goto defaultlabel;
2818 case KEY_Delete:
2819         if (event->state & (CONTROLMASK | ALTMASK)) return editDeleteForwardWord();
2820         break;
2821 case KEY_d:
2822 // Here I may want to arrange that if the current input-buffer is empty
2823 // then ^D causes and EOF to be returned. Well yes, I have arranged that
2824 // elsewhere so I only get here if the user has typed some chars already.
2825         if (event->state & CONTROLMASK) return editDeleteForward();
2826         else if (event->state & ALTMASK) return editDeleteForwardWord();
2827         else goto defaultlabel;
2828 case KEY_e:
2829         if (event->state & CONTROLMASK) return editMoveLineEnd();
2830 // ALT-e enters the EDIT menu: this is handled by having the menu
2831 // registered elsewhere.
2832         else goto defaultlabel;
2833 case KEY_f:
2834         if (event->state & CONTROLMASK) return editNextChar();
2835         else if (event->state & ALTMASK) return editNextWord();
2836         else goto defaultlabel;
2837 case KEY_g:
2838         if ((event->state & ALTMASK) &&
2839             (event->state & CONTROLMASK)) return editBreakLoop();
2840         else if (event->state & CONTROLMASK) return editBacktrace();
2841         else goto defaultlabel;
2842 case KEY_h:
2843         if (event->state & CONTROLMASK) return editDeleteBackward();
2844         else if (event->state & ALTMASK) return editDeleteBackwardWord();
2845         else goto defaultlabel;
2846 case KEY_i:
2847 // ^I is a TAB
2848 // ALT-i enters the FILE menu
2849         if (event->state & CONTROLMASK) ch = '\t';
2850         else goto defaultlabel;
2851 case KEY_j:
2852         if (event->state & (CONTROLMASK | ALTMASK)) return editNewline();
2853         else goto defaultlabel;
2854 case KEY_k:
2855         if (event->state & (CONTROLMASK | ALTMASK)) return editCutLine();
2856         else goto defaultlabel;
2857 case KEY_l:
2858 // ^L will be CLEAR SCREEN
2859         if (event->state & ALTMASK) return editLowercase();
2860         else goto defaultlabel;
2861 case KEY_m:
2862         if (event->state & CONTROLMASK) return editNewline();
2863 // ALT-m enters the MODULE menu
2864         else goto defaultlabel;
2865 case KEY_n:
2866         if (options & TEXT_READONLY)
2867         {   if (event->state & CONTROLMASK)
2868                 return onCmdCursorDown(this, 0, NULL);
2869         }
2870         else
2871         {   if (event->state & CONTROLMASK) return editHistoryNext();
2872             else if (event->state & ALTMASK) return editSearchHistoryNext();
2873         }
2874         goto defaultlabel;
2875 case KEY_o:
2876 // ^O will be purge output.
2877 //    I hope that by making ^O, ^S and ^Q menu shortcuts they will get
2878 //    acted upon whether I am waiting for input or not.
2879 // ALT-O enters the FONT menu
2880         goto defaultlabel;
2881 case KEY_p:
2882         if (options & TEXT_READONLY)
2883         {   if (event->state & CONTROLMASK)
2884                 return onCmdCursorUp(this, 0, NULL);
2885         }
2886         else
2887         {   if (event->state & CONTROLMASK) return editHistoryPrev();
2888             else if (event->state & ALTMASK) return editSearchHistoryPrev();
2889         }
2890         goto defaultlabel;
2891 case KEY_q:
2892 // ^Q will be RESUME OUTPUT
2893         if (event->state & ALTMASK) return 1; // Ignore ALT-Q
2894         goto defaultlabel;
2895 case KEY_r:
2896         if (event->state & CONTROLMASK) return editRedisplay();
2897 // ALT-r will be the B&reak menu
2898         goto defaultlabel;
2899 case KEY_s:
2900 // ^S should pause output
2901 // ALT-s enters the SWITCH menu
2902         goto defaultlabel;
2903 case KEY_t:
2904         if (event->state & (CONTROLMASK | ALTMASK)) return editTranspose();
2905         else goto defaultlabel;
2906 case KEY_u:
2907         if (event->state & CONTROLMASK) return editUndo();
2908         else if (event->state & ALTMASK) return editUppercase();
2909         else goto defaultlabel;
2910 case KEY_v:
2911 // ^V will be PASTE and is handled as a shortcut
2912         goto defaultlabel;
2913 case KEY_w:
2914 // ^W behaviour is just like ALT-H
2915         if (event->state & CONTROLMASK) return editDeleteBackwardWord();
2916         else if (event->state & ALTMASK) return editCopyRegion();
2917         else goto defaultlabel;
2918 case KEY_x:
2919 // Just what these have to do is a mystery to me at present!
2920 // Well that is an overstatement - what I mean is that I am not yet
2921 // implementing anything!
2922         if (event->state & CONTROLMASK) return editExtendedCommand();
2923         else if (event->state & ALTMASK) return editObeyCommand();
2924         else goto defaultlabel;
2925 case KEY_y:
2926 // ^Y is short for YANK, otherwise known as PASTE
2927         if (event->state & CONTROLMASK) return editPaste();
2928         else if (event->state & ALTMASK) return editRotateClipboard();
2929         else goto defaultlabel;
2930 case KEY_z:
2931 // ^Z is short for SUSPEND
2932         goto defaultlabel;
2933 case KEY_bracketleft:
2934         if (event->state & CONTROLMASK) return editEscape();
2935         else goto defaultlabel;
2936 case KEY_Escape:       // ctl-[
2937 // ESC must have the effect of simulating the ALT property for the following
2938 // character.
2939         return editEscape();
2940 case KEY_backslash:
2941 // ^\ exits the application
2942         goto defaultlabel;
2943 case KEY_bracketright:
2944         goto defaultlabel;
2945 case KEY_asciicircum:
2946         if (event->state & CONTROLMASK) return editReinput();
2947         goto defaultlabel;
2948 case KEY_underscore:
2949         if (event->state & ALTMASK) return editCopyPreviousWord();
2950         goto defaultlabel;
2951 
2952 default:
2953 defaultlabel:
2954 // Any codes over 0xfd00 get sent to FXText here... FOX used key codes
2955 // in this range for IBM3270 emulation, virtual terminal control, some dead
2956 // keys, keypad and function keys and cursor control. In other words things
2957 // that do not display as simple characters. So I just pass these down to
2958 // FXText.
2959         if ((event->code & 0xff00) >= 0xfd00)
2960         {   long rr = FXText::onKeyPress(c, s, ptr);
2961             changeStyle(promptEnd, length-promptEnd, STYLE_INPUT);
2962             return rr;
2963         }
2964 // here I should have a single simple character, even though in UTF-8 it
2965 // may be represented as a multi-byte sequence.
2966         ch = event->text[0];
2967 // and I will filter out control characters...
2968         if ((ch & 0xff) < 0x20) return FXText::onKeyPress(c, s, ptr);
2969         break;
2970     }
2971 // Now I am left with printable characters plus TAB and NEWLINE. If the
2972 // terminal is waiting for input or if CTRL or ALT was associated with
2973 // the key I delegate.
2974 // @@@ I should really try to check so that when I insert a ")", "]" or
2975 // "}" I look for the corresponding opening bracket and flash it. FXText
2976 // has some support for that!
2977     if (isEditable() ||
2978         (event->state & (CONTROLMASK|ALTMASK)))
2979     {   long rr = FXText::onKeyPress(c, s, ptr);
2980 // I want the input line to be in a special colour.
2981         changeStyle(promptEnd, length-promptEnd, STYLE_INPUT);
2982         return rr;
2983     }
2984 // I have now delegated everything except simple printable characters
2985 // plus tab, backspace and newline without CTRL or ALT.
2986 // I will interpret backspace as deleting the most recent character
2987 // (if there is one, and not if we get back to a newline). Otherwise
2988 // I just fill a (circular) buffer.
2989     flags&=~FLAG_TIP;
2990     if (ch == '\b')  // delete previous character in buffer if there is one
2991     {   int n = type_in;
2992         if (--n < 0) n = TYPEAHEAD_SIZE-1;
2993 // I can not delete a character if there is not one there. I will not delete
2994 // it if the previous character was a newline. In such cases I just beep.
2995         if (type_in == type_out ||
2996             ahead_buffer[n] == '\n')
2997         {   getApp()->beep();
2998             return 1;
2999         }
3000         type_in = n;
3001     }
3002     else type_ahead(ch);
3003     return 1;
3004 }
3005 
3006 //
3007 // Here I have the procedures that implement each editing action. In
3008 // quite a lot of cases they simply delegate to actions already supported
3009 // by FXText, but I have a method for each here because I think it is
3010 // slightly clearer to have all the entrypoints visible in one place.
3011 //
3012 
3013 //
3014 // Something I have NOT fitted quite carefully enough to all this is
3015 // arrangements that I ignore things if not waiting for input and force
3016 // the cursor to the final line in relevant cases.
3017 //
3018 
3019 // ^@   set mark, ie start a selection
3020 
editSetMark()3021 int FXTerminal::editSetMark()
3022 {
3023 // This is in fact just an operation that FXText already supports.
3024     return onCmdMark(this, 0, NULL);
3025 }
3026 
3027 // ^A   move to start of current line (after any prompt text!)
3028 
editMoveLineStart()3029 int FXTerminal::editMoveLineStart()
3030 {
3031     int n = lineStart(cursorpos);
3032     makePositionVisible(n);
3033     while (n < length && (getStyle(n) & STYLE_PROMPT)) n++;
3034     makePositionVisible(n);
3035     setCursorPos(n);
3036 // If the mark is set maybe I should extend the selection...
3037     return 1;
3038 }
3039 
3040 // ^B  move back a character
3041 
editPrevChar()3042 int FXTerminal::editPrevChar()
3043 {
3044 // If the mark is set maybe I should extend the selection...?
3045 // If I am accepting input I will not let the user move backwards into the
3046 // prompt string.
3047     if ((options & TEXT_READONLY) == 0 &&
3048         cursorpos == promptEnd)
3049     {   getApp()->beep();
3050         return 1;
3051     }
3052     return onCmdCursorLeft(this, 0, NULL);
3053 }
3054 
3055 // ALT-B move back a word
3056 
editPrevWord()3057 int FXTerminal::editPrevWord()
3058 {
3059 // If the mark is set maybe I should extend the selection...?
3060 // If I am accepting input I prevent the user from moving back past where the
3061 // prompt string ends. I beep if I make no move at all.
3062     int w = cursorpos;
3063     if ((options & TEXT_READONLY) == 0 && w == promptEnd)
3064     {   getApp()->beep();
3065         return 1;
3066     }
3067     onCmdCursorWordLeft(this, 0, NULL);
3068     if ((options & TEXT_READONLY) == 0 &&
3069         w > promptEnd &&
3070         cursorpos < promptEnd) setCursorPos(promptEnd);
3071     return 1;
3072 }
3073 
3074 // ^C  abandon input, returning an exception to user
3075 
editBreak()3076 int FXTerminal::editBreak()
3077 {
3078 // Note that ^C generates a break action whether I am waiting for input or not.
3079     return onCmdBreak(this, 0, NULL);
3080 }
3081 
editBreakLoop()3082 int FXTerminal::editBreakLoop()
3083 {
3084 // Note that ALT-^G generates a break action whether I am waiting for input or not.
3085     return onCmdBreakLoop(this, 0, NULL);
3086 }
3087 
3088 // ALT-c  capitalize a word
3089 
editCapitalize()3090 int FXTerminal::editCapitalize()
3091 {
3092 // I arbitrarily limit the length of a word that I casefix to 63
3093 // chars.
3094     if (!isEditable())
3095     {   getApp()->beep();
3096         return 1;
3097     }
3098     char wordbuffer[64];
3099     int cp = cursorpos;
3100     int ws = wordStart(cp);
3101     int we = wordEnd(cp);
3102     if (ws < promptEnd) ws = promptEnd;
3103     if (we > ws + 63) we = ws + 63;
3104     extractText(wordbuffer, ws, we-ws);
3105     int i;
3106     wordbuffer[0] = toupper(wordbuffer[0]);
3107     for (i=1; i<we-ws; i++)
3108         wordbuffer[i] = tolower(wordbuffer[i]);
3109     replaceStyledText(ws, we-ws, wordbuffer, we-ws, STYLE_INPUT);
3110     setCursorPos(cp);
3111     makePositionVisible(cp);
3112     return 1;
3113 }
3114 
3115 // ^D  delete character under cursor (fowards)
3116 
editDeleteForward()3117 int FXTerminal::editDeleteForward()
3118 {
3119     if (!isEditable())     // side effect is to move to last line if necessary
3120     {   getApp()->beep();
3121         return 1;
3122     }
3123     return onCmdDelete(this, 0, NULL);
3124 }
3125 
3126 // Should this do special things (a) if there is a selection or (b)
3127 // if there is a selection and the cursor is within it?
3128 
3129 // ALT-d  delete word forwards
3130 
editDeleteForwardWord()3131 int FXTerminal::editDeleteForwardWord()
3132 {
3133     if (!isEditable())     // side effect is to move to last line if necessary
3134     {   getApp()->beep();
3135         return 1;
3136     }
3137     return onCmdDeleteWord(this, 0, NULL);
3138 }
3139 
3140 // ^E  move to end of current line
3141 
editMoveLineEnd()3142 int FXTerminal::editMoveLineEnd()
3143 {
3144 // extend selection?
3145     return onCmdCursorEnd(this, 0, NULL);
3146 }
3147 
3148 // ^F  forward one character
3149 
editNextChar()3150 int FXTerminal::editNextChar()
3151 {
3152 // If the mark is set maybe I should extend the selection...
3153     return onCmdCursorRight(this, 0, NULL);
3154 }
3155 
3156 // ALT-F  forward one word
3157 
editNextWord()3158 int FXTerminal::editNextWord()
3159 {
3160 // If the mark is set maybe I should extend the selection...
3161     return onCmdCursorWordRight(this, 0, NULL);
3162 }
3163 
3164 // ^G   If it was the very very first character typed or if I am not
3165 //      waiting for input, ^G raises an interrupt. If I am waiting for
3166 //      input and have not typed anything much then it clears the current
3167 //      input line leaving me back with a fresh start. I will make that so
3168 //      fresh that ^G^G guarantees an interrupt!
3169 
editBacktrace()3170 int FXTerminal::editBacktrace()
3171 {
3172     if (!isEditable()) return onCmdBacktrace(this, 0, NULL);
3173     killSelection();
3174     setInputText("", 0);
3175     historyNumber = historyLast + 1;
3176     keyFlags &= ~ANY_KEYS;
3177     return 1;
3178 }
3179 
3180 // ^H  (backspace) delete char before cursor if that is reasonable.
3181 
editDeleteBackward()3182 int FXTerminal::editDeleteBackward()
3183 {
3184     switch (isEditableForBackspace())
3185     {
3186 default:                // within the area for active editing.
3187         return FXText::onCmdBackspace(this, 0, NULL);
3188 case -1:                // current input line is empty.
3189 case 0:                 // input is not active
3190         getApp()->beep();
3191         return 1;
3192     }
3193 }
3194 
3195 // ALT-h  delete previous word
3196 
editDeleteBackwardWord()3197 int FXTerminal::editDeleteBackwardWord()
3198 {
3199     int pos;
3200     switch (isEditableForBackspace())
3201     {
3202 default:                // within the area for active editing.
3203 // I want to be confident that whatever prompt string has been set the
3204 // following will never delete part of the prompt...
3205         pos = leftWord(cursorpos);
3206         if (pos < promptEnd) pos = promptEnd;
3207         removeText(pos, cursorpos-pos, TRUE);
3208         setCursorPos(cursorpos, TRUE);
3209         makePositionVisible(cursorpos);
3210         flags |= FLAG_CHANGED;
3211         modified = TRUE;
3212         return 1;
3213 case -1:                // current input line is empty.
3214 case 0:                 // input is not active
3215         getApp()->beep();
3216         return 1;
3217     }
3218 }
3219 
3220 // ^I was just a TAB and has been handled elsewhere
3221 
3222 // ^J (linefeed) accepts the current line of text
3223 
editNewline()3224 int FXTerminal::editNewline()
3225 {
3226     setCursorPos(length);
3227     return onCmdInsertNewline(this, 0, NULL);
3228 }
3229 
3230 // ^K  kill current line
3231 // Note that ^G and ^U are somewhat related, and that I do not
3232 // do anything by way of putting cut text into a kill-buffer, or allowing the
3233 // user to make selections using the keyboard...
3234 
editCutLine()3235 int FXTerminal::editCutLine()
3236 {
3237     killSelection();
3238     setInputText("", 0);
3239     return 1;
3240 }
3241 
3242 // ^L    clear screen (handled as menu shortcut)
3243 
3244 // ALT-L convert to lower case
3245 
editLowercase()3246 int FXTerminal::editLowercase()
3247 {
3248 // I arbitrarily limit the length of a word that I casefix to 63
3249 // chars.
3250     if (!isEditable())
3251     {   getApp()->beep();
3252         return 1;
3253     }
3254     char wordbuffer[64];
3255     int cp = cursorpos;
3256     int ws = wordStart(cp);
3257     if (ws < promptEnd) ws = promptEnd;
3258     int we = wordEnd(cp);
3259     if (we > ws + 63) we = ws + 63;
3260     extractText(wordbuffer, ws, we-ws);
3261     int i;
3262     for (i=0; i<we-ws; i++)
3263         wordbuffer[i] = tolower(wordbuffer[i]);
3264     replaceStyledText(ws, we-ws, wordbuffer, we-ws, STYLE_INPUT);
3265     setCursorPos(cp);
3266     makePositionVisible(cp);
3267     return 1;
3268 }
3269 
3270 // ^M  as ENTER, ^J
3271 
3272 // ALT-M  a &Module menu
3273 
3274 // ^N  history next if we are at present on the bottom line
3275 //     otherwise move down a line
3276 // (also down-arrow key)
3277 
3278 // To replace the input line I can can use this... It returns the
3279 // index of the first character of the inserted line.
3280 
setInputText(const char * newtext,int n)3281 int FXTerminal::setInputText(const char *newtext, int n)
3282 {
3283     int n2 = length;
3284     int n1 = lineStart(n2);
3285     while (n1 < n2 && (getStyle(n1) & STYLE_PROMPT)) n1++;
3286     replaceStyledText(n1, n2-n1, newtext, n, STYLE_INPUT);
3287     setCursorPos(length);
3288     makePositionVisible(length);
3289     return n1;
3290 }
3291 
3292 
3293 // Oh JOY - I need a version for wide characters as well...
3294 
setInputText(const wchar_t * newtext,int n)3295 int FXTerminal::setInputText(const wchar_t *newtext, int n)
3296 {
3297     FXString foxtext(newtext);
3298     int n2 = length;
3299     int n1 = lineStart(n2);
3300     while (n1 < n2 && (getStyle(n1) & STYLE_PROMPT)) n1++;
3301     replaceStyledText(n1, n2-n1, foxtext.text(), n, STYLE_INPUT);
3302     setCursorPos(length);
3303     makePositionVisible(length);
3304     return n1;
3305 }
3306 
3307 
3308 // The history routines here are never invoked unless we are awaiting input
3309 
editHistoryNext()3310 int FXTerminal::editHistoryNext()
3311 {
3312     const wchar_t *history_string;
3313     if (historyLast == -1) // no history lines at all to retrieve!
3314     {   getApp()->beep();
3315         return 1;
3316     }
3317     if (historyNumber < historyLast) historyNumber++;
3318     if ((history_string = input_history_get(historyNumber)) == NULL)
3319     {   getApp()->beep();
3320         return 1;
3321     }
3322     setInputText(history_string, std::wcslen(history_string));
3323     return 1;
3324 }
3325 
3326 // Commentary on the search mechanism:
3327 //   If not at present engaged in a search the search key
3328 //   enters search mode with an empty search string and a given
3329 //   direction, and the empty string will match against the current
3330 //   (usually empty) input line so nothing much visible will happen.
3331 //
3332 //   A further use of the search key will move one line in the given
3333 //   direction and search again until the pattern matches. If the alternate
3334 //   direction search key is pressed the line is moved one line in the
3335 //   new direction before scanning that way.
3336 //
3337 //   ENTER or an arrow key, or DEL or ESC (in general most things that
3338 //   and not printing characters and not otherwise listed here) exits
3339 //   search mode with the new current line.
3340 //
3341 //   BACKSPACE (^H) removes a character from the search pattern. If there
3342 //   that leaves none it exits search mode. It pops back to the line you
3343 //   had before the character it removed was inserted.
3344 //
3345 //   typical printing characters add that character to the pattern. If the
3346 //   pattern is not a valid Regular Expression at the time concerned it is
3347 //   treated as if completed in the most generous manner possible? Or maybe
3348 //   the match fails so you get a beep and no movement?
3349 //   [Gosh what do I mean by that? Do I *REALLY* want regexp matches here?]
3350 //   Searching continues in the most recently selected direction. If no match
3351 //   is found the line does not move and the system beeps.
3352 //
3353 
3354 // ALT-n  forward search
3355 
editSearchHistoryNext()3356 int FXTerminal::editSearchHistoryNext()
3357 {
3358     if (historyLast == -1) // no history to search
3359     {   getApp()->beep();
3360         return 1;
3361     }
3362 // If I am not in a search at present then set the flag for a search
3363 // with an empty search string and a mark that the direction is forwards.
3364 // Well if I not only am not in a search but I had not previously scrolled
3365 // back in the history so I have nowhere to search then I might as well
3366 // beep and give up.
3367     if (historyNumber > historyLast)
3368     {   getApp()->beep();
3369         return 1;
3370     }
3371     searchFlags = SEARCH_FORWARD;
3372     return 1;
3373 }
3374 
trySearch()3375 int FXTerminal::trySearch()
3376 {
3377     int r = -1;
3378     const wchar_t *history_string = input_history_get(historyNumber);
3379     if (history_string == NULL) return -1;
3380     while ((r = matchString(searchString, SEARCH_LENGTH, history_string)) < 0)
3381     {   if (searchFlags & SEARCH_FORWARD)
3382         {   if (historyNumber == historyLast) return -1;
3383             historyNumber++;
3384         }
3385         else
3386         {   if (historyNumber == historyFirst) return -1;
3387             historyNumber--;
3388         }
3389         history_string = input_history_get(historyNumber);
3390         if (history_string == NULL) return -1;
3391     }
3392     return r;
3393 }
3394 
matchString(const wchar_t * pat,int n,const wchar_t * targettext)3395 int FXTerminal::matchString(const wchar_t *pat, int n, const wchar_t *targettext)
3396 {
3397 // This is a crude and not especially efficient pattern match. I think
3398 // it should be good enough for use here! I make it return the offset where
3399 // a match first occurred (if one does) in case that will be useful to me
3400 // later. I could put the cursor there, perhaps?
3401     int offset;
3402     for (offset=0;*(targettext+offset)!=0;offset++)
3403     {   const wchar_t *p = pat, *q = targettext+offset;
3404         int i;
3405         for (i=0; i<n; i++)
3406         {   if (p[i] != q[i]) break;
3407         }
3408         if (i == n) return offset;
3409     }
3410     return -1;
3411 }
3412 
3413 
3414 // ^O  abandon pending output. Menu shortcut
3415 
3416 // ^P  history previous if we are on bottom line
3417 //     [else cursor up?]
3418 // (also uparrow key)
3419 
editHistoryPrev()3420 int FXTerminal::editHistoryPrev()
3421 {
3422     const wchar_t *history_string;
3423     if (historyLast == -1) // no previous lines to retrieve
3424     {   getApp()->beep();
3425         return 1;
3426     }
3427 // If I have not moved the history pointer at all yet move it into the
3428 // range of valid history entries.
3429     if (historyNumber > historyFirst) historyNumber--;
3430     history_string = input_history_get(historyNumber);
3431     if (history_string == NULL)
3432     {   getApp()->beep();
3433         return 1;
3434     }
3435     setInputText(history_string, std::wcslen(history_string));
3436     return 1;
3437 }
3438 
3439 // ALT-P  reverse search
3440 
editSearchHistoryPrev()3441 int FXTerminal::editSearchHistoryPrev()
3442 {
3443     if (historyLast == -1) // no history to search
3444     {   getApp()->beep();
3445         return 1;
3446     }
3447     if (historyNumber == historyLast + 1) historyNumber--;
3448     searchFlags = SEARCH_BACKWARD;
3449     return 1;
3450 }
3451 
3452 // ^Q  unpause output (see ^Z, ^S) treated as menu shortcut
3453 
3454 
3455 // ^R  Redisplay
3456 
editRedisplay()3457 int FXTerminal::editRedisplay()
3458 {
3459     return onCmdRedraw(this, 0, NULL);
3460 }
3461 
3462 // ^S as pause output is handled as a shortcut so that it can be
3463 // accepted whether or not I am awaiting input.
3464 
3465 // ^T  transpose
3466 
editTranspose()3467 int FXTerminal::editTranspose()
3468 {
3469     if (!isEditable())
3470     {   getApp()->beep();
3471         return 1;
3472     }
3473     char buff[2];
3474     int cp = cursorpos;
3475     if (cp > length-2)
3476     {   getApp()->beep();
3477         return 1;
3478     }
3479     extractText(buff, cp, 2);
3480     int ch;
3481     ch = buff[0];
3482     buff[0] = buff[1];
3483     buff[1] = ch;
3484     replaceStyledText(cp, 2, buff, 2, STYLE_INPUT);
3485     setCursorPos(cp);
3486     makePositionVisible(cp);
3487     return 1;
3488 }
3489 
3490 // ^U  reserved for UNDO, and also exits search mode.
3491 
editUndo()3492 int FXTerminal::editUndo()
3493 {
3494 // @@@@@
3495     return 1;
3496 }
3497 
3498 // ALT-U convert to upper case
3499 
editUppercase()3500 int FXTerminal::editUppercase()
3501 {
3502 // I arbitrarily limit the length of a word that I casefix to 63
3503 // chars.
3504     if (!isEditable())
3505     {   getApp()->beep();
3506         return 1;
3507     }
3508     char wordbuffer[64];
3509     int cp = cursorpos;
3510     int ws = wordStart(cp);
3511     if (ws < promptEnd) ws = promptEnd;
3512     int we = wordEnd(cp);
3513     if (we > ws + 63) we = ws + 63;
3514     extractText(wordbuffer, ws, we-ws);
3515     int i;
3516     for (i=0; i<we-ws; i++)
3517         wordbuffer[i] = toupper(wordbuffer[i]);
3518     replaceStyledText(ws, we-ws, wordbuffer, we-ws, STYLE_INPUT);
3519     setCursorPos(cp);
3520     makePositionVisible(cp);
3521     return 1;
3522 }
3523 
3524 // ^V  shortcut for PASTE
3525 
3526 // ^W  delete previous word just as ALT-H
3527 
3528 // ALT-W  Copy region
3529 
editCopyRegion()3530 int FXTerminal::editCopyRegion()
3531 {
3532 // @@@@@
3533     return 1;
3534 }
3535 
3536 // ^X  extended command
3537 
editExtendedCommand()3538 int FXTerminal::editExtendedCommand()
3539 {
3540 // @@@@@
3541     return 1;
3542 }
3543 
3544 // ALT-X  obey command
3545 
editObeyCommand()3546 int FXTerminal::editObeyCommand()
3547 {
3548 // @@@@@   Use this to do Unicode conversion ...
3549 // @ I really want to implement this!
3550     return 1;
3551 }
3552 
3553 
3554 // ^Y  paste
3555 
editPaste()3556 int FXTerminal::editPaste()
3557 {
3558     if (!isEditable())
3559     {   getApp()->beep();
3560         return 1;
3561     }
3562     return onCmdPasteSel(this, 0, NULL);
3563 }
3564 
3565 // ALT-y rotate killbuffer/clipboard
3566 
editRotateClipboard()3567 int FXTerminal::editRotateClipboard()
3568 {
3569 // @@@@@
3570     return 1;
3571 }
3572 
3573 // ^Z is a keyboard shortcut to pause execution
3574 
3575 // ALT-[, ESCAPE
3576 
editEscape()3577 int FXTerminal::editEscape()
3578 {
3579     keyFlags ^= ESC_PENDING; // so that ESC ESC cancels the effect.
3580     return 1;
3581 }
3582 
3583 // ^\   exit the application (menu shortcut)
3584 // ^]   unused
3585 
3586 // ^^   re-input (= COPY/PASTE)
3587 
editReinput()3588 int FXTerminal::editReinput()
3589 {
3590     if (!isEditable())
3591     {   getApp()->beep();
3592         return 1;
3593     }
3594     return onCmdReinput(this, 0, NULL);
3595 }
3596 
3597 // ALT-_  copy previous word
3598 
editCopyPreviousWord()3599 int FXTerminal::editCopyPreviousWord()
3600 {
3601 // @@@@@
3602     return 1;
3603 }
3604 
3605 
3606 // Return true if editable, which here is used as
3607 // a mark of whether the user has requested input.
3608 
isEditable()3609 FXbool FXTerminal::isEditable()
3610 {
3611     if ((options&TEXT_READONLY)!=0) return FALSE;
3612 // If we are asking if the FXTerminal is editable that is because we
3613 // are trying to insert something. Here it is editable, so the user is
3614 // waiting for input. I will make the very query force the final line
3615 // to be visible and ensure that the cursor is within it. This should prevent
3616 // anybody from every clobbering anything other than the active input line.
3617 // Note that key-presses while the program is NOT ready to accept them
3618 // will not cause cursor movement until the program requests input.
3619     int n = lineStart(length);
3620     makePositionVisible(n);
3621     while (n < length && (getStyle(n) & STYLE_PROMPT)) n++;
3622     makePositionVisible(length);
3623     if (cursorpos < n) setCursorPos(length);
3624 // Furthermore if I am about to change thing I will ensure that any
3625 // selection lies within the active line.
3626     if (selstartpos < n) selstartpos = n;
3627     if (selendpos < selstartpos) selendpos = selstartpos;
3628     return TRUE;
3629 }
3630 
3631 // Return true if editable, to be used when the next operation would
3632 // be a BACKSPACE (delete-previous). It must thus shift the cursor to
3633 // avoid deleting the final character of the prompt string.
3634 
isEditableForBackspace()3635 int FXTerminal::isEditableForBackspace()
3636 {
3637     if ((options&TEXT_READONLY)!=0) return 0;  // must buffer the action
3638     int n = lineStart(length);
3639     makePositionVisible(n);
3640     while (n < length && (getStyle(n) & STYLE_PROMPT)) n++;
3641     makePositionVisible(length);
3642 // The next line has "<=" where the previous function has just "<"
3643     if (cursorpos <= n) setCursorPos(length);
3644 // Furthermore if I am about to change thing I will ensure that any
3645 // selection lies within the active line.
3646     if (selstartpos < n) selstartpos = n;
3647     if (selendpos < selstartpos) selendpos = selstartpos;
3648     if (n == length) return -1; // nothing that I am allowed to delete
3649     return 1;
3650 }
3651 
3652 int recently_flushed = 0;
3653 
onCmdInsertNewline(FXObject * c,FXSelector s,void * ptr)3654 long FXTerminal::onCmdInsertNewline(FXObject *c, FXSelector s, void *ptr)
3655 {
3656 // Note that the 3 args to this procedure are never used!
3657     FXint p = length;
3658 // I find the first "real" character of the input line by scanning back
3659 // to (a) the start of the buffer (b) the end of a previous line or (c) the
3660 // end of a prompt string.
3661     while (p>0 && getChar(p-1)!='\n' && (getStyle(p-1)&STYLE_PROMPT)==0) p--;
3662     FXint n = length-p, j, k;
3663     if (n > (int)sizeof(inputBuffer)-5) n = sizeof(inputBuffer)-5;
3664 // The text that I extract here will be in UTF8 format...
3665     extractText(inputBuffer, p, n);
3666 // I enter the line that has just been collected into the history
3667 // record. I am afraid that wants to be formatted using wchar_t, so I will
3668 // need to convert! Ugh. inputWBuffer is available to collect the adjusted
3669 // version in.
3670     inputBuffer[n] = 0;
3671     j = k = 0;
3672     while (inputBuffer[j] != 0)
3673     {   int ch = utf_decode((const unsigned char *)&inputBuffer[j]);
3674         j += utf_bytes;
3675         if (sizeof(wchar_t) == 2 && ch > 0xffff)
3676         {   inputWBuffer[k++] = ch;  // Use surrogates here! @@@@
3677         }
3678         else inputWBuffer[k++] = ch;
3679     }
3680     inputWBuffer[k] = 0;
3681     input_history_add(inputWBuffer);
3682 // Adding an entry could cause an old one to be discarded. So I now ensure
3683 // that I know what the first and last recorded numbers are.
3684     historyLast = input_history_next - 1;
3685     historyFirst = input_history_next - INPUT_HISTORY_SIZE;
3686     if (historyFirst < 0) historyFirst = 0;
3687     historyNumber = historyLast + 1; // so that ALT-P moves to first entry
3688 // Now I add a newline to the text, since the user will expect to see that.
3689     inputBuffer[n] = '\n';
3690     inputBuffer[n+1] = 0;
3691     inputBufferLen = n+1;
3692     inputBufferP = 0;
3693 // Stick a newline into the text buffer, and make the screen non-updatable.
3694     FXText::onCmdInsertNewline(c, s, ptr);
3695     setEditable(FALSE);
3696     recently_flushed = 0;
3697 // stuff user typed is now in buffer... I should never have got here unless
3698 // the user thread was waiting, so here I unlock it, to tell it that
3699 // the input buffer is ready.
3700     if (sync_even)
3701     {   sync_even = 0;
3702         UnlockMutex(mutex3);
3703         LockMutex(mutex2);
3704         UnlockMutex(mutex4);
3705     }
3706     else
3707     {   sync_even = 1;
3708         UnlockMutex(mutex1);
3709         LockMutex(mutex4);
3710         UnlockMutex(mutex2);
3711     }
3712     return 1;
3713 }
3714 
requestFlushBuffer()3715 long FXTerminal::requestFlushBuffer()
3716 {
3717     recently_flushed = 0;
3718 // here the worker thread is locked waiting for mutex2, so I can afford to
3719 // adjust fwin_in and fwin_out.
3720     if (sync_even)
3721     {   LockMutex(mutex1);
3722         if (fwin_in != fwin_out && (pauseFlags & PAUSE_DISCARD) == 0)
3723         {   if (fwin_in > fwin_out)
3724                 FXText::appendText(&fwin_buffer[fwin_out], fwin_in-fwin_out);
3725             else
3726             {   FXText::appendText(&fwin_buffer[fwin_out], FWIN_BUFFER_SIZE-fwin_out);
3727                 FXText::appendText(&fwin_buffer[0], fwin_in);
3728             }
3729             makePositionVisible(rowStart(length));
3730         }
3731 // After this call fwin_in and fwin_out are always both zero.
3732         fwin_out = fwin_in = 0;
3733         sync_even = 0;
3734         UnlockMutex(mutex3);
3735         LockMutex(mutex2);
3736         UnlockMutex(mutex4);
3737     }
3738     else
3739     {   LockMutex(mutex3);
3740         if (fwin_in != fwin_out && (pauseFlags & PAUSE_DISCARD) == 0)
3741         {   if (fwin_in > fwin_out)
3742                 FXText::appendText(&fwin_buffer[fwin_out], fwin_in-fwin_out);
3743             else
3744             {   FXText::appendText(&fwin_buffer[fwin_out], FWIN_BUFFER_SIZE-fwin_out);
3745                 FXText::appendText(&fwin_buffer[0], fwin_in);
3746             }
3747             makePositionVisible(rowStart(length));
3748         }
3749         fwin_out = fwin_in = 0;
3750         sync_even = 1;
3751         UnlockMutex(mutex1);
3752         LockMutex(mutex4);
3753         UnlockMutex(mutex2);
3754     }
3755     return 1;
3756 }
3757 
staticCharForShowMath()3758 static int staticCharForShowMath()
3759 {
3760     return text->charForShowMath();
3761 }
3762 
charForShowMath()3763 int FXTerminal::charForShowMath()
3764 {
3765     if (charPointer >= length) return 0;
3766 // At present the "showmath" material should never contain exotic
3767 // characters since it should contain TeX-like text. So I can use
3768 // getByte not getChar. It I used getChar I should use inc to increment
3769 // charPointer...
3770     int c = getByte(charPointer) & 0xff;
3771     if (c == '\n') return 0;
3772     charPointer++;
3773     return c;
3774 }
3775 
3776 // At the start of maths display material I have a sequence that goes
3777 // SO some text SI (where SO=0x0e and SI=0x0f) and this contains
3778 // a plain text version of the Reduce output suitable for use with COPY.
3779 // Specifically it will be what arises if "off nat" is used to print stuff.
3780 // I had originally hoped that this would not contain any control characters
3781 // but in some cases it can contain newlines. Here I skip until the
3782 // TeX material that follows it.
3783 
findTeXstart() const3784 void FXTerminal::findTeXstart() const
3785 {
3786     int ch = 0, cp = charPointer;
3787     if (getByte(cp)!=0x0e) return;
3788     while (cp < length &&
3789            (ch = getByte(cp)) != 0x0f) cp++;
3790     if (ch == 0x0f) charPointer = cp+1;
3791 }
3792 
3793 #define APPEND_BUFFER_SIZE 0x100000  // 1 Mbyte buffer
3794 
3795 static char append_buffer[APPEND_BUFFER_SIZE];
3796 static int append_point = 0;
3797 
buffered_append(FXTerminal * t,const char * s,int len)3798 void buffered_append(FXTerminal *t, const char *s, int len)
3799 {   if (append_point+len >= APPEND_BUFFER_SIZE)
3800     {   t->FXText::appendStyledText(append_buffer, append_point,
3801                                     FXTerminal::STYLE_MATH);
3802         append_point = 0;
3803     }
3804     if (len >= APPEND_BUFFER_SIZE)
3805         t->FXText::appendStyledText(s, len, FXTerminal::STYLE_MATH);
3806     else
3807     {   memcpy(&append_buffer[append_point], s, len);
3808         append_point += len;
3809     }
3810 }
3811 
flush_append(FXTerminal * t)3812 void flush_append(FXTerminal *t)
3813 {   if (append_point!=0)
3814     {   t->FXText::appendStyledText(append_buffer, append_point,
3815                                     FXTerminal::STYLE_MATH);
3816         append_point = 0;
3817     }
3818 }
3819 
insertMathsLines()3820 void FXTerminal::insertMathsLines()
3821 {
3822     const char *p = fwin_maths;
3823     int start = length;
3824     int linecount = 0;
3825     bool shifted=false;
3826     while (*p != 0)
3827     {
3828 // Find next line break that is not within "shifted" material. Obviously
3829 // stop at end of buffer too.
3830         while(*p!=0 && (shifted || *p!='\n'))
3831         {   if (*p==0x0e) shifted=true;
3832             else if (*p==0x0f) shifted=false;
3833             p++;
3834         }
3835         if (*p=='\n') p++;
3836 // I hope that apart from 0x02 at the start of a line marking maths-mode
3837 // material that hardly anybody else needs to be aware of anything special.
3838 // FXText.cpp probably has to be so it can measure maths lines, and
3839 // FXShowMath.cpp will need to be reviewed just to check that I keep it in
3840 // step.
3841 
3842 
3843 // 0x02  is a lead-in to introduce the maths data, and it exists because
3844 //       at a higher level I want to map one line of maths onto several
3845 //       rows. I will end up (later on) with one 0x02 for each row to be used.
3846 //       HOWEVER for reasons that convince at least me I will start by
3847 //       inserting '1' where ('0'+n) will be used to indicate a line of
3848 //       maths that needs n rows... Well in fact at the stage I am doing that
3849 //       I will "borrow" the next byte as well so I can have 12 bits to
3850 //       specify the row-count. This lets it be up to 4095 and that seems
3851 //       greater than is ever at all plausible.
3852 // '00'  a pair of bytes, each of which is a character in the range from
3853 //      '0' to 'o'. This leaves 6 bits of data in each byte, ie 12 bits
3854 //      in all. These are split as 3+9. The 3-bit value is used as
3855 //      a scale indicator to pick one of the 5 scales that maths can be
3856 //      rendered at. The 9-bit value is used to get the multiple lines
3857 //      making up a single formula all consistently centred. The maximum
3858 //      offset this can specify is 511, which is better than twice the
3859 //      limit that applied in my previous version of this code.
3860 //      The scale is in the bottom 3 bits of the second byte.
3861 // 'xxxx' is a 4-byte gap that will be used to hold a handle to the
3862 //       box-structure representing the given line of the mathematical
3863 //       formula. The handle will use 6-bits per byte so I have 24-bits here.
3864 //       A consquence is that I have built in an architectural limit at
3865 //       16 Mbytes of display buffer. I will sometimes need to indidate
3866 //       that there is no box structure yet... that is done by putting xxxx
3867 //       in place. That can not be interpreted as a proper handle because
3868 //       the 6-bits per byte used there use the characters
3869 //         0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmno
3870 //       notably stopping before "x".
3871 //
3872 // Note that when I parse a bit of TeX I may run out of memory in the
3873 // area reserved for box structures, and in that case I will discard some
3874 // old box. When I do so I want to be able to identify the reference to it
3875 // so I can mark it as stale. That is achieved by having the maths display
3876 // parsing code hold a reference back into my text buffer so it can clobber
3877 // the reference. For this to work it is VITAL that the text buffer should not
3878 // change under the feet of the box-management package. This is an ugly
3879 // constraint and probably shows that the two chunks of code need a tighter
3880 // interface...
3881         buffered_append(this, "000xxxx", 7);
3882 // At the stage the 000xxxx is split up as:
3883 //     00      will be number of rows used;
3884 //      00     will be centering indent and scale factor;
3885 //        xxxx will be box address.
3886 // A little later it will become
3887 //     {02}nnPPPP
3888 // where {02} is a byte that marks the start of some maths, nn is the
3889 // two byte scale and indent information and PPPP is a pointer.
3890         buffered_append(this, fwin_maths, p-fwin_maths);
3891         linecount++;
3892         fwin_maths = p;
3893     }
3894     flush_append(this);
3895     int scale = 4;
3896     int p1 = start;
3897     while (p1<length)
3898     {   charPointer = p1+7;
3899 // First parse the line of stuff to get a box-structure. The parsed box gets
3900 // a reference back to position p1+3 in the text buffer since that is where
3901 // the reference will be put.
3902         findTeXstart();
3903         Box *b = parseTeX(staticCharForShowMath, p1+3);
3904 // Style 0 in makeTextBox gives Roman typeface at normal size.
3905 // Note that if I had used a blank character in the message here it would have
3906 // been displayed as a combining short slash. So I use a "-" which is at
3907 // lest slightly better!
3908         if (b == NULL) b = makeTopBox(makeTextBox("malformed-TeX-input", 19, 0));
3909 // Remember where the box is. Note that if it gets discarded as I parse
3910 // another box later on this reference will be replaced with a zero.
3911         text->recordBoxAddress(p1+3, b);
3912 // Measure it at the current scale
3913         setMathsFontScale(scale);
3914         measureBox(b);
3915 // If it is too wide then I will try to scale it down until it looks as if it
3916 // will fit. When I do that I will measure all subsequent lines at the new
3917 // reduced scale. But previously-measured boxes may need revision in a later
3918 // pass over the data.
3919         while (b->text.width > text->getDefaultWidth() && scale>0)
3920         {   setMathsFontScale(--scale);
3921             measureBox(b);
3922         }
3923 // Move on to the next line. Note that I arrange that any special info I
3924 // put in the buffer will NEVER include a newline character except within SO/SI
3925         bool shifted1=false;
3926         while (p1<length)
3927         {   int c=getByte(p1);
3928             if (c==0x0e) shifted1=true;
3929             else if (c==0x0f) shifted1=false;
3930             else if (!shifted1 && c=='\n') break;
3931             p1++;
3932         }
3933         if (p1 < length) p1++;
3934     }
3935 // Now I take a second pass, and in this second pass I will sort out just how
3936 // many rows each line will need, and insert markers to record this. I can
3937 // also find the greatest width present in a multi-line display.
3938     int maxWidth = 0;
3939     p1 = start;
3940     while (p1<length)
3941     {
3942 // Recover the box so I can re-measure it.
3943         Box *b = getBoxAddress(p1+3);
3944 // If it has been discarded then the recovered pointer will be NULL
3945 // so I re-parse the LaTeX.
3946         if (b == NULL)
3947         {   charPointer = p1+7;
3948 // Again I should tell the box that its "owner" is the place in the
3949 // text buffer where the pointer to it lives. (p1+3) here.
3950             findTeXstart();
3951             b = parseTeX(staticCharForShowMath, p1+3);
3952             if (b == NULL) b = makeTopBox(makeTextBox("malformed expression", 19, 0));
3953         }
3954 // Measure it (again) at the current scale. In simple cases the effect here
3955 // is that the measureBox() call gets done twice in an unnecessary way. I
3956 // will let that happen and count simplicity in this code more important that
3957 // efficiency!
3958         setMathsFontScale(scale);
3959         measureBox(b);
3960 // By now the line is expected to fit (horizontally). I arranged that on
3961 // my first pass. However I will want to do a bit more magic on multi-line
3962 // formulae.
3963 // Record the scale that is to be used.
3964         replaceStyledText(p1+2, 1, "01234"+scale,
3965             1, STYLE_MATH);
3966         if (b->text.width > maxWidth) maxWidth = b->text.width;
3967         int h = b->text.height + b->text.depth;  // height
3968         FXint hh=font->getFontHeight();         // row height
3969 // Work out how many rows are needed to let this line of maths fit in. I will
3970 // insist that I have at least 0.33 or the row height spare (I will distribute
3971 // that evenly above and below the formula in the display).
3972         int nnrows = (h+hh+hh/3)/hh;
3973 // However as a SPECIAL CASE if the data forms part of a multi-line display
3974 // and this line is just a single item consisting of a string of digits
3975 // then I will force it to use up just one row. This is to try to make the
3976 // display of very big numbers look more sensible.  The macro BoxText is
3977 // defined in FXShowMath.cpp and is mostly private to there...
3978 #define BoxText 0
3979         if (linecount!=1 && b->top.sub->text.type == BoxText)
3980         {   char *ss = b->top.sub->text.text;
3981             int nn = b->top.sub->text.n;
3982             while (--nn >= 0) if (!isdigit(ss[nn])) break;
3983             if (nn < 0) nnrows = 1;
3984         }
3985 // Right now I hold a row-count in a byte such that the largest count that
3986 // can be stored is 4095. I guess that a tall array could use more than this!
3987 // But for a first stab at this code I will not treat that case too seriously
3988 // and I will arbitrarily limit row counts. Having thousands of rows for a
3989 // single line of formula is seriously improbable!
3990         if (nnrows > 4095) nnrows = 4095;
3991 // Now record how many rows are needed. Note that I do this without
3992 // disturbing the layout of the text buffer.
3993         char heightString[2];
3994         heightString[0] = '0' + (nnrows & 0x3f);
3995         heightString[1] = '0' + ((nnrows>>6) & 0x3f);
3996         replaceStyledText(p1, 2, heightString, 2, STYLE_MATH);
3997         bool shifted2=false;
3998         while (p1<length)
3999         {   int c=getByte(p1);
4000             if (c==0x0e) shifted2=true;
4001             else if (c==0x0f) shifted2=false;
4002             else if (!shifted2 && c=='\n') break;
4003             p1++;
4004         }
4005         if (p1 < length) p1++;
4006     }
4007 // A third pass turns the lead-in bytes that are at present in the form
4008 // '0'+rowCount into a sequence of 0x02 chars. The reason I need to
4009 // encode the number of rows used by a formula in unary this way is that
4010 // to fit in with the rest of FXText I need to view the display as
4011 // composed of rows, and I need locations within the buffer that can
4012 // stand for the start of each row.
4013 // When I insert the extra characters here the result will be that
4014 // back-pointers from boxes into the text buffer will become incorrect, so
4015 // I need to correct them all. I do not do ANY operations that could
4016 // involve allocating new boxes during this phase and so I will never follow
4017 // a back-pointer while it is broken...
4018     int spare = (text->getDefaultWidth() - maxWidth)/mathWidth;
4019     if (spare < 0) spare = 0; // should never happen!
4020     else if (spare > 510) spare = 510;
4021     spare++;   // the base value here is 1. 0 is used to stand for "N/A".
4022     if (linecount == 1) spare = 0;
4023 // The above has set up spare to be a value that is there to help with
4024 // multi-line formulae. It is 0 for a 1-line formula (which I will centre),
4025 // or a value bigger than that for any multi-line formulae, and the value
4026 //  then gives info about how much spare space there should be on the
4027 //  longest line in the entire formula.
4028     char spareBytes[3];
4029     spareBytes[0] = 0x02;
4030     spareBytes[1] = '0' + (spare & 0x3f);
4031     p1 = start;
4032     while (p1<length)
4033     {   int heightCode = (getByte(p1) - '0') & 0x3f;
4034         heightCode += ((getByte(p1+1) - '0') & 0x3f) << 6;
4035 // Now I have retrieved the information about how many rows will be needed
4036 // I can write in the centering and scale information.
4037         spareBytes[0] = 0x02;  // Maybe helps clarity to re-specify this?
4038         spareBytes[1] = '0' + (spare & 0x3f);
4039         spareBytes[2] = '0' + (((spare>>6) & 0x7)<<3) + (getByte(p1+2) & 0x7);
4040         replaceStyledText(p1, 3, spareBytes, 3, STYLE_MATH);
4041         while (heightCode > 1)
4042         {   insertStyledText(p1, "\x02", 1, STYLE_MATH);
4043             heightCode--;
4044             p1++;
4045         }
4046         Box *b = getBoxAddress(p1+3);
4047 // If it has been discarded then the recovered pointer will be NULL
4048 // and so there will be no need to reset a back pointer.
4049         if (b != NULL) updateOwner(b, p1+3);
4050         bool shifted3=false;
4051         while (p1<length)
4052         {   int c=getByte(p1);
4053             if (c==0x0e) shifted3=true;
4054             else if (c==0x0f) shifted3=false;
4055             else if (!shifted3 && c=='\n') break;
4056             p1++;
4057         }
4058         if (p1 < length) p1++;
4059     }
4060 // Now I think everything is in a consistent state ready for display!
4061 }
4062 
getBoxAddress(int p) const4063 Box *FXTerminal::getBoxAddress(int p) const
4064 {
4065     int c1 = getByte(p),
4066         c2 = getByte(p+1),
4067         c3 = getByte(p+2),
4068         c4 = getByte(p+3);
4069 #ifdef APRIL_2015
4070     fprintf(stderr, "getBoxAddress at %d = %c%c%c%c\n", p, c1, c2, c3, c4);
4071 #endif
4072     if (c1 == 'x') return NULL;
4073     int n = (c4 - '0') & 0x3f;
4074     n = (n << 6) | ((c3 - '0') & 0x3f);
4075     n = (n << 6) | ((c2 - '0') & 0x3f);
4076     n = (n << 6) | ((c1 - '0') & 0x3f);
4077     return (Box *)poolPointerFromHandle(n);
4078 }
4079 
recordBoxAddress(int p,Box * b)4080 void FXTerminal::recordBoxAddress(int p, Box *b)
4081 {
4082     char s[4];
4083     int c1='x', c2='x', c3='x', c4='x';
4084     if (b != NULL)
4085     {   int n = handleFromPoolPointer(b);
4086         c1 = '0' + (n & 0x3f); n = n>>6;
4087         c2 = '0' + (n & 0x3f); n = n>>6;
4088         c3 = '0' + (n & 0x3f); n = n>>6;
4089         c4 = '0' + (n & 0x3f);
4090     }
4091     s[0] = c1;
4092     s[1] = c2;
4093     s[2] = c3;
4094     s[3] = c4;
4095 #ifdef APRIL_2015
4096     fprintf(stderr, "recordBoxAddress at %d %c%c%c%c\n", p, c1, c2, c3, c4);
4097 #endif
4098     replaceStyledText(p, 4, s, 4, STYLE_MATH);
4099 }
4100 
4101 // This curious function is a call-back from FXShowMath and is invoked
4102 // when a box-structure gets destroyed (they get destroyed on a cyclic basis
4103 // when memory starts to get full). It zeros out the record here of where the
4104 // box structure is, and as a result any future attempt to re-paint that
4105 // bit of the display will provoke a re-parse and thus a re-creation of
4106 // the data (which will presumably displace some other boxes...). Also the
4107 // call-back wants to be a simple C function but to update my buffer I need to
4108 // regain class access...
4109 
reportDestroy(int p)4110 void reportDestroy(int p)
4111 {
4112     if (text != NULL) text->reportDestroy(p);
4113 }
4114 
reportDestroy(int p)4115 void FXTerminal::reportDestroy(int p)
4116 {
4117     replaceStyledText(p, 4, "xxxx", 4, STYLE_MATH);
4118 }
4119 
requestShowMath()4120 long FXTerminal::requestShowMath()
4121 {
4122     recently_flushed = 0;
4123     if (length != 0 && getChar(length-1) != '\n')
4124         FXText::appendText("\n", 1); // terminate any pending line
4125     if (sync_even)
4126     {   LockMutex(mutex1);
4127         insertMathsLines();
4128         makePositionVisible(rowStart(length));
4129         sync_even = 0;
4130         UnlockMutex(mutex3);
4131         LockMutex(mutex2);
4132         UnlockMutex(mutex4);
4133     }
4134     else
4135     {   LockMutex(mutex3);
4136         insertMathsLines();
4137         makePositionVisible(rowStart(length));
4138         sync_even = 1;
4139         UnlockMutex(mutex1);
4140         LockMutex(mutex4);
4141         UnlockMutex(mutex2);
4142     }
4143     return 1;
4144 }
4145 
4146 
4147 static char promptString[MAX_PROMPT_LENGTH] = "> ";
4148 static int promptLength = 2;
4149 
requestSetPrompt()4150 long FXTerminal::requestSetPrompt()
4151 {
4152     strncpy(promptString, fwin_prompt_string, MAX_PROMPT_LENGTH);
4153     promptString[MAX_PROMPT_LENGTH-1] = 0;
4154     promptLength = strlen(promptString);
4155     if (sync_even)
4156     {   LockMutex(mutex1);
4157         sync_even = 0;
4158         UnlockMutex(mutex3);
4159         LockMutex(mutex2);
4160         UnlockMutex(mutex4);
4161     }
4162     else
4163     {   LockMutex(mutex3);
4164         sync_even = 1;
4165         UnlockMutex(mutex1);
4166         LockMutex(mutex4);
4167         UnlockMutex(mutex2);
4168     }
4169     return 1;
4170 }
4171 
requestRefreshTitle()4172 long FXTerminal::requestRefreshTitle()
4173 {
4174     strcpy(window_full_title, full_title);
4175 // I ought to make all actions on the window stuff happen in this thread.
4176     if (pauseFlags == 0) main_window->setTitle(window_full_title);
4177 // Having done all that I can re-sync with the worker thread.
4178     if (sync_even)
4179     {   LockMutex(mutex1);
4180         sync_even = 0;
4181         UnlockMutex(mutex3);
4182         LockMutex(mutex2);
4183         UnlockMutex(mutex4);
4184     }
4185     else
4186     {   LockMutex(mutex3);
4187         sync_even = 1;
4188         UnlockMutex(mutex1);
4189         LockMutex(mutex4);
4190         UnlockMutex(mutex2);
4191     }
4192     return 1;
4193 }
4194 
requestSetMenus()4195 long FXTerminal::requestSetMenus()
4196 {
4197     char **modules = modules_list,
4198          **switches =  switches_list;
4199     FXMenuPane *loadMenu, *switchMenu, *tempMenu;
4200     if (modules != NULL && *modules!=NULL)
4201     {   loadMenu = new FXMenuPane(main_window);
4202 // There is an amazing bit of messing about here! I accept a raw list of
4203 // names, but if I just put them all as menu items directly that could lead
4204 // to an objectionably long menu. So I bunch items alphabetically keeeping
4205 // each block either starting with a single letter or no longer than 20
4206 // items. These bunches then form sub-menus.
4207         int firstletter = 'a';
4208         int lastletter = 'a', nextletter;
4209         int count = 0, nextcount;
4210         char **p = modules;
4211         while (*p && (*p)[1] == lastletter) count++, p+=2;
4212         char **p1 = p;
4213         while (*modules)
4214         {   for (;;)
4215             {   nextcount = 0;
4216                 nextletter = lastletter + 1;
4217                 if (lastletter == 'z') break;
4218                 while (*p && (*p)[1] == nextletter) nextcount++, p+=2;
4219                 if (count + nextcount > 20) break;
4220                 lastletter = nextletter;
4221                 count += nextcount;
4222                 p1 = p;
4223             }
4224             char subname[8];
4225             if (firstletter == lastletter) sprintf(subname, "%c", firstletter);
4226             else sprintf(subname, "%c-%c", firstletter, lastletter);
4227             tempMenu = new FXMenuPane(main_window);
4228             while (modules != p1)
4229             {   FXMenuCommand *m =
4230                     new FXMenuCommand(tempMenu, 1+*modules++, NULL,
4231                         (FXObject *)text, FXTerminal::ID_LOAD_MODULE);
4232                 *modules++ = (char *)m;
4233             }
4234             new FXMenuCascade(loadMenu, subname, NULL, tempMenu);
4235             firstletter = lastletter = nextletter;
4236             count = nextcount;
4237             p1 = p;
4238         }
4239         FXMenuTitle *tt =
4240             new FXMenuTitle(main_menu_bar, "Load P&ackage", NULL, loadMenu);
4241         tt->create();
4242     }
4243 // Now do roughly the same with switches
4244     if (switches != NULL && *switches != NULL)
4245     {   switchMenu = new FXMenuPane(main_window);
4246         int firstletter = 'a';
4247         int lastletter = 'a', nextletter;
4248         int count = 0, nextcount;
4249         char **p = switches;
4250         p = switches;
4251         while (*p && (*p)[1] == lastletter) count++, p+=2;
4252         char **p1 = p;
4253         while (*switches)
4254         {   for (;;)
4255             {   nextcount = 0;
4256                 nextletter = lastletter + 1;
4257                 if (lastletter == 'z') break;
4258                 while (*p && (*p)[1] == nextletter) nextcount++, p+=2;
4259                 if (count + nextcount > 20) break;
4260                 lastletter = nextletter;
4261                 count += nextcount;
4262                 p1 = p;
4263             }
4264             char subname[8];
4265             if (firstletter == lastletter) sprintf(subname, "%c", firstletter);
4266             else sprintf(subname, "%c-%c", firstletter, lastletter);
4267             if (count > 24)
4268             {   int chunks = count/18;
4269                 if (chunks == 1) chunks = 2;
4270                 int step = (count+chunks-1)/chunks;
4271                 tempMenu = new FXMenuPane(main_window);
4272                 for (int i=0; i<chunks; i++)
4273                 {   FXMenuPane *sub = new FXMenuPane(main_window);
4274                     char partname[10];
4275                     sprintf(partname, "Part %d", i+1);
4276                     for (int j=0; j<step; j++)
4277                     {   if (*switches==NULL || switches==p1) break;
4278                         const char *name = *switches++;
4279                         FXMenuCheck *cc = new FXMenuCheck(sub, 1+name,
4280                             (FXObject *)text, FXTerminal::ID_FLIP_SWITCH);
4281                         *switches++ = (char *)cc;
4282                         cc->setCheck(*name=='y' ? TRUE : FALSE);
4283                         if (*name=='x') cc->disable();
4284                         else cc->enable();
4285                     }
4286                     new FXMenuCascade(tempMenu, partname, NULL, sub);
4287                 }
4288             }
4289             else
4290             {   tempMenu = new FXMenuPane(main_window);
4291                 while (*switches && switches != p1)
4292                 {   const char *name = *switches++;
4293                     FXMenuCheck *cc = new FXMenuCheck(tempMenu, 1+name,
4294                         (FXObject *)text, FXTerminal::ID_FLIP_SWITCH);
4295                     *switches++ = (char *)cc;
4296                     cc->setCheck(*name=='y' ? TRUE : FALSE);
4297                     if (*name=='x') cc->disable();
4298                     else cc->enable();
4299                 }
4300             }
4301             new FXMenuCascade(switchMenu, subname, NULL, tempMenu);
4302 
4303             firstletter = lastletter = nextletter;
4304             count = nextcount;
4305             p1 = p;
4306         }
4307         FXMenuTitle *tt =
4308             new FXMenuTitle(main_menu_bar, "&Switch", NULL, switchMenu);
4309         tt->create();
4310     }
4311     main_menu_bar->recalc();
4312     main_menu_bar->update();
4313 // Having done all that I can re-sync with the worker thread.
4314     if (sync_even)
4315     {   LockMutex(mutex1);
4316         sync_even = 0;
4317         UnlockMutex(mutex3);
4318         LockMutex(mutex2);
4319         UnlockMutex(mutex4);
4320     }
4321     else
4322     {   LockMutex(mutex3);
4323         sync_even = 1;
4324         UnlockMutex(mutex1);
4325         LockMutex(mutex4);
4326         UnlockMutex(mutex2);
4327     }
4328     return 1;
4329 }
4330 
requestRefreshSwitches()4331 long FXTerminal::requestRefreshSwitches()
4332 {
4333     char **switches =  switches_list;
4334     char **modules = modules_list;
4335     while (switches != NULL && *switches != NULL)
4336     {   char *sw = *switches++;
4337         FXMenuCheck *m = (FXMenuCheck *)(*switches++);
4338         switch (*sw)
4339         {
4340     default:break;
4341     case 'X':
4342             m->setCheck(FALSE);
4343             m->disable();
4344             *sw = 'x';
4345             break;
4346     case 'Y':
4347             m->enable();
4348     case 0x3f&'Y':
4349             m->setCheck(TRUE);
4350             *sw = 'y';
4351             break;
4352     case 'N':
4353             m->enable();
4354     case 0x3f&'N':
4355             m->setCheck(FALSE);
4356             *sw = 'n';
4357             break;
4358         }
4359     }
4360     while (modules != NULL && *modules != NULL)
4361     {   char *sw = *modules++;
4362         FXMenuCommand *m = (FXMenuCommand *)(*modules++);
4363         switch (*sw)
4364         {
4365     default:break;          // a blank says "currently enabled"
4366     case 'X':               // the "X" said "disable now"
4367             m->disable();
4368             *sw = 'y';      // the "y" says "done that"
4369             break;
4370         }
4371     }
4372 // Having done all that I can re-sync with the worker thread.
4373     if (sync_even)
4374     {   LockMutex(mutex1);
4375         sync_even = 0;
4376         UnlockMutex(mutex3);
4377         LockMutex(mutex2);
4378         UnlockMutex(mutex4);
4379     }
4380     else
4381     {   LockMutex(mutex3);
4382         sync_even = 1;
4383         UnlockMutex(mutex1);
4384         LockMutex(mutex4);
4385         UnlockMutex(mutex2);
4386     }
4387     return 1;
4388 }
4389 
4390 
requestRequestInput()4391 long FXTerminal::requestRequestInput()
4392 {
4393 // The sequence needs to be
4394 //   worker requests another line of input.
4395 //       GUI flushes all pending output to screen
4396 //       GUI displays a prompt and enabled the keyboard
4397 //   worker must remain suspended while GUI does its stuff
4398 //       GUI eventually sees a CR from the user. Transfers data
4399 //       to the worker and releases it to run.
4400     if (sync_even) LockMutex(mutex1);
4401     else LockMutex(mutex3);
4402     int x;
4403 // When I get here I have just interlocked with the worker task. If an
4404 // interrupt has been posted but not yet accepted I will return at once
4405 // with a "^C" or "^G" as relevant, and hope that the worker then picks up
4406 // the interrupt promptly.
4407     if (async_interrupt_callback != NULL &&
4408         (x = (*async_interrupt_callback)(QUERY_INTERRUPT)) != 0)
4409     {   inputBuffer[0] = '\n';
4410         inputBuffer[1] = 0;
4411         inputBufferLen = 1;
4412         inputBufferP = 0;
4413         if (sync_even)
4414         {   sync_even = 0;
4415             UnlockMutex(mutex3);
4416             LockMutex(mutex2);
4417             UnlockMutex(mutex4);
4418         }
4419         else
4420         {   sync_even = 1;
4421             UnlockMutex(mutex1);
4422             LockMutex(mutex4);
4423             UnlockMutex(mutex2);
4424         }
4425         recently_flushed = 0;
4426         if (pauseFlags & PAUSE_DISCARD)
4427             main_window->setTitle(window_full_title);
4428         pauseFlags &= ~PAUSE_DISCARD;
4429         FXText::appendText(x == QUIET_INTERRUPT ? "^C" : "^G", 2);
4430         long r = FXText::onCmdInsertNewline(this, 0, NULL);
4431         setEditable(FALSE);
4432         setFocus();
4433         return r;
4434     }
4435     if (fwin_in != fwin_out && (pauseFlags & PAUSE_DISCARD) == 0)
4436     {   if (fwin_in > fwin_out)
4437             FXText::appendText(&fwin_buffer[fwin_out], fwin_in-fwin_out);
4438         else
4439         {   FXText::appendText(&fwin_buffer[fwin_out], FWIN_BUFFER_SIZE-fwin_out);
4440             FXText::appendText(&fwin_buffer[0], fwin_in);
4441         }
4442         fwin_out = fwin_in;
4443     }
4444     if (pauseFlags & PAUSE_DISCARD) main_window->setTitle(window_full_title);
4445     pauseFlags &= ~PAUSE_DISCARD;
4446     FXText::appendStyledText(promptString, promptLength, STYLE_PROMPT);
4447     promptEnd = length; // start of final line, list after the prompt
4448     makePositionVisible(rowStart(length));
4449     makePositionVisible(length);
4450     setCursorPos(length);
4451 // Now having displayed the prompt, I leave the worker thread locked
4452 // until the user types ENTER, at which stage I will complete the
4453 // handshake. At this stage I "unlock the keyboard" by making the
4454 // object editable.
4455     setEditable(TRUE);
4456 // At this stage I will stact tracking whether keys have been pressed.
4457     keyFlags &= ~ANY_KEYS;
4458 // Hah - before I return from this procedure and hence before allowing anything
4459 // else to happen in this thread I will check the type-ahead buffer and move
4460 // across characters from it as relevant. And I make any pending paste process
4461 // take prioity even over typed-ahead stuff. insertFromPaste returns true if
4462 // it inserts a segment that should end with a carriage return.
4463     if (paste_buffer && insertFromPaste())
4464     {   // I want the input line to be in a special colour
4465         changeStyle(promptEnd, length-promptEnd, STYLE_INPUT);
4466         return onCmdInsertNewline(this, 0, NULL);
4467     }
4468     while (type_out != type_in)
4469     {   int ch[4];
4470         keyFlags |= ANY_KEYS;
4471 //@@@ This bit seems to squash type-ahead characters to 8 bits...
4472 //@@@ I should UTF8 them...
4473         ch[0] = ahead_buffer[type_out++];
4474         ch[1] = 0;
4475         if (type_out == TYPEAHEAD_SIZE) type_out = 0;
4476 // The actions might be compared with what FXText does when a character
4477 // is to be inserted. But here the type-ahead nature of things means that
4478 // we can not possibly have a selection spanning the insert point. Also I
4479 // do not support overstrike mode for type-ahead. So it ends up very simple!
4480         killSelection(TRUE);
4481         switch (ch[0])
4482         {
4483     case '\n':  // I want the input line to be in a special colour
4484             changeStyle(promptEnd, length-promptEnd, STYLE_INPUT);
4485             return onCmdInsertNewline(this, 0, NULL);
4486     case '\t':
4487             onCmdInsertTab(this, 0, NULL);
4488             break;
4489     default:
4490 //@@@ Think about Unicode here please.
4491             onCmdInsertString(this, 0, (void *)ch);
4492             changeStyle(promptEnd, length-promptEnd, STYLE_INPUT);
4493             break; // out of the switch but not out of the while loop.
4494         }
4495     }
4496     return 1;
4497 }
4498 
onTimeout(FXObject * c,FXSelector s,void * p)4499 long FXTerminal::onTimeout(FXObject *c, FXSelector s, void *p)
4500 {
4501     UNUSED_ARG(c); UNUSED_ARG(s); UNUSED_ARG(p);
4502 // This is called (about) one per second. If within the last couple of
4503 // second the worker thread flushed output buffers then nothing happens. If
4504 // however the screen has not been updated for a couple of second and
4505 // there is buffered output then the buffer is flushed. The idea is that
4506 // I can do tolerably enthusiastic buffering of output so that I avoid
4507 // as much synchronisation and GUI overhead, but still be assured that the
4508 // screen remains silent for at worst a second or two.
4509 //
4510 // I will also want to update information on the title-bar here I suspect,
4511 // but that is not implemented yet.
4512 //
4513 // Restart the timer so I get a continuing stream of ticks.
4514     application_object->addTimeout(this, ID_TIMEOUT, 1000, NULL);
4515     if (++recently_flushed < 2) return 0;
4516 // When this handler is triggered it is in the interface thread and so
4517 // no other interface code is running. This it may update fwin_out. However
4518 // it is not interlocked with the worker thread so it MUST NOT allter fwin_in.
4519     if (fwin_in != fwin_out && (pauseFlags & PAUSE_DISCARD) == 0)
4520     {   if (fwin_in > fwin_out)
4521             FXText::appendText(&fwin_buffer[fwin_out], fwin_in-fwin_out);
4522         else
4523         {   FXText::appendText(&fwin_buffer[fwin_out], FWIN_BUFFER_SIZE-fwin_out);
4524             FXText::appendText(&fwin_buffer[0], fwin_in);
4525         }
4526         makePositionVisible(rowStart(length));
4527     }
4528     fwin_out = fwin_in;
4529     recently_flushed = 0;
4530     return 1;
4531 }
4532 
4533 // Repaint lines of text. Note that visrows MUST be arranged to
4534 // reflect displayed maths so that one display expression is one "row".
4535 
drawContents(FXDCWindow & dc,FXint x,FXint y,FXint w,FXint h) const4536 void FXTerminal::drawContents(FXDCWindow& dc,FXint x,FXint y,FXint w,FXint h) const {
4537   FXint hh=font->getFontHeight();
4538   FXint yy=pos_y+margintop+toprow*hh;
4539   FXint tl=(y-yy)/hh;
4540   FXint bl=(y+h-yy)/hh;
4541   FXint ln;
4542   if(tl<0) tl=0;
4543   if(bl>=nvisrows) bl=nvisrows-1;
4544 // Now if I have any mathematical expression that is to be displayed I want to
4545 // call drawTextRow exactly once. I will arrange drawTextRow so that it draws
4546 // the whole formula whichever of the rows that make it up get passed (and I
4547 // want that so I can cope with cases where the formula is only partly on the
4548 // screen).
4549 // To cope with all this I consider 3 sorts of rows
4550 //   (A)  0x02 0x02 ...     maths but another part of same line is to come
4551 //   (B)  0x02 <else>       final row of a maths line
4552 //   (C)  <else>            non-maths
4553 // and I can pretend that just before the top line to draw there had
4554 // been a C. Here is a regular grammar to show what I do, with actions
4555 // in parentheses and comments in brackets:
4556 //            S -> A (drawmath) T [first sight of a maths row sequence]
4557 //            S -> B (drawmath) S [maths formula on one row]
4558 //            S -> C (draw) S     [ordinary line]
4559 //            T -> A T            [follow on rows in one maths line]
4560 //            T -> B S            [final row of a formula]
4561 //            T -> C (draw) S     [can never arise]
4562   int inMath = 'S';
4563   for(ln=tl; ln<=bl; ln++){
4564     int linebeg=visrows[ln];
4565 // Maths data has "0x02" bytes to introduce it, but it has to be in STYLE_MATH
4566 // as well.
4567     int c1 = linebeg<length ?
4568              (getStyle(linebeg) & STYLE_MATH ? getByte(linebeg) : 'x') :
4569              'x';
4570     int c2 = linebeg+1<length ? getByte(linebeg+1) : 'x';
4571     if (inMath == 'S')
4572     {   if (c1 == 0x02 && c2 == 0x02)
4573         {   inMath = 'T';
4574             drawTextRow(dc,ln,x,x+w);
4575         }
4576         else drawTextRow(dc,ln,x,x+w);
4577     }
4578     else
4579     {   if (c1 != 0x02) drawTextRow(dc,ln,x,x+w);
4580         if (c1 != 0x02 || c2 != 0x02) inMath = 'S';
4581     }
4582   }
4583 }
4584 
4585 
4586 // Draw partial text line with correct style. The purpose of this
4587 // over-ride ofthe FXText version is to support FXShowMath stuff, which
4588 // is triggered by having a special marker character at the start and
4589 // and of a line.
4590 
drawTextRow(FXDCWindow & dc,FXint line,FXint left,FXint right) const4591 void FXTerminal::drawTextRow(FXDCWindow& dc,FXint line,FXint left,FXint right) const {
4592   FXint x,y,w,h,linebeg,lineend,truelineend,cw,sp,ep,row,edge;
4593   FXuint curstyle,newstyle;
4594   linebeg=visrows[line];
4595   lineend=truelineend=visrows[line+1];
4596   if(linebeg<lineend && Ascii::isSpace(getByte(lineend-1))) lineend--;         // Back off last space
4597   int firstThis = linebeg < length ? getByte(linebeg) : 'x';
4598   if (firstThis == 0x02)
4599   { lineend=lineEnd(linebeg);  // I want the true end of the LINE not the end
4600                                 // of the ROW here...
4601     int realbeg=lineStart(linebeg);
4602 // Now a bit of a messy issue. I may be drawing something that was passed as
4603 // the second or third row of a single formula, but I want to display the
4604 // whole thing. This can arise eg when a window has been scrolled so that
4605 // the top of a formula will not be visible. I will therefore step
4606 // back to the start of the line and adjust my y position accordingly.
4607     line-=(linebeg-realbeg);
4608     charPointer = linebeg+1;
4609 // now I may be at something other than the final row of a formula, so I will
4610 // need to skip over any extra 0x02 chars that there might be.
4611     while (charPointer<length && getByte(charPointer)==0x02) charPointer++;
4612     int extraLines=charPointer-realbeg-1;
4613 // I will now reset the charPointer to the logical start of the stuff, ie
4614 // pointing at the (last) 0x02. With that the offsets that I need to access
4615 // bits of the row header are as they were when I was creating the data.
4616     charPointer--;
4617     h=font->getFontHeight();
4618     int extra=extraLines*h;
4619 // Oh how HATEFUL C++ is at times! This method is flagged as "const" and I can
4620 // not change that because of the inheritance rules. But getDefaultWidth is
4621 // not (even though it does not actually change anything!). However "text"
4622 // is a reference to the FXTerminal (ie to "this") so I can go via that!
4623     x=text->getDefaultWidth();
4624     y=pos_y+margintop+(toprow+line)*h;
4625     edge=pos_x+marginleft+barwidth;
4626 // Recover the scale that is to be used.
4627     int scale = getByte(charPointer+2) & 0x07;
4628     int indent = (getByte(charPointer+1) - '0') & 0x3f;
4629     indent += ((getByte(charPointer+2) - '0') & 0x38) << 6;
4630     setMathsFontScale(scale);
4631 // Get pointer to box structure for the formula, or NULL if it has been
4632 // discarded because of space limitations.
4633     Box *b = getBoxAddress(charPointer+3);
4634     if (b == NULL)
4635     {   int pp = charPointer;
4636         charPointer += 7;  // Point at start of TeX stuff
4637 // Parse again to re-create a box that had gone away. This time it happens
4638 // that my variables are set up so (pp+2) is the location for the reference to
4639 // the box, ie the "owner" info.
4640         findTeXstart();
4641         b = parseTeX(staticCharForShowMath, pp+3);
4642         if (b == NULL) b = makeTopBox(makeTextBox("malformed-TeX-input", 19, 0));
4643         text->recordBoxAddress(pp+3, b);
4644 //****************************************************************************
4645 //** The above line has a side effect of marking the text buffer as "updated".
4646 //** This is MESSY since it is liable to cause the screen to be redrawn
4647 //** AGAIN. This double redraw happens when memory cycling causes a box to
4648 //** need to be re-parsed. If I get very twitchy I will re-implement
4649 //** recordBoxAddress so it does not flag the display as dirty, but for now
4650 //** I will accept the slight performance hit in somewhat unusual cases.
4651 //****************************************************************************
4652 // If created again it needs measuring again.
4653         measureBox(b);
4654 // If the box has been stored from before then it can have its measurements
4655 // refreshed by measureBox1(). This leaves it alone if the font size has not
4656 // changed since it was last measured, but otherwise re-assesses things.
4657     }
4658     else measureBox1(b);
4659 // preserve font & colour across the drawing code.
4660     FXFont *ff = dc.getFont();
4661     FXColor fc = dc.getForeground();
4662 // I paint the background for math output in a different (a sort of pale
4663 // green) colour to help it starnd out.
4664     dc.setForeground(FXRGB(230,255,242));
4665     dc.fillRectangle(edge,y,right-edge,h+extra);
4666     dc.setForeground(FXRGB(0,0,0));  // render maths in BLACK for now
4667 // Try to centre the formula across the line and within its space
4668 // (well if it was a multi-line formula I try to centre the longest line
4669 // at least roughly, and align the left of all others with that)
4670     int fh=b->text.height, fd=b->text.depth;
4671     int delta = (h+extra+fh-fd)/2;
4672     int xoff = (x - b->text.width)/2;  // This would centre it.
4673     if (indent != 0)                   // Multi-line formula fun.
4674     {   indent--;                      // Space on line in units of
4675         indent *= mathWidth;           // mathWidth, and now in pixels
4676         indent /= 2;                   // Now I have indent to centre it.
4677 // Because the recorded "indent" info is not quite reliable I will try to
4678 // adjust it to avoid spilling over edges even in truly dire cases.
4679         if (indent+b->text.width >= x) indent = x-b->text.width-1;
4680         if (indent < 0) indent = 0;
4681         xoff = indent;
4682     }
4683 // Now actually display the formula!
4684     paintBox(&dc, b,  xoff, y+delta);
4685 // restore font and colour.
4686     dc.setForeground(fc);
4687     dc.setFont(ff);
4688 // Whew! Done.
4689     return;
4690   }
4691   x=0;
4692   w=0;
4693   h=font->getFontHeight();
4694   y=pos_y+margintop+(toprow+line)*h;
4695   edge=pos_x+marginleft+barwidth;
4696   row=toprow+line;
4697 
4698   // Scan ahead till until we hit the end or the left edge
4699   for(sp=linebeg; sp<lineend; sp+=getCharLen(sp)){
4700     cw=charWidth(getChar(sp),x);
4701     if(x+edge+cw>=left) break;
4702     x+=cw;
4703     }
4704 
4705   // First style to display
4706   curstyle=style(row,linebeg,lineend,sp);
4707 
4708   // Draw until we hit the end or the right edge
4709   for(ep=sp; ep<lineend; ep+=getCharLen(ep)){
4710     newstyle=style(row,linebeg,truelineend,ep);
4711     if(newstyle!=curstyle){
4712       fillBufferRect(dc,edge+x,y,w,h,curstyle);
4713       if(curstyle&STYLE_TEXT) drawBufferText(dc,edge+x,y,w,h,sp,ep-sp,curstyle);
4714       curstyle=newstyle;
4715       sp=ep;
4716       x+=w;
4717       w=0;
4718       }
4719     cw=charWidth(getChar(ep),x+w);
4720     if(x+edge+w>=right) break;
4721     w+=cw;
4722     }
4723 
4724   // Draw unfinished fragment
4725   fillBufferRect(dc,edge+x,y,w,h,curstyle);
4726   if(curstyle&STYLE_TEXT) drawBufferText(dc,edge+x,y,w,h,sp,ep-sp,curstyle);
4727   x+=w;
4728 
4729   // Fill any left-overs outside of text
4730   if(x+edge<right){
4731     curstyle=style(row,linebeg,truelineend,ep);
4732     fillBufferRect(dc,edge+x,y,right-edge-x,h,curstyle);
4733     }
4734   }
4735 
4736 
4737 
4738 // Draw fragment of text in given style
4739 // This overrides the version in FXText.cpp adding around 1 extra line of code
4740 // to handle the "PROMPT" style and put prompt & input text in different
4741 // colours.
4742 
drawBufferText(FXDCWindow & dc,FXint x,FXint y,FXint,FXint,FXint pos,FXint n,FXuint style1) const4743 void FXTerminal::drawBufferText(FXDCWindow& dc,FXint x,FXint y,FXint,FXint,FXint pos,FXint n,FXuint style1) const {
4744   FXuint index=(style1&STYLE_MASK);
4745   FXColor color;
4746   FXchar str[2];
4747   color=0;
4748   if(hilitestyles && index){                                                    // Get colors from style table
4749     if(style1&STYLE_SELECTED) color=hilitestyles[index-1].selectForeColor;
4750     else if(style1&STYLE_HILITE) color=hilitestyles[index-1].hiliteForeColor;
4751     if(color==0) color=hilitestyles[index-1].normalForeColor;                   // Fall back on normal foreground color
4752     }
4753   if(color==0){                                                                 // Fall back to default style
4754     if(style1&STYLE_SELECTED) color=seltextColor;
4755     else if(style1&STYLE_HILITE) color=hilitetextColor;
4756     if(color==0) color=textColor;                                               // Fall back to normal text color
4757     }
4758   if (style1&FXTerminal::STYLE_PROMPT)
4759   { color=promptColor; // ACN special
4760   }
4761   else if (style1&FXTerminal::STYLE_INPUT)
4762   { color=inputColor; // ACN special
4763   }
4764   dc.setForeground(color);
4765   if(style1&STYLE_CONTROL){
4766     y+=font->getFontAscent();
4767     str[0]='^';
4768     while(pos<gapstart && 0<n){
4769       str[1]=buffer[pos]|0x40;
4770       dc.drawText(x,y,str,2);
4771       x+=font->getTextWidth(str,2);
4772       pos++;
4773       n--;
4774       }
4775     while(0<n){
4776       str[1]=buffer[pos-gapstart+gapend]|0x40;
4777       dc.drawText(x,y,str,2);
4778       x+=font->getTextWidth(str,2);
4779       pos++;
4780       n--;
4781       }
4782     }
4783   else{
4784     y+=font->getFontAscent();
4785     if(pos+n<=gapstart){
4786       dc.drawText(x,y,&buffer[pos],n);
4787       }
4788     else if(pos>=gapstart){
4789       dc.drawText(x,y,&buffer[pos-gapstart+gapend],n);
4790       }
4791     else{
4792       dc.drawText(x,y,&buffer[pos],gapstart-pos);
4793       x+=font->getTextWidth(&buffer[pos],gapstart-pos);
4794       dc.drawText(x,y,&buffer[gapend],pos+n-gapstart);
4795       }
4796     }
4797   }
4798 
4799 
4800 } // end of FX namespace
4801 
4802 // not in the FX namespace...
4803 
4804 int showmathInitialised = 0;
4805 
4806 // End of FXTerminal.cpp
4807