diff options
| author | Matthias Melcher <github@matthiasm.com> | 2025-03-16 17:16:12 -0400 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-03-16 17:16:12 -0400 |
| commit | 51a55bc73660f64e8f4b32b8b4d3858f2a786f7b (patch) | |
| tree | 122ad9f838fcf8f61ed7cf5fa031e8ed69817e10 /fluid/nodes/Function_Node.cxx | |
| parent | 13a7073a1e007ce5b71ef70bced1a9b15158820d (diff) | |
Fluid: restructuring and rejuvenation of the source code.
* Add classes for application and project
* Removed all globals from Fluid.h
* Extracting args and project history into their own classes
* Moving globals into Application class
* Initialize values inside headers for some classes.
* Undo functionality wrapped in a class inside Project.
* File reader and writer are now linked to a project.
* Avoid global project access
* Nodes (former Types) will be managed by a new Tree class.
* Removed static members (hidden globals) form Node/Fl_Type.
* Adding Tree iterator.
* Use nullptr instead of 0, NULL, or 0L
* Renamed Fl_..._Type to ..._Node, FL_OVERRIDE -> override
* Renaming ..._type to ...::prototype
* Splitting Widget Panel into multiple files.
* Moved callback code into widget panel file.
* Cleaning up Fluid_Image -> Image_asset
* Moving Fd_Snap_Action into new namespace fld::app::Snap_Action etc.
* Moved mergeback into proj folder.
* `enum ID` is now `enum class Type`.
Diffstat (limited to 'fluid/nodes/Function_Node.cxx')
| -rw-r--r-- | fluid/nodes/Function_Node.cxx | 2158 |
1 files changed, 2158 insertions, 0 deletions
diff --git a/fluid/nodes/Function_Node.cxx b/fluid/nodes/Function_Node.cxx new file mode 100644 index 000000000..8ffdbef01 --- /dev/null +++ b/fluid/nodes/Function_Node.cxx @@ -0,0 +1,2158 @@ +// +// C function Node 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 "nodes/Function_Node.h" + +#include "Fluid.h" +#include "proj/mergeback.h" +#include "proj/undo.h" +#include "io/Project_Reader.h" +#include "io/Project_Writer.h" +#include "io/Code_Writer.h" +#include "nodes/Window_Node.h" +#include "nodes/Group_Node.h" +#include "panels/function_panel.h" +#include "rsrcs/comments.h" +#include "widgets/Node_Browser.h" + +#include <FL/fl_string_functions.h> +#include <FL/Fl_File_Chooser.H> +#include <FL/fl_ask.H> +#include "../src/flstring.h" + +#include <zlib.h> + + +/// Set a current class, so that the code of the children is generated correctly. +Class_Node *current_class = nullptr; + +/** + \brief Return 1 if the list contains a function with the given signature at the top level. + Widget_Node uses this to check if a callback by a certain signature is + already defined by the user within this file. If not, Widget_Node will + generate an `extern $sig$;` statement. + \param[in] rtype return type, can be nullptr to avoid checking (not used by Widget_Node) + \param[in] sig function signature + \return 1 if found. + */ +int has_toplevel_function(const char *rtype, const char *sig) { + Node *child; + for (child = Fluid.proj.tree.first; child; child = child->next) { + if (!child->is_in_class() && child->is_a(Type::Function)) { + const Function_Node *fn = (const Function_Node*)child; + if (fn->has_signature(rtype, sig)) + return 1; + } + } + return 0; +} + + +//////////////////////////////////////////////////////////////// +// quick check of any C code for legality, returns an error message + +static char buffer[128]; // for error messages + +/** + Check a quoted string contains a character. + This is used to find a matching " or ' in a string. + \param[inout] c start searching here, return where we found \c type + \param[in] type find this character + \return nullptr if the character was found, else a pointer to a static string + with an error message + */ +const char *_q_check(const char * & c, int type) { + for (;;) switch (*c++) { + case '\0': + sprintf(buffer,"missing %c",type); + return buffer; + case '\\': + if (*c) c++; + break; + default: + if (*(c-1) == type) return nullptr; + } +} + +/** + Check normal code, match brackets and parenthesis. + Recursively run a line of code and make sure that + {, [, ", ', and ( are matched. + \param[inout] c start searching here, return the end of the search + \param[in] type find this character match + \return nullptr if the character was found, else a pointer to a static string + with an error message + */ +const char *_c_check(const char * & c, int type) { + const char *d; + for (;;) switch (*c++) { + case 0: + if (!type) return nullptr; + sprintf(buffer, "missing '%c'", type); + return buffer; + case '/': + // Skip comments as needed... + if (*c == '/') { + while (*c != '\n' && *c) c++; + } else if (*c == '*') { + c++; + while ((*c != '*' || c[1] != '/') && *c) c++; + if (*c == '*') c+=2; + else { + return "missing '*/'"; + } + } + break; +// case '#': +// // treat cpp directives as a comment: +// // Matt: a '#' character can appear as a concatenation when defining macros +// // Matt: so instead we just silently ignore the '#' +// while (*c != '\n' && *c) c++; +// break; + case '{': +// // Matt: C++ does allow {} inside () now +// if (type==')') goto UNEXPECTED; + d = _c_check(c,'}'); + if (d) return d; + break; + case '(': + d = _c_check(c,')'); + if (d) return d; + break; + case '[': + d = _c_check(c,']'); + if (d) return d; + break; + case '\"': + d = _q_check(c,'\"'); + if (d) return d; + break; + case '\'': + d = _q_check(c,'\''); + if (d) return d; + break; + case '}': + case ')': + case ']': +// UNEXPECTED: + if (type == *(c-1)) return nullptr; + sprintf(buffer, "unexpected '%c'", *(c-1)); + return buffer; + } +} + +/** + Check legality of c code (sort of) and return error: + Make sure that {, ", ', and ( are matched. + \param[in] c start searching here + \param[in] type find this character match (default is 0) + \return nullptr if the character was found, else a pointer to a static string + with an error message + \note This function checks every conceivable line of code, which is not + always wanted. It can't differentiate characters in comments, and the + user may well intend to leave a curly bracket open + (i.e. namespace { ... } ). We should make this option user selectable. + */ +const char *c_check(const char *c, int type) { + return _c_check(c,type); +} + +// ---- Function_Node implementation + +/** \class Function_Node + Manage a C++ function node in the Fluid design. + + A function can have a signature (name followed by arguments), a return type + and a comment section. If can be local or global, and it can be declared a C + or C++ function. + */ + +/// Prototype for a function to be used by the factory. +Function_Node Function_Node::prototype; + +/** + Create a new function. + */ +Function_Node::Function_Node() : + Node(), + return_type(nullptr), + public_(0), + cdecl_(0), + constructor(0), + havewidgets(0) +{ } + +/** + Destructor. + */ +Function_Node::~Function_Node() { + if (return_type) free((void*)return_type); +} + +/** + Create a new function for the widget tree. + \param[in] strategy add new function after current or as last child + \return the new node + */ +Node *Function_Node::make(Strategy strategy) { + Node *anchor = Fluid.proj.tree.current, *p = anchor; + if (p && (strategy.placement() == Strategy::AFTER_CURRENT)) + p = p->parent; + while (p && !p->is_decl_block()) { + anchor = p; + strategy.placement(Strategy::AFTER_CURRENT); + p = p->parent; + } + Function_Node *o = new Function_Node(); + o->name("make_window()"); + o->return_type = nullptr; + o->add(anchor, strategy); + o->factory = this; + o->public_ = 1; + o->cdecl_ = 0; + return o; +} + +/** + Write function specific properties to an .fl file. + - "private"/"public" indicates the state of the function + - "C" is written if we want a C signature instead of C++ + - "return_type" is followed by the return type of the function + */ +void Function_Node::write_properties(fld::io::Project_Writer &f) { + Node::write_properties(f); + switch (public_) { + case 0: f.write_string("private"); break; + case 2: f.write_string("protected"); break; + } + if (cdecl_) f.write_string("C"); + if (return_type) { + f.write_string("return_type"); + f.write_word(return_type); + } +} + +/** + Read function specific properties fron an .fl file. + \param[in] c read from this string + */ +void Function_Node::read_property(fld::io::Project_Reader &f, const char *c) { + if (!strcmp(c,"private")) { + public_ = 0; + } else if (!strcmp(c,"protected")) { + public_ = 2; + } else if (!strcmp(c,"C")) { + cdecl_ = 1; + } else if (!strcmp(c,"return_type")) { + storestring(f.read_word(),return_type); + } else { + Node::read_property(f, c); + } +} + +/** + Open the function_panel dialog box to edit this function. + */ +void Function_Node::open() { + // fill dialog box + if (!function_panel) make_function_panel(); + f_return_type_input->value(return_type); + f_name_input->value(name()); + if (is_in_class()) { + f_public_member_choice->value(public_); + f_public_member_choice->show(); + f_public_choice->hide(); + f_c_button->hide(); + } else { + f_public_choice->value(public_); + f_public_choice->show(); + f_public_member_choice->hide(); + f_c_button->show(); + } + f_c_button->value(cdecl_); + const char *c = comment(); + f_comment_input->buffer()->text(c?c:""); + function_panel->show(); + const char* message = nullptr; + for (;;) { // repeat as long as there are errors + // - message loop until OK or cancel is pressed + for (;;) { + Fl_Widget* w = Fl::readqueue(); + if (w == f_panel_cancel) goto BREAK2; + else if (w == f_panel_ok) break; + else if (!w) Fl::wait(); + } + // - check syntax + const char *c = f_name_input->value(); + while (isspace(*c)) c++; + message = c_check(c); + if (!message) { + const char *d = c; + for (; *d != '('; d++) if (isspace(*d) || !*d) break; + if (*c && *d != '(') + message = "must be 'name(arguments)'"; + } + if (!message) { + c = f_return_type_input->value(); + message = c_check(c); + } + // - alert user + if (message) { + int v = fl_choice("Potential syntax error detected: %s", + "Continue Editing", "Ignore Error", nullptr, message); + if (v==0) continue; // Continue Editing + //if (v==1) { } // Ignore Error and close dialog + } + // - copy dialog data to target variables + int mod = 0; + name(f_name_input->value()); + storestring(f_return_type_input->value(), return_type); + if (is_in_class()) { + if (public_ != f_public_member_choice->value()) { + mod = 1; + public_ = f_public_member_choice->value(); + redraw_browser(); + } + } else { + if (public_ != f_public_choice->value()) { + mod = 1; + public_ = f_public_choice->value(); + redraw_browser(); + } + } + if (cdecl_ != f_c_button->value()) { + mod = 1; + cdecl_ = f_c_button->value(); + } + c = f_comment_input->buffer()->text(); + if (c && *c) { + if (!comment() || strcmp(c, comment())) { Fluid.proj.set_modflag(1); redraw_browser(); } + comment(c); + } else { + if (comment()) { Fluid.proj.set_modflag(1); redraw_browser(); } + comment(nullptr); + } + if (c) free((void*)c); + if (mod) Fluid.proj.set_modflag(1); + break; + } +BREAK2: + function_panel->hide(); +} + +/** + Return 1 if the function is global. + \return 1 if public, 0 if local. + */ +int Function_Node::is_public() const { + return public_; +} + +static bool fd_isspace(int c) { + return (c>0 && c<128 && isspace(c)); +} + +// code duplication: see int is_id(char c) in code.cxx +static bool fd_iskeyword(int c) { + return (c>0 && c<128 && (isalnum(c) || c=='_')); +} + +// remove all function default parameters and `override` keyword +static void clean_function_for_implementation(char *out, const char *function_name) { + char *sptr = out; + const char *nptr = function_name; + int skips=0,skipc=0; + int nc=0,plevel=0; + bool arglist_done = false; + for (;*nptr; nc++,nptr++) { + if (arglist_done && fd_isspace(nptr[0])) { + // skip `override` and `FL_OVERRIDE` keywords if they are following the list of arguments + if (strncmp(nptr+1, "override", 8)==0 && !fd_iskeyword(nptr[9])) { nptr += 8; continue; } + else if (strncmp(nptr+1, "FL_OVERRIDE", 11)==0 && !fd_iskeyword(nptr[12])) { nptr += 11; continue; } + } + if (!skips && *nptr=='(') plevel++; + else if (!skips && *nptr==')') { plevel--; if (plevel==0) arglist_done = true; } + if ( *nptr=='"' && !(nc && *(nptr-1)=='\\') ) + skips = skips ? 0 : 1; + else if(!skips && *nptr=='\'' && !(nc && *(nptr-1)=='\\')) + skipc = skipc ? 0 : 1; + if(!skips && !skipc && plevel==1 && *nptr =='=' && !(nc && *(nptr-1)=='\'') ) { // ignore '=' case + while(*++nptr && (skips || skipc || ( (*nptr!=',' && *nptr!=')') || plevel!=1) )) { + if ( *nptr=='"' && *(nptr-1)!='\\' ) + skips = skips ? 0 : 1; + else if(!skips && *nptr=='\'' && *(nptr-1)!='\\') + skipc = skipc ? 0 : 1; + if (!skips && !skipc && *nptr=='(') plevel++; + else if (!skips && *nptr==')') plevel--; + } + if (*nptr==')') if (--plevel==0) arglist_done = true; + } + if (sptr < (out + 1024 - 1)) *sptr++ = *nptr; + } + *sptr = '\0'; +} + + +/** + Write the code for the source and the header file. + This writes the code that goes \b before all children of this class. + \see write_code2(fld::io::Code_Writer& f) + */ +void Function_Node::write_code1(fld::io::Code_Writer& f) { + constructor=0; + havewidgets = 0; + Node *child; + // if the function has no children (hence no body), Fluid will not generate + // the function either. This is great if you decide to implement that function + // inside another module + char havechildren = 0; + for (child = next; child && child->level > level; child = child->next) { + havechildren = 1; + if (child->is_widget()) { + havewidgets = 1; + break; + } + } + if (havechildren) + f.write_c("\n"); + if (ismain()) { + if (havechildren) + f.write_c("int main(int argc, char **argv) {\n"); + } else { + const char* rtype = return_type; + const char* star = ""; + // from matt: let the user type "static " at the start of type + // in order to declare a static method; + int is_static = 0; + int is_virtual = 0; + if (rtype) { + if (!strcmp(rtype,"static")) {is_static = 1; rtype = nullptr;} + else if (!strncmp(rtype, "static ",7)) {is_static = 1; rtype += 7;} + } + if (rtype) { + if (!strcmp(rtype, "virtual")) {is_virtual = 1; rtype = nullptr;} + else if (!strncmp(rtype, "virtual ",8)) {is_virtual = 1; rtype += 8;} + } + if (!rtype) { + if (havewidgets) { + rtype = subclassname(child); + star = "*"; + } else rtype = "void"; + } + + const char* k = class_name(0); + if (k) { + f.write_public(public_); + if (havechildren) + write_comment_c(f); + if (name()[0] == '~') + constructor = 1; + else { + size_t n = strlen(k); + if (!strncmp(name(), k, n) && name()[n] == '(') constructor = 1; + } + f.write_h("%s", f.indent(1)); + if (is_static) f.write_h("static "); + if (is_virtual) f.write_h("virtual "); + if (!constructor) { + f.write_h("%s%s ", rtype, star); + if (havechildren) + f.write_c("%s%s ", rtype, star); + } + + // if this is a subclass, only f.write_h() the part before the ':' + char s[1024], *sptr = s; + char *nptr = (char *)name(); + + while (*nptr) { + if (*nptr == ':') { + if (nptr[1] != ':') break; + // Copy extra ":" for "class::member"... + *sptr++ = *nptr++; + } + *sptr++ = *nptr++; + } + *sptr = '\0'; + + if (s[strlen(s)-1] == '}') { // special case for inlined functions + f.write_h("%s\n", s); + } else { + f.write_h("%s;\n", s); + } + if (havechildren) { + clean_function_for_implementation(s, name()); + f.write_c("%s::%s {\n", k, s); + } + } else { + if (havechildren) + write_comment_c(f); + if (public_==1) { + if (cdecl_) + f.write_h("extern \"C\" { %s%s %s; }\n", rtype, star, name()); + else + f.write_h("%s%s %s;\n", rtype, star, name()); + } else if (public_==2) { + // write neither the prototype nor static, the function may be declared elsewhere + } else { + if (havechildren) + f.write_c("static "); + } + + // write everything but the default parameters (if any) + char s[1024]; + if (havechildren) { + clean_function_for_implementation(s, name()); + f.write_c("%s%s %s {\n", rtype, star, s); + } + } + } + + if (havewidgets && child && !child->name()) + f.write_c("%s%s* w;\n", f.indent(1), subclassname(child)); + f.indentation++; +} + +/** + Write the code for the source and the header file. + This writes the code that goes \b after all children of this class. + \see write_code1(fld::io::Code_Writer& f) + */ +void Function_Node::write_code2(fld::io::Code_Writer& f) { + Node *child; + const char *var = "w"; + char havechildren = 0; + for (child = next; child && child->level > level; child = child->next) { + havechildren = 1; + if (child->is_a(Type::Window) && child->name()) var = child->name(); + } + + if (ismain()) { + if (havewidgets) + f.write_c("%s%s->show(argc, argv);\n", f.indent(1), var); + if (havechildren) + f.write_c("%sreturn Fl::run();\n", f.indent(1)); + } else if (havewidgets && !constructor && !return_type) { + f.write_c("%sreturn %s;\n", f.indent(1), var); + } + if (havechildren) + f.write_c("}\n"); + f.indentation = 0; +} + +/** + Check if the return type and signatures match. + \param[in] rtype function return type + \param[in] sig function name followed by arguments + \return 1 if they match, 0 if not + */ +int Function_Node::has_signature(const char *rtype, const char *sig) const { + if (rtype && !return_type) return 0; + if (!name()) return 0; + if ( (rtype==nullptr || strcmp(return_type, rtype)==0) + && fl_filename_match(name(), sig)) { + return 1; + } + return 0; +} + +// ---- Code_Node declaration + +/** \class Code_Node + Manage a block of C++ code in the Fluid design. + + This node manages an arbitrary block of code inside a function that will + be written into the source code file. Fl_Code_Block has no comment field. + However, the first line of code will be shown in the widget browser. + */ + +/// Prototype for code to be used by the factory. +Code_Node Code_Node::prototype; + +/** + Constructor. + */ +Code_Node::Code_Node() : + cursor_position_(0), + code_input_scroll_row(0), + code_input_scroll_col(0) +{} + +/** + Make a new code node. + If the parent node is not a function, a message box will pop up and + the request will be ignored. + \param[in] strategy add code after current or as last child + \return new Code node + */ +Node *Code_Node::make(Strategy strategy) { + Node *anchor = Fluid.proj.tree.current, *p = anchor; + if (p && (strategy.placement() == Strategy::AFTER_CURRENT)) + p = p->parent; + while (p && !p->is_code_block()) { + anchor = p; + strategy.placement(Strategy::AFTER_CURRENT); + p = p->parent; + } + if (!p) { + fl_message("Please select a function"); + return nullptr; + } + Code_Node *o = new Code_Node(); + o->name("printf(\"Hello, World!\\n\");"); + o->add(anchor, strategy); + o->factory = this; + return o; +} + +/** + Open the code_panel or an external editor to edit this code section. + */ +void Code_Node::open() { + // Using an external code editor? Open it.. + if ( Fluid.use_external_editor && Fluid.external_editor_command[0] ) { + const char *cmd = Fluid.external_editor_command; + const char *code = name(); + if (!code) code = ""; + if ( editor_.open_editor(cmd, code) == 0 ) + return; // return if editor opened ok, fall thru to built-in if not + } + // Use built-in code editor.. + if (!code_panel) make_code_panel(); + const char *text = name(); + code_input->buffer()->text( text ? text : "" ); + code_input->insert_position(cursor_position_); + code_input->scroll(code_input_scroll_row, code_input_scroll_col); + code_panel->show(); + const char* message = nullptr; + for (;;) { // repeat as long as there are errors + for (;;) { + Fl_Widget* w = Fl::readqueue(); + if (w == code_panel_cancel) goto BREAK2; + else if (w == code_panel_ok) break; + else if (!w) Fl::wait(); + } + char*c = code_input->buffer()->text(); + message = c_check(c); + if (message) { + int v = fl_choice("Potential syntax error detected: %s", + "Continue Editing", "Ignore Error", nullptr, message); + if (v==0) continue; // Continue Editing + //if (v==1) { } // Ignore Error and close dialog + } + name(c); + free(c); + break; + } + cursor_position_ = code_input->insert_position(); + code_input_scroll_row = code_input->scroll_row(); + code_input_scroll_col = code_input->scroll_col(); +BREAK2: + code_panel->hide(); +} + +/** + Grab changes from an external editor and write this node. + */ +void Code_Node::write(fld::io::Project_Writer &f) { + // External editor changes? If so, load changes into ram, update mtime/size + if ( handle_editor_changes() == 1 ) { + Fluid.main_window->redraw(); // tell fluid to redraw; edits may affect tree's contents + } + Node::write(f); +} + +/** + Write the code block with the correct indentation. + */ +void Code_Node::write_code1(fld::io::Code_Writer& f) { + // External editor changes? If so, load changes into ram, update mtime/size + if ( handle_editor_changes() == 1 ) { + Fluid.main_window->redraw(); // tell fluid to redraw; edits may affect tree's contents + } + // Matt: disabled f.tag(FD_TAG_GENERIC, 0); + f.write_c_indented(name(), 0, '\n'); + // Matt: disabled f.tag(FD_TAG_CODE, get_uid()); +} + +/** + See if external editor is open. + */ +int Code_Node::is_editing() { + return editor_.is_editing(); +} + +/** + Reap the editor's pid + \return -2: editor not open + \return -1: wait failed + \return 0: process still running + \return \>0: process finished + reaped (returns pid) + */ +int Code_Node::reap_editor() { + return editor_.reap_editor(); +} + +/** + Handle external editor file modifications. + If changed, record keeping is updated and file's contents is loaded into ram + \return 0: file unchanged or not editing + \return 1: file changed, internal records updated, 'code' has new content + \return -1: error getting file info (get_ms_errmsg() has reason) + \todo Figure out how saving a fluid file can be intercepted to grab + current contents of editor file.. + */ +int Code_Node::handle_editor_changes() { + const char *newcode = nullptr; + switch ( editor_.handle_changes(&newcode) ) { + case 1: { // (1)=changed + name(newcode); // update value in ram + free((void*)newcode); + return 1; + } + case -1: return -1; // (-1)=error -- couldn't read file (dialog showed reason) + default: break; // (0)=no change + } + return 0; +} + +// ---- CodeBlock_Node implementation + +/** \class CodeBlock_Node + Manage two blocks of C++ code enclosing its children. + + This node manages two lines of code that enclose all children + of this node. This is usually an if..then clause. + + \todo this node could support multiple lines of code for each block. + */ + +/// Prototype for a block of code to be used by the factory. +CodeBlock_Node CodeBlock_Node::prototype; + +/** + Constructor. + */ +CodeBlock_Node::CodeBlock_Node() : + Node(), + after(nullptr) +{ } + +/** + Destructor. + */ +CodeBlock_Node::~CodeBlock_Node() { + if (after) + free((void*)after); +} + +/** + Make a new code block. + If the parent node is not a function or another codeblock, a message box will + pop up and the request will be ignored. + \param[in] strategy add after current or as last child + \return new CodeBlock + */ +Node *CodeBlock_Node::make(Strategy strategy) { + Node *anchor = Fluid.proj.tree.current, *p = anchor; + if (p && (strategy.placement() == Strategy::AFTER_CURRENT)) + p = p->parent; + while (p && !p->is_code_block()) { + anchor = p; + strategy.placement(Strategy::AFTER_CURRENT); + p = p->parent; + } + if (!p) { + fl_message("Please select a function"); + return nullptr; + } + CodeBlock_Node *o = new CodeBlock_Node(); + o->name("if (test())"); + o->after = nullptr; + o->add(anchor, strategy); + o->factory = this; + return o; +} + +/** + Write the specific properties for this node. + - "after" is followed by the code that comes after the children + The "before" code is stored in the name() field. + */ +void CodeBlock_Node::write_properties(fld::io::Project_Writer &f) { + Node::write_properties(f); + if (after) { + f.write_string("after"); + f.write_word(after); + } +} + +/** + Read the node specific properties. + */ +void CodeBlock_Node::read_property(fld::io::Project_Reader &f, const char *c) { + if (!strcmp(c,"after")) { + storestring(f.read_word(),after); + } else { + Node::read_property(f, c); + } +} + +/** + Open the codeblock_panel. + */ +void CodeBlock_Node::open() { + if (!codeblock_panel) make_codeblock_panel(); + code_before_input->value(name()); + code_after_input->value(after); + codeblock_panel->show(); + const char* message = nullptr; + for (;;) { // repeat as long as there are errors + // event loop + for (;;) { + Fl_Widget* w = Fl::readqueue(); + if (w == codeblock_panel_cancel) goto BREAK2; + else if (w == codeblock_panel_ok) break; + else if (!w) Fl::wait(); + } + // check for syntax errors + message = c_check(code_before_input->value()); + if (!message) { + message = c_check(code_after_input->value()); + } + // alert user + if (message) { + int v = fl_choice("Potential syntax error detected: %s", + "Continue Editing", "Ignore Error", nullptr, message); + if (v==0) continue; // Continue Editing + //if (v==1) { } // Ignore Error and close dialog + } + // write to variables + name(code_before_input->value()); + storestring(code_after_input->value(), after); + break; + } +BREAK2: + codeblock_panel->hide(); +} + +/** + Write the "before" code. + */ +void CodeBlock_Node::write_code1(fld::io::Code_Writer& f) { + const char* c = name(); + f.write_c("%s%s {\n", f.indent(), c ? c : ""); + f.indentation++; +} + +/** + Write the "after" code. + */ +void CodeBlock_Node::write_code2(fld::io::Code_Writer& f) { + f.indentation--; + if (after) f.write_c("%s} %s\n", f.indent(), after); + else f.write_c("%s}\n", f.indent()); +} + +// ---- Decl_Node declaration + +/** \class Decl_Node + Manage the C/C++ declaration of a variable. + + This node manages a single line of code that can be in the header or the source + code, and can be made static. + + \todo this node could support multiple lines. + */ + +/// Prototype for a declaration to be used by the factory. +Decl_Node Decl_Node::prototype; + +/** + Constructor. + */ +Decl_Node::Decl_Node() : + public_(0), + static_(1) +{ } + +/** + Return 1 if this declaration and its parents are public. + */ +int Decl_Node::is_public() const +{ + Node *p = parent; + while (p && !p->is_decl_block()) p = p->parent; + if(p && p->is_public() && public_) + return public_; + else if(!p) + return public_; + return 0; +} + +/** + Make a new declaration. + \param[in] strategy add after current or as last child + \return new Declaration node + */ +Node *Decl_Node::make(Strategy strategy) { + Node *anchor = Fluid.proj.tree.current, *p = anchor; + if (p && (strategy.placement() == Strategy::AFTER_CURRENT)) + p = p->parent; + while (p && !p->is_decl_block()) { + anchor = p; + strategy.placement(Strategy::AFTER_CURRENT); + p = p->parent; + } + Decl_Node *o = new Decl_Node(); + o->public_ = 0; + o->static_ = 1; + o->name("int x;"); + o->add(anchor, strategy); + o->factory = this; + return o; +} + +/** + Write the specific properties. + - "private"/"public"/"protected" + - "local"/"global" if this is static or not + */ +void Decl_Node::write_properties(fld::io::Project_Writer &f) { + Node::write_properties(f); + switch (public_) { + case 0: f.write_string("private"); break; + case 1: f.write_string("public"); break; + case 2: f.write_string("protected"); break; + } + if (static_) + f.write_string("local"); + else + f.write_string("global"); +} + +/** + Read the specific properties. + */ +void Decl_Node::read_property(fld::io::Project_Reader &f, const char *c) { + if (!strcmp(c,"public")) { + public_ = 1; + } else if (!strcmp(c,"private")) { + public_ = 0; + } else if (!strcmp(c,"protected")) { + public_ = 2; + } else if (!strcmp(c,"local")) { + static_ = 1; + } else if (!strcmp(c,"global")) { + static_ = 0; + } else { + Node::read_property(f, c); + } +} + +/** + Open the decl_panel to edit this node. + */ +void Decl_Node::open() { + if (!decl_panel) make_decl_panel(); + decl_input->buffer()->text(name()); + if (is_in_class()) { + decl_class_choice->value(public_); + decl_class_choice->show(); + decl_choice->hide(); + } else { + decl_choice->value((public_&1)|((static_&1)<<1)); + decl_choice->show(); + decl_class_choice->hide(); + } + const char *c = comment(); + decl_comment_input->buffer()->text(c?c:""); + decl_panel->show(); + const char* message = nullptr; + for (;;) { // repeat as long as there are errors + // event loop + for (;;) { + Fl_Widget* w = Fl::readqueue(); + if (w == decl_panel_cancel) goto BREAK2; + else if (w == decl_panel_ok) break; + else if (!w) Fl::wait(); + } + // check values + const char*c = decl_input->buffer()->text(); + while (isspace(*c)) c++; + message = c_check(c&&c[0]=='#' ? c+1 : c); + // alert user + if (message) { + int v = fl_choice("Potential syntax error detected: %s", + "Continue Editing", "Ignore Error", nullptr, message); + if (v==0) continue; // Continue Editing + //if (v==1) { } // Ignore Error and close dialog + } + // copy vlaues + name(c); + if (is_in_class()) { + if (public_!=decl_class_choice->value()) { + Fluid.proj.set_modflag(1); + public_ = decl_class_choice->value(); + } + } else { + if (public_!=(decl_choice->value()&1)) { + Fluid.proj.set_modflag(1); + public_ = (decl_choice->value()&1); + } + if (static_!=((decl_choice->value()>>1)&1)) { + Fluid.proj.set_modflag(1); + static_ = ((decl_choice->value()>>1)&1); + } + } + c = decl_comment_input->buffer()->text(); + if (c && *c) { + if (!comment() || strcmp(c, comment())) { Fluid.proj.set_modflag(1); redraw_browser(); } + comment(c); + } else { + if (comment()) { Fluid.proj.set_modflag(1); redraw_browser(); } + comment(nullptr); + } + if (c) free((void*)c); + break; + } +BREAK2: + decl_panel->hide(); +} + +/** + Write the code to the source and header files. + \todo There are a lot of side effect in this node depending on the given text + and the parent node. They need to be understood and documented. + */ +void Decl_Node::write_code1(fld::io::Code_Writer& f) { + const char* c = name(); + if (!c) return; + // handle a few keywords differently if inside a class + if (is_in_class() && ( (!strncmp(c,"class",5) && isspace(c[5])) + || (!strncmp(c,"typedef",7) && isspace(c[7])) + || (!strncmp(c,"FL_EXPORT",9) && isspace(c[9])) + || (!strncmp(c,"struct",6) && isspace(c[6])) + || (!strncmp(c,"enum",4) && isspace(c[4])) + ) ) { + f.write_public(public_); + write_comment_h(f, f.indent(1)); + f.write_h("%s%s\n", f.indent(1), c); + return; + } + // handle putting #include, extern, using or typedef into decl: + if ( (!isalpha(*c) && *c != '~') + || (!strncmp(c,"extern",6) && isspace(c[6])) + || (!strncmp(c,"class",5) && isspace(c[5])) + || (!strncmp(c,"typedef",7) && isspace(c[7])) + || (!strncmp(c,"using",5) && isspace(c[5])) + || (!strncmp(c,"FL_EXPORT",9) && isspace(c[9])) + // || !strncmp(c,"struct",6) && isspace(c[6]) + ) { + if (public_) { + write_comment_h(f); + f.write_h("%s\n", c); + } else { + write_comment_c(f); + f.write_c("%s\n", c); + } + return; + } + // find the first C++ style comment + const char* e = c+strlen(c), *csc = c; + while (csc<e && (csc[0]!='/' || csc[1]!='/')) csc++; + if (csc!=e) e = csc; // comment found + // lose spaces between text and comment, if any + while (e>c && e[-1]==' ') e--; + if (class_name(1)) { + f.write_public(public_); + write_comment_h(f, f.indent(1)); + f.write_hc(f.indent(1), int(e-c), c, csc); + } else { + if (public_) { + if (static_) + f.write_h("extern "); + else + write_comment_h(f); + f.write_hc("", int(e-c), c, csc); + + if (static_) { + write_comment_c(f); + f.write_cc("", int(e-c), c, csc); + } + } else { + write_comment_c(f); + if (static_) + f.write_c("static "); + f.write_cc("", int(e-c), c, csc); + } + } +} + +// ---- Data_Node declaration + +/** \class Data_Node + Manage data from an external arbitrary file. + + The content of the file will be stored in binary inside the generated + code. This can be used to store images inline in the source code, + */ + +/// Prototype for a data node to be used by the factory. +Data_Node Data_Node::prototype; + +/** + Constructor. + */ +Data_Node::Data_Node() : + Decl_Node(), + filename_(nullptr), + text_mode_(0) +{ } + +/** + Destructor. + */ +Data_Node::~Data_Node() { + if (filename_) + free((void*)filename_); +} + +/** + Create an empty inline data node. + \param[in] strategy add after current or as last child + \return new inline data node + */ +Node *Data_Node::make(Strategy strategy) { + Node *anchor = Fluid.proj.tree.current, *p = anchor; + if (p && (strategy.placement() == Strategy::AFTER_CURRENT)) + p = p->parent; + while (p && !p->is_decl_block()) { + anchor = p; + strategy.placement(Strategy::AFTER_CURRENT); + p = p->parent; + } + Data_Node *o = new Data_Node(); + o->public_ = 1; + o->static_ = 1; + o->filename_ = nullptr; + o->text_mode_ = 0; + o->name("myInlineData"); + o->add(anchor, strategy); + o->factory = this; + return o; +} + +/** + Write additional properties. + - "filename" followed by the filename of the file to inline + - "textmode" if data is written in ASCII vs. binary + */ +void Data_Node::write_properties(fld::io::Project_Writer &f) { + Decl_Node::write_properties(f); + if (filename_) { + f.write_string("filename"); + f.write_word(filename_); + } + if (text_mode_ == 1) { + f.write_string("textmode"); + } + if (text_mode_ == 2) { + f.write_string("compressed"); + } +} + +/** + Read specific properties. + */ +void Data_Node::read_property(fld::io::Project_Reader &f, const char *c) { + if (!strcmp(c,"filename")) { + storestring(f.read_word(), filename_, 1); + } else if (!strcmp(c,"textmode")) { + text_mode_ = 1; + } else if (!strcmp(c,"compressed")) { + text_mode_ = 2; + } else { + Decl_Node::read_property(f, c); + } +} + +/** + Open the data_panel to edit this node. + */ +void Data_Node::open() { + if (!data_panel) make_data_panel(); + data_input->value(name()); + if (is_in_class()) { + data_class_choice->value(public_); + data_class_choice->show(); + data_choice->hide(); + } else { + data_choice->value((public_&1)|((static_&1)<<1)); + data_choice->show(); + data_class_choice->hide(); + } + data_mode->value(text_mode_); + data_filename->value(filename_?filename_:""); + const char *c = comment(); + data_comment_input->buffer()->text(c?c:""); + data_panel->show(); + for (;;) { // repeat as long as there are errors + for (;;) { + Fl_Widget* w = Fl::readqueue(); + if (w == data_panel_cancel) goto BREAK2; + else if (w == data_panel_ok) break; + else if (w == data_filebrowser) { + Fluid.proj.enter_project_dir(); + const char *fn = fl_file_chooser("Load Inline Data", nullptr, data_filename->value(), 1); + Fluid.proj.leave_project_dir(); + if (fn) { + if (strcmp(fn, data_filename->value())) + Fluid.proj.set_modflag(1); + data_filename->value(fn); + } + } + else if (!w) Fl::wait(); + } + // store the variable name: + const char*c = data_input->value(); + char *s = fl_strdup(c), *p = s, *q, *n; + for (;;++p) { // remove leading spaces + if (!isspace((unsigned char)(*p))) break; + } + n = p; + if ( (!isalpha((unsigned char)(*p))) && ((*p)!='_') && ((*p)!=':') ) goto OOPS; + ++p; + for (;;++p) { + if ( (!isalnum((unsigned char)(*p))) && ((*p)!='_') && ((*p)!=':') ) break; + } + q = p; + for (;;++q) { + if (!*q) break; + if (!isspace((unsigned char)(*q))) goto OOPS; + } + *p = 0; // remove trailing spaces + if (n==q) { + OOPS: + int v = fl_choice("%s", + "Continue Editing", "Ignore Error", nullptr, + "Variable name must be a C identifier"); + if (v==0) { free(s); continue; } // Continue Editing + //if (v==1) { } // Ignore Error and close dialog + } + Fluid.proj.undo.checkpoint(); + name(n); + free(s); + // store flags + if (is_in_class()) { + if (public_!=data_class_choice->value()) { + Fluid.proj.set_modflag(1); + public_ = data_class_choice->value(); + } + } else { + if (public_!=(data_choice->value()&1)) { + Fluid.proj.set_modflag(1); + public_ = (data_choice->value()&1); + } + if (static_!=((data_choice->value()>>1)&1)) { + Fluid.proj.set_modflag(1); + static_ = ((data_choice->value()>>1)&1); + } + } + text_mode_ = data_mode->value(); + if (text_mode_ < 0) text_mode_ = 0; + if (text_mode_ > 2) text_mode_ = 2; + // store the filename + c = data_filename->value(); + if (filename_ && strcmp(filename_, data_filename->value())) + Fluid.proj.set_modflag(1); + else if (!filename_ && *c) + Fluid.proj.set_modflag(1); + if (filename_) { free((void*)filename_); filename_ = nullptr; } + if (c && *c) filename_ = fl_strdup(c); + // store the comment + c = data_comment_input->buffer()->text(); + if (c && *c) { + if (!comment() || strcmp(c, comment())) { Fluid.proj.set_modflag(1); redraw_browser(); } + comment(c); + } else { + if (comment()) { Fluid.proj.set_modflag(1); redraw_browser(); } + comment(nullptr); + } + if (c) free((void*)c); + Fluid.proj.set_modflag(1); + break; + } +BREAK2: + data_panel->hide(); +} + +/** + Write the content of the external file inline into the source code. + */ +void Data_Node::write_code1(fld::io::Code_Writer& f) { + const char *message = nullptr; + const char *c = name(); + if (!c) return; + const char *fn = filename_; + char *data = nullptr; + int nData = -1; + int uncompressedDataSize = 0; + // path should be set correctly already + if (filename_ && !f.write_codeview) { + Fluid.proj.enter_project_dir(); + FILE *f = fl_fopen(filename_, "rb"); + Fluid.proj.leave_project_dir(); + if (!f) { + message = "Can't include data from file. Can't open"; + } else { + fseek(f, 0, SEEK_END); + nData = (int)ftell(f); + fseek(f, 0, SEEK_SET); + if (nData) { + data = (char*)calloc(nData, 1); + if (fread(data, nData, 1, f)==0) { /* use default */ } + if (text_mode_ == 2) { + uncompressedDataSize = nData; + uLong nzData = compressBound(nData); + Bytef *zdata = (Bytef*)::malloc(nzData); + if (compress(zdata, &nzData, (Bytef*)data, nData) != Z_OK) { /* error */ } + ::free(data); + data = (char*)zdata; + nData = (int)nzData; + } + } + fclose(f); + } + } else { + fn = filename_ ? filename_ : "<no filename>"; + } + if (is_in_class()) { + f.write_public(public_); + if (text_mode_ == 1) { + f.write_h("%sstatic const char *%s;\n", f.indent(1), c); + f.write_c("\n"); + write_comment_c(f); + f.write_c("const char *%s::%s = /* text inlined from %s */\n", class_name(1), c, fn); + if (message) f.write_c("#error %s %s\n", message, fn); + f.write_cstring(data, nData); + } else if (text_mode_ == 2) { + f.write_h("%sstatic int %s_size;\n", f.indent(1), c); + f.write_h("%sstatic unsigned char %s[%d];\n", f.indent(1), c, nData); + f.write_c("\n"); + write_comment_c(f); + f.write_c("int %s::%s_size = %d;\n", class_name(1), c, uncompressedDataSize); + f.write_c("unsigned char %s::%s[%d] = /* data compressed and inlined from %s */\n", class_name(1), c, nData, fn); + if (message) f.write_c("#error %s %s\n", message, fn); + f.write_cdata(data, nData); + } else { + f.write_h("%sstatic unsigned char %s[%d];\n", f.indent(1), c, nData); + f.write_c("\n"); + write_comment_c(f); + f.write_c("unsigned char %s::%s[%d] = /* data inlined from %s */\n", class_name(1), c, nData, fn); + if (message) f.write_c("#error %s %s\n", message, fn); + f.write_cdata(data, nData); + } + f.write_c(";\n"); + } else { + // the "header only" option does not apply here! + if (public_) { + if (static_) { + if (text_mode_ == 1) { + f.write_h("extern const char *%s;\n", c); + f.write_c("\n"); + write_comment_c(f); + f.write_c("const char *%s = /* text inlined from %s */\n", c, fn); + if (message) f.write_c("#error %s %s\n", message, fn); + f.write_cstring(data, nData); + } else if (text_mode_ == 2) { + f.write_h("extern int %s_size;\n", c); + f.write_h("extern unsigned char %s[%d];\n", c, nData); + f.write_c("\n"); + write_comment_c(f); + f.write_c("int %s_size = %d;\n", c, uncompressedDataSize); + f.write_c("unsigned char %s[%d] = /* data compressed and inlined from %s */\n", c, nData, fn); + if (message) f.write_c("#error %s %s\n", message, fn); + f.write_cdata(data, nData); + } else { + f.write_h("extern unsigned char %s[%d];\n", c, nData); + f.write_c("\n"); + write_comment_c(f); + f.write_c("unsigned char %s[%d] = /* data inlined from %s */\n", c, nData, fn); + if (message) f.write_c("#error %s %s\n", message, fn); + f.write_cdata(data, nData); + } + f.write_c(";\n"); + } else { + write_comment_h(f); + f.write_h("#error Unsupported declaration loading inline data %s\n", fn); + if (text_mode_ == 1) + f.write_h("const char *%s = \"abc...\";\n", c); + else + f.write_h("unsigned char %s[3] = { 1, 2, 3 };\n", c); + } + } else { + f.write_c("\n"); + write_comment_c(f); + if (static_) + f.write_c("static "); + if (text_mode_ == 1) { + f.write_c("const char *%s = /* text inlined from %s */\n", c, fn); + if (message) f.write_c("#error %s %s\n", message, fn); + f.write_cstring(data, nData); + } else if (text_mode_ == 2) { + f.write_c("int %s_size = %d;\n", c, uncompressedDataSize); + if (static_) f.write_c("static "); + f.write_c("unsigned char %s[%d] = /* data compressed and inlined from %s */\n", c, nData, fn); + if (message) f.write_c("#error %s %s\n", message, fn); + f.write_cdata(data, nData); + } else { + f.write_c("unsigned char %s[%d] = /* data inlined from %s */\n", c, nData, fn); + if (message) f.write_c("#error %s %s\n", message, fn); + f.write_cdata(data, nData); + } + f.write_c(";\n"); + } + } + // if we are in interactive mode, we pop up a warning dialog + // giving the error: (Fluid.batch_mode && !write_codeview) ??? + if (message && !f.write_codeview) { + if (Fluid.batch_mode) + fprintf(stderr, "FLUID ERROR: %s %s\n", message, fn); + else + fl_alert("%s\n%s\n", message, fn); + } + if (data) free(data); +} + +// ---- DeclBlock_Node declaration + +/** \class DeclBlock_Node + Manage a declaration block. + + Declaration blocks have two text field that are written before and after + the children of this block. This block is located at the top level and + is written to the source file, and to the header file, if declared public. + */ + +/// Prototype for a declaration block to be used by the factory. +DeclBlock_Node DeclBlock_Node::prototype; + +/** + Constructor. + */ +DeclBlock_Node::DeclBlock_Node() : + Node(), + after(nullptr), + write_map_(CODE_IN_SOURCE) +{ } + +/** + Destructor. + */ +DeclBlock_Node::~DeclBlock_Node() { + if (after) + ::free((void*)after); +} + +/** + Return 1 if this block is public. + */ +int DeclBlock_Node::is_public() const { + return ((write_map_&CODE_IN_HEADER) != 0); +} + +/** + Create a new declaration block. + \param[in] strategy add after current or as last child + \return new Declaration Block node + */ +Node *DeclBlock_Node::make(Strategy strategy) { + Node *anchor = Fluid.proj.tree.current, *p = anchor; + if (p && (strategy.placement() == Strategy::AFTER_CURRENT)) p = p->parent; + while (p && !p->is_decl_block()) { + anchor = p; + strategy.placement(Strategy::AFTER_CURRENT); + p = p->parent; + } + DeclBlock_Node *o = new DeclBlock_Node(); + o->name("#if 1"); + o->write_map_ = CODE_IN_SOURCE; + o->after = fl_strdup("#endif"); + o->add(anchor, strategy); + o->factory = this; + return o; +} + +/** + Write the specific properties. + - "public"/"protected" + - "after" followed by the second code block. + */ +void DeclBlock_Node::write_properties(fld::io::Project_Writer &f) { + Node::write_properties(f); + // deprecated + if (is_public()) f.write_string("public"); + // new way to map declaration block to various parts of the generated code + if (write_map_ != CODE_IN_SOURCE) + f.write_string("map %d", write_map_); + f.write_string("after"); + f.write_word(after); +} + +/** + Read the specific properties. + */ +void DeclBlock_Node::read_property(fld::io::Project_Reader &f, const char *c) { + if(!strcmp(c,"public")) { + write_map_ |= CODE_IN_HEADER; + } else if(!strcmp(c,"protected")) { + // + } else if(!strcmp(c,"map")) { + write_map_ = (int)atol(f.read_word()); + } else if (!strcmp(c,"after")) { + storestring(f.read_word(),after); + } else { + Node::read_property(f, c); + } +} + +/** + Open the declblock_panel to edit this node. + */ +void DeclBlock_Node::open() { + // build dialog box + if (!declblock_panel) make_declblock_panel(); + // preset all values + declblock_before_input->value(name()); + declblock_after_input->value(after); + declblock_static_header->value(write_map_ & STATIC_IN_HEADER); + declblock_static_source->value(write_map_ & STATIC_IN_SOURCE); + declblock_code_header->value(write_map_ & CODE_IN_HEADER); + declblock_code_source->value(write_map_ & CODE_IN_SOURCE); + const char *c = comment(); + declblock_comment_input->buffer()->text(c?c:""); + // show modal dialog and loop until satisfied + declblock_panel->show(); + const char* message = nullptr; + for (;;) { // repeat as long as there are errors + for (;;) { + Fl_Widget* w = Fl::readqueue(); + if (w == declblock_panel_cancel) goto BREAK2; + else if (w == declblock_panel_ok) break; + else if (!w) Fl::wait(); + } + // verify user input + const char* a = declblock_before_input->value(); + while (isspace(*a)) a++; + const char* b = declblock_after_input->value(); + while (isspace(*b)) b++; + message = c_check(a&&a[0]=='#' ? a+1 : a); + if (!message) + message = c_check(b&&b[0]=='#' ? b+1 : b); + if (message) { + int v = fl_choice("Potential syntax error detected: %s", + "Continue Editing", "Ignore Error", nullptr, message); + if (v==0) continue; // Continue Editing + //if (v==1) { } // Ignore Error and close dialog + } + // store user choices in data structure + name(a); + storestring(b, after); + if (write_map_ & STATIC_IN_HEADER) { + if (declblock_static_header->value()==0) { + write_map_ &= ~STATIC_IN_HEADER; + Fluid.proj.set_modflag(1); + } + } else { + if (declblock_static_header->value()) { + write_map_ |= STATIC_IN_HEADER; + Fluid.proj.set_modflag(1); + } + } + if (write_map_ & STATIC_IN_SOURCE) { + if (declblock_static_source->value()==0) { + write_map_ &= ~STATIC_IN_SOURCE; + Fluid.proj.set_modflag(1); + } + } else { + if (declblock_static_source->value()) { + write_map_ |= STATIC_IN_SOURCE; + Fluid.proj.set_modflag(1); + } + } + if (write_map_ & CODE_IN_HEADER) { + if (declblock_code_header->value()==0) { + write_map_ &= ~CODE_IN_HEADER; + Fluid.proj.set_modflag(1); + } + } else { + if (declblock_code_header->value()) { + write_map_ |= CODE_IN_HEADER; + Fluid.proj.set_modflag(1); + } + } + if (write_map_ & CODE_IN_SOURCE) { + if (declblock_code_source->value()==0) { + write_map_ &= ~CODE_IN_SOURCE; + Fluid.proj.set_modflag(1); + } + } else { + if (declblock_code_source->value()) { + write_map_ |= CODE_IN_SOURCE; + Fluid.proj.set_modflag(1); + } + } + c = declblock_comment_input->buffer()->text(); + if (c && *c) { + if (!comment() || strcmp(c, comment())) { Fluid.proj.set_modflag(1); redraw_browser(); } + comment(c); + } else { + if (comment()) { Fluid.proj.set_modflag(1); redraw_browser(); } + comment(nullptr); + } + if (c) free((void*)c); + break; + } +BREAK2: + declblock_panel->hide(); +} + +/** + Write the \b before static code to the source file, and to the header file if declared public. + The before code is stored in the name() field. + */ +void DeclBlock_Node::write_static(fld::io::Code_Writer& f) { + const char* c = name(); + if (c && *c) { + if (write_map_ & STATIC_IN_HEADER) + f.write_h("%s\n", c); + if (write_map_ & STATIC_IN_SOURCE) + f.write_c("%s\n", c); + } +} + +/** + Write the \b after static code to the source file, and to the header file if declared public. + */ +void DeclBlock_Node::write_static_after(fld::io::Code_Writer& f) { + const char* c = after; + if (c && *c) { + if (write_map_ & STATIC_IN_HEADER) + f.write_h("%s\n", c); + if (write_map_ & STATIC_IN_SOURCE) + f.write_c("%s\n", c); + } +} + +/** + Write the \b before code to the source file, and to the header file if declared public. + The before code is stored in the name() field. + */ +void DeclBlock_Node::write_code1(fld::io::Code_Writer& f) { + const char* c = name(); + if (c && *c) { + if (write_map_ & CODE_IN_HEADER) + f.write_h("%s\n", c); + if (write_map_ & CODE_IN_SOURCE) + f.write_c("%s\n", c); + } +} + +/** + Write the \b after code to the source file, and to the header file if declared public. + */ +void DeclBlock_Node::write_code2(fld::io::Code_Writer& f) { + const char* c = after; + if (c && *c) { + if (write_map_ & CODE_IN_HEADER) + f.write_h("%s\n", c); + if (write_map_ & CODE_IN_SOURCE) + f.write_c("%s\n", c); + } +} + +// ---- Comment_Node declaration + +/** \class Comment_Node + Manage a comment node. + + The comment field takes one or more lines of ASCII text. If the text starts + with a '/' and a '*', Fluid assumes that the text is already formatted. If not, + every line will be preceded with "// ". + */ + +/// Prototype for a comment node to be used by the factory. +Comment_Node Comment_Node::prototype; + +/** + Constructor. + */ +Comment_Node::Comment_Node() : + in_c_(1), + in_h_(1), + style_(0) +{ } + +/** + Make a new comment node. + \param[in] strategy add after current or as last child + \return new Comment node + */ +Node *Comment_Node::make(Strategy strategy) { + Node *anchor = Fluid.proj.tree.current, *p = anchor; + if (p && (strategy.placement() == Strategy::AFTER_CURRENT)) + p = p->parent; + while (p && !p->is_code_block()) { + anchor = p; + strategy.placement(Strategy::AFTER_CURRENT); + p = p->parent; + } + Comment_Node *o = new Comment_Node(); + o->in_c_ = 1; + o->in_h_ = 1; + o->style_ = 0; + o->name("my comment"); + o->add(anchor, strategy); + o->factory = this; + return o; +} + +/** + Write respective properties. + - "in_source"/"not_in_source" if the comment will be written to the source code + - "in_header"/"not_in_header" if the comment will be written to the header file + */ +void Comment_Node::write_properties(fld::io::Project_Writer &f) { + Node::write_properties(f); + if (in_c_) f.write_string("in_source"); else f.write_string("not_in_source"); + if (in_h_) f.write_string("in_header"); else f.write_string("not_in_header"); +} + +/** + Read extra properties. + */ +void Comment_Node::read_property(fld::io::Project_Reader &f, const char *c) { + if (!strcmp(c,"in_source")) { + in_c_ = 1; + } else if (!strcmp(c,"not_in_source")) { + in_c_ = 0; + } else if (!strcmp(c,"in_header")) { + in_h_ = 1; + } else if (!strcmp(c,"not_in_header")) { + in_h_ = 0; + } else { + Node::read_property(f, c); + } +} + +/** + Load available preset comments. + Fluid comes with GPL and LGPL preset for comments. Users can + add their own presets which are stored per user in a separate + preferences database. + */ +static void load_comments_preset(Fl_Preferences &menu) { + static const char * const predefined_comment[] = { + "GNU Public License v3/GPL Header", "GNU Public License v3/GPL Footer", + "GNU Public License v3/LGPL Header", "GNU Public License v3/LGPL Footer", + "FLTK/Header" }; + int i, n; + menu.get("n", n, -1); + if (n == -1) menu.set("n", 5); + menu.set("version", 10400); + Fl_Preferences db(Fl_Preferences::USER_L, "fltk.org", "fluid_comments"); + for (i=0; i<5; i++) { + menu.set(Fl_Preferences::Name(i), predefined_comment[i]); + db.set(predefined_comment[i], comment_text[i]); + } +} + +/** + Open the comment_panel to edit this node. + */ +void Comment_Node::open() { + if (!comment_panel) make_comment_panel(); + const char *text = name(); + { + int i=0, n=0, version = 0; + Fl_Preferences menu(Fl_Preferences::USER_L, "fltk.org", "fluid_comments_menu"); + comment_predefined->clear(); + comment_predefined->add("_Edit/Add current comment..."); + comment_predefined->add("_Edit/Remove last selection..."); + menu.get("version", version, -1); + if (version < 10400) load_comments_preset(menu); + menu.get("n", n, 0); + for (i=0;i<n;i++) { + char *text; + menu.get(Fl_Preferences::Name(i), text, ""); + comment_predefined->add(text); + free(text); + } + } + comment_input->buffer()->text( text ? text : "" ); + comment_in_source->value(in_c_); + comment_in_header->value(in_h_); + comment_panel->show(); + char itempath[FL_PATH_MAX]; itempath[0] = 0; + int last_selected_item = 0; + for (;;) { // repeat as long as there are errors + for (;;) { + Fl_Widget* w = Fl::readqueue(); + if (w == comment_panel_cancel) goto BREAK2; + else if (w == comment_panel_ok) break; + else if (w == comment_predefined) { + if (comment_predefined->value()==1) { + // add the current comment to the database + const char *xname = fl_input( + "Please enter a name to reference the current\ncomment in your database.\n\n" + "Use forward slashes '/' to create submenus.", + "My Comment"); + if (xname) { + char *name = fl_strdup(xname); + for (char*s=name;*s;s++) if (*s==':') *s = ';'; + int n; + Fl_Preferences db(Fl_Preferences::USER_L, "fltk.org", "fluid_comments"); + db.set(name, comment_input->buffer()->text()); + Fl_Preferences menu(Fl_Preferences::USER_L, "fltk.org", "fluid_comments_menu"); + menu.get("n", n, 0); + menu.set(Fl_Preferences::Name(n), name); + menu.set("n", ++n); + comment_predefined->add(name); + free(name); + } + } else if (comment_predefined->value()==2) { + // remove the last selected comment from the database + if (itempath[0]==0 || last_selected_item==0) { + fl_message("Please select an entry from this menu first."); + } else if (fl_choice("Are you sure that you want to delete the entry\n" + "\"%s\"\nfrom the database?", "Cancel", "Delete", + nullptr, itempath)) { + Fl_Preferences db(Fl_Preferences::USER_L, "fltk.org", "fluid_comments"); + db.deleteEntry(itempath); + comment_predefined->remove(last_selected_item); + Fl_Preferences menu(Fl_Preferences::USER_L, "fltk.org", "fluid_comments_menu"); + int i, n; + for (i=4, n=0; i<comment_predefined->size(); i++) { + const Fl_Menu_Item *mi = comment_predefined->menu()+i; + if (comment_predefined->item_pathname(itempath, 255, mi)==0) { + if (itempath[0]=='/') memmove(itempath, itempath+1, 255); + if (itempath[0]) menu.set(Fl_Preferences::Name(n++), itempath); + } + } + menu.set("n", n); + } + } else { + // load the selected comment from the database + if (comment_predefined->item_pathname(itempath, 255)==0) { + if (itempath[0]=='/') memmove(itempath, itempath+1, 255); + Fl_Preferences db(Fl_Preferences::USER_L, "fltk.org", "fluid_comments"); + char *text; + db.get(itempath, text, "(no text found in data base)"); + comment_input->buffer()->text(text); + free(text); + last_selected_item = comment_predefined->value(); + } + } + } + else if (w == comment_load) { + // load a comment from disk + fl_file_chooser_ok_label("Use File"); + const char *fname = fl_file_chooser("Pick a comment", nullptr, nullptr); + fl_file_chooser_ok_label(nullptr); + if (fname) { + if (comment_input->buffer()->loadfile(fname)) { + fl_alert("Error loading file\n%s", fname); + } + } + } + else if (!w) Fl::wait(); + } + char*c = comment_input->buffer()->text(); + name(c); + free(c); + int mod = 0; + if (in_c_ != comment_in_source->value()) { + in_c_ = comment_in_source->value(); + mod = 1; + } + if (in_h_ != comment_in_header->value()) { + in_h_ = comment_in_header->value(); + mod = 1; + } + if (mod) Fluid.proj.set_modflag(1); + break; + } +BREAK2: + comment_panel->hide(); +} + +/** + Write the comment to the files. + */ +void Comment_Node::write_code1(fld::io::Code_Writer& f) { + const char* c = name(); + if (!c) return; + if (!in_c_ && !in_h_) return; + // find out if there is already a valid comment: + const char *s = c; + while (isspace(*s)) s++; + // if this seems to be a C style comment, copy the block as is + // (it's up to the user to correctly close the comment) + if (s[0]=='/' && s[1]=='*') { + if (in_h_) f.write_h("%s\n", c); + if (in_c_) f.write_c("%s\n", c); + return; + } + // copy the comment line by line, add the double slash if needed + char *txt = fl_strdup(c); + char *b = txt, *e = txt; + for (;;) { + // find the end of the line and set it to NUL + while (*e && *e!='\n') e++; + char eol = *e; + *e = 0; + // check if there is a C++ style comment at the beginning of the line + char *s = b; + while (isspace(*s)) s++; + if (s!=e && ( s[0]!='/' || s[1]!='/') ) { + // if no comment marker was found, we add one ourselves + if (in_h_) f.write_h("// "); + if (in_c_) f.write_c("// "); + } + // now copy the rest of the line + if (in_h_) f.write_h("%s\n", b); + if (in_c_) f.write_c("%s\n", b); + if (eol==0) break; + *e++ = eol; + b = e; + } + free(txt); +} + +// ---- Class_Node declaration + +/** \class Class_Node + Manage a class declaration and implementation. + */ + +/// Prototype for a class node to be used by the factory. +Class_Node Class_Node::prototype; + +/** + Constructor. + */ +Class_Node::Class_Node() : + Node(), + subclass_of(nullptr), + public_(1), + class_prefix(nullptr) +{ } + +/** + Destructor. + */ +Class_Node::~Class_Node() { + if (subclass_of) + free((void*)subclass_of); + if (class_prefix) + free((void*)class_prefix); +} + +/** + Return 1 if this class is marked public. + */ +int Class_Node::is_public() const { + return public_; +} + +/** + Set the prefixx string. + */ +void Class_Node::prefix(const char*p) { + free((void*) class_prefix); + class_prefix=fl_strdup(p ? p : "" ); +} + +/** + Make a new class node. + \param[in] strategy add after current or as last child + \return new Class node + */ +Node *Class_Node::make(Strategy strategy) { + Node *anchor = Fluid.proj.tree.current, *p = anchor; + if (p && (strategy.placement() == Strategy::AFTER_CURRENT)) + p = p->parent; + while (p && !p->is_decl_block()) { + anchor = p; + strategy.placement(Strategy::AFTER_CURRENT); + p = p->parent; + } + Class_Node *o = new Class_Node(); + o->name("UserInterface"); + o->class_prefix = nullptr; + o->subclass_of = nullptr; + o->public_ = 1; + o->add(anchor, strategy); + o->factory = this; + return o; +} + +/** + Write the respective properties. + - ":" followed by the super class + - "private"/"protected" + */ +void Class_Node::write_properties(fld::io::Project_Writer &f) { + Node::write_properties(f); + if (subclass_of) { + f.write_string(":"); + f.write_word(subclass_of); + } + switch (public_) { + case 0: f.write_string("private"); break; + case 2: f.write_string("protected"); break; + } +} + +/** + Read additional properties. + */ +void Class_Node::read_property(fld::io::Project_Reader &f, const char *c) { + if (!strcmp(c,"private")) { + public_ = 0; + } else if (!strcmp(c,"protected")) { + public_ = 2; + } else if (!strcmp(c,":")) { + storestring(f.read_word(), subclass_of); + } else { + Node::read_property(f, c); + } +} + +/** + Open the class_panel to edit the class name and superclass name. + */ +void Class_Node::open() { + if (!class_panel) make_class_panel(); + char fullname[FL_PATH_MAX]=""; + if (prefix() && strlen(prefix())) + sprintf(fullname,"%s %s",prefix(),name()); + else + strcpy(fullname, name()); + c_name_input->value(fullname); + c_subclass_input->value(subclass_of); + c_public_button->value(public_); + const char *c = comment(); + c_comment_input->buffer()->text(c?c:""); + class_panel->show(); + const char* message = nullptr; + + char *na=nullptr,*pr=nullptr,*p=nullptr; // name and prefix substrings + + for (;;) { // repeat as long as there are errors + // we don;t give the option to ignore this error here because code depends + // on this being a C++ identifier + if (message) fl_alert("%s", message); + for (;;) { + Fl_Widget* w = Fl::readqueue(); + if (w == c_panel_cancel) goto BREAK2; + else if (w == c_panel_ok) break; + else if (!w) Fl::wait(); + } + const char*c = c_name_input->value(); + char *s = fl_strdup(c); + size_t len = strlen(s); + if (!*s) goto OOPS; + p = (char*) (s+len-1); + while (p>=s && isspace(*p)) *(p--)='\0'; + if (p<s) goto OOPS; + while (p>=s && is_id(*p)) p--; + if ( (p<s && !is_id(*(p+1))) || !*(p+1) ) { + OOPS: message = "class name must be C++ identifier"; + free((void*)s); + continue; + } + na=p+1; // now we have the name + if(p>s) *p--='\0'; + while (p>=s && isspace(*p)) *(p--)='\0'; + while (p>=s && is_id(*p)) p--; + if (p<s) p++; + if (is_id(*p) && p<na) pr=p; // prefix detected + c = c_subclass_input->value(); + message = c_check(c); + if (message) { free((void*)s);continue;} + name(na); + prefix(pr); + free((void*)s); + storestring(c, subclass_of); + if (public_ != c_public_button->value()) { + public_ = c_public_button->value(); + Fluid.proj.set_modflag(1); + } + c = c_comment_input->buffer()->text(); + if (c && *c) { + if (!comment() || strcmp(c, comment())) { Fluid.proj.set_modflag(1); redraw_browser(); } + comment(c); + } else { + if (comment()) { Fluid.proj.set_modflag(1); redraw_browser(); } + comment(nullptr); + } + if (c) free((void*)c); + break; + } +BREAK2: + class_panel->hide(); +} + +/** + Write the header code that declares this class. + */ +void Class_Node::write_code1(fld::io::Code_Writer& f) { + parent_class = current_class; + current_class = this; + write_public_state = 0; + f.write_h("\n"); + write_comment_h(f); + if (prefix() && strlen(prefix())) + f.write_h("class %s %s ", prefix(), name()); + else + f.write_h("class %s ", name()); + if (subclass_of) f.write_h(": %s ", subclass_of); + f.write_h("{\n"); +} + +/** + Write the header code that ends the declaration of this class. + */ +void Class_Node::write_code2(fld::io::Code_Writer& f) { + f.write_h("};\n"); + current_class = parent_class; +} + +/** + Return 1 if this class contains a function with the given signature. + */ +int Node::has_function(const char *rtype, const char *sig) const { + Node *child; + for (child = next; child && child->level > level; child = child->next) { + if (child->level == level+1 && child->is_a(Type::Function)) { + const Function_Node *fn = (const Function_Node*)child; + if (fn->has_signature(rtype, sig)) + return 1; + } + } + return 0; +} |
