File indexing completed on 2024-05-12 15:28:00

0001 /***************************************************************************
0002 File                 : LiveDataDock.cpp
0003 Project              : LabPlot
0004 Description          : Dock widget for live data properties
0005 --------------------------------------------------------------------
0006 Copyright            : (C) 2017 by Fabian Kristof (fkristofszabolcs@gmail.com)
0007 Copyright            : (C) 2018-2019 Kovacs Ferencz (kferike98@gmail.com)
0008 Copyright            : (C) 2018 by Stefan Gerlach (stefan.gerlach@uni.kn)
0009 Copyright            : (C) 2017-2020 Alexander Semke (alexander.semke@web.de)
0010 ***************************************************************************/
0011 
0012 /***************************************************************************
0013 *                                                                         *
0014 *  This program is free software; you can redistribute it and/or modify   *
0015 *  it under the terms of the GNU General Public License as published by   *
0016 *  the Free Software Foundation; either version 2 of the License, or      *
0017 *  (at your option) any later version.                                    *
0018 *                                                                         *
0019 *  This program is distributed in the hope that it will be useful,        *
0020 *  but WITHOUT ANY WARRANTY; without even the implied warranty of         *
0021 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the          *
0022 *  GNU General Public License for more details.                           *
0023 *                                                                         *
0024 *   You should have received a copy of the GNU General Public License     *
0025 *   along with this program; if not, write to the Free Software           *
0026 *   Foundation, Inc., 51 Franklin Street, Fifth Floor,                    *
0027 *   Boston, MA  02110-1301  USA                                           *
0028 *                                                                         *
0029 ***************************************************************************/
0030 #include "LiveDataDock.h"
0031 #include "kdefrontend/GuiTools.h"
0032 
0033 #include <QCompleter>
0034 #include <QFile>
0035 #include <QStandardItemModel>
0036 #include <QTimer>
0037 #include <QTreeWidgetItem>
0038 
0039 #include <KLocalizedString>
0040 
0041 #ifdef HAVE_MQTT
0042 #include "kdefrontend/widgets/MQTTWillSettingsWidget.h"
0043 #include "kdefrontend/datasources/MQTTSubscriptionWidget.h"
0044 #include <QMessageBox>
0045 #include <QWidgetAction>
0046 #include <QMenu>
0047 #endif
0048 
0049 LiveDataDock::LiveDataDock(QWidget* parent) : BaseDock(parent)
0050 #ifdef HAVE_MQTT
0051     ,
0052     m_subscriptionWidget(new MQTTSubscriptionWidget(this))
0053 #endif
0054 {
0055     ui.setupUi(this);
0056     m_leName = ui.leName;
0057     //leComment = // not available
0058 
0059     ui.bUpdateNow->setIcon(QIcon::fromTheme(QLatin1String("view-refresh")));
0060 
0061     connect(ui.leName, &QLineEdit::textChanged, this, &LiveDataDock::nameChanged);
0062     connect(ui.bPausePlayReading, &QPushButton::clicked, this, &LiveDataDock::pauseContinueReading);
0063     connect(ui.bUpdateNow, &QPushButton::clicked, this, &LiveDataDock::updateNow);
0064     connect(ui.sbUpdateInterval, static_cast<void (QSpinBox::*) (int)>(&QSpinBox::valueChanged), this, &LiveDataDock::updateIntervalChanged);
0065 
0066     connect(ui.sbKeepNValues, static_cast<void (QSpinBox::*) (int)>(&QSpinBox::valueChanged), this, &LiveDataDock::keepNValuesChanged);
0067     connect(ui.sbSampleSize, static_cast<void (QSpinBox::*) (int)>(&QSpinBox::valueChanged), this, &LiveDataDock::sampleSizeChanged);
0068     connect(ui.cbUpdateType, static_cast<void (QComboBox::*) (int)>(&QComboBox::currentIndexChanged), this, &LiveDataDock::updateTypeChanged);
0069     connect(ui.cbReadingType, static_cast<void (QComboBox::*) (int)>(&QComboBox::currentIndexChanged), this, &LiveDataDock::readingTypeChanged);
0070 
0071 #ifdef HAVE_MQTT
0072     connect(ui.bWillUpdateNow, &QPushButton::clicked, this, &LiveDataDock::willUpdateNow);
0073     connect(ui.bLWT, &QPushButton::clicked, this, &LiveDataDock::showWillSettings);
0074     connect(m_subscriptionWidget, &MQTTSubscriptionWidget::enableWill, this, &LiveDataDock::enableWill);
0075 
0076     ui.swSubscriptions->addWidget(m_subscriptionWidget);
0077     ui.swSubscriptions->setCurrentWidget(m_subscriptionWidget);
0078 
0079     ui.bLWT->setToolTip(i18n("Manage MQTT connection's will settings"));
0080     ui.bLWT->setIcon(ui.bLWT->style()->standardIcon(QStyle::SP_FileDialogDetailedView));
0081 
0082     QString info = i18n("Specify the 'Last Will and Testament' message (LWT). At least one topic has to be subscribed.");
0083     ui.lLWT->setToolTip(info);
0084     ui.bLWT->setToolTip(info);
0085     ui.bLWT->setEnabled(false);
0086     ui.bLWT->setIcon(ui.bLWT->style()->standardIcon(QStyle::SP_FileDialogDetailedView));
0087 #endif
0088 }
0089 
0090 #ifdef HAVE_MQTT
0091 LiveDataDock::~LiveDataDock() {
0092     for (auto & host : m_hosts)
0093         delete host.client;
0094 
0095     delete m_subscriptionWidget;
0096 }
0097 #else
0098 LiveDataDock::~LiveDataDock() = default;
0099 #endif
0100 
0101 #ifdef HAVE_MQTT
0102 /*!
0103  * \brief Sets the MQTTClient of this dock widget
0104  * \param clients
0105  */
0106 void LiveDataDock::setMQTTClient(MQTTClient* const client) {
0107     m_liveDataSource = nullptr; // prevent updates due to changes to input widgets
0108     auto oldclient = m_mqttClient;
0109     m_mqttClient = nullptr; // prevent updates due to changes to input widgets
0110     const QPair<QString, quint16> id(client->clientHostName(), client->clientPort());
0111 
0112     ui.leName->setText(client->name());
0113     ui.leSourceInfo->setText(QStringLiteral("%1:%2").arg(id.first).arg(id.second));
0114     ui.sbUpdateInterval->setValue(client->updateInterval());
0115     ui.cbUpdateType->setCurrentIndex(static_cast<int>(client->updateType()));
0116     ui.cbReadingType->setCurrentIndex(static_cast<int>(client->readingType()));
0117 
0118     if (client->updateType() == MQTTClient::UpdateType::NewData) {
0119         ui.lUpdateInterval->hide();
0120         ui.sbUpdateInterval->hide();
0121     }
0122 
0123     m_paused = client->isPaused();
0124     if (m_paused) {
0125         ui.bPausePlayReading->setText(i18n("Continue reading"));
0126         ui.bPausePlayReading->setIcon(QIcon::fromTheme(QLatin1String("media-record")));
0127     } else {
0128         ui.bPausePlayReading->setText(i18n("Pause reading"));
0129         ui.bPausePlayReading->setIcon(QIcon::fromTheme(QLatin1String("media-playback-pause")));
0130     }
0131 
0132     ui.sbKeepNValues->setValue(client->keepNValues());
0133     ui.sbKeepNValues->setEnabled(true);
0134 
0135     if (client->readingType() == MQTTClient::ReadingType::TillEnd) {
0136         ui.lSampleSize->hide();
0137         ui.sbSampleSize->hide();
0138     } else
0139         ui.sbSampleSize->setValue(client->sampleSize());
0140 
0141     // disable "whole file" option
0142     const auto* model = qobject_cast<const QStandardItemModel*>(ui.cbReadingType->model());
0143     QStandardItem* item = model->item(static_cast<int>(LiveDataSource::ReadingType::WholeFile));
0144     item->setFlags(item->flags() & ~(Qt::ItemIsSelectable | Qt::ItemIsEnabled));
0145     if (ui.cbReadingType->currentIndex() == static_cast<int>(LiveDataSource::ReadingType::WholeFile))
0146         ui.cbReadingType->setCurrentIndex(static_cast<int>(LiveDataSource::ReadingType::TillEnd));
0147 
0148     m_mqttClient = client; // updates may be applied from now on
0149 
0150     //show MQTT connected options
0151     ui.lTopics->show();
0152     ui.swSubscriptions->setVisible(true);
0153     m_subscriptionWidget->setVisible(true);
0154     m_subscriptionWidget->makeVisible(true);
0155     ui.lLWT->show();
0156     ui.bLWT->show();
0157 
0158     m_previousHost = m_currentHost;
0159 
0160     //if there isn't a client with this hostname we instantiate a new one
0161     auto it = m_hosts.find(id);
0162     if (it == m_hosts.end()) {
0163         m_currentHost = &m_hosts[id];
0164         m_currentHost->count = 1;
0165         m_currentHost->client = new QMqttClient;
0166 
0167         connect(client, &MQTTClient::clientAboutToBeDeleted, this, &LiveDataDock::removeClient);
0168 
0169         connect(m_currentHost->client, &QMqttClient::connected, this, &LiveDataDock::onMQTTConnect);
0170         connect(m_currentHost->client, &QMqttClient::messageReceived, this, &LiveDataDock::mqttMessageReceived);
0171 
0172         connect(m_subscriptionWidget, &MQTTSubscriptionWidget::reparentTopic, client, &MQTTClient::reparentTopic);
0173         connect(m_subscriptionWidget, &MQTTSubscriptionWidget::addBeforeRemoveSubscription, client, &MQTTClient::addBeforeRemoveSubscription);
0174         connect(m_subscriptionWidget, &MQTTSubscriptionWidget::removeMQTTSubscription, client, &MQTTClient::removeMQTTSubscription);
0175         connect(m_subscriptionWidget, &MQTTSubscriptionWidget::makeSubscription, client, &MQTTClient::addMQTTSubscription);
0176 
0177         m_currentHost->client->setHostname(id.first);
0178         m_currentHost->client->setPort(id.second);
0179 
0180         if (client->MQTTUseAuthentication()) {
0181             m_currentHost->client->setUsername(client->clientUserName());
0182             m_currentHost->client->setPassword(client->clientPassword());
0183         }
0184 
0185         if (client->MQTTUseID())
0186             m_currentHost->client->setClientId(client->clientID());
0187 
0188         m_currentHost->client->connectToHost();
0189     } else {
0190         m_currentHost = &it.value();
0191         ++m_currentHost->count;
0192     }
0193 
0194     if (m_previousMQTTClient == nullptr) {
0195         m_updateSubscriptionConn = connect(client, &MQTTClient::MQTTSubscribed, [this]() {
0196             emit updateSubscriptionTree(m_mqttClient->MQTTSubscriptions());
0197         });
0198 
0199         //Fill the subscription tree(useful if the MQTTClient was loaded)
0200         QVector<QString> topics = client->topicNames();
0201         for (const auto& topic : topics)
0202             addTopicToTree(topic);
0203         emit updateSubscriptionTree(m_mqttClient->MQTTSubscriptions());
0204     }
0205 
0206     //if the previous MQTTClient's host name was different from the current one we have to disconnect some slots
0207     //and clear the tree widgets
0208     else if (m_previousMQTTClient->clientHostName() != client->clientHostName()) {
0209         disconnect(m_updateSubscriptionConn);
0210         disconnect(m_previousHost->client, &QMqttClient::messageReceived, this, &LiveDataDock::mqttMessageReceived);
0211         connect(m_previousHost->client, &QMqttClient::messageReceived, this, &LiveDataDock::mqttMessageReceivedInBackground);
0212 
0213         disconnect(m_currentHost->client, &QMqttClient::messageReceived, this, &LiveDataDock::mqttMessageReceivedInBackground);
0214 
0215         disconnect(m_subscriptionWidget, &MQTTSubscriptionWidget::reparentTopic, m_previousMQTTClient, &MQTTClient::reparentTopic);
0216         disconnect(m_subscriptionWidget, &MQTTSubscriptionWidget::addBeforeRemoveSubscription, m_previousMQTTClient, &MQTTClient::addBeforeRemoveSubscription);
0217         disconnect(m_subscriptionWidget, &MQTTSubscriptionWidget::removeMQTTSubscription, m_previousMQTTClient, &MQTTClient::removeMQTTSubscription);
0218         disconnect(m_subscriptionWidget, &MQTTSubscriptionWidget::makeSubscription,  m_previousMQTTClient, &MQTTClient::addMQTTSubscription);
0219 
0220         m_previousHost->topicList = m_subscriptionWidget->getTopicList();
0221         m_subscriptionWidget->setTopicList(m_currentHost->topicList);
0222 
0223         emit MQTTClearTopics();
0224         //repopulating the tree widget with the already known topics of the client
0225         for (const auto& topic : m_currentHost->addedTopics)
0226             addTopicToTree(topic);
0227 
0228         //fill subscriptions tree widget
0229         emit updateSubscriptionTree(m_mqttClient->MQTTSubscriptions());
0230 
0231         m_updateSubscriptionConn = connect(client, &MQTTClient::MQTTSubscribed, [this]() {
0232             emit updateSubscriptionTree(m_mqttClient->MQTTSubscriptions());
0233         });
0234         connect(m_currentHost->client, &QMqttClient::messageReceived, this, &LiveDataDock::mqttMessageReceived);
0235 
0236         connect(m_subscriptionWidget, &MQTTSubscriptionWidget::reparentTopic, client, &MQTTClient::reparentTopic);
0237         connect(m_subscriptionWidget, &MQTTSubscriptionWidget::addBeforeRemoveSubscription, client, &MQTTClient::addBeforeRemoveSubscription);
0238         connect(m_subscriptionWidget, &MQTTSubscriptionWidget::removeMQTTSubscription, client, &MQTTClient::removeMQTTSubscription);
0239         connect(m_subscriptionWidget, &MQTTSubscriptionWidget::makeSubscription, client, &MQTTClient::addMQTTSubscription);
0240     }
0241 
0242     if (client->willUpdateType() == MQTTClient::WillUpdateType::OnClick && client->MQTTWillUse())
0243         ui.bWillUpdateNow->show();
0244 
0245     m_previousMQTTClient = oldclient;
0246 }
0247 #endif
0248 
0249 /*!
0250  * \brief Sets the live data source of this dock widget
0251  * \param source
0252  */
0253 void LiveDataDock::setLiveDataSource(LiveDataSource* const source) {
0254 #ifdef HAVE_MQTT
0255     m_mqttClient = nullptr;
0256 #endif
0257     m_liveDataSource = nullptr; // prevent updates due to changes to input widgets
0258 
0259     ui.leName->setText(source->name());
0260     ui.leName->setStyleSheet("");
0261     ui.leName->setToolTip("");
0262     const LiveDataSource::SourceType sourceType = source->sourceType();
0263     const LiveDataSource::ReadingType readingType = source->readingType();
0264     const LiveDataSource::UpdateType updateType = source->updateType();
0265     const AbstractFileFilter::FileType fileType = source->fileType();
0266     ui.sbUpdateInterval->setValue(source->updateInterval());
0267     ui.cbUpdateType->setCurrentIndex(static_cast<int>(updateType));
0268     ui.cbReadingType->setCurrentIndex(static_cast<int>(readingType));
0269 
0270     switch (sourceType) {
0271     case LiveDataSource::SourceType::FileOrPipe: {
0272         ui.leSourceInfo->setText(source->fileName());
0273         bool invalid = !QFile::exists(source->fileName());
0274         GuiTools::highlight(ui.leSourceInfo, invalid);
0275         break;
0276     }
0277     case LiveDataSource::SourceType::NetworkTcpSocket:
0278     case LiveDataSource::SourceType::NetworkUdpSocket:
0279         ui.leSourceInfo->setText(QStringLiteral("%1:%2").arg(source->host()).arg(source->port()));
0280         break;
0281     case LiveDataSource::SourceType::LocalSocket:
0282         ui.leSourceInfo->setText(source->localSocketName());
0283         break;
0284     case LiveDataSource::SourceType::SerialPort:
0285         ui.leSourceInfo->setText(source->serialPortName());
0286         break;
0287     case LiveDataSource::SourceType::MQTT:
0288         break;
0289     }
0290 
0291     if (updateType == LiveDataSource::UpdateType::NewData) {
0292         ui.lUpdateInterval->hide();
0293         ui.sbUpdateInterval->hide();
0294     }
0295 
0296     m_paused = source->isPaused();
0297     if (m_paused) {
0298         ui.bPausePlayReading->setText(i18n("Continue Reading"));
0299         ui.bPausePlayReading->setIcon(QIcon::fromTheme(QLatin1String("media-record")));
0300     } else {
0301         ui.bPausePlayReading->setText(i18n("Pause Reading"));
0302         ui.bPausePlayReading->setIcon(QIcon::fromTheme(QLatin1String("media-playback-pause")));
0303     }
0304 
0305     ui.sbKeepNValues->setValue(source->keepNValues());
0306 
0307     // disable "whole file" when having no file (i.e. socket or port)
0308     auto* model = qobject_cast<const QStandardItemModel*>(ui.cbReadingType->model());
0309     QStandardItem* item = model->item(static_cast<int>(LiveDataSource::ReadingType::WholeFile));
0310     if (sourceType == LiveDataSource::SourceType::FileOrPipe) {
0311         item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
0312         //for file types other than ASCII and binary we support re-reading the whole file only
0313         //select "read whole file" and deactivate the combobox
0314         if (fileType != AbstractFileFilter::FileType::Ascii && fileType != AbstractFileFilter::FileType::Binary) {
0315             ui.cbReadingType->setCurrentIndex(static_cast<int>(LiveDataSource::ReadingType::WholeFile));
0316             ui.cbReadingType->setEnabled(false);
0317         } else
0318             ui.cbReadingType->setEnabled(true);
0319     } else {
0320         if (ui.cbReadingType->currentIndex() == static_cast<int>(LiveDataSource::ReadingType::WholeFile))
0321             ui.cbReadingType->setCurrentIndex(static_cast<int>(LiveDataSource::ReadingType::TillEnd));
0322         item->setFlags(item->flags() & ~(Qt::ItemIsSelectable | Qt::ItemIsEnabled));
0323     }
0324 
0325     if (((sourceType == LiveDataSource::SourceType::FileOrPipe || sourceType == LiveDataSource::SourceType::NetworkUdpSocket) &&
0326             (readingType == LiveDataSource::ReadingType::ContinuousFixed || readingType == LiveDataSource::ReadingType::FromEnd)))
0327         ui.sbSampleSize->setValue(source->sampleSize());
0328     else {
0329         ui.lSampleSize->hide();
0330         ui.sbSampleSize->hide();
0331     }
0332 
0333     // disable "on new data"-option if not available
0334     model = qobject_cast<const QStandardItemModel*>(ui.cbUpdateType->model());
0335     item = model->item(static_cast<int>(LiveDataSource::UpdateType::NewData));
0336     if (sourceType == LiveDataSource::SourceType::NetworkTcpSocket || sourceType == LiveDataSource::SourceType::NetworkUdpSocket ||
0337             sourceType == LiveDataSource::SourceType::SerialPort)
0338         item->setFlags(item->flags() & ~(Qt::ItemIsSelectable | Qt::ItemIsEnabled));
0339     else
0340         item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
0341 
0342     ui.lTopics->hide();
0343     ui.bLWT->hide();
0344     ui.lLWT->hide();
0345     ui.bWillUpdateNow->hide();
0346     ui.swSubscriptions->hide();
0347 #ifdef HAVE_MQTT
0348     m_subscriptionWidget->hide();
0349     m_subscriptionWidget->hide();
0350 #endif
0351     m_liveDataSource = source; // updates may be applied from now on
0352 }
0353 
0354 /*!
0355  * \brief Modifies the sample size of the live data source or MQTTClient object
0356  * \param sampleSize
0357  */
0358 void LiveDataDock::sampleSizeChanged(int sampleSize) {
0359     if (m_liveDataSource)
0360         m_liveDataSource->setSampleSize(sampleSize);
0361 #ifdef HAVE_MQTT
0362     else if (m_mqttClient)
0363         m_mqttClient->setSampleSize(sampleSize);
0364 #endif
0365 }
0366 
0367 /*!
0368  * \brief Updates the live data source now
0369  */
0370 void LiveDataDock::updateNow() {
0371     if (m_liveDataSource)
0372         m_liveDataSource->updateNow();
0373 #ifdef HAVE_MQTT
0374     else if (m_mqttClient)
0375         m_mqttClient->updateNow();
0376 #endif
0377 }
0378 
0379 void LiveDataDock::nameChanged(const QString& name) {
0380     if (m_liveDataSource) {
0381         if (!m_liveDataSource->setName(name, false)) {
0382             ui.leName->setStyleSheet("background:red;");
0383             ui.leName->setToolTip(i18n("Please choose another name, because this is already in use."));
0384             return;
0385         }
0386     }
0387 #ifdef HAVE_MQTT
0388     else if (m_mqttClient) {
0389         if (!m_mqttClient->setName(name, false)) {
0390             ui.leName->setStyleSheet("background:red;");
0391             ui.leName->setToolTip(i18n("Please choose another name, because this is already in use."));
0392             return;
0393         }
0394     }
0395 #endif
0396     ui.leName->setStyleSheet("");
0397     ui.leName->setToolTip("");
0398 }
0399 
0400 /*!
0401  * \brief LiveDataDock::updateTypeChanged
0402  * \param idx
0403  */
0404 void LiveDataDock::updateTypeChanged(int idx) {
0405     if (m_liveDataSource)  {
0406         DEBUG("LiveDataDock::updateTypeChanged()");
0407         const auto updateType = static_cast<LiveDataSource::UpdateType>(idx);
0408 
0409         switch (updateType) {
0410         case LiveDataSource::UpdateType::TimeInterval: {
0411                 ui.lUpdateInterval->show();
0412                 ui.sbUpdateInterval->show();
0413                 const auto s = m_liveDataSource->sourceType();
0414                 const auto r = m_liveDataSource->readingType();
0415                 const bool showSampleSize = ((s == LiveDataSource::SourceType::FileOrPipe || s == LiveDataSource::SourceType::NetworkUdpSocket) &&
0416                                              (r == LiveDataSource::ReadingType::ContinuousFixed || r == LiveDataSource::ReadingType::FromEnd));
0417                 ui.lSampleSize->setVisible(showSampleSize);
0418                 ui.sbSampleSize->setVisible(showSampleSize);
0419 
0420                 m_liveDataSource->setUpdateType(updateType);
0421                 m_liveDataSource->setUpdateInterval(ui.sbUpdateInterval->value());
0422                 break;
0423             }
0424         case LiveDataSource::UpdateType::NewData:
0425             ui.lUpdateInterval->hide();
0426             ui.sbUpdateInterval->hide();
0427             ui.lSampleSize->hide();
0428             ui.sbSampleSize->hide();
0429 
0430             m_liveDataSource->setUpdateType(updateType);
0431         }
0432     }
0433 #ifdef HAVE_MQTT
0434     else if (m_mqttClient) {
0435         DEBUG("LiveDataDock::updateTypeChanged()");
0436         const auto type = static_cast<MQTTClient::UpdateType>(idx);
0437 
0438         if (type == MQTTClient::UpdateType::TimeInterval) {
0439             ui.lUpdateInterval->show();
0440             ui.sbUpdateInterval->show();
0441 
0442             m_mqttClient->setUpdateType(type);
0443             m_mqttClient->setUpdateInterval(ui.sbUpdateInterval->value());
0444         } else if (type == MQTTClient::UpdateType::NewData) {
0445             ui.lUpdateInterval->hide();
0446             ui.sbUpdateInterval->hide();
0447 
0448             m_mqttClient->setUpdateType(type);
0449         }
0450     }
0451 #endif
0452 }
0453 
0454 /*!
0455  * \brief Handles the change of the reading type in the dock widget
0456  * \param idx
0457  */
0458 void LiveDataDock::readingTypeChanged(int idx) {
0459     if (m_liveDataSource)  {
0460         const auto type = static_cast<LiveDataSource::ReadingType>(idx);
0461         const auto sourceType = m_liveDataSource->sourceType();
0462         const auto updateType = m_liveDataSource->updateType();
0463 
0464         if (sourceType == LiveDataSource::SourceType::NetworkTcpSocket || sourceType == LiveDataSource::SourceType::LocalSocket
0465             || sourceType == LiveDataSource::SourceType::SerialPort
0466                 || type == LiveDataSource::ReadingType::TillEnd || type == LiveDataSource::ReadingType::WholeFile
0467                 || updateType == LiveDataSource::UpdateType::NewData) {
0468             ui.lSampleSize->hide();
0469             ui.sbSampleSize->hide();
0470         } else {
0471             ui.lSampleSize->show();
0472             ui.sbSampleSize->show();
0473         }
0474 
0475         m_liveDataSource->setReadingType(type);
0476     }
0477 #ifdef HAVE_MQTT
0478     else if (m_mqttClient) {
0479         auto type = static_cast<MQTTClient::ReadingType>(idx);
0480 
0481         if (type == MQTTClient::ReadingType::TillEnd) {
0482             ui.lSampleSize->hide();
0483             ui.sbSampleSize->hide();
0484         } else {
0485             ui.lSampleSize->show();
0486             ui.sbSampleSize->show();
0487         }
0488 
0489         m_mqttClient->setReadingType(type);
0490     }
0491 #endif
0492 }
0493 
0494 /*!
0495  * \brief Modifies the update interval of the live data source
0496  * \param updateInterval
0497  */
0498 void LiveDataDock::updateIntervalChanged(int updateInterval) {
0499     if (m_liveDataSource)
0500         m_liveDataSource->setUpdateInterval(updateInterval);
0501 #ifdef HAVE_MQTT
0502     else if (m_mqttClient)
0503         m_mqttClient->setUpdateInterval(updateInterval);
0504 #endif
0505 }
0506 
0507 /*!
0508  * \brief Modifies the number of samples to keep in each of the live data source
0509  * \param keepNValues
0510  */
0511 void LiveDataDock::keepNValuesChanged(const int keepNValues) {
0512     if (m_liveDataSource)
0513         m_liveDataSource->setKeepNValues(keepNValues);
0514 #ifdef HAVE_MQTT
0515     else if (m_mqttClient)
0516         m_mqttClient->setKeepNValues(keepNValues);
0517 #endif
0518 }
0519 
0520 /*!
0521  * \brief Pauses the reading of the live data source
0522  */
0523 void LiveDataDock::pauseReading() {
0524     if (m_liveDataSource)
0525         m_liveDataSource->pauseReading();
0526 #ifdef HAVE_MQTT
0527     else if (m_mqttClient)
0528         m_mqttClient->pauseReading();
0529 #endif
0530 }
0531 
0532 /*!
0533  * \brief Continues the reading of the live data source
0534  */
0535 void LiveDataDock::continueReading() {
0536     if (m_liveDataSource)
0537         m_liveDataSource->continueReading();
0538 #ifdef HAVE_MQTT
0539     else if (m_mqttClient)
0540         m_mqttClient->continueReading();
0541 #endif
0542 }
0543 
0544 /*!
0545  * \brief Handles the pausing/continuing of reading of the live data source
0546  */
0547 void LiveDataDock::pauseContinueReading() {
0548     m_paused = !m_paused;
0549 
0550     if (m_paused) {
0551         pauseReading();
0552         ui.bPausePlayReading->setText(i18n("Continue Reading"));
0553         ui.bPausePlayReading->setIcon(QIcon::fromTheme(QLatin1String("media-record")));
0554     } else {
0555         continueReading();
0556         ui.bPausePlayReading->setText(i18n("Pause Reading"));
0557         ui.bPausePlayReading->setIcon(QIcon::fromTheme(QLatin1String("media-playback-pause")));
0558     }
0559 }
0560 
0561 #ifdef HAVE_MQTT
0562 
0563 /*!
0564  *\brief called when use will message checkbox's state is changed in the will settings widget,
0565  * Sets the mqttUseWill according to state for the m_mqttClient
0566  *
0567  * \param state the state of the checbox
0568  */
0569 void LiveDataDock::useWillMessage(bool use) {
0570     if (use) {
0571         m_mqttClient->setMQTTWillUse(true);
0572         if (m_mqttClient->willUpdateType() == MQTTClient::WillUpdateType::OnClick)
0573             ui.bWillUpdateNow->show();
0574     } else {
0575         m_mqttClient->setMQTTWillUse(false);
0576         ui.bWillUpdateNow->hide();
0577     }
0578 }
0579 
0580 /*!
0581  *\brief called when will message's QoS is changed in the will settings widget
0582  * sets the will QoS level for the m_mqttClient
0583  *
0584  * \param QoS the QoS level of the will message
0585  */
0586 void LiveDataDock::willQoSChanged(int QoS) {
0587     m_mqttClient->setWillQoS(QoS);
0588 }
0589 
0590 /*!
0591  *\brief called when will message's retain flag is changed in the will settings widget
0592  * sets the retain flag for the will message in in m_mqttClient
0593  *
0594  * \param state the state of the will retain chechbox
0595  */
0596 void LiveDataDock::willRetainChanged(bool useWillRetainMessages) {
0597     if (useWillRetainMessages)
0598         m_mqttClient->setWillRetain(true);
0599     else
0600         m_mqttClient->setWillRetain(false);
0601 }
0602 
0603 /*!
0604  *\brief called when will topic combobox's current item is changed in the will settings widget
0605  * sets the will topic for the m_mqttClient
0606  *
0607  * \param topic the current text of cbWillTopic
0608  */
0609 void LiveDataDock::willTopicChanged(const QString& topic) {
0610     if (m_mqttClient->willTopic() != topic)
0611         m_mqttClient->clearLastMessage();
0612 
0613     m_mqttClient->setWillTopic(topic);
0614 }
0615 
0616 /*!
0617  *\brief called when the selected will message type is changed in the will settings widget
0618  * sets the will message type for the m_mqttClient
0619  *
0620  * \param type the selected will message type
0621  */
0622 void LiveDataDock::willMessageTypeChanged(MQTTClient::WillMessageType willMessageType) {
0623     m_mqttClient->setWillMessageType(willMessageType);
0624 }
0625 
0626 /*!
0627  *\brief called when the will own message is changed in the will settings widget
0628  * sets the will own message for the m_mqttClient
0629  *
0630  * \param message the will message given by the user
0631  */
0632 void LiveDataDock::willOwnMessageChanged(const QString& message) {
0633     m_mqttClient->setWillOwnMessage(message);
0634 }
0635 
0636 /*!
0637  *\brief called when the selected update type for the will message is changed in the will settings widget
0638  * sets the will update type for the m_mqttClient
0639  *
0640  * \param type the selected will update type
0641  */
0642 void LiveDataDock::willUpdateTypeChanged(int updateType) {
0643     m_mqttClient->setWillUpdateType(static_cast<MQTTClient::WillUpdateType>(updateType));
0644 
0645     if (updateType == static_cast<int>(MQTTClient::WillUpdateType::TimePeriod)) {
0646         ui.bWillUpdateNow->hide();
0647         m_mqttClient->startWillTimer();
0648     } else if (updateType == static_cast<int>(MQTTClient::WillUpdateType::OnClick)) {
0649         ui.bWillUpdateNow->show();
0650 
0651         //if update type is on click we stop the will timer
0652         m_mqttClient->stopWillTimer();
0653     }
0654 }
0655 
0656 /*!
0657  *\brief called when the will update now button is pressed
0658  * updates the will message of m_mqttClient
0659  */
0660 void LiveDataDock::willUpdateNow() {
0661     m_mqttClient->updateWillMessage();
0662 }
0663 
0664 /*!
0665  *\brief called when the update interval for will message is changed in the will settings widget
0666  * sets the will update interval for the m_mqttClient, then starts the will timer for each one
0667  *
0668  * \param interval the new will update interval
0669  */
0670 void LiveDataDock::willUpdateIntervalChanged(int interval) {
0671     m_mqttClient->setWillTimeInterval(interval);
0672     m_mqttClient->startWillTimer();
0673 }
0674 
0675 /*!
0676  *\brief called when the will statistics are changed in the will settings widget
0677  * adds or removes the statistic represented by the index from m_mqttClient
0678  */
0679 void LiveDataDock::statisticsChanged(MQTTClient::WillStatisticsType willStatisticsType) {
0680     if (willStatisticsType != MQTTClient::WillStatisticsType::NoStatistics) {
0681         //if it's not already added and it's checked we add it
0682         if (!m_mqttClient->willStatistics().at(static_cast<int>(willStatisticsType)))
0683             m_mqttClient->addWillStatistics(willStatisticsType);
0684         else //otherwise remove it
0685             m_mqttClient->removeWillStatistics(willStatisticsType);
0686     }
0687 }
0688 
0689 /*!
0690  *\brief called when the client connects to the broker successfully, it subscribes to every topic (# wildcard)
0691  * in order to later list every available topic
0692  */
0693 void LiveDataDock::onMQTTConnect() {
0694     if (!m_currentHost || !m_currentHost->client || !m_currentHost->client->subscribe(QMqttTopicFilter(QLatin1String("#")), 1))
0695         QMessageBox::critical(this, i18n("Couldn't subscribe"), i18n("Couldn't subscribe to all available topics. Something went wrong"));
0696 }
0697 
0698 /*!
0699  *\brief called when the client receives a message
0700  * if the message arrived from a new topic, the topic is put in twTopics
0701  */
0702 void LiveDataDock::mqttMessageReceived(const QByteArray& message, const QMqttTopicName& topic) {
0703     Q_UNUSED(message)
0704     if (!m_currentHost->addedTopics.contains(topic.name())) {
0705         m_currentHost->addedTopics.push_back(topic.name());
0706         addTopicToTree(topic.name());
0707     }
0708 }
0709 
0710 /*!
0711  *\brief Adds topicName to twTopics
0712  *
0713  * \param topicName the name of the topic, which will be added to the tree widget
0714  */
0715 void LiveDataDock::addTopicToTree(const QString &topicName) {
0716     QStringList name;
0717     QChar sep = '/';
0718     QString rootName;
0719     if (topicName.contains(sep)) {
0720         QStringList list = topicName.split(sep, QString::SkipEmptyParts);
0721 
0722         if (!list.isEmpty()) {
0723             rootName = list.at(0);
0724             name.append(list.at(0));
0725             QTreeWidgetItem* currentItem;
0726             //check whether the first level of the topic can be found in twTopics
0727             int topItemIdx = -1;
0728             for (int i = 0; i < m_subscriptionWidget->topicCount(); ++i) {
0729                 if (m_subscriptionWidget->topLevelTopic(i)->text(0) == list.at(0)) {
0730                     topItemIdx = i;
0731                     break;
0732                 }
0733             }
0734             //if not we simply add every level of the topic to the tree
0735             if ( topItemIdx < 0) {
0736                 currentItem = new QTreeWidgetItem(name);
0737                 m_subscriptionWidget->addTopic(currentItem);
0738                 for (int i = 1; i < list.size(); ++i) {
0739                     name.clear();
0740                     name.append(list.at(i));
0741                     currentItem->addChild(new QTreeWidgetItem(name));
0742                     currentItem = currentItem->child(0);
0743                 }
0744             }
0745             //otherwise we search for the first level that isn't part of the tree,
0746             //then add every level of the topic to the tree from that certain level
0747             else {
0748                 currentItem = m_subscriptionWidget->topLevelTopic(topItemIdx);
0749                 int listIdx = 1;
0750                 for (; listIdx < list.size(); ++listIdx) {
0751                     QTreeWidgetItem* childItem = nullptr;
0752                     bool found = false;
0753                     for (int j = 0; j < currentItem->childCount(); ++j) {
0754                         childItem = currentItem->child(j);
0755                         if (childItem->text(0) == list.at(listIdx)) {
0756                             found = true;
0757                             currentItem = childItem;
0758                             break;
0759                         }
0760                     }
0761                     if (!found) {
0762                         //this is the level that isn't present in the tree
0763                         break;
0764                     }
0765                 }
0766 
0767                 //add every level to the tree starting with the first level that isn't part of the tree
0768                 for (; listIdx < list.size(); ++listIdx) {
0769                     name.clear();
0770                     name.append(list.at(listIdx));
0771                     currentItem->addChild(new QTreeWidgetItem(name));
0772                     currentItem = currentItem->child(currentItem->childCount() - 1);
0773                 }
0774             }
0775         }
0776     } else {
0777         rootName = topicName;
0778         name.append(topicName);
0779         m_subscriptionWidget->addTopic(new QTreeWidgetItem(name));
0780     }
0781 
0782     //if a subscribed topic contains the new topic, we have to update twSubscriptions
0783     for (int i = 0; i < m_subscriptionWidget->subscriptionCount(); ++i) {
0784         QStringList subscriptionName = m_subscriptionWidget->topLevelSubscription(i)->text(0).split('/', QString::SkipEmptyParts);
0785         if (rootName == subscriptionName[0]) {
0786             emit updateSubscriptionTree(m_mqttClient->MQTTSubscriptions());
0787             break;
0788         }
0789     }
0790 
0791     //signals that a newTopic was added, in order to fill the completer of leTopics
0792     //we have to pass the whole topic name, not just the root name, for testing purposes
0793     emit newTopic(topicName);
0794 }
0795 
0796 /*!
0797  *\brief called when a client receives a message, if the clients hostname isn't identic with the host name of MQTTClient
0798  * if the message arrived from a new topic, the topic is added to the host data
0799  */
0800 void LiveDataDock::mqttMessageReceivedInBackground(const QByteArray& message, const QMqttTopicName& topic) {
0801     Q_UNUSED(message)
0802     if (!m_currentHost->addedTopics.contains(topic.name()))
0803         m_currentHost->addedTopics.push_back(topic.name());
0804 }
0805 
0806 /*!
0807  *\brief called when an MQTTClient is about to be deleted
0808  * removes every data connected to the MQTTClient, and disconnects the corresponding client from the host
0809  *
0810  * \param hostname the host name of the MQTTClient that will be deleted
0811  * \param name the host name of the MQTTClient that will be deleted
0812  */
0813 void LiveDataDock::removeClient(const QString& hostname, quint16 port) {
0814     auto it = m_hosts.find(qMakePair(hostname, port));
0815     if (it == m_hosts.end())
0816         return;
0817 
0818     MQTTHost & host = it.value();
0819 
0820     if (host.count > 1) {
0821         --host.count;
0822         return;
0823     }
0824 
0825     host.client->disconnectFromHost();
0826 
0827     if (m_previousMQTTClient && m_previousMQTTClient->clientHostName() == hostname) {
0828         disconnect(m_previousHost->client, &QMqttClient::messageReceived, this, &LiveDataDock::mqttMessageReceivedInBackground);
0829         m_previousMQTTClient = nullptr;
0830     }
0831 
0832     if (m_mqttClient && m_mqttClient->clientHostName() == hostname) {
0833         emit MQTTClearTopics();
0834         m_mqttClient = nullptr;
0835     }
0836 
0837     delete host.client;
0838     m_hosts.erase(it);
0839 }
0840 
0841 /*!
0842  * \brief Used for testing the MQTT related features
0843  * \param topic
0844  */
0845 bool LiveDataDock::testSubscribe(const QString& topic) {
0846     QStringList topicList = topic.split('/', QString::SkipEmptyParts);
0847     QTreeWidgetItem* currentItem = nullptr;
0848     for (int i = 0; i < m_subscriptionWidget->topicCount(); ++i) {
0849         if (m_subscriptionWidget->topLevelTopic(i)->text(0) == topicList[0]) {
0850             currentItem = m_subscriptionWidget->topLevelTopic(i);
0851             break;
0852         }
0853     }
0854 
0855     if (currentItem) {
0856         for (int i = 1 ; i < topicList.size(); ++i) {
0857             if (topicList[i] == '#')
0858                 break;
0859 
0860             for (int j = 0; j < currentItem->childCount(); ++j) {
0861                 if (currentItem->child(j)->text(0) == topicList[i]) {
0862                     currentItem = currentItem->child(j);
0863                     break;
0864                 } else if (j == currentItem->childCount() - 1)
0865                     return false;
0866 
0867             }
0868         }
0869     } else
0870         return false;
0871 
0872     m_subscriptionWidget->testSubscribe(currentItem);
0873     return true;
0874 }
0875 
0876 /*!
0877  * \brief Used for testing the MQTT related features
0878  * \param topic
0879  */
0880 bool LiveDataDock::testUnsubscribe(const QString& topic) {
0881     QTreeWidgetItem* currentItem = nullptr;
0882     for (int i = 0; i < m_subscriptionWidget->subscriptionCount(); ++i) {
0883         if (MQTTSubscriptionWidget::checkTopicContains(m_subscriptionWidget->topLevelSubscription(i)->text(0), topic)) {
0884             currentItem = m_subscriptionWidget->topLevelSubscription(i);
0885             break;
0886         }
0887     }
0888 
0889     if (currentItem) {
0890         do {
0891             if (topic == currentItem->text(0)) {
0892                 m_subscriptionWidget->testUnsubscribe(currentItem);
0893                 return true;
0894             } else {
0895                 for (int i = 0; i < currentItem->childCount(); ++i) {
0896                     qDebug()<<currentItem->child(i)->text(0)<<" "<<topic;
0897                     if (MQTTSubscriptionWidget::checkTopicContains(currentItem->child(i)->text(0), topic)) {
0898                         currentItem = currentItem->child(i);
0899                         break;
0900                     } else if (i == currentItem->childCount() - 1)
0901                         return false;
0902                 }
0903             }
0904         } while (currentItem);
0905     } else
0906         return false;
0907 
0908     return false;
0909 }
0910 
0911 void LiveDataDock::showWillSettings() {
0912     QMenu menu;
0913     const QVector<QString>& topics = m_mqttClient->topicNames();
0914     MQTTWillSettingsWidget willSettingsWidget(&menu, m_mqttClient->willSettings(), topics);
0915 
0916     connect(&willSettingsWidget, &MQTTWillSettingsWidget::applyClicked, [this, &menu, &willSettingsWidget]() {
0917         this->useWillMessage(willSettingsWidget.will().enabled);
0918         this->willMessageTypeChanged(willSettingsWidget.will().willMessageType);
0919         this->updateTypeChanged(static_cast<int>(willSettingsWidget.will().willUpdateType));
0920         this->willRetainChanged(willSettingsWidget.will().willRetain);
0921         this->willUpdateIntervalChanged(willSettingsWidget.will().willTimeInterval);
0922         this->willOwnMessageChanged(willSettingsWidget.will().willOwnMessage);
0923         this->willTopicChanged(willSettingsWidget.will().willTopic);
0924         this->statisticsChanged(willSettingsWidget.statisticsType());
0925 
0926         menu.close();
0927     });
0928 
0929     auto* widgetAction = new QWidgetAction(this);
0930     widgetAction->setDefaultWidget(&willSettingsWidget);
0931     menu.addAction(widgetAction);
0932 
0933     QPoint pos(ui.bLWT->sizeHint().width(), ui.bLWT->sizeHint().height());
0934     menu.exec(ui.bLWT->mapToGlobal(pos));
0935 }
0936 
0937 void LiveDataDock::enableWill(bool enable) {
0938     if(enable) {
0939         if(!ui.bLWT->isEnabled())
0940             ui.bLWT->setEnabled(enable);
0941     } else
0942         ui.bLWT->setEnabled(enable);
0943 }
0944 #endif