diff --git a/fluid/app/project.cxx b/fluid/app/project.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..2ce91adc44e510ca7e840615eac2bdfb2376151f
--- /dev/null
+++ b/fluid/app/project.cxx
@@ -0,0 +1,2340 @@
+//
+// 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/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 <FL/Fl.H>
+#ifdef __APPLE__
+#include <FL/platform.H> // for fl_open_callback
+#endif
+#include <FL/Fl_Help_Dialog.H>
+#include <FL/Fl_Menu_Bar.H>
+#include <FL/Fl_PNG_Image.H>
+#include <FL/Fl_Native_File_Chooser.H>
+#include <FL/Fl_Printer.H>
+#include <FL/fl_string_functions.h>
+#include <locale.h>     // setlocale()..
+#include "../src/flstring.h"
+
+extern "C"
+{
+#if defined(HAVE_LIBPNG) && defined(HAVE_LIBZ)
+#  include <zlib.h>
+#  ifdef HAVE_PNG_H
+#    include <png.h>
+#  else
+#    include <libpng/png.h>
+#  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;
+
+////////////////////////////////////////////////////////////////
+
+/// Filename of the current .fl project file
+static const char *filename = 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;
+
+// ---- 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 = "<libintl.h>";
+  i18n_gnu_conditional = "";
+  i18n_gnu_function = "gettext";
+  i18n_gnu_static_function = "gettext_noop";
+
+  i18n_pos_include = "<nl_types.h>";
+  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);
+  }
+}
+
+/**
+ 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 '/'
+ */
+static 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\<username>\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;
+  std::string path = fl_getenv("TMPDIR");
+  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; i<n_de; i++) {
+      std::string path = tmpdir_path + de[i]->d_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 (!filename || !*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(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 = 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(filename, 0)) {
+    undo_resume();
+    widget_browser->rebuild();
+    g_project.update_settings_dialog();
+    fl_message("Can't read %s: %s", 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 (filename) {
+    std::string current_project_file = 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 = filename;
+  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 *)filename);
+    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__
+
+/**
+ 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(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(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(filename), ".cxx");
+  } else if (name[0] == '.') {
+    return fl_filename_setext_str(fl_filename_name(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(filename), ".h");
+  } else if (name[0] == '.') {
+    return fl_filename_setext_str(fl_filename_name_str(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(filename), ".txt");
+    case FD_I18N_GNU: return fl_filename_setext_str(fl_filename_name(filename), ".po");
+    case FD_I18N_POSIX: return fl_filename_setext_str(fl_filename_name(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(filename), "");
+}
+
+/**
+ 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 (!filename) {
+    save_cb(0,0);
+    if (!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; i<n; i++) if (proj_filename[i]=='\\') proj_filename[i] = '/';
+    preferences_get(path, "code", code_filename, "");
+  }
+#endif
+  if (code_filename.empty())
+    code_filename = g_project.codefile_path() + g_project.codefile_name();
+  if (!batch_mode) enter_project_dir();
+  int c = merge_back(code_filename, proj_filename, FD_MERGEBACK_INTERACTIVE);
+  if (!batch_mode) leave_project_dir();
+
+  if (c==0) fl_message("Comparing\n  \"%s\"\nto\n  \"%s\"\n\n"
+                       "MergeBack found no external modifications\n"
+                       "in the source code.",
+                       code_filename.c_str(), proj_filename.c_str());
+  if (c==-2) fl_message("No corresponding source code file found.");
+  return c;
+}
+
+void mergeback_cb(Fl_Widget *, void *) {
+  mergeback_code_files();
+}
+#endif
+
+/**
+ Write the strings that are used in i18n.
+ */
+void write_strings_cb(Fl_Widget *, void *) {
+  flush_text_widgets();
+  if (!filename) {
+    save_cb(0,0);
+    if (!filename) return;
+  }
+  std::string filename = g_project.stringsfile_path() + g_project.stringsfile_name();
+  int x = write_strings(filename);
+  if (batch_mode) {
+    if (x) {
+      fprintf(stderr, "%s : %s\n", filename.c_str(), strerror(errno));
+      exit(1);
+    }
+  } else {
+    if (x) {
+      fl_message("Can't write %s: %s", filename.c_str(), strerror(errno));
+    } else if (completion_button->value()) {
+      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
+      (
+       "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">\n"
+       "<html><head><title>FLTK: Programming with FLUID</title></head><body>\n"
+       "<h2>What is FLUID?</h2>\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 <code>.fl</code> files. These files are text, and you can (with care) "
+       "edit them in a text editor, perhaps to get some special effects.<p>\n"
+       "FLUID can \"compile\" the <code>.fl</code> file into a <code>.cxx</code> "
+       "and a <code>.h</code> file. The <code>.cxx</code> file defines all the "
+       "objects from the <code>.fl</code> file and the <code>.h</code> 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.<p>\n"
+       "A simple program can be made by putting all your code (including a <code>"
+       "main()</code> function) into the <code>.fl</code> file and thus making the "
+       "<code>.cxx</code> file a single source file to compile. Most programs are "
+       "more complex than this, so you write other <code>.cxx</code> files that "
+       "call the FLUID functions. These <code>.cxx</code> files must <code>"
+       "#include</code> the <code>.h</code> file or they can <code>#include</code> "
+       "the <code>.cxx</code> file so it still appears to be a single source file.<p>"
+       "<img src=\"embedded:/fluid_flow_chart_800.png\"></p>"
+       "<p>More information is available online at <a href="
+       "\"https://www.fltk.org/doc-1.4/fluid.html\">https://www.fltk.org/</a>"
+       "</body></html>"
+       );
+    } else if (strcmp(name, "license.html")==0) {
+      fl_open_uri("https://www.fltk.org/doc-1.4/license.html");
+      return;
+    } else if (strcmp(name, "index.html")==0) {
+      fl_open_uri("https://www.fltk.org/doc-1.4/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(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<char *>(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 (filename) free((void *)filename);
+  filename = c ? fl_strdup(c) : NULL;
+
+  if (filename && !batch_mode)
+    update_history(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 (!filename) basename = "Untitled.fl";
+    else basename = fl_filename_name_str(std::string(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 <signal.h>
+#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 <switches> 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 <name> : .cxx output filename, or extension if <name> starts with '.'\n"
+      " -h <name> : .h output filename, or extension if <name> 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/project.h b/fluid/app/project.h
new file mode 100644
index 0000000000000000000000000000000000000000..3d5720330b419454f29c2f2cf1e37351ff7f7878
--- /dev/null
+++ b/fluid/app/project.h
@@ -0,0 +1,209 @@
+//
+// 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_FLUID_H
+#define _FLUID_FLUID_H
+
+#include "tools/filename.h"
+
+#include <FL/Fl_Preferences.H>
+#include <FL/Fl_Menu_Item.H>
+#include <FL/filename.H>
+
+#include <string>
+
+#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;
+
+// ---- 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 `<libintl.h>` 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 `<nl_types.h>` 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, <FL/Fl.H> 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;
+  /// 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;
+
+// ---- 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();
+
+// ---- 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_FLUID_H
+
diff --git a/fluid/io/code.cxx b/fluid/io/Code_Writer.cxx
similarity index 100%
rename from fluid/io/code.cxx
rename to fluid/io/Code_Writer.cxx
diff --git a/fluid/io/code.h b/fluid/io/Code_Writer.h
similarity index 100%
rename from fluid/io/code.h
rename to fluid/io/Code_Writer.h
diff --git a/fluid/io/file.cxx b/fluid/io/Project_Reader.cxx
similarity index 100%
rename from fluid/io/file.cxx
rename to fluid/io/Project_Reader.cxx
diff --git a/fluid/io/file.h b/fluid/io/Project_Reader.h
similarity index 100%
rename from fluid/io/file.h
rename to fluid/io/Project_Reader.h
diff --git a/fluid/io/Project_Writer.cxx b/fluid/io/Project_Writer.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..ba1afeb1b970ec3cb087e8f9f4659c00152afcc8
--- /dev/null
+++ b/fluid/io/Project_Writer.cxx
@@ -0,0 +1,983 @@
+//
+// Fluid file routines for the Fast Light Tool Kit (FLTK).
+//
+// You may find the basic read_* and write_* routines to
+// be useful for other programs.  I have used them many times.
+// They are somewhat similar to tcl, using matching { and }
+// to quote strings.
+//
+// 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 "io/file.h"
+
+#include "app/fluid.h"
+#include "app/shell_command.h"
+#include "app/undo.h"
+#include "io/code.h"
+#include "nodes/factory.h"
+#include "nodes/Fl_Function_Type.h"
+#include "nodes/Fl_Widget_Type.h"
+#include "nodes/Fl_Grid_Type.h"
+#include "nodes/Fl_Window_Type.h"
+#include "panels/settings_panel.h"
+#include "widgets/Node_Browser.h"
+
+#include <FL/Fl.H>
+#include <FL/Fl_Group.H>
+#include <FL/fl_string_functions.h>
+#include <FL/fl_message.H>
+#include "../src/flstring.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+
+/// \defgroup flfile .fl Project File Operations
+/// \{
+
+// This file contains code to read and write .fl files.
+
+/// If set, we read an old fdesign file and widget y coordinates need to be flipped.
+int fdesign_flip = 0;
+
+/** \brief Read a .fl project file.
+
+ The .fl file format is documented in `fluid/README_fl.txt`.
+
+ \param[in] filename read this file
+ \param[in] merge if this is set, merge the file into an existing project
+    at Fl_Type::current
+ \param[in] strategy add new nodes after current or as last child
+ \return 0 if the operation failed, 1 if it succeeded
+ */
+int read_file(const char *filename, int merge, Strategy strategy) {
+  Fd_Project_Reader f;
+  strategy.source(Strategy::FROM_FILE);
+  return f.read_project(filename, merge, strategy);
+}
+
+/** \brief Write an .fl design description file.
+
+ The .fl file format is documented in `fluid/README_fl.txt`.
+
+ \param[in] filename create this file, and if it exists, overwrite it
+ \param[in] selected_only write only the selected nodes in the widget_tree. This
+    is used to implement copy and paste.
+ \return 0 if the operation failed, 1 if it succeeded
+ */
+int write_file(const char *filename, int selected_only, bool to_codeview) {
+  Fd_Project_Writer out;
+  return out.write_project(filename, selected_only, to_codeview);
+}
+
+/**
+ Convert a single ASCII char, assumed to be a hex digit, into its decimal value.
+ \param[in] x ASCII character
+ \return decimal value or 20 if character is not a valid hex digit (0..9,a..f,A..F)
+ */
+static int hexdigit(int x) {
+  if ((x < 0) || (x > 127)) return 20;
+  if (isdigit(x)) return x-'0';
+  if (isupper(x)) return x-'A'+10;
+  if (islower(x)) return x-'a'+10;
+  return 20;
+}
+
+// ---- Fd_Project_Reader ---------------------------------------------- MARK: -
+
+/**
+ A simple growing buffer.
+ Oh how I wish sometimes we would upgrade to modern C++.
+ \param[in] length minimum length in bytes
+ */
+void Fd_Project_Reader::expand_buffer(int length) {
+  if (length >= buflen) {
+    if (!buflen) {
+      buflen = length+1;
+      buffer = (char*)malloc(buflen);
+    } else {
+      buflen = 2*buflen;
+      if (length >= buflen) buflen = length+1;
+      buffer = (char *)realloc((void *)buffer,buflen);
+    }
+  }
+}
+
+/** \brief Construct local project reader. */
+Fd_Project_Reader::Fd_Project_Reader()
+: fin(NULL),
+  lineno(0),
+  fname(NULL),
+  buffer(NULL),
+  buflen(0),
+  read_version(0.0)
+{
+}
+
+/** \brief Release project reader resources. */
+Fd_Project_Reader::~Fd_Project_Reader()
+{
+  // fname is not copied, so do not free it
+  if (buffer)
+    ::free(buffer);
+}
+
+/**
+ Open an .fl file for reading.
+ \param[in] s filename, if NULL, read from stdin instead
+ \return 0 if the operation failed, 1 if it succeeded
+ */
+int Fd_Project_Reader::open_read(const char *s) {
+  lineno = 1;
+  if (!s) {
+    fin = stdin;
+    fname = "stdin";
+  } else {
+    FILE *f = fl_fopen(s, "rb");
+    if (!f)
+      return 0;
+    fin = f;
+    fname = s;
+  }
+  return 1;
+}
+
+/**
+ Close the .fl file.
+ \return 0 if the operation failed, 1 if it succeeded
+ */
+int Fd_Project_Reader::close_read() {
+  if (fin != stdin) {
+    int x = fclose(fin);
+    fin = 0;
+    return x >= 0;
+  }
+  return 1;
+}
+
+/**
+ Return the name part of the current filename and path.
+ \return a pointer into a string that is not owned by this class
+ */
+const char *Fd_Project_Reader::filename_name() {
+  return fl_filename_name(fname);
+}
+
+/**
+ Convert an ASCII sequence from the \.fl file following a previously read `\\` into a single character.
+ Conversion includes the common C style \\ characters like \\n, \\x## hex
+ values, and \\o### octal values.
+ \return a character in the ASCII range
+ */
+int Fd_Project_Reader::read_quoted() {      // read whatever character is after a \ .
+  int c,d,x;
+  switch(c = nextchar()) {
+    case '\n': lineno++; return -1;
+    case 'a' : return('\a');
+    case 'b' : return('\b');
+    case 'f' : return('\f');
+    case 'n' : return('\n');
+    case 'r' : return('\r');
+    case 't' : return('\t');
+    case 'v' : return('\v');
+    case 'x' :    /* read hex */
+      for (c=x=0; x<3; x++) {
+        int ch = nextchar();
+        d = hexdigit(ch);
+        if (d > 15) {ungetc(ch,fin); break;}
+        c = (c<<4)+d;
+      }
+      break;
+    default:              /* read octal */
+      if (c<'0' || c>'7') break;
+      c -= '0';
+      for (x=0; x<2; x++) {
+        int ch = nextchar();
+        d = hexdigit(ch);
+        if (d>7) {ungetc(ch,fin); break;}
+        c = (c<<3)+d;
+      }
+      break;
+  }
+  return(c);
+}
+
+/**
+ Recursively read child nodes in the .fl design file.
+
+ If this is the first call, also read the global settings for this design.
+
+ \param[in] p parent node or NULL
+ \param[in] merge if set, merge into existing design, else replace design
+ \param[in] strategy add nodes after current or as last child
+ \param[in] skip_options this is set if the options were already found in
+ a previous call, and there is no need to waste time searching for them.
+ \return the last type that was created
+ */
+Fl_Type *Fd_Project_Reader::read_children(Fl_Type *p, int merge, Strategy strategy, char skip_options) {
+  Fl_Type::current = p;
+  Fl_Type *last_child_read = NULL;
+  Fl_Type *t = NULL;
+  for (;;) {
+    const char *c = read_word();
+  REUSE_C:
+    if (!c) {
+      if (p && !merge)
+        read_error("Missing '}'");
+      break;
+    }
+
+    if (!strcmp(c,"}")) {
+      if (!p) read_error("Unexpected '}'");
+      break;
+    }
+
+    // Make sure that we don't go through the list of options for child nodes
+    if (!skip_options) {
+      // this is the first word in a .fd file:
+      if (!strcmp(c,"Magic:")) {
+        read_fdesign();
+        return NULL;
+      }
+
+      if (!strcmp(c,"version")) {
+        c = read_word();
+        read_version = strtod(c,0);
+        if (read_version<=0 || read_version>double(FL_VERSION+0.00001))
+          read_error("unknown version '%s'",c);
+        continue;
+      }
+
+      // back compatibility with Vincent Penne's original class code:
+      if (!p && !strcmp(c,"define_in_struct")) {
+        Fl_Type *t = add_new_widget_from_file("class", Strategy::FROM_FILE_AS_LAST_CHILD);
+        t->name(read_word());
+        Fl_Type::current = p = t;
+        merge = 1; // stops "missing }" error
+        continue;
+      }
+
+      if (!strcmp(c,"do_not_include_H_from_C")) {
+        g_project.include_H_from_C=0;
+        goto CONTINUE;
+      }
+      if (!strcmp(c,"use_FL_COMMAND")) {
+        g_project.use_FL_COMMAND=1;
+        goto CONTINUE;
+      }
+      if (!strcmp(c,"utf8_in_src")) {
+        g_project.utf8_in_src=1;
+        goto CONTINUE;
+      }
+      if (!strcmp(c,"avoid_early_includes")) {
+        g_project.avoid_early_includes=1;
+        goto CONTINUE;
+      }
+      if (!strcmp(c,"i18n_type")) {
+        g_project.i18n_type = static_cast<Fd_I18n_Type>(atoi(read_word()));
+        goto CONTINUE;
+      }
+      if (!strcmp(c,"i18n_gnu_function")) {
+        g_project.i18n_gnu_function = read_word();
+        goto CONTINUE;
+      }
+      if (!strcmp(c,"i18n_gnu_static_function")) {
+        g_project.i18n_gnu_static_function = read_word();
+        goto CONTINUE;
+      }
+      if (!strcmp(c,"i18n_pos_file")) {
+        g_project.i18n_pos_file = read_word();
+        goto CONTINUE;
+      }
+      if (!strcmp(c,"i18n_pos_set")) {
+        g_project.i18n_pos_set = read_word();
+        goto CONTINUE;
+      }
+      if (!strcmp(c,"i18n_include")) {
+        if (g_project.i18n_type == FD_I18N_GNU)
+          g_project.i18n_gnu_include = read_word();
+        else if (g_project.i18n_type == FD_I18N_POSIX)
+          g_project.i18n_pos_include = read_word();
+        goto CONTINUE;
+      }
+      if (!strcmp(c,"i18n_conditional")) {
+        if (g_project.i18n_type == FD_I18N_GNU)
+          g_project.i18n_gnu_conditional = read_word();
+        else if (g_project.i18n_type == FD_I18N_POSIX)
+          g_project.i18n_pos_conditional = read_word();
+        goto CONTINUE;
+      }
+      if (!strcmp(c,"header_name")) {
+        if (!g_project.header_file_set) g_project.header_file_name = read_word();
+        else read_word();
+        goto CONTINUE;
+      }
+
+      if (!strcmp(c,"code_name")) {
+        if (!g_project.code_file_set) g_project.code_file_name = read_word();
+        else read_word();
+        goto CONTINUE;
+      }
+
+      if (!strcmp(c, "snap")) {
+        g_layout_list.read(this);
+        goto CONTINUE;
+      }
+
+      if (!strcmp(c, "gridx") || !strcmp(c, "gridy")) {
+        // grid settings are now global
+        read_word();
+        goto CONTINUE;
+      }
+
+      if (strcmp(c, "shell_commands")==0) {
+        if (g_shell_config) {
+          g_shell_config->read(this);
+        } else {
+          read_word();
+        }
+        goto CONTINUE;
+      }
+
+      if (!strcmp(c, "mergeback")) {
+        g_project.write_mergeback_data = read_int();
+        goto CONTINUE;
+      }
+    }
+    t = add_new_widget_from_file(c, strategy);
+    if (!t) {
+      read_error("Unknown word \"%s\"", c);
+      continue;
+    }
+    last_child_read = t;
+    // After reading the first widget, we no longer need to look for options
+    skip_options = 1;
+
+    t->name(read_word());
+
+    c = read_word(1);
+    if (strcmp(c,"{") && t->is_class()) {   // <prefix> <name>
+      ((Fl_Class_Type*)t)->prefix(t->name());
+      t->name(c);
+      c = read_word(1);
+    }
+
+    if (strcmp(c,"{")) {
+      read_error("Missing property list for %s\n",t->title());
+      goto REUSE_C;
+    }
+
+    t->folded_ = 1;
+    for (;;) {
+      const char *cc = read_word();
+      if (!cc || !strcmp(cc,"}")) break;
+      t->read_property(*this, cc);
+    }
+
+    if (t->can_have_children()) {
+      c = read_word(1);
+      if (strcmp(c,"{")) {
+        read_error("Missing child list for %s\n",t->title());
+        goto REUSE_C;
+      }
+      read_children(t, 0, Strategy::FROM_FILE_AS_LAST_CHILD, skip_options);
+      t->postprocess_read();
+      // FIXME: this has no business in the file reader!
+      // TODO: this is called whenever something is pasted from the top level into a grid
+      //    It makes sense to make this more universal for other widget types too.
+      if (merge && t && t->parent && t->parent->is_a(ID_Grid)) {
+        if (Fl_Window_Type::popupx != 0x7FFFFFFF) {
+          ((Fl_Grid_Type*)t->parent)->insert_child_at(((Fl_Widget_Type*)t)->o, Fl_Window_Type::popupx, Fl_Window_Type::popupy);
+        } else {
+          ((Fl_Grid_Type*)t->parent)->insert_child_at_next_free_cell(((Fl_Widget_Type*)t)->o);
+        }
+      }
+
+      t->layout_widget();
+    }
+
+    if (strategy.placement() == Strategy::AS_FIRST_CHILD) {
+      strategy.placement(Strategy::AFTER_CURRENT);
+    }
+    if (strategy.placement() == Strategy::AFTER_CURRENT) {
+      Fl_Type::current = t;
+    } else {
+      Fl_Type::current = p;
+    }
+
+  CONTINUE:;
+  }
+  if (merge && last_child_read && last_child_read->parent) {
+    last_child_read->parent->postprocess_read();
+    last_child_read->parent->layout_widget();
+  }
+  return last_child_read;
+}
+
+/** \brief Read a .fl project file.
+ \param[in] filename read this file
+ \param[in] merge if this is set, merge the file into an existing project
+ at Fl_Type::current
+ \param[in] strategy add new nodes after current or as last child
+ \return 0 if the operation failed, 1 if it succeeded
+ */
+int Fd_Project_Reader::read_project(const char *filename, int merge, Strategy strategy) {
+  Fl_Type *o;
+  undo_suspend();
+  read_version = 0.0;
+  if (!open_read(filename)) {
+    undo_resume();
+    return 0;
+  }
+  if (merge)
+    deselect();
+  else
+    g_project.reset();
+  read_children(Fl_Type::current, merge, strategy);
+  // clear this
+  Fl_Type::current = 0;
+  // Force menu items to be rebuilt...
+  for (o = Fl_Type::first; o; o = o->next) {
+    if (o->is_a(ID_Menu_Manager_)) {
+      o->add_child(0,0);
+    }
+  }
+  for (o = Fl_Type::first; o; o = o->next) {
+    if (o->selected) {
+      Fl_Type::current = o;
+      break;
+    }
+  }
+  selection_changed(Fl_Type::current);
+  if (g_shell_config) {
+    g_shell_config->rebuild_shell_menu();
+    g_shell_config->update_settings_dialog();
+  }
+  g_layout_list.update_dialogs();
+  g_project.update_settings_dialog();
+  int ret = close_read();
+  undo_resume();
+  return ret;
+}
+
+/**
+ Display an error while reading the file.
+ If the .fl file isn't opened for reading, pop up an FLTK dialog, otherwise
+ print to stdout.
+ \note Matt: I am not sure why it is done this way. Shouldn't this depend on \c batch_mode?
+ \todo Not happy about this function. Output channel should depend on `batch_mode`
+       as the note above already states. I want to make all file readers and writers
+       depend on an error handling base class that outputs a useful analysis of file
+       operations.
+ \param[in] format printf style format string, followed by an argument list
+ */
+void Fd_Project_Reader::read_error(const char *format, ...) {
+  va_list args;
+  va_start(args, format);
+  if (!fin) { // FIXME: this line suppresses any error messages in interactive mode
+    char buffer[1024]; // TODO: hides class member "buffer"
+    vsnprintf(buffer, sizeof(buffer), format, args);
+    fl_message("%s", buffer);
+  } else {
+    fprintf(stderr, "%s:%d: ", fname, lineno);
+    vfprintf(stderr, format, args);
+    fprintf(stderr, "\n");
+  }
+  va_end(args);
+}
+
+/**
+ Return a word read from the .fl file, or NULL at the EOF.
+
+ This will skip all comments (# to end of line), and evaluate
+ all \\xxx sequences and use \\ at the end of line to remove the newline.
+
+ A word is any one of:
+ - a continuous string of non-space chars except { and } and #
+ - everything between matching {...} (unless wantbrace != 0)
+ - the characters '{' and '}'
+
+ \param[in] wantbrace if set, reading a `{` as the first non-space character
+    will return the string `"{"`, if clear, a `{` is seen as the start of a word
+ \return a pointer to the internal buffer, containing a copy of the word.
+    Don't free the buffer! Note that most (all?) other file operations will
+    overwrite this buffer. If wantbrace is not set, but we read a leading '{',
+    the returned string will be stripped of its leading and trailing braces.
+ */
+const char *Fd_Project_Reader::read_word(int wantbrace) {
+  int x;
+
+  // skip all the whitespace before it:
+  for (;;) {
+    x = nextchar();
+    if (x < 0 && feof(fin)) {   // eof
+      return 0;
+    } else if (x == '#') {      // comment
+      do x = nextchar(); while (x >= 0 && x != '\n');
+      lineno++;
+      continue;
+    } else if (x == '\n') {
+      lineno++;
+    } else if (!isspace(x & 255)) {
+      break;
+    }
+  }
+
+  expand_buffer(100);
+
+  if (x == '{' && !wantbrace) {
+
+    // read in whatever is between braces
+    int length = 0;
+    int nesting = 0;
+    for (;;) {
+      x = nextchar();
+      if (x<0) {read_error("Missing '}'"); break;}
+      else if (x == '#') { // embedded comment
+        do x = nextchar(); while (x >= 0 && x != '\n');
+        lineno++;
+        continue;
+      } else if (x == '\n') lineno++;
+      else if (x == '\\') {x = read_quoted(); if (x<0) continue;}
+      else if (x == '{') nesting++;
+      else if (x == '}') {if (!nesting--) break;}
+      buffer[length++] = x;
+      expand_buffer(length);
+    }
+    buffer[length] = 0;
+    return buffer;
+
+  } else if (x == '{' || x == '}') {
+    // all the punctuation is a word:
+    buffer[0] = x;
+    buffer[1] = 0;
+    return buffer;
+
+  } else {
+
+    // read in an unquoted word:
+    int length = 0;
+    for (;;) {
+      if (x == '\\') {x = read_quoted(); if (x<0) continue;}
+      else if (x<0 || isspace(x & 255) || x=='{' || x=='}' || x=='#') break;
+      buffer[length++] = x;
+      expand_buffer(length);
+      x = nextchar();
+    }
+    ungetc(x, fin);
+    buffer[length] = 0;
+    return buffer;
+
+  }
+}
+
+/** Read a word and interpret it as an integer value.
+ \return integer value, or 0 if the word is not an integer
+ */
+int Fd_Project_Reader::read_int() {
+  const char *word = read_word();
+  if (word) {
+    return atoi(word);
+  } else {
+    return 0;
+  }
+}
+
+/** Read fdesign name/value pairs.
+ Fdesign is the file format of the XForms UI designer. It stores lists of name
+ and value pairs separated by a colon: `class: FL_LABELFRAME`.
+ \param[out] name string
+ \param[out] value string
+ \return 0 if end of file, else 1
+ */
+int Fd_Project_Reader::read_fdesign_line(const char*& name, const char*& value) {
+  int length = 0;
+  int x;
+  // find a colon:
+  for (;;) {
+    x = nextchar();
+    if (x < 0 && feof(fin)) return 0;
+    if (x == '\n') {length = 0; continue;} // no colon this line...
+    if (!isspace(x & 255)) {
+      buffer[length++] = x;
+      expand_buffer(length);
+    }
+    if (x == ':') break;
+  }
+  int valueoffset = length;
+  buffer[length-1] = 0;
+
+  // skip to start of value:
+  for (;;) {
+    x = nextchar();
+    if ((x < 0 && feof(fin)) || x == '\n' || !isspace(x & 255)) break;
+  }
+
+  // read the value:
+  for (;;) {
+    if (x == '\\') {x = read_quoted(); if (x<0) continue;}
+    else if (x == '\n') break;
+    buffer[length++] = x;
+    expand_buffer(length);
+    x = nextchar();
+  }
+  buffer[length] = 0;
+  name = buffer;
+  value = buffer+valueoffset;
+  return 1;
+}
+
+/// Lookup table from fdesign .fd files to .fl files
+static const char *class_matcher[] = {
+  "FL_CHECKBUTTON", "Fl_Check_Button",
+  "FL_ROUNDBUTTON", "Fl_Round_Button",
+  "FL_ROUND3DBUTTON", "Fl_Round_Button",
+  "FL_LIGHTBUTTON", "Fl_Light_Button",
+  "FL_FRAME", "Fl_Box",
+  "FL_LABELFRAME", "Fl_Box",
+  "FL_TEXT", "Fl_Box",
+  "FL_VALSLIDER", "Fl_Value_Slider",
+  "FL_MENU", "Fl_Menu_Button",
+  "3", "FL_BITMAP",
+  "1", "FL_BOX",
+  "71","FL_BROWSER",
+  "11","FL_BUTTON",
+  "4", "FL_CHART",
+  "42","FL_CHOICE",
+  "61","FL_CLOCK",
+  "25","FL_COUNTER",
+  "22","FL_DIAL",
+  "101","FL_FREE",
+  "31","FL_INPUT",
+  "12","Fl_Light_Button",
+  "41","FL_MENU",
+  "23","FL_POSITIONER",
+  "13","Fl_Round_Button",
+  "21","FL_SLIDER",
+  "2", "FL_BOX", // was FL_TEXT
+  "62","FL_TIMER",
+  "24","Fl_Value_Slider",
+  0};
+
+
+/**
+ Finish a group of widgets and optionally transform its children's coordinates.
+
+ Implements the same functionality as Fl_Group::forms_end() from the forms
+ compatibility library would have done:
+
+ - resize the group to surround its children if the group's w() == 0
+ - optionally flip the \p y coordinates of all children relative to the group's window
+ - Fl_Group::end() the group
+
+ \note Copied from forms_compatibility.cxx and modified as a static fluid
+ function so we don't have to link to fltk_forms.
+
+ \param[in]  g     the Fl_Group widget
+ \param[in]  flip  flip children's \p y coordinates if true (non-zero)
+ */
+static void forms_end(Fl_Group *g, int flip) {
+  // set the dimensions of a group to surround its contents
+  const int nc = g->children();
+  if (nc && !g->w()) {
+    Fl_Widget*const* a = g->array();
+    Fl_Widget* o = *a++;
+    int rx = o->x();
+    int ry = o->y();
+    int rw = rx+o->w();
+    int rh = ry+o->h();
+    for (int i = nc - 1; i--;) {
+      o = *a++;
+      if (o->x() < rx) rx = o->x();
+      if (o->y() < ry) ry = o->y();
+      if (o->x() + o->w() > rw) rw = o->x() + o->w();
+      if (o->y() + o->h() > rh) rh = o->y() + o->h();
+    }
+    g->Fl_Widget::resize(rx, ry, rw-rx, rh-ry);
+  }
+  // flip all the children's coordinate systems:
+  if (nc && flip) {
+    Fl_Widget* o = (g->as_window()) ? g : g->window();
+    int Y = o->h();
+    Fl_Widget*const* a = g->array();
+    for (int i = nc; i--;) {
+      Fl_Widget* ow = *a++;
+      int newy = Y - ow->y() - ow->h();
+      ow->Fl_Widget::resize(ow->x(), newy, ow->w(), ow->h());
+    }
+  }
+  g->end();
+}
+
+/**
+ Read a XForms design file.
+ .fl and .fd file start with the same header. Fluid can recognize .fd XForms
+ Design files by a magic number. It will read them and map XForms widgets onto
+ FLTK widgets.
+ \see http://xforms-toolkit.org
+ */
+void Fd_Project_Reader::read_fdesign() {
+  int fdesign_magic = atoi(read_word());
+  fdesign_flip = (fdesign_magic < 13000);
+  Fl_Widget_Type *window = 0;
+  Fl_Widget_Type *group = 0;
+  Fl_Widget_Type *widget = 0;
+  if (!Fl_Type::current) {
+    Fl_Type *t = add_new_widget_from_file("Function", Strategy::FROM_FILE_AS_LAST_CHILD);
+    t->name("create_the_forms()");
+    Fl_Type::current = t;
+  }
+  for (;;) {
+    const char *name;
+    const char *value;
+    if (!read_fdesign_line(name, value)) break;
+
+    if (!strcmp(name,"Name")) {
+
+      window = (Fl_Widget_Type*)add_new_widget_from_file("Fl_Window", Strategy::FROM_FILE_AS_LAST_CHILD);
+      window->name(value);
+      window->label(value);
+      Fl_Type::current = widget = window;
+
+    } else if (!strcmp(name,"class")) {
+
+      if (!strcmp(value,"FL_BEGIN_GROUP")) {
+        group = widget = (Fl_Widget_Type*)add_new_widget_from_file("Fl_Group", Strategy::FROM_FILE_AS_LAST_CHILD);
+        Fl_Type::current = group;
+      } else if (!strcmp(value,"FL_END_GROUP")) {
+        if (group) {
+          Fl_Group* g = (Fl_Group*)(group->o);
+          g->begin();
+          forms_end(g, fdesign_flip);
+          Fl_Group::current(0);
+        }
+        group = widget = 0;
+        Fl_Type::current = window;
+      } else {
+        for (int i = 0; class_matcher[i]; i += 2)
+          if (!strcmp(value,class_matcher[i])) {
+            value = class_matcher[i+1]; break;}
+        widget = (Fl_Widget_Type*)add_new_widget_from_file(value, Strategy::FROM_FILE_AS_LAST_CHILD);
+        if (!widget) {
+          printf("class %s not found, using Fl_Button\n", value);
+          widget = (Fl_Widget_Type*)add_new_widget_from_file("Fl_Button", Strategy::FROM_FILE_AS_LAST_CHILD);
+        }
+      }
+
+    } else if (widget) {
+      if (!widget->read_fdesign(name, value))
+        printf("Ignoring \"%s: %s\"\n", name, value);
+    }
+  }
+}
+
+// ---- Fd_Project_Writer ---------------------------------------------- MARK: -
+
+/** \brief Construct local project writer. */
+Fd_Project_Writer::Fd_Project_Writer()
+: fout(NULL),
+  needspace(0),
+  write_codeview_(false)
+{
+}
+
+/** \brief Release project writer resources. */
+Fd_Project_Writer::~Fd_Project_Writer()
+{
+}
+
+/**
+ Open the .fl design file for writing.
+ If the filename is NULL, associate stdout instead.
+ \param[in] s the filename or NULL for stdout
+ \return 1 if successful. 0 if the operation failed
+ */
+int Fd_Project_Writer::open_write(const char *s) {
+  if (!s) {
+    fout = stdout;
+  } else {
+    FILE *f = fl_fopen(s,"wb");
+    if (!f) return 0;
+    fout = f;
+  }
+  return 1;
+}
+
+/**
+ Close the .fl design file.
+ Don't close, if data was sent to stdout.
+ \return 1 if succeeded, 0 if fclose failed
+ */
+int Fd_Project_Writer::close_write() {
+  if (fout != stdout) {
+    int x = fclose(fout);
+    fout = stdout;
+    return x >= 0;
+  }
+  return 1;
+}
+
+/** \brief Write an .fl design description file.
+ \param[in] filename create this file, and if it exists, overwrite it
+ \param[in] selected_only write only the selected nodes in the widget_tree. This
+            is used to implement copy and paste.
+ \param[in] sv if set, this file will be used by codeview
+ \return 0 if the operation failed, 1 if it succeeded
+ */
+int Fd_Project_Writer::write_project(const char *filename, int selected_only, bool sv) {
+  write_codeview_ = sv;
+  undo_suspend();
+  if (!open_write(filename)) {
+    undo_resume();
+    return 0;
+  }
+  write_string("# data file for the Fltk User Interface Designer (fluid)\n"
+               "version %.4f",FL_VERSION);
+  if(!g_project.include_H_from_C)
+    write_string("\ndo_not_include_H_from_C");
+  if(g_project.use_FL_COMMAND)
+    write_string("\nuse_FL_COMMAND");
+  if (g_project.utf8_in_src)
+    write_string("\nutf8_in_src");
+  if (g_project.avoid_early_includes)
+    write_string("\navoid_early_includes");
+  if (g_project.i18n_type) {
+    write_string("\ni18n_type %d", g_project.i18n_type);
+    switch (g_project.i18n_type) {
+      case FD_I18N_NONE:
+        break;
+      case FD_I18N_GNU : /* GNU gettext */
+        write_string("\ni18n_include"); write_word(g_project.i18n_gnu_include.c_str());
+        write_string("\ni18n_conditional"); write_word(g_project.i18n_gnu_conditional.c_str());
+        write_string("\ni18n_gnu_function"); write_word(g_project.i18n_gnu_function.c_str());
+        write_string("\ni18n_gnu_static_function"); write_word(g_project.i18n_gnu_static_function.c_str());
+        break;
+      case FD_I18N_POSIX : /* POSIX catgets */
+        write_string("\ni18n_include"); write_word(g_project.i18n_pos_include.c_str());
+        write_string("\ni18n_conditional"); write_word(g_project.i18n_pos_conditional.c_str());
+        if (!g_project.i18n_pos_file.empty()) {
+          write_string("\ni18n_pos_file");
+          write_word(g_project.i18n_pos_file.c_str());
+        }
+        write_string("\ni18n_pos_set"); write_word(g_project.i18n_pos_set.c_str());
+        break;
+    }
+  }
+
+  if (!selected_only) {
+    write_string("\nheader_name"); write_word(g_project.header_file_name.c_str());
+    write_string("\ncode_name"); write_word(g_project.code_file_name.c_str());
+    g_layout_list.write(this);
+    if (g_shell_config)
+      g_shell_config->write(this);
+    if (g_project.write_mergeback_data)
+      write_string("\nmergeback %d", g_project.write_mergeback_data);
+  }
+
+  for (Fl_Type *p = Fl_Type::first; p;) {
+    if (!selected_only || p->selected) {
+      p->write(*this);
+      write_string("\n");
+      int q = p->level;
+      for (p = p->next; p && p->level > q; p = p->next) {/*empty*/}
+    } else {
+      p = p->next;
+    }
+  }
+  int ret = close_write();
+  undo_resume();
+  return ret;
+}
+
+/**
+ Write a string to the .fl file, quoting characters if necessary.
+ \param[in] w NUL terminated text
+ */
+void Fd_Project_Writer::write_word(const char *w) {
+  if (needspace) putc(' ', fout);
+  needspace = 1;
+  if (!w || !*w) {fprintf(fout,"{}"); return;}
+  const char *p;
+  // see if it is a single word:
+  for (p = w; is_id(*p); p++) ;
+  if (!*p) {fprintf(fout,"%s",w); return;}
+  // see if there are matching braces:
+  int n = 0;
+  for (p = w; *p; p++) {
+    if (*p == '{') n++;
+    else if (*p == '}') {n--; if (n<0) break;}
+  }
+  int mismatched = (n != 0);
+  // write out brace-quoted string:
+  putc('{', fout);
+  for (; *w; w++) {
+    switch (*w) {
+    case '{':
+    case '}':
+      if (!mismatched) break;
+    case '\\':
+    case '#':
+      putc('\\',fout);
+      break;
+    }
+    putc(*w,fout);
+  }
+  putc('}', fout);
+}
+
+/**
+ Write an arbitrary formatted word to the .fl file, or a comment, etc .
+ If needspace is set, then one space is written before the string
+ unless the format starts with a newline character \\n.
+ \param[in] format printf style formatting string followed by a list of arguments
+ */
+void Fd_Project_Writer::write_string(const char *format, ...) {
+  va_list args;
+  va_start(args, format);
+  if (needspace && *format != '\n') fputc(' ',fout);
+  vfprintf(fout, format, args);
+  va_end(args);
+  needspace = !isspace(format[strlen(format)-1] & 255);
+}
+
+/**
+ Start a new line in the .fl file and indent it for a given nesting level.
+ \param[in] n indent level
+ */
+void Fd_Project_Writer::write_indent(int n) {
+  fputc('\n',fout);
+  while (n--) {fputc(' ',fout); fputc(' ',fout);}
+  needspace = 0;
+}
+
+/**
+ Write a '{' to the .fl file at the given indenting level.
+ */
+void Fd_Project_Writer::write_open() {
+  if (needspace) fputc(' ',fout);
+  fputc('{',fout);
+  needspace = 0;
+}
+
+/**
+ Write a '}' to the .fl file at the given indenting level.
+ \param[in] n indent level
+ */
+void Fd_Project_Writer::write_close(int n) {
+  if (needspace) write_indent(n);
+  fputc('}',fout);
+  needspace = 1;
+}
+
+/// \}
diff --git a/fluid/io/Project_Writer.h b/fluid/io/Project_Writer.h
new file mode 100644
index 0000000000000000000000000000000000000000..470cc1a7b74767b16753826038b6a9f4155d12c7
--- /dev/null
+++ b/fluid/io/Project_Writer.h
@@ -0,0 +1,94 @@
+//
+// Fluid file 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
+//
+
+#ifndef _FLUID_FILE_H
+#define _FLUID_FILE_H
+
+#include "nodes/Fl_Type.h"
+
+#include <FL/fl_attr.h>
+
+class Fl_Type;
+
+extern int fdesign_flip;
+
+int read_file(const char *, int merge, Strategy strategy=Strategy::FROM_FILE_AS_LAST_CHILD);
+int write_file(const char *, int selected_only = 0, bool to_codeview = false);
+
+class Fd_Project_Reader
+{
+protected:
+  /// Project input file
+  FILE *fin;
+  /// Number of most recently read line
+  int lineno;
+  /// Pointer to the file path and name (not copied!)
+  const char *fname;
+  /// Expanding buffer to store the most recently read word
+  char *buffer;
+  /// Exact size of the expanding buffer in bytes
+  int buflen;
+
+  void expand_buffer(int length);
+
+  int nextchar() { for (;;) { int ret = fgetc(fin); if (ret!='\r') return ret; } }
+
+public:
+  /// Holds the file version number after reading the "version" tag
+  double read_version;
+
+public:
+  Fd_Project_Reader();
+  ~Fd_Project_Reader();
+  int open_read(const char *s);
+  int close_read();
+  const char *filename_name();
+  int read_quoted();
+  Fl_Type *read_children(Fl_Type *p, int merge, Strategy strategy, char skip_options=0);
+  int read_project(const char *, int merge, Strategy strategy=Strategy::FROM_FILE_AS_LAST_CHILD);
+  void read_error(const char *format, ...);
+  const char *read_word(int wantbrace = 0);
+  int read_int();
+  int read_fdesign_line(const char*& name, const char*& value);
+  void read_fdesign();
+};
+
+class Fd_Project_Writer
+{
+protected:
+  // Project output file, always opened in "wb" mode
+  FILE *fout;
+  /// If set, one space is written before text unless the format starts with a newline character
+  int needspace;
+  /// Set if this file will be used in the codeview dialog
+  bool write_codeview_;
+
+public:
+  Fd_Project_Writer();
+  ~Fd_Project_Writer();
+  int open_write(const char *s);
+  int close_write();
+  int write_project(const char *filename, int selected_only, bool codeview);
+  void write_word(const char *);
+  void write_string(const char *,...) __fl_attr((__format__ (__printf__, 2, 3)));
+  void write_indent(int n);
+  void write_open();
+  void write_close(int n);
+  FILE *file() const { return fout; }
+  bool write_codeview() const { return write_codeview_; }
+};
+
+#endif // _FLUID_FILE_H
diff --git a/fluid/tools/fluid_filename.cxx b/fluid/tools/filename.cxx
similarity index 100%
rename from fluid/tools/fluid_filename.cxx
rename to fluid/tools/filename.cxx
diff --git a/fluid/tools/fluid_filename.h b/fluid/tools/filename.h
similarity index 100%
rename from fluid/tools/fluid_filename.h
rename to fluid/tools/filename.h