| | 1 | /* $Header$ */ |
| | 2 | |
| | 3 | /* |
| | 4 | * Copyright (c) 1999, 2002 Michael J. Roberts. All Rights Reserved. |
| | 5 | * |
| | 6 | * Please see the accompanying license file, LICENSE.TXT, for information |
| | 7 | * on using and copying this software. |
| | 8 | */ |
| | 9 | /* |
| | 10 | Name |
| | 11 | vmconsol.h - TADS 3 console input reader and output formatter |
| | 12 | Function |
| | 13 | Provides console input and output for the TADS 3 built-in function set |
| | 14 | for the T3 VM. |
| | 15 | |
| | 16 | T3 uses the UTF-8 character set to represent character strings. The OS |
| | 17 | functions use the local character set. We perform the mapping between |
| | 18 | UTF-8 and the local character set within this module, so that OS routines |
| | 19 | see local characters only, not UTF-8. |
| | 20 | |
| | 21 | This code is based on the TADS 2 output formatter, but has been |
| | 22 | substantially reworked for C++, Unicode, and the slightly different |
| | 23 | TADS 3 formatting model. |
| | 24 | Notes |
| | 25 | |
| | 26 | Modified |
| | 27 | 09/04/99 MJRoberts - Creation |
| | 28 | */ |
| | 29 | |
| | 30 | #ifndef VMCONSOL_H |
| | 31 | #define VMCONSOL_H |
| | 32 | |
| | 33 | #include <string.h> |
| | 34 | #include <stdlib.h> |
| | 35 | #include "wchar.h" |
| | 36 | |
| | 37 | #include "os.h" |
| | 38 | #include "t3std.h" |
| | 39 | #include "vmglob.h" |
| | 40 | |
| | 41 | |
| | 42 | /* ------------------------------------------------------------------------ */ |
| | 43 | /* |
| | 44 | * Synthetic console events. These are events we generate alongside |
| | 45 | * OS_EVT_xxx codes; we start these at 10000 to ensure that we don't |
| | 46 | * overlap any current or future OS_EVT_xxx event codes. |
| | 47 | */ |
| | 48 | #define VMCON_EVT_END_SCRIPT 10000 |
| | 49 | #define VMCON_EVT_DIALOG 10001 |
| | 50 | #define VMCON_EVT_FILE 10002 |
| | 51 | |
| | 52 | /* |
| | 53 | * Event attributes. Some scriptable events have extra attributes |
| | 54 | * associated with them in addition to the main payload. These attributes |
| | 55 | * take the syntactic form of HTML tag attributes, so in principal we could |
| | 56 | * have arbitrarily many attributes, each of which have arbitrary string |
| | 57 | * values. However, at the moment, we only have a small number of |
| | 58 | * attributes, and each attribute's value is merely a boolean, so it's |
| | 59 | * adequate to represent these in the call interface with bit flags. |
| | 60 | */ |
| | 61 | |
| | 62 | /* OVERWRITE, as in <file overwrite> */ |
| | 63 | #define VMCON_EVTATTR_OVERWRITE 0x00000001 |
| | 64 | |
| | 65 | |
| | 66 | |
| | 67 | /* ------------------------------------------------------------------------ */ |
| | 68 | /* |
| | 69 | * Newline type codes. These specify how we are to perform line |
| | 70 | * breaking after writing out text. |
| | 71 | */ |
| | 72 | enum vm_nl_type |
| | 73 | { |
| | 74 | /* |
| | 75 | * no line separation at all - write out this text and subsequent |
| | 76 | * text as part of the same line with no separators |
| | 77 | */ |
| | 78 | VM_NL_NONE, |
| | 79 | |
| | 80 | /* |
| | 81 | * flushing in preparation for input - don't show any line separation, |
| | 82 | * and make sure that we display everything in the buffer, including |
| | 83 | * trailing spaces |
| | 84 | */ |
| | 85 | VM_NL_INPUT, |
| | 86 | |
| | 87 | /* break the line at the end of this text and start a newline */ |
| | 88 | VM_NL_NEWLINE, |
| | 89 | |
| | 90 | /* OS line separation - add a space after the text */ |
| | 91 | VM_NL_OSNEWLINE, |
| | 92 | |
| | 93 | /* |
| | 94 | * flushing internal buffers only: no line separation, and do not |
| | 95 | * flush to underlying OS level yet |
| | 96 | */ |
| | 97 | VM_NL_NONE_INTERNAL |
| | 98 | }; |
| | 99 | |
| | 100 | |
| | 101 | /* ------------------------------------------------------------------------ */ |
| | 102 | /* |
| | 103 | * Handle manager. This is a simple class for mapping system objects to |
| | 104 | * integers, which we give as "handles" to byte code callers. We give only |
| | 105 | * the integer handles to byte code to ensure that handles given back to us |
| | 106 | * by the byte code are valid; if we handed back raw pointers to the byte |
| | 107 | * code, it could call us with random garbage, and we'd have no way to |
| | 108 | * protect against it. |
| | 109 | */ |
| | 110 | class CVmHandleManager |
| | 111 | { |
| | 112 | public: |
| | 113 | CVmHandleManager(); |
| | 114 | |
| | 115 | /* |
| | 116 | * delete the object (use this rather than calling the destructor |
| | 117 | * directly, since we need to call some virtuals in the course of |
| | 118 | * preparing for deletion |
| | 119 | */ |
| | 120 | void delete_obj(); |
| | 121 | |
| | 122 | protected: |
| | 123 | /* delete */ |
| | 124 | virtual ~CVmHandleManager(); |
| | 125 | |
| | 126 | /* allocate a slot for a new item */ |
| | 127 | int alloc_handle(void *item); |
| | 128 | |
| | 129 | /* clear the given handle's slot */ |
| | 130 | void clear_handle(int handle) |
| | 131 | { |
| | 132 | /* if the handle is valid, clear its associated slot */ |
| | 133 | if (is_valid_handle(handle)) |
| | 134 | handles_[handle - 1] = 0; |
| | 135 | } |
| | 136 | |
| | 137 | /* is the given handle valid? */ |
| | 138 | int is_valid_handle(int handle) |
| | 139 | { |
| | 140 | /* |
| | 141 | * it's valid if it's within range for the allocated slots, and |
| | 142 | * there's a non-null object in the handle's slot |
| | 143 | */ |
| | 144 | return (handle >= 1 |
| | 145 | && (size_t)handle <= handles_max_ |
| | 146 | && handles_[handle - 1] != 0); |
| | 147 | } |
| | 148 | |
| | 149 | /* get the object for the given handle */ |
| | 150 | void *get_object(int handle) |
| | 151 | { |
| | 152 | /* |
| | 153 | * If the handle is valid, get the item from the slot; the handle |
| | 154 | * is indexed from 1, so decrement it to get the array index of the |
| | 155 | * object for the handle. If the handle is invalid, return null. |
| | 156 | */ |
| | 157 | return is_valid_handle(handle) ? handles_[handle - 1] : 0; |
| | 158 | } |
| | 159 | |
| | 160 | /* |
| | 161 | * Delete the item in a slot, in preparation for destroying the handle |
| | 162 | * manager itself - each subclass must override this to do the |
| | 163 | * appropriate work on termination. |
| | 164 | */ |
| | 165 | virtual void delete_handle_object(int handle, void *obj) = 0; |
| | 166 | |
| | 167 | /* array of window banners */ |
| | 168 | void **handles_; |
| | 169 | size_t handles_max_; |
| | 170 | }; |
| | 171 | |
| | 172 | |
| | 173 | /* ------------------------------------------------------------------------ */ |
| | 174 | /* |
| | 175 | * Banner manager. This keeps track of banner windows outside of the main |
| | 176 | * console window. |
| | 177 | */ |
| | 178 | class CVmBannerManager: public CVmHandleManager |
| | 179 | { |
| | 180 | public: |
| | 181 | CVmBannerManager() { } |
| | 182 | |
| | 183 | /* |
| | 184 | * Create a banner window. This creates an OS-level banner window, and |
| | 185 | * creates a console object to format its output. Returns a banner |
| | 186 | * handle that can be used to refer to the window. Banner handle zero |
| | 187 | * is invalid and indicates failure. |
| | 188 | * |
| | 189 | * 'parent_id' is the banner ID of the parent of the new banner. The |
| | 190 | * new banner is created as a child of the given parent. If parent_id |
| | 191 | * is zero, then the new banner is created as a child of the main |
| | 192 | * window. The parent determines how the new window is laid out: the |
| | 193 | * new window's display area is carved out of the display area of the |
| | 194 | * parent. |
| | 195 | * |
| | 196 | * 'where' is OS_BANNER_FIRST, OS_BANNER_LAST, OS_BANNER_BEFORE, or |
| | 197 | * OS_BANNER_AFTER. 'other_id' is the banner ID of an existing child |
| | 198 | * of the given parent, for the relative insertion point; this is |
| | 199 | * ignored for OS_BANNER_FIRST and OS_BANNER_LAST. |
| | 200 | * |
| | 201 | * 'wintype' is an OS_BANNER_TYPE_xxx code giving the type of window to |
| | 202 | * be created. |
| | 203 | * |
| | 204 | * 'siz' is the size, in units specified by 'siz_units', an |
| | 205 | * OS_BANNER_SIZE_xxx value. |
| | 206 | * |
| | 207 | * 'style' is a combination of OS_BANNER_STYLE_xxx flags. |
| | 208 | */ |
| | 209 | int create_banner(VMG_ int parent_id, |
| | 210 | int where, int other_id, int wintype, |
| | 211 | int align, int siz, int siz_units, |
| | 212 | unsigned long style); |
| | 213 | |
| | 214 | /* delete a banner */ |
| | 215 | void delete_banner(int banner) |
| | 216 | { delete_or_orphan_banner(banner, FALSE); } |
| | 217 | |
| | 218 | /* |
| | 219 | * Get the OS-level handle for the banner - this handle can be used to |
| | 220 | * call the os_banner_xxx functions directly. |
| | 221 | */ |
| | 222 | void *get_os_handle(int banner); |
| | 223 | |
| | 224 | /* get the banner's console object */ |
| | 225 | class CVmConsoleBanner *get_console(int banner) |
| | 226 | { |
| | 227 | /* the object behind the handle is the console */ |
| | 228 | return (CVmConsoleBanner *)get_object(banner); |
| | 229 | } |
| | 230 | |
| | 231 | /* flush all banners */ |
| | 232 | void flush_all(VMG_ vm_nl_type nl); |
| | 233 | |
| | 234 | protected: |
| | 235 | /* delete the object in a slot, in preparation for deleting the manager */ |
| | 236 | virtual void delete_handle_object(int handle, void *) |
| | 237 | { |
| | 238 | /* |
| | 239 | * delete the banner object, but orphan the system-level banner - |
| | 240 | * this will allow the system-level banner to remain visible even |
| | 241 | * after VM termination, in case the host application continues |
| | 242 | * running even after the VM exits |
| | 243 | */ |
| | 244 | delete_or_orphan_banner(handle, TRUE); |
| | 245 | } |
| | 246 | |
| | 247 | /* delete or orphan a banner window */ |
| | 248 | void delete_or_orphan_banner(int banner, int orphan); |
| | 249 | }; |
| | 250 | |
| | 251 | /* ------------------------------------------------------------------------ */ |
| | 252 | /* |
| | 253 | * Log console manager. This keeps track of log consoles, which are |
| | 254 | * consoles created specifically for capturing text to a log file. |
| | 255 | */ |
| | 256 | class CVmLogConsoleManager: public CVmHandleManager |
| | 257 | { |
| | 258 | public: |
| | 259 | CVmLogConsoleManager() { } |
| | 260 | |
| | 261 | /* create a log console - returns the console handle */ |
| | 262 | int create_log_console(const char *fname, osfildef *fp, |
| | 263 | class CCharmapToLocal *cmap, int width); |
| | 264 | |
| | 265 | /* delete a log console */ |
| | 266 | void delete_log_console(int handle); |
| | 267 | |
| | 268 | /* get the log's console object */ |
| | 269 | class CVmConsoleLog *get_console(int banner) |
| | 270 | { |
| | 271 | /* the object behind the handle is the console */ |
| | 272 | return (CVmConsoleLog *)get_object(banner); |
| | 273 | } |
| | 274 | |
| | 275 | protected: |
| | 276 | /* delete the object associated with a handle */ |
| | 277 | virtual void delete_handle_object(int handle, void *) |
| | 278 | { |
| | 279 | /* delete the console */ |
| | 280 | delete_log_console(handle); |
| | 281 | } |
| | 282 | }; |
| | 283 | |
| | 284 | /* |
| | 285 | * Log console manager item. We use these to track individual log |
| | 286 | * consoles. |
| | 287 | */ |
| | 288 | class CVmLogConsoleItem |
| | 289 | { |
| | 290 | public: |
| | 291 | CVmLogConsoleItem(const char *fname, class CCharmapToLocal *cmap); |
| | 292 | ~CVmLogConsoleItem(); |
| | 293 | |
| | 294 | /* get the console */ |
| | 295 | class CVmConsoleLog *get_console() const { return console_; } |
| | 296 | |
| | 297 | protected: |
| | 298 | /* my console object */ |
| | 299 | class CVmConsoleLog *console_; |
| | 300 | }; |
| | 301 | |
| | 302 | |
| | 303 | /* ------------------------------------------------------------------------ */ |
| | 304 | /* |
| | 305 | * Script stack entry |
| | 306 | */ |
| | 307 | struct script_stack_entry |
| | 308 | { |
| | 309 | script_stack_entry(script_stack_entry *encp, int old_more, |
| | 310 | osfildef *outfp, |
| | 311 | int new_more, int is_quiet, int is_event_script) |
| | 312 | { |
| | 313 | this->enc = encp; |
| | 314 | this->old_more_mode = old_more; |
| | 315 | this->fp = outfp; |
| | 316 | this->more_mode = new_more; |
| | 317 | this->quiet = is_quiet; |
| | 318 | this->event_script = is_event_script; |
| | 319 | } |
| | 320 | |
| | 321 | /* the enclosing stack level */ |
| | 322 | script_stack_entry *enc; |
| | 323 | |
| | 324 | /* the script file at this level */ |
| | 325 | osfildef *fp; |
| | 326 | |
| | 327 | /* the MORE mode that was in effect before this script file */ |
| | 328 | int old_more_mode; |
| | 329 | |
| | 330 | /* the MORE mode in effect during this script */ |
| | 331 | int more_mode; |
| | 332 | |
| | 333 | /* are we reading quietly from this script? */ |
| | 334 | int quiet; |
| | 335 | |
| | 336 | /* is this an event script? */ |
| | 337 | int event_script; |
| | 338 | }; |
| | 339 | |
| | 340 | |
| | 341 | /* ------------------------------------------------------------------------ */ |
| | 342 | /* |
| | 343 | * Console. A console corresponds to device that shows information to |
| | 344 | * and reads text from the user. On a text system, the console is |
| | 345 | * simply the terminal. On a graphical system, the console is usually |
| | 346 | * an application window. |
| | 347 | */ |
| | 348 | class CVmConsole |
| | 349 | { |
| | 350 | public: |
| | 351 | CVmConsole(); |
| | 352 | virtual ~CVmConsole(); |
| | 353 | |
| | 354 | /* write out a null-terminated UTF-8 string */ |
| | 355 | int format_text(VMG_ const char *p) |
| | 356 | { |
| | 357 | /* get its length and write it out */ |
| | 358 | return format_text(vmg_ p, strlen(p)); |
| | 359 | } |
| | 360 | |
| | 361 | /* write out a UTF-8 string of a given byte length */ |
| | 362 | virtual int format_text(VMG_ const char *p, size_t len); |
| | 363 | |
| | 364 | /* format text explicitly to the log file, if any */ |
| | 365 | int format_text_to_log(VMG_ const char *p, size_t len); |
| | 366 | |
| | 367 | /* display a blank line */ |
| | 368 | void write_blank_line(VMG0_); |
| | 369 | |
| | 370 | /* set the whitespace mode (returns the old whitespace mode) */ |
| | 371 | int set_obey_whitespace(int f); |
| | 372 | |
| | 373 | /* set the text color */ |
| | 374 | void set_text_color(VMG_ os_color_t fg, os_color_t bg); |
| | 375 | |
| | 376 | /* set the body color */ |
| | 377 | void set_body_color(VMG_ os_color_t color); |
| | 378 | |
| | 379 | /* set the caps flag - capitalize the next character */ |
| | 380 | void caps(); |
| | 381 | |
| | 382 | /* set the nocaps flag - make the next letter miniscule */ |
| | 383 | void nocaps(); |
| | 384 | |
| | 385 | /* flush the output with the given newline type */ |
| | 386 | void flush(VMG_ vm_nl_type nl); |
| | 387 | |
| | 388 | /* flush the output, ending the current line and starting a new line */ |
| | 389 | void flush(VMG0_) { flush(vmg_ VM_NL_NEWLINE); } |
| | 390 | |
| | 391 | /* empty our buffers */ |
| | 392 | void empty_buffers(VMG0_); |
| | 393 | |
| | 394 | /* clear the window */ |
| | 395 | virtual void clear_window(VMG0_) = 0; |
| | 396 | |
| | 397 | /* |
| | 398 | * Flush all windows we control. By default, we just flush our own |
| | 399 | * window; consoles that manage multiple windows should flush their |
| | 400 | * managed windows here as well. |
| | 401 | */ |
| | 402 | virtual void flush_all(VMG_ vm_nl_type nl) { flush(vmg_ nl); } |
| | 403 | |
| | 404 | /* immediately update the display window */ |
| | 405 | void update_display(VMG0_); |
| | 406 | |
| | 407 | /* |
| | 408 | * Open a new log file. Closes any previous log file. If the file |
| | 409 | * already exists, we'll overwrite it with the new log information, |
| | 410 | * otherwise we'll create a new file. Returns zero on success, |
| | 411 | * non-zero on failure. |
| | 412 | */ |
| | 413 | int open_log_file(const char *fname); |
| | 414 | |
| | 415 | /* |
| | 416 | * close any existing log file - returns zero on success, non-zero |
| | 417 | * on failure |
| | 418 | */ |
| | 419 | int close_log_file(); |
| | 420 | |
| | 421 | /* |
| | 422 | * Open a new command log file. We'll log commands (and only |
| | 423 | * commands) to the command log file. Returns zero on success, |
| | 424 | * non-zero on failure. |
| | 425 | */ |
| | 426 | int open_command_log(const char *fname, int event_script); |
| | 427 | |
| | 428 | /* close the command log file, if there is one */ |
| | 429 | int close_command_log(); |
| | 430 | |
| | 431 | /* |
| | 432 | * Set the current MORE mode. Returns the old state. The state is |
| | 433 | * true if we show MORE prompts, false if not. The state will be |
| | 434 | * false if the underlying OS display layer handles prompting, so a |
| | 435 | * return of false doesn't necessarily mean that MORE prompts are |
| | 436 | * never shown, but merely that we don't handle MORE prompts in the |
| | 437 | * output formatter itself. |
| | 438 | */ |
| | 439 | virtual int set_more_state(int state) = 0; |
| | 440 | |
| | 441 | /* determine if we're in MORE mode */ |
| | 442 | virtual int is_more_mode() const = 0; |
| | 443 | |
| | 444 | /* get the line width of the display device */ |
| | 445 | virtual int get_line_width() const = 0; |
| | 446 | |
| | 447 | /* |
| | 448 | * Do we allow overrunning the line width when we can't find a natural |
| | 449 | * breaking point (at a whitespace character, for example) such that |
| | 450 | * we can fit some text within the line width? |
| | 451 | * |
| | 452 | * If this returns false, then we'll force a newline when we reach the |
| | 453 | * line width, even if doing so breaks up a single word that doesn't |
| | 454 | * have a natural breaking point within. |
| | 455 | */ |
| | 456 | virtual int allow_overrun() const = 0; |
| | 457 | |
| | 458 | /* get the page length of the display device */ |
| | 459 | virtual int get_page_length() const = 0; |
| | 460 | |
| | 461 | /* get/set the double-space flag (for periods and other punctuation) */ |
| | 462 | int get_doublespace() const { return doublespace_; } |
| | 463 | void set_doublespace(int f) { doublespace_ = f; } |
| | 464 | |
| | 465 | /* reset the MORE prompt line count */ |
| | 466 | void reset_line_count(int clearing); |
| | 467 | |
| | 468 | /* |
| | 469 | * check to see if we're reading from a script input file - returns |
| | 470 | * true if so, false if reading from the user (via the keyboard or |
| | 471 | * other input device) |
| | 472 | */ |
| | 473 | int is_reading_script() const { return (script_sp_ != 0); } |
| | 474 | |
| | 475 | /* |
| | 476 | * check to see if we're reading quietly from a script - if we're |
| | 477 | * reading from a script, and this flag is true, we suppress all |
| | 478 | * output |
| | 479 | */ |
| | 480 | int is_quiet_script() const |
| | 481 | { return (script_sp_ != 0 && script_sp_->quiet); } |
| | 482 | |
| | 483 | /* is the script in MORE mode? */ |
| | 484 | int is_moremode_script() const |
| | 485 | { return (script_sp_ != 0 && script_sp_->more_mode); } |
| | 486 | |
| | 487 | /* is the script an <eventscript> type? */ |
| | 488 | int is_event_script() const |
| | 489 | { return (script_sp_ != 0 && script_sp_->event_script); } |
| | 490 | |
| | 491 | /* |
| | 492 | * Open a script file. If 'quiet' is true, no output is displayed |
| | 493 | * while the script is being processed. If 'script_more_mode' is |
| | 494 | * true, MORE mode is in effect while processing the script, |
| | 495 | * otherwise MORE mode is turned off while processing the script (to |
| | 496 | * leave things as they are, simply pass in is_more_mode() for this |
| | 497 | * argument). |
| | 498 | */ |
| | 499 | void open_script_file(const char *fname, int quiet, int script_more_mode); |
| | 500 | |
| | 501 | /* |
| | 502 | * Close the script file. Returns the original MORE mode that was |
| | 503 | * in effect before the script file was opened; this MORE mode |
| | 504 | * should be restored. |
| | 505 | */ |
| | 506 | int close_script_file(); |
| | 507 | |
| | 508 | /* |
| | 509 | * Read a line of text from the keyboard. Fills in the buffer with |
| | 510 | * a null-terminated UTF-8 string. Returns zero on success, |
| | 511 | * non-zero on end-of-file reading the console (which usually |
| | 512 | * indicates that the user has closed the application, so we're in |
| | 513 | * the process of terminating; it might also indicate that the |
| | 514 | * user's terminal has been detached, in which case we also probably |
| | 515 | * can't do much except terminate). |
| | 516 | */ |
| | 517 | int read_line(VMG_ char *buf, size_t buflen); |
| | 518 | |
| | 519 | /* |
| | 520 | * Read a line of input with optional timeout. Fills in the buffer |
| | 521 | * with a null-terminated UTF-8 string. Returns an OS_EVT_xxx code, |
| | 522 | * according to how the input was terminated: |
| | 523 | * |
| | 524 | * OS_EVT_LINE - the user pressed Return to enter the text |
| | 525 | *. OS_EVT_TIMEOUT - the timeout expired before the user pressed Return |
| | 526 | *. OS_EVT_EOF - an error occurred reading the input |
| | 527 | * |
| | 528 | * This routine is a cover for the low-level os_gets_timeout(), and |
| | 529 | * behaves essentially the same way. Note in particular that if this |
| | 530 | * routine returns OS_EVT_TIMEOUT, then our read_line_cancel() routine |
| | 531 | * must be called before any output or other display changes can be |
| | 532 | * made, with the exception that another call to read_line_timeout() |
| | 533 | * is always allowed. |
| | 534 | */ |
| | 535 | int read_line_timeout(VMG_ char *buf, size_t buflen, |
| | 536 | unsigned long timeout, int use_timeout); |
| | 537 | |
| | 538 | /* |
| | 539 | * Cancel a line of input in progress, which was interrupted by a |
| | 540 | * timeout in read_line_timeout(). If 'reset' is true, we'll forget |
| | 541 | * any editing state from the prior line. |
| | 542 | */ |
| | 543 | void read_line_cancel(VMG_ int reset); |
| | 544 | |
| | 545 | /* |
| | 546 | * Display a file dialog. This routine works exactly the same way |
| | 547 | * as os_askfile(), but is implemented here to allow for a formatted |
| | 548 | * text interface on systems where no dialog is available. |
| | 549 | */ |
| | 550 | int askfile(VMG_ const char *prompt, size_t prompt_len, |
| | 551 | char *reply, size_t replen, |
| | 552 | int dialog_type, os_filetype_t file_type); |
| | 553 | |
| | 554 | /* |
| | 555 | * Display a system dialog. This routine works exactly the same way |
| | 556 | * as os_input_dialog(), but is implemented here to allow for a |
| | 557 | * formatted text interface on systems where no dialog is available. |
| | 558 | */ |
| | 559 | int input_dialog(VMG_ int icon_id, const char *prompt, |
| | 560 | int standard_button_set, |
| | 561 | const char **buttons, int button_count, |
| | 562 | int default_index, int cancel_index); |
| | 563 | |
| | 564 | /* show the MORE prompt and wait for the user to acknowledge it */ |
| | 565 | virtual void show_more_prompt(VMG0_) = 0; |
| | 566 | |
| | 567 | /* |
| | 568 | * Log an event. This saves the event to the current script log, if |
| | 569 | * there is one, in the proper format for the script. We return the |
| | 570 | * event code. |
| | 571 | * |
| | 572 | * 'evt' is the event type, as an OS_EVT_xxx or VMCON_EVT_xxx code. |
| | 573 | * |
| | 574 | * 'param' can be given in the local UI character set or in UTF-8 - |
| | 575 | * specify which it is via 'param_is_utf8'. We write the file in the |
| | 576 | * local UI character set, so if the parameter is given in UTF-8, we |
| | 577 | * have to translate it. |
| | 578 | */ |
| | 579 | int log_event(VMG_ int evt, |
| | 580 | const char *param, size_t paramlen, int param_is_utf8); |
| | 581 | int log_event(VMG_ int evt) |
| | 582 | { return log_event(vmg_ evt, 0, 0, FALSE); } |
| | 583 | |
| | 584 | /* read an event from an event script */ |
| | 585 | int read_event_script(VMG_ int *evt, char *buf, size_t buflen, |
| | 586 | const int *filter, int filter_cnt, |
| | 587 | unsigned long *attrs); |
| | 588 | |
| | 589 | protected: |
| | 590 | /* |
| | 591 | * Service routine - show MORE prompt on this console. This can be |
| | 592 | * called from show_more_prompt() when a MORE prompt is desired at all |
| | 593 | * in the subclassed console. |
| | 594 | */ |
| | 595 | void show_con_more_prompt(VMG0_); |
| | 596 | |
| | 597 | /* read a line from the script file */ |
| | 598 | int read_line_from_script(char *buf, size_t buflen, int *evt); |
| | 599 | |
| | 600 | /* read the type tag from the next script event */ |
| | 601 | int read_script_event_type(int *evt, unsigned long *attrs); |
| | 602 | |
| | 603 | /* skip to the next line of the script */ |
| | 604 | void skip_script_line(osfildef *fp); |
| | 605 | |
| | 606 | /* |
| | 607 | * read a script parameter - this reads the rest of the line into the |
| | 608 | * given buffer, and skips to the start of the next line in the script; |
| | 609 | * returns true on success, false if we reach EOF before reading |
| | 610 | * anything |
| | 611 | */ |
| | 612 | int read_script_param(char *buf, size_t buflen, osfildef *fp); |
| | 613 | |
| | 614 | /* internal routine to terminate line reading */ |
| | 615 | void read_line_done(VMG0_); |
| | 616 | |
| | 617 | /* write utf-8 text to a file, mapping to the given file character set */ |
| | 618 | void write_to_file(osfildef *fp, const char *txt, |
| | 619 | class CCharmapToLocal *map); |
| | 620 | |
| | 621 | /* our current display stream */ |
| | 622 | class CVmFormatter *disp_str_; |
| | 623 | |
| | 624 | /* our log stream - this stream is written to the log file, if any */ |
| | 625 | class CVmFormatterLog *log_str_; |
| | 626 | |
| | 627 | /* |
| | 628 | * Flag: the log stream is enabled. We can temporarily disable the |
| | 629 | * log stream, such as when writing to the statusline stream. |
| | 630 | */ |
| | 631 | int log_enabled_ : 1; |
| | 632 | |
| | 633 | /* |
| | 634 | * Flag: display two spaces after a period-like punctuation mark. |
| | 635 | * This should be true if the output should have two spaces after a |
| | 636 | * period, question mark, or exclamation point, false for a single |
| | 637 | * space. This should generally be true for fixed-width fonts, |
| | 638 | * false for proportional fonts, although some users might prefer to |
| | 639 | * use single-spacing even for fixed-width fonts. |
| | 640 | */ |
| | 641 | unsigned int doublespace_ : 1; |
| | 642 | |
| | 643 | /* |
| | 644 | * flag: the command log is an event script; if this is set, we log all |
| | 645 | * input events in the tagged <eventscript> file format, otherwise we |
| | 646 | * log just command lines in the old-style ">line" format |
| | 647 | */ |
| | 648 | unsigned int command_eventscript_ : 1; |
| | 649 | |
| | 650 | /* |
| | 651 | * Script-input stack. Each time we open a script, we create a new |
| | 652 | * stack entry object and link it at the head of the list. So, the |
| | 653 | * head of the list is the current state, the next element is the |
| | 654 | * enclosing state, and so on. |
| | 655 | */ |
| | 656 | script_stack_entry *script_sp_; |
| | 657 | |
| | 658 | /* command log file, if there is one */ |
| | 659 | osfildef *command_fp_; |
| | 660 | |
| | 661 | /* name of the command log file */ |
| | 662 | char command_fname_[OSFNMAX]; |
| | 663 | }; |
| | 664 | |
| | 665 | /* ------------------------------------------------------------------------ */ |
| | 666 | /* |
| | 667 | * Main system console. This console is attached to the OS-level primary |
| | 668 | * console. |
| | 669 | */ |
| | 670 | class CVmConsoleMain: public CVmConsole |
| | 671 | { |
| | 672 | public: |
| | 673 | CVmConsoleMain(VMG0_); |
| | 674 | ~CVmConsoleMain(); |
| | 675 | |
| | 676 | /* get the system banner manager */ |
| | 677 | class CVmBannerManager *get_banner_manager() const |
| | 678 | { return banner_manager_; } |
| | 679 | |
| | 680 | /* get the system log console manager */ |
| | 681 | class CVmLogConsoleManager *get_log_console_manager() const |
| | 682 | { return log_console_manager_; } |
| | 683 | |
| | 684 | /* |
| | 685 | * Switch in or out of statusline mode. When we're running on the text |
| | 686 | * implementation of the OS layer, we must explicitly switch modes |
| | 687 | * between the main text stream and statusline stream. 'mode' is true |
| | 688 | * to switch to statusline mode, false to switch back to main text |
| | 689 | * mode. |
| | 690 | */ |
| | 691 | void set_statusline_mode(VMG_ int mode); |
| | 692 | |
| | 693 | /* clear the window */ |
| | 694 | virtual void clear_window(VMG0_); |
| | 695 | |
| | 696 | /* set MORE mode */ |
| | 697 | virtual int set_more_state(int state) |
| | 698 | { |
| | 699 | int old_state; |
| | 700 | |
| | 701 | /* remember the old state */ |
| | 702 | old_state = G_os_moremode; |
| | 703 | |
| | 704 | /* set the new mode */ |
| | 705 | G_os_moremode = state; |
| | 706 | |
| | 707 | /* return the previous state */ |
| | 708 | return old_state; |
| | 709 | } |
| | 710 | |
| | 711 | /* get the MORE mode */ |
| | 712 | virtual int is_more_mode() const { return G_os_moremode; } |
| | 713 | |
| | 714 | /* |
| | 715 | * Flush everything - this flushes not only the main console, but any |
| | 716 | * banner windows we're managing. This should be called before pausing |
| | 717 | * for input or for a timed delay, to make sure that buffered output in |
| | 718 | * all windows is shown. |
| | 719 | */ |
| | 720 | void flush_all(VMG_ vm_nl_type nl); |
| | 721 | |
| | 722 | /* get the line width of the display device */ |
| | 723 | virtual int get_line_width() const { return G_os_linewidth; } |
| | 724 | |
| | 725 | /* do not allow overrunning the line width on the main console */ |
| | 726 | virtual int allow_overrun() const { return FALSE; } |
| | 727 | |
| | 728 | /* get the page length of the display device */ |
| | 729 | virtual int get_page_length() const { return G_os_pagelength; } |
| | 730 | |
| | 731 | /* show the MORE prompt */ |
| | 732 | virtual void show_more_prompt(VMG0_) { show_con_more_prompt(vmg0_); } |
| | 733 | |
| | 734 | protected: |
| | 735 | /* main text area display stream */ |
| | 736 | class CVmFormatterMain *main_disp_str_; |
| | 737 | |
| | 738 | /* statusline display stream */ |
| | 739 | class CVmFormatterStatline *statline_str_; |
| | 740 | |
| | 741 | /* |
| | 742 | * The system banner window manager. Since the main console is |
| | 743 | * inherently a singleton (as there's only one OS-level primary |
| | 744 | * console), we keep track of the banner manager. |
| | 745 | */ |
| | 746 | class CVmBannerManager *banner_manager_; |
| | 747 | |
| | 748 | /* the system log console manager */ |
| | 749 | class CVmLogConsoleManager *log_console_manager_; |
| | 750 | }; |
| | 751 | |
| | 752 | |
| | 753 | /* ------------------------------------------------------------------------ */ |
| | 754 | /* |
| | 755 | * Banner-window console. |
| | 756 | */ |
| | 757 | class CVmConsoleBanner: public CVmConsole |
| | 758 | { |
| | 759 | public: |
| | 760 | /* create */ |
| | 761 | CVmConsoleBanner(void *banner_handle, int win_type, unsigned long style); |
| | 762 | ~CVmConsoleBanner(); |
| | 763 | |
| | 764 | /* retrieve our OS-level banner handle */ |
| | 765 | void *get_os_handle() const { return banner_; } |
| | 766 | |
| | 767 | /* get banner information */ |
| | 768 | int get_banner_info(os_banner_info_t *info); |
| | 769 | |
| | 770 | /* clear the window */ |
| | 771 | virtual void clear_window(VMG0_); |
| | 772 | |
| | 773 | /* set MORE mode */ |
| | 774 | virtual int set_more_state(int state) |
| | 775 | { |
| | 776 | /* banners never change the global MORE mode state */ |
| | 777 | return is_more_mode(); |
| | 778 | } |
| | 779 | |
| | 780 | /* get the MORE mode - return the global mode flag */ |
| | 781 | virtual int is_more_mode() const { return G_os_moremode; } |
| | 782 | |
| | 783 | /* show the MORE prompt - does nothing for a banner window */ |
| | 784 | virtual void show_more_prompt(VMG0_) { show_con_more_prompt(vmg0_); } |
| | 785 | |
| | 786 | /* get the line width of the display device */ |
| | 787 | virtual int get_line_width() const |
| | 788 | { return os_banner_get_charwidth(banner_); } |
| | 789 | |
| | 790 | /* allow overrunning the line width in a banner */ |
| | 791 | virtual int allow_overrun() const { return TRUE; } |
| | 792 | |
| | 793 | /* get the page length of the display device, for MORE mode */ |
| | 794 | virtual int get_page_length() const |
| | 795 | { return os_banner_get_charheight(banner_); } |
| | 796 | |
| | 797 | protected: |
| | 798 | /* our underlying OS-level banner handle */ |
| | 799 | void *banner_; |
| | 800 | |
| | 801 | /* our window type (an OS_BANNER_TYPE_xxx code) */ |
| | 802 | int win_type_; |
| | 803 | }; |
| | 804 | |
| | 805 | /* ------------------------------------------------------------------------ */ |
| | 806 | /* |
| | 807 | * Common base class for log-only consoles |
| | 808 | */ |
| | 809 | class CVmConsoleLogBase: public CVmConsole |
| | 810 | { |
| | 811 | public: |
| | 812 | /* clear the window - do nothing on a log console */ |
| | 813 | virtual void clear_window(VMG0_) { } |
| | 814 | |
| | 815 | /* set MORE mode - doesn't apply to us */ |
| | 816 | virtual int set_more_state(int state) { return FALSE; } |
| | 817 | |
| | 818 | /* show the MORE prompt - does nothing */ |
| | 819 | virtual void show_more_prompt(VMG0_) { } |
| | 820 | |
| | 821 | /* get the MORE mode */ |
| | 822 | virtual int is_more_mode() const { return FALSE; } |
| | 823 | |
| | 824 | /* get the line width of the display device */ |
| | 825 | virtual int get_line_width() const { return G_os_linewidth; } |
| | 826 | |
| | 827 | /* allow overrunning the line width in a lot file */ |
| | 828 | virtual int allow_overrun() const { return TRUE; } |
| | 829 | |
| | 830 | /* |
| | 831 | * get the page length of the display device - this is arbitrary, since |
| | 832 | * we don't use MORE mode anyway |
| | 833 | */ |
| | 834 | virtual int get_page_length() const { return 55; } |
| | 835 | }; |
| | 836 | |
| | 837 | /* |
| | 838 | * Log console. This is used to create a console that has no display |
| | 839 | * presence, but simply captures its output directly to a log file. (This |
| | 840 | * is similar to the log file attached to a regular display console, but |
| | 841 | * this kind of console ONLY has the log file.) |
| | 842 | */ |
| | 843 | class CVmConsoleLog: public CVmConsoleLogBase |
| | 844 | { |
| | 845 | public: |
| | 846 | /* create */ |
| | 847 | CVmConsoleLog(const char *fname, osfildef *fp, |
| | 848 | class CCharmapToLocal *cmap, int width); |
| | 849 | ~CVmConsoleLog(); |
| | 850 | }; |
| | 851 | |
| | 852 | /* |
| | 853 | * A special console that sends output to the *main* console's log file. |
| | 854 | */ |
| | 855 | class CVmConsoleMainLog: public CVmConsoleLogBase |
| | 856 | { |
| | 857 | public: |
| | 858 | /* create */ |
| | 859 | CVmConsoleMainLog() { } |
| | 860 | ~CVmConsoleMainLog() { } |
| | 861 | |
| | 862 | /* we send text to the main console's log */ |
| | 863 | int format_text(VMG_ const char *p, size_t len) |
| | 864 | { |
| | 865 | /* send the text to the main console's log file */ |
| | 866 | return G_console->format_text_to_log(vmg_ p, len); |
| | 867 | } |
| | 868 | }; |
| | 869 | |
| | 870 | |
| | 871 | /* ------------------------------------------------------------------------ */ |
| | 872 | /* |
| | 873 | * constants |
| | 874 | */ |
| | 875 | |
| | 876 | /* |
| | 877 | * HTML lexical analysis modes. We use these modes for two purposes: |
| | 878 | * |
| | 879 | * First, for tracking our state while doing our own HTML parsing, which we |
| | 880 | * do when the underlying renderer handles only plain text (in which case |
| | 881 | * we must interpret and remove any HTML tags from the stream before |
| | 882 | * sending the stream on to the underlying renderer). |
| | 883 | * |
| | 884 | * Second, for tracking the lexical state in the underlying renderer, when |
| | 885 | * the underlying renderer handles full HTML interpretation. In this case, |
| | 886 | * we simply pass HTML tags through to the underlying renderer; but even |
| | 887 | * though we don't interpret the tags, we do keep track of the lexical |
| | 888 | * structure of the text, so that we can tell when we're inside a tag and |
| | 889 | * when we're in ordinary text. Certain operations we apply (such as case |
| | 890 | * conversions with "\^" and "\v" sequences) apply only to ordinary text, |
| | 891 | * so we need to know what's what in the stream text. |
| | 892 | */ |
| | 893 | enum vmconsole_html_state |
| | 894 | { |
| | 895 | VMCON_HPS_NORMAL, /* normal text, not in a tag */ |
| | 896 | VMCON_HPS_TAG, /* parsing inside a tag */ |
| | 897 | VMCON_HPS_ATTR_NAME, /* parsing an attribute name */ |
| | 898 | VMCON_HPS_ATTR_VAL, /* parsing an attribute value */ |
| | 899 | VMCON_HPS_SQUOTE, /* in a single-quoted string in a tag */ |
| | 900 | VMCON_HPS_DQUOTE, /* in a double-quoted string in a tag */ |
| | 901 | VMCON_HPS_ENTITY_1ST, /* first character in an entity name */ |
| | 902 | VMCON_HPS_ENTITY_NUM_1ST, /* first character of a numeric entitiy */ |
| | 903 | VMCON_HPS_ENTITY_HEX, /* in a hexadecimal entitiy number */ |
| | 904 | VMCON_HPS_ENTITY_DEC, /* in a decimal entity number */ |
| | 905 | VMCON_HPS_ENTITY_NAME, /* in a named entity */ |
| | 906 | VMCON_HPS_MARKUP_END /* at last character of a markup */ |
| | 907 | }; |
| | 908 | |
| | 909 | /* |
| | 910 | * HTML parsing mode flag for <BR> tags. We defer these until we've |
| | 911 | * read the full tag in order to obey an HEIGHT attribute we find. When |
| | 912 | * we encounter a <BR>, we figure out whether we think we'll need a |
| | 913 | * flush or a blank line; if we find a HEIGHT attribute, we may change |
| | 914 | * this opinion. |
| | 915 | */ |
| | 916 | enum vmconsole_html_br_mode |
| | 917 | { |
| | 918 | HTML_DEFER_BR_NONE, /* no pending <BR> */ |
| | 919 | HTML_DEFER_BR_FLUSH, /* only need to flush output */ |
| | 920 | HTML_DEFER_BR_BLANK /* need a blank line */ |
| | 921 | }; |
| | 922 | |
| | 923 | /* |
| | 924 | * Color/attribute information. Each character is tagged with its current |
| | 925 | * color and attributes. |
| | 926 | */ |
| | 927 | struct vmcon_color_t |
| | 928 | { |
| | 929 | /* foreground color */ |
| | 930 | os_color_t fg; |
| | 931 | |
| | 932 | /* background color */ |
| | 933 | os_color_t bg; |
| | 934 | |
| | 935 | /* the current OS_ATTR_xxx attributes */ |
| | 936 | int attr; |
| | 937 | |
| | 938 | /* check for equality with another color structure */ |
| | 939 | int equals(const vmcon_color_t *other) const |
| | 940 | { |
| | 941 | return (fg == other->fg |
| | 942 | && bg == other->bg |
| | 943 | && attr == other->attr); |
| | 944 | } |
| | 945 | }; |
| | 946 | |
| | 947 | /* ------------------------------------------------------------------------ */ |
| | 948 | /* |
| | 949 | * Output Buffer Flags. Each character in the output buffer has an |
| | 950 | * associated flag value associated with it; the flag value is a |
| | 951 | * combination of the bit flags defined here. |
| | 952 | */ |
| | 953 | |
| | 954 | /* |
| | 955 | * Unbreakable character point. This indicates that a line break is not |
| | 956 | * allowed to follow this character, even if the text at this point would |
| | 957 | * ordinarily allow a soft line break. Note that this does NOT override a |
| | 958 | * hard line break (i.e., an explicit newline); it merely prevents a soft |
| | 959 | * line break from occurring immediately after this character. |
| | 960 | * |
| | 961 | * We apply this flag to the character immediately preceding an explicit |
| | 962 | * non-breaking space in the text stream. |
| | 963 | */ |
| | 964 | #define VMCON_OBF_NOBREAK 0x01 |
| | 965 | |
| | 966 | /* |
| | 967 | * Breakable character point. This indicates that a line break is allowed |
| | 968 | * to follow this character, even if the text at this point would not |
| | 969 | * ordinarily allow a soft line break. |
| | 970 | * |
| | 971 | * We apply this flag to the character immediately preceding an explicit |
| | 972 | * zero-width breakable space in the text stream. |
| | 973 | */ |
| | 974 | #define VMCON_OBF_OKBREAK 0x02 |
| | 975 | |
| | 976 | /* |
| | 977 | * Break-anywhere mode. This indicates that this character is part of a |
| | 978 | * run of break-anywhere text. In break-anywhere text, we're allowed to |
| | 979 | * break lines between any two characters, except adjacent to explicit |
| | 980 | * non-breaking spaces in the text stream. |
| | 981 | * |
| | 982 | * Note that the NOBREAK flag overrides this flag, because this flag |
| | 983 | * merely indicates the mode, while the NOBREAK flag indicates an explicit |
| | 984 | * non-breaking indicator. |
| | 985 | */ |
| | 986 | #define VMCON_OBF_BREAK_ANY 0x04 |
| | 987 | |
| | 988 | /* |
| | 989 | * Soft hyphenation point. This flag indicates that the text can be |
| | 990 | * hyphenated immediately following this character, which is to say that |
| | 991 | * we can insert a hyphen following this character and break the line |
| | 992 | * after the hyphen. If we do not hyphenate here, the soft hyphen has no |
| | 993 | * effect; in particular, no hyphen character appears in the output stream |
| | 994 | * when a soft hyphen is not used as a line break point. |
| | 995 | */ |
| | 996 | #define VMCON_OBF_SHY 0x08 |
| | 997 | |
| | 998 | /* |
| | 999 | * Quoted space. This indicates that this is a space character that |
| | 1000 | * doesn't collapse in runs of contiguous whitespace. |
| | 1001 | */ |
| | 1002 | #define VMCON_OBF_QSPACE 0x10 |
| | 1003 | |
| | 1004 | |
| | 1005 | /* ------------------------------------------------------------------------ */ |
| | 1006 | /* |
| | 1007 | * Output formatter stream interface. A formatter stream performs |
| | 1008 | * formatting on a stream of displayed text. |
| | 1009 | * |
| | 1010 | * A given stream of display text might be fed into more than one |
| | 1011 | * formatter stream. For example, if logging is turned on for a |
| | 1012 | * console, we'll feed the same text to the console's main formatting |
| | 1013 | * stream, which will end up being displayed to the user, and also to |
| | 1014 | * the log file's formatting stream, which will end up written out to |
| | 1015 | * the log file. |
| | 1016 | * |
| | 1017 | * Note that the formatter interface is internal to the console system. |
| | 1018 | * Client code should never need to refer to a formatter object, but |
| | 1019 | * should instead call the console object (CVmConsole). |
| | 1020 | */ |
| | 1021 | |
| | 1022 | /* <TAB> tag alignment types */ |
| | 1023 | enum vmfmt_tab_align_t |
| | 1024 | { |
| | 1025 | VMFMT_TAB_NONE, |
| | 1026 | VMFMT_TAB_LEFT, |
| | 1027 | VMFMT_TAB_CENTER, |
| | 1028 | VMFMT_TAB_RIGHT, |
| | 1029 | VMFMT_TAB_DECIMAL |
| | 1030 | }; |
| | 1031 | |
| | 1032 | /* maximum formatter state stack depth */ |
| | 1033 | const size_t CVMFMT_STACK_MAX = 25; |
| | 1034 | |
| | 1035 | /* maximum depth of color stack */ |
| | 1036 | const size_t CVFMT_CLRSTK_MAX = 25; |
| | 1037 | |
| | 1038 | /* maximum length of an attribute name/value */ |
| | 1039 | const size_t CVFMT_MAX_ATTR_NAME = 40; |
| | 1040 | const size_t CVFMT_MAX_ATTR_VAL = 256; |
| | 1041 | |
| | 1042 | /* output stream information structure (forward declaration) */ |
| | 1043 | typedef struct out_stream_info out_stream_info; |
| | 1044 | |
| | 1045 | /* output formatter stream */ |
| | 1046 | class CVmFormatter |
| | 1047 | { |
| | 1048 | public: |
| | 1049 | CVmFormatter(class CVmConsole *console) |
| | 1050 | { |
| | 1051 | /* remember our display object */ |
| | 1052 | console_ = console; |
| | 1053 | |
| | 1054 | /* there's no title buffer by default */ |
| | 1055 | html_title_buf_ = 0; |
| | 1056 | html_title_buf_size_ = 0; |
| | 1057 | |
| | 1058 | /* we have no horizontal tab table yet (for the HTML mini-parser) */ |
| | 1059 | tabs_ = 0; |
| | 1060 | |
| | 1061 | /* no character mapper yet */ |
| | 1062 | cmap_ = 0; |
| | 1063 | } |
| | 1064 | |
| | 1065 | virtual ~CVmFormatter(); |
| | 1066 | |
| | 1067 | /* initialize */ |
| | 1068 | virtual void init() |
| | 1069 | { |
| | 1070 | /* start out at the first column */ |
| | 1071 | linepos_ = 0; |
| | 1072 | linecol_ = 0; |
| | 1073 | linebuf_[0] = '\0'; |
| | 1074 | is_continuation_ = FALSE; |
| | 1075 | |
| | 1076 | /* start out in normal text color */ |
| | 1077 | cur_color_.fg = OS_COLOR_P_TEXT; |
| | 1078 | cur_color_.bg = OS_COLOR_P_TRANSPARENT; |
| | 1079 | cur_color_.attr = 0; |
| | 1080 | os_color_ = cur_color_; |
| | 1081 | |
| | 1082 | /* no pending tab yet */ |
| | 1083 | pending_tab_align_ = VMFMT_TAB_NONE; |
| | 1084 | |
| | 1085 | /* start out at the first line */ |
| | 1086 | linecnt_ = 0; |
| | 1087 | |
| | 1088 | /* we're not in either "caps", "nocaps", or "allcaps" mode yet */ |
| | 1089 | capsflag_ = nocapsflag_ = allcapsflag_ = FALSE; |
| | 1090 | |
| | 1091 | /* |
| | 1092 | * set the initial buffer flags: start out in word-break (not |
| | 1093 | * break-anywhere) more |
| | 1094 | */ |
| | 1095 | cur_flags_ = 0; |
| | 1096 | |
| | 1097 | /* |
| | 1098 | * start in normal spacing mode - treat runs of whitespace as |
| | 1099 | * equivalent to a single space |
| | 1100 | */ |
| | 1101 | obey_whitespace_ = FALSE; |
| | 1102 | |
| | 1103 | /* start out in HTML interpretation mode */ |
| | 1104 | literal_mode_ = FALSE; |
| | 1105 | |
| | 1106 | /* presume we won't have OS-level line wrapping */ |
| | 1107 | os_line_wrap_ = FALSE; |
| | 1108 | |
| | 1109 | /* we haven't flushed a new line yet */ |
| | 1110 | just_did_nl_ = FALSE; |
| | 1111 | |
| | 1112 | /* assume that the underlying system is not HTML-enabled */ |
| | 1113 | html_target_ = FALSE; |
| | 1114 | |
| | 1115 | /* presume this target accepts OS highlighting sequences */ |
| | 1116 | plain_text_target_ = FALSE; |
| | 1117 | |
| | 1118 | /* |
| | 1119 | * start out in "normal" lexical state in our parser and in the |
| | 1120 | * underlying output stream |
| | 1121 | */ |
| | 1122 | html_parse_state_ = VMCON_HPS_NORMAL; |
| | 1123 | html_passthru_state_ = VMCON_HPS_NORMAL; |
| | 1124 | |
| | 1125 | /* not in an ignored tag yet (-> nesting depth is zero) */ |
| | 1126 | html_in_ignore_ = 0; |
| | 1127 | |
| | 1128 | /* not in title mode yet (-> nesting depth is zero) */ |
| | 1129 | html_in_title_ = 0; |
| | 1130 | |
| | 1131 | /* not yet deferring line breaks */ |
| | 1132 | html_defer_br_ = HTML_DEFER_BR_NONE; |
| | 1133 | |
| | 1134 | /* not yet in quotes (-> nesting depth is zero) */ |
| | 1135 | html_quote_level_ = 0; |
| | 1136 | |
| | 1137 | /* we're not parsing inside any tags yet */ |
| | 1138 | html_allow_alt_ = FALSE; |
| | 1139 | html_allow_color_ = FALSE; |
| | 1140 | html_in_body_ = FALSE; |
| | 1141 | html_in_tab_ = FALSE; |
| | 1142 | html_in_wrap_ = FALSE; |
| | 1143 | |
| | 1144 | /* assume we're a normal display stream */ |
| | 1145 | is_disp_stream_ = TRUE; |
| | 1146 | |
| | 1147 | /* no stacked colors yet */ |
| | 1148 | color_sp_ = 0; |
| | 1149 | } |
| | 1150 | |
| | 1151 | /* get/set obey-whitespace mode */ |
| | 1152 | int get_obey_whitespace() const { return obey_whitespace_ != 0; } |
| | 1153 | void set_obey_whitespace(int f) { obey_whitespace_ = (f != 0); } |
| | 1154 | |
| | 1155 | /* turn on CAPS mode */ |
| | 1156 | void caps() |
| | 1157 | { |
| | 1158 | /* turn on CAPS mode */ |
| | 1159 | capsflag_ = TRUE; |
| | 1160 | |
| | 1161 | /* turn off the NOCAPS and ALLCAPS modes */ |
| | 1162 | nocapsflag_ = FALSE; |
| | 1163 | allcapsflag_ = FALSE; |
| | 1164 | } |
| | 1165 | |
| | 1166 | /* turn on NOCAPS mode */ |
| | 1167 | void nocaps() |
| | 1168 | { |
| | 1169 | /* turn on NOCAPS mode */ |
| | 1170 | nocapsflag_ = TRUE; |
| | 1171 | |
| | 1172 | /* turn off CAPS and ALLCAPS mode */ |
| | 1173 | capsflag_ = FALSE; |
| | 1174 | allcapsflag_ = FALSE; |
| | 1175 | } |
| | 1176 | |
| | 1177 | /* turn on or off ALLCAPS mode */ |
| | 1178 | void allcaps(int all_caps) |
| | 1179 | { |
| | 1180 | /* set the ALLCAPS flag */ |
| | 1181 | allcapsflag_ = all_caps; |
| | 1182 | |
| | 1183 | /* clear the CAPS and NOCAPS flags */ |
| | 1184 | capsflag_ = FALSE; |
| | 1185 | nocapsflag_ = FALSE; |
| | 1186 | } |
| | 1187 | |
| | 1188 | /* |
| | 1189 | * Display a string of a given byte-length. The text is given in |
| | 1190 | * UTF-8 format. We'll interpret embedded control codes (newlines, |
| | 1191 | * blank lines, caps flags, etc); depending on the display mode, we |
| | 1192 | * might also interpret HTML markup sequences. |
| | 1193 | */ |
| | 1194 | int format_text(VMG_ const char *s, size_t len); |
| | 1195 | |
| | 1196 | /* set the text attributes for subsequent format_text() displays */ |
| | 1197 | void set_text_attr(VMG_ int attr) |
| | 1198 | { |
| | 1199 | /* remember the new text attributes */ |
| | 1200 | cur_color_.attr = attr; |
| | 1201 | } |
| | 1202 | |
| | 1203 | /* set the text color for subsequent format_text() displays */ |
| | 1204 | void set_text_color(VMG_ os_color_t fg, os_color_t bg) |
| | 1205 | { |
| | 1206 | /* remember the new text colors */ |
| | 1207 | cur_color_.fg = fg; |
| | 1208 | cur_color_.bg = bg; |
| | 1209 | } |
| | 1210 | |
| | 1211 | /* write a blank line to the stream */ |
| | 1212 | void write_blank_line(VMG0_); |
| | 1213 | |
| | 1214 | /* |
| | 1215 | * Flush the current line to the display, using the given type of |
| | 1216 | * line termination. |
| | 1217 | */ |
| | 1218 | void flush(VMG_ vm_nl_type nl); |
| | 1219 | |
| | 1220 | /* clear our buffers */ |
| | 1221 | void empty_buffers(VMG0_); |
| | 1222 | |
| | 1223 | /* immediately update the display window */ |
| | 1224 | void update_display(VMG0_); |
| | 1225 | |
| | 1226 | /* |
| | 1227 | * determine if I'm an HTML target - this returns true if the |
| | 1228 | * underyling renderer accepts HTML, in which case we write HTML |
| | 1229 | * markups directly to the os_xxx routines (os_printz, for example). |
| | 1230 | */ |
| | 1231 | int is_html_target() const { return html_target_; } |
| | 1232 | |
| | 1233 | /* reset the MORE prompt line counter */ |
| | 1234 | virtual void reset_line_count(int clearing) { linecnt_ = 0; } |
| | 1235 | |
| | 1236 | /* |
| | 1237 | * Note that we read a line from the keyboard with echo. Reading a |
| | 1238 | * line displays a carriage return without our help, taking us back to |
| | 1239 | * the first column - we need to know this so that we can deal |
| | 1240 | * properly with output on the following line. |
| | 1241 | */ |
| | 1242 | void note_input_line() |
| | 1243 | { |
| | 1244 | /* |
| | 1245 | * a CR/LF will have been echoed automatically by the input |
| | 1246 | * reader, which takes us back to the first column |
| | 1247 | */ |
| | 1248 | linecol_ = 0; |
| | 1249 | |
| | 1250 | /* note that we effectively just wrote a newline */ |
| | 1251 | just_did_nl_ = TRUE; |
| | 1252 | } |
| | 1253 | |
| | 1254 | /* -------------------------------------------------------------------- */ |
| | 1255 | /* |
| | 1256 | * Virtual interface to underlying OS renderer |
| | 1257 | */ |
| | 1258 | |
| | 1259 | /* turn HTML mode on/off in the underlying OS-level renderer */ |
| | 1260 | virtual void start_html_in_os() = 0; |
| | 1261 | virtual void end_html_in_os() = 0; |
| | 1262 | |
| | 1263 | /* set the text color */ |
| | 1264 | virtual void set_os_text_color(os_color_t fg, os_color_t bg) = 0; |
| | 1265 | |
| | 1266 | /* set the body color */ |
| | 1267 | virtual void set_os_body_color(os_color_t) = 0; |
| | 1268 | |
| | 1269 | /* set text attributes */ |
| | 1270 | virtual void set_os_text_attr(int attr) = 0; |
| | 1271 | |
| | 1272 | /* |
| | 1273 | * display text to the OS window; the text is given in the local |
| | 1274 | * character set |
| | 1275 | */ |
| | 1276 | virtual void print_to_os(const char *txt) = 0; |
| | 1277 | |
| | 1278 | /* flush the underlying OS-level rendere */ |
| | 1279 | virtual void flush_to_os() = 0; |
| | 1280 | |
| | 1281 | /* set the window title in the OS layer */ |
| | 1282 | virtual void set_title_in_os(const char *txt) = 0; |
| | 1283 | |
| | 1284 | /* set a new character mapper */ |
| | 1285 | void set_charmap(class CCharmapToLocal *cmap); |
| | 1286 | |
| | 1287 | protected: |
| | 1288 | /* |
| | 1289 | * Write a line (or a partial line) of text to the stream, using the |
| | 1290 | * indicated line breaking. The text is given as wide Unicode |
| | 1291 | * characters. |
| | 1292 | */ |
| | 1293 | void write_text(VMG_ const wchar_t *txt, size_t cnt, |
| | 1294 | const vmcon_color_t *colors, vm_nl_type nl); |
| | 1295 | |
| | 1296 | /* write a tab to the stream */ |
| | 1297 | void write_tab(VMG_ int indent, int multiple); |
| | 1298 | |
| | 1299 | /* flush the current line, with or without padding */ |
| | 1300 | void flush_line(VMG_ int padding); |
| | 1301 | |
| | 1302 | /* |
| | 1303 | * Buffer a character of output. The character is presented to us as a |
| | 1304 | * wide Unicode character. We'll expand this character with the local |
| | 1305 | * character mapping's expansion rules, then add the expansion to our |
| | 1306 | * output buffer, performing word-wrapping as needed. |
| | 1307 | */ |
| | 1308 | void buffer_char(VMG_ wchar_t c); |
| | 1309 | |
| | 1310 | /* |
| | 1311 | * buffer an expanded character - we'll buffer the given unicode |
| | 1312 | * character, with no further expansion |
| | 1313 | */ |
| | 1314 | void buffer_expchar(VMG_ wchar_t c); |
| | 1315 | |
| | 1316 | /* buffer a rendered expanded character */ |
| | 1317 | void buffer_rendered(wchar_t c, unsigned char flags, int wid); |
| | 1318 | |
| | 1319 | /* |
| | 1320 | * Buffer a string of output to the stream. The string is in UTF-8 |
| | 1321 | * format. |
| | 1322 | */ |
| | 1323 | void buffer_string(VMG_ const char *txt); |
| | 1324 | |
| | 1325 | /* buffer a wide unicode character string */ |
| | 1326 | void buffer_wstring(VMG_ const wchar_t *txt); |
| | 1327 | |
| | 1328 | /* get the next wide unicode character in a UTF8-encoded string */ |
| | 1329 | static wchar_t next_wchar(const char **s, size_t *len); |
| | 1330 | |
| | 1331 | /* |
| | 1332 | * Determine if we should use MORE mode in the formatter layer; if |
| | 1333 | * this returns true, we handle MORE prompting ourselves, otherwise |
| | 1334 | * we handle it through the OS layer. |
| | 1335 | * |
| | 1336 | * The default version of this function is implemented in the output |
| | 1337 | * formatter configuration module. This function can also be |
| | 1338 | * overridden in a subclass (for example, a log stream never uses |
| | 1339 | * MORE mode, no matter what configuration we're using). |
| | 1340 | */ |
| | 1341 | virtual int formatter_more_mode() const; |
| | 1342 | |
| | 1343 | /* |
| | 1344 | * Get the maximum column to store in our internal buffer. If we're |
| | 1345 | * doing our own line breaking, we'll break off the buffer at the |
| | 1346 | * actual line width of the underlying console. If we're doing OS |
| | 1347 | * line wrapping, we'll simply fill up our internal buffer to its |
| | 1348 | * maximum size, since the flushing points are irrelevant to the line |
| | 1349 | * wrapping the underlying OS console will be doing and hence we might |
| | 1350 | * as well buffer as much as we can for efficiency. |
| | 1351 | */ |
| | 1352 | virtual int get_buffer_maxcol() const |
| | 1353 | { |
| | 1354 | /* |
| | 1355 | * if the OS is doing the line wrapping, use the full buffer; |
| | 1356 | * otherwise, use the actual line width from the underyling |
| | 1357 | * console |
| | 1358 | */ |
| | 1359 | if (os_line_wrap_) |
| | 1360 | return OS_MAXWIDTH; |
| | 1361 | else |
| | 1362 | return console_->get_line_width(); |
| | 1363 | } |
| | 1364 | |
| | 1365 | /* |
| | 1366 | * Determine if the underlying stream interprets HTML markups. We |
| | 1367 | * call this during initialization to set our html_target_ member |
| | 1368 | * (we cache the result since we check it frequently). |
| | 1369 | * |
| | 1370 | * This is implemented in the formatter configuration module. |
| | 1371 | */ |
| | 1372 | virtual int get_init_html_target() const; |
| | 1373 | |
| | 1374 | /* -------------------------------------------------------------------- */ |
| | 1375 | /* |
| | 1376 | * HTML mini-parser. This only needs to be implemented when linking |
| | 1377 | * with non-HTML osifc implementations; when osifc provides HTML |
| | 1378 | * parsing, we don't need to do any HTML parsing here, so these can use |
| | 1379 | * dummy implementations. |
| | 1380 | */ |
| | 1381 | |
| | 1382 | /* |
| | 1383 | * Resume text-only HTML mini-parser. This is called when we start |
| | 1384 | * writing a new string and discover that we're parsing inside an HTML |
| | 1385 | * tag in our mini-parser. |
| | 1386 | * |
| | 1387 | * Returns the next character after the run of text we parse. |
| | 1388 | */ |
| | 1389 | wchar_t resume_html_parsing(VMG_ wchar_t c, |
| | 1390 | const char **sp, size_t *slenp); |
| | 1391 | |
| | 1392 | /* |
| | 1393 | * Parse the beginning HTML markup. This is called when we are |
| | 1394 | * scanning a '<' or '&' character in output text, and we're in HTML |
| | 1395 | * mode, and the underlying target doesn't support HTML parsing. |
| | 1396 | * Returns the next character to process after we finish our initial |
| | 1397 | * parsing. |
| | 1398 | */ |
| | 1399 | wchar_t parse_html_markup(VMG_ wchar_t c, |
| | 1400 | const char **sp, size_t *slenp); |
| | 1401 | |
| | 1402 | /* |
| | 1403 | * Expand any pending tab. This should be called when we're doing |
| | 1404 | * HTML mini-parsing, and we see a <TAB> tag or we reach the end of an |
| | 1405 | * output line. We'll expand any pending RIGHT/CENTER tab by going |
| | 1406 | * back to the tab's location and inserting the necessary number of |
| | 1407 | * spaces now that we know the extent of the text affected. |
| | 1408 | * |
| | 1409 | * If 'allow_anon' is true, we will allow "anonymous" tabs, which is |
| | 1410 | * to say tabs with no ID attribute. Anonymous tabs allow text to be |
| | 1411 | * tabbed relative to the full width of the line, but these are |
| | 1412 | * meaningful only with normal line endings; when a line is flushed |
| | 1413 | * before the end of the line is reached (because the line will be |
| | 1414 | * used for a prompt, for example), anonymous tabs should not be |
| | 1415 | * expanded with the line. |
| | 1416 | */ |
| | 1417 | void expand_pending_tab(VMG_ int allow_anon); |
| | 1418 | |
| | 1419 | /* |
| | 1420 | * find a tab definition object; if 'create' it true and the specified |
| | 1421 | * tab doesn't exist, we'll create a new tab object |
| | 1422 | */ |
| | 1423 | class CVmFmtTabStop *find_tab(wchar_t *id, int create); |
| | 1424 | |
| | 1425 | /* service routine - translate a wide character string to an integer */ |
| | 1426 | static int wtoi(const wchar_t *p); |
| | 1427 | |
| | 1428 | /* parse a COLOR attribute of a FONT tag */ |
| | 1429 | int parse_color_attr(VMG_ const wchar_t *val, os_color_t *result); |
| | 1430 | |
| | 1431 | /* push the current color for later restoration */ |
| | 1432 | void push_color() |
| | 1433 | { |
| | 1434 | /* |
| | 1435 | * add a level to the color stack, if possible; if it's not |
| | 1436 | * possible, assume we've lost an end tag somewhere, so rotate the |
| | 1437 | * entire stack down a level |
| | 1438 | */ |
| | 1439 | if (color_sp_ == CVFMT_CLRSTK_MAX) |
| | 1440 | { |
| | 1441 | /* take everything down a level */ |
| | 1442 | memmove(color_stack_, color_stack_ + 1, |
| | 1443 | (CVFMT_CLRSTK_MAX - 1)*sizeof(color_stack_[0])); |
| | 1444 | --color_sp_; |
| | 1445 | } |
| | 1446 | |
| | 1447 | /* save the current color in the stack */ |
| | 1448 | color_stack_[color_sp_++] = cur_color_; |
| | 1449 | } |
| | 1450 | |
| | 1451 | /* pop the current color */ |
| | 1452 | void pop_color() |
| | 1453 | { |
| | 1454 | /* |
| | 1455 | * if we're at the bottom of the stack, we must have had too many |
| | 1456 | * close tags; ignore the extra operation |
| | 1457 | */ |
| | 1458 | if (color_sp_ != 0) |
| | 1459 | { |
| | 1460 | /* restore the next color down */ |
| | 1461 | cur_color_ = color_stack_[--color_sp_]; |
| | 1462 | } |
| | 1463 | } |
| | 1464 | |
| | 1465 | /* |
| | 1466 | * Process a <nolog> tag. By default, we ignore this tag entirely. |
| | 1467 | * Log streams should hide the contents of this tag. |
| | 1468 | */ |
| | 1469 | virtual void process_nolog_tag(int /*is_end_tag*/) |
| | 1470 | { |
| | 1471 | /* by default, ignore this tag */ |
| | 1472 | } |
| | 1473 | |
| | 1474 | /* |
| | 1475 | * Process a <log> tag. By default, we hide the contents of this tag; |
| | 1476 | * this tag marks text that is to be shown only in a log stream. |
| | 1477 | */ |
| | 1478 | virtual void process_log_tag(int is_end_tag) |
| | 1479 | { |
| | 1480 | /* turn hiding on or off as appropriate */ |
| | 1481 | if (is_end_tag) |
| | 1482 | --html_in_ignore_; |
| | 1483 | else |
| | 1484 | ++html_in_ignore_; |
| | 1485 | } |
| | 1486 | |
| | 1487 | /* -------------------------------------------------------------------- */ |
| | 1488 | /* |
| | 1489 | * Member variables |
| | 1490 | */ |
| | 1491 | |
| | 1492 | /* the console that owns this formatter stream */ |
| | 1493 | class CVmConsole *console_; |
| | 1494 | |
| | 1495 | /* our character mapper */ |
| | 1496 | class CCharmapToLocal *cmap_; |
| | 1497 | |
| | 1498 | /* current line position and output column */ |
| | 1499 | int linepos_; |
| | 1500 | int linecol_; |
| | 1501 | |
| | 1502 | /* |
| | 1503 | * flag: the current buffer is a "continuation" line; that is, we've |
| | 1504 | * already flushed a partial line to the display without moving to a |
| | 1505 | * new line vertically, and the current buffer will be displayed on the |
| | 1506 | * same line on the terminal, to the right of the previously-displayed |
| | 1507 | * material |
| | 1508 | */ |
| | 1509 | int is_continuation_; |
| | 1510 | |
| | 1511 | /* number of lines on the screen (since last MORE prompt) */ |
| | 1512 | int linecnt_; |
| | 1513 | |
| | 1514 | /* |
| | 1515 | * Output buffer. We keep the output buffer as wide Unicode |
| | 1516 | * characters, and translate to the local character set when we |
| | 1517 | * flush the buffer and send the text to the os_xxx routines for |
| | 1518 | * display. |
| | 1519 | */ |
| | 1520 | wchar_t linebuf_[OS_MAXWIDTH + 1]; |
| | 1521 | |
| | 1522 | /* |
| | 1523 | * Output buffer character colors. We keep track of the display color |
| | 1524 | * of each character in the buffer. |
| | 1525 | */ |
| | 1526 | vmcon_color_t colorbuf_[OS_MAXWIDTH + 1]; |
| | 1527 | |
| | 1528 | /* |
| | 1529 | * output buffer flags - we keep a flag value for each character in |
| | 1530 | * the output buffer |
| | 1531 | */ |
| | 1532 | unsigned char flagbuf_[OS_MAXWIDTH + 1]; |
| | 1533 | |
| | 1534 | /* current attribute name/value buffers */ |
| | 1535 | wchar_t attrname_[CVFMT_MAX_ATTR_NAME]; |
| | 1536 | wchar_t attrval_[CVFMT_MAX_ATTR_VAL]; |
| | 1537 | wchar_t attr_qu_; |
| | 1538 | |
| | 1539 | /* current color of characters being added to our buffer */ |
| | 1540 | vmcon_color_t cur_color_; |
| | 1541 | |
| | 1542 | /* color of last OS output (via write_text) */ |
| | 1543 | vmcon_color_t os_color_; |
| | 1544 | |
| | 1545 | /* stack of color attributes, saved for nested tags */ |
| | 1546 | vmcon_color_t color_stack_[CVFMT_CLRSTK_MAX]; |
| | 1547 | size_t color_sp_; |
| | 1548 | |
| | 1549 | /* |
| | 1550 | * Current output character flags. This is the base value to write to |
| | 1551 | * flagbuf_ for each character we output, given the current mode |
| | 1552 | * settings. |
| | 1553 | */ |
| | 1554 | unsigned char cur_flags_; |
| | 1555 | |
| | 1556 | /* obey-whitespace mode - treat whitespace as literal */ |
| | 1557 | unsigned int obey_whitespace_ : 1; |
| | 1558 | |
| | 1559 | /* literal mode - ignore HTML markups, pass everything literally */ |
| | 1560 | unsigned int literal_mode_ : 1; |
| | 1561 | |
| | 1562 | /* CAPS mode - next character output is converted to upper-case */ |
| | 1563 | unsigned int capsflag_ : 1; |
| | 1564 | |
| | 1565 | /* NOCAPS mode - next character output is converted to lower-case */ |
| | 1566 | unsigned int nocapsflag_ : 1; |
| | 1567 | |
| | 1568 | /* ALLCAPS mode - all characters output are converted to upper-case */ |
| | 1569 | unsigned int allcapsflag_ : 1; |
| | 1570 | |
| | 1571 | /* flag indicating that we just flushed a new line */ |
| | 1572 | unsigned int just_did_nl_ : 1; |
| | 1573 | |
| | 1574 | /* |
| | 1575 | * Flag indicating that the underlying output system wants to |
| | 1576 | * receive its output as HTML. |
| | 1577 | * |
| | 1578 | * If this is true, we'll pass through HTML to the underlying output |
| | 1579 | * system, and in addition generate HTML sequences for certain |
| | 1580 | * TADS-native escapes (for example, we'll convert the "\n" sequence |
| | 1581 | * to a <BR> sequence). |
| | 1582 | * |
| | 1583 | * If this is false, we'll do just the opposite: we'll remove HTML |
| | 1584 | * from the output stream and convert it into normal text sequences. |
| | 1585 | */ |
| | 1586 | unsigned int html_target_ : 1; |
| | 1587 | |
| | 1588 | /* |
| | 1589 | * Flag indicating that the target uses plain text. If this flag is |
| | 1590 | * set, we won't add the OS escape codes for highlighted characters. |
| | 1591 | */ |
| | 1592 | unsigned int plain_text_target_ : 1; |
| | 1593 | |
| | 1594 | /* |
| | 1595 | * flag: the underlying OS layer handles line wrapping, so we never |
| | 1596 | * need to write newlines when flushing our line buffer except when |
| | 1597 | * we want to indicate a hard line break |
| | 1598 | */ |
| | 1599 | unsigned int os_line_wrap_ : 1; |
| | 1600 | |
| | 1601 | /* |
| | 1602 | * Current lexical analysis state for our own HTML parsing. This is |
| | 1603 | * used to track our HTML state when we have an underlying plain text |
| | 1604 | * renderer. |
| | 1605 | */ |
| | 1606 | vmconsole_html_state html_parse_state_; |
| | 1607 | |
| | 1608 | /* |
| | 1609 | * Current lexical analysis mode for the text stream going to the |
| | 1610 | * underlying renderer. This is used to track the lexical structure of |
| | 1611 | * the stream when we're passing HTML tags through to the underlying |
| | 1612 | * renderer. |
| | 1613 | */ |
| | 1614 | vmconsole_html_state html_passthru_state_; |
| | 1615 | |
| | 1616 | /* <BR> defer mode */ |
| | 1617 | vmconsole_html_br_mode html_defer_br_; |
| | 1618 | |
| | 1619 | /* |
| | 1620 | * HTML "ignore" mode - we suppress all output when parsing the |
| | 1621 | * contents of a <TITLE> or <ABOUTBOX> tag. This is a counter that |
| | 1622 | * keeps track of the nesting depth for ignored tags. |
| | 1623 | */ |
| | 1624 | int html_in_ignore_; |
| | 1625 | |
| | 1626 | /* |
| | 1627 | * HTML <TITLE> mode - when we're in this mode, we're gathering the |
| | 1628 | * title (i.e., we're inside a <TITLE> tag's contents). We'll copy |
| | 1629 | * characters to the title buffer rather than the normal output |
| | 1630 | * buffer, and then call os_set_title() when we reach the </TITLE> |
| | 1631 | * tag. This is a counter that keeps track of the nesting depth of |
| | 1632 | * <TITLE> tags. |
| | 1633 | */ |
| | 1634 | int html_in_title_; |
| | 1635 | |
| | 1636 | /* buffer for the title */ |
| | 1637 | char *html_title_buf_; |
| | 1638 | size_t html_title_buf_size_; |
| | 1639 | |
| | 1640 | /* pointer to next available character in title buffer */ |
| | 1641 | char *html_title_ptr_; |
| | 1642 | |
| | 1643 | /* |
| | 1644 | * quoting level - this is a counter that keeps track of the nesting |
| | 1645 | * depth of <Q> tags |
| | 1646 | */ |
| | 1647 | int html_quote_level_; |
| | 1648 | |
| | 1649 | /* |
| | 1650 | * Parsing mode flag for ALT attributes. If we're parsing a tag |
| | 1651 | * that allows ALT, such as IMG or SOUND, we'll set this flag, then |
| | 1652 | * insert the ALT text if we encounter it during parsing. |
| | 1653 | */ |
| | 1654 | unsigned int html_allow_alt_ : 1; |
| | 1655 | |
| | 1656 | /* parsing mode flag for COLOR attributes */ |
| | 1657 | unsigned int html_allow_color_ : 1; |
| | 1658 | |
| | 1659 | /* parsing a BODY tag's attributes */ |
| | 1660 | unsigned int html_in_body_ : 1; |
| | 1661 | |
| | 1662 | /* parsing a TAB tag's attributes */ |
| | 1663 | unsigned int html_in_tab_ : 1; |
| | 1664 | |
| | 1665 | /* parsing a WRAP tag's attributes */ |
| | 1666 | unsigned int html_in_wrap_ : 1; |
| | 1667 | |
| | 1668 | /* hash table of <TAB> objects */ |
| | 1669 | class CVmHashTable *tabs_; |
| | 1670 | |
| | 1671 | /* characteristics of TAB tag we're defining */ |
| | 1672 | class CVmFmtTabStop *new_tab_entry_; |
| | 1673 | vmfmt_tab_align_t new_tab_align_; |
| | 1674 | wchar_t new_tab_dp_; |
| | 1675 | |
| | 1676 | /* |
| | 1677 | * Characteristics of pending tab. We must handle this tab when we |
| | 1678 | * reach the next <TAB> or the end of the current line. If |
| | 1679 | * pending_tab_align_ is VMFMT_TAB_NONE, it indicates that there is no |
| | 1680 | * pending tab (since a pending tab always requires alignment). |
| | 1681 | */ |
| | 1682 | vmfmt_tab_align_t pending_tab_align_; |
| | 1683 | class CVmFmtTabStop *pending_tab_entry_; |
| | 1684 | wchar_t pending_tab_dp_; |
| | 1685 | |
| | 1686 | /* |
| | 1687 | * starting column of pending tab - this is the output column where we |
| | 1688 | * were writing when the pending tab was encountered, so this is the |
| | 1689 | * column where we insert spaces for the tab |
| | 1690 | */ |
| | 1691 | int pending_tab_start_; |
| | 1692 | |
| | 1693 | /* |
| | 1694 | * color/attributes active at start of pending tab - this is the color |
| | 1695 | * we'll use for the spaces we insert when we insert the tab |
| | 1696 | */ |
| | 1697 | vmcon_color_t pending_tab_color_; |
| | 1698 | |
| | 1699 | /* |
| | 1700 | * Flag: this is a display stream. Other types of streams (such as |
| | 1701 | * log file streams) should set this to false. |
| | 1702 | */ |
| | 1703 | unsigned int is_disp_stream_ : 1; |
| | 1704 | }; |
| | 1705 | |
| | 1706 | /* ------------------------------------------------------------------------ */ |
| | 1707 | /* |
| | 1708 | * Formatter for display windows |
| | 1709 | */ |
| | 1710 | class CVmFormatterDisp: public CVmFormatter |
| | 1711 | { |
| | 1712 | public: |
| | 1713 | CVmFormatterDisp(class CVmConsole *console) |
| | 1714 | : CVmFormatter(console) |
| | 1715 | { |
| | 1716 | } |
| | 1717 | |
| | 1718 | /* initialize */ |
| | 1719 | void init() |
| | 1720 | { |
| | 1721 | /* inherit base class initialization */ |
| | 1722 | CVmFormatter::init(); |
| | 1723 | |
| | 1724 | /* |
| | 1725 | * if we're compiled for HTML mode, set the standard output |
| | 1726 | * stream so that it knows it has an HTML target - this will |
| | 1727 | * ensure that HTML tags are passed through to the underlying |
| | 1728 | * stream, and that we generate HTML equivalents for our own |
| | 1729 | * control sequences |
| | 1730 | */ |
| | 1731 | html_target_ = get_init_html_target(); |
| | 1732 | |
| | 1733 | /* |
| | 1734 | * since we always use HTML mode, turn on HTML mode in the |
| | 1735 | * underlying OS window if our underlying OS renderer is HTML-aware |
| | 1736 | */ |
| | 1737 | if (html_target_) |
| | 1738 | start_html_in_os(); |
| | 1739 | } |
| | 1740 | }; |
| | 1741 | |
| | 1742 | /* ------------------------------------------------------------------------ */ |
| | 1743 | /* |
| | 1744 | * Formatter subclass for the main display |
| | 1745 | */ |
| | 1746 | class CVmFormatterMain: public CVmFormatterDisp |
| | 1747 | { |
| | 1748 | public: |
| | 1749 | CVmFormatterMain(class CVmConsole *console, size_t html_title_buf_size) |
| | 1750 | : CVmFormatterDisp(console) |
| | 1751 | { |
| | 1752 | /* allocate a title buffer */ |
| | 1753 | html_title_buf_size_ = html_title_buf_size; |
| | 1754 | if (html_title_buf_size_ != 0) |
| | 1755 | html_title_buf_ = (char *)t3malloc(html_title_buf_size_); |
| | 1756 | } |
| | 1757 | |
| | 1758 | ~CVmFormatterMain() |
| | 1759 | { |
| | 1760 | /* delete our title buffer */ |
| | 1761 | if (html_title_buf_ != 0) |
| | 1762 | t3free(html_title_buf_); |
| | 1763 | } |
| | 1764 | |
| | 1765 | /* initialize */ |
| | 1766 | void init() |
| | 1767 | { |
| | 1768 | /* inherit base class initialization */ |
| | 1769 | CVmFormatterDisp::init(); |
| | 1770 | |
| | 1771 | /* remember the OS line wrap setting from the console */ |
| | 1772 | os_line_wrap_ = get_os_line_wrap(); |
| | 1773 | } |
| | 1774 | |
| | 1775 | /* set the window title in the OS layer */ |
| | 1776 | virtual void set_title_in_os(const char *txt) |
| | 1777 | { |
| | 1778 | /* set the window title */ |
| | 1779 | os_set_title(txt); |
| | 1780 | } |
| | 1781 | |
| | 1782 | protected: |
| | 1783 | /* |
| | 1784 | * Determine if the main console uses OS-level line wrapping - if this |
| | 1785 | * is returns true, then an output formatter on this console will not |
| | 1786 | * insert a newline at the end of a line that it's flushing for word |
| | 1787 | * wrapping, but will instead let the underlying OS display layer |
| | 1788 | * handle the wrapping. |
| | 1789 | * |
| | 1790 | * The OS line wrapping status is a PERMANENT feature of the console, |
| | 1791 | * so it is safe for the formatter to query this during initialization |
| | 1792 | * and cache the value. |
| | 1793 | */ |
| | 1794 | static int get_os_line_wrap(); |
| | 1795 | |
| | 1796 | /* turn HTML mode on/off in the underlying OS-level renderer */ |
| | 1797 | virtual void start_html_in_os(); |
| | 1798 | virtual void end_html_in_os(); |
| | 1799 | |
| | 1800 | /* display text directly to the OS renderer */ |
| | 1801 | virtual void print_to_os(const char *txt) |
| | 1802 | { |
| | 1803 | /* display the text on the primary OS console */ |
| | 1804 | os_printz(txt); |
| | 1805 | } |
| | 1806 | |
| | 1807 | /* flush the underlying OS-level renderer */ |
| | 1808 | virtual void flush_to_os() { os_flush(); } |
| | 1809 | |
| | 1810 | /* |
| | 1811 | * set text attributes for subsequent text directly in the underlying |
| | 1812 | * OS window |
| | 1813 | */ |
| | 1814 | virtual void set_os_text_attr(int attr) |
| | 1815 | { |
| | 1816 | /* if the target isn't in 'plain' mode, set the attributes */ |
| | 1817 | if (!plain_text_target_) |
| | 1818 | os_set_text_attr(attr); |
| | 1819 | } |
| | 1820 | |
| | 1821 | /* set the text color in the underlying OS window */ |
| | 1822 | virtual void set_os_text_color(os_color_t fg, os_color_t bg) |
| | 1823 | { |
| | 1824 | /* |
| | 1825 | * if the target is in 'plain' mode, don't use colors; otherwise, |
| | 1826 | * ask the console to do the work |
| | 1827 | */ |
| | 1828 | if (!plain_text_target_) |
| | 1829 | os_set_text_color(fg, bg); |
| | 1830 | } |
| | 1831 | |
| | 1832 | /* set the "body" color in the underlying OS window */ |
| | 1833 | virtual void set_os_body_color(os_color_t color) |
| | 1834 | { |
| | 1835 | /* if not in "plain" mode, set the color */ |
| | 1836 | if (!plain_text_target_) |
| | 1837 | os_set_screen_color(color); |
| | 1838 | } |
| | 1839 | |
| | 1840 | }; |
| | 1841 | |
| | 1842 | /* ------------------------------------------------------------------------ */ |
| | 1843 | /* |
| | 1844 | * Formatter subclass for the status line |
| | 1845 | */ |
| | 1846 | class CVmFormatterStatline: public CVmFormatterDisp |
| | 1847 | { |
| | 1848 | public: |
| | 1849 | CVmFormatterStatline(class CVmConsole *console) |
| | 1850 | : CVmFormatterDisp(console) |
| | 1851 | { |
| | 1852 | } |
| | 1853 | |
| | 1854 | /* we never use 'more' mode in the status line */ |
| | 1855 | virtual int formatter_more_mode() const { return FALSE; } |
| | 1856 | |
| | 1857 | /* HTML mode has no effect on the status line */ |
| | 1858 | virtual void start_html_in_os() { } |
| | 1859 | virtual void end_html_in_os() { } |
| | 1860 | |
| | 1861 | /* text colors and attributes are not used in the status line */ |
| | 1862 | virtual void set_os_text_color(os_color_t, os_color_t) { } |
| | 1863 | virtual void set_os_body_color(os_color_t) { } |
| | 1864 | virtual void set_os_text_attr(int) { } |
| | 1865 | |
| | 1866 | /* text displayed in the status line goes directly to the main console */ |
| | 1867 | virtual void print_to_os(const char *txt) { os_printz(txt); } |
| | 1868 | |
| | 1869 | /* flushing the status line simply flushes the main text stream */ |
| | 1870 | virtual void flush_to_os() { os_flush(); } |
| | 1871 | |
| | 1872 | /* titles have no effect in the status line */ |
| | 1873 | virtual void set_title_in_os(const char *) { } |
| | 1874 | }; |
| | 1875 | |
| | 1876 | /* ------------------------------------------------------------------------ */ |
| | 1877 | /* |
| | 1878 | * Formatter subclass for banner windows |
| | 1879 | */ |
| | 1880 | class CVmFormatterBanner: public CVmFormatterDisp |
| | 1881 | { |
| | 1882 | public: |
| | 1883 | CVmFormatterBanner(void *banner, class CVmConsole *console, |
| | 1884 | int win_type, unsigned long style) |
| | 1885 | : CVmFormatterDisp(console) |
| | 1886 | { |
| | 1887 | /* remember my OS banner handle */ |
| | 1888 | banner_ = banner; |
| | 1889 | |
| | 1890 | /* remember my window type */ |
| | 1891 | win_type_ = win_type; |
| | 1892 | |
| | 1893 | /* remember the banner style */ |
| | 1894 | style_ = style; |
| | 1895 | } |
| | 1896 | |
| | 1897 | /* initialize */ |
| | 1898 | void init_banner(int os_line_wrap, int obey_whitespace, int literal_mode) |
| | 1899 | { |
| | 1900 | /* inherit base class initialization */ |
| | 1901 | CVmFormatterDisp::init(); |
| | 1902 | |
| | 1903 | /* remember the OS line wrapping mode in our underlying OS window */ |
| | 1904 | os_line_wrap_ = os_line_wrap; |
| | 1905 | |
| | 1906 | /* remember the whitespace setting */ |
| | 1907 | obey_whitespace_ = obey_whitespace; |
| | 1908 | |
| | 1909 | /* remember the literal-mode setting */ |
| | 1910 | literal_mode_ = literal_mode; |
| | 1911 | } |
| | 1912 | |
| | 1913 | /* set the window title in the OS layer (does nothing for a banner) */ |
| | 1914 | virtual void set_title_in_os(const char *) { } |
| | 1915 | |
| | 1916 | /* reset the MORE prompt line counter */ |
| | 1917 | virtual void reset_line_count(int clearing) |
| | 1918 | { |
| | 1919 | /* |
| | 1920 | * To ensure we always keep a line of context when we page-forward |
| | 1921 | * from a MORE prompt, start the line counter at 2. Note that we |
| | 1922 | * do this in banner windows, but not in the main window, because - |
| | 1923 | * for historical reasons - the OS layer tells us the *paging* size |
| | 1924 | * for the main window, but the *actual* height for banner window. |
| | 1925 | * Note that we don't want to handle this adjustment in our own |
| | 1926 | * page length calculation, because doing so would cause the |
| | 1927 | * *first* MORE prompt (from a cleared window) to show up too |
| | 1928 | * early. |
| | 1929 | * |
| | 1930 | * When we're clearing the screen, reset to zero, since we have no |
| | 1931 | * context to retain. |
| | 1932 | */ |
| | 1933 | linecnt_ = (clearing ? 0 : 1); |
| | 1934 | } |
| | 1935 | |
| | 1936 | protected: |
| | 1937 | /* |
| | 1938 | * Use MORE mode in a banner window if we have the MORE-mode banner |
| | 1939 | * window style, AND the base display banner would use MORE mode. The |
| | 1940 | * latter check tests to see if we handle MORE mode at the OS level or |
| | 1941 | * in the formatter. |
| | 1942 | */ |
| | 1943 | virtual int formatter_more_mode() const |
| | 1944 | { |
| | 1945 | return ((style_ & OS_BANNER_STYLE_MOREMODE) != 0 |
| | 1946 | && CVmFormatterDisp::formatter_more_mode()); |
| | 1947 | } |
| | 1948 | |
| | 1949 | /* display text directly to the OS renderer */ |
| | 1950 | virtual void print_to_os(const char *txt) |
| | 1951 | { |
| | 1952 | /* display the text on our OS banner window */ |
| | 1953 | os_banner_disp(banner_, txt, strlen(txt)); |
| | 1954 | } |
| | 1955 | |
| | 1956 | /* turn HTML mode on/off in the underlying OS-level renderer */ |
| | 1957 | virtual void start_html_in_os() { os_banner_start_html(banner_); } |
| | 1958 | virtual void end_html_in_os() { os_banner_end_html(banner_); } |
| | 1959 | |
| | 1960 | /* set the text color */ |
| | 1961 | virtual void set_os_text_color(os_color_t fg, os_color_t bg) |
| | 1962 | { |
| | 1963 | /* set the color in the banner */ |
| | 1964 | os_banner_set_color(banner_, fg, bg); |
| | 1965 | } |
| | 1966 | |
| | 1967 | /* set the body color */ |
| | 1968 | virtual void set_os_body_color(os_color_t color) |
| | 1969 | { |
| | 1970 | /* set the color in the banner */ |
| | 1971 | os_banner_set_screen_color(banner_, color); |
| | 1972 | } |
| | 1973 | |
| | 1974 | /* set text attributes */ |
| | 1975 | virtual void set_os_text_attr(int attr) |
| | 1976 | { |
| | 1977 | /* set the attributes on the underlying OS-level banner */ |
| | 1978 | os_banner_set_attr(banner_, attr); |
| | 1979 | } |
| | 1980 | |
| | 1981 | /* flush the underlying OS-level renderer */ |
| | 1982 | virtual void flush_to_os() |
| | 1983 | { |
| | 1984 | /* flush the OS banner window */ |
| | 1985 | os_banner_flush(banner_); |
| | 1986 | } |
| | 1987 | |
| | 1988 | /* determine if the target supports HTML */ |
| | 1989 | virtual int get_init_html_target() const |
| | 1990 | { |
| | 1991 | /* if we're a text grid, the underlying window does not use HTML */ |
| | 1992 | if (win_type_ == OS_BANNER_TYPE_TEXTGRID) |
| | 1993 | return FALSE; |
| | 1994 | |
| | 1995 | /* defer to the inherited determination in other cases */ |
| | 1996 | return CVmFormatterDisp::get_init_html_target(); |
| | 1997 | } |
| | 1998 | |
| | 1999 | /* my OS banner handle */ |
| | 2000 | void *banner_; |
| | 2001 | |
| | 2002 | /* my OS window type (OS_BANNER_TYPE_xxx) */ |
| | 2003 | int win_type_; |
| | 2004 | |
| | 2005 | /* banner style (a combination of OS_BANNER_STLE_xxx bit flags) */ |
| | 2006 | unsigned long style_; |
| | 2007 | }; |
| | 2008 | |
| | 2009 | /* ------------------------------------------------------------------------ */ |
| | 2010 | /* |
| | 2011 | * Formatter subclass for the log file |
| | 2012 | */ |
| | 2013 | class CVmFormatterLog: public CVmFormatter |
| | 2014 | { |
| | 2015 | friend class CVmConsole; |
| | 2016 | friend class CVmConsoleLog; |
| | 2017 | |
| | 2018 | public: |
| | 2019 | CVmFormatterLog(class CVmConsole *console, int width) |
| | 2020 | : CVmFormatter(console) |
| | 2021 | { |
| | 2022 | /* we have no log file yet */ |
| | 2023 | logfname_ = 0; |
| | 2024 | logfp_ = 0; |
| | 2025 | |
| | 2026 | /* remember our width */ |
| | 2027 | width_ = width; |
| | 2028 | } |
| | 2029 | |
| | 2030 | ~CVmFormatterLog(); |
| | 2031 | |
| | 2032 | /* initialize */ |
| | 2033 | void init() |
| | 2034 | { |
| | 2035 | /* inherit base class initialization */ |
| | 2036 | CVmFormatter::init(); |
| | 2037 | |
| | 2038 | /* use plain text in the log file stream */ |
| | 2039 | plain_text_target_ = TRUE; |
| | 2040 | |
| | 2041 | /* we're not a display stream */ |
| | 2042 | is_disp_stream_ = FALSE; |
| | 2043 | |
| | 2044 | /* we're not an HTML formatter */ |
| | 2045 | html_target_ = FALSE; |
| | 2046 | |
| | 2047 | /* |
| | 2048 | * we use our own internal line wrapping, since our underlying |
| | 2049 | * display layer is simply dumping to a text file |
| | 2050 | */ |
| | 2051 | os_line_wrap_ = FALSE; |
| | 2052 | |
| | 2053 | /* no log file yet */ |
| | 2054 | logfp_ = 0; |
| | 2055 | } |
| | 2056 | |
| | 2057 | /* don't use MORE mode in a log stream */ |
| | 2058 | int formatter_more_mode() const { return FALSE; } |
| | 2059 | |
| | 2060 | /* get my width */ |
| | 2061 | virtual int get_buffer_maxcol() const { return width_; } |
| | 2062 | |
| | 2063 | protected: |
| | 2064 | /* log streams do not support HTML at the OS level */ |
| | 2065 | virtual void start_html_in_os() { } |
| | 2066 | virtual void end_html_in_os() { } |
| | 2067 | |
| | 2068 | /* set the attributes in the underlying stream */ |
| | 2069 | virtual void set_os_text_attr(int) |
| | 2070 | { |
| | 2071 | /* log streams are plain text - they don't support attributes */ |
| | 2072 | } |
| | 2073 | |
| | 2074 | /* set the color in the underlying stream */ |
| | 2075 | virtual void set_os_text_color(os_color_t, os_color_t) |
| | 2076 | { |
| | 2077 | /* log streams are plain text - they don't support colors */ |
| | 2078 | } |
| | 2079 | |
| | 2080 | /* set the body color */ |
| | 2081 | virtual void set_os_body_color(os_color_t) |
| | 2082 | { |
| | 2083 | /* log streams are plain text - they don't support colors */ |
| | 2084 | } |
| | 2085 | |
| | 2086 | /* display text to the underlying OS device */ |
| | 2087 | virtual void print_to_os(const char *txt) |
| | 2088 | { |
| | 2089 | /* display the text through the log file */ |
| | 2090 | if (logfp_ != 0) |
| | 2091 | os_fprintz(logfp_, txt); |
| | 2092 | } |
| | 2093 | |
| | 2094 | /* flush the underlying OS-level rendere */ |
| | 2095 | virtual void flush_to_os() { } |
| | 2096 | |
| | 2097 | /* set the window title in the OS layer - no effect for log streams */ |
| | 2098 | virtual void set_title_in_os(const char *) { } |
| | 2099 | |
| | 2100 | /* open a log file */ |
| | 2101 | int open_log_file(const char *fname); |
| | 2102 | |
| | 2103 | /* set the log file to a file previously opened */ |
| | 2104 | int set_log_file(const char *fname, osfildef *fp); |
| | 2105 | |
| | 2106 | /* close the log file */ |
| | 2107 | int close_log_file(); |
| | 2108 | |
| | 2109 | /* |
| | 2110 | * Process a <nolog> tag. Since we're a log stream, we hide the |
| | 2111 | * contents of this tag. |
| | 2112 | */ |
| | 2113 | virtual void process_nolog_tag(int is_end_tag) |
| | 2114 | { |
| | 2115 | /* turn hiding on or off as appropriate */ |
| | 2116 | if (is_end_tag) |
| | 2117 | --html_in_ignore_; |
| | 2118 | else |
| | 2119 | ++html_in_ignore_; |
| | 2120 | } |
| | 2121 | |
| | 2122 | /* |
| | 2123 | * Process a <log> tag. Since we're a log stream, we show the contents |
| | 2124 | * of this tag, so we can simply parse and ignore it. |
| | 2125 | */ |
| | 2126 | virtual void process_log_tag(int /*is_end_tag*/) |
| | 2127 | { |
| | 2128 | } |
| | 2129 | |
| | 2130 | /* my log file handle */ |
| | 2131 | osfildef *logfp_; |
| | 2132 | |
| | 2133 | /* log file name */ |
| | 2134 | char *logfname_; |
| | 2135 | |
| | 2136 | /* the maximum width to use for our lines */ |
| | 2137 | int width_; |
| | 2138 | }; |
| | 2139 | |
| | 2140 | |
| | 2141 | #endif /* VMCONSOL_H */ |