Something went wrong. Try again.
OpenMW is an open implementation of the Morrowind engine
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555#include "commands.hpp"
#include <algorithm>#include <cmath>#include <sstream>#include <unordered_set>
#include <apps/opencs/model/world/columns.hpp>#include <apps/opencs/model/world/land.hpp>#include <apps/opencs/model/world/record.hpp>#include <apps/opencs/model/world/universalid.hpp>
#include <components/esm3/loadland.hpp>#include <components/esm3/loadpgrd.hpp>#include <components/misc/constants.hpp>
#include <QAbstractItemModel>#include <QAbstractProxyModel>
#include "cellcoordinates.hpp"#include "idtable.hpp"#include "idtree.hpp"#include "nestedtablewrapper.hpp"#include "pathgrid.hpp"
CSMWorld::TouchCommand::TouchCommand(IdTable& table, const std::string& id, QUndoCommand* parent) : QUndoCommand(parent) , mTable(table) , mId(id) , mOld(nullptr) , mChanged(false){ setText(("Touch " + mId).c_str());}
void CSMWorld::TouchCommand::redo(){ mOld = mTable.getRecord(mId).clone(); mChanged = mTable.touchRecord(mId);}
void CSMWorld::TouchCommand::undo(){ if (mChanged) { mTable.setRecord(mId, std::move(mOld)); mChanged = false; }}
CSMWorld::ImportLandTexturesCommand::ImportLandTexturesCommand( IdTable& landTable, IdTable& ltexTable, QUndoCommand* parent) : QUndoCommand(parent) , mLands(landTable) , mLtexs(ltexTable) , mOldState(0){ setText("Copy land textures to current plugin");}
void CSMWorld::ImportLandTexturesCommand::redo(){ const int pluginColumn = mLands.findColumnIndex(Columns::ColumnId_PluginIndex); const int oldPlugin = mLands.data(mLands.getModelIndex(getOriginId(), pluginColumn)).toInt();
// Original data const int textureColumn = mLands.findColumnIndex(Columns::ColumnId_LandTexturesIndex); mOld = mLands.data(mLands.getModelIndex(getOriginId(), textureColumn)).value<DataType>();
// Need to make a copy so the old values can be looked up DataType copy(mOld);
// Perform touch/copy/etc... onRedo();
std::unordered_map<uint16_t, uint16_t> indexMapping; for (uint16_t index : mOld) { // All indices are offset by 1 for a default texture if (index == 0) continue; if (indexMapping.contains(index)) continue; const CSMWorld::Record<ESM::LandTexture>* record = static_cast<LandTextureIdTable&>(mLtexs).searchRecord(index - 1, oldPlugin); if (!record || record->isDeleted()) { indexMapping.emplace(index, 0); continue; } if (!record->isModified()) { mTouchedTextures.emplace_back(record->clone()); mLtexs.touchRecord(record->get().mId.getRefIdString()); } indexMapping.emplace(index, record->get().mIndex + 1); } for (int i = 0; i < Land::LAND_NUM_TEXTURES; ++i) { uint16_t oldIndex = mOld[i]; uint16_t newIndex = indexMapping[oldIndex]; copy[i] = newIndex; }
// Apply modification const int stateColumn = mLands.findColumnIndex(Columns::ColumnId_Modification); mOldState = mLands.data(mLands.getModelIndex(getDestinationId(), stateColumn)).toInt();
QVariant variant; variant.setValue(copy); mLands.setData(mLands.getModelIndex(getDestinationId(), textureColumn), variant);}
void CSMWorld::ImportLandTexturesCommand::undo(){ // Restore to previous int textureColumn = mLands.findColumnIndex(Columns::ColumnId_LandTexturesIndex); QVariant variant; variant.setValue(mOld); mLands.setData(mLands.getModelIndex(getDestinationId(), textureColumn), variant);
int stateColumn = mLands.findColumnIndex(Columns::ColumnId_Modification); mLands.setData(mLands.getModelIndex(getDestinationId(), stateColumn), mOldState);
// Undo copy/touch/etc... onUndo();
for (auto& ltex : mTouchedTextures) { ESM::RefId id = static_cast<Record<ESM::LandTexture>*>(ltex.get())->get().mId; mLtexs.setRecord(id.getRefIdString(), std::move(ltex)); } mTouchedTextures.clear();}
CSMWorld::CopyLandTexturesCommand::CopyLandTexturesCommand( IdTable& landTable, IdTable& ltexTable, const std::string& origin, const std::string& dest, QUndoCommand* parent) : ImportLandTexturesCommand(landTable, ltexTable, parent) , mOriginId(origin) , mDestId(dest){}
const std::string& CSMWorld::CopyLandTexturesCommand::getOriginId() const{ return mOriginId;}
const std::string& CSMWorld::CopyLandTexturesCommand::getDestinationId() const{ return mDestId;}
CSMWorld::TouchLandCommand::TouchLandCommand( IdTable& landTable, IdTable& ltexTable, const std::string& id, QUndoCommand* parent) : ImportLandTexturesCommand(landTable, ltexTable, parent) , mId(id) , mOld(nullptr) , mChanged(false){ setText(("Touch " + mId).c_str());}
const std::string& CSMWorld::TouchLandCommand::getOriginId() const{ return mId;}
const std::string& CSMWorld::TouchLandCommand::getDestinationId() const{ return mId;}
void CSMWorld::TouchLandCommand::onRedo(){ mOld = mLands.getRecord(mId).clone(); mChanged = mLands.touchRecord(mId);}
void CSMWorld::TouchLandCommand::onUndo(){ if (mChanged) { mLands.setRecord(mId, std::move(mOld)); mChanged = false; }}
CSMWorld::ModifyCommand::ModifyCommand( QAbstractItemModel& model, const QModelIndex& index, const QVariant& newValue, QUndoCommand* parent) : QUndoCommand(parent) , mModel(&model) , mIndex(index) , mNew(newValue) , mHasRecordState(false) , mOldRecordState(CSMWorld::RecordBase::State_BaseOnly){ if (QAbstractProxyModel* proxy = dynamic_cast<QAbstractProxyModel*>(mModel)) { // Replace proxy with actual model mIndex = proxy->mapToSource(mIndex); mModel = proxy->sourceModel(); }}
void CSMWorld::ModifyCommand::redo(){ if (mIndex.parent().isValid()) { CSMWorld::IdTree* tree = &dynamic_cast<CSMWorld::IdTree&>(*mModel); setText("Modify " + tree->nestedHeaderData(mIndex.parent().column(), mIndex.column(), Qt::Horizontal, Qt::DisplayRole) .toString()); } else { setText("Modify " + mModel->headerData(mIndex.column(), Qt::Horizontal, Qt::DisplayRole).toString()); }
// Remember record state before the modification if (CSMWorld::IdTable* table = dynamic_cast<IdTable*>(mModel)) { mHasRecordState = true; int stateColumnIndex = table->findColumnIndex(Columns::ColumnId_Modification);
int rowIndex = mIndex.row(); if (mIndex.parent().isValid()) { rowIndex = mIndex.parent().row(); }
mRecordStateIndex = table->index(rowIndex, stateColumnIndex); mOldRecordState = static_cast<CSMWorld::RecordBase::State>(table->data(mRecordStateIndex).toInt()); }
mOld = mModel->data(mIndex, Qt::EditRole); mModel->setData(mIndex, mNew);}
void CSMWorld::ModifyCommand::undo(){ mModel->setData(mIndex, mOld); if (mHasRecordState) { mModel->setData(mRecordStateIndex, mOldRecordState); }}
void CSMWorld::CreateCommand::applyModifications(){ if (!mNestedValues.empty()) { CSMWorld::IdTree* tree = &dynamic_cast<CSMWorld::IdTree&>(mModel); std::map<int, std::pair<int, QVariant>>::const_iterator current = mNestedValues.begin(); std::map<int, std::pair<int, QVariant>>::const_iterator end = mNestedValues.end(); for (; current != end; ++current) { QModelIndex index = tree->index(0, current->second.first, tree->getNestedModelIndex(mId, current->first)); tree->setData(index, current->second.second); } }}
CSMWorld::CreateCommand::CreateCommand(IdTable& model, const std::string& id, QUndoCommand* parent) : QUndoCommand(parent) , mModel(model) , mId(id) , mType(UniversalId::Type_None){ setText(("Create record " + id).c_str());}
void CSMWorld::CreateCommand::addValue(int column, const QVariant& value){ mValues[column] = value;}
void CSMWorld::CreateCommand::addNestedValue(int parentColumn, int nestedColumn, const QVariant& value){ mNestedValues[parentColumn] = std::make_pair(nestedColumn, value);}
void CSMWorld::CreateCommand::setType(UniversalId::Type type){ mType = type;}
void CSMWorld::CreateCommand::redo(){ mModel.addRecordWithData(mId, mValues, mType); applyModifications();}
void CSMWorld::CreateCommand::undo(){ mModel.removeRow(mModel.getModelIndex(mId, 0).row());}
CSMWorld::RevertCommand::RevertCommand(IdTable& model, const std::string& id, QUndoCommand* parent) : QUndoCommand(parent) , mModel(model) , mId(id) , mOld(nullptr){ setText(("Revert record " + id).c_str());}
void CSMWorld::RevertCommand::redo(){ mOld = mModel.getRecord(mId).clone();
int column = mModel.findColumnIndex(Columns::ColumnId_Modification);
QModelIndex index = mModel.getModelIndex(mId, column); RecordBase::State state = static_cast<RecordBase::State>(mModel.data(index).toInt());
if (state == RecordBase::State_ModifiedOnly) { mModel.removeRows(index.row(), 1); } else { mModel.setData(index, static_cast<int>(RecordBase::State_BaseOnly)); }}
void CSMWorld::RevertCommand::undo(){ mModel.setRecord(mId, std::move(mOld));}
CSMWorld::DeleteCommand::DeleteCommand( IdTable& model, const std::string& id, CSMWorld::UniversalId::Type type, QUndoCommand* parent) : QUndoCommand(parent) , mModel(model) , mId(id) , mOld(nullptr) , mType(type){ setText(("Delete record " + id).c_str());}
void CSMWorld::DeleteCommand::redo(){ mOld = mModel.getRecord(mId).clone();
int column = mModel.findColumnIndex(Columns::ColumnId_Modification);
QModelIndex index = mModel.getModelIndex(mId, column); RecordBase::State state = static_cast<RecordBase::State>(mModel.data(index).toInt());
if (state == RecordBase::State_ModifiedOnly) { mModel.removeRows(index.row(), 1); } else { mModel.setData(index, static_cast<int>(RecordBase::State_Deleted)); }}
void CSMWorld::DeleteCommand::undo(){ mModel.setRecord(mId, std::move(mOld), mType);}
CSMWorld::ReorderRowsCommand::ReorderRowsCommand(IdTable& model, int baseIndex, const std::vector<int>& newOrder) : mModel(model) , mBaseIndex(baseIndex) , mNewOrder(newOrder){}
void CSMWorld::ReorderRowsCommand::redo(){ mModel.reorderRows(mBaseIndex, mNewOrder);}
void CSMWorld::ReorderRowsCommand::undo(){ int size = static_cast<int>(mNewOrder.size()); std::vector<int> reverse(size);
for (int i = 0; i < size; ++i) reverse.at(mNewOrder[i]) = i;
mModel.reorderRows(mBaseIndex, reverse);}
CSMWorld::CloneCommand::CloneCommand(CSMWorld::IdTable& model, const std::string& idOrigin, const std::string& idDestination, const CSMWorld::UniversalId::Type type, QUndoCommand* parent) : CreateCommand(model, idDestination, parent) , mIdOrigin(idOrigin){ setType(type); setText(("Clone record " + idOrigin + " to the " + idDestination).c_str());}
void CSMWorld::CloneCommand::redo(){ mModel.cloneRecord(ESM::RefId::stringRefId(mIdOrigin), ESM::RefId::stringRefId(mId), mType); applyModifications(); for (auto& value : mOverrideValues) { mModel.setData(mModel.getModelIndex(mId, value.first), value.second); }}
void CSMWorld::CloneCommand::undo(){ mModel.removeRow(mModel.getModelIndex(mId, 0).row());}
void CSMWorld::CloneCommand::setOverrideValue(int column, QVariant value){ mOverrideValues.emplace_back(std::make_pair(column, value));}
CSMWorld::CreatePathgridCommand::CreatePathgridCommand(IdTable& model, const std::string& id, QUndoCommand* parent) : CreateCommand(model, id, parent){ setType(UniversalId::Type_Pathgrid);}
void CSMWorld::CreatePathgridCommand::redo(){ CreateCommand::redo();
std::unique_ptr<Record<Pathgrid>> record = std::make_unique<Record<Pathgrid>>(static_cast<const Record<Pathgrid>&>(mModel.getRecord(mId))); record->get().blank(); record->get().mCell = ESM::RefId::stringRefId(mId);
std::pair<CellCoordinates, bool> coords = CellCoordinates::fromId(mId); if (coords.second) { record->get().mData.mX = coords.first.getX(); record->get().mData.mY = coords.first.getY(); }
mModel.setRecord(mId, std::move(record), mType);}
CSMWorld::UpdateCellCommand::UpdateCellCommand(IdTable& model, int row, QUndoCommand* parent) : QUndoCommand(parent) , mModel(model) , mRow(row){ setText("Update cell ID");}
void CSMWorld::UpdateCellCommand::redo(){ if (!mNew.isValid()) { int cellColumn = mModel.searchColumnIndex(Columns::ColumnId_Cell); mIndex = mModel.index(mRow, cellColumn);
QModelIndex xIndex = mModel.index(mRow, mModel.findColumnIndex(Columns::ColumnId_PositionXPos));
QModelIndex yIndex = mModel.index(mRow, mModel.findColumnIndex(Columns::ColumnId_PositionYPos));
int x = std::floor(mModel.data(xIndex).toFloat() / Constants::CellSizeInUnits); int y = std::floor(mModel.data(yIndex).toFloat() / Constants::CellSizeInUnits);
std::ostringstream stream;
stream << "#" << x << " " << y;
mNew = QString::fromUtf8(stream.str().c_str()); }
mModel.setData(mIndex, mNew);}
void CSMWorld::UpdateCellCommand::undo(){ mModel.setData(mIndex, mOld);}
CSMWorld::DeleteNestedCommand::DeleteNestedCommand( IdTree& model, const std::string& id, int nestedRow, int parentColumn, QUndoCommand* parent) : QUndoCommand(parent) , NestedTableStoring(model, id, parentColumn) , mModel(model) , mId(id) , mParentColumn(parentColumn) , mNestedRow(nestedRow){ std::string title = model.headerData(parentColumn, Qt::Horizontal, Qt::DisplayRole).toString().toUtf8().constData(); setText(("Delete row in " + title + " sub-table of " + mId).c_str());
QModelIndex parentIndex = mModel.getModelIndex(mId, mParentColumn); mModifyParentCommand = new ModifyCommand(mModel, parentIndex, parentIndex.data(Qt::EditRole), this);}
void CSMWorld::DeleteNestedCommand::redo(){ QModelIndex parentIndex = mModel.getModelIndex(mId, mParentColumn); mModifyParentCommand->redo(); mModel.removeRows(mNestedRow, 1, parentIndex);}
void CSMWorld::DeleteNestedCommand::undo(){ QModelIndex parentIndex = mModel.getModelIndex(mId, mParentColumn); mModel.setNestedTable(parentIndex, getOld()); mModifyParentCommand->undo();}
CSMWorld::AddNestedCommand::AddNestedCommand( IdTree& model, const std::string& id, int nestedRow, int parentColumn, QUndoCommand* parent) : QUndoCommand(parent) , NestedTableStoring(model, id, parentColumn) , mModel(model) , mId(id) , mNewRow(nestedRow) , mParentColumn(parentColumn){ std::string title = model.headerData(parentColumn, Qt::Horizontal, Qt::DisplayRole).toString().toUtf8().constData(); setText(("Add row in " + title + " sub-table of " + mId).c_str());
QModelIndex parentIndex = mModel.getModelIndex(mId, mParentColumn); mModifyParentCommand = new ModifyCommand(mModel, parentIndex, parentIndex.data(Qt::EditRole), this);}
void CSMWorld::AddNestedCommand::redo(){ QModelIndex parentIndex = mModel.getModelIndex(mId, mParentColumn); mModifyParentCommand->redo(); mModel.addNestedRow(parentIndex, mNewRow);}
void CSMWorld::AddNestedCommand::undo(){ QModelIndex parentIndex = mModel.getModelIndex(mId, mParentColumn); mModel.setNestedTable(parentIndex, getOld()); mModifyParentCommand->undo();}
CSMWorld::NestedTableStoring::NestedTableStoring(const IdTree& model, const std::string& id, int parentColumn) : mOld(model.nestedTable(model.getModelIndex(id, parentColumn))){}
CSMWorld::NestedTableStoring::~NestedTableStoring(){ delete mOld;}
const CSMWorld::NestedTableWrapperBase& CSMWorld::NestedTableStoring::getOld() const{ return *mOld;}