summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--FL/Fl_Terminal.H1013
-rw-r--r--documentation/Doxyfile.in1
-rw-r--r--documentation/Makefile3
-rw-r--r--documentation/src/Fl_Terminal-24bit-colors.pngbin0 -> 63524 bytes
-rw-r--r--documentation/src/Fl_Terminal-3bit-colors.pngbin0 -> 59419 bytes
-rw-r--r--documentation/src/Fl_Terminal-demo.pngbin0 -> 48352 bytes
-rw-r--r--documentation/src/Fl_Terminal-utf8-demo.pngbin0 -> 37477 bytes
-rw-r--r--documentation/src/Fl_Terminal.dox514
-rw-r--r--examples/simple-terminal.cxx12
-rw-r--r--src/CMakeLists.txt1
-rw-r--r--src/Fl_Terminal.cxx3508
-rw-r--r--src/Makefile1
-rw-r--r--src/README-Fl_Terminal.txt586
-rw-r--r--src/makedepend22
-rw-r--r--test/.gitignore3
-rw-r--r--test/CMakeLists.txt3
-rw-r--r--test/Makefile8
-rw-r--r--test/browser.cxx6
-rw-r--r--test/contrast.cxx8
-rw-r--r--test/demo.cxx12
-rw-r--r--test/file_chooser.cxx7
-rw-r--r--test/input.cxx6
-rw-r--r--test/input_choice.cxx6
-rw-r--r--test/makedepend77
-rw-r--r--test/menubar.cxx6
-rw-r--r--test/native-filechooser.cxx6
-rw-r--r--test/table.cxx6
-rw-r--r--test/terminal.fl1972
-rw-r--r--test/tree.fl4
-rw-r--r--test/unittest_core.cxx7
-rw-r--r--test/unittest_simple_terminal.cxx121
-rw-r--r--test/unittest_terminal.cxx98
-rw-r--r--test/unittests.cxx4
-rw-r--r--test/unittests.h4
-rw-r--r--test/valuators.fl10
35 files changed, 7818 insertions, 217 deletions
diff --git a/FL/Fl_Terminal.H b/FL/Fl_Terminal.H
new file mode 100644
index 000000000..4b7039f0c
--- /dev/null
+++ b/FL/Fl_Terminal.H
@@ -0,0 +1,1013 @@
+//
+// Fl_Terminal.cxx - A terminal widget for Fast Light Tool Kit (FLTK).
+//
+// Copyright 2022 by Greg Ercolano.
+// Copyright 2023 by Bill Spitzak and others.
+//
+// This library is free software. Distribution and use rights are outlined in
+// the file "COPYING" which should have been included with this file. If this
+// file is missing or damaged, see the license at:
+//
+// https://www.fltk.org/COPYING.php
+//
+// Please see the following page on how to report bugs and issues:
+//
+// https://www.fltk.org/bugs.php
+//
+
+/** \file
+ Fl_Terminal widget.
+*/
+
+#ifndef Fl_Terminal_H
+#define Fl_Terminal_H
+
+#include <FL/Fl.H>
+#include <FL/Fl_Window.H>
+#include <FL/Fl_Group.H>
+#include <FL/Fl_Scrollbar.H>
+#include <FL/Fl_Rect.H>
+
+/** \class Fl_Terminal
+
+ \brief Terminal widget supporting Unicode/utf-8, ANSI/xterm escape codes with full RGB color control.
+
+ \section Fl_Terminal
+
+ \image html Fl_Terminal-demo.png "Fl_Terminal widget showing a linux manual page"
+ \image latex Fl_Terminal-demo.png "Fl_Terminal widget showing a linux manual page" width=6cm
+
+ Fl_Terminal is an output-only text widget supporting ASCII and UTF-8/Unicode.
+ It supports most terminal text features, such as most VT100/xterm style escape sequences
+ (see \ref Fl_Terminal_escape_codes), text colors/attributes, scrollback history, mouse selection, etc.
+
+ It is recommended that accessing features such as setting text colors and cursor positioning
+ is best done with ANSI/XTERM escape sequences. But if one sets ansi(false) then this is
+ not possible. Many commonly used API functions are public, such as textfgcolor() for setting
+ text colors. Others, such as cursor_up() are protected to prevent common misuse, and are
+ available only to subclasses.
+
+ For applications that need input support, the widget can be subclassed to provide
+ keyboard input, and advanced features like pseudo ttys, termio, serial port I/O, etc.,
+ as such features are beyond the scope of FLTK.
+
+ \subsection Fl_Terminal_Examples Examples
+
+ \par
+ \code
+ //
+ // Fl_Terminal: Simple Use
+ //
+ Fl_Terminal *tty = new Fl_Terminal(...);
+ :
+ tty->append("Hello world.\n"); // simple strings
+ tty->append("\033[31mThis text is red.\033[0m\n"); // colored text
+ tty->append("\033[32mThis text is green.\033[0m\n");
+ tty->printf("The value of x is %.02f\n", x); // printf() formatting
+ \endcode
+ \par
+ There are also public methods for doing what most "\033[" escape codes do,
+ so that if ansi(bool) is set to "false", one can still change text colors
+ or clear the screen via application control, e.g.
+ \par
+ \code
+ tty->home(); // home the cursor
+ tty->clear_screen(); // clear the screen
+ tty->textfgcolor(0xff000000); // change the text color to RED
+ tty->textbgcolor(0x0000ff00); // change the background color to BLUE
+ // ├┘├┘├┘
+ // R G B
+ \endcode
+ \par
+ When creating the widget, the width/height determine the default column
+ and row count for the terminal's display based on the current font size.
+ The column width determines where text will wrap.
+ \par
+ You can specify wider column sizes than the screen using
+ display_columns(colwidth). When this value is larger than
+ the widget's width, text will wrap off-screen, and can be revealed by
+ resizing the widget wider.
+
+ \subsection Fl_Terminal_Writing Writing To Terminal From Applications
+
+ \par
+ An application needing terminal output as part of its user interface
+ can instance Fl_Terminal, and write text strings with:
+ \par
+ - append() to append strings
+ - printf() to append formatted strings
+ \par
+ Single character output can be done with:
+ \par
+ - print_char() to print a single ASCII/UTF-8 char at the cursor
+ - putchar() to put single ASCII/UTF-8 char at an x,y position
+ \par
+
+ \subsection Fl_Terminal_Attributes Text Attributes
+ \par
+ The terminal's text supports these attributes:
+ \par
+ - Italic - italicized text: <TT>\\033[3m</TT>
+ - Bold - brighter/thicker text: <TT>\\033[1m</TT>
+ - Dim - lower brightness text: <TT>\\033[2m</TT>
+ - Underline - text that is underlined: <TT>\\033[4m</TT>
+ - Strikeout - text that has a line through the text: <TT>\\033[9m</TT>
+ - Inverse - text whose background and foreground colors are swapped: <TT>\\033[7m</TT>
+ - Normal - normal text: <TT>\\033[0m</TT>
+ \par
+ \image html Fl_Terminal-utf8-demo.png "Fl_Terminal screen"
+ \image latex Fl_Terminal-utf8-demo.png "Fl_Terminal screen" width=6cm
+
+ \subsection Fl_Terminal_Colors Text and Background Colors
+
+ \par
+ There's at least two ways to specify colors for text and background colors:
+ \par
+ - 3 bit / 8 Color Values
+ - Full 24 bit R/G/B colors
+ \par
+ Example of 3 bit colors:
+ \image html Fl_Terminal-3bit-colors.png "Fl_Terminal 3 bit colors"
+ \image latex Fl_Terminal-3bit-colors.png "Fl_Terminal 3 bit colors" width=6cm
+ \par
+ Example application source code using 3 bit colors:
+ \code
+ //
+ // Text colors
+ //
+ tty->append("\033[31m Red text.\033[0m\n"); // Print red text..
+ tty->append("\033[32m Green text.\033[0m\n");
+ :
+ tty->append("\033[36m Cyan text.\033[0m\n");
+ tty->append("\033[37m White text.\033[0m\n");
+ //
+ // Background colors
+ //
+ tty->append("\033[41m Red Background.\033[0m\n"); // background will be red
+ tty->append("\033[42m Green Background.\033[0m\n");
+ :
+ tty->append("\033[46m Cyan Background.\033[0m\n");
+ tty->append("\033[47m White Background.\033[0m\n");
+ \endcode
+ \par
+ Example of 24 bit colors:
+ \image html Fl_Terminal-24bit-colors.png "Fl_Terminal 24 bit colors"
+ \image latex Fl_Terminal-24bit-colors.png "Fl_Terminal 24 bit colors" width=6cm
+ \par
+ Example application source code using 24 bit colors:
+ \code
+ //
+ // 24 bit Text Color
+ //
+ tty->append("\033[38;2;0;0;255m Text is BLUE.\033[0m\n"); // RGB: R=0, G=0, B=255
+ tty->append("\033[38;2;255;0;0m Text is RED.\033[0m\n"); // RGB: R=255, G=0, B=0
+ tty->append("\033[38;2;127;64;0m Text is DARK ORANGE.\033[0m\n"); // RGB: R=127, G=64, B=0
+ //
+ // 24 bit Background Color
+ //
+ tty->append("\033[48;2;0;0;255m Background is BLUE.\033[0m\n"); // RGB: R=0, G=0, B=255
+ tty->append("\033[48;2;255;0;0m Background is RED.\033[0m\n"); // RGB: R=255, G=0, B=0
+ tty->append("\033[48;2;127;64;0m Background is DARK ORANGE.\033[0m\n"); // RGB: R=127, G=64, B=0
+ \endcode
+ \par
+ For more on the ANSI escape codes, see \ref Fl_Terminal_escape_codes.
+
+ \subsection Fl_Terminal_Features Features
+
+ \par
+ Most standard terminal behaviors are supported, e.g.
+ \par
+ - ASCII + UTF-8/Unicode
+ - scrollback history management
+ - mouse selection + copy/paste (^C, ^A)
+ - autoscroll during selection
+ \par
+ Most popular ANSI/DEC VT100/Xterm escape sequences are supported (see \ref Fl_Terminal_escape_codes), including:
+ - per-character colors for text and background
+ - per-character text attributes: bold/dim, underline, strikeout
+ - scrolling up/down
+ - character insert/delete for characters/rows/screen
+ - clearing characters/rows/screen
+ \par
+ Does not (yet) support:
+ - programmable regions (scroll regions and attribute blocks)
+ - dynamic line wrap (where resizing display dynamically re-wraps long lines)
+ \par
+ Will likely never implement as part of this widget:
+ - pty/termio management (such features should be _subclassed_)
+ - Different per-character font family + sizes (font family/size is global only)
+ - variable width fonts
+ \par
+ Regarding the font family+size; the way the terminal is currently designed,
+ the font family and size must not vary within text; rows have to be consistent
+ height. Varying widths are tricky too, esp. when it comes to moving the cursor
+ up/down within a column; varying *widths* are supported (due to Unicode characters
+ sometimes being "wide", but not heights.
+
+ \subsection Fl_Terminal_Margins Margins
+
+ \par
+ The margins define the amount of space (in pixels) around the outside of the
+ text display area, the space between the widget's inner edge (inside the box())
+ and the text display area's outer edge. The margins can be inspected and changed
+ with the margin_left(), margin_right(), margin_top() and margin_bottom() methods.
+ \par
+ \code
+ TERMINAL WIDGET (Fl_Terminal)
+ ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━┓
+ ┃ ↑ margin_top ┃ ┃
+ ┃ TEXT DISPLAY AREA ↓ ┃ ┃
+ ┃ ┌──────────────────────────────────────────────────────┐ ┃ ┃
+ ┃ │ ↑ │ ┃ S ┃
+ ┃ │ │ │ ┃ c ┃
+ ┃ │ display_rows │ ┃ r ┃
+ ┃ │ │ │ ┃ o ┃
+ ┃ │ │ │ ┃ l ┃
+ ┃ │ │ │ ┃ l ┃
+ ┃ │◄───display_columns────────┼─────────────────────────►│ ┃ ┃
+ ┃ │ │ │ ┃ B ┃
+ ┃ │ │ │ ┃ a ┃
+ ┃ │ │ │ ┃ r ┃
+ ┃ │ │ │ ┃ ┃
+ ┃ │ │ │ ┃ ┃
+ ┃◄──┬──►│ ↓ │◄──┬──►┃ ┃
+ ┃ │ └──────────────────────────────────────────────────────┘ │ ┃ ┃
+ ┃ margin_left ↑ margin_right ┃ ┃
+ ┃ ↓ margin_bottom ┃ ┃
+ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┻━━━┛
+ Fl_Terminal Margins
+ \endcode
+
+ \subsection Fl_Terminal_Caveats Caveats
+
+ \par
+ - This widget is not a full terminal emulator; it does not do stdio redirection,
+ pseudo ttys/termios/character cooking, keyboard input processing, full
+ curses(3) support. However, such features CAN be implemented with subclassing.
+ \par
+ - The printf() and vprintf() functions are limited to strings no longer than
+ 1024 characters (including NULL). For printing longer strings, use append()
+ which has no string limits.
+**/
+
+class Fl_Terminal : public Fl_Group {
+public:
+ //////////////////////////////////////
+ ////// Fl_Terminal Public Enums //////
+ //////////////////////////////////////
+
+ /**
+ \enum RedrawStyle
+ Determines when Fl_Terminal calls redraw() if new text is added.
+ RATE_LIMITED is the recommended setting, using redraw_rate(float) to determine
+ the maximum rate of redraws.
+ \see redraw_style(), redraw_rate()
+ */
+ enum RedrawStyle {
+ NO_REDRAW=0, ///< app must call redraw() as needed to update text to screen
+ RATE_LIMITED, ///< timer controlled redraws. (DEFAULT)
+ PER_WRITE ///< redraw triggered after *every* append() / printf() / etc. operation
+ };
+
+ /**
+ \enum Attrib
+ Bits for the per-character attributes, which control text features
+ such as italic, bold, underlined text, etc.
+
+ NOTE: enum names with a leading underbar must not be used, and are
+ reserved for future use.
+ */
+ enum Attrib {
+ NORMAL = 0x00, ///< all attributes off
+ BOLD = 0x01, ///< bold text: uses bold font, color brighter than normal
+ DIM = 0x02, ///< dim text; color slightly darker than normal
+ ITALIC = 0x04, ///< italic font text
+ UNDERLINE = 0x08, ///< underlined text
+ _RESERVED_1 = 0x10, ///< <EM>(reserved for internal future use)</EM>
+ INVERSE = 0x20, ///< inverse text; fg/bg color are swapped
+ _RESERVED_2 = 0x40, ///< <EM>(reserved for internal future use)</EM>
+ STRIKEOUT = 0x80 ///< strikeout text
+ };
+
+ /**
+ \enum CharFlags
+ Per-character 8 bit flags (uchar) used to manage special states for characters.
+ */
+ enum CharFlags {
+ FG_XTERM = 0x01, ///< this char's fg color is an XTERM color; can be affected by Dim+Bold
+ BG_XTERM = 0x02, ///< this char's bg color is an XTERM color; can be affected by Dim+Bold
+ EOL = 0x04, ///< this char at end of line, used for line wrap during screen resizing
+ RESV_A = 0x08,
+ RESV_B = 0x10,
+ RESV_C = 0x20,
+ RESV_D = 0x40,
+ RESV_E = 0x80,
+ COLORMASK = (FG_XTERM | BG_XTERM)
+ };
+
+protected:
+ // Margin Class ////////////////////////////////////////////
+ //
+ // Class to manage the terminal's margins
+ //
+ class Margin {
+ int left_, right_, top_, bottom_;
+ public:
+ Margin(void) { left_ = right_ = top_ = bottom_ = 3; }
+ int left(void) const { return left_; }
+ int right(void) const { return right_; }
+ int top(void) const { return top_; }
+ int bottom(void) const { return bottom_; }
+ void left(int val) { left_ = val; }
+ void right(int val) { right_ = val; }
+ void top(int val) { top_ = val; }
+ void bottom(int val) { bottom_ = val; }
+ };
+
+ // CharStyle Class ////////////////////////////////////////////
+ //
+ // Class to manage the terminal's character style
+ // This includes the font, color, and some cached internal
+ // info for optimized drawing speed.
+ //
+ class CharStyle {
+ uchar attrib_; // bold, underline..
+ uchar flags_; // CharFlags
+ Fl_Color fgcolor_; // foreground color for text
+ Fl_Color bgcolor_; // background color for text
+ Fl_Color defaultfgcolor_; // default fg color used by ESC[0m
+ Fl_Color defaultbgcolor_; // default bg color used by ESC[0m
+ Fl_Font fontface_; // font face
+ Fl_Fontsize fontsize_; // font size
+ int fontheight_; // font height (in pixels)
+ int fontdescent_; // font descent (pixels below font baseline)
+ int charwidth_; // width of a fixed width ASCII character
+ public:
+ CharStyle(void);
+ Fl_Color fgcolor(void) const;
+ Fl_Color bgcolor(void) const;
+ Fl_Color defaultfgcolor(void) const { return defaultfgcolor_; }
+ Fl_Color defaultbgcolor(void) const { return defaultbgcolor_; }
+ uchar attrib(void) const { return attrib_; }
+ Fl_Font fontface(void) const { return fontface_; }
+ Fl_Fontsize fontsize(void) const { return fontsize_; }
+ int fontheight(void) const { return fontheight_; }
+ int fontdescent(void) const { return fontdescent_; }
+ int charwidth(void) const { return charwidth_; }
+ uchar colorbits_only(uchar inflags) const;
+ void attrib(uchar val) { attrib_ = val; }
+ void set_flag(uchar val) { flags_ |= val; }
+ void clr_flag(uchar val) { flags_ &= ~val; }
+ void fgcolor_uchar(uchar val);
+ void bgcolor_uchar(uchar val);
+ void fgcolor(int r,int g,int b) { fgcolor_ = (r<<24) | (g<<16) | (b<<8); clr_flag(FG_XTERM); }
+ void bgcolor(int r,int g,int b) { bgcolor_ = (r<<24) | (g<<16) | (b<<8); clr_flag(BG_XTERM); }
+ void fgcolor(Fl_Color val) { fgcolor_ = val; clr_flag(FG_XTERM); }
+ void bgcolor(Fl_Color val) { bgcolor_ = val; clr_flag(BG_XTERM); }
+ void defaultfgcolor(Fl_Color val) { defaultfgcolor_ = val; }
+ void defaultbgcolor(Fl_Color val) { defaultbgcolor_ = val; }
+ void fontface(Fl_Font val) { fontface_ = val; update(); }
+ void fontsize(Fl_Fontsize val) { fontsize_ = val; update(); }
+ void update(void);
+ // SGR MODES: Set Graphics Rendition
+ void sgr_reset(void) { // e.g. ESC[0m
+ attrib(Fl_Terminal::NORMAL);
+ fgcolor(defaultfgcolor_);
+ bgcolor(defaultbgcolor_);
+ }
+ int onoff(bool flag, Attrib a) { return (flag ? (attrib_ | a) : (attrib_ & ~a)); }
+ void sgr_bold(bool val) { attrib_ = onoff(val, Fl_Terminal::BOLD); } // e.g. ESC[1m
+ void sgr_dim(bool val) { attrib_ = onoff(val, Fl_Terminal::DIM); } // e.g. ESC[2m
+ void sgr_italic(bool val) { attrib_ = onoff(val, Fl_Terminal::ITALIC); } // e.g. ESC[3m
+ void sgr_underline(bool val) { attrib_ = onoff(val, Fl_Terminal::UNDERLINE); } // e.g. ESC[3m
+ void sgr_dbl_under(bool val) { attrib_ = onoff(val, Fl_Terminal::UNDERLINE); } // e.g. ESC[21m (TODO!)
+ void sgr_blink(bool val) { /* NOT IMPLEMENTED */ } // e.g. ESC[5m
+ void sgr_inverse(bool val) { attrib_ = onoff(val, Fl_Terminal::INVERSE); } // e.g. ESC[7m
+ void sgr_strike(bool val) { attrib_ = onoff(val, Fl_Terminal::STRIKEOUT); } // e.g. ESC[9m
+ };
+
+protected:
+ // Cursor Class ///////////////////////////////////////////////////////////
+ //
+ // Class to manage the terminal's cursor position, color, etc.
+ //
+ class Cursor {
+ int col_; // cursor's current col (x) position on display
+ int row_; // cursor's current row (y) position on display
+ int h_; // cursor's height (affected by font size)
+ Fl_Color fgcolor_; // cursor's fg color (color of text, if any)
+ Fl_Color bgcolor_; // cursor's bg color
+ public:
+ Cursor(void) {
+ col_ = 0;
+ row_ = 0;
+ h_ = 10;
+ fgcolor_ = 0xfffff000; // wht
+ bgcolor_ = 0x00d00000; // grn
+ }
+ int col(void) const { return col_; }
+ int row(void) const { return row_; }
+ int h(void) const { return h_; }
+ Fl_Color fgcolor(void) const { return fgcolor_; }
+ Fl_Color bgcolor(void) const { return bgcolor_; }
+ void col(int val) { col_ = val >= 0 ? val : 0; }
+ void row(int val) { row_ = val >= 0 ? val : 0; }
+ void h(int val) { h_ = val; }
+ void fgcolor(Fl_Color val) { fgcolor_ = val; }
+ void bgcolor(Fl_Color val) { bgcolor_ = val; }
+ int left(void) { col_ = (col_>0) ? (col_-1) : 0; return col_; }
+ int right(void) { return ++col_; }
+ int up(void) { row_ = (row_>0) ? (row_-1) : 0; return row_; }
+ int down(void) { return ++row_; }
+ bool is_rowcol(int drow,int dcol) const;
+ void scroll(int nrows);
+ void home(void) { row_ = 0; col_ = 0; }
+ };
+
+ // Utf8Char Class ///////////////////////////////////////////////////////////
+ //
+ // Class to manage the terminal's individual UTF-8 characters.
+ // Includes fg/bg color, attributes (BOLD, UNDERLINE..)
+ //
+ class Utf8Char {
+ static const int max_utf8_ = 4; // RFC 3629 paraphrased: In UTF-8, chars are encoded with 1 to 4 octets
+ char text_[max_utf8_]; // memory for actual ASCII or UTF-8 byte contents
+ uchar len_; // length of bytes in text_[] buffer; 1 for ASCII, >1 for UTF-8
+ uchar attrib_; // attribute bits for this char (bold, underline..)
+ uchar flags_; // CharFlags bits
+ Fl_Color fgcolor_; // fltk fg color (supports 8color or 24bit color set w/ESC[37;<r>;<g>;<b>m)
+ Fl_Color bgcolor_; // fltk bg color (supports 8color or 24bit color set w/ESC[47;<r>;<g>;<b>m)
+ // Private methods
+ void text_utf8_(const char *text, int len);
+ public:
+ // Public methods
+ Utf8Char(void); // ctor
+ Utf8Char(const Utf8Char& o); // copy ctor
+ ~Utf8Char(void); // dtor
+ Utf8Char& operator=(const Utf8Char& o); // assignment
+ inline int max_utf8() const { return max_utf8_; }
+ void text_utf8(const char *text, int len, const CharStyle& style);
+ void text_ascii(char c, const CharStyle& style);
+ void fl_font_set(const CharStyle& style) const;
+
+ // Return the UTF-8 text string for this character.
+ // Use length() to get number of bytes in string, which will be 1 for ASCII chars.
+ //
+ const char* text_utf8(void) const { return text_; }
+ // Return the attribute for this char
+ uchar attrib(void) const { return attrib_; }
+ uchar flags(void) const { return flags_; }
+ Fl_Color fgcolor(void) const;
+ Fl_Color bgcolor(void) const;
+ // Return the length of this character in bytes (UTF-8 can be multibyte..)
+ int length(void) const { return int(len_); }
+ double pwidth(void) const;
+ int pwidth_int(void) const;
+ // Clear the character to a 'space'
+ void clear(const CharStyle& style) { text_ascii(' ', style); }
+ bool is_char(char c) const { return *text_ == c; }
+ void show_char(void) const { ::printf("%.*s", len_, text_); }
+ void show_char_info(void) const { ::fprintf(stderr, "UTF-8('%.*s', len=%d)\n", len_, text_, len_); }
+
+ Fl_Color attr_color(Fl_Color col, const Fl_Widget *grp) const;
+ Fl_Color attr_fg_color(const Fl_Widget *grp) const;
+ Fl_Color attr_bg_color(const Fl_Widget *grp) const;
+ };
+
+ // RingBuffer Class ///////////////////////////////////////////////////
+ //
+ // Manages ring with indexed row/col and "history" vs. "display" concepts.
+ //
+ class RingBuffer {
+ Utf8Char *ring_chars_; // the ring UTF-8 char buffer
+ int ring_rows_; // #rows in ring total
+ int ring_cols_; // #columns in ring/hist/disp
+ int nchars_; // #chars in ring (ring_rows*ring_cols)
+ int hist_rows_; // #rows in history
+ int hist_use_; // #rows in use by history
+ int disp_rows_; // #rows in display
+ int offset_; // index offset (used for 'scrolling')
+
+private:
+ void new_copy(int drows, int dcols, int hrows, const CharStyle& style);
+ //DEBUG void write_row(FILE *fp, Utf8Char *u8c, int cols) const {
+ //DEBUG cols = (cols != 0) ? cols : ring_cols();
+ //DEBUG for ( int col=0; col<cols; col++, u8c++ ) {
+ //DEBUG ::fprintf(fp, "%.*s", u8c->length(), u8c->text_utf8());
+ //DEBUG }
+ //DEBUG }
+ public:
+ void clear(void);
+ void clear_hist(void);
+ RingBuffer(void);
+ RingBuffer(int drows, int dcols, int hrows);
+ ~RingBuffer(void);
+
+ // Methods to access ring
+ //
+ // The 'offset' concept allows the 'history' and 'display'
+ // to be scrolled indefinitely. The 'offset' is applied
+ // to all the row accesses, and are clamped to within their bounds.
+ //
+ // For 'raw' access to the ring (without the offset concept),
+ // use the ring_chars() method, and walk from 0 - ring_rows().
+ //
+ // _____________
+ // | | <- hist_srow() <- ring_srow()
+ // | H i s t |
+ // | |
+ // |_____________| <- hist_erow()
+ // | | <- disp_srow()
+ // | D i s p |
+ // | |
+ // |_____________| <- disp_erow() <- ring_erow()
+ //
+ // \___________/
+ // ring_cols()
+ // hist_cols()
+ // disp_cols()
+ //
+ inline int ring_rows(void) const { return ring_rows_; }
+ inline int ring_cols(void) const { return ring_cols_; }
+ inline int ring_srow(void) const { return(0); }
+ inline int ring_erow(void) const { return(ring_rows_ - 1); }
+ inline int hist_rows(void) const { return hist_rows_; }
+ inline int hist_cols(void) const { return ring_cols_; }
+ inline int hist_srow(void) const { return((offset_ + 0) % ring_rows_); }
+ inline int hist_erow(void) const { return((offset_ + hist_rows_ - 1) % ring_rows_); }
+ inline int disp_rows(void) const { return disp_rows_; }
+ inline int disp_cols(void) const { return ring_cols_; }
+ inline int disp_srow(void) const { return((offset_ + hist_rows_) % ring_rows_); }
+ inline int disp_erow(void) const { return((offset_ + hist_rows_ + disp_rows_ - 1) % ring_rows_); }
+ inline int offset(void) const { return offset_; }
+ // History use
+ inline int hist_use(void) const { return hist_use_; }
+ inline void hist_use(int val) { hist_use_ = val; }
+ inline int hist_use_srow(void) const { return((offset_ + hist_rows_ - hist_use_) % ring_rows_); }
+ inline Utf8Char *ring_chars(void) { return ring_chars_; } // access ring buffer directly
+ inline Utf8Char *ring_chars(void) const { return ring_chars_; } // access ring buffer directly
+
+ bool is_hist_ring_row(int grow) const;
+ bool is_disp_ring_row(int grow) const;
+ //DEBUG void show_ring_info(void) const;
+ void move_disp_row(int src_row, int dst_row);
+ void clear_disp_row(int drow, const CharStyle& style);
+ void scroll(int rows, const CharStyle& style);
+
+ const Utf8Char* u8c_ring_row(int row) const;
+ const Utf8Char* u8c_hist_row(int hrow) const;
+ const Utf8Char* u8c_hist_use_row(int hurow) const;
+ const Utf8Char* u8c_disp_row(int drow) const;
+
+ // Non-const versions of the above methods
+ // Using "Effective C++" ugly-as-hell syntax technique.
+ //
+ Utf8Char* u8c_ring_row(int row);
+ Utf8Char* u8c_hist_row(int hrow);
+ Utf8Char* u8c_hist_use_row(int hurow);
+ Utf8Char* u8c_disp_row(int drow);
+
+ void create(int drows, int dcols, int hrows);
+ void resize(int drows, int dcols, int hrows, const CharStyle& style);
+
+ void change_disp_rows(int drows, const CharStyle& style);
+ void change_disp_cols(int dcols, const CharStyle& style);
+ };
+
+ // Selection Class ///////////////////////////////////////////////////
+ //
+ // Class to manage mouse selection
+ //
+ class Selection {
+ int srow_, scol_, erow_, ecol_; // selection start/end. NOTE: start *might* be > end
+ int push_row_, push_col_; // global row/col for last FL_PUSH
+ Fl_Color selectionbgcolor_;
+ Fl_Color selectionfgcolor_;
+ int state_ ; // 0=none, 1=started, 2=extended, 3=done
+ bool is_selection_; // false: no selection
+ public:
+ Selection(void);
+ int srow(void) const { return srow_; }
+ int scol(void) const { return scol_; }
+ int erow(void) const { return erow_; }
+ int ecol(void) const { return ecol_; }
+ void push_clear() { push_row_ = push_col_ = -1; }
+ void push_rowcol(int row,int col) { push_row_ = row; push_col_ = col; }
+ void start_push() { start(push_row_, push_col_); }
+ bool dragged_off(int row,int col) { return (push_row_ != row) || (push_col_ != col); }
+ void selectionfgcolor(Fl_Color val) { selectionfgcolor_ = val; }
+ void selectionbgcolor(Fl_Color val) { selectionbgcolor_ = val; }
+ Fl_Color selectionfgcolor(void) const { return selectionfgcolor_; }
+ Fl_Color selectionbgcolor(void) const { return selectionbgcolor_; }
+ bool is_selection(void) const { return is_selection_; }
+ bool get_selection(int &srow,int &scol,int &erow,int &ecol) const; // guarantees return (start < end)
+ bool start(int row, int col);
+ bool extend(int row, int col);
+ void end(void);
+ void select(int srow, int scol, int erow, int ecol);
+ bool clear(void);
+ int state(void) const { return state_; }
+ void scroll(int nrows);
+ };
+
+ // EscapeSeq Class ///////////////////////////////////////////////////
+ //
+ // Class to handle parsing ESC sequences
+ //
+ // Holds all state information for parsing esc sequences,
+ // so sequences can span multiple block read(2) operations, etc.
+ // Handling of parsed sequences is NOT handled in this class,
+ // just the parsing of the sequences and managing generic integers.
+ //
+ class EscapeSeq {
+ public:
+ // EscapeSeq Constants
+ // Maximums
+ static const int maxbuff = 80; // character buffer
+ static const int maxvals = 20; // integer value buffer
+ // Return codes
+ static const int success = 0; // operation succeeded
+ static const int fail = -1; // operation failed
+ static const int completed = 1; // multi-step operation completed successfully
+ private:
+ char esc_mode_; // escape parsing mode state
+ char csi_; // This is an ESC[.. sequence (Ctrl Seq Introducer)
+ char buff_[maxbuff]; // escape sequence being parsed
+ char *buffp_; // parsing ptr into buff[]
+ char *buffendp_; // end of buff[] (ptr to last valid buff char)
+ char *valbuffp_; // pointer to first char in buff of integer being parsed
+ int vals_[maxvals]; // value array for parsing #'s in ESC[#;#;#..
+ int vali_; // parsing index into vals_[], 0 if none
+ int save_row_, save_col_; // used by ESC[s/u for save/restore
+
+ int append_buff(char c);
+ int append_val(void);
+
+ public:
+ EscapeSeq(void);
+ void reset(void);
+ char esc_mode(void) const;
+ void esc_mode(char val);
+ int total_vals(void) const;
+ int val(int i) const;
+ int defvalmax(int dval, int max) const;
+ bool parse_in_progress(void) const;
+ bool is_csi(void) const;
+ int parse(char c);
+ void save_cursor(int row, int col);
+ void restore_cursor(int &row, int &col);
+ };
+
+ // Partial UTF-8 Buffer Class ////////////////////////////////////////////
+ //
+ // Class to manage buffering partial UTF-8 characters between write calls.
+ //
+ class PartialUtf8Buf {
+ char buf_[10]; // buffer partial UTF-8 encoded char
+ int buflen_; // length of buffered UTF-8 encoded char
+ int clen_; // final byte length of a UTF-8 char
+ public:
+ void clear(void) { buflen_ = clen_ = 0; } // clear the buffer
+ PartialUtf8Buf(void) { clear(); } // Ctor
+ // Is byte 'c' in the middle of a UTF-8 encoded byte sequence?
+ bool is_continuation(char c) {
+ // Byte 1 Byte 2 Byte 3 ..etc..
+ // ASCII: 0xxxxxxx
+ // UTF8(2): 110xxxxx 10xxxxxx
+ // UTF8(3): 1110xxxx 10xxxxxx 10xxxxxx
+ // UTF8(4): 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
+ // UTF8(5): 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
+ // UTF8(6): 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
+ // \______/ \______________________________________________/
+ // Start byte Continuation bytes
+ // (c & 0xc0) == 0x80
+ return ((c & 0xc0) == 0x80);
+ }
+ // Access buffer
+ const char* buf(void) const { return buf_; }
+ // Access buffer length
+ int buflen(void) const { return buflen_; }
+ // Append bytes of a partial UTF-8 string to the buffer.
+ //
+ // Returns:
+ // - true if done OK. Use is_complete() to see if a complete char received.
+ // - false if buffer overrun occurred, class is clear()ed.
+ //
+ // An appropriate response to 'false' would be to print the
+ // "unknown character" and skip all subsequent UTF-8 continuation chars.
+ //
+ bool append(const char* p, int len) {
+ if (len <= 0) return true; // ignore silly requests: say we did but dont
+ if (buflen_ + len >= (int)sizeof(buf_)) // overrun check
+ { clear(); return false; } // clear self, return false
+ if (!buflen_) clen_ = fl_utf8len(*p); // first byte? save char len for later
+ while (len>0) { buf_[buflen_++] = *p++; len--; } // append byte to buffer
+ return true;
+ }
+ bool is_complete(void) const { return (buflen_ && (buflen_ == clen_)); }
+ };
+
+private:
+ // Fl_Terminal Members
+ Fl_Scrollbar *vscroll_; // vertical scrollbar (value: rows above disp_chars[])
+ int scrollbar_size_; // local preference for scrollbar size
+ CharStyle current_style_; // current font, attrib, color..
+
+ // A ring buffer is used for the terminal's history (hist) and display (disp) buffer.
+ // See README-Fl_Terminal.txt, section "RING BUFFER DESCRIPTION" for diagrams/info.
+ //
+ // Ring buffer
+ RingBuffer ring_; // terminal history/display ring buffer
+ Cursor cursor_; // terminal cursor (position, color, etc)
+ Margin margin_; // terminal margins (top,left,bottom,right)
+ Selection select_; // mouse selection
+ EscapeSeq escseq; // Escape sequence parsing (ESC[ xterm/vt100)
+ bool show_unknown_; // if true, show unknown chars as '¿' (default off)
+ bool ansi_; // if true, parse ansi codes (default on)
+ char *tabstops_; // array of tab stops (0|1) \__ TODO: This should probably
+ int tabstops_size_; // size of tabstops[] array / be a class "TabStops".
+ Fl_Rect scrn_; // terminal screen xywh inside box(), margins, and vscroll
+ int autoscroll_dir_; // 0=autoscroll timer off, 3=scrolling up, 4=scrolling down
+ int autoscroll_amt_; // #pixels above or below edge, used for autoscroll speed
+ RedrawStyle redraw_style_; // NO_REDRAW, RATE_LIMITED, PER_WRITE
+ float redraw_rate_; // maximum redraw rate in seconds, default=0.10
+ bool redraw_modified_; // display modified; used by update_cb() to rate limit redraws
+ bool redraw_timer_; // if true, redraw timer is running
+ PartialUtf8Buf pub_; // handles Partial Utf8 Buffer (pub)
+
+protected:
+ // Ring buffer management
+ const Utf8Char* u8c_ring_row(int grow) const;
+ const Utf8Char* u8c_hist_row(int hrow) const;
+ const Utf8Char* u8c_hist_use_row(int hrow) const;
+ const Utf8Char* u8c_disp_row(int drow) const;
+ // non-const versions of the above.
+ // "Effective C++" says: implement non-const method to cast away const
+ //
+ Utf8Char* u8c_ring_row(int grow);
+ Utf8Char* u8c_hist_row(int hrow);
+ Utf8Char* u8c_hist_use_row(int hurow);
+ Utf8Char* u8c_disp_row(int drow);
+private:
+ void create_ring(int drows, int dcols, int hrows);
+protected:
+ Utf8Char* u8c_cursor(void);
+private:
+ int vscroll_width(void) const;
+ // Tabstops
+ void init_tabstops(int newsize);
+ void default_tabstops(void);
+ void clear_all_tabstops(void);
+ void set_tabstop(void);
+ void clear_tabstop(void);
+ // Updates
+ void update_screen_xywh(void);
+ void update_screen(bool font_changed);
+ void update_scrollbar(void);
+ // Resize
+ void resize_display_rows(int drows);
+ void resize_display_columns(int dcols);
+ void refit_disp_to_screen(void);
+ // Callbacks
+ static void scrollbar_cb(Fl_Widget*, void*); // scrollbar manipulation
+ static void autoscroll_timer_cb(void*); // mouse drag autoscroll
+ void autoscroll_timer_cb2(void);
+ static void redraw_timer_cb(void*); // redraw rate limiting timer
+ void redraw_timer_cb2(void);
+
+ // Screen management
+protected:
+ const CharStyle& current_style(void) const;
+ void current_style(const CharStyle& sty);
+ int x_to_glob_col(int X, int grow, int &gcol) const;
+ int xy_to_glob_rowcol(int X, int Y, int &grow, int &gcol) const;
+ int w_to_col(int W) const;
+ int h_to_row(int H) const;
+ // API: Display clear operations
+ void clear_screen(bool scroll_to_hist=true);
+ void clear_screen_home(bool scroll_to_hist=true);
+ void clear_sod(void);
+ void clear_eod(void);
+ void clear_eol(void);
+ void clear_sol(void);
+ void clear_line(int row);
+ void clear_line(void);
+ const Utf8Char* walk_selection(const Utf8Char *u8c, int &row, int &col) const;
+ bool get_selection(int &srow,int &scol,int &erow,int &ecol) const;
+ bool is_selection(void) const;
+ bool is_inside_selection(int row,int col) const;
+ bool is_hist_ring_row(int grow) const;
+ bool is_disp_ring_row(int grow) const;
+ int selection_text_len(void) const;
+ const char* selection_text(void) const;
+ void clear_mouse_selection(void);
+ bool selection_extend(int X,int Y);
+ void scroll(int rows);
+ void insert_rows(int count);
+ void delete_rows(int count);
+ void insert_char_eol(char c, int drow, int dcol, int rep);
+ void insert_char(char c, int rep);
+ void delete_chars(int drow, int dcol, int rep);
+ void delete_chars(int rep);
+ // History
+ void history_use(int val, bool update=true);
+public:
+ // API: Terminal operations
+ void clear_history(void); // ESC [ 3 J
+ void reset_terminal(void); // ESC c
+protected:
+ // Cursor management
+ int cursor_h(void) const;
+public:
+ // API: Cursor
+ void cursorfgcolor(Fl_Color val);
+ void cursorbgcolor(Fl_Color val);
+ Fl_Color cursorfgcolor(void) const;
+ Fl_Color cursorbgcolor(void) const;
+protected:
+ void cursor_row(int row);
+ void cursor_col(int col);
+public:
+ int cursor_row(void) const;
+ int cursor_col(void) const;
+protected:
+ void cursor_up(int count=1, bool do_scroll=false);
+ void cursor_down(int count=1, bool do_scroll=false);
+ void cursor_left(int count=1);
+ void cursor_right(int count=1, bool do_scroll=false);
+ void cursor_home(void);
+ void cursor_eol(void);
+ void cursor_sol(void);
+ void cursor_cr(void);
+ void cursor_crlf(int count=1);
+ void cursor_tab_right(int count=1);
+ void cursor_tab_left(int count=1);
+ void save_cursor(void);
+ void restore_cursor(void);
+ // Printing
+ void handle_ctrl(char c);
+ bool is_printable(char c);
+ bool is_ctrl(char c);
+ void handle_SGR(void);
+ void handle_DECRARA(void);
+ void handle_escseq(char c);
+ // --
+ void display_modified(void);
+ void display_modified_clear(void);
+ void clear_char_at_disp(int drow, int dcol);
+ const Utf8Char* utf8_char_at_disp(int drow, int dcol) const;
+ const Utf8Char* utf8_char_at_glob(int grow, int gcol) const;
+ void repeat_char(char c, int rep);
+ void utf8_cache_clear(void);
+ void utf8_cache_flush(void);
+
+ // API: Character display output
+public:
+ void putchar(const char *text, int len, int drow, int dcol);
+ void putchar(char c, int drow, int dcol);
+ void print_char(const char *text, int len=-1);
+ void print_char(char c);
+ // API: String display output
+ void append_utf8(const char *buf, int len=-1);
+ void append_ascii(const char *s);
+ void append(const char *s, int len=-1);
+protected:
+ int handle_unknown_char(void);
+ // Drawing
+ void draw_row_bg(int grow, int X, int Y) const;
+ void draw_row(int grow, int Y) const;
+ void draw_buff(int Y) const;
+ void handle_selection_autoscroll(void);
+ int handle_selection(int e);
+public:
+ // FLTK: draw(), resize(), handle()
+ void draw(void) FL_OVERRIDE;
+ void resize(int X,int Y,int W,int H) FL_OVERRIDE;
+ int handle(int e) FL_OVERRIDE;
+
+protected:
+ // Internal short names
+ // Don't make these public, but allow internals and
+ // derived classes to maintain brevity.
+ //
+ inline int ring_rows(void) const { return ring_.ring_rows(); }
+ inline int ring_cols(void) const { return ring_.ring_cols(); }
+ inline int ring_srow(void) const { return ring_.ring_srow(); }
+ inline int ring_erow(void) const { return ring_.ring_erow(); }
+ inline int hist_rows(void) const { return ring_.hist_rows(); }
+ inline int hist_cols(void) const { return ring_.hist_cols(); }
+ inline int hist_srow(void) const { return ring_.hist_srow(); }
+ inline int hist_erow(void) const { return ring_.hist_erow(); }
+ inline int hist_use(void) const { return ring_.hist_use(); }
+ inline int hist_use_srow(void) const { return ring_.hist_use_srow(); }
+ inline int disp_rows(void) const { return ring_.disp_rows(); }
+ inline int disp_cols(void) const { return ring_.disp_cols(); }
+ inline int disp_srow(void) const { return ring_.disp_srow(); }
+ inline int disp_erow(void) const { return ring_.disp_erow(); }
+ inline int offset(void) const { return ring_.offset(); }
+
+ // TODO: CLEAN UP WHAT'S PUBLIC, AND WHAT SHOULD BE 'PROTECTED' AND 'PRIVATE'
+ // Some of the public stuff should, quite simply, "not be".
+
+ // API: Terminal features
+public:
+ // API: Scrollbar
+ int scrollbar_size(void) const;
+ void scrollbar_size(int val);
+ int scrollbar_actual_size(void) const;
+ // API: History
+ int history_rows(void) const;
+ void history_rows(int val);
+ int history_use(void) const;
+ // API: Display
+ int display_rows(void) const;
+ void display_rows(int val);
+ int display_columns(void) const;
+ void display_columns(int val);
+ // API: Box
+ /// Sets the box type, updates terminal margins et al. Default is FL_DOWN_FRAME.
+ ///
+ /// FL_XXX_FRAME types are handled in a special way by this widget, and guarantee
+ /// the background is a flat field.
+ ///
+ /// FL_XXX_BOX may draw gradients as inherited by Fl::scheme().
+ ///
+ void box(Fl_Boxtype val) { Fl_Group::box(val); update_screen(false); }
+ /// Returns the current box type.
+ Fl_Boxtype box(void) const { return Fl_Group::box(); }
+ // API: Margins
+ /// Return the left margin; see \ref Fl_Terminal_Margins.
+ int margin_left(void) const { return margin_.left(); }
+ /// Return the right margin; see \ref Fl_Terminal_Margins.
+ int margin_right(void) const { return margin_.right(); }
+ /// Return the top margin; see \ref Fl_Terminal_Margins.
+ int margin_top(void) const { return margin_.top(); }
+ /// Return the bottom margin; see \ref Fl_Terminal_Margins.
+ int margin_bottom(void) const { return margin_.bottom(); }
+ void margin_left(int val);
+ void margin_right(int val);
+ void margin_top(int val);
+ void margin_bottom(int val);
+ // API: Text font/size/color
+ void textfont(Fl_Font val);
+ void textsize(Fl_Fontsize val);
+ void textfgcolor(Fl_Color val);
+ void textbgcolor(Fl_Color val);
+ void textfgcolor_default(Fl_Color val);
+ void textbgcolor_default(Fl_Color val);
+ /// Return text font used to draw all text in the terminal.
+ Fl_Font textfont(void) const { return current_style_.fontface(); }
+ /// Return text font size used to draw all text in the terminal.
+ Fl_Fontsize textsize(void) const { return current_style_.fontsize(); }
+ /// Return text's current foreground color.
+ Fl_Color textfgcolor(void) const { return current_style_.fgcolor(); }
+ /// Return text's current background color.
+ Fl_Color textbgcolor(void) const { return current_style_.bgcolor(); }
+ /// Return text's default foreground color. \see textfgcolor()
+ Fl_Color textfgcolor_default(void) const { return current_style_.defaultfgcolor(); }
+ /// Return text's default background color. \see textbgcolor()
+ Fl_Color textbgcolor_default(void) const { return current_style_.defaultbgcolor(); }
+ void textfgcolor_xterm(uchar val);
+ void textbgcolor_xterm(uchar val);
+ /// Set mouse selection foreground color.
+ void selectionfgcolor(Fl_Color val) { select_.selectionfgcolor(val); }
+ /// Set mouse selection background color.
+ void selectionbgcolor(Fl_Color val) { select_.selectionbgcolor(val); }
+ /// Get mouse selection foreground color.
+ Fl_Color selectionfgcolor(void) const { return select_.selectionfgcolor(); }
+ /// Get mouse selection background color.
+ Fl_Color selectionbgcolor(void) const { return select_.selectionbgcolor(); }
+ // API: Text attrib
+ void textattrib(uchar val);
+ // API: Redraw style/rate
+ RedrawStyle redraw_style(void) const;
+ void redraw_style(RedrawStyle val);
+protected:
+ bool is_redraw_style(RedrawStyle val) { return redraw_style_ == val; }
+public:
+ float redraw_rate(void) const;
+ void redraw_rate(float val);
+ // API: Show unknown/unprintable chars
+ bool show_unknown(void) const;
+ void show_unknown(bool val);
+ // API: ANSI sequences
+ bool ansi(void) const;
+ void ansi(bool val);
+ // Fl_Simple_Terminal API compatibility
+ int history_lines(void) const;
+ void history_lines(int val);
+ // API: printf()
+ void printf(const char *fmt, ...);
+ void vprintf(const char *fmt, va_list ap);
+ // Ctor
+ Fl_Terminal(int X, int Y, int W, int H, const char*L=0);
+ // Dtor
+ ~Fl_Terminal(void);
+ // Debugging features
+//DEBUG void show_ring_info() const { ring_.show_ring_info(); }
+//DEBUG void write_row(FILE *fp, Utf8Char *u8c, int cols) const;
+//DEBUG void show_buffers(RingBuffer *a, RingBuffer *b=0) const;
+};
+#endif
diff --git a/documentation/Doxyfile.in b/documentation/Doxyfile.in
index bf88783f4..130dd1845 100644
--- a/documentation/Doxyfile.in
+++ b/documentation/Doxyfile.in
@@ -789,6 +789,7 @@ INPUT = @CMAKE_CURRENT_SOURCE_DIR@/src/index.dox \
@CMAKE_CURRENT_SOURCE_DIR@/src/coordinates.dox \
@CMAKE_CURRENT_SOURCE_DIR@/src/resize.dox \
@CMAKE_CURRENT_SOURCE_DIR@/src/editor.dox \
+ @CMAKE_CURRENT_SOURCE_DIR@/src/Fl_Terminal.dox \
@CMAKE_CURRENT_SOURCE_DIR@/src/drawing.dox \
@CMAKE_CURRENT_SOURCE_DIR@/src/events.dox \
@CMAKE_CURRENT_SOURCE_DIR@/src/subclassing.dox \
diff --git a/documentation/Makefile b/documentation/Makefile
index c75dd567c..5643ba118 100644
--- a/documentation/Makefile
+++ b/documentation/Makefile
@@ -48,7 +48,8 @@ HTMLFILES = \
$(SRC_DOCDIR)/development.dox \
$(SRC_DOCDIR)/license.dox \
$(SRC_DOCDIR)/examples.dox \
- $(SRC_DOCDIR)/faq.dox
+ $(SRC_DOCDIR)/faq.dox \
+ $(SRC_DOCDIR)/Fl_Terminal.dox
MANPAGES = $(SRC_DOCDIR)/fltk.$(CAT3EXT) $(SRC_DOCDIR)/fltk-config.$(CAT1EXT) \
$(SRC_DOCDIR)/fluid.$(CAT1EXT) $(SRC_DOCDIR)/blocks.$(CAT6EXT) \
diff --git a/documentation/src/Fl_Terminal-24bit-colors.png b/documentation/src/Fl_Terminal-24bit-colors.png
new file mode 100644
index 000000000..5e7f6adb1
--- /dev/null
+++ b/documentation/src/Fl_Terminal-24bit-colors.png
Binary files differ
diff --git a/documentation/src/Fl_Terminal-3bit-colors.png b/documentation/src/Fl_Terminal-3bit-colors.png
new file mode 100644
index 000000000..a0407738e
--- /dev/null
+++ b/documentation/src/Fl_Terminal-3bit-colors.png
Binary files differ
diff --git a/documentation/src/Fl_Terminal-demo.png b/documentation/src/Fl_Terminal-demo.png
new file mode 100644
index 000000000..43996ba72
--- /dev/null
+++ b/documentation/src/Fl_Terminal-demo.png
Binary files differ
diff --git a/documentation/src/Fl_Terminal-utf8-demo.png b/documentation/src/Fl_Terminal-utf8-demo.png
new file mode 100644
index 000000000..a04f11bf9
--- /dev/null
+++ b/documentation/src/Fl_Terminal-utf8-demo.png
Binary files differ
diff --git a/documentation/src/Fl_Terminal.dox b/documentation/src/Fl_Terminal.dox
new file mode 100644
index 000000000..2963218da
--- /dev/null
+++ b/documentation/src/Fl_Terminal.dox
@@ -0,0 +1,514 @@
+// vim:syntax=doxygen
+/**
+
+\page Fl_Terminal_Tech_Docs Fl_Terminal Technical Documentation
+
+This chapter covers the vt100/xterm style "escape codes" used by
+Fl_Terminal for cursor positioning, text colors, and other display
+screen control features such as full or partial screen clearing,
+up/down scrolling, character insert/delete, etc.
+
+\section Fl_Terminal_escape_codes The Escape Codes Fl_Terminal Supports
+
+These are the escape codes Fl_Terminal actually supports, and is not
+the 'complete' list that e.g. xterm supports. Most of the important stuff
+has been implemented, but esoteric features (such as scroll regions) has not.
+
+Features will be added as the widget matures.
+
+\code{.unparsed}
+│ --------------------------------------------------------
+│ --- The CSI (Control Sequence Introducer, or "ESC[") ---
+│ --------------------------------------------------------
+│
+│ ESC[#@ - (ICH) Insert blank Chars (default=1)
+│ ESC[#A - (CUU) Cursor Up, no scroll/wrap
+│ ESC[#B - (CUD) Cursor Down, no scroll/wrap
+│ ESC[#C - (CUF) Cursor Forward, no wrap
+│ ESC[#D - (CUB) Cursor Back, no wrap
+│ ESC[#E - (CNL) Cursor Next Line (crlf) xterm, !gnome
+│ ESC[#F - (CPL) Cursor Preceding Line: move to sol and up # lines
+│ ESC[#G - (CHA) Cursor Horizontal Absolute positioning
+│ │
+│ ├── ESC[G - move to column 1 (start of line, sol)
+│ └── ESC[#G - move to column #
+│
+│ ESC[#H - (CUP) Cursor Position (#'s are 1 based)
+│ │
+│ ├── ESC[H - go to row #1
+│ ├── ESC[#H - go to (row #) (default=1)
+│ └── ESC[#;#H - go to (row# ; col#)
+│
+│ ESC[#I - (CHT) Cursor Horizontal Tab: tab forward
+│ │
+│ └── ESC[#I - tab # times (default 1)
+│
+│ ESC[#J - (ED) Erase in Display
+│ │
+│ ├── ESC[0J - clear to end of display (default)
+│ ├── ESC[1J - clear to start of display
+│ ├── ESC[2J - clear all lines
+│ └── ESC[3J - clear screen history
+│
+│ ESC[#K - (EL) Erase in line
+│ │
+│ ├── ESC[0K - clear to end of line (default)
+│ ├── ESC[1K - clear to start of line
+│ └── ESC[2K - clear current line
+│
+│ ESC[#L - (IL) Insert # Lines (default=1)
+│ ESC[#M - (DL) Delete # Lines (default=1)
+│ ESC[#P - (DCH) Delete # Chars (default=1)
+│ ESC[#S - (SU) Scroll Up # lines (default=1)
+│ ESC[#T - (SD) Scroll Down # lines (default=1)
+│ ESC[#X - (ECH) Erase Characters (default=1)
+│
+│ ESC[#Z - (CBT) Cursor Backwards Tab
+│ │
+│ └── ESC[#Z - backwards tab # times (default=1)
+│
+│ ESC[#a - (HPR) move cursor relative [columns] (default=[row,col+1]) (NOT IMPLEMENTED)
+│ ESC[#b - (REP) repeat prev graphics char # times (NOT IMPLEMENTED)
+│ ESC[#d - (VPA) Line Position Absolute [row] (NOT IMPLEMENTED)
+│ ESC[#e - (LPA) Line Position Relative [row] (NOT IMPLEMENTED)
+│ ESC[#f - (CUP) cursor position (#'s 1 based), same as ESC[H
+│
+│ ESC[#g - (TBC)Tabulation Clear
+│ │
+│ ├── ESC[0g - Clear tabstop at cursor
+│ └── ESC[3g - Clear all tabstops
+│
+│ ESC[#m - (SGR) Set Graphic Rendition
+│ │
+│ │ *** Attribute Enable ***
+│ │
+│ ├── ESC[0m - reset: normal attribs/default fg/bg color (VT100)
+│ ├── ESC[1m - bold (VT100)
+│ ├── ESC[2m - dim
+│ ├── ESC[3m - italic
+│ ├── ESC[4m - underline (VT100)
+│ ├── ESC[5m - blink (NOT IMPLEMENTED) (VT100)
+│ ├── ESC[6m - (unused)
+│ ├── ESC[7m - inverse (VT100)
+│ ├── ESC[8m - (unused)
+│ ├── ESC[9m - strikeout
+│ ├── ESC[21m - doubly underline (Currently this just does single underline)
+│ │
+│ │ *** Attribute Disable ***
+│ │
+│ ├── ESC[22m - disable bold/dim
+│ ├── ESC[23m - disable italic
+│ ├── ESC[24m - disable underline
+│ ├── ESC[25m - disable blink (NOT IMPLEMENTED)
+│ ├── ESC[26m - (unused)
+│ ├── ESC[27m - disable inverse
+│ ├── ESC[28m - disable hidden
+│ ├── ESC[29m - disable strikeout
+│ │
+│ │ *** Foreground Text "8 Color" ***
+│ │
+│ ├── ESC[30m - fg Black
+│ ├── ESC[31m - fg Red
+│ ├── ESC[32m - fg Green
+│ ├── ESC[33m - fg Yellow
+│ ├── ESC[34m - fg Blue
+│ ├── ESC[35m - fg Magenta
+│ ├── ESC[36m - fg Cyan
+│ ├── ESC[37m - fg White
+│ ├── ESC[39m - fg default
+│ │
+│ │ *** Background Text "8 Color" ***
+│ │
+│ ├── ESC[40m - bg Black
+│ ├── ESC[41m - bg Red
+│ ├── ESC[42m - bg Green
+│ ├── ESC[43m - bg Yellow
+│ ├── ESC[44m - bg Blue
+│ ├── ESC[45m - bg Magenta
+│ ├── ESC[46m - bg Cyan
+│ ├── ESC[47m - bg White
+│ ├── ESC[49m - bg default
+│ │
+│ │ *** Special RGB Color ***
+│ │
+│ └── ESC [ 38 ; Red ; Grn ; Blue m - where Red,Grn,Blu are decimal (0-255)
+│
+│ ESC[s - save cursor pos (ansi.sys+xterm+gnome, but NOT vt100)
+│ ESC[u - rest cursor pos (ansi.sys+xterm+gnome, but NOT vt100)
+│
+│ ESC[>#q - (DECSCA) Set Cursor style (block/line/blink..) (NOT IMPLEMENTED)
+│ ESC[#;#r - (DECSTBM) Set scroll Region top;bot (NOT IMPLEMENTED)
+│ ESC[#..$t - (DECRARA) (NOT IMPLEMENTED)
+│
+│ ------------------------
+│ --- C1 Control Codes ---
+│ ------------------------
+│
+│ <ESC>c - (RIS) Reset term to Initial State
+│ <ESC>D - (IND) Index: move cursor down a line, scroll if at bottom
+│ <ESC>E - (NEL) Next Line: basically do a crlf, scroll if at bottom
+│ <ESC>H - (HTS) Horizontal Tab Set: set a tabstop
+│ <ESC>M - (RI) Reverse Index (up w/scroll)
+│
+│ NOTE: Acronyms in parens are Digital Equipment Corporation's names these VT features.
+│
+\endcode
+
+\section external_escape_codes Useful Terminal Escape Code Documentation
+
+Useful links for reference:
+
+ - https://vt100.net/docs/vt100-ug/chapter3.html
+ - https://www.xfree86.org/current/ctlseqs.html
+ - https://www.x.org/docs/xterm/ctlseqs.pdf
+ - https://gist.github.com/justinmk/a5102f9a0c1810437885a04a07ef0a91 <-- alphabetic!
+ - https://invisible-island.net/xterm/ctlseqs/ctlseqs.html
+
+\section Fl_Terminal_design Fl_Terminal Design Document
+
+When I started this project, I identified the key concepts needed to
+implement Fl_Terminal:
+
+- Draw and manage multiline Unicode text in FLTK
+- Allow per-character colors and attributes
+- Efficient screen buffer to handle "scrollback history"
+- Efficient scrolling with vertical scrollbar for even large screen history
+- Mouse selection for copy/paste
+- Escape code management to implement VT100 style / ANSI escape codes.
+
+A class was created for each character, since characters can be either ASCII
+or Utf8 encoded byte sequences. This class is called Utf8Char, and handles
+the character, its fg and bg color, and any attributes like dim, bold, italic, etc.
+
+For managing the screen, after various experiments, I decided a ring buffer
+was the best way to manage things, the ring split in two:
+
+- 'screen history' which is where lines scrolled off the top are saved
+- 'display screen' displayed to the user at all times, and where the cursor lives
+
+Scrolling the display, either by scrollbar or by new text causing the display
+to scroll up one line, would simply change an 'offset' index# of where in the
+ring buffer the top of the screen is, automatically moving the top line
+into the history, all without moving memory around.
+
+In fact the only time screen memory is moved around is during these infrequent
+operations:
+
+- during scrolling "down"
+- character insert/delete operations within a line
+- changing the display size
+- changing the history size
+
+So a class "RingBuffer" is defined to manage the ring, and accessing its various
+parts, either as the entire entity ring, just the history, or just the display.
+
+These three concepts, "ring", "history" and "display" are given abbreviated
+names in the RingBuffer class's API:
+
+ ┌─────────────────────────────────────────┬──────────────────────────────┐
+ │ NOTE: Abbreviations "hist" and "disp" │ │
+ ├─────────────────────────────────────────┘ │
+ │ │
+ │ "history" may be abbreviated as "hist", and "display" as "disp" in │
+ │ both this text and the source code. 4 character names are used so │
+ │ they line up cleanly in the source, e.g. │
+ │ │
+ │ ring_rows() ring_cols() │
+ │ hist_rows() hist_cols() │
+ │ disp_rows() disp_cols() │
+ │ └─┬┘ └─┬┘ └─┬┘ └─┬┘ │
+ │ └────┴──────────┴────┴───────── 4 characters │
+ │ │
+ └────────────────────────────────────────────────────────────────────────┘
+
+These concepts were able to fit into C++ classes:
+
+Utf8Char
+--------
+Each character on the screen is a "Utf8Char" which can manage
+the UTF-8 encoding of any character as one or more bytes. Also
+in that class is a byte for an attribute (underline, bold, etc),
+and two integers for fg/bg color.
+
+RingBuffer
+----------
+The RingBuffer class keeps track of the buffer itself, a single
+array of Utf8Chars called "ring_chars" whose width is ring_cols()
+and whose height is ring_rows().
+
+The "top" part of the ring is the history, whose width is hist_cols()
+and whose height is hist_rows(). hist_use_rows() is used to define
+what part of the history is currently in use.
+
+The "bottom" part of the ring is the display, whose width is disp_cols()
+and whose height is disp_rows().
+
+An index number called "offset" points to where in the ring buffer
+the top of the ring currently is. This index changes each time the
+screen is scrolled, and affects both where the top of the display is,
+and where the top of the history is.
+
+The memory layout of the Utf8Char character array is:
+
+ ring_chars[]:
+ ___________________ _ _
+ | | ʌ
+ | | |
+ | | |
+ | H i s t o r y | | hist_rows
+ | | |
+ | | |
+ |___________________| _v_
+ | | ʌ
+ | | |
+ | D i s p l a y | | disp_rows
+ | | |
+ |___________________| _v_
+
+ |<----------------->|
+ ring_cols
+ hist_cols
+ disp_cols
+
+So it's basically a single continuous array of Utf8Char instances
+where any character can generally be accessed by index# using the formula:
+
+ ring_chars[ (row*ring_cols)+col ]
+
+..where 'row' is the desired row, 'col' is the desired column,
+and 'ring_cols' is how many columns "wide" the buffer is.
+
+The "offset" index affects that formula as an extra row offset,
+and the resulting index is then clamped within the range of the
+ring buffer using modulus.
+
+Methods are used to allow direct access to the characters
+in the buffer that automatically handle the offset and modulus
+formulas, namely:
+
+ u8c_ring_row(row,col) // access the entire ring by row/col
+ u8c_hist_row(row,col) // access just the history buffer
+ u8c_disp_row(row,col) // access just the display buffer
+
+A key concept is the use of the simple 'offset' index integer
+to allow the starting point of the history and display to be
+moved around to implement 'text scrolling', such as when
+crlf at the screen bottom causes a 'scroll up'.
+
+This is simply an "index offset" integer applied to the
+hist and disp indexes when drawing the display. So after
+scrolling two lines up, the offset is just increased by 2,
+redefining where the top of the history and display are, e.g.
+
+ Offset is 0: 2 Offset now 2:
+ ┌───────────────────┐ ──┐ ┌───────────────────┐
+ │ │ │ │ D i s p l a y │
+ │ │ └─> ├───────────────────┤
+ │ │ │ │
+ │ H i s t o r y │ │ │
+ │ │ │ H i s t o r y │
+ │ │ 2 │ │
+ ├───────────────────┤ ──┐ │ │
+ │ │ │ │ │
+ │ │ └─> ├───────────────────┤
+ │ D i s p l a y │ │ │
+ │ │ │ D i s p l a y │
+ │ │ │ │
+ └───────────────────┘ └───────────────────┘
+
+This 'offset' trivially implements "text scrolling", avoiding having
+to physically move memory around. Just the 'offset' changes, the
+text remains where it is in memory.
+
+This also makes it appear the top line in the display is 'scrolled up'
+into the bottom of the scrollback 'history'.
+
+If the offset exceeds the size of the ring buffer, it simply wraps
+around back to the beginning of the buffer with a modulo.
+
+Indexes into the display and history are also modulo their respective
+rows, e.g.
+
+ act_ring_index = (hist_rows + disp_row + offset - scrollbar_pos) % ring_rows;
+
+This way indexes for ranges can run beyond the bottom of the ring,
+and automatically wrap around the ring, e.g.
+
+ ┌───────────────────┐
+ ┌─> 2 │ │
+ │ 3 │ D i s p l a y │
+ │ 4 │ │
+ │ ├───────────────────┤ <-- offset points here
+ │ │ │
+ disp │ │ │
+ index ┤ │ H i s t o r y │
+ wraps │ │ │
+ │ │ │
+ │ │ │
+ │ ├───────────────────┤
+ │ 0 │ D i s p l a y │
+ │ 1 └───────────────────┘ <- ring_rows points to end of ring
+ └── 2 : :
+ 3 : :
+ disp_row(5) -> 4 :...................:
+
+The dotted lines show where the display would be if not for the fact
+it extends beyond the bottom of the ring buffer (due to the current offset),
+and therefore wraps up to the top of the ring.
+
+So to find a particular row in the display, in this case a 5 line display
+whose lines lie between 0 and 4, some simple math calculates the row position
+into the ring:
+
+ act_ring_index = (histrows // the display exists AFTER the history, so offset the hist_rows
+ + offset // include the scroll 'offset'
+ + disp_row // add the desired row relative to the top of the display (0..disp_rows)
+ ) % ring_rows; // make sure the resulting index is within the ring buffer (0..ring_rows)
+
+An additional bit of math makes sure if a negative result occurs, that
+negative value works relative to the end of the ring, e.g.
+
+ if (act_ring_index < 0) act_ring_index = ring_rows + act_ring_index;
+
+This guarantees the act_ring_index is within the ring buffer's address space,
+with all offsets applied.
+
+The math that implements this can be found in the u8c_xxxx_row() methods,
+where "xxxx" is one of the concept regions "ring", "hist" or "disp":
+
+ Utf8Char *u8c;
+ u8c = u8c_ring_row(rrow); // address within ring, rrow can be 0..(ring_rows-1)
+ u8c = u8c_hist_row(hrow); // address within hist, hrow can be 0..(hist_rows-1)
+ u8c = u8c_disp_row(drow); // address within disp, drow can be 0..(disp_rows-1)
+
+The small bit of math is only involved whenever a new row address is needed,
+so in a display that's 80x25, to walk all the characters in the screen, the
+math above would only be called 25 times, once for each row, and each column
+in the row is just a simple integer offset:
+
+ for ( int row=0; row<disp_rows(); row++ ) { // walk rows: disp_rows = 25
+ Utf8Char *u8c = u8c_disp_row(row); // get first char in display 'row'
+ for ( int col=0; col<disp_cols(); col++ ) { // walk cols: disp_cols = 80
+ u8c[col].do_something(); // work with the char at row/col
+ }
+ }
+
+So to recap, the concepts here are:
+
+- The ring buffer itself, a linear array that is conceptually
+ split into a 2 dimensional array of rows and columns whose
+ height and width are:
+
+ ring_rows -- how many rows in the entire ring buffer
+ ring_cols -- how many columns in the ring buffer
+ nchars -- total chars in ring, e.g. (ring_rows * ring_cols)
+
+- The "history" within the ring. For simplicity this is thought of
+ as starting relative to the top of the ring buffer, occupying
+ ring buffer rows:
+
+ 0 .. hist_rows()-1
+
+- The "display", or "disp", within the ring, just after the "history".
+ It occupies the ring buffer rows:
+
+ hist_rows() .. hist_rows()+disp_rows()-1
+
+ ..or similarly:
+
+ (hist_rows)..(ring_rows-1)
+
+ The following convenience methods provide access to the
+ start and end indexes within the ring buffer for each entity:
+
+ // Entire ring
+ ring_srow() -- start row index of the ring buffer (always 0)
+ ring_erow() -- end row index of the ring buffer
+
+ // "history" part of ring
+ hist_srow() -- start row index of the screen history
+ hist_erow() -- end row index of the screen history
+
+ // "display" part of ring
+ disp_srow() -- start row index of the display
+ disp_erow() -- end row index of the display
+
+ The values returned by these are as described above.
+ For the hist_xxx() and disp_xxx() methods the 'offset' included into
+ the forumula. (For this reason hist_srow() won't always be zero
+ the way ring_srow() is, due to the 'offset')
+
+ The values returned by these methods can all be passed to the
+ u8c_ring_row() function to access the actual character buffer's contents.
+
+- An "offset" used to move the "history" and "display" around within
+ the ring buffer to implement the "text scrolling" concept. The offset
+ is applied when new characters are added to the buffer, and during
+ drawing to find where the display actually is within the ring.
+
+- The "scrollbar", which only is used when redrawing the screen the user sees,
+ and is simply an additional offset to all the above, where a scrollbar
+ value of zero (the scrollbar tab at the bottom) shows the display rows,
+ and as the scrollbar values increase as the user moves the scrollbar
+ tab upwards, +1 per line, this is subtracted from the normal starting
+ index to let the user work their way backwards into the scrollback history.
+ Again, negative numbers wrap around within the ring buffer automatically.
+
+The ring buffer allows new content to simply be appended to the ring buffer,
+and the index# for the start of the display and start of scrollback history are
+simply incremented. So the next time the display is "drawn", it starts at
+a different position in the ring.
+
+This makes scrolling content at high speed trivial, without memory moves.
+It also makes the concept of "scrolling" with the scrollbar simple as well,
+simply being an extra index offset applied during drawing.
+
+Mouse Selection
+---------------
+
+Dragging the mouse across the screen should highlight the text, allowing the user
+to extend the selection either beyond or before the point started. Extending the
+drag to the top of the screen should automatically 'scroll up' to select more
+lines in the scrollback history, or below the bottom to do the opposite.
+
+The mouse selection is implemented as a class to keep track of the start/end
+row/col positions of the selection, and other details such as a flag indicating
+if a selection has been made, what color the fg/bg text should appear when
+text is selected, and methods that allow setting and extending the selection,
+clearing the selection, and "scrolling" the selection, to ensure the row/col
+indexes adjust correctly to track when the screen or scrollbar is scrolled.
+
+
+Redraw Timer
+------------
+
+Knowing when to redraw is tricky with a terminal, because sometimes high volumes
+of input will come in asynchronously, so in that case we need to determine when
+to redraw the screen to show the new content; too quickly will cause the screen
+to spend more time redrawing itself, preventing new input from being added. Too
+slowly, the user won't see new information appear in a timely manner.
+
+To solve this, a rate timer is used to prevent too many redraws:
+
+- When new data comes in, a 1/10 sec timer is started and a modify flag is set.
+
+- redraw() is NOT called yet, allowing more data to continue to arrive quickly
+
+- When the 1/10th second timer fires, the callback checks the modify flag:
+
+ - if set, calls redraw(), resets the modify to 0, and calls
+ Fl::repeat_timeout() to repeat the callback in another 1/10th sec.
+
+ - if clear, no new data came in, so DISABLE the timer, done.
+
+In this way, redraws don't happen more than 10x per second, and redraw() is called
+only when there's new content to see.
+
+The redraw rate can be set by the user application using the Fl_Terminal::redraw_rate(),
+0.10 being the default.
+
+Some terminal operations necessarily call redraw() directly, such as interactive mouse
+selection, or during user scrolling the terminal's scrollbar, where it's important there's
+no delay in what the user sees while interacting directly with the widget.
+
+*/
diff --git a/examples/simple-terminal.cxx b/examples/simple-terminal.cxx
index f1b6e5d5e..f51dc1bb2 100644
--- a/examples/simple-terminal.cxx
+++ b/examples/simple-terminal.cxx
@@ -1,5 +1,5 @@
//
-// Simple Example app using Fl_Simple_Terminal. - erco 10/12/2017
+// Simple Example app using Fl_Terminal. - erco 10/12/2017
//
// Copyright 2017 Greg Ercolano.
// Copyright 1998-2016 by Bill Spitzak and others.
@@ -15,17 +15,17 @@
// https://www.fltk.org/bugs.php
//
-#include <time.h> //START
+#include <time.h>
#include <FL/Fl_Double_Window.H>
#include <FL/Fl_Box.H>
-#include <FL/Fl_Simple_Terminal.H>
+#include <FL/Fl_Terminal.H>
#define TERMINAL_HEIGHT 120
// Globals
Fl_Double_Window *G_win = 0;
Fl_Box *G_box = 0;
-Fl_Simple_Terminal *G_tty = 0;
+Fl_Terminal *G_tty = 0;
// Append a date/time message to the terminal every 2 seconds
void tick_cb(void *data) {
@@ -43,7 +43,7 @@ int main(int argc, char **argv) {
"Your app's debugging output in tty below");
// Add simple terminal to bottom of app window for scrolling history of status messages.
- G_tty = new Fl_Simple_Terminal(0,200,G_win->w(),TERMINAL_HEIGHT);
+ G_tty = new Fl_Terminal(0,200,G_win->w(),TERMINAL_HEIGHT);
G_tty->ansi(true); // enable use of "\033[32m"
G_win->end();
@@ -51,4 +51,4 @@ int main(int argc, char **argv) {
G_win->show();
Fl::add_timeout(0.5, tick_cb);
return Fl::run();
-} //END
+}
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 0db9631be..f9511f358 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -90,6 +90,7 @@ set (CPPFILES
Fl_Table.cxx
Fl_Table_Row.cxx
Fl_Tabs.cxx
+ Fl_Terminal.cxx
Fl_Text_Buffer.cxx
Fl_Text_Display.cxx
Fl_Text_Editor.cxx
diff --git a/src/Fl_Terminal.cxx b/src/Fl_Terminal.cxx
new file mode 100644
index 000000000..6b7d6dd87
--- /dev/null
+++ b/src/Fl_Terminal.cxx
@@ -0,0 +1,3508 @@
+//
+// Fl_Terminal.H - A terminal widget for Fast Light Tool Kit (FLTK).
+//
+// Copyright 2022 by Greg Ercolano.
+// Copyright 2023 by Bill Spitzak and others.
+//
+// This library is free software. Distribution and use rights are outlined in
+// the file "COPYING" which should have been included with this file. If this
+// file is missing or damaged, see the license at:
+//
+// https://www.fltk.org/COPYING.php
+//
+// Please see the following page on how to report bugs and issues:
+//
+// https://www.fltk.org/bugs.php
+//
+
+// TODO: horizontal scrollbar
+// TODO: double clicking text should make word selection,
+// and drag should word-enlarge selection
+// FIXME: While dragging a selection, hitting shift stops the selection
+
+// This must appear above #include <assert.h>
+#ifndef NDEBUG
+#define NDEBUG // comment out to enable assert()
+#endif
+
+#include <ctype.h> // isdigit
+#include <stdlib.h> // malloc
+#include <string.h> // strlen
+#include <stdarg.h> // vprintf, va_list
+#include <assert.h>
+
+#include <FL/Fl.H>
+#include <FL/Fl_Terminal.H>
+#include <FL/fl_utf8.h> // fl_utf8len1
+#include <FL/fl_draw.H>
+#include <FL/fl_string_functions.h>
+
+/////////////////////////////////
+////// Static Functions /////////
+/////////////////////////////////
+
+#define MIN(a,b) ((a)<=(b)) ? (a) : (b) // Return smaller of two values
+#define MAX(a,b) ((a)>=(b)) ? (a) : (b) // Return larger of two values
+#define ABS(a) ((a)<0) ? -(a) : (a) // Return abs value
+
+// Return val clamped between min and max
+static int clamp(int val, int min, int max)
+ { return (val<min) ? min : (val>max) ? max : val; }
+
+// Swap integer values a and b
+static void swap(int &a, int &b)
+ { int asave = a; a = b; b = asave; }
+
+static int normalize(int row, int maxrows) {
+ row = row % maxrows;
+ if (row < 0) row = maxrows + row; // negative? index relative to end
+ return row;
+}
+
+// Color channel management
+static int red(Fl_Color val) { return (val & 0xff000000) >> 24; }
+static int grn(Fl_Color val) { return (val & 0x00ff0000) >> 16; }
+static int blu(Fl_Color val) { return (val & 0x0000ff00) >> 8; }
+static Fl_Color rgb(int r,int g,int b) { return (r << 24) | (g << 16) | (b << 8); }
+
+// Return dim version of color 'val'
+static Fl_Color dim_color(Fl_Color val) {
+ int r = clamp(red(val) - 0x20, 0, 255);
+ int g = clamp(grn(val) - 0x20, 0, 255);
+ int b = clamp(blu(val) - 0x20, 0, 255);
+ //DEBUG ::printf("DIM COLOR: %08x -> %08x\n", val, rgb(r,g,b));
+ return rgb(r,g,b);
+}
+
+// Return bold version of color 'val'
+static Fl_Color bold_color(Fl_Color val) {
+ int r = clamp(red(val) + 0x20, 0, 255);
+ int g = clamp(grn(val) + 0x20, 0, 255);
+ int b = clamp(blu(val) + 0x20, 0, 255);
+ //DEBUG ::printf("BOLD COLOR: %08x -> %08x\n", val, rgb(r,g,b));
+ return rgb(r,g,b);
+}
+
+// Return an FLTK color for given foreground color index (0..7)
+static Fl_Color fltk_fg_color(uchar ci) {
+ static const Fl_Color xterm_fg_colors_[] = {
+ 0x00000000, // 0
+ 0xd0000000, // 1 - red
+ 0x00d00000, // 2 - grn
+ 0xd0d00000, // 3 - yel
+ 0x0000d000, // 4 - blu
+ 0xd000d000, // 5 - mag
+ 0x00d0d000, // 6 - cyn
+ 0xd0d0d000 // 7 - white
+ };
+ ci &= 0x07; // clamp to array size
+ return xterm_fg_colors_[ci];
+}
+
+// Return an FLTK color for the given background color index (0..7) and attribute.
+// Background colors should be just a little darker than
+// the fg colors to prevent too much brightness clashing
+// for 'normal' bg vs fg colors.
+//
+static Fl_Color fltk_bg_color(uchar ci) {
+ static const Fl_Color xterm_bg_colors_[] = {
+ 0x00000000, // 0
+ 0xc0000000, // 1 - red
+ 0x00c00000, // 2 - grn
+ 0xc0c00000, // 3 - yel
+ 0x0000c000, // 4 - blu
+ 0xc000c000, // 5 - mag
+ 0x00c0c000, // 6 - cyn
+ 0xc0c0c000 // 7 - white
+ };
+ ci &= 0x07; // clamp to array size
+ return xterm_bg_colors_[ci];
+}
+
+// See if an Fl_Boxtype is FL_XXX_FRAME
+static bool is_frame(Fl_Boxtype b) {
+ if (b == FL_UP_FRAME || b == FL_DOWN_FRAME ||
+ b == FL_THIN_UP_FRAME || b == FL_THIN_DOWN_FRAME ||
+ b == FL_ENGRAVED_FRAME || b == FL_EMBOSSED_FRAME ||
+ b == FL_BORDER_FRAME) return true;
+ return false;
+}
+
+///////////////////////////////////////
+////// Selection Class Methods ////////
+///////////////////////////////////////
+
+// Ctor
+Fl_Terminal::Selection::Selection(void) {
+ // These are used to set/get the mouse selection
+ srow_ = scol_ = erow_ = ecol_ = 0;
+ // FL_PUSH event row/col
+ push_clear();
+ selectionfgcolor_ = FL_BLACK;
+ selectionbgcolor_ = FL_WHITE;
+ state_ = 0;
+ is_selection_ = false;
+}
+
+/**
+ Return selection start/end.
+ Ensures (start < end) to allow walking 'forward' thru selection,
+ left-to-right, top-to-bottom.
+
+ Returns:
+ - true -- valid selection values returned
+ - false -- no selection was made, returned values undefined
+*/
+bool Fl_Terminal::Selection::get_selection(int &srow,int &scol,
+ int &erow,int &ecol) const {
+ srow = srow_; scol = scol_;
+ erow = erow_; ecol = ecol_;
+ if (!is_selection_) return false;
+ // Ensure (start < end) on return
+ if (srow_ == erow_ && scol_ > ecol_) swap(scol, ecol);
+ if (srow_ > erow_)
+ { swap(srow, erow); swap(scol, ecol); }
+ return true;
+}
+
+// Start new selection at specified row,col
+// Always returns true.
+//
+bool Fl_Terminal::Selection::start(int row, int col) {
+ srow_ = erow_ = row;
+ scol_ = ecol_ = col;
+ state_ = 1; // state: "started selection"
+ is_selection_ = true;
+ return true;
+}
+
+// Extend existing selection to row,col
+// Returns true if anything changed, false if not.
+//
+bool Fl_Terminal::Selection::extend(int row, int col) {
+ // no selection started yet? start and return true
+ if (!is_selection()) return start(row, col);
+ state_ = 2; // state: "extending selection"
+ if (erow_ == row && ecol_ == col) return false; // no change
+ erow_ = row;
+ ecol_ = col;
+ return true;
+}
+
+// End selection (turn dragging() off)
+void Fl_Terminal::Selection::end(void) {
+ state_ = 3; // state: "finished selection"
+ // Order selection
+ if (erow_ < srow_)
+ { swap(srow_, erow_); swap(scol_, ecol_); }
+ if (erow_ == srow_ && scol_ > ecol_) swap(scol_, ecol_);
+}
+
+// Create a complete selection
+void Fl_Terminal::Selection::select(int srow, int scol, int erow, int ecol) {
+ srow_ = srow; scol_ = scol;
+ erow_ = erow; ecol_ = ecol;
+ state_ = 3; // state: "finished selection"
+ is_selection_ = true;
+}
+
+// Clear selection
+// Returns true if there was a selection, false if there wasn't
+//
+bool Fl_Terminal::Selection::clear(void) {
+ bool was_selected = is_selection(); // save for return
+ srow_ = scol_ = erow_ = ecol_ = 0;
+ state_ = 0;
+ is_selection_ = false;
+ return was_selected;
+}
+
+// Scroll the selection up(+)/down(-) number of rows
+void Fl_Terminal::Selection::scroll(int nrows) {
+ if (is_selection()) {
+ srow_ -= nrows;
+ erow_ -= nrows;
+ // Selection scrolled off? clear selection
+ if (srow_ < 0 || erow_ < 0) clear();
+ }
+}
+
+///////////////////////////////////////
+////// EscapeSeq Class Methods ////////
+///////////////////////////////////////
+
+// Append char to buff[] safely (with bounds checking)
+// Returns:
+// success - ok
+// fail - buffer full/overflow
+//
+int Fl_Terminal::EscapeSeq::append_buff(char c) {
+ if (buffp_ >= buffendp_) return fail; // end of buffer reached?
+ *buffp_++ = c;
+ *buffp_ = 0; // keep buff[] null terminated
+ return success;
+}
+
+// Append whatever integer string is at valbuffp into vals_[] safely w/bounds checking
+// Assumes valbuffp points to a null terminated string.
+// Returns:
+// success - parsed ok
+// fail - error occurred (non-integer, or vals_[] full)
+//
+int Fl_Terminal::EscapeSeq::append_val(void) {
+ if (vali_ >= maxvals) // vals_[] full?
+ { vali_ = maxvals-1; return fail; } // clamp index, fail
+ if (!valbuffp_ || (*valbuffp_ == 0)) // no integer to parse? e.g. ESC[m, ESC[;m
+ { vals_[vali_] = 0; return success; } // zero in array, do not inc vali
+ if (sscanf(valbuffp_, "%d", &vals_[vali_]) != 1) // Parse integer into vals_[]
+ { return fail; } // fail if parsed a non-integer
+ vals_[vali_] &= 0x3ff; // sanity: enforce int in range 0 ~ 1023 (prevent DoS attack)
+ if (++vali_ >= maxvals) // advance val index, fail if too many vals
+ { vali_ = maxvals-1; return fail; } // clamp + fail
+ valbuffp_ = 0; // parsed val ok, reset valbuffp to NULL
+ return success;
+}
+
+// Ctor
+Fl_Terminal::EscapeSeq::EscapeSeq(void) {
+ reset();
+ save_row_ = -1; // only in ctor
+ save_col_ = -1;
+}
+
+// Reset the class
+// Named reset to not be confused with clear() screen/line/etc
+//
+void Fl_Terminal::EscapeSeq::reset(void) {
+ esc_mode_ = 0; // disable ESC mode, so parse_in_progress() returns false
+ csi_ = false; // CSI off until '[' received
+ buffp_ = buff_; // point to beginning of buffer
+ buffendp_ = buff_ + (maxbuff - 1); // point to end of buffer
+ valbuffp_ = 0; // disable val ptr (no vals parsed yet)
+ vali_ = 0; // zero val index
+ buff_[0] = 0; // null terminate buffer
+ vals_[0] = 0; // first val[] 0
+ memset(vals_, 0, sizeof(vals_));
+}
+
+// Return current escape mode.
+// This is really only valid after parse() returns 'completed'.
+// After a reset() this will return 0.
+//
+char Fl_Terminal::EscapeSeq::esc_mode(void) const { return esc_mode_; }
+
+// Set current escape mode.
+void Fl_Terminal::EscapeSeq::esc_mode(char val) { esc_mode_ = val; }
+
+// Return the total vals parsed.
+// This is really only valid after parse() returns 'completed'.
+//
+int Fl_Terminal::EscapeSeq::total_vals(void) const { return vali_; }
+
+// Return the value at index i.
+// i is not range checked; it's assumed 0 <= i < total_vals().
+// It is only valid to call this after parse() returns 'completed'.
+//
+int Fl_Terminal::EscapeSeq::val(int i) const { return vals_[i]; }
+
+// See if we're in the middle of parsing an ESC sequence
+bool Fl_Terminal::EscapeSeq::parse_in_progress(void) const {
+ return (esc_mode_ == 0) ? false : true;
+}
+
+// See if we're in the middle of parsing an ESC sequence
+bool Fl_Terminal::EscapeSeq::is_csi(void) const { return csi_; }
+
+// Return with default value (if none) or vals[0] (if at least one val spec'd).
+// Handles default for single values (e.g. ESC[#H vs. ESC[H)
+// vals[0] is clamped between 0 and 'max'
+//
+int Fl_Terminal::EscapeSeq::defvalmax(int dval, int max) const {
+ if (total_vals() == 0) return dval;
+ else return clamp(vals_[0], 0, max);
+}
+
+// Save row,col for later retrieval
+void Fl_Terminal::EscapeSeq::save_cursor(int row, int col) {
+ save_row_ = row;
+ save_col_ = col;
+}
+
+// Return saved position in row,col
+void Fl_Terminal::EscapeSeq::restore_cursor(int &row, int &col) {
+ row = save_row_;
+ col = save_col_;
+}
+
+// Handle parsing an escape sequence.
+// Call this only if parse_in_progress() is true.
+// Passing ESC does a reset() and sets esc_mode() to ESC.
+// When a full escape sequence has been parsed, 'completed' is returned (see below).
+//
+// Returns:
+// fail - error occurred: escape sequence invalid, class is reset()
+// success - parsing ESC sequence OK so far, still in progress/not done yet
+// completed - complete ESC sequence was parsed, esc_mode() will be the operation, e.g.
+// 'm' - <ESC>[1m -- is_csi() will be true, val() has value(s) parsed
+// 'A' - <ESC>A -- is_csi() will be false (no vals)
+//
+int Fl_Terminal::EscapeSeq::parse(char c) {
+ // NOTE: During parsing esc_mode() will be:
+ // 0 - reset/not parsing
+ // 0x1b - ESC received, expecting next one of A/B/C/D or '['
+ // '[' - actively parsing CSI sequence, e.g. ESC[
+ //
+ // At the /end/ of parsing, after 'completed' is returned,
+ // esc_mode() will be the mode setting char, e.g. 'm' for 'ESC[0m', etc.
+ //
+ if (c == 0) { // NULL? (caller should really never send us this)
+ return success; // do nothing -- leave state unchanged, return 'success'
+ } else if (c == 0x1b) { // ESC at ANY time resets class/begins new ESC sequence
+ reset();
+ esc_mode(0x1b);
+ if (append_buff(c) < 0) goto pfail; // save ESC in buf
+ return success;
+ } else if (c < ' ' || c >= 0x7f) { // any other control or binary characters?
+ goto pfail; // reset + fail out of esc sequence parsing
+ }
+ // Whatever the character is, handle it depending on esc_mode..
+ if (esc_mode() == 0x1b) { // in ESC mode?
+ if (c == '[') { // <ESC>[? CSI (Ctrl Seq Introducer)
+ esc_mode(c); // switch to parsing mode for ESC[#;#;#..
+ csi_ = true; // this is now a CSI sequence
+ vali_ = 0; // zero vals_[] index
+ valbuffp_ = 0; // valbuffp NULL (no vals yet)
+ if (append_buff(c) < 0) goto pfail; // save '[' in buf
+ return success; // success
+ } else if ( (c >= '@' && c <= 'Z') || // C1 control code (e.g. <ESC>D, <ESC>c, etc)
+ (c >= 'a' && c <= 'z') ) {
+ esc_mode(c); // save op in esc_mode() for caller to see
+ csi_ = false; // NOT a CSI sequence
+ vali_ = 0;
+ valbuffp_ = 0; // valbuffp NULL (no vals yet)
+ if (append_buff(c) < 0) goto pfail; // save op in buf
+ return completed; // completed sequence
+ } else { // ESCx?
+ goto pfail; // not supported
+ }
+ } else if (esc_mode() == '[') { // '[' mode? e.g. ESC[... aka. is_csi()
+ if (c == ';') { // ';' indicates end of a value, e.g. ESC[0;2..
+ if (append_val() < 0) goto pfail; // append value parsed so far, vali gets inc'ed
+ if (append_buff(c) < 0) goto pfail; // save ';' in buf
+ return success;
+ }
+ if (isdigit(c)) { // parsing an integer?
+ if (!valbuffp_) // valbuffp not set yet?
+ { valbuffp_ = buffp_; } // point to first char in integer string
+ if (append_buff(c) < 0) goto pfail; // add value to buffer
+ return success;
+ }
+ // Not a ; or digit? fall thru to [A-Z,a-z] check
+ } else { // all other esc_mode() chars are fail/unknown
+ goto pfail;
+ }
+ if ( ( c >= '@' && c<= 'Z') || // ESC#X or ESC[...X, where X is [A-Z,a-z]?
+ ( c >= 'a' && c<= 'z') ) {
+ if (append_val() < 0 ) goto pfail; // append any trailing vals just before letter
+ if (append_buff(c) < 0 ) goto pfail; // save letter in buffer
+ esc_mode(c); // change mode to the mode setting char
+ return completed; // completed/done
+ }
+ // Any other chars? reset+fail
+pfail:
+ reset();
+ return fail;
+}
+
+//////////////////////////////////////
+///// CharStyle Class Methods ////////
+//////////////////////////////////////
+
+// Ctor
+Fl_Terminal::CharStyle::CharStyle(void) {
+ attrib_ = 0;
+ flags_ = 0;
+ defaultfgcolor_ = 0xd0d0d000; // off white
+ defaultbgcolor_ = 0xffffffff; // special color: doesn't draw, 'shows thru' to box()
+ fgcolor_ = defaultfgcolor_;
+ bgcolor_ = defaultbgcolor_;
+ flags_ |= (FG_XTERM | BG_XTERM);
+ fontface_ = FL_COURIER;
+ fontsize_ = 14;
+ update(); // updates fontheight_, fontdescent_, charwidth_
+}
+
+// Update fontheight/descent cache whenever font changes
+void Fl_Terminal::CharStyle::update(void) {
+ // cache these values
+ fl_font(fontface_, fontsize_);
+ fontheight_ = int(fl_height() + 0.5);
+ fontdescent_ = int(fl_descent() + 0.5);
+ charwidth_ = int(fl_width("X") + 0.5);
+}
+
+// Return fg color
+Fl_Color Fl_Terminal::CharStyle::fgcolor(void) const {
+ return fgcolor_;
+}
+
+// Return bg color
+Fl_Color Fl_Terminal::CharStyle::bgcolor(void) const {
+ return bgcolor_;
+}
+
+// Return only the color bit flags
+// Only the color bits of 'inflags' are modified with our color bits.
+//
+uchar Fl_Terminal::CharStyle::colorbits_only(uchar inflags) const {
+ return (inflags & ~COLORMASK) | (flags_ & COLORMASK); // add color bits only
+}
+
+void Fl_Terminal::CharStyle::fgcolor_uchar(uchar val) {
+ fgcolor_ = fltk_fg_color(val);
+ set_flag(FG_XTERM);
+}
+
+void Fl_Terminal::CharStyle::bgcolor_uchar(uchar val) {
+ bgcolor_ = fltk_bg_color(val);
+ set_flag(BG_XTERM);
+}
+
+///////////////////////////////////
+///// Cursor Class Methods ////////
+///////////////////////////////////
+
+// Is cursor at display row,col?
+bool Fl_Terminal::Cursor::is_rowcol(int drow,int dcol) const {
+ return(drow == row_ && dcol == col_);
+}
+
+// Scroll the cursor row up(+)/down(-) number of rows
+void Fl_Terminal::Cursor::scroll(int nrows) {
+ row_ = MAX(row_ - nrows, 0); // don't let (row_<0)
+}
+
+/////////////////////////////////////
+///// Utf8Char Class Methods ////////
+/////////////////////////////////////
+
+// Ctor
+Fl_Terminal::Utf8Char::Utf8Char(void) {
+ text_[0] = ' ';
+ len_ = 1;
+ attrib_ = 0;
+ flags_ = 0;
+ fgcolor_ = 0xffffff00;
+ bgcolor_ = 0xffffffff; // special color: doesn't draw, 'shows thru' to box()
+}
+
+// copy ctor
+Fl_Terminal::Utf8Char::Utf8Char(const Utf8Char& src) {
+ // local instance not initialized yet; init first, then copy text
+ text_[0] = ' ';
+ len_ = 1;
+ attrib_ = src.attrib_;
+ flags_ = src.flags_;
+ fgcolor_ = src.fgcolor_;
+ bgcolor_ = src.bgcolor_;
+ text_utf8_(src.text_utf8(), src.length()); // copy the src text
+}
+
+// assignment
+Fl_Terminal::Utf8Char& Fl_Terminal::Utf8Char::operator=(const Utf8Char& src) {
+ // local instance is already initialized, so just change its contents
+ text_utf8_(src.text_utf8(), src.length()); // local copy src text
+ attrib_ = src.attrib_;
+ flags_ = src.flags_;
+ fgcolor_ = src.fgcolor_;
+ bgcolor_ = src.bgcolor_;
+ return *this;
+}
+
+// dtor
+Fl_Terminal::Utf8Char::~Utf8Char(void) {
+ len_ = 0;
+}
+
+// Set 'text_' to valid UTF-8 string 'text'.
+//
+// text_ must not be NULL, and len must be in range: 1 <= len <= max_utf8().
+// NOTE: Caller must handle such checks, and use handle_unknown_char()
+// for invalid chars.
+//
+void Fl_Terminal::Utf8Char::text_utf8_(const char *text, int len) {
+ memcpy(text_, text, len);
+ len_ = len; // update new length
+}
+
+// Set UTF-8 string for this char.
+//
+// text_ must not be NULL, and len must be in range: 1 <= len <= max_utf8().
+// NOTE: Caller must handle such checks, and use handle_unknown_char()
+// for invalid chars.
+//
+void Fl_Terminal::Utf8Char::text_utf8(const char *text,
+ int len,
+ const CharStyle& style) {
+ text_utf8_(text, len); // updates text_, len_
+ fl_font(style.fontface(), style.fontsize()); // need font to calc UTF-8 width
+ attrib_ = style.attrib();
+ flags_ = style.colorbits_only(flags_);
+ fgcolor_ = style.fgcolor();
+ bgcolor_ = style.bgcolor();
+}
+
+// Set char to single printable ASCII character 'c'
+// 'c' must be "printable" ASCII in the range (0x20 <= c <= 0x7e).
+// Anything outside of that is silently ignored.
+//
+void Fl_Terminal::Utf8Char::text_ascii(char c, const CharStyle& style) {
+ // Signed char vals above 0x7f are /negative/, so <0x20 check covers those
+ if (c < 0x20 || c >= 0x7e) return; // ASCII non-printable?
+ text_utf8(&c, 1, style);
+}
+
+// Set fl_font() based on specified style for this char's attribute
+void Fl_Terminal::Utf8Char::fl_font_set(const CharStyle& style) const {
+ int face = style.fontface() |
+ ((attrib_ & Fl_Terminal::BOLD) ? FL_BOLD : 0) |
+ ((attrib_ & Fl_Terminal::ITALIC) ? FL_ITALIC : 0);
+ fl_font(face, style.fontsize());
+}
+
+// Return the foreground color as an fltk color
+Fl_Color Fl_Terminal::Utf8Char::fgcolor(void) const {
+ return fgcolor_;
+}
+
+// Return the background color as an fltk color
+Fl_Color Fl_Terminal::Utf8Char::bgcolor(void) const {
+ return bgcolor_;
+}
+
+// Return the width of this character in floating point pixels
+//
+// WARNING: Uses current font, so assumes fl_font(face,size)
+// has already been set to current font!
+//
+double Fl_Terminal::Utf8Char::pwidth(void) const {
+ return fl_width(text_, len_);
+}
+
+// Return the width of this character in integer pixels
+//
+// WARNING: Uses current font, so assumes fl_font(face,size)
+// has already been set to current font!
+//
+int Fl_Terminal::Utf8Char::pwidth_int(void) const {
+ return int(fl_width(text_, len_) + 0.5);
+}
+
+// Return color \p col, possibly influenced by BOLD or DIM attributes \p attr.
+// If a \p grp widget is specified (i.e. not NULL), don't let the color \p col be
+// influenced by the attribute bits /if/ \p col matches the \p grp widget's own color().
+//
+Fl_Color Fl_Terminal::Utf8Char::attr_color(Fl_Color col, const Fl_Widget *grp) const {
+ // Don't modify color if it's the special 'see thru' color 0x0 or widget's color()
+ if (grp && ((col == 0xffffffff) || (col == grp->color()))) return grp->color();
+ switch (attrib_ & (Fl_Terminal::BOLD|Fl_Terminal::DIM)) {
+ case 0: return col; // not bold or dim? no change
+ case Fl_Terminal::BOLD: return bold_color(col); // bold? use bold_color()
+ case Fl_Terminal::DIM : return dim_color(col); // dim? use dim_color()
+ default: return col; // bold + dim? cancel out
+ }
+}
+
+// Return the fg color of char \p u8c possibly influenced by BOLD or DIM.
+// If a \p grp widget is specified (i.e. not NULL), don't let the color \p col be
+// influenced by the attribute bits /if/ \p col matches the \p grp widget's own color().
+//
+Fl_Color Fl_Terminal::Utf8Char::attr_fg_color(const Fl_Widget *grp) const {
+ if (grp && (fgcolor_ == 0xffffffff)) // see thru color?
+ { return grp->color(); } // return grp's color()
+ return (flags_ & Fl_Terminal::FG_XTERM) // fg is an xterm color?
+ ? attr_color(fgcolor(), grp) // ..use attributes
+ : fgcolor(); // ..ignore attributes.
+}
+
+Fl_Color Fl_Terminal::Utf8Char::attr_bg_color(const Fl_Widget *grp) const {
+ if (grp && (bgcolor_ == 0xffffffff)) // see thru color?
+ { return grp->color(); } // return grp's color()
+ return (flags_ & Fl_Terminal::BG_XTERM) // bg is an xterm color?
+ ? attr_color(bgcolor(), grp) // ..use attributes
+ : bgcolor(); // ..ignore attributes.
+}
+
+
+////////////////////////////////////
+///// RingBuffer Class Methods /////
+////////////////////////////////////
+
+// Create a new copy of the buffer with different row/col sizes
+// Preserves old contents of display and history in use.
+//
+// The old buffer might have an offset and the hist/disp might wrap
+// around the end of the ring. The NEW buffer's offset will be zero,
+// so the hist/disp do NOT wrap around, making the move operation easier to debug.
+//
+// The copy preservation starts at the LAST ROW in display of both old (src) and new (dst)
+// buffers, and copies rows in reverse until hist_use_srow() reached, or if we hit top
+// of the new history (index=0), which ever comes first. So in the following where the
+// display is being enlarged, the copy preservation starts at "Line 5" (bottom of display)
+// and works upwards, ending at "Line 1" (top of history use):
+//
+// OLD (SRC) NEW (DST)
+// _____________ _____________ ___
+// | x x x x x x | | x x x x x x | ʌ 'x' indicates
+// | Line 1 | ─┐ | x x x x x x | | hist_rows unused history
+// | Line 2 | └─> | Line 1 | v buffer memory.
+// |-------------| |-------------| ---
+// | Line 3 | | Line 2 | ʌ
+// | Line 4 | | Line 3 | |
+// | Line 5 | ─┐ | Line 4 | | disp_rows
+// |_____________| └─> | Line 5 | |
+// |_____________| _v_
+//
+void Fl_Terminal::RingBuffer::new_copy(int drows, int dcols, int hrows, const CharStyle& style) {
+ // Create new buffer
+ int addhist = disp_rows() - drows; // adjust history use
+ int new_ring_rows = (drows+hrows);
+ int new_hist_use = clamp(hist_use_ + addhist, 0, hrows); // clamp incase new_hist_rows smaller than old
+ int new_nchars = (new_ring_rows * dcols);
+ Utf8Char *new_ring_chars = new Utf8Char[new_nchars]; // Create new ring buffer (all blanks)
+ // Preserve old contents in new buffer
+ int dst_cols = dcols;
+ int src_stop_row = hist_use_srow();
+ int tcols = MIN(ring_cols(), dcols);
+ int src_row = hist_use_srow() + hist_use_ + disp_rows_ - 1; // use row#s relative to hist_use_srow()
+ int dst_row = new_ring_rows - 1;
+ // Copy rows: working up from bottom of disp, stop at top of hist
+ while ((src_row >= src_stop_row) && (dst_row >= 0)) {
+ Utf8Char *src = u8c_ring_row(src_row);
+ Utf8Char *dst = new_ring_chars + (dst_row*dst_cols);
+ for (int col=0; col<tcols; col++ ) *dst++ = *src++;
+ --src_row;
+ --dst_row;
+ }
+ // Install new buffer: dump old, install new, adjust internals
+ if (ring_chars_) delete[] ring_chars_;
+ ring_chars_ = new_ring_chars;
+ ring_rows_ = new_ring_rows;
+ ring_cols_ = dcols;
+ nchars_ = new_nchars;
+ hist_rows_ = hrows;
+ hist_use_ = new_hist_use;
+ disp_rows_ = drows;
+ offset_ = 0; // for new buffer, we used a zero offset
+}
+
+// Clear the class, delete previous ring if any
+void Fl_Terminal::RingBuffer::clear(void) {
+ if (ring_chars_) delete[] ring_chars_; // dump our ring
+ ring_chars_ = 0;
+ ring_rows_ = 0;
+ ring_cols_ = 0;
+ nchars_ = 0;
+ hist_rows_ = 0;
+ hist_use_ = 0;
+ disp_rows_ = 0;
+ offset_ = 0;
+}
+
+// Clear history
+void Fl_Terminal::RingBuffer::clear_hist(void) {
+ hist_use_ = 0;
+}
+
+// Default ctor
+Fl_Terminal::RingBuffer::RingBuffer(void) {
+ ring_chars_ = 0;
+ clear();
+}
+
+// Ctor with specific sizes
+Fl_Terminal::RingBuffer::RingBuffer(int drows, int dcols, int hrows) {
+ // Start with cleared buffer first..
+ ring_chars_ = 0;
+ clear();
+ // ..then create.
+ create(drows, dcols, hrows);
+}
+
+// Dtor
+Fl_Terminal::RingBuffer::~RingBuffer(void) {
+ if (ring_chars_) delete[] ring_chars_;
+ ring_chars_ = NULL;
+}
+
+// See if 'grow' is within the history buffer
+// It's assumed grow is in the range 0 .. hist_rows()-1.
+//
+bool Fl_Terminal::RingBuffer::is_hist_ring_row(int grow) const {
+ grow %= ring_rows_;
+ grow -= offset_; // move into positive space
+ if (grow < 0) { grow = (ring_rows_ + grow); }
+ int htop = 0;
+ int hbot = (hist_rows_ - 1);
+ return ((grow >= htop) && (grow <= hbot));
+}
+
+// See if 'grow' is within the display buffer
+// It's assumed grow is in the range hist_rows() .. ring_rows()-1.
+//
+bool Fl_Terminal::RingBuffer::is_disp_ring_row(int grow) const {
+ grow %= ring_rows_;
+ grow -= offset_;
+ if (grow < 0) { grow = (ring_rows_ + grow); }
+ int dtop = hist_rows_;
+ int dbot = hist_rows_ + disp_rows_ - 1;
+ return ((grow >= dtop) && (grow <= dbot));
+}
+
+// Move display row from src_row to dst_row
+void Fl_Terminal::RingBuffer::move_disp_row(int src_row, int dst_row) {
+ Utf8Char *src = u8c_disp_row(src_row);
+ Utf8Char *dst = u8c_disp_row(dst_row);
+ for (int col=0; col<disp_cols(); col++) *dst++ = *src++;
+}
+
+// Clear the display row 'drow' using specified CharStyle 'style'
+void Fl_Terminal::RingBuffer::clear_disp_row(int drow, const CharStyle& style) {
+ int row = hist_rows_ + drow + offset_;
+ Utf8Char *u8c = u8c_ring_row(row);
+ for (int col=0; col<disp_cols(); col++) u8c++->clear(style);
+}
+
+// Scroll the ring buffer up or down #rows, using 'style' for empty rows
+// > Positive rows scroll "up", moves top line(s) into history, clears bot line(s)
+// Increases hist_use (unless maxed out).
+// > Negative rows scroll "down", clears top line(s), history unaffected
+//
+void Fl_Terminal::RingBuffer::scroll(int rows, const CharStyle& style) {
+ if (rows > 0) {
+ // Scroll up into history
+ // Example: scroll(2):
+ //
+ // BEFORE AFTER
+ // --------------- ---------------
+ // | H i s t | --- | x x x x x x x | \_ blanked rows
+ // | | \ | x x x x x x x | /
+ // | | ---> |---------------| <- disp_erow()
+ // | | | H i s t |
+ // | | | |
+ // disp_srow() -> |---------------| --- | 0001 |
+ // | 0001 | \ | 0002 |
+ // | 0002 | ---> |---------------| <- disp_srow()
+ // | 0003 | | 0003 |
+ // | 0004 | | 0004 |
+ // | 0005 | --- | 0005 |
+ // | 0006 | \ | 0006 |
+ // disp_erow() -> --------------- ---> ---------------
+ //
+ // \______/
+ // Simple
+ // Offset
+ rows = clamp(rows, 1, disp_rows()); // sanity
+ // Scroll up into history
+ offset_ = (offset_ + rows) % ring_rows_;
+ // Adjust hist_use, clamp to max
+ hist_use_ = clamp(hist_use_ + rows, 0, hist_rows_);
+ // Clear exposed lines at bottom
+ int srow = (disp_rows() - rows) % disp_rows();
+ int erow = disp_rows();
+ for (int row=srow; row<erow; row++) clear_disp_row(row, style);
+ } else {
+ // Scroll down w/out affecting history
+ // To leave history unaffect, we must move memory.
+ // Start at bottom row [A] and work up to top row [B].
+ //
+ // Example: scroll(-2):
+ //
+ // BEFORE AFTER
+ // ----------------- ----------------- _
+ // | 0001 [B] | ---┐ | x x x x x x x x | \_ blanked
+ // | 0002 | | | x x x x x x x x | _/ rows
+ // | 0003 | └---> | 0001 |
+ // | 0004 [A] | ---┐ | 0002 |
+ // | 0005 | | | 0003 |
+ // | 0006 | └---> | 0004 |
+ // ----------------- -----------------
+ // \______/
+ // Memory
+ // move
+ rows = clamp(-rows, 1, disp_rows()); // make rows positive + sane
+ for (int row=disp_rows()-1; row>=0; row--) {
+ int src_row = (row - rows);
+ int dst_row = row;
+ if (src_row >= 0) move_disp_row(row-rows, dst_row); // move rows
+ else clear_disp_row(dst_row, style); // hit top? blank the rest
+ }
+ }
+}
+
+// Return UTF-8 char for 'row' in the ring
+// Scrolling offset is NOT applied; this is raw access to the ring's rows.
+//
+// Example:
+// // Walk ALL rows in the ring buffer..
+// for (int row=0; row<ring.rows(); row++) {
+// Utf8Char *u8c = ring.u8c_ring_row(row);
+// ..
+// }
+//
+const Fl_Terminal::Utf8Char* Fl_Terminal::RingBuffer::u8c_ring_row(int row) const {
+ row = normalize(row, ring_rows());
+ assert(row >= 0 && row < ring_rows_);
+ return &ring_chars_[row * ring_cols()];
+}
+
+// Return UTF-8 char for beginning of 'row' in the history buffer.
+// Example:
+// // Walk ALL rows in history..
+// for (int hrow=0; hrow<ring.hrows(); hrow++) {
+// Utf8Char *u8c = u8c_hist_row(hrow);
+// ..
+// }
+//
+const Fl_Terminal::Utf8Char* Fl_Terminal::RingBuffer::u8c_hist_row(int hrow) const {
+ int rowi = normalize(hrow, hist_rows());
+ rowi = (rowi + offset_) % ring_rows_;
+ assert(rowi >= 0 && rowi <= ring_rows_);
+ return &ring_chars_[rowi * ring_cols()];
+}
+
+// Special case to walk the "in use" rows of the history
+// Example:
+// // Walk the "in use" rows of history..
+// for (int hrow=0; hrow<ring.hist_use(); hrow++) {
+// Utf8Char *u8c = u8c_hist_use_row(hrow);
+// ..
+// }
+//
+const Fl_Terminal::Utf8Char* Fl_Terminal::RingBuffer::u8c_hist_use_row(int hurow) const {
+ if (hist_use_ == 0) return 0; // history is empty! (caller is dumb to ask)
+ hurow = hurow % hist_use_; // normalize indexing within history in use
+ hurow = hist_rows_ - hist_use_ + hurow; // index hist_use rows from end history
+ hurow = (hurow + offset_) % ring_rows_; // convert to absolute index in ring_chars_[]
+ assert(hurow >= 0 && hurow <= hist_use());
+ return &ring_chars_[hurow * ring_cols()];
+}
+
+// Return UTF-8 char for beginning of 'row' in the display buffer
+// Example:
+// // Walk ALL rows in display..
+// for (int drow=0; drow<ring.drows(); drow++) {
+// Utf8Char *u8c = u8c_disp_row(drow);
+// ..
+// }
+//
+const Fl_Terminal::Utf8Char* Fl_Terminal::RingBuffer::u8c_disp_row(int drow) const {
+ int rowi = normalize(drow, disp_rows());
+ rowi = (hist_rows_ + rowi + offset_) % ring_rows_; // display starts at end of history
+ assert(rowi >= 0 && rowi <= ring_rows_);
+ return &ring_chars_[rowi * ring_cols()];
+}
+
+// non-const versions of the above
+Fl_Terminal::Utf8Char* Fl_Terminal::RingBuffer::u8c_ring_row(int row)
+ { return const_cast<Utf8Char*>(const_cast<const RingBuffer*>(this)->u8c_ring_row(row)); }
+
+Fl_Terminal::Utf8Char* Fl_Terminal::RingBuffer::u8c_hist_row(int hrow)
+ { return const_cast<Utf8Char*>(const_cast<const RingBuffer*>(this)->u8c_hist_row(hrow)); }
+
+Fl_Terminal::Utf8Char* Fl_Terminal::RingBuffer::u8c_hist_use_row(int hurow)
+ { return const_cast<Utf8Char*>(const_cast<const RingBuffer*>(this)->u8c_hist_use_row(hurow)); }
+
+Fl_Terminal::Utf8Char* Fl_Terminal::RingBuffer::u8c_disp_row(int drow)
+ { return const_cast<Utf8Char*>(const_cast<const RingBuffer*>(this)->u8c_disp_row(drow)); }
+
+// Resize ring buffer by creating new one, dumping old (if any).
+// Input:
+// drows -- display height in lines of text (rows)
+// dcols -- display width in characters (columns)
+// hrows -- scrollback history size in lines of text (rows)
+//
+void Fl_Terminal::RingBuffer::create(int drows, int dcols, int hrows) {
+ clear();
+ // History
+ hist_rows_ = hrows;
+ hist_use_ = 0;
+ // Display
+ disp_rows_ = drows;
+ // Ring buffer
+ ring_rows_ = hist_rows_ + disp_rows_;
+ ring_cols_ = dcols;
+ nchars_ = ring_rows_ * ring_cols_;
+ ring_chars_ = new Utf8Char[nchars_];
+}
+
+// Resize the buffer, preserve previous contents as much as possible
+void Fl_Terminal::RingBuffer::resize(int drows, int dcols, int hrows, const CharStyle& style) {
+ // If dcols or (drows+hrows) changed, make a NEW buffer and copy old contents.
+ // New copy will have xxxx_rows/cols and nchars adjusted.
+ //
+ if (dcols != disp_cols() || // cols changed size?
+ (drows+hrows) != (disp_rows()+hist_rows()) ) { // total #rows changed?
+ new_copy(drows, dcols, hrows, style);
+ } else {
+ // Cols and total rows the same, probably just changed disp/hist ratio
+ int addhist = disp_rows() - drows; // adj hist_use smaller if disp enlarged
+ hist_rows_ = hrows; // adj hist rows for new value
+ disp_rows_ = drows; // adj disp rows for new value
+ hist_use_ = clamp(hist_use_ + addhist, 0, hrows);
+ }
+}
+
+// Change the display rows. Use style for new rows, if any.
+void Fl_Terminal::RingBuffer::change_disp_rows(int drows, const CharStyle& style)
+ { resize(drows, ring_cols(), hist_rows(), style); }
+
+// Change the display columns. Use style for new columns, if any.
+void Fl_Terminal::RingBuffer::change_disp_cols(int dcols, const CharStyle& style)
+ { resize(disp_rows(), dcols, hist_rows(), style); }
+
+/////////////////////////////////////
+///// Fl_Terminal Class Methods /////
+/////////////////////////////////////
+
+// Return u8c for beginning of a row inside the ring.
+// 'grow' is 'globally' indexed (relative to the beginning of the ring buffer),
+// and so can access ANY character in the entire ring buffer (hist or disp)
+// by its global index, which is to say without any scrolling offset applied.
+// Should really ONLY be used for making a complete copy of the ring.
+//
+const Fl_Terminal::Utf8Char* Fl_Terminal::u8c_ring_row(int grow) const
+ { return ring_.u8c_ring_row(grow); }
+
+// Return u8c for beginning of a row inside the history.
+// 'hrow' is indexed relative to the beginning of the history buffer.
+//
+const Fl_Terminal::Utf8Char* Fl_Terminal::u8c_hist_row(int hrow) const
+ { return ring_.u8c_hist_row(hrow); }
+
+// Return u8c for beginning of a row inside the 'in use' history.
+// 'hurow' is indexed relative to the beginning of the 'in use' buffer.
+//
+const Fl_Terminal::Utf8Char* Fl_Terminal::u8c_hist_use_row(int hurow) const
+ { return ring_.u8c_hist_use_row(hurow); }
+
+// Return u8c for beginning of a row inside the display
+// 'drow' is indexed relative to the beginning of the display buffer.
+//
+const Fl_Terminal::Utf8Char* Fl_Terminal::u8c_disp_row(int drow) const
+ { return ring_.u8c_disp_row(drow); }
+
+// non-const versions of the above
+Fl_Terminal::Utf8Char* Fl_Terminal::u8c_ring_row(int grow)
+ { return const_cast<Utf8Char*>(const_cast<const Fl_Terminal*>(this)->u8c_ring_row(grow)); }
+
+Fl_Terminal::Utf8Char* Fl_Terminal::u8c_hist_row(int hrow)
+ { return const_cast<Utf8Char*>(const_cast<const Fl_Terminal*>(this)->u8c_hist_row(hrow)); }
+
+Fl_Terminal::Utf8Char* Fl_Terminal::u8c_hist_use_row(int hurow)
+ { return const_cast<Utf8Char*>(const_cast<const Fl_Terminal*>(this)->u8c_hist_use_row(hurow)); }
+
+Fl_Terminal::Utf8Char* Fl_Terminal::u8c_disp_row(int drow)
+ { return const_cast<Utf8Char*>(const_cast<const Fl_Terminal*>(this)->u8c_disp_row(drow)); }
+
+// Create ring buffer.
+// Input:
+// drows -- display height in lines of text (rows)
+// dcols -- display width in characters (columns)
+// hrows -- scrollback history size in lines of text (rows)
+//
+// NOTE: Caller should call update_screen() at some point
+// to fix the scrollbar and other things.
+//
+void Fl_Terminal::create_ring(int drows, int dcols, int hrows) {
+ // recreate tabstops if col width being changed
+ if (dcols != ring_.ring_cols()) init_tabstops(dcols);
+ // recreate ring (dumps old)
+ ring_.create(drows, dcols, hrows);
+ // ensure cursor starts at home position
+ cursor_.home();
+}
+
+// Return the Utf8Char* for character under cursor.
+Fl_Terminal::Utf8Char* Fl_Terminal::u8c_cursor(void) {
+ return u8c_disp_row(cursor_.row()) + cursor_.col();
+}
+
+// Return scrollbar width if visible, or 0 if not visible
+int Fl_Terminal::vscroll_width(void) const {
+ return(vscroll_->visible() ? vscroll_->w() : 0);
+}
+
+// Initialize tabstops for terminal
+// NOTE: 'newsize' should always be at least 'ring_cols()'..
+//
+void Fl_Terminal::init_tabstops(int newsize) {
+ if (newsize > tabstops_size_) { // enlarge?
+ char *oldstops = tabstops_; // save old stops
+ int oldsize = tabstops_size_; // save old size
+ tabstops_ = (char*)malloc(newsize); // alloc new
+ for (int t=0; t<newsize; t++) { // init new tabstops:
+ tabstops_[t] = (oldstops && t<oldsize)
+ ? oldstops[t] // copy old
+ : ((t % 8) == 0) ? 1 : 0; // new defaults
+ }
+ if (oldstops) free((void*)oldstops); // dump old stops
+ tabstops_size_ = newsize;
+ } else {
+ // Same size or smaller? Do nothing -- just keep old tabstops
+ }
+}
+
+// Reset all tabstops to default 8th char
+void Fl_Terminal::default_tabstops(void) {
+ for (int t=1; t<tabstops_size_; t++) // t=1: skip 0
+ tabstops_[t] = ((t % 8) == 0) ? 1 : 0; // every 8th char is a tabstop
+}
+
+// Clear all tabstops
+void Fl_Terminal::clear_all_tabstops(void) {
+ memset(tabstops_, 0, tabstops_size_);
+}
+
+// Set/clear tabstop at current cursor x position
+// val: 0 clears tabstop, 1 sets tabstop
+//
+void Fl_Terminal::set_tabstop(void) {
+ int index = clamp(cursor_col(), 0, tabstops_size_-1); // clamp cursor pos
+ tabstops_[index] = 1; // set/clr tabstop
+}
+
+// Clear tabstop at current cursor x position
+void Fl_Terminal::clear_tabstop(void) {
+ int index = clamp(cursor_col(), 0, tabstops_size_-1); // clamp cursor pos
+ tabstops_[index] = 0; // clear tabstop
+}
+
+// Update the scrollbar based on screen, history buffer, etc
+// Scrollbar should range from 0 (bottom) to hist_rows (top),
+// the scrollbar's value indicating how far back into history.
+//
+void Fl_Terminal::update_scrollbar(void) {
+ int value_before = vscroll_->value();
+ // Enforce minimum tabsize of 10 or width of scrollbar
+ // The minimum vert size of tab should be scrollbar's width,
+ // but not smaller than 10 pixels, so user can grab it easily.
+ {
+ float tabsize = disp_rows() / float(disp_rows() + history_use());
+ float minpix = float(MAX(10, vscroll_->w())); // scroll width preferred, 10pix minimum
+ float minfrac = minpix/vscroll_->h(); // scroll wants a fraction, so convert
+ tabsize = MAX(minfrac, tabsize); // use the best fractional size
+ vscroll_->slider_size(tabsize);
+ }
+ vscroll_->range(hist_use(), 0); // 'minimum' is larger than 'max'
+ if (value_before == 0) vscroll_->value(0); // was at bottom? stay at bottom
+ // Ensure scrollbar in proper position
+ update_screen_xywh(); // ensure scrn_ up to date first
+ int sx = scrn_.r() + margin_.right();
+ int sy = scrn_.y() - margin_.top();
+ int sw = scrollbar_actual_size();
+ int sh = scrn_.h() + margin_.top() + margin_.bottom();
+ if (vscroll_->x() != sx ||
+ vscroll_->y() != sy ||
+ vscroll_->w() != sw ||
+ vscroll_->h() != sh) {
+ vscroll_->resize(sx, sy, sw, sh);
+ init_sizes(); // tell Fl_Group child changed size..
+ update_screen_xywh(); // ensure scrn_ is aware of sw change
+ display_modified(); // redraw Fl_Terminal since scroller changed size
+ }
+ vscroll_->redraw(); // redraw scroll always
+}
+
+// Refit the display to match screen
+void Fl_Terminal::refit_disp_to_screen(void) {
+ int dh = h_to_row(scrn_.h());
+ int dw = MAX(w_to_col(scrn_.w()), disp_cols()); // enlarge cols only
+ int drows = clamp(dh, 2, dh); // 2 rows minimum
+ int dcols = clamp(dw, 10, dw); // 10 cols minimum
+ int drow_diff = drows - ring_.disp_rows(); // change in rows?
+ ring_.resize(drows, dcols, hist_rows(), current_style_);
+ cursor_.scroll(-drow_diff);
+ clear_mouse_selection();
+ update_screen(false);
+}
+
+// Resize the display's vertical size to (drows).
+// When enlarging / shrinking, KEEP BOTTOM OF DISPLAY THE SAME, e.g.
+//
+// Display Display
+// BEFORE: SMALLER:
+// ___________ ___________
+// | Hist -3 | ʌ | Hist -3 | ʌ
+// | Hist -2 | |- 3 | Hist -2 | |
+// | Hist -1 | v | Hist -1 | |-- new hist size
+// |-----------| ---┐ | Line 1 | |
+// | Line 1 | | | Line 2 | v
+// | Line 2 | └---> |-----------|
+// | Line 3 | | Line 3 | ʌ
+// | : | | : | |-- new disp size
+// | Line 25 | -------> | Line 25 | v
+// ----------- -----------
+//
+// Display Display
+// BEFORE: LARGER
+// ___________ ___________
+// | Hist -3 | ʌ | Hist -3 | --- new hist size
+// | Hist -2 | |- 3 ┌-->|-----------|
+// | Hist -1 | v | | Hist -2 | ʌ
+// |-----------| ------┘ | Hist -1 | |
+// | Line 1 | | Line 1 | |
+// | Line 2 | | Line 2 | |-- new disp size
+// | Line 3 | | Line 3 | |
+// | : | | : | |
+// | Line 25 | --------> | Line 25 | v
+// ----------- -----------
+//
+//
+void Fl_Terminal::resize_display_rows(int drows) {
+ int drow_diff = drows - ring_.disp_rows(); // Change in rows?
+ if (drow_diff == 0) return; // No changes? early exit
+ int new_dcols = ring_cols(); // keep cols the same
+ int new_hrows = hist_rows() - drow_diff; // keep disp:hist ratio same
+ if (new_hrows<0) new_hrows = 0; // don't let hist be <0
+ ring_.resize(drows, new_dcols, new_hrows, current_style_);
+ // ..update cursor/selections to track text position
+ cursor_.scroll(-drow_diff);
+ select_.clear(); // clear any mouse selection
+ // ..update scrollbar, since disp_height relative to hist_use changed
+ update_scrollbar();
+}
+
+// Resize the display's columns
+// This affects the history and entire ring buffer too.
+// Make an effort to preserve previous content.
+// Up to caller to enforce any 'minimum' size for drows.
+//
+void Fl_Terminal::resize_display_columns(int dcols) {
+ // No changes? early exit
+ if (dcols == disp_cols()) return;
+ // Change cols, preserves previous content if possible
+ ring_.resize(disp_rows(), dcols, hist_rows(), current_style_);
+ update_scrollbar();
+}
+
+// Update only the internal terminal screen xywh and x2/y2 values
+void Fl_Terminal::update_screen_xywh(void) {
+ const Margin &m = margin_;
+ scrn_ = *this; // start with widget's current xywh
+ scrn_.inset(box()); // apply box offset
+ scrn_.inset(m.left(), m.top(), m.right(), m.bottom()); // apply margins offset
+ scrn_.inset(0, 0, scrollbar_actual_size(), 0); // apply scrollbar width
+}
+
+// Update internals when something "global" changes
+// Call this when something important is changed:
+// Resizing screen or changing font/size affect internals globally.
+// Font change affects per-character caching of char widths.
+// Display resize affects scrn_ cache, scrollbars, etc.
+//
+void Fl_Terminal::update_screen(bool font_changed) {
+ // current_style: update cursor's size for current font/size
+ if (font_changed) {
+ // Change font and current_style's font height
+ fl_font(current_style_.fontface(), current_style_.fontsize());
+ cursor_.h(current_style_.fontheight());
+ }
+ // Update the scrn_* values
+ update_screen_xywh();
+ // Recalc the scrollbar size/position/etc
+ update_scrollbar();
+}
+
+/**
+ Return terminal's scrollback history buffer size in lines of text (rows).
+*/
+int Fl_Terminal::history_rows(void) const {
+ return hist_rows();
+}
+
+/**
+ Set terminal's scrollback history buffer size in lines of text (rows).
+*/
+void Fl_Terminal::history_rows(int hrows) {
+ if (hrows == history_rows()) return; // no change? done
+ ring_.resize(disp_rows(), disp_cols(), hrows, current_style_);
+ update_screen(false); // false: no font change
+ display_modified();
+}
+
+/**
+ Returns how many lines are "in use" by the screen history buffer.
+
+ This value will be 0 if history was recently cleared with e.g.
+ clear_history() or \c "<ESC>c".
+
+ Return value will be in the range 0 .. (history_lines()-1).
+*/
+int Fl_Terminal::history_use(void) const {
+ return ring_.hist_use();
+}
+
+/**
+ Return terminal's display height in lines of text (rows).
+
+ This value is normally managed automatically by resize()
+ based on the current font size.
+*/
+int Fl_Terminal::display_rows(void) const {
+ return ring_.disp_rows();
+}
+
+/**
+ Set terminal's display height in lines of text (rows).
+
+ This value is normally managed automatically by resize()
+ based on the current font size, and should not be changed.
+
+ To change the display height, use resize() instead.
+*/
+void Fl_Terminal::display_rows(int drows) {
+ if (drows == disp_rows()) return; // no change? early exit
+ ring_.resize(drows, disp_cols(), hist_rows(), current_style_);
+ update_screen(false); // false: no font change ?NEED?
+}
+
+/**
+ Return terminal's display width in columns of text characters.
+
+ This value is normally managed automatically by resize()
+ based on the current font size.
+*/
+int Fl_Terminal::display_columns(void) const {
+ return ring_.disp_cols();
+}
+
+/**
+ Set terminal's display width in columns of text characters.
+
+ This value is normally managed automatically by resize()
+ based on the current font size, and should not be changed.
+
+ You CAN make the display_columns() larger than the width of
+ the widget; text in the terminal will simply run off the
+ screen edge and be clipped; the only way to reveal that
+ text is if the user enlarges the widget, or the font size
+ made smaller.
+
+ To change the display width, it is best to use resize() instead.
+*/
+void Fl_Terminal::display_columns(int dcols) {
+ if (dcols == disp_cols()) return; // no change? early exit
+ // Change cols, preserves previous content if possible
+ ring_.resize(disp_rows(), dcols, hist_rows(), current_style_);
+ update_screen(false); // false: no font change ?NEED?
+}
+
+/** Return current style for rendering text. */
+const Fl_Terminal::CharStyle& Fl_Terminal::current_style(void) const {
+ return current_style_;
+}
+
+/** Set current style for rendering text. */
+void Fl_Terminal::current_style(const CharStyle& sty) {
+ current_style_ = sty;
+}
+
+/**
+ Set the left margin; see \ref Fl_Terminal_Margins.
+*/
+void Fl_Terminal::margin_left(int val) {
+ val = clamp(val,0,w()-1);
+ margin_.left(val);
+ update_screen(true);
+ refit_disp_to_screen();
+}
+
+/**
+ Set the right margin; see \ref Fl_Terminal_Margins.
+*/
+void Fl_Terminal::margin_right(int val) {
+ val = clamp(val,0,w()-1);
+ margin_.right(val);
+ update_screen(true);
+ refit_disp_to_screen();
+}
+
+/**
+ Set the top margin; see \ref Fl_Terminal_Margins.
+*/
+void Fl_Terminal::margin_top(int val) {
+ val = clamp(val,0,h()-1);
+ margin_.top(val);
+ update_screen(true);
+ refit_disp_to_screen();
+}
+
+/**
+ Set the bottom margin; see \ref Fl_Terminal_Margins.
+*/
+void Fl_Terminal::margin_bottom(int val) {
+ val = clamp(val,0,h()-1);
+ margin_.bottom(val);
+ update_screen(true);
+ refit_disp_to_screen();
+}
+
+/**
+ Sets the font used for all text displayed in the terminal.
+
+ This affects all existing text (in display and history) as well
+ as any newly printed text.
+
+ Only monospace fonts are recommended, such as FL_COURIER or FL_SCREEN.
+ Custom fonts configured with Fl::set_font() will also work, as long
+ as they are monospace.
+*/
+void Fl_Terminal::textfont(Fl_Font val) {
+ current_style_.fontface(val);
+ update_screen(true);
+ display_modified();
+}
+
+/**
+ Sets the font size used for all text displayed in the terminal.
+
+ This affects all existing text (in display and history) as well
+ as any newly printed text.
+
+ Changing this will affect the display_rows() and display_columns().
+*/
+void Fl_Terminal::textsize(Fl_Fontsize val) {
+ current_style_.fontsize(val);
+ update_screen(true);
+ // Changing font size affects #lines in display, so resize it
+ refit_disp_to_screen();
+ display_modified();
+}
+
+/**
+ Sets the foreground text color as one of the 8 'xterm color' values.
+
+ This will be the foreground color used for all newly printed text,
+ similar to the \c \<ESC\>[\#m escape sequence, where \# is between 30 and 37.
+
+ This color will be reset to the default fg color if reset_terminal()
+ is called, or by \c \<ESC\>c, \c \<ESC\>[0m, etc.
+
+ The xterm color intensity values can be influenced by the Dim/Bold/Normal
+ modes (which can be set with e.g. \c \<ESC\>[1m, textattrib(), etc), so the
+ actual RGB values of these colors allow room for Dim/Bold to influence their
+ brightness. For instance, "Normal Red" is not full brightness to allow
+ "Bold Red" to be brighter. This goes for all colors except 'Black', which
+ is not influenced by Dim or Bold; Black is always Black.
+
+ The 8 color xterm values are:
+ - 0 = Black
+ - 1 = Red
+ - 2 = Green
+ - 3 = Yellow
+ - 4 = Blue
+ - 5 = Magenta
+ - 6 = Cyan
+ - 7 = White
+
+ \see textfgcolor_default(Fl_Color)
+*/
+void Fl_Terminal::textfgcolor_xterm(uchar val) {
+ current_style_.fgcolor(fltk_fg_color(val));
+}
+
+/**
+ Sets the background text color as one of the 8 'xterm color' values.
+
+ This will be the foreground color used for all newly printed text,
+ similar to the \c \<ESC\>[\#m escape sequence, where \# is between 40 and 47.
+
+ This color will be reset to the default bg color if reset_terminal()
+ is called, or by \c \<ESC\>c, \c \<ESC\>[0m, etc.
+
+ The xterm color intensity values can be influenced by the Dim/Bold/Normal
+ modes (which can be set with e.g. \c \<ESC\>[1m, textattrib(), etc), so the
+ actual RGB values of these colors allow room for Dim/Bold to influence their
+ brightness. For instance, "Normal Red" is not full brightness to allow
+ "Bold Red" to be brighter. This goes for all colors except 'Black', which
+ is not influenced by Dim or Bold; Black is always Black.
+
+ The 8 color xterm values are:
+ - 0 = Black
+ - 1 = Red
+ - 2 = Green
+ - 3 = Yellow
+ - 4 = Blue
+ - 5 = Magenta
+ - 6 = Cyan
+ - 7 = White
+
+ \see textbgcolor_default(Fl_Color)
+*/
+void Fl_Terminal::textbgcolor_xterm(uchar val) {
+ current_style_.bgcolor(fltk_bg_color(val));
+}
+
+/**
+ Set text foreground drawing color to fltk color \p val.
+ Use this for temporary color changes, similar to \<ESC\>[38;2;\<R\>;\<G\>;\<B\>m
+
+ This setting does _not_ affect the 'default' text colors used by \<ESC\>[0m,
+ \<ESC\>c, reset_terminal(), etc. To change both the current _and_
+ default fg color, also use textfgcolor_default(Fl_Color). Example:
+ \par
+ \code
+ // Set both 'current' and 'default' colors
+ Fl_Color amber = 0xd0704000;
+ tty->textfgcolor(amber); // set 'current' fg color
+ tty->textfgcolor_default(amber); // set 'default' fg color used by ESC[0m reset
+ \endcode
+ \see textfgcolor_default(Fl_Color)
+*/
+void Fl_Terminal::textfgcolor(Fl_Color val) {
+ current_style_.fgcolor(val);
+}
+
+/**
+ Set text background color to fltk color \p val.
+ Use this for temporary color changes, similar to \<ESC\>[48;2;\<R\>;\<G\>;\<B\>m
+
+ This setting does _not_ affect the 'default' text colors used by \<ESC\>[0m,
+ \<ESC\>c, reset_terminal(), etc. To change both the current _and_
+ default bg color, also use textbgcolor_default(Fl_Color). Example:
+ \par
+ \code
+ // Set both 'current' and 'default' colors
+ Fl_Color darkamber = 0x20100000;
+ tty->textbgcolor(darkamber); // set 'current' bg color
+ tty->textbgcolor_default(darkamber); // set 'default' bg color used by ESC[0m reset
+ \endcode
+
+ \see textbgcolor_default(Fl_Color)
+*/
+void Fl_Terminal::textbgcolor(Fl_Color val) {
+ current_style_.bgcolor(val);
+}
+
+/**
+ Set the default text foreground color used by \c \<ESC\>c, \c \<ESC\>[0m,
+ and reset_terminal().
+
+ Does not affect the 'current' text fg color; use textfgcolor(Fl_Color) to
+ set that.
+
+ \see textfgcolor(Fl_Color)
+*/
+void Fl_Terminal::textfgcolor_default(Fl_Color val) {
+ current_style_.defaultfgcolor(val);
+}
+
+/**
+ Set the default text background color used by \c \<ESC\>c, \c \<ESC\>[0m,
+ and reset_terminal().
+
+ Does not affect the 'current' text fg color; use textbgcolor(Fl_Color) to
+ set that.
+
+ \see textbgcolor(Fl_Color)
+*/
+void Fl_Terminal::textbgcolor_default(Fl_Color val) {
+ current_style_.defaultbgcolor(val);
+}
+
+/**
+ Set text attribute bits (underline, inverse, etc).
+ This will be the default attribute used for all newly printed text.
+
+ \see Fl_Terminal::Attrib
+*/
+void Fl_Terminal::textattrib(uchar val) {
+ current_style_.attrib(val);
+}
+
+// Convert fltk window X coord to column 'gcol' on specified global 'grow'
+// Returns 1 if 'gcol' was found, or 0 if X not within any char in 'grow'
+//
+int Fl_Terminal::x_to_glob_col(int X, int grow, int &gcol) const {
+ int cx = x() + margin_.left(); // char x position
+ const Utf8Char *u8c = utf8_char_at_glob(grow, 0);
+ for (gcol=0; gcol<ring_cols(); gcol++,u8c++) { // walk the cols looking for X
+ u8c->fl_font_set(current_style_); // pwidth_int() needs fl_font set
+ int cx2 = cx + u8c->pwidth_int(); // char x2 (right edge of char)
+ if (X >= cx && X < cx2) return 1; // found? return with gcol set
+ cx += u8c->pwidth_int(); // move cx to start x of next char
+ }
+ gcol = ring_cols()-1; // don't leave larger than #cols
+ return 0; // not found
+}
+
+// Convert fltk window X,Y coords to row + column indexing into ring_chars[]
+// Returns:
+// 1 -- found row,col
+// 0 -- not found, outside display's character area
+// -1/-2/-3/-4 -- not found, off top/bot/lt/rt edge respectively
+//
+int Fl_Terminal::xy_to_glob_rowcol(int X, int Y, int &grow, int &gcol) const {
+ // X,Y outside terminal area? early exit
+ if (Y<scrn_.y()) return -1; // up (off top edge)
+ if (Y>scrn_.b()) return -2; // dn (off bot edge)
+ if (X<scrn_.x()) return -3; // lt (off left edge)
+ if (X>scrn_.r()) return -4; // rt (off right edge)
+ // Find toprow of what's currently drawn on screen
+ int toprow = disp_srow() - vscroll_->value();
+ // Find row the 'Y' value is in
+ grow = toprow + ( (Y-scrn_.y()) / current_style_.fontheight());
+ return x_to_glob_col(X, grow, gcol);
+}
+
+/**
+ Clear the terminal screen only; does not affect the cursor position.
+
+ Also clears the current mouse selection.
+
+ If \p 'scroll_to_hist' is true, the screen is cleared by scrolling the
+ contents into the scrollback history, where it can be retrieved with the
+ scrollbar. This is the default behavior. If false, the screen is cleared
+ and the scrollback history is unchanged.
+
+ Similar to the escape sequence \c "<ESC>[2J".
+
+ \see clear_screen_home()
+*/
+void Fl_Terminal::clear_screen(bool scroll_to_hist) {
+ if (scroll_to_hist) { scroll(disp_rows()); return; }
+ for (int drow=0; drow<disp_rows(); drow++)
+ for (int dcol=0; dcol<disp_cols(); dcol++)
+ clear_char_at_disp(drow, dcol);
+ clear_mouse_selection();
+}
+
+/**
+ Clear the terminal screen and home the cursor.
+
+ Also clears the current mouse selection.
+
+ If \p 'scroll_to_hist' is true, the screen is cleared by scrolling the
+ contents into the scrollback history, where it can be retrieved with the
+ scrollbar. This is the default behavior. If false, the screen is cleared
+ and the scrollback history is unchanged.
+
+ Similar to the escape sequence \c "<ESC>[2J<ESC>[H".
+
+ \see clear_screen()
+*/
+void Fl_Terminal::clear_screen_home(bool scroll_to_hist) {
+ cursor_home();
+ clear_screen(scroll_to_hist);
+}
+
+/// Clear from cursor to Start Of Display (EOD), like \c "<ESC>[1J".
+void Fl_Terminal::clear_sod(void) {
+ for (int drow=0; drow <= cursor_.row(); drow++)
+ if (drow == cursor_.row())
+ for (int dcol=0; dcol<=cursor_.col(); dcol++)
+ putchar(' ', drow, dcol);
+ else
+ for (int dcol=0; dcol<disp_cols(); dcol++)
+ putchar(' ', drow, dcol);
+ //TODO: Clear mouse selection?
+}
+
+/// Clear from cursor to End Of Display (EOD), like \c "<ESC>[J<ESC>[0J".
+void Fl_Terminal::clear_eod(void) {
+ for (int drow=cursor_.row(); drow<disp_rows(); drow++)
+ if (drow == cursor_.row())
+ for (int dcol=cursor_.col(); dcol<disp_cols(); dcol++)
+ putchar(' ', drow, dcol);
+ else
+ for (int dcol=0; dcol<disp_cols(); dcol++)
+ putchar(' ', drow, dcol);
+ //TODO: Clear mouse selection?
+}
+
+/// Clear from cursor to End Of Line (EOL), like \c "<ESC>[K".
+void Fl_Terminal::clear_eol(void) {
+ Utf8Char *u8c = u8c_disp_row(cursor_.row()) + cursor_.col(); // start at cursor
+ for (int col=cursor_.col(); col<disp_cols(); col++) // run from cursor to eol
+ (u8c++)->clear(current_style_);
+ //TODO: Clear mouse selection?
+}
+
+/// Clear from cursor to Start Of Line (SOL), like \c "<ESC>[1K".
+void Fl_Terminal::clear_sol(void) {
+ Utf8Char *u8c = u8c_disp_row(cursor_.row()); // start at sol
+ for (int col=0; col<=cursor_.col(); col++) // run from sol to cursor
+ (u8c++)->clear(current_style_);
+ //TODO: Clear mouse selection?
+}
+
+/// Clear entire line for specified row.
+void Fl_Terminal::clear_line(int drow) {
+ Utf8Char *u8c = u8c_disp_row(drow); // start at sol
+ for (int col=0; col<disp_cols(); col++) // run to eol
+ (u8c++)->clear(current_style_);
+ //TODO: Clear mouse selection?
+}
+
+/// Clear entire line cursor is currently on.
+void Fl_Terminal::clear_line(void) {
+ clear_line(cursor_.row());
+}
+
+/// Returns true if there's a mouse selection.
+bool Fl_Terminal::is_selection(void) const {
+ return select_.is_selection();
+}
+
+/**
+ Walk the mouse selection one character at a time from beginning to end,
+ returning a Utf8Char* to the next character in the selection, or NULL
+ if the end was reached, or if there's no selection.
+
+ This is easier to use for walking the selection than get_selection().
+
+ \p u8c should start out as NULL, rewinding to the beginning of the selection.
+ If the returned Utf8Char* is not NULL, \p row and \p col return the
+ character's row/column position in the ring buffer.
+ \par
+ \code
+ // EXAMPLE: Walk the entire mouse selection, if any
+ int row,col; // the returned row/col for each char
+ Utf8Char *u8c = NULL; // start with NULL to begin walk
+ while ((u8c = walk_selection(u8c, row, col))) { // loop until end char reached
+ ..do something with *u8c..
+ }
+ \endcode
+
+ \see get_selection(), is_selection()
+*/
+const Fl_Terminal::Utf8Char* Fl_Terminal::walk_selection(
+ const Utf8Char *u8c, ///< NULL on first iter
+ int &row, ///< returned row#
+ int &col ///< returned col#
+ ) const {
+ if (u8c==NULL) {
+ if (!is_selection()) return NULL;
+ row = select_.srow();
+ col = select_.scol();
+ u8c = u8c_ring_row(row);
+ } else {
+ // At end? done
+ if (row == select_.erow() && col == select_.ecol()) return NULL;
+ if (++col >= ring_cols()) // advance to next char
+ { col = 0; ++row; } // wrapped to next row?
+ }
+ return u8c_ring_row(row) + col;
+}
+
+/**
+ Return mouse selection's start/end position in the ring buffer, if any.
+
+ Ensures (start < end) to allow walking 'forward' thru selection,
+ left-to-right, top-to-bottom. The row/col values are indexes into
+ the entire ring buffer.
+
+ Example: walk the characters of the mouse selection:
+
+ \par
+ \code
+ // Get selection
+ int srow,scol,erow,ecol;
+ if (get_selection(srow,scol,erow,ecol)) { // mouse selection exists?
+ // Walk entire selection from start to end
+ for (int row=srow; row<=erow; row++) { // walk rows of selection
+ const Utf8Char *u8c = u8c_ring_row(row); // ptr to first character in row
+ int col_start = (row==srow) ? scol : 0; // start row? start at scol
+ int col_end = (row==erow) ? ecol : ring_cols(); // end row? end at ecol
+ u8c += col_start; // include col offset (if any)
+ for (int col=col_start; col<=col_end; col++,u8c++) { // walk columns
+ ..do something with each char at *u8c..
+ }
+ }
+ }
+ \endcode
+
+ Returns:
+ - true -- valid selection values returned
+ - false -- no selection was made, returned values undefined
+
+ \see walk_selection(), is_selection()
+*/
+bool Fl_Terminal::get_selection(int &srow, ///< starting row for selection
+ int &scol, ///< starting column for selection
+ int &erow, ///< ending row for selection
+ int &ecol ///< ending column for selection
+ ) const {
+ return select_.get_selection(srow, scol, erow, ecol);
+}
+
+// Is global row,col (relative to ring_chars[]) inside the current mouse selection?
+// Retruns:
+// true -- (row,col) inside a valid selection.
+// false -- (row,col) outside, or no valid selection.
+//
+bool Fl_Terminal::is_inside_selection(int grow, int gcol) const {
+ if (!is_selection()) return false;
+ int ncols = ring_cols();
+ // Calculate row/col magnitudes to simplify test
+ int check = (grow * ncols) + gcol;
+ int start = (select_.srow() * ncols) + select_.scol();
+ int end = (select_.erow() * ncols) + select_.ecol();
+ if (start > end) swap(start, end); // ensure (start < end)
+ return (check >= start && check <= end);
+}
+
+// See if global row (grow) is inside the 'display' area
+//
+// No wrap case: Wrap case:
+// ______________ ______________
+// ring_srow() -> | | ring_srow() -> | [C] |
+// | | : | |
+// | | : | Display | ...
+// | History | : | | :
+// | | disp_erow() -> | [C] | :
+// | | |--------------| :
+// |______________| | | : Display
+// disp_srow() -> | [A] | | History | : straddles
+// : | | | | : end of ring
+// : | Display | |--------------| :
+// : | | disp_srow() -> | [D] | ..:
+// : | | : | Display |
+// : | | : | |
+// disp_erow ┬─> | [B] | ring_erow() -> | [D] |
+// ring_erow ┘ -------------- --------------
+//
+bool Fl_Terminal::is_disp_ring_row(int grow) const {
+ return ring_.is_disp_ring_row(grow);
+}
+
+// Return byte length of all UTF-8 chars in selection, or 0 if no selection.
+// NOTE: Length includes trailing white on each line.
+//
+int Fl_Terminal::selection_text_len(void) const {
+ int row,col,len=0;
+ const Utf8Char *u8c = NULL; // start with NULL to begin walk
+ while ((u8c = walk_selection(u8c, row, col))) // loop until end char reached
+ len += u8c->length();
+ return len;
+}
+
+// Return text selection (for copy()/paste() operations)
+// Returns allocated NULL terminated string for entire selection.
+// Caller must free() this memory when done.
+// Unicode safe.
+//
+const char* Fl_Terminal::selection_text(void) const {
+ if (!is_selection()) return fl_strdup(""); // no selection? empty string
+ // Allocate buff large enough for all UTF-8 chars
+ int clen = 0; // char length
+ int buflen = selection_text_len();
+ char *buf = (char*)malloc(buflen+1); // +1 for NULL
+ char *bufp = buf;
+ char *nspc = bufp; // last 'non-space' char
+ // Loop from srow,scol .. erow,ecol
+ int row,col;
+ const Utf8Char *u8c = NULL; // start with NULL to begin walk
+ while ((u8c = walk_selection(u8c, row, col))) { // loop until end char reached
+ clen = u8c->length(); // get char length
+ memcpy(bufp, u8c->text_utf8(), clen); // append UTF-8 string to buffer
+ // Handle ignoring trailing whitespace
+ if (!u8c->is_char(' ')) nspc = bufp + clen; // save end pos of last non-spc
+ bufp += clen; // advance into buffer
+ if (col >= (ring_cols()-1)) { // eol? handle trailing white
+ if (nspc && nspc != bufp) { // trailing white space?
+ bufp = nspc; // rewind bufp, and..
+ *bufp++ = '\n'; // ..append crlf
+ nspc = bufp; // becomes new nspc for nxt row
+ }
+ }
+ }
+ *bufp = 0;
+ return buf;
+}
+
+// Clear mouse selection
+void Fl_Terminal::clear_mouse_selection(void) {
+ select_.clear();
+}
+
+// Extend selection to FLTK coords X,Y.
+// Returns true if extended, false if nothing done (X,Y offscreen)
+//
+bool Fl_Terminal::selection_extend(int X,int Y) {
+ if (is_selection()) { // selection already?
+ int grow,gcol;
+ if (xy_to_glob_rowcol(X, Y, grow, gcol) > 0) {
+ select_.extend(grow, gcol); // extend it
+ return true;
+ } else {
+ // TODO: If X,Y outside row/col area and SHIFT down,
+ // extend selection to nearest edge.
+ }
+ }
+ return false;
+}
+
+// Scroll the display up(+) or down(-) number of rows.
+// Negative row value scrolls "down", clearing top line, and history unaffected.
+// Postive row value scrolls "up", clearing bottom line, rotating top line into history.
+//
+void Fl_Terminal::scroll(int rows) {
+ // Scroll the ring
+ ring_.scroll(rows, current_style_);
+ if (rows > 0) update_scrollbar(); // scroll up? changes hist, so scrollbar affected
+ else clear_mouse_selection(); // scroll dn? clear mouse select; it might wrap ring
+}
+
+// Insert (count) rows at cursor position.
+// Causes rows below to scroll down, and empty lines created.
+// Scrolling does not involve history at all.
+//
+void Fl_Terminal::insert_rows(int count) {
+ int dst_drow = disp_rows()-1; // dst is bottom of display
+ int src_drow = clamp((dst_drow-count), 1, (disp_rows()-1)); // src is count lines up from dst
+ while ( src_drow >= cursor_.row() ) { // walk srcrow upwards to cursor row
+ Utf8Char *src = u8c_disp_row(src_drow--);
+ Utf8Char *dst = u8c_disp_row(dst_drow--);
+ for (int dcol=0; dcol<disp_cols(); dcol++) *dst++ = *src++; // move
+ }
+ // Blank remaining rows upwards to and including cursor line
+ while ( dst_drow >= cursor_.row() ) { // walk srcrow to curs line
+ Utf8Char *dst = u8c_disp_row(dst_drow--);
+ for (int dcol=0; dcol<disp_cols(); dcol++)
+ dst++->clear(current_style_);
+ }
+ clear_mouse_selection();
+}
+
+// Delete (count) rows at cursor position.
+// Causes rows to scroll up, and empty lines created at bottom of screen.
+// Scrolling does not involve history at all.
+//
+void Fl_Terminal::delete_rows(int count) {
+ int dst_drow = cursor_.row(); // dst is cursor row
+ int src_drow = clamp((dst_drow+count), 1, (disp_rows()-1)); // src is count rows below cursor
+ while ( src_drow < disp_rows() ) { // walk srcrow to EOD
+ Utf8Char *src = u8c_disp_row(src_drow++);
+ Utf8Char *dst = u8c_disp_row(dst_drow++);
+ for (int dcol=0; dcol<disp_cols(); dcol++)
+ *dst++ = *src++; // move
+ }
+ // Blank remaining rows downwards to End Of Display
+ while ( dst_drow < disp_rows() ) { // walk srcrow to EOD
+ Utf8Char *dst = u8c_disp_row(dst_drow++);
+ for (int dcol=0; dcol<disp_cols(); dcol++)
+ dst++->clear(current_style_);
+ }
+ clear_mouse_selection();
+}
+
+// Repeat printing char 'c' for 'rep' times, not to exceed end of line.
+void Fl_Terminal::repeat_char(char c, int rep) {
+ rep = clamp(rep, 1, disp_cols());
+ while ( rep-- > 0 && cursor_.col() < disp_cols() ) print_char(c);
+}
+
+// Insert char 'c' for 'rep' times at display (drow,dcol).
+void Fl_Terminal::insert_char_eol(char c, int drow, int dcol, int rep) {
+ // Walk the row from the eol backwards to the col position
+ // In this example, rep=3:
+ //
+ // dcol
+ // v
+ // BEFORE: |a|b|c|d|e|f|g|h|i|j| <- eol (disp_cols()-1)
+ // | | | | |_____
+ // src end -> |_____ | <-- src start
+ // | | | | |
+ // v v v v v
+ // AFTER: |a|b|‸|‸|‸|c|d|e|f|g|
+ // |_|_| <-- spaces added last
+ //
+ rep = clamp(rep, 0, disp_cols()); // sanity
+ if (rep == 0) return;
+ const CharStyle &style = current_style_;
+ Utf8Char *src = u8c_disp_row(drow)+disp_cols()-1-rep; // start src at 'g'
+ Utf8Char *dst = u8c_disp_row(drow)+disp_cols()-1; // start dst at 'j'
+ for (int col=(disp_cols()-1); col>=dcol; col--) { // loop col in reverse: eol -> dcol
+ if (col >= (dcol+rep)) *dst-- = *src--; // let assignment do move
+ else (dst--)->clear(style); // clear chars displaced
+ }
+}
+
+// Insert char 'c' for 'rep' times.
+// Does not wrap; characters at end of line are lost.
+//
+void Fl_Terminal::insert_char(char c, int rep) {
+ insert_char_eol(c, cursor_.row(), cursor_.col(), rep);
+}
+
+// Delete char(s) at (drow,dcol) for 'rep' times.
+void Fl_Terminal::delete_chars(int drow, int dcol, int rep) {
+ rep = clamp(rep, 0, disp_cols()); // sanity
+ if (rep == 0) return;
+ const CharStyle &style = current_style_;
+ Utf8Char *u8c = u8c_disp_row(drow);
+ for (int col=dcol; col<disp_cols(); col++) // delete left-to-right
+ if (col+rep >= disp_cols()) u8c[col].text_ascii(' ', style); // blanks
+ else u8c[col] = u8c[col+rep]; // move
+}
+
+// Delete char(s) at cursor position for 'rep' times.
+void Fl_Terminal::delete_chars(int rep) {
+ delete_chars(cursor_.row(), cursor_.col(), rep);
+}
+
+/**
+ Clears the scroll history buffer and adjusts scrollbar,
+ forcing it to redraw().
+*/
+void Fl_Terminal::clear_history(void) {
+ // Adjust history use
+ ring_.clear_hist();
+ vscroll_->value(0); // zero scroll position
+ // Clear entire history buffer
+ for (int hrow=0; hrow<hist_rows(); hrow++) {
+ Utf8Char *u8c = u8c_hist_row(hrow); // walk history rows..
+ for (int hcol=0; hcol<hist_cols(); hcol++) { // ..and history cols
+ (u8c++)->clear(current_style_);
+ }
+ }
+ // Adjust scrollbar (hist_use changed)
+ update_scrollbar();
+}
+
+/**
+ Resets terminal to default colors, clears screen, history and
+ mouse selection, homes cursor, resets tabstops. Same as \c "<ESC>c"
+*/
+void Fl_Terminal::reset_terminal(void) {
+ current_style_.sgr_reset(); // reset current style
+ clear_screen_home(); // clear screen, home cursor
+ clear_history();
+ clear_mouse_selection();
+ default_tabstops(); // reset tabstops to default 8 char
+}
+
+//DEBUG void Fl_Terminal::RingBuffer::show_ring_info(void) const {
+//DEBUG ::printf("\033[s"); // save cursor
+//DEBUG ::printf("\033[05C -- Ring Index\n");
+//DEBUG ::printf("\033[05C ring_rows_: %d\n", ring_rows_);
+//DEBUG ::printf("\033[05C ring_cols_: %d\n", ring_cols_);
+//DEBUG ::printf("\033[05C offset_: %d\n", offset_);
+//DEBUG ::printf("\033[u"); // recall cursor
+//DEBUG ::printf("\033[30C -- History Index\n");
+//DEBUG ::printf("\033[30C hist_rows_: %d srow=%d\n", hist_rows(), hist_srow());
+//DEBUG ::printf("\033[30C hist_cols_: %d erow=%d\n", hist_cols(), hist_erow());
+//DEBUG ::printf("\033[30C hist_use_: %d\n", hist_use());
+//DEBUG ::printf("\033[u"); // recall cursor
+//DEBUG ::printf("\033[60C -- Display Index\n");
+//DEBUG ::printf("\033[60C disp_rows_: %d srow=%d\n", disp_rows(), disp_srow());
+//DEBUG ::printf("\033[60C disp_cols_: %d erow=%d\n", disp_cols(), disp_erow());
+//DEBUG ::printf("\n\n");
+//DEBUG }
+
+//DEBUG // Save specified row from ring buffer 'ring' to FILE*
+//DEBUG void Fl_Terminal::write_row(FILE *fp, Utf8Char *u8c, int cols) const {
+//DEBUG cols = (cols != 0) ? cols : ring_cols();
+//DEBUG for ( int col=0; col<cols; col++, u8c++ ) {
+//DEBUG ::fprintf(fp, "%.*s", u8c->length(), u8c->text_utf8());
+//DEBUG }
+//DEBUG }
+
+//DEBUG // Show two buffers side-by-side on stdout.
+//DEBUG // Second buffer can be NULL to just show the a buffer.
+//DEBUG //
+//DEBUG void Fl_Terminal::show_buffers(RingBuffer *a, RingBuffer *b) const {
+//DEBUG int arows = a->ring_rows(), acols = a->ring_cols();
+//DEBUG int brows = b ? b->ring_rows() : 0, bcols = b ? b->ring_cols() : 0;
+//DEBUG int trows = MAX(arows,brows);
+//DEBUG // Show header
+//DEBUG ::printf("\033[H");
+//DEBUG if (a) ::printf("SRC %d x %d huse=%d off=%d", arows,acols, a->hist_use(), a->offset());
+//DEBUG if (b) ::printf(", DST %d x %d huse=%d off=%d", brows, bcols, b->hist_use(), b->offset());
+//DEBUG ::printf("\033[K\n");
+//DEBUG Utf8Char *u8c;
+//DEBUG // Show rows
+//DEBUG for (int row=0; row < trows; row++) {
+//DEBUG // 'A' buffer
+//DEBUG if (row >= arows) {
+//DEBUG ::printf(" %*s ", acols, "");
+//DEBUG } else {
+//DEBUG u8c = a->ring_chars()+(arows*acols);
+//DEBUG ::printf("%3d/%3d [", row, trows-1); write_row(stdout, u8c, acols); ::printf("] ");
+//DEBUG }
+//DEBUG if (!b) { ::printf("\033[K\n"); continue; }
+//DEBUG // 'B' buffer
+//DEBUG if (row < brows) {
+//DEBUG u8c = b->ring_chars()+(brows*bcols);
+//DEBUG ::printf("["); write_row(stdout, u8c, bcols); ::printf("]");
+//DEBUG }
+//DEBUG ::printf("\033[K\n");
+//DEBUG }
+//DEBUG ::printf("--- END\033[0J\n"); // clear eos
+//DEBUG ::printf(" HIT ENTER TO CONTINUE: "); getchar();
+//DEBUG fflush(stdout);
+//DEBUG }
+
+///////////////////////////////
+////// CURSOR MANAGEMENT //////
+///////////////////////////////
+
+/** Set the cursor's foreground color used for text under the cursor. */
+void Fl_Terminal::cursorfgcolor(Fl_Color val) { cursor_.fgcolor(val); }
+/** Set the cursor's background color used for the cursor itself. */
+void Fl_Terminal::cursorbgcolor(Fl_Color val) { cursor_.bgcolor(val); }
+/** Get the cursor's foreground color used for text under the cursor. */
+Fl_Color Fl_Terminal::cursorfgcolor(void) const { return cursor_.fgcolor(); }
+/** Get the cursor's background color used for the cursor itself. */
+Fl_Color Fl_Terminal::cursorbgcolor(void) const { return cursor_.bgcolor(); }
+
+/**
+ Move cursor to the specified row \p row.
+ This value is clamped to the range (0..display_rows()-1).
+*/
+void Fl_Terminal::cursor_row(int row) { cursor_.row( clamp(row,0,disp_rows()-1) ); }
+/** Move cursor to the specified column \p col.
+ This value is clamped to the range (0..display_columns()-1).
+*/
+void Fl_Terminal::cursor_col(int col) { cursor_.col( clamp(col,0,disp_cols()-1) ); }
+/** Return the cursor's current row position on the screen. */
+int Fl_Terminal::cursor_row(void) const { return cursor_.row(); }
+/** Return the cursor's current column position on the screen. */
+int Fl_Terminal::cursor_col(void) const { return cursor_.col(); }
+
+/**
+ Moves cursor up \p count lines.
+ If cursor hits screen top, it either stops (does not wrap) if \p do_scroll
+ is false, or scrolls down if \p do_scroll is true.
+*/
+void Fl_Terminal::cursor_up(int count, bool do_scroll) {
+ count = clamp(count, 1, disp_rows() * 2); // sanity (max 2 scrns)
+ while (count-- > 0) {
+ if (cursor_.up() <= 0) { // hit screen top?
+ cursor_.row(0); // clamp cursor to top
+ if (do_scroll) scroll(-1); // scrolling on? scroll down
+ else return; // scrolling off? stop at top
+ }
+ }
+}
+
+/**
+ Moves cursor down \p count lines.
+ If cursor hits screen bottom, it either stops (does not wrap) if \p do_scroll
+ is false, or wraps and scrolls up if \p do_scroll is true.
+*/
+void Fl_Terminal::cursor_down(int count, ///< Number of lines to move cursor down
+ bool do_scroll ///< Enable scrolling if set to true
+ ) {
+ count = clamp(count, 1, ring_rows()); // sanity
+ while (count-- > 0) {
+ if (cursor_.down() >= disp_rows()) { // hit screen bottom?
+ cursor_.row(disp_rows() - 1); // clamp
+ if (!do_scroll) break; // don't scroll? done
+ scroll(1); // scroll up 1 row to make room for new line
+ }
+ }
+}
+
+/**
+ Moves cursor left \p count columns, and cursor stops (does not wrap)
+ if it hits screen edge.
+*/
+void Fl_Terminal::cursor_left(int count) {
+ count = clamp(count, 1, disp_cols()); // sanity
+ while (count-- > 0 )
+ if (cursor_.left() < 0) // hit left edge of screen?
+ { cursor_sol(); return; } // stop, done
+}
+
+/**
+ Moves cursor right \p count columns. If cursor hits right edge of screen,
+ it either stops (does not wrap) if \p do_scroll is false, or wraps and
+ scrolls up one line if \p do_scroll is true.
+*/
+void Fl_Terminal::cursor_right(int count, bool do_scroll) {
+ while (count-- > 0 ) {
+ if (cursor_.right() >= disp_cols()) { // hit right edge?
+ if (!do_scroll) // no scroll?
+ { cursor_eol(); return; } // stop at EOL, done
+ else
+ { cursor_crlf(1); } // do scroll? crlf
+ }
+ }
+}
+
+/** Move cursor to the home position (top/left). */
+void Fl_Terminal::cursor_home(void) { cursor_.col(0); cursor_.row(0); }
+
+/** Move cursor to the last column (at the far right) on the current line. */
+void Fl_Terminal::cursor_eol(void) { cursor_.col(disp_cols()-1); }
+
+/** Move cursor to the first column (at the far left) on the current line. */
+void Fl_Terminal::cursor_sol(void) { cursor_.col(0); }
+
+/** Move cursor as if a CR (\\r) was received. Same as cursor_sol() */
+void Fl_Terminal::cursor_cr(void) { cursor_sol(); }
+
+/** Move cursor as if a CR/LF pair (\\r\\n) was received. */
+void Fl_Terminal::cursor_crlf(int count) {
+ const bool do_scroll = true;
+ count = clamp(count, 1, ring_rows()); // sanity
+ cursor_sol();
+ cursor_down(count, do_scroll);
+}
+
+// Tab right, do not wrap beyond right edge
+void Fl_Terminal::cursor_tab_right(int count) {
+ count = clamp(count, 1, disp_cols()); // sanity
+ int X = cursor_.col();
+ while ( count-- > 0 ) {
+ // Find next tabstop
+ while ( ++X < disp_cols() ) {
+ if ( (X<tabstops_size_) && tabstops_[X] ) // found?
+ { cursor_.col(X); return; } // move cur, done
+ }
+ }
+ cursor_eol();
+}
+
+// Tab left, do not wrap beyond left edge
+void Fl_Terminal::cursor_tab_left(int count) {
+ count = clamp(count, 1, disp_cols()); // sanity
+ int X = cursor_.col();
+ while ( count-- > 0 )
+ while ( --X > 0 ) // search for tabstop
+ if ( (X<tabstops_size_) && tabstops_[X] ) // found?
+ { cursor_.col(X); return; } // move cur, done
+ cursor_sol();
+}
+
+// Save current cursor position
+void Fl_Terminal::save_cursor(void) {
+ escseq.save_cursor(cursor_.row(), cursor_.col());
+}
+
+// Restore previously saved cursor position, if there was one
+void Fl_Terminal::restore_cursor(void) {
+ int row,col;
+ escseq.restore_cursor(row, col);
+ if (row != -1 && col != 1) // restore only if save first
+ { cursor_.row(row); cursor_.col(col); }
+}
+
+//////////////////////
+////// PRINTING //////
+//////////////////////
+
+void Fl_Terminal::handle_ctrl(char c) {
+ switch (c) {
+ case '\n': cursor_crlf(); return; // CRLF?
+ case '\b': cursor_left(); return; // BS?
+ case '\r': cursor_cr(); return; // CR?
+ case '\t': cursor_tab_right(); return; // TAB?
+ case 0x1b: if (ansi_) escseq.parse(c); // ESC?
+ else append_utf8("␛");
+ return;
+ default: handle_unknown_char(); return; // Unknown ctrl char?
+ }
+}
+
+// Is char printable? (0x20 thru 0x7e)
+bool Fl_Terminal::is_printable(char c) {
+ return ((c>=0x20) && (c<=0x7e));
+}
+
+// Is char a ctrl character? (0x00 thru 0x1f)
+bool Fl_Terminal::is_ctrl(char c) {
+ return ((c >= 0x00) && (c < 0x20)) ? true : false;
+}
+
+// Handle ESC[m sequences.
+// This is different from the others in that the list of vals
+// separated by ;'s can be long, to allow combining multiple mode
+// settings at once, e.g. fg and bg colors, multiple attributes, etc.
+//
+void Fl_Terminal::handle_SGR(void) { // ESC[...m?
+ // Shortcut varnames..
+ EscapeSeq &esc = escseq;
+ int tot = esc.total_vals();
+ // Handle ESC[m or ESC[;m
+ if (tot == 0)
+ { current_style_.sgr_reset(); return; }
+ // Handle ESC[#;#;#...m
+ int rgbcode = 0; // 0=none, 38=fg, 48=bg
+ int rgbmode = 0; // 0=none, 1="2", 2=<r>, 3=<g>, 4=<b>
+ int r=0,g=0,b=0;
+ for (int i=0; i<tot; i++) { // expect possibly many values
+ int val = esc.val(i); // each val one at a time
+ switch ( rgbmode ) {
+ case 0:
+ // RGB mode values?
+ switch (val) {
+ case 38: // fg RGB mode? e.g. ESC[38;2;<R>;<G>;<B>m
+ case 48: // bg RGB mode? e.g. ESC[48;2;<R>;<G>;<B>m
+ rgbmode = 1;
+ rgbcode = val;
+ continue;
+ }
+ break;
+ case 1: if (val == 2) { rgbmode++; continue; } // '2'?
+ rgbcode = rgbmode = 0; // not '2'? cancel
+ handle_unknown_char();
+ break;
+ case 2: r=clamp(val,0,255); ++rgbmode; continue; // parse red value
+ case 3: g=clamp(val,0,255); ++rgbmode; continue; // parse grn value
+ case 4: b=clamp(val,0,255); // parse blu value
+ switch (rgbcode) {
+ case 38: current_style_.fgcolor(r,g,b); // Set fg rgb
+ break;
+ case 48: current_style_.bgcolor(r,g,b); // Set bg rgb
+ break;
+ }
+ rgbcode = rgbmode = 0; // done w/rgb mode parsing
+ continue; // continue loop to parse more vals
+ }
+ if (val < 10) { // Set attribute? (bold,underline..)
+ switch ( val ) {
+ case 0: current_style_.sgr_reset(); break; // ESC[0m - reset
+ case 1: current_style_.sgr_bold(1); break; // ESC[1m - bold
+ case 2: current_style_.sgr_dim(1); break; // ESC[2m - dim
+ case 3: current_style_.sgr_italic(1); break; // ESC[3m - italic
+ case 4: current_style_.sgr_underline(1);break; // ESC[4m - underline
+ case 5: current_style_.sgr_blink(1); break; // ESC[5m - blink
+ case 6: handle_unknown_char(); break; // ESC[6m - (unused)
+ case 7: current_style_.sgr_inverse(1); break; // ESC[7m - inverse
+ case 8: handle_unknown_char(); break; // ESC[8m - (unused)
+ case 9: current_style_.sgr_strike(1); break; // ESC[9m - strikeout
+ }
+ } else if (val >= 21 && val <= 29) { // attribute extras
+ switch ( val ) {
+ case 21: current_style_.sgr_dbl_under(1);break; // ESC[21m - doubly underline
+ case 22: current_style_.sgr_dim(0); // ESC[22m - disable bold/dim
+ current_style_.sgr_bold(0); break; //
+ case 23: current_style_.sgr_italic(0); break; // ESC[23m - disable italic
+ case 24: current_style_.sgr_underline(0);break; // ESC[24m - disable underline
+ case 25: current_style_.sgr_blink(0); break; // ESC[25m - disable blink
+ case 26: handle_unknown_char(); break; // ESC[26m - (unused)
+ case 27: current_style_.sgr_inverse(0); break; // ESC[27m - disable inverse
+ case 28: handle_unknown_char(); break; // ESC[28m - disable hidden
+ case 29: current_style_.sgr_strike(0); break; // ESC[29m - disable strikeout
+ }
+ } else if (val >= 30 && val <= 37) { // Set fg color?
+ current_style_.fgcolor_uchar(val - 30);
+ } else if (val == 39) { // ESC[39m -- "normal" fg color:
+ Fl_Color fg = current_style_.defaultfgcolor(); // ..get default color
+ current_style_.fgcolor(fg); // ..set current color
+ } else if (val >= 40 && val <= 47) { // Set bg color?
+ current_style_.bgcolor_uchar(val - 40);
+ } else if (val == 49) { // ESC[49m -- "normal" bg color:
+ Fl_Color bg = current_style_.defaultbgcolor(); // ..get default bg color
+ current_style_.bgcolor(bg); // ..set current bg color
+ } else {
+ handle_unknown_char(); // does an escseq.reset() // unimplemented SGR codes
+ }
+ }
+}
+
+// Handle <ESC>[<top>;<lt>;<bot>;<rt>;<att>$t
+// This one is fun! <att> is the attrib to xor, i.e. 1(bold),4,5,7(inverse).
+// gnome-term doesn't support this, but xterm does.
+//
+void Fl_Terminal::handle_DECRARA(void) {
+ // TODO: MAYBE NEVER
+}
+
+// Handle an escape sequence character
+// If this char is the end of the sequence, do the operation
+// if possible, and then reset() to finish parsing.
+//
+void Fl_Terminal::handle_escseq(char c) {
+ // NOTE: Use xterm to test. gnome-terminal has bugs, even in 2022.
+ const bool do_scroll = true;
+ const bool no_scroll = false;
+ //UNUSED const bool do_wrap = true;
+ //UNUSED const bool no_wrap = false;
+ switch (escseq.parse(c)) { // parse char, advance s..
+ case EscapeSeq::fail: escseq.reset(); return; // failed? reset, done
+ case EscapeSeq::success: return; // keep parsing..
+ case EscapeSeq::completed: break; // parsed complete esc sequence?
+ }
+ // Shortcut varnames for escseq parsing..
+ EscapeSeq &esc = escseq;
+ char mode = esc.esc_mode();
+ int tot = esc.total_vals();
+ int val0 = (tot==0) ? 0 : esc.val(0);
+ int val1 = (tot<2) ? 0 : esc.val(1);
+ const int& dw = disp_cols();
+ const int& dh = disp_rows();
+ if (esc.is_csi()) { // Was this a CSI (ESC[..) sequence?
+ switch ( mode ) {
+ case '@': // <ESC>[#@ - (ICH) Insert blank Chars (default=1)
+ insert_char(' ', esc.defvalmax(1,dw));
+ break;
+ case 'A': // <ESC>[#A - (CUU) cursor up, no scroll/wrap
+ cursor_up(esc.defvalmax(1,dh));
+ break;
+ case 'B': // <ESC>[#B - (CUD) cursor down, no scroll/wrap
+ cursor_down(esc.defvalmax(1,dh), no_scroll);
+ break;
+ case 'C': // <ESC>[#C - (CUF) cursor right, no wrap
+ cursor_right(esc.defvalmax(1,dw), no_scroll);
+ break;
+ case 'D': // <ESC>[#D - (CUB) cursor left, no wrap
+ cursor_left(esc.defvalmax(1,dw));
+ break;
+ case 'E': // <ESC>[#E - (CNL) cursor next line (crlf) xterm, !gnome
+ cursor_crlf(esc.defvalmax(1,dh));
+ break;
+ case 'F': // <ESC>[#F - (CPL) move to sol and up # lines
+ cursor_cr();
+ cursor_up(esc.defvalmax(1,dh));
+ break;
+ case 'G': // <ESC>[#G - (CHA) cursor horizal absolute
+ switch (clamp(tot,0,1)) { // │
+ case 0: // ├── <ESC>[G -- move to sol
+ cursor_sol(); // │ default <ESC>[1G
+ break; // │
+ case 1: // └── <ESC>[#G -- move to column
+ cursor_col(clamp(val0,1,dw)-1);
+ break;
+ }
+ break;
+ case 'H':
+cup:
+ switch (clamp(tot,0,2)) { // <ESC>[#H - (CUP) cursor position (#'s are 1 based)
+ case 0: // ├── <ESC>[H -- no vals?
+ cursor_home(); // │ default <ESC>[1H
+ break; // │
+ case 1: // ├── <ESC>[#H -- go to (row #)
+ cursor_row(clamp(val0,1,dh)-1); // │ NOTE: ESC[5H == ESC[5;1H
+ cursor_col(0); // │
+ break; // │
+ case 2: // └── <ESC>[#;#H -- go to (row# ; col#)
+ cursor_row(clamp(val0,1,dh)-1);
+ cursor_col(clamp(val1,1,dw)-1);
+ break;
+ }
+ break;
+ case 'I': // <ESC>[#I - (CHT) cursor forward tab (default=1)
+ switch (clamp(tot,0,1)) { // │
+ case 0: // ├── <ESC>[I -- no vals
+ cursor_tab_right(1); // │ default <ESC>[1I
+ break; // │
+ case 1: // └── <ESC>[#I -- tab # times
+ cursor_tab_right(clamp(val0,1,dw)); //
+ break;
+ }
+ break;
+ case 'J': // <ESC>[#J - (ED) erase in display
+ switch ( clamp(tot,0,1) ) { // │
+ case 0: clear_eol(); break; // ├── <ESC>[J -- no vals: default <ESC>[0J
+ case 1: // │
+ switch ( clamp(val0,0,3) ) { // │
+ case 0: clear_eod(); break; // ├── <ESC>[0J -- clear to end of display
+ case 1: clear_sod(); break; // ├── <ESC>[1J -- clear to start of display
+ case 2: clear_screen(); break; // ├── <ESC>[2J -- clear all lines
+ case 3: clear_history(); break; // └── <ESC>[3J -- clear screen history
+ }
+ break;
+ }
+ break;
+ case 'K':
+ switch ( clamp(tot,0,1) ) { // <ESC>[#K - (EL) Erase in Line
+ case 0: clear_eol(); break; // ├── <ESC>[K -- no vals
+ case 1: switch ( clamp(val0,0,2) ) { // │
+ case 0: clear_eol(); break; // ├── <ESC>[0K -- clear to end of line
+ case 1: clear_sol(); break; // ├── <ESC>[1K -- clear to start of line
+ case 2: clear_line(); break; // └── <ESC>[2K -- clear current line
+ }
+ break;
+ }
+ break;
+ case 'L': // ESC[#L - Insert # lines (def=1)
+ insert_rows(esc.defvalmax(1,dh));
+ break;
+ case 'M': // ESC[#M - Delete # lines (def=1)
+ delete_rows(esc.defvalmax(1,dh));
+ break;
+ case 'P': // ESC[#P - Delete # chars (def=1)
+ delete_chars(esc.defvalmax(1,dh));
+ break;
+ case 'S': // ESC[#S - scroll up # lines (def=1)
+ scroll( +(esc.defvalmax(1,dh)) );
+ // ⮤ positive=scroll up
+ break;
+ case 'T': // ESC[#T - scroll dn # lines (def=1)
+ scroll( -(esc.defvalmax(1,dh)) );
+ // ⮤ negative=scroll down
+ break;
+ case 'X': // <ESC>[#X - (ECH) Erase Characters (default=1)
+ repeat_char(' ', esc.defvalmax(1,dw));
+ break;
+ case 'Z': // ESC[#Z - backtab # tabs
+ switch (clamp(tot,0,1)) { // │
+ case 0: // ├── <ESC>[Z -- no vals
+ cursor_tab_left(1); // │ default <ESC>[1Z
+ break; // │
+ case 1: // └── <ESC>[#Z -- tab # times
+ cursor_tab_left(clamp(val0,1,dw));
+ break;
+ }
+ break;
+ case 'a': // TODO // ESC[#a - (HPR) move cursor relative [columns] (default=[row,col+1])
+ case 'b': // TODO // ESC[#b - (REP) repeat prev graphics char # times
+ case 'd': // TODO // ESC[#d - (VPA) line pos absolute [row]
+ case 'e': // TODO // ESC[#e - line pos relative [rows]
+ handle_unknown_char(); // does an escseq.reset()
+ break;
+ case 'f': // <ESC>[#f - (CUP) cursor position (#'s 1 based)
+ goto cup; // (same as ESC[H)
+ case 'g': // ESC[...g? Tabulation Clear (TBC)
+ switch (val0) {
+ case 0: clear_tabstop(); break; // clears tabstop at cursor
+ case 3: clear_all_tabstops(); break; // clears all tabstops
+ default:
+ handle_unknown_char(); // does an escseq.reset()
+ break;
+ }
+ break;
+ case 'm': handle_SGR(); break; // ESC[#m - set character attributes (SGR)
+ case 's': save_cursor(); break; // ESC[s - save cur pos (xterm+gnome)
+ case 'u': restore_cursor(); break; // ESC[u - rest cur pos (xterm+gnome)
+ case 'q': // TODO? // ESC[>#q set cursor style (block/line/blink..)
+ case 'r': // TODO // ESC[#;#r set scroll region top;bot
+ // default=full window
+ handle_unknown_char(); // does an escseq.reset()
+ break;
+ case 't': handle_DECRARA(); break; // ESC[#..$t -- (DECRARA)
+ // Reverse attribs in Rect Area (row,col)
+ default:
+ handle_unknown_char(); // does an escseq.reset()
+ break;
+ }
+ } else {
+ // Not CSI? Might be C1 Control code (<ESC>D, etc)
+ switch ( esc.esc_mode() ) {
+ case 'c': // <ESC>c - Reset term to Initial State (RIS)
+ reset_terminal();
+ break;
+ case 'D': cursor_down(1, do_scroll); break;// <ESC>D - down line, scroll at bottom
+ case 'E': cursor_crlf(); break;// <ESC>E - do a crlf
+ case 'H': set_tabstop(); break;// <ESC>H - set a tabstop
+ case 'M': cursor_up(1, true); break;// <ESC>M -- (RI) Reverse Index (up w/scroll)
+ default:
+ handle_unknown_char(); // does an escseq.reset()
+ break;
+ }
+ }
+ esc.reset(); // done handling escseq, reset()
+}
+
+void Fl_Terminal::display_modified_clear(void) {
+ redraw_modified_ = false;
+}
+
+// Display modified, trigger redraw handling..
+void Fl_Terminal::display_modified(void) {
+ if (is_redraw_style(RATE_LIMITED)) {
+ if (!redraw_modified_) { // wasn't before but now is?
+ if (!redraw_timer_) {
+ Fl::add_timeout(.01, redraw_timer_cb, this); // turn on timer
+ redraw_timer_ = true;
+ }
+ redraw_modified_ = true;
+ }
+ } else if (is_redraw_style(PER_WRITE)) {
+ if (!redraw_modified_) {
+ redraw_modified_ = true;
+ redraw(); // only call redraw once
+ }
+ } else { // NO_REDRAW?
+ // do nothing
+ }
+}
+
+/**
+ Clear the character at the specified display row and column.
+
+ No range checking done on drow,dcol:
+ - \p drow must be in range 0..(disp_rows()-1)
+ - \p dcol must be in range 0..(disp_cols()-1)
+
+ - Does not trigger redraws
+*/
+void Fl_Terminal::clear_char_at_disp(int drow, int dcol) {
+ Utf8Char *u8c = u8c_disp_row(drow) + dcol;
+ u8c->clear(current_style_);
+}
+
+/**
+ Return Utf8Char* for char at specified display row and column.
+ This accesses any character in the display part of the ring buffer.
+
+ No range checking done on drow,dcol:
+ - \p drow must be in range 0..(disp_rows()-1)
+ - \p dcol must be in range 0..(disp_cols()-1)
+
+ \see u8c_disp_row()
+*/
+const Fl_Terminal::Utf8Char* Fl_Terminal::utf8_char_at_disp(int drow, int dcol) const {
+ return u8c_disp_row(drow) + dcol;
+}
+
+/**
+ Return Utf8Char* for char at specified global (grow,gcol).
+ This accesses any character in the ring buffer (history + display).
+
+ No range checking done on grow,gcol:
+ - \p grow must be in range 0..(ring_rows()-1)
+ - \p gcol must be in range 0..(ring_cols()-1)
+
+ \see u8c_ring_row()
+*/
+const Fl_Terminal::Utf8Char* Fl_Terminal::utf8_char_at_glob(int grow, int gcol) const {
+ return u8c_ring_row(grow) + gcol;
+}
+
+/**
+ Print UTF-8 character \p text of length \p len at display position \p (drow,dcol).
+ The character is displayed using the current text color/attributes.
+
+ This is a very low level method.
+
+ No range checking is done on drow,dcol:
+ - \p drow must be in range 0..(display_rows()-1)
+ - \p dcol must be in range 0..(display_columns()-1)
+
+ - Does not trigger redraws
+ - Does not handle ANSI or XTERM escape sequences
+ - Invalid UTF-8 chars show the error character (¿) depending on show_unknown(bool).
+
+ \see handle_unknown_char()
+*/
+void Fl_Terminal::putchar(const char *text, int len, int drow, int dcol) {
+ Utf8Char *u8c = u8c_disp_row(drow) + dcol;
+ // text_utf8() warns we must do invalid checks first
+ if (!text || len<1 || len>u8c->max_utf8() || len!=fl_utf8len(*text))
+ { handle_unknown_char(); return; }
+ u8c->text_utf8(text, len, current_style_);
+}
+
+/**
+ Print the ASCII character \p c at the terminal's display position \p (drow,dcol).
+
+ The character MUST be printable (in range 0x20 - 0x7e), and is displayed
+ using the current text color/attributes. Characters outside that range are either
+ ignored or print the error character (¿), depending on show_unknown(bool).
+
+ This is a very low level method.
+
+ No range checking is done on drow,dcol:
+ - \p drow must be in range 0..(display_rows()-1)
+ - \p dcol must be in range 0..(display_columns()-1)
+
+ - Does not trigger redraws
+ - Does NOT handle control codes, ANSI or XTERM escape sequences.
+
+ \see show_unknown(bool), handle_unknown_char(), is_printable()
+*/
+void Fl_Terminal::putchar(char c, int drow, int dcol) {
+ if (!is_printable(c)) { handle_unknown_char(); return; }
+ Utf8Char *u8c = u8c_disp_row(drow) + dcol;
+ u8c->text_ascii(c, current_style_);
+}
+
+/**
+ Prints single UTF-8 char \p text of optional byte length \p len
+ at current cursor position, and advances the cursor if the character
+ is printable. Handles ASCII and control codes (CR, LF, etc).
+
+ The character is displayed at the current cursor position
+ using the current text color/attributes.
+
+ Handles control codes and can be used to construct ANSI/XTERM
+ escape sequences.
+
+ - If optional \p len isn't specified or <0, strlen(text) is used.
+ - \p text must not be NULL.
+ - \p len must not be 0.
+ - \p text must be a single char only (whether UTF-8 or ASCII)
+ - \p text can be an ASCII character, though not as efficent as print_char()
+ - Invalid UTF-8 chars show the error character (¿) depending on show_unknown(bool).
+ - Does not trigger redraws
+
+ \see show_unknown(bool), handle_unknown_char()
+*/
+void Fl_Terminal::print_char(const char *text, int len/*=-1*/) {
+ len = len<0 ? fl_utf8len(*text) : len; // int(strlen(text)) : len;
+ const bool do_scroll = true;
+ if (is_ctrl(text[0])) { // Handle ctrl character
+ handle_ctrl(*text);
+ } else if (escseq.parse_in_progress()) { // ESC sequence in progress?
+ handle_escseq(*text);
+ } else { // Handle printable char..
+ putchar(text, len, cursor_row(), cursor_col());
+ cursor_right(1, do_scroll);
+ }
+}
+
+/**
+ Prints single ASCII char \p c at current cursor position, and advances the cursor.
+
+ The character is displayed at the current cursor position
+ using the current text color/attributes.
+
+ - \p c must be ASCII, not utf-8
+ - Does not trigger redraws
+*/
+void Fl_Terminal::print_char(char c) {
+ const bool do_scroll = true;
+ if (is_ctrl(c)) { // Handle ctrl character
+ handle_ctrl(c);
+ } else if (escseq.parse_in_progress()) { // ESC sequence in progress?
+ handle_escseq(c);
+ } else { // Handle printable char..
+ putchar(c, cursor_row(), cursor_col());
+ cursor_right(1, do_scroll);
+ return;
+ }
+}
+
+// Clear the Partial UTF-8 Buffer cache
+void Fl_Terminal::utf8_cache_clear(void) {
+ pub_.clear();
+}
+
+// Flush the Partial UTF-8 Buffer cache, and clear
+void Fl_Terminal::utf8_cache_flush(void) {
+ if (pub_.buflen() > 0) print_char(pub_.buf(), pub_.buflen());
+ pub_.clear();
+}
+
+/**
+ Append NULL terminated UTF-8 string to terminal.
+
+ - If buf is NULL, UTF-8 cache buffer is cleared
+ - If optional \p len isn't specified or is -1, strlen(text) is used.
+ - If \p len is 0 or <-1, no changes are made
+ - Handles UTF-8 chars split across calls (e.g. block writes from pipes, etc)
+ - Redraws are triggered automatically, depending on redraw_style()
+*/
+void Fl_Terminal::append_utf8(const char *buf, int len/*=-1*/) {
+ int mod = 0; // assume no modifications
+ if (!buf) { utf8_cache_clear(); return; } // clear cache, done
+ if (len == -1) len = int(strlen(buf)); // len optional
+ if (len<=0) return; // bad len? early exit
+
+ // Handle any partial UTF-8 from last write
+ // Try to parse up rest of incomplete buffered char from end
+ // of last block, and flush it to terminal.
+ //
+ if (pub_.buflen() > 0) { // partial UTF-8 to deal with?
+ while (len>0 && pub_.is_continuation(*buf)) { // buffer 'continuation' chars
+ if (pub_.append(buf, 1) == false) // append byte to partial UTF-8 buffer
+ { mod |= handle_unknown_char(); break; } // overrun? break loop
+ else { buf++; len--; } // shrink our buffer
+ }
+ if (pub_.is_complete()) utf8_cache_flush(); // complete UTF-8 captured? flush to tty
+ if (len <= 0) { // check len again, we may have run out
+ if (mod) display_modified();
+ return;
+ }
+ }
+
+ // For sure buf is now pointing at a valid char, so walk to end of buffer
+ int clen; // char length
+ const char *p = buf; // ptr to walk buffer
+ while (len>0) {
+ clen = fl_utf8len(*p); // how many bytes long is this char?
+ if (clen == -1) { // not expecting bad UTF-8 here
+ mod |= handle_unknown_char();
+ p += 1;
+ len -= 1;
+ } else {
+ if (len && clen>len) { // char longer than buffer?
+ if (pub_.append(p, len) == false) { // buffer it
+ mod |= handle_unknown_char();
+ utf8_cache_clear();
+ }
+ break;
+ }
+ print_char(p, clen); // write complete UTF-8 char to terminal
+ p += clen; // advance to next char
+ len -= clen; // adjust len
+ mod |= 1;
+ }
+ }
+ if (mod) display_modified();
+}
+
+/**
+ Append NULL terminated ASCII string to terminal,
+ slightly more efficient than append_utf8().
+
+ - If \p s is NULL, behavior is to do nothing
+ - Redraws are triggered automatically, depending on redraw_style()
+*/
+void Fl_Terminal::append_ascii(const char *s) {
+ if (!s) return;
+ while ( *s ) print_char(*s++); // handles display_modified()
+ display_modified();
+}
+
+/**
+ Appends string \p s to the terminal at the current cursor position
+ using the current text color/attributes.
+
+ If \p s is NULL, the UTF-8 character cache is cleared, which is
+ recommended before starting a block reading loop, and again after the
+ block loop has completed.
+
+ If \p len is not specified, it's assumed \p s is a NULL terminated
+ string. If \p len IS specified, it can be used for writing strings
+ that aren't NULL terminated, such as block reads on a pipe, network,
+ or other block oriented data source.
+
+ Redraws of the terminal widget are by default handled automatically,
+ but can be changed with redraw_rate() and redraw_style().
+
+ <B>Block I/O</B>
+
+ When reading block oriented sources (such as pipes), append() will
+ handle partial UTF-8 chars straddling the block boundaries. It does
+ this using an internal byte cache, which should be cleared before
+ and after block I/O loops by calling <TT>append(NULL)</TT> as shown
+ in the example below, to prevent the possibilities of partial UTF-8
+ characters left behind by an interrupted or incomplete block loop.
+
+ \par
+ \code
+ // Example block reading a command pipe in Unix
+
+ // Run command and read as a pipe
+ FILE *fp = popen("ls -la", "r");
+ if (!fp) { ..error_handling.. }
+
+ // Enable non-blocking I/O
+ int fd = fileno(fp);
+ fcntl(fd, F_SETFL, O_NONBLOCK);
+
+ // Clear UTF-8 character cache before starting block loop
+ G_tty->append(NULL); // prevents leftover partial UTF-8 bytes
+
+ // Block read loop
+ while (1) {
+ Fl::wait(0.05); // give fltk .05 secs of cpu to manage UI
+ ssize_t bytes = read(fd, s, sizeof(s)); // read block from pipe
+ if (bytes == -1 && errno == EAGAIN) continue; // no data yet? continue
+ if (bytes > 0) G_tty->append(s); // append output to terminal
+ else break; // end of pipe?
+ }
+
+ // Flush cache again after block loop completes
+ G_tty->append(NULL);
+
+ // Close pipe, done
+ pclose(fp);
+
+ \endcode
+
+ \note
+ - String can contain ASCII or UTF-8 chars
+ - \p len is optional; if unspecified, expects \p s to be a NULL terminated string
+ - Handles partial UTF-8 chars split between calls (e.g. block oriented writes)
+ - If \p s is NULL, this clears the "partial UTF-8" character cache
+ - Redraws are managed automatically by default; see redraw_style()
+*/
+void Fl_Terminal::append(const char *s, int len/*=-1*/) {
+ append_utf8(s, len);
+}
+
+/**
+ Handle an unknown char by either emitting an error symbol to the tty, or do nothing,
+ depending on the user configurable value of show_unknown().
+ Returns 1 if tty modified, 0 if not.
+ \see show_unknown()
+*/
+int Fl_Terminal::handle_unknown_char(void) {
+ const char *unknown = "¿";
+ if (show_unknown_) {
+ escseq.reset(); // disable any pending esc seq to prevent eating unknown char
+ print_char(unknown);
+ return 1;
+ }
+ return 0;
+}
+
+// Handle user interactive scrolling
+void Fl_Terminal::scrollbar_cb(Fl_Widget*, void* userdata) {
+ Fl_Terminal *o = (Fl_Terminal*)userdata;
+ o->redraw();
+}
+
+// Handle mouse selection autoscrolling
+void Fl_Terminal::autoscroll_timer_cb2(void) {
+ // Move scrollbar
+ // NOTE: vscroll is inverted; 0=tab at bot, so minimum() is really max
+ //
+ int amt = autoscroll_amt_; // (amt<0):above top, (amt>0):below bottom
+ int val = vscroll_->value();
+ int max = int(vscroll_->minimum()+.5); // NOTE: minimum() is really max
+ val = (amt<0) ? (val+clamp((-amt/10),1,5)) : // above top edge?
+ (amt>0) ? (val-clamp((+amt/10),1,5)) : 0; // below bot edge?
+ val = clamp(val,0,max); // limit val to scroll's range
+ int diff = ABS(val - vscroll_->value()); // how far scroll tab moved up/dn
+ // Move scrollbar
+ vscroll_->value(val);
+ // Extend selection
+ if (diff) { // >0 if up or down
+ int srow = select_.srow(), scol = select_.scol();
+ int erow = select_.erow(), ecol = select_.ecol();
+ int ltcol = 0, rtcol = ring_cols() - 1;
+ if (amt<0) { erow -= diff; ecol = ltcol; } // above top? use erow: reverse-selecting
+ if (amt>0) { erow += diff; ecol = rtcol; } // below bot? use erow: forward-selecting
+ select_.select(srow, scol, erow, ecol);
+ }
+ // Restart timeout
+ Fl::repeat_timeout(.1, autoscroll_timer_cb, this);
+ redraw();
+}
+
+// Handle mouse selection autoscrolling
+void Fl_Terminal::autoscroll_timer_cb(void *udata) {
+ Fl_Terminal *tty = (Fl_Terminal*)udata;
+ tty->autoscroll_timer_cb2();
+}
+
+// Handle triggering rate limited redraw() updates
+// When data comes in quickly, append() sets the redraw_modified_ flag
+// so our timer can trigger the redraw()s at a controlled rate.
+//
+void Fl_Terminal::redraw_timer_cb2(void) {
+ //DRAWDEBUG ::printf("--- UPDATE TICK %.02f\n", redraw_rate_); fflush(stdout);
+ if (redraw_modified_) {
+ redraw(); // Timer triggered redraw
+ redraw_modified_ = false; // acknowledge modified flag
+ Fl::repeat_timeout(redraw_rate_, redraw_timer_cb, this); // restart timer
+ } else {
+ // Timer went off and nothing to redraw? disable
+ Fl::remove_timeout(redraw_timer_cb, this);
+ redraw_timer_ = false;
+ }
+}
+
+void Fl_Terminal::redraw_timer_cb(void *udata) {
+ Fl_Terminal *tty = (Fl_Terminal*)udata;
+ tty->redraw_timer_cb2();
+}
+
+/**
+ The constructor for Fl_Terminal.
+
+ This creates an empty terminal with defaults:
+ - white on black text; see textfgcolor(Fl_Color), textbgcolor(Fl_Color)
+ - rows/cols based on the \p W and \p H values, see display_rows(), display_columns()
+ - scrollback history of 100 lines, see history_rows()
+ - redraw_style() set to RATE_LIMITED, redraw_rate() set to 0.10 seconds
+
+ Note: While Fl_Terminal derives from Fl_Group, it's not intended for user code
+ to use it as a parent for other widgets, so end() is called.
+
+ \param[in] X,Y,W,H position and size.
+ \param[in] L label string (optional), may be NULL.
+*/
+Fl_Terminal::Fl_Terminal(int X,int Y,int W,int H,const char*L) : Fl_Group(X,Y,W,H,L) {
+ // scrollbar_size must be set before scrn_
+ scrollbar_size_ = 0; // 0 uses Fl::scrollbar_size()
+ update_screen_xywh();
+ // Tabs
+ tabstops_ = 0;
+ tabstops_size_ = 0;
+ // Init ringbuffer. Also creates default tabstops
+ create_ring(h_to_row(scrn_.h()), // desired row
+ w_to_col(scrn_.w()), // desired col
+ 100); // history size: use 100 for production release
+ // Misc
+ redraw_style_ = RATE_LIMITED; // NO_REDRAW, RATE_LIMITED, PER_WRITE
+ redraw_rate_ = 0.10f; // maximum rate in seconds (1/10=10fps)
+ redraw_modified_ = false; // display 'modified' flag
+ redraw_timer_ = false;
+ autoscroll_dir_ = 0;
+ autoscroll_amt_ = 0;
+
+ // Create scrollbar
+ // Final position/size will be set by update_screen() below
+ //
+ vscroll_ = new Fl_Scrollbar(x(), y(), scrollbar_size_, h());
+ vscroll_->type(FL_VERTICAL);
+ vscroll_->linesize(1);
+ vscroll_->slider_size(1);
+ vscroll_->range(0.0, 0.0);
+ vscroll_->value(0);
+ vscroll_->callback(scrollbar_cb, (void*)this);
+ resizable(0);
+ Fl_Group::box(FL_DOWN_FRAME);
+ color(0x0); // black bg by default
+ update_screen(true); // update internal vars after setting screen size/font
+ clear_screen_home(); // clear screen, home cursor
+ clear_history(); // clear history buffer
+ show_unknown_ = false; // default "off"
+ ansi_ = true; // default "on"
+ // End group
+ end();
+}
+
+/**
+ The destructor for Fl_Terminal.
+ Destroys the terminal display, scroll history, and associated widgets.
+*/
+Fl_Terminal::~Fl_Terminal(void) {
+ // Note: RingBuffer class handles destroying itself
+ if (tabstops_)
+ { free(tabstops_); tabstops_ = 0; }
+ if (autoscroll_dir_)
+ { Fl::remove_timeout(autoscroll_timer_cb, this); autoscroll_dir_ = 0; }
+ if (redraw_timer_)
+ { Fl::remove_timeout(redraw_timer_cb, this); redraw_timer_ = false; }
+}
+
+/**
+ Returns the scrollbar's actual size; actual width for vertical scrollbars,
+ actual height for horizontal scrollbars.
+*/
+int Fl_Terminal::scrollbar_actual_size(void) const {
+ return scrollbar_size_ ? scrollbar_size_ : Fl::scrollbar_size();
+}
+
+/**
+ Get the current size of the scrollbar's trough, in pixels.
+
+ If this value is zero (default), this widget will use the
+ Fl::scrollbar_size() value as the scrollbar's width.
+
+ \returns Scrollbar size in pixels, or 0 if the global Fl::scrollbar_size() is being used.
+ \see Fl::scrollbar_size(int)
+*/
+int Fl_Terminal::scrollbar_size(void) const {
+ return scrollbar_size_;
+}
+
+/**
+ Set the width of the scrollbar's trough to \p val, in pixels.
+
+ Only use this method if you need to override the global scrollbar size.
+
+ Setting \p val to the special value 0 causes the widget to
+ track the global Fl::scrollbar_size().
+
+ \see Fl::scrollbar_size()
+*/
+void Fl_Terminal::scrollbar_size(int val) {
+ scrollbar_size_ = val;
+ update_scrollbar();
+}
+
+////////////////////////////
+////// SCREEN DRAWING //////
+////////////////////////////
+
+// Draw the background for the specified ring_chars[] row starting at FLTK coords X,Y
+// Note we may be called to draw display, or even history if we're scrolled back.
+// If there's any change in bg color, we draw the filled rects here.
+//
+void Fl_Terminal::draw_row_bg(int grow, int X, int Y) const {
+ int bg_h = current_style_.fontheight();
+ int bg_y = Y - current_style_.fontheight() + current_style_.fontdescent();
+ Fl_Color bg_col;
+ int pwidth = 9;
+ const Utf8Char *u8c = u8c_ring_row(grow); // start of spec'd row
+ uchar lastattr = u8c->attrib();
+ for (int gcol=0; gcol<ring_cols(); gcol++,u8c++) { // walk columns
+ // Attribute changed since last char?
+ if (gcol==0 || u8c->attrib() != lastattr) {
+ u8c->fl_font_set(current_style_); // pwidth_int() needs fl_font set
+ lastattr = u8c->attrib();
+ }
+ pwidth = u8c->pwidth_int();
+ bg_col = is_inside_selection(grow, gcol) // text in mouse select?
+ ? select_.selectionbgcolor() // ..use select bg color
+ : (u8c->attrib() & Fl_Terminal::INVERSE) // Inverse mode?
+ ? u8c->attr_fg_color(this) // ..use fg color for bg
+ : u8c->attr_bg_color(this); // ..use bg color for bg
+ // Draw only if color != 0x0 ('show through' color) or widget's own color().
+ if (bg_col != 0xffffffff && bg_col != color()) {
+ fl_color(bg_col);
+ fl_rectf(X, bg_y, pwidth, bg_h);
+ }
+ X += pwidth; // advance X to next char
+ }
+}
+
+// Draw specified global row, which is the row in ring_chars[].
+// The global row includes history + display buffers.
+//
+void Fl_Terminal::draw_row(int grow, int Y) const {
+ // Draw background color spans, if any
+ int X = scrn_.x();
+ draw_row_bg(grow, X, Y);
+
+ // Draw forground text
+ int scrollval = vscroll_->value();
+ int disp_top = (disp_srow() - scrollval); // top row we need to view
+ int drow = grow - disp_top; // disp row
+ bool inside_display = is_disp_ring_row(grow); // row inside 'display'?
+ int strikeout_y = Y - (current_style_.fontheight() / 3);
+ int underline_y = Y;
+ const Utf8Char *u8c = u8c_ring_row(grow);
+ uchar lastattr = -1;
+ bool is_cursor;
+ Fl_Color fg;
+ for (int gcol=0; gcol<disp_cols(); gcol++,u8c++) { // walk the columns
+ const int &dcol = gcol; // dcol and gcol are the same
+ // Are we drawing the cursor? Only if inside display
+ is_cursor = inside_display ? cursor_.is_rowcol(drow-scrollval, dcol) : 0;
+ // Attribute changed since last char?
+ if (u8c->attrib() != lastattr) {
+ u8c->fl_font_set(current_style_); // pwidth_int() needs fl_font set
+ lastattr = u8c->attrib();
+ }
+ int pwidth = u8c->pwidth_int();
+ // DRAW CURSOR BLOCK - TODO: support other cursor types?
+ if (is_cursor) {
+ int cx = X;
+ int cy = Y - cursor_.h() + current_style_.fontdescent();
+ int cw = pwidth;
+ int ch = cursor_.h();
+ fl_color(cursorbgcolor());
+ if (Fl::focus() == this) fl_rectf(cx, cy, cw, ch);
+ else fl_rect(cx, cy, cw, ch);
+ }
+ // DRAW TEXT
+ // 1) Color for text
+ if (is_cursor) fg = cursorfgcolor(); // color for text under cursor
+ else fg = is_inside_selection(grow, gcol) // text in mouse selection?
+ ? select_.selectionfgcolor() // ..use selection FG color
+ : (u8c->attrib() & Fl_Terminal::INVERSE) // Inverse attrib?
+ ? u8c->attr_bg_color(this) // ..use char's bg color for fg
+ : u8c->attr_fg_color(this); // ..use char's fg color for fg
+ fl_color(fg);
+ // 2) Font for text - already set by u8c->fl_font_set() in the above
+ if (is_cursor) {
+ fl_font(fl_font()|FL_BOLD, fl_size()); // force text under cursor BOLD
+ lastattr = -1; // (ensure font reset on next iter)
+ }
+ // 3) Draw text for UTF-8 char. No need to draw spaces
+ if (!u8c->is_char(' ')) fl_draw(u8c->text_utf8(), u8c->length(), X, Y);
+ // 4) Strike or underline?
+ if (u8c->attrib() & Fl_Terminal::UNDERLINE) fl_line(X, underline_y, X+pwidth, underline_y);
+ if (u8c->attrib() & Fl_Terminal::STRIKEOUT) fl_line(X, strikeout_y, X+pwidth, strikeout_y);
+ // Move to next char pixel position
+ X += pwidth;
+ }
+}
+
+// Draw the part of the buffer we're scrolled to at FLTK position Y.
+//
+// This could be anywhere in the buffer, and not just the 'active diplay',
+// depending on what the scrollbar is set to.
+//
+// Handles attributes, colors, text selections, cursor.
+//
+void Fl_Terminal::draw_buff(int Y) const {
+ int srow = disp_srow() - vscroll_->value();
+ int erow = srow + disp_rows();
+ const int rowheight = current_style_.fontheight();
+ for (int grow=srow; (grow<erow) && (Y<scrn_.b()); grow++) {
+ Y += rowheight; // advance Y to bottom left corner of row
+ draw_row(grow, Y); // draw global row at Y
+ }
+}
+
+/**
+ Draws the entire Fl_Terminal.
+ Lets the group draw itself first (scrollbar should be only member),
+ followed by the terminal's screen contents.
+*/
+void Fl_Terminal::draw(void) {
+ // Draw group first, terminal last
+ Fl_Group::draw();
+
+ if (is_frame(box())) {
+ // Is box() a frame? Fill area inside frame with rectf().
+ // FL_XXX_FRAME types allow Fl_Terminal to have a /flat/ background.
+ // FL_XXX_BOX types inherit Fl::scheme() which can provide unwanted gradients.
+ //
+ fl_color(color());
+ // Draw flat field (inside border drawn by Fl_Group::draw() above)
+ int X = x() + Fl::box_dx(box());
+ int Y = y() + Fl::box_dy(box());
+ int W = w() - Fl::box_dw(box());
+ int H = h() - Fl::box_dh(box());
+ W -= vscroll_width();
+ fl_rectf(X,Y,W,H);
+ }
+
+ //DEBUG fl_color(0x80000000); // dark red box inside margins
+ //DEBUG fl_rect(scrn_);
+ fl_push_clip(scrn_.x(), scrn_.y(), scrn_.w(), scrn_.h());
+ int Y = scrn_.y();
+ draw_buff(Y);
+ fl_pop_clip();
+}
+
+// Given a width in pixels, return number of columns that "fits"
+int Fl_Terminal::w_to_col(int W) const {
+ return int((float(W) / current_style_.charwidth()) + 0.0); // +.5 overshoots
+}
+
+// Given a height in pixels, return number of rows that "fits"
+int Fl_Terminal::h_to_row(int H) const {
+ return int((float(H) / current_style_.fontheight()) + -0.5); // +.5 overshoots
+}
+
+/**
+ Handle widget resizing, such as if user resizes parent window.
+ This may increase the column width of the widget if the width
+ of the widget is made larger than it was.
+
+ \note Resizing currently does not rewrap existing text.
+ Currently enlarging makes room for longer lines, and shrinking
+ the size lets long lines run off the right edge of the display,
+ hidden from view. This behavior may change in the future to rewrap.
+*/
+void Fl_Terminal::resize(int X,int Y,int W,int H) {
+ // Let group resize itself
+ Fl_Group::resize(X,Y,W,H);
+ // Update screen stuff; margins, etc
+ update_screen(false); // no change in font, just resizing
+ // resize the display's rows+cols to match window size
+ refit_disp_to_screen();
+}
+
+// Handle autoscrolling vals + timer
+// If mouse dragged beyond top/bottom, start/continue auto-scroll select
+//
+void Fl_Terminal::handle_selection_autoscroll(void) {
+ int Y = Fl::event_y();
+ int top = scrn_.y();
+ int bot = scrn_.b();
+ int dist = (Y < top) ? Y - top : // <0 if above top
+ (Y > bot) ? Y - bot : 0; // >0 if below bottom
+ if (dist == 0) {
+ // Not off edge? stop autoscrolling, done
+ if (autoscroll_dir_) Fl::remove_timeout(autoscroll_timer_cb, this);
+ autoscroll_dir_ = 0;
+ } else {
+ // Above top/below bot? Start/continue autoscroll select
+ if (!autoscroll_dir_) Fl::add_timeout(.01, autoscroll_timer_cb, this);
+ autoscroll_amt_ = dist; // <0 if above top, >0 if below bot
+ autoscroll_dir_ = (dist < 0) ? 3 : 4; // 3=scrolling up, 4=scrolling dn
+ }
+}
+
+// Handle mouse selection on LEFT-CLICK push/drag/release
+// Returns: 1 if 'handled', 0 if not.
+//
+int Fl_Terminal::handle_selection(int e) {
+ int grow=0,gcol=0;
+ bool is_rowcol = (xy_to_glob_rowcol(Fl::event_x(), Fl::event_y(), grow, gcol) > 0)
+ ? true : false;
+ switch (e) {
+ case FL_PUSH: {
+ select_.push_rowcol(grow, gcol);
+ // SHIFT-LEFT-CLICK? Extend or start new
+ if (Fl::event_state(FL_SHIFT)) {
+ if (is_selection()) { // extend if select in progress
+ selection_extend(Fl::event_x(), Fl::event_y());
+ return 1; // express interest in FL_DRAG
+ }
+ } else { // Start a new selection
+ if (select_.clear()) redraw(); // clear prev selection
+ if (is_rowcol) return 1; // express interest in FL_DRAG
+ }
+ // Left-Click outside terminal area?
+ if (!Fl::event_state(FL_SHIFT)) {
+ select_.push_clear();
+ clear_mouse_selection();
+ redraw();
+ }
+ return 0; // event NOT handled
+ }
+ case FL_DRAG: {
+ if (is_rowcol) {
+ if (!is_selection()) { // no selection yet?
+ if (select_.dragged_off(grow, gcol)) { // dragged off FL_PUSH? enough to start
+ select_.start_push(); // ..start drag with FL_PUSH position
+ }
+ } else {
+ if (select_.extend(grow, gcol)) redraw(); // redraw if selection changed
+ }
+ }
+ // If we leave scrn area, start timer to auto-scroll+select
+ handle_selection_autoscroll();
+ return 1;
+ }
+ case FL_RELEASE: {
+ select_.push_clear();
+ select_.end();
+ // middlemouse gets immediate copy of selection
+ if (is_selection()) {
+ const char *copy = selection_text();
+ if (*copy) Fl::copy(copy, (int)strlen(copy), 0);
+ free((void*)copy);
+ }
+ return 1;
+ }
+ default:
+ break;
+ }
+ return 0;
+}
+
+/**
+ Handle FLTK events.
+*/
+int Fl_Terminal::handle(int e) {
+ int ret = Fl_Group::handle(e);
+ if (Fl::event_inside(vscroll_)) return ret; // early exit for scrollbar
+ switch (e) {
+ case FL_ENTER:
+ case FL_LEAVE:
+ return 1;
+ case FL_UNFOCUS:
+ case FL_FOCUS:
+ redraw();
+ return Fl::visible_focus() ? 1 : 0;
+ case FL_KEYBOARD:
+ // ^C -- Copy?
+ if ((Fl::event_state()&(FL_CTRL|FL_COMMAND)) && Fl::event_key()=='c') {
+ const char *copy = is_selection() ? selection_text() : fl_strdup(" ");
+ if (*copy) Fl::copy(copy, (int)strlen(copy), 1); // paste buffer
+ free((void*)copy);
+ return 1;
+ }
+ // ^A -- Select all?
+ if ((Fl::event_state()&(FL_CTRL|FL_COMMAND)) && Fl::event_key()=='a') {
+ // Select entire screen and history buffer
+ int srow = disp_srow() - hist_use();
+ int erow = disp_srow() + disp_rows()-1;
+ //DEBUG ::printf("CTRL-A: srow=%d erow=%d\n", srow, erow);
+ select_.select(srow, 0, erow, disp_cols()-1);
+ const char *copy = selection_text();
+ if (*copy) Fl::copy(copy, (int) strlen(copy), 0); // middle mouse buffer
+ free((void*)copy);
+ redraw();
+ return 1;
+ }
+ // Let scrollbar handle these when we have focus
+ if (Fl::focus() == this) {
+ switch (Fl::event_key()) {
+ case FL_Page_Up: case FL_Page_Down:
+ case FL_Up: case FL_Down:
+ case FL_Left: case FL_Right:
+ return vscroll_->handle(e);
+ }
+ }
+ break;
+ case FL_PUSH:
+ if (handle(FL_FOCUS)) Fl::focus(this); // Accepting focus? take it
+ if (Fl::event_button() == FL_LEFT_MOUSE) // LEFT-CLICK?
+ { ret = handle_selection(FL_PUSH); }
+ break;
+ case FL_DRAG:
+ // TODO: This logic can probably be improved to allow an FL_PUSH in margins
+ // to drag into terminal area to start a selection.
+ if (Fl::event_button() == FL_LEFT_MOUSE) // LEFT-DRAG?
+ { ret = handle_selection(FL_DRAG); }
+ break;
+ case FL_RELEASE:
+ // Selection mouse release?
+ if (Fl::event_button() == FL_LEFT_MOUSE) // LEFT-RELEASE?
+ { ret = handle_selection(FL_RELEASE); }
+ // Disable autoscroll timer, if any
+ if (autoscroll_dir_)
+ { Fl::remove_timeout(autoscroll_timer_cb, this); autoscroll_dir_ = 0; }
+ break;
+ } // switch
+ return ret;
+}
+
+/**
+ Get the redraw style.
+
+ This determines when the terminal redraws itself while text
+ is being added to it.
+
+ Value will be one of the Fl_Terminal::RedrawStyle enum values.
+
+ \see redraw_style(Fl_Terminal::RedrawStyle)
+*/
+Fl_Terminal::RedrawStyle Fl_Terminal::redraw_style() const {
+ return redraw_style_;
+}
+
+/**
+ Set how Fl_Terminal manages screen redrawing.
+
+ This setting is relevant when Fl_Terminal is used for high bandwidth
+ data; too many redraws will slow things down, too few cause redraws
+ to be 'choppy' when realtime data comes in.
+
+ Redrawing can be cpu intensive, depending on how many rows/cols are
+ being displayed; worst case: large display + small font. Speed largely
+ depends on the end user's graphics hardware and font drawing system.
+
+ \par
+ | RedrawStyle enum | Description
+ | :-------------------: | :-----------------------------------------------------------
+ | \ref NO_REDRAW | App must call redraw() as needed to update text to screen
+ | \ref RATE_LIMITED | Rate limited, timer controlled redraws. (DEFAULT) See redraw_rate()
+ | \ref PER_WRITE | Redraw triggered *every* call to append() / printf() / etc.
+
+ The default style is RATE_LIMITED, which is the easiest
+ to use, and automates redrawing to be capped at 10 redraws per second
+ max. See redraw_rate(float) to control this automated redraw speed.
+
+ \see redraw_rate(), RedrawStyle
+*/
+void Fl_Terminal::redraw_style(RedrawStyle val) {
+ redraw_style_ = val;
+ // Disable rate limit timer if it's being turned off
+ if (redraw_style_ != RATE_LIMITED && redraw_timer_)
+ { Fl::remove_timeout(redraw_timer_cb, this); redraw_timer_ = false; }
+}
+
+/**
+ Get max rate redraw speed in floating point seconds.
+*/
+float Fl_Terminal::redraw_rate(void) const {
+ return redraw_rate_;
+}
+
+/**
+ Set the maximum rate redraw speed in floating point seconds
+ if redraw_style() is set to RATE_LIMITED.
+
+ When output is sent to the terminal, rather than calling redraw()
+ right away, a timer is started with this value indicating how long
+ to wait before calling redraw(), causing the output to be shown.
+ 0.10 is recommended (1/10th of a second), to limit redraws to no
+ more than 10 redraws per second.
+
+ The value that works best depends on how fast data arrives, and
+ how fast the font system can draw text at runtime.
+
+ Values too small cause too many redraws to occur, causing the
+ terminal to get backlogged if large bursts of data arrive quickly.
+ Values too large cause realtime output to be too "choppy".
+*/
+void Fl_Terminal::redraw_rate(float val) {
+ redraw_rate_ = val;
+}
+
+/**
+ Return the "show unknown" flag.
+ See show_unknown(bool) for more info.
+*/
+bool Fl_Terminal::show_unknown(void) const {
+ return show_unknown_;
+}
+
+/**
+ Set the "show unknown" flag.
+
+ If true, unknown escape sequences and unprintable control characters
+ will be shown with the error character "¿".
+
+ If false, those sequences and characters will be ignored.
+
+ \see handle_unknown_char()
+*/
+void Fl_Terminal::show_unknown(bool val) {
+ show_unknown_ = val;
+}
+
+/**
+ Return the state of the ANSI flag.
+ \see ansi(bool)
+*/
+bool Fl_Terminal::ansi(void) const {
+ return ansi_;
+}
+
+/**
+ Enable/disable the ANSI mode flag.
+ If true, ANSI and VT100/xterm codes will be processed.
+ If false, these codes won't be processed and will either
+ be ignored or print the error character "¿", depending on
+ the value of show_unknown().
+ \see show_unknown(), \ref Fl_Terminal_escape_codes
+*/
+void Fl_Terminal::ansi(bool val) {
+ ansi_ = val;
+ // If disabled, reset the class to clear old state information
+ if (!ansi_) escseq.reset();
+}
+
+/**
+ Return the number of lines of screen history.
+*/
+int Fl_Terminal::history_lines(void) const { return history_rows(); }
+
+/**
+ Set the number of lines of screen history.
+ Large values can be briefly heavy on cpu and memory usage.
+*/
+void Fl_Terminal::history_lines(int val) { history_rows(val); }
+
+/**
+ Appends printf formatted messages to the terminal.
+
+ The string can contain UTF-8, crlf's, and ANSI sequences are
+ also supported. Example:
+ \par
+ \code
+ #include <FL/Fl_Terminal.H>
+ int main(..) {
+ :
+ // Create a terminal, and append some messages to it
+ Fl_Terminal *tty = new Fl_Terminal(..);
+ :
+ // Append three lines of formatted text to the buffer
+ tty->printf("The current date is: %s.\nThe time is: %s\n", date_str, time_str);
+ tty->printf("The current PID is %ld.\n", (long)getpid());
+ :
+ \endcode
+ \note The expanded string is currently limited to 1024 characters (including NULL).
+ For longer strings use append() which has no string limits.
+*/
+void Fl_Terminal::printf(const char *fmt, ...) {
+ va_list ap;
+ va_start(ap, fmt);
+ Fl_Terminal::vprintf(fmt, ap);
+ va_end(ap);
+}
+
+/**
+ Appends printf formatted messages to the terminal.
+
+ Subclasses can use this to implement their own printf()
+ functionality.
+
+ The string can contain UTF-8, crlf's, and ANSI sequences are
+ also supported when ansi(bool) is set to 'true'.
+
+ \note The expanded string is currently limited to 1024 characters (including NULL).
+ For longer strings use append() which has no string limits.
+ \param fmt is a printf format string for the message text.
+ \param ap is a va_list created by va_start() and closed with va_end(),
+ which the caller is responsible for handling.
+*/
+void Fl_Terminal::vprintf(const char *fmt, va_list ap) {
+ char buffer[1024]; // XXX: should be user configurable..
+ ::vsnprintf(buffer, 1024, fmt, ap);
+ buffer[1024-1] = 0; // XXX: MICROSOFT
+ append(buffer);
+}
+
diff --git a/src/Makefile b/src/Makefile
index a8468e3b3..39a77d29c 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -94,6 +94,7 @@ CPPFILES = \
Fl_Table.cxx \
Fl_Table_Row.cxx \
Fl_Tabs.cxx \
+ Fl_Terminal.cxx \
Fl_Text_Buffer.cxx \
Fl_Text_Display.cxx \
Fl_Text_Editor.cxx \
diff --git a/src/README-Fl_Terminal.txt b/src/README-Fl_Terminal.txt
new file mode 100644
index 000000000..4b1b6bc00
--- /dev/null
+++ b/src/README-Fl_Terminal.txt
@@ -0,0 +1,586 @@
+// vim: autoindent tabstop=8 shiftwidth=4 expandtab softtabstop=4
+
+Fl_Terminal Design Document
+===========================
+
+When I started this project, I identified the key concepts needed to
+implement Fl_Terminal:
+
+ - Draw and manage multiline Unicode text in FLTK effectively,
+ allowing per-character colors and attributes like underline,
+ strikeout, background, etc.
+
+ - An efficient screen buffer to handle the "scrollback history"
+ and "screen display" concepts; the "history" being a scrollback
+ history of text that scrolls up and off screen from the "display",
+ and the "display" being where the action is: the cursor can be
+ moved around and text scrolled up or down.
+
+ - How the vertical scrollbar should provide the user with a way to
+ scroll back into the scrollback history to allow the user to scroll back
+ to view the "scrollback history", without stopping the "screen display"
+ from operating
+
+ - How to manage mouse selection for copy/paste
+
+ - Escape code management to implement VT100 style / ANSI escape codes.
+
+
+ ┌─────────────────────────────────────────┬──────────────────────────────┐
+ │ NOTE: Abbreviations "hist" and "disp" │ │
+ ├─────────────────────────────────────────┘ │
+ │ │
+ │ "history" may be abbreviated as "hist", and "display" as "disp" in │
+ │ both this text and the source code. 4 character names are used so │
+ │ they line up cleanly in the source, e.g. │
+ │ │
+ │ ring_cols = 0; ring_rows = 0; │
+ │ hist_cols = 0; ring_cols = 0; │
+ │ disp_cols = 0; ring_cols = 0; │
+ │ └─┬┘ └─┬┘ │
+ │ └────┴─── 4 characters │
+ │ │
+ └────────────────────────────────────────────────────────────────────────┘
+
+So the of these concepts were able to fit into C++ class concepts well.
+Those classes being:
+
+ Utf8Char
+ ========
+ Each character on the screen is a "Utf8Char" which can manage
+ the utf8 encoding of any character as one or more bytes. Also
+ in that class is a byte for an attribute (underline, bold, etc),
+ and two integers for fg/bg color.
+
+ RingBuffer
+ ==========
+ The RingBuffer class keeps track of the buffer itself, a single
+ array of Utf8Chars called "ring_chars", and some index numbers
+ to keep track of how many rows are in the screen's history and
+ display, named "hist_rows" and "disp_rows".
+
+ The memory layout of the Utf8Char array is:
+
+ ___________________ _ _
+ | | ʌ
+ | | |
+ | | |
+ | H i s t o r y | | hist_rows
+ | | |
+ | | |
+ |___________________| _v_
+ | | ʌ
+ | | |
+ | D i s p l a y | | disp_rows
+ | | |
+ |___________________| _v_
+
+ |<----------------->|
+ ring_cols
+
+ So it's basically a single continguous array of Utf8Char instances
+ where any character can be accessed by index# using the formula:
+
+ ring_chars[ (row*ring_cols)+col ]
+
+ ..where 'row' is the desired row, 'col' is the desired column,
+ and 'ring_cols' is how many columns "wide" the buffer is.
+
+ Methods are used to give access the characters in the buffer.
+
+ A key concept is to allow the starting point of the history and
+ display to be moved around to implement 'text scrolling', such
+ as when crlf at the screen bottom causes a 'scroll up'.
+
+ This is simply an "index offset" integer applied to the
+ hist and disp indexes when drawing the display, e.g.
+
+ Offset is 0: 2 Offset now 2:
+ ┌───────────────────┐ ──┐ ┌───────────────────┐
+ │ │ │ │ D i s p l a y │
+ │ │ └─> ├───────────────────┤
+ │ │ │ │
+ │ H i s t o r y │ │ │
+ │ │ │ H i s t o r y │
+ │ │ 2 │ │
+ ├───────────────────┤ ──┐ │ │
+ │ │ │ │ │
+ │ │ └─> ├───────────────────┤
+ │ D i s p l a y │ │ │
+ │ │ │ D i s p l a y │
+ │ │ │ │
+ └───────────────────┘ └───────────────────┘
+
+ Offset is 0: 4 Offset now 4:
+ ┌───────────────────┐ ──┐ ┌───────────────────┐
+ │ │ │ │ │
+ │ │ │ │ D i s p l a y │
+ │ │ │ │ │
+ │ H i s t o r y │ └─> ├───────────────────┤
+ │ │ │ │
+ │ │ 4 │ │
+ ├───────────────────┤ ──┐ │ H i s t o r y │
+ │ │ │ │ │
+ │ │ │ │ │
+ │ D i s p l a y │ │ │ │
+ │ │ └─> ├───────────────────┤
+ │ │ │ D i s p l a y │
+ └───────────────────┘ └───────────────────┘
+
+ The effect of applying an offset trivially implements "text scrolling",
+ so that no screen memory has to physically moved around, simply changing
+ the single integer "offset" is enough. The text remains where it was, and
+ the offset is simply incremented to scroll up. This also automatically
+ makes it appear the top line in the display is 'scrolled up' into the
+ last line of the scrollback history.
+
+ If the offset exceeds the size of the ring buffer, it is simply wrapped
+ back to the beginning of the buffer with a modulo: offset =% ring_rows;
+
+ Indexes into the display and history are also modulo their respective
+ rows, e.g.
+
+ act_ring_index = (hist_rows + disp_row + offset - scrollbar_pos) % ring_rows;
+
+ This way indexes for ranges can run beyond the bottom of the ring,
+ and automatically wrap around the ring, e.g.
+
+ Offset now 4:
+ ┌───────────────────┐
+ 2 │ │
+ 3 │ D i s p l a y │
+ 4 │ │ <- act_disp_row(4)
+ ├───────────────────┤
+ │ │
+ │ │
+ │ H i s t o r y │
+ │ │
+ │ │
+ │ │
+ ├───────────────────┤
+ 0 │ D i s p l a y │
+ 1 └───────────────────┘ <- ring_rows
+ 2 : :
+ 3 : :
+ disp_row(5) -> 4 :...................:
+
+ Here the "disp_row" is the desired offset into the display, but we
+ need the actual index into the ring from the top, since that's the
+ physical array.
+
+ So some simple math calculates the row position based on the "offset",
+ and the "hist" vs "disp" concepts:
+
+ act_ring_index = (histrows // the display exists AFTER the history, so offset the hist_rows
+ + offset // include the scroll 'offset'
+ + disp_row // add the desired row relative to the top of the display (0..disp_rows)
+ ) % ring_rows; // make sure the resulting index is within the ring buffer (0..ring_rows)
+
+ An additional bit of math makes sure if a negative result occurs, that
+ negative value works relative to the end of the ring, e.g.
+
+ if (act_ring_index < 0) act_ring_index = ring_rows + act_ring_index;
+
+ This guaratnees the act_ring_index is within the ring buffer's address space,
+ with all offsets applied.
+
+ The math that implements this can be found in the u8c_xxxx_row() methods,
+ where "xxxx" is one of the concept regions "ring", "hist" or "disp":
+
+ Utf8Char *u8c;
+ u8c = u8c_ring_row(rrow); // address within ring, rrow can be 0..(ring_rows-1)
+ u8c = u8c_hist_row(hrow); // address within hist, hrow can be 0..(hist_rows-1)
+ u8c = u8c_disp_row(drow); // address within disp, drow can be 0..(disp_rows-1)
+
+ The small bit of math is only involved whenever a new row address is needed,
+ so in a display that's 80x25, to walk all the characters in the screen, the
+ math above would only be called 25 times, once for each row, e.g.
+
+ for ( int row=0; row<disp_rows(); row++ ) { // walk rows: disp_rows = 25
+ Utf8Char *u8c = u8c_disp_row(row); // get first char in display 'row'
+ for ( int col=0; col<disp_cols(); col++ ) { // walk cols: disp_cols = 80
+ u8c[col].do_something(); // work with the character at row/col
+ }
+ }
+
+ So to recap, the concepts here are:
+
+ - The ring buffer itself, a linear array that is conceptually
+ split into a 2 dimensional array of rows and columns whose
+ height and width are:
+
+ ring_rows -- how many rows in the entire ring buffer
+ ring_cols -- how many columns in the ring buffer
+ nchars -- total chars in ring, e.g. (ring_rows * ring_cols)
+
+ - The "history" within the ring. For simplicity this is thought of
+ as starting relative to the top of the ring buffer, occupying
+ ring buffer rows:
+
+ 0..(hist_rows-1)
+
+ - The "display", or "disp", within the ring, just after the "history".
+ It occupies the ring buffer rows:
+
+ (hist_rows)..(hist_rows+disp_rows-1)
+
+ ..or similarly:
+
+ (hist_rows)..(ring_rows-1)
+
+ - An "offset" used to move the "history" and "display" around within
+ the ring buffer to implement the "text scrolling" concept. The offset
+ is applied when new characters are added to the buffer, and during
+ drawing to find where the display actually is within the ring.
+
+ - A "scrollbar", which only is used when redrawing the screen the user sees,
+ and is simply an additional offset to all the above, where a scrollback
+ value of zero (the scrollbar tab at the bottom) shows the display rows,
+ and the values increase as the user moves the scrolltab upwards, 1 per line,
+ which is subtracted from the normal starting index to let the user work their
+ way backwards into the scrollback history.
+
+ The ring buffer allows new content to simply be appended to the ring buffer,
+ and the index# for the start of the display and start of scrollback history are
+ simply incremented. So the next time the display is "drawn", it starts at
+ a different position in the ring.
+
+ This makes scrolling content at high speed trivial, without memory moves.
+ It also makes the concept of "scrolling" with the scrollbar simple as well,
+ simply being an extra index offset applied during drawing.
+
+ If the display is enlarged vertically, that's easy too; the display
+ area is simply defined as being more rows, the history as less rows,
+ the history use decreased (since what was in the history before is now
+ being moved into the display), and all the math adjusts accordingly.
+
+Mouse Selection
+===============
+
+Dragging the mouse across the screen should highlight the text, allowing the user
+to extend the selection either beyond or before the point started. Extending the
+drag to the top of the screen should automatically 'scroll up' to select more
+lines in the scrollback history, or below the bottom to do the opposite.
+
+The mouse selection is implemented as a class to keep track of the start/end
+row/col positions of the selection, and other details such as a flag indicating
+if a selection has been made, what color the fg/bg text should appear when
+text is selected, and methods that allow setting and extending the selection,
+clearing the selection, and "scrolling" the selection, to ensure the row/col
+indexes adjust correctly to track when the screen or scrollbar is scrolled.
+
+
+Redraw Timer
+============
+
+Knowing when to redraw is tricky with a terminal, because sometimes high volumes
+of input will come in asynchronously, so in that case we need to determine when
+to redraw the screen to show the new content; too quickly will cause the screen
+to spend more time redrawing itself, preventing new input from being added. Too
+slowly, the user won't see new information appear in a timely manner.
+
+To solve this, a rate timer is used to prevent too many redraws:
+
+ - When new data comes in, a 1/10 sec timer is started and a modify flag is set.
+
+ redraw() is NOT called at this time, allowing new data to continue to arrive
+ quickly. Once the modify flag is set, nothing changes from there.
+
+ - When the 1/10th second timer fires, the callback checks the modify flag:
+
+ - if set, calls redraw(), resets the modify to 0, and calls
+ Fl::repeat_timeout() to repeat the callback in another 1/10th sec.
+
+ - if clear, no new data came in, so DISABLE the timer, done.
+
+In this way, redraws don't happen more than 10x per second, and redraw() is called
+only when there's new content to see.
+
+The redraw rate can be set by the user application using the Fl_Terminal::redraw_rate(),
+0.10 being the default.
+
+Some terminal operations necessarily call redraw() directly, such as interactive mouse
+selection, or during user scrolling the terminal's scrollbar, where it's important there's
+no delay in what the user sees while interacting directly with the widget.
+
+
+
+
+OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD
+ OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD
+ OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD
+ OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD OLD
+
+
+
+RING BUFFER DESCRIPTION
+=======================
+
+ The history and display are defined by row indexes into this buffer
+ which are adjusted whenever the text display is 'scrolled' (e.g. by crlfs)
+
+ The scrollbar is a secondary offset on top of that applied during drawing.
+ the display.
+
+ Here's what the variables managing the split ring buffer look like:
+
+
+
+ RING BUFFER
+ ─ ring_chars[] ─┬─>┌───────────────┐
+ ʌ [hist_srow] ──┘ │ │
+ ┊ │ │
+ ┊ │ H i s t o r y ┊
+ hist_rows_┊ │ │
+ ┊ │ │
+ ┊ │ │
+ ┊ │ │
+ v [hist_erow] ───>│ │
+ ─ [disp_srow] ───>├───────────────┤
+ ʌ │ │
+ ┊ │ │
+ disp_rows_┊ │ D i s p l a y │
+ ┊ │ │
+ ┊ │ │
+ v [ring_erow] ───>│ +│<── ring_bot_
+ ─ └───────────────┘ (last valid ptr address in ring)
+
+ │<──────┬──────>│
+ ring_cols
+ hist_cols
+ disp_cols
+
+ The concept here is the single ring buffer is split into two parts that can differ
+ in height (rows) but not in width (cols). For instance, typically the history is
+ many times larger than the display; a typical old school display might be 80x25,
+ but the history might be 2000 lines.
+
+ ring_row() handles the fact that 'row' might run off the end of the buffer,
+ depending on where hist_srow starts. For instance, if the display has scrolled
+ a few lines, the ring buffer arrangement might look like:
+
+ RING BUFFER
+ ring_chars[] ───>┌───────────────┐
+ │ D i s p l a y │
+ [disp_erow] ───>│ │
+ ├───────────────┤
+ [hist_srow] ───>│ │
+ │ │
+ │ H i s t o r y │
+ │ │
+ │ │
+ │ │
+ [hist_erow] ───>│ │
+ ├───────────────┤
+ [disp_srow] ───>│ │
+ │ D i s p l a y │
+ │ │
+ [ring_erow] ───>│ +│<── ring_bot_
+ └───────────────┘ (last valid ptr address in ring)
+
+
+ Note how the display 'wraps around', straddling the end of the ring buffer.
+ So trivially walking ring_chars[] from 'disp_srow' for 'disp_rows' would run
+ off the end of memory for the ring buffer. Example:
+
+ // BAD!
+ for ( int row=disp_srow; row<disp_rows; row++ ) {
+ for ( int col=0; col<disp_cols; col++ ) {
+ ring_chars[row*disp_cols+col]->do_something(); // BAD! can run off end of array
+ }
+ }
+
+ The function u8c_row() can access the Utf8Char* of each row more safely,
+ ensuring that even if the 'row' index runs off the end of the array,
+ u8c_row() handles wrapping it for you. So the safe way to walk the chars
+ of the display can be done this way:
+
+ // GOOD!
+ for ( int row=disp_srow; row<disp_rows; row++ ) {
+ Utf8Char *u8c = u8c_row(row); // safe: returns utf8 char for start of each row
+ for ( int col=0; col<disp_cols; col++ ) { // walk the columns safely
+ (u8c++)->do_something(); // get/set the utf8 char
+ }
+ }
+
+ Walking the history would be the same, just replace disp_xxxx with hist_xxxx.
+
+ One can also use ring_row_normalize() to return an index# that can be directly
+ used with ring_chars[], the value kept in range of the buffer.
+
+
+RING BUFFER "SCROLLING"
+=======================
+ A ring buffer is used to greatly simplify the act of 'scrolling', which happens a lot
+ when large amounts of data come in, each CRLF triggering a "scroll" that moves the top
+ line up into the history buffer. The history buffer can be quite large (1000's of lines),
+ so it would suck if, on each line scroll, thousands of rows of Utf8Chars had to be
+ physically moved in memory.
+
+ Much easier to just adjust the srow/erow pointers, which simply affect how drawing
+ is done, and where the display area is. This trivially handles scrolling by just
+ adjusting some integers by 1.
+
+
+ -- -- HOW SCROLLING UP ONE LINE IS DONE -- --
+
+
+ ring_chars[] ─┬─>┌───────────────┐ ─┐ ring_chars[] ──>┌─────────────────┐
+ [hist_srow] ──┘ │ │ │ │x x x x x x x x x│ <-- blanks
+ │ │ └─> [hist_srow] ──>├─────────────────┤
+ │ H i s t │ │ │
+ │ │ │ │
+ │ │ │ H i s t │
+ │ │ │ │
+ │ │ │ │
+ [hist_erow] ───>│ │ │ │
+ [disp_srow] ───>├Line 1─────────┤ ─┐ [hist_erow] ──>│Line 1 │
+ │Line 2 │ └─> [disp_srow] ──>├Line 2───────────┤
+ │Line 3 │ │Line 3 │
+ │ │ │ │
+ │ D i s p │ │ D i s p │
+ │ │ │ │
+[disp_erow][ring_erow] ───>│Line 24 +│ [ring_erow] ──>│Line 24 +│
+ └───────────────┘ └─────────────────┘
+
+ In the above, Line 1 has effectively "moved" into history because the disp_s/erow
+ and hist_s/erow variables have just been incremented.
+
+ During resize_display(), we need to preserve the display and history as much as possible
+ when the ring buffer is enlarged/shrank; the hist_rows size should be maintained, and only
+ display section changes size based on the FLTK window size.
+
+
+
+===================== OLD ====================== OLD ====================== OLD ======================
+
+Conventions used for the internals
+==================================
+This is a large widget, and these are some breadcrumbs for anyone
+working on the internals of this class.
+
+ > There is one utf8 char buffer, buff_chars[], the top part is the 'history buffer'
+ (which the user can scroll back to see), and the 'display buffer' which is the
+ 'active display'.
+ > glob or global - refers to global buffer buff_chars[]
+ > disp or display - refers to display buffer disp_chars[]
+ > Abbreviations glob/disp/buff/hist used because 4 chars line up nicely
+ > row/col variable names use a 'g' or 'd' prefix to convey 'g'lobal or 'd'isplay.
+ > The 'Cursor' class uses row/col for the display (disp_chars[]) because the
+ cursor is only ever inside the display.
+ > The 'Selection' class uses row/col for the global buffer (buff_chars[])
+ because it can be in the 'history' or the 'display'
+ > These concepts talk about the same thing:
+ > global buffer == buff_chars[] == "history buffer" == hist == grow/gcol
+ > display == disp_chars[] == "active display" == drow/dcol
+ > There is no hist_chars[] because it's just the top half of buff_chars[]
+ > There is no hist_height_ because it's the same as hist_max_
+ > There is no hist_width_ because it's the same as buff_width_.
+
+
+Fl_Terminal's Class Hierarchy
+=============================
+
+ class Fl_Terminal -- Derived from Fl_Group (to parent scrollbars, popup menus, etc)
+ We mainly use the group's background to draw over in draw().
+ Within the terminal classes are the following private/protected classes
+ that help with bookkeeping and operation of the terminal class:
+
+ class Margin -- Handles the margins around the terminal drawing area
+ class CharStyle -- The styling for the characters: single byte color + attribute (bold/inverse/etc)
+ class Cursor -- The attributes of the cursor -- position, color, etc, and some simple movement logic
+ class Utf8Char -- Visible screen buffer is an array of these, one per character
+ class RingBuffer -- The ring buffer of Utf8Char's, with the "history" and "display" concept.
+ class EscapeSeq -- A class to handle parsing Esc sequences, and keeping state info between chars
+ Single chars go in, and when a complete esc sequence is parsed, the caller
+ can find out all the integer values and command code easily to figure out
+ what op to do.
+
+ OVERALL DESIGN:
+ To handle unicode, the terminal's visible display area is a linear array of pointers to
+ instances of the 'Utf8Char' class, one instance per character. The arrangement of the array
+ is much like the IBM PC's video memory, but instead of Char/Attrib byte pairs, the Utf8Char
+ class handles the more complex per-character data and colors/attributes.
+
+ The cursor x,y value can be quickly converted to an index into this buffer.
+ Strings are printed into the buffer, again, similar to the IBM PC video memory;
+ one character at a time into the Utf8Char class instances.
+
+ When the screen redraws, it just walks this array, and calls fl_draw() to draw
+ the text, one utf8 char at a time, with the colors/fonts/attributes from the Utf8Char class.
+
+ As characters are added, Esc sequences are intercepted and parsed into the EscapeSeq class,
+ which has a single instance for the terminal.
+
+ For the scrollback history, as lines scrolls off the top of the active display area,
+ the Utf8Char's are copied to the history buffer, and the active display's top line
+ is simply rotated to the bottom line and cleared, allowing memory reuse of the Utf8Char's,
+ to prevent memory churn for the display. The goal is to allow high volume output to the
+ terminal with a minimum affect on realloc'ing memory.
+
+ OPTIMIZATIONS
+ Where possible, caching is used to prevent repeated calls to cpu expensive operations,
+ such as anything to do with calculating unicode character width/height/etc.
+
+RingBuffer
+ The ring buffer is split in two; the top part is the history, the bottom part is
+ the "display area", where new text comes in, where the cursor can be positioned,
+ and concepts like "scroll up" and "scroll down" all happen. The "history" is simply
+ a linear buffer where lines pushed up from the display are moved into.
+
+ Methods let one access the ring with index#s:
+
+ - The entire ring buffer can be accessed with:
+
+ for (int i=0; i<ring.ring_rows(); i++) {
+ Utf8Char *u8c_row = ring.u8c_ring_row(i);
+ for (int col=0; i<ring.ring_cols(); i++) {
+ u8c_row[col].xxx(); // access each Utf8Char at the row/col
+ }
+ }
+
+ Row#s can be given that are larger than the ring; these are automatically
+ wrapped around to the top. The ring, "history" and "display" can each be
+ accessed separately with index#s relative to their position
+//////////////////////////////////////////////////////////////////////////////////////
+
+//////////////////////////////////////////////////////////////////////////////////////
+ Moved this down to the bottom of the file for now -- not sure where to put this,
+ but it's useful if one wants to reuse the EscapeSeq class somewhere else. -erco Dec 2022
+
+ Typical use pattern of EscapeSeq class.
+ This is unverified code, but should give the general gist;
+
+ while ( *s ) { // walk text that may contain ESC sequences
+ if ( *s == 0x1b ) {
+ escseq.parse(*s++); // start parsing ESC seq (does a reset())
+ continue;
+ } else if ( escseq.parse_in_progress() ) { // continuing to parse an ESC seq?
+ switch (escseq.parse(*s++)) { // parse char, advance s..
+ case fail: escseq.reset(); continue; // failed? reset, continue..
+ case success: continue; // keep parsing..
+ case completed: // parsed complete esc sequence?
+ break;
+ }
+ // Handle parsed esc sequence here..
+ switch ( escseq.esc_mode() ) {
+ case 'm': // ESC[...m?
+ for ( int i=0; i<escseq.total_vals(); i++ ) {
+ int val = escseq.val(i);
+ ..handle values here..
+ }
+ break;
+ case 'J': // ESC[#J?
+ ..handle..
+ break;
+ }
+ escseq.reset(); // done handling escseq, reset()
+ continue;
+ } else {
+ ..handle non-escape chars here..
+ }
+ ++s; // advance thru string
+ }
+
+----------------------------------------------------------------------------------------
+
diff --git a/src/makedepend b/src/makedepend
index bbb4fe04d..0199f9d96 100644
--- a/src/makedepend
+++ b/src/makedepend
@@ -3878,6 +3878,28 @@ Fl_Tabs.o: ../FL/fl_utf8.h
Fl_Tabs.o: ../FL/Fl_Widget.H
Fl_Tabs.o: ../FL/Fl_Window.H
Fl_Tabs.o: ../FL/platform_types.h
+Fl_Terminal.o: ../FL/Enumerations.H
+Fl_Terminal.o: ../FL/Fl.H
+Fl_Terminal.o: ../FL/fl_attr.h
+Fl_Terminal.o: ../FL/Fl_Bitmap.H
+Fl_Terminal.o: ../FL/Fl_Cairo.H
+Fl_Terminal.o: ../FL/fl_casts.H
+Fl_Terminal.o: ../FL/fl_config.h
+Fl_Terminal.o: ../FL/fl_draw.H
+Fl_Terminal.o: ../FL/Fl_Export.H
+Fl_Terminal.o: ../FL/Fl_Group.H
+Fl_Terminal.o: ../FL/Fl_Image.H
+Fl_Terminal.o: ../FL/Fl_Rect.H
+Fl_Terminal.o: ../FL/Fl_Scrollbar.H
+Fl_Terminal.o: ../FL/Fl_Slider.H
+Fl_Terminal.o: ../FL/fl_string_functions.h
+Fl_Terminal.o: ../FL/Fl_Terminal.H
+Fl_Terminal.o: ../FL/fl_types.h
+Fl_Terminal.o: ../FL/fl_utf8.h
+Fl_Terminal.o: ../FL/Fl_Valuator.H
+Fl_Terminal.o: ../FL/Fl_Widget.H
+Fl_Terminal.o: ../FL/Fl_Window.H
+Fl_Terminal.o: ../FL/platform_types.h
Fl_Text_Buffer.o: ../config.h
Fl_Text_Buffer.o: ../FL/Enumerations.H
Fl_Text_Buffer.o: ../FL/Fl.H
diff --git a/test/.gitignore b/test/.gitignore
index cd29e2fca..316e790b1 100644
--- a/test/.gitignore
+++ b/test/.gitignore
@@ -103,6 +103,7 @@ sudoku
symbols
table
tabs
+terminal
threads
tile
tiled_image
@@ -141,6 +142,8 @@ resize.cxx
resize.h
tabs.cxx
tabs.h
+terminal.cxx
+terminal.h
tree.cxx
tree.h
valuators.cxx
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
index 3338d3df6..d9a061a2f 100644
--- a/test/CMakeLists.txt
+++ b/test/CMakeLists.txt
@@ -157,6 +157,7 @@ CREATE_EXAMPLE (sudoku "sudoku.cxx;sudoku.plist;sudoku.icns;sudoku.rc" "fltk_ima
CREATE_EXAMPLE (symbols symbols.cxx fltk)
CREATE_EXAMPLE (tabs tabs.fl fltk)
CREATE_EXAMPLE (table table.cxx fltk)
+CREATE_EXAMPLE (terminal terminal.fl fltk)
CREATE_EXAMPLE (threads threads.cxx fltk)
CREATE_EXAMPLE (tile tile.cxx fltk)
CREATE_EXAMPLE (tiled_image tiled_image.cxx fltk)
@@ -185,7 +186,7 @@ SET (UNITTEST_SRCS
unittest_viewport.cxx
unittest_scrollbarsize.cxx
unittest_schemes.cxx
- unittest_simple_terminal.cxx
+ unittest_terminal.cxx
)
CREATE_EXAMPLE (unittests "${UNITTEST_SRCS}" "${GLDEMO_LIBS}")
diff --git a/test/Makefile b/test/Makefile
index e69b8a9fd..fd6fddf93 100644
--- a/test/Makefile
+++ b/test/Makefile
@@ -30,7 +30,7 @@ CPPUNITTEST = \
unittest_viewport.cxx \
unittest_scrollbarsize.cxx \
unittest_schemes.cxx \
- unittest_simple_terminal.cxx \
+ unittest_terminal.cxx \
unittest_core.cxx
OBJUNITTEST = \
@@ -125,6 +125,7 @@ CPPFILES =\
symbols.cxx \
table.cxx \
tabs.cxx \
+ terminal.cxx \
threads.cxx \
tile.cxx \
tiled_image.cxx \
@@ -206,6 +207,7 @@ ALL = \
sudoku$(EXEEXT) \
symbols$(EXEEXT) \
table$(EXEEXT) \
+ terminal$(EXEEXT) \
$(THREADS) \
tile$(EXEEXT) \
tiled_image$(EXEEXT) \
@@ -268,6 +270,7 @@ clean:
$(RM) radio.cxx radio.h
$(RM) resize.cxx resize.h
$(RM) tabs.cxx tabs.h
+ $(RM) terminal.cxx terminal.h
$(RM) tree.cxx tree.h
$(RM) valuators.cxx valuators.h
@@ -618,6 +621,9 @@ table$(EXEEXT): table.o
tabs$(EXEEXT): tabs.o
tabs.cxx: tabs.fl ../fluid/fluid$(EXEEXT)
+terminal$(EXEEXT): terminal.o
+terminal.cxx: terminal.fl ../fluid/fluid$(EXEEXT)
+
threads$(EXEEXT): threads.o
# This ensures that we have this dependency even if threads are not
# enabled in the current tree...
diff --git a/test/browser.cxx b/test/browser.cxx
index d269f0d57..0df67433b 100644
--- a/test/browser.cxx
+++ b/test/browser.cxx
@@ -64,7 +64,7 @@ That was a blank line above this.
#include <FL/Fl_Button.H>
#include <FL/Fl_Int_Input.H>
#include <FL/Fl_Choice.H>
-#include <FL/Fl_Simple_Terminal.H>
+#include <FL/Fl_Terminal.H>
#include <FL/fl_ask.H>
#include <stdio.h>
#include <string.h>
@@ -81,7 +81,7 @@ Fl_Button *top,
Fl_Choice *btype;
Fl_Choice *wtype;
Fl_Int_Input *field;
-Fl_Simple_Terminal *tty = 0;
+Fl_Terminal *tty = 0;
typedef struct {
const char *name;
@@ -219,7 +219,7 @@ int main(int argc, char **argv) {
wtype->value(4); // FL_WHEN_RELEASE_ALWAYS is Fl_Browser's default
// Small terminal window for callback messages
- tty = new Fl_Simple_Terminal(0,400,720,120);
+ tty = new Fl_Terminal(0,400,720,120);
tty->history_lines(50);
tty->ansi(true);
diff --git a/test/contrast.cxx b/test/contrast.cxx
index 5619635c4..cdc131a82 100644
--- a/test/contrast.cxx
+++ b/test/contrast.cxx
@@ -26,7 +26,7 @@
#include <FL/Fl_Color_Chooser.H>
#include <FL/Fl_Output.H>
#include <FL/Fl_Hor_Value_Slider.H>
-#include <FL/Fl_Simple_Terminal.H>
+#include <FL/Fl_Terminal.H>
#include <FL/fl_draw.H>
#include <math.h>
@@ -93,7 +93,7 @@ public:
// global variables
-Fl_Simple_Terminal *term = 0;
+Fl_Terminal *term = 0;
double g_lfg; // perceived lightness of foreground color
double g_lbg; // perceived lightness of background color
@@ -428,9 +428,9 @@ int main(int argc, char **argv) {
int ttw = window.w() - 20;
int tth = window.h() - tty - 10;
- term = new Fl_Simple_Terminal(ttx, tty, ttw, tth);
+ term = new Fl_Terminal(ttx, tty, ttw, tth);
term->color(FL_WHITE);
- term->textcolor(FL_BLACK);
+ term->textfgcolor(FL_BLACK);
term->textsize(13);
term->printf("FLTK fl_contrast() test program with different contrast algorithms, version %s\n", version);
diff --git a/test/demo.cxx b/test/demo.cxx
index d24fd92a2..ce9e8fd21 100644
--- a/test/demo.cxx
+++ b/test/demo.cxx
@@ -75,7 +75,7 @@
#include <FL/Fl_Button.H>
#include <FL/Fl_Menu_Button.H> // right click popup menu
#include <FL/Fl_Scheme_Choice.H>
-#include <FL/Fl_Simple_Terminal.H> // tty
+#include <FL/Fl_Terminal.H> // tty
#include <FL/filename.H>
#include <FL/platform.H>
#include <FL/fl_ask.H> // fl_alert()
@@ -102,7 +102,7 @@ void dobut(Fl_Widget *, long);
Fl_Double_Window *form = 0;
Fl_Group *demogrp = 0;
-Fl_Simple_Terminal *tty = 0;
+Fl_Terminal *tty = 0;
Fl_Scheme_Choice *scheme_choice = 0;
Fl_Button *but[9];
Fl_Button *exit_button;
@@ -231,11 +231,15 @@ void create_the_forms() {
demogrp->end();
// Small debug terminal window parented to window, not demogrp
- tty = new Fl_Simple_Terminal(10, FORM_H - 1, FORM_W - 20, 1);
+ // To show/hide debug terminal, use demo's right-click menu
+ //
+ tty = new Fl_Terminal(10, FORM_H - 1, FORM_W - 20, 1);
tty->history_lines(50);
+ tty->display_rows(2); // make display at least 2 rows high, even if not seen
+ tty->display_columns(100); // make display at least 100 cols wide, even if not seen
tty->ansi(true);
tty->hide();
- tty->textsize(10);
+ tty->textsize(12);
// End window
form->end();
diff --git a/test/file_chooser.cxx b/test/file_chooser.cxx
index a79ef2023..46e76d84c 100644
--- a/test/file_chooser.cxx
+++ b/test/file_chooser.cxx
@@ -36,7 +36,7 @@
#include <FL/Fl_PNM_Image.H>
#include <FL/Fl_Light_Button.H>
#include <FL/Fl_Double_Window.H>
-#include <FL/Fl_Simple_Terminal.H>
+#include <FL/Fl_Terminal.H>
#include <stdio.h>
#include <string.h>
@@ -55,7 +55,7 @@ Fl_Input *filter;
Fl_File_Browser *files;
Fl_File_Chooser *fc;
Fl_Shared_Image *image = 0;
-Fl_Simple_Terminal *tty = 0;
+Fl_Terminal *tty = 0;
// for choosing extra groups
Fl_Choice *ch_extra;
@@ -109,8 +109,9 @@ main(int argc, // I - Number of command-line arguments
// Make the main window...
window = new Fl_Double_Window(400, 215+TERMINAL_HEIGHT, "File Chooser Test");
- tty = new Fl_Simple_Terminal(0,215,window->w(),TERMINAL_HEIGHT);
+ tty = new Fl_Terminal(0,215,window->w(),TERMINAL_HEIGHT);
tty->ansi(true);
+ tty->display_columns(100); // at least 100 cols wide, even tho actual window smaller
// Group: limit resizing to filter input (not browse button)
grp = new Fl_Group(0,10,400,25);
diff --git a/test/input.cxx b/test/input.cxx
index 70c47c852..685bd6a2f 100644
--- a/test/input.cxx
+++ b/test/input.cxx
@@ -26,12 +26,12 @@
#include <FL/Fl_Toggle_Button.H>
#include <FL/Fl_Light_Button.H>
#include <FL/Fl_Color_Chooser.H>
-#include <FL/Fl_Simple_Terminal.H>
+#include <FL/Fl_Terminal.H>
#define TERMINAL_HEIGHT 120
// Globals
-Fl_Simple_Terminal *G_tty = 0;
+Fl_Terminal *G_tty = 0;
void cb(Fl_Widget *ob) {
G_tty->printf("Callback for %s '%s'\n",ob->label(),((Fl_Input*)ob)->value());
@@ -91,7 +91,7 @@ int main(int argc, char **argv) {
Fl::args(argc, argv);
Fl::get_system_colors();
Fl_Window *window = new Fl_Window(400,420+TERMINAL_HEIGHT);
- G_tty = new Fl_Simple_Terminal(0,420,window->w(),TERMINAL_HEIGHT);
+ G_tty = new Fl_Terminal(0,420,window->w(),TERMINAL_HEIGHT);
int y = 10;
input[0] = new Fl_Input(70,y,300,30,"Normal:"); y += 35;
diff --git a/test/input_choice.cxx b/test/input_choice.cxx
index b9598d52c..853c79c3a 100644
--- a/test/input_choice.cxx
+++ b/test/input_choice.cxx
@@ -18,12 +18,12 @@
#include <FL/Fl_Button.H>
#include <FL/Fl_Double_Window.H>
#include <FL/Fl_Input_Choice.H>
-#include <FL/Fl_Simple_Terminal.H>
+#include <FL/Fl_Terminal.H>
#define TERMINAL_HEIGHT 120
// Globals
-Fl_Simple_Terminal *G_tty = 0;
+Fl_Terminal *G_tty = 0;
void buttcb(Fl_Widget*,void*data) {
Fl_Input_Choice *in=(Fl_Input_Choice *)data;
@@ -44,7 +44,7 @@ void input_choice_cb(Fl_Widget*,void*data) {
int main(int argc, char **argv) {
Fl_Double_Window win(300, 200+TERMINAL_HEIGHT);
- G_tty = new Fl_Simple_Terminal(0,200,win.w(),TERMINAL_HEIGHT);
+ G_tty = new Fl_Terminal(0,200,win.w(),TERMINAL_HEIGHT);
Fl_Input_Choice in(40,40,100,28,"Test");
in.callback(input_choice_cb, (void*)&in);
diff --git a/test/makedepend b/test/makedepend
index 24ad1c171..a152cfa0e 100644
--- a/test/makedepend
+++ b/test/makedepend
@@ -222,7 +222,7 @@ browser.o: ../FL/Fl_Rect.H
browser.o: ../FL/Fl_RGB_Image.H
browser.o: ../FL/Fl_Scrollbar.H
browser.o: ../FL/Fl_Select_Browser.H
-browser.o: ../FL/Fl_Simple_Terminal.H
+browser.o: ../FL/Fl_Terminal.H
browser.o: ../FL/Fl_Slider.H
browser.o: ../FL/Fl_Text_Buffer.H
browser.o: ../FL/Fl_Text_Display.H
@@ -507,7 +507,7 @@ contrast.o: ../FL/Fl_Return_Button.H
contrast.o: ../FL/Fl_RGB_Image.H
contrast.o: ../FL/Fl_Round_Button.H
contrast.o: ../FL/Fl_Scrollbar.H
-contrast.o: ../FL/Fl_Simple_Terminal.H
+contrast.o: ../FL/Fl_Terminal.H
contrast.o: ../FL/Fl_Slider.H
contrast.o: ../FL/Fl_Text_Buffer.H
contrast.o: ../FL/Fl_Text_Display.H
@@ -693,7 +693,7 @@ demo.o: ../FL/Fl_RGB_Image.H
demo.o: ../FL/Fl_Scheme.H
demo.o: ../FL/Fl_Scheme_Choice.H
demo.o: ../FL/Fl_Scrollbar.H
-demo.o: ../FL/Fl_Simple_Terminal.H
+demo.o: ../FL/Fl_Terminal.H
demo.o: ../FL/Fl_Slider.H
demo.o: ../FL/Fl_Text_Buffer.H
demo.o: ../FL/Fl_Text_Display.H
@@ -911,7 +911,7 @@ file_chooser.o: ../FL/Fl_Return_Button.H
file_chooser.o: ../FL/Fl_RGB_Image.H
file_chooser.o: ../FL/Fl_Scrollbar.H
file_chooser.o: ../FL/Fl_Shared_Image.H
-file_chooser.o: ../FL/Fl_Simple_Terminal.H
+file_chooser.o: ../FL/Fl_Terminal.H
file_chooser.o: ../FL/Fl_Slider.H
file_chooser.o: ../FL/Fl_Text_Buffer.H
file_chooser.o: ../FL/Fl_Text_Display.H
@@ -1409,7 +1409,7 @@ input.o: ../FL/Fl_Return_Button.H
input.o: ../FL/Fl_RGB_Image.H
input.o: ../FL/Fl_Scrollbar.H
input.o: ../FL/Fl_Secret_Input.H
-input.o: ../FL/Fl_Simple_Terminal.H
+input.o: ../FL/Fl_Terminal.H
input.o: ../FL/Fl_Slider.H
input.o: ../FL/Fl_Text_Buffer.H
input.o: ../FL/Fl_Text_Display.H
@@ -1448,7 +1448,7 @@ input_choice.o: ../FL/Fl_Preferences.H
input_choice.o: ../FL/Fl_Rect.H
input_choice.o: ../FL/Fl_RGB_Image.H
input_choice.o: ../FL/Fl_Scrollbar.H
-input_choice.o: ../FL/Fl_Simple_Terminal.H
+input_choice.o: ../FL/Fl_Terminal.H
input_choice.o: ../FL/Fl_Slider.H
input_choice.o: ../FL/Fl_Text_Buffer.H
input_choice.o: ../FL/Fl_Text_Display.H
@@ -1626,7 +1626,7 @@ menubar.o: ../FL/Fl_RGB_Image.H
menubar.o: ../FL/Fl_Scheme.H
menubar.o: ../FL/Fl_Scheme_Choice.H
menubar.o: ../FL/Fl_Scrollbar.H
-menubar.o: ../FL/Fl_Simple_Terminal.H
+menubar.o: ../FL/Fl_Terminal.H
menubar.o: ../FL/Fl_Slider.H
menubar.o: ../FL/fl_string_functions.h
menubar.o: ../FL/Fl_Sys_Menu_Bar.H
@@ -1719,7 +1719,7 @@ native-filechooser.o: ../FL/Fl_Rect.H
native-filechooser.o: ../FL/Fl_Return_Button.H
native-filechooser.o: ../FL/Fl_RGB_Image.H
native-filechooser.o: ../FL/Fl_Scrollbar.H
-native-filechooser.o: ../FL/Fl_Simple_Terminal.H
+native-filechooser.o: ../FL/Fl_Terminal.H
native-filechooser.o: ../FL/Fl_Slider.H
native-filechooser.o: ../FL/Fl_Text_Buffer.H
native-filechooser.o: ../FL/Fl_Text_Display.H
@@ -2446,7 +2446,7 @@ table.o: ../FL/Fl_Rect.H
table.o: ../FL/Fl_RGB_Image.H
table.o: ../FL/Fl_Scroll.H
table.o: ../FL/Fl_Scrollbar.H
-table.o: ../FL/Fl_Simple_Terminal.H
+table.o: ../FL/Fl_Terminal.H
table.o: ../FL/Fl_Slider.H
table.o: ../FL/Fl_Table.H
table.o: ../FL/Fl_Table_Row.H
@@ -2595,7 +2595,7 @@ tree.o: ../FL/Fl_Rect.H
tree.o: ../FL/Fl_Return_Button.H
tree.o: ../FL/Fl_RGB_Image.H
tree.o: ../FL/Fl_Scrollbar.H
-tree.o: ../FL/Fl_Simple_Terminal.H
+tree.o: ../FL/Fl_Terminal.H
tree.o: ../FL/Fl_Slider.H
tree.o: ../FL/Fl_Text_Buffer.H
tree.o: ../FL/Fl_Text_Display.H
@@ -2660,7 +2660,7 @@ unittests.o: ../FL/Fl_Preferences.H
unittests.o: ../FL/Fl_Rect.H
unittests.o: ../FL/Fl_RGB_Image.H
unittests.o: ../FL/Fl_Scrollbar.H
-unittests.o: ../FL/Fl_Simple_Terminal.H
+unittests.o: ../FL/Fl_Terminal.H
unittests.o: ../FL/Fl_Slider.H
unittests.o: ../FL/fl_string_functions.h
unittests.o: ../FL/Fl_Text_Buffer.H
@@ -2788,7 +2788,7 @@ unittest_core.o: ../FL/Fl_Preferences.H
unittest_core.o: ../FL/Fl_Rect.H
unittest_core.o: ../FL/Fl_RGB_Image.H
unittest_core.o: ../FL/Fl_Scrollbar.H
-unittest_core.o: ../FL/Fl_Simple_Terminal.H
+unittest_core.o: ../FL/Fl_Terminal.H
unittest_core.o: ../FL/Fl_Slider.H
unittest_core.o: ../FL/Fl_Text_Buffer.H
unittest_core.o: ../FL/Fl_Text_Display.H
@@ -2980,37 +2980,26 @@ unittest_scrollbarsize.o: ../FL/Fl_Widget.H
unittest_scrollbarsize.o: ../FL/Fl_Window.H
unittest_scrollbarsize.o: ../FL/platform_types.h
unittest_scrollbarsize.o: unittests.h
-unittest_simple_terminal.o: ../FL/Enumerations.H
-unittest_simple_terminal.o: ../FL/Fl.H
-unittest_simple_terminal.o: ../FL/fl_attr.h
-unittest_simple_terminal.o: ../FL/Fl_Bitmap.H
-unittest_simple_terminal.o: ../FL/Fl_Cairo.H
-unittest_simple_terminal.o: ../FL/fl_casts.H
-unittest_simple_terminal.o: ../FL/fl_config.h
-unittest_simple_terminal.o: ../FL/Fl_Device.H
-unittest_simple_terminal.o: ../FL/Fl_Double_Window.H
-unittest_simple_terminal.o: ../FL/fl_draw.H
-unittest_simple_terminal.o: ../FL/Fl_Export.H
-unittest_simple_terminal.o: ../FL/Fl_Graphics_Driver.H
-unittest_simple_terminal.o: ../FL/Fl_Group.H
-unittest_simple_terminal.o: ../FL/Fl_Image.H
-unittest_simple_terminal.o: ../FL/Fl_Pixmap.H
-unittest_simple_terminal.o: ../FL/Fl_Plugin.H
-unittest_simple_terminal.o: ../FL/Fl_Preferences.H
-unittest_simple_terminal.o: ../FL/Fl_Rect.H
-unittest_simple_terminal.o: ../FL/Fl_RGB_Image.H
-unittest_simple_terminal.o: ../FL/Fl_Scrollbar.H
-unittest_simple_terminal.o: ../FL/Fl_Simple_Terminal.H
-unittest_simple_terminal.o: ../FL/Fl_Slider.H
-unittest_simple_terminal.o: ../FL/Fl_Text_Buffer.H
-unittest_simple_terminal.o: ../FL/Fl_Text_Display.H
-unittest_simple_terminal.o: ../FL/fl_types.h
-unittest_simple_terminal.o: ../FL/fl_utf8.h
-unittest_simple_terminal.o: ../FL/Fl_Valuator.H
-unittest_simple_terminal.o: ../FL/Fl_Widget.H
-unittest_simple_terminal.o: ../FL/Fl_Window.H
-unittest_simple_terminal.o: ../FL/platform_types.h
-unittest_simple_terminal.o: unittests.h
+unittest_terminal.o: ../FL/Enumerations.H
+unittest_terminal.o: ../FL/Fl.H
+unittest_terminal.o: ../FL/fl_attr.h
+unittest_terminal.o: ../FL/Fl_Bitmap.H
+unittest_terminal.o: ../FL/Fl_Cairo.H
+unittest_terminal.o: ../FL/fl_casts.H
+unittest_terminal.o: ../FL/fl_config.h
+unittest_terminal.o: ../FL/Fl_Double_Window.H
+unittest_terminal.o: ../FL/Fl_Export.H
+unittest_terminal.o: ../FL/Fl_Group.H
+unittest_terminal.o: ../FL/Fl_Image.H
+unittest_terminal.o: ../FL/Fl_Rect.H
+unittest_terminal.o: ../FL/Fl_Scrollbar.H
+unittest_terminal.o: ../FL/Fl_Terminal.H
+unittest_terminal.o: ../FL/fl_types.h
+unittest_terminal.o: ../FL/fl_utf8.h
+unittest_terminal.o: ../FL/Fl_Widget.H
+unittest_terminal.o: ../FL/Fl_Window.H
+unittest_terminal.o: ../FL/platform_types.h
+unittest_terminal.o: unittests.h
unittest_symbol.o: ../FL/Enumerations.H
unittest_symbol.o: ../FL/Fl.H
unittest_symbol.o: ../FL/fl_attr.h
@@ -3203,7 +3192,7 @@ valuators.o: ../FL/Fl_Repeat_Button.H
valuators.o: ../FL/Fl_RGB_Image.H
valuators.o: ../FL/Fl_Roller.H
valuators.o: ../FL/Fl_Scrollbar.H
-valuators.o: ../FL/Fl_Simple_Terminal.H
+valuators.o: ../FL/Fl_Terminal.H
valuators.o: ../FL/Fl_Slider.H
valuators.o: ../FL/Fl_Spinner.H
valuators.o: ../FL/Fl_Text_Buffer.H
diff --git a/test/menubar.cxx b/test/menubar.cxx
index 034e716e9..e46a60be0 100644
--- a/test/menubar.cxx
+++ b/test/menubar.cxx
@@ -31,14 +31,14 @@
#include <stdio.h>
#include <stdlib.h>
#include <FL/fl_draw.H>
-#include <FL/Fl_Simple_Terminal.H>
+#include <FL/Fl_Terminal.H>
#include <FL/fl_ask.H>
#include <FL/fl_string_functions.h>
#define TERMINAL_HEIGHT 120
// Globals
-Fl_Simple_Terminal *G_tty = 0;
+Fl_Terminal *G_tty = 0;
void window_cb(Fl_Widget* w, void*) {
puts("window callback called"); // end of program, so stdout instead of G_tty
@@ -235,7 +235,7 @@ int main(int argc, char **argv) {
Fl_Scheme_Choice scheme_choice(300, 50, 100, 25, "&scheme");
- G_tty = new Fl_Simple_Terminal(0,400,WIDTH,TERMINAL_HEIGHT);
+ G_tty = new Fl_Terminal(0,400,WIDTH,TERMINAL_HEIGHT);
window.callback(window_cb);
Fl_Menu_Bar menubar(0,0,WIDTH,30); menubar.menu(menutable);
diff --git a/test/native-filechooser.cxx b/test/native-filechooser.cxx
index df2433021..10194f2ca 100644
--- a/test/native-filechooser.cxx
+++ b/test/native-filechooser.cxx
@@ -25,14 +25,14 @@
#include <FL/Fl_Box.H>
#include <FL/Fl_Native_File_Chooser.H>
#include <FL/Fl_Help_View.H>
-#include <FL/Fl_Simple_Terminal.H>
+#include <FL/Fl_Terminal.H>
#define TERMINAL_HEIGHT 120
// GLOBALS
Fl_Input *G_filename = NULL;
Fl_Multiline_Input *G_filter = NULL;
-Fl_Simple_Terminal *G_tty = NULL;
+Fl_Terminal *G_tty = NULL;
void PickFile_CB(Fl_Widget*, void*) {
// Create native chooser
@@ -106,7 +106,7 @@ int main(int argc, char **argv) {
win->size_range(win->w(), win->h(), 0, 0);
win->begin();
{
- G_tty = new Fl_Simple_Terminal(0,400,win->w(),TERMINAL_HEIGHT);
+ G_tty = new Fl_Terminal(0,400,win->w(),TERMINAL_HEIGHT);
int x = 80, y = 10;
G_filename = new Fl_Input(x, y, win->w()-80-10, 25, "Filename");
diff --git a/test/table.cxx b/test/table.cxx
index 06df9bf86..95b9742f7 100644
--- a/test/table.cxx
+++ b/test/table.cxx
@@ -10,7 +10,7 @@
#include <FL/fl_draw.H>
#include <FL/fl_ask.H>
#include <FL/Fl_Table_Row.H>
-#include <FL/Fl_Simple_Terminal.H>
+#include <FL/Fl_Terminal.H>
#include <stdio.h>
#include <string.h>
@@ -19,7 +19,7 @@
#define TERMINAL_HEIGHT 120
// Globals
-Fl_Simple_Terminal *G_tty = 0;
+Fl_Terminal *G_tty = 0;
// Simple demonstration class to derive from Fl_Table_Row
class DemoTable : public Fl_Table_Row
@@ -349,7 +349,7 @@ int main(int argc, char **argv)
{
Fl_Window win(900, 730+TERMINAL_HEIGHT);
- G_tty = new Fl_Simple_Terminal(0,730,win.w(),TERMINAL_HEIGHT);
+ G_tty = new Fl_Terminal(0,730,win.w(),TERMINAL_HEIGHT);
G_table = new DemoTable(20, 20, 860, 460, "Demo");
G_table->selection_color(FL_YELLOW);
diff --git a/test/terminal.fl b/test/terminal.fl
new file mode 100644
index 000000000..920bfc0b3
--- /dev/null
+++ b/test/terminal.fl
@@ -0,0 +1,1972 @@
+# data file for the Fltk User Interface Designer (fluid)
+version 1.0400
+utf8_in_src
+header_name {.h}
+code_name {.cxx}
+comment {DEVELOPER README
+
+APPLICATION
+-----------
+The Application class encompasses the entire application,
+and has the widget hierarchy declared by main_window().
+
+SELF TESTS
+----------
+The "self tests" are all named test_xxx(), and are static
+functions because they were designed that way originally
+(testterm.cxx), so they remain static.
+
+They only need access to the global G_tty to access the
+terminal for printing, and handle their own state
+information.
+
+To add /new/ tests:
+1. Create the test function similar to existing test_xxx().
+2. Add the function's name to the NextEscapeTest() function's
+This makes the new test become part of the self tests.
+
+NextEscapeTest() keeps track of the last test shown as
+a static value internally, and the integer argument
+tells it how to advance to the next test, either fwd or back.
+
+The tests themselves may keep state information, so that
+a single test might have several pages. The return value
+determines whether to recall the test again, or if it's
+on the last page.
+
+} {in_source not_in_header
+}
+
+decl {\#include <stdio.h>} {public global
+}
+
+decl {\#include <stdlib.h> // rand()} {private global
+}
+
+decl {\#include <errno.h>} {public global
+}
+
+decl {\#include <FL/Fl_Window.H>} {public global
+}
+
+decl {\#include <FL/Fl_Terminal.H>} {public global
+}
+
+decl {\#include <FL/Fl_Scheme_Choice.H>} {public global
+}
+
+decl {\#include <FL/fl_ask.H>} {public global
+}
+
+decl {\#include <fcntl.h>} {public global
+}
+
+decl {\#ifdef _WIN32} {public global
+}
+
+decl {\#include <io.h> // _read()} {public global
+}
+
+decl {\#else} {public global
+}
+
+decl {\#include <unistd.h> // read()} {public global
+}
+
+decl {\#endif} {public global
+}
+
+decl {MyTerminal *G_tty;} {
+ comment {Global access to tty} private global
+}
+
+decl {Application *G_app;} {
+ comment {Global access to application} private global
+}
+
+decl {////// TERMINAL RING BUFFER DEBUG CLASS //////} {private local
+}
+
+class MyTerminal {
+ comment {Fl_Terminal derived class that can access ring buffer internals for debugging} open : {public Fl_Terminal}
+} {
+ decl {Fl_Double_Window *ring_debug_win;} {private local
+ }
+ decl {Fl_Terminal *debug_tty;} {private local
+ }
+ decl {bool interactivecursor;} {private local
+ }
+ Function {MyTerminal(int X,int Y,int W,int H, const char *L=0):Fl_Terminal(X,Y,W,H,L)} {
+ comment CTOR open
+ } {
+ code {ring_debug_win = 0;
+debug_tty = 0;
+interactivecursor = false;} {}
+ }
+ Function {show_ring_debug_window()} {open return_type void
+ } {
+ code {// Show the internal ring debug window
+if (!ring_debug_win) {
+ ring_debug_win = new Fl_Double_Window(1250,800,"Fl_Terminal Ring Buffer");
+ ring_debug_win->resizable(ring_debug_win);
+ ring_debug_win->callback(debug_winquit_callback, (void*)this);
+ debug_tty = new Fl_Terminal(0,0,ring_debug_win->w(),ring_debug_win->h());
+ debug_tty->textsize(7);
+ debug_tty->redraw_style(NO_REDRAW);
+ debug_tty->display_columns(400);
+ ring_debug_win->end();
+}
+ring_debug_win->show();
+// Start timer to update ring debug updates
+Fl::add_timeout(.5, ring_debug_timer_callback, (void*)this);} {}
+ }
+ Function {interactive_cursor(bool val)} {
+ comment {Enable/disable interactive cursor movement in test app} open return_type void
+ } {
+ code {interactivecursor = val;} {}
+ }
+ Function {debug_winquit_callback2()} {return_type void
+ } {
+ code {// Hide debug window, disable its update timer
+ring_debug_win->hide();
+Fl::remove_timeout(ring_debug_timer_callback, (void*)this);} {}
+ }
+ Function {debug_winquit_callback(Fl_Widget*,void *userdata)} {return_type {static void}
+ } {
+ code {MyTerminal *o = (MyTerminal*)userdata;
+o->debug_winquit_callback2();} {}
+ }
+ Function {horiz_bracket(int w)} {
+ comment {Print a graphic horizontally oriented bracket of width w} return_type void
+ } {
+ code {debug_tty->append("┏");
+for (int t=0; t<w-2; t++) { debug_tty->print_char("━"); }
+debug_tty->append("┓");} {}
+ }
+ Function {update_ring()} {open return_type void
+ } {
+ code {// Handle updating the ring debug window's contents
+int m1,m2,m3,m4;
+get_selection(m1,m2,m3,m4);
+
+// These reference the Fl_Terminal base class protected members
+int x = 5;
+debug_tty->append("\\033c"); // reset terminal (clear screen, history, home)
+debug_tty->append("\\033[s"); // save cursor
+debug_tty->printf("\\033[%dC \\033[7m Ring Index \\033[0m\\n", x);
+debug_tty->printf("\\033[%dC ring_rows(): %-3d Mouse Selection (srow,scol,erow,ecol)\\n", x, ring_rows());
+debug_tty->printf("\\033[%dC ring_cols(): %-3d \\033[7m(%d,%d,%d,%d)\\033[0m\\n", x, ring_cols(), m1,m2,m3,m4);
+debug_tty->printf("\\033[%dC offset(): %-3d\\n", x, offset());
+debug_tty->printf("\\033[%dC ", x);
+horiz_bracket(disp_cols());
+debug_tty->printf("\\033[u"); // recall cursor
+
+x += disp_cols() + 11;
+debug_tty->printf("\\033[%dC \\033[7m History Index \\033[0m\\n", x);
+debug_tty->printf("\\033[%dC hist_rows(): %d hist_srow()=%d\\n", x, hist_rows(), hist_srow());
+debug_tty->printf("\\033[%dC hist_cols(): %d hist_erow()=%d\\n", x, hist_cols(), hist_erow());
+debug_tty->printf("\\033[%dC hist_use(): %d\\n", x, hist_use());
+debug_tty->printf("\\033[%dC ", x);
+horiz_bracket(disp_cols());
+debug_tty->append("\\033[u"); // recall cursor
+
+x += disp_cols() + 11;
+debug_tty->printf("\\033[%dC \\033[7m Display Index \\033[0m\\n", x);
+debug_tty->printf("\\033[%dC disp_rows(): %d disp_srow()=%d\\n", x, disp_rows(), disp_srow());
+debug_tty->printf("\\033[%dC disp_cols(): %d disp_erow()=%d\\n", x, disp_cols(), disp_erow());
+debug_tty->append("\\n");
+debug_tty->printf("\\033[%dC ", x);
+horiz_bracket(disp_cols());
+debug_tty->append("\\n");
+
+{
+ for ( int row=0; row<ring_rows(); row++ ) {
+ // SHOW RING
+ Utf8Char *u8c = u8c_ring_row(row);
+ debug_tty->printf("%3d R[", row);
+ for ( int col=0; col<ring_cols(); col++,u8c++ ) {
+ // Get Utf8Char's attrib,fg,bg and make that 'current' to draw the char in that style
+ debug_tty->textattrib(u8c->attrib());
+ debug_tty->textfgcolor(u8c->fgcolor());
+ debug_tty->textbgcolor(u8c->bgcolor());
+ debug_tty->print_char(u8c->text_utf8()); // print the char in current style
+ }
+ debug_tty->append("\\033[0m"); // restore default fg/bg/attrib
+ debug_tty->append("] ");
+ // SHOW HISTORY
+ if ( row < hist_rows() ) {
+ u8c = u8c_ring_row(hist_srow() + row);
+ debug_tty->printf("%3d H[", row);
+ for ( int col=0; col<hist_cols(); col++,u8c++ ) {
+ // Get Utf8Char's attrib,fg,bg and make that 'current' to draw the char in that style
+ debug_tty->textattrib(u8c->attrib());
+ debug_tty->textfgcolor(u8c->fgcolor());
+ debug_tty->textbgcolor(u8c->bgcolor());
+ debug_tty->print_char(u8c->text_utf8()); // print the char in current style
+ }
+ debug_tty->append("\\033[0m"); // restore default fg/bg/attrib
+ debug_tty->printf("] ");
+ } else {
+ debug_tty->append(" ");
+ for ( int col=0; col<hist_cols(); col++ ) { debug_tty->append(" "); }
+ debug_tty->append(" ");
+ }
+ // SHOW DISPLAY
+ if ( row < disp_rows() ) {
+ u8c = u8c_ring_row(disp_srow() + row);
+ debug_tty->printf("%3d D[", row);
+ for ( int col=0; col<disp_cols(); col++,u8c++ ) {
+ // Get Utf8Char's attrib,fg,bg and make that 'current' to draw the char in that style
+ debug_tty->textattrib(u8c->attrib());
+ debug_tty->textfgcolor(u8c->fgcolor());
+ debug_tty->textbgcolor(u8c->bgcolor());
+ debug_tty->print_char(u8c->text_utf8()); // print the char in current style
+ }
+ debug_tty->append("\\033[0m"); // restore default fg/bg/attrib
+ debug_tty->append("]\\033[K\\n");
+ } else {
+ debug_tty->append("\\033[K\\n");
+ }
+ }
+}
+debug_tty->printf("--- END\\033[0J\\n"); // clear eos} {}
+ }
+ Function {ring_debug_timer_callback2()} {open return_type void
+ } {
+ code {// Update the ring debug window
+update_ring();
+
+// Redraw after all changes have been made
+ring_debug_win->redraw();
+Fl::repeat_timeout(.10, ring_debug_timer_callback, (void*)this);} {}
+ }
+ Function {ring_debug_timer_callback(void* userdata)} {return_type {static void}
+ } {
+ code {MyTerminal *o = (MyTerminal*)userdata;
+o->ring_debug_timer_callback2();} {}
+ }
+ Function {handle(int e)} {
+ comment {Event handler} return_type int
+ } {
+ code {switch (e) {
+ case FL_KEYBOARD: {
+ if (interactivecursor) {
+ switch (Fl::event_key()) {
+ case FL_Up: cursor_up(1,true); redraw(); return 1;
+ case FL_Down: cursor_down(1,true); redraw(); return 1;
+ case FL_Left: cursor_left(); redraw(); return 1;
+ case FL_Right: cursor_right(); redraw(); return 1;
+ }
+ }
+ break;
+ }
+}
+return Fl_Terminal::handle(e);} {}
+ }
+}
+
+decl {////// APPLICATION CLASS //////} {private local
+}
+
+class Application {open
+} {
+ decl {int G_lines;} {
+ comment {Line counter for +50 line test
+} private local
+ }
+ Function {Application()} {
+ comment {// Ctor}
+ } {
+ code {// CTOR
+G_lines = 1;
+make_window();
+
+// Do widget init in make_window()'s extra 'Code' section, not here.} {}
+ }
+ Function {show(int argc=0, char **argv=0)} {
+ comment {Show the app window
+} return_type void
+ } {
+ code {if (argv) win->show(argc,argv);
+else win->show();} {}
+ }
+ Function {AppendTty(const char *s)} {
+ comment {Append text to Fl_Terminal (and stdout if radio button set)
+} return_type void
+ } {
+ code {// Append to Fl_Terminal
+G_tty->append(s);
+
+// Send to stdout too?
+if (G_app->stdout_radio->value()) {
+ printf("%s", s);
+ fflush(stdout);
+}} {}
+ }
+ decl {////// ESC SELF TESTS //////} {private local
+ }
+ Function {NextEscapeTest(int dir)} {
+ comment {Advance/Reverse thru escape code tests} open return_type {static void}
+ } {
+ code {// *** ADD NEW TESTS HERE ***
+typedef int (TestFunc)(bool);
+static int testret = 1; // last test return value
+static int testnum = 0;
+static TestFunc *tests[] = {
+ test_firstpage,
+ test_esc_attr,
+ test_esc_rgbcolors,
+ test_esc_fgbg_colors,
+ test_esc_curpos,
+ test_esc_tabstops,
+ test_esc_insert_char,
+ test_esc_clear_test,
+ test_esc_delete_row,
+ test_esc_insert_row,
+ test_esc_scrolling
+ // ^^^ INSERT NEW TEST FUNCS ABOVE THIS LINE ^^^
+};
+
+int numtests = sizeof(tests) / sizeof(TestFunc*);
+testnum += (dir * testret); // advance testnum only if last test isn't recall
+if (testnum >= numtests) testnum = 0;
+if (testnum < 0) testnum = numtests-1;
+if (dir == 10000) { // 1000: special case to reset all tests, run first test
+ testnum = 0; // run first test
+ for ( int i=0; i<numtests; i++ ) tests[i](true); // reset all tests
+}
+
+/*** NO: THIS SCREWS UP DELETE CURRENT LINE TESTS
+// If cursor floating, do a crlf first
+if (G_tty->cursor_col()!=0) G_tty->append("\\n");
+****/
+
+// Run test, save return value
+testret = tests[testnum](false);
+G_tty->redraw();} {}
+ }
+ Function {show_test(const char **test, int &index)} {
+ comment {Show test in test[] starting at 'index', returns 0 or 1
+} return_type {static int}
+ } {
+ code {// Show test in test[] starting at 'index'.
+// 'index' is adjusted to point to next test.
+// Returns number to add to parent test index:
+// 0 - test shown, call here again for next page in this test
+// 1 - test shown, move on to next test for next time
+//
+while (test[index]) {
+ G_app->AppendTty(test[index++]);
+ if (strncmp(test[index-1], "--->", 4) == 0 ) {
+ if (test[index] == NULL) { index = 0; return 1; } // rewind, tell parent to move to next test
+ else { return 0; } // tell parent to call us again for next test
+ }
+}
+index = 0;
+return 1; // hit end of test, reset} {}
+ }
+ Function {test_firstpage(bool reset)} {
+ comment {--- 0000: Test unicode alignment} return_type {static int}
+ } {
+ code {G_app->AppendTty(
+ "\\033[0m\\033[H\\033[2J\\033[3J" // color/attr reset, home, cls, clear history
+ "hello.\\nLine one\\nLine two\\n"
+ "\\033[0;0;2m Dim text:\\033[31m red\\033[32m grn\\033[33m yel"
+ "\\033[34m blu\\033[35m mag\\033[36m cyn\\033[37m wht\\n"
+ "\\033[0;0;0m Normal text:\\033[31m red\\033[32m grn\\033[33m yel"
+ "\\033[34m blu\\033[35m mag\\033[36m cyn\\033[37m wht\\n"
+ "\\033[0;0;1m Bold text:\\033[31m red\\033[32m grn\\033[33m yel"
+ "\\033[34m blu\\033[35m mag\\033[36m cyn\\033[37m wht\\n"
+ "\\033[0;1;3m Bold Italic text:\\033[31m red\\033[32m grn\\033[33m yel"
+ "\\033[34m blu\\033[35m mag\\033[36m cyn\\033[37m wht\\n"
+ "\\033[0m\\n"
+ // Language Language
+ // Alignment Alignment
+ // <-----> <---->
+ "Here's some utf8: ||| AAA || AAA\\n"
+ "╳╳ ██ ▏▏┌─┬┐ ┏━┳┓ ╔═╦╗ ██████ ||| AAA Hell Yeah Jim - English || AAA\\n"
+ "╳╳ ██ ▏▏│ ││ ┃ ┃┃ ║ ║║ ██ ██ ||| AAA Первый рабочий - Russian || AAA\\n"
+ "╳╳ ██ ▏▏├─┼┤ ┣━╋┫ ╠═╬╣ ██ ██ ||| AAA テキスト処理法 - Japanese || AAA\\n"
+ "╳╳ ██ ▏▏└─┴┘ ┗━┻┛ ╚═╩╝ ██████ ||| AAA 香港增补字符集 - Chinese || AAA\\n"
+ " ||| AAA || AAA\\n"
+ "underbar: ______ ||| AAA || AAA\\n"
+ " overbar: ‾‾‾‾‾‾ ||| AAA || AAA\\n"
+ "\\n"
+ "\\033[23X >> KEYBOARD KEYS: Arrow keys move cursor <<\\n"
+ "\\033[23X >> 's'=speed test 'c'=colorbars <<\\n"
+ "\\033[23X >> 'e'=next esc test 'E'=prev esc test <<\\n"
+ "\\033[23X >> 'q'=quit <<\\n"
+ "\\033[23X >> <<\\n"
+ "\\033[23X >> <<\\n"
+ "\\n"
+ "Some floating text. >X<\\b\\033[D"); // put cursor between > < using BS and cursor_left
+return 1; // tell parent to move to next test} {}
+ }
+ Function {test_esc_attr(bool reset)} {
+ comment {--- 0010: Test attributes} return_type {static int}
+ } {
+ code {static int index = 0;
+const char *test[] = {
+ // Screen 10
+ "\\033[H\\033[2J" "ESC TEST 0010: Attributes\\n",
+ " \\033[0;2m Some Dim text \\033[0m \\033[0m Normal text \\033[0m \\033[0;1m Some Bold text \\033[0m\\n"
+ " \\033[2;3m Dim italic text \\033[0m \\033[3m Normal italic \\033[0m \\033[1;3m Bold italic text \\033[0m\\n"
+ " \\033[2;4m Dim underline \\033[0m \\033[4m Normal underline \\033[0m \\033[1;4m Bold underline \\033[0m\\n"
+ " \\033[2;5m Dim blinking \\033[0m \\033[5m Normal blinking \\033[0m \\033[1;5m Bold blinking \\033[0m\\n"
+ " \\033[2;7m Dim inverse [█] \\033[0m \\033[7m Normal inverse [█] \\033[0m \\033[1;7m Bold inverse [█] \\033[0m\\n"
+ " \\033[2;9m Dim strikeout \\033[0m \\033[9m Normal strikeout \\033[0m \\033[1;9m Bold strikeout \\033[0m\\n"
+ "\\n",
+ "---> Hit 'e' for next test: ",
+ NULL
+};
+if (reset) { index = 0; return 0; }
+return show_test(test, index);} {}
+ }
+ Function {test_esc_rgbcolors(bool reset)} {
+ comment {--- 0020: Test RGB Colors} return_type {static int}
+ } {
+ code {static int index = 0;
+const char *test[] = {
+ // Screen 20
+ "\\033[2J\\033[H" "ESC TEST 0020: RGB Colors\\n",
+ "\\033[0m\\n", // NORMAL
+ "\\tNormal Grayscale: \\033[38;2;32;32;32mXXX" "\\033[38;2;64;64;64mXXX" "\\033[38;2;96;96;96mXXX" "\\033[38;2;128;128;128mXXX",
+ "\\033[38;2;160;160;160mXXX" "\\033[38;2;192;192;192mXXX" "\\033[38;2;224;224;224mXXX" "\\033[38;2;255;255;255mXXX",
+ "\\033[38;2;255;0;0m\\n", // change to Red for next row
+ "\\tNormal RedScale: \\033[38;2;32;0;0mXXX" "\\033[38;2;64;0;0mXXX" "\\033[38;2;96;0;0mXXX" "\\033[38;2;128;0;0mXXX",
+ "\\033[38;2;160;0;0mXXX" "\\033[38;2;192;0;0mXXX" "\\033[38;2;224;0;0mXXX" "\\033[38;2;255;0;0mXXX",
+ "\\033[38;2;0;255;0m\\n", // change to Grn for next row
+ "\\tNormal GrnScale: \\033[38;2;0;32;0mXXX" "\\033[38;2;0;64;0mXXX" "\\033[38;2;0;96;0mXXX" "\\033[38;2;0;128;0mXXX",
+ "\\033[38;2;0;160;0mXXX" "\\033[38;2;0;192;0mXXX" "\\033[38;2;0;224;0mXXX" "\\033[38;2;0;255;0mXXX",
+ "\\033[38;2;0;0;255m\\n", // change to Blu for next row
+ "\\tNormal BluScale: \\033[38;2;0;0;32mXXX" "\\033[38;2;0;0;64mXXX" "\\033[38;2;0;0;96mXXX" "\\033[38;2;0;0;128mXXX",
+ "\\033[38;2;0;0;160mXXX" "\\033[38;2;0;0;192mXXX" "\\033[38;2;0;0;224mXXX" "\\033[38;2;0;0;255mXXX",
+ "\\n",
+ "\\033[0;3m\\n", // ITALIC
+ "\\tItalic Grayscale: \\033[38;2;32;32;32mXXX" "\\033[38;2;64;64;64mXXX" "\\033[38;2;96;96;96mXXX" "\\033[38;2;128;128;128mXXX",
+ "\\033[38;2;160;160;160mXXX" "\\033[38;2;192;192;192mXXX" "\\033[38;2;224;224;224mXXX" "\\033[38;2;255;255;255mXXX",
+ "\\033[38;2;255;0;0m\\n", // change to Red for next row
+ "\\tItalic RedScale: \\033[38;2;32;0;0mXXX" "\\033[38;2;64;0;0mXXX" "\\033[38;2;96;0;0mXXX" "\\033[38;2;128;0;0mXXX",
+ "\\033[38;2;160;0;0mXXX" "\\033[38;2;192;0;0mXXX" "\\033[38;2;224;0;0mXXX" "\\033[38;2;255;0;0mXXX",
+ "\\033[38;2;0;255;0m\\n", // change to Grn for next row
+ "\\tItalic GrnScale: \\033[38;2;0;32;0mXXX" "\\033[38;2;0;64;0mXXX" "\\033[38;2;0;96;0mXXX" "\\033[38;2;0;128;0mXXX",
+ "\\033[38;2;0;160;0mXXX" "\\033[38;2;0;192;0mXXX" "\\033[38;2;0;224;0mXXX" "\\033[38;2;0;255;0mXXX",
+ "\\033[38;2;0;0;255m\\n", // change to Blu for next row
+ "\\tItalic BluScale: \\033[38;2;0;0;32mXXX" "\\033[38;2;0;0;64mXXX" "\\033[38;2;0;0;96mXXX" "\\033[38;2;0;0;128mXXX",
+ "\\033[38;2;0;0;160mXXX" "\\033[38;2;0;0;192mXXX" "\\033[38;2;0;0;224mXXX" "\\033[38;2;0;0;255mXXX",
+ "\\n",
+ "\\033[0;4m\\n", // UNDERLINE
+ "\\tUnderl Grayscale: \\033[38;2;32;32;32mXXX" "\\033[38;2;64;64;64mXXX" "\\033[38;2;96;96;96mXXX" "\\033[38;2;128;128;128mXXX",
+ "\\033[38;2;160;160;160mXXX" "\\033[38;2;192;192;192mXXX" "\\033[38;2;224;224;224mXXX" "\\033[38;2;255;255;255mXXX",
+ "\\033[38;2;255;0;0m\\n", // change to Red for next row
+ "\\tUnderl RedScale: \\033[38;2;32;0;0mXXX" "\\033[38;2;64;0;0mXXX" "\\033[38;2;96;0;0mXXX" "\\033[38;2;128;0;0mXXX",
+ "\\033[38;2;160;0;0mXXX" "\\033[38;2;192;0;0mXXX" "\\033[38;2;224;0;0mXXX" "\\033[38;2;255;0;0mXXX",
+ "\\033[38;2;0;255;0m\\n", // change to Grn for next row
+ "\\tUnderl GrnScale: \\033[38;2;0;32;0mXXX" "\\033[38;2;0;64;0mXXX" "\\033[38;2;0;96;0mXXX" "\\033[38;2;0;128;0mXXX",
+ "\\033[38;2;0;160;0mXXX" "\\033[38;2;0;192;0mXXX" "\\033[38;2;0;224;0mXXX" "\\033[38;2;0;255;0mXXX",
+ "\\033[38;2;0;0;255m\\n", // change to Blu for next row
+ "\\tUnderl BluScale: \\033[38;2;0;0;32mXXX" "\\033[38;2;0;0;64mXXX" "\\033[38;2;0;0;96mXXX" "\\033[38;2;0;0;128mXXX",
+ "\\033[38;2;0;0;160mXXX" "\\033[38;2;0;0;192mXXX" "\\033[38;2;0;0;224mXXX" "\\033[38;2;0;0;255mXXX",
+ "\\n",
+ "\\033[0;7m\\n", // INVERSE
+ "\\tInvers Grayscale: \\033[7;38;2;32;32;32mXXX" "\\033[38;2;64;64;64mXXX" "\\033[38;2;96;96;96mXXX" "\\033[38;2;128;128;128mXXX",
+ "\\033[38;2;160;160;160mXXX" "\\033[38;2;192;192;192mXXX" "\\033[38;2;224;224;224mXXX" "\\033[38;2;255;255;255mXXX",
+ "\\033[38;2;255;0;0m\\n", // change to Red for next row
+ "\\tInvers RedScale: \\033[38;2;32;0;0mXXX" "\\033[38;2;64;0;0mXXX" "\\033[38;2;96;0;0mXXX" "\\033[38;2;128;0;0mXXX",
+ "\\033[38;2;160;0;0mXXX" "\\033[38;2;192;0;0mXXX" "\\033[38;2;224;0;0mXXX" "\\033[38;2;255;0;0mXXX",
+ "\\033[38;2;0;255;0m\\n", // change to Grn for next row
+ "\\tInvers GrnScale: \\033[38;2;0;32;0mXXX" "\\033[38;2;0;64;0mXXX" "\\033[38;2;0;96;0mXXX" "\\033[38;2;0;128;0mXXX",
+ "\\033[38;2;0;160;0mXXX" "\\033[38;2;0;192;0mXXX" "\\033[38;2;0;224;0mXXX" "\\033[38;2;0;255;0mXXX",
+ "\\033[38;2;0;0;255m\\n", // change to Blu for next row
+ "\\tInvers BluScale: \\033[38;2;0;0;32mXXX" "\\033[38;2;0;0;64mXXX" "\\033[38;2;0;0;96mXXX" "\\033[38;2;0;0;128mXXX",
+ "\\033[38;2;0;0;160mXXX" "\\033[38;2;0;0;192mXXX" "\\033[38;2;0;0;224mXXX" "\\033[38;2;0;0;255mXXX",
+ "\\033[0m\\n\\n",
+ "---> Hit 'e' for next test: ",
+ // Screen 21
+ "\\033[2J\\033[H" "ESC TEST 0021: RGB Color Ramps\\n",
+ "\\n",
+ "Red: ",
+ "\\033[48;2;0;0;0m.\\033[48;2;1;0;0m.\\033[48;2;2;0;0m.\\033[48;2;3;0;0m.\\033[48;2;4;0;0m.\\033[48;2;5;0;0m.\\033[48;2;6;0;0m.\\033[48;2;7;0;0m.",
+ "\\033[48;2;8;0;0m.\\033[48;2;9;0;0m.\\033[48;2;10;0;0m.\\033[48;2;11;0;0m.\\033[48;2;12;0;0m.\\033[48;2;13;0;0m.\\033[48;2;14;0;0m.\\033[48;2;15;0;0m.",
+ "\\033[48;2;16;0;0m.\\033[48;2;17;0;0m.\\033[48;2;18;0;0m.\\033[48;2;19;0;0m.\\033[48;2;20;0;0m.\\033[48;2;21;0;0m.\\033[48;2;22;0;0m.\\033[48;2;23;0;0m.",
+ "\\033[48;2;24;0;0m.\\033[48;2;25;0;0m.\\033[48;2;26;0;0m.\\033[48;2;27;0;0m.\\033[48;2;28;0;0m.\\033[48;2;29;0;0m.\\033[48;2;30;0;0m.\\033[48;2;31;0;0m.",
+ "\\033[48;2;32;0;0m.\\033[48;2;33;0;0m.\\033[48;2;34;0;0m.\\033[48;2;35;0;0m.\\033[48;2;36;0;0m.\\033[48;2;37;0;0m.\\033[48;2;38;0;0m.\\033[48;2;39;0;0m.",
+ "\\033[48;2;40;0;0m.\\033[48;2;41;0;0m.\\033[48;2;42;0;0m.\\033[48;2;43;0;0m.\\033[48;2;44;0;0m.\\033[48;2;45;0;0m.\\033[48;2;46;0;0m.\\033[48;2;47;0;0m.",
+ "\\033[48;2;48;0;0m.\\033[48;2;49;0;0m.\\033[48;2;50;0;0m.\\033[48;2;51;0;0m.\\033[48;2;52;0;0m.\\033[48;2;53;0;0m.\\033[48;2;54;0;0m.\\033[48;2;55;0;0m.",
+ "\\033[48;2;56;0;0m.\\033[48;2;57;0;0m.\\033[48;2;58;0;0m.\\033[48;2;59;0;0m.\\033[48;2;60;0;0m.\\033[48;2;61;0;0m.\\033[48;2;62;0;0m.\\033[48;2;63;0;0m.",
+ "\\033[48;2;64;0;0m.\\033[48;2;65;0;0m.\\033[48;2;66;0;0m.\\033[48;2;67;0;0m.\\033[48;2;68;0;0m.\\033[48;2;69;0;0m.\\033[48;2;70;0;0m.\\033[48;2;71;0;0m.",
+ "\\033[48;2;72;0;0m.\\033[48;2;73;0;0m.\\033[48;2;74;0;0m.\\033[48;2;75;0;0m.\\033[48;2;76;0;0m.\\033[48;2;77;0;0m.\\033[48;2;78;0;0m.\\033[48;2;79;0;0m.",
+ "\\033[48;2;80;0;0m.\\033[48;2;81;0;0m.\\033[48;2;82;0;0m.\\033[48;2;83;0;0m.\\033[48;2;84;0;0m.\\033[48;2;85;0;0m.\\033[48;2;86;0;0m.\\033[48;2;87;0;0m.",
+ "\\033[48;2;88;0;0m.\\033[48;2;89;0;0m.\\033[48;2;90;0;0m.\\033[48;2;91;0;0m.\\033[48;2;92;0;0m.\\033[48;2;93;0;0m.\\033[48;2;94;0;0m.\\033[48;2;95;0;0m.",
+ "\\033[0m\\n ",
+ "\\033[48;2;96;0;0m.\\033[48;2;97;0;0m.\\033[48;2;98;0;0m.\\033[48;2;99;0;0m.\\033[48;2;100;0;0m.\\033[48;2;101;0;0m.\\033[48;2;102;0;0m.\\033[48;2;103;0;0m.",
+ "\\033[48;2;104;0;0m.\\033[48;2;105;0;0m.\\033[48;2;106;0;0m.\\033[48;2;107;0;0m.\\033[48;2;108;0;0m.\\033[48;2;109;0;0m.\\033[48;2;110;0;0m.\\033[48;2;111;0;0m.",
+ "\\033[48;2;112;0;0m.\\033[48;2;113;0;0m.\\033[48;2;114;0;0m.\\033[48;2;115;0;0m.\\033[48;2;116;0;0m.\\033[48;2;117;0;0m.\\033[48;2;118;0;0m.\\033[48;2;119;0;0m.",
+ "\\033[48;2;120;0;0m.\\033[48;2;121;0;0m.\\033[48;2;122;0;0m.\\033[48;2;123;0;0m.\\033[48;2;124;0;0m.\\033[48;2;125;0;0m.\\033[48;2;126;0;0m.\\033[48;2;127;0;0m.",
+ "\\033[48;2;128;0;0m.\\033[48;2;129;0;0m.\\033[48;2;130;0;0m.\\033[48;2;131;0;0m.\\033[48;2;132;0;0m.\\033[48;2;133;0;0m.\\033[48;2;134;0;0m.\\033[48;2;135;0;0m.",
+ "\\033[48;2;136;0;0m.\\033[48;2;137;0;0m.\\033[48;2;138;0;0m.\\033[48;2;139;0;0m.\\033[48;2;140;0;0m.\\033[48;2;141;0;0m.\\033[48;2;142;0;0m.\\033[48;2;143;0;0m.",
+ "\\033[48;2;144;0;0m.\\033[48;2;145;0;0m.\\033[48;2;146;0;0m.\\033[48;2;147;0;0m.\\033[48;2;148;0;0m.\\033[48;2;149;0;0m.\\033[48;2;150;0;0m.\\033[48;2;151;0;0m.",
+ "\\033[48;2;152;0;0m.\\033[48;2;153;0;0m.\\033[48;2;154;0;0m.\\033[48;2;155;0;0m.\\033[48;2;156;0;0m.\\033[48;2;157;0;0m.\\033[48;2;158;0;0m.\\033[48;2;159;0;0m.",
+ "\\033[48;2;160;0;0m.\\033[48;2;161;0;0m.\\033[48;2;162;0;0m.\\033[48;2;163;0;0m.\\033[48;2;164;0;0m.\\033[48;2;165;0;0m.\\033[48;2;166;0;0m.\\033[48;2;167;0;0m.",
+ "\\033[48;2;168;0;0m.\\033[48;2;169;0;0m.\\033[48;2;170;0;0m.\\033[48;2;171;0;0m.\\033[48;2;172;0;0m.\\033[48;2;173;0;0m.\\033[48;2;174;0;0m.\\033[48;2;175;0;0m.",
+ "\\033[48;2;176;0;0m.\\033[48;2;177;0;0m.\\033[48;2;178;0;0m.\\033[48;2;179;0;0m.\\033[48;2;180;0;0m.\\033[48;2;181;0;0m.\\033[48;2;182;0;0m.\\033[48;2;183;0;0m.",
+ "\\033[48;2;184;0;0m.\\033[48;2;185;0;0m.\\033[48;2;186;0;0m.\\033[48;2;187;0;0m.\\033[48;2;188;0;0m.\\033[48;2;189;0;0m.\\033[48;2;190;0;0m.\\033[48;2;191;0;0m.",
+ "\\033[0m\\n ",
+ "\\033[48;2;192;0;0m.\\033[48;2;193;0;0m.\\033[48;2;194;0;0m.\\033[48;2;195;0;0m.\\033[48;2;196;0;0m.\\033[48;2;197;0;0m.\\033[48;2;198;0;0m.\\033[48;2;199;0;0m.",
+ "\\033[48;2;200;0;0m.\\033[48;2;201;0;0m.\\033[48;2;202;0;0m.\\033[48;2;203;0;0m.\\033[48;2;204;0;0m.\\033[48;2;205;0;0m.\\033[48;2;206;0;0m.\\033[48;2;207;0;0m.",
+ "\\033[48;2;208;0;0m.\\033[48;2;209;0;0m.\\033[48;2;210;0;0m.\\033[48;2;211;0;0m.\\033[48;2;212;0;0m.\\033[48;2;213;0;0m.\\033[48;2;214;0;0m.\\033[48;2;215;0;0m.",
+ "\\033[48;2;216;0;0m.\\033[48;2;217;0;0m.\\033[48;2;218;0;0m.\\033[48;2;219;0;0m.\\033[48;2;220;0;0m.\\033[48;2;221;0;0m.\\033[48;2;222;0;0m.\\033[48;2;223;0;0m.",
+ "\\033[48;2;224;0;0m.\\033[48;2;225;0;0m.\\033[48;2;226;0;0m.\\033[48;2;227;0;0m.\\033[48;2;228;0;0m.\\033[48;2;229;0;0m.\\033[48;2;230;0;0m.\\033[48;2;231;0;0m.",
+ "\\033[48;2;232;0;0m.\\033[48;2;233;0;0m.\\033[48;2;234;0;0m.\\033[48;2;235;0;0m.\\033[48;2;236;0;0m.\\033[48;2;237;0;0m.\\033[48;2;238;0;0m.\\033[48;2;239;0;0m.",
+ "\\033[48;2;240;0;0m.\\033[48;2;241;0;0m.\\033[48;2;242;0;0m.\\033[48;2;243;0;0m.\\033[48;2;244;0;0m.\\033[48;2;245;0;0m.\\033[48;2;246;0;0m.\\033[48;2;247;0;0m.",
+ "\\033[48;2;248;0;0m.\\033[48;2;249;0;0m.\\033[48;2;250;0;0m.\\033[48;2;251;0;0m.\\033[48;2;252;0;0m.\\033[48;2;253;0;0m.\\033[48;2;254;0;0m.\\033[48;2;255;0;0m.",
+ "\\033[0m\\n",
+ "\\n",
+ "Grn: ",
+ "\\033[48;2;0;0;0m.\\033[48;2;0;1;0m.\\033[48;2;0;2;0m.\\033[48;2;0;3;0m.\\033[48;2;0;4;0m.\\033[48;2;0;5;0m.\\033[48;2;0;6;0m.\\033[48;2;0;7;0m.",
+ "\\033[48;2;0;8;0m.\\033[48;2;0;9;0m.\\033[48;2;0;10;0m.\\033[48;2;0;11;0m.\\033[48;2;0;12;0m.\\033[48;2;0;13;0m.\\033[48;2;0;14;0m.\\033[48;2;0;15;0m.",
+ "\\033[48;2;0;16;0m.\\033[48;2;0;17;0m.\\033[48;2;0;18;0m.\\033[48;2;0;19;0m.\\033[48;2;0;20;0m.\\033[48;2;0;21;0m.\\033[48;2;0;22;0m.\\033[48;2;0;23;0m.",
+ "\\033[48;2;0;24;0m.\\033[48;2;0;25;0m.\\033[48;2;0;26;0m.\\033[48;2;0;27;0m.\\033[48;2;0;28;0m.\\033[48;2;0;29;0m.\\033[48;2;0;30;0m.\\033[48;2;0;31;0m.",
+ "\\033[48;2;0;32;0m.\\033[48;2;0;33;0m.\\033[48;2;0;34;0m.\\033[48;2;0;35;0m.\\033[48;2;0;36;0m.\\033[48;2;0;37;0m.\\033[48;2;0;38;0m.\\033[48;2;0;39;0m.",
+ "\\033[48;2;0;40;0m.\\033[48;2;0;41;0m.\\033[48;2;0;42;0m.\\033[48;2;0;43;0m.\\033[48;2;0;44;0m.\\033[48;2;0;45;0m.\\033[48;2;0;46;0m.\\033[48;2;0;47;0m.",
+ "\\033[48;2;0;48;0m.\\033[48;2;0;49;0m.\\033[48;2;0;50;0m.\\033[48;2;0;51;0m.\\033[48;2;0;52;0m.\\033[48;2;0;53;0m.\\033[48;2;0;54;0m.\\033[48;2;0;55;0m.",
+ "\\033[48;2;0;56;0m.\\033[48;2;0;57;0m.\\033[48;2;0;58;0m.\\033[48;2;0;59;0m.\\033[48;2;0;60;0m.\\033[48;2;0;61;0m.\\033[48;2;0;62;0m.\\033[48;2;0;63;0m.",
+ "\\033[48;2;0;64;0m.\\033[48;2;0;65;0m.\\033[48;2;0;66;0m.\\033[48;2;0;67;0m.\\033[48;2;0;68;0m.\\033[48;2;0;69;0m.\\033[48;2;0;70;0m.\\033[48;2;0;71;0m.",
+ "\\033[48;2;0;72;0m.\\033[48;2;0;73;0m.\\033[48;2;0;74;0m.\\033[48;2;0;75;0m.\\033[48;2;0;76;0m.\\033[48;2;0;77;0m.\\033[48;2;0;78;0m.\\033[48;2;0;79;0m.",
+ "\\033[48;2;0;80;0m.\\033[48;2;0;81;0m.\\033[48;2;0;82;0m.\\033[48;2;0;83;0m.\\033[48;2;0;84;0m.\\033[48;2;0;85;0m.\\033[48;2;0;86;0m.\\033[48;2;0;87;0m.",
+ "\\033[48;2;0;88;0m.\\033[48;2;0;89;0m.\\033[48;2;0;90;0m.\\033[48;2;0;91;0m.\\033[48;2;0;92;0m.\\033[48;2;0;93;0m.\\033[48;2;0;94;0m.\\033[48;2;0;95;0m.",
+ "\\033[0m\\n ",
+ "\\033[48;2;0;96;0m.\\033[48;2;0;97;0m.\\033[48;2;0;98;0m.\\033[48;2;0;99;0m.\\033[48;2;0;100;0m.\\033[48;2;0;101;0m.\\033[48;2;0;102;0m.\\033[48;2;0;103;0m.",
+ "\\033[48;2;0;104;0m.\\033[48;2;0;105;0m.\\033[48;2;0;106;0m.\\033[48;2;0;107;0m.\\033[48;2;0;108;0m.\\033[48;2;0;109;0m.\\033[48;2;0;110;0m.\\033[48;2;0;111;0m.",
+ "\\033[48;2;0;112;0m.\\033[48;2;0;113;0m.\\033[48;2;0;114;0m.\\033[48;2;0;115;0m.\\033[48;2;0;116;0m.\\033[48;2;0;117;0m.\\033[48;2;0;118;0m.\\033[48;2;0;119;0m.",
+ "\\033[48;2;0;120;0m.\\033[48;2;0;121;0m.\\033[48;2;0;122;0m.\\033[48;2;0;123;0m.\\033[48;2;0;124;0m.\\033[48;2;0;125;0m.\\033[48;2;0;126;0m.\\033[48;2;0;127;0m.",
+ "\\033[48;2;0;128;0m.\\033[48;2;0;129;0m.\\033[48;2;0;130;0m.\\033[48;2;0;131;0m.\\033[48;2;0;132;0m.\\033[48;2;0;133;0m.\\033[48;2;0;134;0m.\\033[48;2;0;135;0m.",
+ "\\033[48;2;0;136;0m.\\033[48;2;0;137;0m.\\033[48;2;0;138;0m.\\033[48;2;0;139;0m.\\033[48;2;0;140;0m.\\033[48;2;0;141;0m.\\033[48;2;0;142;0m.\\033[48;2;0;143;0m.",
+ "\\033[48;2;0;144;0m.\\033[48;2;0;145;0m.\\033[48;2;0;146;0m.\\033[48;2;0;147;0m.\\033[48;2;0;148;0m.\\033[48;2;0;149;0m.\\033[48;2;0;150;0m.\\033[48;2;0;151;0m.",
+ "\\033[48;2;0;152;0m.\\033[48;2;0;153;0m.\\033[48;2;0;154;0m.\\033[48;2;0;155;0m.\\033[48;2;0;156;0m.\\033[48;2;0;157;0m.\\033[48;2;0;158;0m.\\033[48;2;0;159;0m.",
+ "\\033[48;2;0;160;0m.\\033[48;2;0;161;0m.\\033[48;2;0;162;0m.\\033[48;2;0;163;0m.\\033[48;2;0;164;0m.\\033[48;2;0;165;0m.\\033[48;2;0;166;0m.\\033[48;2;0;167;0m.",
+ "\\033[48;2;0;168;0m.\\033[48;2;0;169;0m.\\033[48;2;0;170;0m.\\033[48;2;0;171;0m.\\033[48;2;0;172;0m.\\033[48;2;0;173;0m.\\033[48;2;0;174;0m.\\033[48;2;0;175;0m.",
+ "\\033[48;2;0;176;0m.\\033[48;2;0;177;0m.\\033[48;2;0;178;0m.\\033[48;2;0;179;0m.\\033[48;2;0;180;0m.\\033[48;2;0;181;0m.\\033[48;2;0;182;0m.\\033[48;2;0;183;0m.",
+ "\\033[48;2;0;184;0m.\\033[48;2;0;185;0m.\\033[48;2;0;186;0m.\\033[48;2;0;187;0m.\\033[48;2;0;188;0m.\\033[48;2;0;189;0m.\\033[48;2;0;190;0m.\\033[48;2;0;191;0m.",
+ "\\033[0m\\n ",
+ "\\033[48;2;0;192;0m.\\033[48;2;0;193;0m.\\033[48;2;0;194;0m.\\033[48;2;0;195;0m.\\033[48;2;0;196;0m.\\033[48;2;0;197;0m.\\033[48;2;0;198;0m.\\033[48;2;0;199;0m.",
+ "\\033[48;2;0;200;0m.\\033[48;2;0;201;0m.\\033[48;2;0;202;0m.\\033[48;2;0;203;0m.\\033[48;2;0;204;0m.\\033[48;2;0;205;0m.\\033[48;2;0;206;0m.\\033[48;2;0;207;0m.",
+ "\\033[48;2;0;208;0m.\\033[48;2;0;209;0m.\\033[48;2;0;210;0m.\\033[48;2;0;211;0m.\\033[48;2;0;212;0m.\\033[48;2;0;213;0m.\\033[48;2;0;214;0m.\\033[48;2;0;215;0m.",
+ "\\033[48;2;0;216;0m.\\033[48;2;0;217;0m.\\033[48;2;0;218;0m.\\033[48;2;0;219;0m.\\033[48;2;0;220;0m.\\033[48;2;0;221;0m.\\033[48;2;0;222;0m.\\033[48;2;0;223;0m.",
+ "\\033[48;2;0;224;0m.\\033[48;2;0;225;0m.\\033[48;2;0;226;0m.\\033[48;2;0;227;0m.\\033[48;2;0;228;0m.\\033[48;2;0;229;0m.\\033[48;2;0;230;0m.\\033[48;2;0;231;0m.",
+ "\\033[48;2;0;232;0m.\\033[48;2;0;233;0m.\\033[48;2;0;234;0m.\\033[48;2;0;235;0m.\\033[48;2;0;236;0m.\\033[48;2;0;237;0m.\\033[48;2;0;238;0m.\\033[48;2;0;239;0m.",
+ "\\033[48;2;0;240;0m.\\033[48;2;0;241;0m.\\033[48;2;0;242;0m.\\033[48;2;0;243;0m.\\033[48;2;0;244;0m.\\033[48;2;0;245;0m.\\033[48;2;0;246;0m.\\033[48;2;0;247;0m.",
+ "\\033[48;2;0;248;0m.\\033[48;2;0;249;0m.\\033[48;2;0;250;0m.\\033[48;2;0;251;0m.\\033[48;2;0;252;0m.\\033[48;2;0;253;0m.\\033[48;2;0;254;0m.\\033[48;2;0;255;0m.",
+ "\\033[0m\\n",
+ "\\n",
+ "Blu: ",
+ "\\033[48;2;0;0;0m.\\033[48;2;0;0;1m.\\033[48;2;0;0;2m.\\033[48;2;0;0;3m.\\033[48;2;0;0;4m.\\033[48;2;0;0;5m.\\033[48;2;0;0;6m.\\033[48;2;0;0;7m.",
+ "\\033[48;2;0;0;8m.\\033[48;2;0;0;9m.\\033[48;2;0;0;10m.\\033[48;2;0;0;11m.\\033[48;2;0;0;12m.\\033[48;2;0;0;13m.\\033[48;2;0;0;14m.\\033[48;2;0;0;15m.",
+ "\\033[48;2;0;0;16m.\\033[48;2;0;0;17m.\\033[48;2;0;0;18m.\\033[48;2;0;0;19m.\\033[48;2;0;0;20m.\\033[48;2;0;0;21m.\\033[48;2;0;0;22m.\\033[48;2;0;0;23m.",
+ "\\033[48;2;0;0;24m.\\033[48;2;0;0;25m.\\033[48;2;0;0;26m.\\033[48;2;0;0;27m.\\033[48;2;0;0;28m.\\033[48;2;0;0;29m.\\033[48;2;0;0;30m.\\033[48;2;0;0;31m.",
+ "\\033[48;2;0;0;32m.\\033[48;2;0;0;33m.\\033[48;2;0;0;34m.\\033[48;2;0;0;35m.\\033[48;2;0;0;36m.\\033[48;2;0;0;37m.\\033[48;2;0;0;38m.\\033[48;2;0;0;39m.",
+ "\\033[48;2;0;0;40m.\\033[48;2;0;0;41m.\\033[48;2;0;0;42m.\\033[48;2;0;0;43m.\\033[48;2;0;0;44m.\\033[48;2;0;0;45m.\\033[48;2;0;0;46m.\\033[48;2;0;0;47m.",
+ "\\033[48;2;0;0;48m.\\033[48;2;0;0;49m.\\033[48;2;0;0;50m.\\033[48;2;0;0;51m.\\033[48;2;0;0;52m.\\033[48;2;0;0;53m.\\033[48;2;0;0;54m.\\033[48;2;0;0;55m.",
+ "\\033[48;2;0;0;56m.\\033[48;2;0;0;57m.\\033[48;2;0;0;58m.\\033[48;2;0;0;59m.\\033[48;2;0;0;60m.\\033[48;2;0;0;61m.\\033[48;2;0;0;62m.\\033[48;2;0;0;63m.",
+ "\\033[48;2;0;0;64m.\\033[48;2;0;0;65m.\\033[48;2;0;0;66m.\\033[48;2;0;0;67m.\\033[48;2;0;0;68m.\\033[48;2;0;0;69m.\\033[48;2;0;0;70m.\\033[48;2;0;0;71m.",
+ "\\033[48;2;0;0;72m.\\033[48;2;0;0;73m.\\033[48;2;0;0;74m.\\033[48;2;0;0;75m.\\033[48;2;0;0;76m.\\033[48;2;0;0;77m.\\033[48;2;0;0;78m.\\033[48;2;0;0;79m.",
+ "\\033[48;2;0;0;80m.\\033[48;2;0;0;81m.\\033[48;2;0;0;82m.\\033[48;2;0;0;83m.\\033[48;2;0;0;84m.\\033[48;2;0;0;85m.\\033[48;2;0;0;86m.\\033[48;2;0;0;87m.",
+ "\\033[48;2;0;0;88m.\\033[48;2;0;0;89m.\\033[48;2;0;0;90m.\\033[48;2;0;0;91m.\\033[48;2;0;0;92m.\\033[48;2;0;0;93m.\\033[48;2;0;0;94m.\\033[48;2;0;0;95m.",
+ "\\033[0m\\n ",
+ "\\033[48;2;0;0;96m.\\033[48;2;0;0;97m.\\033[48;2;0;0;98m.\\033[48;2;0;0;99m.\\033[48;2;0;0;100m.\\033[48;2;0;0;101m.\\033[48;2;0;0;102m.\\033[48;2;0;0;103m.",
+ "\\033[48;2;0;0;104m.\\033[48;2;0;0;105m.\\033[48;2;0;0;106m.\\033[48;2;0;0;107m.\\033[48;2;0;0;108m.\\033[48;2;0;0;109m.\\033[48;2;0;0;110m.\\033[48;2;0;0;111m.",
+ "\\033[48;2;0;0;112m.\\033[48;2;0;0;113m.\\033[48;2;0;0;114m.\\033[48;2;0;0;115m.\\033[48;2;0;0;116m.\\033[48;2;0;0;117m.\\033[48;2;0;0;118m.\\033[48;2;0;0;119m.",
+ "\\033[48;2;0;0;120m.\\033[48;2;0;0;121m.\\033[48;2;0;0;122m.\\033[48;2;0;0;123m.\\033[48;2;0;0;124m.\\033[48;2;0;0;125m.\\033[48;2;0;0;126m.\\033[48;2;0;0;127m.",
+ "\\033[48;2;0;0;128m.\\033[48;2;0;0;129m.\\033[48;2;0;0;130m.\\033[48;2;0;0;131m.\\033[48;2;0;0;132m.\\033[48;2;0;0;133m.\\033[48;2;0;0;134m.\\033[48;2;0;0;135m.",
+ "\\033[48;2;0;0;136m.\\033[48;2;0;0;137m.\\033[48;2;0;0;138m.\\033[48;2;0;0;139m.\\033[48;2;0;0;140m.\\033[48;2;0;0;141m.\\033[48;2;0;0;142m.\\033[48;2;0;0;143m.",
+ "\\033[48;2;0;0;144m.\\033[48;2;0;0;145m.\\033[48;2;0;0;146m.\\033[48;2;0;0;147m.\\033[48;2;0;0;148m.\\033[48;2;0;0;149m.\\033[48;2;0;0;150m.\\033[48;2;0;0;151m.",
+ "\\033[48;2;0;0;152m.\\033[48;2;0;0;153m.\\033[48;2;0;0;154m.\\033[48;2;0;0;155m.\\033[48;2;0;0;156m.\\033[48;2;0;0;157m.\\033[48;2;0;0;158m.\\033[48;2;0;0;159m.",
+ "\\033[48;2;0;0;160m.\\033[48;2;0;0;161m.\\033[48;2;0;0;162m.\\033[48;2;0;0;163m.\\033[48;2;0;0;164m.\\033[48;2;0;0;165m.\\033[48;2;0;0;166m.\\033[48;2;0;0;167m.",
+ "\\033[48;2;0;0;168m.\\033[48;2;0;0;169m.\\033[48;2;0;0;170m.\\033[48;2;0;0;171m.\\033[48;2;0;0;172m.\\033[48;2;0;0;173m.\\033[48;2;0;0;174m.\\033[48;2;0;0;175m.",
+ "\\033[48;2;0;0;176m.\\033[48;2;0;0;177m.\\033[48;2;0;0;178m.\\033[48;2;0;0;179m.\\033[48;2;0;0;180m.\\033[48;2;0;0;181m.\\033[48;2;0;0;182m.\\033[48;2;0;0;183m.",
+ "\\033[48;2;0;0;184m.\\033[48;2;0;0;185m.\\033[48;2;0;0;186m.\\033[48;2;0;0;187m.\\033[48;2;0;0;188m.\\033[48;2;0;0;189m.\\033[48;2;0;0;190m.\\033[48;2;0;0;191m.",
+ "\\033[0m\\n ",
+ "\\033[48;2;0;0;192m.\\033[48;2;0;0;193m.\\033[48;2;0;0;194m.\\033[48;2;0;0;195m.\\033[48;2;0;0;196m.\\033[48;2;0;0;197m.\\033[48;2;0;0;198m.\\033[48;2;0;0;199m.",
+ "\\033[48;2;0;0;200m.\\033[48;2;0;0;201m.\\033[48;2;0;0;202m.\\033[48;2;0;0;203m.\\033[48;2;0;0;204m.\\033[48;2;0;0;205m.\\033[48;2;0;0;206m.\\033[48;2;0;0;207m.",
+ "\\033[48;2;0;0;208m.\\033[48;2;0;0;209m.\\033[48;2;0;0;210m.\\033[48;2;0;0;211m.\\033[48;2;0;0;212m.\\033[48;2;0;0;213m.\\033[48;2;0;0;214m.\\033[48;2;0;0;215m.",
+ "\\033[48;2;0;0;216m.\\033[48;2;0;0;217m.\\033[48;2;0;0;218m.\\033[48;2;0;0;219m.\\033[48;2;0;0;220m.\\033[48;2;0;0;221m.\\033[48;2;0;0;222m.\\033[48;2;0;0;223m.",
+ "\\033[48;2;0;0;224m.\\033[48;2;0;0;225m.\\033[48;2;0;0;226m.\\033[48;2;0;0;227m.\\033[48;2;0;0;228m.\\033[48;2;0;0;229m.\\033[48;2;0;0;230m.\\033[48;2;0;0;231m.",
+ "\\033[48;2;0;0;232m.\\033[48;2;0;0;233m.\\033[48;2;0;0;234m.\\033[48;2;0;0;235m.\\033[48;2;0;0;236m.\\033[48;2;0;0;237m.\\033[48;2;0;0;238m.\\033[48;2;0;0;239m.",
+ "\\033[48;2;0;0;240m.\\033[48;2;0;0;241m.\\033[48;2;0;0;242m.\\033[48;2;0;0;243m.\\033[48;2;0;0;244m.\\033[48;2;0;0;245m.\\033[48;2;0;0;246m.\\033[48;2;0;0;247m.",
+ "\\033[48;2;0;0;248m.\\033[48;2;0;0;249m.\\033[48;2;0;0;250m.\\033[48;2;0;0;251m.\\033[48;2;0;0;252m.\\033[48;2;0;0;253m.\\033[48;2;0;0;254m.\\033[48;2;0;0;255m.",
+ "\\033[0m\\n ",
+ "\\n",
+ /**
+ For generating an HSV hue ramp, see:
+ https://en.wikipedia.org/wiki/HSL_and_HSV\#/media/File:HSV-RGB-comparison.svg
+ Basically:
+
+ 255 : RRRRRRR RRRRRRRR
+ : R R
+ : R R
+ : R RED R
+ : R R
+ : R R
+ : R R
+ 0 : RRRRRRRRRRRRRRRR
+ :..|......|......|......|......|......|......|
+ 0 60 120 180 240 300 360
+
+
+
+ 255 : GGGGGGGGGGGGGG
+ : G G
+ : G G
+ : G G
+ : G GREEN G
+ : G G
+ : G G
+ 0 : G GGGGGGGGGGGGGGGG
+ :..|......|......|......|......|......|......|
+ 0 60 120 180 240 300 360
+
+
+ 255 : BBBBBBBBBBBBBBB
+ : B B
+ : B B
+ : B BLUE B
+ : B B
+ : B B
+ : B B
+ 0 : GBBBBBBBBBBBBBB B
+ :..|......|......|......|......|......|......|
+ 0 60 120 180 240 300 360
+ Here's some perl code that generates the Hue ramp:
+ $count = 0;
+ for ($t=0; $t<256;$t+=2) { printf("\\\\033[48;2;%03d;%03d;%03dm ",255,$t,0); if ((++$count % 8) == 0) { printf("\\n"); } }
+ for ($t=0; $t<256;$t+=2) { printf("\\\\033[48;2;%03d;%03d;%03dm ",255-$t,255,0); if ((++$count % 8) == 0) { printf("\\n"); } }
+ for ($t=0; $t<256;$t+=2) { printf("\\\\033[48;2;%03d;%03d;%03dm ",0,255,$t); if ((++$count % 8) == 0) { printf("\\n"); } }
+ for ($t=0; $t<256;$t+=2) { printf("\\\\033[48;2;%03d;%03d;%03dm ",0,255-$t,255); if ((++$count % 8) == 0) { printf("\\n"); } }
+ for ($t=0; $t<256;$t+=2) { printf("\\\\033[48;2;%03d;%03d;%03dm ",$t,0,255); if ((++$count % 8) == 0) { printf("\\n"); } }
+ for ($t=0; $t<256;$t+=2) { printf("\\\\033[48;2;%03d;%03d;%03dm ",255,0,255-$t); if ((++$count % 8) == 0) { printf("\\n"); } }
+**/
+ "Hue: ",
+ "\\033[48;2;255;000;000m.\\033[48;2;255;001;000m.\\033[48;2;255;002;000m.\\033[48;2;255;003;000m.\\033[48;2;255;004;000m.\\033[48;2;255;005;000m.\\033[48;2;255;006;000m.\\033[48;2;255;007;000m.",
+ "\\033[48;2;255;008;000m.\\033[48;2;255;009;000m.\\033[48;2;255;010;000m.\\033[48;2;255;011;000m.\\033[48;2;255;012;000m.\\033[48;2;255;013;000m.\\033[48;2;255;014;000m.\\033[48;2;255;015;000m.",
+ "\\033[48;2;255;016;000m.\\033[48;2;255;017;000m.\\033[48;2;255;018;000m.\\033[48;2;255;019;000m.\\033[48;2;255;020;000m.\\033[48;2;255;021;000m.\\033[48;2;255;022;000m.\\033[48;2;255;023;000m.",
+ "\\033[48;2;255;024;000m.\\033[48;2;255;025;000m.\\033[48;2;255;026;000m.\\033[48;2;255;027;000m.\\033[48;2;255;028;000m.\\033[48;2;255;029;000m.\\033[48;2;255;030;000m.\\033[48;2;255;031;000m.",
+ "\\033[48;2;255;032;000m.\\033[48;2;255;033;000m.\\033[48;2;255;034;000m.\\033[48;2;255;035;000m.\\033[48;2;255;036;000m.\\033[48;2;255;037;000m.\\033[48;2;255;038;000m.\\033[48;2;255;039;000m.",
+ "\\033[48;2;255;040;000m.\\033[48;2;255;041;000m.\\033[48;2;255;042;000m.\\033[48;2;255;043;000m.\\033[48;2;255;044;000m.\\033[48;2;255;045;000m.\\033[48;2;255;046;000m.\\033[48;2;255;047;000m.",
+ "\\033[48;2;255;048;000m.\\033[48;2;255;049;000m.\\033[48;2;255;050;000m.\\033[48;2;255;051;000m.\\033[48;2;255;052;000m.\\033[48;2;255;053;000m.\\033[48;2;255;054;000m.\\033[48;2;255;055;000m.",
+ "\\033[48;2;255;056;000m.\\033[48;2;255;057;000m.\\033[48;2;255;058;000m.\\033[48;2;255;059;000m.\\033[48;2;255;060;000m.\\033[48;2;255;061;000m.\\033[48;2;255;062;000m.\\033[48;2;255;063;000m.",
+ "\\033[48;2;255;064;000m.\\033[48;2;255;065;000m.\\033[48;2;255;066;000m.\\033[48;2;255;067;000m.\\033[48;2;255;068;000m.\\033[48;2;255;069;000m.\\033[48;2;255;070;000m.\\033[48;2;255;071;000m.",
+ "\\033[48;2;255;072;000m.\\033[48;2;255;073;000m.\\033[48;2;255;074;000m.\\033[48;2;255;075;000m.\\033[48;2;255;076;000m.\\033[48;2;255;077;000m.\\033[48;2;255;078;000m.\\033[48;2;255;079;000m.",
+ "\\033[48;2;255;080;000m.\\033[48;2;255;081;000m.\\033[48;2;255;082;000m.\\033[48;2;255;083;000m.\\033[48;2;255;084;000m.\\033[48;2;255;085;000m.\\033[48;2;255;086;000m.\\033[48;2;255;087;000m.",
+ "\\033[48;2;255;088;000m.\\033[48;2;255;089;000m.\\033[48;2;255;090;000m.\\033[48;2;255;091;000m.\\033[48;2;255;092;000m.\\033[48;2;255;093;000m.\\033[48;2;255;094;000m.\\033[48;2;255;095;000m.",
+ "\\033[0m\\n ",
+ "\\033[48;2;255;096;000m.\\033[48;2;255;097;000m.\\033[48;2;255;098;000m.\\033[48;2;255;099;000m.\\033[48;2;255;100;000m.\\033[48;2;255;101;000m.\\033[48;2;255;102;000m.\\033[48;2;255;103;000m.",
+ "\\033[48;2;255;104;000m.\\033[48;2;255;105;000m.\\033[48;2;255;106;000m.\\033[48;2;255;107;000m.\\033[48;2;255;108;000m.\\033[48;2;255;109;000m.\\033[48;2;255;110;000m.\\033[48;2;255;111;000m.",
+ "\\033[48;2;255;112;000m.\\033[48;2;255;113;000m.\\033[48;2;255;114;000m.\\033[48;2;255;115;000m.\\033[48;2;255;116;000m.\\033[48;2;255;117;000m.\\033[48;2;255;118;000m.\\033[48;2;255;119;000m.",
+ "\\033[48;2;255;120;000m.\\033[48;2;255;121;000m.\\033[48;2;255;122;000m.\\033[48;2;255;123;000m.\\033[48;2;255;124;000m.\\033[48;2;255;125;000m.\\033[48;2;255;126;000m.\\033[48;2;255;127;000m.",
+ "\\033[48;2;255;128;000m.\\033[48;2;255;129;000m.\\033[48;2;255;130;000m.\\033[48;2;255;131;000m.\\033[48;2;255;132;000m.\\033[48;2;255;133;000m.\\033[48;2;255;134;000m.\\033[48;2;255;135;000m.",
+ "\\033[48;2;255;136;000m.\\033[48;2;255;137;000m.\\033[48;2;255;138;000m.\\033[48;2;255;139;000m.\\033[48;2;255;140;000m.\\033[48;2;255;141;000m.\\033[48;2;255;142;000m.\\033[48;2;255;143;000m.",
+ "\\033[48;2;255;144;000m.\\033[48;2;255;145;000m.\\033[48;2;255;146;000m.\\033[48;2;255;147;000m.\\033[48;2;255;148;000m.\\033[48;2;255;149;000m.\\033[48;2;255;150;000m.\\033[48;2;255;151;000m.",
+ "\\033[48;2;255;152;000m.\\033[48;2;255;153;000m.\\033[48;2;255;154;000m.\\033[48;2;255;155;000m.\\033[48;2;255;156;000m.\\033[48;2;255;157;000m.\\033[48;2;255;158;000m.\\033[48;2;255;159;000m.",
+ "\\033[48;2;255;160;000m.\\033[48;2;255;161;000m.\\033[48;2;255;162;000m.\\033[48;2;255;163;000m.\\033[48;2;255;164;000m.\\033[48;2;255;165;000m.\\033[48;2;255;166;000m.\\033[48;2;255;167;000m.",
+ "\\033[48;2;255;168;000m.\\033[48;2;255;169;000m.\\033[48;2;255;170;000m.\\033[48;2;255;171;000m.\\033[48;2;255;172;000m.\\033[48;2;255;173;000m.\\033[48;2;255;174;000m.\\033[48;2;255;175;000m.",
+ "\\033[48;2;255;176;000m.\\033[48;2;255;177;000m.\\033[48;2;255;178;000m.\\033[48;2;255;179;000m.\\033[48;2;255;180;000m.\\033[48;2;255;181;000m.\\033[48;2;255;182;000m.\\033[48;2;255;183;000m.",
+ "\\033[48;2;255;184;000m.\\033[48;2;255;185;000m.\\033[48;2;255;186;000m.\\033[48;2;255;187;000m.\\033[48;2;255;188;000m.\\033[48;2;255;189;000m.\\033[48;2;255;190;000m.\\033[48;2;255;191;000m.",
+ "\\033[0m\\n ",
+ "\\033[48;2;255;192;000m.\\033[48;2;255;193;000m.\\033[48;2;255;194;000m.\\033[48;2;255;195;000m.\\033[48;2;255;196;000m.\\033[48;2;255;197;000m.\\033[48;2;255;198;000m.\\033[48;2;255;199;000m.",
+ "\\033[48;2;255;200;000m.\\033[48;2;255;201;000m.\\033[48;2;255;202;000m.\\033[48;2;255;203;000m.\\033[48;2;255;204;000m.\\033[48;2;255;205;000m.\\033[48;2;255;206;000m.\\033[48;2;255;207;000m.",
+ "\\033[48;2;255;208;000m.\\033[48;2;255;209;000m.\\033[48;2;255;210;000m.\\033[48;2;255;211;000m.\\033[48;2;255;212;000m.\\033[48;2;255;213;000m.\\033[48;2;255;214;000m.\\033[48;2;255;215;000m.",
+ "\\033[48;2;255;216;000m.\\033[48;2;255;217;000m.\\033[48;2;255;218;000m.\\033[48;2;255;219;000m.\\033[48;2;255;220;000m.\\033[48;2;255;221;000m.\\033[48;2;255;222;000m.\\033[48;2;255;223;000m.",
+ "\\033[48;2;255;224;000m.\\033[48;2;255;225;000m.\\033[48;2;255;226;000m.\\033[48;2;255;227;000m.\\033[48;2;255;228;000m.\\033[48;2;255;229;000m.\\033[48;2;255;230;000m.\\033[48;2;255;231;000m.",
+ "\\033[48;2;255;232;000m.\\033[48;2;255;233;000m.\\033[48;2;255;234;000m.\\033[48;2;255;235;000m.\\033[48;2;255;236;000m.\\033[48;2;255;237;000m.\\033[48;2;255;238;000m.\\033[48;2;255;239;000m.",
+ "\\033[48;2;255;240;000m.\\033[48;2;255;241;000m.\\033[48;2;255;242;000m.\\033[48;2;255;243;000m.\\033[48;2;255;244;000m.\\033[48;2;255;245;000m.\\033[48;2;255;246;000m.\\033[48;2;255;247;000m.",
+ "\\033[48;2;255;248;000m.\\033[48;2;255;249;000m.\\033[48;2;255;250;000m.\\033[48;2;255;251;000m.\\033[48;2;255;252;000m.\\033[48;2;255;253;000m.\\033[48;2;255;254;000m.\\033[48;2;255;255;000m.",
+ "\\033[48;2;255;255;000m.\\033[48;2;254;255;000m.\\033[48;2;253;255;000m.\\033[48;2;252;255;000m.\\033[48;2;251;255;000m.\\033[48;2;250;255;000m.\\033[48;2;249;255;000m.\\033[48;2;248;255;000m.",
+ "\\033[48;2;247;255;000m.\\033[48;2;246;255;000m.\\033[48;2;245;255;000m.\\033[48;2;244;255;000m.\\033[48;2;243;255;000m.\\033[48;2;242;255;000m.\\033[48;2;241;255;000m.\\033[48;2;240;255;000m.",
+ "\\033[48;2;239;255;000m.\\033[48;2;238;255;000m.\\033[48;2;237;255;000m.\\033[48;2;236;255;000m.\\033[48;2;235;255;000m.\\033[48;2;234;255;000m.\\033[48;2;233;255;000m.\\033[48;2;232;255;000m.",
+ "\\033[48;2;231;255;000m.\\033[48;2;230;255;000m.\\033[48;2;229;255;000m.\\033[48;2;228;255;000m.\\033[48;2;227;255;000m.\\033[48;2;226;255;000m.\\033[48;2;225;255;000m.\\033[48;2;224;255;000m.",
+ "\\033[0m\\n ",
+ "\\033[48;2;223;255;000m.\\033[48;2;222;255;000m.\\033[48;2;221;255;000m.\\033[48;2;220;255;000m.\\033[48;2;219;255;000m.\\033[48;2;218;255;000m.\\033[48;2;217;255;000m.\\033[48;2;216;255;000m.",
+ "\\033[48;2;215;255;000m.\\033[48;2;214;255;000m.\\033[48;2;213;255;000m.\\033[48;2;212;255;000m.\\033[48;2;211;255;000m.\\033[48;2;210;255;000m.\\033[48;2;209;255;000m.\\033[48;2;208;255;000m.",
+ "\\033[48;2;207;255;000m.\\033[48;2;206;255;000m.\\033[48;2;205;255;000m.\\033[48;2;204;255;000m.\\033[48;2;203;255;000m.\\033[48;2;202;255;000m.\\033[48;2;201;255;000m.\\033[48;2;200;255;000m.",
+ "\\033[48;2;199;255;000m.\\033[48;2;198;255;000m.\\033[48;2;197;255;000m.\\033[48;2;196;255;000m.\\033[48;2;195;255;000m.\\033[48;2;194;255;000m.\\033[48;2;193;255;000m.\\033[48;2;192;255;000m.",
+ "\\033[48;2;191;255;000m.\\033[48;2;190;255;000m.\\033[48;2;189;255;000m.\\033[48;2;188;255;000m.\\033[48;2;187;255;000m.\\033[48;2;186;255;000m.\\033[48;2;185;255;000m.\\033[48;2;184;255;000m.",
+ "\\033[48;2;183;255;000m.\\033[48;2;182;255;000m.\\033[48;2;181;255;000m.\\033[48;2;180;255;000m.\\033[48;2;179;255;000m.\\033[48;2;178;255;000m.\\033[48;2;177;255;000m.\\033[48;2;176;255;000m.",
+ "\\033[48;2;175;255;000m.\\033[48;2;174;255;000m.\\033[48;2;173;255;000m.\\033[48;2;172;255;000m.\\033[48;2;171;255;000m.\\033[48;2;170;255;000m.\\033[48;2;169;255;000m.\\033[48;2;168;255;000m.",
+ "\\033[48;2;167;255;000m.\\033[48;2;166;255;000m.\\033[48;2;165;255;000m.\\033[48;2;164;255;000m.\\033[48;2;163;255;000m.\\033[48;2;162;255;000m.\\033[48;2;161;255;000m.\\033[48;2;160;255;000m.",
+ "\\033[48;2;159;255;000m.\\033[48;2;158;255;000m.\\033[48;2;157;255;000m.\\033[48;2;156;255;000m.\\033[48;2;155;255;000m.\\033[48;2;154;255;000m.\\033[48;2;153;255;000m.\\033[48;2;152;255;000m.",
+ "\\033[48;2;151;255;000m.\\033[48;2;150;255;000m.\\033[48;2;149;255;000m.\\033[48;2;148;255;000m.\\033[48;2;147;255;000m.\\033[48;2;146;255;000m.\\033[48;2;145;255;000m.\\033[48;2;144;255;000m.",
+ "\\033[48;2;143;255;000m.\\033[48;2;142;255;000m.\\033[48;2;141;255;000m.\\033[48;2;140;255;000m.\\033[48;2;139;255;000m.\\033[48;2;138;255;000m.\\033[48;2;137;255;000m.\\033[48;2;136;255;000m.",
+ "\\033[48;2;135;255;000m.\\033[48;2;134;255;000m.\\033[48;2;133;255;000m.\\033[48;2;132;255;000m.\\033[48;2;131;255;000m.\\033[48;2;130;255;000m.\\033[48;2;129;255;000m.\\033[48;2;128;255;000m.",
+ "\\033[0m\\n ",
+ "\\033[48;2;127;255;000m.\\033[48;2;126;255;000m.\\033[48;2;125;255;000m.\\033[48;2;124;255;000m.\\033[48;2;123;255;000m.\\033[48;2;122;255;000m.\\033[48;2;121;255;000m.\\033[48;2;120;255;000m.",
+ "\\033[48;2;119;255;000m.\\033[48;2;118;255;000m.\\033[48;2;117;255;000m.\\033[48;2;116;255;000m.\\033[48;2;115;255;000m.\\033[48;2;114;255;000m.\\033[48;2;113;255;000m.\\033[48;2;112;255;000m.",
+ "\\033[48;2;111;255;000m.\\033[48;2;110;255;000m.\\033[48;2;109;255;000m.\\033[48;2;108;255;000m.\\033[48;2;107;255;000m.\\033[48;2;106;255;000m.\\033[48;2;105;255;000m.\\033[48;2;104;255;000m.",
+ "\\033[48;2;103;255;000m.\\033[48;2;102;255;000m.\\033[48;2;101;255;000m.\\033[48;2;100;255;000m.\\033[48;2;099;255;000m.\\033[48;2;098;255;000m.\\033[48;2;097;255;000m.\\033[48;2;096;255;000m.",
+ "\\033[48;2;095;255;000m.\\033[48;2;094;255;000m.\\033[48;2;093;255;000m.\\033[48;2;092;255;000m.\\033[48;2;091;255;000m.\\033[48;2;090;255;000m.\\033[48;2;089;255;000m.\\033[48;2;088;255;000m.",
+ "\\033[48;2;087;255;000m.\\033[48;2;086;255;000m.\\033[48;2;085;255;000m.\\033[48;2;084;255;000m.\\033[48;2;083;255;000m.\\033[48;2;082;255;000m.\\033[48;2;081;255;000m.\\033[48;2;080;255;000m.",
+ "\\033[48;2;079;255;000m.\\033[48;2;078;255;000m.\\033[48;2;077;255;000m.\\033[48;2;076;255;000m.\\033[48;2;075;255;000m.\\033[48;2;074;255;000m.\\033[48;2;073;255;000m.\\033[48;2;072;255;000m.",
+ "\\033[48;2;071;255;000m.\\033[48;2;070;255;000m.\\033[48;2;069;255;000m.\\033[48;2;068;255;000m.\\033[48;2;067;255;000m.\\033[48;2;066;255;000m.\\033[48;2;065;255;000m.\\033[48;2;064;255;000m.",
+ "\\033[48;2;063;255;000m.\\033[48;2;062;255;000m.\\033[48;2;061;255;000m.\\033[48;2;060;255;000m.\\033[48;2;059;255;000m.\\033[48;2;058;255;000m.\\033[48;2;057;255;000m.\\033[48;2;056;255;000m.",
+ "\\033[48;2;055;255;000m.\\033[48;2;054;255;000m.\\033[48;2;053;255;000m.\\033[48;2;052;255;000m.\\033[48;2;051;255;000m.\\033[48;2;050;255;000m.\\033[48;2;049;255;000m.\\033[48;2;048;255;000m.",
+ "\\033[48;2;047;255;000m.\\033[48;2;046;255;000m.\\033[48;2;045;255;000m.\\033[48;2;044;255;000m.\\033[48;2;043;255;000m.\\033[48;2;042;255;000m.\\033[48;2;041;255;000m.\\033[48;2;040;255;000m.",
+ "\\033[48;2;039;255;000m.\\033[48;2;038;255;000m.\\033[48;2;037;255;000m.\\033[48;2;036;255;000m.\\033[48;2;035;255;000m.\\033[48;2;034;255;000m.\\033[48;2;033;255;000m.\\033[48;2;032;255;000m.",
+ "\\033[0m\\n ",
+ "\\033[48;2;031;255;000m.\\033[48;2;030;255;000m.\\033[48;2;029;255;000m.\\033[48;2;028;255;000m.\\033[48;2;027;255;000m.\\033[48;2;026;255;000m.\\033[48;2;025;255;000m.\\033[48;2;024;255;000m.",
+ "\\033[48;2;023;255;000m.\\033[48;2;022;255;000m.\\033[48;2;021;255;000m.\\033[48;2;020;255;000m.\\033[48;2;019;255;000m.\\033[48;2;018;255;000m.\\033[48;2;017;255;000m.\\033[48;2;016;255;000m.",
+ "\\033[48;2;015;255;000m.\\033[48;2;014;255;000m.\\033[48;2;013;255;000m.\\033[48;2;012;255;000m.\\033[48;2;011;255;000m.\\033[48;2;010;255;000m.\\033[48;2;009;255;000m.\\033[48;2;008;255;000m.",
+ "\\033[48;2;007;255;000m.\\033[48;2;006;255;000m.\\033[48;2;005;255;000m.\\033[48;2;004;255;000m.\\033[48;2;003;255;000m.\\033[48;2;002;255;000m.\\033[48;2;001;255;000m.\\033[48;2;000;255;000m.",
+ "\\033[48;2;000;255;000m.\\033[48;2;000;255;001m.\\033[48;2;000;255;002m.\\033[48;2;000;255;003m.\\033[48;2;000;255;004m.\\033[48;2;000;255;005m.\\033[48;2;000;255;006m.\\033[48;2;000;255;007m.",
+ "\\033[48;2;000;255;008m.\\033[48;2;000;255;009m.\\033[48;2;000;255;010m.\\033[48;2;000;255;011m.\\033[48;2;000;255;012m.\\033[48;2;000;255;013m.\\033[48;2;000;255;014m.\\033[48;2;000;255;015m.",
+ "\\033[48;2;000;255;016m.\\033[48;2;000;255;017m.\\033[48;2;000;255;018m.\\033[48;2;000;255;019m.\\033[48;2;000;255;020m.\\033[48;2;000;255;021m.\\033[48;2;000;255;022m.\\033[48;2;000;255;023m.",
+ "\\033[48;2;000;255;024m.\\033[48;2;000;255;025m.\\033[48;2;000;255;026m.\\033[48;2;000;255;027m.\\033[48;2;000;255;028m.\\033[48;2;000;255;029m.\\033[48;2;000;255;030m.\\033[48;2;000;255;031m.",
+ "\\033[48;2;000;255;032m.\\033[48;2;000;255;033m.\\033[48;2;000;255;034m.\\033[48;2;000;255;035m.\\033[48;2;000;255;036m.\\033[48;2;000;255;037m.\\033[48;2;000;255;038m.\\033[48;2;000;255;039m.",
+ "\\033[48;2;000;255;040m.\\033[48;2;000;255;041m.\\033[48;2;000;255;042m.\\033[48;2;000;255;043m.\\033[48;2;000;255;044m.\\033[48;2;000;255;045m.\\033[48;2;000;255;046m.\\033[48;2;000;255;047m.",
+ "\\033[48;2;000;255;048m.\\033[48;2;000;255;049m.\\033[48;2;000;255;050m.\\033[48;2;000;255;051m.\\033[48;2;000;255;052m.\\033[48;2;000;255;053m.\\033[48;2;000;255;054m.\\033[48;2;000;255;055m.",
+ "\\033[48;2;000;255;056m.\\033[48;2;000;255;057m.\\033[48;2;000;255;058m.\\033[48;2;000;255;059m.\\033[48;2;000;255;060m.\\033[48;2;000;255;061m.\\033[48;2;000;255;062m.\\033[48;2;000;255;063m.",
+ "\\033[0m\\n ",
+ "\\033[48;2;000;255;064m.\\033[48;2;000;255;065m.\\033[48;2;000;255;066m.\\033[48;2;000;255;067m.\\033[48;2;000;255;068m.\\033[48;2;000;255;069m.\\033[48;2;000;255;070m.\\033[48;2;000;255;071m.",
+ "\\033[48;2;000;255;072m.\\033[48;2;000;255;073m.\\033[48;2;000;255;074m.\\033[48;2;000;255;075m.\\033[48;2;000;255;076m.\\033[48;2;000;255;077m.\\033[48;2;000;255;078m.\\033[48;2;000;255;079m.",
+ "\\033[48;2;000;255;080m.\\033[48;2;000;255;081m.\\033[48;2;000;255;082m.\\033[48;2;000;255;083m.\\033[48;2;000;255;084m.\\033[48;2;000;255;085m.\\033[48;2;000;255;086m.\\033[48;2;000;255;087m.",
+ "\\033[48;2;000;255;088m.\\033[48;2;000;255;089m.\\033[48;2;000;255;090m.\\033[48;2;000;255;091m.\\033[48;2;000;255;092m.\\033[48;2;000;255;093m.\\033[48;2;000;255;094m.\\033[48;2;000;255;095m.",
+ "\\033[48;2;000;255;096m.\\033[48;2;000;255;097m.\\033[48;2;000;255;098m.\\033[48;2;000;255;099m.\\033[48;2;000;255;100m.\\033[48;2;000;255;101m.\\033[48;2;000;255;102m.\\033[48;2;000;255;103m.",
+ "\\033[48;2;000;255;104m.\\033[48;2;000;255;105m.\\033[48;2;000;255;106m.\\033[48;2;000;255;107m.\\033[48;2;000;255;108m.\\033[48;2;000;255;109m.\\033[48;2;000;255;110m.\\033[48;2;000;255;111m.",
+ "\\033[48;2;000;255;112m.\\033[48;2;000;255;113m.\\033[48;2;000;255;114m.\\033[48;2;000;255;115m.\\033[48;2;000;255;116m.\\033[48;2;000;255;117m.\\033[48;2;000;255;118m.\\033[48;2;000;255;119m.",
+ "\\033[48;2;000;255;120m.\\033[48;2;000;255;121m.\\033[48;2;000;255;122m.\\033[48;2;000;255;123m.\\033[48;2;000;255;124m.\\033[48;2;000;255;125m.\\033[48;2;000;255;126m.\\033[48;2;000;255;127m.",
+ "\\033[48;2;000;255;128m.\\033[48;2;000;255;129m.\\033[48;2;000;255;130m.\\033[48;2;000;255;131m.\\033[48;2;000;255;132m.\\033[48;2;000;255;133m.\\033[48;2;000;255;134m.\\033[48;2;000;255;135m.",
+ "\\033[48;2;000;255;136m.\\033[48;2;000;255;137m.\\033[48;2;000;255;138m.\\033[48;2;000;255;139m.\\033[48;2;000;255;140m.\\033[48;2;000;255;141m.\\033[48;2;000;255;142m.\\033[48;2;000;255;143m.",
+ "\\033[48;2;000;255;144m.\\033[48;2;000;255;145m.\\033[48;2;000;255;146m.\\033[48;2;000;255;147m.\\033[48;2;000;255;148m.\\033[48;2;000;255;149m.\\033[48;2;000;255;150m.\\033[48;2;000;255;151m.",
+ "\\033[48;2;000;255;152m.\\033[48;2;000;255;153m.\\033[48;2;000;255;154m.\\033[48;2;000;255;155m.\\033[48;2;000;255;156m.\\033[48;2;000;255;157m.\\033[48;2;000;255;158m.\\033[48;2;000;255;159m.",
+ "\\033[0m\\n ",
+ "\\033[48;2;000;255;160m.\\033[48;2;000;255;161m.\\033[48;2;000;255;162m.\\033[48;2;000;255;163m.\\033[48;2;000;255;164m.\\033[48;2;000;255;165m.\\033[48;2;000;255;166m.\\033[48;2;000;255;167m.",
+ "\\033[48;2;000;255;168m.\\033[48;2;000;255;169m.\\033[48;2;000;255;170m.\\033[48;2;000;255;171m.\\033[48;2;000;255;172m.\\033[48;2;000;255;173m.\\033[48;2;000;255;174m.\\033[48;2;000;255;175m.",
+ "\\033[48;2;000;255;176m.\\033[48;2;000;255;177m.\\033[48;2;000;255;178m.\\033[48;2;000;255;179m.\\033[48;2;000;255;180m.\\033[48;2;000;255;181m.\\033[48;2;000;255;182m.\\033[48;2;000;255;183m.",
+ "\\033[48;2;000;255;184m.\\033[48;2;000;255;185m.\\033[48;2;000;255;186m.\\033[48;2;000;255;187m.\\033[48;2;000;255;188m.\\033[48;2;000;255;189m.\\033[48;2;000;255;190m.\\033[48;2;000;255;191m.",
+ "\\033[48;2;000;255;192m.\\033[48;2;000;255;193m.\\033[48;2;000;255;194m.\\033[48;2;000;255;195m.\\033[48;2;000;255;196m.\\033[48;2;000;255;197m.\\033[48;2;000;255;198m.\\033[48;2;000;255;199m.",
+ "\\033[48;2;000;255;200m.\\033[48;2;000;255;201m.\\033[48;2;000;255;202m.\\033[48;2;000;255;203m.\\033[48;2;000;255;204m.\\033[48;2;000;255;205m.\\033[48;2;000;255;206m.\\033[48;2;000;255;207m.",
+ "\\033[48;2;000;255;208m.\\033[48;2;000;255;209m.\\033[48;2;000;255;210m.\\033[48;2;000;255;211m.\\033[48;2;000;255;212m.\\033[48;2;000;255;213m.\\033[48;2;000;255;214m.\\033[48;2;000;255;215m.",
+ "\\033[48;2;000;255;216m.\\033[48;2;000;255;217m.\\033[48;2;000;255;218m.\\033[48;2;000;255;219m.\\033[48;2;000;255;220m.\\033[48;2;000;255;221m.\\033[48;2;000;255;222m.\\033[48;2;000;255;223m.",
+ "\\033[48;2;000;255;224m.\\033[48;2;000;255;225m.\\033[48;2;000;255;226m.\\033[48;2;000;255;227m.\\033[48;2;000;255;228m.\\033[48;2;000;255;229m.\\033[48;2;000;255;230m.\\033[48;2;000;255;231m.",
+ "\\033[48;2;000;255;232m.\\033[48;2;000;255;233m.\\033[48;2;000;255;234m.\\033[48;2;000;255;235m.\\033[48;2;000;255;236m.\\033[48;2;000;255;237m.\\033[48;2;000;255;238m.\\033[48;2;000;255;239m.",
+ "\\033[48;2;000;255;240m.\\033[48;2;000;255;241m.\\033[48;2;000;255;242m.\\033[48;2;000;255;243m.\\033[48;2;000;255;244m.\\033[48;2;000;255;245m.\\033[48;2;000;255;246m.\\033[48;2;000;255;247m.",
+ "\\033[48;2;000;255;248m.\\033[48;2;000;255;249m.\\033[48;2;000;255;250m.\\033[48;2;000;255;251m.\\033[48;2;000;255;252m.\\033[48;2;000;255;253m.\\033[48;2;000;255;254m.\\033[48;2;000;255;255m.",
+ "\\033[0m\\n ",
+ "\\033[48;2;000;255;255m.\\033[48;2;000;254;255m.\\033[48;2;000;253;255m.\\033[48;2;000;252;255m.\\033[48;2;000;251;255m.\\033[48;2;000;250;255m.\\033[48;2;000;249;255m.\\033[48;2;000;248;255m.",
+ "\\033[48;2;000;247;255m.\\033[48;2;000;246;255m.\\033[48;2;000;245;255m.\\033[48;2;000;244;255m.\\033[48;2;000;243;255m.\\033[48;2;000;242;255m.\\033[48;2;000;241;255m.\\033[48;2;000;240;255m.",
+ "\\033[48;2;000;239;255m.\\033[48;2;000;238;255m.\\033[48;2;000;237;255m.\\033[48;2;000;236;255m.\\033[48;2;000;235;255m.\\033[48;2;000;234;255m.\\033[48;2;000;233;255m.\\033[48;2;000;232;255m.",
+ "\\033[48;2;000;231;255m.\\033[48;2;000;230;255m.\\033[48;2;000;229;255m.\\033[48;2;000;228;255m.\\033[48;2;000;227;255m.\\033[48;2;000;226;255m.\\033[48;2;000;225;255m.\\033[48;2;000;224;255m.",
+ "\\033[48;2;000;223;255m.\\033[48;2;000;222;255m.\\033[48;2;000;221;255m.\\033[48;2;000;220;255m.\\033[48;2;000;219;255m.\\033[48;2;000;218;255m.\\033[48;2;000;217;255m.\\033[48;2;000;216;255m.",
+ "\\033[48;2;000;215;255m.\\033[48;2;000;214;255m.\\033[48;2;000;213;255m.\\033[48;2;000;212;255m.\\033[48;2;000;211;255m.\\033[48;2;000;210;255m.\\033[48;2;000;209;255m.\\033[48;2;000;208;255m.",
+ "\\033[48;2;000;207;255m.\\033[48;2;000;206;255m.\\033[48;2;000;205;255m.\\033[48;2;000;204;255m.\\033[48;2;000;203;255m.\\033[48;2;000;202;255m.\\033[48;2;000;201;255m.\\033[48;2;000;200;255m.",
+ "\\033[48;2;000;199;255m.\\033[48;2;000;198;255m.\\033[48;2;000;197;255m.\\033[48;2;000;196;255m.\\033[48;2;000;195;255m.\\033[48;2;000;194;255m.\\033[48;2;000;193;255m.\\033[48;2;000;192;255m.",
+ "\\033[48;2;000;191;255m.\\033[48;2;000;190;255m.\\033[48;2;000;189;255m.\\033[48;2;000;188;255m.\\033[48;2;000;187;255m.\\033[48;2;000;186;255m.\\033[48;2;000;185;255m.\\033[48;2;000;184;255m.",
+ "\\033[48;2;000;183;255m.\\033[48;2;000;182;255m.\\033[48;2;000;181;255m.\\033[48;2;000;180;255m.\\033[48;2;000;179;255m.\\033[48;2;000;178;255m.\\033[48;2;000;177;255m.\\033[48;2;000;176;255m.",
+ "\\033[48;2;000;175;255m.\\033[48;2;000;174;255m.\\033[48;2;000;173;255m.\\033[48;2;000;172;255m.\\033[48;2;000;171;255m.\\033[48;2;000;170;255m.\\033[48;2;000;169;255m.\\033[48;2;000;168;255m.",
+ "\\033[48;2;000;167;255m.\\033[48;2;000;166;255m.\\033[48;2;000;165;255m.\\033[48;2;000;164;255m.\\033[48;2;000;163;255m.\\033[48;2;000;162;255m.\\033[48;2;000;161;255m.\\033[48;2;000;160;255m.",
+ "\\033[0m\\n ",
+ "\\033[48;2;000;159;255m.\\033[48;2;000;158;255m.\\033[48;2;000;157;255m.\\033[48;2;000;156;255m.\\033[48;2;000;155;255m.\\033[48;2;000;154;255m.\\033[48;2;000;153;255m.\\033[48;2;000;152;255m.",
+ "\\033[48;2;000;151;255m.\\033[48;2;000;150;255m.\\033[48;2;000;149;255m.\\033[48;2;000;148;255m.\\033[48;2;000;147;255m.\\033[48;2;000;146;255m.\\033[48;2;000;145;255m.\\033[48;2;000;144;255m.",
+ "\\033[48;2;000;143;255m.\\033[48;2;000;142;255m.\\033[48;2;000;141;255m.\\033[48;2;000;140;255m.\\033[48;2;000;139;255m.\\033[48;2;000;138;255m.\\033[48;2;000;137;255m.\\033[48;2;000;136;255m.",
+ "\\033[48;2;000;135;255m.\\033[48;2;000;134;255m.\\033[48;2;000;133;255m.\\033[48;2;000;132;255m.\\033[48;2;000;131;255m.\\033[48;2;000;130;255m.\\033[48;2;000;129;255m.\\033[48;2;000;128;255m.",
+ "\\033[48;2;000;127;255m.\\033[48;2;000;126;255m.\\033[48;2;000;125;255m.\\033[48;2;000;124;255m.\\033[48;2;000;123;255m.\\033[48;2;000;122;255m.\\033[48;2;000;121;255m.\\033[48;2;000;120;255m.",
+ "\\033[48;2;000;119;255m.\\033[48;2;000;118;255m.\\033[48;2;000;117;255m.\\033[48;2;000;116;255m.\\033[48;2;000;115;255m.\\033[48;2;000;114;255m.\\033[48;2;000;113;255m.\\033[48;2;000;112;255m.",
+ "\\033[48;2;000;111;255m.\\033[48;2;000;110;255m.\\033[48;2;000;109;255m.\\033[48;2;000;108;255m.\\033[48;2;000;107;255m.\\033[48;2;000;106;255m.\\033[48;2;000;105;255m.\\033[48;2;000;104;255m.",
+ "\\033[48;2;000;103;255m.\\033[48;2;000;102;255m.\\033[48;2;000;101;255m.\\033[48;2;000;100;255m.\\033[48;2;000;099;255m.\\033[48;2;000;098;255m.\\033[48;2;000;097;255m.\\033[48;2;000;096;255m.",
+ "\\033[48;2;000;095;255m.\\033[48;2;000;094;255m.\\033[48;2;000;093;255m.\\033[48;2;000;092;255m.\\033[48;2;000;091;255m.\\033[48;2;000;090;255m.\\033[48;2;000;089;255m.\\033[48;2;000;088;255m.",
+ "\\033[48;2;000;087;255m.\\033[48;2;000;086;255m.\\033[48;2;000;085;255m.\\033[48;2;000;084;255m.\\033[48;2;000;083;255m.\\033[48;2;000;082;255m.\\033[48;2;000;081;255m.\\033[48;2;000;080;255m.",
+ "\\033[48;2;000;079;255m.\\033[48;2;000;078;255m.\\033[48;2;000;077;255m.\\033[48;2;000;076;255m.\\033[48;2;000;075;255m.\\033[48;2;000;074;255m.\\033[48;2;000;073;255m.\\033[48;2;000;072;255m.",
+ "\\033[48;2;000;071;255m.\\033[48;2;000;070;255m.\\033[48;2;000;069;255m.\\033[48;2;000;068;255m.\\033[48;2;000;067;255m.\\033[48;2;000;066;255m.\\033[48;2;000;065;255m.\\033[48;2;000;064;255m.",
+ "\\033[0m\\n ",
+ "\\033[48;2;000;063;255m.\\033[48;2;000;062;255m.\\033[48;2;000;061;255m.\\033[48;2;000;060;255m.\\033[48;2;000;059;255m.\\033[48;2;000;058;255m.\\033[48;2;000;057;255m.\\033[48;2;000;056;255m.",
+ "\\033[48;2;000;055;255m.\\033[48;2;000;054;255m.\\033[48;2;000;053;255m.\\033[48;2;000;052;255m.\\033[48;2;000;051;255m.\\033[48;2;000;050;255m.\\033[48;2;000;049;255m.\\033[48;2;000;048;255m.",
+ "\\033[48;2;000;047;255m.\\033[48;2;000;046;255m.\\033[48;2;000;045;255m.\\033[48;2;000;044;255m.\\033[48;2;000;043;255m.\\033[48;2;000;042;255m.\\033[48;2;000;041;255m.\\033[48;2;000;040;255m.",
+ "\\033[48;2;000;039;255m.\\033[48;2;000;038;255m.\\033[48;2;000;037;255m.\\033[48;2;000;036;255m.\\033[48;2;000;035;255m.\\033[48;2;000;034;255m.\\033[48;2;000;033;255m.\\033[48;2;000;032;255m.",
+ "\\033[48;2;000;031;255m.\\033[48;2;000;030;255m.\\033[48;2;000;029;255m.\\033[48;2;000;028;255m.\\033[48;2;000;027;255m.\\033[48;2;000;026;255m.\\033[48;2;000;025;255m.\\033[48;2;000;024;255m.",
+ "\\033[48;2;000;023;255m.\\033[48;2;000;022;255m.\\033[48;2;000;021;255m.\\033[48;2;000;020;255m.\\033[48;2;000;019;255m.\\033[48;2;000;018;255m.\\033[48;2;000;017;255m.\\033[48;2;000;016;255m.",
+ "\\033[48;2;000;015;255m.\\033[48;2;000;014;255m.\\033[48;2;000;013;255m.\\033[48;2;000;012;255m.\\033[48;2;000;011;255m.\\033[48;2;000;010;255m.\\033[48;2;000;009;255m.\\033[48;2;000;008;255m.",
+ "\\033[48;2;000;007;255m.\\033[48;2;000;006;255m.\\033[48;2;000;005;255m.\\033[48;2;000;004;255m.\\033[48;2;000;003;255m.\\033[48;2;000;002;255m.\\033[48;2;000;001;255m.\\033[48;2;000;000;255m.",
+ "\\033[48;2;000;000;255m.\\033[48;2;001;000;255m.\\033[48;2;002;000;255m.\\033[48;2;003;000;255m.\\033[48;2;004;000;255m.\\033[48;2;005;000;255m.\\033[48;2;006;000;255m.\\033[48;2;007;000;255m.",
+ "\\033[48;2;008;000;255m.\\033[48;2;009;000;255m.\\033[48;2;010;000;255m.\\033[48;2;011;000;255m.\\033[48;2;012;000;255m.\\033[48;2;013;000;255m.\\033[48;2;014;000;255m.\\033[48;2;015;000;255m.",
+ "\\033[48;2;016;000;255m.\\033[48;2;017;000;255m.\\033[48;2;018;000;255m.\\033[48;2;019;000;255m.\\033[48;2;020;000;255m.\\033[48;2;021;000;255m.\\033[48;2;022;000;255m.\\033[48;2;023;000;255m.",
+ "\\033[48;2;024;000;255m.\\033[48;2;025;000;255m.\\033[48;2;026;000;255m.\\033[48;2;027;000;255m.\\033[48;2;028;000;255m.\\033[48;2;029;000;255m.\\033[48;2;030;000;255m.\\033[48;2;031;000;255m.",
+ "\\033[0m\\n ",
+ "\\033[48;2;032;000;255m.\\033[48;2;033;000;255m.\\033[48;2;034;000;255m.\\033[48;2;035;000;255m.\\033[48;2;036;000;255m.\\033[48;2;037;000;255m.\\033[48;2;038;000;255m.\\033[48;2;039;000;255m.",
+ "\\033[48;2;040;000;255m.\\033[48;2;041;000;255m.\\033[48;2;042;000;255m.\\033[48;2;043;000;255m.\\033[48;2;044;000;255m.\\033[48;2;045;000;255m.\\033[48;2;046;000;255m.\\033[48;2;047;000;255m.",
+ "\\033[48;2;048;000;255m.\\033[48;2;049;000;255m.\\033[48;2;050;000;255m.\\033[48;2;051;000;255m.\\033[48;2;052;000;255m.\\033[48;2;053;000;255m.\\033[48;2;054;000;255m.\\033[48;2;055;000;255m.",
+ "\\033[48;2;056;000;255m.\\033[48;2;057;000;255m.\\033[48;2;058;000;255m.\\033[48;2;059;000;255m.\\033[48;2;060;000;255m.\\033[48;2;061;000;255m.\\033[48;2;062;000;255m.\\033[48;2;063;000;255m.",
+ "\\033[48;2;064;000;255m.\\033[48;2;065;000;255m.\\033[48;2;066;000;255m.\\033[48;2;067;000;255m.\\033[48;2;068;000;255m.\\033[48;2;069;000;255m.\\033[48;2;070;000;255m.\\033[48;2;071;000;255m.",
+ "\\033[48;2;072;000;255m.\\033[48;2;073;000;255m.\\033[48;2;074;000;255m.\\033[48;2;075;000;255m.\\033[48;2;076;000;255m.\\033[48;2;077;000;255m.\\033[48;2;078;000;255m.\\033[48;2;079;000;255m.",
+ "\\033[48;2;080;000;255m.\\033[48;2;081;000;255m.\\033[48;2;082;000;255m.\\033[48;2;083;000;255m.\\033[48;2;084;000;255m.\\033[48;2;085;000;255m.\\033[48;2;086;000;255m.\\033[48;2;087;000;255m.",
+ "\\033[48;2;088;000;255m.\\033[48;2;089;000;255m.\\033[48;2;090;000;255m.\\033[48;2;091;000;255m.\\033[48;2;092;000;255m.\\033[48;2;093;000;255m.\\033[48;2;094;000;255m.\\033[48;2;095;000;255m.",
+ "\\033[48;2;096;000;255m.\\033[48;2;097;000;255m.\\033[48;2;098;000;255m.\\033[48;2;099;000;255m.\\033[48;2;100;000;255m.\\033[48;2;101;000;255m.\\033[48;2;102;000;255m.\\033[48;2;103;000;255m.",
+ "\\033[48;2;104;000;255m.\\033[48;2;105;000;255m.\\033[48;2;106;000;255m.\\033[48;2;107;000;255m.\\033[48;2;108;000;255m.\\033[48;2;109;000;255m.\\033[48;2;110;000;255m.\\033[48;2;111;000;255m.",
+ "\\033[48;2;112;000;255m.\\033[48;2;113;000;255m.\\033[48;2;114;000;255m.\\033[48;2;115;000;255m.\\033[48;2;116;000;255m.\\033[48;2;117;000;255m.\\033[48;2;118;000;255m.\\033[48;2;119;000;255m.",
+ "\\033[48;2;120;000;255m.\\033[48;2;121;000;255m.\\033[48;2;122;000;255m.\\033[48;2;123;000;255m.\\033[48;2;124;000;255m.\\033[48;2;125;000;255m.\\033[48;2;126;000;255m.\\033[48;2;127;000;255m.",
+ "\\033[0m\\n ",
+ "\\033[48;2;128;000;255m.\\033[48;2;129;000;255m.\\033[48;2;130;000;255m.\\033[48;2;131;000;255m.\\033[48;2;132;000;255m.\\033[48;2;133;000;255m.\\033[48;2;134;000;255m.\\033[48;2;135;000;255m.",
+ "\\033[48;2;136;000;255m.\\033[48;2;137;000;255m.\\033[48;2;138;000;255m.\\033[48;2;139;000;255m.\\033[48;2;140;000;255m.\\033[48;2;141;000;255m.\\033[48;2;142;000;255m.\\033[48;2;143;000;255m.",
+ "\\033[48;2;144;000;255m.\\033[48;2;145;000;255m.\\033[48;2;146;000;255m.\\033[48;2;147;000;255m.\\033[48;2;148;000;255m.\\033[48;2;149;000;255m.\\033[48;2;150;000;255m.\\033[48;2;151;000;255m.",
+ "\\033[48;2;152;000;255m.\\033[48;2;153;000;255m.\\033[48;2;154;000;255m.\\033[48;2;155;000;255m.\\033[48;2;156;000;255m.\\033[48;2;157;000;255m.\\033[48;2;158;000;255m.\\033[48;2;159;000;255m.",
+ "\\033[48;2;160;000;255m.\\033[48;2;161;000;255m.\\033[48;2;162;000;255m.\\033[48;2;163;000;255m.\\033[48;2;164;000;255m.\\033[48;2;165;000;255m.\\033[48;2;166;000;255m.\\033[48;2;167;000;255m.",
+ "\\033[48;2;168;000;255m.\\033[48;2;169;000;255m.\\033[48;2;170;000;255m.\\033[48;2;171;000;255m.\\033[48;2;172;000;255m.\\033[48;2;173;000;255m.\\033[48;2;174;000;255m.\\033[48;2;175;000;255m.",
+ "\\033[48;2;176;000;255m.\\033[48;2;177;000;255m.\\033[48;2;178;000;255m.\\033[48;2;179;000;255m.\\033[48;2;180;000;255m.\\033[48;2;181;000;255m.\\033[48;2;182;000;255m.\\033[48;2;183;000;255m.",
+ "\\033[48;2;184;000;255m.\\033[48;2;185;000;255m.\\033[48;2;186;000;255m.\\033[48;2;187;000;255m.\\033[48;2;188;000;255m.\\033[48;2;189;000;255m.\\033[48;2;190;000;255m.\\033[48;2;191;000;255m.",
+ "\\033[48;2;192;000;255m.\\033[48;2;193;000;255m.\\033[48;2;194;000;255m.\\033[48;2;195;000;255m.\\033[48;2;196;000;255m.\\033[48;2;197;000;255m.\\033[48;2;198;000;255m.\\033[48;2;199;000;255m.",
+ "\\033[48;2;200;000;255m.\\033[48;2;201;000;255m.\\033[48;2;202;000;255m.\\033[48;2;203;000;255m.\\033[48;2;204;000;255m.\\033[48;2;205;000;255m.\\033[48;2;206;000;255m.\\033[48;2;207;000;255m.",
+ "\\033[48;2;208;000;255m.\\033[48;2;209;000;255m.\\033[48;2;210;000;255m.\\033[48;2;211;000;255m.\\033[48;2;212;000;255m.\\033[48;2;213;000;255m.\\033[48;2;214;000;255m.\\033[48;2;215;000;255m.",
+ "\\033[48;2;216;000;255m.\\033[48;2;217;000;255m.\\033[48;2;218;000;255m.\\033[48;2;219;000;255m.\\033[48;2;220;000;255m.\\033[48;2;221;000;255m.\\033[48;2;222;000;255m.\\033[48;2;223;000;255m.",
+ "\\033[0m\\n ",
+ "\\033[48;2;224;000;255m.\\033[48;2;225;000;255m.\\033[48;2;226;000;255m.\\033[48;2;227;000;255m.\\033[48;2;228;000;255m.\\033[48;2;229;000;255m.\\033[48;2;230;000;255m.\\033[48;2;231;000;255m.",
+ "\\033[48;2;232;000;255m.\\033[48;2;233;000;255m.\\033[48;2;234;000;255m.\\033[48;2;235;000;255m.\\033[48;2;236;000;255m.\\033[48;2;237;000;255m.\\033[48;2;238;000;255m.\\033[48;2;239;000;255m.",
+ "\\033[48;2;240;000;255m.\\033[48;2;241;000;255m.\\033[48;2;242;000;255m.\\033[48;2;243;000;255m.\\033[48;2;244;000;255m.\\033[48;2;245;000;255m.\\033[48;2;246;000;255m.\\033[48;2;247;000;255m.",
+ "\\033[48;2;248;000;255m.\\033[48;2;249;000;255m.\\033[48;2;250;000;255m.\\033[48;2;251;000;255m.\\033[48;2;252;000;255m.\\033[48;2;253;000;255m.\\033[48;2;254;000;255m.\\033[48;2;255;000;255m.",
+ "\\033[48;2;255;000;255m.\\033[48;2;255;000;254m.\\033[48;2;255;000;253m.\\033[48;2;255;000;252m.\\033[48;2;255;000;251m.\\033[48;2;255;000;250m.\\033[48;2;255;000;249m.\\033[48;2;255;000;248m.",
+ "\\033[48;2;255;000;247m.\\033[48;2;255;000;246m.\\033[48;2;255;000;245m.\\033[48;2;255;000;244m.\\033[48;2;255;000;243m.\\033[48;2;255;000;242m.\\033[48;2;255;000;241m.\\033[48;2;255;000;240m.",
+ "\\033[48;2;255;000;239m.\\033[48;2;255;000;238m.\\033[48;2;255;000;237m.\\033[48;2;255;000;236m.\\033[48;2;255;000;235m.\\033[48;2;255;000;234m.\\033[48;2;255;000;233m.\\033[48;2;255;000;232m.",
+ "\\033[48;2;255;000;231m.\\033[48;2;255;000;230m.\\033[48;2;255;000;229m.\\033[48;2;255;000;228m.\\033[48;2;255;000;227m.\\033[48;2;255;000;226m.\\033[48;2;255;000;225m.\\033[48;2;255;000;224m.",
+ "\\033[48;2;255;000;223m.\\033[48;2;255;000;222m.\\033[48;2;255;000;221m.\\033[48;2;255;000;220m.\\033[48;2;255;000;219m.\\033[48;2;255;000;218m.\\033[48;2;255;000;217m.\\033[48;2;255;000;216m.",
+ "\\033[48;2;255;000;215m.\\033[48;2;255;000;214m.\\033[48;2;255;000;213m.\\033[48;2;255;000;212m.\\033[48;2;255;000;211m.\\033[48;2;255;000;210m.\\033[48;2;255;000;209m.\\033[48;2;255;000;208m.",
+ "\\033[48;2;255;000;207m.\\033[48;2;255;000;206m.\\033[48;2;255;000;205m.\\033[48;2;255;000;204m.\\033[48;2;255;000;203m.\\033[48;2;255;000;202m.\\033[48;2;255;000;201m.\\033[48;2;255;000;200m.",
+ "\\033[48;2;255;000;199m.\\033[48;2;255;000;198m.\\033[48;2;255;000;197m.\\033[48;2;255;000;196m.\\033[48;2;255;000;195m.\\033[48;2;255;000;194m.\\033[48;2;255;000;193m.\\033[48;2;255;000;192m.",
+ "\\033[0m\\n ",
+ "\\033[48;2;255;000;191m.\\033[48;2;255;000;190m.\\033[48;2;255;000;189m.\\033[48;2;255;000;188m.\\033[48;2;255;000;187m.\\033[48;2;255;000;186m.\\033[48;2;255;000;185m.\\033[48;2;255;000;184m.",
+ "\\033[48;2;255;000;183m.\\033[48;2;255;000;182m.\\033[48;2;255;000;181m.\\033[48;2;255;000;180m.\\033[48;2;255;000;179m.\\033[48;2;255;000;178m.\\033[48;2;255;000;177m.\\033[48;2;255;000;176m.",
+ "\\033[48;2;255;000;175m.\\033[48;2;255;000;174m.\\033[48;2;255;000;173m.\\033[48;2;255;000;172m.\\033[48;2;255;000;171m.\\033[48;2;255;000;170m.\\033[48;2;255;000;169m.\\033[48;2;255;000;168m.",
+ "\\033[48;2;255;000;167m.\\033[48;2;255;000;166m.\\033[48;2;255;000;165m.\\033[48;2;255;000;164m.\\033[48;2;255;000;163m.\\033[48;2;255;000;162m.\\033[48;2;255;000;161m.\\033[48;2;255;000;160m.",
+ "\\033[48;2;255;000;159m.\\033[48;2;255;000;158m.\\033[48;2;255;000;157m.\\033[48;2;255;000;156m.\\033[48;2;255;000;155m.\\033[48;2;255;000;154m.\\033[48;2;255;000;153m.\\033[48;2;255;000;152m.",
+ "\\033[48;2;255;000;151m.\\033[48;2;255;000;150m.\\033[48;2;255;000;149m.\\033[48;2;255;000;148m.\\033[48;2;255;000;147m.\\033[48;2;255;000;146m.\\033[48;2;255;000;145m.\\033[48;2;255;000;144m.",
+ "\\033[48;2;255;000;143m.\\033[48;2;255;000;142m.\\033[48;2;255;000;141m.\\033[48;2;255;000;140m.\\033[48;2;255;000;139m.\\033[48;2;255;000;138m.\\033[48;2;255;000;137m.\\033[48;2;255;000;136m.",
+ "\\033[48;2;255;000;135m.\\033[48;2;255;000;134m.\\033[48;2;255;000;133m.\\033[48;2;255;000;132m.\\033[48;2;255;000;131m.\\033[48;2;255;000;130m.\\033[48;2;255;000;129m.\\033[48;2;255;000;128m.",
+ "\\033[48;2;255;000;127m.\\033[48;2;255;000;126m.\\033[48;2;255;000;125m.\\033[48;2;255;000;124m.\\033[48;2;255;000;123m.\\033[48;2;255;000;122m.\\033[48;2;255;000;121m.\\033[48;2;255;000;120m.",
+ "\\033[48;2;255;000;119m.\\033[48;2;255;000;118m.\\033[48;2;255;000;117m.\\033[48;2;255;000;116m.\\033[48;2;255;000;115m.\\033[48;2;255;000;114m.\\033[48;2;255;000;113m.\\033[48;2;255;000;112m.",
+ "\\033[48;2;255;000;111m.\\033[48;2;255;000;110m.\\033[48;2;255;000;109m.\\033[48;2;255;000;108m.\\033[48;2;255;000;107m.\\033[48;2;255;000;106m.\\033[48;2;255;000;105m.\\033[48;2;255;000;104m.",
+ "\\033[48;2;255;000;103m.\\033[48;2;255;000;102m.\\033[48;2;255;000;101m.\\033[48;2;255;000;100m.\\033[48;2;255;000;099m.\\033[48;2;255;000;098m.\\033[48;2;255;000;097m.\\033[48;2;255;000;096m.",
+ "\\033[0m\\n ",
+ "\\033[48;2;255;000;095m.\\033[48;2;255;000;094m.\\033[48;2;255;000;093m.\\033[48;2;255;000;092m.\\033[48;2;255;000;091m.\\033[48;2;255;000;090m.\\033[48;2;255;000;089m.\\033[48;2;255;000;088m.",
+ "\\033[48;2;255;000;087m.\\033[48;2;255;000;086m.\\033[48;2;255;000;085m.\\033[48;2;255;000;084m.\\033[48;2;255;000;083m.\\033[48;2;255;000;082m.\\033[48;2;255;000;081m.\\033[48;2;255;000;080m.",
+ "\\033[48;2;255;000;079m.\\033[48;2;255;000;078m.\\033[48;2;255;000;077m.\\033[48;2;255;000;076m.\\033[48;2;255;000;075m.\\033[48;2;255;000;074m.\\033[48;2;255;000;073m.\\033[48;2;255;000;072m.",
+ "\\033[48;2;255;000;071m.\\033[48;2;255;000;070m.\\033[48;2;255;000;069m.\\033[48;2;255;000;068m.\\033[48;2;255;000;067m.\\033[48;2;255;000;066m.\\033[48;2;255;000;065m.\\033[48;2;255;000;064m.",
+ "\\033[48;2;255;000;063m.\\033[48;2;255;000;062m.\\033[48;2;255;000;061m.\\033[48;2;255;000;060m.\\033[48;2;255;000;059m.\\033[48;2;255;000;058m.\\033[48;2;255;000;057m.\\033[48;2;255;000;056m.",
+ "\\033[48;2;255;000;055m.\\033[48;2;255;000;054m.\\033[48;2;255;000;053m.\\033[48;2;255;000;052m.\\033[48;2;255;000;051m.\\033[48;2;255;000;050m.\\033[48;2;255;000;049m.\\033[48;2;255;000;048m.",
+ "\\033[48;2;255;000;047m.\\033[48;2;255;000;046m.\\033[48;2;255;000;045m.\\033[48;2;255;000;044m.\\033[48;2;255;000;043m.\\033[48;2;255;000;042m.\\033[48;2;255;000;041m.\\033[48;2;255;000;040m.",
+ "\\033[48;2;255;000;039m.\\033[48;2;255;000;038m.\\033[48;2;255;000;037m.\\033[48;2;255;000;036m.\\033[48;2;255;000;035m.\\033[48;2;255;000;034m.\\033[48;2;255;000;033m.\\033[48;2;255;000;032m.",
+ "\\033[48;2;255;000;031m.\\033[48;2;255;000;030m.\\033[48;2;255;000;029m.\\033[48;2;255;000;028m.\\033[48;2;255;000;027m.\\033[48;2;255;000;026m.\\033[48;2;255;000;025m.\\033[48;2;255;000;024m.",
+ "\\033[48;2;255;000;023m.\\033[48;2;255;000;022m.\\033[48;2;255;000;021m.\\033[48;2;255;000;020m.\\033[48;2;255;000;019m.\\033[48;2;255;000;018m.\\033[48;2;255;000;017m.\\033[48;2;255;000;016m.",
+ "\\033[48;2;255;000;015m.\\033[48;2;255;000;014m.\\033[48;2;255;000;013m.\\033[48;2;255;000;012m.\\033[48;2;255;000;011m.\\033[48;2;255;000;010m.\\033[48;2;255;000;009m.\\033[48;2;255;000;008m.",
+ "\\033[48;2;255;000;007m.\\033[48;2;255;000;006m.\\033[48;2;255;000;005m.\\033[48;2;255;000;004m.\\033[48;2;255;000;003m.\\033[48;2;255;000;002m.\\033[48;2;255;000;001m.\\033[48;2;255;000;000m.",
+
+ "\\033[0m\\n",
+ "\\n",
+ "---> Hit 'e' for next test: ",
+ "\\033[2J\\033[H" "ESC TEST 0023: FG/BG Specific Reset\\n",
+ "\\n",
+ "Test fg reset: \\033[31mThis is red on norm bg\\033[39m, This is ESC[39m reset (norm fg on norm bg).\\033[m\\n",
+ "Test bg reset: \\033[41mThis is norm fg on red bg\\033[49m, This is ESC[49m reset (norm fg on norm bg).\\033[m\\n",
+ "\\n",
+ "Test fg/bg with fg reset: \\033[32;44mThis is grn on blu\\033[39m, This is ESC[39m reset (norm fg on blu).\\033[m\\n",
+ "Test fg/bg with bg reset: \\033[32;44mThis is grn on blu\\033[49m, This is ESC[49m reset (grn on norm bg).\\033[m\\n",
+ "\\n",
+ "---> Hit 'e' for next test: ",
+ NULL
+};
+if (reset) { index = 0; return 0; }
+return show_test(test, index);} {}
+ }
+ Function {test_esc_fgbg_colors(bool reset)} {
+ comment {--- 0030: Test FG/BG Colors} return_type {static int}
+ } {
+ code {static int index = 0;
+const char *test[] = {
+ // Screen 30
+ "\\033[2J\\033[H" "ESC TEST 0030: FG/BG Colors\\n",
+ "\\n",
+ " Foreground Colors:\\n",
+ " \\033[2;31m" " Dim Red text \\033[0m \\033[0;32m" " Dim Grn text \\033[0m \\033[2;33m" " Dim Yel text \\033[0m\\n",
+ " \\033[2;34m" " Dim Blu text \\033[0m \\033[0;35m" " Dim Mag text \\033[0m \\033[2;36m" " Dim Cyn text \\033[0m"
+ " \\033[2;37m" " Dim Wht text \\033[0m\\n",
+ " \\033[0;31m" " Nor Red text \\033[0m \\033[0;32m" " Nor Grn text \\033[0m \\033[2;33m" " Nor Yel text \\033[0m\\n",
+ " \\033[0;34m" " Nor Blu text \\033[0m \\033[0;35m" " Nor Mag text \\033[0m \\033[0;36m" " Nor Cyn text \\033[0m"
+ " \\033[0;37m" " Nor Wht text \\033[0m\\n",
+ " \\033[1;31m" " Bold Red text \\033[0m \\033[1;32m" " Bold Grn text \\033[0m \\033[1;33m" " Bold Yel text \\033[0m\\n",
+ " \\033[1;34m" " Bold Blu text \\033[0m \\033[1;35m" " Bold Mag \\033[0m \\033[1;36m" " Bold Cyn text \\033[0m"
+ " \\033[1;37m" " Bold Wht text \\033[0m\\n",
+ "\\n",
+ " Background Colors:\\n",
+ " \\033[2;37;41m"" Dim Red bg \\033[0m \\033[2;37;42m"" Dim Grn bg \\033[0m \\033[2;37;43m"" Dim Yel bg \\033[0m\\n",
+ " \\033[2;37;44m"" Dim Blu bg \\033[0m \\033[2;37;45m"" Dim Mag bg \\033[0m \\033[2;37;46m"" Dim Cyn bg \\033[0m"
+ " \\033[2;37;47m"" Dim Wht bg \\033[0m\\n",
+ " \\033[0;37;41m"" Nor Red bg \\033[0m \\033[0;37;42m"" Nor Grn bg \\033[0m \\033[0;37;43m"" Nor Yel bg \\033[0m\\n",
+ " \\033[0;37;44m"" Nor Blu bg \\033[0m \\033[0;37;45m"" Nor Mag bg \\033[0m \\033[0;37;46m"" Nor Cyn bg \\033[0m"
+ " \\033[0;37;47m"" Nor Wht bg \\033[0m\\n",
+ " \\033[1;37;41m"" Bold Red bg \\033[0m \\033[1;37;42m"" Bold Grn bg \\033[0m \\033[1;37;43m"" Bold Yel bg \\033[0m\\n",
+ " \\033[1;37;44m"" Bold Blu bg \\033[0m \\033[1;37;45m"" Bold Mag bg \\033[0m \\033[1;37;46m"" Bold Cyn bg \\033[0m"
+ " \\033[1;37;47m"" Bold Wht bg \\033[0m\\n",
+ "\\n",
+ "---> Hit 'e' for next test: ",
+ NULL
+};
+if (reset) { index = 0; return 0; }
+return show_test(test, index);} {}
+ }
+ Function {test_esc_curpos(bool reset)} {
+ comment {--- 0040: Test Cursor Positioning} return_type {static int}
+ } {
+ code {static int index = 0;
+const char *test[] = {
+ // Screen 40
+ "\\033[2J\\033[H" "ESC TEST 0040: Cursor Positioning\\n",
+ "2:\\n",
+ "3: 10 15 20 25 30 35 40 45 50 55 60 65 70\\n",
+ "4: |----:----|----:----|----:----|----:----|----:----|----:----|\\n",
+ "5:\\n",
+ "6:\\n",
+ "7:\\n",
+ "\\033[5;10H" "X", "\\033[5;15H" "X", "\\033[5;20H" "X",
+ "\\033[5;40H" "X", "\\033[5;45H" "X", "\\033[5;50H" "X",
+ "\\033[6;10H" "|", "\\033[6;15H" "|", "\\033[6;20H" "|",
+ "\\033[6;40H" "|", "\\033[6;45H" "|", "\\033[6;50H" "|",
+ "\\033[7;10H" "10,5", "\\033[7;15H" "15,5", "\\033[7;20H" "20,5",
+ "\\033[7;40H" "40,5", "\\033[7;45H" "45,5", "\\033[7;50H" "50,5",
+ "\\033[8H\\n",
+ "---> Hit 'e' for next test: ",
+ NULL
+};
+if (reset) { index = 0; return 0; }
+return show_test(test, index);} {}
+ }
+ Function {test_esc_tabstops(bool reset)} {
+ comment {--- 0050: Tabstops: Default Tabstops} return_type {static int}
+ } {
+ code {static int index = 0;
+const char *test[] = {
+ // Screen 50
+ // We use <ESC>c to reset the terminal to default tabstops.
+ "\\033c" "ESC TEST 0050: Tabs: Default Tabstops\\n",
+ " A B C D E F G H I\\n",
+ " | | | | | | | | |\\n",
+ "\\tA\\tB\\tC\\tD-\\tE--\\tF-----\\tG------\\tH-------I\\n",
+ "\\t\\tB\\t\\tD\\t\\tF\\t\\tH-------I\\n",
+ "---> Hit 'e' for next test: ",
+ // We use <ESC>c to reset the terminal to default tabstops.
+ // Should also clear screen and home cursor..
+ "\\033c" "ESC TEST 0051: Tabs: Change Tabstops\\n",
+ "\\n",
+ " 8 16 24 32 40 48 56 64 72\\n",
+ "--------|-------|-------|-------|-------|-------|-------|-------|-------|\\n",
+ "\\tX\\tX\\tX\\tX\\tX\\tX\\tX\\tX\\tX\\n",
+ "\\n",
+ "Setting new tabstops..\\033[3g\\n",
+ ".\\033HT..\\033HT...\\033HT....\\033HT.....\\033HT......\\033HT.......\\033HT........\\033HT.........\\033HT\\n",
+ "-|--|---|----|-----|------|-------|--------|---------|\\n",
+ "\\tX\\tX\\tX\\tX\\tX\\tX\\tX\\tX\\tX\\tX\\n",
+ "---> Hit 'e' for next test: ",
+ "\\033c" "ESC TEST 0052: Tabs: Default Tabstops Again\\n",
+ "\\n",
+ "Back to normal tabstops:\\n",
+ " 8 16 24 32 40 48 56 64 72\\n",
+ "--------|-------|-------|-------|-------|-------|-------|-------|-------|\\n",
+ "\\tX\\tX\\tX\\tX\\tX\\tX\\tX\\tX\\tX\\n",
+ "\\n",
+ "---> Hit 'e' for next test: ",
+ NULL
+};
+if (reset) { index = 0; return 0; }
+return show_test(test, index);} {}
+ }
+ Function {test_esc_insert_char(bool reset)} {
+ comment {--- 0060: Insert Char} return_type {static int}
+ } {
+ code {static int index = 0;
+const char *test[] = {
+ // Screen 60
+ "\\033[2J\\033[HESC TEST 0060: Insert Char (ESC[@)\\n",
+ "\\n",
+ " 10> 20> 30> 40> 50> 60> 70> 80>\\n",
+ "4:34567890123456789012345678901234567890123456789012345678901234567890123456789\\n",
+ "5:34567890123456789012345678901234567890123456789012345678901234567890123456789\\n",
+ "6:34567890123456789012345678901234567890123456789012345678901234567890123456789\\n",
+ "\\033[s\\033[5;29H\\033[1;31m>\\033[0m\\033[u", // save curs, movcur, bold/red, '>', norm, recall curs
+ "\\n",
+ "---> Hit 'e' to test INSERT 1 char at row/col 5/30 (right of '>'): \\033[K",
+ "\\033[5;30H\\033[@\\033[8H",
+ "---> Hit 'e' to test INSERT 1 char at row/col 5/30 (right of '>'): \\033[K",
+ "\\033[5;30H\\033[@\\033[8H",
+ "---> Hit 'e' to test INSERT 4 chars at row/col 5/30 (right of '>'): \\033[K",
+ "\\033[5;30H\\033[4@\\033[8H",
+ "---> Hit 'e' to test INSERT 4 chars at row/col 5/30 (right of '>'): \\033[K",
+ "\\033[5;30H\\033[4@\\033[8H",
+ "---> Hit 'e' to test DELETE 1 chars at row/col 5/20 (right of '>'): \\033[K",
+ "\\033[5;30H\\033[P\\033[8H",
+ "---> Hit 'e' to test DELETE 1 chars at row/col 5/20 (right of '>'): \\033[K",
+ "\\033[5;30H\\033[P\\033[8H",
+ "---> Hit 'e' to test DELETE 2 chars at row/col 5/20 (right of '>'): \\033[K",
+ "\\033[5;30H\\033[2P\\033[8H",
+ "---> Hit 'e' to test DELETE 4 chars at row/col 5/20 (right of '>'): \\033[K",
+ "\\033[5;30H\\033[4P\\033[8H",
+ "---> Hit 'e' to test DELETE 1 char at row/col 5/20 (right of '>'): \\033[K",
+ "\\033[5;30H\\033[P\\033[8H",
+ "---> Hit 'e' to test DELETE 1 char at row/col 5/20 (right of '>'): \\033[K",
+ "\\033[5;30H\\033[P\\033[8H",
+ "---> Hit 'e' to test DELETE 1 *EXTRA* char at row/col 5/20 (right of '>'): \\033[K",
+ "\\033[5;30H\\033[P\\033[8H",
+ "---> Hit 'e' for next test: \\033[K",
+ NULL
+};
+if (reset) { index = 0; return 0; }
+return show_test(test, index);} {}
+ }
+ Function {test_esc_clear_test(bool reset)} {
+ comment {--- 0070: Clear Test ESC[K / ESC[K} return_type {static int}
+ } {
+ code {static int index = 0;
+const char *test[] = {
+ // Screen 70
+ "\\033[2J\\033[H" "ESC TEST 0070: Clear Test (ESC[K / ESC[J)\\n",
+ "2: 10> 20> 30> 40> 50> 60> 70> 80>\\n",
+ "3:34567890123456789012345678901234567890123456789012345678901234567890123456789\\n",
+ "4:3456789012345678901234 ERASE EOL->XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\\n",
+ "5:YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY<-ERASE SOL 012345678901234567890123456789\\n",
+ "6:34567890123456789012345678901234567890123456789012345678901234567890123456789\\n",
+ "7:FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF\\n",
+ "8:34567890123456789012345678901234567890123456789012345678901234567890123456789\\n",
+ "9:3456789012345678901234 ERASE EOD->ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ\\n",
+ "0:ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ\\n",
+ "1:ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ\\n",
+ "2:ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ\\n",
+ "3:ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ\\n",
+ "4:ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ\\n",
+ "5:ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ\\n",
+ "6:ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ\\n",
+ "7:ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ\\n",
+ "\\n",
+ "---> Hit 'e' to test ERASE EOL (should make X's disappear): ",
+ "\\033[4;37H\\033[K\\n", // ERASE EOL
+ "\\033[19H",
+ "---> Hit 'e' to test ERASE SOL (should make Y's disappear): ",
+ "\\033[5;37H\\033[1K\\n", // ERASE SOL
+ "\\033[19H",
+ "---> Hit 'e' to test ERASE LINE (should make F's disappear): ",
+ "\\033[7;37H\\033[2K\\n", // ERASE LINE
+ "\\033[19H",
+ "---> Hit 'e' to test ERASE EOD (should make Z's disappear): ",
+ "\\033[9;37H\\033[0J\\n", // ERASE EOD
+ "\\033[19H",
+ "---> Hit 'e' for next test: ",
+ // Screen 71
+ "\\033[2J\\033[H" "ESC TEST 0071: Clear Test (ESC[K / ESC[J)\\n",
+ "2: | | | | | |\\n",
+ "3:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\\n",
+ "4:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\\n",
+ "5:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\\n",
+ "6:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX<-- ERASE SOD 567890123456789\\n",
+ "7:3456789012345678901234567890123456789012345678901234567890123456789\\n",
+ "8:3456789012345678901234567890123456789012345678901234567890123456789\\n",
+ "9:3456789012345678901234567890123456789012345678901234567890123456789\\n",
+ "\\033[19H",
+ "---> Hit 'e' to test ERASE SOD (should make X's disappear): ",
+ "\\033[6;40H\\033[1J\\n", // ERASE SOD
+ "\\033[19H",
+ "---> Hit 'e' for next test: \\033[K",
+ NULL
+};
+if (reset) { index = 0; return 0; }
+return show_test(test, index);} {}
+ }
+ Function {test_esc_delete_row(bool reset)} {
+ comment {--- 0080: Delete Row - ESC[M} return_type {static int}
+ } {
+ code {static int index = 0;
+const char *test[] = {
+ // Screen 80
+ "\\033[2J\\033[H" "ESC TEST 0080: Delete Row (ESC[M)\\n",
+ "02:456789012345678901234567890123456789012345678901234567890123456789\\n",
+ "03:456789012345678901234567890123456789012345678901234567890123456789\\n",
+ "04:456789012345678901234567890123456789012345678901234567890123456789\\n",
+ "05:456789012345678901234567890123456789012345678901234567890123456789\\n",
+ "06:456789012345678901234567890123456789012345678901234567890123456789\\n",
+ "07:456789012345678901234567890123456789012345678901234567890123456789\\n",
+ "08:456789012345678901234567890123456789012345678901234567890123456789\\n",
+ "09:456789012345678901234567890123456789012345678901234567890123456789\\n",
+ "10:456789012345678901234567890123456789012345678901234567890123456789\\n",
+ "\\n",
+ "\\n",
+ "\\n",
+ "\\n",
+ "\\n",
+ "\\n",
+ "\\n",
+ "\\n",
+ "\\n",
+ "\\n",
+ "\\n",
+ "\\n",
+ "\\n",
+ "24:456789012345678901234567890123456789012345678901234567890123456789\\n",
+ "25:456789012345678901234567890123456789012345678901234567890123456789",
+ "\\033[12H", // to prompt row
+ "---> Hit 'e' to test [1/4] DELETE LINE 3 with ESC[M: \\033[K",
+ "\\033[2K", // clear prompt
+ "\\033[3H", // move to line 3
+ "\\033[M", // delete line 3
+ "\\033[12H", // back to line 12H for prompt
+ //"\\033[2K\\033[3H\\033[M", // clear prompt, to row 3, delete row
+ //"\\033[12H", // to prompt row
+ "---> Hit 'e' to test [2/4] DELETE 3 MORE LINES with ESC[3M: \\033[K",
+ "\\033[2K\\033[3H\\033[3M", // clear prompt, to row 3, delete 3 rows
+ "\\033[12H", // to prompt row
+ "---> Hit 'e' to test [3/4] DELETE 3 MORE LINES with ESC[3M: \\033[K",
+ "\\033[2K\\033[3H\\033[3M", // clear prompt, to row 3, delete 3 rows
+ "\\033[12H", // to prompt row
+ "---> Hit 'e' to test [4/4] DELETE 3 MORE LINES with ESC[3M: \\033[K",
+ "\\033[2K\\033[3H\\033[3M", // clear prompt, to row 3, delete 3 rows
+ "\\033[12H", // to prompt row
+ "---> Hit 'e' for next test: ",
+ NULL
+};
+if (reset) { index = 0; return 0; }
+return show_test(test, index);} {}
+ }
+ Function {test_esc_insert_row(bool reset)} {
+ comment {--- 0085: Insert Row - ESC[L} return_type {static int}
+ } {
+ code {static int index = 0;
+const char *test[] = {
+ // Screen 71
+ "\\033[2J\\033[H" "ESC TEST 0085: Insert Row (ESC[L)\\n",
+ "2:3456789012345678901234567890123456789012345678901234567890123456789\\n",
+ "3:3456789012345678901234567890123456789012345678901234567890123456789\\n",
+ "4:3456789012345678901234567890123456789012345678901234567890123456789\\n",
+ "\\033[24H",
+ "24:456789012345678901234567890123456789012345678901234567890123456789\\n",
+ "25:456789012345678901234567890123456789012345678901234567890123456789",
+ "\\033[12H",
+ "---> Hit 'e' to test [1/3] INSERT 1 ROW AT LINE 3 with ESC[L: \\033[K",
+ "\\033[2K\\033[3H\\033[L", // clear prompt, to row 3, insert row
+ "\\033[12H", // to prompt row 17
+ "---> Hit 'e' to test [2/3] INSERT 3 ROWS AT LINE 3 with ESC[3L: \\033[K",
+ "\\033[2K\\033[3H\\033[3L", // clear prompt, to row 3, insert 3 rows
+ "\\033[12H", // to prompt row 17
+ "---> Hit 'e' for next test: ",
+ NULL
+};
+if (reset) { index = 0; return 0; }
+return show_test(test, index);} {}
+ }
+ Function {test_esc_scrolling(bool reset)} {
+ comment {--- 0090: Scrolling Up/Down - <ESC>[S / <ESC>M} return_type {static int}
+ } {
+ code {static int index = 0;
+const char *test[] = {
+ // Screen 90
+ "\\033[2J\\033[H" "ESC TEST 0090: Scrolling up/down\\n",
+ "Line 2 🡅\\n",
+ "Line 3 [A]\\n",
+ "Line 4 🡅\\n",
+ "Line 5 [B]\\n",
+ "Line 6\\n",
+ "Line 7\\n",
+ "Line 8\\n",
+ "Line 9\\n",
+ "Line 10\\n",
+ "Line 11\\n",
+ "Line 12\\n",
+ // Do this weird thing so the lines still appear in order in our code
+ "\\033[200H\\033[10ABottom Line -10",
+ "\\033[200H\\033[9ABottom Line -9",
+ "\\033[200H\\033[8ABottom Line -8",
+ "\\033[200H\\033[7ABottom Line -7",
+ "\\033[200H\\033[6ABottom Line -6",
+ "\\033[200H\\033[5ABottom Line -5 [D]",
+ "\\033[200H\\033[4ABottom Line -4 🡇",
+ "\\033[200H\\033[3ABottom Line -3",
+ "\\033[200H\\033[2ABottom Line -2 [C]",
+ "\\033[200H\\033[1ABottom Line -1 🡇",
+ "\\033[200H" "Bottom Line 0",
+ "\\033[15H", // Line 15
+ "---> Hit 'e' to SCROLL UP 1 with <ESC>[S so top line at position [A]: \\033[K",
+ "\\033[H\\033[S\\033[14H", // Line 14 because up one line
+ "---> Hit 'e' to SCROLL UP 2 with <ESC>[2S so top line at position [B]: \\033[K",
+ "\\033[H\\033[2S\\033[12H", // Line 12 because up two more lines
+ "---> Hit 'e' to SCROLL DOWN 1 with <ESC>M at screen top: \\033[K",
+ "\\033[H\\033M\\033[13H", // Line 13 because down 1 line
+ "---> Hit 'e' to SCROLL DOWN 2 with 2x<ESC>M: \\033[K",
+ "\\033[H\\033M\\033M\\033[15H", // Line 15 because down 2 lines
+ "---> Hit 'e' to SCROLL DOWN 1 with <ESC>M for bottom line at posn [C]: \\033[K",
+ "\\033[H\\033M\\033[16H", // Line 16 because down 1 lines
+ "---> Hit 'e' to SCROLL DOWN 3 with 3x<ESC>M for bottom line at posn [D]: \\033[K",
+ "\\033[H\\033M\\033M\\033M\\033[19H", // Line 18 because down 3 lines
+ //TODO Other ESC commands to scroll up/down?
+ "---> Hit 'e' for next test: \\033[K",
+ // Screen 100
+ "\\033[2J\\033[HESC TEST 0091: Cursor Save/Restore Test\\n",
+ "\\n",
+ " Cursor positon at 'A' was saved with ESC[s.\\n",
+ "\\033[12;39H>\\033[1;31m\\033[s""A""\\033[0m<\\033[20H",
+ "---> Hit 'e' to recall cursor position and change 'A' to 'B': \\033[K",
+ "\\033[u\\033[1;32m""B\\033[0m\\033[20H",
+ "---> Hit 'e' for next test: \\033[K",
+ NULL
+};
+if (reset) { index = 0; return 0; }
+return show_test(test, index);} {}
+ }
+ decl {////// OTHER SELF TESTS //////} {private local
+ }
+ Function {show_file(const char *filename)} {
+ comment {Show the specified file in the terminal
+} return_type void
+ } {
+ code {// If cursor floating, do a crlf first
+if (G_tty->cursor_col()!=0) G_tty->append("\\n");
+
+char s[1024];
+FILE *fp = fopen(filename, "r");
+if ( fp == NULL ) {
+ snprintf(s, sizeof(s), "\\033[1;31m%s: %s\\033[0m\\n", filename, strerror(errno));
+ s[1023] = 0;
+ G_tty->append_utf8(s);
+ return;
+}
+while ( fgets(s, sizeof(s)-1, fp) ) {
+ s[1023] = 0;
+ G_tty->append(s); // append line to display
+}
+// Last line in file? Done
+fclose(fp);
+fp = NULL;} {}
+ }
+ Function {speed_test()} {
+ comment {Run full screen random character update} return_type void
+ } {
+ code {// 50 iters of WxH random chars (no scrolling)
+
+// First, switch redraw style to none, so we can handle redraws()
+G_tty->redraw_style(Fl_Terminal::NO_REDRAW);
+
+// Clear screen, do 50 iterations of random chars
+G_tty->append("\\033[H"); // home
+int rows = G_tty->display_rows();
+int cols = G_tty->display_columns();
+for (int i=0; i<50; i++ ) {
+ for ( int row=0; row<rows; row++ ) {
+ for ( int col=0; col<cols; col++ ) {
+ char c = ' ' + (rand() % 0x50); // random ASCII characters
+ G_tty->textfgcolor_xterm(rand() % 8); // random fg uchar color for each char
+ G_tty->putchar(c, row, col);
+ }
+ }
+ G_tty->redraw();
+ Fl::wait(0.005);
+}
+// leave test with white on black
+G_tty->textfgcolor(0xd0d0d000);
+G_tty->textbgcolor(0xffffffff); // special see thru color
+// back to rate limited
+G_tty->redraw_style(Fl_Terminal::RATE_LIMITED);
+// Show the Unicode alignment page
+test_firstpage(false);} {}
+ }
+ Function {show_colorbars()} {
+ comment {Show colorbars in terminal
+} return_type void
+ } {
+ code {// If cursor floating, do a crlf first
+if (G_tty->cursor_col()!=0) G_tty->append("\\n");
+
+Fl_Color fgsave = G_tty->textfgcolor();
+Fl_Color bgsave = G_tty->textbgcolor();
+// ECR-1-1978
+// grey yellow cyan green magenta red blue
+Fl_Color bars1[] = { 0xc0c0c000, 0xbfc03700, 0x2ac0bf00, 0x25c03300, 0xbe00bb00, 0xbd051800, 0x1600ba00 };
+Fl_Color bars2[] = { 0x1600ba00, 0x11111100, 0xbe00bb00, 0x11111100, 0x2ac0bf00, 0x11111100, 0xc0c0c000 };
+Fl_Color boxes[] = { 0x05214a00, 0xffffff00, 0x32006700, 0x11111100, 0x03030300, 0x11111100, 0x1d1d1d00, 0x11111100 };
+// -I 100% wht Q+ superblack black 4% black
+// \\________________________________/
+// pluge
+const char *bar = "███████████";
+const char *box1= "██████████████";
+const char *box2= "█████████████";
+const char *pluge = "███";
+G_tty->append("\\033[2J\\033[H");
+// Print 75% bars: bars1[]
+{
+ for ( int y=0; y<16; y++ ) {
+ for ( int i=0; i<7; i++ ) {
+ G_tty->textfgcolor(bars1[i]);
+ G_tty->append(bar);
+ }
+ G_tty->append("\\n");
+ }
+}
+// Print strip: bars2[]
+{
+ for ( int y=0; y<2; y++ ) {
+ for ( int i=0; i<7; i++ ) {
+ G_tty->textfgcolor(bars2[i]);
+ G_tty->append(bar);
+ }
+ G_tty->append("\\n");
+ }
+}
+// Bottom boxes
+{
+ for ( int y=0; y<7; y++ ) {
+ for ( int i=0; i<8; i++ ) {
+ G_tty->textfgcolor(boxes[i]);
+ if ( i < 3 ) G_tty->append(box1);
+ else if ( i < 4 ) G_tty->append(box2);
+ else if ( i < 7 ) G_tty->append(pluge);
+ else G_tty->append(box2);
+ }
+ if ( y < 6 ) G_tty->append("\\n");
+ }
+}
+// Restore fg/bg colors
+G_tty->textfgcolor(fgsave );
+G_tty->textbgcolor(bgsave );} {}
+ }
+ Function {unicode_alignment()} {
+ comment {Show unicode alignment test
+} return_type void
+ } {
+ code {test_firstpage(true);} {}
+ }
+ Function {add_lines(int count)} {
+ comment {Add specified number of test lines to tty} return_type void
+ } {
+ code {// If cursor floating, do a crlf first
+if (G_tty->cursor_col()!=0) G_tty->append("\\n");
+for ( int t=0; t<count; t++ )
+ G_tty->printf("Line %04d -- AAA BBB CCC DDD\\n", G_lines++);} {}
+ }
+ Function {do_command(const char *cmd)} {
+ comment {Run the command, output appends to terminal
+} return_type void
+ } {
+ code {// Run command in pipe, return output to tty
+// TODO: This should probably be reimplemented as a thread.
+// Also, I doubt this will work well on Windows.
+// If it's a real problem, open a file and 'tail' it ourselves instead.
+//
+\#ifdef _WIN32
+\#define POPEN(a,b) _popen(a,b) // effin windows
+\#define PCLOSE(a) _pclose(a)
+\#define READ(a,b,c) _read(a,b,c)
+\#define FILENO(a) _fileno(a)
+\#define SSIZE_T int
+\#else
+\#define POPEN(a,b) popen(a,b)
+\#define PCLOSE(a) pclose(a)
+\#define READ(a,b,c) read(a,b,c)
+\#define FILENO(a) fileno(a)
+\#define SSIZE_T ssize_t
+\#endif
+
+// If cursor floating, do a crlf first
+if (G_tty->cursor_col()!=0) G_tty->append("\\n");
+
+FILE *fp = POPEN(cmd, "r");
+char s[4096];
+if ( fp == NULL ) {
+ sprintf(s, "\\033[0;31mCan't execute '%.200s': %s\\033[0m\\n", cmd, strerror(errno));
+ G_tty->append_utf8(s);
+ return;
+}
+
+int fd = FILENO(fp);
+\#ifndef _WIN32
+// Non-blocking IO keeps interface "alive" during slow commands.
+// Too bad Microsoft's implementation of the C library read() call
+// is so poor it doesn't properly support non-blocking IO or fcntl().
+//
+fcntl(fd, F_SETFL, O_NONBLOCK);
+\#endif
+
+// Read in byte blocks
+G_tty->append(NULL); // (clears utf8 cache)
+while (1) {
+ Fl::wait(0.05);
+ SSIZE_T bytes = READ(fd, s, sizeof(s)); // shout in uppercase so windows can hear us
+ if (bytes == -1 && errno == EAGAIN) continue; // no data yet
+ else if (bytes > 0) G_tty->append(s, bytes); // write block to terminal, handles utf8
+ else break; // pipe closed
+}
+
+PCLOSE(fp);
+G_tty->append_ascii("\\033[33;2m<<END_OF_OUTPUT>>\\033[0m\\n");
+G_tty->redraw();} {}
+ }
+ decl {////// GUI LAYOUT //////} {private local
+ }
+ Function {update_inputs()} {
+ comment {Resync the inputs with widget's values} return_type void
+ } {
+ code {// Scroll History
+scrollhistory_input->value( G_tty->history_rows() );
+
+// Redraw rate
+redraw_rate_spinner->value( G_tty->redraw_rate() );
+
+// Font Size
+fontsize_input->value( G_tty->textsize() );
+
+// Scrollbar Size
+scrollbarsize_input->range(0, 80);
+scrollbarsize_input->step(1);
+scrollbarsize_input->value( G_tty->scrollbar_size() );
+
+// show_unknown() enable/disable
+showunknown_radio->value( G_tty->show_unknown() ? 1 : 0 );
+
+// ansi() enable/disable
+ansi_radio->value(tty ->ansi() ? 1 : 0 );
+
+// box() choice
+switch ( G_tty->box() ) {
+ case FL_UP_FRAME: box_choice->value(0); break;
+ case FL_DOWN_FRAME: box_choice->value(1); break;
+ case FL_THIN_UP_FRAME: box_choice->value(2); break;
+ case FL_THIN_DOWN_FRAME: box_choice->value(3); break;
+ case FL_ENGRAVED_FRAME: box_choice->value(4); break;
+ case FL_EMBOSSED_FRAME: box_choice->value(5); break;
+ case FL_BORDER_FRAME: box_choice->value(6); break;
+ // ---
+ case FL_UP_BOX: box_choice->value(7); break;
+ case FL_DOWN_BOX: box_choice->value(8); break;
+ case FL_FLAT_BOX: box_choice->value(9); break;
+ case FL_THIN_UP_BOX: box_choice->value(10); break;
+ case FL_THIN_DOWN_BOX: box_choice->value(11); break;
+ case FL_BORDER_BOX: box_choice->value(12); break;
+ case FL_NO_BOX: box_choice->value(13); break;
+ default: box_choice->value(1); break;
+}
+
+// color()
+{
+ char s[80];
+ sprintf(s, "\#%08x", G_tty->color());
+ color_input->value(s);
+}
+
+// textfg/bgcolor()
+{
+ char s[80];
+ sprintf(s, "\#%08x", G_tty->textfgcolor());
+ textfgcolor_input->value(s);
+ sprintf(s, "\#%08x", G_tty->textbgcolor());
+ textbgcolor_input->value(s);
+}
+
+// cursorfg/bg color
+{
+ char s[80];
+ sprintf(s, "\#%08x", G_tty->cursorfgcolor()); cursorfgcolor_input->value(s);
+ sprintf(s, "\#%08x", G_tty->cursorbgcolor()); cursorbgcolor_input->value(s);
+}
+
+// Margins
+{
+ int lt = G_tty->margin_left();
+ int rt = G_tty->margin_right();
+ int top = G_tty->margin_top();
+ int bot = G_tty->margin_bottom();
+ char s[100];
+ sprintf(s, "%d, %d, %d, %d", lt,rt,top,bot);
+ margins_input->value(s);
+}
+
+// selectionfg/bgcolor()
+{
+ char s[80];
+ sprintf(s, "\#%08x", G_tty->selectionfgcolor());
+ selectionfgcolor_input->value(s);
+ sprintf(s, "\#%08x", G_tty->selectionbgcolor());
+ selectionbgcolor_input->value(s);
+}} {}
+ }
+ Function {make_window()} {open
+ } {
+ Fl_Window win {
+ label {Fl_Terminal Test}
+ callback {exit(0);} open selected
+ xywh {0 0 772 863} type Double visible
+ } {
+ Fl_Spinner scrollhistory_input {
+ label {Scroll History}
+ callback {int val = int(scrollhistory_input->value() + .5);
+G_tty->history_rows(val);}
+ tooltip {Scrollback history size.
+10,000 max.} xywh {115 10 115 20} labelsize 12 minimum 0 maximum 10000 textsize 12
+ }
+ Fl_Spinner fontsize_input {
+ label {Font Size}
+ callback {int val = int(fontsize_input->value() + .5);
+G_tty->textsize(val);}
+ xywh {115 38 115 20} labelsize 12 textsize 12
+ }
+ Fl_Spinner scrollbarsize_input {
+ label {Scrollbar Size}
+ callback {int val = int(scrollbarsize_input->value() + .5);
+G_tty->scrollbar_size(val); G_tty->redraw();}
+ tooltip {Size of scrollbar width in pixels} xywh {115 67 115 20} labelsize 12 textsize 12
+ }
+ Fl_Spinner redraw_rate_spinner {
+ label {Min Redraw Time}
+ callback {float rate = float(redraw_rate_spinner->value());
+//printf("Setting redraw rate to %.2f\\n", rate);
+G_tty->redraw_rate(rate);}
+ tooltip {Minimum time between redraws
+Default is 0.10 secs.} xywh {115 94 115 20} type Float labelsize 12 minimum 0.05 maximum 5 step 0.1 textsize 12
+ }
+ Fl_Choice scheme_widget {
+ label scheme open
+ tooltip Scheme xywh {115 120 115 23} down_box BORDER_BOX labelsize 12 textsize 12
+ class Fl_Scheme_Choice
+ } {}
+ Fl_Choice box_choice {
+ label {box()} open
+ tooltip {The border box for the Fl_Terminal} xywh {115 147 115 23} down_box BORDER_BOX labelsize 12 textsize 12
+ } {
+ MenuItem {} {
+ label FL_UP_FRAME
+ user_data FL_UP_FRAME user_data_type long
+ callback {G_tty->box(FL_UP_FRAME); G_tty->redraw();}
+ xywh {10 10 100 20} labelsize 8
+ }
+ MenuItem {} {
+ label FL_DOWN_FRAME
+ user_data FL_DOWN_FRAME user_data_type long
+ callback {G_tty->box(FL_DOWN_FRAME); G_tty->redraw();}
+ xywh {10 10 100 20} labelsize 8
+ }
+ MenuItem {} {
+ label FL_THIN_UP_FRAME
+ user_data FL_THIN_UP_FRAME user_data_type long
+ callback {G_tty->box(FL_THIN_UP_FRAME); G_tty->redraw();}
+ xywh {10 10 100 20} labelsize 8
+ }
+ MenuItem {} {
+ label FL_THIN_DOWN_FRAME
+ user_data FL_THIN_DOWN_FRAME user_data_type long
+ callback {G_tty->box(FL_THIN_DOWN_FRAME); G_tty->redraw();}
+ xywh {10 10 100 20} labelsize 8
+ }
+ MenuItem {} {
+ label FL_ENGRAVED_FRAME
+ user_data FL_ENGRAVED_FRAME user_data_type long
+ callback {G_tty->box(FL_ENGRAVED_FRAME); G_tty->redraw();}
+ xywh {10 10 100 20} labelsize 8
+ }
+ MenuItem {} {
+ label FL_EMBOSSED_FRAME
+ user_data FL_EMBOSSED_FRAME user_data_type long
+ callback {G_tty->box(FL_EMBOSSED_FRAME); G_tty->redraw();}
+ xywh {10 10 100 20} labelsize 8
+ }
+ MenuItem {} {
+ label FL_BORDER_FRAME
+ user_data FL_BORDER_FRAME user_data_type long
+ callback {G_tty->box(FL_BORDER_FRAME); G_tty->redraw();}
+ xywh {10 10 100 20} labelsize 8 divider
+ }
+ MenuItem {} {
+ label FL_UP_BOX
+ user_data FL_UP_BOX user_data_type long
+ callback {G_tty->box(FL_UP_BOX); G_tty->redraw();}
+ xywh {10 10 100 20} labelsize 8
+ }
+ MenuItem {} {
+ label FL_DOWN_BOX
+ callback {G_tty->box(FL_DOWN_BOX); G_tty->redraw();}
+ xywh {20 20 100 20} labelsize 8
+ }
+ MenuItem {} {
+ label FL_FLAT_BOX
+ callback {G_tty->box(FL_FLAT_BOX);
+G_tty->redraw();}
+ xywh {30 30 100 20} labelsize 8
+ }
+ MenuItem {} {
+ label FL_THIN_UP_BOX
+ callback {G_tty->box(FL_THIN_UP_BOX); G_tty->redraw();}
+ xywh {40 40 100 20} labelsize 8
+ }
+ MenuItem {} {
+ label FL_THIN_DOWN_BOX
+ callback {G_tty->box(FL_THIN_DOWN_BOX); G_tty->redraw();}
+ xywh {50 50 100 20} labelsize 8
+ }
+ MenuItem {} {
+ label FL_BORDER_BOX
+ callback {G_tty->box(FL_BORDER_BOX); G_tty->redraw();}
+ xywh {0 0 100 20} labelsize 8
+ }
+ MenuItem {} {
+ label FL_NO_BOX
+ callback {G_tty->box(FL_NO_BOX); G_tty->redraw();}
+ xywh {10 10 100 20} labelsize 8
+ }
+ }
+ Fl_Input selectionfgcolor_input {
+ label {selectionfgcolor()}
+ callback {ulong ival;
+const char *val_str = selectionfgcolor_input->value();
+if (sscanf(val_str, "\#%lx", &ival) != 1)
+ if (sscanf(val_str, "%ld", &ival) != 1) {
+ fl_alert("Illegal color value (can be e.g. '12' or '\#0c', etc)");
+ update_inputs();
+ return;
+}
+G_tty->selectionfgcolor(Fl_Color(ival));
+//DEBUG ::printf("IVAL is %08lx\\n",ival);
+G_tty->redraw();}
+ tooltip {The mouse selection foreground color.
+Can be decimal (e.g. 12) or hex (e.g. \#0c, \#0000000c, etc)} xywh {115 185 115 20} labelsize 10 when 28 textfont 4 textsize 12
+ }
+ Fl_Input selectionbgcolor_input {
+ label {selectionbgcolor()}
+ callback {ulong ival;
+const char *val_str = selectionbgcolor_input->value();
+if (sscanf(val_str, "\#%lx", &ival) != 1)
+ if (sscanf(val_str, "%ld", &ival) != 1) {
+ fl_alert("Illegal color value (can be e.g. '12' or '\#0c', etc)");
+ update_inputs();
+ return;
+}
+G_tty->selectionbgcolor(Fl_Color(ival));
+//DEBUG ::printf("IVAL is %08lx\\n",ival);
+G_tty->redraw();}
+ tooltip {The mouse selection background color.
+Can be decimal (e.g. 12) or hex (e.g. \#0c, \#0000000c, etc)} xywh {115 210 115 20} labelsize 10 when 28 textfont 4 textsize 12
+ }
+ Fl_Check_Button showunknown_radio {
+ label {show_unknown()}
+ callback {G_tty->show_unknown(showunknown_radio->value() ? true : false);
+G_tty->redraw();}
+ tooltip {Shows unknown escape sequences/unprintable chars as "¿" character} xywh {255 9 105 22} down_box DOWN_BOX labelsize 10
+ }
+ Fl_Check_Button interactivecursor_radio {
+ label {Interactive Cursor}
+ callback {bool val = interactivecursor_radio->value() ? true : false;
+G_tty->interactive_cursor(val);}
+ tooltip {Allow Up/Dn/Lt/Rt keys to move cursor
+when terminal has focus} xywh {365 9 125 22} down_box DOWN_BOX labelsize 10
+ }
+ Fl_Check_Button ansi_radio {
+ label {ansi()}
+ callback {G_tty->ansi(ansi_radio->value() ? true : false);}
+ tooltip {Handle ANSI/xterm escape sequences} xywh {255 30 95 20} down_box DOWN_BOX labelsize 10
+ }
+ Fl_Check_Button stdout_radio {
+ label {Echo tests to stdout}
+ tooltip {Also send test output to stdout} xywh {365 28 121 20} down_box DOWN_BOX labelsize 10
+ }
+ Fl_Box {} {
+ label {Lt, Rt, Top, Bot}
+ xywh {345 50 145 20} labelsize 10
+ }
+ Fl_Input margins_input {
+ label Margins
+ callback {int lt,rt,top,bot;
+const char *val = margins_input->value();
+if (sscanf(val, "%d,%d,%d,%d",&lt,&rt,&top,&bot)!=4) {
+ fl_alert("Margins: expected four comma separated integers");
+ return;
+}
+G_tty->margin_left(lt);
+G_tty->margin_right(rt);
+G_tty->margin_top(top);
+G_tty->margin_bottom(bot);
+G_tty->redraw();}
+ xywh {345 70 145 20} labelsize 12 when 28 textsize 12
+ }
+ Fl_Input color_input {
+ label {color()}
+ callback {ulong ival;
+const char *val_str = color_input->value();
+if (sscanf(val_str, "\#%lx", &ival) != 1)
+ if (sscanf(val_str, "%ld", &ival) != 1) {
+ fl_alert("Illegal color value (can be e.g. '12' or '\#0c', etc)");
+ update_inputs();
+ return;
+}
+G_tty->color(Fl_Color(ival));
+//DEBUG ::printf("IVAL is %08lx\\n",ival);
+G_tty->redraw();}
+ tooltip {The widget's background color()
+Can be decimal (e.g. 12) or hex (e.g. \#0c, \#0000000c, etc)} xywh {345 95 145 20} labelsize 12 when 28 textfont 4 textsize 12
+ }
+ Fl_Input textfgcolor_input {
+ label {textfgcolor()}
+ callback {ulong ival;
+const char *val_str = textfgcolor_input->value();
+if (sscanf(val_str, "\#%lx", &ival) != 1)
+ if (sscanf(val_str, "%ld", &ival) != 1) {
+ fl_alert("Illegal color value (can be e.g. '12' or '\#0c', etc)");
+ update_inputs();
+ return;
+}
+G_tty->textfgcolor(Fl_Color(ival));
+G_tty->textfgcolor_default(Fl_Color(ival));
+//DEBUG ::printf("IVAL is %08lx\\n",ival);
+G_tty->redraw();}
+ tooltip {The text foreground color.
+Can be decimal (e.g. 12) or hex (e.g. \#0c, \#0000000c, etc)} xywh {345 120 145 20} labelsize 12 when 28 textfont 4 textsize 12
+ }
+ Fl_Input textbgcolor_input {
+ label {textbgcolor()}
+ callback {ulong ival;
+const char *val_str = textbgcolor_input->value();
+if (sscanf(val_str, "\#%lx", &ival) != 1)
+ if (sscanf(val_str, "%ld", &ival) != 1) {
+ fl_alert("Illegal color value (can be e.g. '12' or '\#0c', etc)");
+ update_inputs();
+ return;
+}
+G_tty->textbgcolor(Fl_Color(ival));
+G_tty->textbgcolor_default(Fl_Color(ival));
+//DEBUG ::printf("IVAL is %08lx\\n",ival);
+G_tty->redraw();}
+ tooltip {The text background color.
+Can be decimal (e.g. 12) or hex (e.g. \#0c, \#0000000c, etc)} xywh {345 145 145 20} labelsize 12 when 28 textfont 4 textsize 12
+ }
+ Fl_Input cursorfgcolor_input {
+ label {cursorfgcolor()}
+ callback {ulong ival;
+const char *val_str = cursorfgcolor_input->value();
+if (sscanf(val_str, "\#%lx", &ival) != 1)
+ if (sscanf(val_str, "%ld", &ival) != 1) {
+ fl_alert("Illegal color value (can be e.g. '12' or '\#0c', etc)");
+ update_inputs();
+ return;
+}
+G_tty->cursorfgcolor(Fl_Color(ival));
+//DEBUG ::printf("IVAL is %08lx\\n",ival);
+G_tty->redraw();}
+ tooltip {Foreground color for text under the cursor.} xywh {345 170 145 20} labelsize 12 when 28 textfont 4 textsize 12
+ }
+ Fl_Input cursorbgcolor_input {
+ label {cursorbgcolor()}
+ callback {ulong ival;
+const char *val_str = cursorbgcolor_input->value();
+if (sscanf(val_str, "\#%lx", &ival) != 1)
+ if (sscanf(val_str, "%ld", &ival) != 1) {
+ fl_alert("Illegal color value (can be e.g. '12' or '\#0c', etc)");
+ update_inputs();
+ return;
+}
+G_tty->cursorbgcolor(Fl_Color(ival));
+//DEBUG ::printf("IVAL is %08lx\\n",ival);
+G_tty->redraw();}
+ tooltip {Background color for the cursor.
+This is the cursor block's color} xywh {345 195 145 20} labelsize 12 when 28 textfont 4 textsize 12
+ }
+ Fl_Choice {} {
+ label {Terminal Color}
+ xywh {345 220 145 20} down_box BORDER_BOX labelsize 12 textsize 12
+ } {
+ MenuItem {} {
+ label {White on DarkAmber}
+ callback {G_tty->color(0x30200000);
+G_tty->textfgcolor(0xd0d0d000);
+G_tty->textbgcolor(0xffffffff); // "see through" color
+update_inputs();
+add_lines(50);}
+ xywh {10 10 100 20} labelsize 9
+ }
+ MenuItem {} {
+ label {White on Black}
+ callback {G_tty->textfgcolor(0xd0d0d000);
+G_tty->textbgcolor(0xffffffff);
+G_tty->color(0x00000000);
+update_inputs();
+add_lines(50);}
+ xywh {20 20 100 20} labelsize 9
+ }
+ MenuItem {} {
+ label {Black on White}
+ callback {G_tty->textfgcolor(0x00000000);
+G_tty->textbgcolor(0xd0d0d000);
+G_tty->color(0xd0d0d000);
+update_inputs();
+add_lines(50);}
+ xywh {20 20 100 20} labelsize 9
+ }
+ MenuItem {} {
+ label {Green on Dark Green}
+ callback {G_tty->textfgcolor(0x00d04000);
+G_tty->textbgcolor(0x00200800);
+G_tty->color(0x00200800);
+update_inputs();
+add_lines(50);}
+ xywh {30 30 100 20} labelsize 9
+ }
+ MenuItem {} {
+ label {Orange on Dark Orange}
+ callback {G_tty->textfgcolor(0xd0704000);
+G_tty->textbgcolor(0x20100000);
+G_tty->color(0x20100000);
+update_inputs();
+add_lines(50);}
+ xywh {40 40 100 20} labelsize 9
+ }
+ }
+ Fl_Group {} {
+ label {Terminal Ops}
+ xywh {505 15 120 210} box ENGRAVED_FRAME labelsize 11
+ } {
+ Fl_Button {} {
+ label {Clear Screen}
+ callback {G_tty->append("\\033[H\\033[2J"); // home, cls
+G_tty->redraw();
+// Reset the 'Add +50' line counter to 1
+G_lines = 1;}
+ tooltip {Clear terminal screen.
+Moves what was on the screen to the scroll history.} xywh {520 31 90 25} labelsize 11
+ }
+ Fl_Button {} {
+ label {Clear History}
+ callback {G_tty->append("\\033[3J"); // clr history
+G_tty->redraw();}
+ tooltip {Clear scrollback history.} xywh {520 62 90 25} labelsize 11
+ }
+ Fl_Button {} {
+ label {Reset Terminal}
+ callback {G_tty->append("\\033c"); // reset terminal
+G_tty->redraw();}
+ tooltip {Reset terminal.
+Clears: screen, history, sets default tabstops, etc.} xywh {520 93 90 25} labelsize 11
+ }
+ Fl_Button {} {
+ label {Home Cursor}
+ callback {G_tty->append("\\033[H");}
+ tooltip {Moves cursor to home position (top/left) using ESC[H} xywh {520 124 90 25} labelsize 11
+ }
+ Fl_Button {} {
+ label {Speed Test}
+ callback {speed_test();}
+ tooltip {Runs a full screen random chars/colors
+Shortcut: S} xywh {520 155 90 25} shortcut 0x73 labelsize 11
+ }
+ Fl_Button {} {
+ label {Ring Debug}
+ callback {// Force scroll history to be small so we can see entire ring w/out scrolling
+if (scrollhistory_input->value() > 35) {
+ scrollhistory_input->value(35);
+ scrollhistory_input->do_callback();
+}
+
+// Show debug window
+G_tty->show_ring_debug_window();}
+ tooltip {Show the Fl_Terminal raw ring buffer contents.
+(Warning: This slows the UI and uses continuous cpu until closed)} xywh {520 187 90 25} labelsize 11
+ }
+ }
+ Fl_Group {} {
+ label {Terminal Tests}
+ xywh {640 15 120 210} box ENGRAVED_FRAME labelsize 11
+ } {
+ Fl_Button {} {
+ label {Unicode
+Alignment}
+ callback {unicode_alignment();}
+ tooltip {Show a Unicode screen alignment test
+Checks that troublesome Unicode chars don't cause misalignment} xywh {655 25 90 30} labelsize 9
+ }
+ Fl_Button {} {
+ label {+50 Lines}
+ callback {add_lines(50);}
+ tooltip {Add 50 lines of text to terminal.
+Tests screen history, scrollup} xywh {655 60 90 30} labelsize 9
+ }
+ Fl_Button {} {
+ label {Color Bars}
+ callback {show_colorbars();}
+ tooltip {Show colorbar test
+Tests API for setting colors
+Does *NOT* use ESC codes} xywh {655 95 90 30} labelsize 11
+ }
+ Fl_Box {} {
+ label {Self Tests}
+ xywh {655 167 90 15} labelsize 10
+ }
+ Fl_Button {} {
+ label {@<<}
+ callback {NextEscapeTest(-1);}
+ comment {Move to previous test}
+ tooltip {Reverse through the ESC code self tests
+Shortcut: SHIFT+E} xywh {655 185 40 30} shortcut 0x10065 labelsize 11
+ }
+ Fl_Button {} {
+ label {@>>}
+ callback {NextEscapeTest(1);}
+ comment {Move to next test}
+ tooltip {Advance through the ESC code self tests
+Shortcut: 'e'} xywh {705 185 40 30} shortcut 0x65 labelsize 11
+ }
+ }
+ Fl_Input showfile_input {
+ label {Show File}
+ callback {showfile_input->deactivate(); // causes focus loss?
+command_input->deactivate();
+win->redraw();
+Fl::wait(.01);
+show_file(showfile_input->value());
+showfile_input->activate();
+command_input->activate();
+Fl::focus(showfile_input); // return focus
+win->redraw();}
+ tooltip {Type in the pathname of a file to cat to the screen} xywh {109 250 650 25} labelsize 12 when 10 textfont 4
+ }
+ Fl_Input command_input {
+ label Command
+ callback {showfile_input->deactivate();
+command_input->deactivate(); // causes focus loss?
+win->redraw();
+Fl::wait(.01);
+do_command(command_input->value());
+showfile_input->activate();
+command_input->activate();
+Fl::focus(command_input); // return focus
+win->redraw();}
+ tooltip {Command to run.
+Hit ENTER to run command.
+The command's stdout will appear in terminal.} xywh {109 285 650 25} labelsize 12 when 12 textfont 4
+ }
+ Fl_Group tty {open
+ xywh {10 320 750 530} box DOWN_FRAME color 0
+ class MyTerminal
+ } {}
+ }
+ code {// CTOR
+G_tty = tty; // global access
+G_tty->color(0x30200000); // dark orange (makes true black easy to see)
+G_tty->textsize(12); // smaller text (RGB demo needs this)
+win->resizable(tty);
+command_input->when(FL_WHEN_ENTER_KEY_ALWAYS);
+command_input->take_focus(); // ensure command prompt has keyboard focus on startup
+
+\#ifdef _WIN32
+showfile_input->value("c:\\\\Windows\\\\system.ini");
+command_input->value("dir c:\\\\windows");
+\#else
+showfile_input->value("/etc/login.defs");
+command_input->value("ls -la --color && ping google.com -c 5");
+\#endif
+
+// Sync input vals with widget's current values
+update_inputs();} {}
+ }
+}
+
+decl {////// MAIN //////} {private local
+}
+
+Function {} {open
+} {
+ code {G_tty = NULL;
+Application app; // sets G_tty
+G_app = &app;
+app.show(argc, argv);
+// Run the "firstpage" test
+app.test_firstpage(false);} {}
+}
diff --git a/test/tree.fl b/test/tree.fl
index cb2c8803e..573641535 100644
--- a/test/tree.fl
+++ b/test/tree.fl
@@ -38,7 +38,7 @@ decl {\#include <FL/Fl_Color_Chooser.H>} {public global
decl {\#include <FL/Fl_Text_Display.H>} {public global
}
-decl {\#include <FL/Fl_Simple_Terminal.H>} {public global
+decl {\#include <FL/Fl_Terminal.H>} {public global
}
decl {int G_cb_counter = 0;} {
@@ -1749,7 +1749,7 @@ helpwin->show();}
Fl_Box tty {
label label
xywh {16 571 1014 149} box DOWN_BOX color 0
- class Fl_Simple_Terminal
+ class Fl_Terminal
}
}
code {// Initialize Tree
diff --git a/test/unittest_core.cxx b/test/unittest_core.cxx
index 2d109680f..23e7ab953 100644
--- a/test/unittest_core.cxx
+++ b/test/unittest_core.cxx
@@ -18,8 +18,9 @@
#include <FL/Fl_Group.H>
#include <FL/Fl_Button.H>
-#include <FL/Fl_Simple_Terminal.H>
+#include <FL/Fl_Terminal.H>
#include "../src/Fl_String.H"
+#include <FL/Fl_Preferences.H>
#include <FL/fl_callback_macros.H>
#include <FL/filename.H>
#include <FL/fl_utf8.h>
@@ -374,7 +375,7 @@ TEST(Fl_Callback_Macros, FL_INLINE_CALLBACK) {
*/
class Ut_Core_Test : public Fl_Group {
- Fl_Simple_Terminal *tty;
+ Fl_Terminal *tty;
bool suite_ran_;
public:
@@ -390,7 +391,7 @@ public:
tty(NULL),
suite_ran_(false)
{
- tty = new Fl_Simple_Terminal(x+4, y+4, w-8, h-8, "Unittest Log");
+ tty = new Fl_Terminal(x+4, y+4, w-8, h-8, "Unittest Log");
tty->ansi(true);
end();
Ut_Suite::tty = tty;
diff --git a/test/unittest_simple_terminal.cxx b/test/unittest_simple_terminal.cxx
deleted file mode 100644
index 9e8324926..000000000
--- a/test/unittest_simple_terminal.cxx
+++ /dev/null
@@ -1,121 +0,0 @@
-//
-// Unit tests for the Fast Light Tool Kit (FLTK).
-//
-// Copyright 1998-2022 by Bill Spitzak and others.
-//
-// This library is free software. Distribution and use rights are outlined in
-// the file "COPYING" which should have been included with this file. If this
-// file is missing or damaged, see the license at:
-//
-// https://www.fltk.org/COPYING.php
-//
-// Please see the following page on how to report bugs and issues:
-//
-// https://www.fltk.org/bugs.php
-//
-
-#include "unittests.h"
-
-#include <time.h>
-#include <FL/Fl_Group.H>
-#include <FL/Fl_Simple_Terminal.H>
-
-//
-//------- test the Fl_Simple_Terminal drawing capabilities ----------
-//
-class Ut_Simple_Terminal_Test : public Fl_Group {
- Fl_Simple_Terminal *tty1;
- Fl_Simple_Terminal *tty2;
- Fl_Simple_Terminal *tty3;
- void ansi_test_pattern(Fl_Simple_Terminal *tty) {
- tty->append("\033[30mBlack Courier 14\033[0m Normal text\n"
- "\033[31mRed Courier 14\033[0m Normal text\n"
- "\033[32mGreen Courier 14\033[0m Normal text\n"
- "\033[33mYellow Courier 14\033[0m Normal text\n"
- "\033[34mBlue Courier 14\033[0m Normal text\n"
- "\033[35mMagenta Courier 14\033[0m Normal text\n"
- "\033[36mCyan Courier 14\033[0m Normal text\n"
- "\033[37mWhite Courier 14\033[0m Normal text\n"
- "\033[40mBright Black Courier 14\033[0m Normal text\n"
- "\033[41mBright Red Courier 14\033[0m Normal text\n"
- "\033[42mBright Green Courier 14\033[0m Normal text\n"
- "\033[43mBright Yellow Courier 14\033[0m Normal text\n"
- "\033[44mBright Blue Courier 14\033[0m Normal text\n"
- "\033[45mBright Magenta Courier 14\033[0m Normal text\n"
- "\033[46mBright Cyan Courier 14\033[0m Normal text\n"
- "\033[47mBright White Courier 14\033[0m Normal text\n"
- "\n"
- "\033[31mRed\033[32mGreen\033[33mYellow\033[34mBlue\033[35mMagenta\033[36mCyan\033[37mWhite\033[0m - "
- "\033[31mX\033[32mX\033[33mX\033[34mX\033[35mX\033[36mX\033[37mX\033[0m\n"
- "\033[41mRed\033[42mGreen\033[43mYellow\033[44mBlue\033[45mMagenta\033[46mCyan\033[47mWhite\033[0m - "
- "\033[41mX\033[42mX\033[43mX\033[44mX\033[45mX\033[46mX\033[47mX\033[0m\n");
- }
- void gray_test_pattern(Fl_Simple_Terminal *tty) {
- tty->append("Grayscale Test Pattern\n"
- "--------------------------\n"
- "\033[0m 100% white Courier 14\n"
- "\033[1m 90% white Courier 14\n"
- "\033[2m 80% white Courier 14\n"
- "\033[3m 70% white Courier 14\n"
- "\033[4m 60% white Courier 14\n"
- "\033[5m 50% white Courier 14\n"
- "\033[6m 40% white Courier 14\n"
- "\033[7m 30% white Courier 14\n"
- "\033[8m 20% white Courier 14\n"
- "\033[9m 10% white Courier 14\n"
- "\033[0m");
- }
- static void date_timer_cb(void *data) {
- Fl_Simple_Terminal *tty = (Fl_Simple_Terminal*)data;
- time_t lt = time(NULL);
- tty->printf("The time and date is now: %s", ctime(&lt));
- Fl::repeat_timeout(3.0, date_timer_cb, data);
- }
-public:
- static Fl_Widget *create() {
- return new Ut_Simple_Terminal_Test(UT_TESTAREA_X, UT_TESTAREA_Y, UT_TESTAREA_W, UT_TESTAREA_H);
- }
- Ut_Simple_Terminal_Test(int x, int y, int w, int h)
- : Fl_Group(x, y, w, h) {
- static Fl_Text_Display::Style_Table_Entry my_stable[] = { // 10 entry grayscale
- // Font Color Font Face Font Size ANSI Sequence
- // ---------- ---------------- --------- -------------
- { 0xffffff00, FL_COURIER_BOLD, 14 }, // "\033[0m" 0 white 100%
- { 0xe6e6e600, FL_COURIER_BOLD, 14 }, // "\033[1m" 1 white 90%
- { 0xcccccc00, FL_COURIER_BOLD, 14 }, // "\033[2m" 2 white 80%
- { 0xb3b3b300, FL_COURIER_BOLD, 14 }, // "\033[3m" 3 white 70%
- { 0x99999900, FL_COURIER_BOLD, 14 }, // "\033[4m" 4 white 60%
- { 0x80808000, FL_COURIER_BOLD, 14 }, // "\033[5m" 5 white 50% "\033[0m"
- { 0x66666600, FL_COURIER_BOLD, 14 }, // "\033[6m" 6 white 40%
- { 0x4d4d4d00, FL_COURIER_BOLD, 14 }, // "\033[7m" 7 white 30%
- { 0x33333300, FL_COURIER_BOLD, 14 }, // "\033[8m" 8 white 20%
- { 0x1a1a1a00, FL_COURIER_BOLD, 14 }, // "\033[9m" 9 white 10%
- };
- int tty_h = (int)(h/3.5);
- int tty_y1 = y+(tty_h*0)+20;
- int tty_y2 = y+(tty_h*1)+40;
- int tty_y3 = y+(tty_h*2)+60;
-
- // TTY1
- tty1 = new Fl_Simple_Terminal(x, tty_y1, w, tty_h,"Tty 1: ANSI off");
- tty1->ansi(false);
- Fl::add_timeout(0.5, date_timer_cb, (void*)tty1);
-
- // TTY2
- tty2 = new Fl_Simple_Terminal(x, tty_y2, w, tty_h,"Tty 2: ANSI on");
- tty2->ansi(true);
- ansi_test_pattern(tty2);
- Fl::add_timeout(0.5, date_timer_cb, (void*)tty2);
-
- // TTY3
- tty3 = new Fl_Simple_Terminal(x, tty_y3, w, tty_h, "Tty 3: Grayscale Style Table");
- tty3->style_table(my_stable, sizeof(my_stable), 0);
- tty3->ansi(true);
- gray_test_pattern(tty3);
- Fl::add_timeout(0.5, date_timer_cb, (void*)tty3);
-
- end();
- }
-};
-
-UnitTest simple_terminal(UT_TEST_SIMPLE_TERMINAL, "Simple Terminal", Ut_Simple_Terminal_Test::create);
diff --git a/test/unittest_terminal.cxx b/test/unittest_terminal.cxx
new file mode 100644
index 000000000..0c6aa338b
--- /dev/null
+++ b/test/unittest_terminal.cxx
@@ -0,0 +1,98 @@
+//
+// Unit tests for the Fast Light Tool Kit (FLTK).
+//
+// Copyright 1998-2022 by Bill Spitzak and others.
+//
+// This library is free software. Distribution and use rights are outlined in
+// the file "COPYING" which should have been included with this file. If this
+// file is missing or damaged, see the license at:
+//
+// https://www.fltk.org/COPYING.php
+//
+// Please see the following page on how to report bugs and issues:
+//
+// https://www.fltk.org/bugs.php
+//
+
+#include "unittests.h"
+
+#include <time.h>
+#include <FL/Fl_Group.H>
+#include <FL/Fl_Terminal.H>
+
+//
+//------- test the Fl_Terminal drawing capabilities ----------
+//
+class Ut_Terminal_Test : public Fl_Group {
+ Fl_Terminal *tty1;
+ Fl_Terminal *tty2;
+ void ansi_test_pattern(Fl_Terminal *tty) {
+ tty->append("\033[30mBlack Courier 14\033[0m Normal text\n"
+ "\033[31mRed Courier 14\033[0m Normal text\n"
+ "\033[32mGreen Courier 14\033[0m Normal text\n"
+ "\033[33mYellow Courier 14\033[0m Normal text\n"
+ "\033[34mBlue Courier 14\033[0m Normal text\n"
+ "\033[35mMagenta Courier 14\033[0m Normal text\n"
+ "\033[36mCyan Courier 14\033[0m Normal text\n"
+ "\033[37mWhite Courier 14\033[0m Normal text\n"
+ "\033[1;30mBright Black Courier 14\033[0m Normal text\n"
+ "\033[1;31mBright Red Courier 14\033[0m Normal text\n"
+ "\033[1;32mBright Green Courier 14\033[0m Normal text\n"
+ "\033[1;33mBright Yellow Courier 14\033[0m Normal text\n"
+ "\033[1;34mBright Blue Courier 14\033[0m Normal text\n"
+ "\033[1;35mBright Magenta Courier 14\033[0m Normal text\n"
+ "\033[1;36mBright Cyan Courier 14\033[0m Normal text\n"
+ "\033[1;37mBright White Courier 14\033[0m Normal text\n"
+ "\n"
+ "\033[31mRed\033[32mGreen\033[33mYellow\033[34mBlue\033[35mMagenta\033[36mCyan\033[37mWhite\033[0m - "
+ "\033[31mX\033[32mX\033[33mX\033[34mX\033[35mX\033[36mX\033[37mX\033[0m\n"
+ "\033[1;31mRed\033[1;32mGreen\033[1;33mYellow\033[34mBlue\033[35mMagenta\033[36mCyan\033[1;37mWhite\033[1;0m - "
+ "\033[1;31mX\033[1;32mX\033[1;33mX\033[1;34mX\033[1;35mX\033[1;36mX\033[1;37mX\033[0m\n");
+ }
+ void gray_test_pattern(Fl_Terminal *tty) {
+ tty->append("Grayscale Test Pattern\n"
+ "--------------------------\n"
+ "\033[38;2;255;255;255m 100% white Courier 14\n" // ESC xterm codes for setting r;g;b colors
+ "\033[38;2;230;230;230m 90% white Courier 14\n"
+ "\033[38;2;205;205;205m 80% white Courier 14\n"
+ "\033[38;2;179;179;179m 70% white Courier 14\n"
+ "\033[38;2;154;154;154m 60% white Courier 14\n"
+ "\033[38;2;128;128;128m 50% white Courier 14\n"
+ "\033[38;2;102;102;102m 40% white Courier 14\n"
+ "\033[38;2;77;77;77m" " 30% white Courier 14\n"
+ "\033[38;2;51;51;51m" " 20% white Courier 14\n"
+ "\033[38;2;26;26;26m" " 10% white Courier 14\n"
+ "\033[38;2;0;0;0m" " 0% white Courier 14\n"
+ "\033[0m");
+ }
+ static void date_timer_cb(void *data) {
+ Fl_Terminal *tty = (Fl_Terminal*)data;
+ time_t lt = time(NULL);
+ tty->printf("The time and date is now: %s", ctime(&lt));
+ Fl::repeat_timeout(3.0, date_timer_cb, data);
+ }
+public:
+ static Fl_Widget *create() {
+ return new Ut_Terminal_Test(UT_TESTAREA_X, UT_TESTAREA_Y, UT_TESTAREA_W, UT_TESTAREA_H);
+ }
+ Ut_Terminal_Test(int x, int y, int w, int h)
+ : Fl_Group(x, y, w, h) {
+ int tty_h = (int)(h/2.25+.5);
+ int tty_y1 = y+(tty_h*0)+20;
+ int tty_y2 = y+(tty_h*1)+40;
+
+ // TTY1
+ tty1 = new Fl_Terminal(x, tty_y1, w, tty_h,"Tty 1: Colors");
+ ansi_test_pattern(tty1);
+ Fl::add_timeout(0.5, date_timer_cb, (void*)tty1);
+
+ // TTY2
+ tty2 = new Fl_Terminal(x, tty_y2, w, tty_h,"Tty 2: Grayscale");
+ gray_test_pattern(tty2);
+ Fl::add_timeout(0.5, date_timer_cb, (void*)tty2);
+
+ end();
+ }
+};
+
+UnitTest simple_terminal(UT_TEST_SIMPLE_TERMINAL, "Terminal", Ut_Terminal_Test::create);
diff --git a/test/unittests.cxx b/test/unittests.cxx
index b9103feb5..4783c223e 100644
--- a/test/unittests.cxx
+++ b/test/unittests.cxx
@@ -29,7 +29,7 @@
#include <FL/Fl_Double_Window.H>
#include <FL/Fl_Hold_Browser.H>
#include <FL/Fl_Help_View.H>
-#include <FL/Fl_Simple_Terminal.H>
+#include <FL/Fl_Terminal.H>
#include <FL/Fl_Group.H>
#include <FL/Fl_Box.H>
#include <FL/fl_draw.H> // fl_text_extents()
@@ -181,7 +181,7 @@ int Ut_Suite::num_failed_ = 0;
const char *Ut_Suite::red = "\033[31m";
const char *Ut_Suite::green = "\033[32m";
const char *Ut_Suite::normal = "\033[0m";
-Fl_Simple_Terminal *Ut_Suite::tty = NULL;
+Fl_Terminal *Ut_Suite::tty = NULL;
/** Switch the user of color escape sequnces in the log text. */
void Ut_Suite::color(int v) {
diff --git a/test/unittests.h b/test/unittests.h
index babecde64..ab55c48af 100644
--- a/test/unittests.h
+++ b/test/unittests.h
@@ -22,7 +22,7 @@
#include <stdarg.h>
-class Fl_Simple_Terminal;
+class Fl_Terminal;
// WINDOW/WIDGET SIZES
const int UT_MAINWIN_W = 700; // main window w()
@@ -153,7 +153,7 @@ public:
static const char *red;
static const char *green;
static const char *normal;
- static Fl_Simple_Terminal *tty;
+ static Fl_Terminal *tty;
};
#define UT_CONCAT_(prefix, suffix) prefix##suffix
diff --git a/test/valuators.fl b/test/valuators.fl
index a8dcff87f..8b40eaef1 100644
--- a/test/valuators.fl
+++ b/test/valuators.fl
@@ -2,7 +2,7 @@
version 1.0400
header_name {.h}
code_name {.cxx}
-decl {\#include <FL/Fl_Simple_Terminal.H>} {public global
+decl {\#include <FL/Fl_Terminal.H>} {public global
}
Function {} {open
@@ -232,9 +232,9 @@ Function {} {open
xywh {460 385 110 115} box BORDER_FRAME color 0 selection_color 0 labelsize 11 align 128
}
Fl_Box tty {selected
- xywh {10 513 560 117} color 0
+ xywh {10 513 560 117} box DOWN_BOX color 0
code0 {o->ansi(true);}
- class Fl_Simple_Terminal
+ class Fl_Terminal
}
}
}
@@ -242,7 +242,7 @@ Function {} {open
Function {callback(Fl_Widget* o, void*)} {open return_type void
} {
code {const char *name = (const char*)(o->user_data() ? o->user_data() : "???");
-tty->printf("callback(): %s value() = \\033[2m%g\\033[0m\\n",
+tty->printf("callback(): %s value() = \\033[1m%g\\033[0m\\n",
name, ((Fl_Valuator*)o)->value());} {}
}
@@ -250,6 +250,6 @@ Function {callback_spinner(Fl_Widget* o, void*)} {
comment {Spinner doesn't derive from Fl_Valuator..} open return_type void
} {
code {const char *name = (const char*)(o->user_data() ? o->user_data() : "???");
-tty->printf("callback(): %s value() = \\033[2m%g\\033[0m\\n",
+tty->printf("callback(): %s value() = \\033[1m%g\\033[0m\\n",
name, ((Fl_Spinner*)o)->value());} {}
}