File indexing completed on 2025-01-19 03:57:59

0001 /* ============================================================
0002  *
0003  * This file is a part of digiKam project
0004  * https://www.digikam.org
0005  *
0006  * Date        : 2008-05-19
0007  * Description : Fuzzy search sidebar tab contents.
0008  *
0009  * SPDX-FileCopyrightText: 2016-2018 by Mario Frank <mario dot frank at uni minus potsdam dot de>
0010  * SPDX-FileCopyrightText: 2008-2024 by Gilles Caulier <caulier dot gilles at gmail dot com>
0011  * SPDX-FileCopyrightText: 2008-2012 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
0012  * SPDX-FileCopyrightText: 2012      by Andi Clemens <andi dot clemens at gmail dot com>
0013  *
0014  * SPDX-License-Identifier: GPL-2.0-or-later
0015  *
0016  * ============================================================ */
0017 
0018 #include "fuzzysearchview_p.h"
0019 
0020 namespace Digikam
0021 {
0022 
0023 FuzzySearchView::FuzzySearchView(SearchModel* const searchModel,
0024                                  SearchModificationHelper* const searchModificationHelper,
0025                                  QWidget* const parent)
0026     : QScrollArea      (parent),
0027       StateSavingObject(this),
0028       d                (new Private)
0029 {
0030     d->settings                 = ApplicationSettings::instance();
0031 
0032     const int spacing           = qMin(QApplication::style()->pixelMetric(QStyle::PM_LayoutHorizontalSpacing),
0033                              QApplication::style()->pixelMetric(QStyle::PM_LayoutVerticalSpacing));
0034 
0035     d->thumbLoadThread          = ThumbnailLoadThread::defaultThread();
0036     d->searchModel              = searchModel;
0037     d->searchModificationHelper = searchModificationHelper;
0038 
0039     setWidgetResizable(true);
0040     setAttribute(Qt::WA_DeleteOnClose);
0041     setAcceptDrops(true);
0042     viewport()->setAcceptDrops(true);
0043 
0044     // ---------------------------------------------------------------
0045 
0046     QWidget* const imagePanel  = setupFindSimilarPanel();
0047     QWidget* const sketchPanel = setupSketchPanel();
0048     d->findDuplicatesPanel     = new FindDuplicatesView();
0049 
0050     d->tabWidget               = new QTabWidget();
0051     d->tabWidget->insertTab(Private::DUPLICATES, d->findDuplicatesPanel, i18nc("Find Duplicates Tab", "Duplicates"));
0052     d->tabWidget->insertTab(Private::SIMILARS,   imagePanel,             i18nc("Similar Image Tab", "Image"));
0053     d->tabWidget->insertTab(Private::SKETCH,     sketchPanel,            i18nc("Sketch Search Tab", "Sketch"));
0054 
0055     // ---------------------------------------------------------------
0056 
0057     d->folderView     = new DVBox();
0058     d->searchTreeView = new EditableSearchTreeView(d->folderView, searchModel, searchModificationHelper);
0059     d->searchTreeView->filteredModel()->listHaarSearches();
0060     d->searchTreeView->filteredModel()->setListTemporarySearches(true);
0061     d->searchTreeView->setAlbumManagerCurrentAlbum(true);
0062 
0063     d->searchFuzzyBar = new SearchTextBarDb(d->folderView, QLatin1String("FuzzySearchViewSearchFuzzyBar"));
0064     d->searchFuzzyBar->setModel(d->searchTreeView->filteredModel(),
0065                                 AbstractAlbumModel::AlbumIdRole, AbstractAlbumModel::AlbumTitleRole);
0066     d->searchFuzzyBar->setFilterModel(d->searchTreeView->albumFilterModel());
0067     d->folderView->setContentsMargins(QMargins());
0068     d->folderView->setSpacing(spacing);
0069 
0070     // ---------------------------------------------------------------
0071 
0072     QWidget* const mainWidget     = new QWidget(this);
0073     QVBoxLayout* const mainLayout = new QVBoxLayout();
0074     mainLayout->addWidget(d->tabWidget);
0075     mainLayout->addWidget(d->folderView);
0076     mainLayout->setContentsMargins(QMargins());
0077     mainLayout->setSpacing(0);
0078     mainWidget->setLayout(mainLayout);
0079 
0080     setWidget(mainWidget);
0081 
0082     // ---------------------------------------------------------------
0083 
0084     d->timerSketch = new QTimer(this);
0085     d->timerSketch->setSingleShot(true);
0086     d->timerSketch->setInterval(500);
0087 
0088     d->timerImage  = new QTimer(this);
0089     d->timerImage->setSingleShot(true);
0090     d->timerImage->setInterval(500);
0091 
0092     // ---------------------------------------------------------------
0093 
0094     setupConnections();
0095 
0096     // ---------------------------------------------------------------
0097 
0098     slotCheckNameEditSketchConditions();
0099     slotCheckNameEditImageConditions();
0100 }
0101 
0102 FuzzySearchView::~FuzzySearchView()
0103 {
0104     delete d;
0105 }
0106 
0107 void FuzzySearchView::setupConnections()
0108 {
0109     connect(d->settings, SIGNAL(setupChanged()),
0110             this, SLOT(slotApplicationSettingsChanged()));
0111 
0112     connect(d->tabWidget, SIGNAL(currentChanged(int)),
0113             this, SLOT(slotTabChanged(int)));
0114 
0115     connect(d->searchTreeView, SIGNAL(currentAlbumChanged(Album*)),
0116             this, SLOT(slotAlbumSelected(Album*)));
0117 
0118     connect(d->hsSelector, SIGNAL(valueChanged(int,int)),
0119             this, SLOT(slotHSChanged(int,int)));
0120 
0121     connect(d->vSelector, SIGNAL(valueChanged(int)),
0122             this, SLOT(slotVChanged(int)));
0123 
0124     connect(d->penSize, SIGNAL(valueChanged(int)),
0125             d->sketchWidget, SLOT(setPenWidth(int)));
0126 
0127     connect(d->resultsSketch, SIGNAL(valueChanged(int)),
0128             this, SLOT(slotDirtySketch()));
0129 
0130     connect(d->sketchSearchAlbumSelectors, SIGNAL(signalSelectionChanged()),
0131             this, SLOT(slotDirtySketch()));
0132 
0133     connect(d->fuzzySearchAlbumSelectors, SIGNAL(signalSelectionChanged()),
0134             this, SLOT(slotFuzzyAlbumsChanged()));
0135 
0136     connect(d->similarityRange, SIGNAL(minChanged(int)),
0137             this, SLOT(slotMinLevelImageChanged(int)));
0138 
0139     connect(d->similarityRange, SIGNAL(maxChanged(int)),
0140             this, SLOT(slotMaxLevelImageChanged(int)));
0141 
0142     connect(d->resetButton, SIGNAL(clicked()),
0143             this, SLOT(slotClearSketch()));
0144 
0145     connect(d->sketchWidget, SIGNAL(signalPenSizeChanged(int)),
0146             d->penSize, SLOT(setValue(int)));
0147 
0148     connect(d->sketchWidget, SIGNAL(signalPenColorChanged(QColor)),
0149             this, SLOT(slotPenColorChanged(QColor)));
0150 
0151     connect(d->sketchWidget, SIGNAL(signalSketchChanged(QImage)),
0152             this, SLOT(slotDirtySketch()));
0153 
0154     connect(d->sketchWidget, SIGNAL(signalUndoRedoStateChanged(bool,bool)),
0155             this, SLOT(slotUndoRedoStateChanged(bool,bool)));
0156 
0157     connect(d->undoBtnSketch, SIGNAL(clicked()),
0158             d->sketchWidget, SLOT(slotUndo()));
0159 
0160     connect(d->redoBtnSketch, SIGNAL(clicked()),
0161             d->sketchWidget, SLOT(slotRedo()));
0162 
0163     connect(d->saveBtnSketch, SIGNAL(clicked()),
0164             this, SLOT(slotSaveSketchSAlbum()));
0165 
0166     connect(d->saveBtnImage, SIGNAL(clicked()),
0167             this, SLOT(slotSaveImageSAlbum()));
0168 
0169     connect(d->nameEditSketch, SIGNAL(textChanged()),
0170             this, SLOT(slotCheckNameEditSketchConditions()));
0171 
0172     connect(d->nameEditSketch, SIGNAL(returnPressed()),
0173             d->saveBtnSketch, SLOT(animateClick()));
0174 
0175     connect(d->nameEditImage, SIGNAL(textChanged()),
0176             this, SLOT(slotCheckNameEditImageConditions()));
0177 
0178     connect(d->nameEditImage, SIGNAL(returnPressed()),
0179             d->saveBtnImage, SLOT(animateClick()));
0180 
0181     connect(d->thumbLoadThread, SIGNAL(signalThumbnailLoaded(LoadingDescription,QPixmap)),
0182             this, SLOT(slotThumbnailLoaded(LoadingDescription,QPixmap)));
0183 
0184     connect(d->timerSketch, SIGNAL(timeout()),
0185             this, SLOT(slotTimerSketchDone()));
0186 
0187     connect(d->timerImage, SIGNAL(timeout()),
0188             this, SLOT(slotTimerImageDone()));
0189 }
0190 
0191 SAlbum* FuzzySearchView::currentAlbum() const
0192 {
0193     return d->searchTreeView->currentAlbum();
0194 }
0195 
0196 void FuzzySearchView::setCurrentAlbum(SAlbum* const album)
0197 {
0198     d->searchTreeView->setCurrentAlbums(QList<Album*>() << album);
0199 }
0200 
0201 void FuzzySearchView::newDuplicatesSearch(const QList<PAlbum*>& albums)
0202 {
0203     if (!albums.isEmpty())
0204     {
0205         d->findDuplicatesPanel->slotSetSelectedAlbums(albums);
0206     }
0207 
0208     d->tabWidget->setCurrentIndex(Private::DUPLICATES);
0209 }
0210 
0211 void FuzzySearchView::newDuplicatesSearch(const QList<TAlbum*>& albums)
0212 {
0213     if (!albums.isEmpty())
0214     {
0215         d->findDuplicatesPanel->slotSetSelectedAlbums(albums);
0216     }
0217 
0218     d->tabWidget->setCurrentIndex(Private::DUPLICATES);
0219 }
0220 
0221 void FuzzySearchView::setConfigGroup(const KConfigGroup& group)
0222 {
0223     StateSavingObject::setConfigGroup(group);
0224     d->searchTreeView->setConfigGroup(group);
0225 }
0226 
0227 void FuzzySearchView::doLoadState()
0228 {
0229     KConfigGroup group = getConfigGroup();
0230 
0231     d->tabWidget->setCurrentIndex(group.readEntry(entryName(d->configTabEntry),                    (int)Private::DUPLICATES));
0232     d->penSize->setValue(group.readEntry(entryName(d->configPenSketchSizeEntry),                   10));
0233     d->resultsSketch->setValue(group.readEntry(entryName(d->configResultSketchItemsEntry),         10));
0234     d->hsSelector->setHue(group.readEntry(entryName(d->configPenSketchHueEntry),                   180));
0235     d->hsSelector->setSaturation(group.readEntry(entryName(d->configPenSketchSaturationEntry),     128));
0236     d->vSelector->setValue(group.readEntry(entryName(d->configPenSkethValueEntry),                 255));
0237     d->similarityRange->setInterval(group.readEntry(entryName(d->configSimilarsThresholdEntry),    90),
0238                                     group.readEntry(entryName(d->configSimilarsMaxThresholdEntry), 100));
0239     d->hsSelector->updateContents();
0240 
0241     QColor col;
0242     col.setHsv(d->hsSelector->hue(),
0243                d->hsSelector->saturation(),
0244                d->vSelector->value());
0245     setColor(col);
0246 
0247     d->sketchWidget->setPenWidth(d->penSize->value());
0248 
0249     d->searchTreeView->loadState();
0250     d->fuzzySearchAlbumSelectors->loadState();
0251     d->sketchSearchAlbumSelectors->loadState();
0252 }
0253 
0254 void FuzzySearchView::doSaveState()
0255 {
0256     KConfigGroup group = getConfigGroup();
0257     group.writeEntry(entryName(d->configTabEntry),                  d->tabWidget->currentIndex());
0258     group.writeEntry(entryName(d->configPenSketchSizeEntry),        d->penSize->value());
0259     group.writeEntry(entryName(d->configResultSketchItemsEntry),    d->resultsSketch->value());
0260     group.writeEntry(entryName(d->configPenSketchHueEntry),         d->hsSelector->hue());
0261     group.writeEntry(entryName(d->configPenSketchSaturationEntry),  d->hsSelector->saturation());
0262     group.writeEntry(entryName(d->configPenSkethValueEntry),        d->vSelector->value());
0263     group.writeEntry(entryName(d->configSimilarsThresholdEntry),    d->similarityRange->minValue());
0264     group.writeEntry(entryName(d->configSimilarsMaxThresholdEntry), d->similarityRange->maxValue());
0265     d->searchTreeView->saveState();
0266     group.sync();
0267     d->fuzzySearchAlbumSelectors->saveState();
0268     d->sketchSearchAlbumSelectors->saveState();
0269 }
0270 
0271 void FuzzySearchView::setActive(bool val)
0272 {
0273     d->active = val;
0274 
0275     // at first occasion, warn if no fingerprints are available
0276 
0277     if (val && !d->fingerprintsChecked && isVisible())
0278     {
0279         if (!SimilarityDbAccess().db()->hasFingerprints())
0280         {
0281             QString msg = i18n("Image fingerprints have not yet been generated for your collection. "
0282                                "The Similarity Search Tools will not be operational "
0283                                "without pre-generated fingerprints. Please generate "
0284                                "the fingerprints first.");
0285             Q_EMIT signalNotificationError(msg, DNotificationWidget::Warning);
0286         }
0287 
0288         d->fingerprintsChecked = true;
0289     }
0290 
0291     int tab = d->tabWidget->currentIndex();
0292 
0293     if (val)
0294     {
0295         slotTabChanged(tab);
0296     }
0297 }
0298 
0299 void FuzzySearchView::slotTabChanged(int tab)
0300 {
0301     /**
0302      * Set a list with only one element, albummanager can set only multiple albums
0303      */
0304     QList<Album*> albums;
0305 
0306     switch (tab)
0307     {
0308         case Private::SIMILARS:
0309         {
0310             albums << d->imageSAlbum;
0311             AlbumManager::instance()->setCurrentAlbums(albums);
0312             d->folderView->setVisible(true);
0313             break;
0314         }
0315 
0316         case Private::SKETCH:
0317         {
0318             albums << d->sketchSAlbum;
0319             AlbumManager::instance()->setCurrentAlbums(albums);
0320             d->folderView->setVisible(true);
0321             break;
0322         }
0323 
0324         default:  // DUPLICATES
0325         {
0326             QList<SAlbum*> sAlbums = d->findDuplicatesPanel->currentFindDuplicatesAlbums();
0327 
0328             Q_FOREACH (SAlbum* const album, sAlbums)
0329             {
0330                 albums << album;
0331             }
0332 
0333             AlbumManager::instance()->setCurrentAlbums(albums);
0334             d->findDuplicatesPanel->populateTreeView();
0335             d->findDuplicatesPanel->setActive(true);
0336             d->folderView->setVisible(false);
0337             break;
0338         }
0339     }
0340 }
0341 
0342 void FuzzySearchView::slotAlbumSelected(Album* album)
0343 {
0344     qCDebug(DIGIKAM_GENERAL_LOG) << "Selected new album" << album;
0345 
0346     SAlbum* const salbum = dynamic_cast<SAlbum*>(album);
0347 
0348     if (!salbum || !salbum->isHaarSearch())
0349     {
0350         qCDebug(DIGIKAM_GENERAL_LOG) << "Not a haar search, returning";
0351         return;
0352     }
0353 
0354     if (!d->active)
0355     {
0356         qCDebug(DIGIKAM_GENERAL_LOG) << "Not active, returning";
0357         return;
0358     }
0359 
0360     SearchXmlReader reader(salbum->query());
0361     reader.readToFirstField();
0362     QStringView type               = reader.attributes().value(QLatin1String("type"));
0363 /*
0364     TODO: apply this settings if necessary.
0365 
0366     QStringView numResultsString   = reader.attributes().value(QLatin1String("numberofresults"));
0367     QStringView thresholdString    = reader.attributes().value(QLatin1String("threshold"));
0368     QStringView maxThresholdString = reader.attributes().value(QLatin1String("maxthreshold"));
0369     QStringView sketchTypeString   = reader.attributes().value(QLatin1String("sketchtype"));
0370 */
0371     if      (type == QLatin1String("imageid"))
0372     {
0373         setCurrentImage(reader.valueToLongLong());
0374         d->imageSAlbum = salbum;
0375         d->tabWidget->setCurrentIndex((int)Private::SIMILARS);
0376     }
0377     else if (type == QLatin1String("signature"))
0378     {
0379         d->sketchSAlbum = salbum;
0380         d->tabWidget->setCurrentIndex((int)Private::SKETCH);
0381 
0382         if (reader.readToStartOfElement(QLatin1String("SketchImage")))
0383         {
0384             d->sketchWidget->setSketchImageFromXML(reader);
0385         }
0386     }
0387 }
0388 
0389 void FuzzySearchView::slotApplicationSettingsChanged()
0390 {
0391     d->similarityRange->setRange(d->settings->getMinimumSimilarityBound(), 100);
0392 }
0393 
0394 } // namespace Digikam
0395 
0396 #include "moc_fuzzysearchview.cpp"