diff --git a/FL/Fl_Simple_Terminal.H b/FL/Fl_Simple_Terminal.H new file mode 100644 index 000000000..3ea3fb51d --- /dev/null +++ b/FL/Fl_Simple_Terminal.H @@ -0,0 +1,199 @@ +// +// "$Id$" +// +// A simple terminal widget for Fast Light Tool Kit (FLTK). +// +// Copyright 1998-2011 by Bill Spitzak and others. +// Copyright 2017 by Greg Ercolano. +// +// 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: +// +// http://www.fltk.org/COPYING.php +// +// Please report all bugs and problems on the following page: +// +// http://www.fltk.org/str.php +// + +/* \file + Fl_Simple_Terminal widget . */ + +#ifndef Fl_Simple_Terminal_H +#define Fl_Simple_Terminal_H + +#include "Fl_Export.H" +#include + +/** + This is a continuous text scroll widget for logging and debugging + output, much like a terminal. Includes printf() for appending messages, + a line limit for the screen history size, ANSI sequences to control + text color, font face, font weight and font size. + + This is useful in place of using stdout/stderr for logging messages + when no terminal is available, such as when an application is invoked + from a desktop shortcut, dock, or file browser. + + Like a regular console terminal, the vertical scrollbar 'tracks' + the bottom of the buffer as new output is added. If the user scrolls + away from the bottom, this 'tracking' feature is temporarily suspended, + so the user can browse the terminal history without fighting the scrollbar + when new text is added asynchronously. When the user returns the + scroller to the bottom of the display, the scrollbar's tracking resumes. + + Features include: + + - history_lines(int) can define a maximum size for the terminal screen history + - stay_at_bottom(bool) can be used to cause the terminal to keep scrolled to the bottom + - ansi(bool) enables ANSI sequences within the text to control text colors + - style_table() can be used to define custom color/font/weight/size combinations + + What this widget is NOT is a full terminal emulator; it does NOT + handle stdio redirection, pipes, pseudo ttys, termio character cooking, + keyboard input processing, screen addressing, random cursor positioning, + curses(3) compatibility, or VT100/xterm emulation. + + It is a simple text display widget that leverages the features of the + Fl_Text_Display base class to handle terminal-like behavior, such as + logging events or debug information. + + Example use: + \code + + #include + : + tty = new Fl_Simple_Terminal(...); + tty->ansi(true); // enable use of "\033[#m" + : + tty->printf("The time is now: \033[32m%s\033[0m", date_time_str); + + \endcode + + Example application: + \dontinclude simple-terminal.cxx + \skip //START + \until //END + + Style Tables For Color/Font/Fontsize Control + -------------------------------------------- + Internally this widget derives from Fl_Text_Display, and therefore + inherits some of its idiosyncracies. In particular, when colors + are used, the base class's concept of a 'style table' is used. + + The 'style table' is similar to a color mapped image; where each + pixel is a single value that is an index into a table of colors + to minimize per-pixel memory use. + + The style table has a similar goal; since every character in the + terminal can potentially be a different color, instead of managing + several integer attribute values per-character, a single character + for each character is used as an index into the style table, choosing + one of the available color/font/weight/size values available. + This saves on as much as 3 to 4 times the memory use, useful when + there's a large amount of text. + + When ansi() is set to 'true', ANSI sequences of the form "\033[#m" + can be used to select different colors, font faces, font weights (bold,italic..), + and font sizes, where '#' is the index number into the style table. Example: + + \code + "\033[0mThis text uses the 1st entry in the style table\n" + "\033[1mThis text uses the 2nd entry in the style table\n" + "\033[2mThis text uses the 3rd entry in the style table\n" + etc.. + \endcode + + There is a built-in style table that provides some + commonly used ANSI colors for "\033[30m" through "\033[37m" + (blk,red,grn,yel,blu,mag,cyn,wht), and a brighter version of those + colors for "\033[40" through "\033[47m". See ansi(bool) for more info. + + You can also supply a custom style table using + style_table(Style_Table_Entry*,int,int), allowing you to define + your own color/font/weight/size combinations. See that method's docs + for more info. + + All style index numbers are rounded to the size of the style table + (via modulus) to protect the style array from overruns. + +*/ +class FL_EXPORT Fl_Simple_Terminal : public Fl_Text_Display { +protected: + Fl_Text_Buffer *buf; // text buffer + Fl_Text_Buffer *sbuf; // style buffer + +private: + int history_lines_; // max lines allowed in screen history + bool stay_at_bottom_; // lets scroller chase last line in buffer + bool ansi_; // enables ANSI sequences + // scroll management + int lines; // #lines in buffer (optimization: Fl_Text_Buffer slow to calc this) + bool scrollaway; // true when user changed vscroll away from bottom + bool scrolling; // true while scroll callback active + // Fl_Text_Display vscrollbar's callback+data + Fl_Callback *orig_vscroll_cb; + void *orig_vscroll_data; + // Style table + const Fl_Text_Display::Style_Table_Entry *stable_; // the active style table + int stable_size_; // active style table size (in bytes) + int normal_style_index_; // "normal" style used by "\033[0m" reset sequence + int current_style_index_; // current style used for drawing text + +public: + Fl_Simple_Terminal(int X,int Y,int W,int H,const char *l=0); + ~Fl_Simple_Terminal(); + + // Terminal options + void stay_at_bottom(bool); + bool stay_at_bottom() const; + void history_lines(int); + int history_lines() const; + void ansi(bool val); + bool ansi() const; + void style_table(Fl_Text_Display::Style_Table_Entry *stable, int stable_size, int normal_style_index=0); + const Fl_Text_Display::Style_Table_Entry *style_table() const; + int style_table_size() const; + void normal_style_index(int); + int normal_style_index() const; + void current_style_index(int); + int current_style_index() const; + + // Terminal text management + void append(const char *s, int len=-1); + void text(const char *s, int len=-1); + const char* text() const; + void printf(const char *fmt, ...); + void vprintf(const char *fmt, va_list ap); + void clear(); + void remove_lines(int start, int count); + +private: + // Methods blocking public access to the subclass + // These are subclass methods that would give unexpected + // results if used. By making them private, we effectively + // "block" them. + // + // TODO: There are probably other Fl_Text_Display methods that + // need to be blocked. + // + void insert(const char*) { } + +public: + // Fltk + virtual void draw(); + +protected: + // Internal methods + void enforce_stay_at_bottom(); + void enforce_history_lines(); + void vscroll_cb2(Fl_Widget*, void*); + static void vscroll_cb(Fl_Widget*, void*); +}; + +#endif + +// +// End of "$Id$". +// diff --git a/documentation/Doxyfile.in b/documentation/Doxyfile.in index 0508919b4..c0f7b26bd 100644 --- a/documentation/Doxyfile.in +++ b/documentation/Doxyfile.in @@ -652,7 +652,7 @@ EXCLUDE_SYMBOLS += Fl_Native_File_Chooser_FLTK_Driver # directories that contain example code fragments that are included (see # the \include command). -EXAMPLE_PATH = ../test +EXAMPLE_PATH = ../test ../examples # If the value of the EXAMPLE_PATH tag contains directories, you can use the # EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp diff --git a/documentation/src/simple-terminal-default-ansi.png b/documentation/src/simple-terminal-default-ansi.png new file mode 100644 index 000000000..1c49b3337 Binary files /dev/null and b/documentation/src/simple-terminal-default-ansi.png differ diff --git a/examples/Makefile b/examples/Makefile index 4bd44c838..166086995 100644 --- a/examples/Makefile +++ b/examples/Makefile @@ -26,6 +26,7 @@ ALL = clipboard$(EXEEXT) \ table-spreadsheet-with-keyboard-nav$(EXEEXT) \ table-with-keynav$(EXEEXT) \ tabs-simple$(EXEEXT) \ + simple-terminal$(EXEEXT) \ textdisplay-with-colors$(EXEEXT) \ texteditor-simple$(EXEEXT) \ tree-simple$(EXEEXT) \ diff --git a/examples/simple-terminal.cxx b/examples/simple-terminal.cxx new file mode 100644 index 000000000..5e14cf7b9 --- /dev/null +++ b/examples/simple-terminal.cxx @@ -0,0 +1,60 @@ +// +// "$Id$" +// +// Simple Example app using Fl_Simple_Terminal. - erco 10/12/2017 +// +// Copyright 2017 Greg Ercolano. +// Copyright 1998-2016 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: +// +// http://www.fltk.org/COPYING.php +// +// Please report all bugs and problems on the following page: +// +// http://www.fltk.org/str.php +// + +#include //START +#include +#include +#include + +#define TERMINAL_HEIGHT 120 + +// Globals +Fl_Double_Window *G_win = 0; +Fl_Box *G_box = 0; +Fl_Simple_Terminal *G_tty = 0; + +// Append a date/time message to the terminal every 2 seconds +void tick_cb(void *data) { + time_t lt = time(NULL); + G_tty->printf("Timer tick: \033[32m%s\033[0m\n", ctime(<)); + Fl::repeat_timeout(2.0, tick_cb, data); +} + +int main(int argc, char **argv) { + G_win = new Fl_Double_Window(500, 200+TERMINAL_HEIGHT, "Your App"); + G_win->begin(); + + G_box = new Fl_Box(0, 0, G_win->w(), 200, + "Your app GUI in this area.\n\n" + "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->ansi(true); // enable use of "\033[32m" + + G_win->end(); + G_win->resizable(G_win); + G_win->show(); + Fl::add_timeout(0.5, tick_cb); + return Fl::run(); +} //END + +// +// End of "$Id$". +// diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 5c6532a59..ca6dc74be 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -59,6 +59,7 @@ set (CPPFILES Fl_Scroll.cxx Fl_Scrollbar.cxx Fl_Shared_Image.cxx + Fl_Simple_Terminal.cxx Fl_Single_Window.cxx Fl_Slider.cxx Fl_Spinner.cxx diff --git a/src/Fl_Simple_Terminal.cxx b/src/Fl_Simple_Terminal.cxx new file mode 100644 index 000000000..c10e8c832 --- /dev/null +++ b/src/Fl_Simple_Terminal.cxx @@ -0,0 +1,747 @@ +// +// "$Id$" +// +// A simple terminal widget for Fast Light Tool Kit (FLTK). +// +// Copyright 1998-2011 by Bill Spitzak and others. +// Copyright 2017 by Greg Ercolano. +// +// 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: +// +// http://www.fltk.org/COPYING.php +// +// Please report all bugs and problems on the following page: +// +// http://www.fltk.org/str.php +// + +#include /* isdigit */ +#include /* memset */ +#include /* strtol */ +#include +#include +#include +#include "flstring.h" + +#define STE_SIZE sizeof(Fl_Text_Display::Style_Table_Entry) + +// Default style table +// Simple ANSI style colors with an FL_COURIER font. +// Due to how the modulo works for 20 items, the first 10 map to 40 +// and the second 10 map to 30. +// +static const Fl_Text_Display::Style_Table_Entry builtin_stable[] = { + // FONT COLOR FONT FACE SIZE INDEX COLOR NAME ANSI ANSI MODULO INDEX + // ---------- --------------- ------ ------ -------------- -------- ----------------- + { 0x80808000, FL_COURIER, 14 }, // 0 - Bright Black \033[40m 0,20,40,.. + { 0xff000000, FL_COURIER, 14 }, // 1 - Bright Red \033[41m ^^ + { 0x00ff0000, FL_COURIER, 14 }, // 2 - Bright Green \033[42m + { 0xffff0000, FL_COURIER, 14 }, // 3 - Bright Yellow \033[43m + { 0x0000ff00, FL_COURIER, 14 }, // 4 - Bright Blue \033[44m + { 0xff00ff00, FL_COURIER, 14 }, // 5 - Bright Magenta \033[45m + { 0x00ffff00, FL_COURIER, 14 }, // 6 - Bright Cyan \033[46m + { 0xffffff00, FL_COURIER, 14 }, // 7 - Bright White \033[47m + { 0x00000000, FL_COURIER, 14 }, // 8 - x + { 0x00000000, FL_COURIER, 14 }, // 9 - x + { 0x00000000, FL_COURIER, 14 }, // 10 - Medium Black \033[30m 10,30,50,.. + { 0xbb000000, FL_COURIER, 14 }, // 11 - Medium Red \033[31m ^^ + { 0x00bb0000, FL_COURIER, 14 }, // 12 - Medium Green \033[32m + { 0xbbbb0000, FL_COURIER, 14 }, // 13 - Medium Yellow \033[33m + { 0x0000cc00, FL_COURIER, 14 }, // 14 - Medium Blue \033[34m + { 0xbb00bb00, FL_COURIER, 14 }, // 15 - Medium Magenta \033[35m + { 0x00bbbb00, FL_COURIER, 14 }, // 16 - Medium Cyan \033[36m + { 0xbbbbbb00, FL_COURIER, 14 }, // 17 - Medium White \033[37m (also "\033[0m" reset) + { 0x00000000, FL_COURIER, 14 }, // 18 - x + { 0x00000000, FL_COURIER, 14 } // 19 - x +}; +static const int builtin_stable_size = sizeof(builtin_stable); +static const char builtin_normal_index = 17; // the reset style index used by \033[0m + +// Count how many times character 'c' appears in string 's' +static int strcnt(const char *s, char c) { + int count = 0; + while ( *s ) { if ( *s++ == c ) ++count; } + return count; +} + +// Vertical scrollbar callback intercept +void Fl_Simple_Terminal::vscroll_cb2(Fl_Widget *w, void*) { + scrolling = 1; + orig_vscroll_cb(w, orig_vscroll_data); + scrollaway = (mVScrollBar->value() != mVScrollBar->maximum()); + scrolling = 0; +} +void Fl_Simple_Terminal::vscroll_cb(Fl_Widget *w, void *data) { + Fl_Simple_Terminal *o = (Fl_Simple_Terminal*)data; + o->vscroll_cb2(w,(void*)0); +} + +/** + Creates a new Fl_Simple_Terminal widget that can be a child of other FLTK widgets. +*/ +Fl_Simple_Terminal::Fl_Simple_Terminal(int X,int Y,int W,int H,const char *l) : Fl_Text_Display(X,Y,W,H,l) { + history_lines_ = 500; // something 'reasonable' + stay_at_bottom_ = true; + ansi_ = false; + lines = 0; // note: lines!=mNBufferLines when lines are wrapping + scrollaway = false; + scrolling = false; + // These defaults similar to typical DOS/unix terminals + textfont(FL_COURIER); + color(FL_BLACK); + textcolor(FL_WHITE); + selection_color(FL_YELLOW); // default dark blue looks bad for black background + show_cursor(true); + cursor_color(FL_GREEN); + cursor_style(Fl_Text_Display::BLOCK_CURSOR); + // Setup text buffer + buf = new Fl_Text_Buffer(); + buffer(buf); + sbuf = new Fl_Text_Buffer(); // allocate whether we use it or not + // XXX: We use WRAP_AT_BOUNDS to prevent the hscrollbar from /always/ + // being present, an annoying UI bug in Fl_Text_Display. + wrap_mode(Fl_Text_Display::WRAP_AT_BOUNDS, 0); + // Style table + stable_ = &builtin_stable[0]; + stable_size_ = builtin_stable_size; + normal_style_index_ = builtin_normal_index; + current_style_index_ = builtin_normal_index; + // Intercept vertical scrolling + orig_vscroll_cb = mVScrollBar->callback(); + orig_vscroll_data = mVScrollBar->user_data(); + mVScrollBar->callback(vscroll_cb, (void*)this); +} + +/** + Destructor for this widget; removes any internal allocations + for the terminal, including text buffer, style buffer, etc. +*/ +Fl_Simple_Terminal::~Fl_Simple_Terminal() { + buffer(0); // disassociate buffer /before/ we delete it + if ( buf ) { delete buf; buf = 0; } + if ( sbuf ) { delete sbuf; sbuf = 0; } +} + +/** + Gets the current value of the stay_at_bottom(bool) flag. + + When true, the terminal tries to keep the scrollbar scrolled + to the bottom when new text is added. + + \see stay_at_bottom(bool) +*/ +bool Fl_Simple_Terminal::stay_at_bottom() const { + return stay_at_bottom_; +} + +/** + Configure the terminal to remain scrolled to the bottom when possible, + chasing the end of the buffer whenever new text is added. + + If disabled, the terminal behaves more like a text display widget; + the scrollbar does not chase the bottom of the buffer. + + If the user scrolls away from the bottom, this 'chasing' feature is + temporarily disabled. This prevents the user from having to fight + the scrollbar chasing the end of the buffer while browsing when + new text is also being added asynchronously. When the user returns the + scroller to the bottom of the display, the chasing behavior resumes. + + The default is 'true'. +*/ +void Fl_Simple_Terminal::stay_at_bottom(bool val) { + if ( stay_at_bottom_ == val ) return; // no change + stay_at_bottom_ = val; + if ( stay_at_bottom_ ) enforce_stay_at_bottom(); +} + +/** + Get the maximum number of terminal history lines last set by history_lines(int). + + -1 indicates an unlimited scroll history. + + \see history_lines(int) +*/ +int Fl_Simple_Terminal::history_lines() const { + return history_lines_; +} + +/** + Sets the maximum number of lines for the terminal history. + + The new limit value is automatically enforced on the current screen + history, truncating off any lines that exceed the new limit. + + When a limit is set, the buffer is trimmed as new text is appended, + ensuring the buffer never displays more than the specified number of lines. + + The default maximum is 500 lines. + + \param maxlines Maximum number of lines kept on the terminal buffer history. + Use -1 for an unlimited scroll history. + A value of 0 is not recommended. +*/ +void Fl_Simple_Terminal::history_lines(int maxlines) { + history_lines_ = maxlines; + enforce_history_lines(); +} + +/** + Get the state of the ANSI flag which enables/disables + the handling of ANSI sequences in text. + + When true, ANSI sequences in the text stream control color, font + and font sizes of text (e.g. "\033[41mThis is Red\033[0m"). + For more info, see ansi(bool). + + \see ansi(bool) +*/ +bool Fl_Simple_Terminal::ansi() const { + return ansi_; +} + +/** + Enable/disable support of ANSI sequences like "\033[31m", which sets the + color/font/weight/size of any text that follows. + + If enabled, ANSI sequences of the form "\033[#m" can be used to change + font color, face, and size, where '#' is an index number into the current + style table. These "escape sequences" are hidden from view. + + If disabled, the textcolor() / textfont() / textsize() methods define + the color and font for all text in the terminal. ANSI sequences are not + handled specially, and rendered as raw text. + + A built-in style table is provided, but you can configure a custom style table + using style_table(Style_Table_Entry*,int,int) for your own colors and fonts. + + The built-in style table supports these ANSI sequences: + + ANSI Sequence Color Name Font Face + Size Remarks + ------------- -------------- ---------------- ----------------------- + "\033[0m" "Normal" FL_COURIER, 14 Resets to default color/font/weight/size + "\033[30m" Medium Black FL_COURIER, 14 + "\033[31m" Medium Red FL_COURIER, 14 + "\033[32m" Medium Green FL_COURIER, 14 + "\033[33m" Medium Yellow FL_COURIER, 14 + "\033[34m" Medium Blue FL_COURIER, 14 + "\033[35m" Medium Magenta FL_COURIER, 14 + "\033[36m" Medium Cyan FL_COURIER, 14 + "\033[37m" Medium White FL_COURIER, 14 The color when "\033[0m" reset is used + "\033[40m" Bright Black FL_COURIER, 14 + "\033[41m" Bright Red FL_COURIER, 14 + "\033[42m" Bright Green FL_COURIER, 14 + "\033[43m" Bright Yellow FL_COURIER, 14 + "\033[44m" Bright Blue FL_COURIER, 14 + "\033[45m" Bright Magenta FL_COURIER, 14 + "\033[46m" Bright Cyan FL_COURIER, 14 + "\033[47m" Bright White FL_COURIER, 14 + + Here's example code demonstrating the use of ANSI codes to select + the built-in colors, and how it looks in the terminal: + + \image html simple-terminal-default-ansi.png "Fl_Simple_Terminal built-in ANSI sequences" + \image latex simple-terminal-default-ansi.png "Fl_Simple_Terminal built-in ANSI sequences" width=4cm + + \note Changing the ansi(bool) value clears the buffer and forces a redraw(). + \note Enabling ANSI mode overrides textfont(), textsize(), textcolor() + completely, which are controlled instead by current_style_index() + and the current style_table(). + \see style_table(Style_Table_Entry*,int,int), + current_style_index(), + normal_style_index() +*/ +void Fl_Simple_Terminal::ansi(bool val) { + ansi_ = val; + clear(); + if ( ansi_ ) { + highlight_data(sbuf, stable_, stable_size_/STE_SIZE, 'A', 0, 0); + } else { + // XXX: highlight_data(0,0,0,'A',0,0) can crash, so to disable + // we use sbuf + builtin_stable but /set nitems to 0/. + highlight_data(sbuf, builtin_stable, 0, 'A', 0, 0); + } + redraw(); +} + +/** + Return the current style table being used. + + This is the value last passed as the 1st argument to + style_table(Style_Table_Entry*,int,int). If no style table + was defined, the built-in style table is returned. + + ansi(bool) must be set to 'true' for the style table to be used at all. + + \see style_table(Style_Table_Entry*,int,int) +*/ +const Fl_Text_Display::Style_Table_Entry *Fl_Simple_Terminal::style_table() const { + return stable_; +} + +/** + Return the current style table's size (in bytes). + + This is the value last passed as the 2nd argument to + style_table(Style_Table_Entry*,int,int). +*/ +int Fl_Simple_Terminal::style_table_size() const { + return stable_size_; +} + +/** + Sets the style table index used by the ANSI terminal reset + sequence "\033[0m", which resets the current drawing + color/font/weight/size to "normal". + + Effective only when ansi(bool) is 'true'. + + \see ansi(bool), style_table(Style_Table_Entry*,int,int) + \note Changing this value does *not* change the current drawing color. + To change that, use current_style_index(int). +*/ +void Fl_Simple_Terminal::normal_style_index(int val) { + // Wrap index to ensure it's never larger than table + normal_style_index_ = val % (stable_size_ / STE_SIZE); +} + +/** + Gets the style table index used by the ANSI terminal reset + sequence "\033[0m". + + This is the value last set by normal_style_index(int), or as set by + the 3rd argument to style_table(Style_Table_Entry*,int,int). + + \see normal_style_index(int), ansi(bool), style_table(Style_Table_Entry*,int,int) +*/ +int Fl_Simple_Terminal::normal_style_index() const { + return normal_style_index_; +} + +/** + Set the style table index used as the current drawing + color/font/weight/size for new text. + + For example: + \code + : + tty->ansi(true); + tty->append("Some normal text.\n"); + tty->current_style_index(2); // same as "\033[2m" + tty->append("This text will be green.\n"); + tty->current_style_index(tty->normal_style_index()); // same as "\033[0m" + tty->append("Back to normal text.\n"); + : + \endcode + + This value can also be changed by an ANSI sequence like "\033[#m", + where # would be a new style index value. So if the application executes: + term->append("\033[4mTesting"), then current_style_index() + will be left set to 4. + + The index number specified should be within the number of items in the + current style table. Values larger than the table will be clamped to + the size of the table with a modulus operation. + + Effective only when ansi(bool) is 'true'. +*/ +void Fl_Simple_Terminal::current_style_index(int val) { + // Wrap index to ensure it's never larger than table + current_style_index_ = abs(val) % (stable_size_ / STE_SIZE); +} + +/** + Get the style table index used as the current drawing + color/font/weight/size for new text. + + This value is also controlled by the ANSI sequence "\033[#m", + where # would be a new style index value. So if the application executes: + term->append("\033[4mTesting"), then current_style_index() + returns 4. + + \see current_style_index(int) +*/ +int Fl_Simple_Terminal::current_style_index() const { + return current_style_index_; +} + +/** + Set a user defined style table, which controls the font colors, + faces, weights and sizes available for the terminal's text content. + + ansi(bool) must be set to 'true' for the defined style table + to be used at all. + + If 'table' and 'size' are 0, then the "built in" style table is used. + For info about the built-in colors, see ansi(bool). + + Which style table entry used for drawing depends on the value last set + by current_style_index(), or by the ANSI sequence "\033[#m", where '#' + is the index into the style table, limited to the size of the table + via modulus. + + If the index# passed via "\033[#m" is larger than the number of elements + in the table, the value is clamped via modulus. So for a 10 element table, + the following ANSI codes would all be equivalent, selecting the 5th element + in the table: "\033[5m", "\033[15m", "\033[25m", etc. This is because + 5==(15%10)==(25%10), etc. + + A special exception is made for "\033[0m", which is supposed to "reset" + the current style table to default color/font/weight/size, as last set by + \p normal_style_index, or by the API method normal_style_index(int). + + In cases like the built-in style table, where the 17th item is the + "normal" color, the 'normal_style_index' is set to 17 so that "\033[0m" + resets to that color, instead of the first element in the table. + + If you want "\033[0m" to simply pick the first element in the table, + then set 'normal_style_index' to 0. + + An example of defining a custom style table (white courier 14, red courier 14, + and white helvetica 14): + \code + int main() { + : + // Our custom style table + Fl_Text_Display::Style_Table_Entry mystyle[] = { + // Font Color Font Face Font Size Index ANSI Sequence + // ---------- ---------------- --------- ----- ------------- + { FL_WHITE, FL_COURIER_BOLD, 14 }, // 0 "\033[0m" ("default") + { FL_RED, FL_COURIER_BOLD, 14 }, // 1 "\033[1m" + { FL_WHITE, FL_HELVETICA, 14 } // 2 "\033[2m" + }; + // Create terminal, enable ANSI and our style table + tty = new Fl_Simple_Terminal(..); + tty->ansi(true); // enable ANSI codes + tty->style_table(&mystyle[0], sizeof(mystyle), 0); // use our custom style table + : + // Now write to terminal, with ANSI that uses our style table + tty->printf("\033[0mNormal Text\033[1mRed Courier Text\n"); + tty->append("\033[2mWhite Helvetica\033[0mBack to normal.\n"); + : + \endcode + + \note Changing the style table clear()s the terminal. + \note You currently can't control /background/ color of text, + a limitation of Fl_Text_Display's current implementation. + \note The caller is responsible for managing the memory of the style table. + \note Until STR#3412 is repaired, Fl_Text_Display has scrolling bug if the + style table's font size != textsize() + + \param stable - the style table, an array of structs of the type + Fl_Text_Display::Style_Table_Entry + \param stable_size - the sizeof() the style table (in bytes) + \param normal_style_index - the style table index# used when the special + ANSI sequence "\033[0m" is encountered. + Normally use 0 so that sequence selects the + first item in the table. Only use different + values if a different entry in the table + should be the default. This value should + not be larger than the number of items in + the table, or it will be clamped with a + modulus operation. +*/ +void Fl_Simple_Terminal::style_table(Fl_Text_Display::Style_Table_Entry *stable, + int stable_size, int normal_style_index) { + // Wrap index to ensure it's never larger than table + normal_style_index = abs(normal_style_index) % (stable_size/STE_SIZE); + + if ( stable_ == 0 ) { + // User wants built-in style table? + stable_ = &builtin_stable[0]; + stable_size_ = builtin_stable_size; + normal_style_index_ = builtin_normal_index; // set the index used by \033[0m + current_style_index_ = builtin_normal_index; // set the index used for drawing new text + } else { + // User supplying custom style table + stable_ = stable; + stable_size_ = stable_size; + normal_style_index_ = normal_style_index; // set the index used by \033[0m + current_style_index_ = normal_style_index; // set the index used for drawing new text + } + clear(); // don't take any chances with old style info + highlight_data(sbuf, stable_, stable_size/STE_SIZE, 'A', 0, 0); +} + +/** + Scroll to last line unless someone has manually scrolled + the vertical scrollbar away from the bottom. + + This is a protected member called automatically by the public API functions. + Only internal methods or subclasses adjusting the internal buffer directly + should need to call this. +*/ +void Fl_Simple_Terminal::enforce_stay_at_bottom() { + if ( stay_at_bottom_ && buffer() && !scrollaway ) { + scroll(mNBufferLines, 0); + } +} + +/** + Enforce 'history_lines' limit on the history buffer by trimming off + lines from the top of the buffer. + + This is a protected member called automatically by the public API functions. + Only internal methods or subclasses adjusting the internal buffer directly + should need to call this. +*/ +void Fl_Simple_Terminal::enforce_history_lines() { + if ( history_lines() > -1 && lines > history_lines() ) { + int trimlines = lines - history_lines(); + remove_lines(0, trimlines); // remove lines from top + } +} + +/** + Appends new string 's' to terminal. + + The string can contain UTF-8, crlf's, and ANSI sequences are + also supported when ansi(bool) is set to 'true'. + + \param s string to append. + + \param len optional length of string can be specified if known + to save the internals from having to call strlen() + + \see printf(), vprintf(), text(), clear() +*/ +void Fl_Simple_Terminal::append(const char *s, int len) { + // Remove ansi codes and adjust style buffer accordingly. + if ( ansi() ) { + int nstyles = stable_size_ / STE_SIZE; + if ( len < 0 ) len = strlen(s); + // New text buffer (after ansi codes parsed+removed) + char *ntm = (char*)malloc(len+1); // new text memory + char *ntp = ntm; + char *nsm = (char*)malloc(len+1); // new style memory + char *nsp = nsm; + // ANSI values + char astyle = 'A'+current_style_index_; // the running style index + const char *esc = 0; + const char *sp = s; + // Walk user's string looking for codes, modify new text/style text as needed + while ( *sp ) { + if ( *sp == 033 ) { // "\033.." + esc = sp++; + switch (*sp) { + case 0: // "\033"? stop + continue; + case '[': { // "\033[.." + ++sp; + int vals[4], tv=0, seqdone=0; + while ( *sp && !seqdone && isdigit(*sp) ) { // "\033[#;#.." + char *newsp; + long a = strtol(sp, &newsp, 10); + sp = newsp; + vals[tv++] = (a<0) ? 0 : a; // prevent negative values + if ( tv >= 4 ) // too many #'s specified? abort sequence + { seqdone = 1; sp = esc+1; continue; } + switch(*sp) { + case ';': // numeric separator + ++sp; + continue; + case 'J': // erase in display + switch (vals[0]) { + case 0: // \033[0J -- clear to eol + // unsupported + break; + case 1: // \033[1J -- clear to sol + // unsupported + break; + case 2: // \033[2J -- clear entire screen + clear(); // clear text buffer + ntp = ntm; // clear text contents accumulated so far + nsp = nsm; // clear style contents "" + break; + } + ++sp; + seqdone = 1; + continue; + case 'm': // set color + if ( tv > 0 ) { // at least one value parsed? + current_style_index_ = (vals[0] == 0) // "reset"? + ? normal_style_index_ // use normal color for "reset" + : (vals[0] % nstyles); // use user's value, wrapped to ensure not larger than table + astyle = 'A' + current_style_index_; // convert index -> style buffer char + } + ++sp; + seqdone = 1; + continue; + case '\0': // EOS in middle of sequence? + *ntp = 0; // end of text + *nsp = 0; // end of style + seqdone = 1; + continue; + default: // un-supported cmd? + seqdone = 1; + sp = esc+1; // continue parsing just past esc + break; + } // switch + } // while + } // case '[' + } // switch + } // \033 + else { + // Non-ANSI character? + if ( *sp == '\n' ) ++lines; // keep track of #lines + *ntp++ = *sp++; // pass char thru + *nsp++ = astyle; // use current style + } + } // while + *ntp = 0; + *nsp = 0; + //::printf(" RESULT: ntm='%s'\n", ntm); + //::printf(" RESULT: nsm='%s'\n", nsm); + buf->append(ntm); // new text memory + sbuf->append(nsm); // new style memory + free(ntm); + free(nsm); + } else { + // non-ansi buffer + buf->append(s); + lines += ::strcnt(s, '\n'); // count total line feeds in string added + } + enforce_history_lines(); + enforce_stay_at_bottom(); +} + +/** + Replaces the terminal with new text content in string 's'. + + The string can contain UTF-8, crlf's, and ANSI sequences are + also supported when ansi(bool) is set to 'true'. + + Old terminal content is completely cleared. + + \param s string to append. + + \param len optional length of string can be specified if known + to save the internals from having to call strlen() + + \see append(), printf(), vprintf(), clear() + +*/ +void Fl_Simple_Terminal::text(const char *s, int len) { + clear(); + append(s, len); +} + +/** + Returns entire text content of the terminal as a single string. + + This includes the screen history, as well as the visible + onscreen content. +*/ +const char* Fl_Simple_Terminal::text() const { + return buf->text(); +} + +/** + Appends printf formatted messages to the terminal. + + The string can contain UTF-8, crlf's, and ANSI sequences are + also supported when ansi(bool) is set to 'true'. + + Example: + \code + #include + int main(..) { + : + // Create a simple terminal, and append some messages to it + Fl_Simple_Terminal *tty = new Fl_Simple_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 See Fl_Text_Buffer::vprintf() for limitations. + \param[in] fmt is a printf format string for the message text. +*/ +void Fl_Simple_Terminal::printf(const char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + Fl_Simple_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. + \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_Simple_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); + enforce_history_lines(); +} + +/** + Clears the terminal's screen and history. Cursor moves to top of window. +*/ +void Fl_Simple_Terminal::clear() { + buf->text(""); + sbuf->text(""); + lines = 0; +} + +/** + Remove the specified range of lines from the terminal, starting + with line 'start' and removing 'count' lines. + + This method is used to enforce the history limit. + + \param start -- starting line to remove + \param count -- number of lines to remove +*/ +void Fl_Simple_Terminal::remove_lines(int start, int count) { + int spos = skip_lines(0, start, true); + int epos = skip_lines(spos, count, true); + if ( ansi() ) { + buf->remove(spos, epos); + sbuf->remove(spos, epos); + } else { + buf->remove(spos, epos); + } + lines -= count; + if ( lines < 0 ) lines = 0; +} + +/** + Draws the widget, including a cursor at the end of the buffer. + This is needed since currently Fl_Text_Display doesn't provide + a reliable way to always do this. +*/ +void Fl_Simple_Terminal::draw() { + // XXX: To do this right, we have to steal some of Fl_Text_Display's internal + // magic numbers to do it right, e.g. LEFT_MARGIN, RIGHT_MARGIN.. :/ + // +#define LEFT_MARGIN 3 +#define RIGHT_MARGIN 3 + int buflen = buf->length(); + // Force cursor to EOF so it doesn't draw at user's last left-click + insert_position(buflen); + // Let widget draw itself + Fl_Text_Display::draw(); + // Now draw cursor at the end of the buffer + fl_push_clip(text_area.x-LEFT_MARGIN, + text_area.y, + text_area.w+LEFT_MARGIN+RIGHT_MARGIN, + text_area.h); + int X = 0, Y = 0; + if (position_to_xy(buflen, &X, &Y)) draw_cursor(X, Y); + fl_pop_clip(); +} diff --git a/src/Makefile b/src/Makefile index 5803413b3..b943f813c 100644 --- a/src/Makefile +++ b/src/Makefile @@ -78,6 +78,7 @@ CPPFILES = \ Fl_Scroll.cxx \ Fl_Scrollbar.cxx \ Fl_Shared_Image.cxx \ + Fl_Simple_Terminal.cxx \ Fl_Single_Window.cxx \ Fl_Slider.cxx \ Fl_Spinner.cxx \ diff --git a/test/Makefile b/test/Makefile index 5a9c5d16d..803094340 100644 --- a/test/Makefile +++ b/test/Makefile @@ -286,7 +286,7 @@ unittests$(EXEEXT): unittests.o unittests.o: unittests.cxx unittest_about.cxx unittest_points.cxx unittest_lines.cxx unittest_circles.cxx \ unittest_rects.cxx unittest_text.cxx unittest_symbol.cxx unittest_viewport.cxx unittest_images.cxx \ - unittest_schemes.cxx + unittest_schemes.cxx unittest_simple_terminal.cxx adjuster$(EXEEXT): adjuster.o diff --git a/test/browser.cxx b/test/browser.cxx index 8a303ce4b..48edd302b 100644 --- a/test/browser.cxx +++ b/test/browser.cxx @@ -58,6 +58,7 @@ That was a blank line above this. #include #include #include +#include #include #include #include @@ -74,6 +75,7 @@ Fl_Button *top, Fl_Choice *btype; Fl_Choice *wtype; Fl_Int_Input *field; +Fl_Simple_Terminal *tty = 0; typedef struct { const char *name; @@ -95,7 +97,7 @@ WhenItem when_items[] = { }; void b_cb(Fl_Widget* o, void*) { - printf("callback, selection = %d, event_clicks = %d\n", + tty->printf("callback, selection = \033[31m%d\033[0m, event_clicks = \033[32m%d\033[0m\n", ((Fl_Browser*)o)->value(), Fl::event_clicks()); } @@ -154,7 +156,7 @@ int main(int argc, char **argv) { int i; if (!Fl::args(argc,argv,i)) Fl::fatal(Fl::help); const char* fname = (i < argc) ? argv[i] : "browser.cxx"; - Fl_Double_Window window(720,400,fname); + Fl_Double_Window window(720,520,fname); browser = new Fl_Select_Browser(0,0,window.w(),350,0); browser->type(FL_MULTI_BROWSER); //browser->type(FL_HOLD_BROWSER); @@ -232,6 +234,11 @@ int main(int argc, char **argv) { wtype->callback(wtype_cb); 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->history_lines(50); + tty->ansi(true); + window.resizable(browser); window.show(argc,argv); return Fl::run(); diff --git a/test/file_chooser.cxx b/test/file_chooser.cxx index 12f43f0c1..1a6b0da36 100644 --- a/test/file_chooser.cxx +++ b/test/file_chooser.cxx @@ -41,8 +41,12 @@ #include #include #include +#include #include +#define TERMINAL_HEIGHT 120 +#define TERMINAL_GREEN "\033[32m" +#define TERMINAL_NORMAL "\033[0m" // // Globals... @@ -52,6 +56,7 @@ Fl_Input *filter; Fl_File_Browser *files; Fl_File_Chooser *fc; Fl_Shared_Image *image = 0; +Fl_Simple_Terminal *tty = 0; // for choosing extra groups Fl_Choice *ch_extra; @@ -101,7 +106,10 @@ main(int argc, // I - Number of command-line arguments Fl_Shared_Image::add_handler(ps_check); // Make the main window... - window = new Fl_Double_Window(400, 215, "File Chooser Test"); + window = new Fl_Double_Window(400, 215+TERMINAL_HEIGHT, "File Chooser Test"); + + tty = new Fl_Simple_Terminal(0,215,window->w(),TERMINAL_HEIGHT); + tty->ansi(true); filter = new Fl_Input(50, 10, 315, 25, "Filter:"); // Process standard arguments and find filter argument if present @@ -221,11 +229,11 @@ fc_callback(Fl_File_Chooser *fc, // I - File chooser const char *filename; // Current filename - printf("fc_callback(fc = %p, data = %p)\n", fc, data); + tty->printf("fc_callback(fc = %p, data = %p)\n", fc, data); filename = fc->value(); - printf(" filename = \"%s\"\n", filename ? filename : "(null)"); + tty->printf(" filename = \"%s\"\n", filename ? filename : "(null)"); } @@ -365,6 +373,8 @@ show_callback(void) if (!fc->value(i)) break; + tty->printf("%d/%d) %sPicked: '%s'%s\n", i, count, TERMINAL_GREEN, fc->value(i), TERMINAL_NORMAL); + fl_filename_relative(relative, sizeof(relative), fc->value(i)); files->add(relative, diff --git a/test/input.cxx b/test/input.cxx index 33f8abc81..3b8db7380 100644 --- a/test/input.cxx +++ b/test/input.cxx @@ -28,9 +28,15 @@ #include #include #include +#include + +#define TERMINAL_HEIGHT 120 + +// Globals +Fl_Simple_Terminal *G_tty = 0; void cb(Fl_Widget *ob) { - printf("Callback for %s '%s'\n",ob->label(),((Fl_Input*)ob)->value()); + G_tty->printf("Callback for %s '%s'\n",ob->label(),((Fl_Input*)ob)->value()); } int when = 0; @@ -43,11 +49,11 @@ void toggle_cb(Fl_Widget *o, long v) { void test(Fl_Input *i) { if (i->changed()) { - i->clear_changed(); printf("%s '%s'\n",i->label(),i->value()); + i->clear_changed(); G_tty->printf("%s '%s'\n",i->label(),i->value()); char utf8buf[10]; int last = fl_utf8encode(i->index(i->position()), utf8buf); utf8buf[last] = 0; - printf("Symbol at cursor position: %s\n", utf8buf); + G_tty->printf("Symbol at cursor position: %s\n", utf8buf); } } @@ -86,7 +92,8 @@ int main(int argc, char **argv) { // calling fl_contrast below will return good results Fl::args(argc, argv); Fl::get_system_colors(); - Fl_Window *window = new Fl_Window(400,420); + Fl_Window *window = new Fl_Window(400,420+TERMINAL_HEIGHT); + G_tty = new Fl_Simple_Terminal(0,420,window->w(),TERMINAL_HEIGHT); int y = 10; input[0] = new Fl_Input(70,y,300,30,"Normal:"); y += 35; @@ -153,6 +160,7 @@ int main(int argc, char **argv) { b->tooltip("Color of the text"); window->end(); + window->resizable(G_tty); window->show(argc,argv); return Fl::run(); } diff --git a/test/input_choice.cxx b/test/input_choice.cxx index b38441090..4e4d16109 100644 --- a/test/input_choice.cxx +++ b/test/input_choice.cxx @@ -20,6 +20,12 @@ #include #include #include +#include + +#define TERMINAL_HEIGHT 120 + +// Globals +Fl_Simple_Terminal *G_tty = 0; void buttcb(Fl_Widget*,void*data) { Fl_Input_Choice *in=(Fl_Input_Choice *)data; @@ -28,18 +34,19 @@ void buttcb(Fl_Widget*,void*data) { if ( flag ) in->activate(); else in->deactivate(); if (in->changed()) { - printf("Callback: changed() is set\n"); + G_tty->printf("Callback: changed() is set\n"); in->clear_changed(); } } void input_choice_cb(Fl_Widget*,void*data) { Fl_Input_Choice *in=(Fl_Input_Choice *)data; - fprintf(stderr, "Value='%s'\n", (const char*)in->value()); + G_tty->printf("Value='%s'\n", (const char*)in->value()); } int main(int argc, char **argv) { - Fl_Double_Window win(300, 200); + Fl_Double_Window win(300, 200+TERMINAL_HEIGHT); + G_tty = new Fl_Simple_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/menubar.cxx b/test/menubar.cxx index 67312bcd6..6062db889 100644 --- a/test/menubar.cxx +++ b/test/menubar.cxx @@ -30,9 +30,15 @@ #include #include "../src/flstring.h" #include +#include + +#define TERMINAL_HEIGHT 120 + +// Globals +Fl_Simple_Terminal *G_tty = 0; void window_cb(Fl_Widget* w, void*) { - puts("window callback called"); + puts("window callback called"); // end of program, so stdout instead of G_tty ((Fl_Double_Window *)w)->hide(); } @@ -40,11 +46,11 @@ void test_cb(Fl_Widget* w, void*) { Fl_Menu_* mw = (Fl_Menu_*)w; const Fl_Menu_Item* m = mw->mvalue(); if (!m) - printf("NULL\n"); + G_tty->printf("NULL\n"); else if (m->shortcut()) - printf("%s - %s\n", m->label(), fl_shortcut_label(m->shortcut())); + G_tty->printf("%s - %s\n", m->label(), fl_shortcut_label(m->shortcut())); else - printf("%s\n", m->label()); + G_tty->printf("%s\n", m->label()); } void quit_cb(Fl_Widget*, void*) {exit(0);} @@ -211,7 +217,9 @@ int main(int argc, char **argv) { sprintf(buf,"item %d",i); hugemenu[i].text = strdup(buf); } - Fl_Double_Window window(WIDTH,400); + Fl_Double_Window window(WIDTH,400+TERMINAL_HEIGHT); + G_tty = new Fl_Simple_Terminal(0,400,WIDTH,TERMINAL_HEIGHT); + window.callback(window_cb); Fl_Menu_Bar menubar(0,0,WIDTH,30); menubar.menu(menutable); menubar.callback(test_cb); diff --git a/test/native-filechooser.cxx b/test/native-filechooser.cxx index 81086047f..834ac3d44 100644 --- a/test/native-filechooser.cxx +++ b/test/native-filechooser.cxx @@ -27,10 +27,14 @@ #include #include #include +#include + +#define TERMINAL_HEIGHT 120 // GLOBALS Fl_Input *G_filename = NULL; Fl_Multiline_Input *G_filter = NULL; +Fl_Simple_Terminal *G_tty = NULL; void PickFile_CB(Fl_Widget*, void*) { // Create native chooser @@ -41,13 +45,15 @@ void PickFile_CB(Fl_Widget*, void*) { native.preset_file(G_filename->value()); // Show native chooser switch ( native.show() ) { - case -1: fprintf(stderr, "ERROR: %s\n", native.errmsg()); break; // ERROR - case 1: fprintf(stderr, "*** CANCEL\n"); fl_beep(); break; // CANCEL + case -1: G_tty->printf("ERROR: %s\n", native.errmsg()); break; // ERROR + case 1: G_tty->printf("*** CANCEL\n"); fl_beep(); break; // CANCEL default: // PICKED FILE if ( native.filename() ) { G_filename->value(native.filename()); + G_tty->printf("filename='%s'\n", native.filename()); } else { G_filename->value("NULL"); + G_tty->printf("filename='(null)'\n"); } break; } @@ -61,13 +67,15 @@ void PickDir_CB(Fl_Widget*, void*) { native.type(Fl_Native_File_Chooser::BROWSE_DIRECTORY); // Show native chooser switch ( native.show() ) { - case -1: fprintf(stderr, "ERROR: %s\n", native.errmsg()); break; // ERROR - case 1: fprintf(stderr, "*** CANCEL\n"); fl_beep(); break; // CANCEL + case -1: G_tty->printf("ERROR: %s\n", native.errmsg()); break; // ERROR + case 1: G_tty->printf("*** CANCEL\n"); fl_beep(); break; // CANCEL default: // PICKED DIR if ( native.filename() ) { G_filename->value(native.filename()); + G_tty->printf("dirname='%s'\n", native.filename()); } else { G_filename->value("NULL"); + G_tty->printf("dirname='(null)'\n"); } break; } @@ -91,10 +99,12 @@ int main(int argc, char **argv) { argn++; #endif - Fl_Window *win = new Fl_Window(640, 400, "Native File Chooser Test"); + Fl_Window *win = new Fl_Window(640, 400+TERMINAL_HEIGHT, "Native File Chooser Test"); win->size_range(win->w(), win->h(), 0, 0); win->begin(); { + G_tty = new Fl_Simple_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"); G_filename->value(argc <= argn ? "." : argv[argn]); @@ -131,10 +141,10 @@ int main(int argc, char **argv) { " Apps<Ctrl-I>*.app\n" "\n"); - Fl_Button *but = new Fl_Button(win->w()-x-10, win->h()-25-10, 80, 25, "Pick File"); + Fl_Button *but = new Fl_Button(win->w()-x-10, win->h()-TERMINAL_HEIGHT-25-10, 80, 25, "Pick File"); but->callback(PickFile_CB); - Fl_Button *butdir = new Fl_Button(but->x()-x-10, win->h()-25-10, 80, 25, "Pick Dir"); + Fl_Button *butdir = new Fl_Button(but->x()-x-10, win->h()-TERMINAL_HEIGHT-25-10, 80, 25, "Pick Dir"); butdir->callback(PickDir_CB); win->resizable(G_filter); diff --git a/test/table.cxx b/test/table.cxx index c36713ef5..2890895f8 100644 --- a/test/table.cxx +++ b/test/table.cxx @@ -16,6 +16,12 @@ #include #include #include +#include + +#define TERMINAL_HEIGHT 120 + +// Globals +Fl_Simple_Terminal *G_tty = 0; // Simple demonstration class to derive from Fl_Table_Row class DemoTable : public Fl_Table_Row @@ -99,7 +105,7 @@ void DemoTable::draw_cell(TableContext context, } case CONTEXT_TABLE: - fprintf(stderr, "TABLE CONTEXT CALLED\n"); + G_tty->printf("TABLE CONTEXT CALLED\n"); return; case CONTEXT_ENDPAGE: @@ -121,9 +127,9 @@ void DemoTable::event_callback2() int R = callback_row(), C = callback_col(); TableContext context = callback_context(); - printf("'%s' callback: ", (label() ? label() : "?")); - printf("Row=%d Col=%d Context=%d Event=%d InteractiveResize? %d\n", - R, C, (int)context, (int)Fl::event(), (int)is_interactive_resize()); + const char *name = label() ? label() : "?"; + G_tty->printf("'%s' callback: Row=%d Col=%d Context=%d Event=%d InteractiveResize? %d\n", + name, R, C, (int)context, (int)Fl::event(), (int)is_interactive_resize()); } // GLOBAL TABLE WIDGET @@ -339,7 +345,9 @@ Fl_Menu_Item type_choices[] = { int main(int argc, char **argv) { - Fl_Window win(900, 730); + Fl_Window win(900, 730+TERMINAL_HEIGHT); + + G_tty = new Fl_Simple_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/tree.fl b/test/tree.fl index fcd342ef8..919393f73 100644 --- a/test/tree.fl +++ b/test/tree.fl @@ -38,6 +38,9 @@ decl {\#include } {public global decl {\#include } {public global } +decl {\#include } {public global +} + decl {int G_cb_counter = 0;} { comment {// Global callback event counter} private local } @@ -74,7 +77,7 @@ height += 10; if ( height > 50 ) height = 20; cw->resize(cw->x(), cw->y(), cw->w(), height); tree->redraw(); // adjusted height -fprintf(stderr, "'%s' button pushed (height=%d)\\n", w->label(), height);} {} +tty->printf("'%s' button pushed (height=%d)\\n", w->label(), height);} {} } Function {AssignUserIcons()} { @@ -144,8 +147,7 @@ for ( Fl_Tree_Item *item = tree->first(); item; item=item->next()) { item->userdeicon(0); } } -tree->redraw();} {selected - } +tree->redraw();} {} } Function {RebuildTree()} { @@ -360,7 +362,7 @@ Function {} {open } { Fl_Window window { label tree open - xywh {600 253 1045 580} type Double hide + xywh {539 25 1045 730} type Double visible } { Fl_Group tree { label Tree @@ -368,7 +370,7 @@ Function {} {open callback {G_cb_counter++; // Increment callback counter whenever tree callback is invoked Fl_Tree_Item *item = tree->callback_item(); if ( item ) { - fprintf(stderr, "TREE CALLBACK: label='%s' userdata=%ld reason=%s, changed=%d", + tty->printf("TREE CALLBACK: label='%s' userdata=%ld reason=%s, changed=%d", item->label(), (long)(fl_intptr_t)tree->user_data(), reason_as_name(tree->callback_reason()), @@ -377,12 +379,12 @@ if ( item ) { // Should only happen if reason==FL_TREE_REASON_RESELECTED. // if ( Fl::event_clicks() > 0 ) { - fprintf(stderr, ", clicks=%d\\n", (Fl::event_clicks()+1)); + tty->printf(", clicks=%d\\n", (Fl::event_clicks()+1)); } else { - fprintf(stderr, "\\n"); + tty->printf("\\n"); } } else { - fprintf(stderr, "TREE CALLBACK: reason=%s, changed=%d, item=(no item -- probably multiple items were changed at once)\\n", + tty->printf("TREE CALLBACK: reason=%s, changed=%d, item=(no item -- probably multiple items were changed at once)\\n", reason_as_name(tree->callback_reason()), tree->changed() ? 1 : 0); } @@ -896,7 +898,7 @@ Clears all items} xywh {570 471 95 16} labelsize 9 Fl_Button testcallbackflag_button { label {Test Callback Flag} callback {Fl_Tree_Item *root = tree->root(); -fprintf(stderr, "--- Checking docallback off\\n"); +tty->printf("--- Checking docallback off\\n"); if (!root) return; //// "OFF" TEST @@ -961,7 +963,7 @@ fl_alert("TEST COMPLETED\\n If you didn't see any error dialogs, test PASSED."); Fl_Button testrootshowself_button { label {Root Show Self} callback {Fl_Tree_Item *root = tree->root(); -fprintf(stderr, "--- Show Tree\\n"); +tty->printf("--- Show Tree\\n"); if (root) root->show_self();} tooltip {Test the root->'show_self() method to show the entire tree on stdout} xywh {570 511 95 16} labelsize 9 } @@ -1231,11 +1233,11 @@ If nothing selected, all are changed} xywh {758 174 100 16} selection_color 1 la } Fl_Button showselected_button { label {Show Selected} - callback {fprintf(stderr, "--- SELECTED ITEMS\\n"); + callback {tty->printf("--- SELECTED ITEMS\\n"); for ( Fl_Tree_Item *item = tree->first_selected_item(); item; item = tree->next_selected_item(item) ) { - fprintf(stderr, "\\t%s\\n", item->label() ? item->label() : "???"); + tty->printf("\\t%s\\n", item->label() ? item->label() : "???"); }} tooltip {Clears the selected items} xywh {864 134 95 16} labelsize 9 } @@ -1298,15 +1300,15 @@ tree->redraw();} } Fl_Button nextselected_button { label {next_selected()} - callback {printf("--- TEST next_selected():\\n"); -printf(" // Walk down the tree (forwards)\\n"); + callback {tty->printf("--- TEST next_selected():\\n"); +tty->printf(" // Walk down the tree (forwards)\\n"); for ( Fl_Tree_Item *i=tree->first_selected_item(); i; i=tree->next_selected_item(i, FL_Down) ) { - printf(" Selected item: %s\\n", i->label()?i->label():""); + tty->printf(" Selected item: %s\\n", i->label()?i->label():""); } -printf(" // Walk up the tree (backwards)\\n"); +tty->printf(" // Walk up the tree (backwards)\\n"); for ( Fl_Tree_Item *i=tree->last_selected_item(); i; i=tree->next_selected_item(i, FL_Up) ) { - printf(" Selected item: %s\\n", i->label()?i->label():""); + tty->printf(" Selected item: %s\\n", i->label()?i->label():""); }} tooltip {Tests the Fl_Tree::next_selected() function} xywh {713 239 95 16} labelsize 9 } @@ -1703,7 +1705,7 @@ if ( !helpwin ) { } helpwin->resizable(helpdisp); helpwin->show();} - tooltip {Suggestions on how to do tests} xywh {935 554 95 16} labelsize 9 + tooltip {Suggestions on how to do tests} xywh {935 545 95 16} labelsize 9 } Fl_Value_Slider tree_scrollbar_size_slider { label {Fl_Tree::scrollbar_size()} @@ -1730,6 +1732,11 @@ tree->redraw();} Fl_Box resizer_box { xywh {0 263 15 14} } + Fl_Box tty { + label label selected + xywh {16 571 1014 149} box DOWN_BOX color 0 + class Fl_Simple_Terminal + } } code {// Initialize Tree tree->root_label("ROOT"); diff --git a/test/unittest_simple_terminal.cxx b/test/unittest_simple_terminal.cxx new file mode 100644 index 000000000..8d6d44f6c --- /dev/null +++ b/test/unittest_simple_terminal.cxx @@ -0,0 +1,124 @@ +// +// "$Id$" +// +// Unit tests for the Fast Light Tool Kit (FLTK). +// +// Copyright 1998-2017 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: +// +// http://www.fltk.org/COPYING.php +// +// Please report all bugs and problems on the following page: +// +// http://www.fltk.org/str.php +// + +#include +#include +#include + +// +//------- test the Fl_Simple_Terminal drawing capabilities ---------- +// +class SimpleTerminal : public Fl_Group { + Fl_Simple_Terminal *tty1; + Fl_Simple_Terminal *tty2; + Fl_Simple_Terminal *tty3; + void AnsiTestPattern(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 GrayTestPattern(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 DateTimer_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(<)); + Fl::repeat_timeout(3.0, DateTimer_CB, data); + } +public: + static Fl_Widget *create() { + return new SimpleTerminal(TESTAREA_X, TESTAREA_Y, TESTAREA_W, TESTAREA_H); + } + SimpleTerminal(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 = (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, DateTimer_CB, (void*)tty1); + + // TTY2 + tty2 = new Fl_Simple_Terminal(x, tty_y2, w, tty_h,"Tty 2: ANSI on"); + tty2->ansi(true); + AnsiTestPattern(tty2); + Fl::add_timeout(0.5, DateTimer_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); + GrayTestPattern(tty3); + Fl::add_timeout(0.5, DateTimer_CB, (void*)tty3); + + end(); + } +}; + +UnitTest simple_terminal("simple terminal", SimpleTerminal::create); + +// +// End of "$Id$" +// diff --git a/test/unittests.cxx b/test/unittests.cxx index a5c614918..a0d06b35e 100644 --- a/test/unittests.cxx +++ b/test/unittests.cxx @@ -153,6 +153,7 @@ public: #include "unittest_viewport.cxx" #include "unittest_scrollbarsize.cxx" #include "unittest_schemes.cxx" +#include "unittest_simple_terminal.cxx" // callback whenever the browser value changes void Browser_CB(Fl_Widget*, void*) {