summaryrefslogtreecommitdiffstats
path: root/src/PulseAudioQt/models.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/PulseAudioQt/models.cpp')
-rw-r--r--src/PulseAudioQt/models.cpp396
1 files changed, 396 insertions, 0 deletions
diff --git a/src/PulseAudioQt/models.cpp b/src/PulseAudioQt/models.cpp
new file mode 100644
index 0000000..9fc3f32
--- /dev/null
+++ b/src/PulseAudioQt/models.cpp
@@ -0,0 +1,396 @@
+/*
+ SPDX-FileCopyrightText: 2014-2015 Harald Sitter <sitter@kde.org>
+ SPDX-FileCopyrightText: 2016 David Rosca <nowrep@gmail.com>
+
+ SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
+*/
+
+#include "models.h"
+
+#include "card.h"
+#include "context.h"
+#include "context_p.h"
+#include "debug.h"
+#include "maps.h"
+#include "module.h"
+#include "server.h"
+#include "sink.h"
+#include "sinkinput.h"
+#include "source.h"
+#include "sourceoutput.h"
+#include "streamrestore.h"
+
+#include "models_p.h"
+#include <QMetaEnum>
+
+namespace PulseAudioQt
+{
+AbstractModel::AbstractModel(const MapBaseQObject *map, QObject *parent)
+ : QAbstractListModel(parent)
+ , d(new AbstractModelPrivate(this, map))
+{
+ connect(d->m_map, &MapBaseQObject::aboutToBeAdded, this, [this](int index) {
+ beginInsertRows(QModelIndex(), index, index);
+ });
+ connect(d->m_map, &MapBaseQObject::added, this, [this](int index) {
+ onDataAdded(index);
+ endInsertRows();
+ Q_EMIT countChanged();
+ });
+ connect(d->m_map, &MapBaseQObject::aboutToBeRemoved, this, [this](int index) {
+ beginRemoveRows(QModelIndex(), index, index);
+ });
+ connect(d->m_map, &MapBaseQObject::removed, this, [this](int index) {
+ Q_UNUSED(index);
+ endRemoveRows();
+ Q_EMIT countChanged();
+ });
+}
+
+AbstractModel::~AbstractModel()
+{
+ delete d;
+}
+
+AbstractModelPrivate::AbstractModelPrivate(AbstractModel *q, const MapBaseQObject *map)
+ : q(q)
+ , m_map(map)
+{
+}
+
+AbstractModelPrivate::~AbstractModelPrivate()
+{
+}
+
+QHash<int, QByteArray> AbstractModel::roleNames() const
+{
+ if (!d->m_roles.empty()) {
+ qDebug() << "returning roles" << d->m_roles;
+ return d->m_roles;
+ }
+ Q_UNREACHABLE();
+ return QHash<int, QByteArray>();
+}
+
+int AbstractModel::rowCount(const QModelIndex &parent) const
+{
+ if (parent.isValid()) {
+ return 0;
+ }
+ return d->m_map->count();
+}
+
+QVariant AbstractModel::data(const QModelIndex &index, int role) const
+{
+ if (!hasIndex(index.row(), index.column())) {
+ return QVariant();
+ }
+ QObject *data = d->m_map->objectAt(index.row());
+ Q_ASSERT(data);
+ if (role == PulseObjectRole) {
+ return QVariant::fromValue(data);
+ } else if (role == Qt::DisplayRole) {
+ return static_cast<PulseObject *>(data)->name();
+ }
+ int property = d->m_objectProperties.value(role, -1);
+ if (property == -1) {
+ return QVariant();
+ }
+ return data->metaObject()->property(property).read(data);
+}
+
+bool AbstractModel::setData(const QModelIndex &index, const QVariant &value, int role)
+{
+ if (!hasIndex(index.row(), index.column())) {
+ return false;
+ }
+ int propertyIndex = d->m_objectProperties.value(role, -1);
+ if (propertyIndex == -1) {
+ return false;
+ }
+ QObject *data = d->m_map->objectAt(index.row());
+ auto property = data->metaObject()->property(propertyIndex);
+ return property.write(data, value);
+}
+
+int AbstractModel::role(const QByteArray &roleName) const
+{
+ qDebug() << roleName << d->m_roles.key(roleName, -1);
+ return d->m_roles.key(roleName, -1);
+}
+
+Context *AbstractModel::context() const
+{
+ return Context::instance();
+}
+
+void AbstractModel::initRoleNames(const QMetaObject &qobjectMetaObject)
+{
+ d->m_roles[PulseObjectRole] = QByteArrayLiteral("PulseObject");
+
+ QMetaEnum enumerator;
+ for (int i = 0; i < metaObject()->enumeratorCount(); ++i) {
+ if (metaObject()->enumerator(i).name() == QLatin1String("ItemRole")) {
+ enumerator = metaObject()->enumerator(i);
+ break;
+ }
+ }
+
+ for (int i = 0; i < enumerator.keyCount(); ++i) {
+ // Clip the Role suffix and glue it in the hash.
+ const int roleLength = 4;
+ QByteArray key(enumerator.key(i));
+ // Enum values must end in Role or the enum is crap
+ Q_ASSERT(key.right(roleLength) == QByteArrayLiteral("Role"));
+ key.chop(roleLength);
+ d->m_roles[enumerator.value(i)] = key;
+ }
+
+ int maxEnumValue = -1;
+ for (auto it = d->m_roles.constBegin(); it != d->m_roles.constEnd(); ++it) {
+ if (it.key() > maxEnumValue) {
+ maxEnumValue = it.key();
+ }
+ }
+ Q_ASSERT(maxEnumValue != -1);
+ auto mo = qobjectMetaObject;
+ for (int i = 0; i < mo.propertyCount(); ++i) {
+ QMetaProperty property = mo.property(i);
+ QString name(property.name());
+ name.replace(0, 1, name.at(0).toUpper());
+ d->m_roles[++maxEnumValue] = name.toLatin1();
+ d->m_objectProperties.insert(maxEnumValue, i);
+ if (!property.hasNotifySignal()) {
+ continue;
+ }
+ d->m_signalIndexToProperties.insert(property.notifySignalIndex(), i);
+ }
+ qDebug() << d->m_roles;
+
+ // Connect to property changes also with objects already in model
+ for (int i = 0; i < d->m_map->count(); ++i) {
+ onDataAdded(i);
+ }
+}
+
+void AbstractModel::propertyChanged()
+{
+ if (!sender() || senderSignalIndex() == -1) {
+ return;
+ }
+ int propertyIndex = d->m_signalIndexToProperties.value(senderSignalIndex(), -1);
+ if (propertyIndex == -1) {
+ return;
+ }
+ int role = d->m_objectProperties.key(propertyIndex, -1);
+ if (role == -1) {
+ return;
+ }
+ int index = d->m_map->indexOfObject(sender());
+ qDebug() << "PROPERTY CHANGED (" << index << ") :: " << role << roleNames().value(role);
+ Q_EMIT dataChanged(createIndex(index, 0), createIndex(index, 0), {role});
+}
+
+void AbstractModel::onDataAdded(int index)
+{
+ QObject *data = d->m_map->objectAt(index);
+ const QMetaObject *mo = data->metaObject();
+ // We have all the data changed notify signals already stored
+ auto keys = d->m_signalIndexToProperties.keys();
+ foreach (int index, keys) {
+ QMetaMethod meth = mo->method(index);
+ connect(data, meth, this, propertyChangedMetaMethod());
+ }
+}
+
+QMetaMethod AbstractModel::propertyChangedMetaMethod() const
+{
+ auto mo = metaObject();
+ int methodIndex = mo->indexOfMethod("propertyChanged()");
+ if (methodIndex == -1) {
+ return QMetaMethod();
+ }
+ return mo->method(methodIndex);
+}
+
+SinkModel::SinkModel(QObject *parent)
+ : AbstractModel(&context()->d->m_sinks, parent)
+ , d(new SinkModelPrivate(this))
+{
+ initRoleNames(Sink::staticMetaObject);
+
+ for (int i = 0; i < context()->d->m_sinks.count(); ++i) {
+ sinkAdded(i);
+ }
+
+ connect(&context()->d->m_sinks, &MapBaseQObject::added, this, &SinkModel::sinkAdded);
+ connect(&context()->d->m_sinks, &MapBaseQObject::removed, this, &SinkModel::sinkRemoved);
+
+ connect(context()->server(), &Server::defaultSinkChanged, this, [this]() {
+ updatePreferredSink();
+ Q_EMIT defaultSinkChanged();
+ });
+}
+
+SinkModel::~SinkModel()
+{
+ delete d;
+}
+
+SinkModelPrivate::SinkModelPrivate(SinkModel *q)
+ : q(q)
+ , m_preferredSink(nullptr)
+{
+}
+
+SinkModelPrivate::~SinkModelPrivate()
+{
+}
+
+Sink *SinkModel::defaultSink() const
+{
+ return context()->server()->defaultSink();
+}
+
+Sink *SinkModel::preferredSink() const
+{
+ return d->m_preferredSink;
+}
+
+QVariant SinkModel::data(const QModelIndex &index, int role) const
+{
+ if (role == SortByDefaultRole) {
+ // Workaround QTBUG-1548
+ const QString pulseIndex = data(index, AbstractModel::role(QByteArrayLiteral("Index"))).toString();
+ const QString defaultDevice = data(index, AbstractModel::role(QByteArrayLiteral("Default"))).toString();
+ return defaultDevice + pulseIndex;
+ }
+ return AbstractModel::data(index, role);
+}
+
+void SinkModel::sinkAdded(int index)
+{
+ Q_ASSERT(qobject_cast<Sink *>(context()->d->m_sinks.objectAt(index)));
+ Sink *sink = static_cast<Sink *>(context()->d->m_sinks.objectAt(index));
+ connect(sink, &Sink::stateChanged, this, &SinkModel::updatePreferredSink);
+
+ updatePreferredSink();
+}
+
+void SinkModel::sinkRemoved(int index)
+{
+ Q_UNUSED(index);
+
+ updatePreferredSink();
+}
+
+void SinkModel::updatePreferredSink()
+{
+ Sink *sink = findPreferredSink();
+
+ if (sink != d->m_preferredSink) {
+ qDebug() << "Changing preferred sink to" << sink << (sink ? sink->name() : "");
+ d->m_preferredSink = sink;
+ Q_EMIT preferredSinkChanged();
+ }
+}
+
+Sink *SinkModel::findPreferredSink() const
+{
+ const auto &sinks = context()->d->m_sinks;
+
+ // Only one sink is the preferred one
+ if (sinks.count() == 1) {
+ return static_cast<Sink *>(sinks.objectAt(0));
+ }
+
+ auto lookForState = [&](Device::State state) {
+ Sink *ret = nullptr;
+ const auto data = sinks.data();
+ for (Sink *sink : data) {
+ if (sink->state() != state) {
+ continue;
+ }
+ if (!ret) {
+ ret = sink;
+ } else if (sink == defaultSink()) {
+ ret = sink;
+ break;
+ }
+ }
+ return ret;
+ };
+
+ Sink *preferred = nullptr;
+
+ // Look for playing sinks + prefer default sink
+ preferred = lookForState(Device::RunningState);
+ if (preferred) {
+ return preferred;
+ }
+
+ // Look for idle sinks + prefer default sink
+ preferred = lookForState(Device::IdleState);
+ if (preferred) {
+ return preferred;
+ }
+
+ // Fallback to default sink
+ return defaultSink();
+}
+
+SourceModel::SourceModel(QObject *parent)
+ : AbstractModel(&context()->d->m_sources, parent)
+{
+ initRoleNames(Source::staticMetaObject);
+
+ connect(context()->server(), &Server::defaultSourceChanged, this, &SourceModel::defaultSourceChanged);
+}
+
+Source *SourceModel::defaultSource() const
+{
+ return context()->server()->defaultSource();
+}
+
+QVariant SourceModel::data(const QModelIndex &index, int role) const
+{
+ if (role == SortByDefaultRole) {
+ // Workaround QTBUG-1548
+ const QString pulseIndex = data(index, AbstractModel::role(QByteArrayLiteral("Index"))).toString();
+ const QString defaultDevice = data(index, AbstractModel::role(QByteArrayLiteral("Default"))).toString();
+ return defaultDevice + pulseIndex;
+ }
+ return AbstractModel::data(index, role);
+}
+
+SinkInputModel::SinkInputModel(QObject *parent)
+ : AbstractModel(&context()->d->m_sinkInputs, parent)
+{
+ initRoleNames(SinkInput::staticMetaObject);
+}
+
+SourceOutputModel::SourceOutputModel(QObject *parent)
+ : AbstractModel(&context()->d->m_sourceOutputs, parent)
+{
+ initRoleNames(SourceOutput::staticMetaObject);
+}
+
+CardModel::CardModel(QObject *parent)
+ : AbstractModel(&context()->d->m_cards, parent)
+{
+ initRoleNames(Card::staticMetaObject);
+}
+
+StreamRestoreModel::StreamRestoreModel(QObject *parent)
+ : AbstractModel(&context()->d->m_streamRestores, parent)
+{
+ initRoleNames(StreamRestore::staticMetaObject);
+}
+
+ModuleModel::ModuleModel(QObject *parent)
+ : AbstractModel(&context()->d->m_modules, parent)
+{
+ initRoleNames(Module::staticMetaObject);
+}
+
+} // PulseAudioQt