File indexing completed on 2025-07-13 03:32:35

0001 /*
0002     File                 : ImportFileWidget.cpp
0003     Project              : LabPlot
0004     Description          : import file data widget
0005     --------------------------------------------------------------------
0006     SPDX-FileCopyrightText: 2009-2023 Stefan Gerlach <stefan.gerlach@uni.kn>
0007     SPDX-FileCopyrightText: 2009-2023 Alexander Semke <alexander.semke@web.de>
0008     SPDX-FileCopyrightText: 2017-2018 Fabian Kristof <fkristofszabolcs@gmail.com>
0009     SPDX-FileCopyrightText: 2018-2019 Kovacs Ferencz <kferike98@gmail.com>
0010     SPDX-License-Identifier: GPL-2.0-or-later
0011 */
0012 
0013 #include "ImportFileWidget.h"
0014 #include "AsciiOptionsWidget.h"
0015 #include "BinaryOptionsWidget.h"
0016 #include "CANOptionsWidget.h"
0017 #include "FITSOptionsWidget.h"
0018 #include "HDF5OptionsWidget.h"
0019 #include "ImageOptionsWidget.h"
0020 #include "JsonOptionsWidget.h"
0021 #include "MatioOptionsWidget.h"
0022 #include "NetCDFOptionsWidget.h"
0023 #include "OdsOptionsWidget.h"
0024 #include "ROOTOptionsWidget.h"
0025 #include "XLSXOptionsWidget.h"
0026 #include "backend/core/Settings.h"
0027 #include "backend/datasources/filters/filters.h"
0028 #include "backend/lib/macros.h"
0029 #include "kdefrontend/TemplateHandler.h"
0030 
0031 #include <QCompleter>
0032 #include <QDir>
0033 #include <QFileDialog>
0034 #include <QFileSystemModel>
0035 #include <QIntValidator>
0036 #include <QLocalSocket>
0037 #include <QProcess>
0038 #include <QStandardItemModel>
0039 #include <QTableWidget>
0040 #include <QTcpSocket>
0041 #include <QTimer>
0042 #include <QTreeWidgetItem>
0043 #include <QUdpSocket>
0044 #include <QWhatsThis>
0045 
0046 #include <KConfig>
0047 #include <KConfigGroup>
0048 #include <KLocalizedString>
0049 
0050 #include <KUrlComboBox>
0051 
0052 #ifdef HAVE_MQTT
0053 #include "MQTTConnectionManagerDialog.h"
0054 #include "MQTTSubscriptionWidget.h"
0055 #include "kdefrontend/widgets/MQTTWillSettingsWidget.h"
0056 #include <QMenu>
0057 #include <QMqttClient>
0058 #include <QMqttMessage>
0059 #include <QMqttSubscription>
0060 #include <QMqttTopicFilter>
0061 #include <QWidgetAction>
0062 #endif
0063 
0064 QString ImportFileWidget::absolutePath(const QString& fileName) {
0065     if (fileName.isEmpty())
0066         return fileName;
0067 
0068 #ifdef HAVE_WINDOWS
0069     if (fileName.size() == 1 || (fileName.at(0) != QLatin1Char('/') && fileName.at(1) != QLatin1Char(':')))
0070 #else
0071     if (fileName.at(0) != QLatin1Char('/'))
0072 #endif
0073         return QDir::homePath() + QLatin1Char('/') + fileName;
0074 
0075     return fileName;
0076 }
0077 
0078 /*!
0079    \class ImportFileWidget
0080    \brief Widget for importing data from a file.
0081 
0082    \ingroup kdefrontend
0083 */
0084 ImportFileWidget::ImportFileWidget(QWidget* parent, bool liveDataSource, const QString& fileName)
0085     : QWidget(parent)
0086     , m_fileName(fileName)
0087     , m_liveDataSource(liveDataSource)
0088 #ifdef HAVE_MQTT
0089     , m_subscriptionWidget(new MQTTSubscriptionWidget(this))
0090 #endif
0091 {
0092     ui.setupUi(this);
0093 
0094     // add supported file types (see also ExportSpreadsheetDialog.cpp)
0095     if (!liveDataSource) {
0096         ui.cbFileType->addItem(i18n("ASCII data"), static_cast<int>(AbstractFileFilter::FileType::Ascii));
0097         ui.cbFileType->addItem(i18n("Binary data"), static_cast<int>(AbstractFileFilter::FileType::Binary));
0098         ui.cbFileType->addItem(i18n("Image"), static_cast<int>(AbstractFileFilter::FileType::Image));
0099 #ifdef HAVE_QXLSX
0100         ui.cbFileType->addItem(i18n("Excel 2007+ (XSLX)"), static_cast<int>(AbstractFileFilter::FileType::XLSX));
0101 #endif
0102 #ifdef HAVE_ORCUS
0103         ui.cbFileType->addItem(i18n("OpenDocument Spreadsheet (ODS)"), static_cast<int>(AbstractFileFilter::FileType::Ods));
0104 #endif
0105 #ifdef HAVE_HDF5
0106         ui.cbFileType->addItem(i18n("Hierarchical Data Format 5 (HDF5)"), static_cast<int>(AbstractFileFilter::FileType::HDF5));
0107 #endif
0108 #ifdef HAVE_NETCDF
0109         ui.cbFileType->addItem(i18n("Network Common Data Format (NetCDF)"), static_cast<int>(AbstractFileFilter::FileType::NETCDF));
0110 #endif
0111 #ifdef HAVE_VECTOR_BLF
0112         ui.cbFileType->addItem(i18n("Vector Binary Logfile (BLF)"), static_cast<int>(AbstractFileFilter::FileType::VECTOR_BLF));
0113 #endif
0114 #ifdef HAVE_FITS
0115         ui.cbFileType->addItem(i18n("Flexible Image Transport System Data Format (FITS)"), static_cast<int>(AbstractFileFilter::FileType::FITS));
0116 #endif
0117         ui.cbFileType->addItem(i18n("JSON Data"), static_cast<int>(AbstractFileFilter::FileType::JSON));
0118 #ifdef HAVE_ZIP
0119         ui.cbFileType->addItem(i18n("ROOT (CERN)"), static_cast<int>(AbstractFileFilter::FileType::ROOT));
0120 #endif
0121         ui.cbFileType->addItem(i18n("Spice"), static_cast<int>(AbstractFileFilter::FileType::Spice));
0122 #ifdef HAVE_READSTAT
0123         ui.cbFileType->addItem(i18n("SAS, Stata or SPSS"), static_cast<int>(AbstractFileFilter::FileType::READSTAT));
0124 #endif
0125 #ifdef HAVE_MATIO
0126         ui.cbFileType->addItem(i18n("MATLAB MAT file"), static_cast<int>(AbstractFileFilter::FileType::MATIO));
0127 #endif
0128 
0129         // hide widgets relevant for live data reading only
0130         ui.lRelativePath->hide();
0131         ui.chbRelativePath->hide();
0132         ui.lSourceType->hide();
0133         ui.cbSourceType->hide();
0134         ui.gbUpdateOptions->hide();
0135     } else { // Live data source
0136         ui.cbFileType->addItem(i18n("ASCII Data"), static_cast<int>(AbstractFileFilter::FileType::Ascii));
0137         ui.cbFileType->addItem(i18n("Binary Data"), static_cast<int>(AbstractFileFilter::FileType::Binary));
0138 #ifdef HAVE_ZIP
0139         ui.cbFileType->addItem(i18n("ROOT (CERN)"), static_cast<int>(AbstractFileFilter::FileType::ROOT));
0140 #endif
0141         ui.cbFileType->addItem(i18n("Spice"), static_cast<int>(AbstractFileFilter::FileType::Spice));
0142 
0143         ui.lePort->setValidator(new QIntValidator(ui.lePort));
0144         ui.cbBaudRate->addItems(LiveDataSource::supportedBaudRates());
0145         ui.cbSerialPort->addItems(LiveDataSource::availablePorts());
0146 
0147         ui.tabWidget->removeTab(2);
0148 
0149         ui.chbLinkFile->setToolTip(i18n("If this option is checked, only the link to the file is stored in the project file but not its content."));
0150         ui.chbRelativePath->setToolTip(i18n("If this option is checked, the relative path of the file (relative to project's folder) will be saved."));
0151     }
0152 
0153     // hide options that will be activated on demand
0154     ui.gbOptions->hide();
0155     ui.gbUpdateOptions->hide();
0156     setMQTTVisible(false);
0157 
0158     ui.cbReadingType->addItem(i18n("Whole File"), static_cast<int>(LiveDataSource::ReadingType::WholeFile));
0159 
0160     ui.bOpen->setIcon(QIcon::fromTheme(QStringLiteral("document-open")));
0161     ui.bOpenDBC->setIcon(QIcon::fromTheme(QStringLiteral("document-open")));
0162     ui.bFileInfo->setIcon(QIcon::fromTheme(QStringLiteral("help-about")));
0163     ui.bRefreshPreview->setIcon(QIcon::fromTheme(QStringLiteral("view-refresh")));
0164 
0165     ui.tvJson->header()->setSectionResizeMode(QHeaderView::ResizeToContents);
0166     ui.tvJson->setAlternatingRowColors(true);
0167     showJsonModel(false);
0168 
0169     // the table widget for preview
0170     m_twPreview = new QTableWidget(ui.tePreview);
0171     m_twPreview->verticalHeader()->hide();
0172     m_twPreview->setEditTriggers(QTableWidget::NoEditTriggers);
0173     auto* layout = new QHBoxLayout;
0174     layout->addWidget(m_twPreview);
0175     ui.tePreview->setLayout(layout);
0176     m_twPreview->hide();
0177 
0178     // the combobox for the import path
0179     m_cbFileName = new KUrlComboBox(KUrlComboBox::Mode::Files, this);
0180     m_cbFileName->setMaxItems(7);
0181     auto* gridLayout = dynamic_cast<QGridLayout*>(ui.gbDataSource->layout());
0182     if (gridLayout)
0183         gridLayout->addWidget(m_cbFileName, 1, 2, 1, 3);
0184 
0185     m_cbDBCFileName = new KUrlComboBox(KUrlComboBox::Mode::Files, this);
0186     m_cbDBCFileName->setMaxItems(7);
0187     gridLayout = dynamic_cast<QGridLayout*>(ui.gbDataSource->layout());
0188     if (gridLayout)
0189         gridLayout->addWidget(m_cbDBCFileName, 2, 2, 1, 3);
0190 
0191     // tooltips
0192     QString info = i18n(
0193         "Specify how the data source has to be processed on every read:"
0194         "<ul>"
0195         "<li>Continuously fixed - fixed amount of samples is processed starting from the beginning of the newly received data.</li>"
0196         "<li>From End - fixed amount of samples is processed starting from the end of the newly received data.</li>"
0197         "<li>Till the End - all newly received data is processed.</li>"
0198         "<li>Whole file - on every read the whole file is re-read completely and processed. Only available for \"File Or Named Pipe\" data sources.</li>"
0199         "</ul>");
0200     ui.lReadingType->setToolTip(info);
0201     ui.cbReadingType->setToolTip(info);
0202 
0203     info = i18n(
0204         "Number of samples (lines) to be processed on every read.\n"
0205         "Only needs to be specified for the reading mode \"Continuously Fixed\" and \"From End\".");
0206     ui.lSampleSize->setToolTip(info);
0207     ui.sbSampleSize->setToolTip(info);
0208 
0209     info = i18n(
0210         "Specify when and how frequently the data source needs to be read:"
0211         "<ul>"
0212         "<li>Periodically - the data source is read periodically with user specified time interval.</li>"
0213         "<li>On New Data - the data source is read when new data arrives.</li>"
0214         "</ul>");
0215     ui.lUpdateType->setToolTip(info);
0216     ui.cbUpdateType->setToolTip(info);
0217 
0218     info = i18n("Specify how frequently the data source has to be read.");
0219     ui.lUpdateInterval->setToolTip(info);
0220     ui.sbUpdateInterval->setToolTip(info);
0221 
0222     info = i18n(
0223         "Specify how many samples need to be kept in memory after reading.\n"
0224         "Use \"All\" if all data has to be kept.");
0225     ui.lKeepLastValues->setToolTip(info);
0226     ui.sbKeepNValues->setToolTip(info);
0227 
0228     info = i18n("Enable to use the first row of the selected data region for the column names of the spreadsheet.");
0229     ui.lFirstRowAsColNames->setToolTip(info);
0230     ui.chbFirstRowAsColName->setToolTip(info);
0231 #ifdef HAVE_MQTT
0232     ui.cbSourceType->addItem(QStringLiteral("MQTT"));
0233     m_configPath = QStandardPaths::standardLocations(QStandardPaths::AppDataLocation).constFirst() + QStringLiteral("MQTT_connections");
0234 
0235     // add subscriptions widget
0236     layout = new QHBoxLayout;
0237     layout->setContentsMargins(0, 0, 0, 0);
0238     layout->setSpacing(0);
0239     layout->addWidget(m_subscriptionWidget);
0240     ui.frameSubscriptions->setLayout(layout);
0241 
0242     ui.bManageConnections->setIcon(QIcon::fromTheme(QStringLiteral("network-server")));
0243     ui.bManageConnections->setToolTip(i18n("Manage MQTT connections"));
0244 
0245     info = i18n("Specify the 'Last Will and Testament' message (LWT). At least one topic has to be subscribed.");
0246     ui.lLWT->setToolTip(info);
0247     ui.bLWT->setToolTip(info);
0248     ui.bLWT->setEnabled(false);
0249     ui.bLWT->setIcon(ui.bLWT->style()->standardIcon(QStyle::SP_FileDialogDetailedView));
0250 #endif
0251 
0252     // templates for plot properties
0253     m_templateHandler = new TemplateHandler(this, QLatin1String("import"), false);
0254     m_templateHandler->setSaveDefaultAvailable(false);
0255     m_templateHandler->setLoadAvailable(false);
0256     ui.hLayoutFilter->addWidget(m_templateHandler);
0257     connect(m_templateHandler, &TemplateHandler::saveConfigRequested, this, &ImportFileWidget::saveConfigAsTemplate);
0258 }
0259 
0260 void ImportFileWidget::loadSettings() {
0261     m_suppressRefresh = true;
0262 
0263     // load last used settings
0264     QString confName;
0265     if (m_liveDataSource)
0266         confName = QStringLiteral("LiveDataImport");
0267     else
0268         confName = QStringLiteral("FileImport");
0269     KConfigGroup conf = Settings::group(confName);
0270 
0271     // read the source type first since settings in fileNameChanged() depend on this
0272     ui.cbSourceType->setCurrentIndex(conf.readEntry("SourceType").toInt());
0273 
0274     // general settings
0275     auto fileType = static_cast<AbstractFileFilter::FileType>(conf.readEntry("Type", 0));
0276     for (int i = 0; i < ui.cbFileType->count(); ++i) {
0277         if (static_cast<AbstractFileFilter::FileType>(ui.cbFileType->itemData(i).toInt()) == fileType) {
0278             if (ui.cbFileType->currentIndex() == i)
0279                 initOptionsWidget();
0280             else
0281                 ui.cbFileType->setCurrentIndex(i);
0282 
0283             break;
0284         }
0285     }
0286 
0287     auto urls = m_cbFileName->urls();
0288     urls.append(conf.readXdgListEntry("LastImportedFiles"));
0289     m_cbFileName->setUrls(urls);
0290     if (m_fileName.isEmpty())
0291         m_cbFileName->setUrl(QUrl(conf.readEntry("LastImportedFile", "")));
0292     else
0293         m_cbFileName->setUrl(QUrl(m_fileName));
0294 
0295     urls = m_cbDBCFileName->urls();
0296     urls.append(conf.readXdgListEntry("LastImportedDBCFiles"));
0297     m_cbDBCFileName->setUrls(urls);
0298     if (m_dbcFileName.isEmpty())
0299         m_cbDBCFileName->setUrl(QUrl(conf.readEntry("LastImportedDBCFile", "")));
0300     else
0301         m_cbDBCFileName->setUrl(QUrl(m_dbcFileName));
0302 
0303     ui.sbPreviewLines->setValue(conf.readEntry("PreviewLines", 100));
0304     ui.chbFirstRowAsColName->setCheckState((Qt::CheckState)conf.readEntry("ExcelFirstLineAsColNames", (int)Qt::CheckState::Unchecked));
0305 
0306     // live data related settings
0307     ui.cbBaudRate->setCurrentIndex(conf.readEntry("BaudRate", 13)); // index for bautrate 19200b/s
0308     ui.cbReadingType->setCurrentIndex(conf.readEntry("ReadingType", static_cast<int>(LiveDataSource::ReadingType::WholeFile)));
0309     ui.cbSerialPort->setCurrentIndex(conf.readEntry("SerialPort").toInt());
0310     ui.cbUpdateType->setCurrentIndex(conf.readEntry("UpdateType", static_cast<int>(LiveDataSource::UpdateType::NewData)));
0311     ui.leHost->setText(conf.readEntry("Host", ""));
0312     ui.sbKeepNValues->setValue(conf.readEntry("KeepNValues", 0)); // keep all values
0313     ui.lePort->setText(conf.readEntry("Port", ""));
0314     ui.sbSampleSize->setValue(conf.readEntry("SampleSize", 1));
0315     ui.sbUpdateInterval->setValue(conf.readEntry("UpdateInterval", 1000));
0316     ui.chbLinkFile->setCheckState((Qt::CheckState)conf.readEntry("LinkFile", (int)Qt::CheckState::Unchecked));
0317     ui.chbRelativePath->setCheckState((Qt::CheckState)conf.readEntry("RelativePath", (int)Qt::CheckState::Unchecked));
0318 
0319 #ifdef HAVE_MQTT
0320     // read available MQTT connections
0321     m_initialisingMQTT = true;
0322     readMQTTConnections();
0323     ui.cbConnection->setCurrentIndex(ui.cbConnection->findText(conf.readEntry("Connection", "")));
0324     m_initialisingMQTT = false;
0325 
0326     m_willSettings.enabled = conf.readEntry("mqttWillEnabled", m_willSettings.enabled);
0327     m_willSettings.willRetain = conf.readEntry("mqttWillRetain", m_willSettings.willRetain);
0328     m_willSettings.willUpdateType = static_cast<MQTTClient::WillUpdateType>(conf.readEntry("mqttWillUpdateType", (int)m_willSettings.willUpdateType));
0329     m_willSettings.willMessageType = static_cast<MQTTClient::WillMessageType>(conf.readEntry("mqttWillMessageType", (int)m_willSettings.willMessageType));
0330     m_willSettings.willQoS = conf.readEntry("mqttWillQoS", (int)m_willSettings.willQoS);
0331     m_willSettings.willOwnMessage = conf.readEntry("mqttWillOwnMessage", m_willSettings.willOwnMessage);
0332     m_willSettings.willTimeInterval = conf.readEntry("mqttWillUpdateInterval", m_willSettings.willTimeInterval);
0333 
0334     const QString& willStatistics = conf.readEntry("mqttWillStatistics", "");
0335 #if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
0336     const QStringList& statisticsList = willStatistics.split(QLatin1Char('|'), Qt::SkipEmptyParts);
0337 #else
0338     const QStringList& statisticsList = willStatistics.split(QLatin1Char('|'), QString::SkipEmptyParts);
0339 #endif
0340     for (auto value : statisticsList)
0341         m_willSettings.willStatistics[value.toInt()] = true;
0342 #endif
0343 
0344     // initialize the slots after all settings were set in order to avoid unneeded refreshes
0345     initSlots();
0346 
0347     // update the status of the widgets
0348     sourceTypeChanged(static_cast<int>(currentSourceType()));
0349     fileTypeChanged(); // call it to load the filter templates for the current file type and to select the last used index in cbFilter below
0350     ui.cbFilter->setCurrentIndex(conf.readEntry("Filter", 0));
0351     filterChanged(ui.cbFilter->currentIndex());
0352     updateTypeChanged(ui.cbUpdateType->currentIndex());
0353     readingTypeChanged(ui.cbReadingType->currentIndex());
0354 
0355     // all set now, refresh the content of the file and the preview for the selected dataset
0356     m_suppressRefresh = false;
0357     QTimer::singleShot(100, this, [=]() {
0358         WAIT_CURSOR;
0359         if (currentSourceType() == LiveDataSource::SourceType::FileOrPipe) {
0360             const QString& file = absolutePath(fileName());
0361             if (QFile::exists(file))
0362                 updateContent(file);
0363         }
0364 
0365         refreshPreview();
0366         RESET_CURSOR;
0367     });
0368 }
0369 
0370 ImportFileWidget::~ImportFileWidget() {
0371     // save current settings
0372     QString confName;
0373     if (m_liveDataSource)
0374         confName = QStringLiteral("LiveDataImport");
0375     else
0376         confName = QStringLiteral("FileImport");
0377     KConfigGroup conf = Settings::group(confName);
0378 
0379     // general settings
0380     conf.writeEntry("Type", (int)currentFileType());
0381     conf.writeEntry("Filter", ui.cbFilter->currentIndex());
0382     conf.writeEntry("LastImportedFile", m_cbFileName->currentText());
0383     conf.writeXdgListEntry("LastImportedFiles", m_cbFileName->urls());
0384     conf.writeEntry("LastImportedDBCFile", m_cbDBCFileName->currentText());
0385     conf.writeXdgListEntry("LastImportedDBCFiles", m_cbDBCFileName->urls());
0386     conf.writeEntry("PreviewLines", ui.sbPreviewLines->value());
0387     conf.writeEntry("ExcelFirstLineAsColNames", ui.chbFirstRowAsColName->isChecked());
0388 
0389     // live data related settings
0390     conf.writeEntry("SourceType", (int)currentSourceType());
0391     conf.writeEntry("UpdateType", ui.cbUpdateType->currentIndex());
0392     conf.writeEntry("ReadingType", ui.cbReadingType->currentIndex());
0393     conf.writeEntry("SampleSize", ui.sbSampleSize->value());
0394     conf.writeEntry("KeepNValues", ui.sbKeepNValues->value());
0395     conf.writeEntry("BaudRate", ui.cbBaudRate->currentIndex());
0396     conf.writeEntry("SerialPort", ui.cbSerialPort->currentIndex());
0397     conf.writeEntry("Host", ui.leHost->text());
0398     conf.writeEntry("Port", ui.lePort->text());
0399     conf.writeEntry("UpdateInterval", ui.sbUpdateInterval->value());
0400     conf.writeEntry("LinkFile", (int)ui.chbLinkFile->checkState());
0401     conf.writeEntry("RelativePath", (int)ui.chbRelativePath->checkState());
0402 
0403 #ifdef HAVE_MQTT
0404     delete m_connectTimeoutTimer;
0405     delete m_subscriptionWidget;
0406 
0407     // MQTT related settings
0408     conf.writeEntry("Connection", ui.cbConnection->currentText());
0409     conf.writeEntry("mqttWillMessageType", static_cast<int>(m_willSettings.willMessageType));
0410     conf.writeEntry("mqttWillUpdateType", static_cast<int>(m_willSettings.willUpdateType));
0411     conf.writeEntry("mqttWillQoS", QString::number(m_willSettings.willQoS));
0412     conf.writeEntry("mqttWillOwnMessage", m_willSettings.willOwnMessage);
0413     conf.writeEntry("mqttWillUpdateInterval", QString::number(m_willSettings.willTimeInterval));
0414     QString willStatistics;
0415     for (int i = 0; i < m_willSettings.willStatistics.size(); ++i) {
0416         if (m_willSettings.willStatistics[i])
0417             willStatistics += QString::number(i) + QLatin1Char('|');
0418     }
0419     conf.writeEntry("mqttWillStatistics", willStatistics);
0420     conf.writeEntry("mqttWillRetain", static_cast<int>(m_willSettings.willRetain));
0421     conf.writeEntry("mqttWillUse", static_cast<int>(m_willSettings.enabled));
0422 #endif
0423 
0424     // data type specific settings
0425     if (m_asciiOptionsWidget)
0426         m_asciiOptionsWidget->saveSettings();
0427     if (m_binaryOptionsWidget)
0428         m_binaryOptionsWidget->saveSettings();
0429     if (m_imageOptionsWidget)
0430         m_imageOptionsWidget->saveSettings();
0431     if (m_jsonOptionsWidget)
0432         m_jsonOptionsWidget->saveSettings();
0433     if (m_canOptionsWidget)
0434         m_canOptionsWidget->saveSettings();
0435 }
0436 
0437 void ImportFileWidget::initSlots() {
0438     // SLOTs for the general part of the data source configuration
0439     connect(ui.cbSourceType, QOverload<int>::of(&QComboBox::currentIndexChanged), this, QOverload<int>::of(&ImportFileWidget::sourceTypeChanged));
0440     connect(m_cbFileName, &KUrlComboBox::urlActivated, this, [=](const QUrl& url) {
0441         fileNameChanged(url.toLocalFile());
0442     });
0443     connect(ui.leHost, &QLineEdit::textChanged, this, &ImportFileWidget::hostChanged);
0444     connect(ui.lePort, &QLineEdit::textChanged, this, &ImportFileWidget::portChanged);
0445     connect(ui.cbSerialPort, QOverload<int>::of(&KComboBox::currentIndexChanged), this, &ImportFileWidget::portChanged);
0446     connect(ui.tvJson, &QTreeView::clicked, this, &ImportFileWidget::refreshPreview);
0447 
0448     connect(ui.bOpen, &QPushButton::clicked, this, &ImportFileWidget::selectFile);
0449     connect(ui.bOpenDBC, &QPushButton::clicked, this, &ImportFileWidget::selectDBCFile);
0450     connect(ui.bFileInfo, &QPushButton::clicked, this, &ImportFileWidget::showFileInfo);
0451     connect(ui.cbFileType, QOverload<int>::of(&KComboBox::currentIndexChanged), this, &ImportFileWidget::fileTypeChanged);
0452     connect(ui.cbUpdateType, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &ImportFileWidget::updateTypeChanged);
0453     connect(ui.cbReadingType, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &ImportFileWidget::readingTypeChanged);
0454     connect(ui.cbFilter, QOverload<int>::of(&KComboBox::activated), this, &ImportFileWidget::filterChanged);
0455     connect(ui.bRefreshPreview, &QPushButton::clicked, this, &ImportFileWidget::refreshPreview);
0456 
0457     if (m_asciiOptionsWidget)
0458         connect(m_asciiOptionsWidget.get(), &AsciiOptionsWidget::headerLineChanged, this, &ImportFileWidget::updateStartRow);
0459 
0460 #ifdef HAVE_MQTT
0461     connect(ui.cbConnection, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &ImportFileWidget::mqttConnectionChanged);
0462     connect(ui.cbFileType, QOverload<int>::of(&QComboBox::currentIndexChanged), [this]() {
0463         Q_EMIT checkFileType();
0464     });
0465     connect(ui.bManageConnections, &QPushButton::clicked, this, &ImportFileWidget::showMQTTConnectionManager);
0466     connect(ui.bLWT, &QPushButton::clicked, this, &ImportFileWidget::showWillSettings);
0467     connect(m_subscriptionWidget, &MQTTSubscriptionWidget::makeSubscription, this, &ImportFileWidget::subscribeTopic);
0468     connect(m_subscriptionWidget, &MQTTSubscriptionWidget::MQTTUnsubscribeFromTopic, this, &ImportFileWidget::unsubscribeTopic);
0469     connect(m_subscriptionWidget, &MQTTSubscriptionWidget::enableWill, this, &ImportFileWidget::enableWill);
0470     connect(m_subscriptionWidget, &MQTTSubscriptionWidget::subscriptionChanged, this, &ImportFileWidget::refreshPreview);
0471 #endif
0472 }
0473 
0474 /*!
0475  * \brief Called when the current target data containter was changed in ImportDilaog
0476  */
0477 void ImportFileWidget::dataContainerChanged(AbstractAspect* aspect) {
0478     m_targetContainer = aspect;
0479     updateHeaderOptions();
0480 }
0481 
0482 void ImportFileWidget::enableFirstRowAsColNames(bool enable) {
0483     ui.chbFirstRowAsColName->setEnabled(enable);
0484 }
0485 
0486 /*!
0487  *  update header specific options that are available for some filter types (ASCII, XLSX and Ods)
0488  *  and for some target data containers (Spreadsheet) only
0489  */
0490 void ImportFileWidget::updateHeaderOptions() {
0491     auto fileType = currentFileType();
0492     bool spreadsheet = true; // assume it's spreadsheet on default if no container is selected yet
0493     if (m_targetContainer)
0494         spreadsheet = m_targetContainer->type() == AspectType::Spreadsheet;
0495 
0496     // handle ASCII
0497     bool visible = (fileType == AbstractFileFilter::FileType::Ascii) && spreadsheet;
0498     if (m_asciiOptionsWidget)
0499         m_asciiOptionsWidget->showAsciiHeaderOptions(visible);
0500 
0501     // handle XLSX or ODS
0502     visible = (fileType == AbstractFileFilter::FileType::XLSX || fileType == AbstractFileFilter::FileType::Ods) && spreadsheet;
0503     ui.lFirstRowAsColNames->setVisible(visible);
0504     ui.chbFirstRowAsColName->setVisible(visible);
0505 }
0506 
0507 void ImportFileWidget::showJsonModel(bool b) {
0508     ui.tvJson->setVisible(b);
0509     ui.lField->setVisible(b);
0510 }
0511 
0512 void ImportFileWidget::showOptions(bool b) {
0513     ui.gbOptions->setVisible(b);
0514 
0515     if (m_liveDataSource)
0516         ui.gbUpdateOptions->setVisible(b);
0517 
0518     resize(layout()->minimumSize());
0519 }
0520 
0521 QString ImportFileWidget::fileName() const {
0522     return m_cbFileName->currentText();
0523 }
0524 
0525 QString ImportFileWidget::dbcFileName() const {
0526     return m_cbDBCFileName->currentText();
0527 }
0528 
0529 QString ImportFileWidget::selectedObject() const {
0530     DEBUG(Q_FUNC_INFO)
0531     const QString& path = fileName();
0532 
0533     // determine the file name only
0534     QString name = path.right(path.length() - path.lastIndexOf(QLatin1Char('/')) - 1);
0535 
0536     // strip away the extension if existing
0537     if (name.indexOf(QLatin1Char('.')) != -1)
0538         name = name.left(name.lastIndexOf(QLatin1Char('.')));
0539 
0540     // for multi-dimensional formats add the currently selected object
0541     const auto format = currentFileType();
0542     if (format == AbstractFileFilter::FileType::HDF5) {
0543         const QStringList& names = m_hdf5OptionsWidget->selectedNames();
0544         if (!names.isEmpty())
0545             name += names.first(); // the names of the selected HDF5 objects already have '/'
0546     } else if (format == AbstractFileFilter::FileType::NETCDF) {
0547         const QStringList& names = m_netcdfOptionsWidget->selectedNames();
0548         if (!names.isEmpty())
0549             name += QLatin1Char('/') + names.first();
0550     } else if (format == AbstractFileFilter::FileType::FITS) {
0551         const QString& extensionName = m_fitsOptionsWidget->currentExtensionName();
0552         if (!extensionName.isEmpty())
0553             name += QLatin1Char('/') + extensionName;
0554     } else if (format == AbstractFileFilter::FileType::ROOT) {
0555         const QStringList& names = m_rootOptionsWidget->selectedNames();
0556         if (!names.isEmpty())
0557             name += QLatin1Char('/') + names.first();
0558     } else if (format == AbstractFileFilter::FileType::MATIO) {
0559         const QStringList& names = m_matioOptionsWidget->selectedNames();
0560         if (!names.isEmpty())
0561             name += QLatin1Char('/') + names.first();
0562     } else if (format == AbstractFileFilter::FileType::XLSX) {
0563         const auto& names = m_xlsxOptionsWidget->selectedXLSXRegionNames();
0564         if (!names.isEmpty())
0565             name += QLatin1Char('/') + names.first();
0566     } else if (format == AbstractFileFilter::FileType::Ods) {
0567         const auto& names = m_odsOptionsWidget->selectedOdsSheetNames();
0568         QDEBUG(Q_FUNC_INFO << ", selected sheet names =")
0569         if (!names.isEmpty()) { // name == "start-end", names.first() == "start-end.ods!Sheet2"
0570             name += QLatin1Char('!') + names.first().split(QLatin1Char('!')).last();
0571         }
0572     }
0573     return name;
0574 }
0575 
0576 /*!
0577  * returns \c true if the number of lines to be imported from the currently selected file is zero ("file is empty"),
0578  * returns \c false otherwise.
0579  */
0580 bool ImportFileWidget::importValid() const {
0581     return m_importValid;
0582 }
0583 
0584 QString ImportFileWidget::host() const {
0585     return ui.leHost->text();
0586 }
0587 
0588 QString ImportFileWidget::port() const {
0589     return ui.lePort->text();
0590 }
0591 
0592 QString ImportFileWidget::serialPort() const {
0593     return ui.cbSerialPort->currentText();
0594 }
0595 
0596 int ImportFileWidget::baudRate() const {
0597     return ui.cbBaudRate->currentText().toInt();
0598 }
0599 
0600 /*!
0601     saves the settings to the data source \c source.
0602 */
0603 void ImportFileWidget::saveSettings(LiveDataSource* source) const {
0604     auto fileType = currentFileType();
0605     auto updateType = static_cast<LiveDataSource::UpdateType>(ui.cbUpdateType->currentIndex());
0606     auto sourceType = currentSourceType();
0607     auto readingType = static_cast<LiveDataSource::ReadingType>(ui.cbReadingType->currentIndex());
0608 
0609     source->setFileType(fileType);
0610     currentFileFilter();
0611     source->setFilter(m_currentFilter.release()); // pass ownership of the filter to the LiveDataSource
0612 
0613     source->setSourceType(sourceType);
0614 
0615     switch (sourceType) {
0616     case LiveDataSource::SourceType::FileOrPipe:
0617         source->setFileName(fileName());
0618         source->setFileLinked(ui.chbLinkFile->isChecked());
0619         source->setComment(fileName());
0620         if (m_liveDataSource)
0621             source->setUseRelativePath(ui.chbRelativePath->isChecked());
0622         break;
0623     case LiveDataSource::SourceType::LocalSocket:
0624         source->setFileName(fileName());
0625         source->setLocalSocketName(fileName());
0626         source->setComment(fileName());
0627         break;
0628     case LiveDataSource::SourceType::NetworkTCPSocket:
0629     case LiveDataSource::SourceType::NetworkUDPSocket:
0630         source->setHost(ui.leHost->text());
0631         source->setPort((quint16)ui.lePort->text().toInt());
0632         break;
0633     case LiveDataSource::SourceType::SerialPort:
0634         source->setBaudRate(ui.cbBaudRate->currentText().toInt());
0635         source->setSerialPort(ui.cbSerialPort->currentText());
0636         break;
0637     case LiveDataSource::SourceType::MQTT:
0638         break;
0639     }
0640 
0641     // reading options
0642     source->setReadingType(readingType);
0643     source->setKeepNValues(ui.sbKeepNValues->value());
0644     source->setUpdateType(updateType);
0645     if (updateType == LiveDataSource::UpdateType::TimeInterval)
0646         source->setUpdateInterval(ui.sbUpdateInterval->value());
0647 
0648     if (readingType != LiveDataSource::ReadingType::TillEnd)
0649         source->setSampleSize(ui.sbSampleSize->value());
0650 }
0651 
0652 /*!
0653     returns the currently used file type.
0654 */
0655 AbstractFileFilter::FileType ImportFileWidget::currentFileType() const {
0656     return static_cast<AbstractFileFilter::FileType>(ui.cbFileType->currentData().toInt());
0657 }
0658 
0659 LiveDataSource::SourceType ImportFileWidget::currentSourceType() const {
0660     return static_cast<LiveDataSource::SourceType>(ui.cbSourceType->currentIndex());
0661 }
0662 
0663 /*!
0664     returns the currently used filter.
0665 */
0666 AbstractFileFilter* ImportFileWidget::currentFileFilter() const {
0667     auto fileType = currentFileType();
0668     if (m_currentFilter && m_currentFilter->type() != fileType)
0669         m_currentFilter.reset();
0670 
0671     switch (fileType) {
0672     case AbstractFileFilter::FileType::Ascii: {
0673         DEBUG(Q_FUNC_INFO << ", ASCII");
0674         if (!m_currentFilter)
0675             m_currentFilter.reset(new AsciiFilter);
0676         auto filter = static_cast<AsciiFilter*>(m_currentFilter.get());
0677 
0678         if (ui.cbFilter->currentIndex() == 0) //"automatic"
0679             filter->setAutoModeEnabled(true);
0680         else { //"custom" and templates
0681             filter->setAutoModeEnabled(false);
0682 
0683             // set the data portion to import
0684             filter->setStartRow(ui.sbStartRow->value());
0685             filter->setEndRow(ui.sbEndRow->value());
0686             filter->setStartColumn(ui.sbStartColumn->value());
0687             filter->setEndColumn(ui.sbEndColumn->value());
0688 
0689             // set the remaining filter settings
0690             if (m_asciiOptionsWidget)
0691                 m_asciiOptionsWidget->applyFilterSettings(filter);
0692         }
0693 
0694         break;
0695     }
0696     case AbstractFileFilter::FileType::Binary: {
0697         DEBUG(Q_FUNC_INFO << ", Binary");
0698         if (!m_currentFilter)
0699             m_currentFilter.reset(new BinaryFilter);
0700         auto filter = static_cast<BinaryFilter*>(m_currentFilter.get());
0701 
0702         if (ui.cbFilter->currentIndex() == 0) //"automatic"
0703             filter->setAutoModeEnabled(true);
0704         else { //"custom" and templates
0705             filter->setAutoModeEnabled(false);
0706             if (m_binaryOptionsWidget)
0707                 m_binaryOptionsWidget->applyFilterSettings(filter);
0708         }
0709 
0710         filter->setStartRow(ui.sbStartRow->value());
0711         filter->setEndRow(ui.sbEndRow->value());
0712 
0713         break;
0714     }
0715     case AbstractFileFilter::FileType::XLSX: {
0716         DEBUG(Q_FUNC_INFO << ", XLSX");
0717 
0718         if (!m_currentFilter)
0719             m_currentFilter.reset(new XLSXFilter);
0720 
0721         auto filter = static_cast<XLSXFilter*>(m_currentFilter.get());
0722         filter->setStartRow(ui.sbStartRow->value());
0723         filter->setEndRow(ui.sbEndRow->value());
0724         filter->setStartColumn(ui.sbStartColumn->value());
0725         filter->setEndColumn(ui.sbEndColumn->value());
0726         filter->setFirstRowAsColumnNames(ui.chbFirstRowAsColName->isChecked());
0727 
0728         const auto& sxrn = selectedXLSXRegionNames();
0729         if (!sxrn.isEmpty()) {
0730             const auto& firstRegion = sxrn.last();
0731             const auto& nameSplit = firstRegion.split(QLatin1Char('!'));
0732             const auto& sheet = nameSplit.at(0);
0733             const auto& range = nameSplit.at(1);
0734             filter->setCurrentRange(range);
0735             filter->setCurrentSheet(sheet);
0736         }
0737 
0738         break;
0739     }
0740     case AbstractFileFilter::FileType::Ods: {
0741         DEBUG(Q_FUNC_INFO << ", ODS");
0742 
0743         if (!m_currentFilter)
0744             m_currentFilter.reset(new OdsFilter);
0745 
0746         auto filter = static_cast<OdsFilter*>(m_currentFilter.get());
0747         filter->setStartRow(ui.sbStartRow->value());
0748         filter->setEndRow(ui.sbEndRow->value());
0749         filter->setStartColumn(ui.sbStartColumn->value());
0750         filter->setEndColumn(ui.sbEndColumn->value());
0751         filter->setFirstRowAsColumnNames(ui.chbFirstRowAsColName->isChecked());
0752 
0753         const auto& sorn = selectedOdsSheetNames();
0754         QDEBUG(Q_FUNC_INFO << ", selected Ods sheet names = " << sorn)
0755         if (!sorn.isEmpty())
0756             filter->setSelectedSheetNames(sorn);
0757 
0758         break;
0759     }
0760     case AbstractFileFilter::FileType::Image: {
0761         DEBUG(Q_FUNC_INFO << ", Image");
0762         if (!m_currentFilter)
0763             m_currentFilter.reset(new ImageFilter);
0764         auto filter = static_cast<ImageFilter*>(m_currentFilter.get());
0765 
0766         filter->setImportFormat(m_imageOptionsWidget->currentFormat());
0767         filter->setStartRow(ui.sbStartRow->value());
0768         filter->setEndRow(ui.sbEndRow->value());
0769         filter->setStartColumn(ui.sbStartColumn->value());
0770         filter->setEndColumn(ui.sbEndColumn->value());
0771 
0772         break;
0773     }
0774     case AbstractFileFilter::FileType::HDF5: {
0775         DEBUG(Q_FUNC_INFO << ", HDF5");
0776         if (!m_currentFilter)
0777             m_currentFilter.reset(new HDF5Filter);
0778         auto filter = static_cast<HDF5Filter*>(m_currentFilter.get());
0779         QStringList names = selectedHDF5Names();
0780         QDEBUG(Q_FUNC_INFO << ", selected HDF5 names =" << names);
0781         if (!names.isEmpty())
0782             filter->setCurrentDataSetName(names.at(0));
0783         filter->setStartRow(ui.sbStartRow->value());
0784         filter->setEndRow(ui.sbEndRow->value());
0785         filter->setStartColumn(ui.sbStartColumn->value());
0786         filter->setEndColumn(ui.sbEndColumn->value());
0787         DEBUG(Q_FUNC_INFO << ", OK");
0788 
0789         break;
0790     }
0791     case AbstractFileFilter::FileType::NETCDF: {
0792         DEBUG(Q_FUNC_INFO << ", NetCDF");
0793         if (!m_currentFilter)
0794             m_currentFilter.reset(new NetCDFFilter);
0795         auto filter = static_cast<NetCDFFilter*>(m_currentFilter.get());
0796 
0797         if (!selectedNetCDFNames().isEmpty())
0798             filter->setCurrentVarName(selectedNetCDFNames()[0]);
0799         filter->setStartRow(ui.sbStartRow->value());
0800         filter->setEndRow(ui.sbEndRow->value());
0801         filter->setStartColumn(ui.sbStartColumn->value());
0802         filter->setEndColumn(ui.sbEndColumn->value());
0803 
0804         break;
0805     }
0806     case AbstractFileFilter::FileType::VECTOR_BLF: {
0807         DEBUG(Q_FUNC_INFO << ", VECTOR_BLF");
0808         if (!m_currentFilter) {
0809             auto filter = new VectorBLFFilter;
0810             filter->setDBCFile(dbcFileName());
0811             m_currentFilter.reset(filter);
0812         }
0813         auto filter = static_cast<VectorBLFFilter*>(m_currentFilter.get());
0814         if (m_canOptionsWidget)
0815             m_canOptionsWidget->applyFilterSettings(filter);
0816 
0817         break;
0818     }
0819     case AbstractFileFilter::FileType::FITS: {
0820         DEBUG(Q_FUNC_INFO << ", FITS");
0821         if (!m_currentFilter)
0822             m_currentFilter.reset(new FITSFilter);
0823         auto filter = static_cast<FITSFilter*>(m_currentFilter.get());
0824         filter->setStartRow(ui.sbStartRow->value());
0825         filter->setEndRow(ui.sbEndRow->value());
0826         filter->setStartColumn(ui.sbStartColumn->value());
0827         filter->setEndColumn(ui.sbEndColumn->value());
0828 
0829         break;
0830     }
0831     case AbstractFileFilter::FileType::JSON: {
0832         DEBUG(Q_FUNC_INFO << ", JSON");
0833         if (!m_currentFilter)
0834             m_currentFilter.reset(new JsonFilter);
0835         auto filter = static_cast<JsonFilter*>(m_currentFilter.get());
0836         m_jsonOptionsWidget->applyFilterSettings(filter, ui.tvJson->currentIndex());
0837 
0838         filter->setStartRow(ui.sbStartRow->value());
0839         filter->setEndRow(ui.sbEndRow->value());
0840         filter->setStartColumn(ui.sbStartColumn->value());
0841         filter->setEndColumn(ui.sbEndColumn->value());
0842 
0843         break;
0844     }
0845     case AbstractFileFilter::FileType::ROOT: {
0846         DEBUG(Q_FUNC_INFO << ", ROOT");
0847         if (!m_currentFilter)
0848             m_currentFilter.reset(new ROOTFilter);
0849         auto filter = static_cast<ROOTFilter*>(m_currentFilter.get());
0850         QStringList names = selectedROOTNames();
0851         if (!names.isEmpty())
0852             filter->setCurrentObject(names.first());
0853 
0854         filter->setStartRow(m_rootOptionsWidget->startRow());
0855         filter->setEndRow(m_rootOptionsWidget->endRow());
0856         filter->setColumns(m_rootOptionsWidget->columns());
0857 
0858         break;
0859     }
0860     case AbstractFileFilter::FileType::Spice: {
0861         DEBUG(Q_FUNC_INFO << ", Spice");
0862         if (!m_currentFilter)
0863             m_currentFilter.reset(new SpiceFilter());
0864         auto filter = static_cast<SpiceFilter*>(m_currentFilter.get());
0865         filter->setStartRow(ui.sbStartRow->value());
0866         filter->setEndRow(ui.sbEndRow->value());
0867 
0868         break;
0869     }
0870     case AbstractFileFilter::FileType::READSTAT: {
0871         DEBUG(Q_FUNC_INFO << ", READSTAT");
0872         if (!m_currentFilter)
0873             m_currentFilter.reset(new ReadStatFilter);
0874         auto filter = static_cast<ReadStatFilter*>(m_currentFilter.get());
0875         filter->setStartRow(ui.sbStartRow->value());
0876         filter->setEndRow(ui.sbEndRow->value());
0877         filter->setStartColumn(ui.sbStartColumn->value());
0878         filter->setEndColumn(ui.sbEndColumn->value());
0879 
0880         break;
0881     }
0882     case AbstractFileFilter::FileType::MATIO: {
0883         DEBUG(Q_FUNC_INFO << ", MATIO");
0884         if (!m_currentFilter)
0885             m_currentFilter.reset(new MatioFilter);
0886         auto filter = static_cast<MatioFilter*>(m_currentFilter.get());
0887         if (!selectedMatioNames().isEmpty())
0888             filter->setSelectedVarNames(selectedMatioNames());
0889         filter->setStartRow(ui.sbStartRow->value());
0890         filter->setEndRow(ui.sbEndRow->value());
0891         filter->setStartColumn(ui.sbStartColumn->value());
0892         filter->setEndColumn(ui.sbEndColumn->value());
0893 
0894         break;
0895     }
0896     }
0897 
0898     return m_currentFilter.get();
0899 }
0900 
0901 /*!
0902     opens a file dialog and lets the user select the file data source.
0903 */
0904 void ImportFileWidget::selectFile() {
0905     DEBUG(Q_FUNC_INFO)
0906     KConfigGroup conf = Settings::group(QStringLiteral("ImportFileWidget"));
0907     const QString& dir = conf.readEntry(QStringLiteral("LastDir"), "");
0908     const QString& path = QFileDialog::getOpenFileName(this, i18nc("@title:window", "Select the File Data Source"), dir);
0909     DEBUG(" dir = " << STDSTRING(dir))
0910     DEBUG(" path = " << STDSTRING(path))
0911     if (path.isEmpty()) // cancel was clicked in the file-dialog
0912         return;
0913 
0914     int pos = path.lastIndexOf(QLatin1Char('/'));
0915     if (pos != -1) {
0916         QString newDir = path.left(pos);
0917         if (newDir != dir)
0918             conf.writeEntry(QStringLiteral("LastDir"), newDir);
0919     }
0920 
0921     // process all events after the FileDialog was closed to repaint the widget
0922     // before we start calculating the preview
0923     QApplication::processEvents(QEventLoop::AllEvents, 0);
0924 
0925     QStringList urls = m_cbFileName->urls();
0926     urls.insert(0, QUrl::fromLocalFile(path).url()); // add type of path
0927     m_cbFileName->setUrls(urls);
0928     m_cbFileName->setCurrentText(urls.first());
0929     DEBUG(" combobox text = " << STDSTRING(m_cbFileName->currentText()))
0930     fileNameChanged(path); // why do I have to call this function separately
0931 }
0932 
0933 void ImportFileWidget::selectDBCFile() {
0934     DEBUG(Q_FUNC_INFO)
0935     const QString entry = QStringLiteral("DBCDir");
0936     KConfigGroup conf = Settings::group(QStringLiteral("ImportFileWidget"));
0937     const QString& dir = conf.readEntry(entry, "");
0938     const QString& path = QFileDialog::getOpenFileName(this, i18nc("@title:window", "Select the DBC file"), dir, i18n("DBC file (*.dbc)"));
0939     DEBUG(" dir = " << STDSTRING(dir))
0940     DEBUG(" path = " << STDSTRING(path))
0941     if (path.isEmpty()) // cancel was clicked in the file-dialog
0942         return;
0943 
0944     int pos = path.lastIndexOf(QLatin1Char('/'));
0945     if (pos != -1) {
0946         QString newDir = path.left(pos);
0947         if (newDir != dir)
0948             conf.writeEntry(entry, newDir);
0949     }
0950 
0951     // process all events after the FileDialog was closed to repaint the widget
0952     // before we start calculating the preview
0953     QApplication::processEvents(QEventLoop::AllEvents, 0);
0954 
0955     QStringList urls = m_cbDBCFileName->urls();
0956     urls.insert(0, QUrl::fromLocalFile(path).url()); // add type of path
0957     m_cbDBCFileName->setUrls(urls);
0958     m_cbDBCFileName->setCurrentText(urls.first());
0959     DEBUG(" combobox text = " << STDSTRING(m_cbDBCFileName->currentText()))
0960 
0961     refreshPreview();
0962 }
0963 
0964 /*!
0965     hides the MQTT related items of the widget
0966 */
0967 void ImportFileWidget::setMQTTVisible(bool visible) {
0968     ui.lConnections->setVisible(visible);
0969     ui.cbConnection->setVisible(visible);
0970     ui.bManageConnections->setVisible(visible);
0971 
0972     // topics
0973     if (ui.cbConnection->currentIndex() != -1 && visible) {
0974         ui.lTopics->setVisible(true);
0975         ui.frameSubscriptions->setVisible(true);
0976 #ifdef HAVE_MQTT
0977         m_subscriptionWidget->setVisible(true);
0978         m_subscriptionWidget->makeVisible(true);
0979 #endif
0980     } else {
0981         ui.lTopics->setVisible(false);
0982         ui.frameSubscriptions->setVisible(false);
0983 #ifdef HAVE_MQTT
0984         m_subscriptionWidget->setVisible(false);
0985         m_subscriptionWidget->makeVisible(false);
0986 #endif
0987     }
0988 
0989     // will message
0990     ui.lLWT->setVisible(visible);
0991     ui.bLWT->setVisible(visible);
0992 }
0993 
0994 /************** SLOTS **************************************************************/
0995 /*!
0996     called on file name changes.
0997     Determines the file format (ASCII, binary etc.), if the file exists,
0998     and activates the corresponding options.
0999 */
1000 void ImportFileWidget::fileNameChanged(const QString& name) {
1001     DEBUG(Q_FUNC_INFO << ", file name = " << STDSTRING(name))
1002     Q_EMIT error(QString()); // clear previous errors
1003 
1004     const QString fileName = absolutePath(name);
1005 
1006     bool fileExists = QFile::exists(fileName);
1007     ui.gbOptions->setEnabled(fileExists);
1008     ui.cbFilter->setEnabled(fileExists);
1009     ui.cbFileType->setEnabled(fileExists);
1010     ui.bFileInfo->setEnabled(fileExists);
1011     ui.gbUpdateOptions->setEnabled(fileExists);
1012     if (!fileExists) {
1013         // file doesn't exist -> delete the content preview that is still potentially
1014         // available from the previously selected file
1015         ui.tePreview->clear();
1016         m_twPreview->clear();
1017         initOptionsWidget();
1018 
1019         Q_EMIT fileNameChanged();
1020         return;
1021     }
1022 
1023     if (currentSourceType() == LiveDataSource::SourceType::FileOrPipe) {
1024         const auto fileType = AbstractFileFilter::fileType(fileName);
1025         for (int i = 0; i < ui.cbFileType->count(); ++i) {
1026             if (static_cast<AbstractFileFilter::FileType>(ui.cbFileType->itemData(i).toInt()) == fileType) {
1027                 // automatically select a new file type
1028                 if (ui.cbFileType->currentIndex() != i) {
1029                     ui.cbFileType->setCurrentIndex(i); // will call the slot fileTypeChanged which updates content and preview
1030 
1031                     // automatically set the comma separator if a csv file was selected
1032                     if (fileType == AbstractFileFilter::FileType::Ascii && name.endsWith(QLatin1String("csv"), Qt::CaseInsensitive))
1033                         m_asciiOptionsWidget->setSeparatingCharacter(QLatin1Char(','));
1034 
1035                     Q_EMIT fileNameChanged();
1036                     return;
1037                 } else {
1038                     initOptionsWidget();
1039 
1040                     // automatically set the comma separator if a csv file was selected
1041                     if (fileType == AbstractFileFilter::FileType::Ascii && name.endsWith(QLatin1String("csv"), Qt::CaseInsensitive))
1042                         m_asciiOptionsWidget->setSeparatingCharacter(QLatin1Char(','));
1043 
1044                     updateContent(fileName);
1045                     break;
1046                 }
1047             }
1048         }
1049     }
1050 
1051     Q_EMIT fileNameChanged();
1052     refreshPreview();
1053 }
1054 
1055 /*!
1056   saves the current filter settings as a template
1057 */
1058 void ImportFileWidget::saveConfigAsTemplate(KConfig& config) {
1059     auto fileType = currentFileType();
1060     KConfigGroup group;
1061     if (fileType == AbstractFileFilter::FileType::Ascii) {
1062         m_asciiOptionsWidget->saveConfigAsTemplate(config);
1063         group = config.group(QLatin1String("ImportAscii"));
1064     } else if (fileType == AbstractFileFilter::FileType::Binary) {
1065         m_binaryOptionsWidget->saveConfigAsTemplate(config);
1066         group = config.group(QLatin1String("ImportBinary"));
1067     }
1068 
1069     // save additionally the "data portion to read"-settings which are not
1070     // part of the options widgets and were not saved above
1071     group.writeEntry(QLatin1String("StartRow"), ui.sbStartRow->value());
1072     group.writeEntry(QLatin1String("EndRow"), ui.sbStartRow->value());
1073     group.writeEntry(QLatin1String("StartColumn"), ui.sbStartRow->value());
1074     group.writeEntry(QLatin1String("EndColumn"), ui.sbStartRow->value());
1075 
1076     // add the currently added name of the template and make it current
1077     auto name = TemplateHandler::templateName(config);
1078     ui.cbFilter->addItem(name);
1079     ui.cbFilter->setCurrentText(name);
1080 }
1081 
1082 /*!
1083   loads the settings for the current filter from a template
1084 */
1085 void ImportFileWidget::loadConfigFromTemplate(KConfig& config) {
1086     auto fileType = currentFileType();
1087     KConfigGroup group;
1088     if (fileType == AbstractFileFilter::FileType::Ascii) {
1089         m_asciiOptionsWidget->loadConfigFromTemplate(config);
1090         group = config.group(QLatin1String("ImportAscii"));
1091     } else if (fileType == AbstractFileFilter::FileType::Binary) {
1092         m_binaryOptionsWidget->loadConfigFromTemplate(config);
1093         group = config.group(QLatin1String("ImportBinary"));
1094     }
1095 
1096     // load additionally the "data portion to read"-settings which are not
1097     // part of the options widgets and were not loaded above
1098     ui.sbStartRow->setValue(group.readEntry(QLatin1String("StartRow"), -1));
1099     ui.sbEndRow->setValue(group.readEntry(QLatin1String("EndRow"), -1));
1100     ui.sbStartColumn->setValue(group.readEntry(QLatin1String("StartColumn"), -1));
1101     ui.sbEndColumn->setValue(group.readEntry(QLatin1String("EndColumn"), -1));
1102 }
1103 
1104 /*!
1105  * \brief ImportFileWidget::hidePropertyWidgets
1106  * Hide all Widgets related to the properties. This should be the default
1107  * Currently every filetype is turning off, but it makes sense to turn on
1108  * only when needed
1109  * TODO: move all widgets in here
1110  */
1111 void ImportFileWidget::hidePropertyWidgets() {
1112     ui.lDBCDatabase->hide();
1113     ui.bOpenDBC->hide();
1114     m_cbDBCFileName->hide();
1115     ui.lWarningLimitedMessages->hide();
1116 }
1117 
1118 /*!
1119     Depending on the selected file type, activates the corresponding options in the data portion tab
1120     and populates the combobox with the available pre-defined filter settings for the selected type.
1121 */
1122 void ImportFileWidget::fileTypeChanged(int /*index*/) {
1123     auto fileType = currentFileType();
1124     DEBUG(Q_FUNC_INFO << ", " << ENUM_TO_STRING(AbstractFileFilter, FileType, fileType));
1125     Q_EMIT error(QString()); // clear the potential error message that was shown for the previous file type
1126     initOptionsWidget();
1127 
1128     // enable the options widgets, should be avaible for all types where there is no "automatic" vs "custom",
1129     // will be disabled for "automatic" for the relevant data types
1130     ui.swOptions->setEnabled(true);
1131 
1132     // default
1133     hidePropertyWidgets();
1134     ui.lFilter->hide();
1135     ui.cbFilter->hide();
1136     m_templateHandler->hide();
1137 
1138     // different file types show different number of tabs in ui.tabWidget.
1139     // when switching from the previous file type we re-set the tab widget to its original state
1140     // and remove/add the required tabs further below
1141     for (int i = 0; i < ui.tabWidget->count(); ++i)
1142         ui.tabWidget->removeTab(0);
1143 
1144     ui.tabWidget->addTab(ui.tabDataFormat, i18n("Data Format"));
1145     ui.tabWidget->addTab(ui.tabDataPreview, i18n("Preview"));
1146     if (!m_liveDataSource)
1147         ui.tabWidget->addTab(ui.tabDataPortion, i18n("Data Portion to Read"));
1148 
1149     ui.lPreviewLines->show();
1150     ui.sbPreviewLines->show();
1151     ui.lStartColumn->show();
1152     ui.sbStartColumn->show();
1153     ui.lEndColumn->show();
1154     ui.sbEndColumn->show();
1155 
1156     showJsonModel(false);
1157 
1158     switch (fileType) {
1159     case AbstractFileFilter::FileType::Ascii:
1160         ui.lFilter->show();
1161         ui.cbFilter->show();
1162         m_templateHandler->show();
1163         m_templateHandler->setClassName(QLatin1String("AsciiFilter"));
1164         break;
1165     case AbstractFileFilter::FileType::Binary:
1166         ui.lFilter->show();
1167         ui.cbFilter->show();
1168         m_templateHandler->show();
1169         m_templateHandler->setClassName(QLatin1String("BinaryFilter"));
1170         ui.lStartColumn->hide();
1171         ui.sbStartColumn->hide();
1172         ui.lEndColumn->hide();
1173         ui.sbEndColumn->hide();
1174         break;
1175     case AbstractFileFilter::FileType::ROOT:
1176         ui.tabWidget->removeTab(1);
1177     // falls through
1178     case AbstractFileFilter::FileType::HDF5:
1179     case AbstractFileFilter::FileType::NETCDF:
1180     case AbstractFileFilter::FileType::FITS:
1181     case AbstractFileFilter::FileType::MATIO:
1182     case AbstractFileFilter::FileType::XLSX:
1183     case AbstractFileFilter::FileType::Ods:
1184         ui.lFilter->hide();
1185         ui.cbFilter->hide();
1186         // hide global preview tab. we have our own
1187         ui.tabWidget->setTabText(0, i18n("Data format && preview"));
1188         ui.tabWidget->removeTab(1);
1189         ui.tabWidget->setCurrentIndex(0);
1190         break;
1191     case AbstractFileFilter::FileType::VECTOR_BLF:
1192         ui.lDBCDatabase->show();
1193         ui.bOpenDBC->show();
1194         m_cbDBCFileName->show();
1195         ui.lWarningLimitedMessages->show();
1196         ui.lStartColumn->hide();
1197         ui.sbStartColumn->hide();
1198         ui.lEndColumn->hide();
1199         ui.sbEndColumn->hide();
1200         ui.tabWidget->setCurrentIndex(0);
1201         break;
1202     case AbstractFileFilter::FileType::Image:
1203         ui.lPreviewLines->hide();
1204         ui.sbPreviewLines->hide();
1205         break;
1206     case AbstractFileFilter::FileType::Spice:
1207         ui.lStartColumn->hide();
1208         ui.sbStartColumn->hide();
1209         ui.lEndColumn->hide();
1210         ui.sbEndColumn->hide();
1211         ui.tabWidget->removeTab(0);
1212         ui.tabWidget->setCurrentIndex(0);
1213         break;
1214     case AbstractFileFilter::FileType::JSON:
1215         showJsonModel(true);
1216         break;
1217     case AbstractFileFilter::FileType::READSTAT:
1218         ui.tabWidget->removeTab(0);
1219         ui.tabWidget->setCurrentIndex(0);
1220         break;
1221     }
1222 
1223     // update header specific options that are available for some filter types
1224     // and for some target data containers (Spreadsheet) only
1225     updateHeaderOptions();
1226 
1227     if (fileType == AbstractFileFilter::FileType::Ascii || fileType == AbstractFileFilter::FileType::Binary) {
1228         int lastUsedFilterIndex = ui.cbFilter->currentIndex();
1229         ui.cbFilter->clear();
1230         ui.cbFilter->addItem(i18n("Automatic"));
1231         ui.cbFilter->addItem(i18n("Custom"));
1232 
1233         // add templates
1234         const auto& names = m_templateHandler->templateNames();
1235         if (!names.isEmpty()) {
1236             ui.cbFilter->insertSeparator(2);
1237             ui.cbFilter->addItems(names);
1238         }
1239 
1240         if (lastUsedFilterIndex != -1) {
1241             // if one of the custom and filter specific templates was selected, switch to "Automatic" when
1242             // switching to a different file/filter type and keep the previous selection "Automatic" or "Custom" otherwise
1243             if (lastUsedFilterIndex > 2)
1244                 lastUsedFilterIndex = 0;
1245 
1246             ui.cbFilter->setCurrentIndex(lastUsedFilterIndex);
1247             filterChanged(lastUsedFilterIndex);
1248         }
1249     }
1250 
1251     if (currentSourceType() == LiveDataSource::SourceType::FileOrPipe) {
1252         const QString& file = absolutePath(fileName());
1253         if (QFile::exists(file))
1254             updateContent(file);
1255     }
1256 
1257     // for file types other than ASCII and binary we support re-reading the whole file only
1258     // select "read whole file" and deactivate the combobox
1259     if (m_liveDataSource && (fileType != AbstractFileFilter::FileType::Ascii && fileType != AbstractFileFilter::FileType::Binary)) {
1260         ui.cbReadingType->setCurrentIndex(static_cast<int>(LiveDataSource::ReadingType::WholeFile));
1261         ui.cbReadingType->setEnabled(false);
1262     } else
1263         ui.cbReadingType->setEnabled(true);
1264 
1265     refreshPreview();
1266 }
1267 
1268 // file type specific option widgets
1269 void ImportFileWidget::initOptionsWidget() {
1270     DEBUG(Q_FUNC_INFO << ", for " << ENUM_TO_STRING(AbstractFileFilter, FileType, currentFileType()));
1271     switch (currentFileType()) {
1272     case AbstractFileFilter::FileType::Ascii: {
1273         if (!m_asciiOptionsWidget) {
1274             auto* asciiw = new QWidget();
1275             m_asciiOptionsWidget = std::unique_ptr<AsciiOptionsWidget>(new AsciiOptionsWidget(asciiw));
1276             m_asciiOptionsWidget->loadSettings();
1277 
1278             // allow to add timestamp column for live data sources
1279             if (m_liveDataSource)
1280                 m_asciiOptionsWidget->showTimestampOptions(true);
1281             ui.swOptions->addWidget(asciiw);
1282         }
1283 
1284         ui.swOptions->setCurrentWidget(m_asciiOptionsWidget->parentWidget());
1285         break;
1286     }
1287     case AbstractFileFilter::FileType::Binary:
1288         if (!m_binaryOptionsWidget) {
1289             auto* binaryw = new QWidget();
1290             m_binaryOptionsWidget = std::unique_ptr<BinaryOptionsWidget>(new BinaryOptionsWidget(binaryw));
1291             ui.swOptions->addWidget(binaryw);
1292             m_binaryOptionsWidget->loadSettings();
1293         }
1294         ui.swOptions->setCurrentWidget(m_binaryOptionsWidget->parentWidget());
1295         break;
1296     case AbstractFileFilter::FileType::Image:
1297         if (!m_imageOptionsWidget) {
1298             auto* imagew = new QWidget();
1299             m_imageOptionsWidget = std::unique_ptr<ImageOptionsWidget>(new ImageOptionsWidget(imagew));
1300             ui.swOptions->addWidget(imagew);
1301             m_imageOptionsWidget->loadSettings();
1302         }
1303         ui.swOptions->setCurrentWidget(m_imageOptionsWidget->parentWidget());
1304         break;
1305     case AbstractFileFilter::FileType::XLSX:
1306         if (!m_xlsxOptionsWidget) {
1307             QWidget* xlsxw = new QWidget();
1308             m_xlsxOptionsWidget = std::unique_ptr<XLSXOptionsWidget>(new XLSXOptionsWidget(xlsxw, this));
1309             ui.swOptions->addWidget(xlsxw);
1310             connect(dynamic_cast<XLSXOptionsWidget*>(m_xlsxOptionsWidget.get()),
1311                     &XLSXOptionsWidget::enableDataPortionSelection,
1312                     this,
1313                     &ImportFileWidget::enableDataPortionSelection);
1314         }
1315         ui.swOptions->setCurrentWidget(m_xlsxOptionsWidget->parentWidget());
1316         break;
1317     case AbstractFileFilter::FileType::Ods:
1318         if (!m_odsOptionsWidget) {
1319             QWidget* odsw = new QWidget();
1320             m_odsOptionsWidget = std::unique_ptr<OdsOptionsWidget>(new OdsOptionsWidget(odsw, this));
1321             ui.swOptions->addWidget(odsw);
1322             connect(dynamic_cast<OdsOptionsWidget*>(m_odsOptionsWidget.get()),
1323                     &OdsOptionsWidget::enableDataPortionSelection,
1324                     this,
1325                     &ImportFileWidget::enableDataPortionSelection);
1326         }
1327         ui.swOptions->setCurrentWidget(m_odsOptionsWidget->parentWidget());
1328         break;
1329     case AbstractFileFilter::FileType::HDF5:
1330         if (!m_hdf5OptionsWidget) {
1331             auto* hdf5w = new QWidget();
1332             m_hdf5OptionsWidget = std::unique_ptr<HDF5OptionsWidget>(new HDF5OptionsWidget(hdf5w, this));
1333             ui.swOptions->addWidget(hdf5w);
1334         } else
1335             m_hdf5OptionsWidget->clear();
1336         ui.swOptions->setCurrentWidget(m_hdf5OptionsWidget->parentWidget());
1337         break;
1338     case AbstractFileFilter::FileType::NETCDF:
1339         if (!m_netcdfOptionsWidget) {
1340             auto* netcdfw = new QWidget();
1341             m_netcdfOptionsWidget = std::unique_ptr<NetCDFOptionsWidget>(new NetCDFOptionsWidget(netcdfw, this));
1342             ui.swOptions->insertWidget(static_cast<int>(AbstractFileFilter::FileType::NETCDF), netcdfw);
1343         } else
1344             m_netcdfOptionsWidget->clear();
1345         ui.swOptions->setCurrentWidget(m_netcdfOptionsWidget->parentWidget());
1346         break;
1347     case AbstractFileFilter::FileType::VECTOR_BLF:
1348         if (!m_canOptionsWidget) {
1349             auto* vectorBLF = new QWidget();
1350             m_canOptionsWidget = std::unique_ptr<CANOptionsWidget>(new CANOptionsWidget(vectorBLF));
1351             ui.swOptions->addWidget(vectorBLF);
1352         }
1353         ui.swOptions->setCurrentWidget(m_canOptionsWidget->parentWidget());
1354         break;
1355     case AbstractFileFilter::FileType::FITS:
1356         if (!m_fitsOptionsWidget) {
1357             auto* fitsw = new QWidget();
1358             m_fitsOptionsWidget = std::unique_ptr<FITSOptionsWidget>(new FITSOptionsWidget(fitsw, this));
1359             ui.swOptions->addWidget(fitsw);
1360         } else
1361             m_fitsOptionsWidget->clear();
1362         ui.swOptions->setCurrentWidget(m_fitsOptionsWidget->parentWidget());
1363         break;
1364     case AbstractFileFilter::FileType::JSON:
1365         if (!m_jsonOptionsWidget) {
1366             auto* jsonw = new QWidget();
1367             m_jsonOptionsWidget = std::unique_ptr<JsonOptionsWidget>(new JsonOptionsWidget(jsonw));
1368             ui.tvJson->setModel(m_jsonOptionsWidget->model());
1369             ui.swOptions->addWidget(jsonw);
1370             m_jsonOptionsWidget->loadSettings();
1371             connect(m_jsonOptionsWidget.get(), &JsonOptionsWidget::error, this, &ImportFileWidget::error);
1372         } else
1373             m_jsonOptionsWidget->clearModel();
1374         ui.swOptions->setCurrentWidget(m_jsonOptionsWidget->parentWidget());
1375         showJsonModel(true);
1376         break;
1377     case AbstractFileFilter::FileType::ROOT:
1378         if (!m_rootOptionsWidget) {
1379             auto* rootw = new QWidget();
1380             m_rootOptionsWidget = std::unique_ptr<ROOTOptionsWidget>(new ROOTOptionsWidget(rootw, this));
1381             ui.swOptions->addWidget(rootw);
1382         } else
1383             m_rootOptionsWidget->clear();
1384         ui.swOptions->setCurrentWidget(m_rootOptionsWidget->parentWidget());
1385         break;
1386     case AbstractFileFilter::FileType::MATIO:
1387         if (!m_matioOptionsWidget) {
1388             auto* matiow = new QWidget();
1389             m_matioOptionsWidget = std::unique_ptr<MatioOptionsWidget>(new MatioOptionsWidget(matiow, this));
1390             ui.swOptions->insertWidget(static_cast<int>(AbstractFileFilter::FileType::MATIO), matiow);
1391         } else
1392             m_matioOptionsWidget->clear();
1393         ui.swOptions->setCurrentWidget(m_matioOptionsWidget->parentWidget());
1394         break;
1395     case AbstractFileFilter::FileType::Spice:
1396     case AbstractFileFilter::FileType::READSTAT:
1397         break;
1398     }
1399 }
1400 
1401 const QStringList ImportFileWidget::selectedHDF5Names() const {
1402     return m_hdf5OptionsWidget->selectedNames();
1403 }
1404 
1405 // const QStringList ImportFileWidget::selectedVectorBLFNames() const {
1406 //  return m_vectorBLFOptionsWidget->selectedNames();
1407 // }
1408 
1409 const QStringList ImportFileWidget::selectedNetCDFNames() const {
1410     return m_netcdfOptionsWidget->selectedNames();
1411 }
1412 
1413 const QStringList ImportFileWidget::selectedMatioNames() const {
1414     return m_matioOptionsWidget->selectedNames();
1415 }
1416 
1417 const QStringList ImportFileWidget::selectedFITSExtensions() const {
1418     return m_fitsOptionsWidget->selectedExtensions();
1419 }
1420 
1421 const QStringList ImportFileWidget::selectedROOTNames() const {
1422     return m_rootOptionsWidget->selectedNames();
1423 }
1424 
1425 const QStringList ImportFileWidget::selectedXLSXRegionNames() const {
1426     return m_xlsxOptionsWidget->selectedXLSXRegionNames();
1427 }
1428 
1429 const QStringList ImportFileWidget::selectedOdsSheetNames() const {
1430     return m_odsOptionsWidget->selectedOdsSheetNames();
1431 }
1432 
1433 bool ImportFileWidget::useFirstRowAsColNames() const {
1434     return ui.chbFirstRowAsColName->isChecked();
1435 }
1436 
1437 /*!
1438     shows the dialog with the information about the file(s) to be imported.
1439 */
1440 void ImportFileWidget::showFileInfo() {
1441     const QString& info = fileInfoString(fileName());
1442     QWhatsThis::showText(ui.bFileInfo->mapToGlobal(QPoint(0, 0)), info, ui.bFileInfo);
1443 }
1444 
1445 /*!
1446     returns a string containing the general information about the file \c name
1447     and some content specific information
1448     (number of columns and lines for ASCII, color-depth for images etc.).
1449 */
1450 QString ImportFileWidget::fileInfoString(const QString& name) const {
1451     DEBUG(Q_FUNC_INFO << ", file name = " << STDSTRING(name))
1452     QString infoString;
1453     QFileInfo fileInfo;
1454     QString fileTypeString;
1455     QIODevice* file = new QFile(name);
1456 
1457     QString fileName = absolutePath(name);
1458 
1459     if (!file)
1460         file = new QFile(fileName);
1461 
1462     if (file->open(QIODevice::ReadOnly)) {
1463         QStringList infoStrings;
1464 
1465         infoStrings << QStringLiteral("<u><b>") + fileName + QStringLiteral("</b></u><br>");
1466 
1467         // File type given by "file"
1468 #ifdef Q_OS_LINUX
1469         const QString fileFullPath = QStandardPaths::findExecutable(QStringLiteral("file"));
1470         if (fileFullPath.isEmpty())
1471             return i18n("file command not found");
1472 
1473         QProcess proc;
1474         QStringList args;
1475         args << QStringLiteral("-b") << fileName;
1476         proc.start(fileFullPath, args);
1477 
1478         if (proc.waitForReadyRead(1000) == false)
1479             infoStrings << i18n("Reading from file %1 failed.", fileName);
1480         else {
1481             fileTypeString = QLatin1String(proc.readLine());
1482             if (fileTypeString.contains(i18n("cannot open")))
1483                 fileTypeString.clear();
1484             else
1485                 fileTypeString.remove(fileTypeString.length() - 1, 1); // remove '\n'
1486         }
1487         infoStrings << i18n("<b>File type:</b> %1", fileTypeString);
1488 #endif
1489 
1490         // General:
1491         fileInfo.setFile(fileName);
1492         infoStrings << QStringLiteral("<b>") << i18n("General:") << QStringLiteral("</b>");
1493 
1494         infoStrings << i18n("Readable: %1", fileInfo.isReadable() ? i18n("yes") : i18n("no"));
1495         infoStrings << i18n("Writable: %1", fileInfo.isWritable() ? i18n("yes") : i18n("no"));
1496         infoStrings << i18n("Executable: %1", fileInfo.isExecutable() ? i18n("yes") : i18n("no"));
1497 
1498         infoStrings << i18n("Birth time: %1", fileInfo.birthTime().toString());
1499         infoStrings << i18n("Last metadata changed: %1", fileInfo.metadataChangeTime().toString());
1500         infoStrings << i18n("Last modified: %1", fileInfo.lastModified().toString());
1501         infoStrings << i18n("Last read: %1", fileInfo.lastRead().toString());
1502         infoStrings << i18n("Owner: %1", fileInfo.owner());
1503         infoStrings << i18n("Group: %1", fileInfo.group());
1504         infoStrings << i18n("Size: %1", i18np("%1 cByte", "%1 cBytes", fileInfo.size()));
1505 
1506         // Summary:
1507         infoStrings << QStringLiteral("<b>") << i18n("Summary:") << QStringLiteral("</b>");
1508         // depending on the file type, generate summary and content information about the file
1509         // TODO: content information (in BNF) for more types
1510         // TODO: introduce a function in the base class and work with infoStrings << currentFileFilter()->fileInfoString(fileName);
1511         // instead of this big switch-case.
1512         switch (AbstractFileFilter::fileType(fileName)) {
1513         case AbstractFileFilter::FileType::Ascii:
1514             infoStrings << AsciiFilter::fileInfoString(fileName);
1515             break;
1516         case AbstractFileFilter::FileType::Binary:
1517             infoStrings << BinaryFilter::fileInfoString(fileName);
1518             break;
1519         case AbstractFileFilter::FileType::XLSX:
1520             infoStrings << XLSXFilter::fileInfoString(fileName);
1521             break;
1522         case AbstractFileFilter::FileType::Ods:
1523             infoStrings << OdsFilter::fileInfoString(fileName);
1524             break;
1525         case AbstractFileFilter::FileType::Image:
1526             infoStrings << ImageFilter::fileInfoString(fileName);
1527             break;
1528         case AbstractFileFilter::FileType::HDF5:
1529             infoStrings << HDF5Filter::fileInfoString(fileName);
1530             infoStrings << QStringLiteral("<b>") << i18n("Content:") << QStringLiteral("</b>");
1531             infoStrings << HDF5Filter::fileDDLString(fileName);
1532             break;
1533         case AbstractFileFilter::FileType::NETCDF:
1534             infoStrings << NetCDFFilter::fileInfoString(fileName);
1535             infoStrings << QStringLiteral("<b>") << i18n("Content:") << QStringLiteral("</b>");
1536             infoStrings << NetCDFFilter::fileCDLString(fileName);
1537             break;
1538         case AbstractFileFilter::FileType::VECTOR_BLF:
1539             infoStrings << VectorBLFFilter::fileInfoString(fileName);
1540             break;
1541         case AbstractFileFilter::FileType::FITS:
1542             infoStrings << FITSFilter::fileInfoString(fileName);
1543             break;
1544         case AbstractFileFilter::FileType::JSON:
1545             infoStrings << JsonFilter::fileInfoString(fileName);
1546             break;
1547         case AbstractFileFilter::FileType::ROOT:
1548             infoStrings << ROOTFilter::fileInfoString(fileName);
1549             break;
1550         case AbstractFileFilter::FileType::Spice:
1551             infoStrings << SpiceFilter::fileInfoString(fileName);
1552             break;
1553         case AbstractFileFilter::FileType::READSTAT:
1554             infoStrings << ReadStatFilter::fileInfoString(fileName);
1555             break;
1556         case AbstractFileFilter::FileType::MATIO:
1557             infoStrings << MatioFilter::fileInfoString(fileName);
1558             break;
1559         }
1560 
1561         infoString += infoStrings.join(QLatin1String("<br>"));
1562     } else
1563         infoString += i18n("Could not open file %1 for reading.", fileName);
1564 
1565     return infoString;
1566 }
1567 
1568 /*!
1569  * called when the filter settings type (custom, automatic, from a template) was changed.
1570  * enables the options if the filter "custom" was chosen. Disables the options otherwise.
1571  */
1572 void ImportFileWidget::filterChanged(int index) {
1573     // filter settings are available for ASCII and Binary only, ignore for other file types
1574     auto fileType = currentFileType();
1575     if (fileType != AbstractFileFilter::FileType::Ascii && fileType != AbstractFileFilter::FileType::Binary) {
1576         ui.swOptions->setEnabled(true);
1577         return;
1578     }
1579 
1580     if (index == 0) { // "automatic"
1581         ui.swOptions->setEnabled(false);
1582         m_templateHandler->hide();
1583     } else if (index == 1) { // custom
1584         ui.swOptions->setEnabled(true);
1585         m_templateHandler->show();
1586     } else { // templates
1587         ui.swOptions->setEnabled(false);
1588         m_templateHandler->hide();
1589         auto config = m_templateHandler->config(ui.cbFilter->currentText());
1590         this->loadConfigFromTemplate(config);
1591     }
1592 }
1593 
1594 void ImportFileWidget::refreshPreview() {
1595     DEBUG(Q_FUNC_INFO)
1596     // don't generate any preview if it was explicitly suppressed
1597     // or if the options box together with the preview widget is not visible
1598     if (m_suppressRefresh || !ui.gbOptions->isVisible())
1599         return;
1600 
1601     WAIT_CURSOR;
1602 
1603     QString file = absolutePath(fileName());
1604     const QString dbcFile = dbcFileName();
1605     auto fileType = currentFileType();
1606     auto sourceType = currentSourceType();
1607     int lines = ui.sbPreviewLines->value();
1608 
1609     if (sourceType == LiveDataSource::SourceType::FileOrPipe)
1610         DEBUG(Q_FUNC_INFO << ", file name = " << STDSTRING(file));
1611 
1612     // default preview widget
1613     if (fileType == AbstractFileFilter::FileType::Ascii || fileType == AbstractFileFilter::FileType::Binary || fileType == AbstractFileFilter::FileType::JSON
1614         || fileType == AbstractFileFilter::FileType::Spice || fileType == AbstractFileFilter::FileType::VECTOR_BLF
1615         || fileType == AbstractFileFilter::FileType::READSTAT)
1616         m_twPreview->show();
1617     else
1618         m_twPreview->hide();
1619 
1620     bool ok = true;
1621     QTableWidget* tmpTableWidget = m_twPreview;
1622     QVector<QStringList> importedStrings;
1623     QStringList vectorNameList;
1624     QVector<AbstractColumn::ColumnMode> columnModes;
1625     DEBUG(Q_FUNC_INFO << ", Data File Type: " << ENUM_TO_STRING(AbstractFileFilter, FileType, fileType));
1626     switch (fileType) {
1627     case AbstractFileFilter::FileType::Ascii: {
1628         ui.tePreview->clear();
1629 
1630         auto filter = static_cast<AsciiFilter*>(currentFileFilter());
1631 
1632         DEBUG(Q_FUNC_INFO << ", Data Source Type: " << ENUM_TO_STRING(LiveDataSource, SourceType, sourceType));
1633         switch (sourceType) {
1634         case LiveDataSource::SourceType::FileOrPipe: {
1635             importedStrings = filter->preview(file, lines);
1636             break;
1637         }
1638         case LiveDataSource::SourceType::LocalSocket: {
1639             QLocalSocket lsocket{this};
1640             DEBUG("Local socket: CONNECT PREVIEW");
1641             lsocket.connectToServer(file, QLocalSocket::ReadOnly);
1642             if (lsocket.waitForConnected()) {
1643                 DEBUG("connected to local socket " << STDSTRING(file));
1644                 if (lsocket.waitForReadyRead())
1645                     importedStrings = filter->preview(lsocket);
1646                 DEBUG("Local socket: DISCONNECT PREVIEW");
1647                 lsocket.disconnectFromServer();
1648                 // read-only socket is disconnected immediately (no waitForDisconnected())
1649             } else
1650                 DEBUG("failed connect to local socket " << STDSTRING(file) << " - " << STDSTRING(lsocket.errorString()));
1651 
1652             break;
1653         }
1654         case LiveDataSource::SourceType::NetworkTCPSocket: {
1655             QTcpSocket tcpSocket{this};
1656             tcpSocket.connectToHost(host(), port().toInt(), QTcpSocket::ReadOnly);
1657             if (tcpSocket.waitForConnected()) {
1658                 DEBUG("connected to TCP socket");
1659                 if (tcpSocket.waitForReadyRead())
1660                     importedStrings = filter->preview(tcpSocket);
1661 
1662                 tcpSocket.disconnectFromHost();
1663             } else
1664                 DEBUG("failed to connect to TCP socket "
1665                       << " - " << STDSTRING(tcpSocket.errorString()));
1666 
1667             break;
1668         }
1669         case LiveDataSource::SourceType::NetworkUDPSocket: {
1670             QUdpSocket udpSocket{this};
1671             DEBUG("UDP Socket: CONNECT PREVIEW, state = " << udpSocket.state());
1672             udpSocket.bind(QHostAddress(host()), port().toInt());
1673             udpSocket.connectToHost(host(), 0, QUdpSocket::ReadOnly);
1674             if (udpSocket.waitForConnected()) {
1675                 DEBUG(" connected to UDP socket " << STDSTRING(host()) << ':' << port().toInt());
1676                 if (!udpSocket.waitForReadyRead(2000))
1677                     DEBUG(" ERROR: not ready for read after 2 sec");
1678                 if (udpSocket.hasPendingDatagrams()) {
1679                     DEBUG(" has pending data");
1680                 } else {
1681                     DEBUG(" has no pending data");
1682                 }
1683                 importedStrings = filter->preview(udpSocket);
1684 
1685                 DEBUG("UDP Socket: DISCONNECT PREVIEW, state = " << udpSocket.state());
1686                 udpSocket.disconnectFromHost();
1687             } else
1688                 DEBUG("failed to connect to UDP socket "
1689                       << " - " << STDSTRING(udpSocket.errorString()));
1690 
1691             break;
1692         }
1693         case LiveDataSource::SourceType::SerialPort: {
1694 #ifdef HAVE_QTSERIALPORT
1695             QSerialPort sPort{this};
1696             DEBUG(" Port: " << STDSTRING(serialPort()) << ", Settings: " << baudRate() << ',' << sPort.dataBits() << ',' << sPort.parity() << ','
1697                             << sPort.stopBits());
1698             sPort.setPortName(serialPort());
1699             sPort.setBaudRate(baudRate());
1700 
1701             if (sPort.open(QIODevice::ReadOnly)) {
1702                 if (sPort.waitForReadyRead(2000))
1703                     importedStrings = filter->preview(sPort);
1704                 else
1705                     DEBUG(" ERROR: not ready for read after 2 sec");
1706 
1707                 sPort.close();
1708             } else
1709                 DEBUG(" ERROR: failed to open serial port. error: " << sPort.error());
1710 #endif
1711             break;
1712         }
1713         case LiveDataSource::SourceType::MQTT: {
1714 #ifdef HAVE_MQTT
1715             // show the preview for the currently selected topic
1716             auto* item = m_subscriptionWidget->currentItem();
1717             if (item && item->childCount() == 0) { // only preview if the lowest level (i.e. a topic) is selected
1718                 const QString& topicName = item->text(0);
1719                 auto i = m_lastMessage.find(topicName);
1720                 if (i != m_lastMessage.end())
1721                     importedStrings = filter->preview(QLatin1String(i.value().payload().data()));
1722                 else
1723                     importedStrings << QStringList{i18n("No data arrived yet for the selected topic")};
1724             }
1725 #endif
1726             break;
1727         }
1728         }
1729 
1730         vectorNameList = filter->vectorNames();
1731         columnModes = filter->columnModes();
1732         break;
1733     }
1734     case AbstractFileFilter::FileType::Binary: {
1735         ui.tePreview->clear();
1736         auto filter = static_cast<BinaryFilter*>(currentFileFilter());
1737         importedStrings = filter->preview(file, lines);
1738         break;
1739     }
1740     case AbstractFileFilter::FileType::XLSX:
1741         // update own preview (Nothing else to do)
1742         m_xlsxOptionsWidget->dataRegionSelectionChanged();
1743         // TODO: needed for import (why?)
1744         importedStrings = m_xlsxOptionsWidget->previewString();
1745         break;
1746     case AbstractFileFilter::FileType::Ods:
1747         // update own preview (Nothing else to do)
1748         m_odsOptionsWidget->sheetSelectionChanged();
1749         // TODO: needed for import (why?)
1750         importedStrings = m_odsOptionsWidget->previewString();
1751         break;
1752     case AbstractFileFilter::FileType::Image: {
1753         ui.tePreview->clear();
1754 
1755         QImage image(file);
1756         QTextCursor cursor = ui.tePreview->textCursor();
1757         cursor.insertImage(image);
1758         RESET_CURSOR;
1759         return;
1760     }
1761     case AbstractFileFilter::FileType::HDF5: {
1762         DEBUG(Q_FUNC_INFO << ", HDF5");
1763         auto filter = static_cast<HDF5Filter*>(currentFileFilter());
1764         lines = m_hdf5OptionsWidget->lines();
1765 
1766         importedStrings = filter->readCurrentDataSet(file, nullptr, ok, AbstractFileFilter::ImportMode::Replace, lines);
1767         tmpTableWidget = m_hdf5OptionsWidget->previewWidget();
1768         break;
1769     }
1770     case AbstractFileFilter::FileType::NETCDF: {
1771         DEBUG(Q_FUNC_INFO << ", NetCDF");
1772         auto filter = static_cast<NetCDFFilter*>(currentFileFilter());
1773         lines = m_netcdfOptionsWidget->lines();
1774 
1775         importedStrings = filter->readCurrentVar(file, nullptr, AbstractFileFilter::ImportMode::Replace, lines);
1776         tmpTableWidget = m_netcdfOptionsWidget->previewWidget();
1777         break;
1778     }
1779     case AbstractFileFilter::FileType::VECTOR_BLF: {
1780         ui.tePreview->clear();
1781         auto filter = static_cast<VectorBLFFilter*>(currentFileFilter());
1782         filter->setDBCFile(dbcFile);
1783         importedStrings = filter->preview(file, lines);
1784         vectorNameList = filter->vectorNames();
1785         columnModes = filter->columnModes();
1786         break;
1787     }
1788     case AbstractFileFilter::FileType::FITS: {
1789         DEBUG(Q_FUNC_INFO << ", FITS");
1790         auto filter = static_cast<FITSFilter*>(currentFileFilter());
1791         lines = m_fitsOptionsWidget->lines();
1792 
1793         QString extensionName = m_fitsOptionsWidget->extensionName(&ok);
1794         if (!extensionName.isEmpty()) {
1795             DEBUG(Q_FUNC_INFO << ", extension name = " << STDSTRING(extensionName));
1796             file = extensionName;
1797         }
1798 
1799         bool readFitsTableToMatrix;
1800         importedStrings = filter->readChdu(file, &readFitsTableToMatrix, lines);
1801         Q_EMIT enableImportToMatrix(readFitsTableToMatrix);
1802 
1803         tmpTableWidget = m_fitsOptionsWidget->previewWidget();
1804         break;
1805     }
1806     case AbstractFileFilter::FileType::JSON: {
1807         ui.tePreview->clear();
1808         auto filter = static_cast<JsonFilter*>(currentFileFilter());
1809         m_jsonOptionsWidget->applyFilterSettings(filter, ui.tvJson->currentIndex());
1810         importedStrings = filter->preview(file, lines);
1811 
1812         vectorNameList = filter->vectorNames();
1813         columnModes = filter->columnModes();
1814         break;
1815     }
1816     case AbstractFileFilter::FileType::ROOT: {
1817         auto filter = static_cast<ROOTFilter*>(currentFileFilter());
1818         lines = m_rootOptionsWidget->lines();
1819         m_rootOptionsWidget->setNRows(filter->rowsInCurrentObject(file));
1820         importedStrings = filter->previewCurrentObject(file,
1821                                                        m_rootOptionsWidget->startRow(),
1822                                                        std::min(m_rootOptionsWidget->startRow() + lines - 1, m_rootOptionsWidget->endRow()));
1823         tmpTableWidget = m_rootOptionsWidget->previewWidget();
1824         // the last vector element contains the column names
1825         vectorNameList = importedStrings.last();
1826         importedStrings.removeLast();
1827         columnModes = QVector<AbstractColumn::ColumnMode>(vectorNameList.size(), AbstractColumn::ColumnMode::Double);
1828         break;
1829     }
1830     case AbstractFileFilter::FileType::Spice: {
1831         ui.tePreview->clear();
1832         auto filter = static_cast<SpiceFilter*>(currentFileFilter());
1833         importedStrings = filter->preview(file, lines);
1834         vectorNameList = filter->vectorNames();
1835         columnModes = filter->columnModes();
1836         break;
1837     }
1838     case AbstractFileFilter::FileType::READSTAT: {
1839         ui.tePreview->clear();
1840         auto filter = static_cast<ReadStatFilter*>(currentFileFilter());
1841         importedStrings = filter->preview(file, lines);
1842         vectorNameList = filter->vectorNames();
1843         columnModes = filter->columnModes();
1844         DEBUG(Q_FUNC_INFO << ", got " << columnModes.size() << " columns and " << importedStrings.size() << " rows")
1845         break;
1846     }
1847     case AbstractFileFilter::FileType::MATIO: {
1848         auto filter = static_cast<MatioFilter*>(currentFileFilter());
1849         lines = m_matioOptionsWidget->lines();
1850 
1851         QVector<QStringList> strings;
1852         // loop over all selected vars
1853         for (const QString& var : filter->selectedVarNames()) {
1854             // DEBUG(Q_FUNC_INFO << ", reading variable: " << STDSTRING(var))
1855             filter->setCurrentVarName(var);
1856             strings = filter->readCurrentVar(file, nullptr, AbstractFileFilter::ImportMode::Replace, lines);
1857             if (importedStrings.size() == 0) // first var
1858                 importedStrings = strings;
1859             else { // append
1860                 if (importedStrings.size() < strings.size()) { // more rows than before
1861                     const int oldSize = importedStrings.size();
1862                     importedStrings.resize(strings.size());
1863                     for (int row = oldSize; row < strings.size(); row++) // fill new items
1864                         for (int col = 0; col < importedStrings.at(0).size(); col++)
1865                             importedStrings[row] << QString();
1866                 }
1867                 for (int i = 0; i < strings.size(); i++)
1868                     importedStrings[i] << strings.at(i);
1869             }
1870         }
1871 
1872         tmpTableWidget = m_matioOptionsWidget->previewWidget();
1873         break;
1874     }
1875     }
1876     QDEBUG(Q_FUNC_INFO << ", imported strings =" << importedStrings)
1877 
1878     // fill the table widget
1879     tmpTableWidget->setRowCount(0);
1880     tmpTableWidget->setColumnCount(0);
1881     if (!importedStrings.isEmpty()) {
1882         if (!ok) {
1883             // show imported strings as error message
1884             tmpTableWidget->setRowCount(1);
1885             tmpTableWidget->setColumnCount(1);
1886             auto* item = new QTableWidgetItem();
1887             item->setText(importedStrings[0][0]);
1888             tmpTableWidget->setItem(0, 0, item);
1889         } else {
1890 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
1891             const int rowCount = std::max(importedStrings.size(), static_cast<qsizetype>(1));
1892 #else
1893             const int rowCount = std::max(importedStrings.size(), 1);
1894 #endif
1895             const int maxColumns = 300;
1896             tmpTableWidget->setRowCount(rowCount);
1897 
1898             for (int row = 0; row < rowCount; ++row) {
1899                 const int colCount = importedStrings.at(row).size() > maxColumns ? maxColumns : importedStrings.at(row).size();
1900                 if (colCount > tmpTableWidget->columnCount())
1901                     tmpTableWidget->setColumnCount(colCount);
1902 
1903                 for (int col = 0; col < colCount; ++col) {
1904                     auto* item = new QTableWidgetItem(importedStrings[row][col]);
1905                     tmpTableWidget->setItem(row, col, item);
1906                 }
1907             }
1908 
1909             // XLSX and Ods has special h/vheader, don't overwrite the preview table
1910             if (fileType != AbstractFileFilter::FileType::XLSX && fileType != AbstractFileFilter::FileType::Ods) {
1911                 // set header if columnMode available
1912 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
1913                 for (int i = 0; i < std::min(static_cast<qsizetype>(tmpTableWidget->columnCount()), columnModes.size()); ++i) {
1914 #else
1915                 for (int i = 0; i < std::min(tmpTableWidget->columnCount(), columnModes.size()); ++i) {
1916 #endif
1917                     QString columnName = QString::number(i + 1);
1918                     if (i < vectorNameList.size())
1919                         columnName = vectorNameList.at(i);
1920 
1921                     auto* item = new QTableWidgetItem(columnName + QStringLiteral(" {")
1922                                                       + QLatin1String(ENUM_TO_STRING(AbstractColumn, ColumnMode, columnModes.at(i))) + QStringLiteral("}"));
1923                     item->setTextAlignment(Qt::AlignLeft);
1924                     item->setIcon(AbstractColumn::modeIcon(columnModes.at(i)));
1925 
1926                     DEBUG("COLUMN " << i + 1 << " NAME = " << STDSTRING(columnName))
1927                     tmpTableWidget->setHorizontalHeaderItem(i, item);
1928                 }
1929             }
1930         }
1931 
1932         tmpTableWidget->horizontalHeader()->resizeSections(QHeaderView::ResizeToContents);
1933         m_importValid = false;
1934     } else
1935         m_importValid = true;
1936 
1937     RESET_CURSOR;
1938 }
1939 
1940 void ImportFileWidget::updateStartRow(int line) {
1941     if (line >= ui.sbStartRow->value())
1942         ui.sbStartRow->setValue(line + 1);
1943 }
1944 
1945 void ImportFileWidget::updateContent(const QString& fileName) {
1946     if (m_suppressRefresh)
1947         return;
1948 
1949     QApplication::processEvents(QEventLoop::AllEvents, 0);
1950     WAIT_CURSOR;
1951 
1952     DEBUG(Q_FUNC_INFO << ", file name = " << STDSTRING(fileName));
1953     if (auto filter = currentFileFilter()) {
1954         switch (filter->type()) {
1955         case AbstractFileFilter::FileType::HDF5: {
1956             int status = m_hdf5OptionsWidget->updateContent(static_cast<HDF5Filter*>(filter), fileName);
1957             if (status != 0) { // parsing failed: switch to binary filter
1958                 ui.cbFileType->setCurrentIndex(ui.cbFileType->findData(static_cast<int>(AbstractFileFilter::FileType::Binary)));
1959                 Q_EMIT error(i18n("Not a HDF5 file: %1", fileName));
1960             }
1961             break;
1962         }
1963         case AbstractFileFilter::FileType::NETCDF:
1964             m_netcdfOptionsWidget->updateContent(static_cast<NetCDFFilter*>(filter), fileName);
1965             break;
1966             //      case AbstractFileFilter::FileType::VECTOR_BLF:
1967             //          m_vectorBLFOptionsWidget->updateContent(static_cast<VectorBLFFilter*>(filter), fileName);
1968             //          break;
1969         case AbstractFileFilter::FileType::FITS:
1970 #ifdef HAVE_FITS
1971             m_fitsOptionsWidget->updateContent(static_cast<FITSFilter*>(filter), fileName);
1972 #endif
1973             break;
1974         case AbstractFileFilter::FileType::ROOT:
1975             m_rootOptionsWidget->updateContent(static_cast<ROOTFilter*>(filter), fileName);
1976             break;
1977         case AbstractFileFilter::FileType::JSON:
1978             m_jsonOptionsWidget->loadDocument(fileName);
1979             ui.tvJson->setExpanded(m_jsonOptionsWidget->model()->index(0, 0), true); // expand the root node
1980             break;
1981         case AbstractFileFilter::FileType::MATIO:
1982             m_matioOptionsWidget->updateContent(static_cast<MatioFilter*>(filter), fileName);
1983             break;
1984         case AbstractFileFilter::FileType::XLSX:
1985 #ifdef HAVE_QXLSX
1986             m_xlsxOptionsWidget->updateContent(reinterpret_cast<XLSXFilter*>(filter), fileName);
1987 #endif
1988             break;
1989         case AbstractFileFilter::FileType::Ods:
1990 #ifdef HAVE_ORCUS
1991             m_odsOptionsWidget->updateContent(reinterpret_cast<OdsFilter*>(filter), fileName);
1992 #endif
1993             break;
1994         case AbstractFileFilter::FileType::Ascii:
1995         case AbstractFileFilter::FileType::Binary:
1996         case AbstractFileFilter::FileType::Image:
1997         case AbstractFileFilter::FileType::Spice:
1998         case AbstractFileFilter::FileType::READSTAT:
1999         case AbstractFileFilter::FileType::VECTOR_BLF:
2000             break;
2001         }
2002     }
2003     RESET_CURSOR;
2004 }
2005 
2006 void ImportFileWidget::updateTypeChanged(int idx) {
2007     const auto UpdateType = static_cast<LiveDataSource::UpdateType>(idx);
2008 
2009     switch (UpdateType) {
2010     case LiveDataSource::UpdateType::TimeInterval:
2011         ui.lUpdateInterval->show();
2012         ui.sbUpdateInterval->show();
2013         break;
2014     case LiveDataSource::UpdateType::NewData:
2015         ui.lUpdateInterval->hide();
2016         ui.sbUpdateInterval->hide();
2017     }
2018 }
2019 
2020 void ImportFileWidget::readingTypeChanged(int idx) {
2021     const auto readingType = static_cast<LiveDataSource::ReadingType>(idx);
2022     const LiveDataSource::SourceType sourceType = currentSourceType();
2023 
2024     if (sourceType == LiveDataSource::SourceType::NetworkTCPSocket || sourceType == LiveDataSource::SourceType::LocalSocket
2025         || sourceType == LiveDataSource::SourceType::SerialPort || readingType == LiveDataSource::ReadingType::TillEnd
2026         || readingType == LiveDataSource::ReadingType::WholeFile) {
2027         ui.lSampleSize->hide();
2028         ui.sbSampleSize->hide();
2029     } else {
2030         ui.lSampleSize->show();
2031         ui.sbSampleSize->show();
2032     }
2033 
2034     if (readingType == LiveDataSource::ReadingType::WholeFile) {
2035         ui.lKeepLastValues->hide();
2036         ui.sbKeepNValues->hide();
2037     } else {
2038         ui.lKeepLastValues->show();
2039         ui.sbKeepNValues->show();
2040     }
2041 }
2042 
2043 void ImportFileWidget::firstRowAsColNamesChanged(bool checked) {
2044     if (checked) {
2045         if (ui.sbStartRow->value() == 1)
2046             ui.sbStartRow->setValue(2);
2047     } else
2048         ui.sbStartRow->setValue(1);
2049 }
2050 
2051 void ImportFileWidget::sourceTypeChanged(int idx) {
2052     const auto sourceType = static_cast<LiveDataSource::SourceType>(idx);
2053 
2054 #ifdef HAVE_MQTT
2055     // when switching from mqtt to another source type, make sure we disconnect from
2056     // the current broker, if connected, in order not to get any notification anymore
2057     if (sourceType != LiveDataSource::SourceType::MQTT)
2058         disconnectMqttConnection();
2059 #endif
2060 
2061     // enable/disable "on new data"-option
2062     const auto* model = qobject_cast<const QStandardItemModel*>(ui.cbUpdateType->model());
2063     auto* item = model->item(static_cast<int>(LiveDataSource::UpdateType::NewData));
2064 
2065     switch (sourceType) {
2066     case LiveDataSource::SourceType::FileOrPipe:
2067         ui.lFileName->show();
2068         m_cbFileName->show();
2069         ui.bFileInfo->show();
2070         ui.bOpen->show();
2071         if (m_liveDataSource) {
2072             ui.lRelativePath->show();
2073             ui.chbRelativePath->show();
2074         }
2075         ui.chbLinkFile->show();
2076 
2077         // option for sample size are available for "continuously fixed" and "from end" reading options
2078         if (ui.cbReadingType->currentIndex() < 2) {
2079             ui.lSampleSize->show();
2080             ui.sbSampleSize->show();
2081         } else {
2082             ui.lSampleSize->hide();
2083             ui.sbSampleSize->hide();
2084         }
2085 
2086         ui.cbBaudRate->hide();
2087         ui.lBaudRate->hide();
2088         ui.lHost->hide();
2089         ui.leHost->hide();
2090         ui.lPort->hide();
2091         ui.lePort->hide();
2092         ui.cbSerialPort->hide();
2093         ui.lSerialPort->hide();
2094 
2095         item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
2096 
2097         fileNameChanged(fileName());
2098         ui.cbFileType->show();
2099         ui.lFileType->show();
2100         setMQTTVisible(false);
2101         break;
2102     case LiveDataSource::SourceType::NetworkTCPSocket:
2103     case LiveDataSource::SourceType::NetworkUDPSocket:
2104         ui.lHost->show();
2105         ui.leHost->show();
2106         ui.lePort->show();
2107         ui.lPort->show();
2108         if (sourceType == LiveDataSource::SourceType::NetworkTCPSocket) {
2109             ui.lSampleSize->hide();
2110             ui.sbSampleSize->hide();
2111         } else {
2112             ui.lSampleSize->show();
2113             ui.sbSampleSize->show();
2114         }
2115 
2116         ui.lBaudRate->hide();
2117         ui.cbBaudRate->hide();
2118         ui.lSerialPort->hide();
2119         ui.cbSerialPort->hide();
2120 
2121         ui.lFileName->hide();
2122         m_cbFileName->hide();
2123         ui.bFileInfo->hide();
2124         ui.bOpen->hide();
2125         ui.lRelativePath->hide();
2126         ui.chbRelativePath->hide();
2127         ui.chbLinkFile->hide();
2128 
2129         // don't allow to select "New Data" for network sockets.
2130         // select "Periodically" in the combo box in case "New Data" was selected before
2131         item->setFlags(item->flags() & ~(Qt::ItemIsSelectable | Qt::ItemIsEnabled));
2132         ui.cbUpdateType->setCurrentIndex(0);
2133 
2134         ui.gbOptions->setEnabled(true);
2135         ui.cbFilter->setEnabled(true);
2136         ui.cbFileType->setEnabled(true);
2137         ui.cbFileType->show();
2138         ui.lFileType->show();
2139         setMQTTVisible(false);
2140         break;
2141     case LiveDataSource::SourceType::LocalSocket:
2142         ui.lFileName->show();
2143         m_cbFileName->show();
2144         ui.bFileInfo->hide();
2145         ui.bOpen->show();
2146         ui.lRelativePath->hide();
2147         ui.chbRelativePath->hide();
2148 
2149         ui.lSampleSize->hide();
2150         ui.sbSampleSize->hide();
2151         ui.cbBaudRate->hide();
2152         ui.lBaudRate->hide();
2153         ui.lHost->hide();
2154         ui.leHost->hide();
2155         ui.lPort->hide();
2156         ui.lePort->hide();
2157         ui.cbSerialPort->hide();
2158         ui.lSerialPort->hide();
2159         ui.chbLinkFile->hide();
2160 
2161         item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
2162 
2163         ui.gbOptions->setEnabled(true);
2164         ui.cbFilter->setEnabled(true);
2165         ui.cbFileType->setEnabled(true);
2166         ui.cbFileType->show();
2167         ui.lFileType->show();
2168         setMQTTVisible(false);
2169         break;
2170     case LiveDataSource::SourceType::SerialPort:
2171         ui.lBaudRate->show();
2172         ui.cbBaudRate->show();
2173         ui.lSerialPort->show();
2174         ui.cbSerialPort->show();
2175         ui.lSampleSize->show();
2176         ui.sbSampleSize->show();
2177 
2178         ui.lHost->hide();
2179         ui.leHost->hide();
2180         ui.lePort->hide();
2181         ui.lPort->hide();
2182 
2183         ui.lFileName->hide();
2184         m_cbFileName->hide();
2185         ui.bFileInfo->hide();
2186         ui.bOpen->hide();
2187         ui.lRelativePath->hide();
2188         ui.chbRelativePath->hide();
2189         ui.chbLinkFile->hide();
2190 
2191         // don't allow to select "New Data" serial port.
2192         // select "Periodically" in the combo box in case "New Data" was selected before
2193         item->setFlags(item->flags() & ~(Qt::ItemIsSelectable | Qt::ItemIsEnabled));
2194         ui.cbUpdateType->setCurrentIndex(0);
2195 
2196         ui.cbFileType->setEnabled(true);
2197         ui.cbFileType->show();
2198         ui.gbOptions->setEnabled(true);
2199         ui.cbFilter->setEnabled(true);
2200         ui.lFileType->show();
2201         setMQTTVisible(false);
2202         break;
2203     case LiveDataSource::SourceType::MQTT:
2204 #ifdef HAVE_MQTT
2205         item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
2206 
2207         // for MQTT we read ascii data only, hide the file type options
2208         for (int i = 0; i < ui.cbFileType->count(); ++i) {
2209             if (static_cast<AbstractFileFilter::FileType>(ui.cbFileType->itemData(i).toInt()) == AbstractFileFilter::FileType::Ascii) {
2210                 if (ui.cbFileType->currentIndex() == i)
2211                     initOptionsWidget();
2212                 else
2213                     ui.cbFileType->setCurrentIndex(i);
2214 
2215                 break;
2216             }
2217         }
2218         ui.cbFileType->hide();
2219         ui.lFileType->hide();
2220 
2221         ui.lBaudRate->hide();
2222         ui.cbBaudRate->hide();
2223         ui.lSerialPort->hide();
2224         ui.cbSerialPort->hide();
2225         ui.lHost->hide();
2226         ui.leHost->hide();
2227         ui.lPort->hide();
2228         ui.lePort->hide();
2229         ui.lFileName->hide();
2230         m_cbFileName->hide();
2231         ui.bFileInfo->hide();
2232         ui.bOpen->hide();
2233         ui.lRelativePath->hide();
2234         ui.chbRelativePath->hide();
2235         ui.chbLinkFile->hide();
2236 
2237         setMQTTVisible(true);
2238 
2239         ui.cbFileType->setEnabled(true);
2240         ui.gbOptions->setEnabled(true);
2241         ui.cbFilter->setEnabled(true);
2242 
2243         // in case there are already connections defined,
2244         // show the available topics for the currently selected connection
2245         mqttConnectionChanged();
2246 #endif
2247         break;
2248     }
2249 
2250     // deactivate/activate options that are specific to file of pipe sources only
2251     auto* typeModel = qobject_cast<const QStandardItemModel*>(ui.cbFileType->model());
2252     if (sourceType != LiveDataSource::SourceType::FileOrPipe) {
2253         // deactivate file types other than ascii and binary
2254         for (int i = 2; i < ui.cbFileType->count(); ++i)
2255             typeModel->item(i)->setFlags(item->flags() & ~(Qt::ItemIsSelectable | Qt::ItemIsEnabled));
2256         if (ui.cbFileType->currentIndex() > 1)
2257             ui.cbFileType->setCurrentIndex(1);
2258 
2259         //"whole file" read option is available for file or pipe only, disable it
2260         typeModel = qobject_cast<const QStandardItemModel*>(ui.cbReadingType->model());
2261         auto* item = typeModel->item(static_cast<int>(LiveDataSource::ReadingType::WholeFile));
2262         item->setFlags(item->flags() & ~(Qt::ItemIsSelectable | Qt::ItemIsEnabled));
2263         if (static_cast<LiveDataSource::ReadingType>(ui.cbReadingType->currentIndex()) == LiveDataSource::ReadingType::WholeFile)
2264             ui.cbReadingType->setCurrentIndex(static_cast<int>(LiveDataSource::ReadingType::TillEnd));
2265 
2266         //"update options" groupbox can be deactivated for "file and pipe" if the file is invalid.
2267         // Activate the groupbox when switching from "file and pipe" to a different source type.
2268         ui.gbUpdateOptions->setEnabled(true);
2269     } else {
2270         for (int i = 2; i < ui.cbFileType->count(); ++i)
2271             typeModel->item(i)->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
2272 
2273         // enable "whole file" item for file or pipe
2274         typeModel = qobject_cast<const QStandardItemModel*>(ui.cbReadingType->model());
2275         auto* item = typeModel->item(static_cast<int>(LiveDataSource::ReadingType::WholeFile));
2276         item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
2277     }
2278 
2279     // disable the header options for non-file sources because:
2280     //* for sockets we allow to import one single value only at the moment
2281     //* for MQTT topics we don't allow to set the vector names since the different topics can have different number of columns
2282     // For files this option still can be useful if the user have to re-read the whole file
2283     // and wants to use the header to set the column names or the user provides manually the column names.
2284     // TODO: adjust this logic later once we allow to import multiple columns from sockets,
2285     // it should be possible to provide the names of the columns
2286     bool visible = (sourceType == LiveDataSource::SourceType::FileOrPipe);
2287     if (m_asciiOptionsWidget)
2288         m_asciiOptionsWidget->showAsciiHeaderOptions(visible);
2289 
2290     Q_EMIT sourceTypeChanged();
2291     refreshPreview();
2292 }
2293 
2294 void ImportFileWidget::enableDataPortionSelection(bool enabled) {
2295     ui.tabWidget->setTabEnabled(ui.tabWidget->indexOf(ui.tabDataPortion), enabled);
2296 }
2297 
2298 #ifdef HAVE_MQTT
2299 
2300 /*!
2301  *\brief called when a different MQTT connection is selected in the connection ComboBox.
2302  * connects to the MQTT broker according to the connection settings.
2303  */
2304 void ImportFileWidget::mqttConnectionChanged() {
2305     if (m_initialisingMQTT || ui.cbConnection->currentIndex() == -1) {
2306         ui.lLWT->hide();
2307         ui.bLWT->hide();
2308         ui.lTopics->hide();
2309         return;
2310     }
2311 
2312     WAIT_CURSOR;
2313     Q_EMIT error(QString());
2314 
2315     // disconnected from the broker that was selected before
2316     disconnectMqttConnection();
2317 
2318     // determine the connection settings for the new broker and initialize the mqtt client
2319     KConfig config(m_configPath, KConfig::SimpleConfig);
2320     KConfigGroup group = config.group(ui.cbConnection->currentText());
2321 
2322     m_client = new QMqttClient;
2323     connect(m_client, &QMqttClient::connected, this, &ImportFileWidget::onMqttConnect);
2324     connect(m_client, &QMqttClient::disconnected, this, &ImportFileWidget::onMqttDisconnect);
2325     connect(m_client, &QMqttClient::messageReceived, this, &ImportFileWidget::mqttMessageReceived);
2326     connect(m_client, &QMqttClient::errorChanged, this, &ImportFileWidget::mqttErrorChanged);
2327 
2328     m_client->setHostname(group.readEntry("Host"));
2329     m_client->setPort(group.readEntry("Port").toUInt());
2330 
2331     const bool useID = group.readEntry("UseID").toUInt();
2332     if (useID)
2333         m_client->setClientId(group.readEntry("ClientID"));
2334 
2335     const bool useAuthentication = group.readEntry("UseAuthentication").toUInt();
2336     if (useAuthentication) {
2337         m_client->setUsername(group.readEntry("UserName"));
2338         m_client->setPassword(group.readEntry("Password"));
2339     }
2340 
2341     // connect to the selected broker
2342     QDEBUG("Connect to " << m_client->hostname() << ":" << m_client->port());
2343     if (!m_connectTimeoutTimer) {
2344         m_connectTimeoutTimer = new QTimer(this);
2345         m_connectTimeoutTimer->setInterval(6000);
2346         connect(m_connectTimeoutTimer, &QTimer::timeout, this, &ImportFileWidget::mqttConnectTimeout);
2347     }
2348     m_connectTimeoutTimer->start();
2349     m_client->connectToHost();
2350 }
2351 
2352 void ImportFileWidget::disconnectMqttConnection() {
2353     if (m_client && m_client->state() == QMqttClient::ClientState::Connected) {
2354         Q_EMIT MQTTClearTopics();
2355         disconnect(m_client, &QMqttClient::disconnected, this, &ImportFileWidget::onMqttDisconnect);
2356         QDEBUG("Disconnecting from " << m_client->hostname());
2357         m_client->disconnectFromHost();
2358         delete m_client;
2359         m_client = nullptr;
2360     }
2361 }
2362 
2363 /*!
2364  * returns \c true if there is a valid connection to an MQTT broker and the user has subscribed to at least 1 topic,
2365  * returns \c false otherwise.
2366  */
2367 bool ImportFileWidget::isMqttValid() {
2368     if (!m_client)
2369         return false;
2370 
2371     bool connected = (m_client->state() == QMqttClient::ClientState::Connected);
2372     bool subscribed = (m_subscriptionWidget->subscriptionCount() > 0);
2373     bool fileTypeOk = false;
2374     if (this->currentFileType() == AbstractFileFilter::FileType::Ascii)
2375         fileTypeOk = true;
2376 
2377     return connected && subscribed && fileTypeOk;
2378 }
2379 
2380 /*!
2381  *\brief called when the client connects to the broker successfully.
2382  * subscribes to every topic (# wildcard) in order to later list every available topic
2383  */
2384 void ImportFileWidget::onMqttConnect() {
2385     m_connectTimeoutTimer->stop();
2386     if (m_client->error() == QMqttClient::NoError) {
2387         ui.frameSubscriptions->setVisible(true);
2388         m_subscriptionWidget->setVisible(true);
2389         m_subscriptionWidget->makeVisible(true);
2390 
2391         if (!m_client->subscribe(QMqttTopicFilter(QLatin1String("#")), 1))
2392             Q_EMIT error(i18n("Couldn't subscribe to all available topics."));
2393         else {
2394             Q_EMIT error(QString());
2395             ui.lLWT->show();
2396             ui.bLWT->show();
2397             ui.lTopics->show();
2398         }
2399     } else
2400         Q_EMIT error(QStringLiteral("on mqtt connect error ") + QString::number(m_client->error()));
2401 
2402     Q_EMIT subscriptionsChanged();
2403     RESET_CURSOR;
2404 }
2405 
2406 /*!
2407  *\brief called when the client disconnects from the broker successfully
2408  * removes every information about the former connection
2409  */
2410 void ImportFileWidget::onMqttDisconnect() {
2411     DEBUG("Disconnected from " << STDSTRING(m_client->hostname()));
2412     m_connectTimeoutTimer->stop();
2413 
2414     ui.lTopics->hide();
2415     ui.frameSubscriptions->hide();
2416     ui.lLWT->hide();
2417     ui.bLWT->hide();
2418 
2419     ui.cbConnection->setCurrentIndex(-1);
2420 
2421     Q_EMIT subscriptionsChanged();
2422     Q_EMIT error(i18n("Disconnected from '%1'.", m_client->hostname()));
2423     RESET_CURSOR;
2424 }
2425 
2426 /*!
2427  *\brief called when the subscribe button is pressed
2428  * subscribes to the topic represented by the current item of twTopics
2429  */
2430 void ImportFileWidget::subscribeTopic(const QString& name, uint QoS) {
2431     const QMqttTopicFilter filter{name};
2432     QMqttSubscription* tempSubscription = m_client->subscribe(filter, static_cast<quint8>(QoS));
2433 
2434     if (tempSubscription) {
2435         m_mqttSubscriptions.push_back(tempSubscription);
2436         connect(tempSubscription, &QMqttSubscription::messageReceived, this, &ImportFileWidget::mqttSubscriptionMessageReceived);
2437         Q_EMIT subscriptionsChanged();
2438     }
2439 }
2440 
2441 /*!
2442  *\brief Unsubscribes from the given topic, and removes any data connected to it
2443  *
2444  * \param topicName the name of a topic we want to unsubscribe from
2445  */
2446 void ImportFileWidget::unsubscribeTopic(const QString& topicName, QVector<QTreeWidgetItem*> children) {
2447     if (topicName.isEmpty())
2448         return;
2449 
2450     for (int i = 0; i < m_mqttSubscriptions.count(); ++i) {
2451         if (m_mqttSubscriptions[i]->topic().filter() == topicName) {
2452             // explicitly disconnect from the signal, callling QMqttClient::unsubscribe() below is not enough
2453             disconnect(m_mqttSubscriptions.at(i), &QMqttSubscription::messageReceived, this, &ImportFileWidget::mqttSubscriptionMessageReceived);
2454             m_mqttSubscriptions.remove(i);
2455             break;
2456         }
2457     }
2458 
2459     QMqttTopicFilter filter{topicName};
2460     m_client->unsubscribe(filter);
2461 
2462     QMapIterator<QMqttTopicName, QMqttMessage> j(m_lastMessage);
2463     while (j.hasNext()) {
2464         j.next();
2465         if (MQTTSubscriptionWidget::checkTopicContains(topicName, j.key().name()))
2466             m_lastMessage.remove(j.key());
2467     }
2468 
2469     if (m_willSettings.willTopic == topicName) {
2470         if (m_subscriptionWidget->subscriptionCount() > 0)
2471             m_willSettings.willTopic = children[0]->text(0);
2472         else
2473             m_willSettings.willTopic.clear();
2474     }
2475 
2476     // signals that there was a change among the subscribed topics
2477     Q_EMIT subscriptionsChanged();
2478     refreshPreview();
2479 }
2480 
2481 /*!
2482  *\brief called when the client receives a message
2483  * if the message arrived from a new topic, the topic is put in twTopics
2484  */
2485 void ImportFileWidget::mqttMessageReceived(const QByteArray& /*message*/, const QMqttTopicName& topic) {
2486     //  qDebug()<<"received " << topic.name();
2487     if (m_addedTopics.contains(topic.name()))
2488         return;
2489 
2490     m_addedTopics.push_back(topic.name());
2491     m_subscriptionWidget->setTopicTreeText(i18n("Available (%1)", m_addedTopics.size()));
2492     QStringList name;
2493     QString rootName;
2494     const QChar sep = QLatin1Char('/');
2495 
2496     if (topic.name().contains(sep)) {
2497 #if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
2498         const QStringList& list = topic.name().split(sep, Qt::SkipEmptyParts);
2499 #else
2500         const QStringList& list = topic.name().split(sep, QString::SkipEmptyParts);
2501 #endif
2502 
2503         if (!list.isEmpty()) {
2504             rootName = list.at(0);
2505             name.append(list.at(0));
2506             int topItemIdx = -1;
2507             // check whether the first level of the topic can be found in twTopics
2508             for (int i = 0; i < m_subscriptionWidget->topicCount(); ++i) {
2509                 if (m_subscriptionWidget->topLevelTopic(i)->text(0) == list.at(0)) {
2510                     topItemIdx = i;
2511                     break;
2512                 }
2513             }
2514 
2515             // if not we simply add every level of the topic to the tree
2516             if (topItemIdx < 0) {
2517                 auto* currentItem = new QTreeWidgetItem(name);
2518                 m_subscriptionWidget->addTopic(currentItem);
2519                 for (int i = 1; i < list.size(); ++i) {
2520                     name.clear();
2521                     name.append(list.at(i));
2522                     currentItem->addChild(new QTreeWidgetItem(name));
2523                     currentItem = currentItem->child(0);
2524                 }
2525             }
2526             // otherwise we search for the first level that isn't part of the tree,
2527             // then add every level of the topic to the tree from that certain level
2528             else {
2529                 QTreeWidgetItem* currentItem = m_subscriptionWidget->topLevelTopic(topItemIdx);
2530                 int listIdx = 1;
2531                 for (; listIdx < list.size(); ++listIdx) {
2532                     QTreeWidgetItem* childItem = nullptr;
2533                     bool found = false;
2534                     for (int j = 0; j < currentItem->childCount(); ++j) {
2535                         childItem = currentItem->child(j);
2536                         if (childItem->text(0) == list.at(listIdx)) {
2537                             found = true;
2538                             currentItem = childItem;
2539                             break;
2540                         }
2541                     }
2542                     if (!found) {
2543                         // this is the level that isn't present in the tree
2544                         break;
2545                     }
2546                 }
2547 
2548                 // add every level to the tree starting with the first level that isn't part of the tree
2549                 for (; listIdx < list.size(); ++listIdx) {
2550                     name.clear();
2551                     name.append(list.at(listIdx));
2552                     currentItem->addChild(new QTreeWidgetItem(name));
2553                     currentItem = currentItem->child(currentItem->childCount() - 1);
2554                 }
2555             }
2556         }
2557     } else {
2558         rootName = topic.name();
2559         name.append(topic.name());
2560         m_subscriptionWidget->addTopic(new QTreeWidgetItem(name));
2561     }
2562 
2563     // if a subscribed topic contains the new topic, we have to update twSubscriptions
2564     for (int i = 0; i < m_subscriptionWidget->subscriptionCount(); ++i) {
2565 #if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
2566         const QStringList subscriptionName = m_subscriptionWidget->topLevelSubscription(i)->text(0).split(sep, Qt::SkipEmptyParts);
2567 #else
2568         const QStringList subscriptionName = m_subscriptionWidget->topLevelSubscription(i)->text(0).split(sep, QString::SkipEmptyParts);
2569 #endif
2570         if (!subscriptionName.isEmpty()) {
2571             if (rootName == subscriptionName.first()) {
2572                 QVector<QString> subscriptions;
2573                 for (const auto& sub : m_mqttSubscriptions)
2574                     subscriptions.push_back(sub->topic().filter());
2575                 Q_EMIT updateSubscriptionTree(subscriptions);
2576                 break;
2577             }
2578         }
2579     }
2580 
2581     // signals that a newTopic was added, in order to fill the completer of leTopics
2582     Q_EMIT newTopic(rootName);
2583 }
2584 
2585 /*!
2586  *\brief called when the client receives a message from a subscribed topic (that isn't the "#" wildcard)
2587  */
2588 void ImportFileWidget::mqttSubscriptionMessageReceived(const QMqttMessage& msg) {
2589     QDEBUG("message received from: " << msg.topic().name());
2590 
2591     // update the last message for the topic
2592     m_lastMessage[msg.topic()] = msg;
2593 }
2594 
2595 /*!
2596  *\brief called when the clientError of the MQTT client changes
2597  *
2598  * \param clientError the current error of the client
2599  */
2600 void ImportFileWidget::mqttErrorChanged(QMqttClient::ClientError clientError) {
2601     switch (clientError) {
2602     case QMqttClient::BadUsernameOrPassword:
2603         Q_EMIT error(i18n("Wrong username or password"));
2604         break;
2605     case QMqttClient::IdRejected:
2606         Q_EMIT error(i18n("The client ID wasn't accepted"));
2607         break;
2608     case QMqttClient::ServerUnavailable:
2609     case QMqttClient::TransportInvalid:
2610         Q_EMIT error(i18n("The broker %1 couldn't be reached.", m_client->hostname()));
2611         break;
2612     case QMqttClient::NotAuthorized:
2613         Q_EMIT error(i18n("The client is not authorized to connect."));
2614         break;
2615     case QMqttClient::UnknownError:
2616         Q_EMIT error(i18n("An unknown error occurred."));
2617         break;
2618     case QMqttClient::NoError:
2619     case QMqttClient::InvalidProtocolVersion:
2620     case QMqttClient::ProtocolViolation:
2621     case QMqttClient::Mqtt5SpecificError:
2622         Q_EMIT error(i18n("An error occurred."));
2623         break;
2624     default:
2625         Q_EMIT error(i18n("An error occurred."));
2626         break;
2627     }
2628     m_connectTimeoutTimer->stop();
2629 }
2630 
2631 /*!
2632  *\brief called when m_connectTimeoutTimer ticks,
2633  *       meaning that the client couldn't connect to the broker in 5 seconds
2634  *       disconnects the client, stops the timer, and warns the user
2635  */
2636 void ImportFileWidget::mqttConnectTimeout() {
2637     m_client->disconnectFromHost();
2638     m_connectTimeoutTimer->stop();
2639     Q_EMIT error(i18n("Connecting to '%1:%2' timed out.", m_client->hostname(), m_client->port()));
2640     RESET_CURSOR;
2641 }
2642 
2643 /*!
2644     Shows the MQTT connection manager where the connections are created and edited.
2645     The selected connection is selected in the connection combo box in this widget.
2646 */
2647 void ImportFileWidget::showMQTTConnectionManager() {
2648     bool previousConnectionChanged = false;
2649     auto* dlg = new MQTTConnectionManagerDialog(this, ui.cbConnection->currentText(), previousConnectionChanged);
2650 
2651     if (dlg->exec() == QDialog::Accepted) {
2652         // re-read the available connections to be in sync with the changes in MQTTConnectionManager
2653         m_initialisingMQTT = true;
2654         const QString& prevConn = ui.cbConnection->currentText();
2655         ui.cbConnection->clear();
2656         readMQTTConnections();
2657         m_initialisingMQTT = false;
2658 
2659         // select the connection the user has selected in MQTTConnectionManager
2660         const QString& conn = dlg->connection();
2661 
2662         int index = ui.cbConnection->findText(conn);
2663         if (conn != prevConn) { // Current connection isn't the previous one
2664             if (ui.cbConnection->currentIndex() != index)
2665                 ui.cbConnection->setCurrentIndex(index);
2666             else
2667                 mqttConnectionChanged();
2668         } else if (dlg->initialConnectionChanged()) { // Current connection is the same with previous one but it changed
2669             if (ui.cbConnection->currentIndex() == index)
2670                 mqttConnectionChanged();
2671             else
2672                 ui.cbConnection->setCurrentIndex(index);
2673         } else { // Previous connection wasn't changed
2674             m_initialisingMQTT = true;
2675             ui.cbConnection->setCurrentIndex(index);
2676             m_initialisingMQTT = false;
2677         }
2678     }
2679     delete dlg;
2680 }
2681 
2682 /*!
2683     loads all available saved MQTT nconnections
2684 */
2685 void ImportFileWidget::readMQTTConnections() {
2686     DEBUG("ImportFileWidget: reading available MQTT connections");
2687     KConfig config(m_configPath, KConfig::SimpleConfig);
2688     for (const auto& name : config.groupList())
2689         ui.cbConnection->addItem(name);
2690 }
2691 
2692 /*!
2693  * \brief Shows the mqtt will settings widget, which allows the user to modify the will settings
2694  */
2695 void ImportFileWidget::showWillSettings() {
2696     QMenu menu;
2697 
2698     QVector<QTreeWidgetItem*> children;
2699     for (int i = 0; i < m_subscriptionWidget->subscriptionCount(); ++i)
2700         MQTTSubscriptionWidget::findSubscriptionLeafChildren(children, m_subscriptionWidget->topLevelSubscription(i));
2701 
2702     QVector<QString> topics;
2703     for (const auto& child : children)
2704         topics.append(child->text(0));
2705 
2706     MQTTWillSettingsWidget willSettingsWidget(&menu, m_willSettings, topics);
2707 
2708     connect(&willSettingsWidget, &MQTTWillSettingsWidget::applyClicked, [this, &menu, &willSettingsWidget]() {
2709         m_willSettings = willSettingsWidget.will();
2710         menu.close();
2711     });
2712     auto* widgetAction = new QWidgetAction(this);
2713     widgetAction->setDefaultWidget(&willSettingsWidget);
2714     menu.addAction(widgetAction);
2715 
2716     const QPoint pos(ui.bLWT->sizeHint().width(), ui.bLWT->sizeHint().height());
2717     menu.exec(ui.bLWT->mapToGlobal(pos));
2718 }
2719 
2720 void ImportFileWidget::enableWill(bool enable) {
2721     if (enable) {
2722         if (!ui.bLWT->isEnabled())
2723             ui.bLWT->setEnabled(enable);
2724     } else
2725         ui.bLWT->setEnabled(enable);
2726 }
2727 
2728 /*!
2729     saves the settings to the MQTTClient \c client.
2730 */
2731 void ImportFileWidget::saveMQTTSettings(MQTTClient* client) const {
2732     DEBUG(Q_FUNC_INFO);
2733     auto updateType = static_cast<MQTTClient::UpdateType>(ui.cbUpdateType->currentIndex());
2734     auto readingType = static_cast<MQTTClient::ReadingType>(ui.cbReadingType->currentIndex());
2735 
2736     currentFileFilter();
2737     client->setFilter(static_cast<AsciiFilter*>(m_currentFilter.release())); // pass ownership of the filter to MQTTClient
2738 
2739     client->setReadingType(readingType);
2740 
2741     if (updateType == MQTTClient::UpdateType::TimeInterval)
2742         client->setUpdateInterval(ui.sbUpdateInterval->value());
2743 
2744     client->setKeepNValues(ui.sbKeepNValues->value());
2745     client->setUpdateType(updateType);
2746 
2747     if (readingType != MQTTClient::ReadingType::TillEnd)
2748         client->setSampleSize(ui.sbSampleSize->value());
2749 
2750     client->setMQTTClientHostPort(m_client->hostname(), m_client->port());
2751 
2752     KConfig config(m_configPath, KConfig::SimpleConfig);
2753     KConfigGroup group = config.group(ui.cbConnection->currentText());
2754 
2755     bool useID = group.readEntry("UseID").toUInt();
2756     bool useAuthentication = group.readEntry("UseAuthentication").toUInt();
2757 
2758     client->setMQTTUseAuthentication(useAuthentication);
2759     if (useAuthentication)
2760         client->setMQTTClientAuthentication(m_client->username(), m_client->password());
2761 
2762     client->setMQTTUseID(useID);
2763     if (useID)
2764         client->setMQTTClientId(m_client->clientId());
2765 
2766     for (int i = 0; i < m_mqttSubscriptions.count(); ++i)
2767         client->addInitialMQTTSubscriptions(m_mqttSubscriptions[i]->topic(), m_mqttSubscriptions[i]->qos());
2768 
2769     const bool retain = group.readEntry("Retain").toUInt();
2770     client->setMQTTRetain(retain);
2771 
2772     if (m_willSettings.enabled)
2773         client->setWillSettings(m_willSettings);
2774 }
2775 #endif