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: