File indexing completed on 2025-03-09 03:52:05

0001 /* ============================================================
0002  *
0003  * This file is a part of digiKam project
0004  * https://www.digikam.org
0005  *
0006  * Date        : 2008-09-14
0007  * Description : a presentation tool.
0008  *
0009  * SPDX-FileCopyrightText: 2008-2009 by Valerio Fuoglio <valerio dot fuoglio at gmail dot com>
0010  * SPDX-FileCopyrightText: 2012-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 "presentation_audiopage.h"
0017 
0018 // Qt includes
0019 
0020 #include <QPointer>
0021 #include <QIcon>
0022 #include <QMessageBox>
0023 #include <QDialogButtonBox>
0024 
0025 // Local includes
0026 
0027 #include "presentation_mainpage.h"
0028 #include "presentationcontainer.h"
0029 #include "digikam_debug.h"
0030 #include "digikam_globals.h"
0031 #include "dfiledialog.h"
0032 
0033 using namespace Digikam;
0034 
0035 namespace DigikamGenericPresentationPlugin
0036 {
0037 
0038 SoundtrackPreview::SoundtrackPreview(QWidget* const parent,
0039                                      const QList<QUrl>& urls,
0040                                      PresentationContainer* const sharedData)
0041     : QDialog(parent)
0042 {
0043     setModal(true);
0044     setWindowTitle(i18nc("@title:window", "Soundtrack Preview"));
0045 
0046     m_playbackWidget                  = new PresentationAudioWidget(this, urls, sharedData);
0047     QDialogButtonBox* const buttonBox = new QDialogButtonBox(QDialogButtonBox::Close, this);
0048 
0049     connect(buttonBox, &QDialogButtonBox::rejected,
0050             this, &QDialog::reject);
0051 
0052     QVBoxLayout* const layout = new QVBoxLayout(this);
0053     layout->addWidget(m_playbackWidget);
0054     layout->addWidget(buttonBox);
0055     setLayout(layout);
0056 }
0057 
0058 SoundtrackPreview::~SoundtrackPreview()
0059 {
0060 }
0061 
0062 // ------------------------------------------------------------------------------------
0063 
0064 class Q_DECL_HIDDEN PresentationAudioPage::Private
0065 {
0066 public:
0067 
0068     Private() = default;
0069 
0070     QList<QUrl>                             urlList;
0071     PresentationContainer*                  sharedData = nullptr;
0072     QTime                                   totalTime;
0073     QTime                                   imageTime;
0074     QMap<QUrl, QTime>*                      tracksTime = nullptr;
0075     QMap<QUrl, PresentationAudioListItem*>* soundItems = nullptr;
0076     QMutex*                                 timeMutex  = nullptr;
0077 };
0078 
0079 PresentationAudioPage::PresentationAudioPage(QWidget* const parent,
0080                                              PresentationContainer* const sharedData)
0081     : QWidget(parent),
0082       d      (new Private)
0083 {
0084     setupUi(this);
0085 
0086     d->sharedData = sharedData;
0087     d->totalTime  = QTime(0, 0, 0);
0088     d->imageTime  = QTime(0, 0, 0);
0089     d->tracksTime = new QMap<QUrl, QTime>();
0090     d->soundItems = new QMap<QUrl, PresentationAudioListItem*>();
0091     d->timeMutex  = new QMutex();
0092 
0093     m_soundtrackTimeLabel->setText(d->totalTime.toString());
0094     m_previewButton->setEnabled(false);
0095 
0096     m_rememberSoundtrack->setToolTip(i18n("If set, the soundtrack for the current album "
0097                                           "will be saved and restored automatically on the next startup."));
0098 
0099     // --------------------------------------------------------
0100 
0101     m_SoundFilesButtonUp->setIcon(QIcon::fromTheme(QLatin1String("go-up")));
0102     m_SoundFilesButtonDown->setIcon(QIcon::fromTheme(QLatin1String("go-down")));
0103     m_SoundFilesButtonAdd->setIcon(QIcon::fromTheme(QLatin1String("list-add")));
0104     m_SoundFilesButtonDelete->setIcon(QIcon::fromTheme(QLatin1String("list-remove")));
0105     m_SoundFilesButtonLoad->setIcon(QIcon::fromTheme(QLatin1String("document-open")));
0106     m_SoundFilesButtonSave->setIcon(QIcon::fromTheme(QLatin1String("document-save")));
0107     m_SoundFilesButtonReset->setIcon(QIcon::fromTheme(QLatin1String("edit-clear")));
0108 
0109     m_SoundFilesButtonUp->setText(QString());
0110     m_SoundFilesButtonDown->setText(QString());
0111     m_SoundFilesButtonAdd->setText(QString());
0112     m_SoundFilesButtonDelete->setText(QString());
0113     m_SoundFilesButtonLoad->setText(QString());
0114     m_SoundFilesButtonSave->setText(QString());
0115     m_SoundFilesButtonReset->setText(QString());
0116 
0117     m_SoundFilesButtonUp->setToolTip(i18n("Move the selected track up in the playlist."));
0118     m_SoundFilesButtonDown->setToolTip(i18n("Move the selected track down in the playlist."));
0119     m_SoundFilesButtonAdd->setToolTip(i18n("Add new tracks to the playlist."));
0120     m_SoundFilesButtonDelete->setToolTip(i18n("Delete the selected track from the playlist."));
0121     m_SoundFilesButtonLoad->setToolTip(i18n("Load playlist from a file."));
0122     m_SoundFilesButtonSave->setToolTip(i18n("Save playlist to a file."));
0123     m_SoundFilesButtonReset->setToolTip(i18n("Clear the playlist."));
0124 
0125     // --------------------------------------------------------
0126 
0127     connect(m_SoundFilesListBox, SIGNAL(currentRowChanged(int)),
0128             this, SLOT(slotSoundFilesSelected(int)));
0129 
0130     connect(m_SoundFilesListBox, SIGNAL(signalAddedDropItems(QList<QUrl>)),
0131             this, SLOT(slotAddDropItems(QList<QUrl>)));
0132 
0133     connect(m_SoundFilesButtonAdd, SIGNAL(clicked()),
0134             this, SLOT(slotSoundFilesButtonAdd()));
0135 
0136     connect(m_SoundFilesButtonDelete, SIGNAL(clicked()),
0137             this, SLOT(slotSoundFilesButtonDelete()));
0138 
0139     connect(m_SoundFilesButtonUp, SIGNAL(clicked()),
0140             this, SLOT(slotSoundFilesButtonUp()));
0141 
0142     connect(m_SoundFilesButtonDown, SIGNAL(clicked()),
0143             this, SLOT(slotSoundFilesButtonDown()));
0144 
0145     connect(m_SoundFilesButtonLoad, SIGNAL(clicked()),
0146             this, SLOT(slotSoundFilesButtonLoad()));
0147 
0148     connect(m_SoundFilesButtonSave, SIGNAL(clicked()),
0149             this, SLOT(slotSoundFilesButtonSave()));
0150 
0151     connect(m_SoundFilesButtonReset, SIGNAL(clicked()),
0152             this, SLOT(slotSoundFilesButtonReset()));
0153 
0154     connect(m_previewButton, SIGNAL(clicked()),
0155             this, SLOT(slotPreviewButtonClicked()));
0156 
0157     connect(d->sharedData->mainPage, SIGNAL(signalTotalTimeChanged(QTime)),
0158             this, SLOT(slotImageTotalTimeChanged(QTime)));
0159 }
0160 
0161 PresentationAudioPage::~PresentationAudioPage()
0162 {
0163     delete d->tracksTime;
0164     delete d->soundItems;
0165     delete d->timeMutex;
0166     delete d;
0167 }
0168 
0169 void PresentationAudioPage::readSettings()
0170 {
0171     m_rememberSoundtrack->setChecked(d->sharedData->soundtrackRememberPlaylist);
0172     m_loopCheckBox->setChecked(d->sharedData->soundtrackLoop);
0173     m_playCheckBox->setChecked(d->sharedData->soundtrackPlay);
0174 
0175     connect(d->sharedData->mainPage, SIGNAL(signalTotalTimeChanged(QTime)),
0176             this, SLOT(slotImageTotalTimeChanged(QTime)));
0177 
0178     // if tracks are already set in d->sharedData, add them now
0179 
0180     if (!d->sharedData->soundtrackUrls.isEmpty())
0181     {
0182         addItems(d->sharedData->soundtrackUrls);
0183     }
0184 
0185     updateFileList();
0186     updateTracksNumber();
0187 }
0188 
0189 void PresentationAudioPage::saveSettings()
0190 {
0191     d->sharedData->soundtrackRememberPlaylist = m_rememberSoundtrack->isChecked();
0192     d->sharedData->soundtrackLoop             = m_loopCheckBox->isChecked();
0193     d->sharedData->soundtrackPlay             = m_playCheckBox->isChecked();
0194     d->sharedData->soundtrackUrls             = d->urlList;
0195 }
0196 
0197 void PresentationAudioPage::addItems(const QList<QUrl>& fileList)
0198 {
0199     if (fileList.isEmpty())
0200     {
0201         return;
0202     }
0203 
0204     QList<QUrl> Files = fileList;
0205 
0206     for (QList<QUrl>::ConstIterator it = Files.constBegin() ; it != Files.constEnd() ; ++it)
0207     {
0208         QUrl currentFile                      = *it;
0209         d->sharedData->soundtrackPath         = currentFile;
0210         PresentationAudioListItem* const item = new PresentationAudioListItem(m_SoundFilesListBox, currentFile);
0211         item->setName(currentFile.fileName());
0212         m_SoundFilesListBox->insertItem(m_SoundFilesListBox->count() - 1, item);
0213 
0214         d->soundItems->insert(currentFile, item);
0215 
0216         connect(d->soundItems->value(currentFile), SIGNAL(signalTotalTimeReady(QUrl,QTime)),
0217                 this, SLOT(slotAddNewTime(QUrl,QTime)));
0218 
0219         d->urlList.append(currentFile);
0220     }
0221 
0222     m_SoundFilesListBox->setCurrentItem(m_SoundFilesListBox->item(m_SoundFilesListBox->count() - 1)) ;
0223 
0224     slotSoundFilesSelected(m_SoundFilesListBox->currentRow());
0225     m_SoundFilesListBox->scrollToItem(m_SoundFilesListBox->currentItem());
0226     m_previewButton->setEnabled(true);
0227 }
0228 
0229 void PresentationAudioPage::updateTracksNumber()
0230 {
0231     QTime displayTime(0, 0, 0);
0232     int number = m_SoundFilesListBox->count();
0233 
0234     if (number > 0)
0235     {
0236         displayTime = displayTime.addMSecs(1000 * (number - 1));
0237 
0238         for (QMap<QUrl, QTime>::iterator it = d->tracksTime->begin() ; it != d->tracksTime->end() ; ++it)
0239         {
0240             int hours = it.value().hour()   + displayTime.hour();
0241             int mins  = it.value().minute() + displayTime.minute();
0242             int secs  = it.value().second() + displayTime.second();
0243 
0244             /*
0245              * QTime doesn't get a overflow value in input. They need
0246              * to be cut down to size.
0247              */
0248 
0249             mins        = mins + (int)(secs / 60);
0250             secs        = secs % 60;
0251             hours       = hours + (int)(mins / 60);
0252             displayTime = QTime(hours, mins, secs);
0253         }
0254     }
0255 
0256     m_timeLabel->setText(i18ncp("number of tracks and running time", "1 track [%2]", "%1 tracks [%2]", number, displayTime.toString()));
0257 
0258     m_soundtrackTimeLabel->setText(displayTime.toString());
0259 
0260     d->totalTime = displayTime;
0261 
0262     compareTimes();
0263 }
0264 
0265 void PresentationAudioPage::updateFileList()
0266 {
0267     d->urlList = m_SoundFilesListBox->fileUrls();
0268 
0269     m_SoundFilesButtonUp->setEnabled(!d->urlList.isEmpty());
0270     m_SoundFilesButtonDown->setEnabled(!d->urlList.isEmpty());
0271     m_SoundFilesButtonDelete->setEnabled(!d->urlList.isEmpty());
0272     m_SoundFilesButtonSave->setEnabled(!d->urlList.isEmpty());
0273     m_SoundFilesButtonReset->setEnabled(!d->urlList.isEmpty());
0274 
0275     d->sharedData->soundtrackPlayListNeedsUpdate = true;
0276 }
0277 
0278 void PresentationAudioPage::compareTimes()
0279 {
0280     QFont statusBarFont = m_statusBarLabel->font();
0281 
0282     if (d->imageTime > d->totalTime)
0283     {
0284         m_statusBarLabel->setText(i18n("Slide time is greater than soundtrack time. Suggestion: add more sound files."));
0285 
0286 
0287         QPalette paletteStatusBar = m_statusBarLabel->palette();
0288         paletteStatusBar.setColor(QPalette::WindowText, Qt::red);
0289         m_statusBarLabel->setPalette(paletteStatusBar);
0290 
0291         QPalette paletteTimeLabel = m_soundtrackTimeLabel->palette();
0292         paletteTimeLabel.setColor(QPalette::WindowText, Qt::red);
0293         m_soundtrackTimeLabel->setPalette(paletteTimeLabel);
0294 
0295         statusBarFont.setItalic(true);
0296     }
0297     else
0298     {
0299         m_statusBarLabel->setText(QLatin1String(""));
0300 
0301         QPalette paletteStatusBar = m_statusBarLabel->palette();
0302         paletteStatusBar.setColor(QPalette::WindowText, Qt::red);
0303         m_statusBarLabel->setPalette(paletteStatusBar);
0304 
0305         QPalette paletteTimeLabel = m_soundtrackTimeLabel->palette();
0306 
0307         if (d->imageTime < d->totalTime)
0308         {
0309             paletteTimeLabel.setColor(QPalette::WindowText, Qt::black);
0310         }
0311         else
0312         {
0313             paletteTimeLabel.setColor(QPalette::WindowText, Qt::green);
0314         }
0315 
0316         m_soundtrackTimeLabel->setPalette(paletteTimeLabel);
0317 
0318         statusBarFont.setItalic(false);
0319     }
0320 
0321     m_statusBarLabel->setFont(statusBarFont);
0322 }
0323 
0324 void PresentationAudioPage::slotAddNewTime(const QUrl& url, const QTime& trackTime)
0325 {
0326     d->timeMutex->lock();
0327     d->tracksTime->insert(url, trackTime);
0328     updateTracksNumber();
0329     d->timeMutex->unlock();
0330 }
0331 
0332 void PresentationAudioPage::slotSoundFilesSelected(int row)
0333 {
0334     QListWidgetItem* const item = m_SoundFilesListBox->item(row);
0335 
0336     if (!item || (m_SoundFilesListBox->count() == 0))
0337     {
0338         return;
0339     }
0340 }
0341 
0342 void PresentationAudioPage::slotAddDropItems(const QList<QUrl>& filesUrl)
0343 {
0344     if (!filesUrl.isEmpty())
0345     {
0346         addItems(filesUrl);
0347         updateFileList();
0348     }
0349 }
0350 
0351 void PresentationAudioPage::slotSoundFilesButtonAdd()
0352 {
0353     QPointer<DFileDialog> dlg = new DFileDialog(this,
0354                                                 i18nc("@title:window", "Select Sound Files"),
0355                                                 d->sharedData->soundtrackPath.adjusted(QUrl::RemoveFilename).toLocalFile());
0356 
0357     QStringList atm;
0358     atm << QLatin1String("audio/mp3");
0359     atm << QLatin1String("audio/wav");
0360     atm << QLatin1String("audio/ogg");
0361     atm << QLatin1String("audio/flac");
0362     dlg->setMimeTypeFilters(atm);
0363     dlg->setAcceptMode(QFileDialog::AcceptOpen);
0364     dlg->setFileMode(QFileDialog::ExistingFiles);
0365     dlg->exec();
0366 
0367     if (dlg->hasAcceptedUrls())
0368     {
0369         addItems(dlg->selectedUrls());
0370         updateFileList();
0371     }
0372 
0373     delete dlg;
0374 }
0375 
0376 void PresentationAudioPage::slotSoundFilesButtonDelete()
0377 {
0378     int index = m_SoundFilesListBox->currentRow();
0379 
0380     if (index < 0)
0381     {
0382         return;
0383     }
0384 
0385     PresentationAudioListItem* const pitem = static_cast<PresentationAudioListItem*>(m_SoundFilesListBox->takeItem(index));
0386     d->urlList.removeAll(pitem->url());
0387     d->soundItems->remove(pitem->url());
0388     d->timeMutex->lock();
0389     d->tracksTime->remove(pitem->url());
0390     updateTracksNumber();
0391     d->timeMutex->unlock();
0392     delete pitem;
0393     slotSoundFilesSelected(m_SoundFilesListBox->currentRow());
0394 
0395     if (m_SoundFilesListBox->count() == 0)
0396     {
0397         m_previewButton->setEnabled(false);
0398     }
0399 
0400     updateFileList();
0401 }
0402 
0403 void PresentationAudioPage::slotSoundFilesButtonUp()
0404 {
0405     int cpt = 0;
0406 
0407     for (int i = 0 ; i < m_SoundFilesListBox->count() ; ++i)
0408     {
0409         if (m_SoundFilesListBox->currentRow() == i)
0410         {
0411             ++cpt;
0412         }
0413     }
0414 
0415     if (cpt == 0)
0416     {
0417         return;
0418     }
0419 
0420     if (cpt > 1)
0421     {
0422         QMessageBox::critical(this, QString(), i18n("You can only move image files up one at a time."));
0423         return;
0424     }
0425 
0426     unsigned int index = m_SoundFilesListBox->currentRow();
0427 
0428     if (index == 0)
0429     {
0430         return;
0431     }
0432 
0433     PresentationAudioListItem* const pitem = static_cast<PresentationAudioListItem*>(m_SoundFilesListBox->takeItem(index));
0434 
0435     m_SoundFilesListBox->insertItem(index - 1, pitem);
0436     m_SoundFilesListBox->setCurrentItem(pitem);
0437 
0438     updateFileList();
0439 }
0440 
0441 void PresentationAudioPage::slotSoundFilesButtonDown()
0442 {
0443     int cpt = 0;
0444 
0445     for (int i = 0 ; i < m_SoundFilesListBox->count() ; ++i)
0446     {
0447         if (m_SoundFilesListBox->currentRow() == i)
0448         {
0449             ++cpt;
0450         }
0451     }
0452 
0453     if (cpt == 0)
0454     {
0455         return;
0456     }
0457 
0458     if (cpt > 1)
0459     {
0460         QMessageBox::critical(this, QString(), i18n("You can only move files down one at a time."));
0461         return;
0462     }
0463 
0464     int index = m_SoundFilesListBox->currentRow();
0465 
0466     if (index == m_SoundFilesListBox->count())
0467     {
0468         return;
0469     }
0470 
0471     PresentationAudioListItem* const pitem = static_cast<PresentationAudioListItem*>(m_SoundFilesListBox->takeItem(index));
0472 
0473     m_SoundFilesListBox->insertItem(index + 1, pitem);
0474     m_SoundFilesListBox->setCurrentItem(pitem);
0475 
0476     updateFileList();
0477 }
0478 
0479 void PresentationAudioPage::slotSoundFilesButtonLoad()
0480 {
0481     QPointer<DFileDialog> dlg = new DFileDialog(this, i18nc("@title:window", "Load Playlist"),
0482                                                 QString(), i18n("Playlist (*.m3u)"));
0483     dlg->setAcceptMode(QFileDialog::AcceptOpen);
0484     dlg->setFileMode(QFileDialog::ExistingFile);
0485     dlg->exec();
0486 
0487     if (!dlg->hasAcceptedUrls())
0488     {
0489         delete dlg;
0490 
0491         return;
0492     }
0493 
0494     QString filename = dlg->selectedFiles().first();
0495 
0496     if (!filename.isEmpty())
0497     {
0498         QFile file(filename);
0499 
0500         if (file.open(QIODevice::ReadOnly | QIODevice::Text))
0501         {
0502             QTextStream in(&file);
0503             QList<QUrl> playlistFiles;
0504 
0505             while (!in.atEnd())
0506             {
0507                 QString line = in.readLine();
0508 
0509                 // we ignore the extended information of the m3u playlist file
0510 
0511                 if (line.startsWith(QLatin1Char('#')) || line.isEmpty())
0512                 {
0513                     continue;
0514                 }
0515 
0516                 QUrl fUrl = QUrl::fromLocalFile(line);
0517 
0518                 if (fUrl.isValid() && fUrl.isLocalFile())
0519                 {
0520                     playlistFiles << fUrl;
0521                 }
0522             }
0523 
0524             file.close();
0525 
0526             if (!playlistFiles.isEmpty())
0527             {
0528                 m_SoundFilesListBox->clear();
0529                 addItems(playlistFiles);
0530                 updateFileList();
0531             }
0532         }
0533     }
0534 
0535     delete dlg;
0536 }
0537 
0538 void PresentationAudioPage::slotSoundFilesButtonSave()
0539 {
0540     QPointer<DFileDialog> dlg = new DFileDialog(this, i18nc("@title:window", "Save Playlist"),
0541                                                 QString(), i18n("Playlist (*.m3u)"));
0542     dlg->setAcceptMode(QFileDialog::AcceptSave);
0543     dlg->setFileMode(QFileDialog::AnyFile);
0544     dlg->exec();
0545 
0546     if (!dlg->hasAcceptedUrls())
0547     {
0548         delete dlg;
0549 
0550         return;
0551     }
0552 
0553     QString filename = dlg->selectedFiles().first();
0554 
0555     if (!filename.isEmpty())
0556     {
0557         QFile file(filename);
0558 
0559         if (file.open(QIODevice::WriteOnly | QIODevice::Text))
0560         {
0561             QTextStream out(&file);
0562             QList<QUrl> playlistFiles = m_SoundFilesListBox->fileUrls();
0563 
0564             for (int i = 0 ; i < playlistFiles.count() ; ++i)
0565             {
0566                 QUrl fUrl(playlistFiles.at(i));
0567 
0568                 if (fUrl.isValid() && fUrl.isLocalFile())
0569                 {
0570                     out << fUrl.toLocalFile() << QT_ENDL;
0571                 }
0572             }
0573 
0574             file.close();
0575         }
0576     }
0577 
0578     delete dlg;
0579 }
0580 
0581 void PresentationAudioPage::slotSoundFilesButtonReset()
0582 {
0583     m_SoundFilesListBox->clear();
0584     updateFileList();
0585 }
0586 
0587 void PresentationAudioPage::slotPreviewButtonClicked()
0588 {
0589     QList<QUrl> urlList;
0590 
0591     for (int i = 0 ; i < m_SoundFilesListBox->count() ; ++i)
0592     {
0593         PresentationAudioListItem* const pitem = dynamic_cast<PresentationAudioListItem*>(m_SoundFilesListBox->item(i));
0594 
0595         if (pitem)
0596         {
0597             QString path = pitem->url().toLocalFile();
0598 
0599             if (!QFile::exists(path))
0600             {
0601                 QMessageBox::critical(this, QString(), i18n("Cannot access file \"%1\". Please check the path is correct.", path));
0602                 return;
0603             }
0604 
0605             urlList << pitem->url();
0606         }
0607     }
0608 
0609     if (urlList.isEmpty())
0610     {
0611         QMessageBox::critical(this, QString(), i18n("Cannot create a preview of an empty file list."));
0612         return;
0613     }
0614 
0615     // Update PresentationContainer from interface
0616 
0617     saveSettings();
0618 
0619     qCDebug(DIGIKAM_DPLUGIN_GENERIC_LOG) << "Tracks : " << urlList;
0620 
0621     QPointer<SoundtrackPreview> preview = new SoundtrackPreview(this, urlList, d->sharedData);
0622     preview->exec();
0623 
0624     delete preview;
0625 }
0626 
0627 void PresentationAudioPage::slotImageTotalTimeChanged(const QTime& imageTotalTime)
0628 {
0629     d->imageTime = imageTotalTime;
0630     m_slideTimeLabel->setText(imageTotalTime.toString());
0631     compareTimes();
0632 }
0633 
0634 } // namespace DigikamGenericPresentationPlugin
0635 
0636 #include "moc_presentation_audiopage.cpp"