/** * @RDE_HEADER@ * * TableModel for dynamic topics * * $Id$ * $HeadURL$ * $Author$ */ #include "telegrammodel.h" #include "nglsettings.h" #include "samplemodel.h" #include "xmlhelper.h" #include #include #include #include TelegramModel::TelegramModel(QObject *parent) : QAbstractTableModel(parent) , m_data() , m_infoList(NGLSettings::instance()->infoValues()) , m_logger(DdsView::getLogger("TelegramModel")) { } int TelegramModel::rowCount(const QModelIndex &parent) const { if (parent.isValid()) return 0; return m_data.size(); } int TelegramModel::columnCount(const QModelIndex &parent) const { if (parent.isValid()) return 0; return Column::Maximum + 1; } QVariant TelegramModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) return QVariant(); if (role != Qt::DisplayRole && role != Qt::DecorationRole) return QVariant(); if (index.column() >= columnCount(QModelIndex()) || index.row() >= rowCount(QModelIndex())) return QVariant(); const TelegramData &data = m_data[index.row()]; if (data.typeName.empty()) return QVariant(); if (role == Qt::DecorationRole) { if (index.column() != 0) return QVariant(); switch (data.modType) { case DataModification::eAdded: return QIcon(QLatin1String(":icons/16x16/list-add.png")); case DataModification::eRemoved: return QIcon(QLatin1String(":icons/16x16/list-remove.png")); case DataModification::eModified: return QIcon(QLatin1String(":icons/16x16/edit-redo.png")); } return QVariant(); } switch (index.column()) { case Column::DateTime: return data.dateTime.toString(QStringLiteral("dd.MM.yy - hh:mm:ss.zzz")); case Column::TopicName: return data.topicName; case Column::TypeName: return QString::fromStdString(data.typeName); case Column::Info: return data.info; } return QVariant(); } QVariant TelegramModel::headerData(int section, Qt::Orientation orientation, int role) const { if (orientation != Qt::Horizontal || role != Qt::DisplayRole) return QAbstractTableModel::headerData(section, orientation, role); switch (section) { case Column::DateTime: return tr("Timestamp"); case Column::TopicName: return tr("Topic Name"); case Column::TypeName: return tr("Type Name"); case Column::Info: return tr("Info"); } return QAbstractTableModel::headerData(section, orientation, role); } const QString TelegramModel::topicName(const QModelIndex &index) const { int row = index.row(); if (row < 0 || row >= m_data.size()) return QString(); return m_data[row].topicName; } const std::string TelegramModel::typeName(const QModelIndex &index) const { int row = index.row(); if (row < 0 || row >= m_data.size()) return std::string(); return m_data[row].typeName; } const QSharedPointer TelegramModel::dynamicData(const QModelIndex &index) const { int row = index.row(); if (row < 0 || row >= m_data.size()) return QSharedPointer(); return m_data[row].legatusData; } Qt::ItemFlags TelegramModel::flags(const QModelIndex &index) const { return (QAbstractTableModel::flags(index) | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled); } bool TelegramModel::deleteRow(const QModelIndex &index) { int position = index.row(); if (position < 0 || position > m_data.size()) return false; beginRemoveRows(QModelIndex(), position, position); m_data.remove(position); endRemoveRows(); return true; } void TelegramModel::onClear() { beginResetModel(); m_data.clear(); endResetModel(); } void TelegramModel::processData(const dds::core::xtypes::DynamicData &data, const QString &topicName, DataModification modType) { TelegramData telegram; telegram.dateTime = QDateTime::currentDateTime(); telegram.typeName = data.type().name(); telegram.topicName = topicName; telegram.modType = modType; telegram.legatusData = QSharedPointer(new dds::core::xtypes::DynamicData(data)); telegram.info = getInfo(*telegram.legatusData.data(), telegram.topicName); beginInsertRows(QModelIndex(), m_data.size(), m_data.size()); m_data.push_back(std::move(telegram)); endInsertRows(); } TelegramModel::TelegramData TelegramModel::telegramData(const QModelIndex &index) const { int row = index.row(); if (row < 0 || row >= m_data.size()) return TelegramData(); return m_data[row]; } QMimeData *TelegramModel::mimeData(const QModelIndexList &indexes) const { QMimeData *mimeData = new QMimeData(); QByteArray encodedData; QDataStream stream(&encodedData, QIODevice::WriteOnly); for (const QModelIndex &mi : indexes) { if (!mi.isValid()) continue; if (mi.column() != 0) continue; const TelegramData &telegram = m_data[mi.row()]; stream << quint64(telegram.legatusData.data()) << telegram.topicName; } mimeData->setData(QLatin1String("dds::core::xtypes::DynamicData/Pointer+TopicName"), encodedData); return mimeData; } Qt::DropActions TelegramModel::supportedDropActions() const { return Qt::CopyAction | Qt::MoveAction; } bool TelegramModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) { Q_UNUSED(row) Q_UNUSED(column) Q_UNUSED(parent) if (action == Qt::IgnoreAction) return true; if ((action != Qt::MoveAction) && (action != Qt::CopyAction)) return false; // Only single Selection, therefore, no while() if (data->hasFormat(QLatin1String("SampleModel/SampleData"))) { const QByteArray encodedData = data->data(QLatin1String("SampleModel/SampleData")); const SampleModel::SampleData sample = SampleModel::SampleData::decodeFromMime(encodedData); emit send(sample.legatusData, sample.topicName); return true; } return false; } QStringList TelegramModel::mimeTypes() const { QStringList sl; sl << QLatin1String("SampleModel/SampleData"); // (SampleModel::SampleData) return sl; } void TelegramModel::onTypeCodeInfosChanged() { m_infoList = NGLSettings::instance()->infoValues(); for (TelegramData &telegram : m_data) telegram.info = getInfo(*telegram.legatusData.data(), telegram.topicName); } // // TelegramSortModel // bool TelegramSortModel::lessThan(const QModelIndex &left, const QModelIndex &right) const { if (left.column() != right.column()) return QSortFilterProxyModel::lessThan(left, right); TelegramModel *srcModel = qobject_cast(sourceModel()); if (!srcModel) return QSortFilterProxyModel::lessThan(left, right); TelegramModel::TelegramData dataLeft = srcModel->telegramData(left); TelegramModel::TelegramData dataRight = srcModel->telegramData(right); switch (left.column()) { case TelegramModel::Column::DateTime: return dataLeft.dateTime < dataRight.dateTime; case TelegramModel::Column::TopicName: return dataLeft.topicName < dataRight.topicName; case TelegramModel::Column::TypeName: return dataLeft.typeName < dataRight.typeName; case TelegramModel::Column::Info: return dataLeft.info < dataRight.info; } return QSortFilterProxyModel::lessThan(left, right); } QString TelegramModel::getInfo(const dds::core::xtypes::DynamicData &data, const QString &topicName) const { const QStringList fields = m_infoList.value(topicName); const QMap result = getInfoInternal(data, fields); QStringList sl; for (const QString &str : fields) { if (result.contains(str)) sl << QString(str + QLatin1String(": ") + result[str]); } return sl.isEmpty() ? QStringLiteral(" ") : sl.join(QLatin1String(" | ")); } QString TelegramModel::getString(const dds::core::xtypes::DynamicData &data, uint32_t member_index) const { if (!data.member_exists(member_index)) return QString(); const dds::core::xtypes::DynamicType &type = data.type(); const rti::core::xtypes::DynamicDataMemberInfo info = data.member_info(member_index); const dds::core::xtypes::TypeKind typeKind = info.member_kind(); switch (typeKind.underlying()) { case dds::core::xtypes::TypeKind::CHAR_8_TYPE: return QString::number(data.value(member_index)); #if RTI_DDS_VERSION_MAJOR < 6 case dds::core::xtypes::TypeKind::CHAR_32_TYPE: #else case dds::core::xtypes::TypeKind::CHAR_16_TYPE: #endif return QString::number(data.value(member_index)); case dds::core::xtypes::TypeKind::UINT_8_TYPE: return QString::number(data.value(member_index)); case dds::core::xtypes::TypeKind::INT_16_TYPE: return QString::number(data.value(member_index)); case dds::core::xtypes::TypeKind::UINT_16_TYPE: return QString::number(data.value(member_index)); case dds::core::xtypes::TypeKind::INT_32_TYPE: return QString::number(data.value(member_index)); case dds::core::xtypes::TypeKind::UINT_32_TYPE: return QString::number(data.value(member_index)); case dds::core::xtypes::TypeKind::INT_64_TYPE: return QString::number(data.value(member_index)); case dds::core::xtypes::TypeKind::UINT_64_TYPE: return QString::number(data.value(member_index)); case dds::core::xtypes::TypeKind::FLOAT_32_TYPE: return QString::number(data.value(member_index)); case dds::core::xtypes::TypeKind::FLOAT_64_TYPE: return QString::number(data.value(member_index)); case dds::core::xtypes::TypeKind::BOOLEAN_TYPE: return data.value(member_index) ? QLatin1String("Yes") : QLatin1String("No"); case dds::core::xtypes::TypeKind::ENUMERATION_TYPE: { dds::core::xtypes::StructType structTypeParent = static_cast(type); const int ordinal = data.value(member_index); unsigned int rowInType = structTypeParent.find_member_by_name(info.member_name()); while (rowInType == dds::core::xtypes::EnumType::INVALID_INDEX) { if (!structTypeParent.has_parent()) { WARNING_LOG(m_logger) << "Could not retrieve enum value from Struct Type"; return QStringLiteral("<%1>").arg(ordinal); } structTypeParent = structTypeParent.parent(); rowInType = structTypeParent.find_member_by_name(info.member_name()); } const dds::core::xtypes::Member &enumMember = structTypeParent.member(info.member_name()); const dds::core::xtypes::EnumType &inner = static_cast(enumMember.type()); QString v = QStringLiteral("%1 (%2)").arg(QString::fromStdString(inner.name())).arg(ordinal); for (size_t i = 0; i < inner.member_count(); ++i) { const auto &member = inner.member(i); if (member.ordinal() == ordinal) { v = QStringLiteral("%1 (%2)").arg(QString::fromStdString(member.name())).arg(ordinal); break; } } return v; } case dds::core::xtypes::TypeKind::STRING_TYPE: { const dds::core::string tmp = data.value(member_index); if (tmp.c_str()) // unset -> nullptr return QString::fromUtf8(tmp.c_str()); return tr("(empty)"); } case dds::core::xtypes::TypeKind::SEQUENCE_TYPE: { const dds::core::xtypes::DynamicData inner = data.value(member_index); const dds::core::xtypes::SequenceType &seqType = static_cast(inner.type()); if (!dds::core::xtypes::is_constructed_type(seqType.content_type())) // Primitive Sequence { QStringList data; data.reserve(inner.member_count()); for (uint32_t inner_index = 1; inner_index <= inner.member_count(); ++inner_index) data << getString(inner, inner_index); return data.join(QLatin1String(", ")); } // TODO: impl Struct Sequence WARNING_LOG(m_logger) << "Struct Sequence not implemented yet (because €€€)! returning info string"; return QLatin1String("[Struct Sequence - impl. missing]"); } default: return QString(); } } QMap TelegramModel::getInfoInternal(const dds::core::xtypes::DynamicData &data, const QStringList &fields) const { QMap infotext; for (const QString &field : fields) { QString realField(field); dds::core::xtypes::DynamicData realData = data; int idx; while ((idx = realField.indexOf(QLatin1Char('.'))) != -1) { // when it's optional and not available it will throw try { realData = data.value(realField.left(idx).toStdString()); realField = realField.mid(idx + 1); } catch (...) { break; // optional, non-existing value } } const std::string str = realField.toStdString(); if (realData.member_exists(str)) infotext.insert(field, getString(realData, realData.member_index(str))); } return infotext; } void TelegramModel::TelegramData::toXml(QDomDocument &doc, QDomElement &de) const { QDomElement deSample = doc.createElement(QLatin1String("Sample")); de.appendChild(deSample); QDomElement deDateTime = doc.createElement(QLatin1String("DateTime")); deSample.appendChild(deDateTime); QDomText deDateTimeContents = doc.createTextNode(dateTime.toString(Qt::ISODate)); deDateTime.appendChild(deDateTimeContents); XmlHelper::dds2Xml(*legatusData.data(), topicName, doc, deSample); // XmlHelper::dds2Xml(*legatusData.data(), doc, deSample); } bool TelegramModel::TelegramData::fromXml(QDomElement &de) { if (de.nodeName().compare(QLatin1String("Sample"), Qt::CaseInsensitive) != 0) return false; bool succ = false; QDomNodeList dnl = de.childNodes(); for (int i = 0; i < dnl.size(); ++i) { QDomNode dn = dnl.at(i); if (!dn.isElement()) continue; QDomElement innerDe = dn.toElement(); if (innerDe.nodeName().compare(QLatin1String("DateTime"), Qt::CaseInsensitive) != 0) dateTime = QDateTime::fromString(innerDe.text(), Qt::ISODate); else if (innerDe.nodeName().compare(QLatin1String("DdsData"), Qt::CaseInsensitive) != 0) { const QString topicName = XmlHelper::topicNameFromXml(de); const TopicDescriptionEx td = NGLSettings::instance()->topicFromTopicName(topicName.toStdString()); const dds::core::xtypes::DynamicData data = XmlHelper::xml2Dds(innerDe, td); if (data.type_kind() == dds::core::xtypes::TypeKind::STRUCTURE_TYPE) { const dds::core::xtypes::StructType &type = static_cast(data.type()); legatusData = QSharedPointer(new dds::core::xtypes::DynamicData(data)); typeName = type.name(); succ = true; } } } return succ; }