/** * @RDE_HEADER@ * HMI Plugin for ParticipantManagement - Participant Edit Model * $Id$ * $HeadURL$ * $Author$ * Created by Patric Karwat, SLSE, 2016-10-17 */ #include "participant_selection_model.h" #include #include #include #include #include #include #include #include #include #include namespace dds { namespace core { static inline uint qHash(const ParticipantManagement::TypeClass &tc) { return ::qHash(tc.underlying()); } } } typedef QVector > QModelIndexPairList; namespace ParticipantManagement { static QVariant participantToQVariant(const ParticipantData &pd) { QByteArray ba; QDataStream ds(&ba, QIODevice::WriteOnly); ds << pd.id() << pd.partyId() << pd.hierarchyNumber() << (pd.equipmentNumber().is_set() ? pd.equipmentNumber().get() : -1) << pd.typeId() << pd.externalId() << QString::fromStdString(pd.symbol()) << QString::fromStdString(pd.name()) << QString::fromStdString(pd.identification()) << QString::fromStdString(pd.designator()) << QString::fromStdString(pd.tacticalIdentifier()) << QString::fromStdString(pd.info()) << pd.assignState().underlying() << pd.isSimulated() << pd.isDeleted() << pd.modId() << pd.superiorModId(); return ba; } static ParticipantData qVariantToParticipant(const QVariant &data) { ParticipantData pd; const QByteArray ba = data.toByteArray(); QDataStream ds(ba); int id, partyId, hierarchyNumber, equipmentNumber, typeId, assignState, modId, superiorModId; QString symbol, name, identification, designator, tacticalIdentifier, info; bool isSimulated, isDeleted; qint64 externalId; ds >> id >> partyId >> hierarchyNumber >> equipmentNumber >> typeId >> externalId >> symbol >> name >> identification >> designator >> tacticalIdentifier >> info >> assignState >> isSimulated >> isDeleted >> modId >> superiorModId; pd.id(id); pd.partyId(partyId); pd.hierarchyNumber(hierarchyNumber); if (equipmentNumber >= 0) pd.equipmentNumber(equipmentNumber); pd.typeId(typeId); pd.externalId(externalId); pd.symbol(symbol.toStdString()); pd.name(name.toStdString()); pd.identification(identification.toStdString()); pd.designator(designator.toStdString()); pd.tacticalIdentifier(tacticalIdentifier.toStdString()); pd.info(info.toStdString()); pd.assignState(static_cast(assignState)); pd.isSimulated(isSimulated); pd.isDeleted(isDeleted); pd.modId(modId); pd.superiorModId(superiorModId); return pd; } static bool isChecked(bool bIsInSet, Qt::CheckState cs) { if (cs == Qt::Checked) return bIsInSet; // Qt::Unchecked return !bIsInSet; } bool compareStrings(const std::string &s1, const std::string &s2, const ParticipantSelectionModel::CheckedTreeItem *p1, const ParticipantSelectionModel::CheckedTreeItem *p2) { const int iRet = strcmp(s1.c_str(), s2.c_str()); if (iRet == 0) return p1->participant.hierarchyNumber() < p2->participant.hierarchyNumber(); return iRet < 0; } //============================================================================================================================= // ParticipantSelectionModel //============================================================================================================================= ParticipantSelectionModel::ParticipantSelectionModel(QObject *parent, const std::shared_ptr &ppmDtc, const std::shared_ptr &asbIfc) : ParticipantIconModelBase( ppmDtc, asbIfc, { tr("Hierarchy/Designator"), tr("Tact. Identifier"), tr("Class"), tr("Type"), tr("Equip. No."), tr("Participant Id"), tr("Info"), tr("Sign") }, new CheckedTreeItem(QVariant()), parent, getLogger("ParticipantSelectionModel") ) , m_partyId(-1) , m_bRowsAreCheckable(false) , m_bUnitsSelectable(true) , m_missingSuperiorIdTimer(new QTimer(this)) { connect(m_dtc.get(), &ParticipantManagement::Interface::Dtc::participantChanged, this, &ParticipantSelectionModel::onParticipantChanged); setMimeType(QStringLiteral("ParticipantSelectionModel_%1").arg(qlonglong(this))); m_missingSuperiorIdTimer->setInterval(500); m_missingSuperiorIdTimer->setSingleShot(true); connect(m_missingSuperiorIdTimer, &QTimer::timeout, this, &ParticipantSelectionModel::processMissingSuperiorId); } //----------------------------------------------------------------------------------------------------------------------------- // PUBLIC MEMBER FUNCTIONS //----------------------------------------------------------------------------------------------------------------------------- Qt::ItemFlags ParticipantSelectionModel::flags(const QModelIndex &index) const { auto ret = ParticipantIconModelBase::flags(index); if (m_bRowsAreCheckable && index.column() == 0) ret |= Qt::ItemIsUserCheckable; // are units not selectable? if (!m_bUnitsSelectable && isUnitType(privateData(index)->participant.typeId())) ret &= ~Qt::ItemIsSelectable; return ret; } QVariant ParticipantSelectionModel::headerData(int section, Qt::Orientation orientation, int role) const { if (role == LtsWidgets::LtsColumnFilterProxyModel::ComboboxFilterRole && orientation == Qt::Horizontal && (section == ColTypeClass || section == ColType)) { LtsWidgets::ComboboxFilterContent cfc; cfc.data.reserve(m_usedTypeIds.size()); LtsWidgets::ComboboxFilterData cfd; if (section == ColType) { for (const int typeId : m_usedTypeIds) { cfd.data = typeId; cfd.text = ParticipantManagement::trTypeName(m_types.value(typeId).name()); cfc.data.push_back(cfd); } } else if (section == ColTypeClass) { for (const auto &typeClassId : m_usedTypeClassIds) { cfd.data = typeClassId.underlying(); cfd.text = ParticipantManagement::toString(typeClassId); cfc.data.push_back(cfd); } } cfc.sortItems = true; return cfc.toVariant(); } return ParticipantIconModelBase::headerData(section, orientation, role); } QVariant ParticipantSelectionModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) return ParticipantIconModelBase::data(index, role); if (role != Qt::DisplayRole && role != Qt::CheckStateRole && role != Qt::ToolTipRole && role != LtsWidgets::LtsColumnFilterProxyModel::ComboboxFilterRole) return ParticipantIconModelBase::data(index, role); const CheckedTreeItem *item = privateData(index); const ParticipantManagement::ParticipantData &participantData = item->participant; if (role == Qt::ToolTipRole) // special treatment of tooltip to avoid unnecessary memory allocation { if (index.column() != ColDesignator) return QVariant(); QString toolTip = QStringLiteral(""); QModelIndex idx = ParticipantSelectionModel::index(index.row(), 0, index.parent()); QIcon icon = privateData(idx)->icon(); if (!icon.isNull()) { const QPixmap &img = icon.pixmap(100, 100); QByteArray data; QBuffer buffer(&data); img.save(&buffer, "PNG", 100); toolTip += QStringLiteral("
").arg(QString::fromLatin1(data.toBase64())); } // if for (int i = 0; i < columnCount(); ++i) toolTip.append(QStringLiteral("
%1: %2").arg( headerData(i, Qt::Horizontal, Qt::DisplayRole).toString(), data(ParticipantSelectionModel::index(index.row(), i, index.parent()), Qt::DisplayRole).toString()) ); return toolTip; } // if switch (role) { case Qt::DisplayRole: switch (index.column()) { case ColDesignator: return QString::fromStdString(participantData.designator()); case ColIdentifier: return QString::fromStdString(participantData.tacticalIdentifier()); case ColTypeClass: return typeClassStringFromTypeId(participantData.typeId()); case ColType: return ParticipantManagement::trTypeName(m_types.value(participantData.typeId()).name()); case ColEquipmentNumber: if (!participantData.equipmentNumber().is_set()) { if (participantData.assignState() == ParticipantAssignState::HandoutExcon) return 0; return QVariant(); } if (participantData.assignState() == ParticipantAssignState::ExconWmsOverruled) return tr("%1 (+)").arg(participantData.equipmentNumber().get()); if (participantData.externalId() > 0) return tr("%1 (external)").arg(participantData.equipmentNumber().get()); return participantData.equipmentNumber().get(); case ColParticipantId: return participantData.id(); case ColInfo: return QString::fromStdString(participantData.info()); case ColSign: return QString::fromStdString(participantData.identification()); default: Q_UNREACHABLE(); return QVariant(); } // switch case Qt::CheckStateRole: if (!m_bRowsAreCheckable || index.column() != 0) return ParticipantIconModelBase::data(index, role); return item->bIsChecked ? Qt::Checked : Qt::Unchecked; case LtsWidgets::LtsColumnFilterProxyModel::ComboboxFilterRole: if (index.column() == ColTypeClass) return typeClassIdFromTypeId(participantData.typeId()).underlying(); if (index.column() == ColType) return participantData.typeId(); return QVariant(); default: Q_UNREACHABLE(); return QVariant(); } // switch } bool ParticipantSelectionModel::setData(const QModelIndex &index, const QVariant &value, int role) { if (role != Qt::CheckStateRole || index.column() != 0 || !index.isValid()) return ParticipantIconModelBase::setData(index, value, role); if (!value.canConvert()) return false; CheckedTreeItem *item = privateData(index); if (item->bIsChecked != value.toBool()) { item->bIsChecked = value.toBool(); emit dataChanged(index, index, {role}); } return true; } bool ParticipantSelectionModel::onReinitModel(int partyId, const QSet &checkedParticipants, Qt::CheckState cs) { m_symbolResultMapping.clear(); m_missingSuperiorId.clear(); m_usedTypeIds.clear(); m_usedTypeClassIds.clear(); m_partyId = partyId; const ParticipantManagement::Party &party = m_dtc->party(m_partyId); m_partyRole = AdvancedSymbology::roleToRole<>(party.role()); m_partyColor = QColor::fromRgb(QRgb(party.color())); ParticipantIconModelBase::resetModel(); QHash tmap; QHash> parentId2id; gatherParticipants(tmap, parentId2id); // insert recursivly insertChildrenOf(0, m_rootItem, QModelIndex(), parentId2id, tmap, checkedParticipants, cs); // unset modifed flag resetModifedFlag(m_rootItem); return true; } bool ParticipantSelectionModel::insertRow(int row, const QModelIndex &parent, NGLTreeItem *item) { return ParticipantIconModelBase::insertRow(row, parent, item); } bool ParticipantSelectionModel::removeRow(int row, const QModelIndex &parent) { return ParticipantIconModelBase::removeRow(row, parent); } bool ParticipantSelectionModel::isRowRemovable(const CheckedTreeItem *parentItem) const { if (!parentItem) return false; const ParticipantManagement::ParticipantData &participantData = parentItem->participant; if (participantData.equipmentNumber().is_set() && participantData.equipmentNumber().get() > 0) return false; for (int i = 0; i < parentItem->childCount(); i++) { if (!isRowRemovable(static_cast(parentItem->child(i)))) return false; } return true; } void ParticipantSelectionModel::dataCommitted() { m_dataToDelete.clear(); QHash tmap; QHash> parentId2id; gatherParticipants(tmap, parentId2id); // update recursivly, reupdate later if a new participant was not yet found if (!updateChildrenOf(0, m_rootItem, parentId2id, tmap)) QTimer::singleShot(500, this, &ParticipantSelectionModel::dataCommitted); resetModifedFlag(m_rootItem); } void ParticipantSelectionModel::processMissingSuperiorId() { if (m_missingSuperiorId.isEmpty()) return; // Consider if a complex subtree is move from one party to another... // Edit Participant dialog should be updated accurately: // build tree QHash tmap; QHash> parentId2id; gatherParticipants(tmap, parentId2id); // traverse every subtree: for (int id : qAsConst(m_missingSuperiorId)) { if (!tmap.contains(id)) continue; const ParticipantManagement::Participant &p = m_dtc->participant(id); if (p.id() != id) continue; onParticipantChanged(p, DataModification::eModified); if (!parentId2id.contains(id)) continue; const QSet &childIds = parentId2id.value(id); for (int childId : qAsConst(childIds)) { const ParticipantManagement::Participant &pChild = m_dtc->participant(childId); if (pChild.id() != childId) continue; onParticipantChanged(pChild, DataModification::eModified); } } // finish -> clear m_missingSuperiorId.clear(); } void ParticipantSelectionModel::selectItemByParticipantId(int participantId, const std::function &selectItem, const QModelIndex &parent) { for (int i = 0; i < rowCount(parent); ++i) { const QModelIndex idx = index(i, 0, parent); const ParticipantManagement::ParticipantData &data = privateData(idx)->participant; if (hasChildren(idx)) selectItemByParticipantId(participantId, selectItem, idx); if (data.id() == participantId) return selectItem(idx); } return; } QModelIndexList ParticipantSelectionModel::getIndexesByParticipantIds(const std::vector &participantIds, const QModelIndex &parent) const { QModelIndexList indexes; // clazy:exclude=reserve-candidates for (int row = 0; row < rowCount(parent); ++row) { const QModelIndex idx = index(row, 0, parent); const ParticipantManagement::ParticipantData &data = privateData(idx)->participant; if (hasChildren(idx)) indexes += getIndexesByParticipantIds(participantIds, idx); auto it = std::find(participantIds.begin(), participantIds.end(), data.id()); if (it != participantIds.end()) indexes.append(idx); } // for return indexes; } void ParticipantSelectionModel::setRowsCheckable(bool bCheckable) { if (m_bRowsAreCheckable == bCheckable) return; m_bRowsAreCheckable = bCheckable; onReinitModel(m_partyId); // TODO: not really needed, only invalidate first column } bool ParticipantSelectionModel::setCheckStateForParticipants(const QSet &checkedParticipants, Qt::CheckState cs) { if (cs != Qt::Checked && cs != Qt::Unchecked) return false; if (m_rootItem->childCount() < 1) QVector(); CheckedTreeItem *item = static_cast(m_rootItem); setCheckState(item, checkedParticipants, cs); return true; } ParticipantManagement::ParticipantData ParticipantSelectionModel::getParticipantData(const QModelIndex &idx) const { return privateData(idx)->participant; } std::vector ParticipantSelectionModel::participantData(bool bOnlyModified) const { std::vector ret; if (m_rootItem->childCount() < 1) return ret; int modId = 1000000000; for (int i = 0; i < m_rootItem->childCount(); ++i) { const auto c = getChildrenOf(m_rootItem->child(i), 0, 1, bOnlyModified, modId); ret.insert(ret.end(), c.begin(), c.end()); } ret.insert(ret.end(), m_dataToDelete.begin(), m_dataToDelete.end()); return ret; } QSet ParticipantSelectionModel::participantIds() const { return participantIds(m_rootItem); } QSet ParticipantSelectionModel::checkStateForParticipants(Qt::CheckState cs) const { if (m_rootItem->childCount() < 1) QSet(); CheckedTreeItem *item = static_cast(m_rootItem); return checkState(item, cs); } ParticipantSelectionModel::CheckedTreeItem *ParticipantSelectionModel::privateData(const QModelIndex &index) { return static_cast(ParticipantIconModelBase::privateData(index)); } const ParticipantSelectionModel::CheckedTreeItem *ParticipantSelectionModel::privateData(const QModelIndex &index) const { return static_cast(ParticipantIconModelBase::privateData(index)); } QModelIndex ParticipantSelectionModel::indexFromParticipantId(int participantId) const { return indexFromParticipantId(participantId, static_cast(m_rootItem)); } QString ParticipantSelectionModel::getHierarchyFromIndex(const QModelIndex &index) const { if (!index.isValid()) return QString(); const ParticipantManagement::ParticipantData &participantData = privateData(index)->participant; return m_dtc->hierarchyFromParticipant(participantData.id()); } //----------------------------------------------------------------------------------------------------------------------------- // PROTECTED MEMBER FUNCTIONS //----------------------------------------------------------------------------------------------------------------------------- void ParticipantSelectionModel::gatherParticipants(QHash &tmap, QHash> &parentId2id) const { auto cb = [this, &tmap, &parentId2id](const Participant & part) { if (part.isDeleted()) return; if (part.partyId() != m_partyId) return; // parentId -> id for lookup parentId2id[part.superiorId()] += part.id(); tmap.insert(part.id(), m_dtc->convertParticipantToParticipantData(part)); }; m_dtc->participants(cb); } std::vector ParticipantSelectionModel::getChildrenOf(NGLTreeItem *item, int parentId, int hierarchyNumber, bool bOnlyModifed, int &highestModId) const { if (!item) return {}; std::vector ret; const CheckedTreeItem *cti = static_cast(item); ParticipantManagement::ParticipantData participantData = cti->participant; participantData.superiorModId(parentId); participantData.hierarchyNumber(hierarchyNumber); participantData.partyId(m_partyId); // when moved from one party to another (e.g. tactical organisation) if (participantData.modId() == 0) participantData.modId(++highestModId); // check for children of this item for (int i = 0; i < item->childCount(); i++) { const auto c = getChildrenOf(item->child(i), participantData.modId(), i + 1, bOnlyModifed, highestModId); ret.insert(ret.end(), c.begin(), c.end()); } if (!bOnlyModifed || item->modified() || !ret.empty()) ret.push_back(std::move(participantData)); return ret; } bool ParticipantSelectionModel::updateChildrenOf(int parentId, NGLTreeItem *parent, const QHash> &parentId2id, const QHash &tmap) { bool bRet = true; const int childCount = parent->childCount(); for (int i = 0; i < childCount; ++i) { ParticipantSelectionModel::CheckedTreeItem *childItem = static_cast(parent->child(i)); ParticipantData &pdItem = childItem->participant; m_usedTypeIds += pdItem.typeId(); m_usedTypeClassIds += typeClassIdFromTypeId(pdItem.typeId()); if (pdItem.id() == 0) { const QSet &childIds = parentId2id.value(parentId); for (int childId : childIds) { const auto &childData = tmap.value(childId); if (childData.hierarchyNumber() - 1 == i) { pdItem.id(childData.id()); childItem->setModified(false); break; } } } if (!pdItem.id()) { bRet = false; continue; } if (!updateChildrenOf(pdItem.id(), childItem, parentId2id, tmap)) bRet = false; } return bRet; } void ParticipantSelectionModel::insertChildrenOf(int parentId, NGLTreeItem *parent, const QModelIndex &parentIdx, const QHash> &parentId2id, const QHash &tmap, const QSet &checkedParticipants, Qt::CheckState cs) { const QSet childIds = parentId2id.value(parentId); QMap currentHierachyNumbers; // no QSet, must be sorted! for (int childId : childIds) { auto it = tmap.constFind(childId); if (it == tmap.constEnd()) { WARNING_LOG(m_logger) << "Can not find participant with id " << childId << " - ignoring"; continue; } const ParticipantData &data = it.value(); m_usedTypeIds += data.typeId(); m_usedTypeClassIds += typeClassIdFromTypeId(data.typeId()); int insertPos = 0; if (data.hierarchyNumber() > 0) { auto hierIt = currentHierachyNumbers.insert(data.hierarchyNumber(), data.hierarchyNumber()); insertPos = std::distance(currentHierachyNumbers.begin(), hierIt); } else { // append if no valid hierarchy number is set const NGLTreeItem *treeItemParent = privateData(parentIdx); if (treeItemParent) insertPos = treeItemParent->childCount(); } CheckedTreeItem *childItem = static_cast(createItem(QVariant(), QIcon())); childItem->participant = data; childItem->bIsChecked = isChecked(checkedParticipants.contains(data.id()), cs); DEBUG_LOG(m_logger) << "insert Child " << childItem << "(" << data.id() << ", " << data.hierarchyNumber() << ") to parent " << parent << " at pos " << insertPos; insertRow(insertPos, parentIdx, childItem); tryRenderSymbol(QString::fromStdString(data.symbol()), childItem); // ToDO add role here const QModelIndex childIdx = index(insertPos, 0, parentIdx); insertChildrenOf(data.id(), childItem, childIdx, parentId2id, tmap, checkedParticipants, cs); } } bool ParticipantSelectionModel::isParentOf(const NGLTreeItem *item, int participantId) const { if (item == m_rootItem) return false; const CheckedTreeItem *cti = static_cast(item); if (cti->participant.id() == participantId) return true; return isParentOf(item->parent(), participantId); } QSet ParticipantSelectionModel::participantIds(NGLTreeItem *parentItem) const { QSet ret; ret.reserve(parentItem->childCount() + 1); ret += participantIdFromItem(parentItem); for (int i = 0; i < parentItem->childCount(); ++i) ret += participantIds(parentItem->child(i)); return ret; } QSet ParticipantSelectionModel::checkState(CheckedTreeItem *parentItem, Qt::CheckState cs) const { QSet ret; if (parentItem != m_rootItem) { if ((parentItem->bIsChecked && cs == Qt::Checked) || (!parentItem->bIsChecked && cs == Qt::Unchecked)) ret += participantIdFromItem(parentItem); } for (int i = 0; i < parentItem->childCount(); ++i) ret += checkState(static_cast(parentItem->child(i)), cs); return ret; } void ParticipantSelectionModel::setCheckState(CheckedTreeItem *parentItem, const QSet &checkedParticipants, Qt::CheckState cs) { if (parentItem != m_rootItem) { int participantId = participantIdFromItem(parentItem); const auto newState = isChecked(checkedParticipants.contains(participantId), cs); if (newState != parentItem->bIsChecked) { parentItem->bIsChecked = newState; const QModelIndex idx = indexFromItem(parentItem); emit dataChanged(idx, idx, {Qt::CheckStateRole}); } } for (int i = 0; i < parentItem->childCount(); ++i) setCheckState(static_cast(parentItem->child(i)), checkedParticipants, cs); } QModelIndex ParticipantSelectionModel::indexFromParticipantId(int participantId, const CheckedTreeItem *cti) const { if (cti->participant.id() == participantId) return indexFromItem(cti); for (int row = 0; row < cti->childCount(); ++row) { const QModelIndex idx = indexFromParticipantId(participantId, static_cast(cti->child(row))); if (idx.isValid()) return idx; } return {}; } int ParticipantSelectionModel::participantIdFromItem(NGLTreeItem *item) const { const CheckedTreeItem *cti = static_cast(item); return cti->participant.id(); } ParticipantSelectionModel::CheckedTreeItem *ParticipantSelectionModel::searchParticipant(const NGLTreeItem *parentItem, int participantId) const { const int rowCount = parentItem->childCount(); for (int r = 0; r < rowCount; ++r) { CheckedTreeItem *child = static_cast(parentItem->child(r)); if (child->participant.id() == participantId) return child; child = searchParticipant(child, participantId); if (child) return child; } return nullptr; } NGLTreeItem *ParticipantSelectionModel::createItem(const QVariant &data, const QIcon &icon) const { CheckedTreeItem *cti = new CheckedTreeItem(data, icon); cti->participant = qVariantToParticipant(data); if (cti->participant.symbol().size() != 0) { auto *that = const_cast(this); that->tryRenderSymbol(QString::fromStdString(cti->participant.symbol()), cti); } return cti; } QVariant ParticipantSelectionModel::getItemData(const NGLTreeItem *item) const { const CheckedTreeItem *cti = static_cast(item); return participantToQVariant(cti->participant); } //----------------------------------------------------------------------------------------------------------------------------- // SLOTS //----------------------------------------------------------------------------------------------------------------------------- int ParticipantSelectionModel::insertParticipant(const QModelIndex &parent, ParticipantManagement::ParticipantData participantData, bool bNewParticipant) { const QModelIndex parentCol0 = parent.column() == 0 ? parent : indexFromItem(privateData(parent)); const int row = rowCount(parentCol0); if (bNewParticipant) { participantData.id(0); // need new participantId participantData.modId(0); // need new modId } participantData.partyId(m_partyId); participantData.hierarchyNumber(row + 1); CheckedTreeItem *item = static_cast(createItem(participantToQVariant(participantData), QIcon())); item->setModified(true); if (!insertRow(row, parentCol0, item)) return -1; tryRenderSymbol(QString::fromStdString(participantData.symbol()), item); emit internalDataChanged(); return row; } int ParticipantSelectionModel::onInsertRow(const QModelIndex &parent, const ParticipantData &participantData) { return insertParticipant(parent, participantData, true); } bool ParticipantSelectionModel::onUpdateRow(const QModelIndex &index, const ParticipantManagement::ParticipantData &participantData) { if (!index.isValid()) return false; CheckedTreeItem *item = privateData(index); const ParticipantManagement::ParticipantData &oldData = item->participant; if (participantData == oldData) return true; if (oldData.symbol() != participantData.symbol()) tryRenderSymbol(QString::fromStdString(participantData.symbol()), item); emit internalDataChanged(); item->participant = participantData; return setItemData(index, QVariant(), item->icon()); } bool ParticipantSelectionModel::onDeleteRow(const QModelIndex &idx, bool isFirstItem) { while (rowCount(idx) > 0) onDeleteRow(ParticipantIconModelBase::index(0, 0, idx), false); if (isFirstItem) //edit mode { CheckedTreeItem *item = privateData(idx); ParticipantManagement::ParticipantData data = item->participant; data.isDeleted(true); data.assignState() = ParticipantAssignState::NotAssigned; data.equipmentNumber().reset(); if (data.modId() != 0) //only append old items to remove list m_dataToDelete.push_back(std::move(data)); } removeRow(idx.row(), idx.parent()); emit internalDataChanged(); return true; } void ParticipantSelectionModel::onParticipantChanged(const ParticipantManagement::Participant &data, DataModification modType) { if (data.partyId() != m_partyId) return; Q_UNUSED(modType); CheckedTreeItem *cti = searchParticipant(m_rootItem, data.id()); if (cti) { cti->participant = m_dtc->convertParticipantToParticipantData(data); emit dataChanged(indexFromItem(cti, 0), indexFromItem(cti, ColSign)); return; } CheckedTreeItem *ctiParent = searchParticipant(m_rootItem, data.superiorId()); if (ctiParent) { int insertPos = 0; if (data.hierarchyNumber() > 0) { for (int i = 0; i < ctiParent->childCount(); ++i) { CheckedTreeItem *child = static_cast(ctiParent->child(i)); if (child->participant.hierarchyNumber() > data.hierarchyNumber()) { insertPos = i; break; } } } else { // append if no valid hierarchy number is set insertPos = ctiParent->childCount(); } CheckedTreeItem *childItem = static_cast(createItem(QVariant(), QIcon())); childItem->participant = m_dtc->convertParticipantToParticipantData(data); DEBUG_LOG(m_logger) << "insert Child " << childItem << "(" << data.id() << ", " << data.hierarchyNumber() << ") to parent " << ctiParent << " at pos " << insertPos; insertRow(insertPos, indexFromItem(ctiParent), childItem); tryRenderSymbol(QString::fromStdString(data.symbol()), childItem); // ToDO add role here return; } else { // superiorId() was not found, most likely due to order of incomming updates: // Like dialog 'Edit Participant' m_missingSuperiorId.insert(data.superiorId()); m_missingSuperiorIdTimer->start(); } // onReinitModel(m_partyId); } //============================================================================================================================= // PARTICIPANTTREEFILTERPROXYMODEL //============================================================================================================================= //----------------------------------------------------------------------------------------------------------------------------- // PUBLIC MEMBER FUNCTIONS //----------------------------------------------------------------------------------------------------------------------------- void ParticipantTreeFilterProxyModel::setAllowedTypes(const QSet &allowedTypes) { if (m_allowedTypes == allowedTypes) return; m_allowedTypes = allowedTypes; invalidateFilter(); } void ParticipantTreeFilterProxyModel::setSourceModel(QAbstractItemModel *sourceModel) { m_sourceModel = qobject_cast(sourceModel); setRecursiveFilteringEnabled(true); LtsWidgets::LtsColumnFilterProxyModel::setSourceModel(sourceModel); } //----------------------------------------------------------------------------------------------------------------------------- // PROTECTED MEMBER FUNCTIONS //----------------------------------------------------------------------------------------------------------------------------- bool ParticipantTreeFilterProxyModel::lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const { if ((!m_sourceModel) || (source_left.column() != source_right.column())) return LtsWidgets::LtsColumnFilterProxyModel::lessThan(source_left, source_right); const ParticipantSelectionModel::CheckedTreeItem *left = m_sourceModel->privateData(source_left); const ParticipantSelectionModel::CheckedTreeItem *right = m_sourceModel->privateData(source_right); switch (source_left.column()) { case ParticipantSelectionModel::ColDesignator: return left->participant.hierarchyNumber() < right->participant.hierarchyNumber(); case ParticipantSelectionModel::ColIdentifier: return compareStrings(left->participant.tacticalIdentifier(), right->participant.tacticalIdentifier(), left, right); case ParticipantSelectionModel::ColTypeClass: case ParticipantSelectionModel::ColType: break; // string compare case ParticipantSelectionModel::ColEquipmentNumber: if (!left->participant.equipmentNumber().is_set() && !right->participant.equipmentNumber().is_set()) return left->participant.hierarchyNumber() < right->participant.hierarchyNumber(); if (!left->participant.equipmentNumber().is_set()) return true; if (!right->participant.equipmentNumber().is_set()) return false; return left->participant.equipmentNumber().get() < right->participant.equipmentNumber().get(); case ParticipantSelectionModel::ColParticipantId: return left->participant.id() < right->participant.id(); case ParticipantSelectionModel::ColInfo: return compareStrings(left->participant.info(), right->participant.info(), left, right); case ParticipantSelectionModel::ColSign: return compareStrings(left->participant.identification(), right->participant.identification(), left, right); } return LtsWidgets::LtsColumnFilterProxyModel::lessThan(source_left, source_right); } bool ParticipantTreeFilterProxyModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const { if ((!m_sourceModel) || (m_allowedTypes.isEmpty())) return LtsWidgets::LtsColumnFilterProxyModel::filterAcceptsRow(source_row, source_parent); const ParticipantSelectionModel::CheckedTreeItem *parentItem = m_sourceModel->privateData(source_parent); if (!parentItem) return false; const ParticipantSelectionModel::CheckedTreeItem *childItem = static_cast(parentItem->child(source_row)); if (!childItem) return false; #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) if (!m_allowedTypes.contains(childItem->participant.typeId())) return false; #else // we have to do recursive filtering by ourselves (until we only support Qt >= 5.10) if ((recursiveFilteringEnabled()) && (!filterTypeRecursive(childItem))) return false; #endif return LtsWidgets::LtsColumnFilterProxyModel::filterAcceptsRow(source_row, source_parent); } #if QT_VERSION < QT_VERSION_CHECK(5, 10, 0) bool ParticipantTreeFilterProxyModel::filterTypeRecursive(const ParticipantSelectionModel::CheckedTreeItem *item) const { if (m_allowedTypes.contains(item->participant.typeId())) return true; for (int i = 0; i < item->childCount(); ++i) { const ParticipantSelectionModel::CheckedTreeItem *child = static_cast(item->child(i)); if (m_allowedTypes.contains(child->participant.typeId())) return true; if (filterTypeRecursive(child)) return true; } return false; } #endif //============================================================================================================================= // PARTICIPANTTABLEFILTERPROXYMODEL //============================================================================================================================= ParticipantTableFilterProxyModel::ParticipantTableFilterProxyModel(QObject *parent) : LtsWidgets::LtsColumnFilterProxyModel(parent) , m_logger(ParticipantManagement::getLogger(QStringLiteral("ParticipantTableFilterProxyModel"))) { setRecursiveFilteringEnabled(false); } //----------------------------------------------------------------------------------------------------------------------------- // PUBLIC MEMBER FUNCTIONS //----------------------------------------------------------------------------------------------------------------------------- void ParticipantTableFilterProxyModel::init() { if ((!m_mapFromSource.isEmpty()) || (!m_mapToSource.isEmpty())) resetMapping(); mapTreeToTable(); // m_mapFromSource and m_mapToSource are ready m_mapFromSourceInternal = m_mapFromSource; m_mapToSourceInternal = m_mapToSource; m_mapFromSourceFiltered = m_mapFromSource; m_mapToSourceFiltered = m_mapToSource; onFilter(); if (m_sortData.isActive) onSort(m_sortData.column, m_sortData.order); } void ParticipantTableFilterProxyModel::resetMapping() { m_mapFromSourceInternal.clear(); m_mapToSourceInternal.clear(); m_mapFromSource.clear(); m_mapToSource.clear(); m_mapFromSourceFiltered.clear(); m_mapToSourceFiltered.clear(); m_sortMap.clear(); } void ParticipantTableFilterProxyModel::setSourceModel(QAbstractItemModel *sourceModel) { m_sourceModel = qobject_cast(sourceModel); LtsWidgets::LtsColumnFilterProxyModel::setSourceModel(sourceModel); } int ParticipantTableFilterProxyModel::rowCount(const QModelIndex &parent) const { return parent.isValid() ? 0 : m_mapFromSourceInternal.size(); } int ParticipantTableFilterProxyModel::columnCount(const QModelIndex &parent) const { return m_sourceModel->columnCount(mapToSource(parent)); } QVariant ParticipantTableFilterProxyModel::data(const QModelIndex &index, int role) const { if ((index.column() == ParticipantSelectionModel::Columns::ColDesignator) && (role == Qt::DisplayRole)) return m_sourceModel->getHierarchyFromIndex(mapToSource(index)); return LtsColumnFilterProxyModel::data(index, role); } QVariant ParticipantTableFilterProxyModel::headerData(int section, Qt::Orientation orientation, int role) const { return m_sourceModel->headerData(section, orientation, role); } QModelIndex ParticipantTableFilterProxyModel::mapFromSource(const QModelIndex &sourceIndex) const { if (!m_mapFromSourceInternal.contains(sourceIndex)) return {}; return createIndex(m_mapFromSourceInternal.value(sourceIndex), sourceIndex.column()); } QModelIndex ParticipantTableFilterProxyModel::mapToSource(const QModelIndex &proxyIndex) const { if (!m_mapToSourceInternal.contains(proxyIndex.row())) // this includes !proxyIndex.isValid() return {}; QModelIndex idx = m_mapToSourceInternal.value(proxyIndex.row()); return m_sourceModel->index(idx.row(), proxyIndex.column(), idx.parent()); } QModelIndex ParticipantTableFilterProxyModel::index(int row, int column, const QModelIndex &parent) const { return parent.isValid() ? QModelIndex() : createIndex(row, column); } QModelIndex ParticipantTableFilterProxyModel::parent(const QModelIndex &child) const { Q_UNUSED(child); return {}; } QModelIndex ParticipantTableFilterProxyModel::sibling(int row, int column, const QModelIndex &idx) const { return idx.row() == row ? createIndex(row, column) : QModelIndex(); } bool ParticipantTableFilterProxyModel::hasChildren(const QModelIndex &parent) const { return !parent.isValid(); } //----------------------------------------------------------------------------------------------------------------------------- // PROTECTED MEMBER FUNCTIONS //----------------------------------------------------------------------------------------------------------------------------- bool ParticipantTableFilterProxyModel::lessThan(const QModelIndex &sourceLeft, const QModelIndex &sourceRight) const { if (sourceLeft.column() != sourceRight.column()) return false; const ParticipantSelectionModel::CheckedTreeItem *left = m_sourceModel->privateData(sourceLeft); const ParticipantSelectionModel::CheckedTreeItem *right = m_sourceModel->privateData(sourceRight); switch (sourceLeft.column()) { case ParticipantSelectionModel::ColDesignator: return left->participant.hierarchyNumber() < right->participant.hierarchyNumber(); case ParticipantSelectionModel::ColIdentifier: return compareStrings(left->participant.tacticalIdentifier(), right->participant.tacticalIdentifier(), left, right); case ParticipantSelectionModel::ColTypeClass: case ParticipantSelectionModel::ColType: return LtsWidgets::LtsColumnFilterProxyModel::lessThan(sourceLeft, sourceRight); case ParticipantSelectionModel::ColEquipmentNumber: if (!left->participant.equipmentNumber().is_set() && !right->participant.equipmentNumber().is_set()) return left->participant.hierarchyNumber() < right->participant.hierarchyNumber(); if (!left->participant.equipmentNumber().is_set()) return true; if (!right->participant.equipmentNumber().is_set()) return false; return left->participant.equipmentNumber().get() < right->participant.equipmentNumber().get(); case ParticipantSelectionModel::ColParticipantId: return left->participant.id() < right->participant.id(); case ParticipantSelectionModel::ColInfo: return compareStrings(left->participant.info(), right->participant.info(), left, right); case ParticipantSelectionModel::ColSign: return compareStrings(left->participant.identification(), right->participant.identification(), left, right); default: return false; } // switch } bool ParticipantTableFilterProxyModel::filterAcceptsColumn(int source_column, const QModelIndex &source_parent) const { Q_UNUSED(source_column); Q_UNUSED(source_parent); return true; } bool ParticipantTableFilterProxyModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const { Q_UNUSED(source_row); Q_UNUSED(source_parent); return true; } bool ParticipantTableFilterProxyModel::filterAcceptsRowInternal(int sourceRow, const QModelIndex &sourceParent) const { const QModelIndex index = m_sourceModel->index(sourceRow, 0, sourceParent); if (m_sourceModel->hasChildren(index)) { const ParticipantSelectionModel::CheckedTreeItem *parent = static_cast(index.internalPointer()); for (int i = 0; i < parent->childCount(); ++i) if (filterAcceptsRowInternal(i, index)) return true; } // if // column data filters const QHash cdf = columnDataFilters(); for (auto it = cdf.begin(); it != cdf.end(); ++it) { const QModelIndex idx = m_sourceModel->index(sourceRow, it.key(), sourceParent); const QVariant var = m_sourceModel->data(idx, LtsWidgets::LtsColumnFilterProxyModel::ComboboxFilterRole).toString(); if (var != it.value()) return false; } // for // column string filters const QHash csf = columnStringFilters(); for (auto it = csf.begin(); it != csf.end(); ++it) { const QRegularExpression ®Expr = it.value(); if (!regExpr.isValid()) continue; const QModelIndex idx = m_sourceModel->index(sourceRow, it.key(), sourceParent); const QString str = m_sourceModel->data(idx, Qt::DisplayRole).toString(); if (!str.contains(regExpr)) return false; } // for return true; } //----------------------------------------------------------------------------------------------------------------------------- // PRIVATE MEMBER FUNCTIONS //----------------------------------------------------------------------------------------------------------------------------- int ParticipantTableFilterProxyModel::mapTreeToTable(const QModelIndex &parent, int row) { const ParticipantSelectionModel::CheckedTreeItem *item = m_sourceModel->privateData(parent); // static_cast(parent.internalPointer()) for (int i = 0; i < item->childCount(); ++i) { QModelIndex child = m_sourceModel->index(i, 0, parent); m_mapFromSource.insert(child, row); m_mapToSource.insert(row, child); ++row; if (m_sourceModel->hasChildren(child)) row = mapTreeToTable(child, row); } // for return row; } //----------------------------------------------------------------------------------------------------------------------------- // SLOTS //----------------------------------------------------------------------------------------------------------------------------- void ParticipantTableFilterProxyModel::onFilter() { const QHash &colDataFilters = columnDataFilters(); const QHash &colStringFilters = columnStringFilters(); m_isFilterActive = !colDataFilters.isEmpty() || !colStringFilters.isEmpty(); if (m_isFilterActive) { m_mapToSourceFiltered.clear(); m_mapFromSourceFiltered.clear(); for (auto it = m_mapToSource.begin(); it != m_mapToSource.end(); ++it) // filter all source model indexes { int row = it.key(); QModelIndex index = it.value(); if (filterAcceptsRowInternal(index.row(), index.parent())) { m_mapToSourceFiltered.insert(row, index); m_mapFromSourceFiltered.insert(index, row); } // if } // for if (m_mapToSourceFiltered.size() != m_mapToSource.size()) { QList rows = m_mapToSourceFiltered.keys(); std::sort(rows.begin(), rows.end()); int count = 0; for (const auto &row : qAsConst(rows)) { if (row == count) { ++count; continue; } // if QModelIndex index = m_mapToSourceFiltered.value(row); m_mapToSourceFiltered.remove(row); m_mapFromSourceFiltered.remove(index); m_mapToSourceFiltered.insert(count, index); m_mapFromSourceFiltered.insert(index, count); ++count; } // for } // if m_mapToSourceInternal = m_mapToSourceFiltered; m_mapFromSourceInternal = m_mapFromSourceFiltered; } // if else { m_mapToSourceInternal = m_mapToSource; m_mapFromSourceInternal = m_mapFromSource; m_mapToSourceFiltered = m_mapToSource; m_mapFromSourceFiltered = m_mapFromSource; } // else if ((m_sortData.isActive) && !m_mapFromSourceInternal.isEmpty()) onSort(m_sortData.column, m_sortData.order); emit layoutChanged(); } void ParticipantTableFilterProxyModel::onSort(int column, Qt::SortOrder order) { if ((column < 0) || (column >= m_sourceModel->columnCount())) return; m_sortData.isActive = true; m_sortData.column = column; m_sortData.order = order; m_sortMap.clear(); m_sortMap.reserve(m_mapToSourceFiltered.size()); // m_sortMap is a QVector // undo previous sorting m_mapFromSourceInternal = m_mapFromSourceFiltered; m_mapToSourceInternal = m_mapToSourceFiltered; // insertion sort for (int oldRow = 0; oldRow < rowCount(); ++oldRow) { QModelIndex currentIndex = mapToSource(index(oldRow, column)); int newRow = 0; for (; newRow < oldRow; ++newRow) { QModelIndex compareIndex = mapToSource(index(m_sortMap.value(newRow), column)); if (lessThan(currentIndex, compareIndex)) break; } // for m_sortMap.insert(newRow, oldRow); } // for if (m_sortData.descending()) std::reverse(m_sortMap.begin(), m_sortMap.end()); m_mapToSourceInternal.clear(); m_mapFromSourceInternal.clear(); for (int newRow = 0; newRow < m_sortMap.size(); ++newRow) { QModelIndex index = m_mapToSourceFiltered.value(m_sortMap[newRow]); m_mapFromSourceInternal.insert(index, newRow); m_mapToSourceInternal.insert(newRow, index); } // for emit layoutChanged(); } } //namespace ParticipantManagement