1 /* ============================================================
2  *
3  * This file is a part of digiKam project
4  * https://www.digikam.org
5  *
6  * Date        : 2008-05-19
7  * Description : Fuzzy search sidebar tab contents - similar panel.
8  *
9  * Copyright (C) 2016-2018 by Mario Frank <mario dot frank at uni minus potsdam dot de>
10  * Copyright (C) 2008-2021 by Gilles Caulier <caulier dot gilles at gmail dot com>
11  * Copyright (C) 2008-2012 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
12  * Copyright (C) 2012      by Andi Clemens <andi dot clemens at gmail dot com>
13  *
14  * This program is free software; you can redistribute it
15  * and/or modify it under the terms of the GNU General
16  * Public License as published by the Free Software Foundation;
17  * either version 2, or (at your option)
18  * any later version.
19  *
20  * This program is distributed in the hope that it will be useful,
21  * but WITHOUT ANY WARRANTY; without even the implied warranty of
22  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
23  * GNU General Public License for more details.
24  *
25  * ============================================================ */
26 
27 #include "fuzzysearchview_p.h"
28 
29 namespace Digikam
30 {
31 
setupFindSimilarPanel() const32 QWidget* FuzzySearchView::setupFindSimilarPanel() const
33 {
34     const int spacing = QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing);
35 
36     DHBox* const imageBox = new DHBox();
37     d->imageWidget        = new QLabel(imageBox);
38     d->imageWidget->setFixedSize(256, 256);
39     d->imageWidget->setText(i18n("<p>Drag & drop an image here<br/>to perform similar<br/>items search.</p>"
40                                  "<p>You can also use the context menu<br/> when browsing through your images.</p>"));
41     d->imageWidget->setAlignment(Qt::AlignCenter);
42     imageBox->setFrameStyle(QFrame::StyledPanel | QFrame::Sunken);
43     imageBox->setLineWidth(1);
44 
45     // ---------------------------------------------------------------
46 
47     QLabel* const file   = new QLabel(i18n("<b>File</b>:"));
48     d->labelFile         = new DAdjustableLabel(nullptr);
49     QLabel* const folder = new QLabel(i18n("<b>Folder</b>:"));
50     d->labelFolder       = new DAdjustableLabel(nullptr);
51     int hgt              = fontMetrics().height() - 2;
52     file->setMaximumHeight(hgt);
53     folder->setMaximumHeight(hgt);
54     d->labelFile->setMaximumHeight(hgt);
55     d->labelFolder->setMaximumHeight(hgt);
56 
57     // ---------------------------------------------------------------
58 
59     d->fuzzySearchAlbumSelectors = new AlbumSelectors(i18nc("@label", "Search in albums:"),
60                                                       QLatin1String("Fuzzy Search View"),
61                                                       nullptr, AlbumSelectors::AlbumType::PhysAlbum);
62 
63     // ---------------------------------------------------------------
64 
65     QLabel* const resultsLabel = new QLabel(i18n("Similarity range:"));
66     d->similarityRange         = new DIntRangeBox();
67     d->similarityRange->setSuffix(QLatin1String("%"));
68 
69     if (d->settings)
70     {
71         d->similarityRange->setRange(d->settings->getMinimumSimilarityBound(), 100);
72         d->similarityRange->setInterval(d->settings->getDuplicatesSearchLastMinSimilarity(),
73                                         d->settings->getDuplicatesSearchLastMaxSimilarity());
74     }
75     else
76     {
77         d->similarityRange->setRange(40, 100);
78         d->similarityRange->setInterval(90, 100);
79     }
80 
81     d->similarityRange->setWhatsThis(i18n("Select here the approximate similarity interval "
82                                           "as a percentage. "));
83 
84     // ---------------------------------------------------------------
85 
86     DHBox* const saveBox = new DHBox();
87     saveBox->setContentsMargins(QMargins());
88     saveBox->setSpacing(spacing);
89 
90     d->nameEditImage = new QLineEdit(saveBox);
91     d->nameEditImage->setClearButtonEnabled(true);
92     d->nameEditImage->setWhatsThis(i18n("Enter the name of the current similar image search to save in the "
93                                         "\"Similarity Searches\" view."));
94 
95     d->saveBtnImage  = new QToolButton(saveBox);
96     d->saveBtnImage->setIcon(QIcon::fromTheme(QLatin1String("document-save")));
97     d->saveBtnImage->setEnabled(false);
98     d->saveBtnImage->setToolTip(i18n("Save current similar image search to a new virtual Album"));
99     d->saveBtnImage->setWhatsThis(i18n("If you press this button, the current "
100                                        "similar image search will be saved to a new search "
101                                        "virtual album using name "
102                                        "set on the left side."));
103 
104     // ---------------------------------------------------------------
105 
106     QWidget* const mainWidget     = new QWidget();
107     QGridLayout* const mainLayout = new QGridLayout();
108     mainLayout->addWidget(imageBox,                      0, 0, 1, 6);
109     mainLayout->addWidget(file,                          1, 0, 1, 1);
110     mainLayout->addWidget(d->labelFile,                  1, 1, 1, 5);
111     mainLayout->addWidget(folder,                        2, 0, 1, 1);
112     mainLayout->addWidget(d->labelFolder,                2, 1, 1, 5);
113     mainLayout->addWidget(d->fuzzySearchAlbumSelectors,  3, 0, 1, -1);
114     mainLayout->addWidget(resultsLabel,                  4, 0, 1, 1);
115     mainLayout->addWidget(d->similarityRange,            4, 2, 1, 1);
116     mainLayout->addWidget(saveBox,                       5, 0, 1, 6);
117     mainLayout->setRowStretch(0, 10);
118     mainLayout->setColumnStretch(1, 10);
119     mainLayout->setContentsMargins(spacing, spacing, spacing, spacing);
120     mainLayout->setSpacing(spacing);
121     mainWidget->setLayout(mainLayout);
122 
123     return mainWidget;
124 }
125 
dragEnterEvent(QDragEnterEvent * e)126 void FuzzySearchView::dragEnterEvent(QDragEnterEvent* e)
127 {
128     if (dragEventWrapper(e->mimeData()))
129     {
130         e->acceptProposedAction();
131     }
132 }
133 
dragMoveEvent(QDragMoveEvent * e)134 void FuzzySearchView::dragMoveEvent(QDragMoveEvent* e)
135 {
136     if (dragEventWrapper(e->mimeData()))
137     {
138         e->acceptProposedAction();
139     }
140 }
141 
dragEventWrapper(const QMimeData * data) const142 bool FuzzySearchView::dragEventWrapper(const QMimeData* data) const
143 {
144     if      (DItemDrag::canDecode(data))
145     {
146         return true;
147     }
148     else if (data->hasUrls())
149     {
150         QList<QUrl> urls = data->urls();
151 
152         // If there is at least one URL and the URL is a local file.
153 
154         if (!urls.isEmpty() && urls.first().isLocalFile())
155         {
156             HaarIface haarIface;
157             QString path       = urls.first().toLocalFile();
158             const QImage image = haarIface.loadQImage(path);
159 
160             if (!image.isNull())
161             {
162                 return true;
163             }
164         }
165     }
166 
167     return false;
168 }
169 
dropEvent(QDropEvent * e)170 void FuzzySearchView::dropEvent(QDropEvent* e)
171 {
172     if (DItemDrag::canDecode(e->mimeData()))
173     {
174         QList<QUrl>      urls;
175         QList<int>       albumIDs;
176         QList<qlonglong> imageIDs;
177 
178         if (!DItemDrag::decode(e->mimeData(), urls, albumIDs, imageIDs))
179         {
180             return;
181         }
182 
183         if (imageIDs.isEmpty())
184         {
185             return;
186         }
187 
188         setItemInfo(ItemInfo(imageIDs.first()));
189 
190         e->acceptProposedAction();
191     }
192 
193     // Allow dropping urls and handle them as sketch search if the urls represent images.
194 
195     if (e->mimeData()->hasUrls())
196     {
197         QList<QUrl> urls = e->mimeData()->urls();
198 
199         // If there is at least one URL and the URL is a local file.
200 
201         if (!urls.isEmpty() && urls.first().isLocalFile())
202         {
203             HaarIface haarIface;
204             QString path       = urls.first().toLocalFile();
205             const QImage image = haarIface.loadQImage(path);
206 
207             if (!image.isNull())
208             {
209                 // Set a temporary image id
210 
211                 d->imageInfo = ItemInfo(-1);
212                 d->imageUrl  = urls.first();
213 
214                 d->imageWidget->setPixmap(QPixmap::fromImage(image).scaled(256, 256, Qt::KeepAspectRatio, Qt::SmoothTransformation));
215 
216                 AlbumManager::instance()->clearCurrentAlbums();
217                 QString haarTitle = SAlbum::getTemporaryHaarTitle(DatabaseSearch::HaarImageSearch);
218 
219                 QList<int> albums = d->fuzzySearchAlbumSelectors->selectedAlbumIds();
220 
221                 d->imageSAlbum = d->searchModificationHelper->createFuzzySearchFromDropped(haarTitle, path,
222                                                                                            d->similarityRange->minValue() / 100.0,
223                                                                                            d->similarityRange->maxValue() / 100.0,
224                                                                                            albums, true);
225                 d->searchTreeView->setCurrentAlbums(QList<Album*>() << d->imageSAlbum);
226                 d->labelFile->setAdjustedText(urls.first().fileName());
227                 d->labelFolder->setAdjustedText(urls.first().adjusted(QUrl::RemoveFilename).toLocalFile());
228 
229                 slotCheckNameEditImageConditions();
230 
231                 e->acceptProposedAction();
232             }
233         }
234     }
235 }
236 
slotMaxLevelImageChanged(int)237 void FuzzySearchView::slotMaxLevelImageChanged(int /*newValue*/)
238 {
239     if (d->active)
240     {
241         d->timerImage->start();
242     }
243 }
244 
slotMinLevelImageChanged(int)245 void FuzzySearchView::slotMinLevelImageChanged(int /*newValue*/)
246 {
247     if (d->active)
248     {
249         d->timerImage->start();
250     }
251 }
252 
slotFuzzyAlbumsChanged()253 void FuzzySearchView::slotFuzzyAlbumsChanged()
254 {
255     if (d->active)
256     {
257         d->timerImage->start();
258     }
259 }
260 
slotTimerImageDone()261 void FuzzySearchView::slotTimerImageDone()
262 {
263     if (d->imageInfo.isNull() && (d->imageInfo.id() == -1) && !d->imageUrl.isEmpty())
264     {
265         AlbumManager::instance()->clearCurrentAlbums();
266         QString haarTitle = SAlbum::getTemporaryHaarTitle(DatabaseSearch::HaarImageSearch);
267 
268         QList<int> albums = d->fuzzySearchAlbumSelectors->selectedAlbumIds();
269 
270         d->imageSAlbum    = d->searchModificationHelper->createFuzzySearchFromDropped(haarTitle,
271                                                                                       d->imageUrl.toLocalFile(),
272                                                                                       d->similarityRange->minValue() / 100.0,
273                                                                                       d->similarityRange->maxValue() / 100.0,
274                                                                                       albums, true);
275         d->searchTreeView->setCurrentAlbums(QList<Album*>() << d->imageSAlbum);
276         return;
277     }
278 
279     if (!d->imageInfo.isNull() && d->active)
280     {
281         setItemInfo(d->imageInfo);
282     }
283 }
284 
setCurrentImage(qlonglong imageid)285 void FuzzySearchView::setCurrentImage(qlonglong imageid)
286 {
287     setCurrentImage(ItemInfo(imageid));
288 }
289 
setCurrentImage(const ItemInfo & info)290 void FuzzySearchView::setCurrentImage(const ItemInfo& info)
291 {
292     d->imageInfo = info;
293     d->imageUrl  = info.fileUrl();
294     d->labelFile->setAdjustedText(d->imageInfo.name());
295     d->labelFolder->setAdjustedText(d->imageInfo.fileUrl().adjusted(QUrl::RemoveFilename).toLocalFile());
296     d->thumbLoadThread->find(d->imageInfo.thumbnailIdentifier());
297 }
298 
setItemInfo(const ItemInfo & info)299 void FuzzySearchView::setItemInfo(const ItemInfo& info)
300 {
301     setCurrentImage(info);
302     slotCheckNameEditImageConditions();
303     createNewFuzzySearchAlbumFromImage(SAlbum::getTemporaryHaarTitle(DatabaseSearch::HaarImageSearch), true);
304     d->tabWidget->setCurrentIndex((int)Private::SIMILARS);
305 }
306 
slotThumbnailLoaded(const LoadingDescription & desc,const QPixmap & pix)307 void FuzzySearchView::slotThumbnailLoaded(const LoadingDescription& desc, const QPixmap& pix)
308 {
309     if (!d->imageInfo.isNull() && (QUrl::fromLocalFile(desc.filePath) == d->imageInfo.fileUrl()))
310     {
311         d->imageWidget->setPixmap(pix.scaled(256, 256, Qt::KeepAspectRatio, Qt::SmoothTransformation));
312     }
313 }
314 
createNewFuzzySearchAlbumFromImage(const QString & name,bool force)315 void FuzzySearchView::createNewFuzzySearchAlbumFromImage(const QString& name, bool force)
316 {
317     AlbumManager::instance()->clearCurrentAlbums();
318     QList<int> albums = d->fuzzySearchAlbumSelectors->selectedAlbumIds();
319 
320     d->imageSAlbum = d->searchModificationHelper->createFuzzySearchFromImage(name, d->imageInfo,
321                                                                              d->similarityRange->minValue() / 100.0,
322                                                                              d->similarityRange->maxValue() / 100.0,
323                                                                              albums, force);
324     d->searchTreeView->setCurrentAlbums(QList<Album*>() << d->imageSAlbum);
325 }
326 
slotCheckNameEditImageConditions()327 void FuzzySearchView::slotCheckNameEditImageConditions()
328 {
329     if (!d->imageInfo.isNull())
330     {
331         bool b = d->nameEditImage->text().isEmpty();
332         d->nameEditImage->setEnabled(true);
333         d->saveBtnImage->setEnabled(!b);
334     }
335     else
336     {
337         d->nameEditImage->setEnabled(false);
338         d->saveBtnImage->setEnabled(false);
339     }
340 }
341 
slotSaveImageSAlbum()342 void FuzzySearchView::slotSaveImageSAlbum()
343 {
344     createNewFuzzySearchAlbumFromImage(d->nameEditImage->text());
345 }
346 
347 } // namespace Digikam
348