From 1985aefc0e502048f92b91beef87c0dfbe669fed Mon Sep 17 00:00:00 2001 From: Matthias Melcher Date: Fri, 7 Mar 2025 16:34:35 +0100 Subject: Restructuring Fluid source files. --- fluid/nodes/Fl_Button_Type.cxx | 226 +++ fluid/nodes/Fl_Button_Type.h | 46 + fluid/nodes/Fl_Function_Type.cxx | 2156 +++++++++++++++++++++ fluid/nodes/Fl_Function_Type.h | 259 +++ fluid/nodes/Fl_Grid_Type.cxx | 993 ++++++++++ fluid/nodes/Fl_Grid_Type.h | 82 + fluid/nodes/Fl_Group_Type.cxx | 848 ++++++++ fluid/nodes/Fl_Group_Type.h | 242 +++ fluid/nodes/Fl_Menu_Type.cxx | 922 +++++++++ fluid/nodes/Fl_Menu_Type.h | 287 +++ fluid/nodes/Fl_Type.cxx | 1336 +++++++++++++ fluid/nodes/Fl_Type.h | 317 +++ fluid/nodes/Fl_Widget_Type.cxx | 3937 ++++++++++++++++++++++++++++++++++++++ fluid/nodes/Fl_Widget_Type.h | 132 ++ fluid/nodes/Fl_Window_Type.cxx | 1560 +++++++++++++++ fluid/nodes/Fl_Window_Type.h | 157 ++ fluid/nodes/factory.cxx | 1718 +++++++++++++++++ fluid/nodes/factory.h | 34 + 18 files changed, 15252 insertions(+) create mode 100644 fluid/nodes/Fl_Button_Type.cxx create mode 100644 fluid/nodes/Fl_Button_Type.h create mode 100644 fluid/nodes/Fl_Function_Type.cxx create mode 100644 fluid/nodes/Fl_Function_Type.h create mode 100644 fluid/nodes/Fl_Grid_Type.cxx create mode 100644 fluid/nodes/Fl_Grid_Type.h create mode 100644 fluid/nodes/Fl_Group_Type.cxx create mode 100644 fluid/nodes/Fl_Group_Type.h create mode 100644 fluid/nodes/Fl_Menu_Type.cxx create mode 100644 fluid/nodes/Fl_Menu_Type.h create mode 100644 fluid/nodes/Fl_Type.cxx create mode 100644 fluid/nodes/Fl_Type.h create mode 100644 fluid/nodes/Fl_Widget_Type.cxx create mode 100644 fluid/nodes/Fl_Widget_Type.h create mode 100644 fluid/nodes/Fl_Window_Type.cxx create mode 100644 fluid/nodes/Fl_Window_Type.h create mode 100644 fluid/nodes/factory.cxx create mode 100644 fluid/nodes/factory.h (limited to 'fluid/nodes') diff --git a/fluid/nodes/Fl_Button_Type.cxx b/fluid/nodes/Fl_Button_Type.cxx new file mode 100644 index 000000000..18620b477 --- /dev/null +++ b/fluid/nodes/Fl_Button_Type.cxx @@ -0,0 +1,226 @@ +// +// Button type factory code for the Fast Light Tool Kit (FLTK). +// +// Type classes for most of the fltk widgets. Most of the work +// is done by code in Fl_Widget_Type.C. Also a factory instance +// of each of these type classes. +// +// This file also contains the "new" menu, which has a pointer +// to a factory instance for every class (both the ones defined +// here and ones in other files) +// +// Copyright 1998-2023 by Bill Spitzak and others. +// +// This library is free software. Distribution and use rights are outlined in +// the file "COPYING" which should have been included with this file. If this +// file is missing or damaged, see the license at: +// +// https://www.fltk.org/COPYING.php +// +// Please see the following page on how to report bugs and issues: +// +// https://www.fltk.org/bugs.php +// + +#include "nodes/Fl_Button_Type.h" + +#include "app/Fd_Snap_Action.h" +#include "io/file.h" + +#include +#include +#include +#include +#include +#include +#include + +#include + + + +// ---- Button Types --------------------------------------------------- MARK: - + + +// ---- Button ---- + +static Fl_Menu_Item buttontype_menu[] = { + {"Normal", 0, 0, (void*)0}, + {"Toggle", 0, 0, (void*)FL_TOGGLE_BUTTON}, + {"Radio", 0, 0, (void*)FL_RADIO_BUTTON}, + {0} +}; + +Fl_Menu_Item *Fl_Button_Type::subtypes() { + return buttontype_menu; +} + +void Fl_Button_Type::ideal_size(int &w, int &h) { + h = layout->labelsize + 8; + w = layout->labelsize * 4 + 8; + Fd_Snap_Action::better_size(w, h); +} + +Fl_Widget *Fl_Button_Type::widget(int x, int y, int w, int h) { + return new Fl_Button(x, y, w, h, "Button"); +} + +void Fl_Button_Type::write_properties(Fd_Project_Writer &f) { + Fl_Widget_Type::write_properties(f); + Fl_Button *btn = (Fl_Button*)o; + if (btn->compact()) { + f.write_string("compact"); + f.write_string("%d", btn->compact()); + } +} + +void Fl_Button_Type::read_property(Fd_Project_Reader &f, const char *c) { + Fl_Button *btn = (Fl_Button*)o; + if (!strcmp(c, "compact")) { + btn->compact((uchar)atol(f.read_word())); + } else { + Fl_Widget_Type::read_property(f, c); + } +} + +void Fl_Button_Type::copy_properties() { + Fl_Widget_Type::copy_properties(); + Fl_Button *s = (Fl_Button*)o, *d = (Fl_Button*)live_widget; + d->compact(s->compact()); +} + +Fl_Button_Type Fl_Button_type; + + +// ---- Return Button ---- + +/** + \brief The Return Button is simply a Button with the return key as a hotkey. + */ +class Fl_Return_Button_Type : public Fl_Button_Type +{ + typedef Fl_Button_Type super; +public: + void ideal_size(int &w, int &h) FL_OVERRIDE { + h = layout->labelsize + 8; + w = layout->labelsize * 4 + 8 + h; // make room for the symbol + Fd_Snap_Action::better_size(w, h); + } + const char *type_name() FL_OVERRIDE { return "Fl_Return_Button"; } + const char *alt_type_name() FL_OVERRIDE { return "fltk::ReturnButton"; } + Fl_Widget *widget(int x, int y, int w, int h) FL_OVERRIDE { + return new Fl_Return_Button(x, y, w, h, "Button"); + } + Fl_Widget_Type *_make() FL_OVERRIDE { return new Fl_Return_Button_Type(); } + ID id() const FL_OVERRIDE { return ID_Return_Button; } + bool is_a(ID inID) const FL_OVERRIDE { return (inID==ID_Return_Button) ? true : super::is_a(inID); } +}; + +Fl_Return_Button_Type Fl_Return_Button_type; + + +// ---- Repeat Button ---- + +/** + \brief Handler for Fl_Repeat_Button. + \note Even though Fl_Repeat_Button is somewhat limited compared to Fl_Button, + and some settings may not make much sense, it is still derived from it, + so the wrapper should be as well. + */ +class Fl_Repeat_Button_Type : public Fl_Button_Type +{ + typedef Fl_Button_Type super; +public: + const char *type_name() FL_OVERRIDE { return "Fl_Repeat_Button"; } + const char *alt_type_name() FL_OVERRIDE { return "fltk::RepeatButton"; } + Fl_Widget *widget(int x, int y, int w, int h) FL_OVERRIDE { + return new Fl_Repeat_Button(x, y, w, h, "Button"); + } + Fl_Widget_Type *_make() FL_OVERRIDE { return new Fl_Repeat_Button_Type(); } + ID id() const FL_OVERRIDE { return ID_Repeat_Button; } + bool is_a(ID inID) const FL_OVERRIDE { return (inID==ID_Repeat_Button) ? true : super::is_a(inID); } +}; + +Fl_Repeat_Button_Type Fl_Repeat_Button_type; + + +// ---- Light Button ---- + +/** + \brief A handler for a toggle button with an indicator light. + */ +class Fl_Light_Button_Type : public Fl_Button_Type +{ + typedef Fl_Button_Type super; +public: + void ideal_size(int &w, int &h) FL_OVERRIDE { + h = layout->labelsize + 8; + w = layout->labelsize * 4 + 8 + layout->labelsize; // make room for the light + Fd_Snap_Action::better_size(w, h); + } + const char *type_name() FL_OVERRIDE { return "Fl_Light_Button"; } + const char *alt_type_name() FL_OVERRIDE { return "fltk::LightButton"; } + Fl_Widget *widget(int x, int y, int w, int h) FL_OVERRIDE { + return new Fl_Light_Button(x, y, w, h, "Button"); + } + Fl_Widget_Type *_make() FL_OVERRIDE { return new Fl_Light_Button_Type(); } + ID id() const FL_OVERRIDE { return ID_Light_Button; } + bool is_a(ID inID) const FL_OVERRIDE { return (inID==ID_Light_Button) ? true : super::is_a(inID); } +}; + +Fl_Light_Button_Type Fl_Light_Button_type; + + +// ---- Check Button ---- + +/** + \brief Manage buttons with a check mark on its left. + */ +class Fl_Check_Button_Type : public Fl_Button_Type +{ + typedef Fl_Button_Type super; +public: + void ideal_size(int &w, int &h) FL_OVERRIDE { + h = layout->labelsize + 8; + w = layout->labelsize * 4 + 8 + layout->labelsize; // make room for the symbol + Fd_Snap_Action::better_size(w, h); + } + const char *type_name() FL_OVERRIDE { return "Fl_Check_Button"; } + const char *alt_type_name() FL_OVERRIDE { return "fltk::CheckButton"; } + Fl_Widget *widget(int x, int y, int w, int h) FL_OVERRIDE { + return new Fl_Check_Button(x, y, w, h, "Button"); + } + Fl_Widget_Type *_make() FL_OVERRIDE { return new Fl_Check_Button_Type(); } + ID id() const FL_OVERRIDE { return ID_Check_Button; } + bool is_a(ID inID) const FL_OVERRIDE { return (inID==ID_Check_Button) ? true : super::is_a(inID); } +}; + +Fl_Check_Button_Type Fl_Check_Button_type; + + +// ---- Round Button ---- + +/** + \brief Manage buttons with a round indicator on its left. + */ +class Fl_Round_Button_Type : public Fl_Button_Type +{ + typedef Fl_Button_Type super; +public: + void ideal_size(int &w, int &h) FL_OVERRIDE { + h = layout->labelsize + 8; + w = layout->labelsize * 4 + 8 + layout->labelsize; // make room for the symbol + Fd_Snap_Action::better_size(w, h); + } + const char *type_name() FL_OVERRIDE { return "Fl_Round_Button"; } + const char *alt_type_name() FL_OVERRIDE { return "fltk::RadioButton"; } + Fl_Widget *widget(int x, int y, int w, int h) FL_OVERRIDE { + return new Fl_Round_Button(x, y, w, h, "Button"); + } + Fl_Widget_Type *_make() FL_OVERRIDE { return new Fl_Round_Button_Type(); } + ID id() const FL_OVERRIDE { return ID_Round_Button; } + bool is_a(ID inID) const FL_OVERRIDE { return (inID==ID_Round_Button) ? true : super::is_a(inID); } +}; + +Fl_Round_Button_Type Fl_Round_Button_type; + diff --git a/fluid/nodes/Fl_Button_Type.h b/fluid/nodes/Fl_Button_Type.h new file mode 100644 index 000000000..d513c1398 --- /dev/null +++ b/fluid/nodes/Fl_Button_Type.h @@ -0,0 +1,46 @@ +// +// Button type header file for the Fast Light Tool Kit (FLTK). +// +// Copyright 1998-2023 by Bill Spitzak and others. +// +// This library is free software. Distribution and use rights are outlined in +// the file "COPYING" which should have been included with this file. If this +// file is missing or damaged, see the license at: +// +// https://www.fltk.org/COPYING.php +// +// Please see the following page on how to report bugs and issues: +// +// https://www.fltk.org/bugs.php +// + +#ifndef _FL_BUTTON_TYPE_H +#define _FL_BUTTON_TYPE_H + +#include "nodes/Fl_Widget_Type.h" + +/** + \brief A handler for the simple push button and a base class for all other buttons. + */ +class Fl_Button_Type : public Fl_Widget_Type +{ + typedef Fl_Widget_Type super; + Fl_Menu_Item *subtypes() FL_OVERRIDE; +public: + void ideal_size(int &w, int &h) FL_OVERRIDE; + const char *type_name() FL_OVERRIDE { return "Fl_Button"; } + const char *alt_type_name() FL_OVERRIDE { return "fltk::Button"; } + Fl_Widget *widget(int x, int y, int w, int h) FL_OVERRIDE; + Fl_Widget_Type *_make() FL_OVERRIDE { return new Fl_Button_Type(); } + int is_button() const FL_OVERRIDE { return 1; } + ID id() const FL_OVERRIDE { return ID_Button; } + bool is_a(ID inID) const FL_OVERRIDE { return (inID==ID_Button) ? true : super::is_a(inID); } + void write_properties(Fd_Project_Writer &f) FL_OVERRIDE; + void read_property(Fd_Project_Reader &f, const char *) FL_OVERRIDE; + void copy_properties() FL_OVERRIDE; +}; + +extern Fl_Button_Type Fl_Button_type; + + +#endif // _FL_BUTTON_TYPE_H diff --git a/fluid/nodes/Fl_Function_Type.cxx b/fluid/nodes/Fl_Function_Type.cxx new file mode 100644 index 000000000..1f0a4fea5 --- /dev/null +++ b/fluid/nodes/Fl_Function_Type.cxx @@ -0,0 +1,2156 @@ +// +// C function type code for the Fast Light Tool Kit (FLTK). +// +// Copyright 1998-2023 by Bill Spitzak and others. +// +// This library is free software. Distribution and use rights are outlined in +// the file "COPYING" which should have been included with this file. If this +// file is missing or damaged, see the license at: +// +// https://www.fltk.org/COPYING.php +// +// Please see the following page on how to report bugs and issues: +// +// https://www.fltk.org/bugs.php +// + +#include "nodes/Fl_Function_Type.h" + +#include "app/fluid.h" +#include "app/mergeback.h" +#include "app/undo.h" +#include "io/file.h" +#include "io/code.h" +#include "nodes/Fl_Window_Type.h" +#include "nodes/Fl_Group_Type.h" +#include "panels/function_panel.h" +#include "rsrcs/comments.h" +#include "widgets/widget_browser.h" + +#include +#include +#include +#include "../src/flstring.h" + +#include + + +/// Set a current class, so that the code of the children is generated correctly. +Fl_Class_Type *current_class = NULL; + +/** + \brief Return 1 if the list contains a function with the given signature at the top level. + Fl_Widget_Type uses this to check if a callback by a certain signature is + already defined by the user within this file. If not, Fl_Widget_Type will + generate an `extern $sig$;` statement. + \param[in] rtype return type, can be NULL to avoid checking (not used by Fl_Widget_Type) + \param[in] sig function signature + \return 1 if found. + */ +int has_toplevel_function(const char *rtype, const char *sig) { + Fl_Type *child; + for (child = Fl_Type::first; child; child = child->next) { + if (!child->is_in_class() && child->is_a(ID_Function)) { + const Fl_Function_Type *fn = (const Fl_Function_Type*)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 NULL 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 0; + } +} + +/** + 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 NULL 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 0; + 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 '{': + 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 0; + 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 NULL 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); +} + +// ---- Fl_Function_Type implementation + +/** \class Fl_Function_Type + 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. +Fl_Function_Type Fl_Function_type; + +/** + Create a new function. + */ +Fl_Function_Type::Fl_Function_Type() : + Fl_Type(), + return_type(0L), + public_(0), + cdecl_(0), + constructor(0), + havewidgets(0) +{ } + +/** + Destructor. + */ +Fl_Function_Type::~Fl_Function_Type() { + 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 + */ +Fl_Type *Fl_Function_Type::make(Strategy strategy) { + Fl_Type *anchor = Fl_Type::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; + } + Fl_Function_Type *o = new Fl_Function_Type(); + o->name("make_window()"); + o->return_type = 0; + 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 Fl_Function_Type::write_properties(Fd_Project_Writer &f) { + Fl_Type::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 Fl_Function_Type::read_property(Fd_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 { + Fl_Type::read_property(f, c); + } +} + +/** + Open the function_panel dialog box to edit this function. + */ +void Fl_Function_Type::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 = 0; + 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", NULL, 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())) { set_modflag(1); redraw_browser(); } + comment(c); + } else { + if (comment()) { set_modflag(1); redraw_browser(); } + comment(0); + } + if (c) free((void*)c); + if (mod) set_modflag(1); + break; + } +BREAK2: + function_panel->hide(); +} + +/** + Return 1 if the function is global. + \return 1 if public, 0 if local. + */ +int Fl_Function_Type::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(Fd_Code_Writer& f) + */ +void Fl_Function_Type::write_code1(Fd_Code_Writer& f) { + constructor=0; + havewidgets = 0; + Fl_Type *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 = 0;} + else if (!strncmp(rtype, "static ",7)) {is_static = 1; rtype += 7;} + } + if (rtype) { + if (!strcmp(rtype, "virtual")) {is_virtual = 1; rtype = 0;} + 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(Fd_Code_Writer& f) + */ +void Fl_Function_Type::write_code2(Fd_Code_Writer& f) { + Fl_Type *child; + const char *var = "w"; + char havechildren = 0; + for (child = next; child && child->level > level; child = child->next) { + havechildren = 1; + if (child->is_a(ID_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 signature s match. + \param[in] rtype function return type + \param[in] sig function name followed by arguments + \return 1 if they match, 0 if not + */ +int Fl_Function_Type::has_signature(const char *rtype, const char *sig) const { + if (rtype && !return_type) return 0; + if (!name()) return 0; + if ( (rtype==0L || strcmp(return_type, rtype)==0) + && fl_filename_match(name(), sig)) { + return 1; + } + return 0; +} + +// ---- Fl_Code_Type declaration + +/** \class Fl_Code_Type + 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. +Fl_Code_Type Fl_Code_type; + +/** + Constructor. + */ +Fl_Code_Type::Fl_Code_Type() : + 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 + */ +Fl_Type *Fl_Code_Type::make(Strategy strategy) { + Fl_Type *anchor = Fl_Type::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 0; + } + Fl_Code_Type *o = new Fl_Code_Type(); + 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 Fl_Code_Type::open() { + // Using an external code editor? Open it.. + if ( G_use_external_editor && G_external_editor_command[0] ) { + const char *cmd = G_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 = 0; + 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", NULL, 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 Fl_Code_Type::write(Fd_Project_Writer &f) { + // External editor changes? If so, load changes into ram, update mtime/size + if ( handle_editor_changes() == 1 ) { + main_window->redraw(); // tell fluid to redraw; edits may affect tree's contents + } + Fl_Type::write(f); +} + +/** + Write the code block with the correct indentation. + */ +void Fl_Code_Type::write_code1(Fd_Code_Writer& f) { + // External editor changes? If so, load changes into ram, update mtime/size + if ( handle_editor_changes() == 1 ) { + 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 Fl_Code_Type::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 Fl_Code_Type::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 Fl_Code_Type::handle_editor_changes() { + const char *newcode = 0; + 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; +} + +// ---- Fl_CodeBlock_Type implementation + +/** \class Fl_CodeBlock_Type + 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. +Fl_CodeBlock_Type Fl_CodeBlock_type; + +/** + Constructor. + */ +Fl_CodeBlock_Type::Fl_CodeBlock_Type() : + Fl_Type(), + after(NULL) +{ } + +/** + Destructor. + */ +Fl_CodeBlock_Type::~Fl_CodeBlock_Type() { + 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 + */ +Fl_Type *Fl_CodeBlock_Type::make(Strategy strategy) { + Fl_Type *anchor = Fl_Type::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 0; + } + Fl_CodeBlock_Type *o = new Fl_CodeBlock_Type(); + o->name("if (test())"); + o->after = 0; + 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 Fl_CodeBlock_Type::write_properties(Fd_Project_Writer &f) { + Fl_Type::write_properties(f); + if (after) { + f.write_string("after"); + f.write_word(after); + } +} + +/** + Read the node specific properties. + */ +void Fl_CodeBlock_Type::read_property(Fd_Project_Reader &f, const char *c) { + if (!strcmp(c,"after")) { + storestring(f.read_word(),after); + } else { + Fl_Type::read_property(f, c); + } +} + +/** + Open the codeblock_panel. + */ +void Fl_CodeBlock_Type::open() { + if (!codeblock_panel) make_codeblock_panel(); + code_before_input->value(name()); + code_after_input->value(after); + codeblock_panel->show(); + const char* message = 0; + 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", NULL, 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 Fl_CodeBlock_Type::write_code1(Fd_Code_Writer& f) { + const char* c = name(); + f.write_c("%s%s {\n", f.indent(), c ? c : ""); + f.indentation++; +} + +/** + Write the "after" code. + */ +void Fl_CodeBlock_Type::write_code2(Fd_Code_Writer& f) { + f.indentation--; + if (after) f.write_c("%s} %s\n", f.indent(), after); + else f.write_c("%s}\n", f.indent()); +} + +// ---- Fl_Decl_Type declaration + +/** \class Fl_Decl_Type + 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. +Fl_Decl_Type Fl_Decl_type; + +/** + Constructor. + */ +Fl_Decl_Type::Fl_Decl_Type() : + public_(0), + static_(1) +{ } + +/** + Return 1 if this declaration and its parents are public. + */ +int Fl_Decl_Type::is_public() const +{ + Fl_Type *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 + */ +Fl_Type *Fl_Decl_Type::make(Strategy strategy) { + Fl_Type *anchor = Fl_Type::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; + } + Fl_Decl_Type *o = new Fl_Decl_Type(); + 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 Fl_Decl_Type::write_properties(Fd_Project_Writer &f) { + Fl_Type::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 Fl_Decl_Type::read_property(Fd_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 { + Fl_Type::read_property(f, c); + } +} + +/** + Open the decl_panel to edit this node. + */ +void Fl_Decl_Type::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 = 0; + 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", NULL, 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()) { + set_modflag(1); + public_ = decl_class_choice->value(); + } + } else { + if (public_!=(decl_choice->value()&1)) { + set_modflag(1); + public_ = (decl_choice->value()&1); + } + if (static_!=((decl_choice->value()>>1)&1)) { + set_modflag(1); + static_ = ((decl_choice->value()>>1)&1); + } + } + c = decl_comment_input->buffer()->text(); + if (c && *c) { + if (!comment() || strcmp(c, comment())) { set_modflag(1); redraw_browser(); } + comment(c); + } else { + if (comment()) { set_modflag(1); redraw_browser(); } + comment(0); + } + 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 Fl_Decl_Type::write_code1(Fd_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 (cscc && 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); + } + } +} + +// ---- Fl_Data_Type declaration + +/** \class Fl_Data_Type + 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. +Fl_Data_Type Fl_Data_type; + +/** + Constructor. + */ +Fl_Data_Type::Fl_Data_Type() : + Fl_Decl_Type(), + filename_(NULL), + text_mode_(0) +{ } + +/** + Destructor. + */ +Fl_Data_Type::~Fl_Data_Type() { + 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 + */ +Fl_Type *Fl_Data_Type::make(Strategy strategy) { + Fl_Type *anchor = Fl_Type::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; + } + Fl_Data_Type *o = new Fl_Data_Type(); + o->public_ = 1; + o->static_ = 1; + o->filename_ = 0; + 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 Fl_Data_Type::write_properties(Fd_Project_Writer &f) { + Fl_Decl_Type::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 Fl_Data_Type::read_property(Fd_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 { + Fl_Decl_Type::read_property(f, c); + } +} + +/** + Open the data_panel to edit this node. + */ +void Fl_Data_Type::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) { + enter_project_dir(); + const char *fn = fl_file_chooser("Load Inline Data", 0L, data_filename->value(), 1); + leave_project_dir(); + if (fn) { + if (strcmp(fn, data_filename->value())) + 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", NULL, + "Variable name must be a C identifier"); + if (v==0) { free(s); continue; } // Continue Editing + //if (v==1) { } // Ignore Error and close dialog + } + undo_checkpoint(); + name(n); + free(s); + // store flags + if (is_in_class()) { + if (public_!=data_class_choice->value()) { + set_modflag(1); + public_ = data_class_choice->value(); + } + } else { + if (public_!=(data_choice->value()&1)) { + set_modflag(1); + public_ = (data_choice->value()&1); + } + if (static_!=((data_choice->value()>>1)&1)) { + 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())) + set_modflag(1); + else if (!filename_ && *c) + set_modflag(1); + if (filename_) { free((void*)filename_); filename_ = 0L; } + if (c && *c) filename_ = fl_strdup(c); + // store the comment + c = data_comment_input->buffer()->text(); + if (c && *c) { + if (!comment() || strcmp(c, comment())) { set_modflag(1); redraw_browser(); } + comment(c); + } else { + if (comment()) { set_modflag(1); redraw_browser(); } + comment(0); + } + if (c) free((void*)c); + set_modflag(1); + break; + } +BREAK2: + data_panel->hide(); +} + +/** + Write the content of the external file inline into the source code. + */ +void Fl_Data_Type::write_code1(Fd_Code_Writer& f) { + const char *message = 0; + const char *c = name(); + if (!c) return; + const char *fn = filename_; + char *data = 0; + int nData = -1; + int uncompressedDataSize = 0; + // path should be set correctly already + if (filename_ && !f.write_codeview) { + enter_project_dir(); + FILE *f = fl_fopen(filename_, "rb"); + 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_ : ""; + } + 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: (batch_mode && !write_codeview) ??? + if (message && !f.write_codeview) { + if (batch_mode) + fprintf(stderr, "FLUID ERROR: %s %s\n", message, fn); + else + fl_alert("%s\n%s\n", message, fn); + } + if (data) free(data); +} + +// ---- Fl_DeclBlock_Type declaration + +/** \class Fl_DeclBlock_Type + 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. +Fl_DeclBlock_Type Fl_DeclBlock_type; + +/** + Constructor. + */ +Fl_DeclBlock_Type::Fl_DeclBlock_Type() : + Fl_Type(), + after(NULL), + write_map_(CODE_IN_SOURCE) +{ } + +/** + Destructor. + */ +Fl_DeclBlock_Type::~Fl_DeclBlock_Type() { + if (after) + ::free((void*)after); +} + +/** + Return 1 if this block is public. + */ +int Fl_DeclBlock_Type::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 + */ +Fl_Type *Fl_DeclBlock_Type::make(Strategy strategy) { + Fl_Type *anchor = Fl_Type::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; + } + Fl_DeclBlock_Type *o = new Fl_DeclBlock_Type(); + 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 Fl_DeclBlock_Type::write_properties(Fd_Project_Writer &f) { + Fl_Type::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 Fl_DeclBlock_Type::read_property(Fd_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 { + Fl_Type::read_property(f, c); + } +} + +/** + Open the declblock_panel to edit this node. + */ +void Fl_DeclBlock_Type::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 = 0; + 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", NULL, 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; + set_modflag(1); + } + } else { + if (declblock_static_header->value()) { + write_map_ |= STATIC_IN_HEADER; + set_modflag(1); + } + } + if (write_map_ & STATIC_IN_SOURCE) { + if (declblock_static_source->value()==0) { + write_map_ &= ~STATIC_IN_SOURCE; + set_modflag(1); + } + } else { + if (declblock_static_source->value()) { + write_map_ |= STATIC_IN_SOURCE; + set_modflag(1); + } + } + if (write_map_ & CODE_IN_HEADER) { + if (declblock_code_header->value()==0) { + write_map_ &= ~CODE_IN_HEADER; + set_modflag(1); + } + } else { + if (declblock_code_header->value()) { + write_map_ |= CODE_IN_HEADER; + set_modflag(1); + } + } + if (write_map_ & CODE_IN_SOURCE) { + if (declblock_code_source->value()==0) { + write_map_ &= ~CODE_IN_SOURCE; + set_modflag(1); + } + } else { + if (declblock_code_source->value()) { + write_map_ |= CODE_IN_SOURCE; + set_modflag(1); + } + } + c = declblock_comment_input->buffer()->text(); + if (c && *c) { + if (!comment() || strcmp(c, comment())) { set_modflag(1); redraw_browser(); } + comment(c); + } else { + if (comment()) { set_modflag(1); redraw_browser(); } + comment(0); + } + 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 Fl_DeclBlock_Type::write_static(Fd_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 Fl_DeclBlock_Type::write_static_after(Fd_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 Fl_DeclBlock_Type::write_code1(Fd_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 Fl_DeclBlock_Type::write_code2(Fd_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); + } +} + +// ---- Fl_Comment_Type declaration + +/** \class Fl_Comment_Type + 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. +Fl_Comment_Type Fl_Comment_type; + +/** + Constructor. + */ +Fl_Comment_Type::Fl_Comment_Type() : + 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 + */ +Fl_Type *Fl_Comment_Type::make(Strategy strategy) { + Fl_Type *anchor = Fl_Type::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; + } + Fl_Comment_Type *o = new Fl_Comment_Type(); + 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 Fl_Comment_Type::write_properties(Fd_Project_Writer &f) { + Fl_Type::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 Fl_Comment_Type::read_property(Fd_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 { + Fl_Type::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 Fl_Comment_Type::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;iadd(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", + NULL, 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; isize(); 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", 0L, 0L); + fl_file_chooser_ok_label(NULL); + 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) set_modflag(1); + break; + } +BREAK2: + comment_panel->hide(); +} + +/** + Write the comment to the files. + */ +void Fl_Comment_Type::write_code1(Fd_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); +} + +// ---- Fl_Class_Type declaration + +/** \class Fl_Class_Type + Manage a class declaration and implementation. + */ + +/// Prototype for a class node to be used by the factory. +Fl_Class_Type Fl_Class_type; + +/** + Constructor. + */ +Fl_Class_Type::Fl_Class_Type() : + Fl_Type(), + subclass_of(NULL), + public_(1), + class_prefix(NULL) +{ } + +/** + Destructor. + */ +Fl_Class_Type::~Fl_Class_Type() { + if (subclass_of) + free((void*)subclass_of); + if (class_prefix) + free((void*)class_prefix); +} + +/** + Return 1 if this class is marked public. + */ +int Fl_Class_Type::is_public() const { + return public_; +} + +/** + Set the prefixx string. + */ +void Fl_Class_Type::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 + */ +Fl_Type *Fl_Class_Type::make(Strategy strategy) { + Fl_Type *anchor = Fl_Type::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; + } + Fl_Class_Type *o = new Fl_Class_Type(); + o->name("UserInterface"); + o->class_prefix = NULL; + o->subclass_of = NULL; + o->public_ = 1; + o->add(anchor, strategy); + o->factory = this; + return o; +} + +/** + Write the respective properties. + - ":" followed by the super class + - "private"/"protected" + */ +void Fl_Class_Type::write_properties(Fd_Project_Writer &f) { + Fl_Type::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 Fl_Class_Type::read_property(Fd_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 { + Fl_Type::read_property(f, c); + } +} + +/** + Open the class_panel to edit the class name and superclass name. + */ +void Fl_Class_Type::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 = 0; + + char *na=0,*pr=0,*p=0; // 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 && is_id(*p)) p--; + if ( (ps) *p--='\0'; + while (p>=s && isspace(*p)) *(p--)='\0'; + while (p>=s && is_id(*p)) p--; + if (pvalue(); + 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(); + set_modflag(1); + } + c = c_comment_input->buffer()->text(); + if (c && *c) { + if (!comment() || strcmp(c, comment())) { set_modflag(1); redraw_browser(); } + comment(c); + } else { + if (comment()) { set_modflag(1); redraw_browser(); } + comment(0); + } + if (c) free((void*)c); + break; + } +BREAK2: + class_panel->hide(); +} + +/** + Write the header code that declares this class. + */ +void Fl_Class_Type::write_code1(Fd_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 Fl_Class_Type::write_code2(Fd_Code_Writer& f) { + f.write_h("};\n"); + current_class = parent_class; +} + +/** + Return 1 if this class contains a function with the given signature. + */ +int Fl_Type::has_function(const char *rtype, const char *sig) const { + Fl_Type *child; + for (child = next; child && child->level > level; child = child->next) { + if (child->level == level+1 && child->is_a(ID_Function)) { + const Fl_Function_Type *fn = (const Fl_Function_Type*)child; + if (fn->has_signature(rtype, sig)) + return 1; + } + } + return 0; +} diff --git a/fluid/nodes/Fl_Function_Type.h b/fluid/nodes/Fl_Function_Type.h new file mode 100644 index 000000000..536a511c0 --- /dev/null +++ b/fluid/nodes/Fl_Function_Type.h @@ -0,0 +1,259 @@ +// +// C function type header file for the Fast Light Tool Kit (FLTK). +// +// Copyright 1998-2021 by Bill Spitzak and others. +// +// This library is free software. Distribution and use rights are outlined in +// the file "COPYING" which should have been included with this file. If this +// file is missing or damaged, see the license at: +// +// https://www.fltk.org/COPYING.php +// +// Please see the following page on how to report bugs and issues: +// +// https://www.fltk.org/bugs.php +// + +#ifndef _FLUID_FL_FUNCTION_TYPE_H +#define _FLUID_FL_FUNCTION_TYPE_H + +#include "nodes/Fl_Type.h" + +#include "app/Fluid_Image.h" +#ifdef _WIN32 +#include "tools/ExternalCodeEditor_WIN32.h" +#else +#include "tools/ExternalCodeEditor_UNIX.h" +#endif + +#include +#include +#include +#include + +#include +#include + +extern class Fl_Class_Type *current_class; + +int has_toplevel_function(const char *rtype, const char *sig); + +const char *c_check(const char *c, int type = 0); + +// ---- Fl_Function_Type declaration + +class Fl_Function_Type : public Fl_Type +{ + typedef Fl_Type super; + const char* return_type; + char public_, cdecl_, constructor, havewidgets; + +public: + Fl_Function_Type(); + ~Fl_Function_Type(); + Fl_Type *make(Strategy strategy) FL_OVERRIDE; + void write_code1(Fd_Code_Writer& f) FL_OVERRIDE; + void write_code2(Fd_Code_Writer& f) FL_OVERRIDE; + void open() FL_OVERRIDE; + int ismain() {return name_ == 0;} + const char *type_name() FL_OVERRIDE {return "Function";} + const char *title() FL_OVERRIDE { + return name() ? name() : "main()"; + } + int can_have_children() const FL_OVERRIDE {return 1;} + int is_code_block() const FL_OVERRIDE {return 1;} + int is_public() const FL_OVERRIDE; + ID id() const FL_OVERRIDE { return ID_Function; } + bool is_a(ID inID) const FL_OVERRIDE { return (inID==ID_Function) ? true : super::is_a(inID); } + void write_properties(Fd_Project_Writer &f) FL_OVERRIDE; + void read_property(Fd_Project_Reader &f, const char *) FL_OVERRIDE; + int has_signature(const char *, const char*) const; +}; + +// ---- Fl_Code_Type declaration + +class Fl_Code_Type : public Fl_Type +{ + typedef Fl_Type super; + ExternalCodeEditor editor_; + int cursor_position_; + int code_input_scroll_row; + int code_input_scroll_col; + +public: + Fl_Code_Type(); + Fl_Type *make(Strategy strategy) FL_OVERRIDE; + void write(Fd_Project_Writer &f) FL_OVERRIDE; + void write_code1(Fd_Code_Writer& f) FL_OVERRIDE; + void write_code2(Fd_Code_Writer& f) FL_OVERRIDE { } + void open() FL_OVERRIDE; + const char *type_name() FL_OVERRIDE {return "code";} + int is_code_block() const FL_OVERRIDE {return 0;} + ID id() const FL_OVERRIDE { return ID_Code; } + bool is_a(ID inID) const FL_OVERRIDE { return (inID==ID_Code) ? true : super::is_a(inID); } + int is_public() const FL_OVERRIDE { return -1; } + int is_editing(); + int reap_editor(); + int handle_editor_changes(); +}; + +// ---- Fl_CodeBlock_Type declaration + +class Fl_CodeBlock_Type : public Fl_Type +{ + typedef Fl_Type super; + const char* after; + +public: + Fl_CodeBlock_Type(); + ~Fl_CodeBlock_Type(); + Fl_Type *make(Strategy strategy) FL_OVERRIDE; + void write_code1(Fd_Code_Writer& f) FL_OVERRIDE; + void write_code2(Fd_Code_Writer& f) FL_OVERRIDE; + void open() FL_OVERRIDE; + const char *type_name() FL_OVERRIDE {return "codeblock";} + int is_code_block() const FL_OVERRIDE {return 1;} + int can_have_children() const FL_OVERRIDE {return 1;} + int is_public() const FL_OVERRIDE { return -1; } + ID id() const FL_OVERRIDE { return ID_CodeBlock; } + bool is_a(ID inID) const FL_OVERRIDE { return (inID==ID_CodeBlock) ? true : super::is_a(inID); } + void write_properties(Fd_Project_Writer &f) FL_OVERRIDE; + void read_property(Fd_Project_Reader &f, const char *) FL_OVERRIDE; +}; + +// ---- Fl_Decl_Type declaration + +class Fl_Decl_Type : public Fl_Type +{ + typedef Fl_Type super; + +protected: + char public_; + char static_; + +public: + Fl_Decl_Type(); + Fl_Type *make(Strategy strategy) FL_OVERRIDE; + void write_code1(Fd_Code_Writer& f) FL_OVERRIDE; + void write_code2(Fd_Code_Writer& f) FL_OVERRIDE { } + void open() FL_OVERRIDE; + const char *type_name() FL_OVERRIDE {return "decl";} + void write_properties(Fd_Project_Writer &f) FL_OVERRIDE; + void read_property(Fd_Project_Reader &f, const char *) FL_OVERRIDE; + int is_public() const FL_OVERRIDE; + ID id() const FL_OVERRIDE { return ID_Decl; } + bool is_a(ID inID) const FL_OVERRIDE { return (inID==ID_Decl) ? true : super::is_a(inID); } +}; + +// ---- Fl_Data_Type declaration + +class Fl_Data_Type : public Fl_Decl_Type +{ + typedef Fl_Decl_Type super; + const char *filename_; + int text_mode_; + +public: + Fl_Data_Type(); + ~Fl_Data_Type(); + Fl_Type *make(Strategy strategy) FL_OVERRIDE; + void write_code1(Fd_Code_Writer& f) FL_OVERRIDE; + void write_code2(Fd_Code_Writer& f) FL_OVERRIDE {} + void open() FL_OVERRIDE; + const char *type_name() FL_OVERRIDE {return "data";} + void write_properties(Fd_Project_Writer &f) FL_OVERRIDE; + void read_property(Fd_Project_Reader &f, const char *) FL_OVERRIDE; + ID id() const FL_OVERRIDE { return ID_Data; } + bool is_a(ID inID) const FL_OVERRIDE { return (inID==ID_Data) ? true : super::is_a(inID); } +}; + +// ---- Fl_DeclBlock_Type declaration + +class Fl_DeclBlock_Type : public Fl_Type +{ + typedef Fl_Type super; + enum { + CODE_IN_HEADER = 1, + CODE_IN_SOURCE = 2, + STATIC_IN_HEADER = 4, + STATIC_IN_SOURCE = 8 + }; + const char* after; ///< code after all children of this block + int write_map_; ///< see enum above + +public: + Fl_DeclBlock_Type(); + ~Fl_DeclBlock_Type(); + Fl_Type *make(Strategy strategy) FL_OVERRIDE; + void write_static(Fd_Code_Writer& f) FL_OVERRIDE; + void write_static_after(Fd_Code_Writer& f) FL_OVERRIDE; + void write_code1(Fd_Code_Writer& f) FL_OVERRIDE; + void write_code2(Fd_Code_Writer& f) FL_OVERRIDE; + void open() FL_OVERRIDE; + const char *type_name() FL_OVERRIDE {return "declblock";} + void write_properties(Fd_Project_Writer &f) FL_OVERRIDE; + void read_property(Fd_Project_Reader &f, const char *) FL_OVERRIDE; + int can_have_children() const FL_OVERRIDE {return 1;} + int is_decl_block() const FL_OVERRIDE {return 1;} + int is_public() const FL_OVERRIDE; + ID id() const FL_OVERRIDE { return ID_DeclBlock; } + bool is_a(ID inID) const FL_OVERRIDE { return (inID==ID_DeclBlock) ? true : super::is_a(inID); } +}; + +// ---- Fl_Comment_Type declaration + +class Fl_Comment_Type : public Fl_Type +{ + typedef Fl_Type super; + char in_c_, in_h_, style_; + +public: + Fl_Comment_Type(); + Fl_Type *make(Strategy strategy) FL_OVERRIDE; + void write_code1(Fd_Code_Writer& f) FL_OVERRIDE; + void write_code2(Fd_Code_Writer& f) FL_OVERRIDE { } + void open() FL_OVERRIDE; + const char *type_name() FL_OVERRIDE {return "comment";} + void write_properties(Fd_Project_Writer &f) FL_OVERRIDE; + void read_property(Fd_Project_Reader &f, const char *) FL_OVERRIDE; + int is_public() const FL_OVERRIDE { return 1; } + ID id() const FL_OVERRIDE { return ID_Comment; } + bool is_a(ID inID) const FL_OVERRIDE { return (inID==ID_Comment) ? true : super::is_a(inID); } +}; + +// ---- Fl_Class_Type declaration + +class Fl_Class_Type : public Fl_Type +{ + typedef Fl_Type super; + const char* subclass_of; + char public_; + const char* class_prefix; + +public: + Fl_Class_Type(); + ~Fl_Class_Type(); + // state variables for output: + char write_public_state; // true when public: has been printed + Fl_Class_Type* parent_class; // save class if nested +// + Fl_Type *make(Strategy strategy) FL_OVERRIDE; + void write_code1(Fd_Code_Writer& f) FL_OVERRIDE; + void write_code2(Fd_Code_Writer& f) FL_OVERRIDE; + void open() FL_OVERRIDE; + const char *type_name() FL_OVERRIDE {return "class";} + int can_have_children() const FL_OVERRIDE {return 1;} + int is_decl_block() const FL_OVERRIDE {return 1;} + int is_class() const FL_OVERRIDE {return 1;} + int is_public() const FL_OVERRIDE; + ID id() const FL_OVERRIDE { return ID_Class; } + bool is_a(ID inID) const FL_OVERRIDE { return (inID==ID_Class) ? true : super::is_a(inID); } + void write_properties(Fd_Project_Writer &f) FL_OVERRIDE; + void read_property(Fd_Project_Reader &f, const char *) FL_OVERRIDE; + + // class prefix attribute access + void prefix(const char* p); + const char* prefix() const {return class_prefix;} +}; + +#endif // _FLUID_FL_FUNCTION_TYPE_H diff --git a/fluid/nodes/Fl_Grid_Type.cxx b/fluid/nodes/Fl_Grid_Type.cxx new file mode 100644 index 000000000..2ae05a0ce --- /dev/null +++ b/fluid/nodes/Fl_Grid_Type.cxx @@ -0,0 +1,993 @@ +// +// Fl_Grid object code for the Fast Light Tool Kit (FLTK). +// +// Copyright 2023 by Bill Spitzak and others. +// +// This library is free software. Distribution and use rights are outlined in +// the file "COPYING" which should have been included with this file. If this +// file is missing or damaged, see the license at: +// +// https://www.fltk.org/COPYING.php +// +// Please see the following page on how to report bugs and issues: +// +// https://www.fltk.org/bugs.php +// + +#include "nodes/Fl_Grid_Type.h" + +#include "app/fluid.h" +#include "app/Fd_Snap_Action.h" +#include "app/undo.h" +#include "io/file.h" +#include "io/code.h" +#include "widgets/widget_browser.h" +#include "widgets/custom_widgets.h" + +#include +#include +#include +#include +#include "../src/flstring.h" + +#include +#include +#include + +// ---- Fl_Grid_Proxy --------------------------------------------------- MARK: - + +/** + An implementation of the Fl_Grid widget with additional functionality. + + Fl_Grid_Proxy add a list of transient children, i.e. children that are + temporarily assigned to a cell that is already taken by another child. + */ +Fl_Grid_Proxy::Fl_Grid_Proxy(int X,int Y,int W,int H) +: Fl_Grid(X,Y,W,H), + transient_(NULL), + num_transient_(0), + cap_transient_(0) +{ +} + +Fl_Grid_Proxy::~Fl_Grid_Proxy() { + int i; + if (transient_) { + for (i=0; i 0) { + Fl_Grid::resize(X, Y, W, H); + } else { + Fl_Widget::resize(X, Y, W, H); + } + redraw(); +} + +/** + Override draw() to make groups with no box or flat box background visible. + */ +void Fl_Grid_Proxy::draw() { + if (show_ghosted_outline && (box() == FL_NO_BOX)) { + fl_rect(x(), y(), w(), h(), Fl::box_color(fl_color_average(FL_FOREGROUND_COLOR, color(), .1f))); + } + Fl_Grid::draw(); +} + +/** + Draw additional markings in the overlay plane when a grid is selected. + */ +void Fl_Grid_Proxy::draw_overlay() { + fl_line_style(FL_DOT); + grid_color = fl_color(); + draw_grid(); + fl_color(grid_color); +} + +/** + Move a cell into the grid or within the grid. + + If the target cell is already taken, \p how will determine what to do: + + If \p how is 0, the existing cell at \p to_row, \p to_col will be deleted, + unlinking the occupant from the grid. \p in_child will the be inserted at the + given location. + + If \p how is 1, the old cell will remain intact, however \p in_child will be + unlinked from the grid. + + If \p how is 2, the old cell will remain intact, and \p in_child will be + removed from the grid, but it will be stored in the transient list and + resized to the target cell position and size. If \p in_child is later + moved to an unoccupied cell, it will be removed from the transient list and + relinked to the grid. Rowspan and colspan are ignored here. + + \param[in] in_child must already be a child of grid + \param[in] to_row, to_col move the child into this cell + \param[in] how 0: replace occupant, 1: don't replace, 2: make transient + if occupied + */ +void Fl_Grid_Proxy::move_cell(Fl_Widget *in_child, int to_row, int to_col, int how) { + // the child must already be a true child of grid + assert(find(in_child)row() == to_row && old_cell->col() == to_col) return; + rowspan = old_cell->rowspan(); + colspan = old_cell->colspan(); + align = old_cell->align(); + old_cell->minimum_size(&w, &h); + } + if ((to_row < 0) || (to_row+rowspan > rows())) return; + if ((to_col < 0) || (to_col+colspan > cols())) return; + Fl_Grid::Cell *new_cell = NULL; + if (how == 0) { // replace old occupant in cell, making that one homeless + new_cell = widget(in_child, to_row, to_col, rowspan, colspan, align); + } else if (how == 1) { // don't replace an old occupant, making ourselves homeless + // todo: colspan, rowspan? + if (cell(to_row, to_col) == NULL) { + new_cell = widget(in_child, to_row, to_col, rowspan, colspan, align); + } else { + if (old_cell) remove_cell(old_cell->row(), old_cell->col()); + } + } else if (how == 2) { + Cell *current = cell(to_row, to_col); + if (current == NULL) { + new_cell = widget(in_child, to_row, to_col, rowspan, colspan, align); + } else { + if (old_cell) remove_cell(old_cell->row(), old_cell->col()); + new_cell = transient_widget(in_child, to_row, to_col, rowspan, colspan, align); + Fl_Widget *w = current->widget(); + Fl_Type::allow_layout++; + in_child->resize(w->x(), w->y(), w->w(), w->h()); + Fl_Type::allow_layout--; + } + } + if (new_cell) new_cell->minimum_size(w, h); +} + +/** + Generate or replace a transient widget entry. + + If the widget is in the cell list, it will be removed there. + If the widget is already transient, the cell will be replaced. + + \param[in] wi a child of this Fl_Grid_Proxy, that may be linked to a cell or transient cell + \param[in] row, col, row_span, col_span, align cell parameters + */ +Fl_Grid::Cell* Fl_Grid_Proxy::transient_widget(Fl_Widget *wi, int row, int col, int row_span, int col_span, Fl_Grid_Align align) { + int i = 0; + bool remove_old_cell = false; + Cell *old_cell = cell(wi); + if (old_cell) { + remove_old_cell = true; + } else { + for (i=0; irowspan(row_span); + new_cell->colspan(col_span); + new_cell->align(align); + if (old_cell) { + int mw, mh; + old_cell->minimum_size(&mw, &mh); + new_cell->minimum_size(mw, mh); + if (remove_old_cell) { + remove_cell(old_cell->row(), old_cell->col()); + } else { + delete old_cell; + } + } + if (i == num_transient_) { + transient_make_room_(num_transient_ + 1); + transient_[i].widget = wi; + num_transient_++; + } + transient_[i].cell = new_cell; + return new_cell; +} + +/** + Make room for at least n transient widgets in the array. + \param[in] n minimum number of entries + */ +void Fl_Grid_Proxy::transient_make_room_(int n) { + if (n > cap_transient_) { + cap_transient_ = n + 10; + transient_ = (Cell_Widget_Pair*)::realloc(transient_, cap_transient_ * sizeof(Cell_Widget_Pair)); + } +} + +/** + Remove a widget form the list and deallocate the transient cell. + \param[in] w remove the transient cell for this widget + */ +void Fl_Grid_Proxy::transient_remove_(Fl_Widget *w) { + for (int i=0; ilayout(3, 3); + Fl_Group::current(0); + return g; +} + +Fl_Widget *Fl_Grid_Type::enter_live_mode(int top) { + Fl_Grid *grid = new Fl_Grid(o->x(), o->y(), o->w(), o->h()); + return propagate_live_mode(grid); +} + +void Fl_Grid_Type::leave_live_mode() { +} + +void Fl_Grid_Type::copy_properties() +{ + super::copy_properties(); + Fl_Grid *d = (Fl_Grid*)live_widget, *s =(Fl_Grid*)o; + d->layout(s->rows(), s->cols()); + int lm, tm, rm, bm; + s->margin(&lm, &tm, &rm, &bm); + d->margin(lm, tm, rm, bm); + int rg, cg; + s->gap(&rg, &cg); + d->gap(rg, cg); + // copy col widths, heights, and gaps + for (int c=0; ccols(); c++) { + d->col_width(c, s->col_width(c)); + d->col_gap(c, s->col_gap(c)); + d->col_weight(c, s->col_weight(c)); + } + // copy row widths, heights, and gaps + for (int r=0; rrows(); r++) { + d->row_height(r, s->row_height(r)); + d->row_gap(r, s->row_gap(r)); + d->row_weight(r, s->row_weight(r)); + } +} + +void Fl_Grid_Type::copy_properties_for_children() { + Fl_Grid *d = (Fl_Grid*)live_widget, *s =(Fl_Grid*)o; + for (int i=0; ichildren(); i++) { + Fl_Grid::Cell *cell = s->cell(s->child(i)); + if (cell && ichildren()) { + d->widget(d->child(i), + cell->row(), cell->col(), + cell->rowspan(), cell->colspan(), + cell->align()); + } + } + d->layout(); +} + +void Fl_Grid_Type::write_properties(Fd_Project_Writer &f) +{ + super::write_properties(f); + Fl_Grid* grid = (Fl_Grid*)o; + int i, rows = grid->rows(), cols = grid->cols(); + f.write_indent(level+1); + f.write_string("dimensions {%d %d}", rows, cols); + int lm, tm, rm, bm; + grid->margin(&lm, &tm, &rm, &bm); + if (lm!=0 || tm!=0 || rm!=0 || bm!=0) + f.write_string("margin {%d %d %d %d}", lm, tm, rm, bm); + int rg, cg; + grid->gap(&rg, &cg); + if (rg!=0 || cg!=0) + f.write_string("gap {%d %d}", rg, cg); + // -- write all row heights if one of them is not the default 0 + for (i=0; irow_height(i)!=0) break; + if (irow_height(i)); + f.write_string("}"); + } + // -- write all row weights if one of them is not the default 50 + for (i=0; irow_weight(i)!=50) break; + if (irow_weight(i)); + f.write_string("}"); + } + // -- write all row gaps if one of them is not the default -1 + for (i=0; irow_gap(i)!=-1) break; + if (irow_gap(i)); + f.write_string("}"); + } + // -- write all col widths if one of them is not the default 0 + for (i=0; icol_width(i)!=0) break; + if (icol_width(i)); + f.write_string("}"); + } + // -- write all col weights if one of them is not the default 50 + for (i=0; icol_weight(i)!=50) break; + if (icol_weight(i)); + f.write_string("}"); + } + // -- write all col gaps if one of them is not the default -1 + for (i=0; icol_gap(i)!=-1) break; + if (icol_gap(i)); + f.write_string("}"); + } +} + +void Fl_Grid_Type::read_property(Fd_Project_Reader &f, const char *c) +{ + Fl_Grid* grid = (Fl_Grid*)o; + if (!strcmp(c,"dimensions")) { + int rows = 3, cols = 3; + if (sscanf(f.read_word(),"%d %d", &rows, &cols) == 2) + grid->layout(rows, cols); + } else if (!strcmp(c,"margin")) { + int lm, tm, rm, bm; + if (sscanf(f.read_word(),"%d %d %d %d", &lm, &tm, &rm, &bm) == 4) + grid->margin(lm, tm, rm, bm); + } else if (!strcmp(c,"gap")) { + int rg, cg; + if (sscanf(f.read_word(),"%d %d", &rg, &cg) == 2) + grid->gap(rg, cg); + } else if (!strcmp(c,"rowheights")) { + int rows = grid->rows(); + f.read_word(1); // "{" + for (int i=0; irow_height(i, f.read_int()); + f.read_word(1); // "}" + } else if (!strcmp(c,"rowweights")) { + int rows = grid->rows(); + f.read_word(1); // "{" + for (int i=0; irow_weight(i, f.read_int()); + f.read_word(1); // "}" + } else if (!strcmp(c,"rowgaps")) { + int rows = grid->rows(); + f.read_word(1); // "{" + for (int i=0; irow_gap(i, f.read_int()); + f.read_word(1); // "}" + } else if (!strcmp(c,"colwidths")) { + int cols = grid->cols(); + f.read_word(1); // "{" + for (int i=0; icol_width(i, f.read_int()); + f.read_word(1); // "}" + } else if (!strcmp(c,"colweights")) { + int cols = grid->cols(); + f.read_word(1); // "{" + for (int i=0; icol_weight(i, f.read_int()); + f.read_word(1); // "}" + } else if (!strcmp(c,"colgaps")) { + int cols = grid->cols(); + f.read_word(1); // "{" + for (int i=0; icol_gap(i, f.read_int()); + f.read_word(1); // "}" + } else { + super::read_property(f, c); + } +} + +void Fl_Grid_Type::write_parent_properties(Fd_Project_Writer &f, Fl_Type *child, bool encapsulate) { + Fl_Grid *grid; + Fl_Widget *child_widget; + Fl_Grid::Cell *cell; + if (!child->is_true_widget()) return super::write_parent_properties(f, child, true); + grid = (Fl_Grid*)o; + child_widget = ((Fl_Widget_Type*)child)->o; + cell = grid->cell(child_widget); + if (!cell) return super::write_parent_properties(f, child, true); + if (encapsulate) { + f.write_indent(level+2); + f.write_string("parent_properties {"); + } + f.write_indent(level+3); + f.write_string("location {%d %d}", cell->row(), cell->col()); + int v = cell->colspan(); + if (v>1) { + f.write_indent(level+3); + f.write_string("colspan %d", v); + } + v = cell->rowspan(); + if (v>1) { + f.write_indent(level+3); + f.write_string("rowspan %d", v); + } + v = (int)cell->align(); + if (v!=FL_GRID_FILL) { + f.write_indent(level+3); + f.write_string("align %d", v); + } + int min_w = 0, min_h = 0; + cell->minimum_size(&min_w, &min_h); + if (min_w!=20 || min_h!=20) { + f.write_indent(level+3); + f.write_string("minsize {%d %d}", min_w, min_h); + } + super::write_parent_properties(f, child, false); + if (encapsulate) { + f.write_indent(level+2); + f.write_string("}"); + } + return; +} + +// NOTE: we have to do this in a loop just as ::read_property() in case a new +// property is added. In the current setup, all the remaining properties +// will be skipped +void Fl_Grid_Type::read_parent_property(Fd_Project_Reader &f, Fl_Type *child, const char *property) { + if (!child->is_true_widget()) { + super::read_parent_property(f, child, property); + return; + } + Fl_Grid *grid = (Fl_Grid*)o; + Fl_Widget *child_widget = ((Fl_Widget_Type*)child)->o; + if (!strcmp(property, "location")) { + int row = -1, col = -1; + const char *value = f.read_word(); + sscanf(value, "%d %d", &row, &col); + Fl_Grid::Cell *cell = grid->widget(child_widget, row, col); + if (cell) { + int min_w = 20, min_h = 20; + cell->minimum_size(min_w, min_h); + } + } else if (!strcmp(property, "colspan")) { + int colspan = atoi(f.read_word()); + Fl_Grid::Cell *cell = grid->cell(child_widget); + if (cell) cell->colspan(colspan); + } else if (!strcmp(property, "rowspan")) { + int rowspan = atoi(f.read_word()); + Fl_Grid::Cell *cell = grid->cell(child_widget); + if (cell) cell->rowspan(rowspan); + } else if (!strcmp(property, "align")) { + int align = atoi(f.read_word()); + Fl_Grid::Cell *cell = grid->cell(child_widget); + if (cell) cell->align((Fl_Grid_Align)align); + } if (!strcmp(property, "minsize")) { + int min_w = 20, min_h = 20; + const char *value = f.read_word(); + sscanf(value, "%d %d", &min_w, &min_h); + Fl_Grid::Cell *cell = grid->cell(child_widget); + if (cell) cell->minimum_size(min_w, min_h); + } else { + super::read_parent_property(f, child, property); + } +} + +void Fl_Grid_Type::write_code1(Fd_Code_Writer& f) { + const char *var = name() ? name() : "o"; + Fl_Grid* grid = (Fl_Grid*)o; + Fl_Widget_Type::write_code1(f); + int i, rows = grid->rows(), cols = grid->cols(); + f.write_c("%s%s->layout(%d, %d);\n", f.indent(), var, rows, cols); + int lm, tm, rm, bm; + grid->margin(&lm, &tm, &rm, &bm); + if (lm!=0 || tm!=0 || rm!=0 || bm!=0) + f.write_c("%s%s->margin(%d, %d, %d, %d);\n", f.indent(), var, lm, tm, rm, bm); + int rg, cg; + grid->gap(&rg, &cg); + if (rg!=0 || cg!=0) + f.write_c("%s%s->gap(%d, %d);\n", f.indent(), var, rg, cg); + // -- write all row heights if one of them is not the default 0 + for (i=0; irow_height(i)!=0) break; + if (irow_height(0)); + for (i=1; irow_height(i)); + f.write_c(" };\n"); + f.write_c("%s%s->row_height(rowheights, %d);\n", f.indent(), var, rows); + } + // -- write all row weights if one of them is not the default 50 + for (i=0; irow_weight(i)!=50) break; + if (irow_weight(0)); + for (i=1; irow_weight(i)); + f.write_c(" };\n"); + f.write_c("%s%s->row_weight(rowweights, %d);\n", f.indent(), var, rows); + } + // -- write all row gaps if one of them is not the default -1 + for (i=0; irow_gap(i)!=-1) break; + if (irow_gap(0)); + for (i=1; irow_gap(i)); + f.write_c(" };\n"); + f.write_c("%s%s->row_gap(rowgaps, %d);\n", f.indent(), var, rows); + } + // -- write all col widths if one of them is not the default 0 + for (i=0; icol_width(i)!=0) break; + if (icol_width(0)); + for (i=1; icol_width(i)); + f.write_c(" };\n"); + f.write_c("%s%s->col_width(colwidths, %d);\n", f.indent(), var, cols); + } + // -- write all col weights if one of them is not the default 50 + for (i=0; icol_weight(i)!=50) break; + if (icol_weight(0)); + for (i=1; icol_weight(i)); + f.write_c(" };\n"); + f.write_c("%s%s->col_weight(colweights, %d);\n", f.indent(), var, cols); + } + // -- write all col gaps if one of them is not the default -1 + for (i=0; icol_gap(i)!=-1) break; + if (icol_gap(0)); + for (i=1; icol_gap(i)); + f.write_c(" };\n"); + f.write_c("%s%s->col_gap(colgaps, %d);\n", f.indent(), var, cols); + } +} + +void Fl_Grid_Type::write_code2(Fd_Code_Writer& f) { + const char *var = name() ? name() : "o"; + Fl_Grid* grid = (Fl_Grid*)o; + bool first_cell = true; + for (int i=0; ichildren(); i++) { + Fl_Widget *c = grid->child(i); + Fl_Grid::Cell *cell = grid->cell(c); + if (cell) { + if (first_cell) { + f.write_c("%sFl_Grid::Cell *cell = NULL;\n", f.indent()); + first_cell = false; + } + f.write_c("%scell = %s->widget(%s->child(%d), %d, %d, %d, %d, %d);\n", + f.indent(), var, var, i, cell->row(), cell->col(), + cell->rowspan(), cell->colspan(), cell->align()); + int min_w = 20, min_h = 20; + cell->minimum_size(&min_w, &min_h); + f.write_c("%sif (cell) cell->minimum_size(%d, %d);\n", f.indent(), min_w, min_h); + } + } + super::write_code2(f); +} + +void Fl_Grid_Type::add_child(Fl_Type* a, Fl_Type* b) { + super::add_child(a, b); + Fl_Grid* grid = (Fl_Grid*)o; + grid->need_layout(1); + grid->redraw(); +} + +void Fl_Grid_Type::move_child(Fl_Type* a, Fl_Type* b) { + super::move_child(a, b); + Fl_Grid* grid = (Fl_Grid*)o; + grid->need_layout(1); + grid->redraw(); +} + +void Fl_Grid_Type::remove_child(Fl_Type* a) { + super::remove_child(a); + Fl_Grid* grid = (Fl_Grid*)o; + grid->need_layout(1); + grid->redraw(); +} + +/** Update the initial size of a child widget. + Fl_Grid keeps track of the size of children when they are first added. In + FLUID, users will want to resize children. So we need to trick Fl_Grid into + taking the new size as the initial size. + */ +void Fl_Grid_Type::child_resized(Fl_Widget_Type *child_type) { + Fl_Grid *grid = (Fl_Grid*)o; + Fl_Widget *child = child_type->o; + Fl_Grid::Cell *cell = grid->cell(child); + if (cell && ((cell->align()&FL_GRID_VERTICAL)==0)) { + int min_w = 0, min_h = 0; + cell->minimum_size(&min_w, &min_h); + cell->minimum_size(min_w, child->h()); + } + if (cell && ((cell->align()&FL_GRID_HORIZONTAL)==0)) { + int min_w = 0, min_h = 0; + cell->minimum_size(&min_w, &min_h); + cell->minimum_size(child->w(), min_h); + } + // TODO: if the user resizes an FL_GRID_FILL widget, should we change the alignment? +} + +/** Return the currently selected Grid widget if is a Grid Type. */ +Fl_Grid *Fl_Grid_Type::selected() { + if (current_widget && current_widget->is_a(ID_Grid)) + return ((Fl_Grid*)((Fl_Grid_Type*)current_widget)->o); + return NULL; +} + +/** + Insert a child widget into the cell at the x, y position inside the window. + /param[in] child + /param[in] x, y pixels from the top left of the window + */ +void Fl_Grid_Type::insert_child_at(Fl_Widget *child, int x, int y) { + Fl_Grid_Proxy *grid = (Fl_Grid_Proxy*)o; + int row = -1, col = -1, ml, mt, grg, gcg; + grid->margin(&ml, &mt, NULL, NULL); + grid->gap(&grg, &gcg); + int x0 = grid->x() + Fl::box_dx(grid->box()) + ml; + int y0 = grid->y() + Fl::box_dy(grid->box()) + mt; + + for (int r = 0; r < grid->rows(); r++) { + if (y>y0) row = r; + int gap = grid->row_gap(r)>=0 ? grid->row_gap(r) : grg; + y0 += grid->computed_row_height(r); + y0 += gap; + } + + for (int c = 0; c < grid->cols(); c++) { + if (x>x0) col = c; + int gap = grid->col_gap(c)>=0 ? grid->col_gap(c) : gcg; + x0 += grid->computed_col_width(c); + x0 += gap; + } + + grid->move_cell(child, row, col, 2); +} + +/** + Insert a child widget into the first new cell we can find . + + There are many other possible strategies. How about inserting to the right + of the last added child. Also, what happens if the grid is full? Should + we add a new row at the bottom? + + /param[in] child + */ +void Fl_Grid_Type::insert_child_at_next_free_cell(Fl_Widget *child) { + Fl_Grid_Proxy *grid = (Fl_Grid_Proxy*)o; + if (grid->cell(child)) return; +// The code below would insert the new widget after the last selected one, but +// unfortunately the current_widget is already invalid. +// if (current_widget && (current_widget->parent == this)) { +// Fl_Grid::Cell *current_cell = grid->any_cell(current_widget->o); +// if (current_cell) { +// r = current_cell->row(); +// c = current_cell->col(); +// } +// } + for (int r = 0; r < grid->rows(); r++) { + for (int c = 0; c < grid->cols(); c++) { + if (!grid->cell(r, c)) { + grid->move_cell(child, r, c); + return; + } + } + } + grid->layout(grid->rows() + 1, grid->cols()); + grid->move_cell(child, grid->rows() - 1, 0); +} + +/** Move cells around using the keyboard. + \note this fails if we have two children selected side by side and press 'right', + which will move the left child first, removing the right child from the + cell system. When trying to move the second child, it has no longer an + assigned row or column. + \param[in] child pointer to the child type + \param[in] key code of the last keypress when handling a FL_KEYBOARD event. + */ +void Fl_Grid_Type::keyboard_move_child(Fl_Widget_Type *child, int key) { + Fl_Grid_Proxy *grid = ((Fl_Grid_Proxy*)o); + Fl_Grid::Cell *cell = grid->any_cell(child->o); + if (!cell) return; + if (key == FL_Right) { + grid->move_cell(child->o, cell->row(), cell->col()+1, 2); + } else if (key == FL_Left) { + grid->move_cell(child->o, cell->row(), cell->col()-1, 2); + } else if (key == FL_Up) { + grid->move_cell(child->o, cell->row()-1, cell->col(), 2); + } else if (key == FL_Down) { + grid->move_cell(child->o, cell->row()+1, cell->col(), 2); + } +} + +void Fl_Grid_Type::layout_widget() { + allow_layout++; + ((Fl_Grid*)o)->layout(); + allow_layout--; +} + + +// ---- Widget Panel Callbacks ---------------------------------------- MARK: - + +// TODO: better grid overlay? +// TODO: grid_child_cb should move all selected cells, not just the current_selected. +// TODO: buttons to add and delete rows and columns in the widget dialog +// TODO: ways to resize rows and columns, add and delete them in the project window, pulldown menu? +// TODO: alignment can be FL_GRID_LEFT|FL_GRID_VERTICAL? + +extern Fluid_Coord_Input *widget_grid_row_input, *widget_grid_col_input, +*widget_grid_rowspan_input, *widget_grid_colspan_input; +extern Fl_Group *widget_tab_grid_child; + +void grid_child_cb(Fluid_Coord_Input* i, void* v, int what) { + if ( !current_widget + || !current_widget->parent + || !current_widget->parent->is_a(ID_Grid)) + { + return; + } + Fl_Widget *child = ((Fl_Widget_Type*)current_widget)->o; + Fl_Grid_Proxy *g = ((Fl_Grid_Proxy*)((Fl_Widget_Type*)current_widget->parent)->o); + Fl_Grid::Cell *cell = g->any_cell(child); + if (v == LOAD) { + int v = -1; + if (cell) { + switch (what & 0x00ff) { + case 8: v = cell->row(); break; + case 9: v = cell->col(); break; + case 10: v = cell->rowspan(); break; + case 11: v = cell->colspan(); break; + case 12: cell->minimum_size(&v, NULL); break; + case 13: cell->minimum_size(NULL, &v); break; + } + } + i->value(v); + } else { + undo_checkpoint(); + int v2 = -2, old_v = -2, v = i->value(); + if (i==widget_grid_row_input) v2 = widget_grid_col_input->value(); + if (i==widget_grid_col_input) v2 = widget_grid_row_input->value(); + Fl_Grid::Cell *new_cell = NULL; + if (cell) { + switch (what & 0x00ff) { + case 8: old_v = cell->row(); v2 = cell->col(); break; + case 9: old_v = cell->col(); v2 = cell->row(); break; + case 10: old_v = cell->rowspan(); break; + case 11: old_v = cell->colspan(); break; + case 12: cell->minimum_size(&old_v, &v2); break; + case 13: cell->minimum_size(&v2, &old_v); break; + } + } + switch (what & 0xff00) { + case 0x0100: v--; break; + case 0x0200: v++; break; + } + if (old_v != v) { + switch (what & 0x00ff) { + case 8: + if (v2 == -1 && v >= 0) v2 = 0; + g->move_cell(current_widget->o, v, v2, 2); i->value(v); + break; + case 9: + if (v2 == -1 && v >= 0) v2 = 0; + g->move_cell(current_widget->o, v2, v, 2); i->value(v); + break; + case 10: if (cell && cell->row()+v<=g->rows() && v>0) cell->rowspan(v); + break; + case 11: if (cell && cell->col()+v<=g->cols() && v>0) cell->colspan(v); + break; + case 12: if (cell && v>=0) cell->minimum_size(v, v2); + break; + case 13: if (cell && v>=0) cell->minimum_size(v2, v); + break; + } + if (!cell && new_cell) + new_cell->minimum_size(20, 20); + g->need_layout(true); + set_modflag(1); + } + } +} +void grid_set_row_cb(Fluid_Coord_Input* i, void* v) { + grid_child_cb(i, v, 8); + if (v!=LOAD) widget_tab_grid_child->do_callback(widget_tab_grid_child, LOAD); +} +void grid_dec_row_cb(Fl_Button* i, void* v) { + if (v!=LOAD) { + grid_child_cb(widget_grid_row_input, v, 0x0100 + 8); + widget_tab_grid_child->do_callback(widget_tab_grid_child, LOAD); + } +} +void grid_inc_row_cb(Fl_Button* i, void* v) { + if (v!=LOAD) { + grid_child_cb(widget_grid_row_input, v, 0x0200 + 8); + widget_tab_grid_child->do_callback(widget_tab_grid_child, LOAD); + } +} +void grid_set_col_cb(Fluid_Coord_Input* i, void* v) { + grid_child_cb(i, v, 9); + if (v!=LOAD) widget_tab_grid_child->do_callback(widget_tab_grid_child, LOAD); +} +void grid_dec_col_cb(Fl_Button* i, void* v) { + if (v!=LOAD) { + grid_child_cb(widget_grid_col_input, v, 0x0100 + 9); + widget_tab_grid_child->do_callback(widget_tab_grid_child, LOAD); + } +} +void grid_inc_col_cb(Fl_Button* i, void* v) { + if (v!=LOAD) { + grid_child_cb(widget_grid_col_input, v, 0x0200 + 9); + widget_tab_grid_child->do_callback(widget_tab_grid_child, LOAD); + } +} +void grid_set_rowspan_cb(Fluid_Coord_Input* i, void* v) { + grid_child_cb(i, v, 10); + if (v!=LOAD) widget_tab_grid_child->do_callback(widget_tab_grid_child, LOAD); +} +void grid_dec_rowspan_cb(Fl_Button* i, void* v) { + if (v!=LOAD) { + grid_child_cb(widget_grid_rowspan_input, v, 0x0100 + 10); + widget_tab_grid_child->do_callback(widget_tab_grid_child, LOAD); + } +} +void grid_inc_rowspan_cb(Fl_Button* i, void* v) { + if (v!=LOAD) { + grid_child_cb(widget_grid_rowspan_input, v, 0x0200 + 10); + widget_tab_grid_child->do_callback(widget_tab_grid_child, LOAD); + } +} +void grid_set_colspan_cb(Fluid_Coord_Input* i, void* v) { + grid_child_cb(i, v, 11); + if (v!=LOAD) widget_tab_grid_child->do_callback(widget_tab_grid_child, LOAD); +} +void grid_dec_colspan_cb(Fl_Button* i, void* v) { + if (v!=LOAD) { + grid_child_cb(widget_grid_colspan_input, v, 0x0100 + 11); + widget_tab_grid_child->do_callback(widget_tab_grid_child, LOAD); + } +} +void grid_inc_colspan_cb(Fl_Button* i, void* v) { + if (v!=LOAD) { + grid_child_cb(widget_grid_colspan_input, v, 0x0200 + 11); + widget_tab_grid_child->do_callback(widget_tab_grid_child, LOAD); + } +} +void grid_set_min_wdt_cb(Fluid_Coord_Input* i, void* v) { + grid_child_cb(i, v, 12); +} +void grid_set_min_hgt_cb(Fluid_Coord_Input* i, void* v) { + grid_child_cb(i, v, 13); +} + +void grid_align_horizontal_cb(Fl_Choice* i, void* v) { + if ( !current_widget + || !current_widget->parent + || !current_widget->parent->is_a(ID_Grid)) + { + return; + } + int mask = (FL_GRID_LEFT | FL_GRID_RIGHT | FL_GRID_HORIZONTAL); + Fl_Grid *g = ((Fl_Grid*)((Fl_Widget_Type*)current_widget->parent)->o); + if (v == LOAD) { + int a = FL_GRID_FILL & mask; + Fl_Grid::Cell *cell = g->cell(current_widget->o); + if (cell) { + a = cell->align() & mask; + } + const Fl_Menu_Item *mi = i->find_item_with_argument(a); + if (mi) i->value(mi); + } else { + undo_checkpoint(); + int v = FL_GRID_FILL & mask, old_v = FL_GRID_FILL & mask; + const Fl_Menu_Item *mi = i->mvalue(); + if (mi) v = (int)mi->argument(); + Fl_Grid::Cell *cell = g->cell(current_widget->o); + if (cell) { + old_v = cell->align() & mask; + if (old_v != v) { + cell->align((Fl_Grid_Align)(v | (cell->align() & ~mask))); + g->need_layout(true); + g->redraw(); + set_modflag(1); + } + } + } +} + +void grid_align_vertical_cb(Fl_Choice* i, void* v) { + if ( !current_widget + || !current_widget->parent + || !current_widget->parent->is_a(ID_Grid)) + { + return; + } + int mask = (FL_GRID_TOP | FL_GRID_BOTTOM | FL_GRID_VERTICAL); + Fl_Grid *g = ((Fl_Grid*)((Fl_Widget_Type*)current_widget->parent)->o); + if (v == LOAD) { + int a = FL_GRID_FILL & mask; + Fl_Grid::Cell *cell = g->cell(current_widget->o); + if (cell) { + a = cell->align() & mask; + } + const Fl_Menu_Item *mi = i->find_item_with_argument(a); + if (mi) i->value(mi); + } else { + undo_checkpoint(); + int v = FL_GRID_FILL & mask, old_v = FL_GRID_FILL & mask; + const Fl_Menu_Item *mi = i->mvalue(); + if (mi) v = (int)mi->argument(); + Fl_Grid::Cell *cell = g->cell(current_widget->o); + if (cell) { + old_v = cell->align() & mask; + if (old_v != v) { + cell->align((Fl_Grid_Align)(v | (cell->align() & ~mask))); + g->need_layout(true); + g->redraw(); + set_modflag(1); + } + } + } +} + diff --git a/fluid/nodes/Fl_Grid_Type.h b/fluid/nodes/Fl_Grid_Type.h new file mode 100644 index 000000000..1093a38f3 --- /dev/null +++ b/fluid/nodes/Fl_Grid_Type.h @@ -0,0 +1,82 @@ +// +// Fl_Grid type header file for the Fast Light Tool Kit (FLTK). +// +// Copyright 2023 by Bill Spitzak and others. +// +// This library is free software. Distribution and use rights are outlined in +// the file "COPYING" which should have been included with this file. If this +// file is missing or damaged, see the license at: +// +// https://www.fltk.org/COPYING.php +// +// Please see the following page on how to report bugs and issues: +// +// https://www.fltk.org/bugs.php +// + +#ifndef _FLUID_FL_GRID_TYPE_H +#define _FLUID_FL_GRID_TYPE_H + +#include "nodes/Fl_Group_Type.h" +#include + +// ---- Fl_Grid_Type --------------------------------------------------- MARK: - + +extern const char grid_type_name[]; + +class Fl_Grid_Proxy : public Fl_Grid { +protected: + typedef struct { Fl_Widget *widget; Cell *cell; } Cell_Widget_Pair; + Cell_Widget_Pair *transient_; + int num_transient_; + int cap_transient_; + void transient_make_room_(int n); + void transient_remove_(Fl_Widget *w); +public: + Fl_Grid_Proxy(int X,int Y,int W,int H); + ~Fl_Grid_Proxy(); + void resize(int,int,int,int) FL_OVERRIDE; + void draw() FL_OVERRIDE; + void draw_overlay(); + void move_cell(Fl_Widget *child, int to_row, int to_col, int how = 0); + Cell* any_cell(Fl_Widget *widget) const; + Cell* transient_cell(Fl_Widget *widget) const; + Cell* transient_widget(Fl_Widget *wi, int row, int col, int row_span, int col_span, Fl_Grid_Align align = FL_GRID_FILL); + Cell* widget(Fl_Widget *wi, int row, int col, Fl_Grid_Align align = FL_GRID_FILL); + Cell* widget(Fl_Widget *wi, int row, int col, int rowspan, int colspan, Fl_Grid_Align align = FL_GRID_FILL); +}; + +class Fl_Grid_Type : public Fl_Group_Type +{ + typedef Fl_Group_Type super; +public: + Fl_Grid_Type(); + const char *type_name() FL_OVERRIDE {return grid_type_name;} + const char *alt_type_name() FL_OVERRIDE {return "fltk::GridGroup";} + Fl_Widget_Type *_make() FL_OVERRIDE { return new Fl_Grid_Type(); } + Fl_Widget *widget(int X,int Y,int W,int H) FL_OVERRIDE; + ID id() const FL_OVERRIDE { return ID_Grid; } + bool is_a(ID inID) const FL_OVERRIDE { return (inID==ID_Grid) ? true : super::is_a(inID); } + void write_properties(Fd_Project_Writer &f) FL_OVERRIDE; + void read_property(Fd_Project_Reader &f, const char *) FL_OVERRIDE; + void write_parent_properties(Fd_Project_Writer &f, Fl_Type *child, bool encapsulate) FL_OVERRIDE; + void read_parent_property(Fd_Project_Reader &f, Fl_Type *child, const char *property) FL_OVERRIDE; + Fl_Widget *enter_live_mode(int top=0) FL_OVERRIDE; + void leave_live_mode() FL_OVERRIDE; + void copy_properties() FL_OVERRIDE; + void copy_properties_for_children() FL_OVERRIDE; + void write_code1(Fd_Code_Writer& f) FL_OVERRIDE; + void write_code2(Fd_Code_Writer& f) FL_OVERRIDE; + void add_child(Fl_Type*, Fl_Type*) FL_OVERRIDE; + void move_child(Fl_Type*, Fl_Type*) FL_OVERRIDE; + void remove_child(Fl_Type*) FL_OVERRIDE; + void layout_widget() FL_OVERRIDE; + void child_resized(Fl_Widget_Type *child); + void insert_child_at(Fl_Widget *child, int x, int y); + void insert_child_at_next_free_cell(Fl_Widget *child); + void keyboard_move_child(Fl_Widget_Type*, int key); + + static class Fl_Grid *selected(); +}; + +#endif // _FLUID_FL_GRID_TYPE_H diff --git a/fluid/nodes/Fl_Group_Type.cxx b/fluid/nodes/Fl_Group_Type.cxx new file mode 100644 index 000000000..a7ab20473 --- /dev/null +++ b/fluid/nodes/Fl_Group_Type.cxx @@ -0,0 +1,848 @@ +// +// Fl_Group object code for the Fast Light Tool Kit (FLTK). +// +// Object describing an Fl_Group and links to Fl_Window_Type.C and +// the Fl_Tabs widget, with special stuff to select tab items and +// insure that only one is visible. +// +// Copyright 1998-2023 by Bill Spitzak and others. +// +// This library is free software. Distribution and use rights are outlined in +// the file "COPYING" which should have been included with this file. If this +// file is missing or damaged, see the license at: +// +// https://www.fltk.org/COPYING.php +// +// Please see the following page on how to report bugs and issues: +// +// https://www.fltk.org/bugs.php +// + +#include "nodes/Fl_Group_Type.h" + +#include "app/fluid.h" +#include "app/undo.h" +#include "app/Fd_Snap_Action.h" +#include "io/file.h" +#include "io/code.h" +#include "widgets/widget_browser.h" + +#include +#include +#include +#include +#include +#include +#include "../src/flstring.h" + +#include +#include + + +// ---- Fl_Group_Type -------------------------------------------------- MARK: - + +Fl_Group_Type Fl_Group_type; // the "factory" + +/** + Override group's resize behavior to do nothing to children by default. + \param[in] X, Y, W, H new size + */ +void Fl_Group_Proxy::resize(int X, int Y, int W, int H) { + if (Fl_Type::allow_layout > 0) { + Fl_Group::resize(X, Y, W, H); + } else { + Fl_Widget::resize(X, Y, W, H); + } + redraw(); +} + +/** + Override draw() to make groups with no box or flat box background visible. + */ +void Fl_Group_Proxy::draw() { + if (show_ghosted_outline && (box() == FL_NO_BOX)) { + fl_rect(x(), y(), w(), h(), Fl::box_color(fl_color_average(FL_FOREGROUND_COLOR, color(), .1f))); + } + Fl_Group::draw(); +} + + +/** + \brief Enlarge the group size, so all children fit within. + */ +void fix_group_size(Fl_Type *tt) { + if (!tt || !tt->is_a(ID_Group)) return; + Fl_Group_Type* t = (Fl_Group_Type*)tt; + int X = t->o->x(); + int Y = t->o->y(); + int R = X+t->o->w(); + int B = Y+t->o->h(); + for (Fl_Type *nn = t->next; nn && nn->level > t->level; nn = nn->next) { + if (nn->is_true_widget()) { + Fl_Widget_Type* n = (Fl_Widget_Type*)nn; + int x = n->o->x(); if (x < X) X = x; + int y = n->o->y(); if (y < Y) Y = y; + int r = x+n->o->w();if (r > R) R = r; + int b = y+n->o->h();if (b > B) B = b; + } + } + t->o->resize(X,Y,R-X,B-Y); +} + +extern void group_selected_menuitems(); + +void group_cb(Fl_Widget *, void *) { + if (!Fl_Type::current) { + fl_message("No widgets selected."); + return; + } + if (!Fl_Type::current->is_widget()) { + fl_message("Only widgets and menu items can be grouped."); + return; + } + if (Fl_Type::current->is_a(ID_Menu_Item)) { + group_selected_menuitems(); + return; + } + // The group will be created in the parent group of the current widget + Fl_Type *qq = Fl_Type::current->parent; + Fl_Widget_Type *q = static_cast(Fl_Type::current); + while (qq && !qq->is_a(ID_Group)) { + qq = qq->parent; + } + if (!qq) { + fl_message("Can't create a new group here."); + return; + } + undo_checkpoint(); + undo_suspend(); + Fl_Type::current = qq; + Fl_Group_Type *n = (Fl_Group_Type*)(Fl_Group_type.make(Strategy::AS_LAST_CHILD)); + n->move_before(q); + n->o->resize(q->o->x(),q->o->y(),q->o->w(),q->o->h()); + for (Fl_Type *t = qq->next; t && (t->level > qq->level);) { + if (t->level != n->level || t == n || !t->selected) { + t = t->next; + continue; + } + Fl_Type *nxt = t->remove(); + t->add(n, Strategy::AS_LAST_CHILD); + t = nxt; + } + fix_group_size(n); + Fl_Type::current = q; + n->layout_widget(); + widget_browser->rebuild(); + undo_resume(); + set_modflag(1); +} + +extern void ungroup_selected_menuitems(); + +void ungroup_cb(Fl_Widget *, void *) { + if (!Fl_Type::current) { + fl_message("No widgets selected."); + return; + } + if (!Fl_Type::current->is_widget()) { + fl_message("Only widgets and menu items can be ungrouped."); + return; + } + if (Fl_Type::current->is_a(ID_Menu_Item)) { + ungroup_selected_menuitems(); + return; + } + + Fl_Widget_Type *q = static_cast(Fl_Type::current); + int q_level = q->level; + Fl_Type *qq = Fl_Type::current->parent; + while (qq && !qq->is_true_widget()) qq = qq->parent; + if (!qq || !qq->is_a(ID_Group)) { + fl_message("Only menu widgets inside a group can be ungrouped."); + return; + } + undo_checkpoint(); + undo_suspend(); + Fl_Type::current = qq; + for (Fl_Type *t = qq->next; t && (t->level > qq->level);) { + if (t->level != q_level || !t->selected) { + t = t->next; + continue; + } + Fl_Type *nxt = t->remove(); + t->insert(qq); + t = nxt; + } + if (!qq->next || (qq->next->level <= qq->level)) { + qq->remove(); + delete qq; // qq has no children that need to be delete + } + Fl_Type::current = q; + widget_browser->rebuild(); + undo_resume(); + set_modflag(1); +} + +void Fl_Group_Type::ideal_size(int &w, int &h) { + if (parent && parent->is_true_widget()) { + Fl_Widget *p = ((Fl_Widget_Type*)parent)->o; + w = p->w() / 2; + h = p->h() / 2; + } else { + w = 140; + h = 140; + } + Fd_Snap_Action::better_size(w, h); +} + +void Fl_Group_Type::write_code1(Fd_Code_Writer& f) { + Fl_Widget_Type::write_code1(f); +} + +void Fl_Group_Type::write_code2(Fd_Code_Writer& f) { + const char *var = name() ? name() : "o"; + write_extra_code(f); + f.write_c("%s%s->end();\n", f.indent(), var); + if (resizable()) { + f.write_c("%sFl_Group::current()->resizable(%s);\n", f.indent(), var); + } + write_block_close(f); +} + +// This is called when o is created. If it is in the tab group make +// sure it is visible: +void Fl_Group_Type::add_child(Fl_Type* cc, Fl_Type* before) { + Fl_Widget_Type* c = (Fl_Widget_Type*)cc; + Fl_Widget* b = before ? ((Fl_Widget_Type*)before)->o : 0; + ((Fl_Group*)o)->insert(*(c->o), b); + o->redraw(); +} + +// This is called when o is deleted. If it is in the tab group make +// sure it is not visible: +void Fl_Group_Type::remove_child(Fl_Type* cc) { + Fl_Widget_Type* c = (Fl_Widget_Type*)cc; + ((Fl_Group*)o)->remove(c->o); + o->redraw(); +} + +// move, don't change selected value: +void Fl_Group_Type::move_child(Fl_Type* cc, Fl_Type* before) { + Fl_Widget_Type* c = (Fl_Widget_Type*)cc; + Fl_Widget* b = before ? ((Fl_Widget_Type*)before)->o : 0; + ((Fl_Group*)o)->insert(*(c->o), b); + o->redraw(); +} + +// live mode support +Fl_Widget* Fl_Group_Type::enter_live_mode(int) { + Fl_Group *grp = new Fl_Group(o->x(), o->y(), o->w(), o->h()); + return propagate_live_mode(grp); +} + +void Fl_Group_Type::leave_live_mode() { +} + +/** + copy all properties from the edit widget to the live widget + */ +void Fl_Group_Type::copy_properties() { + Fl_Widget_Type::copy_properties(); +} + +// ---- Fl_Pack_Type --------------------------------------------------- MARK: - + +Fl_Pack_Type Fl_Pack_type; // the "factory" + +const char pack_type_name[] = "Fl_Pack"; + +Fl_Menu_Item pack_type_menu[] = { + {"HORIZONTAL", 0, 0, (void*)Fl_Pack::HORIZONTAL}, + {"VERTICAL", 0, 0, (void*)Fl_Pack::VERTICAL}, + {0} +}; + +Fl_Widget *Fl_Pack_Type::enter_live_mode(int) { + Fl_Group *grp = new Fl_Pack(o->x(), o->y(), o->w(), o->h()); + return propagate_live_mode(grp); +} + +void Fl_Pack_Type::copy_properties() +{ + Fl_Group_Type::copy_properties(); + Fl_Pack *d = (Fl_Pack*)live_widget, *s =(Fl_Pack*)o; + d->spacing(s->spacing()); +} + +// ---- Fl_Flex_Type --------------------------------------------------- MARK: - + +const char flex_type_name[] = "Fl_Flex"; + +Fl_Menu_Item flex_type_menu[] = { + {"HORIZONTAL", 0, 0, (void*)Fl_Flex::HORIZONTAL}, + {"VERTICAL", 0, 0, (void*)Fl_Flex::VERTICAL}, + {0}}; + +Fl_Flex_Type Fl_Flex_type; // the "factory" + +/** + Override flex's resize behavior to do nothing to children by default. + + \param[in] X, Y, W, H new size + */ +void Fl_Flex_Proxy::resize(int X, int Y, int W, int H) { + if (Fl_Type::allow_layout > 0) { + Fl_Flex::resize(X, Y, W, H); + } else { + Fl_Widget::resize(X, Y, W, H); + } + redraw(); +} + +/** + Override draw() to make groups with no box or flat box background visible. + */ +void Fl_Flex_Proxy::draw() { + if (show_ghosted_outline && (box() == FL_NO_BOX)) { + fl_rect(x(), y(), w(), h(), Fl::box_color(fl_color_average(FL_FOREGROUND_COLOR, color(), .1f))); + } + Fl_Flex::draw(); +} + +Fl_Widget *Fl_Flex_Type::enter_live_mode(int) { + Fl_Flex *grp = new Fl_Flex(o->x(), o->y(), o->w(), o->h()); + propagate_live_mode(grp); + Fl_Flex *d = grp, *s =(Fl_Flex*)o; + int nc = s->children(), nd = d->children(); + if (nc>nd) nc = nd; + for (int i=0; ifixed(s->child(i))) { + Fl_Widget *dc = d->child(i); + d->fixed(d->child(i), s->horizontal() ? dc->w() : dc->h()); + } + } + return grp; +} + +void Fl_Flex_Type::copy_properties() +{ + Fl_Group_Type::copy_properties(); + Fl_Flex *d = (Fl_Flex*)live_widget, *s =(Fl_Flex*)o; + int lm, tm, rm, bm; + s->margin(&lm, &tm, &rm, &bm); + d->margin(lm, tm, rm, bm); + d->gap( s->gap() ); +} + +void Fl_Flex_Type::copy_properties_for_children() { + Fl_Flex *d = (Fl_Flex*)live_widget, *s =(Fl_Flex*)o; + for (int i=0; ichildren(); i++) { + if (s->fixed(s->child(i)) && ichildren()) { + if (s->horizontal()) { + d->fixed(d->child(i), d->child(i)->w()); + } else { + d->fixed(d->child(i), d->child(i)->h()); + } + } + } + d->layout(); +} + +void Fl_Flex_Type::write_properties(Fd_Project_Writer &f) +{ + Fl_Group_Type::write_properties(f); + Fl_Flex* flex = (Fl_Flex*)o; + int lm, tm, rm, bm; + flex->margin(&lm, &tm, &rm, &bm); + if (lm!=0 || tm!=0 || rm!=0 || bm!=0) + f.write_string("margin {%d %d %d %d}", lm, tm, rm, bm); + if (flex->gap()) + f.write_string("gap %d", flex->gap()); + int nSet = 0; + for (int i=0; ichildren(); i++) + if (flex->fixed(flex->child(i))) + nSet++; + if (nSet) { + f.write_string("fixed_size_tuples {%d", nSet); + for (int i=0; ichildren(); i++) { + Fl_Widget *ci = flex->child(i); + if (flex->fixed(ci)) + f.write_string(" %d %d", i, flex->horizontal() ? ci->w() : ci->h()); + } + f.write_string("}"); + } +} + +void Fl_Flex_Type::read_property(Fd_Project_Reader &f, const char *c) +{ + Fl_Flex* flex = (Fl_Flex*)o; + suspend_auto_layout = 1; + if (!strcmp(c,"margin")) { + int lm, tm, rm, bm; + if (sscanf(f.read_word(),"%d %d %d %d",&lm,&tm,&rm,&bm) == 4) + flex->margin(lm, tm, rm, bm); + } else if (!strcmp(c,"gap")) { + int g; + if (sscanf(f.read_word(),"%d",&g)) + flex->gap(g); + } else if (!strcmp(c,"fixed_size_tuples")) { + f.read_word(1); // must be '{' + const char *nStr = f.read_word(1); // number of indices in table + fixedSizeTupleSize = atoi(nStr); + fixedSizeTuple = new int[fixedSizeTupleSize*2]; + for (int i=0; i0) { + for (int i=0; i=0 && ixchildren()) { + Fl_Widget *ci = flex->child(ix); + flex->fixed(ci, size); + } + } + fixedSizeTupleSize = 0; + delete[] fixedSizeTuple; + fixedSizeTuple = NULL; + } + suspend_auto_layout = 0; +} + +void Fl_Flex_Type::write_code2(Fd_Code_Writer& f) { + const char *var = name() ? name() : "o"; + Fl_Flex* flex = (Fl_Flex*)o; + int lm, tm, rm, bm; + flex->margin(&lm, &tm, &rm, &bm); + if (lm!=0 || tm!=0 || rm!=0 || bm!=0) + f.write_c("%s%s->margin(%d, %d, %d, %d);\n", f.indent(), var, lm, tm, rm, bm); + if (flex->gap()) + f.write_c("%s%s->gap(%d);\n", f.indent(), var, flex->gap()); + for (int i=0; ichildren(); ++i) { + Fl_Widget *ci = flex->child(i); + if (flex->fixed(ci)) + f.write_c("%s%s->fixed(%s->child(%d), %d);\n", f.indent(), var, var, i, + flex->horizontal() ? ci->w() : ci->h()); + } + Fl_Group_Type::write_code2(f); +} + +//void Fl_Flex_Type::add_child(Fl_Type* a, Fl_Type* b) { +// Fl_Group_Type::add_child(a, b); +// if (!suspend_auto_layout) +// ((Fl_Flex*)o)->layout(); +//} +// +//void Fl_Flex_Type::move_child(Fl_Type* a, Fl_Type* b) { +// Fl_Group_Type::move_child(a, b); +// if (!suspend_auto_layout) +// ((Fl_Flex*)o)->layout(); +//} + +void Fl_Flex_Type::remove_child(Fl_Type* a) { + if (a->is_widget()) + ((Fl_Flex*)o)->fixed(((Fl_Widget_Type*)a)->o, 0); + Fl_Group_Type::remove_child(a); +// ((Fl_Flex*)o)->layout(); + layout_widget(); +} + +void Fl_Flex_Type::layout_widget() { + allow_layout++; + ((Fl_Flex*)o)->layout(); + allow_layout--; +} + +// Change from HORIZONTAL to VERTICAL or back. +// Children in a horizontal Flex have already the full vertical height. If we +// just change to vertical, the accumulated hight of all children is too big. +// We need to relayout existing children. +void Fl_Flex_Type::change_subtype_to(int n) { + Fl_Flex* f = (Fl_Flex*)o; + if (f->type()==n) return; + + int nc = f->children(); + if (nc > 0) { + int dw = Fl::box_dw(f->box()); + int dh = Fl::box_dh(f->box()); + int lm, tm, rm, bm; + f->margin(&lm, &tm, &rm, &bm); + int gap = f->gap(); + int fw = f->w()-dw-lm-rm-(nc*gap); + if (fw<=nc) fw = nc; // avoid division by zero + int fh = f->h()-dh-tm-bm-(nc*gap); + if (fh<=nc) fh = nc; // avoid division by zero + + if (f->type()==Fl_Flex::HORIZONTAL && n==Fl_Flex::VERTICAL) { + float scl = (float)fh/(float)fw; + for (int i=0; ichild(i); + c->size(f->w(), (int)(c->w()*scl)); + } + } else if (f->type()==Fl_Flex::VERTICAL && n==Fl_Flex::HORIZONTAL) { + float scl = (float)fw/(float)fh; + for (int i=0; ichild(i); + c->size((int)(c->h()*scl), f->h()); + } + } + } + f->type(n); + f->layout(); +} + +int Fl_Flex_Type::parent_is_flex(Fl_Type *t) { + return (t->is_widget() + && t->parent + && t->parent->is_a(ID_Flex)); +} + +/** + Insert a widget in the child list so that it moves as close as possible the position. + + \param[in] child any widget in the tree but this, may already be a child of + this and will be relocated if so + \param[in] x, y pixel coordinates relative to the top left of the window + */ +void Fl_Flex_Type::insert_child_at(Fl_Widget *child, int x, int y) { + Fl_Flex *flex = (Fl_Flex*)o; + // find the insertion point closest to x, y + int d = flex->w() + flex->h(), di = -1; + if (flex->horizontal()) { + int i, dx; + for (i=0; ichildren(); i++) { + dx = x - flex->child(i)->x(); + if (dx < 0) dx = -dx; + if (dx < d) { d = dx; di = i; } + } + dx = x - (flex->x()+flex->w()); + if (dx < 0) dx = -dx; + if (dx < d) { d = dx; di = i; } + } else { + int i, dy; + for (i=0; ichildren(); i++) { + dy = y - flex->child(i)->y(); + if (dy < 0) dy = -dy; + if (dy < d) { d = dy; di = i; } + } + dy = y - (flex->y()+flex->h()); + if (dy < 0) dy = -dy; + if (dy < d) { d = dy; di = i; } + } + if (di > -1) { + flex->insert(*child, di); + } +} + +/** Move children around using the keyboard. + \param[in] child pointer to the child type + \param[in] key code of the last keypress when handling a FL_KEYBOARD event. + */ +void Fl_Flex_Type::keyboard_move_child(Fl_Widget_Type *child, int key) { + Fl_Flex *flex = ((Fl_Flex*)o); + int ix = flex->find(child->o); + if (ix == flex->children()) return; + if (flex->horizontal()) { + if (key==FL_Right) { + flex->insert(*child->o, ix+2); + } else if (key==FL_Left) { + if (ix > 0) flex->insert(*child->o, ix-1); + } + } else { + if (key==FL_Down) { + flex->insert(*child->o, ix+2); + } else if (key==FL_Up) { + if (ix > 0) flex->insert(*child->o, ix-1); + } + } +} + +int Fl_Flex_Type::size(Fl_Type *t, char fixed_only) { + if (!t->is_widget()) return 0; + if (!t->parent) return 0; + if (!t->parent->is_a(ID_Flex)) return 0; + Fl_Flex_Type* ft = (Fl_Flex_Type*)t->parent; + Fl_Flex* f = (Fl_Flex*)ft->o; + Fl_Widget *w = ((Fl_Widget_Type*)t)->o; + if (fixed_only && !f->fixed(w)) return 0; + return f->horizontal() ? w->w() : w->h(); +} + +int Fl_Flex_Type::is_fixed(Fl_Type *t) { + if (!t->is_widget()) return 0; + if (!t->parent) return 0; + if (!t->parent->is_a(ID_Flex)) return 0; + Fl_Flex_Type* ft = (Fl_Flex_Type*)t->parent; + Fl_Flex* f = (Fl_Flex*)ft->o; + Fl_Widget *w = ((Fl_Widget_Type*)t)->o; + return f->fixed(w); +} + +// ---- Fl_Table_Type -------------------------------------------------- MARK: - + +Fl_Table_Type Fl_Table_type; // the "factory" + +static const int MAX_ROWS = 14; +static const int MAX_COLS = 7; + +// this is a minimal table widget used as an example when adding tables in Fluid +class Fluid_Table : public Fl_Table { + int data[MAX_ROWS][MAX_COLS]; // data array for cells + + // Draw the row/col headings + // Make this a dark thin upbox with the text inside. + // + void DrawHeader(const char *s, int X, int Y, int W, int H) { + fl_push_clip(X,Y,W,H); + fl_draw_box(FL_THIN_UP_BOX, X,Y,W,H, row_header_color()); + fl_color(FL_BLACK); + fl_draw(s, X,Y,W,H, FL_ALIGN_CENTER); + fl_pop_clip(); + } + // Draw the cell data + // Dark gray text on white background with subtle border + // + void DrawData(const char *s, int X, int Y, int W, int H) { + fl_push_clip(X,Y,W,H); + // Draw cell bg + fl_color(FL_WHITE); fl_rectf(X,Y,W,H); + // Draw cell data + fl_color(FL_GRAY0); fl_draw(s, X,Y,W,H, FL_ALIGN_CENTER); + // Draw box border + fl_color(color()); fl_rect(X,Y,W,H); + fl_pop_clip(); + } + // Handle drawing table's cells + // Fl_Table calls this function to draw each visible cell in the table. + // It's up to us to use FLTK's drawing functions to draw the cells the way we want. + // + void draw_cell(TableContext context, int ROW=0, int COL=0, int X=0, int Y=0, int W=0, int H=0) FL_OVERRIDE { + static char s[40]; + switch ( context ) { + case CONTEXT_STARTPAGE: // before page is drawn.. + fl_font(FL_HELVETICA, 16); // set the font for our drawing operations + return; + case CONTEXT_COL_HEADER: // Draw column headers + sprintf(s,"%c",'A'+COL); // "A", "B", "C", etc. + DrawHeader(s,X,Y,W,H); + return; + case CONTEXT_ROW_HEADER: // Draw row headers + sprintf(s,"%03d:",ROW); // "001:", "002:", etc + DrawHeader(s,X,Y,W,H); + return; + case CONTEXT_CELL: // Draw data in cells + sprintf(s,"%d",data[ROW][COL]); + DrawData(s,X,Y,W,H); + return; + default: + return; + } + } +public: + Fluid_Table(int x, int y, int w, int h, const char *l=0L) + : Fl_Table(x, y, w, h, l) { + end(); + for ( int r=0; ro : 0; + if (((Fl_Table*)o)->children()==1) { // the FLuid_Table has one extra child + fl_message("Inserting child widgets into an Fl_Table is not recommended.\n" + "Please refer to the documentation on Fl_Table."); + } + ((Fl_Table*)o)->insert(*(c->o), b); + o->redraw(); +} + +void Fl_Table_Type::remove_child(Fl_Type* cc) { + Fl_Widget_Type* c = (Fl_Widget_Type*)cc; + ((Fl_Table*)o)->remove(*(c->o)); + o->redraw(); +} + +void Fl_Table_Type::move_child(Fl_Type* cc, Fl_Type* before) { + Fl_Widget_Type* c = (Fl_Widget_Type*)cc; + Fl_Widget* b = before ? ((Fl_Widget_Type*)before)->o : 0; + ((Fl_Table*)o)->insert(*(c->o), b); + o->redraw(); +} + +Fl_Widget *Fl_Table_Type::enter_live_mode(int) { + Fl_Group *grp = new Fluid_Table(o->x(), o->y(), o->w(), o->h()); + live_widget = grp; + copy_properties(); + grp->end(); + return live_widget; +} + +void Fl_Table_Type::ideal_size(int &w, int &h) { + w = 160; + h = 120; + Fd_Snap_Action::better_size(w, h); +} + +// ---- Fl_Tabs_Type --------------------------------------------------- MARK: - + +Fl_Tabs_Type Fl_Tabs_type; // the "factory" + +const char tabs_type_name[] = "Fl_Tabs"; + +// Override group's resize behavior to do nothing to children: +void Fl_Tabs_Proxy::resize(int X, int Y, int W, int H) { + if (Fl_Type::allow_layout > 0) { + Fl_Tabs::resize(X, Y, W, H); + } else { + Fl_Widget::resize(X, Y, W, H); + } + redraw(); +} + +/** + Override draw() to make groups with no box or flat box background visible. + */ +void Fl_Tabs_Proxy::draw() { + if (show_ghosted_outline && (box() == FL_NO_BOX)) { + fl_rect(x(), y(), w(), h(), Fl::box_color(fl_color_average(FL_FOREGROUND_COLOR, color(), .1f))); + } + Fl_Tabs::draw(); +} + +// This is called when user clicks on a widget in the window. See +// if it is a tab title, and adjust visibility and return new selection: +// If none, return o unchanged: + +Fl_Type* Fl_Tabs_Type::click_test(int x, int y) { + Fl_Tabs *t = (Fl_Tabs*)o; + Fl_Widget *a = t->which(x,y); + if (!a) return 0; // didn't click on tab + // changing the visible tab has an impact on the generated + // source code, so mark this project as changed. + int changed = (a!=t->value()); + // okay, run the tabs ui until they let go of mouse: + t->handle(FL_PUSH); + Fl::pushed(t); + while (Fl::pushed()==t) Fl::wait(); + if (changed) set_modflag(1); + return (Fl_Type*)(t->value()->user_data()); +} + +void Fl_Tabs_Type::add_child(Fl_Type* c, Fl_Type* before) { + Fl_Group_Type::add_child(c, before); +} + +void Fl_Tabs_Type::remove_child(Fl_Type* cc) { + Fl_Widget_Type* c = (Fl_Widget_Type*)cc; + Fl_Tabs *t = (Fl_Tabs*)o; + if (t->value() == c->o) t->value(0); + Fl_Group_Type::remove_child(c); +} + +Fl_Widget *Fl_Tabs_Type::enter_live_mode(int) { + Fl_Tabs *original = static_cast(o); + Fl_Tabs *clone = new Fl_Tabs(o->x(), o->y(), o->w(), o->h()); + propagate_live_mode(clone); + int tab_index = original->find(original->value()); + if ((tab_index>=0) && (tab_indexchildren())) + clone->value(clone->child(tab_index)); + return clone; +} + +// ---- Fl_Scroll_Type ------------------------------------------------- MARK: - + +Fl_Scroll_Type Fl_Scroll_type; // the "factory" + +const char scroll_type_name[] = "Fl_Scroll"; + +Fl_Menu_Item scroll_type_menu[] = { + {"BOTH", 0, 0, 0/*(void*)Fl_Scroll::BOTH*/}, + {"HORIZONTAL", 0, 0, (void*)Fl_Scroll::HORIZONTAL}, + {"VERTICAL", 0, 0, (void*)Fl_Scroll::VERTICAL}, + {"HORIZONTAL_ALWAYS", 0, 0, (void*)Fl_Scroll::HORIZONTAL_ALWAYS}, + {"VERTICAL_ALWAYS", 0, 0, (void*)Fl_Scroll::VERTICAL_ALWAYS}, + {"BOTH_ALWAYS", 0, 0, (void*)Fl_Scroll::BOTH_ALWAYS}, + {0}}; + +Fl_Widget *Fl_Scroll_Type::enter_live_mode(int) { + Fl_Group *grp = new Fl_Scroll(o->x(), o->y(), o->w(), o->h()); + grp->show(); + return propagate_live_mode(grp); +} + +void Fl_Scroll_Type::copy_properties() { + Fl_Group_Type::copy_properties(); + Fl_Scroll *s = (Fl_Scroll*)o, *d = (Fl_Scroll*)live_widget; + d->scroll_to(s->xposition(), s->yposition()); + d->type(s->type()); + d->scrollbar.align(s->scrollbar.align()); + d->hscrollbar.align(s->hscrollbar.align()); +} + +// ---- Fl_Tile_Type --------------------------------------------------- MARK: - + +Fl_Tile_Type Fl_Tile_type; // the "factory" + +const char tile_type_name[] = "Fl_Tile"; + +void Fl_Tile_Type::copy_properties() { + Fl_Group_Type::copy_properties(); + // no additional properties +} + +// ---- Fl_Wizard_Type ------------------------------------------------ MARK: - + +Fl_Wizard_Type Fl_Wizard_type; // the "factory" + +const char wizard_type_name[] = "Fl_Wizard"; + +// Override group's resize behavior to do nothing to children: +void Fl_Wizard_Proxy::resize(int X, int Y, int W, int H) { + if (Fl_Type::allow_layout > 0) { + Fl_Wizard::resize(X, Y, W, H); + } else { + Fl_Widget::resize(X, Y, W, H); + } + redraw(); +} + +/** + Override draw() to make groups with no box or flat box background visible. + */ +void Fl_Wizard_Proxy::draw() { + if (show_ghosted_outline && (box() == FL_NO_BOX)) { + fl_rect(x(), y(), w(), h(), Fl::box_color(fl_color_average(FL_FOREGROUND_COLOR, color(), .1f))); + } + Fl_Wizard::draw(); +} + diff --git a/fluid/nodes/Fl_Group_Type.h b/fluid/nodes/Fl_Group_Type.h new file mode 100644 index 000000000..88645a08e --- /dev/null +++ b/fluid/nodes/Fl_Group_Type.h @@ -0,0 +1,242 @@ +// +// Group type header file for the Fast Light Tool Kit (FLTK). +// +// Copyright 1998-2023 by Bill Spitzak and others. +// +// This library is free software. Distribution and use rights are outlined in +// the file "COPYING" which should have been included with this file. If this +// file is missing or damaged, see the license at: +// +// https://www.fltk.org/COPYING.php +// +// Please see the following page on how to report bugs and issues: +// +// https://www.fltk.org/bugs.php +// + +#ifndef _FLUID_FL_GROUP_TYPE_H +#define _FLUID_FL_GROUP_TYPE_H + +#include "nodes/Fl_Widget_Type.h" + +#include +#include +#include +#include + +void group_cb(Fl_Widget *, void *); +void ungroup_cb(Fl_Widget *, void *); + +// ---- Fl_Group_Type -------------------------------------------------- MARK: - + +/** + Proxy group to use in place of Fl_Group in the interactive window. + + In an interactive environment, groups should not automatically resize their + children. This proxy disables the layout of children by default. Children + layout propagation may be enable temporarily by incrementing `allow_layout` + before resizing and decrementing it again afterwards. + */ +class Fl_Group_Proxy : public Fl_Group { +public: + Fl_Group_Proxy(int X,int Y,int W,int H) : Fl_Group(X, Y, W, H) { Fl_Group::current(0); } + void resize(int x, int y, int w, int h) FL_OVERRIDE; + void draw() FL_OVERRIDE; +}; + +class Fl_Group_Type : public Fl_Widget_Type +{ + typedef Fl_Widget_Type super; +public: + void ideal_size(int &w, int &h) FL_OVERRIDE; + const char *type_name() FL_OVERRIDE {return "Fl_Group";} + const char *alt_type_name() FL_OVERRIDE {return "fltk::Group";} + Fl_Widget *widget(int X,int Y,int W,int H) FL_OVERRIDE { + Fl_Group_Proxy *g = new Fl_Group_Proxy(X,Y,W,H); Fl_Group::current(0); return g;} + Fl_Widget_Type *_make() FL_OVERRIDE {return new Fl_Group_Type();} + void write_code1(Fd_Code_Writer& f) FL_OVERRIDE; + void write_code2(Fd_Code_Writer& f) FL_OVERRIDE; + void add_child(Fl_Type*, Fl_Type*) FL_OVERRIDE; + void move_child(Fl_Type*, Fl_Type*) FL_OVERRIDE; + void remove_child(Fl_Type*) FL_OVERRIDE; + int can_have_children() const FL_OVERRIDE {return 1;} + ID id() const FL_OVERRIDE { return ID_Group; } + bool is_a(ID inID) const FL_OVERRIDE { return (inID==ID_Group) ? true : super::is_a(inID); } + Fl_Widget *enter_live_mode(int top=0) FL_OVERRIDE; + void leave_live_mode() FL_OVERRIDE; + void copy_properties() FL_OVERRIDE; +}; + +// ---- Fl_Pack_Type --------------------------------------------------- MARK: - + +extern const char pack_type_name[]; +extern Fl_Menu_Item pack_type_menu[]; + +class Fl_Pack_Type : public Fl_Group_Type +{ + typedef Fl_Group_Type super; + Fl_Menu_Item *subtypes() FL_OVERRIDE {return pack_type_menu;} +public: + const char *type_name() FL_OVERRIDE {return pack_type_name;} + const char *alt_type_name() FL_OVERRIDE {return "fltk::PackedGroup";} + Fl_Widget_Type *_make() FL_OVERRIDE {return new Fl_Pack_Type();} + ID id() const FL_OVERRIDE { return ID_Pack; } + bool is_a(ID inID) const FL_OVERRIDE { return (inID==ID_Pack) ? true : super::is_a(inID); } + Fl_Widget *enter_live_mode(int top=0) FL_OVERRIDE; + void copy_properties() FL_OVERRIDE; +}; + +// ---- Fl_Flex_Type --------------------------------------------------- MARK: - + +extern const char flex_type_name[]; +extern Fl_Menu_Item flex_type_menu[]; + +class Fl_Flex_Proxy : public Fl_Flex { +public: + Fl_Flex_Proxy(int X,int Y,int W,int H) : Fl_Flex(X, Y, W, H) { Fl_Group::current(0); } + void resize(int x, int y, int w, int h) FL_OVERRIDE; + void draw() FL_OVERRIDE; +}; + +class Fl_Flex_Type : public Fl_Group_Type +{ + typedef Fl_Group_Type super; + Fl_Menu_Item *subtypes() FL_OVERRIDE {return flex_type_menu;} + int fixedSizeTupleSize; /* number of pairs in array */ + int *fixedSizeTuple; /* [ index, size, index2, size2, ... ] */ + int suspend_auto_layout; +public: + Fl_Flex_Type() : fixedSizeTupleSize(0), fixedSizeTuple(NULL), suspend_auto_layout(0) { } + const char *type_name() FL_OVERRIDE {return flex_type_name;} + const char *alt_type_name() FL_OVERRIDE {return "fltk::FlexGroup";} + Fl_Widget_Type *_make() FL_OVERRIDE { return new Fl_Flex_Type(); } + Fl_Widget *widget(int X,int Y,int W,int H) FL_OVERRIDE { + Fl_Flex *g = new Fl_Flex_Proxy(X,Y,W,H); Fl_Group::current(0); return g;} + ID id() const FL_OVERRIDE { return ID_Flex; } + bool is_a(ID inID) const FL_OVERRIDE { return (inID==ID_Flex) ? true : super::is_a(inID); } + void write_properties(Fd_Project_Writer &f) FL_OVERRIDE; + void read_property(Fd_Project_Reader &f, const char *) FL_OVERRIDE; + Fl_Widget *enter_live_mode(int top=0) FL_OVERRIDE; + void copy_properties() FL_OVERRIDE; + void copy_properties_for_children() FL_OVERRIDE; + void postprocess_read() FL_OVERRIDE; + void write_code2(Fd_Code_Writer& f) FL_OVERRIDE; +// void add_child(Fl_Type*, Fl_Type*) FL_OVERRIDE; +// void move_child(Fl_Type*, Fl_Type*) FL_OVERRIDE; + void remove_child(Fl_Type*) FL_OVERRIDE; + void layout_widget() FL_OVERRIDE; + void change_subtype_to(int n); + void insert_child_at(Fl_Widget *child, int x, int y); + void keyboard_move_child(Fl_Widget_Type*, int key); + static int parent_is_flex(Fl_Type*); + static int size(Fl_Type*, char fixed_only=0); + static int is_fixed(Fl_Type*); +}; + +// ---- Fl_Table_Type -------------------------------------------------- MARK: - + +class Fl_Table_Type : public Fl_Group_Type +{ + typedef Fl_Group_Type super; +public: + void ideal_size(int &w, int &h) FL_OVERRIDE; + const char *type_name() FL_OVERRIDE { return "Fl_Table"; } + const char *alt_type_name() FL_OVERRIDE { return "fltk::TableGroup"; } + Fl_Widget_Type *_make() FL_OVERRIDE { return new Fl_Table_Type(); } + Fl_Widget *widget(int X, int Y, int W, int H) FL_OVERRIDE; + ID id() const FL_OVERRIDE { return ID_Table; } + bool is_a(ID inID) const FL_OVERRIDE { return (inID==ID_Table) ? true : super::is_a(inID); } + Fl_Widget *enter_live_mode(int top=0) FL_OVERRIDE; + void add_child(Fl_Type*, Fl_Type*) FL_OVERRIDE; + void move_child(Fl_Type*, Fl_Type*) FL_OVERRIDE; + void remove_child(Fl_Type*) FL_OVERRIDE; +}; + +// ---- Fl_Tabs_Type --------------------------------------------------- MARK: - + +extern const char tabs_type_name[]; + +class Fl_Tabs_Proxy : public Fl_Tabs { +public: + Fl_Tabs_Proxy(int X,int Y,int W,int H) : Fl_Tabs(X,Y,W,H) {} + void resize(int,int,int,int) FL_OVERRIDE; + void draw() FL_OVERRIDE; +}; + +class Fl_Tabs_Type : public Fl_Group_Type +{ + typedef Fl_Group_Type super; +public: + const char *type_name() FL_OVERRIDE {return tabs_type_name;} + const char *alt_type_name() FL_OVERRIDE {return "fltk::TabGroup";} + Fl_Widget *widget(int X,int Y,int W,int H) FL_OVERRIDE { + Fl_Tabs_Proxy *g = new Fl_Tabs_Proxy(X,Y,W,H); Fl_Group::current(0); return g;} + Fl_Widget_Type *_make() FL_OVERRIDE {return new Fl_Tabs_Type();} + Fl_Type* click_test(int,int) FL_OVERRIDE; + void add_child(Fl_Type*, Fl_Type*) FL_OVERRIDE; + void remove_child(Fl_Type*) FL_OVERRIDE; + ID id() const FL_OVERRIDE { return ID_Tabs; } + bool is_a(ID inID) const FL_OVERRIDE { return (inID==ID_Tabs) ? true : super::is_a(inID); } + Fl_Widget *enter_live_mode(int top=0) FL_OVERRIDE; +}; + +// ---- Fl_Scroll_Type ------------------------------------------------- MARK: - + +extern const char scroll_type_name[]; +extern Fl_Menu_Item scroll_type_menu[]; + +class Fl_Scroll_Type : public Fl_Group_Type +{ + typedef Fl_Group_Type super; + Fl_Menu_Item *subtypes() FL_OVERRIDE {return scroll_type_menu;} +public: + const char *type_name() FL_OVERRIDE {return scroll_type_name;} + const char *alt_type_name() FL_OVERRIDE {return "fltk::ScrollGroup";} + Fl_Widget_Type *_make() FL_OVERRIDE {return new Fl_Scroll_Type();} + ID id() const FL_OVERRIDE { return ID_Scroll; } + bool is_a(ID inID) const FL_OVERRIDE { return (inID==ID_Scroll) ? true : super::is_a(inID); } + Fl_Widget *enter_live_mode(int top=0) FL_OVERRIDE; + void copy_properties() FL_OVERRIDE; +}; + +// ---- Fl_Tile_Type --------------------------------------------------- MARK: - + +extern const char tile_type_name[]; + +class Fl_Tile_Type : public Fl_Group_Type +{ + typedef Fl_Group_Type super; +public: + const char *type_name() FL_OVERRIDE {return tile_type_name;} + const char *alt_type_name() FL_OVERRIDE {return "fltk::TileGroup";} + Fl_Widget_Type *_make() FL_OVERRIDE {return new Fl_Tile_Type();} + ID id() const FL_OVERRIDE { return ID_Tile; } + bool is_a(ID inID) const FL_OVERRIDE { return (inID==ID_Tile) ? true : super::is_a(inID); } + void copy_properties() FL_OVERRIDE; +}; + +// ---- Fl_Wizard_Type ------------------------------------------------- MARK: - + +class Fl_Wizard_Proxy : public Fl_Wizard { +public: + Fl_Wizard_Proxy(int X,int Y,int W,int H) : Fl_Wizard(X,Y,W,H) {} + void resize(int,int,int,int) FL_OVERRIDE; + void draw() FL_OVERRIDE; +}; + +extern const char wizard_type_name[]; + +class Fl_Wizard_Type : public Fl_Group_Type +{ + typedef Fl_Group_Type super; +public: + const char *type_name() FL_OVERRIDE {return wizard_type_name;} + const char *alt_type_name() FL_OVERRIDE {return "fltk::WizardGroup";} + Fl_Widget *widget(int X,int Y,int W,int H) FL_OVERRIDE { + Fl_Wizard_Proxy *g = new Fl_Wizard_Proxy(X,Y,W,H); Fl_Group::current(0); return g;} + Fl_Widget_Type *_make() FL_OVERRIDE {return new Fl_Wizard_Type();} + ID id() const FL_OVERRIDE { return ID_Wizard; } + bool is_a(ID inID) const FL_OVERRIDE { return (inID==ID_Wizard) ? true : super::is_a(inID); } +}; + +#endif // _FLUID_FL_GROUP_TYPE_H diff --git a/fluid/nodes/Fl_Menu_Type.cxx b/fluid/nodes/Fl_Menu_Type.cxx new file mode 100644 index 000000000..684d765f8 --- /dev/null +++ b/fluid/nodes/Fl_Menu_Type.cxx @@ -0,0 +1,922 @@ +// +// Menu item code for the Fast Light Tool Kit (FLTK). +// +// Menu items are kludged by making a phony Fl_Box widget so the normal +// widget panel can be used to control them. +// +// This file also contains code to make Fl_Menu_Button, Fl_Menu_Bar, +// etc widgets. +// +// Copyright 1998-2023 by Bill Spitzak and others. +// +// This library is free software. Distribution and use rights are outlined in +// the file "COPYING" which should have been included with this file. If this +// file is missing or damaged, see the license at: +// +// https://www.fltk.org/COPYING.php +// +// Please see the following page on how to report bugs and issues: +// +// https://www.fltk.org/bugs.php +// + +#include "nodes/Fl_Menu_Type.h" + +#include "app/fluid.h" +#include "app/Fluid_Image.h" +#include "app/mergeback.h" +#include "app/undo.h" +#include "io/file.h" +#include "io/code.h" +#include "nodes/Fl_Window_Type.h" +#include "widgets/custom_widgets.h" +#include "widgets/widget_browser.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../src/flstring.h" + +#include +#include + +Fl_Menu_Item menu_item_type_menu[] = { + {"Normal",0,0,(void*)0}, + {"Toggle",0,0,(void*)FL_MENU_BOX}, + {"Radio",0,0,(void*)FL_MENU_RADIO}, + {0}}; + +static void delete_dependents(Fl_Menu_Item *m) { + if (!m) + return; + int level = 0; + for (;;m++) { + if (m->label()==NULL) { + if (level==0) { + break; + } else { + level--; + } + } + if (m->flags&FL_SUBMENU) + level++; + if (m->labeltype()==FL_MULTI_LABEL) + delete (Fl_Multi_Label*)m->label(); + } +} + +static void delete_menu(Fl_Menu_Item *m) { + if (!m) + return; + delete_dependents(m); + delete[] m; +} + +void Fl_Input_Choice_Type::build_menu() { + Fl_Input_Choice* w = (Fl_Input_Choice*)o; + // count how many Fl_Menu_Item structures needed: + int n = 0; + Fl_Type* q; + for (q = next; q && q->level > level; q = q->next) { + if (q->can_have_children()) n++; // space for null at end of submenu + n++; + } + if (!n) { + if (menusize) delete_menu((Fl_Menu_Item*)(w->menu())); + w->menu(0); + menusize = 0; + } else { + n++; // space for null at end of menu + if (menusizemenu())); + menusize = n+10; + w->menu(new Fl_Menu_Item[menusize]); + } else { + if (menusize) delete_dependents((Fl_Menu_Item*)(w->menu())); + } + // Menus are already built during the .fl file reading process, so if the + // end of a menu list is not read yet, the end markers (label==NULL) will + // not be set, and deleting dependents will randomly free memory. + // Clearing the array should avoid that. + memset( (void*)w->menu(), 0, menusize * sizeof(Fl_Menu_Item) ); + // fill them all in: + Fl_Menu_Item* m = (Fl_Menu_Item*)(w->menu()); + int lvl = level+1; + for (q = next; q && q->level > level; q = q->next) { + Fl_Menu_Item_Type* i = (Fl_Menu_Item_Type*)q; + if (i->o->image()) { + if (i->o->label() && i->o->label()[0]) { + Fl_Multi_Label *ml = new Fl_Multi_Label; + ml->labela = (char*)i->o->image(); + ml->labelb = i->o->label(); + ml->typea = FL_IMAGE_LABEL; + ml->typeb = FL_NORMAL_LABEL; + ml->label(m); + } else { + i->o->image()->label(m); + } + } else { + m->label(i->o->label() ? i->o->label() : "(nolabel)"); + m->labeltype(i->o->labeltype()); + } + m->shortcut(((Fl_Button*)(i->o))->shortcut()); + m->callback(0,(void*)i); + m->flags = i->flags(); + m->labelfont(i->o->labelfont()); + m->labelsize(i->o->labelsize()); + m->labelcolor(i->o->labelcolor()); + if (q->can_have_children()) {lvl++; m->flags |= FL_SUBMENU;} + m++; + int l1 = + (q->next && q->next->is_a(ID_Menu_Item)) ? q->next->level : level; + while (lvl > l1) {m->label(0); m++; lvl--;} + lvl = l1; + } + } + o->redraw(); +} + +/** + Create and add a new Menu Item node. + \param[in] strategy add after current or as last child + \return new Menu Item node + */ +Fl_Type *Fl_Menu_Item_Type::make(Strategy strategy) { + return Fl_Menu_Item_Type::make(0, strategy); +} + +/** + Create an add a specific Menu Item node. + \param[in] flags set to 0, FL_MENU_RADIO, FL_MENU_TOGGLE, or FL_SUBMENU + \param[in] strategy add after current or as last child + \return new Menu Item node + */ +Fl_Type* Fl_Menu_Item_Type::make(int flags, Strategy strategy) { + // Find a good insert position based on the current marked node + Fl_Type *anchor = Fl_Type::current, *p = anchor; + if (p && (strategy.placement() == Strategy::AFTER_CURRENT)) + p = p->parent; + while (p && !(p->is_a(ID_Menu_Manager_) || p->is_a(ID_Submenu))) { + anchor = p; + strategy.placement(Strategy::AFTER_CURRENT); + p = p->parent; + } + if (!p) { + fl_message("Please select a menu widget or a menu item"); + return 0; + } + if (!o) { + o = new Fl_Button(0,0,100,20); // create template widget + } + + Fl_Menu_Item_Type* t = NULL; + if (flags==FL_SUBMENU) { + t = new Fl_Submenu_Type(); + } else { + t = new Fl_Menu_Item_Type(); + } + t->o = new Fl_Button(0,0,100,20); + t->o->type(flags); + t->factory = this; + t->add(anchor, strategy); + if (strategy.source() == Strategy::FROM_USER) { + if (flags==FL_SUBMENU) { + t->label("submenu"); + } else { + t->label("item"); + } + } + return t; +} + +void group_selected_menuitems() { + // The group will be created in the parent group of the current menuitem + if (!Fl_Type::current->is_a(ID_Menu_Item)) { + return; + } + Fl_Menu_Item_Type *q = static_cast(Fl_Type::current); + Fl_Type *qq = Fl_Type::current->parent; + if (!qq || !(qq->is_a(ID_Menu_Manager_) || qq->is_a(ID_Submenu))) { + fl_message("Can't create a new submenu here."); + return; + } + undo_checkpoint(); + undo_suspend(); + Fl_Widget_Type *n = (Fl_Widget_Type*)(q->make(FL_SUBMENU, Strategy::AFTER_CURRENT)); + for (Fl_Type *t = qq->next; t && (t->level > qq->level);) { + if (t->level != n->level || t == n || !t->selected) { + t = t->next; + continue; + } + Fl_Type *nxt = t->remove(); + t->add(n, Strategy::AS_LAST_CHILD); + t = nxt; + } + widget_browser->rebuild(); + undo_resume(); + set_modflag(1); +} + +void ungroup_selected_menuitems() { + // Find the submenu + Fl_Type *qq = Fl_Type::current->parent; + Fl_Widget_Type *q = static_cast(Fl_Type::current); + int q_level = q->level; + if (!qq || !qq->is_a(ID_Submenu)) { + fl_message("Only menu items inside a submenu can be ungrouped."); + return; + } + undo_checkpoint(); + undo_suspend(); + Fl_Type::current = qq; + for (Fl_Type *t = qq->next; t && (t->level > qq->level);) { + if (t->level != q_level || !t->selected) { + t = t->next; + continue; + } + Fl_Type *nxt = t->remove(); + t->insert(qq); + t = nxt; + } + if (!qq->next || (qq->next->level <= qq->level)) { + qq->remove(); + delete qq; // qq has no children that need to be delete + } + Fl_Type::current = q; + widget_browser->rebuild(); + undo_resume(); + set_modflag(1); +} + + +/** + Create and add a new Checkbox Menu Item node. + \param[in] strategy add after current or as last child + \return new node + */ +Fl_Type *Fl_Checkbox_Menu_Item_Type::make(Strategy strategy) { + return Fl_Menu_Item_Type::make(FL_MENU_TOGGLE, strategy); +} + +/** + Create and add a new Radio ButtonMenu Item node. + \param[in] strategy add after current or as last child + \return new node + */ +Fl_Type *Fl_Radio_Menu_Item_Type::make(Strategy strategy) { + return Fl_Menu_Item_Type::make(FL_MENU_RADIO, strategy); +} + +/** + Create and add a new Submenu Item node. + \param[in] strategy add after current or as last child + \return new node + */ +Fl_Type *Fl_Submenu_Type::make(Strategy strategy) { + return Fl_Menu_Item_Type::make(FL_SUBMENU, strategy); +} + +Fl_Menu_Item_Type Fl_Menu_Item_type; +Fl_Checkbox_Menu_Item_Type Fl_Checkbox_Menu_Item_type; +Fl_Radio_Menu_Item_Type Fl_Radio_Menu_Item_type; +Fl_Submenu_Type Fl_Submenu_type; + +//////////////////////////////////////////////////////////////// +// Writing the C code: + +// test functions in Fl_Widget_Type.C: +int is_name(const char *c); +const char *array_name(Fl_Widget_Type *o); +int isdeclare(const char *c); + +// Search backwards to find the parent menu button and return it's name. +// Also put in i the index into the button's menu item array belonging +// to this menu item. +const char* Fl_Menu_Item_Type::menu_name(Fd_Code_Writer& f, int& i) { + i = 0; + Fl_Type* t = prev; + while (t && t->is_a(ID_Menu_Item)) { + // be sure to count the {0} that ends a submenu: + if (t->level > t->next->level) i += (t->level - t->next->level); + // detect empty submenu: + else if (t->level == t->next->level && t->can_have_children()) i++; + t = t->prev; + i++; + } + if (!t) return "\n#error Fl_Menu_Item_Type::menu_name, invalid f\n"; + return f.unique_id(t, "menu", t->name(), t->label()); +} + +void Fl_Menu_Item_Type::write_static(Fd_Code_Writer& f) { + if (image && label() && label()[0]) { + f.write_h_once("#include "); + f.write_h_once("#include "); + } + if (callback() && is_name(callback()) && !user_defined(callback())) + f.write_h_once("extern void %s(Fl_Menu_*, %s);", callback(), + user_data_type() ? user_data_type() : "void*"); + for (int n=0; n < NUM_EXTRA_CODE; n++) { + if (extra_code(n) && isdeclare(extra_code(n))) + f.write_h_once("%s", extra_code(n)); + } + if (callback() && !is_name(callback())) { + // see if 'o' or 'v' used, to prevent unused argument warnings: + int use_o = 0; + int use_v = 0; + const char *d; + for (d = callback(); *d;) { + if (*d == 'o' && !is_id(d[1])) use_o = 1; + if (*d == 'v' && !is_id(d[1])) use_v = 1; + do d++; while (is_id(*d)); + while (*d && !is_id(*d)) d++; + } + const char* cn = callback_name(f); + const char* k = class_name(1); + if (k) { + f.write_c("\nvoid %s::%s_i(Fl_Menu_*", k, cn); + } else { + f.write_c("\nstatic void %s(Fl_Menu_*", cn); + } + if (use_o) f.write_c(" o"); + const char* ut = user_data_type() ? user_data_type() : "void*"; + f.write_c(", %s", ut); + if (use_v) f.write_c(" v"); + f.write_c(") {\n"); + // Matt: disabled f.tag(FD_TAG_GENERIC, 0); + f.write_c_indented(callback(), 1, 0); + if (*(d-1) != ';' && *(d-1) != '}') { + const char *p = strrchr(callback(), '\n'); + if (p) p ++; + else p = callback(); + // Only add trailing semicolon if the last line is not a preprocessor + // statement... + if (*p != '#' && *p) f.write_c(";"); + } + f.write_c("\n"); + // Matt: disabled f.tag(FD_TAG_MENU_CALLBACK, get_uid()); + f.write_c("}\n"); + + // If the menu item is part of a Class or Widget Class, FLUID generates + // a dummy static callback which retrieves a pointer to the class and then + // calls the original callback from within the class context. + // k is the name of the enclosing class (or classes) + if (k) { + // Implement the callback as a static member function + f.write_c("void %s::%s(Fl_Menu_* o, %s v) {\n", k, cn, ut); + // Find the Fl_Menu_ container for this menu item + Fl_Type* t = parent; while (t->is_a(ID_Menu_Item)) t = t->parent; + if (t) { + Fl_Widget_Type *tw = (t->is_widget()) ? static_cast(t) : NULL; + Fl_Type *q = NULL; + // Generate code to call the callback + if (tw->is_a(ID_Menu_Bar) && ((Fl_Menu_Bar_Type*)tw)->is_sys_menu_bar()) { + // Fl_Sys_Menu_Bar removes itself from any parent on macOS, so we + // wrapped it in a class and remeber the parent class in a new + // class memeber variable. + Fl_Menu_Bar_Type *tmb = (Fl_Menu_Bar_Type*)tw; + f.write_c("%s%s* sys_menu_bar = ((%s*)o);\n", f.indent(1), + tmb->sys_menubar_proxy_name(), tmb->sys_menubar_proxy_name()); + f.write_c("%s%s* parent_class = ((%s*)sys_menu_bar->_parent_class);\n", + f.indent(1), k, k); + f.write_c("%sparent_class->%s_i(o,v);\n}\n", + f.indent(1), cn); + } else { + f.write_c("%s((%s*)(o", f.indent(1), k); + // The class pointer is in the user_data field of the top widget + if (t && t->is_a(ID_Input_Choice)) { + // Go up one more level for Fl_Input_Choice, as these are groups themselves + f.write_c("->parent()"); + } + // Now generate code to find the topmost widget in this class + for (t = t->parent; t && t->is_widget() && !is_class(); q = t, t = t->parent) + f.write_c("->parent()"); + // user_data is cast into a pointer to the + if (!q || !q->is_a(ID_Widget_Class)) + f.write_c("->user_data()"); + f.write_c("))->%s_i(o,v);\n}\n", cn); + } + } else { + f.write_c("#error Enclosing Fl_Menu_* not found\n"); + } + } + } + if (image) { + if (!f.c_contains(image)) + image->write_static(f, compress_image_); + } + if (next && next->is_a(ID_Menu_Item)) return; + // okay, when we hit last item in the menu we have to write the + // entire array out: + const char* k = class_name(1); + if (k) { + int i; + f.write_c("\nFl_Menu_Item %s::%s[] = {\n", k, menu_name(f, i)); + } else { + int i; + f.write_c("\nFl_Menu_Item %s[] = {\n", menu_name(f, i)); + } + Fl_Type* t = prev; while (t && t->is_a(ID_Menu_Item)) t = t->prev; + for (Fl_Type* q = t->next; q && q->is_a(ID_Menu_Item); q = q->next) { + ((Fl_Menu_Item_Type*)q)->write_item(f); + int thislevel = q->level; if (q->can_have_children()) thislevel++; + int nextlevel = + (q->next && q->next->is_a(ID_Menu_Item)) ? q->next->level : t->level+1; + while (thislevel > nextlevel) {f.write_c(" {0,0,0,0,0,0,0,0,0},\n"); thislevel--;} + } + f.write_c(" {0,0,0,0,0,0,0,0,0}\n};\n"); + + if (k) { + // Write menu item variables... + t = prev; while (t && t->is_a(ID_Menu_Item)) t = t->prev; + for (Fl_Type* q = t->next; q && q->is_a(ID_Menu_Item); q = q->next) { + Fl_Menu_Item_Type *m = (Fl_Menu_Item_Type*)q; + const char *c = array_name(m); + if (c) { + if (c==m->name()) { + // assign a menu item address directly to a variable + int i; + const char* n = ((Fl_Menu_Item_Type *)q)->menu_name(f, i); + f.write_c("Fl_Menu_Item* %s::%s = %s::%s + %d;\n", k, c, k, n, i); + } else { + // if the name is an array, only define the array. + // The actual assignment is in write_code1(Fd_Code_Writer& f) + f.write_c("Fl_Menu_Item* %s::%s;\n", k, c); + } + } + } + } +} + +int Fl_Menu_Item_Type::flags() { + int i = o->type(); + if (((Fl_Button*)o)->value()) i |= FL_MENU_VALUE; + if (!o->active()) i |= FL_MENU_INACTIVE; + if (!o->visible()) i |= FL_MENU_INVISIBLE; + if (can_have_children()) { + if (user_data() == NULL) i |= FL_SUBMENU; + else i |= FL_SUBMENU_POINTER; + } + if (hotspot()) i |= FL_MENU_DIVIDER; + return i; +} + +void Fl_Menu_Item_Type::write_item(Fd_Code_Writer& f) { + static const char * const labeltypes[] = { + "FL_NORMAL_LABEL", + "FL_NO_LABEL", + "FL_SHADOW_LABEL", + "FL_ENGRAVED_LABEL", + "FL_EMBOSSED_LABEL", + "FL_MULTI_LABEL", + "FL_ICON_LABEL", + "FL_IMAGE_LABEL" + }; + + write_comment_inline_c(f, " "); + f.write_c(" {"); + if (label() && label()[0]) + switch (g_project.i18n_type) { + case FD_I18N_GNU: + // we will call i18n when the menu is instantiated for the first time + f.write_c("%s(", g_project.i18n_gnu_static_function.c_str()); + f.write_cstring(label()); + f.write_c(")"); + break; + case FD_I18N_POSIX: + // fall through: strings can't be translated before a catalog is chosen + default: + f.write_cstring(label()); + } + else + f.write_c("\"\""); + if (((Fl_Button*)o)->shortcut()) { + int s = ((Fl_Button*)o)->shortcut(); + f.write_c(", "); + if (g_project.use_FL_COMMAND) { + if (s & FL_CTRL) { f.write_c("FL_CONTROL|"); s &= ~FL_CTRL; } + if (s & FL_META) { f.write_c("FL_COMMAND|"); s &= ~FL_META; } + } else { + if (s & FL_CTRL) { f.write_c("FL_CTRL|"); s &= ~FL_CTRL; } + if (s & FL_META) { f.write_c("FL_META|"); s &= ~FL_META; } + } + if (s & FL_SHIFT) { f.write_c("FL_SHIFT|"); s &= ~FL_SHIFT; } + if (s & FL_ALT) { f.write_c("FL_ALT|"); s &= ~FL_ALT; } + if ((s < 127) && isprint(s)) + f.write_c("'%c', ", s); + else + f.write_c("0x%x, ", s); + } else { + f.write_c(", 0, "); + } + if (callback()) { + const char* k = is_name(callback()) ? 0 : class_name(1); + if (k) { + f.write_c(" (Fl_Callback*)%s::%s,", k, callback_name(f)); + } else { + f.write_c(" (Fl_Callback*)%s,", callback_name(f)); + } + } else + f.write_c(" 0,"); + if (user_data()) + f.write_c(" (void*)(%s),", user_data()); + else + f.write_c(" 0,"); + f.write_c(" %d, (uchar)%s, %d, %d, %d", flags(), + labeltypes[o->labeltype()], o->labelfont(), o->labelsize(), o->labelcolor()); + f.write_c("},\n"); +} + +void start_menu_initialiser(Fd_Code_Writer& f, int &initialized, const char *name, int index) { + if (!initialized) { + initialized = 1; + f.write_c("%s{ Fl_Menu_Item* o = &%s[%d];\n", f.indent(), name, index); + f.indentation++; + } +} + +void Fl_Menu_Item_Type::write_code1(Fd_Code_Writer& f) { + int i; const char* mname = menu_name(f, i); + + if (!prev->is_a(ID_Menu_Item)) { + // for first menu item, declare the array + if (class_name(1)) { + f.write_h("%sstatic Fl_Menu_Item %s[];\n", f.indent(1), mname); + } else { + f.write_h("extern Fl_Menu_Item %s[];\n", mname); + } + } + + const char *c = array_name(this); + if (c) { + if (class_name(1)) { + f.write_public(public_); + f.write_h("%sstatic Fl_Menu_Item *%s;\n", f.indent(1), c); + } else { + if (c==name()) + f.write_h("#define %s (%s+%d)\n", c, mname, i); + else + f.write_h("extern Fl_Menu_Item *%s;\n", c); + } + } + + if (callback()) { + if (!is_name(callback()) && class_name(1)) { + const char* cn = callback_name(f); + const char* ut = user_data_type() ? user_data_type() : "void*"; + f.write_public(0); + f.write_h("%sinline void %s_i(Fl_Menu_*, %s);\n", f.indent(1), cn, ut); + f.write_h("%sstatic void %s(Fl_Menu_*, %s);\n", f.indent(1), cn, ut); + } + } + + int menuItemInitialized = 0; + // if the name is an array variable, assign the value here + if (name() && strchr(name(), '[')) { + f.write_c("%s%s = &%s[%d];\n", f.indent_plus(1), name(), mname, i); + } + if (image) { + start_menu_initialiser(f, menuItemInitialized, mname, i); + if (label() && label()[0]) { + f.write_c("%sFl_Multi_Label *ml = new Fl_Multi_Label;\n", f.indent()); + f.write_c("%sml->labela = (char*)", f.indent()); + image->write_inline(f); + f.write_c(";\n"); + if (g_project.i18n_type==FD_I18N_NONE) { + f.write_c("%sml->labelb = o->label();\n", f.indent()); + } else if (g_project.i18n_type==FD_I18N_GNU) { + f.write_c("%sml->labelb = %s(o->label());\n", + f.indent(), g_project.i18n_gnu_function.c_str()); + } else if (g_project.i18n_type==FD_I18N_POSIX) { + f.write_c("%sml->labelb = catgets(%s,%s,i+%d,o->label());\n", + f.indent(), + g_project.i18n_pos_file.empty() ? "_catalog" : g_project.i18n_pos_file.c_str(), + g_project.i18n_pos_set.c_str(), msgnum()); + } + f.write_c("%sml->typea = FL_IMAGE_LABEL;\n", f.indent()); + f.write_c("%sml->typeb = FL_NORMAL_LABEL;\n", f.indent()); + f.write_c("%sml->label(o);\n", f.indent()); + } else { + image->write_code(f, 0, "o"); + } + } + if (g_project.i18n_type && label() && label()[0]) { + Fl_Labeltype t = o->labeltype(); + if (image) { + // label was already copied a few lines up + } else if ( t==FL_NORMAL_LABEL || t==FL_SHADOW_LABEL + || t==FL_ENGRAVED_LABEL || t==FL_EMBOSSED_LABEL) { + start_menu_initialiser(f, menuItemInitialized, mname, i); + if (g_project.i18n_type==FD_I18N_GNU) { + f.write_c("%so->label(%s(o->label()));\n", + f.indent(), g_project.i18n_gnu_function.c_str()); + } else if (g_project.i18n_type==FD_I18N_POSIX) { + f.write_c("%so->label(catgets(%s,%s,i+%d,o->label()));\n", + f.indent(), + g_project.i18n_pos_file.empty() ? "_catalog" : g_project.i18n_pos_file.c_str(), + g_project.i18n_pos_set.c_str(), msgnum()); + } + } + } + for (int n=0; n < NUM_EXTRA_CODE; n++) { + if (extra_code(n) && !isdeclare(extra_code(n))) { + start_menu_initialiser(f, menuItemInitialized, mname, i); + f.write_c("%s%s\n", f.indent(), extra_code(n)); + } + } + if (menuItemInitialized) { + f.indentation--; + f.write_c("%s}\n",f.indent()); + } +} + +void Fl_Menu_Item_Type::write_code2(Fd_Code_Writer&) {} + +//////////////////////////////////////////////////////////////// +// This is the base class for widgets that contain a menu (ie +// subclasses of Fl_Menu_. +// This is a parent widget and menu items can be added as +// children. An actual array of Fl_Menu_Items is kept parallel +// with the child objects and updated as they change. + +void Fl_Menu_Base_Type::build_menu() { + Fl_Menu_* w = (Fl_Menu_*)o; + // count how many Fl_Menu_Item structures needed: + int n = 0; + Fl_Type* q; + for (q = next; q && q->level > level; q = q->next) { + if (q->can_have_children()) n++; // space for null at end of submenu + n++; + } + if (!n) { + if (menusize) delete_menu((Fl_Menu_Item*)(w->menu())); + w->menu(0); + menusize = 0; + } else { + n++; // space for null at end of menu + if (menusizemenu())); + menusize = n+10; + w->menu(new Fl_Menu_Item[menusize]); + } else { + if (menusize) delete_dependents((Fl_Menu_Item*)(w->menu())); + } + // Menus are already built during the .fl file reading process, so if the + // end of a menu list is not read yet, the end markers (label==NULL) will + // not be set, and deleting dependents will randomly free memory. + // Clearing the array should avoid that. + memset( (void*)w->menu(), 0, menusize * sizeof(Fl_Menu_Item) ); + // fill them all in: + Fl_Menu_Item* m = (Fl_Menu_Item*)(w->menu()); + int lvl = level+1; + for (q = next; q && q->level > level; q = q->next) { + Fl_Menu_Item_Type* i = (Fl_Menu_Item_Type*)q; + if (i->o->image()) { + if (i->o->label() && i->o->label()[0]) { + Fl_Multi_Label *ml = new Fl_Multi_Label; + ml->labela = (char*)i->o->image(); + ml->labelb = i->o->label(); + ml->typea = FL_IMAGE_LABEL; + ml->typeb = FL_NORMAL_LABEL; + ml->label(m); + } else { + i->o->image()->label(m); + } + } else { + m->label(i->o->label() ? i->o->label() : "(nolabel)"); + m->labeltype(i->o->labeltype()); + } + m->shortcut(((Fl_Button*)(i->o))->shortcut()); + m->callback(0,(void*)i); + m->flags = i->flags() | i->o->type(); + m->labelfont(i->o->labelfont()); + m->labelsize(i->o->labelsize()); + m->labelcolor(i->o->labelcolor()); + if (q->can_have_children()) {lvl++; m->flags |= FL_SUBMENU;} + m++; + int l1 = + (q->next && q->next->is_a(ID_Menu_Item)) ? q->next->level : level; + while (lvl > l1) {m->label(0); m++; lvl--;} + lvl = l1; + } + } + o->redraw(); +} + +Fl_Type* Fl_Menu_Base_Type::click_test(int, int) { + if (selected) return 0; // let user move the widget + Fl_Menu_* w = (Fl_Menu_*)o; + if (!menusize) return 0; + const Fl_Menu_Item* save = w->mvalue(); + w->value((Fl_Menu_Item*)0); + Fl::pushed(w); + w->handle(FL_PUSH); + Fl::focus(NULL); + const Fl_Menu_Item* m = w->mvalue(); + if (m) { + // restore the settings of toggles & radio items: + if (m->flags & (FL_MENU_RADIO | FL_MENU_TOGGLE)) build_menu(); + return (Fl_Type*)(m->user_data()); + } + w->value(save); + return this; +} + +void Fl_Menu_Manager_Type::write_code2(Fd_Code_Writer& f) { + if (next && next->is_a(ID_Menu_Item)) { + f.write_c("%s%s->menu(%s);\n", f.indent(), name() ? name() : "o", + f.unique_id(this, "menu", name(), label())); + } + Fl_Widget_Type::write_code2(f); +} + +void Fl_Menu_Base_Type::copy_properties() { + Fl_Widget_Type::copy_properties(); + Fl_Menu_ *s = (Fl_Menu_*)o, *d = (Fl_Menu_*)live_widget; + d->menu(s->menu()); + d->down_box(s->down_box()); + d->textcolor(s->textcolor()); + d->textfont(s->textfont()); + d->textsize(s->textsize()); +} + +//////////////////////////////////////////////////////////////// + +Fl_Menu_Item button_type_menu[] = { + {"normal",0,0,(void*)0}, + {"popup1",0,0,(void*)Fl_Menu_Button::POPUP1}, + {"popup2",0,0,(void*)Fl_Menu_Button::POPUP2}, + {"popup3",0,0,(void*)Fl_Menu_Button::POPUP3}, + {"popup12",0,0,(void*)Fl_Menu_Button::POPUP12}, + {"popup23",0,0,(void*)Fl_Menu_Button::POPUP23}, + {"popup13",0,0,(void*)Fl_Menu_Button::POPUP13}, + {"popup123",0,0,(void*)Fl_Menu_Button::POPUP123}, + {0}}; + +Fl_Menu_Button_Type Fl_Menu_Button_type; + +//////////////////////////////////////////////////////////////// + +Fl_Menu_Item dummymenu[] = {{"CHOICE"},{0}}; + +Fl_Choice_Type Fl_Choice_type; + +Fl_Input_Choice_Type Fl_Input_Choice_type; + +void Fl_Input_Choice_Type::copy_properties() { + Fl_Widget_Type::copy_properties(); + Fl_Input_Choice *s = (Fl_Input_Choice*)o, *d = (Fl_Input_Choice*)live_widget; + d->menu(s->menu()); + d->down_box(s->down_box()); + d->textcolor(s->textcolor()); + d->textfont(s->textfont()); + d->textsize(s->textsize()); +} + +Fl_Type* Fl_Input_Choice_Type::click_test(int, int) { + if (selected) return 0; // let user move the widget + Fl_Menu_* w = ((Fl_Input_Choice*)o)->menubutton(); + if (!menusize) return 0; + const Fl_Menu_Item* save = w->mvalue(); + w->value((Fl_Menu_Item*)0); + Fl::pushed(w); + w->handle(FL_PUSH); + Fl::focus(NULL); + const Fl_Menu_Item* m = w->mvalue(); + if (m) { + // restore the settings of toggles & radio items: + if (m->flags & (FL_MENU_RADIO | FL_MENU_TOGGLE)) build_menu(); + return (Fl_Type*)(m->user_data()); + } + w->value(save); + return this; +} + +//////////////////////////////////////////////////////////////// + +Fl_Menu_Bar_Type Fl_Menu_Bar_type; + +Fl_Menu_Item menu_bar_type_menu[] = { + {"Fl_Menu_Bar",0,0,(void*)0}, + {"Fl_Sys_Menu_Bar",0,0,(void*)1}, + {0}}; + +Fl_Menu_Bar_Type::Fl_Menu_Bar_Type() +: _proxy_name(NULL) +{ +} + +Fl_Menu_Bar_Type::~Fl_Menu_Bar_Type() { + if (_proxy_name) + ::free(_proxy_name); +} + +/** + \brief Return true if this is an Fl_Sys_Menu_Bar. + This test fails if subclass() is the name of a class that the user may have + derived from Fl_Sys_Menu_Bar. + */ +bool Fl_Menu_Bar_Type::is_sys_menu_bar() { + if (o->type()==1) return true; + return ( subclass() && (strcmp(subclass(), "Fl_Sys_Menu_Bar")==0) ); +} + +const char *Fl_Menu_Bar_Type::sys_menubar_name() { + if (subclass()) + return subclass(); + else + return "Fl_Sys_Menu_Bar"; +} + +const char *Fl_Menu_Bar_Type::sys_menubar_proxy_name() { + if (!_proxy_name) + _proxy_name = (char*)::malloc(128); + ::snprintf(_proxy_name, 63, "%s_Proxy", sys_menubar_name()); + return _proxy_name; +} + + +void Fl_Menu_Bar_Type::write_static(Fd_Code_Writer& f) { + super::write_static(f); + if (is_sys_menu_bar()) { + f.write_h_once("#include "); + if (is_in_class()) { + // Make room for a pointer to the enclosing class. + f.write_c_once( // must be less than 1024 bytes! + "\nclass %s: public %s {\n" + "public:\n" + " %s(int x, int y, int w, int h, const char *l=NULL)\n" + " : %s(x, y, w, h, l) { }\n" + " void *_parent_class;\n" + "};\n", + sys_menubar_proxy_name(), sys_menubar_name(), + sys_menubar_proxy_name(), sys_menubar_name() + ); + } + } +} + +void Fl_Menu_Bar_Type::write_code1(Fd_Code_Writer& f) { + super::write_code1(f); + if (is_sys_menu_bar() && is_in_class()) { + f.write_c("%s((%s*)%s)->_parent_class = (void*)this;\n", + f.indent(), sys_menubar_proxy_name(), name() ? name() : "o"); + } +} + +//void Fl_Menu_Bar_Type::write_code2(Fd_Code_Writer& f) { +// super::write_code2(f); +//} + +//////////////////////////////////////////////////////////////// +// Shortcut entry item in panel: +void shortcut_in_cb(Fl_Shortcut_Button* i, void* v) { + if (v == LOAD) { + if (current_widget->is_button()) + i->value( ((Fl_Button*)(current_widget->o))->shortcut() ); + else if (current_widget->is_a(ID_Input)) + i->value( ((Fl_Input_*)(current_widget->o))->shortcut() ); + else if (current_widget->is_a(ID_Value_Input)) + i->value( ((Fl_Value_Input*)(current_widget->o))->shortcut() ); + else if (current_widget->is_a(ID_Text_Display)) + i->value( ((Fl_Text_Display*)(current_widget->o))->shortcut() ); + else { + i->hide(); + i->parent()->hide(); + return; + } + //i->default_value( i->value() ); // enable the "undo" capability of the shortcut button + i->show(); + i->parent()->show(); + i->redraw(); + } else { + int mod = 0; + for (Fl_Type *o = Fl_Type::first; o; o = o->next) + if (o->selected && o->is_button()) { + Fl_Button* b = (Fl_Button*)(((Fl_Widget_Type*)o)->o); + if (b->shortcut() != (int)i->value()) mod = 1; + b->shortcut(i->value()); + if (o->is_a(ID_Menu_Item)) ((Fl_Widget_Type*)o)->redraw(); + } else if (o->selected && o->is_a(ID_Input)) { + Fl_Input_* b = (Fl_Input_*)(((Fl_Widget_Type*)o)->o); + if (b->shortcut() != (int)i->value()) mod = 1; + b->shortcut(i->value()); + } else if (o->selected && o->is_a(ID_Value_Input)) { + Fl_Value_Input* b = (Fl_Value_Input*)(((Fl_Widget_Type*)o)->o); + if (b->shortcut() != (int)i->value()) mod = 1; + b->shortcut(i->value()); + } else if (o->selected && o->is_a(ID_Text_Display)) { + Fl_Text_Display* b = (Fl_Text_Display*)(((Fl_Widget_Type*)o)->o); + if (b->shortcut() != (int)i->value()) mod = 1; + b->shortcut(i->value()); + } + if (mod) set_modflag(1); + } +} diff --git a/fluid/nodes/Fl_Menu_Type.h b/fluid/nodes/Fl_Menu_Type.h new file mode 100644 index 000000000..b4ad90081 --- /dev/null +++ b/fluid/nodes/Fl_Menu_Type.h @@ -0,0 +1,287 @@ +// +// Menu type header file for the Fast Light Tool Kit (FLTK). +// +// Type for creating all subclasses of Fl_Widget +// This should have the widget pointer in it, but it is still in the +// Fl_Type base class. +// +// Copyright 1998-2023 by Bill Spitzak and others. +// +// This library is free software. Distribution and use rights are outlined in +// the file "COPYING" which should have been included with this file. If this +// file is missing or damaged, see the license at: +// +// https://www.fltk.org/COPYING.php +// +// Please see the following page on how to report bugs and issues: +// +// https://www.fltk.org/bugs.php +// + +#ifndef _FLUID_FL_MENU_TYPE_H +#define _FLUID_FL_MENU_TYPE_H + +#include "nodes/Fl_Button_Type.h" + +#include "app/Fd_Snap_Action.h" + +#include +#include +#include +#include +#include +#include + +extern Fl_Menu_Item dummymenu[]; +extern Fl_Menu_Item button_type_menu[]; +extern Fl_Menu_Item menu_item_type_menu[]; +extern Fl_Menu_Item menu_bar_type_menu[]; + +/** + \brief Manage all types on menu items. + Deriving Fl_Menu_Item_Type from Fl_Button_Type is intentional. For the purpose + of editing, a Menu Item is implemented with `o` pointing to an Fl_Button for + holding all properties. + */ +class Fl_Menu_Item_Type : public Fl_Button_Type +{ + typedef Fl_Button_Type super; +public: + Fl_Menu_Item* subtypes() FL_OVERRIDE {return menu_item_type_menu;} + const char* type_name() FL_OVERRIDE {return "MenuItem";} + const char* alt_type_name() FL_OVERRIDE {return "fltk::Item";} + Fl_Type* make(Strategy strategy) FL_OVERRIDE; + Fl_Type* make(int flags, Strategy strategy); + int is_button() const FL_OVERRIDE {return 1;} // this gets shortcut to work + Fl_Widget* widget(int,int,int,int) FL_OVERRIDE {return 0;} + Fl_Widget_Type* _make() FL_OVERRIDE {return 0;} + virtual const char* menu_name(Fd_Code_Writer& f, int& i); + int flags(); + void write_static(Fd_Code_Writer& f) FL_OVERRIDE; + void write_item(Fd_Code_Writer& f); + void write_code1(Fd_Code_Writer& f) FL_OVERRIDE; + void write_code2(Fd_Code_Writer& f) FL_OVERRIDE; + int is_true_widget() const FL_OVERRIDE { return 0; } + ID id() const FL_OVERRIDE { return ID_Menu_Item; } + bool is_a(ID inID) const FL_OVERRIDE { return (inID==ID_Menu_Item) ? true : super::is_a(inID); } +}; + +/** + \brief Manage Radio style Menu Items. + */ +class Fl_Radio_Menu_Item_Type : public Fl_Menu_Item_Type +{ + typedef Fl_Menu_Item_Type super; +public: + const char* type_name() FL_OVERRIDE {return "RadioMenuItem";} + Fl_Type* make(Strategy strategy) FL_OVERRIDE; + ID id() const FL_OVERRIDE { return ID_Radio_Menu_Item; } + bool is_a(ID inID) const FL_OVERRIDE { return (inID==ID_Radio_Menu_Item) ? true : super::is_a(inID); } +}; + +/** + \brief Manage Checkbox style Menu Items. + */ +class Fl_Checkbox_Menu_Item_Type : public Fl_Menu_Item_Type +{ + typedef Fl_Menu_Item_Type super; +public: + const char* type_name() FL_OVERRIDE {return "CheckMenuItem";} + Fl_Type* make(Strategy strategy) FL_OVERRIDE; + ID id() const FL_OVERRIDE { return ID_Checkbox_Menu_Item; } + bool is_a(ID inID) const FL_OVERRIDE { return (inID==ID_Checkbox_Menu_Item) ? true : super::is_a(inID); } +}; + +/** + \brief Manage Submenu style Menu Items. + Submenu Items are simply buttons just like all other menu items, but they + can also hold a pointer to a list of submenus, or have a flag set that + allows submenus to follow in the current array. As buttons, they can + be clicked by the user, and they will call their callback, if one is set. + */ +class Fl_Submenu_Type : public Fl_Menu_Item_Type +{ + typedef Fl_Menu_Item_Type super; +public: + Fl_Menu_Item* subtypes() FL_OVERRIDE {return 0;} + const char* type_name() FL_OVERRIDE {return "Submenu";} + const char* alt_type_name() FL_OVERRIDE {return "fltk::ItemGroup";} + int can_have_children() const FL_OVERRIDE {return 1;} + int is_button() const FL_OVERRIDE {return 0;} // disable shortcut + Fl_Type* make(Strategy strategy) FL_OVERRIDE; + // changes to submenu must propagate up so build_menu is called + // on the parent Fl_Menu_Type: + void add_child(Fl_Type*a, Fl_Type*b) FL_OVERRIDE {parent->add_child(a,b);} + void move_child(Fl_Type*a, Fl_Type*b) FL_OVERRIDE {parent->move_child(a,b);} + void remove_child(Fl_Type*a) FL_OVERRIDE {parent->remove_child(a);} + ID id() const FL_OVERRIDE { return ID_Submenu; } + bool is_a(ID inID) const FL_OVERRIDE { return (inID==ID_Submenu) ? true : super::is_a(inID); } +}; + +// ----------------------------------------------------------------------------- + +/** + \brief Base class for all widgets that can have a pulldown menu attached. + Widgets with this type can be derived from Fl_Menu_ or from + Fl_Group (Fl_Input_Choice). + */ +class Fl_Menu_Manager_Type : public Fl_Widget_Type +{ + typedef Fl_Widget_Type super; +public: + void ideal_size(int &w, int &h) FL_OVERRIDE { + h = layout->textsize_not_null() + 8; + w = layout->textsize_not_null() * 6 + 8; + Fd_Snap_Action::better_size(w, h); + } + int can_have_children() const FL_OVERRIDE {return 1;} + int menusize; + virtual void build_menu() = 0; + Fl_Menu_Manager_Type() : Fl_Widget_Type() {menusize = 0;} + void add_child(Fl_Type*, Fl_Type*) FL_OVERRIDE { build_menu(); } + void move_child(Fl_Type*, Fl_Type*) FL_OVERRIDE { build_menu(); } + void remove_child(Fl_Type*) FL_OVERRIDE { build_menu();} + Fl_Type* click_test(int x, int y) FL_OVERRIDE = 0; + void write_code2(Fd_Code_Writer& f) FL_OVERRIDE; + void copy_properties() FL_OVERRIDE = 0; + ID id() const FL_OVERRIDE { return ID_Menu_Manager_; } + bool is_a(ID inID) const FL_OVERRIDE { return (inID==ID_Menu_Manager_) ? true : super::is_a(inID); } +}; + +/** + \brief Manage the composite widget Input Choice. + \note Input Choice is a composite window, so `o` will be pointing to a widget + derived from Fl_Group. All menu related methods from Fl_Menu_Trait_Type must + be virtual and must be reimplemented here (click_test, build_menu, textstuff). + */ +class Fl_Input_Choice_Type : public Fl_Menu_Manager_Type +{ + typedef Fl_Menu_Manager_Type super; + int textstuff(int w, Fl_Font& f, int& s, Fl_Color& c) FL_OVERRIDE { + Fl_Input_Choice *myo = (Fl_Input_Choice*)(w==4 ? ((Fl_Widget_Type*)this->factory)->o : this->o); + switch (w) { + case 4: + case 0: f = (Fl_Font)myo->textfont(); s = myo->textsize(); c = myo->textcolor(); break; + case 1: myo->textfont(f); break; + case 2: myo->textsize(s); break; + case 3: myo->textcolor(c); break; + } + return 1; + } +public: + ~Fl_Input_Choice_Type() { + if (menusize) delete[] (Fl_Menu_Item*)(((Fl_Input_Choice*)o)->menu()); + } + const char *type_name() FL_OVERRIDE {return "Fl_Input_Choice";} + const char *alt_type_name() FL_OVERRIDE {return "fltk::ComboBox";} + Fl_Type* click_test(int,int) FL_OVERRIDE; + Fl_Widget *widget(int X,int Y,int W,int H) FL_OVERRIDE { + Fl_Input_Choice *myo = new Fl_Input_Choice(X,Y,W,H,"input choice:"); + myo->menu(dummymenu); + myo->value("input"); + return myo; + } + Fl_Widget_Type *_make() FL_OVERRIDE {return new Fl_Input_Choice_Type();} + void build_menu() FL_OVERRIDE; + ID id() const FL_OVERRIDE { return ID_Input_Choice; } + bool is_a(ID inID) const FL_OVERRIDE { return (inID==ID_Input_Choice) ? true : super::is_a(inID); } + void copy_properties() FL_OVERRIDE; +}; + +/** + \brief Base class to handle widgets that are derived from Fl_Menu_. + */ +class Fl_Menu_Base_Type : public Fl_Menu_Manager_Type +{ + typedef Fl_Menu_Manager_Type super; + int textstuff(int w, Fl_Font& f, int& s, Fl_Color& c) FL_OVERRIDE { + Fl_Menu_ *myo = (Fl_Menu_*)(w==4 ? ((Fl_Widget_Type*)this->factory)->o : this->o); + switch (w) { + case 4: + case 0: f = myo->textfont(); s = myo->textsize(); c = myo->textcolor(); break; + case 1: myo->textfont(f); break; + case 2: myo->textsize(s); break; + case 3: myo->textcolor(c); break; + } + return 1; + } +public: + int can_have_children() const FL_OVERRIDE {return 1;} + void build_menu() FL_OVERRIDE; + ~Fl_Menu_Base_Type() { + if (menusize) delete[] (Fl_Menu_Item*)(((Fl_Menu_*)o)->menu()); + } + Fl_Type* click_test(int x, int y) FL_OVERRIDE; + void copy_properties() FL_OVERRIDE; + ID id() const FL_OVERRIDE { return ID_Menu_; } + bool is_a(ID inID) const FL_OVERRIDE { return (inID==ID_Menu_) ? true : super::is_a(inID); } +}; + +extern Fl_Menu_Item button_type_menu[]; + +/** + \brief Make Menu Button widgets. + */ +class Fl_Menu_Button_Type : public Fl_Menu_Base_Type +{ + typedef Fl_Menu_Base_Type super; + Fl_Menu_Item *subtypes() FL_OVERRIDE {return button_type_menu;} +public: + const char *type_name() FL_OVERRIDE {return "Fl_Menu_Button";} + const char *alt_type_name() FL_OVERRIDE {return "fltk::MenuButton";} + Fl_Widget *widget(int X,int Y,int W,int H) FL_OVERRIDE { + return new Fl_Menu_Button(X,Y,W,H,"menu");} + Fl_Widget_Type *_make() FL_OVERRIDE {return new Fl_Menu_Button_Type();} + ID id() const FL_OVERRIDE { return ID_Menu_Button; } + bool is_a(ID inID) const FL_OVERRIDE { return (inID==ID_Menu_Button) ? true : super::is_a(inID); } +}; + + +/** + \brief Manage Choice type menu widgets. + */ +class Fl_Choice_Type : public Fl_Menu_Base_Type +{ + typedef Fl_Menu_Base_Type super; +public: + const char *type_name() FL_OVERRIDE {return "Fl_Choice";} + const char *alt_type_name() FL_OVERRIDE {return "fltk::Choice";} + Fl_Widget *widget(int X,int Y,int W,int H) FL_OVERRIDE { + Fl_Choice *myo = new Fl_Choice(X,Y,W,H,"choice:"); + myo->menu(dummymenu); + return myo; + } + Fl_Widget_Type *_make() FL_OVERRIDE {return new Fl_Choice_Type();} + ID id() const FL_OVERRIDE { return ID_Choice; } + bool is_a(ID inID) const FL_OVERRIDE { return (inID==ID_Choice) ? true : super::is_a(inID); } +}; + + +/** + \brief Manage Menubar widgets. + */ +class Fl_Menu_Bar_Type : public Fl_Menu_Base_Type +{ + typedef Fl_Menu_Base_Type super; + Fl_Menu_Item *subtypes() FL_OVERRIDE {return menu_bar_type_menu;} +public: + Fl_Menu_Bar_Type(); + ~Fl_Menu_Bar_Type() FL_OVERRIDE; + const char *type_name() FL_OVERRIDE {return "Fl_Menu_Bar";} + const char *alt_type_name() FL_OVERRIDE {return "fltk::MenuBar";} + Fl_Widget *widget(int X,int Y,int W,int H) FL_OVERRIDE {return new Fl_Menu_Bar(X,Y,W,H);} + Fl_Widget_Type *_make() FL_OVERRIDE {return new Fl_Menu_Bar_Type();} + void write_static(Fd_Code_Writer& f) FL_OVERRIDE; + void write_code1(Fd_Code_Writer& f) FL_OVERRIDE; +// void write_code2(Fd_Code_Writer& f) FL_OVERRIDE; + ID id() const FL_OVERRIDE { return ID_Menu_Bar; } + bool is_a(ID inID) const FL_OVERRIDE { return (inID==ID_Menu_Bar) ? true : super::is_a(inID); } + bool is_sys_menu_bar(); + const char *sys_menubar_name(); + const char *sys_menubar_proxy_name(); +protected: + char *_proxy_name; +}; + + +#endif // _FLUID_FL_MENU_TYPE_H diff --git a/fluid/nodes/Fl_Type.cxx b/fluid/nodes/Fl_Type.cxx new file mode 100644 index 000000000..56a15175e --- /dev/null +++ b/fluid/nodes/Fl_Type.cxx @@ -0,0 +1,1336 @@ +// +// Widget type code for the Fast Light Tool Kit (FLTK). +// +// Copyright 1998-2023 by Bill Spitzak and others. +// +// This library is free software. Distribution and use rights are outlined in +// the file "COPYING" which should have been included with this file. If this +// file is missing or damaged, see the license at: +// +// https://www.fltk.org/COPYING.php +// +// Please see the following page on how to report bugs and issues: +// +// https://www.fltk.org/bugs.php +// + +/// \defgroup fl_type Basic Node for all Widgets and Functions +/// \{ + +/** \class Fl_Type + Each object described by Fluid is one of these objects. They + are all stored in a double-linked list. + + The "type" of the object is covered by the virtual functions. + There will probably be a lot of these virtual functions. + + The type browser is also a list of these objects, but they + are "factory" instances, not "real" ones. These objects exist + only so the "make" method can be called on them. They are + not in the linked list and are not written to files or + copied or otherwise examined. + + The Fl_Type inheritance is currently: + --+-- Fl_Type + +-- Fl_Function_Type + +-- Fl_Code_Type + +-- Fl_CodeBlock_Type + +-+ Fl_Decl_Type + | +-- Fl_Data + +-- Fl_DeclBlock_Type + +-- Fl_Comment_Type + +-- Fl_Class_Type + +-+ Fl_Widget_Type, 'o' points to a class derived from Fl_Widget + +-+ Fl_Browser_Base_Type, 'o' is Fl_Browser + | +-+ Fl_Browser + | | +-- Fl_File_Browser + | +-- Fl_Check_Browser + +-- Fl_Tree_Type + +-- Fl_Help_View_Type + +-+ Fl_Valuator_Type, 'o' is Fl_Valuator_ + | +-- Fl_Counter_Type + | +-- Fl_Adjuster_Type + | +-- Fl_Dial_Type + | +-- Fl_Roller_Type + | +-- Fl_Slider_Type + | +-- Fl_Value_Input_Type + | +-- Fl_Value_Output_Type + +-+ Fl_Input_Type + | +-- Fl_Output_Type + +-+ Fl_Text_Display_Type + | +-- Fl_Text_Editor_Type + +-- Fl_Terminal_Type + +-- Fl_Box_Type + +-- Fl_Clock_Type + +-- Fl_Progress_Type + +-- Fl_Spinner_Type + +-+ Fl_Group_Type + | +-- Fl_Pack_Type + | +-- Fl_Flex_Type + | +-- Fl_Grid_Type + | +-- Fl_Table_Type + | +-- Fl_Tabs_Type + | +-- Fl_Scroll_Type + | +-- Fl_Tile_Type + | +-- Fl_Wizard_Type + | +-+ Fl_Window_Type + | +-- Fl_Widget_Class_Type + +-+ Fl_Menu_Manager_Type, 'o' is based on Fl_Widget + | +-+ Fl_Menu_Base_Type, 'o' is based on Fl_Menu_ + | | +-- Fl_Menu_Button_Type + | | +-- Fl_Choice_Type + | | +-- Fl_Menu_Bar_Type + | +-- Fl_Input_Choice_Type, 'o' is based on Fl_Input_Choice which is Fl_Group + +-+ Fl_Button_Type + +-- Fl_Return_Button_Type + +-- Fl_Repeat_Button_Type + +-- Fl_Light_Button_Type + +-- Fl_Check_Button_Type + +-- Fl_Round_Button_Type + +-+ Fl_Menu_Item_Type, 'o' is derived from Fl_Button in FLUID + +-- Fl_Radio_Menu_Item_Type + +-- Fl_Checkbox_Menu_Item_Type + +-- Fl_Submenu_Item_Type + +*/ + +#include "nodes/Fl_Type.h" + +#include "app/fluid.h" +#include "app/Fd_Snap_Action.h" +#include "app/shell_command.h" +#include "app/undo.h" +#include "io/file.h" +#include "io/code.h" +#include "nodes/Fl_Function_Type.h" +#include "nodes/Fl_Widget_Type.h" +#include "nodes/Fl_Window_Type.h" +#include "nodes/Fl_Group_Type.h" +#include "rsrcs/pixmaps.h" +#include "widgets/widget_browser.h" + +#include +#include +#include +#include "../src/flstring.h" + +#include +#include + +// ---- global variables + +Fl_Type *Fl_Type::first = NULL; +Fl_Type *Fl_Type::last = NULL; +Fl_Type *Fl_Type::current = NULL; +Fl_Type *Fl_Type::current_dnd = NULL; +int Fl_Type::allow_layout = 0; + +Fl_Type *in_this_only; // set if menu popped-up in window + + +// ---- various functions + +#if 0 +#ifndef NDEBUG +/** + Print the current project tree to stderr. + */ +void print_project_tree() { + fprintf(stderr, "---- %s --->\n", g_project.projectfile_name().c_str()); + for (Fl_Type *t = Fl_Type::first; t; t = t->next) { + for (int i = t->level; i > 0; i--) + fprintf(stderr, ". "); + fprintf(stderr, "%s\n", subclassname(t)); + } +} +#endif + +#ifndef NDEBUG +/** + Check the validity of the project tree. + + Write problems with the project tree to stderr. + + \return true if the project tree is valid + */ +bool validate_project_tree() { + // Validate `first` and `last` + if (Fl_Type::first == NULL) { + if (Fl_Type::last == NULL) { + return true; + } else { + fprintf(stderr, "ERROR: `first` is NULL, but `last` is not!\n"); + return false; + } + } + if (Fl_Type::last == NULL) { + fprintf(stderr, "ERROR: `last` is NULL, but `first` is not!\n"); + return false; + } + // Validate the branch linkage, parent links, etc. + return validate_branch(Fl_Type::first); +} +#endif + +#ifndef NDEBUG +/** + Check the validity of a Type branch that is not connected to the project. + + Write problems with the branch to stderr. + + \param[in] root the first node in a branch + \return true if the branch is correctly separated and valid + */ +bool validate_independent_branch(class Fl_Type *root) { + // Make sure that `first` and `last` do not point at any node in this branch + if (Fl_Type::first) { + for (Fl_Type *t = root; t; t = t->next) { + if (Fl_Type::first == t) { + fprintf(stderr, "ERROR: Branch is not independent, `first` is pointing to branch member!\n"); + return false; + } + } + } + if (Fl_Type::last) { + for (Fl_Type *t = root; t; t = t->next) { + if (Fl_Type::last == t) { + fprintf(stderr, "ERROR: Branch is not independent, `last` is pointing to branch member!\n"); + return false; + } + } + } + // Validate the branch linkage, parent links, etc. + return validate_branch(root); +} +#endif + +#ifndef NDEBUG +/** + Check the validity of a Type branch. + + Write problems with the branch to stderr. + + \param[in] root the first node in a branch + \return true if the branch is valid + */ +bool validate_branch(class Fl_Type *root) { + // Only check real branches + if (!root) { + fprintf(stderr, "WARNING: Branch is empty!\n"); + return false; + } + // Check relation between this and next node + for (Fl_Type *t = root; t; t = t->next) { + if (t->level < root->level) { + fprintf(stderr, "ERROR: Node in tree is above root level!\n"); + return false; + } + if (t->next) { + // Make sure that all `next` types have the `prev` member link back + if (t->next->prev != t) { + fprintf(stderr, "ERROR: Doubly linked list broken!\n"); + return false; + } + if (t->next->level > t->level) { + // Validate `level` changes + if (t->next->level - t->level > 1) { + fprintf(stderr, "ERROR: Child level increment greater than one!\n"); + return false; + } + // Ensure that this node can actually have children + if (!t->can_have_children()) { + fprintf(stderr, "ERROR: This parent must not have children!\n"); + return false; + } + } + } + // Validate the `parent` entry + for (Fl_Type *p = t->prev; ; p = p->prev) { + if (p == NULL) { + if (t->parent != NULL) { + fprintf(stderr, "ERROR: `parent` pointer should be NULL!\n"); + return false; + } + break; + } + if (p->level < t->level) { + if (t->parent != p) { + fprintf(stderr, "ERROR: `parent` points to wrong parent!\n"); + return false; + } + break; + } + } + } + return true; +} +#endif +#endif + +void select_all_cb(Fl_Widget *,void *) { + Fl_Type *p = Fl_Type::current ? Fl_Type::current->parent : 0; + if (in_this_only) { + Fl_Type *t = p; + for (; t && t != in_this_only; t = t->parent) {/*empty*/} + if (t != in_this_only) p = in_this_only; + } + for (;;) { + if (p) { + int foundany = 0; + for (Fl_Type *t = p->next; t && t->level>p->level; t = t->next) { + if (!t->new_selected) {widget_browser->select(t,1,0); foundany = 1;} + } + if (foundany) break; + p = p->parent; + } else { + for (Fl_Type *t = Fl_Type::first; t; t = t->next) + widget_browser->select(t,1,0); + break; + } + } + selection_changed(p); +} + +void select_none_cb(Fl_Widget *,void *) { + Fl_Type *p = Fl_Type::current ? Fl_Type::current->parent : 0; + if (in_this_only) { + Fl_Type *t = p; + for (; t && t != in_this_only; t = t->parent) {/*empty*/} + if (t != in_this_only) p = in_this_only; + } + for (;;) { + if (p) { + int foundany = 0; + for (Fl_Type *t = p->next; t && t->level>p->level; t = t->next) { + if (t->new_selected) {widget_browser->select(t,0,0); foundany = 1;} + } + if (foundany) break; + p = p->parent; + } else { + for (Fl_Type *t = Fl_Type::first; t; t = t->next) + widget_browser->select(t,0,0); + break; + } + } + selection_changed(p); +} + +/** + Callback to move all selected items before their previous unselected sibling. + */ +void earlier_cb(Fl_Widget*,void*) { + Fl_Type *f; + int mod = 0; + for (f = Fl_Type::first; f; ) { + Fl_Type* nxt = f->next; + if (f->selected) { + Fl_Type* g; + for (g = f->prev; g && g->level > f->level; g = g->prev) {/*empty*/} + if (g && g->level == f->level && !g->selected) { + if (!mod) undo_checkpoint(); + f->move_before(g); + if (f->parent) f->parent->layout_widget(); + mod = 1; + } + } + f = nxt; + } + if (mod) set_modflag(1); + widget_browser->display(Fl_Type::current); + widget_browser->rebuild(); +} + +/** + Callback to move all selected items after their next unselected sibling. + */ +void later_cb(Fl_Widget*,void*) { + Fl_Type *f; + int mod = 0; + for (f = Fl_Type::last; f; ) { + Fl_Type* prv = f->prev; + if (f->selected) { + Fl_Type* g; + for (g = f->next; g && g->level > f->level; g = g->next) {/*empty*/} + if (g && g->level == f->level && !g->selected) { + if (!mod) undo_checkpoint(); + g->move_before(f); + if (f->parent) f->parent->layout_widget(); + mod = 1; + } + } + f = prv; + } + if (mod) set_modflag(1); + widget_browser->display(Fl_Type::current); + widget_browser->rebuild(); +} + +/** \brief Delete all children of a Type. + */ +static void delete_children(Fl_Type *p) { + Fl_Type *f; + // find all types following p that are higher in level, effectively finding + // the last child of the last child + for (f = p; f && f->next && f->next->level > p->level; f = f->next) {/*empty*/} + // now loop back up to p, deleting all children on the way + for (; f != p; ) { + Fl_Type *g = f->prev; + delete f; + f = g; + } +} + +/** Delete all nodes in the Types tree and reset project settings, or delete selected nodes. + Also calls the browser to refresh. + \note Please refactor this into two separate methods of Fluid_Project. + \param[in] selected_only if set, delete only the selected widgets and + don't reset the project. + */ +void delete_all(int selected_only) { + if (widget_browser) { + if (selected_only) + widget_browser->save_scroll_position(); + widget_browser->new_list(); + } + for (Fl_Type *f = Fl_Type::first; f;) { + if (f->selected || !selected_only) { + delete_children(f); + Fl_Type *g = f->next; + delete f; + f = g; + } else { + f = f->next; + } + } + if(!selected_only) { + // reset the setting for the external shell command + if (g_shell_config) { + g_shell_config->clear(FD_STORE_PROJECT); + g_shell_config->rebuild_shell_menu(); + g_shell_config->update_settings_dialog(); + } + if (widget_browser) { + widget_browser->hposition(0); + widget_browser->vposition(0); + } + g_layout_list.remove_all(FD_STORE_PROJECT); + g_layout_list.current_suite(0); + g_layout_list.current_preset(0); + g_layout_list.update_dialogs(); + } + selection_changed(0); + if (widget_browser) { + if (selected_only) + widget_browser->restore_scroll_position(); + widget_browser->rebuild(); + } +} + +/** Update a string. + Replace a string pointer with new value, strips leading/trailing blanks. + As a side effect, this call also sets the mod flags. + \param[in] n new string, can be NULL + \param[out] p update this pointer, possibly reallocate memory + \param[in] nostrip if set, do not strip leading and trailing spaces and tabs + \return 1 if the string in p changed + */ +int storestring(const char *n, const char * & p, int nostrip) { + if (n == p) return 0; + undo_checkpoint(); + int length = 0; + if (n) { // see if blank, strip leading & trailing blanks + if (!nostrip) while (isspace((int)(unsigned char)*n)) n++; + const char *e = n + strlen(n); + if (!nostrip) while (e > n && isspace((int)(unsigned char)*(e-1))) e--; + length = int(e-n); + if (!length) n = 0; + } + if (n == p) return 0; + if (n && p && !strncmp(n,p,length) && !p[length]) return 0; + if (p) free((void *)p); + if (!n || !*n) { + p = 0; + } else { + char *q = (char *)malloc(length+1); + strlcpy(q,n,length+1); + p = q; + } + set_modflag(1); + return 1; +} + +/** Update the `visible` flag for `p` and all its descendants. + \param[in] p start here and update all descendants + */ +void update_visibility_flag(Fl_Type *p) { + Fl_Type *t = p; + for (;;) { + if (t->parent) t->visible = t->parent->visible && !t->parent->folded_; + else t->visible = 1; + t = t->next; + if (!t || t->level <= p->level) break; + } +} + +// ---- implementation of Fl_Type + +/** \var Fl_Type *Fl_Type::parent + Link to the parent node in the tree structure. + Used for simulating a tree structure via a doubly linked list. + */ +/** \var Fl_Type *Fl_Type::level + Zero based depth of the node within the tree structure. + Level is used to emulate a tree structure: the first node with a lower + level in the prev list would be the parent of this node. If the next member + has a higher level value, it is this nodes first child. At the same level, + it would be the first sibling. + */ +/** \var Fl_Type *Fl_Type::next + Points to the next node in the doubly linked list. + If this is NULL, we are at the end of the list. + Used for simulating a tree structure via a doubly linked list. + */ +/** \var Fl_Type *Fl_Type::prev + Link to the next node in the tree structure. + If this is NULL, we are at the beginning of the list. + Used for simulating a tree structure via a doubly linked list. + */ + +/** + Constructor and base for any node in the widget tree. + */ +Fl_Type::Fl_Type() : + name_(NULL), + label_(NULL), + callback_(NULL), + user_data_(NULL), + user_data_type_(NULL), + comment_(NULL), + uid_(0), + parent(NULL), + new_selected(0), + selected(0), + folded_(0), + visible(0), + level(0), + next(NULL), prev(NULL), + factory(NULL), + code_static_start(-1), code_static_end(-1), + code1_start(-1), code1_end(-1), + code2_start(-1), code2_end(-1), + header1_start(-1), header1_end(-1), + header2_start(-1), header2_end(-1), + header_static_start(-1), header_static_end(-1), + proj1_start(-1), proj1_end(-1), + proj2_start(-1), proj2_end(-1) +{ +} + + +/** + Destructor for any node in the tree. + + The destructor removes itself from the doubly linked list. This is dangerous, + because the node does not know if it is part of the widget tree, or if it is + in a separate tree. We try to take care of that as well as possible. + */ +Fl_Type::~Fl_Type() { + // warning: destructor only works for widgets that have been add()ed. + if (prev) prev->next = next; // else first = next; // don't do that! The Type may not be part of the main list + if (next) next->prev = prev; // else last = prev; + if (Fl_Type::last == this) Fl_Type::last = prev; + if (Fl_Type::first == this) Fl_Type::first = next; + if (current == this) current = NULL; + if (parent) parent->remove_child(this); + if (name_) free((void*)name_); + if (label_) free((void*)label_); + if (callback_) free((void*)callback_); + if (user_data_) free((void*)user_data_); + if (user_data_type_) free((void*)user_data_type_); + if (comment_) free((void*)comment_); +} + +// Return the previous sibling in the tree structure or NULL. +Fl_Type *Fl_Type::prev_sibling() { + Fl_Type *n; + for (n = prev; n && n->level > level; n = n->prev) ; + if (n && (n->level == level)) + return n; + return 0; +} + +// Return the next sibling in the tree structure or NULL. +Fl_Type *Fl_Type::next_sibling() { + Fl_Type *n; + for (n = next; n && n->level > level; n = n->next) ; + if (n && (n->level == level)) + return n; + return 0; +} + +// Return the first child or NULL +Fl_Type *Fl_Type::first_child() { + Fl_Type *n = next; + if (n->level > level) + return n; + return NULL; +} + +// Generate a descriptive text for this item, to put in browser & window titles +const char* Fl_Type::title() { + const char* c = name(); + if (c) + return c; + return type_name(); +} + +/** + Return the window that contains this widget. + \return NULL if this is not a widget. + */ +Fl_Window_Type *Fl_Type::window() { + if (!is_widget()) + return NULL; + for (Fl_Type *t = this; t; t=t->parent) + if (t->is_a(ID_Window)) + return (Fl_Window_Type*)t; + return NULL; +} + +/** + Return the group that contains this widget. + \return NULL if this is not a widget. + */ +Fl_Group_Type *Fl_Type::group() { + if (!is_widget()) + return NULL; + for (Fl_Type *t = this; t; t=t->parent) + if (t->is_a(ID_Group)) + return (Fl_Group_Type*)t; + return NULL; +} + +/** + Add this list/tree of widgets as a new last child of p. + + \c this must not be part of the widget browser. \c p however must be in the + widget_browser, so \c Fl_Type::first and \c Fl_Type::last are valid for \c p. + + This methods updates the widget_browser. + + \param[in] p insert \c this tree as a child of \c p + \param[in] strategy is Strategy::AS_LAST_CHILD or Strategy::AFTER_CURRENT + */ +void Fl_Type::add(Fl_Type *anchor, Strategy strategy) { +#if 0 +#ifndef NDEBUG + // print_project_tree(); + // fprintf(stderr, "Validating project\n"); + validate_project_tree(); + // fprintf(stderr, "Validating branch\n"); + validate_independent_branch(this); +#endif +#endif + + Fl_Type *target = NULL; // insert self before target node, if NULL, insert last + Fl_Type *target_parent = NULL; // this will be the new parent for branch + int target_level = 0; // adjust self to this new level + + // Find the node after our insertion position + switch (strategy.placement()) { + case Strategy::AS_FIRST_CHILD: + default: + if (anchor == NULL) { + target = Fl_Type::first; + } else { + target = anchor->next; + target_level = anchor->level + 1; + target_parent = anchor; + } + break; + case Strategy::AS_LAST_CHILD: + if (anchor == NULL) { + /* empty */ + } else { + for (target = anchor->next; target && target->level > anchor->level; target = target->next) {/*empty*/} + target_level = anchor->level + 1; + target_parent = anchor; + } + break; + case Strategy::AFTER_CURRENT: + if (anchor == NULL) { + target = Fl_Type::first; + } else { + for (target = anchor->next; target && target->level > anchor->level; target = target->next) {/*empty*/} + target_level = anchor->level; + target_parent = anchor->parent; + } + break; + } + + + // Find the last node of our tree + Fl_Type *end = this; + while (end->next) end = end->next; + + // Everything is prepared, now insert ourself in front of the target node + undo_checkpoint(); + + // Walk the tree to update parent pointers and levels + int source_level = level; + for (Fl_Type *t = this; t; t = t->next) { + t->level += (target_level-source_level); + if (t->level == target_level) + t->parent = target_parent; + } + + // Now link ourselves and our children before 'target', or last, if 'target' is NULL + if (target) { + prev = target->prev; + target->prev = end; + end->next = target; + } else { + prev = Fl_Type::last; + end->next = NULL; + Fl_Type::last = end; + } + if (prev) { + prev->next = this; + } else { + Fl_Type::first = this; + } + +#if 0 + { // make sure that we have no duplicate uid's + Fl_Type *tp = this; + do { + tp->set_uid(tp->uid_); + tp = tp->next; + } while (tp!=end && tp!=NULL); + } +#endif + + // Give the widgets in our tree a chance to update themselves + for (Fl_Type *t = this; t && t!=end->next; t = t->next) { + if (target_parent && (t->level == target_level)) + target_parent->add_child(t, 0); + update_visibility_flag(t); + } + + set_modflag(1); + widget_browser->redraw(); + +#if 0 +#ifndef NDEBUG + // fprintf(stderr, "Validating project after adding branch\n"); + validate_project_tree(); +#endif +#endif +} + +/** + Add `this` list/tree of widgets as a new sibling before `g`. + + `This` is not part of the widget browser. `g` must be in the + widget_browser, so `Fl_Type::first` and `Fl_Type::last` are valid for `g . + + This methods updates the widget_browser. + + \param[in] g pointer to a node within the tree + */ +void Fl_Type::insert(Fl_Type *g) { + // 'this' is not in the Widget_Browser, so we must run the linked list to find the last entry + Fl_Type *end = this; + while (end->next) end = end->next; + // 'this' will get the same parent as 'g' + parent = g->parent; + // run the list again to set the future node levels + int newlevel = g->level; + visible = g->visible; + for (Fl_Type *t = this->next; t; t = t->next) t->level += newlevel-level; + level = newlevel; + // insert this in the list before g + prev = g->prev; + if (prev) prev->next = this; else first = this; + end->next = g; + g->prev = end; + update_visibility_flag(this); + { // make sure that we have no duplicate uid's + Fl_Type *tp = this; + do { + tp->set_uid(tp->uid_); + tp = tp->next; + } while (tp!=end && tp!=NULL); + } + // tell parent that it has a new child, so it can update itself + if (parent) parent->add_child(this, g); + widget_browser->redraw(); +} + +// Return message number for I18N... +int Fl_Type::msgnum() { + int count; + Fl_Type *p; + + for (count = 0, p = this; p;) { + if (p->label()) count ++; + if (p != this && p->is_widget() && ((Fl_Widget_Type *)p)->tooltip()) count ++; + + if (p->prev) p = p->prev; + else p = p->parent; + } + + return count; +} + +/** + Remove this node and all its children from the parent node. + + This does not delete anything. The resulting list//tree will no longer be in + the widget_browser, so \c Fl_Type::first and \c Fl_Type::last do not apply + to it. + + \return the node that follows this node after the operation; can be NULL + */ +Fl_Type *Fl_Type::remove() { + // find the last child of this node + Fl_Type *end = this; + for (;;) { + if (!end->next || end->next->level <= level) + break; + end = end->next; + } + // unlink this node from the previous one + if (prev) + prev->next = end->next; + else + first = end->next; + // unlink the last child from their next node + if (end->next) + end->next->prev = prev; + else + last = prev; + Fl_Type *r = end->next; + prev = end->next = 0; + // allow the parent to update changes in the UI + if (parent) parent->remove_child(this); + parent = 0; + // tell the widget_browser that we removed some nodes + widget_browser->redraw(); + selection_changed(0); + return r; +} + +void Fl_Type::name(const char *n) { + int nostrip = is_a(ID_Comment); + if (storestring(n,name_,nostrip)) { + if (visible) widget_browser->redraw(); + } +} + +void Fl_Type::label(const char *n) { + if (storestring(n,label_,1)) { + setlabel(label_); + if (visible && !name_) widget_browser->redraw(); + } +} + +void Fl_Type::callback(const char *n) { + storestring(n,callback_); +} + +void Fl_Type::user_data(const char *n) { + storestring(n,user_data_); +} + +void Fl_Type::user_data_type(const char *n) { + storestring(n,user_data_type_); +} + +void Fl_Type::comment(const char *n) { + if (storestring(n,comment_,1)) { + if (visible) widget_browser->redraw(); + } +} + +void Fl_Type::open() { + printf("Open of '%s' is not yet implemented\n",type_name()); +} + +// returns pointer to whatever is after f & children + +/** + Move this node (and its children) into list before g. + Both `this` and `g` must be in the widget browser. + The caller must make sure that the widget browser is rebuilt correctly. + \param[in] g move \c this tree before \c g + */ +void Fl_Type::move_before(Fl_Type* g) { + if (level != g->level) printf("move_before levels don't match! %d %d\n", + level, g->level); + // Find the last child in the list + Fl_Type *n; + for (n = next; n && n->level > level; n = n->next) ; + if (n == g) return; + // now link this tree before g + Fl_Type *l = n ? n->prev : Fl_Type::last; + prev->next = n; + if (n) n->prev = prev; else Fl_Type::last = prev; + prev = g->prev; + l->next = g; + if (prev) prev->next = this; else Fl_Type::first = this; + g->prev = l; + // tell parent that it has a new child, so it can update itself + if (parent && is_widget()) parent->move_child(this,g); +} + + +// write a widget and all its children: +void Fl_Type::write(Fd_Project_Writer &f) { + if (f.write_codeview()) proj1_start = (int)ftell(f.file()) + 1; + if (f.write_codeview()) proj2_start = (int)ftell(f.file()) + 1; + f.write_indent(level); + f.write_word(type_name()); + + if (is_class()) { + const char * p = ((Fl_Class_Type*)this)->prefix(); + if (p && strlen(p)) + f.write_word(p); + } + + f.write_word(name()); + f.write_open(); + write_properties(f); + if (parent) parent->write_parent_properties(f, this, true); + f.write_close(level); + if (f.write_codeview()) proj1_end = (int)ftell(f.file()); + if (!can_have_children()) { + if (f.write_codeview()) proj2_end = (int)ftell(f.file()); + return; + } + // now do children: + f.write_open(); + Fl_Type *child; + for (child = next; child && child->level > level; child = child->next) + if (child->level == level+1) child->write(f); + if (f.write_codeview()) proj2_start = (int)ftell(f.file()) + 1; + f.write_close(level); + if (f.write_codeview()) proj2_end = (int)ftell(f.file()); +} + +void Fl_Type::write_properties(Fd_Project_Writer &f) { + // repeat this for each attribute: + if (g_project.write_mergeback_data && uid_) { + f.write_word("uid"); + f.write_string("%04x", uid_); + } + if (label()) { + f.write_indent(level+1); + f.write_word("label"); + f.write_word(label()); + } + if (user_data()) { + f.write_indent(level+1); + f.write_word("user_data"); + f.write_word(user_data()); + } + if (user_data_type()) { + f.write_word("user_data_type"); + f.write_word(user_data_type()); + } + if (callback()) { + f.write_indent(level+1); + f.write_word("callback"); + f.write_word(callback()); + } + if (comment()) { + f.write_indent(level+1); + f.write_word("comment"); + f.write_word(comment()); + } + if (can_have_children() && !folded_) f.write_word("open"); + if (selected) f.write_word("selected"); +} + +void Fl_Type::read_property(Fd_Project_Reader &f, const char *c) { + if (!strcmp(c,"uid")) { + const char *hex = f.read_word(); + int x = 0; + if (hex) + x = sscanf(hex, "%04x", &x); + set_uid(x); + } else if (!strcmp(c,"label")) + label(f.read_word()); + else if (!strcmp(c,"user_data")) + user_data(f.read_word()); + else if (!strcmp(c,"user_data_type")) + user_data_type(f.read_word()); + else if (!strcmp(c,"callback")) + callback(f.read_word()); + else if (!strcmp(c,"comment")) + comment(f.read_word()); + else if (!strcmp(c,"open")) + folded_ = 0; + else if (!strcmp(c,"selected")) + select(this,1); + else if (!strcmp(c,"parent_properties")) + if (parent) { + const char *cc = f.read_word(1); + if (strcmp(cc, "{")==0) { + for (;;) { + cc = f.read_word(); + if (!cc || cc[0]==0 || strcmp(cc, "}")==0) break; + parent->read_parent_property(f, this, cc); + } + } else { + f.read_error("'parent_properties' must be followed by '{'"); + } + } else { + f.read_error("Types using 'parent_properties' must have a parent"); + f.read_word(); // skip the entire block (this should generate a warning) + } + else + f.read_error("Unknown property \"%s\"", c); +} + +/** Write parent properties into the child property list. + + Some widgets store information for every child they manage. For example, + Fl_Grid stores the row and column position of every child. This method stores + this information with the child, but it is read and written by the parent. + + Parent properties solve several issues. A child will keep parent properties + if copied from on grid into another. The parent does not have to keep lists + of properties that may diverge from the actual order or number of children. + And lastly, properties are read when they are actually needed and don't have + to be stored in some temporary array. + + Parent properties are written as their own block at the end of the child's + property list. The block starts with the `parent_properties` keyword, followed + by a list of property/value pairs. The order of properties is significant, + however individual properties can be left out. + + To avoid writing the `parent_properties` block unnecessarily, this method + should only generate it if `encapsulate` is set *and* the contained + properties are not at their default. + + Lastly, this method should call the super class to give it a chance to append + its own properties. + + \see Fl_Grid_Type::write_parent_properties(Fd_Project_Writer &f, Fl_Type *child, bool encapsulate) + + \param[in] f the project file writer + \param[in] child write properties for this child, make sure it has the correct type + \param[in] encapsulate write the `parent_properties {}` block if true before writing any properties + */ +void Fl_Type::write_parent_properties(Fd_Project_Writer &f, Fl_Type *child, bool encapsulate) { + (void)f; (void)child; (void)encapsulate; + // nothing to do here + // put the following code into your implementation of write_parent_properties + // if there are actual non-default properties to write + // if (encapsulate) { + // f.write_indent(level+2); + // f.write_string("parent_properties {"); + // } + // now write your properties as name/value pairs + // f.write_indent(level+3); + // f.write_string("location {%d %d}", cell->row(), cell->col()); + // give the super class a chance to write its properties as well + // super::write_parent_properties(f, child, false); + // close the encapsulation + // if (encapsulate) { + // f.write_indent(level+2); + // f.write_string("}"); + // } +} + +/** Read one parent per-child property. + + A parent widget can store properties for every child that it manages. This + method reads back those properties. This function is virtual, so if a Type + does not support a property, it will propagate to its super class. + + \see Fl_Type::write_parent_properties(Fd_Project_Writer &f, Fl_Type *child, bool encapsulate) + \see Fl_Grid_Type::read_parent_property(Fd_Project_Reader &f, Fl_Type *child, const char *property) + + \param[in] f the project file writer + \param[in] child read properties for this child + \param[in] property the name of a property, or "}" when we reach the end of the list + */ +void Fl_Type::read_parent_property(Fd_Project_Reader &f, Fl_Type *child, const char *property) { + (void)child; + f.read_error("Unknown parent property \"%s\"", property); +} + + +int Fl_Type::read_fdesign(const char*, const char*) {return 0;} + +/** + Write a comment into the header file. + \param[in] pre indent the comment by this string +*/ +void Fl_Type::write_comment_h(Fd_Code_Writer& f, const char *pre) +{ + if (comment() && *comment()) { + f.write_h("%s/**\n", pre); + const char *s = comment(); + f.write_h("%s ", pre); + while(*s) { + if (*s=='\n') { + if (s[1]) { + f.write_h("\n%s ", pre); + } + } else { + f.write_h("%c", *s); // FIXME this is much too slow! + } + s++; + } + f.write_h("\n%s*/\n", pre); + } +} + +/** + Write a comment into the source file. +*/ +void Fl_Type::write_comment_c(Fd_Code_Writer& f, const char *pre) +{ + if (comment() && *comment()) { + f.write_c("%s/**\n", pre); + const char *s = comment(); + if (*s && *s!='\n') + f.write_c("%s ", pre); + while(*s) { + if (*s=='\n') { + f.write_c("\n"); + if (s[1] && s[1]!='\n') { + f.write_c("%s ", pre); + } + } else { + f.write_c("%c", *s); // FIXME this is much too slow! + } + s++; + } + f.write_c("\n%s*/\n", pre); + } +} + +/** + Write a comment into the source file. +*/ +void Fl_Type::write_comment_inline_c(Fd_Code_Writer& f, const char *pre) +{ + if (comment() && *comment()) { + const char *s = comment(); + if (strchr(s, '\n')==0L) { + // single line comment + if (pre) f.write_c("%s", pre); + f.write_c("// %s\n", s); + if (!pre) f.write_c("%s", f.indent_plus(1)); + } else { + f.write_c("%s/*\n", pre?pre:""); + if (*s && *s!='\n') { + if (pre) + f.write_c("%s ", pre); + else + f.write_c("%s ", f.indent_plus(1)); + } + while(*s) { + if (*s=='\n') { + f.write_c("\n"); + if (s[1] && s[1]!='\n') { + if (pre) + f.write_c("%s ", pre); + else + f.write_c("%s ", f.indent_plus(1)); + } + } else { + f.write_c("%c", *s); // FIXME this is much too slow! + } + s++; + } + if (pre) + f.write_c("\n%s */\n", pre); + else + f.write_c("\n%s */\n", f.indent_plus(1)); + if (!pre) + f.write_c("%s", f.indent_plus(1)); + } + } +} + +/** + Build widgets and dataset needed in live mode. + \return a widget pointer that the live mode initiator can 'show()' + \see leave_live_mode() +*/ +Fl_Widget *Fl_Type::enter_live_mode(int) { + return 0L; +} + +/** + Release all resources created when entering live mode. + \see enter_live_mode() +*/ +void Fl_Type::leave_live_mode() { +} + +/** + Copy all needed properties for this type into the live object. +*/ +void Fl_Type::copy_properties() { +} + +/** + Check whether callback \p cbname is declared anywhere else by the user. + + \b Warning: this just checks that the name is declared somewhere, + but it should probably also check that the name corresponds to a + plain function or a member function within the same class and that + the parameter types match. + */ +int Fl_Type::user_defined(const char* cbname) const { + for (Fl_Type* p = Fl_Type::first; p ; p = p->next) + if (p->is_a(ID_Function) && p->name() != 0) + if (strncmp(p->name(), cbname, strlen(cbname)) == 0) + if (p->name()[strlen(cbname)] == '(') + return 1; + return 0; +} + +const char *Fl_Type::callback_name(Fd_Code_Writer& f) { + if (is_name(callback())) return callback(); + return f.unique_id(this, "cb", name(), label()); +} + +/** + \brief Return the class name if this type is inside a Class or Widget Class. + + This methods traverses up the hirarchy to find out if this Type is located + inside a Class or Widget Class. It then return the name of that class. If + need_nest is set, class_name searches all the way up the tree and concatenates + the names of classes within classes, separated by a "::". + + \param need_nest if clear, search up one level to the first enclosing class. + If set, recurse all the way up to the top node. + \return the name of the enclosing class, or names of the enclosing classes + in a static buffe (don't call free), or NULL if this Type is not inside a class + */ +const char* Fl_Type::class_name(const int need_nest) const { + Fl_Type* p = parent; + while (p) { + if (p->is_class()) { + // see if we are nested in another class, we must fully-qualify name: + // this is lame but works... + const char* q = 0; + if(need_nest) q=p->class_name(need_nest); + if (q) { + static char s[256]; + if (q != s) strlcpy(s, q, sizeof(s)); + strlcat(s, "::", sizeof(s)); + strlcat(s, p->name(), sizeof(s)); + return s; + } + return p->name(); + } + p = p->parent; + } + return 0; +} + +/** + Check if this is inside a Fl_Class_Type or Fl_Widget_Class_Type. + \return true if any of the parents is Fl_Class_Type or Fl_Widget_Class_Type + */ +bool Fl_Type::is_in_class() const { + Fl_Type* p = parent; + while (p) { + if (p->is_class()) return true; + p = p->parent; + } + return false; +} + +void Fl_Type::write_static(Fd_Code_Writer&) { +} + +void Fl_Type::write_static_after(Fd_Code_Writer&) { +} + +void Fl_Type::write_code1(Fd_Code_Writer& f) { + f.write_h("// Header for %s\n", title()); + f.write_c("// Code for %s\n", title()); +} + +void Fl_Type::write_code2(Fd_Code_Writer&) { +} + +/** Set a uid that is unique within the project. + + Try to set the given id as the unique id for this node. If the suggested id + is 0, or it is already taken inside this project, we try another random id + until we find one that is unique. + + \param[in] suggested_uid the preferred uid for this node + \return the actualt uid that was given to the node + */ +unsigned short Fl_Type::set_uid(unsigned short suggested_uid) { + if (suggested_uid==0) + suggested_uid = (unsigned short)rand(); + for (;;) { + Fl_Type *tp = Fl_Type::first; + for ( ; tp; tp = tp->next) + if (tp!=this && tp->uid_==suggested_uid) + break; + if (tp==NULL) + break; + suggested_uid = (unsigned short)rand(); + } + uid_ = suggested_uid; + return suggested_uid; +} + +/** Find a node by its unique id. + + Every node in a type tree has an id that is unique for the current project. + Walk the tree and return the node with this uid. + + \param[in] uid any number between 0 and 65535 + \return the node with this uid, or NULL if not found + */ +Fl_Type *Fl_Type::find_by_uid(unsigned short uid) { + for (Fl_Type *tp = Fl_Type::first; tp; tp = tp->next) { + if (tp->uid_ == uid) return tp; + } + return NULL; +} + +/** Find a type node by using the codeview text positions. + + \param[in] text_type 0=source file, 1=header, 2=.fl project file + \param[in] crsr cursor position in text + \return the node we found or NULL + */ +Fl_Type *Fl_Type::find_in_text(int text_type, int crsr) { + for (Fl_Type *node = first; node; node = node->next) { + switch (text_type) { + case 0: + if (crsr >= node->code1_start && crsr < node->code1_end) return node; + if (crsr >= node->code2_start && crsr < node->code2_end) return node; + if (crsr >= node->code_static_start && crsr < node->code_static_end) return node; + break; + case 1: + if (crsr >= node->header1_start && crsr < node->header1_end) return node; + if (crsr >= node->header2_start && crsr < node->header2_end) return node; + if (crsr >= node->header_static_start && crsr < node->header_static_end) return node; + break; + case 2: + if (crsr >= node->proj1_start && crsr < node->proj1_end) return node; + if (crsr >= node->proj2_start && crsr < node->proj2_end) return node; + break; + } + } + return 0; +} + +/// \} + diff --git a/fluid/nodes/Fl_Type.h b/fluid/nodes/Fl_Type.h new file mode 100644 index 000000000..478059c13 --- /dev/null +++ b/fluid/nodes/Fl_Type.h @@ -0,0 +1,317 @@ +// +// Widget type header file 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 +// + +#ifndef _FLUID_FL_TYPE_H +#define _FLUID_FL_TYPE_H + +#include "io/code.h" + +#include +#include + +class Fl_Type; +class Fl_Group_Type; +class Fl_Window_Type; + +class Fd_Project_Reader; +class Fd_Project_Writer; + +/** + Declare where a new type is placed and how to create it. + + Placement can be as the first or last child of the anchor, or right after the + anchor. In most cases, the anchor is the last selected type node. + + If the source is FROM_USER, widgets may be created with default titles and + labels. Type created FROM_FILE will start with no label, so the label is set + correctly later. + + \see Fl_Type *Fl_..._Type::make(Strategy strategy) calls `add()` + Add single Type: + Fl_Type *add_new_widget_from_user(Fl_Type *inPrototype, Strategy strategy, bool and_open) + Fl_Type *add_new_widget_from_user(const char *inName, Strategy strategy, bool and_open) + Fl_Type *add_new_widget_from_file(const char *inName, Strategy strategy) + Add a hierarchy of Types + void Fl_Type::add(Fl_Type *p, Strategy strategy) + int read_file(const char *filename, int merge, Strategy strategy) + Fl_Type *Fd_Project_Reader::read_children(Fl_Type *p, int merge, Strategy strategy, char skip_options) + int Fd_Project_Reader::read_project(const char *filename, int merge, Strategy strategy) + */ +typedef struct Strategy { + enum Flags { + AS_FIRST_CHILD = 0x0000, + AS_LAST_CHILD = 0x0001, + AFTER_CURRENT = 0x0002, + PLACEMENT_MASK = 0x000f, + FROM_USER = 0x0000, + FROM_FILE = 0x0010, + SOURCE_MASK = 0x00f0, + FROM_FILE_AS_FIRST_CHILD = 0x0010, + FROM_FILE_AS_LAST_CHILD = 0x0011, + FROM_FILE_AFTER_CURRENT = 0x0012, + }; + Flags flags; + Strategy(Flags f) { flags = f; } + void placement(Flags f) { flags = (Flags)((flags & ~PLACEMENT_MASK) | (f & PLACEMENT_MASK)); } + Flags placement() { return (Flags)(flags & PLACEMENT_MASK); } + void source(Flags f) { flags = (Flags)((flags & ~SOURCE_MASK) | (f & SOURCE_MASK)); } + Flags source() { return (Flags)(flags & SOURCE_MASK); } +} Strategy; + +enum ID { + // administrative + ID_Base_, ID_Widget_, ID_Menu_Manager_, ID_Menu_, ID_Browser_, ID_Valuator_, + // non-widget + ID_Function, ID_Code, ID_CodeBlock, + ID_Decl, ID_DeclBlock, ID_Class, + ID_Widget_Class, ID_Comment, ID_Data, + // groups + ID_Window, ID_Group, ID_Pack, + ID_Flex, ID_Tabs, ID_Scroll, + ID_Tile, ID_Wizard, ID_Grid, + // buttons + ID_Button, ID_Return_Button, ID_Light_Button, + ID_Check_Button, ID_Repeat_Button, ID_Round_Button, + // valuators + ID_Slider, ID_Scrollbar, ID_Value_Slider, + ID_Adjuster, ID_Counter, ID_Spinner, + ID_Dial, ID_Roller, ID_Value_Input, ID_Value_Output, + // text + ID_Input, ID_Output, ID_Text_Editor, + ID_Text_Display, ID_File_Input, ID_Terminal, + // menus + ID_Menu_Bar, ID_Menu_Button, ID_Choice, + ID_Input_Choice, ID_Submenu, ID_Menu_Item, + ID_Checkbox_Menu_Item, ID_Radio_Menu_Item, + // browsers + ID_Browser, ID_Check_Browser, ID_File_Browser, + ID_Tree, ID_Help_View, ID_Table, + // misc + ID_Box, ID_Clock, ID_Progress, + ID_Max_ +}; + +void update_visibility_flag(Fl_Type *p); +void delete_all(int selected_only=0); +int storestring(const char *n, const char * & p, int nostrip=0); + +void select_all_cb(Fl_Widget *,void *); +void select_none_cb(Fl_Widget *,void *); +void earlier_cb(Fl_Widget*,void*); +void later_cb(Fl_Widget*,void*); + +#ifndef NDEBUG +void print_project_tree(); +bool validate_project_tree(); +bool validate_independent_branch(class Fl_Type *root); +bool validate_branch(class Fl_Type *root); +#endif + +/** + \brief This is the base class for all elements in the project tree. + + All widgets and other types in the project are derived from Fl_Types. They + are organized in a doubly linked list. Every Type also has depth information + to create a pseudo tree structure. To make walking up the tree faster, Type + also holds a pointer to the `parent` Type. + + Types can be identified using the builtin ID system that works like RTTI. The + method `id()` returns the exact type, and the method `is_a(ID)` returns true + if this is the exact type or derived from the type, and a dynamic cast will + work reliably. + + \todo it would be nice if we can handle multiple independent trees. To do that + we must remove static members like `first` and `last`. + + \todo add virtual methods to handle events, draw widgets, and draw overlays. + It may also make sense to have a virtual method that returns a boolean if + a specific type can be added as a child. + + \todo it may make sense to have a readable iterator class instead of relying + on pointer manipulation. Or use std in future releases. + */ +class Fl_Type { + /** Copy the label text to Widgets and Windows, does nothing in Type. */ + virtual void setlabel(const char *) { } // virtual part of label(char*) + +protected: + + Fl_Type(); + + /** Name of a widget, or code some non-widget Types. */ + const char *name_; + /** Label text of a widget. */ + const char *label_; + /** If it is just a word, it's the name of the callback function. Otherwise + it is the full callback C++ code. Can be NULL. */ + const char *callback_; + /** Widget user data field as C++ text. */ + const char *user_data_; + /** Widget user data type as C++ text, usually `void*` or `long`. */ + const char *user_data_type_; + /** Optional comment for every node in the graph. Visible in browser and + panels, and will also be copied to the source code. */ + const char *comment_; + /** a unique ID within the project */ + unsigned short uid_; + +public: // things that should not be public: + + /** Quick link to the parent Type instead of walking up the linked list. */ + Fl_Type *parent; + /** This type is rendered "selected" in the tree browser. */ + char new_selected; // browser highlight + /** Backup storage for selection if an error occurred during some operation + (see `haderror`). It seems that this is often confused with new_selected + which seems to hold the true and visible selection state. */ + char selected; // copied here by selection_changed() + char folded_; // if set, children are not shown in browser + char visible; // true if all parents are open + int level; // number of parents over this + static Fl_Type *first, *last; + Fl_Type *next, *prev; + Fl_Type *prev_sibling(); + Fl_Type *next_sibling(); + Fl_Type *first_child(); + + Fl_Type *factory; + const char *callback_name(Fd_Code_Writer& f); + + // text positions of this type in code, header, and project file (see codeview) + int code_static_start, code_static_end; + int code1_start, code1_end; + int code2_start, code2_end; + int header1_start, header1_end; + int header2_start, header2_end; + int header_static_start, header_static_end; + int proj1_start, proj1_end; + int proj2_start, proj2_end; + +protected: + int user_defined(const char* cbname) const; + +public: + + virtual ~Fl_Type(); + virtual Fl_Type *make(Strategy strategy) = 0; + + Fl_Window_Type *window(); + Fl_Group_Type *group(); + + void add(Fl_Type *parent, Strategy strategy); + void insert(Fl_Type *n); // insert into list before n + Fl_Type* remove(); // remove from list + void move_before(Fl_Type*); // move before a sibling + + virtual const char *title(); // string for browser + virtual const char *type_name() = 0; // type for code output + virtual const char *alt_type_name() { return type_name(); } // alternate type for FLTK2 code output + + const char *name() const {return name_;} + void name(const char *); + const char *label() const {return label_;} + void label(const char *); + const char *callback() const {return callback_;} + void callback(const char *); + const char *user_data() const {return user_data_;} + void user_data(const char *); + const char *user_data_type() const {return user_data_type_;} + void user_data_type(const char *); + const char *comment() { return comment_; } + void comment(const char *); + + virtual Fl_Type* click_test(int,int) { return NULL; } + + virtual void add_child(Fl_Type*, Fl_Type* beforethis) { } + virtual void move_child(Fl_Type*, Fl_Type* beforethis) { } + virtual void remove_child(Fl_Type*) { } + + /** Give widgets a chance to arrange their children after all children were added. + If adding individual children, this is called immediately, but if children + are read via a project file, we wait until all children are read and then + lay out the group. + */ + virtual void layout_widget() { } + + static Fl_Type *current; // most recently picked object + static Fl_Type *current_dnd; + + virtual void open(); // what happens when you double-click + + // read and write data to a saved file: + virtual void write(Fd_Project_Writer &f); + virtual void write_properties(Fd_Project_Writer &f); + virtual void read_property(Fd_Project_Reader &f, const char *); + virtual void write_parent_properties(Fd_Project_Writer &f, Fl_Type *child, bool encapsulate); + virtual void read_parent_property(Fd_Project_Reader &f, Fl_Type *child, const char *property); + virtual int read_fdesign(const char*, const char*); + virtual void postprocess_read() { } + + // write code, these are called in order: + virtual void write_static(Fd_Code_Writer& f); // write static stuff to .c file + virtual void write_static_after(Fd_Code_Writer& f); // write static stuff after children + virtual void write_code1(Fd_Code_Writer& f); // code and .h before children + virtual void write_code2(Fd_Code_Writer& f); // code and .h after children + void write_comment_h(Fd_Code_Writer& f, const char *ind=""); // write the commentary text into the header file + void write_comment_c(Fd_Code_Writer& f, const char *ind=""); // write the commentary text into the source file + void write_comment_inline_c(Fd_Code_Writer& f, const char *ind=0L); // write the commentary text + + // live mode + virtual Fl_Widget *enter_live_mode(int top=0); // build widgets needed for live mode + virtual void leave_live_mode(); // free allocated resources + virtual void copy_properties(); // copy properties from this type into a potential live object + virtual void copy_properties_for_children() { } // copy remaining properties after children were added + + // get message number for I18N + int msgnum(); + + /** Return 1 if the Type can have children. */ + virtual int can_have_children() const {return 0;} + /** Return 1 if the type is a widget or menu item. */ + virtual int is_widget() const {return 0;} + /** Return 1 if the type is a widget but not a menu item. */ + virtual int is_true_widget() const {return 0;} + /** Return 1 if a type behaves like a button (Fl_Button and Fl_Menu_Item and derived, but not Fl_Submenu_Type. */ + virtual int is_button() const {return 0;} + /** Return 1 if this is a Fl_Widget_Class_Type, Fl_CodeBlock_Type, or Fl_Function_Type */ + virtual int is_code_block() const {return 0;} + /** Return 1 if this is a Fl_Widget_Class_Type, Fl_Class_Type, or Fl_DeclBlock_Type */ + virtual int is_decl_block() const {return 0;} + /** Return 1 if this is a Fl_Class_Type or Fl_Widget_Class_Type. */ + virtual int is_class() const {return 0;} + /** Return 1 if the type browser shall draw a padlock over the icon. */ + virtual int is_public() const {return 1;} + /** Return the type ID for this Type. */ + virtual ID id() const { return ID_Base_; } + /** Check if this Type is of the give type ID or derived from that type ID. */ + virtual bool is_a(ID inID) const { return (inID==ID_Base_); } + + const char* class_name(const int need_nest) const; + bool is_in_class() const; + + int has_function(const char*, const char*) const; + + unsigned short set_uid(unsigned short suggested_uid=0); + unsigned short get_uid() { return uid_; } + static Fl_Type *find_by_uid(unsigned short uid); + + static Fl_Type *find_in_text(int text_type, int crsr); + + /// If this is greater zero, widgets will be allowed to lay out their children. + static int allow_layout; +}; + +#endif // _FLUID_FL_TYPE_H diff --git a/fluid/nodes/Fl_Widget_Type.cxx b/fluid/nodes/Fl_Widget_Type.cxx new file mode 100644 index 000000000..27e309cbe --- /dev/null +++ b/fluid/nodes/Fl_Widget_Type.cxx @@ -0,0 +1,3937 @@ +// +// Widget type 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/Fl_Widget_Type.h" + +#include "app/fluid.h" +#include "app/Fluid_Image.h" +#include "app/mergeback.h" +#include "app/undo.h" +#include "io/file.h" +#include "io/code.h" +#include "nodes/Fl_Window_Type.h" +#include "nodes/Fl_Group_Type.h" +#include "nodes/Fl_Menu_Type.h" +#include "nodes/Fl_Function_Type.h" +#include "panels/settings_panel.h" +#include "panels/widget_panel.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../src/flstring.h" + +#include +#include + +// Make an Fl_Widget_Type subclass instance. +// It figures out the automatic size and parent of the new widget, +// creates the Fl_Widget (by calling the virtual function _make), +// adds it to the Fl_Widget hierarchy, creates a new Fl_Type +// instance, sets the widget pointers, and makes all the display +// update correctly... + +int Fl_Widget_Type::is_widget() const {return 1;} +int Fl_Widget_Type::is_public() const {return public_;} + +const char* subclassname(Fl_Type* l) { + if (l->is_a(ID_Menu_Bar)) { + Fl_Menu_Bar_Type *mb = static_cast(l); + if (mb->is_sys_menu_bar()) + return mb->sys_menubar_name(); + } + if (l->is_widget()) { + Fl_Widget_Type* p = (Fl_Widget_Type*)l; + const char* c = p->subclass(); + if (c) return c; + if (l->is_class()) return "Fl_Group"; + if (p->o->type() == FL_DOUBLE_WINDOW) return "Fl_Double_Window"; + if (p->id() == ID_Input) { + if (p->o->type() == FL_FLOAT_INPUT) return "Fl_Float_Input"; + if (p->o->type() == FL_INT_INPUT) return "Fl_Int_Input"; + } + } + return l->type_name(); +} + +// Return the ideal widget size... +void +Fl_Widget_Type::ideal_size(int &w, int &h) { + w = 120; + h = 100; + Fd_Snap_Action::better_size(w, h); +} + +/** + Make a new Widget node. + \param[in] strategy is Strategy::AS_LAST_CHILD or Strategy::AFTER_CURRENT + \return new node + */ +Fl_Type *Fl_Widget_Type::make(Strategy strategy) { + Fl_Type *anchor = Fl_Type::current, *pp = anchor; + if (pp && (strategy.placement() == Strategy::AFTER_CURRENT)) + pp = pp->parent; + while (pp && !pp->is_a(ID_Group)) { + anchor = pp; + strategy.placement(Strategy::AFTER_CURRENT); + pp = pp->parent; + } + if (!pp || !pp->is_true_widget() || !anchor->is_true_widget()) { + fl_message("Please select a group widget or window"); + return 0; + } + + Fl_Widget_Type* p = (Fl_Widget_Type*)pp; + Fl_Widget_Type* q = (Fl_Widget_Type*)anchor; + + // Figure out a border between widget and window: + int B = p->o->w()/2; if (p->o->h()/2 < B) B = p->o->h()/2; if (B>25) B = 25; + + int ULX,ULY; // parent's origin in window + if (!p->is_a(ID_Window)) { // if it is a group, add corner + ULX = p->o->x(); ULY = p->o->y(); + } else { + ULX = ULY = 0; + } + + // Figure out a position and size for the widget + int X,Y,W,H; + if (is_a(ID_Group)) { // fill the parent with the widget + X = ULX+B; + W = p->o->w()-B; + Y = ULY+B; + H = p->o->h()-B; + } else if (q != p) { // copy position and size of current widget + W = q->o->w(); + H = q->o->h(); + X = q->o->x()+W; + Y = q->o->y(); + if (X+W > ULX+p->o->w()) { + X = q->o->x(); + Y = q->o->y()+H; + if (Y+H > ULY+p->o->h()) Y = ULY+B; + } + } else { // just make it small and square... + X = ULX+B; + Y = ULY+B; + W = H = B; + } + + // Construct the Fl_Type: + Fl_Widget_Type *t = _make(); + if (!o) o = widget(0,0,100,100); // create template widget + t->factory = this; + // Construct the Fl_Widget: + t->o = widget(X,Y,W,H); + if (strategy.source() == Strategy::FROM_FILE) + t->o->label(0); + else if (t->o->label()) t->label(t->o->label()); // allow editing + t->o->user_data((void*)t); + // Put it in the parent: + // ((Fl_Group *)(p->o))->add(t->o); (done by Fl_Type::add()) + // add to browser: + t->add(anchor, strategy); + t->redraw(); + return t; +} + +void Fl_Widget_Type::setimage(Fluid_Image *i) { + if (i == image || is_a(ID_Window)) return; + if (image) image->decrement(); + if (i) i->increment(); + image = i; + if (i) { + i->image(o); + if (o->image() && (scale_image_w_ || scale_image_h_)) { + int iw = scale_image_w_>0 ? scale_image_w_ : o->image()->data_w(); + int ih = scale_image_h_>0 ? scale_image_h_ : o->image()->data_h(); + o->image()->scale(iw, ih, 0, 1); + } + } else { + o->image(0); + //scale_image_w_ = scale_image_h_ = 0; + } + redraw(); +} + +void Fl_Widget_Type::setinactive(Fluid_Image *i) { + if (i == inactive || is_a(ID_Window)) return; + if (inactive) inactive->decrement(); + if (i) i->increment(); + inactive = i; + if (i) { + i->deimage(o); + if (o->deimage()) { + int iw = scale_deimage_w_>0 ? scale_deimage_w_ : o->deimage()->data_w(); + int ih = scale_deimage_h_>0 ? scale_deimage_h_ : o->deimage()->data_h(); + o->deimage()->scale(iw, ih, 0, 1); + } + } else { + o->deimage(0); + //scale_deimage_w_ = scale_deimage_h_ = 0; + } + redraw(); +} + +void Fl_Widget_Type::setlabel(const char *n) { + o->label(n); + redraw(); +} + +Fl_Widget_Type::Fl_Widget_Type() +: override_visible_(0) +{ + for (int n=0; nwindow(); + delete o; + if (win) + win->redraw(); + } + if (subclass_) free((void*)subclass_); + if (tooltip_) free((void*)tooltip_); + if (image_name_) { + free((void*)image_name_); + if (image) image->decrement(); + } + if (inactive_name_) { + free((void*)inactive_name_); + if (inactive) inactive->decrement(); + } + for (int n=0; ntooltip(n); +} + +void Fl_Widget_Type::image_name(const char *n) { + setimage(Fluid_Image::find(n)); + storestring(n,image_name_); +} + +void Fl_Widget_Type::inactive_name(const char *n) { + setinactive(Fluid_Image::find(n)); + storestring(n,inactive_name_); +} + +void Fl_Widget_Type::redraw() { + Fl_Type *t = this; + if (is_a(ID_Menu_Item)) { + // find the menu button that parents this menu: + do t = t->parent; while (t && t->is_a(ID_Menu_Item)); + // kludge to cause build_menu to be called again: + if (t) + t->add_child(0, 0); + } else { + while (t->parent && t->parent->is_widget()) t = t->parent; + ((Fl_Widget_Type*)t)->o->redraw(); + } +} + +// the recursive part sorts all children, returns pointer to next: +Fl_Type *sort(Fl_Type *parent) { + Fl_Type *f,*n=0; + for (f = parent ? parent->next : Fl_Type::first; ; f = n) { + if (!f || (parent && f->level <= parent->level)) break; + n = sort(f); + if (!f->selected || !f->is_true_widget()) continue; + Fl_Widget* fw = ((Fl_Widget_Type*)f)->o; + Fl_Type *g; // we will insert before this + for (g = parent ? parent->next : Fl_Type::first; g != f; g = g->next) { + if (!g->selected || g->level > f->level) continue; + Fl_Widget* gw = ((Fl_Widget_Type*)g)->o; + if (gw->y() > fw->y()) break; + if (gw->y() == fw->y() && gw->x() > fw->x()) break; + } + if (g != f) f->move_before(g); + } + if (parent) + parent->layout_widget(); + return f; +} + +//////////////////////////////////////////////////////////////// +// The control panels! + +Fl_Window *the_panel; + +// All the callbacks use the argument to indicate whether to load or store. +// This avoids the need for pointers to all the widgets, and keeps the +// code localized in the callbacks. +// A value of LOAD means to load. The hope is that this will not collide +// with any actual useful values for the argument. I also use this to +// initialized parts of the widget that are nyi by fluid. + +Fl_Widget_Type *current_widget; // one of the selected ones +void* const LOAD = (void *)"LOAD"; // "magic" pointer to indicate that we need to load values into the dialog +static int numselected; // number selected +static int haderror; + +void name_cb(Fl_Input* o, void *v) { + if (v == LOAD) { + static char buf[1024]; + if (numselected != 1) { + snprintf(buf, sizeof(buf), "Widget Properties (%d widgets)", numselected); + o->hide(); + } else { + o->value(current_widget->name()); + o->show(); + snprintf(buf, sizeof(buf), "%s Properties", current_widget->title()); + } + + the_panel->label(buf); + } else { + if (numselected == 1) { + current_widget->name(o->value()); + // I don't update window title, as it probably is being closed + // and wm2 (a window manager) barfs if you retitle and then + // hide a window: + // ((Fl_Window*)(o->parent()->parent()->parent()))->label(current_widget->title()); + } + } +} + +void name_public_member_cb(Fl_Choice* i, void* v) { + if (v == LOAD) { + i->value(current_widget->public_); + if (current_widget->is_in_class()) i->show(); else i->hide(); + } else { + int mod = 0; + for (Fl_Type *o = Fl_Type::first; o; o = o->next) { + if (o->selected && o->is_widget()) { + Fl_Widget_Type *w = ((Fl_Widget_Type*)o); + if (w->is_in_class()) { + w->public_ = i->value(); + } else { + // if this is not in a class, it can be only private or public + w->public_ = (i->value()>0); + } + mod = 1; + } + } + if (mod) { + set_modflag(1); + redraw_browser(); + } + } +} + +void name_public_cb(Fl_Choice* i, void* v) { + if (v == LOAD) { + i->value(current_widget->public_>0); + if (current_widget->is_in_class()) i->hide(); else i->show(); + } else { + int mod = 0; + for (Fl_Type *o = Fl_Type::first; o; o = o->next) { + if (o->selected && o->is_widget()) { + ((Fl_Widget_Type*)o)->public_ = i->value(); + mod = 1; + } + } + if (mod) { + set_modflag(1); + redraw_browser(); + } + } +} + +/* Treating UNDO for text widget. + + Goal: we want to continuously update the UI while the user is typing text + (changing the label, in this case). Code View does deferred updates, and + the widget browser and widget panel update on every keystroke. At the same + time, we want to limit undo actions to few and logical units. + + Caveats: + + 1: the text widget has its own undo handling for the text field, but we may want to do a global undo + 2: every o->label() call will create an undo entry, but we want only one single event for all selected widgets + 3: we want a single undo for the entire editing phase, but still propagate changes as they happen + + The edit process has these main states: + + 1: starting to edit [first_change==1 && !unfocus]; we must create a single undo checkpoint before anything changes + 2: continue editing [first_change==0 && !unfocus]; we must suspend any undo checkpoints + 3: done editing, unfocus [first_change==0 && unfocus]; we must make sure that undo checkpoints are enabled again + 4: losing focus without editing [first_change==1 && unfocus]; don't create and checkpoints + + We must also check: + 1: changing focus without changing text (works) + 2: copy and paste, drag and drop operations (works) + 3: save operation without unfocus event (works) + */ +void label_cb(Fl_Input* i, void *v) { + static int first_change = 1; + if (v == LOAD) { + i->value(current_widget->label()); + first_change = 1; + } else { + if (i->changed()) { + undo_suspend(); + int mod = 0; + for (Fl_Type *o = Fl_Type::first; o; o = o->next) { + if (o->selected && o->is_widget()) { + if (!mod) { + if (first_change) { + undo_resume(); + undo_checkpoint(); + undo_suspend(); + first_change = 0; + } + mod = 1; + } + o->label(i->value()); + } + } + undo_resume(); + if (mod) set_modflag(1); + } + int r = (int)Fl::callback_reason(); + if ( (r == FL_REASON_LOST_FOCUS) || (r == FL_REASON_ENTER_KEY) ) + first_change = 1; + } +} + +static Fl_Input *image_input; + +void image_cb(Fl_Input* i, void *v) { + if (v == LOAD) { + image_input = i; + if (current_widget->is_widget() && !current_widget->is_a(ID_Window)) { + i->activate(); + i->value(((Fl_Widget_Type*)current_widget)->image_name()); + } else i->deactivate(); + } else { + int mod = 0; + for (Fl_Type *o = Fl_Type::first; o; o = o->next) { + if (o->selected && o->is_widget()) { + ((Fl_Widget_Type*)o)->image_name(i->value()); + mod = 1; + } + } + if (mod) set_modflag(1); + } +} + +void image_browse_cb(Fl_Button* b, void *v) { + if (v == LOAD) { + if (current_widget->is_widget() && !current_widget->is_a(ID_Window)) + b->activate(); + else + b->deactivate(); + } else { + int mod = 0; + if (ui_find_image(image_input->value())) { + image_input->value(ui_find_image_name); + for (Fl_Type *o = Fl_Type::first; o; o = o->next) { + if (o->selected && o->is_widget()) { + ((Fl_Widget_Type*)o)->image_name(ui_find_image_name); + mod = 1; + } + } + if (mod) set_modflag(1); + } + } +} + +void bind_image_cb(Fl_Check_Button* b, void *v) { + if (v == LOAD) { + if (current_widget->is_widget() && !current_widget->is_a(ID_Window)) { + b->activate(); + b->value(current_widget->bind_image_); + } else { + b->deactivate(); + } + } else { + int mod = 0; + for (Fl_Type *o = Fl_Type::first; o; o = o->next) { + if (o->selected && o->is_widget()) { + ((Fl_Widget_Type*)o)->bind_image_ = b->value(); + mod = 1; + } + } + if (mod) set_modflag(1); + } +} + +void compress_image_cb(Fl_Check_Button* b, void *v) { + if (v == LOAD) { + if (current_widget->is_widget() && !current_widget->is_a(ID_Window)) { + b->activate(); + b->value(!current_widget->compress_image_); + } else { + b->deactivate(); + } + } else { + int mod = 0; + for (Fl_Type *o = Fl_Type::first; o; o = o->next) { + if (o->selected && o->is_widget()) { + ((Fl_Widget_Type*)o)->compress_image_ = !b->value(); + mod = 1; + } + } + if (mod) set_modflag(1); + } +} + +static Fl_Input *inactive_input; + +void inactive_cb(Fl_Input* i, void *v) { + if (v == LOAD) { + inactive_input = i; + if (current_widget->is_widget() && !current_widget->is_a(ID_Window)) { + i->activate(); + i->value(((Fl_Widget_Type*)current_widget)->inactive_name()); + } else i->deactivate(); + } else { + int mod = 0; + for (Fl_Type *o = Fl_Type::first; o; o = o->next) { + if (o->selected && o->is_widget()) { + ((Fl_Widget_Type*)o)->inactive_name(i->value()); + mod = 1; + } + } + if (mod) set_modflag(1); + } +} + +void inactive_browse_cb(Fl_Button* b, void *v) { + if (v == LOAD) { + if (current_widget->is_widget() && !current_widget->is_a(ID_Window)) + b->activate(); + else + b->deactivate(); + } else { + int mod = 0; + if (ui_find_image(inactive_input->value())) { + inactive_input->value(ui_find_image_name); + for (Fl_Type *o = Fl_Type::first; o; o = o->next) { + if (o->selected && o->is_widget()) { + ((Fl_Widget_Type*)o)->inactive_name(ui_find_image_name); + mod = 1; + } + } + if (mod) set_modflag(1); + } + } +} + +void bind_deimage_cb(Fl_Check_Button* b, void *v) { + if (v == LOAD) { + if (current_widget->is_widget() && !current_widget->is_a(ID_Window)) { + b->activate(); + b->value(current_widget->bind_deimage_); + } else { + b->deactivate(); + } + } else { + int mod = 0; + for (Fl_Type *o = Fl_Type::first; o; o = o->next) { + if (o->selected && o->is_widget()) { + ((Fl_Widget_Type*)o)->bind_deimage_ = b->value(); + mod = 1; + } + } + if (mod) set_modflag(1); + } +} + +void compress_deimage_cb(Fl_Check_Button* b, void *v) { + if (v == LOAD) { + if (current_widget->is_widget() && !current_widget->is_a(ID_Window)) { + b->activate(); + b->value(!current_widget->compress_deimage_); + } else { + b->deactivate(); + } + } else { + int mod = 0; + for (Fl_Type *o = Fl_Type::first; o; o = o->next) { + if (o->selected && o->is_widget()) { + ((Fl_Widget_Type*)o)->compress_deimage_ = !b->value(); + mod = 1; + } + } + if (mod) set_modflag(1); + } +} + +void tooltip_cb(Fl_Input* i, void *v) { + if (v == LOAD) { + if (current_widget->is_widget()) { + i->activate(); + i->value(((Fl_Widget_Type*)current_widget)->tooltip()); + } else i->deactivate(); + } else { + int mod = 0; + for (Fl_Type *o = Fl_Type::first; o; o = o->next) { + if (o->selected && o->is_widget()) { + ((Fl_Widget_Type*)o)->tooltip(i->value()); + mod = 1; + } + } + if (mod) set_modflag(1); + } +} + +Fluid_Coord_Input *x_input, *y_input, *w_input, *h_input; + +static int widget_i = 0; + +static int vars_i_cb(const Fluid_Coord_Input*, void *v) { + return widget_i; +} + +static int vars_x_cb(const Fluid_Coord_Input*, void *v) { + Fl_Type *t = (Fl_Type*)v; + if (t->is_widget()) + return ((Fl_Widget_Type*)t)->o->x(); + return 0; +} + +static int vars_y_cb(const Fluid_Coord_Input*, void *v) { + Fl_Type *t = (Fl_Type*)v; + if (t->is_widget()) + return ((Fl_Widget_Type*)t)->o->y(); + return 0; +} + +static int vars_w_cb(const Fluid_Coord_Input*, void *v) { + Fl_Type *t = (Fl_Type*)v; + if (t->is_widget()) + return ((Fl_Widget_Type*)t)->o->w(); + return 0; +} + +static int vars_h_cb(const Fluid_Coord_Input*, void *v) { + Fl_Type *t = (Fl_Type*)v; + if (t->is_widget()) + return ((Fl_Widget_Type*)t)->o->h(); + return 0; +} + +static int vars_px_cb(const Fluid_Coord_Input*, void *v) { + Fl_Type *t = ((Fl_Type*)v)->parent; + if (t && t->is_widget()) + return ((Fl_Widget_Type*)t)->o->x(); + return 0; +} + +static int vars_py_cb(const Fluid_Coord_Input*, void *v) { + Fl_Type *t = ((Fl_Type*)v)->parent; + if (t && t->is_widget()) + return ((Fl_Widget_Type*)t)->o->y(); + return 0; +} + +static int vars_pw_cb(const Fluid_Coord_Input*, void *v) { + Fl_Type *t = ((Fl_Type*)v)->parent; + if (t && t->is_widget()) + return ((Fl_Widget_Type*)t)->o->w(); + return 0; +} + +static int vars_ph_cb(const Fluid_Coord_Input*, void *v) { + Fl_Type *t = ((Fl_Type*)v)->parent; + if (t && t->is_widget()) + return ((Fl_Widget_Type*)t)->o->h(); + return 0; +} + +static int vars_sx_cb(const Fluid_Coord_Input*, void *v) { + Fl_Type *t = ((Fl_Type*)v)->prev_sibling(); + if (t && t->is_widget()) + return ((Fl_Widget_Type*)t)->o->x(); + return 0; +} + +static int vars_sy_cb(const Fluid_Coord_Input*, void *v) { + Fl_Type *t = ((Fl_Type*)v)->prev_sibling(); + if (t && t->is_widget()) + return ((Fl_Widget_Type*)t)->o->y(); + return 0; +} + +static int vars_sw_cb(const Fluid_Coord_Input*, void *v) { + Fl_Type *t = ((Fl_Type*)v)->prev_sibling(); + if (t && t->is_widget()) + return ((Fl_Widget_Type*)t)->o->w(); + return 0; +} + +static int vars_sh_cb(const Fluid_Coord_Input*, void *v) { + Fl_Type *t = ((Fl_Type*)v)->prev_sibling(); + if (t && t->is_widget()) + return ((Fl_Widget_Type*)t)->o->h(); + return 0; +} + +static int bbox_x, bbox_y, bbox_r, bbox_b; +static int bbox_min(int a, int b) { return (ab) ? a : b; } + +static void calculate_bbox(Fl_Type *p) { + char first = 1; + bbox_x = bbox_y = bbox_r = bbox_b = 0; + for (p=p->first_child(); p; p=p->next_sibling()) { + if (p->is_widget()) { + Fl_Widget *o = ((Fl_Widget_Type*)p)->o; + if (first) { + bbox_x = o->x(); bbox_y = o->y(); + bbox_r = o->x() + o->w(); bbox_b = o->y() + o->h(); + first = 0; + } else { + bbox_x = bbox_min(bbox_x, o->x()); + bbox_y = bbox_min(bbox_y, o->y()); + bbox_r = bbox_max(bbox_r, o->x() + o->w()); + bbox_b = bbox_max(bbox_b, o->y() + o->h()); + } + } + } +} + +static int vars_cx_cb(const Fluid_Coord_Input*, void *v) { + calculate_bbox((Fl_Type*)v); + return bbox_x; +} + +static int vars_cy_cb(const Fluid_Coord_Input*, void *v) { + calculate_bbox((Fl_Type*)v); + return bbox_y; +} + +static int vars_cw_cb(const Fluid_Coord_Input*, void *v) { + calculate_bbox((Fl_Type*)v); + return bbox_r - bbox_x; +} + +static int vars_ch_cb(const Fluid_Coord_Input*, void *v) { + calculate_bbox((Fl_Type*)v); + return bbox_b - bbox_y; +} + +Fluid_Coord_Input_Vars widget_vars[] = { + { "i", vars_i_cb }, // zero based counter of selected widgets + { "x", vars_x_cb }, // position and size of current widget + { "y", vars_y_cb }, + { "w", vars_w_cb }, + { "h", vars_h_cb }, + { "px", vars_px_cb }, // position and size of parent widget + { "py", vars_py_cb }, + { "pw", vars_pw_cb }, + { "ph", vars_ph_cb }, + { "sx", vars_sx_cb }, // position and size of previous sibling + { "sy", vars_sy_cb }, + { "sw", vars_sw_cb }, + { "sh", vars_sh_cb }, + { "cx", vars_cx_cb }, // position and size of bounding box of all children + { "cy", vars_cy_cb }, + { "cw", vars_cw_cb }, + { "ch", vars_ch_cb }, + { 0 } +}; + +void x_cb(Fluid_Coord_Input *i, void *v) { + if (v == LOAD) { + x_input = i; + if (current_widget->is_true_widget()) { + i->value(((Fl_Widget_Type *)current_widget)->o->x()); + x_input->activate(); + } else x_input->deactivate(); + } else { + undo_checkpoint(); + widget_i = 0; + int mod = 0; + int v = 0; + for (Fl_Type *o = Fl_Type::first; o; o = o->next) { + if (o->selected && o->is_true_widget()) { + Fl_Widget *w = ((Fl_Widget_Type *)o)->o; + i->variables(widget_vars, o); + v = i->value(); + w->resize(v, w->y(), w->w(), w->h()); + if (w->window()) w->window()->redraw(); + widget_i++; + mod = 1; + } + } + if (mod) { + set_modflag(1); + i->value(v); // change the displayed value to the result of the last + // calculation. Keep the formula if it was not used. + } + } +} + +void y_cb(Fluid_Coord_Input *i, void *v) { + if (v == LOAD) { + y_input = i; + if (current_widget->is_true_widget()) { + i->value(((Fl_Widget_Type *)current_widget)->o->y()); + y_input->activate(); + } else y_input->deactivate(); + } else { + undo_checkpoint(); + widget_i = 0; + int mod = 0; + int v = 0; + for (Fl_Type *o = Fl_Type::first; o; o = o->next) { + if (o->selected && o->is_true_widget()) { + Fl_Widget *w = ((Fl_Widget_Type *)o)->o; + i->variables(widget_vars, o); + v = i->value(); + w->resize(w->x(), v, w->w(), w->h()); + if (w->window()) w->window()->redraw(); + widget_i++; + mod = 1; + } + } + if (mod) { + set_modflag(1); + i->value(v); + } + } +} + +void w_cb(Fluid_Coord_Input *i, void *v) { + if (v == LOAD) { + w_input = i; + if (current_widget->is_true_widget()) { + i->value(((Fl_Widget_Type *)current_widget)->o->w()); + w_input->activate(); + } else w_input->deactivate(); + } else { + undo_checkpoint(); + widget_i = 0; + int mod = 0; + int v = 0; + for (Fl_Type *o = Fl_Type::first; o; o = o->next) { + if (o->selected && o->is_true_widget()) { + Fl_Widget *w = ((Fl_Widget_Type *)o)->o; + i->variables(widget_vars, o); + v = i->value(); + w->resize(w->x(), w->y(), v, w->h()); + if (w->window()) w->window()->redraw(); + widget_i++; + mod = 1; + } + } + if (mod) { + set_modflag(1); + i->value(v); + } + } +} + +void h_cb(Fluid_Coord_Input *i, void *v) { + if (v == LOAD) { + h_input = i; + if (current_widget->is_true_widget()) { + i->value(((Fl_Widget_Type *)current_widget)->o->h()); + h_input->activate(); + } else h_input->deactivate(); + } else { + undo_checkpoint(); + widget_i = 0; + int mod = 0; + int v = 0; + for (Fl_Type *o = Fl_Type::first; o; o = o->next) { + if (o->selected && o->is_true_widget()) { + Fl_Widget *w = ((Fl_Widget_Type *)o)->o; + i->variables(widget_vars, o); + v = i->value(); + w->resize(w->x(), w->y(), w->w(), v); + if (w->window()) w->window()->redraw(); + widget_i++; + mod = 1; + } + } + if (mod) { + set_modflag(1); + i->value(v); + } + } +} + +void wc_relative_cb(Fl_Choice *i, void *v) { + if (v == LOAD) { + if (current_widget->is_a(ID_Widget_Class)) { + i->show(); + i->value(((Fl_Widget_Class_Type *)current_widget)->wc_relative); + } else { + i->hide(); + } + } else { + int mod = 0; + undo_checkpoint(); + for (Fl_Type *o = Fl_Type::first; o; o = o->next) { + if (o->selected && current_widget->is_a(ID_Widget_Class)) { + Fl_Widget_Class_Type *t = (Fl_Widget_Class_Type *)o; + t->wc_relative = i->value(); + mod = 1; + } + } + if (mod) set_modflag(1); + } +} + +//////////////////////////////////////////////////////////////// + +// turn number to string or string to number for saving to file: +// does not work for hierarchical menus! + +const char *item_name(Fl_Menu_Item* m, int i) { + if (m) { + while (m->label()) { + if (m->argument() == i) return m->label(); + m++; + } + } + static char buffer[20]; + sprintf(buffer, "%d", i); + return buffer; +} +int item_number(Fl_Menu_Item* m, const char* i) { + if (!i) + return 0; + if (m && i) { + if (i[0]=='F' && i[1]=='L' && i[2]=='_') i += 3; + while (m->label()) { + if (!strcmp(m->label(), i)) return int(m->argument()); + m++; + } + } + return atoi(i); +} + +#define ZERO_ENTRY 1000 + +Fl_Menu_Item boxmenu[] = { +{"NO_BOX",0,0,(void *)ZERO_ENTRY}, +{"boxes",0,0,0,FL_SUBMENU}, +{"UP_BOX",0,0,(void *)FL_UP_BOX}, +{"DOWN_BOX",0,0,(void *)FL_DOWN_BOX}, +{"FLAT_BOX",0,0,(void *)FL_FLAT_BOX}, +{"BORDER_BOX",0,0,(void *)FL_BORDER_BOX}, +{"THIN_UP_BOX",0,0,(void *)FL_THIN_UP_BOX}, +{"THIN_DOWN_BOX",0,0,(void *)FL_THIN_DOWN_BOX}, +{"ENGRAVED_BOX",0,0,(void *)FL_ENGRAVED_BOX}, +{"EMBOSSED_BOX",0,0,(void *)FL_EMBOSSED_BOX}, +{"ROUND_UP_BOX",0,0,(void *)FL_ROUND_UP_BOX}, +{"ROUND_DOWN_BOX",0,0,(void *)FL_ROUND_DOWN_BOX}, +{"DIAMOND_UP_BOX",0,0,(void *)FL_DIAMOND_UP_BOX}, +{"DIAMOND_DOWN_BOX",0,0,(void *)FL_DIAMOND_DOWN_BOX}, +{"SHADOW_BOX",0,0,(void *)FL_SHADOW_BOX}, +{"ROUNDED_BOX",0,0,(void *)FL_ROUNDED_BOX}, +{"RSHADOW_BOX",0,0,(void *)FL_RSHADOW_BOX}, +{"RFLAT_BOX",0,0,(void *)FL_RFLAT_BOX}, +{"OVAL_BOX",0,0,(void *)FL_OVAL_BOX}, +{"OSHADOW_BOX",0,0,(void *)FL_OSHADOW_BOX}, +{"OFLAT_BOX",0,0,(void *)FL_OFLAT_BOX}, +{"PLASTIC_UP_BOX",0,0,(void *)FL_PLASTIC_UP_BOX}, +{"PLASTIC_DOWN_BOX",0,0,(void *)FL_PLASTIC_DOWN_BOX}, +{"PLASTIC_THIN_UP_BOX",0,0,(void *)FL_PLASTIC_THIN_UP_BOX}, +{"PLASTIC_THIN_DOWN_BOX",0,0,(void *)FL_PLASTIC_THIN_DOWN_BOX}, +{"PLASTIC_ROUND_UP_BOX",0,0,(void *)FL_PLASTIC_ROUND_UP_BOX}, +{"PLASTIC_ROUND_DOWN_BOX",0,0,(void *)FL_PLASTIC_ROUND_DOWN_BOX}, +{"GTK_UP_BOX",0,0,(void *)FL_GTK_UP_BOX}, +{"GTK_DOWN_BOX",0,0,(void *)FL_GTK_DOWN_BOX}, +{"GTK_THIN_UP_BOX",0,0,(void *)FL_GTK_THIN_UP_BOX}, +{"GTK_THIN_DOWN_BOX",0,0,(void *)FL_GTK_THIN_DOWN_BOX}, +{"GTK_ROUND_UP_BOX",0,0,(void *)FL_GTK_ROUND_UP_BOX}, +{"GTK_ROUND_DOWN_BOX",0,0,(void *)FL_GTK_ROUND_DOWN_BOX}, +{"GLEAM_UP_BOX",0,0,(void *)FL_GLEAM_UP_BOX}, +{"GLEAM_DOWN_BOX",0,0,(void *)FL_GLEAM_DOWN_BOX}, +{"GLEAM_THIN_UP_BOX",0,0,(void *)FL_GLEAM_THIN_UP_BOX}, +{"GLEAM_THIN_DOWN_BOX",0,0,(void *)FL_GLEAM_THIN_DOWN_BOX}, +{"GLEAM_ROUND_UP_BOX",0,0,(void *)FL_GLEAM_ROUND_UP_BOX}, +{"GLEAM_ROUND_DOWN_BOX",0,0,(void *)FL_GLEAM_ROUND_DOWN_BOX}, +{"OXY_UP_BOX",0,0,(void *)FL_OXY_UP_BOX}, +{"OXY_DOWN_BOX",0,0,(void *)FL_OXY_DOWN_BOX}, +{"OXY_THIN_UP_BOX",0,0,(void *)FL_OXY_THIN_UP_BOX}, +{"OXY_THIN_DOWN_BOX",0,0,(void *)FL_OXY_THIN_DOWN_BOX}, +{"OXY_ROUND_UP_BOX",0,0,(void *)FL_OXY_ROUND_UP_BOX}, +{"OXY_ROUND_DOWN_BOX",0,0,(void *)FL_OXY_ROUND_DOWN_BOX}, +{"OXY_BUTTON_UP_BOX",0,0,(void *)FL_OXY_BUTTON_UP_BOX}, +{"OXY_BUTTON_DOWN_BOX",0,0,(void *)FL_OXY_BUTTON_DOWN_BOX}, +{0}, +{"frames",0,0,0,FL_SUBMENU}, +{"UP_FRAME",0,0,(void *)FL_UP_FRAME}, +{"DOWN_FRAME",0,0,(void *)FL_DOWN_FRAME}, +{"THIN_UP_FRAME",0,0,(void *)FL_THIN_UP_FRAME}, +{"THIN_DOWN_FRAME",0,0,(void *)FL_THIN_DOWN_FRAME}, +{"ENGRAVED_FRAME",0,0,(void *)FL_ENGRAVED_FRAME}, +{"EMBOSSED_FRAME",0,0,(void *)FL_EMBOSSED_FRAME}, +{"BORDER_FRAME",0,0,(void *)FL_BORDER_FRAME}, +{"SHADOW_FRAME",0,0,(void *)FL_SHADOW_FRAME}, +{"ROUNDED_FRAME",0,0,(void *)FL_ROUNDED_FRAME}, +{"OVAL_FRAME",0,0,(void *)FL_OVAL_FRAME}, +{"PLASTIC_UP_FRAME",0,0,(void *)FL_PLASTIC_UP_FRAME}, +{"PLASTIC_DOWN_FRAME",0,0,(void *)FL_PLASTIC_DOWN_FRAME}, +{"GTK_UP_FRAME",0,0,(void *)FL_GTK_UP_FRAME}, +{"GTK_DOWN_FRAME",0,0,(void *)FL_GTK_DOWN_FRAME}, +{"GTK_THIN_UP_FRAME",0,0,(void *)FL_GTK_THIN_UP_FRAME}, +{"GTK_THIN_DOWN_FRAME",0,0,(void *)FL_GTK_THIN_DOWN_FRAME}, +{"GLEAM_UP_FRAME",0,0,(void *)FL_GLEAM_UP_FRAME}, +{"GLEAM_DOWN_FRAME",0,0,(void *)FL_GLEAM_DOWN_FRAME}, +{"OXY_UP_FRAME",0,0,(void *)FL_OXY_UP_FRAME}, +{"OXY_DOWN_FRAME",0,0,(void *)FL_OXY_DOWN_FRAME}, +{"OXY_THIN_UP_FRAME",0,0,(void *)FL_OXY_THIN_UP_FRAME}, +{"OXY_THIN_DOWN_FRAME",0,0,(void *)FL_OXY_THIN_DOWN_FRAME}, +{0}, +{0}}; + +const char *boxname(int i) { + if (!i) i = ZERO_ENTRY; + for (int j = 0; j < int(sizeof(boxmenu)/sizeof(*boxmenu)); j++) + if (boxmenu[j].argument() == i) return boxmenu[j].label(); + return 0; +} + +int boxnumber(const char *i) { + if (i[0]=='F' && i[1]=='L' && i[2]=='_') i += 3; + for (int j = 0; j < int(sizeof(boxmenu)/sizeof(*boxmenu)); j++) + if (boxmenu[j].label() && !strcmp(boxmenu[j].label(), i)) { + return int(boxmenu[j].argument()); + } + return 0; +} + +void box_cb(Fl_Choice* i, void *v) { + if (v == LOAD) { + if (current_widget->is_a(ID_Menu_Item)) {i->deactivate(); return;} else i->activate(); + int n = current_widget->o->box(); if (!n) n = ZERO_ENTRY; + for (int j = 0; j < int(sizeof(boxmenu)/sizeof(*boxmenu)); j++) + if (boxmenu[j].argument() == n) {i->value(j); break;} + } else { + int mod = 0; + int m = i->value(); + int n = int(boxmenu[m].argument()); + if (!n) return; // should not happen + if (n == ZERO_ENTRY) n = 0; + for (Fl_Type *o = Fl_Type::first; o; o = o->next) { + if (o->selected && o->is_widget()) { + Fl_Widget_Type* q = (Fl_Widget_Type*)o; + q->o->box((Fl_Boxtype)n); + q->redraw(); + mod = 1; + } + } + if (mod) set_modflag(1); + } +} + +void down_box_cb(Fl_Choice* i, void *v) { + if (v == LOAD) { + int n; + if (current_widget->is_a(ID_Button)) + n = ((Fl_Button*)(current_widget->o))->down_box(); + else if (current_widget->is_a(ID_Input_Choice)) + n = ((Fl_Input_Choice*)(current_widget->o))->down_box(); + else if (current_widget->is_a(ID_Menu_Manager_)) + n = ((Fl_Menu_*)(current_widget->o))->down_box(); + else { + i->deactivate(); return; + } + i->activate(); + if (!n) n = ZERO_ENTRY; + for (int j = 0; j < int(sizeof(boxmenu)/sizeof(*boxmenu)); j++) + if (boxmenu[j].argument() == n) {i->value(j); break;} + } else { + int mod = 0; + int m = i->value(); + int n = int(boxmenu[m].argument()); + if (!n) return; // should not happen + if (n == ZERO_ENTRY) n = 0; + for (Fl_Type *o = Fl_Type::first; o; o = o->next) { + if (o->selected) { + if (o->is_a(ID_Button)) { + Fl_Widget_Type* q = (Fl_Widget_Type*)o; + ((Fl_Button*)(q->o))->down_box((Fl_Boxtype)n); + if (((Fl_Button*)(q->o))->value()) q->redraw(); + } else if (o->is_a(ID_Input_Choice)) { + Fl_Widget_Type* q = (Fl_Widget_Type*)o; + ((Fl_Input_Choice*)(q->o))->down_box((Fl_Boxtype)n); + } else if (o->is_a(ID_Menu_Manager_)) { + Fl_Widget_Type* q = (Fl_Widget_Type*)o; + ((Fl_Menu_*)(q->o))->down_box((Fl_Boxtype)n); + } + mod = 1; + } + } + if (mod) set_modflag(1); + } +} + +void compact_cb(Fl_Light_Button* i, void* v) { + if (v == LOAD) { + uchar n; + if (current_widget->is_a(ID_Button) && !current_widget->is_a(ID_Menu_Item)) { + n = ((Fl_Button*)(current_widget->o))->compact(); + i->value(n); + i->show(); + } else { + i->hide(); + } + } else { + int mod = 0; + uchar n = (uchar)i->value(); + for (Fl_Type *o = Fl_Type::first; o; o = o->next) { + if (o->selected && o->is_a(ID_Button) && !o->is_a(ID_Menu_Item)) { + Fl_Widget_Type* q = (Fl_Widget_Type*)o; + uchar v = ((Fl_Button*)(q->o))->compact(); + if (n != v) { + if (!mod) { + mod = 1; + undo_checkpoint(); + } + ((Fl_Button*)(q->o))->compact(n); + q->redraw(); + } + } + } + if (mod) set_modflag(1); + } +} + + + +//////////////////////////////////////////////////////////////// + +Fl_Menu_Item whenmenu[] = { + // set individual bits + {"FL_WHEN_CHANGED",0,0,(void*)FL_WHEN_CHANGED, FL_MENU_TOGGLE}, + {"FL_WHEN_NOT_CHANGED",0,0,(void*)FL_WHEN_NOT_CHANGED, FL_MENU_TOGGLE}, + {"FL_WHEN_RELEASE",0,0,(void*)FL_WHEN_RELEASE, FL_MENU_TOGGLE}, + {"FL_WHEN_ENTER_KEY",0,0,(void*)FL_WHEN_ENTER_KEY, FL_MENU_TOGGLE}, + {"FL_WHEN_CLOSED",0,0,(void*)FL_WHEN_CLOSED, FL_MENU_TOGGLE|FL_MENU_DIVIDER}, + // set bit combinations + {"FL_WHEN_NEVER",0,0,(void*)FL_WHEN_NEVER}, + {"FL_WHEN_RELEASE_ALWAYS",0,0,(void*)FL_WHEN_RELEASE_ALWAYS}, + {"FL_WHEN_ENTER_KEY_ALWAYS",0,0,(void*)FL_WHEN_ENTER_KEY_ALWAYS}, + {"FL_WHEN_ENTER_KEY_CHANGED",0,0,(void*)FL_WHEN_ENTER_KEY_CHANGED}, + {0}}; + + +static Fl_Menu_Item whensymbolmenu[] = { + /* 0 */ {"FL_WHEN_NEVER",0,0,(void*)FL_WHEN_NEVER}, + /* 1 */ {"FL_WHEN_CHANGED",0,0,(void*)FL_WHEN_CHANGED}, + /* 2 */ {"FL_WHEN_NOT_CHANGED",0,0,(void*)FL_WHEN_NOT_CHANGED}, + /* 3 */ {"FL_WHEN_CHANGED | FL_WHEN_NOT_CHANGED",0,0,(void*)(FL_WHEN_CHANGED|FL_WHEN_NOT_CHANGED)}, + /* 4 */ {"FL_WHEN_RELEASE",0,0,(void*)FL_WHEN_RELEASE}, + /* 5 */ {"FL_WHEN_CHANGED | FL_WHEN_RELEASE",0,0,(void*)(FL_WHEN_CHANGED|FL_WHEN_RELEASE)}, + /* 6 */ {"FL_WHEN_RELEASE_ALWAYS",0,0,(void*)FL_WHEN_RELEASE_ALWAYS}, + /* 7 */ {"FL_WHEN_CHANGED | FL_WHEN_RELEASE_ALWAYS",0,0,(void*)(FL_WHEN_CHANGED|FL_WHEN_RELEASE_ALWAYS)}, + /* 8 */ {"FL_WHEN_ENTER_KEY",0,0,(void*)FL_WHEN_ENTER_KEY}, + /* 9 */ {"FL_WHEN_CHANGED | FL_WHEN_ENTER_KEY",0,0,(void*)(FL_WHEN_CHANGED|FL_WHEN_ENTER_KEY)}, + /* 10 */ {"FL_WHEN_ENTER_KEY_ALWAYS",0,0,(void*)FL_WHEN_ENTER_KEY_ALWAYS}, + /* 11 */ {"FL_WHEN_ENTER_KEY_CHANGED",0,0,(void*)FL_WHEN_ENTER_KEY_CHANGED}, + /* 12 */ {"FL_WHEN_RELEASE | FL_WHEN_ENTER_KEY",0,0,(void*)(FL_WHEN_RELEASE|FL_WHEN_ENTER_KEY)}, + /* 13 */ {"FL_WHEN_RELEASE | FL_WHEN_CHANGED | FL_WHEN_ENTER_KEY",0,0,(void*)(FL_WHEN_RELEASE|FL_WHEN_CHANGED|FL_WHEN_ENTER_KEY)}, + /* 14 */ {"FL_WHEN_RELEASE | FL_WHEN_ENTER_KEY_ALWAYS",0,0,(void*)(FL_WHEN_RELEASE|FL_WHEN_ENTER_KEY_ALWAYS)}, + /* 15 */ {"FL_WHEN_RELEASE | FL_WHEN_ENTER_KEY_CHANGED",0,0,(void*)(FL_WHEN_RELEASE|FL_WHEN_ENTER_KEY_CHANGED)}, + {0} +}; + +// Return a text string representing the Fl_When value n +const char* when_symbol_name(int n) { + static char sym[128]; + if (n == FL_WHEN_CLOSED) { + strcpy(sym, "FL_WHEN_CLOSED"); + } else { + strcpy(sym, whensymbolmenu[n&15].label()); + if (n & FL_WHEN_CLOSED) + strcat(sym, " | FL_WHEN_CLOSED"); + } + return sym; +} + +// Set the check marks in the "when()" menu according to the Fl_When value n +void set_whenmenu(int n) { + if (n&FL_WHEN_CHANGED) whenmenu[0].set(); else whenmenu[0].clear(); + if (n&FL_WHEN_NOT_CHANGED) whenmenu[1].set(); else whenmenu[1].clear(); + if (n&FL_WHEN_RELEASE) whenmenu[2].set(); else whenmenu[2].clear(); + if (n&FL_WHEN_ENTER_KEY) whenmenu[3].set(); else whenmenu[3].clear(); + if (n&FL_WHEN_CLOSED) whenmenu[4].set(); else whenmenu[4].clear(); +} + +void when_cb(Fl_Menu_Button* i, void *v) { + if (v == LOAD) { + if (current_widget->is_a(ID_Menu_Item)) {i->deactivate(); return;} else i->activate(); + int n = current_widget->o->when(); + set_whenmenu(n); + w_when_box->copy_label(when_symbol_name(n)); + } else { + int mod = 0; + int n = 0; + if (i->mvalue() && ((i->mvalue()->flags & FL_MENU_TOGGLE) == 0) ) { + n = (int)i->mvalue()->argument(); + set_whenmenu(n); + } else { + if (whenmenu[0].value()) n |= FL_WHEN_CHANGED; + if (whenmenu[1].value()) n |= FL_WHEN_NOT_CHANGED; + if (whenmenu[2].value()) n |= FL_WHEN_RELEASE; + if (whenmenu[3].value()) n |= FL_WHEN_ENTER_KEY; + if (whenmenu[4].value()) n |= FL_WHEN_CLOSED; + } + w_when_box->copy_label(when_symbol_name(n)); + for (Fl_Type *o = Fl_Type::first; o; o = o->next) { + if (o->selected && o->is_widget()) { + Fl_Widget_Type* q = (Fl_Widget_Type*)o; + q->o->when(n); + mod = 1; + } + } + if (mod) set_modflag(1); + } +} + +uchar Fl_Widget_Type::resizable() const { + if (is_a(ID_Window)) return ((Fl_Window*)o)->resizable() != 0; + Fl_Group* p = (Fl_Group*)o->parent(); + if (p) return p->resizable() == o; + else return 0; +} + +void Fl_Widget_Type::resizable(uchar v) { + if (v) { + if (resizable()) return; + if (is_a(ID_Window)) ((Fl_Window*)o)->resizable(o); + else { + Fl_Group* p = (Fl_Group*)o->parent(); + if (p) p->resizable(o); + } + } else { + if (!resizable()) return; + if (is_a(ID_Window)) { + ((Fl_Window*)o)->resizable(0); + } else { + Fl_Group* p = (Fl_Group*)o->parent(); + if (p) p->resizable(0); + } + } +} + +void resizable_cb(Fl_Light_Button* i,void* v) { + if (v == LOAD) { + if (current_widget->is_a(ID_Menu_Item)) {i->deactivate(); return;} + if (numselected > 1) {i->deactivate(); return;} + i->activate(); + i->value(current_widget->resizable()); + } else { + undo_checkpoint(); + current_widget->resizable(i->value()); + set_modflag(1); + } +} + +void hotspot_cb(Fl_Light_Button* i,void* v) { + if (v == LOAD) { + if (numselected > 1) {i->deactivate(); return;} + if (current_widget->is_a(ID_Menu_Item)) i->label("divider"); + else i->label("hotspot"); + i->activate(); + i->value(current_widget->hotspot()); + } else { + undo_checkpoint(); + current_widget->hotspot(i->value()); + if (current_widget->is_a(ID_Menu_Item)) { + current_widget->redraw(); + return; + } + if (i->value()) { + Fl_Type *p = current_widget->parent; + if (!p || !p->is_widget()) return; + while (!p->is_a(ID_Window)) p = p->parent; + for (Fl_Type *o = p->next; o && o->level > p->level; o = o->next) { + if (o->is_widget() && o != current_widget) + ((Fl_Widget_Type*)o)->hotspot(0); + } + } + set_modflag(1); + } +} + +void visible_cb(Fl_Light_Button* i, void* v) { + if (v == LOAD) { + i->value(current_widget->o->visible()); + if (current_widget->is_a(ID_Window)) i->deactivate(); + else i->activate(); + } else { + int mod = 0; + int n = i->value(); + for (Fl_Type *o = Fl_Type::first; o; o = o->next) { + if (o->selected && o->is_widget()) { + if (!mod) { + mod = 1; + undo_checkpoint(); + } + Fl_Widget_Type* q = (Fl_Widget_Type*)o; + n ? q->o->show() : q->o->hide(); + q->redraw(); + if (n && q->parent && q->parent->type_name()) { + if (q->parent->is_a(ID_Tabs)) { + ((Fl_Tabs *)q->o->parent())->value(q->o); + } else if (q->parent->is_a(ID_Wizard)) { + ((Fl_Wizard *)q->o->parent())->value(q->o); + } + } + } + } + if (mod) { + set_modflag(1); + redraw_browser(); + } + } +} + +void active_cb(Fl_Light_Button* i, void* v) { + if (v == LOAD) { + i->value(current_widget->o->active()); + if (current_widget->is_a(ID_Window)) i->deactivate(); + else i->activate(); + } else { + int mod = 0; + int n = i->value(); + for (Fl_Type *o = Fl_Type::first; o; o = o->next) { + if (o->selected && o->is_widget()) { + if (!mod) { + mod = 1; + undo_checkpoint(); + } + Fl_Widget_Type* q = (Fl_Widget_Type*)o; + n ? q->o->activate() : q->o->deactivate(); + q->redraw(); + } + } + if (mod) set_modflag(1); + } +} + +//////////////////////////////////////////////////////////////// + +Fl_Menu_Item fontmenu[] = { + {"Helvetica"}, + {"Helvetica bold"}, + {"Helvetica italic"}, + {"Helvetica bold italic"}, + {"Courier"}, + {"Courier bold"}, + {"Courier italic"}, + {"Courier bold italic"}, + {"Times"}, + {"Times bold"}, + {"Times italic"}, + {"Times bold italic"}, + {"Symbol"}, + {"Terminal"}, + {"Terminal Bold"}, + {"Zapf Dingbats"}, + {NULL} +}; + +Fl_Menu_Item fontmenu_w_default[] = { + {"", 0, NULL, NULL, FL_MENU_DIVIDER}, + {"Helvetica"}, + {"Helvetica bold"}, + {"Helvetica italic"}, + {"Helvetica bold italic"}, + {"Courier"}, + {"Courier bold"}, + {"Courier italic"}, + {"Courier bold italic"}, + {"Times"}, + {"Times bold"}, + {"Times italic"}, + {"Times bold italic"}, + {"Symbol"}, + {"Terminal"}, + {"Terminal Bold"}, + {"Zapf Dingbats"}, + {NULL} +}; + +void labelfont_cb(Fl_Choice* i, void *v) { + if (v == LOAD) { + int n = current_widget->o->labelfont(); + if (n > 15) n = 0; + i->value(n); + } else { + int mod = 0; + int n = i->value(); + if (n <= 0) n = layout->labelfont; + if (n <= 0) n = FL_HELVETICA; + for (Fl_Type *o = Fl_Type::first; o; o = o->next) { + if (o->selected && o->is_widget()) { + Fl_Widget_Type* q = (Fl_Widget_Type*)o; + q->o->labelfont(n); + q->redraw(); + mod = 1; + } + } + if (mod) set_modflag(1); + } +} + +void labelsize_cb(Fl_Value_Input* i, void *v) { + int n; + if (v == LOAD) { + n = current_widget->o->labelsize(); + } else { + int mod = 0; + n = int(i->value()); + if (n <= 0) n = layout->labelsize; + for (Fl_Type *o = Fl_Type::first; o; o = o->next) { + if (o->selected && o->is_widget()) { + Fl_Widget_Type* q = (Fl_Widget_Type*)o; + q->o->labelsize(n); + q->redraw(); + mod = 1; + } + } + if (mod) set_modflag(1); + } + i->value(n); +} + +extern const char *ui_find_image_name; + +Fl_Menu_Item labeltypemenu[] = { + {"NORMAL_LABEL",0,0,(void*)0}, + {"SHADOW_LABEL",0,0,(void*)FL_SHADOW_LABEL}, + {"ENGRAVED_LABEL",0,0,(void*)FL_ENGRAVED_LABEL}, + {"EMBOSSED_LABEL",0,0,(void*)FL_EMBOSSED_LABEL}, + {"NO_LABEL",0,0,(void*)(FL_NO_LABEL)}, +{0}}; + +void labeltype_cb(Fl_Choice* i, void *v) { + if (v == LOAD) { + int n; + n = current_widget->o->labeltype(); + i->when(FL_WHEN_RELEASE); + for (int j = 0; j < int(sizeof(labeltypemenu)/sizeof(*labeltypemenu)); j++) + if (labeltypemenu[j].argument() == n) {i->value(j); break;} + } else { + int mod = 0; + int m = i->value(); + int n = int(labeltypemenu[m].argument()); + if (n<0) return; // should not happen + for (Fl_Type *o = Fl_Type::first; o; o = o->next) { + if (o->selected && o->is_widget()) { + Fl_Widget_Type* p = (Fl_Widget_Type*)o; + p->o->labeltype((Fl_Labeltype)n); + p->redraw(); + mod = 1; + } + } + if (mod) set_modflag(1); + } +} + +//////////////////////////////////////////////////////////////// + +Fl_Menu_Item colormenu[] = { + { "Foreground Color", 0, 0, (void*)(fl_intptr_t)FL_FOREGROUND_COLOR, 0, 0, FL_HELVETICA, 11}, + { "Background Color", 0, 0, (void*)(fl_intptr_t)FL_BACKGROUND_COLOR, 0, 0, FL_HELVETICA, 11}, + { "Background Color 2", 0, 0, (void*)(fl_intptr_t)FL_BACKGROUND2_COLOR, 0, 0, FL_HELVETICA, 11}, + { "Selection Color", 0, 0, (void*)(fl_intptr_t)FL_SELECTION_COLOR, 0, 0, FL_HELVETICA, 11}, + { "Inactive Color", 0, 0, (void*)(fl_intptr_t)FL_INACTIVE_COLOR, FL_MENU_DIVIDER, 0, FL_HELVETICA, 11}, + { "Black", 0, 0, (void*)(fl_intptr_t)FL_BLACK, 0, 0, FL_HELVETICA, 11}, + { "White", 0, 0, (void*)(fl_intptr_t)FL_WHITE, FL_MENU_DIVIDER, 0, FL_HELVETICA, 11}, + { "Gray 0", 0, 0, (void*)(fl_intptr_t)FL_GRAY0, 0, 0, FL_HELVETICA, 11}, + { "Dark 3", 0, 0, (void*)(fl_intptr_t)FL_DARK3, 0, 0, FL_HELVETICA, 11}, + { "Dark 2", 0, 0, (void*)(fl_intptr_t)FL_DARK2, 0, 0, FL_HELVETICA, 11}, + { "Dark 1", 0, 0, (void*)(fl_intptr_t)FL_DARK1, 0, 0, FL_HELVETICA, 11}, + { "Light 1", 0, 0, (void*)(fl_intptr_t)FL_LIGHT1, 0, 0, FL_HELVETICA, 11}, + { "Light 2", 0, 0, (void*)(fl_intptr_t)FL_LIGHT2, 0, 0, FL_HELVETICA, 11}, + { "Light 3", 0, 0, (void*)(fl_intptr_t)FL_LIGHT3, 0, 0, FL_HELVETICA, 11}, + { 0 } +}; + +void color_common(Fl_Color c) { + int mod = 0; + for (Fl_Type *o = Fl_Type::first; o; o = o->next) { + if (o->selected && o->is_widget()) { + Fl_Widget_Type* q = (Fl_Widget_Type*)o; + q->o->color(c); q->o->redraw(); + if (q->parent && q->parent->is_a(ID_Tabs)) { + if (q->o->parent()) q->o->parent()->redraw(); + } + mod = 1; + } + } + if (mod) set_modflag(1); +} + +void color_cb(Fl_Button* i, void *v) { + Fl_Color c = current_widget->o->color(); + if (v == LOAD) { + if (current_widget->is_a(ID_Menu_Item)) {i->deactivate(); return;} else i->activate(); + } else { + Fl_Color d = fl_show_colormap(c); + if (d == c) return; + c = d; + color_common(c); + } + i->color(c); i->labelcolor(fl_contrast(FL_BLACK,c)); i->redraw(); +} + +void color_menu_cb(Fl_Menu_Button* i, void *v) { + Fl_Color c = current_widget->o->color(); + if (v == LOAD) { + if (current_widget->is_a(ID_Menu_Item)) {i->deactivate(); return;} else i->activate(); + } else { + Fl_Color d = (Fl_Color)(i->mvalue()->argument()); + if (d == c) return; + c = d; + color_common(c); + w_color->color(c); w_color->labelcolor(fl_contrast(FL_BLACK,c)); w_color->redraw(); + } +} + +void color2_common(Fl_Color c) { + int mod = 0; + for (Fl_Type *o = Fl_Type::first; o; o = o->next) { + if (o->selected && o->is_widget()) { + Fl_Widget_Type* q = (Fl_Widget_Type*)o; + q->o->selection_color(c); q->o->redraw(); + mod = 1; + } + } + if (mod) set_modflag(1); +} + +void color2_cb(Fl_Button* i, void *v) { + Fl_Color c = current_widget->o->selection_color(); + if (v == LOAD) { + if (current_widget->is_a(ID_Menu_Item)) {i->deactivate(); return;} else i->activate(); + } else { + Fl_Color d = fl_show_colormap(c); + if (d == c) return; + c = d; + color2_common(c); + } + i->color(c); i->labelcolor(fl_contrast(FL_BLACK,c)); i->redraw(); +} + +void color2_menu_cb(Fl_Menu_Button* i, void *v) { + Fl_Color c = current_widget->o->selection_color(); + if (v == LOAD) { + if (current_widget->is_a(ID_Menu_Item)) {i->deactivate(); return;} else i->activate(); + } else { + Fl_Color d = (Fl_Color)(i->mvalue()->argument()); + if (d == c) return; + c = d; + color2_common(c); + w_selectcolor->color(c); w_selectcolor->labelcolor(fl_contrast(FL_BLACK,c)); w_selectcolor->redraw(); + } +} + +void labelcolor_common(Fl_Color c) { + int mod = 0; + for (Fl_Type *o = Fl_Type::first; o; o = o->next) { + if (o->selected && o->is_widget()) { + Fl_Widget_Type* q = (Fl_Widget_Type*)o; + q->o->labelcolor(c); q->redraw(); + mod = 1; + } + } + if (mod) set_modflag(1); +} + +void labelcolor_cb(Fl_Button* i, void *v) { + Fl_Color c = current_widget->o->labelcolor(); + if (v != LOAD) { + Fl_Color d = fl_show_colormap(c); + if (d == c) return; + c = d; + labelcolor_common(c); + } + i->color(c); i->labelcolor(fl_contrast(FL_BLACK,c)); i->redraw(); +} + +void labelcolor_menu_cb(Fl_Menu_Button* i, void *v) { + Fl_Color c = current_widget->o->labelcolor(); + if (v != LOAD) { + Fl_Color d = (Fl_Color)(i->mvalue()->argument()); + if (d == c) return; + c = d; + labelcolor_common(c); + w_labelcolor->color(c); w_labelcolor->labelcolor(fl_contrast(FL_BLACK,c)); w_labelcolor->redraw(); + } +} + +static Fl_Button* relative(Fl_Widget* o, int i) { + Fl_Group* g = (Fl_Group*)(o->parent()); + return (Fl_Button*)(g->child(g->find(*o)+i)); +} + +static Fl_Menu_Item alignmenu[] = { + {"FL_ALIGN_CENTER",0,0,(void*)(fl_intptr_t)(FL_ALIGN_CENTER)}, + {"FL_ALIGN_TOP",0,0,(void*)(fl_intptr_t)(FL_ALIGN_TOP)}, + {"FL_ALIGN_BOTTOM",0,0,(void*)(fl_intptr_t)(FL_ALIGN_BOTTOM)}, + {"FL_ALIGN_LEFT",0,0,(void*)(fl_intptr_t)(FL_ALIGN_LEFT)}, + {"FL_ALIGN_RIGHT",0,0,(void*)(fl_intptr_t)(FL_ALIGN_RIGHT)}, + {"FL_ALIGN_INSIDE",0,0,(void*)(fl_intptr_t)(FL_ALIGN_INSIDE)}, + {"FL_ALIGN_CLIP",0,0,(void*)(fl_intptr_t)(FL_ALIGN_CLIP)}, + {"FL_ALIGN_WRAP",0,0,(void*)(fl_intptr_t)(FL_ALIGN_WRAP)}, + {"FL_ALIGN_TEXT_OVER_IMAGE",0,0,(void*)(fl_intptr_t)(FL_ALIGN_TEXT_OVER_IMAGE)}, + {"FL_ALIGN_TOP_LEFT",0,0,(void*)(fl_intptr_t)(FL_ALIGN_TOP_LEFT)}, + {"FL_ALIGN_TOP_RIGHT",0,0,(void*)(fl_intptr_t)(FL_ALIGN_TOP_RIGHT)}, + {"FL_ALIGN_BOTTOM_LEFT",0,0,(void*)(fl_intptr_t)(FL_ALIGN_BOTTOM_LEFT)}, + {"FL_ALIGN_BOTTOM_RIGHT",0,0,(void*)(fl_intptr_t)(FL_ALIGN_BOTTOM_RIGHT)}, + {"FL_ALIGN_LEFT_TOP",0,0,(void*)(fl_intptr_t)(FL_ALIGN_LEFT_TOP)}, + {"FL_ALIGN_RIGHT_TOP",0,0,(void*)(fl_intptr_t)(FL_ALIGN_RIGHT_TOP)}, + {"FL_ALIGN_LEFT_BOTTOM",0,0,(void*)(fl_intptr_t)(FL_ALIGN_LEFT_BOTTOM)}, + {"FL_ALIGN_RIGHT_BOTTOM",0,0,(void*)(fl_intptr_t)(FL_ALIGN_RIGHT_BOTTOM)}, +{0}}; + +void align_cb(Fl_Button* i, void *v) { + Fl_Align b = Fl_Align(fl_uintptr_t(i->user_data())); + if (v == LOAD) { + if (current_widget->is_a(ID_Menu_Item)) {i->deactivate(); return;} else i->activate(); + i->value(current_widget->o->align() & b); + } else { + int mod = 0; + undo_checkpoint(); + for (Fl_Type *o = Fl_Type::first; o; o = o->next) { + if (o->selected && o->is_widget()) { + Fl_Widget_Type* q = (Fl_Widget_Type*)o; + Fl_Align x = q->o->align(); + Fl_Align y; + if (i->value()) { + y = x | b; + if (b == FL_ALIGN_LEFT || b == FL_ALIGN_TOP) { + Fl_Button *b1 = relative(i,+1); + b1->clear(); + y = y & ~(b1->argument()); + } + if (b == FL_ALIGN_RIGHT || b == FL_ALIGN_BOTTOM) { + Fl_Button *b1 = relative(i,-1); + b1->clear(); + y = y & ~(b1->argument()); + } + } else { + y = x & ~b; + } + if (x != y) { + q->o->align(y); + q->redraw(); + mod = 1; + } + } + } + if (mod) set_modflag(1); + } +} + +void align_position_cb(Fl_Choice *i, void *v) { + if (v == LOAD) { + if (current_widget->is_a(ID_Menu_Item)) {i->deactivate(); return;} else i->activate(); + Fl_Menu_Item *mi = (Fl_Menu_Item*)i->menu(); + Fl_Align b = current_widget->o->align() & FL_ALIGN_POSITION_MASK; + for (;mi->text;mi++) { + if ((Fl_Align)(mi->argument())==b) + i->value(mi); + } + } else { + const Fl_Menu_Item *mi = i->menu() + i->value(); + Fl_Align b = Fl_Align(fl_uintptr_t(mi->user_data())); + int mod = 0; + undo_checkpoint(); + for (Fl_Type *o = Fl_Type::first; o; o = o->next) { + if (o->selected && o->is_widget()) { + Fl_Widget_Type* q = (Fl_Widget_Type*)o; + Fl_Align x = q->o->align(); + Fl_Align y = (x & ~FL_ALIGN_POSITION_MASK) | b; + if (x != y) { + q->o->align(y); + q->redraw(); + mod = 1; + } + } + } + if (mod) set_modflag(1); + } +} + +void align_text_image_cb(Fl_Choice *i, void *v) { + if (v == LOAD) { + if (current_widget->is_a(ID_Menu_Item)) {i->deactivate(); return;} else i->activate(); + Fl_Menu_Item *mi = (Fl_Menu_Item*)i->menu(); + Fl_Align b = current_widget->o->align() & FL_ALIGN_IMAGE_MASK; + for (;mi->text;mi++) { + if ((Fl_Align)(mi->argument())==b) + i->value(mi); + } + } else { + const Fl_Menu_Item *mi = i->menu() + i->value(); + Fl_Align b = Fl_Align(fl_uintptr_t(mi->user_data())); + int mod = 0; + undo_checkpoint(); + for (Fl_Type *o = Fl_Type::first; o; o = o->next) { + if (o->selected && o->is_widget()) { + Fl_Widget_Type* q = (Fl_Widget_Type*)o; + Fl_Align x = q->o->align(); + Fl_Align y = (x & ~FL_ALIGN_IMAGE_MASK) | b; + if (x != y) { + q->o->align(y); + q->redraw(); + mod = 1; + } + } + } + if (mod) set_modflag(1); + } +} + +//////////////////////////////////////////////////////////////// + +void callback_cb(CodeEditor* i, void *v) { + if (v == LOAD) { + const char *cbtext = current_widget->callback(); + i->buffer()->text( cbtext ? cbtext : "" ); + } else { + int mod = 0; + char *c = i->buffer()->text(); + const char *d = c_check(c); + if (d) { + fl_message("Error in callback: %s",d); + if (i->window()) i->window()->make_current(); + haderror = 1; + } + for (Fl_Type *o = Fl_Type::first; o; o = o->next) { + if (o->selected) { + o->callback(c); + mod = 1; + } + } + if (mod) set_modflag(1); + free(c); + } +} + +void comment_cb(Fl_Text_Editor* i, void *v) { + if (v == LOAD) { + const char *cmttext = current_widget->comment(); + i->buffer()->text( cmttext ? cmttext : "" ); + } else { + int mod = 0; + char *c = i->buffer()->text(); + for (Fl_Type *o = Fl_Type::first; o; o = o->next) { + if (o->selected) { + o->comment(c); + mod = 1; + } + } + if (mod) set_modflag(1); + free(c); + } +} + +void user_data_cb(Fl_Input *i, void *v) { + if (v == LOAD) { + i->value(current_widget->user_data()); + } else { + int mod = 0; + const char *c = i->value(); + const char *d = c_check(c); + if (d) {fl_message("Error in user_data: %s",d); haderror = 1; return;} + for (Fl_Type *o = Fl_Type::first; o; o = o->next) { + if (o->selected) { + o->user_data(c); + mod = 1; + } + } + if (mod) set_modflag(1); + } +} + +void user_data_type_cb(Fl_Input_Choice *i, void *v) { + static const char *dflt = "void*"; + if (v == LOAD) { + const char *c = current_widget->user_data_type(); + if (!c) c = dflt; + i->value(c); + } else { + int mod = 0; + const char *c = i->value(); + const char *d = c_check(c); + if (!*c) i->value(dflt); + else if (!strcmp(c,dflt)) c = 0; + if (!d) { + if (c && *c && c[strlen(c)-1] != '*' && strcmp(c,"long")) + d = "must be pointer or long"; + } + if (d) {fl_message("Error in type: %s",d); haderror = 1; return;} + for (Fl_Type *o = Fl_Type::first; o; o = o->next) { + if (o->selected) { + o->user_data_type(c); + mod = 1; + } + } + if (mod) set_modflag(1); + } +} + +// "v_attributes" let user type in random code for attribute settings: + +void v_input_cb(Fl_Input* i, void* v) { + int n = fl_int(i->user_data()); + if (v == LOAD) { + i->value(current_widget->extra_code(n)); + } else { + int mod = 0; + const char *c = i->value(); + const char *d = c_check(c&&c[0]=='#' ? c+1 : c); + if (d) {fl_message("Error in %s: %s",i->label(),d); haderror = 1; return;} + for (Fl_Type *o = Fl_Type::first; o; o = o->next) { + if (o->selected && o->is_widget()) { + Fl_Widget_Type *t = (Fl_Widget_Type*)o; + t->extra_code(n,c); + mod = 1; + } + } + if (mod) set_modflag(1); + } +} + +void subclass_cb(Fl_Input* i, void* v) { + if (v == LOAD) { + if (current_widget->is_a(ID_Menu_Item)) {i->deactivate(); return;} else i->activate(); + i->value(current_widget->subclass()); + } else { + int mod = 0; + const char *c = i->value(); + for (Fl_Type *o = Fl_Type::first; o; o = o->next) { + if (o->selected && o->is_widget()) { + Fl_Widget_Type *t = (Fl_Widget_Type*)o; + t->subclass(c); + mod = 1; + } + } + if (mod) set_modflag(1); + } +} + +//////////////////////////////////////////////////////////////// + +// textstuff: set textfont, textsize, textcolor attributes: + +// default widget returns 0 to indicate not-implemented: +// The first parameter specifies the operation: +// 0: get all values +// 1: set the text font +// 2: set the text size +// 3: set the text color +// 4: get all default values for this type +int Fl_Widget_Type::textstuff(int, Fl_Font&, int&, Fl_Color&) { + return 0; +} + +void textfont_cb(Fl_Choice* i, void* v) { + Fl_Font n; int s; Fl_Color c; + if (v == LOAD) { + if (!current_widget->textstuff(0,n,s,c)) {i->deactivate(); return;} + i->activate(); + if (n > 15) n = FL_HELVETICA; + i->value(n); + } else { + int mod = 0; + n = (Fl_Font)i->value(); + if (n <= 0) n = layout->textfont; + for (Fl_Type *o = Fl_Type::first; o; o = o->next) { + if (o->selected && o->is_widget()) { + Fl_Widget_Type* q = (Fl_Widget_Type*)o; + q->textstuff(1,n,s,c); + q->o->redraw(); + mod = 1; + } + } + if (mod) set_modflag(1); + } +} + +void textsize_cb(Fl_Value_Input* i, void* v) { + Fl_Font n; int s; Fl_Color c; + if (v == LOAD) { + if (!current_widget->textstuff(0,n,s,c)) {i->deactivate(); return;} + i->activate(); + } else { + int mod = 0; + s = int(i->value()); + if (s <= 0) s = layout->textsize; + if (s <= 0) s = layout->labelsize; + for (Fl_Type *o = Fl_Type::first; o; o = o->next) { + if (o->selected && o->is_widget()) { + Fl_Widget_Type* q = (Fl_Widget_Type*)o; + q->textstuff(2,n,s,c); + q->o->redraw(); + mod = 1; + } + } + if (mod) set_modflag(1); + } + i->value(s); +} + +void textcolor_common(Fl_Color c) { + Fl_Font n; int s; + int mod = 0; + for (Fl_Type *o = Fl_Type::first; o; o = o->next) { + if (o->selected && o->is_widget()) { + Fl_Widget_Type* q = (Fl_Widget_Type*)o; + q->textstuff(3,n,s,c); q->o->redraw(); + mod = 1; + } + } + if (mod) set_modflag(1); +} + +void textcolor_cb(Fl_Button* i, void* v) { + Fl_Font n; int s; Fl_Color c; + if (v == LOAD) { + if (!current_widget->textstuff(0,n,s,c)) {i->deactivate(); return;} + i->activate(); + } else { + c = i->color(); + Fl_Color d = fl_show_colormap(c); + if (d == c) return; + c = d; + textcolor_common(c); + } + i->color(c); i->labelcolor(fl_contrast(FL_BLACK,c)); i->redraw(); +} + +void textcolor_menu_cb(Fl_Menu_Button* i, void* v) { + Fl_Font n; int s; Fl_Color c; + if (v == LOAD) { + if (!current_widget->textstuff(0,n,s,c)) {i->deactivate(); return;} + i->activate(); + } else { + c = i->color(); + Fl_Color d = (Fl_Color)(i->mvalue()->argument()); + if (d == c) return; + c = d; + textcolor_common(c); + w_textcolor->color(c); w_textcolor->labelcolor(fl_contrast(FL_BLACK,c)); w_textcolor->redraw(); + } +} + +void image_spacing_cb(Fl_Value_Input* i, void* v) { + int s; + if (v == LOAD) { + if (!current_widget->is_true_widget()) { + i->deactivate(); + i->value(0); + } else { + i->activate(); + i->value(((Fl_Widget_Type*)current_widget)->o->label_image_spacing()); + } + } else { + int mod = 0; + s = int(i->value()); + for (Fl_Type *o = Fl_Type::first; o; o = o->next) { + if (o->selected && o->is_true_widget()) { + Fl_Widget_Type* q = (Fl_Widget_Type*)o; + if (q->o->label_image_spacing() != s) { + q->o->label_image_spacing(s); + if (!(q->o->align() & FL_ALIGN_INSIDE) && q->o->window()) + q->o->window()->damage(FL_DAMAGE_EXPOSE); // outside labels + q->o->redraw(); + mod = 1; + } + } + } + if (mod) set_modflag(1); + } +} + +void h_label_margin_cb(Fl_Value_Input* i, void* v) { + int s; + if (v == LOAD) { + if (!current_widget->is_true_widget()) { + i->deactivate(); + i->value(0); + } else { + i->activate(); + i->value(((Fl_Widget_Type*)current_widget)->o->horizontal_label_margin()); + } + } else { + int mod = 0; + s = int(i->value()); + for (Fl_Type *o = Fl_Type::first; o; o = o->next) { + if (o->selected && o->is_true_widget()) { + Fl_Widget_Type* q = (Fl_Widget_Type*)o; + if (q->o->horizontal_label_margin() != s) { + q->o->horizontal_label_margin(s); + if (!(q->o->align() & FL_ALIGN_INSIDE) && q->o->window()) + q->o->window()->damage(FL_DAMAGE_EXPOSE); // outside labels + q->o->redraw(); + mod = 1; + } + } + } + if (mod) set_modflag(1); + } +} + +void v_label_margin_cb(Fl_Value_Input* i, void* v) { + int s; + if (v == LOAD) { + if (!current_widget->is_true_widget()) { + i->deactivate(); + i->value(0); + } else { + i->activate(); + i->value(((Fl_Widget_Type*)current_widget)->o->vertical_label_margin()); + } + } else { + int mod = 0; + s = int(i->value()); + for (Fl_Type *o = Fl_Type::first; o; o = o->next) { + if (o->selected && o->is_true_widget()) { + Fl_Widget_Type* q = (Fl_Widget_Type*)o; + if (q->o->vertical_label_margin() != s) { + q->o->vertical_label_margin(s); + if (!(q->o->align() & FL_ALIGN_INSIDE) && q->o->window()) + q->o->window()->damage(FL_DAMAGE_EXPOSE); // outside labels + q->o->redraw(); + mod = 1; + } + } + } + if (mod) set_modflag(1); + } +} + +//////////////////////////////////////////////////////////////// +// Kludges to the panel for subclasses: + +void min_w_cb(Fl_Value_Input* i, void* v) { + if (v == LOAD) { + if (!current_widget->is_a(ID_Window)) return; + i->value(((Fl_Window_Type*)current_widget)->sr_min_w); + } else { + int mod = 0; + undo_checkpoint(); + int n = (int)i->value(); + for (Fl_Type *o = Fl_Type::first; o; o = o->next) { + if (o->selected && o->is_a(ID_Window)) { + ((Fl_Window_Type*)current_widget)->sr_min_w = n; + mod = 1; + } + } + if (mod) set_modflag(1); + } +} + +void min_h_cb(Fl_Value_Input* i, void* v) { + if (v == LOAD) { + if (!current_widget->is_a(ID_Window)) return; + i->value(((Fl_Window_Type*)current_widget)->sr_min_h); + } else { + int mod = 0; + undo_checkpoint(); + int n = (int)i->value(); + for (Fl_Type *o = Fl_Type::first; o; o = o->next) { + if (o->selected && o->is_a(ID_Window)) { + ((Fl_Window_Type*)current_widget)->sr_min_h = n; + mod = 1; + } + } + if (mod) set_modflag(1); + } +} + +void max_w_cb(Fl_Value_Input* i, void* v) { + if (v == LOAD) { + if (!current_widget->is_a(ID_Window)) return; + i->value(((Fl_Window_Type*)current_widget)->sr_max_w); + } else { + int mod = 0; + undo_checkpoint(); + int n = (int)i->value(); + for (Fl_Type *o = Fl_Type::first; o; o = o->next) { + if (o->selected && o->is_a(ID_Window)) { + ((Fl_Window_Type*)current_widget)->sr_max_w = n; + mod = 1; + } + } + if (mod) set_modflag(1); + } +} + +void max_h_cb(Fl_Value_Input* i, void* v) { + if (v == LOAD) { + if (!current_widget->is_a(ID_Window)) return; + i->value(((Fl_Window_Type*)current_widget)->sr_max_h); + } else { + int mod = 0; + undo_checkpoint(); + int n = (int)i->value(); + for (Fl_Type *o = Fl_Type::first; o; o = o->next) { + if (o->selected && o->is_a(ID_Window)) { + ((Fl_Window_Type*)current_widget)->sr_max_h = n; + mod = 1; + } + } + if (mod) set_modflag(1); + } +} + +void set_min_size_cb(Fl_Button*, void* v) { + if (v == LOAD) { + } else { + int mod = 0; + undo_checkpoint(); + for (Fl_Type *o = Fl_Type::first; o; o = o->next) { + if (o->selected && o->is_a(ID_Window)) { + Fl_Window_Type *win = (Fl_Window_Type*)current_widget; + win->sr_min_w = win->o->w(); + win->sr_min_h = win->o->h(); + mod = 1; + } + } + propagate_load(the_panel, LOAD); + if (mod) set_modflag(1); + } +} + +void set_max_size_cb(Fl_Button*, void* v) { + if (v == LOAD) { + } else { + int mod = 0; + undo_checkpoint(); + for (Fl_Type *o = Fl_Type::first; o; o = o->next) { + if (o->selected && o->is_a(ID_Window)) { + Fl_Window_Type *win = (Fl_Window_Type*)current_widget; + win->sr_max_w = win->o->w(); + win->sr_max_h = win->o->h(); + mod = 1; + } + } + propagate_load(the_panel, LOAD); + if (mod) set_modflag(1); + } +} + +void slider_size_cb(Fl_Value_Input* i, void* v) { + if (v == LOAD) { + if (!current_widget->is_a(ID_Slider)) {i->deactivate(); return;} + i->activate(); + i->value(((Fl_Slider*)(current_widget->o))->slider_size()); + } else { + int mod = 0; + undo_checkpoint(); + double n = i->value(); + for (Fl_Type *o = Fl_Type::first; o; o = o->next) { + if (o->selected && o->is_widget()) { + Fl_Widget_Type* q = (Fl_Widget_Type*)o; + if (q->is_a(ID_Slider)) { + ((Fl_Slider*)(q->o))->slider_size(n); + q->o->redraw(); + mod = 1; + } + } + } + if (mod) set_modflag(1); + } +} + +void min_cb(Fl_Value_Input* i, void* v) { + if (v == LOAD) { + if (current_widget->is_a(ID_Valuator_)) { + i->activate(); + i->value(((Fl_Valuator*)(current_widget->o))->minimum()); + } else if (current_widget->is_a(ID_Spinner)) { + i->activate(); + i->value(((Fl_Spinner*)(current_widget->o))->minimum()); + } else { + i->deactivate(); + return; + } + } else { + int mod = 0; + undo_checkpoint(); + double n = i->value(); + for (Fl_Type *o = Fl_Type::first; o; o = o->next) { + if (o->selected && o->is_widget()) { + Fl_Widget_Type* q = (Fl_Widget_Type*)o; + if (q->is_a(ID_Valuator_)) { + ((Fl_Valuator*)(q->o))->minimum(n); + q->o->redraw(); + mod = 1; + } else if (q->is_a(ID_Spinner)) { + ((Fl_Spinner*)(q->o))->minimum(n); + q->o->redraw(); + mod = 1; + } + } + } + if (mod) set_modflag(1); + } +} + +void max_cb(Fl_Value_Input* i, void* v) { + if (v == LOAD) { + if (current_widget->is_a(ID_Valuator_)) { + i->activate(); + i->value(((Fl_Valuator*)(current_widget->o))->maximum()); + } else if (current_widget->is_a(ID_Spinner)) { + i->activate(); + i->value(((Fl_Spinner*)(current_widget->o))->maximum()); + } else { + i->deactivate(); + return; + } + } else { + int mod = 0; + undo_checkpoint(); + double n = i->value(); + for (Fl_Type *o = Fl_Type::first; o; o = o->next) { + if (o->selected && o->is_widget()) { + Fl_Widget_Type* q = (Fl_Widget_Type*)o; + if (q->is_a(ID_Valuator_)) { + ((Fl_Valuator*)(q->o))->maximum(n); + q->o->redraw(); + mod = 1; + } else if (q->is_a(ID_Spinner)) { + ((Fl_Spinner*)(q->o))->maximum(n); + q->o->redraw(); + mod = 1; + } + } + } + if (mod) set_modflag(1); + } +} + +void step_cb(Fl_Value_Input* i, void* v) { + if (v == LOAD) { + if (current_widget->is_a(ID_Valuator_)) { + i->activate(); + i->value(((Fl_Valuator*)(current_widget->o))->step()); + } else if (current_widget->is_a(ID_Spinner)) { + i->activate(); + i->value(((Fl_Spinner*)(current_widget->o))->step()); + } else { + i->deactivate(); + return; + } + } else { + int mod = 0; + undo_checkpoint(); + double n = i->value(); + for (Fl_Type *o = Fl_Type::first; o; o = o->next) { + if (o->selected && o->is_widget()) { + Fl_Widget_Type* q = (Fl_Widget_Type*)o; + if (q->is_a(ID_Valuator_)) { + ((Fl_Valuator*)(q->o))->step(n); + q->o->redraw(); + mod = 1; + } else if (q->is_a(ID_Spinner)) { + ((Fl_Spinner*)(q->o))->step(n); + q->o->redraw(); + mod = 1; + } + } + } + if (mod) set_modflag(1); + } +} + +void value_cb(Fl_Value_Input* i, void* v) { + if (v == LOAD) { + if (current_widget->is_a(ID_Valuator_)) { + i->activate(); + i->value(((Fl_Valuator*)(current_widget->o))->value()); + } else if (current_widget->is_button()) { + i->activate(); + i->value(((Fl_Button*)(current_widget->o))->value()); + } else if (current_widget->is_a(ID_Spinner)) { + i->activate(); + i->value(((Fl_Spinner*)(current_widget->o))->value()); + } else + i->deactivate(); + } else { + int mod = 0; + undo_checkpoint(); + double n = i->value(); + for (Fl_Type *o = Fl_Type::first; o; o = o->next) { + if (o->selected && o->is_widget()) { + Fl_Widget_Type* q = (Fl_Widget_Type*)o; + if (q->is_a(ID_Valuator_)) { + ((Fl_Valuator*)(q->o))->value(n); + mod = 1; + } else if (q->is_button()) { + ((Fl_Button*)(q->o))->value(n != 0); + if (q->is_a(ID_Menu_Item)) q->redraw(); + mod = 1; + } else if (q->is_a(ID_Spinner)) { + ((Fl_Spinner*)(q->o))->value(n); + mod = 1; + } + } + } + if (mod) set_modflag(1); + } +} + +// The following three callbacks cooperate, showing only one of the groups of +// widgets that use the same space in the dialog. + +void values_group_cb(Fl_Group* g, void* v) { + if (v == LOAD) { + if ( current_widget->is_a(ID_Flex) + || current_widget->is_a(ID_Grid) + || current_widget->is_a(ID_Window)) + { + g->hide(); + } else { + g->show(); + } + propagate_load(g, v); + } +} + +void flex_margin_group_cb(Fl_Group* g, void* v) { + if (v == LOAD) { + if (current_widget->is_a(ID_Flex)) { + g->show(); + } else { + g->hide(); + } + propagate_load(g, v); + } +} + +void size_range_group_cb(Fl_Group* g, void* v) { + if (v == LOAD) { + if (current_widget->is_a(ID_Window)) { + g->show(); + } else { + g->hide(); + } + propagate_load(g, v); + } +} + + +static void flex_margin_cb(Fl_Value_Input* i, void* v, + void (*load_margin)(Fl_Flex*,Fl_Value_Input*), + int (*update_margin)(Fl_Flex*,int)) { + if (v == LOAD) { + if (current_widget->is_a(ID_Flex)) { + load_margin((Fl_Flex*)current_widget->o, i); + } + } else { + int mod = 0; + int new_value = (int)i->value(); + for (Fl_Type *o = Fl_Type::first; o; o = o->next) { + if (o->selected && o->is_a(ID_Flex)) { + Fl_Flex_Type* q = (Fl_Flex_Type*)o; + Fl_Flex* w = (Fl_Flex*)q->o; + if (update_margin(w, new_value)) { + w->layout(); + mod = 1; + } + } + } + if (mod) set_modflag(1); + } +} + +static void load_left_margin(Fl_Flex *w, Fl_Value_Input* i) +{ + int v; + w->margin(&v, NULL, NULL, NULL); + i->value((double)v); +} + +static int update_left_margin(Fl_Flex *w, int new_value) +{ + int l, t, r, b; + w->margin(&l, &t, &r, &b); + if (new_value!=l) { + w->margin(new_value, t, r, b); + return 1; + } else { + return 0; + } +} + +void flex_margin_left_cb(Fl_Value_Input* i, void* v) { + flex_margin_cb(i, v, load_left_margin, update_left_margin); +} + +static void load_top_margin(Fl_Flex *w, Fl_Value_Input* i) +{ + int v; + w->margin(NULL, &v, NULL, NULL); + i->value((double)v); +} + +static int update_top_margin(Fl_Flex *w, int new_value) +{ + int l, t, r, b; + w->margin(&l, &t, &r, &b); + if (new_value!=t) { + w->margin(l, new_value, r, b); + return 1; + } else { + return 0; + } +} + +void flex_margin_top_cb(Fl_Value_Input* i, void* v) { + flex_margin_cb(i, v, load_top_margin, update_top_margin); +} + +static void load_right_margin(Fl_Flex *w, Fl_Value_Input* i) +{ + int v; + w->margin(NULL, NULL, &v, NULL); + i->value((double)v); +} + +static int update_right_margin(Fl_Flex *w, int new_value) +{ + int l, t, r, b; + w->margin(&l, &t, &r, &b); + if (new_value!=r) { + w->margin(l, t, new_value, b); + return 1; + } else { + return 0; + } +} + +void flex_margin_right_cb(Fl_Value_Input* i, void* v) { + flex_margin_cb(i, v, load_right_margin, update_right_margin); +} + +static void load_bottom_margin(Fl_Flex *w, Fl_Value_Input* i) +{ + int v; + w->margin(NULL, NULL, NULL, &v); + i->value((double)v); +} + +static int update_bottom_margin(Fl_Flex *w, int new_value) +{ + int l, t, r, b; + w->margin(&l, &t, &r, &b); + if (new_value!=b) { + w->margin(l, t, r, new_value); + return 1; + } else { + return 0; + } +} + +void flex_margin_bottom_cb(Fl_Value_Input* i, void* v) { + flex_margin_cb(i, v, load_bottom_margin, update_bottom_margin); +} + +static void load_gap(Fl_Flex *w, Fl_Value_Input* i) +{ + int v = w->gap(); + i->value((double)v); +} + +static int update_gap(Fl_Flex *w, int new_value) +{ + int g = w->gap(); + if (new_value!=g) { + w->gap(new_value); + return 1; + } else { + return 0; + } +} + +void flex_margin_gap_cb(Fl_Value_Input* i, void* v) { + flex_margin_cb(i, v, load_gap, update_gap); +} + +void position_group_cb(Fl_Group* g, void* v) { + if (v == LOAD) { + if (Fl_Flex_Type::parent_is_flex(current_widget)) { + g->hide(); + } else { + g->show(); + } + } + propagate_load(g, v); +} + +void flex_size_group_cb(Fl_Group* g, void* v) { + if (v == LOAD) { + if (Fl_Flex_Type::parent_is_flex(current_widget)) { + g->show(); + } else { + g->hide(); + } + } + propagate_load(g, v); +} + +void flex_size_cb(Fl_Value_Input* i, void* v) { + if (v == LOAD) { + if (Fl_Flex_Type::parent_is_flex(current_widget)) { + i->value(Fl_Flex_Type::size(current_widget)); + } + } else { + int mod = 0; + int new_size = (int)i->value(); + for (Fl_Type *o = Fl_Type::first; o; o = o->next) { + if (o->selected && o->is_widget() && Fl_Flex_Type::parent_is_flex(o)) { + Fl_Widget* w = (Fl_Widget*)((Fl_Widget_Type*)o)->o; + Fl_Flex* f = (Fl_Flex*)((Fl_Flex_Type*)o->parent)->o; + int was_fixed = f->fixed(w); + if (new_size==0) { + if (was_fixed) { + f->fixed(w, 0); + f->layout(); + widget_flex_fixed->value(0); + mod = 1; + } + } else { + int old_size = Fl_Flex_Type::size(o); + if (old_size!=new_size || !was_fixed) { + f->fixed(w, new_size); + f->layout(); + widget_flex_fixed->value(1); + mod = 1; + } + } + } + } + if (mod) set_modflag(1); + } +} + +void flex_fixed_cb(Fl_Check_Button* i, void* v) { + if (v == LOAD) { + if (Fl_Flex_Type::parent_is_flex(current_widget)) { + i->value(Fl_Flex_Type::is_fixed(current_widget)); + } + } else { + int mod = 0; + int new_fixed = (int)i->value(); + for (Fl_Type *o = Fl_Type::first; o; o = o->next) { + if (o->selected && o->is_widget() && Fl_Flex_Type::parent_is_flex(o)) { + Fl_Widget* w = (Fl_Widget*)((Fl_Widget_Type*)o)->o; + Fl_Flex* f = (Fl_Flex*)((Fl_Flex_Type*)o->parent)->o; + int was_fixed = f->fixed(w); + if (new_fixed==0) { + if (was_fixed) { + f->fixed(w, 0); + f->layout(); + mod = 1; + } + } else { + if (!was_fixed) { + f->fixed(w, Fl_Flex_Type::size(o)); + f->layout(); + mod = 1; + } + } + } + } + if (mod) set_modflag(1); + } +} + +//////////////////////////////////////////////////////////////// + +// subtypes: + +Fl_Menu_Item *Fl_Widget_Type::subtypes() {return 0;} + +void subtype_cb(Fl_Choice* i, void* v) { + static Fl_Menu_Item empty_type_menu[] = { + {"Normal",0,0,(void*)0}, + {0}}; + + if (v == LOAD) { + Fl_Menu_Item* m = current_widget->subtypes(); + if (!m) { + i->menu(empty_type_menu); + i->value(0); + i->deactivate(); + } else { + i->menu(m); + int j; + for (j = 0;; j++) { + if (!m[j].text) {j = 0; break;} + if (current_widget->is_a(ID_Spinner)) { + if (m[j].argument() == ((Fl_Spinner*)current_widget->o)->type()) break; + } else { + if (m[j].argument() == current_widget->o->type()) break; + } + } + i->value(j); + i->activate(); + } + i->redraw(); + } else { + int mod = 0; + int n = int(i->mvalue()->argument()); + Fl_Menu_Item* m = current_widget->subtypes(); + for (Fl_Type *o = Fl_Type::first; o; o = o->next) { + if (o->selected && o->is_widget()) { + Fl_Widget_Type* q = (Fl_Widget_Type*)o; + if (q->subtypes()==m) { + if (q->is_a(ID_Spinner)) + ((Fl_Spinner*)q->o)->type(n); + else if (q->is_a(ID_Flex)) + ((Fl_Flex_Type*)q)->change_subtype_to(n); + else + q->o->type(n); + q->redraw(); + mod = 1; + } + } + } + if (mod) set_modflag(1); + } +} + +//////////////////////////////////////////////////////////////// + +void propagate_load(Fl_Group* g, void* v) { + if (v == LOAD) { + Fl_Widget*const* a = g->array(); + for (int i=g->children(); i--;) { + Fl_Widget* o = *a++; + o->do_callback(o, LOAD, FL_REASON_USER); + } + } +} + +void set_cb(Fl_Button*, void*) { + haderror = 0; + Fl_Widget*const* a = the_panel->array(); + for (int i=the_panel->children(); i--;) { + Fl_Widget* o = *a++; + if (o->changed()) { + o->do_callback(); + if (haderror) return; + o->clear_changed(); + } + } +} + +void ok_cb(Fl_Return_Button* o, void* v) { + set_cb(o,v); + if (!haderror) the_panel->hide(); +} + +void toggle_overlays(Fl_Widget *,void *); // in Fl_Window_Type.cxx +void overlay_cb(Fl_Button*o,void *v) { + toggle_overlays(o,v); +} + +void leave_live_mode_cb(Fl_Widget*, void*); + +void live_mode_cb(Fl_Button*o,void *) { + /// \todo live mode should end gracefully when the application quits + /// or when the user closes the live widget + static Fl_Type *live_type = 0L; + static Fl_Widget *live_widget = 0L; + static Fl_Window *live_window = 0L; + // if 'o' is 0, we must quit live mode + if (!o) { + o = wLiveMode; + o->value(0); + } + if (o->value()) { + if (numselected == 1) { + Fl_Group::current(0L); + live_widget = current_widget->enter_live_mode(1); + if (live_widget) { + live_type = current_widget; + Fl_Group::current(0); + int w = live_widget->w(); + int h = live_widget->h(); + live_window = new Fl_Double_Window(w+20, h+55, "Fluid Live Resize"); + live_window->box(FL_FLAT_BOX); + live_window->color(FL_GREEN); + Fl_Group *rsz = new Fl_Group(0, h+20, 130, 35); + rsz->box(FL_NO_BOX); + Fl_Box *rsz_dummy = new Fl_Box(110, h+20, 1, 25); + rsz_dummy->box(FL_NO_BOX); + rsz->resizable(rsz_dummy); + Fl_Button *btn = new Fl_Button(10, h+20, 100, 25, "Exit Live Resize"); + btn->labelsize(12); + btn->callback(leave_live_mode_cb); + rsz->end(); + live_window->add(live_widget); + live_widget->position(10, 10); + live_window->resizable(live_widget); + live_window->set_modal(); // block all other UI + live_window->callback(leave_live_mode_cb); + if (current_widget->is_a(ID_Window)) { + Fl_Window_Type *w = (Fl_Window_Type*)current_widget; + int mw = w->sr_min_w; if (mw>0) mw += 20; + int mh = w->sr_min_h; if (mh>0) mh += 55; + int MW = w->sr_max_w; if (MW>0) MW += 20; + int MH = w->sr_max_h; if (MH>2) MH += 55; + if (mw || mh || MW || MH) + live_window->size_range(mw, mh, MW, MH); + } + live_window->show(); + live_widget->show(); + } else o->value(0); + } else o->value(0); + } else { + if (live_type) + live_type->leave_live_mode(); + if (live_window) { + live_window->hide(); + Fl::delete_widget(live_window); + } + live_type = 0L; + live_widget = 0L; + live_window = 0L; + } +} + +// update the panel according to current widget set: +void load_panel() { + if (!the_panel) return; + + // find all the Fl_Widget subclasses currently selected: + numselected = 0; + current_widget = 0; + if (Fl_Type::current) { + if (Fl_Type::current->is_widget()) + current_widget=(Fl_Widget_Type*)Fl_Type::current; + for (Fl_Type *o = Fl_Type::first; o; o = o->next) { + if (o->is_widget() && o->selected) { + numselected++; + if (!current_widget) current_widget = (Fl_Widget_Type*)o; + } + } + } + if (current_widget && current_widget->is_a(ID_Grid)) { + if (widget_tab_grid->parent()!=widget_tabs) + widget_tabs->add(widget_tab_grid); + } else { + if (widget_tab_grid->parent()==widget_tabs) { + widget_tabs_repo->add(widget_tab_grid); + } + } + if (current_widget && current_widget->parent && current_widget->parent->is_a(ID_Grid)) { + if (widget_tab_grid_child->parent()!=widget_tabs) + widget_tabs->add(widget_tab_grid_child); + } else { + if (widget_tab_grid_child->parent()==widget_tabs) { + widget_tabs_repo->add(widget_tab_grid_child); + } + } + if (numselected) + propagate_load(the_panel, LOAD); + else + the_panel->hide(); +} + +extern Fl_Window *widgetbin_panel; + +// This is called when user double-clicks an item, open or update the panel: +void Fl_Widget_Type::open() { + bool adjust_position = false; + if (!the_panel) { + the_panel = make_widget_panel(); + adjust_position = true; + } + load_panel(); + if (numselected) { + the_panel->show(); + if (adjust_position) { + if (widgetbin_panel && widgetbin_panel->visible()) { + if ( (the_panel->x()+the_panel->w() > widgetbin_panel->x()) + && (the_panel->x() < widgetbin_panel->x()+widgetbin_panel->w()) + && (the_panel->y()+the_panel->h() > widgetbin_panel->y()) + && (the_panel->y() < widgetbin_panel->y()+widgetbin_panel->h()) ) + { + if (widgetbin_panel->y()+widgetbin_panel->h()+the_panel->h() > Fl::h()) + the_panel->position(the_panel->x(), widgetbin_panel->y()-the_panel->h()-30); + else + the_panel->position(the_panel->x(), widgetbin_panel->y()+widgetbin_panel->h()+30); + } + } + } + } +} + +extern void redraw_overlays(); +extern void check_redraw_corresponding_parent(Fl_Type*); +extern void redraw_browser(); +extern void update_codeview_position(); + +// Called when ui changes what objects are selected: +// p is selected object, null for all deletions (we must throw away +// old panel in that case, as the object may no longer exist) +void selection_changed(Fl_Type *p) { + // store all changes to the current selected objects: + if (p && the_panel && the_panel->visible()) { + set_cb(0,0); + // if there was an error, we try to leave the selected set unchanged: + if (haderror) { + Fl_Type *q = 0; + for (Fl_Type *o = Fl_Type::first; o; o = o->next) { + o->new_selected = o->selected; + if (!q && o->selected) q = o; + } + if (!p || !p->selected) p = q; + Fl_Type::current = p; + redraw_browser(); + return; + } + } + // update the selected flags to new set: + Fl_Type *q = 0; + for (Fl_Type *o = Fl_Type::first; o; o = o->next) { + o->selected = o->new_selected; + if (!q && o->selected) q = o; + } + if (!p || !p->selected) p = q; + Fl_Type::current = p; + check_redraw_corresponding_parent(p); + redraw_overlays(); + // load the panel with the new settings: + load_panel(); + // update the code viewer to show the code for the selected object + update_codeview_position(); +} + +//////////////////////////////////////////////////////////////// +// Writing the C code: + +// test to see if user named a function, or typed in code: +int is_name(const char *c) { + for (; *c; c++) + if ((ispunct(*c)||*c=='\n') && *c!='_' && *c!=':') return 0; + return 1; +} + +// Test to see if name() is an array entry. If so, and this is the +// highest number, return name[num+1]. Return null if not the highest +// number or a field or function. Return name() if not an array entry. +const char *array_name(Fl_Widget_Type *o) { + const char *c = o->name(); + if (!c) return 0; + const char *d; + for (d = c; *d != '['; d++) { + if (!*d) return c; + if (ispunct(*d) && *d!='_') return 0; + } + int num = atoi(d+1); + int sawthis = 0; + Fl_Type *t = o->prev; + Fl_Type *tp = o; + const char *cn = o->class_name(1); + for (; t && t->class_name(1) == cn; tp = t, t = t->prev) {/*empty*/} + for (t = tp; t && t->class_name(1) == cn; t = t->next) { + if (t == o) {sawthis=1; continue;} + const char *e = t->name(); + if (!e) continue; + if (strncmp(c,e,d-c)) continue; + int n1 = atoi(e+(d-c)+1); + if (n1 > num || (n1==num && sawthis)) return 0; + } + static char buffer[128]; + // MRS: we want strncpy() here... + strncpy(buffer,c,d-c+1); + snprintf(buffer+(d-c+1),sizeof(buffer) - (d-c+1), "%d]",num+1); + return buffer; +} + +// Test to see if extra code is a declaration: +int isdeclare(const char *c) { + while (isspace(*c)) c++; + if (*c == '#') return 1; + if (!strncmp(c,"extern",6)) return 1; + if (!strncmp(c,"typedef",7)) return 1; + if (!strncmp(c,"using",5)) return 1; + return 0; +} + +void Fl_Widget_Type::write_static(Fd_Code_Writer& f) { + const char* t = subclassname(this); + if (!subclass() || (is_class() && !strncmp(t, "Fl_", 3))) { + f.write_h_once("#include "); + f.write_h_once("#include ", t); + } + for (int n=0; n < NUM_EXTRA_CODE; n++) { + if (extra_code(n) && isdeclare(extra_code(n))) + f.write_h_once("%s", extra_code(n)); + } + if (callback() && is_name(callback())) { + int write_extern_declaration = 1; + char buf[1024]; snprintf(buf, 1023, "%s(*)", callback()); + if (is_in_class()) { + if (has_function("static void", buf)) + write_extern_declaration = 0; + } else { + if (has_toplevel_function(0L, buf)) + write_extern_declaration = 0; + } + if (write_extern_declaration) + f.write_h_once("extern void %s(%s*, %s);", callback(), t, + user_data_type() ? user_data_type() : "void*"); + } + const char* k = class_name(1); + const char* c = array_name(this); + if (c && !k && !is_class()) { + f.write_c("\n"); + if (!public_) f.write_c("static "); + else f.write_h("extern %s *%s;\n", t, c); + if (strchr(c, '[') == NULL) f.write_c("%s *%s=(%s *)0;\n", t, c, t); + else f.write_c("%s *%s={(%s *)0};\n", t, c, t); + } + if (callback() && !is_name(callback())) { + // see if 'o' or 'v' used, to prevent unused argument warnings: + int use_o = 0; + int use_v = 0; + const char *d; + for (d = callback(); *d;) { + if (*d == 'o' && !is_id(d[1])) use_o = 1; + if (*d == 'v' && !is_id(d[1])) use_v = 1; + do d++; while (is_id(*d)); + while (*d && !is_id(*d)) d++; + } + const char* cn = callback_name(f); + if (k) { + f.write_c("\nvoid %s::%s_i(%s*", k, cn, t); + } else { + f.write_c("\nstatic void %s(%s*", cn, t); + } + if (use_o) f.write_c(" o"); + const char* ut = user_data_type() ? user_data_type() : "void*"; + f.write_c(", %s", ut); + if (use_v) f.write_c(" v"); + f.write_c(") {\n"); + // Matt: disabled f.tag(FD_TAG_GENERIC, 0); + f.write_c_indented(callback(), 1, 0); + if (*(d-1) != ';' && *(d-1) != '}') { + const char *p = strrchr(callback(), '\n'); + if (p) p ++; + else p = callback(); + // Only add trailing semicolon if the last line is not a preprocessor + // statement... + if (*p != '#' && *p) f.write_c(";"); + } + f.write_c("\n"); + // Matt: disabled f.tag(FD_TAG_WIDGET_CALLBACK, get_uid()); + f.write_c("}\n"); + if (k) { + f.write_c("void %s::%s(%s* o, %s v) {\n", k, cn, t, ut); + f.write_c("%s((%s*)(o", f.indent(1), k); + Fl_Type *q = 0; + for (Fl_Type* p = parent; p && p->is_widget(); q = p, p = p->parent) + f.write_c("->parent()"); + if (!q || !q->is_a(ID_Widget_Class)) + f.write_c("->user_data()"); + f.write_c("))->%s_i(o,v);\n}\n", cn); + } + } + if (image) { + if (!f.c_contains(image)) + image->write_static(f, compress_image_); + } + if (inactive) { + if (!f.c_contains(inactive)) + inactive->write_static(f, compress_deimage_); + } +} + +void Fl_Widget_Type::write_code1(Fd_Code_Writer& f) { + const char* t = subclassname(this); + const char *c = array_name(this); + if (c) { + if (class_name(1)) { + f.write_public(public_); + f.write_h("%s%s *%s;\n", f.indent(1), t, c); + } + } + if (class_name(1) && callback() && !is_name(callback())) { + const char* cn = callback_name(f); + const char* ut = user_data_type() ? user_data_type() : "void*"; + f.write_public(0); + f.write_h("%sinline void %s_i(%s*, %s);\n", f.indent(1), cn, t, ut); + f.write_h("%sstatic void %s(%s*, %s);\n", f.indent(1), cn, t, ut); + } + // figure out if local variable will be used (prevent compiler warnings): + int wused = !name() && is_a(ID_Window); + const char *ptr; + + f.varused = wused; + + if (!name() && !f.varused) { + f.varused |= can_have_children(); + + if (!f.varused) { + f.varused_test = 1; + write_widget_code(f); + f.varused_test = 0; + } + } + + if (!f.varused) { + for (int n=0; n < NUM_EXTRA_CODE; n++) + if (extra_code(n) && !isdeclare(extra_code(n))) + { + int instring = 0; + int inname = 0; + int incomment = 0; + int incppcomment = 0; + for (ptr = extra_code(n); *ptr; ptr ++) { + if (instring) { + if (*ptr == '\\') ptr++; + else if (*ptr == '\"') instring = 0; + } else if (inname && !isalnum(*ptr & 255)) { + inname = 0; + } else if (*ptr == '/' && ptr[1]=='*') { + incomment = 1; ptr++; + } else if (incomment) { + if (*ptr == '*' && ptr[1]=='/') { + incomment = 0; ptr++; + } + } else if (*ptr == '/' && ptr[1]=='/') { + incppcomment = 1; ptr++; + } else if (incppcomment) { + if (*ptr == '\n') + incppcomment = 0; + } else if (*ptr == '\"') { + instring = 1; + } else if (isalnum(*ptr & 255) || *ptr == '_') { + size_t len = strspn(ptr, "0123456789_" + "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ"); + if (!strncmp(ptr, "o", len)) { + f.varused = 1; + break; + } else { + ptr += len - 1; + } + } + } + } + } + + f.write_c("%s{ ", f.indent()); + write_comment_inline_c(f); + if (f.varused) f.write_c("%s* o = ", t); + if (name()) f.write_c("%s = ", name()); + if (is_a(ID_Window)) { + // Handle special case where user is faking a Fl_Group type as a window, + // there is no 2-argument constructor in that case: + if (!strstr(t, "Window")) + f.write_c("new %s(0, 0, %d, %d", t, o->w(), o->h()); + else + f.write_c("new %s(%d, %d", t, o->w(), o->h()); + } else if (is_a(ID_Menu_Bar) + && ((Fl_Menu_Bar_Type*)this)->is_sys_menu_bar() + && is_in_class()) { + f.write_c("(%s*)new %s(%d, %d, %d, %d", + t, ((Fl_Menu_Bar_Type*)this)->sys_menubar_proxy_name(), + o->x(), o->y(), o->w(), o->h()); + } else { + f.write_c("new %s(%d, %d, %d, %d", t, o->x(), o->y(), o->w(), o->h()); + } + if (label() && *label()) { + f.write_c(", "); + switch (g_project.i18n_type) { + case FD_I18N_NONE : /* None */ + f.write_cstring(label()); + break; + case FD_I18N_GNU : /* GNU gettext */ + f.write_c("%s(", g_project.i18n_gnu_function.c_str()); + f.write_cstring(label()); + f.write_c(")"); + break; + case FD_I18N_POSIX : /* POSIX catgets */ + f.write_c("catgets(%s,%s,%d,", + g_project.i18n_pos_file.empty() ? "_catalog" : g_project.i18n_pos_file.c_str(), + g_project.i18n_pos_set.c_str(), msgnum()); + f.write_cstring(label()); + f.write_c(")"); + break; + } + } + f.write_c(");\n"); + + f.indentation++; + + // Avoid compiler warning for unused variable. + // Also avoid quality control warnings about incorrect allocation error handling. + if (wused) f.write_c("%sw = o; (void)w;\n", f.indent()); + + write_widget_code(f); +} + +void Fl_Widget_Type::write_color(Fd_Code_Writer& f, const char* field, Fl_Color color) { + const char* color_name = 0; + switch (color) { + case FL_FOREGROUND_COLOR: color_name = "FL_FOREGROUND_COLOR"; break; + case FL_BACKGROUND2_COLOR: color_name = "FL_BACKGROUND2_COLOR"; break; + case FL_INACTIVE_COLOR: color_name = "FL_INACTIVE_COLOR"; break; + case FL_SELECTION_COLOR: color_name = "FL_SELECTION_COLOR"; break; + case FL_GRAY0: color_name = "FL_GRAY0"; break; + case FL_DARK3: color_name = "FL_DARK3"; break; + case FL_DARK2: color_name = "FL_DARK2"; break; + case FL_DARK1: color_name = "FL_DARK1"; break; + case FL_BACKGROUND_COLOR: color_name = "FL_BACKGROUND_COLOR"; break; + case FL_LIGHT1: color_name = "FL_LIGHT1"; break; + case FL_LIGHT2: color_name = "FL_LIGHT2"; break; + case FL_LIGHT3: color_name = "FL_LIGHT3"; break; + case FL_BLACK: color_name = "FL_BLACK"; break; + case FL_RED: color_name = "FL_RED"; break; + case FL_GREEN: color_name = "FL_GREEN"; break; + case FL_YELLOW: color_name = "FL_YELLOW"; break; + case FL_BLUE: color_name = "FL_BLUE"; break; + case FL_MAGENTA: color_name = "FL_MAGENTA"; break; + case FL_CYAN: color_name = "FL_CYAN"; break; + case FL_DARK_RED: color_name = "FL_DARK_RED"; break; + case FL_DARK_GREEN: color_name = "FL_DARK_GREEN"; break; + case FL_DARK_YELLOW: color_name = "FL_DARK_YELLOW"; break; + case FL_DARK_BLUE: color_name = "FL_DARK_BLUE"; break; + case FL_DARK_MAGENTA: color_name = "FL_DARK_MAGENTA"; break; + case FL_DARK_CYAN: color_name = "FL_DARK_CYAN"; break; + case FL_WHITE: color_name = "FL_WHITE"; break; + } + const char *var = is_class() ? "this" : name() ? name() : "o"; + if (color_name) { + f.write_c("%s%s->%s(%s);\n", f.indent(), var, field, color_name); + } else { + f.write_c("%s%s->%s((Fl_Color)%d);\n", f.indent(), var, field, color); + } +} + +// this is split from write_code1(Fd_Code_Writer& f) for Fl_Window_Type: +void Fl_Widget_Type::write_widget_code(Fd_Code_Writer& f) { + Fl_Widget* tplate = ((Fl_Widget_Type*)factory)->o; + const char *var = is_class() ? "this" : name() ? name() : "o"; + + if (tooltip() && *tooltip()) { + f.write_c("%s%s->tooltip(",f.indent(), var); + switch (g_project.i18n_type) { + case FD_I18N_NONE : /* None */ + f.write_cstring(tooltip()); + break; + case FD_I18N_GNU : /* GNU gettext */ + f.write_c("%s(", g_project.i18n_gnu_function.c_str()); + f.write_cstring(tooltip()); + f.write_c(")"); + break; + case FD_I18N_POSIX : /* POSIX catgets */ + f.write_c("catgets(%s,%s,%d,", + g_project.i18n_pos_file.empty() ? "_catalog" : g_project.i18n_pos_file.c_str(), + g_project.i18n_pos_set.c_str(), + msgnum() + 1); + f.write_cstring(tooltip()); + f.write_c(")"); + break; + } + f.write_c(");\n"); + } + + if (is_a(ID_Spinner) && ((Fl_Spinner*)o)->type() != ((Fl_Spinner*)tplate)->type()) + f.write_c("%s%s->type(%d);\n", f.indent(), var, ((Fl_Spinner*)o)->type()); + else if (o->type() != tplate->type() && !is_a(ID_Window)) + f.write_c("%s%s->type(%d);\n", f.indent(), var, o->type()); + if (o->box() != tplate->box() || subclass()) + f.write_c("%s%s->box(FL_%s);\n", f.indent(), var, boxname(o->box())); + + // write shortcut command if needed + int shortcut = 0; + if (is_button()) shortcut = ((Fl_Button*)o)->shortcut(); + else if (is_a(ID_Input)) shortcut = ((Fl_Input_*)o)->shortcut(); + else if (is_a(ID_Value_Input)) shortcut = ((Fl_Value_Input*)o)->shortcut(); + else if (is_a(ID_Text_Display)) shortcut = ((Fl_Text_Display*)o)->shortcut(); + if (shortcut) { + int s = shortcut; + f.write_c("%s%s->shortcut(", f.indent(), var); + if (g_project.use_FL_COMMAND) { + if (s & FL_CTRL) { f.write_c("FL_CONTROL|"); s &= ~FL_CTRL; } + if (s & FL_META) { f.write_c("FL_COMMAND|"); s &= ~FL_META; } + } else { + if (s & FL_CTRL) { f.write_c("FL_CTRL|"); s &= ~FL_CTRL; } + if (s & FL_META) { f.write_c("FL_META|"); s &= ~FL_META; } + } + if (s & FL_SHIFT) { f.write_c("FL_SHIFT|"); s &= ~FL_SHIFT; } + if (s & FL_ALT) { f.write_c("FL_ALT|"); s &= ~FL_ALT; } + if ((s < 127) && isprint(s)) + f.write_c("'%c');\n", s); + else + f.write_c("0x%x);\n", s); + } + + if (is_a(ID_Button)) { + Fl_Button* b = (Fl_Button*)o; + if (b->down_box()) f.write_c("%s%s->down_box(FL_%s);\n", f.indent(), var, + boxname(b->down_box())); + if (b->value()) f.write_c("%s%s->value(1);\n", f.indent(), var); + if (b->compact()) f.write_c("%s%s->compact(%d);\n", f.indent(), var, b->compact()); + } else if (is_a(ID_Input_Choice)) { + Fl_Input_Choice* b = (Fl_Input_Choice*)o; + if (b->down_box()) f.write_c("%s%s->down_box(FL_%s);\n", f.indent(), var, + boxname(b->down_box())); + } else if (is_a(ID_Menu_Manager_)) { + Fl_Menu_* b = (Fl_Menu_*)o; + if (b->down_box()) f.write_c("%s%s->down_box(FL_%s);\n", f.indent(), var, + boxname(b->down_box())); + } + if (o->color() != tplate->color() || subclass()) + write_color(f, "color", o->color()); + if (o->selection_color() != tplate->selection_color() || subclass()) + write_color(f, "selection_color", o->selection_color()); + if (image) { + image->write_code(f, bind_image_, var); + if (scale_image_w_ || scale_image_h_) { + f.write_c("%s%s->image()->scale(", f.indent(), var); + if (scale_image_w_>0) + f.write_c("%d, ", scale_image_w_); + else + f.write_c("%s->image()->data_w(), ", var); + if (scale_image_h_>0) + f.write_c("%d, 0, 1);\n", scale_image_h_); + else + f.write_c("%s->image()->data_h(), 0, 1);\n", var); + } + } + if (inactive) { + inactive->write_code(f, bind_deimage_, var, 1); + if (scale_deimage_w_ || scale_deimage_h_) { + f.write_c("%s%s->deimage()->scale(", f.indent(), var); + if (scale_deimage_w_>0) + f.write_c("%d, ", scale_deimage_w_); + else + f.write_c("%s->deimage()->data_w(), ", var); + if (scale_deimage_h_>0) + f.write_c("%d, 0, 1);\n", scale_deimage_h_); + else + f.write_c("%s->deimage()->data_h(), 0, 1);\n", var); + } + } + if (o->labeltype() != tplate->labeltype() || subclass()) + f.write_c("%s%s->labeltype(FL_%s);\n", f.indent(), var, + item_name(labeltypemenu, o->labeltype())); + if (o->labelfont() != tplate->labelfont() || subclass()) + f.write_c("%s%s->labelfont(%d);\n", f.indent(), var, o->labelfont()); + if (o->labelsize() != tplate->labelsize() || subclass()) + f.write_c("%s%s->labelsize(%d);\n", f.indent(), var, o->labelsize()); + if (o->labelcolor() != tplate->labelcolor() || subclass()) + write_color(f, "labelcolor", o->labelcolor()); + if (o->horizontal_label_margin() != tplate->horizontal_label_margin()) + f.write_c("%s%s->horizontal_label_margin(%d);\n", f.indent(), var, o->horizontal_label_margin()); + if (o->vertical_label_margin() != tplate->vertical_label_margin()) + f.write_c("%s%s->vertical_label_margin(%d);\n", f.indent(), var, o->vertical_label_margin()); + if (o->label_image_spacing() != tplate->label_image_spacing()) + f.write_c("%s%s->label_image_spacing(%d);\n", f.indent(), var, o->label_image_spacing()); + if (is_a(ID_Valuator_)) { + Fl_Valuator* v = (Fl_Valuator*)o; + Fl_Valuator* t = (Fl_Valuator*)(tplate); + if (v->minimum()!=t->minimum()) + f.write_c("%s%s->minimum(%g);\n", f.indent(), var, v->minimum()); + if (v->maximum()!=t->maximum()) + f.write_c("%s%s->maximum(%g);\n", f.indent(), var, v->maximum()); + if (v->step()!=t->step()) + f.write_c("%s%s->step(%g);\n", f.indent(), var, v->step()); + if (v->value()) { + if (is_a(ID_Scrollbar)) { // Fl_Scrollbar::value(double) is not available + f.write_c("%s%s->Fl_Slider::value(%g);\n", f.indent(), var, v->value()); + } else { + f.write_c("%s%s->value(%g);\n", f.indent(), var, v->value()); + } + } + if (is_a(ID_Slider)) { + double x = ((Fl_Slider*)v)->slider_size(); + double y = ((Fl_Slider*)t)->slider_size(); + if (x != y) f.write_c("%s%s->slider_size(%g);\n", f.indent(), var, x); + } + } + if (is_a(ID_Spinner)) { + Fl_Spinner* v = (Fl_Spinner*)o; + Fl_Spinner* t = (Fl_Spinner*)(tplate); + if (v->minimum()!=t->minimum()) + f.write_c("%s%s->minimum(%g);\n", f.indent(), var, v->minimum()); + if (v->maximum()!=t->maximum()) + f.write_c("%s%s->maximum(%g);\n", f.indent(), var, v->maximum()); + if (v->step()!=t->step()) + f.write_c("%s%s->step(%g);\n", f.indent(), var, v->step()); + if (v->value()!=1.0f) + f.write_c("%s%s->value(%g);\n", f.indent(), var, v->value()); + } + + {Fl_Font ff; int fs; Fl_Color fc; if (textstuff(4,ff,fs,fc)) { + Fl_Font g; int s; Fl_Color c; textstuff(0,g,s,c); + if (g != ff) f.write_c("%s%s->textfont(%d);\n", f.indent(), var, g); + if (s != fs) f.write_c("%s%s->textsize(%d);\n", f.indent(), var, s); + if (c != fc) write_color(f, "textcolor", c); + }} + const char* ud = user_data(); + if (class_name(1) && !parent->is_widget()) ud = "this"; + if (callback()) { + f.write_c("%s%s->callback((Fl_Callback*)%s", f.indent(), var, callback_name(f)); + if (ud) + f.write_c(", (void*)(%s));\n", ud); + else + f.write_c(");\n"); + } else if (ud) { + f.write_c("%s%s->user_data((void*)(%s));\n", f.indent(), var, ud); + } + if (o->align() != tplate->align() || subclass()) { + int i = o->align(); + f.write_c("%s%s->align(Fl_Align(%s", f.indent(), var, + item_name(alignmenu, i & ~FL_ALIGN_INSIDE)); + if (i & FL_ALIGN_INSIDE) f.write_c("|FL_ALIGN_INSIDE"); + f.write_c("));\n"); + } + Fl_When ww = o->when(); + if (ww != tplate->when() || subclass()) + f.write_c("%s%s->when(%s);\n", f.indent(), var, when_symbol_name(ww)); + if (!o->visible() && o->parent()) + f.write_c("%s%s->hide();\n", f.indent(), var); + if (!o->active()) + f.write_c("%s%s->deactivate();\n", f.indent(), var); + if (!is_a(ID_Group) && resizable()) + f.write_c("%sFl_Group::current()->resizable(%s);\n", f.indent(), var); + if (hotspot()) { + if (is_class()) + f.write_c("%shotspot(%s);\n", f.indent(), var); + else if (is_a(ID_Window)) + f.write_c("%s%s->hotspot(%s);\n", f.indent(), var, var); + else + f.write_c("%s%s->window()->hotspot(%s);\n", f.indent(), var, var); + } +} + +void Fl_Widget_Type::write_extra_code(Fd_Code_Writer& f) { + for (int n=0; n < NUM_EXTRA_CODE; n++) + if (extra_code(n) && !isdeclare(extra_code(n))) + f.write_c("%s%s\n", f.indent(), extra_code(n)); +} + +void Fl_Widget_Type::write_block_close(Fd_Code_Writer& f) { + f.indentation--; + f.write_c("%s} // %s* %s\n", f.indent(), subclassname(this), + name() ? name() : "o"); +} + +void Fl_Widget_Type::write_code2(Fd_Code_Writer& f) { + write_extra_code(f); + write_block_close(f); +} + +//////////////////////////////////////////////////////////////// + +void Fl_Widget_Type::write_properties(Fd_Project_Writer &f) { + Fl_Type::write_properties(f); + f.write_indent(level+1); + switch (public_) { + case 0: f.write_string("private"); break; + case 1: break; + case 2: f.write_string("protected"); break; + } + if (tooltip() && *tooltip()) { + f.write_string("tooltip"); + f.write_word(tooltip()); + } + if (image_name() && *image_name()) { + if (scale_image_w_ || scale_image_h_) + f.write_string("scale_image {%d %d}", scale_image_w_, scale_image_h_); + f.write_string("image"); + f.write_word(image_name()); + f.write_string("compress_image %d", compress_image_); + } + if (bind_image_) f.write_string("bind_image 1"); + if (inactive_name() && *inactive_name()) { + if (scale_deimage_w_ || scale_deimage_h_) + f.write_string("scale_deimage {%d %d}", scale_deimage_w_, scale_deimage_h_); + f.write_string("deimage"); + f.write_word(inactive_name()); + f.write_string("compress_deimage %d", compress_deimage_); + } + if (bind_deimage_) f.write_string("bind_deimage 1"); + f.write_string("xywh {%d %d %d %d}", o->x(), o->y(), o->w(), o->h()); + Fl_Widget* tplate = ((Fl_Widget_Type*)factory)->o; + if (is_a(ID_Spinner) && ((Fl_Spinner*)o)->type() != ((Fl_Spinner*)tplate)->type()) { + f.write_string("type"); + f.write_word(item_name(subtypes(), ((Fl_Spinner*)o)->type())); + } else if (subtypes() && (o->type() != tplate->type() || is_a(ID_Window))) { + f.write_string("type"); + f.write_word(item_name(subtypes(), o->type())); + } + if (o->box() != tplate->box()) { + f.write_string("box"); f.write_word(boxname(o->box()));} + if (is_a(ID_Input)) { + Fl_Input_* b = (Fl_Input_*)o; + if (b->shortcut()) f.write_string("shortcut 0x%x", b->shortcut()); + } + if (is_a(ID_Value_Input)) { + Fl_Value_Input* b = (Fl_Value_Input*)o; + if (b->shortcut()) f.write_string("shortcut 0x%x", b->shortcut()); + } + if (is_a(ID_Text_Display)) { + Fl_Text_Display* b = (Fl_Text_Display*)o; + if (b->shortcut()) f.write_string("shortcut 0x%x", b->shortcut()); + } + if (is_a(ID_Button)) { + Fl_Button* b = (Fl_Button*)o; + if (b->down_box()) { + f.write_string("down_box"); f.write_word(boxname(b->down_box()));} + if (b->shortcut()) f.write_string("shortcut 0x%x", b->shortcut()); + if (b->value()) f.write_string("value 1"); + } else if (is_a(ID_Input_Choice)) { + Fl_Input_Choice* b = (Fl_Input_Choice*)o; + if (b->down_box()) { + f.write_string("down_box"); f.write_word(boxname(b->down_box()));} + } else if (is_a(ID_Menu_)) { + Fl_Menu_* b = (Fl_Menu_*)o; + if (b->down_box()) { + f.write_string("down_box"); f.write_word(boxname(b->down_box()));} + } + if (o->color()!=tplate->color()) + f.write_string("color %d", o->color()); + if (o->selection_color()!=tplate->selection_color()) + f.write_string("selection_color %d", o->selection_color()); + if (o->labeltype()!=tplate->labeltype()) { + f.write_string("labeltype"); + f.write_word(item_name(labeltypemenu, o->labeltype())); + } + if (o->labelfont()!=tplate->labelfont()) + f.write_string("labelfont %d", o->labelfont()); + if (o->labelsize()!=tplate->labelsize()) + f.write_string("labelsize %d", o->labelsize()); + if (o->labelcolor()!=tplate->labelcolor()) + f.write_string("labelcolor %d", o->labelcolor()); + if (o->align()!=tplate->align()) + f.write_string("align %d", o->align()); + if (o->horizontal_label_margin()!=tplate->horizontal_label_margin()) + f.write_string("h_label_margin %d", o->horizontal_label_margin()); + if (o->vertical_label_margin()!=tplate->vertical_label_margin()) + f.write_string("v_label_margin %d", o->vertical_label_margin()); + if (o->label_image_spacing()!=tplate->label_image_spacing()) + f.write_string("image_spacing %d", o->label_image_spacing()); + if (o->when() != tplate->when()) + f.write_string("when %d", o->when()); + if (is_a(ID_Valuator_)) { + Fl_Valuator* v = (Fl_Valuator*)o; + Fl_Valuator* t = (Fl_Valuator*)(tplate); + if (v->minimum()!=t->minimum()) f.write_string("minimum %g",v->minimum()); + if (v->maximum()!=t->maximum()) f.write_string("maximum %g",v->maximum()); + if (v->step()!=t->step()) f.write_string("step %g",v->step()); + if (v->value()!=0.0) f.write_string("value %g",v->value()); + if (is_a(ID_Slider)) { + double x = ((Fl_Slider*)v)->slider_size(); + double y = ((Fl_Slider*)t)->slider_size(); + if (x != y) f.write_string("slider_size %g", x); + } + } + if (is_a(ID_Spinner)) { + Fl_Spinner* v = (Fl_Spinner*)o; + Fl_Spinner* t = (Fl_Spinner*)(tplate); + if (v->minimum()!=t->minimum()) f.write_string("minimum %g",v->minimum()); + if (v->maximum()!=t->maximum()) f.write_string("maximum %g",v->maximum()); + if (v->step()!=t->step()) f.write_string("step %g",v->step()); + if (v->value()!=1.0) f.write_string("value %g",v->value()); + } + {Fl_Font ff; int fs; Fl_Color fc; if (textstuff(4,ff,fs,fc)) { + Fl_Font ft; int s; Fl_Color c; textstuff(0,ft,s,c); + if (ft != ff) f.write_string("textfont %d", ft); + if (s != fs) f.write_string("textsize %d", s); + if (c != fc) f.write_string("textcolor %d", c); + }} + if (!o->visible() && !override_visible_) f.write_string("hide"); + if (!o->active()) f.write_string("deactivate"); + if (resizable()) f.write_string("resizable"); + if (hotspot()) f.write_string(is_a(ID_Menu_Item) ? "divider" : "hotspot"); + for (int n=0; n < NUM_EXTRA_CODE; n++) if (extra_code(n)) { + f.write_indent(level+1); + f.write_string("code%d",n); + f.write_word(extra_code(n)); + } + if (subclass()) { + f.write_indent(level+1); + f.write_string("class"); + f.write_word(subclass()); + } +} + +void Fl_Widget_Type::read_property(Fd_Project_Reader &f, const char *c) { + int x,y,w,h; Fl_Font ft; int s; Fl_Color cc; + if (!strcmp(c,"private")) { + public_ = 0; + } else if (!strcmp(c,"protected")) { + public_ = 2; + } else if (!strcmp(c,"xywh")) { + if (sscanf(f.read_word(),"%d %d %d %d",&x,&y,&w,&h) == 4) { + x += pasteoffset; + y += pasteoffset; + // FIXME temporary change! + if (f.read_version>=2.0 && o->parent() && o->parent()!=o->window()) { + x += o->parent()->x(); + y += o->parent()->y(); + } + o->resize(x,y,w,h); + } + } else if (!strcmp(c,"tooltip")) { + tooltip(f.read_word()); + } else if (!strcmp(c,"scale_image")) { + if (sscanf(f.read_word(),"%d %d",&w,&h) == 2) { + scale_image_w_ = w; + scale_image_h_ = h; + } + } else if (!strcmp(c,"image")) { + image_name(f.read_word()); + // starting in 2023, `image` is always followed by `compress_image` + // the code below is for compatibility with older .fl files + const char *ext = fl_filename_ext(image_name_); + if ( strcmp(ext, ".jpg") + && strcmp(ext, ".png") + && strcmp(ext, ".svg") + && strcmp(ext, ".svgz")) + compress_image_ = 0; // if it is neither of those, default to uncompressed + } else if (!strcmp(c,"bind_image")) { + bind_image_ = (int)atol(f.read_word()); + } else if (!strcmp(c,"compress_image")) { + compress_image_ = (int)atol(f.read_word()); + } else if (!strcmp(c,"scale_deimage")) { + if (sscanf(f.read_word(),"%d %d",&w,&h) == 2) { + scale_deimage_w_ = w; + scale_deimage_h_ = h; + } + } else if (!strcmp(c,"deimage")) { + inactive_name(f.read_word()); + // starting in 2023, `deimage` is always followed by `compress_deimage` + // the code below is for compatibility with older .fl files + const char *ext = fl_filename_ext(inactive_name_); + if ( strcmp(ext, ".jpg") + && strcmp(ext, ".png") + && strcmp(ext, ".svg") + && strcmp(ext, ".svgz")) + compress_deimage_ = 0; // if it is neither of those, default to uncompressed + } else if (!strcmp(c,"bind_deimage")) { + bind_deimage_ = (int)atol(f.read_word()); + } else if (!strcmp(c,"compress_deimage")) { + compress_deimage_ = (int)atol(f.read_word()); + } else if (!strcmp(c,"type")) { + if (is_a(ID_Spinner)) + ((Fl_Spinner*)o)->type(item_number(subtypes(), f.read_word())); + else + o->type(item_number(subtypes(), f.read_word())); + } else if (!strcmp(c,"box")) { + const char* value = f.read_word(); + if ((x = boxnumber(value))) { + if (x == ZERO_ENTRY) x = 0; + o->box((Fl_Boxtype)x); + } else if (sscanf(value,"%d",&x) == 1) o->box((Fl_Boxtype)x); + } else if (is_a(ID_Button) && !strcmp(c,"down_box")) { + const char* value = f.read_word(); + if ((x = boxnumber(value))) { + if (x == ZERO_ENTRY) x = 0; + ((Fl_Button*)o)->down_box((Fl_Boxtype)x); + } + } else if (is_a(ID_Input_Choice) && !strcmp(c,"down_box")) { + const char* value = f.read_word(); + if ((x = boxnumber(value))) { + if (x == ZERO_ENTRY) x = 0; + ((Fl_Input_Choice*)o)->down_box((Fl_Boxtype)x); + } + } else if (is_a(ID_Menu_) && !strcmp(c,"down_box")) { + const char* value = f.read_word(); + if ((x = boxnumber(value))) { + if (x == ZERO_ENTRY) x = 0; + ((Fl_Menu_*)o)->down_box((Fl_Boxtype)x); + } + } else if (is_button() && !strcmp(c,"value")) { + const char* value = f.read_word(); + ((Fl_Button*)o)->value(atoi(value)); + } else if (!strcmp(c,"color")) { + const char *cw = f.read_word(); + if (cw[0]=='0' && cw[1]=='x') { + sscanf(cw,"0x%x",&x); + o->color(x); + } else { + int n = sscanf(cw,"%d %d",&x,&y); + if (n == 2) { // back compatibility... + if (x != 47) o->color(x); + o->selection_color(y); + } else { + o->color(x); + } + } + } else if (!strcmp(c,"selection_color")) { + if (sscanf(f.read_word(),"%d",&x)) o->selection_color(x); + } else if (!strcmp(c,"labeltype")) { + c = f.read_word(); + if (!strcmp(c,"image")) { + Fluid_Image *i = Fluid_Image::find(label()); + if (!i) f.read_error("Image file '%s' not found", label()); + else setimage(i); + image_name(label()); + label(""); + } else { + o->labeltype((Fl_Labeltype)item_number(labeltypemenu,c)); + } + } else if (!strcmp(c,"labelfont")) { + if (sscanf(f.read_word(),"%d",&x) == 1) o->labelfont(x); + } else if (!strcmp(c,"labelsize")) { + if (sscanf(f.read_word(),"%d",&x) == 1) o->labelsize(x); + } else if (!strcmp(c,"labelcolor")) { + if (sscanf(f.read_word(),"%d",&x) == 1) o->labelcolor(x); + } else if (!strcmp(c,"align")) { + if (sscanf(f.read_word(),"%d",&x) == 1) o->align(x); + } else if (!strcmp(c,"h_label_margin")) { + if (sscanf(f.read_word(),"%d",&x) == 1) o->horizontal_label_margin(x); + } else if (!strcmp(c,"v_label_margin")) { + if (sscanf(f.read_word(),"%d",&x) == 1) o->vertical_label_margin(x); + } else if (!strcmp(c,"image_spacing")) { + if (sscanf(f.read_word(),"%d",&x) == 1) o->label_image_spacing(x); + } else if (!strcmp(c,"when")) { + if (sscanf(f.read_word(),"%d",&x) == 1) o->when(x); + } else if (!strcmp(c,"minimum")) { + if (is_a(ID_Valuator_)) ((Fl_Valuator*)o)->minimum(strtod(f.read_word(),0)); + if (is_a(ID_Spinner)) ((Fl_Spinner*)o)->minimum(strtod(f.read_word(),0)); + } else if (!strcmp(c,"maximum")) { + if (is_a(ID_Valuator_)) ((Fl_Valuator*)o)->maximum(strtod(f.read_word(),0)); + if (is_a(ID_Spinner)) ((Fl_Spinner*)o)->maximum(strtod(f.read_word(),0)); + } else if (!strcmp(c,"step")) { + if (is_a(ID_Valuator_)) ((Fl_Valuator*)o)->step(strtod(f.read_word(),0)); + if (is_a(ID_Spinner)) ((Fl_Spinner*)o)->step(strtod(f.read_word(),0)); + } else if (!strcmp(c,"value")) { + if (is_a(ID_Valuator_)) ((Fl_Valuator*)o)->value(strtod(f.read_word(),0)); + if (is_a(ID_Spinner)) ((Fl_Spinner*)o)->value(strtod(f.read_word(),0)); + } else if ( (!strcmp(c,"slider_size") || !strcmp(c,"size")) && is_a(ID_Slider)) { + ((Fl_Slider*)o)->slider_size(strtod(f.read_word(),0)); + } else if (!strcmp(c,"textfont")) { + if (sscanf(f.read_word(),"%d",&x) == 1) {ft=(Fl_Font)x; textstuff(1,ft,s,cc);} + } else if (!strcmp(c,"textsize")) { + if (sscanf(f.read_word(),"%d",&x) == 1) {s=x; textstuff(2,ft,s,cc);} + } else if (!strcmp(c,"textcolor")) { + if (sscanf(f.read_word(),"%d",&x) == 1) {cc=(Fl_Color)x;textstuff(3,ft,s,cc);} + } else if (!strcmp(c,"hide")) { + o->hide(); + } else if (!strcmp(c,"deactivate")) { + o->deactivate(); + } else if (!strcmp(c,"resizable")) { + resizable(1); + } else if (!strcmp(c,"hotspot") || !strcmp(c, "divider")) { + hotspot(1); + } else if (!strcmp(c,"class")) { + subclass(f.read_word()); + } else if (!strcmp(c,"shortcut")) { + int shortcut = (int)strtol(f.read_word(),0,0); + if (is_button()) ((Fl_Button*)o)->shortcut(shortcut); + else if (is_a(ID_Input)) ((Fl_Input_*)o)->shortcut(shortcut); + else if (is_a(ID_Value_Input)) ((Fl_Value_Input*)o)->shortcut(shortcut); + else if (is_a(ID_Text_Display)) ((Fl_Text_Display*)o)->shortcut(shortcut); + } else { + if (!strncmp(c,"code",4)) { + int n = atoi(c+4); + if (n >= 0 && n <= NUM_EXTRA_CODE) { + extra_code(n,f.read_word()); + return; + } + } else if (!strcmp(c,"extra_code")) { + extra_code(0,f.read_word()); + return; + } + Fl_Type::read_property(f, c); + } +} + +Fl_Menu_Item boxmenu1[] = { + // these extra ones are for looking up fdesign saved strings: + {"NO_FRAME", 0,0,(void *)FL_NO_BOX}, + {"ROUNDED3D_UPBOX", 0,0,(void *)_FL_ROUND_UP_BOX}, + {"ROUNDED3D_DOWNBOX", 0,0,(void *)_FL_ROUND_DOWN_BOX}, + {"OVAL3D_UPBOX", 0,0,(void *)_FL_ROUND_UP_BOX}, + {"OVAL3D_DOWNBOX", 0,0,(void *)_FL_ROUND_DOWN_BOX}, + {"0", 0,0,(void *)ZERO_ENTRY}, + {"1", 0,0,(void *)FL_UP_BOX}, + {"2", 0,0,(void *)FL_DOWN_BOX}, + {"3", 0,0,(void *)FL_FLAT_BOX}, + {"4", 0,0,(void *)FL_BORDER_BOX}, + {"5", 0,0,(void *)FL_SHADOW_BOX}, + {"6", 0,0,(void *)FL_FRAME_BOX}, + {"7", 0,0,(void *)FL_ROUNDED_BOX}, + {"8", 0,0,(void *)FL_RFLAT_BOX}, + {"9", 0,0,(void *)FL_RSHADOW_BOX}, + {"10", 0,0,(void *)FL_UP_FRAME}, + {"11", 0,0,(void *)FL_DOWN_FRAME}, +{0}}; + +int lookup_symbol(const char *, int &, int numberok = 0); + +int Fl_Widget_Type::read_fdesign(const char* propname, const char* value) { + int v; + if (!strcmp(propname,"box")) { + float x,y,w,h; + if (sscanf(value,"%f %f %f %f",&x,&y,&w,&h) == 4) { + if (fdesign_flip) { + Fl_Type *p; + for (p = parent; p && !p->is_a(ID_Window); p = p->parent) {/*empty*/} + if (p && p->is_widget()) y = ((Fl_Widget_Type*)p)->o->h()-(y+h); + } + x += pasteoffset; + y += pasteoffset; + o->resize(int(x),int(y),int(w),int(h)); + } + } else if (!strcmp(propname,"label")) { + label(value); + } else if (!strcmp(propname,"name")) { + this->name(value); + } else if (!strcmp(propname,"callback")) { + callback(value); user_data_type("long"); + } else if (!strcmp(propname,"argument")) { + user_data(value); + } else if (!strcmp(propname,"shortcut")) { + if (value[0]) { + char buf[128]; sprintf(buf,"o->shortcut(\"%s\");",value); + extra_code(0,buf); + } + } else if (!strcmp(propname,"style")) { + if (!strncmp(value,"FL_NORMAL",9)) return 1; + if (!lookup_symbol(value,v,1)) return 0; + o->labelfont(v); o->labeltype((Fl_Labeltype)(v>>8)); + } else if (!strcmp(propname,"size")) { + if (!lookup_symbol(value,v,1)) return 0; + o->labelsize(v); + } else if (!strcmp(propname,"type")) { + if (!strncmp(value,"NORMAL",6)) return 1; + if (lookup_symbol(value,v,1)) {o->type(v); return 1;} + if (!strcmp(value+strlen(value)-5,"FRAME")) goto TRY_BOXTYPE; + if (!strcmp(value+strlen(value)-3,"BOX")) goto TRY_BOXTYPE; + return 0; + } else if (!strcmp(propname,"lcol")) { + if (!lookup_symbol(value,v,1)) return 0; + o->labelcolor(v); + } else if (!strcmp(propname,"return")) { + if (!lookup_symbol(value,v,0)) return 0; + o->when(v|FL_WHEN_RELEASE); + } else if (!strcmp(propname,"alignment")) { + if (!lookup_symbol(value,v)) { + // convert old numeric values: + int v1 = atoi(value); if (v1 <= 0 && strcmp(value,"0")) return 0; + v = 0; + if (v1 >= 5) {v = FL_ALIGN_INSIDE; v1 -= 5;} + switch (v1) { + case 0: v += FL_ALIGN_TOP; break; + case 1: v += FL_ALIGN_BOTTOM; break; + case 2: v += FL_ALIGN_LEFT; break; + case 3: v += FL_ALIGN_RIGHT; break; + case 4: v += FL_ALIGN_CENTER; break; + default: return 0; + } + } + o->align(v); + } else if (!strcmp(propname,"resizebox")) { + resizable(1); + } else if (!strcmp(propname,"colors")) { + char* p = (char*)value; + while (*p != ' ') {if (!*p) return 0; p++;} + *p = 0; + int v1; + if (!lookup_symbol(value,v,1) || !lookup_symbol(p+1,v1,1)) { + *p=' '; return 0;} + o->color(v,v1); + } else if (!strcmp(propname,"resize")) { + return !strcmp(value,"FL_RESIZE_ALL"); + } else if (!strcmp(propname,"gravity")) { + return !strcmp(value,"FL_NoGravity FL_NoGravity"); + } else if (!strcmp(propname,"boxtype")) { + TRY_BOXTYPE: + int x = boxnumber(value); + if (!x) {x = item_number(boxmenu1, value); if (x < 0) return 0;} + if (x == ZERO_ENTRY) { + x = 0; + if (o->box() != ((Fl_Widget_Type*)factory)->o->box()) return 1; // kludge for frame + } + o->box((Fl_Boxtype)x); + } else { + return 0; + } + return 1; +} + +void leave_live_mode_cb(Fl_Widget*, void*) { + live_mode_cb(0, 0); +} + +Fl_Widget *Fl_Widget_Type::enter_live_mode(int) { + live_widget = widget(o->x(), o->y(), o->w(), o->h()); + if (live_widget) + copy_properties(); + return live_widget; +} + +Fl_Widget* Fl_Widget_Type::propagate_live_mode(Fl_Group* grp) { + live_widget = grp; + copy_properties(); + Fl_Type *n; + for (n = next; n && n->level > level; n = n->next) { + if (n->level == level+1) { + Fl_Widget* proxy_child = n->enter_live_mode(); + if (proxy_child && n->is_widget() && ((Fl_Widget_Type*)n)->resizable()) { + grp->resizable(proxy_child); + } + } + } + grp->end(); + live_widget = grp; + copy_properties_for_children(); + return live_widget; +} + + +void Fl_Widget_Type::leave_live_mode() { +} + +/** + copy all properties from the edit widget to the live widget + */ +void Fl_Widget_Type::copy_properties() { + if (!live_widget) + return; + + Fl_Font ff = 0; + int fs = 0; + Fl_Color fc = 0; + textstuff(0, ff, fs, fc); + + // copy all attributes common to all widget types + Fl_Widget *w = live_widget; + w->label(o->label()); + w->tooltip(tooltip()); + w->type(o->type()); + w->box(o->box()); + w->color(o->color()); + w->selection_color(o->selection_color()); + w->labeltype(o->labeltype()); + w->labelfont(o->labelfont()); + w->labelsize(o->labelsize()); + w->labelcolor(o->labelcolor()); + w->align(o->align()); + w->when(o->when()); + + // copy all attributes specific to widgets derived from Fl_Button + if (is_button()) { + Fl_Button* d = (Fl_Button*)live_widget, *s = (Fl_Button*)o; + d->down_box(s->down_box()); + d->shortcut(s->shortcut()); + d->value(s->value()); + } + + // copy all attributes specific to widgets derived from Fl_Input_ + if (is_a(ID_Input)) { + Fl_Input_* d = (Fl_Input_*)live_widget, *s = (Fl_Input_*)o; + d->shortcut(s->shortcut()); + d->textfont(ff); + d->textsize(fs); + d->textcolor(fc); + } + + // copy all attributes specific to widgets derived from Fl_Value_Input + if (is_a(ID_Value_Input)) { + Fl_Value_Input* d = (Fl_Value_Input*)live_widget, *s = (Fl_Value_Input*)o; + d->shortcut(s->shortcut()); + d->textfont(ff); + d->textsize(fs); + d->textcolor(fc); + } + + // copy all attributes specific to widgets derived from Fl_Text_Display + if (is_a(ID_Text_Display)) { + Fl_Text_Display* d = (Fl_Text_Display*)live_widget, *s = (Fl_Text_Display*)o; + d->shortcut(s->shortcut()); + d->textfont(ff); + d->textsize(fs); + d->textcolor(fc); + } + + // copy all attributes specific to Fl_Valuator and derived classes + if (is_a(ID_Valuator_)) { + Fl_Valuator* d = (Fl_Valuator*)live_widget, *s = (Fl_Valuator*)o; + d->minimum(s->minimum()); + d->maximum(s->maximum()); + d->step(s->step()); + d->value(s->value()); + if (is_a(ID_Slider)) { + Fl_Slider *d = (Fl_Slider*)live_widget, *s = (Fl_Slider*)o; + d->slider_size(s->slider_size()); + } + } + + // copy all attributes specific to Fl_Spinner and derived classes + if (is_a(ID_Spinner)) { + Fl_Spinner* d = (Fl_Spinner*)live_widget, *s = (Fl_Spinner*)o; + d->minimum(s->minimum()); + d->maximum(s->maximum()); + d->step(s->step()); + d->value(s->value()); + } + + if (!o->visible()) + w->hide(); + if (!o->active()) + w->deactivate(); +} + diff --git a/fluid/nodes/Fl_Widget_Type.h b/fluid/nodes/Fl_Widget_Type.h new file mode 100644 index 000000000..76bea3404 --- /dev/null +++ b/fluid/nodes/Fl_Widget_Type.h @@ -0,0 +1,132 @@ +// +// Widget type header file for the Fast Light Tool Kit (FLTK). +// +// Type for creating all subclasses of Fl_Widget +// This should have the widget pointer in it, but it is still in the +// Fl_Type base class. +// +// Copyright 1998-2023 by Bill Spitzak and others. +// +// This library is free software. Distribution and use rights are outlined in +// the file "COPYING" which should have been included with this file. If this +// file is missing or damaged, see the license at: +// +// https://www.fltk.org/COPYING.php +// +// Please see the following page on how to report bugs and issues: +// +// https://www.fltk.org/bugs.php +// + +#ifndef _FLUID_FL_WIDGET_TYPE_H +#define _FLUID_FL_WIDGET_TYPE_H + +#include "nodes/Fl_Type.h" + +#define NUM_EXTRA_CODE 4 + +class Fl_Widget_Type; +class Fluid_Image; + +extern void* const LOAD; +extern Fl_Widget_Type *current_widget; // one of the selected ones + +extern const char* subclassname(Fl_Type* l); +extern int is_name(const char *c); +void selection_changed(Fl_Type* new_current); +Fl_Type *sort(Fl_Type *parent); +void comment_cb(class Fl_Text_Editor* i, void *v); + +class Fl_Widget_Type : public Fl_Type +{ + typedef Fl_Type super; + + virtual Fl_Widget *widget(int,int,int,int) = 0; + virtual Fl_Widget_Type *_make() = 0; // virtual constructor + void setlabel(const char *) FL_OVERRIDE; + + const char *extra_code_[NUM_EXTRA_CODE]; + const char *subclass_; + const char *tooltip_; + const char *image_name_; + const char *inactive_name_; + uchar hotspot_; + +protected: + + /// This variable is set for visible windows in batch mode. + /// We can't open a window in batch mode, even if we want the "visible" flags + /// set, so we need a second place to store this information while also + /// disabling the output of the "hide" property by the Widget Type. + uchar override_visible_; + + void write_static(Fd_Code_Writer& f) FL_OVERRIDE; + void write_code1(Fd_Code_Writer& f) FL_OVERRIDE; + void write_widget_code(Fd_Code_Writer& f); + void write_extra_code(Fd_Code_Writer& f); + void write_block_close(Fd_Code_Writer& f); + void write_code2(Fd_Code_Writer& f) FL_OVERRIDE; + void write_color(Fd_Code_Writer& f, const char*, Fl_Color); + Fl_Widget *live_widget; + +public: + Fl_Widget *o; + int public_; + int bind_image_; + int compress_image_; + int bind_deimage_; + int compress_deimage_; + int scale_image_w_, scale_image_h_; + int scale_deimage_w_, scale_deimage_h_; + + Fluid_Image *image; + void setimage(Fluid_Image *); + Fluid_Image *inactive; + void setinactive(Fluid_Image *); + + Fl_Widget_Type(); + Fl_Type *make(Strategy strategy) FL_OVERRIDE; + void open() FL_OVERRIDE; + + const char *extra_code(int n) const {return extra_code_[n];} + void extra_code(int n,const char *); + const char *subclass() const {return subclass_;} + void subclass(const char *); + const char *tooltip() const {return tooltip_;} + void tooltip(const char *); + const char *image_name() const {return image_name_;} + void image_name(const char *); + const char *inactive_name() const {return inactive_name_;} + void inactive_name(const char *); + uchar hotspot() const {return hotspot_;} + void hotspot(uchar v) {hotspot_ = v;} + uchar resizable() const; + void resizable(uchar v); + + virtual int textstuff(int what, Fl_Font &, int &, Fl_Color &); + virtual Fl_Menu_Item *subtypes(); + + ID id() const FL_OVERRIDE { return ID_Widget_; } + bool is_a(ID inID) const FL_OVERRIDE { return (inID==ID_Widget_) ? true : super::is_a(inID); } + int is_widget() const FL_OVERRIDE; + int is_true_widget() const FL_OVERRIDE { return 1; } + int is_public() const FL_OVERRIDE; + + void write_properties(Fd_Project_Writer &f) FL_OVERRIDE; + void read_property(Fd_Project_Reader &f, const char *) FL_OVERRIDE; + int read_fdesign(const char*, const char*) FL_OVERRIDE; + + Fl_Widget *enter_live_mode(int top=0) FL_OVERRIDE; + Fl_Widget *propagate_live_mode(Fl_Group* grp); + void leave_live_mode() FL_OVERRIDE; + void copy_properties() FL_OVERRIDE; + + virtual void ideal_size(int &w, int &h); + + ~Fl_Widget_Type(); + void redraw(); +}; + +extern Fl_Window *the_panel; + +#endif // _FLUID_FL_WIDGET_TYPE_H diff --git a/fluid/nodes/Fl_Window_Type.cxx b/fluid/nodes/Fl_Window_Type.cxx new file mode 100644 index 000000000..c26191b72 --- /dev/null +++ b/fluid/nodes/Fl_Window_Type.cxx @@ -0,0 +1,1560 @@ +// +// Window type code file for the Fast Light Tool Kit (FLTK). +// +// The widget describing an Fl_Window. This is also all the code +// for interacting with the overlay, which allows the user to +// select, move, and resize the children widgets. +// +// 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/Fl_Window_Type.h" + +#include "app/Fd_Snap_Action.h" +#include "app/fluid.h" +#include "app/undo.h" +#include "io/file.h" +#include "io/code.h" +#include "nodes/factory.h" +#include "nodes/Fl_Group_Type.h" +#include "nodes/Fl_Grid_Type.h" +#include "panels/settings_panel.h" +#include "panels/widget_panel.h" +#include "widgets/widget_browser.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../src/flstring.h" + +#include +#include +#include + +extern Fl_Window *the_panel; +extern void draw_width(int x, int y, int r, Fl_Align a); +extern void draw_height(int x, int y, int b, Fl_Align a); + +extern Fl_Preferences fluid_prefs; + +// Update the XYWH values in the widget panel... +static void update_xywh() { + if (current_widget && current_widget->is_widget()) { + Fl_Widget *o = ((Fl_Widget_Type *)current_widget)->o; + widget_x_input->value(o->x()); + widget_y_input->value(o->y()); + widget_w_input->value(o->w()); + widget_h_input->value(o->h()); + if (Fl_Flex_Type::parent_is_flex(current_widget)) { + widget_flex_size->value(Fl_Flex_Type::size(current_widget)); + widget_flex_fixed->value(Fl_Flex_Type::is_fixed(current_widget)); + } + } +} + +void i18n_type_cb(Fl_Choice *c, void *v) { + if (v == LOAD) { + c->value(g_project.i18n_type); + } else { + undo_checkpoint(); + g_project.i18n_type = static_cast(c->value()); + set_modflag(1); + } + switch (g_project.i18n_type) { + case FD_I18N_NONE : /* None */ + i18n_gnu_group->hide(); + i18n_posix_group->hide(); + break; + case FD_I18N_GNU : /* GNU gettext */ + i18n_gnu_group->show(); + i18n_posix_group->hide(); + break; + case FD_I18N_POSIX : /* POSIX cat */ + i18n_gnu_group->hide(); + i18n_posix_group->show(); + break; + } + // make sure that the outside labels are redrawn too. + w_settings_i18n_tab->redraw(); +} + +void show_grid_cb(Fl_Widget *, void *) { + settings_window->show(); + w_settings_tabs->value(w_settings_layout_tab); +} + +void show_settings_cb(Fl_Widget *, void *) { + settings_window->hotspot(settings_window); + settings_window->show(); +} + +//////////////////////////////////////////////////////////////// + +Fl_Menu_Item window_type_menu[] = { + {"Single",0,0,(void*)FL_WINDOW}, + {"Double",0,0,(void*)(FL_DOUBLE_WINDOW)}, + {0}}; + +static int overlays_invisible; + +// The following Fl_Widget is used to simulate the windows. It has +// an overlay for the fluid ui, and special-cases the FL_NO_BOX. + +class Overlay_Window : public Fl_Overlay_Window { + void draw() FL_OVERRIDE; + void draw_overlay() FL_OVERRIDE; + static void close_cb(Overlay_Window *self, void*); +public: + Fl_Window_Type *window; + int handle(int) FL_OVERRIDE; + Overlay_Window(int W,int H) : Fl_Overlay_Window(W,H) { + Fl_Group::current(0); + callback((Fl_Callback*)close_cb); + } + void resize(int,int,int,int) FL_OVERRIDE; + uchar *read_image(int &ww, int &hh); +}; + +/** + \brief User closes the window, so we mark the .fl file as changed. + Mark the .fl file a changed, but don;t mark the source files as changed. + \param self pointer to this window + */ +void Overlay_Window::close_cb(Overlay_Window *self, void*) { + if (self->visible()) + set_modflag(1, -2); + self->hide(); +} + +// Use this when drawing flat boxes while editing, so users can see the outline, +// even if the group and its parent have the same color. +static void fd_flat_box_ghosted(int x, int y, int w, int h, Fl_Color c) { + fl_rectf(x, y, w, h, Fl::box_color(c)); + fl_rect(x, y, w, h, Fl::box_color(fl_color_average(FL_FOREGROUND_COLOR, c, .1f))); +} + +void Overlay_Window::draw() { + const int CHECKSIZE = 8; + // see if box is clear or a frame or rounded: + if ((damage()&FL_DAMAGE_ALL) && + (!box() || (box()>=4&&!(box()&2)) || box()>=_FL_ROUNDED_BOX)) { + // if so, draw checkerboard so user can see what areas are clear: + for (int Y = 0; Y < h(); Y += CHECKSIZE) + for (int X = 0; X < w(); X += CHECKSIZE) { + fl_color(((Y/(2*CHECKSIZE))&1) != ((X/(2*CHECKSIZE))&1) ? + FL_WHITE : FL_BLACK); + fl_rectf(X,Y,CHECKSIZE,CHECKSIZE); + } + } + if (show_ghosted_outline) { + Fl_Box_Draw_F *old_flat_box = Fl::get_boxtype(FL_FLAT_BOX); + Fl::set_boxtype(FL_FLAT_BOX, fd_flat_box_ghosted, 0, 0, 0, 0); + Fl_Overlay_Window::draw(); + Fl::set_boxtype(FL_FLAT_BOX, old_flat_box, 0, 0, 0, 0); + } else { + Fl_Overlay_Window::draw(); + } +} + +extern Fl_Window *main_window; + +// Read an image of the overlay window +uchar *Overlay_Window::read_image(int &ww, int &hh) { + // Create an off-screen buffer for the window... + //main_window->make_current(); + make_current(); + + ww = w(); + hh = h(); + + Fl_Offscreen offscreen = fl_create_offscreen(ww, hh); + uchar *pixels; + + // Redraw the window into the offscreen buffer... + fl_begin_offscreen(offscreen); + + if (!shown()) image(Fl::scheme_bg_); + + redraw(); + draw(); + + // Read the screen image... + pixels = fl_read_image(0, 0, 0, ww, hh); + + fl_end_offscreen(); + + // Cleanup and return... + fl_delete_offscreen(offscreen); + main_window->make_current(); + return pixels; +} + +void Overlay_Window::draw_overlay() { + window->draw_overlay(); +} + +int Overlay_Window::handle(int e) { + int ret = window->handle(e); + if (ret==0) { + switch (e) { + case FL_SHOW: + case FL_HIDE: + ret = Fl_Overlay_Window::handle(e); + } + } + return ret; +} + +/** + Make and add a new Window node. + \param[in] strategy is Strategy::AS_LAST_CHILD or Strategy::AFTER_CURRENT + \return new node + */ +Fl_Type *Fl_Window_Type::make(Strategy strategy) { + Fl_Type *anchor = Fl_Type::current, *p = anchor; + if (p && (strategy.placement() == Strategy::AFTER_CURRENT)) p = p->parent; + while (p && (!p->is_code_block() || p->is_a(ID_Widget_Class))) { + anchor = p; + strategy.placement(Strategy::AFTER_CURRENT); + p = p->parent; + } + if (!p) { + fl_message("Please select a function"); + return 0; + } + Fl_Window_Type *myo = new Fl_Window_Type(); + if (!this->o) {// template widget + this->o = new Fl_Window(100,100); + Fl_Group::current(0); + } + myo->factory = this; + myo->drag = 0; + myo->numselected = 0; + Overlay_Window *w = new Overlay_Window(100, 100); + w->size_range(10, 10); + w->window = myo; + myo->o = w; + myo->add(anchor, strategy); + myo->modal = 0; + myo->non_modal = 0; + return myo; +} + +void Fl_Window_Type::add_child(Fl_Type* cc, Fl_Type* before) { + if (!cc->is_widget()) return; + Fl_Widget_Type* c = (Fl_Widget_Type*)cc; + Fl_Widget* b = before ? ((Fl_Widget_Type*)before)->o : 0; + ((Fl_Window*)o)->insert(*(c->o), b); + o->redraw(); +} + +void Fl_Window_Type::remove_child(Fl_Type* cc) { + Fl_Widget_Type* c = (Fl_Widget_Type*)cc; + ((Fl_Window*)o)->remove(c->o); + o->redraw(); +} + +void Fl_Window_Type::move_child(Fl_Type* cc, Fl_Type* before) { + Fl_Widget_Type* c = (Fl_Widget_Type*)cc; + ((Fl_Window*)o)->remove(c->o); + Fl_Widget* b = before ? ((Fl_Widget_Type*)before)->o : 0; + ((Fl_Window*)o)->insert(*(c->o), b); + o->redraw(); +} + +//////////////////////////////////////////////////////////////// + +/** + \brief Show the Window Type editor window without setting the modified flag. + \see Fl_Window_Type::open() + */ +void Fl_Window_Type::open_() { + Overlay_Window *w = (Overlay_Window *)o; + if (w->shown()) { + w->show(); + Fl_Widget_Type::open(); + } else { + Fl_Widget *p = w->resizable(); + if (!p) w->resizable(w); + w->show(); + w->resizable(p); + } + w->image(Fl::scheme_bg_); +} + +/** + \brief Show the Window Type editor window and set the modified flag if needed. + Double-click on window widget shows the window, or if already shown, it shows + the control panel. + \see Fl_Window_Type::open_() + */ +void Fl_Window_Type::open() { + Overlay_Window *w = (Overlay_Window *)o; + if (!w->visible()) { + set_modflag(1, -2); + } + open_(); +} + +// Read an image of the window +uchar *Fl_Window_Type::read_image(int &ww, int &hh) { + Overlay_Window *w = (Overlay_Window *)o; + + int hidden = !w->shown(); + w->show(); // make it the front window + + // Read the screen image... + uchar *idata = w->read_image(ww, hh); + if (hidden) + w->hide(); + return idata; +} + +void Fl_Window_Type::ideal_size(int &w, int &h) { + w = 480; h = 320; + if (main_window) { + int sx, sy, sw, sh; + Fl_Window *win = main_window; + int screen = Fl::screen_num(win->x(), win->y()); + Fl::screen_work_area(sx, sy, sw, sh, screen); + w = fd_min(w, sw*3/4); h = fd_min(h, sh*3/4); + } + Fd_Snap_Action::better_size(w, h); +} + + +// control panel items: + +void modal_cb(Fl_Light_Button* i, void* v) { + if (v == LOAD) { + if (!current_widget->is_a(ID_Window)) {i->hide(); return;} + i->show(); + i->value(((Fl_Window_Type *)current_widget)->modal); + } else { + undo_checkpoint(); + ((Fl_Window_Type *)current_widget)->modal = i->value(); + set_modflag(1); + } +} + +void non_modal_cb(Fl_Light_Button* i, void* v) { + if (v == LOAD) { + if (!current_widget->is_a(ID_Window)) {i->hide(); return;} + i->show(); + i->value(((Fl_Window_Type *)current_widget)->non_modal); + } else { + undo_checkpoint(); + ((Fl_Window_Type *)current_widget)->non_modal = i->value(); + set_modflag(1); + } +} + +void border_cb(Fl_Light_Button* i, void* v) { + if (v == LOAD) { + if (!current_widget->is_a(ID_Window)) {i->hide(); return;} + i->show(); + i->value(((Fl_Window*)(current_widget->o))->border()); + } else { + undo_checkpoint(); + ((Fl_Window*)(current_widget->o))->border(i->value()); + set_modflag(1); + } +} + +void xclass_cb(Fl_Input* i, void* v) { + if (v == LOAD) { + if (current_widget->is_a(ID_Window)) { + i->show(); + i->parent()->show(); + i->value(((Fl_Window_Type *)current_widget)->xclass); + } else { + i->hide(); + i->parent()->hide(); // hides the "X Class:" label as well + } + } else { + int mod = 0; + undo_checkpoint(); + for (Fl_Type *o = Fl_Type::first; o; o = o->next) { + if (o->selected && o->is_a(ID_Window)) { + mod = 1; + Fl_Window_Type *wt = (Fl_Window_Type *)o; + storestring(i->value(), wt->xclass); + ((Fl_Window*)(wt->o))->xclass(wt->xclass); + } + } + if (mod) set_modflag(1); + } +} + +//////////////////////////////////////////////////////////////// + +void Fl_Window_Type::setlabel(const char *n) { + if (o) ((Fl_Window *)o)->label(n); +} + +// make() is called on this widget when user picks window off New menu: +Fl_Window_Type Fl_Window_type; + +// Resize from window manager... +void Overlay_Window::resize(int X,int Y,int W,int H) { + // Make sure we don't create undo checkpoints if the window does not actually change. + // Some WMs seem to send spurious resize events. + if (X!=x() || Y!=y() || W!=w() || H!=h()) { + // Set a checkpoint on the first resize event, ignore further resizes until + // a different type of checkpoint is triggered. + if (undo_checkpoint_once(kUndoWindowResize)) + set_modflag(1); + } + + Fl_Widget* t = resizable(); + if (Fl_Type::allow_layout == 0) { + resizable(0); + } + + // do not set the mod flag if the window was not resized. In FLUID, all + // windows are opened without a given x/y position, so modifying x/y + // should not mark the project as dirty + if (W!=w() || H!=h()) + set_modflag(1); + + Fl_Overlay_Window::resize(X,Y,W,H); + resizable(t); + update_xywh(); +} + +// calculate actual move by moving mouse position (mx,my) to +// nearest multiple of gridsize, and snap to original position +void Fl_Window_Type::newdx() { + int mydx, mydy; + mydx = mx-x1; + mydy = my-y1; + + if (!(drag & (FD_DRAG | FD_BOX | FD_LEFT | FD_RIGHT))) { + mydx = 0; + dx = 0; + } + + if (!(drag & (FD_DRAG | FD_BOX | FD_TOP | FD_BOTTOM))) { + mydy = 0; + dy = 0; + } + + if (show_guides && (drag & (FD_DRAG|FD_TOP|FD_LEFT|FD_BOTTOM|FD_RIGHT))) { + Fl_Type *selection = 0L; // special power for the first selected widget + for (Fl_Type *q=next; q && q->level>level; q = q->next) { + if (q->selected && q->is_true_widget()) { + selection = q; + break; + } + } + Fd_Snap_Data data = { mydx, mydy, bx, by, br, bt, drag, 4, 4, mydx, mydy, (Fl_Widget_Type*)selection, this }; + Fd_Snap_Action::check_all(data); + if (data.x_dist < 4) mydx = data.dx_out; + if (data.y_dist < 4) mydy = data.dy_out; + } + + if (dx != mydx || dy != mydy) { + dx = mydx; dy = mydy; + ((Overlay_Window *)o)->redraw_overlay(); + } +} + +// Move a widget according to dx and dy calculated above +void Fl_Window_Type::newposition(Fl_Widget_Type *myo,int &X,int &Y,int &R,int &T) { + X = myo->o->x(); + Y = myo->o->y(); + R = X+myo->o->w(); + T = Y+myo->o->h(); + if (!drag) return; + if (drag&FD_DRAG) { + X += dx; + Y += dy; + R += dx; + T += dy; + } else { + if (drag&FD_LEFT) { + if (X==bx) { + X += dx; + } else { + if (Xbr+dx) R = br+dx; + } + } + if (drag&FD_BOTTOM) { + if (T==bt) { + T += dy; + } else { + if (T>bt+dx) T = bt+dx; + } + } + } + if (R h) { + for (; yp < h; yp+=size) + fl_line(x, y+yp, x+yp, y); + for (; yp < w; yp+=size) + fl_line(x+yp-h, y+h, x+yp, y); + for (; yp < w+h; yp+=size) + fl_line(x+yp-h, y+h, x+w, y+yp-w); + } else { + for (; yp < w; yp+=size) + fl_line(x, y+yp, x+yp, y); + for (; yp < h; yp+=size) + fl_line(x, y+yp, x+w, y+yp-w); + for (; yp < h+w; yp+=size) + fl_line(x+yp-h, y+h, x+w, y+yp-w); + } +} + +/** + \brief Draw a hatch pattern over all children that overlap the bounds of this box. + \param[in] group check all children of this group + \param[in] x, y, w, h bounding box of this group + */ +void Fl_Window_Type::draw_out_of_bounds(Fl_Widget_Type *group, int x, int y, int w, int h) { + for (Fl_Type *p = group->next; p && p->level>group->level; p = p->next) { + if (p->level == group->level+1 && p->is_true_widget()) { + Fl_Widget *o = ((Fl_Widget_Type*)p)->o; + if (o->x() < x) fd_hatch(o->x(), o->y(), x-o->x(), o->h()); + if (o->y() < y) fd_hatch(o->x(), o->y(), o->w(), y-o->y()); + if (o->x()+o->w() > x+w) fd_hatch(x+w, o->y(), (o->x()+o->w())-(x+w), o->h()); + if (o->y()+o->h() > y+h) fd_hatch(o->x(), y+h, o->w(), (o->y()+o->h())-(y+h)); + } + } +} + +/** + \brief Draw a hatch pattern for all groups that have out of bounds children. + */ +void Fl_Window_Type::draw_out_of_bounds() { + // get every group in the hierarchy, then draw any overlap of a direct child with that group + fl_color(FL_DARK_RED); + draw_out_of_bounds(this, 0, 0, o->w(), o->h()); + for (Fl_Type *q=next; q && q->level>level; q = q->next) { + // don't do this for Fl_Scroll (which we currently can't handle in FLUID anyway) + if (q->is_a(ID_Group) && !q->is_a(ID_Scroll)) { + Fl_Widget_Type *w = (Fl_Widget_Type*)q; + draw_out_of_bounds(w, w->o->x(), w->o->y(), w->o->w(), w->o->h()); + } + } + fl_color(FL_RED); +} + +/** + \brief Compare all children in the same level and hatch overlapping areas. + */ +void Fl_Window_Type::draw_overlaps() { + fl_color(FL_DARK_YELLOW); + // loop through all widgets in this window + for (Fl_Type *q=next; q && q->level>level; q = q->next) { + // is it a valid widget + if (q->is_true_widget()) { + Fl_Widget_Type *w = (Fl_Widget_Type*)q; + // is the widget visible + if (w->o->visible()) { + int x = w->o->x(), y = w->o->y(); + int r = x + w->o->w(), b = y + w->o->h(); + for (Fl_Type *p=q->next; p && p->level>=q->level; p = p->next) { + if (p->level==q->level && p->is_true_widget()) { + Fl_Widget_Type *wp = (Fl_Widget_Type*)p; + if (wp->o->visible()) { + int px = fd_max(x, wp->o->x()); + int py = fd_max(y, wp->o->y()); + int pr = fd_min(r, wp->o->x() + wp->o->w()); + int pb = fd_min(b, wp->o->y() + wp->o->h()); + if (pr > px && pb > py) + fd_hatch(px, py, pr-px, pb-py); + } + } + } + } else { + int l = q->level; + for (; q && q->next && q->next->level>l; q = q->next) { } + } + } + } + fl_color(FL_RED); +} + +void Fl_Window_Type::draw_overlay() { + if (recalc) { + bx = o->w(); by = o->h(); br = 0; bt = 0; + numselected = 0; + for (Fl_Type *q=next; q && q->level>level; q=q->next) + if (q->selected && q->is_true_widget()) { + numselected++; + Fl_Widget_Type* myo = (Fl_Widget_Type*)q; + if (myo->o->x() < bx) bx = myo->o->x(); + if (myo->o->y() < by) by = myo->o->y(); + if (myo->o->x()+myo->o->w() > br) br = myo->o->x()+myo->o->w(); + if (myo->o->y()+myo->o->h() > bt) bt = myo->o->y()+myo->o->h(); + } + recalc = 0; + sx = bx; sy = by; sr = br; st = bt; + } + fl_color(FL_RED); + if (drag==FD_BOX && (x1 != mx || y1 != my)) { + int x = x1; int r = mx; if (x > r) {x = mx; r = x1;} + int y = y1; int b = my; if (y > b) {y = my; b = y1;} + fl_rect(x,y,r-x,b-y); + } + if (overlays_invisible && !drag) return; + + if (show_restricted) { + draw_out_of_bounds(); + draw_overlaps(); + // TODO: for Fl_Tile, find all areas that are not covered by visible children + } + + if (selected) fl_rect(0,0,o->w(),o->h()); + if (!numselected) return; + int mybx,myby,mybr,mybt; + int mysx,mysy,mysr,myst; + mybx = mysx = o->w(); myby = mysy = o->h(); mybr = mysr = 0; mybt = myst = 0; + Fl_Type *selection = 0L; // special power for the first selected widget + for (Fl_Type *q=next; q && q->level>level; q = q->next) + if (q->selected && q->is_true_widget()) { + if (!selection) selection = q; + Fl_Widget_Type* myo = (Fl_Widget_Type*)q; + int x,y,r,t; + newposition(myo,x,y,r,t); + if (show_guides) { + // If we are in a drag operation, and the parent is a grid, show the grid overlay + if (drag && q->parent && q->parent->is_a(ID_Grid)) { + Fl_Grid_Proxy *grid = ((Fl_Grid_Proxy*)((Fl_Grid_Type*)q->parent)->o); + grid->draw_overlay(); + } + } + if (!show_guides || !drag || numselected != 1) { + if (Fl_Flex_Type::parent_is_flex(q) && Fl_Flex_Type::is_fixed(q)) { + Fl_Flex *flex = ((Fl_Flex*)((Fl_Flex_Type*)q->parent)->o); + Fl_Widget *wgt = myo->o; + if (flex->horizontal()) { + draw_width(wgt->x(), wgt->y()+15, wgt->x()+wgt->w(), FL_ALIGN_CENTER); + } else { + draw_height(wgt->x()+15, wgt->y(), wgt->y()+wgt->h(), FL_ALIGN_CENTER); + } + } else if (q->is_a(ID_Grid)) { + Fl_Grid_Proxy *grid = ((Fl_Grid_Proxy*)((Fl_Grid_Type*)q)->o); + grid->draw_overlay(); + } + fl_rect(x,y,r-x,t-y); + } + if (x < mysx) mysx = x; + if (y < mysy) mysy = y; + if (r > mysr) mysr = r; + if (t > myst) myst = t; + if (!(myo->o->align() & FL_ALIGN_INSIDE)) { + // Adjust left/right/top/bottom for top/bottom labels... + int ww, hh; + ww = (myo->o->align() & FL_ALIGN_WRAP) ? myo->o->w() : 0; + hh = myo->o->labelsize(); + myo->o->measure_label(ww, hh); + if (myo->o->align() & FL_ALIGN_TOP) y -= hh; + else if (myo->o->align() & FL_ALIGN_BOTTOM) t += hh; + else if (myo->o->align() & FL_ALIGN_LEFT) x -= ww + 4; + else if (myo->o->align() & FL_ALIGN_RIGHT) r += ww + 4; + } + if (x < mybx) mybx = x; + if (y < myby) myby = y; + if (r > mybr) mybr = r; + if (t > mybt) mybt = t; + } + if (selected) return; + + // align the snapping selection box with the box we draw. + sx = mysx; sy = mysy; sr = mysr; st = myst; + + // Draw selection box + resize handles... + // draw box including all labels + fl_focus_rect(mybx,myby,mybr-mybx,mybt-myby); // issue #816 + // draw box excluding labels + fl_rect(mysx,mysy,mysr-mysx,myst-mysy); + fl_rectf(mysx,mysy,5,5); + fl_rectf(mysr-5,mysy,5,5); + fl_rectf(mysr-5,myst-5,5,5); + fl_rectf(mysx,myst-5,5,5); + + if (show_guides && (drag & (FD_DRAG|FD_TOP|FD_LEFT|FD_BOTTOM|FD_RIGHT))) { + Fd_Snap_Data data = { dx, dy, sx, sy, sr, st, drag, 4, 4, dx, dy, (Fl_Widget_Type*)selection, this}; + Fd_Snap_Action::draw_all(data); + } +} + +extern Fl_Menu_Item Main_Menu[]; + +// Calculate new bounding box of selected widgets: +void Fl_Window_Type::fix_overlay() { + overlay_item->label("Hide O&verlays"); + if (overlay_button) overlay_button->label("Hide &Overlays"); + overlays_invisible = 0; + recalc = 1; + ((Overlay_Window *)(this->o))->redraw_overlay(); +} + +// check if we must redraw any parent of tabs/wizard type +void check_redraw_corresponding_parent(Fl_Type *s) { + Fl_Widget_Type * prev_parent = 0; + if( !s || !s->selected || !s->is_widget()) return; + for (Fl_Type *i=s; i && i->parent; i=i->parent) { + if (i->is_a(ID_Group) && prev_parent) { + if (i->is_a(ID_Tabs)) { + ((Fl_Tabs*)((Fl_Widget_Type*)i)->o)->value(prev_parent->o); + return; + } + if (i->is_a(ID_Wizard)) { + ((Fl_Wizard*)((Fl_Widget_Type*)i)->o)->value(prev_parent->o); + return; + } + } + if (i->is_a(ID_Group) && s->is_widget()) + prev_parent = (Fl_Widget_Type*)i; + } +} + +// do that for every window (when selected set changes): +void redraw_overlays() { + for (Fl_Type *o=Fl_Type::first; o; o=o->next) + if (o->is_a(ID_Window)) ((Fl_Window_Type*)o)->fix_overlay(); +} + +void toggle_overlays(Fl_Widget *,void *) { + overlays_invisible = !overlays_invisible; + + if (overlays_invisible) { + overlay_item->label("Show O&verlays"); + if (overlay_button) overlay_button->label("Show &Overlays"); + } else { + overlay_item->label("Hide O&verlays"); + if (overlay_button) overlay_button->label("Hide &Overlays"); + } + + for (Fl_Type *o=Fl_Type::first; o; o=o->next) + if (o->is_a(ID_Window)) { + Fl_Widget_Type* w = (Fl_Widget_Type*)o; + ((Overlay_Window*)(w->o))->redraw_overlay(); + } +} + +/** + \brief User changes settings to show positioning guides in layout editor overlay. + This is called from the main menu and from the check button in the Settings + dialog. + */ +void toggle_guides(Fl_Widget *,void *) { + show_guides = !show_guides; + fluid_prefs.set("show_guides", show_guides); + + if (show_guides) + guides_item->label("Hide Guides"); + else + guides_item->label("Show Guides"); + if (guides_button) + guides_button->value(show_guides); + + for (Fl_Type *o=Fl_Type::first; o; o=o->next) { + if (o->is_a(ID_Window)) { + Fl_Widget_Type* w = (Fl_Widget_Type*)o; + ((Overlay_Window*)(w->o))->redraw_overlay(); + } + } +} + +/** + \brief User changes settings to show positioning guides in layout editor overlay. + This is called from the check button in the Settings dialog. + */ +void toggle_guides_cb(Fl_Check_Button *o, void *v) { + toggle_guides(NULL, NULL); +} + +/** + \brief User changes settings to show overlapping and out of bounds widgets. + This is called from the main menu and from the check button in the Settings + dialog. + */ +void toggle_restricted(Fl_Widget *,void *) { + show_restricted = !show_restricted; + fluid_prefs.set("show_restricted", show_restricted); + + if (show_restricted) + restricted_item->label("Hide Restricted"); + else + restricted_item->label("Show Restricted"); + if (restricted_button) + restricted_button->value(show_restricted); + + for (Fl_Type *o=Fl_Type::first; o; o=o->next) { + if (o->is_a(ID_Window)) { + Fl_Widget_Type* w = (Fl_Widget_Type*)o; + ((Overlay_Window*)(w->o))->redraw_overlay(); + } + } +} + +/** + \brief User changes settings to show low contrast groups with a ghosted outline. + */ +void toggle_ghosted_outline_cb(Fl_Check_Button *,void *) { + show_ghosted_outline = !show_ghosted_outline; + fluid_prefs.set("show_ghosted_outline", show_ghosted_outline); + for (Fl_Type *o=Fl_Type::first; o; o=o->next) { + if (o->is_a(ID_Window)) { + Fl_Widget_Type* w = (Fl_Widget_Type*)o; + ((Overlay_Window*)(w->o))->redraw(); + } + } +} + +/** + \brief User changes settings to show overlapping and out of bounds widgets. + This is called from the check button in the Settings dialog. + */ +void toggle_restricted_cb(Fl_Check_Button *o, void *v) { + toggle_restricted(NULL, NULL); +} + +extern void select(Fl_Type *,int); +extern void select_only(Fl_Type *); +extern void deselect(); +extern Fl_Type* in_this_only; +extern void fix_group_size(Fl_Type *t); + +extern Fl_Menu_Item Main_Menu[]; +extern Fl_Menu_Item New_Menu[]; + +/** + Move the selected children according to current dx, dy, drag state. + + This is somewhat of a do-all function that received many additions when new + widget types were added. In the default case, moving a group will simply move + all descendants with it. When resizing, children are resized to fit within + the group. + + This is not ideal for widgets that are moved or resized within a group that + manages the layout of its children. We must create a more universal way to + modify move events per widget type. + + \param[in] key if key is not 0, it contains the code of the keypress that + caused this call. This must only be set when handle FL_KEYBOARD events. + */ +void Fl_Window_Type::moveallchildren(int key) +{ + bool update_widget_panel = false; + undo_checkpoint(); + Fl_Type *i; + for (i=next; i && i->level>level;) { + if (i->selected && i->is_true_widget()) { + Fl_Widget_Type* myo = (Fl_Widget_Type*)i; + int x,y,r,t,ow=myo->o->w(),oh=myo->o->h(); + newposition(myo,x,y,r,t); + if (myo->is_a(ID_Flex) || myo->is_a(ID_Grid)) { + // Flex and Grid need to be able to layout their children. + allow_layout++; + myo->o->resize(x,y,r-x,t-y); + allow_layout--; + } else { + // Other groups are resized without affecting their children, however + // they move their children if the entire widget is moved. + myo->o->resize(x,y,r-x,t-y); + } + if (Fl_Flex_Type::parent_is_flex(myo)) { + // If the border of a Flex child is move, give that child a fixed size + // so that the user request is reflected. + Fl_Flex_Type* ft = (Fl_Flex_Type*)myo->parent; + Fl_Flex* f = (Fl_Flex*)ft->o; + if (key) { + ft->keyboard_move_child(myo, key); + } else if (drag & FD_DRAG) { + ft->insert_child_at(myo->o, Fl::event_x(), Fl::event_y()); + } else { + if (f->horizontal()) { + if (myo->o->w()!=ow) { + f->fixed(myo->o, myo->o->w()); + f->layout(); + } + } else { + if (myo->o->h()!=oh) { + f->fixed(myo->o, myo->o->h()); + f->layout(); + } + } + } + // relayout the Flex parent + allow_layout++; + f->layout(); + allow_layout--; + } else if (myo->parent && myo->parent->is_a(ID_Grid)) { + Fl_Grid_Type* gt = (Fl_Grid_Type*)myo->parent; + Fl_Grid* g = (Fl_Grid*)gt->o; + if (key) { + gt->keyboard_move_child(myo, key); + } else { + if (drag & FD_DRAG) { + gt->insert_child_at(myo->o, Fl::event_x(), Fl::event_y()); + } else { + gt->child_resized(myo); + } + } + allow_layout++; + g->layout(); + allow_layout--; + update_widget_panel = true; + } else if (myo->parent && myo->parent->is_a(ID_Group)) { + Fl_Group_Type* gt = (Fl_Group_Type*)myo->parent; + ((Fl_Group*)gt->o)->init_sizes(); + } + // move all the children, whether selected or not: + Fl_Type* p; + for (p = myo->next; p && p->level>myo->level; p = p->next) + if (p->is_true_widget() && !myo->is_a(ID_Flex) && !myo->is_a(ID_Grid)) { + Fl_Widget_Type* myo2 = (Fl_Widget_Type*)p; + int X,Y,R,T; + newposition(myo2,X,Y,R,T); + myo2->o->resize(X,Y,R-X,T-Y); + } + i = p; + } else { + i = i->next; + } + } + for (i=next; i && i->level>level; i=i->next) + fix_group_size(i); + o->redraw(); + recalc = 1; + ((Overlay_Window *)(this->o))->redraw_overlay(); + set_modflag(1); + dx = dy = 0; + + update_xywh(); + if (update_widget_panel && the_panel && the_panel->visible()) { + propagate_load(the_panel, LOAD); + } +} + +int Fl_Window_Type::popupx = 0x7FFFFFFF; // mark as invalid (MAXINT) +int Fl_Window_Type::popupy = 0x7FFFFFFF; + +int Fl_Window_Type::handle(int event) { + static Fl_Type* selection = NULL; + switch (event) { + case FL_DND_ENTER: + // printf("DND enter\n"); + case FL_DND_DRAG: + // printf("DND drag\n"); + { + // find the innermost item clicked on: + selection = this; + for (Fl_Type* i=next; i && i->level>level; i=i->next) + if (i->is_a(ID_Group)) { + Fl_Widget_Type* myo = (Fl_Widget_Type*)i; + if (Fl::event_inside(myo->o) && myo->o->visible_r()) { + selection = myo; + if (Fl::event_clicks()==1) + reveal_in_browser(myo); + } + } + if (selection && !selection->selected) { + select_only(selection); + ((Overlay_Window *)o)->redraw_overlay(); + } + } + Fl::belowmouse(o); + return 1; + case FL_DND_RELEASE: + // printf("DND release\n"); + Fl::belowmouse(o); + return 1; + case FL_PASTE: + // printf("DND paste\n"); + { Fl_Type *prototype = typename_to_prototype(Fl::event_text()); + if (prototype==NULL) { + // it's not a FLUID type, so it could be the filename of an image + const char *cfn = Fl::event_text(); + // printf("DND is filename %s?\n", cfn); + if ((cfn == NULL) || (*cfn == 0)) return 0; + if (strlen(cfn) >= FL_PATH_MAX) return 0; + char fn[FL_PATH_MAX+1]; + // some platform prepend "file://" or "computer://" or similar text + const char *sep = strstr(cfn, "://"); + if (sep) + strcpy(fn, sep+3); + else + strcpy(fn, cfn); + // remove possibly trailing \r\n + int n = (int)strlen(fn)-1; + if (fn[n] == '\n') fn[n--] = 0; + if (fn[n] == '\r') fn[n--] = 0; + // on X11 and Wayland (?), filenames need to be decoded +#if (defined(FLTK_USE_X11) || defined(FLTK_USE_WAYLAND)) + fl_decode_uri(fn); +#endif + // does a file by that name actually exist? + if (fl_access(fn, 4)==-1) return 0; + // but is this an image file? + Fl_Image *img = Fl_Shared_Image::get(fn); + if (!img || (img->ld() < 0)) return 0; + // ok, so it is an image - now add it as image() or deimage() to the widget + // printf("DND check for target %s\n", fn); + Fl_Widget_Type *tgt = NULL; + for (Fl_Type* i=next; i && i->level>level; i=i->next) { + if (i->is_widget()) { + Fl_Widget_Type* myo = (Fl_Widget_Type*)i; + if (Fl::event_inside(myo->o) && myo->o->visible_r()) + tgt = myo; + } + } + if (tgt) { + char rel[FL_PATH_MAX+1]; + enter_project_dir(); + fl_filename_relative(rel, FL_PATH_MAX, fn); + leave_project_dir(); + // printf("DND image = %s\n", fn); + if (Fl::get_key(FL_Alt_L) || Fl::get_key(FL_Alt_R)) { + //if (Fl::event_alt()) { // TODO: X11/Wayland does not set the e_state on DND events + tgt->inactive_name(rel); + tgt->compress_deimage_ = 1; + tgt->bind_deimage_ = 0; + } else { + tgt->image_name(rel); + tgt->compress_image_ = 1; + tgt->bind_image_ = 0; + } + select_only(tgt); + tgt->open(); + } + return 1; + } + + in_this_only = this; + popupx = Fl::event_x(); + popupy = Fl::event_y(); + // If the selected widget at dnd start and the drop target are the same, + // or in the same group, add after selection. Otherwise, just add + // at the end of the selected group. + if ( Fl_Type::current_dnd->group() + && selection && selection->group() + && Fl_Type::current_dnd->group()==selection->group()) + { + Fl_Type *cc = Fl_Type::current; + Fl_Type::current = Fl_Type::current_dnd; + add_new_widget_from_user(prototype, Strategy::AS_LAST_CHILD); + Fl_Type::current = cc; + } else { + add_new_widget_from_user(prototype, Strategy::AS_LAST_CHILD); + } + popupx = 0x7FFFFFFF; + popupy = 0x7FFFFFFF; // mark as invalid (MAXINT) + in_this_only = NULL; + widget_browser->display(Fl_Type::current); + widget_browser->rebuild(); + return 1; + } + case FL_PUSH: + x1 = mx = Fl::event_x(); + y1 = my = Fl::event_y(); + drag = dx = dy = 0; + // test for popup menu: + if (Fl::event_button() >= 3) { + in_this_only = this; // modifies how some menu items work. + static const Fl_Menu_Item* myprev; + popupx = mx; popupy = my; + const Fl_Menu_Item* m = New_Menu->popup(mx,my,"New",myprev); + if (m && m->callback()) {myprev = m; m->do_callback(this->o);} + popupx = 0x7FFFFFFF; popupy = 0x7FFFFFFF; // mark as invalid (MAXINT) + in_this_only = 0; + return 1; + } + // find the innermost item clicked on: + selection = this; + {for (Fl_Type* i=next; i && i->level>level; i=i->next) + if (i->is_true_widget()) { + Fl_Widget_Type* myo = (Fl_Widget_Type*)i; + for (Fl_Widget *o1 = myo->o; o1; o1 = o1->parent()) + if (!o1->visible()) goto CONTINUE2; + if (Fl::event_inside(myo->o)) { + selection = myo; + if (Fl::event_clicks()==1) + reveal_in_browser(myo); + } + CONTINUE2:; + }} + // see if user grabs edges of selected region: + if (numselected && !(Fl::event_state(FL_SHIFT)) && + mx<=br+2 && mx>=bx-2 && my<=bt+2 && my>=by-2) { + if (mx >= br-5) drag |= FD_RIGHT; + else if (mx <= bx+5) drag |= FD_LEFT; + if (my >= bt-5) drag |= FD_BOTTOM; + else if (my <= by+5) drag |= FD_TOP; + if (!drag) drag = FD_DRAG; + } + // do object-specific selection of other objects: + {Fl_Type* t = selection->click_test(mx, my); + if (t) { + //if (t == selection) return 1; // indicates mouse eaten w/o change + if (Fl::event_state(FL_SHIFT)) { + Fl::event_is_click(0); + select(t, !t->selected); + } else { + deselect(); + select(t, 1); + if (t->is_a(ID_Menu_Item)) t->open(); + } + selection = t; + drag = 0; + } else { + if (!drag) drag = FD_BOX; // if all else fails, start a new selection region + }} + return 1; + + case FL_DRAG: + if (!drag) return 0; + mx = Fl::event_x(); + my = Fl::event_y(); + newdx(); + return 1; + + case FL_RELEASE: + if (!drag) return 0; + mx = Fl::event_x(); + my = Fl::event_y(); + if (drag != FD_BOX && (dx || dy || !Fl::event_is_click())) { + if (dx || dy) moveallchildren(); + } else if ((Fl::event_clicks() || Fl::event_state(FL_CTRL))) { + Fl_Widget_Type::open(); + } else { + if (mxlevel>level; i=i->next) + if (i->is_true_widget()) { + Fl_Widget_Type* myo = (Fl_Widget_Type*)i; + for (Fl_Widget *o1 = myo->o; o1; o1 = o1->parent()) + if (!o1->visible()) goto CONTINUE; + if (Fl::event_inside(myo->o)) selection = myo; + if (myo && myo->o && myo->o->x()>=x1 && myo->o->y()>y1 && + myo->o->x()+myo->o->w()o->y()+myo->o->h()selected : 1); + } + CONTINUE:; + } + // if nothing in box, select what was clicked on: + if (selection && !n) { + select(selection, toggle ? !selection->selected : 1); + } + } + drag = 0; + ((Overlay_Window *)o)->redraw_overlay(); + return 1; + + case FL_KEYBOARD: { + + int backtab = 0; + switch (Fl::event_key()) { + + case FL_Escape: + ((Fl_Window*)o)->hide(); + return 1; + + case FL_Tab: { + if (Fl::event_state(FL_SHIFT)) backtab = 1; + // find current child: + Fl_Type *i = Fl_Type::current; + while (i && !i->is_true_widget()) i = i->parent; + if (!i) return 0; + Fl_Type *p = i->parent; + while (p && p != this) p = p->parent; + if (!p || !p->is_widget()) { + i = next; if (!i || i->level <= level) return 0; + } + p = i; + for (;;) { + i = backtab ? i->prev : i->next; + if (!i || i->level <= level) {i = p; break;} + if (i->is_true_widget()) break; + } + deselect(); select(i,1); + return 1;} + + case FL_Left: dx = -1; dy = 0; goto ARROW; + case FL_Right: dx = +1; dy = 0; goto ARROW; + case FL_Up: dx = 0; dy = -1; goto ARROW; + case FL_Down: dx = 0; dy = +1; goto ARROW; + ARROW: + drag = (Fl::event_state(FL_SHIFT)) ? (FD_RIGHT|FD_BOTTOM) : FD_DRAG; + if (Fl::event_state(FL_COMMAND)) { + int x_step, y_step; + if (drag & (FD_RIGHT|FD_BOTTOM)) + Fd_Snap_Action::get_resize_stepsize(x_step, y_step); + else + Fd_Snap_Action::get_move_stepsize(x_step, y_step); + dx *= x_step; + dy *= y_step; + } + moveallchildren(Fl::event_key()); + drag = 0; + return 1; + + case 'o': + toggle_overlays(0, 0); + break; + + default: + return 0; + }} + + case FL_SHORTCUT: { + in_this_only = this; // modifies how some menu items work. + const Fl_Menu_Item* m = Main_Menu->test_shortcut(); + if (m && m->callback()) m->do_callback(this->o); + in_this_only = 0; + return (m != 0);} + + default: + return 0; + } +} + +//////////////////////////////////////////////////////////////// + + +/** + Write the C++ code that comes before the children of the window are written. + \param f the source code output stream + */ +void Fl_Window_Type::write_code1(Fd_Code_Writer& f) { + Fl_Widget_Type::write_code1(f); +} + + +/** + Write the C++ code that comes after the children of the window are written. + \param f the source code output stream + */ +void Fl_Window_Type::write_code2(Fd_Code_Writer& f) { + const char *var = is_class() ? "this" : name() ? name() : "o"; + // make the window modal or non-modal + if (modal) { + f.write_c("%s%s->set_modal();\n", f.indent(), var); + } else if (non_modal) { + f.write_c("%s%s->set_non_modal();\n", f.indent(), var); + } + // clear the window border + if (!((Fl_Window*)o)->border()) { + f.write_c("%s%s->clear_border();\n", f.indent(), var); + } + // set the xclass of the window + if (xclass) { + f.write_c("%s%s->xclass(", f.indent(), var); + f.write_cstring(xclass); + f.write_c(");\n"); + } + // make the window resizable + if (((Fl_Window*)o)->resizable() == o) + f.write_c("%s%s->resizable(%s);\n", f.indent(), var, var); + // set the size range last + if (sr_max_w || sr_max_h) { + f.write_c("%s%s->size_range(%d, %d, %d, %d);\n", f.indent(), var, + sr_min_w, sr_min_h, sr_max_w, sr_max_h); + } else if (sr_min_w || sr_min_h) { + f.write_c("%s%s->size_range(%d, %d);\n", f.indent(), var, sr_min_w, sr_min_h); + } + // insert extra code from user, may call `show()` + write_extra_code(f); + // stop adding widgets to this window + f.write_c("%s%s->end();\n", f.indent(), var); + write_block_close(f); +} + +void Fl_Window_Type::write_properties(Fd_Project_Writer &f) { + Fl_Widget_Type::write_properties(f); + if (modal) f.write_string("modal"); + else if (non_modal) f.write_string("non_modal"); + if (!((Fl_Window*)o)->border()) f.write_string("noborder"); + if (xclass) {f.write_string("xclass"); f.write_word(xclass);} + if (sr_min_w || sr_min_h || sr_max_w || sr_max_h) + f.write_string("size_range {%d %d %d %d}", sr_min_w, sr_min_h, sr_max_w, sr_max_h); + if (o->visible() || override_visible_) f.write_string("visible"); +} + +void Fl_Window_Type::read_property(Fd_Project_Reader &f, const char *c) { + if (!strcmp(c,"modal")) { + modal = 1; + } else if (!strcmp(c,"non_modal")) { + non_modal = 1; + } else if (!strcmp(c, "visible")) { + if (batch_mode) // don't actually open any windows in batch mode + override_visible_ = 1; + else // in interactive mode, we simply show the window + open_(); + } else if (!strcmp(c,"noborder")) { + ((Fl_Window*)o)->border(0); + } else if (!strcmp(c,"xclass")) { + storestring(f.read_word(),xclass); + ((Fl_Window*)o)->xclass(xclass); + } else if (!strcmp(c,"size_range")) { + int mw, mh, MW, MH; + if (sscanf(f.read_word(),"%d %d %d %d",&mw,&mh,&MW,&MH) == 4) { + sr_min_w = mw; sr_min_h = mh; sr_max_w = MW; sr_max_h = MH; + } + } else if (!strcmp(c,"xywh")) { + Fl_Widget_Type::read_property(f, c); + pasteoffset = 0; // make it not apply to contents + } else { + Fl_Widget_Type::read_property(f, c); + } +} + +int Fl_Window_Type::read_fdesign(const char* propname, const char* value) { + int x; + o->box(FL_NO_BOX); // because fdesign always puts an Fl_Box next + if (!strcmp(propname,"Width")) { + if (sscanf(value,"%d",&x) == 1) o->size(x,o->h()); + } else if (!strcmp(propname,"Height")) { + if (sscanf(value,"%d",&x) == 1) o->size(o->w(),x); + } else if (!strcmp(propname,"NumberofWidgets")) { + return 1; // we can figure out count from file + } else if (!strcmp(propname,"border")) { + if (sscanf(value,"%d",&x) == 1) ((Fl_Window*)o)->border(x); + } else if (!strcmp(propname,"title")) { + label(value); + } else { + return Fl_Widget_Type::read_fdesign(propname,value); + } + return 1; +} + +/////////////////////////////////////////////////////////////////////// + +Fl_Widget_Class_Type Fl_Widget_Class_type; +Fl_Widget_Class_Type *current_widget_class = 0; + +/** + Create and add a new Widget Class node. + \param[in] strategy add after current or as last child + \return new node + */ +Fl_Type *Fl_Widget_Class_Type::make(Strategy strategy) { + Fl_Type *anchor = Fl_Type::current, *p = anchor; + if (p && (strategy.placement() == Strategy::AFTER_CURRENT)) p = p->parent; + while (p && (!p->is_decl_block() || (p->is_widget() && p->is_class()))) { + anchor = p; + strategy.placement(Strategy::AFTER_CURRENT); + p = p->parent; + } + Fl_Widget_Class_Type *myo = new Fl_Widget_Class_Type(); + myo->name("UserInterface"); + + if (!this->o) {// template widget + this->o = new Fl_Window(100,100); + Fl_Group::current(0); + } + myo->factory = this; + myo->drag = 0; + myo->numselected = 0; + Overlay_Window *w = new Overlay_Window(100, 100); + w->size_range(10, 10); + w->window = myo; + myo->o = w; + myo->add(anchor, strategy); + myo->modal = 0; + myo->non_modal = 0; + myo->wc_relative = 0; + + return myo; +} + +void Fl_Widget_Class_Type::write_properties(Fd_Project_Writer &f) { + Fl_Window_Type::write_properties(f); + if (wc_relative==1) + f.write_string("position_relative"); + else if (wc_relative==2) + f.write_string("position_relative_rescale"); +} + +void Fl_Widget_Class_Type::read_property(Fd_Project_Reader &f, const char *c) { + if (!strcmp(c,"position_relative")) { + wc_relative = 1; + } else if (!strcmp(c,"position_relative_rescale")) { + wc_relative = 2; + } else { + Fl_Window_Type::read_property(f, c); + } +} + +// Convert A::B::C::D to D (i.e. keep only innermost name) +// This is useful for classes that contain a namespace component +static const char *trimclassname(const char *n) { + if (!n) + return NULL; + const char *nn; + while((nn = strstr(n, "::"))) { + n = nn + 2; + } + return(n); +} + + +void Fl_Widget_Class_Type::write_code1(Fd_Code_Writer& f) { +#if 0 + Fl_Widget_Type::write_code1(Fd_Code_Writer& f); +#endif // 0 + + current_widget_class = this; + write_public_state = 1; + + const char *c = subclass(); + if (!c) c = "Fl_Group"; + + f.write_c("\n"); + write_comment_h(f); + f.write_h("\nclass %s : public %s {\n", name(), c); + if (strstr(c, "Window")) { + f.write_h("%svoid _%s();\n", f.indent(1), trimclassname(name())); + f.write_h("public:\n"); + f.write_h("%s%s(int X, int Y, int W, int H, const char *L = 0);\n", f.indent(1), trimclassname(name())); + f.write_h("%s%s(int W, int H, const char *L = 0);\n", f.indent(1), trimclassname(name())); + f.write_h("%s%s();\n", f.indent(1), trimclassname(name())); + + // a constructor with all four dimensions plus label + f.write_c("%s::%s(int X, int Y, int W, int H, const char *L) :\n", name(), trimclassname(name())); + f.write_c("%s%s(X, Y, W, H, L)\n{\n", f.indent(1), c); + f.write_c("%s_%s();\n", f.indent(1), trimclassname(name())); + f.write_c("}\n\n"); + + // a constructor with just the size and label. The window manager will position the window + f.write_c("%s::%s(int W, int H, const char *L) :\n", name(), trimclassname(name())); + f.write_c("%s%s(0, 0, W, H, L)\n{\n", f.indent(1), c); + f.write_c("%sclear_flag(16);\n", f.indent(1)); + f.write_c("%s_%s();\n", f.indent(1), trimclassname(name())); + f.write_c("}\n\n"); + + // a constructor that takes size and label from the Fluid database + f.write_c("%s::%s() :\n", name(), trimclassname(name())); + f.write_c("%s%s(0, 0, %d, %d, ", f.indent(1), c, o->w(), o->h()); + const char *cstr = label(); + if (cstr) f.write_cstring(cstr); + else f.write_c("0"); + f.write_c(")\n{\n"); + f.write_c("%sclear_flag(16);\n", f.indent(1)); + f.write_c("%s_%s();\n", f.indent(1), trimclassname(name())); + f.write_c("}\n\n"); + + f.write_c("void %s::_%s() {\n", name(), trimclassname(name())); +// f.write_c("%s%s *w = this;\n", f.indent(1), name()); + } else { + f.write_h("public:\n"); + f.write_h("%s%s(int X, int Y, int W, int H, const char *L = 0);\n", + f.indent(1), trimclassname(name())); + f.write_c("%s::%s(int X, int Y, int W, int H, const char *L) :\n", name(), trimclassname(name())); + if (wc_relative==1) + f.write_c("%s%s(0, 0, W, H, L)\n{\n", f.indent(1), c); + else if (wc_relative==2) + f.write_c("%s%s(0, 0, %d, %d, L)\n{\n", f.indent(1), c, o->w(), o->h()); + else + f.write_c("%s%s(X, Y, W, H, L)\n{\n", f.indent(1), c); + } + +// f.write_c("%s%s *o = this;\n", f.indent(1), name()); + + f.indentation++; + write_widget_code(f); +} + +/** + Write the C++ code that comes after the children of the window are written. + \param f the source code output stream + */ +void Fl_Widget_Class_Type::write_code2(Fd_Code_Writer& f) { + // make the window modal or non-modal + if (modal) { + f.write_c("%sset_modal();\n", f.indent()); + } else if (non_modal) { + f.write_c("%sset_non_modal();\n", f.indent()); + } + // clear the window border + if (!((Fl_Window*)o)->border()) f.write_c("%sclear_border();\n", f.indent()); + // set the xclass of the window + if (xclass) { + f.write_c("%sxclass(", f.indent()); + f.write_cstring(xclass); + f.write_c(");\n"); + } + // make the window resizable + if (((Fl_Window*)o)->resizable() == o) + f.write_c("%sresizable(this);\n", f.indent()); + // insert extra code from user + write_extra_code(f); + // stop adding widgets to this window + f.write_c("%send();\n", f.indent()); + // reposition or resize the Widget Class to fit into the target + if (wc_relative==1) + f.write_c("%sposition(X, Y);\n", f.indent()); + else if (wc_relative==2) + f.write_c("%sresize(X, Y, W, H);\n", f.indent()); + f.indentation--; + f.write_c("}\n"); +} + + +//////////////////////////////////////////////////////////////// +// live mode support + +Fl_Widget *Fl_Window_Type::enter_live_mode(int) { + Fl_Window *win = new Fl_Window(10, 10, o->w(), o->h()); + return propagate_live_mode(win); +} + +void Fl_Window_Type::leave_live_mode() { +} + +/** + copy all properties from the edit widget to the live widget + */ +void Fl_Window_Type::copy_properties() { + Fl_Window *self = static_cast(o); + Fl_Window *live = static_cast(live_widget); + if (self->resizable() == self) + live->resizable(live); + Fl_Widget_Type::copy_properties(); +} diff --git a/fluid/nodes/Fl_Window_Type.h b/fluid/nodes/Fl_Window_Type.h new file mode 100644 index 000000000..a0695f2a1 --- /dev/null +++ b/fluid/nodes/Fl_Window_Type.h @@ -0,0 +1,157 @@ +// +// Window type header file for the Fast Light Tool Kit (FLTK). +// +// Type for creating all subclasses of Fl_Widget +// This should have the widget pointer in it, but it is still in the +// Fl_Type base class. +// +// Copyright 1998-2023 by Bill Spitzak and others. +// +// This library is free software. Distribution and use rights are outlined in +// the file "COPYING" which should have been included with this file. If this +// file is missing or damaged, see the license at: +// +// https://www.fltk.org/COPYING.php +// +// Please see the following page on how to report bugs and issues: +// +// https://www.fltk.org/bugs.php +// + +#ifndef _FLUID_FL_WINDOW_TYPE_H +#define _FLUID_FL_WINDOW_TYPE_H + +#include "nodes/Fl_Group_Type.h" + +class Fl_Widget_Class_Type; + +extern Fl_Menu_Item window_type_menu[]; +extern Fl_Widget_Class_Type *current_widget_class; + +void toggle_overlays(Fl_Widget *,void *); +void toggle_guides(Fl_Widget *,void *); +void toggle_restricted(Fl_Widget *,void *); +void show_project_cb(Fl_Widget *, void *); +void show_grid_cb(Fl_Widget *, void *); +void show_settings_cb(Fl_Widget *, void *); + +enum { + FD_LEFT = 1, // user drags the left side of the selection box + FD_RIGHT = 2, + FD_BOTTOM = 4, + FD_TOP = 8, + FD_DRAG = 16, // user drags the entire selection + FD_BOX = 32 // user creates a new selection box +}; + +class Fl_Window_Type : public Fl_Group_Type +{ + typedef Fl_Group_Type super; +protected: + + Fl_Menu_Item* subtypes() FL_OVERRIDE {return window_type_menu;} + + friend class Overlay_Window; + int mx,my; // mouse position during dragging + int x1,y1; // initial position of selection box + int bx,by,br,bt; // bounding box of selection before snapping + int sx,sy,sr,st; // bounding box of selection after snapping to guides + int dx,dy; + int drag; // which parts of bbox are being moved + int numselected; // number of children selected + void draw_out_of_bounds(Fl_Widget_Type *group, int x, int y, int w, int h); + void draw_out_of_bounds(); + void draw_overlaps(); + void draw_overlay(); + void newdx(); + void newposition(Fl_Widget_Type *,int &x,int &y,int &w,int &h); + int handle(int); + void setlabel(const char *) FL_OVERRIDE; + void write_code1(Fd_Code_Writer& f) FL_OVERRIDE; + void write_code2(Fd_Code_Writer& f) FL_OVERRIDE; + Fl_Widget_Type *_make() FL_OVERRIDE {return 0;} // we don't call this + Fl_Widget *widget(int,int,int,int) FL_OVERRIDE {return 0;} + int recalc; // set by fix_overlay() + void moveallchildren(int key=0); + ID id() const FL_OVERRIDE { return ID_Window; } + bool is_a(ID inID) const FL_OVERRIDE { return (inID==ID_Window) ? true : super::is_a(inID); } + void open_(); + +public: + + Fl_Window_Type() : + mx(0), my(0), + x1(0), y1(0), + bx(0), by(0), br(0), bt(0), + sx(0), sy(0), sr(0), st(0), + dx(0), dy(0), + drag(0), + numselected(0), + recalc(0), + modal(0), non_modal(0), + xclass(NULL), + sr_min_w(0), sr_min_h(0), sr_max_w(0), sr_max_h(0) + { } + uchar modal, non_modal; + const char *xclass; // junk string, used for shortcut + + Fl_Type *make(Strategy strategy) FL_OVERRIDE; + const char *type_name() FL_OVERRIDE {return "Fl_Window";} + const char *alt_type_name() FL_OVERRIDE {return "fltk::Window";} + + void open() FL_OVERRIDE; + void ideal_size(int &w, int &h) FL_OVERRIDE; + + void fix_overlay(); // Update the bounding box, etc + uchar *read_image(int &ww, int &hh); // Read an image of the window + + void write_properties(Fd_Project_Writer &f) FL_OVERRIDE; + void read_property(Fd_Project_Reader &f, const char *) FL_OVERRIDE; + int read_fdesign(const char*, const char*) FL_OVERRIDE; + + void add_child(Fl_Type*, Fl_Type*) FL_OVERRIDE; + void move_child(Fl_Type*, Fl_Type*) FL_OVERRIDE; + void remove_child(Fl_Type*) FL_OVERRIDE; + + int can_have_children() const FL_OVERRIDE {return 1;} + + Fl_Widget *enter_live_mode(int top=0) FL_OVERRIDE; + void leave_live_mode() FL_OVERRIDE; + void copy_properties() FL_OVERRIDE; + + int sr_min_w, sr_min_h, sr_max_w, sr_max_h; + + static int popupx, popupy; +}; + +class Fl_Widget_Class_Type : private Fl_Window_Type +{ + typedef Fl_Window_Type super; +protected: + Fl_Menu_Item* subtypes() FL_OVERRIDE {return 0;} + +public: + Fl_Widget_Class_Type() { + write_public_state = 0; + wc_relative = 0; + } + // state variables for output: + char write_public_state; // true when public: has been printed + char wc_relative; // if 1, reposition all children, if 2, reposition and resize + + void write_properties(Fd_Project_Writer &f) FL_OVERRIDE; + void read_property(Fd_Project_Reader &f, const char *) FL_OVERRIDE; + + void write_code1(Fd_Code_Writer& f) FL_OVERRIDE; + void write_code2(Fd_Code_Writer& f) FL_OVERRIDE; + Fl_Type *make(Strategy strategy) FL_OVERRIDE; + const char *type_name() FL_OVERRIDE {return "widget_class";} + ID id() const FL_OVERRIDE { return ID_Widget_Class; } + bool is_a(ID inID) const FL_OVERRIDE { return (inID==ID_Widget_Class) ? true : super::is_a(inID); } + int can_have_children() const FL_OVERRIDE {return 1;} + int is_code_block() const FL_OVERRIDE {return 1;} + int is_decl_block() const FL_OVERRIDE {return 1;} + int is_class() const FL_OVERRIDE {return 1;} +}; + +#endif // _FLUID_FL_WINDOW_TYPE_H diff --git a/fluid/nodes/factory.cxx b/fluid/nodes/factory.cxx new file mode 100644 index 000000000..ad5d388e1 --- /dev/null +++ b/fluid/nodes/factory.cxx @@ -0,0 +1,1718 @@ +// +// Widget factory code for the Fast Light Tool Kit (FLTK). +// +// Type classes for most of the fltk widgets. Most of the work +// is done by code in Fl_Widget_Type.cxx. Also a factory instance +// of each of these type classes. +// +// This file also contains the "new" menu, which has a pointer +// to a factory instance for every class (both the ones defined +// here and ones in other files) +// +// 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/factory.h" + +#include "app/Fd_Snap_Action.h" +#include "app/fluid.h" +#include "app/undo.h" +#include "nodes/Fl_Group_Type.h" +#include "nodes/Fl_Grid_Type.h" +#include "nodes/Fl_Menu_Type.h" +#include "rsrcs/pixmaps.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../src/flstring.h" + +#include +#include + + +// ---- Browser Types -------------------------------------------------- MARK: - + + +// ---- Browser_Base ---- + +static Fl_Menu_Item browser_base_type_menu[] = { + {"No Select", 0, 0, (void*)FL_NORMAL_BROWSER}, + {"Select", 0, 0, (void*)FL_SELECT_BROWSER}, + {"Hold", 0, 0, (void*)FL_HOLD_BROWSER}, + {"Multi", 0, 0, (void*)FL_MULTI_BROWSER}, + {0} +}; + +/** + \brief This is the base class for some browsers types. + This class will not be instantiated. + */ +class Fl_Browser_Base_Type : public Fl_Widget_Type +{ + typedef Fl_Widget_Type super; + Fl_Menu_Item *subtypes() FL_OVERRIDE { return browser_base_type_menu; } + int textstuff(int w, Fl_Font& f, int& s, Fl_Color& c) FL_OVERRIDE { + Fl_Browser_ *myo = (Fl_Browser_*)(w==4 ? ((Fl_Widget_Type*)factory)->o : o); + switch (w) { + case 4: + case 0: f = myo->textfont(); s = myo->textsize(); c = myo->textcolor(); break; + case 1: myo->textfont(f); break; + case 2: myo->textsize(s); break; + case 3: myo->textcolor(c); break; + } + return 1; + } +public: + void ideal_size(int &w, int &h) FL_OVERRIDE { + w = 120; + h = 160; + Fd_Snap_Action::better_size(w, h); + } + const char *type_name() FL_OVERRIDE { return "Fl_Browser_"; } + const char *alt_type_name() FL_OVERRIDE { return "fltk::Browser_"; } + Fl_Widget *widget(int x, int y, int w, int h) FL_OVERRIDE { + Fl_Browser* b = new Fl_Browser(x, y, w, h); + return b; + } + Fl_Widget_Type *_make() FL_OVERRIDE { return new Fl_Browser_Base_Type(); } + ID id() const FL_OVERRIDE { return ID_Browser_; } + bool is_a(ID inID) const FL_OVERRIDE { return (inID==ID_Browser_) ? true : super::is_a(inID); } +}; + +static Fl_Browser_Base_Type Fl_Browser_Base_type; + + +// ---- Browser ---- + +/** + \brief Handle a plain browser widget. + Most of the work is already done in Fl_Browser_Base_Type. + */ +class Fl_Browser_Type : public Fl_Browser_Base_Type +{ + typedef Fl_Browser_Base_Type super; +public: + const char *type_name() FL_OVERRIDE { return "Fl_Browser"; } + const char *alt_type_name() FL_OVERRIDE { return "fltk::Browser"; } + Fl_Widget *widget(int x, int y, int w, int h) FL_OVERRIDE { + Fl_Browser* b = new Fl_Browser(x, y, w, h); + // Fl_Browser::add calls fl_height(), which requires the X display open. + // Avoid this when compiling so it works w/o a display: + if (!batch_mode) { + char buffer[20]; + for (int i = 1; i <= 20; i++) { + sprintf(buffer,"Browser Line %d",i); + b->add(buffer); + } + } + return b; + } + Fl_Widget_Type *_make() FL_OVERRIDE { return new Fl_Browser_Type(); } + ID id() const FL_OVERRIDE { return ID_Browser; } + bool is_a(ID inID) const FL_OVERRIDE { return (inID==ID_Browser) ? true : super::is_a(inID); } +}; + +static Fl_Browser_Type Fl_Browser_type; + + +// ---- Check Browser ---- + +/** + \brief Manage the Check Browser. + The Fl_Check_Browser is derived form Fl_Browser_ (underline!), not Fl_Browser. + */ +class Fl_Check_Browser_Type : public Fl_Browser_Base_Type +{ + typedef Fl_Browser_Base_Type super; +public: + const char *type_name() FL_OVERRIDE { return "Fl_Check_Browser"; } + const char *alt_type_name() FL_OVERRIDE { return "fltk::CheckBrowser"; } + Fl_Widget *widget(int x, int y, int w, int h) FL_OVERRIDE { + Fl_Check_Browser* b = new Fl_Check_Browser(x, y, w, h); + // Fl_Check_Browser::add calls fl_height(), which requires the X display open. + // Avoid this when compiling so it works w/o a display: + if (!batch_mode) { + char buffer[20]; + for (int i = 1; i <= 20; i++) { + sprintf(buffer,"Browser Line %d",i); + b->add(buffer); + } + } + return b; + } + Fl_Widget_Type *_make() FL_OVERRIDE { return new Fl_Check_Browser_Type(); } + ID id() const FL_OVERRIDE { return ID_Check_Browser; } + bool is_a(ID inID) const FL_OVERRIDE { return (inID==ID_Check_Browser) ? true : super::is_a(inID); } +}; + +static Fl_Check_Browser_Type Fl_Check_Browser_type; + + +// ---- File Browser ---- + +/** + \brief Manage the File Browser, not to be confused with the file dialog. + As oppoesed to the Hold, Multi, and Select Browser, this is not a subclass, but + its own implementation, based on Fl_Browser. + */ +class Fl_File_Browser_Type : public Fl_Browser_Type +{ + typedef Fl_Browser_Type super; +public: + const char *type_name() FL_OVERRIDE { return "Fl_File_Browser"; } + const char *alt_type_name() FL_OVERRIDE { return "fltk::FileBrowser"; } + Fl_Widget *widget(int x, int y, int w, int h) FL_OVERRIDE { + Fl_File_Browser* b = new Fl_File_Browser(x, y, w, h); + if (!batch_mode) b->load("."); + return b; + } + Fl_Widget_Type *_make() FL_OVERRIDE { return new Fl_File_Browser_Type(); } + ID id() const FL_OVERRIDE { return ID_File_Browser; } + bool is_a(ID inID) const FL_OVERRIDE { return (inID==ID_File_Browser) ? true : super::is_a(inID); } +}; + +static Fl_File_Browser_Type Fl_File_Browser_type; + + +// ---- Tree Type ------------------------------------------------------ MARK: - + +/** + \brief Handle the Tree widget. + Fl_Tree is derived from Fl_Group, but FLUID does not support extended Fl_Tree + functionality, so we derive the Type from Fl_Widget_Type. + \note Updating item_labelfont etc. does not refresh any of the existing + items in the tree, so I decided against implementig those via + the labelfont UI. + */ +class Fl_Tree_Type : public Fl_Widget_Type +{ + typedef Fl_Widget_Type super; +public: + void ideal_size(int &w, int &h) FL_OVERRIDE { + w = 120; + h = 160; + Fd_Snap_Action::better_size(w, h); + } + const char *type_name() FL_OVERRIDE { return "Fl_Tree"; } + const char *alt_type_name() FL_OVERRIDE { return "fltk::TreeBrowser"; } + Fl_Widget *widget(int x, int y, int w, int h) FL_OVERRIDE { + Fl_Tree* b = new Fl_Tree(x, y, w, h); + if (!batch_mode) { + b->add("/A1/B1/C1"); + b->add("/A1/B1/C2"); + b->add("/A1/B2/C1"); + b->add("/A1/B2/C2"); + b->add("/A2/B1/C1"); + b->add("/A2/B1/C2"); + b->add("/A2/B2/C1"); + b->add("/A2/B2/C2"); + } + return b; + } + Fl_Widget_Type *_make() FL_OVERRIDE { return new Fl_Tree_Type(); } + ID id() const FL_OVERRIDE { return ID_Tree; } + bool is_a(ID inID) const FL_OVERRIDE { return (inID==ID_Tree) ? true : super::is_a(inID); } +}; + +static Fl_Tree_Type Fl_Tree_type; + + + +// ---- Help Viewer ---------------------------------------------------- MARK: - + +/** + \brief Handle the Help View widget. + Fl_Help_View is derived from Fl_Group, but supporting children is not useful, + so we derive from Fl_Widget_Type. + */ +class Fl_Help_View_Type : public Fl_Widget_Type +{ + typedef Fl_Widget_Type super; + int textstuff(int w, Fl_Font& f, int& s, Fl_Color& c) FL_OVERRIDE { + Fl_Help_View *myo = (Fl_Help_View*)(w==4 ? ((Fl_Widget_Type*)factory)->o : o); + switch (w) { + case 4: + case 0: f = myo->textfont(); s = myo->textsize(); c = myo->textcolor(); break; + case 1: myo->textfont(f); break; + case 2: myo->textsize(s); break; + case 3: myo->textcolor(c); break; + } + return 1; + } +public: + void ideal_size(int &w, int &h) FL_OVERRIDE { + w = 160; + h = 120; + Fd_Snap_Action::better_size(w, h); + } + const char *type_name() FL_OVERRIDE { return "Fl_Help_View"; } + const char *alt_type_name() FL_OVERRIDE { return "fltk::HelpView"; } + Fl_Widget *widget(int x, int y, int w, int h) FL_OVERRIDE { + Fl_Help_View *myo = new Fl_Help_View(x, y, w, h); + if (!batch_mode) { + myo->value("

Fl_Help_View Widget

" + "

This is a Fl_Help_View widget.

"); + } + return myo; + } + Fl_Widget_Type *_make() FL_OVERRIDE { return new Fl_Help_View_Type(); } + ID id() const FL_OVERRIDE { return ID_Help_View; } + bool is_a(ID inID) const FL_OVERRIDE { return (inID==ID_Help_View) ? true : super::is_a(inID); } +}; + +static Fl_Help_View_Type Fl_Help_View_type; + + + +// ---- Valuators ------------------------------------------------------ MARK: - + + +// ---- Valuator Base ---- + +/** + \brief Just a base class for all valuators. + */ +class Fl_Valuator_Type : public Fl_Widget_Type +{ + typedef Fl_Widget_Type super; +public: + const char *type_name() FL_OVERRIDE { return "Fl_Valuator"; } + const char *alt_type_name() FL_OVERRIDE { return "fltk::Valuator"; } + Fl_Widget *widget(int x, int y, int w, int h) FL_OVERRIDE { + return new Fl_Slider(x, y, w, h, "Valuator"); + } + Fl_Widget_Type *_make() FL_OVERRIDE { return new Fl_Valuator_Type(); } + ID id() const FL_OVERRIDE { return ID_Valuator_; } + bool is_a(ID inID) const FL_OVERRIDE { return (inID==ID_Valuator_) ? true : super::is_a(inID); } +}; + +static Fl_Valuator_Type Fl_Valuator_type; + + +// ---- Counter ---- + +static Fl_Menu_Item counter_type_menu[] = { + { "Normal", 0, 0, (void*)FL_NORMAL_COUNTER }, + { "Simple", 0, 0, (void*)FL_SIMPLE_COUNTER }, + { 0 } +}; + +/** + \brief Manage the Counter widget. + Strictly speaking, the ideal size should derive from the textsize not the labelsize. + */ +class Fl_Counter_Type : public Fl_Valuator_Type +{ + typedef Fl_Valuator_Type super; + Fl_Menu_Item *subtypes() FL_OVERRIDE { return counter_type_menu; } + int textstuff(int w, Fl_Font& f, int& s, Fl_Color& c) FL_OVERRIDE { + Fl_Counter *myo = (Fl_Counter*)(w==4 ? ((Fl_Widget_Type*)factory)->o : o); + switch (w) { + case 4: + case 0: f = myo->textfont(); s = myo->textsize(); c = myo->textcolor(); break; + case 1: myo->textfont(f); break; + case 2: myo->textsize(s); break; + case 3: myo->textcolor(c); break; + } + return 1; + } +public: + void ideal_size(int &w, int &h) FL_OVERRIDE { + h = layout->textsize_not_null() + 8; + w = layout->textsize_not_null() * 4 + 4 * h; // make room for the arrows + Fd_Snap_Action::better_size(w, h); + } + const char *type_name() FL_OVERRIDE { return "Fl_Counter"; } + const char *alt_type_name() FL_OVERRIDE { return "fltk::Counter"; } + Fl_Widget *widget(int x, int y, int w, int h) FL_OVERRIDE { + return new Fl_Counter(x, y, w, h, "counter:"); + } + Fl_Widget_Type *_make() FL_OVERRIDE { return new Fl_Counter_Type(); } + ID id() const FL_OVERRIDE { return ID_Counter; } + bool is_a(ID inID) const FL_OVERRIDE { return (inID==ID_Counter) ? true : super::is_a(inID); } +}; + +static Fl_Counter_Type Fl_Counter_type; + + +// ---- Adjuster ---- + +/** + \brief Handle Adjuster widgets which are derived from valuators. + */ +class Fl_Adjuster_Type : public Fl_Valuator_Type +{ + typedef Fl_Valuator_Type super; +public: + void ideal_size(int &w, int &h) FL_OVERRIDE { + h = layout->labelsize + 8; + w = 3 * h; + Fd_Snap_Action::better_size(w, h); + } + const char *type_name() FL_OVERRIDE { return "Fl_Adjuster"; } + const char *alt_type_name() FL_OVERRIDE { return "fltk::Adjuster"; } + Fl_Widget *widget(int x, int y, int w, int h) FL_OVERRIDE { + return new Fl_Adjuster(x, y, w, h); + } + Fl_Widget_Type *_make() FL_OVERRIDE { return new Fl_Adjuster_Type(); } + ID id() const FL_OVERRIDE { return ID_Adjuster; } + bool is_a(ID inID) const FL_OVERRIDE { return (inID==ID_Adjuster) ? true : super::is_a(inID); } +}; + +static Fl_Adjuster_Type Fl_Adjuster_type; + + +// ---- Dial ---- + +static Fl_Menu_Item dial_type_menu[] = { + { "Dot", 0, 0, (void*)0 }, + { "Line", 0, 0, (void*)FL_LINE_DIAL }, + { "Fill", 0, 0, (void*)FL_FILL_DIAL }, + { 0 } +}; + +/** + \brief Manage dials. + */ +class Fl_Dial_Type : public Fl_Valuator_Type +{ + typedef Fl_Valuator_Type super; + Fl_Menu_Item *subtypes() FL_OVERRIDE { return dial_type_menu; } +public: + void ideal_size(int &w, int &h) FL_OVERRIDE { + w = 60; h = 60; + Fd_Snap_Action::better_size(w, h); + } + const char *type_name() FL_OVERRIDE { return "Fl_Dial"; } + const char *alt_type_name() FL_OVERRIDE { return "fltk::Dial"; } + Fl_Widget *widget(int x, int y, int w, int h) FL_OVERRIDE { + return new Fl_Dial(x, y, w, h); + } + Fl_Widget_Type *_make() FL_OVERRIDE { return new Fl_Dial_Type(); } + ID id() const FL_OVERRIDE { return ID_Dial; } + bool is_a(ID inID) const FL_OVERRIDE { return (inID==ID_Dial) ? true : super::is_a(inID); } +}; +static Fl_Dial_Type Fl_Dial_type; + + +// ---- Roller ---- + +static Fl_Menu_Item roller_type_menu[] = { + { "Vertical", 0, 0, (void*)0 }, + { "Horizontal", 0, 0, (void*)FL_HORIZONTAL }, + { 0 } +}; + +/** + \brief Manage Roller widgets. They are vertical by default. + */ +class Fl_Roller_Type : public Fl_Valuator_Type +{ + typedef Fl_Valuator_Type super; + Fl_Menu_Item *subtypes() FL_OVERRIDE { return roller_type_menu; } +public: + void ideal_size(int &w, int &h) FL_OVERRIDE { + w = layout->labelsize + 8; + h = 4 * w; + Fd_Snap_Action::better_size(w, h); + } + const char *type_name() FL_OVERRIDE { return "Fl_Roller"; } + const char *alt_type_name() FL_OVERRIDE { return "fltk::Roller"; } + Fl_Widget *widget(int x, int y, int w, int h) FL_OVERRIDE { + return new Fl_Roller(x, y, w, h); + } + Fl_Widget_Type *_make() FL_OVERRIDE { return new Fl_Roller_Type(); } + ID id() const FL_OVERRIDE { return ID_Roller; } + bool is_a(ID inID) const FL_OVERRIDE { return (inID==ID_Roller) ? true : super::is_a(inID); } +}; + +static Fl_Roller_Type Fl_Roller_type; + + +// ---- Slider ---- + +static Fl_Menu_Item slider_type_menu[] = { + { "Vertical", 0, 0, (void*)FL_VERT_SLIDER }, + { "Horizontal", 0, 0, (void*)FL_HOR_SLIDER }, + { "Vert Fill", 0, 0, (void*)FL_VERT_FILL_SLIDER }, + { "Horz Fill", 0, 0, (void*)FL_HOR_FILL_SLIDER }, + { "Vert Knob", 0, 0, (void*)FL_VERT_NICE_SLIDER }, + { "Horz Knob", 0, 0, (void*)FL_HOR_NICE_SLIDER }, + { 0 } +}; + +/** + \brief Manage Slider widgets. + They are vertical by default. + Fl_Value_Slider has its own type. + */ +class Fl_Slider_Type : public Fl_Valuator_Type +{ + typedef Fl_Valuator_Type super; + Fl_Menu_Item *subtypes() FL_OVERRIDE { return slider_type_menu; } +public: + void ideal_size(int &w, int &h) FL_OVERRIDE { + w = layout->labelsize + 8; + h = 4 * w; + Fd_Snap_Action::better_size(w, h); + } + const char *type_name() FL_OVERRIDE { return "Fl_Slider"; } + const char *alt_type_name() FL_OVERRIDE { return "fltk::Slider"; } + Fl_Widget *widget(int x, int y, int w, int h) FL_OVERRIDE { + return new Fl_Slider(x, y, w, h, "slider:"); + } + Fl_Widget_Type *_make() FL_OVERRIDE { return new Fl_Slider_Type(); } + ID id() const FL_OVERRIDE { return ID_Slider; } + bool is_a(ID inID) const FL_OVERRIDE { return (inID==ID_Slider) ? true : super::is_a(inID); } +}; + +static Fl_Slider_Type Fl_Slider_type; + + +// ---- Scrollbar ---- + +static Fl_Menu_Item scrollbar_type_menu[] = { + { "Vertical", 0, 0, (void*)FL_VERT_SLIDER }, + { "Horizontal", 0, 0, (void*)FL_HOR_SLIDER }, + { 0 } +}; + +/** + \brief Manage Scrollbars which are derived from Sliders. + */ +class Fl_Scrollbar_Type : public Fl_Slider_Type +{ + typedef Fl_Slider_Type super; + Fl_Menu_Item *subtypes() FL_OVERRIDE { return scrollbar_type_menu; } +public: + const char *type_name() FL_OVERRIDE { return "Fl_Scrollbar"; } + const char *alt_type_name() FL_OVERRIDE { return "fltk::Scrollbar"; } + Fl_Widget *widget(int x, int y, int w, int h) FL_OVERRIDE { + return new Fl_Scrollbar(x, y, w, h); + } + Fl_Widget_Type *_make() FL_OVERRIDE { return new Fl_Scrollbar_Type(); } + ID id() const FL_OVERRIDE { return ID_Scrollbar; } + bool is_a(ID inID) const FL_OVERRIDE { return (inID==ID_Scrollbar) ? true : super::is_a(inID); } +}; +static Fl_Scrollbar_Type Fl_Scrollbar_type; + + +// ---- Value Slider ---- + +/** + \brief Manage Value Sliders and their text settings. + */ +class Fl_Value_Slider_Type : public Fl_Slider_Type +{ + typedef Fl_Slider_Type super; + int textstuff(int w, Fl_Font& f, int& s, Fl_Color& c) FL_OVERRIDE { + Fl_Value_Slider *myo = (Fl_Value_Slider*)(w==4 ? ((Fl_Widget_Type*)factory)->o : o); + switch (w) { + case 4: + case 0: f = myo->textfont(); s = myo->textsize(); c = myo->textcolor(); break; + case 1: myo->textfont(f); break; + case 2: myo->textsize(s); break; + case 3: myo->textcolor(c); break; + } + return 1; + } +public: + const char *type_name() FL_OVERRIDE { return "Fl_Value_Slider"; } + const char *alt_type_name() FL_OVERRIDE { return "fltk::ValueSlider"; } + Fl_Widget *widget(int x, int y, int w, int h) FL_OVERRIDE { + return new Fl_Value_Slider(x, y, w, h, "slider:"); + } + Fl_Widget_Type *_make() FL_OVERRIDE { return new Fl_Value_Slider_Type(); } + ID id() const FL_OVERRIDE { return ID_Value_Slider; } + bool is_a(ID inID) const FL_OVERRIDE { return (inID==ID_Value_Slider) ? true : super::is_a(inID); } +}; + +static Fl_Value_Slider_Type Fl_Value_Slider_type; + + +// ---- Value Input ---- + +/** + \brief Manage Value Inputs and their text settings. + */ +class Fl_Value_Input_Type : public Fl_Valuator_Type +{ + typedef Fl_Valuator_Type super; + int textstuff(int w, Fl_Font& f, int& s, Fl_Color& c) FL_OVERRIDE { + Fl_Value_Input *myo = (Fl_Value_Input*)(w==4 ? ((Fl_Widget_Type*)factory)->o : o); + switch (w) { + case 4: + case 0: f = myo->textfont(); s = myo->textsize(); c = myo->textcolor(); break; + case 1: myo->textfont(f); break; + case 2: myo->textsize(s); break; + case 3: myo->textcolor(c); break; + } + return 1; + } +public: + void ideal_size(int &w, int &h) FL_OVERRIDE { + h = layout->textsize_not_null() + 8; + w = layout->textsize_not_null() * 4 + 8; + Fd_Snap_Action::better_size(w, h); + } + const char *type_name() FL_OVERRIDE { return "Fl_Value_Input"; } + const char *alt_type_name() FL_OVERRIDE { return "fltk::ValueInput"; } + Fl_Widget *widget(int x, int y, int w, int h) FL_OVERRIDE { + Fl_Value_Input *myo = new Fl_Value_Input(x, y, w, h, "value:"); + return myo; + } + Fl_Widget_Type *_make() FL_OVERRIDE { return new Fl_Value_Input_Type(); } + ID id() const FL_OVERRIDE { return ID_Value_Input; } + bool is_a(ID inID) const FL_OVERRIDE { return (inID==ID_Value_Input) ? true : super::is_a(inID); } +}; + +static Fl_Value_Input_Type Fl_Value_Input_type; + + +// ---- Value Output ---- + +/** + \brief Handle Value Output widgets, no shortcut with Value Input unfortunately. + */ +class Fl_Value_Output_Type : public Fl_Valuator_Type +{ + typedef Fl_Valuator_Type super; + int textstuff(int w, Fl_Font& f, int& s, Fl_Color& c) FL_OVERRIDE { + Fl_Value_Output *myo = (Fl_Value_Output*)(w==4 ? ((Fl_Widget_Type*)factory)->o : o); + switch (w) { + case 4: + case 0: f = myo->textfont(); s = myo->textsize(); c = myo->textcolor(); break; + case 1: myo->textfont(f); break; + case 2: myo->textsize(s); break; + case 3: myo->textcolor(c); break; + } + return 1; + } +public: + void ideal_size(int &w, int &h) FL_OVERRIDE { + h = layout->textsize_not_null() + 8; + w = layout->textsize_not_null() * 4 + 8; + Fd_Snap_Action::better_size(w, h); + } + const char *type_name() FL_OVERRIDE { return "Fl_Value_Output"; } + const char *alt_type_name() FL_OVERRIDE { return "fltk::ValueOutput"; } + Fl_Widget *widget(int x, int y, int w, int h) FL_OVERRIDE { + Fl_Value_Output *myo = new Fl_Value_Output(x, y, w, h, "value:"); + return myo; + } + Fl_Widget_Type *_make() FL_OVERRIDE { return new Fl_Value_Output_Type(); } + ID id() const FL_OVERRIDE { return ID_Value_Output; } + bool is_a(ID inID) const FL_OVERRIDE { return (inID==ID_Value_Output) ? true : super::is_a(inID); } +}; + +static Fl_Value_Output_Type Fl_Value_Output_type; + + + +// ---- Input ---------------------------------------------------------- MARK: - + + +// ---- Input ---- + +static Fl_Menu_Item input_type_menu[] = { + { "Normal", 0, 0, (void*)FL_NORMAL_INPUT }, + { "Multiline", 0, 0, (void*)FL_MULTILINE_INPUT }, + { "Secret", 0, 0, (void*)FL_SECRET_INPUT }, + { "Int", 0, 0, (void*)FL_INT_INPUT }, + { "Float", 0, 0, (void*)FL_FLOAT_INPUT }, + {0} +}; + +/** + \brief Manage simple text input widgets. + The managed class is derived from Fl_Input_, but for simplicity, deriving from + Fl_Widget_Type seems sufficient here. + */ +class Fl_Input_Type : public Fl_Widget_Type +{ + typedef Fl_Widget_Type super; + Fl_Menu_Item *subtypes() FL_OVERRIDE { return input_type_menu; } + int textstuff(int w, Fl_Font& f, int& s, Fl_Color& c) FL_OVERRIDE { + Fl_Input_ *myo = (Fl_Input_*)(w==4 ? ((Fl_Widget_Type*)factory)->o : o); + switch (w) { + case 4: + case 0: f = myo->textfont(); s = myo->textsize(); c = myo->textcolor(); break; + case 1: myo->textfont(f); break; + case 2: myo->textsize(s); break; + case 3: myo->textcolor(c); break; + } + return 1; + } +public: + void ideal_size(int &w, int &h) FL_OVERRIDE { + h = layout->textsize_not_null() + 8; + w = layout->textsize_not_null() * 6 + 8; + Fd_Snap_Action::better_size(w, h); + } + const char *type_name() FL_OVERRIDE { return "Fl_Input"; } + const char *alt_type_name() FL_OVERRIDE { return "fltk::Input"; } + Fl_Widget *widget(int x, int y, int w, int h) FL_OVERRIDE { + Fl_Input *myo = new Fl_Input(x, y, w, h, "input:"); + myo->value("Text Input"); + return myo; + } + Fl_Widget_Type *_make() FL_OVERRIDE { return new Fl_Input_Type(); } + ID id() const FL_OVERRIDE { return ID_Input; } + bool is_a(ID inID) const FL_OVERRIDE { return (inID==ID_Input) ? true : super::is_a(inID); } + void copy_properties() FL_OVERRIDE { + Fl_Widget_Type::copy_properties(); + Fl_Input_ *d = (Fl_Input_*)live_widget, *s = (Fl_Input_*)o; + d->textfont(s->textfont()); + d->textsize(s->textsize()); + d->textcolor(s->textcolor()); + d->shortcut(s->shortcut()); + } +}; +static Fl_Input_Type Fl_Input_type; + + +// ---- File Input ---- + +/** + \brief Manage file name input widgets. + */ +class Fl_File_Input_Type : public Fl_Input_Type +{ + typedef Fl_Input_Type super; + Fl_Menu_Item *subtypes() FL_OVERRIDE { return NULL; } // Don't inherit. +public: + void ideal_size(int &w, int &h) FL_OVERRIDE { + h = layout->textsize_not_null() + 8 + 10; // Directoy bar is additional 10 pixels high + w = layout->textsize_not_null() * 10 + 8; + Fd_Snap_Action::better_size(w, h); + } + const char *type_name() FL_OVERRIDE { return "Fl_File_Input"; } + const char *alt_type_name() FL_OVERRIDE { return "fltk::FileInput"; } + Fl_Widget *widget(int x, int y, int w, int h) FL_OVERRIDE { + Fl_File_Input *myo = new Fl_File_Input(x, y, w, h, "file:"); + myo->value("/usr/include/FL/Fl.H"); + return myo; + } + Fl_Widget_Type *_make() FL_OVERRIDE { return new Fl_File_Input_Type(); } + ID id() const FL_OVERRIDE { return ID_File_Input; } + bool is_a(ID inID) const FL_OVERRIDE { return (inID==ID_File_Input) ? true : super::is_a(inID); } +}; + +static Fl_File_Input_Type Fl_File_Input_type; + + +// ---- Output ---- + +static Fl_Menu_Item output_type_menu[] = { + { "Normal", 0, 0, (void*)FL_NORMAL_OUTPUT }, + { "Multiline", 0, 0, (void*)FL_MULTILINE_OUTPUT }, + { 0 } +}; + +/** + \brief Manage Output widgets, derived from Input. + */ +class Fl_Output_Type : public Fl_Input_Type +{ + typedef Fl_Input_Type super; + Fl_Menu_Item *subtypes() FL_OVERRIDE { return output_type_menu; } +public: + const char *type_name() FL_OVERRIDE { return "Fl_Output"; } + const char *alt_type_name() FL_OVERRIDE { return "fltk::Output"; } + Fl_Widget *widget(int x, int y, int w, int h) FL_OVERRIDE { + Fl_Output *myo = new Fl_Output(x, y, w, h, "output:"); + myo->value("Text Output"); + return myo; + } + Fl_Widget_Type *_make() FL_OVERRIDE { return new Fl_Output_Type(); } + ID id() const FL_OVERRIDE { return ID_Output; } + bool is_a(ID inID) const FL_OVERRIDE { return (inID==ID_Output) ? true : super::is_a(inID); } +}; + +static Fl_Output_Type Fl_Output_type; + + + +// ---- Text Editor ---------------------------------------------------- MARK: - + + +// ---- Text Display ---- + +/** + \brief Manage the Text Display as a base class. + Fl_Text_Display is actually derived from Fl_Group, but for FLUID, deriving + the type from Widget is better. + */ +class Fl_Text_Display_Type : public Fl_Widget_Type +{ + typedef Fl_Widget_Type super; + int textstuff(int w, Fl_Font& f, int& s, Fl_Color& c) FL_OVERRIDE { + Fl_Text_Display *myo = (Fl_Text_Display*)(w==4 ? ((Fl_Widget_Type*)factory)->o : o); + switch (w) { + case 4: + case 0: f = myo->textfont(); s = myo->textsize(); c = myo->textcolor(); break; + case 1: myo->textfont(f); break; + case 2: myo->textsize(s); break; + case 3: myo->textcolor(c); break; + } + return 1; + } +public: + void ideal_size(int &w, int &h) FL_OVERRIDE { + h = layout->textsize_not_null() * 4 + 8; + w = layout->textsize_not_null() * 10 + 8; + Fd_Snap_Action::better_size(w, h); + } + const char *type_name() FL_OVERRIDE { return "Fl_Text_Display"; } + const char *alt_type_name() FL_OVERRIDE { return "fltk::TextDisplay"; } + Fl_Widget *widget(int x, int y, int w, int h) FL_OVERRIDE { + Fl_Text_Display *myo = new Fl_Text_Display(x, y, w, h); + if (!batch_mode) { + Fl_Text_Buffer *b = new Fl_Text_Buffer(); + b->text("Lorem ipsum dolor\nsit amet, consetetur\nsadipscing elitr"); + myo->buffer(b); + } + return myo; + } + Fl_Widget_Type *_make() FL_OVERRIDE { return new Fl_Text_Display_Type(); } + ID id() const FL_OVERRIDE { return ID_Text_Display; } + bool is_a(ID inID) const FL_OVERRIDE { return (inID==ID_Text_Display) ? true : super::is_a(inID); } +}; +static Fl_Text_Display_Type Fl_Text_Display_type; + + +// ---- Text Editor ---- + +/** + \brief Manage Text Editors based on Text Display. + */ +class Fl_Text_Editor_Type : public Fl_Text_Display_Type +{ + typedef Fl_Text_Display_Type super; +public: + const char *type_name() FL_OVERRIDE {return "Fl_Text_Editor";} + const char *alt_type_name() FL_OVERRIDE {return "fltk::TextEditor";} + Fl_Widget *widget(int x, int y, int w, int h) FL_OVERRIDE { + Fl_Text_Editor *myo = new Fl_Text_Editor(x, y, w, h); + if (!batch_mode) { + Fl_Text_Buffer *b = new Fl_Text_Buffer(); + b->text("Lorem ipsum dolor\nsit amet, consetetur\nsadipscing elitr"); + myo->buffer(b); + } + return myo; + } + Fl_Widget_Type *_make() FL_OVERRIDE { return new Fl_Text_Editor_Type(); } + ID id() const FL_OVERRIDE { return ID_Text_Editor; } + bool is_a(ID inID) const FL_OVERRIDE { return (inID==ID_Text_Editor) ? true : super::is_a(inID); } +}; + +static Fl_Text_Editor_Type Fl_Text_Editor_type; + + +// ---- Terminal ---- + +/** Use this terminal instead of Fl_Terminal to capture resize actions. */ +class Fl_Terminal_Proxy : public Fl_Terminal { +public: + Fl_Terminal_Proxy(int x, int y, int w, int h, const char *l=NULL) + : Fl_Terminal(x, y, w, h, l) { } + void print_sample_text() { + clear_screen_home(false); + append("> ls -als"); + } + void resize(int x, int y, int w, int h) FL_OVERRIDE { + Fl_Terminal::resize(x, y, w, h); + // After a resize, the top text vanishes, so make sure we redraw it. + print_sample_text(); + } +}; + +/** Use this terminal in batch mode to avoid opening a DISPLAY connection. */ +class Fl_Batchmode_Terminal : public Fl_Group { +public: + Fl_Font tfont_; + int tsize_; + Fl_Color tcolor_; + Fl_Batchmode_Terminal(int x, int y, int w, int h, const char *l=NULL) + : Fl_Group(x, y, w, h, l) + { // set the defaults that Fl_Terminal would set + box(FL_DOWN_BOX); + color(FL_FOREGROUND_COLOR); + selection_color(FL_BACKGROUND_COLOR); + labeltype(FL_NORMAL_LABEL); + labelfont(0); + labelsize(14); + labelcolor(FL_FOREGROUND_COLOR); + tfont_ = 4; + tcolor_ = 0xd0d0d000; + tsize_ = 14; + align(Fl_Align(FL_ALIGN_TOP)); + when(FL_WHEN_RELEASE); + end(); + } +}; + +/** + \brief Manage a terminal widget. + */ +class Fl_Terminal_Type : public Fl_Widget_Type +{ + typedef Fl_Widget_Type super; +public: + const char *type_name() FL_OVERRIDE { return "Fl_Terminal"; } + // Older .fl files with Fl_Simple_Terminal will create a Fl_Terminal instead. + const char *alt_type_name() FL_OVERRIDE { return "Fl_Simple_Terminal"; } + Fl_Widget *widget(int x, int y, int w, int h) FL_OVERRIDE { + Fl_Widget *ret = NULL; + if (batch_mode) { + ret = new Fl_Batchmode_Terminal(x, y, w, h); + } else { + Fl_Terminal_Proxy *term = new Fl_Terminal_Proxy(x, y, w+100, h); + ret = term; + } + return ret; + } + int textstuff(int w, Fl_Font& f, int& s, Fl_Color& c) FL_OVERRIDE { + if (batch_mode) { + Fl_Batchmode_Terminal *myo = (Fl_Batchmode_Terminal*)(w==4 ? ((Fl_Widget_Type*)factory)->o : o); + switch (w) { + case 4: + case 0: f = (Fl_Font)myo->tfont_; s = myo->tsize_; c = myo->tcolor_; break; + case 1: myo->tfont_ = f; break; + case 2: myo->tsize_ = s; break; + case 3: myo->tcolor_ = c; break; + } + } else { + Fl_Terminal_Proxy *myo = (Fl_Terminal_Proxy*)(w==4 ? ((Fl_Widget_Type*)factory)->o : o); + switch (w) { + case 4: + case 0: f = (Fl_Font)myo->textfont(); s = myo->textsize(); c = myo->textcolor(); break; + case 1: myo->textfont(f); myo->print_sample_text(); break; + case 2: myo->textsize(s); myo->print_sample_text(); break; + case 3: myo->textcolor(c); myo->print_sample_text(); break; + } + } + return 1; + } + Fl_Widget_Type *_make() FL_OVERRIDE {return new Fl_Terminal_Type();} + ID id() const FL_OVERRIDE { return ID_Terminal; } + bool is_a(ID inID) const FL_OVERRIDE { return (inID==ID_Terminal) ? true : super::is_a(inID); } +}; + +static Fl_Terminal_Type Fl_Terminal_type; + + +// ---- Other ---------------------------------------------------------- MARK: - + + +// ---- Box ---- + +/** + \brief Manage box widgets. + Ideal size is set to 100x100, snapped to layout. + */ +class Fl_Box_Type : public Fl_Widget_Type +{ + typedef Fl_Widget_Type super; +public: + void ideal_size(int &w, int &h) FL_OVERRIDE { + w = 100; h = 100; + Fd_Snap_Action::better_size(w, h); + } + const char *type_name() FL_OVERRIDE { return "Fl_Box"; } + const char *alt_type_name() FL_OVERRIDE { return "fltk::Widget"; } + Fl_Widget *widget(int x, int y, int w, int h) FL_OVERRIDE { + return new Fl_Box(x, y, w, h, "label"); + } + Fl_Widget_Type *_make() FL_OVERRIDE { return new Fl_Box_Type(); } + ID id() const FL_OVERRIDE { return ID_Box; } + bool is_a(ID inID) const FL_OVERRIDE { return (inID==ID_Box) ? true : super::is_a(inID); } +}; + +static Fl_Box_Type Fl_Box_type; + + +// ---- Clock ---- + +/** + \brief Manage Clock widgets. + Ideal size is set to 80x80 snapped to layout. + */ +class Fl_Clock_Type : public Fl_Widget_Type +{ + typedef Fl_Widget_Type super; +public: + void ideal_size(int &w, int &h) FL_OVERRIDE { + w = 80; h = 80; + Fd_Snap_Action::better_size(w, h); + } + const char *type_name() FL_OVERRIDE { return "Fl_Clock"; } + const char *alt_type_name() FL_OVERRIDE { return "fltk::Clock"; } + Fl_Widget *widget(int x, int y, int w, int h) FL_OVERRIDE { + return new Fl_Clock(x, y, w, h); + } + Fl_Widget_Type *_make() FL_OVERRIDE { return new Fl_Clock_Type(); } + ID id() const FL_OVERRIDE { return ID_Clock; } + bool is_a(ID inID) const FL_OVERRIDE { return (inID==ID_Clock) ? true : super::is_a(inID); } +}; + +static Fl_Clock_Type Fl_Clock_type; + + +// ---- Progress ---- + +/** + \brief Manage a Progress widget. + Ideal size is set to match the label font and label text width times 3. + \note minimum, maximum, and value must be set via extra code fields. + */ +class Fl_Progress_Type : public Fl_Widget_Type +{ + typedef Fl_Widget_Type super; +public: + void ideal_size(int &w, int &h) FL_OVERRIDE { + h = layout->labelsize + 8; + w = layout->labelsize * 12; + Fd_Snap_Action::better_size(w, h); + } + const char *type_name() FL_OVERRIDE { return "Fl_Progress"; } + const char *alt_type_name() FL_OVERRIDE { return "fltk::ProgressBar"; } + Fl_Widget *widget(int x, int y, int w, int h) FL_OVERRIDE { + Fl_Progress *myo = new Fl_Progress(x, y, w, h, "label"); + myo->value(50); + return myo; + } + Fl_Widget_Type *_make() FL_OVERRIDE { return new Fl_Progress_Type(); } + ID id() const FL_OVERRIDE { return ID_Progress; } + bool is_a(ID inID) const FL_OVERRIDE { return (inID==ID_Progress) ? true : super::is_a(inID); } +}; + +static Fl_Progress_Type Fl_Progress_type; + +// ---- Spinner ---- + +static Fl_Menu_Item spinner_type_menu[] = { + { "Integer", 0, 0, (void*)FL_INT_INPUT }, + { "Float", 0, 0, (void*)FL_FLOAT_INPUT }, + { 0 } +}; + +/** + \brief Manage Spinner widgets. + \note Fl_Spinner is derived from Fl_Group, *not* Fl_Valuator as one may expect. + For FLUID, this means some special handling and no Group support. + */ +class Fl_Spinner_Type : public Fl_Widget_Type +{ + typedef Fl_Widget_Type super; + Fl_Menu_Item *subtypes() FL_OVERRIDE { return spinner_type_menu; } + int textstuff(int w, Fl_Font& f, int& s, Fl_Color& c) FL_OVERRIDE { + Fl_Spinner *myo = (Fl_Spinner*)(w==4 ? ((Fl_Widget_Type*)factory)->o : o); + switch (w) { + case 4: + case 0: f = (Fl_Font)myo->textfont(); s = myo->textsize(); c = myo->textcolor(); break; + case 1: myo->textfont(f); break; + case 2: myo->textsize(s); break; + case 3: myo->textcolor(c); break; + } + return 1; + } +public: + void ideal_size(int &w, int &h) FL_OVERRIDE { + h = layout->textsize_not_null() + 8; + w = layout->textsize_not_null() * 4 + 8; + Fd_Snap_Action::better_size(w, h); + } + const char *type_name() FL_OVERRIDE { return "Fl_Spinner"; } + const char *alt_type_name() FL_OVERRIDE { return "fltk::Spinner"; } + Fl_Widget *widget(int x, int y, int w, int h) FL_OVERRIDE { + return new Fl_Spinner(x, y, w, h, "spinner:"); + } + Fl_Widget_Type *_make() FL_OVERRIDE { return new Fl_Spinner_Type(); } + ID id() const FL_OVERRIDE { return ID_Spinner; } + bool is_a(ID inID) const FL_OVERRIDE { return (inID==ID_Spinner) ? true : super::is_a(inID); } +}; + +static Fl_Spinner_Type Fl_Spinner_type; + + + +// ---- Type Factory --------------------------------------------------- MARK: - + +extern class Fl_Function_Type Fl_Function_type; +extern class Fl_Code_Type Fl_Code_type; +extern class Fl_CodeBlock_Type Fl_CodeBlock_type; +extern class Fl_Data_Type Fl_Data_type; +extern class Fl_Decl_Type Fl_Decl_type; +extern class Fl_DeclBlock_Type Fl_DeclBlock_type; +extern class Fl_Comment_Type Fl_Comment_type; +extern class Fl_Class_Type Fl_Class_type; +extern class Fl_Window_Type Fl_Window_type; +extern class Fl_Widget_Class_Type Fl_Widget_Class_type; +extern class Fl_Group_Type Fl_Group_type; +extern class Fl_Pack_Type Fl_Pack_type; +extern class Fl_Flex_Type Fl_Flex_type; +extern class Fl_Grid_Type Fl_Grid_type; +extern class Fl_Tabs_Type Fl_Tabs_type; +extern class Fl_Scroll_Type Fl_Scroll_type; +extern class Fl_Table_Type Fl_Table_type; +extern class Fl_Tile_Type Fl_Tile_type; +extern class Fl_Input_Choice_Type Fl_Input_Choice_type; +extern class Fl_Choice_Type Fl_Choice_type; +extern class Fl_Menu_Bar_Type Fl_Menu_Bar_type; +extern class Fl_Menu_Button_Type Fl_Menu_Button_type; +extern class Fl_Menu_Item_Type Fl_Menu_Item_type; +extern class Fl_Checkbox_Menu_Item_Type Fl_Checkbox_Menu_Item_type; +extern class Fl_Radio_Menu_Item_Type Fl_Radio_Menu_Item_type; +extern class Fl_Submenu_Type Fl_Submenu_type; +extern class Fl_Wizard_Type Fl_Wizard_type; + +extern class Fl_Button_Type Fl_Button_type; +extern class Fl_Return_Button_Type Fl_Return_Button_type; +extern class Fl_Light_Button_Type Fl_Light_Button_type; +extern class Fl_Check_Button_Type Fl_Check_Button_type; +extern class Fl_Repeat_Button_Type Fl_Repeat_Button_type; +extern class Fl_Round_Button_Type Fl_Round_Button_type; + +extern void select(Fl_Type *,int); +extern void select_only(Fl_Type *); + +/** + List all known types. + This is used to convert a type name into a pointer to the prototype. + This list may contain types that are supported in .fl files, but not + available in the *New* menu. + + \note Make sure that this array stays synchronized to `Fl_Menu_Item New_Menu[]` + further down in this file. + */ +static Fl_Type *known_types[] = { + // functions + (Fl_Type*)&Fl_Function_type, + (Fl_Type*)&Fl_Code_type, + (Fl_Type*)&Fl_CodeBlock_type, + (Fl_Type*)&Fl_Decl_type, + (Fl_Type*)&Fl_DeclBlock_type, + (Fl_Type*)&Fl_Class_type, + (Fl_Type*)&Fl_Widget_Class_type, + (Fl_Type*)&Fl_Comment_type, + (Fl_Type*)&Fl_Data_type, + // groups + (Fl_Type*)&Fl_Window_type, + (Fl_Type*)&Fl_Group_type, + (Fl_Type*)&Fl_Pack_type, + (Fl_Type*)&Fl_Flex_type, + (Fl_Type*)&Fl_Tabs_type, + (Fl_Type*)&Fl_Scroll_type, + (Fl_Type*)&Fl_Tile_type, + (Fl_Type*)&Fl_Wizard_type, + (Fl_Type*)&Fl_Grid_type, + // buttons + (Fl_Type*)&Fl_Button_type, + (Fl_Type*)&Fl_Return_Button_type, + (Fl_Type*)&Fl_Light_Button_type, + (Fl_Type*)&Fl_Check_Button_type, + (Fl_Type*)&Fl_Repeat_Button_type, + (Fl_Type*)&Fl_Round_Button_type, + // valuators + (Fl_Type*)&Fl_Slider_type, + (Fl_Type*)&Fl_Scrollbar_type, + (Fl_Type*)&Fl_Value_Slider_type, + (Fl_Type*)&Fl_Adjuster_type, + (Fl_Type*)&Fl_Counter_type, + (Fl_Type*)&Fl_Spinner_type, + (Fl_Type*)&Fl_Dial_type, + (Fl_Type*)&Fl_Roller_type, + (Fl_Type*)&Fl_Value_Input_type, + (Fl_Type*)&Fl_Value_Output_type, + // text + (Fl_Type*)&Fl_Input_type, + (Fl_Type*)&Fl_Output_type, + (Fl_Type*)&Fl_Text_Editor_type, + (Fl_Type*)&Fl_Text_Display_type, + (Fl_Type*)&Fl_File_Input_type, + (Fl_Type*)&Fl_Terminal_type, + // menus + (Fl_Type*)&Fl_Menu_Bar_type, + (Fl_Type*)&Fl_Menu_Button_type, + (Fl_Type*)&Fl_Choice_type, + (Fl_Type*)&Fl_Input_Choice_type, + (Fl_Type*)&Fl_Submenu_type, + (Fl_Type*)&Fl_Menu_Item_type, + (Fl_Type*)&Fl_Checkbox_Menu_Item_type, + (Fl_Type*)&Fl_Radio_Menu_Item_type, + // browsers + (Fl_Type*)&Fl_Browser_type, + (Fl_Type*)&Fl_Check_Browser_type, + (Fl_Type*)&Fl_File_Browser_type, + (Fl_Type*)&Fl_Tree_type, + (Fl_Type*)&Fl_Help_View_type, + (Fl_Type*)&Fl_Table_type, + // misc + (Fl_Type*)&Fl_Box_type, + (Fl_Type*)&Fl_Clock_type, + (Fl_Type*)&Fl_Progress_type, +}; + +/** + Create and add a new widget to the widget tree. + + Fluid will try to set a default position for widgets to the user's expectation. + Using the context menu will put new widgets at the position of the mouse click. + Pulldown menu and bin actions will generate widgets no too far from previously + added widgets in the same group. + + Widgets can be added by dragging them from the widget bin to the + desired location. + + By setting the strategy, widgets are added as the last child of a group (this + is done when reading them from a file), or close to the current widget, which + the user would expect in interactive mode. + + \param[in] inPrototype pointer to one of the FL_..._type prototype; note the + lower case 't' in type. + \param[in] strategy add after current or as last child + \param[in] and_open if set to true, call open() on the widget after creating it + \return the newly created type or NULL + + \see add_new_widget_from_file(const char*, int) + add_new_widget_from_user(Fl_Type*, int) + add_new_widget_from_user(const char*, int) + */ +Fl_Type *add_new_widget_from_user(Fl_Type *inPrototype, Strategy strategy, bool and_open) { + undo_checkpoint(); + undo_suspend(); + Fl_Type *t = ((Fl_Type*)inPrototype)->make(strategy); + if (t) { + if (t->is_widget() && !t->is_a(ID_Window)) { + Fl_Widget_Type *wt = (Fl_Widget_Type *)t; + bool changed = false; + + // Set font sizes... + changed |= (wt->o->labelsize() != layout->labelsize); + wt->o->labelsize(layout->labelsize); + if (layout->labelfont >= 0) { + changed |= (wt->o->labelfont() != layout->labelfont); + wt->o->labelfont(layout->labelfont); + } + + Fl_Font fc, f = layout->textfont; + int sc, s = layout->textsize; + Fl_Color cc, c; + wt->textstuff(0, fc, sc, cc); + + if ((f >= 0) && (fc != f)) { + changed = true; + wt->textstuff(1, f, s, c); + } + if ((s > 0) && (sc != s)) { + changed = true; + wt->textstuff(2, f, s, c); + } + + if (changed && t->is_a(ID_Menu_Item)) { + Fl_Type * tt = t->parent; + while (tt && !tt->is_a(ID_Menu_Manager_)) tt = tt->parent; + if (tt) + ((Fl_Menu_Manager_Type*)tt)->build_menu(); + } + } + if (t->is_true_widget() && !t->is_a(ID_Window)) { + // Resize and/or reposition new widget... + Fl_Widget_Type *wt = (Fl_Widget_Type *)t; + + // The parent field is already set at this point, so we can use that + // inside ideal_size(). + int w = 0, h = 0; + wt->ideal_size(w, h); + + if ((t->parent && t->parent->is_a(ID_Flex))) { + if (Fl_Window_Type::popupx != 0x7FFFFFFF) + ((Fl_Flex_Type*)t->parent)->insert_child_at(((Fl_Widget_Type*)t)->o, Fl_Window_Type::popupx, Fl_Window_Type::popupy); + t->parent->layout_widget(); + } else if ( wt->is_a(ID_Group) + && wt->parent + && wt->parent->is_a(ID_Tabs) + //&& (Fl_Window_Type::popupx == 0x7FFFFFFF) + && (layout->top_tabs_margin > 0)) { + // If the widget is a group and the parent is tabs and the top tabs + // margin is set (and the user is not requesting a specific position) + // then prefit the group correctly to the Tabs container. + Fl_Widget *po = ((Fl_Tabs_Type*)wt->parent)->o; + wt->o->resize(po->x(), po->y() + layout->top_tabs_margin, + po->w(), po->h() - layout->top_tabs_margin); + } else if ( wt->is_a(ID_Menu_Bar) + && wt->parent + && wt->parent->is_a(ID_Window) + && (wt->prev == wt->parent)) { + // If this is the first child of a window, make the menu bar as wide as + // the window and drop it at 0, 0. Otherwise just use the suggested size. + w = wt->o->window()->w(); + wt->o->resize(0, 0, w, h); + } else { + if (Fl_Window_Type::popupx != 0x7FFFFFFF) { + // If this callback was called from the RMB popup menu in a window, + // popupx and popupy will contain the mouse coordinates at RMB event. + wt->o->resize(Fl_Window_Type::popupx, Fl_Window_Type::popupy, w, h); + } else { + // If popupx is invalid, use the default position and find a good + // size for the widget. + wt->o->size(w, h); + } + } + if (t->parent && t->parent->is_a(ID_Grid)) { + if (Fl_Window_Type::popupx != 0x7FFFFFFF) { + ((Fl_Grid_Type*)t->parent)->insert_child_at(((Fl_Widget_Type*)t)->o, Fl_Window_Type::popupx, Fl_Window_Type::popupy); + } else { + ((Fl_Grid_Type*)t->parent)->insert_child_at_next_free_cell(((Fl_Widget_Type*)t)->o); + } + } + } + if (t->is_a(ID_Window)) { + int x = 0, y = 0, w = 480, h = 320; + Fl_Window_Type *wt = (Fl_Window_Type *)t; + wt->ideal_size(w, h); + if (main_window) { + int sx, sy, sw, sh; + Fl_Window *win = main_window; + int screen = Fl::screen_num(win->x(), win->y()); + Fl::screen_work_area(sx, sy, sw, sh, screen); + x = sx + sw/2 - w/2; + y = sy + sh/2 - h/2; + } + wt->o->resize(x, y, w, h); + } + // make the new widget visible + select_only(t); + set_modflag(1); + if (and_open) + t->open(); + } else { + undo_current --; + undo_last --; + } + undo_resume(); + return t; +} + +/** + Create and add a new widget to the widget tree. + + \param[in] inName find the right prototype by this name + \param[in] strategy where to add the node + \param[in] and_open if set to true, call open() on the widget after creating it + \return the newly created type or NULL + + \see add_new_widget_from_file(const char*, int) + add_new_widget_from_user(Fl_Type*, int) + add_new_widget_from_user(const char*, int) + */ +Fl_Type *add_new_widget_from_user(const char *inName, Strategy strategy, bool and_open) { + Fl_Type *prototype = typename_to_prototype(inName); + if (prototype) + return add_new_widget_from_user(prototype, strategy, and_open); + else + return NULL; +} + +/** + Callback for all non-widget menu items. + */ +static void cbf(Fl_Widget *, void *v) { + Fl_Type *t = NULL; + if (Fl_Type::current && Fl_Type::current->can_have_children()) + t = ((Fl_Type*)v)->make(Strategy::AS_LAST_CHILD); + else + t = ((Fl_Type*)v)->make(Strategy::AFTER_CURRENT); + select_only(t); +} + +/** + Callback for all widget menu items. + + \param[in] v cast to Fl_Type to get the prototype of the type that the user + wants to create. + */ +static void cb(Fl_Widget *, void *v) { + Fl_Type *t = NULL; + if (Fl_Type::current && Fl_Type::current->can_have_children()) + t = add_new_widget_from_user((Fl_Type*)v, Strategy::AS_LAST_CHILD); + else + t = add_new_widget_from_user((Fl_Type*)v, Strategy::AFTER_CURRENT); + select_only(t); +} + +/** + \note Make sure that this menu stays synchronized to `Fl_Type *known_types[]` + defined further up in this file. + */ +Fl_Menu_Item New_Menu[] = { +{"Code",0,0,0,FL_SUBMENU}, + {"Function/Method",0,cbf,(void*)&Fl_Function_type}, + {"Code",0,cbf,(void*)&Fl_Code_type}, + {"Code Block",0,cbf,(void*)&Fl_CodeBlock_type}, + {"Declaration",0,cbf,(void*)&Fl_Decl_type}, + {"Declaration Block",0,cbf,(void*)&Fl_DeclBlock_type}, + {"Class",0,cbf,(void*)&Fl_Class_type}, + {"Widget Class",0,cb,(void*)&Fl_Widget_Class_type}, + {"Comment",0,cbf,(void*)&Fl_Comment_type}, + {"Inlined Data",0,cbf,(void*)&Fl_Data_type}, +{0}, +{"Group",0,0,0,FL_SUBMENU}, + {0,0,cb,(void*)&Fl_Window_type}, + {0,0,cb,(void*)&Fl_Group_type}, + {0,0,cb,(void*)&Fl_Pack_type}, + {0,0,cb,(void*)&Fl_Flex_type}, + {0,0,cb,(void*)&Fl_Tabs_type}, + {0,0,cb,(void*)&Fl_Scroll_type}, + {0,0,cb,(void*)&Fl_Tile_type}, + {0,0,cb,(void*)&Fl_Wizard_type}, + {0,0,cb,(void*)&Fl_Grid_type}, +{0}, +{"Buttons",0,0,0,FL_SUBMENU}, + {0,0,cb,(void*)&Fl_Button_type}, + {0,0,cb,(void*)&Fl_Return_Button_type}, + {0,0,cb,(void*)&Fl_Light_Button_type}, + {0,0,cb,(void*)&Fl_Check_Button_type}, + {0,0,cb,(void*)&Fl_Repeat_Button_type}, + {0,0,cb,(void*)&Fl_Round_Button_type}, +{0}, +{"Valuators",0,0,0,FL_SUBMENU}, + {0,0,cb,(void*)&Fl_Slider_type}, + {0,0,cb,(void*)&Fl_Scrollbar_type}, + {0,0,cb,(void*)&Fl_Value_Slider_type}, + {0,0,cb,(void*)&Fl_Adjuster_type}, + {0,0,cb,(void*)&Fl_Counter_type}, + {0,0,cb,(void*)&Fl_Spinner_type}, + {0,0,cb,(void*)&Fl_Dial_type}, + {0,0,cb,(void*)&Fl_Roller_type}, + {0,0,cb,(void*)&Fl_Value_Input_type}, + {0,0,cb,(void*)&Fl_Value_Output_type}, +{0}, +{"Text",0,0,0,FL_SUBMENU}, + {0,0,cb,(void*)&Fl_Input_type}, + {0,0,cb,(void*)&Fl_Output_type}, + {0,0,cb,(void*)&Fl_Text_Editor_type}, + {0,0,cb,(void*)&Fl_Text_Display_type}, + {0,0,cb,(void*)&Fl_File_Input_type}, + {0,0,cb,(void*)&Fl_Terminal_type}, +{0}, +{"Menus",0,0,0,FL_SUBMENU}, + {0,0,cb,(void*)&Fl_Menu_Bar_type}, + {0,0,cb,(void*)&Fl_Menu_Button_type}, + {0,0,cb,(void*)&Fl_Choice_type}, + {0,0,cb,(void*)&Fl_Input_Choice_type}, + {0,0,cb, (void*)&Fl_Submenu_type}, + {0,0,cb, (void*)&Fl_Menu_Item_type}, + {"Checkbox Menu Item",0,cb, (void*)&Fl_Checkbox_Menu_Item_type}, + {"Radio Menu Item",0,cb, (void*)&Fl_Radio_Menu_Item_type}, +{0}, +{"Browsers",0,0,0,FL_SUBMENU}, + {0,0,cb,(void*)&Fl_Browser_type}, + {0,0,cb,(void*)&Fl_Check_Browser_type}, + {0,0,cb,(void*)&Fl_File_Browser_type}, + {0,0,cb,(void*)&Fl_Tree_type}, + {0,0,cb,(void*)&Fl_Help_View_type}, + {0,0,cb,(void*)&Fl_Table_type}, +{0}, +{"Other",0,0,0,FL_SUBMENU}, + {0,0,cb,(void*)&Fl_Box_type}, + {0,0,cb,(void*)&Fl_Clock_type}, + {0,0,cb,(void*)&Fl_Progress_type}, +{0}, +{0}}; + +#include + +/** + Modify a menuitem to display an icon in front of the label. + This is implemented using Fl_Multi_Label as the labeltype (FL_MULTI_LABEL). + The icon may be null. If ic is null only the text is assigned + to the label and Fl_Multi_Label is not used. + \param[in] mi pointer to tme menu item that will be modified + \param[in] ic icon for the menu, may be NULL + \param[in] txt new label text, may *not* be NULL, will not be copied + */ +static void make_iconlabel(Fl_Menu_Item *mi, Fl_Image *ic, const char *txt) +{ + if (ic) { + char *t1 = new char[strlen(txt)+6]; + strcpy(t1, " "); + strcat(t1, txt); + strcat(t1, "..."); + Fl_Multi_Label *ml = new Fl_Multi_Label; + ml->labela = (char*)ic; + ml->labelb = t1; + ml->typea = FL_IMAGE_LABEL; + ml->typeb = FL_NORMAL_LABEL; + ml->label(mi); + } else { + if (txt != mi->text) + mi->label(txt); + } +} + +/** + Create the labels and icons for the `New_Menu` array. + + Names and icons are taken from the referenced prototypes. + */ +void fill_in_New_Menu() { + for (unsigned i = 0; i < sizeof(New_Menu)/sizeof(*New_Menu); i++) { + Fl_Menu_Item *m = New_Menu+i; + if (m->user_data()) { + Fl_Type *t = (Fl_Type*)m->user_data(); + if (m->text) { + make_iconlabel( m, pixmap[t->id()], m->label() ); + } else { + const char *n = t->type_name(); + if (!strncmp(n,"Fl_",3)) n += 3; + if (!strncmp(n,"fltk::",6)) n += 6; + make_iconlabel( m, pixmap[t->id()], n ); + } + } + } +} + +/** + Find the correct prototype for a given type name. + \param[in] inName a C string that must match type_name() or alt_type_name() of + one of the known Fl_Type classes. + \return the matching prototype or NULL + */ +Fl_Type *typename_to_prototype(const char *inName) +{ + if (inName==NULL || *inName==0) + return NULL; + for (unsigned i = 0; i < sizeof(known_types)/sizeof(*known_types); i++) { + Fl_Type *prototype = known_types[i]; + if (fl_ascii_strcasecmp(inName, prototype->type_name())==0) + return prototype; + if (fl_ascii_strcasecmp(inName, prototype->alt_type_name())==0) + return prototype; + } + return NULL; +} + +/** + Create and add a new type node to the widget tree. + + This is used by the .fl file reader. New types are always created as + the last child of the first compatible parent. New widgets have a default + setup. Their position, size and label will be read next in the file. + + \param[in] inName a C string that described the type we want + \param[in] strategy add after current or as last child + \return the type node that was created or NULL + \see add_new_widget_from_file(const char*, int) + add_new_widget_from_user(Fl_Type*, int) + add_new_widget_from_user(const char*, int) +*/ +Fl_Type *add_new_widget_from_file(const char *inName, Strategy strategy) { + Fl_Type *prototype = typename_to_prototype(inName); + if (!prototype) + return NULL; + Fl_Type *new_node = prototype->make(strategy); + return new_node; +} + +//////////////////////////////////////////////////////////////// + +// Since I have included all the .H files, do this table here: +// This table is only used to read fdesign files: + +struct symbol {const char *name; int value;}; + +/** + Table with all symbols known by the "fdesign" format reader. + This table does not need to be sorted alphabetically. + */ +static symbol table[] = { + {"BLACK", FL_BLACK}, + {"RED", FL_RED}, + {"GREEN", FL_GREEN}, + {"YELLOW", FL_YELLOW}, + {"BLUE", FL_BLUE}, + {"MAGENTA", FL_MAGENTA}, + {"CYAN", FL_CYAN}, + {"WHITE", FL_WHITE}, + + {"LCOL", FL_BLACK}, + {"COL1", FL_GRAY}, + {"MCOL", FL_LIGHT1}, + {"LEFT_BCOL", FL_LIGHT3}, + {"TOP_BCOL", FL_LIGHT2}, + {"BOTTOM_BCOL", FL_DARK2}, + {"RIGHT_BCOL", FL_DARK3}, + {"INACTIVE", FL_INACTIVE_COLOR}, + {"INACTIVE_COL", FL_INACTIVE_COLOR}, + {"FREE_COL1", FL_FREE_COLOR}, + {"FREE_COL2", FL_FREE_COLOR+1}, + {"FREE_COL3", FL_FREE_COLOR+2}, + {"FREE_COL4", FL_FREE_COLOR+3}, + {"FREE_COL5", FL_FREE_COLOR+4}, + {"FREE_COL6", FL_FREE_COLOR+5}, + {"FREE_COL7", FL_FREE_COLOR+6}, + {"FREE_COL8", FL_FREE_COLOR+7}, + {"FREE_COL9", FL_FREE_COLOR+8}, + {"FREE_COL10", FL_FREE_COLOR+9}, + {"FREE_COL11", FL_FREE_COLOR+10}, + {"FREE_COL12", FL_FREE_COLOR+11}, + {"FREE_COL13", FL_FREE_COLOR+12}, + {"FREE_COL14", FL_FREE_COLOR+13}, + {"FREE_COL15", FL_FREE_COLOR+14}, + {"FREE_COL16", FL_FREE_COLOR+15}, + {"TOMATO", 131}, + {"INDIANRED", 164}, + {"SLATEBLUE", 195}, + {"DARKGOLD", 84}, + {"PALEGREEN", 157}, + {"ORCHID", 203}, + {"DARKCYAN", 189}, + {"DARKTOMATO", 113}, + {"WHEAT", 174}, + {"ALIGN_CENTER", FL_ALIGN_CENTER}, + {"ALIGN_TOP", FL_ALIGN_TOP}, + {"ALIGN_BOTTOM", FL_ALIGN_BOTTOM}, + {"ALIGN_LEFT", FL_ALIGN_LEFT}, + {"ALIGN_RIGHT", FL_ALIGN_RIGHT}, + {"ALIGN_INSIDE", FL_ALIGN_INSIDE}, + {"ALIGN_TOP_LEFT", FL_ALIGN_TOP | FL_ALIGN_LEFT}, + {"ALIGN_TOP_RIGHT", FL_ALIGN_TOP | FL_ALIGN_RIGHT}, + {"ALIGN_BOTTOM_LEFT", FL_ALIGN_BOTTOM | FL_ALIGN_LEFT}, + {"ALIGN_BOTTOM_RIGHT", FL_ALIGN_BOTTOM | FL_ALIGN_RIGHT}, + {"ALIGN_CENTER|FL_ALIGN_INSIDE", FL_ALIGN_CENTER|FL_ALIGN_INSIDE}, + {"ALIGN_TOP|FL_ALIGN_INSIDE", FL_ALIGN_TOP|FL_ALIGN_INSIDE}, + {"ALIGN_BOTTOM|FL_ALIGN_INSIDE", FL_ALIGN_BOTTOM|FL_ALIGN_INSIDE}, + {"ALIGN_LEFT|FL_ALIGN_INSIDE", FL_ALIGN_LEFT|FL_ALIGN_INSIDE}, + {"ALIGN_RIGHT|FL_ALIGN_INSIDE", FL_ALIGN_RIGHT|FL_ALIGN_INSIDE}, + {"ALIGN_INSIDE|FL_ALIGN_INSIDE", FL_ALIGN_INSIDE|FL_ALIGN_INSIDE}, + {"ALIGN_TOP_LEFT|FL_ALIGN_INSIDE", FL_ALIGN_TOP|FL_ALIGN_LEFT|FL_ALIGN_INSIDE}, + {"ALIGN_TOP_RIGHT|FL_ALIGN_INSIDE", FL_ALIGN_TOP|FL_ALIGN_RIGHT|FL_ALIGN_INSIDE}, + {"ALIGN_BOTTOM_LEFT|FL_ALIGN_INSIDE", FL_ALIGN_BOTTOM|FL_ALIGN_LEFT|FL_ALIGN_INSIDE}, + {"ALIGN_BOTTOM_RIGHT|FL_ALIGN_INSIDE",FL_ALIGN_BOTTOM|FL_ALIGN_RIGHT|FL_ALIGN_INSIDE}, + + {"ALIGN_LEFT_TOP", FL_ALIGN_TOP | FL_ALIGN_LEFT}, + {"ALIGN_RIGHT_TOP", FL_ALIGN_TOP | FL_ALIGN_RIGHT}, + {"ALIGN_LEFT_BOTTOM", FL_ALIGN_BOTTOM | FL_ALIGN_LEFT}, + {"ALIGN_RIGHT_BOTTOM", FL_ALIGN_BOTTOM | FL_ALIGN_RIGHT}, + {"INVALID_STYLE", 255}, + {"NORMAL_STYLE", FL_HELVETICA}, + {"BOLD_STYLE", FL_HELVETICA|FL_BOLD}, + {"ITALIC_STYLE", FL_HELVETICA|FL_ITALIC}, + {"BOLDITALIC_STYLE", FL_HELVETICA|FL_BOLD|FL_ITALIC}, + {"FIXED_STYLE", FL_COURIER}, + {"FIXEDBOLD_STYLE", FL_COURIER|FL_BOLD}, + {"FIXEDITALIC_STYLE", FL_COURIER|FL_ITALIC}, + {"FIXEDBOLDITALIC_STYLE", FL_COURIER|FL_BOLD|FL_ITALIC}, + {"TIMES_STYLE", FL_TIMES}, + {"TIMESBOLD_STYLE", FL_TIMES|FL_BOLD}, + {"TIMESITALIC_STYLE", FL_TIMES|FL_ITALIC}, + {"TIMESBOLDITALIC_STYLE", FL_TIMES|FL_BOLD|FL_ITALIC}, + {"SHADOW_STYLE", (_FL_SHADOW_LABEL<<8)}, + {"ENGRAVED_STYLE", (_FL_ENGRAVED_LABEL<<8)}, + {"EMBOSSED_STYLE", (_FL_EMBOSSED_LABEL<<0)}, + {"TINY_SIZE", 8}, + {"SMALL_SIZE", 11}, + {"NORMAL_SIZE", FL_NORMAL_SIZE}, + {"MEDIUM_SIZE", 18}, + {"LARGE_SIZE", 24}, + {"HUGE_SIZE", 32}, + {"DEFAULT_SIZE", FL_NORMAL_SIZE}, + {"TINY_FONT", 8}, + {"SMALL_FONT", 11}, + {"NORMAL_FONT", FL_NORMAL_SIZE}, + {"MEDIUM_FONT", 18}, + {"LARGE_FONT", 24}, + {"HUGE_FONT", 32}, + {"NORMAL_FONT1", 11}, + {"NORMAL_FONT2", FL_NORMAL_SIZE}, + {"DEFAULT_FONT", 11}, + {"RETURN_END_CHANGED", 0}, + {"RETURN_CHANGED", 1}, + {"RETURN_END", 2}, + {"RETURN_ALWAYS", 3}, + {"PUSH_BUTTON", FL_TOGGLE_BUTTON}, + {"RADIO_BUTTON", FL_RADIO_BUTTON}, + {"HIDDEN_BUTTON", FL_HIDDEN_BUTTON}, + {"SELECT_BROWSER", FL_SELECT_BROWSER}, + {"HOLD_BROWSER", FL_HOLD_BROWSER}, + {"MULTI_BROWSER", FL_MULTI_BROWSER}, + {"SIMPLE_COUNTER", FL_SIMPLE_COUNTER}, + {"LINE_DIAL", FL_LINE_DIAL}, + {"FILL_DIAL", FL_FILL_DIAL}, + {"VERT_SLIDER", FL_VERT_SLIDER}, + {"HOR_SLIDER", FL_HOR_SLIDER}, + {"VERT_FILL_SLIDER", FL_VERT_FILL_SLIDER}, + {"HOR_FILL_SLIDER", FL_HOR_FILL_SLIDER}, + {"VERT_NICE_SLIDER", FL_VERT_NICE_SLIDER}, + {"HOR_NICE_SLIDER", FL_HOR_NICE_SLIDER}, +}; + +/** + \brief Find a symbol in an array of name/value pairs and return the value. + + If numberok is 0, and the symbol was not found, v remains unchanged and the + function returns 0. + + If numberok is set and no label matched, the symbol is interpreted as a + string containing an integer. If the string is not an integer, v is set to 0 + and the function returns 0. + + If the symbol is found, or the integer could be read, v is set to the + value, and the function returns 1. + + \param[in] name find a symbol by this name, a leading "FL_" is ignored + \param[out] v value associated to the symbol, or the integer value + \param[in] numberok if set, the symbol can also be a text representing an + integer number + \return 0 if the symbol was not found and the integer was not valid + \return 1 otherwise and set v + */ +int lookup_symbol(const char *name, int &v, int numberok) { + if ((name[0]=='F') && (name[1]=='L') && (name[2]=='_')) + name += 3; + for (int i=0; i < int(sizeof(table)/sizeof(*table)); i++) { + if (!fl_ascii_strcasecmp(name,table[i].name)) { + v = table[i].value; + return 1; + } + } + if (numberok && ((v = atoi(name)) || !strcmp(name,"0"))) + return 1; + return 0; +} diff --git a/fluid/nodes/factory.h b/fluid/nodes/factory.h new file mode 100644 index 000000000..f1968b167 --- /dev/null +++ b/fluid/nodes/factory.h @@ -0,0 +1,34 @@ +// +// Widget type header file for the Fast Light Tool Kit (FLTK). +// +// Copyright 1998-2021 by Bill Spitzak and others. +// +// This library is free software. Distribution and use rights are outlined in +// the file "COPYING" which should have been included with this file. If this +// file is missing or damaged, see the license at: +// +// https://www.fltk.org/COPYING.php +// +// Please see the following page on how to report bugs and issues: +// +// https://www.fltk.org/bugs.php +// + +#ifndef _FLUID_FACTORY_H +#define _FLUID_FACTORY_H + +#include "nodes/Fl_Type.h" + +struct Fl_Menu_Item; + +extern Fl_Menu_Item New_Menu[]; + +void fill_in_New_Menu(); +Fl_Type *typename_to_prototype(const char *inName); + +Fl_Type *add_new_widget_from_file(const char *inName, Strategy strategy); +Fl_Type *add_new_widget_from_user(Fl_Type *inPrototype, Strategy strategy, bool and_open=true); +Fl_Type *add_new_widget_from_user(const char *inName, Strategy strategy, bool and_open=true); + + +#endif // _FLUID_FACTORY_H -- cgit v1.2.3