all: replace list properties with ObjectModels

This commit is contained in:
outfoxxed 2024-05-23 17:28:07 -07:00
parent 6326f60ce2
commit 5016dbf0d4
Signed by: outfoxxed
GPG key ID: 4C88A185FB89301E
11 changed files with 201 additions and 152 deletions

View file

@ -26,6 +26,7 @@ qt_add_library(quickshell-core STATIC
imageprovider.cpp
transformwatcher.cpp
boundcomponent.cpp
model.cpp
)
set_source_files_properties(main.cpp PROPERTIES COMPILE_DEFINITIONS GIT_REVISION="${GIT_REVISION}")

View file

@ -10,5 +10,8 @@
#define QSDOC_ELEMENT
#define QSDOC_NAMED_ELEMENT(name)
// change the cname used for this type
#define QSDOC_CNAME(name)
// overridden properties
#define QSDOC_PROPERTY_OVERRIDE(...)

67
src/core/model.cpp Normal file
View file

@ -0,0 +1,67 @@
#include "model.hpp"
#include <qabstractitemmodel.h>
#include <qhash.h>
#include <qobject.h>
#include <qqmllist.h>
#include <qtmetamacros.h>
#include <qtypes.h>
#include <qvariant.h>
qint32 UntypedObjectModel::rowCount(const QModelIndex& parent) const {
if (parent != QModelIndex()) return 0;
return static_cast<qint32>(this->valuesList.length());
}
QVariant UntypedObjectModel::data(const QModelIndex& index, qint32 role) const {
if (role != 0) return QVariant();
return QVariant::fromValue(this->valuesList.at(index.row()));
}
QHash<int, QByteArray> UntypedObjectModel::roleNames() const { return {{0, "modelData"}}; }
QQmlListProperty<QObject> UntypedObjectModel::values() {
return QQmlListProperty<QObject>(
this,
nullptr,
&UntypedObjectModel::valuesCount,
&UntypedObjectModel::valueAt
);
}
qsizetype UntypedObjectModel::valuesCount(QQmlListProperty<QObject>* property) {
return static_cast<UntypedObjectModel*>(property->object)->valuesList.count(); // NOLINT
}
QObject* UntypedObjectModel::valueAt(QQmlListProperty<QObject>* property, qsizetype index) {
return static_cast<UntypedObjectModel*>(property->object)->valuesList.at(index); // NOLINT
}
void UntypedObjectModel::insertObject(QObject* object, qsizetype index) {
auto iindex = index == -1 ? this->valuesList.length() : index;
auto intIndex = static_cast<qint32>(iindex);
this->beginInsertRows(QModelIndex(), intIndex, intIndex);
this->valuesList.insert(iindex, object);
this->endInsertRows();
emit this->valuesChanged();
}
void UntypedObjectModel::removeAt(qsizetype index) {
auto intIndex = static_cast<qint32>(index);
this->beginRemoveRows(QModelIndex(), intIndex, intIndex);
this->valuesList.removeAt(index);
this->endRemoveRows();
emit this->valuesChanged();
}
bool UntypedObjectModel::removeObject(const QObject* object) {
auto index = this->valuesList.indexOf(object);
if (index == -1) return false;
this->removeAt(index);
return true;
}
qsizetype UntypedObjectModel::indexOf(QObject* object) { return this->valuesList.indexOf(object); }

86
src/core/model.hpp Normal file
View file

@ -0,0 +1,86 @@
#pragma once
#include <qabstractitemmodel.h>
#include <qcontainerfwd.h>
#include <qobject.h>
#include <qqmlintegration.h>
#include <qqmllist.h>
#include <qtmetamacros.h>
#include <qtypes.h>
#include <qvariant.h>
#include "doc.hpp"
///! View into a list of objets
/// Typed view into a list of objects.
///
/// An ObjectModel works as a QML [Data Model], allowing efficient interaction with
/// components that act on models. It has a single role named `modelData`, to match the
/// behavior of lists.
/// The same information contained in the list model is available as a normal list
/// via the `values` property.
///
/// #### Differences from a list
/// Unlike with a list, the following property binding will never be updated when `model[3]` changes.
/// ```qml
/// // will not update reactively
/// property var foo: model[3]
/// ```
///
/// You can work around this limitation using the `values` property of the model to view it as a list.
/// ```qml
/// // will update reactively
/// property var foo: model.values[3]
/// ```
///
/// [Data Model]: https://doc.qt.io/qt-6/qtquick-modelviewsdata-modelview.html#qml-data-models
class UntypedObjectModel: public QAbstractListModel {
QSDOC_CNAME(ObjectModel);
Q_OBJECT;
/// The content of the object model, as a QML list.
/// The values of this property will always be of the type of the model.
Q_PROPERTY(QQmlListProperty<QObject> values READ values NOTIFY valuesChanged);
QML_NAMED_ELEMENT(ObjectModel);
QML_UNCREATABLE("ObjectModels cannot be created directly.");
public:
explicit UntypedObjectModel(QObject* parent): QAbstractListModel(parent) {}
[[nodiscard]] qint32 rowCount(const QModelIndex& parent) const override;
[[nodiscard]] QVariant data(const QModelIndex& index, qint32 role) const override;
[[nodiscard]] QHash<int, QByteArray> roleNames() const override;
[[nodiscard]] QQmlListProperty<QObject> values();
void removeAt(qsizetype index);
Q_INVOKABLE qsizetype indexOf(QObject* object);
signals:
void valuesChanged();
protected:
void insertObject(QObject* object, qsizetype index = -1);
bool removeObject(const QObject* object);
QVector<QObject*> valuesList;
private:
static qsizetype valuesCount(QQmlListProperty<QObject>* property);
static QObject* valueAt(QQmlListProperty<QObject>* property, qsizetype index);
};
template <typename T>
class ObjectModel: public UntypedObjectModel {
public:
explicit ObjectModel(QObject* parent): UntypedObjectModel(parent) {}
[[nodiscard]] const QVector<T*>& valueList() const {
return *reinterpret_cast<const QVector<T*>*>(&this->valuesList); // NOLINT
}
void insertObject(T* object, qsizetype index = -1) {
this->UntypedObjectModel::insertObject(object, index);
}
void removeObject(const T* object) { this->UntypedObjectModel::removeObject(object); }
};

View file

@ -18,5 +18,6 @@ headers = [
"easingcurve.hpp",
"transformwatcher.hpp",
"boundcomponent.hpp",
"model.hpp",
]
-----

View file

@ -4,14 +4,12 @@
#include <qdbusconnection.h>
#include <qdbusconnectioninterface.h>
#include <qdbusservicewatcher.h>
#include <qlist.h>
#include <qlogging.h>
#include <qloggingcategory.h>
#include <qobject.h>
#include <qqmllist.h>
#include <qtmetamacros.h>
#include <qtypes.h>
#include "../../core/model.hpp"
#include "player.hpp"
namespace qs::service::mpris {
@ -74,34 +72,15 @@ void MprisWatcher::onServiceUnregistered(const QString& service) {
void MprisWatcher::onPlayerReady() {
auto* player = qobject_cast<MprisPlayer*>(this->sender());
this->readyPlayers.push_back(player);
emit this->playersChanged();
this->readyPlayers.insertObject(player);
}
void MprisWatcher::onPlayerDestroyed(QObject* object) {
auto* player = static_cast<MprisPlayer*>(object); // NOLINT
if (this->readyPlayers.removeOne(player)) {
emit this->playersChanged();
}
this->readyPlayers.removeObject(player);
}
QQmlListProperty<MprisPlayer> MprisWatcher::players() {
return QQmlListProperty<MprisPlayer>(
this,
nullptr,
&MprisWatcher::playersCount,
&MprisWatcher::playerAt
);
}
qsizetype MprisWatcher::playersCount(QQmlListProperty<MprisPlayer>* property) {
return static_cast<MprisWatcher*>(property->object)->readyPlayers.count(); // NOLINT
}
MprisPlayer* MprisWatcher::playerAt(QQmlListProperty<MprisPlayer>* property, qsizetype index) {
return static_cast<MprisWatcher*>(property->object)->readyPlayers.at(index); // NOLINT
}
ObjectModel<MprisPlayer>* MprisWatcher::players() { return &this->readyPlayers; }
void MprisWatcher::registerPlayer(const QString& address) {
if (this->mPlayers.contains(address)) {

View file

@ -4,14 +4,13 @@
#include <qdbusinterface.h>
#include <qdbusservicewatcher.h>
#include <qhash.h>
#include <qlist.h>
#include <qloggingcategory.h>
#include <qobject.h>
#include <qqmlintegration.h>
#include <qqmllist.h>
#include <qtmetamacros.h>
#include <qtypes.h>
#include "../../core/model.hpp"
#include "player.hpp"
namespace qs::service::mpris {
@ -22,15 +21,12 @@ class MprisWatcher: public QObject {
QML_NAMED_ELEMENT(Mpris);
QML_SINGLETON;
/// All connected MPRIS players.
Q_PROPERTY(QQmlListProperty<MprisPlayer> players READ players NOTIFY playersChanged);
Q_PROPERTY(ObjectModel<MprisPlayer>* players READ players CONSTANT);
public:
explicit MprisWatcher(QObject* parent = nullptr);
[[nodiscard]] QQmlListProperty<MprisPlayer> players();
signals:
void playersChanged();
[[nodiscard]] ObjectModel<MprisPlayer>* players();
private slots:
void onServiceRegistered(const QString& service);
@ -39,15 +35,12 @@ private slots:
void onPlayerDestroyed(QObject* object);
private:
static qsizetype playersCount(QQmlListProperty<MprisPlayer>* property);
static MprisPlayer* playerAt(QQmlListProperty<MprisPlayer>* property, qsizetype index);
void registerExisting();
void registerPlayer(const QString& address);
QDBusServiceWatcher serviceWatcher;
QHash<QString, MprisPlayer*> mPlayers;
QList<MprisPlayer*> readyPlayers;
ObjectModel<MprisPlayer> readyPlayers {this};
};
} // namespace qs::service::mpris

View file

@ -8,6 +8,7 @@
#include <qtypes.h>
#include <qvariant.h>
#include "../../core/model.hpp"
#include "connection.hpp"
#include "link.hpp"
#include "metadata.hpp"
@ -65,88 +66,43 @@ Pipewire::Pipewire(QObject* parent): QObject(parent) {
// clang-format on
}
QQmlListProperty<PwNodeIface> Pipewire::nodes() {
return QQmlListProperty<PwNodeIface>(this, nullptr, &Pipewire::nodesCount, &Pipewire::nodeAt);
}
qsizetype Pipewire::nodesCount(QQmlListProperty<PwNodeIface>* property) {
return static_cast<Pipewire*>(property->object)->mNodes.count(); // NOLINT
}
PwNodeIface* Pipewire::nodeAt(QQmlListProperty<PwNodeIface>* property, qsizetype index) {
return static_cast<Pipewire*>(property->object)->mNodes.at(index); // NOLINT
}
ObjectModel<PwNodeIface>* Pipewire::nodes() { return &this->mNodes; }
void Pipewire::onNodeAdded(PwNode* node) {
auto* iface = PwNodeIface::instance(node);
QObject::connect(iface, &QObject::destroyed, this, &Pipewire::onNodeRemoved);
this->mNodes.push_back(iface);
emit this->nodesChanged();
this->mNodes.insertObject(iface);
}
void Pipewire::onNodeRemoved(QObject* object) {
auto* iface = static_cast<PwNodeIface*>(object); // NOLINT
this->mNodes.removeOne(iface);
emit this->nodesChanged();
this->mNodes.removeObject(iface);
}
QQmlListProperty<PwLinkIface> Pipewire::links() {
return QQmlListProperty<PwLinkIface>(this, nullptr, &Pipewire::linksCount, &Pipewire::linkAt);
}
qsizetype Pipewire::linksCount(QQmlListProperty<PwLinkIface>* property) {
return static_cast<Pipewire*>(property->object)->mLinks.count(); // NOLINT
}
PwLinkIface* Pipewire::linkAt(QQmlListProperty<PwLinkIface>* property, qsizetype index) {
return static_cast<Pipewire*>(property->object)->mLinks.at(index); // NOLINT
}
ObjectModel<PwLinkIface>* Pipewire::links() { return &this->mLinks; }
void Pipewire::onLinkAdded(PwLink* link) {
auto* iface = PwLinkIface::instance(link);
QObject::connect(iface, &QObject::destroyed, this, &Pipewire::onLinkRemoved);
this->mLinks.push_back(iface);
emit this->linksChanged();
this->mLinks.insertObject(iface);
}
void Pipewire::onLinkRemoved(QObject* object) {
auto* iface = static_cast<PwLinkIface*>(object); // NOLINT
this->mLinks.removeOne(iface);
emit this->linksChanged();
this->mLinks.removeObject(iface);
}
QQmlListProperty<PwLinkGroupIface> Pipewire::linkGroups() {
return QQmlListProperty<PwLinkGroupIface>(
this,
nullptr,
&Pipewire::linkGroupsCount,
&Pipewire::linkGroupAt
);
}
qsizetype Pipewire::linkGroupsCount(QQmlListProperty<PwLinkGroupIface>* property) {
return static_cast<Pipewire*>(property->object)->mLinkGroups.count(); // NOLINT
}
PwLinkGroupIface*
Pipewire::linkGroupAt(QQmlListProperty<PwLinkGroupIface>* property, qsizetype index) {
return static_cast<Pipewire*>(property->object)->mLinkGroups.at(index); // NOLINT
}
ObjectModel<PwLinkGroupIface>* Pipewire::linkGroups() { return &this->mLinkGroups; }
void Pipewire::onLinkGroupAdded(PwLinkGroup* linkGroup) {
auto* iface = PwLinkGroupIface::instance(linkGroup);
QObject::connect(iface, &QObject::destroyed, this, &Pipewire::onLinkGroupRemoved);
this->mLinkGroups.push_back(iface);
emit this->linkGroupsChanged();
this->mLinkGroups.insertObject(iface);
}
void Pipewire::onLinkGroupRemoved(QObject* object) {
auto* iface = static_cast<PwLinkGroupIface*>(object); // NOLINT
this->mLinkGroups.removeOne(iface);
emit this->linkGroupsChanged();
this->mLinkGroups.removeObject(iface);
}
PwNodeIface* Pipewire::defaultAudioSink() const { // NOLINT

View file

@ -8,6 +8,7 @@
#include <qtmetamacros.h>
#include <qtypes.h>
#include "../../core/model.hpp"
#include "link.hpp"
#include "node.hpp"
#include "registry.hpp"
@ -52,11 +53,11 @@ class Pipewire: public QObject {
Q_OBJECT;
// clang-format off
/// All pipewire nodes.
Q_PROPERTY(QQmlListProperty<PwNodeIface> nodes READ nodes NOTIFY nodesChanged);
Q_PROPERTY(ObjectModel<PwNodeIface>* nodes READ nodes CONSTANT);
/// All pipewire links.
Q_PROPERTY(QQmlListProperty<PwLinkIface> links READ links NOTIFY linksChanged);
Q_PROPERTY(ObjectModel<PwLinkIface>* links READ links CONSTANT);
/// All pipewire link groups.
Q_PROPERTY(QQmlListProperty<PwLinkGroupIface> linkGroups READ linkGroups NOTIFY linkGroupsChanged);
Q_PROPERTY(ObjectModel<PwLinkGroupIface>* linkGroups READ linkGroups CONSTANT);
/// The default audio sink or `null`.
Q_PROPERTY(PwNodeIface* defaultAudioSink READ defaultAudioSink NOTIFY defaultAudioSinkChanged);
/// The default audio source or `null`.
@ -68,16 +69,13 @@ class Pipewire: public QObject {
public:
explicit Pipewire(QObject* parent = nullptr);
[[nodiscard]] QQmlListProperty<PwNodeIface> nodes();
[[nodiscard]] QQmlListProperty<PwLinkIface> links();
[[nodiscard]] QQmlListProperty<PwLinkGroupIface> linkGroups();
[[nodiscard]] ObjectModel<PwNodeIface>* nodes();
[[nodiscard]] ObjectModel<PwLinkIface>* links();
[[nodiscard]] ObjectModel<PwLinkGroupIface>* linkGroups();
[[nodiscard]] PwNodeIface* defaultAudioSink() const;
[[nodiscard]] PwNodeIface* defaultAudioSource() const;
signals:
void nodesChanged();
void linksChanged();
void linkGroupsChanged();
void defaultAudioSinkChanged();
void defaultAudioSourceChanged();
@ -90,17 +88,9 @@ private slots:
void onLinkGroupRemoved(QObject* object);
private:
static qsizetype nodesCount(QQmlListProperty<PwNodeIface>* property);
static PwNodeIface* nodeAt(QQmlListProperty<PwNodeIface>* property, qsizetype index);
static qsizetype linksCount(QQmlListProperty<PwLinkIface>* property);
static PwLinkIface* linkAt(QQmlListProperty<PwLinkIface>* property, qsizetype index);
static qsizetype linkGroupsCount(QQmlListProperty<PwLinkGroupIface>* property);
static PwLinkGroupIface*
linkGroupAt(QQmlListProperty<PwLinkGroupIface>* property, qsizetype index);
QVector<PwNodeIface*> mNodes;
QVector<PwLinkIface*> mLinks;
QVector<PwLinkGroupIface*> mLinkGroups;
ObjectModel<PwNodeIface> mNodes {this};
ObjectModel<PwLinkIface> mLinks {this};
ObjectModel<PwLinkGroupIface> mLinkGroups {this};
};
///! Tracks all link connections to a given node.

View file

@ -9,6 +9,7 @@
#include <qtmetamacros.h>
#include <qtypes.h>
#include "../../core/model.hpp"
#include "../../dbus/dbusmenu/dbusmenu.hpp"
#include "../../dbus/properties.hpp"
#include "host.hpp"
@ -106,46 +107,25 @@ SystemTray::SystemTray(QObject* parent): QObject(parent) {
// clang-format on
for (auto* item: host->items()) {
this->mItems.push_back(new SystemTrayItem(item, this));
this->mItems.insertObject(new SystemTrayItem(item, this));
}
}
void SystemTray::onItemRegistered(StatusNotifierItem* item) {
this->mItems.push_back(new SystemTrayItem(item, this));
emit this->itemsChanged();
this->mItems.insertObject(new SystemTrayItem(item, this));
}
void SystemTray::onItemUnregistered(StatusNotifierItem* item) {
SystemTrayItem* trayItem = nullptr;
this->mItems.removeIf([item, &trayItem](SystemTrayItem* testItem) {
if (testItem->item == item) {
trayItem = testItem;
return true;
} else return false;
});
emit this->itemsChanged();
delete trayItem;
for (const auto* storedItem: this->mItems.valueList()) {
if (storedItem->item == item) {
this->mItems.removeObject(storedItem);
delete storedItem;
break;
}
}
}
QQmlListProperty<SystemTrayItem> SystemTray::items() {
return QQmlListProperty<SystemTrayItem>(
this,
nullptr,
&SystemTray::itemsCount,
&SystemTray::itemAt
);
}
qsizetype SystemTray::itemsCount(QQmlListProperty<SystemTrayItem>* property) {
return reinterpret_cast<SystemTray*>(property->object)->mItems.count(); // NOLINT
}
SystemTrayItem* SystemTray::itemAt(QQmlListProperty<SystemTrayItem>* property, qsizetype index) {
return reinterpret_cast<SystemTray*>(property->object)->mItems.at(index); // NOLINT
}
ObjectModel<SystemTrayItem>* SystemTray::items() { return &this->mItems; }
SystemTrayItem* SystemTrayMenuWatcher::trayItem() const { return this->item; }

View file

@ -2,10 +2,9 @@
#include <qobject.h>
#include <qqmlintegration.h>
#include <qqmllist.h>
#include <qtmetamacros.h>
#include <qtypes.h>
#include "../../core/model.hpp"
#include "item.hpp"
namespace SystemTrayStatus { // NOLINT
@ -108,27 +107,21 @@ signals:
class SystemTray: public QObject {
Q_OBJECT;
/// List of all system tray icons.
Q_PROPERTY(QQmlListProperty<SystemTrayItem> items READ items NOTIFY itemsChanged);
Q_PROPERTY(ObjectModel<SystemTrayItem>* items READ items CONSTANT);
QML_ELEMENT;
QML_SINGLETON;
public:
explicit SystemTray(QObject* parent = nullptr);
[[nodiscard]] QQmlListProperty<SystemTrayItem> items();
signals:
void itemsChanged();
[[nodiscard]] ObjectModel<SystemTrayItem>* items();
private slots:
void onItemRegistered(qs::service::sni::StatusNotifierItem* item);
void onItemUnregistered(qs::service::sni::StatusNotifierItem* item);
private:
static qsizetype itemsCount(QQmlListProperty<SystemTrayItem>* property);
static SystemTrayItem* itemAt(QQmlListProperty<SystemTrayItem>* property, qsizetype index);
QList<SystemTrayItem*> mItems;
ObjectModel<SystemTrayItem> mItems {this};
};
///! Accessor for SystemTrayItem menus.