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 }