summaryrefslogtreecommitdiff
path: root/fluid/nodes
diff options
context:
space:
mode:
Diffstat (limited to 'fluid/nodes')
-rw-r--r--fluid/nodes/Fl_Button_Type.cxx226
-rw-r--r--fluid/nodes/Fl_Button_Type.h46
-rw-r--r--fluid/nodes/Fl_Function_Type.cxx2156
-rw-r--r--fluid/nodes/Fl_Function_Type.h259
-rw-r--r--fluid/nodes/Fl_Grid_Type.cxx993
-rw-r--r--fluid/nodes/Fl_Grid_Type.h82
-rw-r--r--fluid/nodes/Fl_Group_Type.cxx848
-rw-r--r--fluid/nodes/Fl_Group_Type.h242
-rw-r--r--fluid/nodes/Fl_Menu_Type.cxx922
-rw-r--r--fluid/nodes/Fl_Menu_Type.h287
-rw-r--r--fluid/nodes/Fl_Type.cxx1336
-rw-r--r--fluid/nodes/Fl_Type.h317
-rw-r--r--fluid/nodes/Fl_Widget_Type.cxx3937
-rw-r--r--fluid/nodes/Fl_Widget_Type.h132
-rw-r--r--fluid/nodes/Fl_Window_Type.cxx1560
-rw-r--r--fluid/nodes/Fl_Window_Type.h157
-rw-r--r--fluid/nodes/factory.cxx1718
-rw-r--r--fluid/nodes/factory.h34
18 files changed, 15252 insertions, 0 deletions
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 <FL/Fl.H>
+#include <FL/Fl_Button.H>
+#include <FL/Fl_Check_Button.H>
+#include <FL/Fl_Menu_Item.H>
+#include <FL/Fl_Return_Button.H>
+#include <FL/Fl_Repeat_Button.H>
+#include <FL/Fl_Round_Button.H>
+
+#include <stdlib.h>
+
+
+
+// ---- 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 <FL/fl_string_functions.h>
+#include <FL/Fl_File_Chooser.H>
+#include <FL/fl_ask.H>
+#include "../src/flstring.h"
+
+#include <zlib.h>
+
+
+/// Set a current class, so that the code of the children is generated correctly.
+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 (csc<e && (csc[0]!='/' || csc[1]!='/')) csc++;
+ if (csc!=e) e = csc; // comment found
+ // lose spaces between text and comment, if any
+ while (e>c && e[-1]==' ') e--;
+ if (class_name(1)) {
+ f.write_public(public_);
+ write_comment_h(f, f.indent(1));
+ f.write_hc(f.indent(1), int(e-c), c, csc);
+ } else {
+ if (public_) {
+ if (static_)
+ f.write_h("extern ");
+ else
+ write_comment_h(f);
+ f.write_hc("", int(e-c), c, csc);
+
+ if (static_) {
+ write_comment_c(f);
+ f.write_cc("", int(e-c), c, csc);
+ }
+ } else {
+ write_comment_c(f);
+ if (static_)
+ f.write_c("static ");
+ f.write_cc("", int(e-c), c, csc);
+ }
+ }
+}
+
+// ---- 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_ : "<no filename>";
+ }
+ if (is_in_class()) {
+ f.write_public(public_);
+ if (text_mode_ == 1) {
+ f.write_h("%sstatic const char *%s;\n", f.indent(1), c);
+ f.write_c("\n");
+ write_comment_c(f);
+ f.write_c("const char *%s::%s = /* text inlined from %s */\n", class_name(1), c, fn);
+ if (message) f.write_c("#error %s %s\n", message, fn);
+ f.write_cstring(data, nData);
+ } else if (text_mode_ == 2) {
+ f.write_h("%sstatic int %s_size;\n", f.indent(1), c);
+ f.write_h("%sstatic unsigned char %s[%d];\n", f.indent(1), c, nData);
+ f.write_c("\n");
+ write_comment_c(f);
+ f.write_c("int %s::%s_size = %d;\n", class_name(1), c, uncompressedDataSize);
+ f.write_c("unsigned char %s::%s[%d] = /* data compressed and inlined from %s */\n", class_name(1), c, nData, fn);
+ if (message) f.write_c("#error %s %s\n", message, fn);
+ f.write_cdata(data, nData);
+ } else {
+ f.write_h("%sstatic unsigned char %s[%d];\n", f.indent(1), c, nData);
+ f.write_c("\n");
+ write_comment_c(f);
+ f.write_c("unsigned char %s::%s[%d] = /* data inlined from %s */\n", class_name(1), c, nData, fn);
+ if (message) f.write_c("#error %s %s\n", message, fn);
+ f.write_cdata(data, nData);
+ }
+ f.write_c(";\n");
+ } else {
+ // the "header only" option does not apply here!
+ if (public_) {
+ if (static_) {
+ if (text_mode_ == 1) {
+ f.write_h("extern const char *%s;\n", c);
+ f.write_c("\n");
+ write_comment_c(f);
+ f.write_c("const char *%s = /* text inlined from %s */\n", c, fn);
+ if (message) f.write_c("#error %s %s\n", message, fn);
+ f.write_cstring(data, nData);
+ } else if (text_mode_ == 2) {
+ f.write_h("extern int %s_size;\n", c);
+ f.write_h("extern unsigned char %s[%d];\n", c, nData);
+ f.write_c("\n");
+ write_comment_c(f);
+ f.write_c("int %s_size = %d;\n", c, uncompressedDataSize);
+ f.write_c("unsigned char %s[%d] = /* data compressed and inlined from %s */\n", c, nData, fn);
+ if (message) f.write_c("#error %s %s\n", message, fn);
+ f.write_cdata(data, nData);
+ } else {
+ f.write_h("extern unsigned char %s[%d];\n", c, nData);
+ f.write_c("\n");
+ write_comment_c(f);
+ f.write_c("unsigned char %s[%d] = /* data inlined from %s */\n", c, nData, fn);
+ if (message) f.write_c("#error %s %s\n", message, fn);
+ f.write_cdata(data, nData);
+ }
+ f.write_c(";\n");
+ } else {
+ write_comment_h(f);
+ f.write_h("#error Unsupported declaration loading inline data %s\n", fn);
+ if (text_mode_ == 1)
+ f.write_h("const char *%s = \"abc...\";\n", c);
+ else
+ f.write_h("unsigned char %s[3] = { 1, 2, 3 };\n", c);
+ }
+ } else {
+ f.write_c("\n");
+ write_comment_c(f);
+ if (static_)
+ f.write_c("static ");
+ if (text_mode_ == 1) {
+ f.write_c("const char *%s = /* text inlined from %s */\n", c, fn);
+ if (message) f.write_c("#error %s %s\n", message, fn);
+ f.write_cstring(data, nData);
+ } else if (text_mode_ == 2) {
+ f.write_c("int %s_size = %d;\n", c, uncompressedDataSize);
+ if (static_) f.write_c("static ");
+ f.write_c("unsigned char %s[%d] = /* data compressed and inlined from %s */\n", c, nData, fn);
+ if (message) f.write_c("#error %s %s\n", message, fn);
+ f.write_cdata(data, nData);
+ } else {
+ f.write_c("unsigned char %s[%d] = /* data inlined from %s */\n", c, nData, fn);
+ if (message) f.write_c("#error %s %s\n", message, fn);
+ f.write_cdata(data, nData);
+ }
+ f.write_c(";\n");
+ }
+ }
+ // if we are in interactive mode, we pop up a warning dialog
+ // giving the error: (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;i<n;i++) {
+ char *text;
+ menu.get(Fl_Preferences::Name(i), text, "");
+ comment_predefined->add(text);
+ free(text);
+ }
+ }
+ comment_input->buffer()->text( text ? text : "" );
+ comment_in_source->value(in_c_);
+ comment_in_header->value(in_h_);
+ comment_panel->show();
+ char itempath[FL_PATH_MAX]; itempath[0] = 0;
+ int last_selected_item = 0;
+ for (;;) { // repeat as long as there are errors
+ for (;;) {
+ Fl_Widget* w = Fl::readqueue();
+ if (w == comment_panel_cancel) goto BREAK2;
+ else if (w == comment_panel_ok) break;
+ else if (w == comment_predefined) {
+ if (comment_predefined->value()==1) {
+ // add the current comment to the database
+ const char *xname = fl_input(
+ "Please enter a name to reference the current\ncomment in your database.\n\n"
+ "Use forward slashes '/' to create submenus.",
+ "My Comment");
+ if (xname) {
+ char *name = fl_strdup(xname);
+ for (char*s=name;*s;s++) if (*s==':') *s = ';';
+ int n;
+ Fl_Preferences db(Fl_Preferences::USER_L, "fltk.org", "fluid_comments");
+ db.set(name, comment_input->buffer()->text());
+ Fl_Preferences menu(Fl_Preferences::USER_L, "fltk.org", "fluid_comments_menu");
+ menu.get("n", n, 0);
+ menu.set(Fl_Preferences::Name(n), name);
+ menu.set("n", ++n);
+ comment_predefined->add(name);
+ free(name);
+ }
+ } else if (comment_predefined->value()==2) {
+ // remove the last selected comment from the database
+ if (itempath[0]==0 || last_selected_item==0) {
+ fl_message("Please select an entry from this menu first.");
+ } else if (fl_choice("Are you sure that you want to delete the entry\n"
+ "\"%s\"\nfrom the database?", "Cancel", "Delete",
+ 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; i<comment_predefined->size(); i++) {
+ const Fl_Menu_Item *mi = comment_predefined->menu()+i;
+ if (comment_predefined->item_pathname(itempath, 255, mi)==0) {
+ if (itempath[0]=='/') memmove(itempath, itempath+1, 255);
+ if (itempath[0]) menu.set(Fl_Preferences::Name(n++), itempath);
+ }
+ }
+ menu.set("n", n);
+ }
+ } else {
+ // load the selected comment from the database
+ if (comment_predefined->item_pathname(itempath, 255)==0) {
+ if (itempath[0]=='/') memmove(itempath, itempath+1, 255);
+ Fl_Preferences db(Fl_Preferences::USER_L, "fltk.org", "fluid_comments");
+ char *text;
+ db.get(itempath, text, "(no text found in data base)");
+ comment_input->buffer()->text(text);
+ free(text);
+ last_selected_item = comment_predefined->value();
+ }
+ }
+ }
+ else if (w == comment_load) {
+ // load a comment from disk
+ fl_file_chooser_ok_label("Use File");
+ const char *fname = fl_file_chooser("Pick a comment", 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) goto OOPS;
+ while (p>=s && is_id(*p)) p--;
+ if ( (p<s && !is_id(*(p+1))) || !*(p+1) ) {
+ OOPS: message = "class name must be C++ identifier";
+ free((void*)s);
+ continue;
+ }
+ na=p+1; // now we have the name
+ if(p>s) *p--='\0';
+ while (p>=s && isspace(*p)) *(p--)='\0';
+ while (p>=s && is_id(*p)) p--;
+ if (p<s) p++;
+ if (is_id(*p) && p<na) pr=p; // prefix detected
+ c = c_subclass_input->value();
+ message = c_check(c);
+ if (message) { free((void*)s);continue;}
+ name(na);
+ prefix(pr);
+ free((void*)s);
+ storestring(c, subclass_of);
+ if (public_ != c_public_button->value()) {
+ public_ = c_public_button->value();
+ 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 <FL/Fl_Widget.H>
+#include <FL/Fl_Menu.H>
+#include <FL/fl_draw.H>
+#include <FL/fl_attr.h>
+
+#include <stdarg.h>
+#include <stdlib.h>
+
+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 <FL/Fl_Grid.H>
+#include <FL/Fl_Value_Input.H>
+#include <FL/Fl_Button.H>
+#include <FL/Fl_Choice.H>
+#include "../src/flstring.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+
+// ---- 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<num_transient_; i++) {
+ if (transient_[i].cell) ::free(transient_[i].cell);
+ }
+ ::free(transient_);
+ }
+}
+
+// Override group's resize behavior to do nothing to children:
+void Fl_Grid_Proxy::resize(int X, int Y, int W, int H) {
+ if (Fl_Type::allow_layout > 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)<children());
+
+ short rowspan = 1, colspan = 1;
+ Fl_Grid_Align align = FL_GRID_FILL;
+ int w = 20, h = 20;
+ const Fl_Grid::Cell *old_cell = cell(in_child);
+ if (old_cell) {
+ if (old_cell->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; i<num_transient_; i++) {
+ if (transient_[i].widget == wi) {
+ old_cell = transient_[i].cell;
+ break;
+ }
+ }
+ }
+ Cell *new_cell = new Cell(wi, row, col);
+ new_cell->rowspan(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; i<num_transient_; i++) {
+ if (transient_[i].widget==w) {
+ if (transient_[i].cell) {
+ ::free(transient_[i].cell);
+ ::memmove(transient_+i, transient_+i+1, sizeof(Cell_Widget_Pair)*(num_transient_-i-1));
+ num_transient_--;
+ return;
+ }
+ }
+ }
+}
+
+/**
+ Find a cell in the grid or in the transient cell list.
+ \param[in] widget must be a child of the grid.
+ \return the cell, the transient cell, or NULL if neither was found.
+ */
+Fl_Grid_Proxy::Cell *Fl_Grid_Proxy::any_cell(Fl_Widget *widget) const {
+ Cell *c = cell(widget);
+ if (c) return c;
+ return transient_cell(widget);
+}
+
+/**
+ Find a cell in the transient cell list.
+ \param[in] widget must be a child of the grid.
+ \return the transient cell, or NULL if it was not found.
+ */
+Fl_Grid_Proxy::Cell *Fl_Grid_Proxy::transient_cell(Fl_Widget *widget) const {
+ for (int i=0; i<num_transient_; i++) {
+ if (transient_[i].widget == widget)
+ return transient_[i].cell;
+ }
+ return NULL;
+}
+
+/**
+ Forwarding the call.
+ \param[in] wi generate a cell for this widget
+ \param[in] row, col, align cell parameters
+ */
+Fl_Grid::Cell *Fl_Grid_Proxy::widget(Fl_Widget *wi, int row, int col, Fl_Grid_Align align) {
+ return widget(wi, row, col, 1, 1, align);
+}
+
+/**
+ Just like the Fl_Grid original, but removes potential transient cell.
+ \param[in] wi generate a cell for this widget
+ \param[in] row, col, rowspan, colspan, align cell parameters
+ */
+Fl_Grid::Cell *Fl_Grid_Proxy::widget(Fl_Widget *wi, int row, int col, int rowspan, int colspan, Fl_Grid_Align align) {
+ transient_remove_(wi);
+ return Fl_Grid::widget(wi, row, col, rowspan, colspan, align);
+}
+
+
+
+// ---- Fl_Grid_Type --------------------------------------------------- MARK: -
+
+const char grid_type_name[] = "Fl_Grid";
+
+Fl_Grid_Type Fl_Grid_type; // the "factory"
+
+Fl_Grid_Type::Fl_Grid_Type() {
+}
+
+Fl_Widget *Fl_Grid_Type::widget(int X,int Y,int W,int H) {
+ Fl_Grid *g = new Fl_Grid_Proxy(X,Y,W,H);
+ g->layout(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; c<s->cols(); 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; r<s->rows(); 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; i<s->children(); i++) {
+ Fl_Grid::Cell *cell = s->cell(s->child(i));
+ if (cell && i<d->children()) {
+ 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; i<rows; i++) if (grid->row_height(i)!=0) break;
+ if (i<rows) {
+ f.write_indent(level+1);
+ f.write_string("rowheights {");
+ for (i=0; i<rows; i++) f.write_string("%d", grid->row_height(i));
+ f.write_string("}");
+ }
+ // -- write all row weights if one of them is not the default 50
+ for (i=0; i<rows; i++) if (grid->row_weight(i)!=50) break;
+ if (i<rows) {
+ f.write_indent(level+1);
+ f.write_string("rowweights {");
+ for (i=0; i<rows; i++) f.write_string("%d", grid->row_weight(i));
+ f.write_string("}");
+ }
+ // -- write all row gaps if one of them is not the default -1
+ for (i=0; i<rows; i++) if (grid->row_gap(i)!=-1) break;
+ if (i<rows) {
+ f.write_indent(level+1);
+ f.write_string("rowgaps {");
+ for (i=0; i<rows; i++) f.write_string("%d", grid->row_gap(i));
+ f.write_string("}");
+ }
+ // -- write all col widths if one of them is not the default 0
+ for (i=0; i<cols; i++) if (grid->col_width(i)!=0) break;
+ if (i<cols) {
+ f.write_indent(level+1);
+ f.write_string("colwidths {");
+ for (i=0; i<cols; i++) f.write_string("%d", grid->col_width(i));
+ f.write_string("}");
+ }
+ // -- write all col weights if one of them is not the default 50
+ for (i=0; i<cols; i++) if (grid->col_weight(i)!=50) break;
+ if (i<cols) {
+ f.write_indent(level+1);
+ f.write_string("colweights {");
+ for (i=0; i<cols; i++) f.write_string("%d", grid->col_weight(i));
+ f.write_string("}");
+ }
+ // -- write all col gaps if one of them is not the default -1
+ for (i=0; i<cols; i++) if (grid->col_gap(i)!=-1) break;
+ if (i<cols) {
+ f.write_indent(level+1);
+ f.write_string("colgaps {");
+ for (i=0; i<cols; i++) f.write_string("%d", grid->col_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; i<rows; i++) grid->row_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; i<rows; i++) grid->row_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; i<rows; i++) grid->row_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; i<cols; i++) grid->col_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; i<cols; i++) grid->col_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; i<cols; i++) grid->col_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; i<rows; i++) if (grid->row_height(i)!=0) break;
+ if (i<rows) {
+ f.write_c("%sstatic const int rowheights[] = { %d", f.indent(), grid->row_height(0));
+ for (i=1; i<rows; i++) f.write_c(", %d", grid->row_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; i<rows; i++) if (grid->row_weight(i)!=50) break;
+ if (i<rows) {
+ f.write_c("%sstatic const int rowweights[] = { %d", f.indent(), grid->row_weight(0));
+ for (i=1; i<rows; i++) f.write_c(", %d", grid->row_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; i<rows; i++) if (grid->row_gap(i)!=-1) break;
+ if (i<rows) {
+ f.write_c("%sstatic const int rowgaps[] = { %d", f.indent(), grid->row_gap(0));
+ for (i=1; i<rows; i++) f.write_c(", %d", grid->row_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; i<cols; i++) if (grid->col_width(i)!=0) break;
+ if (i<cols) {
+ f.write_c("%sstatic const int colwidths[] = { %d", f.indent(), grid->col_width(0));
+ for (i=1; i<cols; i++) f.write_c(", %d", grid->col_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; i<cols; i++) if (grid->col_weight(i)!=50) break;
+ if (i<cols) {
+ f.write_c("%sstatic const int colweights[] = { %d", f.indent(), grid->col_weight(0));
+ for (i=1; i<cols; i++) f.write_c(", %d", grid->col_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; i<cols; i++) if (grid->col_gap(i)!=-1) break;
+ if (i<cols) {
+ f.write_c("%sstatic const int colgaps[] = { %d", f.indent(), grid->col_gap(0));
+ for (i=1; i<cols; i++) f.write_c(", %d", grid->col_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; i<grid->children(); 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/Fl_Grid.H>
+
+// ---- 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 <FL/Fl.H>
+#include <FL/Fl_Group.H>
+#include <FL/Fl_Table.H>
+#include <FL/Fl_Menu_Item.H>
+#include <FL/fl_message.H>
+#include <FL/Fl_Scroll.H>
+#include "../src/flstring.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+
+
+// ---- 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_Widget_Type*>(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_Widget_Type*>(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; i<nc; i++) {
+ if (s->fixed(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; i<s->children(); i++) {
+ if (s->fixed(s->child(i)) && i<d->children()) {
+ 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; i<flex->children(); i++)
+ if (flex->fixed(flex->child(i)))
+ nSet++;
+ if (nSet) {
+ f.write_string("fixed_size_tuples {%d", nSet);
+ for (int i=0; i<flex->children(); 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; i<fixedSizeTupleSize; i++) {
+ const char *ix = f.read_word(1); // child at that index is fixed in size
+ fixedSizeTuple[i*2] = atoi(ix);
+ const char *size = f.read_word(1); // fixed size of that child
+ fixedSizeTuple[i*2+1] = atoi(size);
+ }
+ f.read_word(1); // must be '}'
+ } else {
+ Fl_Group_Type::read_property(f, c);
+ }
+}
+
+void Fl_Flex_Type::postprocess_read()
+{
+ Fl_Flex* flex = (Fl_Flex*)o;
+ if (fixedSizeTupleSize>0) {
+ for (int i=0; i<fixedSizeTupleSize; i++) {
+ int ix = fixedSizeTuple[2*i];
+ int size = fixedSizeTuple[2*i+1];
+ if (ix>=0 && ix<flex->children()) {
+ 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; i<flex->children(); ++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; i<nc; i++) {
+ Fl_Widget* c = f->child(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; i<nc; i++) {
+ Fl_Widget* c = f->child(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; i<flex->children(); 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; i<flex->children(); 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; r<MAX_ROWS; r++ )
+ for ( int c=0; c<MAX_COLS; c++ )
+ data[r][c] = 1000+(r*1000)+c;
+ // Rows
+ rows(MAX_ROWS); // how many rows
+ row_header(1); // enable row headers (along left)
+ row_height_all(20); // default height of rows
+ row_resize(0); // disable row resizing
+ // Cols
+ cols(MAX_COLS); // how many columns
+ col_header(1); // enable column headers (along top)
+ col_width_all(80); // default width of columns
+ col_resize(1); // enable column resizing
+ }
+};
+
+Fl_Widget *Fl_Table_Type::widget(int X,int Y,int W,int H) {
+ Fluid_Table *table = new Fluid_Table(X, Y, W, H);
+ return table;
+}
+
+void Fl_Table_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;
+ 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<Fl_Tabs*>(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_index<clone->children()))
+ 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 <FL/Fl_Tabs.H>
+#include <FL/Fl_Pack.H>
+#include <FL/Fl_Flex.H>
+#include <FL/Fl_Wizard.H>
+
+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 <FL/Fl.H>
+#include <FL/fl_message.H>
+#include <FL/Fl_Menu_.H>
+#include <FL/Fl_Button.H>
+#include <FL/Fl_Value_Input.H>
+#include <FL/Fl_Text_Display.H>
+#include <FL/Fl_Menu_Button.H>
+#include <FL/Fl_Shortcut_Button.H>
+#include <FL/Fl_Output.H>
+#include <FL/fl_draw.H>
+#include <FL/Fl_Multi_Label.H>
+#include "../src/flstring.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+
+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 (menusize<n) {
+ if (menusize) delete_menu((Fl_Menu_Item*)(w->menu()));
+ 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_Menu_Item_Type*>(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_Widget_Type*>(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 <FL/Fl.H>");
+ f.write_h_once("#include <FL/Fl_Multi_Label.H>");
+ }
+ 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<Fl_Widget_Type*>(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 (menusize<n) {
+ if (menusize) delete_menu((Fl_Menu_Item*)(w->menu()));
+ 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 <FL/Fl_Sys_Menu_Bar.H>");
+ 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 <FL/Fl_Choice.H>
+#include <FL/Fl_Menu_.H>
+#include <FL/Fl_Menu_Button.H>
+#include <FL/Fl_Input_Choice.H>
+#include <FL/Fl_Window.H>
+#include <FL/Fl_Menu_Bar.H>
+
+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 <FL/Fl.H>
+#include <FL/Fl_Browser_.H>
+#include <FL/fl_draw.H>
+#include "../src/flstring.h"
+
+#include <stdlib.h>
+#include <stdio.h>
+
+// ---- 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 <FL/Fl_Widget.H>
+#include <FL/fl_draw.H>
+
+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 <FL/Fl.H>
+#include <FL/Fl_Group.H>
+#include <FL/Fl_Table.H>
+#include <FL/Fl_Input.H>
+#include <FL/fl_message.H>
+#include <FL/Fl_Slider.H>
+#include <FL/Fl_Spinner.H>
+#include <FL/Fl_Window.H>
+#include <FL/fl_show_colormap.H>
+#include "../src/flstring.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+
+// 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<Fl_Menu_Bar_Type*>(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; n<NUM_EXTRA_CODE; n++) {extra_code_[n] = 0; }
+ subclass_ = 0;
+ hotspot_ = 0;
+ tooltip_ = 0;
+ image_name_ = 0;
+ inactive_name_ = 0;
+ image = 0;
+ inactive = 0;
+ o = 0;
+ public_ = 1;
+ bind_image_ = 0;
+ compress_image_ = 1;
+ bind_deimage_ = 0;
+ compress_deimage_ = 1;
+ scale_image_w_ = 0;
+ scale_image_h_ = 0;
+ scale_deimage_w_ = 0;
+ scale_deimage_h_ = 0;
+}
+
+Fl_Widget_Type::~Fl_Widget_Type() {
+ if (o) {
+ Fl_Window *win = o->window();
+ 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; n<NUM_EXTRA_CODE; n++) {
+ if (extra_code_[n]) free((void*) extra_code_[n]);
+ }
+}
+
+void Fl_Widget_Type::extra_code(int m,const char *n) {
+ storestring(n,extra_code_[m]);
+}
+
+extern void redraw_browser();
+void Fl_Widget_Type::subclass(const char *n) {
+ if (storestring(n,subclass_) && visible)
+ redraw_browser();
+}
+
+void Fl_Widget_Type::tooltip(const char *n) {
+ storestring(n,tooltip_);
+ o->tooltip(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 (a<b) ? a : b; }
+static int bbox_max(int a, int b) { return (a>b) ? 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[] = {
+ {"<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 <FL/Fl.H>");
+ f.write_h_once("#include <FL/%s.H>", 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 <FL/Fl.H>
+#include <FL/Fl_Overlay_Window.H>
+#include <FL/fl_message.H>
+#include <FL/fl_draw.H>
+#include <FL/platform.H>
+#include <FL/Fl_Menu_Item.H>
+#include <FL/Fl_Round_Button.H>
+#include <FL/Fl_Shared_Image.H>
+#include <FL/Fl_Tooltip.H>
+#include "../src/flstring.h"
+
+#include <math.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+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<Fd_I18n_Type>(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 (X<bx+dx) X = bx+dx;
+ }
+ }
+ if (drag&FD_TOP) {
+ if (Y==by) {
+ Y += dy;
+ } else {
+ if (Y<by+dy) Y = by+dy;
+ }
+ }
+ if (drag&FD_RIGHT) {
+ if (R==br) {
+ R += dx;
+ } else {
+ if (R>br+dx) R = br+dx;
+ }
+ }
+ if (drag&FD_BOTTOM) {
+ if (T==bt) {
+ T += dy;
+ } else {
+ if (T>bt+dx) T = bt+dx;
+ }
+ }
+ }
+ if (R<X) {int n = X; X = R; R = n;}
+ if (T<Y) {int n = Y; Y = T; T = n;}
+}
+
+void fd_hatch(int x, int y, int w, int h, int size=6, int offset=0, int pad=3) {
+ x -= pad; y -= pad; w += 2*pad; h += 2*pad;
+ int yp = (x+offset+y*size-1-y)%size;
+ if (w > 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 (mx<x1) {int t = x1; x1 = mx; mx = t;}
+ if (my<y1) {int t = y1; y1 = my; my = t;}
+ int n = 0;
+ int toggle = Fl::event_state(FL_SHIFT);
+ // clear selection on everything:
+ if (!toggle) deselect(); else Fl::event_is_click(0);
+ // select everything in box:
+ 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 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()<mx && myo->o->y()+myo->o->h()<my) {
+ n++;
+ select(myo, toggle ? !myo->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<Fl_Window*>(o);
+ Fl_Window *live = static_cast<Fl_Window*>(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 <FL/Fl.H>
+#include <FL/Fl_Adjuster.H>
+#include <FL/Fl_Box.H>
+#include <FL/Fl_Browser.H>
+#include <FL/Fl_Check_Browser.H>
+#include <FL/Fl_Clock.H>
+#include <FL/Fl_Counter.H>
+#include <FL/Fl_Dial.H>
+#include <FL/Fl_File_Browser.H>
+#include <FL/Fl_File_Input.H>
+#include <FL/Fl_Help_View.H>
+#include <FL/Fl_Input.H>
+#include <FL/Fl_Menu_Item.H>
+#include <FL/Fl_Output.H>
+#include <FL/Fl_Progress.H>
+#include <FL/Fl_Roller.H>
+#include <FL/Fl_Scrollbar.H>
+#include <FL/Fl_Terminal.H>
+#include <FL/Fl_Spinner.H>
+#include <FL/Fl_Text_Display.H>
+#include <FL/Fl_Text_Editor.H>
+#include <FL/Fl_Tree.H>
+#include <FL/Fl_Value_Slider.H>
+#include <FL/Fl_Value_Input.H>
+#include <FL/Fl_Value_Output.H>
+#include <FL/Fl_Window.H>
+#include "../src/flstring.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+
+
+// ---- 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("<HTML><BODY><H1>Fl_Help_View Widget</H1>"
+ "<P>This is a Fl_Help_View widget.</P></BODY></HTML>");
+ }
+ 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 <FL/Fl_Multi_Label.H>
+
+/**
+ 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