~ubuntu-branches/ubuntu/wily/gargoyle-free/wily-proposed

« back to all changes in this revision

Viewing changes to tads/tads3/vmconsol.cpp

  • Committer: Bazaar Package Importer
  • Author(s): Sylvain Beucler
  • Date: 2009-09-11 20:09:43 UTC
  • Revision ID: james.westby@ubuntu.com-20090911200943-idgzoyupq6650zpn
Tags: upstream-2009-08-25
ImportĀ upstreamĀ versionĀ 2009-08-25

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#ifdef RCSID
 
2
static char RCSid[] =
 
3
"$Header$";
 
4
#endif
 
5
 
 
6
/* 
 
7
 *   Copyright (c) 1987, 2002 Michael J. Roberts.  All Rights Reserved.
 
8
 *   
 
9
 *   Please see the accompanying license file, LICENSE.TXT, for information
 
10
 *   on using and copying this software.  
 
11
 */
 
12
/*
 
13
Name
 
14
  vmconsol.cpp - TADS 3 console input reader and output formatter
 
15
Function
 
16
  Provides console input and output for the TADS 3 built-in function set
 
17
  for the T3 VM, including the output formatter.
 
18
 
 
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.
 
23
 
 
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.
 
27
Notes
 
28
 
 
29
Returns
 
30
  None
 
31
Modified
 
32
  08/25/99 MJRoberts  - created from TADS 2 output formatter
 
33
*/
 
34
 
 
35
#include <stdio.h>
 
36
#include <ctype.h>
 
37
#include <stdlib.h>
 
38
#include <string.h>
 
39
#include <stdarg.h>
 
40
#include "wchar.h"
 
41
 
 
42
#include "os.h"
 
43
#include "t3std.h"
 
44
#include "utf8.h"
 
45
#include "charmap.h"
 
46
#include "vmuni.h"
 
47
#include "vmconsol.h"
 
48
#include "vmglob.h"
 
49
#include "vmhash.h"
 
50
 
 
51
 
 
52
/* ------------------------------------------------------------------------ */
 
53
/*
 
54
 *   Log-file formatter subclass implementation 
 
55
 */
 
56
 
 
57
/*
 
58
 *   delete 
 
59
 */
 
60
CVmFormatterLog::~CVmFormatterLog()
 
61
{
 
62
    /* close any active log file */
 
63
    close_log_file();
 
64
}
 
65
 
 
66
/*
 
67
 *   Open a new log file 
 
68
 */
 
69
int CVmFormatterLog::open_log_file(const char *fname)
 
70
{
 
71
    /* close any existing log file */
 
72
    if (close_log_file())
 
73
        return 1;
 
74
 
 
75
    /* reinitialize */
 
76
    init();
 
77
 
 
78
    /* save the filename for later (we'll need it when we close the file) */
 
79
    logfname_ = lib_copy_str(fname);
 
80
 
 
81
    /* open the new file */
 
82
    logfp_ = osfopwt(fname, OSFTLOG);
 
83
 
 
84
    /* return success if we successfully opened the file, failure otherwise */
 
85
    return (logfp_ == 0);
 
86
}
 
87
 
 
88
/*
 
89
 *   Set the log file to a file opened by the caller 
 
90
 */
 
91
int CVmFormatterLog::set_log_file(const char *fname, osfildef *fp)
 
92
{
 
93
    /* close any existing log file */
 
94
    if (close_log_file())
 
95
        return 1;
 
96
 
 
97
    /* reinitialize */
 
98
    init();
 
99
 
 
100
    /* remember the file */
 
101
    logfp_ = fp;
 
102
 
 
103
    /* remember the filename */
 
104
    logfname_ = lib_copy_str(fname);
 
105
 
 
106
    /* success */
 
107
    return 0;
 
108
}
 
109
 
 
110
/*
 
111
 *   Close the log file 
 
112
 */
 
113
int CVmFormatterLog::close_log_file()
 
114
{
 
115
    /* if we have a file, close it */
 
116
    if (logfp_ != 0)
 
117
    {
 
118
        /* close the handle */
 
119
        osfcls(logfp_);
 
120
 
 
121
        /* forget about our log file handle */
 
122
        logfp_ = 0;
 
123
 
 
124
        /* set the system file type to "log file" */
 
125
        if (logfname_ != 0)
 
126
            os_settype(logfname_, OSFTLOG);
 
127
    }
 
128
 
 
129
    /* forget the log file name, if we have one */
 
130
    if (logfname_ != 0)
 
131
    {
 
132
        lib_free_str(logfname_);
 
133
        logfname_ = 0;
 
134
    }
 
135
 
 
136
    /* success */
 
137
    return 0;
 
138
}
 
139
 
 
140
 
 
141
/* ------------------------------------------------------------------------ */
 
142
/*
 
143
 *   Base Formatter 
 
144
 */
 
145
 
 
146
/*
 
147
 *   deletion
 
148
 */
 
149
CVmFormatter::~CVmFormatter()
 
150
{
 
151
    /* if we have a table of horizontal tabs, delete it */
 
152
    if (tabs_ != 0)
 
153
        delete tabs_;
 
154
 
 
155
    /* forget the character mapper */
 
156
    set_charmap(0);
 
157
}
 
158
 
 
159
/*
 
160
 *   set a new character mapper 
 
161
 */
 
162
void CVmFormatter::set_charmap(CCharmapToLocal *cmap)
 
163
{
 
164
    /* add a reference to the new mapper, if we have one */
 
165
    if (cmap != 0)
 
166
        cmap->add_ref();
 
167
 
 
168
    /* release our reference on any old mapper */
 
169
    if (cmap_ != 0)
 
170
        cmap_->release_ref();
 
171
 
 
172
    /* remember the new mapper */
 
173
    cmap_ = cmap;
 
174
}
 
175
 
 
176
/*
 
177
 *   Write out a line.  Text we receive is in the UTF-8 character set.
 
178
 */
 
179
void CVmFormatter::write_text(VMG_ const wchar_t *txt, size_t cnt,
 
180
                              const vmcon_color_t *colors, vm_nl_type nl)
 
181
{
 
182
    /* 
 
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.  
 
188
     */
 
189
    if (!console_->is_quiet_script() || !is_disp_stream_)
 
190
    {
 
191
        char local_buf[128];
 
192
        char *dst;
 
193
        size_t rem;
 
194
 
 
195
        /*
 
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
 
200
         *   issues.
 
201
         *   
 
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.
 
205
         *   
 
206
         *   Skip the MORE prompt if this stream doesn't use it.  
 
207
         */
 
208
        if (formatter_more_mode()
 
209
            && console_->is_more_mode()
 
210
            && !is_continuation_
 
211
            && linecnt_ + 1 >= console_->get_page_length())
 
212
        {
 
213
            /* set the standard text color */
 
214
            set_os_text_color(OS_COLOR_P_TEXT, OS_COLOR_P_TEXTBG);
 
215
            set_os_text_attr(0);
 
216
 
 
217
            /* display the MORE prompt */
 
218
            console_->show_more_prompt(vmg0_);
 
219
 
 
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);
 
223
        }
 
224
 
 
225
        /* count the line if a newline follows */
 
226
        if (nl != VM_NL_NONE && nl != VM_NL_NONE_INTERNAL)
 
227
            ++linecnt_;
 
228
 
 
229
        /* convert and display the text */
 
230
        for (dst = local_buf, rem = sizeof(local_buf) - 1 ; cnt != 0 ; )
 
231
        {
 
232
            size_t cur;
 
233
            size_t old_rem;
 
234
            wchar_t c;
 
235
            
 
236
            /* 
 
237
             *   if this character is in a new color, write out the OS-level
 
238
             *   color switch code 
 
239
             */
 
240
            if (colors != 0 && !colors->equals(&os_color_))
 
241
            {
 
242
                /* 
 
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
 
246
                 */
 
247
                *dst = '\0';
 
248
                print_to_os(local_buf);
 
249
 
 
250
                /* reset to the start of the local output buffer */
 
251
                dst = local_buf;
 
252
                rem = sizeof(local_buf) - 1;
 
253
 
 
254
                /* set the text attributes, if they changed */
 
255
                if (colors->attr != os_color_.attr)
 
256
                    set_os_text_attr(colors->attr);
 
257
 
 
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);
 
262
 
 
263
                /* 
 
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. 
 
268
                 */
 
269
                os_color_ = *colors;
 
270
            }
 
271
 
 
272
            /* get this character */
 
273
            c = *txt;
 
274
 
 
275
            /* 
 
276
             *   translate non-breaking spaces into ordinary spaces if the
 
277
             *   underlying target isn't HTML-based 
 
278
             */
 
279
            if (!html_target_ && c == 0x00A0)
 
280
                c = ' ';
 
281
 
 
282
            /* try storing another character */
 
283
            old_rem = rem;
 
284
            cur = (cmap_ != 0 ? cmap_ : G_cmap_to_ui)->map(c, &dst, &rem);
 
285
 
 
286
            /* if that failed, flush the buffer and try again */
 
287
            if (cur > old_rem)
 
288
            {
 
289
                /* null-terminate the buffer */
 
290
                *dst = '\0';
 
291
                
 
292
                /* display the text */
 
293
                print_to_os(local_buf);
 
294
 
 
295
                /* reset to the start of the local output buffer */
 
296
                dst = local_buf;
 
297
                rem = sizeof(local_buf) - 1;
 
298
            }
 
299
            else
 
300
            {
 
301
                /* we've now consumed this character of input */
 
302
                ++txt;
 
303
                --cnt;
 
304
                if (colors != 0)
 
305
                    ++colors;
 
306
            }
 
307
        }
 
308
 
 
309
        /* if we have a partially-filled buffer, display it */
 
310
        if (dst > local_buf)
 
311
        {
 
312
            /* null-terminate and display the buffer */
 
313
            *dst = '\0';
 
314
            print_to_os(local_buf);
 
315
        }
 
316
 
 
317
        /* write the appropriate type of line termination */
 
318
        switch(nl)
 
319
        {
 
320
        case VM_NL_NONE:
 
321
        case VM_NL_INPUT:
 
322
        case VM_NL_NONE_INTERNAL:
 
323
            /* no line termination is needed */
 
324
            break;
 
325
 
 
326
        case VM_NL_NEWLINE:
 
327
            /* write a newline */
 
328
            print_to_os(html_target_ ? "<BR HEIGHT=0>\n" : "\n");
 
329
            break;
 
330
 
 
331
        case VM_NL_OSNEWLINE:
 
332
            /* 
 
333
             *   the OS will provide a newline, but add a space to make it
 
334
             *   explicit that we can break the line here 
 
335
             */
 
336
            print_to_os(" ");
 
337
            break;
 
338
        }
 
339
    }
 
340
}
 
341
 
 
342
/* ------------------------------------------------------------------------ */
 
343
/*
 
344
 *   Flush the current line to the display, using the given type of line
 
345
 *   termination.
 
346
 *   
 
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
 
350
 *   input.
 
351
 *   
 
352
 *   VM_NL_INPUT: acts like VM_NL_NONE, except that we flush everything,
 
353
 *   including trailing spaces.
 
354
 *   
 
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.
 
361
 *   
 
362
 *   VM_NL_NEWLINE: flush the line and start a new line by writing out a
 
363
 *   newline character.
 
364
 *   
 
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.)  
 
371
 */
 
372
void CVmFormatter::flush(VMG_ vm_nl_type nl)
 
373
{
 
374
    int cnt;
 
375
    vm_nl_type write_nl;
 
376
 
 
377
    /* null-terminate the current output line buffer */
 
378
    linebuf_[linepos_] = '\0';
 
379
 
 
380
    /* 
 
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
 
384
     *   ending. 
 
385
     */
 
386
    expand_pending_tab(vmg_ nl == VM_NL_NEWLINE);
 
387
 
 
388
    /* 
 
389
     *   note number of characters to display - assume we'll display all of
 
390
     *   the characters in the buffer 
 
391
     */
 
392
    cnt = wcslen(linebuf_);
 
393
 
 
394
    /* 
 
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.)  
 
400
     */
 
401
    if (nl != VM_NL_INPUT && nl != VM_NL_NONE_INTERNAL)
 
402
    {
 
403
        /* 
 
404
         *   look for last non-space character, but keep any spaces that come
 
405
         *   before an explicit non-breaking flag 
 
406
         */
 
407
        for ( ; cnt > 0 && linebuf_[cnt-1] == ' ' ; --cnt)
 
408
        {
 
409
            /* don't remove this character if it's marked as non-breaking */
 
410
            if ((flagbuf_[cnt-1] & VMCON_OBF_NOBREAK) != 0)
 
411
                break;
 
412
        }
 
413
 
 
414
        /* 
 
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 
 
417
         */
 
418
        if (nl == VM_NL_NEWLINE)
 
419
            linepos_ = cnt;
 
420
    }
 
421
 
 
422
    /* check the newline mode */
 
423
    switch(nl)
 
424
    {
 
425
    case VM_NL_NONE:
 
426
    case VM_NL_NONE_INTERNAL:
 
427
        /* no newline - just flush out what we have */
 
428
        write_nl = VM_NL_NONE;
 
429
        break;
 
430
 
 
431
    case VM_NL_INPUT:
 
432
        /* no newline - flush out what we have */
 
433
        write_nl = VM_NL_NONE;
 
434
 
 
435
        /* on input, reset the HTML parsing state */
 
436
        html_passthru_state_ = VMCON_HPS_NORMAL;
 
437
        break;
 
438
 
 
439
    case VM_NL_NEWLINE:
 
440
        /* 
 
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.  
 
446
         */
 
447
        if (linecol_ != 0 || !just_did_nl_)
 
448
        {
 
449
            /* add the newline */
 
450
            write_nl = VM_NL_NEWLINE;
 
451
        }
 
452
        else
 
453
        {
 
454
            /* 
 
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
 
459
             *   newline).  
 
460
             */
 
461
            write_nl = VM_NL_NONE;
 
462
        }
 
463
        break;
 
464
 
 
465
    case VM_NL_OSNEWLINE:
 
466
        /* 
 
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
 
470
         *   break here 
 
471
         */
 
472
        write_nl = VM_NL_OSNEWLINE;
 
473
        break;
 
474
    }
 
475
 
 
476
    /* 
 
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 
 
481
     */
 
482
    if (cnt != 0 || (linecol_ != 0 && !just_did_nl_))
 
483
    {
 
484
        /* write it out */
 
485
        write_text(vmg_ linebuf_, cnt, colorbuf_, write_nl);
 
486
    }
 
487
 
 
488
    /* check the line ending */
 
489
    switch (nl)
 
490
    {
 
491
    case VM_NL_NONE:
 
492
    case VM_NL_INPUT:
 
493
        /* we're not displaying a newline, so flush what we have */
 
494
        flush_to_os();
 
495
 
 
496
        /* 
 
497
         *   the subsequent buffer will be a continuation of the current
 
498
         *   text, if we've displayed anything at all here 
 
499
         */
 
500
        is_continuation_ = (linecol_ != 0);
 
501
        break;
 
502
 
 
503
    case VM_NL_NONE_INTERNAL:
 
504
        /* 
 
505
         *   internal buffer flush only - subsequent text will be a
 
506
         *   continuation of the current line, if there's anything on the
 
507
         *   current line 
 
508
         */
 
509
        is_continuation_ = (linecol_ != 0);
 
510
        break;
 
511
 
 
512
    default:
 
513
        /* we displayed a newline, so reset the column position */
 
514
        linecol_ = 0;
 
515
 
 
516
        /* the next buffer starts a new line on the display */
 
517
        is_continuation_ = FALSE;
 
518
        break;
 
519
    }
 
520
 
 
521
    /* 
 
522
     *   Move any trailing characters we didn't write in this go to the start
 
523
     *   of the buffer.  
 
524
     */
 
525
    if (cnt < linepos_)
 
526
    {
 
527
        size_t movecnt;
 
528
 
 
529
        /* calculate how many trailing characters we didn't write */
 
530
        movecnt = linepos_ - cnt;
 
531
 
 
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]));
 
536
    }
 
537
 
 
538
    /* move the line output position to follow the preserved characters */
 
539
    linepos_ -= cnt;
 
540
 
 
541
    /* 
 
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.  
 
545
     */
 
546
    if (nl == VM_NL_NEWLINE)
 
547
        just_did_nl_ = TRUE;
 
548
    else if (cnt != 0)
 
549
        just_did_nl_ = FALSE;
 
550
 
 
551
    /* 
 
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) 
 
557
     */
 
558
    if (!cur_color_.equals(&os_color_))
 
559
    {
 
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);
 
563
 
 
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);
 
568
 
 
569
        /* set the new osifc color */
 
570
        os_color_ = cur_color_;
 
571
    }
 
572
}
 
573
 
 
574
/* ------------------------------------------------------------------------ */
 
575
/*
 
576
 *   Clear out our buffers 
 
577
 */
 
578
void CVmFormatter::empty_buffers(VMG0_)
 
579
{
 
580
    /* reset our buffer pointers */
 
581
    linepos_ = 0;
 
582
    linecol_ = 0;
 
583
    linebuf_[0] = '\0';
 
584
    just_did_nl_ = FALSE;
 
585
    is_continuation_ = FALSE;
 
586
 
 
587
    /* there's no pending tab now */
 
588
    pending_tab_align_ = VMFMT_TAB_NONE;
 
589
 
 
590
    /* start out at the first line */
 
591
    linecnt_ = 0;
 
592
 
 
593
    /* reset the HTML lexical state */
 
594
    html_passthru_state_ = VMCON_HPS_NORMAL;
 
595
}
 
596
 
 
597
/* ------------------------------------------------------------------------ */
 
598
/*
 
599
 *   Immediately update the display window 
 
600
 */
 
601
void CVmFormatter::update_display(VMG0_)
 
602
{
 
603
    /* update the display window at the OS layer */
 
604
    os_update_display();
 
605
}
 
606
 
 
607
/* ------------------------------------------------------------------------ */
 
608
/*
 
609
 *   Display a blank line to the stream
 
610
 */
 
611
void CVmFormatter::write_blank_line(VMG0_)
 
612
{
 
613
    /* flush the stream */
 
614
    flush(vmg_ VM_NL_NEWLINE);
 
615
 
 
616
    /* if generating for an HTML display target, add an HTML line break */
 
617
    if (html_target_)
 
618
        write_text(vmg_ L"<BR>", 4, 0, VM_NL_NONE);
 
619
 
 
620
    /* write out a blank line */
 
621
    write_text(vmg_ L"", 0, 0, VM_NL_NEWLINE);
 
622
}
 
623
 
 
624
/* ------------------------------------------------------------------------ */
 
625
/*
 
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.
 
628
 *   
 
629
 *   Standard (non-HTML) version: we'll generate enough spaces to take us to
 
630
 *   the next tab stop.
 
631
 *   
 
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
 
635
 *   MULTIPLE=4>.  
 
636
 */
 
637
void CVmFormatter::write_tab(VMG_ int indent, int multiple)
 
638
{
 
639
    int maxcol;
 
640
 
 
641
    /* check to see what the underlying system is expecting */
 
642
    if (html_target_)
 
643
    {
 
644
        char buf[40];
 
645
 
 
646
        /* 
 
647
         *   the underlying system is HTML - generate an appropriate <TAB>
 
648
         *   sequence to produce the desired effect 
 
649
         */
 
650
        sprintf(buf, "<TAB %s=%d>",
 
651
                indent != 0 ? "INDENT" : "MULTIPLE",
 
652
                indent != 0 ? indent : multiple);
 
653
            
 
654
        /* write it out */
 
655
        buffer_string(vmg_ buf);
 
656
    }
 
657
    else if (multiple != 0)
 
658
    {
 
659
        /* get the maximum column */
 
660
        maxcol = get_buffer_maxcol();
 
661
 
 
662
        /*
 
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.  
 
666
         */
 
667
        do
 
668
        {
 
669
            /* stop if we've reached the maximum column */
 
670
            if (linecol_ >= maxcol)
 
671
                break;
 
672
 
 
673
            /* add another space */
 
674
            linebuf_[linepos_] = ' ';
 
675
            flagbuf_[linepos_] = cur_flags_;
 
676
            colorbuf_[linepos_] = cur_color_;
 
677
 
 
678
            /* advance one character in the buffer */
 
679
            ++linepos_;
 
680
 
 
681
            /* advance the column counter */
 
682
            ++linecol_;
 
683
        } while ((linecol_ + 1) % multiple != 0);
 
684
    }
 
685
    else if (indent != 0)
 
686
    {
 
687
        /* 
 
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.  
 
691
         */
 
692
        for (maxcol = get_buffer_maxcol() ;
 
693
             indent != 0 && linecol_ < maxcol ; --indent)
 
694
        {
 
695
            /* add another space */
 
696
            linebuf_[linepos_] = ' ';
 
697
            flagbuf_[linepos_] = cur_flags_;
 
698
            colorbuf_[linepos_] = cur_color_;
 
699
 
 
700
            /* advance one character in the buffer and one column */
 
701
            ++linepos_;
 
702
            ++linecol_;
 
703
        }
 
704
    }
 
705
}
 
706
 
 
707
 
 
708
/* ------------------------------------------------------------------------ */
 
709
/*
 
710
 *   Flush a line 
 
711
 */
 
712
void CVmFormatter::flush_line(VMG_ int padding)
 
713
{
 
714
    /* 
 
715
     *   check to see if we're using the underlying display layer's line
 
716
     *   wrapping 
 
717
     */
 
718
    if (os_line_wrap_)
 
719
    {
 
720
        /*
 
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
 
727
         *   suppress newlines.
 
728
         *   
 
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.
 
733
         *   
 
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
 
738
         *   like it is.  
 
739
         */
 
740
        flush(vmg_ padding ? VM_NL_OSNEWLINE : VM_NL_NONE_INTERNAL);
 
741
    }
 
742
    else
 
743
    {
 
744
        /*
 
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.  
 
749
         */
 
750
        flush(vmg_ VM_NL_NEWLINE);
 
751
    }
 
752
}
 
753
 
 
754
 
 
755
/* ------------------------------------------------------------------------ */
 
756
/*
 
757
 *   Write a character to an output stream.  The character is provided to us
 
758
 *   as a wide Unicode character.  
 
759
 */
 
760
void CVmFormatter::buffer_char(VMG_ wchar_t c)
 
761
{
 
762
    const wchar_t *exp;
 
763
    size_t exp_len;
 
764
 
 
765
    /* check for a display expansion */
 
766
    exp = (cmap_ != 0 ? cmap_ : G_cmap_to_ui)->get_expansion(c, &exp_len);
 
767
    if (exp != 0)
 
768
    {
 
769
        /* write each character of the expansion */
 
770
        for ( ; exp_len != 0 ; ++exp, --exp_len)
 
771
            buffer_expchar(vmg_ *exp);
 
772
    }
 
773
    else
 
774
    {
 
775
        /* there's no expansion - buffer the character as-is */
 
776
        buffer_expchar(vmg_ c);
 
777
    }
 
778
}
 
779
 
 
780
/*
 
781
 *   Write an expanded character to an output stream.  
 
782
 */
 
783
void CVmFormatter::buffer_expchar(VMG_ wchar_t c)
 
784
{
 
785
    int i;
 
786
    int cwid;
 
787
    unsigned char cflags;
 
788
    int shy;
 
789
    int qspace;
 
790
 
 
791
    /* presume the character takes up only one column */
 
792
    cwid = 1;
 
793
 
 
794
    /* presume we'll use the current flags for the new character */
 
795
    cflags = cur_flags_;
 
796
 
 
797
    /* assume it's not a quoted space */
 
798
    qspace = FALSE;
 
799
 
 
800
    /* 
 
801
     *   Check for some special characters.
 
802
     *   
 
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.
 
807
     *   
 
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.  
 
815
     */
 
816
    if (html_target_)
 
817
    {
 
818
        /* 
 
819
         *   track the lexical state of the HTML stream going to the
 
820
         *   underlying renderer 
 
821
         */
 
822
        switch (html_passthru_state_)
 
823
        {
 
824
        case VMCON_HPS_MARKUP_END:
 
825
        case VMCON_HPS_NORMAL:
 
826
            /* check to see if we're starting a markup */
 
827
            if (c == '&')
 
828
                html_passthru_state_ = VMCON_HPS_ENTITY_1ST;
 
829
            else if (c == '<')
 
830
                html_passthru_state_ = VMCON_HPS_TAG;
 
831
            else
 
832
                html_passthru_state_ = VMCON_HPS_NORMAL;
 
833
            break;
 
834
 
 
835
        case VMCON_HPS_ENTITY_1ST:
 
836
            /* check to see what kind of entity we have */
 
837
            if (c == '#')
 
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;
 
841
            else
 
842
                html_passthru_state_ = VMCON_HPS_NORMAL;
 
843
            break;
 
844
 
 
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;
 
851
            else
 
852
                html_passthru_state_ = VMCON_HPS_NORMAL;
 
853
            break;
 
854
 
 
855
        case VMCON_HPS_ENTITY_HEX:
 
856
            /* see if we're done with hex digits */
 
857
            if (c == ';')
 
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;
 
863
            break;
 
864
 
 
865
        case VMCON_HPS_ENTITY_DEC:
 
866
            /* see if we're done with decimal digits */
 
867
            if (c == ';')
 
868
                html_passthru_state_ = VMCON_HPS_MARKUP_END;
 
869
            else if (c < '0' || c > '9')
 
870
                html_passthru_state_ = VMCON_HPS_NORMAL;
 
871
            break;
 
872
 
 
873
        case VMCON_HPS_ENTITY_NAME:
 
874
            /* see if we're done with alphanumerics */
 
875
            if (c == ';')
 
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;
 
881
            break;
 
882
 
 
883
        case VMCON_HPS_TAG:
 
884
            /* see if we're done with the tag, or entering quoted material */
 
885
            if (c == '>')
 
886
                html_passthru_state_ = VMCON_HPS_MARKUP_END;
 
887
            else if (c == '"')
 
888
                html_passthru_state_ = VMCON_HPS_DQUOTE;
 
889
            else if (c == '\'')
 
890
                html_passthru_state_ = VMCON_HPS_SQUOTE;
 
891
            break;
 
892
 
 
893
        case VMCON_HPS_SQUOTE:
 
894
            /* see if we're done with the quoted material */
 
895
            if (c == '\'')
 
896
                html_passthru_state_ = VMCON_HPS_TAG;
 
897
            break;
 
898
 
 
899
        case VMCON_HPS_DQUOTE:
 
900
            /* see if we're done with the quoted material */
 
901
            if (c == '"')
 
902
                html_passthru_state_ = VMCON_HPS_TAG;
 
903
            break;
 
904
 
 
905
        default:
 
906
            /* ignore other states */
 
907
            break;
 
908
        }
 
909
    }
 
910
    else
 
911
    {
 
912
        /* check for special characters */
 
913
        switch(c)
 
914
        {
 
915
        case 0x00AD:
 
916
            /*
 
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
 
921
             *   is invisible.  
 
922
             *   
 
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
 
926
             *   breaking the line.  
 
927
             */
 
928
            if (linepos_ != 0)
 
929
                flagbuf_[linepos_ - 1] |= VMCON_OBF_SHY;
 
930
 
 
931
            /* we don't need to buffer anything, so we're done */
 
932
            return;
 
933
 
 
934
        case 0xFEFF:
 
935
            /*
 
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
 
941
             *   breaking.  
 
942
             */
 
943
            if (linepos_ != 0)
 
944
                flagbuf_[linepos_ - 1] |= VMCON_OBF_NOBREAK;
 
945
 
 
946
            /* we don't buffer anything, so we're done */
 
947
            return;
 
948
 
 
949
        case 0x200B:                                    /* zero-width space */
 
950
        case 0x200a:                                          /* hair space */
 
951
        case 0x2008:                                   /* punctuation space */
 
952
            /* 
 
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.  
 
957
             *   
 
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.
 
967
             *   
 
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
 
973
             *   character.  
 
974
             */
 
975
            if (linepos_ != 0)
 
976
            {
 
977
                flagbuf_[linepos_ - 1] &= ~VMCON_OBF_NOBREAK;
 
978
                flagbuf_[linepos_ - 1] |= VMCON_OBF_OKBREAK;
 
979
            }
 
980
 
 
981
            /* we don't buffer anything, so we're done */
 
982
            return;
 
983
 
 
984
        case 0x00A0:
 
985
            /* non-breaking space - buffer it as given */
 
986
            break;
 
987
            
 
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 */
 
993
            /* 
 
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.
 
998
             */
 
999
            qspace = TRUE;
 
1000
            c = ' ';
 
1001
            break;
 
1002
 
 
1003
        case 0x2002:                                            /* en space */
 
1004
        case 0x2004:                                  /* three-per-em space */
 
1005
            /* 
 
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.  
 
1012
             */
 
1013
            qspace = TRUE;
 
1014
            cwid = 2;
 
1015
            c = ' ';
 
1016
            break;
 
1017
            
 
1018
        case 0x2003:
 
1019
            /* em space - mark it as non-combining */
 
1020
            qspace = TRUE;
 
1021
            
 
1022
            /* render this as three ordinary spaces */
 
1023
            cwid = 3;
 
1024
            c = ' ';
 
1025
            break;
 
1026
 
 
1027
        default:
 
1028
            /* 
 
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.  
 
1033
             */
 
1034
            if (t3_is_space(c))
 
1035
            {
 
1036
                /* convert it to an ordinary space */
 
1037
                c = ' ';
 
1038
                
 
1039
                /* if we're in obey-whitespace mode, quote this space */
 
1040
                qspace = obey_whitespace_;
 
1041
            }
 
1042
            break;
 
1043
        }
 
1044
    }
 
1045
 
 
1046
    /* if it's a quoted space, mark it as such in the buffer flags */
 
1047
    if (qspace)
 
1048
        cflags |= VMCON_OBF_QSPACE;
 
1049
 
 
1050
    /* 
 
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
 
1054
     *   entity text.  
 
1055
     */
 
1056
    if (html_passthru_state_ == VMCON_HPS_NORMAL)
 
1057
    {
 
1058
        if ((capsflag_ || allcapsflag_) && t3_is_alpha(c))
 
1059
        {
 
1060
            /* capsflag is set, so capitalize this character */
 
1061
            c = t3_to_upper(c);
 
1062
            
 
1063
            /* okay, we've capitalized something; clear flag */
 
1064
            capsflag_ = FALSE;
 
1065
        }
 
1066
        else if (nocapsflag_ && t3_is_alpha(c))
 
1067
        {
 
1068
            /* nocapsflag is set, so minisculize this character */
 
1069
            c = t3_to_lower(c);
 
1070
            
 
1071
            /* clear the flag now that we've done the job */
 
1072
            nocapsflag_ = FALSE;
 
1073
        }
 
1074
    }
 
1075
 
 
1076
    /*
 
1077
     *   If this is a space of some kind, we might be able to consolidate it
 
1078
     *   with a preceding character. 
 
1079
     */
 
1080
    if (c == ' ')
 
1081
    {
 
1082
        /* ignore ordinary whitespace at the start of a line */
 
1083
        if (linecol_ == 0 && !qspace)
 
1084
            return;
 
1085
 
 
1086
        /* 
 
1087
         *   Consolidate runs of whitespace.  Ordinary whitespace is
 
1088
         *   subsumed into any type of quoted spaces, but quoted spaces do
 
1089
         *   not combine.  
 
1090
         */
 
1091
        if (linepos_ > 0)
 
1092
        {
 
1093
            wchar_t prv;
 
1094
 
 
1095
            /* get the previous character */
 
1096
            prv = linebuf_[linepos_ - 1];
 
1097
            
 
1098
            /* 
 
1099
             *   if the new character is an ordinary (combining) whitespace
 
1100
             *   character, subsume it into any preceding space character 
 
1101
             */
 
1102
            if (!qspace && prv == ' ')
 
1103
                return;
 
1104
 
 
1105
            /* 
 
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 
 
1109
             */
 
1110
            if (qspace
 
1111
                && prv == ' '
 
1112
                && !(flagbuf_[linepos_ - 1] & VMCON_OBF_QSPACE))
 
1113
            {
 
1114
                /* remove the preceding ordinary whitespace */
 
1115
                --linepos_;
 
1116
                --linecol_;
 
1117
            }
 
1118
        }
 
1119
    }
 
1120
 
 
1121
    /* if the new character fits in the line, add it */
 
1122
    if (linecol_ + cwid < get_buffer_maxcol())
 
1123
    {
 
1124
        /* buffer this character */
 
1125
        buffer_rendered(c, cflags, cwid);
 
1126
 
 
1127
        /* we're finished processing the character */
 
1128
        return;
 
1129
    }
 
1130
 
 
1131
    /*
 
1132
     *   The line would overflow if this character were added.
 
1133
     *   
 
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.  
 
1140
     */
 
1141
    if (c == ' ')
 
1142
    {
 
1143
        /* 
 
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.  
 
1149
         */
 
1150
        if (linebuf_[linepos_ - 1] == ' ')
 
1151
        {
 
1152
            /* 
 
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
 
1160
             *   whitespace. 
 
1161
             */
 
1162
        }
 
1163
        else
 
1164
        {
 
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_;
 
1169
 
 
1170
            /* advance one character in the buffer */
 
1171
            ++linepos_;
 
1172
        }
 
1173
 
 
1174
        /* 
 
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.  
 
1183
         */
 
1184
        linecol_ += cwid;
 
1185
 
 
1186
        /* done for now */
 
1187
        return;
 
1188
    }
 
1189
    
 
1190
    /*
 
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. 
 
1194
     *   
 
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.)
 
1199
     */
 
1200
    linebuf_[linepos_] = c;
 
1201
    flagbuf_[linepos_] = cur_flags_;
 
1202
 
 
1203
    /* 
 
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 
 
1208
     */
 
1209
    if (os_line_wrap_)
 
1210
    {
 
1211
        /* flush the line, adding no padding after it */
 
1212
        flush_line(vmg_ FALSE);
 
1213
 
 
1214
        /* 
 
1215
         *   we've completely cleared out the line buffer, so reset all of
 
1216
         *   the line buffer counters 
 
1217
         */
 
1218
        linepos_ = 0;
 
1219
        linecol_ = 0;
 
1220
        linebuf_[0] = '\0';
 
1221
        is_continuation_ = FALSE;
 
1222
 
 
1223
        /* we're done */
 
1224
        goto done_with_wrapping;
 
1225
    }
 
1226
 
 
1227
    /*
 
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.  
 
1233
     */
 
1234
    for (shy = FALSE, i = linepos_ ; i >= 0 ; --i)
 
1235
    {
 
1236
        unsigned char f;
 
1237
        unsigned char prvf;
 
1238
        
 
1239
        /* 
 
1240
         *   There are two break modes: word-break mode and break-anywhere
 
1241
         *   mode.  The modes are applied to each character, via the buffer
 
1242
         *   flags.
 
1243
         *   
 
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.
 
1248
         *   
 
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.  
 
1252
         */
 
1253
 
 
1254
        /* get the current character's flags */
 
1255
        f = flagbuf_[i];
 
1256
 
 
1257
        /* get the preceding character's flags */
 
1258
        prvf = (i > 0 ? flagbuf_[i-1] : 0);
 
1259
 
 
1260
        /* 
 
1261
         *   if the preceding character is marked as a no-break point, we
 
1262
         *   definitely can't break here, so keep looking 
 
1263
         */
 
1264
        if ((prvf & VMCON_OBF_NOBREAK) != 0)
 
1265
            continue;
 
1266
 
 
1267
        /* 
 
1268
         *   if the preceding character is marked as an explicit ok-to-break
 
1269
         *   point, we definitely can break here 
 
1270
         */
 
1271
        if ((prvf & VMCON_OBF_OKBREAK) != 0)
 
1272
            break;
 
1273
 
 
1274
        /* 
 
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.
 
1281
         *   
 
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.
 
1291
         */
 
1292
        if ((f & VMCON_OBF_BREAK_ANY) != 0
 
1293
            || (i > 0 && (prvf & VMCON_OBF_BREAK_ANY) != 0))
 
1294
            break;
 
1295
 
 
1296
        /* 
 
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.  
 
1303
         */
 
1304
        if (i > 0 && i < linepos_ && (prvf & VMCON_OBF_SHY) != 0)
 
1305
        {
 
1306
            /* note that we're breaking at a soft hyphen */
 
1307
            shy = TRUE;
 
1308
 
 
1309
            /* we can break here */
 
1310
            break;
 
1311
        }
 
1312
 
 
1313
        /* 
 
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) 
 
1316
         */
 
1317
        if (linebuf_[i] == ' ')
 
1318
            break;
 
1319
 
 
1320
        /* 
 
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.  
 
1326
         */
 
1327
        if (i > 0 && linebuf_[i-1] == ' ')
 
1328
            break;
 
1329
 
 
1330
        /* 
 
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).
 
1334
         */
 
1335
        if (i > 0 && linebuf_[i-1] == '-' && linebuf_[i] != '-')
 
1336
            break;
 
1337
    }
 
1338
    
 
1339
    /* check to see if we found a good place to break */
 
1340
    if (i < 0)
 
1341
    {
 
1342
        /*
 
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
 
1347
         *   breaking point.
 
1348
         *   
 
1349
         *   In any case, don't let our buffer fill up beyond its maximum
 
1350
         *   size.  
 
1351
         */
 
1352
        if (!console_->allow_overrun() || linepos_ + 1 >= OS_MAXWIDTH)
 
1353
        {
 
1354
            /* 
 
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
 
1358
             *   word 
 
1359
             */
 
1360
            flush_line(vmg_ FALSE);
 
1361
            
 
1362
            /* 
 
1363
             *   we've completely cleared out the line buffer, so reset all
 
1364
             *   of the line buffer counters 
 
1365
             */
 
1366
            linepos_ = 0;
 
1367
            linecol_ = 0;
 
1368
            linebuf_[0] = '\0';
 
1369
            is_continuation_ = FALSE;
 
1370
        }
 
1371
    }
 
1372
    else
 
1373
    {
 
1374
        wchar_t tmpbuf[OS_MAXWIDTH];
 
1375
        vmcon_color_t tmpcolor[OS_MAXWIDTH];
 
1376
        unsigned char tmpflags[OS_MAXWIDTH];
 
1377
        size_t tmpchars;
 
1378
        int nxti;
 
1379
 
 
1380
        /* null-terminate the line buffer */        
 
1381
        linebuf_[linepos_] = '\0';
 
1382
 
 
1383
        /* trim off leading spaces on the next line after the break */
 
1384
        for (nxti = i ; linebuf_[nxti] == ' ' ; ++nxti) ;
 
1385
 
 
1386
        /* 
 
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
 
1390
         *   to expand tabs.  
 
1391
         */
 
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]));
 
1396
 
 
1397
        /* if we're breaking at a soft hyphen, insert a real hyphen */
 
1398
        if (shy)
 
1399
            linebuf_[i++] = '-';
 
1400
        
 
1401
        /* trim off trailing spaces */
 
1402
        for ( ; i > 0 && linebuf_[i-1] == ' ' ; --i)
 
1403
        {
 
1404
            /* stop if we've reached a non-breaking point */
 
1405
            if ((flagbuf_[i-1] & VMCON_OBF_NOBREAK) != 0)
 
1406
                break;
 
1407
        }
 
1408
 
 
1409
        /* terminate the buffer after the break point */
 
1410
        linebuf_[i] = '\0';
 
1411
        
 
1412
        /* write out everything up to the break point */
 
1413
        flush_line(vmg_ TRUE);
 
1414
 
 
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;
 
1420
    }
 
1421
    
 
1422
done_with_wrapping:
 
1423
    /* add the new character to buffer */
 
1424
    buffer_rendered(c, cflags, cwid);
 
1425
}
 
1426
 
 
1427
/*
 
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.
 
1431
 *   
 
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.  
 
1436
 */
 
1437
void CVmFormatter::buffer_rendered(wchar_t c, unsigned char flags, int wid)
 
1438
{
 
1439
    unsigned char flags_before;
 
1440
 
 
1441
    /* note whether or not we have a break before us */
 
1442
    flags_before = (linepos_ > 0
 
1443
                    ? flagbuf_[linepos_-1] & VMCON_OBF_NOBREAK
 
1444
                    : 0);
 
1445
 
 
1446
    /* add the character the given number of times */
 
1447
    for ( ; wid != 0 ; --wid)
 
1448
    {
 
1449
        /* buffer the character */
 
1450
        linebuf_[linepos_] = c;
 
1451
        flagbuf_[linepos_] = flags;
 
1452
        colorbuf_[linepos_] = cur_color_;
 
1453
 
 
1454
        /* 
 
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 
 
1459
         */
 
1460
        if (wid > 1)
 
1461
            flagbuf_[linepos_] |= flags_before;
 
1462
 
 
1463
        /* advance one character in the buffer */
 
1464
        ++linepos_;
 
1465
 
 
1466
        /* adjust our column counter */
 
1467
        ++linecol_;
 
1468
    }
 
1469
}
 
1470
 
 
1471
/* ------------------------------------------------------------------------ */
 
1472
/* 
 
1473
 *   write out a UTF-8 string
 
1474
 */
 
1475
void CVmFormatter::buffer_string(VMG_ const char *txt)
 
1476
{
 
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));
 
1480
}
 
1481
 
 
1482
/*
 
1483
 *   write out a wide unicode string 
 
1484
 */
 
1485
void CVmFormatter::buffer_wstring(VMG_ const wchar_t *txt)
 
1486
{
 
1487
    /* write out each wide character */
 
1488
    for ( ; *txt != '\0' ; ++txt)
 
1489
        buffer_char(vmg_ *txt);
 
1490
}
 
1491
 
 
1492
 
 
1493
/* ------------------------------------------------------------------------ */
 
1494
/*
 
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.  
 
1498
 */
 
1499
wchar_t CVmFormatter::next_wchar(const char **s, size_t *len)
 
1500
{
 
1501
    wchar_t ret;
 
1502
    size_t charsize;
 
1503
 
 
1504
    /* if there's nothing left, return a null terminator */
 
1505
    if (*len == 0)
 
1506
        return 0;
 
1507
 
 
1508
    /* get this character */
 
1509
    ret = utf8_ptr::s_getch(*s);
 
1510
 
 
1511
    /* advance the string pointer and length counter */
 
1512
    charsize = utf8_ptr::s_charsize(**s);
 
1513
    *len -= charsize;
 
1514
    *s += charsize;
 
1515
 
 
1516
    /* return the result */
 
1517
    return ret;
 
1518
}
 
1519
 
 
1520
/* ------------------------------------------------------------------------ */
 
1521
/*
 
1522
 *   Display a string of a given length.  The text is encoded as UTF-8
 
1523
 *   characters.  
 
1524
 */
 
1525
int CVmFormatter::format_text(VMG_ const char *s, size_t slen)
 
1526
{
 
1527
    wchar_t c;
 
1528
    int done = FALSE;
 
1529
 
 
1530
    /* get the first character */
 
1531
    c = next_wchar(&s, &slen);
 
1532
 
 
1533
    /* if we have anything to show, show it */
 
1534
    while (c != '\0')
 
1535
    {
 
1536
        /* 
 
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 
 
1540
         */
 
1541
        if (html_parse_state_ != VMCON_HPS_NORMAL
 
1542
            || (html_in_ignore_ && c != '&' && c != '<'))
 
1543
        {
 
1544
            /* run our HTML parsing until we finish the tag */
 
1545
            c = resume_html_parsing(vmg_ c, &s, &slen);
 
1546
 
 
1547
            /* proceed with the next character */
 
1548
            continue;
 
1549
        }
 
1550
 
 
1551
        /* check for special characters */
 
1552
        switch(c)
 
1553
        {
 
1554
        case 10:
 
1555
            /* newline */
 
1556
            flush(vmg_ VM_NL_NEWLINE);
 
1557
            break;
 
1558
                    
 
1559
        case 9:
 
1560
            /* tab - write an ordinary every-4-columns tab */
 
1561
            write_tab(vmg_ 0, 4);
 
1562
            break;
 
1563
 
 
1564
        case 0x000B:
 
1565
            /* \b - blank line */
 
1566
            write_blank_line(vmg0_);
 
1567
            break;
 
1568
                    
 
1569
        case 0x000F:
 
1570
            /* capitalize next character */
 
1571
            capsflag_ = TRUE;
 
1572
            nocapsflag_ = FALSE;
 
1573
            break;
 
1574
 
 
1575
        case 0x000E:
 
1576
            /* un-capitalize next character */
 
1577
            nocapsflag_ = TRUE;
 
1578
            capsflag_ = FALSE;
 
1579
            break;
 
1580
 
 
1581
        case '<':
 
1582
        case '&':
 
1583
            /* HTML markup-start character - process it */
 
1584
            if (html_target_ || literal_mode_)
 
1585
            {
 
1586
                /* 
 
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.  
 
1592
                 */
 
1593
                goto normal_char;
 
1594
            }
 
1595
            else
 
1596
            {
 
1597
                /*
 
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.  
 
1602
                 */
 
1603
                c = parse_html_markup(vmg_ c, &s, &slen);
 
1604
 
 
1605
                /* go back and process the next character */
 
1606
                continue;
 
1607
            }
 
1608
            break;
 
1609
 
 
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 */
 
1624
            /* 
 
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.
 
1628
             */
 
1629
            if (html_target_)
 
1630
            {
 
1631
                char buf[15];
 
1632
                char *p;
 
1633
                
 
1634
                /* 
 
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
 
1638
                 *   the semicolon) 
 
1639
                 */
 
1640
                p = buf + sizeof(buf) - 1;
 
1641
                *p-- = '\0';
 
1642
                *p-- = ';';
 
1643
 
 
1644
                /* generate the decimal representation of 'c' */
 
1645
                for ( ; c != 0 ; c /= 10)
 
1646
                    *p-- = (c % 10) + '0';
 
1647
 
 
1648
                /* add the '&#' sequence */
 
1649
                *p-- = '#';
 
1650
                *p = '&';
 
1651
 
 
1652
                /* write out the sequence */
 
1653
                buffer_string(vmg_ p);
 
1654
            }
 
1655
            else
 
1656
            {
 
1657
                /* for non-HTML targets, treat these as normal */
 
1658
                goto normal_char;
 
1659
            }
 
1660
            break;
 
1661
 
 
1662
        default:
 
1663
        normal_char:
 
1664
            /* normal character - write it out */
 
1665
            buffer_char(vmg_ c);
 
1666
            break;
 
1667
        }
 
1668
 
 
1669
        /* move on to the next character, unless we're finished */
 
1670
        if (done)
 
1671
            c = '\0';
 
1672
        else
 
1673
            c = next_wchar(&s, &slen);
 
1674
    }
 
1675
 
 
1676
    /* success */
 
1677
    return 0;
 
1678
}
 
1679
 
 
1680
/* ------------------------------------------------------------------------ */
 
1681
/*
 
1682
 *   Initialize the display object 
 
1683
 */
 
1684
CVmConsole::CVmConsole()
 
1685
{
 
1686
    /* no script file yet */
 
1687
    script_sp_ = 0;
 
1688
 
 
1689
    /* no command log file yet */
 
1690
    command_fp_ = 0;
 
1691
 
 
1692
    /* assume we'll double-space after each period */
 
1693
    doublespace_ = TRUE;
 
1694
 
 
1695
    /* presume we'll have no log stream */
 
1696
    log_str_ = 0;
 
1697
    log_enabled_ = FALSE;
 
1698
}
 
1699
 
 
1700
/*
 
1701
 *   Delete the display object 
 
1702
 */
 
1703
CVmConsole::~CVmConsole()
 
1704
{
 
1705
    /* close any active script file(s) */
 
1706
    while (script_sp_ != 0)
 
1707
    {
 
1708
        /* close this file */
 
1709
        osfcls(script_sp_->fp);
 
1710
 
 
1711
        /* unlink this stack level */
 
1712
        script_stack_entry *cur = script_sp_;
 
1713
        script_sp_ = cur->enc;
 
1714
 
 
1715
        /* delete the entry */
 
1716
        delete cur;
 
1717
    }
 
1718
 
 
1719
    /* close any active command log file */
 
1720
    close_command_log();
 
1721
 
 
1722
    /* delete the log stream if we have one */
 
1723
    if (log_str_ != 0)
 
1724
        delete log_str_;
 
1725
}
 
1726
 
 
1727
/* ------------------------------------------------------------------------ */
 
1728
/*
 
1729
 *   Display a string of a given byte length 
 
1730
 */
 
1731
int CVmConsole::format_text(VMG_ const char *p, size_t len)
 
1732
{
 
1733
    /* display the string */
 
1734
    disp_str_->format_text(vmg_ p, len);
 
1735
 
 
1736
    /* if there's a log file, write to the log file as well */
 
1737
    if (log_enabled_)
 
1738
        log_str_->format_text(vmg_ p, len);
 
1739
 
 
1740
    /* indicate success */
 
1741
    return 0;
 
1742
}
 
1743
 
 
1744
/*
 
1745
 *   Display a string on the log stream only 
 
1746
 */
 
1747
int CVmConsole::format_text_to_log(VMG_ const char *p, size_t len)
 
1748
{
 
1749
    /* if there's a log file, write to it; otherwise ignore the whole thing */
 
1750
    if (log_enabled_)
 
1751
        log_str_->format_text(vmg_ p, len);
 
1752
 
 
1753
    /* indicate success */
 
1754
    return 0;
 
1755
}
 
1756
 
 
1757
/* ------------------------------------------------------------------------ */
 
1758
/*
 
1759
 *   Set the text color 
 
1760
 */
 
1761
void CVmConsole::set_text_color(VMG_ os_color_t fg, os_color_t bg)
 
1762
{
 
1763
    /* set the color in our main display stream */
 
1764
    disp_str_->set_text_color(vmg_ fg, bg);
 
1765
}
 
1766
 
 
1767
/*
 
1768
 *   Set the body color 
 
1769
 */
 
1770
void CVmConsole::set_body_color(VMG_ os_color_t color)
 
1771
{
 
1772
    /* set the color in the main display stream */
 
1773
    disp_str_->set_os_body_color(color);
 
1774
}
 
1775
 
 
1776
/* ------------------------------------------------------------------------ */
 
1777
/*
 
1778
 *   Display a blank line 
 
1779
 */
 
1780
void CVmConsole::write_blank_line(VMG0_)
 
1781
{
 
1782
    /* generate the newline to the standard display */
 
1783
    disp_str_->write_blank_line(vmg0_);
 
1784
 
 
1785
    /* if we're logging, generate the newline to the log file as well */
 
1786
    if (log_enabled_)
 
1787
        log_str_->write_blank_line(vmg0_);
 
1788
}
 
1789
 
 
1790
 
 
1791
/* ------------------------------------------------------------------------ */
 
1792
/*
 
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).  
 
1796
 */
 
1797
void CVmConsole::caps()
 
1798
{
 
1799
    disp_str_->caps();
 
1800
    if (log_enabled_)
 
1801
        log_str_->caps();
 
1802
}
 
1803
 
 
1804
/*
 
1805
 *   outnocaps() - sets the next letter to a miniscule, whether it came in
 
1806
 *   that way or not.  
 
1807
 */
 
1808
void CVmConsole::nocaps()
 
1809
{
 
1810
    disp_str_->nocaps();
 
1811
    if (log_enabled_)
 
1812
        log_str_->nocaps();
 
1813
}
 
1814
 
 
1815
/*
 
1816
 *   obey_whitespace() - sets the obey-whitespace mode 
 
1817
 */
 
1818
int CVmConsole::set_obey_whitespace(int f)
 
1819
{
 
1820
    int ret;
 
1821
 
 
1822
    /* note the original display stream status */
 
1823
    ret = disp_str_->get_obey_whitespace();
 
1824
 
 
1825
    /* set the stream status */
 
1826
    disp_str_->set_obey_whitespace(f);
 
1827
    if (log_enabled_)
 
1828
        log_str_->set_obey_whitespace(f);
 
1829
 
 
1830
    /* return the original status of the display stream */
 
1831
    return ret;
 
1832
}
 
1833
 
 
1834
/* ------------------------------------------------------------------------ */
 
1835
/*
 
1836
 *   Open a log file 
 
1837
 */
 
1838
int CVmConsole::open_log_file(const char *fname)
 
1839
{
 
1840
    /* if there's no log stream, we can't open a log file */
 
1841
    if (log_str_ == 0)
 
1842
        return 1;
 
1843
 
 
1844
    /* 
 
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.  
 
1849
     */
 
1850
    return log_str_->open_log_file(fname);
 
1851
}
 
1852
 
 
1853
/*
 
1854
 *   Close the log file 
 
1855
 */
 
1856
int CVmConsole::close_log_file()
 
1857
{
 
1858
    /* if there's no log stream, there's obviously no file open */
 
1859
    if (log_str_ == 0)
 
1860
        return 1;
 
1861
 
 
1862
    /* tell the log stream to close its file */
 
1863
    return log_str_->close_log_file();
 
1864
}
 
1865
 
 
1866
#if 0 //$$$
 
1867
/*
 
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.  
 
1871
 */
 
1872
 
 
1873
/* ------------------------------------------------------------------------ */
 
1874
/*
 
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.
 
1881
 *   
 
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
 
1884
 *   here.  
 
1885
 */
 
1886
void CVmConsole::write_to_logfile(VMG_ const char *txt, int nl)
 
1887
{
 
1888
    /* if there's no log file, there's nothing to do */
 
1889
    if (logfp_ == 0)
 
1890
        return;
 
1891
 
 
1892
    /* write the text in the log file character set */
 
1893
    write_to_file(logfp_, txt, G_cmap_to_log);
 
1894
 
 
1895
    /* add a newline if desired */
 
1896
    if (nl)
 
1897
    {
 
1898
        /* add a normal newline */
 
1899
        os_fprintz(logfp_, "\n");
 
1900
 
 
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");
 
1904
    }
 
1905
 
 
1906
    /* flush the output */
 
1907
    osfflush(logfp_);
 
1908
}
 
1909
 
 
1910
/*
 
1911
 *   Write text to a file in the given character set 
 
1912
 */
 
1913
void CVmConsole::write_to_file(osfildef *fp, const char *txt,
 
1914
                               CCharmapToLocal *map)
 
1915
{
 
1916
    size_t txtlen = strlen(txt);
 
1917
    
 
1918
    /* 
 
1919
     *   convert the text from UTF-8 to the local character set and write the
 
1920
     *   converted text to the log file 
 
1921
     */
 
1922
    while (txtlen != 0)
 
1923
    {
 
1924
        char local_buf[128];
 
1925
        size_t src_bytes_used;
 
1926
        size_t out_bytes;
 
1927
        
 
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);
 
1931
 
 
1932
        /* null-terminate the result */
 
1933
        local_buf[out_bytes] = '\0';
 
1934
 
 
1935
        /* write the converted text */
 
1936
        os_fprintz(fp, local_buf);
 
1937
 
 
1938
        /* skip the text we were able to convert */
 
1939
        txt += src_bytes_used;
 
1940
        txtlen -= src_bytes_used;
 
1941
    }
 
1942
}
 
1943
#endif /* 0 */
 
1944
 
 
1945
 
 
1946
/* ------------------------------------------------------------------------ */
 
1947
/*
 
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.  
 
1952
 */
 
1953
void CVmConsole::reset_line_count(int clearing)
 
1954
{
 
1955
    /* reset the MORE counter in the display stream */
 
1956
    disp_str_->reset_line_count(clearing);
 
1957
}
 
1958
 
 
1959
/* ------------------------------------------------------------------------ */
 
1960
/*
 
1961
 *   Flush the output line.  We'll write to both the standard display and
 
1962
 *   the log file, as needed.  
 
1963
 */
 
1964
void CVmConsole::flush(VMG_ vm_nl_type nl)
 
1965
{
 
1966
    /* flush the display stream */
 
1967
    disp_str_->flush(vmg_ nl);
 
1968
 
 
1969
    /* flush the log stream, if we have an open log file */
 
1970
    if (log_enabled_)
 
1971
        log_str_->flush(vmg_ nl);
 
1972
}
 
1973
 
 
1974
/* ------------------------------------------------------------------------ */
 
1975
/*
 
1976
 *   Clear our buffers
 
1977
 */
 
1978
void CVmConsole::empty_buffers(VMG0_)
 
1979
{
 
1980
    /* tell the formatter to clear its buffer */
 
1981
    disp_str_->empty_buffers(vmg0_);
 
1982
 
 
1983
    /* same with the log stream, if applicable */
 
1984
    if (log_enabled_)
 
1985
        log_str_->empty_buffers(vmg0_);
 
1986
}
 
1987
 
 
1988
/* ------------------------------------------------------------------------ */
 
1989
/*
 
1990
 *   Immediately update the display 
 
1991
 */
 
1992
void CVmConsole::update_display(VMG0_)
 
1993
{
 
1994
    /* update the display for the main display stream */
 
1995
    disp_str_->update_display(vmg0_);
 
1996
}
 
1997
 
 
1998
/* ------------------------------------------------------------------------ */
 
1999
/*
 
2000
 *   Open a script file 
 
2001
 */
 
2002
void CVmConsole::open_script_file(const char *fname, int quiet,
 
2003
                                  int script_more_mode)
 
2004
{
 
2005
    int evt;
 
2006
    char buf[50];
 
2007
    
 
2008
    /* try opening the file */
 
2009
    osfildef *fp = osfoprt(fname, OSFTCMD);
 
2010
 
 
2011
    /* if that failed, silently ignore the request */
 
2012
    if (fp == 0)
 
2013
        return;
 
2014
 
 
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)
 
2018
    {
 
2019
        /* remember that it's an event script */
 
2020
        evt = TRUE;
 
2021
    }
 
2022
    else
 
2023
    {
 
2024
        /* 
 
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
 
2027
         *   input line 
 
2028
         */
 
2029
        evt = FALSE;
 
2030
        osfseek(fp, 0, OSFSK_SET);
 
2031
    }
 
2032
 
 
2033
    /* if there's an enclosing script, inherit its modes */
 
2034
    if (script_sp_ != 0)
 
2035
    {
 
2036
        /* 
 
2037
         *   if the enclosing script is quiet, force the nested script to be
 
2038
         *   quiet as well 
 
2039
         */
 
2040
        if (script_sp_->quiet)
 
2041
            quiet = TRUE;
 
2042
 
 
2043
        /* 
 
2044
         *   if the enclosing script is nonstop, force the nested script to
 
2045
         *   be nonstop as well
 
2046
         */
 
2047
        if (!script_sp_->more_mode)
 
2048
            script_more_mode = FALSE;
 
2049
    }
 
2050
 
 
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);
 
2055
    
 
2056
    /* turn on NONSTOP mode in the OS layer if applicable */
 
2057
    if (!script_more_mode)
 
2058
        os_nonstop_mode(TRUE);
 
2059
}
 
2060
 
 
2061
/*
 
2062
 *   Close the current script file 
 
2063
 */
 
2064
int CVmConsole::close_script_file()
 
2065
{
 
2066
    script_stack_entry *e;
 
2067
    
 
2068
    /* if we have a file, close it */
 
2069
    if ((e = script_sp_) != 0)
 
2070
    {
 
2071
        int ret;
 
2072
        
 
2073
        /* close the file */
 
2074
        osfcls(e->fp);
 
2075
 
 
2076
        /* pop the stack */
 
2077
        script_sp_ = e->enc;
 
2078
 
 
2079
        /* restore the enclosing level's MORE mode */
 
2080
        os_nonstop_mode(!e->old_more_mode);
 
2081
 
 
2082
        /* 
 
2083
         *   return the MORE mode in effect before we started reading the
 
2084
         *   script file 
 
2085
         */
 
2086
        ret = e->old_more_mode;
 
2087
 
 
2088
        /* delete the stack level */
 
2089
        delete e;
 
2090
 
 
2091
        /* return the result */
 
2092
        return ret;
 
2093
    }
 
2094
    else
 
2095
    {
 
2096
        /* 
 
2097
         *   there's no script file - just return the current MORE mode,
 
2098
         *   since we're not making any changes 
 
2099
         */
 
2100
        return is_more_mode();
 
2101
    }
 
2102
}
 
2103
 
 
2104
/* ------------------------------------------------------------------------ */
 
2105
/*
 
2106
 *   Open a command log file 
 
2107
 */
 
2108
int CVmConsole::open_command_log(const char *fname, int event_script)
 
2109
{
 
2110
    /* close any existing command log file */
 
2111
    close_command_log();
 
2112
    
 
2113
    /* remember the filename */
 
2114
    strcpy(command_fname_, fname);
 
2115
 
 
2116
    /* open the file */
 
2117
    command_fp_ = osfopwt(fname, OSFTCMD);
 
2118
 
 
2119
    /* note the type */
 
2120
    command_eventscript_ = event_script;
 
2121
 
 
2122
    /* if it's an event script, write the file type tag */
 
2123
    if (event_script && command_fp_ != 0)
 
2124
    {
 
2125
        os_fprintz(command_fp_, "<eventscript>\n");
 
2126
        osfflush(command_fp_);
 
2127
    }
 
2128
 
 
2129
    /* return success if we successfully opened the file */
 
2130
    return (command_fp_ == 0);
 
2131
}
 
2132
 
 
2133
/* 
 
2134
 *   close the active command log file 
 
2135
 */
 
2136
int CVmConsole::close_command_log()
 
2137
{
 
2138
    /* if there's a command log file, close it */
 
2139
    if (command_fp_ != 0)
 
2140
    {
 
2141
        /* close the file */
 
2142
        osfcls(command_fp_);
 
2143
 
 
2144
        /* set its file type */
 
2145
        os_settype(command_fname_, OSFTCMD);
 
2146
 
 
2147
        /* forget the file */
 
2148
        command_fp_ = 0;
 
2149
    }
 
2150
 
 
2151
    /* success */
 
2152
    return 0;
 
2153
}
 
2154
 
 
2155
 
 
2156
/* ------------------------------------------------------------------------ */
 
2157
/*
 
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.  
 
2161
 */
 
2162
int CVmConsole::read_line(VMG_ char *buf, size_t buflen)
 
2163
{
 
2164
    /* cancel any previous interrupted input */
 
2165
    read_line_cancel(vmg_ TRUE);
 
2166
 
 
2167
try_again:
 
2168
    /* use the timeout version, with no timeout specified */
 
2169
    switch(read_line_timeout(vmg_ buf, buflen, 0, FALSE))
 
2170
    {
 
2171
    case OS_EVT_LINE:
 
2172
        /* success */
 
2173
        return 0;
 
2174
 
 
2175
    case VMCON_EVT_END_SCRIPT:
 
2176
        /* 
 
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
 
2179
         *   line 
 
2180
         */
 
2181
        goto try_again;
 
2182
 
 
2183
    default:
 
2184
        /* anything else is an error */
 
2185
        return 1;
 
2186
    }
 
2187
}
 
2188
 
 
2189
 
 
2190
/* ------------------------------------------------------------------------ */
 
2191
/*
 
2192
 *   Log an event to the output script.  The parameter is in the UI character
 
2193
 *   set.  
 
2194
 */
 
2195
int CVmConsole::log_event(VMG_ int evt,
 
2196
                          const char *param, size_t paramlen,
 
2197
                          int param_is_utf8)
 
2198
{
 
2199
    /* if there's a script file, log the event */
 
2200
    if (command_fp_ != 0)
 
2201
    {
 
2202
        /* write the event in the proper format for the script type */
 
2203
        if (command_eventscript_)
 
2204
        {
 
2205
            const char *tag = 0;
 
2206
            
 
2207
            /* write the event according to its type */
 
2208
            switch (evt)
 
2209
            {
 
2210
            case OS_EVT_KEY:
 
2211
                /* use the "<key>" tag */
 
2212
                tag = "<key>";
 
2213
                
 
2214
                /* 
 
2215
                 *   use the normal key representation, except we want to
 
2216
                 *   write \n as [enter] and \t as [tab] 
 
2217
                 */
 
2218
                if (param != 0)
 
2219
                {
 
2220
                    switch (*param)
 
2221
                    {
 
2222
                    case '\n':
 
2223
                        param = "[enter]";
 
2224
                        paramlen = 7;
 
2225
                        break;
 
2226
 
 
2227
                    case '\t':
 
2228
                        param = "[tab]";
 
2229
                        paramlen = 5;
 
2230
                        break;
 
2231
 
 
2232
                    case ' ':
 
2233
                        param = "[space]";
 
2234
                        paramlen = 7;
 
2235
                        break;
 
2236
                    }
 
2237
                }
 
2238
                break;
 
2239
                
 
2240
            case OS_EVT_TIMEOUT:
 
2241
                tag = "<timeout>";
 
2242
                break;
 
2243
                
 
2244
            case OS_EVT_HREF:
 
2245
                tag = "<href>";
 
2246
                break;
 
2247
                
 
2248
            case OS_EVT_NOTIMEOUT:
 
2249
                tag = "<notimeout>";
 
2250
                param = 0;
 
2251
                break;
 
2252
                
 
2253
            case OS_EVT_EOF:
 
2254
                tag = "<eof>";
 
2255
                param = 0;
 
2256
                break;
 
2257
                
 
2258
            case OS_EVT_LINE:
 
2259
                tag = "<line>";
 
2260
                break;
 
2261
                
 
2262
            case OS_EVT_COMMAND:
 
2263
                tag = "<command>";
 
2264
                break;
 
2265
 
 
2266
            case VMCON_EVT_END_SCRIPT:
 
2267
                tag = "<endqs>";
 
2268
                break;
 
2269
 
 
2270
            case VMCON_EVT_DIALOG:
 
2271
                tag = "<dialog>";
 
2272
                break;
 
2273
 
 
2274
            case VMCON_EVT_FILE:
 
2275
                tag = "<file>";
 
2276
                break;
 
2277
            }
 
2278
            
 
2279
            /* if we found a tag, write it */
 
2280
            if (tag != 0)
 
2281
            {
 
2282
                /* write the tag, in the local character set */
 
2283
                G_cmap_to_ui->write_file(command_fp_, tag, strlen(tag));
 
2284
                
 
2285
                /* add the parameter, if present */
 
2286
                if (param != 0)
 
2287
                {
 
2288
                    if (param_is_utf8)
 
2289
                        G_cmap_to_ui->write_file(
 
2290
                            command_fp_, param, paramlen);
 
2291
                    else
 
2292
                        os_fprint(command_fp_, param, paramlen);
 
2293
                }
 
2294
 
 
2295
                /* add the newline */
 
2296
                G_cmap_to_ui->write_file(command_fp_, "\n", 1);
 
2297
 
 
2298
                /* flush the output */
 
2299
                osfflush(command_fp_);
 
2300
            }
 
2301
        }
 
2302
        else
 
2303
        {
 
2304
            /*
 
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.  
 
2308
             */
 
2309
            if (evt == OS_EVT_LINE && param != 0)
 
2310
            {
 
2311
                /* write the ">" prefix */
 
2312
                G_cmap_to_ui->write_file(command_fp_, ">", 1);
 
2313
 
 
2314
                /* add the command line */
 
2315
                if (param_is_utf8)
 
2316
                    G_cmap_to_ui->write_file(command_fp_, param, paramlen);
 
2317
                else
 
2318
                    os_fprint(command_fp_, param, paramlen);
 
2319
 
 
2320
                /* add the newline */
 
2321
                G_cmap_to_ui->write_file(command_fp_, "\n", 1);
 
2322
 
 
2323
                /* flush the output */
 
2324
                osfflush(command_fp_);
 
2325
            }
 
2326
        }
 
2327
    }
 
2328
 
 
2329
    /* return the event code */
 
2330
    return evt;
 
2331
}
 
2332
 
 
2333
/* ------------------------------------------------------------------------ */
 
2334
/*
 
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. 
 
2338
 */
 
2339
 
 
2340
/* original 'more' mode, before input began */
 
2341
static int S_old_more_mode;
 
2342
 
 
2343
/* flag: input is pending from an interrupted read_line_timeout invocation */
 
2344
static int S_read_in_progress;
 
2345
 
 
2346
/* local buffer for reading input lines */
 
2347
static char S_read_buf[256];
 
2348
 
 
2349
 
 
2350
/*
 
2351
 *   Read a line of input from the console, with an optional timeout value. 
 
2352
 */
 
2353
int CVmConsole::read_line_timeout(VMG_ char *buf, size_t buflen,
 
2354
                                  unsigned long timeout, int use_timeout)
 
2355
{
 
2356
    int echo_text;
 
2357
    char *outp;
 
2358
    size_t outlen;
 
2359
    int evt;
 
2360
    int resuming;
 
2361
 
 
2362
    /* 
 
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
 
2365
     *   the keyboard 
 
2366
     */
 
2367
    echo_text = FALSE;
 
2368
 
 
2369
    /* remember the initial MORE mode */
 
2370
    S_old_more_mode = is_more_mode();
 
2371
 
 
2372
    /*
 
2373
     *   If we're not resuming an interrupted read already in progress,
 
2374
     *   initialize some display settings. 
 
2375
     */
 
2376
    if (!S_read_in_progress)
 
2377
    {
 
2378
        /* 
 
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.  
 
2381
         */
 
2382
        S_old_more_mode = set_more_state(FALSE);
 
2383
 
 
2384
        /* 
 
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
 
2387
         *   input 
 
2388
         */
 
2389
        flush_all(vmg_ VM_NL_INPUT);
 
2390
 
 
2391
        /* if there's a script file, read from it */
 
2392
        if (script_sp_ != 0)
 
2393
        {
 
2394
        read_script:
 
2395
            /* note whether we're in quiet mode */
 
2396
            int was_quiet = script_sp_->quiet;
 
2397
            
 
2398
            /* try reading a line from the script file */
 
2399
            if (read_line_from_script(S_read_buf, sizeof(S_read_buf), &evt))
 
2400
            {
 
2401
                /* 
 
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
 
2404
                 *   the display 
 
2405
                 */
 
2406
                if (!script_sp_->quiet)
 
2407
                    echo_text = TRUE;
 
2408
            }
 
2409
            else
 
2410
            {
 
2411
                int is_quiet;
 
2412
                
 
2413
                /* 
 
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
 
2421
                 *   return.  
 
2422
                 */
 
2423
                S_old_more_mode = close_script_file();
 
2424
 
 
2425
                /* note the new 'quiet' mode */
 
2426
                is_quiet = (script_sp_ != 0 && script_sp_->quiet);
 
2427
 
 
2428
                /* 
 
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 
 
2433
                 */
 
2434
                if (script_sp_ != 0 && is_quiet == was_quiet)
 
2435
                    goto read_script;
 
2436
                
 
2437
                /* 
 
2438
                 *   temporarily turn off MORE mode, in case we read from the
 
2439
                 *   keyboard 
 
2440
                 */
 
2441
                set_more_state(FALSE);
 
2442
                
 
2443
                /* flush any output we generated while reading the script */
 
2444
                flush(vmg_ VM_NL_NONE);
 
2445
                
 
2446
                /* 
 
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
 
2450
                 *   the keyboard.
 
2451
                 *   
 
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.  
 
2455
                 */
 
2456
                if (was_quiet && !is_quiet)
 
2457
                {
 
2458
                    /* return to the old MORE mode */
 
2459
                    set_more_state(S_old_more_mode);
 
2460
 
 
2461
                    /* add a blank line to the log file, if necessary */
 
2462
                    if (log_enabled_)
 
2463
                        log_str_->print_to_os("\n");
 
2464
 
 
2465
                    /* note in the streams that we've read an input line */
 
2466
                    disp_str_->note_input_line();
 
2467
                    if (log_str_ != 0)
 
2468
                        log_str_->note_input_line();
 
2469
 
 
2470
                    /* 
 
2471
                     *   generate a synthetic "end of script" event to let
 
2472
                     *   the caller know we're switching back to regular
 
2473
                     *   keyboard reading 
 
2474
                     */
 
2475
                    return log_event(vmg_ VMCON_EVT_END_SCRIPT);
 
2476
                }
 
2477
 
 
2478
                /*
 
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.  
 
2486
                 */
 
2487
            }
 
2488
        }
 
2489
 
 
2490
        /* 
 
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 
 
2495
         */
 
2496
        if (script_sp_ == 0)
 
2497
            reset_line_count(FALSE);
 
2498
    }
 
2499
 
 
2500
    /* 
 
2501
     *   if reading was already in progress, we're resuming a previously
 
2502
     *   interrupted read operation 
 
2503
     */
 
2504
    resuming = S_read_in_progress;
 
2505
 
 
2506
    /* reading is now in progress */
 
2507
    S_read_in_progress = TRUE;
 
2508
 
 
2509
    /* 
 
2510
     *   if we don't have a script file, or we're resuming an interrupted
 
2511
     *   read operation, read from the keyboard 
 
2512
     */
 
2513
    if (script_sp_ == 0 || resuming)
 
2514
    {
 
2515
        /* read a line from the keyboard */
 
2516
        evt = os_gets_timeout((uchar *)S_read_buf, sizeof(S_read_buf),
 
2517
                              timeout, use_timeout);
 
2518
 
 
2519
        /*
 
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.  
 
2524
         */
 
2525
        if (evt == OS_EVT_NOTIMEOUT && !use_timeout)
 
2526
        {
 
2527
            /* perform an ordinary untimed input */
 
2528
            if (os_gets((uchar *)S_read_buf, sizeof(S_read_buf)) != 0)
 
2529
            {
 
2530
                /* success */
 
2531
                evt = OS_EVT_LINE;
 
2532
            }
 
2533
            else
 
2534
            {
 
2535
                /* error reading input */
 
2536
                evt = OS_EVT_EOF;
 
2537
            }
 
2538
        }
 
2539
 
 
2540
        /* 
 
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.
 
2545
         *   
 
2546
         *   Don't note the input if we timed out, since we haven't finished
 
2547
         *   reading the line yet in this case.  
 
2548
         */
 
2549
        if (evt == OS_EVT_LINE)
 
2550
        {
 
2551
            disp_str_->note_input_line();
 
2552
            if (log_str_ != 0)
 
2553
                log_str_->note_input_line();
 
2554
        }
 
2555
    }
 
2556
 
 
2557
    /* if we got an error, return it */
 
2558
    if (evt == OS_EVT_EOF)
 
2559
    {
 
2560
        set_more_state(S_old_more_mode);
 
2561
        return log_event(vmg_ evt);
 
2562
    }
 
2563
 
 
2564
    /* 
 
2565
     *   Convert the text from the local UI character set to UTF-8.  Reserve
 
2566
     *   space in the output buffer for the null terminator.  
 
2567
     */
 
2568
    outp = buf;
 
2569
    outlen = buflen - 1;
 
2570
    G_cmap_from_ui->map(&outp, &outlen, S_read_buf, strlen(S_read_buf));
 
2571
 
 
2572
    /* add the null terminator */
 
2573
    *outp = '\0';
 
2574
 
 
2575
    /* 
 
2576
     *   If we need to echo the text (because we read it from a script file),
 
2577
     *   do so now.  
 
2578
     */
 
2579
    if (echo_text)
 
2580
    {
 
2581
        /* show the text */
 
2582
        format_text(vmg_ buf);
 
2583
 
 
2584
        /* add a newline */
 
2585
        format_text(vmg_ "\n");
 
2586
    }
 
2587
 
 
2588
    /* if we finished reading the line, do our line-finishing work */
 
2589
    if (evt == OS_EVT_LINE)
 
2590
        read_line_done(vmg0_);
 
2591
 
 
2592
    /* 
 
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
 
2595
     *   input buffer. 
 
2596
     */
 
2597
    return log_event(vmg_ evt, S_read_buf, strlen(S_read_buf), FALSE);
 
2598
}
 
2599
 
 
2600
/*
 
2601
 *   Cancel an interrupted input. 
 
2602
 */
 
2603
void CVmConsole::read_line_cancel(VMG_ int reset)
 
2604
{
 
2605
    /* reset the underling OS layer */
 
2606
    os_gets_cancel(reset);
 
2607
 
 
2608
    /* do our line-ending work */
 
2609
    read_line_done(vmg0_);
 
2610
}
 
2611
 
 
2612
/*
 
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(). 
 
2616
 */
 
2617
void CVmConsole::read_line_done(VMG0_)
 
2618
{
 
2619
    /* if we have a line in progress, finish it off */
 
2620
    if (S_read_in_progress)
 
2621
    {
 
2622
        /* set the original 'more' mode */
 
2623
        set_more_state(S_old_more_mode);
 
2624
 
 
2625
        /* 
 
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.
 
2629
         *   
 
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.  
 
2636
         */
 
2637
        if (log_enabled_ && (script_sp_ == 0 || script_sp_->quiet))
 
2638
        {
 
2639
            log_str_->print_to_os(S_read_buf);
 
2640
            log_str_->print_to_os("\n");
 
2641
        }
 
2642
        
 
2643
        /* note in the streams that we've read an input line */
 
2644
        disp_str_->note_input_line();
 
2645
        if (log_str_ != 0)
 
2646
            log_str_->note_input_line();
 
2647
 
 
2648
        /* clear the in-progress flag */
 
2649
        S_read_in_progress = FALSE;
 
2650
    }
 
2651
}
 
2652
 
 
2653
/* ------------------------------------------------------------------------ */
 
2654
/*
 
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.
 
2659
 *   
 
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.
 
2663
 *   
 
2664
 *   If 'filter' is non-null, we'll only return events of the types in the
 
2665
 *   filter list.  
 
2666
 */
 
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)
 
2670
{
 
2671
    /* 
 
2672
     *   if we're not reading a script, or it's not an event script, skip
 
2673
     *   this 
 
2674
     */
 
2675
    if (script_sp_ == 0 || !script_sp_->event_script)
 
2676
        return FALSE;
 
2677
 
 
2678
    /* get the script file */
 
2679
    osfildef *fp = script_sp_->fp;
 
2680
 
 
2681
    /* keep going until we find something */
 
2682
    for (;;)
 
2683
    {
 
2684
        /* read the next event */
 
2685
        if (!read_script_event_type(evt, attrs))
 
2686
        {
 
2687
            /* end of the script - close it */
 
2688
            set_more_state(close_script_file());
 
2689
 
 
2690
            /* if there's no more script file, there's no event */
 
2691
            if (script_sp_ == 0)
 
2692
                return FALSE;
 
2693
 
 
2694
            /* go back for the next event */
 
2695
            fp = script_sp_->fp;
 
2696
            continue;
 
2697
        }
 
2698
 
 
2699
        /* if it's not in the filter list, skip it */
 
2700
        if (filter != 0)
 
2701
        {
 
2702
            int i, found;
 
2703
 
 
2704
            /* look for a match in our filter list */
 
2705
            for (i = 0, found = FALSE ; i < filter_cnt ; ++i)
 
2706
            {
 
2707
                if (filter[i] == *evt)
 
2708
                {
 
2709
                    found = TRUE;
 
2710
                    break;
 
2711
                }
 
2712
            }
 
2713
 
 
2714
            /* if we didn't find it, skip this line */
 
2715
            if (!found)
 
2716
            {
 
2717
                skip_script_line(fp);
 
2718
                continue;
 
2719
            }
 
2720
        }
 
2721
 
 
2722
        /* if there's a buffer, read the rest of the line */
 
2723
        if (buf != 0)
 
2724
        {
 
2725
            /* read the parameter into the buffer, and return the result */
 
2726
            if (!read_script_param(buf, buflen, fp))
 
2727
                return FALSE;
 
2728
 
 
2729
            /* if this is an OS_EVT_KEY event, translate special keys */
 
2730
            if (*evt == OS_EVT_KEY)
 
2731
            {
 
2732
                if (strcmp(buf, "[enter]") == 0)
 
2733
                    strcpy(buf, "\n");
 
2734
                else if (strcmp(buf, "[tab]") == 0)
 
2735
                    strcpy(buf, "\t");
 
2736
                else if (strcmp(buf, "[space]") == 0)
 
2737
                    strcpy(buf, " ");
 
2738
            }
 
2739
        }
 
2740
        else
 
2741
        {
 
2742
            /* no result buffer - just skip anything left on the line */
 
2743
            skip_script_line(fp);
 
2744
        }
 
2745
 
 
2746
        /* success */
 
2747
        return TRUE;
 
2748
    }
 
2749
}
 
2750
 
 
2751
/*
 
2752
 *   Read a <tag> or attribute token from a script file.  Returns the
 
2753
 *   character after the end of the token.  
 
2754
 */
 
2755
static int read_script_token(char *buf, size_t buflen, osfildef *fp)
 
2756
{
 
2757
    char *p;
 
2758
    int c;
 
2759
 
 
2760
    /* skip leading whitespace */
 
2761
    for (c = osfgetc(fp) ; isspace(c) ; c = osfgetc(fp)) ;
 
2762
 
 
2763
    /* read from the file until we reach the end of the token */
 
2764
    for (p = buf ;
 
2765
         p < buf + buflen - 1
 
2766
             && c != '>' && !isspace(c)
 
2767
             && c != '\n' && c != '\r' && c != EOF ; )
 
2768
    {
 
2769
        /* store this character */
 
2770
        *p++ = (char)c;
 
2771
 
 
2772
        /* get the next one */
 
2773
        c = osfgetc(fp);
 
2774
    }
 
2775
 
 
2776
    /* null-terminate the token */
 
2777
    *p = '\0';
 
2778
 
 
2779
    /* return the character that ended the token */
 
2780
    return c;
 
2781
}
 
2782
 
 
2783
/*
 
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.  
 
2787
 */
 
2788
int CVmConsole::read_script_event_type(int *evt, unsigned long *attrs)
 
2789
{
 
2790
    /* clear the caller's attribute flags, if provided */
 
2791
    if (attrs != 0)
 
2792
        *attrs = 0;
 
2793
 
 
2794
    /* if there's no script, there's no event */
 
2795
    if (script_sp_ == 0)
 
2796
        return FALSE;
 
2797
 
 
2798
    /* get the file */
 
2799
    osfildef *fp = script_sp_->fp;
 
2800
 
 
2801
    /* if it's a command-line script, there are only line input events */
 
2802
    if (!script_sp_->event_script)
 
2803
    {
 
2804
        /* keep going until we find an input line */
 
2805
        for (;;)
 
2806
        {
 
2807
            /* read the first charater of the line */
 
2808
            int c = osfgetc(fp);
 
2809
            if (c == '>')
 
2810
            {
 
2811
                /* we found a line input event */
 
2812
                *evt = OS_EVT_LINE;
 
2813
                return TRUE;
 
2814
            }
 
2815
            else if (c == EOF)
 
2816
            {
 
2817
                /* end of file - give up */
 
2818
                return FALSE;
 
2819
            }
 
2820
            else
 
2821
            {
 
2822
                /* 
 
2823
                 *   anything else is just a comment line - just skip it and
 
2824
                 *   keep looking 
 
2825
                 */
 
2826
                skip_script_line(fp);
 
2827
            }
 
2828
        }
 
2829
    }
 
2830
 
 
2831
    /* keep going until we find an event tag */
 
2832
    for (;;)
 
2833
    {
 
2834
        int c;
 
2835
        char tag[32];
 
2836
        static const struct
 
2837
        {
 
2838
            const char *tag;
 
2839
            int evt;
 
2840
        }
 
2841
        *tp, tags[] =
 
2842
        {
 
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 },
 
2849
            
 
2850
            { "endqs", VMCON_EVT_END_SCRIPT },
 
2851
            { "dialog", VMCON_EVT_DIALOG },
 
2852
            { "file", VMCON_EVT_FILE },
 
2853
 
 
2854
            { 0, 0 }
 
2855
        };
 
2856
 
 
2857
        /* check the start of the line to make sure it's an event */
 
2858
        c = osfgetc(fp);
 
2859
 
 
2860
        /* if at EOF, return failure */
 
2861
        if (c == EOF)
 
2862
            return FALSE;
 
2863
 
 
2864
        /* if it's not an event code line, skip the line */
 
2865
        if (c != '<')
 
2866
        {
 
2867
            skip_script_line(fp);
 
2868
            continue;
 
2869
        }
 
2870
 
 
2871
        /* read the event type (up to the '>') */
 
2872
        c = read_script_token(tag, sizeof(tag), fp);
 
2873
 
 
2874
        /* check for attributes */
 
2875
        while (isspace(c))
 
2876
        {
 
2877
            char attr[32];
 
2878
            static const struct
 
2879
            {
 
2880
                const char *name;
 
2881
                unsigned long flag;
 
2882
            }
 
2883
            *ap, attrlist[] =
 
2884
            {
 
2885
                { "overwrite", VMCON_EVTATTR_OVERWRITE },
 
2886
                { 0, 0 }
 
2887
            };
 
2888
 
 
2889
            /* read the attribute name */
 
2890
            c = read_script_token(attr, sizeof(attr), fp);
 
2891
 
 
2892
            /* if the name is empty, stop */
 
2893
            if (attr[0] == '\0')
 
2894
                break;
 
2895
 
 
2896
            /* look up the token */
 
2897
            for (ap = attrlist ; ap->name != 0 ; ++ap)
 
2898
            {
 
2899
                /* check for a match */
 
2900
                if (stricmp(attr, ap->name) == 0)
 
2901
                {
 
2902
                    /* if the caller wants the flag, set it */
 
2903
                    if (attrs != 0)
 
2904
                        *attrs |= ap->flag;
 
2905
 
 
2906
                    /* no need to look any further */
 
2907
                    break;
 
2908
                }
 
2909
            }
 
2910
 
 
2911
            /* if we're at the '>' or at end of line or file, stop */
 
2912
            if (c == '>' || c == '\n' || c == '\r' || c == EOF)
 
2913
                break;
 
2914
        }
 
2915
 
 
2916
        /* if it's not a well-formed tag, ignore it */
 
2917
        if (c != '>')
 
2918
        {
 
2919
            skip_script_line(fp);
 
2920
            continue;
 
2921
        }
 
2922
 
 
2923
        /* look up the tag */
 
2924
        for (tp = tags ; tp->tag != 0 ; ++tp)
 
2925
        {
 
2926
            /* check for a match to this tag name */
 
2927
            if (stricmp(tp->tag, tag) == 0)
 
2928
            {
 
2929
                /* got it - return the event type */
 
2930
                *evt = tp->evt;
 
2931
                return TRUE;
 
2932
            }
 
2933
        }
 
2934
 
 
2935
        /* we don't recognize the tag name; skip the line and keep looking */
 
2936
        skip_script_line(fp);
 
2937
    }
 
2938
}
 
2939
 
 
2940
/*
 
2941
 *   Skip to the next script line 
 
2942
 */
 
2943
void CVmConsole::skip_script_line(osfildef *fp)
 
2944
{
 
2945
    int c;
 
2946
 
 
2947
    /* read until we find the end of the current line */
 
2948
    for (c = osfgetc(fp) ; c != EOF && c != '\n' && c != '\r' ;
 
2949
         c = osfgetc(fp)) ;
 
2950
}
 
2951
 
 
2952
/*
 
2953
 *   read the rest of the current script line into the given buffer 
 
2954
 */
 
2955
int CVmConsole::read_script_param(char *buf, size_t buflen, osfildef *fp)
 
2956
{
 
2957
    /* ignore zero-size buffer requests */
 
2958
    if (buflen == 0)
 
2959
        return FALSE;
 
2960
 
 
2961
    /* read characters until we run out of buffer or reach a newline */
 
2962
    for (;;)
 
2963
    {
 
2964
        /* get the next character */
 
2965
        int c = osfgetc(fp);
 
2966
 
 
2967
        /* if it's a newline or end of file, we're done */
 
2968
        if (c == '\n' || c == EOF)
 
2969
        {
 
2970
            /* null-terminate the buffer */
 
2971
            *buf = '\0';
 
2972
 
 
2973
            /* indicate success */
 
2974
            return TRUE;
 
2975
        }
 
2976
 
 
2977
        /* 
 
2978
         *   if there's room in the buffer, add the character - always leave
 
2979
         *   one byte for the null terminator 
 
2980
         */
 
2981
        if (buflen > 1)
 
2982
        {
 
2983
            *buf++ = (char)c;
 
2984
            --buflen;
 
2985
        }
 
2986
    }
 
2987
}
 
2988
 
 
2989
 
 
2990
/*
 
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
 
2993
 *   any other error.  
 
2994
 */
 
2995
int CVmConsole::read_line_from_script(char *buf, size_t buflen, int *evt)
 
2996
{
 
2997
    /* if there's no script file, return failure */
 
2998
    if (script_sp_ == 0)
 
2999
        return FALSE;
 
3000
 
 
3001
    /* get the file from the script stack */
 
3002
    osfildef *fp = script_sp_->fp;
 
3003
 
 
3004
    /* keep going until we find a line that we like */
 
3005
    for (;;)
 
3006
    {
 
3007
        /* read the script according to its type ('event' or 'line input') */
 
3008
        if (script_sp_->event_script)
 
3009
        {
 
3010
            /* read to the next event */
 
3011
            if (!read_script_event_type(evt, 0))
 
3012
                return FALSE;
 
3013
 
 
3014
            /* check the event code */
 
3015
            switch (*evt)
 
3016
            {
 
3017
            case OS_EVT_LINE:
 
3018
            case OS_EVT_TIMEOUT:
 
3019
                /* 
 
3020
                 *   it's one of our line input events - read the line (or
 
3021
                 *   partial line, in the case of TIMEOUT) 
 
3022
                 */
 
3023
                return read_script_param(buf, buflen, fp);
 
3024
 
 
3025
            default:
 
3026
                /* 
 
3027
                 *   it's not our type of event - skip the rest of the line
 
3028
                 *   and keep looking 
 
3029
                 */
 
3030
                skip_script_line(fp);
 
3031
                break;
 
3032
            }
 
3033
        }
 
3034
        else
 
3035
        {
 
3036
            /* 
 
3037
             *   We have a basic line-input script rather than an event
 
3038
             *   script.  Each input line starts with a '>'; everything else
 
3039
             *   is a comment.
 
3040
             *   
 
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.  
 
3044
             */
 
3045
            int c = osfgetc(fp);
 
3046
            if (c == '>')
 
3047
            {
 
3048
                /* it's a command line - read it */
 
3049
                *evt = OS_EVT_LINE;
 
3050
                return read_script_param(buf, buflen, fp);
 
3051
            }
 
3052
            else if (c == EOF)
 
3053
            {
 
3054
                /* end of file */
 
3055
                return FALSE;
 
3056
            }
 
3057
            else if (c == '\n' || c == '\r')
 
3058
            {
 
3059
                /* blank line - continue on to the next line */
 
3060
            }
 
3061
            else
 
3062
            {
 
3063
                /* it's not a command line - just skip it and keep looking */
 
3064
                skip_script_line(fp);
 
3065
            }
 
3066
        }
 
3067
    }
 
3068
}
 
3069
 
 
3070
/* ------------------------------------------------------------------------ */
 
3071
/*
 
3072
 *   Main System Console 
 
3073
 */
 
3074
 
 
3075
/*
 
3076
 *   create 
 
3077
 */
 
3078
CVmConsoleMain::CVmConsoleMain(VMG0_)
 
3079
{
 
3080
    /* create the system banner manager */
 
3081
    banner_manager_ = new CVmBannerManager();
 
3082
 
 
3083
    /* create the log console manager */
 
3084
    log_console_manager_ = new CVmLogConsoleManager();
 
3085
 
 
3086
    /* create and initialize our display stream */
 
3087
    main_disp_str_ = new CVmFormatterMain(this, 256);
 
3088
    main_disp_str_->init();
 
3089
 
 
3090
    /* initially send text to the main display stream */
 
3091
    disp_str_ = main_disp_str_;
 
3092
 
 
3093
    /* 
 
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
 
3097
     *   activation.  
 
3098
     */
 
3099
    log_str_ = new CVmFormatterLog(this, 80);
 
3100
 
 
3101
    /* 
 
3102
     *   use the default log file character mapper - on some systems, files
 
3103
     *   don't use the same character set as the display 
 
3104
     */
 
3105
    log_str_->set_charmap(G_cmap_to_log);
 
3106
 
 
3107
    /* initialize the log stream */
 
3108
    log_str_->init();
 
3109
 
 
3110
    /* 
 
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) 
 
3114
     */
 
3115
    log_enabled_ = TRUE;
 
3116
 
 
3117
    /* we don't have a statusline formatter until asked for one */
 
3118
    statline_str_ = 0;
 
3119
 
 
3120
    /* reset statics */
 
3121
    S_read_in_progress = FALSE;
 
3122
    S_read_buf[0] = '\0';
 
3123
}
 
3124
 
 
3125
/*
 
3126
 *   delete 
 
3127
 */
 
3128
CVmConsoleMain::~CVmConsoleMain()
 
3129
{
 
3130
    /* delete the system banner manager */
 
3131
    banner_manager_->delete_obj();
 
3132
 
 
3133
    /* delete the system log console manager */
 
3134
    log_console_manager_->delete_obj();
 
3135
 
 
3136
    /* delete the display stream */
 
3137
    delete main_disp_str_;
 
3138
 
 
3139
    /* delete the statusline stream, if we have one */
 
3140
    if (statline_str_ != 0)
 
3141
        delete statline_str_;
 
3142
}
 
3143
 
 
3144
/*
 
3145
 *   Clear the window 
 
3146
 */
 
3147
void CVmConsoleMain::clear_window(VMG0_)
 
3148
{
 
3149
    /* flush and empty our output buffer */
 
3150
    flush(vmg_ VM_NL_NONE);
 
3151
    empty_buffers(vmg0_);
 
3152
    
 
3153
    /* clear the main window */
 
3154
    oscls();
 
3155
 
 
3156
    /* reset the MORE line counter in the display stream */
 
3157
    disp_str_->reset_line_count(TRUE);
 
3158
}
 
3159
 
 
3160
/*
 
3161
 *   Set statusline mode 
 
3162
 */
 
3163
void CVmConsoleMain::set_statusline_mode(VMG_ int mode)
 
3164
{
 
3165
    CVmFormatterDisp *str;
 
3166
 
 
3167
    /* 
 
3168
     *   if we're switching into statusline mode, and we don't have a
 
3169
     *   statusline stream yet, create one 
 
3170
     */
 
3171
    if (mode && statline_str_ == 0)
 
3172
    {
 
3173
        /* create and initialize the statusline stream */
 
3174
        statline_str_ = new CVmFormatterStatline(this);
 
3175
        statline_str_->init();
 
3176
    }
 
3177
 
 
3178
    /* get the stream selected by the new mode */
 
3179
    if (mode)
 
3180
        str = statline_str_;
 
3181
    else
 
3182
        str = main_disp_str_;
 
3183
 
 
3184
    /* if this is already the active stream, we have nothing more to do */
 
3185
    if (str == disp_str_)
 
3186
        return;
 
3187
 
 
3188
    /* make the new stream current */
 
3189
    disp_str_ = str;
 
3190
 
 
3191
    /* 
 
3192
     *   check which mode we're switching to, so we can do some extra work
 
3193
     *   specific to each mode 
 
3194
     */
 
3195
    if (mode)
 
3196
    {
 
3197
        /* 
 
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 
 
3201
         */
 
3202
        log_enabled_ = FALSE;
 
3203
    }
 
3204
    else
 
3205
    {
 
3206
        /*
 
3207
         *   we're switching back to the main stream, so flush the statusline
 
3208
         *   so we're sure the statusline text is displayed 
 
3209
         */
 
3210
 
 
3211
        /* end the line */
 
3212
        statline_str_->format_text(vmg_ "\n", 1);
 
3213
 
 
3214
        /* flush output */
 
3215
        statline_str_->flush(vmg_ VM_NL_NONE);
 
3216
 
 
3217
        /* re-enable the log stream, if we have one */
 
3218
        if (log_str_ != 0)
 
3219
            log_enabled_ = TRUE;
 
3220
    }
 
3221
 
 
3222
    /* switch at the OS layer */
 
3223
    os_status(mode);
 
3224
}
 
3225
 
 
3226
/*
 
3227
 *   Flush everything 
 
3228
 */
 
3229
void CVmConsoleMain::flush_all(VMG_ vm_nl_type nl)
 
3230
{
 
3231
    /* flush our primary console */
 
3232
    flush(vmg_ nl);
 
3233
 
 
3234
    /* 
 
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. 
 
3241
     */
 
3242
    banner_manager_->flush_all(vmg_ VM_NL_NONE);
 
3243
}
 
3244
 
 
3245
/* ------------------------------------------------------------------------ */
 
3246
/*
 
3247
 *   Handle manager 
 
3248
 */
 
3249
 
 
3250
/* initialize */
 
3251
CVmHandleManager::CVmHandleManager()
 
3252
{
 
3253
    size_t i;
 
3254
 
 
3255
    /* allocate an initial array of handle slots */
 
3256
    handles_max_ = 32;
 
3257
    handles_ = (void **)t3malloc(handles_max_ * sizeof(*handles_));
 
3258
 
 
3259
    /* all slots are initially empty */
 
3260
    for (i = 0 ; i < handles_max_ ; ++i)
 
3261
        handles_[i] = 0;
 
3262
}
 
3263
 
 
3264
/* delete the object - this is the public destructor interface */
 
3265
void CVmHandleManager::delete_obj()
 
3266
{
 
3267
    size_t i;
 
3268
 
 
3269
    /* 
 
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).  
 
3274
     */
 
3275
    for (i = 0 ; i < handles_max_ ; ++i)
 
3276
    {
 
3277
        /* if this banner is still valid, delete it */
 
3278
        if (handles_[i] != 0)
 
3279
            delete_handle_object(i + 1, handles_[i]);
 
3280
    }
 
3281
 
 
3282
    /* delete the object */
 
3283
    delete this;
 
3284
}
 
3285
 
 
3286
/* destructor */
 
3287
CVmHandleManager::~CVmHandleManager()
 
3288
{
 
3289
    /* delete the handle pointer array */
 
3290
    t3free(handles_);
 
3291
}
 
3292
 
 
3293
/* 
 
3294
 *   Allocate a new handle 
 
3295
 */
 
3296
int CVmHandleManager::alloc_handle(void *item)
 
3297
{
 
3298
    size_t slot;
 
3299
 
 
3300
    /* scan for a free slot */
 
3301
    for (slot = 0 ; slot < handles_max_ ; ++slot)
 
3302
    {
 
3303
        /* if this one is free, use it */
 
3304
        if (handles_[slot] == 0)
 
3305
            break;
 
3306
    }
 
3307
 
 
3308
    /* if we didn't find a free slot, extend the array */
 
3309
    if (slot == handles_max_)
 
3310
    {
 
3311
        size_t i;
 
3312
 
 
3313
        /* allocate a larger array */
 
3314
        handles_max_ += 32;
 
3315
        handles_ = (void **)
 
3316
                   t3realloc(handles_, handles_max_ * sizeof(*handles_));
 
3317
 
 
3318
        /* clear out the newly-allocated slots */
 
3319
        for (i = slot ; i < handles_max_ ; ++i)
 
3320
            handles_[i] = 0;
 
3321
    }
 
3322
 
 
3323
    /* store the new item in our pointer array */
 
3324
    handles_[slot] = item;
 
3325
 
 
3326
    /* 
 
3327
     *   convert the slot number to a handle by adjusting it to a 1-based
 
3328
     *   index, and return the result 
 
3329
     */
 
3330
    return slot + 1;
 
3331
}
 
3332
 
 
3333
 
 
3334
/* ------------------------------------------------------------------------ */
 
3335
/*
 
3336
 *   Banner manager 
 
3337
 */
 
3338
 
 
3339
/*
 
3340
 *   Create a banner 
 
3341
 */
 
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)
 
3347
{
 
3348
    void *handle;
 
3349
    void *parent_handle;
 
3350
    void *other_handle;
 
3351
    CVmConsoleBanner *item;
 
3352
 
 
3353
    /* get the parent handle, if provided */
 
3354
    parent_handle = get_os_handle(parent_id);
 
3355
 
 
3356
    /* get the 'other' handle, if we need it for the 'where' */
 
3357
    switch(where)
 
3358
    {
 
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);
 
3363
        break;
 
3364
 
 
3365
    default:
 
3366
        /* we don't need 'other' for other 'where' modes */
 
3367
        other_handle = 0;
 
3368
        break;
 
3369
    }
 
3370
 
 
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);
 
3374
 
 
3375
    /* if we couldn't create the OS-level window, return failure */
 
3376
    if (handle == 0)
 
3377
        return 0;
 
3378
 
 
3379
    /* create the new console */
 
3380
    item = new CVmConsoleBanner(handle, wintype, style);
 
3381
 
 
3382
    /* allocate a handle for the new banner, and return the handle */
 
3383
    return alloc_handle(item);
 
3384
}
 
3385
 
 
3386
/*
 
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
 
3394
 *   terminated.  
 
3395
 */
 
3396
void CVmBannerManager::delete_or_orphan_banner(int banner, int orphan)
 
3397
{
 
3398
    CVmConsoleBanner *item;
 
3399
    void *handle;
 
3400
 
 
3401
    /* if the banner is invalid, ignore the request */
 
3402
    if ((item = (CVmConsoleBanner *)get_object(banner)) == 0)
 
3403
        return;
 
3404
 
 
3405
    /* get the OS-level banner handle */
 
3406
    handle = item->get_os_handle();
 
3407
 
 
3408
    /* delete the banner item */
 
3409
    delete item;
 
3410
 
 
3411
    /* clear the slot */
 
3412
    clear_handle(banner);
 
3413
 
 
3414
    /* delete the OS-level banner */
 
3415
    if (orphan)
 
3416
        os_banner_orphan(handle);
 
3417
    else
 
3418
        os_banner_delete(handle);
 
3419
}
 
3420
 
 
3421
/*
 
3422
 *   Get the OS-level handle for the given banner 
 
3423
 */
 
3424
void *CVmBannerManager::get_os_handle(int banner)
 
3425
{
 
3426
    CVmConsoleBanner *item;
 
3427
 
 
3428
    /* if the banner is invalid, return failure */
 
3429
    if ((item = (CVmConsoleBanner *)get_object(banner)) == 0)
 
3430
        return 0;
 
3431
 
 
3432
    /* return the handle from the slot */
 
3433
    return item->get_os_handle();
 
3434
}
 
3435
 
 
3436
/*
 
3437
 *   Flush all banners 
 
3438
 */
 
3439
void CVmBannerManager::flush_all(VMG_ vm_nl_type nl)
 
3440
{
 
3441
    size_t slot;
 
3442
 
 
3443
    /* flush each banner */
 
3444
    for (slot = 0 ; slot < handles_max_ ; ++slot)
 
3445
    {
 
3446
        /* if this slot has a valid banner, flush it */
 
3447
        if (handles_[slot] != 0)
 
3448
            ((CVmConsoleBanner *)handles_[slot])->flush(vmg_ nl);
 
3449
    }
 
3450
}
 
3451
 
 
3452
/* ------------------------------------------------------------------------ */
 
3453
/*
 
3454
 *   Banner Window Console 
 
3455
 */
 
3456
CVmConsoleBanner::CVmConsoleBanner(void *banner_handle, int win_type,
 
3457
                                   unsigned long style)
 
3458
{
 
3459
    CVmFormatterBanner *str;
 
3460
    os_banner_info_t info;
 
3461
    int obey_whitespace = FALSE;
 
3462
    int literal_mode = FALSE;
 
3463
 
 
3464
    /* remember our OS-level banner handle */
 
3465
    banner_ = banner_handle;
 
3466
 
 
3467
    /* get osifc-level information on the banner */
 
3468
    if (!os_banner_getinfo(banner_, &info))
 
3469
        info.os_line_wrap = FALSE;
 
3470
 
 
3471
    /* 
 
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.  
 
3478
     */
 
3479
    if (win_type == OS_BANNER_TYPE_TEXTGRID)
 
3480
    {
 
3481
        /* do not wrap lines in the formatter */
 
3482
        info.os_line_wrap = TRUE;
 
3483
 
 
3484
        /* use literal mode, and obey whitespace literally */
 
3485
        literal_mode = TRUE;
 
3486
        obey_whitespace = TRUE;
 
3487
    }
 
3488
 
 
3489
    /* create and initialize our display stream */
 
3490
    disp_str_ = str = new CVmFormatterBanner(banner_handle, this,
 
3491
                                             win_type, style);
 
3492
    str->init_banner(info.os_line_wrap, obey_whitespace, literal_mode);
 
3493
 
 
3494
    /* remember our window type */
 
3495
    win_type_ = win_type;
 
3496
}
 
3497
 
 
3498
/*
 
3499
 *   Deletion 
 
3500
 */
 
3501
CVmConsoleBanner::~CVmConsoleBanner()
 
3502
{
 
3503
    /* delete our display stream */
 
3504
    delete disp_str_;
 
3505
}
 
3506
 
 
3507
/*
 
3508
 *   Clear the banner window 
 
3509
 */
 
3510
void CVmConsoleBanner::clear_window(VMG0_)
 
3511
{
 
3512
    /* flush and empty our output buffer */
 
3513
    flush(vmg_ VM_NL_NONE);
 
3514
    empty_buffers(vmg0_);
 
3515
    
 
3516
    /* clear our underlying system banner */
 
3517
    os_banner_clear(banner_);
 
3518
    
 
3519
    /* tell our display stream to zero its line counter */
 
3520
    disp_str_->reset_line_count(TRUE);
 
3521
}
 
3522
 
 
3523
/*
 
3524
 *   Get banner information 
 
3525
 */
 
3526
int CVmConsoleBanner::get_banner_info(os_banner_info_t *info)
 
3527
{
 
3528
    int ret;
 
3529
    
 
3530
    /* get the OS-level information */
 
3531
    ret = os_banner_getinfo(banner_, info);
 
3532
 
 
3533
    /* make some adjustments if we got valid information back */
 
3534
    if (ret)
 
3535
    {
 
3536
        /* 
 
3537
         *   check the window type for further adjustments we might need to
 
3538
         *   make to the data returned from the OS layer 
 
3539
         */
 
3540
        switch(win_type_)
 
3541
        {
 
3542
        case OS_BANNER_TYPE_TEXTGRID:
 
3543
            /* 
 
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
 
3547
             *   window 
 
3548
             */
 
3549
            info->style &= ~OS_BANNER_STYLE_TAB_ALIGN;
 
3550
            break;
 
3551
 
 
3552
        default:
 
3553
            /* other types don't require any adjustments */
 
3554
            break;
 
3555
        }
 
3556
    }
 
3557
 
 
3558
    /* return the success indication */
 
3559
    return ret;
 
3560
}
 
3561
 
 
3562
/* ------------------------------------------------------------------------ */
 
3563
/*
 
3564
 *   Log file console manager 
 
3565
 */
 
3566
 
 
3567
/*
 
3568
 *   create a log console 
 
3569
 */
 
3570
int CVmLogConsoleManager::create_log_console(const char *fname,
 
3571
                                             osfildef *fp,
 
3572
                                             class CCharmapToLocal *cmap,
 
3573
                                             int width)
 
3574
{
 
3575
    CVmConsoleLog *con;
 
3576
    
 
3577
    /* create the new console */
 
3578
    con = new CVmConsoleLog(fname, fp, cmap, width);
 
3579
 
 
3580
    /* allocate a handle for the new console and return the handle */
 
3581
    return alloc_handle(con);
 
3582
}
 
3583
 
 
3584
/*
 
3585
 *   delete log a console 
 
3586
 */
 
3587
void CVmLogConsoleManager::delete_log_console(int handle)
 
3588
{
 
3589
    CVmConsoleLog *con;
 
3590
    
 
3591
    /* if the handle is invalid, ignore the request */
 
3592
    if ((con = (CVmConsoleLog *)get_object(handle)) == 0)
 
3593
        return;
 
3594
 
 
3595
    /* delete the console */
 
3596
    delete con;
 
3597
 
 
3598
    /* clear the slot */
 
3599
    clear_handle(handle);
 
3600
}
 
3601
 
 
3602
/* ------------------------------------------------------------------------ */
 
3603
/*
 
3604
 *   Log file console 
 
3605
 */
 
3606
CVmConsoleLog::CVmConsoleLog(const char *fname, osfildef *fp,
 
3607
                             class CCharmapToLocal *cmap, int width)
 
3608
{
 
3609
    CVmFormatterLog *str;
 
3610
 
 
3611
    /* create our display stream */
 
3612
    disp_str_ = str = new CVmFormatterLog(this, width);
 
3613
 
 
3614
    /* set the file */
 
3615
    str->set_log_file(fname, fp);
 
3616
 
 
3617
    /* set the character mapper */
 
3618
    str->set_charmap(cmap);
 
3619
}
 
3620
 
 
3621
/*
 
3622
 *   destroy 
 
3623
 */
 
3624
CVmConsoleLog::~CVmConsoleLog()
 
3625
{
 
3626
    /* delete our display stream */
 
3627
    delete disp_str_;
 
3628
}
 
3629