File indexing completed on 2025-10-19 03:37:52

0001 /*
0002     File                 : WelcomeScreenHelper.cpp
0003     Project              : LabPlot
0004     Description          : Helper class for the welcome screen
0005     --------------------------------------------------------------------
0006     SPDX-FileCopyrightText: 2019 Ferencz Kovacs <kferike98@gmail.com>
0007 
0008     SPDX-License-Identifier: GPL-2.0-or-later
0009 */
0010 #include "WelcomeScreenHelper.h"
0011 #include "backend/datasources/DatasetHandler.h"
0012 #include "kdefrontend/DatasetModel.h"
0013 #include "kdefrontend/datasources/ImportDatasetWidget.h"
0014 
0015 #include <QBuffer>
0016 #include <QDebug>
0017 #include <QFile>
0018 #include <QJsonArray>
0019 #include <QJsonDocument>
0020 #include <QJsonObject>
0021 #include <QStandardPaths>
0022 #include <QTimer>
0023 #include <QUrl>
0024 
0025 #include <KCompressionDevice>
0026 #include <KConfigGroup>
0027 #include <KFilterDev>
0028 #include <KSharedConfig>
0029 
0030 /*!
0031 \class WelcomeScreenHelper
0032 \brief Helper class for the welcome screen
0033 
0034 \ingroup kdefrontend
0035 */
0036 WelcomeScreenHelper::WelcomeScreenHelper()
0037     : m_datasetWidget(new ImportDatasetWidget(0)) {
0038     m_datasetWidget->hide();
0039 
0040     QIcon icon = QIcon::fromTheme("labplot-maximize");
0041     m_maxIcon = icon.pixmap(icon.availableSizes().constFirst());
0042 
0043     icon = QIcon::fromTheme("labplot-minimize");
0044     m_minIcon = icon.pixmap(icon.availableSizes().constFirst());
0045 
0046     m_datasetModel = new DatasetModel(m_datasetWidget->getDatasetsMap());
0047 
0048     loadConfig();
0049     processExampleProjects();
0050 }
0051 
0052 WelcomeScreenHelper::~WelcomeScreenHelper() {
0053     // save width&height ratio values
0054     KConfigGroup conf(KSharedConfig::openConfig(), "WelcomeScreenHelper");
0055 
0056     int widthCount = m_widthScale.size();
0057     conf.writeEntry("width_count", widthCount);
0058     int currentWidthIndex = 0;
0059 
0060     for (auto item = m_widthScale.begin(); item != m_widthScale.end() && currentWidthIndex < widthCount; ++item) {
0061         conf.writeEntry("widthName_" + QString::number(currentWidthIndex), item.key());
0062         conf.writeEntry("widthValue_" + QString::number(currentWidthIndex), QString::number(item.value()));
0063         currentWidthIndex++;
0064     }
0065 
0066     int heightCount = m_heightScale.size();
0067     conf.writeEntry("height_count", widthCount);
0068     int currentHeightIndex = 0;
0069 
0070     for (auto item = m_heightScale.begin(); item != m_heightScale.end() && currentHeightIndex < heightCount; ++item) {
0071         conf.writeEntry("heightName_" + QString::number(currentHeightIndex), item.key());
0072         conf.writeEntry("heightValue_" + QString::number(currentHeightIndex), QString::number(item.value()));
0073         currentHeightIndex++;
0074     }
0075 }
0076 
0077 /**
0078  * @brief Loads the saved configuration
0079  */
0080 void WelcomeScreenHelper::loadConfig() {
0081     KConfigGroup conf(KSharedConfig::openConfig(), "WelcomeScreenHelper");
0082 
0083     int widthCount = conf.readEntry("width_count", -1);
0084     for (int i = 0; i < widthCount; ++i) {
0085         QString id = conf.readEntry("widthName_" + QString::number(i), "");
0086         double value = QString(conf.readEntry("widthValue_" + QString::number(i), "-1")).toDouble();
0087 
0088         if (!id.isEmpty() && value != -1)
0089             m_widthScale[id] = value;
0090     }
0091 
0092     int heightCount = conf.readEntry("height_count", -1);
0093     for (int i = 0; i < heightCount; ++i) {
0094         QString id = conf.readEntry("heightName_" + QString::number(i), "");
0095         double value = QString(conf.readEntry("heightValue_" + QString::number(i), "-1")).toDouble();
0096 
0097         if (!id.isEmpty() && value != -1)
0098             m_heightScale[id] = value;
0099     }
0100 }
0101 
0102 /**
0103  * @brief Handles a dataset being clicked in the dataset section of the WelcomeScreen.
0104  * Initiates listing information about the dataset in the previously mentioned section.
0105  *
0106  * @param category the category the dataset belongs to
0107  * @param subcategory the subcategory the dataset belongs to
0108  * @param datasetName the name of the dataset
0109  */
0110 void WelcomeScreenHelper::datasetClicked(const QString& category, const QString& subcategory, const QString& datasetName) {
0111     m_datasetWidget->setCollection("All");
0112     m_datasetWidget->setCategory(category);
0113     m_datasetWidget->setSubcategory(subcategory);
0114     m_datasetWidget->setDataset(datasetName);
0115     m_spreadsheet.reset(new Spreadsheet(i18n("Dataset%1", 1)));
0116 
0117     if (m_datasetHandler)
0118         delete m_datasetHandler;
0119     m_datasetHandler = new DatasetHandler(m_spreadsheet.get());
0120 
0121     m_datasetWidget->import(m_datasetHandler);
0122 
0123     QTimer timer;
0124     timer.setSingleShot(true);
0125     QEventLoop loop;
0126     connect(m_datasetHandler, &DatasetHandler::downloadCompleted, &loop, &QEventLoop::quit);
0127     connect(&timer, &QTimer::timeout, &loop, &QEventLoop::quit);
0128     timer.start(1500);
0129     loop.exec();
0130 
0131     if (timer.isActive()) {
0132         timer.stop();
0133         Q_EMIT datasetFound();
0134     } else
0135         Q_EMIT datasetNotFound();
0136 }
0137 
0138 /**
0139  * @brief Returns the dataset's full name.
0140  */
0141 QVariant WelcomeScreenHelper::datasetName() {
0142     return QVariant(m_spreadsheet->name());
0143 }
0144 
0145 /**
0146  * @brief Returns the dataset's descripton.
0147  */
0148 QVariant WelcomeScreenHelper::datasetDescription() {
0149     return QVariant(m_spreadsheet->comment());
0150 }
0151 
0152 /**
0153  * @brief Returns the number of the dataset's columns.
0154  */
0155 QVariant WelcomeScreenHelper::datasetColumns() {
0156     return QVariant(m_spreadsheet->columnCount());
0157 }
0158 
0159 /**
0160  * @brief Returns the number of the dataset's rows.
0161  */
0162 QVariant WelcomeScreenHelper::datasetRows() {
0163     return QVariant(m_spreadsheet->rowCount());
0164 }
0165 
0166 /**
0167  * @brief Returns a pointer to the spreadsheet in which the data of the dataset was loaded.
0168  */
0169 Spreadsheet* WelcomeScreenHelper::releaseConfiguredSpreadsheet() {
0170     return m_spreadsheet.release();
0171 }
0172 
0173 /**
0174  * @brief Returns the thumbnail image saved with the project.
0175  * @param url the path to the saved project file.
0176  */
0177 QVariant WelcomeScreenHelper::getProjectThumbnail(const QUrl& url) {
0178     QString filename;
0179     if (url.isLocalFile()) // fix for Windows
0180         filename = url.toLocalFile();
0181     else
0182         filename = url.path();
0183 
0184     QIODevice* file;
0185     // first try gzip compression, because projects can be gzipped and end with .lml
0186     if (filename.endsWith(QLatin1String(".lml"), Qt::CaseInsensitive))
0187         file = new KCompressionDevice(filename, KFilterDev::compressionTypeForMimeType("application/x-gzip"));
0188     else // opens filename using file ending
0189         file = new KFilterDev(filename);
0190 
0191     if (!file)
0192         file = new QFile(filename);
0193 
0194     if (!file->open(QIODevice::ReadOnly)) {
0195         qDebug() << "Could not open file for reading.";
0196         return QVariant();
0197     }
0198 
0199     char c;
0200     bool rc = file->getChar(&c);
0201     if (!rc) {
0202         qDebug() << "The project file is empty.";
0203         file->close();
0204         delete file;
0205         return false;
0206     }
0207     file->seek(0);
0208 
0209     // parse XML
0210     XmlStreamReader reader(file);
0211     while (!(reader.isStartDocument() || reader.atEnd()))
0212         reader.readNext();
0213 
0214     if (!(reader.atEnd())) {
0215         if (!reader.skipToNextTag())
0216             return false;
0217 
0218         if (reader.name() == "project") {
0219             QString thumbnail = reader.attributes().value("thumbnail").toString();
0220 
0221             thumbnail.prepend("data:image/jpg;base64,");
0222             return QVariant(thumbnail);
0223         }
0224     }
0225     return QVariant();
0226 }
0227 
0228 /**
0229  * @brief Returns a pointer to the datasetModel of the class.
0230  */
0231 DatasetModel* WelcomeScreenHelper::getDatasetModel() {
0232     return m_datasetModel;
0233 }
0234 
0235 /**
0236  * @brief Processes metadata file containing example projects.
0237  */
0238 void WelcomeScreenHelper::processExampleProjects() {
0239     const QString filePath = QStandardPaths::locate(QStandardPaths::AppDataLocation, "example_projects/example_projects.json");
0240     QFile file(filePath);
0241 
0242     if (file.open(QIODevice::ReadOnly)) {
0243         QJsonDocument document = QJsonDocument::fromJson(file.readAll());
0244         QJsonArray exampleArray = document.array();
0245 
0246         // processing examples
0247         for (int i = 0; i < exampleArray.size(); ++i) {
0248             const QJsonObject currentExample = exampleArray[i].toObject();
0249 
0250             const QString exampleName = currentExample.value("name").toString();
0251             if (m_projectNameList.contains(exampleName)) {
0252                 qDebug() << "There is already an example file with this name";
0253             } else {
0254                 m_projectNameList.append(exampleName);
0255                 const QString exampleFile = currentExample.value("fileName").toString();
0256                 m_pathMap[exampleName] = exampleFile;
0257 
0258                 // processing tags
0259                 const QJsonArray tags = currentExample.value("tags").toArray();
0260                 for (int j = 0; j < tags.size(); ++j) {
0261                     QString tagName = tags[j].toString();
0262                     m_tagMap[tagName].append(exampleName);
0263                     m_datasetTag[exampleName].append(tagName);
0264                 }
0265             }
0266         }
0267 
0268         file.close();
0269     } else {
0270         qDebug("Couldn't open dataset category file");
0271     }
0272 }
0273 
0274 /**
0275  * @brief Returns in string format the thumbnail for the given example file
0276  */
0277 QVariant WelcomeScreenHelper::getExampleProjectThumbnail(const QString& exampleName) {
0278     const QString filePath = QStandardPaths::locate(QStandardPaths::AppDataLocation, "example_projects/" + m_pathMap[exampleName]);
0279     return getProjectThumbnail(filePath);
0280 }
0281 
0282 /**
0283  * @brief Returns the list of example projects.
0284  */
0285 QVariant WelcomeScreenHelper::getExampleProjects() {
0286     return QVariant(m_projectNameList);
0287 }
0288 
0289 /**
0290  * @brief Returns the tags of the given example project.
0291  */
0292 QVariant WelcomeScreenHelper::getExampleProjectTags(const QString& exampleName) {
0293     QString tags;
0294     const QStringList& tagList = m_datasetTag[exampleName];
0295 
0296     for (int i = 0; i < tagList.size(); i++) {
0297         tags.append(tagList[i]);
0298         if (i < tagList.size() - 1)
0299             tags.append(", ");
0300     }
0301 
0302     return QVariant(tags);
0303 }
0304 
0305 /**
0306  * @brief Handles an example project being clicked in the welcome screen.
0307  * @param exampleName the name of the clicked example project
0308  */
0309 void WelcomeScreenHelper::exampleProjectClicked(const QString& exampleName) {
0310     QString path = QStandardPaths::locate(QStandardPaths::AppDataLocation, "example_projects/" + m_pathMap[exampleName]);
0311     Q_EMIT openExampleProject(path);
0312 }
0313 
0314 /**
0315  * @brief Searches among the example projects based on the text introduce in the search bar of the welcome screen.
0316  * @param searchtext - the text based on which we'll have to search
0317  * @return Returns the results
0318  */
0319 QVariant WelcomeScreenHelper::searchExampleProjects(const QString& searchtext) {
0320     QStringList results;
0321 
0322     // search based on tags
0323     for (auto tag = m_tagMap.begin(); tag != m_tagMap.end(); ++tag) {
0324         if (tag.key().contains(searchtext)) {
0325             for (QString example : tag.value()) {
0326                 if (!results.contains(example))
0327                     results.append(example);
0328             }
0329         }
0330     }
0331 
0332     // search based on name
0333     for (QString example : m_projectNameList) {
0334         if (example.contains(searchtext) && !results.contains(example))
0335             results.append(example);
0336     }
0337 
0338     return QVariant(results);
0339 }
0340 
0341 /**
0342  * @brief Sets the width scale for the given section, which will be saved.
0343  */
0344 void WelcomeScreenHelper::setWidthScale(const QString& sectionID, double scale) {
0345     m_widthScale[sectionID] = scale;
0346 }
0347 
0348 /**
0349  * @brief Sets the height scale for the given section, which will be saved.
0350  */
0351 void WelcomeScreenHelper::setHeightScale(const QString& sectionID, double scale) {
0352     m_heightScale[sectionID] = scale;
0353 }
0354 
0355 /**
0356  * @brief Returns the width scale for the given section.
0357  */
0358 QVariant WelcomeScreenHelper::getWidthScale(const QString& sectionID) {
0359     if (m_widthScale.contains(sectionID))
0360         return QVariant(m_widthScale[sectionID]);
0361 
0362     return QVariant(-1);
0363 }
0364 
0365 /**
0366  * @brief Returns the height scale for the given section.
0367  */
0368 QVariant WelcomeScreenHelper::getHeightScale(const QString& sectionID) {
0369     if (m_heightScale.contains(sectionID))
0370         return QVariant(m_heightScale[sectionID]);
0371 
0372     return QVariant(-1);
0373 }
0374 
0375 /**
0376  * @brief Returns the maximize icon.
0377  */
0378 QVariant WelcomeScreenHelper::getMaxIcon() {
0379     QByteArray bArray;
0380     QBuffer buffer(&bArray);
0381     buffer.open(QIODevice::WriteOnly);
0382     m_maxIcon.save(&buffer, "PNG");
0383     QString image = QString::fromLatin1(bArray.toBase64().data());
0384     image.prepend("data:image/png;base64,");
0385     return QVariant(image);
0386 }
0387 
0388 /**
0389  * @brief Returns the minimize icon.
0390  */
0391 QVariant WelcomeScreenHelper::getMinIcon() {
0392     QByteArray bArray;
0393     QBuffer buffer(&bArray);
0394     buffer.open(QIODevice::WriteOnly);
0395     m_minIcon.save(&buffer, "PNG");
0396     QString image = QString::fromLatin1(bArray.toBase64().data());
0397     image.prepend("data:image/png;base64,");
0398     return QVariant(image);
0399 }
0400 
0401 /**
0402  * @brief Returns the go-back icon.
0403  */
0404 QVariant WelcomeScreenHelper::getBackIcon() {
0405     QIcon icon = QIcon::fromTheme("labplot-back");
0406     QPixmap pixmap = icon.pixmap(icon.availableSizes().constFirst());
0407     QByteArray bArray;
0408     QBuffer buffer(&bArray);
0409     buffer.open(QIODevice::WriteOnly);
0410     pixmap.save(&buffer, "PNG");
0411     QString image = QString::fromLatin1(bArray.toBase64().data());
0412     image.prepend("data:image/png;base64,");
0413     return QVariant(image);
0414 }
0415 
0416 /**
0417  * @brief Returns the go-forward icon.
0418  */
0419 QVariant WelcomeScreenHelper::getForwardIcon() {
0420     QIcon icon = QIcon::fromTheme("labplot-forward");
0421     QPixmap pixmap = icon.pixmap(icon.availableSizes().constFirst());
0422     QByteArray bArray;
0423     QBuffer buffer(&bArray);
0424     buffer.open(QIODevice::WriteOnly);
0425     pixmap.save(&buffer, "PNG");
0426     QString image = QString::fromLatin1(bArray.toBase64().data());
0427     image.prepend("data:image/png;base64,");
0428     return QVariant(image);
0429 }