From 68f07db58aab37dac7f9eb9445f5632b1b09741a Mon Sep 17 00:00:00 2001 From: Greg Ercolano Date: Tue, 17 Oct 2017 00:28:56 +0000 Subject: [PATCH] Added Fl_Simple_Terminal widget, and mods to test+example programs (STR #3411). git-svn-id: file:///fltk/svn/fltk/branches/branch-1.4@12506 ea41ed52-d2ee-0310-a9c1-e6b18d33e121 --- FL/Fl_Simple_Terminal.H | 199 +++++ documentation/Doxyfile.in | 2 +- .../src/simple-terminal-default-ansi.png | Bin 0 -> 31676 bytes examples/Makefile | 1 + examples/simple-terminal.cxx | 60 ++ src/CMakeLists.txt | 1 + src/Fl_Simple_Terminal.cxx | 747 ++++++++++++++++++ src/Makefile | 1 + test/Makefile | 2 +- test/browser.cxx | 11 +- test/file_chooser.cxx | 16 +- test/input.cxx | 16 +- test/input_choice.cxx | 13 +- test/menubar.cxx | 18 +- test/native-filechooser.cxx | 24 +- test/table.cxx | 18 +- test/tree.fl | 43 +- test/unittest_simple_terminal.cxx | 124 +++ test/unittests.cxx | 1 + 19 files changed, 1248 insertions(+), 49 deletions(-) create mode 100644 FL/Fl_Simple_Terminal.H create mode 100644 documentation/src/simple-terminal-default-ansi.png create mode 100644 examples/simple-terminal.cxx create mode 100644 src/Fl_Simple_Terminal.cxx create mode 100644 test/unittest_simple_terminal.cxx 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 0000000000000000000000000000000000000000..1c49b333706360189a4280604e564f8152bd55a2 GIT binary patch literal 31676 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 //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*) {