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"