Files
qt5-legatus-legacy/Lts/Plugins/SituationMapPlugin/Gui/SituationMap/MapObjectSelection/map_object_model.cpp
T
2026-08-13 18:33:10 +02:00

546 lines
17 KiB
C++
Executable File

/**
* @RDE_HEADER@
* HMI Plugin for ExerciseManangement - Exercise Overview Dialog Model
* $Id$
* $HeadURL$
* $Author$
* Created by Christian Ehrlicher, SLSE, 2016-02-29
*/
// Own
#include "map_object_model.h"
#include <Symbology/sidc_codec.h>
#include <SituationMap/Map/party_id_to_color_functor.h>
#include <LtsWidgets/lts_column_filter_proxy_model.h>
// Qt
#include <QString>
#include <QBuffer>
#include <QPainter>
#include <QGuiApplication>
namespace SituationMap
{
static constexpr int ICON_RESOLUTION_CUSTOM = 128;
static constexpr int ICON_RESOLUTION_TABLE = 32;
void updateMapObjectDataToEnsureGoodQuality(MapObjectData *mod)
{
mod->m_symDataToRender.sizeL = ICON_RESOLUTION_CUSTOM;
}
MapObjectData::~MapObjectData()
{
delete m_symbolResultWatcher;
}
void MapObjectData::onResultReady()
{
if (!m_symbolResultWatcher)
return;
QSharedPointer<AdvancedSymbology::SymbolResult> symResult = m_symbolResultWatcher->symbolResult();
if (!symResult)
return; // should NEVER happen! (Which object should have triggered the signal???)
setImage(symResult->symbolImage().image());
emit dataChanged(m_uniqueMapId);
m_symbolResultWatcher->deleteLater();
m_symbolResultWatcher = nullptr;
}
void MapObjectData::requestImage(AdvancedSymbology::SymbolDictionary *dict)
{
if (!m_symbolResultWatcher)
{
m_symbolResultWatcher = new AdvancedSymbology::SymbolResultWatcher;
connect(m_symbolResultWatcher, &AdvancedSymbology::SymbolResultWatcher::resultReady,
this, &MapObjectData::onResultReady);
}
updateMapObjectDataToEnsureGoodQuality(this);
m_symbolResultWatcher->setSymbolResult(dict->findImage(m_symDataToRender));
}
void MapObjectData::setImage(const QImage &img)
{
if (img.isNull())
{
m_pixIcon = QPixmap();
m_pixPicture = QPixmap();
return;
}
const auto dpr = qApp->devicePixelRatio(); // only works when all screens have the same dpr, otherwise
// the widget (= screen) must be known
m_pixIcon = QPixmap::fromImage(img.scaled(QSize(ICON_RESOLUTION_TABLE, ICON_RESOLUTION_TABLE) * dpr,
Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation));
m_pixPicture = QPixmap::fromImage(img.scaled(QSize(ICON_RESOLUTION_CUSTOM, ICON_RESOLUTION_CUSTOM) * dpr,
Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation));
m_pixIcon.setDevicePixelRatio(dpr);
m_pixPicture.setDevicePixelRatio(dpr);
}
//---------------------------------
// MapObjectModel
//---------------------------------
MapObjectModel::MapObjectModel(QObject *parent)
: LtsWidgets::LtsAbstractTableModel<QSharedPointer<MapObjectData>>(
{
tr("UniqueMapid"),
tr("Sidc"),
tr("Description"),
tr("Icon"),
tr("ParticipantId"),
tr("Equipment number"),
tr("Party"),
tr("TypeId"),
tr("Type"),
tr("VulnerabilityStateId"),
tr("VulnerabilityState"),
tr("Designator"),
tr("Tact. Identifier"),
tr("Picture")
},
parent)
, m_logger(getLogger("MapObjectModel"))
{
TRACE_LOG(m_logger) << LOGFUNCTION << " START";
TRACE_LOG(m_logger) << LOGFUNCTION << " END";
}
MapObjectModel::~MapObjectModel()
{
TRACE_LOG(m_logger) << LOGFUNCTION << " START";
TRACE_LOG(m_logger) << LOGFUNCTION << " END";
}
void MapObjectModel::initModel(Map *map)
{
m_map = map;
m_dictionary = &(m_map->symbolDictionary());
// Assume that the dialog (and therefore this underlying model) is created
// AFTER all user-defined data is udes to update SymbolDictionary.
// So we can simply use the changedDELAYED:
connect(m_dictionary, &AdvancedSymbology::SymbolDictionary::changedDelayed, this, &MapObjectModel::requestCivilIcons, Qt::UniqueConnection);
connect(this, &MapObjectModel::rowsInserted, this, &MapObjectModel::updateUsedPartyIds);
connect(this, &MapObjectModel::rowsRemoved, this, &MapObjectModel::updateUsedPartyIds);
connect(this, &MapObjectModel::modelReset, this, &MapObjectModel::updateUsedPartyIds);
}
bool MapObjectModel::contains(UniqueMapId uniqueMapId) const
{
return findRowForUniqueMapId(uniqueMapId) >= 0;
}
void MapObjectModel::remove(UniqueMapId uniqueMapId)
{
const int row = findRowForUniqueMapId(uniqueMapId);
removeRow(row);
}
void MapObjectModel::setMapObjectData(const QVector<QSharedPointer<MapObjectData>> &mapObjectDataVec)
{
setData(mapObjectDataVec);
for (const QSharedPointer<MapObjectData> &mapObjectData : mapObjectDataVec)
if (mapObjectData)
{
updateMapObjectDataToEnsureGoodQuality(mapObjectData.data());
connect(mapObjectData.data(), &MapObjectData::dataChanged,
this, &MapObjectModel::onDataChanged, Qt::UniqueConnection);
}
}
bool MapObjectModel::addMapObjectData(const QSharedPointer<MapObjectData> &mapObjectData)
{
const int row = findRowForUniqueMapId(mapObjectData->m_uniqueMapId);
if (row >= 0)
return false;
updateMapObjectDataToEnsureGoodQuality(mapObjectData.data());
// APPEND
if (!insertRow(mapObjectData))
return false;
connect(mapObjectData.data(), &MapObjectData::dataChanged,
this, &MapObjectModel::onDataChanged, Qt::UniqueConnection);
return true;
}
QImage markImage(const QPixmap &orgPixmap)
{
QImage newImage(orgPixmap.size(), QImage::Format_ARGB32);
static QColor fillColor = QColor(211, 211, 211, 120);
newImage.fill(fillColor);
QPainter painter(&newImage);
painter.setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform);
painter.drawPixmap(orgPixmap.rect(), orgPixmap);
painter.end();
return newImage;
}
bool MapObjectModel::updateMapObjectData(const QSharedPointer<MapObjectData> &mapObjectData)
{
const int row = findRowForUniqueMapId(mapObjectData->m_uniqueMapId);
if (row < 0)
return false;
// UPDATE
if (!mapObjectData->m_symDataToRender.sidc.isEmpty()) // if sidc is provided
{
updateMapObjectDataToEnsureGoodQuality(mapObjectData.data());
const auto &oldRowData = m_vecData.at(row);
if (oldRowData->m_symDataToRender == mapObjectData->m_symDataToRender)
return true; // image will not change -> return;
// 1) 'mark' old image and reuse it:
mapObjectData->setImage(markImage(oldRowData->m_pixPicture));
// 2) icons are 'reused', but start update
requestIcon(mapObjectData);
}
connect(mapObjectData.data(), &MapObjectData::dataChanged,
this, &MapObjectModel::onDataChanged, Qt::UniqueConnection);
return setRowData(row, mapObjectData);
}
void MapObjectModel::requestCivilIcons()
{
for (const DATA_T &moData : qAsConst(m_vecData))
{
if (!moData)
continue;
const QString &sidc = moData->m_symDataToRender.sidc;
if (!AdvancedSymbology::SidcCodec::isEncoded(sidc))
continue;
if (!AdvancedSymbology::SidcCodec::isCivil(sidc))
continue;
requestIcon(moData);
} // for
}
QVariant MapObjectModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if (role == LtsWidgets::LtsColumnFilterProxyModel::ComboboxFilterRole &&
orientation == Qt::Horizontal)
{
if (section == PARTY)
{
LtsWidgets::ComboboxFilterContent cfc;
cfc.data.reserve(m_usedPartyIds.size());
LtsWidgets::ComboboxFilterData cfd;
for (const int partyId : m_usedPartyIds)
{
const PartyIdToColorFunctor::PartyData party = m_map->partyIdToColorFunctor().partyId(partyId);// m_parties.value(partyId);
cfd.data = partyId;
cfd.text = party.m_name;
cfd.icon = party.m_icon;
cfc.data.push_back(cfd);
}
cfc.sortItems = false;
return cfc.toVariant();
}
if (section == TYPE_NAME)
{
LtsWidgets::ComboboxFilterContent cfc;
cfc.data.reserve(m_usedTypeName.size());
LtsWidgets::ComboboxFilterData cfd;
for (const QString &typeName : m_usedTypeName)
{
cfd.data = typeName;
cfd.text = typeName;
cfc.data.push_back(cfd);
}
cfc.sortItems = true;
return cfc.toVariant();
}
if (section == VULNERABILITY_STATE)
{
LtsWidgets::ComboboxFilterContent cfc;
cfc.data.reserve(m_usedVulnerabilities.size());
LtsWidgets::ComboboxFilterData cfd;
for (auto it = m_usedVulnerabilities.begin(); it != m_usedVulnerabilities.end(); ++it)
{
cfd.data = it.key();
cfd.text = it.value();
cfc.data.push_back(cfd);
}
cfc.sortItems = false;
return cfc.toVariant();
}
}
return BaseClass::headerData(section, orientation, role);
}
QVariant MapObjectModel::data(const QModelIndex &index, int role) const
{
if (!indexIsValid(index))
return QVariant();
if (role != Qt::DisplayRole &&
role != Qt::ToolTipRole &&
role != Qt::EditRole && // required by QDataWidgetMapper of MapObjectCustomView
role != Qt::DecorationRole &&
role != LtsWidgets::LtsColumnFilterProxyModel::ComboboxFilterRole)
return QVariant();
const DATA_T &moData = m_vecData.at(index.row());
if (role == Qt::ToolTipRole)
{
switch (index.column())
{
case PARTY:
return QStringLiteral("%1 (%2)").arg(moData->m_partyName).arg(moData->m_partyId);
case UNIQUE_MAP_ID: // CustomView -> ToolTipRole and column==UNIQUE_MAP_ID (0)
case ICON: // TableView -> ToolTipRole and column==ICON
{
QString tooltip(QStringLiteral("<html>"));
const QPixmap &img = moData->m_pixPicture;
if (!img.isNull())
{
// without a temporary file:
QByteArray data;
QBuffer buffer(&data);
img.save(&buffer, "PNG", 100);
tooltip.append(QStringLiteral("<center><img src='data:image/png;base64,%1'></center>").arg(QString::fromLatin1(data.toBase64())));
}
if (!moData->m_typeName.isEmpty())
{
const QString &hdr = this->headerData(MapObjectModel::Columns::TYPE_NAME , Qt::Horizontal, Qt::DisplayRole).toString();
tooltip.append(QStringLiteral("<p>%1: %2</p>").arg(hdr, moData->m_typeName));
}
if (!moData->m_typeName.isEmpty())
{
const QString &hdr = this->headerData(MapObjectModel::Columns::VULNERABILITY_STATE, Qt::Horizontal, Qt::DisplayRole).toString();
tooltip.append(QStringLiteral("<p>%1: %2</p>").arg(hdr, moData->m_vulnerabilityString));
}
if (!moData->m_description1.isEmpty())
{
const QString &hdr = this->headerData(MapObjectModel::Columns::DESCRIPTION1, Qt::Horizontal, Qt::DisplayRole).toString();
tooltip.append(QStringLiteral("<p>%1: %2</p>").arg(hdr, moData->m_description1));
}
if (!moData->m_description2.isEmpty())
{
const QString &hdr = this->headerData(MapObjectModel::Columns::DESCRIPTION2, Qt::Horizontal, Qt::DisplayRole).toString();
tooltip.append(QStringLiteral("<p>%1: %2</p>").arg(hdr, moData->m_description2));
}
#ifdef LTS_DEBUG
tooltip.append(QStringLiteral("<p>participantId: %2</p>").arg(moData->m_participantId));
tooltip.append(QStringLiteral("<p>row: %2</p>").arg(index.row()));
tooltip.append(QStringLiteral("<p>uniqueMapId: %2</p>").arg(moData->m_uniqueMapId));
tooltip.append(QStringLiteral("<p>typeId: %2</p>").arg(moData->m_typeId));
tooltip.append(QStringLiteral("<p>vulnerabilityStateId: %2</p>").arg(moData->m_vulnerabilityState));
#endif // LTS_DEBUG
tooltip.append(QStringLiteral("</html>"));
return tooltip;
}
} // switch
} // if ((role == Qt::ToolTipRole))
if ((role == Qt::DisplayRole) || (role == Qt::EditRole) || (role != LtsWidgets::LtsColumnFilterProxyModel::ComboboxFilterRole))
{
const UniqueMapId &uniqueMapId = moData->m_uniqueMapId;
switch (index.column())
{
case UNIQUE_MAP_ID:
return uniqueMapId;
case SIDC:
return moData->m_symDataToRender.sidc;
case SINGLE_DESCRIPTION:
if (moData->m_equipmentNumber >= 0)
return moData->m_equipmentNumber;
else
return moData->m_description1;
case ICON:
{
// provided or already cached?
if (moData->m_pixIcon.isNull())
{
moData->setImage(AdvancedSymbology::SymbolDictionary::temporaryImage());
requestIcon(moData);
}
if (moData->m_pixIcon.isNull())
return QVariant();
return moData->m_pixIcon;
}
case PARTICIPANT_ID:
return moData->m_participantId <= 0 ? QVariant() : QVariant(moData->m_participantId);
case EQUIPMENT_NUMBER:
return moData->m_equipmentNumber <= 0 ? QVariant() : QVariant(moData->m_equipmentNumber);
case PARTY:
return moData->m_partyName;
case TYPE_ID:
return moData->m_typeId <= 0 ? QVariant() : QVariant(moData->m_typeId);
case TYPE_NAME:
return moData->m_typeName;
case VULNERABILITY_STATE:
return moData->m_vulnerabilityString;
case VULNERABILITY_STATE_ID:
return moData->m_vulnerabilityState <= 0 ? QVariant() : QVariant(moData->m_vulnerabilityState);
case DESCRIPTION1:
return moData->m_description1;
case DESCRIPTION2:
return moData->m_description2;
case PICTURE:
{
// provided or already cached?
if (moData->m_pixPicture.isNull())
{
moData->setImage(AdvancedSymbology::SymbolDictionary::temporaryImage());
requestIcon(moData);
}
if (moData->m_pixPicture.isNull())
return QVariant();
return moData->m_pixPicture;
} // case PICTURE
} // switch
}
if (role == LtsWidgets::LtsColumnFilterProxyModel::ComboboxFilterRole)
{
if (index.column() == PARTY)
return moData->m_partyId;
if (index.column() == TYPE_NAME)
return moData->m_typeName;
if (index.column() == VULNERABILITY_STATE)
return moData->m_vulnerabilityState;
}
return QVariant();
}
UniqueMapId MapObjectModel::uniqueMapIdOfIndex(const QModelIndex &index) const
{
if (!indexIsValid(index))
return SituationMap::INVALID_MAP_ID;
const DATA_T &moData = m_vecData.at(index.row());
if (!moData)
return SituationMap::INVALID_MAP_ID;
return moData->m_uniqueMapId;
}
void MapObjectModel::onDataChanged(UniqueMapId uniqueMapId)
{
TRACE_LOG(m_logger) << LOGFUNCTION << " START, uniqueMapId=" << uniqueMapId;
// resultWatcher has generated a new image -> update view:
int row = findRowForUniqueMapId(uniqueMapId);
emit dataChanged(index(row, 0), index(row, LtsAbstractTableModel::columnCount(QModelIndex()) - 1));
}
int MapObjectModel::findRowForUniqueMapId(UniqueMapId uniqueMapId) const
{
for (int i = 0; i < m_vecData.size(); ++i)
{
if (m_vecData.at(i)->m_uniqueMapId == uniqueMapId)
return i;
}
return -1;
}
void MapObjectModel::requestIcon(const MapObjectModel::DATA_T &data) const
{
// start rendering using SymbolResultWatcher:
data->requestImage(m_dictionary);
}
void MapObjectModel::updateUsedPartyIds()
{
m_usedPartyIds.clear();
m_usedTypeName.clear();
m_usedVulnerabilities.clear();
for (const QSharedPointer<MapObjectData> &item : qAsConst(m_vecData))
{
if (!item)
continue;
m_usedPartyIds[item->m_partyId] = item->m_partyId;
m_usedTypeName.insert(item->m_typeName);
m_usedVulnerabilities.insert(item->m_vulnerabilityState,
item->m_vulnerabilityState < 0 ? QStringLiteral("-") : item->m_vulnerabilityString);
}
}
// --------------------------------------------------------
// MapObjectProxyModel
// --------------------------------------------------------
void MapObjectProxyModel::setSourceModel(QAbstractItemModel *sourceModel)
{
m_sourceModel = qobject_cast<MapObjectModel*>(sourceModel);
BaseClass::setSourceModel(sourceModel);
}
bool MapObjectProxyModel::lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const
{
if (!m_sourceModel ||
source_left.column() != source_right.column() ||
source_left.row() < 0 || source_left.row() >= m_sourceModel->m_vecData.size() ||
source_right.row() < 0 || source_right.row() >= m_sourceModel->m_vecData.size()
)
return BaseClass::lessThan(source_left, source_right);
const QSharedPointer<MapObjectData> &left = m_sourceModel->m_vecData.at(source_left.row());
const QSharedPointer<MapObjectData>&right = m_sourceModel->m_vecData.at(source_right.row());
switch (source_left.column())
{
case MapObjectModel::Columns::UNIQUE_MAP_ID:
return left->m_uniqueMapId < right->m_uniqueMapId;
case MapObjectModel::Columns::SIDC:
return left->m_symDataToRender.sidc < right->m_symDataToRender.sidc;
case MapObjectModel::Columns::SINGLE_DESCRIPTION:
if (left->m_equipmentNumber && right->m_equipmentNumber)
return left->m_equipmentNumber < right->m_equipmentNumber;
if (left->m_equipmentNumber)
return true;
if (right->m_equipmentNumber)
return true;
return left->m_description1 < left->m_description1;
case MapObjectModel::Columns::PARTICIPANT_ID:
return left->m_participantId < right->m_participantId;
case MapObjectModel::Columns::EQUIPMENT_NUMBER:
return left->m_equipmentNumber < right->m_equipmentNumber;
case MapObjectModel::Columns::PARTY:
return left->m_partyId < right->m_partyId;
case MapObjectModel::Columns::TYPE_ID:
return left->m_typeId < right->m_typeId;
case MapObjectModel::Columns::TYPE_NAME:
return left->m_typeName < right->m_typeName;
case MapObjectModel::Columns::DESCRIPTION1:
return left->m_description1 < right->m_description1;
case MapObjectModel::Columns::DESCRIPTION2:
return left->m_description2 < right->m_description2;
}
return BaseClass::lessThan(source_left, source_right);
}
} // namespace SituationMap