diff options
Diffstat (limited to 'fluid/io/code.cxx')
| -rw-r--r-- | fluid/io/code.cxx | 1107 |
1 files changed, 1107 insertions, 0 deletions
diff --git a/fluid/io/code.cxx b/fluid/io/code.cxx new file mode 100644 index 000000000..b77cad147 --- /dev/null +++ b/fluid/io/code.cxx @@ -0,0 +1,1107 @@ +// +// Code output routines 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 "io/code.h" + +#include "app/fluid.h" +#include "app/undo.h" +#include "io/file.h" +#include "nodes/Fl_Group_Type.h" +#include "nodes/Fl_Window_Type.h" +#include "nodes/Fl_Function_Type.h" +#include "tools/fluid_filename.h" + +#include <FL/Fl.H> +#include <FL/fl_string_functions.h> +#include <FL/fl_ask.H> +#include "../src/flstring.h" + +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> + +#include <zlib.h> + +/// \defgroup cfile C Code File Operations +/// \{ + + +/** + Return true if c can be in a C identifier. + I needed this so it is not messed up by locale settings. + \param[in] c a character, or the start of a utf-8 sequence + \return 1 if c is alphanumeric or '_' + */ +int is_id(char c) { + return (c>='a' && c<='z') || (c>='A' && c<='Z') || (c>='0' && c<='9') || c=='_'; +} + +/** + Write a string to a file, replacing all non-ASCII characters with octal codes. + \param[in] out output file + \param[in] text write this NUL terminated utf-8 string + \return EOF if any of the file access calls failed, 0 if OK + */ +int write_escaped_strings(FILE *out, const char *text) { + int ret = 0; + const unsigned char *utf8_text = (const unsigned char *)text; + for (const unsigned char *s = utf8_text; *s; ++s) { + unsigned char c = *s; + // escape control characters, delete, all utf-8, and the double quotes + // note: we should have an option in the project settings to allow utf-8 + // characters in the output text and not escape them + if (c < 32 || c > 126 || c == '\"') { + if (c == '\r') { + ret = fputs("\\r", out); + } else if (c == '\n') { + ret = fputs("\\n", out); + } else { + ret = fprintf(out, "\\%03o", c); + } + } else { + ret = putc((int)c, out); + } + } + return ret; +} + +/** + Write a file that contains all label and tooltip strings for internationalization. + The user is responsible to set the right file name extension. The file format + is determined by `g_project.i18n_type`. + \param[in] filename file path and name to a file that will hold the strings + \return 1 if the file could not be opened for writing, or the result of `fclose`. + */ +int write_strings(const std::string &filename) { + Fl_Type *p; + Fl_Widget_Type *w; + int i; + + FILE *fp = fl_fopen(filename.c_str(), "wb"); + if (!fp) return 1; + + switch (g_project.i18n_type) { + case FD_I18N_NONE : /* None, just put static text out */ + fprintf(fp, "# generated by Fast Light User Interface Designer (fluid) version %.4f\n", + FL_VERSION); + for (p = Fl_Type::first; p; p = p->next) { + if (p->is_widget()) { + w = (Fl_Widget_Type *)p; + + if (w->label()) { + write_escaped_strings(fp, w->label()); + putc('\n', fp); + } + + if (w->tooltip()) { + write_escaped_strings(fp, w->tooltip()); + putc('\n', fp); + } + } + } + break; + case FD_I18N_GNU : /* GNU gettext, put a .po file out */ + fprintf(fp, "# generated by Fast Light User Interface Designer (fluid) version %.4f\n", + FL_VERSION); + for (p = Fl_Type::first; p; p = p->next) { + if (p->is_widget()) { + w = (Fl_Widget_Type *)p; + + if (w->label()) { + fputs("msgid \"", fp); + write_escaped_strings(fp, w->label()); + fputs("\"\n", fp); + + fputs("msgstr \"", fp); + write_escaped_strings(fp, w->label()); + fputs("\"\n", fp); + } + + if (w->tooltip()) { + fputs("msgid \"", fp); + write_escaped_strings(fp, w->tooltip()); + fputs("\"\n", fp); + + fputs("msgstr \"", fp); + write_escaped_strings(fp, w->tooltip()); + fputs("\"\n", fp); + } + } + } + break; + case FD_I18N_POSIX : /* POSIX catgets, put a .msg file out */ + fprintf(fp, "$ generated by Fast Light User Interface Designer (fluid) version %.4f\n", + FL_VERSION); + fprintf(fp, "$set %s\n", g_project.i18n_pos_set.c_str()); + fputs("$quote \"\n", fp); + + for (i = 1, p = Fl_Type::first; p; p = p->next) { + if (p->is_widget()) { + w = (Fl_Widget_Type *)p; + + if (w->label()) { + fprintf(fp, "%d \"", i ++); + write_escaped_strings(fp, w->label()); + fputs("\"\n", fp); + } + + if (w->tooltip()) { + fprintf(fp, "%d \"", i ++); + write_escaped_strings(fp, w->tooltip()); + fputs("\"\n", fp); + } + } + } + break; + } + + return fclose(fp); +} + +//////////////////////////////////////////////////////////////// +// Generate unique but human-readable identifiers: + +/** A binary searchable tree storing identifiers for quick retrieval. */ +struct Fd_Identifier_Tree { + char* text; + void* object; + Fd_Identifier_Tree *left, *right; + Fd_Identifier_Tree (const char* t, void* o) : text(fl_strdup(t)), object(o) {left = right = 0;} + ~Fd_Identifier_Tree(); +}; + +Fd_Identifier_Tree::~Fd_Identifier_Tree() { + delete left; + free((void *)text); + delete right; +} + +/** \brief Return a unique name for the given object. + + This function combines the name and label into an identifier. It then checks + if that id was already taken by another object, and if so, appends a + hexadecimal value which is incremented until the id is unique in this file. + + If a new id was created, it is stored in the id tree. + + \param[in] o create an ID for this object + \param[in] type is the first word of the ID + \param[in] name if name is set, it is appended to the ID + \param[in] label else if label is set, it is appended, skipping non-keyword characters + \return buffer to a unique identifier, managed by Fd_Code_Writer, so caller must NOT free() it + */ +const char* Fd_Code_Writer::unique_id(void* o, const char* type, const char* name, const char* label) { + char buffer[128]; + char* q = buffer; + char* q_end = q + 128 - 8 - 1; // room for hex number and NUL + while (*type) *q++ = *type++; + *q++ = '_'; + const char* n = name; + if (!n || !*n) n = label; + if (n && *n) { + while (*n && !is_id(*n)) n++; + while (is_id(*n) && (q < q_end)) *q++ = *n++; + } + *q = 0; + // okay, search the tree and see if the name was already used: + Fd_Identifier_Tree** p = &id_root; + int which = 0; + while (*p) { + int i = strcmp(buffer, (*p)->text); + if (!i) { + if ((*p)->object == o) return (*p)->text; + // already used, we need to pick a new name: + sprintf(q,"%x",++which); + p = &id_root; + continue; + } + else if (i < 0) p = &((*p)->left); + else p = &((*p)->right); + } + *p = new Fd_Identifier_Tree(buffer, o); + return (*p)->text; +} + +//////////////////////////////////////////////////////////////// +// return current indentation: + + +/** + Return a C string that indents code to the given depth. + + Indentation can be changed by modifying the multiplicator (``*2`` to keep + the FLTK indent style). Changing `spaces` to a list of tabs would generate + tab indents instead. This function can also be used for fixed depth indents + in the header file. + + Do *not* ever make this a user preference, or you will end up writing a + fully featured code formatter. + + \param[in] set generate this indent depth + \return pointer to a static string + */ +const char *Fd_Code_Writer::indent(int set) { + static const char* spaces = " "; + int i = set * 2; + if (i>32) i = 32; + if (i<0) i = 0; + return spaces+32-i; +} + +/** + Return a C string that indents code to the current source file depth. + \return pointer to a static string + */ +const char *Fd_Code_Writer::indent() { + return indent(indentation); +} + +/** + Return a C string that indents code to the current source file depth plus an offset. + \param[in] offset adds a temporary offset for this call only; this does not + change the `indentation` variable; offset can be negative + \return pointer to a static string + */ +const char *Fd_Code_Writer::indent_plus(int offset) { + return indent(indentation+offset); +} + + +//////////////////////////////////////////////////////////////// +// declarations/include files: +// Each string generated by write_h_once is written only once to +// the header file. This is done by keeping a binary tree of all +// the calls so far and not printing it if it is in the tree. + +/** A binary searchable tree storing text for quick retrieval. */ +struct Fd_Text_Tree { + char *text; + Fd_Text_Tree *left, *right; + Fd_Text_Tree(const char *t) { + text = fl_strdup(t); + left = right = 0; + } + ~Fd_Text_Tree(); +}; + +Fd_Text_Tree::~Fd_Text_Tree() { + delete left; + free((void *)text); + delete right; +} + +/** A binary searchable tree storing pointers for quick retrieval. */ +struct Fd_Pointer_Tree { + void *ptr; + Fd_Pointer_Tree *left, *right; + Fd_Pointer_Tree(void *p) { + ptr = p; + left = right = 0; + } + ~Fd_Pointer_Tree(); +}; + +Fd_Pointer_Tree::~Fd_Pointer_Tree() { + delete left; + delete right; +} + +/** + Print a formatted line to the header file, unless the same line was produced before in this header file. + \note Resulting line is cropped at 1023 bytes. + \param[in] format printf-style formatting text, followed by a vararg list + \return 1 if the text was written to the file, 0 if it was previously written. + */ +int Fd_Code_Writer::write_h_once(const char *format, ...) { + va_list args; + char buf[1024]; + va_start(args, format); + vsnprintf(buf, sizeof(buf), format, args); + va_end(args); + Fd_Text_Tree **p = &text_in_header; + while (*p) { + int i = strcmp(buf,(*p)->text); + if (!i) return 0; + else if (i < 0) p = &((*p)->left); + else p = &((*p)->right); + } + fprintf(header_file,"%s\n",buf); + *p = new Fd_Text_Tree(buf); + return 1; +} + +/** + Print a formatted line to the source file, unless the same line was produced before in this code file. + \note Resulting line is cropped at 1023 bytes. + \param[in] format printf-style formatting text, followed by a vararg list + \return 1 if the text was written to the file, 0 if it was previously written. + */ +int Fd_Code_Writer::write_c_once(const char *format, ...) { + va_list args; + char buf[1024]; + va_start(args, format); + vsnprintf(buf, sizeof(buf), format, args); + va_end(args); + Fd_Text_Tree **p = &text_in_header; + while (*p) { + int i = strcmp(buf,(*p)->text); + if (!i) return 0; + else if (i < 0) p = &((*p)->left); + else p = &((*p)->right); + } + p = &text_in_code; + while (*p) { + int i = strcmp(buf,(*p)->text); + if (!i) return 0; + else if (i < 0) p = &((*p)->left); + else p = &((*p)->right); + } + crc_printf("%s\n", buf); + *p = new Fd_Text_Tree(buf); + return 1; +} + +/** + Return true if this pointer was already included in the code file. + If it was not, add it to the list and return false. + \param[in] pp ay pointer + \return true if found in the tree, false if added to the tree + */ +bool Fd_Code_Writer::c_contains(void *pp) { + Fd_Pointer_Tree **p = &ptr_in_code; + while (*p) { + if ((*p)->ptr == pp) return true; + else if ((*p)->ptr < pp) p = &((*p)->left); + else p = &((*p)->right); + } + *p = new Fd_Pointer_Tree(pp); + return false; +} + +/** + Write a C string to the code file, escaping non-ASCII characters. + + Text is broken into lines of 78 character. + FLUID " before and after every line text. + + A list of control characters and ", ', and \\ are escaped by adding a \\ in + front of them. Escape ?? by writing ?\\?. All other characters that are not + between 32 and 126 inclusive will be escaped as octal characters. + + This function is utf8 agnostic. + + \param[in] s write this string + \param[in] length write so many bytes in this string + + \see f.write_cstring(const char*) + */ +void Fd_Code_Writer::write_cstring(const char *s, int length) { + const char *next_line = "\"\n\""; + if (varused_test) { + varused = 1; + return; + } + // if we are rendering to the source code preview window, and the text is + // longer than four lines, we only render a placeholder. + if (write_codeview && ((s==NULL) || (length>300))) { + if (length>=0) + crc_printf("\" ... %d bytes of text... \"", length); + else + crc_puts("\" ... text... \""); + return; + } + if (length==-1 || s==0L) { + crc_puts("\n#error string not found\n"); + crc_puts("\" ... undefined size text... \""); + return; + } + + const char *p = s; + const char *e = s+length; + int linelength = 1; + crc_putc('\"'); + for (; p < e;) { + int c = *p++; + switch (c) { + case '\b': c = 'b'; goto QUOTED; + case '\t': c = 't'; goto QUOTED; + case '\n': c = 'n'; goto QUOTED; + case '\f': c = 'f'; goto QUOTED; + case '\r': c = 'r'; goto QUOTED; + case '\"': + case '\'': + case '\\': + QUOTED: + if (linelength >= 77) { crc_puts(next_line); linelength = 0; } + crc_putc('\\'); + crc_putc(c); + linelength += 2; + break; + case '?': // prevent trigraphs by writing ?? as ?\? + if (p-2 >= s && *(p-2) == '?') goto QUOTED; + // else fall through: + default: + if (c >= ' ' && c < 127) { + // a legal ASCII character + if (linelength >= 78) { crc_puts(next_line); linelength = 0; } + crc_putc(c); + linelength++; + break; + } + // if the UTF-8 option is checked, write unicode characters verbatim + if (g_project.utf8_in_src && (c&0x80)) { + if ((c&0x40)) { + // This is the first character in a utf-8 sequence (0b11......). + // A line break would be ok here. Do not put linebreak in front of + // following characters (0b10......) + if (linelength >= 78) { crc_puts(next_line); linelength = 0; } + } + crc_putc(c); + linelength++; + break; + } + // otherwise we must print it as an octal constant: + c &= 255; + if (linelength >= 74) { crc_puts(next_line); linelength = 0; } + crc_printf("\\%03o", c); + linelength += 4; + break; + } + } + crc_putc('\"'); +} + +/** + Write a C string, escaping non-ASCII characters. + \param[in] s write this string + \see f.write_cstring(const char*, int) + */ +void Fd_Code_Writer::write_cstring(const char *s) { + write_cstring(s, (int)strlen(s)); +} + +/** + Write an array of C binary data (does not add a null). + The output is bracketed in { and }. The content is written + as decimal bytes, i.e. `{ 1, 2, 200 }` + + \param[in] s a block of binary data, interpreted as unsigned bytes + \param[in] length size of the block in bytes + */ +void Fd_Code_Writer::write_cdata(const char *s, int length) { + if (varused_test) { + varused = 1; + return; + } + if (write_codeview) { + if (length>=0) + crc_printf("{ /* ... %d bytes of binary data... */ }", length); + else + crc_puts("{ /* ... binary data... */ }"); + return; + } + if (length==-1) { + crc_puts("\n#error data not found\n"); + crc_puts("{ /* ... undefined size binary data... */ }"); + return; + } + const unsigned char *w = (const unsigned char *)s; + const unsigned char *e = w+length; + int linelength = 1; + crc_putc('{'); + for (; w < e;) { + unsigned char c = *w++; + if (c>99) linelength += 4; + else if (c>9) linelength += 3; + else linelength += 2; + if (linelength >= 77) {crc_puts("\n"); linelength = 0;} + crc_printf("%d", c); + if (w<e) crc_putc(','); + } + crc_putc('}'); +} + +/** + Print a formatted line to the source file. + \param[in] format printf-style formatting text + \param[in] args list of arguments + */ +void Fd_Code_Writer::vwrite_c(const char* format, va_list args) { + if (varused_test) { + varused = 1; + return; + } + crc_vprintf(format, args); +} + +/** + Print a formatted line to the source file. + \param[in] format printf-style formatting text, followed by a vararg list + */ +void Fd_Code_Writer::write_c(const char* format,...) { + va_list args; + va_start(args, format); + vwrite_c(format, args); + va_end(args); +} + +/** + Write code (c) of size (n) to C file, with optional comment (com) w/o trailing space. + if the code line does not end in a ';' or '}', a ';' will be added. + \param[in] indent indentation string for all lines + \param[in] n number of bytes in code line + \param[in] c line of code + \param[in] com optional commentary + */ +void Fd_Code_Writer::write_cc(const char *indent, int n, const char *c, const char *com) { + write_c("%s%.*s", indent, n, c); + char cc = c[n-1]; + if (cc!='}' && cc!=';') + write_c(";"); + if (*com) + write_c(" %s", com); + write_c("\n"); +} + +/** + Print a formatted line to the header file. + \param[in] format printf-style formatting text, followed by a vararg list + */ +void Fd_Code_Writer::write_h(const char* format,...) { + if (varused_test) return; + va_list args; + va_start(args, format); + vfprintf(header_file, format, args); + va_end(args); +} + +/** + Write code (c) of size (n) to H file, with optional comment (com) w/o trailing space. + if the code line does not end in a ';' or '}', a ';' will be added. + \param[in] indent indentation string for all lines + \param[in] n number of bytes in code line + \param[in] c line of code + \param[in] com optional commentary + */ +void Fd_Code_Writer::write_hc(const char *indent, int n, const char* c, const char *com) { + write_h("%s%.*s", indent, n, c); + char cc = c[n-1]; + if (cc!='}' && cc!=';') + write_h(";"); + if (*com) + write_h(" %s", com); + write_h("\n"); +} + +/** + Write one or more lines of code, indenting each one of them. + \param[in] textlines one or more lines of text, separated by \\n + \param[in] inIndent increment indentation by this amount + \param[in] inTrailWith append this character if the last line did not end with + a newline, usually 0 or newline. + */ +void Fd_Code_Writer::write_c_indented(const char *textlines, int inIndent, char inTrailWith) { + if (textlines) { + indentation += inIndent; + for (;;) { + int line_len; + const char *newline = strchr(textlines, '\n'); + if (newline) + line_len = (int)(newline-textlines); + else + line_len = (int)strlen(textlines); + if (textlines[0]=='\n') { + // avoid trailing spaces + } else if (textlines[0]=='#') { + // don't indent preprocessor statments starting with '#' + write_c("%.*s", line_len, textlines); + } else { + // indent all other text lines + write_c("%s%.*s", indent(), line_len, textlines); + } + if (newline) { + write_c("\n"); + } else { + if (inTrailWith) + write_c("%c", inTrailWith); + break; + } + textlines = newline+1; + } + indentation -= inIndent; + } +} + +/** + Return true if the type would be the member of a class. + Some types are treated differently if they are inside class. Especially within + a Widget Class, children that are widgets are written as part of the + constructor whereas functions, declarations, and inline data are seen as + members of the class itself. + */ +bool is_class_member(Fl_Type *t) { + return t->is_a(ID_Function) + || t->is_a(ID_Decl) + || t->is_a(ID_Data); +// || t->is_a(ID_Class) // FLUID can't handle a class inside a class +// || t->is_a(ID_Widget_Class) +// || t->is_a(ID_DeclBlock) // Declaration blocks are generally not handled well +} + +/** + Return true, if this is a comment, and if it is followed by a class member. + This must only be called if q is inside a widget class. + Widget classes can have widgets and members (functions/methods, declarations, + etc.) intermixed. + \param[in] q should be a comment type + \return true if this comment is followed by a class member + \return false if it is followed by a widget or code + \see is_class_member(Fl_Type *t) + */ +bool is_comment_before_class_member(Fl_Type *q) { + if (q->is_a(ID_Comment) && q->next && q->next->level==q->level) { + if (q->next->is_a(ID_Comment)) + return is_comment_before_class_member(q->next); + if (is_class_member(q->next)) + return true; + } + return false; +} + +/** + Recursively write static code and declarations + \param[in] p write this type and all its children + \return pointer to the next sibling + */ +Fl_Type* Fd_Code_Writer::write_static(Fl_Type* p) { + if (write_codeview) p->header_static_start = (int)ftell(header_file); + if (write_codeview) p->code_static_start = (int)ftell(code_file); + p->write_static(*this); + if (write_codeview) p->code_static_end = (int)ftell(code_file); + if (write_codeview) p->header_static_end = (int)ftell(header_file); + + Fl_Type* q; + for (q = p->next; q && q->level > p->level;) { + q = write_static(q); + } + + p->write_static_after(*this); + + return q; +} + +/** + Recursively write code, putting children between the two parts of the parent code. + \param[in] p write this type and all its children + \return pointer to the next sibling + */ +Fl_Type* Fd_Code_Writer::write_code(Fl_Type* p) { + // write all code that comes before the children code + // (but don't write the last comment until the very end) + if (!(p==Fl_Type::last && p->is_a(ID_Comment))) { + if (write_codeview) p->code1_start = (int)ftell(code_file); + if (write_codeview) p->header1_start = (int)ftell(header_file); + p->write_code1(*this); + if (write_codeview) p->code1_end = (int)ftell(code_file); + if (write_codeview) p->header1_end = (int)ftell(header_file); + } + // recursively write the code of all children + Fl_Type* q; + if (p->is_widget() && p->is_class()) { + // Handle widget classes specially + for (q = p->next; q && q->level > p->level;) { + // note: maybe declaration blocks should be handled like comments in the context + if (!is_class_member(q) && !is_comment_before_class_member(q)) { + q = write_code(q); + } else { + int level = q->level; + do { + q = q->next; + } while (q && q->level > level); + } + } + + // write all code that come after the children + if (write_codeview) p->code2_start = (int)ftell(code_file); + if (write_codeview) p->header2_start = (int)ftell(header_file); + p->write_code2(*this); + if (write_codeview) p->code2_end = (int)ftell(code_file); + if (write_codeview) p->header2_end = (int)ftell(header_file); + + for (q = p->next; q && q->level > p->level;) { + if (is_class_member(q) || is_comment_before_class_member(q)) { + q = write_code(q); + } else { + int level = q->level; + do { + q = q->next; + } while (q && q->level > level); + } + } + + write_h("};\n"); + current_widget_class = 0L; + } else { + for (q = p->next; q && q->level > p->level;) q = write_code(q); + // write all code that come after the children + if (write_codeview) p->code2_start = (int)ftell(code_file); + if (write_codeview) p->header2_start = (int)ftell(header_file); + p->write_code2(*this); + if (write_codeview) p->code2_end = (int)ftell(code_file); + if (write_codeview) p->header2_end = (int)ftell(header_file); + } + return q; +} + +/** + Write the source and header files for the current design. + + If the files already exist, they will be overwritten. + + \note There is no true error checking here. + + \param[in] s filename of source code file + \param[in] t filename of the header file + \return 0 if the operation failed, 1 if it was successful + */ +int Fd_Code_Writer::write_code(const char *s, const char *t, bool to_codeview) { + write_codeview = to_codeview; + delete id_root; id_root = 0; + indentation = 0; + current_class = 0L; + current_widget_class = 0L; + if (!s) code_file = stdout; + else { + FILE *f = fl_fopen(s, "wb"); + if (!f) return 0; + code_file = f; + } + if (!t) header_file = stdout; + else { + FILE *f = fl_fopen(t, "wb"); + if (!f) {fclose(code_file); return 0;} + header_file = f; + } + // Remember the last code file location for MergeBack + if (s && g_project.write_mergeback_data && !to_codeview) { + std::string proj_filename = g_project.projectfile_path() + g_project.projectfile_name(); + int i, n = (int)proj_filename.size(); + for (i=0; i<n; i++) if (proj_filename[i]=='\\') proj_filename[i] = '/'; + Fl_Preferences build_records(Fl_Preferences::USER_L, "fltk.org", "fluid-build"); + Fl_Preferences path(build_records, proj_filename.c_str()); + path.set("code", s); + } + // if the first entry in the Type tree is a comment, then it is probably + // a copyright notice. We print that before anything else in the file! + Fl_Type* first_type = Fl_Type::first; + if (first_type && first_type->is_a(ID_Comment)) { + if (write_codeview) { + first_type->code1_start = first_type->code2_start = (int)ftell(code_file); + first_type->header1_start = first_type->header2_start = (int)ftell(header_file); + } + // it is ok to write non-recursive code here, because comments have no children or code2 blocks + first_type->write_code1(*this); + if (write_codeview) { + first_type->code1_end = first_type->code2_end = (int)ftell(code_file); + first_type->header1_end = first_type->header2_end = (int)ftell(header_file); + } + first_type = first_type->next; + } + + const char *hdr = "\ +// generated by Fast Light User Interface Designer (fluid) version %.4f\n\n"; + fprintf(header_file, hdr, FL_VERSION); + crc_printf(hdr, FL_VERSION); + + {char define_name[102]; + const char* a = fl_filename_name(t); + char* b = define_name; + if (!isalpha(*a)) {*b++ = '_';} + while (*a) {*b++ = isalnum(*a) ? *a : '_'; a++;} + *b = 0; + fprintf(header_file, "#ifndef %s\n", define_name); + fprintf(header_file, "#define %s\n", define_name); + } + + if (g_project.avoid_early_includes==0) { + write_h_once("#include <FL/Fl.H>"); + } + if (t && g_project.include_H_from_C) { + if (to_codeview) { + write_c("#include \"CodeView.h\"\n"); + } else if (g_project.header_file_name[0] == '.' && strchr(g_project.header_file_name.c_str(), '/') == NULL) { + write_c("#include \"%s\"\n", fl_filename_name(t)); + } else { + write_c("#include \"%s\"\n", g_project.header_file_name.c_str()); + } + } + std::string loc_include, loc_conditional; + if (g_project.i18n_type==FD_I18N_GNU) { + loc_include = g_project.i18n_gnu_include; + loc_conditional = g_project.i18n_gnu_conditional; + } else { + loc_include = g_project.i18n_pos_include; + loc_conditional = g_project.i18n_pos_conditional; + } + if (g_project.i18n_type && !loc_include.empty()) { + int conditional = !loc_conditional.empty(); + if (conditional) { + write_c("#ifdef %s\n", loc_conditional.c_str()); + indentation++; + } + if (loc_include[0] != '<' && loc_include[0] != '\"') + write_c("#%sinclude \"%s\"\n", indent(), loc_include.c_str()); + else + write_c("#%sinclude %s\n", indent(), loc_include.c_str()); + if (g_project.i18n_type == FD_I18N_POSIX) { + if (!g_project.i18n_pos_file.empty()) { + write_c("extern nl_catd %s;\n", g_project.i18n_pos_file.c_str()); + } else { + write_c("// Initialize I18N stuff now for menus...\n"); + write_c("#%sinclude <locale.h>\n", indent()); + write_c("static char *_locale = setlocale(LC_MESSAGES, \"\");\n"); + write_c("static nl_catd _catalog = catopen(\"%s\", 0);\n", g_project.basename().c_str()); + } + } + if (conditional) { + write_c("#else\n"); + if (g_project.i18n_type == FD_I18N_GNU) { + if (!g_project.i18n_gnu_function.empty()) { + write_c("#%sifndef %s\n", indent(), g_project.i18n_gnu_function.c_str()); + write_c("#%sdefine %s(text) text\n", indent_plus(1), g_project.i18n_gnu_function.c_str()); + write_c("#%sendif\n", indent()); + } + } + if (g_project.i18n_type == FD_I18N_POSIX) { + write_c("#%sifndef catgets\n", indent()); + write_c("#%sdefine catgets(catalog, set, msgid, text) text\n", indent_plus(1)); + write_c("#%sendif\n", indent()); + } + indentation--; + write_c("#endif\n"); + } + if (g_project.i18n_type == FD_I18N_GNU && g_project.i18n_gnu_static_function[0]) { + write_c("#ifndef %s\n", g_project.i18n_gnu_static_function.c_str()); + write_c("#%sdefine %s(text) text\n", indent_plus(1), g_project.i18n_gnu_static_function.c_str()); + write_c("#endif\n"); + } + } + for (Fl_Type* p = first_type; p;) { + // write all static data for this & all children first + write_static(p); + // then write the nested code: + p = write_code(p); + } + + if (!s) return 1; + + fprintf(header_file, "#endif\n"); + + Fl_Type* last_type = Fl_Type::last; + if (last_type && (last_type != Fl_Type::first) && last_type->is_a(ID_Comment)) { + if (write_codeview) { + last_type->code1_start = last_type->code2_start = (int)ftell(code_file); + last_type->header1_start = last_type->header2_start = (int)ftell(header_file); + } + last_type->write_code1(*this); + if (write_codeview) { + last_type->code1_end = last_type->code2_end = (int)ftell(code_file); + last_type->header1_end = last_type->header2_end = (int)ftell(header_file); + } + } + int x = 0, y = 0; + + if (code_file != stdout) + x = fclose(code_file); + code_file = 0; + if (header_file != stdout) + y = fclose(header_file); + header_file = 0; + return x >= 0 && y >= 0; +} + + +/** + Write the public/private/protected keywords inside the class. + This avoids repeating these words if the mode is already set. + \param[in] state 0 for private, 1 for public, 2 for protected + */ +void Fd_Code_Writer::write_public(int state) { + if (!current_class && !current_widget_class) return; + if (current_class && current_class->write_public_state == state) return; + if (current_widget_class && current_widget_class->write_public_state == state) return; + if (current_class) current_class->write_public_state = state; + if (current_widget_class) current_widget_class->write_public_state = state; + switch (state) { + case 0: write_h("private:\n"); break; + case 1: write_h("public:\n"); break; + case 2: write_h("protected:\n"); break; + } +} + +/** + Create and initialize a new C++ source code writer. + */ +Fd_Code_Writer::Fd_Code_Writer() +: code_file(NULL), + header_file(NULL), + id_root(NULL), + text_in_header(NULL), + text_in_code(NULL), + ptr_in_code(NULL), + block_crc_(0), + block_line_start_(true), + block_buffer_(NULL), + block_buffer_size_(0), + indentation(0), + write_codeview(false), + varused_test(0), + varused(0) +{ + block_crc_ = crc32(0, NULL, 0); +} + +/** + Release all resources. + */ +Fd_Code_Writer::~Fd_Code_Writer() +{ + delete id_root; + delete ptr_in_code; + delete text_in_code; + delete text_in_header; + if (block_buffer_) ::free(block_buffer_); +} + +/** + Write a MergeBack tag as a separate line of C++ comment. + The tag contains information about the type of tag that we are writing, a + link back to the type using its unique id, and the CRC of all code written + after the previous tag up to this point. + \param[in] type FD_TAG_GENERIC, FD_TAG_CODE, FD_TAG_MENU_CALLBACK, or FD_TAG_WIDGET_CALLBACK + \param[in] uid the unique id of the current type + */ +void Fd_Code_Writer::tag(int type, unsigned short uid) { + if (g_project.write_mergeback_data) + fprintf(code_file, "//~fl~%d~%04x~%08x~~\n", type, (int)uid, (unsigned int)block_crc_); + block_crc_ = crc32(0, NULL, 0); +} + +/** + Static function to calculate the CRC32 of a block of C source code. + Calculation of the CRC ignores leading whitespace in a line and all linefeed + characters ('\\r'). + \param[in] data a pointer to the data block + \param[in] n the size of the data in bytes, or -1 to use strlen() + \param[in] in_crc add to this CRC, 0 by default to start a new block + \param[inout] inout_line_start optional pointer to flag that determines + if we are the start of a line, used to find leading whitespace + \return the new CRC + */ +unsigned long Fd_Code_Writer::block_crc(const void *data, int n, unsigned long in_crc, bool *inout_line_start) { + if (!data) return 0; + if (n==-1) n = (int)strlen((const char*)data); + bool line_start = true; + if (inout_line_start) line_start = *inout_line_start; + const char *s = (const char*)data; + for ( ; n>0; --n, ++s) { + if (line_start) { + // don't count leading spaces and tabs in a line + while (n>0 && *s>0 && isspace(*s)) { s++; n--; } + if (*s) line_start = false; + } + // don't count '\r' that may be introduced by Windows + if (n>0 && *s=='\r') { s++; n--; } + if (n>0 && *s=='\n') line_start = true; + if (n>0) { + in_crc = crc32(in_crc, (const Bytef*)s, 1); + } + } + if (inout_line_start) *inout_line_start = line_start; + return in_crc; +} + +/** Add the following block of text to the CRC of this class. + \param[in] data a pointer to the data block + \param[in] n the size of the data in bytes, or -1 to use strlen() + */ +void Fd_Code_Writer::crc_add(const void *data, int n) { + block_crc_ = block_crc(data, n, block_crc_, &block_line_start_); +} + +/** Write formatted text to the code file. + If MergeBack is enabled, the CRC calculation is continued. + \param[in] format printf style formatting string + \return see fprintf(FILE *, *const char*, ...) + */ +int Fd_Code_Writer::crc_printf(const char *format, ...) { + va_list args; + va_start(args, format); + int ret = crc_vprintf(format, args); + va_end(args); + return ret; +} + +/** Write formatted text to the code file. + If MergeBack is enabled, the CRC calculation is continued. + \param[in] format printf style formatting string + \param[in] args list of arguments + \return see fprintf(FILE *, *const char*, ...) + */ +int Fd_Code_Writer::crc_vprintf(const char *format, va_list args) { + if (g_project.write_mergeback_data) { + int n = vsnprintf(block_buffer_, block_buffer_size_, format, args); + if (n > block_buffer_size_) { + block_buffer_size_ = n + 128; + if (block_buffer_) ::free(block_buffer_); + block_buffer_ = (char*)::malloc(block_buffer_size_+1); + n = vsnprintf(block_buffer_, block_buffer_size_, format, args); + } + crc_add(block_buffer_, n); + return fputs(block_buffer_, code_file); + } else { + return vfprintf(code_file, format, args); + } +} + +/** Write some text to the code file. + If MergeBack is enabled, the CRC calculation is continued. + \param[in] text any text, no requirements to end in a newline or such + \return see fputs(const char*, FILE*) + */ +int Fd_Code_Writer::crc_puts(const char *text) { + if (g_project.write_mergeback_data) { + crc_add(text); + } + return fputs(text, code_file); +} + +/** Write a single ASCII character to the code file. + If MergeBack is enabled, the CRC calculation is continued. + \note to write UTF-8 characters, use Fd_Code_Writer::crc_puts(const char *text) + \param[in] c any character between 0 and 127 inclusive + \return see fputc(int, FILE*) + */ +int Fd_Code_Writer::crc_putc(int c) { + if (g_project.write_mergeback_data) { + uchar uc = (uchar)c; + crc_add(&uc, 1); + } + return fputc(c, code_file); +} + +/// \} + |
