File indexing completed on 2025-01-05 04:00:06
0001 /* ============================================================ 0002 * 0003 * This file is a part of digiKam project 0004 * https://www.digikam.org 0005 * 0006 * Date : 2000-12-05 0007 * Description : helper class used to modify search albums in views 0008 * 0009 * SPDX-FileCopyrightText: 2009-2010 by Johannes Wienke <languitar at semipol dot de> 0010 * SPDX-FileCopyrightText: 2011-2024 by Gilles Caulier <caulier dot gilles at gmail dot com> 0011 * 0012 * SPDX-License-Identifier: GPL-2.0-or-later 0013 * 0014 * ============================================================ */ 0015 0016 #include "searchmodificationhelper.h" 0017 0018 // Qt includes 0019 0020 #include <QInputDialog> 0021 #include <QMessageBox> 0022 0023 // KDE includes 0024 0025 #include <klocalizedstring.h> 0026 0027 // Local includes 0028 0029 #include "digikam_debug.h" 0030 #include "albummanager.h" 0031 #include "similaritydbaccess.h" 0032 #include "similaritydb.h" 0033 #include "haariface.h" 0034 #include "iteminfo.h" 0035 #include "coredbsearchxml.h" 0036 #include "sketchwidget.h" 0037 0038 namespace Digikam 0039 { 0040 0041 class Q_DECL_HIDDEN SearchModificationHelper::Private 0042 { 0043 public: 0044 0045 explicit Private() 0046 : dialogParent(nullptr) 0047 { 0048 } 0049 0050 QWidget* dialogParent; 0051 }; 0052 0053 SearchModificationHelper::SearchModificationHelper(QObject* const parent, QWidget* const dialogParent) 0054 : QObject(parent), 0055 d (new Private) 0056 { 0057 d->dialogParent = dialogParent; 0058 } 0059 0060 SearchModificationHelper::~SearchModificationHelper() 0061 { 0062 delete d; 0063 } 0064 0065 void SearchModificationHelper::slotSearchDelete(SAlbum* searchAlbum) 0066 { 0067 if (!searchAlbum) 0068 { 0069 return; 0070 } 0071 0072 // Make sure that a complicated search is not deleted accidentally 0073 0074 int result = QMessageBox::warning(d->dialogParent, i18nc("@title:window", "Delete Search?"), 0075 i18n("Are you sure you want to " 0076 "delete the selected search " 0077 "\"%1\"?", searchAlbum->displayTitle()), 0078 QMessageBox::Yes | QMessageBox::Cancel); 0079 0080 0081 if (result != QMessageBox::Yes) 0082 { 0083 return; 0084 } 0085 0086 AlbumManager::instance()->deleteSAlbum(searchAlbum); 0087 } 0088 0089 bool SearchModificationHelper::checkAlbum(const QString& name) const 0090 { 0091 const AlbumList list = AlbumManager::instance()->allSAlbums(); 0092 0093 for (AlbumList::ConstIterator it = list.constBegin() ; it != list.constEnd() ; ++it) 0094 { 0095 SAlbum* const album = (SAlbum*)(*it); 0096 0097 if (album->title() == name) 0098 { 0099 return false; 0100 } 0101 } 0102 0103 return true; 0104 } 0105 0106 bool SearchModificationHelper::checkName(QString& name) 0107 { 0108 bool checked = checkAlbum(name); 0109 0110 while (!checked) 0111 { 0112 QString label = i18n( "Search name already exists.\n" 0113 "Please enter a new name:" ); 0114 bool ok; 0115 QString newTitle = QInputDialog::getText(d->dialogParent, 0116 i18nc("@title:window", "Name Exists"), 0117 label, 0118 QLineEdit::Normal, 0119 name, 0120 &ok); 0121 0122 if (!ok) 0123 { 0124 return false; 0125 } 0126 0127 name = newTitle; 0128 checked = checkAlbum(name); 0129 } 0130 0131 return true; 0132 } 0133 0134 void SearchModificationHelper::slotSearchRename(SAlbum* searchAlbum) 0135 { 0136 if (!searchAlbum) 0137 { 0138 return; 0139 } 0140 0141 QString oldName(searchAlbum->title()); 0142 bool ok; 0143 QString name = QInputDialog::getText(d->dialogParent, 0144 i18nc("@title:window", "Rename Album (%1)", oldName), 0145 i18n("Enter new album name:"), 0146 QLineEdit::Normal, 0147 oldName, 0148 &ok); 0149 0150 if (!ok || (name == oldName) || (name.isEmpty())) 0151 { 0152 return; 0153 } 0154 0155 if (!checkName(name)) 0156 { 0157 return; 0158 } 0159 0160 AlbumManager::instance()->updateSAlbum(searchAlbum, searchAlbum->query(), name); 0161 } 0162 0163 SAlbum* SearchModificationHelper::slotCreateTimeLineSearch(const QString& desiredName, 0164 const DateRangeList& dateRanges, 0165 bool overwriteIfExisting) 0166 { 0167 QString name = desiredName; 0168 0169 if (!overwriteIfExisting) 0170 { 0171 if (!checkName(name)) 0172 { 0173 return nullptr; 0174 } 0175 } 0176 0177 if (dateRanges.isEmpty()) 0178 { 0179 AlbumManager::instance()->clearCurrentAlbums(); 0180 return nullptr; 0181 } 0182 0183 // Create an XML search query for the list of date ranges 0184 0185 SearchXmlWriter writer; 0186 0187 // for each range, write a group with two fields 0188 0189 for (int i = 0 ; i < dateRanges.size() ; ++i) 0190 { 0191 writer.writeGroup(); 0192 writer.writeField(QLatin1String("creationdate"), SearchXml::GreaterThanOrEqual); 0193 writer.writeValue(dateRanges.at(i).first); 0194 writer.finishField(); 0195 writer.writeField(QLatin1String("creationdate"), SearchXml::LessThan); 0196 writer.writeValue(dateRanges.at(i).second); 0197 writer.finishField(); 0198 writer.finishGroup(); 0199 } 0200 0201 writer.finish(); 0202 0203 qCDebug(DIGIKAM_GENERAL_LOG) << "Date search XML:\n" << writer.xml(); 0204 0205 SAlbum* const album = AlbumManager::instance()->createSAlbum(name, DatabaseSearch::TimeLineSearch, writer.xml()); 0206 AlbumManager::instance()->setCurrentAlbums(QList<Album*>() << album); 0207 0208 return album; 0209 } 0210 0211 SAlbum* SearchModificationHelper::createFuzzySearchFromSketch(const QString& proposedName, 0212 SketchWidget* sketchWidget, 0213 unsigned int numberOfResults, 0214 const QList<int>& targetAlbums, 0215 bool overwriteIfExisting) 0216 { 0217 if (sketchWidget->isClear()) 0218 { 0219 return nullptr; 0220 } 0221 0222 QString name = proposedName; 0223 0224 if (!overwriteIfExisting) 0225 { 0226 if (!checkName(name)) 0227 { 0228 return nullptr; 0229 } 0230 } 0231 0232 // We query database here 0233 0234 HaarIface haarIface; 0235 SearchXmlWriter writer; 0236 0237 writer.writeGroup(); 0238 writer.writeField(QLatin1String("similarity"), SearchXml::Like); 0239 writer.writeAttribute(QLatin1String("type"), QLatin1String("signature")); // we pass a signature 0240 writer.writeAttribute(QLatin1String("numberofresults"), QString::number(numberOfResults)); 0241 writer.writeAttribute(QLatin1String("sketchtype"), QLatin1String("handdrawn")); 0242 writer.writeValue(haarIface.signatureAsText(sketchWidget->sketchImage())); 0243 sketchWidget->sketchImageToXML(writer); 0244 writer.finishField(); 0245 0246 // Add the target albums, i.e. define that the found similar images 0247 // must be located in one of the target albums. 0248 0249 writer.writeField(QLatin1String("noeffect_targetAlbums"), SearchXml::OneOf); 0250 writer.writeValue(targetAlbums); 0251 writer.finishField(); 0252 0253 writer.finishGroup(); 0254 writer.finish(); 0255 0256 SAlbum* const salbum = AlbumManager::instance()->createSAlbum(name, DatabaseSearch::HaarSearch, writer.xml()); 0257 AlbumManager::instance()->setCurrentAlbums(QList<Album*>() << salbum); 0258 0259 return salbum; 0260 } 0261 0262 void SearchModificationHelper::slotCreateFuzzySearchFromSketch(const QString& proposedName, 0263 SketchWidget* sketchWidget, 0264 unsigned int numberOfResults, 0265 const QList<int>& targetAlbums, 0266 bool overwriteIfExisting) 0267 { 0268 createFuzzySearchFromSketch(proposedName, 0269 sketchWidget, 0270 numberOfResults, 0271 targetAlbums, 0272 overwriteIfExisting); 0273 } 0274 0275 SAlbum* SearchModificationHelper::createFuzzySearchFromDropped(const QString& proposedName, 0276 const QString& filePath, 0277 float threshold, 0278 float maxThreshold, 0279 const QList<int>& targetAlbums, 0280 bool overwriteIfExisting) 0281 { 0282 QString name = proposedName; 0283 0284 if (!overwriteIfExisting) 0285 { 0286 if (!checkName(name)) 0287 { 0288 return nullptr; 0289 } 0290 } 0291 0292 // We query database here 0293 0294 HaarIface haarIface; 0295 SearchXmlWriter writer; 0296 0297 writer.writeGroup(); 0298 writer.writeField(QLatin1String("similarity"), SearchXml::Like); 0299 writer.writeAttribute(QLatin1String("type"), QLatin1String("image")); // we pass an image path 0300 writer.writeAttribute(QLatin1String("threshold"), QString::number(threshold)); 0301 writer.writeAttribute(QLatin1String("maxthreshold"), QString::number(maxThreshold)); 0302 writer.writeAttribute(QLatin1String("sketchtype"), QLatin1String("scanned")); 0303 writer.writeValue(filePath); 0304 writer.finishField(); 0305 0306 // Add the target albums, i.e. define that the found similar images 0307 // must be located in one of the target albums. 0308 0309 writer.writeField(QLatin1String("noeffect_targetAlbums"), SearchXml::OneOf); 0310 writer.writeValue(targetAlbums); 0311 writer.finishField(); 0312 0313 writer.finishGroup(); 0314 writer.finish(); 0315 0316 SAlbum* const salbum = AlbumManager::instance()->createSAlbum(name, DatabaseSearch::HaarSearch, writer.xml()); 0317 AlbumManager::instance()->setCurrentAlbums(QList<Album*>() << salbum); 0318 0319 return salbum; 0320 } 0321 0322 void SearchModificationHelper::slotCreateFuzzySearchFromDropped(const QString& proposedName, 0323 const QString& filePath, 0324 float threshold, 0325 float maxThreshold, 0326 const QList<int>& targetAlbums, 0327 bool overwriteIfExisting) 0328 { 0329 createFuzzySearchFromDropped(proposedName, 0330 filePath, 0331 threshold, 0332 maxThreshold, 0333 targetAlbums, 0334 overwriteIfExisting); 0335 } 0336 0337 SAlbum* SearchModificationHelper::createFuzzySearchFromImage(const QString& proposedName, 0338 const ItemInfo& image, 0339 float threshold, 0340 float maxThreshold, 0341 const QList<int>& targetAlbums, 0342 bool overwriteIfExisting) 0343 { 0344 if (image.isNull()) 0345 { 0346 return nullptr; 0347 } 0348 0349 QString name = proposedName; 0350 0351 if (!overwriteIfExisting) 0352 { 0353 if (!checkName(name)) 0354 { 0355 return nullptr; 0356 } 0357 } 0358 0359 // If the image has no fingerprint, generate it. 0360 0361 if (SimilarityDbAccess().db()->hasDirtyOrMissingFingerprint(image)) 0362 { 0363 QString path = image.filePath(); 0364 0365 if (!path.isEmpty()) 0366 { 0367 qCDebug(DIGIKAM_GENERAL_LOG) << "(Re-)generating fingerprint for file:" << path; 0368 0369 HaarIface haarIface; 0370 haarIface.indexImage(path); 0371 } 0372 } 0373 0374 // We query database here 0375 0376 SearchXmlWriter writer; 0377 0378 writer.writeGroup(); 0379 writer.writeField(QLatin1String("similarity"), SearchXml::Like); 0380 writer.writeAttribute(QLatin1String("type"), QLatin1String("imageid")); 0381 writer.writeAttribute(QLatin1String("threshold"), QString::number(threshold)); 0382 writer.writeAttribute(QLatin1String("maxthreshold"), QString::number(maxThreshold)); 0383 writer.writeAttribute(QLatin1String("sketchtype"), QLatin1String("scanned")); 0384 writer.writeValue(image.id()); 0385 writer.finishField(); 0386 0387 // Add the target albums, i.e. define that the found similar images 0388 // must be located in one of the target albums. 0389 0390 writer.writeField(QLatin1String("noeffect_targetAlbums"), SearchXml::OneOf); 0391 writer.writeValue(targetAlbums); 0392 writer.finishField(); 0393 0394 writer.finishGroup(); 0395 writer.finish(); 0396 0397 SAlbum* const salbum = AlbumManager::instance()->createSAlbum(name, DatabaseSearch::HaarSearch, writer.xml()); 0398 AlbumManager::instance()->setCurrentAlbums(QList<Album*>() << salbum); 0399 0400 return salbum; 0401 } 0402 0403 void SearchModificationHelper::slotCreateFuzzySearchFromImage(const QString& proposedName, 0404 const ItemInfo& image, 0405 float threshold, 0406 float maxThreshold, 0407 const QList<int>& targetAlbums, 0408 bool overwriteIfExisting) 0409 { 0410 createFuzzySearchFromImage(proposedName, 0411 image, 0412 threshold, 0413 maxThreshold, 0414 targetAlbums, 0415 overwriteIfExisting); 0416 } 0417 0418 } // namespace Digikam 0419 0420 #include "moc_searchmodificationhelper.cpp"