File indexing completed on 2024-12-15 03:45:04
0001 /* 0002 SPDX-FileCopyrightText: 2016 Volker Krause <vkrause@kde.org> 0003 0004 SPDX-License-Identifier: MIT 0005 */ 0006 0007 #include "propertyratiosource.h" 0008 #include "abstractdatasource_p.h" 0009 #include "logging_p.h" 0010 0011 #include <QDebug> 0012 #include <QHash> 0013 #include <QMap> 0014 #include <QMetaProperty> 0015 #include <QObject> 0016 #include <QPointer> 0017 #include <QSettings> 0018 #include <QStringList> 0019 #include <QTime> 0020 #include <QElapsedTimer> 0021 0022 using namespace KUserFeedback; 0023 0024 namespace KUserFeedback { 0025 class PropertyRatioSourcePrivate : public AbstractDataSourcePrivate 0026 { 0027 public: 0028 PropertyRatioSourcePrivate(); 0029 ~PropertyRatioSourcePrivate() override; 0030 0031 void propertyChanged(); 0032 QString valueToString(const QVariant &value) const; 0033 void trySetup(); 0034 0035 QString name; 0036 QString description; 0037 QPointer<QObject> obj; 0038 QByteArray propertyName; 0039 QObject *signalMonitor; 0040 QMetaProperty property; 0041 QString previousValue; 0042 QElapsedTimer lastChangeTime; 0043 QHash<QString, int> ratioSet; // data we are currently tracking 0044 QHash<QString, int> baseRatioSet; // data loaded from storage 0045 QList<QPair<QVariant, QString>> valueMap; 0046 }; 0047 0048 // inefficient workaround for not being able to connect QMetaMethod to a function directly 0049 class SignalMonitor : public QObject 0050 { 0051 Q_OBJECT 0052 public: 0053 explicit SignalMonitor(PropertyRatioSourcePrivate *r) : m_receiver(r) {} 0054 public Q_SLOTS: 0055 void propertyChanged() 0056 { 0057 m_receiver->propertyChanged(); 0058 } 0059 private: 0060 PropertyRatioSourcePrivate *m_receiver; 0061 }; 0062 0063 } 0064 0065 PropertyRatioSourcePrivate::PropertyRatioSourcePrivate() 0066 : obj(nullptr) 0067 , signalMonitor(nullptr) 0068 { 0069 } 0070 0071 PropertyRatioSourcePrivate::~PropertyRatioSourcePrivate() 0072 { 0073 delete signalMonitor; 0074 } 0075 0076 void PropertyRatioSourcePrivate::propertyChanged() 0077 { 0078 if (!previousValue.isEmpty() && lastChangeTime.elapsed() > 1000) { 0079 ratioSet[previousValue] += lastChangeTime.elapsed() / 1000; 0080 } 0081 0082 lastChangeTime.start(); 0083 previousValue = valueToString(property.read(obj)); 0084 } 0085 0086 QString PropertyRatioSourcePrivate::valueToString(const QVariant &value) const 0087 { 0088 const auto it = std::find_if(valueMap.constBegin(), valueMap.constEnd(), [value](QPair<QVariant, QString> p) { 0089 return p.first == value; 0090 }); 0091 if (it != valueMap.constEnd()) { 0092 return it->second; 0093 } 0094 return value.toString(); 0095 } 0096 0097 void PropertyRatioSourcePrivate::trySetup() 0098 { 0099 if (!obj || propertyName.isEmpty()) 0100 return; 0101 0102 auto idx = obj->metaObject()->indexOfProperty(propertyName.constData()); 0103 Q_ASSERT(idx >= 0); 0104 if (idx < 0) { 0105 qCWarning(Log) << "Property" << propertyName << "not found in" << obj << "!"; 0106 return; 0107 } 0108 0109 property = obj->metaObject()->property(idx); 0110 if (!property.hasNotifySignal()) { 0111 qCWarning(Log) << "Property" << propertyName << "has no notification signal!"; 0112 return; 0113 } 0114 0115 idx = signalMonitor->metaObject()->indexOfMethod("propertyChanged()"); 0116 Q_ASSERT(idx >= 0); 0117 const auto propertyChangedMethod = signalMonitor->metaObject()->method(idx); 0118 QObject::connect(obj, property.notifySignal(), signalMonitor, propertyChangedMethod); 0119 0120 lastChangeTime.start(); 0121 propertyChangedMethod.invoke(signalMonitor, Qt::QueuedConnection); 0122 } 0123 0124 PropertyRatioSource::PropertyRatioSource(QObject *obj, const char *propertyName, const QString &sampleName) : 0125 AbstractDataSource(sampleName, Provider::DetailedUsageStatistics, new PropertyRatioSourcePrivate) 0126 { 0127 Q_D(PropertyRatioSource); 0128 0129 d->obj = obj; 0130 d->propertyName = propertyName; 0131 d->signalMonitor = new SignalMonitor(d); 0132 d->trySetup(); 0133 } 0134 0135 QObject* PropertyRatioSource::object() const 0136 { 0137 Q_D(const PropertyRatioSource); 0138 return d->obj; 0139 } 0140 0141 void PropertyRatioSource::setObject(QObject* object) 0142 { 0143 Q_D(PropertyRatioSource); 0144 if (d->obj == object) 0145 return; 0146 d->obj = object; 0147 d->trySetup(); 0148 } 0149 0150 QString PropertyRatioSource::propertyName() const 0151 { 0152 Q_D(const PropertyRatioSource); 0153 return QString::fromUtf8(d->propertyName.constData()); 0154 } 0155 0156 void PropertyRatioSource::setPropertyName(const QString& name) 0157 { 0158 Q_D(PropertyRatioSource); 0159 const auto n = name.toUtf8(); 0160 if (d->propertyName == n) 0161 return; 0162 d->propertyName = n; 0163 d->trySetup(); 0164 } 0165 0166 void PropertyRatioSource::addValueMapping(const QVariant &value, const QString &str) 0167 { 0168 Q_D(PropertyRatioSource); 0169 auto it = std::find_if(d->valueMap.begin(), d->valueMap.end(), [value](QPair<QVariant, QString> p) { 0170 return p.first == value; 0171 }); 0172 if (it != d->valueMap.end()) { 0173 it->second = str; 0174 } else { 0175 d->valueMap.append(QPair<QVariant, QString>(value, str)); 0176 } 0177 } 0178 0179 QString PropertyRatioSource::name() const 0180 { 0181 Q_D(const PropertyRatioSource); 0182 return d->name; 0183 } 0184 0185 void PropertyRatioSource::setName(const QString &name) 0186 { 0187 Q_D(PropertyRatioSource); 0188 d->name = name; 0189 } 0190 0191 QString PropertyRatioSource::description() const 0192 { 0193 Q_D(const PropertyRatioSource); 0194 return d->description; 0195 } 0196 0197 void PropertyRatioSource::setDescription(const QString& desc) 0198 { 0199 Q_D(PropertyRatioSource); 0200 d->description = desc; 0201 } 0202 0203 QVariant PropertyRatioSource::data() 0204 { 0205 Q_D(PropertyRatioSource); 0206 d->propertyChanged(); 0207 0208 QVariantMap m; 0209 int total = 0; 0210 for (auto it = d->ratioSet.constBegin(); it != d->ratioSet.constEnd(); ++it) 0211 total += it.value() + d->baseRatioSet.value(it.key()); 0212 if (total <= 0) 0213 return m; 0214 0215 for (auto it = d->ratioSet.constBegin(); it != d->ratioSet.constEnd(); ++it) { 0216 double currentValue = it.value() + d->baseRatioSet.value(it.key()); 0217 QVariantMap v; 0218 v.insert(QStringLiteral("property"), currentValue / (double)(total)); 0219 m.insert(it.key(), v); 0220 } 0221 0222 return m; 0223 } 0224 0225 void PropertyRatioSource::loadImpl(QSettings *settings) 0226 { 0227 Q_D(PropertyRatioSource); 0228 foreach (const auto &value, settings->childKeys()) { 0229 const auto amount = std::max(settings->value(value, 0).toInt(), 0); 0230 d->baseRatioSet.insert(value, amount); 0231 if (!d->ratioSet.contains(value)) 0232 d->ratioSet.insert(value, 0); 0233 } 0234 } 0235 0236 void PropertyRatioSource::storeImpl(QSettings *settings) 0237 { 0238 Q_D(PropertyRatioSource); 0239 d->propertyChanged(); 0240 0241 // note that a second process can have updated the data meanwhile! 0242 for (auto it = d->ratioSet.begin(); it != d->ratioSet.end(); ++it) { 0243 if (it.value() == 0) 0244 continue; 0245 const auto oldValue = std::max(settings->value(it.key(), 0).toInt(), 0); 0246 const auto newValue = oldValue + it.value(); 0247 settings->setValue(it.key(), newValue); 0248 *it = 0; 0249 d->baseRatioSet.insert(it.key(), newValue); 0250 } 0251 } 0252 0253 void PropertyRatioSource::resetImpl(QSettings* settings) 0254 { 0255 Q_D(PropertyRatioSource); 0256 d->baseRatioSet.clear(); 0257 d->ratioSet.clear(); 0258 settings->remove(QString()); 0259 } 0260 0261 #include "propertyratiosource.moc"