summaryrefslogtreecommitdiff
path: root/fluid/widgets
diff options
context:
space:
mode:
authorMatthias Melcher <github@matthiasm.com>2025-03-07 16:34:35 +0100
committerMatthias Melcher <github@matthiasm.com>2025-03-07 16:34:48 +0100
commit1985aefc0e502048f92b91beef87c0dfbe669fed (patch)
treeaf62874def4590e437a47784b4428d975ceb262f /fluid/widgets
parent42a04c064d4b31c3a85210311f3ada163c406a25 (diff)
Restructuring Fluid source files.
Diffstat (limited to 'fluid/widgets')
-rw-r--r--fluid/widgets/CodeEditor.cxx316
-rw-r--r--fluid/widgets/CodeEditor.h105
-rw-r--r--fluid/widgets/StyleParse.cxx328
-rw-r--r--fluid/widgets/StyleParse.h61
-rw-r--r--fluid/widgets/custom_widgets.cxx311
-rw-r--r--fluid/widgets/custom_widgets.h90
-rw-r--r--fluid/widgets/widget_browser.cxx669
-rw-r--r--fluid/widgets/widget_browser.h84
8 files changed, 1964 insertions, 0 deletions
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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+
+// ---- 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 <FL/Fl.H>
+#include <FL/Fl_Text_Buffer.H>
+#include <FL/Fl_Text_Editor.H>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+
+// ---- 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 <stdio.h>
+#include <string.h>
+#include <ctype.h>
+#include <stdlib.h> // 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 <FL/platform.H>
+#include <FL/Fl_Button.H>
+#include <FL/Fl_Window.H>
+#include <FL/fl_draw.H>
+#include <FL/Fl_Menu_.H>
+#include <FL/fl_string_functions.h>
+#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 <FL/Fl_Button.H>
+#include <FL/Fl_Input.H>
+
+// 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 <FL/Fl.H>
+#include <FL/Fl_Browser_.H>
+#include <FL/fl_draw.H>
+
+/**
+ \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 <maxl> 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()<X+13) {
+ title = pushedtitle = l;
+ redraw_line(l);
+ return 1;
+ }
+ }
+ break;
+ case FL_DRAG:
+ if (!title) 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()<X+13) ;
+ else l = 0;
+ }
+ if (l != pushedtitle) {
+ if (pushedtitle) redraw_line(pushedtitle);
+ if (l) redraw_line(l);
+ pushedtitle = l;
+ }
+ return 1;
+ case FL_RELEASE:
+ if (!title) {
+ l = (Fl_Type*)find_item(Fl::event_y());
+ if (l && l->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 (nodeV<currentV+margin_height)
+ newV = nodeV - margin_height;
+ else if (nodeV>currentV+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 <FL/Fl_Browser_.H>
+
+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