PulseAudio Qt Bindings

models.cpp
1 /*
2  SPDX-FileCopyrightText: 2014-2015 Harald Sitter <[email protected]>
3  SPDX-FileCopyrightText: 2016 David Rosca <[email protected]>
4 
5  SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
6 */
7 
8 #include "models.h"
9 
10 #include "card.h"
11 #include "context.h"
12 #include "context_p.h"
13 #include "debug.h"
14 #include "maps.h"
15 #include "module.h"
16 #include "server.h"
17 #include "sink.h"
18 #include "sinkinput.h"
19 #include "source.h"
20 #include "sourceoutput.h"
21 #include "streamrestore.h"
22 
23 #include "models_p.h"
24 #include <QMetaEnum>
25 
26 namespace PulseAudioQt
27 {
28 AbstractModel::AbstractModel(const MapBaseQObject *map, QObject *parent)
29  : QAbstractListModel(parent)
30  , d(new AbstractModelPrivate(this, map))
31 {
32  connect(d->m_map, &MapBaseQObject::aboutToBeAdded, this, [this](int index) {
33  beginInsertRows(QModelIndex(), index, index);
34  });
35  connect(d->m_map, &MapBaseQObject::added, this, [this](int index) {
36  onDataAdded(index);
37  endInsertRows();
38  Q_EMIT countChanged();
39  });
40  connect(d->m_map, &MapBaseQObject::aboutToBeRemoved, this, [this](int index) {
41  beginRemoveRows(QModelIndex(), index, index);
42  });
43  connect(d->m_map, &MapBaseQObject::removed, this, [this](int index) {
44  Q_UNUSED(index);
45  endRemoveRows();
46  Q_EMIT countChanged();
47  });
48 }
49 
50 AbstractModel::~AbstractModel()
51 {
52  delete d;
53 }
54 
55 AbstractModelPrivate::AbstractModelPrivate(AbstractModel *q, const MapBaseQObject *map)
56  : q(q)
57  , m_map(map)
58 {
59 }
60 
61 AbstractModelPrivate::~AbstractModelPrivate()
62 {
63 }
64 
65 QHash<int, QByteArray> AbstractModel::roleNames() const
66 {
67  if (!d->m_roles.empty()) {
68  qCDebug(PULSEAUDIOQT) << "returning roles" << d->m_roles;
69  return d->m_roles;
70  }
71  Q_UNREACHABLE();
72  return QHash<int, QByteArray>();
73 }
74 
75 int AbstractModel::rowCount(const QModelIndex &parent) const
76 {
77  if (parent.isValid()) {
78  return 0;
79  }
80  return d->m_map->count();
81 }
82 
83 QVariant AbstractModel::data(const QModelIndex &index, int role) const
84 {
85  if (!hasIndex(index.row(), index.column())) {
86  return QVariant();
87  }
88  QObject *data = d->m_map->objectAt(index.row());
89  Q_ASSERT(data);
90  if (role == PulseObjectRole) {
91  return QVariant::fromValue(data);
92  } else if (role == Qt::DisplayRole) {
93  return static_cast<PulseObject *>(data)->name();
94  }
95  int property = d->m_objectProperties.value(role, -1);
96  if (property == -1) {
97  return QVariant();
98  }
99  return data->metaObject()->property(property).read(data);
100 }
101 
102 bool AbstractModel::setData(const QModelIndex &index, const QVariant &value, int role)
103 {
104  if (!hasIndex(index.row(), index.column())) {
105  return false;
106  }
107  int propertyIndex = d->m_objectProperties.value(role, -1);
108  if (propertyIndex == -1) {
109  return false;
110  }
111  QObject *data = d->m_map->objectAt(index.row());
112  auto property = data->metaObject()->property(propertyIndex);
113  return property.write(data, value);
114 }
115 
116 int AbstractModel::role(const QByteArray &roleName) const
117 {
118  qCDebug(PULSEAUDIOQT) << roleName << d->m_roles.key(roleName, -1);
119  return d->m_roles.key(roleName, -1);
120 }
121 
122 Context *AbstractModel::context() const
123 {
124  return Context::instance();
125 }
126 
127 void AbstractModel::initRoleNames(const QMetaObject &qobjectMetaObject)
128 {
129  d->m_roles[PulseObjectRole] = QByteArrayLiteral("PulseObject");
130 
131  QMetaEnum enumerator;
132  for (int i = 0; i < metaObject()->enumeratorCount(); ++i) {
133  if (metaObject()->enumerator(i).name() == QLatin1String("ItemRole")) {
134  enumerator = metaObject()->enumerator(i);
135  break;
136  }
137  }
138 
139  for (int i = 0; i < enumerator.keyCount(); ++i) {
140  // Clip the Role suffix and glue it in the hash.
141  const int roleLength = 4;
142  QByteArray key(enumerator.key(i));
143  // Enum values must end in Role or the enum is crap
144  Q_ASSERT(key.right(roleLength) == QByteArrayLiteral("Role"));
145  key.chop(roleLength);
146  d->m_roles[enumerator.value(i)] = key;
147  }
148 
149  int maxEnumValue = -1;
150  for (auto it = d->m_roles.constBegin(); it != d->m_roles.constEnd(); ++it) {
151  if (it.key() > maxEnumValue) {
152  maxEnumValue = it.key();
153  }
154  }
155  Q_ASSERT(maxEnumValue != -1);
156  auto mo = qobjectMetaObject;
157  for (int i = 0; i < mo.propertyCount(); ++i) {
158  QMetaProperty property = mo.property(i);
159  QString name(property.name());
160  name.replace(0, 1, name.at(0).toUpper());
161  d->m_roles[++maxEnumValue] = name.toLatin1();
162  d->m_objectProperties.insert(maxEnumValue, i);
163  if (!property.hasNotifySignal()) {
164  continue;
165  }
166  d->m_signalIndexToProperties.insert(property.notifySignalIndex(), i);
167  }
168  qCDebug(PULSEAUDIOQT) << d->m_roles;
169 
170  // Connect to property changes also with objects already in model
171  for (int i = 0; i < d->m_map->count(); ++i) {
172  onDataAdded(i);
173  }
174 }
175 
176 void AbstractModel::propertyChanged()
177 {
178  if (!sender() || senderSignalIndex() == -1) {
179  return;
180  }
181  int propertyIndex = d->m_signalIndexToProperties.value(senderSignalIndex(), -1);
182  if (propertyIndex == -1) {
183  return;
184  }
185  int role = d->m_objectProperties.key(propertyIndex, -1);
186  if (role == -1) {
187  return;
188  }
189  int index = d->m_map->indexOfObject(sender());
190  qCDebug(PULSEAUDIOQT) << "PROPERTY CHANGED (" << index << ") :: " << role << roleNames().value(role);
191  Q_EMIT dataChanged(createIndex(index, 0), createIndex(index, 0), {role});
192 }
193 
194 void AbstractModel::onDataAdded(int index)
195 {
196  QObject *data = d->m_map->objectAt(index);
197  const QMetaObject *mo = data->metaObject();
198  // We have all the data changed notify signals already stored
199  auto keys = d->m_signalIndexToProperties.keys();
200  foreach (int index, keys) {
201  QMetaMethod meth = mo->method(index);
202  connect(data, meth, this, propertyChangedMetaMethod());
203  }
204 }
205 
206 QMetaMethod AbstractModel::propertyChangedMetaMethod() const
207 {
208  auto mo = metaObject();
209  int methodIndex = mo->indexOfMethod("propertyChanged()");
210  if (methodIndex == -1) {
211  return QMetaMethod();
212  }
213  return mo->method(methodIndex);
214 }
215 
216 SinkModel::SinkModel(QObject *parent)
217  : AbstractModel(&context()->d->m_sinks, parent)
218  , d(new SinkModelPrivate(this))
219 {
220  initRoleNames(Sink::staticMetaObject);
221 
222  for (int i = 0; i < context()->d->m_sinks.count(); ++i) {
223  sinkAdded(i);
224  }
225 
226  connect(&context()->d->m_sinks, &MapBaseQObject::added, this, &SinkModel::sinkAdded);
227  connect(&context()->d->m_sinks, &MapBaseQObject::removed, this, &SinkModel::sinkRemoved);
228 
229  connect(context()->server(), &Server::defaultSinkChanged, this, [this]() {
230  updatePreferredSink();
231  Q_EMIT defaultSinkChanged();
232  });
233 }
234 
235 SinkModel::~SinkModel()
236 {
237  delete d;
238 }
239 
240 SinkModelPrivate::SinkModelPrivate(SinkModel *q)
241  : q(q)
242  , m_preferredSink(nullptr)
243 {
244 }
245 
246 SinkModelPrivate::~SinkModelPrivate()
247 {
248 }
249 
250 Sink *SinkModel::defaultSink() const
251 {
252  return context()->server()->defaultSink();
253 }
254 
255 Sink *SinkModel::preferredSink() const
256 {
257  return d->m_preferredSink;
258 }
259 
260 QVariant SinkModel::data(const QModelIndex &index, int role) const
261 {
262  if (role == SortByDefaultRole) {
263  // Workaround QTBUG-1548
264  const QString pulseIndex = data(index, AbstractModel::role(QByteArrayLiteral("Index"))).toString();
265  const QString defaultDevice = data(index, AbstractModel::role(QByteArrayLiteral("Default"))).toString();
266  return defaultDevice + pulseIndex;
267  }
268  return AbstractModel::data(index, role);
269 }
270 
271 void SinkModel::sinkAdded(int index)
272 {
273  Q_ASSERT(qobject_cast<Sink *>(context()->d->m_sinks.objectAt(index)));
274  Sink *sink = static_cast<Sink *>(context()->d->m_sinks.objectAt(index));
275  connect(sink, &Sink::stateChanged, this, &SinkModel::updatePreferredSink);
276 
277  updatePreferredSink();
278 }
279 
280 void SinkModel::sinkRemoved(int index)
281 {
282  Q_UNUSED(index);
283 
284  updatePreferredSink();
285 }
286 
287 void SinkModel::updatePreferredSink()
288 {
289  Sink *sink = findPreferredSink();
290 
291  if (sink != d->m_preferredSink) {
292  qCDebug(PULSEAUDIOQT) << "Changing preferred sink to" << sink << (sink ? sink->name() : "");
293  d->m_preferredSink = sink;
294  Q_EMIT preferredSinkChanged();
295  }
296 }
297 
298 Sink *SinkModel::findPreferredSink() const
299 {
300  const auto &sinks = context()->d->m_sinks;
301 
302  // Only one sink is the preferred one
303  if (sinks.count() == 1) {
304  return static_cast<Sink *>(sinks.objectAt(0));
305  }
306 
307  auto lookForState = [&](Device::State state) {
308  Sink *ret = nullptr;
309  const auto data = sinks.data();
310  for (Sink *sink : data) {
311  if (sink->state() != state) {
312  continue;
313  }
314  if (!ret) {
315  ret = sink;
316  } else if (sink == defaultSink()) {
317  ret = sink;
318  break;
319  }
320  }
321  return ret;
322  };
323 
324  Sink *preferred = nullptr;
325 
326  // Look for playing sinks + prefer default sink
327  preferred = lookForState(Device::RunningState);
328  if (preferred) {
329  return preferred;
330  }
331 
332  // Look for idle sinks + prefer default sink
333  preferred = lookForState(Device::IdleState);
334  if (preferred) {
335  return preferred;
336  }
337 
338  // Fallback to default sink
339  return defaultSink();
340 }
341 
342 SourceModel::SourceModel(QObject *parent)
343  : AbstractModel(&context()->d->m_sources, parent)
344 {
345  initRoleNames(Source::staticMetaObject);
346 
347  connect(context()->server(), &Server::defaultSourceChanged, this, &SourceModel::defaultSourceChanged);
348 }
349 
350 Source *SourceModel::defaultSource() const
351 {
352  return context()->server()->defaultSource();
353 }
354 
355 QVariant SourceModel::data(const QModelIndex &index, int role) const
356 {
357  if (role == SortByDefaultRole) {
358  // Workaround QTBUG-1548
359  const QString pulseIndex = data(index, AbstractModel::role(QByteArrayLiteral("Index"))).toString();
360  const QString defaultDevice = data(index, AbstractModel::role(QByteArrayLiteral("Default"))).toString();
361  return defaultDevice + pulseIndex;
362  }
363  return AbstractModel::data(index, role);
364 }
365 
366 SinkInputModel::SinkInputModel(QObject *parent)
367  : AbstractModel(&context()->d->m_sinkInputs, parent)
368 {
369  initRoleNames(SinkInput::staticMetaObject);
370 }
371 
372 SourceOutputModel::SourceOutputModel(QObject *parent)
373  : AbstractModel(&context()->d->m_sourceOutputs, parent)
374 {
375  initRoleNames(SourceOutput::staticMetaObject);
376 }
377 
378 CardModel::CardModel(QObject *parent)
379  : AbstractModel(&context()->d->m_cards, parent)
380 {
381  initRoleNames(Card::staticMetaObject);
382 }
383 
384 StreamRestoreModel::StreamRestoreModel(QObject *parent)
385  : AbstractModel(&context()->d->m_streamRestores, parent)
386 {
387  initRoleNames(StreamRestore::staticMetaObject);
388 }
389 
390 ModuleModel::ModuleModel(QObject *parent)
391  : AbstractModel(&context()->d->m_modules, parent)
392 {
393  initRoleNames(Module::staticMetaObject);
394 }
395 
396 } // PulseAudioQt
int value(int index) const const
DisplayRole
QVariant fromValue(const T &value)
Q_EMITQ_EMIT
int column() const const
T value() const const
QChar toUpper() const const
bool write(QObject *object, const QVariant &value) const const
QByteArray toLatin1() const const
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
The primary namespace of PulseAudioQt.
Definition: card.cpp:16
@ IdleState
When idle, the sink/source is playing/recording but there is no non-corked sink-input/source-output a...
Definition: device.h:41
@ RunningState
Running, sink/source is playing/recording and used by at least one non-corked sink-input/source-outpu...
Definition: device.h:39
QMetaMethod method(int index) const const
int indexOfMethod(const char *method) const const
QVariant read(const QObject *object) const const
bool isValid() const const
virtual const QMetaObject * metaObject() const const
QString & replace(int position, int n, QChar after)
int row() const const
int keyCount() const const
QMetaProperty property(int index) const const
QString name(StandardShortcut id)
const QChar at(int position) const const
const char * key(int index) const const
QFuture< void > map(Sequence &sequence, MapFunctor function)
QByteArray & insert(int i, char ch)
This file is part of the KDE documentation.
Documentation copyright © 1996-2022 The KDE developers.
Generated on Sat Jun 25 2022 06:12:40 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.