1
0
mirror of https://gitlab.com/OpenMW/openmw.git synced 2025-01-10 15:39:02 +00:00
OpenMW/apps/opencs/model/world/regionmap.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

503 lines
13 KiB
C++

#include "regionmap.hpp"
#include <QBrush>
#include <QModelIndex>
#include <QSize>
#include <QVariant>
#include <algorithm>
#include <sstream>
#include <stdexcept>
#include <type_traits>
#include <apps/opencs/model/world/cell.hpp>
#include <apps/opencs/model/world/cellcoordinates.hpp>
#include <apps/opencs/model/world/idcollection.hpp>
#include <apps/opencs/model/world/record.hpp>
#include <components/esm3/loadregn.hpp>
#include <components/misc/strings/lower.hpp>
#include "data.hpp"
#include "universalid.hpp"
CSMWorld::RegionMap::CellDescription::CellDescription()
: mDeleted(false)
{
}
CSMWorld::RegionMap::CellDescription::CellDescription(const Record<Cell>& cell)
{
const Cell& cell2 = cell.get();
if (!cell2.isExterior())
throw std::logic_error("Interior cell in region map");
mDeleted = cell.isDeleted();
mRegion = cell2.mRegion;
mName = cell2.mName;
}
CSMWorld::CellCoordinates CSMWorld::RegionMap::getIndex(const QModelIndex& index) const
{
return mMin.move(index.column(), mMax.getY() - mMin.getY() - index.row() - 1);
}
QModelIndex CSMWorld::RegionMap::getIndex(const CellCoordinates& index) const
{
// I hate you, Qt API naming scheme!
return QAbstractTableModel::index(
mMax.getY() - mMin.getY() - (index.getY() - mMin.getY()) - 1, index.getX() - mMin.getX());
}
CSMWorld::CellCoordinates CSMWorld::RegionMap::getIndex(const Cell& cell) const
{
std::istringstream stream(cell.mId.getRefIdString());
char ignore;
int x = 0;
int y = 0;
stream >> ignore >> x >> y;
return CellCoordinates(x, y);
}
void CSMWorld::RegionMap::buildRegions()
{
const IdCollection<ESM::Region>& regions = mData.getRegions();
int size = regions.getSize();
for (int i = 0; i < size; ++i)
{
const Record<ESM::Region>& region = regions.getRecord(i);
if (!region.isDeleted())
mColours.insert(std::make_pair(region.get().mId, region.get().mMapColor));
}
}
void CSMWorld::RegionMap::buildMap()
{
const IdCollection<Cell>& cells = mData.getCells();
int size = cells.getSize();
for (int i = 0; i < size; ++i)
{
const Record<Cell>& cell = cells.getRecord(i);
const Cell& cell2 = cell.get();
if (cell2.isExterior())
{
CellDescription description(cell);
CellCoordinates index = getIndex(cell2);
mMap.insert(std::make_pair(index, description));
}
}
std::pair<CellCoordinates, CellCoordinates> mapSize = getSize();
mMin = mapSize.first;
mMax = mapSize.second;
}
void CSMWorld::RegionMap::addCell(const CellCoordinates& index, const CellDescription& description)
{
std::map<CellCoordinates, CellDescription>::iterator cell = mMap.find(index);
if (cell != mMap.end())
{
cell->second = description;
}
else
{
updateSize();
mMap.insert(std::make_pair(index, description));
}
QModelIndex index2 = getIndex(index);
dataChanged(index2, index2);
}
void CSMWorld::RegionMap::addCells(int start, int end)
{
const IdCollection<Cell>& cells = mData.getCells();
for (int i = start; i <= end; ++i)
{
const Record<Cell>& cell = cells.getRecord(i);
const Cell& cell2 = cell.get();
if (cell2.isExterior())
{
CellCoordinates index = getIndex(cell2);
CellDescription description(cell);
addCell(index, description);
}
}
}
void CSMWorld::RegionMap::removeCell(const CellCoordinates& index)
{
std::map<CellCoordinates, CellDescription>::iterator cell = mMap.find(index);
if (cell != mMap.end())
{
mMap.erase(cell);
QModelIndex index2 = getIndex(index);
dataChanged(index2, index2);
updateSize();
}
}
void CSMWorld::RegionMap::addRegion(const ESM::RefId& region, unsigned int colour)
{
mColours[region] = colour;
}
void CSMWorld::RegionMap::removeRegion(const ESM::RefId& region)
{
auto iter(mColours.find(region));
if (iter != mColours.end())
mColours.erase(iter);
}
void CSMWorld::RegionMap::updateRegions(const std::vector<ESM::RefId>& regions)
{
std::vector<ESM::RefId> regions2(regions);
std::sort(regions2.begin(), regions2.end());
for (std::map<CellCoordinates, CellDescription>::const_iterator iter(mMap.begin()); iter != mMap.end(); ++iter)
{
if (!iter->second.mRegion.empty()
&& std::find(regions2.begin(), regions2.end(), iter->second.mRegion) != regions2.end())
{
QModelIndex index = getIndex(iter->first);
dataChanged(index, index);
}
}
}
void CSMWorld::RegionMap::updateSize()
{
std::pair<CellCoordinates, CellCoordinates> size = getSize();
if (int diff = size.first.getX() - mMin.getX())
{
beginInsertColumns(QModelIndex(), 0, std::abs(diff) - 1);
mMin = CellCoordinates(size.first.getX(), mMin.getY());
endInsertColumns();
}
if (int diff = size.first.getY() - mMin.getY())
{
beginInsertRows(QModelIndex(), 0, std::abs(diff) - 1);
mMin = CellCoordinates(mMin.getX(), size.first.getY());
endInsertRows();
}
if (int diff = size.second.getX() - mMax.getX())
{
int columns = columnCount();
if (diff > 0)
beginInsertColumns(QModelIndex(), columns, columns + diff - 1);
else
beginRemoveColumns(QModelIndex(), columns + diff, columns - 1);
mMax = CellCoordinates(size.second.getX(), mMax.getY());
endInsertColumns();
}
if (int diff = size.second.getY() - mMax.getY())
{
int rows = rowCount();
if (diff > 0)
beginInsertRows(QModelIndex(), rows, rows + diff - 1);
else
beginRemoveRows(QModelIndex(), rows + diff, rows - 1);
mMax = CellCoordinates(mMax.getX(), size.second.getY());
endInsertRows();
}
}
std::pair<CSMWorld::CellCoordinates, CSMWorld::CellCoordinates> CSMWorld::RegionMap::getSize() const
{
const IdCollection<Cell>& cells = mData.getCells();
int size = cells.getSize();
CellCoordinates min(0, 0);
CellCoordinates max(0, 0);
for (int i = 0; i < size; ++i)
{
const Record<Cell>& cell = cells.getRecord(i);
const Cell& cell2 = cell.get();
if (cell2.isExterior())
{
CellCoordinates index = getIndex(cell2);
if (min == max)
{
min = index;
max = min.move(1, 1);
}
else
{
if (index.getX() < min.getX())
min = CellCoordinates(index.getX(), min.getY());
else if (index.getX() >= max.getX())
max = CellCoordinates(index.getX() + 1, max.getY());
if (index.getY() < min.getY())
min = CellCoordinates(min.getX(), index.getY());
else if (index.getY() >= max.getY())
max = CellCoordinates(max.getX(), index.getY() + 1);
}
}
}
return std::make_pair(min, max);
}
CSMWorld::RegionMap::RegionMap(Data& data)
: mData(data)
{
buildRegions();
buildMap();
QAbstractItemModel* regions = data.getTableModel(UniversalId(UniversalId::Type_Regions));
connect(regions, &QAbstractItemModel::rowsAboutToBeRemoved, this, &RegionMap::regionsAboutToBeRemoved);
connect(regions, &QAbstractItemModel::rowsInserted, this, &RegionMap::regionsInserted);
connect(regions, &QAbstractItemModel::dataChanged, this, &RegionMap::regionsChanged);
QAbstractItemModel* cells = data.getTableModel(UniversalId(UniversalId::Type_Cells));
connect(cells, &QAbstractItemModel::rowsAboutToBeRemoved, this, &RegionMap::cellsAboutToBeRemoved);
connect(cells, &QAbstractItemModel::rowsInserted, this, &RegionMap::cellsInserted);
connect(cells, &QAbstractItemModel::dataChanged, this, &RegionMap::cellsChanged);
}
int CSMWorld::RegionMap::rowCount(const QModelIndex& parent) const
{
if (parent.isValid())
return 0;
return mMax.getY() - mMin.getY();
}
int CSMWorld::RegionMap::columnCount(const QModelIndex& parent) const
{
if (parent.isValid())
return 0;
return mMax.getX() - mMin.getX();
}
QVariant CSMWorld::RegionMap::data(const QModelIndex& index, int role) const
{
if (role == Qt::SizeHintRole)
return QSize(16, 16);
if (role == Qt::BackgroundRole)
{
/// \todo GUI class in non-GUI code. Needs to be addressed eventually.
std::map<CellCoordinates, CellDescription>::const_iterator cell = mMap.find(getIndex(index));
if (cell != mMap.end())
{
if (cell->second.mDeleted)
return QBrush(Qt::red, Qt::DiagCrossPattern);
auto iter = mColours.find(cell->second.mRegion);
if (iter != mColours.end())
return QBrush(QColor(iter->second & 0xff, (iter->second >> 8) & 0xff, (iter->second >> 16) & 0xff));
if (cell->second.mRegion.empty())
return QBrush(Qt::Dense6Pattern); // no region
return QBrush(Qt::red, Qt::Dense6Pattern); // invalid region
}
return QBrush(Qt::DiagCrossPattern);
}
if (role == Qt::ToolTipRole)
{
CellCoordinates cellIndex = getIndex(index);
std::ostringstream stream;
stream << cellIndex;
std::map<CellCoordinates, CellDescription>::const_iterator cell = mMap.find(cellIndex);
if (cell != mMap.end())
{
if (!cell->second.mName.empty())
stream << " " << cell->second.mName;
if (cell->second.mDeleted)
stream << " (deleted)";
if (!cell->second.mRegion.empty())
{
stream << "<br>";
auto iter = mColours.find(cell->second.mRegion);
if (iter != mColours.end())
stream << cell->second.mRegion;
else
stream << "<font color=red>" << cell->second.mRegion << "</font>";
}
}
else
stream << " (no cell)";
return QString::fromUtf8(stream.str().c_str());
}
if (role == Role_Region)
{
CellCoordinates cellIndex = getIndex(index);
std::map<CellCoordinates, CellDescription>::const_iterator cell = mMap.find(cellIndex);
if (cell != mMap.end() && !cell->second.mRegion.empty())
return QString::fromUtf8(cell->second.mRegion.getRefIdString().c_str());
}
if (role == Role_CellId)
{
CellCoordinates cellIndex = getIndex(index);
std::ostringstream stream;
stream << "#" << cellIndex.getX() << " " << cellIndex.getY();
return QString::fromUtf8(stream.str().c_str());
}
return QVariant();
}
Qt::ItemFlags CSMWorld::RegionMap::flags(const QModelIndex& index) const
{
return Qt::ItemIsSelectable | Qt::ItemIsEnabled;
}
void CSMWorld::RegionMap::regionsAboutToBeRemoved(const QModelIndex& parent, int start, int end)
{
std::vector<ESM::RefId> update;
const IdCollection<ESM::Region>& regions = mData.getRegions();
for (int i = start; i <= end; ++i)
{
const Record<ESM::Region>& region = regions.getRecord(i);
update.push_back(region.get().mId);
removeRegion(region.get().mId);
}
updateRegions(update);
}
void CSMWorld::RegionMap::regionsInserted(const QModelIndex& parent, int start, int end)
{
std::vector<ESM::RefId> update;
const IdCollection<ESM::Region>& regions = mData.getRegions();
for (int i = start; i <= end; ++i)
{
const Record<ESM::Region>& region = regions.getRecord(i);
if (!region.isDeleted())
{
update.push_back(region.get().mId);
addRegion(region.get().mId, region.get().mMapColor);
}
}
updateRegions(update);
}
void CSMWorld::RegionMap::regionsChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight)
{
// Note: At this point an additional check could be inserted to see if there is any change to the
// columns we are interested in. If not we can exit the function here and avoid all updating.
std::vector<ESM::RefId> update;
const IdCollection<ESM::Region>& regions = mData.getRegions();
for (int i = topLeft.row(); i <= bottomRight.column(); ++i)
{
const Record<ESM::Region>& region = regions.getRecord(i);
update.push_back(region.get().mId);
if (!region.isDeleted())
addRegion(region.get().mId, region.get().mMapColor);
else
removeRegion(region.get().mId);
}
updateRegions(update);
}
void CSMWorld::RegionMap::cellsAboutToBeRemoved(const QModelIndex& parent, int start, int end)
{
const IdCollection<Cell>& cells = mData.getCells();
for (int i = start; i <= end; ++i)
{
const Record<Cell>& cell = cells.getRecord(i);
const Cell& cell2 = cell.get();
if (cell2.isExterior())
removeCell(getIndex(cell2));
}
}
void CSMWorld::RegionMap::cellsInserted(const QModelIndex& parent, int start, int end)
{
addCells(start, end);
}
void CSMWorld::RegionMap::cellsChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight)
{
// Note: At this point an additional check could be inserted to see if there is any change to the
// columns we are interested in. If not we can exit the function here and avoid all updating.
addCells(topLeft.row(), bottomRight.row());
}