File indexing completed on 2024-04-28 16:49:54
0001 /* 0002 SPDX-FileCopyrightText: 2020 David Edmundson <davidedmundson@kde.org> 0003 0004 SPDX-License-Identifier: LGPL-2.0-or-later 0005 */ 0006 0007 #include "process_data_model.h" 0008 #include "formatter.h" 0009 0010 #include "processcore/extended_process_list.h" 0011 #include "processcore/process.h" 0012 #include "processcore/process_attribute.h" 0013 #include "processcore/process_attribute_model.h" 0014 #include "processcore/process_data_provider.h" 0015 0016 #include <QMetaEnum> 0017 #include <QTimer> 0018 0019 using namespace KSysGuard; 0020 0021 class Q_DECL_HIDDEN KSysGuard::ProcessDataModel::Private 0022 { 0023 public: 0024 Private(ProcessDataModel *q); 0025 void beginInsertRow(KSysGuard::Process *parent); 0026 void endInsertRow(); 0027 void beginMoveProcess(KSysGuard::Process *process, KSysGuard::Process *new_parent); 0028 void endMoveProcess(); 0029 void beginRemoveRow(KSysGuard::Process *process); 0030 void endRemoveRow(); 0031 0032 void update(); 0033 QModelIndex getQModelIndex(Process *process, int column) const; 0034 0035 ProcessDataModel *q; 0036 KSysGuard::Process *m_rootProcess; 0037 QSharedPointer<ExtendedProcesses> m_processes; 0038 QTimer *m_timer; 0039 ProcessAttributeModel *m_attributeModel = nullptr; 0040 const int m_updateInterval = 2000; 0041 bool m_flatList = true; 0042 KSysGuard::Process *m_removingRowFor = nullptr; 0043 0044 QHash<QString, KSysGuard::ProcessAttribute *> m_availableAttributes; 0045 QVector<KSysGuard::ProcessAttribute *> m_enabledAttributes; 0046 }; 0047 0048 ProcessDataModel::ProcessDataModel(QObject *parent) 0049 : QAbstractItemModel(parent) 0050 , d(new ProcessDataModel::Private(this)) 0051 { 0052 } 0053 0054 ProcessDataModel::~ProcessDataModel() 0055 { 0056 } 0057 0058 ProcessDataModel::Private::Private(ProcessDataModel *_q) 0059 : q(_q) 0060 , m_processes(KSysGuard::ExtendedProcesses::instance()) 0061 , m_timer(new QTimer(_q)) 0062 { 0063 m_rootProcess = m_processes->getProcess(-1); 0064 connect(m_processes.get(), &KSysGuard::Processes::beginAddProcess, q, [this](KSysGuard::Process *process) { 0065 beginInsertRow(process); 0066 }); 0067 connect(m_processes.get(), &KSysGuard::Processes::endAddProcess, q, [this]() { 0068 endInsertRow(); 0069 }); 0070 connect(m_processes.get(), &KSysGuard::Processes::beginMoveProcess, q, [this](KSysGuard::Process *process, KSysGuard::Process *new_parent) { 0071 beginMoveProcess(process, new_parent); 0072 }); 0073 connect(m_processes.get(), &KSysGuard::Processes::endMoveProcess, q, [this]() { 0074 endMoveProcess(); 0075 }); 0076 connect(m_processes.get(), &KSysGuard::Processes::beginRemoveProcess, q, [this](KSysGuard::Process *process) { 0077 beginRemoveRow(process); 0078 }); 0079 connect(m_processes.get(), &KSysGuard::Processes::endRemoveProcess, q, [this]() { 0080 endRemoveRow(); 0081 }); 0082 0083 const auto attributes = m_processes->attributes(); 0084 m_availableAttributes.reserve(attributes.count()); 0085 for (auto attr : attributes) { 0086 m_availableAttributes[attr->id()] = attr; 0087 } 0088 0089 connect(m_timer, &QTimer::timeout, q, [this]() { 0090 update(); 0091 }); 0092 m_timer->setInterval(m_updateInterval); 0093 m_timer->start(); 0094 } 0095 0096 QVariant ProcessDataModel::data(const QModelIndex &index, int role) const 0097 { 0098 if (!checkIndex(index, QAbstractItemModel::CheckIndexOption::IndexIsValid)) { 0099 return QVariant(); 0100 } 0101 0102 const int attr = index.column(); 0103 auto attribute = d->m_enabledAttributes[attr]; 0104 switch (role) { 0105 case Qt::DisplayRole: 0106 case FormattedValue: { 0107 KSysGuard::Process *process = reinterpret_cast<KSysGuard::Process *>(index.internalPointer()); 0108 const QVariant value = attribute->data(process); 0109 return KSysGuard::Formatter::formatValue(value, attribute->unit()); 0110 } 0111 case Value: { 0112 KSysGuard::Process *process = reinterpret_cast<KSysGuard::Process *>(index.internalPointer()); 0113 const QVariant value = attribute->data(process); 0114 return value; 0115 } 0116 case Attribute: { 0117 return attribute->id(); 0118 } 0119 case Minimum: { 0120 return attribute->min(); 0121 } 0122 case Maximum: { 0123 return attribute->max(); 0124 } 0125 case ShortName: { 0126 if (!attribute->shortName().isEmpty()) { 0127 return attribute->shortName(); 0128 } 0129 return attribute->name(); 0130 } 0131 case Name: { 0132 return attribute->name(); 0133 } 0134 case Unit: { 0135 return attribute->unit(); 0136 } 0137 case UpdateInterval: { 0138 return d->m_updateInterval; 0139 } 0140 } 0141 return QVariant(); 0142 } 0143 0144 int ProcessDataModel::rowCount(const QModelIndex &parent) const 0145 { 0146 if (d->m_flatList) { 0147 if (parent.isValid()) { 0148 return 0; 0149 } else { 0150 return d->m_processes->processCount(); 0151 } 0152 } 0153 0154 if (!parent.isValid()) { 0155 return d->m_rootProcess->children().count(); 0156 } else if (parent.column() != 0) { 0157 return 0; 0158 } 0159 0160 KSysGuard::Process *proc = reinterpret_cast<KSysGuard::Process *>(parent.internalPointer()); 0161 Q_ASSERT(proc); 0162 return proc->children().count(); 0163 } 0164 0165 QModelIndex ProcessDataModel::parent(const QModelIndex &index) const 0166 { 0167 Q_UNUSED(index) 0168 if (d->m_flatList || !index.isValid()) { 0169 return QModelIndex(); 0170 } 0171 0172 KSysGuard::Process *proc = reinterpret_cast<KSysGuard::Process *>(index.internalPointer()); 0173 Q_ASSERT(proc); 0174 return d->getQModelIndex(proc->parent(), 0); 0175 } 0176 0177 QStringList ProcessDataModel::availableAttributes() const 0178 { 0179 return d->m_availableAttributes.keys(); 0180 } 0181 0182 QStringList ProcessDataModel::enabledAttributes() const 0183 { 0184 QStringList rc; 0185 rc.reserve(d->m_enabledAttributes.size()); 0186 for (auto attr : d->m_enabledAttributes) { 0187 rc << attr->id(); 0188 } 0189 return rc; 0190 } 0191 0192 void ProcessDataModel::setEnabledAttributes(const QStringList &enabledAttributes) 0193 { 0194 beginResetModel(); 0195 0196 QVector<ProcessAttribute *> unusedAttributes = d->m_enabledAttributes; 0197 d->m_enabledAttributes.clear(); 0198 0199 for (auto attributeId : enabledAttributes) { 0200 auto attribute = d->m_availableAttributes[attributeId]; 0201 if (!attribute) { 0202 continue; 0203 } 0204 unusedAttributes.removeOne(attribute); 0205 d->m_enabledAttributes << attribute; 0206 int columnIndex = d->m_enabledAttributes.count() - 1; 0207 0208 // reconnect as using the columnIndex in the lambda makes everything super fast 0209 disconnect(attribute, &KSysGuard::ProcessAttribute::dataChanged, this, nullptr); 0210 connect(attribute, &KSysGuard::ProcessAttribute::dataChanged, this, [this, columnIndex](KSysGuard::Process *process) { 0211 if (process->pid() != -1) { 0212 const QModelIndex index = d->getQModelIndex(process, columnIndex); 0213 if (index.isValid() && process != d->m_removingRowFor) { 0214 Q_EMIT dataChanged(index, index); 0215 } 0216 } 0217 }); 0218 } 0219 0220 for (auto *unusedAttribute : std::as_const(unusedAttributes)) { 0221 disconnect(unusedAttribute, &KSysGuard::ProcessAttribute::dataChanged, this, nullptr); 0222 } 0223 0224 d->update(); 0225 endResetModel(); 0226 0227 Q_EMIT enabledAttributesChanged(); 0228 } 0229 0230 bool ProcessDataModel::enabled() const 0231 { 0232 return d->m_timer->isActive(); 0233 } 0234 0235 void ProcessDataModel::setEnabled(bool newEnabled) 0236 { 0237 if (newEnabled == d->m_timer->isActive()) { 0238 return; 0239 } 0240 0241 if (newEnabled) { 0242 d->m_timer->start(); 0243 } else { 0244 d->m_timer->stop(); 0245 } 0246 0247 Q_EMIT enabledChanged(); 0248 } 0249 0250 bool ProcessDataModel::flatList() const 0251 { 0252 return d->m_flatList; 0253 } 0254 0255 void ProcessDataModel::setFlatList(bool flat) 0256 { 0257 if (d->m_flatList == flat) { 0258 return; 0259 } 0260 beginResetModel(); 0261 // NOTE: layoutAboutToBeChanged doesn't play well with TableView delegate recycling 0262 // Q_EMIT layoutAboutToBeChanged(); 0263 0264 d->m_flatList = flat; 0265 endResetModel(); 0266 Q_EMIT flatListChanged(); 0267 } 0268 0269 QModelIndex ProcessDataModel::index(int row, int column, const QModelIndex &parent) const 0270 { 0271 if (row < 0 || column < 0 || column >= columnCount()) { 0272 return QModelIndex(); 0273 } 0274 0275 // Flat List 0276 if (d->m_flatList) { 0277 if (parent.isValid()) { 0278 return QModelIndex(); 0279 } 0280 0281 if (d->m_processes->processCount() <= row) { 0282 return QModelIndex(); 0283 } 0284 0285 return createIndex(row, column, d->m_processes->getAllProcesses().at(row)); 0286 } 0287 0288 // Tree mode 0289 KSysGuard::Process *process; 0290 0291 if (parent.isValid()) { 0292 process = reinterpret_cast<KSysGuard::Process *>(parent.internalPointer()); 0293 } else { 0294 process = d->m_rootProcess; 0295 } 0296 0297 if (row >= process->children().count()) { 0298 return QModelIndex(); 0299 } else { 0300 return createIndex(row, column, process->children()[row]); 0301 } 0302 } 0303 0304 void ProcessDataModel::Private::beginInsertRow(KSysGuard::Process *process) 0305 { 0306 Q_ASSERT(process); 0307 0308 // Flat List 0309 if (m_flatList) { 0310 const int row = m_processes->processCount(); 0311 q->beginInsertRows(QModelIndex(), row, row); 0312 return; 0313 } 0314 0315 // Tree mode 0316 const int row = process->parent()->children().count(); 0317 q->beginInsertRows(getQModelIndex(process->parent(), 0), row, row); 0318 } 0319 0320 void ProcessDataModel::Private::endInsertRow() 0321 { 0322 q->endInsertRows(); 0323 } 0324 0325 void ProcessDataModel::Private::beginRemoveRow(KSysGuard::Process *process) 0326 { 0327 Q_ASSERT(process); 0328 Q_ASSERT(!m_removingRowFor); 0329 0330 m_removingRowFor = process; 0331 int row = process->parent()->children().indexOf(process); 0332 Q_ASSERT(row >= 0); 0333 if (m_flatList) { 0334 q->beginRemoveRows(QModelIndex(), process->index(), process->index()); 0335 } else { 0336 q->beginRemoveRows(getQModelIndex(process->parent(), 0), row, row); 0337 } 0338 } 0339 0340 void ProcessDataModel::Private::endRemoveRow() 0341 { 0342 m_removingRowFor = nullptr; 0343 q->endRemoveRows(); 0344 } 0345 0346 void ProcessDataModel::Private::beginMoveProcess(KSysGuard::Process *process, KSysGuard::Process *new_parent) 0347 { 0348 if (m_flatList) 0349 return; // We don't need to move processes when in simple mode 0350 0351 int current_row = process->parent()->children().indexOf(process); 0352 Q_ASSERT(current_row != -1); 0353 int new_row = new_parent->children().count(); 0354 QModelIndex sourceParent = getQModelIndex(process->parent(), 0); 0355 QModelIndex destinationParent = getQModelIndex(new_parent, 0); 0356 q->beginMoveRows(sourceParent, current_row, current_row, destinationParent, new_row); 0357 } 0358 0359 void ProcessDataModel::Private::endMoveProcess() 0360 { 0361 if (m_flatList) 0362 return; // We don't need to move processes when in simple mode 0363 0364 q->endMoveRows(); 0365 } 0366 0367 void ProcessDataModel::Private::update() 0368 { 0369 Processes::UpdateFlags flags; 0370 for (auto attribute : std::as_const(m_enabledAttributes)) { 0371 flags |= attribute->requiredUpdateFlags(); 0372 } 0373 0374 m_processes->updateAllProcesses(m_updateInterval, flags); 0375 } 0376 0377 QModelIndex ProcessDataModel::Private::getQModelIndex(KSysGuard::Process *process, int column) const 0378 { 0379 Q_ASSERT(process); 0380 if (process->pid() == -1) 0381 return QModelIndex(); // pid -1 is our fake process meaning the very root (never drawn). To represent that, we return QModelIndex() which also means 0382 // the top element 0383 0384 int row; 0385 0386 if (m_flatList) { 0387 row = process->index(); 0388 } else { 0389 row = process->parent()->children().indexOf(process); 0390 } 0391 0392 Q_ASSERT(row != -1); 0393 return q->createIndex(row, column, process); 0394 } 0395 0396 ProcessAttributeModel *ProcessDataModel::attributesModel() 0397 { 0398 // lazy load 0399 if (!d->m_attributeModel) { 0400 d->m_attributeModel = new KSysGuard::ProcessAttributeModel(d->m_availableAttributes.values().toVector(), this); 0401 } 0402 return d->m_attributeModel; 0403 } 0404 0405 int ProcessDataModel::columnCount(const QModelIndex &parent) const 0406 { 0407 return d->m_enabledAttributes.count(); 0408 } 0409 0410 QHash<int, QByteArray> ProcessDataModel::roleNames() const 0411 { 0412 QHash<int, QByteArray> roles = QAbstractItemModel::roleNames(); 0413 0414 const QMetaEnum e = QMetaEnum::fromType<AdditionalRoles>(); 0415 0416 for (int i = 0; i < e.keyCount(); ++i) { 0417 roles.insert(e.value(i), e.key(i)); 0418 } 0419 0420 return roles; 0421 } 0422 0423 QVariant ProcessDataModel::headerData(int section, Qt::Orientation orientation, int role) const 0424 { 0425 if (orientation == Qt::Vertical) { 0426 return QVariant(); 0427 } 0428 0429 if (section < 0 || section >= columnCount()) { 0430 return QVariant(); 0431 } 0432 0433 auto attribute = d->m_enabledAttributes[section]; 0434 0435 switch (role) { 0436 case Qt::DisplayRole: 0437 case ShortName: { 0438 if (!attribute->shortName().isEmpty()) { 0439 return attribute->shortName(); 0440 } 0441 return attribute->name(); 0442 } 0443 case Name: 0444 return attribute->name(); 0445 case Value: 0446 case Attribute: { 0447 return attribute->id(); 0448 } 0449 case Unit: { 0450 auto attribute = d->m_enabledAttributes[section]; 0451 return attribute->unit(); 0452 } 0453 case Minimum: { 0454 return attribute->min(); 0455 } 0456 case Maximum: { 0457 return attribute->max(); 0458 } 0459 case UpdateInterval: { 0460 return d->m_updateInterval; 0461 } 0462 default: 0463 break; 0464 } 0465 0466 return QVariant(); 0467 }