File indexing completed on 2025-11-09 04:11:26

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 - similar panel.
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 QWidget* FuzzySearchView::setupFindSimilarPanel() const
0024 {
0025     const int spacing = qMin(QApplication::style()->pixelMetric(QStyle::PM_LayoutHorizontalSpacing),
0026                              QApplication::style()->pixelMetric(QStyle::PM_LayoutVerticalSpacing));
0027 
0028     DHBox* const imageBox = new DHBox();
0029     d->imageWidget        = new QLabel(imageBox);
0030     d->imageWidget->setFixedSize(256, 256);
0031     d->imageWidget->setText(i18n("<p>Drag & drop an image here<br/>to perform similar<br/>items search.</p>"
0032                                  "<p>You can also use the context menu<br/> when browsing through your images.</p>"));
0033     d->imageWidget->setAlignment(Qt::AlignCenter);
0034     imageBox->setFrameStyle(QFrame::StyledPanel | QFrame::Sunken);
0035     imageBox->setLineWidth(1);
0036 
0037     // ---------------------------------------------------------------
0038 
0039     QLabel* const file   = new QLabel(i18n("<b>File</b>:"));
0040     d->labelFile         = new DAdjustableLabel(nullptr);
0041     QLabel* const folder = new QLabel(i18n("<b>Folder</b>:"));
0042     d->labelFolder       = new DAdjustableLabel(nullptr);
0043     int hgt              = fontMetrics().height() - 2;
0044     file->setMaximumHeight(hgt);
0045     folder->setMaximumHeight(hgt);
0046     d->labelFile->setMaximumHeight(hgt);
0047     d->labelFolder->setMaximumHeight(hgt);
0048 
0049     // ---------------------------------------------------------------
0050 
0051     d->fuzzySearchAlbumSelectors = new AlbumSelectors(i18nc("@label", "Search in albums:"),
0052                                                       QLatin1String("Fuzzy Search View"),
0053                                                       nullptr, AlbumSelectors::AlbumType::PhysAlbum, true);
0054 
0055     // ---------------------------------------------------------------
0056 
0057     QLabel* const resultsLabel = new QLabel(i18n("Similarity range:"));
0058     d->similarityRange         = new DIntRangeBox();
0059     d->similarityRange->setSuffix(QLatin1String("%"));
0060 
0061     if (d->settings)
0062     {
0063         d->similarityRange->setRange(d->settings->getMinimumSimilarityBound(), 100);
0064         d->similarityRange->setInterval(d->settings->getDuplicatesSearchLastMinSimilarity(),
0065                                         d->settings->getDuplicatesSearchLastMaxSimilarity());
0066     }
0067     else
0068     {
0069         d->similarityRange->setRange(40, 100);
0070         d->similarityRange->setInterval(90, 100);
0071     }
0072 
0073     d->similarityRange->setWhatsThis(i18n("Select here the approximate similarity interval "
0074                                           "as a percentage. "));
0075 
0076     // ---------------------------------------------------------------
0077 
0078     DHBox* const saveBox = new DHBox();
0079     saveBox->setContentsMargins(QMargins());
0080     saveBox->setSpacing(spacing);
0081 
0082     d->nameEditImage = new DTextEdit(saveBox);
0083     d->nameEditImage->setLinesVisible(1);
0084     d->nameEditImage->setWhatsThis(i18n("Enter the name of the current similar image search to save in the "
0085                                         "\"Similarity Searches\" view."));
0086 
0087     d->saveBtnImage  = new QToolButton(saveBox);
0088     d->saveBtnImage->setIcon(QIcon::fromTheme(QLatin1String("document-save")));
0089     d->saveBtnImage->setEnabled(false);
0090     d->saveBtnImage->setToolTip(i18n("Save current similar image search to a new virtual Album"));
0091     d->saveBtnImage->setWhatsThis(i18n("If you press this button, the current "
0092                                        "similar image search will be saved to a new search "
0093                                        "virtual album using name "
0094                                        "set on the left side."));
0095 
0096     // ---------------------------------------------------------------
0097 
0098     QWidget* const mainWidget     = new QWidget();
0099     QGridLayout* const mainLayout = new QGridLayout();
0100     mainLayout->addWidget(imageBox,                      0, 0, 1, 6);
0101     mainLayout->addWidget(file,                          1, 0, 1, 1);
0102     mainLayout->addWidget(d->labelFile,                  1, 1, 1, 5);
0103     mainLayout->addWidget(folder,                        2, 0, 1, 1);
0104     mainLayout->addWidget(d->labelFolder,                2, 1, 1, 5);
0105     mainLayout->addWidget(d->fuzzySearchAlbumSelectors,  3, 0, 1, -1);
0106     mainLayout->addWidget(resultsLabel,                  4, 0, 1, 1);
0107     mainLayout->addWidget(d->similarityRange,            4, 2, 1, 1);
0108     mainLayout->addWidget(saveBox,                       5, 0, 1, 6);
0109     mainLayout->setRowStretch(0, 10);
0110     mainLayout->setColumnStretch(1, 10);
0111     mainLayout->setContentsMargins(spacing, spacing, spacing, spacing);
0112     mainLayout->setSpacing(spacing);
0113     mainWidget->setLayout(mainLayout);
0114 
0115     return mainWidget;
0116 }
0117 
0118 void FuzzySearchView::dragEnterEvent(QDragEnterEvent* e)
0119 {
0120     if (dragEventWrapper(e->mimeData()))
0121     {
0122         e->acceptProposedAction();
0123     }
0124 }
0125 
0126 void FuzzySearchView::dragMoveEvent(QDragMoveEvent* e)
0127 {
0128     if (dragEventWrapper(e->mimeData()))
0129     {
0130         e->acceptProposedAction();
0131     }
0132 }
0133 
0134 bool FuzzySearchView::dragEventWrapper(const QMimeData* data) const
0135 {
0136     if      (DItemDrag::canDecode(data))
0137     {
0138         return true;
0139     }
0140     else if (data->hasUrls())
0141     {
0142         QList<QUrl> urls = data->urls();
0143 
0144         // If there is at least one URL and the URL is a local file.
0145 
0146         if (!urls.isEmpty() && urls.first().isLocalFile())
0147         {
0148             HaarIface haarIface;
0149             QString path       = urls.first().toLocalFile();
0150             const QImage image = haarIface.loadQImage(path);
0151 
0152             if (!image.isNull())
0153             {
0154                 return true;
0155             }
0156         }
0157     }
0158 
0159     return false;
0160 }
0161 
0162 void FuzzySearchView::dropEvent(QDropEvent* e)
0163 {
0164     if (DItemDrag::canDecode(e->mimeData()))
0165     {
0166         QList<QUrl>      urls;
0167         QList<int>       albumIDs;
0168         QList<qlonglong> imageIDs;
0169 
0170         if (!DItemDrag::decode(e->mimeData(), urls, albumIDs, imageIDs))
0171         {
0172             return;
0173         }
0174 
0175         if (imageIDs.isEmpty())
0176         {
0177             return;
0178         }
0179 
0180         setItemInfo(ItemInfo(imageIDs.first()));
0181 
0182         e->acceptProposedAction();
0183     }
0184 
0185     // Allow dropping urls and handle them as sketch search if the urls represent images.
0186 
0187     if (e->mimeData()->hasUrls())
0188     {
0189         QList<QUrl> urls = e->mimeData()->urls();
0190 
0191         // If there is at least one URL and the URL is a local file.
0192 
0193         if (!urls.isEmpty() && urls.first().isLocalFile())
0194         {
0195             HaarIface haarIface;
0196             QString path       = urls.first().toLocalFile();
0197             const QImage image = haarIface.loadQImage(path);
0198 
0199             if (!image.isNull())
0200             {
0201                 // Set a temporary image id
0202 
0203                 d->imageInfo = ItemInfo(-1);
0204                 d->imageUrl  = urls.first();
0205 
0206                 d->imageWidget->setPixmap(QPixmap::fromImage(image).scaled(256, 256, Qt::KeepAspectRatio, Qt::SmoothTransformation));
0207 
0208                 AlbumManager::instance()->clearCurrentAlbums();
0209                 QString haarTitle = SAlbum::getTemporaryHaarTitle(DatabaseSearch::HaarImageSearch);
0210 
0211                 QList<int> albums = d->fuzzySearchAlbumSelectors->selectedAlbumIds();
0212 
0213                 d->imageSAlbum = d->searchModificationHelper->createFuzzySearchFromDropped(haarTitle, path,
0214                                                                                            d->similarityRange->minValue() / 100.0,
0215                                                                                            d->similarityRange->maxValue() / 100.0,
0216                                                                                            albums, true);
0217                 d->searchTreeView->setCurrentAlbums(QList<Album*>() << d->imageSAlbum);
0218                 d->labelFile->setAdjustedText(urls.first().fileName());
0219                 d->labelFolder->setAdjustedText(urls.first().adjusted(QUrl::RemoveFilename).toLocalFile());
0220 
0221                 slotCheckNameEditImageConditions();
0222 
0223                 e->acceptProposedAction();
0224             }
0225         }
0226     }
0227 }
0228 
0229 void FuzzySearchView::slotMaxLevelImageChanged(int /*newValue*/)
0230 {
0231     if (d->active)
0232     {
0233         d->timerImage->start();
0234     }
0235 }
0236 
0237 void FuzzySearchView::slotMinLevelImageChanged(int /*newValue*/)
0238 {
0239     if (d->active)
0240     {
0241         d->timerImage->start();
0242     }
0243 }
0244 
0245 void FuzzySearchView::slotFuzzyAlbumsChanged()
0246 {
0247     if (d->active)
0248     {
0249         d->timerImage->start();
0250     }
0251 }
0252 
0253 void FuzzySearchView::slotTimerImageDone()
0254 {
0255     if (d->imageInfo.isNull() && (d->imageInfo.id() == -1) && !d->imageUrl.isEmpty())
0256     {
0257         AlbumManager::instance()->clearCurrentAlbums();
0258         QString haarTitle = SAlbum::getTemporaryHaarTitle(DatabaseSearch::HaarImageSearch);
0259 
0260         QList<int> albums = d->fuzzySearchAlbumSelectors->selectedAlbumIds();
0261 
0262         d->imageSAlbum    = d->searchModificationHelper->createFuzzySearchFromDropped(haarTitle,
0263                                                                                       d->imageUrl.toLocalFile(),
0264                                                                                       d->similarityRange->minValue() / 100.0,
0265                                                                                       d->similarityRange->maxValue() / 100.0,
0266                                                                                       albums, true);
0267         d->searchTreeView->setCurrentAlbums(QList<Album*>() << d->imageSAlbum);
0268         return;
0269     }
0270 
0271     if (!d->imageInfo.isNull() && d->active)
0272     {
0273         setItemInfo(d->imageInfo);
0274     }
0275 }
0276 
0277 void FuzzySearchView::setCurrentImage(qlonglong imageid)
0278 {
0279     setCurrentImage(ItemInfo(imageid));
0280 }
0281 
0282 void FuzzySearchView::setCurrentImage(const ItemInfo& info)
0283 {
0284     d->imageInfo = info;
0285     d->imageUrl  = info.fileUrl();
0286     d->labelFile->setAdjustedText(d->imageInfo.name());
0287     d->labelFolder->setAdjustedText(d->imageInfo.fileUrl().adjusted(QUrl::RemoveFilename).toLocalFile());
0288     d->thumbLoadThread->find(d->imageInfo.thumbnailIdentifier());
0289 }
0290 
0291 void FuzzySearchView::setItemInfo(const ItemInfo& info)
0292 {
0293     setCurrentImage(info);
0294     slotCheckNameEditImageConditions();
0295     createNewFuzzySearchAlbumFromImage(SAlbum::getTemporaryHaarTitle(DatabaseSearch::HaarImageSearch), true);
0296     d->tabWidget->setCurrentIndex((int)Private::SIMILARS);
0297 }
0298 
0299 void FuzzySearchView::slotThumbnailLoaded(const LoadingDescription& desc, const QPixmap& pix)
0300 {
0301     if (!d->imageInfo.isNull() && (QUrl::fromLocalFile(desc.filePath) == d->imageInfo.fileUrl()))
0302     {
0303         d->imageWidget->setPixmap(pix.scaled(256, 256, Qt::KeepAspectRatio, Qt::SmoothTransformation));
0304     }
0305 }
0306 
0307 void FuzzySearchView::createNewFuzzySearchAlbumFromImage(const QString& name, bool force)
0308 {
0309     AlbumManager::instance()->clearCurrentAlbums();
0310     QList<int> albums = d->fuzzySearchAlbumSelectors->selectedAlbumIds();
0311 
0312     d->imageSAlbum = d->searchModificationHelper->createFuzzySearchFromImage(name, d->imageInfo,
0313                                                                              d->similarityRange->minValue() / 100.0,
0314                                                                              d->similarityRange->maxValue() / 100.0,
0315                                                                              albums, force);
0316     d->searchTreeView->setCurrentAlbums(QList<Album*>() << d->imageSAlbum);
0317 }
0318 
0319 void FuzzySearchView::slotCheckNameEditImageConditions()
0320 {
0321     if (!d->imageInfo.isNull())
0322     {
0323         bool b = d->nameEditImage->text().isEmpty();
0324         d->nameEditImage->setEnabled(true);
0325         d->saveBtnImage->setEnabled(!b);
0326     }
0327     else
0328     {
0329         d->nameEditImage->setEnabled(false);
0330         d->saveBtnImage->setEnabled(false);
0331     }
0332 }
0333 
0334 void FuzzySearchView::slotSaveImageSAlbum()
0335 {
0336     createNewFuzzySearchAlbumFromImage(d->nameEditImage->text());
0337 }
0338 
0339 } // namespace Digikam