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

469 lines
15 KiB
C++
Executable File

/**
* @RDE_HEADER@
*
* MainWindow for DDS View
*
* $Id$
* $HeadURL$
* $Author$
*/
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "nglsettings.h"
#include "zwmsui_version.h"
#include "telegramview.h"
#include "detailviewdlg.h"
#include "topic_tree_model.h"
#include "topicselector.h"
#include "sampleview.h"
#include <DDS/Generic/topic_type_plugin.h>
#include <TopicDiscover/topic_discoverer.h>
#include <LtsPluginManager/lts_plugin_loader.h>
#include <QMenuBar>
MainWindow::MainWindow(int domainId, int exerciseId, DDS::QosProfile::ConnectionType ct, const QUrl &tcpDiscoveryUrl, QWidget *parent)
: NGLMainWindow(parent)
, m_dsr(nullptr)
, m_ddsCredentials()
, m_topicDiscoverer(new TopicDiscoverer(domainId, this))
, m_dataSortModel(new TopicSortModel(this))
, m_functionSortModel(new TopicSortModel(this))
, m_ui(new Ui::MainWindow)
{
m_ui->setupUi(this);
m_ui->sbExercise->setValue(exerciseId);
setWindowTitle(tr("DDS View - Domain %1").arg(domainId));
m_ui->splitter->setSizes(QList<int>() << 550 << 750);
m_menu[QLatin1String("Edit")] = new QMenu(tr("Edit"));
m_menuBar->insertMenu(m_menu[QLatin1String("Help")]->menuAction(), m_menu[QLatin1String("Edit")]);
m_menu[QLatin1String("View")] = new QMenu(tr("View"));
m_menuBar->insertMenu(m_menu[QLatin1String("Help")]->menuAction(), m_menu[QLatin1String("View")]);
QAction *action;
action = m_ui->dwDetailViewTree->toggleViewAction();
action->setText(tr("Toggle Details (tree)"));
action->setShortcut(QKeySequence(tr("Ctrl+t")));
m_menu[QLatin1String("View")]->addAction(action);
action = new QAction(tr("Load Sample List"), this);
action->setShortcut(QKeySequence(tr("Ctrl+o")));
connect(action, &QAction::triggered, m_ui->sampleView, &SampleView::onLoadSampleList);
m_menu[QLatin1String("File")]->insertAction(m_fileActionBefore, action);
action = new QAction(tr("Save Sample List"), this);
action->setShortcut(QKeySequence(tr("Ctrl+s")));
connect(action, &QAction::triggered, m_ui->sampleView, &SampleView::onSaveSampleList);
m_menu[QLatin1String("File")]->insertAction(m_fileActionBefore, action);
action = new QAction(tr("Configure Details"), this);
connect(action, &QAction::triggered, this, &MainWindow::onConfigureDetails);
m_menu[QLatin1String("Edit")]->addAction(action);
setVersionView();
m_treeSrcModelData = new TreeModel(m_ui->trvDataTopics);
m_dataSortModel->setSourceModel(m_treeSrcModelData);
m_ui->trvDataTopics->setModel(m_dataSortModel);
m_dataSortModel->sort(0, Qt::AscendingOrder);
m_treeSrcModelFunction = new TreeModel(m_ui->trvFunctionTopics);
m_functionSortModel->setSourceModel(m_treeSrcModelFunction);
m_ui->trvFunctionTopics->setModel(m_functionSortModel);
m_functionSortModel->sort(0, Qt::AscendingOrder);
m_topicDataModel = new TopicDataModel(m_ui->trvTopicData);
m_ui->trvTopicData->setModel(m_topicDataModel);
m_ui->trvTopicData->setColumnHidden(2, true);
m_ui->trvTopicData->setColumnHidden(3, true);
m_ui->trvTopicData->setColumnHidden(4, true);
connect(m_ui->trvDataTopics->selectionModel(), &QItemSelectionModel::selectionChanged,
this, &MainWindow::onReaderChanged);
connect(m_ui->trvFunctionTopics->selectionModel(), &QItemSelectionModel::selectionChanged,
this, &MainWindow::onSelectionFunctionChanged);
connect(m_ui->sbExercise, QOverload<int>::of(&QSpinBox::valueChanged), this, &MainWindow::onReaderChanged);
connect(m_ui->pbConnect, &QPushButton::clicked, this, &MainWindow::onConnect);
connect(m_ui->pbDisconnect, &QPushButton::clicked, this, &MainWindow::onDisconnect);
connect(m_ui->pbPlay, &QPushButton::clicked, m_ui->sampleView, &SampleView::onPlayChecked);
connect(m_ui->cbRepeat, &QCheckBox::clicked, m_ui->sampleView, &SampleView::onSetRepeat);
connect(m_ui->sampleView, &SampleView::playerActive, m_ui->pbPlay, &QToolButton::setChecked);
m_ui->pbDisconnect->setEnabled(false);
m_ddsCredentials.domain = -1;
connect(m_topicDiscoverer, &TopicDiscoverer::topicDescription,
this, &MainWindow::onTopicDescription);
m_topicDiscoverer->init(ct, tcpDiscoveryUrl);
connect(m_ui->telegramView, &TelegramView::dataClicked,
m_topicDataModel, &TopicDataModel::setTopicData);
connect(m_ui->telegramView, &TelegramView::clearDetailView,
m_topicDataModel, &TopicDataModel::reset);
connect(m_ui->telegramView, &TelegramView::send,
this, &MainWindow::onSend);
connect(m_ui->sampleView, &SampleView::send,
this, &MainWindow::onSend);
loadPlugins();
}
MainWindow::~MainWindow()
{
m_versionView->disconnect(this);
delete m_ui;
delete m_dsr;
}
IDL::PartitionType MainWindow::partitionType() const
{
if (m_ui->rbExercise->isChecked())
return IDL::PartitionType::Exercise;
if (m_ui->rbPlayback->isChecked())
return IDL::PartitionType::Replay;
// Maybe we add all the other options later...
// default:
return IDL::PartitionType::Exercise;
}
MainWindow::DdsCredentials MainWindow::onReaderChanged()
{
const QModelIndexList milDt = m_ui->trvDataTopics->selectionModel()->selectedRows();
const QModelIndexList milFt = m_ui->trvFunctionTopics->selectionModel()->selectedRows();
QVector<DDSGeneric::TopicDescription> vTopicDescription;
vTopicDescription.reserve(vTopicDescription.size() + milFt.size());
for (const QModelIndex &idx : milDt)
{
const DDSGeneric::TopicDescription topicDesc = m_dataSortModel->getData(idx);
if (topicDesc.typeName.empty())
continue;
DEBUG_LOG(m_logger) << "selected data topic: " << topicDesc.name;
vTopicDescription << topicDesc;
}
for (const QModelIndex &idx : milFt)
{
const DDSGeneric::TopicDescription topicDesc = m_functionSortModel->getData(idx);
if (topicDesc.typeName.empty())
continue;
DEBUG_LOG(m_logger) << "selected function topic: " << topicDesc.name;
vTopicDescription << topicDesc;
}
DdsCredentials current(m_ui->sbExercise->value(), m_topicDiscoverer->getDomainParticipant().domain_id(), partitionType(), vTopicDescription);
updateButtons(current);
return current;
}
class ScopedBoolean
{
public:
explicit ScopedBoolean(bool &b)
: m_b(b)
{
m_b = true;
}
~ScopedBoolean()
{
m_b = false;
}
private:
bool &m_b;
};
void MainWindow::onSelectionFunctionChanged(const QItemSelection &selected, const QItemSelection &deselected)
{
static bool bInReaderChanged = false;
if (bInReaderChanged)
return;
ScopedBoolean b(bInReaderChanged);
static QItemSelection itemSelection;
QModelIndexList mil;
if (!selected.isEmpty())
mil = selected.indexes();
else if (!deselected.isEmpty())
mil = deselected.indexes();
else
return; //should never happen
if (mil.isEmpty())
return;
const QModelIndex index = mil.first();
if (index.column() != 0)
return;
const DDSGeneric::TopicDescription topicDesc = m_functionSortModel->getData(index);
if (topicDesc.typeName.empty()) // --> knot de(selected)
{
int childCount = m_functionSortModel->rowCount(index);
if (childCount == 0)
return;
const QModelIndex startindex = m_functionSortModel->index(0, index.column(), index);
const QModelIndex endindex = m_functionSortModel->index(childCount - 1, index.column(), index);
itemSelection.merge(QItemSelection(startindex, endindex), QItemSelectionModel::Select);
}
else
{
// one child (de)selected
if (!selected.isEmpty())
{
bool allSelected = true;
int childs = m_functionSortModel->rowCount(index.parent());
for (int count = 0; count < childs; count ++)
{
QModelIndex sibling = index.sibling(count, index.column());
if (sibling == index)
continue; // ignore same ModelIndex
if (!m_ui->trvFunctionTopics->selectionModel()->isSelected(sibling))
{
allSelected = false;
break;
}
}
if (allSelected)
itemSelection.merge(QItemSelection(index.parent(), index.parent()), QItemSelectionModel::Select);
itemSelection.append(selected);
}
else if (!deselected.isEmpty())
{
itemSelection.merge(QItemSelection(index.parent(), index.parent()), QItemSelectionModel::Select);
itemSelection.append(deselected);
}
else
return; //should never happen
}
if (!selected.isEmpty() && !itemSelection.isEmpty())
{
m_ui->trvFunctionTopics->selectionModel()->select(itemSelection, QItemSelectionModel::Select);
m_ui->trvFunctionTopics->selectionModel()->setCurrentIndex(index, QItemSelectionModel::Select);
}
else if (!deselected.isEmpty())
{
m_ui->trvFunctionTopics->selectionModel()->select(itemSelection, QItemSelectionModel::Deselect);
m_ui->trvFunctionTopics->selectionModel()->setCurrentIndex(index, QItemSelectionModel::Deselect);
}
else
return; //should never happen
itemSelection.clear();
ERROR_LOG(m_logger) << "cur: " << m_ui->trvFunctionTopics->selectionModel()->selectedIndexes().size();
onReaderChanged();
}
void MainWindow::updateButtons(const MainWindow::DdsCredentials &current)
{
if (current == m_ddsCredentials || m_dsr == nullptr)
{
m_ui->pbConnect->setText(tr("Connect"));
m_ui->pbConnect->setEnabled(m_dsr == nullptr);
m_ui->pbDisconnect->setEnabled(m_dsr != nullptr);
}
else
{
m_ui->pbConnect->setText(tr("Switch"));
m_ui->pbConnect->setEnabled(true);
m_ui->pbDisconnect->setEnabled(true);
}
m_ui->telegramView->setConnected(m_dsr != nullptr);
m_ui->sampleView->setConnected(m_dsr != nullptr);
}
void MainWindow::onSend(const QSharedPointer<dds::core::xtypes::DynamicData> &data, const QString &topicName)
{
if (!m_dsr || !data)
return;
dds::core::xtypes::DynamicData dataToSend(*data);
if (dataToSend.member_exists("sampleTime"))
{
qint64 curValue = dataToSend.value<qint64>("sampleTime");
if (curValue <= 1000000)
dataToSend.value("sampleTime", QDateTime::currentMSecsSinceEpoch() + curValue);
}
m_dsr->publish(dataToSend, topicName.toStdString());
}
void MainWindow::onConnect()
{
if (m_ui->trvDataTopics->selectionModel()->selectedRows().isEmpty() &&
m_ui->trvFunctionTopics->selectionModel()->selectedRows().isEmpty())
return;
// Todo: Change of exercise not regarded!
DdsCredentials cred = onReaderChanged();
if (!m_dsr)
m_dsr = new DdsView::Initialisation(m_topicDiscoverer->getDomainParticipant(),
cred.domain,
cred.partitionType,
cred.exercise,
m_ui->telegramView->telegramModel());
// Set DroTypes after Initialisation
m_dsr->initDDTCs(cred.droTypes);
m_ddsCredentials = cred;
updateButtons(cred);
}
void MainWindow::onDisconnect()
{
delete m_dsr;
m_dsr = nullptr;
onReaderChanged();
}
void MainWindow::onConfigureDetails()
{
TopicSelector *ts = new TopicSelector(TopicSelector::Usage::TypeCodeInfos, this);
connect(ts, &TopicSelector::typeCodeInfosChanged, m_ui->telegramView, &TelegramView::onTypeCodeInfosChanged);
ts->show();
}
void MainWindow::onTopicDescription(const std::string &topicName, const std::string &typeName, const dds::core::xtypes::StructType &type)
{
if (NGLSettings::instance()->hasDataTopic(topicName) ||
NGLSettings::instance()->hasFunctionTopic(topicName))
return;
TopicDescriptionEx topicDesc;
topicDesc.name = topicName;
topicDesc.typeName = typeName;
topicDesc.type = type;
topicDesc.serviceName = tr("DDS");
TreeItem *parent = nullptr;
DEBUG_LOG(m_logger) << "TypeName:" << typeName;
if (QString::fromStdString(typeName).endsWith(QLatin1String("Request")) ||
QString::fromStdString(typeName).endsWith(QLatin1String("Reply")))
{
// FunctionTopic
m_functionSortModel->addData(topicDesc, parent);
NGLSettings::instance()->insertFunctionTopic(topicDesc);
}
else
{
// DataTopic
m_dataSortModel->addData(topicDesc, parent);
NGLSettings::instance()->insertDataTopic(topicDesc);
}
}
void MainWindow::setVersionView()
{
m_versionView = new VersionView(NGLSettings::instance(), tr("DDS View"), this);
connect(m_versionView, &VersionView::destroyed,
this, &MainWindow::onVersionViewDeleted, Qt::DirectConnection);
}
bool MainWindow::loadPlugin(const LtsPluginManager::PluginInstance &pi)
{
DDSGeneric::TopicTypePlugin *typePlugin = qobject_cast<DDSGeneric::TopicTypePlugin *>(pi.plugin);
if (!typePlugin)
return false;
const QMap<std::string, DDSGeneric::TopicDescription> dataTopics = typePlugin->getDataTopics();
for (const DDSGeneric::TopicDescription &td : dataTopics)
{
DEBUG_LOG(m_logger) << "Loading data topic from service: " << typePlugin->getServiceShortName()
<< ", topic: " << td.name;
TopicDescriptionEx tdEx(td);
tdEx.serviceName = typePlugin->getServiceShortName();
NGLSettings::instance()->insertDataTopic(tdEx);
// nullptr -> no parent -> model must search correct parent on first level
m_dataSortModel->addData(tdEx, nullptr);
}
struct RequestReplyStruct
{
DDSGeneric::TopicDescription ftRequest;
DDSGeneric::TopicDescription ftReply;
bool hasRequestAndReply() const { return !ftRequest.isEmpty() && !ftReply.isEmpty(); }
};
QMap<std::string, RequestReplyStruct> tempData;
const QMap<std::string, DDSGeneric::TopicDescription> functionTopics = typePlugin->getFunctionTopics();
for (const DDSGeneric::TopicDescription &td : functionTopics)
{
DEBUG_LOG(m_logger) << "req: " << typePlugin->getServiceShortName() << " - " << td.name;
const QString topicName = QString::fromStdString(td.name);
if (topicName.endsWith(QLatin1String("Reply")))
{
const QString key = topicName.left(topicName.size() - QLatin1String("Reply").size());
tempData[key.toStdString()].ftReply = td;
}
else if (topicName.endsWith(QLatin1String("Request")))
{
const QString key = topicName.left(topicName.size() - QLatin1String("Request").size());
tempData[key.toStdString()].ftRequest = td;
}
else
{
WARNING_LOG(m_logger) << "Got unknown FTC/S topic: " << td.name;
// should not happend at all - print warning
}
}
for (auto it = tempData.cbegin(); it != tempData.cend(); ++it)
{
const std::string &key = it.key();
const RequestReplyStruct &rr = it.value();
TopicDescriptionEx tdKnot;
tdKnot.serviceName = typePlugin->getServiceShortName();
tdKnot.name = key;
// nullptr -> no parent -> model must search correct parent on first level
TreeItem *knot = m_functionSortModel->addData(tdKnot, nullptr);
TopicDescriptionEx tdReply(rr.ftReply);
tdReply.serviceName = typePlugin->getServiceShortName();
m_functionSortModel->addData(tdReply, knot);
NGLSettings::instance()->insertFunctionTopic(tdReply);
TopicDescriptionEx tdRequest(rr.ftRequest);
tdRequest.serviceName = typePlugin->getServiceShortName();
m_functionSortModel->addData(tdRequest, knot);
NGLSettings::instance()->insertFunctionTopic(tdRequest);
}
return true; // do not unload plugin because we have some static QString references in there!
}
void MainWindow::loadPlugins()
{
LtsPluginManager::LtsPluginLoader::getInstance()->loadPlugins(LtsPluginManager::PluginType::TOPIC_TYPE,
[&](const LtsPluginManager::PluginInstance & pi) { return loadPlugin(pi); });
}