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 // < -> < : -> 4
1146 // > -> > : -> 4
1147 // & -> & : -> 5
1148 // \n -> <br> : -> 4+CRLF
1149 //
1150 // POSSIBLY ' ' ->
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, "<"); p += 4; break;
1215 case '>': strcpy(p, ">"); p += 4; break;
1216 case '&': strcpy(p, "&"); 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 "<", 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