Undo: use QUndoStack::isClean() to determine unsaved changes

Properly implement the unsaved-changes flag(s). Since we currently have
two kinds of changes, there are two flags:
1) dive_list_changed in divelist.c marks non-undoable changes. This flag
   is only cleared on save or load.
2) QUndoStack::isClean() is used to determine the state of undoable
   changes. Every time the user returns to the state where they saved,
   this flag is cleared.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
This commit is contained in:
Berthold Stoeger 2019-03-30 18:39:27 +01:00 committed by Dirk Hohndel
parent 837ab6c90b
commit 58f2e5f77c
9 changed files with 46 additions and 31 deletions

View file

@ -20,6 +20,8 @@
#include "git-access.h" #include "git-access.h"
#include "table.h" #include "table.h"
/* This flag is set to true by operations that are not implemented in the
* undo system. It is therefore only cleared on save and load. */
static bool dive_list_changed = false; static bool dive_list_changed = false;
bool autogroup = false; bool autogroup = false;

View file

@ -108,7 +108,6 @@ void reverseGeoLookup(degrees_t latitude, degrees_t longitude, taxonomy_data *ta
taxonomy->category[ri].category = TC_ADMIN_L3; taxonomy->category[ri].category = TC_ADMIN_L3;
taxonomy->nr++; taxonomy->nr++;
} }
mark_divelist_changed(true);
} else { } else {
report_error("geonames.org did not provide reverse lookup information"); report_error("geonames.org did not provide reverse lookup information");
qDebug() << "no reverse geo lookup; geonames returned\n" << fullReply; qDebug() << "no reverse geo lookup; geonames returned\n" << fullReply;
@ -157,7 +156,6 @@ void reverseGeoLookup(degrees_t latitude, degrees_t longitude, taxonomy_data *ta
if (idx == taxonomy->nr) if (idx == taxonomy->nr)
taxonomy->nr++; taxonomy->nr++;
} }
mark_divelist_changed(true);
} }
} else { } else {
report_error("timeout accessing geonames.org"); report_error("timeout accessing geonames.org");

View file

@ -10,7 +10,10 @@
namespace Command { namespace Command {
// 1) General commands // 1) General commands
void init(); // Setup signals to inform frontend of clean status.
void clear(); // Reset the undo stack. Delete all commands. void clear(); // Reset the undo stack. Delete all commands.
void setClean(); // Call after save - this marks a state where no changes need to be saved.
bool isClean(); // Any changes need to be saved?
QAction *undoAction(QObject *parent); // Create an undo action. QAction *undoAction(QObject *parent); // Create an undo action.
QAction *redoAction(QObject *parent); // Create an redo action. QAction *redoAction(QObject *parent); // Create an redo action.

View file

@ -1,17 +1,33 @@
// SPDX-License-Identifier: GPL-2.0 // SPDX-License-Identifier: GPL-2.0
#include "command_base.h" #include "command_base.h"
#include "core/qthelper.h" // for updateWindowTitle()
namespace Command { namespace Command {
static QUndoStack undoStack; static QUndoStack undoStack;
// General commands // General commands
void init()
{
QObject::connect(&undoStack, &QUndoStack::cleanChanged, &updateWindowTitle);
}
void clear() void clear()
{ {
undoStack.clear(); undoStack.clear();
} }
void setClean()
{
undoStack.setClean();
}
bool isClean()
{
return undoStack.isClean();
}
QAction *undoAction(QObject *parent) QAction *undoAction(QObject *parent)
{ {
return undoStack.createUndoAction(parent, QCoreApplication::translate("Command", "&Undo")); return undoStack.createUndoAction(parent, QCoreApplication::translate("Command", "&Undo"));

View file

@ -411,7 +411,6 @@ void AddDive::redoit()
divesAndSitesToRemove = addDives(divesToAdd); divesAndSitesToRemove = addDives(divesToAdd);
sort_trip_table(&trip_table); // Though unlikely, adding a dive may reorder trips sort_trip_table(&trip_table); // Though unlikely, adding a dive may reorder trips
mark_divelist_changed(true);
// Select the newly added dive // Select the newly added dive
restoreSelection(divesAndSitesToRemove.dives, divesAndSitesToRemove.dives[0]); restoreSelection(divesAndSitesToRemove.dives, divesAndSitesToRemove.dives[0]);
@ -500,8 +499,6 @@ void ImportDives::redoit()
// Remember dives and sites to remove // Remember dives and sites to remove
divesAndSitesToRemove = std::move(divesAndSitesToRemoveNew); divesAndSitesToRemove = std::move(divesAndSitesToRemoveNew);
mark_divelist_changed(true);
} }
void ImportDives::undoit() void ImportDives::undoit()
@ -517,8 +514,6 @@ void ImportDives::undoit()
// ...and restore the selection // ...and restore the selection
restoreSelection(selection, currentDive); restoreSelection(selection, currentDive);
mark_divelist_changed(true);
} }
DeleteDive::DeleteDive(const QVector<struct dive*> &divesToDeleteIn) DeleteDive::DeleteDive(const QVector<struct dive*> &divesToDeleteIn)
@ -536,7 +531,6 @@ void DeleteDive::undoit()
{ {
divesToDelete = addDives(divesToAdd); divesToDelete = addDives(divesToAdd);
sort_trip_table(&trip_table); // Though unlikely, removing a dive may reorder trips sort_trip_table(&trip_table); // Though unlikely, removing a dive may reorder trips
mark_divelist_changed(true);
// Select all re-added dives and make the first one current // Select all re-added dives and make the first one current
dive *currentDive = !divesToDelete.dives.empty() ? divesToDelete.dives[0] : nullptr; dive *currentDive = !divesToDelete.dives.empty() ? divesToDelete.dives[0] : nullptr;
@ -547,7 +541,6 @@ void DeleteDive::redoit()
{ {
divesToAdd = removeDives(divesToDelete); divesToAdd = removeDives(divesToDelete);
sort_trip_table(&trip_table); // Though unlikely, adding a dive may reorder trips sort_trip_table(&trip_table); // Though unlikely, adding a dive may reorder trips
mark_divelist_changed(true);
// Deselect all dives and select dive that was close to the first deleted dive // Deselect all dives and select dive that was close to the first deleted dive
dive *newCurrent = nullptr; dive *newCurrent = nullptr;
@ -587,8 +580,6 @@ void ShiftTime::redoit()
// Negate the time-shift so that the next call does the reverse // Negate the time-shift so that the next call does the reverse
timeChanged = -timeChanged; timeChanged = -timeChanged;
mark_divelist_changed(true);
} }
bool ShiftTime::workToBeDone() bool ShiftTime::workToBeDone()
@ -611,7 +602,6 @@ RenumberDives::RenumberDives(const QVector<QPair<dive *, int>> &divesToRenumberI
void RenumberDives::undoit() void RenumberDives::undoit()
{ {
renumberDives(divesToRenumber); renumberDives(divesToRenumber);
mark_divelist_changed(true);
} }
bool RenumberDives::workToBeDone() bool RenumberDives::workToBeDone()
@ -634,8 +624,6 @@ void TripBase::redoit()
{ {
moveDivesBetweenTrips(divesToMove); moveDivesBetweenTrips(divesToMove);
sort_trip_table(&trip_table); // Though unlikely, moving dives may reorder trips sort_trip_table(&trip_table); // Though unlikely, moving dives may reorder trips
mark_divelist_changed(true);
} }
void TripBase::undoit() void TripBase::undoit()
@ -764,7 +752,6 @@ void SplitDivesBase::redoit()
{ {
divesToUnsplit = addDives(splitDives); divesToUnsplit = addDives(splitDives);
unsplitDive = removeDives(diveToSplit); unsplitDive = removeDives(diveToSplit);
mark_divelist_changed(true);
// Select split dives and make first dive current // Select split dives and make first dive current
restoreSelection(divesToUnsplit.dives, divesToUnsplit.dives[0]); restoreSelection(divesToUnsplit.dives, divesToUnsplit.dives[0]);
@ -775,7 +762,6 @@ void SplitDivesBase::undoit()
// Note: reverse order with respect to redoit() // Note: reverse order with respect to redoit()
diveToSplit = addDives(unsplitDive); diveToSplit = addDives(unsplitDive);
splitDives = removeDives(divesToUnsplit); splitDives = removeDives(divesToUnsplit);
mark_divelist_changed(true);
// Select unsplit dive and make it current // Select unsplit dive and make it current
restoreSelection(diveToSplit.dives, diveToSplit.dives[0] ); restoreSelection(diveToSplit.dives, diveToSplit.dives[0] );

View file

@ -92,8 +92,6 @@ void EditBase<T>::undo()
if (setSelection(selectedDives, current)) if (setSelection(selectedDives, current))
emit diveListNotifier.selectionChanged(); // If the selection changed -> tell the frontend emit diveListNotifier.selectionChanged(); // If the selection changed -> tell the frontend
mark_divelist_changed(true);
} }
// We have to manually instantiate the constructors of the EditBase class, // We have to manually instantiate the constructors of the EditBase class,
@ -509,8 +507,6 @@ void EditTagsBase::undo()
if (setSelection(selectedDives, current)) if (setSelection(selectedDives, current))
emit diveListNotifier.selectionChanged(); // If the selection changed -> tell the frontend emit diveListNotifier.selectionChanged(); // If the selection changed -> tell the frontend
mark_divelist_changed(true);
} }
// Undo and redo do the same as just the stored value is exchanged // Undo and redo do the same as just the stored value is exchanged

View file

@ -2,7 +2,6 @@
#include "command_edit_trip.h" #include "command_edit_trip.h"
#include "command_private.h" #include "command_private.h"
#include "core/divelist.h" // for mark_divelist_changed(). TODO: remove
#include "core/qthelper.h" #include "core/qthelper.h"
namespace Command { namespace Command {
@ -27,7 +26,6 @@ void EditTripBase::undo()
value = old; value = old;
emit diveListNotifier.tripChanged(trip, fieldId()); emit diveListNotifier.tripChanged(trip, fieldId());
mark_divelist_changed(true);
} }
// Undo and redo do the same as just the stored value is exchanged // Undo and redo do the same as just the stored value is exchanged

View file

@ -128,6 +128,7 @@ MainWindow::MainWindow() : QMainWindow(),
m_Instance = this; m_Instance = this;
ui.setupUi(this); ui.setupUi(this);
read_hashes(); read_hashes();
Command::init();
// Define the States of the Application Here, Currently the states are situations where the different // Define the States of the Application Here, Currently the states are situations where the different
// widgets will change on the mainwindow. // widgets will change on the mainwindow.
@ -589,7 +590,15 @@ void MainWindow::on_actionCloudstoragesave_triggered()
return; return;
setCurrentFile(qPrintable(filename)); setCurrentFile(qPrintable(filename));
mark_divelist_changed(false); setFileClean();
}
// Currently we have two markers for unsaved changes:
// 1) unsaved_changes() returns true for non-undoable changes.
// 2) Command::isClean() returns false for undoable changes.
static bool unsavedChanges()
{
return unsaved_changes() || !Command::isClean();
} }
void MainWindow::on_actionCloudOnline_triggered() void MainWindow::on_actionCloudOnline_triggered()
@ -611,7 +620,7 @@ void MainWindow::on_actionCloudOnline_triggered()
git_local_only = isOffline; git_local_only = isOffline;
if (!isOffline) { if (!isOffline) {
// User requests to go online. Try to sync cloud storage // User requests to go online. Try to sync cloud storage
if (unsaved_changes()) { if (unsavedChanges()) {
// If there are unsaved changes, ask the user if they want to save them. // If there are unsaved changes, ask the user if they want to save them.
// If they don't, they have to sync manually. // If they don't, they have to sync manually.
if (QMessageBox::warning(this, tr("Save changes?"), if (QMessageBox::warning(this, tr("Save changes?"),
@ -653,12 +662,18 @@ bool MainWindow::okToClose(QString message)
QMessageBox::warning(this, tr("Warning"), message); QMessageBox::warning(this, tr("Warning"), message);
return false; return false;
} }
if (unsaved_changes() && askSaveChanges() == false) if (unsavedChanges() && askSaveChanges() == false)
return false; return false;
return true; return true;
} }
void MainWindow::setFileClean()
{
mark_divelist_changed(false);
Command::setClean();
}
void MainWindow::closeCurrentFile() void MainWindow::closeCurrentFile()
{ {
graphics->setEmptyState(); graphics->setEmptyState();
@ -667,7 +682,7 @@ void MainWindow::closeCurrentFile()
clear_dive_file_data(); clear_dive_file_data();
setCurrentFile(nullptr); setCurrentFile(nullptr);
cleanUpEmpty(); cleanUpEmpty();
mark_divelist_changed(false); setFileClean();
clear_events(); clear_events();
@ -763,7 +778,7 @@ void MainWindow::on_actionQuit_triggered()
return; return;
} }
if (unsaved_changes() && (askSaveChanges() == false)) if (unsavedChanges() && (askSaveChanges() == false))
return; return;
writeSettings(); writeSettings();
QApplication::quit(); QApplication::quit();
@ -1421,7 +1436,7 @@ void MainWindow::closeEvent(QCloseEvent *event)
return; return;
} }
if (unsaved_changes() && (askSaveChanges() == false)) { if (unsavedChanges() && (askSaveChanges() == false)) {
event->ignore(); event->ignore();
return; return;
} }
@ -1555,7 +1570,7 @@ int MainWindow::file_save_as(void)
return -1; return -1;
setCurrentFile(qPrintable(filename)); setCurrentFile(qPrintable(filename));
mark_divelist_changed(false); setFileClean();
addRecentFile(filename, true); addRecentFile(filename, true);
return 0; return 0;
} }
@ -1592,7 +1607,7 @@ int MainWindow::file_save(void)
} }
if (is_cloud) if (is_cloud)
hideProgressBar(); hideProgressBar();
mark_divelist_changed(false); setFileClean();
addRecentFile(QString(existing_filename), true); addRecentFile(QString(existing_filename), true);
return 0; return 0;
} }
@ -1631,7 +1646,7 @@ void MainWindow::setTitle()
return; return;
} }
QString unsaved = (unsaved_changes() ? " *" : ""); QString unsaved = (unsavedChanges() ? " *" : "");
QString shown = QString(" (%1)").arg(filterWidget2.shownText()); QString shown = QString(" (%1)").arg(filterWidget2.shownText());
setWindowTitle("Subsurface: " + displayedFilename(existing_filename) + unsaved + shown); setWindowTitle("Subsurface: " + displayedFilename(existing_filename) + unsaved + shown);
} }

View file

@ -203,6 +203,7 @@ private:
void writeSettings(); void writeSettings();
int file_save(); int file_save();
int file_save_as(); int file_save_as();
void setFileClean();
void beginChangeState(CurrentState s); void beginChangeState(CurrentState s);
void saveSplitterSizes(); void saveSplitterSizes();
void toggleCollapsible(bool toggle); void toggleCollapsible(bool toggle);