1/*
2 Copyright (C) 2006-2008 Matthias Kretz <kretz@kde.org>
3 Copyright (C) 2011 Casian Andrei <skeletk13@gmail.com>
4 Copyright (C) 2014-2019 Harald Sitter <sitter@kde.org>
5
6 This program is free software; you can redistribute it and/or
7 modify it under the terms of the GNU Library General Public
8 License as published by the Free Software Foundation; either
9 version 2 of the License, or (at your option) version 3.
10
11 This library 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 GNU
14 Library General Public License for more details.
15
16 You should have received a copy of the GNU Library General Public License
17 along with this library; see the file COPYING.LIB. If not, write to
18 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19 Boston, MA 02110-1301, USA.
20*/
21
22#include "devicepreference.h"
23
24#include <QDialogButtonBox>
25#include <QListWidget>
26#include <QLabel>
27#include <QMessageBox>
28#include <QPointer>
29#include <QStandardPaths>
30
31#include <phonon/AudioOutput>
32#include <phonon/BackendCapabilities>
33#include <phonon/MediaObject>
34#include <phonon/VideoWidget>
35#include <phonon/globalconfig.h>
36#include <phonon/phononnamespace.h>
37
38#ifndef METATYPE_QLIST_INT_DEFINED
39#define METATYPE_QLIST_INT_DEFINED
40// Want this exactly once, see phonondefs_p.h kcm/devicepreference.cpp
41Q_DECLARE_METATYPE(QList<int>)
42#endif
43
44namespace Phonon {
45
46/*
47 * Lists of categories for every device type
48 */
49static const Category audioOutCategories[] = {
50 NoCategory,
51 NotificationCategory,
52 MusicCategory,
53 VideoCategory,
54 CommunicationCategory,
55 GameCategory,
56 AccessibilityCategory,
57};
58
59static const CaptureCategory audioCapCategories[] = {
60 NoCaptureCategory,
61 CommunicationCaptureCategory,
62 RecordingCaptureCategory,
63 ControlCaptureCategory
64};
65
66static const CaptureCategory videoCapCategories[] = {
67 NoCaptureCategory,
68 CommunicationCaptureCategory,
69 RecordingCaptureCategory,
70};
71
72static const int audioOutCategoriesCount = sizeof(audioOutCategories) / sizeof(Category);
73static const int audioCapCategoriesCount = sizeof(audioCapCategories) / sizeof(CaptureCategory);
74static const int videoCapCategoriesCount = sizeof(videoCapCategories) / sizeof(CaptureCategory);
75
76void operator++(Category &c)
77{
78 c = static_cast<Category>(1 + static_cast<int>(c));
79 //Q_ASSERT(c <= LastCategory);
80}
81
82class CategoryItem : public QStandardItem {
83public:
84 CategoryItem(Category cat)
85 : QStandardItem(),
86 m_cat(cat),
87 m_odtype(AudioOutputDeviceType)
88 {
89 if (cat == NoCategory) {
90 setText(QObject::tr(s: "Audio Playback"));
91 } else {
92 setText(categoryToString(c: cat));
93 }
94 }
95
96 CategoryItem(CaptureCategory cat, ObjectDescriptionType t = AudioCaptureDeviceType)
97 : QStandardItem(),
98 m_capcat(cat),
99 m_odtype(t)
100 {
101 if (cat == NoCaptureCategory) {
102 switch(t) {
103 case AudioCaptureDeviceType:
104 setText(QObject::tr(s: "Audio Recording"));
105 break;
106 case VideoCaptureDeviceType:
107 setText(QObject::tr(s: "Video Recording"));
108 break;
109 default:
110 setText(QObject::tr(s: "Invalid"));
111 }
112 } else {
113 setText(categoryToString(c: cat));
114 }
115 }
116
117 int type() const override { return 1001; }
118 Category category() const { return m_cat; }
119 CaptureCategory captureCategory() const { return m_capcat; }
120 ObjectDescriptionType odtype() const { return m_odtype; }
121
122private:
123 Category m_cat;
124 CaptureCategory m_capcat;
125 ObjectDescriptionType m_odtype;
126};
127
128/**
129 * Need this to change the colors of the ListView if the Palette changed. With CSS set this won't
130 * change automatically
131 */
132void DevicePreference::changeEvent(QEvent *e)
133{
134 QWidget::changeEvent(e);
135 if (e->type() == QEvent::PaletteChange) {
136 deviceList->setStyleSheet(deviceList->styleSheet());
137 }
138}
139
140DevicePreference::DevicePreference(QWidget *parent)
141 : QWidget(parent),
142 m_headerModel(0, 1, nullptr),
143 m_media(nullptr), m_audioOutput(nullptr), m_videoWidget(nullptr)
144{
145 setupUi(this);
146
147 // Setup the buttons
148 testPlaybackButton->setIcon(QIcon::fromTheme(QStringLiteral("media-playback-start")));
149 testPlaybackButton->setEnabled(false);
150 testPlaybackButton->setToolTip(tr(s: "Test the selected device"));
151 deferButton->setIcon(QIcon::fromTheme(QStringLiteral("go-down")));
152 preferButton->setIcon(QIcon::fromTheme(QStringLiteral("go-up")));
153
154 // Configure the device list
155 deviceList->setDragDropMode(QAbstractItemView::InternalMove);
156 deviceList->setStyleSheet(QStringLiteral("QTreeView {"
157 "background-color: palette(base);"
158 "background-image: url(:/phononsettings/listview-background.png);"
159 "background-position: bottom left;"
160 "background-attachment: fixed;"
161 "background-repeat: no-repeat;"
162 "background-clip: padding;"
163 "}"));
164 deviceList->setAlternatingRowColors(false);
165
166 // The root item for the categories
167 QStandardItem *parentItem = m_categoryModel.invisibleRootItem();
168
169 // Audio Output Parent
170 QStandardItem *aOutputItem = new CategoryItem(NoCategory);
171 m_audioOutputModel[NoCategory] = new AudioOutputDeviceModel(this);
172 aOutputItem->setEditable(false);
173 aOutputItem->setToolTip(tr(s: "Defines the default ordering of devices which can be overridden by individual categories."));
174 parentItem->appendRow(aitem: aOutputItem);
175
176 // Audio Capture Parent
177 QStandardItem *aCaptureItem = new CategoryItem(NoCaptureCategory, AudioCaptureDeviceType);
178 m_audioCaptureModel[NoCaptureCategory] = new AudioCaptureDeviceModel(this);
179 aCaptureItem->setEditable(false);
180 aCaptureItem->setToolTip(tr(s: "Defines the default ordering of devices which can be overridden by individual categories."));
181 parentItem->appendRow(aitem: aCaptureItem);
182
183 // Video Capture Parent
184 QStandardItem *vCaptureItem = new CategoryItem(NoCaptureCategory, VideoCaptureDeviceType);
185 m_videoCaptureModel[NoCaptureCategory] = new VideoCaptureDeviceModel(this);
186 vCaptureItem->setEditable(false);
187 vCaptureItem->setToolTip(tr(s: "Defines the default ordering of devices which can be overridden by individual categories."));
188 parentItem->appendRow(aitem: vCaptureItem);
189
190 // Audio Output Children
191 parentItem = aOutputItem;
192 for (int i = 1; i < audioOutCategoriesCount; ++i) { // i == 1 to skip NoCategory
193 m_audioOutputModel[audioOutCategories[i]] = new AudioOutputDeviceModel(this);
194 QStandardItem *item = new CategoryItem(audioOutCategories[i]);
195 item->setEditable(false);
196 parentItem->appendRow(aitem: item);
197 }
198
199 // Audio Capture Children
200 parentItem = aCaptureItem;
201 for (int i = 1; i < audioCapCategoriesCount; ++i) { // i == 1 to skip NoCategory
202 m_audioCaptureModel[audioCapCategories[i]] = new AudioCaptureDeviceModel(this);
203 QStandardItem *item = new CategoryItem(audioCapCategories[i], AudioCaptureDeviceType);
204 item->setEditable(false);
205 parentItem->appendRow(aitem: item);
206 }
207
208 // Video Capture Children
209 parentItem = vCaptureItem;
210 for (int i = 1; i < videoCapCategoriesCount; ++i) { // i == 1 to skip NoCategory
211 m_videoCaptureModel[videoCapCategories[i]] = new VideoCaptureDeviceModel(this);
212 QStandardItem *item = new CategoryItem(videoCapCategories[i], VideoCaptureDeviceType);
213 item->setEditable(false);
214 parentItem->appendRow(aitem: item);
215 }
216
217 // Configure the category tree
218 categoryTree->setModel(&m_categoryModel);
219 if (categoryTree->header()) {
220 categoryTree->header()->hide();
221 }
222 categoryTree->expandAll();
223
224 connect(asender: categoryTree->selectionModel(), SIGNAL(currentChanged(const QModelIndex &,const QModelIndex &)),
225 SLOT(updateDeviceList()));
226
227 // Connect all model data change signals to the changed slot
228 for (int i = -1; i <= LastCategory; ++i) {
229 connect(sender: m_audioOutputModel[i], SIGNAL(rowsInserted(QModelIndex, int, int)), receiver: this, SIGNAL(changed()));
230 connect(sender: m_audioOutputModel[i], SIGNAL(rowsRemoved(QModelIndex, int, int)), receiver: this, SIGNAL(changed()));
231 connect(sender: m_audioOutputModel[i], SIGNAL(layoutChanged()), receiver: this, SIGNAL(changed()));
232 connect(sender: m_audioOutputModel[i], SIGNAL(dataChanged(QModelIndex, QModelIndex)), receiver: this, SIGNAL(changed()));
233 if (m_audioCaptureModel.contains(key: i)) {
234 connect(sender: m_audioCaptureModel[i], SIGNAL(rowsInserted(QModelIndex, int, int)), receiver: this, SIGNAL(changed()));
235 connect(sender: m_audioCaptureModel[i], SIGNAL(rowsRemoved(QModelIndex , int, int)), receiver: this, SIGNAL(changed()));
236 connect(sender: m_audioCaptureModel[i], SIGNAL(layoutChanged()), receiver: this, SIGNAL(changed()));
237 connect(sender: m_audioCaptureModel[i], SIGNAL(dataChanged(QModelIndex, QModelIndex)), receiver: this, SIGNAL(changed()));
238 }
239 if (m_videoCaptureModel.contains(key: i)) {
240 connect(sender: m_videoCaptureModel[i], SIGNAL(rowsInserted(QModelIndex, int, int)), receiver: this, SIGNAL(changed()));
241 connect(sender: m_videoCaptureModel[i], SIGNAL(rowsRemoved(QModelIndex, int, int)), receiver: this, SIGNAL(changed()));
242 connect(sender: m_videoCaptureModel[i], SIGNAL(layoutChanged()), receiver: this, SIGNAL(changed()));
243 connect(sender: m_videoCaptureModel[i], SIGNAL(dataChanged(QModelIndex, QModelIndex)), receiver: this, SIGNAL(changed()));
244 }
245 }
246
247 connect(sender: showAdvancedDevicesCheckBox, signal: &QCheckBox::stateChanged, context: this, slot: &DevicePreference::changed);
248
249 // Connect the signals from Phonon that notify changes in the device lists
250 connect(asender: BackendCapabilities::notifier(), SIGNAL(availableAudioOutputDevicesChanged()), SLOT(updateAudioOutputDevices()));
251 connect(asender: BackendCapabilities::notifier(), SIGNAL(availableAudioCaptureDevicesChanged()), SLOT(updateAudioCaptureDevices()));
252 connect(asender: BackendCapabilities::notifier(), SIGNAL(availableVideoCaptureDevicesChanged()), SLOT(updateVideoCaptureDevices()));
253 connect(asender: BackendCapabilities::notifier(), SIGNAL(capabilitiesChanged()), SLOT(updateAudioOutputDevices()));
254 connect(asender: BackendCapabilities::notifier(), SIGNAL(capabilitiesChanged()), SLOT(updateAudioCaptureDevices()));
255 connect(asender: BackendCapabilities::notifier(), SIGNAL(capabilitiesChanged()), SLOT(updateVideoCaptureDevices()));
256
257 if (!categoryTree->currentIndex().isValid()) {
258 categoryTree->setCurrentIndex(m_categoryModel.index(row: 1, column: 0, parent: m_categoryModel.index(row: 0, column: 0)));
259 }
260}
261
262DevicePreference::~DevicePreference()
263{
264 // Ensure that the video widget is destroyed, if it remains active
265 delete m_videoWidget;
266}
267
268void DevicePreference::updateDeviceList()
269{
270 // Temporarily disconnect the device list selection model
271 if (deviceList->selectionModel()) {
272 disconnect(sender: deviceList->selectionModel(),
273 SIGNAL(currentRowChanged(const QModelIndex &,const QModelIndex &)),
274 receiver: this, SLOT(updateButtonsEnabled()));
275 }
276
277 // Get the current selected category item
278 QStandardItem *currentItem = m_categoryModel.itemFromIndex(index: categoryTree->currentIndex());
279 if (currentItem && currentItem->type() == 1001) {
280 CategoryItem *catItem = static_cast<CategoryItem *>(currentItem);
281 bool cap = catItem->odtype() != AudioOutputDeviceType;
282 const Category cat = catItem->category();
283 const CaptureCategory capcat = catItem->captureCategory();
284
285 // Update the device list, by setting it's model to the one for the corresponding category
286 switch (catItem->odtype()) {
287 case AudioOutputDeviceType:
288 deviceList->setModel(m_audioOutputModel[cat]);
289 break;
290 case AudioCaptureDeviceType:
291 deviceList->setModel(m_audioCaptureModel[capcat]);
292 break;
293 case VideoCaptureDeviceType:
294 deviceList->setModel(m_videoCaptureModel[capcat]);
295 break;
296 default: ;
297 }
298
299 // Update the header
300 if (cap ? capcat == NoCaptureCategory : cat == NoCategory) {
301 switch (catItem->odtype()) {
302 case AudioOutputDeviceType:
303 m_headerModel.setHeaderData(section: 0, orientation: Qt::Horizontal, value: tr(s: "Default Audio Playback Device Preference"), role: Qt::DisplayRole);
304 break;
305 case AudioCaptureDeviceType:
306 m_headerModel.setHeaderData(section: 0, orientation: Qt::Horizontal, value: tr(s: "Default Audio Recording Device Preference"), role: Qt::DisplayRole);
307 break;
308 case VideoCaptureDeviceType:
309 m_headerModel.setHeaderData(section: 0, orientation: Qt::Horizontal, value: tr(s: "Default Video Recording Device Preference"), role: Qt::DisplayRole);
310 break;
311 default: ;
312 }
313 } else {
314 switch (catItem->odtype()) {
315 case AudioOutputDeviceType:
316 m_headerModel.setHeaderData(section: 0, orientation: Qt::Horizontal, value: tr(s: "Audio Playback Device Preference for the '%1' Category").arg(
317 a: categoryToString(c: cat)), role: Qt::DisplayRole);
318 break;
319 case AudioCaptureDeviceType:
320 m_headerModel.setHeaderData(section: 0, orientation: Qt::Horizontal, value: tr(s: "Audio Recording Device Preference for the '%1' Category").arg(
321 a: categoryToString(c: capcat)), role: Qt::DisplayRole);
322 break;
323 case VideoCaptureDeviceType:
324 m_headerModel.setHeaderData(section: 0, orientation: Qt::Horizontal, value: tr(s: "Video Recording Device Preference for the '%1' Category ").arg(
325 a: categoryToString(c: capcat)), role: Qt::DisplayRole);
326 break;
327 default: ;
328 }
329 }
330 } else {
331 // No valid category selected
332 m_headerModel.setHeaderData(section: 0, orientation: Qt::Horizontal, value: QString(), role: Qt::DisplayRole);
333 deviceList->setModel(nullptr);
334 }
335
336 // Update the header, the buttons enabled state
337 deviceList->header()->setModel(&m_headerModel);
338 updateButtonsEnabled();
339
340 // Reconnect the device list selection model
341 if (deviceList->selectionModel()) {
342 connect(sender: deviceList->selectionModel(), SIGNAL(currentRowChanged(const QModelIndex &,const QModelIndex &)),
343 receiver: this, SLOT(updateButtonsEnabled()));
344 }
345
346 deviceList->resizeColumnToContents(column: 0);
347}
348
349void DevicePreference::updateAudioCaptureDevices()
350{
351 const QList<AudioCaptureDevice> list = availableAudioCaptureDevices();
352 QHash<int, AudioCaptureDevice> hash;
353 foreach (const AudioCaptureDevice &dev, list) {
354 hash.insert(key: dev.index(), value: dev);
355 }
356
357 for (int catIndex = 0; catIndex < audioCapCategoriesCount; ++ catIndex) {
358 const int i = audioCapCategories[catIndex];
359 AudioCaptureDeviceModel *model = m_audioCaptureModel.value(key: i);
360 Q_ASSERT(model);
361
362 QHash<int, AudioCaptureDevice> hashCopy(hash);
363 QList<AudioCaptureDevice> orderedList;
364
365 if (model->rowCount() > 0) {
366 QList<int> order = model->tupleIndexOrder();
367 foreach (int idx, order) {
368 if (hashCopy.contains(key: idx)) {
369 orderedList << hashCopy.take(key: idx);
370 }
371 }
372
373 if (hashCopy.size() > 1) {
374 // keep the order of the original list
375 foreach (const AudioCaptureDevice &dev, list) {
376 if (hashCopy.contains(key: dev.index())) {
377 orderedList << hashCopy.take(key: dev.index());
378 }
379 }
380 } else if (hashCopy.size() == 1) {
381 orderedList += hashCopy.values();
382 }
383
384 model->setModelData(orderedList);
385 } else {
386 model->setModelData(list);
387 }
388 }
389
390 deviceList->resizeColumnToContents(column: 0);
391}
392
393void DevicePreference::updateVideoCaptureDevices()
394{
395 const QList<VideoCaptureDevice> list = availableVideoCaptureDevices();
396 QHash<int, VideoCaptureDevice> hash;
397 foreach (const VideoCaptureDevice &dev, list) {
398 hash.insert(key: dev.index(), value: dev);
399 }
400
401 for (int catIndex = 0; catIndex < videoCapCategoriesCount; ++ catIndex) {
402 const int i = videoCapCategories[catIndex];
403 VideoCaptureDeviceModel *model = m_videoCaptureModel.value(key: i);
404 Q_ASSERT(model);
405
406 QHash<int, VideoCaptureDevice> hashCopy(hash);
407 QList<VideoCaptureDevice> orderedList;
408
409 if (model->rowCount() > 0) {
410 QList<int> order = model->tupleIndexOrder();
411 foreach (int idx, order) {
412 if (hashCopy.contains(key: idx)) {
413 orderedList << hashCopy.take(key: idx);
414 }
415 }
416
417 if (hashCopy.size() > 1) {
418 // keep the order of the original list
419 foreach (const VideoCaptureDevice &dev, list) {
420 if (hashCopy.contains(key: dev.index())) {
421 orderedList << hashCopy.take(key: dev.index());
422 }
423 }
424 } else if (hashCopy.size() == 1) {
425 orderedList += hashCopy.values();
426 }
427
428 model->setModelData(orderedList);
429 } else {
430 model->setModelData(list);
431 }
432 }
433
434 deviceList->resizeColumnToContents(column: 0);
435}
436
437void DevicePreference::updateAudioOutputDevices()
438{
439 const QList<AudioOutputDevice> list = availableAudioOutputDevices();
440 QHash<int, AudioOutputDevice> hash;
441 foreach (const AudioOutputDevice &dev, list) {
442 hash.insert(key: dev.index(), value: dev);
443 }
444
445 for (int catIndex = 0; catIndex < audioOutCategoriesCount; ++ catIndex) {
446 const int i = audioOutCategories[catIndex];
447 AudioOutputDeviceModel *model = m_audioOutputModel.value(key: i);
448 Q_ASSERT(model);
449
450 QHash<int, AudioOutputDevice> hashCopy(hash);
451 QList<AudioOutputDevice> orderedList;
452
453 if (model->rowCount() > 0) {
454 QList<int> order = model->tupleIndexOrder();
455 foreach (int idx, order) {
456 if (hashCopy.contains(key: idx)) {
457 orderedList << hashCopy.take(key: idx);
458 }
459 }
460
461 if (hashCopy.size() > 1) {
462 // keep the order of the original list
463 foreach (const AudioOutputDevice &dev, list) {
464 if (hashCopy.contains(key: dev.index())) {
465 orderedList << hashCopy.take(key: dev.index());
466 }
467 }
468 } else if (hashCopy.size() == 1) {
469 orderedList += hashCopy.values();
470 }
471
472 model->setModelData(orderedList);
473 } else {
474 model->setModelData(list);
475 }
476 }
477
478 deviceList->resizeColumnToContents(column: 0);
479}
480
481QList<AudioOutputDevice> DevicePreference::availableAudioOutputDevices() const
482{
483 return BackendCapabilities::availableAudioOutputDevices();
484}
485
486QList<AudioCaptureDevice> DevicePreference::availableAudioCaptureDevices() const
487{
488#ifndef PHONON_NO_AUDIOCAPTURE
489 return BackendCapabilities::availableAudioCaptureDevices();
490#else
491 return QList<AudioCaptureDevice>();
492#endif
493}
494
495QList<VideoCaptureDevice> DevicePreference::availableVideoCaptureDevices() const
496{
497#ifndef PHONON_NO_VIDEOCAPTURE
498 return BackendCapabilities::availableVideoCaptureDevices();
499#else
500 return QList<VideoCaptureDevice>();
501#endif
502}
503
504void DevicePreference::load()
505{
506 showAdvancedDevicesCheckBox->setChecked(!GlobalConfig().hideAdvancedDevices());
507 loadCategoryDevices();
508}
509
510void DevicePreference::loadCategoryDevices()
511{
512 // "Load" the settings from the backend.
513 for (int i = 0; i < audioOutCategoriesCount; ++ i) {
514 const Category cat = audioOutCategories[i];
515 QList<AudioOutputDevice> list;
516 const QList<int> deviceIndexes = GlobalConfig().audioOutputDeviceListFor(category: cat);
517 foreach (int i, deviceIndexes) {
518 list.append(t: AudioOutputDevice::fromIndex(index: i));
519 }
520
521 m_audioOutputModel[cat]->setModelData(list);
522 }
523
524#ifndef PHONON_NO_AUDIOCAPTURE
525 for (int i = 0; i < audioCapCategoriesCount; ++ i) {
526 const CaptureCategory cat = audioCapCategories[i];
527 QList<AudioCaptureDevice> list;
528 const QList<int> deviceIndexes = GlobalConfig().audioCaptureDeviceListFor(category: cat);
529 foreach (int i, deviceIndexes) {
530 list.append(t: AudioCaptureDevice::fromIndex(index: i));
531 }
532
533 m_audioCaptureModel[cat]->setModelData(list);
534 }
535#endif
536
537#ifndef PHONON_NO_VIDEOCAPTURE
538 for (int i = 0; i < videoCapCategoriesCount; ++ i) {
539 const CaptureCategory cat = videoCapCategories[i];
540 QList<VideoCaptureDevice> list;
541 const QList<int> deviceIndexes = GlobalConfig().videoCaptureDeviceListFor(category: cat);
542 foreach (int i, deviceIndexes) {
543 list.append(t: VideoCaptureDevice::fromIndex(index: i));
544 }
545
546 m_videoCaptureModel[cat]->setModelData(list);
547 }
548#endif
549
550 deviceList->resizeColumnToContents(column: 0);
551}
552
553void DevicePreference::save()
554{
555 for (int i = 0; i < audioOutCategoriesCount; ++i) {
556 const Category cat = audioOutCategories[i];
557 Q_ASSERT(m_audioOutputModel.value(cat));
558 const QList<int> order = m_audioOutputModel.value(key: cat)->tupleIndexOrder();
559 GlobalConfig().setAudioOutputDeviceListFor(category: cat, order);
560 }
561
562#ifndef PHONON_NO_AUDIOCAPTURE
563 for (int i = 0; i < audioCapCategoriesCount; ++i) {
564 const CaptureCategory cat = audioCapCategories[i];
565 Q_ASSERT(m_audioCaptureModel.value(cat));
566 const QList<int> order = m_audioCaptureModel.value(key: cat)->tupleIndexOrder();
567 GlobalConfig().setAudioCaptureDeviceListFor(category: cat, order);
568 }
569#endif
570
571#ifndef PHONON_NO_VIDEOCAPTURE
572 for (int i = 0; i < videoCapCategoriesCount; ++i) {
573 const CaptureCategory cat = videoCapCategories[i];
574 Q_ASSERT(m_videoCaptureModel.value(cat));
575 const QList<int> order = m_videoCaptureModel.value(key: cat)->tupleIndexOrder();
576 GlobalConfig().setVideoCaptureDeviceListFor(category: cat, order);
577 }
578#endif
579}
580
581void DevicePreference::defaults()
582{
583 {
584 const QList<AudioOutputDevice> list = availableAudioOutputDevices();
585 for (int i = 0; i < audioOutCategoriesCount; ++i) {
586 m_audioOutputModel[audioOutCategories[i]]->setModelData(list);
587 }
588 }
589 {
590 const QList<AudioCaptureDevice> list = availableAudioCaptureDevices();
591 for (int i = 0; i < audioCapCategoriesCount; ++i) {
592 m_audioCaptureModel[audioCapCategories[i]]->setModelData(list);
593 }
594 }
595 {
596 const QList<VideoCaptureDevice> list = availableVideoCaptureDevices();
597 for (int i = 0; i < videoCapCategoriesCount; ++i) {
598 m_videoCaptureModel[videoCapCategories[i]]->setModelData(list);
599 }
600 }
601
602 /*
603 * Save this list (that contains even hidden devices) to GlobaConfig, and then
604 * load them back. All devices that should be hidden will be hidden
605 */
606 save();
607 loadCategoryDevices();
608
609 deviceList->resizeColumnToContents(column: 0);
610}
611
612void DevicePreference::pulseAudioEnabled()
613{
614 showAdvancedDevicesContainer->removeItem(showAdvancedDevicesSpacer);
615 delete showAdvancedDevicesSpacer;
616 showAdvancedDevicesCheckBox->setVisible(false);
617}
618
619void DevicePreference::on_preferButton_clicked()
620{
621 QAbstractItemModel *model = deviceList->model();
622 {
623 AudioOutputDeviceModel *deviceModel = dynamic_cast<AudioOutputDeviceModel *>(model);
624 if (deviceModel) {
625 deviceModel->moveUp(index: deviceList->currentIndex());
626 updateButtonsEnabled();
627 emit changed();
628 }
629 }
630 {
631 AudioCaptureDeviceModel *deviceModel = dynamic_cast<AudioCaptureDeviceModel *>(model);
632 if (deviceModel) {
633 deviceModel->moveUp(index: deviceList->currentIndex());
634 updateButtonsEnabled();
635 emit changed();
636 }
637 }
638 {
639 VideoCaptureDeviceModel *deviceModel = dynamic_cast<VideoCaptureDeviceModel *>(model);
640 if (deviceModel) {
641 deviceModel->moveUp(index: deviceList->currentIndex());
642 updateButtonsEnabled();
643 emit changed();
644 }
645 }
646}
647
648void DevicePreference::on_deferButton_clicked()
649{
650 QAbstractItemModel *model = deviceList->model();
651 {
652 AudioOutputDeviceModel *deviceModel = dynamic_cast<AudioOutputDeviceModel *>(model);
653 if (deviceModel) {
654 deviceModel->moveDown(index: deviceList->currentIndex());
655 updateButtonsEnabled();
656 emit changed();
657 }
658 }
659 {
660 AudioCaptureDeviceModel *deviceModel = dynamic_cast<AudioCaptureDeviceModel *>(model);
661 if (deviceModel) {
662 deviceModel->moveDown(index: deviceList->currentIndex());
663 updateButtonsEnabled();
664 emit changed();
665 }
666 }
667 {
668 VideoCaptureDeviceModel *deviceModel = dynamic_cast<VideoCaptureDeviceModel *>(model);
669 if (deviceModel) {
670 deviceModel->moveDown(index: deviceList->currentIndex());
671 updateButtonsEnabled();
672 emit changed();
673 }
674 }
675}
676
677DevicePreference::DeviceType DevicePreference::shownModelType() const
678{
679 const QStandardItem *item = m_categoryModel.itemFromIndex(index: categoryTree->currentIndex());
680 if (!item)
681 return dtInvalidDevice;
682 Q_ASSERT(item->type() == 1001);
683
684 const CategoryItem *catItem = static_cast<const CategoryItem *>(item);
685 if (!catItem)
686 return dtInvalidDevice;
687
688 switch (catItem->odtype()) {
689 case AudioOutputDeviceType:
690 return dtAudioOutput;
691 case AudioCaptureDeviceType:
692 return dtAudioCapture;
693 case VideoCaptureDeviceType:
694 return dtVideoCapture;
695 default:
696 return dtInvalidDevice;
697 }
698}
699
700void DevicePreference::on_applyPreferencesButton_clicked()
701{
702 const QModelIndex idx = categoryTree->currentIndex();
703 const QStandardItem *item = m_categoryModel.itemFromIndex(index: idx);
704 if (!item)
705 return;
706 Q_ASSERT(item->type() == 1001);
707
708 const CategoryItem *catItem = static_cast<const CategoryItem *>(item);
709
710 QList<AudioOutputDevice> aoPreferredList;
711 QList<AudioCaptureDevice> acPreferredList;
712 QList<VideoCaptureDevice> vcPreferredList;
713 const Category *categoryList = nullptr;
714 const CaptureCategory *capCategoryList = nullptr;
715 int categoryListCount;
716 int catIndex;
717 bool cap = false;
718
719 switch (catItem->odtype()) {
720 case AudioOutputDeviceType:
721 aoPreferredList = m_audioOutputModel.value(key: catItem->category())->modelData();
722 categoryList = audioOutCategories;
723 categoryListCount = audioOutCategoriesCount;
724 cap = false;
725 break;
726
727 case AudioCaptureDeviceType:
728 acPreferredList = m_audioCaptureModel.value(key: catItem->captureCategory())->modelData();
729 capCategoryList = audioCapCategories;
730 categoryListCount = audioCapCategoriesCount;
731 cap = true;
732 break;
733
734 case VideoCaptureDeviceType:
735 vcPreferredList = m_videoCaptureModel.value(key: catItem->captureCategory())->modelData();
736 capCategoryList = videoCapCategories;
737 categoryListCount = videoCapCategoriesCount;
738 cap = true;
739 break;
740
741 default:
742 return;
743 }
744
745 QPointer<QDialog> dialog = new QDialog(this);
746
747 QLabel *label = new QLabel(dialog);
748 label->setText(tr(s: "Apply the currently shown device preference list to the following other "
749 "audio playback categories:"));
750 label->setWordWrap(true);
751
752 QListWidget *list = new QListWidget(dialog);
753
754 for (catIndex = 0; catIndex < categoryListCount; catIndex ++) {
755 Category cat = cap ? NoCategory : categoryList[catIndex];
756 CaptureCategory capcat = cap ? capCategoryList[catIndex] : NoCaptureCategory;
757
758 QListWidgetItem *item = nullptr;
759 if (cap) {
760 if (capcat == NoCaptureCategory) {
761 item = new QListWidgetItem(tr(s: "Default/Unspecified Category"), list, capcat);
762 } else {
763 item = new QListWidgetItem(categoryToString(c: capcat), list, capcat);
764 }
765 } else {
766 if (cat == NoCategory) {
767 item = new QListWidgetItem(tr(s: "Default/Unspecified Category"), list, cat);
768 } else {
769 item = new QListWidgetItem(categoryToString(c: cat), list, cat);
770 }
771 }
772
773 item->setCheckState(Qt::Checked);
774 if (cat == catItem->category()) {
775 item->setFlags(item->flags() & ~Qt::ItemIsEnabled);
776 }
777 }
778
779 QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok
780 | QDialogButtonBox::Cancel, dialog);
781 connect(sender: buttonBox, signal: &QDialogButtonBox::accepted, context: dialog.data(), slot: &QDialog::accept);
782 connect(sender: buttonBox, signal: &QDialogButtonBox::rejected, context: dialog.data(), slot: &QDialog::reject);
783
784 QVBoxLayout *layout = new QVBoxLayout(dialog);
785 layout->addWidget(label);
786 layout->addWidget(list);
787 layout->addWidget(buttonBox);
788
789 switch (dialog->exec()) {
790 case QDialog::Accepted:
791 for (catIndex = 0; catIndex < categoryListCount; catIndex ++) {
792 Category cat = cap ? NoCategory : categoryList[catIndex];
793 CaptureCategory capcat = cap ? capCategoryList[catIndex] : NoCaptureCategory;
794
795 if (cap ? capcat != catItem->captureCategory() : cat != catItem->category()) {
796 QListWidgetItem *item = list->item(row: catIndex);
797 Q_ASSERT(item->type() == cap ? (int) capcat : (int) cat);
798 if (item->checkState() == Qt::Checked) {
799 switch (catItem->odtype()) {
800 case AudioOutputDeviceType:
801 m_audioOutputModel.value(key: cat)->setModelData(aoPreferredList);
802 break;
803
804 case AudioCaptureDeviceType:
805 m_audioCaptureModel.value(key: capcat)->setModelData(acPreferredList);
806 break;
807
808 case VideoCaptureDeviceType:
809 m_videoCaptureModel.value(key: capcat)->setModelData(vcPreferredList);
810 break;
811
812 default: ;
813 }
814 }
815 }
816 }
817
818 emit changed();
819 break;
820
821 case QDialog::Rejected:
822 // nothing to do
823 break;
824 }
825
826 delete dialog;
827}
828
829void DevicePreference::on_showAdvancedDevicesCheckBox_toggled()
830{
831 // In order to get the right list from the backend, we need to update the settings now
832 // before calling availableAudio{Output,Capture}Devices()
833 GlobalConfig().setHideAdvancedDevices(!showAdvancedDevicesCheckBox->isChecked());
834 loadCategoryDevices();
835}
836
837void DevicePreference::on_testPlaybackButton_toggled(bool down)
838{
839 if (down) {
840 QModelIndex idx = deviceList->currentIndex();
841 if (!idx.isValid()) {
842 return;
843 }
844
845 // Shouldn't happen, but better to be on the safe side
846 if (m_testingType != dtInvalidDevice) {
847 delete m_media;
848 m_media = nullptr;
849 delete m_audioOutput;
850 m_audioOutput = nullptr;
851 delete m_videoWidget;
852 m_videoWidget = nullptr;
853 }
854
855 // Setup the Phonon objects according to the testing type
856 m_testingType = shownModelType();
857 switch (m_testingType) {
858 case dtAudioOutput: {
859 // Create an audio output with the selected device
860 m_media = new MediaObject(this);
861 const AudioOutputDeviceModel *model = static_cast<const AudioOutputDeviceModel *>(idx.model());
862 const AudioOutputDevice &device = model->modelData(index: idx);
863 m_audioOutput = new AudioOutput(this);
864 if (!m_audioOutput->setOutputDevice(device)) {
865 QMessageBox::critical(parent: this, title: tr(s: "Failed to set the selected audio output device"), text: tr(s: "Failed to set the selected audio output device"));
866 break;
867 }
868
869 // Just to be very sure that nothing messes our test sound up
870 m_audioOutput->setVolume(1.0);
871 m_audioOutput->setMuted(false);
872
873 createPath(source: m_media, sink: m_audioOutput);
874 static QUrl testUrl = QUrl::fromLocalFile(localfile: QStandardPaths::locate(
875 type: QStandardPaths::GenericDataLocation,
876 QStringLiteral("sounds/Oxygen-Sys-Log-In.ogg")));
877 m_media->setCurrentSource(testUrl);
878 connect(sender: m_media, signal: &MediaObject::finished, context: testPlaybackButton, slot: &QToolButton::toggle);
879
880 break;
881 }
882
883#ifndef PHONON_NO_AUDIOCAPTURE
884 case dtAudioCapture: {
885 // Create a media object and an audio output
886 m_media = new MediaObject(this);
887 m_audioOutput = new AudioOutput(NoCategory, this);
888
889 // Just to be very sure that nothing messes our test sound up
890 m_audioOutput->setVolume(1.0);
891 m_audioOutput->setMuted(false);
892
893 // Try to create a path
894 if (!createPath(source: m_media, sink: m_audioOutput).isValid()) {
895 QMessageBox::critical(parent: this, title: tr(s: "Your backend may not support audio recording"), text: tr(s: "Your backend may not support audio recording"));
896 break;
897 }
898
899 // Determine the selected device
900 const AudioCaptureDeviceModel *model = static_cast<const AudioCaptureDeviceModel *>(idx.model());
901 const AudioCaptureDevice &device = model->modelData(index: idx);
902 m_media->setCurrentSource(device);
903
904 break;
905 }
906#endif
907
908#ifndef PHONON_NO_VIDEOCAPTURE
909 case dtVideoCapture: {
910 // Create a media object and a video output
911 m_media = new MediaObject(this);
912 m_videoWidget = new VideoWidget(nullptr);
913
914 // Try to create a path
915 if (!createPath(source: m_media, sink: m_videoWidget).isValid()) {
916 QMessageBox::critical(parent: this, title: tr(s: "Your backend may not support video recording"), text: tr(s: "Your backend may not support video recording"));
917 break;
918 }
919
920 // Determine the selected device
921 const VideoCaptureDeviceModel *model = static_cast<const VideoCaptureDeviceModel *>(idx.model());
922 const VideoCaptureDevice &device = model->modelData(index: idx);
923 m_media->setCurrentSource(device);
924
925 // Set up the testing video widget
926 m_videoWidget->setWindowTitle(tr(s: "Testing %1").arg(a: device.name()));
927 m_videoWidget->setWindowFlags(Qt::WindowStaysOnTopHint | Qt::WindowTitleHint | Qt::WindowMinMaxButtonsHint);
928 if (device.property(name: "icon").canConvert(targetTypeId: QVariant::String))
929 m_videoWidget->setWindowIcon(QIcon::fromTheme(name: device.property(name: "icon").toString()));
930 m_videoWidget->move(QCursor::pos() - QPoint(250, 295));
931 m_videoWidget->resize(w: 320, h: 240);
932 m_videoWidget->show();
933
934 break;
935 }
936#endif
937
938 default:
939 return;
940 }
941
942 m_media->play();
943 } else {
944 // Uninitialize the Phonon objects according to the testing type
945 switch (m_testingType) {
946 case dtAudioOutput:
947 disconnect(sender: m_media, signal: &MediaObject::finished, receiver: testPlaybackButton, slot: &QToolButton::toggle);
948 delete m_media;
949 delete m_audioOutput;
950 break;
951
952 case dtAudioCapture:
953 delete m_media;
954 delete m_audioOutput;
955 break;
956
957 case dtVideoCapture:
958 delete m_media;
959 delete m_videoWidget;
960 break;
961
962 default:
963 return;
964 }
965
966 m_media = nullptr;
967 m_videoWidget = nullptr;
968 m_audioOutput = nullptr;
969 m_testingType = dtInvalidDevice;
970 }
971}
972
973void DevicePreference::updateButtonsEnabled()
974{
975 if (deviceList->model()) {
976 QModelIndex idx = deviceList->currentIndex();
977 preferButton->setEnabled(idx.isValid() && idx.row() > 0);
978 deferButton->setEnabled(idx.isValid() && idx.row() < deviceList->model()->rowCount() - 1);
979 testPlaybackButton->setEnabled(idx.isValid() && (idx.flags() & Qt::ItemIsEnabled));
980 } else {
981 preferButton->setEnabled(false);
982 deferButton->setEnabled(false);
983 testPlaybackButton->setEnabled(false);
984 }
985}
986
987} // Phonon namespace
988
989#include "moc_devicepreference.cpp"
990

source code of phonon/settings/devicepreference.cpp