From 7a40427d103f9b6615345a6d0c28effaedca2d57 Mon Sep 17 00:00:00 2001 From: Jakub Melka Date: Sun, 1 Aug 2021 11:19:14 +0200 Subject: [PATCH] DocPage Organizer: Undo/Redo --- Pdf4QtDocPageOrganizer/mainwindow.cpp | 22 +++++ Pdf4QtDocPageOrganizer/mainwindow.h | 3 + Pdf4QtDocPageOrganizer/mainwindow.ui | 27 ++++++ Pdf4QtDocPageOrganizer/pageitemmodel.cpp | 97 +++++++++++++++++++ Pdf4QtDocPageOrganizer/pageitemmodel.h | 36 ++++++++ Pdf4QtDocPageOrganizer/resources.qrc | 2 + Pdf4QtDocPageOrganizer/resources/redo.svg | 108 ++++++++++++++++++++++ Pdf4QtDocPageOrganizer/resources/undo.svg | 108 ++++++++++++++++++++++ 8 files changed, 403 insertions(+) create mode 100644 Pdf4QtDocPageOrganizer/resources/redo.svg create mode 100644 Pdf4QtDocPageOrganizer/resources/undo.svg 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 @@ + + + + + + + + + + + + image/svg+xml + + + + + Jakub Melka + + + + + + + + + + + + + + + + + + + + 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 @@ + + + + + + + + + + + + image/svg+xml + + + + + Jakub Melka + + + + + + + + + + + + + + + + + + + +