Files
qt5-legatus-legacy/Lts/Tools/DdsView/telegrammodel.cpp
T
2026-08-13 18:33:10 +02:00

465 lines
15 KiB
C++
Executable File

/**
* @RDE_HEADER@
*
* TableModel for dynamic topics
*
* $Id$
* $HeadURL$
* $Author$
*/
#include "telegrammodel.h"
#include "nglsettings.h"
#include "samplemodel.h"
#include "xmlhelper.h"
#include <QDataStream>
#include <QMimeData>
#include <QIODevice>
#include <QIcon>
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<dds::core::xtypes::DynamicData> TelegramModel::dynamicData(const QModelIndex &index) const
{
int row = index.row();
if (row < 0 || row >= m_data.size())
return QSharedPointer<dds::core::xtypes::DynamicData>();
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<dds::core::xtypes::DynamicData>(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<TelegramModel *>(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<QString, QString> 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<char>(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<DDS_Wchar>(member_index));
case dds::core::xtypes::TypeKind::UINT_8_TYPE:
return QString::number(data.value<uint8_t>(member_index));
case dds::core::xtypes::TypeKind::INT_16_TYPE:
return QString::number(data.value<int16_t>(member_index));
case dds::core::xtypes::TypeKind::UINT_16_TYPE:
return QString::number(data.value<uint16_t>(member_index));
case dds::core::xtypes::TypeKind::INT_32_TYPE:
return QString::number(data.value<int32_t>(member_index));
case dds::core::xtypes::TypeKind::UINT_32_TYPE:
return QString::number(data.value<uint32_t>(member_index));
case dds::core::xtypes::TypeKind::INT_64_TYPE:
return QString::number(data.value<int64_t>(member_index));
case dds::core::xtypes::TypeKind::UINT_64_TYPE:
return QString::number(data.value<uint64_t>(member_index));
case dds::core::xtypes::TypeKind::FLOAT_32_TYPE:
return QString::number(data.value<float>(member_index));
case dds::core::xtypes::TypeKind::FLOAT_64_TYPE:
return QString::number(data.value<double>(member_index));
case dds::core::xtypes::TypeKind::BOOLEAN_TYPE:
return data.value<bool>(member_index) ? QLatin1String("Yes") : QLatin1String("No");
case dds::core::xtypes::TypeKind::ENUMERATION_TYPE:
{
dds::core::xtypes::StructType structTypeParent = static_cast<const dds::core::xtypes::StructType &>(type);
const int ordinal = data.value<int32_t>(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<const dds::core::xtypes::EnumType &>(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<dds::core::string>(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<dds::core::xtypes::DynamicData>(member_index);
const dds::core::xtypes::SequenceType &seqType = static_cast<const dds::core::xtypes::SequenceType &>(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<QString, QString> TelegramModel::getInfoInternal(const dds::core::xtypes::DynamicData &data, const QStringList &fields) const
{
QMap<QString, QString> 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<dds::core::xtypes::DynamicData>(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<const dds::core::xtypes::StructType &>(data.type());
legatusData = QSharedPointer<dds::core::xtypes::DynamicData>(new dds::core::xtypes::DynamicData(data));
typeName = type.name();
succ = true;
}
}
}
return succ;
}