From 1985aefc0e502048f92b91beef87c0dfbe669fed Mon Sep 17 00:00:00 2001 From: Matthias Melcher Date: Fri, 7 Mar 2025 16:34:35 +0100 Subject: Restructuring Fluid source files. --- fluid/widgets/CodeEditor.cxx | 316 ++++++++++++++++++ fluid/widgets/CodeEditor.h | 105 ++++++ fluid/widgets/StyleParse.cxx | 328 +++++++++++++++++++ fluid/widgets/StyleParse.h | 61 ++++ fluid/widgets/custom_widgets.cxx | 311 ++++++++++++++++++ fluid/widgets/custom_widgets.h | 90 ++++++ fluid/widgets/widget_browser.cxx | 669 +++++++++++++++++++++++++++++++++++++++ fluid/widgets/widget_browser.h | 84 +++++ 8 files changed, 1964 insertions(+) create mode 100644 fluid/widgets/CodeEditor.cxx create mode 100644 fluid/widgets/CodeEditor.h create mode 100644 fluid/widgets/StyleParse.cxx create mode 100644 fluid/widgets/StyleParse.h create mode 100644 fluid/widgets/custom_widgets.cxx create mode 100644 fluid/widgets/custom_widgets.h create mode 100644 fluid/widgets/widget_browser.cxx create mode 100644 fluid/widgets/widget_browser.h (limited to 'fluid/widgets') diff --git a/fluid/widgets/CodeEditor.cxx b/fluid/widgets/CodeEditor.cxx new file mode 100644 index 000000000..ca114b577 --- /dev/null +++ b/fluid/widgets/CodeEditor.cxx @@ -0,0 +1,316 @@ +// +// Code editor widget for the Fast Light Tool Kit (FLTK). +// Syntax highlighting rewritten by erco@seriss.com 09/15/20. +// +// Copyright 1998-2025 by Bill Spitzak and others. +// +// This library is free software. Distribution and use rights are outlined in +// the file "COPYING" which should have been included with this file. If this +// file is missing or damaged, see the license at: +// +// https://www.fltk.org/COPYING.php +// +// Please see the following page on how to report bugs and issues: +// +// https://www.fltk.org/bugs.php +// + +// +// Include necessary headers... +// + +#include "widgets/CodeEditor.h" + +#include +#include +#include +#include + +// ---- CodeEditor implementation + +/** + Lookup table for all supported styles. + Every table entry describes a rendering style for the corresponding text. + */ +Fl_Text_Display::Style_Table_Entry CodeEditor::styletable[] = { // Style table + { FL_FOREGROUND_COLOR, FL_COURIER, 11 }, // A - Plain + { FL_DARK_GREEN, FL_COURIER_ITALIC, 11 }, // B - Line comments + { FL_DARK_GREEN, FL_COURIER_ITALIC, 11 }, // C - Block comments + { FL_BLUE, FL_COURIER, 11 }, // D - Strings + { FL_DARK_RED, FL_COURIER, 11 }, // E - Directives + { FL_DARK_RED, FL_COURIER_BOLD, 11 }, // F - Types + { FL_BLUE, FL_COURIER_BOLD, 11 }, // G - Keywords + { 220, /* med cyan */ FL_COURIER, 11 } // H - Single quote chars + }; + +/** + Parse text and produce style data. + \param[in] in_tbuff text buffer to parse + \param[inout] in_sbuff style buffer we modify + \param[in] in_len byte length to parse + \param[in] in_style starting style letter + */ +void CodeEditor::style_parse(const char *in_tbuff, // text buffer to parse + char *in_sbuff, // style buffer we modify + int in_len, // byte length to parse + char in_style) { // starting style letter + // Style letters: + // + // 'A' - Plain + // 'B' - Line comments // .. + // 'C' - Block comments /*..*/ + // 'D' - Strings "xxx" + // 'E' - Directives #define, #include.. + // 'F' - Types void, char.. + // 'G' - Keywords if, while.. + // 'H' - Chars 'x' + + StyleParse sp; + sp.tbuff = in_tbuff; + sp.sbuff = in_sbuff; + sp.len = in_len; + sp.style = in_style; + sp.lwhite = 1; // 1:while parsing over leading white and first char past, 0:past white + sp.col = 0; + sp.last = 0; + + // Loop through the code, updating style buffer + char c; + while ( sp.len > 0 ) { + c = sp.tbuff[0]; // current char + if ( sp.style == 'C' ) { // Started in middle of comment block? + if ( !sp.parse_block_comment() ) break; + } else if ( strncmp(sp.tbuff, "/*", 2)==0 ) { // C style comment block? + if ( !sp.parse_block_comment() ) break; + } else if ( c == '\\' ) { // Backslash escape char? + if ( !sp.parse_escape() ) break; + } else if ( strncmp(sp.tbuff, "//", 2)==0 ) { // Line comment? + if ( !sp.parse_line_comment() ) break; + } else if ( c == '"' ) { // Start of double quoted string? + if ( !sp.parse_quoted_string('"', 'D') ) break; + } else if ( c == '\'' ) { // Start of single quoted string? + if ( !sp.parse_quoted_string('\'', 'H') ) break; + } else if ( c == '#' && sp.lwhite ) { // Start of '#' directive? + if ( !sp.parse_directive() ) break; + } else if ( !sp.last && (islower(c) || c == '_') ) { // Possible C/C++ keyword? + if ( !sp.parse_keyword() ) break; + } else { // All other chars? + if ( !sp.parse_all_else() ) break; + } + } +} + +/** + Update unfinished styles. + */ +void CodeEditor::style_unfinished_cb(int, void*) { +} + +/** + Update the style buffer. + \param[in] pos insert position in text + \param[in] nInserted number of bytes inserted + \param[in] nDeleted number of bytes deleted + \param[in] cbArg pointer back to the code editor + */ +void CodeEditor::style_update(int pos, int nInserted, int nDeleted, + int /*nRestyled*/, const char * /*deletedText*/, + void *cbArg) { + CodeEditor *editor = (CodeEditor*)cbArg; + char *style, // Style data + *text; // Text data + + + // If this is just a selection change, just unselect the style buffer... + if (nInserted == 0 && nDeleted == 0) { + editor->mStyleBuffer->unselect(); + return; + } + + // Track changes in the text buffer... + if (nInserted > 0) { + // Insert characters into the style buffer... + style = new char[nInserted + 1]; + memset(style, 'A', nInserted); + style[nInserted] = '\0'; + + editor->mStyleBuffer->replace(pos, pos + nDeleted, style); + delete[] style; + } else { + // Just delete characters in the style buffer... + editor->mStyleBuffer->remove(pos, pos + nDeleted); + } + + // Select the area that was just updated to avoid unnecessary + // callbacks... + editor->mStyleBuffer->select(pos, pos + nInserted - nDeleted); + + // Reparse whole buffer, don't get cute. Maybe optimize range later + int len = editor->buffer()->length(); + text = editor->mBuffer->text_range(0, len); + style = editor->mStyleBuffer->text_range(0, len); + + style_parse(text, style, editor->mBuffer->length(), 'A'); + + editor->mStyleBuffer->replace(0, len, style); + editor->redisplay_range(0, len); + editor->redraw(); + + free(text); + free(style); +} + +/** + Find the right indentation depth after pressing the Enter key. + \param[in] e pointer back to the code editor + */ +int CodeEditor::auto_indent(int, CodeEditor* e) { + if (e->buffer()->selected()) { + e->insert_position(e->buffer()->primary_selection()->start()); + e->buffer()->remove_selection(); + } + + int pos = e->insert_position(); + int start = e->line_start(pos); + char *text = e->buffer()->text_range(start, pos); + char *ptr; + + for (ptr = text; isspace(*ptr); ptr ++) {/*empty*/} + *ptr = '\0'; + if (*text) { + // use only a single 'insert' call to avoid redraw issues + size_t n = strlen(text); + char *b = (char*)malloc(n+2); + *b = '\n'; + strcpy(b+1, text); + e->insert(b); + free(b); + } else { + e->insert("\n"); + } + e->show_insert_position(); + e->set_changed(); + if (e->when()&FL_WHEN_CHANGED) e->do_callback(FL_REASON_CHANGED); + + free(text); + + return 1; +} + +/** + Create a CodeEditor widget. + \param[in] X, Y, W, H position and size of the widget + \param[in] L optional label + */ +CodeEditor::CodeEditor(int X, int Y, int W, int H, const char *L) : + Fl_Text_Editor(X, Y, W, H, L) { + buffer(new Fl_Text_Buffer); + + char *style = new char[mBuffer->length() + 1]; + char *text = mBuffer->text(); + + memset(style, 'A', mBuffer->length()); + style[mBuffer->length()] = '\0'; + + highlight_data(new Fl_Text_Buffer(mBuffer->length()), styletable, + sizeof(styletable) / sizeof(styletable[0]), + 'A', style_unfinished_cb, this); + + style_parse(text, style, mBuffer->length(), 'A'); + + mStyleBuffer->text(style); + delete[] style; + free(text); + + mBuffer->add_modify_callback(style_update, this); + add_key_binding(FL_Enter, FL_TEXT_EDITOR_ANY_STATE, + (Fl_Text_Editor::Key_Func)auto_indent); +} + +/** + Destroy a CodeEditor widget. + */ +CodeEditor::~CodeEditor() { + Fl_Text_Buffer *buf = mStyleBuffer; + mStyleBuffer = 0; + delete buf; + + buf = mBuffer; + buffer(0); + delete buf; +} + +/** + Attempt to make the fluid code editor widget honor textsize setting. + This works by updating the fontsizes in the style table. + \param[in] s the new general height of the text font + */ +void CodeEditor::textsize(Fl_Fontsize s) { + Fl_Text_Editor::textsize(s); // call base class method + // now attempt to update our styletable to honor the new size... + int entries = sizeof(styletable) / sizeof(styletable[0]); + for(int iter = 0; iter < entries; iter++) { + styletable[iter].size = s; + } +} // textsize + +// ---- CodeViewer implementation + +/** + Create a CodeViewer widget. + \param[in] X, Y, W, H position and size of the widget + \param[in] L optional label + */ +CodeViewer::CodeViewer(int X, int Y, int W, int H, const char *L) +: CodeEditor(X, Y, W, H, L) +{ + default_key_function(kf_ignore); + remove_all_key_bindings(&key_bindings); + cursor_style(CARET_CURSOR); +} + +/** + Tricking Fl_Text_Display into using bearable colors for this specific task. + */ +void CodeViewer::draw() +{ + Fl_Color c = Fl::get_color(FL_SELECTION_COLOR); + Fl::set_color(FL_SELECTION_COLOR, fl_color_average(FL_BACKGROUND_COLOR, FL_FOREGROUND_COLOR, 0.9f)); + CodeEditor::draw(); + Fl::set_color(FL_SELECTION_COLOR, c); +} + +// ---- TextViewer implementation + +/** + Create a TextViewer widget. + \param[in] X, Y, W, H position and size of the widget + \param[in] L optional label + */ +TextViewer::TextViewer(int X, int Y, int W, int H, const char *L) +: Fl_Text_Display(X, Y, W, H, L) +{ + buffer(new Fl_Text_Buffer); +} + +/** + Avoid memory leaks. + */ +TextViewer::~TextViewer() { + Fl_Text_Buffer *buf = mBuffer; + buffer(0); + delete buf; +} + +/** + Tricking Fl_Text_Display into using bearable colors for this specific task. + */ +void TextViewer::draw() +{ + Fl_Color c = Fl::get_color(FL_SELECTION_COLOR); + Fl::set_color(FL_SELECTION_COLOR, fl_color_average(FL_BACKGROUND_COLOR, FL_FOREGROUND_COLOR, 0.9f)); + Fl_Text_Display::draw(); + Fl::set_color(FL_SELECTION_COLOR, c); +} + + diff --git a/fluid/widgets/CodeEditor.h b/fluid/widgets/CodeEditor.h new file mode 100644 index 000000000..cc720d618 --- /dev/null +++ b/fluid/widgets/CodeEditor.h @@ -0,0 +1,105 @@ +// +// Code editor widget for the Fast Light Tool Kit (FLTK). +// Syntax highlighting rewritten by erco@seriss.com 09/15/20. +// +// Copyright 1998-2025 by Bill Spitzak and others. +// +// This library is free software. Distribution and use rights are outlined in +// the file "COPYING" which should have been included with this file. If this +// file is missing or damaged, see the license at: +// +// https://www.fltk.org/COPYING.php +// +// Please see the following page on how to report bugs and issues: +// +// https://www.fltk.org/bugs.php +// + +#ifndef CodeEditor_h +#define CodeEditor_h + +// +// Include necessary headers... +// + +#include "StyleParse.h" + +#include +#include +#include + +#include +#include +#include +#include + +// ---- CodeEditor declaration + +/** + A widget derived from Fl_Text_Editor that implements C++ code highlighting. + + CodeEditor is used in Fluid whenever the user can edit C++ source + code or header text. + */ +class CodeEditor : public Fl_Text_Editor { + friend class StyleParse; + + static Fl_Text_Display::Style_Table_Entry styletable[]; + static void style_parse(const char *tbuff, char *sbuff, int len, char style); + static void style_unfinished_cb(int, void*); + static void style_update(int pos, int nInserted, int nDeleted, + int /*nRestyled*/, const char * /*deletedText*/, + void *cbArg); + static int auto_indent(int, CodeEditor* e); + +public: + CodeEditor(int X, int Y, int W, int H, const char *L=0); + ~CodeEditor(); + void textsize(Fl_Fontsize s); + + /// access to protected member get_absolute_top_line_number() + int top_line() { return get_absolute_top_line_number(); } + + /// access to protected member mTopLineNum + int scroll_row() { return mTopLineNum; } + + /// access to protected member mHorizOffset + int scroll_col() { return mHorizOffset; } +}; + +// ---- CodeViewer declaration + +/** + A widget derived from CodeEditor with highlighting for code blocks. + + This widget is used by the codeview system to show the design's + source and header code. The secondary highlighting show the text + part that corresponds to the selected widget(s). + */ +class CodeViewer : public CodeEditor { +public: + CodeViewer(int X, int Y, int W, int H, const char *L=0); + +protected: + void draw() FL_OVERRIDE; + + /// Limit event handling to viewing, not editing + int handle(int ev) FL_OVERRIDE { return Fl_Text_Display::handle(ev); } +}; + +// ---- Project File Text Viewer declaration + +/** + A text viewer with an additional highlighting color scheme. + */ +class TextViewer : public Fl_Text_Display { +public: + TextViewer(int X, int Y, int W, int H, const char *L=0); + ~TextViewer(); + void draw() FL_OVERRIDE; + + /// access to protected member get_absolute_top_line_number() + int top_line() { return get_absolute_top_line_number(); } +}; + +#endif // !CodeEditor_h diff --git a/fluid/widgets/StyleParse.cxx b/fluid/widgets/StyleParse.cxx new file mode 100644 index 000000000..b8b8ff4f0 --- /dev/null +++ b/fluid/widgets/StyleParse.cxx @@ -0,0 +1,328 @@ +// +// Syntax highlighting for the Fast Light Tool Kit (FLTK). +// +// Copyright 1998-2020 by Bill Spitzak and others. +// Copyright 2020 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: +// +// https://www.fltk.org/COPYING.php +// +// Please see the following page on how to report bugs and issues: +// +// https://www.fltk.org/bugs.php +// + +#include "StyleParse.h" + +#include +#include +#include +#include // bsearch() + +// Sorted list of C/C++ keywords... +static const char * const code_keywords[] = { + "and", + "and_eq", + "asm", + "bitand", + "bitor", + "break", + "case", + "catch", + "compl", + "continue", + "default", + "delete", + "do", + "else", + "false", + "for", + "goto", + "if", + "new", + "not", + "not_eq", + "operator", + "or", + "or_eq", + "return", + "switch", + "template", + "this", + "throw", + "true", + "try", + "while", + "xor", + "xor_eq" +}; + +// Sorted list of C/C++ types... +static const char * const code_types[] = { + "auto", + "bool", + "char", + "class", + "const", + "const_cast", + "double", + "dynamic_cast", + "enum", + "explicit", + "extern", + "float", + "friend", + "inline", + "int", + "long", + "mutable", + "namespace", + "private", + "protected", + "public", + "register", + "short", + "signed", + "sizeof", + "static", + "static_cast", + "struct", + "template", + "typedef", + "typename", + "union", + "unsigned", + "virtual", + "void", + "volatile" +}; + +// 'compare_keywords()' - Compare two keywords... +extern "C" { + static int compare_keywords(const void *a, const void *b) { + return strcmp(*((const char **)a), *((const char **)b)); + } +} + +// See if 'find' is a C/C++ keyword. +// Refer to bsearch(3) for return value. +// +static void* search_keywords(char *find) { + return bsearch(&find, code_keywords, + sizeof(code_keywords) / sizeof(code_keywords[0]), + sizeof(code_keywords[0]), compare_keywords); +} + +// See if 'find' is a C/C++ type. +// Refer to bsearch(3) for return value. +// +static void* search_types(char *find) { + return bsearch(&find, code_types, + sizeof(code_types) / sizeof(code_types[0]), + sizeof(code_types[0]), compare_keywords); +} + +// Handle style parsing over a character +// Handles updating col counter when \n encountered. +// Applies the current style, advances to next text + style char. +// Returns 0 if hit end of buffer, 1 otherwise. +// +int StyleParse::parse_over_char(int handle_crlf) { + char c = *tbuff; + + // End of line? + if ( handle_crlf ) { + if ( c == '\n' ) { + lwhite = 1; // restart leading white flag + } else { + // End of leading white? (used by #directive) + if ( !strchr(" \t", c) ) lwhite = 0; + } + } + + // Adjust and advance + // If handling crlfs, zero col on crlf. If not handling, let col continue to count past crlf + // e.g. for multiline #define's that have lines ending in backslashes. + // + col = (c=='\n') ? (handle_crlf ? 0 : col) : col+1; // column counter + tbuff++; // advance text ptr + *sbuff++ = style; // apply style & advance its ptr + if ( --len <= 0 ) return 0; // keep track of length + return 1; +} + +// Parse over white space using current style +// Returns 0 if hit end of buffer, 1 otherwise. +// +int StyleParse::parse_over_white() { + while ( len > 0 && strchr(" \t", *tbuff)) + { if ( !parse_over_char() ) return 0; } + return 1; +} + +// Parse over non-white alphabetic text +// Returns 0 if hit end of buffer, 1 otherwise. +// +int StyleParse::parse_over_alpha() { + while ( len > 0 && isalpha(*tbuff) ) + { if ( !parse_over_char() ) return 0; } + return 1; +} + +// Parse to end of line in specified style. +// Returns 0 if hit end of buffer, 1 otherwise. +// +int StyleParse::parse_to_eol(char s) { + char save = style; + style = s; + while ( *tbuff != '\n' ) + { if ( !parse_over_char() ) return 0; } + style = save; + return 1; +} + +// Parse a block comment until end of comment or buffer. +// Returns 0 if hit end of buffer, 1 otherwise. +// +int StyleParse::parse_block_comment() { + char save = style; + style = 'C'; // block comment style + while ( len > 0 ) { + if ( strncmp(tbuff, "*/", 2) == 0 ) { + if ( !parse_over_char() ) return 0; // handle '*' + if ( !parse_over_char() ) return 0; // handle '/' + break; + } + if ( !parse_over_char() ) return 0; // handle comment text + } + style = save; // revert style + return 1; +} + +// Copy keyword from tbuff -> keyword[] buffer +void StyleParse::buffer_keyword() { + char *key = keyword; + char *kend = key + sizeof(keyword) - 1; // end of buffer + for ( const char *s=tbuff; + (islower(*s) || *s=='_') && (key < kend); + *key++ = *s++ ) { } + *key = 0; // terminate +} + +// Parse over specified 'key'word in specified style 's'. +// Returns 0 if hit end of buffer, 1 otherwise. +// +int StyleParse::parse_over_key(const char *key, char s) { + char save = style; + style = s; + // Parse over the keyword while applying style to sbuff + while ( *key++ ) + { if ( !parse_over_char() ) return 0; } + last = 1; + style = save; + return 1; +} + +// Parse over angle brackets <..> in specified style. +// Returns 0 if hit end of buffer, 1 otherwise. +// +int StyleParse::parse_over_angles(char s) { + if ( *tbuff != '<' ) return 1; // not <..>, early exit + char save = style; + style = s; + // Parse over angle brackets in specified style + while ( len > 0 && *tbuff != '>' ) + { if ( !parse_over_char() ) return 0; } // parse over '<' and angle content + if ( !parse_over_char() ) return 0; // parse over trailing '>' + style = save; + return 1; +} + +// Parse line for possible keyword +// spi.keyword[] will contain parsed word. +// Returns 0 if hit end of buffer, 1 otherwise. +// +int StyleParse::parse_keyword() { + // Parse into 'keyword' buffer + buffer_keyword(); + char *key = keyword; + // C/C++ type? (void, char..) + if ( search_types(key) ) + return parse_over_key(key, 'F'); // 'type' style + // C/C++ Keyword? (switch, return..) + else if ( search_keywords(key) ) + return parse_over_key(key, 'G'); // 'keyword' style + // Not a type or keyword? Parse over it + return parse_over_key(key, style); +} + +// Style parse a quoted string, either "" or ''. +// Returns 0 if hit end of buffer, 1 otherwise. +// +int StyleParse::parse_quoted_string(char quote_char, // e.g. '"' or '\'' + char in_style) { // style for quoted text + style = in_style; // start string style + if ( !parse_over_char() ) return 0; // parse over opening quote + + // Parse until closing quote reached + char c; + while ( len > 0 ) { + c = tbuff[0]; + if ( c == quote_char ) { // Closing quote? Parse and done + if ( !parse_over_char() ) return 0; // close quote + break; + } else if ( c == '\\' ) { // Escape sequence? Parse over, continue + if ( !parse_over_char() ) return 0; // escape + if ( !parse_over_char() ) return 0; // char being escaped + continue; + } + // Keep parsing until end of buffer or closing quote.. + if ( !parse_over_char() ) return 0; + } + style = 'A'; // revert normal style + return 1; +} + +// Style parse a directive (#include, #define..) +// Returns 0 if hit end of buffer, 1 otherwise. +// +int StyleParse::parse_directive() { + style = 'E'; // start directive style + if ( !parse_over_char() ) return 0; // Parse over '#' + if ( !parse_over_white() ) return 0; // Parse over any whitespace after '#' + if ( !parse_over_alpha() ) return 0; // Parse over the directive + style = 'A'; // revert normal style + if ( !parse_over_white() ) return 0; // Parse over white after directive + if ( !parse_over_angles('D')) return 0; // #include <..> (if any) + return 1; +} + +// Style parse a line comment to end of line. +// Returns 0 if hit end of buffer, 1 otherwise. +// +int StyleParse::parse_line_comment() { + return parse_to_eol('B'); +} + +// Parse a backslash escape character sequence. +// Purposefully don't 'handle' \n, since an escaped \n should be +// a continuation of a line, such as in a multiline #directive. +// Returns 0 if hit end of buffer, 1 otherwise. +// +int StyleParse::parse_escape() { + const char no_crlf = 0; + if ( !parse_over_char(no_crlf) ) return 0; // backslash + if ( !parse_over_char(no_crlf) ) return 0; // char escaped + return 1; +} + +// Parse all other non-specific characters +// Returns 0 if hit end of buffer, 1 otherwise. +// +int StyleParse::parse_all_else() { + last = isalnum(*tbuff) || *tbuff == '_' || *tbuff == '.'; + return parse_over_char(); +} diff --git a/fluid/widgets/StyleParse.h b/fluid/widgets/StyleParse.h new file mode 100644 index 000000000..2fcc4f4db --- /dev/null +++ b/fluid/widgets/StyleParse.h @@ -0,0 +1,61 @@ +// +// Syntax highlighting for the Fast Light Tool Kit (FLTK). +// +// Copyright 1998-2020 by Bill Spitzak and others. +// Copyright 2020 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: +// +// https://www.fltk.org/COPYING.php +// +// Please see the following page on how to report bugs and issues: +// +// https://www.fltk.org/bugs.php +// + +#ifndef StyleParse_h +#define StyleParse_h + +// Class to manage style parsing, friend of CodeEditor +class StyleParse { +public: + const char *tbuff; // text buffer + char *sbuff; // style buffer + int len; // running length + char style; // current style + char lwhite; // leading white space (1=white, 0=past white) + int col; // line's column counter + char keyword[40]; // keyword parsing buffer + char last; // flag for keyword parsing + + StyleParse() { + tbuff = 0; + sbuff = 0; + len = 0; + style = 0; + lwhite = 1; + col = 0; + last = 0; + } + + // Methods to aid in parsing + int parse_over_char(int handle_crlf=1); + int parse_over_white(); + int parse_over_alpha(); + int parse_to_eol(char s); + int parse_block_comment(); // "/* text.. */" + void buffer_keyword(); + int parse_over_key(const char *key, char s); + int parse_over_angles(char s); + int parse_keyword(); // "switch" + int parse_quoted_string(char quote_char, char in_style); + // "hello", 'x' + int parse_directive(); // "#define" + int parse_line_comment(); // "// text.." + int parse_escape(); // "\'" + int parse_all_else(); // all other code +}; + +#endif // StyleParse_h diff --git a/fluid/widgets/custom_widgets.cxx b/fluid/widgets/custom_widgets.cxx new file mode 100644 index 000000000..25a630783 --- /dev/null +++ b/fluid/widgets/custom_widgets.cxx @@ -0,0 +1,311 @@ +// +// Widget type code for the Fast Light Tool Kit (FLTK). +// +// Copyright 1998-2023 by Bill Spitzak and others. +// +// This library is free software. Distribution and use rights are outlined in +// the file "COPYING" which should have been included with this file. If this +// file is missing or damaged, see the license at: +// +// https://www.fltk.org/COPYING.php +// +// Please see the following page on how to report bugs and issues: +// +// https://www.fltk.org/bugs.php +// + +#include "widgets/custom_widgets.h" + +#include "app/fluid.h" +#include "nodes/Fl_Window_Type.h" +#include "nodes/factory.h" +#include "panels/widget_panel.h" +#include "widgets/widget_browser.h" + +#include +#include +#include +#include +#include +#include +#include "../src/flstring.h" + +/** \class Widget_Bin_Button + The Widget_Bin_Button button is a button that can be used in the widget bin to + allow the user to drag and drop widgets into a window or group. This feature + makes it easy for the user to position a widget at a specific location within + the window or group. + */ + +/** + Convert mouse dragging into a drag and drop event. + */ +int Widget_Bin_Button::handle(int inEvent) +{ + int ret = 0; + switch (inEvent) { + case FL_PUSH: + Fl_Button::handle(inEvent); + return 1; // make sure that we get drag events + case FL_DRAG: + ret = Fl_Button::handle(inEvent); + if (!user_data()) + return ret; + if (!Fl::event_is_click()) { // make it a dnd event + // fake a drag outside of the widget + Fl::e_x = x()-1; + Fl_Button::handle(inEvent); + // fake a button release + Fl_Button::handle(FL_RELEASE); + // make it into a dnd event + const char *type_name = (const char*)user_data(); + Fl_Type::current_dnd = Fl_Type::current; + Fl::copy(type_name, (int)strlen(type_name)+1, 0); + Fl::dnd(); + return 1; + } + return ret; + } + return Fl_Button::handle(inEvent); +} + +/** \class Widget_Bin_Window_Button + The Widget_Bin_Window_Button button is used in the widget bin to create new + windows by dragging and dropping. When the button is dragged and dropped onto + the desktop, a new window will be created at the drop location. + */ + +/** + Convert mouse dragging into a drag and drop event. + */ +int Widget_Bin_Window_Button::handle(int inEvent) +{ + static Fl_Window *drag_win = NULL; + int ret = 0; + switch (inEvent) { + case FL_PUSH: + Fl_Button::handle(inEvent); + return 1; // make sure that we get drag events + case FL_DRAG: + ret = Fl_Button::handle(inEvent); + if (!user_data()) + return ret; + if (!Fl::event_is_click()) { + if (!drag_win) { + drag_win = new Fl_Window(0, 0, 480, 320); + drag_win->border(0); + drag_win->set_non_modal(); + } + if (drag_win) { + drag_win->position(Fl::event_x_root()+1, Fl::event_y_root()+1); + drag_win->show(); + } + // Does not work outside window: fl_cursor(FL_CURSOR_HAND); + } + return ret; + case FL_RELEASE: + if (drag_win) { + Fl::delete_widget(drag_win); + drag_win = NULL; + // create a new window here + Fl_Type *prototype = typename_to_prototype((char*)user_data()); + if (prototype) { + Fl_Type *new_type = add_new_widget_from_user(prototype, Strategy::AFTER_CURRENT); + if (new_type && new_type->is_a(ID_Window)) { + Fl_Window_Type *new_window = (Fl_Window_Type*)new_type; + Fl_Window *w = (Fl_Window *)new_window->o; + w->position(Fl::event_x_root(), Fl::event_y_root()); + } + } + widget_browser->display(Fl_Type::current); + widget_browser->rebuild(); + } + return Fl_Button::handle(inEvent); + } + return Fl_Button::handle(inEvent); +} + +/** \class Fluid_Coord_Input + The Fluid_Coord_Input widget is an input field for entering widget coordinates + and sizes. It includes basic math capabilities and allows the use of variables + in formulas. This widget is useful for specifying precise positions and + dimensions for widgets in a graphical user interface. + */ + +/** + Create an input field. + */ +Fluid_Coord_Input::Fluid_Coord_Input(int x, int y, int w, int h, const char *l) : +Fl_Input(x, y, w, h, l), +user_callback_(0L), +vars_(0L), +vars_user_data_(0L) +{ + Fl_Input::callback((Fl_Callback*)callback_handler_cb); + text("0"); +} + +void Fluid_Coord_Input::callback_handler_cb(Fluid_Coord_Input *This, void *v) { + This->callback_handler(v); +} + +void Fluid_Coord_Input::callback_handler(void *v) { + if (user_callback_) + (*user_callback_)(this, v); + // do *not* update the value to show the evaluated formula here, because the + // values of the variables have already updated after the user callback. +} + +/** + \brief Get the value of a variable. + Collects all consecutive ASCII letters into a variable name, scans the + Variable list for that name, and then calls the corresponding callback from + the Variable array. + \param s points to the first character of the variable name, must point after + the last character of the variable name when returning. + \return the integer value that was found or calculated + */ +int Fluid_Coord_Input::eval_var(uchar *&s) const { + if (!vars_) + return 0; + // find the end of the variable name + uchar *v = s; + while (isalpha(*s)) s++; + int n = (int)(s-v); + // find the variable in the list + for (Fluid_Coord_Input_Vars *vars = vars_; vars->name_; vars++) { + if (strncmp((char*)v, vars->name_, n)==0 && vars->name_[n]==0) + return vars->callback_(this, vars_user_data_); + } + return 0; +} + +/** + Evaluate a formula into an integer, recursive part. + \param s remaining text in this formula, must return a pointer to the next + character that will be interpreted. + \param prio priority of current operation + \return the value so far + */ +int Fluid_Coord_Input::eval(uchar *&s, int prio) const { + int v = 0, sgn = 1; + uchar c = *s++; + + // check for end of text + if (c==0) { s--; return sgn*v; } + + // check for unary operator + if (c=='-') { sgn = -1; c = *s++; } + else if (c=='+') { sgn = 1; c = *s++; } + + // read value, variable, or bracketed term + if (c==0) { + s--; return sgn*v; + } else if (c>='0' && c<='9') { + // numeric value + while (c>='0' && c<='9') { + v = v*10 + (c-'0'); + c = *s++; + } + } else if (isalpha(c)) { + v = eval_var(--s); + c = *s++; + } else if (c=='(') { + // opening bracket + v = eval(s, 5); + } else { + return sgn*v; // syntax error + } + if (sgn==-1) v = -v; + + // Now evaluate all following binary operators + for (;;) { + if (c==0) { + s--; + return v; + } else if (c=='+' || c=='-') { + if (prio<=4) { s--; return v; } + if (c=='+') { v += eval(s, 4); } + else if (c=='-') { v -= eval(s, 4); } + } else if (c=='*' || c=='/') { + if (prio<=3) { s--; return v; } + if (c=='*') { v *= eval(s, 3); } + else if (c=='/') { + int x = eval(s, 3); + if (x!=0) // if x is zero, don't divide + v /= x; + } + } else if (c==')') { + return v; + } else { + return v; // syntax error + } + c = *s++; + } + return v; +} + +/** + Evaluate a formula into an integer. + + The Fluid_Coord_Input widget includes a formula interpreter that allows you + to evaluate a string containing a mathematical formula and obtain the result + as an integer. The interpreter supports unary plus and minus, basic integer + math operations (such as addition, subtraction, multiplication, and division), + and brackets. It also allows you to define a list of variables by name and use + them in the formula. The interpreter does not perform error checking, so it is + assumed that the formula is entered correctly. + + \param s formula as a C string + \return the calculated value + */ +int Fluid_Coord_Input::eval(const char *s) const +{ + // duplicate the text, so we can modify it + uchar *buf = (uchar*)fl_strdup(s); + uchar *src = buf, *dst = buf; + // remove all whitespace to make the parser easier + for (;;) { + uchar c = *src++; + if (c==' ' || c=='\t') continue; + *dst++ = c; + if (c==0) break; + } + src = buf; + // now jump into the recursion + int ret = eval(src, 5); + ::free(buf); + return ret; +} + +/** + Evaluate the formula and return the result. + */ +int Fluid_Coord_Input::value() const { + return eval(text()); +} + +/** + Set the field to an integer value, replacing previous texts. + */ +void Fluid_Coord_Input::value(int v) { + char buf[32]; + fl_snprintf(buf, sizeof(buf), "%d", v); + text(buf); +} + +/** + Allow vertical mouse dragging and mouse wheel to interactively change the value. + */ +int Fluid_Coord_Input::handle(int event) { + switch (event) { + case FL_MOUSEWHEEL: + if (Fl::event_dy()) { + value( value() - Fl::event_dy() ); + set_changed(); + do_callback(FL_REASON_CHANGED); + } + return 1; + } + return Fl_Input::handle(event); +} diff --git a/fluid/widgets/custom_widgets.h b/fluid/widgets/custom_widgets.h new file mode 100644 index 000000000..875496b8e --- /dev/null +++ b/fluid/widgets/custom_widgets.h @@ -0,0 +1,90 @@ +// +// Shortcut header file for the Fast Light Tool Kit (FLTK). +// +// Copyright 1998-2023 by Bill Spitzak and others. +// +// This library is free software. Distribution and use rights are outlined in +// the file "COPYING" which should have been included with this file. If this +// file is missing or damaged, see the license at: +// +// https://www.fltk.org/COPYING.php +// +// Please see the following page on how to report bugs and issues: +// +// https://www.fltk.org/bugs.php +// + +#ifndef _FLUID_SHORTCUT_BUTTON_H +#define _FLUID_SHORTCUT_BUTTON_H + +#include +#include + +// Adding drag and drop for dragging widgets into windows. +class Widget_Bin_Button : public Fl_Button { +public: + int handle(int) FL_OVERRIDE; + Widget_Bin_Button(int X,int Y,int W,int H, const char* l = 0) : + Fl_Button(X,Y,W,H,l) { } +}; + +// Adding drag and drop functionality to drag window prototypes onto the desktop. +class Widget_Bin_Window_Button : public Fl_Button { +public: + int handle(int) FL_OVERRIDE; + Widget_Bin_Window_Button(int X,int Y,int W,int H, const char* l = 0) : + Fl_Button(X,Y,W,H,l) { } +}; + +// Callback signature for function returning the value of a variable. +typedef int (Fluid_Coord_Callback)(class Fluid_Coord_Input const *, void*); + +// Entry for a list of variables available to an input field. +// Fluid_Coord_Input::variables() expects an array of Fluid_Coord_Input_Vars +// with the last entry's name_ set to NULL. +typedef struct Fluid_Coord_Input_Vars { + const char *name_; + Fluid_Coord_Callback *callback_; +} Fluid_Coord_Input_Vars; + +// A text input widget that understands simple math. +class Fluid_Coord_Input : public Fl_Input +{ + Fl_Callback *user_callback_; + Fluid_Coord_Input_Vars *vars_; + void *vars_user_data_; + static void callback_handler_cb(Fluid_Coord_Input *This, void *v); + void callback_handler(void *v); + int eval_var(uchar *&s) const; + int eval(uchar *&s, int prio) const; + int eval(const char *s) const; + +public: + Fluid_Coord_Input(int x, int y, int w, int h, const char *l=0L); + + /** Return the text in the widget text field. */ + const char *text() const { return Fl_Input::value(); } + + /** Set the text in the text field */ + void text(const char *v) { Fl_Input::value(v); } + + int value() const; + void value(int v); + + /** Set the general callback for this widget. */ + void callback(Fl_Callback *cb) { + user_callback_ = cb; + } + + /** Set the list of the available variables + \param vars array of variables, last entry `has name_` set to `NULL` + \param user_data is forwarded to the Variable callback */ + void variables(Fluid_Coord_Input_Vars *vars, void *user_data) { + vars_ = vars; + vars_user_data_ = user_data; + } + + int handle(int) FL_OVERRIDE; +}; + +#endif diff --git a/fluid/widgets/widget_browser.cxx b/fluid/widgets/widget_browser.cxx new file mode 100644 index 000000000..9f7c8ff6c --- /dev/null +++ b/fluid/widgets/widget_browser.cxx @@ -0,0 +1,669 @@ +// +// Widget Browser code for the Fast Light Tool Kit (FLTK). +// +// Copyright 1998-2025 by Bill Spitzak and others. +// +// This library is free software. Distribution and use rights are outlined in +// the file "COPYING" which should have been included with this file. If this +// file is missing or damaged, see the license at: +// +// https://www.fltk.org/COPYING.php +// +// Please see the following page on how to report bugs and issues: +// +// https://www.fltk.org/bugs.php +// + +#include "widgets/widget_browser.h" + +#include "app/fluid.h" +#include "nodes/Fl_Widget_Type.h" +#include "rsrcs/pixmaps.h" + +#include +#include +#include + +/** + \class Widget_Browser + + A widget that displays the nodes in the widget tree. + + The Widget Browser is derived from the FLTK basic browser, extending + tree browsing functionality by using the \c depth component of the double + linked list of \c Fl_Type items. + + \see Fl_Type + */ + +// ---- global variables + +/// Global access to the widget browser. +Widget_Browser *widget_browser = NULL; + +// ---- static variables + +Fl_Color Widget_Browser::label_color = 72; +Fl_Font Widget_Browser::label_font = FL_HELVETICA; +Fl_Color Widget_Browser::class_color = FL_FOREGROUND_COLOR; +Fl_Font Widget_Browser::class_font = FL_HELVETICA_BOLD; +Fl_Color Widget_Browser::func_color = FL_FOREGROUND_COLOR; +Fl_Font Widget_Browser::func_font = FL_HELVETICA; +Fl_Color Widget_Browser::name_color = FL_FOREGROUND_COLOR; +Fl_Font Widget_Browser::name_font = FL_HELVETICA; +Fl_Color Widget_Browser::code_color = FL_FOREGROUND_COLOR; +Fl_Font Widget_Browser::code_font = FL_HELVETICA; +Fl_Color Widget_Browser::comment_color = FL_DARK_GREEN; +Fl_Font Widget_Browser::comment_font = FL_HELVETICA; + +// ---- global functions + +/** + Shortcut to have the widget browser graphics refreshed soon. + */ +void redraw_browser() { + widget_browser->redraw(); +} + +/** + Shortcut to create the widget browser. + */ +Fl_Widget *make_widget_browser(int x,int y,int w,int h) { + return (widget_browser = new Widget_Browser(x,y,w,h)); +} + +/** + Make sure that the caller is visible in the widget browser. + \param[in] caller scroll the browser in y so that caller + is visible (may be NULL) + */ +void redraw_widget_browser(Fl_Type *caller) +{ + if (caller) + widget_browser->display(caller); + widget_browser->redraw(); +} + +/** + Select or deselect a node in the widget browser. + \param[in] o (de)select this node + \param[in] v the new selection state (1=select, 0=de-select) + */ +void select(Fl_Type *o, int v) { + widget_browser->select(o,v,1); +} + +/** + Select a single node in the widget browser, deselect all others. + \param[in] o select this node + */ +void select_only(Fl_Type *o) { + widget_browser->select_only(o,1); +} + +/** + Deselect all nodes in the widget browser. + */ +void deselect() { + widget_browser->deselect(); +} + +/** + Show the selected item in the browser window. + + Make sure that the given item is visible in the browser by opening + all parent groups and moving the item into the visible space. + + \param[in] t show this item + */ +void reveal_in_browser(Fl_Type *t) { + Fl_Type *p = t->parent; + if (p) { + for (;;) { + if (p->folded_) + p->folded_ = 0; + if (!p->parent) break; + p = p->parent; + } + update_visibility_flag(p); + } + widget_browser->display(t); + widget_browser->redraw(); +} + +// ---- local functions + +/** + Copy the given string str to buffer p with no more than maxl characters. + + Add "..." if string was truncated. + + Quote characters are NOT counted. + + \param[out] p return the resulting string in this buffer, terminated with + a NUL byte + \param[in] str copy this string; utf8 aware + \param[in] maxl maximum number of letters to copy until we print + the ellipsis (...) + \param[in] quote if set, the resulting string is embedded in double quotes + \param[in] trunc_lf if set, truncates at first newline + \returns pointer to end of string (before terminating null byte). + \note the buffer p must be large enough to hold (4 * (maxl+1) + 1) bytes + or (4 * (maxl+1) + 3) bytes if quoted, e.g. "123..." because each UTF-8 + character can consist of 4 bytes, "..." adds 3 bytes, quotes '""' add two + bytes, and the terminating null byte adds another byte. + This supports Unicode code points up to U+10FFFF (standard as of 10/2016). + Sanity checks for illegal UTF-8 sequences are included. + */ +static char *copy_trunc(char *p, const char *str, int maxl, int quote, int trunc_lf) +{ + int size = 0; // truncated string size in characters + int bs; // size of UTF-8 character in bytes + if (!p) return NULL; // bad buffer + if (!str) { // no input string + if (quote) { *p++='"'; *p++='"'; } + *p = 0; + return p; + } + const char *end = str + strlen(str); // end of input string + if (quote) *p++ = '"'; // opening quote + while (size < maxl) { // maximum characters + if (*str == '\n') { + if (trunc_lf) { // handle trunc at \n + if (quote) *p++ = '"'; // closing quote + *p = 0; + return p; + } + *p++ = '\\'; *p++ = 'n'; + str++; size+=2; + continue; + } + if (!(*str & (-32))) break; // end of string (0 or control char) + bs = fl_utf8len(*str); // size of next character + if (bs <= 0) break; // some error - leave + if (str + bs > end) break; // UTF-8 sequence beyond end of string + while (bs--) *p++ = *str++; // copy that character into the buffer + size++; // count copied characters + } + if (*str && *str!='\n') { // string was truncated + strcpy(p,"..."); p += 3; + } + if (quote) *p++ = '"'; // closing quote + *p = 0; // terminating null byte + return p; +} + +// ---- Widget_Browser implementation + +/** + Create a new instance of the Widget_Browser widget. + + Fluid currently generates only one instance of this browser. If we want + to use multiple browser at some point, we need to refactor a few global + variables, i.e. Fl_Type::first and Fl_Type::last . + + \param[in] X, Y, W, H position and size of widget + \param[in] l optional label + \todo It would be nice to be able to grab one or more nodes and move them + within the hierarchy. + */ +Widget_Browser::Widget_Browser(int X,int Y,int W,int H,const char*l) : + Fl_Browser_(X,Y,W,H,l), + pushedtitle(NULL), + saved_h_scroll_(0), + saved_v_scroll_(0) +{ + type(FL_MULTI_BROWSER); + Fl_Widget::callback(callback_stub); + when(FL_WHEN_RELEASE); +} + +/** + Override the method to find the first item in the list of elements. + \return the first item + */ +void *Widget_Browser::item_first() const { + return Fl_Type::first; +} + +/** + Override the method to find the next item in the list of elements. + \param l this item + \return the next item, irregardless of tree depth, or NULL at the end + */ +void *Widget_Browser::item_next(void *l) const { + return ((Fl_Type*)l)->next; +} + +/** + Override the method to find the previous item in the list of elements. + \param l this item + \return the previous item, irregardless of tree depth, or NULL at the start + */ +void *Widget_Browser::item_prev(void *l) const { + return ((Fl_Type*)l)->prev; +} + +/** + Override the method to check if an item was selected. + \param l this item + \return 1 if selected, 0 if not + \todo what is the difference between selected and new_selected, and why do we do this? + */ +int Widget_Browser::item_selected(void *l) const { + return ((Fl_Type*)l)->new_selected; +} + +/** + Override the method to mark an item selected. + \param l this item + \param[in] v 1 if selecting, 0 if not + */ +void Widget_Browser::item_select(void *l,int v) { + ((Fl_Type*)l)->new_selected = v; +} + +/** + Override the method to return the height of an item representation in Flixels. + \param l this item + \return height in FLTK units (used to be pixels before high res screens) + */ +int Widget_Browser::item_height(void *l) const { + Fl_Type *t = (Fl_Type*)l; + if (t->visible) { + if (show_comments && t->comment()) + return textsize()*2+4; + else + return textsize()+5; + } + return 0; +} + +/** + Override the method to return the estimated height of all items. + \return height in FLTK units + */ +int Widget_Browser::incr_height() const { + return textsize() + 5 + linespacing(); +} + +/** + Draw an item in the widget browser. + + A browser line starts with a variable size space. This space directly + relates to the level of the type entry. + + If this type has the ability to store children, a triangle follows, + pointing right (closed) or pointing down (open, children shown). + + Next follows an icon that is specific to the type. This makes it easy to + spot certain types. + + Now follows some text. For classes and widgets, this is the type itself, + followed by the name of the object. Other objects show their content as + text, possibly abbreviated with an ellipsis. + + \param v v is a pointer to the actual widget type and can be cast safely + to Fl_Type + \param X,Y these give the position in window coordinates of the top left + corner of this line + */ +void Widget_Browser::item_draw(void *v, int X, int Y, int, int) const { + // cast to a more general type + Fl_Type *l = (Fl_Type *)v; + + char buf[500]; // edit buffer: large enough to hold 80 UTF-8 chars + nul + + // calculate the horizontal start position of this item + // 3 is the edge of the browser + // 13 is the width of the arrow that indicates children for the item + // 18 is the width of the icon + // 12 is the indent per level + X += 3 + 13 + 18 + l->level * 12; + + // calculate the horizontal start position and width of the separator line + int x1 = X; + int w1 = w() - x1; + + // items can contain a comment. If they do, the comment gets a second text + // line inside this browser line + int comment_incr = 0; + if (show_comments && l->comment()) { + // -- comment + copy_trunc(buf, l->comment(), 80, 0, 1); + comment_incr = textsize()-1; + if (l->new_selected) fl_color(fl_contrast(comment_color, FL_SELECTION_COLOR)); + else fl_color(comment_color); + fl_font(comment_font, textsize()-2); + fl_draw(buf, X, Y+12); + Y += comment_incr/2; + comment_incr -= comment_incr/2; + } + + if (l->new_selected) fl_color(fl_contrast(FL_FOREGROUND_COLOR,FL_SELECTION_COLOR)); + else fl_color(FL_FOREGROUND_COLOR); + + // Width=10: Draw the triangle that indicates possible children + if (l->can_have_children()) { + X = X - 18 - 13; + if (!l->next || l->next->level <= l->level) { + if (l->folded_==(l==pushedtitle)) { + // an outlined triangle to the right indicates closed item, no children + fl_loop(X,Y+7,X+5,Y+12,X+10,Y+7); + } else { + // an outlined triangle to the bottom indicates open item, no children + fl_loop(X+2,Y+2,X+7,Y+7,X+2,Y+12); + } + } else { + if (l->folded_==(l==pushedtitle)) { + // a filled triangle to the right indicates closed item, with children + fl_polygon(X,Y+7,X+5,Y+12,X+10,Y+7); + } else { + // a filled triangle to the bottom indicates open item, with children + fl_polygon(X+2,Y+2,X+7,Y+7,X+2,Y+12); + } + } + X = X + 13 + 18; + } + + // Width=18: Draw the icon associated with the type. + Fl_Pixmap *pm = pixmap[l->id()]; + if (pm) pm->draw(X-18, Y); + + // Add tags on top of the icon for locked and protected types. + switch (l->is_public()) { + case 0: lock_pixmap->draw(X - 17, Y); break; + case 2: protected_pixmap->draw(X - 17, Y); break; + } + + if ( l->is_widget() + && !l->is_a(ID_Window) + && ((Fl_Widget_Type*)l)->o + && !((Fl_Widget_Type*)l)->o->visible() + && (!l->parent || ( !l->parent->is_a(ID_Tabs) + && !l->parent->is_a(ID_Wizard) ) ) + ) + { + invisible_pixmap->draw(X - 17, Y); + } + + // Indent=12 per level: Now write the text that comes after the graphics representation + Y += comment_incr; + if (l->is_widget() || l->is_class()) { + const char* c = subclassname(l); + if (!strncmp(c,"Fl_",3)) c += 3; + // -- class + fl_font(class_font, textsize()); + if (l->new_selected) fl_color(fl_contrast(class_color, FL_SELECTION_COLOR)); + else fl_color(class_color); + fl_draw(c, X, Y+13); + X += int(fl_width(c)+fl_width('n')); + c = l->name(); + if (c) { + // -- name + fl_font(name_font, textsize()); + if (l->new_selected) fl_color(fl_contrast(name_color, FL_SELECTION_COLOR)); + else fl_color(name_color); + fl_draw(c, X, Y+13); + } else if ((c = l->label())) { + // -- label + fl_font(label_font, textsize()); + if (l->new_selected) fl_color(fl_contrast(label_color, FL_SELECTION_COLOR)); + else fl_color(label_color); + copy_trunc(buf, c, 32, 1, 0); // quoted string + fl_draw(buf, X, Y+13); + } + } else { + if (l->is_code_block() && (l->level==0 || l->parent->is_class())) { + // -- function names + fl_font(func_font, textsize()); + if (l->new_selected) fl_color(fl_contrast(func_color, FL_SELECTION_COLOR)); + else fl_color(func_color); + copy_trunc(buf, l->title(), 55, 0, 0); + } else { + if (l->is_a(ID_Comment)) { + // -- comment (in main line, not above entry) + fl_font(comment_font, textsize()); + if (l->new_selected) fl_color(fl_contrast(comment_color, FL_SELECTION_COLOR)); + else fl_color(comment_color); + copy_trunc(buf, l->title(), 55, 0, 0); + } else { + // -- code + fl_font(code_font, textsize()); + if (l->new_selected) fl_color(fl_contrast(code_color, FL_SELECTION_COLOR)); + else fl_color(code_color); + copy_trunc(buf, l->title(), 55, 0, 1); + } + } + fl_draw(buf, X, Y+13); + } + + // draw a thin line below the item if this item is not selected + // (if it is selected this additional line would look bad) + if (!l->new_selected) { + fl_color(fl_lighter(FL_GRAY)); + fl_line(x1,Y+16,x1+w1,Y+16); + } +} + +/** + Override the method to return the width of an item representation in Flixels. + \param v this item + \return width in FLTK units + */ +int Widget_Browser::item_width(void *v) const { + + char buf[500]; // edit buffer: large enough to hold 80 UTF-8 chars + nul + + Fl_Type *l = (Fl_Type *)v; + + if (!l->visible) return 0; + + int W = 3 + 13 + 18 + l->level * 12; + + if (l->is_widget() || l->is_class()) { + const char* c = l->type_name(); + if (!strncmp(c,"Fl_",3)) c += 3; + fl_font(textfont(), textsize()); + W += int(fl_width(c) + fl_width('n')); + c = l->name(); + if (c) { + fl_font(textfont()|FL_BOLD, textsize()); + W += int(fl_width(c)); + } else if (l->label()) { + copy_trunc(buf, l->label(), 32, 1, 0); // quoted string + W += int(fl_width(buf)); + } + } else { + copy_trunc(buf, l->title(), 55, 0, 0); + fl_font(textfont() | (l->is_code_block() && (l->level==0 || l->parent->is_class())?0:FL_BOLD), textsize()); + W += int(fl_width(buf)); + } + + return W; +} + +/** + Callback to tell the Fluid UI when the list of selected items changed. + */ +void Widget_Browser::callback() { + selection_changed((Fl_Type*)selection()); +} + +/** + Override the event handling for this browser. + + The vertical mouse position corresponds to an entry in the type tree. + The horizontal position has the following hot zones: + - 0-3 is the widget frame and ignored + - the next hot zone starts 12*indent pixels further to the right + - the next 13 pixels refer to the arrow that indicates children for the item + - 18 pixels follow for the icon + - the remaining part is filled with text + + \param[in] e the incoming event type + \return 0 if the event is not supported, and 1 if the event was "used up" + */ +int Widget_Browser::handle(int e) { + static Fl_Type *title; + Fl_Type *l; + int X,Y,W,H; bbox(X,Y,W,H); + switch (e) { + case FL_PUSH: + if (!Fl::event_inside(X,Y,W,H)) break; + l = (Fl_Type*)find_item(Fl::event_y()); + if (l) { + X += 3 + 12*l->level - hposition(); + if (l->can_have_children() && Fl::event_x()>X && Fl::event_x()level - hposition(); + if (l->can_have_children() && Fl::event_x()>X && Fl::event_x()new_selected && (Fl::event_clicks() || Fl::event_state(FL_CTRL))) + l->open(); + break; + } + l = pushedtitle; + title = pushedtitle = 0; + if (l) { + if (!l->folded_) { + l->folded_ = 1; + for (Fl_Type*k = l->next; k&&k->level>l->level; k = k->next) + k->visible = 0; + } else { + l->folded_ = 0; + for (Fl_Type*k=l->next; k&&k->level>l->level;) { + k->visible = 1; + if (k->can_have_children() && k->folded_) { + Fl_Type *j; + for (j = k->next; j && j->level>k->level; j = j->next) {/*empty*/} + k = j; + } else + k = k->next; + } + } + redraw(); + } + return 1; + } + return Fl_Browser_::handle(e); +} + +/** + Save the current scrollbar position during rebuild. + */ +void Widget_Browser::save_scroll_position() { + saved_h_scroll_ = hposition(); + saved_v_scroll_ = vposition(); +} + +/** + Restore the previous scrollbar position after rebuild. + */ +void Widget_Browser::restore_scroll_position() { + hposition(saved_h_scroll_); + vposition(saved_v_scroll_); +} + +/** + Rebuild the browser layout to reflect multiple changes. + This clears internal caches, recalculates the scroll bar sizes, and + sends a redraw() request to the widget. + */ +void Widget_Browser::rebuild() { + save_scroll_position(); + new_list(); + damage(FL_DAMAGE_SCROLL); + redraw(); + restore_scroll_position(); +} + +/** + Rebuild the browser layout and make sure that the given item is visible. + \param[in] inNode pointer to a widget node derived from Fl_Type. + */ +void Widget_Browser::display(Fl_Type *inNode) { + if (!inNode) { + // Alternative: find the first (last?) visible selected item. + return; + } + // remeber our current scroll position + int currentV = vposition(), newV = currentV; + int nodeV = 0; + // find the inNode in the tree and check, if it is already visible + Fl_Type *p=Fl_Type::first; + for ( ; p && p!=inNode; p=p->next) { + if (p->visible) + nodeV += item_height(p) + linespacing(); + } + if (p) { + int xx, yy, ww, hh; + bbox(xx, yy, ww, hh); + int frame_top = xx-x(); + int frame_bottom = frame_top + hh; + int node_height = item_height(inNode) + linespacing(); + int margin_height = 2 * (item_quick_height(inNode) + linespacing()); + if (margin_height>hh/2) margin_height = hh/2; + // is the inNode above the current scroll position? + if (nodeVcurrentV+frame_bottom-margin_height-node_height) + newV = nodeV - frame_bottom + margin_height + node_height; + if (newV<0) + newV = 0; + } + if (newV!=currentV) + vposition(newV); +} + +void Widget_Browser::load_prefs() { + int c; + Fl_Preferences p(fluid_prefs, "widget_browser"); + p.get("label_color", c, 72); label_color = c; + p.get("label_font", c, FL_HELVETICA); label_font = c; + p.get("class_color", c, FL_FOREGROUND_COLOR); class_color = c; + p.get("class_font", c, FL_HELVETICA_BOLD); class_font = c; + p.get("func_color", c, FL_FOREGROUND_COLOR); func_color = c; + p.get("func_font", c, FL_HELVETICA); func_font = c; + p.get("name_color", c, FL_FOREGROUND_COLOR); name_color = c; + p.get("name_font", c, FL_HELVETICA); name_font = c; + p.get("code_color", c, FL_FOREGROUND_COLOR); code_color = c; + p.get("code_font", c, FL_HELVETICA); code_font = c; + p.get("comment_color",c, FL_DARK_GREEN); comment_color = c; + p.get("comment_font", c, FL_HELVETICA); comment_font = c; +} + +void Widget_Browser::save_prefs() { + Fl_Preferences p(fluid_prefs, "widget_browser"); + p.set("label_color", (int)label_color); + p.set("label_font", (int)label_font); + p.set("class_color", (int)class_color); + p.set("class_font", (int)class_font); + p.set("func_color", (int)func_color); + p.set("func_font", (int)func_font); + p.set("name_color", (int)name_color); + p.set("name_font", (int)name_font); + p.set("code_color", (int)code_color); + p.set("code_font", (int)code_font); + p.set("comment_color", (int)comment_color); + p.set("comment_font", (int)comment_font); +} diff --git a/fluid/widgets/widget_browser.h b/fluid/widgets/widget_browser.h new file mode 100644 index 000000000..86c60777c --- /dev/null +++ b/fluid/widgets/widget_browser.h @@ -0,0 +1,84 @@ +// +// Widget Browser code for the Fast Light Tool Kit (FLTK). +// +// Copyright 1998-2021 by Bill Spitzak and others. +// +// This library is free software. Distribution and use rights are outlined in +// the file "COPYING" which should have been included with this file. If this +// file is missing or damaged, see the license at: +// +// https://www.fltk.org/COPYING.php +// +// Please see the following page on how to report bugs and issues: +// +// https://www.fltk.org/bugs.php +// + +#ifndef _FLUID_WIDGET_BROWSER_H +#define _FLUID_WIDGET_BROWSER_H + +#include + +class Fl_Type; +class Widget_Browser; + +extern Widget_Browser *widget_browser; + +extern void redraw_browser(); +extern Fl_Widget *make_widget_browser(int x,int y,int w,int h); +extern void redraw_widget_browser(Fl_Type *caller); +extern void select(Fl_Type *o, int v); +extern void select_only(Fl_Type *o); +extern void deselect(); +extern void reveal_in_browser(Fl_Type *t); + +class Widget_Browser : public Fl_Browser_ +{ + friend class Fl_Type; + + static void callback_stub(Fl_Widget *o, void *) { + ((Widget_Browser *)o)->callback(); + } + + Fl_Type* pushedtitle; + int saved_h_scroll_; + int saved_v_scroll_; + + // required routines for Fl_Browser_ subclass: + void *item_first() const FL_OVERRIDE; + void *item_next(void *) const FL_OVERRIDE; + void *item_prev(void *) const FL_OVERRIDE; + int item_selected(void *) const FL_OVERRIDE; + void item_select(void *,int) FL_OVERRIDE; + int item_width(void *) const FL_OVERRIDE; + int item_height(void *) const FL_OVERRIDE; + void item_draw(void *,int,int,int,int) const FL_OVERRIDE; + int incr_height() const FL_OVERRIDE; + +public: + Widget_Browser(int,int,int,int,const char * =NULL); + int handle(int) FL_OVERRIDE; + void callback(); + void save_scroll_position(); + void restore_scroll_position(); + void rebuild(); + void new_list() { Fl_Browser_::new_list(); } + void display(Fl_Type *); + void load_prefs(); + void save_prefs(); + + static Fl_Color label_color; + static Fl_Font label_font; + static Fl_Color class_color; + static Fl_Font class_font; + static Fl_Color func_color; + static Fl_Font func_font; + static Fl_Color name_color; + static Fl_Font name_font; + static Fl_Color code_color; + static Fl_Font code_font; + static Fl_Color comment_color; + static Fl_Font comment_font; +}; + +#endif // _FLUID_WIDGET_BROWSER_H -- cgit v1.2.3