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