File indexing completed on 2024-05-12 05:14:58
0001 /* 0002 * soundpicker.cpp - widget to select a sound file or a beep 0003 * Program: kalarm 0004 * SPDX-FileCopyrightText: 2002-2022 David Jarvie <djarvie@kde.org> 0005 * 0006 * SPDX-License-Identifier: GPL-2.0-or-later 0007 */ 0008 0009 #include "soundpicker.h" 0010 0011 #include "sounddlg.h" 0012 #include "lib/autoqpointer.h" 0013 #include "lib/combobox.h" 0014 #include "lib/file.h" 0015 #include "lib/pushbutton.h" 0016 0017 #ifdef HAVE_TEXT_TO_SPEECH_SUPPORT 0018 #include <TextEditTextToSpeech/TextToSpeech> 0019 #endif 0020 #include <KLocalizedString> 0021 #include <phonon/backendcapabilities.h> 0022 0023 #include <QIcon> 0024 #include <QFileInfo> 0025 #include <QTimer> 0026 #include <QLabel> 0027 #include <QHBoxLayout> 0028 #include <QStandardPaths> 0029 #include <QMimeDatabase> 0030 0031 //clazy:excludeall=non-pod-global-static 0032 0033 namespace 0034 { 0035 const int NoneIndex = 0; 0036 const int FileIndex = 2; 0037 } 0038 0039 // Collect these widget labels together to ensure consistent wording and 0040 // translations across different modules. 0041 QString SoundPicker::i18n_label_Sound() { return i18nc("@label:listbox Listbox providing audio options", "Sound:"); } 0042 QString SoundPicker::i18n_combo_None() { return i18nc("@item:inlistbox No sound", "None"); } 0043 QString SoundPicker::i18n_combo_Beep() { return i18nc("@item:inlistbox", "Beep"); } 0044 QString SoundPicker::i18n_combo_Speak() { return i18nc("@item:inlistbox", "Speak"); } 0045 QString SoundPicker::i18n_combo_File() { return i18nc("@item:inlistbox", "Sound file"); } 0046 0047 0048 SoundPicker::SoundPicker(QWidget* parent) 0049 : QFrame(parent) 0050 { 0051 auto soundLayout = new QHBoxLayout(this); 0052 soundLayout->setContentsMargins(0, 0, 0, 0); 0053 mTypeBox = new QWidget(this); // this is to control the QWhatsThis text display area 0054 auto typeBoxLayout = new QHBoxLayout(mTypeBox); 0055 typeBoxLayout->setContentsMargins(0, 0, 0, 0); 0056 0057 QLabel* label = new QLabel(i18n_label_Sound(), mTypeBox); 0058 typeBoxLayout->addWidget(label); 0059 0060 // Sound type combo box 0061 mTypeCombo = new ComboBox(mTypeBox); 0062 typeBoxLayout->addWidget(mTypeCombo); 0063 mTypeCombo->addItem(i18n_combo_None(), Preferences::Sound_None); // index None 0064 mTypeCombo->addItem(i18n_combo_Beep(), Preferences::Sound_Beep); // index Beep 0065 mTypeCombo->addItem(i18n_combo_File(), Preferences::Sound_File); // index PlayFile 0066 mTypeCombo->addItem(i18n_combo_Speak(), Preferences::Sound_Speak); // index Speak (only displayed if appropriate) 0067 mFileShowing = true; 0068 mSpeakShowing = true; 0069 showSpeak(true); 0070 connect(mTypeCombo, &ComboBox::activated, this, &SoundPicker::slotTypeSelected); 0071 connect(mTypeCombo, static_cast<void (ComboBox::*)(int)>(&ComboBox::currentIndexChanged), this, &SoundPicker::changed); 0072 label->setBuddy(mTypeCombo); 0073 soundLayout->addWidget(mTypeBox); 0074 mTypeBox->setWhatsThis(xi18nc("@info:whatsthis", 0075 "<para>Choose a sound to play when the message is displayed." 0076 "<list><item><interface>%1</interface>: the message is displayed silently.</item>" 0077 "<item><interface>%2</interface>: a simple beep is sounded.</item>" 0078 "<item><interface>%3</interface>: an audio file is played. You will be prompted to choose the file and set play options. (Option not available if using notification.)</item>" 0079 "<item><interface>%4</interface>: the message text is spoken. (Option requires working Qt text-to-speech installation.)</item>" 0080 "</list></para>", 0081 i18n_combo_None(), i18n_combo_Beep(), i18n_combo_File(), i18n_combo_Speak())); 0082 0083 // Sound file picker button 0084 mFilePicker = new PushButton(this); 0085 mFilePicker->setIcon(QIcon::fromTheme(QStringLiteral("audio-x-generic"))); 0086 connect(mFilePicker, &PushButton::clicked, this, &SoundPicker::slotPickFile); 0087 mFilePicker->setToolTip(i18nc("@info:tooltip", "Configure sound file")); 0088 mFilePicker->setWhatsThis(i18nc("@info:whatsthis", "Configure a sound file to play when the alarm is displayed.")); 0089 soundLayout->addWidget(mFilePicker); 0090 0091 // Initialise the file picker button state and tooltip 0092 mTypeCombo->setCurrentIndex(NoneIndex); 0093 mFilePicker->setEnabled(false); 0094 } 0095 0096 /****************************************************************************** 0097 * Set the read-only status of the widget. 0098 */ 0099 void SoundPicker::setReadOnly(bool readOnly) 0100 { 0101 // Don't set the sound file picker read-only since it still needs to 0102 // display the read-only SoundDlg. 0103 mTypeCombo->setReadOnly(readOnly); 0104 mReadOnly = readOnly; 0105 } 0106 0107 /****************************************************************************** 0108 * Show or hide the File option. 0109 */ 0110 void SoundPicker::showFile(bool show) 0111 { 0112 if (show != mFileShowing) 0113 { 0114 if (show) 0115 mTypeCombo->insertItem(FileIndex, i18n_combo_File(), Preferences::Sound_File); 0116 else 0117 { 0118 if (mTypeCombo->currentData().toInt() == Preferences::Sound_File) 0119 mTypeCombo->setCurrentIndex(NoneIndex); 0120 mTypeCombo->removeItem(FileIndex); 0121 } 0122 mFilePicker->setVisible(show); 0123 mFileShowing = show; 0124 } 0125 } 0126 0127 /****************************************************************************** 0128 * Show or hide the Speak option. 0129 */ 0130 void SoundPicker::showSpeak(bool show) 0131 { 0132 #ifdef HAVE_TEXT_TO_SPEECH_SUPPORT 0133 if (!TextEditTextToSpeech::TextToSpeech::self()->isReady()) 0134 #endif 0135 show = false; // speech capability is not installed or configured 0136 if (show != mSpeakShowing) 0137 { 0138 // Note that 'Speak' is always the last option. 0139 if (show) 0140 mTypeCombo->addItem(i18n_combo_Speak(), Preferences::Sound_Speak); 0141 else 0142 { 0143 if (mTypeCombo->currentData().toInt() == Preferences::Sound_Speak) 0144 mTypeCombo->setCurrentIndex(NoneIndex); 0145 mTypeCombo->removeItem(mTypeCombo->count() - 1); 0146 } 0147 mSpeakShowing = show; 0148 } 0149 } 0150 0151 /****************************************************************************** 0152 * Return the currently selected option. 0153 */ 0154 Preferences::SoundType SoundPicker::sound() const 0155 { 0156 if (mTypeCombo->currentIndex() < 0) 0157 return Preferences::Sound_None; 0158 #ifndef HAVE_TEXT_TO_SPEECH_SUPPORT 0159 if (mTypeCombo->currentData().toInt() == Preferences::Sound_Speak) 0160 return Preferences::Sound_None; 0161 #endif 0162 return static_cast<Preferences::SoundType>(mTypeCombo->currentData().toInt()); 0163 } 0164 0165 /****************************************************************************** 0166 * Return the selected sound file, if the File option is selected. 0167 * Returns empty URL if File is not currently selected. 0168 */ 0169 QUrl SoundPicker::file() const 0170 { 0171 return (mTypeCombo->currentData().toInt() == Preferences::Sound_File) ? mFile : QUrl(); 0172 } 0173 0174 /****************************************************************************** 0175 * Return the specified volumes (range 0 - 1). 0176 * Returns < 0 if beep is currently selected, or if 'set volume' is not selected. 0177 */ 0178 float SoundPicker::volume(float& fadeVolume, int& fadeSeconds) const 0179 { 0180 if (mTypeCombo->currentData().toInt() == Preferences::Sound_File && !mFile.isEmpty()) 0181 { 0182 fadeVolume = mFadeVolume; 0183 fadeSeconds = mFadeSeconds; 0184 return mVolume; 0185 } 0186 else 0187 { 0188 fadeVolume = -1; 0189 fadeSeconds = 0; 0190 return -1; 0191 } 0192 } 0193 0194 /****************************************************************************** 0195 * Return the pause between sound file repetitions is selected. 0196 * Reply = pause in seconds, or -1 if repetition is not selected or beep is selected. 0197 */ 0198 int SoundPicker::repeatPause() const 0199 { 0200 return mTypeCombo->currentData().toInt() == Preferences::Sound_File && !mFile.isEmpty() ? mRepeatPause : -1; 0201 } 0202 0203 /****************************************************************************** 0204 * Initialise the widget's state. 0205 */ 0206 void SoundPicker::set(Preferences::SoundType type, const QString& f, float volume, float fadeVolume, int fadeSeconds, int repeatPause) 0207 { 0208 if (type == Preferences::Sound_File && f.isEmpty()) 0209 type = Preferences::Sound_Beep; 0210 mFile = QUrl::fromUserInput(f, QString(), QUrl::AssumeLocalFile); 0211 mVolume = volume; 0212 mFadeVolume = fadeVolume; 0213 mFadeSeconds = fadeSeconds; 0214 mRepeatPause = repeatPause; 0215 selectType(type); // this doesn't trigger slotTypeSelected() 0216 mFilePicker->setEnabled(type == Preferences::Sound_File); 0217 mTypeCombo->setToolTip(type == Preferences::Sound_File ? mFile.toDisplayString() : QString()); 0218 mLastType = type; 0219 } 0220 0221 /****************************************************************************** 0222 * Called when the sound option is changed. 0223 */ 0224 void SoundPicker::slotTypeSelected(int id) 0225 { 0226 const Preferences::SoundType newType = (id >= 0) ? static_cast<Preferences::SoundType>(mTypeCombo->itemData(id).toInt()) : Preferences::Sound_None; 0227 if (newType == mLastType || mRevertType) 0228 return; 0229 if (mLastType == Preferences::Sound_File) 0230 { 0231 mFilePicker->setEnabled(false); 0232 mTypeCombo->setToolTip(QString()); 0233 } 0234 else if (newType == Preferences::Sound_File) 0235 { 0236 if (mFile.isEmpty()) 0237 { 0238 slotPickFile(); 0239 if (mFile.isEmpty()) 0240 return; // revert to previously selected type 0241 } 0242 mFilePicker->setEnabled(true); 0243 mTypeCombo->setToolTip(mFile.toDisplayString()); 0244 } 0245 mLastType = newType; 0246 } 0247 0248 /****************************************************************************** 0249 * Called when the file picker button is clicked. 0250 */ 0251 void SoundPicker::slotPickFile() 0252 { 0253 QUrl oldfile = mFile; 0254 // Use AutoQPointer to guard against crash on application exit while 0255 // the dialogue is still open. It prevents double deletion (both on 0256 // deletion of EditAlarmDlg, and on return from this function). 0257 AutoQPointer<SoundDlg> dlg = new SoundDlg(mFile.toDisplayString(), mVolume, mFadeVolume, mFadeSeconds, mRepeatPause, i18nc("@title:window", "Sound File"), this); 0258 dlg->setReadOnly(mReadOnly); 0259 bool accepted = (dlg->exec() == QDialog::Accepted); 0260 if (mReadOnly) 0261 return; 0262 if (accepted) 0263 { 0264 float volume, fadeVolume; 0265 int fadeTime; 0266 dlg->getVolume(volume, fadeVolume, fadeTime); 0267 QUrl file = dlg->getFile(); 0268 if (!file.isEmpty()) 0269 mFile = file; 0270 mRepeatPause = dlg->repeatPause(); 0271 mVolume = volume; 0272 mFadeVolume = fadeVolume; 0273 mFadeSeconds = fadeTime; 0274 } 0275 if (mFile.isEmpty()) 0276 { 0277 // No audio file is selected, so revert to previously selected option 0278 #if 0 0279 // Remove mRevertType, setLastType(), #include QTimer 0280 // But wait a moment until setting the radio button, or it won't work. 0281 mRevertType = true; // prevent sound dialog popping up twice 0282 QTimer::singleShot(0, this, &SoundPicker::setLastType); 0283 #else 0284 selectType(mLastType); 0285 #endif 0286 mTypeCombo->setToolTip(QString()); 0287 } 0288 else 0289 mTypeCombo->setToolTip(mFile.toDisplayString()); 0290 if (accepted || mFile != oldfile) 0291 Q_EMIT changed(); 0292 } 0293 0294 /****************************************************************************** 0295 * Select the previously selected sound type. 0296 */ 0297 void SoundPicker::setLastType() 0298 { 0299 selectType(mLastType); 0300 mRevertType = false; 0301 } 0302 0303 /****************************************************************************** 0304 * Display a dialog to choose a sound file, initially highlighting any 0305 * specified file. 'initialFile' must be a full path name or URL. 0306 * 'defaultDir' is updated to the directory containing the chosen file. 0307 * @param file Updated to URL selected, or empty if none is selected. 0308 * Reply = true if @p file value can be used, 0309 * = false if the dialog was deleted while visible. 0310 */ 0311 bool SoundPicker::browseFile(QString& file, QString& defaultDir, const QString& initialFile) 0312 { 0313 static QString audioFilter; 0314 if (audioFilter.isEmpty()) 0315 { 0316 audioFilter = QStringLiteral("%1 (").arg(i18nc("@item:inlistbox", "Audio files")); 0317 QMimeDatabase db; 0318 const QStringList mimeTypes = Phonon::BackendCapabilities::availableMimeTypes(); 0319 for (const QString& mimeType : mimeTypes) 0320 if (mimeType.startsWith(QStringLiteral("audio/"))) 0321 { 0322 const QMimeType mt = db.mimeTypeForName(mimeType); 0323 audioFilter += mt.globPatterns().join(QLatin1Char(' ')); 0324 audioFilter += QLatin1Char(' '); 0325 } 0326 audioFilter[audioFilter.length() - 1] = QLatin1Char(')'); 0327 } 0328 0329 static QString kdeSoundDir; // directory containing KDE sound files 0330 if (defaultDir.isEmpty()) 0331 { 0332 if (kdeSoundDir.isNull()) 0333 { 0334 kdeSoundDir = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("sounds"), QStandardPaths::LocateDirectory); 0335 kdeSoundDir = QFileInfo(kdeSoundDir).absoluteFilePath(); 0336 } 0337 defaultDir = kdeSoundDir; 0338 } 0339 0340 return File::browseFile(file, i18nc("@title:window", "Choose Sound File"), defaultDir, 0341 audioFilter, initialFile, true, nullptr); 0342 } 0343 0344 /****************************************************************************** 0345 * Select the item corresponding to a given sound type. 0346 */ 0347 void SoundPicker::selectType(Preferences::SoundType type) 0348 { 0349 int i = mTypeCombo->findData(type); 0350 if (i >= 0) 0351 mTypeCombo->setCurrentIndex(i); 0352 } 0353 0354 #include "moc_soundpicker.cpp" 0355 0356 // vim: et sw=4: