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"