File indexing completed on 2024-12-22 04:15:11

0001 /*
0002  * SPDX-FileCopyrightText: 2021 Agata Cacko <cacko.azh@gmail.com>
0003  *
0004  * SPDX-License-Identifier: GPL-3.0-or-later
0005  */
0006 
0007 #include "ResourceImporter.h"
0008 
0009 #include <QItemSelection>
0010 #include <QPainter>
0011 #include <QStandardPaths>
0012 #include <QMessageBox>
0013 
0014 #include <KoFileDialog.h>
0015 
0016 #include <KisResourceModel.h>
0017 #include <kis_assert.h>
0018 #include <KisResourceTypes.h>
0019 #include <KisMainWindow.h>
0020 #include <KisResourceTypeModel.h>
0021 #include <KisResourceLoaderRegistry.h>
0022 #include <KisMimeDatabase.h>
0023 #include <KisStorageModel.h>
0024 #include <KisResourceLocator.h>
0025 #include <kis_config.h>
0026 #include <KisResourceUserOperations.h>
0027 
0028 #include "DlgResourceTypeForFile.h"
0029 
0030 // ------------ Warnings dialog ---------------
0031 class FailureReasonsDialog : public KoDialog
0032 {
0033 
0034 public:
0035 
0036     FailureReasonsDialog(QWidget* parent, QMap<ResourceImporter::ImportFailureReason, QStringList> failureReasons)
0037         : KoDialog(parent)
0038     {
0039         setCaption(i18n("Import of some files failed"));
0040         setBaseSize(QSize(0, 0));
0041         setButtons(ButtonCode::Ok);
0042 
0043         QVBoxLayout* layout = new QVBoxLayout(parent);
0044         QWidget* widget = new QWidget(parent);
0045         widget->setBaseSize(QSize(0, 0));
0046 
0047         QList<ResourceImporter::ImportFailureReason> keys = failureReasons.keys();
0048         for (int i = 0; i < keys.size(); i++) {
0049             if (failureReasons[keys[i]].size() > 0) {
0050                 QLabel* label = new QLabel(widget);
0051                 QString text;
0052                 if (keys[i] == ResourceImporter::ResourceCannotBeLoaded) {
0053                     label->setText(i18nc("Warning message after failed attempt to import resources, after this label there is a box with a list of files",
0054                                          "The following files couldn't be opened as resources:"));
0055                 } else if (keys[i] == ResourceImporter::MimetypeResourceTypeUnknown) {
0056                     label->setText(i18nc("Warning message after failed attempt to import resources, after this label there is a box with a list of files",
0057                                          "The resource type of following files is unknown:"));
0058                 } else if (keys[i] == ResourceImporter::CancelledByTheUser) {
0059                     label->setText(i18nc("Warning message after failed attempt to import resources, after this label there is a box with a list of files",
0060                                          "The import of following files has been cancelled:"));
0061                 } else if (keys[i] == ResourceImporter::StorageAlreadyExists) {
0062                     label->setText(i18nc("Warning message after failed attempt to import resources, after this label there is a box with a list of files",
0063                                          "A resources bundle, an ASL or an ABR file with the same name already exists in the resources folder:"));
0064                 }
0065 
0066                 label->setWordWrap(true);
0067                 layout->addWidget(label);
0068 
0069 
0070                 QPlainTextEdit* textBox = new QPlainTextEdit(widget);
0071                 textBox->setBaseSize(0, 0);
0072                 for (int j = 0; j < failureReasons[keys[i]].size(); j++) {
0073                     textBox->appendPlainText(failureReasons[keys[i]][j]);
0074                 }
0075                 textBox->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Maximum);
0076 
0077                 layout->addWidget(textBox, 0);
0078 
0079             }
0080         }
0081 
0082 
0083         widget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Maximum);
0084         widget->setLayout(layout);
0085         widget->setGeometry(QRect(QPoint(0,0), layout->sizeHint()));
0086         this->setMainWidget(widget);
0087 
0088         this->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Maximum);
0089     }
0090 
0091 };
0092 
0093 
0094 
0095 // ------------ Resource Importer --------------
0096 
0097 ResourceImporter::ResourceImporter(QWidget *parent)
0098     : m_widgetParent(parent)
0099 {
0100     initialize();
0101 }
0102 
0103 
0104 ResourceImporter::~ResourceImporter()
0105 {
0106     qDeleteAll(m_resourceModelsForResourceType);
0107 }
0108 
0109 void ResourceImporter::importResources(QString startPath)
0110 {
0111     // TODO: remove debug after finishing the importer
0112     bool debug = false;
0113 
0114     if (startPath.isEmpty()) {
0115         startPath = QStandardPaths::writableLocation(QStandardPaths::HomeLocation);
0116     }
0117 
0118     KoFileDialog dialog(m_widgetParent, KoFileDialog::OpenFiles, "krita_resources");
0119     dialog.setDefaultDir(startPath);
0120     dialog.setCaption(i18nc("Resource Importer file dialog title", "Import Resources and Resource Libraries"));
0121     dialog.setMimeTypeFilters(m_allMimetypes);
0122     QStringList filenames = dialog.filenames();
0123 
0124 
0125     if (debug) qCritical() << "All filenames: " << filenames;
0126 
0127 
0128     QMap<QString, QString> troublesomeFiles;
0129     QStringList troublesomeMimetypes;
0130 
0131     QMap<QString, QStringList> troublesomeFilesPerMimetype;
0132 
0133 
0134 
0135     QMap<QString, QString> resourceTypePerFile;
0136 
0137     QStringList successfullyImportedFiles;
0138     QMap<ImportFailureReason, QStringList> failedFiles;
0139     failedFiles.insert(StorageAlreadyExists, QStringList());
0140     failedFiles.insert(MimetypeResourceTypeUnknown, QStringList());
0141     failedFiles.insert(ResourceCannotBeLoaded, QStringList());
0142     failedFiles.insert(CancelledByTheUser, QStringList());
0143 
0144 
0145     for (int i = 0; i < filenames.count(); i++) {
0146         QString mimetype = KisMimeDatabase::mimeTypeForFile(filenames[i]);
0147         if (m_storagesMimetypes.contains(mimetype)) {
0148             if (debug) qCritical() << "We're loading a storage here: " << filenames[i];
0149 
0150             // import the bundle/asl/abr storage
0151 
0152             KisStorageModel::StorageImportOption importMode = KisStorageModel::None;
0153             if (debug) qCritical() << "checking for storage" << filenames[i] << QFileInfo(filenames[i]).fileName();
0154             // TODO: three options in case of the same filename: cancel; overwrite; rename;
0155             // but for now, let's just skip
0156             bool skip = false;
0157             if (KisResourceLocator::instance()->hasStorage(QFileInfo(filenames[i]).fileName())) {
0158                 skip = true;
0159                 /*
0160                 if (QMessageBox::warning(m_widgetParent, i18nc("@title:window", "Warning"),
0161                                          i18n("There is already a resource library with this name installed. Do you want to overwrite it? Resource library name: "),
0162                                          QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Cancel) {
0163                     importMode = KisStorageModel::Rename;
0164                 }
0165                 else {
0166                     importMode = KisStorageModel::Overwrite;
0167                 }
0168                 */
0169             }
0170             if (skip || !KisStorageModel::instance()->importStorage(filenames[i], importMode)) {
0171                 failedFiles[StorageAlreadyExists] << filenames[i];
0172             } else {
0173                 successfullyImportedFiles << filenames[i];
0174             }
0175 
0176         } else if (m_zipMimetypes.contains(mimetype)) {
0177             // TODO: unpack a zip file and then proceed the same way like with others
0178         } else if (m_resourceTypesForMimetype.contains(mimetype)) {
0179             bool differentResourceTypes = m_resourceTypesForMimetype.value(mimetype).count() > 1;
0180             if (differentResourceTypes) {
0181                 if (debug) qCritical() << "We have a difficult situation here!" << filenames[i];
0182 
0183                 troublesomeFiles.insert(filenames[i], mimetype);
0184                 if (!troublesomeMimetypes.contains(mimetype)) {
0185                     troublesomeMimetypes.append(mimetype);
0186                 }
0187 
0188                 if (!troublesomeFilesPerMimetype.contains(mimetype)) {
0189                     troublesomeFilesPerMimetype.insert(mimetype, QStringList());
0190                 }
0191                 troublesomeFilesPerMimetype[mimetype] = troublesomeFilesPerMimetype[mimetype] << filenames[i];
0192 
0193             } else {
0194                 if (debug) qCritical() << "We're loading a" << mimetype << " here: " << filenames[i];
0195                 resourceTypePerFile.insert(filenames[i], m_resourceTypesForMimetype[mimetype][0]);
0196             }
0197         } else {
0198             failedFiles[MimetypeResourceTypeUnknown] << filenames[i];
0199         }
0200 
0201     }
0202 
0203     QMap<QString, QStringList> troublesomeResourceTypesPerMimetype;
0204     for (int i = 0; i < troublesomeMimetypes.size(); i++) {
0205         troublesomeResourceTypesPerMimetype.insert(troublesomeMimetypes[i], m_resourceTypesForMimetype[troublesomeMimetypes[i]]);
0206     }
0207 
0208     if (troublesomeMimetypes.count() > 0) {
0209         DlgResourceTypeForFile dlg(m_widgetParent, troublesomeResourceTypesPerMimetype);
0210         if (dlg.exec() == QDialog::Accepted) {
0211             for (int i = 0; i < troublesomeMimetypes.count(); i++) {
0212                 QString resourceType = dlg.getResourceTypeForMimetype(troublesomeMimetypes[i]);
0213                 QString mimetype = troublesomeMimetypes[i];
0214                 if (troublesomeFilesPerMimetype.contains(mimetype)) {
0215                     for (int j = 0; j < troublesomeFilesPerMimetype[mimetype].size(); j++) {
0216                         resourceTypePerFile.insert(troublesomeFilesPerMimetype[mimetype][j], resourceType);
0217                     }
0218                 }
0219             }
0220         } else {
0221             for (int i = 0; i < troublesomeMimetypes.count(); i++) {
0222                 for (int j = 0; j < troublesomeFilesPerMimetype[troublesomeMimetypes[i]].size(); j++) {
0223                     failedFiles[CancelledByTheUser] << troublesomeFilesPerMimetype[troublesomeMimetypes[i]][j];
0224                 }
0225             }
0226         }
0227     }
0228 
0229 
0230 
0231 
0232     if (debug) qCritical() <<  "Resource types for mimetype: ";
0233 
0234     for (int i = 0; i < m_resourceTypesForMimetype.keys().size(); i++) {
0235         if (m_resourceTypesForMimetype[m_resourceTypesForMimetype.keys()[i]].size() > 1) {
0236             if (debug) qCritical() << m_resourceTypesForMimetype.keys()[i] << m_resourceTypesForMimetype[m_resourceTypesForMimetype.keys()[i]];
0237         }
0238     }
0239 
0240     QString resourceLocationBase = KisResourceLocator::instance()->resourceLocationBase();
0241 
0242     QStringList resourceFiles = resourceTypePerFile.keys();
0243     for (int i = 0; i < resourceFiles.count(); i++) {
0244         QString resourceType = resourceTypePerFile[resourceFiles[i]];
0245         if (debug) qCritical() << "Loading " << resourceFiles[i] << "as" << resourceType;
0246         if (m_resourceModelsForResourceType.contains(resourceType)) {
0247             if (debug) qCritical() << "We do have a resource model for that!";
0248             KisResourceModel* model = m_resourceModelsForResourceType[resourceType];
0249 
0250             bool allowOverwrite = false;
0251 
0252             // first check if we are going to overwrite anything
0253             if (model->importWillOverwriteResource(resourceFiles[i])) {
0254                 if(!KisResourceUserOperations::userAllowsOverwrite(m_widgetParent, resourceFiles[i])) {
0255                     continue;
0256                 } else {
0257                     allowOverwrite = true;
0258                 }
0259             }
0260 
0261             KoResourceSP res = model->importResourceFile(resourceFiles[i], allowOverwrite);
0262             if (res.isNull()) {
0263                 if (debug) qCritical() << "But the resource is null :( ";
0264                 failedFiles[ResourceCannotBeLoaded] << resourceFiles[i];
0265             } else {
0266                 if (debug) qCritical() << "The resource isn't null, great!";
0267                 successfullyImportedFiles << resourceFiles[i];
0268             }
0269         } else {
0270             failedFiles[MimetypeResourceTypeUnknown] << resourceFiles[i];
0271         }
0272     }
0273 
0274     if (debug) qCritical() << "Failed files: " << failedFiles;
0275     if (debug) qCritical() << "Successfully imported files: " << successfullyImportedFiles;
0276 
0277     QList<ImportFailureReason> keys = failedFiles.keys();
0278     int failedFilesCount = 0;
0279     for (int i = 0; i < keys.size(); i++) {
0280         failedFilesCount += failedFiles[keys[i]].size();
0281     }
0282     if (failedFilesCount > 0) {
0283         FailureReasonsDialog dlg(m_widgetParent, failedFiles);
0284         dlg.exec();
0285     }
0286 
0287 }
0288 
0289 void ResourceImporter::prepareTypesMaps()
0290 {
0291     m_storagesMimetypes = QStringList() << "application/x-krita-bundle"
0292                                         << "image/x-adobe-brushlibrary"
0293                                         << "application/x-photoshop-style-library";
0294 
0295     m_zipMimetypes = QStringList(); // << "application/zip"; // TODO: implement for zip archives
0296 
0297     QStringList resourceTypes;
0298     KisResourceTypeModel model;
0299     for (int i = 0; i < model.rowCount(); i++) {
0300         QModelIndex idx = model.index(i, 0);
0301         resourceTypes << model.data(idx, Qt::UserRole + KisResourceTypeModel::ResourceType).toString();
0302     }
0303     qCritical() << "resource types = " << resourceTypes;
0304 
0305     m_mimetypeForResourceType.clear();
0306     m_resourceTypesForMimetype.clear();
0307 
0308     QStringList mimetypes;
0309     for (int i = 0; i < resourceTypes.count(); i++) {
0310         QStringList mime = KisResourceLoaderRegistry::instance()->mimeTypes(resourceTypes[i]);
0311 
0312         // remove mypaint brushes for now, because we'd need to figure out an UX for them
0313         // probably 1) remove all the _prev.png files from a list to import as patterns or brush tips
0314         // and 2) when importing a .myb file, find a proper .png file alongside it to import together
0315         mime.removeAll("application/x-mypaint-brush");
0316 
0317         m_mimetypeForResourceType.insert(resourceTypes[i], mime);
0318         mimetypes << mime;
0319         for (int j = 0; j < mime.count(); j++) {
0320             if (m_resourceTypesForMimetype.contains(mime[j])) {
0321                 if (!m_resourceTypesForMimetype[mime[j]].contains(resourceTypes[i])) {
0322                     m_resourceTypesForMimetype[mime[j]].append(resourceTypes[i]);
0323                 }
0324             } else {
0325                 m_resourceTypesForMimetype.insert(mime[j], QStringList() << resourceTypes[i]);
0326             }
0327         }
0328     }
0329 
0330     m_allMimetypes << m_storagesMimetypes;
0331     m_allMimetypes << m_zipMimetypes;
0332     m_allMimetypes << mimetypes;
0333 
0334 
0335     m_allMimetypes.removeDuplicates();
0336 
0337 
0338 
0339 }
0340 
0341 void ResourceImporter::prepareModelsMap()
0342 {
0343     KisResourceTypeModel model;
0344     for (int i = 0; i < model.rowCount(); i++) {
0345         QModelIndex idx = model.index(i, 0);
0346         QString resourceType = model.data(idx, Qt::UserRole + KisResourceTypeModel::ResourceType).toString();
0347         if (!m_resourceModelsForResourceType.contains(resourceType)) {
0348             KisResourceModel* model = new KisResourceModel(resourceType);
0349             if (model) {
0350                 m_resourceModelsForResourceType.insert(resourceType, model);
0351             } else {
0352                 dbgResources << "There is no KisResourceModel available for " << resourceType;
0353             }
0354         }
0355     }
0356 }
0357 
0358 void ResourceImporter::initialize()
0359 {
0360     if (!m_isInitialized) {
0361         prepareTypesMaps();
0362         prepareModelsMap();
0363         m_isInitialized = true;
0364     }
0365 }
0366 
0367 
0368 
0369 
0370 
0371 
0372 
0373