From 90ac16bd59fe0c594566bcffee55796a9b31f65e Mon Sep 17 00:00:00 2001 From: Mike Nelson Date: Fri, 15 May 2026 12:25:12 -0500 Subject: [PATCH 1/3] remove legacy undo system from QtFRED --- code/missioneditor/missionsave.cpp | 5 +- code/missioneditor/missionsave.h | 19 +-- qtfred/src/mission/Editor.cpp | 122 +----------------- qtfred/src/mission/Editor.h | 31 ----- qtfred/src/mission/EditorViewport.cpp | 5 - .../src/mission/dialogs/SceneBrowserModel.cpp | 3 - qtfred/src/ui/FredView.cpp | 31 +---- qtfred/src/ui/FredView.h | 2 - .../src/ui/dialogs/AsteroidEditorDialog.cpp | 1 - .../src/ui/dialogs/BackgroundEditorDialog.cpp | 1 - .../src/ui/dialogs/BriefingEditorDialog.cpp | 1 - .../src/ui/dialogs/CommandBriefingDialog.cpp | 1 - qtfred/src/ui/dialogs/DebriefingDialog.cpp | 1 - qtfred/src/ui/dialogs/MissionEventsDialog.cpp | 1 - qtfred/src/ui/dialogs/MissionGoalsDialog.cpp | 1 - qtfred/src/ui/dialogs/MissionSpecDialog.cpp | 1 - .../ui/dialogs/ObjectOrientEditorDialog.cpp | 1 - .../dialogs/ShipEditor/ShipEditorDialog.cpp | 1 - qtfred/src/ui/dialogs/TeamLoadoutDialog.cpp | 1 - qtfred/src/ui/dialogs/WingEditorDialog.cpp | 1 - qtfred/src/ui/widgets/renderwidget.cpp | 1 - qtfred/ui/FredView.ui | 16 --- 22 files changed, 13 insertions(+), 234 deletions(-) diff --git a/code/missioneditor/missionsave.cpp b/code/missioneditor/missionsave.cpp index d1cbcb99225..7c27572dd69 100644 --- a/code/missioneditor/missionsave.cpp +++ b/code/missioneditor/missionsave.cpp @@ -81,6 +81,7 @@ bypass_comment(expected_version " " property); \ } while (false) + int Fred_mission_save::autosave_mission_file(char* pathname) { char backup_name[256], name2[256]; @@ -89,9 +90,9 @@ int Fred_mission_save::autosave_mission_file(char* pathname) auto len = strlen(pathname); strcpy_s(backup_name, pathname); strcpy_s(name2, pathname); - sprintf(backup_name + len, ".%.3d", save_config.mission_backup_depth); + sprintf(backup_name + len, ".%.3d", MISSION_BACKUP_DEPTH); cf_delete(backup_name, CF_TYPE_MISSIONS); - for (i = save_config.mission_backup_depth; i > 1; i--) { + for (i = MISSION_BACKUP_DEPTH; i > 1; i--) { sprintf(backup_name + len, ".%.3d", i - 1); sprintf(name2 + len, ".%.3d", i); cf_rename(backup_name, name2, CF_TYPE_MISSIONS); diff --git a/code/missioneditor/missionsave.h b/code/missioneditor/missionsave.h index b3e183c53d0..2df2b9adc30 100644 --- a/code/missioneditor/missionsave.h +++ b/code/missioneditor/missionsave.h @@ -9,7 +9,7 @@ #include "ship/shipfx.h" #define MISSION_BACKUP_NAME "Backup" -inline constexpr int MISSION_BACKUP_DEPTH = 9; // TODO make user configurable in QtFRED's future settings menu +inline constexpr int MISSION_BACKUP_DEPTH = 9; struct sexp_container; @@ -41,9 +41,6 @@ struct FredSaveConfig { MissionFormat save_format = MissionFormat::STANDARD; - int mission_backup_depth = MISSION_BACKUP_DEPTH; // TODO make user configurable - SCP_string mission_backup_name = MISSION_BACKUP_NAME; // TODO make user configurable - MissionTemplateInfo template_info; }; @@ -66,21 +63,11 @@ class Fred_mission_save { void set_fred_alt_names(const char (*names)[NAME_LENGTH + 1]) { save_config.fred_alt_names = names; } void set_fred_callsigns(const char (*callsigns)[NAME_LENGTH + 1]) { save_config.fred_callsigns = callsigns; } void set_always_save_display_names(bool always) { save_config.always_save_display_names = always; } - void set_mission_backup_depth(int depth) { save_config.mission_backup_depth = depth; } - void set_mission_backup_name(const SCP_string& name) { save_config.mission_backup_name = name; } - /** - * @brief Saves the mission onto the undo stack - * - * @param[in] pathname The full pathname - * - * @details Returns the value of CFred_mission_save::err, which is: + * @brief Saves the mission onto the backup stack (used by legacy FRED2 only). * - * @returns 0 for no error, or - * @returns A negative value if an error occured. - * - * @see save_mission_internal() + * @deprecated QtFRED no longer uses this. Retained for FRED2 (fred2/freddoc.cpp) compatibility. */ int autosave_mission_file(char* pathname); diff --git a/qtfred/src/mission/Editor.cpp b/qtfred/src/mission/Editor.cpp index 32ee0ad5a15..f43918f3d44 100644 --- a/qtfred/src/mission/Editor.cpp +++ b/qtfred/src/mission/Editor.cpp @@ -134,119 +134,11 @@ void Editor::update() { } } -int Editor::autosave(const char* /*desc*/) { - if (autosaveDisabled || !_lastActiveViewport) - return 0; - - Fred_mission_save save; - save.set_always_save_display_names(_lastActiveViewport->Always_save_display_names); - save.set_view_pos(_lastActiveViewport->camera.view_pos); - save.set_view_orient(_lastActiveViewport->camera.view_orient); - save.set_fred_alt_names(Fred_alt_names); - save.set_fred_callsigns(Fred_callsigns); - - // autosave_mission_file() needs a mutable buffer because it reads but doesn't write through it - char backup_name_buf[] = MISSION_BACKUP_NAME; - if (save.autosave_mission_file(backup_name_buf)) { - undoCount = undoAvailable = 0; - return -1; - } - - undoCount++; - checkUndo(); - return 0; -} - -int Editor::checkUndo() { - undoAvailable = 0; - if (undoCount == 0) - return 0; - - // Undo is available when Backup.002 exists (Backup.001 is the current, .002 is what we load) - CFileLocation loc = cf_find_file_location("Backup.002", CF_TYPE_MISSIONS); - if (loc.found) { - undoAvailable = 1; - return 1; - } - return 0; -} - -bool Editor::autoload() { - if (!undoAvailable || !_lastActiveViewport) - return false; - - // Load the previous state from Backup.002 - if (!loadMission("Backup.002", MPF_FAST_RELOAD)) - return false; - - // Delete Backup.001 (the state we just replaced) - cf_delete("Backup.001", CF_TYPE_MISSIONS); - - // Rotate backups back one slot: .003->.002, .004->.003, etc, .009->.008 - char old_name[256], new_name[256]; - for (int i = 1; i < MISSION_BACKUP_DEPTH; i++) { - sprintf(old_name, "Backup.%.3d", i + 1); - sprintf(new_name, "Backup.%.3d", i); - cf_rename(old_name, new_name, CF_TYPE_MISSIONS); - } - - if (undoCount > 0) - undoCount--; - checkUndo(); - return true; -} - -void Editor::maybeUseAutosave(std::string& filepath) +void Editor::maybeUseAutosave(std::string& /*filepath*/) { - // first, just grab the info of this mission - if (!parse_main(filepath.c_str(), MPF_ONLY_MISSION_INFO)) - return; - SCP_string created = The_mission.created; - CFileLocation res = cf_find_file_location(filepath.c_str(), CF_TYPE_ANY); - time_t modified = res.m_time; - if (!res.found) - { - UNREACHABLE("Couldn't find path '%s' even though parse_main() succeeded!", filepath.c_str()); - return; - } - - // now check all the autosaves - SCP_string backup_name; - CFileLocation backup_res; - for (int i = 1; i <= MISSION_BACKUP_DEPTH; ++i) - { - backup_name = MISSION_BACKUP_NAME; - char extension[5]; - sprintf(extension, ".%.3d", i); - backup_name += extension; - - backup_res = cf_find_file_location(backup_name.c_str(), CF_TYPE_MISSIONS); - if (backup_res.found && parse_main(backup_res.full_name.c_str(), MPF_ONLY_MISSION_INFO)) - { - SCP_string this_created = The_mission.created; - time_t this_modified = backup_res.m_time; - - if (created == this_created && this_modified > modified) - break; - } - - backup_name.clear(); - } - - // maybe load from the backup instead - if (!backup_name.empty()) - { - SCP_string prompt = "Autosaved file "; - prompt += backup_name; - prompt += " has a file modification time more recent than the specified file. Do you want to load the autosave instead?"; - - auto z = _lastActiveViewport->dialogProvider->showButtonDialog(DialogType::Question, - "Recover from autosave", - prompt.c_str(), - { DialogButton::Yes, DialogButton::No }); - if (z == DialogButton::Yes) - filepath = backup_res.full_name; // replace the specified file with the autosave file - } + // TODO(Phase 2): Implement timer-based autosave recovery. + // Will check _autosaveDirectory/.fs2 against the original file's mtime + // and offer recovery if the autosave is newer. } bool Editor::loadMission(const std::string& mission_name, int flags) { @@ -483,8 +375,7 @@ bool Editor::loadMission(const std::string& mission_name, int flags) { } if (!(flags & MPF_FAST_RELOAD)) { - undoCount = undoAvailable = 0; - autosave("nothing"); + // TODO(Phase 3): _undoStack->clear() } return true; @@ -875,8 +766,7 @@ void Editor::createNewMission() { clearMission(); create_player(&vmd_zero_vector, &vmd_identity_matrix); stars_post_level_init(); - undoCount = undoAvailable = 0; - autosave("nothing"); + // TODO(Phase 3): _undoStack->clear() missionLoaded(""); } void Editor::hideMarkedObjects() { diff --git a/qtfred/src/mission/Editor.h b/qtfred/src/mission/Editor.h index e4fca653382..2c0085af586 100644 --- a/qtfred/src/mission/Editor.h +++ b/qtfred/src/mission/Editor.h @@ -163,32 +163,6 @@ class Editor : public QObject { void layerListChanged(); public: - // --- Undo / autosave state --- - int undoAvailable = 0; ///< Whether an undo state (Backup.002) is available - int autosaveDisabled = 0; ///< When non-zero, autosave writes are suppressed - - /** - * @brief Rotate the backup stack and save the current mission state. - * - * Equivalent to CFREDDoc::autosave() in old FRED2. Should be called after - * any significant edit operation. Does nothing when @c autosaveDisabled is set. - * - * @param desc Optional human-readable description of the operation being saved. - * @return 0 on success, -1 if the backup write failed. - */ - int autosave(const char* desc = nullptr); - - /** - * @brief Restore the previous mission state from the backup stack. - * - * Equivalent to CFREDDoc::autoload() in old FRED2. - * Caller is responsible for preserving and restoring the camera state and - * @c saveName around this call. - * - * @return true on success, false if no undo state was available. - */ - bool autoload(); - // object numbers for ships in a wing. int wing_objects[MAX_WINGS][MAX_SHIPS_PER_WING]; @@ -290,11 +264,6 @@ class Editor : public QObject { SCP_vector> _viewports; EditorViewport* _lastActiveViewport = nullptr; - int undoCount = 0; ///< Number of autosave operations performed since last reset - - /** @brief Check whether Backup.002 exists and update @c undoAvailable accordingly. */ - int checkUndo(); - int numMarked = 0; SCP_vector Shield_sys_teams; diff --git a/qtfred/src/mission/EditorViewport.cpp b/qtfred/src/mission/EditorViewport.cpp index 28a916b27e8..dfdee9d8779 100644 --- a/qtfred/src/mission/EditorViewport.cpp +++ b/qtfred/src/mission/EditorViewport.cpp @@ -548,7 +548,6 @@ void EditorViewport::level_controlled() { level_object(&Objects[camera.getViewObj()].orient); object_moved(&Objects[camera.getViewObj()]); ///! \todo Notify. - editor->autosave("level object"); editor->missionChanged(); } break; @@ -575,7 +574,6 @@ void EditorViewport::level_controlled() { ///! \todo Notify. if (count) { - editor->autosave(count > 1 ? "level objects" : "level object"); editor->missionChanged(); } @@ -604,7 +602,6 @@ void EditorViewport::verticalize_controlled() { verticalize_object(&Objects[camera.getViewObj()].orient); object_moved(&Objects[camera.getViewObj()]); ///! \todo notify. - editor->autosave("align object"); editor->missionChanged(); } break; @@ -631,7 +628,6 @@ void EditorViewport::verticalize_controlled() { ///! \todo Notify. if (count) { - editor->autosave(count > 1 ? "align objects" : "align object"); editor->missionChanged(); } @@ -1124,7 +1120,6 @@ int EditorViewport::create_object_on_grid(int x, int y, int waypoint_instance, C editor->markObject(obj); editor->missionChanged(); - editor->autosave("object create"); } else if (obj == -1) { dialogProvider->showButtonDialog(DialogType::Error, "Error", "Maximum ship limit reached. Can't add any more ships.", { DialogButton::Ok }); diff --git a/qtfred/src/mission/dialogs/SceneBrowserModel.cpp b/qtfred/src/mission/dialogs/SceneBrowserModel.cpp index 181079b86bf..33e4ec58f39 100644 --- a/qtfred/src/mission/dialogs/SceneBrowserModel.cpp +++ b/qtfred/src/mission/dialogs/SceneBrowserModel.cpp @@ -199,7 +199,6 @@ void SceneBrowserModel::moveObjectToLayer(int objNum, const QString& layerName) { // Single inline call: temporary QByteArray lives until end of full expression — safe. _viewport->moveObjectToLayer(objNum, layerName.toUtf8().constData()); - _editor->autosave("move object to layer"); buildTree(); treeStructureChanged(); } @@ -216,7 +215,6 @@ void SceneBrowserModel::moveWingToLayer(int wingIndex, const QString& layerName) _viewport->moveObjectToLayer(objNum, layer.constData()); } } - _editor->autosave("move wing to layer"); buildTree(); treeStructureChanged(); } @@ -230,7 +228,6 @@ void SceneBrowserModel::moveWaypointPathToLayer(int waypointListIndex, const QSt if (!wl.get_waypoints().empty()) { _viewport->moveObjectToLayer(wl.get_waypoints().front().get_objnum(), layerName.toUtf8().constData()); } - _editor->autosave("move waypoint path to layer"); buildTree(); treeStructureChanged(); } diff --git a/qtfred/src/ui/FredView.cpp b/qtfred/src/ui/FredView.cpp index 7a7a240da02..23188ad4f56 100644 --- a/qtfred/src/ui/FredView.cpp +++ b/qtfred/src/ui/FredView.cpp @@ -105,7 +105,6 @@ FredView::FredView(QWidget* parent) : QMainWindow(parent), ui(new Ui::FredView() ui->actionOpen->setShortcuts(QKeySequence::Open); ui->actionSave->setShortcuts(QKeySequence::Save); ui->actionExit->setShortcuts(QKeySequence::Quit); - ui->actionUndo->setShortcuts(QKeySequence::Undo); ui->actionDelete->setShortcuts(QKeySequence::Delete); connect(ui->actionOpen, &QAction::triggered, this, &FredView::openLoadMissionDialog); @@ -255,9 +254,6 @@ void FredView::setEditor(Editor* editor, EditorViewport* viewport) { this, [this]() { ui->actionRestore_Camera_Pos->setEnabled(_viewport->camera.hasSavedPosition()); }); connect(this, &FredView::viewIdle, this, [this]() { ui->actionRevert->setEnabled(!saveName.isEmpty()); }); - connect(this, &FredView::viewIdle, this, [this]() { ui->actionUndo->setEnabled(fred->undoAvailable != 0); }); - connect(this, &FredView::viewIdle, this, [this]() { ui->actionDisable_Undo->setChecked(fred->autosaveDisabled != 0); }); - // Scene Browser dock panel _browserPanel = new SceneBrowserPanel(this, _viewport); addDockWidget(Qt::LeftDockWidgetArea, _browserPanel); @@ -573,23 +569,6 @@ void FredView::on_actionRevert_triggered(bool) { loadMissionFile(saveName); } -void FredView::on_actionUndo_triggered(bool) { - // Preserve camera state and saveName because autoload() triggers missionLoaded which would overwrite them - auto savedViewPos = _viewport->camera.view_pos; - auto savedViewOrient = _viewport->camera.view_orient; - auto savedSaveName = saveName; - - fred->autoload(); - - _viewport->camera.view_pos = savedViewPos; - _viewport->camera.view_orient = savedViewOrient; - saveName = savedSaveName; -} - -void FredView::on_actionDisable_Undo_triggered(bool checked) { - fred->autosaveDisabled = checked ? 1 : 0; -} - void FredView::on_actionFS2_Open_triggered(bool) { _missionSaveFormat = MissionFormat::STANDARD; } @@ -1784,7 +1763,6 @@ void FredView::showWingContextMenu(int wingIndex, const QPoint& globalPos) auto* deleteAction = menu.addAction(tr("Delete %1").arg(wingName)); connect(deleteAction, &QAction::triggered, this, [this, wingIndex]() { fred->delete_wing(wingIndex, 0); - fred->autosave("wing delete"); }); menu.exec(globalPos); @@ -1837,7 +1815,6 @@ void FredView::showWaypointPathContextMenu(int pathIndex, const QPoint& globalPo auto* deleteAction = menu.addAction(tr("Delete %1").arg(pathName)); connect(deleteAction, &QAction::triggered, this, [this]() { fred->delete_marked(); - fred->autosave("waypoint path delete"); }); menu.exec(globalPos); @@ -2668,14 +2645,11 @@ void FredView::on_actionWingForm_triggered(bool /*enabled*/) { } } - if (fred->create_wing()) { - fred->autosave("form wing"); - } + fred->create_wing(); } void FredView::on_actionWingDisband_triggered(bool /*enabled*/) { if (fred->query_single_wing_marked()) { fred->remove_wing(fred->cur_wing); - fred->autosave("wing disband"); } else { showButtonDialog(DialogType::Error, "Error", @@ -2772,19 +2746,16 @@ void FredView::on_actionRestore_Camera_Pos_triggered(bool) { void FredView::on_actionClone_Marked_Objects_triggered(bool) { if (fred->getNumMarked() > 0) { _viewport->duplicate_marked_objects(); - fred->autosave("clone marked"); } } void FredView::on_actionDelete_triggered(bool) { if (fred->getNumMarked() > 0) { fred->delete_marked(); - fred->autosave("object delete"); } } void FredView::on_actionDelete_Wing_triggered(bool) { if (fred->cur_wing >= 0) { fred->delete_wing(fred->cur_wing, 0); - fred->autosave("wing delete"); } } void FredView::initializeGroupActions() { diff --git a/qtfred/src/ui/FredView.h b/qtfred/src/ui/FredView.h index f313294d068..4f8b0420862 100644 --- a/qtfred/src/ui/FredView.h +++ b/qtfred/src/ui/FredView.h @@ -72,8 +72,6 @@ class FredView: public QMainWindow, public IDialogProvider { void on_actionSave_triggered(bool); void on_actionExit_triggered(bool); void on_actionRevert_triggered(bool); - void on_actionUndo_triggered(bool); - void on_actionDisable_Undo_triggered(bool checked); void on_actionLoad_Template_triggered(bool); void on_actionSave_As_Template_triggered(bool); void on_actionFS2_Open_triggered(bool); diff --git a/qtfred/src/ui/dialogs/AsteroidEditorDialog.cpp b/qtfred/src/ui/dialogs/AsteroidEditorDialog.cpp index 56574134cc7..0349af77c61 100644 --- a/qtfred/src/ui/dialogs/AsteroidEditorDialog.cpp +++ b/qtfred/src/ui/dialogs/AsteroidEditorDialog.cpp @@ -49,7 +49,6 @@ void AsteroidEditorDialog::accept() { // If apply() returns true, close the dialog if (_model->apply()) { - _editor->autosave("asteroid field editor"); QDialog::accept(); } // else: validation failed, don't close diff --git a/qtfred/src/ui/dialogs/BackgroundEditorDialog.cpp b/qtfred/src/ui/dialogs/BackgroundEditorDialog.cpp index df2f14fdbdc..d814be3f01a 100644 --- a/qtfred/src/ui/dialogs/BackgroundEditorDialog.cpp +++ b/qtfred/src/ui/dialogs/BackgroundEditorDialog.cpp @@ -32,7 +32,6 @@ BackgroundEditorDialog::~BackgroundEditorDialog() = default; void BackgroundEditorDialog::closeEvent(QCloseEvent* e) { - _viewport->editor->autosave("background editor"); QDialog::closeEvent(e); } diff --git a/qtfred/src/ui/dialogs/BriefingEditorDialog.cpp b/qtfred/src/ui/dialogs/BriefingEditorDialog.cpp index 6b56b2294ad..4cdc8a8ba82 100644 --- a/qtfred/src/ui/dialogs/BriefingEditorDialog.cpp +++ b/qtfred/src/ui/dialogs/BriefingEditorDialog.cpp @@ -104,7 +104,6 @@ void BriefingEditorDialog::accept() { // If apply() returns true, close the dialog if (_model->apply()) { - _viewport->editor->autosave("briefing editor"); ui->defaultMusicWidget->stopPlayback(); ui->musicPackWidget->stopPlayback(); QDialog::accept(); diff --git a/qtfred/src/ui/dialogs/CommandBriefingDialog.cpp b/qtfred/src/ui/dialogs/CommandBriefingDialog.cpp index f9b5ee2cbc8..529346e6120 100644 --- a/qtfred/src/ui/dialogs/CommandBriefingDialog.cpp +++ b/qtfred/src/ui/dialogs/CommandBriefingDialog.cpp @@ -37,7 +37,6 @@ void CommandBriefingDialog::accept() { // If apply() returns true, close the dialog if (_model->apply()) { - _viewport->editor->autosave("command briefing editor"); QDialog::accept(); } // else: validation failed, don't close diff --git a/qtfred/src/ui/dialogs/DebriefingDialog.cpp b/qtfred/src/ui/dialogs/DebriefingDialog.cpp index 5b7d92c2935..680700ba731 100644 --- a/qtfred/src/ui/dialogs/DebriefingDialog.cpp +++ b/qtfred/src/ui/dialogs/DebriefingDialog.cpp @@ -35,7 +35,6 @@ void DebriefingDialog::accept() { // If apply() returns true, close the dialog if (_model->apply()) { - _viewport->editor->autosave("debriefing editor"); ui->successMusicWidget->stopPlayback(); ui->averageMusicWidget->stopPlayback(); ui->failureMusicWidget->stopPlayback(); diff --git a/qtfred/src/ui/dialogs/MissionEventsDialog.cpp b/qtfred/src/ui/dialogs/MissionEventsDialog.cpp index d6a12dc91cc..d8a033df3a5 100644 --- a/qtfred/src/ui/dialogs/MissionEventsDialog.cpp +++ b/qtfred/src/ui/dialogs/MissionEventsDialog.cpp @@ -265,7 +265,6 @@ void MissionEventsDialog::accept() { // If apply() returns true, close the dialog if (_model->apply()) { - _viewport->editor->autosave("event editor"); QDialog::accept(); } // else: validation failed, don't close diff --git a/qtfred/src/ui/dialogs/MissionGoalsDialog.cpp b/qtfred/src/ui/dialogs/MissionGoalsDialog.cpp index f2692dd487a..e3197f07f34 100644 --- a/qtfred/src/ui/dialogs/MissionGoalsDialog.cpp +++ b/qtfred/src/ui/dialogs/MissionGoalsDialog.cpp @@ -36,7 +36,6 @@ void MissionGoalsDialog::accept() { // If apply() returns true, close the dialog if (_model->apply()) { - _viewport->editor->autosave("goal editor"); QDialog::accept(); } // else: validation failed, don't close diff --git a/qtfred/src/ui/dialogs/MissionSpecDialog.cpp b/qtfred/src/ui/dialogs/MissionSpecDialog.cpp index 70f5d4fc417..2715f1f324f 100644 --- a/qtfred/src/ui/dialogs/MissionSpecDialog.cpp +++ b/qtfred/src/ui/dialogs/MissionSpecDialog.cpp @@ -43,7 +43,6 @@ void MissionSpecDialog::accept() { // If apply() returns true, close the dialog if (_model->apply()) { - _viewport->editor->autosave("mission specs editor"); QDialog::accept(); } // else: validation failed, don't close diff --git a/qtfred/src/ui/dialogs/ObjectOrientEditorDialog.cpp b/qtfred/src/ui/dialogs/ObjectOrientEditorDialog.cpp index 0142c461208..625c12425da 100644 --- a/qtfred/src/ui/dialogs/ObjectOrientEditorDialog.cpp +++ b/qtfred/src/ui/dialogs/ObjectOrientEditorDialog.cpp @@ -30,7 +30,6 @@ void ObjectOrientEditorDialog::accept() { // If apply() returns true, close the dialog if (_model->apply()) { - _viewport->editor->autosave("object editor"); QDialog::accept(); } // else: validation failed, don't close diff --git a/qtfred/src/ui/dialogs/ShipEditor/ShipEditorDialog.cpp b/qtfred/src/ui/dialogs/ShipEditor/ShipEditorDialog.cpp index 15beeed4f40..1f25aed0401 100644 --- a/qtfred/src/ui/dialogs/ShipEditor/ShipEditorDialog.cpp +++ b/qtfred/src/ui/dialogs/ShipEditor/ShipEditorDialog.cpp @@ -70,7 +70,6 @@ bool ShipEditorDialog::getIfMultipleShips() const void ShipEditorDialog::closeEvent(QCloseEvent* e) { - _viewport->editor->autosave("ship editor"); QDialog::closeEvent(e); } diff --git a/qtfred/src/ui/dialogs/TeamLoadoutDialog.cpp b/qtfred/src/ui/dialogs/TeamLoadoutDialog.cpp index 9fdac89d8c9..75659b66d0d 100644 --- a/qtfred/src/ui/dialogs/TeamLoadoutDialog.cpp +++ b/qtfred/src/ui/dialogs/TeamLoadoutDialog.cpp @@ -45,7 +45,6 @@ void TeamLoadoutDialog::accept() { // If apply() returns true, close the dialog if (_model->apply()) { - _viewport->editor->autosave("loadout editor"); QDialog::accept(); } // else: validation failed, don't close diff --git a/qtfred/src/ui/dialogs/WingEditorDialog.cpp b/qtfred/src/ui/dialogs/WingEditorDialog.cpp index c831c755f41..140a1e2632d 100644 --- a/qtfred/src/ui/dialogs/WingEditorDialog.cpp +++ b/qtfred/src/ui/dialogs/WingEditorDialog.cpp @@ -45,7 +45,6 @@ WingEditorDialog::~WingEditorDialog() = default; void WingEditorDialog::closeEvent(QCloseEvent* e) { - _viewport->editor->autosave("wing editor"); QDialog::closeEvent(e); } diff --git a/qtfred/src/ui/widgets/renderwidget.cpp b/qtfred/src/ui/widgets/renderwidget.cpp index 7cc6cc8752e..47c2f88e6ca 100644 --- a/qtfred/src/ui/widgets/renderwidget.cpp +++ b/qtfred/src/ui/widgets/renderwidget.cpp @@ -340,7 +340,6 @@ void RenderWidget::mouseReleaseEvent(QMouseEvent* event) { _viewport->drag_rotate_objects(0, 0); } - fred->autosave("object move"); fred->missionChanged(); } else { diff --git a/qtfred/ui/FredView.ui b/qtfred/ui/FredView.ui index 1c957482e86..1240494f71f 100644 --- a/qtfred/ui/FredView.ui +++ b/qtfred/ui/FredView.ui @@ -69,7 +69,6 @@ E&dit - @@ -78,8 +77,6 @@ - - @@ -647,11 +644,6 @@ &Exit - - - &Undo - - &Clone Marked Objects @@ -680,14 +672,6 @@ &Unlock All Objects - - - D&isable Undo - - - true - - Visibility Layers... From 4c255f84a0bbc8d9f8c31b761e7bc21c09a411ba Mon Sep 17 00:00:00 2001 From: Mike Nelson Date: Fri, 15 May 2026 12:27:12 -0500 Subject: [PATCH 2/3] implement timed autosave feature --- code/missioneditor/missionsave.cpp | 32 +++++---- code/missioneditor/missionsave.h | 13 ++++ qtfred/src/mission/Editor.cpp | 65 +++++++++++++++++-- qtfred/src/mission/Editor.h | 20 ++++++ qtfred/src/mission/EditorViewport.cpp | 4 ++ qtfred/src/mission/EditorViewport.h | 4 +- .../dialogs/PreferencesDialogModel.cpp | 12 +++- .../mission/dialogs/PreferencesDialogModel.h | 8 +++ qtfred/src/ui/FredView.cpp | 25 +++++++ qtfred/src/ui/FredView.h | 2 + qtfred/src/ui/dialogs/PreferencesDialog.cpp | 11 ++++ qtfred/src/ui/dialogs/PreferencesDialog.h | 2 + qtfred/ui/PreferencesDialog.ui | 40 ++++++++++-- 13 files changed, 216 insertions(+), 22 deletions(-) diff --git a/code/missioneditor/missionsave.cpp b/code/missioneditor/missionsave.cpp index 7c27572dd69..b86dedc18be 100644 --- a/code/missioneditor/missionsave.cpp +++ b/code/missioneditor/missionsave.cpp @@ -2421,27 +2421,37 @@ int Fred_mission_save::save_mission_file(const char* pathname) save_mission_internal(savepath); if (!err) { - char backup_name[MAX_PATH_LEN]; + if (save_config.create_bak_file) { + char backup_name[MAX_PATH_LEN]; - strcpy_s(backup_name, pathname); + strcpy_s(backup_name, pathname); - // drop extension - auto ext_ch = strrchr(backup_name, '.'); - if (ext_ch != nullptr) - *ext_ch = 0; + // drop extension + auto ext_ch = strrchr(backup_name, '.'); + if (ext_ch != nullptr) + *ext_ch = 0; - strcat_s(backup_name, ".bak"); + strcat_s(backup_name, ".bak"); #ifdef _WIN32 - cf_attrib(pathname, 0, FILE_ATTRIBUTE_READONLY, CF_TYPE_MISSIONS); + cf_attrib(pathname, 0, FILE_ATTRIBUTE_READONLY, CF_TYPE_MISSIONS); #endif - cf_delete(backup_name, CF_TYPE_MISSIONS); - cf_rename(pathname, backup_name, CF_TYPE_MISSIONS); - cf_rename(savepath, pathname, CF_TYPE_MISSIONS); + cf_delete(backup_name, CF_TYPE_MISSIONS); + cf_rename(pathname, backup_name, CF_TYPE_MISSIONS); + cf_rename(savepath, pathname, CF_TYPE_MISSIONS); + } else { + cf_rename(savepath, pathname, CF_TYPE_MISSIONS); + } } return err; } +int Fred_mission_save::save_autosave_file(const char* pathname) +{ + save_mission_internal(pathname); + return err; +} + int Fred_mission_save::save_template_file(const char* pathname) { char savepath[MAX_PATH_LEN]; diff --git a/code/missioneditor/missionsave.h b/code/missioneditor/missionsave.h index 2df2b9adc30..cb4b4a482df 100644 --- a/code/missioneditor/missionsave.h +++ b/code/missioneditor/missionsave.h @@ -32,6 +32,7 @@ struct FredSaveConfig { matrix view_orient{}; bool always_save_display_names = false; + bool create_bak_file = true; // These are a little strange since mission saving and campaign saving use the same class here // which may be worth splitting up in the future. For now these will assert if not set when saving @@ -63,6 +64,7 @@ class Fred_mission_save { void set_fred_alt_names(const char (*names)[NAME_LENGTH + 1]) { save_config.fred_alt_names = names; } void set_fred_callsigns(const char (*callsigns)[NAME_LENGTH + 1]) { save_config.fred_callsigns = callsigns; } void set_always_save_display_names(bool always) { save_config.always_save_display_names = always; } + void set_create_bak_file(bool create) { save_config.create_bak_file = create; } /** * @brief Saves the mission onto the backup stack (used by legacy FRED2 only). @@ -85,6 +87,17 @@ class Fred_mission_save { */ int save_mission_file(const char* pathname); + /** + * @brief Saves the mission directly to an absolute path without any .bak rename dance. + * Used by the QtFRED timer-based autosave, which writes to an AppData directory + * outside the game's virtual file system. + * + * @param[in] pathname Absolute path for the autosave file + * + * @returns 0 for no error, or a negative value if an error occurred + */ + int save_autosave_file(const char* pathname); + /** * @brief Saves a mission template (.fst) to the given full pathname * diff --git a/qtfred/src/mission/Editor.cpp b/qtfred/src/mission/Editor.cpp index f43918f3d44..a106b74c9c6 100644 --- a/qtfred/src/mission/Editor.cpp +++ b/qtfred/src/mission/Editor.cpp @@ -36,6 +36,11 @@ #include "ui/QtGraphicsOperations.h" +#include +#include +#include +#include + #include "object.h" #include "management.h" #include "util.h" @@ -114,6 +119,13 @@ Editor::Editor() : currentObject{ -1 }, Shield_sys_teams(Iff_info.size(), Global // When a mission was loaded we need to notify everyone that the mission has changed connect(this, &Editor::missionLoaded, this, [this](const std::string&) { missionChanged(); }); + _autosaveDirectory = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/autosave/"; + QDir().mkpath(_autosaveDirectory); + + _autosaveTimer = new QTimer(this); + _autosaveTimer->setSingleShot(false); + connect(_autosaveTimer, &QTimer::timeout, this, &Editor::performTimedAutosave); + fredApp->runAfterInit([this]() { initialSetup(); }); } @@ -134,11 +146,56 @@ void Editor::update() { } } -void Editor::maybeUseAutosave(std::string& /*filepath*/) +void Editor::maybeUseAutosave(std::string& filepath) { - // TODO(Phase 2): Implement timer-based autosave recovery. - // Will check _autosaveDirectory/.fs2 against the original file's mtime - // and offer recovery if the autosave is newer. + const QString qpath = QString::fromStdString(filepath); + const QString basename = QFileInfo(qpath).fileName(); + const QString autosavePath = _autosaveDirectory + basename; + + const QFileInfo autosaveInfo(autosavePath); + if (!autosaveInfo.exists()) + return; + + const QFileInfo originalInfo(qpath); + if (autosaveInfo.lastModified() <= originalInfo.lastModified()) + return; + + if (_lastActiveViewport == nullptr || _lastActiveViewport->dialogProvider == nullptr) + return; + + const auto result = _lastActiveViewport->dialogProvider->showButtonDialog( + DialogType::Question, + "Autosave Recovery", + "An autosave file for this mission is newer than the original. Load the autosave instead?", + { DialogButton::Yes, DialogButton::No }); + + if (result == DialogButton::Yes) { + filepath = autosavePath.toStdString(); + } +} + +void Editor::startAutosaveTimer(int intervalSeconds) { + _autosaveTimer->stop(); + if (intervalSeconds > 0) + _autosaveTimer->start(intervalSeconds * 1000); +} + +void Editor::stopAutosaveTimer() { + _autosaveTimer->stop(); +} + +void Editor::setCurrentMissionPath(const QString& path) { + _currentMissionPath = path; +} + +void Editor::performTimedAutosave() { + QString savePath; + if (_currentMissionPath.isEmpty()) { + savePath = _autosaveDirectory + "untitled_autosave.fs2"; + } else { + savePath = _autosaveDirectory + QFileInfo(_currentMissionPath).fileName(); + } + autosaveDue(savePath); } bool Editor::loadMission(const std::string& mission_name, int flags) { diff --git a/qtfred/src/mission/Editor.h b/qtfred/src/mission/Editor.h index 2c0085af586..2001c70153e 100644 --- a/qtfred/src/mission/Editor.h +++ b/qtfred/src/mission/Editor.h @@ -10,6 +10,8 @@ #include #include +#include +#include #include #include #include @@ -67,6 +69,11 @@ class Editor : public QObject { void maybeUseAutosave(std::string& filepath); + void startAutosaveTimer(int intervalSeconds); + void stopAutosaveTimer(); + void setCurrentMissionPath(const QString& path); + const QString& autosaveDirectory() const { return _autosaveDirectory; } + /*! Load a mission. */ bool loadMission(const std::string& filepath, int flags = 0); @@ -123,6 +130,12 @@ class Editor : public QObject { void notifyLayerListChanged() { layerListChanged(); } signals: + /** + * @brief Emitted when the autosave timer fires; receiver performs the actual file save. + * @param savePath Absolute path for the autosave file + */ + void autosaveDue(const QString& savePath); + /** * @brief Signal for when a new mission has been loaded * @param filepath The path of the mission file, empty if new mission @@ -254,7 +267,14 @@ class Editor : public QObject { void generate_team_weaponry_usage_list(int team, int* arr); + private slots: + void performTimedAutosave(); + private: + QTimer* _autosaveTimer = nullptr; + QString _autosaveDirectory; + QString _currentMissionPath; + void clearMission(bool fast_reload = false); void initialSetup(); diff --git a/qtfred/src/mission/EditorViewport.cpp b/qtfred/src/mission/EditorViewport.cpp index dfdee9d8779..5508dc7c309 100644 --- a/qtfred/src/mission/EditorViewport.cpp +++ b/qtfred/src/mission/EditorViewport.cpp @@ -130,6 +130,8 @@ void EditorViewport::loadSettings() { settings.beginGroup(SETTINGS_GROUP); toolbar_icon_size = settings.value("toolbar_icon_size", toolbar_icon_size).toInt(); Offer_autosave_recovery = settings.value("offer_autosave_recovery", Offer_autosave_recovery).toBool(); + autosave_interval_seconds = settings.value("autosave_interval_seconds", autosave_interval_seconds).toInt(); + Create_bak_on_save = settings.value("create_bak_on_save", Create_bak_on_save).toBool(); Move_ships_when_undocking = settings.value("move_ships_when_undocking", Move_ships_when_undocking).toBool(); Always_save_display_names = settings.value("always_save_display_names", Always_save_display_names).toBool(); Error_checker_checks_potential_issues = settings.value("error_checker_checks_potential_issues", Error_checker_checks_potential_issues).toBool(); @@ -171,6 +173,8 @@ void EditorViewport::saveSettings() const { settings.beginGroup(SETTINGS_GROUP); settings.setValue("toolbar_icon_size", toolbar_icon_size); settings.setValue("offer_autosave_recovery", Offer_autosave_recovery); + settings.setValue("autosave_interval_seconds", autosave_interval_seconds); + settings.setValue("create_bak_on_save", Create_bak_on_save); settings.setValue("move_ships_when_undocking", Move_ships_when_undocking); settings.setValue("always_save_display_names", Always_save_display_names); settings.setValue("error_checker_checks_potential_issues", Error_checker_checks_potential_issues); diff --git a/qtfred/src/mission/EditorViewport.h b/qtfred/src/mission/EditorViewport.h index ec4f4cd6850..027ca2af363 100644 --- a/qtfred/src/mission/EditorViewport.h +++ b/qtfred/src/mission/EditorViewport.h @@ -193,7 +193,9 @@ class EditorViewport { bool Group_rotate = true; int toolbar_icon_size = 24; ///< Toolbar icon size in pixels (16, 24, or 32) - bool Offer_autosave_recovery = true; + bool Offer_autosave_recovery = true; + int autosave_interval_seconds = 300; // 5 minutes; 0 = disabled + bool Create_bak_on_save = true; bool Move_ships_when_undocking = true; bool Always_save_display_names = false; bool Error_checker_checks_potential_issues = true; diff --git a/qtfred/src/mission/dialogs/PreferencesDialogModel.cpp b/qtfred/src/mission/dialogs/PreferencesDialogModel.cpp index 49e581a9107..138abe67b94 100644 --- a/qtfred/src/mission/dialogs/PreferencesDialogModel.cpp +++ b/qtfred/src/mission/dialogs/PreferencesDialogModel.cpp @@ -10,6 +10,8 @@ namespace fso::fred::dialogs { PreferencesDialogModel::PreferencesDialogModel(QObject* parent, EditorViewport* viewport) : AbstractDialogModel(parent, viewport) , _offerAutosaveRecovery(viewport->Offer_autosave_recovery) + , _autosaveIntervalSeconds(viewport->autosave_interval_seconds) + , _createBakOnSave(viewport->Create_bak_on_save) , _moveShipsWhenUndocking(viewport->Move_ships_when_undocking) , _alwaysSaveDisplayNames(viewport->Always_save_display_names) , _checkPotentialIssues(viewport->Error_checker_checks_potential_issues) @@ -42,7 +44,9 @@ PreferencesDialogModel::PreferencesDialogModel(QObject* parent, EditorViewport* } bool PreferencesDialogModel::apply() { - _viewport->Offer_autosave_recovery = _offerAutosaveRecovery; + _viewport->Offer_autosave_recovery = _offerAutosaveRecovery; + _viewport->autosave_interval_seconds = _autosaveIntervalSeconds; + _viewport->Create_bak_on_save = _createBakOnSave; _viewport->Move_ships_when_undocking = _moveShipsWhenUndocking; _viewport->Always_save_display_names = _alwaysSaveDisplayNames; _viewport->Error_checker_checks_potential_issues = _checkPotentialIssues; @@ -95,6 +99,12 @@ void PreferencesDialogModel::reject() { bool PreferencesDialogModel::getOfferAutosaveRecovery() const { return _offerAutosaveRecovery; } void PreferencesDialogModel::setOfferAutosaveRecovery(bool value) { modify(_offerAutosaveRecovery, value); } +int PreferencesDialogModel::getAutosaveIntervalSeconds() const { return _autosaveIntervalSeconds; } +void PreferencesDialogModel::setAutosaveIntervalSeconds(int value) { modify(_autosaveIntervalSeconds, value); } + +bool PreferencesDialogModel::getCreateBakOnSave() const { return _createBakOnSave; } +void PreferencesDialogModel::setCreateBakOnSave(bool value) { modify(_createBakOnSave, value); } + bool PreferencesDialogModel::getMoveShipsWhenUndocking() const { return _moveShipsWhenUndocking; } void PreferencesDialogModel::setMoveShipsWhenUndocking(bool value) { modify(_moveShipsWhenUndocking, value); } diff --git a/qtfred/src/mission/dialogs/PreferencesDialogModel.h b/qtfred/src/mission/dialogs/PreferencesDialogModel.h index cd522fcfd8f..beeb03e7522 100644 --- a/qtfred/src/mission/dialogs/PreferencesDialogModel.h +++ b/qtfred/src/mission/dialogs/PreferencesDialogModel.h @@ -21,6 +21,12 @@ class PreferencesDialogModel : public AbstractDialogModel { bool getOfferAutosaveRecovery() const; void setOfferAutosaveRecovery(bool value); + int getAutosaveIntervalSeconds() const; + void setAutosaveIntervalSeconds(int value); + + bool getCreateBakOnSave() const; + void setCreateBakOnSave(bool value); + bool getMoveShipsWhenUndocking() const; void setMoveShipsWhenUndocking(bool value); @@ -71,6 +77,8 @@ class PreferencesDialogModel : public AbstractDialogModel { private: // General bool _offerAutosaveRecovery; + int _autosaveIntervalSeconds; + bool _createBakOnSave; bool _moveShipsWhenUndocking; bool _alwaysSaveDisplayNames; bool _checkPotentialIssues; diff --git a/qtfred/src/ui/FredView.cpp b/qtfred/src/ui/FredView.cpp index 23188ad4f56..004fe4c5d7c 100644 --- a/qtfred/src/ui/FredView.cpp +++ b/qtfred/src/ui/FredView.cpp @@ -225,6 +225,16 @@ void FredView::setEditor(Editor* editor, EditorViewport* viewport) { connect(fred, &Editor::missionLoaded, this, &FredView::on_mission_loaded); connect(fred, &Editor::missionChanged, this, [this]() { _missionModified = true; }); + connect(fred, &Editor::autosaveDue, this, [this](const QString& savePath) { + Fred_mission_save save; + save.set_save_format(_missionSaveFormat); + save.set_always_save_display_names(_viewport->Always_save_display_names); + save.set_view_pos(_viewport->camera.view_pos); + save.set_view_orient(_viewport->camera.view_orient); + save.set_fred_alt_names(Fred_alt_names); + save.set_fred_callsigns(Fred_callsigns); + save.save_autosave_file(savePath.toUtf8().constData()); + }); connect(fred, &Editor::layerListChanged, this, [this]() { _tbLayerComboDirty = true; }); // Sets the initial window title @@ -426,6 +436,7 @@ bool FredView::saveMissionToCurrentPath() { Fred_mission_save save; save.set_save_format(_missionSaveFormat); save.set_always_save_display_names(_viewport->Always_save_display_names); + save.set_create_bak_file(_viewport->Create_bak_on_save); save.set_view_pos(_viewport->camera.view_pos); save.set_view_orient(_viewport->camera.view_orient); save.set_fred_alt_names(Fred_alt_names); @@ -465,6 +476,7 @@ bool FredView::saveMissionAs() { Fred_mission_save save; save.set_save_format(_missionSaveFormat); save.set_always_save_display_names(_viewport->Always_save_display_names); + save.set_create_bak_file(_viewport->Create_bak_on_save); save.set_view_pos(_viewport->camera.view_pos); save.set_view_orient(_viewport->camera.view_orient); save.set_fred_alt_names(Fred_alt_names); @@ -484,9 +496,17 @@ bool FredView::saveMissionAs() { if (_errorCheckerDialog && _errorCheckerDialog->isVisible()) _errorCheckerDialog->runCheck(); + fred->setCurrentMissionPath(saveName); + restartAutosaveTimer(); return true; } +void FredView::restartAutosaveTimer() { + if (!fred || !_viewport) + return; + fred->startAutosaveTimer(_viewport->autosave_interval_seconds); +} + void FredView::saveAsTemplate() { // Collect template metadata first dialogs::SaveAsTemplateDialog metaDialog(this, getUsername()); @@ -634,6 +654,7 @@ void FredView::on_actionFS1_Mission_triggered(bool) { Fred_mission_save fileSave; fileSave.set_save_format(_missionSaveFormat); fileSave.set_always_save_display_names(_viewport->Always_save_display_names); + fileSave.set_create_bak_file(_viewport->Create_bak_on_save); fileSave.set_view_pos(_viewport->camera.view_pos); fileSave.set_view_orient(_viewport->camera.view_orient); fileSave.set_fred_alt_names(Fred_alt_names); @@ -754,6 +775,10 @@ void FredView::on_mission_loaded(const std::string& filepath) { saveName = QString(); } + // Update autosave path and start/stop timer based on whether we have a named file. + fred->setCurrentMissionPath(saveName); + restartAutosaveTimer(); + _missionModified = false; if (filepath.empty()) { diff --git a/qtfred/src/ui/FredView.h b/qtfred/src/ui/FredView.h index 4f8b0420862..16a25be8db3 100644 --- a/qtfred/src/ui/FredView.h +++ b/qtfred/src/ui/FredView.h @@ -59,6 +59,8 @@ class FredView: public QMainWindow, public IDialogProvider { void showWingContextMenu(int wingIndex, const QPoint& globalPos); void showWaypointPathContextMenu(int pathIndex, const QPoint& globalPos); + void restartAutosaveTimer(); + public slots: void openLoadMissionDialog(); diff --git a/qtfred/src/ui/dialogs/PreferencesDialog.cpp b/qtfred/src/ui/dialogs/PreferencesDialog.cpp index 328d074d352..7ce500a62d7 100644 --- a/qtfred/src/ui/dialogs/PreferencesDialog.cpp +++ b/qtfred/src/ui/dialogs/PreferencesDialog.cpp @@ -63,6 +63,7 @@ PreferencesDialog::~PreferencesDialog() = default; void PreferencesDialog::applyChanges() { _model->apply(); _fredView->setIconSize(QSize(_model->getToolbarIconSize(), _model->getToolbarIconSize())); + _fredView->restartAutosaveTimer(); _viewport->needsUpdate(); } @@ -85,6 +86,8 @@ void PreferencesDialog::updateUi() { // General ui->offerAutosaveRecovery->setChecked(_model->getOfferAutosaveRecovery()); + ui->autosaveIntervalSeconds->setValue(_model->getAutosaveIntervalSeconds()); + ui->createBakOnSave->setChecked(_model->getCreateBakOnSave()); ui->moveShipsWhenUndocking->setChecked(_model->getMoveShipsWhenUndocking()); ui->alwaysSaveDisplayNames->setChecked(_model->getAlwaysSaveDisplayNames()); ui->checkPotentialIssues->setChecked(_model->getCheckPotentialIssues()); @@ -124,6 +127,14 @@ void PreferencesDialog::on_offerAutosaveRecovery_toggled(bool checked) { _model->setOfferAutosaveRecovery(checked); } +void PreferencesDialog::on_autosaveIntervalSeconds_valueChanged(int value) { + _model->setAutosaveIntervalSeconds(value); +} + +void PreferencesDialog::on_createBakOnSave_toggled(bool checked) { + _model->setCreateBakOnSave(checked); +} + void PreferencesDialog::on_moveShipsWhenUndocking_toggled(bool checked) { _model->setMoveShipsWhenUndocking(checked); } diff --git a/qtfred/src/ui/dialogs/PreferencesDialog.h b/qtfred/src/ui/dialogs/PreferencesDialog.h index b51032c3cb6..1c2e3feff75 100644 --- a/qtfred/src/ui/dialogs/PreferencesDialog.h +++ b/qtfred/src/ui/dialogs/PreferencesDialog.h @@ -24,6 +24,8 @@ private slots: void applyChanges(); // General void on_offerAutosaveRecovery_toggled(bool checked); + void on_autosaveIntervalSeconds_valueChanged(int value); + void on_createBakOnSave_toggled(bool checked); void on_moveShipsWhenUndocking_toggled(bool checked); void on_alwaysSaveDisplayNames_toggled(bool checked); void on_checkPotentialIssues_toggled(bool checked); diff --git a/qtfred/ui/PreferencesDialog.ui b/qtfred/ui/PreferencesDialog.ui index e73b35564aa..aa9b5cbadd7 100644 --- a/qtfred/ui/PreferencesDialog.ui +++ b/qtfred/ui/PreferencesDialog.ui @@ -85,28 +85,58 @@ Mission Editing - - + + - Offer to load backup file on mission open + Offer to load autosave on mission open - + + + + Autosave interval (seconds, 0 = disabled): + + + + + + + 0 + + + 3600 + + + 30 + + + 300 + + + + Move Ships When Undocking - + Always save Display Names + + + + Create .bak file when saving a mission + + + From 287a0017f4f4f00938d1ca803577f38a074deee7 Mon Sep 17 00:00:00 2001 From: Mike Nelson Date: Fri, 15 May 2026 13:14:07 -0500 Subject: [PATCH 3/3] clang --- qtfred/src/mission/Editor.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qtfred/src/mission/Editor.h b/qtfred/src/mission/Editor.h index 2001c70153e..dfb461808d6 100644 --- a/qtfred/src/mission/Editor.h +++ b/qtfred/src/mission/Editor.h @@ -270,7 +270,7 @@ class Editor : public QObject { private slots: void performTimedAutosave(); - private: + private: // NOLINT(readability-redundant-access-specifiers) QTimer* _autosaveTimer = nullptr; QString _autosaveDirectory; QString _currentMissionPath;