File indexing completed on 2024-04-28 05:31:38

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     QList<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     QList<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     endResetModel();
0225     d->update();
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 
0352     int current_row = process->parent()->children().indexOf(process);
0353     Q_ASSERT(current_row != -1);
0354     int new_row = new_parent->children().count();
0355     QModelIndex sourceParent = getQModelIndex(process->parent(), 0);
0356     QModelIndex destinationParent = getQModelIndex(new_parent, 0);
0357     q->beginMoveRows(sourceParent, current_row, current_row, destinationParent, new_row);
0358 }
0359 
0360 void ProcessDataModel::Private::endMoveProcess()
0361 {
0362     if (m_flatList) {
0363         return; // We don't need to move processes when in simple mode
0364     }
0365 
0366     q->endMoveRows();
0367 }
0368 
0369 void ProcessDataModel::Private::update()
0370 {
0371     Processes::UpdateFlags flags;
0372     for (auto attribute : std::as_const(m_enabledAttributes)) {
0373         flags |= attribute->requiredUpdateFlags();
0374     }
0375 
0376     m_processes->updateAllProcesses(m_updateInterval, flags);
0377 }
0378 
0379 QModelIndex ProcessDataModel::Private::getQModelIndex(KSysGuard::Process *process, int column) const
0380 {
0381     Q_ASSERT(process);
0382     if (process->pid() == -1) {
0383         return QModelIndex(); // pid -1 is our fake process meaning the very root (never drawn).  To represent that, we return QModelIndex() which also means
0384                               // the top element
0385     }
0386 
0387     int row;
0388 
0389     if (m_flatList) {
0390         row = process->index();
0391     } else {
0392         row = process->parent()->children().indexOf(process);
0393     }
0394 
0395     Q_ASSERT(row != -1);
0396     return q->createIndex(row, column, process);
0397 }
0398 
0399 ProcessAttributeModel *ProcessDataModel::attributesModel()
0400 {
0401     // lazy load
0402     if (!d->m_attributeModel) {
0403         d->m_attributeModel = new KSysGuard::ProcessAttributeModel(d->m_availableAttributes.values().toVector(), this);
0404     }
0405     return d->m_attributeModel;
0406 }
0407 
0408 int ProcessDataModel::columnCount(const QModelIndex &parent) const
0409 {
0410     Q_UNUSED(parent);
0411     return d->m_enabledAttributes.count();
0412 }
0413 
0414 QHash<int, QByteArray> ProcessDataModel::roleNames() const
0415 {
0416     QHash<int, QByteArray> roles = QAbstractItemModel::roleNames();
0417 
0418     const QMetaEnum e = QMetaEnum::fromType<AdditionalRoles>();
0419 
0420     for (int i = 0; i < e.keyCount(); ++i) {
0421         roles.insert(e.value(i), e.key(i));
0422     }
0423 
0424     return roles;
0425 }
0426 
0427 QVariant ProcessDataModel::headerData(int section, Qt::Orientation orientation, int role) const
0428 {
0429     if (orientation == Qt::Vertical) {
0430         return QVariant();
0431     }
0432 
0433     if (section < 0 || section >= columnCount()) {
0434         return QVariant();
0435     }
0436 
0437     auto attribute = d->m_enabledAttributes[section];
0438 
0439     switch (role) {
0440     case Qt::DisplayRole:
0441     case ShortName: {
0442         if (!attribute->shortName().isEmpty()) {
0443             return attribute->shortName();
0444         }
0445         return attribute->name();
0446     }
0447     case Name:
0448         return attribute->name();
0449     case Value:
0450     case Attribute: {
0451         return attribute->id();
0452     }
0453     case Unit: {
0454         auto attribute = d->m_enabledAttributes[section];
0455         return attribute->unit();
0456     }
0457     case Minimum: {
0458         return attribute->min();
0459     }
0460     case Maximum: {
0461         return attribute->max();
0462     }
0463     case UpdateInterval: {
0464         return d->m_updateInterval;
0465     }
0466     default:
0467         break;
0468     }
0469 
0470     return QVariant();
0471 }