"Fossies" - the Fresh Open Source Software Archive

Member "labplot-2.8.2/src/kdefrontend/datasources/ImportFileWidget.cpp" (24 Feb 2021, 80041 Bytes) of package /linux/privat/labplot-2.8.2.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "ImportFileWidget.cpp" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 2.8.1_vs_2.8.2.

    1 /***************************************************************************
    2 File                 : ImportFileWidget.cpp
    3 Project              : LabPlot
    4 Description          : import file data widget
    5 --------------------------------------------------------------------
    6 Copyright            : (C) 2009-2018 Stefan Gerlach (stefan.gerlach@uni.kn)
    7 Copyright            : (C) 2009-2020 Alexander Semke (alexander.semke@web.de)
    8 Copyright            : (C) 2017-2018 Fabian Kristof (fkristofszabolcs@gmail.com)
    9 Copyright            : (C) 2018-2019 Kovacs Ferencz (kferike98@gmail.com)
   10 
   11 ***************************************************************************/
   12 
   13 /***************************************************************************
   14  *                                                                         *
   15  *  This program is free software; you can redistribute it and/or modify   *
   16  *  it under the terms of the GNU General Public License as published by   *
   17  *  the Free Software Foundation; either version 2 of the License, or      *
   18  *  (at your option) any later version.                                    *
   19  *                                                                         *
   20  *  This program is distributed in the hope that it will be useful,        *
   21  *  but WITHOUT ANY WARRANTY; without even the implied warranty of         *
   22  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the          *
   23  *  GNU General Public License for more details.                           *
   24  *                                                                         *
   25  *   You should have received a copy of the GNU General Public License     *
   26  *   along with this program; if not, write to the Free Software           *
   27  *   Foundation, Inc., 51 Franklin Street, Fifth Floor,                    *
   28  *   Boston, MA  02110-1301  USA                                           *
   29  *                                                                         *
   30  ***************************************************************************/
   31 
   32 #include "ImportFileWidget.h"
   33 #include "FileInfoDialog.h"
   34 #include "backend/datasources/filters/filters.h"
   35 #include "AsciiOptionsWidget.h"
   36 #include "BinaryOptionsWidget.h"
   37 #include "HDF5OptionsWidget.h"
   38 #include "ImageOptionsWidget.h"
   39 #include "NetCDFOptionsWidget.h"
   40 #include "FITSOptionsWidget.h"
   41 #include "JsonOptionsWidget.h"
   42 #include "ROOTOptionsWidget.h"
   43 
   44 #include <QCompleter>
   45 #include <QDir>
   46 #include <QDirModel>
   47 #include <QFileDialog>
   48 #include <QInputDialog>
   49 #include <QIntValidator>
   50 #include <QLocalSocket>
   51 #include <QStandardItemModel>
   52 #include <QTableWidget>
   53 #include <QTcpSocket>
   54 #include <QTimer>
   55 #include <QUdpSocket>
   56 #include <QTreeWidgetItem>
   57 
   58 #include <KConfigGroup>
   59 #include <KLocalizedString>
   60 #include <KSharedConfig>
   61 #include <KUrlComboBox>
   62 
   63 #ifdef HAVE_MQTT
   64 #include "kdefrontend/widgets/MQTTWillSettingsWidget.h"
   65 #include "MQTTConnectionManagerDialog.h"
   66 #include "MQTTSubscriptionWidget.h"
   67 #include <QMqttClient>
   68 #include <QMqttSubscription>
   69 #include <QMqttTopicFilter>
   70 #include <QMqttMessage>
   71 #include <QMessageBox>
   72 #include <QWidgetAction>
   73 #include <QMenu>
   74 #endif
   75 
   76 QString absolutePath(const QString& fileName) {
   77 #ifdef HAVE_WINDOWS
   78     if (!fileName.isEmpty() && fileName.at(1) != QLatin1String(":"))
   79 #else
   80     if (!fileName.isEmpty() && fileName.at(0) != QLatin1String("/"))
   81 #endif
   82         return QDir::homePath() + QLatin1String("/") + fileName;
   83 
   84     return fileName;
   85 }
   86 
   87 /*!
   88    \class ImportFileWidget
   89    \brief Widget for importing data from a file.
   90 
   91    \ingroup kdefrontend
   92 */
   93 ImportFileWidget::ImportFileWidget(QWidget* parent, bool liveDataSource, const QString& fileName) : QWidget(parent),
   94     m_fileName(fileName),
   95     m_liveDataSource(liveDataSource)
   96 #ifdef HAVE_MQTT
   97     ,
   98     m_subscriptionWidget(new MQTTSubscriptionWidget(this))
   99 #endif
  100 {
  101     ui.setupUi(this);
  102 
  103     //add supported file types
  104     if (!liveDataSource) {
  105         ui.cbFileType->addItem(i18n("ASCII data"), static_cast<int>(AbstractFileFilter::FileType::Ascii));
  106         ui.cbFileType->addItem(i18n("Binary data"), static_cast<int>(AbstractFileFilter::FileType::Binary));
  107         ui.cbFileType->addItem(i18n("Image"), static_cast<int>(AbstractFileFilter::FileType::Image));
  108 #ifdef HAVE_HDF5
  109         ui.cbFileType->addItem(i18n("Hierarchical Data Format 5 (HDF5)"), static_cast<int>(AbstractFileFilter::FileType::HDF5));
  110 #endif
  111 #ifdef HAVE_NETCDF
  112         ui.cbFileType->addItem(i18n("Network Common Data Format (NetCDF)"), static_cast<int>(AbstractFileFilter::FileType::NETCDF));
  113 #endif
  114 #ifdef HAVE_FITS
  115         ui.cbFileType->addItem(i18n("Flexible Image Transport System Data Format (FITS)"), static_cast<int>(AbstractFileFilter::FileType::FITS));
  116 #endif
  117         ui.cbFileType->addItem(i18n("JSON data"), static_cast<int>(AbstractFileFilter::FileType::JSON));
  118 #ifdef HAVE_ZIP
  119         ui.cbFileType->addItem(i18n("ROOT (CERN)"), static_cast<int>(AbstractFileFilter::FileType::ROOT));
  120 #endif
  121         ui.cbFileType->addItem(i18n("Ngspice RAW ASCII"), static_cast<int>(AbstractFileFilter::FileType::NgspiceRawAscii));
  122         ui.cbFileType->addItem(i18n("Ngspice RAW Binary"), static_cast<int>(AbstractFileFilter::FileType::NgspiceRawBinary));
  123 
  124         //hide widgets relevant for live data reading only
  125         ui.lRelativePath->hide();
  126         ui.chbRelativePath->hide();
  127         ui.lSourceType->hide();
  128         ui.cbSourceType->hide();
  129         ui.gbUpdateOptions->hide();
  130     } else {
  131         ui.cbFileType->addItem(i18n("ASCII data"), static_cast<int>(AbstractFileFilter::FileType::Ascii));
  132         ui.cbFileType->addItem(i18n("Binary data"), static_cast<int>(AbstractFileFilter::FileType::Binary));
  133 #ifdef HAVE_ZIP
  134         ui.cbFileType->addItem(i18n("ROOT (CERN)"), static_cast<int>(AbstractFileFilter::FileType::ROOT));
  135 #endif
  136         ui.cbFileType->addItem(i18n("Ngspice RAW ASCII"), static_cast<int>(AbstractFileFilter::FileType::NgspiceRawAscii));
  137         ui.cbFileType->addItem(i18n("Ngspice RAW Binary"), static_cast<int>(AbstractFileFilter::FileType::NgspiceRawBinary));
  138 
  139         ui.lePort->setValidator( new QIntValidator(ui.lePort) );
  140         ui.cbBaudRate->addItems(LiveDataSource::supportedBaudRates());
  141         ui.cbSerialPort->addItems(LiveDataSource::availablePorts());
  142 
  143         ui.tabWidget->removeTab(2);
  144 
  145         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."));
  146         ui.chbRelativePath->setToolTip(i18n("If this option is checked, the relative path of the file (relative to project's folder) will be saved."));
  147     }
  148 
  149     QStringList filterItems {i18n("Automatic"), i18n("Custom")};
  150     ui.cbFilter->addItems(filterItems);
  151 
  152     //hide options that will be activated on demand
  153     ui.gbOptions->hide();
  154     ui.gbUpdateOptions->hide();
  155     setMQTTVisible(false);
  156 
  157     ui.cbReadingType->addItem(i18n("Whole file"), static_cast<int>(LiveDataSource::ReadingType::WholeFile));
  158 
  159     ui.bOpen->setIcon( QIcon::fromTheme(QLatin1String("document-open")) );
  160     ui.bFileInfo->setIcon( QIcon::fromTheme(QLatin1String("help-about")) );
  161     ui.bManageFilters->setIcon( QIcon::fromTheme(QLatin1String("configure")) );
  162     ui.bSaveFilter->setIcon( QIcon::fromTheme(QLatin1String("document-save")) );
  163     ui.bRefreshPreview->setIcon( QIcon::fromTheme(QLatin1String("view-refresh")) );
  164 
  165     ui.tvJson->header()->setSectionResizeMode(QHeaderView::ResizeToContents);
  166     ui.tvJson->setAlternatingRowColors(true);
  167     showJsonModel(false);
  168 
  169     // the table widget for preview
  170     m_twPreview = new QTableWidget(ui.tePreview);
  171     m_twPreview->verticalHeader()->hide();
  172     m_twPreview->setEditTriggers(QTableWidget::NoEditTriggers);
  173     auto* layout = new QHBoxLayout;
  174     layout->addWidget(m_twPreview);
  175     ui.tePreview->setLayout(layout);
  176     m_twPreview->hide();
  177 
  178     // the combobox for the import path
  179     m_cbFileName = new KUrlComboBox(KUrlComboBox::Mode::Files, this);
  180     m_cbFileName->setMaxItems(7);
  181     auto* gridLayout = dynamic_cast<QGridLayout*>(ui.gbDataSource->layout());
  182     if (gridLayout)
  183         gridLayout->addWidget(m_cbFileName, 1, 2, 1, 3);
  184 
  185 
  186     //tooltips
  187     QString info = i18n("Specify how the data source has to be processed on every read:"
  188                        "<ul>"
  189                        "<li>Continuously fixed - fixed amount of samples is processed starting from the beginning of the newly received data.</li>"
  190                        "<li>From End - fixed amount of samples is processed starting from the end of the newly received data.</li>"
  191                        "<li>Till the End - all newly received data is processed.</li>"
  192                        "<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>"
  193                        "</ul>");
  194     ui.lReadingType->setToolTip(info);
  195     ui.cbReadingType->setToolTip(info);
  196 
  197     info = i18n("Number of samples (lines) to be processed on every read.\n"
  198                 "Only needs to be specified for the reading mode \"Continuously Fixed\" and \"From End\".");
  199     ui.lSampleSize->setToolTip(info);
  200     ui.sbSampleSize->setToolTip(info);
  201 
  202     info = i18n("Specify when and how frequently the data source needs to be read:"
  203                 "<ul>"
  204                 "<li>Periodically - the data source is read periodically with user specified time interval.</li>"
  205                 "<li>On New Data - the data source is read when new data arrives.</li>"
  206                 "</ul>");
  207     ui.lUpdateType->setToolTip(info);
  208     ui.cbUpdateType->setToolTip(info);
  209 
  210     info = i18n("Specify how frequently the data source has to be read.");
  211     ui.lUpdateInterval->setToolTip(info);
  212     ui.sbUpdateInterval->setToolTip(info);
  213 
  214     info = i18n("Specify how many samples need to be kept in memory after reading.\n"
  215                 "Use \"All\" if all data has to be kept.");
  216     ui.lKeepLastValues->setToolTip(info);
  217     ui.sbKeepNValues->setToolTip(info);
  218 
  219 #ifdef HAVE_MQTT
  220     ui.cbSourceType->addItem(QLatin1String("MQTT"));
  221     m_configPath = QStandardPaths::standardLocations(QStandardPaths::AppDataLocation).constFirst() + QLatin1String("MQTT_connections");
  222 
  223     //add subscriptions widget
  224     layout = new QHBoxLayout;
  225     layout->setContentsMargins(0, 0, 0, 0);
  226     layout->setSpacing(0);
  227     layout->addWidget(m_subscriptionWidget);
  228     ui.frameSubscriptions->setLayout(layout);
  229 
  230     ui.bManageConnections->setIcon(QIcon::fromTheme(QLatin1String("network-server")));
  231     ui.bManageConnections->setToolTip(i18n("Manage MQTT connections"));
  232 
  233     info = i18n("Specify the 'Last Will and Testament' message (LWT). At least one topic has to be subscribed.");
  234     ui.lLWT->setToolTip(info);
  235     ui.bLWT->setToolTip(info);
  236     ui.bLWT->setEnabled(false);
  237     ui.bLWT->setIcon(ui.bLWT->style()->standardIcon(QStyle::SP_FileDialogDetailedView));
  238 #endif
  239 
  240     //TODO: implement save/load of user-defined settings later and activate these buttons again
  241     ui.bSaveFilter->hide();
  242     ui.bManageFilters->hide();
  243 }
  244 
  245 void ImportFileWidget::loadSettings() {
  246     m_suppressRefresh = true;
  247 
  248     //load last used settings
  249     QString confName;
  250     if (m_liveDataSource)
  251         confName = QLatin1String("LiveDataImport");
  252     else
  253         confName = QLatin1String("FileImport");
  254     KConfigGroup conf(KSharedConfig::openConfig(), confName);
  255 
  256     //read the source type first since settings in fileNameChanged() depend on this
  257     ui.cbSourceType->setCurrentIndex(conf.readEntry("SourceType").toInt());
  258 
  259     //general settings
  260     AbstractFileFilter::FileType fileType = static_cast<AbstractFileFilter::FileType>(conf.readEntry("Type", 0));
  261     for (int i = 0; i < ui.cbFileType->count(); ++i) {
  262         if (static_cast<AbstractFileFilter::FileType>(ui.cbFileType->itemData(i).toInt()) == fileType) {
  263             if (ui.cbFileType->currentIndex() == i)
  264                 initOptionsWidget();
  265             else
  266                 ui.cbFileType->setCurrentIndex(i);
  267 
  268             break;
  269         }
  270     }
  271 
  272     if (m_fileName.isEmpty()) {
  273         ui.cbFilter->setCurrentIndex(conf.readEntry("Filter", 0));
  274         m_cbFileName->setUrl(conf.readEntry("LastImportedFile", ""));
  275         QStringList urls = m_cbFileName->urls();
  276         urls.append(conf.readXdgListEntry("LastImportedFiles"));
  277         m_cbFileName->setUrls(urls);
  278         filterChanged(ui.cbFilter->currentIndex()); // needed if filter is not changed
  279     } else
  280         m_cbFileName->setUrl(QUrl(m_fileName));
  281 
  282     ui.sbPreviewLines->setValue(conf.readEntry("PreviewLines", 100));
  283 
  284     //live data related settings
  285     ui.cbBaudRate->setCurrentIndex(conf.readEntry("BaudRate", 13)); // index for bautrate 19200b/s
  286     ui.cbReadingType->setCurrentIndex(conf.readEntry("ReadingType", static_cast<int>(LiveDataSource::ReadingType::WholeFile)));
  287     ui.cbSerialPort->setCurrentIndex(conf.readEntry("SerialPort").toInt());
  288     ui.cbUpdateType->setCurrentIndex(conf.readEntry("UpdateType", static_cast<int>(LiveDataSource::UpdateType::NewData)));
  289     updateTypeChanged(ui.cbUpdateType->currentIndex());
  290     ui.leHost->setText(conf.readEntry("Host",""));
  291     ui.sbKeepNValues->setValue(conf.readEntry("KeepNValues", 0)); // keep all values
  292     ui.lePort->setText(conf.readEntry("Port",""));
  293     ui.sbSampleSize->setValue(conf.readEntry("SampleSize", 1));
  294     ui.sbUpdateInterval->setValue(conf.readEntry("UpdateInterval", 1000));
  295     ui.chbLinkFile->setCheckState((Qt::CheckState)conf.readEntry("LinkFile", (int)Qt::CheckState::Unchecked));
  296     ui.chbRelativePath->setCheckState((Qt::CheckState)conf.readEntry("RelativePath", (int)Qt::CheckState::Unchecked));
  297 
  298 #ifdef HAVE_MQTT
  299     //read available MQTT connections
  300     m_initialisingMQTT = true;
  301     readMQTTConnections();
  302     ui.cbConnection->setCurrentIndex(ui.cbConnection->findText(conf.readEntry("Connection", "")));
  303     m_initialisingMQTT = false;
  304 
  305     m_willSettings.enabled = conf.readEntry("mqttWillEnabled", m_willSettings.enabled);
  306     m_willSettings.willRetain = conf.readEntry("mqttWillRetain", m_willSettings.willRetain);
  307     m_willSettings.willUpdateType = static_cast<MQTTClient::WillUpdateType>(conf.readEntry("mqttWillUpdateType", (int)m_willSettings.willUpdateType));
  308     m_willSettings.willMessageType = static_cast<MQTTClient::WillMessageType>(conf.readEntry("mqttWillMessageType", (int)m_willSettings.willMessageType));
  309     m_willSettings.willQoS = conf.readEntry("mqttWillQoS", (int)m_willSettings.willQoS);
  310     m_willSettings.willOwnMessage = conf.readEntry("mqttWillOwnMessage", m_willSettings.willOwnMessage);
  311     m_willSettings.willTimeInterval = conf.readEntry("mqttWillUpdateInterval", m_willSettings.willTimeInterval);
  312 
  313     const QString& willStatistics = conf.readEntry("mqttWillStatistics","");
  314     const QStringList& statisticsList = willStatistics.split('|', QString::SplitBehavior::SkipEmptyParts);
  315     for (auto value : statisticsList)
  316         m_willSettings.willStatistics[value.toInt()] = true;
  317 #endif
  318 
  319     //initialize the slots after all settings were set in order to avoid unneeded refreshes
  320     initSlots();
  321 
  322     //update the status of the widgets
  323     fileTypeChanged();
  324     sourceTypeChanged(static_cast<int>(currentSourceType()));
  325     readingTypeChanged(ui.cbReadingType->currentIndex());
  326 
  327     //all set now, refresh the content of the file and the preview for the selected dataset
  328     m_suppressRefresh = false;
  329     QTimer::singleShot(100, this, [=] () {
  330         WAIT_CURSOR;
  331         if (currentSourceType() == LiveDataSource::SourceType::FileOrPipe) {
  332             QString tempFileName = fileName();
  333             const QString& fileName = absolutePath(tempFileName);
  334             if (QFile::exists(fileName))
  335                 updateContent(fileName);
  336         }
  337 
  338         refreshPreview();
  339         RESET_CURSOR;
  340     });
  341 }
  342 
  343 ImportFileWidget::~ImportFileWidget() {
  344     // save current settings
  345     QString confName;
  346     if (m_liveDataSource)
  347         confName = QLatin1String("LiveDataImport");
  348     else
  349         confName = QLatin1String("FileImport");
  350     KConfigGroup conf(KSharedConfig::openConfig(), confName);
  351 
  352     // general settings
  353     conf.writeEntry("Type", (int)currentFileType());
  354     conf.writeEntry("Filter", ui.cbFilter->currentIndex());
  355     conf.writeEntry("LastImportedFile", m_cbFileName->currentText());
  356     conf.writeXdgListEntry("LastImportedFiles", m_cbFileName->urls());
  357     conf.writeEntry("PreviewLines", ui.sbPreviewLines->value());
  358 
  359     //live data related settings
  360     conf.writeEntry("SourceType", (int)currentSourceType());
  361     conf.writeEntry("UpdateType", ui.cbUpdateType->currentIndex());
  362     conf.writeEntry("ReadingType", ui.cbReadingType->currentIndex());
  363     conf.writeEntry("SampleSize", ui.sbSampleSize->value());
  364     conf.writeEntry("KeepNValues", ui.sbKeepNValues->value());
  365     conf.writeEntry("BaudRate", ui.cbBaudRate->currentIndex());
  366     conf.writeEntry("SerialPort", ui.cbSerialPort->currentIndex());
  367     conf.writeEntry("Host", ui.leHost->text());
  368     conf.writeEntry("Port", ui.lePort->text());
  369     conf.writeEntry("UpdateInterval", ui.sbUpdateInterval->value());
  370     conf.writeEntry("LinkFile", (int)ui.chbLinkFile->checkState());
  371     conf.writeEntry("RelativePath", (int)ui.chbRelativePath->checkState());
  372 
  373 #ifdef HAVE_MQTT
  374     delete m_connectTimeoutTimer;
  375     delete m_subscriptionWidget;
  376 
  377     //MQTT related settings
  378     conf.writeEntry("Connection", ui.cbConnection->currentText());
  379     conf.writeEntry("mqttWillMessageType", static_cast<int>(m_willSettings.willMessageType));
  380     conf.writeEntry("mqttWillUpdateType", static_cast<int>(m_willSettings.willUpdateType));
  381     conf.writeEntry("mqttWillQoS", QString::number(m_willSettings.willQoS));
  382     conf.writeEntry("mqttWillOwnMessage", m_willSettings.willOwnMessage);
  383     conf.writeEntry("mqttWillUpdateInterval", QString::number(m_willSettings.willTimeInterval));
  384     QString willStatistics;
  385     for (int i = 0; i < m_willSettings.willStatistics.size(); ++i) {
  386         if (m_willSettings.willStatistics[i])
  387             willStatistics += QString::number(i)+ QLatin1Char('|');
  388     }
  389     conf.writeEntry("mqttWillStatistics", willStatistics);
  390     conf.writeEntry("mqttWillRetain", static_cast<int>(m_willSettings.willRetain));
  391     conf.writeEntry("mqttWillUse", static_cast<int>(m_willSettings.enabled));
  392 #endif
  393 
  394     // data type specific settings
  395     if (m_asciiOptionsWidget)
  396         m_asciiOptionsWidget->saveSettings();
  397     if (m_binaryOptionsWidget)
  398         m_binaryOptionsWidget->saveSettings();
  399     if (m_imageOptionsWidget)
  400         m_imageOptionsWidget->saveSettings();
  401     if (m_jsonOptionsWidget)
  402         m_jsonOptionsWidget->saveSettings();
  403 }
  404 
  405 void ImportFileWidget::initSlots() {
  406     //SLOTs for the general part of the data source configuration
  407     connect(ui.cbSourceType, static_cast<void (QComboBox::*) (int)>(&QComboBox::currentIndexChanged),
  408             this, static_cast<void (ImportFileWidget::*) (int)>(&ImportFileWidget::sourceTypeChanged));
  409     connect(m_cbFileName, &KUrlComboBox::urlActivated,
  410             this, [=](const QUrl &url){fileNameChanged(url.path());});
  411     connect(ui.leHost, &QLineEdit::textChanged, this, &ImportFileWidget::hostChanged);
  412     connect(ui.lePort, &QLineEdit::textChanged, this, &ImportFileWidget::portChanged);
  413     connect(ui.tvJson, &QTreeView::clicked, this, &ImportFileWidget::refreshPreview);
  414     connect(ui.bOpen, &QPushButton::clicked, this, &ImportFileWidget::selectFile);
  415     connect(ui.bFileInfo, &QPushButton::clicked, this, &ImportFileWidget::fileInfoDialog);
  416     connect(ui.bSaveFilter, &QPushButton::clicked, this, &ImportFileWidget::saveFilter);
  417     connect(ui.bManageFilters, &QPushButton::clicked, this, &ImportFileWidget::manageFilters);
  418     connect(ui.cbFileType, static_cast<void (KComboBox::*) (int)>(&KComboBox::currentIndexChanged),
  419             this, &ImportFileWidget::fileTypeChanged);
  420     connect(ui.cbUpdateType, static_cast<void (QComboBox::*) (int)>(&QComboBox::currentIndexChanged),
  421             this, &ImportFileWidget::updateTypeChanged);
  422     connect(ui.cbReadingType, static_cast<void (QComboBox::*) (int)>(&QComboBox::currentIndexChanged),
  423             this, &ImportFileWidget::readingTypeChanged);
  424     connect(ui.cbFilter, static_cast<void (KComboBox::*) (int)>(&KComboBox::activated), this, &ImportFileWidget::filterChanged);
  425     connect(ui.bRefreshPreview, &QPushButton::clicked, this, &ImportFileWidget::refreshPreview);
  426 
  427 #ifdef HAVE_MQTT
  428     connect(ui.cbConnection, static_cast<void (QComboBox::*) (int)>(&QComboBox::currentIndexChanged), this, &ImportFileWidget::mqttConnectionChanged);
  429     connect(ui.cbFileType, static_cast<void (QComboBox::*) (int)>(&QComboBox::currentIndexChanged), [this]() {
  430         emit checkFileType();
  431     });
  432     connect(ui.bManageConnections, &QPushButton::clicked, this, &ImportFileWidget::showMQTTConnectionManager);
  433     connect(ui.bLWT, &QPushButton::clicked, this, &ImportFileWidget::showWillSettings);
  434     connect(m_subscriptionWidget, &MQTTSubscriptionWidget::makeSubscription, this, &ImportFileWidget::subscribeTopic);
  435     connect(m_subscriptionWidget, &MQTTSubscriptionWidget::MQTTUnsubscribeFromTopic, this, &ImportFileWidget::unsubscribeTopic);
  436     connect(m_subscriptionWidget, &MQTTSubscriptionWidget::enableWill, this, &ImportFileWidget::enableWill);
  437     connect(m_subscriptionWidget, &MQTTSubscriptionWidget::subscriptionChanged, this, &ImportFileWidget::refreshPreview);
  438 #endif
  439 }
  440 
  441 void ImportFileWidget::showAsciiHeaderOptions(bool b) {
  442     if (m_asciiOptionsWidget)
  443         m_asciiOptionsWidget->showAsciiHeaderOptions(b);
  444 }
  445 
  446 void ImportFileWidget::showJsonModel(bool b) {
  447     ui.tvJson->setVisible(b);
  448     ui.lField->setVisible(b);
  449 }
  450 
  451 void ImportFileWidget::showOptions(bool b) {
  452     ui.gbOptions->setVisible(b);
  453 
  454     if (m_liveDataSource)
  455         ui.gbUpdateOptions->setVisible(b);
  456 
  457     resize(layout()->minimumSize());
  458 }
  459 
  460 QString ImportFileWidget::fileName() const {
  461     return m_cbFileName->currentText();
  462 }
  463 
  464 QString ImportFileWidget::selectedObject() const {
  465     const QString& path = fileName();
  466 
  467     //determine the file name only
  468     QString name = path.right(path.length() - path.lastIndexOf('/') - 1);
  469 
  470     //strip away the extension if available
  471     if (name.indexOf('.') != -1)
  472         name = name.left(name.lastIndexOf('.'));
  473 
  474     //for multi-dimensional formats like HDF, netCDF and FITS add the currently selected object
  475     const auto format = currentFileType();
  476     if (format == AbstractFileFilter::FileType::HDF5) {
  477         const QStringList& hdf5Names = m_hdf5OptionsWidget->selectedNames();
  478         if (hdf5Names.size())
  479             name += hdf5Names.first(); //the names of the selected HDF5 objects already have '/'
  480     } else if (format == AbstractFileFilter::FileType::NETCDF) {
  481         const QStringList& names = m_netcdfOptionsWidget->selectedNames();
  482         if (names.size())
  483             name += QLatin1Char('/') + names.first();
  484     } else if (format == AbstractFileFilter::FileType::FITS) {
  485         const QString& extensionName = m_fitsOptionsWidget->currentExtensionName();
  486         if (!extensionName.isEmpty())
  487             name += QLatin1Char('/') + extensionName;
  488     } else if (format == AbstractFileFilter::FileType::ROOT) {
  489         const QStringList& names = m_rootOptionsWidget->selectedNames();
  490         if (names.size())
  491             name += QLatin1Char('/') + names.first();
  492     }
  493 
  494     return name;
  495 }
  496 
  497 /*!
  498  * returns \c true if the number of lines to be imported from the currently selected file is zero ("file is empty"),
  499  * returns \c false otherwise.
  500  */
  501 bool ImportFileWidget::isFileEmpty() const {
  502     return m_fileEmpty;
  503 }
  504 
  505 QString ImportFileWidget::host() const {
  506     return ui.leHost->text();
  507 }
  508 
  509 QString ImportFileWidget::port() const {
  510     return ui.lePort->text();
  511 }
  512 
  513 QString ImportFileWidget::serialPort() const {
  514     return ui.cbSerialPort->currentText();
  515 }
  516 
  517 int ImportFileWidget::baudRate() const {
  518     return ui.cbBaudRate->currentText().toInt();
  519 }
  520 
  521 /*!
  522     saves the settings to the data source \c source.
  523 */
  524 void ImportFileWidget::saveSettings(LiveDataSource* source) const {
  525     AbstractFileFilter::FileType fileType = currentFileType();
  526     auto updateType = static_cast<LiveDataSource::UpdateType>(ui.cbUpdateType->currentIndex());
  527     LiveDataSource::SourceType sourceType = currentSourceType();
  528     auto readingType = static_cast<LiveDataSource::ReadingType>(ui.cbReadingType->currentIndex());
  529 
  530     source->setFileType(fileType);
  531     currentFileFilter();
  532     source->setFilter(m_currentFilter.release()); // pass ownership of the filter to the LiveDataSource
  533 
  534     source->setSourceType(sourceType);
  535 
  536     switch (sourceType) {
  537     case LiveDataSource::SourceType::FileOrPipe:
  538         source->setFileName(fileName());
  539         source->setFileLinked(ui.chbLinkFile->isChecked());
  540         source->setComment(fileName());
  541         if (m_liveDataSource)
  542             source->setUseRelativePath(ui.chbRelativePath->isChecked());
  543         break;
  544     case LiveDataSource::SourceType::LocalSocket:
  545         source->setFileName(fileName());
  546         source->setLocalSocketName(fileName());
  547         source->setComment(fileName());
  548         break;
  549     case LiveDataSource::SourceType::NetworkTcpSocket:
  550     case LiveDataSource::SourceType::NetworkUdpSocket:
  551         source->setHost(ui.leHost->text());
  552         source->setPort((quint16)ui.lePort->text().toInt());
  553         break;
  554     case LiveDataSource::SourceType::SerialPort:
  555         source->setBaudRate(ui.cbBaudRate->currentText().toInt());
  556         source->setSerialPort(ui.cbSerialPort->currentText());
  557         break;
  558     case LiveDataSource::SourceType::MQTT:
  559         break;
  560     default:
  561         break;
  562     }
  563 
  564     //reading options
  565     source->setReadingType(readingType);
  566     source->setKeepNValues(ui.sbKeepNValues->value());
  567     source->setUpdateType(updateType);
  568     if (updateType == LiveDataSource::UpdateType::TimeInterval)
  569         source->setUpdateInterval(ui.sbUpdateInterval->value());
  570 
  571     if (readingType != LiveDataSource::ReadingType::TillEnd)
  572         source->setSampleSize(ui.sbSampleSize->value());
  573 }
  574 
  575 /*!
  576     returns the currently used file type.
  577 */
  578 AbstractFileFilter::FileType ImportFileWidget::currentFileType() const {
  579     return static_cast<AbstractFileFilter::FileType>(ui.cbFileType->currentData().toInt());
  580 }
  581 
  582 LiveDataSource::SourceType ImportFileWidget::currentSourceType() const {
  583     return static_cast<LiveDataSource::SourceType>(ui.cbSourceType->currentIndex());
  584 }
  585 
  586 /*!
  587     returns the currently used filter.
  588 */
  589 AbstractFileFilter* ImportFileWidget::currentFileFilter() const {
  590     DEBUG("ImportFileWidget::currentFileFilter()");
  591     AbstractFileFilter::FileType fileType = currentFileType();
  592     if (m_currentFilter && m_currentFilter->type() != fileType)
  593         m_currentFilter.reset();
  594 
  595     switch (fileType) {
  596     case AbstractFileFilter::FileType::Ascii: {
  597         DEBUG(" ASCII");
  598         if (!m_currentFilter)
  599             m_currentFilter.reset(new AsciiFilter);
  600         auto filter = static_cast<AsciiFilter*>(m_currentFilter.get());
  601 
  602         if (ui.cbFilter->currentIndex() == 0)     //"automatic"
  603             filter->setAutoModeEnabled(true);
  604         else if (ui.cbFilter->currentIndex() == 1) { //"custom"
  605             filter->setAutoModeEnabled(false);
  606             if (m_asciiOptionsWidget)
  607                 m_asciiOptionsWidget->applyFilterSettings(filter);
  608         } else
  609             filter->loadFilterSettings(ui.cbFilter->currentText());
  610 
  611         //save the data portion to import
  612         filter->setStartRow(ui.sbStartRow->value());
  613         filter->setEndRow(ui.sbEndRow->value());
  614         filter->setStartColumn(ui.sbStartColumn->value());
  615         filter->setEndColumn(ui.sbEndColumn->value());
  616 
  617         break;
  618     }
  619     case AbstractFileFilter::FileType::Binary: {
  620         DEBUG(" Binary");
  621         if (!m_currentFilter)
  622             m_currentFilter.reset(new BinaryFilter);
  623         auto filter = static_cast<BinaryFilter*>(m_currentFilter.get());
  624         if ( ui.cbFilter->currentIndex() == 0 )     //"automatic"
  625             filter->setAutoModeEnabled(true);
  626         else if (ui.cbFilter->currentIndex() == 1) {    //"custom"
  627             filter->setAutoModeEnabled(false);
  628             if (m_binaryOptionsWidget)
  629                 m_binaryOptionsWidget->applyFilterSettings(filter);
  630         } else {
  631             //TODO: load filter settings
  632             //          filter->setFilterName( ui.cbFilter->currentText() );
  633         }
  634 
  635         filter->setStartRow(ui.sbStartRow->value());
  636         filter->setEndRow(ui.sbEndRow->value());
  637 
  638         break;
  639     }
  640     case AbstractFileFilter::FileType::Image: {
  641         DEBUG(" Image");
  642         if (!m_currentFilter)
  643             m_currentFilter.reset(new ImageFilter);
  644         auto filter = static_cast<ImageFilter*>(m_currentFilter.get());
  645 
  646         filter->setImportFormat(m_imageOptionsWidget->currentFormat());
  647         filter->setStartRow(ui.sbStartRow->value());
  648         filter->setEndRow(ui.sbEndRow->value());
  649         filter->setStartColumn(ui.sbStartColumn->value());
  650         filter->setEndColumn(ui.sbEndColumn->value());
  651 
  652         break;
  653     }
  654     case AbstractFileFilter::FileType::HDF5: {
  655         DEBUG("ImportFileWidget::currentFileFilter(): HDF5");
  656         if (!m_currentFilter)
  657             m_currentFilter.reset(new HDF5Filter);
  658         auto filter = static_cast<HDF5Filter*>(m_currentFilter.get());
  659         QStringList names = selectedHDF5Names();
  660         QDEBUG("ImportFileWidget::currentFileFilter(): selected HDF5 names =" << names);
  661         if (!names.isEmpty())
  662             filter->setCurrentDataSetName(names[0]);
  663         filter->setStartRow(ui.sbStartRow->value());
  664         filter->setEndRow(ui.sbEndRow->value());
  665         filter->setStartColumn(ui.sbStartColumn->value());
  666         filter->setEndColumn(ui.sbEndColumn->value());
  667         DEBUG("ImportFileWidget::currentFileFilter(): OK");
  668 
  669         break;
  670     }
  671     case AbstractFileFilter::FileType::NETCDF: {
  672         DEBUG(" NETCDF");
  673         if (!m_currentFilter)
  674             m_currentFilter.reset(new NetCDFFilter);
  675         auto filter = static_cast<NetCDFFilter*>(m_currentFilter.get());
  676 
  677         if (!selectedNetCDFNames().isEmpty())
  678             filter->setCurrentVarName(selectedNetCDFNames()[0]);
  679         filter->setStartRow(ui.sbStartRow->value());
  680         filter->setEndRow(ui.sbEndRow->value());
  681         filter->setStartColumn(ui.sbStartColumn->value());
  682         filter->setEndColumn(ui.sbEndColumn->value());
  683 
  684         break;
  685     }
  686     case AbstractFileFilter::FileType::FITS: {
  687         DEBUG(" FITS");
  688         if (!m_currentFilter)
  689             m_currentFilter.reset(new FITSFilter);
  690         auto filter = static_cast<FITSFilter*>(m_currentFilter.get());
  691         filter->setStartRow(ui.sbStartRow->value());
  692         filter->setEndRow(ui.sbEndRow->value());
  693         filter->setStartColumn(ui.sbStartColumn->value());
  694         filter->setEndColumn(ui.sbEndColumn->value());
  695 
  696         break;
  697     }
  698     case AbstractFileFilter::FileType::JSON: {
  699         DEBUG(" JSON");
  700         if (!m_currentFilter)
  701             m_currentFilter.reset(new JsonFilter);
  702         auto filter = static_cast<JsonFilter*>(m_currentFilter.get());
  703         m_jsonOptionsWidget->applyFilterSettings(filter, ui.tvJson->currentIndex());
  704 
  705         filter->setStartRow(ui.sbStartRow->value());
  706         filter->setEndRow(ui.sbEndRow->value());
  707         filter->setStartColumn(ui.sbStartColumn->value());
  708         filter->setEndColumn(ui.sbEndColumn->value());
  709 
  710         break;
  711     }
  712     case AbstractFileFilter::FileType::ROOT: {
  713         DEBUG(" ROOT");
  714         if (!m_currentFilter)
  715             m_currentFilter.reset(new ROOTFilter);
  716         auto filter = static_cast<ROOTFilter*>(m_currentFilter.get());
  717         QStringList names = selectedROOTNames();
  718         if (!names.isEmpty())
  719             filter->setCurrentObject(names.first());
  720 
  721         filter->setStartRow(m_rootOptionsWidget->startRow());
  722         filter->setEndRow(m_rootOptionsWidget->endRow());
  723         filter->setColumns(m_rootOptionsWidget->columns());
  724 
  725         break;
  726     }
  727     case AbstractFileFilter::FileType::NgspiceRawAscii: {
  728         DEBUG(" NgspiceRawAscii");
  729         if (!m_currentFilter)
  730             m_currentFilter.reset(new NgspiceRawAsciiFilter);
  731         auto filter = static_cast<NgspiceRawAsciiFilter*>(m_currentFilter.get());
  732         filter->setStartRow(ui.sbStartRow->value());
  733         filter->setEndRow(ui.sbEndRow->value());
  734 
  735         break;
  736     }
  737     case AbstractFileFilter::FileType::NgspiceRawBinary: {
  738         DEBUG(" NgspiceRawBinary");
  739         if (!m_currentFilter)
  740             m_currentFilter.reset(new NgspiceRawBinaryFilter);
  741         auto filter = static_cast<NgspiceRawBinaryFilter*>(m_currentFilter.get());
  742         filter->setStartRow(ui.sbStartRow->value());
  743         filter->setEndRow(ui.sbEndRow->value());
  744 
  745         break;
  746     }
  747     }
  748 
  749     return m_currentFilter.get();
  750 }
  751 
  752 /*!
  753     opens a file dialog and lets the user select the file data source.
  754 */
  755 void ImportFileWidget::selectFile() {
  756     DEBUG("ImportFileWidget::selectFile()")
  757     KConfigGroup conf(KSharedConfig::openConfig(), QLatin1String("ImportFileWidget"));
  758     const QString& dir = conf.readEntry(QLatin1String("LastDir"), "");
  759     const QString& path = QFileDialog::getOpenFileName(this, i18n("Select the File Data Source"), dir);
  760     DEBUG(" dir = " << STDSTRING(dir))
  761     DEBUG(" path = " << STDSTRING(path))
  762     if (path.isEmpty()) //cancel was clicked in the file-dialog
  763         return;
  764 
  765     int pos = path.lastIndexOf('/');
  766     if (pos != -1) {
  767         QString newDir = path.left(pos);
  768         if (newDir != dir)
  769             conf.writeEntry(QLatin1String("LastDir"), newDir);
  770     }
  771 
  772     //process all events after the FileDialog was closed to repaint the widget
  773     //before we start calculating the preview
  774     QApplication::processEvents(QEventLoop::AllEvents, 0);
  775 
  776     QStringList urls = m_cbFileName->urls();
  777     urls.insert(0, QUrl::fromLocalFile(path).url()); // add type of path
  778     m_cbFileName->setUrls(urls);
  779     m_cbFileName->setCurrentText(urls.first());
  780     DEBUG(" combobox text = " << STDSTRING(m_cbFileName->currentText()))
  781     fileNameChanged(path); // why do I have to call this function separately
  782 }
  783 
  784 /*!
  785     hides the MQTT related items of the widget
  786 */
  787 void ImportFileWidget::setMQTTVisible(bool visible) {
  788     ui.lConnections->setVisible(visible);
  789     ui.cbConnection->setVisible(visible);
  790     ui.bManageConnections->setVisible(visible);
  791 
  792     //topics
  793     if (ui.cbConnection->currentIndex() != -1 && visible) {
  794         ui.lTopics->setVisible(true);
  795         ui.frameSubscriptions->setVisible(true);
  796 #ifdef HAVE_MQTT
  797         m_subscriptionWidget->setVisible(true);
  798         m_subscriptionWidget->makeVisible(true);
  799 #endif
  800     } else {
  801         ui.lTopics->setVisible(false);
  802         ui.frameSubscriptions->setVisible(false);
  803 #ifdef HAVE_MQTT
  804         m_subscriptionWidget->setVisible(false);
  805         m_subscriptionWidget->makeVisible(false);
  806 #endif
  807     }
  808 
  809     //will message
  810     ui.lLWT->setVisible(visible);
  811     ui.bLWT->setVisible(visible);
  812 }
  813 
  814 /************** SLOTS **************************************************************/
  815 /*!
  816     called on file name changes.
  817     Determines the file format (ASCII, binary etc.), if the file exists,
  818     and activates the corresponding options.
  819 */
  820 void ImportFileWidget::fileNameChanged(const QString& name) {
  821     DEBUG("ImportFileWidget::fileNameChanged() : " << STDSTRING(name))
  822     const QString fileName = absolutePath(name);
  823 
  824     bool fileExists = QFile::exists(fileName);
  825     ui.gbOptions->setEnabled(fileExists);
  826     ui.bManageFilters->setEnabled(fileExists);
  827     ui.cbFilter->setEnabled(fileExists);
  828     ui.cbFileType->setEnabled(fileExists);
  829     ui.bFileInfo->setEnabled(fileExists);
  830     ui.gbUpdateOptions->setEnabled(fileExists);
  831     if (!fileExists) {
  832         //file doesn't exist -> delete the content preview that is still potentially
  833         //available from the previously selected file
  834         ui.tePreview->clear();
  835         m_twPreview->clear();
  836         initOptionsWidget();
  837 
  838         emit fileNameChanged();
  839         return;
  840     }
  841 
  842     if (currentSourceType() == LiveDataSource::SourceType::FileOrPipe) {
  843         const AbstractFileFilter::FileType fileType = AbstractFileFilter::fileType(fileName);
  844         for (int i = 0; i < ui.cbFileType->count(); ++i) {
  845             if (static_cast<AbstractFileFilter::FileType>(ui.cbFileType->itemData(i).toInt()) == fileType) {
  846                 // automatically select a new file type
  847                 if (ui.cbFileType->currentIndex() != i) {
  848                     ui.cbFileType->setCurrentIndex(i); // will call the slot fileTypeChanged which updates content and preview
  849 
  850                     //automatically set the comma separator if a csv file was selected
  851                     if (fileType == AbstractFileFilter::FileType::Ascii && name.endsWith(QLatin1String("csv"), Qt::CaseInsensitive))
  852                         m_asciiOptionsWidget->setSeparatingCharacter(QLatin1Char(','));
  853 
  854                     emit fileNameChanged();
  855                     return;
  856                 } else {
  857                     initOptionsWidget();
  858 
  859                     //automatically set the comma separator if a csv file was selected
  860                     if (fileType == AbstractFileFilter::FileType::Ascii && name.endsWith(QLatin1String("csv"), Qt::CaseInsensitive))
  861                         m_asciiOptionsWidget->setSeparatingCharacter(QLatin1Char(','));
  862 
  863                     updateContent(fileName);
  864                     break;
  865                 }
  866             }
  867         }
  868     }
  869 
  870     emit fileNameChanged();
  871     refreshPreview();
  872 }
  873 
  874 /*!
  875   saves the current filter settings
  876 */
  877 void ImportFileWidget::saveFilter() {
  878     bool ok;
  879     QString text = QInputDialog::getText(this, i18n("Save Filter Settings as"),
  880                                          i18n("Filter name:"), QLineEdit::Normal, i18n("new filter"), &ok);
  881     if (ok && !text.isEmpty()) {
  882         //TODO
  883         //AsciiFilter::saveFilter()
  884     }
  885 }
  886 
  887 /*!
  888   opens a dialog for managing all available predefined filters.
  889 */
  890 void ImportFileWidget::manageFilters() {
  891     //TODO
  892 }
  893 
  894 /*!
  895     Depending on the selected file type, activates the corresponding options in the data portion tab
  896     and populates the combobox with the available pre-defined filter settings for the selected type.
  897 */
  898 void ImportFileWidget::fileTypeChanged(int index) {
  899     Q_UNUSED(index);
  900     AbstractFileFilter::FileType fileType = currentFileType();
  901     DEBUG("ImportFileWidget::fileTypeChanged " << ENUM_TO_STRING(AbstractFileFilter, FileType, fileType));
  902     initOptionsWidget();
  903 
  904     //default
  905     ui.lFilter->show();
  906     ui.cbFilter->show();
  907 
  908     //different file types show different number of tabs in ui.tabWidget.
  909     //when switching from the previous file type we re-set the tab widget to its original state
  910     //and remove/add the required tabs further below
  911     for (int i = 0; i<ui.tabWidget->count(); ++i)
  912         ui.tabWidget->removeTab(0);
  913 
  914     ui.tabWidget->addTab(ui.tabDataFormat, i18n("Data format"));
  915     ui.tabWidget->addTab(ui.tabDataPreview, i18n("Preview"));
  916     if (!m_liveDataSource)
  917         ui.tabWidget->addTab(ui.tabDataPortion, i18n("Data portion to read"));
  918 
  919     ui.lPreviewLines->show();
  920     ui.sbPreviewLines->show();
  921     ui.lStartColumn->show();
  922     ui.sbStartColumn->show();
  923     ui.lEndColumn->show();
  924     ui.sbEndColumn->show();
  925 
  926     showJsonModel(false);
  927 
  928     switch (fileType) {
  929     case AbstractFileFilter::FileType::Ascii:
  930         break;
  931     case AbstractFileFilter::FileType::Binary:
  932         ui.lStartColumn->hide();
  933         ui.sbStartColumn->hide();
  934         ui.lEndColumn->hide();
  935         ui.sbEndColumn->hide();
  936         break;
  937     case AbstractFileFilter::FileType::ROOT:
  938         ui.tabWidget->removeTab(1);
  939     // falls through
  940     case AbstractFileFilter::FileType::HDF5:
  941     case AbstractFileFilter::FileType::NETCDF:
  942     case AbstractFileFilter::FileType::FITS:
  943         ui.lFilter->hide();
  944         ui.cbFilter->hide();
  945         // hide global preview tab. we have our own
  946         ui.tabWidget->setTabText(0, i18n("Data format && preview"));
  947         ui.tabWidget->removeTab(1);
  948         ui.tabWidget->setCurrentIndex(0);
  949         break;
  950     case AbstractFileFilter::FileType::Image:
  951         ui.lFilter->hide();
  952         ui.cbFilter->hide();
  953         ui.lPreviewLines->hide();
  954         ui.sbPreviewLines->hide();
  955         break;
  956     case AbstractFileFilter::FileType::NgspiceRawAscii:
  957     case AbstractFileFilter::FileType::NgspiceRawBinary:
  958         ui.lFilter->hide();
  959         ui.cbFilter->hide();
  960         ui.lStartColumn->hide();
  961         ui.sbStartColumn->hide();
  962         ui.lEndColumn->hide();
  963         ui.sbEndColumn->hide();
  964         ui.tabWidget->removeTab(0);
  965         ui.tabWidget->setCurrentIndex(0);
  966         break;
  967     case AbstractFileFilter::FileType::JSON:
  968         ui.lFilter->hide();
  969         ui.cbFilter->hide();
  970         showJsonModel(true);
  971         break;
  972     default:
  973         DEBUG("unknown file type");
  974     }
  975 
  976     int lastUsedFilterIndex = ui.cbFilter->currentIndex();
  977     ui.cbFilter->clear();
  978     ui.cbFilter->addItem( i18n("Automatic") );
  979     ui.cbFilter->addItem( i18n("Custom") );
  980 
  981     //TODO: populate the combobox with the available pre-defined filter settings for the selected type
  982     ui.cbFilter->setCurrentIndex(lastUsedFilterIndex);
  983     filterChanged(lastUsedFilterIndex);
  984 
  985     if (currentSourceType() == LiveDataSource::SourceType::FileOrPipe) {
  986         QString tempFileName = fileName();
  987         const QString& fileName = absolutePath(tempFileName);
  988         if (QFile::exists(fileName))
  989             updateContent(fileName);
  990     }
  991 
  992     //for file types other than ASCII and binary we support re-reading the whole file only
  993     //select "read whole file" and deactivate the combobox
  994     if (m_liveDataSource && (fileType != AbstractFileFilter::FileType::Ascii && fileType != AbstractFileFilter::FileType::Binary)) {
  995         ui.cbReadingType->setCurrentIndex(static_cast<int>(LiveDataSource::ReadingType::WholeFile));
  996         ui.cbReadingType->setEnabled(false);
  997     } else
  998         ui.cbReadingType->setEnabled(true);
  999 
 1000     refreshPreview();
 1001 }
 1002 
 1003 // file type specific option widgets
 1004 void ImportFileWidget::initOptionsWidget() {
 1005     DEBUG("ImportFileWidget::initOptionsWidget for " << ENUM_TO_STRING(AbstractFileFilter, FileType, currentFileType()));
 1006     switch (currentFileType()) {
 1007     case AbstractFileFilter::FileType::Ascii: {
 1008         if (!m_asciiOptionsWidget) {
 1009             QWidget* asciiw = new QWidget();
 1010             m_asciiOptionsWidget = std::unique_ptr<AsciiOptionsWidget>(new AsciiOptionsWidget(asciiw));
 1011             m_asciiOptionsWidget->loadSettings();
 1012 
 1013             //allow to add timestamp column for live data sources
 1014             if (m_liveDataSource)
 1015                 m_asciiOptionsWidget->showTimestampOptions(true);
 1016             ui.swOptions->addWidget(asciiw);
 1017         }
 1018 
 1019         ui.swOptions->setCurrentWidget(m_asciiOptionsWidget->parentWidget());
 1020         break;
 1021     }
 1022     case AbstractFileFilter::FileType::Binary:
 1023         if (!m_binaryOptionsWidget) {
 1024             QWidget* binaryw = new QWidget();
 1025             m_binaryOptionsWidget = std::unique_ptr<BinaryOptionsWidget>(new BinaryOptionsWidget(binaryw));
 1026             ui.swOptions->addWidget(binaryw);
 1027             m_binaryOptionsWidget->loadSettings();
 1028         }
 1029         ui.swOptions->setCurrentWidget(m_binaryOptionsWidget->parentWidget());
 1030         break;
 1031     case AbstractFileFilter::FileType::Image:
 1032         if (!m_imageOptionsWidget) {
 1033             QWidget* imagew = new QWidget();
 1034             m_imageOptionsWidget = std::unique_ptr<ImageOptionsWidget>(new ImageOptionsWidget(imagew));
 1035             ui.swOptions->addWidget(imagew);
 1036             m_imageOptionsWidget->loadSettings();
 1037         }
 1038         ui.swOptions->setCurrentWidget(m_imageOptionsWidget->parentWidget());
 1039         break;
 1040     case AbstractFileFilter::FileType::HDF5:
 1041         if (!m_hdf5OptionsWidget) {
 1042             QWidget* hdf5w = new QWidget();
 1043             m_hdf5OptionsWidget = std::unique_ptr<HDF5OptionsWidget>(new HDF5OptionsWidget(hdf5w, this));
 1044             ui.swOptions->addWidget(hdf5w);
 1045         } else
 1046             m_hdf5OptionsWidget->clear();
 1047         ui.swOptions->setCurrentWidget(m_hdf5OptionsWidget->parentWidget());
 1048         break;
 1049     case AbstractFileFilter::FileType::NETCDF:
 1050         if (!m_netcdfOptionsWidget) {
 1051             QWidget* netcdfw = new QWidget();
 1052             m_netcdfOptionsWidget = std::unique_ptr<NetCDFOptionsWidget>(new NetCDFOptionsWidget(netcdfw, this));
 1053             ui.swOptions->insertWidget(static_cast<int>(AbstractFileFilter::FileType::NETCDF), netcdfw);
 1054         } else
 1055             m_netcdfOptionsWidget->clear();
 1056         ui.swOptions->setCurrentWidget(m_netcdfOptionsWidget->parentWidget());
 1057         break;
 1058     case AbstractFileFilter::FileType::FITS:
 1059         if (!m_fitsOptionsWidget) {
 1060             QWidget* fitsw = new QWidget();
 1061             m_fitsOptionsWidget = std::unique_ptr<FITSOptionsWidget>(new FITSOptionsWidget(fitsw, this));
 1062             ui.swOptions->addWidget(fitsw);
 1063         } else
 1064             m_fitsOptionsWidget->clear();
 1065         ui.swOptions->setCurrentWidget(m_fitsOptionsWidget->parentWidget());
 1066         break;
 1067     case AbstractFileFilter::FileType::JSON:
 1068         if (!m_jsonOptionsWidget) {
 1069             QWidget* jsonw = new QWidget();
 1070             m_jsonOptionsWidget = std::unique_ptr<JsonOptionsWidget>(new JsonOptionsWidget(jsonw, this));
 1071             ui.tvJson->setModel(m_jsonOptionsWidget->model());
 1072             ui.swOptions->addWidget(jsonw);
 1073             m_jsonOptionsWidget->loadSettings();
 1074         } else
 1075             m_jsonOptionsWidget->clearModel();
 1076         ui.swOptions->setCurrentWidget(m_jsonOptionsWidget->parentWidget());
 1077         showJsonModel(true);
 1078         break;
 1079     case AbstractFileFilter::FileType::ROOT:
 1080         if (!m_rootOptionsWidget) {
 1081             QWidget* rootw = new QWidget();
 1082             m_rootOptionsWidget = std::unique_ptr<ROOTOptionsWidget>(new ROOTOptionsWidget(rootw, this));
 1083             ui.swOptions->addWidget(rootw);
 1084         } else
 1085             m_rootOptionsWidget->clear();
 1086         ui.swOptions->setCurrentWidget(m_rootOptionsWidget->parentWidget());
 1087         break;
 1088     case AbstractFileFilter::FileType::NgspiceRawAscii:
 1089     case AbstractFileFilter::FileType::NgspiceRawBinary:
 1090         break;
 1091     }
 1092 }
 1093 
 1094 const QStringList ImportFileWidget::selectedHDF5Names() const {
 1095     return m_hdf5OptionsWidget->selectedNames();
 1096 }
 1097 
 1098 const QStringList ImportFileWidget::selectedNetCDFNames() const {
 1099     return m_netcdfOptionsWidget->selectedNames();
 1100 }
 1101 
 1102 const QStringList ImportFileWidget::selectedFITSExtensions() const {
 1103     return m_fitsOptionsWidget->selectedExtensions();
 1104 }
 1105 
 1106 const QStringList ImportFileWidget::selectedROOTNames() const {
 1107     return m_rootOptionsWidget->selectedNames();
 1108 }
 1109 
 1110 /*!
 1111     shows the dialog with the information about the file(s) to be imported.
 1112 */
 1113 void ImportFileWidget::fileInfoDialog() {
 1114     QStringList files = fileName().split(';');
 1115     auto* dlg = new FileInfoDialog(this);
 1116     dlg->setFiles(files);
 1117     dlg->exec();
 1118 }
 1119 
 1120 /*!
 1121     enables the options if the filter "custom" was chosen. Disables the options otherwise.
 1122 */
 1123 void ImportFileWidget::filterChanged(int index) {
 1124     // ignore filter for these formats
 1125     AbstractFileFilter::FileType fileType = currentFileType();
 1126     if (fileType != AbstractFileFilter::FileType::Ascii && fileType != AbstractFileFilter::FileType::Binary) {
 1127         ui.swOptions->setEnabled(true);
 1128         return;
 1129     }
 1130 
 1131     if (index == 0) { // "automatic"
 1132         ui.swOptions->setEnabled(false);
 1133         ui.bSaveFilter->setEnabled(false);
 1134     } else if (index == 1) { //custom
 1135         ui.swOptions->setEnabled(true);
 1136         ui.bSaveFilter->setEnabled(true);
 1137     } else {
 1138         // predefined filter settings were selected.
 1139         //load and show them in the GUI.
 1140         //TODO
 1141     }
 1142 }
 1143 
 1144 void ImportFileWidget::refreshPreview() {
 1145     //don't generate any preview if it was explicitly suppressed
 1146     //or if the options box together with the preview widget is not visible
 1147     if (m_suppressRefresh || !ui.gbOptions->isVisible())
 1148         return;
 1149 
 1150     DEBUG("ImportFileWidget::refreshPreview()");
 1151     WAIT_CURSOR;
 1152 
 1153     QString tempFileName = fileName();
 1154     QString fileName = absolutePath(tempFileName);
 1155     AbstractFileFilter::FileType fileType = currentFileType();
 1156     LiveDataSource::SourceType sourceType = currentSourceType();
 1157     int lines = ui.sbPreviewLines->value();
 1158 
 1159     if (sourceType == LiveDataSource::SourceType::FileOrPipe)
 1160         DEBUG(" file name = " << STDSTRING(fileName));
 1161 
 1162     // generic table widget
 1163     if (fileType == AbstractFileFilter::FileType::Ascii || fileType == AbstractFileFilter::FileType::Binary
 1164             || fileType == AbstractFileFilter::FileType::JSON || fileType == AbstractFileFilter::FileType::NgspiceRawAscii
 1165             || fileType == AbstractFileFilter::FileType::NgspiceRawBinary)
 1166         m_twPreview->show();
 1167     else
 1168         m_twPreview->hide();
 1169 
 1170     bool ok = true;
 1171     QTableWidget* tmpTableWidget = m_twPreview;
 1172     QVector<QStringList> importedStrings;
 1173     QStringList vectorNameList;
 1174     QVector<AbstractColumn::ColumnMode> columnModes;
 1175     DEBUG("Data File Type: " << ENUM_TO_STRING(AbstractFileFilter, FileType, fileType));
 1176     switch (fileType) {
 1177     case AbstractFileFilter::FileType::Ascii: {
 1178         ui.tePreview->clear();
 1179 
 1180         auto filter = static_cast<AsciiFilter*>(currentFileFilter());
 1181 
 1182         DEBUG("Data Source Type: " << ENUM_TO_STRING(LiveDataSource, SourceType, sourceType));
 1183         switch (sourceType) {
 1184         case LiveDataSource::SourceType::FileOrPipe: {
 1185             importedStrings = filter->preview(fileName, lines);
 1186             break;
 1187         }
 1188         case LiveDataSource::SourceType::LocalSocket: {
 1189             QLocalSocket lsocket{this};
 1190             DEBUG("Local socket: CONNECT PREVIEW");
 1191             lsocket.connectToServer(fileName, QLocalSocket::ReadOnly);
 1192             if (lsocket.waitForConnected()) {
 1193                 DEBUG("connected to local socket " << STDSTRING(fileName));
 1194                 if (lsocket.waitForReadyRead())
 1195                     importedStrings = filter->preview(lsocket);
 1196                 DEBUG("Local socket: DISCONNECT PREVIEW");
 1197                 lsocket.disconnectFromServer();
 1198                 // read-only socket is disconnected immediately (no waitForDisconnected())
 1199             } else
 1200                 DEBUG("failed connect to local socket " << STDSTRING(fileName) << " - " << STDSTRING(lsocket.errorString()));
 1201 
 1202             break;
 1203         }
 1204         case LiveDataSource::SourceType::NetworkTcpSocket: {
 1205             QTcpSocket tcpSocket{this};
 1206             tcpSocket.connectToHost(host(), port().toInt(), QTcpSocket::ReadOnly);
 1207             if (tcpSocket.waitForConnected()) {
 1208                 DEBUG("connected to TCP socket");
 1209                 if ( tcpSocket.waitForReadyRead() )
 1210                     importedStrings = filter->preview(tcpSocket);
 1211 
 1212                 tcpSocket.disconnectFromHost();
 1213             } else
 1214                 DEBUG("failed to connect to TCP socket " << " - " << STDSTRING(tcpSocket.errorString()));
 1215 
 1216             break;
 1217         }
 1218         case LiveDataSource::SourceType::NetworkUdpSocket: {
 1219             QUdpSocket udpSocket{this};
 1220             DEBUG("UDP Socket: CONNECT PREVIEW, state = " << udpSocket.state());
 1221             udpSocket.bind(QHostAddress(host()), port().toInt());
 1222             udpSocket.connectToHost(host(), 0, QUdpSocket::ReadOnly);
 1223             if (udpSocket.waitForConnected()) {
 1224                 DEBUG(" connected to UDP socket " << STDSTRING(host()) << ':' << port().toInt());
 1225                 if (!udpSocket.waitForReadyRead(2000) )
 1226                     DEBUG(" ERROR: not ready for read after 2 sec");
 1227                 if (udpSocket.hasPendingDatagrams()) {
 1228                     DEBUG(" has pending data");
 1229                 } else {
 1230                     DEBUG(" has no pending data");
 1231                 }
 1232                 importedStrings = filter->preview(udpSocket);
 1233 
 1234                 DEBUG("UDP Socket: DISCONNECT PREVIEW, state = " << udpSocket.state());
 1235                 udpSocket.disconnectFromHost();
 1236             } else
 1237                 DEBUG("failed to connect to UDP socket " << " - " << STDSTRING(udpSocket.errorString()));
 1238 
 1239             break;
 1240         }
 1241         case LiveDataSource::SourceType::SerialPort: {
 1242 #ifdef HAVE_QTSERIALPORT
 1243             QSerialPort sPort{this};
 1244             DEBUG(" Port: " << STDSTRING(serialPort()) << ", Settings: " << baudRate() << ',' << sPort.dataBits()
 1245                   << ',' << sPort.parity() << ',' << sPort.stopBits());
 1246             sPort.setPortName(serialPort());
 1247             sPort.setBaudRate(baudRate());
 1248 
 1249             if (sPort.open(QIODevice::ReadOnly)) {
 1250                 if (sPort.waitForReadyRead(2000))
 1251                     importedStrings = filter->preview(sPort);
 1252                 else
 1253                     DEBUG(" ERROR: not ready for read after 2 sec");
 1254 
 1255                 sPort.close();
 1256             } else
 1257                 DEBUG(" ERROR: failed to open serial port. error: " << sPort.error());
 1258 #endif
 1259             break;
 1260         }
 1261         case LiveDataSource::SourceType::MQTT: {
 1262 #ifdef HAVE_MQTT
 1263             //show the preview for the currently selected topic
 1264             auto* item = m_subscriptionWidget->currentItem();
 1265             if (item && item->childCount() == 0) { //only preview if the lowest level (i.e. a topic) is selected
 1266                 const QString& topicName = item->text(0);
 1267                 auto i = m_lastMessage.find(topicName);
 1268                 if (i != m_lastMessage.end())
 1269                     importedStrings = filter->preview(i.value().payload().data());
 1270                 else
 1271                     importedStrings << QStringList{i18n("No data arrived yet for the selected topic")};
 1272             }
 1273 #endif
 1274             break;
 1275         }
 1276         }
 1277 
 1278         vectorNameList = filter->vectorNames();
 1279         columnModes = filter->columnModes();
 1280         break;
 1281     }
 1282     case AbstractFileFilter::FileType::Binary: {
 1283         ui.tePreview->clear();
 1284         auto filter = static_cast<BinaryFilter*>(currentFileFilter());
 1285         importedStrings = filter->preview(fileName, lines);
 1286         break;
 1287     }
 1288     case AbstractFileFilter::FileType::Image: {
 1289         ui.tePreview->clear();
 1290 
 1291         QImage image(fileName);
 1292         QTextCursor cursor = ui.tePreview->textCursor();
 1293         cursor.insertImage(image);
 1294         RESET_CURSOR;
 1295         return;
 1296     }
 1297     case AbstractFileFilter::FileType::HDF5: {
 1298         DEBUG("ImportFileWidget::refreshPreview: HDF5");
 1299         auto filter = static_cast<HDF5Filter*>(currentFileFilter());
 1300         lines = m_hdf5OptionsWidget->lines();
 1301 
 1302         importedStrings = filter->readCurrentDataSet(fileName, nullptr, ok, AbstractFileFilter::ImportMode::Replace, lines);
 1303         tmpTableWidget = m_hdf5OptionsWidget->previewWidget();
 1304         break;
 1305     }
 1306     case AbstractFileFilter::FileType::NETCDF: {
 1307         auto filter = static_cast<NetCDFFilter*>(currentFileFilter());
 1308         lines = m_netcdfOptionsWidget->lines();
 1309 
 1310         importedStrings = filter->readCurrentVar(fileName, nullptr, AbstractFileFilter::ImportMode::Replace, lines);
 1311         tmpTableWidget = m_netcdfOptionsWidget->previewWidget();
 1312         break;
 1313     }
 1314     case AbstractFileFilter::FileType::FITS: {
 1315         auto filter = static_cast<FITSFilter*>(currentFileFilter());
 1316         lines = m_fitsOptionsWidget->lines();
 1317 
 1318         QString extensionName = m_fitsOptionsWidget->extensionName(&ok);
 1319         if (!extensionName.isEmpty()) {
 1320             DEBUG(" extension name = " << STDSTRING(extensionName));
 1321             fileName = extensionName;
 1322         }
 1323 
 1324         bool readFitsTableToMatrix;
 1325         importedStrings = filter->readChdu(fileName, &readFitsTableToMatrix, lines);
 1326         emit checkedFitsTableToMatrix(readFitsTableToMatrix);
 1327 
 1328         tmpTableWidget = m_fitsOptionsWidget->previewWidget();
 1329         break;
 1330     }
 1331     case AbstractFileFilter::FileType::JSON: {
 1332         ui.tePreview->clear();
 1333         auto filter = static_cast<JsonFilter*>(currentFileFilter());
 1334         m_jsonOptionsWidget->applyFilterSettings(filter, ui.tvJson->currentIndex());
 1335         importedStrings = filter->preview(fileName, lines);
 1336 
 1337         vectorNameList = filter->vectorNames();
 1338         columnModes = filter->columnModes();
 1339         break;
 1340     }
 1341     case AbstractFileFilter::FileType::ROOT: {
 1342         auto filter = static_cast<ROOTFilter*>(currentFileFilter());
 1343         lines = m_rootOptionsWidget->lines();
 1344         m_rootOptionsWidget->setNRows(filter->rowsInCurrentObject(fileName));
 1345         importedStrings = filter->previewCurrentObject(
 1346                               fileName,
 1347                               m_rootOptionsWidget->startRow(),
 1348                               qMin(m_rootOptionsWidget->startRow() + lines - 1,
 1349                                    m_rootOptionsWidget->endRow())
 1350                           );
 1351         tmpTableWidget = m_rootOptionsWidget->previewWidget();
 1352         // the last vector element contains the column names
 1353         vectorNameList = importedStrings.last();
 1354         importedStrings.removeLast();
 1355         columnModes = QVector<AbstractColumn::ColumnMode>(vectorNameList.size(), AbstractColumn::ColumnMode::Numeric);
 1356         break;
 1357     }
 1358     case AbstractFileFilter::FileType::NgspiceRawAscii: {
 1359         ui.tePreview->clear();
 1360         auto filter = static_cast<NgspiceRawAsciiFilter*>(currentFileFilter());
 1361         importedStrings = filter->preview(fileName, lines);
 1362         vectorNameList = filter->vectorNames();
 1363         columnModes = filter->columnModes();
 1364         break;
 1365     }
 1366     case AbstractFileFilter::FileType::NgspiceRawBinary: {
 1367         ui.tePreview->clear();
 1368         auto filter = static_cast<NgspiceRawBinaryFilter*>(currentFileFilter());
 1369         importedStrings = filter->preview(fileName, lines);
 1370         vectorNameList = filter->vectorNames();
 1371         columnModes = filter->columnModes();
 1372         break;
 1373     }
 1374     }
 1375 
 1376     // fill the table widget
 1377     tmpTableWidget->setRowCount(0);
 1378     tmpTableWidget->setColumnCount(0);
 1379     if ( !importedStrings.isEmpty() ) {
 1380         if (!ok) {
 1381             // show imported strings as error message
 1382             tmpTableWidget->setRowCount(1);
 1383             tmpTableWidget->setColumnCount(1);
 1384             auto* item = new QTableWidgetItem();
 1385             item->setText(importedStrings[0][0]);
 1386             tmpTableWidget->setItem(0, 0, item);
 1387         } else {
 1388             //TODO: maxrows not used
 1389             const int rows = qMax(importedStrings.size(), 1);
 1390             const int maxColumns = 300;
 1391             tmpTableWidget->setRowCount(rows);
 1392 
 1393             for (int i = 0; i < rows; ++i) {
 1394                 const int cols = importedStrings[i].size() > maxColumns ? maxColumns : importedStrings[i].size();
 1395                 if (cols > tmpTableWidget->columnCount())
 1396                     tmpTableWidget->setColumnCount(cols);
 1397 
 1398                 for (int j = 0; j < cols; ++j) {
 1399                     auto* item = new QTableWidgetItem(importedStrings[i][j]);
 1400                     tmpTableWidget->setItem(i, j, item);
 1401                 }
 1402             }
 1403 
 1404             // set header if columnMode available
 1405             for (int i = 0; i < qMin(tmpTableWidget->columnCount(), columnModes.size()); ++i) {
 1406                 QString columnName = QString::number(i+1);
 1407                 if (i < vectorNameList.size())
 1408                     columnName = vectorNameList[i];
 1409 
 1410                 auto* item = new QTableWidgetItem(columnName + QLatin1String(" {") + ENUM_TO_STRING(AbstractColumn, ColumnMode, columnModes[i]) + QLatin1String("}"));
 1411                 item->setTextAlignment(Qt::AlignLeft);
 1412                 item->setIcon(AbstractColumn::iconForMode(columnModes[i]));
 1413 
 1414                 tmpTableWidget->setHorizontalHeaderItem(i, item);
 1415             }
 1416         }
 1417 
 1418         tmpTableWidget->horizontalHeader()->resizeSections(QHeaderView::ResizeToContents);
 1419         m_fileEmpty = false;
 1420     } else
 1421         m_fileEmpty = true;
 1422 
 1423     RESET_CURSOR;
 1424 }
 1425 
 1426 void ImportFileWidget::updateContent(const QString& fileName) {
 1427     if (m_suppressRefresh)
 1428         return;
 1429 
 1430     QApplication::processEvents(QEventLoop::AllEvents, 0);
 1431     WAIT_CURSOR;
 1432 
 1433     QDEBUG("ImportFileWidget::updateContent(): file name = " << fileName);
 1434     if (auto filter = currentFileFilter()) {
 1435         switch (filter->type()) {
 1436         case AbstractFileFilter::FileType::HDF5:
 1437             m_hdf5OptionsWidget->updateContent(static_cast<HDF5Filter*>(filter), fileName);
 1438             break;
 1439         case AbstractFileFilter::FileType::NETCDF:
 1440             m_netcdfOptionsWidget->updateContent(static_cast<NetCDFFilter*>(filter), fileName);
 1441             break;
 1442         case AbstractFileFilter::FileType::FITS:
 1443 #ifdef HAVE_FITS
 1444             m_fitsOptionsWidget->updateContent(static_cast<FITSFilter*>(filter), fileName);
 1445 #endif
 1446             break;
 1447         case AbstractFileFilter::FileType::ROOT:
 1448             m_rootOptionsWidget->updateContent(static_cast<ROOTFilter*>(filter), fileName);
 1449             break;
 1450         case AbstractFileFilter::FileType::JSON:
 1451             m_jsonOptionsWidget->loadDocument(fileName);
 1452             ui.tvJson->setExpanded( m_jsonOptionsWidget->model()->index(0, 0), true); //expand the root node
 1453             break;
 1454         case AbstractFileFilter::FileType::Ascii:
 1455         case AbstractFileFilter::FileType::Binary:
 1456         case AbstractFileFilter::FileType::Image:
 1457         case AbstractFileFilter::FileType::NgspiceRawAscii:
 1458         case AbstractFileFilter::FileType::NgspiceRawBinary:
 1459             break;
 1460         }
 1461     }
 1462     RESET_CURSOR;
 1463 }
 1464 
 1465 void ImportFileWidget::updateTypeChanged(int idx) {
 1466     const auto UpdateType = static_cast<LiveDataSource::UpdateType>(idx);
 1467 
 1468     switch (UpdateType) {
 1469     case LiveDataSource::UpdateType::TimeInterval:
 1470         ui.lUpdateInterval->show();
 1471         ui.sbUpdateInterval->show();
 1472         break;
 1473     case LiveDataSource::UpdateType::NewData:
 1474         ui.lUpdateInterval->hide();
 1475         ui.sbUpdateInterval->hide();
 1476     }
 1477 }
 1478 
 1479 void ImportFileWidget::readingTypeChanged(int idx) {
 1480     const auto readingType = static_cast<LiveDataSource::ReadingType>(idx);
 1481     const LiveDataSource::SourceType sourceType = currentSourceType();
 1482 
 1483     if (sourceType == LiveDataSource::SourceType::NetworkTcpSocket || sourceType == LiveDataSource::SourceType::LocalSocket
 1484             || sourceType == LiveDataSource::SourceType::SerialPort
 1485             || readingType == LiveDataSource::ReadingType::TillEnd || readingType == LiveDataSource::ReadingType::WholeFile) {
 1486         ui.lSampleSize->hide();
 1487         ui.sbSampleSize->hide();
 1488     } else {
 1489         ui.lSampleSize->show();
 1490         ui.sbSampleSize->show();
 1491     }
 1492 
 1493     if (readingType == LiveDataSource::ReadingType::WholeFile) {
 1494         ui.lKeepLastValues->hide();
 1495         ui.sbKeepNValues->hide();
 1496     } else {
 1497         ui.lKeepLastValues->show();
 1498         ui.sbKeepNValues->show();
 1499     }
 1500 }
 1501 
 1502 void ImportFileWidget::sourceTypeChanged(int idx) {
 1503     const auto sourceType = static_cast<LiveDataSource::SourceType>(idx);
 1504 
 1505 #ifdef HAVE_MQTT
 1506     //when switching from mqtt to another source type, make sure we disconnect from
 1507     //the current broker, if connected, in order not to get any notification anymore
 1508     if (sourceType != LiveDataSource::SourceType::MQTT)
 1509         disconnectMqttConnection();
 1510 #endif
 1511 
 1512     // enable/disable "on new data"-option
 1513     const auto* model = qobject_cast<const QStandardItemModel*>(ui.cbUpdateType->model());
 1514     QStandardItem* item = model->item(static_cast<int>(LiveDataSource::UpdateType::NewData));
 1515 
 1516     switch (sourceType) {
 1517     case LiveDataSource::SourceType::FileOrPipe:
 1518         ui.lFileName->show();
 1519         m_cbFileName->show();
 1520         ui.bFileInfo->show();
 1521         ui.bOpen->show();
 1522         if (m_liveDataSource) {
 1523             ui.lRelativePath->show();
 1524             ui.chbRelativePath->show();
 1525         }
 1526         ui.chbLinkFile->show();
 1527 
 1528         //option for sample size are available for "continuously fixed" and "from end" reading options
 1529         if (ui.cbReadingType->currentIndex() < 2) {
 1530             ui.lSampleSize->show();
 1531             ui.sbSampleSize->show();
 1532         } else {
 1533             ui.lSampleSize->hide();
 1534             ui.sbSampleSize->hide();
 1535         }
 1536 
 1537         ui.cbBaudRate->hide();
 1538         ui.lBaudRate->hide();
 1539         ui.lHost->hide();
 1540         ui.leHost->hide();
 1541         ui.lPort->hide();
 1542         ui.lePort->hide();
 1543         ui.cbSerialPort->hide();
 1544         ui.lSerialPort->hide();
 1545 
 1546         item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
 1547 
 1548         fileNameChanged(fileName());
 1549         ui.cbFileType->show();
 1550         ui.lFileType->show();
 1551         setMQTTVisible(false);
 1552         break;
 1553     case LiveDataSource::SourceType::NetworkTcpSocket:
 1554     case LiveDataSource::SourceType::NetworkUdpSocket:
 1555         ui.lHost->show();
 1556         ui.leHost->show();
 1557         ui.lePort->show();
 1558         ui.lPort->show();
 1559         if (sourceType == LiveDataSource::SourceType::NetworkTcpSocket) {
 1560             ui.lSampleSize->hide();
 1561             ui.sbSampleSize->hide();
 1562         } else {
 1563             ui.lSampleSize->show();
 1564             ui.sbSampleSize->show();
 1565         }
 1566 
 1567         ui.lBaudRate->hide();
 1568         ui.cbBaudRate->hide();
 1569         ui.lSerialPort->hide();
 1570         ui.cbSerialPort->hide();
 1571 
 1572         ui.lFileName->hide();
 1573         m_cbFileName->hide();
 1574         ui.bFileInfo->hide();
 1575         ui.bOpen->hide();
 1576         ui.lRelativePath->hide();
 1577         ui.chbRelativePath->hide();
 1578         ui.chbLinkFile->hide();
 1579 
 1580         item->setFlags(item->flags() & ~(Qt::ItemIsSelectable | Qt::ItemIsEnabled));
 1581 
 1582         ui.gbOptions->setEnabled(true);
 1583         ui.bManageFilters->setEnabled(true);
 1584         ui.cbFilter->setEnabled(true);
 1585         ui.cbFileType->setEnabled(true);
 1586         ui.cbFileType->show();
 1587         ui.lFileType->show();
 1588         setMQTTVisible(false);
 1589         break;
 1590     case LiveDataSource::SourceType::LocalSocket:
 1591         ui.lFileName->show();
 1592         m_cbFileName->show();
 1593         ui.bFileInfo->hide();
 1594         ui.bOpen->show();
 1595         ui.lRelativePath->hide();
 1596         ui.chbRelativePath->hide();
 1597 
 1598         ui.lSampleSize->hide();
 1599         ui.sbSampleSize->hide();
 1600         ui.cbBaudRate->hide();
 1601         ui.lBaudRate->hide();
 1602         ui.lHost->hide();
 1603         ui.leHost->hide();
 1604         ui.lPort->hide();
 1605         ui.lePort->hide();
 1606         ui.cbSerialPort->hide();
 1607         ui.lSerialPort->hide();
 1608         ui.chbLinkFile->hide();
 1609 
 1610         item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
 1611 
 1612         ui.gbOptions->setEnabled(true);
 1613         ui.bManageFilters->setEnabled(true);
 1614         ui.cbFilter->setEnabled(true);
 1615         ui.cbFileType->setEnabled(true);
 1616         ui.cbFileType->show();
 1617         ui.lFileType->show();
 1618         setMQTTVisible(false);
 1619         break;
 1620     case LiveDataSource::SourceType::SerialPort:
 1621         ui.lBaudRate->show();
 1622         ui.cbBaudRate->show();
 1623         ui.lSerialPort->show();
 1624         ui.cbSerialPort->show();
 1625         ui.lSampleSize->show();
 1626         ui.sbSampleSize->show();
 1627 
 1628         ui.lHost->hide();
 1629         ui.leHost->hide();
 1630         ui.lePort->hide();
 1631         ui.lPort->hide();
 1632 
 1633         ui.lFileName->hide();
 1634         m_cbFileName->hide();
 1635         ui.bFileInfo->hide();
 1636         ui.bOpen->hide();
 1637         ui.lRelativePath->hide();
 1638         ui.chbRelativePath->hide();
 1639         ui.chbLinkFile->hide();
 1640 
 1641         item->setFlags(item->flags() & ~(Qt::ItemIsSelectable | Qt::ItemIsEnabled));
 1642 
 1643         ui.cbFileType->setEnabled(true);
 1644         ui.cbFileType->show();
 1645         ui.gbOptions->setEnabled(true);
 1646         ui.bManageFilters->setEnabled(true);
 1647         ui.cbFilter->setEnabled(true);
 1648         ui.lFileType->show();
 1649         setMQTTVisible(false);
 1650         break;
 1651     case LiveDataSource::SourceType::MQTT:
 1652 #ifdef HAVE_MQTT
 1653         item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
 1654 
 1655         //for MQTT we read ascii data only, hide the file type options
 1656         for (int i = 0; i < ui.cbFileType->count(); ++i) {
 1657             if (static_cast<AbstractFileFilter::FileType>(ui.cbFileType->itemData(i).toInt()) == AbstractFileFilter::FileType::Ascii) {
 1658                 if (ui.cbFileType->currentIndex() == i)
 1659                     initOptionsWidget();
 1660                 else
 1661                     ui.cbFileType->setCurrentIndex(i);
 1662 
 1663                 break;
 1664             }
 1665         }
 1666         ui.cbFileType->hide();
 1667         ui.lFileType->hide();
 1668 
 1669         ui.lBaudRate->hide();
 1670         ui.cbBaudRate->hide();
 1671         ui.lSerialPort->hide();
 1672         ui.cbSerialPort->hide();
 1673         ui.lHost->hide();
 1674         ui.leHost->hide();
 1675         ui.lPort->hide();
 1676         ui.lePort->hide();
 1677         ui.lFileName->hide();
 1678         m_cbFileName->hide();
 1679         ui.bFileInfo->hide();
 1680         ui.bOpen->hide();
 1681         ui.lRelativePath->hide();
 1682         ui.chbRelativePath->hide();
 1683         ui.chbLinkFile->hide();
 1684 
 1685         setMQTTVisible(true);
 1686 
 1687         ui.cbFileType->setEnabled(true);
 1688         ui.gbOptions->setEnabled(true);
 1689         ui.bManageFilters->setEnabled(true);
 1690         ui.cbFilter->setEnabled(true);
 1691 
 1692         //in case there are already connections defined,
 1693         //show the available topics for the currently selected connection
 1694         mqttConnectionChanged();
 1695 #endif
 1696         break;
 1697     }
 1698 
 1699     //deactivate/activate options that are specific to file of pipe sources only
 1700     auto* typeModel = qobject_cast<const QStandardItemModel*>(ui.cbFileType->model());
 1701     if (sourceType != LiveDataSource::SourceType::FileOrPipe) {
 1702         //deactivate file types other than ascii and binary
 1703         for (int i = 2; i < ui.cbFileType->count(); ++i)
 1704             typeModel->item(i)->setFlags(item->flags() & ~(Qt::ItemIsSelectable | Qt::ItemIsEnabled));
 1705         if (ui.cbFileType->currentIndex() > 1)
 1706             ui.cbFileType->setCurrentIndex(1);
 1707 
 1708         //"whole file" read option is available for file or pipe only, disable it
 1709         typeModel = qobject_cast<const QStandardItemModel*>(ui.cbReadingType->model());
 1710         QStandardItem* item = typeModel->item(static_cast<int>(LiveDataSource::ReadingType::WholeFile));
 1711         item->setFlags(item->flags() & ~(Qt::ItemIsSelectable | Qt::ItemIsEnabled));
 1712         if (static_cast<LiveDataSource::ReadingType>(ui.cbReadingType->currentIndex()) == LiveDataSource::ReadingType::WholeFile)
 1713             ui.cbReadingType->setCurrentIndex(static_cast<int>(LiveDataSource::ReadingType::TillEnd));
 1714 
 1715         //"update options" groupbox can be deactivated for "file and pipe" if the file is invalid.
 1716         //Activate the groupbox when switching from "file and pipe" to a different source type.
 1717         ui.gbUpdateOptions->setEnabled(true);
 1718     } else {
 1719         for (int i = 2; i < ui.cbFileType->count(); ++i)
 1720             typeModel->item(i)->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
 1721 
 1722         //enable "whole file" item for file or pipe
 1723         typeModel = qobject_cast<const QStandardItemModel*>(ui.cbReadingType->model());
 1724         QStandardItem* item = typeModel->item(static_cast<int>(LiveDataSource::ReadingType::WholeFile));
 1725         item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
 1726     }
 1727 
 1728     //disable the header options for non-file sources because:
 1729     //* for sockets we allow to import one single value only at the moment
 1730     //* for MQTT topics we don't allow to set the vector names since the different topics can have different number of columns
 1731     //For files this option still can be usefull if the user have to re-read the whole file
 1732     //and wants to use the header to set the column names or the user provides manually the column names.
 1733     //TODO: adjust this logic later once we allow to import multiple columns from sockets,
 1734     //it should be possible to provide the names of the columns
 1735     bool visible = (currentSourceType() == LiveDataSource::SourceType::FileOrPipe);
 1736     if (m_asciiOptionsWidget)
 1737         m_asciiOptionsWidget->showAsciiHeaderOptions(visible);
 1738 
 1739     emit sourceTypeChanged();
 1740     refreshPreview();
 1741 }
 1742 
 1743 #ifdef HAVE_MQTT
 1744 
 1745 /*!
 1746  *\brief called when a different MQTT connection is selected in the connection ComboBox.
 1747  * connects to the MQTT broker according to the connection settings.
 1748  */
 1749 void ImportFileWidget::mqttConnectionChanged() {
 1750     if (m_initialisingMQTT || ui.cbConnection->currentIndex() == -1) {
 1751         ui.lLWT->hide();
 1752         ui.bLWT->hide();
 1753         ui.lTopics->hide();
 1754         return;
 1755     }
 1756 
 1757     WAIT_CURSOR;
 1758     emit error(QString());
 1759 
 1760     //disconnected from the broker that was selected before
 1761     disconnectMqttConnection();
 1762 
 1763     //determine the connection settings for the new broker and initialize the mqtt client
 1764     KConfig config(m_configPath, KConfig::SimpleConfig);
 1765     KConfigGroup group = config.group(ui.cbConnection->currentText());
 1766 
 1767     m_client = new QMqttClient;
 1768     connect(m_client, &QMqttClient::connected, this, &ImportFileWidget::onMqttConnect);
 1769     connect(m_client, &QMqttClient::disconnected, this, &ImportFileWidget::onMqttDisconnect);
 1770     connect(m_client, &QMqttClient::messageReceived, this, &ImportFileWidget::mqttMessageReceived);
 1771     connect(m_client, &QMqttClient::errorChanged, this, &ImportFileWidget::mqttErrorChanged);
 1772 
 1773     m_client->setHostname(group.readEntry("Host"));
 1774     m_client->setPort(group.readEntry("Port").toUInt());
 1775 
 1776     const bool useID = group.readEntry("UseID").toUInt();
 1777     if (useID)
 1778         m_client->setClientId(group.readEntry("ClientID"));
 1779 
 1780     const bool useAuthentication = group.readEntry("UseAuthentication").toUInt();
 1781     if (useAuthentication) {
 1782         m_client->setUsername(group.readEntry("UserName"));
 1783         m_client->setPassword(group.readEntry("Password"));
 1784     }
 1785 
 1786     //connect to the selected broker
 1787     QDEBUG("Connect to " << m_client->hostname() << ":" << m_client->port());
 1788     if (!m_connectTimeoutTimer) {
 1789         m_connectTimeoutTimer = new QTimer(this);
 1790         m_connectTimeoutTimer->setInterval(6000);
 1791         connect(m_connectTimeoutTimer, &QTimer::timeout, this, &ImportFileWidget::mqttConnectTimeout);
 1792     }
 1793     m_connectTimeoutTimer->start();
 1794     m_client->connectToHost();
 1795 }
 1796 
 1797 void ImportFileWidget::disconnectMqttConnection() {
 1798     if (m_client && m_client->state() == QMqttClient::ClientState::Connected) {
 1799         emit MQTTClearTopics();
 1800         disconnect(m_client, &QMqttClient::disconnected, this, &ImportFileWidget::onMqttDisconnect);
 1801         QDEBUG("Disconnecting from " << m_client->hostname());
 1802         m_client->disconnectFromHost();
 1803         delete m_client;
 1804         m_client = nullptr;
 1805     }
 1806 }
 1807 
 1808 /*!
 1809  * returns \c true if there is a valid connection to an MQTT broker and the user has subscribed to at least 1 topic,
 1810  * returns \c false otherwise.
 1811  */
 1812 bool ImportFileWidget::isMqttValid() {
 1813     if (!m_client)
 1814         return false;
 1815 
 1816     bool connected = (m_client->state() == QMqttClient::ClientState::Connected);
 1817     bool subscribed = (m_subscriptionWidget->subscriptionCount() > 0);
 1818     bool fileTypeOk = false;
 1819     if (this->currentFileType() == AbstractFileFilter::FileType::Ascii)
 1820         fileTypeOk = true;
 1821 
 1822     return connected && subscribed && fileTypeOk;
 1823 }
 1824 
 1825 /*!
 1826  *\brief called when the client connects to the broker successfully.
 1827  * subscribes to every topic (# wildcard) in order to later list every available topic
 1828  */
 1829 void ImportFileWidget::onMqttConnect() {
 1830     m_connectTimeoutTimer->stop();
 1831     if (m_client->error() == QMqttClient::NoError) {
 1832         ui.frameSubscriptions->setVisible(true);
 1833         m_subscriptionWidget->setVisible(true);
 1834         m_subscriptionWidget->makeVisible(true);
 1835 
 1836         if (!m_client->subscribe(QMqttTopicFilter(QLatin1String("#")), 1))
 1837             emit error(i18n("Couldn't subscribe to all available topics."));
 1838         else {
 1839             emit error(QString());
 1840             ui.lLWT->show();
 1841             ui.bLWT->show();
 1842             ui.lTopics->show();
 1843         }
 1844     } else
 1845         emit error("on mqtt connect error " + QString::number(m_client->error()));
 1846 
 1847     emit subscriptionsChanged();
 1848     RESET_CURSOR;
 1849 }
 1850 
 1851 /*!
 1852  *\brief called when the client disconnects from the broker successfully
 1853  * removes every information about the former connection
 1854  */
 1855 void ImportFileWidget::onMqttDisconnect() {
 1856     DEBUG("Disconnected from " << STDSTRING(m_client->hostname()));
 1857     m_connectTimeoutTimer->stop();
 1858 
 1859     ui.lTopics->hide();
 1860     ui.frameSubscriptions->hide();
 1861     ui.lLWT->hide();
 1862     ui.bLWT->hide();
 1863 
 1864     ui.cbConnection->setCurrentIndex(-1);
 1865 
 1866     emit subscriptionsChanged();
 1867     emit error(i18n("Disconnected from '%1'.", m_client->hostname()));
 1868     RESET_CURSOR;
 1869 }
 1870 
 1871 /*!
 1872  *\brief called when the subscribe button is pressed
 1873  * subscribes to the topic represented by the current item of twTopics
 1874  */
 1875 void ImportFileWidget::subscribeTopic(const QString& name, uint QoS) {
 1876     const QMqttTopicFilter filter {name};
 1877     QMqttSubscription* tempSubscription = m_client->subscribe(filter, static_cast<quint8>(QoS) );
 1878 
 1879     if (tempSubscription) {
 1880         m_mqttSubscriptions.push_back(tempSubscription);
 1881         connect(tempSubscription, &QMqttSubscription::messageReceived, this, &ImportFileWidget::mqttSubscriptionMessageReceived);
 1882         emit subscriptionsChanged();
 1883     }
 1884 }
 1885 
 1886 /*!
 1887  *\brief Unsubscribes from the given topic, and removes any data connected to it
 1888  *
 1889  * \param topicName the name of a topic we want to unsubscribe from
 1890  */
 1891 void ImportFileWidget::unsubscribeTopic(const QString& topicName, QVector<QTreeWidgetItem*> children) {
 1892     if (topicName.isEmpty())
 1893         return;
 1894 
 1895     for (int i = 0; i< m_mqttSubscriptions.count(); ++i) {
 1896         if (m_mqttSubscriptions[i]->topic().filter() == topicName) {
 1897             //explicitely disconnect from the signal, callling QMqttClient::unsubscribe() below is not enough
 1898             disconnect(m_mqttSubscriptions.at(i), &QMqttSubscription::messageReceived, this, &ImportFileWidget::mqttSubscriptionMessageReceived);
 1899             m_mqttSubscriptions.remove(i);
 1900             break;
 1901         }
 1902     }
 1903 
 1904     QMqttTopicFilter filter{topicName};
 1905     m_client->unsubscribe(filter);
 1906 
 1907     QMapIterator<QMqttTopicName, QMqttMessage> j(m_lastMessage);
 1908     while (j.hasNext()) {
 1909         j.next();
 1910         if (MQTTSubscriptionWidget::checkTopicContains(topicName, j.key().name()))
 1911             m_lastMessage.remove(j.key());
 1912     }
 1913 
 1914     if (m_willSettings.willTopic == topicName) {
 1915         if (m_subscriptionWidget->subscriptionCount() > 0)
 1916             m_willSettings.willTopic = children[0]->text(0);
 1917         else
 1918             m_willSettings.willTopic.clear();
 1919     }
 1920 
 1921     //signals that there was a change among the subscribed topics
 1922     emit subscriptionsChanged();
 1923     refreshPreview();
 1924 }
 1925 
 1926 /*!
 1927  *\brief called when the client receives a message
 1928  * if the message arrived from a new topic, the topic is put in twTopics
 1929  */
 1930 void ImportFileWidget::mqttMessageReceived(const QByteArray& message, const QMqttTopicName& topic) {
 1931     Q_UNUSED(message);
 1932 //  qDebug()<<"received " << topic.name();
 1933     if (m_addedTopics.contains(topic.name()))
 1934         return;
 1935 
 1936     m_addedTopics.push_back(topic.name());
 1937     m_subscriptionWidget->setTopicTreeText(i18n("Available (%1)", m_addedTopics.size()));
 1938     QStringList name;
 1939     QString rootName;
 1940     const QChar sep = '/';
 1941 
 1942     if (topic.name().contains(sep)) {
 1943         const QStringList& list = topic.name().split(sep, QString::SkipEmptyParts);
 1944 
 1945         if (!list.isEmpty()) {
 1946             rootName = list.at(0);
 1947             name.append(list.at(0));
 1948             int topItemIdx = -1;
 1949             //check whether the first level of the topic can be found in twTopics
 1950             for (int i = 0; i < m_subscriptionWidget->topicCount(); ++i) {
 1951                 if (m_subscriptionWidget->topLevelTopic(i)->text(0) == list.at(0)) {
 1952                     topItemIdx = i;
 1953                     break;
 1954                 }
 1955             }
 1956 
 1957             //if not we simply add every level of the topic to the tree
 1958             if (topItemIdx < 0) {
 1959                 auto* currentItem = new QTreeWidgetItem(name);
 1960                 m_subscriptionWidget->addTopic(currentItem);
 1961                 for (int i = 1; i < list.size(); ++i) {
 1962                     name.clear();
 1963                     name.append(list.at(i));
 1964                     currentItem->addChild(new QTreeWidgetItem(name));
 1965                     currentItem = currentItem->child(0);
 1966                 }
 1967             }
 1968             //otherwise we search for the first level that isn't part of the tree,
 1969             //then add every level of the topic to the tree from that certain level
 1970             else {
 1971                 QTreeWidgetItem* currentItem = m_subscriptionWidget->topLevelTopic(topItemIdx);
 1972                 int listIdx = 1;
 1973                 for (; listIdx < list.size(); ++listIdx) {
 1974                     QTreeWidgetItem* childItem = nullptr;
 1975                     bool found = false;
 1976                     for (int j = 0; j < currentItem->childCount(); ++j) {
 1977                         childItem = currentItem->child(j);
 1978                         if (childItem->text(0) == list.at(listIdx)) {
 1979                             found = true;
 1980                             currentItem = childItem;
 1981                             break;
 1982                         }
 1983                     }
 1984                     if (!found) {
 1985                         //this is the level that isn't present in the tree
 1986                         break;
 1987                     }
 1988                 }
 1989 
 1990                 //add every level to the tree starting with the first level that isn't part of the tree
 1991                 for (; listIdx < list.size(); ++listIdx) {
 1992                     name.clear();
 1993                     name.append(list.at(listIdx));
 1994                     currentItem->addChild(new QTreeWidgetItem(name));
 1995                     currentItem = currentItem->child(currentItem->childCount() - 1);
 1996                 }
 1997             }
 1998         }
 1999     } else {
 2000         rootName = topic.name();
 2001         name.append(topic.name());
 2002         m_subscriptionWidget->addTopic(new QTreeWidgetItem(name));
 2003     }
 2004 
 2005     //if a subscribed topic contains the new topic, we have to update twSubscriptions
 2006     for (int i = 0; i < m_subscriptionWidget->subscriptionCount(); ++i) {
 2007         const QStringList subscriptionName = m_subscriptionWidget->topLevelSubscription(i)->text(0).split(sep, QString::SkipEmptyParts);
 2008         if (!subscriptionName.isEmpty()) {
 2009             if (rootName == subscriptionName.first()) {
 2010                 QVector<QString> subscriptions;
 2011                 for (const auto& sub : m_mqttSubscriptions)
 2012                     subscriptions.push_back(sub->topic().filter());
 2013                 emit updateSubscriptionTree(subscriptions);
 2014                 break;
 2015             }
 2016         }
 2017     }
 2018 
 2019     //signals that a newTopic was added, in order to fill the completer of leTopics
 2020     emit newTopic(rootName);
 2021 }
 2022 
 2023 /*!
 2024  *\brief called when the client receives a message from a subscribed topic (that isn't the "#" wildcard)
 2025  */
 2026 void ImportFileWidget::mqttSubscriptionMessageReceived(const QMqttMessage &msg) {
 2027     QDEBUG("message received from: " << msg.topic().name());
 2028 
 2029     //update the last message for the topic
 2030     m_lastMessage[msg.topic()] = msg;
 2031 }
 2032 
 2033 /*!
 2034  *\brief called when the clientError of the MQTT client changes
 2035  *
 2036  * \param clientError the current error of the client
 2037  */
 2038 void ImportFileWidget::mqttErrorChanged(QMqttClient::ClientError clientError) {
 2039     switch (clientError) {
 2040     case QMqttClient::BadUsernameOrPassword:
 2041         emit error(i18n("Wrong username or password"));
 2042         break;
 2043     case QMqttClient::IdRejected:
 2044         emit error(i18n("The client ID wasn't accepted"));
 2045         break;
 2046     case QMqttClient::ServerUnavailable:
 2047     case QMqttClient::TransportInvalid:
 2048         emit error(i18n("The broker %1 couldn't be reached.", m_client->hostname()));
 2049         break;
 2050     case QMqttClient::NotAuthorized:
 2051         emit error(i18n("The client is not authorized to connect."));
 2052         break;
 2053     case QMqttClient::UnknownError:
 2054         emit error(i18n("An unknown error occurred."));
 2055         break;
 2056     case QMqttClient::NoError:
 2057     case QMqttClient::InvalidProtocolVersion:
 2058     case QMqttClient::ProtocolViolation:
 2059     case QMqttClient::Mqtt5SpecificError:
 2060         emit error(i18n("An error occurred."));
 2061         break;
 2062     default:
 2063         emit error(i18n("An error occurred."));
 2064         break;
 2065     }
 2066     m_connectTimeoutTimer->stop();
 2067 }
 2068 
 2069 /*!
 2070  *\brief called when m_connectTimeoutTimer ticks,
 2071  *       meaning that the client couldn't connect to the broker in 5 seconds
 2072  *       disconnects the client, stops the timer, and warns the user
 2073  */
 2074 void ImportFileWidget::mqttConnectTimeout() {
 2075     m_client->disconnectFromHost();
 2076     m_connectTimeoutTimer->stop();
 2077     emit error(i18n("Connecting to '%1:%2' timed out.", m_client->hostname(), m_client->port()));
 2078     RESET_CURSOR;
 2079 }
 2080 
 2081 /*!
 2082     Shows the MQTT connection manager where the connections are created and edited.
 2083     The selected connection is selected in the connection combo box in this widget.
 2084 */
 2085 void ImportFileWidget::showMQTTConnectionManager() {
 2086     bool previousConnectionChanged = false;
 2087     auto* dlg = new MQTTConnectionManagerDialog(this, ui.cbConnection->currentText(), previousConnectionChanged);
 2088 
 2089     if (dlg->exec() == QDialog::Accepted) {
 2090         //re-read the available connections to be in sync with the changes in MQTTConnectionManager
 2091         m_initialisingMQTT = true;
 2092         const QString& prevConn = ui.cbConnection->currentText();
 2093         ui.cbConnection->clear();
 2094         readMQTTConnections();
 2095         m_initialisingMQTT = false;
 2096 
 2097         //select the connection the user has selected in MQTTConnectionManager
 2098         const QString& conn = dlg->connection();
 2099 
 2100         int index = ui.cbConnection->findText(conn);
 2101         if (conn != prevConn) {//Current connection isn't the previous one
 2102             if (ui.cbConnection->currentIndex() != index)
 2103                 ui.cbConnection->setCurrentIndex(index);
 2104             else
 2105                 mqttConnectionChanged();
 2106         } else if (dlg->initialConnectionChanged()) {//Current connection is the same with previous one but it changed
 2107             if (ui.cbConnection->currentIndex() == index)
 2108                 mqttConnectionChanged();
 2109             else
 2110                 ui.cbConnection->setCurrentIndex(index);
 2111         } else { //Previous connection wasn't changed
 2112             m_initialisingMQTT = true;
 2113             ui.cbConnection->setCurrentIndex(index);
 2114             m_initialisingMQTT = false;
 2115         }
 2116     }
 2117     delete dlg;
 2118 }
 2119 
 2120 /*!
 2121     loads all available saved MQTT nconnections
 2122 */
 2123 void ImportFileWidget::readMQTTConnections() {
 2124     DEBUG("ImportFileWidget: reading available MQTT connections");
 2125     KConfig config(m_configPath, KConfig::SimpleConfig);
 2126     for (const auto& name : config.groupList())
 2127         ui.cbConnection->addItem(name);
 2128 }
 2129 
 2130 /*!
 2131  * \brief Shows the mqtt will settings widget, which allows the user to modify the will settings
 2132  */
 2133 void ImportFileWidget::showWillSettings() {
 2134     QMenu menu;
 2135 
 2136     QVector<QTreeWidgetItem*> children;
 2137     for (int i = 0; i < m_subscriptionWidget->subscriptionCount(); ++i)
 2138         MQTTSubscriptionWidget::findSubscriptionLeafChildren(children, m_subscriptionWidget->topLevelSubscription(i));
 2139 
 2140     QVector<QString> topics;
 2141     for (const auto& child : children)
 2142         topics.append(child->text(0));
 2143 
 2144     MQTTWillSettingsWidget willSettingsWidget(&menu, m_willSettings, topics);
 2145 
 2146     connect(&willSettingsWidget, &MQTTWillSettingsWidget::applyClicked, [this, &menu, &willSettingsWidget]() {
 2147         m_willSettings = willSettingsWidget.will();
 2148         menu.close();
 2149     });
 2150     auto* widgetAction = new QWidgetAction(this);
 2151     widgetAction->setDefaultWidget(&willSettingsWidget);
 2152     menu.addAction(widgetAction);
 2153 
 2154     const QPoint pos(ui.bLWT->sizeHint().width(),ui.bLWT->sizeHint().height());
 2155     menu.exec(ui.bLWT->mapToGlobal(pos));
 2156 }
 2157 
 2158 void ImportFileWidget::enableWill(bool enable) {
 2159     if (enable) {
 2160         if (!ui.bLWT->isEnabled())
 2161             ui.bLWT->setEnabled(enable);
 2162     } else
 2163         ui.bLWT->setEnabled(enable);
 2164 }
 2165 
 2166 
 2167 /*!
 2168     saves the settings to the MQTTClient \c client.
 2169 */
 2170 void ImportFileWidget::saveMQTTSettings(MQTTClient* client) const {
 2171     DEBUG("ImportFileWidget::saveMQTTSettings");
 2172     auto updateType = static_cast<MQTTClient::UpdateType>(ui.cbUpdateType->currentIndex());
 2173     auto readingType = static_cast<MQTTClient::ReadingType>(ui.cbReadingType->currentIndex());
 2174 
 2175     currentFileFilter();
 2176     client->setFilter(static_cast<AsciiFilter*>(m_currentFilter.release())); // pass ownership of the filter to MQTTClient
 2177 
 2178     client->setReadingType(readingType);
 2179 
 2180     if (updateType == MQTTClient::UpdateType::TimeInterval)
 2181         client->setUpdateInterval(ui.sbUpdateInterval->value());
 2182 
 2183     client->setKeepNValues(ui.sbKeepNValues->value());
 2184     client->setUpdateType(updateType);
 2185 
 2186     if (readingType != MQTTClient::ReadingType::TillEnd)
 2187         client->setSampleSize(ui.sbSampleSize->value());
 2188 
 2189     client->setMQTTClientHostPort(m_client->hostname(), m_client->port());
 2190 
 2191     KConfig config(m_configPath, KConfig::SimpleConfig);
 2192     KConfigGroup group = config.group(ui.cbConnection->currentText());
 2193 
 2194     bool useID = group.readEntry("UseID").toUInt();
 2195     bool useAuthentication = group.readEntry("UseAuthentication").toUInt();
 2196 
 2197     client->setMQTTUseAuthentication(useAuthentication);
 2198     if (useAuthentication)
 2199         client->setMQTTClientAuthentication(m_client->username(), m_client->password());
 2200 
 2201     client->setMQTTUseID(useID);
 2202     if (useID)
 2203         client->setMQTTClientId(m_client->clientId());
 2204 
 2205     for (int i = 0; i < m_mqttSubscriptions.count(); ++i)
 2206         client->addInitialMQTTSubscriptions(m_mqttSubscriptions[i]->topic(), m_mqttSubscriptions[i]->qos());
 2207 
 2208     const bool retain = group.readEntry("Retain").toUInt();
 2209     client->setMQTTRetain(retain);
 2210 
 2211     if (m_willSettings.enabled)
 2212         client->setWillSettings(m_willSettings);
 2213 }
 2214 #endif