/** * @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 #include #include #include 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() << 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::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 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 &data, const QString &topicName) { if (!m_dsr || !data) return; dds::core::xtypes::DynamicData dataToSend(*data); if (dataToSend.member_exists("sampleTime")) { qint64 curValue = dataToSend.value("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(pi.plugin); if (!typePlugin) return false; const QMap 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 tempData; const QMap 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); }); }