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 |
41 | Q_DECLARE_METATYPE(QList<int>) |
42 | #endif |
43 | |
44 | namespace Phonon { |
45 | |
46 | /* |
47 | * Lists of categories for every device type |
48 | */ |
49 | static const Category audioOutCategories[] = { |
50 | NoCategory, |
51 | NotificationCategory, |
52 | MusicCategory, |
53 | VideoCategory, |
54 | CommunicationCategory, |
55 | GameCategory, |
56 | AccessibilityCategory, |
57 | }; |
58 | |
59 | static const CaptureCategory audioCapCategories[] = { |
60 | NoCaptureCategory, |
61 | CommunicationCaptureCategory, |
62 | RecordingCaptureCategory, |
63 | ControlCaptureCategory |
64 | }; |
65 | |
66 | static const CaptureCategory videoCapCategories[] = { |
67 | NoCaptureCategory, |
68 | CommunicationCaptureCategory, |
69 | RecordingCaptureCategory, |
70 | }; |
71 | |
72 | static const int audioOutCategoriesCount = sizeof(audioOutCategories) / sizeof(Category); |
73 | static const int audioCapCategoriesCount = sizeof(audioCapCategories) / sizeof(CaptureCategory); |
74 | static const int videoCapCategoriesCount = sizeof(videoCapCategories) / sizeof(CaptureCategory); |
75 | |
76 | void operator++(Category &c) |
77 | { |
78 | c = static_cast<Category>(1 + static_cast<int>(c)); |
79 | //Q_ASSERT(c <= LastCategory); |
80 | } |
81 | |
82 | class CategoryItem : public QStandardItem { |
83 | public: |
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 | |
122 | private: |
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 | */ |
132 | void DevicePreference::changeEvent(QEvent *e) |
133 | { |
134 | QWidget::changeEvent(e); |
135 | if (e->type() == QEvent::PaletteChange) { |
136 | deviceList->setStyleSheet(deviceList->styleSheet()); |
137 | } |
138 | } |
139 | |
140 | DevicePreference::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 | |
262 | DevicePreference::~DevicePreference() |
263 | { |
264 | // Ensure that the video widget is destroyed, if it remains active |
265 | delete m_videoWidget; |
266 | } |
267 | |
268 | void 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 | |
349 | void 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 | |
393 | void 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 | |
437 | void 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 | |
481 | QList<AudioOutputDevice> DevicePreference::availableAudioOutputDevices() const |
482 | { |
483 | return BackendCapabilities::availableAudioOutputDevices(); |
484 | } |
485 | |
486 | QList<AudioCaptureDevice> DevicePreference::availableAudioCaptureDevices() const |
487 | { |
488 | #ifndef PHONON_NO_AUDIOCAPTURE |
489 | return BackendCapabilities::availableAudioCaptureDevices(); |
490 | #else |
491 | return QList<AudioCaptureDevice>(); |
492 | #endif |
493 | } |
494 | |
495 | QList<VideoCaptureDevice> DevicePreference::availableVideoCaptureDevices() const |
496 | { |
497 | #ifndef PHONON_NO_VIDEOCAPTURE |
498 | return BackendCapabilities::availableVideoCaptureDevices(); |
499 | #else |
500 | return QList<VideoCaptureDevice>(); |
501 | #endif |
502 | } |
503 | |
504 | void DevicePreference::load() |
505 | { |
506 | showAdvancedDevicesCheckBox->setChecked(!GlobalConfig().hideAdvancedDevices()); |
507 | loadCategoryDevices(); |
508 | } |
509 | |
510 | void 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 | |
553 | void 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 | |
581 | void 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 | |
612 | void DevicePreference::pulseAudioEnabled() |
613 | { |
614 | showAdvancedDevicesContainer->removeItem(showAdvancedDevicesSpacer); |
615 | delete showAdvancedDevicesSpacer; |
616 | showAdvancedDevicesCheckBox->setVisible(false); |
617 | } |
618 | |
619 | void 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 | |
648 | void 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 | |
677 | DevicePreference::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 | |
700 | void 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 | |
829 | void 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 | |
837 | void 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 | |
973 | void 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 | |