469 lines
15 KiB
C++
Executable File
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 ¤t)
|
|
{
|
|
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); });
|
|
}
|