// // 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-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 "Fl_Window_Type.h" #include "Fl_Group_Type.h" #include "Fl_Grid_Type.h" #include "fluid.h" #include "widget_browser.h" #include "undo.h" #include "settings_panel.h" #include "file.h" #include "code.h" #include "widget_panel.h" #include "factory.h" #include "Fd_Snap_Action.h" #include #include #include #include #include #include #include #include #include #include "../src/flstring.h" #include #include #include extern Fl_Window *the_panel; extern void draw_width(int x, int y, int r, Fl_Align a); extern void draw_height(int x, int y, int b, Fl_Align a); extern Fl_Preferences fluid_prefs; // Update the XYWH values in the widget panel... static void update_xywh() { if (current_widget && current_widget->is_widget()) { Fl_Widget *o = ((Fl_Widget_Type *)current_widget)->o; widget_x_input->value(o->x()); widget_y_input->value(o->y()); widget_w_input->value(o->w()); widget_h_input->value(o->h()); if (Fl_Flex_Type::parent_is_flex(current_widget)) { widget_flex_size->value(Fl_Flex_Type::size(current_widget)); widget_flex_fixed->value(Fl_Flex_Type::is_fixed(current_widget)); } } } void i18n_type_cb(Fl_Choice *c, void *v) { if (v == LOAD) { c->value(g_project.i18n_type); } else { undo_checkpoint(); g_project.i18n_type = static_cast(c->value()); set_modflag(1); } switch (g_project.i18n_type) { case FD_I18N_NONE : /* None */ i18n_gnu_group->hide(); i18n_posix_group->hide(); break; case FD_I18N_GNU : /* GNU gettext */ i18n_gnu_group->show(); i18n_posix_group->hide(); break; case FD_I18N_POSIX : /* POSIX cat */ i18n_gnu_group->hide(); i18n_posix_group->show(); break; } // make sure that the outside labels are redrawn too. w_settings_i18n_tab->redraw(); } void show_grid_cb(Fl_Widget *, void *) { settings_window->show(); w_settings_tabs->value(w_settings_layout_tab); } void show_settings_cb(Fl_Widget *, void *) { settings_window->hotspot(settings_window); settings_window->show(); } //////////////////////////////////////////////////////////////// Fl_Menu_Item window_type_menu[] = { {"Single",0,0,(void*)FL_WINDOW}, {"Double",0,0,(void*)(FL_WINDOW+1)}, {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 kAddAsLastChild or kAddAfterCurrent \return new node */ Fl_Type *Fl_Window_Type::make(Strategy strategy) { Fl_Type *p = Fl_Type::current; while (p && (!p->is_code_block() || p->is_a(ID_Widget_Class))) 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(p, 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) { Fl_Widget* t = resizable(); resizable(0); // do not set the mod flag if the window was not resized. In FLUID, all // windows are opened without a given x/y position, so modifying x/y // should not mark the project as dirty if (W!=w() || H!=h()) set_modflag(1); Fl_Overlay_Window::resize(X,Y,W,H); resizable(t); update_xywh(); } // calculate actual move by moving mouse position (mx,my) to // nearest multiple of gridsize, and snap to original position void Fl_Window_Type::newdx() { int mydx, mydy; mydx = mx-x1; mydy = my-y1; if (!(drag & (FD_DRAG | FD_BOX | FD_LEFT | FD_RIGHT))) { mydx = 0; dx = 0; } if (!(drag & (FD_DRAG | FD_BOX | FD_TOP | FD_BOTTOM))) { mydy = 0; dy = 0; } if (show_guides && (drag & (FD_DRAG|FD_TOP|FD_LEFT|FD_BOTTOM|FD_RIGHT))) { Fl_Type *selection = 0L; // special power for the first selected widget for (Fl_Type *q=next; q && q->level>level; q = q->next) { if (q->selected && q->is_true_widget()) { selection = q; break; } } Fd_Snap_Data data = { mydx, mydy, bx, by, br, bt, drag, 4, 4, mydx, mydy, (Fl_Widget_Type*)selection, this }; Fd_Snap_Action::check_all(data); if (data.x_dist < 4) mydx = data.dx_out; if (data.y_dist < 4) mydy = data.dy_out; } if (dx != mydx || dy != mydy) { dx = mydx; dy = mydy; ((Overlay_Window *)o)->redraw_overlay(); } } // Move a widget according to dx and dy calculated above void Fl_Window_Type::newposition(Fl_Widget_Type *myo,int &X,int &Y,int &R,int &T) { X = myo->o->x(); Y = myo->o->y(); R = X+myo->o->w(); T = Y+myo->o->h(); if (!drag) return; if (drag&FD_DRAG) { X += dx; Y += dy; R += dx; T += dy; } else { if (drag&FD_LEFT) { if (X==bx) { X += dx; } else { if (Xbr+dx) R = br+dx; } } if (drag&FD_BOTTOM) { if (T==bt) { T += dy; } else { if (T>bt+dx) T = bt+dx; } } } if (R h) { for (; yp < h; yp+=size) fl_line(x, y+yp, x+yp, y); for (; yp < w; yp+=size) fl_line(x+yp-h, y+h, x+yp, y); for (; yp < w+h; yp+=size) fl_line(x+yp-h, y+h, x+w, y+yp-w); } else { for (; yp < w; yp+=size) fl_line(x, y+yp, x+yp, y); for (; yp < h; yp+=size) fl_line(x, y+yp, x+w, y+yp-w); for (; yp < h+w; yp+=size) fl_line(x+yp-h, y+h, x+w, y+yp-w); } } /** \brief Draw a hatch pattern over all children that overlap the bounds of this box. \param[in] group check all children of this group \param[in] x, y, w, h bounding box of this group */ void Fl_Window_Type::draw_out_of_bounds(Fl_Widget_Type *group, int x, int y, int w, int h) { for (Fl_Type *p = group->next; p && p->level>group->level; p = p->next) { if (p->level == group->level+1 && p->is_true_widget()) { Fl_Widget *o = ((Fl_Widget_Type*)p)->o; if (o->x() < x) fd_hatch(o->x(), o->y(), x-o->x(), o->h()); if (o->y() < y) fd_hatch(o->x(), o->y(), o->w(), y-o->y()); if (o->x()+o->w() > x+w) fd_hatch(x+w, o->y(), (o->x()+o->w())-(x+w), o->h()); if (o->y()+o->h() > y+h) fd_hatch(o->x(), y+h, o->w(), (o->y()+o->h())-(y+h)); } } } /** \brief Draw a hatch pattern for all groups that have out of bounds children. */ void Fl_Window_Type::draw_out_of_bounds() { // get every group in the hierarchy, then draw any overlap of a direct child with that group fl_color(FL_DARK_RED); draw_out_of_bounds(this, 0, 0, o->w(), o->h()); for (Fl_Type *q=next; q && q->level>level; q = q->next) { // don't do this for Fl_Scroll (which we currently can't handle in FLUID anyway) if (q->is_a(ID_Group) && !q->is_a(ID_Scroll)) { Fl_Widget_Type *w = (Fl_Widget_Type*)q; draw_out_of_bounds(w, w->o->x(), w->o->y(), w->o->w(), w->o->h()); } } fl_color(FL_RED); } /** \brief Compare all children in the same level and hatch overlapping areas. */ void Fl_Window_Type::draw_overlaps() { fl_color(FL_DARK_YELLOW); // loop through all widgets in this window for (Fl_Type *q=next; q && q->level>level; q = q->next) { // is it a valid widget if (q->is_true_widget()) { Fl_Widget_Type *w = (Fl_Widget_Type*)q; // is the widget visible if (w->o->visible()) { int x = w->o->x(), y = w->o->y(); int r = x + w->o->w(), b = y + w->o->h(); for (Fl_Type *p=q->next; p && p->level>=q->level; p = p->next) { if (p->level==q->level && p->is_true_widget()) { Fl_Widget_Type *wp = (Fl_Widget_Type*)p; if (wp->o->visible()) { int px = fd_max(x, wp->o->x()); int py = fd_max(y, wp->o->y()); int pr = fd_min(r, wp->o->x() + wp->o->w()); int pb = fd_min(b, wp->o->y() + wp->o->h()); if (pr > px && pb > py) fd_hatch(px, py, pr-px, pb-py); } } } } else { int l = q->level; for (; q && q->next && q->next->level>l; q = q->next) { } } } } fl_color(FL_RED); } void Fl_Window_Type::draw_overlay() { if (recalc) { bx = o->w(); by = o->h(); br = 0; bt = 0; numselected = 0; for (Fl_Type *q=next; q && q->level>level; q=q->next) if (q->selected && q->is_true_widget()) { numselected++; Fl_Widget_Type* myo = (Fl_Widget_Type*)q; if (myo->o->x() < bx) bx = myo->o->x(); if (myo->o->y() < by) by = myo->o->y(); if (myo->o->x()+myo->o->w() > br) br = myo->o->x()+myo->o->w(); if (myo->o->y()+myo->o->h() > bt) bt = myo->o->y()+myo->o->h(); } recalc = 0; sx = bx; sy = by; sr = br; st = bt; } fl_color(FL_RED); if (drag==FD_BOX && (x1 != mx || y1 != my)) { int x = x1; int r = mx; if (x > r) {x = mx; r = x1;} int y = y1; int b = my; if (y > b) {y = my; b = y1;} fl_rect(x,y,r-x,b-y); } if (overlays_invisible && !drag) return; if (show_restricted) { draw_out_of_bounds(); draw_overlaps(); // TODO: for Fl_Tile, find all areas that are not covered by visible children } if (selected) fl_rect(0,0,o->w(),o->h()); if (!numselected) return; int mybx,myby,mybr,mybt; int mysx,mysy,mysr,myst; mybx = mysx = o->w(); myby = mysy = o->h(); mybr = mysr = 0; mybt = myst = 0; Fl_Type *selection = 0L; // special power for the first selected widget for (Fl_Type *q=next; q && q->level>level; q = q->next) if (q->selected && q->is_true_widget()) { if (!selection) selection = q; Fl_Widget_Type* myo = (Fl_Widget_Type*)q; int x,y,r,t; newposition(myo,x,y,r,t); if (show_guides) { // If we are in a drag operation, and the parent is a grid, show the grid overlay if (drag && q->parent && q->parent->is_a(ID_Grid)) { Fl_Grid_Proxy *grid = ((Fl_Grid_Proxy*)((Fl_Grid_Type*)q->parent)->o); grid->draw_overlay(); } } if (!show_guides || !drag || numselected != 1) { if (Fl_Flex_Type::parent_is_flex(q) && Fl_Flex_Type::is_fixed(q)) { Fl_Flex *flex = ((Fl_Flex*)((Fl_Flex_Type*)q->parent)->o); Fl_Widget *wgt = myo->o; if (flex->horizontal()) { draw_width(wgt->x(), wgt->y()+15, wgt->x()+wgt->w(), FL_ALIGN_CENTER); } else { draw_height(wgt->x()+15, wgt->y(), wgt->y()+wgt->h(), FL_ALIGN_CENTER); } } else if (q->is_a(ID_Grid)) { Fl_Grid_Proxy *grid = ((Fl_Grid_Proxy*)((Fl_Grid_Type*)q)->o); grid->draw_overlay(); } fl_rect(x,y,r-x,t-y); } if (x < mysx) mysx = x; if (y < mysy) mysy = y; if (r > mysr) mysr = r; if (t > myst) myst = t; if (!(myo->o->align() & FL_ALIGN_INSIDE)) { // Adjust left/right/top/bottom for top/bottom labels... int ww, hh; ww = (myo->o->align() & FL_ALIGN_WRAP) ? myo->o->w() : 0; hh = myo->o->labelsize(); myo->o->measure_label(ww, hh); if (myo->o->align() & FL_ALIGN_TOP) y -= hh; else if (myo->o->align() & FL_ALIGN_BOTTOM) t += hh; else if (myo->o->align() & FL_ALIGN_LEFT) x -= ww + 4; else if (myo->o->align() & FL_ALIGN_RIGHT) r += ww + 4; } if (x < mybx) mybx = x; if (y < myby) myby = y; if (r > mybr) mybr = r; if (t > mybt) mybt = t; } if (selected) return; // align the snapping selection box with the box we draw. sx = mysx; sy = mysy; sr = mysr; st = myst; // Draw selection box + resize handles... // draw box including all labels fl_focus_rect(mybx,myby,mybr-mybx,mybt-myby); // issue #816 // draw box excluding labels fl_rect(mysx,mysy,mysr-mysx,myst-mysy); fl_rectf(mysx,mysy,5,5); fl_rectf(mysr-5,mysy,5,5); fl_rectf(mysr-5,myst-5,5,5); fl_rectf(mysx,myst-5,5,5); if (show_guides && (drag & (FD_DRAG|FD_TOP|FD_LEFT|FD_BOTTOM|FD_RIGHT))) { Fd_Snap_Data data = { dx, dy, sx, sy, sr, st, drag, 4, 4, dx, dy, (Fl_Widget_Type*)selection, this}; Fd_Snap_Action::draw_all(data); } } extern Fl_Menu_Item Main_Menu[]; // Calculate new bounding box of selected widgets: void Fl_Window_Type::fix_overlay() { overlay_item->label("Hide O&verlays"); if (overlay_button) overlay_button->label("Hide &Overlays"); overlays_invisible = 0; recalc = 1; ((Overlay_Window *)(this->o))->redraw_overlay(); } // check if we must redraw any parent of tabs/wizard type void check_redraw_corresponding_parent(Fl_Type *s) { Fl_Widget_Type * prev_parent = 0; if( !s || !s->selected || !s->is_widget()) return; for (Fl_Type *i=s; i && i->parent; i=i->parent) { if (i->is_a(ID_Group) && prev_parent) { if (i->is_a(ID_Tabs)) { ((Fl_Tabs*)((Fl_Widget_Type*)i)->o)->value(prev_parent->o); return; } if (i->is_a(ID_Wizard)) { ((Fl_Wizard*)((Fl_Widget_Type*)i)->o)->value(prev_parent->o); return; } } if (i->is_a(ID_Group) && s->is_widget()) prev_parent = (Fl_Widget_Type*)i; } } // do that for every window (when selected set changes): void redraw_overlays() { for (Fl_Type *o=Fl_Type::first; o; o=o->next) if (o->is_a(ID_Window)) ((Fl_Window_Type*)o)->fix_overlay(); } void toggle_overlays(Fl_Widget *,void *) { overlays_invisible = !overlays_invisible; if (overlays_invisible) { overlay_item->label("Show O&verlays"); if (overlay_button) overlay_button->label("Show &Overlays"); } else { overlay_item->label("Hide O&verlays"); if (overlay_button) overlay_button->label("Hide &Overlays"); } for (Fl_Type *o=Fl_Type::first; o; o=o->next) if (o->is_a(ID_Window)) { Fl_Widget_Type* w = (Fl_Widget_Type*)o; ((Overlay_Window*)(w->o))->redraw_overlay(); } } /** \brief User changes settings to show positioning guides in layout editor overlay. This is called from the main menu and from the check button in the Settings dialog. */ void toggle_guides(Fl_Widget *,void *) { show_guides = !show_guides; fluid_prefs.set("show_guides", show_guides); if (show_guides) guides_item->label("Hide Guides"); else guides_item->label("Show Guides"); if (guides_button) guides_button->value(show_guides); for (Fl_Type *o=Fl_Type::first; o; o=o->next) { if (o->is_a(ID_Window)) { Fl_Widget_Type* w = (Fl_Widget_Type*)o; ((Overlay_Window*)(w->o))->redraw_overlay(); } } } /** \brief User changes settings to show positioning guides in layout editor overlay. This is called from the check button in the Settings dialog. */ void toggle_guides_cb(Fl_Check_Button *o, void *v) { toggle_guides(NULL, NULL); } /** \brief User changes settings to show overlapping and out of bounds widgets. This is called from the main menu and from the check button in the Settings dialog. */ void toggle_restricted(Fl_Widget *,void *) { show_restricted = !show_restricted; fluid_prefs.set("show_restricted", show_restricted); if (show_restricted) restricted_item->label("Hide Restricted"); else restricted_item->label("Show Restricted"); if (restricted_button) restricted_button->value(show_restricted); for (Fl_Type *o=Fl_Type::first; o; o=o->next) { if (o->is_a(ID_Window)) { Fl_Widget_Type* w = (Fl_Widget_Type*)o; ((Overlay_Window*)(w->o))->redraw_overlay(); } } } /** \brief User changes settings to show low contrast groups with a ghosted outline. */ void toggle_ghosted_outline_cb(Fl_Check_Button *,void *) { show_ghosted_outline = !show_ghosted_outline; fluid_prefs.set("show_ghosted_outline", show_ghosted_outline); for (Fl_Type *o=Fl_Type::first; o; o=o->next) { if (o->is_a(ID_Window)) { Fl_Widget_Type* w = (Fl_Widget_Type*)o; ((Overlay_Window*)(w->o))->redraw(); } } } /** \brief User changes settings to show overlapping and out of bounds widgets. This is called from the check button in the Settings dialog. */ void toggle_restricted_cb(Fl_Check_Button *o, void *v) { toggle_restricted(NULL, NULL); } extern void select(Fl_Type *,int); extern void select_only(Fl_Type *); extern void deselect(); extern Fl_Type* in_this_only; extern void fix_group_size(Fl_Type *t); extern Fl_Menu_Item Main_Menu[]; extern Fl_Menu_Item New_Menu[]; /** Move the selected children according to current dx, dy, drag state. This is somewhat of a do-all function that received many additions when new widget types were added. In the default case, moving a group will simply move all descendants with it. When resizing, children are resized to fit within the group. This is not ideal for widgets that are moved or resized within a group that manages the layout of its children. We must create a more universal way to modify move events per widget type. \param[in] key if key is not 0, it contains the code of the keypress that caused this call. This must only be set when handle FL_KEYBOARD events. */ void Fl_Window_Type::moveallchildren(int key) { bool update_widget_panel = false; undo_checkpoint(); Fl_Type *i; for (i=next; i && i->level>level;) { if (i->selected && i->is_true_widget()) { Fl_Widget_Type* myo = (Fl_Widget_Type*)i; int x,y,r,t,ow=myo->o->w(),oh=myo->o->h(); newposition(myo,x,y,r,t); if (myo->is_a(ID_Flex) || myo->is_a(ID_Grid)) { // Flex and Grid need to be able to layout their children. allow_layout++; myo->o->resize(x,y,r-x,t-y); allow_layout--; } else { // Other groups are resized without affecting their children, however // they move their children if the entire widget is moved. myo->o->resize(x,y,r-x,t-y); } if (Fl_Flex_Type::parent_is_flex(myo)) { // If the border of a Flex child is move, give that child a fixed size // so that the user request is reflected. Fl_Flex_Type* ft = (Fl_Flex_Type*)myo->parent; Fl_Flex* f = (Fl_Flex*)ft->o; if (key) { ft->keyboard_move_child(myo, key); } else if (drag & FD_DRAG) { ft->insert_child_at(myo->o, Fl::event_x(), Fl::event_y()); } else { if (f->horizontal()) { if (myo->o->w()!=ow) { f->fixed(myo->o, myo->o->w()); f->layout(); } } else { if (myo->o->h()!=oh) { f->fixed(myo->o, myo->o->h()); f->layout(); } } } // relayout the Flex parent allow_layout++; f->layout(); allow_layout--; } else if (myo->parent && myo->parent->is_a(ID_Grid)) { Fl_Grid_Type* gt = (Fl_Grid_Type*)myo->parent; Fl_Grid* g = (Fl_Grid*)gt->o; if (key) { gt->keyboard_move_child(myo, key); } else { if (drag & FD_DRAG) { gt->insert_child_at(myo->o, Fl::event_x(), Fl::event_y()); } else { gt->child_resized(myo); } } allow_layout++; g->layout(); allow_layout--; update_widget_panel = true; } else if (myo->parent && myo->parent->is_a(ID_Group)) { Fl_Group_Type* gt = (Fl_Group_Type*)myo->parent; ((Fl_Group*)gt->o)->init_sizes(); } // move all the children, whether selected or not: Fl_Type* p; for (p = myo->next; p && p->level>myo->level; p = p->next) if (p->is_true_widget() && !myo->is_a(ID_Flex) && !myo->is_a(ID_Grid)) { Fl_Widget_Type* myo2 = (Fl_Widget_Type*)p; int X,Y,R,T; newposition(myo2,X,Y,R,T); myo2->o->resize(X,Y,R-X,T-Y); } i = p; } else { i = i->next; } } for (i=next; i && i->level>level; i=i->next) fix_group_size(i); o->redraw(); recalc = 1; ((Overlay_Window *)(this->o))->redraw_overlay(); set_modflag(1); dx = dy = 0; update_xywh(); if (update_widget_panel && the_panel && the_panel->visible()) { propagate_load(the_panel, LOAD); } } int Fl_Window_Type::popupx = 0x7FFFFFFF; // mark as invalid (MAXINT) int Fl_Window_Type::popupy = 0x7FFFFFFF; int Fl_Window_Type::handle(int event) { static Fl_Type* selection = NULL; switch (event) { case FL_DND_ENTER: // printf("DND enter\n"); case FL_DND_DRAG: // printf("DND drag\n"); { // find the innermost item clicked on: selection = this; for (Fl_Type* i=next; i && i->level>level; i=i->next) if (i->is_a(ID_Group)) { Fl_Widget_Type* myo = (Fl_Widget_Type*)i; if (Fl::event_inside(myo->o) && myo->o->visible_r()) { selection = myo; if (Fl::event_clicks()==1) reveal_in_browser(myo); } } if (selection && !selection->selected) { select_only(selection); ((Overlay_Window *)o)->redraw_overlay(); } } Fl::belowmouse(o); return 1; case FL_DND_RELEASE: // printf("DND release\n"); Fl::belowmouse(o); return 1; case FL_PASTE: // printf("DND paste\n"); { Fl_Type *prototype = typename_to_prototype(Fl::event_text()); if (prototype==NULL) { // it's not a FLUID type, so it could be the filename of an image const char *cfn = Fl::event_text(); // printf("DND is filename %s?\n", cfn); if ((cfn == NULL) || (*cfn == 0)) return 0; if (strlen(cfn) >= FL_PATH_MAX) return 0; char fn[FL_PATH_MAX+1]; // some platform prepend "file://" or "computer://" or similar text const char *sep = strstr(cfn, "://"); if (sep) strcpy(fn, sep+3); else strcpy(fn, cfn); // remove possibly trailing \r\n int n = (int)strlen(fn)-1; if (fn[n] == '\n') fn[n--] = 0; if (fn[n] == '\r') fn[n--] = 0; // on X11 and Wayland (?), filenames need to be decoded #if (defined(FLTK_USE_X11) || defined(FLTK_USE_WAYLAND)) fl_decode_uri(fn); #endif // does a file by that name actually exist? if (fl_access(fn, 4)==-1) return 0; // but is this an image file? Fl_Image *img = Fl_Shared_Image::get(fn); if (!img || (img->ld() < 0)) return 0; // ok, so it is an image - now add it as image() or deimage() to the widget // printf("DND check for target %s\n", fn); Fl_Widget_Type *tgt = NULL; for (Fl_Type* i=next; i && i->level>level; i=i->next) { if (i->is_widget()) { Fl_Widget_Type* myo = (Fl_Widget_Type*)i; if (Fl::event_inside(myo->o) && myo->o->visible_r()) tgt = myo; } } if (tgt) { char rel[FL_PATH_MAX+1]; enter_project_dir(); fl_filename_relative(rel, FL_PATH_MAX, fn); leave_project_dir(); // printf("DND image = %s\n", fn); if (Fl::get_key(FL_Alt_L) || Fl::get_key(FL_Alt_R)) { //if (Fl::event_alt()) { // TODO: X11/Wayland does not set the e_state on DND events tgt->inactive_name(rel); tgt->compress_deimage_ = 1; tgt->bind_deimage_ = 0; } else { tgt->image_name(rel); tgt->compress_image_ = 1; tgt->bind_image_ = 0; } select_only(tgt); tgt->open(); } return 1; } in_this_only = this; popupx = Fl::event_x(); popupy = Fl::event_y(); // If the selected widget at dnd start and the drop target are the same, // or in the same group, add after selection. Otherwise, just add // at the end of the selected group. if ( Fl_Type::current_dnd->group() && selection && selection->group() && Fl_Type::current_dnd->group()==selection->group()) { Fl_Type *cc = Fl_Type::current; Fl_Type::current = Fl_Type::current_dnd; add_new_widget_from_user(prototype, kAddAsLastChild); Fl_Type::current = cc; } else { add_new_widget_from_user(prototype, kAddAsLastChild); } popupx = 0x7FFFFFFF; popupy = 0x7FFFFFFF; // mark as invalid (MAXINT) in_this_only = NULL; widget_browser->display(Fl_Type::current); widget_browser->rebuild(); return 1; } case FL_PUSH: x1 = mx = Fl::event_x(); y1 = my = Fl::event_y(); drag = dx = dy = 0; // test for popup menu: if (Fl::event_button() >= 3) { in_this_only = this; // modifies how some menu items work. static const Fl_Menu_Item* myprev; popupx = mx; popupy = my; const Fl_Menu_Item* m = New_Menu->popup(mx,my,"New",myprev); if (m && m->callback()) {myprev = m; m->do_callback(this->o);} popupx = 0x7FFFFFFF; popupy = 0x7FFFFFFF; // mark as invalid (MAXINT) in_this_only = 0; return 1; } // find the innermost item clicked on: selection = this; {for (Fl_Type* i=next; i && i->level>level; i=i->next) if (i->is_true_widget()) { Fl_Widget_Type* myo = (Fl_Widget_Type*)i; for (Fl_Widget *o1 = myo->o; o1; o1 = o1->parent()) if (!o1->visible()) goto CONTINUE2; if (Fl::event_inside(myo->o)) { selection = myo; if (Fl::event_clicks()==1) reveal_in_browser(myo); } CONTINUE2:; }} // see if user grabs edges of selected region: if (numselected && !(Fl::event_state(FL_SHIFT)) && mx<=br+2 && mx>=bx-2 && my<=bt+2 && my>=by-2) { if (mx >= br-5) drag |= FD_RIGHT; else if (mx <= bx+5) drag |= FD_LEFT; if (my >= bt-5) drag |= FD_BOTTOM; else if (my <= by+5) drag |= FD_TOP; if (!drag) drag = FD_DRAG; } // do object-specific selection of other objects: {Fl_Type* t = selection->click_test(mx, my); if (t) { //if (t == selection) return 1; // indicates mouse eaten w/o change if (Fl::event_state(FL_SHIFT)) { Fl::event_is_click(0); select(t, !t->selected); } else { deselect(); select(t, 1); if (t->is_a(ID_Menu_Item)) t->open(); } selection = t; drag = 0; } else { if (!drag) drag = FD_BOX; // if all else fails, start a new selection region }} return 1; case FL_DRAG: if (!drag) return 0; mx = Fl::event_x(); my = Fl::event_y(); newdx(); return 1; case FL_RELEASE: if (!drag) return 0; mx = Fl::event_x(); my = Fl::event_y(); if (drag != FD_BOX && (dx || dy || !Fl::event_is_click())) { if (dx || dy) moveallchildren(); } else if ((Fl::event_clicks() || Fl::event_state(FL_CTRL))) { Fl_Widget_Type::open(); } else { if (mxlevel>level; i=i->next) if (i->is_true_widget()) { Fl_Widget_Type* myo = (Fl_Widget_Type*)i; for (Fl_Widget *o1 = myo->o; o1; o1 = o1->parent()) if (!o1->visible()) goto CONTINUE; if (Fl::event_inside(myo->o)) selection = myo; if (myo && myo->o && myo->o->x()>=x1 && myo->o->y()>y1 && myo->o->x()+myo->o->w()o->y()+myo->o->h()selected : 1); } CONTINUE:; } // if nothing in box, select what was clicked on: if (selection && !n) { select(selection, toggle ? !selection->selected : 1); } } drag = 0; ((Overlay_Window *)o)->redraw_overlay(); return 1; case FL_KEYBOARD: { int backtab = 0; switch (Fl::event_key()) { case FL_Escape: ((Fl_Window*)o)->hide(); return 1; case FL_Tab: { if (Fl::event_state(FL_SHIFT)) backtab = 1; // find current child: Fl_Type *i = Fl_Type::current; while (i && !i->is_true_widget()) i = i->parent; if (!i) return 0; Fl_Type *p = i->parent; while (p && p != this) p = p->parent; if (!p || !p->is_widget()) { i = next; if (!i || i->level <= level) return 0; } p = i; for (;;) { i = backtab ? i->prev : i->next; if (!i || i->level <= level) {i = p; break;} if (i->is_true_widget()) break; } deselect(); select(i,1); return 1;} case FL_Left: dx = -1; dy = 0; goto ARROW; case FL_Right: dx = +1; dy = 0; goto ARROW; case FL_Up: dx = 0; dy = -1; goto ARROW; case FL_Down: dx = 0; dy = +1; goto ARROW; ARROW: drag = (Fl::event_state(FL_SHIFT)) ? (FD_RIGHT|FD_BOTTOM) : FD_DRAG; if (Fl::event_state(FL_COMMAND)) { int x_step, y_step; if (drag & (FD_RIGHT|FD_BOTTOM)) Fd_Snap_Action::get_resize_stepsize(x_step, y_step); else Fd_Snap_Action::get_move_stepsize(x_step, y_step); dx *= x_step; dy *= y_step; } moveallchildren(Fl::event_key()); drag = 0; return 1; case 'o': toggle_overlays(0, 0); break; default: return 0; }} case FL_SHORTCUT: { in_this_only = this; // modifies how some menu items work. const Fl_Menu_Item* m = Main_Menu->test_shortcut(); if (m && m->callback()) m->do_callback(this->o); in_this_only = 0; return (m != 0);} default: return 0; } } //////////////////////////////////////////////////////////////// /** Write the C++ code that comes before the children of the window are written. \param f the source code output stream */ void Fl_Window_Type::write_code1(Fd_Code_Writer& f) { Fl_Widget_Type::write_code1(f); } /** Write the C++ code that comes after the children of the window are written. \param f the source code output stream */ void Fl_Window_Type::write_code2(Fd_Code_Writer& f) { const char *var = is_class() ? "this" : name() ? name() : "o"; // make the window modal or non-modal if (modal) { f.write_c("%s%s->set_modal();\n", f.indent(), var); } else if (non_modal) { f.write_c("%s%s->set_non_modal();\n", f.indent(), var); } // clear the window border if (!((Fl_Window*)o)->border()) { f.write_c("%s%s->clear_border();\n", f.indent(), var); } // set the xclass of the window if (xclass) { f.write_c("%s%s->xclass(", f.indent(), var); f.write_cstring(xclass); f.write_c(");\n"); } // make the window resizable if (((Fl_Window*)o)->resizable() == o) f.write_c("%s%s->resizable(%s);\n", f.indent(), var, var); // set the size range last if (sr_max_w || sr_max_h) { f.write_c("%s%s->size_range(%d, %d, %d, %d);\n", f.indent(), var, sr_min_w, sr_min_h, sr_max_w, sr_max_h); } else if (sr_min_w || sr_min_h) { f.write_c("%s%s->size_range(%d, %d);\n", f.indent(), var, sr_min_w, sr_min_h); } // insert extra code from user, may call `show()` write_extra_code(f); // stop adding widgets to this window f.write_c("%s%s->end();\n", f.indent(), var); write_block_close(f); } void Fl_Window_Type::write_properties(Fd_Project_Writer &f) { Fl_Widget_Type::write_properties(f); if (modal) f.write_string("modal"); else if (non_modal) f.write_string("non_modal"); if (!((Fl_Window*)o)->border()) f.write_string("noborder"); if (xclass) {f.write_string("xclass"); f.write_word(xclass);} if (sr_min_w || sr_min_h || sr_max_w || sr_max_h) f.write_string("size_range {%d %d %d %d}", sr_min_w, sr_min_h, sr_max_w, sr_max_h); if (o->visible() || override_visible_) f.write_string("visible"); } void Fl_Window_Type::read_property(Fd_Project_Reader &f, const char *c) { if (!strcmp(c,"modal")) { modal = 1; } else if (!strcmp(c,"non_modal")) { non_modal = 1; } else if (!strcmp(c, "visible")) { if (batch_mode) // don't actually open any windows in batch mode override_visible_ = 1; else // in interactive mode, we simply show the window open_(); } else if (!strcmp(c,"noborder")) { ((Fl_Window*)o)->border(0); } else if (!strcmp(c,"xclass")) { storestring(f.read_word(),xclass); ((Fl_Window*)o)->xclass(xclass); } else if (!strcmp(c,"size_range")) { int mw, mh, MW, MH; if (sscanf(f.read_word(),"%d %d %d %d",&mw,&mh,&MW,&MH) == 4) { sr_min_w = mw; sr_min_h = mh; sr_max_w = MW; sr_max_h = MH; } } else if (!strcmp(c,"xywh")) { Fl_Widget_Type::read_property(f, c); pasteoffset = 0; // make it not apply to contents } else { Fl_Widget_Type::read_property(f, c); } } int Fl_Window_Type::read_fdesign(const char* propname, const char* value) { int x; o->box(FL_NO_BOX); // because fdesign always puts an Fl_Box next if (!strcmp(propname,"Width")) { if (sscanf(value,"%d",&x) == 1) o->size(x,o->h()); } else if (!strcmp(propname,"Height")) { if (sscanf(value,"%d",&x) == 1) o->size(o->w(),x); } else if (!strcmp(propname,"NumberofWidgets")) { return 1; // we can figure out count from file } else if (!strcmp(propname,"border")) { if (sscanf(value,"%d",&x) == 1) ((Fl_Window*)o)->border(x); } else if (!strcmp(propname,"title")) { label(value); } else { return Fl_Widget_Type::read_fdesign(propname,value); } return 1; } /////////////////////////////////////////////////////////////////////// Fl_Widget_Class_Type Fl_Widget_Class_type; Fl_Widget_Class_Type *current_widget_class = 0; /** Create and add a new Widget Class node. \param[in] strategy add after current or as last child \return new node */ Fl_Type *Fl_Widget_Class_Type::make(Strategy strategy) { Fl_Type *p = Fl_Type::current; while (p && (!p->is_decl_block() || (p->is_widget() && p->is_class()))) 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(p, 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_Widget_Type::copy_properties(); /// \todo copy resizing constraints over }