546 lines
17 KiB
C++
Executable File
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
|