Reactivating Mergeback functionality. (#1226)

Reactivated code.
Various fixes.
New documentation.
This commit is contained in:
Matthias Melcher
2025-04-15 14:47:24 +02:00
committed by GitHub
parent 63db80c078
commit fc91880faf
19 changed files with 368 additions and 126 deletions
+3 -1
View File
@@ -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]},
+1
View File
@@ -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
+2
View File
@@ -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
@@ -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.
*/
+9 -6
View File
@@ -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);
}
+3 -1
View File
@@ -17,6 +17,8 @@
#ifndef FLUID_IO_CODE_WRITER_H
#define FLUID_IO_CODE_WRITER_H
#include "proj/mergeback.h"
#include <FL/fl_attr.h>
#include <stdarg.h>
@@ -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);
};
+5 -2
View File
@@ -35,6 +35,9 @@
#include <zlib.h>
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());
}
/**
+5 -2
View File
@@ -44,6 +44,9 @@
#include <stdio.h>
#include <stdlib.h>
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
+6 -5
View File
@@ -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;
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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
+5 -2
View File
@@ -46,6 +46,9 @@
#undef max
#include <algorithm>
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);
+2 -2
View File
@@ -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)
+2 -5
View File
@@ -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();
+4 -5
View File
@@ -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
+1 -1
View File
@@ -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
+201 -70
View File
@@ -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<<i);
}
return word;
}
/** Analyse the code file and return findings in class member variables.
/**
Print a 32 value so it looks like a divider line.
\param[in] out Write to this file.
\param[in] value 32 bit integer value to encode.
*/
void Mergeback::print_trichar32(FILE *out, uint32_t value) {
static const char *lut[] = { "--", "-~", "~-", "~~", "-=", "=-", "~=", "=~" };
for (int i=30; i>=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<Tag>((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<uint32_t>(prev_type);
uint32_t nt = static_cast<uint32_t>(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);
}
/** 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; i<n; i++) if (proj_filename[i]=='\\') proj_filename[i] = '/';
preferences_get(path, "code", code_filename, "");
path.get("code", code_filename, "");
}
#endif
if (code_filename.empty())
code_filename = Fluid.proj.codefile_path() + Fluid.proj.codefile_name();
code_filename = proj.codefile_path() + proj.codefile_name();
if (!Fluid.batch_mode) proj.enter_project_dir();
int c = merge_back(code_filename, proj_filename, FD_MERGEBACK_INTERACTIVE);
int c = merge_back(proj, code_filename, proj_filename, Mergeback::Task::INTERACTIVE);
if (c>0) {
// 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
+31 -20
View File
@@ -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 <FL/fl_attr.h>
#include <stdint.h>
#include <stdio.h>
#include <string>
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
+2 -2
View File
@@ -187,7 +187,7 @@ Fl_Input::paste_menu_text = gettext("Paste");} {}
xywh {0 0 100 20}
}
MenuItem {} {
label {flip flops} selected
label {flip flops}
xywh {0 0 100 20}
}
MenuItem {} {
@@ -203,7 +203,7 @@ Fl_Input::paste_menu_text = gettext("Paste");} {}
xywh {35 112 100 24} type Radio down_box ROUND_DOWN_BOX
}
Fl_Round_Button wRight {
label {right side}
label {right side} selected
xywh {35 136 100 24} type Radio down_box ROUND_DOWN_BOX
}
Fl_Box {} {