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

0001 /* ============================================================
0002  *
0003  * This file is a part of digiKam project
0004  * https://www.digikam.org
0005  *
0006  * Date        : 2008-09-24
0007  * Description : DNG converter batch dialog
0008  *
0009  * SPDX-FileCopyrightText: 2012      by Smit Mehta <smit dot meh at gmail dot com>
0010  * SPDX-FileCopyrightText: 2008-2024 by Gilles Caulier <caulier dot gilles at gmail dot com>
0011  * SPDX-FileCopyrightText: 2010-2011 by Jens Mueller <tschenser at gmx dot de>
0012  * SPDX-FileCopyrightText: 2011      by Veaceslav Munteanu <slavuttici at gmail dot com>
0013  *
0014  * SPDX-License-Identifier: GPL-2.0-or-later
0015  *
0016  * ============================================================ */
0017 
0018 #include "dngconverterdialog.h"
0019 
0020 // Qt includes
0021 
0022 #include <QCloseEvent>
0023 #include <QFile>
0024 #include <QFileInfo>
0025 #include <QGridLayout>
0026 #include <QPixmap>
0027 #include <QTimer>
0028 #include <QTreeWidgetItemIterator>
0029 #include <QApplication>
0030 #include <QMessageBox>
0031 #include <QMenu>
0032 #include <QPushButton>
0033 #include <QCursor>
0034 #include <QBoxLayout>
0035 
0036 // KDE includes
0037 
0038 #include <kconfiggroup.h>
0039 #include <ksharedconfig.h>
0040 #include <klocalizedstring.h>
0041 
0042 // Local includes
0043 
0044 #include "dngconverteractions.h"
0045 #include "dngconverterthread.h"
0046 #include "dngconverterlist.h"
0047 #include "filesaveconflictbox.h"
0048 #include "dngsettings.h"
0049 #include "dprogresswdg.h"
0050 #include "dngwriter.h"
0051 #include "dmetadata.h"
0052 #include "digikam_debug.h"
0053 #include "imagedialog.h"
0054 #include "dexpanderbox.h"
0055 #include "dfileoperations.h"
0056 
0057 using namespace Digikam;
0058 
0059 namespace DigikamGenericDNGConverterPlugin
0060 {
0061 
0062 class DNGConverterDialog::Private
0063 {
0064 public:
0065 
0066     Private()
0067       : busy            (false),
0068         progressBar     (nullptr),
0069         listView        (nullptr),
0070         thread          (nullptr),
0071         dngSettings     (nullptr),
0072         conflictSettings(nullptr),
0073         iface           (nullptr)
0074     {
0075     }
0076 
0077     bool                      busy;
0078 
0079     QStringList               fileList;
0080 
0081     DProgressWdg*             progressBar;
0082 
0083     DNGConverterList*         listView;
0084 
0085     DNGConverterActionThread* thread;
0086 
0087     DNGSettings*              dngSettings;
0088 
0089     FileSaveConflictBox*      conflictSettings;
0090 
0091     DInfoInterface*           iface;
0092 };
0093 
0094 DNGConverterDialog::DNGConverterDialog(QWidget* const parent, DInfoInterface* const iface)
0095     : DPluginDialog(parent, QLatin1String("DNG Converter Dialog")),
0096       d            (new Private)
0097 {
0098     setWindowTitle(i18nc("@title:window", "DNG Converter"));
0099     setMinimumSize(900, 500);
0100     setModal(true);
0101 
0102     d->iface                  = iface;
0103 
0104     m_buttons->addButton(QDialogButtonBox::Close);
0105     m_buttons->addButton(QDialogButtonBox::Ok);
0106     m_buttons->button(QDialogButtonBox::Ok)->setText(i18nc("@action:button", "&Convert"));
0107 
0108     QWidget* const mainWidget = new QWidget(this);
0109     QVBoxLayout* const vbx    = new QVBoxLayout(this);
0110     vbx->addWidget(mainWidget);
0111     vbx->addWidget(m_buttons);
0112     setLayout(vbx);
0113 
0114     //---------------------------------------------
0115 
0116     QGridLayout* const mainLayout = new QGridLayout(mainWidget);
0117     d->listView                   = new DNGConverterList(mainWidget);
0118     d->progressBar                = new DProgressWdg(mainWidget);
0119     d->progressBar->reset();
0120     d->progressBar->hide();
0121 
0122     d->listView->appendControlButtonsWidget(d->progressBar);
0123     QBoxLayout* const blay        = d->listView->setControlButtonsPlacement(DItemsList::ControlButtonsBelow);
0124     blay->setStretchFactor(d->progressBar, 20);
0125 
0126     d->dngSettings                = new DNGSettings(this);
0127     DLineWidget* const line       = new DLineWidget(Qt::Horizontal, this);
0128     d->conflictSettings           = new FileSaveConflictBox(this);
0129 
0130     mainLayout->addWidget(d->listView,         0, 0, 5, 1);
0131     mainLayout->addWidget(d->dngSettings,      0, 1, 1, 1);
0132     mainLayout->addWidget(line,                1, 1, 1, 1);
0133     mainLayout->addWidget(d->conflictSettings, 2, 1, 1, 1);
0134     mainLayout->setColumnStretch(0, 10);
0135     mainLayout->setRowStretch(3, 10);
0136     mainLayout->setContentsMargins(QMargins());
0137 
0138     // ---------------------------------------------------------------
0139 
0140     d->thread = new DNGConverterActionThread(this);
0141 
0142     connect(d->thread, SIGNAL(signalStarting(DigikamGenericDNGConverterPlugin::DNGConverterActionData)),
0143             this, SLOT(slotDNGConverterAction(DigikamGenericDNGConverterPlugin::DNGConverterActionData)));
0144 
0145     connect(d->thread, SIGNAL(signalFinished(DigikamGenericDNGConverterPlugin::DNGConverterActionData)),
0146             this, SLOT(slotDNGConverterAction(DigikamGenericDNGConverterPlugin::DNGConverterActionData)));
0147 
0148     connect(d->thread, SIGNAL(finished()),
0149             this, SLOT(slotThreadFinished()));
0150 
0151     // ---------------------------------------------------------------
0152 
0153     connect(m_buttons->button(QDialogButtonBox::Ok), SIGNAL(clicked()),
0154             this, SLOT(slotStartStop()));
0155 
0156     connect(m_buttons->button(QDialogButtonBox::Close), SIGNAL(clicked()),
0157             this, SLOT(slotClose()));
0158 
0159     connect(d->listView, SIGNAL(signalImageListChanged()),
0160             this, SLOT(slotIdentify()));
0161 
0162     connect(d->progressBar, SIGNAL(signalProgressCanceled()),
0163             this, SLOT(slotStartStop()));
0164 
0165     connect(d->dngSettings, SIGNAL(signalSettingsChanged()),
0166             this, SLOT(slotIdentify()));
0167 
0168     connect(d->dngSettings, SIGNAL(signalSetupExifTool()),
0169             this, SLOT(slotSetupExifTool()));
0170 
0171     connect(d->conflictSettings, SIGNAL(signalConflictButtonChanged(int)),
0172             this, SLOT(slotIdentify()));
0173 
0174     // ---------------------------------------------------------------
0175 
0176     d->listView->setIface(d->iface);
0177     d->listView->loadImagesFromCurrentSelection();
0178 
0179     busy(false);
0180     readSettings();
0181 }
0182 
0183 DNGConverterDialog::~DNGConverterDialog()
0184 {
0185     delete d;
0186 }
0187 
0188 void DNGConverterDialog::closeEvent(QCloseEvent* e)
0189 {
0190     if (!e)
0191     {
0192         return;
0193     }
0194 
0195     // Stop current conversion if necessary
0196 
0197     if (d->busy)
0198     {
0199         slotStartStop();
0200     }
0201 
0202     saveSettings();
0203     d->listView->listView()->clear();
0204     e->accept();
0205 }
0206 
0207 void DNGConverterDialog::slotSetupExifTool()
0208 {
0209     if (d->iface)
0210     {
0211         connect(d->iface, SIGNAL(signalSetupChanged()),
0212                 d->dngSettings, SLOT(slotSetupChanged()));
0213 
0214         d->iface->openSetupPage(DInfoInterface::ExifToolPage);
0215     }
0216 }
0217 
0218 void DNGConverterDialog::slotClose()
0219 {
0220     // Stop current conversion if necessary
0221 
0222     if (d->busy)
0223     {
0224         slotStartStop();
0225     }
0226 
0227     saveSettings();
0228     d->listView->listView()->clear();
0229     d->fileList.clear();
0230     accept();
0231 }
0232 
0233 void DNGConverterDialog::slotDefault()
0234 {
0235     d->dngSettings->setDefaultSettings();
0236     d->conflictSettings->resetToDefault();
0237 }
0238 
0239 void DNGConverterDialog::readSettings()
0240 {
0241     KSharedConfig::Ptr config = KSharedConfig::openConfig();
0242     KConfigGroup group        = config->group(QLatin1String("DNGConverter Settings"));
0243 
0244     d->dngSettings->setBackupOriginalRawFile(group.readEntry("BackupOriginalRawFile", false));
0245     d->dngSettings->setCompressLossLess(group.readEntry("CompressLossLess",           true));
0246     d->dngSettings->setPreviewMode(group.readEntry("PreviewMode",                     (int)(DNGWriter::FULL_SIZE)));
0247     d->conflictSettings->readSettings(group);
0248 }
0249 
0250 void DNGConverterDialog::saveSettings()
0251 {
0252     KSharedConfig::Ptr config = KSharedConfig::openConfig();
0253     KConfigGroup group        = config->group(QLatin1String("DNGConverter Settings"));
0254 
0255     group.writeEntry("BackupOriginalRawFile", d->dngSettings->backupOriginalRawFile());
0256     group.writeEntry("CompressLossLess",      d->dngSettings->compressLossLess());
0257     group.writeEntry("PreviewMode",           (int)d->dngSettings->previewMode());
0258     d->conflictSettings->writeSettings(group);
0259 }
0260 
0261 void DNGConverterDialog::slotStartStop()
0262 {
0263     if (!d->busy)
0264     {
0265         d->fileList.clear();
0266 
0267         QTreeWidgetItemIterator it(d->listView->listView());
0268 
0269         while (*it)
0270         {
0271             DNGConverterListViewItem* const lvItem = dynamic_cast<DNGConverterListViewItem*>(*it);
0272 
0273             if (lvItem)
0274             {
0275                 if (!lvItem->isDisabled() && (lvItem->state() != DNGConverterListViewItem::Success))
0276                 {
0277                     lvItem->setIcon(1, QIcon());
0278                     lvItem->setState(DNGConverterListViewItem::Waiting);
0279                     d->fileList.append(lvItem->url().path());
0280                 }
0281             }
0282 
0283             ++it;
0284         }
0285 
0286         if (d->fileList.empty())
0287         {
0288             QMessageBox::information(this, i18nc("@title:window", "DNG Converter"), i18n("The list does not contain any Raw files to process."));
0289             busy(false);
0290             slotAborted();
0291             return;
0292         }
0293 
0294         d->progressBar->setMaximum(d->fileList.count());
0295         d->progressBar->setValue(0);
0296         d->progressBar->show();
0297         d->progressBar->progressScheduled(i18n("DNG Converter"), true, true);
0298         d->progressBar->progressThumbnailChanged(QIcon::fromTheme(QLatin1String("image-x-adobe-dng")).pixmap(22, 22));
0299 
0300         processAll();
0301     }
0302     else
0303     {
0304         d->fileList.clear();
0305         d->thread->cancel();
0306         busy(false);
0307 
0308         d->listView->cancelProcess();
0309 
0310         QTimer::singleShot(500, this, SLOT(slotAborted()));
0311     }
0312 }
0313 
0314 void DNGConverterDialog::addItems(const QList<QUrl>& itemList)
0315 {
0316     d->listView->slotAddImages(itemList);
0317 }
0318 
0319 void DNGConverterDialog::slotAborted()
0320 {
0321     d->progressBar->setValue(0);
0322     d->progressBar->hide();
0323     d->progressBar->progressCompleted();
0324 }
0325 
0326 void DNGConverterDialog::slotIdentify()
0327 {
0328     QList<QUrl> urlList = d->listView->imageUrls(true);
0329 
0330     for (QList<QUrl>::const_iterator  it = urlList.constBegin() ; it != urlList.constEnd() ; ++it)
0331     {
0332         QFileInfo fi((*it).path());
0333 
0334         if (d->conflictSettings->conflictRule() == FileSaveConflictBox::OVERWRITE)
0335         {
0336             QString dest                         = fi.completeBaseName() + QLatin1String(".dng");
0337             DNGConverterListViewItem* const item = dynamic_cast<DNGConverterListViewItem*>(d->listView->listView()->findItem(*it));
0338 
0339             if (item)
0340             {
0341                 item->setDestFileName(dest);
0342             }
0343         }
0344         else
0345         {
0346             QString dest      = fi.absolutePath() + QLatin1String("/") + fi.completeBaseName() + QLatin1String(".dng");
0347             QFileInfo a(dest);
0348             bool fileNotFound = (a.exists());
0349 
0350             if (!fileNotFound)
0351             {
0352                 dest = fi.completeBaseName() + QLatin1String(".dng");
0353             }
0354 
0355             else
0356             {
0357                 int i = 0;
0358 
0359                 while(fileNotFound)
0360                 {
0361                     a = QFileInfo(dest);
0362 
0363                     if (!a.exists())
0364                     {
0365                         fileNotFound = false;
0366                     }
0367                     else
0368                     {
0369                         i++;
0370                         dest = fi.absolutePath()     +
0371                                QLatin1String("/")    +
0372                                fi.completeBaseName() +
0373                                QLatin1String("_")    +
0374                                QString::number(i)    +
0375                                QLatin1String(".dng");
0376                     }
0377                 }
0378 
0379                 dest = fi.completeBaseName() + QLatin1String("_") + QString::number(i) + QLatin1String(".dng");
0380             }
0381 
0382             DNGConverterListViewItem* const item = dynamic_cast<DNGConverterListViewItem*>(d->listView->listView()->findItem(*it));
0383 
0384             if (item)
0385             {
0386                 item->setDestFileName(dest);
0387             }
0388         }
0389     }
0390 
0391     if (!urlList.empty())
0392     {
0393         d->thread->identifyRawFiles(urlList);
0394 
0395         if (!d->thread->isRunning())
0396         {
0397             d->thread->start();
0398         }
0399     }
0400 }
0401 
0402 void DNGConverterDialog::processAll()
0403 {
0404     d->thread->setBackupOriginalRawFile(d->dngSettings->backupOriginalRawFile());
0405     d->thread->setCompressLossLess(d->dngSettings->compressLossLess());
0406     d->thread->setPreviewMode(d->dngSettings->previewMode());
0407     d->thread->processRawFiles(d->listView->imageUrls(true));
0408 
0409     if (!d->thread->isRunning())
0410     {
0411         d->thread->start();
0412     }
0413 }
0414 
0415 void DNGConverterDialog::slotThreadFinished()
0416 {
0417     busy(false);
0418     slotAborted();
0419 }
0420 
0421 void DNGConverterDialog::busy(bool busy)
0422 {
0423     d->busy = busy;
0424 
0425     if (d->busy)
0426     {
0427         m_buttons->button(QDialogButtonBox::Ok)->setText(i18n("&Abort"));
0428         m_buttons->button(QDialogButtonBox::Ok)->setToolTip(i18n("Abort the conversion of Raw files."));
0429     }
0430     else
0431     {
0432         m_buttons->button(QDialogButtonBox::Ok)->setText(i18n("Con&vert"));
0433         m_buttons->button(QDialogButtonBox::Ok)->setToolTip(i18n("Start converting the Raw images using the current settings."));
0434     }
0435 
0436     d->dngSettings->setEnabled(!d->busy);
0437     d->conflictSettings->setEnabled(!d->busy);
0438     d->listView->listView()->viewport()->setEnabled(!d->busy);
0439     d->busy ? setCursor(Qt::WaitCursor) : unsetCursor();
0440 }
0441 
0442 void DNGConverterDialog::processed(const QUrl& url, const QString& tmpFile)
0443 {
0444     DNGConverterListViewItem* const item = dynamic_cast<DNGConverterListViewItem*>(d->listView->listView()->findItem(url));
0445 
0446     if (!item)
0447     {
0448         return;
0449     }
0450 
0451     QString destFile(item->destPath());
0452 
0453     if (d->conflictSettings->conflictRule() != FileSaveConflictBox::OVERWRITE)
0454     {
0455         if (!QFile::exists(destFile))
0456         {
0457             item->setStatus(i18n("Failed to save image"));
0458         }
0459     }
0460 
0461     if (!destFile.isEmpty())
0462     {
0463         if (DMetadata::hasSidecar(tmpFile))
0464         {
0465             if (!DFileOperations::renameFile(DMetadata::sidecarPath(tmpFile),
0466                                              DMetadata::sidecarPath(destFile)))
0467             {
0468                 item->setStatus(i18n("Failed to move sidecar"));
0469             }
0470         }
0471 
0472         if (!DFileOperations::renameFile(tmpFile, destFile))
0473         {
0474             item->setStatus(i18n("Failed to save image."));
0475             d->listView->processed(url, false);
0476         }
0477         else
0478         {
0479             item->setDestFileName(QFileInfo(destFile).fileName());
0480             d->listView->processed(url, true);
0481             item->setStatus(i18n("Success"));
0482         }
0483     }
0484 
0485     d->progressBar->setValue(d->progressBar->value()+1);
0486 }
0487 
0488 void DNGConverterDialog::processingFailed(const QUrl& url, int result)
0489 {
0490     d->listView->processed(url, false);
0491     d->progressBar->setValue(d->progressBar->value()+1);
0492 
0493     DNGConverterListViewItem* const item = dynamic_cast<DNGConverterListViewItem*>(d->listView->listView()->findItem(url));
0494 
0495     if (!item)
0496     {
0497         return;
0498     }
0499 
0500     QString status;
0501 
0502     switch (result)
0503     {
0504         case DNGWriter::PROCESS_FAILED:
0505         {
0506             status = i18n("Process failed");
0507             break;
0508         }
0509 
0510         case DNGWriter::PROCESS_CANCELED:
0511         {
0512             status = i18n("Process Canceled");
0513             break;
0514         }
0515 
0516         case DNGWriter::FILE_NOT_SUPPORTED:
0517         {
0518             status = i18n("File not supported");
0519             break;
0520         }
0521 
0522         default:
0523         {
0524             status = i18n("Internal error");
0525             break;
0526         }
0527     }
0528 
0529     item->setStatus(status);
0530 }
0531 
0532 void DNGConverterDialog::slotDNGConverterAction(const DigikamGenericDNGConverterPlugin::DNGConverterActionData& ad)
0533 {
0534     QString text;
0535 
0536     if (ad.starting)            // Something have been started...
0537     {
0538         switch (ad.action)
0539         {
0540             case IDENTIFY:
0541             {
0542                 break;
0543             }
0544 
0545             case PROCESS:
0546             {
0547                 busy(true);
0548                 d->listView->processing(ad.fileUrl);
0549                 d->progressBar->progressStatusChanged(i18n("Processing %1", ad.fileUrl.fileName()));
0550                 break;
0551             }
0552 
0553             default:
0554             {
0555                 qCWarning(DIGIKAM_DPLUGIN_GENERIC_LOG) << "DigikamGenericDNGConverterPlugin: Unknown action";
0556                 break;
0557             }
0558         }
0559     }
0560     else
0561     {
0562         if (ad.result != DNGWriter::PROCESS_COMPLETE)        // Something is failed...
0563         {
0564             switch (ad.action)
0565             {
0566                 case IDENTIFY:
0567                 {
0568                     break;
0569                 }
0570 
0571                 case PROCESS:
0572                 {
0573                     processingFailed(ad.fileUrl, ad.result);
0574                     break;
0575                 }
0576 
0577                 default:
0578                 {
0579                     qCWarning(DIGIKAM_DPLUGIN_GENERIC_LOG) << "DigikamGenericDNGConverterPlugin: Unknown action";
0580                     break;
0581                 }
0582             }
0583         }
0584         else                    // Something is done...
0585         {
0586             switch (ad.action)
0587             {
0588                 case IDENTIFY:
0589                 {
0590                     DNGConverterListViewItem* const item = dynamic_cast<DNGConverterListViewItem*>(d->listView->listView()->findItem(ad.fileUrl));
0591 
0592                     if (item)
0593                     {
0594                         item->setIdentity(ad.message);
0595                     }
0596 
0597                     break;
0598                 }
0599 
0600                 case PROCESS:
0601                 {
0602                     processed(ad.fileUrl, ad.destPath);
0603                     break;
0604                 }
0605 
0606                 default:
0607                 {
0608                     qCWarning(DIGIKAM_DPLUGIN_GENERIC_LOG) << "DigikamGenericDNGConverterPlugin: Unknown action";
0609                     break;
0610                 }
0611             }
0612         }
0613     }
0614 }
0615 
0616 } // namespace DigikamGenericDNGConverterPlugin
0617 
0618 #include "moc_dngconverterdialog.cpp"