File indexing completed on 2024-05-05 04:22:02
0001 // SPDX-FileCopyrightText: 2003-2020 The KPhotoAlbum Development Team 0002 // SPDX-FileCopyrightText: 2022-2023 Johannes Zarl-Zierl <johannes@zarl-zierl.at> 0003 // 0004 // SPDX-License-Identifier: GPL-2.0-or-later 0005 0006 #include "ImportDialog.h" 0007 0008 #include "ImageRow.h" 0009 #include "ImportMatcher.h" 0010 #include "KimFileReader.h" 0011 #include "MD5CheckPage.h" 0012 0013 #include <DB/ImageDB.h> 0014 #include <DB/ImageInfo.h> 0015 #include <kpabase/SettingsData.h> 0016 0017 #include <KHelpClient> 0018 #include <KLocalizedString> 0019 #include <KMessageBox> 0020 #include <QCheckBox> 0021 #include <QComboBox> 0022 #include <QDir> 0023 #include <QFileDialog> 0024 #include <QGridLayout> 0025 #include <QHBoxLayout> 0026 #include <QLabel> 0027 #include <QLineEdit> 0028 #include <QPixmap> 0029 #include <QPushButton> 0030 #include <QScrollArea> 0031 #include <kwidgetsaddons_version.h> 0032 0033 using Utilities::StringSet; 0034 0035 class QPushButton; 0036 using namespace ImportExport; 0037 0038 ImportDialog::ImportDialog(QWidget *parent) 0039 : KAssistantDialog(parent) 0040 , m_hasFilled(false) 0041 , m_md5CheckPage(nullptr) 0042 { 0043 } 0044 0045 bool ImportDialog::exec(KimFileReader *kimFileReader, const QUrl &kimFileURL) 0046 { 0047 m_kimFileReader = kimFileReader; 0048 0049 if (kimFileURL.isLocalFile()) { 0050 QDir cwd; 0051 // convert relative local path to absolute 0052 m_kimFile = QUrl::fromLocalFile(cwd.absoluteFilePath(kimFileURL.toLocalFile())) 0053 .adjusted(QUrl::NormalizePathSegments); 0054 } else { 0055 m_kimFile = kimFileURL; 0056 } 0057 0058 QByteArray indexXML = m_kimFileReader->indexXML(); 0059 if (indexXML.isNull()) 0060 return false; 0061 0062 bool ok = readFile(indexXML); 0063 if (!ok) 0064 return false; 0065 0066 setupPages(); 0067 0068 return KAssistantDialog::exec(); 0069 } 0070 0071 bool ImportDialog::readFile(const QByteArray &data) 0072 { 0073 DB::ReaderPtr reader = DB::ReaderPtr(new DB::XmlReader(DB::ImageDB::instance()->uiDelegate(), 0074 m_kimFile.toDisplayString())); 0075 reader->addData(data); 0076 0077 DB::ElementInfo info = reader->readNextStartOrStopElement(QString::fromUtf8("KimDaBa-export")); 0078 if (!info.isStartToken) 0079 reader->complainStartElementExpected(QString::fromUtf8("KimDaBa-export")); 0080 0081 // Read source 0082 QString source = reader->attribute(QString::fromUtf8("location")).toLower(); 0083 if (source != QString::fromLatin1("inline") && source != QString::fromLatin1("external")) { 0084 KMessageBox::error(this, i18n("<p>XML file did not specify the source of the images, " 0085 "this is a strong indication that the file is corrupted</p>")); 0086 return false; 0087 } 0088 0089 m_externalSource = (source == QString::fromLatin1("external")); 0090 0091 // Read base url 0092 m_baseUrl = QUrl::fromUserInput(reader->attribute(QString::fromLatin1("baseurl"))); 0093 0094 while (reader->readNextStartOrStopElement(QString::fromUtf8("image")).isStartToken) { 0095 const DB::FileName fileName = DB::FileName::fromRelativePath(reader->attribute(QString::fromUtf8("file"))); 0096 DB::ImageInfoPtr info = DB::ImageDB::createImageInfo(fileName, reader); 0097 m_images.append(info); 0098 } 0099 // the while loop already read the end element, so we tell readEndElement to not read the next token: 0100 reader->readEndElement(false); 0101 0102 return true; 0103 } 0104 0105 void ImportDialog::setupPages() 0106 { 0107 createIntroduction(); 0108 createImagesPage(); 0109 createDestination(); 0110 createCategoryPages(); 0111 connect(this, &ImportDialog::currentPageChanged, this, &ImportDialog::updateNextButtonState); 0112 QPushButton *helpButton = buttonBox()->button(QDialogButtonBox::Help); 0113 connect(helpButton, &QPushButton::clicked, this, &ImportDialog::slotHelp); 0114 } 0115 0116 void ImportDialog::createIntroduction() 0117 { 0118 QString txt = i18n("<h1><font size=\"+2\">Welcome to KPhotoAlbum Import</font></h1>" 0119 "This wizard will take you through the steps of an import operation. The steps are: " 0120 "<ol><li>First you must select which images you want to import from the export file. " 0121 "You do so by selecting the checkbox next to the image.</li>" 0122 "<li>Next you must tell KPhotoAlbum in which folder to put the images. This folder must " 0123 "of course be contained by the base folder KPhotoAlbum uses for images. " 0124 "KPhotoAlbum will take care to avoid name clashes</li>" 0125 "<li>The next step is to specify which categories you want to import (People, Places, ... ) " 0126 "and also tell KPhotoAlbum how to match the categories from the file to your categories. " 0127 "Imagine you load from a file, where a category is called <b>Blomst</b> (which is the " 0128 "Danish word for flower), then you would likely want to match this with your category, which might be " 0129 "called <b>Blume</b> (which is the German word for flower) - of course given you are German.</li>" 0130 "<li>The final steps, is matching the individual tokens from the categories. I may call myself <b>Jesper</b> " 0131 "in my image database, while you want to call me by my full name, namely <b>Jesper K. Pedersen</b>. " 0132 "In this step non matches will be highlighted in red, so you can see which tokens was not found in your " 0133 "database, or which tokens was only a partial match.</li></ol>"); 0134 0135 QLabel *intro = new QLabel(txt, this); 0136 intro->setWordWrap(true); 0137 addPage(intro, i18nc("@title:tab introduction page", "Introduction")); 0138 } 0139 0140 void ImportDialog::createImagesPage() 0141 { 0142 QScrollArea *top = new QScrollArea; 0143 top->setWidgetResizable(true); 0144 0145 QWidget *container = new QWidget; 0146 QVBoxLayout *lay1 = new QVBoxLayout(container); 0147 top->setWidget(container); 0148 0149 // Select all and Deselect All buttons 0150 QHBoxLayout *lay2 = new QHBoxLayout; 0151 lay1->addLayout(lay2); 0152 0153 QPushButton *selectAll = new QPushButton(i18n("Select All"), container); 0154 lay2->addWidget(selectAll); 0155 QPushButton *selectNone = new QPushButton(i18n("Deselect All"), container); 0156 lay2->addWidget(selectNone); 0157 lay2->addStretch(1); 0158 connect(selectAll, &QPushButton::clicked, this, &ImportDialog::slotSelectAll); 0159 connect(selectNone, &QPushButton::clicked, this, &ImportDialog::slotSelectNone); 0160 0161 QGridLayout *lay3 = new QGridLayout; 0162 lay1->addLayout(lay3); 0163 0164 lay3->setColumnStretch(2, 1); 0165 0166 int row = 0; 0167 for (DB::ImageInfoListConstIterator it = m_images.constBegin(); it != m_images.constEnd(); ++it, ++row) { 0168 DB::ImageInfoPtr info = *it; 0169 ImageRow *ir = new ImageRow(info, this, m_kimFileReader, container); 0170 lay3->addWidget(ir->m_checkbox, row, 0); 0171 0172 QPixmap pixmap = m_kimFileReader->loadThumbnail(info->fileName().relative()); 0173 if (!pixmap.isNull()) { 0174 QPushButton *but = new QPushButton(container); 0175 but->setIcon(pixmap); 0176 but->setIconSize(pixmap.size()); 0177 lay3->addWidget(but, row, 1); 0178 connect(but, &QPushButton::clicked, ir, &ImageRow::showImage); 0179 } else { 0180 QLabel *label = new QLabel(info->label()); 0181 lay3->addWidget(label, row, 1); 0182 } 0183 0184 QLabel *label = new QLabel(QString::fromLatin1("<p>%1</p>").arg(info->description())); 0185 lay3->addWidget(label, row, 2); 0186 m_imagesSelect.append(ir); 0187 } 0188 0189 addPage(top, i18n("Select Which Images to Import")); 0190 } 0191 0192 void ImportDialog::createDestination() 0193 { 0194 QWidget *top = new QWidget(this); 0195 QVBoxLayout *topLay = new QVBoxLayout(top); 0196 QHBoxLayout *lay = new QHBoxLayout; 0197 topLay->addLayout(lay); 0198 0199 topLay->addStretch(1); 0200 0201 QLabel *label = new QLabel(i18n("Destination of images: "), top); 0202 lay->addWidget(label); 0203 0204 m_destinationEdit = new QLineEdit(top); 0205 lay->addWidget(m_destinationEdit, 1); 0206 0207 QPushButton *but = new QPushButton(QString::fromLatin1("..."), top); 0208 but->setFixedWidth(30); 0209 lay->addWidget(but); 0210 0211 m_destinationEdit->setText(Settings::SettingsData::instance()->imageDirectory()); 0212 connect(but, &QPushButton::clicked, this, &ImportDialog::slotEditDestination); 0213 connect(m_destinationEdit, &QLineEdit::textChanged, this, &ImportDialog::updateNextButtonState); 0214 m_destinationPage = addPage(top, i18n("Destination of Images")); 0215 } 0216 0217 void ImportDialog::slotEditDestination() 0218 { 0219 QString file = QFileDialog::getExistingDirectory(this, QString(), m_destinationEdit->text()); 0220 if (!file.isNull()) { 0221 if (!QFileInfo(file).absoluteFilePath().startsWith(QFileInfo(Settings::SettingsData::instance()->imageDirectory()).absoluteFilePath())) { 0222 KMessageBox::error(this, i18n("The folder must be a subfolder of %1", Settings::SettingsData::instance()->imageDirectory())); 0223 } else if (QFileInfo(file).absoluteFilePath().startsWith( 0224 QFileInfo(Settings::SettingsData::instance()->imageDirectory()).absoluteFilePath() + QString::fromLatin1("CategoryImages"))) { 0225 KMessageBox::error(this, i18n("This folder is reserved for category images.")); 0226 } else { 0227 m_destinationEdit->setText(file); 0228 updateNextButtonState(); 0229 } 0230 } 0231 } 0232 0233 void ImportDialog::updateNextButtonState() 0234 { 0235 bool enabled = true; 0236 if (currentPage() == m_destinationPage) { 0237 QString dest = m_destinationEdit->text(); 0238 if (QFileInfo(dest).isFile()) 0239 enabled = false; 0240 else if (!QFileInfo(dest).absoluteFilePath().startsWith(QFileInfo(Settings::SettingsData::instance()->imageDirectory()).absoluteFilePath())) 0241 enabled = false; 0242 } 0243 0244 setValid(currentPage(), enabled); 0245 } 0246 0247 void ImportDialog::createCategoryPages() 0248 { 0249 QStringList categories; 0250 const DB::ImageInfoList images = selectedImages(); 0251 for (DB::ImageInfoListConstIterator it = images.constBegin(); it != images.constEnd(); ++it) { 0252 const DB::ImageInfoPtr info = *it; 0253 const QStringList categoriesForImage = info->availableCategories(); 0254 for (const QString &category : categoriesForImage) { 0255 auto catPtr = DB::ImageDB::instance()->categoryCollection()->categoryForName(category); 0256 if (!categories.contains(category) && !(catPtr && catPtr->isSpecialCategory())) { 0257 categories.append(category); 0258 } 0259 } 0260 } 0261 0262 if (!categories.isEmpty()) { 0263 m_categoryMatcher = new ImportMatcher(QString(), QString(), categories, DB::ImageDB::instance()->categoryCollection()->categoryNames(DB::CategoryCollection::IncludeSpecialCategories::No), 0264 false, this); 0265 m_categoryMatcherPage = addPage(m_categoryMatcher, i18n("Match Categories")); 0266 0267 QWidget *dummy = new QWidget; 0268 m_dummy = addPage(dummy, QString()); 0269 } else { 0270 m_categoryMatcherPage = nullptr; 0271 possiblyAddMD5CheckPage(); 0272 } 0273 } 0274 0275 ImportMatcher *ImportDialog::createCategoryPage(const QString &myCategory, const QString &otherCategory) 0276 { 0277 StringSet otherItems; 0278 DB::ImageInfoList images = selectedImages(); 0279 for (DB::ImageInfoListConstIterator it = images.constBegin(); it != images.constEnd(); ++it) { 0280 otherItems += (*it)->itemsOfCategory(otherCategory); 0281 } 0282 0283 QStringList myItems = DB::ImageDB::instance()->categoryCollection()->categoryForName(myCategory)->itemsInclCategories(); 0284 myItems.sort(); 0285 0286 const QStringList otherItemsList(otherItems.begin(), otherItems.end()); 0287 ImportMatcher *matcher = new ImportMatcher(otherCategory, myCategory, otherItemsList, myItems, true, this); 0288 addPage(matcher, myCategory); 0289 return matcher; 0290 } 0291 0292 void ImportDialog::next() 0293 { 0294 if (currentPage() == m_destinationPage) { 0295 QString dir = m_destinationEdit->text(); 0296 if (!QFileInfo::exists(dir)) { 0297 const QString question = i18n("Folder %1 does not exist. Should it be created?", dir); 0298 const QString title = i18nc("@title", "Create folder?"); 0299 #if KWIDGETSADDONS_VERSION >= QT_VERSION_CHECK(5, 100, 0) 0300 const auto answer = KMessageBox::questionTwoActions(this, 0301 question, 0302 title, 0303 KGuiItem(i18nc("@action:button", "Create")), 0304 KStandardGuiItem::cancel()); 0305 if (answer != KMessageBox::ButtonCode::PrimaryAction) 0306 return; 0307 #else 0308 const auto answer = KMessageBox::questionYesNo(this, question, title); 0309 if (answer != KMessageBox::Yes) 0310 return; 0311 #endif 0312 bool ok = QDir().mkpath(dir); 0313 if (!ok) { 0314 KMessageBox::error(this, i18n("Error creating folder %1", dir)); 0315 return; 0316 } 0317 } 0318 } 0319 if (!m_hasFilled && currentPage() == m_categoryMatcherPage) { 0320 m_hasFilled = true; 0321 m_categoryMatcher->setEnabled(false); 0322 removePage(m_dummy); 0323 0324 ImportMatcher *matcher = nullptr; 0325 const auto matchers = m_categoryMatcher->m_matchers; 0326 for (const CategoryMatch *match : matchers) { 0327 if (match->m_checkbox->isChecked()) { 0328 matcher = createCategoryPage(match->m_combobox->currentText(), match->m_text); 0329 m_matchers.append(matcher); 0330 } 0331 } 0332 possiblyAddMD5CheckPage(); 0333 } 0334 0335 KAssistantDialog::next(); 0336 } 0337 0338 void ImportDialog::slotSelectAll() 0339 { 0340 selectImage(true); 0341 } 0342 0343 void ImportDialog::slotSelectNone() 0344 { 0345 selectImage(false); 0346 } 0347 0348 void ImportDialog::selectImage(bool on) 0349 { 0350 for (ImageRow *row : qAsConst(m_imagesSelect)) { 0351 row->m_checkbox->setChecked(on); 0352 } 0353 } 0354 0355 DB::ImageInfoList ImportDialog::selectedImages() const 0356 { 0357 DB::ImageInfoList res; 0358 for (QList<ImageRow *>::ConstIterator it = m_imagesSelect.begin(); it != m_imagesSelect.end(); ++it) { 0359 if ((*it)->m_checkbox->isChecked()) 0360 res.append((*it)->m_info); 0361 } 0362 return res; 0363 } 0364 0365 void ImportDialog::slotHelp() 0366 { 0367 KHelpClient::invokeHelp(QString::fromLatin1("chp-importExport")); 0368 } 0369 0370 ImportSettings ImportExport::ImportDialog::settings() 0371 { 0372 ImportSettings settings; 0373 settings.setSelectedImages(selectedImages()); 0374 settings.setDestination(m_destinationEdit->text()); 0375 settings.setExternalSource(m_externalSource); 0376 settings.setKimFile(m_kimFile); 0377 settings.setBaseURL(m_baseUrl); 0378 0379 if (m_md5CheckPage) { 0380 settings.setImportActions(m_md5CheckPage->settings()); 0381 } 0382 0383 for (ImportMatcher *match : m_matchers) 0384 settings.addCategoryMatchSetting(match->settings()); 0385 0386 return settings; 0387 } 0388 0389 void ImportExport::ImportDialog::possiblyAddMD5CheckPage() 0390 { 0391 if (MD5CheckPage::pageNeeded(settings())) { 0392 m_md5CheckPage = new MD5CheckPage(settings()); 0393 addPage(m_md5CheckPage, i18n("How to resolve clashes")); 0394 } 0395 } 0396 0397 // vi:expandtab:tabstop=4 shiftwidth=4: 0398 0399 #include "moc_ImportDialog.cpp"