diff --git a/src/kdefrontend/datasources/ImportFileWidget.cpp b/src/kdefrontend/datasources/ImportFileWidget.cpp index 68de27cb4..88d6b6603 100644 --- a/src/kdefrontend/datasources/ImportFileWidget.cpp +++ b/src/kdefrontend/datasources/ImportFileWidget.cpp @@ -1,2764 +1,2920 @@ /*************************************************************************** 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::setCompleter); + 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(this, &ImportFileWidget::newTopicForWill, this, &ImportFileWidget::updateWillTopics); 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::scrollToTreeItem); + 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_completer = new QCompleter; + 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(); } - } - else + 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)) { QStringList list = topic.name().split(sep, QString::SkipEmptyParts); rootName = list.at(0); name.append(list.at(0)); QTreeWidgetItem* currentItem; int topItemIdx = -1; //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); } } //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; } } //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 { + } else { 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 + 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_completer->setCaseSensitivity(Qt::CaseSensitive); - ui.leTopics->setCompleter(m_completer); + m_topicCompleter = new QCompleter(m_topicList, this); + m_topicCompleter->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(); - for(int i = 0; i < m_subscribedTopicNames.size(); ++i) { - ui.cbWillTopic->addItem(m_subscribedTopicNames[i]); + + //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)); } - ui.cbWillTopic->setCurrentText(current); + + //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::scrollToTreeItem(const QString& rootName) { +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/datasources/ImportFileWidget.h b/src/kdefrontend/datasources/ImportFileWidget.h index 3414a307a..41555cd1f 100644 --- a/src/kdefrontend/datasources/ImportFileWidget.h +++ b/src/kdefrontend/datasources/ImportFileWidget.h @@ -1,202 +1,206 @@ /*************************************************************************** File : ImportFileWidget.h Project : LabPlot Description : import file data widget -------------------------------------------------------------------- Copyright : (C) 2009-2017 by Stefan Gerlach (stefan.gerlach@uni-konstanz.de) Copyright : (C) 2009-2015 Alexander Semke (alexander.semke@web.de) Copyright : (C) 2017-2018 Fabian Kristof (fkristofszabolcs@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 * * * ***************************************************************************/ #ifndef IMPORTFILEWIDGET_H #define IMPORTFILEWIDGET_H #include "ui_importfilewidget.h" #include "backend/datasources/LiveDataSource.h" #include #ifdef HAVE_MQTT #include "backend/datasources/MQTTClient.h" #include #include #include #include #endif #include #include class AbstractFileFilter; class AsciiOptionsWidget; class BinaryOptionsWidget; class HDF5OptionsWidget; class ImageOptionsWidget; class NetCDFOptionsWidget; class FITSOptionsWidget; class JsonOptionsWidget; class ROOTOptionsWidget; class QTableWidget; class QCompleter; class QTimer; class QTreeWidgetItem; class ImportFileWidget : public QWidget { Q_OBJECT public: explicit ImportFileWidget(QWidget*, const QString& fileName = QString()); ~ImportFileWidget(); void showOptions(bool); void saveSettings(LiveDataSource*) const; AbstractFileFilter::FileType currentFileType() const; LiveDataSource::SourceType currentSourceType() const; AbstractFileFilter* currentFileFilter() const; QString fileName() const; QString selectedObject() const; bool isFileEmpty() const; const QStringList selectedHDF5Names() const; const QStringList selectedNetCDFNames() const; const QStringList selectedFITSExtensions() const; const QStringList selectedROOTNames() const; void hideDataSource(); void showAsciiHeaderOptions(bool); void showJsonModel(bool); QString host() const; QString port() const; QString serialPort() const; int baudRate() const; void initializeAndFillPortsAndBaudRates(); private: Ui::ImportFileWidget ui; void hideMQTT(); std::unique_ptr m_asciiOptionsWidget; std::unique_ptr m_binaryOptionsWidget; std::unique_ptr m_hdf5OptionsWidget; std::unique_ptr m_imageOptionsWidget; std::unique_ptr m_netcdfOptionsWidget; std::unique_ptr m_fitsOptionsWidget; std::unique_ptr m_jsonOptionsWidget; std::unique_ptr m_rootOptionsWidget; QTableWidget* m_twPreview; const QString& m_fileName; bool m_fileEmpty; bool m_liveDataSource; bool m_suppressRefresh; public slots: void loadSettings(); private slots: void fileNameChanged(const QString&); void fileTypeChanged(int); void updateTypeChanged(int); void sourceTypeChanged(int); void readingTypeChanged(int); void saveFilter(); void manageFilters(); void filterChanged(int); void selectFile(); void fileInfoDialog(); void refreshPreview(); signals: void fileNameChanged(); void sourceTypeChanged(); void hostChanged(); void portChanged(); void previewRefreshed(); void checkedFitsTableToMatrix(const bool enable); friend class HDF5OptionsWidget; // to access refreshPreview() friend class NetCDFOptionsWidget; // to access refreshPreview() and others friend class FITSOptionsWidget; friend class JsonOptionsWidget; friend class ROOTOptionsWidget; // to access refreshPreview() and others #ifdef HAVE_MQTT private: + void updateSubscriptionCompleter(); bool checkTopicContains(const QString&, const QString&) ; QString checkCommonLevel(const QString&, const QString&); int commonLevelIndex(const QString& first, const QString& second); void unsubscribeFromTopic(const QString&); void addSubscriptionChildren(QTreeWidgetItem*, QTreeWidgetItem*); void findSubscriptionLeafChildren(QVector&, QTreeWidgetItem*); int checkCommonChildCount(int levelIdx, int level, QStringList& namelist, QTreeWidgetItem* currentItem); void manageCommonLevelSubscriptions(); + void updateSubscriptionTree(); + void restoreSubscriptionChildren(QTreeWidgetItem * topic, QTreeWidgetItem * subscription, const QStringList& list, int level); QMqttClient *m_client; QMqttSubscription *m_mainSubscription; QMqttTopicFilter *m_filter; QVector m_mqttSubscriptions; - QCompleter* m_completer; + QCompleter* m_topicCompleter; + QCompleter* m_subscriptionCompleter; QStringList m_topicList; bool m_searching; QTimer *m_searchTimer; QTimer *m_connectTimeoutTimer; QMap m_messageArrived; QMap m_lastMessage; bool m_mqttReadyForPreview; QVector m_subscribedTopicNames; QVector m_addedTopics; public: void saveMQTTSettings(MQTTClient*) const; bool isMqttValid(); signals: void newTopic(QString); void subscriptionsChanged(); void checkFileType(); - void newTopicForWill(); private slots: void idChecked(int); void authenticationChecked(int); void mqttConnection(); void onMqttConnect(); void mqttSubscribe(); void mqttUnsubscribe(); void mqttMessageReceived(const QByteArray&, const QMqttTopicName&); - void setCompleter(const QString&); + void setTopicCompleter(const QString&); void topicTimeout(); void mqttSubscriptionMessageReceived(const QMqttMessage& ); void onMqttDisconnect(); void useWillMessage(int); void willMessageTypeChanged(int); void updateWillTopics(); void willUpdateTypeChanged(int); void mqttErrorChanged(QMqttClient::ClientError); - void scrollToTreeItem(const QString&); + void scrollToTopicTreeItem(const QString&); + void scrollToSubsriptionTreeItem(const QString&); void mqttConnectTimeout(); void checkConnectEnable(); #endif }; #endif diff --git a/src/kdefrontend/dockwidgets/LiveDataDock.cpp b/src/kdefrontend/dockwidgets/LiveDataDock.cpp index 94cd1d125..01ea406cc 100644 --- a/src/kdefrontend/dockwidgets/LiveDataDock.cpp +++ b/src/kdefrontend/dockwidgets/LiveDataDock.cpp @@ -1,1671 +1,1671 @@ /*************************************************************************** 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::setCompleter); 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::scrollToTreeItem); 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 subscirptions contains an already existing one, we remove the inferior one + //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->newMQTTSubscription(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(); } 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(); } } 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::setCompleter(const QString& topic) { if(!m_searching) { if(!m_topicList[m_mqttClients.first()->clientHostName()].contains(topic)) { m_topicList[m_mqttClients.first()->clientHostName()].append(topic); m_completer = new QCompleter(m_topicList[m_mqttClients.first()->clientHostName()], this); m_completer->setCompletionMode(QCompleter::PopupCompletion); m_completer->setCaseSensitivity(Qt::CaseSensitive); ui.leTopics->setCompleter(m_completer); } } } /*! *\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->mqttSubscribtions(); 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 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->newMQTTSubscription(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 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; 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); } } } 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 diff --git a/src/kdefrontend/ui/datasources/importfilewidget.ui b/src/kdefrontend/ui/datasources/importfilewidget.ui index 1bacba490..13b5abc1f 100644 --- a/src/kdefrontend/ui/datasources/importfilewidget.ui +++ b/src/kdefrontend/ui/datasources/importfilewidget.ui @@ -1,1325 +1,1335 @@ ImportFileWidget 0 0 630 1272 0 0 0 0 0 0 true 0 - -349 - 611 - 1885 + 0 + 614 + 1828 0 0 Data Source Set ID File or Named Pipe Network TCP Socket Network UDP Socket Local Socket Serial Port MQTT false 0 0 Manage filters false 0 0 Show file info Host: Source: Username: Password: Type: false Port: Interpret retain messages Qt::Vertical QSizePolicy::Fixed 20 10 ID: Filter: Qt::Vertical 20 40 Connect Specify the name of the file to import. true Name: Baud rate: 0 0 Select the file to import Port: false Save the current filter settings false 0 0 QLineEdit::Password The MQTT broker requires authentication Field: QAbstractItemView::NoEditTriggers false 16 16 0 0 Manage subscriptions QOS level: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 1 2 Available topics 12 Qt::Vertical 20 40 Subscribe Unsubscribe Qt::Vertical 20 40 Scroll to root: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter Subscribed topics + + + + Scroll to root: + + + + + + false 0 0 Format Options 0 0 0 0 0 0 0 Data format 0 0 0 0 0 0 0 0 Preview 0 0 Number of rows to preview: 1 10000 100 Qt::Horizontal 23 20 Refresh true QTextEdit::NoWrap true 0 0 Data portion to read Start row: Specify the end row to import; -1 stands for the last row -1 2147483647 -1 Start column: Specify the start column for import 1 2147483647 Qt::Horizontal QSizePolicy::Fixed 40 20 Specify the start row for import 1 2147483647 1 Qt::Horizontal 108 20 Qt::Horizontal QSizePolicy::Fixed 40 20 End column: Specify the end column to import; -1 stands for the last column -1 2147483647 -1 Qt::Horizontal 108 20 End row: Qt::Vertical 20 10 Update Options Keep last values: Read: 0 0 Periodically On New Data If this option is checked, only the link to the file is stored in the project file but not it's content. Link the file true Update: Update interval: All 999999 0 0 ms 5 60000 1000 Continuously Fixed From End Till the End Sample rate: 1 10000 1 MQTT Will Message Will topic: Minimum Unchecked ItemIsUserCheckable|ItemIsEnabled Maximum Unchecked ItemIsUserCheckable|ItemIsEnabled Arithmetic mean Unchecked ItemIsUserCheckable|ItemIsEnabled Geometric mean Unchecked ItemIsUserCheckable|ItemIsEnabled Harmonic mean Unchecked ItemIsUserCheckable|ItemIsEnabled Contraharmonic mean Unchecked ItemIsUserCheckable|ItemIsEnabled Median Unchecked ItemIsUserCheckable|ItemIsEnabled Variance Unchecked ItemIsUserCheckable|ItemIsEnabled Standard deviation Unchecked ItemIsUserCheckable|ItemIsEnabled Mean deviation Unchecked ItemIsUserCheckable|ItemIsEnabled Mean deviation around median Unchecked ItemIsUserCheckable|ItemIsEnabled Median deviation Unchecked ItemIsUserCheckable|ItemIsEnabled Skewness Unchecked ItemIsUserCheckable|ItemIsEnabled Kurtosis Unchecked ItemIsUserCheckable|ItemIsEnabled Entropy Unchecked ItemIsUserCheckable|ItemIsEnabled Own message: Time interval On click 0 0 Own message Statistics Last message received 0 0 0 1 2 Set will message as retain 10000 Time interval: Will QoS: Will Update Type: Message type: Will Statistics: 0 0 Set will message for the client KComboBox QComboBox
kcombobox.h
cbSourceType leFileName bOpen bFileInfo leHost lePort cbSerialPort cbBaudRate cbFileType cbFilter bSaveFilter bManageFilters tvJson tabWidget sbPreviewLines bRefreshPreview tePreview sbStartRow sbEndRow sbStartColumn sbEndColumn cbReadingType sbSampleSize cbUpdateType sbUpdateInterval sbKeepNValues chbLinkFile