/** * @RDE_HEADER@ * AssignmentParticipantModel * $Id$ * $HeadURL$ * $Author$ * Created by Norbert Thoden, NLE, 2020-06-29 */ #include "tgt_assignment_part_model.h" #include "tgt_common.h" #include #include #include #include // pixelMetric #include namespace TargetManagement { // some helper bool isEqual(const ShootbackConfiguration &c1, const ShootbackConfiguration &c2) { return ( (c1.interval1() == c2.interval1()) && (c1.flash1() == c2.flash1()) && (c1.effect1() == c2.effect1()) && (c1.interval2() == c2.interval2()) && (c1.flash2() == c2.flash2()) && (c1.effect2() == c2.effect2()) ); } bool isDefault(const ShootbackConfiguration &c) { return isEqual(c, ShootbackConfiguration()); } bool isEqual(const TargetAreaAssignment &a1, const TargetAreaAssignment &a2, bool ignoringAreaIdAndParticipantId) { return ( (a1.id() == a2.id()) && (a1.name() == a2.name()) && (ignoringAreaIdAndParticipantId || (a1.areaId() == a2.areaId())) && (ignoringAreaIdAndParticipantId || (a1.targetParticipantId() == a2.targetParticipantId())) && (a1.isActive() == a2.isActive()) && isEqual(a1.configuration(), a2.configuration()) ); } //--------------------------------- // AssignmentParticipantModel //--------------------------------- AssignmentParticipantModel::AssignmentParticipantModel(QObject *parent) : BaseClass( { tr("Id"), tr("Assignment Name"), tr("Assigned"), // isActive // associated target: tr("TargetId"), tr("TargetName"), // associated participant: tr("Participant Id"), tr("Equipmentnumber"), tr("Participant"), // associated Area tr("AreaId"), tr("AreaName"), // configuration tr("Interval_1"), tr("Flash_1"), tr("Effect_1"), tr("Interval_2"), tr("Flash_2"), tr("Effect_2") }, parent) , m_logger(getLogger("AssignmentParticipantModel")) {} bool AssignmentParticipantModel::initModel(const std::shared_ptr &tgtDtc, const std::shared_ptr &ppmDtc, const std::shared_ptr &ppmGuiCbxIfc, LtsSession::LtsSession *session) { m_tgt_dtc = tgtDtc; m_session = session; delete m_participantHandler; m_participantHandler = new ParticipantHandler(this); m_participantHandler->init(m_tgt_dtc, ppmDtc, ppmGuiCbxIfc /*, exmDtc*/); // exercises are no relavant yet connect(m_participantHandler, &ParticipantHandler::signalParticipantNamesChanged, this, &AssignmentParticipantModel::signalParticipantNamesChanged); connect(m_participantHandler, &ParticipantHandler::signalParticipantChanged, this, &AssignmentParticipantModel::update); // insert Model const QMap targetAreaAssignments = m_tgt_dtc->targetAreaAssignments(); INFO_LOG(m_logger) << "initModel - #targetAreaAssignments.size(): " << targetAreaAssignments.size(); connect(m_tgt_dtc.get(), &Interface::Dtc::targetAreaAssignmentChanged, this, &AssignmentParticipantModel::onTargetAreaAssignmentChanged); { auto cb = [this](const TargetManagement::TargetAreaAssignment & d) { onTargetAreaAssignmentChanged(d, DataModification::eAdded); }; m_tgt_dtc->targetAreaAssignments(cb); } INFO_LOG(m_logger) << "initModel - #targetAreaAssignments.size(): " << targetAreaAssignments.size() << " DONE"; // updateColumnFilter connect(this, &AssignmentParticipantModel::rowsInserted, this, &AssignmentParticipantModel::updateColumnFilter); connect(this, &AssignmentParticipantModel::rowsRemoved, this, &AssignmentParticipantModel::updateColumnFilter); connect(this, &AssignmentParticipantModel::modelReset, this, &AssignmentParticipantModel::updateColumnFilter); // in case of changing value like hitsToKill: connect(this, &AssignmentParticipantModel::dataChanged, this, &AssignmentParticipantModel::updateColumnFilter); updateColumnFilter(); return true; } QVariant AssignmentParticipantModel::headerData(int section, Qt::Orientation orientation, int role) const { if (role == LtsWidgets::LtsColumnFilterProxyModel::ComboboxFilterRole && orientation == Qt::Horizontal) { // a few QSet ==> const auto processString = [](const QSet &usedXYCont) -> QVariant { LtsWidgets::ComboboxFilterContent cfc; cfc.data.reserve(usedXYCont.size()); LtsWidgets::ComboboxFilterData cfd; for (const QString &value : usedXYCont) { cfd.data = value; cfd.text = value; cfc.data.push_back(cfd); } cfc.sortItems = true; return cfc.toVariant(); }; // a few Set ==> const auto processBoolean = [this](const QSet &usedXYCont) -> QVariant { LtsWidgets::ComboboxFilterContent cfc; cfc.data.reserve(usedXYCont.size()); LtsWidgets::ComboboxFilterData cfd; for (int value : usedXYCont) { cfd.data = value; cfd.text = value ? tr("Yes") : tr("No"); cfd.icon = value ? m_yes : m_no; cfc.data.push_back(cfd); } cfc.sortItems = true; return cfc.toVariant(); }; // a few Set ==> const auto processInt = [](const QSet &usedXYCont) -> QVariant { LtsWidgets::ComboboxFilterContent cfc; cfc.data.reserve(usedXYCont.size()); LtsWidgets::ComboboxFilterData cfd; for (int value : usedXYCont) { cfd.data = value; cfd.text = QString::number(value); cfc.data.push_back(cfd); } cfc.sortItems = true; return cfc.toVariant(); }; if (section == IsActive) return processBoolean(m_usedIsActive); else if (section == AreaId) return processInt(m_usedAreaIds); else if (section == AreaName) return processString(m_usedAreas); else if (section == TargetId) return processInt(m_usedTargetIds); else if (section == TargetName) return processString(m_usedTargets); else if (section == TargetPartipantId) return processInt(m_usedTargetParticipantIds); else if (section == TargetPartipantEquipmentNumber) return processInt(m_usedParticipantEqNo); else if (section == Participant) return processString(m_usedParticipants); } return BaseClass::headerData(section, orientation, role); } template QVariant handleIsDefault(const TargetManagement::TargetAreaAssignment &assignment, const T &value) { if (isDefault(assignment.configuration())) return QStringLiteral("-"); return value; } QVariant AssignmentParticipantModel::data(const QModelIndex &index, int role) const { INFO_LOG(m_logger) << LOGFUNCTION << " role=" << role << " index = " << index.column(); if (!indexIsValid(index)) return QVariant(); if (role != Qt::DisplayRole && role != Qt::EditRole && role != Qt::CheckStateRole && role != Qt::DecorationRole && role != Qt::ForegroundRole && role != Qt::BackgroundRole && role != Qt::ToolTipRole && role != LtsWidgets::LtsColumnFilterProxyModel::ComboboxFilterRole) return QVariant(); const TargetManagement::TargetAreaAssignment &assignment = m_vecData.at(index.row()); if (role == Qt::CheckStateRole) { // CheckBoxes only for columns that are actually checkable if (!flags(index).testFlag(Qt::ItemIsUserCheckable)) return QVariant(); return assignment.isActive() ? Qt::Checked : Qt::Unchecked; } else if (role == Qt::DecorationRole) { if (index.column() == Participant) { static const int pmSize = QApplication::style()->pixelMetric(QStyle::PM_SmallIconSize); const QPixmap &pm = m_participantHandler->getParticipantDecoration(assignment.targetParticipantId()); if (!pm.isNull()) return pm.scaled(pmSize, pmSize); } else if ((index.column() == Flash_1)) return assignment.configuration().flash1() ? m_yes : m_no; else if ((index.column() == Flash_2)) return assignment.configuration().flash2() ? m_yes : m_no; return QVariant(); } else if (role == Qt::BackgroundRole) { if ((index.column() == TargetId) || (index.column() == TargetName)) { int targetId = m_participantHandler->getParticipantIdToTargetId(assignment.targetParticipantId()); if (targetId <= 0) return getNoParticipantColor(); // orange } else if ((index.column() == AreaId) || (index.column() == AreaName)) { if (m_tgt_dtc->shootbackArea(assignment.areaId()).id()<=0) return getNoParticipantColor(); // orange } else if ((index.column() == TargetPartipantId) && (assignment.targetParticipantId() <= 0)) return getNoParticipantColor(); // orange else if (index.column() == TargetPartipantEquipmentNumber) { const int eqNo = m_participantHandler->getParticipantIdToEquipmentNumber(assignment.targetParticipantId()); if (eqNo <= 0) return getNoParticipantColor(); // orange } // no else here if (assignment.isActive() && isDefault(assignment.configuration())) return getAssignedButNoConfigurationColor(); // lightRed } else if (role == Qt::ForegroundRole) { if (!assignment.isActive() && (index.column() != Participant)) return QColor(Qt::gray); } else if (role == Qt::ToolTipRole) { if (index.column() == Participant) { QString tooltip(QStringLiteral("")); const QPixmap &img = m_participantHandler->getParticipantDecoration(assignment.targetParticipantId()); 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()))); QString hdr = this->headerData(AssignmentName, Qt::Horizontal, Qt::DisplayRole).toString(); tooltip.append(QStringLiteral("

%1: %2

").arg(hdr, QString::fromStdString(assignment.name()))); hdr = this->headerData(Participant, Qt::Horizontal, Qt::DisplayRole).toString(); const QString &val = m_participantHandler->getParticipantHierarchie(assignment.targetParticipantId()); tooltip.append(QStringLiteral("

%1: %2

").arg(hdr, val)); tooltip.append(QStringLiteral("")); return tooltip; } } } else if (role == LtsWidgets::LtsColumnFilterProxyModel::ComboboxFilterRole) { // following static_cast are important!!! if (index.column() == IsActive) return static_cast(assignment.isActive()); if (index.column() == AreaId) return assignment.areaId(); if (index.column() == TargetPartipantId) return assignment.targetParticipantId(); } switch (index.column()) { case Id: return assignment.id(); case AssignmentName: return QString::fromStdString(assignment.name()); case IsActive: return assignment.isActive() ? tr("Yes") : tr("No"); case TargetId: { int targetId = m_participantHandler->getParticipantIdToTargetId(assignment.targetParticipantId()); return targetId; } case TargetName: { int targetId = m_participantHandler->getParticipantIdToTargetId(assignment.targetParticipantId()); if (targetId <= 0) return QStringLiteral("-"); const ShootbackTarget &st = m_tgt_dtc->shootbackTarget(targetId); if (st.id() != targetId) return QStringLiteral("-"); return QString::fromStdString(st.name()); } // associated participant: case TargetPartipantId: return assignment.targetParticipantId(); case TargetPartipantEquipmentNumber: { const int eqNo = m_participantHandler->getParticipantIdToEquipmentNumber(assignment.targetParticipantId()); if (eqNo <= 0) return QStringLiteral("-"); return eqNo; } case Participant: return m_participantHandler->getParticipantHierarchie(assignment.targetParticipantId()); // associated area: case AreaId: return assignment.areaId(); case AreaName: return QString::fromStdString(m_tgt_dtc->shootbackArea(assignment.areaId()).name()); // configuration: case Interval_1: return handleIsDefault(assignment, assignment.configuration().interval1()); case Flash_1: return handleIsDefault(assignment, assignment.configuration().flash1()); case Effect_1: return handleIsDefault(assignment, effectToString(assignment.configuration().effect1())); case Interval_2: return handleIsDefault(assignment, assignment.configuration().interval2()); case Flash_2: return handleIsDefault(assignment, assignment.configuration().flash2()); case Effect_2: return handleIsDefault(assignment, effectToString(assignment.configuration().effect2())); } // switch return QVariant(); } bool AssignmentParticipantModel::setData(const QModelIndex &index, const QVariant &value, int role) { if (!indexIsValid(index)) return false; if (role != Qt::CheckStateRole) return false; if (!value.canConvert()) return false; bool bVal = value.toBool(); TargetAreaAssignment &data = m_vecData[index.row()]; if (data.isActive() != bVal) { data.isActive(bVal); emit dataChanged(index, index); emit signalEditableItemChanged(); } return true; } Qt::ItemFlags AssignmentParticipantModel::flags(const QModelIndex &index) const { auto f = QAbstractTableModel::flags(index); if (!index.isValid() || index.row() >= m_vecData.size()) return f; if (index.column() == Columns::IsActive) { f.setFlag(Qt::ItemIsUserCheckable); return f; } return f; } QSet AssignmentParticipantModel::getAssociatedParticipantIds() const { QSet ret; ret.reserve(m_vecData.size()); for (const TargetManagement::TargetAreaAssignment &data : m_vecData) ret += data.targetParticipantId(); return ret; } QSet AssignmentParticipantModel::getAssociatedParticipantIdsForArea(int areaId) const { QSet ret; ret.reserve(m_vecData.size()); for (const TargetManagement::TargetAreaAssignment &data : m_vecData) if (data.areaId() == areaId) ret += data.targetParticipantId(); return ret; } QSet AssignmentParticipantModel::getAssociatedAreasIdsForParticipantId(int participantId) const { QSet ret; ret.reserve(m_vecData.size()); for (const TargetManagement::TargetAreaAssignment &data : m_vecData) if (data.targetParticipantId() == participantId) ret += data.areaId(); return ret; } int AssignmentParticipantModel::findRowForAreaTargetParticipantId(int areaId, int targetParticipantId) { for (int i = 0; i < m_vecData.size(); ++i) // clazy:exclude=range-loop { const TargetAreaAssignment &curAssignment = m_vecData.at(i); if ((curAssignment.areaId() == areaId) && (curAssignment.targetParticipantId() == targetParticipantId)) return i; } return -1; } void AssignmentParticipantModel::addGapFillerForArea(int areaId) { DEBUG_LOG(m_logger) << LOGFUNCTION << " START, areaId=" << areaId; const QSet &associatedParticipantIds = getAssociatedParticipantIdsForArea(areaId); QVector gapFillers; #define USE_PARTICIPANTS_OF_ORBAT #ifdef USE_PARTICIPANTS_OF_ORBAT // participantIds with EquipmentType (WmsTypeClass::TargetVehicle||WmsTypeClass::TargetHuman) QList pIds = getParticipantHierarchies().keys(); for (auto pId : qAsConst(pIds)) { if (!associatedParticipantIds.contains(pId)) { TargetManagement::TargetAreaAssignment gapFiller; gapFiller.areaId(areaId); gapFiller.targetParticipantId(pId); gapFiller.isActive(false); gapFillers.push_back(gapFiller); } } // for #else // USE_PARTICIPANTS_OF_ORBAT // use just the (base)configured targets: auto cb = [associatedParticipantIds, areaId, &gapFillers](const TargetManagement::ShootbackTarget & targets) { if (targets.targetParticipantId() <= 0) return; if (!associatedParticipantIds.contains(targets.targetParticipantId())) { TargetManagement::TargetAreaAssignment gapFiller; gapFiller.areaId(areaId); gapFiller.targetParticipantId(targets.targetParticipantId()); gapFiller.isActive(false); gapFillers.push_back(gapFiller); } }; m_tgt_dtc->shootbackTargets(cb); #endif this->insertRows(gapFillers); DEBUG_LOG(m_logger) << LOGFUNCTION << " END, areaId=" << areaId; } void AssignmentParticipantModel::addGapFillerForTargetParticipantId(int targetParticipantId) { DEBUG_LOG(m_logger) << LOGFUNCTION << " START, targetParticipantId=" << targetParticipantId; const QSet &associatedAreaIds = getAssociatedAreasIdsForParticipantId(targetParticipantId); QVector gapFillers; // gapFillers.reserve(1000); auto cb = [associatedAreaIds, targetParticipantId, &gapFillers](const TargetManagement::ShootbackArea & area) { if (!associatedAreaIds.contains(area.id())) { TargetManagement::TargetAreaAssignment gapFiller; gapFiller.areaId(area.id()); gapFiller.targetParticipantId(targetParticipantId); gapFiller.isActive(false); gapFillers.push_back(gapFiller); } }; m_tgt_dtc->shootbackAreas(cb); this->insertRows(gapFillers); DEBUG_LOG(m_logger) << LOGFUNCTION << " END, targetParticipantId=" << targetParticipantId; } void AssignmentParticipantModel::onTargetAreaAssignmentChanged(const TargetManagement::TargetAreaAssignment &data, DataModification modType) { DEBUG_LOG(m_logger) << LOGFUNCTION << " modType=" << modType << " data=" << data; // avoid adding an assignment which already exists: if (modType == DataModification::eAdded) { int row = findRowForAreaTargetParticipantId(data.areaId(), data.targetParticipantId()); DEBUG_LOG(m_logger) << LOGFUNCTION << " row=" << row; if (row >= 0) { setRowData(row, data); emit signalParticipantNamesChanged(); return; } } BaseClass::onDataChanged(data, modType); emit signalParticipantNamesChanged(); } void AssignmentParticipantModel::updateColumnFilter() { m_usedIsActive.clear(); m_usedAreaIds.clear(); m_usedAreas.clear(); m_usedTargetIds.clear(); m_usedTargets.clear(); m_usedTargetParticipantIds.clear(); m_usedParticipantEqNo.clear(); m_usedParticipants.clear(); int row = -1; for (const TargetAreaAssignment &assignment : qAsConst(m_vecData)) { ++row; // This loops over ALL item, even if some of them are filtered by the AssignmentParticipantProxyModel (areaId, ..) m_usedIsActive.insert(assignment.isActive()); m_usedAreaIds.insert(assignment.areaId()); m_usedAreas.insert( QString::fromStdString(m_tgt_dtc->shootbackArea(assignment.areaId()).name()) ); int targetId = m_participantHandler->getParticipantIdToTargetId(assignment.targetParticipantId()); m_usedTargetIds.insert(targetId); m_usedTargets.insert(this->data(index(row, TargetName), Qt::DisplayRole).toString()); m_usedTargetParticipantIds.insert(assignment.targetParticipantId()); const int eqNo = m_participantHandler->getParticipantIdToEquipmentNumber(assignment.targetParticipantId()); m_usedParticipantEqNo.insert(eqNo); m_usedParticipants.insert(m_participantHandler->getParticipantHierarchie(assignment.targetParticipantId())); } } QString AssignmentParticipantModel::effectToString(const Effect &effect) const { switch (effect.underlying()) { case TargetManagement::Effect_def::NearMiss: return tr("NearMiss"); case TargetManagement::Effect_def::MobilityKill: return tr("MobilityKill"); case TargetManagement::Effect_def::WeaponKill: return tr("WeaponKill"); default: { // empty } } return QStringLiteral("-"); } // -------------------------------------------------------- // AssignmentParticipantProxyModel // -------------------------------------------------------- void AssignmentParticipantProxyModel::setSourceModel(QAbstractItemModel *sourceModel) { m_sourceModel = qobject_cast(sourceModel); BaseClass::setSourceModel(sourceModel); } void AssignmentParticipantProxyModel::filterToAreaId(int areaId) { m_areaId = areaId; m_targetParticipantId = -1; invalidateFilter(); } void AssignmentParticipantProxyModel::filterToTargetParticpantId(int targetParticpantId) { m_targetParticipantId = targetParticpantId; m_areaId = -1; invalidateFilter(); } bool AssignmentParticipantProxyModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const { if (!m_sourceModel) return BaseClass::filterAcceptsRow(source_row, source_parent); if (m_areaId > 0) { const TargetManagement::TargetAreaAssignment &item = m_sourceModel->m_vecData.at(source_row); return (item.areaId() == m_areaId) && BaseClass::filterAcceptsRow(source_row, source_parent); } else if ((m_targetParticipantId > 0)) { const TargetManagement::TargetAreaAssignment &item = m_sourceModel->m_vecData.at(source_row); return (item.targetParticipantId() == m_targetParticipantId) && BaseClass::filterAcceptsRow(source_row, source_parent);; } return BaseClass::filterAcceptsRow(source_row, source_parent); } bool AssignmentParticipantProxyModel::lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const { if (!m_sourceModel || !m_sourceModel->m_participantHandler || 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 TargetManagement::TargetAreaAssignment &left = m_sourceModel->m_vecData.at(source_left.row()); const TargetManagement::TargetAreaAssignment &right = m_sourceModel->m_vecData.at(source_right.row()); switch (source_left.column()) { case AssignmentParticipantModel::Columns::Id: return left.id() < right.id(); case AssignmentParticipantModel::Columns::AssignmentName: return left.name() < right.name(); case AssignmentParticipantModel::Columns::TargetPartipantId: return left.targetParticipantId() < right.targetParticipantId(); case AssignmentParticipantModel::Columns::TargetPartipantEquipmentNumber: { const int eqNoLeft = m_sourceModel->m_participantHandler->getParticipantIdToEquipmentNumber(left.targetParticipantId()); const int eqNoRight = m_sourceModel->m_participantHandler->getParticipantIdToEquipmentNumber(right.targetParticipantId()); return eqNoLeft < eqNoRight; } case AssignmentParticipantModel::Columns::AreaId: return left.areaId() < right.areaId(); case AssignmentParticipantModel::Columns::Participant: case AssignmentParticipantModel::Columns::AreaName: // QString compare return LtsUtils::lessThan(source_left.data(), source_right.data()); } return BaseClass::lessThan(source_left, source_right); } } // namespace TargetManagement