7
* Copyright (c) 1987, 2002 Michael J. Roberts. All Rights Reserved.
9
* Please see the accompanying license file, LICENSE.TXT, for information
10
* on using and copying this software.
14
vmconsol.cpp - TADS 3 console input reader and output formatter
16
Provides console input and output for the TADS 3 built-in function set
17
for the T3 VM, including the output formatter.
19
T3 uses the UTF-8 character set to represent character strings. The OS
20
functions use the local character set. We perform the mapping between
21
UTF-8 and the local character set within this module, so that OS routines
22
see local characters only, not UTF-8.
24
This code is based on the TADS 2 output formatter, but has been
25
substantially reworked for C++, Unicode, and the slightly different
26
TADS 3 formatting model.
32
08/25/99 MJRoberts - created from TADS 2 output formatter
52
/* ------------------------------------------------------------------------ */
54
* Log-file formatter subclass implementation
60
CVmFormatterLog::~CVmFormatterLog()
62
/* close any active log file */
69
int CVmFormatterLog::open_log_file(const char *fname)
71
/* close any existing log file */
78
/* save the filename for later (we'll need it when we close the file) */
79
logfname_ = lib_copy_str(fname);
81
/* open the new file */
82
logfp_ = osfopwt(fname, OSFTLOG);
84
/* return success if we successfully opened the file, failure otherwise */
89
* Set the log file to a file opened by the caller
91
int CVmFormatterLog::set_log_file(const char *fname, osfildef *fp)
93
/* close any existing log file */
100
/* remember the file */
103
/* remember the filename */
104
logfname_ = lib_copy_str(fname);
113
int CVmFormatterLog::close_log_file()
115
/* if we have a file, close it */
118
/* close the handle */
121
/* forget about our log file handle */
124
/* set the system file type to "log file" */
126
os_settype(logfname_, OSFTLOG);
129
/* forget the log file name, if we have one */
132
lib_free_str(logfname_);
141
/* ------------------------------------------------------------------------ */
149
CVmFormatter::~CVmFormatter()
151
/* if we have a table of horizontal tabs, delete it */
155
/* forget the character mapper */
160
* set a new character mapper
162
void CVmFormatter::set_charmap(CCharmapToLocal *cmap)
164
/* add a reference to the new mapper, if we have one */
168
/* release our reference on any old mapper */
170
cmap_->release_ref();
172
/* remember the new mapper */
177
* Write out a line. Text we receive is in the UTF-8 character set.
179
void CVmFormatter::write_text(VMG_ const wchar_t *txt, size_t cnt,
180
const vmcon_color_t *colors, vm_nl_type nl)
183
* Check the "script quiet" mode - this indicates that we're reading
184
* a script and not echoing output to the display. If this mode is
185
* on, and we're writing to the display, suppress this write. If
186
* the mode is off, or we're writing to a non-display stream (such
187
* as a log file stream), show the output as normal.
189
if (!console_->is_quiet_script() || !is_disp_stream_)
196
* Check to see if we've reached the end of the screen, and if so
197
* run the MORE prompt. Note that we don't show a MORE prompt
198
* unless we're in "formatter more mode," since if we're not, then
199
* the OS layer code is taking responsibility for pagination
202
* Note that we suppress the MORE prompt if we're showing a
203
* continuation of a line already partially shown. We only want to
204
* show a MORE prompt at the start of a new line.
206
* Skip the MORE prompt if this stream doesn't use it.
208
if (formatter_more_mode()
209
&& console_->is_more_mode()
211
&& linecnt_ + 1 >= console_->get_page_length())
213
/* set the standard text color */
214
set_os_text_color(OS_COLOR_P_TEXT, OS_COLOR_P_TEXTBG);
217
/* display the MORE prompt */
218
console_->show_more_prompt(vmg0_);
220
/* restore the current color scheme */
221
set_os_text_color(os_color_.fg, os_color_.bg);
222
set_os_text_attr(os_color_.attr);
225
/* count the line if a newline follows */
226
if (nl != VM_NL_NONE && nl != VM_NL_NONE_INTERNAL)
229
/* convert and display the text */
230
for (dst = local_buf, rem = sizeof(local_buf) - 1 ; cnt != 0 ; )
237
* if this character is in a new color, write out the OS-level
240
if (colors != 0 && !colors->equals(&os_color_))
243
* null-terminate and display what's in the buffer so far,
244
* so that we close out all of the remaining text in the
245
* old color and attributes
248
print_to_os(local_buf);
250
/* reset to the start of the local output buffer */
252
rem = sizeof(local_buf) - 1;
254
/* set the text attributes, if they changed */
255
if (colors->attr != os_color_.attr)
256
set_os_text_attr(colors->attr);
258
/* set the color, if it changed */
259
if (colors->fg != os_color_.fg
260
|| colors->bg != os_color_.bg)
261
set_os_text_color(colors->fg, colors->bg);
264
* Whatever happened, set our new color internally as the
265
* last color we sent to the OS. Even if we didn't
266
* actually do anything, we'll at least know we won't have
267
* to do anything more until we find another new color.
272
/* get this character */
276
* translate non-breaking spaces into ordinary spaces if the
277
* underlying target isn't HTML-based
279
if (!html_target_ && c == 0x00A0)
282
/* try storing another character */
284
cur = (cmap_ != 0 ? cmap_ : G_cmap_to_ui)->map(c, &dst, &rem);
286
/* if that failed, flush the buffer and try again */
289
/* null-terminate the buffer */
292
/* display the text */
293
print_to_os(local_buf);
295
/* reset to the start of the local output buffer */
297
rem = sizeof(local_buf) - 1;
301
/* we've now consumed this character of input */
309
/* if we have a partially-filled buffer, display it */
312
/* null-terminate and display the buffer */
314
print_to_os(local_buf);
317
/* write the appropriate type of line termination */
322
case VM_NL_NONE_INTERNAL:
323
/* no line termination is needed */
327
/* write a newline */
328
print_to_os(html_target_ ? "<BR HEIGHT=0>\n" : "\n");
331
case VM_NL_OSNEWLINE:
333
* the OS will provide a newline, but add a space to make it
334
* explicit that we can break the line here
342
/* ------------------------------------------------------------------------ */
344
* Flush the current line to the display, using the given type of line
347
* VM_NL_NONE: flush the current line but do not start a new line; more
348
* text will follow on the current line. This is used, for example, to
349
* flush text after displaying a prompt and before waiting for user
352
* VM_NL_INPUT: acts like VM_NL_NONE, except that we flush everything,
353
* including trailing spaces.
355
* VM_NL_NONE_INTERNAL: same as VM_NL_NONE, but doesn't flush at the OS
356
* level. This is used when we're only flushing our buffers in order to
357
* clear out space internally, not because we want the underlying OS
358
* renderer to display things immediately. This distinction is
359
* important in HTML mode, since it ensures that the HTML parser only
360
* sees well-formed strings when flushing.
362
* VM_NL_NEWLINE: flush the line and start a new line by writing out a
365
* VM_NL_OSNEWLINE: flush the line as though starting a new line, but
366
* don't add an actual newline character to the output, since the
367
* underlying OS display code will handle this. Instead, add a space
368
* after the line to indicate to the OS code that a line break is
369
* possible there. (This differs from VM_NL_NONE in that VM_NL_NONE
370
* doesn't add anything at all after the line.)
372
void CVmFormatter::flush(VMG_ vm_nl_type nl)
377
/* null-terminate the current output line buffer */
378
linebuf_[linepos_] = '\0';
381
* Expand any pending tab. Allow "anonymous" tabs only if we're
382
* flushing because we're ending the line normally; if we're not
383
* ending the line, we can't handle tabs that depend on the line
386
expand_pending_tab(vmg_ nl == VM_NL_NEWLINE);
389
* note number of characters to display - assume we'll display all of
390
* the characters in the buffer
392
cnt = wcslen(linebuf_);
395
* Trim trailing spaces, unless we're about to read input or are doing
396
* an internal flush. (Show trailing spaces when reading input, since
397
* we won't be able to revise the layout after this point. Don't trim
398
* on an internal flush either, as this kind of flushing simply empties
399
* out our buffer exactly as it is.)
401
if (nl != VM_NL_INPUT && nl != VM_NL_NONE_INTERNAL)
404
* look for last non-space character, but keep any spaces that come
405
* before an explicit non-breaking flag
407
for ( ; cnt > 0 && linebuf_[cnt-1] == ' ' ; --cnt)
409
/* don't remove this character if it's marked as non-breaking */
410
if ((flagbuf_[cnt-1] & VMCON_OBF_NOBREAK) != 0)
415
* if we're actually doing a newline, discard the trailing spaces
416
* for good - we don't want them at the start of the next line
418
if (nl == VM_NL_NEWLINE)
422
/* check the newline mode */
426
case VM_NL_NONE_INTERNAL:
427
/* no newline - just flush out what we have */
428
write_nl = VM_NL_NONE;
432
/* no newline - flush out what we have */
433
write_nl = VM_NL_NONE;
435
/* on input, reset the HTML parsing state */
436
html_passthru_state_ = VMCON_HPS_NORMAL;
441
* We're adding a newline. We want to suppress redundant
442
* newlines -- we reduce any run of consecutive vertical
443
* whitespace to a single newline. So, if we have anything in
444
* this line, or we didn't already just write a newline, write
445
* out a newline now; otherwise, write nothing.
447
if (linecol_ != 0 || !just_did_nl_)
449
/* add the newline */
450
write_nl = VM_NL_NEWLINE;
455
* Don't write out a newline after all - the line buffer is
456
* empty, and we just wrote a newline, so this is a
457
* redundant newline that we wish to suppress (so that we
458
* collapse a run of vertical whitespace down to a single
461
write_nl = VM_NL_NONE;
465
case VM_NL_OSNEWLINE:
467
* we're going to depend on the underlying OS output layer to do
468
* line breaking, so we won't add a newline, but we will add a
469
* space, so that the underlying OS layer knows we have a word
472
write_nl = VM_NL_OSNEWLINE;
477
* display the line, as long as we have something buffered to
478
* display; even if we don't, display it if our column is non-zero
479
* and we didn't just do a newline, since this must mean that we've
480
* flushed a partial line and are just now doing the newline
482
if (cnt != 0 || (linecol_ != 0 && !just_did_nl_))
485
write_text(vmg_ linebuf_, cnt, colorbuf_, write_nl);
488
/* check the line ending */
493
/* we're not displaying a newline, so flush what we have */
497
* the subsequent buffer will be a continuation of the current
498
* text, if we've displayed anything at all here
500
is_continuation_ = (linecol_ != 0);
503
case VM_NL_NONE_INTERNAL:
505
* internal buffer flush only - subsequent text will be a
506
* continuation of the current line, if there's anything on the
509
is_continuation_ = (linecol_ != 0);
513
/* we displayed a newline, so reset the column position */
516
/* the next buffer starts a new line on the display */
517
is_continuation_ = FALSE;
522
* Move any trailing characters we didn't write in this go to the start
529
/* calculate how many trailing characters we didn't write */
530
movecnt = linepos_ - cnt;
532
/* move the characters, colors, and flags */
533
memmove(linebuf_, linebuf_ + cnt, movecnt * sizeof(linebuf_[0]));
534
memmove(colorbuf_, colorbuf_ + cnt, movecnt * sizeof(colorbuf_[0]));
535
memmove(flagbuf_, flagbuf_ + cnt, movecnt * sizeof(flagbuf_[0]));
538
/* move the line output position to follow the preserved characters */
542
* If we just output a newline, note it. If we didn't just output a
543
* newline, but we did write out anything else, note that we're no
544
* longer at the start of a line on the underlying output device.
546
if (nl == VM_NL_NEWLINE)
549
just_did_nl_ = FALSE;
552
* if the current buffering color doesn't match the current osifc-layer
553
* color, then we must need to flush just the new color/attribute
554
* settings (this can happen when we have changed the attributes in
555
* preparation for reading input, since we won't have any actual text
556
* to write after the color change)
558
if (!cur_color_.equals(&os_color_))
560
/* set the text attributes in the OS window, if they changed */
561
if (cur_color_.attr != os_color_.attr)
562
set_os_text_attr(cur_color_.attr);
564
/* set the color in the OS window, if it changed */
565
if (cur_color_.fg != os_color_.fg
566
|| cur_color_.bg != os_color_.bg)
567
set_os_text_color(cur_color_.fg, cur_color_.bg);
569
/* set the new osifc color */
570
os_color_ = cur_color_;
574
/* ------------------------------------------------------------------------ */
576
* Clear out our buffers
578
void CVmFormatter::empty_buffers(VMG0_)
580
/* reset our buffer pointers */
584
just_did_nl_ = FALSE;
585
is_continuation_ = FALSE;
587
/* there's no pending tab now */
588
pending_tab_align_ = VMFMT_TAB_NONE;
590
/* start out at the first line */
593
/* reset the HTML lexical state */
594
html_passthru_state_ = VMCON_HPS_NORMAL;
597
/* ------------------------------------------------------------------------ */
599
* Immediately update the display window
601
void CVmFormatter::update_display(VMG0_)
603
/* update the display window at the OS layer */
607
/* ------------------------------------------------------------------------ */
609
* Display a blank line to the stream
611
void CVmFormatter::write_blank_line(VMG0_)
613
/* flush the stream */
614
flush(vmg_ VM_NL_NEWLINE);
616
/* if generating for an HTML display target, add an HTML line break */
618
write_text(vmg_ L"<BR>", 4, 0, VM_NL_NONE);
620
/* write out a blank line */
621
write_text(vmg_ L"", 0, 0, VM_NL_NEWLINE);
624
/* ------------------------------------------------------------------------ */
626
* Generate a tab for a "\t" sequence in the game text, or a <TAB
627
* MULTIPLE> or <TAB INDENT> sequence parsed in our mini-parser.
629
* Standard (non-HTML) version: we'll generate enough spaces to take us to
632
* HTML version: if we're in native HTML mode, we'll just generate the
633
* equivalent HTML; if we're not in HTML mode, we'll generate a hard tab
634
* character, which the HTML formatter will interpret as a <TAB
637
void CVmFormatter::write_tab(VMG_ int indent, int multiple)
641
/* check to see what the underlying system is expecting */
647
* the underlying system is HTML - generate an appropriate <TAB>
648
* sequence to produce the desired effect
650
sprintf(buf, "<TAB %s=%d>",
651
indent != 0 ? "INDENT" : "MULTIPLE",
652
indent != 0 ? indent : multiple);
655
buffer_string(vmg_ buf);
657
else if (multiple != 0)
659
/* get the maximum column */
660
maxcol = get_buffer_maxcol();
663
* We don't have an HTML target, and we have a tab to an every-N
664
* stop: expand the tab with spaces. Keep going until we reach
665
* the next tab stop of the given multiple.
669
/* stop if we've reached the maximum column */
670
if (linecol_ >= maxcol)
673
/* add another space */
674
linebuf_[linepos_] = ' ';
675
flagbuf_[linepos_] = cur_flags_;
676
colorbuf_[linepos_] = cur_color_;
678
/* advance one character in the buffer */
681
/* advance the column counter */
683
} while ((linecol_ + 1) % multiple != 0);
685
else if (indent != 0)
688
* We don't have an HTML target, and we just want to add a given
689
* number of spaces. Simply write out the given number of spaces,
690
* up to our maximum column limit.
692
for (maxcol = get_buffer_maxcol() ;
693
indent != 0 && linecol_ < maxcol ; --indent)
695
/* add another space */
696
linebuf_[linepos_] = ' ';
697
flagbuf_[linepos_] = cur_flags_;
698
colorbuf_[linepos_] = cur_color_;
700
/* advance one character in the buffer and one column */
708
/* ------------------------------------------------------------------------ */
712
void CVmFormatter::flush_line(VMG_ int padding)
715
* check to see if we're using the underlying display layer's line
721
* In the HTML version, we don't need the normal *MORE*
722
* processing, since the HTML layer will handle that.
723
* Furthermore, we don't need to provide actual newline breaks
724
* -- that happens after the HTML is parsed, so we don't have
725
* enough information here to figure out actual line breaks.
726
* So, we'll just flush out our buffer whenever it fills up, and
729
* Similarly, if we have OS-level line wrapping, don't try to
730
* figure out where the line breaks go -- just flush our buffer
731
* without a trailing newline whenever the buffer is full, and
732
* let the OS layer worry about formatting lines and paragraphs.
734
* If we're using padding, use newline mode VM_NL_OSNEWLINE. If
735
* we don't want padding (which is the case if we completely
736
* fill up the buffer without finding any word breaks), write
737
* out in mode VM_NL_NONE, which just flushes the buffer exactly
740
flush(vmg_ padding ? VM_NL_OSNEWLINE : VM_NL_NONE_INTERNAL);
745
* Normal mode - we process the *MORE* prompt ourselves, and we
746
* are responsible for figuring out where the actual line breaks
747
* go. Use flush() to generate an actual newline whenever we
748
* flush out our buffer.
750
flush(vmg_ VM_NL_NEWLINE);
755
/* ------------------------------------------------------------------------ */
757
* Write a character to an output stream. The character is provided to us
758
* as a wide Unicode character.
760
void CVmFormatter::buffer_char(VMG_ wchar_t c)
765
/* check for a display expansion */
766
exp = (cmap_ != 0 ? cmap_ : G_cmap_to_ui)->get_expansion(c, &exp_len);
769
/* write each character of the expansion */
770
for ( ; exp_len != 0 ; ++exp, --exp_len)
771
buffer_expchar(vmg_ *exp);
775
/* there's no expansion - buffer the character as-is */
776
buffer_expchar(vmg_ c);
781
* Write an expanded character to an output stream.
783
void CVmFormatter::buffer_expchar(VMG_ wchar_t c)
787
unsigned char cflags;
791
/* presume the character takes up only one column */
794
/* presume we'll use the current flags for the new character */
797
/* assume it's not a quoted space */
801
* Check for some special characters.
803
* If we have an underlying HTML renderer, keep track of the HTML
804
* lexical state, so we know if we're in a tag or in ordinary text. We
805
* can pass through all of the special line-breaking and spacing
806
* characters to the underlying HTML renderer.
808
* If our underlying renderer is a plain text renderer, we actually
809
* parse the HTML ourselves, so HTML tags will never make it this far -
810
* the caller will already have interpreted any HTML tags and removed
811
* them from the text stream, passing only the final plain text to us.
812
* However, with a plain text renderer, we have to do all of the work
813
* of line breaking, so we must look at the special spacing and
814
* line-break control characters.
819
* track the lexical state of the HTML stream going to the
820
* underlying renderer
822
switch (html_passthru_state_)
824
case VMCON_HPS_MARKUP_END:
825
case VMCON_HPS_NORMAL:
826
/* check to see if we're starting a markup */
828
html_passthru_state_ = VMCON_HPS_ENTITY_1ST;
830
html_passthru_state_ = VMCON_HPS_TAG;
832
html_passthru_state_ = VMCON_HPS_NORMAL;
835
case VMCON_HPS_ENTITY_1ST:
836
/* check to see what kind of entity we have */
838
html_passthru_state_ = VMCON_HPS_ENTITY_NUM_1ST;
839
else if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'))
840
html_passthru_state_ = VMCON_HPS_ENTITY_NAME;
842
html_passthru_state_ = VMCON_HPS_NORMAL;
845
case VMCON_HPS_ENTITY_NUM_1ST:
846
/* check to see what kind of number we have */
847
if (c == 'x' || c == 'X')
848
html_passthru_state_ = VMCON_HPS_ENTITY_HEX;
849
else if (c >= '0' && c <= '9')
850
html_passthru_state_ = VMCON_HPS_ENTITY_DEC;
852
html_passthru_state_ = VMCON_HPS_NORMAL;
855
case VMCON_HPS_ENTITY_HEX:
856
/* see if we're done with hex digits */
858
html_passthru_state_ = VMCON_HPS_MARKUP_END;
859
else if ((c < '0' || c > '9')
860
&& (c < 'a' || c > 'f')
861
&& (c < 'A' || c > 'F'))
862
html_passthru_state_ = VMCON_HPS_NORMAL;
865
case VMCON_HPS_ENTITY_DEC:
866
/* see if we're done with decimal digits */
868
html_passthru_state_ = VMCON_HPS_MARKUP_END;
869
else if (c < '0' || c > '9')
870
html_passthru_state_ = VMCON_HPS_NORMAL;
873
case VMCON_HPS_ENTITY_NAME:
874
/* see if we're done with alphanumerics */
876
html_passthru_state_ = VMCON_HPS_MARKUP_END;
877
else if ((c < 'a' || c > 'z')
878
&& (c < 'A' || c > 'Z')
879
&& (c < '0' || c > '9'))
880
html_passthru_state_ = VMCON_HPS_NORMAL;
884
/* see if we're done with the tag, or entering quoted material */
886
html_passthru_state_ = VMCON_HPS_MARKUP_END;
888
html_passthru_state_ = VMCON_HPS_DQUOTE;
890
html_passthru_state_ = VMCON_HPS_SQUOTE;
893
case VMCON_HPS_SQUOTE:
894
/* see if we're done with the quoted material */
896
html_passthru_state_ = VMCON_HPS_TAG;
899
case VMCON_HPS_DQUOTE:
900
/* see if we're done with the quoted material */
902
html_passthru_state_ = VMCON_HPS_TAG;
906
/* ignore other states */
912
/* check for special characters */
917
* The Unicode "soft hyphen" character. This indicates a
918
* point at which we can insert a hyphen followed by a soft
919
* line break, if it's a convenient point to break the line;
920
* if we don't choose to break the line here, the soft hyphen
923
* Don't buffer anything at all; instead, just flag the
924
* preceding character as being a soft hyphenation point, so
925
* that we can insert a hyphen there when we get around to
929
flagbuf_[linepos_ - 1] |= VMCON_OBF_SHY;
931
/* we don't need to buffer anything, so we're done */
936
* The Unicode zero-width non-breaking space. This indicates
937
* a point at which we cannot break the line, even if we could
938
* normally break here. Flag the preceding character as a
939
* non-breaking point. Don't buffer anything for this
940
* character, as it's not rendered; it merely controls line
944
flagbuf_[linepos_ - 1] |= VMCON_OBF_NOBREAK;
946
/* we don't buffer anything, so we're done */
949
case 0x200B: /* zero-width space */
950
case 0x200a: /* hair space */
951
case 0x2008: /* punctuation space */
953
* Zero-width space: This indicates an explicitly allowed
954
* line-breaking point, but is rendered as invisible. Flag the
955
* preceding character as an OK-to-break point, but don't
956
* buffer anything, as the zero-width space isn't rendered.
958
* Hair and punctuation spaces: Treat these very thin spaces as
959
* invisible in a fixed-width font. These are normally used
960
* for subtle typographical effects in proportionally-spaced
961
* fonts; for example, for separating a right single quote from
962
* an immediately following right double quote (as in a
963
* quotation within a quotation: I said, "type 'quote'"). When
964
* translating to fixed-pitch type, these special spacing
965
* effects aren't usually necessary or desirable because of the
966
* built-in space in every character cell.
968
* These spaces cancel any explicit non-breaking flag that
969
* precedes them, since they cause the flag to act on the
970
* space's left edge, while leaving the right edge open for
971
* breaking. Since we don't actually take up any buffer space,
972
* push our right edge's breakability back to the preceding
977
flagbuf_[linepos_ - 1] &= ~VMCON_OBF_NOBREAK;
978
flagbuf_[linepos_ - 1] |= VMCON_OBF_OKBREAK;
981
/* we don't buffer anything, so we're done */
985
/* non-breaking space - buffer it as given */
988
case 0x0015: /* special internal quoted space character */
989
case 0x2005: /* four-per-em space */
990
case 0x2006: /* six-per-em space */
991
case 0x2007: /* figure space */
992
case 0x2009: /* thin space */
994
* Treat all of these as non-combining spaces, and render them
995
* all as single ordinary spaces. In text mode, we are
996
* limited to a monospaced font, so we can't render any
997
* differences among these various thinner-than-normal spaces.
1003
case 0x2002: /* en space */
1004
case 0x2004: /* three-per-em space */
1006
* En space, three-per-em space - mark these as non-combining,
1007
* and render them as a two ordinary spaces. In the case of
1008
* an en space, we really do want to take up the space of two
1009
* ordinary spaces; for a three-per-em space, we want about a
1010
* space and a half, but since we're dealing with a monospaced
1011
* font, we have to round up to a full two spaces.
1019
/* em space - mark it as non-combining */
1022
/* render this as three ordinary spaces */
1029
* Translate any whitespace character to a regular space
1030
* character. Note that, once this is done, we don't need to
1031
* worry about calling t3_is_space() any more - we can just
1032
* check that we have a regular ' ' character.
1036
/* convert it to an ordinary space */
1039
/* if we're in obey-whitespace mode, quote this space */
1040
qspace = obey_whitespace_;
1046
/* if it's a quoted space, mark it as such in the buffer flags */
1048
cflags |= VMCON_OBF_QSPACE;
1051
* Check for the caps/nocaps flags - but only if our HTML lexical state
1052
* in the underlying text stream is plain text, because we don't want
1053
* to apply these flags to alphabetic characters that are inside tag or
1056
if (html_passthru_state_ == VMCON_HPS_NORMAL)
1058
if ((capsflag_ || allcapsflag_) && t3_is_alpha(c))
1060
/* capsflag is set, so capitalize this character */
1063
/* okay, we've capitalized something; clear flag */
1066
else if (nocapsflag_ && t3_is_alpha(c))
1068
/* nocapsflag is set, so minisculize this character */
1071
/* clear the flag now that we've done the job */
1072
nocapsflag_ = FALSE;
1077
* If this is a space of some kind, we might be able to consolidate it
1078
* with a preceding character.
1082
/* ignore ordinary whitespace at the start of a line */
1083
if (linecol_ == 0 && !qspace)
1087
* Consolidate runs of whitespace. Ordinary whitespace is
1088
* subsumed into any type of quoted spaces, but quoted spaces do
1095
/* get the previous character */
1096
prv = linebuf_[linepos_ - 1];
1099
* if the new character is an ordinary (combining) whitespace
1100
* character, subsume it into any preceding space character
1102
if (!qspace && prv == ' ')
1106
* if the new character is a quoted space, and the preceding
1107
* character is a non-quoted space, subsume the preceding
1108
* space into the new character
1112
&& !(flagbuf_[linepos_ - 1] & VMCON_OBF_QSPACE))
1114
/* remove the preceding ordinary whitespace */
1121
/* if the new character fits in the line, add it */
1122
if (linecol_ + cwid < get_buffer_maxcol())
1124
/* buffer this character */
1125
buffer_rendered(c, cflags, cwid);
1127
/* we're finished processing the character */
1132
* The line would overflow if this character were added.
1134
* If we're trying to output any kind of breakable space, just add it
1135
* to the line buffer for now; we'll come back later and figure out
1136
* where to break upon buffering the next non-space character. This
1137
* ensures that we don't carry trailing space (even trailing en or em
1138
* spaces) to the start of the next line if we have an explicit
1139
* newline before the next non-space character.
1144
* We're adding a space, so we'll figure out the breaking later,
1145
* when we output the next non-space character. If the preceding
1146
* character is any kind of space, don't bother adding the new
1147
* one, since any contiguous whitespace at the end of the line has
1148
* no effect on the line's appearance.
1150
if (linebuf_[linepos_ - 1] == ' ')
1153
* We're adding a space to a line that already ends in a
1154
* space, so we don't really need to add the character.
1155
* However, reflect the virtual addition in the output column
1156
* position, since the space does affect our column position.
1157
* We know that we're adding the new space even though we have
1158
* a space preceding, since we wouldn't have gotten this far
1159
* if we were going to collapse the space with a run of
1165
/* the line doesn't already end in space, so add the space */
1166
linebuf_[linepos_] = ' ';
1167
flagbuf_[linepos_] = cflags;
1168
colorbuf_[linepos_] = cur_color_;
1170
/* advance one character in the buffer */
1175
* Adjust the column position for the added space. Note that we
1176
* adjust by the rendered width of the new character even though
1177
* we actually added only one character; we only add one character
1178
* to the buffer to avoid buffer overflow, but the column position
1179
* needs adjustment by the full rendered width. The fact that the
1180
* actual buffer size and rendered width no longer match isn't
1181
* important because the difference is entirely in invisible
1182
* whitespace at the right end of the line.
1191
* We're adding something other than an ordinary space to the line,
1192
* and the new character won't fit, so we must find an appropriate
1193
* point to break the line.
1195
* First, add the new character to the buffer - it could be
1196
* significant in how we calculate the break position. (Note that we
1197
* allocate the buffer with space for one extra character after
1198
* reaching the maximum line width, so we know we have room for this.)
1200
linebuf_[linepos_] = c;
1201
flagbuf_[linepos_] = cur_flags_;
1204
* if the underlying OS layer is doing the line wrapping, just flush
1205
* out the buffer; don't bother trying to do any line wrapping
1206
* ourselves, since this work would just be redundant with what the OS
1207
* layer has to do anyway
1211
/* flush the line, adding no padding after it */
1212
flush_line(vmg_ FALSE);
1215
* we've completely cleared out the line buffer, so reset all of
1216
* the line buffer counters
1221
is_continuation_ = FALSE;
1224
goto done_with_wrapping;
1228
* Scan backwards, looking for a break position. Start at the current
1229
* column: we know we can fit everything up to this point on a line on
1230
* the underlying display, so this is the rightmost possible position
1231
* at which we could break the line. Keep going until we find a
1232
* breaking point or reach the left edge of the line.
1234
for (shy = FALSE, i = linepos_ ; i >= 0 ; --i)
1240
* There are two break modes: word-break mode and break-anywhere
1241
* mode. The modes are applied to each character, via the buffer
1244
* In word-break mode, we can break at any ordinary space, at a
1245
* soft hyphen, just after a regular hyphen, or at any explicit
1246
* ok-to-break point; but we can't break after any character
1247
* marked as a no-break point.
1249
* In break-anywhere mode, we can break between any two
1250
* characters, except that we can't break after any character
1251
* marked as a no-break point.
1254
/* get the current character's flags */
1257
/* get the preceding character's flags */
1258
prvf = (i > 0 ? flagbuf_[i-1] : 0);
1261
* if the preceding character is marked as a no-break point, we
1262
* definitely can't break here, so keep looking
1264
if ((prvf & VMCON_OBF_NOBREAK) != 0)
1268
* if the preceding character is marked as an explicit ok-to-break
1269
* point, we definitely can break here
1271
if ((prvf & VMCON_OBF_OKBREAK) != 0)
1275
* If the current character is in a run of break-anywhere text,
1276
* then we can insert a break just before the current character.
1277
* Likewise, if the preceding character is in a run of
1278
* break-anywhere text, we can break just after the preceding
1279
* character, which is the same as breaking just before the
1280
* current character.
1282
* Note that we must test for both cases to properly handle
1283
* boundaries between break-anywhere and word-break text. If
1284
* we're switching from word-break to break-anywhere text, the
1285
* current character will be marked as break-anywhere, so if we
1286
* only tested the previous character, we'd miss this transition.
1287
* If we're switching from break-anywhere to word-break text, the
1288
* previous character will be marked as break-anywhere, so we'd
1289
* miss the fact that we could break right here (rather than
1290
* before the previous character) if we didn't test it explicitly.
1292
if ((f & VMCON_OBF_BREAK_ANY) != 0
1293
|| (i > 0 && (prvf & VMCON_OBF_BREAK_ANY) != 0))
1297
* If the preceding character is marked as a soft hyphenation
1298
* point, and we're not at the rightmost position, we can break
1299
* here with hyphenation. We can't break with hyphenation at the
1300
* last position because hyphenation requires us to actually
1301
* insert a hyphen character, and we know that at the last
1302
* position we don't have room for inserting another character.
1304
if (i > 0 && i < linepos_ && (prvf & VMCON_OBF_SHY) != 0)
1306
/* note that we're breaking at a soft hyphen */
1309
/* we can break here */
1314
* we can break to the left of a space (i.e., we can break before
1315
* the current character if the current character is a space)
1317
if (linebuf_[i] == ' ')
1321
* We can also break to the right of a space. We need to check
1322
* for this case separately from checking that the current
1323
* charatcer is a space (which breaks to the left of the space),
1324
* because we could have a no-break marker on one side of the
1325
* space but not on the other side.
1327
if (i > 0 && linebuf_[i-1] == ' ')
1331
* If we're to the right of a hyphen, we can break here. However,
1332
* don't break in the middle of a set of consecutive hyphens
1333
* (i.e., we don't want to break up "--" sequences).
1335
if (i > 0 && linebuf_[i-1] == '-' && linebuf_[i] != '-')
1339
/* check to see if we found a good place to break */
1343
* We didn't find a good place to break. If the underlying
1344
* console allows overrunning the line width, simply add the
1345
* character, even though it overflows; otherwise, force a break
1346
* at the line width, even though it doesn't occur at a natural
1349
* In any case, don't let our buffer fill up beyond its maximum
1352
if (!console_->allow_overrun() || linepos_ + 1 >= OS_MAXWIDTH)
1355
* we didn't find any good place to break, and the console
1356
* doesn't allow us to overrun the terminal width - flush the
1357
* entire line as-is, breaking arbitrarily in the middle of a
1360
flush_line(vmg_ FALSE);
1363
* we've completely cleared out the line buffer, so reset all
1364
* of the line buffer counters
1369
is_continuation_ = FALSE;
1374
wchar_t tmpbuf[OS_MAXWIDTH];
1375
vmcon_color_t tmpcolor[OS_MAXWIDTH];
1376
unsigned char tmpflags[OS_MAXWIDTH];
1380
/* null-terminate the line buffer */
1381
linebuf_[linepos_] = '\0';
1383
/* trim off leading spaces on the next line after the break */
1384
for (nxti = i ; linebuf_[nxti] == ' ' ; ++nxti) ;
1387
* The next line starts after the break - save a copy. We actually
1388
* have to save a copy of the trailing material outside the buffer,
1389
* since we might have to overwrite the trailing part of the buffer
1392
tmpchars = wcslen(&linebuf_[nxti]);
1393
memcpy(tmpbuf, &linebuf_[nxti], tmpchars*sizeof(tmpbuf[0]));
1394
memcpy(tmpcolor, &colorbuf_[nxti], tmpchars*sizeof(tmpcolor[0]));
1395
memcpy(tmpflags, &flagbuf_[nxti], tmpchars*sizeof(tmpflags[0]));
1397
/* if we're breaking at a soft hyphen, insert a real hyphen */
1399
linebuf_[i++] = '-';
1401
/* trim off trailing spaces */
1402
for ( ; i > 0 && linebuf_[i-1] == ' ' ; --i)
1404
/* stop if we've reached a non-breaking point */
1405
if ((flagbuf_[i-1] & VMCON_OBF_NOBREAK) != 0)
1409
/* terminate the buffer after the break point */
1412
/* write out everything up to the break point */
1413
flush_line(vmg_ TRUE);
1415
/* move the saved start of the next line into the line buffer */
1416
memcpy(linebuf_, tmpbuf, tmpchars*sizeof(tmpbuf[0]));
1417
memcpy(colorbuf_, tmpcolor, tmpchars*sizeof(tmpcolor[0]));
1418
memcpy(flagbuf_, tmpflags, tmpchars*sizeof(tmpflags[0]));
1419
linecol_ = linepos_ = tmpchars;
1423
/* add the new character to buffer */
1424
buffer_rendered(c, cflags, cwid);
1428
* Write a rendered character to an output stream buffer. This is a
1429
* low-level internal routine that we call from buffer_expchar() to put
1430
* the final rendition of a character into a buffer.
1432
* Some characters render as multiple copies of a single character; 'wid'
1433
* gives the number of copies to store. The caller is responsible for
1434
* ensuring that the rendered representation fits in the buffer and in the
1435
* available line width.
1437
void CVmFormatter::buffer_rendered(wchar_t c, unsigned char flags, int wid)
1439
unsigned char flags_before;
1441
/* note whether or not we have a break before us */
1442
flags_before = (linepos_ > 0
1443
? flagbuf_[linepos_-1] & VMCON_OBF_NOBREAK
1446
/* add the character the given number of times */
1447
for ( ; wid != 0 ; --wid)
1449
/* buffer the character */
1450
linebuf_[linepos_] = c;
1451
flagbuf_[linepos_] = flags;
1452
colorbuf_[linepos_] = cur_color_;
1455
* if this isn't the last part of the character, carry forward any
1456
* no-break flag from the previous part of the character; this will
1457
* ensure that a no-break to the left of the sequence applies to
1458
* the entire sequence
1461
flagbuf_[linepos_] |= flags_before;
1463
/* advance one character in the buffer */
1466
/* adjust our column counter */
1471
/* ------------------------------------------------------------------------ */
1473
* write out a UTF-8 string
1475
void CVmFormatter::buffer_string(VMG_ const char *txt)
1477
/* write out each character in the string */
1478
for ( ; utf8_ptr::s_getch(txt) != 0 ; txt += utf8_ptr::s_charsize(*txt))
1479
buffer_char(vmg_ utf8_ptr::s_getch(txt));
1483
* write out a wide unicode string
1485
void CVmFormatter::buffer_wstring(VMG_ const wchar_t *txt)
1487
/* write out each wide character */
1488
for ( ; *txt != '\0' ; ++txt)
1489
buffer_char(vmg_ *txt);
1493
/* ------------------------------------------------------------------------ */
1495
* Get the next wide unicode character in a UTF8-encoded string, and
1496
* update the string pointer and remaining length. Returns zero if no
1497
* more characters are available in the string.
1499
wchar_t CVmFormatter::next_wchar(const char **s, size_t *len)
1504
/* if there's nothing left, return a null terminator */
1508
/* get this character */
1509
ret = utf8_ptr::s_getch(*s);
1511
/* advance the string pointer and length counter */
1512
charsize = utf8_ptr::s_charsize(**s);
1516
/* return the result */
1520
/* ------------------------------------------------------------------------ */
1522
* Display a string of a given length. The text is encoded as UTF-8
1525
int CVmFormatter::format_text(VMG_ const char *s, size_t slen)
1530
/* get the first character */
1531
c = next_wchar(&s, &slen);
1533
/* if we have anything to show, show it */
1537
* first, process the character through our built-in text-only HTML
1538
* mini-parser, if our HTML mini-parser state indicates that we're
1539
* in the midst of parsing a tag
1541
if (html_parse_state_ != VMCON_HPS_NORMAL
1542
|| (html_in_ignore_ && c != '&' && c != '<'))
1544
/* run our HTML parsing until we finish the tag */
1545
c = resume_html_parsing(vmg_ c, &s, &slen);
1547
/* proceed with the next character */
1551
/* check for special characters */
1556
flush(vmg_ VM_NL_NEWLINE);
1560
/* tab - write an ordinary every-4-columns tab */
1561
write_tab(vmg_ 0, 4);
1565
/* \b - blank line */
1566
write_blank_line(vmg0_);
1570
/* capitalize next character */
1572
nocapsflag_ = FALSE;
1576
/* un-capitalize next character */
1583
/* HTML markup-start character - process it */
1584
if (html_target_ || literal_mode_)
1587
* The underlying OS renderer interprets HTML mark-up
1588
* sequences, OR we're processing all text literally; in
1589
* either case, we don't need to perform any
1590
* interpretation. Simply pass through the character as
1591
* though it were any other.
1598
* The underlying target does not accept HTML sequences.
1599
* It appears we're at the start of an "&" entity or a tag
1600
* sequence, so parse it, remove it, and replace it (if
1601
* possible) with a text-only equivalent.
1603
c = parse_html_markup(vmg_ c, &s, &slen);
1605
/* go back and process the next character */
1610
case 0x0015: /* our own quoted space character */
1611
case 0x00A0: /* non-breaking space */
1612
case 0x00AD: /* soft hyphen */
1613
case 0xFEFF: /* non-breaking zero-width space */
1614
case 0x2002: /* en space */
1615
case 0x2003: /* em space */
1616
case 0x2004: /* three-per-em space */
1617
case 0x2005: /* four-per-em space */
1618
case 0x2006: /* six-per-em space */
1619
case 0x2007: /* figure space */
1620
case 0x2008: /* punctuation space */
1621
case 0x2009: /* thin space */
1622
case 0x200a: /* hair space */
1623
case 0x200b: /* zero-width space */
1625
* Special Unicode characters. For HTML targets, write these
1626
* as &# sequences - this bypasses character set translation
1627
* and ensures that the HTML parser will see them as intended.
1635
* it's an HTML target - render these as &# sequences;
1636
* generate the decimal representation of 'c' (in reverse
1637
* order, hence start with the terminating null byte and
1640
p = buf + sizeof(buf) - 1;
1644
/* generate the decimal representation of 'c' */
1645
for ( ; c != 0 ; c /= 10)
1646
*p-- = (c % 10) + '0';
1648
/* add the '&#' sequence */
1652
/* write out the sequence */
1653
buffer_string(vmg_ p);
1657
/* for non-HTML targets, treat these as normal */
1664
/* normal character - write it out */
1665
buffer_char(vmg_ c);
1669
/* move on to the next character, unless we're finished */
1673
c = next_wchar(&s, &slen);
1680
/* ------------------------------------------------------------------------ */
1682
* Initialize the display object
1684
CVmConsole::CVmConsole()
1686
/* no script file yet */
1689
/* no command log file yet */
1692
/* assume we'll double-space after each period */
1693
doublespace_ = TRUE;
1695
/* presume we'll have no log stream */
1697
log_enabled_ = FALSE;
1701
* Delete the display object
1703
CVmConsole::~CVmConsole()
1705
/* close any active script file(s) */
1706
while (script_sp_ != 0)
1708
/* close this file */
1709
osfcls(script_sp_->fp);
1711
/* unlink this stack level */
1712
script_stack_entry *cur = script_sp_;
1713
script_sp_ = cur->enc;
1715
/* delete the entry */
1719
/* close any active command log file */
1720
close_command_log();
1722
/* delete the log stream if we have one */
1727
/* ------------------------------------------------------------------------ */
1729
* Display a string of a given byte length
1731
int CVmConsole::format_text(VMG_ const char *p, size_t len)
1733
/* display the string */
1734
disp_str_->format_text(vmg_ p, len);
1736
/* if there's a log file, write to the log file as well */
1738
log_str_->format_text(vmg_ p, len);
1740
/* indicate success */
1745
* Display a string on the log stream only
1747
int CVmConsole::format_text_to_log(VMG_ const char *p, size_t len)
1749
/* if there's a log file, write to it; otherwise ignore the whole thing */
1751
log_str_->format_text(vmg_ p, len);
1753
/* indicate success */
1757
/* ------------------------------------------------------------------------ */
1759
* Set the text color
1761
void CVmConsole::set_text_color(VMG_ os_color_t fg, os_color_t bg)
1763
/* set the color in our main display stream */
1764
disp_str_->set_text_color(vmg_ fg, bg);
1768
* Set the body color
1770
void CVmConsole::set_body_color(VMG_ os_color_t color)
1772
/* set the color in the main display stream */
1773
disp_str_->set_os_body_color(color);
1776
/* ------------------------------------------------------------------------ */
1778
* Display a blank line
1780
void CVmConsole::write_blank_line(VMG0_)
1782
/* generate the newline to the standard display */
1783
disp_str_->write_blank_line(vmg0_);
1785
/* if we're logging, generate the newline to the log file as well */
1787
log_str_->write_blank_line(vmg0_);
1791
/* ------------------------------------------------------------------------ */
1793
* outcaps() - sets an internal flag which makes the next letter output
1794
* a capital, whether it came in that way or not. Set the same state in
1795
* both formatters (standard and log).
1797
void CVmConsole::caps()
1805
* outnocaps() - sets the next letter to a miniscule, whether it came in
1808
void CVmConsole::nocaps()
1810
disp_str_->nocaps();
1816
* obey_whitespace() - sets the obey-whitespace mode
1818
int CVmConsole::set_obey_whitespace(int f)
1822
/* note the original display stream status */
1823
ret = disp_str_->get_obey_whitespace();
1825
/* set the stream status */
1826
disp_str_->set_obey_whitespace(f);
1828
log_str_->set_obey_whitespace(f);
1830
/* return the original status of the display stream */
1834
/* ------------------------------------------------------------------------ */
1838
int CVmConsole::open_log_file(const char *fname)
1840
/* if there's no log stream, we can't open a log file */
1845
* Tell the log stream to open the file. Set the log file's HTML
1846
* source mode flag to the same value as is currently being used in
1847
* the main display stream, so that it will interpret source markups
1848
* the same way that the display stream is going to.
1850
return log_str_->open_log_file(fname);
1854
* Close the log file
1856
int CVmConsole::close_log_file()
1858
/* if there's no log stream, there's obviously no file open */
1862
/* tell the log stream to close its file */
1863
return log_str_->close_log_file();
1868
* This code is currently unused. However, I'm leaving it in for now -
1869
* the algorithm takes a little thought, so it would be nicer to be able
1870
* to uncomment the existing code should we ever need it in the future.
1873
/* ------------------------------------------------------------------------ */
1875
* Write UTF-8 text explicitly to the log file. This can be used to add
1876
* special text (such as prompt text) that would normally be suppressed
1877
* from the log file. When more mode is turned off, we don't
1878
* automatically copy text to the log file; any text that the caller
1879
* knows should be in the log file during times when more mode is turned
1880
* off can be explicitly added with this function.
1882
* If nl is true, we'll add a newline at the end of this text. The
1883
* caller should not include any newlines in the text being displayed
1886
void CVmConsole::write_to_logfile(VMG_ const char *txt, int nl)
1888
/* if there's no log file, there's nothing to do */
1892
/* write the text in the log file character set */
1893
write_to_file(logfp_, txt, G_cmap_to_log);
1895
/* add a newline if desired */
1898
/* add a normal newline */
1899
os_fprintz(logfp_, "\n");
1901
/* if the logfile is an html target, write an HTML line break */
1902
if (log_str_ != 0 && log_str_->is_html_target())
1903
os_fprintz(logfp_, "<BR HEIGHT=0>\n");
1906
/* flush the output */
1911
* Write text to a file in the given character set
1913
void CVmConsole::write_to_file(osfildef *fp, const char *txt,
1914
CCharmapToLocal *map)
1916
size_t txtlen = strlen(txt);
1919
* convert the text from UTF-8 to the local character set and write the
1920
* converted text to the log file
1924
char local_buf[128];
1925
size_t src_bytes_used;
1928
/* convert as much as we can (leaving room for a null terminator) */
1929
out_bytes = map->map_utf8(local_buf, sizeof(local_buf),
1930
txt, txtlen, &src_bytes_used);
1932
/* null-terminate the result */
1933
local_buf[out_bytes] = '\0';
1935
/* write the converted text */
1936
os_fprintz(fp, local_buf);
1938
/* skip the text we were able to convert */
1939
txt += src_bytes_used;
1940
txtlen -= src_bytes_used;
1946
/* ------------------------------------------------------------------------ */
1948
* Reset the MORE line counter. This should be called whenever user
1949
* input is read, since stopping to read user input makes it unnecessary
1950
* to show another MORE prompt until the point at which input was
1951
* solicited scrolls off the screen.
1953
void CVmConsole::reset_line_count(int clearing)
1955
/* reset the MORE counter in the display stream */
1956
disp_str_->reset_line_count(clearing);
1959
/* ------------------------------------------------------------------------ */
1961
* Flush the output line. We'll write to both the standard display and
1962
* the log file, as needed.
1964
void CVmConsole::flush(VMG_ vm_nl_type nl)
1966
/* flush the display stream */
1967
disp_str_->flush(vmg_ nl);
1969
/* flush the log stream, if we have an open log file */
1971
log_str_->flush(vmg_ nl);
1974
/* ------------------------------------------------------------------------ */
1978
void CVmConsole::empty_buffers(VMG0_)
1980
/* tell the formatter to clear its buffer */
1981
disp_str_->empty_buffers(vmg0_);
1983
/* same with the log stream, if applicable */
1985
log_str_->empty_buffers(vmg0_);
1988
/* ------------------------------------------------------------------------ */
1990
* Immediately update the display
1992
void CVmConsole::update_display(VMG0_)
1994
/* update the display for the main display stream */
1995
disp_str_->update_display(vmg0_);
1998
/* ------------------------------------------------------------------------ */
2000
* Open a script file
2002
void CVmConsole::open_script_file(const char *fname, int quiet,
2003
int script_more_mode)
2008
/* try opening the file */
2009
osfildef *fp = osfoprt(fname, OSFTCMD);
2011
/* if that failed, silently ignore the request */
2015
/* read the first line to see if it looks like an event script */
2016
if (osfgets(buf, sizeof(buf), fp) != 0
2017
&& strcmp(buf, "<eventscript>\n") == 0)
2019
/* remember that it's an event script */
2025
* it's not an event script, so it must be a regular command-line
2026
* script - rewind it so we read the first line again as a regular
2030
osfseek(fp, 0, OSFSK_SET);
2033
/* if there's an enclosing script, inherit its modes */
2034
if (script_sp_ != 0)
2037
* if the enclosing script is quiet, force the nested script to be
2040
if (script_sp_->quiet)
2044
* if the enclosing script is nonstop, force the nested script to
2045
* be nonstop as well
2047
if (!script_sp_->more_mode)
2048
script_more_mode = FALSE;
2051
/* push the new script file onto the stack */
2052
script_sp_ = new script_stack_entry(
2053
script_sp_, set_more_state(script_more_mode), fp,
2054
script_more_mode, quiet, evt);
2056
/* turn on NONSTOP mode in the OS layer if applicable */
2057
if (!script_more_mode)
2058
os_nonstop_mode(TRUE);
2062
* Close the current script file
2064
int CVmConsole::close_script_file()
2066
script_stack_entry *e;
2068
/* if we have a file, close it */
2069
if ((e = script_sp_) != 0)
2073
/* close the file */
2077
script_sp_ = e->enc;
2079
/* restore the enclosing level's MORE mode */
2080
os_nonstop_mode(!e->old_more_mode);
2083
* return the MORE mode in effect before we started reading the
2086
ret = e->old_more_mode;
2088
/* delete the stack level */
2091
/* return the result */
2097
* there's no script file - just return the current MORE mode,
2098
* since we're not making any changes
2100
return is_more_mode();
2104
/* ------------------------------------------------------------------------ */
2106
* Open a command log file
2108
int CVmConsole::open_command_log(const char *fname, int event_script)
2110
/* close any existing command log file */
2111
close_command_log();
2113
/* remember the filename */
2114
strcpy(command_fname_, fname);
2117
command_fp_ = osfopwt(fname, OSFTCMD);
2120
command_eventscript_ = event_script;
2122
/* if it's an event script, write the file type tag */
2123
if (event_script && command_fp_ != 0)
2125
os_fprintz(command_fp_, "<eventscript>\n");
2126
osfflush(command_fp_);
2129
/* return success if we successfully opened the file */
2130
return (command_fp_ == 0);
2134
* close the active command log file
2136
int CVmConsole::close_command_log()
2138
/* if there's a command log file, close it */
2139
if (command_fp_ != 0)
2141
/* close the file */
2142
osfcls(command_fp_);
2144
/* set its file type */
2145
os_settype(command_fname_, OSFTCMD);
2147
/* forget the file */
2156
/* ------------------------------------------------------------------------ */
2158
* Read a line of input from the console. Fills in the buffer with a
2159
* null-terminated string in the UTF-8 character set. Returns zero on
2160
* success, non-zero on end-of-file.
2162
int CVmConsole::read_line(VMG_ char *buf, size_t buflen)
2164
/* cancel any previous interrupted input */
2165
read_line_cancel(vmg_ TRUE);
2168
/* use the timeout version, with no timeout specified */
2169
switch(read_line_timeout(vmg_ buf, buflen, 0, FALSE))
2175
case VMCON_EVT_END_SCRIPT:
2177
* end of script - we have no way to communicate this result back
2178
* to our caller, so simply ignore the result and ask for another
2184
/* anything else is an error */
2190
/* ------------------------------------------------------------------------ */
2192
* Log an event to the output script. The parameter is in the UI character
2195
int CVmConsole::log_event(VMG_ int evt,
2196
const char *param, size_t paramlen,
2199
/* if there's a script file, log the event */
2200
if (command_fp_ != 0)
2202
/* write the event in the proper format for the script type */
2203
if (command_eventscript_)
2205
const char *tag = 0;
2207
/* write the event according to its type */
2211
/* use the "<key>" tag */
2215
* use the normal key representation, except we want to
2216
* write \n as [enter] and \t as [tab]
2240
case OS_EVT_TIMEOUT:
2248
case OS_EVT_NOTIMEOUT:
2249
tag = "<notimeout>";
2262
case OS_EVT_COMMAND:
2266
case VMCON_EVT_END_SCRIPT:
2270
case VMCON_EVT_DIALOG:
2274
case VMCON_EVT_FILE:
2279
/* if we found a tag, write it */
2282
/* write the tag, in the local character set */
2283
G_cmap_to_ui->write_file(command_fp_, tag, strlen(tag));
2285
/* add the parameter, if present */
2289
G_cmap_to_ui->write_file(
2290
command_fp_, param, paramlen);
2292
os_fprint(command_fp_, param, paramlen);
2295
/* add the newline */
2296
G_cmap_to_ui->write_file(command_fp_, "\n", 1);
2298
/* flush the output */
2299
osfflush(command_fp_);
2305
* It's a plain old command-line script. If the event is an
2306
* input-line event, record it; otherwise leave it out, as this
2307
* script file format can't represent any other event types.
2309
if (evt == OS_EVT_LINE && param != 0)
2311
/* write the ">" prefix */
2312
G_cmap_to_ui->write_file(command_fp_, ">", 1);
2314
/* add the command line */
2316
G_cmap_to_ui->write_file(command_fp_, param, paramlen);
2318
os_fprint(command_fp_, param, paramlen);
2320
/* add the newline */
2321
G_cmap_to_ui->write_file(command_fp_, "\n", 1);
2323
/* flush the output */
2324
osfflush(command_fp_);
2329
/* return the event code */
2333
/* ------------------------------------------------------------------------ */
2335
* Static variables for input state. We keep these statically, because we
2336
* might need to use the values across a series of read_line_timeout calls
2337
* if timeouts occur.
2340
/* original 'more' mode, before input began */
2341
static int S_old_more_mode;
2343
/* flag: input is pending from an interrupted read_line_timeout invocation */
2344
static int S_read_in_progress;
2346
/* local buffer for reading input lines */
2347
static char S_read_buf[256];
2351
* Read a line of input from the console, with an optional timeout value.
2353
int CVmConsole::read_line_timeout(VMG_ char *buf, size_t buflen,
2354
unsigned long timeout, int use_timeout)
2363
* presume we won't echo the text to the display; in most cases, it
2364
* will be echoed to the display in the course of reading it from
2369
/* remember the initial MORE mode */
2370
S_old_more_mode = is_more_mode();
2373
* If we're not resuming an interrupted read already in progress,
2374
* initialize some display settings.
2376
if (!S_read_in_progress)
2379
* Turn off MORE mode if it's on - we don't want a MORE prompt
2380
* showing up in the midst of user input.
2382
S_old_more_mode = set_more_state(FALSE);
2385
* flush the output; don't start a new line, since we might have
2386
* displayed a prompt that is to be on the same line with the user
2389
flush_all(vmg_ VM_NL_INPUT);
2391
/* if there's a script file, read from it */
2392
if (script_sp_ != 0)
2395
/* note whether we're in quiet mode */
2396
int was_quiet = script_sp_->quiet;
2398
/* try reading a line from the script file */
2399
if (read_line_from_script(S_read_buf, sizeof(S_read_buf), &evt))
2402
* we successfully got a line from the script file - if
2403
* we're not in quiet mode, make a note to echo the text to
2406
if (!script_sp_->quiet)
2414
* End of script file - return to reading from the
2415
* enclosing level (i.e., the enclosing script, or the
2416
* keyboard if this is the outermost script). The return
2417
* value from close_script_file() is the MORE mode that was
2418
* in effect before we started reading the script file;
2419
* we'll use this when we restore the enclosing MORE mode
2420
* so that we restore the pre-script MORE mode when we
2423
S_old_more_mode = close_script_file();
2425
/* note the new 'quiet' mode */
2426
is_quiet = (script_sp_ != 0 && script_sp_->quiet);
2429
* if we're still reading from a script (which means we
2430
* closed the old script and popped out to an enclosing
2431
* script), and the 'quiet' mode hasn't changed, simply go
2432
* back for another read
2434
if (script_sp_ != 0 && is_quiet == was_quiet)
2438
* temporarily turn off MORE mode, in case we read from the
2441
set_more_state(FALSE);
2443
/* flush any output we generated while reading the script */
2444
flush(vmg_ VM_NL_NONE);
2447
* If we were in quiet mode but no longer are, let the
2448
* caller know we've finished reading a script, so that the
2449
* caller can set up the display properly for reading from
2452
* If we weren't in quiet mode, we'll simply proceed to the
2453
* normal keyboard reading; when not in quiet mode, no
2454
* special display fixup is needed.
2456
if (was_quiet && !is_quiet)
2458
/* return to the old MORE mode */
2459
set_more_state(S_old_more_mode);
2461
/* add a blank line to the log file, if necessary */
2463
log_str_->print_to_os("\n");
2465
/* note in the streams that we've read an input line */
2466
disp_str_->note_input_line();
2468
log_str_->note_input_line();
2471
* generate a synthetic "end of script" event to let
2472
* the caller know we're switching back to regular
2475
return log_event(vmg_ VMCON_EVT_END_SCRIPT);
2479
* Note that we do not have an event yet - we've merely
2480
* closed the script file, and now we're going to continue
2481
* by reading a line from the keyboard instead. The call
2482
* to close_script_file() above will have left script_sp_
2483
* == 0, so we'll shortly read an event from the keyboard.
2484
* Thus 'evt' is still not set to any value, because we do
2485
* not yet have an event - this is intentional.
2491
* if we're not reading from a scripot, reset the MORE line
2492
* counter, since we're reading user input at the current point and
2493
* shouldn't pause for a MORE prompt until the text we're reading
2494
* has scrolled off the screen
2496
if (script_sp_ == 0)
2497
reset_line_count(FALSE);
2501
* if reading was already in progress, we're resuming a previously
2502
* interrupted read operation
2504
resuming = S_read_in_progress;
2506
/* reading is now in progress */
2507
S_read_in_progress = TRUE;
2510
* if we don't have a script file, or we're resuming an interrupted
2511
* read operation, read from the keyboard
2513
if (script_sp_ == 0 || resuming)
2515
/* read a line from the keyboard */
2516
evt = os_gets_timeout((uchar *)S_read_buf, sizeof(S_read_buf),
2517
timeout, use_timeout);
2520
* If that failed because timeout is not supported on this
2521
* platform, and the caller didn't actually want to use a timeout,
2522
* try again with an ordinary os_gets(). If they wanted to use a
2523
* timeout, simply return the NOTIMEOUT indication to our caller.
2525
if (evt == OS_EVT_NOTIMEOUT && !use_timeout)
2527
/* perform an ordinary untimed input */
2528
if (os_gets((uchar *)S_read_buf, sizeof(S_read_buf)) != 0)
2535
/* error reading input */
2541
* If we actually read a line, notify the display stream that we
2542
* read text from the console - it might need to make some
2543
* internal bookkeeping adjustments to account for the fact that
2544
* we moved the write position around on the display.
2546
* Don't note the input if we timed out, since we haven't finished
2547
* reading the line yet in this case.
2549
if (evt == OS_EVT_LINE)
2551
disp_str_->note_input_line();
2553
log_str_->note_input_line();
2557
/* if we got an error, return it */
2558
if (evt == OS_EVT_EOF)
2560
set_more_state(S_old_more_mode);
2561
return log_event(vmg_ evt);
2565
* Convert the text from the local UI character set to UTF-8. Reserve
2566
* space in the output buffer for the null terminator.
2569
outlen = buflen - 1;
2570
G_cmap_from_ui->map(&outp, &outlen, S_read_buf, strlen(S_read_buf));
2572
/* add the null terminator */
2576
* If we need to echo the text (because we read it from a script file),
2582
format_text(vmg_ buf);
2585
format_text(vmg_ "\n");
2588
/* if we finished reading the line, do our line-finishing work */
2589
if (evt == OS_EVT_LINE)
2590
read_line_done(vmg0_);
2593
* Log and return the event. Note that we log events in the UI
2594
* character set, so we want to simply use the original, untranslated
2597
return log_event(vmg_ evt, S_read_buf, strlen(S_read_buf), FALSE);
2601
* Cancel an interrupted input.
2603
void CVmConsole::read_line_cancel(VMG_ int reset)
2605
/* reset the underling OS layer */
2606
os_gets_cancel(reset);
2608
/* do our line-ending work */
2609
read_line_done(vmg0_);
2613
* Perform line-ending work. This is used when we finish reading a line
2614
* in read_line_timeout(), or when we cancel an interrupted line, thus
2615
* finishing the line, in read_line_cancel().
2617
void CVmConsole::read_line_done(VMG0_)
2619
/* if we have a line in progress, finish it off */
2620
if (S_read_in_progress)
2622
/* set the original 'more' mode */
2623
set_more_state(S_old_more_mode);
2626
* Write the input line, followed by a newline, to the log file.
2627
* Note that the text is still in the local character set, so we
2628
* can write it directly to the log file.
2630
* If we're reading from a script file in "echo" mode, skip this.
2631
* When reading from a script file in "echo" mode, we will manually
2632
* copy the input commands to the main console, which will
2633
* automatically copy to the main log file. If we're in quiet
2634
* scripting mode, though, we won't do that, so we do need to
2635
* capture the input explicitly here.
2637
if (log_enabled_ && (script_sp_ == 0 || script_sp_->quiet))
2639
log_str_->print_to_os(S_read_buf);
2640
log_str_->print_to_os("\n");
2643
/* note in the streams that we've read an input line */
2644
disp_str_->note_input_line();
2646
log_str_->note_input_line();
2648
/* clear the in-progress flag */
2649
S_read_in_progress = FALSE;
2653
/* ------------------------------------------------------------------------ */
2655
* Read an input event from the script file. If we're reading an event
2656
* script file, we'll read the next event and return TRUE; if we're not
2657
* reading a script file, or the script file is a command-line script
2658
* rather than an event script, we'll simply return FALSE.
2660
* If the event takes a parameter, we'll read the parameter into 'buf'.
2661
* The value is returned in the local character set, so the caller will
2662
* need to translate it to UTF-8.
2664
* If 'filter' is non-null, we'll only return events of the types in the
2667
int CVmConsole::read_event_script(VMG_ int *evt, char *buf, size_t buflen,
2668
const int *filter, int filter_cnt,
2669
unsigned long *attrs)
2672
* if we're not reading a script, or it's not an event script, skip
2675
if (script_sp_ == 0 || !script_sp_->event_script)
2678
/* get the script file */
2679
osfildef *fp = script_sp_->fp;
2681
/* keep going until we find something */
2684
/* read the next event */
2685
if (!read_script_event_type(evt, attrs))
2687
/* end of the script - close it */
2688
set_more_state(close_script_file());
2690
/* if there's no more script file, there's no event */
2691
if (script_sp_ == 0)
2694
/* go back for the next event */
2695
fp = script_sp_->fp;
2699
/* if it's not in the filter list, skip it */
2704
/* look for a match in our filter list */
2705
for (i = 0, found = FALSE ; i < filter_cnt ; ++i)
2707
if (filter[i] == *evt)
2714
/* if we didn't find it, skip this line */
2717
skip_script_line(fp);
2722
/* if there's a buffer, read the rest of the line */
2725
/* read the parameter into the buffer, and return the result */
2726
if (!read_script_param(buf, buflen, fp))
2729
/* if this is an OS_EVT_KEY event, translate special keys */
2730
if (*evt == OS_EVT_KEY)
2732
if (strcmp(buf, "[enter]") == 0)
2734
else if (strcmp(buf, "[tab]") == 0)
2736
else if (strcmp(buf, "[space]") == 0)
2742
/* no result buffer - just skip anything left on the line */
2743
skip_script_line(fp);
2752
* Read a <tag> or attribute token from a script file. Returns the
2753
* character after the end of the token.
2755
static int read_script_token(char *buf, size_t buflen, osfildef *fp)
2760
/* skip leading whitespace */
2761
for (c = osfgetc(fp) ; isspace(c) ; c = osfgetc(fp)) ;
2763
/* read from the file until we reach the end of the token */
2765
p < buf + buflen - 1
2766
&& c != '>' && !isspace(c)
2767
&& c != '\n' && c != '\r' && c != EOF ; )
2769
/* store this character */
2772
/* get the next one */
2776
/* null-terminate the token */
2779
/* return the character that ended the token */
2784
* Read the next event type from current event script file. This leaves
2785
* the file positioned at the parameter data for the event, if any.
2786
* Returns FALSE if we reach end of file without finding an event.
2788
int CVmConsole::read_script_event_type(int *evt, unsigned long *attrs)
2790
/* clear the caller's attribute flags, if provided */
2794
/* if there's no script, there's no event */
2795
if (script_sp_ == 0)
2799
osfildef *fp = script_sp_->fp;
2801
/* if it's a command-line script, there are only line input events */
2802
if (!script_sp_->event_script)
2804
/* keep going until we find an input line */
2807
/* read the first charater of the line */
2808
int c = osfgetc(fp);
2811
/* we found a line input event */
2817
/* end of file - give up */
2823
* anything else is just a comment line - just skip it and
2826
skip_script_line(fp);
2831
/* keep going until we find an event tag */
2843
{ "key", OS_EVT_KEY },
2844
{ "timeout", OS_EVT_TIMEOUT },
2845
{ "notimeout", OS_EVT_NOTIMEOUT },
2846
{ "eof", OS_EVT_EOF },
2847
{ "line", OS_EVT_LINE },
2848
{ "command", OS_EVT_COMMAND },
2850
{ "endqs", VMCON_EVT_END_SCRIPT },
2851
{ "dialog", VMCON_EVT_DIALOG },
2852
{ "file", VMCON_EVT_FILE },
2857
/* check the start of the line to make sure it's an event */
2860
/* if at EOF, return failure */
2864
/* if it's not an event code line, skip the line */
2867
skip_script_line(fp);
2871
/* read the event type (up to the '>') */
2872
c = read_script_token(tag, sizeof(tag), fp);
2874
/* check for attributes */
2885
{ "overwrite", VMCON_EVTATTR_OVERWRITE },
2889
/* read the attribute name */
2890
c = read_script_token(attr, sizeof(attr), fp);
2892
/* if the name is empty, stop */
2893
if (attr[0] == '\0')
2896
/* look up the token */
2897
for (ap = attrlist ; ap->name != 0 ; ++ap)
2899
/* check for a match */
2900
if (stricmp(attr, ap->name) == 0)
2902
/* if the caller wants the flag, set it */
2906
/* no need to look any further */
2911
/* if we're at the '>' or at end of line or file, stop */
2912
if (c == '>' || c == '\n' || c == '\r' || c == EOF)
2916
/* if it's not a well-formed tag, ignore it */
2919
skip_script_line(fp);
2923
/* look up the tag */
2924
for (tp = tags ; tp->tag != 0 ; ++tp)
2926
/* check for a match to this tag name */
2927
if (stricmp(tp->tag, tag) == 0)
2929
/* got it - return the event type */
2935
/* we don't recognize the tag name; skip the line and keep looking */
2936
skip_script_line(fp);
2941
* Skip to the next script line
2943
void CVmConsole::skip_script_line(osfildef *fp)
2947
/* read until we find the end of the current line */
2948
for (c = osfgetc(fp) ; c != EOF && c != '\n' && c != '\r' ;
2953
* read the rest of the current script line into the given buffer
2955
int CVmConsole::read_script_param(char *buf, size_t buflen, osfildef *fp)
2957
/* ignore zero-size buffer requests */
2961
/* read characters until we run out of buffer or reach a newline */
2964
/* get the next character */
2965
int c = osfgetc(fp);
2967
/* if it's a newline or end of file, we're done */
2968
if (c == '\n' || c == EOF)
2970
/* null-terminate the buffer */
2973
/* indicate success */
2978
* if there's room in the buffer, add the character - always leave
2979
* one byte for the null terminator
2991
* Read a line of text from the script file, if there is one. Returns TRUE
2992
* on success, FALSE if we reach the end of the script file or encounter
2995
int CVmConsole::read_line_from_script(char *buf, size_t buflen, int *evt)
2997
/* if there's no script file, return failure */
2998
if (script_sp_ == 0)
3001
/* get the file from the script stack */
3002
osfildef *fp = script_sp_->fp;
3004
/* keep going until we find a line that we like */
3007
/* read the script according to its type ('event' or 'line input') */
3008
if (script_sp_->event_script)
3010
/* read to the next event */
3011
if (!read_script_event_type(evt, 0))
3014
/* check the event code */
3018
case OS_EVT_TIMEOUT:
3020
* it's one of our line input events - read the line (or
3021
* partial line, in the case of TIMEOUT)
3023
return read_script_param(buf, buflen, fp);
3027
* it's not our type of event - skip the rest of the line
3030
skip_script_line(fp);
3037
* We have a basic line-input script rather than an event
3038
* script. Each input line starts with a '>'; everything else
3041
* Read the first character on the line. If it's not a
3042
* newline, there's more text on the same line, so read the
3043
* rest and determine what to do.
3045
int c = osfgetc(fp);
3048
/* it's a command line - read it */
3050
return read_script_param(buf, buflen, fp);
3057
else if (c == '\n' || c == '\r')
3059
/* blank line - continue on to the next line */
3063
/* it's not a command line - just skip it and keep looking */
3064
skip_script_line(fp);
3070
/* ------------------------------------------------------------------------ */
3072
* Main System Console
3078
CVmConsoleMain::CVmConsoleMain(VMG0_)
3080
/* create the system banner manager */
3081
banner_manager_ = new CVmBannerManager();
3083
/* create the log console manager */
3084
log_console_manager_ = new CVmLogConsoleManager();
3086
/* create and initialize our display stream */
3087
main_disp_str_ = new CVmFormatterMain(this, 256);
3088
main_disp_str_->init();
3090
/* initially send text to the main display stream */
3091
disp_str_ = main_disp_str_;
3094
* Create our log stream. The main console always has a log stream,
3095
* even when it's not in use, so that we can keep the log stream's
3096
* state synchronized with the display stream in preparation for
3099
log_str_ = new CVmFormatterLog(this, 80);
3102
* use the default log file character mapper - on some systems, files
3103
* don't use the same character set as the display
3105
log_str_->set_charmap(G_cmap_to_log);
3107
/* initialize the log stream */
3111
* the log stream is initially enabled (this is separate from the log
3112
* file being opened; it merely indicates that we send output
3113
* operations to the log stream for processing)
3115
log_enabled_ = TRUE;
3117
/* we don't have a statusline formatter until asked for one */
3121
S_read_in_progress = FALSE;
3122
S_read_buf[0] = '\0';
3128
CVmConsoleMain::~CVmConsoleMain()
3130
/* delete the system banner manager */
3131
banner_manager_->delete_obj();
3133
/* delete the system log console manager */
3134
log_console_manager_->delete_obj();
3136
/* delete the display stream */
3137
delete main_disp_str_;
3139
/* delete the statusline stream, if we have one */
3140
if (statline_str_ != 0)
3141
delete statline_str_;
3147
void CVmConsoleMain::clear_window(VMG0_)
3149
/* flush and empty our output buffer */
3150
flush(vmg_ VM_NL_NONE);
3151
empty_buffers(vmg0_);
3153
/* clear the main window */
3156
/* reset the MORE line counter in the display stream */
3157
disp_str_->reset_line_count(TRUE);
3161
* Set statusline mode
3163
void CVmConsoleMain::set_statusline_mode(VMG_ int mode)
3165
CVmFormatterDisp *str;
3168
* if we're switching into statusline mode, and we don't have a
3169
* statusline stream yet, create one
3171
if (mode && statline_str_ == 0)
3173
/* create and initialize the statusline stream */
3174
statline_str_ = new CVmFormatterStatline(this);
3175
statline_str_->init();
3178
/* get the stream selected by the new mode */
3180
str = statline_str_;
3182
str = main_disp_str_;
3184
/* if this is already the active stream, we have nothing more to do */
3185
if (str == disp_str_)
3188
/* make the new stream current */
3192
* check which mode we're switching to, so we can do some extra work
3193
* specific to each mode
3198
* we're switching to the status line, so disable the log stream -
3199
* statusline text is never sent to the log, since the log reflects
3200
* only what was displayed in the main text area
3202
log_enabled_ = FALSE;
3207
* we're switching back to the main stream, so flush the statusline
3208
* so we're sure the statusline text is displayed
3212
statline_str_->format_text(vmg_ "\n", 1);
3215
statline_str_->flush(vmg_ VM_NL_NONE);
3217
/* re-enable the log stream, if we have one */
3219
log_enabled_ = TRUE;
3222
/* switch at the OS layer */
3229
void CVmConsoleMain::flush_all(VMG_ vm_nl_type nl)
3231
/* flush our primary console */
3235
* Flush each banner we're controlling. Note that we explicitly flush
3236
* the banners with newline mode 'NONE', regardless of the newline mode
3237
* passed in by the caller: the caller's mode is for the primary
3238
* console, but for the banners we just want to make sure they're
3239
* flushed out normally, since whatever we're doing in the primary
3240
* console that requires flushing doesn't concern the banners.
3242
banner_manager_->flush_all(vmg_ VM_NL_NONE);
3245
/* ------------------------------------------------------------------------ */
3251
CVmHandleManager::CVmHandleManager()
3255
/* allocate an initial array of handle slots */
3257
handles_ = (void **)t3malloc(handles_max_ * sizeof(*handles_));
3259
/* all slots are initially empty */
3260
for (i = 0 ; i < handles_max_ ; ++i)
3264
/* delete the object - this is the public destructor interface */
3265
void CVmHandleManager::delete_obj()
3270
* Delete each remaining object. Note that we need to call the virtual
3271
* delete_handle_object routine, so we must do this before reaching the
3272
* destructor (once in the base class destructor, we no longer have
3273
* access to the subclass virtuals).
3275
for (i = 0 ; i < handles_max_ ; ++i)
3277
/* if this banner is still valid, delete it */
3278
if (handles_[i] != 0)
3279
delete_handle_object(i + 1, handles_[i]);
3282
/* delete the object */
3287
CVmHandleManager::~CVmHandleManager()
3289
/* delete the handle pointer array */
3294
* Allocate a new handle
3296
int CVmHandleManager::alloc_handle(void *item)
3300
/* scan for a free slot */
3301
for (slot = 0 ; slot < handles_max_ ; ++slot)
3303
/* if this one is free, use it */
3304
if (handles_[slot] == 0)
3308
/* if we didn't find a free slot, extend the array */
3309
if (slot == handles_max_)
3313
/* allocate a larger array */
3315
handles_ = (void **)
3316
t3realloc(handles_, handles_max_ * sizeof(*handles_));
3318
/* clear out the newly-allocated slots */
3319
for (i = slot ; i < handles_max_ ; ++i)
3323
/* store the new item in our pointer array */
3324
handles_[slot] = item;
3327
* convert the slot number to a handle by adjusting it to a 1-based
3328
* index, and return the result
3334
/* ------------------------------------------------------------------------ */
3342
int CVmBannerManager::create_banner(VMG_ int parent_id,
3343
int where, int other_id,
3344
int wintype, int align,
3345
int siz, int siz_units,
3346
unsigned long style)
3349
void *parent_handle;
3351
CVmConsoleBanner *item;
3353
/* get the parent handle, if provided */
3354
parent_handle = get_os_handle(parent_id);
3356
/* get the 'other' handle, if we need it for the 'where' */
3359
case OS_BANNER_BEFORE:
3360
case OS_BANNER_AFTER:
3361
/* retrieve the handle for the other_id */
3362
other_handle = get_os_handle(other_id);
3366
/* we don't need 'other' for other 'where' modes */
3371
/* try creating the OS-level banner window */
3372
handle = os_banner_create(parent_handle, where, other_handle, wintype,
3373
align, siz, siz_units, style);
3375
/* if we couldn't create the OS-level window, return failure */
3379
/* create the new console */
3380
item = new CVmConsoleBanner(handle, wintype, style);
3382
/* allocate a handle for the new banner, and return the handle */
3383
return alloc_handle(item);
3387
* Delete or orphan a banner window. Deleting and orphaning both sever
3388
* all ties from the banner manager (and thus from the T3 program) to the
3389
* banner. Deleting a banner actually gets deletes it at the OS level;
3390
* orphaning the banner severs our ties, but hands the banner over to the
3391
* OS to do with as it pleases. On some implementations, the OS will
3392
* continue to display the banner after it's orphaned to allow the final
3393
* display configuration to remain visible even after the program has
3396
void CVmBannerManager::delete_or_orphan_banner(int banner, int orphan)
3398
CVmConsoleBanner *item;
3401
/* if the banner is invalid, ignore the request */
3402
if ((item = (CVmConsoleBanner *)get_object(banner)) == 0)
3405
/* get the OS-level banner handle */
3406
handle = item->get_os_handle();
3408
/* delete the banner item */
3411
/* clear the slot */
3412
clear_handle(banner);
3414
/* delete the OS-level banner */
3416
os_banner_orphan(handle);
3418
os_banner_delete(handle);
3422
* Get the OS-level handle for the given banner
3424
void *CVmBannerManager::get_os_handle(int banner)
3426
CVmConsoleBanner *item;
3428
/* if the banner is invalid, return failure */
3429
if ((item = (CVmConsoleBanner *)get_object(banner)) == 0)
3432
/* return the handle from the slot */
3433
return item->get_os_handle();
3439
void CVmBannerManager::flush_all(VMG_ vm_nl_type nl)
3443
/* flush each banner */
3444
for (slot = 0 ; slot < handles_max_ ; ++slot)
3446
/* if this slot has a valid banner, flush it */
3447
if (handles_[slot] != 0)
3448
((CVmConsoleBanner *)handles_[slot])->flush(vmg_ nl);
3452
/* ------------------------------------------------------------------------ */
3454
* Banner Window Console
3456
CVmConsoleBanner::CVmConsoleBanner(void *banner_handle, int win_type,
3457
unsigned long style)
3459
CVmFormatterBanner *str;
3460
os_banner_info_t info;
3461
int obey_whitespace = FALSE;
3462
int literal_mode = FALSE;
3464
/* remember our OS-level banner handle */
3465
banner_ = banner_handle;
3467
/* get osifc-level information on the banner */
3468
if (!os_banner_getinfo(banner_, &info))
3469
info.os_line_wrap = FALSE;
3472
* If it's a text grid window, don't do any line wrapping. Text grids
3473
* simply don't have any line wrapping, so we don't want to impose any
3474
* at the formatter level. Set the formatter to "os line wrap" mode,
3475
* to indicate that the formatter doesn't do wrapping - even though
3476
* the underlying OS banner window won't do any wrapping either, the
3477
* lack of line wrapping counts as OS handling of line wrapping.
3479
if (win_type == OS_BANNER_TYPE_TEXTGRID)
3481
/* do not wrap lines in the formatter */
3482
info.os_line_wrap = TRUE;
3484
/* use literal mode, and obey whitespace literally */
3485
literal_mode = TRUE;
3486
obey_whitespace = TRUE;
3489
/* create and initialize our display stream */
3490
disp_str_ = str = new CVmFormatterBanner(banner_handle, this,
3492
str->init_banner(info.os_line_wrap, obey_whitespace, literal_mode);
3494
/* remember our window type */
3495
win_type_ = win_type;
3501
CVmConsoleBanner::~CVmConsoleBanner()
3503
/* delete our display stream */
3508
* Clear the banner window
3510
void CVmConsoleBanner::clear_window(VMG0_)
3512
/* flush and empty our output buffer */
3513
flush(vmg_ VM_NL_NONE);
3514
empty_buffers(vmg0_);
3516
/* clear our underlying system banner */
3517
os_banner_clear(banner_);
3519
/* tell our display stream to zero its line counter */
3520
disp_str_->reset_line_count(TRUE);
3524
* Get banner information
3526
int CVmConsoleBanner::get_banner_info(os_banner_info_t *info)
3530
/* get the OS-level information */
3531
ret = os_banner_getinfo(banner_, info);
3533
/* make some adjustments if we got valid information back */
3537
* check the window type for further adjustments we might need to
3538
* make to the data returned from the OS layer
3542
case OS_BANNER_TYPE_TEXTGRID:
3544
* text grids don't support <TAB> alignment, even if the
3545
* underlying OS banner says we do, because we simply don't
3546
* support <TAB> (or any other HTML markups) in a text grid
3549
info->style &= ~OS_BANNER_STYLE_TAB_ALIGN;
3553
/* other types don't require any adjustments */
3558
/* return the success indication */
3562
/* ------------------------------------------------------------------------ */
3564
* Log file console manager
3568
* create a log console
3570
int CVmLogConsoleManager::create_log_console(const char *fname,
3572
class CCharmapToLocal *cmap,
3577
/* create the new console */
3578
con = new CVmConsoleLog(fname, fp, cmap, width);
3580
/* allocate a handle for the new console and return the handle */
3581
return alloc_handle(con);
3585
* delete log a console
3587
void CVmLogConsoleManager::delete_log_console(int handle)
3591
/* if the handle is invalid, ignore the request */
3592
if ((con = (CVmConsoleLog *)get_object(handle)) == 0)
3595
/* delete the console */
3598
/* clear the slot */
3599
clear_handle(handle);
3602
/* ------------------------------------------------------------------------ */
3606
CVmConsoleLog::CVmConsoleLog(const char *fname, osfildef *fp,
3607
class CCharmapToLocal *cmap, int width)
3609
CVmFormatterLog *str;
3611
/* create our display stream */
3612
disp_str_ = str = new CVmFormatterLog(this, width);
3615
str->set_log_file(fname, fp);
3617
/* set the character mapper */
3618
str->set_charmap(cmap);
3624
CVmConsoleLog::~CVmConsoleLog()
3626
/* delete our display stream */