summaryrefslogtreecommitdiff
path: root/fluid/nodes/Group_Node.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'fluid/nodes/Group_Node.cxx')
-rw-r--r--fluid/nodes/Group_Node.cxx849
1 files changed, 849 insertions, 0 deletions
diff --git a/fluid/nodes/Group_Node.cxx b/fluid/nodes/Group_Node.cxx
new file mode 100644
index 000000000..d269b62ab
--- /dev/null
+++ b/fluid/nodes/Group_Node.cxx
@@ -0,0 +1,849 @@
+//
+// Group Node code for the Fast Light Tool Kit (FLTK).
+//
+// Object describing an Fl_Group and links to Window_Node.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/Group_Node.h"
+
+#include "Fluid.h"
+#include "proj/undo.h"
+#include "app/Snap_Action.h"
+#include "io/Project_Reader.h"
+#include "io/Project_Writer.h"
+#include "io/Code_Writer.h"
+#include "widgets/Node_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>
+
+
+// ---- Group_Node -------------------------------------------------- MARK: -
+
+Group_Node Group_Node::prototype;
+
+/**
+ 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 (Fluid.proj.tree.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 (Fluid.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(Node *tt) {
+ if (!tt || !tt->is_a(Type::Group)) return;
+ Group_Node* t = (Group_Node*)tt;
+ int X = t->o->x();
+ int Y = t->o->y();
+ int R = X+t->o->w();
+ int B = Y+t->o->h();
+ for (Node *nn = t->next; nn && nn->level > t->level; nn = nn->next) {
+ if (nn->is_true_widget()) {
+ Widget_Node* n = (Widget_Node*)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 (!Fluid.proj.tree.current) {
+ fl_message("No widgets selected.");
+ return;
+ }
+ if (!Fluid.proj.tree.current->is_widget()) {
+ fl_message("Only widgets and menu items can be grouped.");
+ return;
+ }
+ if (Fluid.proj.tree.current->is_a(Type::Menu_Item)) {
+ group_selected_menuitems();
+ return;
+ }
+ // The group will be created in the parent group of the current widget
+ Node *qq = Fluid.proj.tree.current->parent;
+ Widget_Node *q = static_cast<Widget_Node*>(Fluid.proj.tree.current);
+ while (qq && !qq->is_a(Type::Group)) {
+ qq = qq->parent;
+ }
+ if (!qq) {
+ fl_message("Can't create a new group here.");
+ return;
+ }
+ Fluid.proj.undo.checkpoint();
+ Fluid.proj.undo.suspend();
+ Fluid.proj.tree.current = qq;
+ Group_Node *n = (Group_Node*)(Group_Node::prototype.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 (Node *t = qq->next; t && (t->level > qq->level);) {
+ if (t->level != n->level || t == n || !t->selected) {
+ t = t->next;
+ continue;
+ }
+ Node *nxt = t->remove();
+ t->add(n, Strategy::AS_LAST_CHILD);
+ t = nxt;
+ }
+ fix_group_size(n);
+ Fluid.proj.tree.current = q;
+ n->layout_widget();
+ widget_browser->rebuild();
+ Fluid.proj.undo.resume();
+ Fluid.proj.set_modflag(1);
+}
+
+extern void ungroup_selected_menuitems();
+
+void ungroup_cb(Fl_Widget *, void *) {
+ if (!Fluid.proj.tree.current) {
+ fl_message("No widgets selected.");
+ return;
+ }
+ if (!Fluid.proj.tree.current->is_widget()) {
+ fl_message("Only widgets and menu items can be ungrouped.");
+ return;
+ }
+ if (Fluid.proj.tree.current->is_a(Type::Menu_Item)) {
+ ungroup_selected_menuitems();
+ return;
+ }
+
+ Widget_Node *q = static_cast<Widget_Node*>(Fluid.proj.tree.current);
+ int q_level = q->level;
+ Node *qq = Fluid.proj.tree.current->parent;
+ while (qq && !qq->is_true_widget()) qq = qq->parent;
+ if (!qq || !qq->is_a(Type::Group)) {
+ fl_message("Only menu widgets inside a group can be ungrouped.");
+ return;
+ }
+ Fluid.proj.undo.checkpoint();
+ Fluid.proj.undo.suspend();
+ Fluid.proj.tree.current = qq;
+ for (Node *t = qq->next; t && (t->level > qq->level);) {
+ if (t->level != q_level || !t->selected) {
+ t = t->next;
+ continue;
+ }
+ Node *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
+ }
+ Fluid.proj.tree.current = q;
+ widget_browser->rebuild();
+ Fluid.proj.undo.resume();
+ Fluid.proj.set_modflag(1);
+}
+
+void Group_Node::ideal_size(int &w, int &h) {
+ if (parent && parent->is_true_widget()) {
+ Fl_Widget *p = ((Widget_Node*)parent)->o;
+ w = p->w() / 2;
+ h = p->h() / 2;
+ } else {
+ w = 140;
+ h = 140;
+ }
+ fld::app::Snap_Action::better_size(w, h);
+}
+
+void Group_Node::write_code1(fld::io::Code_Writer& f) {
+ Widget_Node::write_code1(f);
+}
+
+void Group_Node::write_code2(fld::io::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 Group_Node::add_child(Node* cc, Node* before) {
+ Widget_Node* c = (Widget_Node*)cc;
+ Fl_Widget* b = before ? ((Widget_Node*)before)->o : nullptr;
+ ((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 Group_Node::remove_child(Node* cc) {
+ Widget_Node* c = (Widget_Node*)cc;
+ ((Fl_Group*)o)->remove(c->o);
+ o->redraw();
+}
+
+// move, don't change selected value:
+void Group_Node::move_child(Node* cc, Node* before) {
+ Widget_Node* c = (Widget_Node*)cc;
+ Fl_Widget* b = before ? ((Widget_Node*)before)->o : nullptr;
+ ((Fl_Group*)o)->insert(*(c->o), b);
+ o->redraw();
+}
+
+// live mode support
+Fl_Widget* Group_Node::enter_live_mode(int) {
+ Fl_Group *grp = new Fl_Group(o->x(), o->y(), o->w(), o->h());
+ return propagate_live_mode(grp);
+}
+
+void Group_Node::leave_live_mode() {
+}
+
+/**
+ copy all properties from the edit widget to the live widget
+ */
+void Group_Node::copy_properties() {
+ Widget_Node::copy_properties();
+}
+
+// ---- Pack_Node --------------------------------------------------- MARK: -
+
+Pack_Node Pack_Node::prototype; // the "factory"
+
+const char pack_type_name[] = "Fl_Pack";
+
+Fl_Menu_Item pack_type_menu[] = {
+ {"HORIZONTAL", 0, nullptr, (void*)Fl_Pack::HORIZONTAL},
+ {"VERTICAL", 0, nullptr, (void*)Fl_Pack::VERTICAL},
+ {nullptr}
+};
+
+Fl_Widget *Pack_Node::enter_live_mode(int) {
+ Fl_Group *grp = new Fl_Pack(o->x(), o->y(), o->w(), o->h());
+ return propagate_live_mode(grp);
+}
+
+void Pack_Node::copy_properties()
+{
+ Group_Node::copy_properties();
+ Fl_Pack *d = (Fl_Pack*)live_widget, *s =(Fl_Pack*)o;
+ d->spacing(s->spacing());
+}
+
+// ---- Flex_Node --------------------------------------------------- MARK: -
+
+const char flex_type_name[] = "Fl_Flex";
+
+Fl_Menu_Item flex_type_menu[] = {
+ {"HORIZONTAL", 0, nullptr, (void*)Fl_Flex::HORIZONTAL},
+ {"VERTICAL", 0, nullptr, (void*)Fl_Flex::VERTICAL},
+ {nullptr}};
+
+Flex_Node Flex_Node::prototype; // 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 (Fluid.proj.tree.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 (Fluid.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 *Flex_Node::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 Flex_Node::copy_properties()
+{
+ Group_Node::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 Flex_Node::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 Flex_Node::write_properties(fld::io::Project_Writer &f)
+{
+ Group_Node::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 Flex_Node::read_property(fld::io::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 {
+ Group_Node::read_property(f, c);
+ }
+}
+
+void Flex_Node::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 = nullptr;
+ }
+ suspend_auto_layout = 0;
+}
+
+void Flex_Node::write_code2(fld::io::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());
+ }
+ Group_Node::write_code2(f);
+}
+
+//void Flex_Node::add_child(Node* a, Node* b) {
+// Group_Node::add_child(a, b);
+// if (!suspend_auto_layout)
+// ((Fl_Flex*)o)->layout();
+//}
+//
+//void Flex_Node::move_child(Node* a, Node* b) {
+// Group_Node::move_child(a, b);
+// if (!suspend_auto_layout)
+// ((Fl_Flex*)o)->layout();
+//}
+
+void Flex_Node::remove_child(Node* a) {
+ if (a->is_widget())
+ ((Fl_Flex*)o)->fixed(((Widget_Node*)a)->o, 0);
+ Group_Node::remove_child(a);
+// ((Fl_Flex*)o)->layout();
+ layout_widget();
+}
+
+void Flex_Node::layout_widget() {
+ Fluid.proj.tree.allow_layout++;
+ ((Fl_Flex*)o)->layout();
+ Fluid.proj.tree.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 Flex_Node::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 Flex_Node::parent_is_flex(Node *t) {
+ return (t->is_widget()
+ && t->parent
+ && t->parent->is_a(Type::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 Flex_Node::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 Flex_Node::keyboard_move_child(Widget_Node *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 Flex_Node::size(Node *t, char fixed_only) {
+ if (!t->is_widget()) return 0;
+ if (!t->parent) return 0;
+ if (!t->parent->is_a(Type::Flex)) return 0;
+ Flex_Node* ft = (Flex_Node*)t->parent;
+ Fl_Flex* f = (Fl_Flex*)ft->o;
+ Fl_Widget *w = ((Widget_Node*)t)->o;
+ if (fixed_only && !f->fixed(w)) return 0;
+ return f->horizontal() ? w->w() : w->h();
+}
+
+int Flex_Node::is_fixed(Node *t) {
+ if (!t->is_widget()) return 0;
+ if (!t->parent) return 0;
+ if (!t->parent->is_a(Type::Flex)) return 0;
+ Flex_Node* ft = (Flex_Node*)t->parent;
+ Fl_Flex* f = (Fl_Flex*)ft->o;
+ Fl_Widget *w = ((Widget_Node*)t)->o;
+ return f->fixed(w);
+}
+
+// ---- Table_Node -------------------------------------------------- MARK: -
+
+Table_Node Table_Node::prototype; // 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 Fl_Table_Proxy : 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) 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:
+ Fl_Table_Proxy(int x, int y, int w, int h, const char *l=nullptr)
+ : 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 *Table_Node::widget(int X,int Y,int W,int H) {
+ Fl_Table_Proxy *table = new Fl_Table_Proxy(X, Y, W, H);
+ return table;
+}
+
+void Table_Node::add_child(Node* cc, Node* before) {
+ Widget_Node* c = (Widget_Node*)cc;
+ Fl_Widget* b = before ? ((Widget_Node*)before)->o : nullptr;
+ 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 Table_Node::remove_child(Node* cc) {
+ Widget_Node* c = (Widget_Node*)cc;
+ ((Fl_Table*)o)->remove(*(c->o));
+ o->redraw();
+}
+
+void Table_Node::move_child(Node* cc, Node* before) {
+ Widget_Node* c = (Widget_Node*)cc;
+ Fl_Widget* b = before ? ((Widget_Node*)before)->o : nullptr;
+ ((Fl_Table*)o)->insert(*(c->o), b);
+ o->redraw();
+}
+
+Fl_Widget *Table_Node::enter_live_mode(int) {
+ Fl_Group *grp = new Fl_Table_Proxy(o->x(), o->y(), o->w(), o->h());
+ live_widget = grp;
+ copy_properties();
+ grp->end();
+ return live_widget;
+}
+
+void Table_Node::ideal_size(int &w, int &h) {
+ w = 160;
+ h = 120;
+ fld::app::Snap_Action::better_size(w, h);
+}
+
+// ---- Tabs_Node --------------------------------------------------- MARK: -
+
+Tabs_Node Tabs_Node::prototype;
+
+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 (Fluid.proj.tree.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 (Fluid.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:
+
+Node* Tabs_Node::click_test(int x, int y) {
+ Fl_Tabs *t = (Fl_Tabs*)o;
+ Fl_Widget *a = t->which(x,y);
+ if (!a) return nullptr; // 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) Fluid.proj.set_modflag(1);
+ return (Node*)(t->value()->user_data());
+}
+
+void Tabs_Node::add_child(Node* c, Node* before) {
+ Group_Node::add_child(c, before);
+}
+
+void Tabs_Node::remove_child(Node* cc) {
+ Widget_Node* c = (Widget_Node*)cc;
+ Fl_Tabs *t = (Fl_Tabs*)o;
+ if (t->value() == c->o) t->value(nullptr);
+ Group_Node::remove_child(c);
+}
+
+Fl_Widget *Tabs_Node::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;
+}
+
+// ---- Scroll_Node ------------------------------------------------- MARK: -
+
+Scroll_Node Scroll_Node::prototype; // the "factory"
+
+const char scroll_type_name[] = "Fl_Scroll";
+
+Fl_Menu_Item scroll_type_menu[] = {
+ {"BOTH", 0, nullptr, nullptr/*(void*)Fl_Scroll::BOTH*/},
+ {"HORIZONTAL", 0, nullptr, (void*)Fl_Scroll::HORIZONTAL},
+ {"VERTICAL", 0, nullptr, (void*)Fl_Scroll::VERTICAL},
+ {"HORIZONTAL_ALWAYS", 0, nullptr, (void*)Fl_Scroll::HORIZONTAL_ALWAYS},
+ {"VERTICAL_ALWAYS", 0, nullptr, (void*)Fl_Scroll::VERTICAL_ALWAYS},
+ {"BOTH_ALWAYS", 0, nullptr, (void*)Fl_Scroll::BOTH_ALWAYS},
+ {nullptr}};
+
+Fl_Widget *Scroll_Node::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 Scroll_Node::copy_properties() {
+ Group_Node::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());
+}
+
+// ---- Tile_Node --------------------------------------------------- MARK: -
+
+Tile_Node Tile_Node::prototype; // the "factory"
+
+const char tile_type_name[] = "Fl_Tile";
+
+void Tile_Node::copy_properties() {
+ Group_Node::copy_properties();
+ // no additional properties
+}
+
+// ---- Wizard_Node ------------------------------------------------ MARK: -
+
+Wizard_Node Wizard_Node::prototype; // 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 (Fluid.proj.tree.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 (Fluid.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();
+}
+