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