File indexing completed on 2024-05-12 09:39:13

0001 /*
0002     SPDX-FileCopyrightText: 2014-2015 Harald Sitter <sitter@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
0005 */
0006 
0007 #ifndef MAPS_H
0008 #define MAPS_H
0009 
0010 #include "debug.h"
0011 #include <QMap>
0012 #include <QObject>
0013 
0014 #include <pulse/ext-stream-restore.h>
0015 #include <pulse/pulseaudio.h>
0016 
0017 #include <iterator>
0018 
0019 namespace QPulseAudio
0020 {
0021 // Used for typedefs.
0022 class Card;
0023 class Client;
0024 class Sink;
0025 class SinkInput;
0026 class Source;
0027 class SourceOutput;
0028 class StreamRestore;
0029 class Module;
0030 
0031 /**
0032  * @see MapBase
0033  * This class is nothing more than the QObject base since moc cannot handle
0034  * templates.
0035  */
0036 class MapBaseQObject : public QObject
0037 {
0038     Q_OBJECT
0039 
0040 public:
0041     virtual int count() const = 0;
0042     virtual QObject *objectAt(int index) const = 0;
0043     virtual int indexOfObject(QObject *object) const = 0;
0044 
0045 Q_SIGNALS:
0046     void aboutToBeAdded(int index);
0047     void added(int index);
0048     void aboutToBeRemoved(int index);
0049     void removed(int index);
0050 };
0051 
0052 /**
0053  * Maps a specific index to a specific object pointer.
0054  * This is used to give the unique arbitrary PulseAudio index of a PulseObject a
0055  * serialized list index. Namely it enables us to translate a discrete list
0056  * index to a pulse index to an object, and any permutation thereof.
0057  */
0058 template<typename Type, typename PAInfo>
0059 class MapBase : public MapBaseQObject
0060 {
0061 public:
0062     ~MapBase() override = default;
0063 
0064     const QMap<quint32, Type *> &data() const
0065     {
0066         return m_data;
0067     }
0068 
0069     int count() const override
0070     {
0071         return m_data.count();
0072     }
0073 
0074     int indexOfObject(QObject *object) const override
0075     {
0076         int index = 0;
0077         QMapIterator<quint32, Type *> it(m_data);
0078         while (it.hasNext()) {
0079             it.next();
0080             if (it.value() == object) {
0081                 return index;
0082             }
0083             index++;
0084         }
0085         return -1;
0086     }
0087 
0088     QObject *objectAt(int index) const override
0089     {
0090         return std::next(m_data.constBegin(), index).value();
0091     }
0092 
0093     void reset()
0094     {
0095         while (!m_data.isEmpty()) {
0096             removeEntry(m_data.lastKey());
0097         }
0098         m_pendingRemovals.clear();
0099     }
0100 
0101     void insert(Type *object)
0102     {
0103         Q_ASSERT(!m_data.contains(object->index()));
0104 
0105         int modelIndex = 0;
0106         for (auto it = m_data.constBegin(); it != m_data.constEnd(); ++it) {
0107             if (object->index() < it.key()) {
0108                 break;
0109             }
0110             modelIndex++;
0111         }
0112 
0113         Q_EMIT aboutToBeAdded(modelIndex);
0114         m_data.insert(object->index(), object);
0115         Q_ASSERT(modelIndex == m_data.keys().indexOf(object->index()));
0116         Q_EMIT added(modelIndex);
0117     }
0118 
0119     // Context is passed in as parent because context needs to include the maps
0120     // so we'd cause a circular dep if we were to try to use the instance here.
0121     // Plus that's weird separation anyway.
0122     void updateEntry(const PAInfo *info, QObject *parent)
0123     {
0124         Q_ASSERT(info);
0125 
0126         if (m_pendingRemovals.remove(info->index)) {
0127             // Was already removed again.
0128             return;
0129         }
0130 
0131         auto *obj = m_data.value(info->index, nullptr);
0132         if (!obj) {
0133             obj = new Type(parent);
0134         }
0135         obj->update(info);
0136 
0137         if (!m_data.contains(info->index)) {
0138             insert(obj);
0139         }
0140     }
0141 
0142     void removeEntry(quint32 index)
0143     {
0144         if (!m_data.contains(index)) {
0145             m_pendingRemovals.insert(index);
0146         } else {
0147             const int modelIndex = m_data.keys().indexOf(index);
0148             Q_EMIT aboutToBeRemoved(modelIndex);
0149             delete m_data.take(index);
0150             Q_EMIT removed(modelIndex);
0151         }
0152     }
0153 
0154 protected:
0155     QMap<quint32, Type *> m_data;
0156     QSet<quint32> m_pendingRemovals;
0157 };
0158 
0159 using SinkMap = MapBase<Sink, pa_sink_info>;
0160 using SinkInputMap = MapBase<SinkInput, pa_sink_input_info>;
0161 using SourceMap = MapBase<Source, pa_source_info>;
0162 using SourceOutputMap = MapBase<SourceOutput, pa_source_output_info>;
0163 using ClientMap = MapBase<Client, pa_client_info>;
0164 using CardMap = MapBase<Card, pa_card_info>;
0165 using ModuleMap = MapBase<Module, pa_module_info>;
0166 using StreamRestoreMap = MapBase<StreamRestore, pa_ext_stream_restore_info>;
0167 
0168 } // QPulseAudio
0169 
0170 #endif // MAPS_H