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: