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