File indexing completed on 2024-04-21 16:20:31

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