diff --git a/Pdf4QtDocPageOrganizer/mainwindow.cpp b/Pdf4QtDocPageOrganizer/mainwindow.cpp
index 4e3fd2b..baa41f6 100644
--- a/Pdf4QtDocPageOrganizer/mainwindow.cpp
+++ b/Pdf4QtDocPageOrganizer/mainwindow.cpp
@@ -60,6 +60,8 @@ MainWindow::MainWindow(QWidget* parent) :
ui->actionRemoveSelection->setData(int(Operation::RemoveSelection));
ui->actionReplaceSelection->setData(int(Operation::ReplaceSelection));
ui->actionRestoreRemovedItems->setData(int(Operation::RestoreRemovedItems));
+ ui->actionUndo->setData(int(Operation::Undo));
+ ui->actionRedo->setData(int(Operation::Redo));
ui->actionCut->setData(int(Operation::Cut));
ui->actionCopy->setData(int(Operation::Copy));
ui->actionPaste->setData(int(Operation::Paste));
@@ -96,6 +98,8 @@ MainWindow::MainWindow(QWidget* parent) :
mainToolbar->addSeparator();
mainToolbar->addActions({ ui->actionCloneSelection, ui->actionRemoveSelection });
mainToolbar->addSeparator();
+ mainToolbar->addActions({ ui->actionUndo, ui->actionRedo });
+ mainToolbar->addSeparator();
mainToolbar->addActions({ ui->actionCut, ui->actionCopy, ui->actionPaste });
mainToolbar->addSeparator();
mainToolbar->addActions({ ui->actionGroup, ui->actionUngroup });
@@ -303,6 +307,12 @@ bool MainWindow::canPerformOperation(Operation operation) const
case Operation::RestoreRemovedItems:
return !m_model->isTrashBinEmpty();
+ case Operation::Undo:
+ return m_model->canUndo();
+
+ case Operation::Redo:
+ return m_model->canRedo();
+
case Operation::Cut:
case Operation::Copy:
return isSelected;
@@ -417,6 +427,18 @@ void MainWindow::performOperation(Operation operation)
break;
}
+ case Operation::Undo:
+ {
+ m_model->undo();
+ break;
+ }
+
+ case Operation::Redo:
+ {
+ m_model->redo();
+ break;
+ }
+
case Operation::Cut:
case Operation::Copy:
{
diff --git a/Pdf4QtDocPageOrganizer/mainwindow.h b/Pdf4QtDocPageOrganizer/mainwindow.h
index d8dc717..1e87738 100644
--- a/Pdf4QtDocPageOrganizer/mainwindow.h
+++ b/Pdf4QtDocPageOrganizer/mainwindow.h
@@ -53,6 +53,9 @@ public:
ReplaceSelection,
RestoreRemovedItems,
+ Undo,
+ Redo,
+
Cut,
Copy,
Paste,
diff --git a/Pdf4QtDocPageOrganizer/mainwindow.ui b/Pdf4QtDocPageOrganizer/mainwindow.ui
index 55f142d..506c2eb 100644
--- a/Pdf4QtDocPageOrganizer/mainwindow.ui
+++ b/Pdf4QtDocPageOrganizer/mainwindow.ui
@@ -63,6 +63,9 @@
Edit
+
+
+
@@ -555,6 +558,30 @@
Invert Selection
+
+
+
+ :/pdfdocpage/resources/undo.svg:/pdfdocpage/resources/undo.svg
+
+
+ Undo
+
+
+ Ctrl+Z
+
+
+
+
+
+ :/pdfdocpage/resources/redo.svg:/pdfdocpage/resources/redo.svg
+
+
+ Redo
+
+
+ Ctrl+Y
+
+
diff --git a/Pdf4QtDocPageOrganizer/pageitemmodel.cpp b/Pdf4QtDocPageOrganizer/pageitemmodel.cpp
index 834e526..c5118d6 100644
--- a/Pdf4QtDocPageOrganizer/pageitemmodel.cpp
+++ b/Pdf4QtDocPageOrganizer/pageitemmodel.cpp
@@ -97,6 +97,7 @@ QVariant PageItemModel::data(const QModelIndex& index, int role) const
int PageItemModel::insertDocument(QString fileName, pdf::PDFDocument document, const QModelIndex& index)
{
+ Modifier modifier(this);
auto it = std::find_if(m_documents.cbegin(), m_documents.cend(), [&](const auto& item) { return item.second.fileName == fileName; });
if (it != m_documents.cend())
{
@@ -117,6 +118,7 @@ int PageItemModel::insertDocument(QString fileName, pdf::PDFDocument document, c
int PageItemModel::insertImage(QString fileName, const QModelIndex& index)
{
+ Modifier modifier(this);
QFile file(fileName);
if (file.open(QFile::ReadOnly))
@@ -234,6 +236,8 @@ void PageItemModel::group(const QModelIndexList& list)
return;
}
+ Modifier modifier(this);
+
std::vector groupedIndices;
groupedIndices.reserve(list.size());
std::transform(list.cbegin(), list.cend(), std::back_inserter(groupedIndices), [](const auto& index) { return index.row(); });
@@ -275,6 +279,8 @@ void PageItemModel::ungroup(const QModelIndexList& list)
return;
}
+ Modifier modifier(this);
+
std::vector ungroupedIndices;
ungroupedIndices.reserve(list.size());
std::transform(list.cbegin(), list.cend(), std::back_inserter(ungroupedIndices), [](const auto& index) { return index.row(); });
@@ -319,6 +325,8 @@ QModelIndexList PageItemModel::restoreRemovedItems()
return result;
}
+ Modifier modifier(this);
+
const int trashBinSize = int(m_trashBin.size());
const int rowCount = this->rowCount(QModelIndex());
beginInsertRows(QModelIndex(), rowCount, rowCount + trashBinSize - 1);
@@ -345,6 +353,8 @@ QModelIndexList PageItemModel::cloneSelection(const QModelIndexList& list)
return result;
}
+ Modifier modifier(this);
+
std::vector rows;
rows.reserve(list.size());
std::transform(list.cbegin(), list.cend(), std::back_inserter(rows), [](const auto& index) { return index.row(); });
@@ -381,6 +391,8 @@ void PageItemModel::removeSelection(const QModelIndexList& list)
return;
}
+ Modifier modifier(this);
+
std::vector rows;
rows.reserve(list.size());
std::transform(list.cbegin(), list.cend(), std::back_inserter(rows), [](const auto& index) { return index.row(); });
@@ -397,6 +409,8 @@ void PageItemModel::removeSelection(const QModelIndexList& list)
void PageItemModel::insertEmptyPage(const QModelIndexList& list)
{
+ Modifier modifier(this);
+
if (list.isEmpty())
{
insertEmptyPage(QModelIndex());
@@ -461,6 +475,8 @@ void PageItemModel::rotateLeft(const QModelIndexList& list)
return;
}
+ Modifier modifier(this);
+
int rowMin = list.front().row();
int rowMax = list.front().row();
@@ -488,6 +504,8 @@ void PageItemModel::rotateRight(const QModelIndexList& list)
return;
}
+ Modifier modifier(this);
+
int rowMin = list.front().row();
int rowMax = list.front().row();
@@ -560,6 +578,8 @@ void PageItemModel::regroupEvenOdd(const QModelIndexList& list)
return;
}
+ Modifier modifier(this);
+
std::vector pageGroupItems = m_pageGroupItems;
std::vector extractedItems = extractItems(pageGroupItems, list);
@@ -598,6 +618,8 @@ void PageItemModel::regroupPaired(const QModelIndexList& list)
return;
}
+ Modifier modifier(this);
+
std::vector pageGroupItems = m_pageGroupItems;
std::vector extractedItems = extractItems(pageGroupItems, list);
@@ -627,6 +649,8 @@ void PageItemModel::regroupPaired(const QModelIndexList& list)
void PageItemModel::regroupBookmarks(const QModelIndexList& list)
{
Q_ASSERT(false);
+
+ Modifier modifier(this);
}
void PageItemModel::regroupAlternatingPages(const QModelIndexList& list, bool reversed)
@@ -636,6 +660,8 @@ void PageItemModel::regroupAlternatingPages(const QModelIndexList& list, bool re
return;
}
+ Modifier modifier(this);
+
std::vector pageGroupItems = m_pageGroupItems;
std::vector extractedItems = extractItems(pageGroupItems, list);
const int documentIndex = extractedItems.front().documentIndex;
@@ -678,6 +704,35 @@ void PageItemModel::regroupAlternatingPages(const QModelIndexList& list, bool re
}
}
+void PageItemModel::undo()
+{
+ performUndoRedo(m_undoSteps, m_redoSteps);
+}
+
+void PageItemModel::performUndoRedo(std::vector& load,
+ std::vector& save)
+{
+ if (load.empty())
+ {
+ return;
+ }
+
+ save.emplace_back(getCurrentStep());
+ UndoRedoStep step = std::move(load.back());
+ load.pop_back();
+ updateUndoRedoSteps();
+
+ beginResetModel();
+ m_pageGroupItems = std::move(step.pageGroupItems);
+ m_trashBin = std::move(step.trashBin);
+ endResetModel();
+}
+
+void PageItemModel::redo()
+{
+ performUndoRedo(m_redoSteps, m_undoSteps);
+}
+
QItemSelection PageItemModel::getSelectionImpl(std::function filter) const
{
QItemSelection result;
@@ -707,6 +762,25 @@ QItemSelection PageItemModel::getSelectionImpl(std::function MAX_UNDO_REDO_STEPS)
+ {
+ m_undoSteps.erase(m_undoSteps.begin());
+ }
+
+ while (m_redoSteps.size() > MAX_UNDO_REDO_STEPS)
+ {
+ m_redoSteps.erase(m_redoSteps.begin());
+ }
+}
+
+void PageItemModel::clearUndoRedo()
+{
+ m_undoSteps.clear();
+ m_redoSteps.clear();
+}
+
void PageItemModel::createDocumentGroup(int index, const QModelIndex& insertIndex)
{
const DocumentItem& item = m_documents.at(index);
@@ -939,6 +1013,8 @@ bool PageItemModel::dropMimeData(const QMimeData* data, Qt::DropAction action, i
return false;
}
+ Modifier modifier(this);
+
int insertRow = rowCount(QModelIndex());
if (row > -1)
{
@@ -1140,7 +1216,28 @@ void PageItemModel::clear()
m_pageGroupItems.clear();
m_documents.clear();
m_trashBin.clear();
+ clearUndoRedo();
endResetModel();
}
+PageItemModel::Modifier::Modifier(PageItemModel* model) :
+ m_model(model)
+{
+ Q_ASSERT(model);
+
+ m_stateBeforeModification = m_model->getCurrentStep();
+}
+
+PageItemModel::Modifier::~Modifier()
+{
+ UndoRedoStep stateAfterModification = m_model->getCurrentStep();
+
+ if (m_stateBeforeModification != stateAfterModification)
+ {
+ m_model->m_undoSteps.emplace_back(std::move(m_stateBeforeModification));
+ m_model->m_redoSteps.clear();
+ m_model->updateUndoRedoSteps();
+ }
+}
+
} // namespace pdfdocpage
diff --git a/Pdf4QtDocPageOrganizer/pageitemmodel.h b/Pdf4QtDocPageOrganizer/pageitemmodel.h
index 72c0347..3662caa 100644
--- a/Pdf4QtDocPageOrganizer/pageitemmodel.h
+++ b/Pdf4QtDocPageOrganizer/pageitemmodel.h
@@ -186,20 +186,56 @@ public:
void regroupBookmarks(const QModelIndexList& list);
void regroupAlternatingPages(const QModelIndexList& list, bool reversed);
+ bool canUndo() const { return !m_undoSteps.empty(); }
+ bool canRedo() const { return !m_redoSteps.empty(); }
+
+ void undo();
+ void redo();
+
private:
+ static const int MAX_UNDO_REDO_STEPS = 10;
+
void createDocumentGroup(int index, const QModelIndex& insertIndex);
QString getGroupNameFromDocument(int index) const;
void updateItemCaptionAndTags(PageGroupItem& item) const;
void insertEmptyPage(const QModelIndex& index);
+ struct UndoRedoStep
+ {
+ auto operator<=>(const UndoRedoStep&) const = default;
+
+ std::vector pageGroupItems;
+ std::vector trashBin;
+ };
+
+ class Modifier
+ {
+ public:
+ explicit Modifier(PageItemModel* model);
+ ~Modifier();
+
+ private:
+ PageItemModel* m_model;
+ UndoRedoStep m_stateBeforeModification;
+ };
+
std::vector extractItems(std::vector& items, const QModelIndexList& selection) const;
QItemSelection getSelectionImpl(std::function filter) const;
+ UndoRedoStep getCurrentStep() const { return UndoRedoStep{ m_pageGroupItems, m_trashBin }; }
+ void updateUndoRedoSteps();
+ void clearUndoRedo();
+
+ void performUndoRedo(std::vector& load, std::vector& save);
+
std::vector m_pageGroupItems;
std::map m_documents;
std::map m_images;
std::vector m_trashBin;
+
+ std::vector m_undoSteps; // Oldest step is first, newest step is last
+ std::vector m_redoSteps;
};
} // namespace pdfdocpage
diff --git a/Pdf4QtDocPageOrganizer/resources.qrc b/Pdf4QtDocPageOrganizer/resources.qrc
index de380d4..6b0f04f 100644
--- a/Pdf4QtDocPageOrganizer/resources.qrc
+++ b/Pdf4QtDocPageOrganizer/resources.qrc
@@ -36,5 +36,7 @@
resources/regroup-bookmarks.svg
resources/regroup-even-odd.svg
resources/regroup-pairs.svg
+ resources/undo.svg
+ resources/redo.svg
diff --git a/Pdf4QtDocPageOrganizer/resources/redo.svg b/Pdf4QtDocPageOrganizer/resources/redo.svg
new file mode 100644
index 0000000..6954cd3
--- /dev/null
+++ b/Pdf4QtDocPageOrganizer/resources/redo.svg
@@ -0,0 +1,108 @@
+
+
+
+
diff --git a/Pdf4QtDocPageOrganizer/resources/undo.svg b/Pdf4QtDocPageOrganizer/resources/undo.svg
new file mode 100644
index 0000000..6954cd3
--- /dev/null
+++ b/Pdf4QtDocPageOrganizer/resources/undo.svg
@@ -0,0 +1,108 @@
+
+
+
+