/** * @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 #include #include // Qt #include #include #include #include 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 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>( { 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> &mapObjectDataVec) { setData(mapObjectDataVec); for (const QSharedPointer &mapObjectData : mapObjectDataVec) if (mapObjectData) { updateMapObjectDataToEnsureGoodQuality(mapObjectData.data()); connect(mapObjectData.data(), &MapObjectData::dataChanged, this, &MapObjectModel::onDataChanged, Qt::UniqueConnection); } } bool MapObjectModel::addMapObjectData(const QSharedPointer &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) { 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("")); 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("
").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("

%1: %2

").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("

%1: %2

").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("

%1: %2

").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("

%1: %2

").arg(hdr, moData->m_description2)); } #ifdef LTS_DEBUG tooltip.append(QStringLiteral("

participantId: %2

").arg(moData->m_participantId)); tooltip.append(QStringLiteral("

row: %2

").arg(index.row())); tooltip.append(QStringLiteral("

uniqueMapId: %2

").arg(moData->m_uniqueMapId)); tooltip.append(QStringLiteral("

typeId: %2

").arg(moData->m_typeId)); tooltip.append(QStringLiteral("

vulnerabilityStateId: %2

").arg(moData->m_vulnerabilityState)); #endif // LTS_DEBUG tooltip.append(QStringLiteral("")); 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 &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(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 &left = m_sourceModel->m_vecData.at(source_left.row()); const QSharedPointer&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