Libksysguard

SensorTreeModel.cpp
1/*
2 SPDX-FileCopyrightText: 2019 Eike Hein <hein@kde.org>
3 SPDX-FileCopyrightText: 2020 Arjen Hiemstra <ahiemstra@heimr.nl>
4
5 SPDX-License-Identifier: LGPL-2.0-or-later
6*/
7
8#include "SensorTreeModel.h"
9
10#include <optional>
11
12#include <KLocalizedString>
13#include <QCollator>
14#include <QDebug>
15#include <QMetaEnum>
16#include <QMimeData>
17#include <QRegularExpression>
18
19#include "formatter/Formatter.h"
20#include "systemstats/SensorInfo.h"
21
22#include "Sensor.h"
23#include "SensorDaemonInterface_p.h"
24#include "SensorGroup_p.h"
25#include "SensorQuery.h"
26
27using namespace KSysGuard;
28
29struct Compare {
30 bool operator()(const QString &first, const QString &second) const
31 {
32 // Place "All" object at the top.
33 if (first == QLatin1String("all") && first != second) {
34 return true;
35 }
36
37 if (second == QLatin1String("all")) {
38 return false;
39 }
40
41 if (!collator) {
42 collator = QCollator();
43 collator->setNumericMode(true);
44 collator->setCaseSensitivity(Qt::CaseInsensitive);
45 }
46
47 return collator->compare(first, second) < 0;
48 }
49
50 // This uses thread-local storage because QCollator may not be thread safe.
51 // We store it in an optional to make sure we can initialize it above.
52 thread_local static std::optional<QCollator> collator;
53};
54
55thread_local std::optional<QCollator> Compare::collator = std::nullopt;
56
57struct Q_DECL_HIDDEN SensorTreeItem {
58 SensorTreeItem *parent = nullptr;
59 QString segment;
60 std::map<QString, std::unique_ptr<SensorTreeItem>, Compare> children;
61
62 inline int indexOf(const QString &segment) const
63 {
64 auto itr = std::find_if(children.cbegin(), children.cend(), [segment](const auto &item) {
65 return item.second->segment == segment;
66 });
67
68 if (itr != children.cend()) {
69 return std::distance(children.cbegin(), itr);
70 }
71
72 return -1;
73 }
74
75 inline SensorTreeItem *itemAt(std::size_t index) const
76 {
77 if (index >= children.size()) {
78 return nullptr;
79 }
80
81 auto itr = children.cbegin();
82 std::advance(itr, index);
83 return itr->second.get();
84 }
85};
86
87class Q_DECL_HIDDEN SensorTreeModel::Private
88{
89public:
90 Private(SensorTreeModel *qq)
91 : rootItem(new SensorTreeItem)
92 , q(qq)
93 {
94 m_sensorGroup = new SensorGroup;
95 }
96 ~Private()
97 {
98 delete rootItem;
99 delete m_sensorGroup;
100 }
101
102 SensorTreeItem *rootItem;
104
105 void addSensor(const QString &sensorId, const SensorInfo &info);
106 void removeSensor(const QString &sensorId);
107
108 QString sensorId(const QModelIndex &index);
109
110 SensorTreeItem *find(const QString &sensorId);
111
112 SensorGroup *m_sensorGroup;
113
114 QHash<QString, int> m_groupMatches;
115
116private:
118};
119
120void SensorTreeModel::Private::addSensor(const QString &sensorId, const SensorInfo &info)
121{
122 const QStringList &segments = sensorId.split(QLatin1Char('/'));
123
124 if (!segments.count() || segments.at(0).isEmpty()) {
125 qDebug() << "Rejecting sensor" << sensorId << "- sensor id is not well-formed.";
126 return;
127 }
128
129 QString sensorIdExpr = m_sensorGroup->groupRegexForId(sensorId);
130
131 if (!sensorIdExpr.isEmpty()) {
132 if (m_groupMatches.contains(sensorIdExpr)) {
133 m_groupMatches[sensorIdExpr]++;
134 } else {
135 m_groupMatches[sensorIdExpr] = 1;
136 }
137
138 if (m_groupMatches[sensorIdExpr] == 2) {
139 SensorInfo newInfo;
140 newInfo.name = m_sensorGroup->sensorNameForRegEx(sensorIdExpr);
141 newInfo.description = info.description;
142 newInfo.variantType = info.variantType;
143 newInfo.unit = info.unit;
144 newInfo.min = info.min;
145 newInfo.max = info.max;
146
147 addSensor(sensorIdExpr, newInfo);
148 }
149 }
150
151 SensorTreeItem *item = rootItem;
152 for (auto segment : segments) {
153 if (auto itr = item->children.find(segment); itr != item->children.end() && itr->second) {
154 item = itr->second.get();
155 } else {
156 auto newItem = std::make_unique<SensorTreeItem>();
157 newItem->parent = item;
158 newItem->segment = segment;
159
160 const QModelIndex &parentIndex = (item == rootItem) ? QModelIndex() : q->createIndex(item->parent->indexOf(item->segment), 0, item);
161
162 auto index = std::distance(item->children.begin(), item->children.upper_bound(segment));
163
164 q->beginInsertRows(parentIndex, index, index);
165 item->children[segment] = std::move(newItem);
166 q->endInsertRows();
167
168 item = item->children[segment].get();
169 }
170 }
171
172 sensorInfos[item] = info;
173}
174
175void SensorTreeModel::Private::removeSensor(const QString &sensorId)
176{
177 QString sensorIdExpr = m_sensorGroup->groupRegexForId(sensorId);
178 if (!sensorIdExpr.isEmpty()) {
179 if (m_groupMatches[sensorIdExpr] == 1) {
180 m_groupMatches.remove(sensorIdExpr);
181 removeSensor(sensorIdExpr);
182 } else if (m_groupMatches.contains(sensorIdExpr)) {
183 m_groupMatches[sensorIdExpr]--;
184 }
185 }
186
187 SensorTreeItem *item = find(sensorId);
188 if (!item) {
189 return;
190 }
191
192 SensorTreeItem *parent = item->parent;
193 if (!parent) {
194 return;
195 }
196
197 auto remove = [this](SensorTreeItem *item, SensorTreeItem *parent) {
198 const int index = item->parent->indexOf(item->segment);
199
200 const QModelIndex &parentIndex = (parent == rootItem) ? QModelIndex() : q->createIndex(parent->parent->indexOf(parent->segment), 0, parent);
201 q->beginRemoveRows(parentIndex, index, index);
202
203 auto itr = item->parent->children.find(item->segment);
204 item->parent->children.erase(itr);
205
206 q->endRemoveRows();
207
208 sensorInfos.remove(item);
209 };
210
211 remove(item, parent);
212
213 while (!parent->children.size()) {
214 item = parent;
216
217 if (!parent) {
218 break;
219 }
220
221 remove(item, parent);
222 }
223}
224
225QString SensorTreeModel::Private::sensorId(const QModelIndex &index)
226{
227 QStringList segments;
228
229 SensorTreeItem *item = static_cast<SensorTreeItem *>(index.internalPointer());
230
231 segments << item->segment;
232
233 while (item->parent && item->parent != rootItem) {
234 item = item->parent;
235 segments.prepend(item->segment);
236 }
237
238 return segments.join(QLatin1Char('/'));
239}
240
241SensorTreeItem *KSysGuard::SensorTreeModel::Private::find(const QString &sensorId)
242{
243 auto item = rootItem;
244 const auto segments = sensorId.split(QLatin1Char('/'));
245 for (const QString &segment : segments) {
246 if (auto itr = item->children.find(segment); itr != item->children.end() && itr->second) {
247 item = itr->second.get();
248 } else {
249 return nullptr;
250 }
251 }
252 return item;
253}
254
255SensorTreeModel::SensorTreeModel(QObject *parent)
257 , d(new Private(this))
258{
259 connect(SensorDaemonInterface::instance(), &SensorDaemonInterface::sensorAdded, this, &SensorTreeModel::onSensorAdded);
260 connect(SensorDaemonInterface::instance(), &SensorDaemonInterface::sensorRemoved, this, &SensorTreeModel::onSensorRemoved);
261 connect(SensorDaemonInterface::instance(), &SensorDaemonInterface::metaDataChanged, this, &SensorTreeModel::onMetaDataChanged);
262 init();
263}
264
265SensorTreeModel::~SensorTreeModel()
266{
267}
268
269QHash<int, QByteArray> SensorTreeModel::roleNames() const
270{
272
273 QMetaEnum e = metaObject()->enumerator(metaObject()->indexOfEnumerator("AdditionalRoles"));
274
275 for (int i = 0; i < e.keyCount(); ++i) {
276 roles.insert(e.value(i), e.key(i));
277 }
278
279 return roles;
280}
281
282QVariant SensorTreeModel::headerData(int section, Qt::Orientation, int role) const
283{
284 if (role != Qt::DisplayRole) {
285 return QVariant();
286 }
287
288 if (section == 0) {
289 return i18n("Sensor Browser");
290 }
291
292 return QVariant();
293}
294
295QStringList SensorTreeModel::mimeTypes() const
296{
297 return QStringList() << QStringLiteral("application/x-ksysguard");
298}
299
300QVariant SensorTreeModel::data(const QModelIndex &index, int role) const
301{
302 if (!checkIndex(index, CheckIndexOption::IndexIsValid)) {
303 return QVariant();
304 }
305
306 if (role == Qt::DisplayRole) {
307 SensorTreeItem *item = static_cast<SensorTreeItem *>(index.internalPointer());
308
309 if (d->sensorInfos.contains(item)) {
310 auto info = d->sensorInfos.value(item);
311 const QString &unit = Formatter::symbol(info.unit);
312
313 if (!unit.isEmpty()) {
314 return i18nc("Name (unit)", "%1 (%2)", info.name, unit);
315 }
316
317 return info.name;
318 }
319
320 return d->m_sensorGroup->segmentNameForRegEx(item->segment);
321 // Only leaf nodes are valid sensors
322 } else if (role == SensorId) {
323 if (rowCount(index)) {
324 return QString();
325 } else {
326 return d->sensorId(index);
327 }
328 }
329
330 return QVariant();
331}
332
333QMimeData *SensorTreeModel::mimeData(const QModelIndexList &indexes) const
334{
335 QMimeData *mimeData = new QMimeData();
336
337 if (indexes.count() != 1) {
338 return mimeData;
339 }
340
341 const QModelIndex &index = indexes.at(0);
342
343 if (!checkIndex(index, CheckIndexOption::IndexIsValid)) {
344 return mimeData;
345 }
346
347 if (rowCount(index)) {
348 return mimeData;
349 }
350
351 mimeData->setData(QStringLiteral("application/x-ksysguard"), d->sensorId(index).toUtf8());
352
353 return mimeData;
354}
355
356Qt::ItemFlags SensorTreeModel::flags(const QModelIndex &index) const
357{
358 if (!checkIndex(index, CheckIndexOption::IndexIsValid)) {
359 return Qt::NoItemFlags;
360 }
361
362 if (!rowCount(index)) {
364 }
365
366 return Qt::ItemIsEnabled;
367}
368
369int SensorTreeModel::rowCount(const QModelIndex &parent) const
370{
371 if (parent.isValid()) {
372 if (!checkIndex(parent, CheckIndexOption::IndexIsValid)) {
373 return 0;
374 }
375
376 const SensorTreeItem *item = static_cast<SensorTreeItem *>(parent.internalPointer());
377 return item->children.size();
378 }
379
380 return d->rootItem->children.size();
381}
382
383int SensorTreeModel::columnCount(const QModelIndex &parent) const
384{
385 Q_UNUSED(parent)
386
387 return 1;
388}
389
390QModelIndex SensorTreeModel::index(int row, int column, const QModelIndex &parent) const
391{
392 SensorTreeItem *parentItem = d->rootItem;
393
394 if (parent.isValid()) {
395 if (parent.model() != this) {
396 return QModelIndex();
397 }
398
399 parentItem = static_cast<SensorTreeItem *>(parent.internalPointer());
400 }
401
402 if (row < 0 || row >= int(parentItem->children.size())) {
403 return QModelIndex();
404 }
405
406 if (column < 0) {
407 return QModelIndex();
408 }
409
410 return createIndex(row, column, parentItem->itemAt(row));
411}
412
414{
415 if (!checkIndex(index, CheckIndexOption::IndexIsValid | CheckIndexOption::DoNotUseParent)) {
416 return QModelIndex();
417 }
418
419 if (index.column() > 0) {
420 return QModelIndex();
421 }
422
423 const SensorTreeItem *item = static_cast<SensorTreeItem *>(index.internalPointer());
424 SensorTreeItem *parentItem = item->parent;
425
426 if (parentItem == d->rootItem) {
427 return QModelIndex();
428 }
429
430 return createIndex(parentItem->parent->indexOf(parentItem->segment), 0, parentItem);
431}
432
433void SensorTreeModel::init()
434{
435 auto query = new SensorQuery{QString(), this};
436 connect(query, &SensorQuery::finished, [query, this]() {
437 query->deleteLater();
438 const auto result = query->result();
440 for (auto pair : result) {
441 d->addSensor(pair.first, pair.second);
442 }
444 });
445 query->execute();
446}
447
448void KSysGuard::SensorTreeModel::onSensorAdded(const QString &sensor)
449{
450 SensorDaemonInterface::instance()->requestMetaData(sensor);
451}
452
453void KSysGuard::SensorTreeModel::onSensorRemoved(const QString &sensor)
454{
455 d->removeSensor(sensor);
456}
457
458void KSysGuard::SensorTreeModel::onMetaDataChanged(const QString &sensorId, const SensorInfo &info)
459{
460 auto item = d->find(sensorId);
461 if (!item) {
462 d->addSensor(sensorId, info);
463 } else {
464 d->sensorInfos[item] = info;
465
466 auto parentItem = item->parent;
467 if (!parentItem) {
468 return;
469 }
470
471 auto parentIndex = QModelIndex{};
472 if (parentItem != d->rootItem) {
473 parentIndex = createIndex(parentItem->parent->indexOf(parentItem->segment), 0, parentItem);
474 }
475
476 auto itemIndex = index(parentItem->indexOf(item->segment), 0, parentIndex);
477 Q_EMIT dataChanged(itemIndex, itemIndex);
478 }
479}
static QString symbol(Unit unit)
Returns a symbol that corresponds to the given unit.
An object to query the daemon for a list of sensors and their metadata.
Definition SensorQuery.h:26
A model representing a tree of sensors that are available from the daemon.
QString i18nc(const char *context, const char *text, const TYPE &arg...)
QString i18n(const char *text, const TYPE &arg...)
KSERVICE_EXPORT KService::List query(FilterFunc filterFunc)
KGuiItem remove()
const QList< QKeySequence > & find()
QCA_EXPORT void init()
void beginInsertRows(const QModelIndex &parent, int first, int last)
bool checkIndex(const QModelIndex &index, CheckIndexOptions options) const const
QModelIndex createIndex(int row, int column, const void *ptr) const const
virtual QHash< int, QByteArray > roleNames() const const
bool contains(const Key &key) const const
iterator insert(const Key &key, const T &value)
const_reference at(qsizetype i) const const
qsizetype count() const const
void prepend(parameter_type value)
const char * key(int index) const const
int keyCount() const const
int value(int index) const const
QMetaEnum enumerator(int index) const const
void setData(const QString &mimeType, const QByteArray &data)
int column() const const
void * internalPointer() const const
const QObjectList & children() const const
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
virtual const QMetaObject * metaObject() const const
QObject * parent() const const
bool isEmpty() const const
QString & remove(QChar ch, Qt::CaseSensitivity cs)
QStringList split(QChar sep, Qt::SplitBehavior behavior, Qt::CaseSensitivity cs) const const
QString join(QChar separator) const const
CaseInsensitive
DisplayRole
typedef ItemFlags
Orientation
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:21:23 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.