1 /* ============================================================
2  *
3  * This file is a part of digiKam project
4  * https://www.digikam.org
5  *
6  * Date        : 2006-10-18
7  * Description : EXIF date and time settings page.
8  *
9  * Copyright (C) 2006-2021 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 "exifdatetime.h"
24 
25 // Qt includes
26 
27 #include <QCheckBox>
28 #include <QPushButton>
29 #include <QGridLayout>
30 #include <QApplication>
31 #include <QStyle>
32 #include <QSpinBox>
33 #include <QDateTimeEdit>
34 
35 // KDE includes
36 
37 #include <klocalizedstring.h>
38 
39 // Local includes
40 
41 #include "dlayoutbox.h"
42 #include "dexpanderbox.h"
43 
44 namespace DigikamGenericMetadataEditPlugin
45 {
46 
47 class Q_DECL_HIDDEN EXIFDateTime::Private
48 {
49 public:
50 
Private()51     explicit Private()
52     {
53         dateCreatedSel             = nullptr;
54         dateOriginalSel            = nullptr;
55         dateDigitalizedSel         = nullptr;
56         dateCreatedSubSecEdit      = nullptr;
57         dateOriginalSubSecEdit     = nullptr;
58         dateDigitalizedSubSecEdit  = nullptr;
59 
60         dateCreatedCheck           = nullptr;
61         dateOriginalCheck          = nullptr;
62         dateDigitalizedCheck       = nullptr;
63         dateCreatedSubSecCheck     = nullptr;
64         dateOriginalSubSecCheck    = nullptr;
65         dateDigitalizedSubSecCheck = nullptr;
66         syncXMPDateCheck           = nullptr;
67         syncIPTCDateCheck          = nullptr;
68 
69         setTodayCreatedBtn         = nullptr;
70         setTodayOriginalBtn        = nullptr;
71         setTodayDigitalizedBtn     = nullptr;
72     }
73 
74     QCheckBox*       dateCreatedCheck;
75     QCheckBox*       dateOriginalCheck;
76     QCheckBox*       dateDigitalizedCheck;
77     QCheckBox*       dateCreatedSubSecCheck;
78     QCheckBox*       dateOriginalSubSecCheck;
79     QCheckBox*       dateDigitalizedSubSecCheck;
80     QCheckBox*       syncXMPDateCheck;
81     QCheckBox*       syncIPTCDateCheck;
82 
83     QPushButton*     setTodayCreatedBtn;
84     QPushButton*     setTodayOriginalBtn;
85     QPushButton*     setTodayDigitalizedBtn;
86 
87     QSpinBox*        dateCreatedSubSecEdit;
88     QSpinBox*        dateOriginalSubSecEdit;
89     QSpinBox*        dateDigitalizedSubSecEdit;
90 
91     QDateTimeEdit*   dateCreatedSel;
92     QDateTimeEdit*   dateOriginalSel;
93     QDateTimeEdit*   dateDigitalizedSel;
94 };
95 
EXIFDateTime(QWidget * const parent)96 EXIFDateTime::EXIFDateTime(QWidget* const parent)
97     : QWidget(parent),
98       d(new Private)
99 {
100     QGridLayout* const grid = new QGridLayout(this);
101 
102     QString dateTimeFormat  = QLocale().dateTimeFormat(QLocale::ShortFormat);
103 
104     if (!dateTimeFormat.contains(QLatin1String("yyyy")))
105     {
106         dateTimeFormat.replace(QLatin1String("yy"),
107                                QLatin1String("yyyy"));
108     }
109 
110     if (!dateTimeFormat.contains(QLatin1String("ss")))
111     {
112         dateTimeFormat.replace(QLatin1String("mm"),
113                                QLatin1String("mm:ss"));
114     }
115 
116     // --------------------------------------------------------
117 
118     d->dateCreatedCheck       = new QCheckBox(i18n("Creation date and time"), this);
119     d->dateCreatedSubSecCheck = new QCheckBox(i18n("Creation sub-second"), this);
120 
121     d->dateCreatedSel         = new QDateTimeEdit(this);
122     d->dateCreatedSel->setDisplayFormat(dateTimeFormat);
123 
124     d->dateCreatedSubSecEdit  = new QSpinBox(this);
125     d->dateCreatedSubSecEdit->setMinimum(0);
126     d->dateCreatedSubSecEdit->setMaximum(999);
127     d->dateCreatedSubSecEdit->setSingleStep(1);
128     d->dateCreatedSubSecEdit->setValue(0);
129 
130     d->syncXMPDateCheck       = new QCheckBox(i18n("Sync XMP creation date"), this);
131     d->syncIPTCDateCheck      = new QCheckBox(i18n("Sync IPTC creation date"), this);
132 
133     d->setTodayCreatedBtn     = new QPushButton();
134     d->setTodayCreatedBtn->setIcon(QIcon::fromTheme(QLatin1String("view-calendar")));
135     d->setTodayCreatedBtn->setWhatsThis(i18n("Set creation date to today"));
136 
137     if (!DMetadata::supportXmp())
138         d->syncXMPDateCheck->setEnabled(false);
139 
140     d->dateCreatedSel->setWhatsThis(i18n("Set here the date and time of image creation. "
141                                        "In this standard it is the date and time the file was changed."));
142     d->dateCreatedSubSecEdit->setWhatsThis(i18n("Set here the fractions of seconds for the date "
143                                                 "and time of image creation."));
144 
145     slotSetTodayCreated();
146 
147     // --------------------------------------------------------
148 
149     d->dateOriginalCheck       = new QCheckBox(i18n("Original date and time"), this);
150     d->dateOriginalSubSecCheck = new QCheckBox(i18n("Original sub-second"), this);
151 
152     d->dateOriginalSel         = new QDateTimeEdit(this);
153     d->dateOriginalSel->setDisplayFormat(dateTimeFormat);
154 
155     d->dateOriginalSubSecEdit  = new QSpinBox(this);
156     d->dateOriginalSubSecEdit->setMinimum(0);
157     d->dateOriginalSubSecEdit->setMaximum(999);
158     d->dateOriginalSubSecEdit->setSingleStep(1);
159     d->dateOriginalSubSecEdit->setValue(0);
160 
161     d->setTodayOriginalBtn     = new QPushButton();
162     d->setTodayOriginalBtn->setIcon(QIcon::fromTheme(QLatin1String("view-calendar")));
163     d->setTodayOriginalBtn->setWhatsThis(i18n("Set original date to today"));
164 
165     d->dateOriginalSel->setWhatsThis(i18n("Set here the date and time when the original image "
166                                           "data was generated. For a digital still camera the date and "
167                                           "time the picture was taken are recorded."));
168     d->dateOriginalSubSecEdit->setWhatsThis(i18n("Set here the fractions of seconds for the date "
169                                                  "and time when the original image data was generated."));
170 
171     slotSetTodayOriginal();
172 
173     // --------------------------------------------------------
174 
175     d->dateDigitalizedCheck       = new QCheckBox(i18n("Digitization date and time"), this);
176     d->dateDigitalizedSubSecCheck = new QCheckBox(i18n("Digitization sub-second"), this);
177 
178     d->dateDigitalizedSel         = new QDateTimeEdit(this);
179     d->dateDigitalizedSel->setDisplayFormat(dateTimeFormat);
180 
181     d->dateDigitalizedSubSecEdit  = new QSpinBox(this);
182     d->dateDigitalizedSubSecEdit->setMinimum(0);
183     d->dateDigitalizedSubSecEdit->setMaximum(999);
184     d->dateDigitalizedSubSecEdit->setSingleStep(1);
185     d->dateDigitalizedSubSecEdit->setValue(0);
186 
187     d->setTodayDigitalizedBtn     = new QPushButton();
188     d->setTodayDigitalizedBtn->setIcon(QIcon::fromTheme(QLatin1String("view-calendar")));
189     d->setTodayDigitalizedBtn->setWhatsThis(i18n("Set digitization date to today"));
190 
191     d->dateDigitalizedSel->setWhatsThis(i18n("Set here the date and time when the image was "
192                                              "stored as digital data. If, for example, an image was "
193                                              "captured by a digital still camera and at the same "
194                                              "time the file was recorded, then Original and Digitization "
195                                              "date and time will have the same contents."));
196     d->dateDigitalizedSubSecEdit->setWhatsThis(i18n("Set here the fractions of seconds for the date "
197                                                     "and time when the image was stored as digital data."));
198 
199     slotSetTodayDigitalized();
200 
201     // --------------------------------------------------------
202 
203     grid->addWidget(d->dateCreatedCheck,                    0, 0, 1, 1);
204     grid->addWidget(d->dateCreatedSubSecCheck,              0, 1, 1, 3);
205     grid->addWidget(d->dateCreatedSel,                      1, 0, 1, 1);
206     grid->addWidget(d->dateCreatedSubSecEdit,               1, 1, 1, 1);
207     grid->addWidget(d->setTodayCreatedBtn,                  1, 3, 1, 1);
208     grid->addWidget(d->syncXMPDateCheck,                    2, 0, 1, 4);
209     grid->addWidget(d->syncIPTCDateCheck,                   3, 0, 1, 4);
210     grid->addWidget(new DLineWidget(Qt::Horizontal, this),  4, 0, 1, 4);
211     grid->addWidget(d->dateOriginalCheck,                   5, 0, 1, 1);
212     grid->addWidget(d->dateOriginalSubSecCheck,             5, 1, 1, 3);
213     grid->addWidget(d->dateOriginalSel,                     6, 0, 1, 1);
214     grid->addWidget(d->dateOriginalSubSecEdit,              6, 1, 1, 1);
215     grid->addWidget(d->setTodayOriginalBtn,                 6, 3, 1, 1);
216     grid->addWidget(new DLineWidget(Qt::Horizontal, this),  7, 0, 1, 4);
217     grid->addWidget(d->dateDigitalizedCheck,                8, 0, 1, 1);
218     grid->addWidget(d->dateDigitalizedSubSecCheck,          8, 1, 1, 3);
219     grid->addWidget(d->dateDigitalizedSel,                  9, 0, 1, 1);
220     grid->addWidget(d->dateDigitalizedSubSecEdit,           9, 1, 1, 1);
221     grid->addWidget(d->setTodayDigitalizedBtn,              9, 3, 1, 1);
222     grid->setColumnStretch(2, 10);
223     grid->setRowStretch(10, 10);
224     grid->setContentsMargins(QMargins());
225     grid->setSpacing(QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing));
226 
227     // --------------------------------------------------------
228 
229     connect(d->dateCreatedCheck, SIGNAL(toggled(bool)),
230             d->dateCreatedSel, SLOT(setEnabled(bool)));
231 
232     connect(d->dateOriginalCheck, SIGNAL(toggled(bool)),
233             d->dateOriginalSel, SLOT(setEnabled(bool)));
234 
235     connect(d->dateDigitalizedCheck, SIGNAL(toggled(bool)),
236             d->dateDigitalizedSel, SLOT(setEnabled(bool)));
237 
238     connect(d->dateCreatedSubSecCheck, SIGNAL(toggled(bool)),
239             d->dateCreatedSubSecEdit, SLOT(setEnabled(bool)));
240 
241     connect(d->dateOriginalSubSecCheck, SIGNAL(toggled(bool)),
242             d->dateOriginalSubSecEdit, SLOT(setEnabled(bool)));
243 
244     connect(d->dateDigitalizedSubSecCheck, SIGNAL(toggled(bool)),
245             d->dateDigitalizedSubSecEdit, SLOT(setEnabled(bool)));
246 
247     connect(d->dateCreatedCheck, SIGNAL(toggled(bool)),
248             d->syncXMPDateCheck, SLOT(setEnabled(bool)));
249 
250     connect(d->dateCreatedCheck, SIGNAL(toggled(bool)),
251             d->syncIPTCDateCheck, SLOT(setEnabled(bool)));
252 
253     // --------------------------------------------------------
254 
255     connect(d->dateCreatedCheck, SIGNAL(toggled(bool)),
256             this, SIGNAL(signalModified()));
257 
258     connect(d->dateOriginalCheck, SIGNAL(toggled(bool)),
259             this, SIGNAL(signalModified()));
260 
261     connect(d->dateDigitalizedCheck, SIGNAL(toggled(bool)),
262             this, SIGNAL(signalModified()));
263 
264     connect(d->dateCreatedSubSecCheck, SIGNAL(toggled(bool)),
265             this, SIGNAL(signalModified()));
266 
267     connect(d->dateOriginalSubSecCheck, SIGNAL(toggled(bool)),
268             this, SIGNAL(signalModified()));
269 
270     connect(d->dateDigitalizedSubSecCheck, SIGNAL(toggled(bool)),
271             this, SIGNAL(signalModified()));
272 
273     // --------------------------------------------------------
274 
275     connect(d->dateCreatedSubSecEdit, SIGNAL(valueChanged(int)),
276             this, SIGNAL(signalModified()));
277 
278     connect(d->dateOriginalSubSecEdit, SIGNAL(valueChanged(int)),
279             this, SIGNAL(signalModified()));
280 
281     connect(d->dateDigitalizedSubSecEdit, SIGNAL(valueChanged(int)),
282             this, SIGNAL(signalModified()));
283 
284     connect(d->dateCreatedSel, SIGNAL(dateTimeChanged(QDateTime)),
285             this, SIGNAL(signalModified()));
286 
287     connect(d->dateOriginalSel, SIGNAL(dateTimeChanged(QDateTime)),
288             this, SIGNAL(signalModified()));
289 
290     connect(d->dateDigitalizedSel, SIGNAL(dateTimeChanged(QDateTime)),
291             this, SIGNAL(signalModified()));
292 
293     // --------------------------------------------------------
294 
295     connect(d->setTodayCreatedBtn, SIGNAL(clicked()),
296             this, SLOT(slotSetTodayCreated()));
297 
298     connect(d->setTodayOriginalBtn, SIGNAL(clicked()),
299             this, SLOT(slotSetTodayOriginal()));
300 
301     connect(d->setTodayDigitalizedBtn, SIGNAL(clicked()),
302             this, SLOT(slotSetTodayDigitalized()));
303 }
304 
~EXIFDateTime()305 EXIFDateTime::~EXIFDateTime()
306 {
307     delete d;
308 }
309 
slotSetTodayCreated()310 void EXIFDateTime::slotSetTodayCreated()
311 {
312     d->dateCreatedSel->setDateTime(QDateTime::currentDateTime());
313     d->dateCreatedSubSecEdit->setValue(0);
314 }
315 
slotSetTodayOriginal()316 void EXIFDateTime::slotSetTodayOriginal()
317 {
318     d->dateOriginalSel->setDateTime(QDateTime::currentDateTime());
319     d->dateOriginalSubSecEdit->setValue(0);
320 }
321 
slotSetTodayDigitalized()322 void EXIFDateTime::slotSetTodayDigitalized()
323 {
324     d->dateDigitalizedSel->setDateTime(QDateTime::currentDateTime());
325     d->dateDigitalizedSubSecEdit->setValue(0);
326 }
327 
syncXMPDateIsChecked() const328 bool EXIFDateTime::syncXMPDateIsChecked() const
329 {
330     return d->syncXMPDateCheck->isChecked();
331 }
332 
syncIPTCDateIsChecked() const333 bool EXIFDateTime::syncIPTCDateIsChecked() const
334 {
335     return d->syncIPTCDateCheck->isChecked();
336 }
337 
setCheckedSyncXMPDate(bool c)338 void EXIFDateTime::setCheckedSyncXMPDate(bool c)
339 {
340     d->syncXMPDateCheck->setChecked(c);
341 }
342 
setCheckedSyncIPTCDate(bool c)343 void EXIFDateTime::setCheckedSyncIPTCDate(bool c)
344 {
345     d->syncIPTCDateCheck->setChecked(c);
346 }
347 
getEXIFCreationDate() const348 QDateTime EXIFDateTime::getEXIFCreationDate() const
349 {
350     return d->dateCreatedSel->dateTime();
351 }
352 
readMetadata(const DMetadata & meta)353 void EXIFDateTime::readMetadata(const DMetadata& meta)
354 {
355     blockSignals(true);
356 
357     QDateTime datetime;
358     QString datetimeStr, data;
359 
360     d->dateCreatedSel->setDateTime(QDateTime::currentDateTime());
361     d->dateCreatedCheck->setChecked(false);
362     datetimeStr = meta.getExifTagString("Exif.Image.DateTime", false);
363 
364     if (!datetimeStr.isEmpty())
365     {
366         datetime = QDateTime::fromString(datetimeStr, Qt::ISODate);
367 
368         if (datetime.isValid())
369         {
370             d->dateCreatedSel->setDateTime(datetime);
371             d->dateCreatedCheck->setChecked(true);
372         }
373     }
374 
375     d->dateCreatedSel->setEnabled(d->dateCreatedCheck->isChecked());
376     d->syncXMPDateCheck->setEnabled(d->dateCreatedCheck->isChecked());
377     d->syncIPTCDateCheck->setEnabled(d->dateCreatedCheck->isChecked());
378 
379     d->dateCreatedSubSecEdit->setValue(0);
380     d->dateCreatedSubSecCheck->setChecked(false);
381     data = meta.getExifTagString("Exif.Photo.SubSecTime", false);
382 
383     if (!data.isNull())
384     {
385         bool ok    = false;
386         int subsec = data.toInt(&ok);
387 
388         if (ok)
389         {
390             d->dateCreatedSubSecEdit->setValue(subsec);
391             d->dateCreatedSubSecCheck->setChecked(true);
392         }
393     }
394 
395     d->dateCreatedSubSecEdit->setEnabled(d->dateCreatedSubSecCheck->isChecked());
396 
397     d->dateOriginalSel->setDateTime(QDateTime::currentDateTime());
398     d->dateOriginalCheck->setChecked(false);
399     datetimeStr = meta.getExifTagString("Exif.Photo.DateTimeOriginal", false);
400 
401     if (!datetimeStr.isEmpty())
402     {
403         datetime = QDateTime::fromString(datetimeStr, Qt::ISODate);
404 
405         if (datetime.isValid())
406         {
407             d->dateOriginalSel->setDateTime(datetime);
408             d->dateOriginalCheck->setChecked(true);
409         }
410     }
411 
412     d->dateOriginalSel->setEnabled(d->dateOriginalCheck->isChecked());
413 
414     d->dateOriginalSubSecEdit->setValue(0);
415     d->dateOriginalSubSecCheck->setChecked(false);
416     data = meta.getExifTagString("Exif.Photo.SubSecTimeOriginal", false);
417 
418     if (!data.isNull())
419     {
420         bool ok    = false;
421         int subsec = data.toInt(&ok);
422 
423         if (ok)
424         {
425             d->dateOriginalSubSecEdit->setValue(subsec);
426             d->dateOriginalSubSecCheck->setChecked(true);
427         }
428     }
429 
430     d->dateOriginalSubSecEdit->setEnabled(d->dateOriginalSubSecCheck->isChecked());
431 
432     d->dateDigitalizedSel->setDateTime(QDateTime::currentDateTime());
433     d->dateDigitalizedCheck->setChecked(false);
434     datetimeStr = meta.getExifTagString("Exif.Photo.DateTimeDigitized", false);
435 
436     if (!datetimeStr.isEmpty())
437     {
438         datetime = QDateTime::fromString(datetimeStr, Qt::ISODate);
439 
440         if (datetime.isValid())
441         {
442             d->dateDigitalizedSel->setDateTime(datetime);
443             d->dateDigitalizedCheck->setChecked(true);
444         }
445     }
446 
447     d->dateDigitalizedSel->setEnabled(d->dateDigitalizedCheck->isChecked());
448 
449     d->dateDigitalizedSubSecEdit->setValue(0);
450     d->dateDigitalizedSubSecCheck->setChecked(false);
451     data = meta.getExifTagString("Exif.Photo.SubSecTimeDigitized", false);
452 
453     if (!data.isNull())
454     {
455         bool ok    = false;
456         int subsec = data.toInt(&ok);
457 
458         if (ok)
459         {
460             d->dateDigitalizedSubSecEdit->setValue(subsec);
461             d->dateDigitalizedSubSecCheck->setChecked(true);
462         }
463     }
464 
465     d->dateDigitalizedSubSecEdit->setEnabled(d->dateDigitalizedSubSecCheck->isChecked());
466 
467     blockSignals(false);
468 }
469 
applyMetadata(const DMetadata & meta)470 void EXIFDateTime::applyMetadata(const DMetadata& meta)
471 {
472     QString exifDateTimeFormat = QLatin1String("yyyy:MM:dd hh:mm:ss");
473     QString xmpDateTimeFormat  = QLatin1String("yyyy-MM-ddThh:mm:ss");
474 
475     if (d->dateCreatedCheck->isChecked())
476     {
477         meta.setExifTagString("Exif.Image.DateTime",
478             d->dateCreatedSel->dateTime().toString(exifDateTimeFormat));
479 
480         if (meta.supportXmp() && d->syncXMPDateCheck->isChecked())
481         {
482             meta.setXmpTagString("Xmp.exif.DateTimeOriginal",
483                 d->dateCreatedSel->dateTime().toString(xmpDateTimeFormat));
484             meta.setXmpTagString("Xmp.photoshop.DateCreated",
485                 d->dateCreatedSel->dateTime().toString(xmpDateTimeFormat));
486             meta.setXmpTagString("Xmp.tiff.DateTime",
487                 d->dateCreatedSel->dateTime().toString(xmpDateTimeFormat));
488             meta.setXmpTagString("Xmp.xmp.CreateDate",
489                 d->dateCreatedSel->dateTime().toString(xmpDateTimeFormat));
490             meta.setXmpTagString("Xmp.xmp.MetadataDate",
491                 d->dateCreatedSel->dateTime().toString(xmpDateTimeFormat));
492             meta.setXmpTagString("Xmp.xmp.ModifyDate",
493                 d->dateCreatedSel->dateTime().toString(xmpDateTimeFormat));
494         }
495 
496         if (syncIPTCDateIsChecked())
497         {
498             meta.setIptcTagString("Iptc.Application2.DateCreated",
499                        d->dateCreatedSel->dateTime().date().toString(Qt::ISODate));
500             meta.setIptcTagString("Iptc.Application2.TimeCreated",
501                        d->dateCreatedSel->dateTime().time().toString(Qt::ISODate));
502         }
503     }
504     else
505         meta.removeExifTag("Exif.Image.DateTime");
506 
507     if (d->dateCreatedSubSecCheck->isChecked())
508         meta.setExifTagString("Exif.Photo.SubSecTime",
509                    QString::number(d->dateCreatedSubSecEdit->value()));
510     else
511         meta.removeExifTag("Exif.Photo.SubSecTime");
512 
513     if (d->dateOriginalCheck->isChecked())
514         meta.setExifTagString("Exif.Photo.DateTimeOriginal",
515                    d->dateOriginalSel->dateTime().toString(exifDateTimeFormat));
516     else
517         meta.removeExifTag("Exif.Photo.DateTimeOriginal");
518 
519     if (d->dateOriginalSubSecCheck->isChecked())
520         meta.setExifTagString("Exif.Photo.SubSecTimeOriginal",
521                    QString::number(d->dateOriginalSubSecEdit->value()));
522     else
523         meta.removeExifTag("Exif.Photo.SubSecTimeOriginal");
524 
525     if (d->dateDigitalizedCheck->isChecked())
526         meta.setExifTagString("Exif.Photo.DateTimeDigitized",
527                    d->dateDigitalizedSel->dateTime().toString(exifDateTimeFormat));
528     else
529         meta.removeExifTag("Exif.Photo.DateTimeDigitized");
530 
531     if (d->dateDigitalizedSubSecCheck->isChecked())
532         meta.setExifTagString("Exif.Photo.SubSecTimeDigitized",
533                    QString::number(d->dateDigitalizedSubSecEdit->value()));
534     else
535         meta.removeExifTag("Exif.Photo.SubSecTimeDigitized");
536 }
537 
538 } // namespace DigikamGenericMetadataEditPlugin
539