From 51a55bc73660f64e8f4b32b8b4d3858f2a786f7b Mon Sep 17 00:00:00 2001 From: Matthias Melcher Date: Sun, 16 Mar 2025 17:16:12 -0400 Subject: Fluid: restructuring and rejuvenation of the source code. * Add classes for application and project * Removed all globals from Fluid.h * Extracting args and project history into their own classes * Moving globals into Application class * Initialize values inside headers for some classes. * Undo functionality wrapped in a class inside Project. * File reader and writer are now linked to a project. * Avoid global project access * Nodes (former Types) will be managed by a new Tree class. * Removed static members (hidden globals) form Node/Fl_Type. * Adding Tree iterator. * Use nullptr instead of 0, NULL, or 0L * Renamed Fl_..._Type to ..._Node, FL_OVERRIDE -> override * Renaming ..._type to ...::prototype * Splitting Widget Panel into multiple files. * Moved callback code into widget panel file. * Cleaning up Fluid_Image -> Image_asset * Moving Fd_Snap_Action into new namespace fld::app::Snap_Action etc. * Moved mergeback into proj folder. * `enum ID` is now `enum class Type`. --- fluid/nodes/Window_Node.cxx | 1497 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1497 insertions(+) create mode 100644 fluid/nodes/Window_Node.cxx (limited to 'fluid/nodes/Window_Node.cxx') diff --git a/fluid/nodes/Window_Node.cxx b/fluid/nodes/Window_Node.cxx new file mode 100644 index 000000000..d6cbe80e6 --- /dev/null +++ b/fluid/nodes/Window_Node.cxx @@ -0,0 +1,1497 @@ +// +// Window Node code 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 +// + +// +// 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. + +#include "nodes/Window_Node.h" + +#include "app/Snap_Action.h" +#include "Fluid.h" +#include "Project.h" +#include "proj/undo.h" +#include "io/Project_Reader.h" +#include "io/Project_Writer.h" +#include "io/Code_Writer.h" +#include "nodes/factory.h" +#include "nodes/Group_Node.h" +#include "nodes/Grid_Node.h" +#include "panels/settings_panel.h" +#include "panels/widget_panel.h" +#include "widgets/Node_Browser.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../src/flstring.h" + +#include +#include +#include +#undef min +#undef max +#include + +extern Fl_Window *the_panel; +extern void draw_width(int x, int y, int r, Fl_Align a); +extern void draw_height(int x, int y, int b, Fl_Align a); + +// Update the XYWH values in the widget panel... +static void update_xywh() { + if (current_widget && current_widget->is_widget()) { + Fl_Widget *o = ((Widget_Node *)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 (Flex_Node::parent_is_flex(current_widget)) { + widget_flex_size->value(Flex_Node::size(current_widget)); + widget_flex_fixed->value(Flex_Node::is_fixed(current_widget)); + } + } +} + +void i18n_type_cb(Fl_Choice *c, void *v) { + if (v == LOAD) { + c->value(static_cast(Fluid.proj.i18n_type)); + } else { + Fluid.proj.undo.checkpoint(); + Fluid.proj.i18n_type = static_cast(c->value()); + Fluid.proj.set_modflag(1); + } + switch (Fluid.proj.i18n_type) { + case fld::I18n_Type::NONE : /* None */ + i18n_gnu_group->hide(); + i18n_posix_group->hide(); + break; + case fld::I18n_Type::GNU : /* GNU gettext */ + i18n_gnu_group->show(); + i18n_posix_group->hide(); + break; + case fld::I18n_Type::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,nullptr,(void*)FL_WINDOW}, + {"Double",0,nullptr,(void*)(FL_DOUBLE_WINDOW)}, + {nullptr}}; + +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() override; + void draw_overlay() override; + static void close_cb(Overlay_Window *self, void*); +public: + Window_Node *window; + int handle(int) override; + Overlay_Window(int W,int H) : Fl_Overlay_Window(W,H) { + Fl_Group::current(nullptr); + callback((Fl_Callback*)close_cb); + } + void resize(int,int,int,int) 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()) + Fluid.proj.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 (Fluid.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(); + } +} + +// Read an image of the overlay window +uchar *Overlay_Window::read_image(int &ww, int &hh) { + // Create an off-screen buffer for the window... + //Fluid.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(nullptr, 0, 0, ww, hh); + + fl_end_offscreen(); + + // Cleanup and return... + fl_delete_offscreen(offscreen); + Fluid.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 + */ +Node *Window_Node::make(Strategy strategy) { + Node *anchor = Fluid.proj.tree.current, *p = anchor; + if (p && (strategy.placement() == Strategy::AFTER_CURRENT)) p = p->parent; + while (p && (!p->is_code_block() || p->is_a(Type::Widget_Class))) { + anchor = p; + strategy.placement(Strategy::AFTER_CURRENT); + p = p->parent; + } + if (!p) { + fl_message("Please select a function"); + return nullptr; + } + Window_Node *myo = new Window_Node(); + if (!this->o) {// template widget + this->o = new Fl_Window(100,100); + Fl_Group::current(nullptr); + } + 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 Window_Node::add_child(Node* cc, Node* before) { + if (!cc->is_widget()) return; + Widget_Node* c = (Widget_Node*)cc; + Fl_Widget* b = before ? ((Widget_Node*)before)->o : nullptr; + ((Fl_Window*)o)->insert(*(c->o), b); + o->redraw(); +} + +void Window_Node::remove_child(Node* cc) { + Widget_Node* c = (Widget_Node*)cc; + ((Fl_Window*)o)->remove(c->o); + o->redraw(); +} + +void Window_Node::move_child(Node* cc, Node* before) { + Widget_Node* c = (Widget_Node*)cc; + ((Fl_Window*)o)->remove(c->o); + Fl_Widget* b = before ? ((Widget_Node*)before)->o : nullptr; + ((Fl_Window*)o)->insert(*(c->o), b); + o->redraw(); +} + +//////////////////////////////////////////////////////////////// + +/** + \brief Show the Window Type editor window without setting the modified flag. + \see Window_Node::open() + */ +void Window_Node::open_() { + Overlay_Window *w = (Overlay_Window *)o; + if (w->shown()) { + w->show(); + Widget_Node::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 Window_Node::open_() + */ +void Window_Node::open() { + Overlay_Window *w = (Overlay_Window *)o; + if (!w->visible()) { + Fluid.proj.set_modflag(1, -2); + } + open_(); +} + +// Read an image of the window +uchar *Window_Node::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 Window_Node::ideal_size(int &w, int &h) { + w = 480; h = 320; + if (Fluid.main_window) { + int sx, sy, sw, sh; + Fl_Window *win = Fluid.main_window; + int screen = Fl::screen_num(win->x(), win->y()); + Fl::screen_work_area(sx, sy, sw, sh, screen); + w = std::min(w, sw*3/4); h = std::min(h, sh*3/4); + } + fld::app::Snap_Action::better_size(w, h); +} + + + +//////////////////////////////////////////////////////////////// + +Window_Node Window_Node::prototype; + +void Window_Node::setlabel(const char *n) { + if (o) ((Fl_Window *)o)->label(n); +} + +// 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 (Fluid.proj.undo.checkpoint(fld::proj::Undo::OnceType::WINDOW_RESIZE)) + Fluid.proj.set_modflag(1); + } + + Fl_Widget* t = resizable(); + if (Fluid.proj.tree.allow_layout == 0) { + resizable(nullptr); + } + + // 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()) + Fluid.proj.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 Window_Node::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 (Fluid.show_guides && (drag & (FD_DRAG|FD_TOP|FD_LEFT|FD_BOTTOM|FD_RIGHT))) { + Node *selection = nullptr; // special power for the first selected widget + for (Node *q=next; q && q->level>level; q = q->next) { + if (q->selected && q->is_true_widget()) { + selection = q; + break; + } + } + fld::app::Snap_Data data = { mydx, mydy, bx, by, br, bt, drag, 4, 4, mydx, mydy, (Widget_Node*)selection, this }; + fld::app::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 Window_Node::newposition(Widget_Node *myo,int &X,int &Y,int &R,int &T) { + X = myo->o->x(); + Y = myo->o->y(); + R = X+myo->o->w(); + T = Y+myo->o->h(); + if (!drag) return; + if (drag&FD_DRAG) { + X += dx; + Y += dy; + R += dx; + T += dy; + } else { + if (drag&FD_LEFT) { + if (X==bx) { + X += dx; + } else { + if (Xbr+dx) R = br+dx; + } + } + if (drag&FD_BOTTOM) { + if (T==bt) { + T += dy; + } else { + if (T>bt+dx) T = bt+dx; + } + } + } + if (R h) { + for (; yp < h; yp+=size) + fl_line(x, y+yp, x+yp, y); + for (; yp < w; yp+=size) + fl_line(x+yp-h, y+h, x+yp, y); + for (; yp < w+h; yp+=size) + fl_line(x+yp-h, y+h, x+w, y+yp-w); + } else { + for (; yp < w; yp+=size) + fl_line(x, y+yp, x+yp, y); + for (; yp < h; yp+=size) + fl_line(x, y+yp, x+w, y+yp-w); + for (; yp < h+w; yp+=size) + fl_line(x+yp-h, y+h, x+w, y+yp-w); + } +} + +/** + \brief Draw a hatch pattern over all children that overlap the bounds of this box. + \param[in] group check all children of this group + \param[in] x, y, w, h bounding box of this group + */ +void Window_Node::draw_out_of_bounds(Widget_Node *group, int x, int y, int w, int h) { + for (Node *p = group->next; p && p->level>group->level; p = p->next) { + if (p->level == group->level+1 && p->is_true_widget()) { + Fl_Widget *o = ((Widget_Node*)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 Window_Node::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 (Node *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(Type::Group) && !q->is_a(Type::Scroll)) { + Widget_Node *w = (Widget_Node*)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 Window_Node::draw_overlaps() { + fl_color(FL_DARK_YELLOW); + // loop through all widgets in this window + for (Node *q=next; q && q->level>level; q = q->next) { + // is it a valid widget + if (q->is_true_widget()) { + Widget_Node *w = (Widget_Node*)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 (Node *p=q->next; p && p->level>=q->level; p = p->next) { + if (p->level==q->level && p->is_true_widget()) { + Widget_Node *wp = (Widget_Node*)p; + if (wp->o->visible()) { + int px = std::max(x, wp->o->x()); + int py = std::max(y, wp->o->y()); + int pr = std::min(r, wp->o->x() + wp->o->w()); + int pb = std::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 Window_Node::draw_overlay() { + if (recalc) { + bx = o->w(); by = o->h(); br = 0; bt = 0; + numselected = 0; + for (Node *q=next; q && q->level>level; q=q->next) + if (q->selected && q->is_true_widget()) { + numselected++; + Widget_Node* myo = (Widget_Node*)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 (Fluid.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; + Node *selection = nullptr; // special power for the first selected widget + for (Node *q=next; q && q->level>level; q = q->next) + if (q->selected && q->is_true_widget()) { + if (!selection) selection = q; + Widget_Node* myo = (Widget_Node*)q; + int x,y,r,t; + newposition(myo,x,y,r,t); + if (Fluid.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(Type::Grid)) { + Fl_Grid_Proxy *grid = ((Fl_Grid_Proxy*)((Grid_Node*)q->parent)->o); + grid->draw_overlay(); + } + } + if (!Fluid.show_guides || !drag || numselected != 1) { + if (Flex_Node::parent_is_flex(q) && Flex_Node::is_fixed(q)) { + Fl_Flex *flex = ((Fl_Flex*)((Flex_Node*)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(Type::Grid)) { + Fl_Grid_Proxy *grid = ((Fl_Grid_Proxy*)((Grid_Node*)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 (Fluid.show_guides && (drag & (FD_DRAG|FD_TOP|FD_LEFT|FD_BOTTOM|FD_RIGHT))) { + fld::app::Snap_Data data = { dx, dy, sx, sy, sr, st, drag, 4, 4, dx, dy, (Widget_Node*)selection, this}; + fld::app::Snap_Action::draw_all(data); + } +} + +// Calculate new bounding box of selected widgets: +void Window_Node::fix_overlay() { + Fluid.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(Node *s) { + Widget_Node * prev_parent = nullptr; + if( !s || !s->selected || !s->is_widget()) return; + for (Node *i=s; i && i->parent; i=i->parent) { + if (i->is_a(Type::Group) && prev_parent) { + if (i->is_a(Type::Tabs)) { + ((Fl_Tabs*)((Widget_Node*)i)->o)->value(prev_parent->o); + return; + } + if (i->is_a(Type::Wizard)) { + ((Fl_Wizard*)((Widget_Node*)i)->o)->value(prev_parent->o); + return; + } + } + if (i->is_a(Type::Group) && s->is_widget()) + prev_parent = (Widget_Node*)i; + } +} + +// do that for every window (when selected set changes): +void redraw_overlays() { + for (Node *o=Fluid.proj.tree.first; o; o=o->next) + if (o->is_a(Type::Window)) ((Window_Node*)o)->fix_overlay(); +} + +void toggle_overlays(Fl_Widget *,void *) { + overlays_invisible = !overlays_invisible; + + if (overlays_invisible) { + Fluid.overlay_item->label("Show O&verlays"); + if (overlay_button) overlay_button->label("Show &Overlays"); + } else { + Fluid.overlay_item->label("Hide O&verlays"); + if (overlay_button) overlay_button->label("Hide &Overlays"); + } + + for (Node *o=Fluid.proj.tree.first; o; o=o->next) + if (o->is_a(Type::Window)) { + Widget_Node* w = (Widget_Node*)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 *) { + Fluid.show_guides = !Fluid.show_guides; + Fluid.preferences.set("Fluid.show_guides", Fluid.show_guides); + + if (Fluid.show_guides) + Fluid.guides_item->label("Hide Guides"); + else + Fluid.guides_item->label("Show Guides"); + if (guides_button) + guides_button->value(Fluid.show_guides); + + for (Node *o=Fluid.proj.tree.first; o; o=o->next) { + if (o->is_a(Type::Window)) { + Widget_Node* w = (Widget_Node*)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(nullptr, nullptr); +} + +/** + \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 *) { + Fluid.show_restricted = !Fluid.show_restricted; + Fluid.preferences.set("Fluid.show_restricted", Fluid.show_restricted); + + if (Fluid.show_restricted) + Fluid.restricted_item->label("Hide Restricted"); + else + Fluid.restricted_item->label("Show Restricted"); + if (restricted_button) + restricted_button->value(Fluid.show_restricted); + + for (Node *o=Fluid.proj.tree.first; o; o=o->next) { + if (o->is_a(Type::Window)) { + Widget_Node* w = (Widget_Node*)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 *) { + Fluid.show_ghosted_outline = !Fluid.show_ghosted_outline; + Fluid.preferences.set("Fluid.show_ghosted_outline", Fluid.show_ghosted_outline); + for (Node *o=Fluid.proj.tree.first; o; o=o->next) { + if (o->is_a(Type::Window)) { + Widget_Node* w = (Widget_Node*)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(nullptr, nullptr); +} + +extern void select(Node *,int); +extern void select_only(Node *); +extern void deselect(); +extern Node* in_this_only; +extern void fix_group_size(Node *t); + +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 Window_Node::moveallchildren(int key) +{ + bool update_widget_panel = false; + Fluid.proj.undo.checkpoint(); + Node *i; + for (i=next; i && i->level>level;) { + if (i->selected && i->is_true_widget()) { + Widget_Node* myo = (Widget_Node*)i; + int x,y,r,t,ow=myo->o->w(),oh=myo->o->h(); + newposition(myo,x,y,r,t); + if (myo->is_a(Type::Flex) || myo->is_a(Type::Grid)) { + // Flex and Grid need to be able to layout their children. + Fluid.proj.tree.allow_layout++; + myo->o->resize(x,y,r-x,t-y); + Fluid.proj.tree.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 (Flex_Node::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. + Flex_Node* ft = (Flex_Node*)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 + Fluid.proj.tree.allow_layout++; + f->layout(); + Fluid.proj.tree.allow_layout--; + } else if (myo->parent && myo->parent->is_a(Type::Grid)) { + Grid_Node* gt = (Grid_Node*)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); + } + } + Fluid.proj.tree.allow_layout++; + g->layout(); + Fluid.proj.tree.allow_layout--; + update_widget_panel = true; + } else if (myo->parent && myo->parent->is_a(Type::Group)) { + Group_Node* gt = (Group_Node*)myo->parent; + ((Fl_Group*)gt->o)->init_sizes(); + } + // move all the children, whether selected or not: + Node* p; + for (p = myo->next; p && p->level>myo->level; p = p->next) + if (p->is_true_widget() && !myo->is_a(Type::Flex) && !myo->is_a(Type::Grid)) { + Widget_Node* myo2 = (Widget_Node*)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(); + Fluid.proj.set_modflag(1); + dx = dy = 0; + + update_xywh(); + if (update_widget_panel && the_panel && the_panel->visible()) { + propagate_load(the_panel, LOAD); + } +} + +int Window_Node::popupx = 0x7FFFFFFF; // mark as invalid (MAXINT) +int Window_Node::popupy = 0x7FFFFFFF; + +int Window_Node::handle(int event) { + static Node* selection = nullptr; + 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 (Node* i=next; i && i->level>level; i=i->next) + if (i->is_a(Type::Group)) { + Widget_Node* myo = (Widget_Node*)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"); + { Node *prototype = typename_to_prototype(Fl::event_text()); + if (prototype==nullptr) { + // 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 == nullptr) || (*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); + Widget_Node *tgt = nullptr; + for (Node* i=next; i && i->level>level; i=i->next) { + if (i->is_widget()) { + Widget_Node* myo = (Widget_Node*)i; + if (Fl::event_inside(myo->o) && myo->o->visible_r()) + tgt = myo; + } + } + if (tgt) { + char rel[FL_PATH_MAX+1]; + Fluid.proj.enter_project_dir(); + fl_filename_relative(rel, FL_PATH_MAX, fn); + Fluid.proj.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 ( Fluid.proj.tree.current_dnd->group() + && selection && selection->group() + && Fluid.proj.tree.current_dnd->group()==selection->group()) + { + Node *cc = Fluid.proj.tree.current; + Fluid.proj.tree.current = Fluid.proj.tree.current_dnd; + add_new_widget_from_user(prototype, Strategy::AS_LAST_CHILD); + Fluid.proj.tree.current = cc; + } else { + add_new_widget_from_user(prototype, Strategy::AS_LAST_CHILD); + } + popupx = 0x7FFFFFFF; + popupy = 0x7FFFFFFF; // mark as invalid (MAXINT) + in_this_only = nullptr; + widget_browser->display(Fluid.proj.tree.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 = nullptr; + return 1; + } + // find the innermost item clicked on: + selection = this; + {for (Node* i=next; i && i->level>level; i=i->next) + if (i->is_true_widget()) { + Widget_Node* myo = (Widget_Node*)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: + {Node* 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(Type::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))) { + Widget_Node::open(); + } else { + if (mxlevel>level; i=i->next) + if (i->is_true_widget()) { + Widget_Node* myo = (Widget_Node*)i; + for (Fl_Widget *o1 = myo->o; o1; o1 = o1->parent()) + if (!o1->visible()) goto CONTINUE; + if (Fl::event_inside(myo->o)) selection = myo; + if (myo && myo->o && myo->o->x()>=x1 && myo->o->y()>y1 && + myo->o->x()+myo->o->w()o->y()+myo->o->h()selected : 1); + } + CONTINUE:; + } + // if nothing in box, select what was clicked on: + if (selection && !n) { + select(selection, toggle ? !selection->selected : 1); + } + } + drag = 0; + ((Overlay_Window *)o)->redraw_overlay(); + return 1; + + case FL_KEYBOARD: { + + int backtab = 0; + switch (Fl::event_key()) { + + case FL_Escape: + ((Fl_Window*)o)->hide(); + return 1; + + case FL_Tab: { + if (Fl::event_state(FL_SHIFT)) backtab = 1; + // find current child: + Node *i = Fluid.proj.tree.current; + while (i && !i->is_true_widget()) i = i->parent; + if (!i) return 0; + Node *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)) + fld::app::Snap_Action::get_resize_stepsize(x_step, y_step); + else + fld::app::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(nullptr, nullptr); + break; + + default: + return 0; + }} + + case FL_SHORTCUT: { + in_this_only = this; // modifies how some menu items work. + const Fl_Menu_Item* m = Fluid.main_menu->test_shortcut(); + if (m && m->callback()) m->do_callback(this->o); + in_this_only = nullptr; + return (m != nullptr);} + + 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 Window_Node::write_code1(fld::io::Code_Writer& f) { + Widget_Node::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 Window_Node::write_code2(fld::io::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 Window_Node::write_properties(fld::io::Project_Writer &f) { + Widget_Node::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 Window_Node::read_property(fld::io::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 (Fluid.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")) { + Widget_Node::read_property(f, c); + Fluid.pasteoffset = 0; // make it not apply to contents + } else { + Widget_Node::read_property(f, c); + } +} + +int Window_Node::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 Widget_Node::read_fdesign(propname,value); + } + return 1; +} + +/////////////////////////////////////////////////////////////////////// + +Widget_Class_Node Widget_Class_Node::prototype; + +Widget_Class_Node *current_widget_class = nullptr; + +/** + Create and add a new Widget Class node. + \param[in] strategy add after current or as last child + \return new node + */ +Node *Widget_Class_Node::make(Strategy strategy) { + Node *anchor = Fluid.proj.tree.current, *p = anchor; + if (p && (strategy.placement() == Strategy::AFTER_CURRENT)) p = p->parent; + while (p && (!p->is_decl_block() || (p->is_widget() && p->is_class()))) { + anchor = p; + strategy.placement(Strategy::AFTER_CURRENT); + p = p->parent; + } + Widget_Class_Node *myo = new Widget_Class_Node(); + myo->name("UserInterface"); + + if (!this->o) {// template widget + this->o = new Fl_Window(100,100); + Fl_Group::current(nullptr); + } + 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 Widget_Class_Node::write_properties(fld::io::Project_Writer &f) { + Window_Node::write_properties(f); + if (wc_relative==1) + f.write_string("position_relative"); + else if (wc_relative==2) + f.write_string("position_relative_rescale"); +} + +void Widget_Class_Node::read_property(fld::io::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 { + Window_Node::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 nullptr; + const char *nn; + while((nn = strstr(n, "::"))) { + n = nn + 2; + } + return(n); +} + + +void Widget_Class_Node::write_code1(fld::io::Code_Writer& f) { +#if 0 + Widget_Node::write_code1(fld::io::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 Widget_Class_Node::write_code2(fld::io::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 *Window_Node::enter_live_mode(int) { + Fl_Window *win = new Fl_Window(10, 10, o->w(), o->h()); + return propagate_live_mode(win); +} + +void Window_Node::leave_live_mode() { +} + +/** + copy all properties from the edit widget to the live widget + */ +void Window_Node::copy_properties() { + Fl_Window *self = static_cast(o); + Fl_Window *live = static_cast(live_widget); + if (self->resizable() == self) + live->resizable(live); + Widget_Node::copy_properties(); +} -- cgit v1.2.3