File indexing completed on 2024-04-21 16:12:16

0001 // SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
0002 // SPDX-FileCopyrightText: 2020-2022 Harald Sitter <sitter@kde.org>
0003 
0004 #include "PatientModel.h"
0005 
0006 #include <chrono>
0007 
0008 #include <QDebug>
0009 #include <QMetaMethod>
0010 
0011 using namespace std::chrono_literals;
0012 
0013 PatientModel::PatientModel(const QMetaObject &mo, QObject *parent)
0014     : QAbstractListModel(parent)
0015 {
0016     initRoleNames(mo);
0017 }
0018 
0019 QHash<int, QByteArray> PatientModel::roleNames() const
0020 {
0021     return m_roles;
0022 }
0023 
0024 int PatientModel::rowCount(const QModelIndex &parent) const
0025 {
0026     Q_UNUSED(parent); // this is a flat list we decidedly don't care about the parent
0027     return m_objects.count();
0028 }
0029 
0030 QVariant PatientModel::data(const QModelIndex &index, int role) const
0031 {
0032     if (!hasIndex(index.row(), index.column())) {
0033         return {};
0034     }
0035     // return QVariant::fromValue((QObject *)0x1);
0036     QObject *obj = m_objects.at(index.row());
0037     switch ((ItemRole)role) {
0038     case ObjectRole:
0039         return QVariant::fromValue(obj);
0040     case IndexRole:
0041         return QVariant::fromValue(index.row());
0042     }
0043     const QByteArray prop = m_objectProperties.value(role);
0044     if (prop.isEmpty()) {
0045         return {};
0046     }
0047     return obj->property(prop.constData());
0048 }
0049 
0050 bool PatientModel::setData(const QModelIndex &index, const QVariant &value, int role)
0051 {
0052     if (!hasIndex(index.row(), index.column())) {
0053         return false;
0054     }
0055     QObject *obj = m_objects.at(index.row());
0056     if (role == ObjectRole) {
0057         return false; // cannot set object!
0058     }
0059     const QByteArray prop = m_objectProperties.value(role);
0060     if (prop.isEmpty()) {
0061         return false;
0062     }
0063     return obj->setProperty(prop.constData(), value);
0064 }
0065 
0066 int PatientModel::role(const QByteArray &roleName) const
0067 {
0068     return m_roles.key(roleName, -1);
0069 }
0070 
0071 void PatientModel::addObject(std::unique_ptr<QObject> patient)
0072 {
0073     const int index = m_objects.size();
0074     beginInsertRows(QModelIndex(), index, index);
0075 
0076     QObject *object = patient.release();
0077     object->setParent(this);
0078 
0079     m_objects.append(object);
0080     Q_ASSERT(!m_roles.isEmpty());
0081 
0082     const QMetaObject *mo = object->metaObject();
0083     // We have all the data changed notify signals already stored, let's connect all changed signals to our property change wrapper.
0084     const QList<int> signalIndices = m_signalIndexToProperties.keys();
0085     for (const auto &signalMethodIndex : signalIndices) {
0086         const QMetaMethod meth = mo->method(signalMethodIndex);
0087         // Since we dynamically connect all relevant change signals through QMetaMethods we also need the slot to be a QMM. Unfortunate since this is
0088         // a bit fiddly.
0089         connect(object, meth, this, propertyChangedMetaMethod());
0090     }
0091 
0092     endInsertRows();
0093 }
0094 
0095 QMetaMethod PatientModel::propertyChangedMetaMethod() const
0096 {
0097     // This function purely exists because we meta-program a connection between a generic signal of the type QMetaMethod.
0098     // There is no connect() overload for (obj, QMetaMethod, obj, lambda) so we need a QMetaMethod to connect to!
0099 
0100     const auto mo = metaObject();
0101     // The function serves no purpose other than compile-time asserting the signature.
0102     // Mind that if the slot signature changes you must reflect this here and ensure that the connect calls stay valid WRT argument compatibility!
0103     const std::function<void(PatientModel &)> function = &PatientModel::propertyChanged;
0104     Q_ASSERT(function);
0105     const int methodIndex = mo->indexOfMethod("propertyChanged()");
0106     Q_ASSERT(methodIndex != -1);
0107     return mo->method(methodIndex);
0108 }
0109 
0110 void PatientModel::propertyChanged()
0111 {
0112     // Property index and role index are the same so we only need to map the signal index to the property index.
0113     const int role = m_signalIndexToProperties.value(senderSignalIndex(), -1);
0114     Q_ASSERT(role != -1);
0115     const int index = m_objects.indexOf(sender());
0116     Q_ASSERT(index != -1);
0117     qDebug() << "PROPERTY CHANGED (" << index << ") :: " << role << roleNames().value(role);
0118     Q_EMIT dataChanged(createIndex(index, 0), createIndex(index, 0), {role});
0119 }
0120 
0121 int PatientModel::initRoleNames(const QMetaObject &mo)
0122 {
0123     m_roles[ObjectRole] = QByteArrayLiteral("modelObject");
0124     m_roles[IndexRole] = QByteArrayLiteral("modelIndex");
0125 
0126     int maxEnumValue = ObjectRole;
0127     Q_ASSERT(maxEnumValue != -1);
0128     for (int i = 0; i < mo.propertyCount(); ++i) {
0129         const QMetaProperty property = mo.property(i);
0130         m_roles[++maxEnumValue] = QByteArray("ROLE_") + property.name();
0131         m_objectProperties.insert(maxEnumValue, property.name());
0132         if (!property.hasNotifySignal()) {
0133             continue;
0134         }
0135         m_signalIndexToProperties.insert(property.notifySignalIndex(), maxEnumValue);
0136     }
0137     return maxEnumValue;
0138 }
0139 
0140 void PatientModel::addDynamicRoleNames(int maxEnumValue, QObject *object)
0141 {
0142     const auto dynamicPropertyNames = object->dynamicPropertyNames();
0143     for (const auto &dynProperty : dynamicPropertyNames) {
0144         m_roles[++maxEnumValue] = dynProperty;
0145         m_objectProperties.insert(maxEnumValue, dynProperty);
0146     }
0147 }
0148 
0149 bool PatientModel::ready() const
0150 {
0151     return m_ready;
0152 }
0153 
0154 void PatientModel::setReady(bool ready)
0155 {
0156     m_ready = ready;
0157     Q_EMIT readyChanged();
0158 }