1
0
Fork 0
mirror of https://github.com/OpenMW/openmw.git synced 2025-03-29 01:06:42 +00:00
openmw/apps/opencs/model/world/commanddispatcher.cpp
florent.teppe 65cdd489fb create a specific esm reader function for RefID to avoid allocation for string and then again for RefId
Fixed some types

removed useless header

applied clang format

fixed compile tests

fixed clang tidy, and closer to logic before this MR

Removed hardcoded refids

unless there is a returned value we don't use static RefIds
can use == between RefId and hardcoded string

Fix clang format

Fixed a few instances where std::string was used, when only const std::string& was needed

removed unused variable
2022-12-27 19:15:57 +01:00

330 lines
10 KiB
C++

#include "commanddispatcher.hpp"
#include <algorithm>
#include <cmath>
#include <memory>
#include <string>
#include <QAbstractItemModel>
#include <QModelIndex>
#include <QObject>
#include <QVariant>
#include <apps/opencs/model/world/columnbase.hpp>
#include <apps/opencs/model/world/columns.hpp>
#include <apps/opencs/model/world/data.hpp>
#include <apps/opencs/model/world/idtablebase.hpp>
#include <apps/opencs/model/world/ref.hpp>
#include <apps/opencs/model/world/refcollection.hpp>
#include <apps/opencs/model/world/universalid.hpp>
#include <components/esm3/loaddial.hpp>
#include <components/misc/constants.hpp>
#include <components/misc/strings/lower.hpp>
#include "../doc/document.hpp"
#include "commandmacro.hpp"
#include "commands.hpp"
#include "idtable.hpp"
#include "idtableproxymodel.hpp"
#include "record.hpp"
std::vector<std::string> CSMWorld::CommandDispatcher::getDeletableRecords() const
{
std::vector<std::string> result;
IdTable& model = dynamic_cast<IdTable&>(*mDocument.getData().getTableModel(mId));
int stateColumnIndex = model.findColumnIndex(Columns::ColumnId_Modification);
for (std::vector<std::string>::const_iterator iter(mSelection.begin()); iter != mSelection.end(); ++iter)
{
int row = model.getModelIndex(*iter, 0).row();
// check record state
RecordBase::State state
= static_cast<RecordBase::State>(model.data(model.index(row, stateColumnIndex)).toInt());
if (state == RecordBase::State_Deleted)
continue;
// check other columns (only relevant for a subset of the tables)
int dialogueTypeIndex = model.searchColumnIndex(Columns::ColumnId_DialogueType);
if (dialogueTypeIndex != -1)
{
int type = model.data(model.index(row, dialogueTypeIndex)).toInt();
if (type != ESM::Dialogue::Topic && type != ESM::Dialogue::Journal)
continue;
}
result.push_back(*iter);
}
return result;
}
std::vector<std::string> CSMWorld::CommandDispatcher::getRevertableRecords() const
{
std::vector<std::string> result;
IdTable& model = dynamic_cast<IdTable&>(*mDocument.getData().getTableModel(mId));
/// \todo Reverting temporarily disabled on tables that support reordering, because
/// revert logic currently can not handle reordering.
if (model.getFeatures() & IdTable::Feature_ReorderWithinTopic)
return result;
int stateColumnIndex = model.findColumnIndex(Columns::ColumnId_Modification);
for (std::vector<std::string>::const_iterator iter(mSelection.begin()); iter != mSelection.end(); ++iter)
{
int row = model.getModelIndex(*iter, 0).row();
// check record state
RecordBase::State state
= static_cast<RecordBase::State>(model.data(model.index(row, stateColumnIndex)).toInt());
if (state == RecordBase::State_BaseOnly)
continue;
result.push_back(*iter);
}
return result;
}
CSMWorld::CommandDispatcher::CommandDispatcher(
CSMDoc::Document& document, const CSMWorld::UniversalId& id, QObject* parent)
: QObject(parent)
, mLocked(false)
, mDocument(document)
, mId(id)
{
}
void CSMWorld::CommandDispatcher::setEditLock(bool locked)
{
mLocked = locked;
}
void CSMWorld::CommandDispatcher::setSelection(const std::vector<std::string>& selection)
{
mSelection = selection;
for (auto& sel : mSelection)
{
Misc::StringUtils::lowerCaseInPlace(sel);
}
std::sort(mSelection.begin(), mSelection.end());
}
void CSMWorld::CommandDispatcher::setExtendedTypes(const std::vector<UniversalId>& types)
{
mExtendedTypes = types;
}
bool CSMWorld::CommandDispatcher::canDelete() const
{
if (mLocked)
return false;
return getDeletableRecords().size() != 0;
}
bool CSMWorld::CommandDispatcher::canRevert() const
{
if (mLocked)
return false;
return getRevertableRecords().size() != 0;
}
std::vector<CSMWorld::UniversalId> CSMWorld::CommandDispatcher::getExtendedTypes() const
{
std::vector<CSMWorld::UniversalId> tables;
if (mId == UniversalId::Type_Cells)
{
tables.push_back(mId);
tables.emplace_back(UniversalId::Type_References);
/// \todo add other cell-specific types
}
return tables;
}
void CSMWorld::CommandDispatcher::executeModify(
QAbstractItemModel* model, const QModelIndex& index, const QVariant& new_)
{
if (mLocked)
return;
std::unique_ptr<CSMWorld::UpdateCellCommand> modifyCell;
int columnId = model->data(index, ColumnBase::Role_ColumnId).toInt();
if (columnId == Columns::ColumnId_PositionXPos || columnId == Columns::ColumnId_PositionYPos)
{
const float oldPosition = model->data(index).toFloat();
// Modulate by cell size, update cell id if reference has been moved to a new cell
if (std::abs(std::fmod(oldPosition, Constants::CellSizeInUnits))
- std::abs(std::fmod(new_.toFloat(), Constants::CellSizeInUnits))
>= 0.5f)
{
IdTableProxyModel* proxy = dynamic_cast<IdTableProxyModel*>(model);
int row = proxy ? proxy->mapToSource(index).row() : index.row();
// This is not guaranteed to be the same as \a model, since a proxy could be used.
IdTable& model2 = dynamic_cast<IdTable&>(*mDocument.getData().getTableModel(mId));
int cellColumn = model2.searchColumnIndex(Columns::ColumnId_Cell);
if (cellColumn != -1)
{
QModelIndex cellIndex = model2.index(row, cellColumn);
std::string cellId = model2.data(cellIndex).toString().toUtf8().data();
if (cellId.find('#') != std::string::npos)
{
// Need to recalculate the cell
modifyCell = std::make_unique<UpdateCellCommand>(model2, row);
}
}
}
}
auto modifyData = std::make_unique<CSMWorld::ModifyCommand>(*model, index, new_);
if (modifyCell.get())
{
CommandMacro macro(mDocument.getUndoStack());
macro.push(modifyData.release());
macro.push(modifyCell.release());
}
else
mDocument.getUndoStack().push(modifyData.release());
}
void CSMWorld::CommandDispatcher::executeDelete()
{
if (mLocked)
return;
std::vector<std::string> rows = getDeletableRecords();
if (rows.empty())
return;
IdTable& model = dynamic_cast<IdTable&>(*mDocument.getData().getTableModel(mId));
int columnIndex = model.findColumnIndex(Columns::ColumnId_Id);
CommandMacro macro(mDocument.getUndoStack(), rows.size() > 1 ? "Delete multiple records" : "");
for (std::vector<std::string>::const_iterator iter(rows.begin()); iter != rows.end(); ++iter)
{
std::string id = model.data(model.getModelIndex(*iter, columnIndex)).toString().toUtf8().constData();
if (mId.getType() == UniversalId::Type_Referenceables)
{
macro.push(new CSMWorld::DeleteCommand(model, id,
static_cast<CSMWorld::UniversalId::Type>(
model
.data(model.index(model.getModelIndex(id, columnIndex).row(),
model.findColumnIndex(CSMWorld::Columns::ColumnId_RecordType)))
.toInt())));
}
else
mDocument.getUndoStack().push(new CSMWorld::DeleteCommand(model, id));
}
}
void CSMWorld::CommandDispatcher::executeRevert()
{
if (mLocked)
return;
std::vector<std::string> rows = getRevertableRecords();
if (rows.empty())
return;
IdTable& model = dynamic_cast<IdTable&>(*mDocument.getData().getTableModel(mId));
int columnIndex = model.findColumnIndex(Columns::ColumnId_Id);
CommandMacro macro(mDocument.getUndoStack(), rows.size() > 1 ? "Revert multiple records" : "");
for (std::vector<std::string>::const_iterator iter(rows.begin()); iter != rows.end(); ++iter)
{
std::string id = model.data(model.getModelIndex(*iter, columnIndex)).toString().toUtf8().constData();
macro.push(new CSMWorld::RevertCommand(model, id));
}
}
void CSMWorld::CommandDispatcher::executeExtendedDelete()
{
CommandMacro macro(
mDocument.getUndoStack(), mExtendedTypes.size() > 1 ? tr("Extended delete of multiple records") : "");
for (std::vector<UniversalId>::const_iterator iter(mExtendedTypes.begin()); iter != mExtendedTypes.end(); ++iter)
{
if (*iter == mId)
executeDelete();
else if (*iter == UniversalId::Type_References)
{
IdTable& model = dynamic_cast<IdTable&>(*mDocument.getData().getTableModel(*iter));
const RefCollection& collection = mDocument.getData().getReferences();
int size = collection.getSize();
for (int i = size - 1; i >= 0; --i)
{
const Record<CellRef>& record = collection.getRecord(i);
if (record.mState == RecordBase::State_Deleted)
continue;
if (!std::binary_search(mSelection.begin(), mSelection.end(), record.get().mCell.getRefIdString()))
continue;
macro.push(new CSMWorld::DeleteCommand(model, record.get().mId.getRefIdString()));
}
}
}
}
void CSMWorld::CommandDispatcher::executeExtendedRevert()
{
CommandMacro macro(
mDocument.getUndoStack(), mExtendedTypes.size() > 1 ? tr("Extended revert of multiple records") : "");
for (std::vector<UniversalId>::const_iterator iter(mExtendedTypes.begin()); iter != mExtendedTypes.end(); ++iter)
{
if (*iter == mId)
executeRevert();
else if (*iter == UniversalId::Type_References)
{
IdTable& model = dynamic_cast<IdTable&>(*mDocument.getData().getTableModel(*iter));
const RefCollection& collection = mDocument.getData().getReferences();
int size = collection.getSize();
for (int i = size - 1; i >= 0; --i)
{
const Record<CellRef>& record = collection.getRecord(i);
if (!std::binary_search(mSelection.begin(), mSelection.end(), record.get().mCell.getRefIdString()))
continue;
macro.push(new CSMWorld::RevertCommand(model, record.get().mId.getRefIdString()));
}
}
}
}