File indexing completed on 2025-01-19 03:51:20

0001 /* ============================================================
0002  *
0003  * This file is a part of digiKam project
0004  * https://www.digikam.org
0005  *
0006  * Date        : 2011-03-14
0007  * Description : a dialog to edit EXIF,IPTC and XMP metadata
0008  *
0009  * SPDX-FileCopyrightText: 2011      by Victor Dodon <dodon dot victor at gmail dot com>
0010  * SPDX-FileCopyrightText: 2006-2024 by Gilles Caulier <caulier dot gilles at gmail dot com>
0011  *
0012  * SPDX-License-Identifier: GPL-2.0-or-later
0013  *
0014  * ============================================================ */
0015 
0016 #include "metadataeditdialog.h"
0017 
0018 // Qt includes
0019 
0020 #include <QKeyEvent>
0021 #include <QPointer>
0022 #include <QObject>
0023 #include <QApplication>
0024 #include <QMenu>
0025 #include <QTabWidget>
0026 #include <QDialogButtonBox>
0027 #include <QPushButton>
0028 #include <QVBoxLayout>
0029 #include <QImage>
0030 #include <QBuffer>
0031 #include <QPainter>
0032 #include <QPalette>
0033 #include <QIcon>
0034 
0035 // KDE includes
0036 
0037 #include <ksharedconfig.h>
0038 #include <kconfiggroup.h>
0039 #include <klocalizedstring.h>
0040 
0041 // Local includes
0042 
0043 #include "exifeditwidget.h"
0044 #include "iptceditwidget.h"
0045 #include "xmpeditwidget.h"
0046 #include "thumbnailloadthread.h"
0047 #include "dxmlguiwindow.h"
0048 
0049 namespace DigikamGenericMetadataEditPlugin
0050 {
0051 
0052 class Q_DECL_HIDDEN MetadataEditDialog::Private
0053 {
0054 public:
0055 
0056     explicit Private()
0057       : isReadOnly  (false),
0058         tabWidget   (nullptr),
0059         tabExif     (nullptr),
0060         tabIptc     (nullptr),
0061         tabXmp      (nullptr),
0062         catcher     (nullptr),
0063         iface   (nullptr)
0064     {
0065     }
0066 
0067     bool                   isReadOnly;
0068 
0069     QString                preview;
0070 
0071     QList<QUrl>            urls;
0072     QList<QUrl>::iterator  currItem;
0073 
0074     QTabWidget*            tabWidget;
0075 
0076     EXIFEditWidget*        tabExif;
0077     IPTCEditWidget*        tabIptc;
0078     XMPEditWidget*         tabXmp;
0079 
0080     ThumbnailImageCatcher* catcher;
0081 
0082     DInfoInterface*        iface;
0083 };
0084 
0085 MetadataEditDialog::MetadataEditDialog(QWidget* const parent, DInfoInterface* const iface)
0086     : DPluginDialog(parent, QLatin1String("Metadata Edit Dialog")),
0087       d            (new Private)
0088 {
0089     d->iface = iface;
0090 
0091     setWindowTitle(i18nc("@title:window", "Metadata Editor"));
0092     setModal(true);
0093 
0094     ThumbnailLoadThread* const thread = new ThumbnailLoadThread;
0095     thread->setThumbnailSize(48);
0096     thread->setPixmapRequested(false);
0097     d->catcher                        = new ThumbnailImageCatcher(thread, this);
0098 
0099     d->urls     = d->iface->currentSelectedItems();
0100     d->currItem = d->urls.begin();
0101     updatePreview();
0102 
0103     QDialogButtonBox::StandardButtons btns = QDialogButtonBox::Ok    |
0104                                              QDialogButtonBox::Apply |
0105                                              QDialogButtonBox::Close |
0106                                              QDialogButtonBox::No    |   // NextPrevious item
0107                                              QDialogButtonBox::Yes;      // Previous item
0108 
0109     m_buttons = new QDialogButtonBox(btns, this);
0110     m_buttons->button(QDialogButtonBox::Ok)->setDefault(true);
0111     m_buttons->button(QDialogButtonBox::Apply)->setEnabled(false);
0112     m_buttons->button(QDialogButtonBox::No)->setText(i18nc("@action: button",  "Next"));
0113     m_buttons->button(QDialogButtonBox::No)->setIcon(QIcon::fromTheme(QLatin1String("go-next")));
0114     m_buttons->button(QDialogButtonBox::Yes)->setText(i18nc("@action: button", "Previous"));
0115     m_buttons->button(QDialogButtonBox::Yes)->setIcon(QIcon::fromTheme(QLatin1String("go-previous")));
0116 
0117     if (d->urls.count() <= 1)
0118     {
0119         m_buttons->button(QDialogButtonBox::No)->setDisabled(true);
0120         m_buttons->button(QDialogButtonBox::Yes)->setDisabled(true);
0121     }
0122 
0123     d->tabWidget = new QTabWidget(this);
0124     d->tabExif   = new EXIFEditWidget(this);
0125     d->tabIptc   = new IPTCEditWidget(this);
0126     d->tabXmp    = new XMPEditWidget(this);
0127     d->tabWidget->addTab(d->tabExif, i18nc("@item", "Edit EXIF"));
0128     d->tabWidget->addTab(d->tabIptc, i18nc("@item", "Edit IPTC"));
0129     d->tabWidget->addTab(d->tabXmp,  i18nc("@item", "Edit XMP"));
0130 
0131     QVBoxLayout* const vbx = new QVBoxLayout(this);
0132     vbx->addWidget(d->tabWidget);
0133     vbx->addWidget(m_buttons);
0134     setLayout(vbx);
0135 
0136     //----------------------------------------------------------
0137 
0138     connect(d->tabExif, SIGNAL(signalModified()),
0139             this, SLOT(slotModified()));
0140 
0141     connect(d->tabIptc, SIGNAL(signalModified()),
0142             this, SLOT(slotModified()));
0143 
0144     connect(d->tabXmp, SIGNAL(signalModified()),
0145             this, SLOT(slotModified()));
0146 
0147     connect(d->tabExif, SIGNAL(signalSetReadOnly(bool)),
0148             this, SLOT(slotSetReadOnly(bool)));
0149 
0150     connect(d->tabIptc, SIGNAL(signalSetReadOnly(bool)),
0151             this, SLOT(slotSetReadOnly(bool)));
0152 
0153     connect(d->tabXmp, SIGNAL(signalSetReadOnly(bool)),
0154             this, SLOT(slotSetReadOnly(bool)));
0155 
0156     connect(m_buttons->button(QDialogButtonBox::Apply), SIGNAL(clicked()),
0157             this, SLOT(slotApply()));
0158 
0159     connect(m_buttons->button(QDialogButtonBox::Ok), SIGNAL(clicked()),
0160             this, SLOT(slotOk()));
0161 
0162     connect(m_buttons->button(QDialogButtonBox::Close), SIGNAL(clicked()),
0163             this, SLOT(slotClose()));
0164 
0165     connect(m_buttons->button(QDialogButtonBox::No), SIGNAL(clicked()),
0166             this, SLOT(slotNext()));
0167 
0168     connect(m_buttons->button(QDialogButtonBox::Yes), SIGNAL(clicked()),
0169             this, SLOT(slotPrevious()));
0170 
0171     connect(this, SIGNAL(signalMetadataChangedForUrl(QUrl)),
0172             d->iface, SLOT(slotMetadataChangedForUrl(QUrl)));
0173 
0174     //----------------------------------------------------------
0175 
0176     readSettings();
0177     slotItemChanged();
0178 }
0179 
0180 MetadataEditDialog::~MetadataEditDialog()
0181 {
0182     d->catcher->thread()->stopAllTasks();
0183     d->catcher->cancel();
0184 
0185     delete d->catcher->thread();
0186     delete d->catcher;
0187     delete d;
0188 }
0189 
0190 QString MetadataEditDialog::currentItemTitleHeader(const QString& title) const
0191 {
0192     QString start = QLatin1String("<qt><table cellspacing=\"0\" cellpadding=\"0\" width=\"250\" border=\"0\">");
0193     QString end   = QLatin1String("</table></qt>");
0194 
0195     return QString::fromLatin1("%1<tr><td>%2</td><td>%3</td></tr>%4").arg(start).arg(d->preview).arg(title).arg(end);
0196 }
0197 
0198 void MetadataEditDialog::updatePreview()
0199 {
0200     d->catcher->setActive(true);
0201 
0202     d->catcher->thread()->find(ThumbnailIdentifier(d->currItem->toLocalFile()));
0203     d->catcher->enqueue();
0204     QList<QImage> images = d->catcher->waitForThumbnails();
0205 
0206     QImage img(48, 48, QImage::Format_ARGB32);
0207     QImage thm           = images.first();
0208     QPainter p(&img);
0209     p.fillRect(img.rect(), QPalette().window());
0210     p.setPen(Qt::black);
0211     p.drawRect(img.rect().left(), img.rect().top(), img.rect().right()-1, img.rect().bottom()-1);
0212     p.drawImage((img.width() - thm.width())/2, (img.height() - thm.height())/2, thm);
0213 
0214     QByteArray byteArray;
0215     QBuffer    buffer(&byteArray);
0216     img.save(&buffer, "PNG");
0217     d->preview = QString::fromLatin1("<img src=\"data:image/png;base64,%1\">  ").arg(QLatin1String(byteArray.toBase64().data()));
0218 
0219     d->catcher->setActive(false);
0220 }
0221 
0222 QList<QUrl>::iterator MetadataEditDialog::currentItem() const
0223 {
0224     return d->currItem;
0225 }
0226 
0227 void MetadataEditDialog::slotModified()
0228 {
0229     bool modified = false;
0230 
0231     switch (d->tabWidget->currentIndex())
0232     {
0233         case 0:
0234             modified = d->tabExif->isModified();
0235             break;
0236 
0237         case 1:
0238             modified = d->tabIptc->isModified();
0239             break;
0240 
0241         case 2:
0242             modified = d->tabXmp->isModified();
0243             break;
0244     }
0245 
0246     m_buttons->button(QDialogButtonBox::Apply)->setEnabled(modified);
0247 }
0248 
0249 void MetadataEditDialog::slotOk()
0250 {
0251     slotApply();
0252     saveSettings();
0253     accept();
0254 }
0255 
0256 void MetadataEditDialog::slotClose()
0257 {
0258     saveSettings();
0259     close();
0260 }
0261 
0262 void MetadataEditDialog::slotApply()
0263 {
0264     d->tabExif->apply();
0265     d->tabIptc->apply();
0266     d->tabXmp->apply();
0267     Q_EMIT signalMetadataChangedForUrl(*d->currItem);
0268     slotItemChanged();
0269 }
0270 
0271 void MetadataEditDialog::slotNext()
0272 {
0273     slotApply();
0274     ++d->currItem;
0275     slotItemChanged();
0276 }
0277 
0278 void MetadataEditDialog::slotPrevious()
0279 {
0280     slotApply();
0281     --d->currItem;
0282     slotItemChanged();
0283 }
0284 
0285 void MetadataEditDialog::readSettings()
0286 {
0287     KSharedConfig::Ptr config = KSharedConfig::openConfig();
0288     KConfigGroup group        = config->group(QLatin1String("Metadata Edit Dialog"));
0289     d->tabWidget->setCurrentIndex(group.readEntry(QLatin1String("Tab Index"), 0));
0290 }
0291 
0292 void MetadataEditDialog::saveSettings()
0293 {
0294     KSharedConfig::Ptr config = KSharedConfig::openConfig();
0295     KConfigGroup group        = config->group(QLatin1String("Metadata Edit Dialog"));
0296     group.writeEntry(QLatin1String("Tab Index"), d->tabWidget->currentIndex());
0297 
0298     d->tabExif->saveSettings();
0299     d->tabIptc->saveSettings();
0300     d->tabXmp->saveSettings();
0301 }
0302 
0303 void MetadataEditDialog::slotItemChanged()
0304 {
0305     updatePreview();
0306     d->tabExif->slotItemChanged();
0307     d->tabIptc->slotItemChanged();
0308     d->tabXmp->slotItemChanged();
0309 
0310     setWindowTitle(i18nc("@title:window", "%1 (%2/%3) - Edit Metadata",
0311         (*d->currItem).fileName(),
0312         d->urls.indexOf(*(d->currItem))+1,
0313         d->urls.count()));
0314 
0315     m_buttons->button(QDialogButtonBox::No)->setEnabled(*(d->currItem) != d->urls.last());
0316     m_buttons->button(QDialogButtonBox::Yes)->setEnabled(*(d->currItem) != d->urls.first());
0317     m_buttons->button(QDialogButtonBox::Apply)->setEnabled(!d->isReadOnly);
0318 }
0319 
0320 bool MetadataEditDialog::eventFilter(QObject*, QEvent* e)
0321 {
0322     if (e->type() == QEvent::KeyPress)
0323     {
0324         QKeyEvent* const k = (QKeyEvent*)e;
0325 
0326         if      ((k->modifiers() == Qt::ControlModifier) &&
0327                  ((k->key() == Qt::Key_Enter) || (k->key() == Qt::Key_Return)))
0328         {
0329             slotApply();
0330 
0331             if (m_buttons->button(QDialogButtonBox::No)->isEnabled())
0332             {
0333                 slotNext();
0334             }
0335 
0336             return true;
0337         }
0338         else if ((k->modifiers() == Qt::ShiftModifier) &&
0339                  ((k->key() == Qt::Key_Enter) || (k->key() == Qt::Key_Return)))
0340         {
0341             slotApply();
0342 
0343             if (m_buttons->button(QDialogButtonBox::Yes)->isEnabled())
0344             {
0345                 slotPrevious();
0346             }
0347 
0348             return true;
0349         }
0350 
0351         return false;
0352     }
0353 
0354     return false;
0355 }
0356 
0357 void MetadataEditDialog::closeEvent(QCloseEvent* e)
0358 {
0359     if (!e)
0360     {
0361         return;
0362     }
0363 
0364     saveSettings();
0365     e->accept();
0366 }
0367 
0368 void MetadataEditDialog::slotSetReadOnly(bool state)
0369 {
0370     d->isReadOnly = state;
0371 }
0372 
0373 } // namespace DigikamGenericMetadataEditPlugin
0374 
0375 #include "moc_metadataeditdialog.cpp"