diff --git a/src/kdefrontend/datasources/ImportFileWidget.cpp b/src/kdefrontend/datasources/ImportFileWidget.cpp index 88d6b6603..179a40e6b 100644 --- a/src/kdefrontend/datasources/ImportFileWidget.cpp +++ b/src/kdefrontend/datasources/ImportFileWidget.cpp @@ -1,2920 +1,2933 @@ /*************************************************************************** File : ImportFileWidget.cpp Project : LabPlot Description : import file data widget -------------------------------------------------------------------- Copyright : (C) 2009-2018 Stefan Gerlach (stefan.gerlach@uni.kn) Copyright : (C) 2009-2017 Alexander Semke (alexander.semke@web.de) Copyright : (C) 2017-2018 Fabian Kristof (fkristofszabolcs@gmail.com) Copyright : (C) 2018 Kovacs Ferencz (kferike98@gmail.com) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "ImportFileWidget.h" #include "FileInfoDialog.h" #include "backend/datasources/filters/AsciiFilter.h" #include "backend/datasources/filters/BinaryFilter.h" #include "backend/datasources/filters/HDF5Filter.h" #include "backend/datasources/filters/NetCDFFilter.h" #include "backend/datasources/filters/ImageFilter.h" #include "backend/datasources/filters/FITSFilter.h" #include "backend/datasources/filters/JsonFilter.h" #include "backend/datasources/filters/QJsonModel.h" #include "backend/datasources/filters/NgspiceRawAsciiFilter.h" #include "backend/datasources/filters/NgspiceRawBinaryFilter.h" #include "backend/datasources/filters/ROOTFilter.h" #include "AsciiOptionsWidget.h" #include "BinaryOptionsWidget.h" #include "HDF5OptionsWidget.h" #include "ImageOptionsWidget.h" #include "NetCDFOptionsWidget.h" #include "FITSOptionsWidget.h" #include "JsonOptionsWidget.h" #include "ROOTOptionsWidget.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_MQTT #include "backend/core/Project.h" #include #include #include #include #include #endif /*! \class ImportFileWidget \brief Widget for importing data from a file. \ingroup kdefrontend */ ImportFileWidget::ImportFileWidget(QWidget* parent, const QString& fileName) : QWidget(parent), m_fileName(fileName), m_fileEmpty(false), m_liveDataSource(true), #ifdef HAVE_MQTT m_mqttReadyForPreview (false), m_searching(false), m_searchTimer(new QTimer(this)), m_connectTimeoutTimer(new QTimer(this)), m_client(new QMqttClient(this)), #endif m_suppressRefresh(false) { ui.setupUi(this); #ifdef HAVE_MQTT m_searchTimer->setInterval(10000); m_connectTimeoutTimer->setInterval(5000); #endif QCompleter* completer = new QCompleter(this); completer->setModel(new QDirModel); ui.leFileName->setCompleter(completer); ui.cbFileType->addItems(AbstractFileFilter::fileTypes()); QStringList filterItems; filterItems << i18n("Automatic") << i18n("Custom"); ui.cbFilter->addItems(filterItems); // file type specific option widgets QWidget* asciiw = new QWidget(); m_asciiOptionsWidget = std::unique_ptr(new AsciiOptionsWidget(asciiw)); ui.swOptions->insertWidget(AbstractFileFilter::Ascii, asciiw); QWidget* binaryw = new QWidget(); m_binaryOptionsWidget = std::unique_ptr(new BinaryOptionsWidget(binaryw)); ui.swOptions->insertWidget(AbstractFileFilter::Binary, binaryw); QWidget* imagew = new QWidget(); m_imageOptionsWidget = std::unique_ptr(new ImageOptionsWidget(imagew)); ui.swOptions->insertWidget(AbstractFileFilter::Image, imagew); QWidget* hdf5w = new QWidget(); m_hdf5OptionsWidget = std::unique_ptr(new HDF5OptionsWidget(hdf5w, this)); ui.swOptions->insertWidget(AbstractFileFilter::HDF5, hdf5w); QWidget* netcdfw = new QWidget(); m_netcdfOptionsWidget = std::unique_ptr(new NetCDFOptionsWidget(netcdfw, this)); ui.swOptions->insertWidget(AbstractFileFilter::NETCDF, netcdfw); QWidget* fitsw = new QWidget(); m_fitsOptionsWidget = std::unique_ptr(new FITSOptionsWidget(fitsw, this)); ui.swOptions->insertWidget(AbstractFileFilter::FITS, fitsw); QWidget* jsonw = new QWidget(); m_jsonOptionsWidget = std::unique_ptr(new JsonOptionsWidget(jsonw, this)); ui.swOptions->insertWidget(AbstractFileFilter::Json, jsonw); QWidget* rootw = new QWidget(); m_rootOptionsWidget = std::unique_ptr(new ROOTOptionsWidget(rootw, this)); ui.swOptions->insertWidget(AbstractFileFilter::ROOT, rootw); ui.tvJson->header()->setSectionResizeMode(QHeaderView::ResizeToContents); ui.tvJson->setAlternatingRowColors(true); ui.tvJson->setModel(m_jsonOptionsWidget->model()); showJsonModel(false); // the table widget for preview m_twPreview = new QTableWidget(ui.tePreview); m_twPreview->verticalHeader()->hide(); m_twPreview->setEditTriggers(QTableWidget::NoEditTriggers); QHBoxLayout* layout = new QHBoxLayout; layout->addWidget(m_twPreview); ui.tePreview->setLayout(layout); m_twPreview->hide(); // default filter ui.swOptions->setCurrentIndex(AbstractFileFilter::Ascii); #if !defined(HAVE_HDF5) || !defined(HAVE_NETCDF) || !defined(HAVE_FITS) || !defined(HAVE_ZIP) const QStandardItemModel* model = qobject_cast(ui.cbFileType->model()); #endif #ifndef HAVE_HDF5 // disable HDF5 item QStandardItem* item = model->item(AbstractFileFilter::HDF5); item->setFlags(item->flags() & ~(Qt::ItemIsSelectable | Qt::ItemIsEnabled)); #endif #ifndef HAVE_NETCDF // disable NETCDF item QStandardItem* item2 = model->item(AbstractFileFilter::NETCDF); item2->setFlags(item2->flags() & ~(Qt::ItemIsSelectable | Qt::ItemIsEnabled)); #endif #ifndef HAVE_FITS // disable FITS item QStandardItem* item3 = model->item(AbstractFileFilter::FITS); item3->setFlags(item3->flags() & ~(Qt::ItemIsSelectable | Qt::ItemIsEnabled)); #endif #ifndef HAVE_ZIP // disable ROOT item QStandardItem* item4 = model->item(AbstractFileFilter::ROOT); item4->setFlags(item4->flags() & ~(Qt::ItemIsSelectable | Qt::ItemIsEnabled)); #endif #ifndef HAVE_MQTT // disable MQTT item QStandardItem* item5 = model->item(LiveDataSource::MQTT); item5->setFlags(item5->flags() & ~(Qt::ItemIsSelectable | Qt::ItemIsEnabled)); #endif ui.cbReadingType->addItem(i18n("Whole file"), LiveDataSource::WholeFile); ui.lePort->setValidator( new QIntValidator(ui.lePort) ); ui.gbOptions->hide(); ui.gbUpdateOptions->hide(); ui.bOpen->setIcon( QIcon::fromTheme("document-open") ); ui.bFileInfo->setIcon( QIcon::fromTheme("help-about") ); ui.bManageFilters->setIcon( QIcon::fromTheme("configure") ); ui.bSaveFilter->setIcon( QIcon::fromTheme("document-save") ); ui.bRefreshPreview->setIcon( QIcon::fromTheme("view-refresh") ); connect( ui.leFileName, SIGNAL(textChanged(QString)), SLOT(fileNameChanged(QString)) ); connect( ui.bOpen, SIGNAL(clicked()), this, SLOT (selectFile()) ); connect( ui.bFileInfo, SIGNAL(clicked()), this, SLOT (fileInfoDialog()) ); connect( ui.bSaveFilter, SIGNAL(clicked()), this, SLOT (saveFilter()) ); connect( ui.bManageFilters, SIGNAL(clicked()), this, SLOT (manageFilters()) ); connect( ui.cbFileType, SIGNAL(currentIndexChanged(int)), SLOT(fileTypeChanged(int)) ); connect( ui.cbUpdateType, SIGNAL(currentIndexChanged(int)), this, SLOT(updateTypeChanged(int))); connect( ui.cbReadingType, SIGNAL(currentIndexChanged(int)), this, SLOT(readingTypeChanged(int))); connect( ui.cbFilter, SIGNAL(activated(int)), SLOT(filterChanged(int)) ); connect( ui.bRefreshPreview, SIGNAL(clicked()), SLOT(refreshPreview()) ); #ifdef HAVE_MQTT connect(ui.chbID, &QCheckBox::stateChanged, this, &ImportFileWidget::idChecked); connect(ui.chbAuthentication, &QCheckBox::stateChanged, this, &ImportFileWidget::authenticationChecked); connect(ui.bConnect, &QPushButton::clicked, this, &ImportFileWidget::mqttConnection); connect(m_client, &QMqttClient::connected, this, &ImportFileWidget::onMqttConnect); connect(m_client, &QMqttClient::disconnected, this, &ImportFileWidget::onMqttDisconnect); connect(ui.bSubscribe, &QPushButton::clicked, this, &ImportFileWidget::mqttSubscribe); connect(ui.bUnsubscribe, &QPushButton::clicked, this,&ImportFileWidget::mqttUnsubscribe); connect(m_client, &QMqttClient::messageReceived, this, &ImportFileWidget::mqttMessageReceived); connect(this, &ImportFileWidget::newTopic, this, &ImportFileWidget::setTopicCompleter); connect(m_searchTimer, &QTimer::timeout, this, &ImportFileWidget::topicTimeout); connect(m_connectTimeoutTimer, &QTimer::timeout, this, &ImportFileWidget::mqttConnectTimeout); connect(ui.chbWill, &QCheckBox::stateChanged, this, &ImportFileWidget::useWillMessage); connect(ui.cbWillMessageType, static_cast(&QComboBox::currentIndexChanged), this, &ImportFileWidget::willMessageTypeChanged); connect(ui.cbWillUpdate, static_cast(&QComboBox::currentIndexChanged), this, &ImportFileWidget::willUpdateTypeChanged); connect(m_client, &QMqttClient::errorChanged, this, &ImportFileWidget::mqttErrorChanged); connect(ui.cbFileType, static_cast(&QComboBox::currentIndexChanged), [this]() {emit checkFileType();}); connect(ui.leTopics, &QLineEdit::textChanged, this, &ImportFileWidget::scrollToTopicTreeItem); connect(ui.leSubscriptions, &QLineEdit::textChanged, this, &ImportFileWidget::scrollToSubsriptionTreeItem); connect(ui.chbAuthentication, &QCheckBox::stateChanged, this, &ImportFileWidget::checkConnectEnable); connect(ui.chbID, &QCheckBox::stateChanged, this, &ImportFileWidget::checkConnectEnable); connect(ui.leHost, &QLineEdit::textChanged, this, &ImportFileWidget::checkConnectEnable); connect(ui.lePort, &QLineEdit::textChanged, this, &ImportFileWidget::checkConnectEnable); connect(ui.lePassword, &QLineEdit::textChanged, this, &ImportFileWidget::checkConnectEnable); connect(ui.leUsername, &QLineEdit::textChanged, this, &ImportFileWidget::checkConnectEnable); connect(ui.leID, &QLineEdit::textChanged, this, &ImportFileWidget::checkConnectEnable); ui.bSubscribe->setIcon(ui.bSubscribe->style()->standardIcon(QStyle::SP_ArrowRight)); ui.bUnsubscribe->setIcon(ui.bUnsubscribe->style()->standardIcon(QStyle::SP_BrowserStop)); #endif connect(ui.leHost, SIGNAL(textChanged(QString)), this, SIGNAL(hostChanged())); connect(ui.lePort, SIGNAL(textChanged(QString)), this, SIGNAL(portChanged())); connect( ui.cbSourceType, SIGNAL(currentIndexChanged(int)), this, SLOT(sourceTypeChanged(int))); connect( ui.tvJson, SIGNAL(clicked(const QModelIndex&)), this, SLOT(refreshPreview())); //TODO: implement save/load of user-defined settings later and activate these buttons again ui.bSaveFilter->hide(); ui.bManageFilters->hide(); //defer the loading of settings a bit in order to show the dialog prior to blocking the GUI in refreshPreview() QTimer::singleShot( 100, this, SLOT(loadSettings()) ); hideMQTT(); } void ImportFileWidget::loadSettings() { m_suppressRefresh = true; //load last used settings QString confName; if (m_liveDataSource) confName = QLatin1String("LiveDataImport"); else confName = QLatin1String("FileImport"); KConfigGroup conf(KSharedConfig::openConfig(), confName); //settings for data type specific widgets m_asciiOptionsWidget->loadSettings(); m_binaryOptionsWidget->loadSettings(); m_imageOptionsWidget->loadSettings(); m_jsonOptionsWidget->loadSettings(); //read the source type first since settings in fileNameChanged() depend on this ui.cbSourceType->setCurrentIndex(conf.readEntry("SourceType").toInt()); //general settings ui.cbFileType->setCurrentIndex(conf.readEntry("Type", 0)); ui.cbFilter->setCurrentIndex(conf.readEntry("Filter", 0)); filterChanged(ui.cbFilter->currentIndex()); // needed if filter is not changed if (m_fileName.isEmpty()) ui.leFileName->setText(conf.readEntry("LastImportedFile", "")); else ui.leFileName->setText(m_fileName); //live data related settings ui.cbBaudRate->setCurrentIndex(conf.readEntry("BaudRate").toInt()); ui.cbReadingType->setCurrentIndex(conf.readEntry("ReadingType").toInt()); ui.cbSerialPort->setCurrentIndex(conf.readEntry("SerialPort").toInt()); ui.cbUpdateType->setCurrentIndex(conf.readEntry("UpdateType").toInt()); ui.leHost->setText(conf.readEntry("Host","")); ui.sbKeepNValues->setValue(conf.readEntry("KeepNValues").toInt()); ui.lePort->setText(conf.readEntry("Port","")); ui.sbSampleSize->setValue(conf.readEntry("SampleSize").toInt()); ui.sbUpdateInterval->setValue(conf.readEntry("UpdateInterval").toInt()); #ifdef HAVE_MQTT //MQTT related settings ui.chbID->setChecked(conf.readEntry("mqttUseId").toInt()); ui.chbAuthentication->setChecked(conf.readEntry("mqttUseAuthentication").toInt()); ui.chbRetain->setChecked(conf.readEntry("mqttUseRetain").toInt()); ui.leUsername->setText(conf.readEntry("mqttUsername","")); ui.lePassword->setText(conf.readEntry("mqttPassword","")); ui.leID->setText(conf.readEntry("mqttId","")); ui.chbWillRetain->setChecked(conf.readEntry("mqttWillRetain").toInt()); ui.cbWillUpdate->setCurrentIndex(conf.readEntry("mqttWillUpdateType").toInt()); ui.cbWillQoS->setCurrentIndex(conf.readEntry("mqttWillQoS").toInt()); ui.leWillOwnMessage->setText(conf.readEntry("mqttWillOwnMessage","")); ui.leWillUpdateInterval->setText(conf.readEntry("mqttWillUpdateInterval","")); QString willStatistics = conf.readEntry("mqttWillStatistics",""); QStringList statisticsList = willStatistics.split('|', QString::SplitBehavior::SkipEmptyParts); for(auto value : statisticsList) { QListWidgetItem* item = ui.lwWillStatistics->item(value.toInt()); item->setCheckState(Qt::Checked); } ui.cbWillMessageType->setCurrentIndex(conf.readEntry("mqttWillMessageType").toInt()); ui.chbWill->setChecked(conf.readEntry("mqttWillUse").toInt()); //chbWill is unchecked by deafult, so if false is loaded it doesn't emit state changed signal, we have to force it if(!ui.chbWill->isChecked()) { ui.chbWill->setChecked(true); ui.chbWill->setChecked(false); } #endif m_suppressRefresh = false; refreshPreview(); } ImportFileWidget::~ImportFileWidget() { // save current settings QString confName; if (m_liveDataSource) confName = QLatin1String("LiveDataImport"); else confName = QLatin1String("FileImport"); KConfigGroup conf(KSharedConfig::openConfig(), confName); // general settings conf.writeEntry("Type", ui.cbFileType->currentIndex()); conf.writeEntry("Filter", ui.cbFilter->currentIndex()); conf.writeEntry("LastImportedFile", ui.leFileName->text()); //live data related settings conf.writeEntry("SourceType", ui.cbSourceType->currentIndex()); conf.writeEntry("UpdateType", ui.cbUpdateType->currentIndex()); conf.writeEntry("ReadingType", ui.cbReadingType->currentIndex()); conf.writeEntry("SampleSize", ui.sbSampleSize->value()); conf.writeEntry("KeepNValues", ui.sbKeepNValues->value()); conf.writeEntry("BaudRate", ui.cbBaudRate->currentIndex()); conf.writeEntry("SerialPort", ui.cbSerialPort->currentIndex()); conf.writeEntry("Host", ui.leHost->text()); conf.writeEntry("Port", ui.lePort->text()); conf.writeEntry("UpdateInterval", ui.sbUpdateInterval->value()); #ifdef HAVE_MQTT //MQTT related settings conf.writeEntry("mqttUsername", ui.leUsername->text()); conf.writeEntry("mqttPassword", ui.lePassword->text()); conf.writeEntry("mqttId", ui.leID->text()); conf.writeEntry("mqttWillMessageType", ui.cbWillMessageType->currentIndex()); conf.writeEntry("mqttWillUpdateType", ui.cbWillUpdate->currentIndex()); conf.writeEntry("mqttWillQoS", ui.cbWillQoS->currentIndex()); conf.writeEntry("mqttWillOwnMessage", ui.leWillOwnMessage->text()); conf.writeEntry("mqttWillUpdateInterval", ui.leWillUpdateInterval->text()); QString willStatistics; for(int i = 0; i < ui.lwWillStatistics->count(); ++i) { QListWidgetItem* item = ui.lwWillStatistics->item(i); if (item->checkState() == Qt::Checked) willStatistics += QString::number(i)+"|"; } conf.writeEntry("mqttWillStatistics", willStatistics); conf.writeEntry("mqttWillRetain", static_cast(ui.chbWillRetain->isChecked())); conf.writeEntry("mqttWillUse", static_cast(ui.chbWill->isChecked())); conf.writeEntry("mqttUseId", static_cast(ui.chbID->isChecked())); conf.writeEntry("mqttUseAuthentication", static_cast(ui.chbAuthentication->isChecked())); conf.writeEntry("mqttUseRetain", static_cast(ui.chbRetain->isChecked())); #endif // data type specific settings m_asciiOptionsWidget->saveSettings(); m_binaryOptionsWidget->saveSettings(); m_imageOptionsWidget->saveSettings(); m_jsonOptionsWidget->saveSettings(); } void ImportFileWidget::hideDataSource() { m_liveDataSource = false; ui.gbUpdateOptions->hide(); ui.chbLinkFile->hide(); ui.cbBaudRate->hide(); ui.lBaudRate->hide(); ui.lHost->hide(); ui.leHost->hide(); ui.lPort->hide(); ui.lePort->hide(); ui.cbSerialPort->hide(); ui.lSerialPort->hide(); ui.lSourceType->hide(); ui.cbSourceType->hide(); ui.cbUpdateType->hide(); ui.lUpdateType->hide(); ui.sbUpdateInterval->hide(); ui.lUpdateInterval->hide(); #ifdef HAVE_MQTT hideMQTT(); #endif } void ImportFileWidget::showAsciiHeaderOptions(bool b) { m_asciiOptionsWidget->showAsciiHeaderOptions(b); } void ImportFileWidget::showJsonModel(bool b) { ui.tvJson->setVisible(b); ui.lField->setVisible(b); } void ImportFileWidget::showOptions(bool b) { ui.gbOptions->setVisible(b); if (m_liveDataSource) ui.gbUpdateOptions->setVisible(b); resize(layout()->minimumSize()); } QString ImportFileWidget::fileName() const { return ui.leFileName->text(); } QString ImportFileWidget::selectedObject() const { const QString& path = ui.leFileName->text(); //determine the file name only QString name = path.right(path.length() - path.lastIndexOf(QDir::separator()) - 1); //strip away the extension if available if (name.indexOf('.') != -1) name = name.left(name.lastIndexOf('.')); //for multi-dimensinal formats like HDF, netCDF and FITS add the currently selected object const auto format = currentFileType(); if (format == AbstractFileFilter::HDF5) { const QStringList& hdf5Names = m_hdf5OptionsWidget->selectedHDF5Names(); if (hdf5Names.size()) name += hdf5Names.first(); //the names of the selected HDF5 objects already have '/' } else if (format == AbstractFileFilter::NETCDF) { const QStringList& names = m_netcdfOptionsWidget->selectedNetCDFNames(); if (names.size()) name += QLatin1Char('/') + names.first(); } else if (format == AbstractFileFilter::FITS) { const QString& extensionName = m_fitsOptionsWidget->currentExtensionName(); if (!extensionName.isEmpty()) name += QLatin1Char('/') + extensionName; } else if (format == AbstractFileFilter::ROOT) { const QStringList& names = m_rootOptionsWidget->selectedROOTNames(); if (names.size()) name += QLatin1Char('/') + names.first(); } return name; } /*! * returns \c true if the number of lines to be imported from the currently selected file is zero ("file is empty"), * returns \c false otherwise. */ bool ImportFileWidget::isFileEmpty() const { return m_fileEmpty; } QString ImportFileWidget::host() const { return ui.leHost->text(); } QString ImportFileWidget::port() const { return ui.lePort->text(); } QString ImportFileWidget::serialPort() const { return ui.cbSerialPort->currentText(); } int ImportFileWidget::baudRate() const { return ui.cbBaudRate->currentText().toInt(); } /*! saves the settings to the data source \c source. */ void ImportFileWidget::saveSettings(LiveDataSource* source) const { AbstractFileFilter::FileType fileType = static_cast(ui.cbFileType->currentIndex()); LiveDataSource::UpdateType updateType = static_cast(ui.cbUpdateType->currentIndex()); LiveDataSource::SourceType sourceType = static_cast(ui.cbSourceType->currentIndex()); LiveDataSource::ReadingType readingType = static_cast(ui.cbReadingType->currentIndex()); source->setComment( ui.leFileName->text() ); source->setFileType(fileType); source->setFilter(this->currentFileFilter()); source->setSourceType(sourceType); source->setReadingType(readingType); if (updateType == LiveDataSource::UpdateType::TimeInterval) source->setUpdateInterval(ui.sbUpdateInterval->value()); else source->setFileWatched(true); source->setKeepNValues(ui.sbKeepNValues->value()); source->setUpdateType(updateType); if (readingType != LiveDataSource::ReadingType::TillEnd) source->setSampleSize(ui.sbSampleSize->value()); switch (sourceType) { case LiveDataSource::SourceType::FileOrPipe: source->setFileName( ui.leFileName->text() ); source->setFileLinked( ui.chbLinkFile->isChecked() ); break; case LiveDataSource::SourceType::LocalSocket: source->setLocalSocketName(ui.leFileName->text()); break; case LiveDataSource::SourceType::NetworkTcpSocket: case LiveDataSource::SourceType::NetworkUdpSocket: source->setHost(ui.leHost->text()); source->setPort((quint16)ui.lePort->text().toInt()); break; case LiveDataSource::SourceType::SerialPort: source->setBaudRate(ui.cbBaudRate->currentText().toInt()); source->setSerialPort(ui.cbSerialPort->currentText()); break; default: break; } } #ifdef HAVE_MQTT /*! saves the settings to the MQTTClient \c client. */ void ImportFileWidget::saveMQTTSettings(MQTTClient* client) const { MQTTClient::UpdateType updateType = static_cast(ui.cbUpdateType->currentIndex()); MQTTClient::ReadingType readingType = static_cast(ui.cbReadingType->currentIndex()); client->setComment( ui.leFileName->text() ); client->setFilter(this->currentFileFilter()); client->setReadingType(readingType); if (updateType == MQTTClient::UpdateType::TimeInterval) client->setUpdateInterval(ui.sbUpdateInterval->value()); client->setKeepNValues(ui.sbKeepNValues->value()); client->setUpdateType(updateType); if (readingType != MQTTClient::ReadingType::TillEnd) client->setSampleSize(ui.sbSampleSize->value()); client->setMqttClientHostPort(m_client->hostname(), m_client->port()); client->setMQTTUseAuthentication(ui.chbAuthentication->isChecked()); if(ui.chbAuthentication->isChecked()) client->setMqttClientAuthentication(m_client->username(), m_client->password()); client->setMQTTUseID(ui.chbID->isChecked()); if(ui.chbID->isChecked()) client->setMqttClientId(m_client->clientId()); for(int i=0; iaddInitialMqttSubscriptions(m_mqttSubscriptions[i]->topic(), m_mqttSubscriptions[i]->qos()); } client->setMqttRetain(ui.chbRetain->isChecked()); client->setWillMessageType(static_cast(ui.cbWillMessageType->currentIndex()) ); client->setWillOwnMessage(ui.leWillOwnMessage->text()); client->setWillQoS(ui.cbWillQoS->currentIndex() ); client->setWillRetain(ui.chbWillRetain->isChecked()); client->setWillTimeInterval(ui.leWillUpdateInterval->text().toInt()); client->setWillTopic(ui.cbWillTopic->currentText()); client->setWillUpdateType(static_cast(ui.cbWillUpdate->currentIndex()) ); client->setMqttWillUse(ui.chbWill->isChecked()); for(int i = 0; i < ui.lwWillStatistics->count(); ++i) { QListWidgetItem* item = ui.lwWillStatistics->item(i); if (item->checkState() == Qt::Checked) client->addWillStatistics(static_cast (i)); } } #endif /*! returns the currently used file type. */ AbstractFileFilter::FileType ImportFileWidget::currentFileType() const { return static_cast(ui.cbFileType->currentIndex()); } LiveDataSource::SourceType ImportFileWidget::currentSourceType() const { return static_cast(ui.cbSourceType->currentIndex()); } /*! returns the currently used filter. */ AbstractFileFilter* ImportFileWidget::currentFileFilter() const { DEBUG("ImportFileWidget::currentFileFilter()"); AbstractFileFilter::FileType fileType = static_cast(ui.cbFileType->currentIndex()); switch (fileType) { case AbstractFileFilter::Ascii: { DEBUG(" ASCII"); //TODO std::unique_ptr filter(new AsciiFilter()); AsciiFilter* filter = new AsciiFilter(); if (ui.cbFilter->currentIndex() == 0) //"automatic" filter->setAutoModeEnabled(true); else if (ui.cbFilter->currentIndex() == 1) { //"custom" filter->setAutoModeEnabled(false); m_asciiOptionsWidget->applyFilterSettings(filter); } else filter->loadFilterSettings( ui.cbFilter->currentText() ); //save the data portion to import filter->setStartRow( ui.sbStartRow->value()); filter->setEndRow( ui.sbEndRow->value() ); filter->setStartColumn( ui.sbStartColumn->value()); filter->setEndColumn( ui.sbEndColumn->value()); return filter; } case AbstractFileFilter::Binary: { BinaryFilter* filter = new BinaryFilter(); if ( ui.cbFilter->currentIndex() == 0 ) //"automatic" filter->setAutoModeEnabled(true); else if ( ui.cbFilter->currentIndex() == 1 ) { //"custom" filter->setAutoModeEnabled(false); m_binaryOptionsWidget->applyFilterSettings(filter); } else { //TODO: load filter settings // filter->setFilterName( ui.cbFilter->currentText() ); } filter->setStartRow( ui.sbStartRow->value() ); filter->setEndRow( ui.sbEndRow->value() ); return filter; } case AbstractFileFilter::Image: { ImageFilter* filter = new ImageFilter(); filter->setImportFormat(m_imageOptionsWidget->currentFormat()); filter->setStartRow( ui.sbStartRow->value() ); filter->setEndRow( ui.sbEndRow->value() ); filter->setStartColumn( ui.sbStartColumn->value() ); filter->setEndColumn( ui.sbEndColumn->value() ); return filter; } case AbstractFileFilter::HDF5: { HDF5Filter* filter = new HDF5Filter(); QStringList names = selectedHDF5Names(); if (!names.isEmpty()) filter->setCurrentDataSetName(names[0]); filter->setStartRow( ui.sbStartRow->value() ); filter->setEndRow( ui.sbEndRow->value() ); filter->setStartColumn( ui.sbStartColumn->value() ); filter->setEndColumn( ui.sbEndColumn->value() ); return filter; } case AbstractFileFilter::NETCDF: { NetCDFFilter* filter = new NetCDFFilter(); if (!selectedNetCDFNames().isEmpty()) filter->setCurrentVarName(selectedNetCDFNames()[0]); filter->setStartRow( ui.sbStartRow->value() ); filter->setEndRow( ui.sbEndRow->value() ); filter->setStartColumn( ui.sbStartColumn->value() ); filter->setEndColumn( ui.sbEndColumn->value() ); return filter; } case AbstractFileFilter::FITS: { FITSFilter* filter = new FITSFilter(); filter->setStartRow( ui.sbStartRow->value()); filter->setEndRow( ui.sbEndRow->value() ); filter->setStartColumn( ui.sbStartColumn->value()); filter->setEndColumn( ui.sbEndColumn->value()); return filter; } case AbstractFileFilter::Json: { JsonFilter* filter = new JsonFilter(); m_jsonOptionsWidget->applyFilterSettings(filter, ui.tvJson->currentIndex()); filter->setStartRow( ui.sbStartRow->value() ); filter->setEndRow( ui.sbEndRow->value() ); filter->setStartColumn( ui.sbStartColumn->value()); filter->setEndColumn( ui.sbEndColumn->value()); return filter; } case AbstractFileFilter::ROOT: { ROOTFilter* filter = new ROOTFilter(); QStringList names = selectedROOTNames(); if (!names.isEmpty()) filter->setCurrentHistogram(names.first()); filter->setStartBin( m_rootOptionsWidget->startBin() ); filter->setEndBin( m_rootOptionsWidget->endBin() ); filter->setColumns( m_rootOptionsWidget->columns() ); return filter; } case AbstractFileFilter::NgspiceRawAscii: { NgspiceRawAsciiFilter* filter = new NgspiceRawAsciiFilter(); filter->setStartRow( ui.sbStartRow->value() ); filter->setEndRow( ui.sbEndRow->value() ); return filter; } case AbstractFileFilter::NgspiceRawBinary: { NgspiceRawBinaryFilter* filter = new NgspiceRawBinaryFilter(); filter->setStartRow( ui.sbStartRow->value() ); filter->setEndRow( ui.sbEndRow->value() ); return filter; } } return 0; } /*! opens a file dialog and lets the user select the file data source. */ void ImportFileWidget::selectFile() { KConfigGroup conf(KSharedConfig::openConfig(), "ImportFileWidget"); QString dir = conf.readEntry("LastDir", ""); QString path = QFileDialog::getOpenFileName(this, i18n("Select the File Data Source"), dir); if (path.isEmpty()) //cancel was clicked in the file-dialog return; int pos = path.lastIndexOf(QDir::separator()); if (pos != -1) { QString newDir = path.left(pos); if (newDir != dir) conf.writeEntry("LastDir", newDir); } ui.leFileName->setText(path); //TODO: decide whether the selection of several files should be possible // QStringList filelist = QFileDialog::getOpenFileNames(this,i18n("Select one or more files to open")); // if (! filelist.isEmpty() ) // ui.leFileName->setText(filelist.join(";")); } /*! hides the MQTT related items of the widget */ void ImportFileWidget::hideMQTT() { ui.leID->hide(); ui.lMqttID->hide(); ui.lePassword->hide(); ui.lPassword->hide(); ui.leUsername->hide(); ui.lUsername->hide(); ui.cbQos->hide(); ui.lQos->hide(); ui.twTopics->hide(); ui.lTopicSearch->hide(); ui.leTopics->hide(); ui.twSubscriptions->hide(); ui.chbAuthentication->hide(); ui.chbID->hide(); ui.bSubscribe->hide(); ui.bUnsubscribe->hide(); ui.bConnect->hide(); ui.gbMqttWill->hide(); ui.chbWill->hide(); ui.chbWillRetain->hide(); ui.cbWillQoS->hide(); ui.cbWillMessageType->hide(); ui.cbWillTopic->hide(); ui.cbWillUpdate->hide(); ui.leWillOwnMessage->hide(); ui.leWillUpdateInterval->setValidator(new QIntValidator(2, 1000000) ); ui.leWillUpdateInterval->hide(); ui.lWillMessageType->hide(); ui.lWillOwnMessage->hide(); ui.lWillQos->hide(); ui.lWillTopic->hide(); ui.lWillUpdate->hide(); ui.lWillUpdateInterval->hide(); ui.lwWillStatistics->hide(); ui.lWillStatistics->hide(); } #ifdef HAVE_MQTT /*! * returns \c true if there is a valid connection to an MQTT broker and the user has subscribed to at least 1 topic, * returns \c false otherwise. */ bool ImportFileWidget::isMqttValid(){ bool connected = (m_client->state() == QMqttClient::ClientState::Connected); bool subscribed = (ui.twSubscriptions->topLevelItemCount() > 0); bool fileTypeOk = false; if(this->currentFileType() == AbstractFileFilter::FileType::Ascii) fileTypeOk = true; return connected && subscribed && fileTypeOk; } /*! *\brief Checks if a topic contains another one * * \param superior the name of a topic * \param inferior the name of a topic * \return true if superior is equal to or contains(if superior contains wildcards) inferior, * false otherwise */ bool ImportFileWidget::checkTopicContains(const QString& superior, const QString& inferior) { if (superior == inferior) return true; else { if(superior.contains("/")) { QStringList superiorList = superior.split('/', QString::SkipEmptyParts); QStringList inferiorList = inferior.split('/', QString::SkipEmptyParts); //a longer topic can't contain a shorter one if(superiorList.size() > inferiorList.size()) return false; bool ok = true; for(int i = 0; i < superiorList.size(); ++i) { if(superiorList.at(i) != inferiorList.at(i)) { if((superiorList.at(i) != "+") && !(superiorList.at(i) == "#" && i == superiorList.size() - 1)) { //if the two topics differ, and the superior's current level isn't + or #(which can be only in the last position) //then superior can't contain inferior qDebug() < 0) { for(int j = differIndex + 1; j < firstList.size(); ++j) { if(firstList.at(j) != secondtList.at(j)) { differ = true; break; } } } else differ = true; if(!differ) { for(int i = 0; i < firstList.size(); ++i) { if(i != differIndex) { commonTopic.append(firstList.at(i)); } else { //we put "+" wildcard at the level where they differ commonTopic.append("+"); } if(i != firstList.size() - 1) commonTopic.append("/"); } } } } qDebug() << "Common topic: "< 0) { for(int j = differIndex + 1; j < firstList.size(); ++j) { if(firstList.at(j) != secondtList.at(j)) { differ = true; break; } } } else differ = true; if(!differ) { for(int i = 0; i < firstList.size(); ++i) { if(i != differIndex) commonTopic.append(firstList.at(i)); else commonTopic.append("+"); if(i != firstList.size() - 1) commonTopic.append("/"); } } } } qDebug() << "Common topic: "<unsubscribe(filter); qDebug()<<"unsubscribe occured"; for(int i = 0; i< m_mqttSubscriptions.count(); ++i) if(m_mqttSubscriptions[i]->topic().filter() == topicName) { m_mqttSubscriptions.remove(i); break; } m_mqttReadyForPreview = false; QMapIterator i(m_messageArrived); while(i.hasNext()) { i.next(); if(checkTopicContains(topicName, i.key().name())) { m_messageArrived.remove(i.key()); } } QMapIterator j(m_lastMessage); while(j.hasNext()) { j.next(); if(checkTopicContains(topicName, j.key().name())) { m_lastMessage.remove(j.key()); } } for(int row = 0; rowtopLevelItemCount(); row++) { if(ui.twSubscriptions->topLevelItem(row)->text(0) == topicName) { ui.twSubscriptions->topLevelItem(row)->takeChildren(); ui.twSubscriptions->takeTopLevelItem(row); } } for(int i = 0; i < m_subscribedTopicNames.size(); ++i) { if(checkTopicContains(topicName, m_subscribedTopicNames[i])) { m_subscribedTopicNames.remove(i); i--; } } for(int item = 0; item < ui.cbWillTopic->count(); ++item) { if(checkTopicContains(topicName, ui.cbWillTopic->itemText(item))) { ui.cbWillTopic->removeItem(item); item--; } } //signals that there was a change among the subscribed topics emit subscriptionsChanged(); refreshPreview(); } } /*! *\brief Adds to a # wildcard containing topic, every topic present in twTopics that the former topic contains * * \param topic pointer to the TreeWidgetItem which was selected before subscribing * \param subscription pointer to the TreeWidgetItem which represents the new subscirption, * we add all of the children to this item */ void ImportFileWidget::addSubscriptionChildren(QTreeWidgetItem * topic, QTreeWidgetItem * subscription) { //if the topic doesn't have any children we don't do anything if(topic->childCount() > 0) { for(int i = 0; i < topic->childCount(); ++i) { QTreeWidgetItem* temp = topic->child(i); QString name; //if it has children, then we add it as a # wildcrad containing topic if(topic->child(i)->childCount() > 0) { name.append(temp->text(0) + "/#"); while(temp->parent() != nullptr) { temp = temp->parent(); name.prepend(temp->text(0) + "/"); } } //if not then we simply add the topic itself else { name.append(temp->text(0)); while(temp->parent() != nullptr) { temp = temp->parent(); name.prepend(temp->text(0) + "/"); } } QStringList nameList; nameList.append(name); QTreeWidgetItem* childItem = new QTreeWidgetItem(nameList); subscription->addChild(childItem); //we use the function recursively on the given item addSubscriptionChildren(topic->child(i), childItem); } } } /*! *\brief Fills the children vector, with the root item's (twSubscriptions) leaf children (meaning no wildcard containing topics) * * \param children vector of TreeWidgetItem pointers * \param root pointer to a TreeWidgetItem of twSubscriptions */ void ImportFileWidget::findSubscriptionLeafChildren(QVector& children, QTreeWidgetItem* root) { if(root->childCount() == 0) { children.push_back(root); } else { for(int i = 0; i < root->childCount(); ++i) { findSubscriptionLeafChildren(children, root->child(i)); } } } /*! *\brief Returns the amount of topics that the "+" wildcard will replace in the level position * * \param levelIdx the level currently being investigated * \param level the level where the new + wildcard will be placed * \param commonList the topic name split into levels * \param currentItem pointer to a TreeWidgetItem which represents the parent of the level * represented by levelIdx * \return returns the childCount, or -1 if some topics already represented by + wildcard have different * amount of children */ int ImportFileWidget::checkCommonChildCount(int levelIdx, int level, QStringList& commonList, QTreeWidgetItem* currentItem) { //we recursively check the number of children, until we get to level-1 if(levelIdx < level - 1) { if(commonList[levelIdx] != "+") { for(int j = 0; j < currentItem->childCount(); ++j) { if(currentItem->child(j)->text(0) == commonList[levelIdx]) { //if the level isn't represented by + wildcard we simply return the amount of children of the corresponding item, recursively return checkCommonChildCount(levelIdx + 1, level, commonList, currentItem->child(j)); } } } else { int childCount = -1; bool ok = true; //otherwise we check if every + wildcard represented topic has the same number of children, recursively for(int j = 0; j < currentItem->childCount(); ++j) { int temp = checkCommonChildCount(levelIdx + 1, level, commonList, currentItem->child(j)); if((j > 0) && (temp != childCount)) { ok = false; break; } childCount = temp; } //if yes we return this number, otherwise -1 if(ok) return childCount; else return -1; } } else if (levelIdx == level - 1) { if(commonList[levelIdx] != "+") { for(int j = 0; j < currentItem->childCount(); ++j) { if(currentItem->child(j)->text(0) == commonList[levelIdx]) { //if the level isn't represented by + wildcard we simply return the amount of children of the corresponding item return currentItem->child(j)->childCount(); } } } else { int childCount = -1; bool ok = true; //otherwise we check if every + wildcard represented topic has the same number of children for(int j = 0; j < currentItem->childCount(); ++j) { if((j > 0) && (currentItem->child(j)->childCount() != childCount)) { ok = false; break; } childCount = currentItem->child(j)->childCount(); } //if yes we return this number, otherwise -1 if(ok) return childCount; else return -1; } } else if (level == 1 && levelIdx == 1) { return currentItem->childCount(); } return -1; } /*! *\brief We search in twSubscriptions for topics that can be represented using + wildcards, then merge them. * We do this until there are no topics to merge */ void ImportFileWidget::manageCommonLevelSubscriptions() { bool foundEqual = false; do{ foundEqual = false; QMap> equalTopicsMap; QVector equalTopics; //compare the subscriptions present in the TreeWidget for(int i = 0; i < ui.twSubscriptions->topLevelItemCount() - 1; ++i) { for(int j = i + 1; j < ui.twSubscriptions->topLevelItemCount(); ++j) { qDebug()<topLevelItem(i)->text(0)<<" "<topLevelItem(j)->text(0); QString commonTopic = checkCommonLevel(ui.twSubscriptions->topLevelItem(i)->text(0), ui.twSubscriptions->topLevelItem(j)->text(0)); //if there is a common topic for the 2 compared topics, we add them to the map (using the common topic as key) if(!commonTopic.isEmpty()) { if(!equalTopicsMap[commonTopic].contains(ui.twSubscriptions->topLevelItem(i)->text(0))) { equalTopicsMap[commonTopic].push_back(ui.twSubscriptions->topLevelItem(i)->text(0)); } if(!equalTopicsMap[commonTopic].contains(ui.twSubscriptions->topLevelItem(j)->text(0))) { qDebug()<topLevelItem(i)->text(0); equalTopicsMap[commonTopic].push_back(ui.twSubscriptions->topLevelItem(j)->text(0)); } } } } if(!equalTopicsMap.isEmpty()) { qDebug()<<"Equal topics not empty"; QVector commonTopics; QMapIterator> topics(equalTopicsMap); //check for every map entry, if the found topics can be merged or not while(topics.hasNext()) { topics.next(); int level = commonLevelIndex(topics.value().last(), topics.value().first()); QStringList commonList = topics.value().first().split('/', QString::SkipEmptyParts); QTreeWidgetItem* currentItem; //search the corresponding item to the common topics first level(root) for(int i = 0; i < ui.twTopics->topLevelItemCount(); ++i) { if(ui.twTopics->topLevelItem(i)->text(0) == commonList.first()) { currentItem = ui.twTopics->topLevelItem(i); break; } } //calculate the number of topics the new + wildcard could replace int childCount = checkCommonChildCount(1, level, commonList, currentItem); if(childCount > 0) { //if the number of topics found and the calculated number of topics is equal, the topics can be merged if(topics.value().size() == childCount) { foundEqual = true; commonTopics.push_back(topics.key()); } } } if(foundEqual) { //if there are more common topics, the topics of which can be merged, we choose the one which has the lowest level new "+" wildcard int lowestLevel = INT_MAX; int topicIdx = -1; for(int i = 0; i < commonTopics.size(); ++i) { int level = commonLevelIndex(equalTopicsMap[commonTopics[i]].first(), commonTopics[i]); if(level < lowestLevel) { topicIdx = i; lowestLevel = level; } } equalTopics.append(equalTopicsMap[commonTopics[topicIdx]]); qDebug()<<"Adding common topic"; //Add the common topic ("merging") QString commonTopic; commonTopic = checkCommonLevel(equalTopics.first(), equalTopics.last()); QStringList nameList; nameList.append(commonTopic); QTreeWidgetItem* newTopic = new QTreeWidgetItem(nameList); ui.twSubscriptions->addTopLevelItem(newTopic); QMqttTopicFilter filter {commonTopic}; QMqttSubscription *temp_subscription = m_client->subscribe(filter, static_cast (ui.cbQos->currentText().toUInt()) ); if(temp_subscription) { m_mqttSubscriptions.push_back(temp_subscription); connect(temp_subscription, &QMqttSubscription::messageReceived, this, &ImportFileWidget::mqttSubscriptionMessageReceived); emit subscriptionsChanged(); } //remove the "merged" topics qDebug()<<"unsubscribe from equal topics"; for(int i = 0; i < equalTopics.size(); ++i) { for(int j = 0; j < ui.twSubscriptions->topLevelItemCount(); ++j){ if(ui.twSubscriptions->topLevelItem(j)->text(0) == equalTopics[i]) { newTopic->addChild(ui.twSubscriptions->takeTopLevelItem(j)); unsubscribeFromTopic(equalTopics[i]); break; } } } //remove any subscription that the new subscription contains for(int i = 0; i < ui.twSubscriptions->topLevelItemCount(); ++i) { if(checkTopicContains(commonTopic, ui.twSubscriptions->topLevelItem(i)->text(0)) && commonTopic != ui.twSubscriptions->topLevelItem(i)->text(0) ) { unsubscribeFromTopic(ui.twSubscriptions->topLevelItem(i)->text(0)); i--; } } } } } while(foundEqual); } /*! *\brief Fills twSubscriptions with the subscriptions made by the client */ void ImportFileWidget::updateSubscriptionTree() { ui.twSubscriptions->clear(); for (int i = 0; i < m_mqttSubscriptions.size(); ++i) { QStringList name; name.append(m_mqttSubscriptions[i]->topic().filter()); bool found = false; for(int j = 0; j < ui.twSubscriptions->topLevelItemCount(); ++j) { if(ui.twSubscriptions->topLevelItem(j)->text(0) == m_mqttSubscriptions[i]->topic().filter()) { found = true; break; } } if(!found) { qDebug()<<"add:" << m_mqttSubscriptions[i]->topic().filter(); //Add the subscription to the tree widget QTreeWidgetItem* newItem = new QTreeWidgetItem(name); ui.twSubscriptions->addTopLevelItem(newItem); name.clear(); name = m_mqttSubscriptions[i]->topic().filter().split('/', QString::SkipEmptyParts); //find the corresponding "root" item in twTopics QTreeWidgetItem* topic = nullptr; for(int j = 0; j < ui.twTopics->topLevelItemCount(); ++j) { if(ui.twTopics->topLevelItem(j)->text(0) == name[0]) { qDebug()<<"found top level topic: "<topLevelItem(j); break; } } //restore the children of the subscription if(topic != nullptr && topic->childCount() > 0) { qDebug()<<"restoring Children"; restoreSubscriptionChildren(topic, newItem, name, 1); } } } m_searching = false; updateWillTopics(); } /*! *\brief Restores the children of a top level item in twSubscriptions if it contains wildcards * * \param topic pointer to a top level item in twTopics wich represents the root of the subscription topic * \param subscription pointer to a top level item in twSubscriptions, this is the item whose children will be restored * \param list QStringList containing the levels of the subscription topic * \param level the level's number which is being investigated */ void ImportFileWidget::restoreSubscriptionChildren(QTreeWidgetItem * topic, QTreeWidgetItem * subscription, const QStringList& list, int level) { if(list[level] != "+" && list[level] != "#" && level < list.size() - 1) { for(int i = 0; i < topic->childCount(); ++i) { //if the current level isn't + or # wildcard we recursively continue with the next level if(topic->child(i)->text(0) == list[level]) { restoreSubscriptionChildren(topic->child(i), subscription, list, level + 1); break; } } } else if (list[level] == "+") { for(int i = 0; i < topic->childCount(); ++i) { //determine the name of the topic, contained by the subscription QString name; name.append(topic->child(i)->text(0)); for(int j = level + 1; j < list.size(); ++j) { name.append("/" + list[j]); } QTreeWidgetItem* temp = topic->child(i); while(temp->parent() != nullptr) { temp = temp->parent(); name.prepend(temp->text(0) + "/"); } //Add the topic as child of the subscription QStringList nameList; nameList.append(name); QTreeWidgetItem* newItem = new QTreeWidgetItem(nameList); subscription->addChild(newItem); //Continue adding children recursively to the new item restoreSubscriptionChildren(topic->child(i), newItem, list, level + 1); } } else if (list[level] == "#") { //add the children of the # wildcard containing subscription addSubscriptionChildren(topic, subscription); } } /*! *\brief Updates the completer for leSubscriptions */ void ImportFileWidget::updateSubscriptionCompleter() { QStringList subscriptionList; for(int i = 0; i < ui.twSubscriptions->topLevelItemCount(); ++i) { subscriptionList.append(ui.twSubscriptions->topLevelItem(i)->text(0)); } if(!subscriptionList.isEmpty()) { m_subscriptionCompleter = new QCompleter(subscriptionList, this); m_subscriptionCompleter->setCompletionMode(QCompleter::PopupCompletion); m_subscriptionCompleter->setCaseSensitivity(Qt::CaseSensitive); ui.leSubscriptions->setCompleter(m_subscriptionCompleter); } else { ui.leSubscriptions->setCompleter(0); } } #endif /************** SLOTS **************************************************************/ /*! called on file name changes. Determines the file format (ASCII, binary etc.), if the file exists, and activates the corresponding options. */ void ImportFileWidget::fileNameChanged(const QString& name) { QString fileName = name; #ifndef HAVE_WINDOWS // make relative path if ( !fileName.isEmpty() && fileName.at(0) != QDir::separator()) fileName = QDir::homePath() + QDir::separator() + fileName; #endif bool fileExists = QFile::exists(fileName); if (fileExists) ui.leFileName->setStyleSheet(""); else ui.leFileName->setStyleSheet("QLineEdit{background:red;}"); ui.gbOptions->setEnabled(fileExists); ui.bManageFilters->setEnabled(fileExists); ui.cbFilter->setEnabled(fileExists); ui.cbFileType->setEnabled(fileExists); ui.bFileInfo->setEnabled(fileExists); ui.gbUpdateOptions->setEnabled(fileExists); if (!fileExists) { //file doesn't exist -> delete the content preview that is still potentially //available from the previously selected file ui.tePreview->clear(); m_twPreview->clear(); m_hdf5OptionsWidget->clear(); m_netcdfOptionsWidget->clear(); m_fitsOptionsWidget->clear(); m_jsonOptionsWidget->clearModel(); m_rootOptionsWidget->clear(); emit fileNameChanged(); return; } if (currentSourceType() == LiveDataSource::FileOrPipe) { const AbstractFileFilter::FileType fileType = AbstractFileFilter::fileType(fileName); switch(fileType) { case AbstractFileFilter::Ascii: ui.cbFileType->setCurrentIndex(AbstractFileFilter::Ascii); break; case AbstractFileFilter::Binary: ui.cbFileType->setCurrentIndex(AbstractFileFilter::Binary); break; case AbstractFileFilter::Image: ui.cbFileType->setCurrentIndex(AbstractFileFilter::Image); break; case AbstractFileFilter::HDF5: ui.cbFileType->setCurrentIndex(AbstractFileFilter::HDF5); m_hdf5OptionsWidget->updateContent((HDF5Filter*)this->currentFileFilter(), fileName); break; case AbstractFileFilter::NETCDF: ui.cbFileType->setCurrentIndex(AbstractFileFilter::NETCDF); m_netcdfOptionsWidget->updateContent((NetCDFFilter*)this->currentFileFilter(), fileName); break; case AbstractFileFilter::FITS: #ifdef HAVE_FITS ui.cbFileType->setCurrentIndex(AbstractFileFilter::FITS); m_fitsOptionsWidget->updateContent((FITSFilter*)this->currentFileFilter(), fileName); #endif break; case AbstractFileFilter::Json: ui.cbFileType->setCurrentIndex(AbstractFileFilter::Json); m_jsonOptionsWidget->loadDocument(fileName); break; case AbstractFileFilter::ROOT: ui.cbFileType->setCurrentIndex(AbstractFileFilter::ROOT); m_rootOptionsWidget->updateContent((ROOTFilter*)this->currentFileFilter(), fileName); break; case AbstractFileFilter::NgspiceRawAscii: ui.cbFileType->setCurrentIndex(AbstractFileFilter::NgspiceRawAscii); break; case AbstractFileFilter::NgspiceRawBinary: ui.cbFileType->setCurrentIndex(AbstractFileFilter::NgspiceRawBinary); break; } } refreshPreview(); emit fileNameChanged(); } /*! saves the current filter settings */ void ImportFileWidget::saveFilter() { bool ok; QString text = QInputDialog::getText(this, i18n("Save Filter Settings as"), i18n("Filter name:"), QLineEdit::Normal, i18n("new filter"), &ok); if (ok && !text.isEmpty()) { //TODO //AsciiFilter::saveFilter() } } /*! opens a dialog for managing all available predefined filters. */ void ImportFileWidget::manageFilters() { //TODO } /*! Depending on the selected file type, activates the corresponding options in the data portion tab and populates the combobox with the available pre-defined fllter settings for the selected type. */ void ImportFileWidget::fileTypeChanged(int fileType) { ui.swOptions->setCurrentIndex(fileType); //default ui.lFilter->show(); ui.cbFilter->show(); //different file types show different number of tabs in ui.tabWidget. //when switching from the previous file type we re-set the tab widget to its original state //and remove/add the required tabs further below for (int i = 0; icount(); ++i) ui.tabWidget->count(); ui.tabWidget->addTab(ui.tabDataFormat, i18n("Data format")); ui.tabWidget->addTab(ui.tabDataPreview, i18n("Preview")); ui.tabWidget->addTab(ui.tabDataPortion, i18n("Data portion to read")); ui.lPreviewLines->show(); ui.sbPreviewLines->show(); ui.lStartColumn->show(); ui.sbStartColumn->show(); ui.lEndColumn->show(); ui.sbEndColumn->show(); showJsonModel(false); switch (fileType) { case AbstractFileFilter::Ascii: break; case AbstractFileFilter::Binary: ui.lStartColumn->hide(); ui.sbStartColumn->hide(); ui.lEndColumn->hide(); ui.sbEndColumn->hide(); break; case AbstractFileFilter::ROOT: ui.tabWidget->removeTab(1); // falls through case AbstractFileFilter::HDF5: case AbstractFileFilter::NETCDF: case AbstractFileFilter::FITS: ui.lFilter->hide(); ui.cbFilter->hide(); // hide global preview tab. we have our own ui.tabWidget->setTabText(0, i18n("Data format && preview")); ui.tabWidget->removeTab(1); ui.tabWidget->setCurrentIndex(0); break; case AbstractFileFilter::Image: ui.lPreviewLines->hide(); ui.sbPreviewLines->hide(); ui.lFilter->hide(); ui.cbFilter->hide(); break; case AbstractFileFilter::NgspiceRawAscii: case AbstractFileFilter::NgspiceRawBinary: ui.lStartColumn->hide(); ui.sbStartColumn->hide(); ui.lEndColumn->hide(); ui.sbEndColumn->hide(); ui.tabWidget->removeTab(0); ui.tabWidget->setCurrentIndex(0); break; case AbstractFileFilter::Json: ui.lFilter->hide(); ui.cbFilter->hide(); showJsonModel(true); break; default: DEBUG("unknown file type"); } m_hdf5OptionsWidget->clear(); m_netcdfOptionsWidget->clear(); m_rootOptionsWidget->clear(); int lastUsedFilterIndex = ui.cbFilter->currentIndex(); ui.cbFilter->clear(); ui.cbFilter->addItem( i18n("Automatic") ); ui.cbFilter->addItem( i18n("Custom") ); //TODO: populate the combobox with the available pre-defined filter settings for the selected type ui.cbFilter->setCurrentIndex(lastUsedFilterIndex); filterChanged(lastUsedFilterIndex); refreshPreview(); } const QStringList ImportFileWidget::selectedHDF5Names() const { return m_hdf5OptionsWidget->selectedHDF5Names(); } const QStringList ImportFileWidget::selectedNetCDFNames() const { return m_netcdfOptionsWidget->selectedNetCDFNames(); } const QStringList ImportFileWidget::selectedFITSExtensions() const { return m_fitsOptionsWidget->selectedFITSExtensions(); } const QStringList ImportFileWidget::selectedROOTNames() const { return m_rootOptionsWidget->selectedROOTNames(); } /*! shows the dialog with the information about the file(s) to be imported. */ void ImportFileWidget::fileInfoDialog() { QStringList files = ui.leFileName->text().split(';'); FileInfoDialog* dlg = new FileInfoDialog(this); dlg->setFiles(files); dlg->exec(); } /*! enables the options if the filter "custom" was chosen. Disables the options otherwise. */ void ImportFileWidget::filterChanged(int index) { // ignore filter for these formats if (ui.cbFileType->currentIndex() == AbstractFileFilter::HDF5 || ui.cbFileType->currentIndex() == AbstractFileFilter::NETCDF || ui.cbFileType->currentIndex() == AbstractFileFilter::Image || ui.cbFileType->currentIndex() == AbstractFileFilter::FITS || ui.cbFileType->currentIndex() == AbstractFileFilter::Json || ui.cbFileType->currentIndex() == AbstractFileFilter::ROOT) { ui.swOptions->setEnabled(true); return; } if (index == 0) { // "automatic" ui.swOptions->setEnabled(false); ui.bSaveFilter->setEnabled(false); } else if (index == 1) { //custom ui.swOptions->setEnabled(true); ui.bSaveFilter->setEnabled(true); } else { // predefined filter settings were selected. //load and show them in the GUI. //TODO } } void ImportFileWidget::refreshPreview() { if (m_suppressRefresh) return; WAIT_CURSOR; QString fileName = ui.leFileName->text(); #ifndef HAVE_WINDOWS if (!fileName.isEmpty() && fileName.at(0) != QDir::separator()) fileName = QDir::homePath() + QDir::separator() + fileName; #endif DEBUG("refreshPreview(): file name = " << fileName.toStdString()); QVector importedStrings; AbstractFileFilter::FileType fileType = (AbstractFileFilter::FileType)ui.cbFileType->currentIndex(); // generic table widget if (fileType == AbstractFileFilter::Ascii || fileType == AbstractFileFilter::Binary || fileType == AbstractFileFilter::Json || fileType == AbstractFileFilter::NgspiceRawAscii || fileType == AbstractFileFilter::NgspiceRawBinary) m_twPreview->show(); else m_twPreview->hide(); int lines = ui.sbPreviewLines->value(); bool ok = true; QTableWidget* tmpTableWidget{nullptr}; QStringList vectorNameList; QVector columnModes; DEBUG("Data File Type: " << ENUM_TO_STRING(AbstractFileFilter, FileType, fileType)); switch (fileType) { case AbstractFileFilter::Ascii: { ui.tePreview->clear(); AsciiFilter* filter = static_cast(this->currentFileFilter()); DEBUG("Data Source Type: " << ENUM_TO_STRING(LiveDataSource, SourceType, currentSourceType())); switch (currentSourceType()) { case LiveDataSource::SourceType::FileOrPipe: { importedStrings = filter->preview(fileName, lines); break; } case LiveDataSource::SourceType::LocalSocket: { QLocalSocket lsocket{this}; DEBUG("Local socket: CONNECT PREVIEW"); lsocket.connectToServer(fileName, QLocalSocket::ReadOnly); if (lsocket.waitForConnected()) { DEBUG("connected to local socket " << fileName.toStdString()); if (lsocket.waitForReadyRead()) importedStrings = filter->preview(lsocket); DEBUG("Local socket: DISCONNECT PREVIEW"); lsocket.disconnectFromServer(); // read-only socket is disconnected immediately (no waitForDisconnected()) } else { DEBUG("failed connect to local socket " << fileName.toStdString() << " - " << lsocket.errorString().toStdString()); } break; } case LiveDataSource::SourceType::NetworkTcpSocket: { QTcpSocket tcpSocket{this}; tcpSocket.connectToHost(host(), port().toInt(), QTcpSocket::ReadOnly); if (tcpSocket.waitForConnected()) { DEBUG("connected to TCP socket"); if ( tcpSocket.waitForReadyRead() ) importedStrings = filter->preview(tcpSocket); tcpSocket.disconnectFromHost(); } else { DEBUG("failed to connect to TCP socket " << " - " << tcpSocket.errorString().toStdString()); } break; } case LiveDataSource::SourceType::NetworkUdpSocket: { QUdpSocket udpSocket{this}; DEBUG("UDP Socket: CONNECT PREVIEW, state = " << udpSocket.state()); udpSocket.bind(QHostAddress(host()), port().toInt()); udpSocket.connectToHost(host(), 0, QUdpSocket::ReadOnly); if (udpSocket.waitForConnected()) { DEBUG(" connected to UDP socket " << host().toStdString() << ':' << port().toInt()); if (!udpSocket.waitForReadyRead(2000) ) DEBUG(" ERROR: not ready for read after 2 sec"); if (udpSocket.hasPendingDatagrams()) { DEBUG(" has pending data"); } else { DEBUG(" has no pending data"); } importedStrings = filter->preview(udpSocket); DEBUG("UDP Socket: DISCONNECT PREVIEW, state = " << udpSocket.state()); udpSocket.disconnectFromHost(); } else { DEBUG("failed to connect to UDP socket " << " - " << udpSocket.errorString().toStdString()); } break; } case LiveDataSource::SourceType::SerialPort: { QSerialPort sPort{this}; DEBUG(" Port: " << serialPort().toStdString() << ", Settings: " << baudRate() << ',' << sPort.dataBits() << ',' << sPort.parity() << ',' << sPort.stopBits()); sPort.setPortName(serialPort()); sPort.setBaudRate(baudRate()); if (sPort.open(QIODevice::ReadOnly)) { if (sPort.waitForReadyRead(2000)) importedStrings = filter->preview(sPort); else { DEBUG(" ERROR: not ready for read after 2 sec"); } sPort.close(); } else { DEBUG(" ERROR: failed to open serial port. error: " << sPort.error()); } break; } case LiveDataSource::SourceType::MQTT: { #ifdef HAVE_MQTT qDebug()<<"preview mqtt, is it ready:"<vectorNames().clear(); QMapIterator i(m_lastMessage); while(i.hasNext()) { i.next(); qDebug()<<"calling ascii mqtt preview"<< importedStrings << " "<mqttPreview(importedStrings, QString(i.value().payload().data()), i.key().name() ); if(importedStrings.isEmpty()) break; } QMapIterator j(m_messageArrived); while(j.hasNext()) { j.next(); qDebug()<<"Set false after preview: "<vectorNames(); columnModes = filter->columnModes(); break; } case AbstractFileFilter::Binary: { ui.tePreview->clear(); BinaryFilter *filter = (BinaryFilter *)this->currentFileFilter(); importedStrings = filter->preview(fileName, lines); tmpTableWidget = m_twPreview; break; } case AbstractFileFilter::Image: { ui.tePreview->clear(); QImage image(fileName); QTextCursor cursor = ui.tePreview->textCursor(); cursor.insertImage(image); RESET_CURSOR; return; } case AbstractFileFilter::HDF5: { HDF5Filter *filter = (HDF5Filter *)this->currentFileFilter(); lines = m_hdf5OptionsWidget->lines(); importedStrings = filter->readCurrentDataSet(fileName, NULL, ok, AbstractFileFilter::Replace, lines); tmpTableWidget = m_hdf5OptionsWidget->previewWidget(); break; } case AbstractFileFilter::NETCDF: { NetCDFFilter *filter = (NetCDFFilter *)this->currentFileFilter(); lines = m_netcdfOptionsWidget->lines(); importedStrings = filter->readCurrentVar(fileName, NULL, AbstractFileFilter::Replace, lines); tmpTableWidget = m_netcdfOptionsWidget->previewWidget(); break; } case AbstractFileFilter::FITS: { FITSFilter* filter = (FITSFilter*)this->currentFileFilter(); lines = m_fitsOptionsWidget->lines(); // update file name (may be any file type) m_fitsOptionsWidget->updateContent(filter, fileName); QString extensionName = m_fitsOptionsWidget->extensionName(&ok); if (!extensionName.isEmpty()) { DEBUG(" extension name = " << extensionName.toStdString()); fileName = extensionName; } DEBUG(" file name = " << fileName.toStdString()); bool readFitsTableToMatrix; importedStrings = filter->readChdu(fileName, &readFitsTableToMatrix, lines); emit checkedFitsTableToMatrix(readFitsTableToMatrix); tmpTableWidget = m_fitsOptionsWidget->previewWidget(); break; } case AbstractFileFilter::Json: { ui.tePreview->clear(); m_jsonOptionsWidget->loadDocument(fileName); JsonFilter *filter = (JsonFilter*)this->currentFileFilter(); m_jsonOptionsWidget->applyFilterSettings(filter, ui.tvJson->currentIndex()); importedStrings = filter->preview(fileName); tmpTableWidget = m_twPreview; columnModes = filter->columnModes(); break; } case AbstractFileFilter::ROOT: { ROOTFilter *filter = (ROOTFilter *)this->currentFileFilter(); lines = m_rootOptionsWidget->lines(); m_rootOptionsWidget->setNBins(filter->binsInCurrentHistogram(fileName)); importedStrings = filter->previewCurrentHistogram( fileName, m_rootOptionsWidget->startBin(), qMin(m_rootOptionsWidget->startBin() + m_rootOptionsWidget->lines() - 1, m_rootOptionsWidget->endBin()) ); tmpTableWidget = m_rootOptionsWidget->previewWidget(); // the last vector element contains the column names vectorNameList = importedStrings.last(); importedStrings.removeLast(); columnModes = QVector(vectorNameList.size(), AbstractColumn::Numeric); break; } case AbstractFileFilter::NgspiceRawAscii: { ui.tePreview->clear(); NgspiceRawAsciiFilter* filter = (NgspiceRawAsciiFilter*)this->currentFileFilter(); importedStrings = filter->preview(fileName, lines); tmpTableWidget = m_twPreview; vectorNameList = filter->vectorNames(); columnModes = filter->columnModes(); break; } case AbstractFileFilter::NgspiceRawBinary: { ui.tePreview->clear(); NgspiceRawBinaryFilter* filter = (NgspiceRawBinaryFilter*)this->currentFileFilter(); importedStrings = filter->preview(fileName, lines); tmpTableWidget = m_twPreview; vectorNameList = filter->vectorNames(); columnModes = filter->columnModes(); break; } } // fill the table widget tmpTableWidget->setRowCount(0); tmpTableWidget->setColumnCount(0); if( !importedStrings.isEmpty() ) { //QDEBUG("importedStrings =" << importedStrings); if (!ok) { // show imported strings as error message tmpTableWidget->setRowCount(1); tmpTableWidget->setColumnCount(1); QTableWidgetItem* item = new QTableWidgetItem(); item->setText(importedStrings[0][0]); tmpTableWidget->setItem(0, 0, item); } else { //TODO: maxrows not used const int rows = qMax(importedStrings.size(), 1); const int maxColumns = 300; tmpTableWidget->setRowCount(rows); for (int i = 0; i < rows; ++i) { // QDEBUG("imported string " << importedStrings[i]); int cols = importedStrings[i].size() > maxColumns ? maxColumns : importedStrings[i].size(); // new if (cols > tmpTableWidget->columnCount()) tmpTableWidget->setColumnCount(cols); for (int j = 0; j < cols; ++j) { QTableWidgetItem* item = new QTableWidgetItem(importedStrings[i][j]); tmpTableWidget->setItem(i, j, item); } } // set header if columnMode available for (int i = 0; i < qMin(tmpTableWidget->columnCount(), columnModes.size()); ++i) { QString columnName = QString::number(i+1); if (i < vectorNameList.size()) columnName = vectorNameList[i]; auto* item = new QTableWidgetItem(columnName + QLatin1String(" {") + ENUM_TO_STRING(AbstractColumn, ColumnMode, columnModes[i]) + QLatin1String("}")); item->setTextAlignment(Qt::AlignLeft); item->setIcon(AbstractColumn::iconForMode(columnModes[i])); tmpTableWidget->setHorizontalHeaderItem(i, item); } } tmpTableWidget->horizontalHeader()->resizeSections(QHeaderView::ResizeToContents); m_fileEmpty = false; } else { m_fileEmpty = true; } emit previewRefreshed(); RESET_CURSOR; } void ImportFileWidget::updateTypeChanged(int idx) { LiveDataSource::UpdateType type = static_cast(idx); switch (type) { case LiveDataSource::UpdateType::TimeInterval: ui.lUpdateInterval->show(); ui.sbUpdateInterval->show(); break; case LiveDataSource::UpdateType::NewData: ui.lUpdateInterval->hide(); ui.sbUpdateInterval->hide(); } } void ImportFileWidget::readingTypeChanged(int idx) { LiveDataSource::ReadingType type = static_cast(idx); if (type == LiveDataSource::ReadingType::TillEnd || type == LiveDataSource::ReadingType::WholeFile) { ui.lSampleSize->hide(); ui.sbSampleSize->hide(); } else { ui.lSampleSize->show(); ui.sbSampleSize->show(); } if (type == LiveDataSource::ReadingType::WholeFile) { ui.lKeepLastValues->hide(); ui.sbKeepNValues->hide(); } else { ui.lKeepLastValues->show(); ui.sbKeepNValues->show(); } } void ImportFileWidget::sourceTypeChanged(int idx) { LiveDataSource::SourceType type = static_cast(idx); switch (type) { case LiveDataSource::SourceType::FileOrPipe: ui.lFileName->show(); ui.leFileName->show(); ui.bFileInfo->show(); ui.bOpen->show(); ui.chbLinkFile->show(); ui.cbBaudRate->hide(); ui.lBaudRate->hide(); ui.lHost->hide(); ui.leHost->hide(); ui.lPort->hide(); ui.lePort->hide(); ui.cbSerialPort->hide(); ui.lSerialPort->hide(); fileNameChanged(ui.leFileName->text()); break; case LiveDataSource::SourceType::LocalSocket: ui.lFileName->show(); ui.leFileName->show(); ui.bOpen->show(); ui.bFileInfo->hide(); ui.cbBaudRate->hide(); ui.lBaudRate->hide(); ui.lHost->hide(); ui.leHost->hide(); ui.lPort->hide(); ui.lePort->hide(); ui.cbSerialPort->hide(); ui.lSerialPort->hide(); ui.chbLinkFile->hide(); ui.gbOptions->setEnabled(true); ui.bManageFilters->setEnabled(true); ui.cbFilter->setEnabled(true); ui.cbFileType->setEnabled(true); break; case LiveDataSource::SourceType::NetworkTcpSocket: case LiveDataSource::SourceType::NetworkUdpSocket: ui.lHost->show(); ui.leHost->show(); ui.lePort->show(); ui.lPort->show(); ui.lBaudRate->hide(); ui.cbBaudRate->hide(); ui.lSerialPort->hide(); ui.cbSerialPort->hide(); ui.lFileName->hide(); ui.leFileName->hide(); ui.bFileInfo->hide(); ui.bOpen->hide(); ui.chbLinkFile->hide(); ui.gbOptions->setEnabled(true); ui.bManageFilters->setEnabled(true); ui.cbFilter->setEnabled(true); ui.cbFileType->setEnabled(true); break; case LiveDataSource::SourceType::SerialPort: ui.lBaudRate->show(); ui.cbBaudRate->show(); ui.lSerialPort->show(); ui.cbSerialPort->show(); ui.lHost->hide(); ui.leHost->hide(); ui.lePort->hide(); ui.lPort->hide(); ui.lFileName->hide(); ui.leFileName->hide(); ui.bFileInfo->hide(); ui.bOpen->hide(); ui.chbLinkFile->hide(); ui.cbFileType->setEnabled(true); ui.gbOptions->setEnabled(true); ui.bManageFilters->setEnabled(true); ui.cbFilter->setEnabled(true); break; case LiveDataSource::SourceType::MQTT: #ifdef HAVE_MQTT ui.lBaudRate->hide(); ui.cbBaudRate->hide(); ui.lSerialPort->hide(); ui.cbSerialPort->hide(); ui.lHost->show(); ui.leHost->show(); ui.lePort->show(); ui.lPort->show(); ui.lFileName->hide(); ui.leFileName->hide(); ui.bFileInfo->hide(); ui.bOpen->hide(); ui.chbLinkFile->hide(); ui.cbFileType->setEnabled(true); ui.leID->hide(); ui.lMqttID->hide(); ui.lePassword->hide(); ui.lPassword->hide(); ui.leUsername->hide(); ui.lUsername->hide(); ui.cbQos->show(); ui.lQos->show(); ui.twTopics->show(); ui.lTopicSearch->show(); ui.leTopics->show(); ui.twSubscriptions->show(); ui.chbAuthentication->show(); ui.chbID->show(); ui.bSubscribe->show(); ui.bUnsubscribe->show(); ui.bConnect->show(); ui.gbOptions->setEnabled(true); ui.bManageFilters->setEnabled(true); ui.cbFilter->setEnabled(true); ui.gbMqttWill->show(); ui.chbWill->show(); ui.chbWillRetain->hide(); ui.cbWillQoS->hide(); ui.cbWillMessageType->hide(); ui.cbWillTopic->hide(); ui.cbWillUpdate->hide(); ui.leWillOwnMessage->hide(); ui.leWillUpdateInterval->setValidator(new QIntValidator(2, 1000000) ); ui.leWillUpdateInterval->hide(); ui.lWillMessageType->hide(); ui.lWillOwnMessage->hide(); ui.lWillQos->hide(); ui.lWillTopic->hide(); ui.lWillUpdate->hide(); ui.lWillUpdateInterval->hide(); ui.lwWillStatistics->hide(); ui.lWillStatistics->hide(); ui.gbManageSubscriptions->setEnabled(false); checkConnectEnable(); if(ui.chbWill->isChecked()) { ui.chbWillRetain->show(); ui.cbWillQoS->show(); ui.cbWillMessageType->show(); ui.cbWillTopic->show(); ui.cbWillUpdate->show(); ui.lWillMessageType->show(); ui.lWillQos->show(); ui.lWillTopic->show(); ui.lWillUpdate->show(); if (ui.cbWillMessageType->currentIndex() == static_cast(MQTTClient::WillMessageType::OwnMessage) ) { ui.leWillOwnMessage->show(); ui.lWillOwnMessage->show(); } else if(ui.cbWillMessageType->currentIndex() == static_cast(MQTTClient::WillMessageType::Statistics) ){ qDebug()<<"source type changed show statistics"; ui.lWillStatistics->show(); ui.lwWillStatistics->show(); } if(ui.cbWillUpdate->currentIndex() == 0) { ui.leWillUpdateInterval->show(); ui.lWillUpdateInterval->show(); } else if (ui.cbWillUpdate->currentIndex() == 1) { ui.leWillUpdateInterval->hide(); ui.lWillUpdateInterval->hide(); } } #endif break; } // "whole file" item const QStandardItemModel* model = qobject_cast(ui.cbReadingType->model()); QStandardItem* item = model->item(LiveDataSource::ReadingType::WholeFile); if (type == LiveDataSource::SourceType::FileOrPipe) item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); else item->setFlags(item->flags() & ~(Qt::ItemIsSelectable | Qt::ItemIsEnabled)); //"update options" groupbox can be deactived for "file and pipe" if the file is invalid. //Activate the groupbox when switching from "file and pipe" to a different source type. if (type != LiveDataSource::SourceType::FileOrPipe) ui.gbUpdateOptions->setEnabled(true); emit sourceTypeChanged(); refreshPreview(); } void ImportFileWidget::initializeAndFillPortsAndBaudRates() { for (int i = 2; i < ui.swOptions->count(); ++i) ui.swOptions->removeWidget(ui.swOptions->widget(i)); const int size = ui.cbFileType->count(); for (int i = 2; i < size; ++i) ui.cbFileType->removeItem(2); ui.cbBaudRate->hide(); ui.lBaudRate->hide(); ui.lHost->hide(); ui.leHost->hide(); ui.lPort->hide(); ui.lePort->hide(); ui.cbSerialPort->hide(); ui.lSerialPort->hide(); ui.cbBaudRate->addItems(LiveDataSource::supportedBaudRates()); ui.cbSerialPort->addItems(LiveDataSource::availablePorts()); ui.tabWidget->removeTab(2); } #ifdef HAVE_MQTT /*! *\brief called when ID checkbox's state is changed, if checked a lineEdit is shown so the user can set the ID * \param state the state of the checbox */ void ImportFileWidget::idChecked(int state) { if (state == 2) { ui.leID->show(); ui.lMqttID->show(); } else if (state == 0) { ui.leID->hide(); ui.lMqttID->hide(); } } /*! *\brief called when authentication checkbox's state is changed, * if checked two lineEdits are shown so the user can set the username and password * * \param state the state of the checbox */ void ImportFileWidget::authenticationChecked(int state) { if(state == 2) { ui.leUsername->show(); ui.lePassword->show(); ui.lPassword->show(); ui.lUsername->show(); } else if (state == 0) { ui.leUsername->hide(); ui.lePassword->hide(); ui.lUsername->hide(); ui.lPassword->hide(); } } /*! *\brief called when the connect/disconnect button is pressed * makes the connection to the given MQTT broker, with the given options * or disconnects from the broker */ void ImportFileWidget::mqttConnection() { if(m_client->state() == QMqttClient::ClientState::Disconnected) { //Check whether the set options are valid and a connection can be made, or not const bool hostSet = !ui.leHost->text().isEmpty(); const bool portSet = !ui.lePort->text().isEmpty(); const bool idUsed = ui.chbID->isChecked(); const bool idSet = !ui.leID->text().isEmpty(); const bool idValid = !(idUsed && !idSet); const bool authenticationUsed = ui.chbAuthentication->isChecked(); const bool usernameSet = !ui.leUsername->text().isEmpty(); const bool passwordSet = !ui.lePassword->text().isEmpty(); const bool authenticationValid = ! (authenticationUsed && ( !usernameSet || !passwordSet) ); const bool valid =hostSet && portSet && idValid && authenticationValid; if (valid) { m_client->setHostname(ui.leHost->text().simplified()); m_client->setPort(ui.lePort->text().toUInt()); if(ui.chbID->isChecked()) m_client->setClientId(ui.leID->text().simplified()); if(ui.chbAuthentication->isChecked()) { m_client->setUsername(ui.leUsername->text().simplified()); m_client->setPassword(ui.lePassword->text().simplified()); } qDebug()<< ui.leHost->text() << " " << m_client->hostname() << " " << m_client->port(); qDebug()<<"Trying to connect"; m_client->connectToHost(); m_connectTimeoutTimer->start(); } } else if (m_client->state() == QMqttClient::ClientState::Connected) { qDebug()<<"Disconnecting from mqtt broker" ; m_client->disconnectFromHost(); } } /*! *\brief called when the client connects to the broker succesfully, it subscribes to every topic (# wildcard) * in order to later list every available topic */ void ImportFileWidget::onMqttConnect() { if(m_client->error() == QMqttClient::NoError) { m_connectTimeoutTimer->stop(); ui.gbManageSubscriptions->setEnabled(true); ui.bConnect->setText("Disconnect"); ui.leHost->setEnabled(false); ui.lePort->setEnabled(false); ui.lePassword->setEnabled(false); ui.leUsername->setEnabled(false); ui.leID->setEnabled(false); ui.cbSourceType->setEnabled(false); ui.chbAuthentication->setEnabled(false); ui.chbID->setEnabled(false); ui.chbRetain->setEnabled(false); QMessageBox::information(this, "Connection successful", "Connection established"); //subscribing to every topic (# wildcard) in order to later list every available topic QMqttTopicFilter globalFilter{"#"}; m_mainSubscription = m_client->subscribe(globalFilter, 1); if(!m_mainSubscription) QMessageBox::information(this, "Couldn't subscribe", "Something went wrong"); } } /*! *\brief called when the client disconnects from the broker succesfully * removes every information about the former connection */ void ImportFileWidget::onMqttDisconnect() { ui.gbManageSubscriptions->setEnabled(false); ui.bConnect->setText("Connect"); ui.leHost->setEnabled(true); ui.leHost->clear(); ui.lePort->setEnabled(true); ui.lePort->clear(); ui.lePassword->setEnabled(true); ui.lePassword->clear(); ui.leUsername->setEnabled(true); ui.leUsername->clear(); ui.leID->setEnabled(true); ui.leID->clear(); ui.twSubscriptions->clear(); ui.twTopics->clear(); ui.chbRetain->setEnabled(true); ui.cbSourceType->setEnabled(true); ui.chbAuthentication->setEnabled(true); ui.chbID->setEnabled(true); m_mqttReadyForPreview = false; m_mqttSubscriptions.clear(); m_topicCompleter = new QCompleter; m_subscriptionCompleter = new QCompleter; m_topicList.clear(); m_searching = false; m_searchTimer->stop(); m_messageArrived.clear(); m_lastMessage.clear(); } /*! *\brief called when the subscribe button is pressed * subscribes to the topic represented by the current item of twTopics */ void ImportFileWidget::mqttSubscribe() { QString name; QTreeWidgetItem *item = ui.twTopics->currentItem(); if(item != nullptr) { QTreeWidgetItem *tempItem = item; //determine the topic name that the current item represents name.prepend(item->text(0)); if(item->childCount() != 0) name.append("/#"); while(tempItem->parent() != nullptr) { tempItem = tempItem->parent(); name.prepend(tempItem->text(0) + "/"); } QList topLevelList = ui.twSubscriptions->findItems(name, Qt::MatchExactly); //check if the subscription already exists if(topLevelList.isEmpty() || topLevelList.first()->parent() != nullptr) { qDebug() << name; bool foundSuperior = false; for(int i = 0; i < ui.twSubscriptions->topLevelItemCount(); ++i) { qDebug()<topLevelItemCount(); //if the new subscirptions contains an already existing one, we remove the inferior one if(checkTopicContains(name, ui.twSubscriptions->topLevelItem(i)->text(0)) && name != ui.twSubscriptions->topLevelItem(i)->text(0)) { qDebug()<<"1"<topLevelItem(i)->text(0); unsubscribeFromTopic(ui.twSubscriptions->topLevelItem(i)->text(0)); i--; continue; } qDebug()<<"checked inferior"; //if there is a subscription containing the new one we set foundSuperior true if(checkTopicContains(ui.twSubscriptions->topLevelItem(i)->text(0), name) && name != ui.twSubscriptions->topLevelItem(i)->text(0)) { foundSuperior = true; qDebug()<<"2"<topLevelItem(i)->text(0); break; } qDebug()<<"checked superior"; } //if there wasn't a superior subscription we can subscribe to the new topic if(!foundSuperior) { qDebug()<<"Adding new topic"; QStringList toplevelName; toplevelName.push_back(name); QTreeWidgetItem* newTopLevelItem = new QTreeWidgetItem(toplevelName); ui.twSubscriptions->addTopLevelItem(newTopLevelItem); QMqttTopicFilter filter {name}; QMqttSubscription *temp_subscription = m_client->subscribe(filter, static_cast (ui.cbQos->currentText().toUInt()) ); if(temp_subscription) { m_mqttSubscriptions.push_back(temp_subscription); connect(temp_subscription, &QMqttSubscription::messageReceived, this, &ImportFileWidget::mqttSubscriptionMessageReceived); emit subscriptionsChanged(); } if(name.endsWith("#")) { //adding every topic that the subscription contains to twSubscriptions addSubscriptionChildren(item, newTopLevelItem); //if an already existing subscription contains a topic that the new subscription also contains //we decompose the already existing subscription //by unsubscribing from its topics, that are present in the new subscription as well QStringList nameList = name.split('/', QString::SkipEmptyParts); QString root = nameList.first(); QVector children; for(int i = 0; i < ui.twSubscriptions->topLevelItemCount(); ++i) { if(ui.twSubscriptions->topLevelItem(i)->text(0).startsWith(root) && name != ui.twSubscriptions->topLevelItem(i)->text(0)) { children.clear(); //get the "leaf" children of the inspected subscription findSubscriptionLeafChildren(children, ui.twSubscriptions->topLevelItem(i)); for(int j = 0; j < children.size(); ++j) { if(checkTopicContains(name, children[j]->text(0))) { //if the new subscription contains a topic, we unsubscribe from it ui.twSubscriptions->setCurrentItem(children[j]); mqttUnsubscribe(); } } } } } manageCommonLevelSubscriptions(); updateWillTopics(); updateSubscriptionCompleter(); } else { QMessageBox::warning(this, "Warning", "You already subscribed to a topic containing this one"); } } else QMessageBox::warning(this, "Warning", "You already subscribed to this topic"); } else QMessageBox::warning(this, "Warning", "You didn't select any item from the Tree Widget"); } /*! *\brief called when the unsubscribe button is pressed * unsubscribes from the topic represented by the current item of twSubscription */ void ImportFileWidget::mqttUnsubscribe() { QTreeWidgetItem* unsubscribeItem = ui.twSubscriptions->currentItem(); if(unsubscribeItem != nullptr) { //if it is a top level item, meaning a topic that we really subscribed to(not one that belongs to a subscription) //we can simply unsubscribe from it if(unsubscribeItem->parent() == nullptr) unsubscribeFromTopic(unsubscribeItem->text(0)); //otherwise we remove the selected item, but subscribe to every other topic, that was contained by //the selected item's parent subscription(top level item of twSubscripitons) else{ while(unsubscribeItem->parent() != nullptr) { for(int i = 0; i < unsubscribeItem->parent()->childCount(); ++i) { if(unsubscribeItem->text(0) != unsubscribeItem->parent()->child(i)->text(0)) { QMqttTopicFilter filter {unsubscribeItem->parent()->child(i)->text(0)}; QMqttSubscription *temp_subscription = m_client->subscribe(filter, static_cast (ui.cbQos->currentText().toUInt()) ); ui.twSubscriptions->addTopLevelItem(unsubscribeItem->parent()->takeChild(i)); if(temp_subscription) { m_mqttSubscriptions.push_back(temp_subscription); connect(temp_subscription, &QMqttSubscription::messageReceived, this, &ImportFileWidget::mqttSubscriptionMessageReceived); emit subscriptionsChanged(); } i--; } } unsubscribeItem = unsubscribeItem->parent(); } unsubscribeFromTopic(unsubscribeItem->text(0)); //check if any common topics were subscribed, if possible merge them manageCommonLevelSubscriptions(); } updateWillTopics(); updateSubscriptionCompleter(); } else QMessageBox::warning(this, "Warning", "You didn't select any item from the Tree Widget"); } /*! *\brief called when the client receives a message * if the message arrived from a new topic, the topic is put in twTopics */ void ImportFileWidget::mqttMessageReceived(const QByteArray &message , const QMqttTopicName &topic) { if(!m_addedTopics.contains(topic.name())) { m_addedTopics.push_back(topic.name()); QStringList name; QChar sep = '/'; QString rootName; if(topic.name().contains(sep)) { + qDebug()<topLevelItemCount(); ++i) { - if(ui.twTopics->topLevelItem(i)->text(0) == list.at(0)) { - topItemIdx = i; - break; + if(!list.isEmpty()) { + rootName = list.at(0); + name.append(list.at(0)); + QTreeWidgetItem* currentItem; + int topItemIdx = -1; + qDebug()<<"check whether the first level of the topic can be found in twTopics"; + //check whether the first level of the topic can be found in twTopics + for(int i = 0; i < ui.twTopics->topLevelItemCount(); ++i) { + if(ui.twTopics->topLevelItem(i)->text(0) == list.at(0)) { + topItemIdx = i; + break; + } } - } - //if not we simply add every level of the topic to the tree - if( topItemIdx < 0) { - currentItem = new QTreeWidgetItem(name); - ui.twTopics->addTopLevelItem(currentItem); - for(int i = 1; i < list.size(); ++i) { - name.clear(); - name.append(list.at(i)); - currentItem->addChild(new QTreeWidgetItem(name)); - currentItem = currentItem->child(0); + //if not we simply add every level of the topic to the tree + if( topItemIdx < 0) { + qDebug()<<"if not we simply add every level of the topic to the tree"; + currentItem = new QTreeWidgetItem(name); + ui.twTopics->addTopLevelItem(currentItem); + for(int i = 1; i < list.size(); ++i) { + name.clear(); + name.append(list.at(i)); + currentItem->addChild(new QTreeWidgetItem(name)); + currentItem = currentItem->child(0); + } } - } - //otherwise we search for the first level that isn't part of the tree, - //then add every level of the topic to the tree from that certain level - else { - currentItem = ui.twTopics->topLevelItem(topItemIdx); - int listIdx = 1; - for(; listIdx < list.size(); ++listIdx) { - QTreeWidgetItem* childItem = nullptr; - bool found = false; - for(int j = 0; j < currentItem->childCount(); ++j) { - childItem = currentItem->child(j); - if(childItem->text(0) == list.at(listIdx)) { - found = true; - currentItem = childItem; + //otherwise we search for the first level that isn't part of the tree, + //then add every level of the topic to the tree from that certain level + else { + qDebug()<<"otherwise we search for the first level"; + currentItem = ui.twTopics->topLevelItem(topItemIdx); + int listIdx = 1; + for(; listIdx < list.size(); ++listIdx) { + QTreeWidgetItem* childItem = nullptr; + bool found = false; + for(int j = 0; j < currentItem->childCount(); ++j) { + childItem = currentItem->child(j); + if(childItem->text(0) == list.at(listIdx)) { + found = true; + currentItem = childItem; + break; + } + } + if(!found) { + qDebug()<<"this is the level that isn't present in the tree "<addChild(new QTreeWidgetItem(name)); - currentItem = currentItem->child(currentItem->childCount() - 1); + qDebug()<<"dd every level to the tree starting with the first level that isn't part of the tree"; + //add every level to the tree starting with the first level that isn't part of the tree + for(; listIdx < list.size(); ++listIdx) { + name.clear(); + name.append(list.at(listIdx)); + currentItem->addChild(new QTreeWidgetItem(name)); + currentItem = currentItem->child(currentItem->childCount() - 1); + } } } } else { + qDebug()<<"no sep"; rootName = topic.name(); name.append(topic.name()); ui.twTopics->addTopLevelItem(new QTreeWidgetItem(name)); } //if a subscribed topic contains the new topic, we have to update twSubscriptions + qDebug()<<"if a subscribed topic contains the new topic, we have to update twSubscriptions"; for(int i = 0; i < ui.twSubscriptions->topLevelItemCount(); ++i) { QStringList subscriptionName = ui.twSubscriptions->topLevelItem(i)->text(0).split('/', QString::SkipEmptyParts); - if (rootName == subscriptionName[0]) { - qDebug()<setCompletionMode(QCompleter::PopupCompletion); m_topicCompleter->setCaseSensitivity(Qt::CaseSensitive); ui.leTopics->setCompleter(m_topicCompleter); } } } /*! *\brief called when 10 seconds passed since the last time the user searched for a certain root in twTopics * enables updating the completer for le */ void ImportFileWidget::topicTimeout() { qDebug()<<"lejart ido"; m_searching = false; m_searchTimer->stop(); } /*! *\brief called when the client receives a message from a subscribed topic (that isn't the "#" wildcard) */ void ImportFileWidget::mqttSubscriptionMessageReceived(const QMqttMessage &msg) { qDebug()<<"message received from: "< i(m_messageArrived); while(i.hasNext()) { i.next(); if(i.value() == false ) { qDebug()<<"Found false: "<show(); ui.cbWillQoS->show(); ui.cbWillMessageType->show(); ui.cbWillTopic->show(); ui.cbWillUpdate->show(); ui.lWillMessageType->show(); ui.lWillQos->show(); ui.lWillTopic->show(); ui.lWillUpdate->show(); if (ui.cbWillMessageType->currentIndex() == static_cast(MQTTClient::WillMessageType::OwnMessage) ) { ui.leWillOwnMessage->show(); ui.lWillOwnMessage->show(); } else if(ui.cbWillMessageType->currentIndex() == static_cast(MQTTClient::WillMessageType::Statistics) ) { ui.lWillStatistics->show(); ui.lwWillStatistics->show(); } if(ui.cbWillUpdate->currentIndex() == 0) { ui.leWillUpdateInterval->show(); ui.lWillUpdateInterval->show(); } } else if (state == Qt::Unchecked) { qDebug()<<"will use unchecked"; ui.chbWillRetain->hide(); ui.cbWillQoS->hide(); ui.cbWillMessageType->hide(); ui.cbWillTopic->hide(); ui.cbWillUpdate->hide(); ui.leWillOwnMessage->hide(); ui.leWillUpdateInterval->hide(); ui.lWillMessageType->hide(); ui.lWillOwnMessage->hide(); ui.lWillQos->hide(); ui.lWillTopic->hide(); ui.lWillUpdate->hide(); ui.lWillUpdateInterval->hide(); ui.lWillStatistics->hide(); ui.lwWillStatistics->hide(); } } /*! *\brief called when the selected will message type is changed, * shows the options for the selected message type, hides the irrelevant ones * * \param type the selected will message type */ void ImportFileWidget::willMessageTypeChanged(int type) { if(static_cast (type) == MQTTClient::WillMessageType::OwnMessage) { ui.leWillOwnMessage->show(); ui.lWillOwnMessage->show(); ui.lWillStatistics->hide(); ui.lwWillStatistics->hide(); } else if(static_cast (type) == MQTTClient::WillMessageType::LastMessage) { ui.leWillOwnMessage->hide(); ui.lWillOwnMessage->hide(); ui.lWillStatistics->hide(); ui.lwWillStatistics->hide(); } else if(static_cast (type) == MQTTClient::WillMessageType::Statistics) { qDebug()<<"will message type changed show statistics"; ui.lWillStatistics->show(); ui.lwWillStatistics->show(); ui.leWillOwnMessage->hide(); ui.lWillOwnMessage->hide(); } } /*! *\brief called when newTopicForWill signal is emitted, * updates the topics that can be selected as the will message's topic */ void ImportFileWidget::updateWillTopics() { QString current = ui.cbWillTopic->currentText(); ui.cbWillTopic->clear(); //Get every leaf subscribed topic QVector children; for(int i = 0; i < ui.twSubscriptions->topLevelItemCount(); ++i) { findSubscriptionLeafChildren(children, ui.twSubscriptions->topLevelItem(i)); } for(int i = 0; i < children.size(); ++i) { ui.cbWillTopic->addItem(children[i]->text(0)); } //Set back the previous value if(!current.isEmpty()) ui.cbWillTopic->setCurrentText(current); } /*! *\brief called when the selected update type for the will message is changed, * shows the options for the selected update type, hides the irrelevant ones * * \param type the selected will update type */ void ImportFileWidget::willUpdateTypeChanged(int updateType) { if(static_cast(updateType) == MQTTClient::WillUpdateType::TimePeriod) { ui.leWillUpdateInterval->show(); ui.lWillUpdateInterval->show(); } else if (static_cast(updateType) == MQTTClient::WillUpdateType::OnClick) { ui.leWillUpdateInterval->hide(); ui.lWillUpdateInterval->hide(); } } /*! *\brief called when the clientError of the MQTT client changes * * \param clientError the current error of the client */ void ImportFileWidget::mqttErrorChanged(QMqttClient::ClientError clientError) { switch (clientError) { case QMqttClient::BadUsernameOrPassword: QMessageBox::warning(this, "Couldn't connect", "Bad username or password"); break; case QMqttClient::IdRejected: QMessageBox::warning(this, "Couldn't connect", "The client ID wasn't accepted"); break; case QMqttClient::ServerUnavailable: QMessageBox::warning(this, "Server unavailable", "The network connection has been established, but the service is unavailable on the broker side."); break; case QMqttClient::NotAuthorized: QMessageBox::warning(this, "Couldn't connect", "The client is not authorized to connect."); break; case QMqttClient::UnknownError: QMessageBox::warning(this, "Unknown MQTT error", "An unknown error occurred."); break; default: break; } } /*! *\brief called when leTopics' text is changed * if the rootName can be found in twTopics, then we scroll it to the top of the tree widget * * \param rootName the current text of leTopics */ void ImportFileWidget::scrollToTopicTreeItem(const QString& rootName) { m_searching = true; m_searchTimer->start(); int topItemIdx = -1; for(int i = 0; i < ui.twTopics->topLevelItemCount(); ++i) if(ui.twTopics->topLevelItem(i)->text(0) == rootName) { topItemIdx = i; break; } if(topItemIdx >= 0) { ui.twTopics->scrollToItem(ui.twTopics->topLevelItem(topItemIdx), QAbstractItemView::ScrollHint::PositionAtTop); } } /*! *\brief called when leSubscriptions' text is changed * if the rootName can be found in twSubscriptions, then we scroll it to the top of the tree widget * * \param rootName the current text of leSubscriptions */ void ImportFileWidget::scrollToSubsriptionTreeItem(const QString& rootName) { m_searching = true; m_searchTimer->start(); int topItemIdx = -1; for(int i = 0; i < ui.twSubscriptions->topLevelItemCount(); ++i) if(ui.twSubscriptions->topLevelItem(i)->text(0) == rootName) { topItemIdx = i; break; } if(topItemIdx >= 0) { ui.twSubscriptions->scrollToItem(ui.twSubscriptions->topLevelItem(topItemIdx), QAbstractItemView::ScrollHint::PositionAtTop); } } /*! *\brief called when any option of the client (host, port, id, etc.) is changed before connecting to the broker, * checks if every option needed for the connection is set, if it is, then enables the connect button */ void ImportFileWidget::checkConnectEnable() { bool authenticationUsed = ui.chbAuthentication->isChecked(); bool idUsed = ui.chbID->isChecked(); bool authenticationFilled = !ui.leUsername->text().isEmpty() && !ui.lePassword->text().isEmpty(); bool idFilled = !ui.leID->text().isEmpty(); bool authenticationOK = !authenticationUsed || (authenticationUsed && authenticationFilled); bool idOK = !idUsed || (idUsed && idFilled); bool hostOK = !ui.leHost->text().isEmpty(); bool portOK = !ui.lePort->text().isEmpty(); bool enable = authenticationOK && idOK && hostOK && portOK; ui.bConnect->setEnabled(enable); } /*! *\brief called when m_connectTimeoutTimer ticks, * meaning that the client couldn't connect to the broker in 5 seconds * disconnects the client, stops the timer, and warns the user */ void ImportFileWidget::mqttConnectTimeout() { m_client->disconnectFromHost(); m_connectTimeoutTimer->stop(); QMessageBox::warning(this, "Warning", "Couldn't connect to the given broker"); } #endif diff --git a/src/kdefrontend/dockwidgets/LiveDataDock.cpp b/src/kdefrontend/dockwidgets/LiveDataDock.cpp index cfb436335..a0f40c084 100644 --- a/src/kdefrontend/dockwidgets/LiveDataDock.cpp +++ b/src/kdefrontend/dockwidgets/LiveDataDock.cpp @@ -1,1718 +1,1720 @@ /*************************************************************************** File : LiveDataDock.cpp Project : LabPlot Description : Dock widget for live data properties -------------------------------------------------------------------- Copyright : (C) 2017 by Fabian Kristof (fkristofszabolcs@gmail.com) Copyright : (C) 2018 by Kovacs Ferencz (kferike98@gmail.com) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "LiveDataDock.h" #include #include #include #include #include #include #ifdef HAVE_MQTT #include #endif LiveDataDock::LiveDataDock(QWidget* parent) : QWidget(parent), #ifdef HAVE_MQTT m_searching(true), m_previousMQTTClient(nullptr), m_searchTimer(new QTimer()), m_interpretMessage(true), #endif m_paused(false) { ui.setupUi(this); ui.bUpdateNow->setIcon(QIcon::fromTheme(QLatin1String("view-refresh"))); connect(ui.bPausePlayReading, &QPushButton::clicked, this, &LiveDataDock::pauseContinueReading); connect(ui.bUpdateNow, &QPushButton::clicked, this, &LiveDataDock::updateNow); connect(ui.sbUpdateInterval, static_cast(&QSpinBox::valueChanged), this, &LiveDataDock::updateIntervalChanged); connect(ui.sbKeepNValues, static_cast(&QSpinBox::valueChanged), this, &LiveDataDock::keepNValuesChanged); connect(ui.sbSampleSize, static_cast(&QSpinBox::valueChanged), this, &LiveDataDock::sampleSizeChanged); connect(ui.cbUpdateType, static_cast(&QComboBox::currentIndexChanged), this, &LiveDataDock::updateTypeChanged); connect(ui.cbReadingType, static_cast(&QComboBox::currentIndexChanged), this, &LiveDataDock::readingTypeChanged); #ifdef HAVE_MQTT m_searchTimer->setInterval(10000); connect(this, &LiveDataDock::newTopic, this, &LiveDataDock::setTopicCompleter); connect(m_searchTimer, &QTimer::timeout, this, &LiveDataDock::topicTimeout); connect(ui.bSubscribe, &QPushButton::clicked, this, &LiveDataDock::addSubscription); connect(ui.bUnsubscribe, &QPushButton::clicked, this, &LiveDataDock::removeSubscription); connect(ui.chbWill, &QCheckBox::stateChanged, this, &LiveDataDock::useWillMessage); connect(ui.cbWillQoS, static_cast(&QComboBox::currentIndexChanged), this, &LiveDataDock::willQoSChanged); connect(ui.chbWillRetain, &QCheckBox::stateChanged, this, &LiveDataDock::willRetainChanged); connect(ui.cbWillTopic, &QComboBox::currentTextChanged, this, &LiveDataDock::willTopicChanged); connect(ui.cbWillMessageType, static_cast(&QComboBox::currentIndexChanged), this, &LiveDataDock::willMessageTypeChanged); connect(ui.leWillOwnMessage, &QLineEdit::textChanged, this, &LiveDataDock::willOwnMessageChanged); connect(ui.cbWillUpdate, static_cast(&QComboBox::currentIndexChanged), this, &LiveDataDock::willUpdateTypeChanged); connect(ui.bWillUpdateNow, &QPushButton::clicked, this, &LiveDataDock::willUpdateNow); connect(ui.leWillUpdateInterval, &QLineEdit::textChanged, this, &LiveDataDock::willUpdateIntervalChanged); connect(ui.lwWillStatistics, &QListWidget::itemChanged, this, &LiveDataDock::statisticsChanged); connect(ui.leTopics, &QLineEdit::textChanged, this, &LiveDataDock::scrollToTopicTreeItem); connect(ui.leSubscriptions, &QLineEdit::textChanged, this, &LiveDataDock::scrollToSubsriptionTreeItem); ui.bSubscribe->setIcon(ui.bSubscribe->style()->standardIcon(QStyle::SP_ArrowRight)); ui.bUnsubscribe->setIcon(ui.bUnsubscribe->style()->standardIcon(QStyle::SP_BrowserStop)); #endif } LiveDataDock::~LiveDataDock() { delete m_searchTimer; QMapIterator clients(m_clients); while(clients.hasNext()) { clients.next(); delete clients.value(); } } #ifdef HAVE_MQTT /*! * \brief Sets the MQTTClients of this dock widget * \param clients */ void LiveDataDock::setMQTTClients(const QList &clients) { m_liveDataSources.clear(); m_mqttClients.clear(); m_mqttClients = clients; const MQTTClient* const fds = clients.at(0); ui.sbUpdateInterval->setValue(fds->updateInterval()); ui.cbUpdateType->setCurrentIndex(static_cast(fds->updateType())); ui.cbReadingType->setCurrentIndex(static_cast(fds->readingType())); if (fds->updateType() == MQTTClient::UpdateType::NewData) { ui.lUpdateInterval->hide(); ui.sbUpdateInterval->hide(); } if (fds->isPaused()) { ui.bPausePlayReading->setText(i18n("Continue reading")); ui.bPausePlayReading->setIcon(QIcon::fromTheme(QLatin1String("media-record"))); } else { ui.bPausePlayReading->setText(i18n("Pause reading")); ui.bPausePlayReading->setIcon(QIcon::fromTheme(QLatin1String("media-playback-pause"))); } ui.sbKeepNValues->setValue(fds->keepNValues()); if (fds->readingType() == MQTTClient::ReadingType::TillEnd) { ui.lSampleSize->hide(); ui.sbSampleSize->hide(); } else ui.sbSampleSize->setValue(fds->sampleSize()); // disable "whole file" when having no file (i.e. socket or port) const QStandardItemModel* model = qobject_cast(ui.cbReadingType->model()); QStandardItem* item = model->item(LiveDataSource::ReadingType::WholeFile); item->setFlags(item->flags() & ~(Qt::ItemIsSelectable | Qt::ItemIsEnabled)); ui.chbWill->hide(); ui.chbWillRetain->hide(); ui.cbWillQoS->hide(); ui.cbWillMessageType->hide(); ui.cbWillTopic->hide(); ui.cbWillUpdate->hide(); ui.leWillOwnMessage->hide(); ui.leWillUpdateInterval->setValidator(new QIntValidator(2, 1000000) ); ui.leWillUpdateInterval->hide(); ui.lWillMessageType->hide(); ui.lWillOwnMessage->hide(); ui.lWillQos->hide(); ui.lWillTopic->hide(); ui.lWillUpdate->hide(); ui.lWillUpdateInterval->hide(); ui.bWillUpdateNow->hide(); ui.lwWillStatistics->hide(); ui.lWillStatistics->hide(); //show MQTT connected options ui.gbManageSubscriptions->show(); ui.bSubscribe->show(); ui.bUnsubscribe->show(); ui.twTopics->show(); ui.leTopics->show(); ui.lTopicSearch->show(); ui.twSubscriptions->show(); ui.lQoS->show(); ui.cbQoS->show(); ui.chbWill->show(); //if there isn't a client with this hostname we instantiate a new one if(m_clients[fds->clientHostName()] == nullptr) { m_clients[fds->clientHostName()] = new QMqttClient(); connect(fds, &MQTTClient::clientAboutToBeDeleted, this, &LiveDataDock::removeClient); connect(m_clients[fds->clientHostName()], &QMqttClient::connected, this, &LiveDataDock::onMQTTConnect); connect(m_clients[fds->clientHostName()], &QMqttClient::messageReceived, this, &LiveDataDock::mqttMessageReceived); m_clients[fds->clientHostName()]->setHostname(fds->clientHostName()); m_clients[fds->clientHostName()]->setPort(fds->clientPort()); if(fds->mqttUseAuthentication()) { m_clients[fds->clientHostName()]->setUsername(fds->clientUserName()); m_clients[fds->clientHostName()]->setPassword(fds->clientPassword()); } if(fds->mqttUseID()) { m_clients[fds->clientHostName()]->setClientId(fds->clientID()); } m_clients[fds->clientHostName()]->connectToHost(); } if(m_previousMQTTClient == nullptr) { connect(fds, &MQTTClient::mqttSubscribed, this, &LiveDataDock::fillSubscriptions); connect(fds, &MQTTClient::mqttTopicsChanged, this, &LiveDataDock::updateWillTopics); } //if the previous MQTTClient's host name was different from the current one we have to disconnect some slots //and clear the tree widgets else if(m_previousMQTTClient->clientHostName() != fds->clientHostName()) { qDebug()<<"At load host name not equal: "<clientHostName()<<" "<clientHostName(); disconnect(m_previousMQTTClient, &MQTTClient::mqttSubscribed, this, &LiveDataDock::fillSubscriptions); disconnect(m_previousMQTTClient, &MQTTClient::mqttTopicsChanged, this, &LiveDataDock::updateWillTopics); disconnect(m_clients[m_previousMQTTClient->clientHostName()], &QMqttClient::messageReceived, this, &LiveDataDock::mqttMessageReceived); connect(m_clients[m_previousMQTTClient->clientHostName()], &QMqttClient::messageReceived, this, &LiveDataDock::mqttMessageReceivedInBackground); disconnect(m_clients[fds->clientHostName()], &QMqttClient::messageReceived, this, &LiveDataDock::mqttMessageReceivedInBackground); ui.twTopics->clear(); //repopulating the tree widget with the already known topics of the client for(int i = 0; i < m_addedTopics[fds->clientHostName()].size(); ++i) { addTopicToTree(m_addedTopics[fds->clientHostName()].at(i)); } //fill subscriptions tree widget ui.twSubscriptions->clear(); fillSubscriptions(); connect(fds, &MQTTClient::mqttSubscribed, this, &LiveDataDock::fillSubscriptions); connect(fds, &MQTTClient::mqttTopicsChanged, this, &LiveDataDock::updateWillTopics); connect(m_clients[fds->clientHostName()], &QMqttClient::messageReceived, this, &LiveDataDock::mqttMessageReceived); } //set will message connected options updateWillTopics(); ui.leWillOwnMessage->setText(fds->willOwnMessage()); ui.leWillUpdateInterval->setText(QString::number(fds->willTimeInterval())); ui.cbWillUpdate->setCurrentIndex(static_cast(fds->willUpdateType()) ); fds->startWillTimer(); ui.cbWillMessageType->setCurrentIndex(static_cast(fds->willMessageType()) ); ui.cbWillQoS->setCurrentIndex(fds->willQoS()); ui.cbWillTopic->setCurrentText(fds->willTopic()); ui.chbWillRetain->setChecked(fds->willRetain()); QVector statitics = fds->willStatistics(); for(int i = 0; i < statitics.count(); ++i) { QListWidgetItem* item = ui.lwWillStatistics->item(static_cast(i)); if(statitics[i]) { item->setCheckState(Qt::Checked); } else { item->setCheckState(Qt::Unchecked); } } //when chbWill's isChecked corresponds with source's m_mqttWillUse it doesn't emit state changed signal, we have to force it bool checked = fds->mqttWillUse(); ui.chbWill->setChecked(!checked); ui.chbWill->setChecked(checked); m_previousMQTTClient = fds; } #endif /*! * \brief Sets the live data sources of this dock widget * \param sources */ void LiveDataDock::setLiveDataSources(const QList& sources) { #ifdef HAVE_MQTT m_mqttClients.clear(); #endif m_liveDataSources = sources; const LiveDataSource* const fds = sources.at(0); ui.sbUpdateInterval->setValue(fds->updateInterval()); ui.cbUpdateType->setCurrentIndex(static_cast(fds->updateType())); ui.cbReadingType->setCurrentIndex(static_cast(fds->readingType())); if (fds->updateType() == LiveDataSource::UpdateType::NewData) { ui.lUpdateInterval->hide(); ui.sbUpdateInterval->hide(); } if (fds->isPaused()) { ui.bPausePlayReading->setText(i18n("Continue Reading")); ui.bPausePlayReading->setIcon(QIcon::fromTheme(QLatin1String("media-record"))); } else { ui.bPausePlayReading->setText(i18n("Pause Reading")); ui.bPausePlayReading->setIcon(QIcon::fromTheme(QLatin1String("media-playback-pause"))); } ui.sbKeepNValues->setValue(fds->keepNValues()); // disable "whole file" when having no file (i.e. socket or port) const QStandardItemModel* model = qobject_cast(ui.cbReadingType->model()); QStandardItem* item = model->item(LiveDataSource::ReadingType::WholeFile); if (fds->sourceType() == LiveDataSource::SourceType::FileOrPipe) item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); else item->setFlags(item->flags() & ~(Qt::ItemIsSelectable | Qt::ItemIsEnabled)); if (fds->readingType() == LiveDataSource::ReadingType::TillEnd || fds->readingType() == LiveDataSource::ReadingType::WholeFile) { ui.lSampleSize->hide(); ui.sbSampleSize->hide(); } else ui.sbSampleSize->setValue(fds->sampleSize()); ui.chbWill->hide(); ui.chbWillRetain->hide(); ui.cbWillQoS->hide(); ui.cbWillMessageType->hide(); ui.cbWillTopic->hide(); ui.cbWillUpdate->hide(); ui.leWillOwnMessage->hide(); ui.leWillUpdateInterval->setValidator(new QIntValidator(2, 1000000) ); ui.leWillUpdateInterval->hide(); ui.lWillMessageType->hide(); ui.lWillOwnMessage->hide(); ui.lWillQos->hide(); ui.lWillTopic->hide(); ui.lWillUpdate->hide(); ui.lWillUpdateInterval->hide(); ui.bWillUpdateNow->hide(); ui.lwWillStatistics->hide(); ui.lWillStatistics->hide(); ui.bSubscribe->hide(); ui.bUnsubscribe->hide(); ui.twTopics->hide(); ui.leTopics->hide(); ui.lTopicSearch->hide(); ui.twSubscriptions->hide(); ui.gbManageSubscriptions->hide(); ui.lQoS->hide(); ui.cbQoS->hide(); } /*! * \brief Modifies the sample size of the live data sources or MQTTClient objects * \param sampleSize */ void LiveDataDock::sampleSizeChanged(int sampleSize) { if(!m_liveDataSources.isEmpty()) { for (auto* source : m_liveDataSources) source->setSampleSize(sampleSize); } #ifdef HAVE_MQTT else if (!m_mqttClients.isEmpty()) { for (auto* client : m_mqttClients) client->setSampleSize(sampleSize); } #endif } /*! * \brief Updates the live data sources now */ void LiveDataDock::updateNow() { if(!m_liveDataSources.isEmpty()) { for (auto* source : m_liveDataSources) source->updateNow(); } #ifdef HAVE_MQTT else if (!m_mqttClients.isEmpty()) { for (auto* client : m_mqttClients) client->updateNow(); } #endif } /*! * \brief LiveDataDock::updateTypeChanged * \param idx */ void LiveDataDock::updateTypeChanged(int idx) { if(!m_liveDataSources.isEmpty()) { DEBUG("LiveDataDock::updateTypeChanged()"); LiveDataSource::UpdateType type = static_cast(idx); switch (type) { case LiveDataSource::UpdateType::TimeInterval: ui.lUpdateInterval->show(); ui.sbUpdateInterval->show(); for (auto* source: m_liveDataSources) { source->setUpdateType(type); source->setUpdateInterval(ui.sbUpdateInterval->value()); source->setFileWatched(false); } break; case LiveDataSource::UpdateType::NewData: ui.lUpdateInterval->hide(); ui.sbUpdateInterval->hide(); for (auto* source: m_liveDataSources) { source->setFileWatched(true); source->setUpdateType(type); } } } #ifdef HAVE_MQTT else if (!m_mqttClients.isEmpty()) { MQTTClient::UpdateType type = static_cast(idx); if (type == MQTTClient::UpdateType::TimeInterval) { ui.lUpdateInterval->show(); ui.sbUpdateInterval->show(); for (auto* client : m_mqttClients) { client->setUpdateType(type); client->setUpdateInterval(ui.sbUpdateInterval->value()); } } else if (type == MQTTClient::UpdateType::NewData) { ui.lUpdateInterval->hide(); ui.sbUpdateInterval->hide(); for (auto* client : m_mqttClients) { client->setUpdateType(type); } } } #endif } /*! * \brief Handles the change of the reading type in the dock widget * \param idx */ void LiveDataDock::readingTypeChanged(int idx) { if(!m_liveDataSources.isEmpty()) { LiveDataSource::ReadingType type = static_cast(idx); if (type == LiveDataSource::ReadingType::TillEnd) { ui.lSampleSize->hide(); ui.sbSampleSize->hide(); } else { ui.lSampleSize->show(); ui.sbSampleSize->show(); } for (auto* source : m_liveDataSources) source->setReadingType(type); } #ifdef HAVE_MQTT else if (!m_mqttClients.isEmpty()) { MQTTClient::ReadingType type = static_cast(idx); if (type == MQTTClient::ReadingType::TillEnd) { ui.lSampleSize->hide(); ui.sbSampleSize->hide(); } else { ui.lSampleSize->show(); ui.sbSampleSize->show(); } for (auto* client : m_mqttClients) client->setReadingType(type); } #endif } /*! * \brief Modifies the update interval of the live data sources * \param updateInterval */ void LiveDataDock::updateIntervalChanged(int updateInterval) { if(!m_liveDataSources.isEmpty()) { for (auto* source : m_liveDataSources) source->setUpdateInterval(updateInterval); } #ifdef HAVE_MQTT else if (!m_mqttClients.isEmpty()) { for (auto* client : m_mqttClients) client->setUpdateInterval(updateInterval); } #endif } /*! * \brief Modifies the number of samples to keep in each of the live data sources * \param keepNValues */ void LiveDataDock::keepNValuesChanged(const int keepNValues) { if(!m_liveDataSources.isEmpty()) { for (auto* source : m_liveDataSources) source->setKeepNValues(keepNValues); } #ifdef HAVE_MQTT else if (!m_mqttClients.isEmpty()) { for (auto* client : m_mqttClients) client->setKeepNValues(keepNValues); } #endif } /*! * \brief Pauses the reading of the live data source */ void LiveDataDock::pauseReading() { if(!m_liveDataSources.isEmpty()) { for (auto* source: m_liveDataSources) source->pauseReading(); } #ifdef HAVE_MQTT else if (!m_mqttClients.isEmpty()) { for (auto* client : m_mqttClients) client->pauseReading(); } #endif } /*! * \brief Continues the reading of the live data source */ void LiveDataDock::continueReading() { if(!m_liveDataSources.isEmpty()) { for (auto* source: m_liveDataSources) source->continueReading(); } #ifdef HAVE_MQTT else if (!m_mqttClients.isEmpty()) { for (auto* client : m_mqttClients) client->continueReading(); } #endif } /*! * \brief Handles the pausing/continuing of reading of the live data source */ void LiveDataDock::pauseContinueReading() { m_paused = !m_paused; if (m_paused) { pauseReading(); ui.bPausePlayReading->setText(i18n("Continue Reading")); ui.bPausePlayReading->setIcon(QIcon::fromTheme(QLatin1String("media-record"))); } else { continueReading(); ui.bPausePlayReading->setText(i18n("Pause Reading")); ui.bPausePlayReading->setIcon(QIcon::fromTheme(QLatin1String("media-playback-pause"))); } } #ifdef HAVE_MQTT /*! *\brief called when use will message checkbox's state is changed, *if state is checked it shows the options regarding the will message * and also sets the mqttUseWill according to state for every client in m_mqttClients * * \param state the state of the checbox */ void LiveDataDock::useWillMessage(int state) { qDebug()<<"will checkstate changed" <setMqttWillUse(true); ui.chbWillRetain->show(); ui.cbWillQoS->show(); ui.cbWillMessageType->show(); ui.cbWillTopic->show(); ui.cbWillUpdate->show(); ui.lWillMessageType->show(); ui.lWillQos->hide(); ui.lWillTopic->show(); ui.lWillUpdate->show(); if (ui.cbWillMessageType->currentIndex() == (int)MQTTClient::WillMessageType::OwnMessage) { ui.leWillOwnMessage->show(); ui.lWillOwnMessage->show(); } else if(ui.cbWillMessageType->currentIndex() == (int)MQTTClient::WillMessageType::Statistics){ ui.lWillStatistics->show(); ui.lwWillStatistics->show(); } if(ui.cbWillUpdate->currentIndex() == static_cast(MQTTClient::WillUpdateType::TimePeriod)) { ui.leWillUpdateInterval->show(); ui.lWillUpdateInterval->show(); } else if (ui.cbWillUpdate->currentIndex() == static_cast(MQTTClient::WillUpdateType::OnClick)) ui.bWillUpdateNow->show(); } else if (state == Qt::Unchecked) { for (auto* source: m_mqttClients) source->setMqttWillUse(false); ui.chbWillRetain->hide(); ui.cbWillQoS->hide(); ui.cbWillMessageType->hide(); ui.cbWillTopic->hide(); ui.cbWillUpdate->hide(); ui.leWillOwnMessage->hide(); ui.leWillUpdateInterval->hide(); ui.lWillMessageType->hide(); ui.lWillOwnMessage->hide(); ui.lWillQos->hide(); ui.lWillTopic->hide(); ui.lWillUpdate->hide(); ui.lWillUpdateInterval->hide(); ui.bWillUpdateNow->hide(); ui.lWillStatistics->hide(); ui.lwWillStatistics->hide(); } } /*! *\brief called when will message's QoS is changed * sets the will QoS level for every client in m_mqttClients * * \param QoS the QoS level of the will message */ void LiveDataDock::willQoSChanged(int QoS) { for (auto* source: m_mqttClients) source->setWillQoS(QoS); } /*! *\brief called when will message's retain flag is changed * sets the retain flag for the will message in every client in m_mqttClients * * \param state the state of the will retain chechbox */ void LiveDataDock::willRetainChanged(int state) { if(state == Qt::Checked) { for (auto* source: m_mqttClients) source->setWillRetain(true); } else if (state == Qt::Unchecked) { for (auto* source: m_mqttClients) source->setWillRetain(false); } } /*! *\brief called when will topic combobox's current item is changed * sets the will topic for every client in m_mqttClients * * \param topic the current text of cbWillTopic */ void LiveDataDock::willTopicChanged(const QString& topic) { qDebug()<<"topic changed" << topic; for (auto* source: m_mqttClients) { if(source->willTopic() != topic) source->clearLastMessage(); source->setWillTopic(topic); } } /*! *\brief called when the selected will message type is changed, * shows the options for the selected message type, hides the irrelevant onesd * sets the will message type for every client in m_mqttClients * * \param type the selected will message type */ void LiveDataDock::willMessageTypeChanged(int type) { for (auto* source: m_mqttClients) source->setWillMessageType(static_cast (type)); if(static_cast (type) == MQTTClient::WillMessageType::OwnMessage) { ui.leWillOwnMessage->show(); ui.lWillOwnMessage->show(); ui.lWillStatistics->hide(); ui.lwWillStatistics->hide(); } else if(static_cast (type) == MQTTClient::WillMessageType::LastMessage) { ui.leWillOwnMessage->hide(); ui.lWillOwnMessage->hide(); ui.lWillStatistics->hide(); ui.lwWillStatistics->hide(); } else if(static_cast (type) == MQTTClient::WillMessageType::Statistics) { ui.lWillStatistics->show(); ui.lwWillStatistics->show(); ui.leWillOwnMessage->hide(); ui.lWillOwnMessage->hide(); } } /*! *\brief called when the will own message is changed * sets the will own message for every client in m_mqttClients * * \param message the will message given by the user */ void LiveDataDock::willOwnMessageChanged(const QString& message) { for (auto* source: m_mqttClients) source->setWillOwnMessage(message); } /*! *\brief called when the mqttTopicsChanged signal of a MQTTClient from m_mqttClients is emitted * updates the content of the cbWillTopic with every topic belonging to the MQTTClient */ void LiveDataDock::updateWillTopics() { ui.cbWillTopic->clear(); const MQTTClient* const fds = m_mqttClients.at(0); QVector topics = fds->topicNames(); if(!topics.isEmpty()) { for(int i = 0; i < topics.count(); i++) { qDebug()<<"Live Data Dock: updating will topics: "<addItem(topics[i]); } if(!fds->willTopic().isEmpty()) ui.cbWillTopic->setCurrentText(fds->willTopic()); } else qDebug()<<"Topic Vector Empty"; } /*! *\brief called when the selected update type for the will message is changed, * shows the options for the selected update type, hides the irrelevant ones * sets the will update type for every client in m_mqttClients * * \param type the selected will update type */ void LiveDataDock::willUpdateTypeChanged(int updateType) { qDebug()<<"Update type changed" << updateType; for (auto* source: m_mqttClients) source->setWillUpdateType(static_cast(updateType)); if(static_cast(updateType) == MQTTClient::WillUpdateType::TimePeriod) { ui.bWillUpdateNow->hide(); ui.leWillUpdateInterval->show(); ui.lWillUpdateInterval->show(); for (auto* source: m_mqttClients) { source->setWillTimeInterval(ui.leWillUpdateInterval->text().toInt()); source->startWillTimer(); } } else if (static_cast(updateType) == MQTTClient::WillUpdateType::OnClick) { ui.bWillUpdateNow->show(); ui.leWillUpdateInterval->hide(); ui.lWillUpdateInterval->hide(); //if update type is on click we stop the will timer for (auto* source: m_mqttClients) source->stopWillTimer(); } } /*! *\brief called when the will update now button is pressed * updates the will message of every client in m_mqttClients */ void LiveDataDock::willUpdateNow() { for (auto* source: m_mqttClients) source->updateWillMessage(); } /*! *\brief called when the update interval for will message is changed, * sets the will update interval for every client in m_mqttClients, then starts the will timer for each one * * \param interval the new will update interval */ void LiveDataDock::willUpdateIntervalChanged(const QString& interval) { qDebug()<<"Update interval changed " <setWillTimeInterval(interval.toInt()); source->startWillTimer(); } } /*! *\brief called when the check state of a ListWidget item is changed * adds or removes the statistic represented by the item from every client in m_mqttClients * * \param item the ListWidgetItem, the check state of which was changed */ void LiveDataDock::statisticsChanged(QListWidgetItem *item) { //determine the index of the item int idx = -1; for(int i = 0; i < ui.lwWillStatistics->count(); i++) if(item->text() == ui.lwWillStatistics->item(i)->text()) { idx = i; break; } //if it's checked we add it if(item->checkState() == Qt::Checked) { if(idx >= 0) { for (auto* source: m_mqttClients) source->addWillStatistics(static_cast(idx) ); } } //otherwise remove it else { if(idx >= 0){ for (auto* source: m_mqttClients) source->removeWillStatistics(static_cast(idx) ); } } } /*! *\brief called when the client connects to the broker succesfully, it subscribes to every topic (# wildcard) * in order to later list every available topic */ void LiveDataDock::onMQTTConnect() { QMqttTopicFilter globalFilter{"#"}; QMqttSubscription * subscription = m_clients[m_mqttClients.first()->clientHostName()]->subscribe(globalFilter, 1); if(!subscription) qDebug()<<"Couldn't make global subscription in LiveDataDock"; } /*! *\brief called when the client receives a message * if the message arrived from a new topic, the topic is put in twTopics */ void LiveDataDock::mqttMessageReceived(const QByteArray& message, const QMqttTopicName& topic) { if(!m_addedTopics[m_mqttClients.first()->clientHostName()].contains(topic.name())) { m_addedTopics[m_mqttClients.first()->clientHostName()].push_back(topic.name()); addTopicToTree(topic.name()); } } /*! *\brief called when the subscribe button is pressed * subscribes to the topic represented by the current item of twTopics in every client from m_mqttClients */ void LiveDataDock::addSubscription() { QString name; QTreeWidgetItem *item = ui.twTopics->currentItem(); if(item != nullptr) { QTreeWidgetItem *tempItem = item; //determine the topic name that the current item represents name.prepend(item->text(0)); if(item->childCount() != 0) name.append("/#"); while(tempItem->parent() != nullptr) { tempItem = tempItem->parent(); name.prepend(tempItem->text(0) + "/"); } //check if the subscription already exists QList topLevelList = ui.twSubscriptions->findItems(name, Qt::MatchExactly); if(topLevelList.isEmpty() || topLevelList.first()->parent() != nullptr) { qDebug() << name; bool foundSuperior = false; for(int i = 0; i < ui.twSubscriptions->topLevelItemCount(); ++i) { qDebug()<topLevelItemCount(); //if the new subscriptions contains an already existing one, we remove the inferior one if(checkTopicContains(name, ui.twSubscriptions->topLevelItem(i)->text(0)) && name != ui.twSubscriptions->topLevelItem(i)->text(0)) { qDebug()<<"1"<topLevelItem(i)->text(0); ui.twSubscriptions->topLevelItem(i)->takeChildren(); ui.twSubscriptions->takeTopLevelItem(i); i--; continue; } qDebug()<<"checked inferior"; //if there is a subscription containing the new one we set foundSuperior true if(checkTopicContains(ui.twSubscriptions->topLevelItem(i)->text(0), name) && name != ui.twSubscriptions->topLevelItem(i)->text(0)) { foundSuperior = true; qDebug()<<"2"<topLevelItem(i)->text(0); break; } qDebug()<<"checked superior"; } //if there wasn't a superior subscription we can subscribe to the new topic if(!foundSuperior) { qDebug()<<"Adding new topic"; QStringList toplevelName; toplevelName.push_back(name); QTreeWidgetItem* newTopLevelItem = new QTreeWidgetItem(toplevelName); ui.twSubscriptions->addTopLevelItem(newTopLevelItem); if(name.endsWith("#")) { //adding every topic that the subscription contains to twSubscriptions addSubscriptionChildren(item, newTopLevelItem); } //subscribe in every MQTTClient for (auto* source: m_mqttClients) { source->addMQTTSubscription(name, ui.cbQoS->currentIndex()); } if(name.endsWith("#")) { //if an already existing subscription contains a topic that the new subscription also contains //we decompose the already existing subscription //by unsubscribing from its topics, that are present in the new subscription as well QStringList nameList = name.split('/', QString::SkipEmptyParts); QString root = nameList.first(); QVector children; for(int i = 0; i < ui.twSubscriptions->topLevelItemCount(); ++i) { if(ui.twSubscriptions->topLevelItem(i)->text(0).startsWith(root) && name != ui.twSubscriptions->topLevelItem(i)->text(0)) { children.clear(); //get the "leaf" children of the inspected subscription findSubscriptionLeafChildren(children, ui.twSubscriptions->topLevelItem(i)); for(int j = 0; j < children.size(); ++j) { if(checkTopicContains(name, children[j]->text(0))) { qDebug()<text(0); //if the new subscription contains a topic, we unsubscribe from it QTreeWidgetItem* unsubscribeItem = children[j]; while(unsubscribeItem->parent() != nullptr) { for(int i = 0; i < unsubscribeItem->parent()->childCount(); ++i) { qDebug()<parent()->childCount(); if(unsubscribeItem->text(0) != unsubscribeItem->parent()->child(i)->text(0)) { //add topic as subscription to every client for (auto* source: m_mqttClients) { source->addBeforeRemoveSubscription(unsubscribeItem->parent()->child(i)->text(0), ui.cbQoS->currentIndex()); } //also add it to twSubscripitons ui.twSubscriptions->addTopLevelItem(unsubscribeItem->parent()->takeChild(i)); i--; } else { //before we remove the topic, we reparent it to the new subscription //so no data is lost for (auto* source: m_mqttClients) { source->reparentTopic(unsubscribeItem->text(0), name); } } } unsubscribeItem = unsubscribeItem->parent(); } qDebug()<<"Remove: "<text(0); //Remove topic/subscription for (auto* source: m_mqttClients) { source->removeMQTTSubscription(unsubscribeItem->text(0)); } ui.twSubscriptions->takeTopLevelItem(ui.twSubscriptions->indexOfTopLevelItem(unsubscribeItem)); } } } } } manageCommonLevelSubscriptions(); updateSubscriptionCompleter(); } else { QMessageBox::warning(this, "Warning", "You already subscribed to a topic containing this one"); } } else QMessageBox::warning(this, "Warning", "You already subscribed to this topic"); } else QMessageBox::warning(this, "Warning", "You didn't select any item from the Tree Widget"); } /*! *\brief called when the unsubscribe button is pressed * unsubscribes from the topic represented by the current item of twSubscription in every client from m_mqttClients */ void LiveDataDock::removeSubscription() { QTreeWidgetItem* unsubscribeItem = ui.twSubscriptions->currentItem(); if(unsubscribeItem != nullptr) { //if it is a top level item, meaning a topic that we really subscribed to(not one that belongs to a subscription) //we can simply unsubscribe from it if(unsubscribeItem->parent() == nullptr) { for (auto* source: m_mqttClients) { source->removeMQTTSubscription(unsubscribeItem->text(0)); } ui.twSubscriptions->takeTopLevelItem(ui.twSubscriptions->indexOfTopLevelItem(unsubscribeItem)); } //otherwise we remove the selected item, but subscribe to every other topic, that was contained by //the selected item's parent subscription(top level item of twSubscripitons) else{ while(unsubscribeItem->parent() != nullptr) { for(int i = 0; i < unsubscribeItem->parent()->childCount(); ++i) { if(unsubscribeItem->text(0) != unsubscribeItem->parent()->child(i)->text(0)) { //add topic as subscription to every client for (auto* source: m_mqttClients) { source->addBeforeRemoveSubscription(unsubscribeItem->parent()->child(i)->text(0), ui.cbQoS->currentIndex()); } ui.twSubscriptions->addTopLevelItem(unsubscribeItem->parent()->takeChild(i)); i--; } } unsubscribeItem = unsubscribeItem->parent(); } //remove topic/subscription from every client for (auto* source: m_mqttClients) { source->removeMQTTSubscription(unsubscribeItem->text(0)); } ui.twSubscriptions->takeTopLevelItem(ui.twSubscriptions->indexOfTopLevelItem(unsubscribeItem)); //check if any common topics were subscribed, if possible merge them manageCommonLevelSubscriptions(); } updateSubscriptionCompleter(); } else QMessageBox::warning(this, "Warning", "You didn't select any item from the Tree Widget"); } /*! *\brief called when a new topic is added to the tree(twTopics) * appends the topic's root to the topicList if it isn't in the list already * then sets the completer for leTopics */ void LiveDataDock::setTopicCompleter(const QString& topic) { if(!m_searching) { if(!m_topicList[m_mqttClients.first()->clientHostName()].contains(topic)) { m_topicList[m_mqttClients.first()->clientHostName()].append(topic); m_topicCompleter = new QCompleter(m_topicList[m_mqttClients.first()->clientHostName()], this); m_topicCompleter->setCompletionMode(QCompleter::PopupCompletion); m_topicCompleter->setCaseSensitivity(Qt::CaseSensitive); ui.leTopics->setCompleter(m_topicCompleter); } } } /*! *\brief Updates the completer for leSubscriptions */ void LiveDataDock::updateSubscriptionCompleter() { QStringList subscriptionList; QVector subscriptions = m_mqttClients.first()->mqttSubscriptions(); if(!subscriptions.isEmpty()) { for(int i = 0; i < subscriptions.size(); ++i) { subscriptionList.append(subscriptions[i]); } m_subscriptionCompleter = new QCompleter(subscriptionList, this); m_subscriptionCompleter->setCompletionMode(QCompleter::PopupCompletion); m_subscriptionCompleter->setCaseSensitivity(Qt::CaseSensitive); ui.leSubscriptions->setCompleter(m_subscriptionCompleter); } else { ui.leSubscriptions->setCompleter(0); } } /*! *\brief called when 10 seconds passed since the last time the user searched for a certain root in twTopics * enables updating the completer for le */ void LiveDataDock::topicTimeout() { qDebug()<<"lejart ido"; m_searching = false; m_searchTimer->stop(); } /*! *\brief called when a new the host name of the MQTTClients from m _mqttClients changes * or when the MQTTClients initialize their subscriptions * Fills twSubscriptions with the subscriptions of the MQTTClient */ void LiveDataDock::fillSubscriptions() { const MQTTClient* const fds = m_mqttClients.at(0); ui.twSubscriptions->clear(); QVector subscriptions = fds->mqttSubscriptions(); for (int i = 0; i < subscriptions.count(); ++i) { QStringList name; name.append(subscriptions[i]); bool found = false; for(int j = 0; j < ui.twSubscriptions->topLevelItemCount(); ++j) { if(ui.twSubscriptions->topLevelItem(j)->text(0) == subscriptions[i]) { found = true; break; } } if(!found) { qDebug()<<"add:" << subscriptions[i]; //Add the subscription to the tree widget QTreeWidgetItem* newItem = new QTreeWidgetItem(name); ui.twSubscriptions->addTopLevelItem(newItem); name.clear(); name = subscriptions[i].split('/', QString::SkipEmptyParts); //find the corresponding "root" item in twTopics QTreeWidgetItem* topic = nullptr; for(int j = 0; j < ui.twTopics->topLevelItemCount(); ++j) { if(ui.twTopics->topLevelItem(j)->text(0) == name[0]) { qDebug()<<"found top level topic: "<topLevelItem(j); break; } } //restore the children of the subscription if(topic != nullptr && topic->childCount() > 0) { qDebug()<<"restoring Children"; restoreSubscriptionChildren(topic, newItem, name, 1); } } } m_searching = false; } /*! *\brief Checks if a topic contains another one * * \param superior the name of a topic * \param inferior the name of a topic * \return true if superior is equal to or contains(if superior contains wildcards) inferior, * false otherwise */ bool LiveDataDock::checkTopicContains(const QString &superior, const QString &inferior) { if (superior == inferior) return true; else { if(superior.contains("/")) { QStringList superiorList = superior.split('/', QString::SkipEmptyParts); QStringList inferiorList = inferior.split('/', QString::SkipEmptyParts); //a longer topic can't contain a shorter one if(superiorList.size() > inferiorList.size()) return false; bool ok = true; for(int i = 0; i < superiorList.size(); ++i) { if(superiorList.at(i) != inferiorList.at(i)) { if((superiorList.at(i) != "+") && !(superiorList.at(i) == "#" && i == superiorList.size() - 1)) { qDebug() <start(); qDebug()<topLevelItemCount(); ++i) if(ui.twTopics->topLevelItem(i)->text(0) == rootName) { topItemIdx = i; break; } if(topItemIdx >= 0) { ui.twTopics->scrollToItem(ui.twTopics->topLevelItem(topItemIdx), QAbstractItemView::ScrollHint::PositionAtTop); } } /*! *\brief called when leSubscriptions' text is changed * if the rootName can be found in twSubscriptions, then we scroll it to the top of the tree widget * * \param rootName the current text of leSubscriptions */ void LiveDataDock::scrollToSubsriptionTreeItem(const QString& rootName) { m_searching = true; m_searchTimer->start(); int topItemIdx = -1; for(int i = 0; i < ui.twSubscriptions->topLevelItemCount(); ++i) if(ui.twSubscriptions->topLevelItem(i)->text(0) == rootName) { topItemIdx = i; break; } if(topItemIdx >= 0) { ui.twSubscriptions->scrollToItem(ui.twSubscriptions->topLevelItem(topItemIdx), QAbstractItemView::ScrollHint::PositionAtTop); } } /*! *\brief Returns the "+" wildcard containing topic name, which includes the given topic names * * \param first the name of a topic * \param second the name of a topic * \return The name of the common topic, if it exists, otherwise "" */ QString LiveDataDock::checkCommonLevel(const QString& first, const QString& second) { qDebug()< 0 && differIndex < firstList.size() -1) { for(int j = differIndex +1; j < firstList.size(); ++j) { if(firstList.at(j) != secondtList.at(j)) { differ = true; break; } } } else differ = true; if(!differ) { for(int i = 0; i < firstList.size(); ++i) { if(i != differIndex) { commonTopic.append(firstList.at(i)); } else { //we put "+" wildcard at the level where they differ commonTopic.append("+"); } if(i != firstList.size() - 1) commonTopic.append("/"); } } } } qDebug() << "Common topic: "<childCount() > 0) { for(int i = 0; i < topic->childCount(); ++i) { QTreeWidgetItem* temp = topic->child(i); QString name; //if it has children, then we add it as a # wildcrad containing topic if(topic->child(i)->childCount() > 0) { name.append(temp->text(0) + "/#"); while(temp->parent() != nullptr) { temp = temp->parent(); name.prepend(temp->text(0) + "/"); } } //if not then we simply add the topic itself else { name.append(temp->text(0)); while(temp->parent() != nullptr) { temp = temp->parent(); name.prepend(temp->text(0) + "/"); } } QStringList nameList; nameList.append(name); QTreeWidgetItem* childItem = new QTreeWidgetItem(nameList); subscription->addChild(childItem); //we use the function recursively on the given item addSubscriptionChildren(topic->child(i), childItem); } } } /*! *\brief Restores the children of a top level item in twSubscriptions if it contains wildcards * * \param topic pointer to a top level item in twTopics wich represents the root of the subscription topic * \param subscription pointer to a top level item in twSubscriptions, this is the item whose children will be restored * \param list QStringList containing the levels of the subscription topic * \param level the level's number which is being investigated */ void LiveDataDock::restoreSubscriptionChildren(QTreeWidgetItem * topic, QTreeWidgetItem * subscription, const QStringList& list, int level) { if(list[level] != "+" && list[level] != "#" && level < list.size() - 1) { for(int i = 0; i < topic->childCount(); ++i) { //if the current level isn't + or # wildcard we recursively continue with the next level if(topic->child(i)->text(0) == list[level]) { restoreSubscriptionChildren(topic->child(i), subscription, list, level + 1); break; } } } else if (list[level] == "+") { for(int i = 0; i < topic->childCount(); ++i) { //determine the name of the topic, contained by the subscription QString name; name.append(topic->child(i)->text(0)); for(int j = level + 1; j < list.size(); ++j) { name.append("/" + list[j]); } QTreeWidgetItem* temp = topic->child(i); while(temp->parent() != nullptr) { temp = temp->parent(); name.prepend(temp->text(0) + "/"); } //Add the topic as child of the subscription QStringList nameList; nameList.append(name); QTreeWidgetItem* newItem = new QTreeWidgetItem(nameList); subscription->addChild(newItem); //Continue adding children recursively to the new item restoreSubscriptionChildren(topic->child(i), newItem, list, level + 1); } } else if (list[level] == "#") { //add the children of the # wildcard containing subscription addSubscriptionChildren(topic, subscription); } } /*! *\brief Returns the index of level where the two topic names differ, if there is a common topic for them * * \param first the name of a topic * \param second the name of a topic * \return The index of the unequal level, if there is a common topic, otherwise -1 */ int LiveDataDock::commonLevelIndex(const QString& first, const QString& second) { qDebug()< 0) { for(int j = differIndex +1; j < firstList.size(); ++j) { if(firstList.at(j) != secondtList.at(j)) { differ = true; break; } } } else differ = true; if(!differ) { for(int i = 0; i < firstList.size(); ++i) { if(i != differIndex) commonTopic.append(firstList.at(i)); else commonTopic.append("+"); if(i != firstList.size() - 1) commonTopic.append("/"); } } } } //if there is a common topic we return the differIndex if(!commonTopic.isEmpty()) return differIndex; else return -1; } /*! *\brief Fills the children vector, with the root item's (twSubscriptions) leaf children (meaning no wildcard containing topics) * * \param children vector of TreeWidgetItem pointers * \param root pointer to a TreeWidgetItem of twSubscriptions */ void LiveDataDock::findSubscriptionLeafChildren(QVector& children, QTreeWidgetItem* root) { if(root->childCount() == 0) { children.push_back(root); } else { for(int i = 0; i < root->childCount(); ++i) { findSubscriptionLeafChildren(children, root->child(i)); } } } /*! *\brief Returns the amount of topics that the "+" wildcard will replace in the level position * * \param levelIdx the level currently being investigated * \param level the level where the new + wildcard will be placed * \param commonList the topic name split into levels * \param currentItem pointer to a TreeWidgetItem which represents the parent of the level * represented by levelIdx * \return returns the childCount, or -1 if some topics already represented by + wildcard have different * amount of children */ int LiveDataDock::checkCommonChildCount(int levelIdx, int level, QStringList& commonList, QTreeWidgetItem* currentItem) { //we recursively check the number of children, until we get to level-1 if(levelIdx < level - 1) { if(commonList[levelIdx] != "+") { for(int j = 0; j < currentItem->childCount(); ++j) { if(currentItem->child(j)->text(0) == commonList[levelIdx]) { //if the level isn't represented by + wildcard we simply return the amount of children of the corresponding item, recursively return checkCommonChildCount(levelIdx + 1, level, commonList, currentItem->child(j)); } } } else { int childCount = -1; bool ok = true; //otherwise we check if every + wildcard represented topic has the same number of children, recursively for(int j = 0; j < currentItem->childCount(); ++j) { int temp = checkCommonChildCount(levelIdx + 1, level, commonList, currentItem->child(j)); if((j > 0) && (temp != childCount)) { ok = false; break; } childCount = temp; } //if yes we return this number, otherwise -1 if(ok) return childCount; else return -1; } } else if (levelIdx == level - 1) { if(commonList[levelIdx] != "+") { for(int j = 0; j < currentItem->childCount(); ++j) { if(currentItem->child(j)->text(0) == commonList[levelIdx]) { //if the level isn't represented by + wildcard we simply return the amount of children of the corresponding item return currentItem->child(j)->childCount(); } } } else { int childCount = -1; bool ok = true; //otherwise we check if every + wildcard represented topic has the same number of children for(int j = 0; j < currentItem->childCount(); ++j) { if((j > 0) && (currentItem->child(j)->childCount() != childCount)) { ok = false; break; } childCount = currentItem->child(j)->childCount(); } //if yes we return this number, otherwise -1 if(ok) return childCount; else return -1; } } else if (level == 1 && levelIdx == 1) return currentItem->childCount(); return -1; } /*! *\brief We search in twSubscriptions for topics that can be represented using + wildcards, then merge them. * We do this until there are no topics to merge */ void LiveDataDock::manageCommonLevelSubscriptions() { bool foundEqual = false; do{ foundEqual = false; QMap> equalTopicsMap; QVector equalTopics; //compare the subscriptions present in the TreeWidget for(int i = 0; i < ui.twSubscriptions->topLevelItemCount() - 1; ++i) { for(int j = i + 1; j < ui.twSubscriptions->topLevelItemCount(); ++j) { qDebug()<topLevelItem(i)->text(0)<<" "<topLevelItem(j)->text(0); QString commonTopic = checkCommonLevel(ui.twSubscriptions->topLevelItem(i)->text(0), ui.twSubscriptions->topLevelItem(j)->text(0)); //if there is a common topic for the 2 compared topics, we add them to the map (using the common topic as key) if(!commonTopic.isEmpty()) { if(!equalTopicsMap[commonTopic].contains(ui.twSubscriptions->topLevelItem(i)->text(0))) { qDebug()<topLevelItem(i)->text(0); equalTopicsMap[commonTopic].push_back(ui.twSubscriptions->topLevelItem(i)->text(0)); } if(!equalTopicsMap[commonTopic].contains(ui.twSubscriptions->topLevelItem(j)->text(0))) { qDebug()<topLevelItem(i)->text(0); equalTopicsMap[commonTopic].push_back(ui.twSubscriptions->topLevelItem(j)->text(0)); } } } } if(!equalTopicsMap.isEmpty()) { qDebug()<<"Equal topics not empty"; QVector commonTopics; QMapIterator> topics(equalTopicsMap); //check for every map entry, if the found topics can be merged or not while(topics.hasNext()) { topics.next(); qDebug()<<"Checking: " << topics.key(); int level = commonLevelIndex(topics.value().last(), topics.value().first()); QStringList commonList = topics.value().first().split('/', QString::SkipEmptyParts); QTreeWidgetItem* currentItem; //search the corresponding item to the common topics first level(root) for(int i = 0; i < ui.twTopics->topLevelItemCount(); ++i) { if(ui.twTopics->topLevelItem(i)->text(0) == commonList.first()) { currentItem = ui.twTopics->topLevelItem(i); break; } } //calculate the number of topics the new + wildcard could replace int childCount = checkCommonChildCount(1, level, commonList, currentItem); if(childCount > 0) { //if the number of topics found and the calculated number of topics is equal, the topics can be merged if(topics.value().size() == childCount) { foundEqual = true; commonTopics.push_back(topics.key()); qDebug()<addTopLevelItem(newTopic); //remove the "merged" topics for(int i = 0; i < equalTopics.size(); ++i) { for(int j = 0; j < ui.twSubscriptions->topLevelItemCount(); ++j){ if(ui.twSubscriptions->topLevelItem(j)->text(0) == equalTopics[i]) { newTopic->addChild(ui.twSubscriptions->takeTopLevelItem(j)); break; } } } //remove any subscription that the new subscription contains for(int i = 0; i < ui.twSubscriptions->topLevelItemCount(); ++i) { if(checkTopicContains(commonTopic, ui.twSubscriptions->topLevelItem(i)->text(0)) && commonTopic != ui.twSubscriptions->topLevelItem(i)->text(0) ) { ui.twSubscriptions->topLevelItem(i)->takeChildren(); ui.twSubscriptions->takeTopLevelItem(i); i--; } } //make the subscripiton on commonTopic in every MQTTClient from m_mqttClients for (auto* source: m_mqttClients) { source->addMQTTSubscription(commonTopic, ui.cbQoS->currentIndex()); } } } } while(foundEqual); } /*! *\brief Adds topicName to twTopics * * \param topicName the name of the topic, which will be added to the tree widget */ void LiveDataDock::addTopicToTree(const QString &topicName) { QStringList name; QChar sep = '/'; QString rootName; if(topicName.contains(sep)) { QStringList list = topicName.split(sep, QString::SkipEmptyParts); - rootName = list.at(0); - name.append(list.at(0)); - QTreeWidgetItem* currentItem; - //check whether the first level of the topic can be found in twTopics - int topItemIdx = -1; - for(int i = 0; i < ui.twTopics->topLevelItemCount(); ++i) { - if(ui.twTopics->topLevelItem(i)->text(0) == list.at(0)) { - topItemIdx = i; - break; + if(!list.isEmpty()) { + rootName = list.at(0); + name.append(list.at(0)); + QTreeWidgetItem* currentItem; + //check whether the first level of the topic can be found in twTopics + int topItemIdx = -1; + for(int i = 0; i < ui.twTopics->topLevelItemCount(); ++i) { + if(ui.twTopics->topLevelItem(i)->text(0) == list.at(0)) { + topItemIdx = i; + break; + } } - } - //if not we simply add every level of the topic to the tree - if( topItemIdx < 0) { - currentItem = new QTreeWidgetItem(name); - ui.twTopics->addTopLevelItem(currentItem); - for(int i = 1; i < list.size(); ++i) { - name.clear(); - name.append(list.at(i)); - currentItem->addChild(new QTreeWidgetItem(name)); - currentItem = currentItem->child(0); + //if not we simply add every level of the topic to the tree + if( topItemIdx < 0) { + currentItem = new QTreeWidgetItem(name); + ui.twTopics->addTopLevelItem(currentItem); + for(int i = 1; i < list.size(); ++i) { + name.clear(); + name.append(list.at(i)); + currentItem->addChild(new QTreeWidgetItem(name)); + currentItem = currentItem->child(0); + } } - } - //otherwise we search for the first level that isn't part of the tree, - //then add every level of the topic to the tree from that certain level - else { - currentItem = ui.twTopics->topLevelItem(topItemIdx); - int listIdx = 1; - for(; listIdx < list.size(); ++listIdx) { - QTreeWidgetItem* childItem = nullptr; - bool found = false; - for(int j = 0; j < currentItem->childCount(); ++j) { - childItem = currentItem->child(j); - if(childItem->text(0) == list.at(listIdx)) { - found = true; - currentItem = childItem; + //otherwise we search for the first level that isn't part of the tree, + //then add every level of the topic to the tree from that certain level + else { + currentItem = ui.twTopics->topLevelItem(topItemIdx); + int listIdx = 1; + for(; listIdx < list.size(); ++listIdx) { + QTreeWidgetItem* childItem = nullptr; + bool found = false; + for(int j = 0; j < currentItem->childCount(); ++j) { + childItem = currentItem->child(j); + if(childItem->text(0) == list.at(listIdx)) { + found = true; + currentItem = childItem; + break; + } + } + if(!found) { + //this is the level that isn't present in the tree break; } } - if(!found) { - //this is the level that isn't present in the tree - break; - } - } - //add every level to the tree starting with the first level that isn't part of the tree - for(; listIdx < list.size(); ++listIdx) { - name.clear(); - name.append(list.at(listIdx)); - currentItem->addChild(new QTreeWidgetItem(name)); - currentItem = currentItem->child(currentItem->childCount() - 1); + //add every level to the tree starting with the first level that isn't part of the tree + for(; listIdx < list.size(); ++listIdx) { + name.clear(); + name.append(list.at(listIdx)); + currentItem->addChild(new QTreeWidgetItem(name)); + currentItem = currentItem->child(currentItem->childCount() - 1); + } } } } else { rootName = topicName; name.append(topicName); ui.twTopics->addTopLevelItem(new QTreeWidgetItem(name)); } //if a subscribed topic contains the new topic, we have to update twSubscriptions for(int i = 0; i < ui.twSubscriptions->topLevelItemCount(); ++i) { QStringList subscriptionName = ui.twSubscriptions->topLevelItem(i)->text(0).split('/', QString::SkipEmptyParts); if (rootName == subscriptionName[0]) { qDebug()<clientHostName()].contains(topic.name())) { m_addedTopics[m_mqttClients.first()->clientHostName()].push_back(topic.name()); } } /*! *\brief called when an MQTTClient is about to be deleted * removes every data connected to the MQTTClient, and disconnects the corresponding client from m_clients * * \param name the host name of the MQTTClient that will be deleted */ void LiveDataDock::removeClient(const QString& name) { m_clients[name]->disconnectFromHost(); m_addedTopics.remove(name); m_topicList.remove(name); if(m_previousMQTTClient != nullptr && m_previousMQTTClient->clientHostName() == name) { disconnect(m_clients[m_previousMQTTClient->clientHostName()], &QMqttClient::messageReceived, this, &LiveDataDock::mqttMessageReceivedInBackground); m_previousMQTTClient = nullptr; } if(m_mqttClients.first()->clientHostName() == name) { ui.twSubscriptions->clear(); ui.twTopics->clear(); m_mqttClients.clear(); } delete m_clients[name]; m_clients.remove(name); } #endif