diff options
Diffstat (limited to 'fluid/app/undo.cxx')
| -rw-r--r-- | fluid/app/undo.cxx | 263 |
1 files changed, 263 insertions, 0 deletions
diff --git a/fluid/app/undo.cxx b/fluid/app/undo.cxx new file mode 100644 index 000000000..eb5001468 --- /dev/null +++ b/fluid/app/undo.cxx @@ -0,0 +1,263 @@ +// +// FLUID undo support for the Fast Light Tool Kit (FLTK). +// +// Copyright 1998-2025 by Bill Spitzak and others. +// +// This library is free software. Distribution and use rights are outlined in +// the file "COPYING" which should have been included with this file. If this +// file is missing or damaged, see the license at: +// +// https://www.fltk.org/COPYING.php +// +// Please see the following page on how to report bugs and issues: +// +// https://www.fltk.org/bugs.php +// + +#include "app/undo.h" + +#include "app/fluid.h" +#include "io/file.h" +#include "nodes/Fl_Type.h" +#include "nodes/Fl_Widget_Type.h" +#include "widgets/widget_browser.h" + +#include <FL/Fl.H> +#include <FL/Fl_Window.H> +#include <FL/Fl_Preferences.H> +#include <FL/Fl_Menu_Bar.H> +#include <FL/fl_ask.H> +#include "tools/fluid_filename.h" +#include "../src/flstring.h" + +#if defined(_WIN32) && !defined(__CYGWIN__) +# include <io.h> +# include <windows.h> +# define getpid (int)GetCurrentProcessId +#else +# include <unistd.h> +#endif // _WIN32 && !__CYGWIN__ + + +// +// This file implements an undo system using temporary files; ideally +// we'd like to do this in memory, however the current data structures +// and design aren't well-suited... Instead, we save and restore +// checkpoint files. +// + +extern Fl_Window* the_panel; + +int undo_current = 0; // Current undo level in buffer +int undo_last = 0; // Last undo level in buffer +int undo_max = 0; // Maximum undo level used +int undo_save = -1; // Last undo level that was saved +static int undo_paused = 0; // Undo checkpointing paused? +int undo_once_type = 0; // Suspend further undos of the same type + + +// Return the undo filename. +// The filename is constructed in a static internal buffer and +// this buffer is overwritten by every call of this function. +// The return value is a pointer to this internal string. +static char *undo_filename(int level) { + static char undo_path[FL_PATH_MAX] = ""; // Undo path + static unsigned int undo_path_len = 0; // length w/o filename + + if (!undo_path_len) { + fluid_prefs.getUserdataPath(undo_path, sizeof(undo_path)); + undo_path_len = (unsigned int)strlen(undo_path); + } + + // append filename: "undo_PID_LEVEL.fl" + snprintf(undo_path + undo_path_len, + sizeof(undo_path) - undo_path_len - 1, + "undo_%d_%d.fl", getpid(), level); + return undo_path; +} + + +// Redo menu callback +void redo_cb(Fl_Widget *, void *) { + // int undo_item = main_menubar->find_index(undo_cb); + // int redo_item = main_menubar->find_index(redo_cb); + undo_once_type = 0; + + if (undo_current >= undo_last) { + fl_beep(); + return; + } + + undo_suspend(); + if (widget_browser) { + widget_browser->save_scroll_position(); + widget_browser->new_list(); + } + int reload_panel = (the_panel && the_panel->visible()); + if (!read_file(undo_filename(undo_current + 1), 0)) { + // Unable to read checkpoint file, don't redo... + widget_browser->rebuild(); + g_project.update_settings_dialog(); + undo_resume(); + return; + } + if (reload_panel) { + for (Fl_Type *t = Fl_Type::first; t; t=t->next) { + if (t->is_widget() && t->selected) + t->open(); + } + } + if (widget_browser) widget_browser->restore_scroll_position(); + + undo_current ++; + + // Update modified flag... + set_modflag(undo_current != undo_save); + widget_browser->rebuild(); + g_project.update_settings_dialog(); + + // Update undo/redo menu items... + // if (undo_current >= undo_last) Main_Menu[redo_item].deactivate(); + // Main_Menu[undo_item].activate(); + undo_resume(); +} + +// Undo menu callback +void undo_cb(Fl_Widget *, void *) { + // int undo_item = main_menubar->find_index(undo_cb); + // int redo_item = main_menubar->find_index(redo_cb); + undo_once_type = 0; + + if (undo_current <= 0) { + fl_beep(); + return; + } + + if (undo_current == undo_last) { + write_file(undo_filename(undo_current)); + } + + undo_suspend(); + // Undo first deletes all widgets which resets the widget_tree browser. + // Save the current scroll position, so we don't scroll back to 0 at undo. + // TODO: make the scroll position part of the .fl project file + if (widget_browser) { + widget_browser->save_scroll_position(); + widget_browser->new_list(); + } + int reload_panel = (the_panel && the_panel->visible()); + if (!read_file(undo_filename(undo_current - 1), 0)) { + // Unable to read checkpoint file, don't undo... + widget_browser->rebuild(); + g_project.update_settings_dialog(); + set_modflag(0, 0); + undo_resume(); + return; + } + if (reload_panel) { + for (Fl_Type *t = Fl_Type::first; t; t=t->next) { + if (t->is_widget() && t->selected) { + t->open(); + break; + } + } + } + // Restore old browser position. + // Ideally, we would save the browser position inside the undo file. + if (widget_browser) widget_browser->restore_scroll_position(); + + undo_current --; + + // Update modified flag... + set_modflag(undo_current != undo_save); + + // Update undo/redo menu items... + // if (undo_current <= 0) Main_Menu[undo_item].deactivate(); + // Main_Menu[redo_item].activate(); + widget_browser->rebuild(); + g_project.update_settings_dialog(); + undo_resume(); +} + +/** + \param[in] type set a new type, or set to 0 to clear the once_type without setting a checkpoint + \return 1 if the checkpoint was set, 0 if this is a repeating event + */ +int undo_checkpoint_once(int type) { + if (type == 0) { + undo_once_type = 0; + return 0; + } + if (undo_paused) return 0; + if (undo_once_type != type) { + undo_checkpoint(); + undo_once_type = type; + return 1; + } else { + // do not add more checkpoints for the same undo type + return 0; + } +} + +// Save current file to undo buffer +void undo_checkpoint() { + // printf("undo_checkpoint(): undo_current=%d, undo_paused=%d, modflag=%d\n", + // undo_current, undo_paused, modflag); + + // Don't checkpoint if undo_suspend() has been called... + if (undo_paused) return; + + // int undo_item = main_menubar->find_index(undo_cb); + // int redo_item = main_menubar->find_index(redo_cb); + undo_once_type = 0; + + // Save the current UI to a checkpoint file... + const char *filename = undo_filename(undo_current); + if (!write_file(filename)) { + // Don't attempt to do undo stuff if we can't write a checkpoint file... + perror(filename); + return; + } + + // Update the saved level... + if (modflag && undo_current <= undo_save) undo_save = -1; + else if (!modflag) undo_save = undo_current; + + // Update the current undo level... + undo_current ++; + undo_last = undo_current; + if (undo_current > undo_max) undo_max = undo_current; + + // Enable the Undo and disable the Redo menu items... + // Main_Menu[undo_item].activate(); + // Main_Menu[redo_item].deactivate(); +} + +// Clear undo buffer +void undo_clear() { + // int undo_item = main_menubar->find_index(undo_cb); + // int redo_item = main_menubar->find_index(redo_cb); + // Remove old checkpoint files... + for (int i = 0; i <= undo_max; i ++) { + fl_unlink(undo_filename(i)); + } + + // Reset current, last, and save indices... + undo_current = undo_last = undo_max = 0; + if (modflag) undo_save = -1; + else undo_save = 0; + + // Disable the Undo and Redo menu items... + // Main_Menu[undo_item].deactivate(); + // Main_Menu[redo_item].deactivate(); +} + +// Resume undo checkpoints +void undo_resume() { + undo_paused--; +} + +// Suspend undo checkpoints +void undo_suspend() { + undo_paused++; +} |
