File indexing completed on 2024-11-10 04:54:52
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 } 0159 0160 #include "moc_PatientModel.cpp"