File indexing completed on 2024-05-12 05:14:57

0001 /*
0002  *  sounddlg.cpp  -  sound file selection and configuration dialog and widget
0003  *  Program:  kalarm
0004  *  SPDX-FileCopyrightText: 2005-2023 David Jarvie <djarvie@kde.org>
0005  *
0006  *  SPDX-License-Identifier: GPL-2.0-or-later
0007  */
0008 
0009 #include "sounddlg.h"
0010 
0011 #include "mainwindow.h"
0012 #include "soundpicker.h"
0013 #include "lib/checkbox.h"
0014 #include "lib/config.h"
0015 #include "lib/file.h"
0016 #include "lib/groupbox.h"
0017 #include "lib/lineedit.h"
0018 #include "lib/pushbutton.h"
0019 #include "lib/slider.h"
0020 #include "lib/spinbox.h"
0021 
0022 #include <KLocalizedString>
0023 #include <phonon/MediaObject>
0024 #include <phonon/AudioOutput>
0025 #include <phonon/VolumeFaderEffect>
0026 
0027 #include <QIcon>
0028 #include <QLabel>
0029 #include <QDir>
0030 #include <QGroupBox>
0031 #include <QHBoxLayout>
0032 #include <QVBoxLayout>
0033 #include <QGridLayout>
0034 #include <QShowEvent>
0035 #include <QResizeEvent>
0036 #include <QDialogButtonBox>
0037 #include <QStandardPaths>
0038 
0039 
0040 // Collect these widget labels together to ensure consistent wording and
0041 // translations across different modules.
0042 QString SoundWidget::i18n_chk_Repeat()      { return i18nc("@option:check", "Repeat"); }
0043 QString SoundWidget::i18n_chk_SetVolume()   { return i18nc("@option:check", "Set volume"); }
0044 
0045 static const char SOUND_DIALOG_NAME[] = "SoundDialog";
0046 
0047 
0048 SoundDlg::SoundDlg(const QString& file, float volume, float fadeVolume, int fadeSeconds, int repeatPause,
0049                    const QString& caption, QWidget* parent)
0050     : QDialog(parent)
0051 {
0052     auto layout = new QVBoxLayout(this);
0053     const QString repWhatsThis = i18nc("@info:whatsthis", "If checked, the sound file will be played repeatedly for as long as the message is displayed.");
0054     mSoundWidget = new SoundWidget(true, repWhatsThis, this);
0055     layout->addWidget(mSoundWidget);
0056 
0057     mButtonBox = new QDialogButtonBox;
0058     mButtonBox->addButton(QDialogButtonBox::Ok);
0059     mButtonBox->addButton(QDialogButtonBox::Cancel);
0060     connect(mButtonBox, &QDialogButtonBox::clicked,
0061             this, &SoundDlg::slotButtonClicked);
0062     layout->addWidget(mButtonBox);
0063 
0064     setWindowTitle(caption);
0065 
0066     // Restore the dialog size from last time
0067     QSize s;
0068     if (Config::readWindowSize(SOUND_DIALOG_NAME, s))
0069         resize(s);
0070 
0071     // Initialise the control values
0072     mSoundWidget->set(file, volume, fadeVolume, fadeSeconds, repeatPause);
0073 }
0074 
0075 /******************************************************************************
0076 * Set the read-only status of the dialog.
0077 */
0078 void SoundDlg::setReadOnly(bool readOnly)
0079 {
0080     if (readOnly != mReadOnly)
0081     {
0082         mSoundWidget->setReadOnly(readOnly);
0083         mReadOnly = readOnly;
0084         if (readOnly)
0085         {
0086             mButtonBox->clear();
0087             mButtonBox->addButton(QDialogButtonBox::Cancel);
0088         }
0089         else
0090         {
0091             mButtonBox->clear();
0092             mButtonBox->addButton(QDialogButtonBox::Ok);
0093             mButtonBox->addButton(QDialogButtonBox::Cancel);
0094         }
0095     }
0096 }
0097 
0098 QUrl SoundDlg::getFile() const
0099 {
0100     QUrl url;
0101     mSoundWidget->file(url);
0102     return url;
0103 }
0104 
0105 /******************************************************************************
0106 * Called when the dialog's size has changed.
0107 * Records the new size in the config file.
0108 */
0109 void SoundDlg::resizeEvent(QResizeEvent* re)
0110 {
0111     if (isVisible())
0112         Config::writeWindowSize(SOUND_DIALOG_NAME, re->size());
0113     QDialog::resizeEvent(re);
0114 }
0115 
0116 /******************************************************************************
0117 * Called when the OK or Cancel button is clicked.
0118 */
0119 void SoundDlg::slotButtonClicked(QAbstractButton* button)
0120 {
0121     if (button == mButtonBox->button(QDialogButtonBox::Ok))
0122     {
0123         if (mReadOnly)
0124             reject();
0125         else if (mSoundWidget->validate(true))
0126             accept();
0127     }
0128     else
0129         reject();
0130 
0131 }
0132 
0133 
0134 /*=============================================================================
0135 = Class SoundWidget
0136 = Select a sound file and configure how to play it.
0137 =============================================================================*/
0138 QString SoundWidget::mDefaultDir;
0139 
0140 SoundWidget::SoundWidget(bool showPlay, const QString& repeatWhatsThis, QWidget* parent)
0141     : QWidget(parent)
0142     , mRepeatWhatsThis(repeatWhatsThis)
0143 {
0144     auto layout = new QVBoxLayout(this);
0145 
0146     QLabel* label = nullptr;
0147     if (!showPlay)
0148     {
0149         label = new QLabel(i18nc("@label", "Sound file:"), this);
0150         layout->addWidget(label);
0151     }
0152 
0153     QWidget* box = new QWidget(this);
0154     layout->addWidget(box);
0155     auto boxHLayout = new QHBoxLayout(box);
0156     boxHLayout->setContentsMargins(0, 0, 0, 0);
0157     boxHLayout->setSpacing(0);
0158 
0159     if (showPlay)
0160     {
0161         // File play button
0162         mFilePlay = new QPushButton(box);
0163         boxHLayout->addWidget(mFilePlay);
0164         mFilePlay->setIcon(QIcon::fromTheme(QStringLiteral("media-playback-start")));
0165         connect(mFilePlay, &QPushButton::clicked, this, &SoundWidget::playSound);
0166         mFilePlay->setToolTip(i18nc("@info:tooltip", "Test the sound"));
0167         mFilePlay->setWhatsThis(i18nc("@info:whatsthis", "Play the selected sound file."));
0168     }
0169 
0170     // File name edit box
0171     mFileEdit = new LineEdit(LineEdit::Type::Url, box);
0172     boxHLayout->addWidget(mFileEdit);
0173     mFileEdit->setAcceptDrops(true);
0174     mFileEdit->setWhatsThis(i18nc("@info:whatsthis", "Enter the name or URL of a sound file to play."));
0175     if (label)
0176         label->setBuddy(mFileEdit);
0177     connect(mFileEdit, &LineEdit::textChanged, this, &SoundWidget::changed);
0178 
0179     // File browse button
0180     mFileBrowseButton = new PushButton(box);
0181     boxHLayout->addWidget(mFileBrowseButton);
0182     mFileBrowseButton->setIcon(QIcon(QIcon::fromTheme(QStringLiteral("document-open"))));
0183     connect(mFileBrowseButton, &PushButton::clicked, this, &SoundWidget::slotPickFile);
0184     mFileBrowseButton->setToolTip(i18nc("@info:tooltip", "Choose a file"));
0185     mFileBrowseButton->setWhatsThis(i18nc("@info:whatsthis", "Select a sound file to play."));
0186 
0187     if (mFilePlay)
0188     {
0189         const int size = qMax(mFilePlay->sizeHint().height(), mFileBrowseButton->sizeHint().height());
0190         mFilePlay->setFixedSize(size, size);
0191         mFileBrowseButton->setFixedSize(size, size);
0192     }
0193 
0194     // Sound repetition checkbox
0195     mRepeatGroupBox = new GroupBox(i18n_chk_Repeat(), this);
0196     mRepeatGroupBox->setCheckable(true);
0197     mRepeatGroupBox->setWhatsThis(mRepeatWhatsThis);
0198     connect(mRepeatGroupBox, &GroupBox::toggled, this, &SoundWidget::changed);
0199     layout->addWidget(mRepeatGroupBox);
0200     auto glayout = new QVBoxLayout(mRepeatGroupBox);
0201 
0202     // Pause between repetitions
0203     box = new QWidget(mRepeatGroupBox);
0204     glayout->addWidget(box, 0, Qt::AlignLeft);
0205     boxHLayout = new QHBoxLayout(box);
0206     boxHLayout->setContentsMargins(0, 0, 0, 0);
0207     label = new QLabel(i18nc("@label:spinbox Length of time to pause between repetitions", "Pause between repetitions:"), box);
0208     boxHLayout->addWidget(label);
0209     mRepeatPause = new SpinBox(0, 999, box);
0210     boxHLayout->addWidget(mRepeatPause);
0211     mRepeatPause->setSingleShiftStep(10);
0212     label->setBuddy(mRepeatPause);
0213     connect(mRepeatPause, &SpinBox::valueChanged, this, &SoundWidget::changed);
0214     label = new QLabel(i18nc("@label", "seconds"), box);
0215     boxHLayout->addWidget(label);
0216     box->setWhatsThis(i18nc("@info:whatsthis", "Enter how many seconds to pause between repetitions."));
0217 
0218     // Volume
0219     QGroupBox* group = new QGroupBox(i18nc("@title:group Sound volume", "Volume"), this);
0220     layout->addWidget(group);
0221     auto grid = new QGridLayout(group);
0222     grid->setColumnStretch(2, 1);
0223     const int indentWidth = CheckBox::textIndent(this) - grid->horizontalSpacing();
0224     grid->setColumnMinimumWidth(0, indentWidth);
0225     grid->setColumnMinimumWidth(1, indentWidth);
0226 
0227     // 'Set volume' checkbox
0228     box = new QWidget(group);
0229     grid->addWidget(box, 1, 0, 1, 3);
0230     boxHLayout = new QHBoxLayout(box);
0231     boxHLayout->setContentsMargins(0, 0, 0, 0);
0232     mVolumeCheckbox = new CheckBox(i18n_chk_SetVolume(), box);
0233     boxHLayout->addWidget(mVolumeCheckbox);
0234     connect(mVolumeCheckbox, &CheckBox::toggled, this, &SoundWidget::slotVolumeToggled);
0235     mVolumeCheckbox->setWhatsThis(i18nc("@info:whatsthis", "Select to choose the volume for playing the sound file."));
0236 
0237     // Volume slider
0238     mVolumeSlider = new Slider(0, 100, 10, Qt::Horizontal, box);
0239     boxHLayout->addWidget(mVolumeSlider);
0240     mVolumeSlider->setTickPosition(QSlider::TicksBelow);
0241     mVolumeSlider->setTickInterval(10);
0242     mVolumeSlider->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed));
0243     mVolumeSlider->setWhatsThis(i18nc("@info:whatsthis", "Choose the volume for playing the sound file."));
0244     mVolumeCheckbox->setFocusWidget(mVolumeSlider);
0245     connect(mVolumeSlider, &Slider::valueChanged, this, &SoundWidget::changed);
0246     label = new QLabel;
0247     mVolumeSlider->setValueLabel(label, QStringLiteral("%1%"), true);
0248     boxHLayout->addWidget(label);
0249 
0250     // Show fade controls only if the current Phonon backend supports fading.
0251     if (Phonon::VolumeFaderEffect(this).isValid())
0252     {
0253         // Fade checkbox
0254         mFadeCheckbox = new CheckBox(i18nc("@option:check", "Fade"), group);
0255         connect(mFadeCheckbox, &CheckBox::toggled, this, &SoundWidget::slotFadeToggled);
0256         mFadeCheckbox->setWhatsThis(i18nc("@info:whatsthis", "Select to fade the volume when the sound file first starts to play."));
0257         grid->addWidget(mFadeCheckbox, 2, 1, 1, 2, Qt::AlignLeft);
0258 
0259         // Fade time
0260         mFadeBox = new QWidget(group);
0261         grid->addWidget(mFadeBox, 3, 2, Qt::AlignLeft);
0262         boxHLayout = new QHBoxLayout(mFadeBox);
0263         boxHLayout->setContentsMargins(0, 0, 0, 0);
0264         label = new QLabel(i18nc("@label:spinbox Time period over which to fade the sound", "Fade time:"), mFadeBox);
0265         boxHLayout->addWidget(label);
0266         mFadeTime = new SpinBox(1, 999, mFadeBox);
0267         boxHLayout->addWidget(mFadeTime);
0268         mFadeTime->setSingleShiftStep(10);
0269         label->setBuddy(mFadeTime);
0270         connect(mFadeTime, &SpinBox::valueChanged, this, &SoundWidget::changed);
0271         label = new QLabel(i18nc("@label", "seconds"), mFadeBox);
0272         boxHLayout->addWidget(label);
0273         mFadeBox->setWhatsThis(i18nc("@info:whatsthis", "Enter how many seconds to fade the sound before reaching the set volume."));
0274 
0275         // Fade slider
0276         mFadeVolumeBox = new QWidget(group);
0277         grid->addWidget(mFadeVolumeBox, 4, 2);
0278         boxHLayout = new QHBoxLayout(mFadeVolumeBox);
0279         boxHLayout->setContentsMargins(0, 0, 0, 0);
0280         label = new QLabel(i18nc("@label:slider", "Initial volume:"), mFadeVolumeBox);
0281         boxHLayout->addWidget(label);
0282         mFadeSlider = new Slider(0, 100, 10, Qt::Horizontal, mFadeVolumeBox);
0283         boxHLayout->addWidget(mFadeSlider);
0284         mFadeSlider->setTickPosition(QSlider::TicksBelow);
0285         mFadeSlider->setTickInterval(10);
0286         mFadeSlider->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed));
0287         label->setBuddy(mFadeSlider);
0288         connect(mFadeSlider, &Slider::valueChanged, this, &SoundWidget::changed);
0289         mFadeVolumeBox->setWhatsThis(i18nc("@info:whatsthis", "Choose the initial volume for playing the sound file."));
0290     }
0291 
0292     slotVolumeToggled(false);
0293 }
0294 
0295 SoundWidget::~SoundWidget()
0296 {
0297     delete mPlayer;   // this stops playing if not already stopped
0298     mPlayer = nullptr;
0299 }
0300 
0301 /******************************************************************************
0302 * Set the controls' values.
0303 */
0304 void SoundWidget::set(const QString& file, float volume, float fadeVolume, int fadeSeconds, int repeatPause)
0305 {
0306     // Initialise the control values
0307     QUrl url(file);
0308     mFileEdit->setText(url.isLocalFile() ? url.toLocalFile() : url.toDisplayString());
0309     if (mRepeatGroupBox)
0310     {
0311         mRepeatGroupBox->setChecked(repeatPause >= 0);
0312         mRepeatPause->setValue(repeatPause >= 0 ? repeatPause : 0);
0313     }
0314     mVolumeCheckbox->setChecked(volume >= 0);
0315     mVolumeSlider->setValue(volume >= 0 ? static_cast<int>(volume*100) : 100);
0316     if (mFadeCheckbox)
0317     {
0318         mFadeCheckbox->setChecked(fadeVolume >= 0);
0319         mFadeSlider->setValue(fadeVolume >= 0 ? static_cast<int>(fadeVolume*100) : 100);
0320         mFadeTime->setValue(fadeSeconds);
0321     }
0322     slotVolumeToggled(volume >= 0);
0323 }
0324 
0325 /******************************************************************************
0326 * Set the read-only status of the widget.
0327 */
0328 void SoundWidget::setReadOnly(bool readOnly)
0329 {
0330     if (readOnly != mReadOnly)
0331     {
0332         mFileEdit->setReadOnly(readOnly);
0333         mFileBrowseButton->setReadOnly(readOnly);
0334         if (mRepeatGroupBox)
0335             mRepeatGroupBox->setReadOnly(readOnly);
0336         mVolumeCheckbox->setReadOnly(readOnly);
0337         mVolumeSlider->setReadOnly(readOnly);
0338         if (mFadeCheckbox)
0339         {
0340             mFadeCheckbox->setReadOnly(readOnly);
0341             mFadeTime->setReadOnly(readOnly);
0342             mFadeSlider->setReadOnly(readOnly);
0343         }
0344         mReadOnly = readOnly;
0345     }
0346 }
0347 
0348 /******************************************************************************
0349 * Return the file name typed in the edit field.
0350 */
0351 QString SoundWidget::fileName() const
0352 {
0353     return mFileEdit->text();
0354 }
0355 
0356 /******************************************************************************
0357 * Validate the entered file and return it.
0358 */
0359 bool SoundWidget::file(QUrl& url, bool showErrorMessage) const
0360 {
0361     const bool result = validate(showErrorMessage);
0362     url = mUrl;
0363     return result;
0364 }
0365 
0366 /******************************************************************************
0367 * Return the entered repetition and volume settings:
0368 * 'volume' is in range 0 - 1, or < 0 if volume is not to be set.
0369 * 'fadeVolume is similar, with 'fadeTime' set to the fade interval in seconds.
0370 */
0371 void SoundWidget::getVolume(float& volume, float& fadeVolume, int& fadeSeconds) const
0372 {
0373     volume = mVolumeCheckbox->isChecked() ? (float)mVolumeSlider->value() / 100 : -1;
0374     if (mFadeCheckbox  &&  mFadeCheckbox->isChecked())
0375     {
0376         fadeVolume  = (float)mFadeSlider->value() / 100;
0377         fadeSeconds = mFadeTime->value();
0378     }
0379     else
0380     {
0381         fadeVolume  = -1;
0382         fadeSeconds = 0;
0383     }
0384 }
0385 
0386 /******************************************************************************
0387 * Return the entered repetition setting.
0388 * Reply = seconds to pause between repetitions, or -1 if no repeat.
0389 */
0390 int SoundWidget::repeatPause() const
0391 {
0392     return mRepeatGroupBox && mRepeatGroupBox->isChecked() ? mRepeatPause->value() : -1;
0393 }
0394 
0395 /******************************************************************************
0396 * Called when the dialog's size has changed.
0397 * Records the new size in the config file.
0398 */
0399 void SoundWidget::resizeEvent(QResizeEvent* re)
0400 {
0401     if (mFadeCheckbox)
0402         mVolumeSlider->resize(mFadeSlider->size());
0403     QWidget::resizeEvent(re);
0404 }
0405 
0406 void SoundWidget::showEvent(QShowEvent* se)
0407 {
0408     if (mFadeCheckbox)
0409         mVolumeSlider->resize(mFadeSlider->size());
0410     QWidget::showEvent(se);
0411 }
0412 
0413 /******************************************************************************
0414 * Called when the file browser button is clicked.
0415 */
0416 void SoundWidget::slotPickFile()
0417 {
0418     QString file;
0419     if (SoundPicker::browseFile(file, mDefaultDir, mFileEdit->text()))
0420     {
0421         if (!file.isEmpty())
0422             mFileEdit->setText(File::pathOrUrl(file));
0423     }
0424 }
0425 
0426 /******************************************************************************
0427 * Called when the file play or stop button is clicked.
0428 */
0429 void SoundWidget::playSound()
0430 {
0431     if (mPlayer)
0432     {
0433         // The file is currently playing. Stop it.
0434         playFinished();
0435         return;
0436     }
0437     if (!validate(true))
0438         return;
0439 #if 0
0440 #warning Phonon::createPlayer() does not work
0441     mPlayer = Phonon::createPlayer(Phonon::MusicCategory, mUrl);
0442     mPlayer->setParent(this);
0443 #else
0444     mPlayer = new Phonon::MediaObject(this);
0445     auto output = new Phonon::AudioOutput(Phonon::MusicCategory, mPlayer);   //NOLINT(clang-analyzer-cplusplus.NewDeleteLeaks)
0446     mPlayer->setCurrentSource(mUrl);
0447     Phonon::createPath(mPlayer, output);
0448 #endif
0449     connect(mPlayer, &Phonon::MediaObject::finished, this, &SoundWidget::playFinished);
0450     mFilePlay->setIcon(QIcon::fromTheme(QStringLiteral("media-playback-stop")));   // change the play button to a stop button
0451     mFilePlay->setToolTip(i18nc("@info:tooltip", "Stop sound"));
0452     mFilePlay->setWhatsThis(i18nc("@info:whatsthis", "Stop playing the sound"));
0453     mPlayer->play();
0454 }
0455 
0456 /******************************************************************************
0457 * Called when playing the file has completed, or to stop playing.
0458 */
0459 void SoundWidget::playFinished()
0460 {
0461     delete mPlayer;   // this stops playing if not already stopped
0462     mPlayer = nullptr;
0463     mFilePlay->setIcon(QIcon::fromTheme(QStringLiteral("media-playback-start")));
0464     mFilePlay->setToolTip(i18nc("@info:tooltip", "Test the sound"));
0465     mFilePlay->setWhatsThis(i18nc("@info:whatsthis", "Play the selected sound file."));
0466 }
0467 
0468 /******************************************************************************
0469 * Check whether the specified sound file exists.
0470 */
0471 bool SoundWidget::validate(bool showErrorMessage) const
0472 {
0473     QString file = mFileEdit->text();
0474     if (file == mValidatedFile  &&  !file.isEmpty())
0475         return true;
0476     mValidatedFile = file;
0477     if (file.isEmpty()  &&  mEmptyFileAllowed)
0478     {
0479         mUrl.clear();
0480         return true;
0481     }
0482     File::Error err = File::checkFileExists(file, mUrl, MainWindow::mainMainWindow());
0483     if (err == File::Error::None)
0484         return true;
0485     if (err == File::Error::Nonexistent)
0486     {
0487         if (mUrl.isLocalFile()  &&  !file.startsWith(QLatin1Char('/')))
0488         {
0489             // It's a relative path.
0490             // Find the first sound resource that contains files.
0491             const QStringList soundDirs = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("sounds"));
0492             if (!soundDirs.isEmpty())
0493             {
0494                 QDir dir;
0495                 dir.setFilter(QDir::Files | QDir::Readable);
0496                 for (int i = 0, end = soundDirs.count();  i < end;  ++i)
0497                 {
0498                     dir.setPath(soundDirs[i]);
0499                     if (dir.isReadable() && dir.count() > 2)
0500                     {
0501                         QString f = soundDirs[i] + QLatin1Char('/') + file;
0502                         err = File::checkFileExists(f, mUrl, MainWindow::mainMainWindow());
0503                         if (err == File::Error::None)
0504                             return true;
0505                         if (err != File::Error::Nonexistent)
0506                         {
0507                             file = f;   // for inclusion in error message
0508                             break;
0509                         }
0510                     }
0511                 }
0512             }
0513             if (err == File::Error::Nonexistent)
0514             {
0515                 QString f = QDir::homePath() + QLatin1Char('/') + file;
0516                 err = File::checkFileExists(f, mUrl, MainWindow::mainMainWindow());
0517                 if (err == File::Error::None)
0518                     return true;
0519                 if (err != File::Error::Nonexistent)
0520                     file = f;   // for inclusion in error message
0521             }
0522         }
0523     }
0524     mFileEdit->setFocus();
0525     if (showErrorMessage
0526     &&  File::showFileErrMessage(file, err, File::Error::BlankPlay, const_cast<SoundWidget*>(this)))
0527         return true;
0528     mValidatedFile.clear();
0529     mUrl.clear();
0530     return false;
0531 }
0532 
0533 /******************************************************************************
0534 * Called when the Set Volume checkbox is toggled.
0535 */
0536 void SoundWidget::slotVolumeToggled(bool on)
0537 {
0538     mVolumeSlider->setEnabled(on);
0539     if (mFadeCheckbox)
0540     {
0541         mFadeCheckbox->setEnabled(on);
0542         slotFadeToggled(on  &&  mFadeCheckbox->isChecked());
0543     }
0544 }
0545 
0546 /******************************************************************************
0547 * Called when the Fade checkbox is toggled.
0548 */
0549 void SoundWidget::slotFadeToggled(bool on)
0550 {
0551     if (mFadeCheckbox)
0552     {
0553         mFadeBox->setEnabled(on);
0554         mFadeVolumeBox->setEnabled(on);
0555         Q_EMIT changed();
0556     }
0557 }
0558 
0559 #include "moc_sounddlg.cpp"
0560 
0561 // vim: et sw=4: