Fix Bug 363586 - Kmail2 handles mailto: incorrectly. urlencoded does not work
[kdepim.git] / kalarm / src / sounddlg.cpp
blob24267c4bcde980c14a31d7782c148833e9033b21
1 /*
2 * sounddlg.cpp - sound file selection and configuration dialog and widget
3 * Program: kalarm
4 * Copyright © 2005-2012 by David Jarvie <djarvie@kde.org>
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License along
17 * with this program; if not, write to the Free Software Foundation, Inc.,
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
21 #include "kalarm.h"
22 #include "sounddlg.h"
24 #include "checkbox.h"
25 #include "functions.h"
26 #include "groupbox.h"
27 #include "lineedit.h"
28 #include "pushbutton.h"
29 #include "slider.h"
30 #include "soundpicker.h"
31 #include "spinbox.h"
33 #include <QIcon>
34 #include <KGlobal>
35 #include <KLocalizedString>
36 #include <kstandarddirs.h>
37 #include <phonon/mediaobject.h>
38 #include <phonon/audiooutput.h>
40 #include <QLabel>
41 #include <QDir>
42 #include <QGroupBox>
43 #include <QHBoxLayout>
44 #include <QVBoxLayout>
45 #include <QGridLayout>
46 #include <QShowEvent>
47 #include <QResizeEvent>
48 #include <QDialogButtonBox>
49 #include <QStyle>
50 #include "kalarm_debug.h"
53 // Collect these widget labels together to ensure consistent wording and
54 // translations across different modules.
55 QString SoundWidget::i18n_chk_Repeat() { return i18nc("@option:check", "Repeat"); }
57 static const char SOUND_DIALOG_NAME[] = "SoundDialog";
60 SoundDlg::SoundDlg(const QString& file, float volume, float fadeVolume, int fadeSeconds, int repeatPause,
61 const QString& caption, QWidget* parent)
62 : QDialog(parent),
63 mReadOnly(false)
65 QVBoxLayout *layout = new QVBoxLayout(this);
66 mSoundWidget = new SoundWidget(true, true, this);
67 layout->addWidget(mSoundWidget);
69 mButtonBox = new QDialogButtonBox;
70 mButtonBox->addButton(QDialogButtonBox::Ok);
71 mButtonBox->addButton(QDialogButtonBox::Cancel);
72 connect(mButtonBox, &QDialogButtonBox::clicked,
73 this, &SoundDlg::slotButtonClicked);
74 layout->addWidget(mButtonBox);
76 setWindowTitle(caption);
78 // Restore the dialog size from last time
79 QSize s;
80 if (KAlarm::readConfigWindowSize(SOUND_DIALOG_NAME, s))
81 resize(s);
83 // Initialise the control values
84 mSoundWidget->set(file, volume, fadeVolume, fadeSeconds, repeatPause);
87 /******************************************************************************
88 * Set the read-only status of the dialog.
90 void SoundDlg::setReadOnly(bool readOnly)
92 if (readOnly != mReadOnly)
94 mSoundWidget->setReadOnly(readOnly);
95 mReadOnly = readOnly;
96 if (readOnly)
98 mButtonBox->clear();
99 mButtonBox->addButton(QDialogButtonBox::Cancel);
101 else
103 mButtonBox->clear();
104 mButtonBox->addButton(QDialogButtonBox::Ok);
105 mButtonBox->addButton(QDialogButtonBox::Cancel);
110 QUrl SoundDlg::getFile() const
112 QUrl url;
113 mSoundWidget->file(url);
114 return url;
117 /******************************************************************************
118 * Called when the dialog's size has changed.
119 * Records the new size in the config file.
121 void SoundDlg::resizeEvent(QResizeEvent* re)
123 if (isVisible())
124 KAlarm::writeConfigWindowSize(SOUND_DIALOG_NAME, re->size());
125 QDialog::resizeEvent(re);
128 /******************************************************************************
129 * Called when the OK or Cancel button is clicked.
131 void SoundDlg::slotButtonClicked(QAbstractButton *button)
133 if (button == mButtonBox->button(QDialogButtonBox::Ok))
135 if (mReadOnly)
136 reject();
137 else if (mSoundWidget->validate(true))
138 accept();
140 else
141 reject();
146 /*=============================================================================
147 = Class SoundWidget
148 = Select a sound file and configure how to play it.
149 =============================================================================*/
150 QString SoundWidget::mDefaultDir;
152 SoundWidget::SoundWidget(bool showPlay, bool showRepeat, QWidget* parent)
153 : QWidget(parent),
154 mFilePlay(Q_NULLPTR),
155 mRepeatGroupBox(Q_NULLPTR),
156 mRepeatPause(Q_NULLPTR),
157 mPlayer(Q_NULLPTR),
158 mReadOnly(false),
159 mEmptyFileAllowed(false)
161 QVBoxLayout* layout = new QVBoxLayout(this);
163 QLabel* label = Q_NULLPTR;
164 if (!showPlay)
166 label = new QLabel(i18nc("@label", "Sound file:"), this);
167 layout->addWidget(label);
170 QWidget* box = new QWidget(this);
171 layout->addWidget(box);
172 QHBoxLayout* boxHLayout = new QHBoxLayout(box);
173 boxHLayout->setMargin(0);
174 boxHLayout->setSpacing(0);
176 if (showPlay)
178 // File play button
179 mFilePlay = new QPushButton(box);
180 boxHLayout->addWidget(mFilePlay);
181 mFilePlay->setIcon(QIcon::fromTheme(QStringLiteral("media-playback-start")));
182 connect(mFilePlay, &QPushButton::clicked, this, &SoundWidget::playSound);
183 mFilePlay->setToolTip(i18nc("@info:tooltip", "Test the sound"));
184 mFilePlay->setWhatsThis(i18nc("@info:whatsthis", "Play the selected sound file."));
187 // File name edit box
188 mFileEdit = new LineEdit(LineEdit::Url, box);
189 boxHLayout->addWidget(mFileEdit);
190 mFileEdit->setAcceptDrops(true);
191 mFileEdit->setWhatsThis(i18nc("@info:whatsthis", "Enter the name or URL of a sound file to play."));
192 if (label)
193 label->setBuddy(mFileEdit);
194 connect(mFileEdit, &LineEdit::textChanged, this, &SoundWidget::changed);
196 // File browse button
197 mFileBrowseButton = new PushButton(box);
198 boxHLayout->addWidget(mFileBrowseButton);
199 mFileBrowseButton->setIcon(QIcon(QIcon::fromTheme(QStringLiteral("document-open"))));
200 int size = mFileBrowseButton->sizeHint().height();
201 mFileBrowseButton->setFixedSize(size, size);
202 connect(mFileBrowseButton, &PushButton::clicked, this, &SoundWidget::slotPickFile);
203 mFileBrowseButton->setToolTip(i18nc("@info:tooltip", "Choose a file"));
204 mFileBrowseButton->setWhatsThis(i18nc("@info:whatsthis", "Select a sound file to play."));
206 if (mFilePlay)
208 int size = qMax(mFilePlay->sizeHint().height(), mFileBrowseButton->sizeHint().height());
209 mFilePlay->setFixedSize(size, size);
210 mFileBrowseButton->setFixedSize(size, size);
213 if (showRepeat)
215 // Sound repetition checkbox
216 mRepeatGroupBox = new GroupBox(i18n_chk_Repeat(), this);
217 mRepeatGroupBox->setCheckable(true);
218 mRepeatGroupBox->setWhatsThis(i18nc("@info:whatsthis", "If checked, the sound file will be played repeatedly for as long as the message is displayed."));
219 connect(mRepeatGroupBox, &GroupBox::toggled, this, &SoundWidget::changed);
220 layout->addWidget(mRepeatGroupBox);
221 QVBoxLayout* glayout = new QVBoxLayout(mRepeatGroupBox);
223 // Pause between repetitions
224 QWidget* box = new QWidget(mRepeatGroupBox);
225 glayout->addWidget(box);
226 boxHLayout = new QHBoxLayout(box);
227 boxHLayout->setMargin(0);
228 label = new QLabel(i18nc("@label:spinbox Length of time to pause between repetitions", "Pause between repetitions:"), box);
229 boxHLayout->addWidget(label);
230 label->setFixedSize(label->sizeHint());
231 mRepeatPause = new SpinBox(0, 999, box);
232 boxHLayout->addWidget(mRepeatPause);
233 mRepeatPause->setSingleShiftStep(10);
234 mRepeatPause->setFixedSize(mRepeatPause->sizeHint());
235 label->setBuddy(mRepeatPause);
236 connect(mRepeatPause, static_cast<void (SpinBox::*)(int)>(&SpinBox::valueChanged), this, &SoundWidget::changed);
237 label = new QLabel(i18nc("@label", "seconds"), box);
238 boxHLayout->addWidget(label);
239 label->setFixedSize(label->sizeHint());
240 box->setWhatsThis(i18nc("@info:whatsthis", "Enter how many seconds to pause between repetitions."));
243 // Volume
244 QGroupBox* group = new QGroupBox(i18nc("@title:group Sound volume", "Volume"), this);
245 layout->addWidget(group);
246 QGridLayout* grid = new QGridLayout(group);
247 grid->setMargin(style()->pixelMetric(QStyle::PM_DefaultChildMargin));
248 grid->setSpacing(style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing));
249 grid->setColumnStretch(2, 1);
250 int indentWidth = 3 * style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing);
251 grid->setColumnMinimumWidth(0, indentWidth);
252 grid->setColumnMinimumWidth(1, indentWidth);
254 // 'Set volume' checkbox
255 box = new QWidget(group);
256 grid->addWidget(box, 1, 0, 1, 3);
257 boxHLayout = new QHBoxLayout(box);
258 boxHLayout->setMargin(0);
259 mVolumeCheckbox = new CheckBox(i18nc("@option:check", "Set volume"), box);
260 boxHLayout->addWidget(mVolumeCheckbox);
261 mVolumeCheckbox->setFixedSize(mVolumeCheckbox->sizeHint());
262 connect(mVolumeCheckbox, &CheckBox::toggled, this, &SoundWidget::slotVolumeToggled);
263 mVolumeCheckbox->setWhatsThis(i18nc("@info:whatsthis", "Select to choose the volume for playing the sound file."));
265 // Volume slider
266 mVolumeSlider = new Slider(0, 100, 10, Qt::Horizontal, box);
267 boxHLayout->addWidget(mVolumeSlider);
268 mVolumeSlider->setTickPosition(QSlider::TicksBelow);
269 mVolumeSlider->setTickInterval(10);
270 mVolumeSlider->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed));
271 mVolumeSlider->setWhatsThis(i18nc("@info:whatsthis", "Choose the volume for playing the sound file."));
272 mVolumeCheckbox->setFocusWidget(mVolumeSlider);
273 connect(mVolumeSlider, &Slider::valueChanged, this, &SoundWidget::changed);
275 // Fade checkbox
276 mFadeCheckbox = new CheckBox(i18nc("@option:check", "Fade"), group);
277 mFadeCheckbox->setFixedSize(mFadeCheckbox->sizeHint());
278 connect(mFadeCheckbox, &CheckBox::toggled, this, &SoundWidget::slotFadeToggled);
279 mFadeCheckbox->setWhatsThis(i18nc("@info:whatsthis", "Select to fade the volume when the sound file first starts to play."));
280 grid->addWidget(mFadeCheckbox, 2, 1, 1, 2, Qt::AlignLeft);
282 // Fade time
283 mFadeBox = new QWidget(group);
284 grid->addWidget(mFadeBox, 3, 2, Qt::AlignLeft);
285 boxHLayout = new QHBoxLayout(mFadeBox);
286 boxHLayout->setMargin(0);
287 label = new QLabel(i18nc("@label:spinbox Time period over which to fade the sound", "Fade time:"), mFadeBox);
288 boxHLayout->addWidget(label);
289 label->setFixedSize(label->sizeHint());
290 mFadeTime = new SpinBox(1, 999, mFadeBox);
291 boxHLayout->addWidget(mFadeTime);
292 mFadeTime->setSingleShiftStep(10);
293 mFadeTime->setFixedSize(mFadeTime->sizeHint());
294 label->setBuddy(mFadeTime);
295 connect(mFadeTime, static_cast<void (SpinBox::*)(int)>(&SpinBox::valueChanged), this, &SoundWidget::changed);
296 label = new QLabel(i18nc("@label", "seconds"), mFadeBox);
297 boxHLayout->addWidget(label);
298 label->setFixedSize(label->sizeHint());
299 mFadeBox->setWhatsThis(i18nc("@info:whatsthis", "Enter how many seconds to fade the sound before reaching the set volume."));
301 // Fade slider
302 mFadeVolumeBox = new QWidget(group);
303 grid->addWidget(mFadeVolumeBox, 4, 2);
304 boxHLayout = new QHBoxLayout(mFadeVolumeBox);
305 boxHLayout->setMargin(0);
306 label = new QLabel(i18nc("@label:slider", "Initial volume:"), mFadeVolumeBox);
307 boxHLayout->addWidget(label);
308 label->setFixedSize(label->sizeHint());
309 mFadeSlider = new Slider(0, 100, 10, Qt::Horizontal, mFadeVolumeBox);
310 boxHLayout->addWidget(mFadeSlider);
311 mFadeSlider->setTickPosition(QSlider::TicksBelow);
312 mFadeSlider->setTickInterval(10);
313 mFadeSlider->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed));
314 label->setBuddy(mFadeSlider);
315 connect(mFadeSlider, &Slider::valueChanged, this, &SoundWidget::changed);
316 mFadeVolumeBox->setWhatsThis(i18nc("@info:whatsthis", "Choose the initial volume for playing the sound file."));
318 slotVolumeToggled(false);
321 SoundWidget::~SoundWidget()
323 delete mPlayer; // this stops playing if not already stopped
324 mPlayer = Q_NULLPTR;
327 /******************************************************************************
328 * Set the controls' values.
330 void SoundWidget::set(const QString& file, float volume, float fadeVolume, int fadeSeconds, int repeatPause)
332 // Initialise the control values
333 mFileEdit->setText(KAlarm::pathOrUrl(file));
334 if (mRepeatGroupBox)
336 mRepeatGroupBox->setChecked(repeatPause >= 0);
337 mRepeatPause->setValue(repeatPause >= 0 ? repeatPause : 0);
339 mVolumeCheckbox->setChecked(volume >= 0);
340 mVolumeSlider->setValue(volume >= 0 ? static_cast<int>(volume*100) : 100);
341 mFadeCheckbox->setChecked(fadeVolume >= 0);
342 mFadeSlider->setValue(fadeVolume >= 0 ? static_cast<int>(fadeVolume*100) : 100);
343 mFadeTime->setValue(fadeSeconds);
344 slotVolumeToggled(volume >= 0);
347 /******************************************************************************
348 * Set the read-only status of the widget.
350 void SoundWidget::setReadOnly(bool readOnly)
352 if (readOnly != mReadOnly)
354 mFileEdit->setReadOnly(readOnly);
355 mFileBrowseButton->setReadOnly(readOnly);
356 if (mRepeatGroupBox)
357 mRepeatGroupBox->setReadOnly(readOnly);
358 mVolumeCheckbox->setReadOnly(readOnly);
359 mVolumeSlider->setReadOnly(readOnly);
360 mFadeCheckbox->setReadOnly(readOnly);
361 mFadeTime->setReadOnly(readOnly);
362 mFadeSlider->setReadOnly(readOnly);
363 mReadOnly = readOnly;
367 /******************************************************************************
368 * Return the file name typed in the edit field.
370 QString SoundWidget::fileName() const
372 return mFileEdit->text();
375 /******************************************************************************
376 * Validate the entered file and return it.
378 bool SoundWidget::file(QUrl& url, bool showErrorMessage) const
380 bool result = validate(showErrorMessage);
381 url = mUrl;
382 return result;
385 /******************************************************************************
386 * Return the entered repetition and volume settings:
387 * 'volume' is in range 0 - 1, or < 0 if volume is not to be set.
388 * 'fadeVolume is similar, with 'fadeTime' set to the fade interval in seconds.
390 void SoundWidget::getVolume(float& volume, float& fadeVolume, int& fadeSeconds) const
392 volume = mVolumeCheckbox->isChecked() ? (float)mVolumeSlider->value() / 100 : -1;
393 if (mFadeCheckbox->isChecked())
395 fadeVolume = (float)mFadeSlider->value() / 100;
396 fadeSeconds = mFadeTime->value();
398 else
400 fadeVolume = -1;
401 fadeSeconds = 0;
405 /******************************************************************************
406 * Return the entered repetition setting.
407 * Reply = seconds to pause between repetitions, or -1 if no repeat.
409 int SoundWidget::repeatPause() const
411 return mRepeatGroupBox && mRepeatGroupBox->isChecked() ? mRepeatPause->value() : -1;
414 /******************************************************************************
415 * Called when the dialog's size has changed.
416 * Records the new size in the config file.
418 void SoundWidget::resizeEvent(QResizeEvent* re)
420 mVolumeSlider->resize(mFadeSlider->size());
421 QWidget::resizeEvent(re);
424 void SoundWidget::showEvent(QShowEvent* se)
426 mVolumeSlider->resize(mFadeSlider->size());
427 QWidget::showEvent(se);
430 /******************************************************************************
431 * Called when the file browser button is clicked.
433 void SoundWidget::slotPickFile()
435 QString url = SoundPicker::browseFile(mDefaultDir, mFileEdit->text());
436 if (!url.isEmpty())
437 mFileEdit->setText(KAlarm::pathOrUrl(url));
440 /******************************************************************************
441 * Called when the file play or stop button is clicked.
443 void SoundWidget::playSound()
445 if (mPlayer)
447 // The file is currently playing. Stop it.
448 playFinished();
449 return;
451 if (!validate(true))
452 return;
453 #if 0
454 #warning Phonon::createPlayer() does not work
455 mPlayer = Phonon::createPlayer(Phonon::MusicCategory, mUrl);
456 mPlayer->setParent(this);
457 #else
458 mPlayer = new Phonon::MediaObject(this);
459 Phonon::AudioOutput* output = new Phonon::AudioOutput(Phonon::MusicCategory, mPlayer);
460 mPlayer->setCurrentSource(mUrl);
461 Phonon::createPath(mPlayer, output);
462 #endif
463 connect(mPlayer, &Phonon::MediaObject::finished, this, &SoundWidget::playFinished);
464 mFilePlay->setIcon(QIcon::fromTheme(QStringLiteral("media-playback-stop"))); // change the play button to a stop button
465 mFilePlay->setToolTip(i18nc("@info:tooltip", "Stop sound"));
466 mFilePlay->setWhatsThis(i18nc("@info:whatsthis", "Stop playing the sound"));
467 mPlayer->play();
470 /******************************************************************************
471 * Called when playing the file has completed, or to stop playing.
473 void SoundWidget::playFinished()
475 delete mPlayer; // this stops playing if not already stopped
476 mPlayer = Q_NULLPTR;
477 mFilePlay->setIcon(QIcon::fromTheme(QStringLiteral("media-playback-start")));
478 mFilePlay->setToolTip(i18nc("@info:tooltip", "Test the sound"));
479 mFilePlay->setWhatsThis(i18nc("@info:whatsthis", "Play the selected sound file."));
482 /******************************************************************************
483 * Check whether the specified sound file exists.
485 bool SoundWidget::validate(bool showErrorMessage) const
487 QString file = mFileEdit->text();
488 if (file == mValidatedFile && !file.isEmpty())
489 return true;
490 mValidatedFile = file;
491 if (file.isEmpty() && mEmptyFileAllowed)
493 mUrl.clear();
494 return true;
496 KAlarm::FileErr err = KAlarm::checkFileExists(file, mUrl);
497 if (err == KAlarm::FileErr_None)
498 return true;
499 if (err == KAlarm::FileErr_Nonexistent)
501 if (mUrl.isLocalFile() && !file.startsWith(QStringLiteral("/")))
503 // It's a relative path.
504 // Find the first sound resource that contains files.
505 QStringList soundDirs = KGlobal::dirs()->resourceDirs("sound");
506 if (!soundDirs.isEmpty())
508 QDir dir;
509 dir.setFilter(QDir::Files | QDir::Readable);
510 for (int i = 0, end = soundDirs.count(); i < end; ++i)
512 dir = soundDirs[i];
513 if (dir.isReadable() && dir.count() > 2)
515 QString f = soundDirs[i] + QDir::separator() + file;
516 err = KAlarm::checkFileExists(f, mUrl);
517 if (err == KAlarm::FileErr_None)
518 return true;
519 if (err != KAlarm::FileErr_Nonexistent)
521 file = f; // for inclusion in error message
522 break;
527 if (err == KAlarm::FileErr_Nonexistent)
529 QString f = QDir::homePath() + QDir::separator() + file;
530 err = KAlarm::checkFileExists(f, mUrl);
531 if (err == KAlarm::FileErr_None)
532 return true;
533 if (err != KAlarm::FileErr_Nonexistent)
534 file = f; // for inclusion in error message
538 mFileEdit->setFocus();
539 if (showErrorMessage
540 && KAlarm::showFileErrMessage(file, err, KAlarm::FileErr_BlankPlay, const_cast<SoundWidget*>(this)))
541 return true;
542 mValidatedFile.clear();
543 mUrl.clear();
544 return false;
547 /******************************************************************************
548 * Called when the Set Volume checkbox is toggled.
550 void SoundWidget::slotVolumeToggled(bool on)
552 mVolumeSlider->setEnabled(on);
553 mFadeCheckbox->setEnabled(on);
554 slotFadeToggled(on && mFadeCheckbox->isChecked());
557 /******************************************************************************
558 * Called when the Fade checkbox is toggled.
560 void SoundWidget::slotFadeToggled(bool on)
562 mFadeBox->setEnabled(on);
563 mFadeVolumeBox->setEnabled(on);
564 Q_EMIT changed();
567 // vim: et sw=4: