From fc91880faf7968432710e439e77ef067759d16fc Mon Sep 17 00:00:00 2001 From: Matthias Melcher Date: Tue, 15 Apr 2025 14:47:24 +0200 Subject: Reactivating Mergeback functionality. (#1226) Reactivated code. Various fixes. New documentation. --- fluid/app/Menu.cxx | 4 +- fluid/documentation/Doxyfile.in | 1 + fluid/documentation/src/index.dox | 2 + fluid/documentation/src/page_mergeback.dox | 84 +++++++++ fluid/io/Code_Writer.cxx | 15 +- fluid/io/Code_Writer.h | 4 +- fluid/nodes/Function_Node.cxx | 7 +- fluid/nodes/Menu_Node.cxx | 7 +- fluid/nodes/Node.cxx | 11 +- fluid/nodes/Node.h | 2 +- fluid/nodes/Tree.cxx | 2 +- fluid/nodes/Widget_Node.cxx | 7 +- fluid/nodes/factory.cxx | 4 +- fluid/panels/settings_panel.cxx | 7 +- fluid/panels/settings_panel.fl | 9 +- fluid/panels/settings_panel.h | 2 +- fluid/proj/mergeback.cxx | 271 +++++++++++++++++++++-------- fluid/proj/mergeback.h | 51 +++--- 18 files changed, 366 insertions(+), 124 deletions(-) create mode 100644 fluid/documentation/src/page_mergeback.dox (limited to 'fluid') diff --git a/fluid/app/Menu.cxx b/fluid/app/Menu.cxx index 044cc29a7..32fe59805 100644 --- a/fluid/app/Menu.cxx +++ b/fluid/app/Menu.cxx @@ -18,6 +18,7 @@ #include "Fluid.h" +#include "proj/mergeback.h" #include "proj/undo.h" #include "app/templates.h" #include "nodes/Node.h" @@ -52,6 +53,7 @@ void help_cb(Fl_Widget *, void *) { Fluid.show_help("fluid.html"); } static void save_template_cb(Fl_Widget *, void *) { fld::app::save_template(); } +void mergeback_cb(Fl_Widget *, void *); void manual_cb(Fl_Widget *, void *) { Fluid.show_help("index.html"); @@ -99,7 +101,7 @@ Fl_Menu_Item Application::main_menu[] = { {"Save As &Template...", 0, save_template_cb, nullptr, FL_MENU_DIVIDER}, {"&Print...", FL_COMMAND+'p', menu_file_print_cb}, {"Write &Code", FL_COMMAND+FL_SHIFT+'c', write_cb, nullptr}, -// Matt: disabled {"MergeBack Code", FL_COMMAND+FL_SHIFT+'m', mergeback_cb, 0}, + {"MergeBack Code", FL_COMMAND+FL_SHIFT+'m', mergeback_cb, 0}, {"&Write Strings", FL_COMMAND+FL_SHIFT+'w', write_strings_cb, nullptr, FL_MENU_DIVIDER}, {Fluid.history.relpath[0], FL_COMMAND+'1', menu_file_open_history_cb, Fluid.history.abspath[0]}, {Fluid.history.relpath[1], FL_COMMAND+'2', menu_file_open_history_cb, Fluid.history.abspath[1]}, diff --git a/fluid/documentation/Doxyfile.in b/fluid/documentation/Doxyfile.in index f625dd52a..8e9bee234 100644 --- a/fluid/documentation/Doxyfile.in +++ b/fluid/documentation/Doxyfile.in @@ -792,6 +792,7 @@ INPUT = @CMAKE_CURRENT_SOURCE_DIR@/src/index.dox \ @CMAKE_CURRENT_SOURCE_DIR@/src/page_widget_panel.dox \ @CMAKE_CURRENT_SOURCE_DIR@/src/page_setting_dialog.dox \ @CMAKE_CURRENT_SOURCE_DIR@/src/page_codeview_panel.dox \ + @CMAKE_CURRENT_SOURCE_DIR@/src/page_mergeback.dox \ @CMAKE_CURRENT_SOURCE_DIR@/src/page_tutorial.dox \ @CMAKE_CURRENT_SOURCE_DIR@/src/page_appendices.dox diff --git a/fluid/documentation/src/index.dox b/fluid/documentation/src/index.dox index 7a2808207..94c47b4c5 100644 --- a/fluid/documentation/src/index.dox +++ b/fluid/documentation/src/index.dox @@ -96,6 +96,8 @@ - \ref setting_i18n - \ref setting_user + \subpage page_mergeback + \subpage page_tutorial - \ref fluid_hello_world_tutorial - \ref fluid_1of7guis_tutorial diff --git a/fluid/documentation/src/page_mergeback.dox b/fluid/documentation/src/page_mergeback.dox new file mode 100644 index 000000000..20c0026c9 --- /dev/null +++ b/fluid/documentation/src/page_mergeback.dox @@ -0,0 +1,84 @@ +/** + + \page page_mergeback Fluid Mergeback + + \tableofcontents + + # Introduction to Mergeback # + + Fluid has introduced an experimental feature called **MergeBack** in version 1.5. + This feature allows developers to edit the C++ source files generated by Fluid and + then merge those changes back into the corresponding Fluid project file (.fl). This + can streamline the development process by reducing the need to switch between Fluid + and your code editor for minor adjustments. + + **Important Note:** MergeBack is currently experimental and may not handle all + scenarios perfectly. It's advisable to back up your project files before using this + feature to prevent potential data loss. + + **Enabling MergeBack:** + + 1. **Open Your Project in Fluid:** + - Launch Fluid and load your existing `.fl` project file. + + 2. **Enable MergeBack:** + - Navigate to the **Settings Panel**. + - In the **Project Settings**, locate and enable the **MergeBack** option. + + Once enabled, Fluid assigns a unique ID to each node in your project. + Additionally, user-editable code sections in the generated source files will + be marked with special divider lines, indicating areas that can be edited + and later merged back. + + **Understanding the Code Markers:** + + In the generated `.cxx` files, editable sections are demarcated as follows: + + ```cpp + static void cb_script_panel(Fl_Double_Window*, void*) { + //fl ▼ ---------------------- callback ~~----~~~~=-=~~~~=-~~~ ▼ fl// + if (Fl::event() == FL_SHORTCUT && Fl::event_key() == FL_Escape) return; + script_panel->hide(); + //fl ▲ ----------=~-~~=-=--=~-----------~-==--~=~-~~--~=-~=~- ▲ fl// + } + ``` + + The lines beginning with `//fl ▼` and `//fl ▲` are markers that Fluid + uses to identify editable sections. These markers contain encoded information + about the code block, including a CRC32 checksum and a unique node ID. Fluid + uses this information during the merge process to detect changes and associate + them with the correct nodes in your project. + + **Editing and Merging Back:** + + 1. **Edit the Source Code:** + - Open the generated `.cxx` file in your preferred code editor. + - Make your desired changes within the marked sections. + + 2. **Merge Changes Back into Fluid:** + - After saving your edits, return to Fluid. + - Select **File > MergeBack** from the main menu or press `Ctrl+Shift+M`. + - Fluid will analyze the `.cxx` file, detect changes within the marked + sections, and offer to merge them back into the `.fl` project file. + + **Things to Keep in Mind:** + + - **Scope of MergeBack:** Currently, MergeBack supports merging changes + made to code blocks and widget callbacks. Other modifications might + not be recognized or merged correctly. + +- **Conflict Detection:** Fluid calculates checksums (CRC32) for the + code blocks to detect changes. If discrepancies are found, Fluid + will prompt you with options to merge or cancel the changes. + +- **Undo Functionality:** In case of unintended merges, Fluid provides + an undo feature to revert the project to its previous state. + +**Caution:** Given the experimental nature of MergeBack, unexpected +behaviors may occur. Always ensure you have backups of your project +files before using this feature. + +\note The MergeBack feature is still under development. It's recommended + to use it cautiously and provide feedback to the FLTK development + team to help improve its functionality and reliability. +*/ diff --git a/fluid/io/Code_Writer.cxx b/fluid/io/Code_Writer.cxx index c9e638c93..b7179826d 100644 --- a/fluid/io/Code_Writer.cxx +++ b/fluid/io/Code_Writer.cxx @@ -18,6 +18,7 @@ #include "Fluid.h" #include "Project.h" +#include "proj/mergeback.h" #include "nodes/Window_Node.h" #include "nodes/Function_Node.h" @@ -28,6 +29,7 @@ using namespace fld; using namespace fld::io; +using namespace fld::proj; /** Return true if c can be in a C identifier. @@ -103,11 +105,11 @@ const char* Code_Writer::unique_id(void* o, const char* type, const char* name, \return pointer to a static string */ const char *Code_Writer::indent(int set) { - static const char* spaces = " "; + static const char* spaces = " "; int i = set * 2; - if (i>32) i = 32; + if (i>64) i = 64; if (i<0) i = 0; - return spaces+32-i; + return spaces+64-i; } /** @@ -774,9 +776,10 @@ Code_Writer::~Code_Writer() \param[in] type FD_TAG_GENERIC, FD_TAG_CODE, FD_TAG_MENU_CALLBACK, or FD_TAG_WIDGET_CALLBACK \param[in] uid the unique id of the current type */ -void Code_Writer::tag(int type, unsigned short uid) { - if (proj_.write_mergeback_data) - fprintf(code_file, "//~fl~%d~%04x~%08x~~\n", type, (int)uid, (unsigned int)block_crc_); +void Code_Writer::tag(proj::Mergeback::Tag prev_type, proj::Mergeback::Tag next_type, unsigned short uid) { + if (proj_.write_mergeback_data) { + Mergeback::print_tag(code_file, prev_type, next_type, uid, (uint32_t)block_crc_); + } block_crc_ = crc32(0, nullptr, 0); } diff --git a/fluid/io/Code_Writer.h b/fluid/io/Code_Writer.h index ca23ca0a2..c199197ec 100644 --- a/fluid/io/Code_Writer.h +++ b/fluid/io/Code_Writer.h @@ -17,6 +17,8 @@ #ifndef FLUID_IO_CODE_WRITER_H #define FLUID_IO_CODE_WRITER_H +#include "proj/mergeback.h" + #include #include @@ -114,7 +116,7 @@ public: int write_code(const char *cfile, const char *hfile, bool to_codeview=false); void write_public(int state); // writes pubic:/private: as needed - void tag(int type, unsigned short uid); + void tag(proj::Mergeback::Tag prev_type, proj::Mergeback::Tag next_type, unsigned short uid); static unsigned long block_crc(const void *data, int n=-1, unsigned long in_crc=0, bool *inout_line_start=nullptr); }; diff --git a/fluid/nodes/Function_Node.cxx b/fluid/nodes/Function_Node.cxx index 8ffdbef01..a70b10462 100644 --- a/fluid/nodes/Function_Node.cxx +++ b/fluid/nodes/Function_Node.cxx @@ -35,6 +35,9 @@ #include +using namespace fld; +using namespace fld::io; +using namespace fld::proj; /// Set a current class, so that the code of the children is generated correctly. Class_Node *current_class = nullptr; @@ -682,9 +685,9 @@ void Code_Node::write_code1(fld::io::Code_Writer& f) { if ( handle_editor_changes() == 1 ) { Fluid.main_window->redraw(); // tell fluid to redraw; edits may affect tree's contents } - // Matt: disabled f.tag(FD_TAG_GENERIC, 0); + f.tag(Mergeback::Tag::GENERIC, Mergeback::Tag::CODE, 0); f.write_c_indented(name(), 0, '\n'); - // Matt: disabled f.tag(FD_TAG_CODE, get_uid()); + f.tag(Mergeback::Tag::CODE, Mergeback::Tag::GENERIC, get_uid()); } /** diff --git a/fluid/nodes/Menu_Node.cxx b/fluid/nodes/Menu_Node.cxx index 2a76b833a..a4434a4dd 100644 --- a/fluid/nodes/Menu_Node.cxx +++ b/fluid/nodes/Menu_Node.cxx @@ -44,6 +44,9 @@ #include #include +using namespace fld; +using namespace fld::proj; + Fl_Menu_Item menu_item_type_menu[] = { {"Normal",0,nullptr,(void*)nullptr}, {"Toggle",0,nullptr,(void*)FL_MENU_BOX}, @@ -349,7 +352,7 @@ void Menu_Item_Node::write_static(fld::io::Code_Writer& f) { f.write_c(", %s", ut); if (use_v) f.write_c(" v"); f.write_c(") {\n"); - // Matt: disabled f.tag(FD_TAG_GENERIC, 0); + f.tag(Mergeback::Tag::GENERIC, Mergeback::Tag::MENU_CALLBACK, 0); f.write_c_indented(callback(), 1, 0); if (*(d-1) != ';' && *(d-1) != '}') { const char *p = strrchr(callback(), '\n'); @@ -360,7 +363,7 @@ void Menu_Item_Node::write_static(fld::io::Code_Writer& f) { if (*p != '#' && *p) f.write_c(";"); } f.write_c("\n"); - // Matt: disabled f.tag(FD_TAG_MENU_CALLBACK, get_uid()); + f.tag(Mergeback::Tag::MENU_CALLBACK, Mergeback::Tag::GENERIC, get_uid()); f.write_c("}\n"); // If the menu item is part of a Class or Widget Class, FLUID generates diff --git a/fluid/nodes/Node.cxx b/fluid/nodes/Node.cxx index 194484a93..08ec4a05b 100644 --- a/fluid/nodes/Node.cxx +++ b/fluid/nodes/Node.cxx @@ -696,7 +696,6 @@ void Node::add(Node *anchor, Strategy strategy) { Fluid.proj.tree.first = this; } -#if 0 { // make sure that we have no duplicate uid's Node *tp = this; do { @@ -704,7 +703,6 @@ void Node::add(Node *anchor, Strategy strategy) { tp = tp->next; } while (tp!=end && tp!=nullptr); } -#endif // Give the widgets in our tree a chance to update themselves for (Node *t = this; t && t!=end->next; t = t->next) { @@ -1274,11 +1272,14 @@ unsigned short Node::set_uid(unsigned short suggested_uid) { suggested_uid = (unsigned short)rand(); for (;;) { Node *tp = Fluid.proj.tree.first; - for ( ; tp; tp = tp->next) - if (tp!=this && tp->uid_==suggested_uid) + for ( ; tp; tp = tp->next) { + if (tp!=this && tp->uid_==suggested_uid) { break; - if (tp==nullptr) + } + } + if (tp==nullptr) { break; + } suggested_uid = (unsigned short)rand(); } uid_ = suggested_uid; diff --git a/fluid/nodes/Node.h b/fluid/nodes/Node.h index 5c473a936..64d720297 100644 --- a/fluid/nodes/Node.h +++ b/fluid/nodes/Node.h @@ -39,7 +39,7 @@ class Project_Writer; Declare where a new type is placed and how to create it. Placement can be as the first or last child of the anchor, or right after the - anchor. In most cases, the anchor is the last selected type node. + anchor. In most cases, the anchor is the last selected node. If the source is FROM_USER, widgets may be created with default titles and labels. Type created FROM_FILE will start with no label, so the label is set diff --git a/fluid/nodes/Tree.cxx b/fluid/nodes/Tree.cxx index ca9b3d761..92b5e7506 100644 --- a/fluid/nodes/Tree.cxx +++ b/fluid/nodes/Tree.cxx @@ -99,7 +99,7 @@ Node *Tree::find_by_uid(unsigned short uid) { } -/** Find a type node by using the codeview text positions. +/** Find a node by using the codeview text positions. \param[in] text_type 0=source file, 1=header, 2=.fl project file \param[in] crsr cursor position in text diff --git a/fluid/nodes/Widget_Node.cxx b/fluid/nodes/Widget_Node.cxx index cba8607f2..95be9493d 100644 --- a/fluid/nodes/Widget_Node.cxx +++ b/fluid/nodes/Widget_Node.cxx @@ -46,6 +46,9 @@ #undef max #include +using namespace fld; +using namespace fld::proj; + // Make an Widget_Node subclass instance. // It figures out the automatic size and parent of the new widget, // creates the Fl_Widget (by calling the virtual function _make), @@ -1468,7 +1471,7 @@ void Widget_Node::write_static(fld::io::Code_Writer& f) { f.write_c(", %s", ut); if (use_v) f.write_c(" v"); f.write_c(") {\n"); - // Matt: disabled f.tag(FD_TAG_GENERIC, 0); + f.tag(Mergeback::Tag::GENERIC, Mergeback::Tag::WIDGET_CALLBACK, 0); f.write_c_indented(callback(), 1, 0); if (*(d-1) != ';' && *(d-1) != '}') { const char *p = strrchr(callback(), '\n'); @@ -1479,7 +1482,7 @@ void Widget_Node::write_static(fld::io::Code_Writer& f) { if (*p != '#' && *p) f.write_c(";"); } f.write_c("\n"); - // Matt: disabled f.tag(FD_TAG_WIDGET_CALLBACK, get_uid()); + f.tag(Mergeback::Tag::WIDGET_CALLBACK, Mergeback::Tag::GENERIC, get_uid()); f.write_c("}\n"); if (k) { f.write_c("void %s::%s(%s* o, %s v) {\n", k, cn, t, ut); diff --git a/fluid/nodes/factory.cxx b/fluid/nodes/factory.cxx index 8388e0202..ca049880a 100644 --- a/fluid/nodes/factory.cxx +++ b/fluid/nodes/factory.cxx @@ -1597,7 +1597,7 @@ Node *typename_to_prototype(const char *inName) } /** - Create and add a new type node to the widget tree. + Create and add a new node to the widget tree. This is used by the .fl file reader. New types are always created as the last child of the first compatible parent. New widgets have a default @@ -1605,7 +1605,7 @@ Node *typename_to_prototype(const char *inName) \param[in] inName a C string that described the type we want \param[in] strategy add after current or as last child - \return the type node that was created or nullptr + \return the node that was created or nullptr \see add_new_widget_from_file(const char*, int) add_new_widget_from_user(Node*, int) add_new_widget_from_user(const char*, int) diff --git a/fluid/panels/settings_panel.cxx b/fluid/panels/settings_panel.cxx index 432613623..a57c797c2 100644 --- a/fluid/panels/settings_panel.cxx +++ b/fluid/panels/settings_panel.cxx @@ -1,7 +1,7 @@ // // Setting and shell dialogs for the Fast Light Tool Kit (FLTK). // -// Copyright 1998-2023 by Bill Spitzak and others. +// Copyright 1998-2025 by Bill Spitzak and others. // // This library is free software. Distribution and use rights are outlined in // the file "COPYING" which should have been included with this file. If this @@ -2666,17 +2666,14 @@ Fl_Double_Window* make_settings_window() { o->labelfont(1); o->labelsize(11); o->align(Fl_Align(FL_ALIGN_LEFT)); - o->hide(); } // Fl_Box* o - { // // Matt: disabled - w_proj_mergeback = new Fl_Check_Button(100, 283, 220, 20, "generate MergeBack data"); + { w_proj_mergeback = new Fl_Check_Button(100, 283, 220, 20, "generate MergeBack data"); w_proj_mergeback->tooltip("MergeBack is a feature under construction that allows changes in code files t" "o be merged back into the project file. Checking this option will generate add" "itional data in code and project files."); w_proj_mergeback->down_box(FL_DOWN_BOX); w_proj_mergeback->labelsize(11); w_proj_mergeback->callback((Fl_Callback*)cb_w_proj_mergeback); - w_proj_mergeback->hide(); } // Fl_Check_Button* w_proj_mergeback { Fl_Box* o = new Fl_Box(100, 530, 220, 10); o->hide(); diff --git a/fluid/panels/settings_panel.fl b/fluid/panels/settings_panel.fl index 6ea964dc2..dd1f61107 100644 --- a/fluid/panels/settings_panel.fl +++ b/fluid/panels/settings_panel.fl @@ -34,7 +34,7 @@ snap { comment {// // Setting and shell dialogs for the Fast Light Tool Kit (FLTK). // -// Copyright 1998-2023 by Bill Spitzak and others. +// Copyright 1998-2025 by Bill Spitzak and others. // // This library is free software. Distribution and use rights are outlined in // the file "COPYING" which should have been included with this file. If this @@ -191,7 +191,7 @@ Function {make_settings_window()} {open } { Fl_Window settings_window { label {FLUID Settings} open - xywh {392 362 340 580} type Double align 80 resizable size_range {340 580 0 0} visible + xywh {504 366 340 580} type Double align 80 resizable size_range {340 580 0 0} visible } { Fl_Tabs w_settings_tabs { callback {propagate_load(o, v);} open @@ -428,7 +428,7 @@ or just ".ext" to set extension.} } Fl_Box {} { label {Experimental: } - xywh {100 283 0 20} labelfont 1 labelsize 11 align 4 hide + xywh {100 283 0 20} labelfont 1 labelsize 11 align 4 } Fl_Check_Button w_proj_mergeback { label {generate MergeBack data} @@ -440,8 +440,7 @@ or just ".ext" to set extension.} Fluid.proj.write_mergeback_data = o->value(); } }} - comment {// Matt: disabled} - tooltip {MergeBack is a feature under construction that allows changes in code files to be merged back into the project file. Checking this option will generate additional data in code and project files.} xywh {100 283 220 20} down_box DOWN_BOX labelsize 11 hide + tooltip {MergeBack is a feature under construction that allows changes in code files to be merged back into the project file. Checking this option will generate additional data in code and project files.} xywh {100 283 220 20} down_box DOWN_BOX labelsize 11 } Fl_Box {} { xywh {100 530 220 10} hide resizable diff --git a/fluid/panels/settings_panel.h b/fluid/panels/settings_panel.h index 69482d307..45c1457a6 100644 --- a/fluid/panels/settings_panel.h +++ b/fluid/panels/settings_panel.h @@ -1,7 +1,7 @@ // // Setting and shell dialogs for the Fast Light Tool Kit (FLTK). // -// Copyright 1998-2023 by Bill Spitzak and others. +// Copyright 1998-2025 by Bill Spitzak and others. // // This library is free software. Distribution and use rights are outlined in // the file "COPYING" which should have been included with this file. If this diff --git a/fluid/proj/mergeback.cxx b/fluid/proj/mergeback.cxx index 12f272638..53ea93244 100644 --- a/fluid/proj/mergeback.cxx +++ b/fluid/proj/mergeback.cxx @@ -14,9 +14,6 @@ // https://www.fltk.org/bugs.php // -#if 0 -// Matt: disabled - #include "proj/mergeback.h" #include "Fluid.h" @@ -38,6 +35,9 @@ extern void propagate_load(Fl_Group*, void*); extern void load_panel(); extern void redraw_browser(); +using namespace fld; +using namespace fld::proj; + // TODO: add application user setting to control mergeback // [] new projects default to mergeback // [] check mergeback when loading project @@ -76,8 +76,8 @@ extern void redraw_browser(); Callers can set different task. FD_MERGEBACK_ANALYSE checks if there are any modifications in the code file and returns -1 if there was an error, or a bit field where bit 0 is set if internal structures were modified, bit 1 if - code was changed, and bit 2 if modified blocks were found, but no Type node. - Bit 3 is set, if code was changed in the code file *and* the project. + code was changed, and bit 2 if modified blocks were found, but no node with the + same UID. Bit 3 is set, if code was changed in the code file *and* the project. FD_MERGEBACK_INTERACTIVE checks for changes and presents a status dialog box to the user if there were conflicting changes or if a mergeback is possible, @@ -101,9 +101,9 @@ extern void redraw_browser(); \return -2 if no code file was found \return see above */ -int merge_back(const std::string &s, const std::string &p, int task) { - if (Fluid.proj.write_mergeback_data) { - Fd_Mergeback mergeback; +int merge_back(Project &proj, const std::string &s, const std::string &p, Mergeback::Task task) { + if (proj.write_mergeback_data) { + Mergeback mergeback(proj); return mergeback.merge_back(s, p, task); } else { // nothing to be done if the mergeback option is disabled in the project @@ -112,7 +112,8 @@ int merge_back(const std::string &s, const std::string &p, int task) { } /** Allocate and initialize MergeBack class. */ -Fd_Mergeback::Fd_Mergeback() : +Mergeback::Mergeback(Project &proj) +: proj_(proj), code(nullptr), line_no(0), tag_error(0), @@ -124,7 +125,7 @@ Fd_Mergeback::Fd_Mergeback() : } /** Release allocated resources. */ -Fd_Mergeback::~Fd_Mergeback() +Mergeback::~Mergeback() { if (code) ::fclose(code); } @@ -132,7 +133,7 @@ Fd_Mergeback::~Fd_Mergeback() /** Remove the first two spaces at every line start. \param[inout] s block of C code */ -void Fd_Mergeback::unindent(char *s) { +void Mergeback::unindent(char *s) { char *d = s; bool line_start = true; while (*s) { @@ -154,7 +155,7 @@ void Fd_Mergeback::unindent(char *s) { \param[in] end end of text within the file \return a string holding the text that was found in the file */ -std::string Fd_Mergeback::read_and_unindent_block(long start, long end) { +std::string Mergeback::read_and_unindent_block(long start, long end) { long bsize = end-start; long here = ::ftell(code); ::fseek(code, start, SEEK_SET); @@ -178,7 +179,7 @@ std::string Fd_Mergeback::read_and_unindent_block(long start, long end) { \return -1 if the user wants to cancel or an error occurred or an issue was presented (message or choice dialog was shown) */ -int Fd_Mergeback::ask_user_to_merge(const std::string &code_filename, const std::string &proj_filename) { +int Mergeback::ask_user_to_merge(const std::string &code_filename, const std::string &proj_filename) { if (tag_error) { fl_message("Comparing\n \"%s\"\nto\n \"%s\"\n\n" "MergeBack found an error in line %d while reading tags\n" @@ -192,7 +193,7 @@ int Fd_Mergeback::ask_user_to_merge(const std::string &code_filename, const std: if (num_changed_structure && !num_changed_code) { fl_message("Comparing\n \"%1$s\"\nto\n \"%2$s\"\n\n" "MergeBack found %3$d modifications in the project structure\n" - "of the source code. These kind of changes can no be\n" + "of the source code. These kind of changes can not be\n" "merged back and will be lost when the source code is\n" "generated again from the open project.", code_filename.c_str(), proj_filename.c_str(), num_changed_structure); @@ -205,14 +206,14 @@ int Fd_Mergeback::ask_user_to_merge(const std::string &code_filename, const std: "changed in the project. Merging will override changes in\n" "the project with changes from the source code file."; if (num_uid_not_found) - msg += "\n\nWARNING: for %4$d of these modifications no Type node\n" - "can be found and these modification can't be merged back."; + msg += "\n\nWARNING: no Node can be found for %4$d of these\n" + "modifications and they can not be merged back."; if (!num_possible_override && !num_uid_not_found) msg += "\nMerging these changes back appears to be safe."; if (num_changed_structure) msg += "\n\nWARNING: %5$d modifications were found in the project\n" - "structure. These kind of changes can no be merged back\n" + "structure. These kind of changes can not be merged back\n" "and will be lost when the source code is generated again\n" "from the open project."; @@ -238,8 +239,8 @@ int Fd_Mergeback::ask_user_to_merge(const std::string &code_filename, const std: /** Analyse the block and its corresponding widget callback. Return findings in num_changed_code, num_changed_code, and num_uid_not_found. */ -void Fd_Mergeback::analyse_callback(unsigned long code_crc, unsigned long tag_crc, int uid) { - Node *tp = Node::find_by_uid(uid); +void Mergeback::analyse_callback(unsigned long code_crc, unsigned long tag_crc, int uid) { + Node *tp = proj_.tree.find_by_uid(uid); if (tp && tp->is_true_widget()) { std::string cb = tp->callback(); cb += "\n"; unsigned long project_crc = fld::io::Code_Writer::block_crc(cb.c_str()); @@ -260,9 +261,9 @@ void Fd_Mergeback::analyse_callback(unsigned long code_crc, unsigned long tag_cr /** Analyse the block and its corresponding Code Type. Return findings in num_changed_code, num_changed_code, and num_uid_not_found. */ -void Fd_Mergeback::analyse_code(unsigned long code_crc, unsigned long tag_crc, int uid) { - Node *tp = Node::find_by_uid(uid); - if (tp && tp->is_a(ID_Code)) { +void Mergeback::analyse_code(unsigned long code_crc, unsigned long tag_crc, int uid) { + Node *tp = proj_.tree.find_by_uid(uid); + if (tp && tp->is_a(Type::Code)) { std::string code = tp->name(); code += "\n"; unsigned long project_crc = fld::io::Code_Writer::block_crc(code.c_str()); // check if the code and project crc are the same, so this modification was already applied @@ -279,8 +280,114 @@ void Fd_Mergeback::analyse_code(unsigned long code_crc, unsigned long tag_crc, i } } +/** + Decode a 22 bytes string of three distinct characters into a 32 bit integer. + \param[in] text at least 22 characters '-', '~', and '=' + \return the decoded integer + */ +uint32_t Mergeback::decode_trichar32(const char *text) { + uint32_t word = 0; + for (int i=30; i>=0; i-=3) { + char a = *text++; + if (a==0) break; + char b = *text++; + if (b==0) break; + // "--", "-~", "~-", "~~", "-=", "=-", "~=", "=~" + uint32_t oct = 0; + if (a=='-') { + if (b=='~') oct = 1; + else if (b=='-') oct = 0; + else if (b=='=') oct = 4; + } else if (a=='~') { + if (b=='~') oct = 3; + else if (b=='-') oct = 2; + else if (b=='=') oct = 6; + } else if (a=='=') { + if (b=='~') oct = 7; + else if (b=='-') oct = 5; + } + word |= (oct<=0; i-=3) fputs(lut[(value>>i)&7], out); +} + +/** + Check if a line contains the special MergeBack tag + \param[in] line A line of NUL terminated text. + \return a pointer to the character after the tag, or nullptr if not found + */ +const char *Mergeback::find_mergeback_tag(const char *line) { + const char *tag = strstr(line, "//fl "); + if (tag) return tag + strlen("//fl "); + return nullptr; +} + +/** + Read all data from a MergeBack tag line. + \param[in] tag start of tag string after "//fl " but before "▲" or "▼". + \param[out] prev_type Return type information here, see FD_TAG_GENERIC etc. + \param[out] uid Unique ID of the node that generated the tag + \param[out] crc The CRC32 of the previous block. + \return true if the tag could be read + */ +bool Mergeback::read_tag(const char *tag, Tag *prev_type, uint16_t *uid, uint32_t *crc) { + // Tag starts with decoration: + if (*tag==' ') tag += 1; + if (strncmp(tag, "▲", 3)==0) tag += 3; // E2 96 B2 + if (*tag=='/') tag += 1; + if (strncmp(tag, "▼", 3)==0) tag += 3; // E2 96 BC + if (*tag==' ') tag += 1; + uint32_t w1 = decode_trichar32(tag); // Read the first word + for (int i=0; i<32; i++) if (*tag++ == 0) return false; + uint32_t w2 = decode_trichar32(tag); // Read the second word + // Return the decoded values + *prev_type = static_cast((w1>>16) & 0xff); + *uid = (w1 & 0xffff); + *crc = w2; + return true; +} + +void Mergeback::print_tag(FILE *out, Tag prev_type, Tag next_type, uint16_t uid, uint32_t crc) { + static const char *tag_lut[] = { "----------", "-- code --", " callback ", " callback " }; + fputs("//fl ", out); // Distinct start of tag using utf8 + // Indicate that the text above can be edited + if (prev_type != Tag::GENERIC) fputs("▲", out); + if (prev_type != Tag::GENERIC && next_type != Tag::GENERIC) fputc('/', out); + // Indicate that the text below can be edited + if (next_type != Tag::GENERIC) fputs("▼", out); + fputc(' ', out); + // Write the first 32 bit word as an encoded divider line + uint32_t pt = static_cast(prev_type); + uint32_t nt = static_cast(next_type); + uint32_t word = (0<<24) | (pt<<16) | (uid); // top 8 bit available for encoding type + print_trichar32(out, word); + // Write a string indicating the type of editable text + if ( next_type != Tag::GENERIC) { + fputs(tag_lut[nt], out); + } else if (prev_type != Tag::GENERIC) { + fputs(tag_lut[nt], out); + } + // Write the second 32 bit word as an encoded divider line + print_trichar32(out, crc); + // Repeat the intor pattern + fputc(' ', out); + if (prev_type != Tag::GENERIC) fputs("▲", out); + if (prev_type != Tag::GENERIC && next_type != Tag::GENERIC) fputc('/', out); + if (next_type != Tag::GENERIC) fputs("▼", out); + fputs(" fl//\n", out); +} -/** Analyse the code file and return findings in class member variables. +/** Analyze the code file and return findings in class member variables. The code file must be open for reading already. @@ -297,7 +404,7 @@ void Fd_Mergeback::analyse_code(unsigned long code_crc, unsigned long tag_crc, i \return -1 if reading a tag failed, otherwise 0 */ -int Fd_Mergeback::analyse() { +int Mergeback::analyse() { // initialize local variables unsigned long code_crc = 0; bool line_start = true; @@ -318,28 +425,33 @@ int Fd_Mergeback::analyse() { // get the next line until end of file if (fgets(line, 1023, code)==0) break; line_no++; - const char *tag = strstr(line, "//~fl~"); + const char *tag = find_mergeback_tag(line); if (!tag) { // if this line has no tag, add the contents to the CRC and continue code_crc = fld::io::Code_Writer::block_crc(line, -1, code_crc, &line_start); } else { // if this line has a tag, read all tag data - int tag_type = -1, uid = 0; - unsigned long tag_crc = 0; - int n = sscanf(tag, "//~fl~%d~%04x~%08lx~~", &tag_type, &uid, &tag_crc); - if (n!=3 || tag_type<0 || tag_type>FD_TAG_LAST ) { tag_error = 1; return -1; } + Tag tag_type = Tag::UNUSED_; + uint16_t uid = 0; + uint32_t tag_crc = 0; + bool tag_ok = read_tag(tag, &tag_type, &uid, &tag_crc); + if (!tag_ok || tag_type==Tag::UNUSED_ ) { + tag_error = 1; + return -1; + } if (code_crc != tag_crc) { switch (tag_type) { - case FD_TAG_GENERIC: + case Tag::GENERIC: num_changed_structure++; break; - case FD_TAG_MENU_CALLBACK: - case FD_TAG_WIDGET_CALLBACK: + case Tag::MENU_CALLBACK: + case Tag::WIDGET_CALLBACK: analyse_callback(code_crc, tag_crc, uid); break; - case FD_TAG_CODE: + case Tag::CODE: analyse_code(code_crc, tag_crc, uid); break; + default: break; } } // reset everything for the next block @@ -353,8 +465,8 @@ int Fd_Mergeback::analyse() { /** Apply callback mergebacks from the code file to the project. \return 1 if the project changed */ -int Fd_Mergeback::apply_callback(long block_end, long block_start, unsigned long code_crc, int uid) { - Node *tp = Node::find_by_uid(uid); +int Mergeback::apply_callback(long block_end, long block_start, unsigned long code_crc, int uid) { + Node *tp = proj_.tree.find_by_uid(uid); if (tp && tp->is_true_widget()) { std::string cb = tp->callback(); cb += "\n"; unsigned long project_crc = fld::io::Code_Writer::block_crc(cb.c_str()); @@ -369,9 +481,9 @@ int Fd_Mergeback::apply_callback(long block_end, long block_start, unsigned long /** Apply callback mergebacks from the code file to the project. \return 1 if the project changed */ -int Fd_Mergeback::apply_code(long block_end, long block_start, unsigned long code_crc, int uid) { - Node *tp = Node::find_by_uid(uid); - if (tp && tp->is_a(ID_Code)) { +int Mergeback::apply_code(long block_end, long block_start, unsigned long code_crc, int uid) { + Node *tp = proj_.tree.find_by_uid(uid); + if (tp && tp->is_a(Type::Code)) { std::string cb = tp->name(); cb += "\n"; unsigned long project_crc = fld::io::Code_Writer::block_crc(cb.c_str()); if (project_crc!=code_crc) { @@ -386,7 +498,7 @@ int Fd_Mergeback::apply_code(long block_end, long block_start, unsigned long cod The code file must be open for reading already. \return -1 if reading a tag failed, 0 if nothing changed, 1 if the project changed */ -int Fd_Mergeback::apply() { +int Mergeback::apply() { // initialize local variables unsigned long code_crc = 0; bool line_start = true; @@ -406,21 +518,25 @@ int Fd_Mergeback::apply() { // get the next line until end of file if (fgets(line, 1023, code)==0) break; line_no++; - const char *tag = strstr(line, "//~fl~"); + const char *tag = find_mergeback_tag(line); if (!tag) { // if this line has no tag, add the contents to the CRC and continue code_crc = fld::io::Code_Writer::block_crc(line, -1, code_crc, &line_start); block_end = ::ftell(code); } else { // if this line has a tag, read all tag data - int tag_type = -1, uid = 0; - unsigned long tag_crc = 0; - int n = sscanf(tag, "//~fl~%d~%04x~%08lx~~", &tag_type, &uid, &tag_crc); - if (n!=3 || tag_type<0 || tag_type>FD_TAG_LAST ) { tag_error = 1; return -1; } + Tag tag_type = Tag::UNUSED_; + uint16_t uid = 0; + uint32_t tag_crc = 0; + bool tag_ok = read_tag(tag, &tag_type, &uid, &tag_crc); + if (!tag_ok || tag_type==Tag::UNUSED_ ) { + tag_error = 1; + return -1; + } if (code_crc != tag_crc) { - if (tag_type==FD_TAG_MENU_CALLBACK || tag_type==FD_TAG_WIDGET_CALLBACK) { + if (tag_type==Tag::MENU_CALLBACK || tag_type==Tag::WIDGET_CALLBACK) { changed |= apply_callback(block_end, block_start, code_crc, uid); - } else if (tag_type==FD_TAG_CODE) { + } else if (tag_type==Tag::CODE) { changed |= apply_code(block_end, block_start, code_crc, uid); } } @@ -441,12 +557,12 @@ int Fd_Mergeback::apply() { \return -2 if no code file was found \return See more at ::merge_back(const std::string &s, int task). */ -int Fd_Mergeback::merge_back(const std::string &s, const std::string &p, int task) { +int Mergeback::merge_back(const std::string &s, const std::string &p, Task task) { int ret = 0; code = fl_fopen(s.c_str(), "rb"); if (!code) return -2; do { // no actual loop, just make sure we close the code file - if (task == FD_MERGEBACK_ANALYSE) { + if (task == Task::ANALYSE) { analyse(); if (tag_error) {ret = -1; break; } if (num_changed_structure) ret |= 1; @@ -455,14 +571,14 @@ int Fd_Mergeback::merge_back(const std::string &s, const std::string &p, int tas if (num_possible_override) ret |= 8; break; } - if (task == FD_MERGEBACK_INTERACTIVE) { + if (task == Task::INTERACTIVE) { analyse(); ret = ask_user_to_merge(s, p); if (ret != 1) return ret; - task = FD_MERGEBACK_APPLY; // fall through + task = Task::APPLY; // fall through } - if (task == FD_MERGEBACK_APPLY_IF_SAFE) { + if (task == Task::APPLY_IF_SAFE) { analyse(); if (tag_error || num_changed_structure || num_possible_override) { ret = -1; @@ -472,12 +588,12 @@ int Fd_Mergeback::merge_back(const std::string &s, const std::string &p, int tas ret = 0; break; } - task = FD_MERGEBACK_APPLY; // fall through + task = Task::APPLY; // fall through } - if (task == FD_MERGEBACK_APPLY) { + if (task == Task::APPLY) { ret = apply(); if (ret == 1) { - Fluid.proj.set_modflag(1); + proj_.set_modflag(1); redraw_browser(); load_panel(); } @@ -489,37 +605,55 @@ int Fd_Mergeback::merge_back(const std::string &s, const std::string &p, int tas return ret; } -#if 0 -// Matt: disabled /** - Merge the possibly modified content of code files back into the project. + \brief Merges back code files with the project file. + + This function flushes text widgets, checks if the project filename and + mergeback data are available, and then attempts to merge back the code + files with the project file. If MergeBack is not enabled, it displays + a message to the user. It handles both batch and interactive modes. + + \return int - Returns 1 if the project filename is not available, + 0 if MergeBack is not enabled, + or the result of the merge_back function. */ -int mergeback_code_files() +int mergeback_code_files(Project &proj) { - flush_text_widgets(); - if (!filename) return 1; - if (!Fluid.proj.write_mergeback_data) { + Fluid.flush_text_widgets(); + if (!proj.proj_filename) return 1; + if (!proj.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 = Fluid.proj.projectfile_path() + Fluid.proj.projectfile_name(); + std::string proj_filename = proj.projectfile_path() + proj.projectfile_name(); std::string code_filename; #if 1 if (!Fluid.batch_mode) { + // Depending on the workflow in interactive mode, an external copy of + // Fluid may have written the source code elswhere (e.g. in a CMake setup). + // Fluid tries to keep track of the last write location of a source file + // matching a project, and uses that location instead. + // TODO: this is not working as expected yet. 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(); + int i, n = (int)proj_filename.size(); for (i=0; i0) { + // update the project to reflect the changes + proj.set_modflag(1); + redraw_browser(); + load_panel(); + } if (!Fluid.batch_mode) proj.leave_project_dir(); if (c==0) fl_message("Comparing\n \"%s\"\nto\n \"%s\"\n\n" @@ -531,9 +665,6 @@ int mergeback_code_files() } void mergeback_cb(Fl_Widget *, void *) { - mergeback_code_files(); + mergeback_code_files(Fluid.proj); } -#endif - -#endif diff --git a/fluid/proj/mergeback.h b/fluid/proj/mergeback.h index 2986d379c..f395717f8 100644 --- a/fluid/proj/mergeback.h +++ b/fluid/proj/mergeback.h @@ -14,34 +14,37 @@ // https://www.fltk.org/bugs.php // -// Matt: disabled -#if 0 - -#ifndef _FLUID_MERGEBACK_H -#define _FLUID_MERGEBACK_H +#ifndef FLUID_PROJ_MERGEBACK_H +#define FLUID_PROJ_MERGEBACK_H #include +#include #include #include -const int FD_TAG_GENERIC = 0; -const int FD_TAG_CODE = 1; -const int FD_TAG_MENU_CALLBACK = 2; -const int FD_TAG_WIDGET_CALLBACK = 3; -const int FD_TAG_LAST = 3; +namespace fld { + +class Project; + +namespace proj { -const int FD_MERGEBACK_ANALYSE = 0; -const int FD_MERGEBACK_INTERACTIVE = 1; -const int FD_MERGEBACK_APPLY = 2; -const int FD_MERGEBACK_APPLY_IF_SAFE = 3; /** Class that implements the MergeBack functionality. \see merge_back(const std::string &s, int task) */ -class Fd_Mergeback +class Mergeback { + public: + enum class Tag { + GENERIC = 0, CODE, MENU_CALLBACK, WIDGET_CALLBACK, UNUSED_ + }; + enum class Task { + ANALYSE = 0, INTERACTIVE, APPLY, APPLY_IF_SAFE = 3 + }; protected: + /// Apply mergeback for this project. + Project &proj_; /// Pointer to the C++ code file. FILE *code; /// Current line number in the C++ code file. @@ -64,18 +67,26 @@ protected: int apply_callback(long block_end, long block_start, unsigned long code_crc, int uid); int apply_code(long block_end, long block_start, unsigned long code_crc, int uid); + static uint32_t decode_trichar32(const char *text); + static void print_trichar32(FILE *out, uint32_t value); + + static const char *find_mergeback_tag(const char *line); + static bool read_tag(const char *tag, Tag *prev_type, uint16_t *uid, uint32_t *crc); + public: - Fd_Mergeback(); - ~Fd_Mergeback(); - int merge_back(const std::string &s, const std::string &p, int task); + Mergeback(Project &proj); + ~Mergeback(); + int merge_back(const std::string &s, const std::string &p, Task task); int ask_user_to_merge(const std::string &s, const std::string &p); int analyse(); int apply(); + static void print_tag(FILE *out, Tag prev_type, Tag next_type, uint16_t uid, uint32_t crc); }; extern int merge_back(const std::string &s, const std::string &p, int task); +} // namespace proj +} // namespace fld -#endif // _FLUID_MERGEBACK_H +#endif // FLUID_PROJ_MERGEBACK_H -#endif -- cgit v1.2.3