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/app/Fd_Snap_Action.cxx | 1817 ----------------------------------- fluid/app/Fd_Snap_Action.h | 193 ---- fluid/app/Fluid_Image.cxx | 413 -------- fluid/app/Fluid_Image.h | 60 -- fluid/app/Image_Asset.cxx | 532 +++++++++++ fluid/app/Image_Asset.h | 63 ++ fluid/app/Menu.cxx | 190 ++++ fluid/app/Menu.h | 29 + fluid/app/Snap_Action.cxx | 1835 +++++++++++++++++++++++++++++++++++ fluid/app/Snap_Action.h | 207 ++++ fluid/app/align_widget.cxx | 414 -------- fluid/app/align_widget.h | 24 - fluid/app/args.cxx | 136 +++ fluid/app/args.h | 55 ++ fluid/app/fluid.cxx | 2169 ------------------------------------------ fluid/app/fluid.h | 134 --- fluid/app/history.cxx | 115 +++ fluid/app/history.h | 45 + fluid/app/mergeback.cxx | 493 ---------- fluid/app/mergeback.h | 81 -- fluid/app/project.cxx | 192 ---- fluid/app/project.h | 103 -- fluid/app/shell_command.cxx | 197 ++-- fluid/app/shell_command.h | 46 +- fluid/app/templates.cxx | 143 +++ fluid/app/templates.h | 29 + fluid/app/undo.cxx | 265 ------ fluid/app/undo.h | 37 - 28 files changed, 3482 insertions(+), 6535 deletions(-) delete mode 100644 fluid/app/Fd_Snap_Action.cxx delete mode 100644 fluid/app/Fd_Snap_Action.h delete mode 100644 fluid/app/Fluid_Image.cxx delete mode 100644 fluid/app/Fluid_Image.h create mode 100644 fluid/app/Image_Asset.cxx create mode 100644 fluid/app/Image_Asset.h create mode 100644 fluid/app/Menu.cxx create mode 100644 fluid/app/Menu.h create mode 100644 fluid/app/Snap_Action.cxx create mode 100644 fluid/app/Snap_Action.h delete mode 100644 fluid/app/align_widget.cxx delete mode 100644 fluid/app/align_widget.h create mode 100644 fluid/app/args.cxx create mode 100644 fluid/app/args.h delete mode 100644 fluid/app/fluid.cxx delete mode 100644 fluid/app/fluid.h create mode 100644 fluid/app/history.cxx create mode 100644 fluid/app/history.h delete mode 100644 fluid/app/mergeback.cxx delete mode 100644 fluid/app/mergeback.h delete mode 100644 fluid/app/project.cxx delete mode 100644 fluid/app/project.h create mode 100644 fluid/app/templates.cxx create mode 100644 fluid/app/templates.h delete mode 100644 fluid/app/undo.cxx delete mode 100644 fluid/app/undo.h (limited to 'fluid/app') diff --git a/fluid/app/Fd_Snap_Action.cxx b/fluid/app/Fd_Snap_Action.cxx deleted file mode 100644 index 0a407ac8d..000000000 --- a/fluid/app/Fd_Snap_Action.cxx +++ /dev/null @@ -1,1817 +0,0 @@ -// -// Snap action code file for the Fast Light Tool Kit (FLTK). -// -// Copyright 2023 by Bill Spitzak and others. -// -// This library is free software. Distribution and use rights are outlined in -// the file "COPYING" which should have been included with this file. If this -// file is missing or damaged, see the license at: -// -// https://www.fltk.org/COPYING.php -// -// Please see the following page on how to report bugs and issues: -// -// https://www.fltk.org/bugs.php -// - -#include "app/Fd_Snap_Action.h" - -#include "io/Project_Reader.h" -#include "io/Project_Writer.h" -#include "nodes/Fl_Group_Type.h" -#include "panels/settings_panel.h" - -#include -#include -#include -#include -#include -#include - -// TODO: warning if the user wants to change builtin layouts -// TODO: move panel to global settings panel (move load & save to main pulldown, or to toolbox?) -// INFO: how about a small tool box for quick preset selection and disabling of individual snaps? - -void select_layout_suite_cb(Fl_Widget *, void *user_data); - -int Fd_Snap_Action::eex = 0; -int Fd_Snap_Action::eey = 0; - -static Fd_Layout_Preset fltk_app = { - 15, 15, 15, 15, 0, 0, // window: l, r, t, b, gx, gy - 10, 10, 10, 10, 0, 0, // group: l, r, t, b, gx, gy - 25, 25, // tabs: t, b - 20, 10, 4, // widget_x: min, inc, gap - 20, 4, 8, // widget_y: min, inc, gap - 0, 14, -1, 14 // labelfont/size, textfont/size -}; -static Fd_Layout_Preset fltk_dlg = { - 10, 10, 10, 10, 0, 0, // window: l, r, t, b, gx, gy - 10, 10, 10, 10, 0, 0, // group: l, r, t, b, gx, gy - 20, 20, // tabs: t, b - 20, 10, 5, // widget_x: min, inc, gap - 20, 5, 5, // widget_y: min, inc, gap - 0, 11, -1, 11 // labelfont/size, textfont/size -}; -static Fd_Layout_Preset fltk_tool = { - 10, 10, 10, 10, 0, 0, // window: l, r, t, b, gx, gy - 10, 10, 10, 10, 0, 0, // group: l, r, t, b, gx, gy - 18, 18, // tabs: t, b - 16, 8, 2, // widget_x: min, inc, gap - 16, 4, 2, // widget_y: min, inc, gap - 0, 10, -1, 10 // labelfont/size, textfont/size -}; - -static Fd_Layout_Preset grid_app = { - 12, 12, 12, 12, 12, 12, // window: l, r, t, b, gx, gy - 12, 12, 12, 12, 12, 12, // group: l, r, t, b, gx, gy - 24, 24, // tabs: t, b - 12, 6, 6, // widget_x: min, inc, gap - 12, 6, 6, // widget_y: min, inc, gap - 0, 14, -1, 14 // labelfont/size, textfont/size -}; - -static Fd_Layout_Preset grid_dlg = { - 10, 10, 10, 10, 10, 10, // window: l, r, t, b, gx, gy - 10, 10, 10, 10, 10, 10, // group: l, r, t, b, gx, gy - 20, 20, // tabs: t, b - 10, 5, 5, // widget_x: min, inc, gap - 10, 5, 5, // widget_y: min, inc, gap - 0, 12, -1, 12 // labelfont/size, textfont/size -}; - -static Fd_Layout_Preset grid_tool = { - 8, 8, 8, 8, 8, 8, // window: l, r, t, b, gx, gy - 8, 8, 8, 8, 8, 8, // group: l, r, t, b, gx, gy - 16, 16, // tabs: t, b - 8, 4, 4, // widget_x: min, inc, gap - 8, 4, 4, // widget_y: min, inc, gap - 0, 10, -1, 10 // labelfont/size, textfont/size -}; - -static Fd_Layout_Suite static_suite_list[] = { - { (char*)"FLTK", (char*)"@fd_beaker FLTK", { &fltk_app, &fltk_dlg, &fltk_tool }, FD_STORE_INTERNAL }, - { (char*)"Grid", (char*)"@fd_beaker Grid", { &grid_app, &grid_dlg, &grid_tool }, FD_STORE_INTERNAL } -}; - -Fl_Menu_Item main_layout_submenu_[] = { - { static_suite_list[0].menu_label, 0, select_layout_suite_cb, (void*)0, FL_MENU_RADIO|FL_MENU_VALUE }, - { static_suite_list[1].menu_label, 0, select_layout_suite_cb, (void*)1, FL_MENU_RADIO }, - { NULL } -}; - -static Fl_Menu_Item static_choice_menu[] = { - { static_suite_list[0].menu_label }, - { static_suite_list[1].menu_label }, - { NULL } -}; - -Fd_Layout_Preset *layout = &fltk_app; -Fd_Layout_List g_layout_list; - -// ---- Callbacks ------------------------------------------------------ MARK: - - -void layout_suite_marker(Fl_Widget *, void *) { - // intentionally left empty -} - -void select_layout_suite_cb(Fl_Widget *, void *user_data) { - int index = (int)(fl_intptr_t)user_data; - assert(index >= 0); - assert(index < g_layout_list.list_size_); - g_layout_list.current_suite(index); - g_layout_list.update_dialogs(); -} - -void select_layout_preset_cb(Fl_Widget *, void *user_data) { - int index = (int)(fl_intptr_t)user_data; - assert(index >= 0); - assert(index < 3); - g_layout_list.current_preset(index); - g_layout_list.update_dialogs(); -} - -void edit_layout_preset_cb(Fl_Button *w, long user_data) { - int index = (int)w->argument(); - assert(index >= 0); - assert(index < 3); - if (user_data == (long)(fl_intptr_t)LOAD) { - w->value(g_layout_list.current_preset() == index); - } else { - g_layout_list.current_preset(index); - g_layout_list.update_dialogs(); - } -} - -// ---- Fd_Layout_Suite ------------------------------------------------ MARK: - - -/** - Write presets to a Preferences database. - */ -void Fd_Layout_Preset::write(Fl_Preferences &prefs) { - assert(this); - Fl_Preferences p_win(prefs, "Window"); - p_win.set("left_margin", left_window_margin); - p_win.set("right_margin", right_window_margin); - p_win.set("top_margin", top_window_margin); - p_win.set("bottom_margin", bottom_window_margin); - p_win.set("grid_x", window_grid_x); - p_win.set("grid_y", window_grid_y); - - Fl_Preferences p_grp(prefs, "Group"); - p_grp.set("left_margin", left_group_margin); - p_grp.set("right_margin", right_group_margin); - p_grp.set("top_margin", top_group_margin); - p_grp.set("bottom_margin", bottom_group_margin); - p_grp.set("grid_x", group_grid_x); - p_grp.set("grid_y", group_grid_y); - - Fl_Preferences p_tbs(prefs, "Tabs"); - p_tbs.set("top_margin", top_tabs_margin); - p_tbs.set("bottom_margin", bottom_tabs_margin); - - Fl_Preferences p_wgt(prefs, "Widget"); - p_wgt.set("min_w", widget_min_w); - p_wgt.set("inc_w", widget_inc_w); - p_wgt.set("gap_x", widget_gap_x); - p_wgt.set("min_h", widget_min_h); - p_wgt.set("inc_h", widget_inc_h); - p_wgt.set("gap_y", widget_gap_y); - - Fl_Preferences p_lyt(prefs, "Layout"); - p_lyt.set("labelfont", labelfont); - p_lyt.set("labelsize", labelsize); - p_lyt.set("textfont", textfont); - p_lyt.set("textsize", textsize); -} - -/** - Read presets from a Preferences database. - */ -void Fd_Layout_Preset::read(Fl_Preferences &prefs) { - assert(this); - Fl_Preferences p_win(prefs, "Window"); - p_win.get("left_margin", left_window_margin, 15); - p_win.get("right_margin", right_window_margin, 15); - p_win.get("top_margin", top_window_margin, 15); - p_win.get("bottom_margin", bottom_window_margin, 15); - p_win.get("grid_x", window_grid_x, 0); - p_win.get("grid_y", window_grid_y, 0); - - Fl_Preferences p_grp(prefs, "Group"); - p_grp.get("left_margin", left_group_margin, 10); - p_grp.get("right_margin", right_group_margin, 10); - p_grp.get("top_margin", top_group_margin, 10); - p_grp.get("bottom_margin", bottom_group_margin, 10); - p_grp.get("grid_x", group_grid_x, 0); - p_grp.get("grid_y", group_grid_y, 0); - - Fl_Preferences p_tbs(prefs, "Tabs"); - p_tbs.get("top_margin", top_tabs_margin, 25); - p_tbs.get("bottom_margin", bottom_tabs_margin, 25); - - Fl_Preferences p_wgt(prefs, "Widget"); - p_wgt.get("min_w", widget_min_w, 20); - p_wgt.get("inc_w", widget_inc_w, 10); - p_wgt.get("gap_x", widget_gap_x, 4); - p_wgt.get("min_h", widget_min_h, 20); - p_wgt.get("inc_h", widget_inc_h, 4); - p_wgt.get("gap_y", widget_gap_y, 8); - - Fl_Preferences p_lyt(prefs, "Layout"); - p_lyt.get("labelfont", labelfont, 0); - p_lyt.get("labelsize", labelsize, 14); - p_lyt.get("textfont", textfont, 0); - p_lyt.get("textsize", textsize, 14); -} - -/** - Write presets to an .fl project file. - */ -void Fd_Layout_Preset::write(fld::io::Project_Writer *out) { - out->write_string(" preset { 1\n"); // preset format version - out->write_string(" %d %d %d %d %d %d\n", - left_window_margin, right_window_margin, - top_window_margin, bottom_window_margin, - window_grid_x, window_grid_y); - out->write_string(" %d %d %d %d %d %d\n", - left_group_margin, right_group_margin, - top_group_margin, bottom_group_margin, - group_grid_x, group_grid_y); - out->write_string(" %d %d\n", top_tabs_margin, bottom_tabs_margin); - out->write_string(" %d %d %d %d %d %d\n", - widget_min_w, widget_inc_w, widget_gap_x, - widget_min_h, widget_inc_h, widget_gap_y); - out->write_string(" %d %d %d %d\n", - labelfont, labelsize, textfont, textsize); - out->write_string(" }\n"); // preset format version -} - -/** - Read presets from an .fl project file. - */ -void Fd_Layout_Preset::read(fld::io::Project_Reader *in) { - const char *key; - key = in->read_word(1); - if (key && !strcmp(key, "{")) { - for (;;) { - key = in->read_word(); - if (!key) return; - if (key[0] == '}') break; - int ver = atoi(key); - if (ver == 0) { - continue; - } else if (ver == 1) { - left_window_margin = in->read_int(); - right_window_margin = in->read_int(); - top_window_margin = in->read_int(); - bottom_window_margin = in->read_int(); - window_grid_x = in->read_int(); - window_grid_y = in->read_int(); - - left_group_margin = in->read_int(); - right_group_margin = in->read_int(); - top_group_margin = in->read_int(); - bottom_group_margin = in->read_int(); - group_grid_x = in->read_int(); - group_grid_y = in->read_int(); - - top_tabs_margin = in->read_int(); - bottom_tabs_margin = in->read_int(); - - widget_min_w = in->read_int(); - widget_inc_w = in->read_int(); - widget_gap_x = in->read_int(); - widget_min_h = in->read_int(); - widget_inc_h = in->read_int(); - widget_gap_y = in->read_int(); - - labelfont = in->read_int(); - labelsize = in->read_int(); - textfont = in->read_int(); - textsize = in->read_int(); - } else { // skip unknown chunks - for (;;) { - key = in->read_word(1); - if (key && (key[0] == '}')) - return; - } - } - } - } else { - // format error - } -} - -/** - Return the preferred text size, but make sure it's not 0. - */ -int Fd_Layout_Preset::textsize_not_null() { - // try the user selected text size - if (textsize > 0) return textsize; - // if the user did not set one, try the label size - if (labelsize > 0) return labelsize; - // if that doesn;t work, fall back to the default value - return 14; -} - - -// ---- Fd_Layout_Suite ------------------------------------------------ MARK: - - -/** - Write a presets suite to a Preferences database. - */ -void Fd_Layout_Suite::write(Fl_Preferences &prefs) { - assert(this); - assert(name_); - prefs.set("name", name_); - for (int i = 0; i < 3; ++i) { - Fl_Preferences prefs_preset(prefs, Fl_Preferences::Name(i)); - assert(layout[i]); - layout[i]->write(prefs_preset); - } -} - -/** - Read a presets suite from a Preferences database. - */ -void Fd_Layout_Suite::read(Fl_Preferences &prefs) { - assert(this); - for (int i = 0; i < 3; ++i) { - Fl_Preferences prefs_preset(prefs, Fl_Preferences::Name(i)); - assert(layout[i]); - layout[i]->read(prefs_preset); - } -} - -/** - Write a presets suite to an .fl project file. - */ -void Fd_Layout_Suite::write(fld::io::Project_Writer *out) { - out->write_string(" suite {\n"); - out->write_string(" name "); out->write_word(name_); out->write_string("\n"); - for (int i = 0; i < 3; ++i) { - layout[i]->write(out); - } - out->write_string(" }\n"); -} - -/** - Read a presets suite from an .fl project file. - */ -void Fd_Layout_Suite::read(fld::io::Project_Reader *in) { - const char *key; - key = in->read_word(1); - if (key && !strcmp(key, "{")) { - int ix = 0; - for (;;) { - key = in->read_word(); - if (!key) return; - if (!strcmp(key, "name")) { - name(in->read_word()); - } else if (!strcmp(key, "preset")) { - if (ix >= 3) return; // file format error - layout[ix++]->read(in); - } else if (!strcmp(key, "}")) { - break; - } else { - in->read_word(); // unknown key, ignore, hopefully a key-value pair - } - } - } else { - // file format error - } -} - -/** - \brief Update the menu_label to show a symbol representing the storage location. - Also updates the FLUID user interface. - */ -void Fd_Layout_Suite::update_label() { - std::string sym; - switch (storage_) { - case FD_STORE_INTERNAL: sym.assign("@fd_beaker "); break; - case FD_STORE_USER: sym.assign("@fd_user "); break; - case FD_STORE_PROJECT: sym.assign("@fd_project "); break; - case FD_STORE_FILE: sym.assign("@fd_file "); break; - } - sym.append(name_); - if (menu_label) - ::free(menu_label); - menu_label = fl_strdup(sym.c_str()); - g_layout_list.update_menu_labels(); -} - -/** - \brief Update the Suite name and the Suite menu_label. - Also updates the FLUID user interface. - */ -void Fd_Layout_Suite::name(const char *n) { - if (name_) - ::free(name_); - if (n) - name_ = fl_strdup(n); - else - name_ = NULL; - update_label(); -} - -/** - Initialize the class for first use. - */ -void Fd_Layout_Suite::init() { - name_ = NULL; - menu_label = NULL; - layout[0] = layout[1] = layout[2] = NULL; - storage_ = FD_STORE_INTERNAL; -} - -/** - Free all allocated resources. - */ -Fd_Layout_Suite::~Fd_Layout_Suite() { - if (storage_ == FD_STORE_INTERNAL) return; - if (name_) ::free(name_); - for (int i = 0; i < 3; ++i) { - delete layout[i]; - } -} - -// ---- Fd_Layout_List ------------------------------------------------- MARK: - - -/** - Draw a little FLUID beaker symbol. - */ -static void fd_beaker(Fl_Color c) { - fl_color(221); - fl_begin_polygon(); - fl_vertex(-0.6, 0.2); - fl_vertex(-0.9, 0.8); - fl_vertex(-0.8, 0.9); - fl_vertex( 0.8, 0.9); - fl_vertex( 0.9, 0.8); - fl_vertex( 0.6, 0.2); - fl_end_polygon(); - fl_color(c); - fl_begin_line(); - fl_vertex(-0.3, -0.9); - fl_vertex(-0.2, -0.8); - fl_vertex(-0.2, -0.2); - fl_vertex(-0.9, 0.8); - fl_vertex(-0.8, 0.9); - fl_vertex( 0.8, 0.9); - fl_vertex( 0.9, 0.8); - fl_vertex( 0.2, -0.2); - fl_vertex( 0.2, -0.8); - fl_vertex( 0.3, -0.9); - fl_end_line(); -} - -/** - Draw a user silhouette symbol - */ -static void fd_user(Fl_Color c) { - fl_color(245); - fl_begin_complex_polygon(); - fl_arc( 0.1, 0.9, 0.8, 0.0, 80.0); - fl_arc( 0.0, -0.5, 0.4, -65.0, 245.0); - fl_arc(-0.1, 0.9, 0.8, 100.0, 180.0); - fl_end_complex_polygon(); - fl_color(c); - fl_begin_line(); - fl_arc( 0.1, 0.9, 0.8, 0.0, 80.0); - fl_arc( 0.0, -0.5, 0.4, -65.0, 245.0); - fl_arc(-0.1, 0.9, 0.8, 100.0, 180.0); - fl_end_line(); -} - -/** - Draw a document symbol. - */ -static void fd_project(Fl_Color c) { - Fl_Color fc = FL_LIGHT2; - fl_color(fc); - fl_begin_complex_polygon(); - fl_vertex(-0.7, -1.0); - fl_vertex(0.1, -1.0); - fl_vertex(0.1, -0.4); - fl_vertex(0.7, -0.4); - fl_vertex(0.7, 1.0); - fl_vertex(-0.7, 1.0); - fl_end_complex_polygon(); - - fl_color(fl_lighter(fc)); - fl_begin_polygon(); - fl_vertex(0.1, -1.0); - fl_vertex(0.1, -0.4); - fl_vertex(0.7, -0.4); - fl_end_polygon(); - - fl_color(fl_darker(c)); - fl_begin_loop(); - fl_vertex(-0.7, -1.0); - fl_vertex(0.1, -1.0); - fl_vertex(0.1, -0.4); - fl_vertex(0.7, -0.4); - fl_vertex(0.7, 1.0); - fl_vertex(-0.7, 1.0); - fl_end_loop(); - - fl_begin_line(); - fl_vertex(0.1, -1.0); - fl_vertex(0.7, -0.4); - fl_end_line(); -} - -/** - Draw a 3 1/2" floppy symbol. - */ -void fd_file(Fl_Color c) { - Fl_Color fl = FL_LIGHT2; - Fl_Color fc = FL_DARK3; - fl_color(fc); - fl_begin_polygon(); // case - fl_vertex(-0.9, -1.0); - fl_vertex(0.9, -1.0); - fl_vertex(1.0, -0.9); - fl_vertex(1.0, 0.9); - fl_vertex(0.9, 1.0); - fl_vertex(-0.9, 1.0); - fl_vertex(-1.0, 0.9); - fl_vertex(-1.0, -0.9); - fl_end_polygon(); - - fl_color(fl_lighter(fl)); - fl_begin_polygon(); - fl_vertex(-0.7, -1.0); // slider - fl_vertex(0.7, -1.0); - fl_vertex(0.7, -0.4); - fl_vertex(-0.7, -0.4); - fl_end_polygon(); - - fl_begin_polygon(); // label - fl_vertex(-0.7, 0.0); - fl_vertex(0.7, 0.0); - fl_vertex(0.7, 1.0); - fl_vertex(-0.7, 1.0); - fl_end_polygon(); - - fl_color(fc); - fl_begin_polygon(); - fl_vertex(-0.5, -0.9); // slot - fl_vertex(-0.3, -0.9); - fl_vertex(-0.3, -0.5); - fl_vertex(-0.5, -0.5); - fl_end_polygon(); - - fl_color(fl_darker(c)); - fl_begin_loop(); - fl_vertex(-0.9, -1.0); - fl_vertex(0.9, -1.0); - fl_vertex(1.0, -0.9); - fl_vertex(1.0, 0.9); - fl_vertex(0.9, 1.0); - fl_vertex(-0.9, 1.0); - fl_vertex(-1.0, 0.9); - fl_vertex(-1.0, -0.9); - fl_end_loop(); -} - -/** - Instantiate the class that holds a list of all layouts and manages the UI. - */ -Fd_Layout_List::Fd_Layout_List() -: main_menu_(main_layout_submenu_), - choice_menu_(static_choice_menu), - list_(static_suite_list), - list_size_(2), - list_capacity_(2), - list_is_static_(true), - current_suite_(0), - current_preset_(0) -{ - fl_add_symbol("fd_beaker", fd_beaker, 1); - fl_add_symbol("fd_user", fd_user, 1); - fl_add_symbol("fd_project", fd_project, 1); - fl_add_symbol("fd_file", fd_file, 1); -} - -/** - Release allocated resources. - */ -Fd_Layout_List::~Fd_Layout_List() { - assert(this); - if (!list_is_static_) { - ::free(main_menu_); - ::free(choice_menu_); - for (int i = 0; i < list_size_; i++) { - Fd_Layout_Suite &suite = list_[i]; - if (suite.storage_ != FD_STORE_INTERNAL) - suite.~Fd_Layout_Suite(); - } - ::free(list_); - } -} - -/** - Update the Setting dialog and menus to reflect the current Layout selection state. - */ -void Fd_Layout_List::update_dialogs() { - static Fl_Menu_Item *preset_menu = NULL; - if (!preset_menu) { - preset_menu = (Fl_Menu_Item*)main_menubar->find_item(select_layout_preset_cb); - assert(preset_menu); - } - assert(this); - assert(current_suite_ >= 0 ); - assert(current_suite_ < list_size_); - assert(current_preset_ >= 0 ); - assert(current_preset_ < 3); - layout = list_[current_suite_].layout[current_preset_]; - assert(layout); - if (w_settings_layout_tab) { - w_settings_layout_tab->do_callback(w_settings_layout_tab, LOAD); - layout_choice->redraw(); - } - preset_menu[current_preset_].setonly(preset_menu); - main_menu_[current_suite_].setonly(main_menu_); -} - -/** - Refresh the label pointers for both pulldown menus. - */ -void Fd_Layout_List::update_menu_labels() { - for (int i=0; iwrite_string("\nsnap {\n ver 1\n"); - out->write_string(" current_suite "); out->write_word(list_[current_suite()].name_); out->write_string("\n"); - out->write_string(" current_preset %d\n", current_preset()); - for (int i=0; iwrite_string("}"); -} - -/** - Read Suite and Layout selection and project layout data from an .fl project file. - */ -void Fd_Layout_List::read(fld::io::Project_Reader *in) { - const char *key; - key = in->read_word(1); - if (key && !strcmp(key, "{")) { - std::string cs; - int cp = 0; - for (;;) { - key = in->read_word(); - if (!key) return; - if (!strcmp(key, "ver")) { - in->read_int(); - } else if (!strcmp(key, "current_suite")) { - cs = in->read_word(); - } else if (!strcmp(key, "current_preset")) { - cp = in->read_int(); - } else if (!strcmp(key, "suite")) { - int n = add(in->filename_name()); - list_[n].read(in); - list_[n].storage(FD_STORE_PROJECT); - } else if (!strcmp(key, "}")) { - break; - } else { - in->read_word(); // unknown key, ignore, hopefully a key-value pair - } - } - current_suite(cs); - current_preset(cp); - update_dialogs(); - } else { - // old style "snap" is followed by an integer. Ignore. - } -} - -/** - Set the current Suite. - \param[in] ix index into list of suites - */ -void Fd_Layout_List::current_suite(int ix) { - assert(ix >= 0); - assert(ix < list_size_); - current_suite_ = ix; - layout = list_[current_suite_].layout[current_preset_]; -} - -/** - Set the current Suite. - \param[in] arg_name name of the selected suite - \return if no name is given or the name is not found, keep the current suite selected - */ -void Fd_Layout_List::current_suite(std::string arg_name) { - if (arg_name.empty()) return; - for (int i = 0; i < list_size_; ++i) { - Fd_Layout_Suite &suite = list_[i]; - if (suite.name_ && (strcmp(suite.name_, arg_name.c_str()) == 0)) { - current_suite(i); - break; - } - } -} - -/** - Select a Preset within the current Suite. - \param[in] ix 0 = application, 1 = dialog, 2 = toolbox - */ -void Fd_Layout_List::current_preset(int ix) { - assert(ix >= 0); - assert(ix < 3); - current_preset_ = ix; - layout = list_[current_suite_].layout[current_preset_]; -} - -/** - Allocate enough space for n entries in the list. - */ -void Fd_Layout_List::capacity(int n) { - static Fl_Menu_Item *suite_menu = NULL; - if (!suite_menu) - suite_menu = (Fl_Menu_Item*)main_menubar->find_item(layout_suite_marker); - - int old_n = list_size_; - int i; - - Fd_Layout_Suite *new_list = (Fd_Layout_Suite*)::calloc(n, sizeof(Fd_Layout_Suite)); - for (i = 0; i < old_n; i++) - new_list[i] = list_[i]; - if (!list_is_static_) ::free(list_); - list_ = new_list; - - Fl_Menu_Item *new_main_menu = (Fl_Menu_Item*)::calloc(n+1, sizeof(Fl_Menu_Item)); - for (i = 0; i < old_n; i++) - new_main_menu[i] = main_menu_[i]; - if (!list_is_static_) ::free(main_menu_); - main_menu_ = new_main_menu; - suite_menu->user_data(main_menu_); - - Fl_Menu_Item *new_choice_menu = (Fl_Menu_Item*)::calloc(n+1, sizeof(Fl_Menu_Item)); - for (i = 0; i < old_n; i++) - new_choice_menu[i] = choice_menu_[i]; - if (!list_is_static_) ::free(choice_menu_); - choice_menu_ = new_choice_menu; - if (layout_choice) layout_choice->menu(choice_menu_); - - list_capacity_ = n; - list_is_static_ = false; -} - -/** - \brief Clone the currently selected suite and append it to the list. - Selects the new layout and updates the UI. - */ -int Fd_Layout_List::add(const char *name) { - if (list_size_ == list_capacity_) { - capacity(list_capacity_ * 2); - } - int n = list_size_; - Fd_Layout_Suite &old_suite = list_[current_suite_]; - Fd_Layout_Suite &new_suite = list_[n]; - new_suite.init(); - new_suite.name(name); - for (int i=0; i<3; ++i) { - new_suite.layout[i] = new Fd_Layout_Preset; - ::memcpy(new_suite.layout[i], old_suite.layout[i], sizeof(Fd_Layout_Preset)); - } - Fd_Tool_Store new_storage = old_suite.storage_; - if (new_storage == FD_STORE_INTERNAL) - new_storage = FD_STORE_USER; - new_suite.storage(new_storage); - main_menu_[n].label(new_suite.menu_label); - main_menu_[n].callback(main_menu_[0].callback()); - main_menu_[n].argument(n); - main_menu_[n].flags = main_menu_[0].flags; - choice_menu_[n].label(new_suite.menu_label); - list_size_++; - current_suite(n); - return n; -} - -/** - Rename the current Suite. - */ -void Fd_Layout_List::rename(const char *name) { - int n = current_suite(); - list_[n].name(name); - main_menu_[n].label(list_[n].menu_label); - choice_menu_[n].label(list_[n].menu_label); -} - -/** - Remove the given suite. - \param[in] ix index into list of suites - */ -void Fd_Layout_List::remove(int ix) { - int tail = list_size_-ix-1; - if (tail) { - for (int i = ix; i < list_size_-1; i++) - list_[i] = list_[i+1]; - } - ::memmove(main_menu_+ix, main_menu_+ix+1, (tail+1) * sizeof(Fl_Menu_Item)); - ::memmove(choice_menu_+ix, choice_menu_+ix+1, (tail+1) * sizeof(Fl_Menu_Item)); - list_size_--; - if (current_suite() >= list_size_) - current_suite(list_size_ - 1); -} - -/** - Remove all Suites that use the given storage attribute. - \param[in] storage storage attribute, see FD_STORE_INTERNAL, etc. - */ -void Fd_Layout_List::remove_all(Fd_Tool_Store storage) { - for (int i=list_size_-1; i>=0; --i) { - if (list_[i].storage_ == storage) - remove(i); - } -} - -// ---- Helper --------------------------------------------------------- MARK: - - -static void draw_h_arrow(int, int, int); -static void draw_v_arrow(int x, int y1, int y2); -static void draw_left_brace(const Fl_Widget *w); -static void draw_right_brace(const Fl_Widget *w); -static void draw_top_brace(const Fl_Widget *w); -static void draw_bottom_brace(const Fl_Widget *w); -static void draw_grid(int x, int y, int dx, int dy); -void draw_width(int x, int y, int r, Fl_Align a); -void draw_height(int x, int y, int b, Fl_Align a); - -static int nearest(int x, int left, int grid, int right=0x7fff) { - int grid_x = ((x-left+grid/2)/grid)*grid+left; - if (grid_x < left+grid/2) return left; // left+grid/2; - if (grid_x > right-grid/2) return right; // right-grid/2; - return grid_x; -} - -static bool in_window(Fd_Snap_Data &d) { - return (d.wgt && d.wgt->parent == d.win); -} - -static bool in_group(Fd_Snap_Data &d) { - return (d.wgt && d.wgt->parent && d.wgt->parent->is_a(ID_Group) && d.wgt->parent != d.win); -} - -static bool in_tabs(Fd_Snap_Data &d) { - return (d.wgt && d.wgt->parent && d.wgt->parent->is_a(ID_Tabs)); -} - -static Fl_Group *parent(Fd_Snap_Data &d) { - return (d.wgt->o->parent()); -} - -// ---- Fd_Snap_Action ------------------------------------------------- MARK: - - -/** \class Fd_Snap_Action - - When a user drags one or more widgets, snap actions can be defined that provide - hints if a preferred widget position or size is nearby. The user's motion is - then directed towards the nearest preferred position, and the widget selection - snaps into place. - - FLUID provides a list of various snap actions. Every snap action uses the data - from the motion event and combines it with the sizes and positions of all other - widgets in the layout. - - Common snap actions include gaps and margins, but also alignments and - simple grid positions. - */ - -/** - \brief Check if a snap action has reached a preferred x position. - \param[inout] d current event data - \param[in] x_ref position of moving point - \param[in] x_snap position of target point - \return 1 if the points are not within range and won;t be considered - \return 0 if the point is as close as another in a previous action - \return -1 if this point is closer than any previous check, and this is the - new distance to beat. - */ -int Fd_Snap_Action::check_x_(Fd_Snap_Data &d, int x_ref, int x_snap) { - int dd = x_ref + d.dx - x_snap; - int d2 = abs(dd); - if (d2 > d.x_dist) return 1; - dx = d.dx_out = d.dx - dd; - ex = d.ex_out = x_snap; - if (d2 == d.x_dist) return 0; - d.x_dist = d2; - return -1; -} - -/** - \brief Check if a snap action has reached a preferred y position. - \see Fd_Snap_Action::check_x_(Fd_Snap_Data &d, int x_ref, int x_snap) - */ -int Fd_Snap_Action::check_y_(Fd_Snap_Data &d, int y_ref, int y_snap) { - int dd = y_ref + d.dy - y_snap; - int d2 = abs(dd); - if (d2 > d.y_dist) return 1; - dy = d.dy_out = d.dy - dd; - ey = d.ey_out = y_snap; - if (d2 == d.y_dist) return 0; - d.y_dist = d2; - return -1; -} - -/** - \brief Check if a snap action has reached a preferred x and y position. - \see Fd_Snap_Action::check_x_(Fd_Snap_Data &d, int x_ref, int x_snap) - */ -void Fd_Snap_Action::check_x_y_(Fd_Snap_Data &d, int x_ref, int x_snap, int y_ref, int y_snap) { - int ddx = x_ref + d.dx - x_snap; - int d2x = abs(ddx); - int ddy = y_ref + d.dy - y_snap; - int d2y = abs(ddy); - if ((d2x <= d.x_dist) && (d2y <= d.y_dist)) { - dx = d.dx_out = d.dx - ddx; - ex = d.ex_out = x_snap; - d.x_dist = d2x; - dy = d.dy_out = d.dy - ddy; - ey = d.ey_out = y_snap; - d.y_dist = d2y; - } -} - -/** - \brief Check if a snap action was applied to the current event. - This method is used to determine if a visual indicator for this snap action - should be drawn. - \param[inout] d current event data - */ -bool Fd_Snap_Action::matches(Fd_Snap_Data &d) { - switch (type) { - case 1: return (d.drag & mask) && (eex == ex) && (d.dx == dx); - case 2: return (d.drag & mask) && (eey == ey) && (d.dy == dy); - case 3: return (d.drag & mask) && (eex == ex) && (d.dx == dx) && (eey == ey) && (d.dy == dy); - } - return false; -} - -/** - \brief Run through all possible snap actions and store the winning coordinates in eex and eey. - \param[inout] d current event data - */ -void Fd_Snap_Action::check_all(Fd_Snap_Data &data) { - for (int i=0; list[i]; i++) { - if (list[i]->mask & data.drag) - list[i]->check(data); - } - eex = data.ex_out; - eey = data.ey_out; -} - -/** - \brief Draw a visual indicator for all snap actions that were applied during the last check. - Only one snap coordinate can win. FLUID chooses the one that is closest to - the current user event. If two or more snap actions suggest the same - coordinate, all of them will be drawn. - \param[inout] d current event data - */ -void Fd_Snap_Action::draw_all(Fd_Snap_Data &data) { - for (int i=0; list[i]; i++) { - if (list[i]->matches(data)) - list[i]->draw(data); - } -} - -/** Return a sensible step size for resizing a widget. */ -void Fd_Snap_Action::get_resize_stepsize(int &x_step, int &y_step) { - if ((layout->widget_inc_w > 1) && (layout->widget_inc_h > 1)) { - x_step = layout->widget_inc_w; - y_step = layout->widget_inc_h; - } else if ((layout->group_grid_x > 1) && (layout->group_grid_y > 1)) { - x_step = layout->group_grid_x; - y_step = layout->group_grid_y; - } else { - x_step = layout->window_grid_x; - y_step = layout->window_grid_y; - } -} - -/** Return a sensible step size for moving a widget. */ -void Fd_Snap_Action::get_move_stepsize(int &x_step, int &y_step) { - if ((layout->group_grid_x > 1) && (layout->group_grid_y > 1)) { - x_step = layout->group_grid_x; - y_step = layout->group_grid_y; - } else if ((layout->window_grid_x > 1) && (layout->window_grid_y > 1)) { - x_step = layout->window_grid_x; - y_step = layout->window_grid_y; - } else { - x_step = layout->widget_gap_x; - y_step = layout->widget_gap_y; - } -} - -/** Fix the given size to the same or next bigger snap position. */ -void Fd_Snap_Action::better_size(int &w, int &h) { - int x_min = 1, y_min = 1, x_inc = 1, y_inc = 1; - get_resize_stepsize(x_inc, y_inc); - if (x_inc < 1) x_inc = 1; - if (y_inc < 1) y_inc = 1; - if ((layout->widget_min_w > 1) && (layout->widget_min_h > 1)) { - x_min = layout->widget_min_w; - y_min = layout->widget_min_h; - } else if ((layout->group_grid_x > 1) && (layout->group_grid_y > 1)) { - x_min = layout->group_grid_x; - y_min = layout->group_grid_y; - } else { - x_min = x_inc; - y_min = y_inc; - } - int ww = fd_max(w - x_min, 0); w = (w - ww + x_inc - 1) / x_inc; w = w * x_inc; w = w + ww; - int hh = fd_max(h - y_min, 0); h = (h - hh + y_inc - 1) / y_inc; h = h * y_inc; h = h + hh; -} - - -// ---- snapping prototypes -------------------------------------------- MARK: - - -/** - Base class for all actions that drag the left side or the entire widget. - */ -class Fd_Snap_Left : public Fd_Snap_Action { -public: - Fd_Snap_Left() { type = 1; mask = FD_LEFT|FD_DRAG; } -}; - -/** - Base class for all actions that drag the right side or the entire widget. - */ -class Fd_Snap_Right : public Fd_Snap_Action { -public: - Fd_Snap_Right() { type = 1; mask = FD_RIGHT|FD_DRAG; } -}; - -/** - Base class for all actions that drag the top side or the entire widget. - */ -class Fd_Snap_Top : public Fd_Snap_Action { -public: - Fd_Snap_Top() { type = 2; mask = FD_TOP|FD_DRAG; } -}; - -/** - Base class for all actions that drag the bottom side or the entire widget. - */ -class Fd_Snap_Bottom : public Fd_Snap_Action { -public: - Fd_Snap_Bottom() { type = 2; mask = FD_BOTTOM|FD_DRAG; } -}; - -// ---- window snapping ------------------------------------------------ MARK: - - -/** - Check if the widget hits the left window edge. - */ -class Fd_Snap_Left_Window_Edge : public Fd_Snap_Left { -public: - void check(Fd_Snap_Data &d) FL_OVERRIDE { clr(); check_x_(d, d.bx, 0); } - void draw(Fd_Snap_Data &d) FL_OVERRIDE { draw_left_brace(d.win->o); }; -}; -Fd_Snap_Left_Window_Edge snap_left_window_edge; - -/** - Check if the widget hits the right window edge. - */ -class Fd_Snap_Right_Window_Edge : public Fd_Snap_Right { -public: - void check(Fd_Snap_Data &d) FL_OVERRIDE { clr(); check_x_(d, d.br, d.win->o->w()); } - void draw(Fd_Snap_Data &d) FL_OVERRIDE { draw_right_brace(d.win->o); }; -}; -Fd_Snap_Right_Window_Edge snap_right_window_edge; - -/** - Check if the widget hits the top window edge. - */ -class Fd_Snap_Top_Window_Edge : public Fd_Snap_Top { -public: - void check(Fd_Snap_Data &d) FL_OVERRIDE { clr(); check_y_(d, d.by, 0); } - void draw(Fd_Snap_Data &d) FL_OVERRIDE { draw_top_brace(d.win->o); }; -}; -Fd_Snap_Top_Window_Edge snap_top_window_edge; - -/** - Check if the widget hits the bottom window edge. - */ -class Fd_Snap_Bottom_Window_Edge : public Fd_Snap_Bottom { -public: - void check(Fd_Snap_Data &d) FL_OVERRIDE { clr(); check_y_(d, d.bt, d.win->o->h()); } - void draw(Fd_Snap_Data &d) FL_OVERRIDE { draw_bottom_brace(d.win->o); }; -}; -Fd_Snap_Bottom_Window_Edge snap_bottom_window_edge; - -/** - Check if the widget hits the left window edge plus a user defined margin. - */ -class Fd_Snap_Left_Window_Margin : public Fd_Snap_Left { -public: - void check(Fd_Snap_Data &d) FL_OVERRIDE { - clr(); - if (in_window(d)) check_x_(d, d.bx, layout->left_window_margin); - } - void draw(Fd_Snap_Data &d) FL_OVERRIDE { - draw_h_arrow(d.bx, (d.by+d.bt)/2, 0); - }; -}; -Fd_Snap_Left_Window_Margin snap_left_window_margin; - -class Fd_Snap_Right_Window_Margin : public Fd_Snap_Right { -public: - void check(Fd_Snap_Data &d) FL_OVERRIDE { - clr(); - if (in_window(d)) check_x_(d, d.br, d.win->o->w()-layout->right_window_margin); - } - void draw(Fd_Snap_Data &d) FL_OVERRIDE { - draw_h_arrow(d.br, (d.by+d.bt)/2, d.win->o->w()-1); - }; -}; -Fd_Snap_Right_Window_Margin snap_right_window_margin; - -class Fd_Snap_Top_Window_Margin : public Fd_Snap_Top { -public: - void check(Fd_Snap_Data &d) FL_OVERRIDE { - clr(); - if (in_window(d)) check_y_(d, d.by, layout->top_window_margin); - } - void draw(Fd_Snap_Data &d) FL_OVERRIDE { - draw_v_arrow((d.bx+d.br)/2, d.by, 0); - }; -}; -Fd_Snap_Top_Window_Margin snap_top_window_margin; - -class Fd_Snap_Bottom_Window_Margin : public Fd_Snap_Bottom { -public: - void check(Fd_Snap_Data &d) FL_OVERRIDE { - clr(); - if (in_window(d)) check_y_(d, d.bt, d.win->o->h()-layout->bottom_window_margin); - } - void draw(Fd_Snap_Data &d) FL_OVERRIDE { - draw_v_arrow((d.bx+d.br)/2, d.bt, d.win->o->h()-1); - }; -}; -Fd_Snap_Bottom_Window_Margin snap_bottom_window_margin; - -// ---- group snapping ------------------------------------------------- MARK: - - -/** - Check if the widget hits the left group edge. - */ -class Fd_Snap_Left_Group_Edge : public Fd_Snap_Left { -public: - void check(Fd_Snap_Data &d) FL_OVERRIDE { - clr(); - if (in_group(d)) check_x_(d, d.bx, parent(d)->x()); - } - void draw(Fd_Snap_Data &d) FL_OVERRIDE { - draw_left_brace(parent(d)); - }; -}; -Fd_Snap_Left_Group_Edge snap_left_group_edge; - -class Fd_Snap_Right_Group_Edge : public Fd_Snap_Right { -public: - void check(Fd_Snap_Data &d) FL_OVERRIDE { - clr(); - if (in_group(d)) check_x_(d, d.br, parent(d)->x() + parent(d)->w()); - } - void draw(Fd_Snap_Data &d) FL_OVERRIDE { - draw_right_brace(parent(d)); - }; -}; -Fd_Snap_Right_Group_Edge snap_right_group_edge; - -class Fd_Snap_Top_Group_Edge : public Fd_Snap_Top { -public: - void check(Fd_Snap_Data &d) FL_OVERRIDE { - clr(); - if (in_group(d)) check_y_(d, d.by, parent(d)->y()); - } - void draw(Fd_Snap_Data &d) FL_OVERRIDE { - draw_top_brace(parent(d)); - }; -}; -Fd_Snap_Top_Group_Edge snap_top_group_edge; - -class Fd_Snap_Bottom_Group_Edge : public Fd_Snap_Bottom { -public: - void check(Fd_Snap_Data &d) FL_OVERRIDE { - clr(); - if (in_group(d)) check_y_(d, d.bt, parent(d)->y() + parent(d)->h()); - } - void draw(Fd_Snap_Data &d) FL_OVERRIDE { - draw_bottom_brace(parent(d)); - }; -}; -Fd_Snap_Bottom_Group_Edge snap_bottom_group_edge; - - -/** - Check if the widget hits the left group edge plus a user defined margin. - */ -class Fd_Snap_Left_Group_Margin : public Fd_Snap_Left { -public: - void check(Fd_Snap_Data &d) FL_OVERRIDE { - clr(); - if (in_group(d)) check_x_(d, d.bx, parent(d)->x() + layout->left_group_margin); - } - void draw(Fd_Snap_Data &d) FL_OVERRIDE { - draw_left_brace(parent(d)); - draw_h_arrow(d.bx, (d.by+d.bt)/2, parent(d)->x()); - }; -}; -Fd_Snap_Left_Group_Margin snap_left_group_margin; - -class Fd_Snap_Right_Group_Margin : public Fd_Snap_Right { -public: - void check(Fd_Snap_Data &d) FL_OVERRIDE { - clr(); - if (in_group(d)) check_x_(d, d.br, parent(d)->x()+parent(d)->w()-layout->right_group_margin); - } - void draw(Fd_Snap_Data &d) FL_OVERRIDE { - draw_right_brace(parent(d)); - draw_h_arrow(d.br, (d.by+d.bt)/2, parent(d)->x()+parent(d)->w()-1); - }; -}; -Fd_Snap_Right_Group_Margin snap_right_group_margin; - -class Fd_Snap_Top_Group_Margin : public Fd_Snap_Top { -public: - void check(Fd_Snap_Data &d) FL_OVERRIDE { - clr(); - if (in_group(d) && !in_tabs(d)) check_y_(d, d.by, parent(d)->y()+layout->top_group_margin); - } - void draw(Fd_Snap_Data &d) FL_OVERRIDE { - draw_top_brace(parent(d)); - draw_v_arrow((d.bx+d.br)/2, d.by, parent(d)->y()); - }; -}; -Fd_Snap_Top_Group_Margin snap_top_group_margin; - -class Fd_Snap_Bottom_Group_Margin : public Fd_Snap_Bottom { -public: - void check(Fd_Snap_Data &d) FL_OVERRIDE { - clr(); - if (in_group(d) && !in_tabs(d)) check_y_(d, d.bt, parent(d)->y()+parent(d)->h()-layout->bottom_group_margin); - } - void draw(Fd_Snap_Data &d) FL_OVERRIDE { - draw_bottom_brace(parent(d)); - draw_v_arrow((d.bx+d.br)/2, d.bt, parent(d)->y()+parent(d)->h()-1); - }; -}; -Fd_Snap_Bottom_Group_Margin snap_bottom_group_margin; - -// ----- tabs snapping ------------------------------------------------- MARK: - - -/** - Check if the widget top hits the Fl_Tabs group top edge plus a user defined margin. - */ -class Fd_Snap_Top_Tabs_Margin : public Fd_Snap_Top_Group_Margin { -public: - void check(Fd_Snap_Data &d) FL_OVERRIDE { - clr(); - if (in_tabs(d)) check_y_(d, d.by, parent(d)->y()+layout->top_tabs_margin); - } -}; -Fd_Snap_Top_Tabs_Margin snap_top_tabs_margin; - -class Fd_Snap_Bottom_Tabs_Margin : public Fd_Snap_Bottom_Group_Margin { -public: - void check(Fd_Snap_Data &d) FL_OVERRIDE { - clr(); - if (in_tabs(d)) check_y_(d, d.bt, parent(d)->y()+parent(d)->h()-layout->bottom_tabs_margin); - } -}; -Fd_Snap_Bottom_Tabs_Margin snap_bottom_tabs_margin; - -// ----- grid snapping ------------------------------------------------- MARK: - - -/** - Base class for grid based snapping. - */ -class Fd_Snap_Grid : public Fd_Snap_Action { -protected: - int nearest_x, nearest_y; -public: - Fd_Snap_Grid() { type = 3; mask = FD_LEFT|FD_TOP|FD_DRAG; } - void check_grid(Fd_Snap_Data &d, int left, int grid_x, int right, int top, int grid_y, int bottom) { - if ((grid_x <= 1) || (grid_y <= 1)) return; - int suggested_x = d.bx + d.dx; - nearest_x = nearest(suggested_x, left, grid_x, right); - int suggested_y = d.by + d.dy; - nearest_y = nearest(suggested_y, top, grid_y, bottom); - if (d.drag == FD_LEFT) - check_x_(d, d.bx, nearest_x); - else if (d.drag == FD_TOP) - check_y_(d, d.by, nearest_y); - else - check_x_y_(d, d.bx, nearest_x, d.by, nearest_y); - } - bool matches(Fd_Snap_Data &d) FL_OVERRIDE { - if (d.drag == FD_LEFT) return (eex == ex); - if (d.drag == FD_TOP) return (eey == ey) && (d.dx == dx); - return (d.drag & mask) && (eex == ex) && (d.dx == dx) && (eey == ey) && (d.dy == dy); - } -}; - -/** - Check if the widget hits window grid coordinates. - */ -class Fd_Snap_Window_Grid : public Fd_Snap_Grid { -public: - void check(Fd_Snap_Data &d) FL_OVERRIDE { - clr(); - if (in_window(d)) check_grid(d, layout->left_window_margin, layout->window_grid_x, d.win->o->w()-layout->right_window_margin, - layout->top_window_margin, layout->window_grid_y, d.win->o->h()-layout->bottom_window_margin); - } - void draw(Fd_Snap_Data &d) FL_OVERRIDE { - draw_grid(nearest_x, nearest_y, layout->window_grid_x, layout->window_grid_y); - }; -}; -Fd_Snap_Window_Grid snap_window_grid; - -/** - Check if the widget hits group grid coordinates. - */ -class Fd_Snap_Group_Grid : public Fd_Snap_Grid { -public: - void check(Fd_Snap_Data &d) FL_OVERRIDE { - if (in_group(d)) { - clr(); - Fl_Widget *g = parent(d); - check_grid(d, g->x()+layout->left_group_margin, layout->group_grid_x, g->x()+g->w()-layout->right_group_margin, - g->y()+layout->top_group_margin, layout->group_grid_y, g->y()+g->h()-layout->bottom_group_margin); - } - } - void draw(Fd_Snap_Data &d) FL_OVERRIDE { - draw_grid(nearest_x, nearest_y, layout->group_grid_x, layout->group_grid_y); - }; -}; -Fd_Snap_Group_Grid snap_group_grid; - -// ----- sibling snapping ---------------------------------------------- MARK: - - -/** - Base class the check distance to other widgets in the same group. - */ -class Fd_Snap_Sibling : public Fd_Snap_Action { -protected: - Fl_Widget *best_match; -public: - Fd_Snap_Sibling() : best_match(NULL) { } - virtual int sibling_check(Fd_Snap_Data &d, Fl_Widget *s) = 0; - void check(Fd_Snap_Data &d) FL_OVERRIDE { - clr(); - best_match = NULL; - if (!d.wgt) return; - if (!d.wgt->parent->is_a(ID_Group)) return; - int dsib_min = 1024; - Fl_Group_Type *gt = (Fl_Group_Type*)d.wgt->parent; - Fl_Group *g = (Fl_Group*)gt->o; - Fl_Widget *w = d.wgt->o; - for (int i=0; ichildren(); i++) { - Fl_Widget *c = g->child(i); - if (c == w) continue; - int sret = sibling_check(d, c); - if (sret < 1) { - int dsib; - if (type==1) - dsib = abs( ((d.by+d.bt)/2+d.dy) - (c->y()+c->h()/2) ); - else - dsib = abs( ((d.bx+d.br)/2+d.dx) - (c->x()+c->w()/2) ); - if (sret == -1 || (dsib < dsib_min)) { - dsib_min = dsib; - best_match = c; - } - } - } - } -}; - -/** - Check if widgets have the same x coordinate, so they can be vertically aligned. - */ -class Fd_Snap_Siblings_Left_Same : public Fd_Snap_Sibling { -public: - Fd_Snap_Siblings_Left_Same() { type = 1; mask = FD_LEFT|FD_DRAG; } - int sibling_check(Fd_Snap_Data &d, Fl_Widget *s) FL_OVERRIDE { - return check_x_(d, d.bx, s->x()); - } - void draw(Fd_Snap_Data &d) FL_OVERRIDE { - if (best_match) draw_left_brace(best_match); - }; -}; -Fd_Snap_Siblings_Left_Same snap_siblings_left_same; - -/** - Check if widgets touch left to right, or have a user selected gap left to right. - */ -class Fd_Snap_Siblings_Left : public Fd_Snap_Sibling { -public: - Fd_Snap_Siblings_Left() { type = 1; mask = FD_LEFT|FD_DRAG; } - int sibling_check(Fd_Snap_Data &d, Fl_Widget *s) FL_OVERRIDE { - return fd_min(check_x_(d, d.bx, s->x()+s->w()), - check_x_(d, d.bx, s->x()+s->w()+layout->widget_gap_x) ); - } - void draw(Fd_Snap_Data &d) FL_OVERRIDE { - if (best_match) draw_right_brace(best_match); - }; -}; -Fd_Snap_Siblings_Left snap_siblings_left; - -class Fd_Snap_Siblings_Right_Same : public Fd_Snap_Sibling { -public: - Fd_Snap_Siblings_Right_Same() { type = 1; mask = FD_RIGHT|FD_DRAG; } - int sibling_check(Fd_Snap_Data &d, Fl_Widget *s) FL_OVERRIDE { - return check_x_(d, d.br, s->x()+s->w()); - } - void draw(Fd_Snap_Data &d) FL_OVERRIDE { - if (best_match) draw_right_brace(best_match); - }; -}; -Fd_Snap_Siblings_Right_Same snap_siblings_right_same; - -class Fd_Snap_Siblings_Right : public Fd_Snap_Sibling { -public: - Fd_Snap_Siblings_Right() { type = 1; mask = FD_RIGHT|FD_DRAG; } - int sibling_check(Fd_Snap_Data &d, Fl_Widget *s) FL_OVERRIDE { - return fd_min(check_x_(d, d.br, s->x()), - check_x_(d, d.br, s->x()-layout->widget_gap_x)); - } - void draw(Fd_Snap_Data &d) FL_OVERRIDE { - if (best_match) draw_left_brace(best_match); - }; -}; -Fd_Snap_Siblings_Right snap_siblings_right; - -class Fd_Snap_Siblings_Top_Same : public Fd_Snap_Sibling { -public: - Fd_Snap_Siblings_Top_Same() { type = 2; mask = FD_TOP|FD_DRAG; } - int sibling_check(Fd_Snap_Data &d, Fl_Widget *s) FL_OVERRIDE { - return check_y_(d, d.by, s->y()); - } - void draw(Fd_Snap_Data &d) FL_OVERRIDE { - if (best_match) draw_top_brace(best_match); - }; -}; -Fd_Snap_Siblings_Top_Same snap_siblings_top_same; - -class Fd_Snap_Siblings_Top : public Fd_Snap_Sibling { -public: - Fd_Snap_Siblings_Top() { type = 2; mask = FD_TOP|FD_DRAG; } - int sibling_check(Fd_Snap_Data &d, Fl_Widget *s) FL_OVERRIDE { - return fd_min(check_y_(d, d.by, s->y()+s->h()), - check_y_(d, d.by, s->y()+s->h()+layout->widget_gap_y)); - } - void draw(Fd_Snap_Data &d) FL_OVERRIDE { - if (best_match) draw_bottom_brace(best_match); - }; -}; -Fd_Snap_Siblings_Top snap_siblings_top; - -class Fd_Snap_Siblings_Bottom_Same : public Fd_Snap_Sibling { -public: - Fd_Snap_Siblings_Bottom_Same() { type = 2; mask = FD_BOTTOM|FD_DRAG; } - int sibling_check(Fd_Snap_Data &d, Fl_Widget *s) FL_OVERRIDE { - return check_y_(d, d.bt, s->y()+s->h()); - } - void draw(Fd_Snap_Data &d) FL_OVERRIDE { - if (best_match) draw_bottom_brace(best_match); - }; -}; -Fd_Snap_Siblings_Bottom_Same snap_siblings_bottom_same; - -class Fd_Snap_Siblings_Bottom : public Fd_Snap_Sibling { -public: - Fd_Snap_Siblings_Bottom() { type = 2; mask = FD_BOTTOM|FD_DRAG; } - int sibling_check(Fd_Snap_Data &d, Fl_Widget *s) FL_OVERRIDE { - return fd_min(check_y_(d, d.bt, s->y()), - check_y_(d, d.bt, s->y()-layout->widget_gap_y)); - } - void draw(Fd_Snap_Data &d) FL_OVERRIDE { - if (best_match) draw_top_brace(best_match); - }; -}; -Fd_Snap_Siblings_Bottom snap_siblings_bottom; - - -// ------ widget snapping ---------------------------------------------- MARK: - - -/** - Snap horizontal resizing to min_w or min_w and a multiple of inc_w. - */ -class Fd_Snap_Widget_Ideal_Width : public Fd_Snap_Action { -public: - Fd_Snap_Widget_Ideal_Width() { type = 1; mask = FD_LEFT|FD_RIGHT; } - void check(Fd_Snap_Data &d) FL_OVERRIDE { - clr(); - if (!d.wgt) return; - int iw = 15, ih = 15; - d.wgt->ideal_size(iw, ih); - if (d.drag == FD_RIGHT) { - check_x_(d, d.br, d.bx+iw); - iw = layout->widget_min_w; - if (iw > 0) iw = nearest(d.br-d.bx+d.dx, layout->widget_min_w, layout->widget_inc_w); - check_x_(d, d.br, d.bx+iw); - } else { - check_x_(d, d.bx, d.br-iw); - iw = layout->widget_min_w; - if (iw > 0) iw = nearest(d.br-d.bx-d.dx, layout->widget_min_w, layout->widget_inc_w); - check_x_(d, d.bx, d.br-iw); - } - } - void draw(Fd_Snap_Data &d) FL_OVERRIDE { - draw_width(d.bx, d.bt+7, d.br, 0); - }; -}; -Fd_Snap_Widget_Ideal_Width snap_widget_ideal_width; - -class Fd_Snap_Widget_Ideal_Height : public Fd_Snap_Action { -public: - Fd_Snap_Widget_Ideal_Height() { type = 2; mask = FD_TOP|FD_BOTTOM; } - void check(Fd_Snap_Data &d) FL_OVERRIDE { - clr(); - if (!d.wgt) return; - int iw, ih; - d.wgt->ideal_size(iw, ih); - if (d.drag == FD_BOTTOM) { - check_y_(d, d.bt, d.by+ih); - ih = layout->widget_min_h; - if (ih > 0) ih = nearest(d.bt-d.by+d.dy, layout->widget_min_h, layout->widget_inc_h); - check_y_(d, d.bt, d.by+ih); - } else { - check_y_(d, d.by, d.bt-ih); - ih = layout->widget_min_h; - if (ih > 0) ih = nearest(d.bt-d.by-d.dy, layout->widget_min_h, layout->widget_inc_h); - check_y_(d, d.by, d.bt-ih); - } - } - void draw(Fd_Snap_Data &d) FL_OVERRIDE { - draw_height(d.br+7, d.by, d.bt, 0); - }; -}; -Fd_Snap_Widget_Ideal_Height snap_widget_ideal_height; - -// ---- snap actions list ---------------------------------------------- MARK: - - -/** - /brief The list of all snap actions available to FLUID. - New snap actions can be appended to the list. If multiple snap actions - with different coordinates, but the same snap distance are found, the last - action in the list wins. All snap actions with the same distance and same - winning coordinates are drawn in the overlay plane. - */ -Fd_Snap_Action *Fd_Snap_Action::list[] = { - &snap_left_window_edge, - &snap_right_window_edge, - &snap_top_window_edge, - &snap_bottom_window_edge, - - &snap_left_window_margin, - &snap_right_window_margin, - &snap_top_window_margin, - &snap_bottom_window_margin, - - &snap_window_grid, - &snap_group_grid, - - &snap_left_group_edge, - &snap_right_group_edge, - &snap_top_group_edge, - &snap_bottom_group_edge, - - &snap_left_group_margin, - &snap_right_group_margin, - &snap_top_group_margin, - &snap_bottom_group_margin, - - &snap_top_tabs_margin, - &snap_bottom_tabs_margin, - - &snap_siblings_left_same, &snap_siblings_left, - &snap_siblings_right_same, &snap_siblings_right, - &snap_siblings_top_same, &snap_siblings_top, - &snap_siblings_bottom_same, &snap_siblings_bottom, - - &snap_widget_ideal_width, - &snap_widget_ideal_height, - - NULL -}; - -// ---- draw alignment marks ------------------------------------------- MARK: - - -static void draw_v_arrow(int x, int y1, int y2) { - int dy = (y1>y2) ? -1 : 1 ; - fl_yxline(x, y1, y2); - fl_xyline(x-4, y2, x+4); - fl_line(x-2, y2-dy*5, x, y2-dy); - fl_line(x+2, y2-dy*5, x, y2-dy); -} - -static void draw_h_arrow(int x1, int y, int x2) { - int dx = (x1>x2) ? -1 : 1 ; - fl_xyline(x1, y, x2); - fl_yxline(x2, y-4, y+4); - fl_line(x2-dx*5, y-2, x2-dx, y); - fl_line(x2-dx*5, y+2, x2-dx, y); -} - -static void draw_top_brace(const Fl_Widget *w) { - int x = w->as_window() ? 0 : w->x(); - int y = w->as_window() ? 0 : w->y(); - fl_yxline(x, y-2, y+6); - fl_yxline(x+w->w()-1, y-2, y+6); - fl_xyline(x-2, y, x+w->w()+1); -} - -static void draw_left_brace(const Fl_Widget *w) { - int x = w->as_window() ? 0 : w->x(); - int y = w->as_window() ? 0 : w->y(); - fl_xyline(x-2, y, x+6); - fl_xyline(x-2, y+w->h()-1, x+6); - fl_yxline(x, y-2, y+w->h()+1); -} - -static void draw_right_brace(const Fl_Widget *w) { - int x = w->as_window() ? w->w() - 1 : w->x() + w->w() - 1; - int y = w->as_window() ? 0 : w->y(); - fl_xyline(x-6, y, x+2); - fl_xyline(x-6, y+w->h()-1, x+2); - fl_yxline(x, y-2, y+w->h()+1); -} - -static void draw_bottom_brace(const Fl_Widget *w) { - int x = w->as_window() ? 0 : w->x(); - int y = w->as_window() ? w->h() - 1 : w->y() + w->h() - 1; - fl_yxline(x, y-6, y+2); - fl_yxline(x+w->w()-1, y-6, y+2); - fl_xyline(x-2, y, x+w->w()+1); -} - -void draw_height(int x, int y, int b, Fl_Align a) { - char buf[16]; - int h = b - y; - sprintf(buf, "%d", h); - fl_font(FL_HELVETICA, 9); - int lw = (int)fl_width(buf); - int lx; - - b --; - if (h < 30) { - // Move height to the side... - if (a == FL_ALIGN_LEFT) lx = x - lw - 2; - else lx = x + 2; - fl_yxline(x, y, b); - } else { - // Put height inside the arrows... - if (a == FL_ALIGN_LEFT) lx = x - lw + 2; - else lx = x - lw / 2; - fl_yxline(x, y, y + (h - 11) / 2); - fl_yxline(x, y + (h + 11) / 2, b); - } - - // Draw the height... - fl_draw(buf, lx, y + (h + 7) / 2); - - // Draw the arrowheads... - fl_line(x-2, y+5, x, y+1, x+2, y+5); - fl_line(x-2, b-5, x, b-1, x+2, b-5); - - // Draw the end lines... - fl_xyline(x - 4, y, x + 4); - fl_xyline(x - 4, b, x + 4); -} - -void draw_width(int x, int y, int r, Fl_Align a) { - char buf[16]; - int w = r-x; - sprintf(buf, "%d", w); - fl_font(FL_HELVETICA, 9); - int lw = (int)fl_width(buf); - int ly = y + 4; - - r--; - - if (lw > (w - 20)) { - // Move width above/below the arrows... - if (a == FL_ALIGN_TOP) ly -= 10; - else ly += 10; - - fl_xyline(x, y, r); - } else { - // Put width inside the arrows... - fl_xyline(x, y, x + (w - lw - 2) / 2); - fl_xyline(x + (w + lw + 2) / 2, y, r); - } - - // Draw the width... - fl_draw(buf, x + (w - lw) / 2, ly-2); - - // Draw the arrowheads... - fl_line(x+5, y-2, x+1, y, x+5, y+2); - fl_line(r-5, y-2, r-1, y, r-5, y+2); - - // Draw the end lines... - fl_yxline(x, y - 4, y + 4); - fl_yxline(r, y - 4, y + 4); -} - -static void draw_grid(int x, int y, int dx, int dy) { - int dx2 = 1, dy2 = 1; - const int n = 2; - for (int i=-n; i<=n; i++) { - for (int j=-n; j<=n; j++) { - if (abs(i)+abs(j) < 4) { - int xx = x + i*dx , yy = y + j*dy; - fl_xyline(xx-dx2, yy, xx+dx2); - fl_yxline(xx, yy-dy2, yy+dy2); - } - } - } -} diff --git a/fluid/app/Fd_Snap_Action.h b/fluid/app/Fd_Snap_Action.h deleted file mode 100644 index 0c98b56b8..000000000 --- a/fluid/app/Fd_Snap_Action.h +++ /dev/null @@ -1,193 +0,0 @@ -// -// Snap action header file for the Fast Light Tool Kit (FLTK). -// -// Copyright 2023 by Bill Spitzak and others. -// -// This library is free software. Distribution and use rights are outlined in -// the file "COPYING" which should have been included with this file. If this -// file is missing or damaged, see the license at: -// -// https://www.fltk.org/COPYING.php -// -// Please see the following page on how to report bugs and issues: -// -// https://www.fltk.org/bugs.php -// - -#ifndef _FLUID_FD_SNAP_ACTION_H -#define _FLUID_FD_SNAP_ACTION_H - -#include "app/fluid.h" -#include "nodes/Fl_Window_Type.h" - -#include - -struct Fl_Menu_Item; - -extern Fl_Menu_Item main_layout_submenu_[]; - -/** - \brief Collection of layout settings. - - Presets contain default fonts and font sizes for labels and text. They - can be used to guide widget positions using margins, grids, and gap sizes. - There are three Presets available in one Suite, marked "application", - "dialog", and "toolbox". - */ -class Fd_Layout_Preset { -public: - int left_window_margin; ///< gap between the window border and the widget - int right_window_margin; - int top_window_margin; - int bottom_window_margin; - int window_grid_x; ///< a regular grid across the window with its origin in the top left window corner - int window_grid_y; - - int left_group_margin; ///< gap between the border of a widget and its parent group - int right_group_margin; - int top_group_margin; - int bottom_group_margin; - int group_grid_x; ///< a regular grid across the group with its origin in the top left group corner - int group_grid_y; - - int top_tabs_margin; ///< preferred top edge tab size inside Fl_Tabs - int bottom_tabs_margin; ///< preferred bottom edge tab size inside Fl_Tabs - - int widget_min_w; ///< minimum widget width - int widget_inc_w; ///< widget width increments starting from widget_min_w - int widget_gap_x; ///< preferred horizontal gap between widgets - int widget_min_h; - int widget_inc_h; - int widget_gap_y; - - int labelfont; ///< preferred font for labels - int labelsize; ///< preferred size for labels - int textfont; ///< preferred font for text elements - int textsize; ///< preferred size for text elements - - void write(Fl_Preferences &prefs); - void read(Fl_Preferences &prefs); - void write(fld::io::Project_Writer*); - void read(fld::io::Project_Reader*); - - int textsize_not_null(); -}; - -extern Fd_Layout_Preset *layout; - -/** - \brief A collection of layout presets. - - A suite of layout presets is designed to cover various use cases when - designing UI layouts for applications. - There are three Presets available in one Suite, marked "application", - "dialog", and "toolbox". - */ -class Fd_Layout_Suite { -public: - char *name_; ///< name of the suite - char *menu_label; ///< label text used in pulldown menu - Fd_Layout_Preset *layout[3]; ///< presets for application, dialog, and toolbox windows - Fd_Tool_Store storage_; ///< storage location (see FD_STORE_INTERNAL, etc.) - void write(Fl_Preferences &prefs); - void read(Fl_Preferences &prefs); - void write(fld::io::Project_Writer*); - void read(fld::io::Project_Reader*); - void update_label(); - void storage(Fd_Tool_Store s) { storage_ = s; update_label(); } - void name(const char *n); - void init(); - ~Fd_Layout_Suite(); -public: - -}; - -/** - \brief Manage all layout suites that are available to the user. - - FLUID has two built-in suites. More suites can be cloned or added and stored - as a user preference, as part of an .fl project file, or in a separate file - for import/export and sharing. - */ -class Fd_Layout_List { -public: - Fl_Menu_Item *main_menu_; - Fl_Menu_Item *choice_menu_; - Fd_Layout_Suite *list_; - int list_size_; - int list_capacity_; - bool list_is_static_; - int current_suite_; - int current_preset_; - std::string filename_; -public: - Fd_Layout_List(); - ~Fd_Layout_List(); - void update_dialogs(); - void update_menu_labels(); - int current_suite() const { return current_suite_; } - void current_suite(int ix); - void current_suite(std::string); - int current_preset() const { return current_preset_; } - void current_preset(int ix); - Fd_Layout_Suite &operator[](int ix) { return list_[ix]; } - int add(const char *name); - void rename(const char *name); - void capacity(int); - - int load(const std::string &filename); - int save(const std::string &filename); - void write(Fl_Preferences &prefs, Fd_Tool_Store storage); - void read(Fl_Preferences &prefs, Fd_Tool_Store storage); - void write(fld::io::Project_Writer*); - void read(fld::io::Project_Reader*); - int add(Fd_Layout_Suite*); - void remove(int index); - void remove_all(Fd_Tool_Store storage); - Fd_Layout_Preset *at(int); - int size(); -}; - -extern Fd_Layout_List g_layout_list; - -/** - \brief Structure holding all the data to perform interactive alignment operations. - */ -typedef struct Fd_Snap_Data { - int dx, dy; ///< distance of the mouse from its initial PUSH event - int bx, by, br, bt; ///< bounding box of the original push event or current bounding box when drawing - int drag; ///< drag event mask - int x_dist, y_dist; ///< current closest snapping distance in x and y - int dx_out, dy_out; ///< current closest snapping point as a delta - Fl_Widget_Type *wgt; ///< first selected widget - Fl_Window_Type *win; ///< window that handles the drag action - int ex_out, ey_out; ///< chosen snap position -} Fd_Snap_Data; - -/** - \brief Find points of interest when moving the bounding box of all selected widgets. - */ -class Fd_Snap_Action { -protected: - int check_x_(Fd_Snap_Data &d, int x_ref, int x_snap); - int check_y_(Fd_Snap_Data &d, int y_ref, int y_snap); - void check_x_y_(Fd_Snap_Data &d, int x_ref, int x_snap, int y_ref, int y_snap); - void clr() { ex = dx = 0x7fff; } -public: - int ex, ey, dx, dy, type, mask; - Fd_Snap_Action() : ex(0x7fff), ey(0x7fff), dx(128), dy(128), type(0), mask(0) { } - virtual ~Fd_Snap_Action() { } - virtual void check(Fd_Snap_Data &d) = 0; - virtual void draw(Fd_Snap_Data &d) { } - virtual bool matches(Fd_Snap_Data &d); -public: - static int eex, eey; - static Fd_Snap_Action *list[]; - static void check_all(Fd_Snap_Data &d); - static void draw_all(Fd_Snap_Data &d); - static void get_resize_stepsize(int &x_step, int &y_step); - static void get_move_stepsize(int &x_step, int &y_step); - static void better_size(int &w, int &h); -}; - -#endif // _FLUID_FD_SNAP_ACTION_H diff --git a/fluid/app/Fluid_Image.cxx b/fluid/app/Fluid_Image.cxx deleted file mode 100644 index a79ac6c35..000000000 --- a/fluid/app/Fluid_Image.cxx +++ /dev/null @@ -1,413 +0,0 @@ -// -// Pixmap (and other images) label support for the Fast Light Tool Kit (FLTK). -// -// Copyright 1998-2022 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 "app/Fluid_Image.h" - -#include "app/fluid.h" -#include "io/Project_Reader.h" -#include "io/Project_Writer.h" -#include "io/Code_Writer.h" -#include "nodes/Fl_Group_Type.h" -#include "nodes/Fl_Window_Type.h" -#include "tools/filename.h" - -#include -#include -#include -#include -#include // fl_fopen() -#include -#include -#include -#include "../src/flstring.h" - -#include -#include -#include -#include - -void Fluid_Image::image(Fl_Widget *o) { - if (o->window() != o) o->image(img); -} - -void Fluid_Image::deimage(Fl_Widget *o) { - if (o->window() != o) o->deimage(img); -} - -/** Write the contents of the name() file as binary source code. - \param fmt short name of file contents for error message - \return 0 if the file could not be opened or read */ -size_t Fluid_Image::write_static_binary(fld::io::Code_Writer& f, const char* fmt) { - size_t nData = 0; - enter_project_dir(); - FILE *in = fl_fopen(name(), "rb"); - leave_project_dir(); - if (!in) { - write_file_error(f, fmt); - return 0; - } else { - fseek(in, 0, SEEK_END); - nData = ftell(in); - fseek(in, 0, SEEK_SET); - if (nData) { - char *data = (char*)calloc(nData, 1); - if (fread(data, nData, 1, in)==0) { /* ignore */ } - f.write_cdata(data, (int)nData); - free(data); - } - fclose(in); - } - return nData; -} - -/** Write the contents of the name() file as textual source code. - \param fmt short name of file contents for error message - \return 0 if the file could not be opened or read */ -size_t Fluid_Image::write_static_text(fld::io::Code_Writer& f, const char* fmt) { - size_t nData = 0; - enter_project_dir(); - FILE *in = fl_fopen(name(), "rb"); - leave_project_dir(); - if (!in) { - write_file_error(f, fmt); - return 0; - } else { - fseek(in, 0, SEEK_END); - nData = ftell(in); - fseek(in, 0, SEEK_SET); - if (nData) { - char *data = (char*)calloc(nData+1, 1); - if (fread(data, nData, 1, in)==0) { /* ignore */ } - f.write_cstring(data, (int)nData); - free(data); - } - fclose(in); - } - return nData; -} - -void Fluid_Image::write_static_rgb(fld::io::Code_Writer& f, const char* idata_name) { - // Write image data... - f.write_c("\n"); - f.write_c_once("#include \n"); - f.write_c("static const unsigned char %s[] =\n", idata_name); - const int extra_data = img->ld() ? (img->ld()-img->w()*img->d()) : 0; - f.write_cdata(img->data()[0], (img->w() * img->d() + extra_data) * img->h()); - f.write_c(";\n"); - write_initializer(f, "Fl_RGB_Image", "%s, %d, %d, %d, %d", idata_name, img->w(), img->h(), img->d(), img->ld()); -} - -/** - Write the static image data into the soutrce file. - - If \p compressed is set, write the original image format, which requires - linking the matching image reader at runtime, or if we want to store the raw - uncompressed pixels, which makes images fast, needs no reader, but takes a - lot of memory (current default for PNG) - - \param compressed write data in the original compressed file format - */ -void Fluid_Image::write_static(fld::io::Code_Writer& f, int compressed) { - if (!img) return; - const char *idata_name = f.unique_id(this, "idata", fl_filename_name(name()), 0); - function_name_ = f.unique_id(this, "image", fl_filename_name(name()), 0); - - if (is_animated_gif_) { - // Write animated gif image data... - f.write_c("\n"); - f.write_c_once("#include \n"); - f.write_c("static const unsigned char %s[] =\n", idata_name); - size_t nData = write_static_binary(f, "AnimGIF"); - f.write_c(";\n"); - write_initializer(f, "Fl_Anim_GIF_Image", "\"%s\", %s, %d", fl_filename_name(name()), idata_name, nData); - } else if (compressed && fl_ascii_strcasecmp(fl_filename_ext(name()), ".gif")==0) { - // Write gif image data... - f.write_c("\n"); - f.write_c_once("#include \n"); - f.write_c("static const unsigned char %s[] =\n", idata_name); - size_t nData = write_static_binary(f, "GIF"); - f.write_c(";\n"); - write_initializer(f, "Fl_GIF_Image", "\"%s\", %s, %d", fl_filename_name(name()), idata_name, nData); - } else if (compressed && fl_ascii_strcasecmp(fl_filename_ext(name()), ".bmp")==0) { - // Write bmp image data... - f.write_c("\n"); - f.write_c_once("#include \n"); - f.write_c("static const unsigned char %s[] =\n", idata_name); - size_t nData = write_static_binary(f, "BMP"); - f.write_c(";\n"); - write_initializer(f, "Fl_BMP_Image", "\"%s\", %s, %d", fl_filename_name(name()), idata_name, nData); - } else if (img->count() > 1) { - // Write Pixmap data... - f.write_c("\n"); - f.write_c_once("#include \n"); - f.write_c("static const char *%s[] = {\n", idata_name); - f.write_cstring(img->data()[0], (int)strlen(img->data()[0])); - - int i; - int ncolors, chars_per_color; - sscanf(img->data()[0], "%*d%*d%d%d", &ncolors, &chars_per_color); - - if (ncolors < 0) { - f.write_c(",\n"); - f.write_cstring(img->data()[1], ncolors * -4); - i = 2; - } else { - for (i = 1; i <= ncolors; i ++) { - f.write_c(",\n"); - f.write_cstring(img->data()[i], (int)strlen(img->data()[i])); - } - } - for (; i < img->count(); i ++) { - f.write_c(",\n"); - f.write_cstring(img->data()[i], img->w() * chars_per_color); - } - f.write_c("\n};\n"); - write_initializer(f, "Fl_Pixmap", "%s", idata_name); - } else if (img->d() == 0) { - // Write Bitmap data... - f.write_c("\n"); - f.write_c_once("#include \n"); - f.write_c("static const unsigned char %s[] =\n", idata_name); - f.write_cdata(img->data()[0], ((img->w() + 7) / 8) * img->h()); - f.write_c(";\n"); - write_initializer(f, "Fl_Bitmap", "%s, %d, %d, %d", idata_name, ((img->w() + 7) / 8) * img->h(), img->w(), img->h()); - } else if (compressed && fl_ascii_strcasecmp(fl_filename_ext(name()), ".jpg")==0) { - // Write jpeg image data... - f.write_c("\n"); - f.write_c_once("#include \n"); - f.write_c("static const unsigned char %s[] =\n", idata_name); - size_t nData = write_static_binary(f, "JPEG"); - f.write_c(";\n"); - write_initializer(f, "Fl_JPEG_Image", "\"%s\", %s, %d", fl_filename_name(name()), idata_name, nData); - } else if (compressed && fl_ascii_strcasecmp(fl_filename_ext(name()), ".png")==0) { - // Write png image data... - f.write_c("\n"); - f.write_c_once("#include \n"); - f.write_c("static const unsigned char %s[] =\n", idata_name); - size_t nData = write_static_binary(f, "PNG"); - f.write_c(";\n"); - write_initializer(f, "Fl_PNG_Image", "\"%s\", %s, %d", fl_filename_name(name()), idata_name, nData); - } -#ifdef FLTK_USE_SVG - else if (fl_ascii_strcasecmp(fl_filename_ext(name()), ".svg")==0 || fl_ascii_strcasecmp(fl_filename_ext(name()), ".svgz")==0) { - bool gzipped = (strcmp(fl_filename_ext(name()), ".svgz") == 0); - // Write svg image data... - if (compressed) { - f.write_c("\n"); - f.write_c_once("#include \n"); - if (gzipped) { - f.write_c("static const unsigned char %s[] =\n", idata_name); - size_t nData = write_static_binary(f, "SVGZ"); - f.write_c(";\n"); - write_initializer(f, "Fl_SVG_Image", "\"%s\", %s, %ld", fl_filename_name(name()), idata_name, nData); - } else { - f.write_c("static const char %s[] =\n", idata_name); - write_static_text(f, "SVG"); - f.write_c(";\n"); - write_initializer(f, "Fl_SVG_Image", "\"%s\", %s", fl_filename_name(name()), idata_name); - } - } else { - // if FLUID runs from the command line, make sure that the image is not - // only loaded but also rasterized, so we can write the RGB image data - Fl_RGB_Image* rgb_image = NULL; - Fl_SVG_Image* svg_image = NULL; - if (img->d()>0) - rgb_image = (Fl_RGB_Image*)img->image(); - if (rgb_image) - svg_image = rgb_image->as_svg_image(); - if (svg_image) { - svg_image->resize(svg_image->w(), svg_image->h()); - write_static_rgb(f, idata_name); - } else { - write_file_error(f, "RGB_from_SVG"); - } - } - } -#endif // FLTK_USE_SVG - else { - write_static_rgb(f, idata_name); - } -} - -void Fluid_Image::write_file_error(fld::io::Code_Writer& f, const char *fmt) { - f.write_c("#warning Cannot read %s file \"%s\": %s\n", fmt, name(), strerror(errno)); - enter_project_dir(); - f.write_c("// Searching in path \"%s\"\n", fl_getcwd(0, FL_PATH_MAX)); - leave_project_dir(); -} - -void Fluid_Image::write_initializer(fld::io::Code_Writer& f, const char *type_name, const char *format, ...) { - /* Outputs code that returns (and initializes if needed) an Fl_Image as follows: - static Fl_Image *'function_name_'() { - static Fl_Image *image = NULL; - if (!image) - image = new 'type_name'('product of format and remaining args'); - return image; - } */ - va_list ap; - va_start(ap, format); - f.write_c("static Fl_Image *%s() {\n", function_name_); - if (is_animated_gif_) - f.write_c("%sFl_GIF_Image::animate = true;\n", f.indent(1)); - f.write_c("%sstatic Fl_Image *image = NULL;\n", f.indent(1)); - f.write_c("%sif (!image)\n", f.indent(1)); - f.write_c("%simage = new %s(", f.indent(2), type_name); - f.vwrite_c(format, ap); - f.write_c(");\n"); - f.write_c("%sreturn image;\n", f.indent(1)); - f.write_c("}\n"); - va_end(ap); -} - -void Fluid_Image::write_code(fld::io::Code_Writer& f, int bind, const char *var, int inactive) { - /* Outputs code that attaches an image to an Fl_Widget or Fl_Menu_Item. - This code calls a function output before by Fluid_Image::write_initializer() */ - if (img) { - f.write_c("%s%s->%s%s( %s() );\n", f.indent(), var, bind ? "bind_" : "", inactive ? "deimage" : "image", function_name_); - if (is_animated_gif_) - f.write_c("%s((Fl_Anim_GIF_Image*)(%s()))->canvas(%s, Fl_Anim_GIF_Image::DONT_RESIZE_CANVAS);\n", f.indent(), function_name_, var); - } -} - -void Fluid_Image::write_inline(fld::io::Code_Writer& f, int inactive) { - if (img) - f.write_c("%s()", function_name_); -} - - -//////////////////////////////////////////////////////////////// - -static Fluid_Image** images = 0; // sorted list -static int numimages = 0; -static int tablesize = 0; - -Fluid_Image* Fluid_Image::find(const char *iname) { - if (!iname || !*iname) return 0; - - // first search to see if it exists already: - int a = 0; - int b = numimages; - while (a < b) { - int c = (a+b)/2; - int i = strcmp(iname,images[c]->name_); - if (i < 0) b = c; - else if (i > 0) a = c+1; - else return images[c]; - } - - // no, so now see if the file exists: - - enter_project_dir(); - FILE *f = fl_fopen(iname,"rb"); - if (!f) { - if (batch_mode) - fprintf(stderr, "Can't open image file:\n%s\n%s",iname,strerror(errno)); - else - fl_message("Can't open image file:\n%s\n%s",iname,strerror(errno)); - leave_project_dir(); - return 0; - } - fclose(f); - - Fluid_Image *ret = new Fluid_Image(iname); - - if (!ret->img || !ret->img->w() || !ret->img->h()) { - delete ret; - ret = 0; - if (batch_mode) - fprintf(stderr, "Can't read image file:\n%s\nunrecognized image format",iname); - else - fl_message("Can't read image file:\n%s\nunrecognized image format",iname); - } - leave_project_dir(); - if (!ret) return 0; - - // make a new entry in the table: - numimages++; - if (numimages > tablesize) { - tablesize = tablesize ? 2*tablesize : 16; - if (images) images = (Fluid_Image**)realloc(images, tablesize*sizeof(Fluid_Image*)); - else images = (Fluid_Image**)malloc(tablesize*sizeof(Fluid_Image*)); - } - for (b = numimages-1; b > a; b--) images[b] = images[b-1]; - images[a] = ret; - - return ret; -} - -Fluid_Image::Fluid_Image(const char *iname) - : is_animated_gif_(false) -{ - name_ = fl_strdup(iname); - written = 0; - refcount = 0; - img = Fl_Shared_Image::get(iname); - if (img && iname) { - const char *ext = fl_filename_ext(iname); - if (fl_ascii_strcasecmp(ext, ".gif")==0) { - int fc = Fl_Anim_GIF_Image::frame_count(iname); - if (fc > 0) is_animated_gif_ = true; - } - } - function_name_ = NULL; -} - -void Fluid_Image::increment() { - ++refcount; -} - -void Fluid_Image::decrement() { - --refcount; - if (refcount > 0) return; - delete this; -} - -Fluid_Image::~Fluid_Image() { - int a; - if (images) { - for (a = 0; arelease(); - free((void*)name_); -} - -//////////////////////////////////////////////////////////////// - -const char *ui_find_image_name; -Fluid_Image *ui_find_image(const char *oldname) { - enter_project_dir(); - fl_file_chooser_ok_label("Use Image"); - const char *name = fl_file_chooser("Image?", - "Image Files (*.{bm,bmp,gif,jpg,pbm,pgm,png,ppm,xbm,xpm,svg" -#ifdef HAVE_LIBZ - ",svgz" -#endif - "})", - oldname,1); - fl_file_chooser_ok_label(NULL); - ui_find_image_name = name; - Fluid_Image *ret = (name && *name) ? Fluid_Image::find(name) : 0; - leave_project_dir(); - return ret; -} diff --git a/fluid/app/Fluid_Image.h b/fluid/app/Fluid_Image.h deleted file mode 100644 index f095e83ce..000000000 --- a/fluid/app/Fluid_Image.h +++ /dev/null @@ -1,60 +0,0 @@ -// -// Image header file for the Fast Light Tool Kit (FLTK). -// -// This class stores the image labels for widgets in fluid. This is -// not a class in FLTK itself, and will produce different types of -// code depending on what the image type is. -// -// Copyright 1998-2010 by Bill Spitzak and others. -// -// This library is free software. Distribution and use rights are outlined in -// the file "COPYING" which should have been included with this file. If this -// file is missing or damaged, see the license at: -// -// https://www.fltk.org/COPYING.php -// -// Please see the following page on how to report bugs and issues: -// -// https://www.fltk.org/bugs.php -// - -#ifndef FLUID_IMAGE_H -#define FLUID_IMAGE_H - -#include "io/Code_Writer.h" - -#include - -class Fluid_Image { - bool is_animated_gif_; - const char *name_; - int refcount; - Fl_Shared_Image *img; - const char *function_name_; -protected: - Fluid_Image(const char *name); // no public constructor - ~Fluid_Image(); // no public destructor - size_t write_static_binary(fld::io::Code_Writer& f, const char* fmt); - size_t write_static_text(fld::io::Code_Writer& f, const char* fmt); - void write_static_rgb(fld::io::Code_Writer& f, const char* idata_name); -public: - int written; - static Fluid_Image* find(const char *); - void decrement(); // reference counting & automatic free - void increment(); - void image(Fl_Widget *); // set the image of this widget - void deimage(Fl_Widget *); // set the deimage of this widget - void write_static(fld::io::Code_Writer& f, int compressed); - void write_initializer(fld::io::Code_Writer& f, const char *type_name, const char *format, ...); - void write_code(fld::io::Code_Writer& f, int bind, const char *var, int inactive = 0); - void write_inline(fld::io::Code_Writer& f, int inactive = 0); - void write_file_error(fld::io::Code_Writer& f, const char *fmt); - const char *name() const {return name_;} -}; - -// pop up file chooser and return a legal image selected by user, -// or zero for any errors: -Fluid_Image *ui_find_image(const char *); -extern const char *ui_find_image_name; - -#endif diff --git a/fluid/app/Image_Asset.cxx b/fluid/app/Image_Asset.cxx new file mode 100644 index 000000000..a001f2291 --- /dev/null +++ b/fluid/app/Image_Asset.cxx @@ -0,0 +1,532 @@ +// +// Image Helper code for the Fast Light Tool Kit (FLTK). +// +// Copyright 1998-2025 by Bill Spitzak and others. +// +// This library is free software. Distribution and use rights are outlined in +// the file "COPYING" which should have been included with this file. If this +// file is missing or damaged, see the license at: +// +// https://www.fltk.org/COPYING.php +// +// Please see the following page on how to report bugs and issues: +// +// https://www.fltk.org/bugs.php +// + +#include "app/Image_Asset.h" + +#include "Fluid.h" +#include "io/Project_Reader.h" +#include "io/Project_Writer.h" +#include "io/Code_Writer.h" +#include "nodes/Group_Node.h" +#include "nodes/Window_Node.h" +#include "tools/filename.h" + +#include +#include +#include +#include +#include // fl_fopen() +#include +#include +#include +#include "../src/flstring.h" + +#include +#include +#include +#include +#include +#include + + +/** + \brief A map of all image assets. + \todo This is a global variable, but should be associated + with a project instead. + */ +static std::map image_asset_map; + + +/** + \brief Write the contents of the image file as binary source code. + + Write the contents of the image file as C++ code, so the image is available in + the target app in the original binary format, for example: + ``` + { 1, 2, 3, ...} + ``` + \param f Write code to this C++ source code file + \param fmt short name of file contents for error message + \return 0 if the file could not be opened or read + */ +size_t Image_Asset::write_static_binary(fld::io::Code_Writer& f, const char* fmt) { + size_t nData = 0; + Fluid.proj.enter_project_dir(); + FILE *in = fl_fopen(filename(), "rb"); + Fluid.proj.leave_project_dir(); + if (!in) { + write_file_error(f, fmt); + return 0; + } else { + fseek(in, 0, SEEK_END); + nData = ftell(in); + fseek(in, 0, SEEK_SET); + if (nData) { + char *data = (char*)calloc(nData, 1); + if (fread(data, nData, 1, in)==0) { /* ignore */ } + f.write_cdata(data, (int)nData); + free(data); + } + fclose(in); + } + return nData; +} + + +/** + \brief Write the contents of the image file as text with escaped special characters. + + This function is only useful for writing out image formats that are ASCII text + based, like svg and pixmaps. Other formats should use write_static_binary(). + + \param f Write code to this C++ source code file + \param fmt short name of file contents for error message + \return 0 if the file could not be opened or read + */ +size_t Image_Asset::write_static_text(fld::io::Code_Writer& f, const char* fmt) { + size_t nData = 0; + Fluid.proj.enter_project_dir(); + FILE *in = fl_fopen(filename(), "rb"); + Fluid.proj.leave_project_dir(); + if (!in) { + write_file_error(f, fmt); + return 0; + } else { + fseek(in, 0, SEEK_END); + nData = ftell(in); + fseek(in, 0, SEEK_SET); + if (nData) { + char *data = (char*)calloc(nData+1, 1); + if (fread(data, nData, 1, in)==0) { /* ignore */ } + f.write_cstring(data, (int)nData); + free(data); + } + fclose(in); + } + return nData; +} + + +/** + \brief Write the contents of the image file as uncompressed RGB. + + Write source code that generates the uncompressed RGB image data at compile + time, and an initializer that creates the image at run time. + + \todo If the ld() value is not 0 and not d()*w(), the image data is not + written correctly. There is no check if the data is actually in RGB format. + + \param f Write code to this C++ source code file + \param fmt short name of file contents for error message + \return 0 if the file could not be opened or read + */ +void Image_Asset::write_static_rgb(fld::io::Code_Writer& f, const char* idata_name) { + // Write image data... + f.write_c("\n"); + f.write_c_once("#include \n"); + f.write_c("static const unsigned char %s[] =\n", idata_name); + const int extra_data = image_->ld() ? (image_->ld()-image_->w()*image_->d()) : 0; + f.write_cdata(image_->data()[0], (image_->w() * image_->d() + extra_data) * image_->h()); + f.write_c(";\n"); + write_initializer(f, "Fl_RGB_Image", "%s, %d, %d, %d, %d", idata_name, image_->w(), image_->h(), image_->d(), image_->ld()); +} + + +/** + \brief Write the static image data into the source file. + + Write source code that generates the image data at compile time, and an + initializer that creates the image at run time. + + If \p compressed is set, write the original image format, which requires + linking the matching image reader at runtime, or if we want to store the raw + uncompressed pixels, which makes images fast, needs no reader, but takes a + lot of memory (current default for PNG) + + \param f Write code to this C++ source code file + \param compressed write data in the original compressed file format + */ +void Image_Asset::write_static(fld::io::Code_Writer& f, int compressed) { + if (!image_) return; + const char *idata_name = f.unique_id(this, "idata", fl_filename_name(filename()), nullptr); + initializer_function_ = f.unique_id(this, "image", fl_filename_name(filename()), nullptr); + + if (is_animated_gif_) { + // Write animated gif image data... + f.write_c("\n"); + f.write_c_once("#include \n"); + f.write_c("static const unsigned char %s[] =\n", idata_name); + size_t nData = write_static_binary(f, "AnimGIF"); + f.write_c(";\n"); + write_initializer(f, "Fl_Anim_GIF_Image", "\"%s\", %s, %d", fl_filename_name(filename()), idata_name, nData); + } else if (compressed && fl_ascii_strcasecmp(fl_filename_ext(filename()), ".gif")==0) { + // Write gif image data... + f.write_c("\n"); + f.write_c_once("#include \n"); + f.write_c("static const unsigned char %s[] =\n", idata_name); + size_t nData = write_static_binary(f, "GIF"); + f.write_c(";\n"); + write_initializer(f, "Fl_GIF_Image", "\"%s\", %s, %d", fl_filename_name(filename()), idata_name, nData); + } else if (compressed && fl_ascii_strcasecmp(fl_filename_ext(filename()), ".bmp")==0) { + // Write bmp image data... + f.write_c("\n"); + f.write_c_once("#include \n"); + f.write_c("static const unsigned char %s[] =\n", idata_name); + size_t nData = write_static_binary(f, "BMP"); + f.write_c(";\n"); + write_initializer(f, "Fl_BMP_Image", "\"%s\", %s, %d", fl_filename_name(filename()), idata_name, nData); + } else if (image_->count() > 1) { + // Write Pixmap data... + f.write_c("\n"); + f.write_c_once("#include \n"); + f.write_c("static const char *%s[] = {\n", idata_name); + f.write_cstring(image_->data()[0], (int)strlen(image_->data()[0])); + + int i; + int ncolors, chars_per_color; + sscanf(image_->data()[0], "%*d%*d%d%d", &ncolors, &chars_per_color); + + if (ncolors < 0) { + f.write_c(",\n"); + f.write_cstring(image_->data()[1], ncolors * -4); + i = 2; + } else { + for (i = 1; i <= ncolors; i ++) { + f.write_c(",\n"); + f.write_cstring(image_->data()[i], (int)strlen(image_->data()[i])); + } + } + for (; i < image_->count(); i ++) { + f.write_c(",\n"); + f.write_cstring(image_->data()[i], image_->w() * chars_per_color); + } + f.write_c("\n};\n"); + write_initializer(f, "Fl_Pixmap", "%s", idata_name); + } else if (image_->d() == 0) { + // Write Bitmap data... + f.write_c("\n"); + f.write_c_once("#include \n"); + f.write_c("static const unsigned char %s[] =\n", idata_name); + f.write_cdata(image_->data()[0], ((image_->w() + 7) / 8) * image_->h()); + f.write_c(";\n"); + write_initializer(f, "Fl_Bitmap", "%s, %d, %d, %d", idata_name, ((image_->w() + 7) / 8) * image_->h(), image_->w(), image_->h()); + } else if (compressed && fl_ascii_strcasecmp(fl_filename_ext(filename()), ".jpg")==0) { + // Write jpeg image data... + f.write_c("\n"); + f.write_c_once("#include \n"); + f.write_c("static const unsigned char %s[] =\n", idata_name); + size_t nData = write_static_binary(f, "JPEG"); + f.write_c(";\n"); + write_initializer(f, "Fl_JPEG_Image", "\"%s\", %s, %d", fl_filename_name(filename()), idata_name, nData); + } else if (compressed && fl_ascii_strcasecmp(fl_filename_ext(filename()), ".png")==0) { + // Write png image data... + f.write_c("\n"); + f.write_c_once("#include \n"); + f.write_c("static const unsigned char %s[] =\n", idata_name); + size_t nData = write_static_binary(f, "PNG"); + f.write_c(";\n"); + write_initializer(f, "Fl_PNG_Image", "\"%s\", %s, %d", fl_filename_name(filename()), idata_name, nData); + } +#ifdef FLTK_USE_SVG + else if (fl_ascii_strcasecmp(fl_filename_ext(filename()), ".svg")==0 || fl_ascii_strcasecmp(fl_filename_ext(filename()), ".svgz")==0) { + bool gzipped = (strcmp(fl_filename_ext(filename()), ".svgz") == 0); + // Write svg image data... + if (compressed) { + f.write_c("\n"); + f.write_c_once("#include \n"); + if (gzipped) { + f.write_c("static const unsigned char %s[] =\n", idata_name); + size_t nData = write_static_binary(f, "SVGZ"); + f.write_c(";\n"); + write_initializer(f, "Fl_SVG_Image", "\"%s\", %s, %ld", fl_filename_name(filename()), idata_name, nData); + } else { + f.write_c("static const char %s[] =\n", idata_name); + write_static_text(f, "SVG"); + f.write_c(";\n"); + write_initializer(f, "Fl_SVG_Image", "\"%s\", %s", fl_filename_name(filename()), idata_name); + } + } else { + // if FLUID runs from the command line, make sure that the image is not + // only loaded but also rasterized, so we can write the RGB image data + Fl_RGB_Image* rgb_image = nullptr; + Fl_SVG_Image* svg_image = nullptr; + if (image_->d()>0) + rgb_image = (Fl_RGB_Image*)image_->image(); + if (rgb_image) + svg_image = rgb_image->as_svg_image(); + if (svg_image) { + svg_image->resize(svg_image->w(), svg_image->h()); + write_static_rgb(f, idata_name); + } else { + write_file_error(f, "RGB_from_SVG"); + } + } + } +#endif // FLTK_USE_SVG + else { + write_static_rgb(f, idata_name); + } +} + + +/** + \brief Write a warning message to the generated code file that the image asset + can't be read. + + This writes a #warning directive to the generated code file which contains the + filename of the image asset and the error message as returned by strerror(errno). + The current working directory is also printed for debugging purposes. + \param f The C++ source code file to write the warning message to. + \param fmt The format string for the image file type. + */ +void Image_Asset::write_file_error(fld::io::Code_Writer& f, const char *fmt) { + f.write_c("#warning Cannot read %s file \"%s\": %s\n", fmt, filename(), strerror(errno)); + Fluid.proj.enter_project_dir(); + f.write_c("// Searching in path \"%s\"\n", fl_getcwd(nullptr, FL_PATH_MAX)); + Fluid.proj.leave_project_dir(); +} + + +/** + \brief Outputs code that loads and returns an Fl_Image. + + The generated code loads the image if it hasn't been loaded yet, and then + returns a pointer to the image. + + \code + static Fl_Image *'initializer_function_'() { + static Fl_Image *image = 0L; + if (!image) + image = new 'type_name'('product of format and remaining args'); + return image; + } + \endcode + + \param f Write the C++ code to this file. + \param image_class Name of the Fl_Image class, for example Fl_GIF_Image. + \param format Format string for additional parameters for the constructor. + */ +void Image_Asset::write_initializer(fld::io::Code_Writer& f, const char *image_class, const char *format, ...) { + va_list ap; + va_start(ap, format); + f.write_c("static Fl_Image *%s() {\n", initializer_function_.c_str()); + if (is_animated_gif_) + f.write_c("%sFl_GIF_Image::animate = true;\n", f.indent(1)); + f.write_c("%sstatic Fl_Image *image = 0L;\n", f.indent(1)); + f.write_c("%sif (!image)\n", f.indent(1)); + f.write_c("%simage = new %s(", f.indent(2), image_class); + f.vwrite_c(format, ap); + f.write_c(");\n"); + f.write_c("%sreturn image;\n", f.indent(1)); + f.write_c("}\n"); + va_end(ap); +} + + +/** + \brief Outputs code that attaches an image to an Fl_Widget or Fl_Menu_Item. + + The generated code will call the image initializer function and assign + the resulting image to the widget. + + \param f Write the C++ code to this file. + \param bind If true, use bind_image() instead of image(). + \param var Name of the Fl_Widget or Fl_Menu_Item to attach the image to. + \param inactive If true, use deimage() instead of image(). + */ +void Image_Asset::write_code(fld::io::Code_Writer& f, int bind, const char *var, int inactive) { + if (image_) { + f.write_c("%s%s->%s%s( %s() );\n", + f.indent(), + var, + bind ? "bind_" : "", + inactive ? "deimage" : "image", + initializer_function_.c_str()); + if (is_animated_gif_) + f.write_c("%s((Fl_Anim_GIF_Image*)(%s()))->canvas(%s, Fl_Anim_GIF_Image::DONT_RESIZE_CANVAS);\n", + f.indent(), + initializer_function_.c_str(), + var); + } +} + + +/** + \brief Outputs code that calls the image initializer function. + + The generated code calls the image initializer function, loading an image and + assigning it to a Fl_Menu_Item. + + \param f Write the C++ code to this file. + \param inactive Unused. + */ +void Image_Asset::write_inline(fld::io::Code_Writer& f, int inactive) { + (void)inactive; + if (image_) { + f.write_c("%s()", initializer_function_.c_str()); + } +} + + +/** + \brief Finds an image asset by filename. + + If the image asset has already been loaded, it is returned from the cache. + If the image asset has not been loaded, it is loaded from the file system. + If the image asset cannot be loaded, nullptr is returned. + + \param iname The filename of the image asset to find. + \returns The image asset, or nullptr if it cannot be loaded. + */ +Image_Asset* Image_Asset::find(const char *iname) { + if (!iname || !*iname) return nullptr; + + // First search to see if it exists already. If it does, return it. + auto result = image_asset_map.find(iname); + if (result != image_asset_map.end()) + return result->second; + + // Check if a file by that name exists. + Fluid.proj.enter_project_dir(); + FILE *f = fl_fopen(iname,"rb"); + if (!f) { + if (Fluid.batch_mode) + fprintf(stderr, "Can't open image file:\n%s\n%s",iname,strerror(errno)); + else + fl_message("Can't open image file:\n%s\n%s",iname,strerror(errno)); + Fluid.proj.leave_project_dir(); + return nullptr; + } + fclose(f); + + // We found the file. Create the asset. + Image_Asset *asset = new Image_Asset(iname); + if (!asset->image_ || !asset->image_->w() || !asset->image_->h()) { + delete asset; + if (Fluid.batch_mode) + fprintf(stderr, "Can't read image file:\n%s\nunrecognized image format",iname); + else + fl_message("Can't read image file:\n%s\nunrecognized image format",iname); + Fluid.proj.leave_project_dir(); + return nullptr; + } + + // Add the new asset to our image asset map and return it to the caller. + image_asset_map[iname] = asset; + return asset; +} + +/** + \brief Construct an image asset from a file in the project directory. + + This constructor creates an image asset from a file in the project + directory. The image file is loaded and stored in the map of image + assets. The image asset is given a reference count of 1, which means + that it will be destroyed when all references to it have been released. + The constructor also sets the flag to indicate if the image is an + animated GIF. This information is used when generating code for the + image asset. + + \param iname The name of the image file in the project directory. +*/ +Image_Asset::Image_Asset(const char *iname) +{ + filename_ = iname; + image_ = Fl_Shared_Image::get(iname); + if (image_ && iname) { + const char *ext = fl_filename_ext(iname); + if (fl_ascii_strcasecmp(ext, ".gif")==0) { + int fc = Fl_Anim_GIF_Image::frame_count(iname); + if (fc > 0) is_animated_gif_ = true; + } + } +} + +/** + \brief Increments the reference count of the image asset. + + This method increments the reference count of the image asset. The + reference count is used to keep track of how many times the image asset + is referenced in the user interface. When the reference count reaches zero, + the image asset is destroyed. +*/ +void Image_Asset::inc_ref() { + ++refcount_; +} + +/** + \brief Decrements the reference count of the image asset. + + This method decrements the reference count of the image asset. The + reference count is used to keep track of how many times the image asset + is referenced in the user interface. If the reference count reaches zero, + the image asset is destroyed. +*/ +void Image_Asset::dec_ref() { + --refcount_; + if (refcount_ > 0) return; + delete this; +} + +/** + \brief Destructor for the Image_Asset class. + + This destructor removes the image asset from the global image asset map + and releases the associated shared image if it exists. It ensures that + any resources associated with the Image_Asset are properly cleaned up + when the object is destroyed. +*/ +Image_Asset::~Image_Asset() { + image_asset_map.erase(filename_); + if (image_) image_->release(); +} + +//////////////////////////////////////////////////////////////// + +/** + \brief Displays a file chooser for the user to select an image file. + + This function displays a file chooser dialog with the title "Image?" and + the current working directory set to the project directory. The file + chooser displays files with the extensions .bm, .bmp, .gif, .jpg, .pbm, + .pgm, .png, .ppm, .xbm, .xpm, and .svg (and .svgz if zlib support is + enabled). The function returns a pointer to an Image_Asset object that + references the selected image. If the user cancels the file chooser or + selects a file that does not exist, the function returns nullptr. + + \param oldname The default filename to display in the file chooser. + + \return A pointer to an Image_Asset object that references the selected + image, or nullptr if the user cancels the file chooser or selects a file + that does not exist. The asset is automaticly added to the global image + asset map. +*/ +Image_Asset *ui_find_image(const char *oldname) { + Fluid.proj.enter_project_dir(); + fl_file_chooser_ok_label("Use Image"); + const char *name = fl_file_chooser("Image?", + "Image Files (*.{bm,bmp,gif,jpg,pbm,pgm,png,ppm,xbm,xpm,svg" +#ifdef HAVE_LIBZ + ",svgz" +#endif + "})", + oldname,1); + fl_file_chooser_ok_label(nullptr); + Image_Asset *ret = (name && *name) ? Image_Asset::find(name) : nullptr; + Fluid.proj.leave_project_dir(); + return ret; +} + diff --git a/fluid/app/Image_Asset.h b/fluid/app/Image_Asset.h new file mode 100644 index 000000000..f5376beaa --- /dev/null +++ b/fluid/app/Image_Asset.h @@ -0,0 +1,63 @@ +// +// Image Helper header file for the Fast Light Tool Kit (FLTK). +// +// Copyright 1998-2025 by Bill Spitzak and others. +// +// This library is free software. Distribution and use rights are outlined in +// the file "COPYING" which should have been included with this file. If this +// file is missing or damaged, see the license at: +// +// https://www.fltk.org/COPYING.php +// +// Please see the following page on how to report bugs and issues: +// +// https://www.fltk.org/bugs.php +// + +// This class stores the image labels for widgets in fluid. This is +// not a class in FLTK itself, and will produce different types of +// code depending on what the image type is. + + +#ifndef APP_IMAGE_ASSET_H +#define APP_IMAGE_ASSET_H + +#include "io/Code_Writer.h" + +#include + +class Image_Asset { + +private: // member variables + bool is_animated_gif_ = false; ///< It's an animated gif. + std::string filename_ { }; ///< Relative path to the image file + int refcount_ = 0; ///< Reference count + Fl_Shared_Image *image_ = nullptr; ///< The actual image as managed by FLTK + std::string initializer_function_ { }; ///< The name of the initializer function + +private: // methods + Image_Asset(const char *name); // no public constructor + ~Image_Asset(); // no public destructor + size_t write_static_binary(fld::io::Code_Writer& f, const char* fmt); + size_t write_static_text(fld::io::Code_Writer& f, const char* fmt); + void write_static_rgb(fld::io::Code_Writer& f, const char* idata_name); + +public: // methods + static Image_Asset* find(const char *); + void dec_ref(); // reference counting & automatic free + void inc_ref(); + Fl_Shared_Image *image() const { return image_; } + void write_static(fld::io::Code_Writer& f, int compressed); + void write_initializer(fld::io::Code_Writer& f, const char *type_name, const char *format, ...); + void write_code(fld::io::Code_Writer& f, int bind, const char *var, int inactive = 0); + void write_inline(fld::io::Code_Writer& f, int inactive = 0); + void write_file_error(fld::io::Code_Writer& f, const char *fmt); + const char *filename() const { return filename_.c_str(); } +}; + +// pop up file chooser and return a legal image selected by user, +// or zero for any errors: +Image_Asset *ui_find_image(const char *); +extern const char *ui_find_image_name; + +#endif // APP_IMAGE_ASSET_H diff --git a/fluid/app/Menu.cxx b/fluid/app/Menu.cxx new file mode 100644 index 000000000..044cc29a7 --- /dev/null +++ b/fluid/app/Menu.cxx @@ -0,0 +1,190 @@ +// +// Application Main Menu code for the Fast Light Tool Kit (FLTK). +// +// Copyright 1998-2025 by Bill Spitzak and others. +// +// This library is free software. Distribution and use rights are outlined in +// the file "COPYING" which should have been included with this file. If this +// file is missing or damaged, see the license at: +// +// https://www.fltk.org/COPYING.php +// +// Please see the following page on how to report bugs and issues: +// +// https://www.fltk.org/bugs.php +// + +#include "app/Menu.h" + +#include "Fluid.h" + +#include "proj/undo.h" +#include "app/templates.h" +#include "nodes/Node.h" +#include "nodes/Group_Node.h" +#include "nodes/Window_Node.h" +#include "nodes/factory.h" +#include "panels/codeview_panel.h" +#include "app/shell_command.h" + +#include + +// In Snap_Action.h +extern void layout_suite_marker(Fl_Widget *, void *user_data); +extern void select_layout_preset_cb(Fl_Widget *, void *user_data); +extern Fl_Menu_Item main_layout_submenu_[]; + +using namespace fld; + + +void write_cb(Fl_Widget *, void *) { + Fluid.write_code_files(); +} +void openwidget_cb(Fl_Widget *, void *) { Fluid.edit_selected(); } +void copy_cb(Fl_Widget*, void*) { Fluid.copy_selected(); } +void cut_cb(Fl_Widget *, void *) { Fluid.cut_selected(); } +void delete_cb(Fl_Widget *, void *) { Fluid.delete_selected(); } +void paste_cb(Fl_Widget*, void*) { Fluid.paste_from_clipboard(); } +void duplicate_cb(Fl_Widget*, void*) { Fluid.duplicate_selected(); } +static void sort_cb(Fl_Widget *,void *) { Fluid.sort_selected(); } +void about_cb(Fl_Widget *, void *) { Fluid.about(); } +void help_cb(Fl_Widget *, void *) { + Fluid.show_help("fluid.html"); +} +static void save_template_cb(Fl_Widget *, void *) { fld::app::save_template(); } + +void manual_cb(Fl_Widget *, void *) { + Fluid.show_help("index.html"); +} + +static void menu_file_new_cb(Fl_Widget *, void *) { Fluid.new_project(); } +static void menu_file_new_from_template_cb(Fl_Widget *, void *) { Fluid.new_project_from_template(); } +static void menu_file_open_cb(Fl_Widget *, void *) { Fluid.open_project_file(""); } +static void menu_file_insert_cb(Fl_Widget *, void *) { Fluid.merge_project_file(""); } +void menu_file_save_cb(Fl_Widget *, void *arg) { Fluid.save_project_file(arg); } +static void menu_file_print_cb(Fl_Widget *, void *arg) { Fluid.print_snapshots(); } +void menu_file_open_history_cb(Fl_Widget *, void *v) { Fluid.open_project_file(std::string((const char*)v)); } +static void menu_layout_sync_resize_cb(Fl_Menu_ *m, void*) { + if (m->mvalue()->value()) Fluid.proj.tree.allow_layout = 1; else Fluid.proj.tree.allow_layout = 0; +} +static void menu_file_revert_cb(Fl_Widget *, void *) { Fluid.revert_project(); } +void exit_cb(Fl_Widget *,void *) { Fluid.quit(); } +static void write_strings_cb(Fl_Widget *, void *) { Fluid.proj.write_strings(); } +void toggle_widgetbin_cb(Fl_Widget *, void *) { Fluid.toggle_widget_bin(); } +/** + This is the main Fluid menu. + + Design history is manipulated right inside this menu structure. + Some menu items change or deactivate correctly, but most items just trigger + various callbacks. + + \c New_Menu creates new widgets and is explained in detail in another location. + + \see New_Menu + \todo This menu needs some major modernization. Menus are too long and their + sorting is not always obvious. + \todo Shortcuts are all over the place (Alt, Ctrl, Command, Shift-Ctrl, + function keys), and there should be a help page listing all shortcuts. + */ +Fl_Menu_Item Application::main_menu[] = { + {"&File",0,nullptr,nullptr,FL_SUBMENU}, + {"&New", FL_COMMAND+'n', menu_file_new_cb}, + {"&Open...", FL_COMMAND+'o', menu_file_open_cb}, + {"&Insert...", FL_COMMAND+'i', menu_file_insert_cb, nullptr, FL_MENU_DIVIDER}, + {"&Save", FL_COMMAND+'s', menu_file_save_cb, nullptr}, + {"Save &As...", FL_COMMAND+FL_SHIFT+'s', menu_file_save_cb, (void*)1}, + {"Sa&ve A Copy...", 0, menu_file_save_cb, (void*)2}, + {"&Revert...", 0, menu_file_revert_cb, nullptr, FL_MENU_DIVIDER}, + {"New &From Template...", FL_COMMAND+'N', menu_file_new_from_template_cb, nullptr}, + {"Save As &Template...", 0, save_template_cb, nullptr, FL_MENU_DIVIDER}, + {"&Print...", FL_COMMAND+'p', menu_file_print_cb}, + {"Write &Code", FL_COMMAND+FL_SHIFT+'c', write_cb, nullptr}, +// Matt: disabled {"MergeBack Code", FL_COMMAND+FL_SHIFT+'m', mergeback_cb, 0}, + {"&Write Strings", FL_COMMAND+FL_SHIFT+'w', write_strings_cb, nullptr, FL_MENU_DIVIDER}, + {Fluid.history.relpath[0], FL_COMMAND+'1', menu_file_open_history_cb, Fluid.history.abspath[0]}, + {Fluid.history.relpath[1], FL_COMMAND+'2', menu_file_open_history_cb, Fluid.history.abspath[1]}, + {Fluid.history.relpath[2], FL_COMMAND+'3', menu_file_open_history_cb, Fluid.history.abspath[2]}, + {Fluid.history.relpath[3], FL_COMMAND+'4', menu_file_open_history_cb, Fluid.history.abspath[3]}, + {Fluid.history.relpath[4], FL_COMMAND+'5', menu_file_open_history_cb, Fluid.history.abspath[4]}, + {Fluid.history.relpath[5], FL_COMMAND+'6', menu_file_open_history_cb, Fluid.history.abspath[5]}, + {Fluid.history.relpath[6], FL_COMMAND+'7', menu_file_open_history_cb, Fluid.history.abspath[6]}, + {Fluid.history.relpath[7], FL_COMMAND+'8', menu_file_open_history_cb, Fluid.history.abspath[7]}, + {Fluid.history.relpath[8], FL_COMMAND+'9', menu_file_open_history_cb, Fluid.history.abspath[8]}, + {Fluid.history.relpath[9], 0, menu_file_open_history_cb, Fluid.history.abspath[9], FL_MENU_DIVIDER}, + {"&Quit", FL_COMMAND+'q', exit_cb}, + {nullptr}, + {"&Edit",0,nullptr,nullptr,FL_SUBMENU}, + {"&Undo", FL_COMMAND+'z', fld::proj::Undo::undo_cb}, + {"&Redo", FL_COMMAND+FL_SHIFT+'z', fld::proj::Undo::redo_cb, nullptr, FL_MENU_DIVIDER}, + {"C&ut", FL_COMMAND+'x', cut_cb}, + {"&Copy", FL_COMMAND+'c', copy_cb}, + {"&Paste", FL_COMMAND+'v', paste_cb}, + {"Dup&licate", FL_COMMAND+'u', duplicate_cb}, + {"&Delete", FL_Delete, delete_cb, nullptr, FL_MENU_DIVIDER}, + {"Select &All", FL_COMMAND+'a', select_all_cb}, + {"Select &None", FL_COMMAND+FL_SHIFT+'a', select_none_cb, nullptr, FL_MENU_DIVIDER}, + {"Pr&operties...", FL_F+1, openwidget_cb}, + {"&Sort",0,sort_cb}, + {"&Earlier", FL_F+2, earlier_cb}, + {"&Later", FL_F+3, later_cb}, + {"&Group", FL_F+7, group_cb}, + {"Ung&roup", FL_F+8, ungroup_cb,nullptr, FL_MENU_DIVIDER}, + {"Hide O&verlays",FL_COMMAND+FL_SHIFT+'o',toggle_overlays}, + {"Hide Guides",FL_COMMAND+FL_SHIFT+'g',toggle_guides}, + {"Hide Restricted",FL_COMMAND+FL_SHIFT+'r',toggle_restricted}, + {"Show Widget &Bin...",FL_ALT+'b',toggle_widgetbin_cb}, + {"Show Code View",FL_ALT+'c', (Fl_Callback*)toggle_codeview_cb, nullptr, FL_MENU_DIVIDER}, + {"Settings...",FL_ALT+'p',show_settings_cb}, + {nullptr}, + {"&New", 0, nullptr, (void *)New_Menu, FL_SUBMENU_POINTER}, + {"&Layout",0,nullptr,nullptr,FL_SUBMENU}, + {"&Align",0,nullptr,nullptr,FL_SUBMENU}, + {"&Left",0,(Fl_Callback *)align_widget_cb,(void*)10}, + {"&Center",0,(Fl_Callback *)align_widget_cb,(void*)11}, + {"&Right",0,(Fl_Callback *)align_widget_cb,(void*)12}, + {"&Top",0,(Fl_Callback *)align_widget_cb,(void*)13}, + {"&Middle",0,(Fl_Callback *)align_widget_cb,(void*)14}, + {"&Bottom",0,(Fl_Callback *)align_widget_cb,(void*)15}, + {nullptr}, + {"&Space Evenly",0,nullptr,nullptr,FL_SUBMENU}, + {"&Across",0,(Fl_Callback *)align_widget_cb,(void*)20}, + {"&Down",0,(Fl_Callback *)align_widget_cb,(void*)21}, + {nullptr}, + {"&Make Same Size",0,nullptr,nullptr,FL_SUBMENU}, + {"&Width",0,(Fl_Callback *)align_widget_cb,(void*)30}, + {"&Height",0,(Fl_Callback *)align_widget_cb,(void*)31}, + {"&Both",0,(Fl_Callback *)align_widget_cb,(void*)32}, + {nullptr}, + {"&Center In Group",0,nullptr,nullptr,FL_SUBMENU}, + {"&Horizontal",0,(Fl_Callback *)align_widget_cb,(void*)40}, + {"&Vertical",0,(Fl_Callback *)align_widget_cb,(void*)41}, + {nullptr}, + {"Synchronized Resize", 0, (Fl_Callback*)menu_layout_sync_resize_cb, nullptr, FL_MENU_TOGGLE|FL_MENU_DIVIDER }, + {"&Grid and Size Settings...",FL_COMMAND+'g',show_grid_cb, nullptr, FL_MENU_DIVIDER}, + {"Presets", 0, layout_suite_marker, (void*)main_layout_submenu_, FL_SUBMENU_POINTER }, + {"Application", 0, select_layout_preset_cb, (void*)nullptr, FL_MENU_RADIO|FL_MENU_VALUE }, + {"Dialog", 0, select_layout_preset_cb, (void*)1, FL_MENU_RADIO }, + {"Toolbox", 0, select_layout_preset_cb, (void*)2, FL_MENU_RADIO }, + {nullptr}, +{"&Shell", 0, Fd_Shell_Command_List::menu_marker, (void*)Fd_Shell_Command_List::default_menu, FL_SUBMENU_POINTER}, + {"&Help",0,nullptr,nullptr,FL_SUBMENU}, + {"&Rapid development with FLUID...",0,help_cb}, + {"&FLTK Programmers Manual...",0,manual_cb, nullptr, FL_MENU_DIVIDER}, + {"&About FLUID...",0,about_cb}, + {nullptr}, +{nullptr}}; + +/** +Show or hide the code preview window. +*/ +void toggle_codeview_cb(Fl_Double_Window *, void *) { + codeview_toggle_visibility(); +} + +/** +Show or hide the code preview window, button callback. +*/ +void toggle_codeview_b_cb(Fl_Button*, void *) { + codeview_toggle_visibility(); +} + diff --git a/fluid/app/Menu.h b/fluid/app/Menu.h new file mode 100644 index 000000000..43f5d7e6f --- /dev/null +++ b/fluid/app/Menu.h @@ -0,0 +1,29 @@ +// +// Application Main Menu header for the Fast Light Tool Kit (FLTK). +// +// Copyright 1998-2025 by Bill Spitzak and others. +// +// This library is free software. Distribution and use rights are outlined in +// the file "COPYING" which should have been included with this file. If this +// file is missing or damaged, see the license at: +// +// https://www.fltk.org/COPYING.php +// +// Please see the following page on how to report bugs and issues: +// +// https://www.fltk.org/bugs.php +// + +#ifndef FLUID_APP_MENU_H +#define FLUID_APP_MENU_H + +#include + +extern void exit_cb(class Fl_Widget *,void *); // TODO: remove this +extern void toggle_widgetbin_cb(Fl_Widget *, void *); +extern void menu_file_save_cb(Fl_Widget *, void *arg); +extern void menu_file_open_history_cb(Fl_Widget *, void *v); +extern void align_widget_cb(Fl_Widget *, long); + +#endif // FLUID_APP_MENU_H + diff --git a/fluid/app/Snap_Action.cxx b/fluid/app/Snap_Action.cxx new file mode 100644 index 000000000..f33054eca --- /dev/null +++ b/fluid/app/Snap_Action.cxx @@ -0,0 +1,1835 @@ +// +// Snap action code file for the Fast Light Tool Kit (FLTK). +// +// Copyright 2023-2025 by Bill Spitzak and others. +// +// This library is free software. Distribution and use rights are outlined in +// the file "COPYING" which should have been included with this file. If this +// file is missing or damaged, see the license at: +// +// https://www.fltk.org/COPYING.php +// +// Please see the following page on how to report bugs and issues: +// +// https://www.fltk.org/bugs.php +// + +#include "app/Snap_Action.h" + +#include "Fluid.h" +#include "io/Project_Reader.h" +#include "io/Project_Writer.h" +#include "nodes/Group_Node.h" +#include "nodes/Window_Node.h" +#include "panels/settings_panel.h" + +#include +#include +#include + +#include +#include +#include +#undef min +#undef max +#include + +using namespace fld; +using namespace fld::app; + +// TODO: warning if the user wants to change builtin layouts +// TODO: move panel to global settings panel (move load & save to main pulldown, or to toolbox?) +// INFO: how about a small tool box for quick preset selection and disabling of individual snaps? + +void select_layout_suite_cb(Fl_Widget *, void *user_data); + +int Snap_Action::eex = 0; +int Snap_Action::eey = 0; + +static Layout_Preset fltk_app = { + 15, 15, 15, 15, 0, 0, // window: l, r, t, b, gx, gy + 10, 10, 10, 10, 0, 0, // group: l, r, t, b, gx, gy + 25, 25, // tabs: t, b + 20, 10, 4, // widget_x: min, inc, gap + 20, 4, 8, // widget_y: min, inc, gap + 0, 14, -1, 14 // labelfont/size, textfont/size +}; +static Layout_Preset fltk_dlg = { + 10, 10, 10, 10, 0, 0, // window: l, r, t, b, gx, gy + 10, 10, 10, 10, 0, 0, // group: l, r, t, b, gx, gy + 20, 20, // tabs: t, b + 20, 10, 5, // widget_x: min, inc, gap + 20, 5, 5, // widget_y: min, inc, gap + 0, 11, -1, 11 // labelfont/size, textfont/size +}; +static Layout_Preset fltk_tool = { + 10, 10, 10, 10, 0, 0, // window: l, r, t, b, gx, gy + 10, 10, 10, 10, 0, 0, // group: l, r, t, b, gx, gy + 18, 18, // tabs: t, b + 16, 8, 2, // widget_x: min, inc, gap + 16, 4, 2, // widget_y: min, inc, gap + 0, 10, -1, 10 // labelfont/size, textfont/size +}; + +static Layout_Preset grid_app = { + 12, 12, 12, 12, 12, 12, // window: l, r, t, b, gx, gy + 12, 12, 12, 12, 12, 12, // group: l, r, t, b, gx, gy + 24, 24, // tabs: t, b + 12, 6, 6, // widget_x: min, inc, gap + 12, 6, 6, // widget_y: min, inc, gap + 0, 14, -1, 14 // labelfont/size, textfont/size +}; + +static Layout_Preset grid_dlg = { + 10, 10, 10, 10, 10, 10, // window: l, r, t, b, gx, gy + 10, 10, 10, 10, 10, 10, // group: l, r, t, b, gx, gy + 20, 20, // tabs: t, b + 10, 5, 5, // widget_x: min, inc, gap + 10, 5, 5, // widget_y: min, inc, gap + 0, 12, -1, 12 // labelfont/size, textfont/size +}; + +static Layout_Preset grid_tool = { + 8, 8, 8, 8, 8, 8, // window: l, r, t, b, gx, gy + 8, 8, 8, 8, 8, 8, // group: l, r, t, b, gx, gy + 16, 16, // tabs: t, b + 8, 4, 4, // widget_x: min, inc, gap + 8, 4, 4, // widget_y: min, inc, gap + 0, 10, -1, 10 // labelfont/size, textfont/size +}; + +Layout_Preset *fld::app::default_layout_preset = &fltk_app; + +static Layout_Suite static_suite_list[] = { + { (char*)"FLTK", (char*)"@fd_beaker FLTK", { &fltk_app, &fltk_dlg, &fltk_tool }, fld::Tool_Store::INTERNAL }, + { (char*)"Grid", (char*)"@fd_beaker Grid", { &grid_app, &grid_dlg, &grid_tool }, fld::Tool_Store::INTERNAL } +}; + +Fl_Menu_Item main_layout_submenu_[] = { + { static_suite_list[0].menu_label, 0, select_layout_suite_cb, (void*)0, FL_MENU_RADIO|FL_MENU_VALUE }, + { static_suite_list[1].menu_label, 0, select_layout_suite_cb, (void*)1, FL_MENU_RADIO }, + { nullptr } +}; + +static Fl_Menu_Item static_choice_menu[] = { + { static_suite_list[0].menu_label }, + { static_suite_list[1].menu_label }, + { nullptr } +}; + + +// ---- Callbacks ------------------------------------------------------ MARK: - + +void layout_suite_marker(Fl_Widget *, void *) { + // intentionally left empty +} + +void select_layout_suite_cb(Fl_Widget *, void *user_data) { + int index = (int)(fl_intptr_t)user_data; + assert(index >= 0); + assert(index < Fluid.layout_list.list_size_); + Fluid.layout_list.current_suite(index); + Fluid.layout_list.update_dialogs(); +} + +void select_layout_preset_cb(Fl_Widget *, void *user_data) { + int index = (int)(fl_intptr_t)user_data; + assert(index >= 0); + assert(index < 3); + Fluid.layout_list.current_preset(index); + Fluid.layout_list.update_dialogs(); +} + +void edit_layout_preset_cb(Fl_Button *w, long user_data) { + int index = (int)w->argument(); + assert(index >= 0); + assert(index < 3); + if (user_data == (long)(fl_intptr_t)LOAD) { + w->value(Fluid.layout_list.current_preset() == index); + } else { + Fluid.layout_list.current_preset(index); + Fluid.layout_list.update_dialogs(); + } +} + +// ---- Layout_Suite ------------------------------------------------ MARK: - + +/** + Write presets to a Preferences database. + */ +void Layout_Preset::write(Fl_Preferences &prefs) { + assert(this); + Fl_Preferences p_win(prefs, "Window"); + p_win.set("left_margin", left_window_margin); + p_win.set("right_margin", right_window_margin); + p_win.set("top_margin", top_window_margin); + p_win.set("bottom_margin", bottom_window_margin); + p_win.set("grid_x", window_grid_x); + p_win.set("grid_y", window_grid_y); + + Fl_Preferences p_grp(prefs, "Group"); + p_grp.set("left_margin", left_group_margin); + p_grp.set("right_margin", right_group_margin); + p_grp.set("top_margin", top_group_margin); + p_grp.set("bottom_margin", bottom_group_margin); + p_grp.set("grid_x", group_grid_x); + p_grp.set("grid_y", group_grid_y); + + Fl_Preferences p_tbs(prefs, "Tabs"); + p_tbs.set("top_margin", top_tabs_margin); + p_tbs.set("bottom_margin", bottom_tabs_margin); + + Fl_Preferences p_wgt(prefs, "Widget"); + p_wgt.set("min_w", widget_min_w); + p_wgt.set("inc_w", widget_inc_w); + p_wgt.set("gap_x", widget_gap_x); + p_wgt.set("min_h", widget_min_h); + p_wgt.set("inc_h", widget_inc_h); + p_wgt.set("gap_y", widget_gap_y); + + Fl_Preferences p_lyt(prefs, "Layout"); + p_lyt.set("labelfont", labelfont); + p_lyt.set("labelsize", labelsize); + p_lyt.set("textfont", textfont); + p_lyt.set("textsize", textsize); +} + +/** + Read presets from a Preferences database. + */ +void Layout_Preset::read(Fl_Preferences &prefs) { + assert(this); + Fl_Preferences p_win(prefs, "Window"); + p_win.get("left_margin", left_window_margin, 15); + p_win.get("right_margin", right_window_margin, 15); + p_win.get("top_margin", top_window_margin, 15); + p_win.get("bottom_margin", bottom_window_margin, 15); + p_win.get("grid_x", window_grid_x, 0); + p_win.get("grid_y", window_grid_y, 0); + + Fl_Preferences p_grp(prefs, "Group"); + p_grp.get("left_margin", left_group_margin, 10); + p_grp.get("right_margin", right_group_margin, 10); + p_grp.get("top_margin", top_group_margin, 10); + p_grp.get("bottom_margin", bottom_group_margin, 10); + p_grp.get("grid_x", group_grid_x, 0); + p_grp.get("grid_y", group_grid_y, 0); + + Fl_Preferences p_tbs(prefs, "Tabs"); + p_tbs.get("top_margin", top_tabs_margin, 25); + p_tbs.get("bottom_margin", bottom_tabs_margin, 25); + + Fl_Preferences p_wgt(prefs, "Widget"); + p_wgt.get("min_w", widget_min_w, 20); + p_wgt.get("inc_w", widget_inc_w, 10); + p_wgt.get("gap_x", widget_gap_x, 4); + p_wgt.get("min_h", widget_min_h, 20); + p_wgt.get("inc_h", widget_inc_h, 4); + p_wgt.get("gap_y", widget_gap_y, 8); + + Fl_Preferences p_lyt(prefs, "Layout"); + p_lyt.get("labelfont", labelfont, 0); + p_lyt.get("labelsize", labelsize, 14); + p_lyt.get("textfont", textfont, 0); + p_lyt.get("textsize", textsize, 14); +} + +/** + Write presets to an .fl project file. + */ +void Layout_Preset::write(fld::io::Project_Writer *out) { + out->write_string(" preset { 1\n"); // preset format version + out->write_string(" %d %d %d %d %d %d\n", + left_window_margin, right_window_margin, + top_window_margin, bottom_window_margin, + window_grid_x, window_grid_y); + out->write_string(" %d %d %d %d %d %d\n", + left_group_margin, right_group_margin, + top_group_margin, bottom_group_margin, + group_grid_x, group_grid_y); + out->write_string(" %d %d\n", top_tabs_margin, bottom_tabs_margin); + out->write_string(" %d %d %d %d %d %d\n", + widget_min_w, widget_inc_w, widget_gap_x, + widget_min_h, widget_inc_h, widget_gap_y); + out->write_string(" %d %d %d %d\n", + labelfont, labelsize, textfont, textsize); + out->write_string(" }\n"); // preset format version +} + +/** + Read presets from an .fl project file. + */ +void Layout_Preset::read(fld::io::Project_Reader *in) { + const char *key; + key = in->read_word(1); + if (key && !strcmp(key, "{")) { + for (;;) { + key = in->read_word(); + if (!key) return; + if (key[0] == '}') break; + int ver = atoi(key); + if (ver == 0) { + continue; + } else if (ver == 1) { + left_window_margin = in->read_int(); + right_window_margin = in->read_int(); + top_window_margin = in->read_int(); + bottom_window_margin = in->read_int(); + window_grid_x = in->read_int(); + window_grid_y = in->read_int(); + + left_group_margin = in->read_int(); + right_group_margin = in->read_int(); + top_group_margin = in->read_int(); + bottom_group_margin = in->read_int(); + group_grid_x = in->read_int(); + group_grid_y = in->read_int(); + + top_tabs_margin = in->read_int(); + bottom_tabs_margin = in->read_int(); + + widget_min_w = in->read_int(); + widget_inc_w = in->read_int(); + widget_gap_x = in->read_int(); + widget_min_h = in->read_int(); + widget_inc_h = in->read_int(); + widget_gap_y = in->read_int(); + + labelfont = in->read_int(); + labelsize = in->read_int(); + textfont = in->read_int(); + textsize = in->read_int(); + } else { // skip unknown chunks + for (;;) { + key = in->read_word(1); + if (key && (key[0] == '}')) + return; + } + } + } + } else { + // format error + } +} + +/** + Return the preferred text size, but make sure it's not 0. + */ +int Layout_Preset::textsize_not_null() { + // try the user selected text size + if (textsize > 0) return textsize; + // if the user did not set one, try the label size + if (labelsize > 0) return labelsize; + // if that doesn;t work, fall back to the default value + return 14; +} + + +// ---- Layout_Suite ------------------------------------------------ MARK: - + +/** + Write a presets suite to a Preferences database. + */ +void Layout_Suite::write(Fl_Preferences &prefs) { + assert(this); + assert(name_); + prefs.set("name", name_); + for (int i = 0; i < 3; ++i) { + Fl_Preferences prefs_preset(prefs, Fl_Preferences::Name(i)); + assert(layout[i]); + layout[i]->write(prefs_preset); + } +} + +/** + Read a presets suite from a Preferences database. + */ +void Layout_Suite::read(Fl_Preferences &prefs) { + assert(this); + for (int i = 0; i < 3; ++i) { + Fl_Preferences prefs_preset(prefs, Fl_Preferences::Name(i)); + assert(layout[i]); + layout[i]->read(prefs_preset); + } +} + +/** + Write a presets suite to an .fl project file. + */ +void Layout_Suite::write(fld::io::Project_Writer *out) { + out->write_string(" suite {\n"); + out->write_string(" name "); out->write_word(name_); out->write_string("\n"); + for (int i = 0; i < 3; ++i) { + layout[i]->write(out); + } + out->write_string(" }\n"); +} + +/** + Read a presets suite from an .fl project file. + */ +void Layout_Suite::read(fld::io::Project_Reader *in) { + const char *key; + key = in->read_word(1); + if (key && !strcmp(key, "{")) { + int ix = 0; + for (;;) { + key = in->read_word(); + if (!key) return; + if (!strcmp(key, "name")) { + name(in->read_word()); + } else if (!strcmp(key, "preset")) { + if (ix >= 3) return; // file format error + layout[ix++]->read(in); + } else if (!strcmp(key, "}")) { + break; + } else { + in->read_word(); // unknown key, ignore, hopefully a key-value pair + } + } + } else { + // file format error + } +} + +/** + \brief Update the menu_label to show a symbol representing the storage location. + Also updates the FLUID user interface. + */ +void Layout_Suite::update_label() { + std::string sym; + switch (storage_) { + case fld::Tool_Store::INTERNAL: sym.assign("@fd_beaker "); break; + case fld::Tool_Store::USER: sym.assign("@fd_user "); break; + case fld::Tool_Store::PROJECT: sym.assign("@fd_project "); break; + case fld::Tool_Store::FILE: sym.assign("@fd_file "); break; + } + sym.append(name_); + if (menu_label) + ::free(menu_label); + menu_label = fl_strdup(sym.c_str()); + Fluid.layout_list.update_menu_labels(); +} + +/** + \brief Update the Suite name and the Suite menu_label. + Also updates the FLUID user interface. + */ +void Layout_Suite::name(const char *n) { + if (name_) + ::free(name_); + if (n) + name_ = fl_strdup(n); + else + name_ = nullptr; + update_label(); +} + +/** + Initialize the class for first use. + */ +void Layout_Suite::init() { + name_ = nullptr; + menu_label = nullptr; + layout[0] = layout[1] = layout[2] = nullptr; + storage_ = fld::Tool_Store::INTERNAL; +} + +/** + Free all allocated resources. + */ +Layout_Suite::~Layout_Suite() { + if (storage_ == fld::Tool_Store::INTERNAL) return; + if (name_) ::free(name_); + for (int i = 0; i < 3; ++i) { + delete layout[i]; + } +} + +// ---- Layout_List ------------------------------------------------- MARK: - + +/** + Draw a little FLUID beaker symbol. + */ +static void fd_beaker(Fl_Color c) { + fl_color(221); + fl_begin_polygon(); + fl_vertex(-0.6, 0.2); + fl_vertex(-0.9, 0.8); + fl_vertex(-0.8, 0.9); + fl_vertex( 0.8, 0.9); + fl_vertex( 0.9, 0.8); + fl_vertex( 0.6, 0.2); + fl_end_polygon(); + fl_color(c); + fl_begin_line(); + fl_vertex(-0.3, -0.9); + fl_vertex(-0.2, -0.8); + fl_vertex(-0.2, -0.2); + fl_vertex(-0.9, 0.8); + fl_vertex(-0.8, 0.9); + fl_vertex( 0.8, 0.9); + fl_vertex( 0.9, 0.8); + fl_vertex( 0.2, -0.2); + fl_vertex( 0.2, -0.8); + fl_vertex( 0.3, -0.9); + fl_end_line(); +} + +/** + Draw a user silhouette symbol + */ +static void fd_user(Fl_Color c) { + fl_color(245); + fl_begin_complex_polygon(); + fl_arc( 0.1, 0.9, 0.8, 0.0, 80.0); + fl_arc( 0.0, -0.5, 0.4, -65.0, 245.0); + fl_arc(-0.1, 0.9, 0.8, 100.0, 180.0); + fl_end_complex_polygon(); + fl_color(c); + fl_begin_line(); + fl_arc( 0.1, 0.9, 0.8, 0.0, 80.0); + fl_arc( 0.0, -0.5, 0.4, -65.0, 245.0); + fl_arc(-0.1, 0.9, 0.8, 100.0, 180.0); + fl_end_line(); +} + +/** + Draw a document symbol. + */ +static void fd_project(Fl_Color c) { + Fl_Color fc = FL_LIGHT2; + fl_color(fc); + fl_begin_complex_polygon(); + fl_vertex(-0.7, -1.0); + fl_vertex(0.1, -1.0); + fl_vertex(0.1, -0.4); + fl_vertex(0.7, -0.4); + fl_vertex(0.7, 1.0); + fl_vertex(-0.7, 1.0); + fl_end_complex_polygon(); + + fl_color(fl_lighter(fc)); + fl_begin_polygon(); + fl_vertex(0.1, -1.0); + fl_vertex(0.1, -0.4); + fl_vertex(0.7, -0.4); + fl_end_polygon(); + + fl_color(fl_darker(c)); + fl_begin_loop(); + fl_vertex(-0.7, -1.0); + fl_vertex(0.1, -1.0); + fl_vertex(0.1, -0.4); + fl_vertex(0.7, -0.4); + fl_vertex(0.7, 1.0); + fl_vertex(-0.7, 1.0); + fl_end_loop(); + + fl_begin_line(); + fl_vertex(0.1, -1.0); + fl_vertex(0.7, -0.4); + fl_end_line(); +} + +/** + Draw a 3 1/2" floppy symbol. + */ +void fd_file(Fl_Color c) { + Fl_Color fl = FL_LIGHT2; + Fl_Color fc = FL_DARK3; + fl_color(fc); + fl_begin_polygon(); // case + fl_vertex(-0.9, -1.0); + fl_vertex(0.9, -1.0); + fl_vertex(1.0, -0.9); + fl_vertex(1.0, 0.9); + fl_vertex(0.9, 1.0); + fl_vertex(-0.9, 1.0); + fl_vertex(-1.0, 0.9); + fl_vertex(-1.0, -0.9); + fl_end_polygon(); + + fl_color(fl_lighter(fl)); + fl_begin_polygon(); + fl_vertex(-0.7, -1.0); // slider + fl_vertex(0.7, -1.0); + fl_vertex(0.7, -0.4); + fl_vertex(-0.7, -0.4); + fl_end_polygon(); + + fl_begin_polygon(); // label + fl_vertex(-0.7, 0.0); + fl_vertex(0.7, 0.0); + fl_vertex(0.7, 1.0); + fl_vertex(-0.7, 1.0); + fl_end_polygon(); + + fl_color(fc); + fl_begin_polygon(); + fl_vertex(-0.5, -0.9); // slot + fl_vertex(-0.3, -0.9); + fl_vertex(-0.3, -0.5); + fl_vertex(-0.5, -0.5); + fl_end_polygon(); + + fl_color(fl_darker(c)); + fl_begin_loop(); + fl_vertex(-0.9, -1.0); + fl_vertex(0.9, -1.0); + fl_vertex(1.0, -0.9); + fl_vertex(1.0, 0.9); + fl_vertex(0.9, 1.0); + fl_vertex(-0.9, 1.0); + fl_vertex(-1.0, 0.9); + fl_vertex(-1.0, -0.9); + fl_end_loop(); +} + +/** + Instantiate the class that holds a list of all layouts and manages the UI. + */ +Layout_List::Layout_List() +: main_menu_(main_layout_submenu_), + choice_menu_(static_choice_menu), + list_(static_suite_list), + list_size_(2), + list_capacity_(2), + list_is_static_(true), + current_suite_(0), + current_preset_(0) +{ + fl_add_symbol("fd_beaker", fd_beaker, 1); + fl_add_symbol("fd_user", fd_user, 1); + fl_add_symbol("fd_project", fd_project, 1); + fl_add_symbol("fd_file", fd_file, 1); +} + +/** + Release allocated resources. + */ +Layout_List::~Layout_List() { + assert(this); + if (!list_is_static_) { + ::free(main_menu_); + ::free(choice_menu_); + for (int i = 0; i < list_size_; i++) { + Layout_Suite &suite = list_[i]; + if (suite.storage_ != fld::Tool_Store::INTERNAL) + suite.~Layout_Suite(); + } + ::free(list_); + } +} + +/** + Update the Setting dialog and menus to reflect the current Layout selection state. + */ +void Layout_List::update_dialogs() { + static Fl_Menu_Item *preset_menu = nullptr; + if (!preset_menu) { + preset_menu = (Fl_Menu_Item*)Fluid.main_menubar->find_item(select_layout_preset_cb); + assert(preset_menu); + } + assert(this); + assert(current_suite_ >= 0 ); + assert(current_suite_ < list_size_); + assert(current_preset_ >= 0 ); + assert(current_preset_ < 3); + Fluid.proj.layout = list_[current_suite_].layout[current_preset_]; + assert(Fluid.proj.layout); + if (w_settings_layout_tab) { + w_settings_layout_tab->do_callback(w_settings_layout_tab, LOAD); + layout_choice->redraw(); + } + preset_menu[current_preset_].setonly(preset_menu); + main_menu_[current_suite_].setonly(main_menu_); +} + +/** + Refresh the label pointers for both pulldown menus. + */ +void Layout_List::update_menu_labels() { + for (int i=0; iwrite_string("\nsnap {\n ver 1\n"); + out->write_string(" current_suite "); out->write_word(list_[current_suite()].name_); out->write_string("\n"); + out->write_string(" current_preset %d\n", current_preset()); + for (int i=0; iwrite_string("}"); +} + +/** + Read Suite and Layout selection and project layout data from an .fl project file. + */ +void Layout_List::read(fld::io::Project_Reader *in) { + const char *key; + key = in->read_word(1); + if (key && !strcmp(key, "{")) { + std::string cs; + int cp = 0; + for (;;) { + key = in->read_word(); + if (!key) return; + if (!strcmp(key, "ver")) { + in->read_int(); + } else if (!strcmp(key, "current_suite")) { + cs = in->read_word(); + } else if (!strcmp(key, "current_preset")) { + cp = in->read_int(); + } else if (!strcmp(key, "suite")) { + int n = add(in->filename_name()); + list_[n].read(in); + list_[n].storage(fld::Tool_Store::PROJECT); + } else if (!strcmp(key, "}")) { + break; + } else { + in->read_word(); // unknown key, ignore, hopefully a key-value pair + } + } + current_suite(cs); + current_preset(cp); + update_dialogs(); + } else { + // old style "snap" is followed by an integer. Ignore. + } +} + +/** + Set the current Suite. + \param[in] ix index into list of suites + */ +void Layout_List::current_suite(int ix) { + assert(ix >= 0); + assert(ix < list_size_); + current_suite_ = ix; + Fluid.proj.layout = list_[current_suite_].layout[current_preset_]; +} + +/** + Set the current Suite. + \param[in] arg_name name of the selected suite + \return if no name is given or the name is not found, keep the current suite selected + */ +void Layout_List::current_suite(std::string arg_name) { + if (arg_name.empty()) return; + for (int i = 0; i < list_size_; ++i) { + Layout_Suite &suite = list_[i]; + if (suite.name_ && (strcmp(suite.name_, arg_name.c_str()) == 0)) { + current_suite(i); + break; + } + } +} + +/** + Select a Preset within the current Suite. + \param[in] ix 0 = application, 1 = dialog, 2 = toolbox + */ +void Layout_List::current_preset(int ix) { + assert(ix >= 0); + assert(ix < 3); + current_preset_ = ix; + Fluid.proj.layout = list_[current_suite_].layout[current_preset_]; +} + +/** + Allocate enough space for n entries in the list. + */ +void Layout_List::capacity(int n) { + static Fl_Menu_Item *suite_menu = nullptr; + if (!suite_menu) + suite_menu = (Fl_Menu_Item*)Fluid.main_menubar->find_item(layout_suite_marker); + + int old_n = list_size_; + int i; + + Layout_Suite *new_list = (Layout_Suite*)::calloc(n, sizeof(Layout_Suite)); + for (i = 0; i < old_n; i++) + new_list[i] = list_[i]; + if (!list_is_static_) ::free(list_); + list_ = new_list; + + Fl_Menu_Item *new_main_menu = (Fl_Menu_Item*)::calloc(n+1, sizeof(Fl_Menu_Item)); + for (i = 0; i < old_n; i++) + new_main_menu[i] = main_menu_[i]; + if (!list_is_static_) ::free(main_menu_); + main_menu_ = new_main_menu; + suite_menu->user_data(main_menu_); + + Fl_Menu_Item *new_choice_menu = (Fl_Menu_Item*)::calloc(n+1, sizeof(Fl_Menu_Item)); + for (i = 0; i < old_n; i++) + new_choice_menu[i] = choice_menu_[i]; + if (!list_is_static_) ::free(choice_menu_); + choice_menu_ = new_choice_menu; + if (layout_choice) layout_choice->menu(choice_menu_); + + list_capacity_ = n; + list_is_static_ = false; +} + +/** + \brief Clone the currently selected suite and append it to the list. + Selects the new layout and updates the UI. + */ +int Layout_List::add(const char *name) { + if (list_size_ == list_capacity_) { + capacity(list_capacity_ * 2); + } + int n = list_size_; + Layout_Suite &old_suite = list_[current_suite_]; + Layout_Suite &new_suite = list_[n]; + new_suite.init(); + new_suite.name(name); + for (int i=0; i<3; ++i) { + new_suite.layout[i] = new Layout_Preset; + ::memcpy(new_suite.layout[i], old_suite.layout[i], sizeof(Layout_Preset)); + } + fld::Tool_Store new_storage = old_suite.storage_; + if (new_storage == fld::Tool_Store::INTERNAL) + new_storage = fld::Tool_Store::USER; + new_suite.storage(new_storage); + main_menu_[n].label(new_suite.menu_label); + main_menu_[n].callback(main_menu_[0].callback()); + main_menu_[n].argument(n); + main_menu_[n].flags = main_menu_[0].flags; + choice_menu_[n].label(new_suite.menu_label); + list_size_++; + current_suite(n); + return n; +} + +/** + Rename the current Suite. + */ +void Layout_List::rename(const char *name) { + int n = current_suite(); + list_[n].name(name); + main_menu_[n].label(list_[n].menu_label); + choice_menu_[n].label(list_[n].menu_label); +} + +/** + Remove the given suite. + \param[in] ix index into list of suites + */ +void Layout_List::remove(int ix) { + int tail = list_size_-ix-1; + if (tail) { + for (int i = ix; i < list_size_-1; i++) + list_[i] = list_[i+1]; + } + ::memmove(main_menu_+ix, main_menu_+ix+1, (tail+1) * sizeof(Fl_Menu_Item)); + ::memmove(choice_menu_+ix, choice_menu_+ix+1, (tail+1) * sizeof(Fl_Menu_Item)); + list_size_--; + if (current_suite() >= list_size_) + current_suite(list_size_ - 1); +} + +/** + Remove all Suites that use the given storage attribute. + \param[in] storage storage attribute, see fld::Tool_Store::INTERNAL, etc. + */ +void Layout_List::remove_all(fld::Tool_Store storage) { + for (int i=list_size_-1; i>=0; --i) { + if (list_[i].storage_ == storage) + remove(i); + } +} + +// ---- Helper --------------------------------------------------------- MARK: - + +static void draw_h_arrow(int, int, int); +static void draw_v_arrow(int x, int y1, int y2); +static void draw_left_brace(const Fl_Widget *w); +static void draw_right_brace(const Fl_Widget *w); +static void draw_top_brace(const Fl_Widget *w); +static void draw_bottom_brace(const Fl_Widget *w); +static void draw_grid(int x, int y, int dx, int dy); +void draw_width(int x, int y, int r, Fl_Align a); +void draw_height(int x, int y, int b, Fl_Align a); + +static int nearest(int x, int left, int grid, int right=0x7fff) { + int grid_x = ((x-left+grid/2)/grid)*grid+left; + if (grid_x < left+grid/2) return left; // left+grid/2; + if (grid_x > right-grid/2) return right; // right-grid/2; + return grid_x; +} + +static bool in_window(Snap_Data &d) { + return (d.wgt && d.wgt->parent == d.win); +} + +static bool in_group(Snap_Data &d) { + return (d.wgt && d.wgt->parent && d.wgt->parent->is_a(Type::Group) && d.wgt->parent != d.win); +} + +static bool in_tabs(Snap_Data &d) { + return (d.wgt && d.wgt->parent && d.wgt->parent->is_a(Type::Tabs)); +} + +static Fl_Group *parent(Snap_Data &d) { + return (d.wgt->o->parent()); +} + +// ---- Snap_Action ------------------------------------------------- MARK: - + +/** \class Snap_Action + + When a user drags one or more widgets, snap actions can be defined that provide + hints if a preferred widget position or size is nearby. The user's motion is + then directed towards the nearest preferred position, and the widget selection + snaps into place. + + FLUID provides a list of various snap actions. Every snap action uses the data + from the motion event and combines it with the sizes and positions of all other + widgets in the layout. + + Common snap actions include gaps and margins, but also alignments and + simple grid positions. + */ + +/** + \brief Check if a snap action has reached a preferred x position. + \param[inout] d current event data + \param[in] x_ref position of moving point + \param[in] x_snap position of target point + \return 1 if the points are not within range and won;t be considered + \return 0 if the point is as close as another in a previous action + \return -1 if this point is closer than any previous check, and this is the + new distance to beat. + */ +int Snap_Action::check_x_(Snap_Data &d, int x_ref, int x_snap) { + int dd = x_ref + d.dx - x_snap; + int d2 = abs(dd); + if (d2 > d.x_dist) return 1; + dx = d.dx_out = d.dx - dd; + ex = d.ex_out = x_snap; + if (d2 == d.x_dist) return 0; + d.x_dist = d2; + return -1; +} + +/** + \brief Check if a snap action has reached a preferred y position. + \see Snap_Action::check_x_(Snap_Data &d, int x_ref, int x_snap) + */ +int Snap_Action::check_y_(Snap_Data &d, int y_ref, int y_snap) { + int dd = y_ref + d.dy - y_snap; + int d2 = abs(dd); + if (d2 > d.y_dist) return 1; + dy = d.dy_out = d.dy - dd; + ey = d.ey_out = y_snap; + if (d2 == d.y_dist) return 0; + d.y_dist = d2; + return -1; +} + +/** + \brief Check if a snap action has reached a preferred x and y position. + \see Snap_Action::check_x_(Snap_Data &d, int x_ref, int x_snap) + */ +void Snap_Action::check_x_y_(Snap_Data &d, int x_ref, int x_snap, int y_ref, int y_snap) { + int ddx = x_ref + d.dx - x_snap; + int d2x = abs(ddx); + int ddy = y_ref + d.dy - y_snap; + int d2y = abs(ddy); + if ((d2x <= d.x_dist) && (d2y <= d.y_dist)) { + dx = d.dx_out = d.dx - ddx; + ex = d.ex_out = x_snap; + d.x_dist = d2x; + dy = d.dy_out = d.dy - ddy; + ey = d.ey_out = y_snap; + d.y_dist = d2y; + } +} + +/** + \brief Check if a snap action was applied to the current event. + This method is used to determine if a visual indicator for this snap action + should be drawn. + \param[inout] d current event data + */ +bool Snap_Action::matches(Snap_Data &d) { + switch (type) { + case 1: return (d.drag & mask) && (eex == ex) && (d.dx == dx); + case 2: return (d.drag & mask) && (eey == ey) && (d.dy == dy); + case 3: return (d.drag & mask) && (eex == ex) && (d.dx == dx) && (eey == ey) && (d.dy == dy); + } + return false; +} + +/** + \brief Run through all possible snap actions and store the winning coordinates in eex and eey. + \param[inout] d current event data + */ +void Snap_Action::check_all(Snap_Data &data) { + for (int i=0; list[i]; i++) { + if (list[i]->mask & data.drag) + list[i]->check(data); + } + eex = data.ex_out; + eey = data.ey_out; +} + +/** + \brief Draw a visual indicator for all snap actions that were applied during the last check. + Only one snap coordinate can win. FLUID chooses the one that is closest to + the current user event. If two or more snap actions suggest the same + coordinate, all of them will be drawn. + \param[inout] d current event data + */ +void Snap_Action::draw_all(Snap_Data &data) { + for (int i=0; list[i]; i++) { + if (list[i]->matches(data)) + list[i]->draw(data); + } +} + +/** Return a sensible step size for resizing a widget. */ +void Snap_Action::get_resize_stepsize(int &x_step, int &y_step) { + auto layout = Fluid.proj.layout; + if ((layout->widget_inc_w > 1) && (layout->widget_inc_h > 1)) { + x_step = layout->widget_inc_w; + y_step = layout->widget_inc_h; + } else if ((layout->group_grid_x > 1) && (layout->group_grid_y > 1)) { + x_step = layout->group_grid_x; + y_step = layout->group_grid_y; + } else { + x_step = layout->window_grid_x; + y_step = layout->window_grid_y; + } +} + +/** Return a sensible step size for moving a widget. */ +void Snap_Action::get_move_stepsize(int &x_step, int &y_step) { + auto layout = Fluid.proj.layout; + if ((layout->group_grid_x > 1) && (layout->group_grid_y > 1)) { + x_step = layout->group_grid_x; + y_step = layout->group_grid_y; + } else if ((layout->window_grid_x > 1) && (layout->window_grid_y > 1)) { + x_step = layout->window_grid_x; + y_step = layout->window_grid_y; + } else { + x_step = layout->widget_gap_x; + y_step = layout->widget_gap_y; + } +} + +/** Fix the given size to the same or next bigger snap position. */ +void Snap_Action::better_size(int &w, int &h) { + auto layout = Fluid.proj.layout; + int x_min = 1, y_min = 1, x_inc = 1, y_inc = 1; + get_resize_stepsize(x_inc, y_inc); + if (x_inc < 1) x_inc = 1; + if (y_inc < 1) y_inc = 1; + if ((layout->widget_min_w > 1) && (layout->widget_min_h > 1)) { + x_min = layout->widget_min_w; + y_min = layout->widget_min_h; + } else if ((layout->group_grid_x > 1) && (layout->group_grid_y > 1)) { + x_min = layout->group_grid_x; + y_min = layout->group_grid_y; + } else { + x_min = x_inc; + y_min = y_inc; + } + int ww = std::max(w - x_min, 0); w = (w - ww + x_inc - 1) / x_inc; w = w * x_inc; w = w + ww; + int hh = std::max(h - y_min, 0); h = (h - hh + y_inc - 1) / y_inc; h = h * y_inc; h = h + hh; +} + + +// ---- snapping prototypes -------------------------------------------- MARK: - + +/** + Base class for all actions that drag the left side or the entire widget. + */ +class Fd_Snap_Left : public Snap_Action { +public: + Fd_Snap_Left() { type = 1; mask = FD_LEFT|FD_DRAG; } +}; + +/** + Base class for all actions that drag the right side or the entire widget. + */ +class Fd_Snap_Right : public Snap_Action { +public: + Fd_Snap_Right() { type = 1; mask = FD_RIGHT|FD_DRAG; } +}; + +/** + Base class for all actions that drag the top side or the entire widget. + */ +class Fd_Snap_Top : public Snap_Action { +public: + Fd_Snap_Top() { type = 2; mask = FD_TOP|FD_DRAG; } +}; + +/** + Base class for all actions that drag the bottom side or the entire widget. + */ +class Fd_Snap_Bottom : public Snap_Action { +public: + Fd_Snap_Bottom() { type = 2; mask = FD_BOTTOM|FD_DRAG; } +}; + +// ---- window snapping ------------------------------------------------ MARK: - + +/** + Check if the widget hits the left window edge. + */ +class Fd_Snap_Left_Window_Edge : public Fd_Snap_Left { +public: + void check(Snap_Data &d) override { clr(); check_x_(d, d.bx, 0); } + void draw(Snap_Data &d) override { draw_left_brace(d.win->o); }; +}; +Fd_Snap_Left_Window_Edge snap_left_window_edge; + +/** + Check if the widget hits the right window edge. + */ +class Fd_Snap_Right_Window_Edge : public Fd_Snap_Right { +public: + void check(Snap_Data &d) override { clr(); check_x_(d, d.br, d.win->o->w()); } + void draw(Snap_Data &d) override { draw_right_brace(d.win->o); }; +}; +Fd_Snap_Right_Window_Edge snap_right_window_edge; + +/** + Check if the widget hits the top window edge. + */ +class Fd_Snap_Top_Window_Edge : public Fd_Snap_Top { +public: + void check(Snap_Data &d) override { clr(); check_y_(d, d.by, 0); } + void draw(Snap_Data &d) override { draw_top_brace(d.win->o); }; +}; +Fd_Snap_Top_Window_Edge snap_top_window_edge; + +/** + Check if the widget hits the bottom window edge. + */ +class Fd_Snap_Bottom_Window_Edge : public Fd_Snap_Bottom { +public: + void check(Snap_Data &d) override { clr(); check_y_(d, d.bt, d.win->o->h()); } + void draw(Snap_Data &d) override { draw_bottom_brace(d.win->o); }; +}; +Fd_Snap_Bottom_Window_Edge snap_bottom_window_edge; + +/** + Check if the widget hits the left window edge plus a user defined margin. + */ +class Fd_Snap_Left_Window_Margin : public Fd_Snap_Left { +public: + void check(Snap_Data &d) override { + clr(); + if (in_window(d)) check_x_(d, d.bx, Fluid.proj.layout->left_window_margin); + } + void draw(Snap_Data &d) override { + draw_h_arrow(d.bx, (d.by+d.bt)/2, 0); + }; +}; +Fd_Snap_Left_Window_Margin snap_left_window_margin; + +class Fd_Snap_Right_Window_Margin : public Fd_Snap_Right { +public: + void check(Snap_Data &d) override { + clr(); + if (in_window(d)) check_x_(d, d.br, d.win->o->w()-Fluid.proj.layout->right_window_margin); + } + void draw(Snap_Data &d) override { + draw_h_arrow(d.br, (d.by+d.bt)/2, d.win->o->w()-1); + }; +}; +Fd_Snap_Right_Window_Margin snap_right_window_margin; + +class Fd_Snap_Top_Window_Margin : public Fd_Snap_Top { +public: + void check(Snap_Data &d) override { + clr(); + if (in_window(d)) check_y_(d, d.by, Fluid.proj.layout->top_window_margin); + } + void draw(Snap_Data &d) override { + draw_v_arrow((d.bx+d.br)/2, d.by, 0); + }; +}; +Fd_Snap_Top_Window_Margin snap_top_window_margin; + +class Fd_Snap_Bottom_Window_Margin : public Fd_Snap_Bottom { +public: + void check(Snap_Data &d) override { + clr(); + if (in_window(d)) check_y_(d, d.bt, d.win->o->h()-Fluid.proj.layout->bottom_window_margin); + } + void draw(Snap_Data &d) override { + draw_v_arrow((d.bx+d.br)/2, d.bt, d.win->o->h()-1); + }; +}; +Fd_Snap_Bottom_Window_Margin snap_bottom_window_margin; + +// ---- group snapping ------------------------------------------------- MARK: - + +/** + Check if the widget hits the left group edge. + */ +class Fd_Snap_Left_Group_Edge : public Fd_Snap_Left { +public: + void check(Snap_Data &d) override { + clr(); + if (in_group(d)) check_x_(d, d.bx, parent(d)->x()); + } + void draw(Snap_Data &d) override { + draw_left_brace(parent(d)); + }; +}; +Fd_Snap_Left_Group_Edge snap_left_group_edge; + +class Fd_Snap_Right_Group_Edge : public Fd_Snap_Right { +public: + void check(Snap_Data &d) override { + clr(); + if (in_group(d)) check_x_(d, d.br, parent(d)->x() + parent(d)->w()); + } + void draw(Snap_Data &d) override { + draw_right_brace(parent(d)); + }; +}; +Fd_Snap_Right_Group_Edge snap_right_group_edge; + +class Fd_Snap_Top_Group_Edge : public Fd_Snap_Top { +public: + void check(Snap_Data &d) override { + clr(); + if (in_group(d)) check_y_(d, d.by, parent(d)->y()); + } + void draw(Snap_Data &d) override { + draw_top_brace(parent(d)); + }; +}; +Fd_Snap_Top_Group_Edge snap_top_group_edge; + +class Fd_Snap_Bottom_Group_Edge : public Fd_Snap_Bottom { +public: + void check(Snap_Data &d) override { + clr(); + if (in_group(d)) check_y_(d, d.bt, parent(d)->y() + parent(d)->h()); + } + void draw(Snap_Data &d) override { + draw_bottom_brace(parent(d)); + }; +}; +Fd_Snap_Bottom_Group_Edge snap_bottom_group_edge; + + +/** + Check if the widget hits the left group edge plus a user defined margin. + */ +class Fd_Snap_Left_Group_Margin : public Fd_Snap_Left { +public: + void check(Snap_Data &d) override { + clr(); + if (in_group(d)) check_x_(d, d.bx, parent(d)->x() + Fluid.proj.layout->left_group_margin); + } + void draw(Snap_Data &d) override { + draw_left_brace(parent(d)); + draw_h_arrow(d.bx, (d.by+d.bt)/2, parent(d)->x()); + }; +}; +Fd_Snap_Left_Group_Margin snap_left_group_margin; + +class Fd_Snap_Right_Group_Margin : public Fd_Snap_Right { +public: + void check(Snap_Data &d) override { + clr(); + if (in_group(d)) check_x_(d, d.br, parent(d)->x()+parent(d)->w()-Fluid.proj.layout->right_group_margin); + } + void draw(Snap_Data &d) override { + draw_right_brace(parent(d)); + draw_h_arrow(d.br, (d.by+d.bt)/2, parent(d)->x()+parent(d)->w()-1); + }; +}; +Fd_Snap_Right_Group_Margin snap_right_group_margin; + +class Fd_Snap_Top_Group_Margin : public Fd_Snap_Top { +public: + void check(Snap_Data &d) override { + clr(); + if (in_group(d) && !in_tabs(d)) check_y_(d, d.by, parent(d)->y()+Fluid.proj.layout->top_group_margin); + } + void draw(Snap_Data &d) override { + draw_top_brace(parent(d)); + draw_v_arrow((d.bx+d.br)/2, d.by, parent(d)->y()); + }; +}; +Fd_Snap_Top_Group_Margin snap_top_group_margin; + +class Fd_Snap_Bottom_Group_Margin : public Fd_Snap_Bottom { +public: + void check(Snap_Data &d) override { + clr(); + if (in_group(d) && !in_tabs(d)) check_y_(d, d.bt, parent(d)->y()+parent(d)->h()-Fluid.proj.layout->bottom_group_margin); + } + void draw(Snap_Data &d) override { + draw_bottom_brace(parent(d)); + draw_v_arrow((d.bx+d.br)/2, d.bt, parent(d)->y()+parent(d)->h()-1); + }; +}; +Fd_Snap_Bottom_Group_Margin snap_bottom_group_margin; + +// ----- tabs snapping ------------------------------------------------- MARK: - + +/** + Check if the widget top hits the Fl_Tabs group top edge plus a user defined margin. + */ +class Fd_Snap_Top_Tabs_Margin : public Fd_Snap_Top_Group_Margin { +public: + void check(Snap_Data &d) override { + clr(); + if (in_tabs(d)) check_y_(d, d.by, parent(d)->y()+Fluid.proj.layout->top_tabs_margin); + } +}; +Fd_Snap_Top_Tabs_Margin snap_top_tabs_margin; + +class Fd_Snap_Bottom_Tabs_Margin : public Fd_Snap_Bottom_Group_Margin { +public: + void check(Snap_Data &d) override { + clr(); + if (in_tabs(d)) check_y_(d, d.bt, parent(d)->y()+parent(d)->h()-Fluid.proj.layout->bottom_tabs_margin); + } +}; +Fd_Snap_Bottom_Tabs_Margin snap_bottom_tabs_margin; + +// ----- grid snapping ------------------------------------------------- MARK: - + +/** + Base class for grid based snapping. + */ +class Fd_Snap_Grid : public Snap_Action { +protected: + int nearest_x, nearest_y; +public: + Fd_Snap_Grid() { type = 3; mask = FD_LEFT|FD_TOP|FD_DRAG; } + void check_grid(Snap_Data &d, int left, int grid_x, int right, int top, int grid_y, int bottom) { + if ((grid_x <= 1) || (grid_y <= 1)) return; + int suggested_x = d.bx + d.dx; + nearest_x = nearest(suggested_x, left, grid_x, right); + int suggested_y = d.by + d.dy; + nearest_y = nearest(suggested_y, top, grid_y, bottom); + if (d.drag == FD_LEFT) + check_x_(d, d.bx, nearest_x); + else if (d.drag == FD_TOP) + check_y_(d, d.by, nearest_y); + else + check_x_y_(d, d.bx, nearest_x, d.by, nearest_y); + } + bool matches(Snap_Data &d) override { + if (d.drag == FD_LEFT) return (eex == ex); + if (d.drag == FD_TOP) return (eey == ey) && (d.dx == dx); + return (d.drag & mask) && (eex == ex) && (d.dx == dx) && (eey == ey) && (d.dy == dy); + } +}; + +/** + Check if the widget hits window grid coordinates. + */ +class Fd_Snap_Window_Grid : public Fd_Snap_Grid { +public: + void check(Snap_Data &d) override { + auto layout = Fluid.proj.layout; + clr(); + if (in_window(d)) check_grid(d, layout->left_window_margin, layout->window_grid_x, d.win->o->w()-layout->right_window_margin, + layout->top_window_margin, layout->window_grid_y, d.win->o->h()-layout->bottom_window_margin); + } + void draw(Snap_Data &d) override { + auto layout = Fluid.proj.layout; + draw_grid(nearest_x, nearest_y, layout->window_grid_x, layout->window_grid_y); + }; +}; +Fd_Snap_Window_Grid snap_window_grid; + +/** + Check if the widget hits group grid coordinates. + */ +class Fd_Snap_Group_Grid : public Fd_Snap_Grid { +public: + void check(Snap_Data &d) override { + if (in_group(d)) { + auto layout = Fluid.proj.layout; + clr(); + Fl_Widget *g = parent(d); + check_grid(d, g->x()+layout->left_group_margin, layout->group_grid_x, g->x()+g->w()-layout->right_group_margin, + g->y()+layout->top_group_margin, layout->group_grid_y, g->y()+g->h()-layout->bottom_group_margin); + } + } + void draw(Snap_Data &d) override { + auto layout = Fluid.proj.layout; + draw_grid(nearest_x, nearest_y, layout->group_grid_x, layout->group_grid_y); + }; +}; +Fd_Snap_Group_Grid snap_group_grid; + +// ----- sibling snapping ---------------------------------------------- MARK: - + +/** + Base class the check distance to other widgets in the same group. + */ +class Fd_Snap_Sibling : public Snap_Action { +protected: + Fl_Widget *best_match; +public: + Fd_Snap_Sibling() : best_match(nullptr) { } + virtual int sibling_check(Snap_Data &d, Fl_Widget *s) = 0; + void check(Snap_Data &d) override { + clr(); + best_match = nullptr; + if (!d.wgt) return; + if (!d.wgt->parent->is_a(Type::Group)) return; + int dsib_min = 1024; + Group_Node *gt = (Group_Node*)d.wgt->parent; + Fl_Group *g = (Fl_Group*)gt->o; + Fl_Widget *w = d.wgt->o; + for (int i=0; ichildren(); i++) { + Fl_Widget *c = g->child(i); + if (c == w) continue; + int sret = sibling_check(d, c); + if (sret < 1) { + int dsib; + if (type==1) + dsib = abs( ((d.by+d.bt)/2+d.dy) - (c->y()+c->h()/2) ); + else + dsib = abs( ((d.bx+d.br)/2+d.dx) - (c->x()+c->w()/2) ); + if (sret == -1 || (dsib < dsib_min)) { + dsib_min = dsib; + best_match = c; + } + } + } + } +}; + +/** + Check if widgets have the same x coordinate, so they can be vertically aligned. + */ +class Fd_Snap_Siblings_Left_Same : public Fd_Snap_Sibling { +public: + Fd_Snap_Siblings_Left_Same() { type = 1; mask = FD_LEFT|FD_DRAG; } + int sibling_check(Snap_Data &d, Fl_Widget *s) override { + return check_x_(d, d.bx, s->x()); + } + void draw(Snap_Data &d) override { + if (best_match) draw_left_brace(best_match); + }; +}; +Fd_Snap_Siblings_Left_Same snap_siblings_left_same; + +/** + Check if widgets touch left to right, or have a user selected gap left to right. + */ +class Fd_Snap_Siblings_Left : public Fd_Snap_Sibling { +public: + Fd_Snap_Siblings_Left() { type = 1; mask = FD_LEFT|FD_DRAG; } + int sibling_check(Snap_Data &d, Fl_Widget *s) override { + return std::min(check_x_(d, d.bx, s->x()+s->w()), + check_x_(d, d.bx, s->x()+s->w()+Fluid.proj.layout->widget_gap_x) ); + } + void draw(Snap_Data &d) override { + if (best_match) draw_right_brace(best_match); + }; +}; +Fd_Snap_Siblings_Left snap_siblings_left; + +class Fd_Snap_Siblings_Right_Same : public Fd_Snap_Sibling { +public: + Fd_Snap_Siblings_Right_Same() { type = 1; mask = FD_RIGHT|FD_DRAG; } + int sibling_check(Snap_Data &d, Fl_Widget *s) override { + return check_x_(d, d.br, s->x()+s->w()); + } + void draw(Snap_Data &d) override { + if (best_match) draw_right_brace(best_match); + }; +}; +Fd_Snap_Siblings_Right_Same snap_siblings_right_same; + +class Fd_Snap_Siblings_Right : public Fd_Snap_Sibling { +public: + Fd_Snap_Siblings_Right() { type = 1; mask = FD_RIGHT|FD_DRAG; } + int sibling_check(Snap_Data &d, Fl_Widget *s) override { + return std::min(check_x_(d, d.br, s->x()), + check_x_(d, d.br, s->x()-Fluid.proj.layout->widget_gap_x)); + } + void draw(Snap_Data &d) override { + if (best_match) draw_left_brace(best_match); + }; +}; +Fd_Snap_Siblings_Right snap_siblings_right; + +class Fd_Snap_Siblings_Top_Same : public Fd_Snap_Sibling { +public: + Fd_Snap_Siblings_Top_Same() { type = 2; mask = FD_TOP|FD_DRAG; } + int sibling_check(Snap_Data &d, Fl_Widget *s) override { + return check_y_(d, d.by, s->y()); + } + void draw(Snap_Data &d) override { + if (best_match) draw_top_brace(best_match); + }; +}; +Fd_Snap_Siblings_Top_Same snap_siblings_top_same; + +class Fd_Snap_Siblings_Top : public Fd_Snap_Sibling { +public: + Fd_Snap_Siblings_Top() { type = 2; mask = FD_TOP|FD_DRAG; } + int sibling_check(Snap_Data &d, Fl_Widget *s) override { + return std::min(check_y_(d, d.by, s->y()+s->h()), + check_y_(d, d.by, s->y()+s->h()+Fluid.proj.layout->widget_gap_y)); + } + void draw(Snap_Data &d) override { + if (best_match) draw_bottom_brace(best_match); + }; +}; +Fd_Snap_Siblings_Top snap_siblings_top; + +class Fd_Snap_Siblings_Bottom_Same : public Fd_Snap_Sibling { +public: + Fd_Snap_Siblings_Bottom_Same() { type = 2; mask = FD_BOTTOM|FD_DRAG; } + int sibling_check(Snap_Data &d, Fl_Widget *s) override { + return check_y_(d, d.bt, s->y()+s->h()); + } + void draw(Snap_Data &d) override { + if (best_match) draw_bottom_brace(best_match); + }; +}; +Fd_Snap_Siblings_Bottom_Same snap_siblings_bottom_same; + +class Fd_Snap_Siblings_Bottom : public Fd_Snap_Sibling { +public: + Fd_Snap_Siblings_Bottom() { type = 2; mask = FD_BOTTOM|FD_DRAG; } + int sibling_check(Snap_Data &d, Fl_Widget *s) override { + return std::min(check_y_(d, d.bt, s->y()), + check_y_(d, d.bt, s->y()-Fluid.proj.layout->widget_gap_y)); + } + void draw(Snap_Data &d) override { + if (best_match) draw_top_brace(best_match); + }; +}; +Fd_Snap_Siblings_Bottom snap_siblings_bottom; + + +// ------ widget snapping ---------------------------------------------- MARK: - + +/** + Snap horizontal resizing to min_w or min_w and a multiple of inc_w. + */ +class Fd_Snap_Widget_Ideal_Width : public Snap_Action { +public: + Fd_Snap_Widget_Ideal_Width() { type = 1; mask = FD_LEFT|FD_RIGHT; } + void check(Snap_Data &d) override { + auto layout = Fluid.proj.layout; + clr(); + if (!d.wgt) return; + int iw = 15, ih = 15; + d.wgt->ideal_size(iw, ih); + if (d.drag == FD_RIGHT) { + check_x_(d, d.br, d.bx+iw); + iw = layout->widget_min_w; + if (iw > 0) iw = nearest(d.br-d.bx+d.dx, layout->widget_min_w, layout->widget_inc_w); + check_x_(d, d.br, d.bx+iw); + } else { + check_x_(d, d.bx, d.br-iw); + iw = layout->widget_min_w; + if (iw > 0) iw = nearest(d.br-d.bx-d.dx, layout->widget_min_w, layout->widget_inc_w); + check_x_(d, d.bx, d.br-iw); + } + } + void draw(Snap_Data &d) override { + draw_width(d.bx, d.bt+7, d.br, 0); + }; +}; +Fd_Snap_Widget_Ideal_Width snap_widget_ideal_width; + +class Fd_Snap_Widget_Ideal_Height : public Snap_Action { +public: + Fd_Snap_Widget_Ideal_Height() { type = 2; mask = FD_TOP|FD_BOTTOM; } + void check(Snap_Data &d) override { + auto layout = Fluid.proj.layout; + clr(); + if (!d.wgt) return; + int iw, ih; + d.wgt->ideal_size(iw, ih); + if (d.drag == FD_BOTTOM) { + check_y_(d, d.bt, d.by+ih); + ih = layout->widget_min_h; + if (ih > 0) ih = nearest(d.bt-d.by+d.dy, layout->widget_min_h, layout->widget_inc_h); + check_y_(d, d.bt, d.by+ih); + } else { + check_y_(d, d.by, d.bt-ih); + ih = layout->widget_min_h; + if (ih > 0) ih = nearest(d.bt-d.by-d.dy, layout->widget_min_h, layout->widget_inc_h); + check_y_(d, d.by, d.bt-ih); + } + } + void draw(Snap_Data &d) override { + draw_height(d.br+7, d.by, d.bt, 0); + }; +}; +Fd_Snap_Widget_Ideal_Height snap_widget_ideal_height; + +// ---- snap actions list ---------------------------------------------- MARK: - + +/** + /brief The list of all snap actions available to FLUID. + New snap actions can be appended to the list. If multiple snap actions + with different coordinates, but the same snap distance are found, the last + action in the list wins. All snap actions with the same distance and same + winning coordinates are drawn in the overlay plane. + */ +Snap_Action *Snap_Action::list[] = { + &snap_left_window_edge, + &snap_right_window_edge, + &snap_top_window_edge, + &snap_bottom_window_edge, + + &snap_left_window_margin, + &snap_right_window_margin, + &snap_top_window_margin, + &snap_bottom_window_margin, + + &snap_window_grid, + &snap_group_grid, + + &snap_left_group_edge, + &snap_right_group_edge, + &snap_top_group_edge, + &snap_bottom_group_edge, + + &snap_left_group_margin, + &snap_right_group_margin, + &snap_top_group_margin, + &snap_bottom_group_margin, + + &snap_top_tabs_margin, + &snap_bottom_tabs_margin, + + &snap_siblings_left_same, &snap_siblings_left, + &snap_siblings_right_same, &snap_siblings_right, + &snap_siblings_top_same, &snap_siblings_top, + &snap_siblings_bottom_same, &snap_siblings_bottom, + + &snap_widget_ideal_width, + &snap_widget_ideal_height, + + nullptr +}; + +// ---- draw alignment marks ------------------------------------------- MARK: - + +static void draw_v_arrow(int x, int y1, int y2) { + int dy = (y1>y2) ? -1 : 1 ; + fl_yxline(x, y1, y2); + fl_xyline(x-4, y2, x+4); + fl_line(x-2, y2-dy*5, x, y2-dy); + fl_line(x+2, y2-dy*5, x, y2-dy); +} + +static void draw_h_arrow(int x1, int y, int x2) { + int dx = (x1>x2) ? -1 : 1 ; + fl_xyline(x1, y, x2); + fl_yxline(x2, y-4, y+4); + fl_line(x2-dx*5, y-2, x2-dx, y); + fl_line(x2-dx*5, y+2, x2-dx, y); +} + +static void draw_top_brace(const Fl_Widget *w) { + int x = w->as_window() ? 0 : w->x(); + int y = w->as_window() ? 0 : w->y(); + fl_yxline(x, y-2, y+6); + fl_yxline(x+w->w()-1, y-2, y+6); + fl_xyline(x-2, y, x+w->w()+1); +} + +static void draw_left_brace(const Fl_Widget *w) { + int x = w->as_window() ? 0 : w->x(); + int y = w->as_window() ? 0 : w->y(); + fl_xyline(x-2, y, x+6); + fl_xyline(x-2, y+w->h()-1, x+6); + fl_yxline(x, y-2, y+w->h()+1); +} + +static void draw_right_brace(const Fl_Widget *w) { + int x = w->as_window() ? w->w() - 1 : w->x() + w->w() - 1; + int y = w->as_window() ? 0 : w->y(); + fl_xyline(x-6, y, x+2); + fl_xyline(x-6, y+w->h()-1, x+2); + fl_yxline(x, y-2, y+w->h()+1); +} + +static void draw_bottom_brace(const Fl_Widget *w) { + int x = w->as_window() ? 0 : w->x(); + int y = w->as_window() ? w->h() - 1 : w->y() + w->h() - 1; + fl_yxline(x, y-6, y+2); + fl_yxline(x+w->w()-1, y-6, y+2); + fl_xyline(x-2, y, x+w->w()+1); +} + +void draw_height(int x, int y, int b, Fl_Align a) { + char buf[16]; + int h = b - y; + sprintf(buf, "%d", h); + fl_font(FL_HELVETICA, 9); + int lw = (int)fl_width(buf); + int lx; + + b --; + if (h < 30) { + // Move height to the side... + if (a == FL_ALIGN_LEFT) lx = x - lw - 2; + else lx = x + 2; + fl_yxline(x, y, b); + } else { + // Put height inside the arrows... + if (a == FL_ALIGN_LEFT) lx = x - lw + 2; + else lx = x - lw / 2; + fl_yxline(x, y, y + (h - 11) / 2); + fl_yxline(x, y + (h + 11) / 2, b); + } + + // Draw the height... + fl_draw(buf, lx, y + (h + 7) / 2); + + // Draw the arrowheads... + fl_line(x-2, y+5, x, y+1, x+2, y+5); + fl_line(x-2, b-5, x, b-1, x+2, b-5); + + // Draw the end lines... + fl_xyline(x - 4, y, x + 4); + fl_xyline(x - 4, b, x + 4); +} + +void draw_width(int x, int y, int r, Fl_Align a) { + char buf[16]; + int w = r-x; + sprintf(buf, "%d", w); + fl_font(FL_HELVETICA, 9); + int lw = (int)fl_width(buf); + int ly = y + 4; + + r--; + + if (lw > (w - 20)) { + // Move width above/below the arrows... + if (a == FL_ALIGN_TOP) ly -= 10; + else ly += 10; + + fl_xyline(x, y, r); + } else { + // Put width inside the arrows... + fl_xyline(x, y, x + (w - lw - 2) / 2); + fl_xyline(x + (w + lw + 2) / 2, y, r); + } + + // Draw the width... + fl_draw(buf, x + (w - lw) / 2, ly-2); + + // Draw the arrowheads... + fl_line(x+5, y-2, x+1, y, x+5, y+2); + fl_line(r-5, y-2, r-1, y, r-5, y+2); + + // Draw the end lines... + fl_yxline(x, y - 4, y + 4); + fl_yxline(r, y - 4, y + 4); +} + +static void draw_grid(int x, int y, int dx, int dy) { + int dx2 = 1, dy2 = 1; + const int n = 2; + for (int i=-n; i<=n; i++) { + for (int j=-n; j<=n; j++) { + if (abs(i)+abs(j) < 4) { + int xx = x + i*dx , yy = y + j*dy; + fl_xyline(xx-dx2, yy, xx+dx2); + fl_yxline(xx, yy-dy2, yy+dy2); + } + } + } +} diff --git a/fluid/app/Snap_Action.h b/fluid/app/Snap_Action.h new file mode 100644 index 000000000..e555f9a44 --- /dev/null +++ b/fluid/app/Snap_Action.h @@ -0,0 +1,207 @@ +// +// Snap action header file for the Fast Light Tool Kit (FLTK). +// +// Copyright 2023-2025 by Bill Spitzak and others. +// +// This library is free software. Distribution and use rights are outlined in +// the file "COPYING" which should have been included with this file. If this +// file is missing or damaged, see the license at: +// +// https://www.fltk.org/COPYING.php +// +// Please see the following page on how to report bugs and issues: +// +// https://www.fltk.org/bugs.php +// + +#ifndef _FLUID_FD_SNAP_ACTION_H +#define _FLUID_FD_SNAP_ACTION_H + +#include + +class Window_Node; +class Widget_Node; +struct Fl_Menu_Item; +class Fl_Preferences; + +extern Fl_Menu_Item main_layout_submenu_[]; + +namespace fld { +enum class Tool_Store; // fld:: +namespace io { +class Project_Reader; // fld::io:: +class Project_Writer; // fld::io:: +} // namespace io +} // namespace fld + +namespace fld { +namespace app { + +/** + \brief Collection of layout settings. + + Presets contain default fonts and font sizes for labels and text. They + can be used to guide widget positions using margins, grids, and gap sizes. + There are three Presets available in one Suite, marked "application", + "dialog", and "toolbox". + */ +class Layout_Preset { +public: + int left_window_margin; ///< gap between the window border and the widget + int right_window_margin; + int top_window_margin; + int bottom_window_margin; + int window_grid_x; ///< a regular grid across the window with its origin in the top left window corner + int window_grid_y; + + int left_group_margin; ///< gap between the border of a widget and its parent group + int right_group_margin; + int top_group_margin; + int bottom_group_margin; + int group_grid_x; ///< a regular grid across the group with its origin in the top left group corner + int group_grid_y; + + int top_tabs_margin; ///< preferred top edge tab size inside Fl_Tabs + int bottom_tabs_margin; ///< preferred bottom edge tab size inside Fl_Tabs + + int widget_min_w; ///< minimum widget width + int widget_inc_w; ///< widget width increments starting from widget_min_w + int widget_gap_x; ///< preferred horizontal gap between widgets + int widget_min_h; + int widget_inc_h; + int widget_gap_y; + + int labelfont; ///< preferred font for labels + int labelsize; ///< preferred size for labels + int textfont; ///< preferred font for text elements + int textsize; ///< preferred size for text elements + + void write(Fl_Preferences &prefs); + void read(Fl_Preferences &prefs); + void write(fld::io::Project_Writer*); + void read(fld::io::Project_Reader*); + + int textsize_not_null(); +}; + +extern Layout_Preset *default_layout_preset; + +/** + \brief A collection of layout presets. + + A suite of layout presets is designed to cover various use cases when + designing UI layouts for applications. + There are three Presets available in one Suite, marked "application", + "dialog", and "toolbox". + */ +class Layout_Suite { +public: + char *name_; ///< name of the suite + char *menu_label; ///< label text used in pulldown menu + Layout_Preset *layout[3]; ///< presets for application, dialog, and toolbox windows + fld::Tool_Store storage_; ///< storage location (see fld::Tool_Store::INTERNAL, etc.) + void write(Fl_Preferences &prefs); + void read(Fl_Preferences &prefs); + void write(fld::io::Project_Writer*); + void read(fld::io::Project_Reader*); + void update_label(); + void storage(fld::Tool_Store s) { storage_ = s; update_label(); } + void name(const char *n); + void init(); + ~Layout_Suite(); +public: + +}; + + +/** + \brief Manage all layout suites that are available to the user. + + FLUID has two built-in suites. More suites can be cloned or added and stored + as a user preference, as part of an .fl project file, or in a separate file + for import/export and sharing. + */ +class Layout_List { +public: + Fl_Menu_Item *main_menu_; + Fl_Menu_Item *choice_menu_; + Layout_Suite *list_; + int list_size_; + int list_capacity_; + bool list_is_static_; + int current_suite_; + int current_preset_; + std::string filename_; +public: + Layout_List(); + ~Layout_List(); + void update_dialogs(); + void update_menu_labels(); + int current_suite() const { return current_suite_; } + void current_suite(int ix); + void current_suite(std::string); + int current_preset() const { return current_preset_; } + void current_preset(int ix); + Layout_Suite &operator[](int ix) { return list_[ix]; } + int add(const char *name); + void rename(const char *name); + void capacity(int); + + int load(const std::string &filename); + int save(const std::string &filename); + void write(Fl_Preferences &prefs, fld::Tool_Store storage); + void read(Fl_Preferences &prefs, fld::Tool_Store storage); + void write(fld::io::Project_Writer*); + void read(fld::io::Project_Reader*); + int add(Layout_Suite*); + void remove(int index); + void remove_all(fld::Tool_Store storage); + Layout_Preset *at(int); + int size(); +}; + +/** + \brief Structure holding all the data to perform interactive alignment operations. + */ +typedef struct Snap_Data { + int dx, dy; ///< distance of the mouse from its initial PUSH event + int bx, by, br, bt; ///< bounding box of the original push event or current bounding box when drawing + int drag; ///< drag event mask + int x_dist, y_dist; ///< current closest snapping distance in x and y + int dx_out, dy_out; ///< current closest snapping point as a delta + Widget_Node *wgt; ///< first selected widget + Window_Node *win; ///< window that handles the drag action + int ex_out, ey_out; ///< chosen snap position +} Snap_Data; + +/** + \brief Find points of interest when moving the bounding box of all selected widgets. + */ +class Snap_Action { +protected: + int check_x_(Snap_Data &d, int x_ref, int x_snap); + int check_y_(Snap_Data &d, int y_ref, int y_snap); + void check_x_y_(Snap_Data &d, int x_ref, int x_snap, int y_ref, int y_snap); + void clr() { ex = dx = 0x7fff; } +public: + int ex, ey, dx, dy, type, mask; + Snap_Action() : ex(0x7fff), ey(0x7fff), dx(128), dy(128), type(0), mask(0) { } + virtual ~Snap_Action() { } + virtual void check(Snap_Data &d) = 0; + virtual void draw(Snap_Data &d) { } + virtual bool matches(Snap_Data &d); +public: + static int eex, eey; + static Snap_Action *list[]; + static void check_all(Snap_Data &d); + static void draw_all(Snap_Data &d); + static void get_resize_stepsize(int &x_step, int &y_step); + static void get_move_stepsize(int &x_step, int &y_step); + static void better_size(int &w, int &h); +}; + +} // namespace app +} // namespace fld + + +#endif // _FLUID_FD_SNAP_ACTION_H diff --git a/fluid/app/align_widget.cxx b/fluid/app/align_widget.cxx deleted file mode 100644 index a9badf9e4..000000000 --- a/fluid/app/align_widget.cxx +++ /dev/null @@ -1,414 +0,0 @@ -// -// Alignment code for the Fast Light Tool Kit (FLTK). -// -// Copyright 1998-2023 by Bill Spitzak and others. -// -// This library is free software. Distribution and use rights are outlined in -// the file "COPYING" which should have been included with this file. If this -// file is missing or damaged, see the license at: -// -// https://www.fltk.org/COPYING.php -// -// Please see the following page on how to report bugs and issues: -// -// https://www.fltk.org/bugs.php -// - -#include "app/align_widget.h" - -#include "app/fluid.h" -#include "app/undo.h" -#include "nodes/Fl_Group_Type.h" - -#include -#include - -/** - the first behavior always uses the first selected widget as a reference - the second behavior uses the largest widget (most extreme positions) as - a reference. - */ -#define BREAK_ON_FIRST break -//#define BREAK_ON_FIRST - -void align_widget_cb(Fl_Widget*, long how) -{ - const int max = 32768, min = -32768; - int left, right, top, bot, wdt, hgt, n; - Fl_Type *o; - int changed = 0; - switch ( how ) - { - //---- align - case 10: // align left - left = max; - for (o = Fl_Type::first; o; o = o->next) - if (o->selected && o->is_widget()) - { - Fl_Widget *w = ((Fl_Widget_Type *)o)->o; - if (w->x()x(); - BREAK_ON_FIRST; - } - if (left!=max) - for (Fl_Type *o = Fl_Type::first; o; o = o->next) - if (o->selected && o->is_widget()) - { - if (!changed) { - changed = 1; - undo_checkpoint(); - } - Fl_Widget *w = ((Fl_Widget_Type *)o)->o; - Fl_Type::allow_layout++; - w->resize(left, w->y(), w->w(), w->h()); - Fl_Type::allow_layout--; - w->redraw(); - if (w->window()) w->window()->redraw(); - } - break; - case 11: // align h.center - left = max; right = min; - for (o = Fl_Type::first; o; o = o->next) - if (o->selected && o->is_widget()) - { - Fl_Widget *w = ((Fl_Widget_Type *)o)->o; - if (w->x()x(); - if (w->x()+w->w()>right) - right = w->x()+w->w(); - BREAK_ON_FIRST; - } - if (left!=max) - { - int center2 = left+right; - for (Fl_Type *o = Fl_Type::first; o; o = o->next) - if (o->selected && o->is_widget()) - { - if (!changed) { - changed = 1; - undo_checkpoint(); - } - Fl_Widget *w = ((Fl_Widget_Type *)o)->o; - Fl_Type::allow_layout++; - w->resize((center2-w->w())/2, w->y(), w->w(), w->h()); - Fl_Type::allow_layout--; - w->redraw(); - if (w->window()) w->window()->redraw(); - } - } - break; - case 12: // align right - right = min; - for (o = Fl_Type::first; o; o = o->next) - if (o->selected && o->is_widget()) - { - Fl_Widget *w = ((Fl_Widget_Type *)o)->o; - if (w->x()+w->w()>right) - right = w->x()+w->w(); - BREAK_ON_FIRST; - } - if (right!=min) - for (Fl_Type *o = Fl_Type::first; o; o = o->next) - if (o->selected && o->is_widget()) - { - if (!changed) { - changed = 1; - undo_checkpoint(); - } - Fl_Widget *w = ((Fl_Widget_Type *)o)->o; - Fl_Type::allow_layout++; - w->resize(right-w->w(), w->y(), w->w(), w->h()); - Fl_Type::allow_layout--; - w->redraw(); - if (w->window()) w->window()->redraw(); - } - break; - case 13: // align top - top = max; - for (o = Fl_Type::first; o; o = o->next) - if (o->selected && o->is_widget()) - { - Fl_Widget *w = ((Fl_Widget_Type *)o)->o; - if (w->y()y(); - BREAK_ON_FIRST; - } - if (top!=max) - for (Fl_Type *o = Fl_Type::first; o; o = o->next) - if (o->selected && o->is_widget()) - { - if (!changed) { - changed = 1; - undo_checkpoint(); - } - Fl_Widget *w = ((Fl_Widget_Type *)o)->o; - Fl_Type::allow_layout++; - w->resize(w->x(), top, w->w(), w->h()); - Fl_Type::allow_layout--; - w->redraw(); - if (w->window()) w->window()->redraw(); - } - break; - case 14: // align v.center - top = max; bot = min; - for (o = Fl_Type::first; o; o = o->next) - if (o->selected && o->is_widget()) - { - Fl_Widget *w = ((Fl_Widget_Type *)o)->o; - if (w->y()y(); - if (w->y()+w->h()>bot) - bot = w->y()+w->h(); - BREAK_ON_FIRST; - } - if (top!=max) - { - int center2 = top+bot; - for (Fl_Type *o = Fl_Type::first; o; o = o->next) - if (o->selected && o->is_widget()) - { - if (!changed) { - changed = 1; - undo_checkpoint(); - } - Fl_Widget *w = ((Fl_Widget_Type *)o)->o; - Fl_Type::allow_layout++; - w->resize(w->x(), (center2-w->h())/2, w->w(), w->h()); - Fl_Type::allow_layout--; - w->redraw(); - if (w->window()) w->window()->redraw(); - } - } - break; - case 15: // align bottom - bot = min; - for (o = Fl_Type::first; o; o = o->next) - if (o->selected && o->is_widget()) - { - Fl_Widget *w = ((Fl_Widget_Type *)o)->o; - if (w->y()+w->h()>bot) - bot = w->y()+w->h(); - BREAK_ON_FIRST; - } - if (bot!=min) - for (Fl_Type *o = Fl_Type::first; o; o = o->next) - if (o->selected && o->is_widget()) - { - if (!changed) { - changed = 1; - undo_checkpoint(); - } - Fl_Widget *w = ((Fl_Widget_Type *)o)->o; - Fl_Type::allow_layout++; - w->resize( w->x(), bot-w->h(), w->w(), w->h()); - Fl_Type::allow_layout--; - w->redraw(); - if (w->window()) w->window()->redraw(); - } - break; - //---- space evenly - case 20: // space evenly across - left = max; right = min; wdt = 0; n = 0; - for (o = Fl_Type::first; o; o = o->next) - if (o->selected && o->is_widget()) - { - Fl_Widget *w = ((Fl_Widget_Type *)o)->o; - if (w->x()x(); - if (w->x()+w->w()>right) - right = w->x()+w->w(); - wdt += w->w(); - n++; - } - wdt = (right-left)-wdt; - n--; - if (n>0) - { - wdt = wdt/n*n; // make sure that all gaps are the same, possibly moving the rightmost widget - int cnt = 0, wsum = 0; - for (Fl_Type *o = Fl_Type::first; o; o = o->next) - if (o->selected && o->is_widget()) - { - if (!changed) { - changed = 1; - undo_checkpoint(); - } - Fl_Widget *w = ((Fl_Widget_Type *)o)->o; - Fl_Type::allow_layout++; - w->resize(left+wsum+wdt*cnt/n, w->y(), w->w(), w->h()); - Fl_Type::allow_layout--; - w->redraw(); - if (w->window()) w->window()->redraw(); - cnt++; - wsum += w->w(); - } - } - break; - case 21: // space evenly down - top = max; bot = min; hgt = 0, n = 0; - for (o = Fl_Type::first; o; o = o->next) - if (o->selected && o->is_widget()) - { - Fl_Widget *w = ((Fl_Widget_Type *)o)->o; - if (w->y()y(); - if (w->y()+w->h()>bot) - bot = w->y()+w->h(); - hgt += w->h(); - n++; - } - hgt = (bot-top)-hgt; - n--; - if (n>0) - { - hgt = hgt/n*n; // make sure that all gaps are the same, possibly moving the rightmost widget - int cnt = 0, hsum = 0; - for (Fl_Type *o = Fl_Type::first; o; o = o->next) - if (o->selected && o->is_widget()) - { - if (!changed) { - changed = 1; - undo_checkpoint(); - } - Fl_Widget *w = ((Fl_Widget_Type *)o)->o; - Fl_Type::allow_layout++; - w->resize(w->x(), top+hsum+hgt*cnt/n, w->w(), w->h()); - Fl_Type::allow_layout--; - w->redraw(); - if (w->window()) w->window()->redraw(); - cnt++; - hsum += w->h(); - } - } - break; - //---- make same size - case 30: // same width - wdt = min; - for (o = Fl_Type::first; o; o = o->next) - if (o->selected && o->is_widget()) - { - Fl_Widget *w = ((Fl_Widget_Type *)o)->o; - if (w->w()>wdt) - wdt = w->w(); - BREAK_ON_FIRST; - } - if (wdt!=min) - for (Fl_Type *o = Fl_Type::first; o; o = o->next) - if (o->selected && o->is_widget()) - { - if (!changed) { - changed = 1; - undo_checkpoint(); - } - Fl_Widget *w = ((Fl_Widget_Type *)o)->o; - Fl_Type::allow_layout++; - w->resize(w->x(), w->y(), wdt, w->h()); - Fl_Type::allow_layout--; - w->redraw(); - if (w->window()) w->window()->redraw(); - } - break; - case 31: // same height - hgt = min; - for (o = Fl_Type::first; o; o = o->next) - if (o->selected && o->is_widget()) - { - Fl_Widget *w = ((Fl_Widget_Type *)o)->o; - if (w->h()>hgt) - hgt = w->h(); - BREAK_ON_FIRST; - } - if (hgt!=min) - for (Fl_Type *o = Fl_Type::first; o; o = o->next) - if (o->selected && o->is_widget()) - { - if (!changed) { - changed = 1; - undo_checkpoint(); - } - Fl_Widget *w = ((Fl_Widget_Type *)o)->o; - Fl_Type::allow_layout++; - w->resize( w->x(), w->y(), w->w(), hgt); - Fl_Type::allow_layout--; - w->redraw(); - if (w->window()) w->window()->redraw(); - } - break; - case 32: // same size - hgt = min; wdt = min; - for (o = Fl_Type::first; o; o = o->next) - if (o->selected && o->is_widget()) - { - Fl_Widget *w = ((Fl_Widget_Type *)o)->o; - if (w->w()>wdt) - wdt = w->w(); - if (w->h()>hgt) - hgt = w->h(); - BREAK_ON_FIRST; - } - if (hgt!=min) - for (Fl_Type *o = Fl_Type::first; o; o = o->next) - if (o->selected && o->is_widget()) - { - if (!changed) { - changed = 1; - undo_checkpoint(); - } - Fl_Widget *w = ((Fl_Widget_Type *)o)->o; - Fl_Type::allow_layout++; - w->resize( w->x(), w->y(), wdt, hgt); - Fl_Type::allow_layout--; - w->redraw(); - if (w->window()) w->window()->redraw(); - } - break; - //---- center in group - case 40: // center hor - for (o = Fl_Type::first; o; o = o->next) - if (o->selected && o->is_widget() && o->parent) - { - if (!changed) { - changed = 1; - undo_checkpoint(); - } - Fl_Widget *w = ((Fl_Widget_Type *)o)->o; - Fl_Widget *p = ((Fl_Widget_Type *)o->parent)->o; - int center2; - - if (w->window() == p) center2 = p->w(); - else center2 = 2*p->x()+p->w(); - - Fl_Type::allow_layout++; - w->resize((center2-w->w())/2, w->y(), w->w(), w->h()); - Fl_Type::allow_layout--; - w->redraw(); - if (w->window()) w->window()->redraw(); - } - break; - case 41: // center vert - for (o = Fl_Type::first; o; o = o->next) - if (o->selected && o->is_widget() && o->parent) - { - if (!changed) { - changed = 1; - undo_checkpoint(); - } - Fl_Widget *w = ((Fl_Widget_Type *)o)->o; - Fl_Widget *p = ((Fl_Widget_Type *)o->parent)->o; - int center2; - - if (w->window() == p) center2 = p->h(); - else center2 = 2*p->y()+p->h(); - - Fl_Type::allow_layout++; - w->resize(w->x(), (center2-w->h())/2, w->w(), w->h()); - Fl_Type::allow_layout--; - set_modflag(1); - w->redraw(); - if (w->window()) w->window()->redraw(); - } - break; - } - if (changed) - set_modflag(1); -} diff --git a/fluid/app/align_widget.h b/fluid/app/align_widget.h deleted file mode 100644 index f04372215..000000000 --- a/fluid/app/align_widget.h +++ /dev/null @@ -1,24 +0,0 @@ -// -// FLUID main entry for the Fast Light Tool Kit (FLTK). -// -// Copyright 1998-2021 by Bill Spitzak and others. -// -// This library is free software. Distribution and use rights are outlined in -// the file "COPYING" which should have been included with this file. If this -// file is missing or damaged, see the license at: -// -// https://www.fltk.org/COPYING.php -// -// Please see the following page on how to report bugs and issues: -// -// https://www.fltk.org/bugs.php -// - -#ifndef _FLUID_ALIGN_WIDGET_H -#define _FLUID_ALIGN_WIDGET_H - -class Fl_Widget; - -void align_widget_cb(Fl_Widget *, long); - -#endif // _FLUID_ALIGN_WIDGET_H diff --git a/fluid/app/args.cxx b/fluid/app/args.cxx new file mode 100644 index 000000000..b9d958e8c --- /dev/null +++ b/fluid/app/args.cxx @@ -0,0 +1,136 @@ +// +// Command Line Arguments Handling code for the Fast Light Tool Kit (FLTK). +// +// Copyright 1998-2025 by Bill Spitzak and others. +// +// This library is free software. Distribution and use rights are outlined in +// the file "COPYING" which should have been included with this file. If this +// file is missing or damaged, see the license at: +// +// https://www.fltk.org/COPYING.php +// +// Please see the following page on how to report bugs and issues: +// +// https://www.fltk.org/bugs.php +// + +#include "app/args.h" + +#include "Fluid.h" + +#include +#include +#include + +using namespace fld; +using namespace fld::app; + +/** + Load args from command line into variables. + + \param[in] argc number of arguments in the list + \param[in] argv pointer to an array of arguments + \return 0 if the args were handled successfully, -1 if there was an error + and the usage message was shown. + */ +int Args::load(int argc,char **argv) { + int i = 1; + Fl::args_to_utf8(argc, argv); // for MSYS2/MinGW + if ( (Fl::args(argc,argv,i,arg_cb) == 0) // unsupported argument found + || (Fluid.batch_mode && (i != argc-1)) // .fl filename missing + || (!Fluid.batch_mode && (i < argc-1)) // more than one filename found + || (argv[i] && (argv[i][0] == '-'))) { // unknown option + static const char *msg = + "usage: %s name.fl\n" + " -u : update .fl file and exit (may be combined with '-c' or '-cs')\n" + " -c : write .cxx and .h and exit\n" + " -cs : write .cxx and .h and strings and exit\n" + " -o : .cxx output filename, or extension if starts with '.'\n" + " -h : .h output filename, or extension if starts with '.'\n" + " --help : brief usage information\n" + " --version, -v : print fluid version number\n" + " -d : enable internal debugging\n"; + const char *app_name = nullptr; + if ( (argc > 0) && argv[0] && argv[0][0] ) + app_name = fl_filename_name(argv[0]); + if ( !app_name || !app_name[0]) + app_name = "fluid"; +#ifdef _MSC_VER + // TODO: if this is fluid-cmd, use stderr and not fl_message + fl_message(msg, app_name); +#else + fprintf(stderr, msg, app_name); +#endif + return -1; + } + return i; +} + + +int Args::arg_cb(int argc, char** argv, int& i) { + return Fluid.args.arg(argc, argv, i); +} + + +/** + Handle command line arguments. + \param[in] argc number of arguments in the list + \param[in] argv pointer to an array of arguments + \param[inout] i current argument index + \return number of arguments used; if 0, the argument is not supported + */ +int Args::arg(int argc, char** argv, int& i) { + if (argv[i][0] != '-') + return 0; + if (argv[i][1] == 'd' && !argv[i][2]) { + Fluid.debug_external_editor=1; + i++; return 1; + } + if (argv[i][1] == 'u' && !argv[i][2]) { + update_file++; + Fluid.batch_mode++; + i++; return 1; + } + if (argv[i][1] == 'c' && !argv[i][2]) { + compile_file++; + Fluid.batch_mode++; + i++; return 1; + } + if ((strcmp(argv[i], "-v")==0) || (strcmp(argv[i], "--version")==0)) { + show_version = 1; + i++; return 1; + } + if (argv[i][1] == 'c' && argv[i][2] == 's' && !argv[i][3]) { + compile_file++; + compile_strings++; + Fluid.batch_mode++; + i++; return 1; + } + if (argv[i][1] == 'o' && !argv[i][2] && i+1 < argc) { + code_filename = argv[i+1]; + Fluid.batch_mode++; + i += 2; return 2; + } +#ifndef NDEBUG + if ((i+1 < argc) && (strcmp(argv[i], "--autodoc") == 0)) { + autodoc_path = argv[i+1]; + i += 2; return 2; + } +#endif + if (strcmp(argv[i], "--help")==0) { + return 0; + } + if (argv[i][1] == 'h' && !argv[i][2]) { + if ( (i+1 < argc) && (argv[i+1][0] != '-') ) { + header_filename = argv[i+1]; + Fluid.batch_mode++; + i += 2; + return 2; + } else { + // a lone "-h" without a filename will output the help string + return 0; + } + } + return 0; +} + diff --git a/fluid/app/args.h b/fluid/app/args.h new file mode 100644 index 000000000..80dfa2290 --- /dev/null +++ b/fluid/app/args.h @@ -0,0 +1,55 @@ +// +// Command Line Arguments Handling header for the Fast Light Tool Kit (FLTK). +// +// Copyright 1998-2025 by Bill Spitzak and others. +// +// This library is free software. Distribution and use rights are outlined in +// the file "COPYING" which should have been included with this file. If this +// file is missing or damaged, see the license at: +// +// https://www.fltk.org/COPYING.php +// +// Please see the following page on how to report bugs and issues: +// +// https://www.fltk.org/bugs.php +// + +#ifndef FLUID_APP_ARGS_H +#define FLUID_APP_ARGS_H + +#include + +namespace fld { +namespace app { + +class Args { + // Callback. + static int arg_cb(int argc, char** argv, int& i); + // Handle args individually. + int arg(int argc, char** argv, int& i); +public: + /// Set, if Fluid was started with the command line argument -u + int update_file { 0 }; // fluid -u + /// Set, if Fluid was started with the command line argument -c + int compile_file { 0 }; // fluid -c + /// Set, if Fluid was started with the command line argument -cs + int compile_strings { 0 }; // fluid -cs + /// command line arguments that overrides the generate code file extension or name + std::string code_filename { }; // fluid -o filename + /// command line arguments that overrides the generate header file extension or name + std::string header_filename { }; // fluid -h filename + /// if set, generate images for automatic documentation in this directory + std::string autodoc_path { }; // fluid --autodoc path + /// Set, if Fluid was started with the command line argument -v + int show_version { 0 }; // fluid -v + /// Constructor. + Args() = default; + // Load args from command line into variables. + int load(int argc, char **argv); +}; + +} // namespace app +} // namespace fld + +#endif // FLUID_APP_ARGS_H + diff --git a/fluid/app/fluid.cxx b/fluid/app/fluid.cxx deleted file mode 100644 index 91c28d0e6..000000000 --- a/fluid/app/fluid.cxx +++ /dev/null @@ -1,2169 +0,0 @@ -// -// FLUID main entry for the Fast Light Tool Kit (FLTK). -// -// Copyright 1998-2025 by Bill Spitzak and others. -// -// This library is free software. Distribution and use rights are outlined in -// the file "COPYING" which should have been included with this file. If this -// file is missing or damaged, see the license at: -// -// https://www.fltk.org/COPYING.php -// -// Please see the following page on how to report bugs and issues: -// -// https://www.fltk.org/bugs.php -// - -#include "app/fluid.h" - -#include "app/project.h" -#include "app/mergeback.h" -#include "app/undo.h" -#include "io/Project_Reader.h" -#include "io/Project_Writer.h" -#include "io/Code_Writer.h" -#include "nodes/Fl_Type.h" -#include "nodes/Fl_Function_Type.h" -#include "nodes/Fl_Group_Type.h" -#include "nodes/Fl_Window_Type.h" -#include "nodes/factory.h" -#include "panels/settings_panel.h" -#include "panels/function_panel.h" -#include "panels/codeview_panel.h" -#include "panels/template_panel.h" -#include "panels/about_panel.h" -#include "rsrcs/pixmaps.h" -#include "app/shell_command.h" -#include "tools/autodoc.h" -#include "widgets/Node_Browser.h" - -#include -#ifdef __APPLE__ -#include // for fl_open_callback -#endif -#include -#include -#include -#include -#include -#include -#include // setlocale().. -#include "../src/flstring.h" - -extern "C" -{ -#if defined(HAVE_LIBPNG) && defined(HAVE_LIBZ) -# include -# ifdef HAVE_PNG_H -# include -# else -# include -# endif // HAVE_PNG_H -#endif // HAVE_LIBPNG && HAVE_LIBZ -} - -/// \defgroup globals Fluid Global Variables, Functions and Callbacks -/// \{ - -// -// Globals.. -// - -/// FLUID-wide help dialog. -static Fl_Help_Dialog *help_dialog = NULL; - -/// Main app window menu bar. -Fl_Menu_Bar *main_menubar = NULL; - -/// Main app window. -Fl_Window *main_window; - -/// Fluid application preferences, always accessible, will be flushed when app closes. -Fl_Preferences fluid_prefs(Fl_Preferences::USER_L, "fltk.org", "fluid"); - -/// Show guides in the design window when positioning widgets, saved in app preferences. -int show_guides = 1; - -/// Show areas of restricted use in overlay plane. -/// Restricted areas are widget that overlap each other, widgets that are outside -/// of their parent's bounds (except children of Scroll groups), and areas -/// within an Fl_Tile that are not covered by children. -int show_restricted = 1; - -/// Show a ghosted outline for groups that have very little contrast. -/// This makes groups with NO_BOX or FLAT_BOX better editable. -int show_ghosted_outline = 1; - -/// Show widget comments in the browser, saved in app preferences. -int show_comments = 1; - -/// Use external editor for editing Fl_Code_Type, saved in app preferences. -int G_use_external_editor = 0; - -/// Debugging help for external Fl_Code_Type editor. -int G_debug = 0; - -/// Run this command to load an Fl_Code_Type into an external editor, save in app preferences. -char G_external_editor_command[512]; - - -// File history info... - -/// Stores the absolute filename of the last 10 design files, saved in app preferences. -char absolute_history[10][FL_PATH_MAX]; - -/// This list of filenames is computed from \c absolute_history and displayed in the main menu. -char relative_history[10][FL_PATH_MAX]; - -/// Menuitem to save a .fl design file, will be deactivated if the design is unchanged. -Fl_Menu_Item *save_item = NULL; - -/// First Menuitem that shows the .fl design file history. -Fl_Menu_Item *history_item = NULL; - -/// Menuitem to show or hide the widget bin, label will change if bin is visible. -Fl_Menu_Item *widgetbin_item = NULL; - -/// Menuitem to show or hide the code view, label will change if view is visible. -Fl_Menu_Item *codeview_item = NULL; - -/// Menuitem to show or hide the editing overlay, label will change if overlay visibility changes. -Fl_Menu_Item *overlay_item = NULL; - -/// Menuitem to show or hide the editing guides, label will change if overlay visibility changes. -Fl_Menu_Item *guides_item = NULL; - -/// Menuitem to show or hide the restricted area overlys, label will change if overlay visibility changes. -Fl_Menu_Item *restricted_item = NULL; - -//////////////////////////////////////////////////////////////// - -/// Set if the current design has been modified compared to the associated .fl design file. -int modflag = 0; - -/// Set if the code files are older than the current design. -int modflag_c = 0; - -/// Application work directory, stored here when temporarily changing to the source code directory. -/// \see goto_source_dir() -static std::string app_work_dir; - -/// Used as a counter to set the .fl project dir as the current directory. -/// \see enter_project_dir(), leave_project_dir() -static char in_project_dir = 0; - -/// Set, if Fluid was started with the command line argument -u -int update_file = 0; // fluid -u - -/// Set, if Fluid was started with the command line argument -c -int compile_file = 0; // fluid -c - -/// Set, if Fluid was started with the command line argument -cs -int compile_strings = 0; // fluid -cs - -/// Set, if Fluid was started with the command line argument -v -int show_version = 0; // fluid -v - -/// Set, if Fluid runs in batch mode, and no user interface is activated. -int batch_mode = 0; // if set (-c, -u) don't open display - -/// command line arguments that overrides the generate code file extension or name -std::string g_code_filename_arg; - -/// command line arguments that overrides the generate header file extension or name -std::string g_header_filename_arg; - -/// current directory path at application launch -std::string g_launch_path; - -/// if set, generate images for automatic documentation in this directory -std::string g_autodoc_path; - -/// path to store temporary files during app run -/// \see tmpdir_create_called -std::string tmpdir_path; - -/// true if the temporary file path was already created -/// \see tmpdir_path -bool tmpdir_create_called = false; - - -/// Offset in pixels when adding widgets from an .fl file. -int pasteoffset = 0; - -/// Paste offset incrementing at every paste command. -static int ipasteoffset = 0; - - -/** - Make sure that a path name ends with a forward slash. - \param[in] str directory or path name - \return a new string, ending with a '/' - */ -std::string end_with_slash(const std::string &str) { - char last = str[str.size()-1]; - if (last !='/' && last != '\\') - return str + "/"; - else - return str; -} - -/** - Generate a path to a directory for temporary data storage. - The path is stored in g_tmpdir. - */ -static void create_tmpdir() { - if (tmpdir_create_called) - return; - tmpdir_create_called = true; - - char buf[128]; -#if _WIN32 - // The usual temp file locations on Windows are - // %system%\Windows\Temp - // %userprofiles%\AppData\Local - // usually resolving into - // C:/Windows/Temp/ - // C:\Users\\AppData\Local\Temp - fl_snprintf(buf, sizeof(buf)-1, "fluid-%d/", (long)GetCurrentProcessId()); - std::string name = buf; - wchar_t tempdirW[FL_PATH_MAX+1]; - char tempdir[FL_PATH_MAX+1]; - unsigned len = GetTempPathW(FL_PATH_MAX, tempdirW); - if (len == 0) { - strcpy(tempdir, "c:/windows/temp/"); - } else { - unsigned wn = fl_utf8fromwc(tempdir, FL_PATH_MAX, tempdirW, len); - tempdir[wn] = 0; - } - std::string path = tempdir; - end_with_slash(path); - path += name; - fl_make_path(path.c_str()); - if (fl_access(path.c_str(), 6) == 0) tmpdir_path = path; -#else - fl_snprintf(buf, sizeof(buf)-1, "fluid-%d/", getpid()); - std::string name = buf; - auto path_temp = fl_getenv("TMPDIR"); - std::string path = path_temp ? path_temp : ""; - if (!path.empty()) { - end_with_slash(path); - path += name; - fl_make_path(path.c_str()); - if (fl_access(path.c_str(), 6) == 0) tmpdir_path = path; - } - if (tmpdir_path.empty()) { - path = std::string("/tmp/") + name; - fl_make_path(path.c_str()); - if (fl_access(path.c_str(), 6) == 0) tmpdir_path = path; - } -#endif - if (tmpdir_path.empty()) { - char pbuf[FL_PATH_MAX+1]; - fluid_prefs.get_userdata_path(pbuf, FL_PATH_MAX); - path = std::string(pbuf); - end_with_slash(path); - path += name; - fl_make_path(path.c_str()); - if (fl_access(path.c_str(), 6) == 0) tmpdir_path = path; - } - if (tmpdir_path.empty()) { - if (batch_mode) { - fprintf(stderr, "ERROR: Can't create directory for temporary data storage.\n"); - } else { - fl_alert("Can't create directory for temporary data storage."); - } - } -} - -/** - Delete the temporary directory that was created in set_tmpdir. - */ -static void delete_tmpdir() { - // was a temporary directory created - if (!tmpdir_create_called) - return; - if (tmpdir_path.empty()) - return; - - // first delete all files that may still be left in the temp directory - struct dirent **de; - int n_de = fl_filename_list(tmpdir_path.c_str(), &de); - if (n_de >= 0) { - for (int i=0; id_name; - fl_unlink(path.c_str()); - } - fl_filename_free_list(&de, n_de); - } - - // then delete the directory itself - if (fl_rmdir(tmpdir_path.c_str()) < 0) { - if (batch_mode) { - fprintf(stderr, "WARNING: Can't delete tmpdir '%s': %s", tmpdir_path.c_str(), strerror(errno)); - } else { - fl_alert("WARNING: Can't delete tmpdir '%s': %s", tmpdir_path.c_str(), strerror(errno)); - } - } -} - -/** - Return the path to a temporary directory for this instance of FLUID. - Fluid will do its best to clear and delete this directory when exiting. - \return the path to the temporary directory, ending in a '/', or and empty - string if no directory could be created. - */ -const std::string &get_tmpdir() { - if (!tmpdir_create_called) - create_tmpdir(); - return tmpdir_path; -} - -/** - Give the user the opportunity to save a project before clearing it. - - If the project has unsaved changes, this function pops up a dialog, that - allows the user to save the project, continue without saving the project, - or to cancel the operation. - - If the user chooses to save, and no filename was set, a file dialog allows - the user to pick a name and location, or to cancel the operation. - - \return false if the user aborted the operation and the calling function - should abort as well - */ -bool confirm_project_clear() { - if (modflag == 0) return true; - switch (fl_choice("This project has unsaved changes. Do you want to save\n" - "the project file before proceeding?", - "Cancel", "Save", "Don't Save")) - { - case 0 : /* Cancel */ - return false; - case 1 : /* Save */ - save_cb(NULL, NULL); - if (modflag) return false; // user canceled the "Save As" dialog - } - return true; -} - -// ---- - -extern Fl_Window *the_panel; - -/** - Ensure that text widgets in the widget panel propagates apply current changes. - By temporarily clearing the text focus, all text widgets with changed text - will unfocus and call their respective callbacks, propagating those changes to - their data set. - */ -void flush_text_widgets() { - if (Fl::focus() && (Fl::focus()->top_window() == the_panel)) { - Fl_Widget *old_focus = Fl::focus(); - Fl::focus(NULL); // trigger callback of the widget that is losing focus - Fl::focus(old_focus); - } -} - -// ---- - -/** - Change the current working directory to the .fl project directory. - - Every call to enter_project_dir() must have a corresponding leave_project_dir() - call. Enter and leave calls can be nested. - - The first call to enter_project_dir() remembers the original directory, usually - the launch directory of the application. Nested calls will increment a nesting - counter. When the nesting counter is back to 0, leave_project_dir() will return - to the original directory. - - The global variable 'filename' must be set to the current project file with - absolute or relative path information. - - \see leave_project_dir(), pwd, in_project_dir - */ -void enter_project_dir() { - if (in_project_dir<0) { - fprintf(stderr, "** Fluid internal error: enter_project_dir() calls unmatched\n"); - return; - } - in_project_dir++; - // check if we are already in the project dir and do nothing if so - if (in_project_dir>1) return; - // check if there is an active project, and do nothing if there is none - if (!g_project.proj_filename || !*g_project.proj_filename) { - fprintf(stderr, "** Fluid internal error: enter_project_dir() no filename set\n"); - return; - } - // store the current working directory for later - app_work_dir = fl_getcwd_str(); - // set the current directory to the path of our .fl file - std::string project_path = fl_filename_path_str(fl_filename_absolute_str(g_project.proj_filename)); - if (fl_chdir(project_path.c_str()) == -1) { - fprintf(stderr, "** Fluid internal error: enter_project_dir() can't chdir to %s: %s\n", - project_path.c_str(), strerror(errno)); - return; - } - //fprintf(stderr, "chdir from %s to %s\n", app_work_dir.c_str(), fl_getcwd().c_str()); -} - -/** - Change the current working directory to the previous directory. - \see enter_project_dir(), pwd, in_project_dir - */ -void leave_project_dir() { - if (in_project_dir == 0) { - fprintf(stderr, "** Fluid internal error: leave_project_dir() calls unmatched\n"); - return; - } - in_project_dir--; - // still nested, stay in the project directory - if (in_project_dir > 0) return; - // no longer nested, return to the original, usually the application working directory - if (fl_chdir(app_work_dir.c_str()) < 0) { - fprintf(stderr, "** Fluid internal error: leave_project_dir() can't chdir back to %s : %s\n", - app_work_dir.c_str(), strerror(errno)); - } -} - -/** - Position the given window window based on entries in the app preferences. - Customisable by user; feature can be switched off. - The window is not shown or hidden by this function, but a value is returned - to indicate the state to the caller. - \param[in] w position this window - \param[in] prefsName name of the preferences item that stores the window settings - \param[in] Visible default value if window is hidden or shown - \param[in] X, Y, W, H default size and position if nothing is specified in the preferences - \return 1 if the caller should make the window visible, 0 if hidden. - */ -char position_window(Fl_Window *w, const char *prefsName, int Visible, int X, int Y, int W, int H) { - Fl_Preferences pos(fluid_prefs, prefsName); - if (prevpos_button->value()) { - pos.get("x", X, X); - pos.get("y", Y, Y); - if ( W!=0 ) { - pos.get("w", W, W); - pos.get("h", H, H); - w->resize( X, Y, W, H ); - } - else - w->position( X, Y ); - } - pos.get("visible", Visible, Visible); - return Visible; -} - -/** - Save the position and visibility state of a window to the app preferences. - \param[in] w save this window data - \param[in] prefsName name of the preferences item that stores the window settings - */ -void save_position(Fl_Window *w, const char *prefsName) { - Fl_Preferences pos(fluid_prefs, prefsName); - pos.set("x", w->x()); - pos.set("y", w->y()); - pos.set("w", w->w()); - pos.set("h", w->h()); - pos.set("visible", (int)(w->shown() && w->visible())); -} - -/** - Return the path and filename of a temporary file for cut or duplicated data. - \param[in] which 0 gets the cut/copy/paste buffer, 1 gets the duplication buffer - \return a pointer to a string in a static buffer - */ -static char* cutfname(int which = 0) { - static char name[2][FL_PATH_MAX]; - static char beenhere = 0; - - if (!beenhere) { - beenhere = 1; - fluid_prefs.getUserdataPath(name[0], sizeof(name[0])); - strlcat(name[0], "cut_buffer", sizeof(name[0])); - fluid_prefs.getUserdataPath(name[1], sizeof(name[1])); - strlcat(name[1], "dup_buffer", sizeof(name[1])); - } - - return name[which]; -} - -/** - Timer to watch for external editor modifications. - - If one or more external editors open, check if their files were modified. - If so: reload to ram, update size/mtime records, and change fluid's - 'modified' state. - */ -static void external_editor_timer(void*) { - int editors_open = ExternalCodeEditor::editors_open(); - if ( G_debug ) printf("--- TIMER --- External editors open=%d\n", editors_open); - if ( editors_open > 0 ) { - // Walk tree looking for files modified by external editors. - int modified = 0; - for (Fl_Type *p = Fl_Type::first; p; p = p->next) { - if ( p->is_a(ID_Code) ) { - Fl_Code_Type *code = (Fl_Code_Type*)p; - // Code changed by external editor? - if ( code->handle_editor_changes() ) { // updates ram, file size/mtime - modified++; - } - if ( code->is_editing() ) { // editor open? - code->reap_editor(); // Try to reap; maybe it recently closed - } - } - } - if ( modified ) set_modflag(1); - } - // Repeat timeout if editors still open - // The ExternalCodeEditor class handles start/stopping timer, we just - // repeat_timeout() if it's already on. NOTE: above code may have reaped - // only open editor, which would disable further timeouts. So *recheck* - // if editors still open, to ensure we don't accidentally re-enable them. - // - if ( ExternalCodeEditor::editors_open() ) { - Fl::repeat_timeout(2.0, external_editor_timer); - } -} - -/** - Save the current design to the file given by \c filename. - If automatic, this overwrites an existing file. If interactive, if will - verify with the user. - \param[in] v if v is not NULL, or no filename is set, open a filechooser. - */ -void save_cb(Fl_Widget *, void *v) { - flush_text_widgets(); - Fl_Native_File_Chooser fnfc; - const char *c = g_project.proj_filename; - if (v || !c || !*c) { - fnfc.title("Save To:"); - fnfc.type(Fl_Native_File_Chooser::BROWSE_SAVE_FILE); - fnfc.filter("FLUID Files\t*.f[ld]"); - if (fnfc.show() != 0) return; - c = fnfc.filename(); - if (!fl_access(c, 0)) { - std::string basename = fl_filename_name_str(std::string(c)); - if (fl_choice("The file \"%s\" already exists.\n" - "Do you want to replace it?", "Cancel", - "Replace", NULL, basename.c_str()) == 0) return; - } - - if (v != (void *)2) set_filename(c); - } - if (!fld::io::write_file(c)) { - fl_alert("Error writing %s: %s", c, strerror(errno)); - return; - } - - if (v != (void *)2) { - set_modflag(0, 1); - undo_save = undo_current; - } -} - -/** - Save a design template. - \todo We should document the concept of templates. - */ -void save_template_cb(Fl_Widget *, void *) { - // Setup the template panel... - if (!template_panel) make_template_panel(); - - template_clear(); - template_browser->add("New Template"); - template_load(); - - template_name->show(); - template_name->value(""); - - template_instance->hide(); - - template_delete->show(); - template_delete->deactivate(); - - template_submit->label("Save"); - template_submit->deactivate(); - - template_panel->label("Save Template"); - - // Show the panel and wait for the user to do something... - template_panel->show(); - while (template_panel->shown()) Fl::wait(); - - // Get the template name, return if it is empty... - const char *c = template_name->value(); - if (!c || !*c) return; - - // Convert template name to filename_with_underscores - char savename[FL_PATH_MAX], *saveptr; - strlcpy(savename, c, sizeof(savename)); - for (saveptr = savename; *saveptr; saveptr ++) { - if (isspace(*saveptr)) *saveptr = '_'; - } - - // Find the templates directory... - char filename[FL_PATH_MAX]; - fluid_prefs.getUserdataPath(filename, sizeof(filename)); - - strlcat(filename, "templates", sizeof(filename)); - if (fl_access(filename, 0)) fl_make_path(filename); - - strlcat(filename, "/", sizeof(filename)); - strlcat(filename, savename, sizeof(filename)); - - char *ext = filename + strlen(filename); - if (ext >= (filename + sizeof(filename) - 5)) { - fl_alert("The template name \"%s\" is too long!", c); - return; - } - - // Save the .fl file... - strcpy(ext, ".fl"); - - if (!fl_access(filename, 0)) { - if (fl_choice("The template \"%s\" already exists.\n" - "Do you want to replace it?", "Cancel", - "Replace", NULL, c) == 0) return; - } - - if (!fld::io::write_file(filename)) { - fl_alert("Error writing %s: %s", filename, strerror(errno)); - return; - } - -#if defined(HAVE_LIBPNG) && defined(HAVE_LIBZ) - // Get the screenshot, if any... - Fl_Type *t; - - for (t = Fl_Type::first; t; t = t->next) { - // Find the first window... - if (t->is_a(ID_Window)) break; - } - - if (!t) return; - - // Grab a screenshot... - Fl_Window_Type *wt = (Fl_Window_Type *)t; - uchar *pixels; - int w, h; - - if ((pixels = wt->read_image(w, h)) == NULL) return; - - // Save to a PNG file... - strcpy(ext, ".png"); - - errno = 0; - if (fl_write_png(filename, pixels, w, h, 3) != 0) { - delete[] pixels; - fl_alert("Error writing %s: %s", filename, strerror(errno)); - return; - } - -# if 0 // The original PPM output code... - strcpy(ext, ".ppm"); - fp = fl_fopen(filename, "wb"); - fprintf(fp, "P6\n%d %d 255\n", w, h); - fwrite(pixels, w * h, 3, fp); - fclose(fp); -# endif // 0 - - delete[] pixels; -#endif // HAVE_LIBPNG && HAVE_LIBZ -} - -/** - Reload the file set by \c filename, replacing the current design. - If the design was modified, a dialog will ask for confirmation. - */ -void revert_cb(Fl_Widget *,void *) { - if (modflag) { - if (!fl_choice("This user interface has been changed. Really revert?", - "Cancel", "Revert", NULL)) return; - } - undo_suspend(); - if (!fld::io::read_file(g_project.proj_filename, 0)) { - undo_resume(); - widget_browser->rebuild(); - g_project.update_settings_dialog(); - fl_message("Can't read %s: %s", g_project.proj_filename, strerror(errno)); - return; - } - widget_browser->rebuild(); - undo_resume(); - set_modflag(0, 0); - undo_clear(); - g_project.update_settings_dialog(); -} - -/** - Exit Fluid; we hope you had a nice experience. - If the design was modified, a dialog will ask for confirmation. - */ -void exit_cb(Fl_Widget *,void *) { - if (shell_command_running()) { - int choice = fl_choice("Previous shell command still running!", - "Cancel", - "Exit", - NULL); - if (choice == 0) { // user chose to cancel the exit operation - return; - } - } - - flush_text_widgets(); - - // verify user intention - if (confirm_project_clear() == false) - return; - - // Stop any external editor update timers - ExternalCodeEditor::stop_update_timer(); - - save_position(main_window,"main_window_pos"); - - if (widgetbin_panel) { - save_position(widgetbin_panel,"widgetbin_pos"); - delete widgetbin_panel; - } - if (codeview_panel) { - Fl_Preferences svp(fluid_prefs, "codeview"); - svp.set("autorefresh", cv_autorefresh->value()); - svp.set("autoposition", cv_autoposition->value()); - svp.set("tab", cv_tab->find(cv_tab->value())); - svp.set("code_choice", cv_code_choice); - save_position(codeview_panel,"codeview_pos"); - delete codeview_panel; - codeview_panel = 0; - } - if (shell_run_window) { - save_position(shell_run_window,"shell_run_Window_pos"); - } - - if (about_panel) - delete about_panel; - if (help_dialog) - delete help_dialog; - - if (g_shell_config) - g_shell_config->write(fluid_prefs, FD_STORE_USER); - g_layout_list.write(fluid_prefs, FD_STORE_USER); - - undo_clear(); - - // Destroy tree - // Doing so causes dtors to automatically close all external editors - // and cleans up editor tmp files. Then remove fluid tmpdir /last/. - g_project.reset(); - ExternalCodeEditor::tmpdir_clear(); - delete_tmpdir(); - - exit(0); -} - -/** - Clear the current project and create a new, empty one. - - If the current project was modified, FLUID will give the user the opportunity - to save the old project first. - - \param[in] user_must_confirm if set, a confimation dialog is presented to the - user before resetting the project. Default is `true`. - \return false if the operation was canceled - */ -bool new_project(bool user_must_confirm) { - // verify user intention - if ((user_must_confirm) && (confirm_project_clear() == false)) - return false; - - // clear the current project - g_project.reset(); - set_filename(NULL); - set_modflag(0, 0); - widget_browser->rebuild(); - g_project.update_settings_dialog(); - - // all is clear to continue - return true; -} - -/** - Open the template browser and load a new file from templates. - - If the current project was modified, FLUID will give the user the opportunity - to save the old project first. - - \return false if the operation was canceled or failed otherwise - */ -bool new_project_from_template() { - // clear the current project first - if (new_project() == false) - return false; - - // Setup the template panel... - if (!template_panel) make_template_panel(); - - template_clear(); - template_browser->add("Blank"); - template_load(); - - template_name->hide(); - template_name->value(""); - - template_instance->show(); - template_instance->deactivate(); - template_instance->value(""); - - template_delete->show(); - - template_submit->label("New"); - template_submit->deactivate(); - - template_panel->label("New"); - - //if ( template_browser->size() == 1 ) { // only one item? - template_browser->value(1); // select it - template_browser->do_callback(); - //} - - // Show the panel and wait for the user to do something... - template_panel->show(); - while (template_panel->shown()) Fl::wait(); - - // See if the user chose anything... - int item = template_browser->value(); - if (item < 1) return false; - - // Load the template, if any... - const char *tname = (const char *)template_browser->data(item); - - if (tname) { - // Grab the instance name... - const char *iname = template_instance->value(); - - if (iname && *iname) { - // Copy the template to a temp file, then read it in... - char line[1024], *ptr, *next; - FILE *infile, *outfile; - - if ((infile = fl_fopen(tname, "rb")) == NULL) { - fl_alert("Error reading template file \"%s\":\n%s", tname, - strerror(errno)); - set_modflag(0); - undo_clear(); - return false; - } - - if ((outfile = fl_fopen(cutfname(1), "wb")) == NULL) { - fl_alert("Error writing buffer file \"%s\":\n%s", cutfname(1), - strerror(errno)); - fclose(infile); - set_modflag(0); - undo_clear(); - return false; - } - - while (fgets(line, sizeof(line), infile)) { - // Replace @INSTANCE@ with the instance name... - for (ptr = line; (next = strstr(ptr, "@INSTANCE@")) != NULL; ptr = next + 10) { - fwrite(ptr, next - ptr, 1, outfile); - fputs(iname, outfile); - } - - fputs(ptr, outfile); - } - - fclose(infile); - fclose(outfile); - - undo_suspend(); - fld::io::read_file(cutfname(1), 0); - fl_unlink(cutfname(1)); - undo_resume(); - } else { - // No instance name, so read the template without replacements... - undo_suspend(); - fld::io::read_file(tname, 0); - undo_resume(); - } - } - - widget_browser->rebuild(); - g_project.update_settings_dialog(); - set_modflag(0); - undo_clear(); - - return true; -} - -/** - Open a native file chooser to allow choosing a project file for reading. - - Path and filename are preset with the current project filename, if there - is one. - - \param title a text describing the action after selecting a file (load, merge, ...) - \return the file path and name, or an empty string if the operation was canceled - */ -std::string open_project_filechooser(const std::string &title) { - Fl_Native_File_Chooser dialog; - dialog.title(title.c_str()); - dialog.type(Fl_Native_File_Chooser::BROWSE_FILE); - dialog.filter("FLUID Files\t*.f[ld]\n"); - if (g_project.proj_filename) { - std::string current_project_file = g_project.proj_filename; - dialog.directory(fl_filename_path_str(current_project_file).c_str()); - dialog.preset_file(fl_filename_name_str(current_project_file).c_str()); - } - if (dialog.show() != 0) - return std::string(); - return std::string(dialog.filename()); -} - -/** - Load a project from the give file name and path. - - The project file is inserted at the currently selected type. - - If no filename is given, FLUID will open a file chooser dialog. - - \param[in] filename_arg path and name of the new project file - \return false if the operation failed - */ -bool merge_project_file(const std::string &filename_arg) { - bool is_a_merge = (Fl_Type::first != NULL); - std::string title = is_a_merge ? "Merge Project File" : "Open Project File"; - - // ask for a filename if none was given - std::string new_filename = filename_arg; - if (new_filename.empty()) { - new_filename = open_project_filechooser(title); - if (new_filename.empty()) { - return false; - } - } - - const char *c = new_filename.c_str(); - const char *oldfilename = g_project.proj_filename; - g_project.proj_filename = NULL; - set_filename(c); - if (is_a_merge) undo_checkpoint(); - undo_suspend(); - if (!fld::io::read_file(c, is_a_merge)) { - undo_resume(); - widget_browser->rebuild(); - g_project.update_settings_dialog(); - fl_message("Can't read %s: %s", c, strerror(errno)); - free((void *)g_project.proj_filename); - g_project.proj_filename = oldfilename; - if (main_window) set_modflag(modflag); - return false; - } - undo_resume(); - widget_browser->rebuild(); - if (is_a_merge) { - // Inserting a file; restore the original filename... - set_filename(oldfilename); - set_modflag(1); - } else { - // Loaded a file; free the old filename... - set_modflag(0, 0); - undo_clear(); - } - if (oldfilename) free((void *)oldfilename); - return true; -} - -/** - Open a file chooser and load an exiting project file. - - If the current project was modified, FLUID will give the user the opportunity - to save the old project first. - - If no filename is given, FLUID will open a file chooser dialog. - - \param[in] filename_arg load from this file, or show file chooser if empty - \return false if the operation was canceled or failed otherwise - */ -bool open_project_file(const std::string &filename_arg) { - // verify user intention - if (confirm_project_clear() == false) - return false; - - // ask for a filename if none was given - std::string new_filename = filename_arg; - if (new_filename.empty()) { - new_filename = open_project_filechooser("Open Project File"); - if (new_filename.empty()) { - return false; - } - } - - // clear the project and merge a file by the given name - new_project(false); - return merge_project_file(new_filename); -} - -#ifdef __APPLE__ -/** - Handle app launch with an associated filename (macOS only). - Should there be a modified design already, Fluid asks for user confirmation. - \param[in] c the filename of the new design - */ -void apple_open_cb(const char *c) { - open_project_file(std::string(c)); -} -#endif // __APPLE__ - -/** - Generate the C++ source and header filenames and write those files. - - This function creates the source filename by setting the file - extension to \c code_file_name and a header filename - with the extension \c code_file_name which are both - settable by the user. - - If the code filename has not been set yet, a "save file as" dialog will be - presented to the user. - - In batch_mode, the function will either be silent, or, if opening or writing - the files fails, write an error message to \c stderr and exit with exit code 1. - - In interactive mode, it will pop up an error message, or, if the user - hasn't disabled that, pop up a confirmation message. - - \param[in] dont_show_completion_dialog don't show the completion dialog - \return 1 if the operation failed, 0 if it succeeded - */ -int write_code_files(bool dont_show_completion_dialog) -{ - // -- handle user interface issues - flush_text_widgets(); - if (!g_project.proj_filename) { - save_cb(0,0); - if (!g_project.proj_filename) return 1; - } - - // -- generate the file names with absolute paths - fld::io::Code_Writer f; - std::string code_filename = g_project.codefile_path() + g_project.codefile_name(); - std::string header_filename = g_project.headerfile_path() + g_project.headerfile_name(); - - // -- write the code and header files - if (!batch_mode) enter_project_dir(); - int x = f.write_code(code_filename.c_str(), header_filename.c_str()); - std::string code_filename_rel = fl_filename_relative_str(code_filename); - std::string header_filename_rel = fl_filename_relative_str(header_filename); - if (!batch_mode) leave_project_dir(); - - // -- print error message in batch mode or pop up an error or confirmation dialog box - if (batch_mode) { - if (!x) { - fprintf(stderr, "%s and %s: %s\n", - code_filename_rel.c_str(), - header_filename_rel.c_str(), - strerror(errno)); - exit(1); - } - } else { - if (!x) { - fl_message("Can't write %s or %s: %s", - code_filename_rel.c_str(), - header_filename_rel.c_str(), - strerror(errno)); - } else { - set_modflag(-1, 0); - if (dont_show_completion_dialog==false && completion_button->value()) { - fl_message("Wrote %s and %s", - code_filename_rel.c_str(), - header_filename_rel.c_str()); - } - } - } - return 0; -} - -/** - Callback to write C++ code and header files. - */ -void write_cb(Fl_Widget *, void *) { - write_code_files(); -} - -#if 0 -// Matt: disabled -/** - Merge the possibly modified content of code files back into the project. - */ -int mergeback_code_files() -{ - flush_text_widgets(); - if (!filename) return 1; - if (!g_project.write_mergeback_data) { - fl_message("MergeBack is not enabled for this project.\n" - "Please enable MergeBack in the project settings\n" - "dialog and re-save the project file and the code."); - return 0; - } - - std::string proj_filename = g_project.projectfile_path() + g_project.projectfile_name(); - std::string code_filename; -#if 1 - if (!batch_mode) { - Fl_Preferences build_records(Fl_Preferences::USER_L, "fltk.org", "fluid-build"); - Fl_Preferences path(build_records, proj_filename.c_str()); - int i, n = proj_filename.size(); - for (i=0; ivalue()) { - fl_message("Wrote %s", g_project.stringsfile_name().c_str()); - } - } -} - -/** - Show the editor for the \c current Fl_Type. - */ -void openwidget_cb(Fl_Widget *, void *) { - if (!Fl_Type::current) { - fl_message("Please select a widget"); - return; - } - Fl_Type::current->open(); -} - -/** - User chose to copy the currently selected widgets. - */ -void copy_cb(Fl_Widget*, void*) { - flush_text_widgets(); - if (!Fl_Type::current) { - fl_beep(); - return; - } - flush_text_widgets(); - ipasteoffset = 10; - if (!fld::io::write_file(cutfname(),1)) { - fl_message("Can't write %s: %s", cutfname(), strerror(errno)); - return; - } -} - -/** - User chose to cut the currently selected widgets. - */ -void cut_cb(Fl_Widget *, void *) { - if (!Fl_Type::current) { - fl_beep(); - return; - } - flush_text_widgets(); - if (!fld::io::write_file(cutfname(),1)) { - fl_message("Can't write %s: %s", cutfname(), strerror(errno)); - return; - } - undo_checkpoint(); - set_modflag(1); - ipasteoffset = 0; - Fl_Type *p = Fl_Type::current->parent; - while (p && p->selected) p = p->parent; - delete_all(1); - if (p) select_only(p); - widget_browser->rebuild(); -} - -/** - User chose to delete the currently selected widgets. - */ -void delete_cb(Fl_Widget *, void *) { - if (!Fl_Type::current) { - fl_beep(); - return; - } - undo_checkpoint(); - set_modflag(1); - ipasteoffset = 0; - Fl_Type *p = Fl_Type::current->parent; - while (p && p->selected) p = p->parent; - delete_all(1); - if (p) select_only(p); - widget_browser->rebuild(); -} - -/** - User chose to paste the widgets from the cut buffer. - - This function will paste the widgets in the cut buffer after the currently - selected widget. If the currently selected widget is a group widget and - it is not folded, the new widgets will be added inside the group. - */ -void paste_cb(Fl_Widget*, void*) { - pasteoffset = ipasteoffset; - undo_checkpoint(); - undo_suspend(); - Strategy strategy = Strategy::FROM_FILE_AFTER_CURRENT; - if (Fl_Type::current && Fl_Type::current->can_have_children()) { - if (Fl_Type::current->folded_ == 0) { - // If the current widget is a group widget and it is not folded, - // add the new widgets inside the group. - strategy = Strategy::FROM_FILE_AS_LAST_CHILD; - // The following alternative also works quite nicely - //strategy = Strategy::FROM_FILE_AS_FIRST_CHILD; - } - } - if (!fld::io::read_file(cutfname(), 1, strategy)) { - widget_browser->rebuild(); - fl_message("Can't read %s: %s", cutfname(), strerror(errno)); - } - undo_resume(); - widget_browser->display(Fl_Type::current); - widget_browser->rebuild(); - pasteoffset = 0; - ipasteoffset += 10; -} - -/** - Duplicate the selected widgets. - - This code is a bit complex because it needs to find the last selected - widget with the lowest level, so that the new widgets are inserted after - this one. - */ -void duplicate_cb(Fl_Widget*, void*) { - if (!Fl_Type::current) { - fl_beep(); - return; - } - - // flush the text widgets to make sure the user's changes are saved: - flush_text_widgets(); - - // find the last selected node with the lowest level: - int lowest_level = 9999; - Fl_Type *new_insert = NULL; - if (Fl_Type::current->selected) { - for (Fl_Type *t = Fl_Type::first; t; t = t->next) { - if (t->selected && (t->level <= lowest_level)) { - lowest_level = t->level; - new_insert = t; - } - } - } - if (new_insert) - Fl_Type::current = new_insert; - - // write the selected widgets to a file: - if (!fld::io::write_file(cutfname(1),1)) { - fl_message("Can't write %s: %s", cutfname(1), strerror(errno)); - return; - } - - // read the file and add the widgets after the current one: - pasteoffset = 0; - undo_checkpoint(); - undo_suspend(); - if (!fld::io::read_file(cutfname(1), 1, Strategy::FROM_FILE_AFTER_CURRENT)) { - fl_message("Can't read %s: %s", cutfname(1), strerror(errno)); - } - fl_unlink(cutfname(1)); - widget_browser->display(Fl_Type::current); - widget_browser->rebuild(); - undo_resume(); -} - -/** - User wants to sort selected widgets by y coordinate. - */ -static void sort_cb(Fl_Widget *,void *) { - undo_checkpoint(); - sort((Fl_Type*)NULL); - widget_browser->rebuild(); - set_modflag(1); -} - -/** - Open the "About" dialog. - */ -void about_cb(Fl_Widget *, void *) { - if (!about_panel) make_about_panel(); - about_panel->show(); -} - -/** - Open a dialog to show the HTML help page form the FLTK documentation folder. - \param[in] name name of the HTML help file. - */ -void show_help(const char *name) { - const char *docdir; - char helpname[FL_PATH_MAX]; - - if (!help_dialog) help_dialog = new Fl_Help_Dialog(); - - if ((docdir = fl_getenv("FLTK_DOCDIR")) == NULL) { - docdir = FLTK_DOCDIR; - } - snprintf(helpname, sizeof(helpname), "%s/%s", docdir, name); - - // make sure that we can read the file - FILE *f = fopen(helpname, "rb"); - if (f) { - fclose(f); - help_dialog->load(helpname); - } else { - // if we can not read the file, we display the canned version instead - // or ask the native browser to open the page on www.fltk.org - if (strcmp(name, "fluid.html")==0) { - if (!Fl_Shared_Image::find("embedded:/fluid_flow_chart_800.png")) - new Fl_PNG_Image("embedded:/fluid_flow_chart_800.png", fluid_flow_chart_800_png, sizeof(fluid_flow_chart_800_png)); - help_dialog->value - ( - "\n" - "FLTK: Programming with FLUID\n" - "

What is FLUID?

\n" - "The Fast Light User Interface Designer, or FLUID, is a graphical editor " - "that is used to produce FLTK source code. FLUID edits and saves its state " - "in .fl files. These files are text, and you can (with care) " - "edit them in a text editor, perhaps to get some special effects.

\n" - "FLUID can \"compile\" the .fl file into a .cxx " - "and a .h file. The .cxx file defines all the " - "objects from the .fl file and the .h file " - "declares all the global ones. FLUID also supports localization " - "(Internationalization) of label strings using message files and the GNU " - "gettext or POSIX catgets interfaces.

\n" - "A simple program can be made by putting all your code (including a " - "main() function) into the .fl file and thus making the " - ".cxx file a single source file to compile. Most programs are " - "more complex than this, so you write other .cxx files that " - "call the FLUID functions. These .cxx files must " - "#include the .h file or they can #include " - "the .cxx file so it still appears to be a single source file.

" - "

" - "

More information is available online at https://www.fltk.org/" - "" - ); - } else if (strcmp(name, "license.html")==0) { - fl_open_uri("https://www.fltk.org/doc-1.5/license.html"); - return; - } else if (strcmp(name, "index.html")==0) { - fl_open_uri("https://www.fltk.org/doc-1.5/index.html"); - return; - } else { - snprintf(helpname, sizeof(helpname), "https://www.fltk.org/%s", name); - fl_open_uri(helpname); - return; - } - } - help_dialog->show(); -} - -/** - User wants help on Fluid. - */ -void help_cb(Fl_Widget *, void *) { - show_help("fluid.html"); -} - -/** - User wants to see the Fluid manual. - */ -void manual_cb(Fl_Widget *, void *) { - show_help("index.html"); -} - -// ---- Printing - -/** - Open the dialog to allow the user to print the current window. - */ -void print_menu_cb(Fl_Widget *, void *) { - int w, h, ww, hh; - int frompage, topage; - Fl_Type *t; // Current widget - int num_windows; // Number of windows - Fl_Window_Type *windows[1000]; // Windows to print - int winpage; // Current window page - Fl_Window *win; - - for (t = Fl_Type::first, num_windows = 0; t; t = t->next) { - if (t->is_a(ID_Window)) { - windows[num_windows] = (Fl_Window_Type *)t; - if (!((Fl_Window*)(windows[num_windows]->o))->shown()) continue; - num_windows ++; - } - } - - Fl_Printer printjob; - if ( printjob.start_job(num_windows, &frompage, &topage) ) return; - int pagecount = 0; - for (winpage = 0; winpage < num_windows; winpage++) { - float scale = 1, scale_x = 1, scale_y = 1; - if (winpage+1 < frompage || winpage+1 > topage) continue; - printjob.start_page(); - printjob.printable_rect(&w, &h); - - // Get the time and date... - time_t curtime = time(NULL); - struct tm *curdate = localtime(&curtime); - char date[1024]; - strftime(date, sizeof(date), "%c", curdate); - fl_font(FL_HELVETICA, 12); - fl_color(FL_BLACK); - fl_draw(date, (w - (int)fl_width(date))/2, fl_height()); - sprintf(date, "%d/%d", ++pagecount, topage-frompage+1); - fl_draw(date, w - (int)fl_width(date), fl_height()); - - // Get the base filename... - std::string basename = fl_filename_name_str(std::string(g_project.proj_filename)); - fl_draw(basename.c_str(), 0, fl_height()); - - // print centered and scaled to fit in the page - win = (Fl_Window*)windows[winpage]->o; - ww = win->decorated_w(); - if(ww > w) scale_x = float(w)/ww; - hh = win->decorated_h(); - if(hh > h) scale_y = float(h)/hh; - if (scale_x < scale) scale = scale_x; - if (scale_y < scale) scale = scale_y; - if (scale < 1) { - printjob.scale(scale); - printjob.printable_rect(&w, &h); - } - printjob.origin(w/2, h/2); - printjob.print_window(win, -ww/2, -hh/2); - printjob.end_page(); - } - printjob.end_job(); -} - -// ---- Main menu bar - -extern void select_layout_preset_cb(Fl_Widget *, void *user_data); -extern void layout_suite_marker(Fl_Widget *, void *user_data); - -static void menu_file_new_cb(Fl_Widget *, void *) { new_project(); } -static void menu_file_new_from_template_cb(Fl_Widget *, void *) { new_project_from_template(); } -static void menu_file_open_cb(Fl_Widget *, void *) { open_project_file(""); } -static void menu_file_insert_cb(Fl_Widget *, void *) { merge_project_file(""); } -static void menu_file_open_history_cb(Fl_Widget *, void *v) { open_project_file(std::string((const char*)v)); } -static void menu_layout_sync_resize_cb(Fl_Menu_ *m, void*) { - if (m->mvalue()->value()) Fl_Type::allow_layout = 1; else Fl_Type::allow_layout = 0; } -/** - This is the main Fluid menu. - - Design history is manipulated right inside this menu structure. - Some menu items change or deactivate correctly, but most items just trigger - various callbacks. - - \c New_Menu creates new widgets and is explained in detail in another location. - - \see New_Menu - \todo This menu needs some major modernization. Menus are too long and their - sorting is not always obvious. - \todo Shortcuts are all over the place (Alt, Ctrl, Command, Shift-Ctrl, - function keys), and there should be a help page listing all shortcuts. - */ -Fl_Menu_Item Main_Menu[] = { -{"&File",0,0,0,FL_SUBMENU}, - {"&New", FL_COMMAND+'n', menu_file_new_cb}, - {"&Open...", FL_COMMAND+'o', menu_file_open_cb}, - {"&Insert...", FL_COMMAND+'i', menu_file_insert_cb, 0, FL_MENU_DIVIDER}, - {"&Save", FL_COMMAND+'s', save_cb, 0}, - {"Save &As...", FL_COMMAND+FL_SHIFT+'s', save_cb, (void*)1}, - {"Sa&ve A Copy...", 0, save_cb, (void*)2}, - {"&Revert...", 0, revert_cb, 0, FL_MENU_DIVIDER}, - {"New &From Template...", FL_COMMAND+'N', menu_file_new_from_template_cb, 0}, - {"Save As &Template...", 0, save_template_cb, 0, FL_MENU_DIVIDER}, - {"&Print...", FL_COMMAND+'p', print_menu_cb}, - {"Write &Code", FL_COMMAND+FL_SHIFT+'c', write_cb, 0}, -// Matt: disabled {"MergeBack Code", FL_COMMAND+FL_SHIFT+'m', mergeback_cb, 0}, - {"&Write Strings", FL_COMMAND+FL_SHIFT+'w', write_strings_cb, 0, FL_MENU_DIVIDER}, - {relative_history[0], FL_COMMAND+'1', menu_file_open_history_cb, absolute_history[0]}, - {relative_history[1], FL_COMMAND+'2', menu_file_open_history_cb, absolute_history[1]}, - {relative_history[2], FL_COMMAND+'3', menu_file_open_history_cb, absolute_history[2]}, - {relative_history[3], FL_COMMAND+'4', menu_file_open_history_cb, absolute_history[3]}, - {relative_history[4], FL_COMMAND+'5', menu_file_open_history_cb, absolute_history[4]}, - {relative_history[5], FL_COMMAND+'6', menu_file_open_history_cb, absolute_history[5]}, - {relative_history[6], FL_COMMAND+'7', menu_file_open_history_cb, absolute_history[6]}, - {relative_history[7], FL_COMMAND+'8', menu_file_open_history_cb, absolute_history[7]}, - {relative_history[8], FL_COMMAND+'9', menu_file_open_history_cb, absolute_history[8]}, - {relative_history[9], 0, menu_file_open_history_cb, absolute_history[9], FL_MENU_DIVIDER}, - {"&Quit", FL_COMMAND+'q', exit_cb}, - {0}, -{"&Edit",0,0,0,FL_SUBMENU}, - {"&Undo", FL_COMMAND+'z', undo_cb}, - {"&Redo", FL_COMMAND+FL_SHIFT+'z', redo_cb, 0, FL_MENU_DIVIDER}, - {"C&ut", FL_COMMAND+'x', cut_cb}, - {"&Copy", FL_COMMAND+'c', copy_cb}, - {"&Paste", FL_COMMAND+'v', paste_cb}, - {"Dup&licate", FL_COMMAND+'u', duplicate_cb}, - {"&Delete", FL_Delete, delete_cb, 0, FL_MENU_DIVIDER}, - {"Select &All", FL_COMMAND+'a', select_all_cb}, - {"Select &None", FL_COMMAND+FL_SHIFT+'a', select_none_cb, 0, FL_MENU_DIVIDER}, - {"Pr&operties...", FL_F+1, openwidget_cb}, - {"&Sort",0,sort_cb}, - {"&Earlier", FL_F+2, earlier_cb}, - {"&Later", FL_F+3, later_cb}, - {"&Group", FL_F+7, group_cb}, - {"Ung&roup", FL_F+8, ungroup_cb,0, FL_MENU_DIVIDER}, - {"Hide O&verlays",FL_COMMAND+FL_SHIFT+'o',toggle_overlays}, - {"Hide Guides",FL_COMMAND+FL_SHIFT+'g',toggle_guides}, - {"Hide Restricted",FL_COMMAND+FL_SHIFT+'r',toggle_restricted}, - {"Show Widget &Bin...",FL_ALT+'b',toggle_widgetbin_cb}, - {"Show Code View",FL_ALT+'c', (Fl_Callback*)toggle_codeview_cb, 0, FL_MENU_DIVIDER}, - {"Settings...",FL_ALT+'p',show_settings_cb}, - {0}, -{"&New", 0, 0, (void *)New_Menu, FL_SUBMENU_POINTER}, -{"&Layout",0,0,0,FL_SUBMENU}, - {"&Align",0,0,0,FL_SUBMENU}, - {"&Left",0,(Fl_Callback *)align_widget_cb,(void*)10}, - {"&Center",0,(Fl_Callback *)align_widget_cb,(void*)11}, - {"&Right",0,(Fl_Callback *)align_widget_cb,(void*)12}, - {"&Top",0,(Fl_Callback *)align_widget_cb,(void*)13}, - {"&Middle",0,(Fl_Callback *)align_widget_cb,(void*)14}, - {"&Bottom",0,(Fl_Callback *)align_widget_cb,(void*)15}, - {0}, - {"&Space Evenly",0,0,0,FL_SUBMENU}, - {"&Across",0,(Fl_Callback *)align_widget_cb,(void*)20}, - {"&Down",0,(Fl_Callback *)align_widget_cb,(void*)21}, - {0}, - {"&Make Same Size",0,0,0,FL_SUBMENU}, - {"&Width",0,(Fl_Callback *)align_widget_cb,(void*)30}, - {"&Height",0,(Fl_Callback *)align_widget_cb,(void*)31}, - {"&Both",0,(Fl_Callback *)align_widget_cb,(void*)32}, - {0}, - {"&Center In Group",0,0,0,FL_SUBMENU}, - {"&Horizontal",0,(Fl_Callback *)align_widget_cb,(void*)40}, - {"&Vertical",0,(Fl_Callback *)align_widget_cb,(void*)41}, - {0}, - {"Synchronized Resize", 0, (Fl_Callback*)menu_layout_sync_resize_cb, NULL, FL_MENU_TOGGLE|FL_MENU_DIVIDER }, - {"&Grid and Size Settings...",FL_COMMAND+'g',show_grid_cb, NULL, FL_MENU_DIVIDER}, - {"Presets", 0, layout_suite_marker, (void*)main_layout_submenu_, FL_SUBMENU_POINTER }, - {"Application", 0, select_layout_preset_cb, (void*)0, FL_MENU_RADIO|FL_MENU_VALUE }, - {"Dialog", 0, select_layout_preset_cb, (void*)1, FL_MENU_RADIO }, - {"Toolbox", 0, select_layout_preset_cb, (void*)2, FL_MENU_RADIO }, - {0}, -{"&Shell", 0, Fd_Shell_Command_List::menu_marker, (void*)Fd_Shell_Command_List::default_menu, FL_SUBMENU_POINTER}, -{"&Help",0,0,0,FL_SUBMENU}, - {"&Rapid development with FLUID...",0,help_cb}, - {"&FLTK Programmers Manual...",0,manual_cb, 0, FL_MENU_DIVIDER}, - {"&About FLUID...",0,about_cb}, - {0}, -{0}}; - -/** - Change the app's and hence preview the design's scheme. - - The scheme setting is stored in the app preferences - - in key \p 'scheme_name' since 1.4.0 - - in key \p 'scheme' (index: 0 - 4) in 1.3.x - - This callback is triggered by changing the scheme in the - Fl_Scheme_Choice widget (\p Edit/GUI Settings). - - \param[in] choice the calling widget - - \see init_scheme() for choice values and backwards compatibility - */ -void scheme_cb(Fl_Scheme_Choice *choice, void *) { - if (batch_mode) - return; - - // set the new scheme only if the scheme was changed - const char *new_scheme = choice->text(choice->value()); - - if (Fl::is_scheme(new_scheme)) - return; - - Fl::scheme(new_scheme); - fluid_prefs.set("scheme_name", new_scheme); - - // Backwards compatibility: store 1.3 scheme index (1-4). - // We assume that index 0-3 (base, plastic, gtk+, gleam) are in the - // same order as in 1.3.x (index 1-4), higher values are ignored - - int scheme_index = scheme_choice->value(); - if (scheme_index <= 3) // max. index for 1.3.x (Gleam) - fluid_prefs.set("scheme", scheme_index + 1); // compensate for different indexing -} - -/** - Read Fluid's scheme preferences and set the app's scheme. - - Since FLTK 1.4.0 the scheme \b name is stored as a character string - with key "scheme_name" in the preference database. - - In FLTK 1.3.x the scheme preference was stored as an integer index - with key "scheme" in the database. The known schemes were hardcoded in - Fluid's sources (here for reference): - - | Index | 1.3 Scheme Name | Choice | 1.4 Scheme Name | - |-------|-----------------|-------|-----------------| - | 0 | Default (same as None) | n/a | n/a | - | 1 | None (same as Default) | 0 | base | - | 2 | Plastic | 1 | plastic | - | 3 | GTK+ | 2 | gtk+ | - | 4 | Gleam | 3 | gleam | - | n/a | n/a | 4 | oxy | - - The new Fluid tries to keep backwards compatibility and reads both - keys (\p scheme and \p scheme_name). If the latter is defined, it is used. - If not the old \p scheme (index) is used - but we need to subtract one to - get the new Fl_Scheme_Choice index (column "Choice" above). -*/ -void init_scheme() { - int scheme_index = 0; // scheme index for backwards compatibility (1.3.x) - char *scheme_name = 0; // scheme name since 1.4.0 - fluid_prefs.get("scheme_name", scheme_name, "XXX"); // XXX means: not set => fallback 1.3.x - if (!strcmp(scheme_name, "XXX")) { - fluid_prefs.get("scheme", scheme_index, 0); - if (scheme_index > 0) { - scheme_index--; - scheme_choice->value(scheme_index); // set the choice value - } - if (scheme_index < 0) - scheme_index = 0; - else if (scheme_index > scheme_choice->size() - 1) - scheme_index = 0; - scheme_name = const_cast(scheme_choice->text(scheme_index)); - fluid_prefs.set("scheme_name", scheme_name); - } - // Set the new scheme only if it was not overridden by the -scheme - // command line option - if (Fl::scheme() == NULL) { - Fl::scheme(scheme_name); - } - free(scheme_name); -} - -/** - Show or hide the widget bin. - The state is stored in the app preferences. - */ -void toggle_widgetbin_cb(Fl_Widget *, void *) { - if (!widgetbin_panel) { - make_widgetbin(); - if (!position_window(widgetbin_panel,"widgetbin_pos", 1, 320, 30)) return; - } - - if (widgetbin_panel->visible()) { - widgetbin_panel->hide(); - widgetbin_item->label("Show Widget &Bin..."); - } else { - widgetbin_panel->show(); - widgetbin_item->label("Hide Widget &Bin"); - } -} - -/** - Show or hide the code preview window. - */ -void toggle_codeview_cb(Fl_Double_Window *, void *) { - codeview_toggle_visibility(); -} - -/** - Show or hide the code preview window, button callback. - */ -void toggle_codeview_b_cb(Fl_Button*, void *) { - codeview_toggle_visibility(); -} - -/** - Build the main app window and create a few other dialogs. - */ -void make_main_window() { - if (!batch_mode) { - fluid_prefs.get("show_guides", show_guides, 1); - fluid_prefs.get("show_restricted", show_restricted, 1); - fluid_prefs.get("show_ghosted_outline", show_ghosted_outline, 0); - fluid_prefs.get("show_comments", show_comments, 1); - make_shell_window(); - } - - if (!main_window) { - Fl_Widget *o; - loadPixmaps(); - main_window = new Fl_Double_Window(WINWIDTH,WINHEIGHT,"fluid"); - main_window->box(FL_NO_BOX); - o = make_widget_browser(0,MENUHEIGHT,BROWSERWIDTH,BROWSERHEIGHT); - o->box(FL_FLAT_BOX); - o->tooltip("Double-click to view or change an item."); - main_window->resizable(o); - main_menubar = new Fl_Menu_Bar(0,0,BROWSERWIDTH,MENUHEIGHT); - main_menubar->menu(Main_Menu); - // quick access to all dynamic menu items - save_item = (Fl_Menu_Item*)main_menubar->find_item(save_cb); - history_item = (Fl_Menu_Item*)main_menubar->find_item(menu_file_open_history_cb); - widgetbin_item = (Fl_Menu_Item*)main_menubar->find_item(toggle_widgetbin_cb); - codeview_item = (Fl_Menu_Item*)main_menubar->find_item((Fl_Callback*)toggle_codeview_cb); - overlay_item = (Fl_Menu_Item*)main_menubar->find_item((Fl_Callback*)toggle_overlays); - guides_item = (Fl_Menu_Item*)main_menubar->find_item((Fl_Callback*)toggle_guides); - restricted_item = (Fl_Menu_Item*)main_menubar->find_item((Fl_Callback*)toggle_restricted); - main_menubar->global(); - fill_in_New_Menu(); - main_window->end(); - } - - if (!batch_mode) { - load_history(); - g_shell_config = new Fd_Shell_Command_List; - widget_browser->load_prefs(); - make_settings_window(); - } -} - -/** - Load file history from preferences. - - This loads the absolute filepaths of the last 10 used design files. - It also computes and stores the relative filepaths for display in - the main menu. - */ -void load_history() { - int i; // Looping var - int max_files; - - fluid_prefs.get("recent_files", max_files, 5); - if (max_files > 10) max_files = 10; - - for (i = 0; i < max_files; i ++) { - fluid_prefs.get( Fl_Preferences::Name("file%d", i), absolute_history[i], "", sizeof(absolute_history[i])); - if (absolute_history[i][0]) { - // Make a shortened version of the filename for the menu... - std::string fn = fl_filename_shortened(absolute_history[i], 48); - strncpy(relative_history[i], fn.c_str(), sizeof(relative_history[i]) - 1); - if (i == 9) history_item[i].flags = FL_MENU_DIVIDER; - else history_item[i].flags = 0; - } else break; - } - - for (; i < 10; i ++) { - if (i) history_item[i-1].flags |= FL_MENU_DIVIDER; - history_item[i].hide(); - } -} - -/** - Update file history from preferences. - - Add this new filepath to the history and update the main menu. - Writes the new file history to the app preferences. - - \param[in] flname path or filename of .fl file, will be converted into an - absolute file path based on the current working directory. - */ -void update_history(const char *flname) { - int i; // Looping var - char absolute[FL_PATH_MAX]; - int max_files; - - - fluid_prefs.get("recent_files", max_files, 5); - if (max_files > 10) max_files = 10; - - fl_filename_absolute(absolute, sizeof(absolute), flname); -#ifdef _WIN32 - // Make path canonical. - for (char *s = absolute; *s; s++) { - if (*s == '\\') - *s = '/'; - } -#endif - - - for (i = 0; i < max_files; i ++) -#if defined(_WIN32) || defined(__APPLE__) - if (!strcasecmp(absolute, absolute_history[i])) break; -#else - if (!strcmp(absolute, absolute_history[i])) break; -#endif // _WIN32 || __APPLE__ - - if (i == 0) return; - - if (i >= max_files) i = max_files - 1; - - // Move the other flnames down in the list... - memmove(absolute_history + 1, absolute_history, - i * sizeof(absolute_history[0])); - memmove(relative_history + 1, relative_history, - i * sizeof(relative_history[0])); - - // Put the new file at the top... - strlcpy(absolute_history[0], absolute, sizeof(absolute_history[0])); - std::string fn = fl_filename_shortened(absolute_history[0], 48); - strncpy(relative_history[0], fn.c_str(), sizeof(relative_history[0]) - 1); - - // Update the menu items as needed... - for (i = 0; i < max_files; i ++) { - fluid_prefs.set( Fl_Preferences::Name("file%d", i), absolute_history[i]); - if (absolute_history[i][0]) { - if (i == 9) history_item[i].flags = FL_MENU_DIVIDER; - else history_item[i].flags = 0; - } else break; - } - - for (; i < 10; i ++) { - fluid_prefs.set( Fl_Preferences::Name("file%d", i), ""); - if (i) history_item[i-1].flags |= FL_MENU_DIVIDER; - history_item[i].hide(); - } - fluid_prefs.flush(); -} - -/** - Set the filename of the current .fl design. - \param[in] c the new absolute filename and path - */ -void set_filename(const char *c) { - if (g_project.proj_filename) free((void *)g_project.proj_filename); - g_project.proj_filename = c ? fl_strdup(c) : NULL; - - if (g_project.proj_filename && !batch_mode) - update_history(g_project.proj_filename); - - set_modflag(modflag); -} - - -/** - Set the "modified" flag and update the title of the main window. - - The first argument sets the modification state of the current design against - the corresponding .fl design file. Any change to the widget tree will mark - the design 'modified'. Saving the design will mark it clean. - - The second argument is optional and set the modification state of the current - design against the source code and header file. Any change to the tree, - including saving the tree, will mark the code 'outdated'. Generating source - code and header files will clear this flag until the next modification. - - \param[in] mf 0 to clear the modflag, 1 to mark the design "modified", -1 to - ignore this parameter - \param[in] mfc default -1 to let \c mf control \c modflag_c, 0 to mark the - code files current, 1 to mark it out of date. -2 to ignore changes to mf. - */ -void set_modflag(int mf, int mfc) { - const char *code_ext = NULL; - char new_title[FL_PATH_MAX]; - - // Update the modflag_c to the worst possible condition. We could be a bit - // more graceful and compare modification times of the files, but C++ has - // no API for that until C++17. - if (mf!=-1) { - modflag = mf; - if (mfc==-1 && mf==1) - mfc = mf; - } - if (mfc>=0) { - modflag_c = mfc; - } - - if (main_window) { - std::string basename; - if (!g_project.proj_filename) basename = "Untitled.fl"; - else basename = fl_filename_name_str(std::string(g_project.proj_filename)); - code_ext = fl_filename_ext(g_project.code_file_name.c_str()); - char mod_star = modflag ? '*' : ' '; - char mod_c_star = modflag_c ? '*' : ' '; - snprintf(new_title, sizeof(new_title), "%s%c %s%c", - basename.c_str(), mod_star, code_ext, mod_c_star); - const char *old_title = main_window->label(); - // only update the title if it actually changed - if (!old_title || strcmp(old_title, new_title)) - main_window->copy_label(new_title); - } - // if the UI was modified in any way, update the Code View panel - if (codeview_panel && codeview_panel->visible() && cv_autorefresh->value()) - codeview_defer_update(); -} - -// ---- Main program entry point - -/** - Handle command line arguments. - \param[in] argc number of arguments in the list - \param[in] argv pointer to an array of arguments - \param[inout] i current argument index - \return number of arguments used; if 0, the argument is not supported - */ -static int arg(int argc, char** argv, int& i) { - if (argv[i][0] != '-') - return 0; - if (argv[i][1] == 'd' && !argv[i][2]) { - G_debug=1; - i++; return 1; - } - if (argv[i][1] == 'u' && !argv[i][2]) { - update_file++; - batch_mode++; - i++; return 1; - } - if (argv[i][1] == 'c' && !argv[i][2]) { - compile_file++; - batch_mode++; - i++; return 1; - } - if ((strcmp(argv[i], "-v")==0) || (strcmp(argv[i], "--version")==0)) { - show_version = 1; - i++; return 1; - } - if (argv[i][1] == 'c' && argv[i][2] == 's' && !argv[i][3]) { - compile_file++; - compile_strings++; - batch_mode++; - i++; return 1; - } - if (argv[i][1] == 'o' && !argv[i][2] && i+1 < argc) { - g_code_filename_arg = argv[i+1]; - batch_mode++; - i += 2; return 2; - } -#ifndef NDEBUG - if ((i+1 < argc) && (strcmp(argv[i], "--autodoc") == 0)) { - g_autodoc_path = argv[i+1]; - i += 2; return 2; - } -#endif - if (strcmp(argv[i], "--help")==0) { - return 0; - } - if (argv[i][1] == 'h' && !argv[i][2]) { - if ( (i+1 < argc) && (argv[i+1][0] != '-') ) { - g_header_filename_arg = argv[i+1]; - batch_mode++; - i += 2; - return 2; - } else { - // a lone "-h" without a filename will output the help string - return 0; - } - } - return 0; -} - -#if ! (defined(_WIN32) && !defined (__CYGWIN__)) - -int quit_flag = 0; -#include -#ifdef _sigargs -#define SIGARG _sigargs -#else -#ifdef __sigargs -#define SIGARG __sigargs -#else -#define SIGARG int // you may need to fix this for older systems -#endif -#endif - -extern "C" { -static void sigint(SIGARG) { - signal(SIGINT,sigint); - quit_flag = 1; -} -} - -#endif - -/** - Start Fluid. - - Fluid can run in interactive mode with a full user interface to design new - user interfaces and write the C++ files to manage them, - - Fluid can run form the command line in batch mode to convert .fl design files - into C++ source and header files. In batch mode, no display is needed, - particularly no X11 connection will be attempted on Linux/Unix. - - \param[in] argc number of arguments in the list - \param[in] argv pointer to an array of arguments - \return in batch mode, an error code will be returned via \c exit() . This - function return 1, if there was an error in the parameters list. - \todo On Windows, Fluid can under certain conditions open a dialog box, even - in batch mode. Is that intentional? Does it circumvent issues with Windows' - stderr and stdout? - */ -int fluid_main(int argc,char **argv) { - int i = 1; - - setlocale(LC_ALL, ""); // enable multi-language errors in file chooser - setlocale(LC_NUMERIC, "C"); // make sure numeric values are written correctly - g_launch_path = end_with_slash(fl_getcwd_str()); // store the current path at launch - - Fl::args_to_utf8(argc, argv); // for MSYS2/MinGW - if ( (Fl::args(argc,argv,i,arg) == 0) // unsupported argument found - || (batch_mode && (i != argc-1)) // .fl filename missing - || (!batch_mode && (i < argc-1)) // more than one filename found - || (argv[i] && (argv[i][0] == '-'))) { // unknown option - static const char *msg = - "usage: %s name.fl\n" - " -u : update .fl file and exit (may be combined with '-c' or '-cs')\n" - " -c : write .cxx and .h and exit\n" - " -cs : write .cxx and .h and strings and exit\n" - " -o : .cxx output filename, or extension if starts with '.'\n" - " -h : .h output filename, or extension if starts with '.'\n" - " --help : brief usage information\n" - " --version, -v : print fluid version number\n" - " -d : enable internal debugging\n"; - const char *app_name = NULL; - if ( (argc > 0) && argv[0] && argv[0][0] ) - app_name = fl_filename_name(argv[0]); - if ( !app_name || !app_name[0]) - app_name = "fluid"; -#ifdef _MSC_VER - // TODO: if this is fluid-cmd, use stderr and not fl_message - fl_message(msg, app_name); -#else - fprintf(stderr, msg, app_name); -#endif - return 1; - } - if (show_version) { - printf("fluid v%d.%d.%d\n", FL_MAJOR_VERSION, FL_MINOR_VERSION, FL_PATCH_VERSION); - ::exit(0); - } - - const char *c = NULL; - if (g_autodoc_path.empty()) - c = argv[i]; - - fl_register_images(); - - make_main_window(); - - if (c) set_filename(c); - if (!batch_mode) { -#ifdef __APPLE__ - fl_open_callback(apple_open_cb); -#endif // __APPLE__ - Fl::visual((Fl_Mode)(FL_DOUBLE|FL_INDEX)); - Fl_File_Icon::load_system_icons(); - main_window->callback(exit_cb); - position_window(main_window,"main_window_pos", 1, 10, 30, WINWIDTH, WINHEIGHT ); - if (g_shell_config) { - g_shell_config->read(fluid_prefs, FD_STORE_USER); - g_shell_config->update_settings_dialog(); - g_shell_config->rebuild_shell_menu(); - } - g_layout_list.read(fluid_prefs, FD_STORE_USER); - main_window->show(argc,argv); - toggle_widgetbin_cb(0,0); - toggle_codeview_cb(0,0); - if (!c && openlast_button->value() && absolute_history[0][0] && g_autodoc_path.empty()) { - // Open previous file when no file specified... - open_project_file(absolute_history[0]); - } - } - undo_suspend(); - if (c && !fld::io::read_file(c,0)) { - if (batch_mode) { - fprintf(stderr,"%s : %s\n", c, strerror(errno)); - exit(1); - } - fl_message("Can't read %s: %s", c, strerror(errno)); - } - undo_resume(); - - // command line args override code and header filenames from the project file - // in batch mode only - if (batch_mode) { - if (!g_code_filename_arg.empty()) { - g_project.code_file_set = 1; - g_project.code_file_name = g_code_filename_arg; - } - if (!g_header_filename_arg.empty()) { - g_project.header_file_set = 1; - g_project.header_file_name = g_header_filename_arg; - } - } - - if (update_file) { // fluid -u - fld::io::write_file(c,0); - if (!compile_file) - exit(0); - } - - if (compile_file) { // fluid -c[s] - if (compile_strings) - write_strings_cb(0,0); - write_cb(0,0); - exit(0); - } - - // don't lock up if inconsistent command line arguments were given - if (batch_mode) - exit(0); - - set_modflag(0); - undo_clear(); -#ifndef _WIN32 - signal(SIGINT,sigint); -#endif - - // Set (but do not start) timer callback for external editor updates - ExternalCodeEditor::set_update_timer_callback(external_editor_timer); - -#ifndef NDEBUG - // check if the user wants FLUID to generate image for the user documentation - if (!g_autodoc_path.empty()) { - run_autodoc(g_autodoc_path); - set_modflag(0, 0); - exit_cb(0,0); - return 0; - } -#endif - -#ifdef _WIN32 - Fl::run(); -#else - while (!quit_flag) Fl::wait(); - if (quit_flag) exit_cb(0,0); -#endif // _WIN32 - - undo_clear(); - return (0); -} - -/// \} - diff --git a/fluid/app/fluid.h b/fluid/app/fluid.h deleted file mode 100644 index 686047a72..000000000 --- a/fluid/app/fluid.h +++ /dev/null @@ -1,134 +0,0 @@ -// -// FLUID main entry for the Fast Light Tool Kit (FLTK). -// -// Copyright 1998-2025 by Bill Spitzak and others. -// -// This library is free software. Distribution and use rights are outlined in -// the file "COPYING" which should have been included with this file. If this -// file is missing or damaged, see the license at: -// -// https://www.fltk.org/COPYING.php -// -// Please see the following page on how to report bugs and issues: -// -// https://www.fltk.org/bugs.php -// - -#ifndef FLUID_APP_FLUID_H -#define FLUID_APP_FLUID_H - -#include "tools/filename.h" - -#include -#include -#include - -#include - -#define BROWSERWIDTH 300 -#define BROWSERHEIGHT 500 -#define WINWIDTH 300 -#define MENUHEIGHT 25 -#define WINHEIGHT (BROWSERHEIGHT+MENUHEIGHT) - -// ---- types - -class Fl_Double_Window; -class Fl_Window; -class Fl_Menu_Bar; -class Fl_Type; -class Fl_Choice; -class Fl_Button; -class Fl_Check_Button; - -/** - Indicate the storage location for tools like layout suites and shell macros. - \see class Fd_Shell_Command, class Fd_Layout_Suite - */ -typedef enum { - FD_STORE_INTERNAL, ///< stored inside FLUID app - FD_STORE_USER, ///< suite is stored in the user wide FLUID settings - FD_STORE_PROJECT, ///< suite is stored within the current .fl project file - FD_STORE_FILE ///< store suite in external file -} Fd_Tool_Store; - -// ---- global variables - -extern Fl_Preferences fluid_prefs; -extern Fl_Menu_Item Main_Menu[]; -extern Fl_Menu_Bar *main_menubar; -extern Fl_Window *main_window; - -extern int show_guides; -extern int show_restricted; -extern int show_ghosted_outline; -extern int show_comments; - -extern int G_use_external_editor; -extern int G_debug; -extern char G_external_editor_command[512]; - -// File history info... -extern char absolute_history[10][FL_PATH_MAX]; -extern char relative_history[10][FL_PATH_MAX]; -extern void load_history(); -extern void update_history(const char *); - -extern Fl_Menu_Item *save_item; -extern Fl_Menu_Item *history_item; -extern Fl_Menu_Item *widgetbin_item; -extern Fl_Menu_Item *codeview_item; -extern Fl_Menu_Item *overlay_item; -extern Fl_Button *overlay_button; -extern Fl_Menu_Item *guides_item; -extern Fl_Menu_Item *restricted_item; -extern Fl_Check_Button *guides_button; - -extern int modflag; - -extern int update_file; // fluid -u -extern int compile_file; // fluid -c -extern int compile_strings; // fluic -cs -extern int batch_mode; - -extern int pasteoffset; - -extern std::string g_code_filename_arg; -extern std::string g_header_filename_arg; -extern std::string g_launch_path; - -extern std::string g_autodoc_path; - -// ---- public functions - -extern int fluid_main(int argc,char **argv); - -extern bool new_project(bool user_must_confirm = true); -extern void enter_project_dir(); -extern void leave_project_dir(); -extern void set_filename(const char *c); -extern void set_modflag(int mf, int mfc=-1); - -extern const std::string &get_tmpdir(); -extern std::string end_with_slash(const std::string &str); - -// ---- public callback functions - -extern void save_cb(Fl_Widget *, void *v); -extern void save_template_cb(Fl_Widget *, void *); -extern void revert_cb(Fl_Widget *,void *); -extern void exit_cb(Fl_Widget *,void *); - -extern int write_code_files(bool dont_show_completion_dialog=false); -extern void write_strings_cb(Fl_Widget *, void *); -extern void align_widget_cb(Fl_Widget *, long); -extern void toggle_widgetbin_cb(Fl_Widget *, void *); - -extern char position_window(Fl_Window *w, const char *prefsName, int Visible, int X, int Y, int W=0, int H=0); - -inline int fd_min(int a, int b) { return (a < b ? a : b); } -inline int fd_max(int a, int b) { return (a > b ? a : b); } -inline int fd_min(int a, int b, int c) { return fd_min(a, fd_min(b, c)); } - -#endif // FLUID_APP_FLUID_H - diff --git a/fluid/app/history.cxx b/fluid/app/history.cxx new file mode 100644 index 000000000..7eadb5469 --- /dev/null +++ b/fluid/app/history.cxx @@ -0,0 +1,115 @@ +// +// Fluid Project File History code for the Fast Light Tool Kit (FLTK). +// +// Copyright 1998-2025 by Bill Spitzak and others. +// +// This library is free software. Distribution and use rights are outlined in +// the file "COPYING" which should have been included with this file. If this +// file is missing or damaged, see the license at: +// +// https://www.fltk.org/COPYING.php +// +// Please see the following page on how to report bugs and issues: +// +// https://www.fltk.org/bugs.php +// + +#include "app/history.h" + +#include "Fluid.h" + +#include "../src/flstring.h" + +using namespace fld; +using namespace fld::app; + + +/** + Load file history from preferences. + + This loads the absolute filepaths of the last 10 used design files. + It also computes and stores the relative filepaths for display in + the main menu. + */ +void History::load() { + int i; // Looping var + int max_files; + + Fluid.preferences.get("recent_files", max_files, 5); + if (max_files > 10) max_files = 10; + + for (i = 0; i < max_files; i ++) { + Fluid.preferences.get( Fl_Preferences::Name("file%d", i), abspath[i], "", sizeof(abspath[i])); + if (abspath[i][0]) { + // Make a shortened version of the filename for the menu... + std::string fn = fl_filename_shortened(abspath[i], 48); + strncpy(relpath[i], fn.c_str(), sizeof(relpath[i]) - 1); + if (i == 9) Fluid.history_item[i].flags = FL_MENU_DIVIDER; + else Fluid.history_item[i].flags = 0; + } else break; + } + + for (; i < 10; i ++) { + if (i) Fluid.history_item[i-1].flags |= FL_MENU_DIVIDER; + Fluid.history_item[i].hide(); + } +} + +/** + Update file history from preferences. + + Add this new filepath to the history and update the main menu. + Writes the new file history to the app preferences. + + \param[in] project_file path and filename of .fl project file, will be + converted into an absolute file path based on the current working directory. + */ +void History::update(std::string project_file) { + int i; // Looping var + int max_files; + + Fluid.preferences.get("recent_files", max_files, 5); + if (max_files > 10) max_files = 10; + + std::string absolute = fld::fix_separators(fl_filename_absolute_str(project_file)); + for (i = 0; i < max_files; i ++) +#if defined(_WIN32) || defined(__APPLE__) + if (!strcasecmp(absolute.c_str(), abspath[i])) break; +#else + if (!strcmp(absolute.c_str(), abspath[i])) break; +#endif // _WIN32 || __APPLE__ + + // Does the first entry match? + if (i == 0) + return; + + // Was there no match? + if (i >= max_files) + i = max_files - 1; + + // Move the other filenames down in the list... + memmove(abspath + 1, abspath, i * sizeof(abspath[0])); + memmove(relpath + 1, relpath, i * sizeof(relpath[0])); + + // Put the new file at the top... + strlcpy(abspath[0], absolute.c_str(), sizeof(abspath[0])); + std::string fn = fl_filename_shortened(absolute, 48); + strncpy(relpath[0], fn.c_str(), sizeof(relpath[0]) - 1); + + // Update the menu items as needed... + for (i = 0; i < max_files; i ++) { + Fluid.preferences.set( Fl_Preferences::Name("file%d", i), abspath[i]); + if (abspath[i][0]) { + if (i == 9) Fluid.history_item[i].flags = FL_MENU_DIVIDER; + else Fluid.history_item[i].flags = 0; + } else break; + } + + for (; i < 10; i ++) { + Fluid.preferences.set( Fl_Preferences::Name("file%d", i), ""); + if (i) Fluid.history_item[i-1].flags |= FL_MENU_DIVIDER; + Fluid.history_item[i].hide(); + } + Fluid.preferences.flush(); +} + diff --git a/fluid/app/history.h b/fluid/app/history.h new file mode 100644 index 000000000..e32723b4b --- /dev/null +++ b/fluid/app/history.h @@ -0,0 +1,45 @@ +// +// Fluid Project File History header for the Fast Light Tool Kit (FLTK). +// +// Copyright 1998-2025 by Bill Spitzak and others. +// +// This library is free software. Distribution and use rights are outlined in +// the file "COPYING" which should have been included with this file. If this +// file is missing or damaged, see the license at: +// +// https://www.fltk.org/COPYING.php +// +// Please see the following page on how to report bugs and issues: +// +// https://www.fltk.org/bugs.php +// + +#ifndef FLUID_APP_HISTORY_H +#define FLUID_APP_HISTORY_H + +#include + +#include + +namespace fld { +namespace app { + +class History { +public: + /// Stores the absolute filename of the last 10 project files, saved in app preferences. + char abspath[10][FL_PATH_MAX] { }; + /// The list of filenames as displayed in the main menu. + char relpath[10][FL_PATH_MAX] { }; + // Create the project file history. + History() = default; + // Load the project history from the preferences database. + void load(); + // Add a new file to the project history using absolute paths. + void update(std::string project_file); +}; + +} // namespace app +} // namespace fld + +#endif // FLUID_APP_HISTORY_H + diff --git a/fluid/app/mergeback.cxx b/fluid/app/mergeback.cxx deleted file mode 100644 index e0eafe500..000000000 --- a/fluid/app/mergeback.cxx +++ /dev/null @@ -1,493 +0,0 @@ -// -// MergeBack routines for the Fast Light Tool Kit (FLTK). -// -// Copyright 1998-2023 by Bill Spitzak and others. -// -// This library is free software. Distribution and use rights are outlined in -// the file "COPYING" which should have been included with this file. If this -// file is missing or damaged, see the license at: -// -// https://www.fltk.org/COPYING.php -// -// Please see the following page on how to report bugs and issues: -// -// https://www.fltk.org/bugs.php -// - -#if 0 -// Matt: disabled - -#include "app/mergeback.h" - -#include "app/fluid.h" -#include "app/undo.h" -#include "io/Code_Writer.h" -#include "nodes/Fl_Function_Type.h" -#include "nodes/Fl_Widget_Type.h" - -#include -#include - -#include -#include -#include -#include -#include - -extern void propagate_load(Fl_Group*, void*); -extern void load_panel(); -extern void redraw_browser(); - -// TODO: add application user setting to control mergeback -// [] new projects default to mergeback -// [] check mergeback when loading project -// [] check mergeback when app gets focus -// [] always apply if safe -// TODO: command line option for mergeback -// -mb or --merge-back -// -mbs or --merge-back-if-safe -// NOTE: automatic mergeback on timer when file changes if app focus doesn't work -// NOTE: allow the user to edit comment blocks - -/** - Merge external changes in a source code file back into the current project. - - This experimental function reads a source code file line by line. When it - encounters a special tag in a line, the crc32 stored in the tag is compared - to the crc32 that was calculated from the code lines since the previous tag. - - If the crc's differ, the user has modified the source file externally, and the - given block differs from the block as it was generated by FLUID. Depending on - the block type, the user has modified the widget code (FD_TAG_GENERIC), which - can not be transferred back into the project. - - Modifications to code blocks and callbacks (CODE, CALLBACK) can be merged back - into the project. Their corresponding Fl_Type is found using the unique - node id that is part of the tag. The block is only merged back if the crc's - from the project and from the edited block differ. - - The caller must make sure that this code file was generated by the currently - loaded project. - - The user is informed in detailed dialogs what the function discovered and - offered to merge or cancel if appropriate. Just in case this function is - destructive, "undo" restores the state before a MergeBack. - - Callers can set different task. FD_MERGEBACK_ANALYSE checks if there are any - modifications in the code file and returns -1 if there was an error, or a - bit field where bit 0 is set if internal structures were modified, bit 1 if - code was changed, and bit 2 if modified blocks were found, but no Type node. - Bit 3 is set, if code was changed in the code file *and* the project. - - FD_MERGEBACK_INTERACTIVE checks for changes and presents a status dialog box - to the user if there were conflicting changes or if a mergeback is possible, - presenting the user the option to merge or cancel. Returns 0 if the project - remains unchanged, and 1 if the user merged changes back. -1 is returned if an - invalid tag was found. - - FD_MERGEBACK_APPLY merges all changes back into the project without any - interaction. Returns 0 if nothing changed, and 1 if it merged any changes back. - - FD_MERGEBACK_APPLY_IF_SAFE merges changes back only if there are no conflicts. - Returns 0 if nothing changed, and 1 if it merged any changes back, and -1 if - there were conflicts. - - \note this function is currently part of fld::io::Code_Writer to get easy access - to our crc32 code that also wrote the code file originally. - - \param[in] s path and filename of the source code file - \param[in] task see above - \return -1 if an error was found in a tag - \return -2 if no code file was found - \return see above - */ -int merge_back(const std::string &s, const std::string &p, int task) { - if (g_project.write_mergeback_data) { - Fd_Mergeback mergeback; - return mergeback.merge_back(s, p, task); - } else { - // nothing to be done if the mergeback option is disabled in the project - return 0; - } -} - -/** Allocate and initialize MergeBack class. */ -Fd_Mergeback::Fd_Mergeback() : - code(NULL), - line_no(0), - tag_error(0), - num_changed_code(0), - num_changed_structure(0), - num_uid_not_found(0), - num_possible_override(0) -{ -} - -/** Release allocated resources. */ -Fd_Mergeback::~Fd_Mergeback() -{ - if (code) ::fclose(code); -} - -/** Remove the first two spaces at every line start. - \param[inout] s block of C code - */ -void Fd_Mergeback::unindent(char *s) { - char *d = s; - bool line_start = true; - while (*s) { - if (line_start) { - if (*s>0 && isspace(*s)) s++; - if (*s>0 && isspace(*s)) s++; - line_start = false; - } - if (*s=='\r') s++; - if (*s=='\n') line_start = true; - *d++ = *s++; - } - *d = 0; -} - -/** - Read a block of text from the source file and remove the leading two spaces in every line. - \param[in] start start of the block within the file - \param[in] end end of text within the file - \return a string holding the text that was found in the file - */ -std::string Fd_Mergeback::read_and_unindent_block(long start, long end) { - long bsize = end-start; - long here = ::ftell(code); - ::fseek(code, start, SEEK_SET); - char *block = (char*)::malloc(bsize+1); - size_t n = ::fread(block, bsize, 1, code); - if (n!=1) - block[0] = 0; // read error - else - block[bsize] = 0; - unindent(block); - std::string str = block; - ::free(block); - ::fseek(code, here, SEEK_SET); - return str; -} - -/** Tell user the results of our MergeBack analysis and pop up a dialog to give - the user a choice to merge or cancel. - \return 1 if the user wants to merge (choice dialog was shown) - \return 0 if there is nothing to merge (no dialog was shown) - \return -1 if the user wants to cancel or an error occurred or an issue was presented - (message or choice dialog was shown) - */ -int Fd_Mergeback::ask_user_to_merge(const std::string &code_filename, const std::string &proj_filename) { - if (tag_error) { - fl_message("Comparing\n \"%s\"\nto\n \"%s\"\n\n" - "MergeBack found an error in line %d while reading tags\n" - "from the source code. Merging code back is not possible.", - code_filename.c_str(), proj_filename.c_str(), line_no); - return -1; - } - if (!num_changed_code && !num_changed_structure) { - return 0; - } - if (num_changed_structure && !num_changed_code) { - fl_message("Comparing\n \"%1$s\"\nto\n \"%2$s\"\n\n" - "MergeBack found %3$d modifications in the project structure\n" - "of the source code. These kind of changes can no be\n" - "merged back and will be lost when the source code is\n" - "generated again from the open project.", - code_filename.c_str(), proj_filename.c_str(), num_changed_structure); - return -1; - } - std::string msg = "Comparing\n \"%1$s\"\nto\n \"%2$s\"\n\n" - "MergeBack found %3$d modifications in the source code."; - if (num_possible_override) - msg += "\n\nWARNING: %6$d of these modified blocks appear to also have\n" - "changed in the project. Merging will override changes in\n" - "the project with changes from the source code file."; - if (num_uid_not_found) - msg += "\n\nWARNING: for %4$d of these modifications no Type node\n" - "can be found and these modification can't be merged back."; - if (!num_possible_override && !num_uid_not_found) - msg += "\nMerging these changes back appears to be safe."; - - if (num_changed_structure) - msg += "\n\nWARNING: %5$d modifications were found in the project\n" - "structure. These kind of changes can no be merged back\n" - "and will be lost when the source code is generated again\n" - "from the open project."; - - if (num_changed_code==num_uid_not_found) { - fl_message(msg.c_str(), - code_filename.c_str(), proj_filename.c_str(), - num_changed_code, num_uid_not_found, - num_changed_structure, num_possible_override); - return -1; - } else { - msg += "\n\nClick Cancel to abort the MergeBack operation.\n" - "Click Merge to merge all code changes back into\n" - "the open project."; - int c = fl_choice(msg.c_str(), "Cancel", "Merge", NULL, - code_filename.c_str(), proj_filename.c_str(), - num_changed_code, num_uid_not_found, - num_changed_structure, num_possible_override); - if (c==0) return -1; - return 1; - } -} - -/** Analyse the block and its corresponding widget callback. - Return findings in num_changed_code, num_changed_code, and num_uid_not_found. - */ -void Fd_Mergeback::analyse_callback(unsigned long code_crc, unsigned long tag_crc, int uid) { - Fl_Type *tp = Fl_Type::find_by_uid(uid); - if (tp && tp->is_true_widget()) { - std::string cb = tp->callback(); cb += "\n"; - unsigned long project_crc = fld::io::Code_Writer::block_crc(cb.c_str()); - // check if the code and project crc are the same, so this modification was already applied - if (project_crc!=code_crc) { - num_changed_code++; - // check if the block change on the project side as well, so we may override changes - if (project_crc!=tag_crc) { - num_possible_override++; - } - } - } else { - num_uid_not_found++; - num_changed_code++; - } -} - -/** Analyse the block and its corresponding Code Type. - Return findings in num_changed_code, num_changed_code, and num_uid_not_found. - */ -void Fd_Mergeback::analyse_code(unsigned long code_crc, unsigned long tag_crc, int uid) { - Fl_Type *tp = Fl_Type::find_by_uid(uid); - if (tp && tp->is_a(ID_Code)) { - std::string code = tp->name(); code += "\n"; - unsigned long project_crc = fld::io::Code_Writer::block_crc(code.c_str()); - // check if the code and project crc are the same, so this modification was already applied - if (project_crc!=code_crc) { - num_changed_code++; - // check if the block change on the project side as well, so we may override changes - if (project_crc!=tag_crc) { - num_possible_override++; - } - } - } else { - num_changed_code++; - num_uid_not_found++; - } -} - - -/** Analyse the code file and return findings in class member variables. - - The code file must be open for reading already. - - * tag_error is set if a tag was found, but could not be read - * line_no returns the line where an error occurred - * num_changed_code is set to the number of changed code blocks in the file. - Code changes can be merged back to the project. - * num_changed_structure is set to the number of structural changes. - Structural changes outside of code blocks can not be read back. - * num_uid_not_found number of blocks that were modified, but the corresponding - type or widget can not be found in the project - * num_possible_override number of blocks that were changed in the code file, - but also were changed in the project. - - \return -1 if reading a tag failed, otherwise 0 - */ -int Fd_Mergeback::analyse() { - // initialize local variables - unsigned long code_crc = 0; - bool line_start = true; - char line[1024]; - // bail if the caller has not opened a file yet - if (!code) return 0; - // initialize member variables to return our findings - line_no = 0; - tag_error = 0; - num_changed_code = 0; - num_changed_structure = 0; - num_uid_not_found = 0; - num_possible_override = 0; - code_crc = 0; - // loop through all lines in the code file - ::fseek(code, 0, SEEK_SET); - for (;;) { - // get the next line until end of file - if (fgets(line, 1023, code)==0) break; - line_no++; - const char *tag = strstr(line, "//~fl~"); - if (!tag) { - // if this line has no tag, add the contents to the CRC and continue - code_crc = fld::io::Code_Writer::block_crc(line, -1, code_crc, &line_start); - } else { - // if this line has a tag, read all tag data - int tag_type = -1, uid = 0; - unsigned long tag_crc = 0; - int n = sscanf(tag, "//~fl~%d~%04x~%08lx~~", &tag_type, &uid, &tag_crc); - if (n!=3 || tag_type<0 || tag_type>FD_TAG_LAST ) { tag_error = 1; return -1; } - if (code_crc != tag_crc) { - switch (tag_type) { - case FD_TAG_GENERIC: - num_changed_structure++; - break; - case FD_TAG_MENU_CALLBACK: - case FD_TAG_WIDGET_CALLBACK: - analyse_callback(code_crc, tag_crc, uid); - break; - case FD_TAG_CODE: - analyse_code(code_crc, tag_crc, uid); - break; - } - } - // reset everything for the next block - code_crc = 0; - line_start = true; - } - } - return 0; -} - -/** Apply callback mergebacks from the code file to the project. - \return 1 if the project changed - */ -int Fd_Mergeback::apply_callback(long block_end, long block_start, unsigned long code_crc, int uid) { - Fl_Type *tp = Fl_Type::find_by_uid(uid); - if (tp && tp->is_true_widget()) { - std::string cb = tp->callback(); cb += "\n"; - unsigned long project_crc = fld::io::Code_Writer::block_crc(cb.c_str()); - if (project_crc!=code_crc) { - tp->callback(read_and_unindent_block(block_start, block_end).c_str()); - return 1; - } - } - return 0; -} - -/** Apply callback mergebacks from the code file to the project. - \return 1 if the project changed - */ -int Fd_Mergeback::apply_code(long block_end, long block_start, unsigned long code_crc, int uid) { - Fl_Type *tp = Fl_Type::find_by_uid(uid); - if (tp && tp->is_a(ID_Code)) { - std::string cb = tp->name(); cb += "\n"; - unsigned long project_crc = fld::io::Code_Writer::block_crc(cb.c_str()); - if (project_crc!=code_crc) { - tp->name(read_and_unindent_block(block_start, block_end).c_str()); - return 1; - } - } - return 0; -} - -/** Apply all possible mergebacks from the code file to the project. - The code file must be open for reading already. - \return -1 if reading a tag failed, 0 if nothing changed, 1 if the project changed - */ -int Fd_Mergeback::apply() { - // initialize local variables - unsigned long code_crc = 0; - bool line_start = true; - char line[1024]; - int changed = 0; - long block_start = 0; - long block_end = 0; - // bail if the caller has not opened a file yet - if (!code) return 0; - // initialize member variables to return our findings - line_no = 0; - tag_error = 0; - code_crc = 0; - // loop through all lines in the code file - ::fseek(code, 0, SEEK_SET); - for (;;) { - // get the next line until end of file - if (fgets(line, 1023, code)==0) break; - line_no++; - const char *tag = strstr(line, "//~fl~"); - if (!tag) { - // if this line has no tag, add the contents to the CRC and continue - code_crc = fld::io::Code_Writer::block_crc(line, -1, code_crc, &line_start); - block_end = ::ftell(code); - } else { - // if this line has a tag, read all tag data - int tag_type = -1, uid = 0; - unsigned long tag_crc = 0; - int n = sscanf(tag, "//~fl~%d~%04x~%08lx~~", &tag_type, &uid, &tag_crc); - if (n!=3 || tag_type<0 || tag_type>FD_TAG_LAST ) { tag_error = 1; return -1; } - if (code_crc != tag_crc) { - if (tag_type==FD_TAG_MENU_CALLBACK || tag_type==FD_TAG_WIDGET_CALLBACK) { - changed |= apply_callback(block_end, block_start, code_crc, uid); - } else if (tag_type==FD_TAG_CODE) { - changed |= apply_code(block_end, block_start, code_crc, uid); - } - } - // reset everything for the next block - code_crc = 0; - line_start = true; - block_start = ::ftell(code); - } - } - return changed; -} - -/** Dispatch the MergeBack into analysis, interactive, or apply directly. - \param[in] s source code filename and path - \param[in] task one of FD_MERGEBACK_ANALYSE, FD_MERGEBACK_INTERACTIVE, - FD_MERGEBACK_APPLY_IF_SAFE, or FD_MERGEBACK_APPLY - \return -1 if an error was found in a tag - \return -2 if no code file was found - \return See more at ::merge_back(const std::string &s, int task). - */ -int Fd_Mergeback::merge_back(const std::string &s, const std::string &p, int task) { - int ret = 0; - code = fl_fopen(s.c_str(), "rb"); - if (!code) return -2; - do { // no actual loop, just make sure we close the code file - if (task == FD_MERGEBACK_ANALYSE) { - analyse(); - if (tag_error) {ret = -1; break; } - if (num_changed_structure) ret |= 1; - if (num_changed_code) ret |= 2; - if (num_uid_not_found) ret |= 4; - if (num_possible_override) ret |= 8; - break; - } - if (task == FD_MERGEBACK_INTERACTIVE) { - analyse(); - ret = ask_user_to_merge(s, p); - if (ret != 1) - return ret; - task = FD_MERGEBACK_APPLY; // fall through - } - if (task == FD_MERGEBACK_APPLY_IF_SAFE) { - analyse(); - if (tag_error || num_changed_structure || num_possible_override) { - ret = -1; - break; - } - if (num_changed_code==0) { - ret = 0; - break; - } - task = FD_MERGEBACK_APPLY; // fall through - } - if (task == FD_MERGEBACK_APPLY) { - ret = apply(); - if (ret == 1) { - set_modflag(1); - redraw_browser(); - load_panel(); - } - ret = 1; // avoid message box in caller - } - } while (0); - fclose(code); - code = NULL; - return ret; -} - -#endif - diff --git a/fluid/app/mergeback.h b/fluid/app/mergeback.h deleted file mode 100644 index f828e9669..000000000 --- a/fluid/app/mergeback.h +++ /dev/null @@ -1,81 +0,0 @@ -// -// MergeBack routines for the Fast Light Tool Kit (FLTK). -// -// Copyright 2023 by Bill Spitzak and others. -// -// This library is free software. Distribution and use rights are outlined in -// the file "COPYING" which should have been included with this file. If this -// file is missing or damaged, see the license at: -// -// https://www.fltk.org/COPYING.php -// -// Please see the following page on how to report bugs and issues: -// -// https://www.fltk.org/bugs.php -// - -// Matt: disabled -#if 0 - -#ifndef _FLUID_MERGEBACK_H -#define _FLUID_MERGEBACK_H - -#include - -#include -#include - -const int FD_TAG_GENERIC = 0; -const int FD_TAG_CODE = 1; -const int FD_TAG_MENU_CALLBACK = 2; -const int FD_TAG_WIDGET_CALLBACK = 3; -const int FD_TAG_LAST = 3; - -const int FD_MERGEBACK_ANALYSE = 0; -const int FD_MERGEBACK_INTERACTIVE = 1; -const int FD_MERGEBACK_APPLY = 2; -const int FD_MERGEBACK_APPLY_IF_SAFE = 3; - -/** Class that implements the MergeBack functionality. - \see merge_back(const std::string &s, int task) - */ -class Fd_Mergeback -{ -protected: - /// Pointer to the C++ code file. - FILE *code; - /// Current line number in the C++ code file. - int line_no; - /// Set if there was an error reading a tag. - int tag_error; - /// Number of code blocks that were different than the CRC in their tag. - int num_changed_code; - /// Number of generic structure blocks that were different than the CRC in their tag. - int num_changed_structure; - /// Number of code block that were modified, but a type node by that uid was not found. - int num_uid_not_found; - /// Number of modified code block where the corresponding project block also changed. - int num_possible_override; - - void unindent(char *s); - std::string read_and_unindent_block(long start, long end); - void analyse_callback(unsigned long code_crc, unsigned long tag_crc, int uid); - void analyse_code(unsigned long code_crc, unsigned long tag_crc, int uid); - int apply_callback(long block_end, long block_start, unsigned long code_crc, int uid); - int apply_code(long block_end, long block_start, unsigned long code_crc, int uid); - -public: - Fd_Mergeback(); - ~Fd_Mergeback(); - int merge_back(const std::string &s, const std::string &p, int task); - int ask_user_to_merge(const std::string &s, const std::string &p); - int analyse(); - int apply(); -}; - -extern int merge_back(const std::string &s, const std::string &p, int task); - - -#endif // _FLUID_MERGEBACK_H - -#endif diff --git a/fluid/app/project.cxx b/fluid/app/project.cxx deleted file mode 100644 index f369680e5..000000000 --- a/fluid/app/project.cxx +++ /dev/null @@ -1,192 +0,0 @@ -// -// FLUID main entry for the Fast Light Tool Kit (FLTK). -// -// Copyright 1998-2025 by Bill Spitzak and others. -// -// This library is free software. Distribution and use rights are outlined in -// the file "COPYING" which should have been included with this file. If this -// file is missing or damaged, see the license at: -// -// https://www.fltk.org/COPYING.php -// -// Please see the following page on how to report bugs and issues: -// -// https://www.fltk.org/bugs.php -// - -#include "app/project.h" - -#include "nodes/Fl_Type.h" -#include "panels/settings_panel.h" - -// ---- project settings - -/// The current project, possibly a new, empty roject -Fluid_Project g_project; - -/** - Initialize a new project. - */ -Fluid_Project::Fluid_Project() : -i18n_type(FD_I18N_NONE), -include_H_from_C(1), -use_FL_COMMAND(0), -utf8_in_src(0), -avoid_early_includes(0), -header_file_set(0), -code_file_set(0), -write_mergeback_data(0), -header_file_name(".h"), -code_file_name(".cxx") -{ } - -/** - Clear all project resources. - Not implemented. - */ -Fluid_Project::~Fluid_Project() { -} - -/** - Reset all project setting to create a new empty project. - */ -void Fluid_Project::reset() { - ::delete_all(); - i18n_type = FD_I18N_NONE; - - i18n_gnu_include = ""; - i18n_gnu_conditional = ""; - i18n_gnu_function = "gettext"; - i18n_gnu_static_function = "gettext_noop"; - - i18n_pos_include = ""; - i18n_pos_conditional = ""; - i18n_pos_file = ""; - i18n_pos_set = "1"; - - include_H_from_C = 1; - use_FL_COMMAND = 0; - utf8_in_src = 0; - avoid_early_includes = 0; - header_file_set = 0; - code_file_set = 0; - header_file_name = ".h"; - code_file_name = ".cxx"; - write_mergeback_data = 0; -} - -/** - Tell the project and i18n tab of the settings dialog to refresh themselves. - */ -void Fluid_Project::update_settings_dialog() { - if (settings_window) { - w_settings_project_tab->do_callback(w_settings_project_tab, LOAD); - w_settings_i18n_tab->do_callback(w_settings_i18n_tab, LOAD); - } -} - -/** - Get the absolute path of the project file, for example `/Users/matt/dev/`. - \return the path ending in '/' - */ -std::string Fluid_Project::projectfile_path() const { - return end_with_slash(fl_filename_absolute_str(fl_filename_path_str(proj_filename), g_launch_path)); -} - -/** - Get the project file name including extension, for example `test.fl`. - \return the file name without path - */ -std::string Fluid_Project::projectfile_name() const { - return fl_filename_name(proj_filename); -} - -/** - Get the absolute path of the generated C++ code file, for example `/Users/matt/dev/src/`. - \return the path ending in '/' - */ -std::string Fluid_Project::codefile_path() const { - std::string path = fl_filename_path_str(code_file_name); - if (batch_mode) - return end_with_slash(fl_filename_absolute_str(path, g_launch_path)); - else - return end_with_slash(fl_filename_absolute_str(path, projectfile_path())); -} - -/** - Get the generated C++ code file name including extension, for example `test.cxx`. - \return the file name without path - */ -std::string Fluid_Project::codefile_name() const { - std::string name = fl_filename_name_str(code_file_name); - if (name.empty()) { - return fl_filename_setext_str(fl_filename_name(proj_filename), ".cxx"); - } else if (name[0] == '.') { - return fl_filename_setext_str(fl_filename_name(proj_filename), code_file_name); - } else { - return name; - } -} - -/** - Get the absolute path of the generated C++ header file, for example `/Users/matt/dev/src/`. - \return the path ending in '/' - */ -std::string Fluid_Project::headerfile_path() const { - std::string path = fl_filename_path_str(header_file_name); - if (batch_mode) - return end_with_slash(fl_filename_absolute_str(path, g_launch_path)); - else - return end_with_slash(fl_filename_absolute_str(path, projectfile_path())); -} - -/** - Get the generated C++ header file name including extension, for example `test.cxx`. - \return the file name without path - */ -std::string Fluid_Project::headerfile_name() const { - std::string name = fl_filename_name_str(header_file_name); - if (name.empty()) { - return fl_filename_setext_str(fl_filename_name_str(proj_filename), ".h"); - } else if (name[0] == '.') { - return fl_filename_setext_str(fl_filename_name_str(proj_filename), header_file_name); - } else { - return name; - } -} - -/** - Get the absolute path of the generated i18n strings file, for example `/Users/matt/dev/`. - Although it may be more useful to put the text file into the same directory - with the source and header file, historically, the text is always saved with - the project file in interactive mode, and in the FLUID launch directory in - batch mode. - \return the path ending in '/' - */ -std::string Fluid_Project::stringsfile_path() const { - if (batch_mode) - return g_launch_path; - else - return projectfile_path(); -} - -/** - Get the generated i18n text file name including extension, for example `test.po`. - \return the file name without path - */ -std::string Fluid_Project::stringsfile_name() const { - switch (i18n_type) { - default: return fl_filename_setext_str(fl_filename_name(proj_filename), ".txt"); - case FD_I18N_GNU: return fl_filename_setext_str(fl_filename_name(proj_filename), ".po"); - case FD_I18N_POSIX: return fl_filename_setext_str(fl_filename_name(proj_filename), ".msg"); - } -} - -/** - Get the name of the project file without the filename extension. - \return the file name without path or extension - */ -std::string Fluid_Project::basename() const { - return fl_filename_setext_str(fl_filename_name(proj_filename), ""); -} - diff --git a/fluid/app/project.h b/fluid/app/project.h deleted file mode 100644 index 058ce1764..000000000 --- a/fluid/app/project.h +++ /dev/null @@ -1,103 +0,0 @@ -// -// FLUID main entry for the Fast Light Tool Kit (FLTK). -// -// Copyright 1998-2025 by Bill Spitzak and others. -// -// This library is free software. Distribution and use rights are outlined in -// the file "COPYING" which should have been included with this file. If this -// file is missing or damaged, see the license at: -// -// https://www.fltk.org/COPYING.php -// -// Please see the following page on how to report bugs and issues: -// -// https://www.fltk.org/bugs.php -// - - -#ifndef FLUID_APP_PROJECT_H -#define FLUID_APP_PROJECT_H - -#include - -// ---- project class declaration - -/** - Enumeration of available internationalization types. - */ -typedef enum { - FD_I18N_NONE = 0, ///< No i18n, all strings are litearals - FD_I18N_GNU, ///< GNU gettext internationalization - FD_I18N_POSIX ///< Posix catgets internationalization -} Fd_I18n_Type; - -/** - Data and settings for a FLUID project file. - */ -class Fluid_Project { -public: - Fluid_Project(); - ~Fluid_Project(); - void reset(); - void update_settings_dialog(); - - std::string projectfile_path() const; - std::string projectfile_name() const; - std::string codefile_path() const; - std::string codefile_name() const; - std::string headerfile_path() const; - std::string headerfile_name() const; - std::string stringsfile_path() const; - std::string stringsfile_name() const; - std::string basename() const; - - /// One of the available internationalization types. - Fd_I18n_Type i18n_type; - /// Include file for GNU i18n, writes an #include statement into the source - /// file. This is usually `` or `"gettext.h"` for GNU gettext. - std::string i18n_gnu_include; - // Optional name of a macro for conditional i18n compilation. - std::string i18n_gnu_conditional; - /// For the gettext/intl.h options, this is the function that translates text - /// at runtime. This is usually "gettext" or "_". - std::string i18n_gnu_function; - /// For the gettext/intl.h options, this is the function that marks the translation - /// of text at initialisation time. This is usually "gettext_noop" or "N_". - std::string i18n_gnu_static_function; - - /// Include file for Posix i18n, write a #include statement into the source - /// file. This is usually `` for Posix catgets. - std::string i18n_pos_include; - // Optional name of a macro for conditional i18n compilation. - std::string i18n_pos_conditional; - /// Name of the nl_catd database - std::string i18n_pos_file; - /// Message set ID for the catalog. - std::string i18n_pos_set; - - /// If set, generate code to include the header file form the c++ file - int include_H_from_C; - /// If set, handle keyboard shortcut Ctrl on macOS using Cmd instead - int use_FL_COMMAND; - /// Clear if UTF-8 characters in statics texts are written as escape sequences - int utf8_in_src; - /// If set, will not be included from the header code before anything else - int avoid_early_includes; - /// If set, command line overrides header file name in .fl file. - int header_file_set; - /// If set, command line overrides source code file name in .fl file. - int code_file_set; - int write_mergeback_data; - /// Filename of the current .fl project file - const char *proj_filename { nullptr }; - /// Hold the default extension for header files, or the entire filename if set via command line. - std::string header_file_name; - /// Hold the default extension for source code files, or the entire filename if set via command line. - std::string code_file_name; -}; - -extern Fluid_Project g_project; - -#endif // FLUID_APP_PROJECT_H - - diff --git a/fluid/app/shell_command.cxx b/fluid/app/shell_command.cxx index 7012de8e6..3bb252cae 100644 --- a/fluid/app/shell_command.cxx +++ b/fluid/app/shell_command.cxx @@ -1,7 +1,7 @@ // -// FLUID main entry for the Fast Light Tool Kit (FLTK). +// Shell Command database coe for the Fast Light Tool Kit (FLTK). // -// Copyright 1998-2023 by Bill Spitzak and others. +// 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 @@ -62,7 +62,7 @@ // TODO: make the settings dialog resizable // TODO: make g_shell_config static, not a pointer, but don't load anything in batch mode -// FEATURE: Fd_Tool_Store icons are currently redundant with @file and @save and could be improved +// FEATURE: fld::Tool_Store icons are currently redundant with @file and @save and could be improved // FEATURE: hostname, username, getenv support? // FEATURE: add the files ./fluid.prefs and ./fluid.user.prefs as tool locations // FEATURE: interpret compiler output, for example: clang, and highlight errors and warnings @@ -97,8 +97,8 @@ #include "app/shell_command.h" -#include "app/fluid.h" -#include "app/project.h" +#include "Fluid.h" +#include "Project.h" #include "io/Project_Reader.h" #include "io/Project_Writer.h" #include "panels/settings_panel.h" @@ -110,6 +110,8 @@ #include +using namespace fld; + static std::string fltk_config_cmd; static Fl_Process s_proc; @@ -120,40 +122,6 @@ bool shell_command_running() { return s_proc.desc() ? true : false; } -/** - Reads an entry from the group. A default value must be - supplied. The return value indicates if the value was available - (non-zero) or the default was used (0). - - \param[in] prefs preference group - \param[in] key name of entry - \param[out] value returned from preferences or default value if none was set - \param[in] defaultValue default value to be used if no preference was set - \return 0 if the default value was used - */ -char preferences_get(Fl_Preferences &prefs, const char *key, std::string &value, const std::string &defaultValue) { - char *v = NULL; - char ret = prefs.get(key, v, defaultValue.c_str()); - value = v; - ::free(v); - return ret; -} - -/** - Sets an entry (name/value pair). The return value indicates if there - was a problem storing the data in memory. However it does not - reflect if the value was actually stored in the preference file. - - \param[in] prefs preference group - \param[in] entry name of entry - \param[in] value set this entry to value (stops at the first nul character). - \return 0 if setting the value failed - */ -char preferences_set(Fl_Preferences &prefs, const char *key, const std::string &value) { - return prefs.set(key, value.c_str()); -} - - /** \class Fl_Process Launch an external shell command. */ @@ -162,7 +130,6 @@ char preferences_set(Fl_Preferences &prefs, const char *key, const std::string & Create a process manager */ Fl_Process::Fl_Process() { - _fpt= NULL; } /** @@ -183,13 +150,13 @@ Fl_Process::~Fl_Process() { FILE * Fl_Process::popen(const char *cmd, const char *mode) { #if defined(_WIN32) && !defined(__CYGWIN__) // PRECONDITIONS - if (!mode || !*mode || (*mode!='r' && *mode!='w') ) return NULL; + if (!mode || !*mode || (*mode!='r' && *mode!='w') ) return nullptr; if (_fpt) close(); // close first before reuse ptmode = *mode; pin[0] = pin[1] = pout[0] = pout[1] = perr[0] = perr[1] = INVALID_HANDLE_VALUE; // stderr to stdout wanted ? - int fusion = (strstr(cmd,"2>&1") !=NULL); + int fusion = (strstr(cmd,"2>&1") !=nullptr); // Create windows pipes if (!createPipe(pin) || !createPipe(pout) || (!fusion && !createPipe(perr) ) ) @@ -203,8 +170,8 @@ FILE * Fl_Process::popen(const char *cmd, const char *mode) { si.hStdOutput = pout[1]; si.hStdError = fusion ? pout[1] : perr [1]; - if ( CreateProcess(NULL, (LPTSTR) cmd,NULL,NULL,TRUE, - DETACHED_PROCESS,NULL,NULL, &si, &pi)) { + if ( CreateProcess(nullptr, (LPTSTR) cmd,nullptr,nullptr,TRUE, + DETACHED_PROCESS,nullptr,nullptr, &si, &pi)) { // don't need theses handles inherited by child process: clean_close(pin[0]); clean_close(pout[1]); clean_close(perr[1]); HANDLE & h = *mode == 'r' ? pout[0] : pin[1]; @@ -231,13 +198,13 @@ int Fl_Process::close() { clean_close(perr[0]); clean_close(pin[1]); clean_close(pout[0]); - _fpt = NULL; + _fpt = nullptr; return 0; } return -1; #else int ret = ::pclose(_fpt); - _fpt=NULL; + _fpt=nullptr; return ret; #endif } @@ -256,10 +223,10 @@ FILE *Fl_Process::desc() const { \param[out] line buffer to receive the line \param[in] s size of the provided buffer - \return NULL if an error occurred, otherwise a pointer to the string + \return nullptr if an error occurred, otherwise a pointer to the string */ char *Fl_Process::get_line(char * line, size_t s) const { - return _fpt ? fgets(line, (int)s, _fpt) : NULL; + return _fpt ? fgets(line, (int)s, _fpt) : nullptr; } // returns fileno(FILE*): @@ -279,7 +246,7 @@ int Fl_Process::get_fileno() const { bool Fl_Process::createPipe(HANDLE * h, BOOL bInheritHnd) { SECURITY_ATTRIBUTES sa; sa.nLength = sizeof(sa); - sa.lpSecurityDescriptor = NULL; + sa.lpSecurityDescriptor = nullptr; sa.bInheritHandle = bInheritHnd; return CreatePipe (&h[0],&h[1],&sa,0) ? true : false; } @@ -288,7 +255,7 @@ FILE *Fl_Process::freeHandles() { clean_close(pin[0]); clean_close(pin[1]); clean_close(pout[0]); clean_close(pout[1]); clean_close(perr[0]); clean_close(perr[1]); - return NULL; // convenient for error management + return nullptr; // convenient for error management } void Fl_Process::clean_close(HANDLE& h) { @@ -312,13 +279,13 @@ static bool prepare_shell_command(int flags) { return false; } if (flags & Fd_Shell_Command::SAVE_PROJECT) { - save_cb(0, 0); + Fluid.save_project_file(nullptr); } if (flags & Fd_Shell_Command::SAVE_SOURCECODE) { - write_code_files(true); + Fluid.write_code_files(true); } if (flags & Fd_Shell_Command::SAVE_STRINGS) { - write_strings_cb(0, 0); + Fluid.proj.write_strings(); } return true; } @@ -345,7 +312,7 @@ void shell_timer_cb(void*) { void shell_pipe_cb(FL_SOCKET, void*) { char line[1024]=""; // Line from command output... - if (s_proc.get_line(line, sizeof(line)) != NULL) { + if (s_proc.get_line(line, sizeof(line)) != nullptr) { // Add the line to the output list... shell_run_terminal->append(line); } else { @@ -373,29 +340,29 @@ static void expand_macro(std::string &cmd, const std::string ¯o, const std:: } static void expand_macros(std::string &cmd) { - expand_macro(cmd, "@BASENAME@", g_project.basename()); - expand_macro(cmd, "@PROJECTFILE_PATH@", g_project.projectfile_path()); - expand_macro(cmd, "@PROJECTFILE_NAME@", g_project.projectfile_name()); - expand_macro(cmd, "@CODEFILE_PATH@", g_project.codefile_path()); - expand_macro(cmd, "@CODEFILE_NAME@", g_project.codefile_name()); - expand_macro(cmd, "@HEADERFILE_PATH@", g_project.headerfile_path()); - expand_macro(cmd, "@HEADERFILE_NAME@", g_project.headerfile_name()); - expand_macro(cmd, "@TEXTFILE_PATH@", g_project.stringsfile_path()); - expand_macro(cmd, "@TEXTFILE_NAME@", g_project.stringsfile_name()); + expand_macro(cmd, "@BASENAME@", Fluid.proj.basename()); + expand_macro(cmd, "@PROJECTFILE_PATH@", Fluid.proj.projectfile_path()); + expand_macro(cmd, "@PROJECTFILE_NAME@", Fluid.proj.projectfile_name()); + expand_macro(cmd, "@CODEFILE_PATH@", Fluid.proj.codefile_path()); + expand_macro(cmd, "@CODEFILE_NAME@", Fluid.proj.codefile_name()); + expand_macro(cmd, "@HEADERFILE_PATH@", Fluid.proj.headerfile_path()); + expand_macro(cmd, "@HEADERFILE_NAME@", Fluid.proj.headerfile_name()); + expand_macro(cmd, "@TEXTFILE_PATH@", Fluid.proj.stringsfile_path()); + expand_macro(cmd, "@TEXTFILE_NAME@", Fluid.proj.stringsfile_name()); // TODO: implement finding the script `fltk-config` for all platforms // if (cmd.find("@FLTK_CONFIG@") != std::string::npos) { // find_fltk_config(); // expand_macro(cmd, "@FLTK_CONFIG@", fltk_config_cmd.c_str()); // } if (cmd.find("@TMPDIR@") != std::string::npos) - expand_macro(cmd, "@TMPDIR@", get_tmpdir()); + expand_macro(cmd, "@TMPDIR@", Fluid.get_tmpdir()); } /** Show the terminal window where it was last positioned. */ void show_terminal_window() { - Fl_Preferences pos(fluid_prefs, "shell_run_Window_pos"); + Fl_Preferences pos(Fluid.preferences, "shell_run_Window_pos"); int x, y, w, h; pos.get("x", x, -1); pos.get("y", y, 0); @@ -439,7 +406,7 @@ void run_shell_command(const std::string &cmd, int flags) { shell_run_terminal->printf("\033[0;32m%s\033[0m\n", expanded_cmd.c_str()); shell_run_window->label(expanded_cmd.c_str()); - if (s_proc.popen((char *)expanded_cmd.c_str()) == NULL) { + if (s_proc.popen((char *)expanded_cmd.c_str()) == nullptr) { shell_run_terminal->printf("\033[1;31mUnable to run shell command: %s\033[0m\n", strerror(errno)); shell_run_window->label("FLUID Shell"); @@ -459,10 +426,10 @@ void run_shell_command(const std::string &cmd, int flags) { */ Fd_Shell_Command::Fd_Shell_Command() : shortcut(0), - storage(FD_STORE_USER), + storage(fld::Tool_Store::USER), condition(0), flags(0), - shell_menu_item_(NULL) + shell_menu_item_(nullptr) { } @@ -480,7 +447,7 @@ Fd_Shell_Command::Fd_Shell_Command(const Fd_Shell_Command *rhs) condition_data(rhs->condition_data), command(rhs->command), flags(rhs->flags), - shell_menu_item_(NULL) + shell_menu_item_(nullptr) { } @@ -493,11 +460,11 @@ Fd_Shell_Command::Fd_Shell_Command(const std::string &in_name) : name(in_name), label(in_name), shortcut(0), - storage(FD_STORE_USER), + storage(fld::Tool_Store::USER), condition(Fd_Shell_Command::ALWAYS), command("echo \"Hello, FLUID!\""), flags(Fd_Shell_Command::SAVE_PROJECT|Fd_Shell_Command::SAVE_SOURCECODE), - shell_menu_item_(NULL) + shell_menu_item_(nullptr) { } @@ -516,7 +483,7 @@ Fd_Shell_Command::Fd_Shell_Command(const std::string &in_name) Fd_Shell_Command::Fd_Shell_Command(const std::string &in_name, const std::string &in_label, Fl_Shortcut in_shortcut, - Fd_Tool_Store in_storage, + fld::Tool_Store in_storage, int in_condition, const std::string &in_condition_data, const std::string &in_command, @@ -529,7 +496,7 @@ Fd_Shell_Command::Fd_Shell_Command(const std::string &in_name, condition_data(in_condition_data), command(in_command), flags(in_flags), - shell_menu_item_(NULL) + shell_menu_item_(nullptr) { } @@ -549,8 +516,8 @@ void Fd_Shell_Command::run() { */ void Fd_Shell_Command::update_shell_menu() { if (shell_menu_item_) { - const char *old_label = shell_menu_item_->label(); // can be NULL - const char *new_label = label.c_str(); // never NULL + const char *old_label = shell_menu_item_->label(); // can be nullptr + const char *new_label = label.c_str(); // never nullptr if (!old_label || (old_label && strcmp(old_label, new_label))) { if (old_label) ::free((void*)old_label); shell_menu_item_->label(fl_strdup(new_label)); @@ -597,33 +564,33 @@ bool Fd_Shell_Command::is_active() { void Fd_Shell_Command::read(Fl_Preferences &prefs) { int tmp; - preferences_get(prefs, "name", name, ""); - preferences_get(prefs, "label", label, ""); + prefs.get("name", name, ""); + prefs.get("label", label, ""); prefs.get("shortcut", tmp, 0); shortcut = (Fl_Shortcut)tmp; prefs.get("storage", tmp, -1); - if (tmp != -1) storage = (Fd_Tool_Store)tmp; + if (tmp != -1) storage = (fld::Tool_Store)tmp; prefs.get("condition", condition, ALWAYS); - preferences_get(prefs, "condition_data", condition_data, ""); - preferences_get(prefs, "command", command, ""); + prefs.get("condition_data", condition_data, ""); + prefs.get("command", command, ""); prefs.get("flags", flags, 0); } void Fd_Shell_Command::write(Fl_Preferences &prefs, bool save_location) { - preferences_set(prefs, "name", name); - preferences_set(prefs, "label", label); + prefs.set("name", name); + prefs.set("label", label); if (shortcut != 0) prefs.set("shortcut", (int)shortcut); if (save_location) prefs.set("storage", (int)storage); if (condition != ALWAYS) prefs.set("condition", condition); - if (!condition_data.empty()) preferences_set(prefs, "condition_data", condition_data); - if (!command.empty()) preferences_set(prefs, "command", command); + if (!condition_data.empty()) prefs.set("condition_data", condition_data); + if (!command.empty()) prefs.set("command", command); if (flags != 0) prefs.set("flags", flags); } void Fd_Shell_Command::read(class fld::io::Project_Reader *in) { const char *c = in->read_word(1); if (strcmp(c, "{")!=0) return; // expecting start of group - storage = FD_STORE_PROJECT; + storage = fld::Tool_Store::PROJECT; for (;;) { c = in->read_word(1); if (strcmp(c, "}")==0) break; // end of command list @@ -667,10 +634,6 @@ void Fd_Shell_Command::write(class fld::io::Project_Writer *out) { Manage a list of shell commands and their parameters. */ Fd_Shell_Command_List::Fd_Shell_Command_List() -: list(NULL), - list_size(0), - list_capacity(0), - shell_menu_(NULL) { } @@ -702,14 +665,14 @@ void Fd_Shell_Command_List::clear() { ::free(list); list_size = 0; list_capacity = 0; - list = 0; + list = nullptr; } } /** remove all shell commands of the given storage location from the list. */ -void Fd_Shell_Command_List::clear(Fd_Tool_Store storage) { +void Fd_Shell_Command_List::clear(fld::Tool_Store storage) { for (int i=list_size-1; i>=0; i--) { if (list[i]->storage == storage) { remove(i); @@ -720,22 +683,22 @@ void Fd_Shell_Command_List::clear(Fd_Tool_Store storage) { /** Read shell configuration from a preferences group. */ -void Fd_Shell_Command_List::read(Fl_Preferences &prefs, Fd_Tool_Store storage) { +void Fd_Shell_Command_List::read(Fl_Preferences &prefs, fld::Tool_Store storage) { // import the old shell commands from previous user settings - if (&fluid_prefs == &prefs) { + if (&Fluid.preferences == &prefs) { int version; prefs.get("shell_commands_version", version, 0); if (version == 0) { int save_fl, save_code, save_strings; Fd_Shell_Command *cmd = new Fd_Shell_Command(); - cmd->storage = FD_STORE_USER; + cmd->storage = fld::Tool_Store::USER; cmd->name = "Sample Shell Command"; cmd->label = "Sample Shell Command"; cmd->shortcut = FL_ALT+'g'; - preferences_get(fluid_prefs, "shell_command", cmd->command, "echo \"Sample Shell Command\""); - fluid_prefs.get("shell_savefl", save_fl, 1); - fluid_prefs.get("shell_writecode", save_code, 1); - fluid_prefs.get("shell_writemsgs", save_strings, 0); + Fluid.preferences.get("shell_command", cmd->command, "echo \"Sample Shell Command\""); + Fluid.preferences.get("shell_savefl", save_fl, 1); + Fluid.preferences.get("shell_writecode", save_code, 1); + Fluid.preferences.get("shell_writemsgs", save_strings, 0); if (save_fl) cmd->flags |= Fd_Shell_Command::SAVE_PROJECT; if (save_code) cmd->flags |= Fd_Shell_Command::SAVE_SOURCECODE; if (save_strings) cmd->flags |= Fd_Shell_Command::SAVE_STRINGS; @@ -749,7 +712,7 @@ void Fd_Shell_Command_List::read(Fl_Preferences &prefs, Fd_Tool_Store storage) { for (int i=0; istorage = FD_STORE_USER; + cmd->storage = fld::Tool_Store::USER; cmd->read(cmd_prefs); add(cmd); } @@ -758,12 +721,12 @@ void Fd_Shell_Command_List::read(Fl_Preferences &prefs, Fd_Tool_Store storage) { /** Write shell configuration to a preferences group. */ -void Fd_Shell_Command_List::write(Fl_Preferences &prefs, Fd_Tool_Store storage) { +void Fd_Shell_Command_List::write(Fl_Preferences &prefs, fld::Tool_Store storage) { Fl_Preferences shell_commands(prefs, "shell_commands"); shell_commands.delete_all_groups(); int index = 0; for (int i=0; istorage == FD_STORE_USER) { + if (list[i]->storage == fld::Tool_Store::USER) { Fl_Preferences cmd(shell_commands, Fl_Preferences::Name(index++)); list[i]->write(cmd); } @@ -776,7 +739,7 @@ void Fd_Shell_Command_List::write(Fl_Preferences &prefs, Fd_Tool_Store storage) void Fd_Shell_Command_List::read(fld::io::Project_Reader *in) { const char *c = in->read_word(1); if (strcmp(c, "{")!=0) return; // expecting start of group - clear(FD_STORE_PROJECT); + clear(fld::Tool_Store::PROJECT); for (;;) { c = in->read_word(1); if (strcmp(c, "}")==0) break; // end of command list @@ -796,13 +759,13 @@ void Fd_Shell_Command_List::read(fld::io::Project_Reader *in) { void Fd_Shell_Command_List::write(fld::io::Project_Writer *out) { int n_in_project_file = 0; for (int i=0; istorage == FD_STORE_PROJECT) + if (list[i]->storage == fld::Tool_Store::PROJECT) n_in_project_file++; } if (n_in_project_file > 0) { out->write_string("\nshell_commands {"); for (int i=0; istorage == FD_STORE_PROJECT) + if (list[i]->storage == fld::Tool_Store::PROJECT) list[i]->write(out); } out->write_string("\n}"); @@ -872,9 +835,9 @@ void menu_shell_customize_cb(Fl_Widget*, void*) { Rebuild the entire shell submenu from scratch and replace the old menu. */ void Fd_Shell_Command_List::rebuild_shell_menu() { - static Fl_Menu_Item *shell_submenu = NULL; + static Fl_Menu_Item *shell_submenu = nullptr; if (!shell_submenu) - shell_submenu = (Fl_Menu_Item*)main_menubar->find_item(menu_marker); + shell_submenu = (Fl_Menu_Item*)Fluid.main_menubar->find_item(menu_marker); int i, j, num_active_items = 0; // count the active commands @@ -926,7 +889,7 @@ void Fd_Shell_Command_List::update_settings_dialog() { */ Fl_Menu_Item Fd_Shell_Command_List::default_menu[] = { { "Customize...", FL_ALT+'x', menu_shell_customize_cb }, - { NULL } + { nullptr } }; /** @@ -939,7 +902,7 @@ void Fd_Shell_Command_List::menu_marker(Fl_Widget*, void*) { /** Export all selected shell commands to an external file. - Verify that g_shell_config and w_settings_shell_list are not NULL. Open a + Verify that g_shell_config and w_settings_shell_list are not nullptr. Open a file chooser and export all items that are selected in w_settings_shell_list into an external file. */ @@ -951,11 +914,11 @@ void Fd_Shell_Command_List::export_selected() { dialog.title("Export selected shell commands:"); dialog.type(Fl_Native_File_Chooser::BROWSE_SAVE_FILE); dialog.filter("FLUID Files\t*.flcmd\n"); - dialog.directory(g_project.projectfile_path().c_str()); - dialog.preset_file((g_project.basename() + ".flcmd").c_str()); + dialog.directory(Fluid.proj.projectfile_path().c_str()); + dialog.preset_file((Fluid.proj.basename() + ".flcmd").c_str()); if (dialog.show() != 0) return; - Fl_Preferences file(dialog.filename(), "flcmd.fluid.fltk.org", NULL, (Fl_Preferences::Root)(Fl_Preferences::C_LOCALE|Fl_Preferences::CLEAR)); + Fl_Preferences file(dialog.filename(), "flcmd.fluid.fltk.org", nullptr, (Fl_Preferences::Root)(Fl_Preferences::C_LOCALE|Fl_Preferences::CLEAR)); Fl_Preferences shell_commands(file, "shell_commands"); int i, index = 0, n = w_settings_shell_list->size(); for (i = 0; i < n; i++) { @@ -969,7 +932,7 @@ void Fd_Shell_Command_List::export_selected() { /** Import shell commands from an external file and add them to the list. - Verify that g_shell_config and w_settings_shell_list are not NULL. Open a + Verify that g_shell_config and w_settings_shell_list are not nullptr. Open a file chooser and import all items. */ void Fd_Shell_Command_List::import_from_file() { @@ -980,17 +943,17 @@ void Fd_Shell_Command_List::import_from_file() { dialog.title("Import shell commands:"); dialog.type(Fl_Native_File_Chooser::BROWSE_FILE); dialog.filter("FLUID Files\t*.flcmd\n"); - dialog.directory(g_project.projectfile_path().c_str()); - dialog.preset_file((g_project.basename() + ".flcmd").c_str()); + dialog.directory(Fluid.proj.projectfile_path().c_str()); + dialog.preset_file((Fluid.proj.basename() + ".flcmd").c_str()); if (dialog.show() != 0) return; - Fl_Preferences file(dialog.filename(), "flcmd.fluid.fltk.org", NULL, Fl_Preferences::C_LOCALE); + Fl_Preferences file(dialog.filename(), "flcmd.fluid.fltk.org", nullptr, Fl_Preferences::C_LOCALE); Fl_Preferences shell_commands(file, "shell_commands"); int i, n = shell_commands.groups(); for (i = 0; i < n; i++) { Fl_Preferences cmd_prefs(shell_commands, Fl_Preferences::Name(i)); Fd_Shell_Command *cmd = new Fd_Shell_Command(); - cmd->storage = FD_STORE_USER; + cmd->storage = fld::Tool_Store::USER; cmd->read(cmd_prefs); g_shell_config->add(cmd); } @@ -1003,5 +966,5 @@ void Fd_Shell_Command_List::import_from_file() { /** A pointer to the list of shell commands if we are not in batch mode. */ -Fd_Shell_Command_List *g_shell_config = NULL; +Fd_Shell_Command_List *g_shell_config = nullptr; diff --git a/fluid/app/shell_command.h b/fluid/app/shell_command.h index 3f5508493..bc7a18c3e 100644 --- a/fluid/app/shell_command.h +++ b/fluid/app/shell_command.h @@ -1,7 +1,7 @@ // -// FLUID main entry for the Fast Light Tool Kit (FLTK). +// Shell Command database header for the Fast Light Tool Kit (FLTK). // -// Copyright 1998-2023 by Bill Spitzak and others. +// 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 @@ -17,7 +17,7 @@ #ifndef _FLUID_SHELL_COMMAND_H #define _FLUID_SHELL_COMMAND_H -#include "app/fluid.h" +#include "Fluid.h" #include @@ -48,13 +48,11 @@ struct Fl_Menu_Item; class Fl_Widget; class Fl_Preferences; -char preferences_get(Fl_Preferences &prefs, const char *key, std::string &value, const std::string &defaultValue); -char preferences_set(Fl_Preferences &prefs, const char *key, const std::string &value); - void show_terminal_window(); void run_shell_command(const std::string &cmd, int flags); bool shell_command_running(void); + class Fl_Process { public: Fl_Process(); @@ -83,9 +81,10 @@ private: #endif protected: - FILE * _fpt; + FILE * _fpt = nullptr; }; + class Fd_Shell_Command { public: enum { ALWAYS, NEVER, MAC_ONLY, UX_ONLY, WIN_ONLY, MAC_AND_UX_ONLY, USER_ONLY, HOST_ONLY, ENV_ONLY }; // conditions @@ -97,19 +96,19 @@ public: Fd_Shell_Command(const std::string &in_name, const std::string &in_label, Fl_Shortcut in_shortcut, - Fd_Tool_Store in_storage, + fld::Tool_Store in_storage, int in_condition, const std::string &in_condition_data, const std::string &in_command, int in_flags); - std::string name; - std::string label; - Fl_Shortcut shortcut; - Fd_Tool_Store storage; - int condition; // always, hide, windows only, linux only, mac only, user, machine - std::string condition_data; // user name, machine name - std::string command; - int flags; // save_project, save_code, save_string, ... + std::string name { }; + std::string label { }; + Fl_Shortcut shortcut = 0; + fld::Tool_Store storage = fld::Tool_Store::USER; + int condition = ALWAYS; // always, hide, windows only, linux only, mac only, user, machine + std::string condition_data { }; // user name, machine name + std::string command { }; + int flags = 0; // save_project, save_code, save_string, ... Fl_Menu_Item *shell_menu_item_; void run(); void read(Fl_Preferences &prefs); @@ -120,12 +119,13 @@ public: bool is_active(); }; + class Fd_Shell_Command_List { public: - Fd_Shell_Command **list; - int list_size; - int list_capacity; - Fl_Menu_Item *shell_menu_; + Fd_Shell_Command **list = nullptr; + int list_size = 0; + int list_capacity = 0; + Fl_Menu_Item *shell_menu_ = nullptr; public: Fd_Shell_Command_List(); ~Fd_Shell_Command_List(); @@ -134,13 +134,13 @@ public: void insert(int index, Fd_Shell_Command *cmd); void remove(int index); void clear(); - void clear(Fd_Tool_Store store); + void clear(fld::Tool_Store store); // void move_up(); // void move_down(); // int load(const std::string &filename); // int save(const std::string &filename); - void read(Fl_Preferences &prefs, Fd_Tool_Store storage); - void write(Fl_Preferences &prefs, Fd_Tool_Store storage); + void read(Fl_Preferences &prefs, fld::Tool_Store storage); + void write(Fl_Preferences &prefs, fld::Tool_Store storage); void read(class fld::io::Project_Reader*); void write(class fld::io::Project_Writer*); void rebuild_shell_menu(); diff --git a/fluid/app/templates.cxx b/fluid/app/templates.cxx new file mode 100644 index 000000000..802357ed2 --- /dev/null +++ b/fluid/app/templates.cxx @@ -0,0 +1,143 @@ +// +// Fluid Project Templates code for the Fast Light Tool Kit (FLTK). +// +// Copyright 1998-2025 by Bill Spitzak and others. +// +// This library is free software. Distribution and use rights are outlined in +// the file "COPYING" which should have been included with this file. If this +// file is missing or damaged, see the license at: +// +// https://www.fltk.org/COPYING.php +// +// Please see the following page on how to report bugs and issues: +// +// https://www.fltk.org/bugs.php +// + +#include "app/templates.h" + +#include "Fluid.h" +#include "io/Project_Writer.h" +#include "nodes/factory.h" +#include "nodes/Tree.h" +#include "nodes/Window_Node.h" +#include "panels/template_panel.h" + +#include +#include +#include +#include "../src/flstring.h" + +using namespace fld; +using namespace fld::app; + +/** + Save a design template. + \todo We should document the concept of templates. + */ +void fld::app::save_template() { + // Setup the template panel... + if (!template_panel) make_template_panel(); + + template_clear(); + template_browser->add("New Template"); + template_load(); + + template_name->show(); + template_name->value(""); + + template_instance->hide(); + + template_delete->show(); + template_delete->deactivate(); + + template_submit->label("Save"); + template_submit->deactivate(); + + template_panel->label("Save Template"); + + // Show the panel and wait for the user to do something... + template_panel->show(); + while (template_panel->shown()) Fl::wait(); + + // Get the template name, return if it is empty... + const char *c = template_name->value(); + if (!c || !*c) return; + + // Convert template name to filename_with_underscores + char savename[FL_PATH_MAX], *saveptr; + strlcpy(savename, c, sizeof(savename)); + for (saveptr = savename; *saveptr; saveptr ++) { + if (isspace(*saveptr)) *saveptr = '_'; + } + + // Find the templates directory... + char filename[FL_PATH_MAX]; + Fluid.preferences.getUserdataPath(filename, sizeof(filename)); + + strlcat(filename, "templates", sizeof(filename)); + if (fl_access(filename, 0)) fl_make_path(filename); + + strlcat(filename, "/", sizeof(filename)); + strlcat(filename, savename, sizeof(filename)); + + char *ext = filename + strlen(filename); + if (ext >= (filename + sizeof(filename) - 5)) { + fl_alert("The template name \"%s\" is too long!", c); + return; + } + + // Save the .fl file... + strcpy(ext, ".fl"); + + if (!fl_access(filename, 0)) { + if (fl_choice("The template \"%s\" already exists.\n" + "Do you want to replace it?", "Cancel", + "Replace", nullptr, c) == 0) return; + } + + if (!fld::io::write_file(Fluid.proj, filename)) { + fl_alert("Error writing %s: %s", filename, strerror(errno)); + return; + } + +#if defined(HAVE_LIBPNG) && defined(HAVE_LIBZ) + // Get the screenshot, if any... + Node *t; + + for (t = Fluid.proj.tree.first; t; t = t->next) { + // Find the first window... + if (t->is_a(Type::Window)) break; + } + + if (!t) return; + + // Grab a screenshot... + Window_Node *wt = (Window_Node *)t; + uchar *pixels; + int w, h; + + if ((pixels = wt->read_image(w, h)) == nullptr) return; + + // Save to a PNG file... + strcpy(ext, ".png"); + + errno = 0; + if (fl_write_png(filename, pixels, w, h, 3) != 0) { + delete[] pixels; + fl_alert("Error writing %s: %s", filename, strerror(errno)); + return; + } + +# if 0 // The original PPM output code... + strcpy(ext, ".ppm"); + fp = fl_fopen(filename, "wb"); + fprintf(fp, "P6\n%d %d 255\n", w, h); + fwrite(pixels, w * h, 3, fp); + fclose(fp); +# endif // 0 + + delete[] pixels; +#endif // HAVE_LIBPNG && HAVE_LIBZ +} + diff --git a/fluid/app/templates.h b/fluid/app/templates.h new file mode 100644 index 000000000..cedec9f76 --- /dev/null +++ b/fluid/app/templates.h @@ -0,0 +1,29 @@ +// +// Fluid Project Templates header for the Fast Light Tool Kit (FLTK). +// +// Copyright 1998-2025 by Bill Spitzak and others. +// +// This library is free software. Distribution and use rights are outlined in +// the file "COPYING" which should have been included with this file. If this +// file is missing or damaged, see the license at: +// +// https://www.fltk.org/COPYING.php +// +// Please see the following page on how to report bugs and issues: +// +// https://www.fltk.org/bugs.php +// + +#ifndef FLUID_APP_TEMPLATES_H +#define FLUID_APP_TEMPLATES_H + +namespace fld { +namespace app { + +void save_template(); + +} // namespace app +} // namespace fld + +#endif // FLUID_APP_TEMPLATES_H + diff --git a/fluid/app/undo.cxx b/fluid/app/undo.cxx deleted file mode 100644 index baef9e717..000000000 --- a/fluid/app/undo.cxx +++ /dev/null @@ -1,265 +0,0 @@ -// -// FLUID undo support for the Fast Light Tool Kit (FLTK). -// -// Copyright 1998-2025 by Bill Spitzak and others. -// -// This library is free software. Distribution and use rights are outlined in -// the file "COPYING" which should have been included with this file. If this -// file is missing or damaged, see the license at: -// -// https://www.fltk.org/COPYING.php -// -// Please see the following page on how to report bugs and issues: -// -// https://www.fltk.org/bugs.php -// - -#include "app/undo.h" - -#include "app/fluid.h" -#include "app/project.h" -#include "io/Project_Reader.h" -#include "io/Project_Writer.h" -#include "nodes/Fl_Type.h" -#include "nodes/Fl_Widget_Type.h" -#include "widgets/Node_Browser.h" - -#include -#include -#include -#include -#include -#include "tools/filename.h" -#include "../src/flstring.h" - -#if defined(_WIN32) && !defined(__CYGWIN__) -# include -# include -# define getpid (int)GetCurrentProcessId -#else -# include -#endif // _WIN32 && !__CYGWIN__ - - -// -// This file implements an undo system using temporary files; ideally -// we'd like to do this in memory, however the current data structures -// and design aren't well-suited... Instead, we save and restore -// checkpoint files. -// - -extern Fl_Window* the_panel; - -int undo_current = 0; // Current undo level in buffer -int undo_last = 0; // Last undo level in buffer -int undo_max = 0; // Maximum undo level used -int undo_save = -1; // Last undo level that was saved -static int undo_paused = 0; // Undo checkpointing paused? -int undo_once_type = 0; // Suspend further undos of the same type - - -// Return the undo filename. -// The filename is constructed in a static internal buffer and -// this buffer is overwritten by every call of this function. -// The return value is a pointer to this internal string. -static char *undo_filename(int level) { - static char undo_path[FL_PATH_MAX] = ""; // Undo path - static unsigned int undo_path_len = 0; // length w/o filename - - if (!undo_path_len) { - fluid_prefs.getUserdataPath(undo_path, sizeof(undo_path)); - undo_path_len = (unsigned int)strlen(undo_path); - } - - // append filename: "undo_PID_LEVEL.fl" - snprintf(undo_path + undo_path_len, - sizeof(undo_path) - undo_path_len - 1, - "undo_%d_%d.fl", getpid(), level); - return undo_path; -} - - -// Redo menu callback -void redo_cb(Fl_Widget *, void *) { - // int undo_item = main_menubar->find_index(undo_cb); - // int redo_item = main_menubar->find_index(redo_cb); - undo_once_type = 0; - - if (undo_current >= undo_last) { - fl_beep(); - return; - } - - undo_suspend(); - if (widget_browser) { - widget_browser->save_scroll_position(); - widget_browser->new_list(); - } - int reload_panel = (the_panel && the_panel->visible()); - if (!fld::io::read_file(undo_filename(undo_current + 1), 0)) { - // Unable to read checkpoint file, don't redo... - widget_browser->rebuild(); - g_project.update_settings_dialog(); - undo_resume(); - return; - } - if (reload_panel) { - for (Fl_Type *t = Fl_Type::first; t; t=t->next) { - if (t->is_widget() && t->selected) - t->open(); - } - } - if (widget_browser) widget_browser->restore_scroll_position(); - - undo_current ++; - - // Update modified flag... - set_modflag(undo_current != undo_save); - widget_browser->rebuild(); - g_project.update_settings_dialog(); - - // Update undo/redo menu items... - // if (undo_current >= undo_last) Main_Menu[redo_item].deactivate(); - // Main_Menu[undo_item].activate(); - undo_resume(); -} - -// Undo menu callback -void undo_cb(Fl_Widget *, void *) { - // int undo_item = main_menubar->find_index(undo_cb); - // int redo_item = main_menubar->find_index(redo_cb); - undo_once_type = 0; - - if (undo_current <= 0) { - fl_beep(); - return; - } - - if (undo_current == undo_last) { - fld::io::write_file(undo_filename(undo_current)); - } - - undo_suspend(); - // Undo first deletes all widgets which resets the widget_tree browser. - // Save the current scroll position, so we don't scroll back to 0 at undo. - // TODO: make the scroll position part of the .fl project file - if (widget_browser) { - widget_browser->save_scroll_position(); - widget_browser->new_list(); - } - int reload_panel = (the_panel && the_panel->visible()); - if (!fld::io::read_file(undo_filename(undo_current - 1), 0)) { - // Unable to read checkpoint file, don't undo... - widget_browser->rebuild(); - g_project.update_settings_dialog(); - set_modflag(0, 0); - undo_resume(); - return; - } - if (reload_panel) { - for (Fl_Type *t = Fl_Type::first; t; t=t->next) { - if (t->is_widget() && t->selected) { - t->open(); - break; - } - } - } - // Restore old browser position. - // Ideally, we would save the browser position inside the undo file. - if (widget_browser) widget_browser->restore_scroll_position(); - - undo_current --; - - // Update modified flag... - set_modflag(undo_current != undo_save); - - // Update undo/redo menu items... - // if (undo_current <= 0) Main_Menu[undo_item].deactivate(); - // Main_Menu[redo_item].activate(); - widget_browser->rebuild(); - g_project.update_settings_dialog(); - undo_resume(); -} - -/** - \param[in] type set a new type, or set to 0 to clear the once_type without setting a checkpoint - \return 1 if the checkpoint was set, 0 if this is a repeating event - */ -int undo_checkpoint_once(int type) { - if (type == 0) { - undo_once_type = 0; - return 0; - } - if (undo_paused) return 0; - if (undo_once_type != type) { - undo_checkpoint(); - undo_once_type = type; - return 1; - } else { - // do not add more checkpoints for the same undo type - return 0; - } -} - -// Save current file to undo buffer -void undo_checkpoint() { - // printf("undo_checkpoint(): undo_current=%d, undo_paused=%d, modflag=%d\n", - // undo_current, undo_paused, modflag); - - // Don't checkpoint if undo_suspend() has been called... - if (undo_paused) return; - - // int undo_item = main_menubar->find_index(undo_cb); - // int redo_item = main_menubar->find_index(redo_cb); - undo_once_type = 0; - - // Save the current UI to a checkpoint file... - const char *filename = undo_filename(undo_current); - if (!fld::io::write_file(filename)) { - // Don't attempt to do undo stuff if we can't write a checkpoint file... - perror(filename); - return; - } - - // Update the saved level... - if (modflag && undo_current <= undo_save) undo_save = -1; - else if (!modflag) undo_save = undo_current; - - // Update the current undo level... - undo_current ++; - undo_last = undo_current; - if (undo_current > undo_max) undo_max = undo_current; - - // Enable the Undo and disable the Redo menu items... - // Main_Menu[undo_item].activate(); - // Main_Menu[redo_item].deactivate(); -} - -// Clear undo buffer -void undo_clear() { - // int undo_item = main_menubar->find_index(undo_cb); - // int redo_item = main_menubar->find_index(redo_cb); - // Remove old checkpoint files... - for (int i = 0; i <= undo_max; i ++) { - fl_unlink(undo_filename(i)); - } - - // Reset current, last, and save indices... - undo_current = undo_last = undo_max = 0; - if (modflag) undo_save = -1; - else undo_save = 0; - - // Disable the Undo and Redo menu items... - // Main_Menu[undo_item].deactivate(); - // Main_Menu[redo_item].deactivate(); -} - -// Resume undo checkpoints -void undo_resume() { - undo_paused--; -} - -// Suspend undo checkpoints -void undo_suspend() { - undo_paused++; -} diff --git a/fluid/app/undo.h b/fluid/app/undo.h deleted file mode 100644 index e8ffab891..000000000 --- a/fluid/app/undo.h +++ /dev/null @@ -1,37 +0,0 @@ -// -// FLUID undo definitions for the Fast Light Tool Kit (FLTK). -// -// Copyright 1998-2025 by Bill Spitzak and others. -// -// This library is free software. Distribution and use rights are outlined in -// the file "COPYING" which should have been included with this file. If this -// file is missing or damaged, see the license at: -// -// https://www.fltk.org/COPYING.php -// -// Please see the following page on how to report bugs and issues: -// -// https://www.fltk.org/bugs.php -// - -#ifndef undo_h -#define undo_h - -class Fl_Widget; - -#define kUndoWindowResize 1 - -extern int undo_current; // Current undo level in buffer -extern int undo_last; // Last undo level in buffer -extern int undo_save; // Last undo level that was saved -extern int undo_once_type; // Suspend further undos of the same type - -void redo_cb(Fl_Widget *, void *); // Redo menu callback -void undo_cb(Fl_Widget *, void *); // Undo menu callback -void undo_checkpoint(); // Save current file to undo buffer -int undo_checkpoint_once(int type); // Save undo buffer once until a different checkpoint type is called -void undo_clear(); // Clear undo buffer -void undo_resume(); // Resume undo checkpoints -void undo_suspend(); // Suspend undo checkpoints - -#endif // !undo_h -- cgit v1.2.3