1 /* ============================================================
2 *
3 * This file is a part of KDE project
4 *
5 *
6 * Date : 2004-05-01
7 * Description : image files selector dialog.
8 *
9 * Copyright (C) 2004-2018 by Gilles Caulier <caulier dot gilles at gmail dot com>
10 *
11 * This program is free software; you can redistribute it
12 * and/or modify it under the terms of the GNU General
13 * Public License as published by the Free Software Foundation;
14 * either version 2, or (at your option) any later version.
15 *
16 * This program is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
20 *
21 * ============================================================ */
22
23 #include "kpimagedialog.h"
24
25 // Qt includes
26
27 #include <QApplication>
28 #include <QStyle>
29 #include <QLabel>
30 #include <QVBoxLayout>
31 #include <QPointer>
32 #include <QDesktopServices>
33 #include <QImageReader>
34 #include <QFileDialog>
35 #include <QLocale>
36 #include <QStandardPaths>
37
38 // KDE includes
39
40 #include <klocalizedstring.h>
41
42 // Libkipi includes
43
44 #include <KIPI/Interface>
45 #include <KIPI/ImageCollection>
46 #include <KIPI/PluginLoader>
47
48 // Local includes
49
50 #include "kipiplugins_debug.h"
51
52 using namespace KIPI;
53
54 namespace KIPIPlugins
55 {
56
57 class KPImageDialogPreview::Private
58 {
59
60 public:
61
Private()62 Private()
63 {
64 imageLabel = nullptr;
65 infoLabel = nullptr;
66 iface = nullptr;
67 meta = nullptr;
68
69 PluginLoader* const pl = PluginLoader::instance();
70
71 if (pl)
72 {
73 iface = pl->interface();
74
75 if (iface)
76 meta = iface->createMetadataProcessor();
77 }
78 }
79
80 public:
81
82 QLabel* imageLabel;
83 QLabel* infoLabel;
84
85 QUrl currentUrl;
86
87 MetadataProcessor* meta;
88 Interface* iface;
89 };
90
KPImageDialogPreview(QWidget * const parent)91 KPImageDialogPreview::KPImageDialogPreview(QWidget* const parent)
92 : QScrollArea(parent),
93 d(new Private)
94 {
95 QVBoxLayout* const vlay = new QVBoxLayout(this);
96 d->imageLabel = new QLabel(this);
97 d->imageLabel->setAlignment(Qt::AlignHCenter | Qt::AlignVCenter);
98 d->imageLabel->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding));
99
100 d->infoLabel = new QLabel(this);
101 d->infoLabel->setAlignment(Qt::AlignCenter);
102
103 vlay->setSpacing(QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing));
104 vlay->setContentsMargins(QMargins());
105 vlay->addWidget(d->imageLabel);
106 vlay->addWidget(d->infoLabel);
107 vlay->addStretch();
108
109 if (d->iface)
110 {
111 connect(d->iface, &Interface::gotThumbnail,
112 this, &KPImageDialogPreview::slotThumbnail);
113 }
114 }
115
~KPImageDialogPreview()116 KPImageDialogPreview::~KPImageDialogPreview()
117 {
118 delete d;
119 }
120
sizeHint() const121 QSize KPImageDialogPreview::sizeHint() const
122 {
123 return QSize(256, 256);
124 }
125
resizeEvent(QResizeEvent *)126 void KPImageDialogPreview::resizeEvent(QResizeEvent*)
127 {
128 QMetaObject::invokeMethod(this, "showPreview", Qt::QueuedConnection);
129 }
130
showPreview()131 void KPImageDialogPreview::showPreview()
132 {
133 QUrl url(d->currentUrl);
134 clearPreview();
135 showPreview(url);
136 }
137
showPreview(const QUrl & url)138 void KPImageDialogPreview::showPreview(const QUrl& url)
139 {
140 if (!url.isValid())
141 {
142 clearPreview();
143 return;
144 }
145
146 if (url != d->currentUrl)
147 {
148 QString make, model, dateTime, aperture, focalLength, exposureTime, sensitivity;
149 QString unavailable(i18n("<i>unavailable</i>"));
150 clearPreview();
151 d->currentUrl = url;
152
153 if (d->iface)
154 {
155 d->iface->thumbnail(d->currentUrl, 256);
156 }
157 else
158 {
159 qCDebug(KIPIPLUGINS_LOG) << "No KIPI interface available : thumbnails will not generated.";
160 }
161
162 // Try to use Metadata Processor from KIPI host to identify image.
163
164 if (d->meta &&
165 d->meta->load(d->currentUrl) &&
166 (d->meta->hasExif() || d->meta->hasXmp()))
167 {
168 make = d->meta->getExifTagString(QLatin1String("Exif.Image.Make"));
169 if (make.isEmpty())
170 make = d->meta->getXmpTagString(QLatin1String("Xmp.tiff.Make"));
171
172 model = d->meta->getExifTagString(QLatin1String("Exif.Image.Model"));
173 if (model.isEmpty())
174 model = d->meta->getXmpTagString(QLatin1String("Xmp.tiff.Model"));
175
176 if (d->meta->getImageDateTime().isValid())
177 dateTime = QLocale().toString(d->meta->getImageDateTime(), QLocale::ShortFormat);
178
179 aperture = d->meta->getExifTagString(QLatin1String("Exif.Photo.FNumber"));
180 if (aperture.isEmpty())
181 {
182 aperture = d->meta->getExifTagString(QLatin1String("Exif.Photo.ApertureValue"));
183 if (aperture.isEmpty())
184 {
185 aperture = d->meta->getXmpTagString(QLatin1String("Xmp.exif.FNumber"));
186 if (aperture.isEmpty())
187 aperture = d->meta->getXmpTagString(QLatin1String("Xmp.exif.ApertureValue"));
188 }
189 }
190
191 focalLength = d->meta->getExifTagString(QLatin1String("Exif.Photo.FocalLength"));
192 if (focalLength.isEmpty())
193 focalLength = d->meta->getXmpTagString(QLatin1String("Xmp.exif.FocalLength"));
194
195 exposureTime = d->meta->getExifTagString(QLatin1String("Exif.Photo.ExposureTime"));
196 if (exposureTime.isEmpty())
197 {
198 exposureTime = d->meta->getExifTagString(QLatin1String("Exif.Photo.ShutterSpeedValue"));
199 if (exposureTime.isEmpty())
200 {
201 exposureTime = d->meta->getXmpTagString(QLatin1String("Xmp.exif.ExposureTime"));
202 if (exposureTime.isEmpty())
203 exposureTime = d->meta->getXmpTagString(QLatin1String("Xmp.exif.ShutterSpeedValue"));
204 }
205 }
206
207 sensitivity = d->meta->getExifTagString(QLatin1String("Exif.Photo.ISOSpeedRatings"));
208 if (sensitivity.isEmpty())
209 {
210 sensitivity = d->meta->getExifTagString(QLatin1String("Exif.Photo.ExposureIndex"));
211 if (sensitivity.isEmpty())
212 {
213 sensitivity = d->meta->getXmpTagString(QLatin1String("Xmp.exif.ISOSpeedRatings"));
214 if (sensitivity.isEmpty())
215 sensitivity = d->meta->getXmpTagString(QLatin1String("Xmp.exif.ExposureIndex"));
216 }
217 }
218 }
219
220 if (make.isEmpty()) make = unavailable;
221 if (model.isEmpty()) model = unavailable;
222 if (dateTime.isEmpty()) dateTime = unavailable;
223 if (aperture.isEmpty()) aperture = unavailable;
224 if (focalLength.isEmpty()) focalLength = unavailable;
225 if (exposureTime.isEmpty()) exposureTime = unavailable;
226
227 if (sensitivity.isEmpty()) sensitivity = unavailable;
228 else sensitivity = i18n("%1 ISO", sensitivity);
229
230 QString identify(QString::fromLatin1("<qt><center>"));
231 QString cellBeg(QString::fromLatin1("<tr><td><nobr><font size=-1>"));
232 QString cellMid(QString::fromLatin1("</font></nobr></td><td><nobr><font size=-1>"));
233 QString cellEnd(QString::fromLatin1("</font></nobr></td></tr>"));
234
235 identify += QString::fromLatin1("<table cellspacing=0 cellpadding=0>");
236 identify += cellBeg + i18n("<i>Make:</i>") + cellMid + make + cellEnd;
237 identify += cellBeg + i18n("<i>Model:</i>") + cellMid + model + cellEnd;
238 identify += cellBeg + i18n("<i>Created:</i>") + cellMid + dateTime + cellEnd;
239 identify += cellBeg + i18n("<i>Aperture:</i>") + cellMid + aperture + cellEnd;
240 identify += cellBeg + i18n("<i>Focal:</i>") + cellMid + focalLength + cellEnd;
241 identify += cellBeg + i18n("<i>Exposure:</i>") + cellMid + exposureTime + cellEnd;
242 identify += cellBeg + i18n("<i>Sensitivity:</i>") + cellMid + sensitivity + cellEnd;
243 identify += QString::fromLatin1("</table></center></qt>");
244
245 d->infoLabel->setText(identify);
246 }
247 }
248
slotThumbnail(const QUrl & url,const QPixmap & pix)249 void KPImageDialogPreview::slotThumbnail(const QUrl& url, const QPixmap& pix)
250 {
251 if (url == d->currentUrl)
252 {
253 QPixmap pixmap;
254 QSize s = d->imageLabel->contentsRect().size();
255
256 if (s.width() < pix.width() || s.height() < pix.height())
257 pixmap = pix.scaled(s, Qt::KeepAspectRatio);
258 else
259 pixmap = pix;
260
261 d->imageLabel->setPixmap(pixmap);
262 }
263 }
264
clearPreview()265 void KPImageDialogPreview::clearPreview()
266 {
267 d->imageLabel->clear();
268 d->infoLabel->clear();
269 d->currentUrl = QUrl();
270 }
271
272 // ------------------------------------------------------------------------
273
274 class KPImageDialog::Private
275 {
276
277 public:
278
Private()279 Private()
280 {
281 onlyRaw = false;
282 singleSelect = false;
283 iface = nullptr;
284
285 PluginLoader* const pl = PluginLoader::instance();
286
287 if (pl)
288 {
289 iface = pl->interface();
290 }
291 }
292
293 bool onlyRaw;
294 bool singleSelect;
295
296 QString fileFormats;
297
298 QUrl url;
299 QList<QUrl> urls;
300
301 Interface* iface;
302 };
303
KPImageDialog(QWidget * const parent,bool singleSelect,bool onlyRaw)304 KPImageDialog::KPImageDialog(QWidget* const parent, bool singleSelect, bool onlyRaw)
305 : d(new Private)
306 {
307 d->singleSelect = singleSelect;
308 d->onlyRaw = onlyRaw;
309
310 QStringList patternList;
311 QString allPictures;
312 QString rawFiles;
313
314 if (d->iface)
315 {
316 rawFiles = d->iface->rawFiles();
317 }
318
319 if (!d->onlyRaw)
320 {
321 patternList = d->iface->supportedImageMimeTypes();
322
323 // All Images from list must been always the first entry given by KDE API
324 allPictures = patternList[0];
325
326 allPictures.insert(allPictures.indexOf(QString::fromLatin1("|")), rawFiles + QString::fromLatin1(" *.JPE *.TIF"));
327 patternList.removeAll(patternList[0]);
328 patternList.prepend(allPictures);
329 }
330 else
331 {
332 allPictures.insert(allPictures.indexOf(QString::fromLatin1("|")), rawFiles + QString::fromLatin1(" *.JPE *.TIF"));
333 patternList.prepend(allPictures);
334 }
335
336 // Added RAW file formats supported by dcraw program like a type mime.
337 // Nota: we cannot use here "image/x-raw" type mime from KDE because it uncomplete
338 // or unavailable(see file #121242 in bug).
339 patternList.append(i18n("\n%1|Camera RAW files", rawFiles));
340
341 d->fileFormats = patternList.join(QString::fromLatin1("\n"));
342
343 QString alternatePath = QStandardPaths::writableLocation(QStandardPaths::PicturesLocation);
344 QPointer<QFileDialog> dlg = new QFileDialog(parent, QString(),
345 d->iface ? d->iface->currentAlbum().url().toLocalFile()
346 : alternatePath,
347 d->fileFormats);
348 /*
349 NOTE: KF5 do not provide a way to pass preview widget.
350 KPImageDialogPreview* const preview = new KPImageDialogPreview(dlg);
351 dlg->setPreviewWidget(preview);
352 */
353 dlg->setAcceptMode(QFileDialog::AcceptOpen);
354
355 if (singleSelect)
356 {
357 dlg->setFileMode( QFileDialog::ExistingFile );
358 dlg->setWindowTitle(i18n("Select an Image"));
359 dlg->exec();
360 d->url = dlg->selectedUrls().first();
361 }
362 else
363 {
364 dlg->setFileMode( QFileDialog::ExistingFiles );
365 dlg->setWindowTitle(i18n("Select Images"));
366 dlg->exec();
367 d->urls = dlg->selectedUrls();
368 }
369
370 delete dlg;
371 }
372
~KPImageDialog()373 KPImageDialog::~KPImageDialog()
374 {
375 delete d;
376 }
377
onlyRaw() const378 bool KPImageDialog::onlyRaw() const
379 {
380 return d->onlyRaw;
381 }
382
singleSelect() const383 bool KPImageDialog::singleSelect() const
384 {
385 return d->singleSelect;
386 }
387
fileFormats() const388 QString KPImageDialog::fileFormats() const
389 {
390 return d->fileFormats;
391 }
392
url() const393 QUrl KPImageDialog::url() const
394 {
395 return d->url;
396 }
397
urls() const398 QList<QUrl> KPImageDialog::urls() const
399 {
400 return d->urls;
401 }
402
getImageUrl(QWidget * const parent,bool onlyRaw)403 QUrl KPImageDialog::getImageUrl(QWidget* const parent, bool onlyRaw)
404 {
405 KPImageDialog dlg(parent, true, onlyRaw);
406
407 if (dlg.url().isValid())
408 {
409 return dlg.url();
410 }
411 else
412 {
413 return QUrl();
414 }
415 }
416
getImageUrls(QWidget * const parent,bool onlyRaw)417 QList<QUrl> KPImageDialog::getImageUrls(QWidget* const parent, bool onlyRaw)
418 {
419 KPImageDialog dlg(parent, false, onlyRaw);
420
421 if (!dlg.urls().isEmpty())
422 {
423 return dlg.urls();
424 }
425 else
426 {
427 return QList<QUrl>();
428 }
429 }
430
431 } // namespace KIPIPlugins
432