From cb262504afa64f76b2d701302a025424431ec706 Mon Sep 17 00:00:00 2001 From: Aruki Date: Wed, 2 Jan 2019 12:26:06 -0700 Subject: [PATCH] Support for adding/removing strings --- .../Resource/StringTable/CStringTable.cpp | 80 +++++ src/Core/Resource/StringTable/CStringTable.h | 9 + src/Editor/CTweakEditor.cpp | 42 +-- src/Editor/Editor.pro | 3 +- src/Editor/StringEditor/CStringEditor.cpp | 299 ++++++++++++++++-- src/Editor/StringEditor/CStringEditor.h | 27 ++ src/Editor/StringEditor/CStringEditor.ui | 11 +- src/Editor/Undo/TSerializeUndoCommand.h | 118 +++++++ 8 files changed, 549 insertions(+), 40 deletions(-) create mode 100644 src/Editor/Undo/TSerializeUndoCommand.h diff --git a/src/Core/Resource/StringTable/CStringTable.cpp b/src/Core/Resource/StringTable/CStringTable.cpp index 24fb8b1b..4eef865b 100644 --- a/src/Core/Resource/StringTable/CStringTable.cpp +++ b/src/Core/Resource/StringTable/CStringTable.cpp @@ -90,6 +90,86 @@ void CStringTable::SetStringName(uint StringIndex, const TString& kNewName) mStringNames[StringIndex] = kNewName; } +/** Move string to another position in the table */ +void CStringTable::MoveString(uint StringIndex, uint NewIndex) +{ + ASSERT( NumStrings() > StringIndex ); + ASSERT( NumStrings() > NewIndex ); + + if (NewIndex == StringIndex) + return; + + // Update string data + for (uint LanguageIdx = 0; LanguageIdx < StringIndex; LanguageIdx++) + { + SLanguageData& Language = mLanguages[LanguageIdx]; + TString String = Language.Strings[StringIndex]; + + if (NewIndex > StringIndex) + { + for (uint i=StringIndex; iNewIndex; i--) + Language.Strings[i] = Language.Strings[i-1]; + } + + Language.Strings[NewIndex] = String; + } + + // Update string name + TString Name = mStringNames[StringIndex]; + + if (NewIndex > StringIndex) + { + for (uint i=StringIndex; iNewIndex; i--) + mStringNames[i] = mStringNames[i-1]; + } + mStringNames[NewIndex] = Name; +} + +/** Add a new string to the table */ +void CStringTable::AddString(uint AtIndex) +{ + if (AtIndex < NumStrings()) + { + if (mStringNames.size() > AtIndex) + { + mStringNames.insert( mStringNames.begin() + AtIndex, 1, "" ); + } + } + else + AtIndex = NumStrings(); + + for (uint LanguageIdx = 0; LanguageIdx < mLanguages.size(); LanguageIdx++) + { + SLanguageData& Language = mLanguages[LanguageIdx]; + Language.Strings.insert( Language.Strings.begin() + AtIndex, 1, "" ); + } +} + +/** Remove a string from the table */ +void CStringTable::RemoveString(uint StringIndex) +{ + ASSERT( StringIndex < NumStrings() ); + + if (mStringNames.size() > StringIndex) + mStringNames.erase( mStringNames.begin() + StringIndex ); + + for (uint LanguageIdx = 0; LanguageIdx < mLanguages.size(); LanguageIdx++) + { + SLanguageData& Language = mLanguages[LanguageIdx]; + Language.Strings.erase( Language.Strings.begin() + StringIndex ); + } +} + /** Configures the string table with default languages for the game/region pairing of the resource */ void CStringTable::ConfigureDefaultLanguages() { diff --git a/src/Core/Resource/StringTable/CStringTable.h b/src/Core/Resource/StringTable/CStringTable.h index da2e3a56..3ba83427 100644 --- a/src/Core/Resource/StringTable/CStringTable.h +++ b/src/Core/Resource/StringTable/CStringTable.h @@ -58,6 +58,15 @@ public: /** Updates a string name */ void SetStringName(uint StringIndex, const TString& kNewName); + /** Move string to another position in the table */ + void MoveString(uint StringIndex, uint NewIndex); + + /** Add a new string to the table */ + void AddString(uint AtIndex); + + /** Remove a string from the table */ + void RemoveString(uint StringIndex); + /** Configures the string table with default languages for the game/region pairing of the resource */ void ConfigureDefaultLanguages(); diff --git a/src/Editor/CTweakEditor.cpp b/src/Editor/CTweakEditor.cpp index ed82afd8..74c6c335 100644 --- a/src/Editor/CTweakEditor.cpp +++ b/src/Editor/CTweakEditor.cpp @@ -2,6 +2,26 @@ #include "ui_CTweakEditor.h" #include "Editor/Undo/IUndoCommand.h" +/** Internal undo command for changing tabs */ +class CSetTweakIndexCommand : public IUndoCommand +{ + CTweakEditor* mpEditor; + int mOldIndex, mNewIndex; + +public: + CSetTweakIndexCommand(CTweakEditor* pEditor, int OldIndex, int NewIndex) + : IUndoCommand("Change Tab") + , mpEditor(pEditor) + , mOldIndex(OldIndex) + , mNewIndex(NewIndex) + {} + + virtual void undo() override { mpEditor->SetActiveTweakIndex(mOldIndex); } + virtual void redo() override { mpEditor->SetActiveTweakIndex(mNewIndex); } + virtual bool AffectsCleanState() const { return false; } +}; + +/** CTweakEditor functions */ CTweakEditor::CTweakEditor(QWidget* pParent) : IEditor(pParent) , mpUI(new Ui::CTweakEditor) @@ -73,7 +93,8 @@ void CTweakEditor::SetActiveTweakData(CTweakData* pTweakData) { if (mTweakAssets[TweakIdx] == pTweakData) { - SetActiveTweakIndex(TweakIdx); + CSetTweakIndexCommand* pCommand = new CSetTweakIndexCommand(this, mCurrentTweakIndex, TweakIdx); + UndoStack().push(pCommand); break; } } @@ -96,25 +117,6 @@ void CTweakEditor::SetActiveTweakIndex(int Index) void CTweakEditor::OnTweakTabClicked(int Index) { - /** Internal undo command for changing tabs */ - class CSetTweakIndexCommand : public IUndoCommand - { - CTweakEditor* mpEditor; - int mOldIndex, mNewIndex; - - public: - CSetTweakIndexCommand(CTweakEditor* pEditor, int OldIndex, int NewIndex) - : IUndoCommand("Change Tab") - , mpEditor(pEditor) - , mOldIndex(OldIndex) - , mNewIndex(NewIndex) - {} - - virtual void undo() override { mpEditor->SetActiveTweakIndex(mOldIndex); } - virtual void redo() override { mpEditor->SetActiveTweakIndex(mNewIndex); } - virtual bool AffectsCleanState() const { return false; } - }; - if (Index != mCurrentTweakIndex) { CSetTweakIndexCommand* pCommand = new CSetTweakIndexCommand(this, mCurrentTweakIndex, Index); diff --git a/src/Editor/Editor.pro b/src/Editor/Editor.pro index bdd361d3..9dc4d407 100644 --- a/src/Editor/Editor.pro +++ b/src/Editor/Editor.pro @@ -205,7 +205,8 @@ HEADERS += \ StringEditor/CStringDelegate.h \ CCustomDelegate.h \ CTweakEditor.h \ - Undo/CEditIntrinsicPropertyCommand.h + Undo/CEditIntrinsicPropertyCommand.h \ + Undo/TSerializeUndoCommand.h # Source Files SOURCES += \ diff --git a/src/Editor/StringEditor/CStringEditor.cpp b/src/Editor/StringEditor/CStringEditor.cpp index 862c6929..1172284d 100644 --- a/src/Editor/StringEditor/CStringEditor.cpp +++ b/src/Editor/StringEditor/CStringEditor.cpp @@ -3,12 +3,43 @@ #include "CStringDelegate.h" #include "Editor/UICommon.h" +#include "Editor/Undo/TSerializeUndoCommand.h" #include +#include /** Settings strings */ const char* gkpLanguageSetting = "StringEditor/EditLanguage"; +/** Command classes */ +class CSetStringIndexCommand : public IUndoCommand +{ + CStringEditor* mpEditor; + int mOldIndex, mNewIndex; +public: + CSetStringIndexCommand(CStringEditor* pEditor, int OldIndex, int NewIndex) + : mpEditor(pEditor), mOldIndex(OldIndex), mNewIndex(NewIndex) + {} + + virtual void undo() override { mpEditor->SetActiveString(mOldIndex); } + virtual void redo() override { mpEditor->SetActiveString(mNewIndex); } + virtual bool AffectsCleanState() const override { return false; } +}; + +class CSetLanguageCommand : public IUndoCommand +{ + CStringEditor* mpEditor; + ELanguage mOldLanguage, mNewLanguage; +public: + CSetLanguageCommand(CStringEditor* pEditor, ELanguage OldLanguage, ELanguage NewLanguage) + : mpEditor(pEditor), mOldLanguage(OldLanguage), mNewLanguage(NewLanguage) + {} + + virtual void undo() override { mpEditor->SetActiveLanguage(mOldLanguage); } + virtual void redo() override { mpEditor->SetActiveLanguage(mNewLanguage); } + virtual bool AffectsCleanState() const override { return false; } +}; + /** Constructor */ CStringEditor::CStringEditor(CStringTable* pStringTable, QWidget* pParent) : IEditor(pParent) @@ -16,12 +47,12 @@ CStringEditor::CStringEditor(CStringTable* pStringTable, QWidget* pParent) , mpStringTable(pStringTable) , mCurrentLanguage(ELanguage::English) , mCurrentStringIndex(0) + , mCurrentStringCount(0) + , mIsEditingStringName(false) + , mIsEditingStringData(false) { - mpListModel = new CStringListModel(pStringTable, this); - mpUI->setupUi(this); - InitUI(); - LoadSettings(); +// LoadSettings(); // Disabled for now } CStringEditor::~CStringEditor() @@ -29,10 +60,42 @@ CStringEditor::~CStringEditor() delete mpUI; } +bool CStringEditor::eventFilter(QObject* pWatched, QEvent* pEvent) +{ + if (pEvent->type() == QEvent::FocusOut) + { + if (pWatched == mpUI->StringNameLineEdit && mIsEditingStringName) + { + IUndoCommand* pCommand = new TSerializeUndoCommand("Edit String Name", mpStringTable, true); + UndoStack().push(pCommand); + mIsEditingStringName = false; + return true; + } + else if (pWatched == mpUI->StringTextEdit && mIsEditingStringData) + { + IUndoCommand* pCommand = new TSerializeUndoCommand("Edit String", mpStringTable, true); + UndoStack().push(pCommand); + mIsEditingStringData = false; + return true; + } + } + return false; +} + void CStringEditor::InitUI() { + mpUI->setupUi(this); + mpListModel = new CStringListModel(mpStringTable, this); mpUI->StringNameListView->setModel(mpListModel); mpUI->StringNameListView->setItemDelegate( new CStringDelegate(this) ); + mpUI->AddStringButton->setShortcut( QKeySequence("Alt+=") ); + mpUI->RemoveStringButton->setShortcut( QKeySequence("Alt+-") ); + + // Register shortcuts + new QShortcut( QKeySequence("Alt+Down"), this, SLOT(IncrementStringIndex()) ); + new QShortcut( QKeySequence("Alt+Up"), this, SLOT(DecrementStringIndex()) ); + new QShortcut( QKeySequence("Alt+Right"), this, SLOT(IncrementLanguageIndex()) ); + new QShortcut( QKeySequence("Alt+Left"), this, SLOT(DecrementLanguageIndex()) ); // Set up language tabs mpUI->EditLanguageTabBar->setExpanding(false); @@ -48,7 +111,20 @@ void CStringEditor::InitUI() connect( mpUI->StringNameListView->selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)), this, SLOT(OnStringSelected(QModelIndex)) ); + connect( mpUI->StringNameLineEdit, SIGNAL(textChanged(QString)), this, SLOT(OnStringNameEdited()) ); + connect( mpUI->StringTextEdit, SIGNAL(textChanged()), this, SLOT(OnStringTextEdited()) ); connect( mpUI->EditLanguageTabBar, SIGNAL(currentChanged(int)), this, SLOT(OnLanguageChanged(int)) ); + connect( mpUI->AddStringButton, SIGNAL(pressed()), this, SLOT(OnAddString()) ); + connect( mpUI->RemoveStringButton, SIGNAL(pressed()), this, SLOT(OnRemoveString()) ); + + connect( &UndoStack(), SIGNAL(indexChanged(int)), this, SLOT(UpdateUI()) ); + + mpUI->ToolBar->addSeparator(); + AddUndoActions(mpUI->ToolBar); + + // Install event filters + mpUI->StringNameLineEdit->installEventFilter(this); + mpUI->StringTextEdit->installEventFilter(this); // Update window title QString WindowTitle = "%APP_FULL_NAME% - String Editor - %1[*]"; @@ -60,8 +136,8 @@ void CStringEditor::InitUI() SplitterSizes << (height() * 0.95) << (height() * 0.05); mpUI->splitter->setSizes(SplitterSizes); - // Initialize status bar - UpdateStatusBar(); + // Initialize UI + UpdateUI(); } void CStringEditor::UpdateStatusBar() @@ -86,9 +162,8 @@ void CStringEditor::SetActiveLanguage(ELanguage Language) if (mCurrentLanguage != Language) { mCurrentLanguage = Language; - - // Force UI to update with the correct string for the new language - SetActiveString( mCurrentStringIndex ); + mpListModel->SetPreviewLanguage(Language); + UpdateUI(); SaveSettings(); } } @@ -96,11 +171,7 @@ void CStringEditor::SetActiveLanguage(ELanguage Language) void CStringEditor::SetActiveString(int StringIndex) { mCurrentStringIndex = StringIndex; - TString StringName = mpStringTable->StringNameByIndex(mCurrentStringIndex); - TString StringData = mpStringTable->GetString(mCurrentLanguage, mCurrentStringIndex); - mpUI->StringNameLineEdit->setText( TO_QSTRING(StringName) ); - mpUI->StringTextEdit->setPlainText( TO_QSTRING(StringData) ); - UpdateStatusBar(); + UpdateUI(); } void CStringEditor::LoadSettings() @@ -108,12 +179,13 @@ void CStringEditor::LoadSettings() QSettings Settings; // Set language - mCurrentLanguage = (ELanguage) Settings.value(gkpLanguageSetting, (int) ELanguage::English).toInt(); + ELanguage Language = (ELanguage) Settings.value(gkpLanguageSetting, (int) ELanguage::English).toInt(); for (uint LanguageIdx = 0; LanguageIdx < mpStringTable->NumLanguages(); LanguageIdx++) { - if (mpStringTable->LanguageByIndex(LanguageIdx) == mCurrentLanguage) + if (mpStringTable->LanguageByIndex(LanguageIdx) == Language) { + SetActiveLanguage(Language); mpUI->EditLanguageTabBar->setCurrentIndex(LanguageIdx); break; } @@ -127,14 +199,205 @@ void CStringEditor::SaveSettings() } /** Slots */ +void CStringEditor::UpdateUI() +{ + // Update string list + if (mCurrentStringCount != mpStringTable->NumStrings()) + { + mpUI->StringNameListView->model()->layoutChanged(); + mCurrentStringCount = mpStringTable->NumStrings(); + } + + // Update selection in string list + QModelIndex OldStringIndex = mpUI->StringNameListView->selectionModel()->hasSelection() ? + mpUI->StringNameListView->currentIndex() : QModelIndex(); + + QModelIndex NewStringIndex = mpUI->StringNameListView->model()->index(mCurrentStringIndex,0); + + if (OldStringIndex != NewStringIndex) + { + QItemSelectionModel* pSelectionModel = mpUI->StringNameListView->selectionModel(); + pSelectionModel->blockSignals(true); + pSelectionModel->setCurrentIndex(NewStringIndex, QItemSelectionModel::ClearAndSelect); + pSelectionModel->blockSignals(false); + mpUI->StringNameListView->update(OldStringIndex); + } + mpUI->StringNameListView->update(NewStringIndex); + + // Update language tabs + uint LanguageIndex = mpUI->EditLanguageTabBar->currentIndex(); + ELanguage TabLanguage = mpStringTable->LanguageByIndex(LanguageIndex); + + if (TabLanguage != mCurrentLanguage) + { + for (uint LangIdx = 0; LangIdx < mpStringTable->NumLanguages(); LangIdx++) + { + if (mpStringTable->LanguageByIndex(LangIdx) == mCurrentLanguage) + { + mpUI->EditLanguageTabBar->blockSignals(true); + mpUI->EditLanguageTabBar->setCurrentIndex(LangIdx); + mpUI->EditLanguageTabBar->blockSignals(false); + break; + } + } + } + + // Update string name/data fields + QString StringName = TO_QSTRING( mpStringTable->StringNameByIndex(mCurrentStringIndex) ); + QString StringData = TO_QSTRING( mpStringTable->GetString(mCurrentLanguage, mCurrentStringIndex) ); + + if (StringName != mpUI->StringNameLineEdit->text()) + { + mpUI->StringNameLineEdit->blockSignals(true); + mpUI->StringNameLineEdit->setText( StringName ); + mpUI->StringNameLineEdit->blockSignals(false); + } + + if (StringData != mpUI->StringTextEdit->toPlainText()) + { + mpUI->StringTextEdit->blockSignals(true); + mpUI->StringTextEdit->setPlainText( StringData ); + mpUI->StringTextEdit->blockSignals(false); + } + + UpdateStatusBar(); +} + void CStringEditor::OnStringSelected(const QModelIndex& kIndex) { - SetActiveString( kIndex.row() ); + if (mCurrentStringIndex != kIndex.row()) + { + IUndoCommand* pCommand = new CSetStringIndexCommand(this, mCurrentStringIndex, kIndex.row()); + UndoStack().push(pCommand); + } } void CStringEditor::OnLanguageChanged(int LanguageIndex) { ASSERT( LanguageIndex >= 0 && LanguageIndex < (int) mpStringTable->NumLanguages() ); ELanguage Language = mpStringTable->LanguageByIndex(LanguageIndex); - SetActiveLanguage(Language); + + if (Language != mCurrentLanguage) + { + IUndoCommand* pCommand = new CSetLanguageCommand(this, mCurrentLanguage, Language); + UndoStack().push(pCommand); + } +} + +void CStringEditor::OnStringNameEdited() +{ + TString NewName = TO_TSTRING( mpUI->StringNameLineEdit->text() ); + + if (mpStringTable->StringNameByIndex(mCurrentStringIndex) != NewName) + { + IUndoCommand* pCommand = new TSerializeUndoCommand("Edit String Name", mpStringTable, false); + mpStringTable->SetStringName( mCurrentStringIndex, NewName ); + mIsEditingStringName = true; + UndoStack().push(pCommand); + } +} + +void CStringEditor::OnStringTextEdited() +{ + TString NewText = TO_TSTRING( mpUI->StringTextEdit->toPlainText() ); + + if (mpStringTable->GetString(mCurrentLanguage, mCurrentStringIndex) != NewText) + { + IUndoCommand* pCommand = new TSerializeUndoCommand("Edit String", mpStringTable, false); + mpStringTable->SetString(mCurrentLanguage, mCurrentStringIndex, NewText); + mIsEditingStringData = true; + UndoStack().push(pCommand); + } +} + +void CStringEditor::OnAddString() +{ + UndoStack().beginMacro("Add String"); + + // Add string + IUndoCommand* pCommand = new TSerializeUndoCommand("Add String", mpStringTable, true); + uint Index = mCurrentStringIndex + 1; + mpStringTable->AddString(Index); + UndoStack().push(pCommand); + + // Select new string + pCommand = new CSetStringIndexCommand(this, mCurrentStringIndex, Index); + UndoStack().push(pCommand); + UndoStack().endMacro(); + + // Give focus to the text edit so the user can edit the string + mpUI->StringTextEdit->setFocus(); +} + +void CStringEditor::OnRemoveString() +{ + if (mpUI->StringNameListView->selectionModel()->hasSelection()) + { + UndoStack().beginMacro("Remove String"); + uint Index = mCurrentStringIndex; + + // Change selection to a new string. + // Do this before actually removing the string so that if the action is undone, the + // editor will not attempt to re-select the string before it gets readded to the table. + uint NewStringCount = mpStringTable->NumStrings() - 1; + uint NewIndex = (Index >= NewStringCount ? NewStringCount - 1 : Index); + IUndoCommand* pCommand = new CSetStringIndexCommand(this, Index, NewIndex); + UndoStack().push(pCommand); + + // Remove the string + pCommand = new TSerializeUndoCommand("Remove String", mpStringTable, true); + mpStringTable->RemoveString(Index); + UndoStack().push(pCommand); + UndoStack().endMacro(); + } +} + +void CStringEditor::IncrementStringIndex() +{ + uint NewIndex = mCurrentStringIndex + 1; + + if (NewIndex < mpStringTable->NumStrings()) + { + IUndoCommand* pCommand = new CSetStringIndexCommand(this, mCurrentStringIndex, NewIndex); + UndoStack().push(pCommand); + } +} + +void CStringEditor::DecrementStringIndex() +{ + uint NewIndex = mCurrentStringIndex - 1; + + if (NewIndex != -1) + { + IUndoCommand* pCommand = new CSetStringIndexCommand(this, mCurrentStringIndex, NewIndex); + UndoStack().push(pCommand); + } +} + +void CStringEditor::IncrementLanguageIndex() +{ + for (uint i=0; iNumLanguages() - 1; i++) + { + if (mpStringTable->LanguageByIndex(i) == mCurrentLanguage) + { + ELanguage NewLanguage = mpStringTable->LanguageByIndex(i+1); + IUndoCommand* pCommand = new CSetLanguageCommand(this, mCurrentLanguage, NewLanguage); + UndoStack().push(pCommand); + break; + } + } +} + +void CStringEditor::DecrementLanguageIndex() +{ + for (uint i=mpStringTable->NumLanguages() - 1; i>0; i--) + { + if (mpStringTable->LanguageByIndex(i) == mCurrentLanguage) + { + ELanguage NewLanguage = mpStringTable->LanguageByIndex(i-1); + IUndoCommand* pCommand = new CSetLanguageCommand(this, mCurrentLanguage, NewLanguage); + UndoStack().push(pCommand); + break; + } + } } diff --git a/src/Editor/StringEditor/CStringEditor.h b/src/Editor/StringEditor/CStringEditor.h index e2719a47..2e546d4a 100644 --- a/src/Editor/StringEditor/CStringEditor.h +++ b/src/Editor/StringEditor/CStringEditor.h @@ -29,12 +29,22 @@ class CStringEditor : public IEditor /** Index of the string being edited */ uint mCurrentStringIndex; + /** Current string count */ + uint mCurrentStringCount; + /** Model for the string list view */ CStringListModel* mpListModel; + /** Editor state flags */ + bool mIsEditingStringName; + bool mIsEditingStringData; + public: explicit CStringEditor(CStringTable* pStringTable, QWidget* pParent = 0); ~CStringEditor(); + + bool eventFilter(QObject* pWatched, QEvent* pEvent); + void InitUI(); void UpdateStatusBar(); void SetActiveLanguage(ELanguage Language); @@ -43,9 +53,26 @@ public: void LoadSettings(); void SaveSettings(); + // Accessors + inline CStringTable* StringTable() const { return mpStringTable; } + public slots: + void UpdateUI(); void OnStringSelected(const QModelIndex& kIndex); void OnLanguageChanged(int LanguageIndex); + void OnStringNameEdited(); + void OnStringTextEdited(); + void OnAddString(); + void OnRemoveString(); + + void IncrementStringIndex(); + void DecrementStringIndex(); + void IncrementLanguageIndex(); + void DecrementLanguageIndex(); + +signals: + void StringNameEdited(); + void StringEdited(); }; #endif // CSTRINGEDITOR_H diff --git a/src/Editor/StringEditor/CStringEditor.ui b/src/Editor/StringEditor/CStringEditor.ui index 6e9c254b..fee88a1a 100644 --- a/src/Editor/StringEditor/CStringEditor.ui +++ b/src/Editor/StringEditor/CStringEditor.ui @@ -44,6 +44,15 @@ 9 + + true + + + QAbstractItemView::InternalMove + + + Qt::MoveAction + true @@ -108,7 +117,7 @@ - + diff --git a/src/Editor/Undo/TSerializeUndoCommand.h b/src/Editor/Undo/TSerializeUndoCommand.h new file mode 100644 index 00000000..6d06be09 --- /dev/null +++ b/src/Editor/Undo/TSerializeUndoCommand.h @@ -0,0 +1,118 @@ +#ifndef TSERIALIZEUNDOCOMMAND_H +#define TSERIALIZEUNDOCOMMAND_H + +#include "IUndoCommand.h" +#include + +/** + * Undo command that works by restoring the full state of an object + * on undo/redo. To use, create the command object, apply the change + * you want to make to the object, and then push the command. + * + * Commands with IsActionComplete=false will be merged. + * To prevent merging, push a final command with IsActionComplete=true. + * + * The current implementatation is "dumb" and just saves and restores + * the entire object, including parameters that have not changed. Due + * to this, it could be memory-intensive and not super performant. + * As a result, it's probably not a good idea to use this on very + * large objects right now. + */ +template +class TSerializeUndoCommand : public IUndoCommand +{ + ObjectT* mpObject; + std::vector mOldData; + std::vector mNewData; + bool mIsActionComplete; + +public: + TSerializeUndoCommand(const QString& kText, ObjectT* pObject, bool IsActionComplete) + : IUndoCommand(kText) + , mpObject(pObject) + , mIsActionComplete(IsActionComplete) + { + // Save old state of object + CVectorOutStream Out(&mOldData, EEndian::SystemEndian); + CBasicBinaryWriter Writer(&Out, 0, EGame::Invalid); + mpObject->Serialize(Writer); + } + + /** IUndoCommand interface */ + virtual int id() const override + { + return FOURCC('TSUC'); + } + + virtual void undo() override + { + // Restore old state of object + CMemoryInStream In(&mOldData[0], mOldData.size(), EEndian::SystemEndian); + CBasicBinaryReader Reader(&In, CSerialVersion(0,0,EGame::Invalid)); + mpObject->Serialize(Reader); + } + + virtual void redo() override + { + // First call when command is pushed - save new state of object + if (mNewData.empty()) + { + CVectorOutStream Out(&mNewData, EEndian::SystemEndian); + CBasicBinaryWriter Writer(&Out, 0, EGame::Invalid); + mpObject->Serialize(Writer); + + // Obsolete command if memory buffers match + if (mIsActionComplete) + { + if (mOldData.size() == mNewData.size()) + { + if (memcmp(mOldData.data(), mNewData.data(), mNewData.size()) == 0) + { + setObsolete(true); + } + } + } + } + // Subsequent calls - restore new state of object + else + { + CMemoryInStream In(&mNewData[0], mNewData.size(), EEndian::SystemEndian); + CBasicBinaryReader Reader(&In, CSerialVersion(0,0,EGame::Invalid)); + mpObject->Serialize(Reader); + } + } + + virtual bool mergeWith(const QUndoCommand* pkOther) override + { + if (!mIsActionComplete && pkOther->id() == id()) + { + const TSerializeUndoCommand* pkSerializeCommand = + static_cast(pkOther); + + mNewData = pkSerializeCommand->mNewData; + mIsActionComplete = pkSerializeCommand->mIsActionComplete; + + // Obsolete command if memory buffers match + if (mIsActionComplete) + { + if (mOldData.size() == mNewData.size()) + { + if (memcmp(mOldData.data(), mNewData.data(), mNewData.size()) == 0) + { + setObsolete(true); + } + } + } + + return true; + } + return false; + } + + virtual bool AffectsCleanState() const override + { + return true; + } +}; + +#endif // TSERIALIZEUNDOCOMMAND_H