diff --git a/src/kdefrontend/DatasetModel.cpp b/src/kdefrontend/DatasetModel.cpp index 4c9c6f26d..447ed73c6 100644 --- a/src/kdefrontend/DatasetModel.cpp +++ b/src/kdefrontend/DatasetModel.cpp @@ -1,261 +1,213 @@ /*************************************************************************** File : DatasetModel.cpp Project : LabPlot -------------------------------------------------------------------- Copyright : (C) 2019 Ferencz Kovacs (kferike98@gmail.com) Description : Wrapper class for datasets, and also for their categories and subcategories ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "DatasetModel.h" #include "kdefrontend/datasources/ImportDatasetWidget.h" #include "backend/datasources/DatasetHandler.h" #include #include #include #include #include #include #include /*! \class DatasetModel \brief Wrapper class for datasets, and also for their categories and subcategories \ingroup kdefrontend */ DatasetModel::DatasetModel(const QMap>>>& datasetsMap) { initCollections(datasetsMap); initCategories(datasetsMap); initSubcategories(datasetsMap); initDatasets(datasetsMap); } DatasetModel::~DatasetModel() { } /** * @brief Initializes the list of collections. */ void DatasetModel::initCollections(const QMap > > > & datasetMap) { m_collectionList = datasetMap.keys(); - //m_collectionList.removeAll("Test"); } /** * @brief Initializes the list of categories. */ void DatasetModel::initCategories(const QMap > > > & datasetMap) { for(auto i = datasetMap.begin(); i != datasetMap.end(); ++i) { - if(i.key().compare("Test") != 0) { - m_categories[i.key()] = i.value().keys(); + m_categories[i.key()] = i.value().keys(); - for(auto category : i.value().keys()) { - if(!m_allCategories.contains(category)) - m_allCategories.append(category); - } - } else { - m_testCategories.append(i.value().keys()); + for(auto category : i.value().keys()) { + if(!m_allCategories.contains(category)) + m_allCategories.append(category); } + } } /** * @brief Initializes the list of subcategories. */ void DatasetModel::initSubcategories(const QMap > > > & datasetMap) { for(auto collection = datasetMap.begin(); collection != datasetMap.end(); ++collection) { const QMap > > collection_ = collection.value(); for(auto category = collection_.begin(); category != collection_.end(); category++) { - if(collection.key().compare("Test") != 0) { - m_subcategories[collection.key()][category.key()] = category.value().keys(); + m_subcategories[collection.key()][category.key()] = category.value().keys(); - for(auto subcategory: category.value().keys()) { - if(!m_allSubcategories[category.key()].contains(subcategory)) - m_allSubcategories[category.key()].append(subcategory); - } - } else { - m_testSubcategories[category.key()].append(category.value().keys()); + for(auto subcategory: category.value().keys()) { + if(!m_allSubcategories[category.key()].contains(subcategory)) + m_allSubcategories[category.key()].append(subcategory); } } } } /** * @brief Initializes the list of datasets. */ void DatasetModel::initDatasets(const QMap > > >& datasetMap) { for(auto collection = datasetMap.begin(); collection != datasetMap.end(); ++collection) { const QMap > > collection_ = collection.value(); for(auto category = collection_.begin(); category != collection_.end(); category++) { const QMap >category_ = category.value(); for(auto subcategory = category_.begin(); subcategory != category_.end(); subcategory++) { - if(collection.key().compare("Test") != 0) { - m_datasets[collection.key()][category.key()][subcategory.key()] = subcategory.value().toList(); - m_allDatasets[category.key()][subcategory.key()].append(subcategory.value().toList()); - m_datasetList.append(subcategory.value().toList()); - } else { - m_testDatasets[category.key()][subcategory.key()].append(subcategory.value().toList()); - } + m_datasets[collection.key()][category.key()][subcategory.key()] = subcategory.value().toList(); + m_allDatasets[category.key()][subcategory.key()].append(subcategory.value().toList()); + m_datasetList.append(subcategory.value().toList()); } } } } /** * @brief Returns the list of categories. */ QVariant DatasetModel::allCategories() { return QVariant(m_allCategories); } /** * @brief Returns the list of subcategories of a given category. * @param category the category the subcategories of which will be returned */ QVariant DatasetModel::allSubcategories(const QString& category) { return QVariant(m_allSubcategories[category]); } /** * @brief Returns the list of datasets of a given category and subcategory. */ QVariant DatasetModel::allDatasets(const QString& category, const QString& subcategory) { return QVariant(m_allDatasets[category][subcategory]); } /** * @brief Returns the list of every dataset. */ QVariant DatasetModel::allDatasetsList() { return QVariant(m_datasetList); } /** * @brief Returns the list of categories for a given collection */ QStringList DatasetModel::categories(const QString& collection) { if(collection.compare("All") != 0) return m_categories[collection]; else return allCategories().toStringList(); } /** * @brief Returns the list of subcategories of a given collection and category. */ QStringList DatasetModel::subcategories(const QString& collection, const QString& category) { if(collection.compare("All") != 0) return m_subcategories[collection][category]; else return allSubcategories(category).toStringList(); } /** * @brief Returns the list of datasets of a given collection, category and subcategory. */ QStringList DatasetModel::datasets(const QString& collection, const QString& category, const QString& subcategory) { if(collection.compare("All") != 0) return m_datasets[collection][category][subcategory]; else return allDatasets(category, subcategory).toStringList(); } /** * @brief Returns the number of datasets belonging to the given collection */ int DatasetModel::datasetCount(const QString& collection) { int count = 0; for(const QString& category: categories(collection)) { for(const QString& subcategory: subcategories(collection, category)) { count += datasets(collection, category, subcategory).size(); } } return count; } /** * @brief Returns the number of datasets belonging to the given collection and category */ int DatasetModel::datasetCount(const QString& collection, const QString& category) { int count = 0; for(const QString& subcategory: subcategories(collection, category)) { count += datasets(collection, category, subcategory).size(); } return count; } /** * @brief Returns the number of datasets belonging to the given collection, category and subcategory */ int DatasetModel::datasetCount(const QString& collection, const QString& category, const QString& subcategory) { return datasets(collection, category, subcategory).size(); } /** * @brief Returns the list of every collection. */ QStringList DatasetModel::collections() { return m_collectionList; } -/** - * @brief Returns the list of test-categories. - */ -QStringList DatasetModel::testCategories() { - return m_testCategories; -} - -/** - * @brief Returns the list of test-subcategories of a given category. - * @param category the category the subcategories of which will be returned - */ -QStringList DatasetModel::testSubcategories(const QString& category) { - return m_testSubcategories[category]; -} - -/** - * @brief Returns the list of test-datasets of a given category and subcategory. - */ -QStringList DatasetModel::testDatasets(const QString& category, const QString& subcategory) { - return m_testDatasets[category][subcategory]; -} - -/** - * @brief Returns the list of all the test-datasets. - */ -QStringList DatasetModel::allTestDatasets() { - QStringList datasets; - - for(const QString category : m_testCategories) { - for(const QString subcategory : m_testSubcategories[category]) { - datasets.append(m_testDatasets[category][subcategory]); - } - } - - return datasets; -} diff --git a/src/kdefrontend/DatasetModel.h b/src/kdefrontend/DatasetModel.h index 444013379..f175d02e7 100644 --- a/src/kdefrontend/DatasetModel.h +++ b/src/kdefrontend/DatasetModel.h @@ -1,84 +1,74 @@ /*************************************************************************** File : DatasetModel.h Project : LabPlot -------------------------------------------------------------------- Copyright : (C) 2019 Ferencz Kovacs (kferike98@gmail.com) Description : Wrapper class for datasets, and also for their categories and subcategories ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #ifndef DATASETMODEL_H #define DATASETMODEL_H #include #include #include class ImportDatasetWidget; class DatasetHandler; class Spreadsheet; class DatasetModel : public QObject { Q_OBJECT public: DatasetModel(const QMap>>>&); ~DatasetModel(); QStringList collections(); QStringList categories(const QString&); QStringList subcategories(const QString&, const QString&); QStringList datasets(const QString&, const QString&, const QString&); - QStringList testCategories(); - QStringList testSubcategories(const QString&); - QStringList testDatasets(const QString&, const QString&); - QStringList allTestDatasets(); int datasetCount(const QString& collection); int datasetCount(const QString& collection, const QString& category); int datasetCount(const QString& collection, const QString& category, const QString& subcategory); Q_INVOKABLE QVariant allCategories(); Q_INVOKABLE QVariant allSubcategories(const QString&); Q_INVOKABLE QVariant allDatasets(const QString&, const QString&); Q_INVOKABLE QVariant allDatasetsList(); private: QStringList m_collectionList; QStringList m_allCategories; QMap m_allSubcategories; QMap> m_allDatasets; QMap m_categories; QMap> m_subcategories; QMap>> m_datasets; QStringList m_datasetList; - QStringList m_testAllDatasets; - QStringList m_testCategories; - QMap m_testSubcategories; - QMap> m_testDatasets; - - void initCollections(const QMap>>>&); void initCategories(const QMap>>>& datasetMap); void initSubcategories(const QMap>>>& datasetMap); void initDatasets(const QMap>>>& datasetMap); }; #endif //DATASETMODEL_H diff --git a/src/kdefrontend/datasources/DatasetMetadataManagerDialog.cpp b/src/kdefrontend/datasources/DatasetMetadataManagerDialog.cpp index baf02c3a9..75122cbe0 100644 --- a/src/kdefrontend/datasources/DatasetMetadataManagerDialog.cpp +++ b/src/kdefrontend/datasources/DatasetMetadataManagerDialog.cpp @@ -1,105 +1,154 @@ /*************************************************************************** File : DatasetMetadataManagerDialog.cpp Project : LabPlot Description : Dialog for managing a metadata file of a dataset -------------------------------------------------------------------- Copyright : (C) 2019 Ferencz Kovacs (kferike98@gmail.com) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "src/kdefrontend/datasources/DatasetMetadataManagerDialog.h" #include "src/kdefrontend/datasources/DatasetMetadataManagerWidget.h" #include #include #include #include #include #include #include /*! \class DatasetMetadataManagerDialog \brief Dialog for adding a new dataset to LabPlot's current collection. Embeds \c DatasetMetadataManagerWidget and provides the standard buttons. \ingroup kdefrontend */ DatasetMetadataManagerDialog::DatasetMetadataManagerDialog(QWidget* parent, const QMap< QString, QMap>>>& datasetMap) : QDialog(parent), m_mainWidget(new DatasetMetadataManagerWidget(this, datasetMap)), m_buttonBox(nullptr), m_okButton(nullptr) { connect(m_mainWidget, &DatasetMetadataManagerWidget::checkOk, this, &DatasetMetadataManagerDialog::checkOkButton); setWindowTitle(i18nc("@title:window", "Dataset metadata manager")); m_buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); m_okButton = m_buttonBox->button(QDialogButtonBox::Ok); m_okButton->setEnabled(false); QVBoxLayout* layout = new QVBoxLayout(this); layout->addWidget(m_mainWidget); layout->addWidget(m_buttonBox); connect(m_buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); connect(m_buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); //restore saved settings if available create(); // ensure there's a window created KConfigGroup conf(KSharedConfig::openConfig(), "DatasetMetadataManagerDialog"); if (conf.exists()) { KWindowConfig::restoreWindowSize(windowHandle(), conf); resize(windowHandle()->size()); // workaround for QTBUG-40584 } else { resize(QSize(0, 0).expandedTo(minimumSize())); } checkOkButton(); } DatasetMetadataManagerDialog::~DatasetMetadataManagerDialog() { //save current settings KConfigGroup conf(KSharedConfig::openConfig(), "DatasetMetadataManagerDialog"); KWindowConfig::saveWindowSize(windowHandle(), conf); } /** * @brief Checks whether the OK button of the dialog can be pressed or not */ void DatasetMetadataManagerDialog::checkOkButton() { bool enable = m_mainWidget->checkDataValidity(); m_okButton->setEnabled(enable); } /** * @brief Triggers updating the metadata file containing the categories, subcategories and datasets. * @param fileName the name of the metadata file (path) */ void DatasetMetadataManagerDialog::updateDocument(const QString& fileName) { m_mainWidget->updateDocument(fileName); } /** * @brief returns the path to the new metadata file of the new dataset. */ QString DatasetMetadataManagerDialog::getMetadataFilePath() const { return m_mainWidget->getMetadataFilePath(); } + +/** + * @brief Sets the collection name in the DatasetMetadataManagerWidget + */ +void DatasetMetadataManagerDialog::setCollection(const QString& collection) { + m_mainWidget->setCollection(collection); +} + +/** + * @brief Sets the category name in the DatasetMetadataManagerWidget + */ +void DatasetMetadataManagerDialog::setCategory(const QString& category) { + m_mainWidget->setCategory(category); +} + +/** + * @brief Sets the subcategory name in the DatasetMetadataManagerWidget + */ +void DatasetMetadataManagerDialog::setSubcategory(const QString& subcategory) { + m_mainWidget->setSubcategory(subcategory); +} + +/** + * @brief Sets the short name of the dataset in the DatasetMetadataManagerWidget + */ +void DatasetMetadataManagerDialog::setShortName(const QString& name) { + m_mainWidget->setShortName(name); +} + +/** + * @brief Sets the full name of the dataset in the DatasetMetadataManagerWidget + */ +void DatasetMetadataManagerDialog::setFullName(const QString& name) { + m_mainWidget->setFullName(name); +} + +/** + * @brief Sets the text of the description in the DatasetMetadataManagerWidget + */ +void DatasetMetadataManagerDialog::setDescription(const QString& description) { + m_mainWidget->setDescription(description); +} + +/** + * @brief Sets the download url in the DatasetMetadataManagerWidget + */ +void DatasetMetadataManagerDialog::setURL(const QString& url) { + m_mainWidget->setURL(url); +} diff --git a/src/kdefrontend/datasources/DatasetMetadataManagerDialog.h b/src/kdefrontend/datasources/DatasetMetadataManagerDialog.h index 6764d47f2..ca51b146b 100644 --- a/src/kdefrontend/datasources/DatasetMetadataManagerDialog.h +++ b/src/kdefrontend/datasources/DatasetMetadataManagerDialog.h @@ -1,55 +1,63 @@ /*************************************************************************** File : DatasetMetadataManagerDialog.h Project : LabPlot Description : Dialog for managing a metadata file of a dataset -------------------------------------------------------------------- Copyright : (C) 2019 Ferencz Kovacs (kferike98@gmail.com) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #ifndef DATASETMETADATAMANAGERDIALOG_H #define DATASETMETADATAMANAGERDIALOG_H #include class QDialogButtonBox; class DatasetMetadataManagerWidget; class DatasetMetadataManagerDialog : public QDialog { Q_OBJECT public: explicit DatasetMetadataManagerDialog(QWidget*, const QMap< QString, QMap>>>&); virtual ~DatasetMetadataManagerDialog() override; void updateDocument(const QString& fileName); QString getMetadataFilePath() const; + void setCollection(const QString&); + void setCategory(const QString&); + void setSubcategory(const QString&); + void setShortName(const QString&); + void setFullName(const QString&); + void setDescription(const QString&); + void setURL(const QString&); + private: DatasetMetadataManagerWidget* m_mainWidget; QDialogButtonBox* m_buttonBox; QPushButton* m_okButton; protected slots: void checkOkButton(); }; #endif // DATASETMETADATAMANAGERDIALOG_H diff --git a/src/kdefrontend/datasources/DatasetMetadataManagerWidget.cpp b/src/kdefrontend/datasources/DatasetMetadataManagerWidget.cpp index 45bf31f26..7d95862fc 100644 --- a/src/kdefrontend/datasources/DatasetMetadataManagerWidget.cpp +++ b/src/kdefrontend/datasources/DatasetMetadataManagerWidget.cpp @@ -1,569 +1,610 @@ /*************************************************************************** File : DatasetMetadataManagerWidget.cpp Project : LabPlot Description : widget for managing a metadata file of a dataset -------------------------------------------------------------------- Copyright : (C) 2019 Ferencz Kovacs (kferike98@gmail.com) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "backend/datasources/filters/AsciiFilter.h" #include "src/kdefrontend/DatasetModel.h" #include "src/kdefrontend/datasources/DatasetMetadataManagerWidget.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /*! \class DatasetMetadataManagerWidget \brief Widget for adding a new dataset to LabPlot's current collection. \ingroup kdefrontend */ DatasetMetadataManagerWidget::DatasetMetadataManagerWidget(QWidget* parent, const QMap< QString, QMap>>>& datasetMap) : QWidget(parent) { ui.setupUi(this); m_datasetModel = new DatasetModel(datasetMap); m_baseColor = (palette().color(QPalette::Base).lightness() < 128) ? QLatin1String("#5f5f5f") : QLatin1String("#ffffff"); m_textColor = (palette().color(QPalette::Base).lightness() < 128) ? QLatin1String("#ffffff") : QLatin1String("#000000"); ui.cbCollection->addItems(m_datasetModel->collections()); ui.cbCategory->addItems(m_datasetModel->categories(ui.cbCollection->currentText())); ui.cbSubcategory->addItems(m_datasetModel->subcategories(ui.cbCollection->currentText(), ui.cbCategory->currentText())); ui.cbSeparatingCharacter->addItems(AsciiFilter::separatorCharacters()); ui.cbCommentCharacter->addItems(AsciiFilter::commentCharacters()); ui.cbNumberFormat->addItems(AbstractFileFilter::numberFormats()); ui.cbDateTimeFormat->addItems(AbstractColumn::dateTimeFormats()); connect(ui.leDatasetName, &QLineEdit::textChanged, [this] { emit checkOk(); }); connect(ui.leDownloadURL, &QLineEdit::textChanged, [this] { emit checkOk(); }); connect(ui.teDescription, &QTextEdit::textChanged, [this] { emit checkOk(); }); connect(ui.leFileName, &QLineEdit::textChanged, [this] { emit checkOk(); }); connect(ui.cbSubcategory, &QComboBox::currentTextChanged, [this] { emit checkOk(); }); connect(ui.cbCollection, &QComboBox::currentTextChanged, this, &DatasetMetadataManagerWidget::updateCategories); connect(ui.cbCategory, &QComboBox::currentTextChanged, this, &DatasetMetadataManagerWidget::updateSubcategories); connect(ui.bNewColumn, &QPushButton::clicked, this, &DatasetMetadataManagerWidget::addColumnDescription); connect(ui.bDelete, &QPushButton::clicked, this, &DatasetMetadataManagerWidget::removeColumnDescription); loadSettings(); } DatasetMetadataManagerWidget::~DatasetMetadataManagerWidget() { KConfigGroup conf(KSharedConfig::openConfig(), "DatasetMetadataManagerWidget"); //filter settings conf.writeEntry("separator", ui.cbSeparatingCharacter->currentText()); conf.writeEntry("commentChar", ui.cbCommentCharacter->currentText()); conf.writeEntry("numberFormat", ui.cbNumberFormat->currentIndex()); conf.writeEntry("dateTimeFormat", ui.cbDateTimeFormat->currentText()); conf.writeEntry("createIndexColumn", ui.chbCreateIndex->isChecked()); conf.writeEntry("skipEmptyParts", ui.chbSkipEmptyParts->isChecked()); conf.writeEntry("simplifyWhitespaces", ui.chbSimplifyWhitespaces->isChecked()); conf.writeEntry("removeQuotes", ui.chbRemoveQuotes->isChecked()); conf.writeEntry("useFirstRowForVectorName", ui.chbHeader->isChecked()); } /** * @brief Loads the settings of the widget. */ void DatasetMetadataManagerWidget::loadSettings() { KConfigGroup conf(KSharedConfig::openConfig(), "DatasetMetadataManagerWidget"); ui.cbCommentCharacter->setCurrentItem(conf.readEntry("commentChar", "#")); ui.cbSeparatingCharacter->setCurrentItem(conf.readEntry("separator", "auto")); - ui.cbNumberFormat->setCurrentIndex(conf.readEntry("numberFormat", (int)QLocale::AnyLanguage)); + ui.cbNumberFormat->setCurrentIndex(conf.readEntry("numberFormat", static_cast(QLocale::AnyLanguage))); ui.cbDateTimeFormat->setCurrentItem(conf.readEntry("dateTimeFormat", "yyyy-MM-dd hh:mm:ss.zzz")); ui.chbCreateIndex->setChecked(conf.readEntry("createIndexColumn", false)); ui.chbSimplifyWhitespaces->setChecked(conf.readEntry("simplifyWhitespaces", true)); ui.chbRemoveQuotes->setChecked(conf.readEntry("removeQuotes", false)); ui.chbSkipEmptyParts->setChecked(conf.readEntry("skipEmptyParts", false)); ui.chbHeader->setChecked(conf.readEntry("useFirstRowForVectorName", true)); } /** * @brief Checks whether leFileName contains a valid file name. */ bool DatasetMetadataManagerWidget::checkFileName() { const QString fileName = ui.leFileName->text(); //check whether it contains only digits, letters, -, _ or not const QRegularExpression re("^[\\w\\d-]+$"); const QRegularExpressionMatch match = re.match(fileName); bool hasMatch = match.hasMatch(); if(!hasMatch || fileName.isEmpty()) { qDebug("File name invalid"); QPalette palette; palette.setColor(QPalette::Base, Qt::red); palette.setColor(QPalette::Text, Qt::black); ui.leFileName->setPalette(palette); ui.leFileName->setToolTip("Invalid name for a file (it can contain:digits, letters, -, _)"); } else { QPalette palette; palette.setColor(QPalette::Base, m_baseColor); palette.setColor(QPalette::Text, m_textColor); ui.leFileName->setPalette(palette); ui.leFileName->setToolTip(""); } //check whether there already is a file named like this or not. bool found = false; if(m_datasetModel->allDatasetsList().toStringList().contains(fileName)) { qDebug("There already is a metadata file with this name"); QPalette palette; palette.setColor(QPalette::Base, Qt::red); palette.setColor(QPalette::Text, Qt::black); ui.leFileName->setPalette(palette); ui.leFileName->setToolTip("There already is a dataset metadata file with this name!"); found = true; } else { if(hasMatch) { QPalette palette; palette.setColor(QPalette::Base, m_baseColor); palette.setColor(QPalette::Text, m_textColor); ui.leFileName->setPalette(palette); ui.leFileName->setToolTip(""); } } return hasMatch && !found; } /** * @brief Checks whether leDownloadURL contains a valid URL. */ bool DatasetMetadataManagerWidget::urlExists() { //Check whether the given url is acceptable syntactically const QRegularExpression re("^(?:http(s)?:\\/\\/)?[\\w.-]+(?:\\.[\\w\\.-]+)+[\\w\\-\\._~:/?#[\\]@!\\$&'\\(\\)\\*\\+,;=.]+$"); const QRegularExpressionMatch match = re.match(ui.leDownloadURL->text()); bool hasMatch = match.hasMatch(); const bool urlExists = hasMatch && !ui.leDownloadURL->text().isEmpty(); if(!urlExists){ QPalette palette; palette.setColor(QPalette::Base, Qt::red); palette.setColor(QPalette::Text, Qt::black); ui.leDownloadURL->setPalette(palette); ui.leDownloadURL->setToolTip("The URL is invalid!"); } else { QPalette palette; palette.setColor(QPalette::Base, m_baseColor); palette.setColor(QPalette::Text, m_textColor);; ui.leDownloadURL->setPalette(palette); ui.leDownloadURL->setToolTip(""); } return urlExists; } /** * @brief Checks whether leDatasetName is empty or not. */ bool DatasetMetadataManagerWidget::checkDatasetName() { const bool longNameOk = !ui.leDatasetName->text().isEmpty(); if(!longNameOk) { QPalette palette; palette.setColor(QPalette::Base, Qt::red); palette.setColor(QPalette::Text, Qt::black); ui.leDatasetName->setPalette(palette); ui.leDatasetName->setToolTip("Please fill this out!"); } else { QPalette palette; palette.setColor(QPalette::Base, m_baseColor); palette.setColor(QPalette::Text, m_textColor); ui.leDatasetName->setPalette(palette); ui.leDatasetName->setToolTip(""); } return longNameOk; } /** * @brief Checks whether teDescription is empty or not. */ bool DatasetMetadataManagerWidget::checkDescription() { const bool descriptionOk = !ui.teDescription->toPlainText().isEmpty(); if(!descriptionOk) { QPalette palette; palette.setColor(QPalette::Base, Qt::red); palette.setColor(QPalette::Text, Qt::black); ui.teDescription->setPalette(palette); ui.teDescription->setToolTip("Please fill this out!"); } else { QPalette palette; palette.setColor(QPalette::Base, m_baseColor); palette.setColor(QPalette::Text, m_textColor); ui.teDescription->setPalette(palette); ui.teDescription->setToolTip(""); } return descriptionOk; } /** * @brief Checks whether the given QComboBox's current text is empty or not. */ bool DatasetMetadataManagerWidget::checkCategories(QComboBox* comboBox) { //Check whether it is a word or not (might contain digits) const QString fileName = comboBox->currentText(); const QRegularExpression re("^[\\w\\d]+$"); const QRegularExpressionMatch match = re.match(fileName); const bool hasMatch = match.hasMatch(); - qDebug() << hasMatch; if(!hasMatch || fileName.isEmpty()) { - qDebug("categoty/subcategory name invalid"); + qDebug("category/subcategory name invalid"); QPalette palette; palette.setColor(QPalette::Base,Qt::red); palette.setColor(QPalette::Text,Qt::black); comboBox->setPalette(palette); comboBox->setToolTip("Invalid or empty name for a category/subcategory (only digits and letters)"); } else { - qDebug("categoty/subcategory name valid"); QPalette palette; palette.setColor(QPalette::Base, m_baseColor); palette.setColor(QPalette::Text, m_textColor); comboBox->setPalette(palette); comboBox->setToolTip(""); } return hasMatch; } /** * @brief Enables/disables the widget's components meant to configure the metadata file of the new dataset. */ void DatasetMetadataManagerWidget::enableDatasetSettings(bool enable) { ui.leFileName->setEnabled(enable); ui.leFileName->setReadOnly(!enable); ui.leDatasetName->setEnabled(enable); ui.leDatasetName->setReadOnly(!enable); ui.leDownloadURL->setEnabled(enable); ui.leDownloadURL->setReadOnly(!enable); ui.teDescription->setEnabled(enable); ui.teDescription->setReadOnly(!enable); ui.gbColumnDescriptions->setEnabled(enable); ui.gbFilter->setEnabled(enable); } /** * @brief Checks whether the introduced data is valid or not. Used by DatasetMetadataManagerDialog. */ bool DatasetMetadataManagerWidget::checkDataValidity() { const bool fileNameOK = checkFileName(); const bool urlOk = urlExists(); const bool longNameOk = checkDatasetName(); const bool descriptionOk = checkDescription(); const bool categoryOk = checkCategories(ui.cbCategory); const bool subcategoryOk = checkCategories(ui.cbSubcategory); const bool collectionOk = checkCategories(ui.cbCollection); enableDatasetSettings(categoryOk && subcategoryOk && collectionOk); return fileNameOK && urlOk && longNameOk && descriptionOk && subcategoryOk && categoryOk && collectionOk; } /** * @brief Updates content of cbCategory based on current collection. */ void DatasetMetadataManagerWidget::updateCategories(const QString& collection) { ui.cbCategory->clear(); if( m_datasetModel->collections().contains(collection)) ui.cbCategory->addItems(m_datasetModel->categories(collection)); emit checkOk(); } /** * @brief Updates content of cbSubcategory based on current category. */ void DatasetMetadataManagerWidget::updateSubcategories(const QString& category) { ui.cbSubcategory->clear(); const QString collection = ui.cbCollection->currentText(); if( m_datasetModel->categories(collection).contains(category)) ui.cbSubcategory->addItems(m_datasetModel->subcategories(collection, category)); emit checkOk(); } /** * @brief Updates the metadata file containing the categories, subcategories and datasets. * @param fileName the name of the metadata file (path) */ void DatasetMetadataManagerWidget::updateDocument(const QString& dirPath) { - //Check whether the current collection already exists, if yes update it - if(m_datasetModel->collections().contains(ui.cbCollection->currentText())) { - QString fileName = dirPath + ui.cbCollection->currentText() + ".json"; - qDebug() << "Updating: " << fileName; - QFile file(fileName); - if (file.open(QIODevice::ReadWrite)) { - QJsonDocument document = QJsonDocument::fromJson(file.readAll()); - QJsonObject rootObject = document.object(); - QJsonValueRef categoryArrayRef = rootObject.find("categories").value(); - QJsonArray categoryArray = categoryArrayRef.toArray(); - - //Check whether the current category already exists - bool foundCategory = false; - for(int i = 0 ; i < categoryArray.size(); ++i) { - QJsonValueRef categoryRef = categoryArray[i]; - QJsonObject currentCategory = categoryRef.toObject(); - QString categoryName = currentCategory.value("category_name").toString(); - qDebug() << "Category name: " << categoryName; - - //If we find the category we have to update that QJsonObject - if(categoryName.compare(ui.cbCategory->currentText()) == 0) { - foundCategory = true; - QJsonValueRef subcategoryArrayRef = currentCategory.find("subcategories").value(); - QJsonArray subcategoryArray = subcategoryArrayRef.toArray(); - qDebug() << "subcategoryArray: " << subcategoryArray.toVariantList(); - - //Check whether the current subcategory already exists - bool subcategoryFound = false; - for(int j = 0; j < subcategoryArray.size(); ++j) { - QJsonValueRef subcategoryRef = subcategoryArray[j]; - QJsonObject currentSubcategory = subcategoryRef.toObject(); - QString subcategoryName = currentSubcategory.value("subcategory_name").toString(); - qDebug() << "Subcat name: " << subcategoryName; - - //If we find the subcategory we have to update that QJsonObject - if(subcategoryName.compare(ui.cbSubcategory->currentText()) == 0) { - subcategoryFound = true; - QJsonValueRef datasetsRef = currentSubcategory.find("datasets").value(); - QJsonArray datasets = datasetsRef.toArray(); - qDebug() <<"Datasets content: " << datasets.toVariantList(); + if(checkDataValidity()) { + //Check whether the current collection already exists, if yes update it + if(m_datasetModel->collections().contains(ui.cbCollection->currentText())) { + QString fileName = dirPath + ui.cbCollection->currentText() + ".json"; + qDebug() << "Updating: " << fileName; + QFile file(fileName); + if (file.open(QIODevice::ReadWrite)) { + QJsonDocument document = QJsonDocument::fromJson(file.readAll()); + QJsonObject rootObject = document.object(); + QJsonValueRef categoryArrayRef = rootObject.find("categories").value(); + QJsonArray categoryArray = categoryArrayRef.toArray(); + + //Check whether the current category already exists + bool foundCategory = false; + for(int i = 0 ; i < categoryArray.size(); ++i) { + QJsonValueRef categoryRef = categoryArray[i]; + QJsonObject currentCategory = categoryRef.toObject(); + QString categoryName = currentCategory.value("category_name").toString(); + + //If we find the category we have to update that QJsonObject + if(categoryName.compare(ui.cbCategory->currentText()) == 0) { + foundCategory = true; + QJsonValueRef subcategoryArrayRef = currentCategory.find("subcategories").value(); + QJsonArray subcategoryArray = subcategoryArrayRef.toArray(); + + //Check whether the current subcategory already exists + bool subcategoryFound = false; + for(int j = 0; j < subcategoryArray.size(); ++j) { + QJsonValueRef subcategoryRef = subcategoryArray[j]; + QJsonObject currentSubcategory = subcategoryRef.toObject(); + QString subcategoryName = currentSubcategory.value("subcategory_name").toString(); + + //If we find the subcategory we have to update that QJsonObject + if(subcategoryName.compare(ui.cbSubcategory->currentText()) == 0) { + subcategoryFound = true; + QJsonValueRef datasetsRef = currentSubcategory.find("datasets").value(); + QJsonArray datasets = datasetsRef.toArray(); + + datasets.append(createDatasetObject()); + datasetsRef = datasets; + + subcategoryRef = currentSubcategory; + subcategoryArrayRef = subcategoryArray; + categoryRef = currentCategory; + categoryArrayRef = categoryArray; + document.setObject(rootObject); + break; + } + } + + //If we didn't find the subcategory, we have to create it + if(!subcategoryFound) { + qDebug() << "Subcategory not found"; + QJsonObject newSubcategory; + newSubcategory.insert("subcategory_name", ui.cbSubcategory->currentText()); + QJsonArray datasets; datasets.append(createDatasetObject()); - datasetsRef = datasets; - subcategoryRef = currentSubcategory; + newSubcategory.insert("datasets", datasets); + subcategoryArray.append(newSubcategory); + subcategoryArrayRef = subcategoryArray; categoryRef = currentCategory; categoryArrayRef = categoryArray; document.setObject(rootObject); - break; } + break; } - - //If we didn't find the subcategory, we have to create it - if(!subcategoryFound) { - qDebug() << "Subcat not found"; - QJsonObject newSubcategory; - newSubcategory.insert("subcategory_name", ui.cbSubcategory->currentText()); - - QJsonArray datasets; - datasets.append(createDatasetObject()); - - newSubcategory.insert("datasets", datasets); - subcategoryArray.append(newSubcategory); - - subcategoryArrayRef = subcategoryArray; - categoryRef = currentCategory; - categoryArrayRef = categoryArray; - document.setObject(rootObject); - } - break; } - } - //If we didn't find the category, we have to create it - if(!foundCategory) { - qDebug() << "Cat not found"; - QJsonObject newCategory; - newCategory.insert("category_name", ui.cbCategory->currentText()); + //If we didn't find the category, we have to create it + if(!foundCategory) { + qDebug() << "Category not found"; + QJsonObject newCategory; + newCategory.insert("category_name", ui.cbCategory->currentText()); - QJsonArray subcategoryArray; + QJsonArray subcategoryArray; - QJsonObject newSubcategory; - newSubcategory.insert("subcategory_name", ui.cbSubcategory->currentText()); + QJsonObject newSubcategory; + newSubcategory.insert("subcategory_name", ui.cbSubcategory->currentText()); - QJsonArray datasets; - datasets.append(createDatasetObject()); - newSubcategory.insert("datasets", datasets); + QJsonArray datasets; + datasets.append(createDatasetObject()); + newSubcategory.insert("datasets", datasets); - subcategoryArray.append(newSubcategory); - newCategory.insert("subcategories", subcategoryArray); + subcategoryArray.append(newSubcategory); + newCategory.insert("subcategories", subcategoryArray); - categoryArray.append(newCategory); - categoryArrayRef = categoryArray; - document.setObject(rootObject); + categoryArray.append(newCategory); + categoryArrayRef = categoryArray; + document.setObject(rootObject); + } + file.close(); + file.open(QIODevice::ReadWrite | QIODevice::Truncate); + file.write(document.toJson()); + file.close(); + } else { + qDebug() << "Couldn't open dataset category file, because " << file.errorString(); } - qDebug() <collections()) - collectionArray.append(collection); - - collectionArray.append(ui.cbCollection->currentText()); - - QJsonDocument newDocument; - newDocument.setArray(collectionArray); - file.write(newDocument.toJson()); - file.close(); } + //If the collection doesn't exist we have to create a new json file for it. + else { + QString fileName = dirPath + "DatasetCollections.json"; + qDebug() << "creating: " << fileName; + QFile file(fileName); + if (file.open(QIODevice::ReadWrite | QIODevice::Truncate | QIODevice::Text)) { + QJsonArray collectionArray; + + for(QString collection : m_datasetModel->collections()) + collectionArray.append(collection); + + collectionArray.append(ui.cbCollection->currentText()); + + QJsonDocument newDocument; + newDocument.setArray(collectionArray); + file.write(newDocument.toJson()); + file.close(); + } - QJsonObject rootObject; + QJsonObject rootObject; - rootObject.insert("collection_name", ui.cbCollection->currentText()); + rootObject.insert("collection_name", ui.cbCollection->currentText()); - QJsonArray categoryArray; - QJsonObject newCategory; - newCategory.insert("category_name", ui.cbCategory->currentText()); + QJsonArray categoryArray; + QJsonObject newCategory; + newCategory.insert("category_name", ui.cbCategory->currentText()); - QJsonArray subcategoryArray; + QJsonArray subcategoryArray; - QJsonObject newSubcategory; - newSubcategory.insert("subcategory_name", ui.cbSubcategory->currentText()); + QJsonObject newSubcategory; + newSubcategory.insert("subcategory_name", ui.cbSubcategory->currentText()); - QJsonArray datasets; - datasets.append(createDatasetObject()); - newSubcategory.insert("datasets", datasets); + QJsonArray datasets; + datasets.append(createDatasetObject()); + newSubcategory.insert("datasets", datasets); - subcategoryArray.append(newSubcategory); - newCategory.insert("subcategories", subcategoryArray); - categoryArray.append(newCategory); - rootObject.insert("categories", categoryArray); + subcategoryArray.append(newSubcategory); + newCategory.insert("subcategories", subcategoryArray); + categoryArray.append(newCategory); + rootObject.insert("categories", categoryArray); - QJsonDocument document; - document.setObject(rootObject); - qDebug() <currentText() + ".json"); - if (collectionFile.open(QIODevice::WriteOnly | QIODevice::Truncate)) { - collectionFile.write(document.toJson()); - collectionFile.close(); + QFile collectionFile(dirPath + ui.cbCollection->currentText() + ".json"); + if (collectionFile.open(QIODevice::WriteOnly | QIODevice::Truncate)) { + collectionFile.write(document.toJson()); + collectionFile.close(); + } } } } /** * @brief Creates and returns a QJsonObject based on the given settings of the widget, this will be part of the collection's metadata file */ QJsonObject DatasetMetadataManagerWidget::createDatasetObject() { QJsonObject rootObject; rootObject.insert("filename", ui.leFileName->text()); rootObject.insert("name", ui.leDatasetName->text()); rootObject.insert("download", ui.leDownloadURL->text()); rootObject.insert("description", ui.teDescription->toPlainText()); rootObject.insert("separator", ui.cbSeparatingCharacter->currentText()); rootObject.insert("comment_character", ui.cbCommentCharacter->currentText()); rootObject.insert("DateTime_format", ui.cbDateTimeFormat->currentText()); rootObject.insert("number_format", ui.cbNumberFormat->currentIndex()); rootObject.insert("create_index_column", ui.chbCreateIndex->isChecked()); rootObject.insert("skip_empty_parts", ui.chbSkipEmptyParts->isChecked()); rootObject.insert("simplify_whitespaces", ui.chbSimplifyWhitespaces->isChecked()); rootObject.insert("remove_quotes", ui.chbRemoveQuotes->isChecked()); rootObject.insert("use_first_row_for_vectorname", ui.chbHeader->isChecked()); for(int i = 0; i < m_columnDescriptions.size(); ++i) rootObject.insert(i18n("column_description_%1", i), m_columnDescriptions[i]); return rootObject; } /** * @brief Adds a new QLineEdit so the user can set a new column description. */ void DatasetMetadataManagerWidget::addColumnDescription() { QLabel* label = new QLabel(); label->setText(i18n("Description for column %1", m_columnDescriptions.size() + 1)); QLineEdit* lineEdit = new QLineEdit; int layoutIndex = m_columnDescriptions.size() + 1; - qDebug() << "Layout index " << layoutIndex; ui.columnLayout->addWidget(label, layoutIndex, 0); ui.columnLayout->addWidget(lineEdit, layoutIndex, 1, 1, -1); connect(lineEdit, &QLineEdit::textChanged, [this, layoutIndex] (const QString& text) { m_columnDescriptions[layoutIndex - 1] = text; - qDebug() << m_columnDescriptions; }); m_columnDescriptions.append(""); } /** * @brief Removes the lastly added QLineEdit (used to set a column description). */ void DatasetMetadataManagerWidget::removeColumnDescription() { const int index = ui.columnLayout->count() - 1; QLayoutItem *item; if ((item = ui.columnLayout->takeAt(index)) != nullptr) { delete item->widget(); delete item; } if ((item = ui.columnLayout->takeAt(index - 1)) != nullptr){ delete item->widget(); delete item; } m_columnDescriptions.removeLast(); } /** * @brief returns the path to the new metadata file of the new dataset. */ QString DatasetMetadataManagerWidget::getMetadataFilePath() const { return m_metadataFilePath; } + +/** + * @brief Sets the collection name. + */ +void DatasetMetadataManagerWidget::setCollection(const QString& collection) { + ui.cbCollection->setCurrentText(collection); +} + +/** + * @brief Sets the category name. + */ +void DatasetMetadataManagerWidget::setCategory(const QString& category) { + ui.cbCategory->setCurrentText(category); +} + +/** + * @brief Sets the subcategory name. + */ +void DatasetMetadataManagerWidget::setSubcategory(const QString& subcategory) { + ui.cbSubcategory->setCurrentText(subcategory); +} + +/** + * @brief Sets the short name of the dataset. + */ +void DatasetMetadataManagerWidget::setShortName(const QString& name) { + ui.leFileName->setText(name); +} + +/** + * @brief Sets the full name of the dataset. + */ +void DatasetMetadataManagerWidget::setFullName(const QString& name) { + ui.leDatasetName->setText(name); +} + +/** + * @brief Sets the text of the description. + */ +void DatasetMetadataManagerWidget::setDescription(const QString& description) { + ui.teDescription->setText(description); +} + +/** + * @brief Sets the download url. + */ +void DatasetMetadataManagerWidget::setURL(const QString& url) { + ui.leDownloadURL->setText(url); +} diff --git a/src/kdefrontend/datasources/DatasetMetadataManagerWidget.h b/src/kdefrontend/datasources/DatasetMetadataManagerWidget.h index 1db182121..a99b5371a 100644 --- a/src/kdefrontend/datasources/DatasetMetadataManagerWidget.h +++ b/src/kdefrontend/datasources/DatasetMetadataManagerWidget.h @@ -1,76 +1,84 @@ /*************************************************************************** File : DatasetMetadataManagerWidget.h Project : LabPlot Description : widget for managing a metadata file of a dataset -------------------------------------------------------------------- Copyright : (C) 2019 Ferencz Kovacs (kferike98@gmail.com) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #ifndef DATASETMETADATAMANAGERWIDGET_H #define DATASETMETADATAMANAGERWIDGET_H #include "ui_datasetmetadatamanagerwidget.h" class DatasetModel; class DatasetMetadataManagerWidget : public QWidget { Q_OBJECT public: explicit DatasetMetadataManagerWidget(QWidget*, const QMap< QString, QMap>>>&); virtual ~DatasetMetadataManagerWidget() override; bool checkDataValidity(); void updateDocument(const QString& fileName); QString getMetadataFilePath() const; + void setCollection(const QString&); + void setCategory(const QString&); + void setSubcategory(const QString&); + void setShortName(const QString&); + void setFullName(const QString&); + void setDescription(const QString&); + void setURL(const QString&); + private: Ui::DatasetMetadataManagerWidget ui; DatasetModel* m_datasetModel; QStringList m_columnDescriptions; QString m_metadataFilePath; QString m_baseColor; QString m_textColor; void initCategories(const QMap>>&); void initSubcategories(const QMap>>&); void initDatasets(const QMap>>&); bool checkFileName(); bool urlExists(); bool checkDatasetName(); bool checkDescription(); bool checkCategories(QComboBox*); void loadSettings(); void enableDatasetSettings(bool); QJsonObject createDatasetObject(); private slots: void updateCategories(const QString&); void updateSubcategories(const QString&); void addColumnDescription(); void removeColumnDescription(); signals: void checkOk(); }; #endif // DATASETMETADATAMANAGERWIDGET_H diff --git a/src/kdefrontend/datasources/ImportDatasetWidget.cpp b/src/kdefrontend/datasources/ImportDatasetWidget.cpp index 2904dcaef..7996c8487 100644 --- a/src/kdefrontend/datasources/ImportDatasetWidget.cpp +++ b/src/kdefrontend/datasources/ImportDatasetWidget.cpp @@ -1,833 +1,838 @@ /*************************************************************************** File : ImportDatasetWidget.cpp Project : LabPlot Description : import online dataset widget -------------------------------------------------------------------- Copyright : (C) 2019 Kovacs Ferencz (kferike98@gmail.com) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "src/backend/datasources/DatasetHandler.h" #include "src/kdefrontend/datasources/ImportDatasetWidget.h" #include "src/kdefrontend/datasources/DatasetMetadataManagerDialog.h" #include "src/kdefrontend/DatasetModel.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /*! \class ImportDatasetWidget \brief Widget for importing data from a dataset. \ingroup kdefrontend */ ImportDatasetWidget::ImportDatasetWidget(QWidget* parent) : QWidget(parent), m_categoryCompleter(new QCompleter), m_datasetCompleter(new QCompleter), m_loadingCategories(false) { const QString baseDir = QStandardPaths::standardLocations(QStandardPaths::AppDataLocation).first(); QString containingDir = "labplot_data"; m_jsonDir = baseDir + QDir::separator() + containingDir + QDir::separator(); ui.setupUi(this); if(!QFile(m_jsonDir + "DatasetCollections.json").exists()) downloadCollectionsFile(); loadDatasetCategoriesFromJson(); ui.lwDatasets->setSelectionMode(QAbstractItemView::SingleSelection); ui.twCategories->setSelectionMode(QAbstractItemView::SingleSelection); ui.lDescription->setWordWrap(true); ui.lFullName->setWordWrap(true); showDetails(m_showDetails); connect(ui.cbCollections, &QComboBox::currentTextChanged, this, &ImportDatasetWidget::updateCategoryTree); connect(ui.twCategories, &QTreeWidget::itemDoubleClicked, this, &ImportDatasetWidget::listDatasetsForSubcategory); connect(ui.twCategories, &QTreeWidget::itemSelectionChanged, [this] { if(!m_loadingCategories) listDatasetsForSubcategory(ui.twCategories->selectedItems().first()); }); connect(ui.leSearchDatasets, &QLineEdit::textChanged, this, &ImportDatasetWidget::scrollToDatasetListItem); connect(ui.bClearCache, &QPushButton::clicked, this, &ImportDatasetWidget::clearCache); connect(ui.leSearchCategories, &QLineEdit::textChanged, this, &ImportDatasetWidget::scrollToCategoryTreeItem); connect(ui.bRefresh, &QPushButton::clicked, this, &ImportDatasetWidget::refreshCategories); connect(ui.bNewDataset, &QPushButton::clicked, this, &ImportDatasetWidget::showDatasetMetadataManager); connect(ui.lwDatasets, &QListWidget::itemSelectionChanged, [this]() { emit datasetSelected(); if(m_showDetails) updateDetails(); }); connect(ui.lwDatasets, &QListWidget::doubleClicked, [this]() { emit datasetDoubleClicked(); }); connect(ui.bShowDetails, &QPushButton::clicked, [this]() { m_showDetails = !m_showDetails; if(m_showDetails) { ui.bShowDetails->setText("Hide details"); } else { ui.bShowDetails->setText("Show details"); } showDetails(m_showDetails); }); } ImportDatasetWidget::~ImportDatasetWidget() { if(m_categoryCompleter != nullptr) delete m_categoryCompleter; if(m_datasetCompleter != nullptr) delete m_datasetCompleter; //TODO: Save selected state } /** * @brief Locates in the file system the json metadata file that contains the list of categories and subcategories. * @return The location of the file */ QString ImportDatasetWidget::locateCategoryJsonFile() const { qDebug() << "Locating category file" << QStandardPaths::locate(QStandardPaths::AppDataLocation, "datasets/DatasetCategories.json"); return QStandardPaths::locate(QStandardPaths::AppDataLocation, "datasets/DatasetCategories.json"); } /** * @brief Processes the json metadata file that contains the list of categories and subcategories and their datasets. */ void ImportDatasetWidget::loadDatasetCategoriesFromJson() { QString filePath = m_jsonDir + "DatasetCollections.json"; QFile file(filePath); if (file.open(QIODevice::ReadOnly)) { m_datasetsMap.clear(); ui.cbCollections->clear(); QJsonDocument document = QJsonDocument::fromJson(file.readAll()); QJsonArray collections; if(document.isArray()) collections = document.array(); else { qDebug()<< "The DatasetCollections.json file is invalid"; return; } for (int collectionIndex = 0; collectionIndex < collections.size(); collectionIndex++) { const QString currentCollection = collections[collectionIndex].toString(); if(!QFile(m_jsonDir + currentCollection + ".json").exists()) downloadCollectionFile(currentCollection + ".json"); QFile collectionFile(m_jsonDir + currentCollection + ".json"); if (collectionFile.open(QIODevice::ReadOnly)) { QJsonDocument collectionDocument = QJsonDocument::fromJson(collectionFile.readAll()); QJsonObject collectionObject; if(collectionDocument.isObject()) { collectionObject = collectionDocument.object(); } else { qDebug()<< "The " + currentCollection + ".json file is invalid"; return; } if(collectionObject.value("collection_name").toString().compare(currentCollection) != 0) { qDebug()<< "The " + currentCollection + ".json file name is invalid"; return; } QJsonArray categoryArray = collectionObject.value("categories").toArray(); //processing categories for(int i = 0 ; i < categoryArray.size(); ++i) { const QJsonObject currentCategory = categoryArray[i].toObject(); const QString categoryName = currentCategory.value("category_name").toString(); const QJsonArray subcategories = currentCategory.value("subcategories").toArray(); //processing subcategories for(int j = 0; j < subcategories.size(); ++j) { QJsonObject currentSubCategory = subcategories[j].toObject(); QString subcategoryName = currentSubCategory.value("subcategory_name").toString(); const QJsonArray datasetArray = currentSubCategory.value("datasets").toArray(); //processing the datasets o the actual subcategory for (const auto& dataset : datasetArray) { m_datasetsMap[currentCollection][categoryName][subcategoryName].push_back(dataset.toObject().value("filename").toString()); } } } } } if(m_datasetModel != nullptr) delete m_datasetModel; m_datasetModel = new DatasetModel(m_datasetsMap); //Fill up collections combo box ui.cbCollections->addItem(QString("All (" + QString::number(m_datasetModel->allDatasetsList().toStringList().size()) + ")")); for(QString collection : m_datasetModel->collections()) ui.cbCollections->addItem(collection + " (" + QString::number(m_datasetModel->datasetCount(collection)) + ")"); updateCategoryTree(ui.cbCollections->currentText()); restoreSelectedSubcategory(ui.cbCollections->currentText()); file.close(); } else { qDebug("Couldn't open dataset collections file"); } } /** * @brief Returns the valid collection name based on given collection name (containing the count of datasets of the given collection) */ QString ImportDatasetWidget::validCollectionName(const QString &collection) { int index = collection.lastIndexOf(" ("); QString collectionName = collection.left(index); return collectionName; } /** * @brief Updates/fills ui.twCategories based on the selected collection. */ void ImportDatasetWidget::updateCategoryTree(const QString& collectionName) { QString collection = validCollectionName(collectionName); m_loadingCategories = true; ui.lwDatasets->clear(); ui.twCategories->clear(); QStringList categories = (collection.compare("All") == 0) ? m_datasetModel->allCategories().toStringList() : m_datasetModel->categories(collection); //Go through every category that was previously processed. for(auto category : categories) { QTreeWidgetItem* const currentCategoryItem = new QTreeWidgetItem(QStringList(category)); ui.twCategories->addTopLevelItem(currentCategoryItem); QStringList subcategories = (collection.compare("All") == 0) ? m_datasetModel->allSubcategories(category).toStringList() : m_datasetModel->subcategories(collection, category); //Go through every subcategory of the current category, that was previously processed. for(auto subcategory : subcategories) { currentCategoryItem->addChild(new QTreeWidgetItem(QStringList(subcategory))); } } if(m_selectedCollection.compare(collection) == 0) { restoreSelectedSubcategory(ui.cbCollections->currentText()); } else { m_selectedCollection = collection; m_selectedCategory = ""; m_selectedSubcategory = ""; } m_loadingCategories = false; updateCategoryCompleter(); } /** * @brief Restores the lastly selected collection, category and subcategory making it the selected QTreeWidgetItem and also lists the datasets belonigng to it */ void ImportDatasetWidget::restoreSelectedSubcategory(const QString& collectionName) { QString collection = validCollectionName(collectionName); ui.cbCollections->setCurrentText(collection); if(m_datasetModel->categories(collection).contains(m_selectedCategory)) { const QTreeWidgetItem* const categoryItem = ui.twCategories->findItems(m_selectedCategory, Qt::MatchExactly).first(); if(m_datasetModel->subcategories(collection, m_selectedCategory).contains(m_selectedSubcategory)) { for(int i = 0; i < categoryItem->childCount(); ++i) { if(categoryItem->child(i)->text(0).compare(m_selectedSubcategory) == 0) { QTreeWidgetItem* const subcategoryItem = categoryItem->child(i); ui.twCategories->setCurrentItem(subcategoryItem); subcategoryItem->setSelected(true); m_selectedSubcategory.clear(); listDatasetsForSubcategory(subcategoryItem); break; } } } } } /** * @brief Populates lwDatasets with the datasets of the selected subcategory. * @param item the selected subcategory */ void ImportDatasetWidget::listDatasetsForSubcategory(QTreeWidgetItem* item) { if(item->childCount() == 0) { if(m_selectedSubcategory.compare(item->text(0)) != 0) { m_selectedSubcategory = item->text(0); m_selectedCategory = item->parent()->text(0); QString categoryName = item->parent()->text(0); ui.lwDatasets->clear(); for(QString dataset : m_datasetModel->datasets(m_selectedCollection, categoryName, m_selectedSubcategory)) { ui.lwDatasets->addItem(new QListWidgetItem(dataset)); } updateDatasetCompleter(); highlightLocalMetadataFiles(); } } else { if(item->text(0).compare(m_selectedCategory) != 0) { m_selectedCategory = item->text(0); m_selectedSubcategory = ""; ui.lwDatasets->clear(); item->setExpanded(true); } } } /** * @brief Updates the completer used for searching among datasets. */ void ImportDatasetWidget::updateDatasetCompleter() { QStringList datasetList; for(int i = 0; i count(); ++i) { datasetList.append(ui.lwDatasets->item(i)->text()); } if(!datasetList.isEmpty()) { if(m_datasetCompleter != nullptr) delete m_datasetCompleter; m_datasetCompleter = new QCompleter(datasetList); m_datasetCompleter->setCompletionMode(QCompleter::PopupCompletion); m_datasetCompleter->setCaseSensitivity(Qt::CaseSensitive); ui.leSearchDatasets->setCompleter(m_datasetCompleter); } else ui.leSearchDatasets->setCompleter(nullptr); } /** * @brief Updates the completer used for searching among categories and subcategories. */ void ImportDatasetWidget::updateCategoryCompleter() { QStringList categoryList; for (int i = 0; i < ui.twCategories->topLevelItemCount(); ++i) { categoryList.append(ui.twCategories->topLevelItem(i)->text(0)); for(int j = 0; j < ui.twCategories->topLevelItem(i)->childCount(); ++j) { categoryList.append(ui.twCategories->topLevelItem(i)->text(0) + QLatin1Char(':') + ui.twCategories->topLevelItem(i)->child(j)->text(0)); } } if(!categoryList.isEmpty()) { if(m_categoryCompleter != nullptr) delete m_categoryCompleter; m_categoryCompleter = new QCompleter(categoryList); m_categoryCompleter->setCompletionMode(QCompleter::PopupCompletion); m_categoryCompleter->setCaseSensitivity(Qt::CaseSensitive); ui.leSearchCategories->setCompleter(m_categoryCompleter); } else ui.leSearchCategories->setCompleter(nullptr); } /** * @brief Scrolls the twCategories to the given category or subcategory * @param rootName the name of the category or category+subcategory */ void ImportDatasetWidget::scrollToCategoryTreeItem(const QString& rootName) { int topItemIdx = -1; for (int i = 0; i < ui.twCategories->topLevelItemCount(); ++i) if (rootName.startsWith(ui.twCategories->topLevelItem(i)->text(0))) { topItemIdx = i; break; } if (topItemIdx >= 0) { if(!rootName.contains(QLatin1Char(':'))) { ui.twCategories->scrollToItem(ui.twCategories->topLevelItem(topItemIdx), QAbstractItemView::ScrollHint::PositionAtTop); } else { int childIdx = -1; for(int j = 0; j < ui.twCategories->topLevelItem(topItemIdx)->childCount(); ++j) { if(rootName.endsWith(ui.twCategories->topLevelItem(topItemIdx)->child(j)->text(0))) { childIdx = j; break; } } if(childIdx >= 0) { ui.twCategories->scrollToItem(ui.twCategories->topLevelItem(topItemIdx)->child(childIdx), QAbstractItemView::ScrollHint::PositionAtTop); } else { ui.twCategories->scrollToItem(ui.twCategories->topLevelItem(topItemIdx), QAbstractItemView::ScrollHint::PositionAtTop); } } } } /** * @brief Scrolls the lwDatasets to the given dataset name. * @param rootName the name of the dataset */ void ImportDatasetWidget::scrollToDatasetListItem(const QString& rootName) { int itemIdx = -1; for (int i = 0; i < ui.lwDatasets->count(); ++i) if (ui.lwDatasets->item(i)->text() == rootName) { itemIdx = i; break; } if (itemIdx >= 0) ui.lwDatasets->scrollToItem(ui.lwDatasets->item(itemIdx), QAbstractItemView::ScrollHint::PositionAtTop); } /** * @brief Returns the name of the selected dataset */ QString ImportDatasetWidget::getSelectedDataset() const { if (ui.lwDatasets->selectedItems().count() > 0) return ui.lwDatasets->selectedItems().at(0)->text(); else return QString(); } /** * @brief Initiates the processing of the dataset's metadata file and of the dataset itself. * @param datasetHandler the DatasetHanlder that downloads processes the dataset */ void ImportDatasetWidget::loadDatasetToProcess(DatasetHandler* datasetHandler) { const QString fileName = getSelectedDataset() + QLatin1String(".json"); QString filePath = m_jsonDir; QJsonObject datasetObject = loadDatasetObject(); if(!datasetObject.isEmpty()) { datasetHandler->processMetadata(datasetObject, filePath); } else { QMessageBox::critical(this, i18n("Invalid metadata file"), i18n("The metadata file for the choosen dataset isn't valid")); } } /** * @brief Returns the QJsonObject associated with the currently selected dataset. */ QJsonObject ImportDatasetWidget::loadDatasetObject() { QString filePath = m_jsonDir + "DatasetCollections.json"; QFile file(filePath); bool allCollections = (m_selectedCollection.compare("All") == 0); if (file.open(QIODevice::ReadOnly)) { QJsonDocument document = QJsonDocument::fromJson(file.readAll()); QJsonArray collections; if(document.isArray()) collections = document.array(); else { qDebug()<< "The DatasetCollections.json file is invalid"; return QJsonObject(); } for (int collectionIndex = 0; collectionIndex < collections.size(); collectionIndex++) { const QString currentCollection = collections[collectionIndex].toString(); //we have to find the selected collection in the metadata file. if(currentCollection.compare(m_selectedCollection) == 0 || allCollections) { QFile collectionFile(m_jsonDir + currentCollection + ".json"); //open the metadata file of the current collection if (collectionFile.open(QIODevice::ReadOnly)) { QJsonDocument collectionDocument = QJsonDocument::fromJson(collectionFile.readAll()); QJsonObject collectionObject; if(collectionDocument.isObject()) { collectionObject = collectionDocument.object(); } else { qDebug()<< "The " + currentCollection + ".json file is invalid"; return QJsonObject(); } if(collectionObject.value("collection_name").toString().compare(currentCollection) != 0) { qDebug()<< "The " + currentCollection + ".json file's name is invalid"; return QJsonObject(); } QJsonArray categoryArray = collectionObject.value("categories").toArray(); //processing categories for(int i = 0 ; i < categoryArray.size(); ++i) { const QJsonObject currentCategory = categoryArray[i].toObject(); const QString categoryName = currentCategory.value("category_name").toString(); if(categoryName.compare(m_selectedCategory) == 0) { const QJsonArray subcategories = currentCategory.value("subcategories").toArray(); //processing subcategories for(int j = 0; j < subcategories.size(); ++j) { QJsonObject currentSubCategory = subcategories[j].toObject(); QString subcategoryName = currentSubCategory.value("subcategory_name").toString(); if(subcategoryName.compare(m_selectedSubcategory) == 0) { const QJsonArray datasetArray = currentSubCategory.value("datasets").toArray(); //processing the datasets o the actual subcategory for (const auto& dataset : datasetArray) { if(getSelectedDataset().compare(dataset.toObject().value("filename").toString()) == 0) return dataset.toObject(); } } } } } } } } } return QJsonObject(); } /** * @brief Opens the DatasetMetadataManagerDialog when the user wants to add a new dataset. */ void ImportDatasetWidget::showDatasetMetadataManager() { DatasetMetadataManagerDialog* dlg = new DatasetMetadataManagerDialog(this, m_datasetsMap); if (dlg->exec() == QDialog::Accepted) { const QString pathToJson = m_jsonDir + QLatin1String("DatasetCategories.json"); const QString dirPath = QFileInfo(pathToJson).dir().absolutePath(); //update the metadata document dlg->updateDocument(m_jsonDir); //Not working due to problems with KNS3 library /*uploadCategoryFile(); uploadDatasetFile(dlg->getMetadataFilePath());*/ //process the changes made in the metadata files loadDatasetCategoriesFromJson(); } delete dlg; } /** * @brief Places the metadata file containing the categories and subcategories into a specific directory. */ void ImportDatasetWidget::downloadCollectionsFile() { const QString fileNameOld = QStandardPaths::locate(QStandardPaths::AppDataLocation, "datasets/DatasetCollections.json"); const QString fileNameNew =m_jsonDir + QLatin1String("DatasetCollections.json"); const QString parentDir = m_jsonDir.left(m_jsonDir.left(m_jsonDir.length() - 1).lastIndexOf(QDir::separator())); if(!QDir(m_jsonDir).exists()) { qDebug() << parentDir; QDir(parentDir).mkdir(QLatin1String("labplot_data")); } QFile::copy(fileNameOld, fileNameNew); } /** * @brief Places the metadata file of the given dataset into a specific directory. * @param datasetName the name of the dataset */ void ImportDatasetWidget::downloadCollectionFile(const QString& collectionName) { const QString fileNameOld = QStandardPaths::locate(QStandardPaths::AppDataLocation, QLatin1String("datasets") + QDir::separator() + collectionName); const QString fileNameNew =m_jsonDir + collectionName; QFile::copy(fileNameOld, fileNameNew); } /** * @brief Refreshes the categories, subcategories and datasets. */ void ImportDatasetWidget::refreshCategories() { QString fileNameNew = m_jsonDir + QLatin1String("DatasetCollections.json"); QFile existingCategoriesFile(fileNameNew); if(existingCategoriesFile.exists()) { //Delete old backup QFile oldBackup(m_jsonDir + QLatin1String("DatasetCollections_backup.json")); if(oldBackup.exists()) { oldBackup.remove(); } oldBackup.close(); //Create new backup if(!existingCategoriesFile.rename(m_jsonDir + QLatin1String("DatasetCollections_backup.json"))) qDebug() << " Couldn't create backup because " << existingCategoriesFile.errorString(); } //Obtain the new file downloadCollectionsFile(); QString filePath = m_jsonDir + "DatasetCollections.json"; QFile file(filePath); if (file.open(QIODevice::ReadOnly)) { m_datasetsMap.clear(); QJsonDocument document = QJsonDocument::fromJson(file.readAll()); QJsonArray collections; if(document.isArray()) collections = document.array(); else { qDebug()<< "The DatasetCollections.json file is invalid"; return; } //Go trough every collection's metadata file for (int collectionIndex = 0; collectionIndex < collections.size(); collectionIndex++) { const QString currentCollection = collections[collectionIndex].toString(); QFile existingCollectionFile(m_jsonDir + currentCollection + ".json"); //we copy the file to the data location if it doesn't exist if(!existingCollectionFile.exists()) { downloadCollectionFile(currentCollection + ".json"); } //otherwise we have to create a backup first else { QFile oldBackupCollection(m_jsonDir + currentCollection + "_backup.json"); if(oldBackupCollection.exists()) { oldBackupCollection.remove(); } oldBackupCollection.close(); if(!existingCollectionFile.rename(m_jsonDir + currentCollection + "_backup.json")) qDebug() << " Couldn't create backup because " << existingCollectionFile.errorString(); downloadCollectionFile(currentCollection + ".json"); } } } //process the "refreshed" files and update the widget accordingly loadDatasetCategoriesFromJson(); } /** * @brief Clears the content of the directory in which the download of metadata files was done. */ void ImportDatasetWidget::clearCache() { QDir dir(m_jsonDir); if(dir.exists()) { for(const auto& entry : dir.entryList()) { //delete every file that isn't potentially a metadata file if(!(entry.endsWith(QLatin1String(".json")) || entry.startsWith(QLatin1Char('.')))) { QFile deleteFile (m_jsonDir + entry); if(deleteFile.exists()) { deleteFile.remove(); } } } } else { qDebug("Couldn't clear cache, containing folder doesn't exist!"); } highlightLocalMetadataFiles(); } /** * @brief Highlights the name of the locally available metadata files in lwDatasets. */ void ImportDatasetWidget::highlightLocalMetadataFiles() { QDir dir(m_jsonDir); for(int i = 0 ; i < ui.lwDatasets->count(); ++i) { QListWidgetItem* const currentItem = ui.lwDatasets->item(i); bool found = false; for(QString entry : dir.entryList()) { if(entry.startsWith(currentItem->text()) && !entry.endsWith(".json")) { found = true; break; } } if(found) currentItem->setBackgroundColor(Qt::yellow); else currentItem->setBackgroundColor(Qt::white); } } /** * @brief TODO: uploads the metadata file that contains the categories to store.kde.org -- Library doesn't work for indefinite time. */ void ImportDatasetWidget::uploadCategoryFile() { /*KNS3::UploadDialog dialog("labplot2_datasets.knsrc", this); QFile file(m_jsonDir + "DatasetCategories.json"); qDebug() << "file " << m_jsonDir + "DatasetCategories.json "<< file.exists(); qDebug() << "file can be opened: " << file.open(QIODevice::ReadOnly) << " " << file.errorString(); file.close(); QUrl payloadFile ="file:" + m_jsonDir + "DatasetCategories.json"; QFile file2(payloadFile.toLocalFile()); qDebug() << "Local file: " << payloadFile.toLocalFile(); if (!file2.open(QIODevice::ReadOnly)) { qDebug() << i18n("File not found: %1 ", payloadFile.url()); } else { qDebug() << i18n("File found: %1 ", payloadFile.url()); } file2.close(); dialog.setUploadFile("file:" + m_jsonDir + "DatasetCategories.json"); qDebug("Upload file set!"); dialog.setUploadName("Dataset Categories"); qDebug() << "Upload name set: "; dialog.exec();*/ } /** * @brief TODO: uploads the metadata file of a dataset to store.kde.org -- Library doesn't work for indefinite time. */ void ImportDatasetWidget::uploadDatasetFile(const QString& filePath) { /*KNS3::UploadDialog dialog("labplot2_datasets.knsrc", this); QFile file(filePath); qDebug() << filePath + " " << file.exists(); qDebug() << "file can be opened: " << file.open(QIODevice::ReadOnly) << " " << file.errorString(); file.close(); QUrl payloadFile ="file:" + filePath; QFile file2(payloadFile.toLocalFile()); qDebug() << "Local file: " << payloadFile.toLocalFile(); if (!file2.open(QIODevice::ReadOnly)) { qDebug() << i18n("File not found: %1 ", payloadFile.url()); } else { qDebug() << i18n("File found: %1 ", payloadFile.url()); } file2.close(); dialog.setUploadFile("file:" + filePath); qDebug("Upload file set!"); dialog.setUploadName("Dataset Categories"); qDebug() << "Upload name set: "; dialog.exec();*/ } /** * @brief Returns the structure containing the categories, subcategories and datasets. * @return the structure containing the categories, subcategories and datasets */ const QMap< QString, QMap>>>& ImportDatasetWidget::getDatasetsMap() { return m_datasetsMap; } /** * @brief Sets the currently selected collection * @param category the name of the collection */ void ImportDatasetWidget::setCollection(const QString& collection) { ui.cbCollections->setCurrentText(collection + " (" + QString(m_datasetModel->datasetCount(collection)) + ")"); } /** * @brief Sets the currently selected category * @param category the name of the category */ void ImportDatasetWidget::setCategory(const QString &category) { for(int i = 0; i < ui.twCategories->topLevelItemCount(); i++) { if (ui.twCategories->topLevelItem(i)->text(0).compare(category) == 0) { listDatasetsForSubcategory(ui.twCategories->topLevelItem(i)); break; } } } /** * @brief Sets the currently selected subcategory * @param subcategory the name of the subcategory */ void ImportDatasetWidget::setSubcategory(const QString &subcategory) { for(int i = 0; i < ui.twCategories->topLevelItemCount(); i++) { if (ui.twCategories->topLevelItem(i)->text(0).compare(m_selectedCategory) == 0) { QTreeWidgetItem* categoryItem = ui.twCategories->topLevelItem(i); for(int j = 0; j childCount(); j++) { if(categoryItem->child(j)->text(0).compare(subcategory) == 0) { listDatasetsForSubcategory(categoryItem->child(j)); break; } } break; } } } /** * @brief Sets the currently selected dataset * @param the currently selected dataset */ void ImportDatasetWidget::setDataset(const QString &datasetName) { for(int i = 0; i < ui.lwDatasets->count() ; i++) { if(ui.lwDatasets->item(i)->text().compare(datasetName) == 0) { ui.lwDatasets->item(i)->setSelected(true); break; } } } /** * @brief Updates the details of the currently selected dataset */ void ImportDatasetWidget::updateDetails() { if(!getSelectedDataset().isEmpty()) { QJsonObject datasetObject = loadDatasetObject(); ui.lFullName->setText(datasetObject.value("name").toString()); ui.lDescription->setText(datasetObject.value("description").toString()); } else { ui.lFullName->setText("-"); ui.lDescription->setText("-"); } } /** * @brief Hides or displays the details of the currently selected dataset * @param show boolean value determining to show or not the details. */ void ImportDatasetWidget::showDetails(bool show) { ui.saDetails->setEnabled(show); ui.saDetails->setVisible(show); if(show) updateDetails(); } void ImportDatasetWidget::processTest(const QString& category, const QString& subcategory, const QString& dataset, DatasetHandler* datasetHandler) { m_selectedCollection = "Test"; m_selectedCategory = category; m_selectedSubcategory = subcategory; ui.lwDatasets->clear(); - ui.lwDatasets->addItems(m_datasetModel->testDatasets(category, subcategory)); + ui.lwDatasets->addItems(m_datasetModel->allDatasets(category, subcategory).toStringList()); setDataset(dataset); loadDatasetToProcess(datasetHandler); } +const QString ImportDatasetWidget::getJsonDir() { + return m_jsonDir; +} + + diff --git a/src/kdefrontend/datasources/ImportDatasetWidget.h b/src/kdefrontend/datasources/ImportDatasetWidget.h index f99de4335..91c1d25a0 100644 --- a/src/kdefrontend/datasources/ImportDatasetWidget.h +++ b/src/kdefrontend/datasources/ImportDatasetWidget.h @@ -1,101 +1,102 @@ /*************************************************************************** File : ImportDatasetWidget.h Project : LabPlot Description : import online dataset widget -------------------------------------------------------------------- Copyright : (C) 2019 Kovacs Ferencz (kferike98@gmail.com) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #ifndef IMPORTDATASETWIDGET_H #define IMPORTDATASETWIDGET_H #include "ui_importdatasetwidget.h" #include "QWidget" #include "QMap" class QCompleter; class DatasetHandler; class DatasetModel; class ImportDatasetWidget : public QWidget { Q_OBJECT public: explicit ImportDatasetWidget(QWidget* parent); ~ImportDatasetWidget() override; QString getSelectedDataset() const; void loadDatasetToProcess(DatasetHandler* datasetHandler); QString locateCategoryJsonFile() const; const QMap< QString, QMap>>>& getDatasetsMap(); void setCollection(const QString&); void setCategory(const QString&); void setSubcategory(const QString&); void setDataset(const QString&); void processTest(const QString& category, const QString& subcategory, const QString& dataset, DatasetHandler* datasetHandler); + const QString getJsonDir(); private: Ui::ImportDatasetWidget ui; QMap< QString, QMap>>> m_datasetsMap; QString m_selectedSubcategory; QCompleter* m_categoryCompleter; QCompleter* m_datasetCompleter; QString m_jsonDir; bool m_loadingCategories; QString m_selectedCategory; QString m_selectedCollection; DatasetModel* m_datasetModel{nullptr}; bool m_showDetails{false}; QString validCollectionName(const QString& collection); void downloadCollectionsFile(); void downloadCollectionFile(const QString&); void uploadCategoryFile(); void uploadDatasetFile(const QString&); void updateDatasetCompleter(); void updateCategoryCompleter(); void loadDatasetCategoriesFromJson(); void listDatasetsForSubcategory(QTreeWidgetItem* item); void restoreSelectedSubcategory(const QString& collection); void highlightLocalMetadataFiles(); QJsonObject loadDatasetObject(); void updateDetails(); void showDetails(bool show); private slots: void updateCategoryTree(const QString&); void scrollToCategoryTreeItem(const QString& rootName); void scrollToDatasetListItem(const QString& rootName); void showDatasetMetadataManager(); void refreshCategories(); void clearCache(); signals: void datasetSelected(); void datasetDoubleClicked(); }; #endif // IMPORTDATASETWIDGET_H diff --git a/src/kdefrontend/datasources/ImportFileWidget.cpp b/src/kdefrontend/datasources/ImportFileWidget.cpp index b068f0687..072798e3e 100644 --- a/src/kdefrontend/datasources/ImportFileWidget.cpp +++ b/src/kdefrontend/datasources/ImportFileWidget.cpp @@ -1,2167 +1,2167 @@ /*************************************************************************** File : ImportFileWidget.cpp Project : LabPlot Description : import file data widget -------------------------------------------------------------------- Copyright : (C) 2009-2018 Stefan Gerlach (stefan.gerlach@uni.kn) Copyright : (C) 2009-2019 Alexander Semke (alexander.semke@web.de) Copyright : (C) 2017-2018 Fabian Kristof (fkristofszabolcs@gmail.com) Copyright : (C) 2018-2019 Kovacs Ferencz (kferike98@gmail.com) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "ImportFileWidget.h" #include "FileInfoDialog.h" #include "backend/datasources/filters/filters.h" #include "AsciiOptionsWidget.h" #include "BinaryOptionsWidget.h" #include "HDF5OptionsWidget.h" #include "ImageOptionsWidget.h" #include "NetCDFOptionsWidget.h" #include "FITSOptionsWidget.h" #include "JsonOptionsWidget.h" #include "ROOTOptionsWidget.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_MQTT #include "kdefrontend/widgets/MQTTWillSettingsWidget.h" #include "MQTTConnectionManagerDialog.h" #include "MQTTSubscriptionWidget.h" #include #include #include #include #include #include #include #endif /*! \class ImportFileWidget \brief Widget for importing data from a file. \ingroup kdefrontend */ ImportFileWidget::ImportFileWidget(QWidget* parent, bool liveDataSource, const QString& fileName) : QWidget(parent), m_fileName(fileName), m_liveDataSource(liveDataSource) #ifdef HAVE_MQTT , m_connectTimeoutTimer(new QTimer(this)), m_subscriptionWidget(new MQTTSubscriptionWidget(this)) #endif { ui.setupUi(this); //add supported file types if (!liveDataSource) { ui.cbFileType->addItem(i18n("ASCII data"), AbstractFileFilter::Ascii); ui.cbFileType->addItem(i18n("Binary data"), AbstractFileFilter::Binary); ui.cbFileType->addItem(i18n("Image"), AbstractFileFilter::Image); #ifdef HAVE_HDF5 ui.cbFileType->addItem(i18n("Hierarchical Data Format 5 (HDF5)"), AbstractFileFilter::HDF5); #endif #ifdef HAVE_NETCDF ui.cbFileType->addItem(i18n("Network Common Data Format (NetCDF)"), AbstractFileFilter::NETCDF); #endif #ifdef HAVE_FITS ui.cbFileType->addItem(i18n("Flexible Image Transport System Data Format (FITS)"), AbstractFileFilter::FITS); #endif ui.cbFileType->addItem(i18n("JSON data"), AbstractFileFilter::JSON); #ifdef HAVE_ZIP ui.cbFileType->addItem(i18n("ROOT (CERN)"), AbstractFileFilter::ROOT); #endif ui.cbFileType->addItem(i18n("Ngspice RAW ASCII"), AbstractFileFilter::NgspiceRawAscii); ui.cbFileType->addItem(i18n("Ngspice RAW Binary"), AbstractFileFilter::NgspiceRawBinary); //hide widgets relevant for live data reading only ui.lSourceType->hide(); ui.cbSourceType->hide(); ui.gbUpdateOptions->hide(); } else { ui.cbFileType->addItem(i18n("ASCII data"), AbstractFileFilter::Ascii); ui.cbFileType->addItem(i18n("Binary data"), AbstractFileFilter::Binary); #ifdef HAVE_ZIP ui.cbFileType->addItem(i18n("ROOT (CERN)"), AbstractFileFilter::ROOT); #endif ui.cbFileType->addItem(i18n("Ngspice RAW ASCII"), AbstractFileFilter::NgspiceRawAscii); ui.cbFileType->addItem(i18n("Ngspice RAW Binary"), AbstractFileFilter::NgspiceRawBinary); ui.lePort->setValidator( new QIntValidator(ui.lePort) ); ui.cbBaudRate->addItems(LiveDataSource::supportedBaudRates()); ui.cbSerialPort->addItems(LiveDataSource::availablePorts()); ui.tabWidget->removeTab(2); 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.")); ui.chbRelativePath->setToolTip(i18n("If this option is checked, the relative path of the file (relative to project's folder) will be saved.")); #ifdef HAVE_MQTT m_connectTimeoutTimer->setInterval(6000); #endif } QStringList filterItems {i18n("Automatic"), i18n("Custom")}; ui.cbFilter->addItems(filterItems); //hide options that will be activated on demand ui.gbOptions->hide(); ui.gbUpdateOptions->hide(); setMQTTVisible(false); ui.cbReadingType->addItem(i18n("Whole file"), LiveDataSource::WholeFile); ui.bOpen->setIcon( QIcon::fromTheme(QLatin1String("document-open")) ); ui.bFileInfo->setIcon( QIcon::fromTheme(QLatin1String("help-about")) ); ui.bManageFilters->setIcon( QIcon::fromTheme(QLatin1String("configure")) ); ui.bSaveFilter->setIcon( QIcon::fromTheme(QLatin1String("document-save")) ); ui.bRefreshPreview->setIcon( QIcon::fromTheme(QLatin1String("view-refresh")) ); ui.tvJson->header()->setSectionResizeMode(QHeaderView::ResizeToContents); ui.tvJson->setAlternatingRowColors(true); showJsonModel(false); // the table widget for preview m_twPreview = new QTableWidget(ui.tePreview); m_twPreview->verticalHeader()->hide(); m_twPreview->setEditTriggers(QTableWidget::NoEditTriggers); auto* layout = new QHBoxLayout; layout->addWidget(m_twPreview); ui.tePreview->setLayout(layout); m_twPreview->hide(); // the combobox for the import path m_cbFileName = new KUrlComboBox(KUrlComboBox::Mode::Files, ui.tePreview); m_cbFileName->setMaxItems(7); auto* gridLayout = dynamic_cast(ui.gbDataSource->layout()); if (gridLayout) gridLayout->addWidget(m_cbFileName, 1, 2, 1, 3); //tooltips QString info = i18n("Specify how the data source has to be processed on every read:" "
    " "
  • Continuosly fixed - fixed amount of samples is processed starting from the beginning of the newly recieved data.
  • " "
  • From End - fixed amount of samples is processed starting from the end of the newly recieved data.
  • " "
  • Till the End - all newly recieved data is processed.
  • " "
  • Whole file - on every read the whole file is re-read completely and processed. Only available for \"File Or Named Pipe\" data sources.
  • " "
"); ui.lReadingType->setToolTip(info); ui.cbReadingType->setToolTip(info); info = i18n("Number of samples (lines) to be processed on every read.\n" "Only needs to be specified for the reading modi \"Continiously Fixed\" and \"From End\"."); ui.lSampleSize->setToolTip(info); ui.sbSampleSize->setToolTip(info); info = i18n("Specify when and how frequently the data source needs to be read:" "
    " "
  • Periodically - the data source is read periodically with user specified time intervall.
  • " "
  • On New Data - the data source is read when new data arrives.
  • " "
"); ui.lUpdateType->setToolTip(info); ui.cbUpdateType->setToolTip(info); info = i18n("Specify how frequently the data source has to be read."); ui.lUpdateInterval->setToolTip(info); ui.sbUpdateInterval->setToolTip(info); info = i18n("Specify how many samples need to be kept in memory after reading.\n" "Use \"All\" if all data has to be kept."); ui.lKeepLastValues->setToolTip(info); ui.sbKeepNValues->setToolTip(info); #ifdef HAVE_MQTT ui.cbSourceType->addItem(QLatin1String("MQTT")); m_configPath = QStandardPaths::standardLocations(QStandardPaths::AppDataLocation).constFirst() + QLatin1String("MQTT_connections"); //add subscriptions widget layout = new QHBoxLayout; layout->setContentsMargins(0, 0, 0, 0); layout->setSpacing(0); layout->addWidget(m_subscriptionWidget); ui.frameSubscriptions->setLayout(layout); ui.bManageConnections->setIcon(QIcon::fromTheme(QLatin1String("network-server"))); ui.bManageConnections->setToolTip(i18n("Manage MQTT connections")); - QString info = i18n("Specify the 'Last Will and Testament' message (LWT). At least one topic has to be subscribed."); + info = i18n("Specify the 'Last Will and Testament' message (LWT). At least one topic has to be subscribed."); ui.lLWT->setToolTip(info); ui.bLWT->setToolTip(info); ui.bLWT->setEnabled(false); ui.bLWT->setIcon(ui.bLWT->style()->standardIcon(QStyle::SP_FileDialogDetailedView)); #endif //TODO: implement save/load of user-defined settings later and activate these buttons again ui.bSaveFilter->hide(); ui.bManageFilters->hide(); } void ImportFileWidget::loadSettings() { m_suppressRefresh = true; //load last used settings QString confName; if (m_liveDataSource) confName = QLatin1String("LiveDataImport"); else confName = QLatin1String("FileImport"); KConfigGroup conf(KSharedConfig::openConfig(), confName); //read the source type first since settings in fileNameChanged() depend on this ui.cbSourceType->setCurrentIndex(conf.readEntry("SourceType").toInt()); //general settings AbstractFileFilter::FileType fileType = static_cast(conf.readEntry("Type", 0)); for (int i = 0; i < ui.cbFileType->count(); ++i) { if (static_cast(ui.cbFileType->itemData(i).toInt()) == fileType) { if (ui.cbFileType->currentIndex() == i) initOptionsWidget(); else ui.cbFileType->setCurrentIndex(i); break; } } if (m_fileName.isEmpty()) { ui.cbFilter->setCurrentIndex(conf.readEntry("Filter", 0)); m_cbFileName->setUrl(conf.readEntry("LastImportedFile", "")); QStringList urls = m_cbFileName->urls(); urls.append(conf.readXdgListEntry("LastImportedFiles")); m_cbFileName->setUrls(urls); filterChanged(ui.cbFilter->currentIndex()); // needed if filter is not changed } else m_cbFileName->setUrl(QUrl(m_fileName)); //live data related settings ui.cbBaudRate->setCurrentIndex(conf.readEntry("BaudRate", 13)); // index for bautrate 19200b/s ui.cbReadingType->setCurrentIndex(conf.readEntry("ReadingType", (int)LiveDataSource::WholeFile)); ui.cbSerialPort->setCurrentIndex(conf.readEntry("SerialPort").toInt()); ui.cbUpdateType->setCurrentIndex(conf.readEntry("UpdateType", (int)LiveDataSource::NewData)); updateTypeChanged(ui.cbUpdateType->currentIndex()); ui.leHost->setText(conf.readEntry("Host","")); ui.sbKeepNValues->setValue(conf.readEntry("KeepNValues", 0)); // keep all values ui.lePort->setText(conf.readEntry("Port","")); ui.sbSampleSize->setValue(conf.readEntry("SampleSize", 1)); ui.sbUpdateInterval->setValue(conf.readEntry("UpdateInterval", 1000)); ui.chbLinkFile->setCheckState((Qt::CheckState)conf.readEntry("LinkFile", (int)Qt::CheckState::Unchecked)); ui.chbRelativePath->setCheckState((Qt::CheckState)conf.readEntry("RelativePath", (int)Qt::CheckState::Unchecked)); #ifdef HAVE_MQTT //read available MQTT connections m_initialisingMQTT = true; readMQTTConnections(); ui.cbConnection->setCurrentIndex(ui.cbConnection->findText(conf.readEntry("Connection", ""))); m_initialisingMQTT = false; m_willSettings.enabled = conf.readEntry("mqttWillEnabled", m_willSettings.enabled); m_willSettings.willRetain = conf.readEntry("mqttWillRetain", m_willSettings.willRetain); m_willSettings.willUpdateType = static_cast(conf.readEntry("mqttWillUpdateType", (int)m_willSettings.willUpdateType)); m_willSettings.willMessageType = static_cast(conf.readEntry("mqttWillMessageType", (int)m_willSettings.willMessageType)); m_willSettings.willQoS = conf.readEntry("mqttWillQoS", (int)m_willSettings.willQoS); m_willSettings.willOwnMessage = conf.readEntry("mqttWillOwnMessage", m_willSettings.willOwnMessage); m_willSettings.willTimeInterval = conf.readEntry("mqttWillUpdateInterval", m_willSettings.willTimeInterval); const QString& willStatistics = conf.readEntry("mqttWillStatistics",""); const QStringList& statisticsList = willStatistics.split('|', QString::SplitBehavior::SkipEmptyParts); for (auto value : statisticsList) m_willSettings.willStatistics[value.toInt()] = true; #endif //initialize the slots after all settings were set in order to avoid unneeded refreshes initSlots(); //update the status of the widgets fileTypeChanged(fileType); sourceTypeChanged(currentSourceType()); readingTypeChanged(ui.cbReadingType->currentIndex()); //all set now, refresh the preview m_suppressRefresh = false; QTimer::singleShot(0, this, [=] () { refreshPreview(); }); } ImportFileWidget::~ImportFileWidget() { // save current settings QString confName; if (m_liveDataSource) confName = QLatin1String("LiveDataImport"); else confName = QLatin1String("FileImport"); KConfigGroup conf(KSharedConfig::openConfig(), confName); // general settings conf.writeEntry("Type", (int)currentFileType()); conf.writeEntry("Filter", ui.cbFilter->currentIndex()); conf.writeEntry("LastImportedFile", m_cbFileName->currentText()); conf.writeXdgListEntry("LastImportedFiles", m_cbFileName->urls()); //live data related settings conf.writeEntry("SourceType", (int)currentSourceType()); conf.writeEntry("UpdateType", ui.cbUpdateType->currentIndex()); conf.writeEntry("ReadingType", ui.cbReadingType->currentIndex()); conf.writeEntry("SampleSize", ui.sbSampleSize->value()); conf.writeEntry("KeepNValues", ui.sbKeepNValues->value()); conf.writeEntry("BaudRate", ui.cbBaudRate->currentIndex()); conf.writeEntry("SerialPort", ui.cbSerialPort->currentIndex()); conf.writeEntry("Host", ui.leHost->text()); conf.writeEntry("Port", ui.lePort->text()); conf.writeEntry("UpdateInterval", ui.sbUpdateInterval->value()); conf.writeEntry("LinkFile", (int)ui.chbLinkFile->checkState()); conf.writeEntry("RelativePath", (int)ui.chbRelativePath->checkState()); #ifdef HAVE_MQTT delete m_connectTimeoutTimer; delete m_subscriptionWidget; //MQTT related settings conf.writeEntry("Connection", ui.cbConnection->currentText()); conf.writeEntry("mqttWillMessageType", static_cast(m_willSettings.willMessageType)); conf.writeEntry("mqttWillUpdateType", static_cast(m_willSettings.willUpdateType)); conf.writeEntry("mqttWillQoS", QString::number(m_willSettings.willQoS)); conf.writeEntry("mqttWillOwnMessage", m_willSettings.willOwnMessage); conf.writeEntry("mqttWillUpdateInterval", QString::number(m_willSettings.willTimeInterval)); QString willStatistics; for (int i = 0; i < m_willSettings.willStatistics.size(); ++i) { if (m_willSettings.willStatistics[i]) willStatistics += QString::number(i)+ QLatin1Char('|'); } conf.writeEntry("mqttWillStatistics", willStatistics); conf.writeEntry("mqttWillRetain", static_cast(m_willSettings.willRetain)); conf.writeEntry("mqttWillUse", static_cast(m_willSettings.enabled)); #endif // data type specific settings if (m_asciiOptionsWidget) m_asciiOptionsWidget->saveSettings(); if (m_binaryOptionsWidget) m_binaryOptionsWidget->saveSettings(); if (m_imageOptionsWidget) m_imageOptionsWidget->saveSettings(); if (m_jsonOptionsWidget) m_jsonOptionsWidget->saveSettings(); } void ImportFileWidget::initSlots() { //SLOTs for the general part of the data source configuration connect(ui.cbSourceType, static_cast(&QComboBox::currentIndexChanged), this, static_cast(&ImportFileWidget::sourceTypeChanged)); connect(m_cbFileName, &KUrlComboBox::urlActivated, this, [=](const QUrl &url){fileNameChanged(url.path());}); connect(ui.leHost, &QLineEdit::textChanged, this, &ImportFileWidget::hostChanged); connect(ui.lePort, &QLineEdit::textChanged, this, &ImportFileWidget::portChanged); connect(ui.tvJson, &QTreeView::clicked, this, &ImportFileWidget::refreshPreview); connect(ui.bOpen, &QPushButton::clicked, this, &ImportFileWidget::selectFile); connect(ui.bFileInfo, &QPushButton::clicked, this, &ImportFileWidget::fileInfoDialog); connect(ui.bSaveFilter, &QPushButton::clicked, this, &ImportFileWidget::saveFilter); connect(ui.bManageFilters, &QPushButton::clicked, this, &ImportFileWidget::manageFilters); connect(ui.cbFileType, static_cast(&KComboBox::currentIndexChanged), this, &ImportFileWidget::fileTypeChanged); connect(ui.cbUpdateType, static_cast(&QComboBox::currentIndexChanged), this, &ImportFileWidget::updateTypeChanged); connect(ui.cbReadingType, static_cast(&QComboBox::currentIndexChanged), this, &ImportFileWidget::readingTypeChanged); connect(ui.cbFilter, static_cast(&KComboBox::activated), this, &ImportFileWidget::filterChanged); connect(ui.bRefreshPreview, &QPushButton::clicked, this, &ImportFileWidget::refreshPreview); #ifdef HAVE_MQTT connect(ui.cbConnection, static_cast(&QComboBox::currentIndexChanged), this, &ImportFileWidget::mqttConnectionChanged); connect(m_connectTimeoutTimer, &QTimer::timeout, this, &ImportFileWidget::mqttConnectTimeout); connect(ui.cbFileType, static_cast(&QComboBox::currentIndexChanged), [this]() { emit checkFileType(); }); connect(ui.bManageConnections, &QPushButton::clicked, this, &ImportFileWidget::showMQTTConnectionManager); connect(ui.bLWT, &QPushButton::clicked, this, &ImportFileWidget::showWillSettings); connect(m_subscriptionWidget, &MQTTSubscriptionWidget::makeSubscription, this, &ImportFileWidget::mqttSubscribe); connect(m_subscriptionWidget, &MQTTSubscriptionWidget::MQTTUnsubscribeFromTopic, this, &ImportFileWidget::unsubscribeFromTopic); connect(m_subscriptionWidget, &MQTTSubscriptionWidget::enableWill, this, &ImportFileWidget::enableWill); connect(m_subscriptionWidget, &MQTTSubscriptionWidget::subscriptionChanged, this, &ImportFileWidget::refreshPreview); #endif } void ImportFileWidget::showAsciiHeaderOptions(bool b) { if (m_asciiOptionsWidget) m_asciiOptionsWidget->showAsciiHeaderOptions(b); } void ImportFileWidget::showJsonModel(bool b) { ui.tvJson->setVisible(b); ui.lField->setVisible(b); } void ImportFileWidget::showOptions(bool b) { ui.gbOptions->setVisible(b); if (m_liveDataSource) ui.gbUpdateOptions->setVisible(b); resize(layout()->minimumSize()); } QString ImportFileWidget::fileName() const { DEBUG("ImportFileWidget::fileName() : " << m_cbFileName->currentText().toStdString()) return m_cbFileName->currentText(); } QString ImportFileWidget::selectedObject() const { const QString& path = fileName(); //determine the file name only QString name = path.right(path.length() - path.lastIndexOf('/') - 1); //strip away the extension if available if (name.indexOf('.') != -1) name = name.left(name.lastIndexOf('.')); //for multi-dimensional formats like HDF, netCDF and FITS add the currently selected object const auto format = currentFileType(); if (format == AbstractFileFilter::HDF5) { const QStringList& hdf5Names = m_hdf5OptionsWidget->selectedNames(); if (hdf5Names.size()) name += hdf5Names.first(); //the names of the selected HDF5 objects already have '/' } else if (format == AbstractFileFilter::NETCDF) { const QStringList& names = m_netcdfOptionsWidget->selectedNames(); if (names.size()) name += QLatin1Char('/') + names.first(); } else if (format == AbstractFileFilter::FITS) { const QString& extensionName = m_fitsOptionsWidget->currentExtensionName(); if (!extensionName.isEmpty()) name += QLatin1Char('/') + extensionName; } else if (format == AbstractFileFilter::ROOT) { const QStringList& names = m_rootOptionsWidget->selectedNames(); if (names.size()) name += QLatin1Char('/') + names.first(); } return name; } /*! * returns \c true if the number of lines to be imported from the currently selected file is zero ("file is empty"), * returns \c false otherwise. */ bool ImportFileWidget::isFileEmpty() const { return m_fileEmpty; } QString ImportFileWidget::host() const { return ui.leHost->text(); } QString ImportFileWidget::port() const { return ui.lePort->text(); } QString ImportFileWidget::serialPort() const { return ui.cbSerialPort->currentText(); } int ImportFileWidget::baudRate() const { return ui.cbBaudRate->currentText().toInt(); } /*! saves the settings to the data source \c source. */ void ImportFileWidget::saveSettings(LiveDataSource* source) const { AbstractFileFilter::FileType fileType = currentFileType(); auto updateType = static_cast(ui.cbUpdateType->currentIndex()); LiveDataSource::SourceType sourceType = currentSourceType(); auto readingType = static_cast(ui.cbReadingType->currentIndex()); source->setComment( fileName() ); source->setFileType(fileType); currentFileFilter(); source->setFilter(m_currentFilter.release()); // pass ownership of the filter to the LiveDataSource source->setSourceType(sourceType); switch (sourceType) { case LiveDataSource::SourceType::FileOrPipe: source->setFileName(fileName()); source->setFileLinked(ui.chbLinkFile->isChecked()); source->setUseRelativePath(ui.chbRelativePath->isChecked()); break; case LiveDataSource::SourceType::LocalSocket: source->setFileName(fileName()); source->setLocalSocketName(fileName()); break; case LiveDataSource::SourceType::NetworkTcpSocket: case LiveDataSource::SourceType::NetworkUdpSocket: source->setHost(ui.leHost->text()); source->setPort((quint16)ui.lePort->text().toInt()); break; case LiveDataSource::SourceType::SerialPort: source->setBaudRate(ui.cbBaudRate->currentText().toInt()); source->setSerialPort(ui.cbSerialPort->currentText()); break; case LiveDataSource::SourceType::MQTT: break; default: break; } //reading options source->setReadingType(readingType); source->setKeepNValues(ui.sbKeepNValues->value()); source->setUpdateType(updateType); if (updateType == LiveDataSource::UpdateType::TimeInterval) source->setUpdateInterval(ui.sbUpdateInterval->value()); if (readingType != LiveDataSource::ReadingType::TillEnd) source->setSampleSize(ui.sbSampleSize->value()); } #ifdef HAVE_MQTT /*! saves the settings to the MQTTClient \c client. */ void ImportFileWidget::saveMQTTSettings(MQTTClient* client) const { DEBUG("ImportFileWidget::saveMQTTSettings"); MQTTClient::UpdateType updateType = static_cast(ui.cbUpdateType->currentIndex()); MQTTClient::ReadingType readingType = static_cast(ui.cbReadingType->currentIndex()); client->setComment(fileName()); currentFileFilter(); client->setFilter(static_cast(m_currentFilter.release())); // pass ownership of the filter to MQTTClient client->setReadingType(readingType); if (updateType == MQTTClient::UpdateType::TimeInterval) client->setUpdateInterval(ui.sbUpdateInterval->value()); client->setKeepNValues(ui.sbKeepNValues->value()); client->setUpdateType(updateType); if (readingType != MQTTClient::ReadingType::TillEnd) client->setSampleSize(ui.sbSampleSize->value()); client->setMQTTClientHostPort(m_client->hostname(), m_client->port()); KConfig config(m_configPath, KConfig::SimpleConfig); KConfigGroup group = config.group(ui.cbConnection->currentText()); bool useID = group.readEntry("UseID").toUInt(); bool useAuthentication = group.readEntry("UseAuthentication").toUInt(); client->setMQTTUseAuthentication(useAuthentication); if (useAuthentication) client->setMQTTClientAuthentication(m_client->username(), m_client->password()); client->setMQTTUseID(useID); if (useID) client->setMQTTClientId(m_client->clientId()); for (int i = 0; i < m_mqttSubscriptions.count(); ++i) client->addInitialMQTTSubscriptions(m_mqttSubscriptions[i]->topic(), m_mqttSubscriptions[i]->qos()); const bool retain = group.readEntry("Retain").toUInt(); client->setMQTTRetain(retain); if (m_willSettings.enabled) client->setWillSettings(m_willSettings); } #endif /*! returns the currently used file type. */ AbstractFileFilter::FileType ImportFileWidget::currentFileType() const { return static_cast(ui.cbFileType->currentData().toInt()); } LiveDataSource::SourceType ImportFileWidget::currentSourceType() const { return static_cast(ui.cbSourceType->currentIndex()); } /*! returns the currently used filter. */ AbstractFileFilter* ImportFileWidget::currentFileFilter() const { DEBUG("ImportFileWidget::currentFileFilter()"); AbstractFileFilter::FileType fileType = currentFileType(); if (m_currentFilter && m_currentFilter->type() != fileType) m_currentFilter.reset(); switch (fileType) { case AbstractFileFilter::Ascii: { DEBUG(" ASCII"); if (!m_currentFilter) m_currentFilter.reset(new AsciiFilter); auto filter = static_cast(m_currentFilter.get()); if (ui.cbFilter->currentIndex() == 0) //"automatic" filter->setAutoModeEnabled(true); else if (ui.cbFilter->currentIndex() == 1) { //"custom" filter->setAutoModeEnabled(false); if (m_asciiOptionsWidget) m_asciiOptionsWidget->applyFilterSettings(filter); } else filter->loadFilterSettings(ui.cbFilter->currentText()); //save the data portion to import filter->setStartRow(ui.sbStartRow->value()); filter->setEndRow(ui.sbEndRow->value()); filter->setStartColumn(ui.sbStartColumn->value()); filter->setEndColumn(ui.sbEndColumn->value()); break; } case AbstractFileFilter::Binary: { DEBUG(" Binary"); if (!m_currentFilter) m_currentFilter.reset(new BinaryFilter); auto filter = static_cast(m_currentFilter.get()); if ( ui.cbFilter->currentIndex() == 0 ) //"automatic" filter->setAutoModeEnabled(true); else if (ui.cbFilter->currentIndex() == 1) { //"custom" filter->setAutoModeEnabled(false); if (m_binaryOptionsWidget) m_binaryOptionsWidget->applyFilterSettings(filter); } else { //TODO: load filter settings // filter->setFilterName( ui.cbFilter->currentText() ); } filter->setStartRow(ui.sbStartRow->value()); filter->setEndRow(ui.sbEndRow->value()); break; } case AbstractFileFilter::Image: { DEBUG(" Image"); if (!m_currentFilter) m_currentFilter.reset(new ImageFilter); auto filter = static_cast(m_currentFilter.get()); filter->setImportFormat(m_imageOptionsWidget->currentFormat()); filter->setStartRow(ui.sbStartRow->value()); filter->setEndRow(ui.sbEndRow->value()); filter->setStartColumn(ui.sbStartColumn->value()); filter->setEndColumn(ui.sbEndColumn->value()); break; } case AbstractFileFilter::HDF5: { DEBUG("ImportFileWidget::currentFileFilter(): HDF5"); if (!m_currentFilter) m_currentFilter.reset(new HDF5Filter); auto filter = static_cast(m_currentFilter.get()); QStringList names = selectedHDF5Names(); QDEBUG("ImportFileWidget::currentFileFilter(): selected HDF5 names =" << names); if (!names.isEmpty()) filter->setCurrentDataSetName(names[0]); filter->setStartRow(ui.sbStartRow->value()); filter->setEndRow(ui.sbEndRow->value()); filter->setStartColumn(ui.sbStartColumn->value()); filter->setEndColumn(ui.sbEndColumn->value()); DEBUG("ImportFileWidget::currentFileFilter(): OK"); break; } case AbstractFileFilter::NETCDF: { DEBUG(" NETCDF"); if (!m_currentFilter) m_currentFilter.reset(new NetCDFFilter); auto filter = static_cast(m_currentFilter.get()); if (!selectedNetCDFNames().isEmpty()) filter->setCurrentVarName(selectedNetCDFNames()[0]); filter->setStartRow(ui.sbStartRow->value()); filter->setEndRow(ui.sbEndRow->value()); filter->setStartColumn(ui.sbStartColumn->value()); filter->setEndColumn(ui.sbEndColumn->value()); break; } case AbstractFileFilter::FITS: { DEBUG(" FITS"); if (!m_currentFilter) m_currentFilter.reset(new FITSFilter); auto filter = static_cast(m_currentFilter.get()); filter->setStartRow(ui.sbStartRow->value()); filter->setEndRow(ui.sbEndRow->value()); filter->setStartColumn(ui.sbStartColumn->value()); filter->setEndColumn(ui.sbEndColumn->value()); break; } case AbstractFileFilter::JSON: { DEBUG(" JSON"); if (!m_currentFilter) m_currentFilter.reset(new JsonFilter); auto filter = static_cast(m_currentFilter.get()); m_jsonOptionsWidget->applyFilterSettings(filter, ui.tvJson->currentIndex()); filter->setStartRow(ui.sbStartRow->value()); filter->setEndRow(ui.sbEndRow->value()); filter->setStartColumn(ui.sbStartColumn->value()); filter->setEndColumn(ui.sbEndColumn->value()); break; } case AbstractFileFilter::ROOT: { DEBUG(" ROOT"); if (!m_currentFilter) m_currentFilter.reset(new ROOTFilter); auto filter = static_cast(m_currentFilter.get()); QStringList names = selectedROOTNames(); if (!names.isEmpty()) filter->setCurrentObject(names.first()); filter->setStartRow(m_rootOptionsWidget->startRow()); filter->setEndRow(m_rootOptionsWidget->endRow()); filter->setColumns(m_rootOptionsWidget->columns()); break; } case AbstractFileFilter::NgspiceRawAscii: { DEBUG(" NgspiceRawAscii"); if (!m_currentFilter) m_currentFilter.reset(new NgspiceRawAsciiFilter); auto filter = static_cast(m_currentFilter.get()); filter->setStartRow(ui.sbStartRow->value()); filter->setEndRow(ui.sbEndRow->value()); break; } case AbstractFileFilter::NgspiceRawBinary: { DEBUG(" NgspiceRawBinary"); if (!m_currentFilter) m_currentFilter.reset(new NgspiceRawBinaryFilter); auto filter = static_cast(m_currentFilter.get()); filter->setStartRow(ui.sbStartRow->value()); filter->setEndRow(ui.sbEndRow->value()); break; } } return m_currentFilter.get(); } /*! opens a file dialog and lets the user select the file data source. */ void ImportFileWidget::selectFile() { DEBUG("ImportFileWidget::selectFile()") KConfigGroup conf(KSharedConfig::openConfig(), QLatin1String("ImportFileWidget")); const QString& dir = conf.readEntry(QLatin1String("LastDir"), ""); const QString& path = QFileDialog::getOpenFileName(this, i18n("Select the File Data Source"), dir); DEBUG(" dir = " << dir.toStdString()) DEBUG(" path = " << path.toStdString()) if (path.isEmpty()) //cancel was clicked in the file-dialog return; int pos = path.lastIndexOf('/'); if (pos != -1) { QString newDir = path.left(pos); if (newDir != dir) conf.writeEntry(QLatin1String("LastDir"), newDir); } //process all events after the FileDialog was closed to repaint the widget //before we start calculating the preview QApplication::processEvents(QEventLoop::AllEvents, 0); QStringList urls = m_cbFileName->urls(); urls.insert(0, QUrl::fromLocalFile(path).url()); // add type of path m_cbFileName->setUrls(urls); m_cbFileName->setCurrentText(urls.first()); DEBUG(" combobox text = " << m_cbFileName->currentText().toStdString()) fileNameChanged(path); // why do I have to call this function separately } /*! hides the MQTT related items of the widget */ void ImportFileWidget::setMQTTVisible(bool visible) { ui.lConnections->setVisible(visible); ui.cbConnection->setVisible(visible); ui.bManageConnections->setVisible(visible); //topics if (ui.cbConnection->currentIndex() != -1 && visible) { ui.lTopics->setVisible(true); ui.frameSubscriptions->setVisible(true); #ifdef HAVE_MQTT m_subscriptionWidget->setVisible(true); m_subscriptionWidget->makeVisible(true); #endif } else { ui.lTopics->setVisible(false); ui.frameSubscriptions->setVisible(false); #ifdef HAVE_MQTT m_subscriptionWidget->setVisible(false); m_subscriptionWidget->makeVisible(false); #endif } //will message ui.lLWT->setVisible(visible); ui.bLWT->setVisible(visible); } #ifdef HAVE_MQTT /*! * returns \c true if there is a valid connection to an MQTT broker and the user has subscribed to at least 1 topic, * returns \c false otherwise. */ bool ImportFileWidget::isMqttValid() { if (!m_client) return false; bool connected = (m_client->state() == QMqttClient::ClientState::Connected); bool subscribed = (m_subscriptionWidget->subscriptionCount() > 0); bool fileTypeOk = false; if (this->currentFileType() == AbstractFileFilter::FileType::Ascii) fileTypeOk = true; return connected && subscribed && fileTypeOk; } /*! *\brief Unsubscribes from the given topic, and removes any data connected to it * * \param topicName the name of a topic we want to unsubscribe from */ void ImportFileWidget::unsubscribeFromTopic(const QString& topicName, QVector children) { if (topicName.isEmpty()) return; QMqttTopicFilter filter{topicName}; m_client->unsubscribe(filter); for (int i = 0; i< m_mqttSubscriptions.count(); ++i) if (m_mqttSubscriptions[i]->topic().filter() == topicName) { m_mqttSubscriptions.remove(i); break; } QMapIterator j(m_lastMessage); while (j.hasNext()) { j.next(); if (MQTTSubscriptionWidget::checkTopicContains(topicName, j.key().name())) m_lastMessage.remove(j.key()); } for (int i = 0; i < m_subscribedTopicNames.size(); ++i) { if (MQTTSubscriptionWidget::checkTopicContains(topicName, m_subscribedTopicNames[i])) { m_subscribedTopicNames.remove(i); i--; } } if (m_willSettings.willTopic == topicName) { if (m_subscriptionWidget->subscriptionCount() > 0) m_willSettings.willTopic = children[0]->text(0); else m_willSettings.willTopic.clear(); } //signals that there was a change among the subscribed topics emit subscriptionsChanged(); refreshPreview(); } #endif /************** SLOTS **************************************************************/ QString absolutePath(const QString& fileName) { #ifndef HAVE_WINDOWS // make absolute path // FIXME if (!fileName.isEmpty() && fileName.at(0) != QDir::separator()) return QDir::homePath() + QDir::separator() + fileName; #endif return fileName; } /*! called on file name changes. Determines the file format (ASCII, binary etc.), if the file exists, and activates the corresponding options. */ void ImportFileWidget::fileNameChanged(const QString& name) { DEBUG("ImportFileWidget::fileNameChanged() : " << name.toStdString()) const QString fileName = absolutePath(name); bool fileExists = QFile::exists(fileName); if (fileExists) m_cbFileName->setStyleSheet(QString()); else m_cbFileName->setStyleSheet("QComboBox{background:red;}"); ui.gbOptions->setEnabled(fileExists); ui.bManageFilters->setEnabled(fileExists); ui.cbFilter->setEnabled(fileExists); ui.cbFileType->setEnabled(fileExists); ui.bFileInfo->setEnabled(fileExists); ui.gbUpdateOptions->setEnabled(fileExists); if (!fileExists) { //file doesn't exist -> delete the content preview that is still potentially //available from the previously selected file ui.tePreview->clear(); m_twPreview->clear(); initOptionsWidget(); emit fileNameChanged(); return; } if (currentSourceType() == LiveDataSource::FileOrPipe) { const AbstractFileFilter::FileType fileType = AbstractFileFilter::fileType(fileName); for (int i = 0; i < ui.cbFileType->count(); ++i) { if (static_cast(ui.cbFileType->itemData(i).toInt()) == fileType) { // automatically select a new file type if (ui.cbFileType->currentIndex() != i) { ui.cbFileType->setCurrentIndex(i); // will call the slot fileTypeChanged which updates content and preview emit fileNameChanged(); return; } else { initOptionsWidget(); updateContent(fileName); break; } } } } emit fileNameChanged(); refreshPreview(); } /*! saves the current filter settings */ void ImportFileWidget::saveFilter() { bool ok; QString text = QInputDialog::getText(this, i18n("Save Filter Settings as"), i18n("Filter name:"), QLineEdit::Normal, i18n("new filter"), &ok); if (ok && !text.isEmpty()) { //TODO //AsciiFilter::saveFilter() } } /*! opens a dialog for managing all available predefined filters. */ void ImportFileWidget::manageFilters() { //TODO } /*! Depending on the selected file type, activates the corresponding options in the data portion tab and populates the combobox with the available pre-defined fllter settings for the selected type. */ void ImportFileWidget::fileTypeChanged(int index) { Q_UNUSED(index); AbstractFileFilter::FileType fileType = currentFileType(); DEBUG("ImportFileWidget::fileTypeChanged " << ENUM_TO_STRING(AbstractFileFilter, FileType, fileType)); initOptionsWidget(); //default ui.lFilter->show(); ui.cbFilter->show(); //different file types show different number of tabs in ui.tabWidget. //when switching from the previous file type we re-set the tab widget to its original state //and remove/add the required tabs further below for (int i = 0; icount(); ++i) ui.tabWidget->removeTab(0); ui.tabWidget->addTab(ui.tabDataFormat, i18n("Data format")); ui.tabWidget->addTab(ui.tabDataPreview, i18n("Preview")); if (!m_liveDataSource) ui.tabWidget->addTab(ui.tabDataPortion, i18n("Data portion to read")); ui.lPreviewLines->show(); ui.sbPreviewLines->show(); ui.lStartColumn->show(); ui.sbStartColumn->show(); ui.lEndColumn->show(); ui.sbEndColumn->show(); showJsonModel(false); switch (fileType) { case AbstractFileFilter::Ascii: break; case AbstractFileFilter::Binary: ui.lStartColumn->hide(); ui.sbStartColumn->hide(); ui.lEndColumn->hide(); ui.sbEndColumn->hide(); break; case AbstractFileFilter::ROOT: ui.tabWidget->removeTab(1); // falls through case AbstractFileFilter::HDF5: case AbstractFileFilter::NETCDF: case AbstractFileFilter::FITS: ui.lFilter->hide(); ui.cbFilter->hide(); // hide global preview tab. we have our own ui.tabWidget->setTabText(0, i18n("Data format && preview")); ui.tabWidget->removeTab(1); ui.tabWidget->setCurrentIndex(0); break; case AbstractFileFilter::Image: ui.lFilter->hide(); ui.cbFilter->hide(); ui.lPreviewLines->hide(); ui.sbPreviewLines->hide(); break; case AbstractFileFilter::NgspiceRawAscii: case AbstractFileFilter::NgspiceRawBinary: ui.lFilter->hide(); ui.cbFilter->hide(); ui.lStartColumn->hide(); ui.sbStartColumn->hide(); ui.lEndColumn->hide(); ui.sbEndColumn->hide(); ui.tabWidget->removeTab(0); ui.tabWidget->setCurrentIndex(0); break; case AbstractFileFilter::JSON: ui.lFilter->hide(); ui.cbFilter->hide(); showJsonModel(true); break; default: DEBUG("unknown file type"); } int lastUsedFilterIndex = ui.cbFilter->currentIndex(); ui.cbFilter->clear(); ui.cbFilter->addItem( i18n("Automatic") ); ui.cbFilter->addItem( i18n("Custom") ); //TODO: populate the combobox with the available pre-defined filter settings for the selected type ui.cbFilter->setCurrentIndex(lastUsedFilterIndex); filterChanged(lastUsedFilterIndex); if (currentSourceType() == LiveDataSource::FileOrPipe) { QString tempFileName = fileName(); const QString& fileName = absolutePath(tempFileName); if (QFile::exists(fileName)) updateContent(fileName); } //for file types other than ASCII and binary we support re-reading the whole file only //select "read whole file" and deactivate the combobox if (m_liveDataSource && (fileType != AbstractFileFilter::Ascii && fileType != AbstractFileFilter::Binary)) { ui.cbReadingType->setCurrentIndex(LiveDataSource::ReadingType::WholeFile); ui.cbReadingType->setEnabled(false); } else ui.cbReadingType->setEnabled(true); refreshPreview(); } // file type specific option widgets void ImportFileWidget::initOptionsWidget() { DEBUG("ImportFileWidget::initOptionsWidget for " << ENUM_TO_STRING(AbstractFileFilter, FileType, currentFileType())); switch (currentFileType()) { case AbstractFileFilter::Ascii: { if (!m_asciiOptionsWidget) { QWidget* asciiw = new QWidget(); m_asciiOptionsWidget = std::unique_ptr(new AsciiOptionsWidget(asciiw)); m_asciiOptionsWidget->loadSettings(); ui.swOptions->addWidget(asciiw); } //for MQTT topics we don't allow to set the vector names since the different topics //can have different number of columns bool isMQTT = (currentSourceType() == LiveDataSource::MQTT); m_asciiOptionsWidget->showAsciiHeaderOptions(!isMQTT); m_asciiOptionsWidget->showTimestampOptions(isMQTT); ui.swOptions->setCurrentWidget(m_asciiOptionsWidget->parentWidget()); break; } case AbstractFileFilter::Binary: if (!m_binaryOptionsWidget) { QWidget* binaryw = new QWidget(); m_binaryOptionsWidget = std::unique_ptr(new BinaryOptionsWidget(binaryw)); ui.swOptions->addWidget(binaryw); m_binaryOptionsWidget->loadSettings(); } ui.swOptions->setCurrentWidget(m_binaryOptionsWidget->parentWidget()); break; case AbstractFileFilter::Image: if (!m_imageOptionsWidget) { QWidget* imagew = new QWidget(); m_imageOptionsWidget = std::unique_ptr(new ImageOptionsWidget(imagew)); ui.swOptions->addWidget(imagew); m_imageOptionsWidget->loadSettings(); } ui.swOptions->setCurrentWidget(m_imageOptionsWidget->parentWidget()); break; case AbstractFileFilter::HDF5: if (!m_hdf5OptionsWidget) { QWidget* hdf5w = new QWidget(); m_hdf5OptionsWidget = std::unique_ptr(new HDF5OptionsWidget(hdf5w, this)); ui.swOptions->addWidget(hdf5w); } else m_hdf5OptionsWidget->clear(); ui.swOptions->setCurrentWidget(m_hdf5OptionsWidget->parentWidget()); break; case AbstractFileFilter::NETCDF: if (!m_netcdfOptionsWidget) { QWidget* netcdfw = new QWidget(); m_netcdfOptionsWidget = std::unique_ptr(new NetCDFOptionsWidget(netcdfw, this)); ui.swOptions->insertWidget(AbstractFileFilter::NETCDF, netcdfw); } else m_netcdfOptionsWidget->clear(); ui.swOptions->setCurrentWidget(m_netcdfOptionsWidget->parentWidget()); break; case AbstractFileFilter::FITS: if (!m_fitsOptionsWidget) { QWidget* fitsw = new QWidget(); m_fitsOptionsWidget = std::unique_ptr(new FITSOptionsWidget(fitsw, this)); ui.swOptions->addWidget(fitsw); } else m_fitsOptionsWidget->clear(); ui.swOptions->setCurrentWidget(m_fitsOptionsWidget->parentWidget()); break; case AbstractFileFilter::JSON: if (!m_jsonOptionsWidget) { QWidget* jsonw = new QWidget(); m_jsonOptionsWidget = std::unique_ptr(new JsonOptionsWidget(jsonw, this)); ui.tvJson->setModel(m_jsonOptionsWidget->model()); ui.swOptions->addWidget(jsonw); m_jsonOptionsWidget->loadSettings(); } else m_jsonOptionsWidget->clearModel(); ui.swOptions->setCurrentWidget(m_jsonOptionsWidget->parentWidget()); showJsonModel(true); break; case AbstractFileFilter::ROOT: if (!m_rootOptionsWidget) { QWidget* rootw = new QWidget(); m_rootOptionsWidget = std::unique_ptr(new ROOTOptionsWidget(rootw, this)); ui.swOptions->addWidget(rootw); } else m_rootOptionsWidget->clear(); ui.swOptions->setCurrentWidget(m_rootOptionsWidget->parentWidget()); break; case AbstractFileFilter::NgspiceRawAscii: case AbstractFileFilter::NgspiceRawBinary: break; } } const QStringList ImportFileWidget::selectedHDF5Names() const { return m_hdf5OptionsWidget->selectedNames(); } const QStringList ImportFileWidget::selectedNetCDFNames() const { return m_netcdfOptionsWidget->selectedNames(); } const QStringList ImportFileWidget::selectedFITSExtensions() const { return m_fitsOptionsWidget->selectedExtensions(); } const QStringList ImportFileWidget::selectedROOTNames() const { return m_rootOptionsWidget->selectedNames(); } /*! shows the dialog with the information about the file(s) to be imported. */ void ImportFileWidget::fileInfoDialog() { QStringList files = fileName().split(';'); auto* dlg = new FileInfoDialog(this); dlg->setFiles(files); dlg->exec(); } /*! enables the options if the filter "custom" was chosen. Disables the options otherwise. */ void ImportFileWidget::filterChanged(int index) { // ignore filter for these formats AbstractFileFilter::FileType fileType = currentFileType(); if (fileType != AbstractFileFilter::Ascii && fileType != AbstractFileFilter::Binary) { ui.swOptions->setEnabled(true); return; } if (index == 0) { // "automatic" ui.swOptions->setEnabled(false); ui.bSaveFilter->setEnabled(false); } else if (index == 1) { //custom ui.swOptions->setEnabled(true); ui.bSaveFilter->setEnabled(true); } else { // predefined filter settings were selected. //load and show them in the GUI. //TODO } } void ImportFileWidget::refreshPreview() { if (m_suppressRefresh) return; DEBUG("ImportFileWidget::refreshPreview()"); WAIT_CURSOR; QString tempFileName = fileName(); QString fileName = absolutePath(tempFileName); AbstractFileFilter::FileType fileType = currentFileType(); LiveDataSource::SourceType sourceType = currentSourceType(); int lines = ui.sbPreviewLines->value(); if (sourceType == LiveDataSource::SourceType::FileOrPipe) DEBUG(" file name = " << fileName.toStdString()); // generic table widget if (fileType == AbstractFileFilter::Ascii || fileType == AbstractFileFilter::Binary || fileType == AbstractFileFilter::JSON || fileType == AbstractFileFilter::NgspiceRawAscii || fileType == AbstractFileFilter::NgspiceRawBinary) m_twPreview->show(); else m_twPreview->hide(); bool ok = true; QTableWidget* tmpTableWidget = m_twPreview; QVector importedStrings; QStringList vectorNameList; QVector columnModes; DEBUG("Data File Type: " << ENUM_TO_STRING(AbstractFileFilter, FileType, fileType)); switch (fileType) { case AbstractFileFilter::Ascii: { ui.tePreview->clear(); auto filter = static_cast(currentFileFilter()); DEBUG("Data Source Type: " << ENUM_TO_STRING(LiveDataSource, SourceType, sourceType)); switch (sourceType) { case LiveDataSource::SourceType::FileOrPipe: { importedStrings = filter->preview(fileName, lines); break; } case LiveDataSource::SourceType::LocalSocket: { QLocalSocket lsocket{this}; DEBUG("Local socket: CONNECT PREVIEW"); lsocket.connectToServer(fileName, QLocalSocket::ReadOnly); if (lsocket.waitForConnected()) { DEBUG("connected to local socket " << fileName.toStdString()); if (lsocket.waitForReadyRead()) importedStrings = filter->preview(lsocket); DEBUG("Local socket: DISCONNECT PREVIEW"); lsocket.disconnectFromServer(); // read-only socket is disconnected immediately (no waitForDisconnected()) } else DEBUG("failed connect to local socket " << fileName.toStdString() << " - " << lsocket.errorString().toStdString()); break; } case LiveDataSource::SourceType::NetworkTcpSocket: { QTcpSocket tcpSocket{this}; tcpSocket.connectToHost(host(), port().toInt(), QTcpSocket::ReadOnly); if (tcpSocket.waitForConnected()) { DEBUG("connected to TCP socket"); if ( tcpSocket.waitForReadyRead() ) importedStrings = filter->preview(tcpSocket); tcpSocket.disconnectFromHost(); } else DEBUG("failed to connect to TCP socket " << " - " << tcpSocket.errorString().toStdString()); break; } case LiveDataSource::SourceType::NetworkUdpSocket: { QUdpSocket udpSocket{this}; DEBUG("UDP Socket: CONNECT PREVIEW, state = " << udpSocket.state()); udpSocket.bind(QHostAddress(host()), port().toInt()); udpSocket.connectToHost(host(), 0, QUdpSocket::ReadOnly); if (udpSocket.waitForConnected()) { DEBUG(" connected to UDP socket " << host().toStdString() << ':' << port().toInt()); if (!udpSocket.waitForReadyRead(2000) ) DEBUG(" ERROR: not ready for read after 2 sec"); if (udpSocket.hasPendingDatagrams()) { DEBUG(" has pending data"); } else { DEBUG(" has no pending data"); } importedStrings = filter->preview(udpSocket); DEBUG("UDP Socket: DISCONNECT PREVIEW, state = " << udpSocket.state()); udpSocket.disconnectFromHost(); } else DEBUG("failed to connect to UDP socket " << " - " << udpSocket.errorString().toStdString()); break; } case LiveDataSource::SourceType::SerialPort: { QSerialPort sPort{this}; DEBUG(" Port: " << serialPort().toStdString() << ", Settings: " << baudRate() << ',' << sPort.dataBits() << ',' << sPort.parity() << ',' << sPort.stopBits()); sPort.setPortName(serialPort()); sPort.setBaudRate(baudRate()); if (sPort.open(QIODevice::ReadOnly)) { if (sPort.waitForReadyRead(2000)) importedStrings = filter->preview(sPort); else DEBUG(" ERROR: not ready for read after 2 sec"); sPort.close(); } else DEBUG(" ERROR: failed to open serial port. error: " << sPort.error()); break; } case LiveDataSource::SourceType::MQTT: { #ifdef HAVE_MQTT //show the preview for the currently selected topic auto* item = m_subscriptionWidget->currentItem(); if (item && item->childCount() == 0) { //only preview if the lowest level (i.e. a topic) is selected const QString& topicName = item->text(0); auto i = m_lastMessage.find(topicName); if (i != m_lastMessage.end()) importedStrings = filter->preview(i.value().payload().data()); else importedStrings << QStringList{i18n("No data arrived yet for the selected topic")}; } #endif break; } } vectorNameList = filter->vectorNames(); columnModes = filter->columnModes(); break; } case AbstractFileFilter::Binary: { ui.tePreview->clear(); auto filter = static_cast(currentFileFilter()); importedStrings = filter->preview(fileName, lines); break; } case AbstractFileFilter::Image: { ui.tePreview->clear(); QImage image(fileName); QTextCursor cursor = ui.tePreview->textCursor(); cursor.insertImage(image); RESET_CURSOR; return; } case AbstractFileFilter::HDF5: { DEBUG("ImportFileWidget::refreshPreview: HDF5"); auto filter = static_cast(currentFileFilter()); lines = m_hdf5OptionsWidget->lines(); importedStrings = filter->readCurrentDataSet(fileName, nullptr, ok, AbstractFileFilter::Replace, lines); tmpTableWidget = m_hdf5OptionsWidget->previewWidget(); break; } case AbstractFileFilter::NETCDF: { auto filter = static_cast(currentFileFilter()); lines = m_netcdfOptionsWidget->lines(); importedStrings = filter->readCurrentVar(fileName, nullptr, AbstractFileFilter::Replace, lines); tmpTableWidget = m_netcdfOptionsWidget->previewWidget(); break; } case AbstractFileFilter::FITS: { auto filter = static_cast(currentFileFilter()); lines = m_fitsOptionsWidget->lines(); QString extensionName = m_fitsOptionsWidget->extensionName(&ok); if (!extensionName.isEmpty()) { DEBUG(" extension name = " << extensionName.toStdString()); fileName = extensionName; } DEBUG(" file name = " << fileName.toStdString()); bool readFitsTableToMatrix; importedStrings = filter->readChdu(fileName, &readFitsTableToMatrix, lines); emit checkedFitsTableToMatrix(readFitsTableToMatrix); tmpTableWidget = m_fitsOptionsWidget->previewWidget(); break; } case AbstractFileFilter::JSON: { ui.tePreview->clear(); auto filter = static_cast(currentFileFilter()); m_jsonOptionsWidget->applyFilterSettings(filter, ui.tvJson->currentIndex()); importedStrings = filter->preview(fileName); vectorNameList = filter->vectorNames(); columnModes = filter->columnModes(); break; } case AbstractFileFilter::ROOT: { auto filter = static_cast(currentFileFilter()); lines = m_rootOptionsWidget->lines(); m_rootOptionsWidget->setNRows(filter->rowsInCurrentObject(fileName)); importedStrings = filter->previewCurrentObject( fileName, m_rootOptionsWidget->startRow(), qMin(m_rootOptionsWidget->startRow() + m_rootOptionsWidget->lines() - 1, m_rootOptionsWidget->endRow()) ); tmpTableWidget = m_rootOptionsWidget->previewWidget(); // the last vector element contains the column names vectorNameList = importedStrings.last(); importedStrings.removeLast(); columnModes = QVector(vectorNameList.size(), AbstractColumn::Numeric); break; } case AbstractFileFilter::NgspiceRawAscii: { ui.tePreview->clear(); auto filter = static_cast(currentFileFilter()); importedStrings = filter->preview(fileName, lines); vectorNameList = filter->vectorNames(); columnModes = filter->columnModes(); break; } case AbstractFileFilter::NgspiceRawBinary: { ui.tePreview->clear(); auto filter = static_cast(currentFileFilter()); importedStrings = filter->preview(fileName, lines); vectorNameList = filter->vectorNames(); columnModes = filter->columnModes(); break; } } // fill the table widget tmpTableWidget->setRowCount(0); tmpTableWidget->setColumnCount(0); if ( !importedStrings.isEmpty() ) { if (!ok) { // show imported strings as error message tmpTableWidget->setRowCount(1); tmpTableWidget->setColumnCount(1); auto* item = new QTableWidgetItem(); item->setText(importedStrings[0][0]); tmpTableWidget->setItem(0, 0, item); } else { //TODO: maxrows not used const int rows = qMax(importedStrings.size(), 1); const int maxColumns = 300; tmpTableWidget->setRowCount(rows); for (int i = 0; i < rows; ++i) { const int cols = importedStrings[i].size() > maxColumns ? maxColumns : importedStrings[i].size(); if (cols > tmpTableWidget->columnCount()) tmpTableWidget->setColumnCount(cols); for (int j = 0; j < cols; ++j) { auto* item = new QTableWidgetItem(importedStrings[i][j]); tmpTableWidget->setItem(i, j, item); } } // set header if columnMode available for (int i = 0; i < qMin(tmpTableWidget->columnCount(), columnModes.size()); ++i) { QString columnName = QString::number(i+1); if (i < vectorNameList.size()) columnName = vectorNameList[i]; auto* item = new QTableWidgetItem(columnName + QLatin1String(" {") + ENUM_TO_STRING(AbstractColumn, ColumnMode, columnModes[i]) + QLatin1String("}")); item->setTextAlignment(Qt::AlignLeft); item->setIcon(AbstractColumn::iconForMode(columnModes[i])); tmpTableWidget->setHorizontalHeaderItem(i, item); } } tmpTableWidget->horizontalHeader()->resizeSections(QHeaderView::ResizeToContents); m_fileEmpty = false; } else m_fileEmpty = true; emit previewRefreshed(); RESET_CURSOR; } void ImportFileWidget::updateContent(const QString& fileName) { QDEBUG("ImportFileWidget::updateContent(): file name = " << fileName); if (auto filter = currentFileFilter()) { switch (filter->type()) { case AbstractFileFilter::HDF5: m_hdf5OptionsWidget->updateContent(static_cast(filter), fileName); break; case AbstractFileFilter::NETCDF: m_netcdfOptionsWidget->updateContent(static_cast(filter), fileName); break; case AbstractFileFilter::FITS: #ifdef HAVE_FITS m_fitsOptionsWidget->updateContent(static_cast(filter), fileName); #endif break; case AbstractFileFilter::ROOT: m_rootOptionsWidget->updateContent(static_cast(filter), fileName); break; case AbstractFileFilter::JSON: m_jsonOptionsWidget->loadDocument(fileName); ui.tvJson->setExpanded( m_jsonOptionsWidget->model()->index(0, 0), true); //expand the root node break; case AbstractFileFilter::Ascii: case AbstractFileFilter::Binary: case AbstractFileFilter::Image: case AbstractFileFilter::NgspiceRawAscii: case AbstractFileFilter::NgspiceRawBinary: break; } } } void ImportFileWidget::updateTypeChanged(int idx) { const auto UpdateType = static_cast(idx); switch (UpdateType) { case LiveDataSource::UpdateType::TimeInterval: ui.lUpdateInterval->show(); ui.sbUpdateInterval->show(); break; case LiveDataSource::UpdateType::NewData: ui.lUpdateInterval->hide(); ui.sbUpdateInterval->hide(); } } void ImportFileWidget::readingTypeChanged(int idx) { const auto readingType = static_cast(idx); const LiveDataSource::SourceType sourceType = currentSourceType(); if (sourceType == LiveDataSource::SourceType::NetworkTcpSocket || sourceType == LiveDataSource::SourceType::LocalSocket || sourceType == LiveDataSource::SourceType::SerialPort || readingType == LiveDataSource::ReadingType::TillEnd || readingType == LiveDataSource::ReadingType::WholeFile) { ui.lSampleSize->hide(); ui.sbSampleSize->hide(); } else { ui.lSampleSize->show(); ui.sbSampleSize->show(); } if (readingType == LiveDataSource::ReadingType::WholeFile) { ui.lKeepLastValues->hide(); ui.sbKeepNValues->hide(); } else { ui.lKeepLastValues->show(); ui.sbKeepNValues->show(); } } void ImportFileWidget::sourceTypeChanged(int idx) { const auto sourceType = static_cast(idx); // enable/disable "on new data"-option const auto* model = qobject_cast(ui.cbUpdateType->model()); QStandardItem* item = model->item(LiveDataSource::UpdateType::NewData); switch (sourceType) { case LiveDataSource::SourceType::FileOrPipe: ui.lFileName->show(); m_cbFileName->show(); ui.bFileInfo->show(); ui.bOpen->show(); ui.lRelativePath->show(); ui.chbRelativePath->show(); ui.chbLinkFile->show(); //option for sample size are available for "continuously fixed" and "from end" reading options if (ui.cbReadingType->currentIndex() < 2) { ui.lSampleSize->show(); ui.sbSampleSize->show(); } else { ui.lSampleSize->hide(); ui.sbSampleSize->hide(); } ui.cbBaudRate->hide(); ui.lBaudRate->hide(); ui.lHost->hide(); ui.leHost->hide(); ui.lPort->hide(); ui.lePort->hide(); ui.cbSerialPort->hide(); ui.lSerialPort->hide(); item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); fileNameChanged(fileName()); ui.cbFileType->show(); ui.lFileType->show(); setMQTTVisible(false); break; case LiveDataSource::SourceType::NetworkTcpSocket: case LiveDataSource::SourceType::NetworkUdpSocket: ui.lHost->show(); ui.leHost->show(); ui.lePort->show(); ui.lPort->show(); if (sourceType == LiveDataSource::SourceType::NetworkTcpSocket) { ui.lSampleSize->hide(); ui.sbSampleSize->hide(); } else { ui.lSampleSize->show(); ui.sbSampleSize->show(); } ui.lBaudRate->hide(); ui.cbBaudRate->hide(); ui.lSerialPort->hide(); ui.cbSerialPort->hide(); ui.lFileName->hide(); m_cbFileName->hide(); ui.bFileInfo->hide(); ui.bOpen->hide(); ui.lRelativePath->hide(); ui.chbRelativePath->hide(); ui.chbLinkFile->hide(); item->setFlags(item->flags() & ~(Qt::ItemIsSelectable | Qt::ItemIsEnabled)); ui.gbOptions->setEnabled(true); ui.bManageFilters->setEnabled(true); ui.cbFilter->setEnabled(true); ui.cbFileType->setEnabled(true); ui.cbFileType->show(); ui.lFileType->show(); setMQTTVisible(false); break; case LiveDataSource::SourceType::LocalSocket: ui.lFileName->show(); m_cbFileName->show(); ui.bFileInfo->hide(); ui.bOpen->show(); ui.lRelativePath->hide(); ui.chbRelativePath->hide(); ui.lSampleSize->hide(); ui.sbSampleSize->hide(); ui.cbBaudRate->hide(); ui.lBaudRate->hide(); ui.lHost->hide(); ui.leHost->hide(); ui.lPort->hide(); ui.lePort->hide(); ui.cbSerialPort->hide(); ui.lSerialPort->hide(); ui.chbLinkFile->hide(); item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); ui.gbOptions->setEnabled(true); ui.bManageFilters->setEnabled(true); ui.cbFilter->setEnabled(true); ui.cbFileType->setEnabled(true); ui.cbFileType->show(); ui.lFileType->show(); setMQTTVisible(false); break; case LiveDataSource::SourceType::SerialPort: ui.lBaudRate->show(); ui.cbBaudRate->show(); ui.lSerialPort->show(); ui.cbSerialPort->show(); ui.lSampleSize->show(); ui.sbSampleSize->show(); ui.lHost->hide(); ui.leHost->hide(); ui.lePort->hide(); ui.lPort->hide(); ui.lFileName->hide(); m_cbFileName->hide(); ui.bFileInfo->hide(); ui.bOpen->hide(); ui.lRelativePath->hide(); ui.chbRelativePath->hide(); ui.chbLinkFile->hide(); item->setFlags(item->flags() & ~(Qt::ItemIsSelectable | Qt::ItemIsEnabled)); ui.cbFileType->setEnabled(true); ui.cbFileType->show(); ui.gbOptions->setEnabled(true); ui.bManageFilters->setEnabled(true); ui.cbFilter->setEnabled(true); ui.lFileType->show(); setMQTTVisible(false); break; case LiveDataSource::SourceType::MQTT: #ifdef HAVE_MQTT item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); //for MQTT we read ascii data only, hide the file type options for (int i = 0; i < ui.cbFileType->count(); ++i) { if (static_cast(ui.cbFileType->itemData(i).toInt()) == AbstractFileFilter::Ascii) { if (ui.cbFileType->currentIndex() == i) initOptionsWidget(); else ui.cbFileType->setCurrentIndex(i); break; } } ui.cbFileType->hide(); ui.lFileType->hide(); ui.lBaudRate->hide(); ui.cbBaudRate->hide(); ui.lSerialPort->hide(); ui.cbSerialPort->hide(); ui.lHost->hide(); ui.leHost->hide(); ui.lPort->hide(); ui.lePort->hide(); ui.lFileName->hide(); m_cbFileName->hide(); ui.bFileInfo->hide(); ui.bOpen->hide(); ui.lRelativePath->hide(); ui.chbRelativePath->hide(); ui.chbLinkFile->hide(); setMQTTVisible(true); ui.cbFileType->setEnabled(true); ui.gbOptions->setEnabled(true); ui.bManageFilters->setEnabled(true); ui.cbFilter->setEnabled(true); //in case there are already connections defined, //show the available topics for the currently selected connection mqttConnectionChanged(); #endif break; } //deactivate/activate options that are specific to file of pipe sources only auto* typeModel = qobject_cast(ui.cbFileType->model()); if (sourceType != LiveDataSource::FileOrPipe) { //deactivate file types other than ascii and binary for (int i = 2; i < ui.cbFileType->count(); ++i) typeModel->item(i)->setFlags(item->flags() & ~(Qt::ItemIsSelectable | Qt::ItemIsEnabled)); if (ui.cbFileType->currentIndex() > 1) ui.cbFileType->setCurrentIndex(1); //"whole file" read option is available for file or pipe only, disable it typeModel = qobject_cast(ui.cbReadingType->model()); QStandardItem* item = typeModel->item(LiveDataSource::WholeFile); item->setFlags(item->flags() & ~(Qt::ItemIsSelectable | Qt::ItemIsEnabled)); if (static_cast(ui.cbReadingType->currentIndex()) == LiveDataSource::WholeFile) ui.cbReadingType->setCurrentIndex(LiveDataSource::TillEnd); //"update options" groupbox can be deactivated for "file and pipe" if the file is invalid. //Activate the groupbox when switching from "file and pipe" to a different source type. ui.gbUpdateOptions->setEnabled(true); } else { for (int i = 2; i < ui.cbFileType->count(); ++i) typeModel->item(i)->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); //enable "whole file" item for file or pipe typeModel = qobject_cast(ui.cbReadingType->model()); QStandardItem* item = typeModel->item(LiveDataSource::ReadingType::WholeFile); item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); } emit sourceTypeChanged(); refreshPreview(); } #ifdef HAVE_MQTT /*! *\brief called when a different MQTT connection is selected in the connection ComboBox. * connects to the MQTT broker according to the connection settings. */ void ImportFileWidget::mqttConnectionChanged() { if (m_initialisingMQTT || ui.cbConnection->currentIndex() == -1) return; WAIT_CURSOR; //disconnected from the broker that was selected before, if this is the case if (m_client && m_client->state() == QMqttClient::ClientState::Connected) { emit MQTTClearTopics(); disconnect(m_client, &QMqttClient::disconnected, this, &ImportFileWidget::onMqttDisconnect); QDEBUG("Disconnecting from " << m_client->hostname()); m_client->disconnectFromHost(); delete m_client; } //determine the connection settings for the new broker and initialize the mqtt client KConfig config(m_configPath, KConfig::SimpleConfig); KConfigGroup group = config.group(ui.cbConnection->currentText()); m_client = new QMqttClient; connect(m_client, &QMqttClient::connected, this, &ImportFileWidget::onMqttConnect); connect(m_client, &QMqttClient::disconnected, this, &ImportFileWidget::onMqttDisconnect); connect(m_client, &QMqttClient::messageReceived, this, &ImportFileWidget::mqttMessageReceived); connect(m_client, &QMqttClient::errorChanged, this, &ImportFileWidget::mqttErrorChanged); m_client->setHostname(group.readEntry("Host")); m_client->setPort(group.readEntry("Port").toUInt()); const bool useID = group.readEntry("UseID").toUInt(); if (useID) m_client->setClientId(group.readEntry("ClientID")); const bool useAuthentication = group.readEntry("UseAuthentication").toUInt(); if (useAuthentication) { m_client->setUsername(group.readEntry("UserName")); m_client->setPassword(group.readEntry("Password")); } //connect to the selected broker QDEBUG("Connect to " << m_client->hostname() << ":" << m_client->port()); m_connectTimeoutTimer->start(); m_client->connectToHost(); } /*! *\brief called when the client connects to the broker successfully. * subscribes to every topic (# wildcard) in order to later list every available topic */ void ImportFileWidget::onMqttConnect() { if (m_client->error() == QMqttClient::NoError) { m_connectTimeoutTimer->stop(); ui.frameSubscriptions->setVisible(true); m_subscriptionWidget->setVisible(true); m_subscriptionWidget->makeVisible(true); if (!m_client->subscribe(QMqttTopicFilter(QLatin1String("#")), 1)) QMessageBox::critical(this, i18n("Couldn't subscribe"), i18n("Couldn't subscribe to all available topics. Something went wrong")); } emit subscriptionsChanged(); RESET_CURSOR; } /*! *\brief called when the client disconnects from the broker successfully * removes every information about the former connection */ void ImportFileWidget::onMqttDisconnect() { DEBUG("Disconected from " << m_client->hostname().toStdString()); m_connectTimeoutTimer->stop(); ui.lTopics->hide(); ui.frameSubscriptions->hide(); ui.lLWT->hide(); ui.bLWT->hide(); ui.cbConnection->setItemText(ui.cbConnection->currentIndex(), ui.cbConnection->currentText() + ' ' + i18n("(Disconnected)")); emit subscriptionsChanged(); RESET_CURSOR; QMessageBox::critical(this, i18n("Disconnected"), i18n("Disconnected from the broker '%1' before the connection was successful.", m_client->hostname())); } /*! *\brief called when the subscribe button is pressed * subscribes to the topic represented by the current item of twTopics */ void ImportFileWidget::mqttSubscribe(const QString& name, uint QoS) { const QMqttTopicFilter filter {name}; QMqttSubscription* tempSubscription = m_client->subscribe(filter, static_cast(QoS) ); if (tempSubscription) { m_mqttSubscriptions.push_back(tempSubscription); connect(tempSubscription, &QMqttSubscription::messageReceived, this, &ImportFileWidget::mqttSubscriptionMessageReceived); emit subscriptionsChanged(); } } /*! *\brief called when the client receives a message * if the message arrived from a new topic, the topic is put in twTopics */ void ImportFileWidget::mqttMessageReceived(const QByteArray& message, const QMqttTopicName& topic) { Q_UNUSED(message); // qDebug()<<"received " << topic.name(); if (m_addedTopics.contains(topic.name())) return; m_addedTopics.push_back(topic.name()); m_subscriptionWidget->setTopicTreeText(i18n("Available (%1)", m_addedTopics.size())); QStringList name; QString rootName; const QChar sep = '/'; if (topic.name().contains(sep)) { const QStringList& list = topic.name().split(sep, QString::SkipEmptyParts); if (!list.isEmpty()) { rootName = list.at(0); name.append(list.at(0)); int topItemIdx = -1; //check whether the first level of the topic can be found in twTopics for (int i = 0; i < m_subscriptionWidget->topicCount(); ++i) { if (m_subscriptionWidget->topLevelTopic(i)->text(0) == list.at(0)) { topItemIdx = i; break; } } //if not we simply add every level of the topic to the tree if (topItemIdx < 0) { QTreeWidgetItem* currentItem = new QTreeWidgetItem(name); m_subscriptionWidget->addTopic(currentItem); for (int i = 1; i < list.size(); ++i) { name.clear(); name.append(list.at(i)); currentItem->addChild(new QTreeWidgetItem(name)); currentItem = currentItem->child(0); } } //otherwise we search for the first level that isn't part of the tree, //then add every level of the topic to the tree from that certain level else { QTreeWidgetItem* currentItem = m_subscriptionWidget->topLevelTopic(topItemIdx); int listIdx = 1; for (; listIdx < list.size(); ++listIdx) { QTreeWidgetItem* childItem = nullptr; bool found = false; for (int j = 0; j < currentItem->childCount(); ++j) { childItem = currentItem->child(j); if (childItem->text(0) == list.at(listIdx)) { found = true; currentItem = childItem; break; } } if (!found) { //this is the level that isn't present in the tree break; } } //add every level to the tree starting with the first level that isn't part of the tree for (; listIdx < list.size(); ++listIdx) { name.clear(); name.append(list.at(listIdx)); currentItem->addChild(new QTreeWidgetItem(name)); currentItem = currentItem->child(currentItem->childCount() - 1); } } } } else { rootName = topic.name(); name.append(topic.name()); m_subscriptionWidget->addTopic(new QTreeWidgetItem(name)); } //if a subscribed topic contains the new topic, we have to update twSubscriptions for (int i = 0; i < m_subscriptionWidget->subscriptionCount(); ++i) { const QStringList subscriptionName = m_subscriptionWidget->topLevelSubscription(i)->text(0).split('/', QString::SkipEmptyParts); if (!subscriptionName.isEmpty()) { if (rootName == subscriptionName.first()) { QVector subscriptions; for(int i = 0; i < m_mqttSubscriptions.size(); ++i) subscriptions.push_back(m_mqttSubscriptions[i]->topic().filter()); emit updateSubscriptionTree(subscriptions); break; } } } //signals that a newTopic was added, in order to fill the completer of leTopics emit newTopic(rootName); } /*! *\brief called when the client receives a message from a subscribed topic (that isn't the "#" wildcard) */ void ImportFileWidget::mqttSubscriptionMessageReceived(const QMqttMessage &msg) { QDEBUG("message received from: " << msg.topic().name()); if (!m_subscribedTopicNames.contains(msg.topic().name())) m_subscribedTopicNames.push_back(msg.topic().name()); //update the last message for the topic m_lastMessage[msg.topic()] = msg; } /*! *\brief called when the clientError of the MQTT client changes * * \param clientError the current error of the client */ void ImportFileWidget::mqttErrorChanged(QMqttClient::ClientError clientError) { switch (clientError) { case QMqttClient::BadUsernameOrPassword: QMessageBox::critical(this, i18n("Couldn't connect"), i18n("Wrong username or password")); break; case QMqttClient::IdRejected: QMessageBox::critical(this, i18n("Couldn't connect"), i18n("The client ID wasn't accepted")); break; case QMqttClient::ServerUnavailable: QMessageBox::critical(this, i18n("Server unavailable"), i18n("The broker couldn't be reached.")); break; case QMqttClient::NotAuthorized: QMessageBox::critical(this, i18n("Not authorized"), i18n("The client is not authorized to connect.")); break; case QMqttClient::UnknownError: QMessageBox::critical(this, i18n("Unknown MQTT error"), i18n("An unknown error occurred.")); break; case QMqttClient::NoError: case QMqttClient::InvalidProtocolVersion: case QMqttClient::TransportInvalid: case QMqttClient::ProtocolViolation: case QMqttClient::Mqtt5SpecificError: break; default: break; } } /*! *\brief called when m_connectTimeoutTimer ticks, * meaning that the client couldn't connect to the broker in 5 seconds * disconnects the client, stops the timer, and warns the user */ void ImportFileWidget::mqttConnectTimeout() { m_connectionTimedOut = true; m_client->disconnectFromHost(); m_connectTimeoutTimer->stop(); RESET_CURSOR; QMessageBox::warning(this, i18n("Warning"), i18n("Connecting to the given broker timed out! Try changing the settings")); } /*! Shows the MQTT connection manager where the connections are created and edited. The selected connection is selected in the connection combo box in this widget. */ void ImportFileWidget::showMQTTConnectionManager() { bool previousConnectionChanged = false; MQTTConnectionManagerDialog* dlg = new MQTTConnectionManagerDialog(this, ui.cbConnection->currentText(), previousConnectionChanged); if (dlg->exec() == QDialog::Accepted) { //re-read the available connections to be in sync with the changes in MQTTConnectionManager m_initialisingMQTT = true; const QString& prevConn = ui.cbConnection->currentText(); ui.cbConnection->clear(); readMQTTConnections(); m_initialisingMQTT = false; //select the connection the user has selected in MQTTConnectionManager const QString& conn = dlg->connection(); int index = ui.cbConnection->findText(conn); if (conn != prevConn) {//Current connection isn't the previous one if (ui.cbConnection->currentIndex() != index) ui.cbConnection->setCurrentIndex(index); else mqttConnectionChanged(); } else if (dlg->initialConnectionChanged()) {//Current connection is the same with previous one but it changed if (ui.cbConnection->currentIndex() == index) mqttConnectionChanged(); else ui.cbConnection->setCurrentIndex(index); } else { //Previous connection wasn't changed m_initialisingMQTT = true; ui.cbConnection->setCurrentIndex(index); m_initialisingMQTT = false; } } delete dlg; } /*! loads all available saved MQTT nconnections */ void ImportFileWidget::readMQTTConnections() { DEBUG("ImportFileWidget: reading available MQTT connections"); KConfig config(m_configPath, KConfig::SimpleConfig); for (const auto& name : config.groupList()) ui.cbConnection->addItem(name); } /*! * \brief Shows the mqtt will settings widget, which allows the user to modify the will settings */ void ImportFileWidget::showWillSettings() { QMenu menu; QVector children; for (int i = 0; i < m_subscriptionWidget->subscriptionCount(); ++i) MQTTSubscriptionWidget::findSubscriptionLeafChildren(children, m_subscriptionWidget->topLevelSubscription(i)); QVector topics; for (int i = 0; i < children.size(); ++i) topics.append(children[i]->text(0)); MQTTWillSettingsWidget willSettingsWidget(&menu, m_willSettings, topics); connect(&willSettingsWidget, &MQTTWillSettingsWidget::applyClicked, [this, &menu, &willSettingsWidget]() { m_willSettings = willSettingsWidget.will(); menu.close(); }); QWidgetAction* widgetAction = new QWidgetAction(this); widgetAction->setDefaultWidget(&willSettingsWidget); menu.addAction(widgetAction); const QPoint pos(ui.bLWT->sizeHint().width(),ui.bLWT->sizeHint().height()); menu.exec(ui.bLWT->mapToGlobal(pos)); } void ImportFileWidget::enableWill(bool enable) { if(enable) { if(!ui.bLWT->isEnabled()) ui.bLWT->setEnabled(enable); } else ui.bLWT->setEnabled(enable); } #endif diff --git a/tests/import_export/DATASETS/DatasetsUnitTest.cpp b/tests/import_export/DATASETS/DatasetsUnitTest.cpp index faaaf2fe2..ef7fe7feb 100644 --- a/tests/import_export/DATASETS/DatasetsUnitTest.cpp +++ b/tests/import_export/DATASETS/DatasetsUnitTest.cpp @@ -1,113 +1,286 @@ /*************************************************************************** File : DatasetsUnitTest.cpp Project : LabPlot Description : Tests for Dataset related features -------------------------------------------------------------------- Copyright : (C) 2019 Kovacs Ferencz (kferike98@gmail.com) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "DatasetsUnitTest.h" #include "backend/datasources/filters/AsciiFilter.h" #include "backend/spreadsheet/Spreadsheet.h" #include "backend/datasources/DatasetHandler.h" #include "kdefrontend/DatasetModel.h" #include "kdefrontend/datasources/ImportDatasetWidget.h" +#include "kdefrontend/datasources/DatasetMetadataManagerDialog.h" #include #include #include #include #include #include #include +#include -void DatasetsUnitTest::initTestCase() { +void DatasetsUnitTest::initTestCase() { + const QString currentDir = __FILE__; + m_dataDir = currentDir.left(currentDir.lastIndexOf(QDir::separator())) + QDir::separator() + QLatin1String("data") + QDir::separator(); + + const QString baseDir = QStandardPaths::standardLocations(QStandardPaths::AppDataLocation).first(); + const QString containingDir = "labplot_data"; + m_jsonDir = baseDir + QDir::separator() + containingDir + QDir::separator(); // needed in order to have the signals triggered by SignallingUndoCommand, see LabPlot.cpp //TODO: redesign/remove this qRegisterMetaType("const AbstractAspect*"); qRegisterMetaType("const AbstractColumn*"); + + removeFiles(); + copyFiles(); +} + +void DatasetsUnitTest::copyFiles() { + const QString baseDir = QStandardPaths::standardLocations(QStandardPaths::AppDataLocation).first(); + + if(!QDir(baseDir).exists()) + QDir().mkdir(baseDir); + + if(!QDir(m_jsonDir).exists()) + QDir().mkdir(m_jsonDir); + + QFile::copy(m_dataDir + "Test.json", m_jsonDir + "Test.json"); + QFile::copy(m_dataDir + "DatasetCollections.json", m_jsonDir + "DatasetCollections.json"); +} + +//############################################################################## +//##################### Test processing metadata files ####################### +//############################################################################## + +void DatasetsUnitTest::removeFiles() { + const QString baseDir = QStandardPaths::standardLocations(QStandardPaths::AppDataLocation).first(); + const QString containingDir = "labplot_data"; + const QString jsonDir = baseDir + QDir::separator() + containingDir + QDir::separator(); + + QDir deleteDir(jsonDir); + deleteDir.setNameFilters(QStringList() << "*.*"); + deleteDir.setFilter(QDir::Files); + + for(QString dirFile : deleteDir.entryList()) { + deleteDir.remove(dirFile); + } } void DatasetsUnitTest::testCategories() { + copyFiles(); ImportDatasetWidget* importWidget = new ImportDatasetWidget(nullptr); DatasetModel* model = new DatasetModel(importWidget->getDatasetsMap()); - QCOMPARE(model->testCategories().size(), 3); - QCOMPARE(model->testCategories().contains("Test_Cat"), true); - QCOMPARE(model->testCategories().contains("Test_Cat_2"), true); - QCOMPARE(model->testCategories().contains("Test_Cat_3"), true); + QCOMPARE(model->categories("Test").size(), 3); + QCOMPARE(model->categories("Test").contains("Test_Cat"), true); + QCOMPARE(model->categories("Test").contains("Test_Cat_2"), true); + QCOMPARE(model->categories("Test").contains("Test_Cat_3"), true); delete importWidget; delete model; } void DatasetsUnitTest::testSubcategories() { ImportDatasetWidget* importWidget = new ImportDatasetWidget(nullptr); DatasetModel* model = new DatasetModel(importWidget->getDatasetsMap()); - QCOMPARE(model->testSubcategories("Test_Cat").size(), 4); - QCOMPARE(model->testSubcategories("Test_Cat").contains("Test_Subcat"), true); - QCOMPARE(model->testSubcategories("Test_Cat").contains("Test_Subcat2"), true); - QCOMPARE(model->testSubcategories("Test_Cat").contains("Test_Subcat3"), true); - QCOMPARE(model->testSubcategories("Test_Cat").contains("Test_Subcat4"), true); + QCOMPARE(model->subcategories("Test", "Test_Cat").size(), 4); + QCOMPARE(model->subcategories("Test", "Test_Cat").contains("Test_Subcat"), true); + QCOMPARE(model->subcategories("Test", "Test_Cat").contains("Test_Subcat2"), true); + QCOMPARE(model->subcategories("Test", "Test_Cat").contains("Test_Subcat3"), true); + QCOMPARE(model->subcategories("Test", "Test_Cat").contains("Test_Subcat4"), true); delete importWidget; delete model; } void DatasetsUnitTest::testDatasets() { ImportDatasetWidget* importWidget = new ImportDatasetWidget(nullptr); DatasetModel* model = new DatasetModel(importWidget->getDatasetsMap()); - QCOMPARE(model->testDatasets("Test_Cat", "Test_Subcat").size(), 1); - QCOMPARE(model->testDatasets("Test_Cat", "Test_Subcat").contains("test1"), true); - QCOMPARE(model->allTestDatasets().size(), 6); + QCOMPARE(model->datasets("Test", "Test_Cat", "Test_Subcat").size(), 1); + QCOMPARE(model->datasets("Test", "Test_Cat", "Test_Subcat").contains("test1"), true); + QCOMPARE(model->allDatasetsList().toStringList().size(), 6); delete importWidget; delete model; } +//############################################################################## +//################### Test processing and downloading dataset ################ +//############################################################################## + void DatasetsUnitTest::testProcessDataset() { ImportDatasetWidget* importWidget = new ImportDatasetWidget(nullptr); Spreadsheet* spreadsheet = new Spreadsheet("test"); DatasetHandler* datasetHandler = new DatasetHandler(spreadsheet); importWidget->processTest("Test_Cat", "Test_Subcat", "test1", datasetHandler); - QCOMPARE(spreadsheet->rowCount(), 23); - QCOMPARE(spreadsheet->columnCount(), 2); + QTimer timer; + timer.setSingleShot(true); + QEventLoop loop; + connect(datasetHandler, &DatasetHandler::downloadCompleted, &loop, &QEventLoop::quit); + connect(&timer, &QTimer::timeout, &loop, &QEventLoop::quit); + timer.start(1500); + loop.exec(); + + bool datasetFound = false; + + if(timer.isActive()){ + timer.stop(); + datasetFound = true; + } + qDebug() << datasetFound; + + QCOMPARE(datasetFound, true); + QCOMPARE(spreadsheet->rowCount(), 60); + QCOMPARE(spreadsheet->columnCount(), 4); Column* firstColumn = spreadsheet->column(0); Column* secondColumn = spreadsheet->column(1); - QCOMPARE(firstColumn->valueAt(0), "Jan 12"); - QCOMPARE(secondColumn->valueAt(3),1212000); + Column* thirdColumn = spreadsheet->column(2); + Column* fourthColumn = spreadsheet->column(3); + + QCOMPARE(firstColumn->valueAt(0), 1); + QCOMPARE(secondColumn->textAt(3),"4/86"); + QCOMPARE(thirdColumn->valueAt(0), -0.061134); + QCOMPARE(fourthColumn->valueAt(0), 0.03016); delete importWidget; delete spreadsheet; delete datasetHandler; } +//############################################################################## +//########### Test adding new datasets to the existing collection ############ +//############################################################################## + +void DatasetsUnitTest::testNewCollection() { + removeFiles(); + copyFiles(); + + ImportDatasetWidget* importWidget = new ImportDatasetWidget(nullptr); + DatasetMetadataManagerDialog* datasetDialog = new DatasetMetadataManagerDialog(nullptr, importWidget->getDatasetsMap()); + + delete importWidget; + + datasetDialog->setCollection("Test2"); + datasetDialog->setCategory("Test_Cat"); + datasetDialog->setSubcategory("Test_Subcat"); + datasetDialog->setShortName("test_new"); + datasetDialog->setFullName("New test dataset"); + datasetDialog->setDescription("This is a new test dataset"); + datasetDialog->setURL("www.testdataset.com"); + datasetDialog->updateDocument(m_jsonDir); + + importWidget = new ImportDatasetWidget(nullptr); + DatasetModel* model = new DatasetModel(importWidget->getDatasetsMap()); + + QCOMPARE(model->collections().size(), 2); + QCOMPARE(model->allDatasetsList().toStringList().size(), 7); +} + +void DatasetsUnitTest::testNewCategory() { + removeFiles(); + copyFiles(); + + ImportDatasetWidget* importWidget = new ImportDatasetWidget(nullptr); + DatasetMetadataManagerDialog* datasetDialog = new DatasetMetadataManagerDialog(nullptr, importWidget->getDatasetsMap()); + + delete importWidget; + + datasetDialog->setCollection("Test"); + datasetDialog->setCategory("Test_Cat_4"); + datasetDialog->setSubcategory("Test_Subcat"); + datasetDialog->setShortName("test_new"); + datasetDialog->setFullName("New test dataset"); + datasetDialog->setDescription("This is a new test dataset"); + datasetDialog->setURL("www.testdataset.com"); + datasetDialog->updateDocument(m_jsonDir); + + importWidget = new ImportDatasetWidget(nullptr); + DatasetModel* model = new DatasetModel(importWidget->getDatasetsMap()); + + QCOMPARE(model->categories("Test").size(), 4); + QCOMPARE(model->allDatasetsList().toStringList().size(), 7); +} + +void DatasetsUnitTest::testNewSubcategory() { + removeFiles(); + copyFiles(); + + ImportDatasetWidget* importWidget = new ImportDatasetWidget(nullptr); + DatasetMetadataManagerDialog* datasetDialog = new DatasetMetadataManagerDialog(nullptr, importWidget->getDatasetsMap()); + + delete importWidget; + + datasetDialog->setCollection("Test"); + datasetDialog->setCategory("Test_Cat"); + datasetDialog->setSubcategory("Test_Subcat5"); + datasetDialog->setShortName("test_new"); + datasetDialog->setFullName("New test dataset"); + datasetDialog->setDescription("This is a new test dataset"); + datasetDialog->setURL("www.testdataset.com"); + datasetDialog->updateDocument(m_jsonDir); + + importWidget = new ImportDatasetWidget(nullptr); + DatasetModel* model = new DatasetModel(importWidget->getDatasetsMap()); + + QCOMPARE(model->subcategories("Test", "Test_Cat").size(), 5); + QCOMPARE(model->allDatasetsList().toStringList().size(), 7); +} + +void DatasetsUnitTest::testNewDataset() { + removeFiles(); + copyFiles(); + + ImportDatasetWidget* importWidget = new ImportDatasetWidget(nullptr); + DatasetMetadataManagerDialog* datasetDialog = new DatasetMetadataManagerDialog(nullptr, importWidget->getDatasetsMap()); + + delete importWidget; + + datasetDialog->setCollection("Test"); + datasetDialog->setCategory("Test_Cat"); + datasetDialog->setSubcategory("Test_Subcat"); + datasetDialog->setShortName("test_new"); + datasetDialog->setFullName("New test dataset"); + datasetDialog->setDescription("This is a new test dataset"); + datasetDialog->setURL("www.testdataset.com"); + datasetDialog->updateDocument(m_jsonDir); + + importWidget = new ImportDatasetWidget(nullptr); + DatasetModel* model = new DatasetModel(importWidget->getDatasetsMap()); + + QCOMPARE(model->datasets("Test", "Test_Cat", "Test_Subcat").size(), 2); + QCOMPARE(model->allDatasetsList().toStringList().size(), 7); +} + QTEST_MAIN(DatasetsUnitTest) diff --git a/tests/import_export/DATASETS/DatasetsUnitTest.h b/tests/import_export/DATASETS/DatasetsUnitTest.h index c68f1b3ed..c1822744c 100644 --- a/tests/import_export/DATASETS/DatasetsUnitTest.h +++ b/tests/import_export/DATASETS/DatasetsUnitTest.h @@ -1,49 +1,63 @@ /*************************************************************************** File : DatasetsUnitTest.h Project : LabPlot Description : Tests for Dataset related features -------------------------------------------------------------------- Copyright : (C) 2019 Kovacs Ferencz (kferike98@gmail.com) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #ifndef MQTTUNITTEST_H #define MQTTUNITTEST_H #include class DatasetsUnitTest : public QObject { Q_OBJECT private slots: void initTestCase(); + //Test processing metadata files. void testCategories(); void testSubcategories(); void testDatasets(); + //Test processing and downloading dataset void testProcessDataset(); + //Test adding new datasets to the existing collection + void testNewCollection(); + void testNewCategory(); + void testNewSubcategory(); + void testNewDataset(); -private: + + +private: + void copyFiles(); + void removeFiles(); + + QString m_dataDir; + QString m_jsonDir; }; #endif diff --git a/tests/import_export/DATASETS/data/DatasetCollections.json b/tests/import_export/DATASETS/data/DatasetCollections.json new file mode 100644 index 000000000..95fe2823e --- /dev/null +++ b/tests/import_export/DATASETS/data/DatasetCollections.json @@ -0,0 +1,3 @@ +[ + "Test" +] diff --git a/tests/import_export/DATASETS/data/Test.json b/tests/import_export/DATASETS/data/Test.json new file mode 100644 index 000000000..1d8d41813 --- /dev/null +++ b/tests/import_export/DATASETS/data/Test.json @@ -0,0 +1,140 @@ +{ + "categories": [ + { + "category_name": "Test_Cat", + "subcategories": [ + { + "datasets": [ + { + "filename": "test1", + "DateTime_format": "yyyy-MM-dd", + "comment_character": "#", + "create_index_column": false, + "description": "The excess return for the Acme Cleveland Corporation are recorded along with those for all stocks listed on the New York and American Stock Exchanges were recorded over a five year period. These excess returns are relative to the return on a risk-less investment such a U.S. Treasury bills. ", + "download": "http://vincentarelbundock.github.io/Rdatasets/csv/boot/acme.csv", + "name": "Monthly Excess Returns", + "number_format": 31, + "remove_quotes": true, + "separator": ",", + "simplify_whitespaces": true, + "skip_empty_parts": false, + "use_first_row_for_vectorname": true + } + ], + "subcategory_name": "Test_Subcat" + }, + { + "datasets": [ + { + "DateTime_format": "yyyy-MM-dd hh:mm:ss.zzz", + "comment_character": "#", + "create_index_column": false, + "description": "Test", + "download": "Test.com", + "filename": "Test", + "name": "Test", + "number_format": 31, + "remove_quotes": true, + "separator": ",", + "simplify_whitespaces": false, + "skip_empty_parts": true, + "use_first_row_for_vectorname": true + } + ], + "subcategory_name": "Test_Subcat2" + }, + { + "datasets": [ + { + "DateTime_format": "yyyy-MM-dd hh:mm:ss.zzz", + "comment_character": "#", + "create_index_column": false, + "description": "Test", + "download": "Test.com", + "filename": "Test2", + "name": "Test", + "number_format": 31, + "remove_quotes": true, + "separator": ",", + "simplify_whitespaces": false, + "skip_empty_parts": true, + "use_first_row_for_vectorname": true + } + ], + "subcategory_name": "Test_Subcat3" + }, + { + "datasets": [ + { + "DateTime_format": "yyyy-MM-dd hh:mm:ss.zzz", + "comment_character": "#", + "create_index_column": false, + "description": "Test", + "download": "Test.com", + "filename": "Test3", + "name": "Test", + "number_format": 31, + "remove_quotes": true, + "separator": ",", + "simplify_whitespaces": false, + "skip_empty_parts": true, + "use_first_row_for_vectorname": true + } + ], + "subcategory_name": "Test_Subcat4" + } + ] + }, + { + "category_name": "Test_Cat_2", + "subcategories": [ + { + "datasets": [ + { + "DateTime_format": "yyyy-MM-dd hh:mm:ss.zzz", + "comment_character": "#", + "create_index_column": false, + "description": "test\n", + "download": "test.com", + "filename": "test2", + "name": "test", + "number_format": 31, + "remove_quotes": true, + "separator": ",", + "simplify_whitespaces": false, + "skip_empty_parts": true, + "use_first_row_for_vectorname": true + } + ], + "subcategory_name": "TesT_Subcat" + } + ] + }, + { + "category_name": "Test_Cat_3", + "subcategories": [ + { + "datasets": [ + { + "DateTime_format": "yyyy-MM-dd hh:mm:ss.zzz", + "comment_character": "#", + "create_index_column": false, + "description": "test", + "download": "test.com", + "filename": "test3", + "name": "test", + "number_format": 31, + "remove_quotes": true, + "separator": ",", + "simplify_whitespaces": false, + "skip_empty_parts": true, + "use_first_row_for_vectorname": true + } + ], + "subcategory_name": "test_subcat" + } + ] + } + ], + "collection_name": "Test" +}