diff --git a/app/src/generalpage.cpp b/app/src/generalpage.cpp index 9a931b574a..77044ca5ea 100644 --- a/app/src/generalpage.cpp +++ b/app/src/generalpage.cpp @@ -100,6 +100,9 @@ GeneralPage::GeneralPage() : ui(new Ui::GeneralPage) ui->backgroundButtons->setId(ui->dotsBackgroundButton, 4); ui->backgroundButtons->setId(ui->weaveBackgroundButton, 5); + ui->undoRedoGroupApplyButton->setDisabled(true); + ui->undoRedoGroupCancelButton->setDisabled(true); + auto buttonClicked = static_cast(&QButtonGroup::buttonClicked); auto curIndexChanged = static_cast(&QComboBox::currentIndexChanged); auto spinValueChanged = static_cast(&QSpinBox::valueChanged); @@ -122,6 +125,10 @@ GeneralPage::GeneralPage() : ui(new Ui::GeneralPage) connect(ui->gridCheckBox, &QCheckBox::stateChanged, this, &GeneralPage::gridCheckBoxStateChanged); connect(ui->framePoolSizeSpin, spinValueChanged, this, &GeneralPage::frameCacheNumberChanged); connect(ui->invertScrollDirectionBox, &QCheckBox::stateChanged, this, &GeneralPage::invertScrollDirectionBoxStateChanged); + connect(ui->newUndoRedoCheckBox, &QCheckBox::stateChanged, this, &GeneralPage::newUndoRedoCheckBoxStateChanged); + connect(ui->undoStepsBox, spinValueChanged, this, &GeneralPage::undoRedoMaxStepsChanged); + connect(ui->undoRedoGroupApplyButton, &QPushButton::clicked, this, &GeneralPage::undoRedoApplyButtonPressed); + connect(ui->undoRedoGroupCancelButton, &QPushButton::clicked, this, &GeneralPage::undoRedoCancelButtonPressed); } GeneralPage::~GeneralPage() @@ -181,6 +188,12 @@ void GeneralPage::updateValues() QSignalBlocker b12(ui->framePoolSizeSpin); ui->framePoolSizeSpin->setValue(mManager->getInt(SETTING::FRAME_POOL_SIZE)); + QSignalBlocker bNewUndoRedoCheckBox(ui->newUndoRedoCheckBox); + ui->newUndoRedoCheckBox->setChecked(mManager->isOn(SETTING::NEW_UNDO_REDO_SYSTEM_ON)); + + QSignalBlocker bUndoRedoLimitSpinBox(ui->undoStepsBox); + ui->undoStepsBox->setValue(mManager->getInt(SETTING::UNDO_REDO_MAX_STEPS)); + int buttonIdx = 1; if (bgName == "checkerboard") buttonIdx = 1; else if (bgName == "white") buttonIdx = 2; @@ -318,3 +331,80 @@ void GeneralPage::invertScrollDirectionBoxStateChanged(int b) { mManager->set(SETTING::INVERT_SCROLL_ZOOM_DIRECTION, b != Qt::Unchecked); } + +void GeneralPage::newUndoRedoCheckBoxStateChanged() +{ + ui->undoRedoGroupApplyButton->setEnabled(canApplyOrCancelUndoRedoChanges()); + ui->undoRedoGroupCancelButton->setEnabled(canApplyOrCancelUndoRedoChanges()); +} + +void GeneralPage::undoRedoMaxStepsChanged() +{ + ui->undoRedoGroupApplyButton->setEnabled(canApplyOrCancelUndoRedoChanges()); + ui->undoRedoGroupCancelButton->setEnabled(canApplyOrCancelUndoRedoChanges()); +} + +bool GeneralPage::canApplyOrCancelUndoRedoChanges() const +{ + const bool newSystemIsOnCurrent = mManager->isOn(SETTING::NEW_UNDO_REDO_SYSTEM_ON); + const int maxUndoRedoStepNumCurrent = mManager->getInt(SETTING::UNDO_REDO_MAX_STEPS); + + if (newSystemIsOnCurrent != ui->newUndoRedoCheckBox->isChecked() + || maxUndoRedoStepNumCurrent != ui->undoStepsBox->value()) { + return true; + } else { + return false; + } +} + +void GeneralPage::undoRedoApplyButtonPressed() +{ + if (ui->undoStepsBox->value() != mManager->getInt(SETTING::UNDO_REDO_MAX_STEPS)) { + QMessageBox messageBox(this); + messageBox.setIcon(QMessageBox::Warning); + messageBox.setText(tr("Resets your current undo history")); + messageBox.setInformativeText(tr("Changing the maximum number of undo/redo steps resets your current undo/redo history. \n\nAre you sure you want to proceed?")); + messageBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No); + + if (messageBox.exec() == QMessageBox::Yes) { + mManager->set(SETTING::UNDO_REDO_MAX_STEPS, ui->undoStepsBox->value()); + } else { + ui->undoStepsBox->setValue(mManager->getInt(SETTING::UNDO_REDO_MAX_STEPS)); + } + } + + const bool systemIsOn = mManager->isOn(SETTING::NEW_UNDO_REDO_SYSTEM_ON); + if (ui->newUndoRedoCheckBox->isChecked() != systemIsOn) { + if (ui->newUndoRedoCheckBox->isChecked()) { + QMessageBox messageBox(this); + messageBox.setIcon(QMessageBox::Warning); + messageBox.setText(tr("Experimental feature!")); + messageBox.setInformativeText(tr("This feature is work in progress and may not currently allow for the same features as the current undo/redo system. Once enabled, you'll need to restart the application to start using it. \n\nDo you still want to try?")); + messageBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No); + + if (messageBox.exec() == QMessageBox::Yes) { + mManager->set(SETTING::NEW_UNDO_REDO_SYSTEM_ON, true); + } else { + ui->newUndoRedoCheckBox->setCheckState(Qt::Unchecked); + mManager->set(SETTING::NEW_UNDO_REDO_SYSTEM_ON, false); + } + } else { + QMessageBox messageBox(this); + messageBox.setIcon(QMessageBox::Information); + messageBox.setText(tr("The undo/redo system will be changed on the next launch of the application")); + messageBox.exec(); + mManager->set(SETTING::NEW_UNDO_REDO_SYSTEM_ON, false); + } + } + + ui->undoRedoGroupCancelButton->setDisabled(true); + ui->undoRedoGroupApplyButton->setDisabled(true); +} + +void GeneralPage::undoRedoCancelButtonPressed() +{ + ui->undoStepsBox->setValue(mManager->getInt(SETTING::UNDO_REDO_MAX_STEPS)); + ui->newUndoRedoCheckBox->setCheckState(mManager->isOn(SETTING::NEW_UNDO_REDO_SYSTEM_ON) ? Qt::Checked : Qt::Unchecked); + ui->undoRedoGroupCancelButton->setDisabled(true); + ui->undoRedoGroupApplyButton->setDisabled(true); +} diff --git a/app/src/generalpage.h b/app/src/generalpage.h index 0767090e03..2b951f7765 100644 --- a/app/src/generalpage.h +++ b/app/src/generalpage.h @@ -58,9 +58,15 @@ private slots: void backgroundChanged(QAbstractButton* button); void frameCacheNumberChanged(int value); void invertScrollDirectionBoxStateChanged(int b); + void newUndoRedoCheckBoxStateChanged(); + void undoRedoMaxStepsChanged(); + + void undoRedoApplyButtonPressed(); + void undoRedoCancelButtonPressed(); private: + bool canApplyOrCancelUndoRedoChanges() const; void updateSafeHelperTextEnabledState(); Ui::GeneralPage* ui = nullptr; diff --git a/app/src/mainwindow2.cpp b/app/src/mainwindow2.cpp index 8c138faf5c..0fbd23d794 100644 --- a/app/src/mainwindow2.cpp +++ b/app/src/mainwindow2.cpp @@ -51,11 +51,12 @@ GNU General Public License for more details. #include "soundmanager.h" #include "viewmanager.h" #include "selectionmanager.h" +#include "undoredomanager.h" #include "actioncommands.h" #include "fileformat.h" //contains constants used by Pencil File Format #include "util.h" -#include "backupelement.h" +#include "undoredocommand.h" // app headers #include "colorbox.h" @@ -263,10 +264,8 @@ void MainWindow2::createMenus() connect(ui->actionImport_Replace_Palette, &QAction::triggered, this, &MainWindow2::openPalette); //--- Edit Menu --- - connect(mEditor, &Editor::updateBackup, this, &MainWindow2::undoActSetText); - connect(ui->actionUndo, &QAction::triggered, mEditor, &Editor::undo); + replaceUndoRedoActions(); connect(ui->actionRemoveLastPolylineSegment, &QAction::triggered, static_cast(mEditor->tools()->getTool(POLYLINE)), &PolylineTool::removeLastPolylineSegment); - connect(ui->actionRedo, &QAction::triggered, mEditor, &Editor::redo); connect(ui->actionCut, &QAction::triggered, mEditor, &Editor::copyAndCut); connect(ui->actionCopy, &QAction::triggered, mEditor, &Editor::copy); connect(ui->actionPaste_Previous, &QAction::triggered, mEditor, &Editor::pasteFromPreviousFrame); @@ -465,6 +464,16 @@ void MainWindow2::createMenus() connect(mRecentFileMenu, &RecentFileMenu::loadRecentFile, this, &MainWindow2::openFile); } +void MainWindow2::replaceUndoRedoActions() +{ + ui->menuEdit->removeAction(ui->actionUndo); + ui->menuEdit->removeAction(ui->actionRedo); + ui->actionUndo = mEditor->undoRedo()->createUndoAction(this, ui->actionUndo->icon()); + ui->actionRedo = mEditor->undoRedo()->createRedoAction(this, ui->actionRedo->icon()); + ui->menuEdit->insertAction(ui->actionCut, ui->actionUndo); + ui->menuEdit->insertAction(ui->actionCut, ui->actionRedo); +} + void MainWindow2::setOpacity(int opacity) { mEditor->preference()->set(SETTING::WINDOW_OPACITY, 100 - opacity); @@ -473,11 +482,17 @@ void MainWindow2::setOpacity(int opacity) void MainWindow2::updateSaveState() { - const bool hasUnsavedChanges = mEditor->currentBackup() != mBackupAtSave; + const bool hasUnsavedChanges = mEditor->undoRedo()->hasUnsavedChanges(); setWindowModified(hasUnsavedChanges); ui->statusBar->updateModifiedStatus(hasUnsavedChanges); } +void MainWindow2::updateBackupActionState() +{ + mEditor->undoRedo()->updateUndoAction(ui->actionUndo); + mEditor->undoRedo()->updateRedoAction(ui->actionRedo); +} + void MainWindow2::openPegAlignDialog() { if (mPegAlign != nullptr) @@ -698,7 +713,7 @@ bool MainWindow2::openObject(const QString& strFilePath) progress.setValue(progress.maximum()); updateSaveState(); - undoActSetText(); + updateBackupActionState(); if (!QFileInfo(strFilePath).isWritable()) { @@ -779,7 +794,6 @@ bool MainWindow2::saveObject(QString strSavedFileName) mTimeLine->updateContent(); setWindowTitle(strSavedFileName.prepend("[*]")); - mBackupAtSave = mEditor->currentBackup(); updateSaveState(); progress.setValue(progress.maximum()); @@ -799,7 +813,7 @@ bool MainWindow2::saveDocument() bool MainWindow2::maybeSave() { - if (mEditor->currentBackup() == mBackupAtSave) + if (!mEditor->undoRedo()->hasUnsavedChanges()) { return true; } @@ -1046,7 +1060,8 @@ void MainWindow2::newObject() closeDialogs(); setWindowTitle(PENCIL_WINDOW_TITLE); - undoActSetText(); + + updateBackupActionState(); } bool MainWindow2::newObjectFromPresets(int presetIndex) @@ -1071,7 +1086,7 @@ bool MainWindow2::newObjectFromPresets(int presetIndex) setWindowTitle(PENCIL_WINDOW_TITLE); updateSaveState(); - undoActSetText(); + updateBackupActionState(); return true; } @@ -1322,35 +1337,6 @@ void MainWindow2::clearKeyboardShortcuts() } } -void MainWindow2::undoActSetText() -{ - if (mEditor->mBackupIndex < 0) - { - ui->actionUndo->setText(tr("Undo", "Menu item text")); - ui->actionUndo->setEnabled(false); - } - else - { - ui->actionUndo->setText(QString("%1 %2 %3").arg(tr("Undo", "Menu item text")) - .arg(mEditor->mBackupIndex + 1) - .arg(mEditor->mBackupList.at(mEditor->mBackupIndex)->undoText)); - ui->actionUndo->setEnabled(true); - } - - if (mEditor->mBackupIndex + 2 < mEditor->mBackupList.size()) - { - ui->actionRedo->setText(QString("%1 %2 %3").arg(tr("Redo", "Menu item text")) - .arg(mEditor->mBackupIndex + 2) - .arg(mEditor->mBackupList.at(mEditor->mBackupIndex + 1)->undoText)); - ui->actionRedo->setEnabled(true); - } - else - { - ui->actionRedo->setText(tr("Redo", "Menu item text")); - ui->actionRedo->setEnabled(false); - } -} - void MainWindow2::exportPalette() { QString filePath = FileDialog::getSaveFileName(this, FileType::PALETTE); @@ -1406,7 +1392,8 @@ void MainWindow2::openPalette() void MainWindow2::makeConnections(Editor* editor) { - connect(editor, &Editor::updateBackup, this, &MainWindow2::updateSaveState); + connect(editor->undoRedo(), &UndoRedoManager::didUpdateUndoStack, this, &MainWindow2::updateSaveState); + connect(editor->undoRedo(), &UndoRedoManager::didUpdateUndoStack, this, &MainWindow2::updateBackupActionState); connect(editor, &Editor::needDisplayInfo, this, &MainWindow2::displayMessageBox); connect(editor, &Editor::needDisplayInfoNoTitle, this, &MainWindow2::displayMessageBoxNoTitle); connect(editor->layers(), &LayerManager::currentLayerChanged, this, &MainWindow2::currentLayerChanged); @@ -1634,7 +1621,7 @@ void MainWindow2::startProjectRecovery(int result) Q_ASSERT(o); mEditor->setObject(o); updateSaveState(); - undoActSetText(); + updateBackupActionState(); const QString title = tr("Recovery Succeeded!"); const QString text = tr("Please save your work immediately to prevent loss of data"); @@ -1682,7 +1669,7 @@ void MainWindow2::createToolbars() mOverlayToolbar->setIconSize(QSize(22,22)); mViewToolbar->setIconSize(QSize(22,22)); mMainToolbar->setIconSize(QSize(22,22)); - + QToolButton* perspectiveLinesAngleButton = new QToolButton(this); perspectiveLinesAngleButton->setDefaultAction(ui->menuPerspectiveLinesAngle->menuAction()); perspectiveLinesAngleButton->setPopupMode(QToolButton::InstantPopup); diff --git a/app/src/mainwindow2.h b/app/src/mainwindow2.h index 9f1b683f6c..be2e334882 100644 --- a/app/src/mainwindow2.h +++ b/app/src/mainwindow2.h @@ -41,7 +41,7 @@ class ColorInspector; class RecentFileMenu; class ActionCommands; class ImportImageSeqDialog; -class BackupElement; +class UndoRedoCommand; class LayerOpacityDialog; class PegBarAlignmentDialog; class RepositionFramesDialog; @@ -65,7 +65,7 @@ class MainWindow2 : public QMainWindow Editor* mEditor = nullptr; public slots: - void undoActSetText(); + void updateBackupActionState(); void updateSaveState(); void openPegAlignDialog(); void openRepositionDialog(); @@ -124,6 +124,7 @@ private slots: void createDockWidgets(); void createMenus(); + void replaceUndoRedoActions(); void setupKeyboardShortcuts(); void clearKeyboardShortcuts(); bool loadMostRecent(); @@ -167,9 +168,6 @@ private slots: QToolBar* mViewToolbar = nullptr; QToolBar* mOverlayToolbar = nullptr; - // backup - BackupElement* mBackupAtSave = nullptr; - PegBarAlignmentDialog* mPegAlign = nullptr; RepositionFramesDialog* mReposDialog = nullptr; LayerOpacityDialog* mLayerOpacityDialog = nullptr; diff --git a/app/ui/generalpage.ui b/app/ui/generalpage.ui index 9c131144fc..1d96d9e204 100644 --- a/app/ui/generalpage.ui +++ b/app/ui/generalpage.ui @@ -26,9 +26,9 @@ 0 - -470 - 306 - 997 + -407 + 314 + 932 @@ -434,7 +434,7 @@ Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - + 6 @@ -442,51 +442,128 @@ 6 - + + + + + Memory Cache Budget + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + true + + + + + + + + 0 + 0 + + + + + 80 + 0 + + + + + 80 + 16777215 + + + + MB + + + 100 + + + 16000 + + + 1024 + + + + + + + + + + + + Undo/Redo + + + + - Memory Cache Budget - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - true + Enable New System (Experimental) - - - - 0 - 0 - + + + 0 - - - 80 - 0 - - - - - 80 - 16777215 - - - - MB - - - 100 - - - 16000 - - - 1024 + + + + How many steps you're allowed to undo/redo + + + Maximum Number of Undo/Redo Steps + + + + + + + + 80 + 16777215 + + + + 20 + + + 250 + + + 100 + + + + + + + + + 0 - + + + + Apply + + + + + + + Cancel + + + + diff --git a/core_lib/core_lib.pro b/core_lib/core_lib.pro index 54e396635c..c02e91abb9 100644 --- a/core_lib/core_lib.pro +++ b/core_lib/core_lib.pro @@ -38,12 +38,13 @@ HEADERS += \ src/graphics/vector/vectorimage.h \ src/graphics/vector/vectorselection.h \ src/graphics/vector/vertexref.h \ - src/interface/backupelement.h \ src/interface/editor.h \ src/interface/flowlayout.h \ + src/interface/legacybackupelement.h \ src/interface/recentfilemenu.h \ src/interface/scribblearea.h \ src/interface/backgroundwidget.h \ + src/interface/undoredocommand.h \ src/managers/basemanager.h \ src/managers/overlaymanager.h \ src/managers/clipboardmanager.h \ @@ -52,6 +53,7 @@ HEADERS += \ src/managers/layermanager.h \ src/managers/toolmanager.h \ src/managers/playbackmanager.h \ + src/managers/undoredomanager.h \ src/managers/viewmanager.h \ src/managers/preferencemanager.h \ src/managers/soundmanager.h \ @@ -126,12 +128,13 @@ SOURCES += src/graphics/bitmap/bitmapimage.cpp \ src/graphics/vector/vectorimage.cpp \ src/graphics/vector/vectorselection.cpp \ src/graphics/vector/vertexref.cpp \ - src/interface/backupelement.cpp \ src/interface/editor.cpp \ src/interface/flowlayout.cpp \ + src/interface/legacybackupelement.cpp \ src/interface/recentfilemenu.cpp \ src/interface/scribblearea.cpp \ src/interface/backgroundwidget.cpp \ + src/interface/undoredocommand.cpp \ src/managers/basemanager.cpp \ src/managers/overlaymanager.cpp \ src/managers/clipboardmanager.cpp \ @@ -141,6 +144,7 @@ SOURCES += src/graphics/bitmap/bitmapimage.cpp \ src/managers/toolmanager.cpp \ src/managers/preferencemanager.cpp \ src/managers/playbackmanager.cpp \ + src/managers/undoredomanager.cpp \ src/managers/viewmanager.cpp \ src/managers/soundmanager.cpp \ src/movieimporter.cpp \ diff --git a/core_lib/src/interface/editor.cpp b/core_lib/src/interface/editor.cpp index 71af79df34..14ad8e34d9 100644 --- a/core_lib/src/interface/editor.cpp +++ b/core_lib/src/interface/editor.cpp @@ -29,7 +29,7 @@ GNU General Public License for more details. #include "layerbitmap.h" #include "layervector.h" #include "layercamera.h" -#include "backupelement.h" +#include "undoredocommand.h" #include "colormanager.h" #include "filemanager.h" @@ -42,18 +42,17 @@ GNU General Public License for more details. #include "selectionmanager.h" #include "overlaymanager.h" #include "clipboardmanager.h" +#include "undoredomanager.h" #include "scribblearea.h" Editor::Editor(QObject* parent) : QObject(parent) { - mBackupIndex = -1; } Editor::~Editor() { // a lot more probably needs to be cleaned here... - clearUndoStack(); clearTemporary(); } @@ -70,6 +69,7 @@ bool Editor::init() mSelectionManager = new SelectionManager(this); mOverlayManager = new OverlayManager(this); mClipboardManager = new ClipboardManager(this); + mUndoRedoManager = new UndoRedoManager(this); mAllManagers = { @@ -82,7 +82,8 @@ bool Editor::init() mSoundManager, mSelectionManager, mOverlayManager, - mClipboardManager + mClipboardManager, + mUndoRedoManager }; for (BaseManager* pManager : mAllManagers) @@ -117,8 +118,11 @@ void Editor::setFps(int fps) void Editor::makeConnections() { connect(mPreferenceManager, &PreferenceManager::optionChanged, this, &Editor::settingUpdated); + connect(mUndoRedoManager, &UndoRedoManager::didUpdateUndoStack, this, &Editor::updateAutoSaveCounter); + connect(mPreferenceManager, &PreferenceManager::optionChanged, mUndoRedoManager, &UndoRedoManager::onSettingChanged); + // XXX: This is a hack to prevent crashes until #864 is done (see #1412) - connect(mLayerManager, &LayerManager::layerDeleted, this, &Editor::sanitizeBackupElementsAfterLayerDeletion); + connect(mLayerManager, &LayerManager::layerDeleted, mUndoRedoManager, &UndoRedoManager::sanitizeLegacyBackupElementsAfterLayerDeletion); connect(mLayerManager, &LayerManager::currentLayerWillChange, this, &Editor::onCurrentLayerWillChange); } @@ -169,337 +173,6 @@ void Editor::onCurrentLayerWillChange(int index) } } -BackupElement* Editor::currentBackup() -{ - if (mBackupIndex >= 0) - { - return mBackupList[mBackupIndex]; - } - return nullptr; -} - -void Editor::backup(const QString& undoText) -{ - KeyFrame* frame = nullptr; - if (mLastModifiedLayer > -1 && mLastModifiedFrame > 0) - { - if (layers()->currentLayer()->type() == Layer::SOUND) - { - frame = layers()->currentLayer()->getKeyFrameWhichCovers(mLastModifiedFrame); - if (frame != nullptr) - { - backup(mLastModifiedLayer, frame->pos(), undoText); - } - } - else - { - backup(mLastModifiedLayer, mLastModifiedFrame, undoText); - } - } - if (mLastModifiedLayer != layers()->currentLayerIndex() || mLastModifiedFrame != currentFrame()) - { - if (layers()->currentLayer()->type() == Layer::SOUND) - { - frame = layers()->currentLayer()->getKeyFrameWhichCovers(currentFrame()); - - if (frame != nullptr) - { - backup(layers()->currentLayerIndex(), frame->pos(), undoText); - } - } - else - { - backup(layers()->currentLayerIndex(), currentFrame(), undoText); - } - } -} - -bool Editor::backup(int backupLayer, int backupFrame, const QString& undoText) -{ - while (mBackupList.size() - 1 > mBackupIndex && !mBackupList.empty()) - { - delete mBackupList.takeLast(); - } - while (mBackupList.size() > 19) // we authorize only 20 levels of cancellation - { - delete mBackupList.takeFirst(); - mBackupIndex--; - } - - Layer* layer = mObject->getLayer(backupLayer); - if (layer != nullptr) - { - if (layer->type() == Layer::BITMAP) - { - BitmapImage* bitmapImage = static_cast(layer->getLastKeyFrameAtPosition(backupFrame)); - if (currentFrame() == 1) - { - int previous = layer->getPreviousKeyFramePosition(backupFrame); - bitmapImage = static_cast(layer->getKeyFrameAt(previous)); - } - if (bitmapImage != nullptr) - { - BackupBitmapElement* element = new BackupBitmapElement(bitmapImage); - element->layerId = layer->id(); - element->layer = backupLayer; - element->frame = bitmapImage->pos(); - element->undoText = undoText; - element->somethingSelected = select()->somethingSelected(); - element->mySelection = select()->mySelectionRect(); - element->rotationAngle = select()->myRotation(); - element->scaleX = select()->myScaleX(); - element->scaleY = select()->myScaleY(); - element->translation = select()->myTranslation(); - element->selectionAnchor = select()->currentTransformAnchor(); - - mBackupList.append(element); - mBackupIndex++; - } - else - { - return false; - } - } - else if (layer->type() == Layer::VECTOR) - { - VectorImage* vectorImage = static_cast(layer->getLastKeyFrameAtPosition(mFrame)); - if (vectorImage != nullptr) - { - BackupVectorElement* element = new BackupVectorElement(vectorImage); - element->layerId = layer->id(); - element->layer = backupLayer; - element->frame = vectorImage->pos(); - element->undoText = undoText; - element->somethingSelected = select()->somethingSelected(); - element->mySelection = select()->mySelectionRect(); - element->rotationAngle = select()->myRotation(); - element->scaleX = select()->myScaleX(); - element->scaleY = select()->myScaleY(); - element->translation = select()->myTranslation(); - element->selectionAnchor = select()->currentTransformAnchor(); - mBackupList.append(element); - mBackupIndex++; - } - else - { - return false; - } - } - else if (layer->type() == Layer::SOUND) - { - int previous = layer->getPreviousKeyFramePosition(backupFrame); - KeyFrame* key = layer->getLastKeyFrameAtPosition(backupFrame); - - // in case tracks overlap, get previous frame - if (key == nullptr) - { - KeyFrame* previousKey = layer->getKeyFrameAt(previous); - key = previousKey; - } - if (key != nullptr) { - SoundClip* clip = static_cast(key); - if (clip) - { - BackupSoundElement* element = new BackupSoundElement(clip); - element->layerId = layer->id(); - element->layer = backupLayer; - element->frame = backupFrame; - element->undoText = undoText; - element->fileName = clip->fileName(); - element->originalName = clip->soundClipName(); - mBackupList.append(element); - mBackupIndex++; - } - } - else - { - return false; - } - } - } - - updateAutoSaveCounter(); - - emit updateBackup(); - - return true; -} - -void Editor::sanitizeBackupElementsAfterLayerDeletion(int layerIndex) -{ - for (int i = 0; i < mBackupList.size(); i++) - { - BackupElement *backupElement = mBackupList[i]; - BackupBitmapElement *bitmapElement; - BackupVectorElement *vectorElement; - BackupSoundElement *soundElement; - switch (backupElement->type()) - { - case BackupElement::BITMAP_MODIF: - bitmapElement = qobject_cast(backupElement); - Q_ASSERT(bitmapElement); - if (bitmapElement->layer > layerIndex) - { - bitmapElement->layer--; - continue; - } - else if (bitmapElement->layer != layerIndex) - { - continue; - } - break; - case BackupElement::VECTOR_MODIF: - vectorElement = qobject_cast(backupElement); - Q_ASSERT(vectorElement); - if (vectorElement->layer > layerIndex) - { - vectorElement->layer--; - continue; - } - else if (vectorElement->layer != layerIndex) - { - continue; - } - break; - case BackupElement::SOUND_MODIF: - soundElement = qobject_cast(backupElement); - Q_ASSERT(soundElement); - if (soundElement->layer > layerIndex) - { - soundElement->layer--; - continue; - } - else if (soundElement->layer != layerIndex) - { - continue; - } - break; - default: - Q_UNREACHABLE(); - } - if (i <= mBackupIndex) - { - mBackupIndex--; - } - delete mBackupList.takeAt(i); - i--; - } -} - -void Editor::restoreKey() -{ - BackupElement* lastBackupElement = mBackupList[mBackupIndex]; - - Layer* layer = nullptr; - int frame = 0; - int layerIndex = 0; - if (lastBackupElement->type() == BackupElement::BITMAP_MODIF) - { - BackupBitmapElement* lastBackupBitmapElement = static_cast(lastBackupElement); - layerIndex = lastBackupBitmapElement->layer; - frame = lastBackupBitmapElement->frame; - layer = object()->findLayerById(lastBackupBitmapElement->layerId); - addKeyFrame(layerIndex, frame); - dynamic_cast(layer)->getBitmapImageAtFrame(frame)->paste(&lastBackupBitmapElement->bitmapImage); - emit frameModified(frame); - } - if (lastBackupElement->type() == BackupElement::VECTOR_MODIF) - { - BackupVectorElement* lastBackupVectorElement = static_cast(lastBackupElement); - layerIndex = lastBackupVectorElement->layer; - frame = lastBackupVectorElement->frame; - layer = object()->findLayerById(layerIndex); - addKeyFrame(layerIndex, frame); - dynamic_cast(layer)->getVectorImageAtFrame(frame)->paste(lastBackupVectorElement->vectorImage); - emit frameModified(frame); - } - if (lastBackupElement->type() == BackupElement::SOUND_MODIF) - { - QString strSoundFile; - BackupSoundElement* lastBackupSoundElement = static_cast(lastBackupElement); - layerIndex = lastBackupSoundElement->layer; - frame = lastBackupSoundElement->frame; - - strSoundFile = lastBackupSoundElement->fileName; - if (strSoundFile.isEmpty()) return; - KeyFrame* key = addKeyFrame(layerIndex, frame); - SoundClip* clip = dynamic_cast(key); - if (clip) - { - Status st = sound()->loadSound(clip, lastBackupSoundElement->fileName); - clip->setSoundClipName(lastBackupSoundElement->originalName); - if (!st.ok()) - { - removeKey(); - emit layers()->currentLayerChanged(layers()->currentLayerIndex()); // trigger timeline repaint. - } - } - } -} - -void Editor::undo() -{ - if (!mBackupList.empty() && mBackupIndex > -1) - { - if (mBackupIndex == mBackupList.size() - 1) - { - BackupElement* lastBackupElement = mBackupList[mBackupIndex]; - if (lastBackupElement->type() == BackupElement::BITMAP_MODIF) - { - BackupBitmapElement* lastBackupBitmapElement = static_cast(lastBackupElement); - if (backup(lastBackupBitmapElement->layer, lastBackupBitmapElement->frame, "NoOp")) - { - mBackupIndex--; - } - } - if (lastBackupElement->type() == BackupElement::VECTOR_MODIF) - { - BackupVectorElement* lastBackupVectorElement = static_cast(lastBackupElement); - if (backup(lastBackupVectorElement->layer, lastBackupVectorElement->frame, "NoOp")) - { - mBackupIndex--; - } - } - if (lastBackupElement->type() == BackupElement::SOUND_MODIF) - { - BackupSoundElement* lastBackupSoundElement = static_cast(lastBackupElement); - if (backup(lastBackupSoundElement->layer, lastBackupSoundElement->frame, "NoOp")) - { - mBackupIndex--; - } - } - } - - qDebug() << "Undo" << mBackupIndex; - mBackupList[mBackupIndex]->restore(this); - mBackupIndex--; - - emit updateBackup(); - } -} - -void Editor::redo() -{ - if (!mBackupList.empty() && mBackupIndex < mBackupList.size() - 2) - { - mBackupIndex++; - - mBackupList[mBackupIndex + 1]->restore(this); - emit updateBackup(); - } -} - -void Editor::clearUndoStack() -{ - mBackupIndex = -1; - while (!mBackupList.isEmpty()) - { - delete mBackupList.takeLast(); - } - mLastModifiedLayer = -1; - mLastModifiedFrame = -1; -} - void Editor::updateAutoSaveCounter() { if (mIsAutosave == false) @@ -733,9 +406,7 @@ void Editor::setModified(int layerNumber, int frameNumber) if (layer == nullptr) { return; } layer->setModified(frameNumber, true); - - mLastModifiedLayer = layerNumber; - mLastModifiedFrame = frameNumber; + undoRedo()->rememberLastModifiedFrame(layerNumber, frameNumber); emit frameModified(frameNumber); } @@ -880,7 +551,6 @@ Status Editor::setObject(Object* newObject) return Status::SAFE; } - clearUndoStack(); mObject.reset(newObject); updateObject(); @@ -1365,3 +1035,17 @@ bool Editor::canCopyVectorImage(const VectorImage* vectorImage) const { return vectorImage != nullptr && !vectorImage->isEmpty(); } + +void Editor::backup(const QString &undoText) +{ + undoRedo()->legacyBackup(undoText); + updateAutoSaveCounter(); +} + +bool Editor::backup(int layerNumber, int frameNumber, const QString &undoText) +{ + bool didBackup = undoRedo()->legacyBackup(layerNumber, frameNumber, undoText); + + updateAutoSaveCounter(); + return didBackup; +} diff --git a/core_lib/src/interface/editor.h b/core_lib/src/interface/editor.h index 6236f961c6..57add5cd3b 100644 --- a/core_lib/src/interface/editor.h +++ b/core_lib/src/interface/editor.h @@ -35,6 +35,7 @@ Q_MOC_INCLUDE("selectionmanager.h") Q_MOC_INCLUDE("soundmanager.h") Q_MOC_INCLUDE("overlaymanager.h") Q_MOC_INCLUDE("clipboardmanager.h") +Q_MOC_INCLUDE("undoredomanager.h") #endif class QClipboard; @@ -56,9 +57,10 @@ class SelectionManager; class SoundManager; class OverlayManager; class ClipboardManager; +class UndoRedoManager; class ScribbleArea; class TimeLine; -class BackupElement; +class UndoRedoCommand; class ActiveFramePool; class Layer; @@ -78,6 +80,8 @@ class Editor : public QObject Q_PROPERTY(SelectionManager* select READ select) Q_PROPERTY(OverlayManager* overlays READ overlays) Q_PROPERTY(ClipboardManager* clipboards READ clipboards) + Q_PROPERTY(UndoRedoManager* undoRedo READ undoRedo) + public: explicit Editor(QObject* parent = nullptr); @@ -98,6 +102,7 @@ class Editor : public QObject SelectionManager* select() const { return mSelectionManager; } OverlayManager* overlays() const { return mOverlayManager; } ClipboardManager* clipboards() const { return mClipboardManager; } + UndoRedoManager* undoRedo() const { return mUndoRedoManager; } Object* object() const { return mObject.get(); } Status openObject(const QString& strFilePath, const std::function& progressChanged, const std::function& progressRangeChanged); @@ -129,11 +134,6 @@ class Editor : public QObject void clipboardChanged(); - // backup - int mBackupIndex; - BackupElement* currentBackup(); - QList mBackupList; - signals: /** This should be emitted after scrubbing */ @@ -148,6 +148,8 @@ class Editor : public QObject void updateTimeLine() const; void updateTimeLineCached(); + void updateLayerCount(); + void updateBackup(); void objectLoaded(); @@ -174,7 +176,6 @@ class Editor : public QObject Status importImage(const QString& filePath); Status importAnimatedImage(const QString& filePath, int frameSpacing, const std::function& progressChanged, const std::function& wasCanceled); - void restoreKey(); void scrubNextKeyFrame(); void scrubPreviousKeyFrame(); @@ -188,27 +189,24 @@ class Editor : public QObject * @return The newly created keyframe or `nullptr` if the layer is not visible */ KeyFrame* addNewKey(); + /** + * Attempts to create a new keyframe at the given position and layer. If a keyframe already exists at the position, + * the new one will instead be created on the first empty frame that follows. If the layer is not visible, a warning + * dialog will be shown to the user and no new keyframe is created. + * @param layerNumber The number of an existing layer on which the keyframe is to be created + * @param frameIndex The desired position of the new keyframe + * @return The newly created keyframe or `nullptr` if the layer is not visible + * @see Editor::addNewKey() + */ + KeyFrame* addKeyFrame(int layerNumber, int frameIndex); void removeKey(); void switchVisibilityOfLayer(int layerNumber); void backup(const QString& undoText); bool backup(int layerNumber, int frameNumber, const QString& undoText); - /** - * Restores integrity of the backup elements after a layer has been deleted. - * Removes backup elements affecting the deleted layer and adjusts the layer - * index on other backup elements as necessary. - * - * @param layerIndex The index of the layer that was deleted - * - * @warning This serves as a temporary hack to prevent crashes until #864 is done - * (see #1412). - */ - void sanitizeBackupElementsAfterLayerDeletion(int layerIndex); void onCurrentLayerWillChange(int index); - void undo(); - void redo(); void copy(); void copyAndCut(); @@ -262,6 +260,7 @@ class Editor : public QObject SelectionManager* mSelectionManager = nullptr; OverlayManager* mOverlayManager = nullptr; ClipboardManager* mClipboardManager = nullptr; + UndoRedoManager* mUndoRedoManager = nullptr; std::vector< BaseManager* > mAllManagers; @@ -271,24 +270,10 @@ class Editor : public QObject bool mAutosaveNeverAskAgain = false; void makeConnections(); - /** - * Attempts to create a new keyframe at the given position and layer. If a keyframe already exists at the position, - * the new one will instead be created on the first empty frame that follows. If the layer is not visible, a warning - * dialog will be shown to the user and no new keyframe is created. - * @param layerNumber The number of an existing layer on which the keyframe is to be created - * @param frameIndex The desired position of the new keyframe - * @return The newly created keyframe or `nullptr` if the layer is not visible - * @see Editor::addNewKey() - */ - KeyFrame* addKeyFrame(int layerNumber, int frameIndex); QList mTemporaryDirs; - // backup - void clearUndoStack(); void updateAutoSaveCounter(); - int mLastModifiedFrame = -1; - int mLastModifiedLayer = -1; }; #endif diff --git a/core_lib/src/interface/backupelement.cpp b/core_lib/src/interface/legacybackupelement.cpp similarity index 90% rename from core_lib/src/interface/backupelement.cpp rename to core_lib/src/interface/legacybackupelement.cpp index b4718a7b5a..0fa0ea265a 100644 --- a/core_lib/src/interface/backupelement.cpp +++ b/core_lib/src/interface/legacybackupelement.cpp @@ -15,7 +15,7 @@ GNU General Public License for more details. */ -#include "backupelement.h" +#include "legacybackupelement.h" #include "editor.h" #include "layer.h" @@ -24,8 +24,9 @@ GNU General Public License for more details. #include "object.h" #include "selectionmanager.h" #include "layermanager.h" +#include "undoredomanager.h" -void BackupBitmapElement::restore(Editor* editor) +void BackupLegacyBitmapElement::restore(Editor* editor) { Layer* layer = editor->object()->findLayerById(this->layerId); @@ -37,7 +38,7 @@ void BackupBitmapElement::restore(Editor* editor) if (this->frame > 0 && layer->getKeyFrameAt(this->frame) == nullptr) { - editor->restoreKey(); + editor->undoRedo()->restoreLegacyKey(); } else { @@ -63,7 +64,7 @@ void BackupBitmapElement::restore(Editor* editor) emit editor->frameModified(this->frame); } -void BackupVectorElement::restore(Editor* editor) +void BackupLegacyVectorElement::restore(Editor* editor) { Layer* layer = editor->object()->findLayerById(this->layerId); for (int i = 0; i < editor->object()->getLayerCount(); i++) @@ -87,7 +88,7 @@ void BackupVectorElement::restore(Editor* editor) if (this->frame > 0 && layer->getKeyFrameAt(this->frame) == nullptr) { - editor->restoreKey(); + editor->undoRedo()->restoreLegacyKey(); } else { @@ -113,7 +114,7 @@ void BackupVectorElement::restore(Editor* editor) } -void BackupSoundElement::restore(Editor* editor) +void BackupLegacySoundElement::restore(Editor* editor) { Layer* layer = editor->object()->findLayerById(this->layerId); @@ -127,6 +128,6 @@ void BackupSoundElement::restore(Editor* editor) // TODO: soundclip won't restore if overlapping on first frame if (this->frame > 0 && layer->getKeyFrameAt(this->frame) == nullptr) { - editor->restoreKey(); + editor->undoRedo()->restoreLegacyKey(); } } diff --git a/core_lib/src/interface/backupelement.h b/core_lib/src/interface/legacybackupelement.h similarity index 67% rename from core_lib/src/interface/backupelement.h rename to core_lib/src/interface/legacybackupelement.h index 3f86860988..ad0b9bddcc 100644 --- a/core_lib/src/interface/backupelement.h +++ b/core_lib/src/interface/legacybackupelement.h @@ -15,8 +15,8 @@ GNU General Public License for more details. */ -#ifndef BACKUPELEMENT_H -#define BACKUPELEMENT_H +#ifndef LEGACYBACKUPELEMENT_H +#define LEGACYBACKUPELEMENT_H #include #include "vectorimage.h" @@ -25,7 +25,7 @@ GNU General Public License for more details. class Editor; -class BackupElement : public QObject +class LegacyBackupElement : public QObject { Q_OBJECT public: @@ -44,41 +44,41 @@ class BackupElement : public QObject virtual void restore(Editor*) { Q_ASSERT(false); } }; -class BackupBitmapElement : public BackupElement +class BackupLegacyBitmapElement : public LegacyBackupElement { Q_OBJECT public: - explicit BackupBitmapElement(BitmapImage* bi) { bitmapImage = *bi; } + explicit BackupLegacyBitmapElement(BitmapImage* bi) { bitmapImage = *bi; } int layerId = 0; int layer = 0; int frame = 0; BitmapImage bitmapImage; - int type() override { return BackupElement::BITMAP_MODIF; } + int type() override { return LegacyBackupElement::BITMAP_MODIF; } void restore(Editor*) override; }; -class BackupVectorElement : public BackupElement +class BackupLegacyVectorElement : public LegacyBackupElement { Q_OBJECT public: - explicit BackupVectorElement(VectorImage* vi) { vectorImage = *vi; } + explicit BackupLegacyVectorElement(VectorImage* vi) { vectorImage = *vi; } int layerId = 0; int layer = 0; int frame = 0; VectorImage vectorImage; - int type() override { return BackupElement::VECTOR_MODIF; } + int type() override { return LegacyBackupElement::VECTOR_MODIF; } void restore(Editor*) override; }; -class BackupSoundElement : public BackupElement +class BackupLegacySoundElement : public LegacyBackupElement { Q_OBJECT public: - explicit BackupSoundElement(SoundClip* sound) { clip = *sound; } + explicit BackupLegacySoundElement(SoundClip* sound) { clip = *sound; } int layerId = 0; int layer = 0; @@ -86,8 +86,8 @@ class BackupSoundElement : public BackupElement SoundClip clip; QString fileName, originalName; - int type() override { return BackupElement::SOUND_MODIF; } + int type() override { return LegacyBackupElement::SOUND_MODIF; } void restore( Editor* ) override; }; -#endif // BACKUPELEMENT_H +#endif // LEGACYBACKUPELEMENT_H diff --git a/core_lib/src/interface/undoredocommand.cpp b/core_lib/src/interface/undoredocommand.cpp new file mode 100644 index 0000000000..8439fd8c54 --- /dev/null +++ b/core_lib/src/interface/undoredocommand.cpp @@ -0,0 +1,196 @@ +/* + +Pencil2D - Traditional Animation Software +Copyright (C) 2005-2007 Patrick Corrieri & Pascal Naidon +Copyright (C) 2008-2009 Mj Mendoza IV +Copyright (C) 2012-2020 Matthew Chiawen Chang + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; version 2 of the License. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +*/ + +#include + +#include "layermanager.h" +#include "selectionmanager.h" + +#include "layersound.h" +#include "layerbitmap.h" +#include "layervector.h" + +#include "editor.h" +#include "undoredocommand.h" + +UndoRedoCommand::UndoRedoCommand(Editor* editor, QUndoCommand* parent) : QUndoCommand(parent) +{ + qDebug() << "backupElement created"; + mEditor = editor; +} + +UndoRedoCommand::~UndoRedoCommand() +{ +} + +BitmapReplaceCommand::BitmapReplaceCommand(const BitmapImage* undoBitmap, + const int undoLayerId, + const QString& description, + Editor *editor, + QUndoCommand *parent) : UndoRedoCommand(editor, parent) +{ + + this->undoBitmap = *undoBitmap; + this->undoLayerId = undoLayerId; + + Layer* layer = editor->layers()->currentLayer(); + redoLayerId = layer->id(); + redoBitmap = *static_cast(layer)-> + getBitmapImageAtFrame(editor->currentFrame()); + + setText(description); +} + +void BitmapReplaceCommand::undo() +{ + QUndoCommand::undo(); + + Layer* layer = editor()->layers()->findLayerById(undoLayerId); + static_cast(layer)->replaceKeyFrame(&undoBitmap); + + editor()->scrubTo(undoBitmap.pos()); +} + +void BitmapReplaceCommand::redo() +{ + QUndoCommand::redo(); + + // Ignore automatic redo when added to undo stack + if (isFirstRedo()) { setFirstRedo(false); return; } + + Layer* layer = editor()->layers()->findLayerById(redoLayerId); + static_cast(layer)->replaceKeyFrame(&redoBitmap); + + editor()->scrubTo(redoBitmap.pos()); +} + +VectorReplaceCommand::VectorReplaceCommand(const VectorImage* undoVector, + const int undoLayerId, + const QString& description, + Editor* editor, + QUndoCommand* parent) : UndoRedoCommand(editor, parent) +{ + + this->undoVector = *undoVector; + this->undoLayerId = undoLayerId; + Layer* layer = editor->layers()->currentLayer(); + redoLayerId = layer->id(); + redoVector = *static_cast(layer)-> + getVectorImageAtFrame(editor->currentFrame()); + + setText(description); +} + +void VectorReplaceCommand::undo() +{ + QUndoCommand::undo(); + + Layer* layer = editor()->layers()->findLayerById(undoLayerId); + + static_cast(layer)->replaceKeyFrame(&undoVector); + + editor()->scrubTo(undoVector.pos()); +} + +void VectorReplaceCommand::redo() +{ + QUndoCommand::redo(); + + // Ignore automatic redo when added to undo stack + if (isFirstRedo()) { setFirstRedo(false); return; } + + Layer* layer = editor()->layers()->findLayerById(redoLayerId); + + static_cast(layer)->replaceKeyFrame(&redoVector); + + editor()->scrubTo(redoVector.pos()); +} + +TransformCommand::TransformCommand(const QRectF& undoSelectionRect, + const QPointF& undoTranslation, + const qreal undoRotationAngle, + const qreal undoScaleX, + const qreal undoScaleY, + const QPointF& undoTransformAnchor, + const bool roundPixels, + const QString& description, + Editor* editor, + QUndoCommand *parent) : UndoRedoCommand(editor, parent) +{ + this->roundPixels = roundPixels; + + this->undoSelectionRect = undoSelectionRect; + this->undoAnchor = undoTransformAnchor; + this->undoTranslation = undoTranslation; + this->undoRotationAngle = undoRotationAngle; + this->undoScaleX = undoScaleX; + this->undoScaleY = undoScaleY; + + auto selectMan = editor->select(); + redoSelectionRect = selectMan->mySelectionRect(); + redoAnchor = selectMan->currentTransformAnchor(); + redoTranslation = selectMan->myTranslation(); + redoRotationAngle = selectMan->myRotation(); + redoScaleX = selectMan->myScaleX(); + redoScaleY = selectMan->myScaleY(); + + setText(description); +} + +void TransformCommand::undo() +{ + apply(undoSelectionRect, + undoTranslation, + undoRotationAngle, + undoScaleX, + undoScaleY, + undoAnchor, + roundPixels); +} + +void TransformCommand::redo() +{ + // Ignore automatic redo when added to undo stack + if (isFirstRedo()) { setFirstRedo(false); return; } + + apply(redoSelectionRect, + redoTranslation, + redoRotationAngle, + redoScaleX, + redoScaleY, + redoAnchor, + roundPixels); +} + +void TransformCommand::apply(const QRectF& selectionRect, + const QPointF& translation, + const qreal rotationAngle, + const qreal scaleX, + const qreal scaleY, + const QPointF& selectionAnchor, + const bool roundPixels) +{ + auto selectMan = editor()->select(); + selectMan->setSelection(selectionRect, roundPixels); + selectMan->setTransformAnchor(selectionAnchor); + selectMan->setTranslation(translation); + selectMan->setRotation(rotationAngle); + selectMan->setScale(scaleX, scaleY); + + selectMan->calculateSelectionTransformation(); +} diff --git a/core_lib/src/interface/undoredocommand.h b/core_lib/src/interface/undoredocommand.h new file mode 100644 index 0000000000..05c4b4766c --- /dev/null +++ b/core_lib/src/interface/undoredocommand.h @@ -0,0 +1,143 @@ +/* + +Pencil2D - Traditional Animation Software +Copyright (C) 2005-2007 Patrick Corrieri & Pascal Naidon +Copyright (C) 2008-2009 Mj Mendoza IV +Copyright (C) 2012-2020 Matthew Chiawen Chang + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; version 2 of the License. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +*/ + +#ifndef UNDOREDOCOMMAND_H +#define UNDOREDOCOMMAND_H + +#include +#include + +#include "bitmapimage.h" +#include "vectorimage.h" + +class Editor; +class UndoRedoManager; +class PreferenceManager; +class SoundClip; +class Camera; +class Layer; +class KeyFrame; +class TransformCommand; + +class UndoRedoCommand : public QUndoCommand +{ +public: + explicit UndoRedoCommand(Editor* editor, QUndoCommand* parent = nullptr); + ~UndoRedoCommand() override; + +protected: + Editor* editor() { return mEditor; } + + bool isFirstRedo() const { return mIsFirstRedo; } + void setFirstRedo(const bool state) { mIsFirstRedo = state; } + +private: + Editor* mEditor = nullptr; + bool mIsFirstRedo = true; +}; + +class BitmapReplaceCommand : public UndoRedoCommand +{ + +public: + BitmapReplaceCommand(const BitmapImage* backupBitmap, + const int undoLayerId, + const QString& description, + Editor* editor, + QUndoCommand* parent = nullptr); + + void undo() override; + void redo() override; + +private: + int undoLayerId = 0; + int redoLayerId = 0; + + BitmapImage undoBitmap; + BitmapImage redoBitmap; +}; + +class VectorReplaceCommand : public UndoRedoCommand +{ +public: + VectorReplaceCommand(const VectorImage* undoVector, + const int undoLayerId, + const QString& description, + Editor* editor, + QUndoCommand* parent = nullptr); + + void undo() override; + void redo() override; + +private: + int undoLayerId = 0; + int redoLayerId = 0; + + VectorImage undoVector; + VectorImage redoVector; +}; + +class TransformCommand : public UndoRedoCommand + +{ +public: + TransformCommand(const QRectF& undoSelectionRect, + const QPointF& undoTranslation, + const qreal undoRotationAngle, + const qreal undoScaleX, + const qreal undoScaleY, + const QPointF& undoTransformAnchor, + const bool roundPixels, + const QString& description, + Editor* editor, + QUndoCommand* parent = nullptr); + + void undo() override; + void redo() override; + +private: + void apply(const QRectF& selectionRect, + const QPointF& translation, + const qreal rotationAngle, + const qreal scaleX, + const qreal scaleY, + const QPointF& selectionAnchor, + const bool roundPixels); + + QRectF undoSelectionRect; + QRectF redoSelectionRect; + + QPointF undoAnchor; + QPointF redoAnchor; + + QPointF undoTranslation; + QPointF redoTranslation; + + qreal undoScaleX; + qreal undoScaleY; + + qreal redoScaleX; + qreal redoScaleY; + + qreal undoRotationAngle; + qreal redoRotationAngle; + + bool roundPixels; +}; + +#endif // UNDOREDOCOMMAND_H diff --git a/core_lib/src/managers/layermanager.cpp b/core_lib/src/managers/layermanager.cpp index 43ee5c65de..8d67722dbd 100644 --- a/core_lib/src/managers/layermanager.cpp +++ b/core_lib/src/managers/layermanager.cpp @@ -104,6 +104,11 @@ Layer* LayerManager::findLayerByName(QString sName, Layer::LAYER_TYPE type) return object()->findLayerByName(sName, type); } +Layer* LayerManager::findLayerById(int layerId) +{ + return object()->findLayerById(layerId); +} + int LayerManager::currentLayerIndex() { return editor()->currentLayerIndex(); diff --git a/core_lib/src/managers/layermanager.h b/core_lib/src/managers/layermanager.h index 6ab2e6009e..d7031f21d8 100644 --- a/core_lib/src/managers/layermanager.h +++ b/core_lib/src/managers/layermanager.h @@ -44,6 +44,7 @@ class LayerManager : public BaseManager Layer* getLayer(int index); LayerCamera* getCameraLayerBelow(int layerIndex) const; Layer* findLayerByName(QString sName, Layer::LAYER_TYPE type = Layer::UNDEFINED); + Layer* findLayerById(int layerId); Layer* getLastCameraLayer(); int currentLayerIndex(); void setCurrentLayer(int nIndex); diff --git a/core_lib/src/managers/preferencemanager.cpp b/core_lib/src/managers/preferencemanager.cpp index b889f36868..342de38181 100644 --- a/core_lib/src/managers/preferencemanager.cpp +++ b/core_lib/src/managers/preferencemanager.cpp @@ -92,6 +92,8 @@ void PreferenceManager::loadPrefs() set(SETTING::LAYOUT_LOCK, settings.value(SETTING_LAYOUT_LOCK, false).toBool()); set(SETTING::FRAME_POOL_SIZE, settings.value(SETTING_FRAME_POOL_SIZE, 1024).toInt()); + set(SETTING::NEW_UNDO_REDO_SYSTEM_ON, settings.value(SETTING_NEW_UNDO_REDO_ON, false).toBool()); + set(SETTING::UNDO_REDO_MAX_STEPS, settings.value(SETTING_UNDO_REDO_MAX_STEPS, 100).toInt()); set(SETTING::FPS, settings.value(SETTING_FPS, 12).toInt()); set(SETTING::FIELD_W, settings.value(SETTING_FIELD_W, 800).toInt()); @@ -311,6 +313,9 @@ void PreferenceManager::set(SETTING option, int value) case SETTING::FRAME_POOL_SIZE: settings.setValue(SETTING_FRAME_POOL_SIZE, value); break; + case SETTING::UNDO_REDO_MAX_STEPS: + settings.setValue(SETTING_UNDO_REDO_MAX_STEPS, value); + break; case SETTING::DRAW_ON_EMPTY_FRAME_ACTION: settings.setValue( SETTING_DRAW_ON_EMPTY_FRAME_ACTION, value); break; @@ -463,6 +468,9 @@ void PreferenceManager::set(SETTING option, bool value) case SETTING::LOAD_DEFAULT_PRESET: settings.setValue(SETTING_LOAD_DEFAULT_PRESET, value); break; + case SETTING::NEW_UNDO_REDO_SYSTEM_ON: + settings.setValue(SETTING_NEW_UNDO_REDO_ON, value); + break; default: Q_ASSERT(false); break; diff --git a/core_lib/src/managers/undoredomanager.cpp b/core_lib/src/managers/undoredomanager.cpp new file mode 100644 index 0000000000..7dea4748fe --- /dev/null +++ b/core_lib/src/managers/undoredomanager.cpp @@ -0,0 +1,689 @@ +/* + +Pencil2D - Traditional Animation Software +Copyright (C) 2005-2007 Patrick Corrieri & Pascal Naidon +Copyright (C) 2008-2009 Mj Mendoza IV +Copyright (C) 2012-2020 Matthew Chiawen Chang + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; version 2 of the License. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +*/ + +#include "object.h" +#include "editor.h" + +#include +#include +#include + + +#include "preferencemanager.h" +#include "layermanager.h" +#include "soundmanager.h" +#include "undoredomanager.h" +#include "selectionmanager.h" + +#include "undoredocommand.h" +#include "legacybackupelement.h" + +#include "layerbitmap.h" +#include "layervector.h" +#include "layersound.h" + + +#include "bitmapimage.h" +#include "vectorimage.h" +#include "soundclip.h" + +UndoRedoManager::UndoRedoManager(Editor* editor) : BaseManager(editor, "UndoRedoManager") +{ + qDebug() << "UndoRedoManager: created"; +} + +UndoRedoManager::~UndoRedoManager() +{ + if (!mNewBackupSystemEnabled) + { + clearStack(); + } + qDebug() << "UndoRedoManager: destroyed"; +} + +bool UndoRedoManager::init() +{ + qDebug() << "UndoRedoManager: init"; + + mUndoStack.setUndoLimit(editor()->preference()->getInt(SETTING::UNDO_REDO_MAX_STEPS)); + mNewBackupSystemEnabled = editor()->preference()->isOn(SETTING::NEW_UNDO_REDO_SYSTEM_ON); + + return true; +} + +void UndoRedoManager::onSettingChanged(SETTING setting) +{ + if (setting == SETTING::UNDO_REDO_MAX_STEPS) { + // The stack needs to be cleared in order to change the undo redo limit + clearStack(); + qDebug() << "updated undo stack limit"; + mUndoStack.setUndoLimit(editor()->preference()->getInt(SETTING::UNDO_REDO_MAX_STEPS)); + } +} + +Status UndoRedoManager::load(Object* /*o*/) +{ + clearStack(); + return Status::OK; +} + +Status UndoRedoManager::save(Object* /*o*/) +{ + if (mNewBackupSystemEnabled) { + mUndoStack.setClean(); + } else { + mLegacyBackupAtSave = mLegacyBackupList[mLegacyBackupIndex]; + } + return Status::OK; +} + +void UndoRedoManager::record(const UndoSaveState*& undoState, const QString& description) +{ + if (!mNewBackupSystemEnabled) { + return; + } + + if (!undoState) { + return; + } + + switch (undoState->recordType) + { + case UndoRedoRecordType::KEYFRAME_MODIFY: { + replaceKeyFrame(*undoState, description); + break; + } + default: { + QString reason("Unhandled case for: "); + reason.append(description); + Q_ASSERT_X(false, "UndoRedoManager::record", qPrintable(reason)); + break; + } + } + + + // The save state has now been used and should be invalidated so we can't use it again. + delete undoState; + undoState = nullptr; +} + +bool UndoRedoManager::hasUnsavedChanges() const +{ + if (mNewBackupSystemEnabled) { + return !mUndoStack.isClean(); + } else { + if (mLegacyBackupIndex >= 0) { + return mLegacyBackupAtSave != mLegacyBackupList[mLegacyBackupIndex]; + } + return false; + } +} + +void UndoRedoManager::pushCommand(QUndoCommand* command) +{ + mUndoStack.push(command); + + emit didUpdateUndoStack(); +} + +void UndoRedoManager::replaceKeyFrame(const UndoSaveState& undoState, const QString& description) +{ + if (undoState.layerType == Layer::BITMAP) { + replaceBitmap(undoState, description); + } else if (undoState.layerType == Layer::VECTOR) { + replaceVector(undoState, description); + } else { + // Implement other cases + } +} + + +void UndoRedoManager::replaceBitmap(const UndoSaveState& undoState, const QString& description) +{ + if (undoState.keyframe == nullptr || undoState.layerType != Layer::BITMAP) { return; } + BitmapReplaceCommand* element = new BitmapReplaceCommand(static_cast(undoState.keyframe.get()), + undoState.layerId, + description, + editor()); + + const SelectionSaveState* selectionState = undoState.selectionState.get(); + new TransformCommand(selectionState->bounds, + selectionState->translation, + selectionState->rotationAngle, + selectionState->scaleX, + selectionState->scaleY, + selectionState->anchor, + true, // roundPixels + description, + editor(), element); + + pushCommand(element); +} + +void UndoRedoManager::replaceVector(const UndoSaveState& undoState, const QString& description) +{ + if (undoState.keyframe == nullptr || undoState.layerType != Layer::VECTOR) { return; } + VectorReplaceCommand* element = new VectorReplaceCommand(static_cast(undoState.keyframe.get()), + undoState.layerId, + description, + editor()); + + const SelectionSaveState* selectionState = undoState.selectionState.get(); + new TransformCommand(selectionState->bounds, + selectionState->translation, + selectionState->rotationAngle, + selectionState->scaleX, + selectionState->scaleY, + selectionState->anchor, + false, // Round pixels + description, + editor(), element); + pushCommand(element); +} + +const UndoSaveState* UndoRedoManager::state(UndoRedoRecordType recordType) const +{ + if (!mNewBackupSystemEnabled) { + return nullptr; + } + + switch (recordType) + { + case UndoRedoRecordType::KEYFRAME_MODIFY: { + return savedKeyFrameState(); + default: + return nullptr; + } + } +} + +const UndoSaveState* UndoRedoManager::savedKeyFrameState() const +{ + UndoSaveState* undoSaveState = new UndoSaveState(); + undoSaveState->recordType = UndoRedoRecordType::KEYFRAME_MODIFY; + + const Layer* layer = editor()->layers()->currentLayer(); + undoSaveState->layerType = layer->type(); + undoSaveState->layerId = layer->id(); + + if (layer->type() == Layer::BITMAP || layer->type() == Layer::VECTOR) { + auto selectMan = editor()->select(); + undoSaveState->selectionState = std::unique_ptr( new SelectionSaveState( + selectMan->mySelectionRect(), + selectMan->myRotation(), + selectMan->myScaleX(), + selectMan->myScaleY(), + selectMan->myTranslation(), + selectMan->currentTransformAnchor()) + ); + } + + const int frameIndex = editor()->currentFrame(); + if (layer->keyExists(frameIndex)) + { + undoSaveState->keyframe = std::unique_ptr(layer->getLastKeyFrameAtPosition(frameIndex)->clone()); + } + else if (layer->getKeyFrameWhichCovers(frameIndex) != nullptr) + { + undoSaveState->keyframe = std::unique_ptr(layer->getKeyFrameWhichCovers(frameIndex)->clone()); + } + + return undoSaveState; +} + +QAction* UndoRedoManager::createUndoAction(QObject* parent, const QIcon& icon) +{ + QAction* undoAction = nullptr; + if (mNewBackupSystemEnabled) { + undoAction = mUndoStack.createUndoAction(parent); + } else { + undoAction = new QAction(parent); + undoAction->setText(tr("Undo")); + undoAction->setDisabled(true); + } + undoAction->setIcon(icon); + + if (mNewBackupSystemEnabled) { + // The new system takes care of this automatically + } else { + connect(undoAction, &QAction::triggered, this, &UndoRedoManager::legacyUndo); + } + return undoAction; +} + +QAction* UndoRedoManager::createRedoAction(QObject* parent, const QIcon& icon) +{ + QAction* redoAction = nullptr; + if (mNewBackupSystemEnabled) { + redoAction = mUndoStack.createRedoAction(parent); + } else { + redoAction = new QAction(parent); + redoAction->setText(tr("Redo")); + redoAction->setDisabled(true); + } + redoAction->setIcon(icon); + + if (mNewBackupSystemEnabled) { + // The new system takes care of this automatically + } else { + connect(redoAction, &QAction::triggered, this, &UndoRedoManager::legacyRedo); + } + return redoAction; +} + +void UndoRedoManager::updateUndoAction(QAction* undoAction) +{ + if (mNewBackupSystemEnabled) { + // Not used + // function can be removed when we have replaced the legacy system + } else { + if (mLegacyBackupIndex < 0) + { + undoAction->setText(tr("Undo", "Menu item text")); + undoAction->setEnabled(false); + qDebug() << undoAction->text(); + } + else + { + undoAction->setText(QString("%1 %2 %3").arg(tr("Undo", "Menu item text")) + .arg(mLegacyBackupIndex + 1) + .arg(mLegacyBackupList.at(mLegacyBackupIndex)->undoText)); + undoAction->setIconText(QString("%1 %2 %3").arg(tr("Undo", "Menu item text")) + .arg(mLegacyBackupIndex + 1) + .arg(mLegacyBackupList.at(mLegacyBackupIndex)->undoText)); + undoAction->setEnabled(true); + qDebug() << undoAction->text(); + } + } +} + +void UndoRedoManager::updateRedoAction(QAction* redoAction) +{ + if (mNewBackupSystemEnabled) { + // Not used + // function can be removed when we have replaced the legacy system + } else { + if (mLegacyBackupIndex + 2 < mLegacyBackupList.size()) + { + redoAction->setText(QString("%1 %2 %3").arg(tr("Redo", "Menu item text")) + .arg(mLegacyBackupIndex + 2) + .arg(mLegacyBackupList.at(mLegacyBackupIndex + 1)->undoText)); + redoAction->setEnabled(true); + } + else + { + redoAction->setText(tr("Redo", "Menu item text")); + redoAction->setEnabled(false); + } + } +} + +void UndoRedoManager::clearStack() +{ + if (mNewBackupSystemEnabled) { + mUndoStack.clear(); + } else { + mLegacyBackupIndex = -1; + while (!mLegacyBackupList.isEmpty()) + { + delete mLegacyBackupList.takeLast(); + } + mLegacyLastModifiedLayer = -1; + mLegacyLastModifiedFrame = -1; + } +} + +// Legacy backup system + +void UndoRedoManager::legacyBackup(const QString& undoText) +{ + + if (mNewBackupSystemEnabled) { + return; + } + + KeyFrame* frame = nullptr; + int currentFrame = editor()->currentFrame(); + if (mLegacyLastModifiedLayer > -1 && mLegacyLastModifiedFrame > 0) + { + if (editor()->layers()->currentLayer()->type() == Layer::SOUND) + { + frame = editor()->layers()->currentLayer()->getKeyFrameWhichCovers(mLegacyLastModifiedFrame); + if (frame != nullptr) + { + legacyBackup(mLegacyLastModifiedLayer, frame->pos(), undoText); + } + } + else + { + legacyBackup(mLegacyLastModifiedLayer, mLegacyLastModifiedFrame, undoText); + } + } + if (mLegacyLastModifiedLayer != editor()->layers()->currentLayerIndex() || mLegacyLastModifiedFrame != currentFrame) + { + if (editor()->layers()->currentLayer()->type() == Layer::SOUND) + { + frame = editor()->layers()->currentLayer()->getKeyFrameWhichCovers(currentFrame); + + if (frame != nullptr) + { + legacyBackup(editor()->layers()->currentLayerIndex(), frame->pos(), undoText); + } + } + else + { + legacyBackup(editor()->layers()->currentLayerIndex(), currentFrame, undoText); + } + } +} + +bool UndoRedoManager::legacyBackup(int backupLayer, int backupFrame, const QString& undoText) +{ + if (mNewBackupSystemEnabled) { + return false; + } + + while (mLegacyBackupList.size() - 1 > mLegacyBackupIndex && !mLegacyBackupList.empty()) + { + delete mLegacyBackupList.takeLast(); + } + while (mLegacyBackupList.size() >= editor()->preference()->getInt(SETTING::UNDO_REDO_MAX_STEPS)) + { + delete mLegacyBackupList.takeFirst(); + mLegacyBackupIndex--; + } + + Layer* layer = editor()->layers()->getLayer(backupLayer); + int currentFrame = editor()->currentFrame(); + if (layer != nullptr) + { + if (layer->type() == Layer::BITMAP) + { + BitmapImage* bitmapImage = static_cast(layer->getLastKeyFrameAtPosition(backupFrame)); + if (currentFrame == 1) + { + int previous = layer->getPreviousKeyFramePosition(backupFrame); + bitmapImage = static_cast(layer->getKeyFrameAt(previous)); + } + if (bitmapImage != nullptr) + { + BackupLegacyBitmapElement* element = new BackupLegacyBitmapElement(bitmapImage); + element->layerId = layer->id(); + element->layer = backupLayer; + element->frame = bitmapImage->pos(); + element->undoText = undoText; + element->somethingSelected = editor()->select()->somethingSelected(); + element->mySelection = editor()->select()->mySelectionRect(); + element->rotationAngle = editor()->select()->myRotation(); + element->scaleX = editor()->select()->myScaleX(); + element->scaleY = editor()->select()->myScaleY(); + element->translation = editor()->select()->myTranslation(); + element->selectionAnchor = editor()->select()->currentTransformAnchor(); + + mLegacyBackupList.append(element); + mLegacyBackupIndex++; + } + else + { + return false; + } + } + else if (layer->type() == Layer::VECTOR) + { + VectorImage* vectorImage = static_cast(layer->getLastKeyFrameAtPosition(backupFrame)); + if (vectorImage != nullptr) + { + BackupLegacyVectorElement* element = new BackupLegacyVectorElement(vectorImage); + element->layerId = layer->id(); + element->layer = backupLayer; + element->frame = vectorImage->pos(); + element->undoText = undoText; + element->somethingSelected = editor()->select()->somethingSelected(); + element->mySelection = editor()->select()->mySelectionRect(); + element->rotationAngle = editor()->select()->myRotation(); + element->scaleX = editor()->select()->myScaleX(); + element->scaleY = editor()->select()->myScaleY(); + element->translation = editor()->select()->myTranslation(); + element->selectionAnchor = editor()->select()->currentTransformAnchor(); + mLegacyBackupList.append(element); + mLegacyBackupIndex++; + } + else + { + return false; + } + } + else if (layer->type() == Layer::SOUND) + { + int previous = layer->getPreviousKeyFramePosition(backupFrame); + KeyFrame* key = layer->getLastKeyFrameAtPosition(backupFrame); + + // in case tracks overlap, get previous frame + if (key == nullptr) + { + KeyFrame* previousKey = layer->getKeyFrameAt(previous); + key = previousKey; + } + if (key != nullptr) { + SoundClip* clip = static_cast(key); + if (clip) + { + BackupLegacySoundElement* element = new BackupLegacySoundElement(clip); + element->layerId = layer->id(); + element->layer = backupLayer; + element->frame = backupFrame; + element->undoText = undoText; + element->fileName = clip->fileName(); + element->originalName = clip->soundClipName(); + mLegacyBackupList.append(element); + mLegacyBackupIndex++; + } + } + else + { + return false; + } + } + } + + emit didUpdateUndoStack(); + + return true; +} + +void UndoRedoManager::sanitizeLegacyBackupElementsAfterLayerDeletion(int layerIndex) +{ + if (mNewBackupSystemEnabled) { + return; + } + + for (int i = 0; i < mLegacyBackupList.size(); i++) + { + LegacyBackupElement *backupElement = mLegacyBackupList[i]; + BackupLegacyBitmapElement *bitmapElement; + BackupLegacyVectorElement *vectorElement; + BackupLegacySoundElement *soundElement; + switch (backupElement->type()) + { + case LegacyBackupElement::BITMAP_MODIF: + bitmapElement = qobject_cast(backupElement); + Q_ASSERT(bitmapElement); + if (bitmapElement->layer > layerIndex) + { + bitmapElement->layer--; + continue; + } + else if (bitmapElement->layer != layerIndex) + { + continue; + } + break; + case LegacyBackupElement::VECTOR_MODIF: + vectorElement = qobject_cast(backupElement); + Q_ASSERT(vectorElement); + if (vectorElement->layer > layerIndex) + { + vectorElement->layer--; + continue; + } + else if (vectorElement->layer != layerIndex) + { + continue; + } + break; + case LegacyBackupElement::SOUND_MODIF: + soundElement = qobject_cast(backupElement); + Q_ASSERT(soundElement); + if (soundElement->layer > layerIndex) + { + soundElement->layer--; + continue; + } + else if (soundElement->layer != layerIndex) + { + continue; + } + break; + default: + Q_UNREACHABLE(); + } + if (i <= mLegacyBackupIndex) + { + mLegacyBackupIndex--; + } + delete mLegacyBackupList.takeAt(i); + i--; + } +} + +void UndoRedoManager::restoreLegacyKey() +{ + if (mNewBackupSystemEnabled) { + return; + } + + LegacyBackupElement* lastBackupElement = mLegacyBackupList[mLegacyBackupIndex]; + + Layer* layer = nullptr; + int frame = 0; + int layerIndex = 0; + if (lastBackupElement->type() == LegacyBackupElement::BITMAP_MODIF) + { + BackupLegacyBitmapElement* lastBackupBitmapElement = static_cast(lastBackupElement); + layerIndex = lastBackupBitmapElement->layer; + frame = lastBackupBitmapElement->frame; + layer = object()->findLayerById(lastBackupBitmapElement->layerId); + editor()->addKeyFrame(layerIndex, frame); + dynamic_cast(layer)->getBitmapImageAtFrame(frame)->paste(&lastBackupBitmapElement->bitmapImage); + editor()->setModified(layerIndex, frame); + } + if (lastBackupElement->type() == LegacyBackupElement::VECTOR_MODIF) + { + BackupLegacyVectorElement* lastBackupVectorElement = static_cast(lastBackupElement); + layerIndex = lastBackupVectorElement->layer; + frame = lastBackupVectorElement->frame; + layer = object()->findLayerById(layerIndex); + editor()->addKeyFrame(layerIndex, frame); + dynamic_cast(layer)->getVectorImageAtFrame(frame)->paste(lastBackupVectorElement->vectorImage); + editor()->setModified(layerIndex, frame); + } + if (lastBackupElement->type() == LegacyBackupElement::SOUND_MODIF) + { + QString strSoundFile; + BackupLegacySoundElement* lastBackupSoundElement = static_cast(lastBackupElement); + layerIndex = lastBackupSoundElement->layer; + frame = lastBackupSoundElement->frame; + + strSoundFile = lastBackupSoundElement->fileName; + if (strSoundFile.isEmpty()) return; + KeyFrame* key = editor()->addKeyFrame(layerIndex, frame); + SoundClip* clip = dynamic_cast(key); + if (clip) + { + Status st = editor()->sound()->loadSound(clip, lastBackupSoundElement->fileName); + clip->setSoundClipName(lastBackupSoundElement->originalName); + if (!st.ok()) + { + editor()->removeKey(); + emit editor()->layers()->currentLayerChanged(editor()->layers()->currentLayerIndex()); // trigger timeline repaint. + } + } + } +} + +void UndoRedoManager::legacyUndo() +{ + if (!mLegacyBackupList.empty() && mLegacyBackupIndex > -1) + { + if (mLegacyBackupIndex == mLegacyBackupList.size() - 1) + { + LegacyBackupElement* lastBackupElement = mLegacyBackupList[mLegacyBackupIndex]; + if (lastBackupElement->type() == LegacyBackupElement::BITMAP_MODIF) + { + BackupLegacyBitmapElement* lastBackupBitmapElement = static_cast(lastBackupElement); + if (legacyBackup(lastBackupBitmapElement->layer, lastBackupBitmapElement->frame, "NoOp")) + { + mLegacyBackupIndex--; + } + } + if (lastBackupElement->type() == LegacyBackupElement::VECTOR_MODIF) + { + BackupLegacyVectorElement* lastBackupVectorElement = static_cast(lastBackupElement); + if (legacyBackup(lastBackupVectorElement->layer, lastBackupVectorElement->frame, "NoOp")) + { + mLegacyBackupIndex--; + } + } + if (lastBackupElement->type() == LegacyBackupElement::SOUND_MODIF) + { + BackupLegacySoundElement* lastBackupSoundElement = static_cast(lastBackupElement); + if (legacyBackup(lastBackupSoundElement->layer, lastBackupSoundElement->frame, "NoOp")) + { + mLegacyBackupIndex--; + } + } + } + + qDebug() << "Undo" << mLegacyBackupIndex; + mLegacyBackupList[mLegacyBackupIndex]->restore(editor()); + mLegacyBackupIndex--; + + emit didUpdateUndoStack(); + } +} + +void UndoRedoManager::legacyRedo() +{ + if (!mLegacyBackupList.empty() && mLegacyBackupIndex < mLegacyBackupList.size() - 2) + { + mLegacyBackupIndex++; + + mLegacyBackupList[mLegacyBackupIndex + 1]->restore(editor()); + emit didUpdateUndoStack(); + } +} + +void UndoRedoManager::rememberLastModifiedFrame(int layerNumber, int frameNumber) +{ + if (mNewBackupSystemEnabled) { + // not required + } else { + mLegacyLastModifiedLayer = layerNumber; + mLegacyLastModifiedFrame = frameNumber; + } +} diff --git a/core_lib/src/managers/undoredomanager.h b/core_lib/src/managers/undoredomanager.h new file mode 100644 index 0000000000..84f8d951d1 --- /dev/null +++ b/core_lib/src/managers/undoredomanager.h @@ -0,0 +1,179 @@ +/* + +Pencil2D - Traditional Animation Software +Copyright (C) 2005-2007 Patrick Corrieri & Pascal Naidon +Copyright (C) 2008-2009 Mj Mendoza IV +Copyright (C) 2012-2020 Matthew Chiawen Chang + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; version 2 of the License. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +*/ +#ifndef UNDOREDOMANAGER_H +#define UNDOREDOMANAGER_H + +#include "basemanager.h" +#include "layer.h" +#include "keyframe.h" + +#include "preferencesdef.h" + +#include +#include + +class QAction; +class QUndoCommand; + +class BitmapImage; +class VectorImage; +class Camera; +class SoundClip; +class KeyFrame; +class LegacyBackupElement; +class UndoRedoCommand; + +/// The undo/redo type which correspond to what is being recorded +enum class UndoRedoRecordType { + KEYFRAME_MODIFY, // Any modification that involve a keyframe + + // Possible future actions + // KEYFRAME_REMOVE, // Removing a keyframe + // KEYFRAME_ADD, // Adding a keyframe + // SCRUB_LAYER, // Scrubbing layer + // SCRUB_KEYFRAME, // Scrubbing keyframe + INVALID +}; + +struct SelectionSaveState { + + SelectionSaveState(const QRectF& rect, + const qreal rotationAngle, + const qreal scaleX, + const qreal scaleY, + const QPointF& translation, + const QPointF& anchor) + { + this->bounds = rect; + this->rotationAngle = rotationAngle; + this->scaleX = scaleX; + this->scaleY = scaleY; + this->translation = translation; + this->anchor = anchor; + } + + QRectF bounds; + qreal rotationAngle = 0.0; + qreal scaleX = 0.0; + qreal scaleY = 0.0; + QPointF translation; + QPointF anchor; +}; + +/// This is the main undo/redo state structure which is meant to populate +/// whatever states that needs to be stored temporarily. +struct UndoSaveState { + int layerId = 0; + Layer::LAYER_TYPE layerType = Layer::UNDEFINED; + + std::unique_ptr keyframe = nullptr; + std::unique_ptr selectionState = nullptr; + + UndoRedoRecordType recordType = UndoRedoRecordType::INVALID; +}; + +class UndoRedoManager : public BaseManager +{ + Q_OBJECT + +public: + explicit UndoRedoManager(Editor* editor); + ~UndoRedoManager() override; + + bool init() override; + Status load(Object*) override; + Status save(Object*) override; + + /** Records the given save state. + * The input save state is cleaned up and set to nullptr after use. + * @param undoState The state to record. + * @param description The description that will bound to the undo/redo action. + */ + void record(const UndoSaveState*& undoState, const QString& description); + + + /** Checks whether there are unsaved changes. + * @return true if there are unsaved changes, otherwise false */ + bool hasUnsavedChanges() const; + + /** Prepares and returns a save state with the given scope. + * @return A struct with state of the given record type */ + const UndoSaveState* state(UndoRedoRecordType recordType) const; + + QAction* createUndoAction(QObject* parent, const QIcon& icon); + QAction* createRedoAction(QObject* parent, const QIcon& icon); + + void updateUndoAction(QAction* undoAction); + void updateRedoAction(QAction* redoAction); + + /** Clears the undo stack */ + void clearStack(); + + // Developer note: + // Our legacy undo/redo system is not meant to be build upon anymore. + // The implementation should however be kept until the new undo/redo system takes over. + + void legacyBackup(const QString& undoText); + bool legacyBackup(int backupLayer, int backupFrame, const QString& undoText); + /** + * Restores integrity of the backup elements after a layer has been deleted. + * Removes backup elements affecting the deleted layer and adjusts the layer + * index on other backup elements as necessary. + * + * @param layerIndex The index of the layer that was deleted + * + * @warning This serves as a temporary hack to prevent crashes until #864 is + * done (see #1412). + */ + void sanitizeLegacyBackupElementsAfterLayerDeletion(int layerIndex); + void restoreLegacyKey(); + + void rememberLastModifiedFrame(int layerNumber, int frameNumber); + + void onSettingChanged(SETTING setting); + +signals: + void didUpdateUndoStack(); + +private: + + void replaceKeyFrame(const UndoSaveState& undoState, const QString& description); + void replaceBitmap(const UndoSaveState& undoState, const QString& description); + void replaceVector(const UndoSaveState& undoState, const QString& description); + + const UndoSaveState* savedKeyFrameState() const; + + void pushCommand(QUndoCommand* command); + + void legacyUndo(); + void legacyRedo(); + + QUndoStack mUndoStack; + + // Legacy system + int mLegacyBackupIndex = -1; + LegacyBackupElement* mLegacyBackupAtSave = nullptr; + QList mLegacyBackupList; + + int mLegacyLastModifiedLayer = -1; + int mLegacyLastModifiedFrame = -1; + + bool mNewBackupSystemEnabled = false; +}; + +#endif // UNDOREDOMANAGER_H diff --git a/core_lib/src/structure/layer.h b/core_lib/src/structure/layer.h index 2e8897d47a..455f8c86cf 100644 --- a/core_lib/src/structure/layer.h +++ b/core_lib/src/structure/layer.h @@ -104,6 +104,8 @@ class Layer */ virtual bool addKeyFrame(int position, KeyFrame* pKeyFrame); virtual bool removeKeyFrame(int position); + virtual void replaceKeyFrame(const KeyFrame* pKeyFrame) = 0; + bool swapKeyFrames(int position1, int position2); bool moveKeyFrame(int position, int offset); KeyFrame* getKeyFrameAt(int position) const; diff --git a/core_lib/src/structure/layerbitmap.cpp b/core_lib/src/structure/layerbitmap.cpp index c13460c2ba..974ce2b028 100644 --- a/core_lib/src/structure/layerbitmap.cpp +++ b/core_lib/src/structure/layerbitmap.cpp @@ -44,6 +44,11 @@ BitmapImage* LayerBitmap::getLastBitmapImageAtFrame(int frameNumber, int increme return static_cast(getLastKeyFrameAtPosition(frameNumber + increment)); } +void LayerBitmap::replaceKeyFrame(const KeyFrame* bitmapImage) +{ + *getBitmapImageAtFrame(bitmapImage->pos()) = *static_cast(bitmapImage); +} + void LayerBitmap::repositionFrame(QPoint point, int frame) { BitmapImage* image = getBitmapImageAtFrame(frame); diff --git a/core_lib/src/structure/layerbitmap.h b/core_lib/src/structure/layerbitmap.h index f91d06156f..1246e91d5d 100644 --- a/core_lib/src/structure/layerbitmap.h +++ b/core_lib/src/structure/layerbitmap.h @@ -36,6 +36,7 @@ class LayerBitmap : public Layer BitmapImage* getBitmapImageAtFrame(int frameNumber); BitmapImage* getLastBitmapImageAtFrame(int frameNumber, int increment = 0); + void replaceKeyFrame(const KeyFrame*) override; void repositionFrame(QPoint point, int frame); QRect getFrameBounds(int frame); diff --git a/core_lib/src/structure/layercamera.cpp b/core_lib/src/structure/layercamera.cpp index e30ecbc8d5..2d82feebad 100644 --- a/core_lib/src/structure/layercamera.cpp +++ b/core_lib/src/structure/layercamera.cpp @@ -56,6 +56,11 @@ bool LayerCamera::removeKeyFrame(int position) return Layer::removeKeyFrame(position); } +void LayerCamera::replaceKeyFrame(const KeyFrame* camera) +{ + *getCameraAtFrame(camera->pos()) = *static_cast(camera); +} + Camera* LayerCamera::getCameraAtFrame(int frameNumber) const { return static_cast(getKeyFrameAt(frameNumber)); diff --git a/core_lib/src/structure/layercamera.h b/core_lib/src/structure/layercamera.h index da836c28b1..6dadeb942f 100644 --- a/core_lib/src/structure/layercamera.h +++ b/core_lib/src/structure/layercamera.h @@ -40,6 +40,7 @@ class LayerCamera : public Layer bool addKeyFrame(int position, KeyFrame* pKeyFrame) override; bool removeKeyFrame(int position) override; + void replaceKeyFrame(const KeyFrame* camera) override; Camera* getCameraAtFrame(int frameNumber) const; Camera* getLastCameraAtFrame(int frameNumber, int increment) const; diff --git a/core_lib/src/structure/layersound.cpp b/core_lib/src/structure/layersound.cpp index 7a09d9e87c..2cb42f72d0 100644 --- a/core_lib/src/structure/layersound.cpp +++ b/core_lib/src/structure/layersound.cpp @@ -117,6 +117,11 @@ void LayerSound::loadDomElement(const QDomElement& element, QString dataDirPath, } } +void LayerSound::replaceKeyFrame(const KeyFrame* soundClip) +{ + *getSoundClipWhichCovers(soundClip->pos()) = *static_cast(soundClip); +} + Status LayerSound::saveKeyFrameFile(KeyFrame* key, QString path) { Q_ASSERT(key); diff --git a/core_lib/src/structure/layersound.h b/core_lib/src/structure/layersound.h index 1ea4813e7a..4425758f19 100644 --- a/core_lib/src/structure/layersound.h +++ b/core_lib/src/structure/layersound.h @@ -32,6 +32,8 @@ class LayerSound : public Layer QDomElement createDomElement(QDomDocument& doc) const override; void loadDomElement(const QDomElement& element, QString dataDirPath, ProgressCallback progressStep) override; + void replaceKeyFrame(const KeyFrame* soundClip) override; + Status loadSoundClipAtFrame( const QString& sSoundClipName, const QString& filePathString, int frame ); void updateFrameLengths(int fps); diff --git a/core_lib/src/structure/layervector.cpp b/core_lib/src/structure/layervector.cpp index a12def2f66..deea0a3cdb 100644 --- a/core_lib/src/structure/layervector.cpp +++ b/core_lib/src/structure/layervector.cpp @@ -194,3 +194,8 @@ VectorImage* LayerVector::getLastVectorImageAtFrame(int frameNumber, int increme { return static_cast(getLastKeyFrameAtPosition(frameNumber + increment)); } + +void LayerVector::replaceKeyFrame(const KeyFrame* vectorImage) +{ + *getVectorImageAtFrame(vectorImage->pos()) = *static_cast(vectorImage); +} diff --git a/core_lib/src/structure/layervector.h b/core_lib/src/structure/layervector.h index ee13b91cb0..be19e29b5c 100644 --- a/core_lib/src/structure/layervector.h +++ b/core_lib/src/structure/layervector.h @@ -38,6 +38,7 @@ class LayerVector : public Layer VectorImage* getVectorImageAtFrame(int frameNumber) const; VectorImage* getLastVectorImageAtFrame(int frameNumber, int increment) const; + void replaceKeyFrame(const KeyFrame* vectorImage) override; bool usesColor(int index); void removeColor(int index); diff --git a/core_lib/src/tool/brushtool.cpp b/core_lib/src/tool/brushtool.cpp index 655f257f28..db5fb71d5c 100644 --- a/core_lib/src/tool/brushtool.cpp +++ b/core_lib/src/tool/brushtool.cpp @@ -30,6 +30,7 @@ GNU General Public License for more details. #include "layermanager.h" #include "viewmanager.h" #include "selectionmanager.h" +#include "undoredomanager.h" #include "scribblearea.h" #include "pointerevent.h" @@ -200,6 +201,7 @@ void BrushTool::pointerReleaseEvent(PointerEvent *event) } endStroke(); + StrokeTool::pointerReleaseEvent(event); } diff --git a/core_lib/src/tool/erasertool.cpp b/core_lib/src/tool/erasertool.cpp index 9b54d074d8..3f943759c2 100644 --- a/core_lib/src/tool/erasertool.cpp +++ b/core_lib/src/tool/erasertool.cpp @@ -25,6 +25,7 @@ GNU General Public License for more details. #include "scribblearea.h" #include "layermanager.h" #include "viewmanager.h" +#include "undoredomanager.h" #include "layervector.h" #include "vectorimage.h" #include "pointerevent.h" diff --git a/core_lib/src/tool/movetool.cpp b/core_lib/src/tool/movetool.cpp index 72e66315c6..56876aa696 100644 --- a/core_lib/src/tool/movetool.cpp +++ b/core_lib/src/tool/movetool.cpp @@ -27,6 +27,7 @@ GNU General Public License for more details. #include "strokeinterpolator.h" #include "selectionmanager.h" #include "overlaymanager.h" +#include "undoredomanager.h" #include "scribblearea.h" #include "layervector.h" #include "layermanager.h" @@ -161,6 +162,8 @@ void MoveTool::pointerMoveEvent(PointerEvent* event) void MoveTool::pointerReleaseEvent(PointerEvent*) { + mEditor->undoRedo()->record(mUndoSaveState, typeName()); + if (mEditor->overlays()->anyOverlayEnabled()) { mEditor->overlays()->setMoveMode(MoveMode::NONE); @@ -209,6 +212,7 @@ void MoveTool::beginInteraction(const QPointF& pos, Qt::KeyboardModifiers keyMod QRectF selectionRect = selectMan->mySelectionRect(); if (!selectionRect.isNull()) { + mUndoSaveState = mEditor->undoRedo()->state(UndoRedoRecordType::KEYFRAME_MODIFY); mEditor->backup(typeName()); } diff --git a/core_lib/src/tool/movetool.h b/core_lib/src/tool/movetool.h index 6c0033d3bc..6eb16041f7 100644 --- a/core_lib/src/tool/movetool.h +++ b/core_lib/src/tool/movetool.h @@ -21,6 +21,7 @@ GNU General Public License for more details. #include "basetool.h" #include "movemode.h" #include "preferencemanager.h" +#include "undoredomanager.h" class Layer; class VectorImage; @@ -65,6 +66,8 @@ class MoveTool : public BaseTool int mRotationIncrement = 0; MoveMode mPerspMode; QPointF mOffset; + + const UndoSaveState* mUndoSaveState = nullptr; }; #endif diff --git a/core_lib/src/tool/penciltool.cpp b/core_lib/src/tool/penciltool.cpp index a0f63e9025..d85a5fc197 100644 --- a/core_lib/src/tool/penciltool.cpp +++ b/core_lib/src/tool/penciltool.cpp @@ -25,6 +25,7 @@ GNU General Public License for more details. #include "viewmanager.h" #include "preferencemanager.h" #include "selectionmanager.h" +#include "undoredomanager.h" #include "editor.h" #include "scribblearea.h" diff --git a/core_lib/src/tool/pentool.cpp b/core_lib/src/tool/pentool.cpp index 65c262f44f..5629ac6492 100644 --- a/core_lib/src/tool/pentool.cpp +++ b/core_lib/src/tool/pentool.cpp @@ -24,6 +24,7 @@ GNU General Public License for more details. #include "colormanager.h" #include "layermanager.h" #include "viewmanager.h" +#include "undoredomanager.h" #include "selectionmanager.h" #include "editor.h" #include "scribblearea.h" diff --git a/core_lib/src/tool/polylinetool.cpp b/core_lib/src/tool/polylinetool.cpp index 4b8e1bcf70..97dd109248 100644 --- a/core_lib/src/tool/polylinetool.cpp +++ b/core_lib/src/tool/polylinetool.cpp @@ -24,6 +24,7 @@ GNU General Public License for more details. #include "layermanager.h" #include "colormanager.h" #include "viewmanager.h" +#include "undoredomanager.h" #include "pointerevent.h" #include "layervector.h" #include "layerbitmap.h" @@ -206,9 +207,11 @@ void PolylineTool::pointerDoubleClickEvent(PointerEvent* event) // include the current point before ending the line. mPoints << getCurrentPoint(); + const UndoSaveState* saveState = mEditor->undoRedo()->state(UndoRedoRecordType::KEYFRAME_MODIFY); mEditor->backup(typeName()); endPolyline(mPoints); + mEditor->undoRedo()->record(saveState, typeName()); } void PolylineTool::removeLastPolylineSegment() @@ -240,8 +243,9 @@ bool PolylineTool::keyPressEvent(QKeyEvent* event) case Qt::Key_Return: if (mPoints.size() > 0) { - mEditor->backup(typeName()); + const UndoSaveState* saveState = mEditor->undoRedo()->state(UndoRedoRecordType::KEYFRAME_MODIFY); endPolyline(mPoints); + mEditor->undoRedo()->record(saveState, typeName()); return true; } break; @@ -361,6 +365,7 @@ void PolylineTool::endPolyline(QList points) { drawPolyline(points, points.last()); } + mScribbleArea->endStroke(); mEditor->setModified(mEditor->layers()->currentLayerIndex(), mEditor->currentFrame()); diff --git a/core_lib/src/tool/selecttool.cpp b/core_lib/src/tool/selecttool.cpp index 02c1653436..87e36fcfbc 100644 --- a/core_lib/src/tool/selecttool.cpp +++ b/core_lib/src/tool/selecttool.cpp @@ -24,6 +24,7 @@ GNU General Public License for more details. #include "layermanager.h" #include "toolmanager.h" #include "selectionmanager.h" +#include "undoredomanager.h" SelectTool::SelectTool(QObject* parent) : BaseTool(parent) { @@ -125,6 +126,8 @@ void SelectTool::pointerPressEvent(PointerEvent* event) if (event->button() != Qt::LeftButton) { return; } auto selectMan = mEditor->select(); + mUndoState = mEditor->undoRedo()->state(UndoRedoRecordType::KEYFRAME_MODIFY); + mPressPoint = event->canvasPos(); selectMan->setMoveModeForAnchorInRange(mPressPoint); mMoveMode = selectMan->getMoveMode(); @@ -184,6 +187,8 @@ void SelectTool::pointerReleaseEvent(PointerEvent* event) keepSelection(currentLayer); } + mEditor->undoRedo()->record(mUndoState, typeName()); + mStartMoveMode = MoveMode::NONE; mSelectionRect = mEditor->select()->mapToSelection(mEditor->select()->mySelectionRect()).boundingRect(); diff --git a/core_lib/src/tool/selecttool.h b/core_lib/src/tool/selecttool.h index 91a1d106eb..09aa833137 100644 --- a/core_lib/src/tool/selecttool.h +++ b/core_lib/src/tool/selecttool.h @@ -20,6 +20,7 @@ GNU General Public License for more details. #include "basetool.h" #include "movemode.h" +#include "undoredomanager.h" #include @@ -67,6 +68,8 @@ class SelectTool : public BaseTool QRectF mSelectionRect; QPixmap mCursorPixmap = QPixmap(24, 24); + + const UndoSaveState* mUndoState = nullptr; }; #endif diff --git a/core_lib/src/tool/smudgetool.cpp b/core_lib/src/tool/smudgetool.cpp index 075c2a067d..d36f734334 100644 --- a/core_lib/src/tool/smudgetool.cpp +++ b/core_lib/src/tool/smudgetool.cpp @@ -26,6 +26,7 @@ GNU General Public License for more details. #include "layermanager.h" #include "viewmanager.h" #include "selectionmanager.h" +#include "undoredomanager.h" #include "layerbitmap.h" #include "layervector.h" diff --git a/core_lib/src/tool/stroketool.cpp b/core_lib/src/tool/stroketool.cpp index 4ff0c29e43..108788bb8b 100644 --- a/core_lib/src/tool/stroketool.cpp +++ b/core_lib/src/tool/stroketool.cpp @@ -140,6 +140,7 @@ void StrokeTool::startStroke(PointerEvent::InputType inputType) mStrokePressures << mInterpolator.getPressure(); mCurrentInputType = inputType; + mUndoSaveState = mEditor->undoRedo()->state(UndoRedoRecordType::KEYFRAME_MODIFY); disableCoalescing(); } @@ -179,6 +180,8 @@ void StrokeTool::endStroke() mEditor->setModified(mEditor->currentLayerIndex(), mEditor->currentFrame()); mScribbleArea->endStroke(); + + mEditor->undoRedo()->record(mUndoSaveState, typeName()); } void StrokeTool::drawStroke() diff --git a/core_lib/src/tool/stroketool.h b/core_lib/src/tool/stroketool.h index 8d21fa7090..2c01b8298c 100644 --- a/core_lib/src/tool/stroketool.h +++ b/core_lib/src/tool/stroketool.h @@ -22,6 +22,7 @@ GNU General Public License for more details. #include "pointerevent.h" #include "preferencesdef.h" #include "strokeinterpolator.h" +#include "undoredomanager.h" #include "canvascursorpainter.h" @@ -111,6 +112,8 @@ public slots: StrokeInterpolator mInterpolator; + const UndoSaveState* mUndoSaveState = nullptr; + private: /// Sets the width value without calling settings to store the state void setTemporaryWidth(qreal width); diff --git a/core_lib/src/util/pencildef.h b/core_lib/src/util/pencildef.h index 971162b4ee..c21a430d65 100644 --- a/core_lib/src/util/pencildef.h +++ b/core_lib/src/util/pencildef.h @@ -325,6 +325,8 @@ const static int MaxFramesBound = 9999; #define SETTING_FLIP_INBETWEEN_MSEC "FlipInbetween" #define SETTING_SOUND_SCRUB_ACTIVE "SoundScrubActive" #define SETTING_SOUND_SCRUB_MSEC "SoundScrubMsec" +#define SETTING_NEW_UNDO_REDO_ON "NewUndoRedoOn" +#define SETTING_UNDO_REDO_MAX_STEPS "UndoRedoMaxSteps" #define SETTING_BUCKET_TOLERANCE "Tolerance" #define SETTING_BUCKET_TOLERANCE_ON "BucketToleranceEnabled" diff --git a/core_lib/src/util/preferencesdef.h b/core_lib/src/util/preferencesdef.h index e8eb216057..ebf183c1c2 100644 --- a/core_lib/src/util/preferencesdef.h +++ b/core_lib/src/util/preferencesdef.h @@ -75,6 +75,7 @@ enum class SETTING TIMECODE_TEXT, TITLE_SAFE_ON, TITLE_SAFE, + NEW_UNDO_REDO_SYSTEM_ON, QUICK_SIZING, INVERT_DRAG_ZOOM_DIRECTION, INVERT_SCROLL_ZOOM_DIRECTION, @@ -83,6 +84,7 @@ enum class SETTING LAYOUT_LOCK, DRAW_ON_EMPTY_FRAME_ACTION, FRAME_POOL_SIZE, + UNDO_REDO_MAX_STEPS, ROTATION_INCREMENT, SHOW_SELECTION_INFO, ASK_FOR_PRESET,